samagotchi 0.134.0 → 0.136.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.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -1
  3. data/bin/chi +2 -3
  4. data/docs/architecture.md +6 -3
  5. data/docs/cli.md +43 -38
  6. data/docs/configuration.md +11 -9
  7. data/docs/guardrails.md +53 -9
  8. data/docs/internals/engine.md +6 -3
  9. data/docs/internals/participants.md +1 -1
  10. data/docs/internals/prompt-caching.md +1 -1
  11. data/docs/internals/web-frontend.md +263 -0
  12. data/docs/internals/working-set.md +2 -2
  13. data/docs/sessions.md +5 -3
  14. data/docs/testing.md +36 -6
  15. data/lib/samagotchi/bridge.rb +11 -6
  16. data/lib/samagotchi/bridge_client.rb +8 -5
  17. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  18. data/lib/samagotchi/config.rb +0 -2
  19. data/lib/samagotchi/engine.rb +69 -12
  20. data/lib/samagotchi/guardrail_wiring.rb +8 -6
  21. data/lib/samagotchi/guardrails/approval.rb +41 -5
  22. data/lib/samagotchi/guardrails/implicit_writers.rb +19 -4
  23. data/lib/samagotchi/guardrails/pair_boundary.rb +5 -1
  24. data/lib/samagotchi/guardrails/pair_command.rb +387 -78
  25. data/lib/samagotchi/guardrails/parent_approvals.rb +12 -15
  26. data/lib/samagotchi/guardrails/read_only_shell.rb +24 -2
  27. data/lib/samagotchi/guardrails/shared_git.rb +1 -1
  28. data/lib/samagotchi/guardrails/shell_git_dirs.rb +1 -1
  29. data/lib/samagotchi/guardrails/shell_lex.rb +22 -0
  30. data/lib/samagotchi/guardrails_command.rb +13 -10
  31. data/lib/samagotchi/llm/chat_loop.rb +4 -3
  32. data/lib/samagotchi/message_parts.rb +1 -1
  33. data/lib/samagotchi/revise_anchor.rb +33 -0
  34. data/lib/samagotchi/session_inbox.rb +20 -12
  35. data/lib/samagotchi/session_manager.rb +13 -11
  36. data/lib/samagotchi/system_one/approval_question.rb +21 -16
  37. data/lib/samagotchi/system_one/self_protect.rb +13 -15
  38. data/lib/samagotchi/tools/delegate_relay.rb +6 -5
  39. data/lib/samagotchi/tools/delegate_wait.rb +5 -5
  40. data/lib/samagotchi/tools/execute.rb +6 -6
  41. data/lib/samagotchi/tools/output_guardrails.rb +9 -2
  42. data/lib/samagotchi/tools/read.rb +5 -5
  43. data/lib/samagotchi/turn_anchors.rb +49 -0
  44. data/lib/samagotchi/verify_anchor.rb +5 -5
  45. data/lib/samagotchi/version.rb +1 -1
  46. data/lib/samagotchi/web/app.rb +6 -18
  47. data/lib/samagotchi/web/message_display.rb +33 -1
  48. data/lib/samagotchi/web/participant_access.rb +2 -2
  49. data/lib/samagotchi/web/participant_view.rb +8 -0
  50. data/lib/samagotchi/web/public/activity.js +1 -1
  51. data/lib/samagotchi/web/public/annotations.js +1 -1
  52. data/lib/samagotchi/web/public/answer_fold.js +100 -31
  53. data/lib/samagotchi/web/public/app.js +451 -367
  54. data/lib/samagotchi/web/public/bubbles.js +30 -10
  55. data/lib/samagotchi/web/public/cards_place.js +4 -2
  56. data/lib/samagotchi/web/public/chip_popover.js +2 -2
  57. data/lib/samagotchi/web/public/composer.css +8 -0
  58. data/lib/samagotchi/web/public/context_chips.js +9 -6
  59. data/lib/samagotchi/web/public/data.js +4 -2
  60. data/lib/samagotchi/web/public/dock_reserve.js +13 -15
  61. data/lib/samagotchi/web/public/feature_gates.js +33 -0
  62. data/lib/samagotchi/web/public/history_reveal.js +10 -6
  63. data/lib/samagotchi/web/public/index.html +105 -53
  64. data/lib/samagotchi/web/public/info_bar.js +11 -12
  65. data/lib/samagotchi/web/public/info_bar_model.js +8 -10
  66. data/lib/samagotchi/web/public/latest.js +46 -0
  67. data/lib/samagotchi/web/public/level_chip.js +33 -95
  68. data/lib/samagotchi/web/public/lists_view.js +15 -13
  69. data/lib/samagotchi/web/public/live_timing.js +2 -0
  70. data/lib/samagotchi/web/public/llm_context_chip.js +13 -144
  71. data/lib/samagotchi/web/public/markdown_place.js +1 -1
  72. data/lib/samagotchi/web/public/method_api.js +14 -0
  73. data/lib/samagotchi/web/public/notifications.js +4 -11
  74. data/lib/samagotchi/web/public/participant.js +11 -6
  75. data/lib/samagotchi/web/public/participant_names.js +1 -1
  76. data/lib/samagotchi/web/public/participants_panel.js +4 -2
  77. data/lib/samagotchi/web/public/project_picker.js +18 -22
  78. data/lib/samagotchi/web/public/question_cards.js +2 -2
  79. data/lib/samagotchi/web/public/reasoning_history_chip.js +8 -34
  80. data/lib/samagotchi/web/public/restore.js +2 -0
  81. data/lib/samagotchi/web/public/revise_stack.js +378 -0
  82. data/lib/samagotchi/web/public/scope.js +12 -20
  83. data/lib/samagotchi/web/public/send_turn.js +29 -16
  84. data/lib/samagotchi/web/public/session_actions.js +15 -14
  85. data/lib/samagotchi/web/public/session_fields.js +152 -0
  86. data/lib/samagotchi/web/public/session_payload.js +35 -0
  87. data/lib/samagotchi/web/public/session_state.js +9 -5
  88. data/lib/samagotchi/web/public/session_store.js +191 -0
  89. data/lib/samagotchi/web/public/settings_chip.js +212 -0
  90. data/lib/samagotchi/web/public/settings_panel.js +121 -0
  91. data/lib/samagotchi/web/public/shell_command.js +26 -0
  92. data/lib/samagotchi/web/public/stage_model.js +4 -4
  93. data/lib/samagotchi/web/public/stage_view.js +11 -16
  94. data/lib/samagotchi/web/public/start_settings.js +25 -88
  95. data/lib/samagotchi/web/public/stream_handlers.js +88 -90
  96. data/lib/samagotchi/web/public/task_stop.js +1 -1
  97. data/lib/samagotchi/web/public/thinking_chip.js +7 -45
  98. data/lib/samagotchi/web/public/time_gaps.js +5 -2
  99. data/lib/samagotchi/web/public/timing.js +15 -5
  100. data/lib/samagotchi/web/public/toolkit_chip.js +37 -21
  101. data/lib/samagotchi/web/public/{turn_view.js → turn_block.js} +33 -93
  102. data/lib/samagotchi/web/public/turn_events.js +3 -3
  103. data/lib/samagotchi/web/public/turn_html.js +20 -16
  104. data/lib/samagotchi/web/public/turn_model.js +6 -6
  105. data/lib/samagotchi/web/public/verdict_fresh.js +2 -0
  106. data/lib/samagotchi/web/public/working_set_view.js +3 -1
  107. data/lib/samagotchi/web/server.rb +4 -4
  108. data/lib/samagotchi/web/session_control.rb +2 -2
  109. data/lib/samagotchi/web/session_create.rb +1 -1
  110. data/lib/samagotchi/web/session_show.rb +6 -3
  111. data/lib/samagotchi/worker.rb +9 -9
  112. data/lib/samagotchi/worker_inbound.rb +6 -5
  113. metadata +15 -3
  114. data/lib/samagotchi/web/public/hold.js +0 -78
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1400c2aa3cceb5a84b6aa0d5610021b5e5893415afa37f98e8c99efe291de436
4
- data.tar.gz: eda23134194381fd10460ba0b026ae10aa5a8aaf674fbc9dc4504359b1acb86d
3
+ metadata.gz: 12638807efa522a91eb43ef7934f29aa0a4473d48da39897d6244b755eafdfdc
4
+ data.tar.gz: e4dbe2698ef9517d9ccbacbd6e216720d8f0ce8e75bef8974f05951b7e1d2bfd
5
5
  SHA512:
6
- metadata.gz: 7029cb713c02b4c3099ade377bc57618ec6467c2a1f04a4b21c7ebd3fbb6e34705e1ad54fe547672becdc6553824b785c96222e310690fd707a12c0fb057e852
7
- data.tar.gz: e7400f091254ba9ffdf04f14160e8d6816cbe264016f8be8558c9da091edcc33bf34ed93652c83b6647be728a349b31631db49fc26e483280deda75a7658e5e3
6
+ metadata.gz: 605c9b52f0d2b949e2f875ef939ba01401def71fb879ef78affe5e97b9f04ba2c761e48e17c801525617a87cfb4f7bed2d5b64045080f6408974e97a7e8f3f07
7
+ data.tar.gz: c8d922fa4080f5b098ad953859506cfded71dc2be9038f836e88f53c288abca31265df0acdda01d3b945e43107d5c41b6ab9d5e2c3fa9196d4be284654e9d8e9
data/CHANGELOG.md CHANGED
@@ -7,6 +7,81 @@ 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.136.0] - 2026-10-30
11
+
12
+ ### Changed
13
+
14
+ - Web: an unfolded long answer's fold row is the answer's header: it keeps the first line and size, and its bar runs
15
+ on into the answer's. The recap's ▸/▾ matches the steps' and thinking's in place of the browser's triangle.
16
+ - Web: a long delegate report folds to one row, "delegate report · its first heading · N lines", where the report
17
+ sits; a click opens it and this browser remembers that. A report merged into a running turn gets its row on the
18
+ next reload.
19
+ - Web: a delegate report in a reloaded history renders as markdown (headings, lists, code), as answers do; Copy
20
+ still copies its text. A report that arrives live shows as plain text until the next reload.
21
+ - Web: the session bar's id copies itself on click; the copy chi --attach button is gone.
22
+ - Web: the session bar's think, reasoning and llm ctx chips are one ⚙ chip whose panel is the start page's:
23
+ segments, a pick applies at once; the chip names only what the session set.
24
+ - Web: the session bar's tools chip is a 🧰 chip after ⚙: 🧰 alone while the session is on the configured default
25
+ kit (its tooltip names it), "🧰 lean" on another.
26
+
27
+ ### Fixed
28
+
29
+ - Web: a `!cmd`'s output in a reloaded history is its command bubble alone: it no longer opens a turn that showed
30
+ the next turn's steps and "turn N" timing a second time.
31
+ - Web: on a phone, a labelled line in the history (a delegate report, a tui line, a participant's) puts its label
32
+ above the text instead of squeezing the text into a narrow column beside it.
33
+ - Web: a session's recap is the page's last line again: the last turn's check-in notice no longer lands under it.
34
+
35
+ Update with `chi update`, then restart `chi web` and the session workers (`chi sessions restart`). `chi home doctor`
36
+ says what else to do.
37
+
38
+ ## [0.135.0] - 2026-10-30
39
+
40
+ ### Added
41
+
42
+ - Web: Revise on a finished answer: your next message rewrites it, and once that turn completes the earlier version
43
+ folds in place to one stub row ("v1 · its first line · N lines — superseded by v2", a click unfolds it); the newest
44
+ answer's "v2 · 1 earlier ▾" chip lists the versions by their prompts. Stacks show on every owner's page, live and
45
+ after a reload.
46
+
47
+ ### Changed
48
+
49
+ - Guardrails: a file or string check (`test -f x`, `[ -d dir ]`, `[ "$a" = b ]`) counts as read-only, so
50
+ `test -f x && cat x` no longer asks where `cat x` alone wouldn't.
51
+
52
+ ### Removed
53
+
54
+ - Web: the turn view (`web.view: turn`, `chi web --web-view`, `SAMAGOTCHI_WEB_VIEW`, `?view=`): the stage is the only
55
+ view; finished turns still show as blocks of steps in the history. A config.yml that still sets `web.view` warns
56
+ about an unknown key; a chi web started with `--web-view` doesn't come back from an Update restart (start it again
57
+ without the flag).
58
+
59
+ ### Fixed
60
+
61
+ - Web: opening another session clears the previous one's init row, markdown tip and card actions at once; they no
62
+ longer stayed when the open failed.
63
+ - Web: a canceled turn's timing shows in the info bar right away, not at the next unrelated redraw.
64
+ - Web: the info bar follows a terminal taking the open session (its Continue and Stop, the title's ✎) at once, not at
65
+ the hub's next event.
66
+ - Web: Verify fresh on a verdict's card says it's unavailable as soon as the open session's worker goes, not at the
67
+ hub's next event.
68
+ - Web: a turn's end no longer shows the session idle while the next queued turn already runs (its late re-read wrote
69
+ the stored "idle" over "running").
70
+ - Web: a sign-in that lapsed behind an auth proxy is reported (the sign-in prompt) by the notification bell and the
71
+ push devices list too.
72
+ - Pair turns: a program is judged by its name in any case, as macOS's disk runs it: `GIT config …`, `CHI share`,
73
+ `NOHUP`, `PRINTENV` or `Bash -c` are refused like their lower-case names (System 1's memories exemption reads
74
+ `MAKE` or `RUBY x.rb` the same way). More spellings that hid a command are caught: fish's `--command S` and
75
+ `-C`/`--init-command S`, `PROMPT_COMMAND` and a `$(…)` kept in a value bash expands later (`PS4='$(…)' bash -x`),
76
+ the program behind `xcrun`, `arch`, `script`, `builtin`, `uv run`, `pnpm exec`, `bunx` and a wrapper's value option
77
+ (`env -u X`, `sudo -u root`), the command lines `env -S`, `ssh -o ProxyCommand=`, `hyperfine` and `parallel` run,
78
+ `php -B/-R/-E`, `perl -de`, `jruby`/`raku`/`julia -e`. git or chi behind a program chi doesn't know
79
+ (`some-runner git config …`, `chronic chi share x`) and a `$(…)` kept in a value now ask you (refused when what it
80
+ runs reads as git config or chi). `git add maintenance start` no longer asks you; `git lfs update` does.
81
+
82
+ Update with `chi update`, then restart `chi web` (without `--web-view`) and the session workers (`chi sessions
83
+ restart`). `chi home doctor` says what else to do.
84
+
10
85
  ## [0.134.0] - 2026-10-30
11
86
 
12
87
  ### Added
@@ -5143,7 +5218,9 @@ and long-lived sessions.
5143
5218
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
5144
5219
  hotkey panel that send selected text or the clipboard to your sessions.
5145
5220
 
5146
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.134.0...HEAD
5221
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.136.0...HEAD
5222
+ [0.136.0]: https://github.com/dm1try/samagotchi/compare/v0.135.0...v0.136.0
5223
+ [0.135.0]: https://github.com/dm1try/samagotchi/compare/v0.134.0...v0.135.0
5147
5224
  [0.134.0]: https://github.com/dm1try/samagotchi/compare/v0.133.0...v0.134.0
5148
5225
  [0.133.0]: https://github.com/dm1try/samagotchi/compare/v0.132.0...v0.133.0
5149
5226
  [0.132.0]: https://github.com/dm1try/samagotchi/compare/v0.131.0...v0.132.0
data/bin/chi CHANGED
@@ -234,7 +234,7 @@ if options[:dir]
234
234
  exit 2
235
235
  end
236
236
 
237
- # `chi web`: its flags (--port, --open, --web-view…) went through the parser above.
237
+ # `chi web`: its flags (--port, --open…) went through the parser above.
238
238
  options[:web] = true if web
239
239
 
240
240
  # The first interactive start of a new installed chi says what `chi update` would update.
@@ -253,9 +253,8 @@ if options[:web]
253
253
  port = options[:web_port] || Samagotchi::Config.get("web.port")
254
254
  markdown = options[:web_markdown]
255
255
  markdown = Samagotchi::Config.get("web.markdown") if markdown.nil?
256
- view = Samagotchi::Config.get("web.view")
257
256
  exit Samagotchi::Web::Server.launch(port: port, scope: options[:scope] || "project", open_browser: options[:web_open],
258
- markdown: markdown, view: view, new_token: options[:new_token], tunnel: options[:tunnel],
257
+ markdown: markdown, new_token: options[:new_token], tunnel: options[:tunnel],
259
258
  working_set: Samagotchi::Config.get("web.working_set"),
260
259
  annotate_presets: Samagotchi::Config.get("web.annotate_presets"),
261
260
  editor: Samagotchi::Config.get("web.editor"),
data/docs/architecture.md CHANGED
@@ -236,6 +236,9 @@ agent logic and can be used without any terminal rendering; the UI is a thin lay
236
236
  run's own prompts until their turns end; `#run` distinguishes an empty answer from a
237
237
  prompt this terminal removed before it ran (both exit 1), as well as chi's other outcomes.
238
238
  Without a capable terminal it falls back to `PlainSurface` (append-only).
239
+ - The browser's page (`lib/samagotchi/web/public/`: `app.js` for the owner, `participant.js` for a participant) is a
240
+ client of chi web: where its state lives, which redraw follows which write, its render idioms, stale-answer guards,
241
+ storage keys and streams are in [internals/web-frontend.md](internals/web-frontend.md).
239
242
 
240
243
  #### Using the core
241
244
 
@@ -532,16 +535,16 @@ The page's frequent reads of `GET /api/sessions/:id` stay light too:
532
535
  (`turn_model.js` `questionRefs`) before the card draws, so the stage's asking slot (`stage_slots.js`
533
536
  `drawAsking(text, refs)`) pairs them on its raw draw and again after its markdown swap; the card pairs its
534
537
  question text and its kept reply (`question_cards.js`); the step's closed `.gen-text` takes them
535
- (`turn_view.js`; on a reload its `data-answer-at`, the message's `display_refs`) and `linkTextRefs` pairs it at
538
+ (`turn_block.js`; on a reload its `data-answer-at`, the message's `display_refs`) and `linkTextRefs` pairs it at
536
539
  the turn's end and after a reload. A closed step's text is markdown too with `web.markdown` on: a reload's
537
- from its message's `html` (`turn_view.js` `textTemplate`), a live turn's from `POST /api/render` on its step's
540
+ from its message's `html` (`turn_block.js` `textTemplate`), a live turn's from `POST /api/render` on its step's
538
541
  first open after the turn's end (`markdown_place.js` `renderStepTexts`, which pairs its refs again after the
539
542
  swap); the text as written stays on it as `data-copy-source` (`stepText`, a reloaded card's kept reply). A file ref outside the
540
543
  session's folder is dropped, as a reload drops it: the folder `/tail` names, else (an older worker) the hub's
541
544
  session list entry's, else (refs to check, no hub entry) the session file's; with no folder known at all, every
542
545
  file ref is dropped rather than shown unchecked.
543
546
  A plugin tool's row refs (`refs`, `RowRefs`) ride its tool events and, on a reload, its part (saved as
544
- `tool_refs`); `turn_view.js` binds them to the row's title (`refs.js bindRowRefs`: the first on the title,
547
+ `tool_refs`); `turn_block.js` binds them to the row's title (`refs.js bindRowRefs`: the first on the title,
545
548
  the others in its menu).
546
549
  - `?timing=1`: the whole timing (`analytics.json` merged with the worker's `/state`) and
547
550
  `turn_count`, when the page's merge came up short.
data/docs/cli.md CHANGED
@@ -17,7 +17,6 @@
17
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
18
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
19
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
20
- - `chi web --web-view turn` — draw turns with the turn view (each turn as one block of steps, the running one at the bottom of the history) instead of the default stage view (the running turn pinned above the composer); `?view=stage|turn` on the page URL overrides it (see [Web views](#web-views))
21
20
  - `chi web --no-web-working-set` — turns the stage view's working set off (on by default; `--web-working-set` turns a config's `web.working_set: false` back on): what each turn brought into the model's context, in the stage and under each finished turn; `?working_set=1|0` on the page URL overrides it (see [The working set](#the-working-set))
22
21
  - `chi sessions list|stop|restart|archive|unarchive|delete|prune|clean|stats` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>`, `list --archived` the archived ones too (see [Sessions](sessions.md)). `stats ID` prints a session's cost, tokens and progress, and its own settings (`--set`; a `settings:` line), from a live worker's `GET stats`, else its saved `analytics.json` (`--format json` one object, with `settings`), without running a model turn. A usage error (an unknown subcommand or flag, a flag missing its value, `stop`, `restart` or `delete` with no ids, a bad `list --format` or `--scope`, a `--days`, `--keep` or `--limit` that isn't a whole number) exits 2; an unknown or refused session exits 1
23
22
  - `chi note [--source NAME] [-m TEXT] (ID|PREFIX)... | --all` — add a context note (TEXT or stdin) to sessions: background the model sees on its next turn; it starts no turn (see [Sessions: Context notes](sessions.md#context-notes))
@@ -708,9 +707,9 @@ back). Once the session has a model reply, the two that break the cache now
708
707
  the `--yes` line that applies them; `/llm-context reset --yes` works too.
709
708
  `/llm-context` alone answers mid-turn too; a change sent while a turn runs
710
709
  waits for the turn's end (see *Typing during a turn*). `/stats` shows
711
- the same values on its `llm context:` line. The web's info bar shows them as the
712
- `llm ctx` chip (`llm ctx stale,forget · session`), whose popover sets them
713
- through `/llm-context … --yes` (its form says what a change costs). See "LLM context: a session's own strategy" in
710
+ the same values on its `llm context:` line. The web's session bar names the
711
+ session's own in its ⚙ chip (`⚙ ctx stale,forget`), whose panel sets them
712
+ through `/llm-context … --yes`, one field a tap (its note says what a change costs). See "LLM context: a session's own strategy" in
714
713
  [configuration.md](configuration.md).
715
714
 
716
715
  `/working-set` lists the session's working set as the model has it (see [The
@@ -778,7 +777,7 @@ comes first; `/reasoning-history default` unsets it. It is saved and carried as
778
777
  or from `all` once an earlier reply has thinking rewrites the earlier replies;
779
778
  `drop` ↔ `turn` costs nothing. Under `turn` a step-limit Continue keeps the
780
779
  reasoning of the turn it continues. `/stats` shows it on its `reasoning history:`
781
- line, the web as the `reasoning` chip. See "Reasoning history" in
780
+ line, the web in the session bar's ⚙ panel (`⚙ reasoning all` once the session set its own). See "Reasoning history" in
782
781
  [configuration.md](configuration.md).
783
782
 
784
783
  `/toolkit` lists the tools the next turn declares (each with its estimated
@@ -1037,29 +1036,31 @@ the keys of sessions gone from the list are dropped when the list loads);
1037
1036
  other viewers and the participant page are not affected. A jump into a
1038
1037
  folded answer (a claim link, say) unfolds it for that page load.
1039
1038
 
1040
- ### Web views
1041
-
1042
- `web.view` picks how the page draws a turn: `stage` (the default: the
1043
- running turn pinned above the composer, below) or `turn` (each turn as one
1044
- block of steps in the history, the running one at the bottom, below).
1045
-
1046
- ```sh
1047
- chi web --web-view turn # stage is the default
1048
- ```
1049
-
1050
- The setting also supports `SAMAGOTCHI_WEB_VIEW=turn` or the global config:
1051
-
1052
- ```yaml
1053
- web:
1054
- view: turn
1055
- ```
1056
-
1057
- `?view=stage` or `?view=turn` on the page URL picks the view for that page
1058
- load, whatever the config says; the parameter is dropped when you switch
1059
- between the project and all-sessions views. The terminal UIs are not
1060
- affected.
1061
-
1062
- **The stage view** pins the running turn above the composer, in its own
1039
+ **Revise** rewrites an answer and stacks the versions. A finished turn's
1040
+ final answer has a Revise button on its bottom edge (on hover; always shown
1041
+ on a touch screen), unless it's a Verify turn's answer, a canceled or failed
1042
+ turn's partial answer, or an earlier version in a stack. Revise arms the
1043
+ composer with a chip, "Revising: ‹its first line› ✕"; your next message (say
1044
+ "cut it shorter") is sent as a Revise of that answer. ✕, emptying the box or
1045
+ opening another session lets go of it. When the Revise turn completes with an
1046
+ answer, the earlier version folds where it was into one stub row,
1047
+ "▸ v1 · “its first line” · 84 lines — superseded by v2": its prompt, its
1048
+ steps and its answer, all behind it (a warn card, a verdict card and the
1049
+ recap stay on view). Click the stub to unfold that version in place (kept per
1050
+ browser, with the folded answers); a time divider between two folded stubs
1051
+ moves into the later stub. The newest version has a chip, "v3 · 2 earlier ▾",
1052
+ listing every version by its prompt; pick one to jump to its stub. Revise
1053
+ again on the newest version adds to the stack. Stacks are session data (the
1054
+ stamp is on the Revise prompt, see [sessions.md](sessions.md), Revise
1055
+ stacks): every owner's page shows them, live and after a reload; the
1056
+ participant page doesn't draw them yet. A session whose worker predates
1057
+ Revise (started by an older chi) disables the button and shows the chip as
1058
+ "Revise: restart this session to use Revise"; a message sent then is a plain
1059
+ one. A Revise turn that fails comes back into the composer with its chip.
1060
+
1061
+ ### How the page draws a turn
1062
+
1063
+ **The stage** pins the running turn above the composer, in its own
1063
1064
  card, so it stays on screen without scrolling: a status row (what it is
1064
1065
  doing, the step, the elapsed time), your prompt on one line, the newest
1065
1066
  narration sentence (else the newest thinking one, in italics; click it for
@@ -1078,8 +1079,8 @@ stage's bottom edge takes you to it. Once answered, the model's
1078
1079
  question or its own approval keeps that reply (a delegate's relayed
1079
1080
  approval, a hook's question and the step limit don't): two muted lines
1080
1081
  under its `question → answer` line, the whole text when you open the card,
1081
- in both views and after a reload. The chip at the stage's bottom edge, `N steps · M tool
1082
- calls` with one tick per call, opens the turn view's block right above it (newest step first
1082
+ live and after a reload. The chip at the stage's bottom edge, `N steps · M tool
1083
+ calls` with one tick per call, opens the turn block right above it (newest step first
1083
1084
  while it runs; a tick opens its step). Below the chip, past a dashed rule, wait the
1084
1085
  messages queued for after the turn and the commands queued for its end; they go into
1085
1086
  the history after the turn's answer. `▾` folds the stage to its status
@@ -1094,10 +1095,11 @@ message moves it at once. The history is never scrolled while a turn runs, and o
1094
1095
  follows the hand-off if you were at its end. The `/` command list opens
1095
1096
  over the stage's lower edge.
1096
1097
 
1097
- **The turn view** shows a turn as *one block* where the work
1098
- happens. The running
1099
- generation is the live part at the bottom (its thinking, its narration, its
1100
- tool rows), the earlier ones stack above it collapsed to one line each
1098
+ **The turn block** shows a turn's work as *one block*: in the stage's cloud
1099
+ while the turn runs (newest step first), in the history after the hand-off
1100
+ and on a reload. The running
1101
+ generation is the live part on top (its thinking, its narration, its
1102
+ tool rows), the earlier ones below it collapsed to one line each
1101
1103
  (their narration's first line, else their first call's title such as
1102
1104
  `edit lib/a.rb`, and a call count), expandable for inspection. A tool row
1103
1105
  says what the call did: a file's path relative to the session's folder, a
@@ -1116,8 +1118,8 @@ first line, so a mid-turn note reads without a click), and each run of steps
1116
1118
  that only called tools or thought sits behind one `· N steps` line that
1117
1119
  shows them; old turns open the same way after a reload. A turn whose steps
1118
1120
  said nothing collapses the block to its summary (`3 steps
1119
- · 8 tool calls · execute ×7 · read ×1`). Either way the answer expands from
1120
- the box into a normal bubble under it (Markdown, annotate). A plain answer
1121
+ · 8 tool calls · execute ×7 · read ×1`). Either way the answer is a normal
1122
+ bubble under the block (Markdown, annotate). A plain answer
1121
1123
  without tools ends exactly as it does today. Rows the code collapses stay
1122
1124
  as you toggled them. A reloaded session shows the same block
1123
1125
  from the saved messages (each step's thinking, narration, tool parameters
@@ -1130,7 +1132,10 @@ steps saved before that have none, so they show no thinking. An `edit` or
1130
1132
  made (up to 120 lines or 8 KB), live and after a reload (see
1131
1133
  [Guardrails](guardrails.md#ask) for the diff an approval shows first).
1132
1134
 
1133
- **When things happened**, in both views and on a participant's review page: where 10 minutes or
1135
+ `web.view`, `chi web --web-view`, `SAMAGOTCHI_WEB_VIEW` and `?view=` were removed with the turn view (the
1136
+ stage is the only view; a config.yml that still sets `web.view:` warns about an unknown key).
1137
+
1138
+ **When things happened**, on the owner's page and on a participant's review page: where 10 minutes or
1134
1139
  more of quiet passed between the end of one activity and the start of the next (a turn, a context
1135
1140
  note, a reminder), the history has one dim centred line, `── 14:32 · 47 min later ──`; the date is
1136
1141
  added when the day changed or the turn wasn't today (`── Sat 24 Oct 09:10 · 13 h later ──`).
@@ -1147,7 +1152,7 @@ it read, edited or wrote, the memories it read or wrote and the URLs it
1147
1152
  fetched. It is on by default; `web.working_set: false` (or `chi web
1148
1153
  --no-web-working-set`, `SAMAGOTCHI_WEB_WORKING_SET=false`; `?working_set=0` on
1149
1154
  the page URL for one page load, `?working_set=1` back on) turns it off, and the
1150
- session bar shows the `mem N` chip again. The turn view ignores it.
1155
+ session bar shows the `mem N` chip again.
1151
1156
 
1152
1157
  - **The rail**, on the right of the stage's live slots: the running turn's set
1153
1158
  in four lines, folded as it grows (files under one folder become
@@ -656,7 +656,7 @@ models:
656
656
  came from);
657
657
  - `chi --thinking LEVEL` on a run that starts or resumes a session (the REPL, `-p`, `--resume`, an attached one);
658
658
  - `chi send --new --thinking LEVEL`;
659
- - the web's `think` chip in the session bar, and the start page's ⚙ panel (`POST /api/sessions` takes `"thinking": "low"`;
659
+ - the web's ⚙ panel in the session bar (its Thinking row), and the start page's ⚙ panel (`POST /api/sessions` takes `"thinking": "low"`;
660
660
  anything that isn't a level answers `400 invalid_thinking`).
661
661
  - `chi web --thinking LEVEL` is the default for the sessions its workers run, below a session's own. A session started
662
662
  with only the env (`SAMAGOTCHI_THINKING_LEVEL=low chi send --new …`) loses the level when a worker without that env
@@ -809,7 +809,7 @@ models:
809
809
  (its file's `"reasoning_history"`), so a `--resume`, a respawned worker, a continue and `/model` keep it; a plugin's
810
810
  fork copies it, a delegate child starts without it. Set it with `/reasoning-history LEVEL` in the session
811
811
  (`/reasoning-history default` unsets it, `/reasoning-history` alone shows the level and where it came from) or the
812
- web's `reasoning` chip in the session bar (shown on chat hosts only). There is no start flag: a fresh session runs
812
+ web's ⚙ panel in the session bar (its Reasoning row, shown on chat hosts only). There is no start flag: a fresh session runs
813
813
  the command before its first turn at no cost. A change takes effect at the next turn's start; sent while a turn runs
814
814
  it waits for its end.
815
815
  - What a change costs the prompt cache (`/reasoning-history` says so, and asks first when it costs: "Changes that
@@ -849,8 +849,8 @@ first: an unknown host or a bad alias is an error at once, not a question.
849
849
  - `Nothing changed yet: send /thinking low --yes to apply.` names the line that applies it.
850
850
  - `--yes` (or `-y`, anywhere after the command) applies it at once.
851
851
  - The terminal (the REPL and an attached TUI) puts the `--yes` line in the next input: Enter applies it.
852
- - The web's reply bubble has an **Apply (restarts the cache)** button. The session bar's chips are already an explicit
853
- choice: their Set sends the `--yes` line, and their form says what a change costs. Command bubbles are page-only, so
852
+ - The web's reply bubble has an **Apply (restarts the cache)** button. The session bar's ⚙ panel is already an
853
+ explicit choice: a tap sends the `--yes` line, and its note says what a change costs. Command bubbles are page-only, so
854
854
  the button is gone after a reload.
855
855
  - Only a person at a web tab or a chi TUI is asked. `chi send ID "/thinking low"`,
856
856
  `chi -p "/thinking low" --non-interactive`, lines piped into the REPL, a delegate, a plugin and a card's action
@@ -1570,12 +1570,15 @@ toolkit:
1570
1570
  cache; `full` and every kit without a prompt send the bytes they sent before. Keep it to a line or two that
1571
1571
  fits the kit's work (the coordinator bundle's `coding`: be concise): one that isn't text or is over 2000
1572
1572
  characters is left out with a warning, and long text belongs in `append_system_prompt:`. `/toolkit` shows it
1573
- (`prompt: …`), `/stats` its kit and size (`kit prompt:`), the web's tools chip in its tooltip.
1573
+ (`prompt: …`), `/stats` its kit and size (`kit prompt:`), the web's 🧰 chip in its tooltip.
1574
1574
  - **Saved at creation, definitions resolve at worker start.** A session stores its toolkit as named
1575
1575
  (`"toolkit": {"name": "lean", "add": [], "remove": []}`) when it is created: `toolkit.default`, `toolkit.child`
1576
1576
  for a delegate child (or the kit its `delegate` call names), or `chi --toolkit NAME` / `--tool` / `--no-tool` ([cli](cli.md)). Changing `toolkit.default`
1577
1577
  doesn't move existing sessions, not even when their worker is woken again; a session from before toolkits is on
1578
- `full`. A continue and a fork keep the toolkit name. `/toolkit` shows and changes it. The kit's tools, prompt
1578
+ `full`. A continue and a fork keep the toolkit name. `/toolkit` shows and changes it. The web's session bar
1579
+ shows it as the 🧰 chip after ⚙: `🧰` alone while the session is on `toolkit.default` (its tooltip names the kit,
1580
+ "full (the default)"), `🧰 lean` on another kit (`GET /api/sessions/:id`'s `session.toolkit` against
1581
+ `session.toolkit_default`, the default as chi web reads it now); it has no picker, `/toolkit` changes it. The kit's tools, prompt
1579
1582
  text and group selectors are part of its definition, not the session file: edits reach a saved name at its next
1580
1583
  worker start.
1581
1584
  - **A new web chat: 🧰.** The start page's 🧰 (beside ⚙; a bottom sheet on a phone) picks the new session's toolkit.
@@ -1708,7 +1711,7 @@ its own). The first target is a long session such as a day-long coordinator, whe
1708
1711
 
1709
1712
  - **Set it** at start with `chi --llm-context stale,forget --llm-context-apply turn_end --llm-context-budget 64k`
1710
1713
  (the same flags on `chi send --new`), in the session with `/llm-context strategy … apply … budget …`
1711
- (docs/cli.md, "Session commands"), or from the web's `llm ctx` chip in the info bar.
1714
+ (docs/cli.md, "Session commands"), or from the Context row of the web's ⚙ panel in the session bar.
1712
1715
  - **A new web chat:** the start page's ⚙ panel (beside the model picker; a bottom sheet on a phone) holds the new
1713
1716
  session's rarely used settings, each a row of segments that applies at once: **Thinking** and **Context** (the
1714
1717
  strategy; **Apply and budget ▸** opens the other two fields, with their own Set), the first segment following the
@@ -2184,8 +2187,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2184
2187
  | `web.port` | `4567` | `--port` | `chi web`'s port. See [CLI](cli.md). |
2185
2188
  | `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token, asked of this machine's 127.0.0.1 too (its `Local:` link carries a one-time code; `chi web --new-token` replaces the token). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
2186
2189
  | `web.markdown` | `false` | yes | Render answers as Markdown in `chi web`. |
2187
- | `web.view` | `stage` | yes | How `chi web` draws a turn: `stage` (the running turn pinned above the composer) or `turn` (one block per turn in the history). See [CLI](cli.md#web-views). |
2188
- | `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). |
2190
+ | `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. See [CLI: the working set](cli.md#the-working-set). |
2189
2191
  | `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). |
2190
2192
  | `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). |
2191
2193
  | `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; the header's project picker switches projects. `~` and a relative path (resolved from your home folder) work; one that isn't a folder stops `chi web` from starting. `chi web` never changes its own folder. See [CLI](cli.md). |
data/docs/guardrails.md CHANGED
@@ -54,6 +54,11 @@ changed: `diff +3 −1` under the row on the web (closed, it survives a
54
54
  reload) and ` +3 −1` at the end of the terminal's tool line. The model never
55
55
  sees these diffs.
56
56
 
57
+ The card also carries the ask's facts as `approval:` (tool, command or paths, where, rule, scopes, preview…):
58
+ `Guardrails::Approval.payload` builds them as a `Guardrails::ApprovalFacts` value, which System 1 and the parent
59
+ checks read in the worker; the card and the session file get its `to_h`, the same hash every UI reads, and a saved
60
+ card comes back through `ApprovalFacts.from_h` (string or symbol keys, missing fields nil).
61
+
57
62
  Who answers:
58
63
 
59
64
  - REPL (`chi --no-shared`, with or without `-p`): at the `? ` prompt.
@@ -516,10 +521,36 @@ own, one another interpreter runs (`ruby -e`, `python - <<EOF`) by its text
516
521
  variable a program runs through a shell (`PairCommand::COMMAND_ENV`:
517
522
  `GIT_EDITOR`, `GIT_SEQUENCE_EDITOR`, `EDITOR`, `VISUAL`, `GIT_EXTERNAL_DIFF`,
518
523
  `GIT_SSH_COMMAND`, `GIT_SSH`, `GIT_PROXY_COMMAND`, `GIT_PAGER`, `PAGER`,
519
- `MANPAGER`, `GIT_ASKPASS`, `SSH_ASKPASS`), as a prefix, `env`'s or `export`'s:
520
- read as `sh -c` would run it, for the refusals and for an outside write. A heredoc written as data (`cat`,
524
+ `MANPAGER`, `GIT_ASKPASS`, `SSH_ASKPASS`, bash's `PROMPT_COMMAND`), as a prefix,
525
+ `env`'s or `export`'s: read as `sh -c` would run it, for the refusals and for
526
+ an outside write. An assigned value that keeps a `$(…)`, `${ …; }` or backticks
527
+ as text (`PS4='$(…)' bash -x`, `PS0`, `PS1`, `$'\x24(…)'`: the shell expands it
528
+ later), a prefix's, a bare assignment's or `export`'s (not `grep 'X=$(…)'`), is
529
+ refused for what its `$(…)` runs (`PS4='$(git config …)'`), and otherwise the
530
+ owner approves it (`PairBoundary::NET`: `CFLAGS='$(BASE)' make`).
531
+ The program is found behind the wrappers (`env`, `sudo`, `timeout`, `xargs`,
532
+ `nice`, `exec`, `command`, `builtin`, `xcrun`, `arch`, `script`'s file, `npx`,
533
+ `bunx`, `uvx`, and two-word ones: `uv`/`poetry`/`pdm`/`hatch`/`pipx run`,
534
+ `pnpm`/`yarn exec`/`dlx`, `npm exec`/`x`, `bun x`, `mise exec`/`x`) and their
535
+ value options (`env -u X`, `sudo -u root`, `timeout -s KILL 5`; `xcrun -r`,
536
+ `sudo -n` are flags). A command line a program takes as text is read as a
537
+ command: `env -S S` (`-vS S` too), `script -c S`, `npx`/`npm exec -c S`
538
+ (`--call`), `rsync -e S` (`--rsh`), ssh's (scp's, sftp's) `-o ProxyCommand=S`
539
+ (`LocalCommand`, `KnownHostsCommand`: they run here), `hyperfine`'s arguments,
540
+ `parallel`'s and `watch`'s. And as a net for a runner no list knows, any word
541
+ but the program's (a wrapper's option value too) that names git is read as git
542
+ from there, one naming chi before a chi command counts (`chronic chi share x`;
543
+ not `cp bin/chi /tmp/x`), and a command line starting with git or chi is read
544
+ as a command (`some-runner 'git config k v'`): a hit asks the owner
545
+ (`PairBoundary::NET`), it doesn't refuse, since chi can't tell whether that
546
+ word runs (`gh pr create --title 'git config fix'` asks too). A read stays a
547
+ read, and a command that only reads (`rg 'git config'`) isn't netted. A heredoc written as data (`cat`,
521
548
  `tee`, `git commit -F -`) is not looked into: where it goes is checked. A
522
549
  command the checks can't read is refused, never let through on a text match.
550
+ A program is told by its name in any case, on every disk (`GIT config`, `Chi
551
+ share`, `/bin/SH -c`, `NOHUP`: a disk that folds case, macOS's default, runs
552
+ them as git, chi, sh, nohup; `ShellLex.program_name`), and so is chi or git
553
+ config in a script's text.
523
554
 
524
555
  And it **asks the owner** before a `write` or `edit` that may land outside the
525
556
  session's project root, and before a command that may write there, every time:
@@ -552,7 +583,8 @@ rule lowers it), so it comes even in auto mode. A command fails closed
552
583
  install -g`, `corepack enable`, `rustup`/`pyenv`/`rbenv`/`asdf`/`volta`/`nvm`
553
584
  installs and globals, `code --install-extension`, `gh auth`/`config
554
585
  set`/`extension install`, `launchctl` but its reading verbs, `git maintenance register`/`start`, `git
555
- lfs install`, `scalar register`/`clone`; a project
586
+ lfs install`/`update` (git's words from its subcommand on: not `git add
587
+ maintenance start`), `scalar register`/`clone`; a project
556
588
  install such as `npm install` or `bundle install` and build caches don't
557
589
  count), and no shell or interpreter that reads its script from stdin: one
558
590
  on the receiving end of a pipe whatever its options (`… | sh`, `| bash -x`,
@@ -560,8 +592,10 @@ rule lowers it), so it comes even in auto mode. A command fails closed
560
592
  or with no script at all: chi can't see that script. A shell's `-e`, `-x`
561
593
  and `-o …` are flags, only `-c` (or a cluster holding it, `-ec`, `-cx`)
562
594
  hands it a script: the first word after its options (`sh -c -- S`, `bash -c
563
- -x S`); an interpreter's own code flag does (`python -c`, `ruby -e`,
564
- `node -e`/`-p`/`--eval=`, `perl -e`/`-E`, `php -r`), alone or in a cluster
595
+ -x S`), and fish's `--command S` (`--command=S`) or `-C`/`--init-command S`,
596
+ a command it runs first (also attached, `-C'S'`, in a cluster, `-lC S`, and
597
+ by a prefix fish takes, `--comm`, `--init`); an interpreter's own code flag does (`python -c`, `ruby -e`,
598
+ `node -e`/`-p`/`--eval=`, `perl -e`/`-E`, `php -r`/`-B`/`-R`/`-E`, `jruby`/`raku`/`julia -e`), alone or in a cluster
565
599
  (`ruby -we`, `perl -le`, `node -pe`, `python3 -Ic`, `python3 -c'…'`;
566
600
  `PairCommand.cluster_code_at`: a letter that takes the rest of the word,
567
601
  `ruby -rset`, holds no code). A script file it names (`sh
@@ -570,11 +604,15 @@ rule lowers it), so it comes even in auto mode. A command fails closed
570
604
  Accepted gaps (an accident guard for a trusted team): a script file the turn
571
605
  wrote and then runs; inline scripts read only by their text for paths
572
606
  (`find … -exec sh -c '…'`, `flock … sh -c`, `awk 'BEGIN{system(…)}'`,
573
- `deno eval`); a `GIT_CONFIG_*` set by `printf -v` or `read` and exported
574
- (it affects that command only); a variable outside `COMMAND_ENV` whose
575
- value a program runs (`LESSOPEN`, a tool's own `*_EDITOR`); `npx -y`,
607
+ `deno eval`); a `GIT_CONFIG_*` or a `COMMAND_ENV` variable set by `printf -v`
608
+ or `read` and exported (`printf -v GIT_PAGER …; export GIT_PAGER; git log`);
609
+ a variable outside `COMMAND_ENV` whose value a program runs (`LESSOPEN`,
610
+ `BROWSER`, a tool's own `*_EDITOR`; `GIT_EXEC_PATH` runs only a file the
611
+ turn wrote); an interpreter no list names (as `awk`); `npx -y`,
576
612
  `npm exec`, `pnpm dlx`, `uvx`: they fetch into a cache, which doesn't count,
577
- and run the package as in-project work.
613
+ and run the package (judged as the program) as in-project work; a wrapper's
614
+ value option no list names (`uv run --with x git …` reads `x` as the program;
615
+ the net still reads the git or chi after it).
578
616
 
579
617
  Anything else asks: `rm -rf ~/x`, `sed -i … ~/x`, `curl -o ~/x`, `dd of=~/x`,
580
618
  `touch`/`mkdir` outside, `perl -pi … ~/x`, `cd .. && make`, `cd && make`, and
@@ -726,6 +764,11 @@ checked even after the database name, and only its read/display options are
726
764
  allowed (unknown options and dot-command arguments are rejected). `gh` allows
727
765
  only `run list|view`, `pr list|view|diff|checks`, `issue list|view` and
728
766
  `repo view`; `--web`/`-w`, `gh api` and `pr checks --watch` are not read-only.
767
+ `test` and `[ … ]` (with its closing `]`) are read-only as one check, optionally after `!`: a file or string
768
+ test (`-e`, `-f`, `-d`, `-s`, `-r`, `-w`, `-x`, `-L`, `-h`, `-b`, `-c`, `-p`, `-S`, `-g`, `-u`, `-k`, `-O`, `-G`,
769
+ `-n`, `-z` with one operand) or a comparison (`=`, `==`, `!=`, `-eq`, `-ne`, `-lt`, `-le`, `-gt`, `-ge`), so
770
+ `test -f x && cat x` and `[ -d dir ] && ls dir` only read; `-a`/`-o`, `( … )`, any other operator or a missing
771
+ operand, and `[[ … ]]` are not.
729
772
  `$(…)`, backticks, `$VAR` (other than `$HOME`, `$XDG_*`, `$TMPDIR` and `$PWD`
730
773
  at a word's start), an env prefix, `sh -c`, `xargs`, `eval`, `sudo` and the
731
774
  like make a command not read-only, as does a `task_create` `env:` that sets `BASH_ENV`, `ENV`, `CDPATH`, `IFS`,
@@ -873,6 +916,7 @@ every surface answers for the same paths):
873
916
  | in a dir: `under?` (case-folded on a folding disk; `fold: false` exact), `strictly_under?`, `within?` (root resolved too) | `PathResolve` | `under?`: ShellPaths, ReviewPaths, PairCommand, ChildBoundary, SelfProtect's words; `Outside.within?` and RmTargets' `strictly_under?` fold only when the dir exists (a missing session root keeps the case it was typed in); `within?`: ProtectedPaths, ScratchWrites, ParentApprovals, SelfProtect's paths |
874
917
  | tmp dirs and "not one the session is in" | `PathResolve.tmp_roots(session:)` (exact case) | Outside (`Outside.tmp_roots` is the list it and Targets read), RmTargets; `touches_chi?` takes all of them |
875
918
  | a shell word's `~`, `$HOME`, XDG dirs, `$TMPDIR` | `PathResolve.expand_word` | ShellPaths::Walk, ShellGitDirs; PairCommand has its own `~`/`$HOME` (`expand_tilde`) |
919
+ | a program word's name: basename, case-folded on every disk (`GIT`, `/bin/SH`, `ſh`) | `ShellLex.program_name` / `.program?` | PairCommand, ImplicitWriters, SelfProtect's file runners, ShellGitDirs, SharedGit |
876
920
  | chi's dirs | `ChiDirs.protected` / `.touches` / `.self_protect` / `.review` | ParentApprovals, Targets, SelfProtect, ReviewPaths (`ProtectedPaths` and `ScratchWrites` get theirs from the wiring) |
877
921
 
878
922
  Left apart on purpose: `Targets.prompt_memory_path?` compares in any case on
@@ -76,7 +76,10 @@ Engine#run_turn(session, prompt, on_event:, max_iterations:, cancel_controller:,
76
76
  │ # messages, goes IDLE, records last_turn (enqueued_id: the turn's input, from its origin;
77
77
  │ # a continue turn's origin carries its offer turn's, ContinueOffer#run_continue_turn),
78
78
  │ # and :turn_completed {result, turn_summary, display_pending} or :turn_canceled
79
- │ # {cancellation_reason, cancelled_by, duration_ms} is emitted
79
+ │ # {cancellation_reason, cancelled_by, duration_ms} is emitted; before the replace,
80
+ │ # stamp_opening puts a Verify turn's `verify` and a completed Revise turn's `revises`
81
+ │ # (re-targeted to its stack's top) on the opening message, metadata never sent, and
82
+ │ # :turn_completed carries `verdict` / `revises` when there are
80
83
  │ # then the :after_turn hook (status, messages, present: the AnswerDisplay presenter,
81
84
  │ # refs: the AnswerRefs adder)
82
85
  │ # store_answer_display (one replace of the answer: `display` and/or `display_refs`;
@@ -330,7 +333,7 @@ One iteration with tool calls:
330
333
  stored under the real path; at most `RowRefs::MAX_REFS` and `MAX_BYTES`). `ToolRowFields.for` can't build it (no plugins on a reload), so the run carries it
331
334
  as `refs:`, saved with the result as `tool_refs` (`ToolResponse::SAVED_KEYS`, never sent), and
332
335
  `MessageParts#tool_part` reads it back through `RowRefs.for_display` (checked again). The page binds them to the
333
- row's title (`refs.js bindRowRefs`, from `turn_view.js` live and on a reload): the first ref on it, the rest in its
336
+ row's title (`refs.js bindRowRefs`, from `turn_block.js` live and on a reload): the first ref on it, the rest in its
334
337
  menu.
335
338
  - Settles an ask verdict after `:tool_call_started`, so a UI shows the tool line and then the approval.
336
339
  - Dispatches through `KernelLoop#dispatch_tool_call`, or writes the denial text. Unknown tools are answered in
@@ -545,7 +548,7 @@ Turn events (the main ones; `Events` in `events.rb` keeps the shared sets):
545
548
  | `:working_set_updated` | after `:generation_started` (the running turn's conversation, the turn's strategy) and once in `end_turn` before the end event (the kept messages), only when the set differs from the last one emitted for the session and is not empty, except one empty event when the last one had items (a rollback emptied the set): a tool-free turn emits none (`Engine#emit_working_set`); between turns `Engine#announce_working_set` announces it under the next turn's strategy (after a `/working-set` op, held behind the command's `command_ran`) | `{items: [{kind, key, state (live\|stale\|forgotten\|dropped), tokens, ops, count, first_id, pinned, has_drop}], at, adds?: [{kind, key, label}]}` (`WorkingSet`, internals/working-set.md; `adds` while the user's adds are pending) |
546
549
  | `:hook_notice` | a hook's or plugin's notify during a turn | `{hook, text, level}` (announced with `between_turns: true` outside a turn) |
547
550
  | `:guardrail_warning` | a guardrails line during a turn | `{message}` |
548
- | `:turn_completed` | normal end | Engine event `{result, turn_summary, display_pending}`; the Bridge's SSE frame omits `result` (answer remains in `turn_summary.output`) |
551
+ | `:turn_completed` | normal end | Engine event `{result, turn_summary, display_pending}`, plus `verdict` (a Verify turn's recorded verdict) and `revises` (a Revise turn's stamp, `{turn_id}`) when there are; the Bridge's SSE frame omits `result` (answer remains in `turn_summary.output`) |
549
552
  | `:turn_canceled` | Stop, hook stop, Ctrl-C | `{cancellation_reason, cancelled_by?, duration_ms}` |
550
553
  | `:turn_failed` | an error | `{error_class, message, duration_ms, error_kind?, retryable?, host?, summary?}` |
551
554
  | `:answer_display` | after the after_turn hooks (observers only, not the sink) | `{display, refs?}` (display a string, or nil; refs the count of the answer's refs, only when a hook attached some) |
@@ -583,7 +583,7 @@ message's `author_name`, `turn_started`'s, or the panel's list (`participant_nam
583
583
  A review turn proposes; the owner applies (D4, §3 Apply-as-quote). A pair turn acted itself: no Apply under it
584
584
  (`ReviewApply.review_turn?`: a participant's opening message not stamped `pair`; the route answers it
585
585
  `not_review_turn`, and its "latest" skips it). Under each participant's review turn that has a
586
- reply, the owner's page shows **Apply** (`review_apply.js`; drawn by the history render, `turn_view.js`, and at a live
586
+ reply, the owner's page shows **Apply** (`review_apply.js`; drawn by the history render, `turn_block.js`, and at a live
587
587
  turn's end from its `turn_started`'s turn id and author, in the stage while the stage holds the turn; a full-width
588
588
  button on a phone). It reads `GET …/apply?turn=T` and puts the answer in the composer, after whatever is typed there,
589
589
  never sent: the owner reads it, edits it, sends it or not. While the composer holds it (`deliveryControl.hold()`,
@@ -253,7 +253,7 @@ is prefilled once. The native and the chat loop use the same plan.
253
253
  estimate when no count is known. On `api: anthropic`, a `:full` / `:provider` change also warns that preserved
254
254
  thinking in replies is dropped from then on. A new setting with a cost adds its classifier and goes through
255
255
  the same step. A launch flag (`chi --model`, `--llm-context`, `--thinking` on an attached or resumed session,
256
- `LLMContextOverride.command_line`) and a chip whose form states the cost (`level_chip.js`, `llm_context_chip.js`)
256
+ `LLMContextOverride.command_line`) and the session bar's ⚙ panel, whose note states the cost (`settings_chip.js`)
257
257
  send their line with `--yes`: typing the flag, or the Set, is the choice. A plugin command asks
258
258
  through `ctx.cache_confirm(line, changed:)` (cost `:full`: its prompt section is in the stable part), with the
259
259
  sender's flag `SessionCommands#run` gives it (`CacheConfirm.asking`), and returns `[text, confirm]`.