samagotchi 0.136.1 → 0.138.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -1
  3. data/docs/architecture.md +2 -2
  4. data/docs/cli.md +15 -7
  5. data/docs/context.md +7 -9
  6. data/docs/guardrails.md +12 -5
  7. data/docs/internals/engine.md +4 -4
  8. data/docs/internals/participants.md +2 -1
  9. data/docs/internals/web-frontend.md +19 -9
  10. data/docs/plugins.md +2 -2
  11. data/docs/sessions.md +8 -8
  12. data/docs/testing.md +2 -1
  13. data/lib/samagotchi/bridge.rb +4 -0
  14. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  15. data/lib/samagotchi/desktop/macos.rb +3 -2
  16. data/lib/samagotchi/engine.rb +6 -1
  17. data/lib/samagotchi/guardrail_wiring.rb +3 -1
  18. data/lib/samagotchi/guardrails/chi_dirs.rb +2 -2
  19. data/lib/samagotchi/guardrails/mise_paths.rb +36 -0
  20. data/lib/samagotchi/guardrails/outside.rb +5 -5
  21. data/lib/samagotchi/guardrails/pair_boundary.rb +11 -4
  22. data/lib/samagotchi/guardrails/pair_command.rb +3 -19
  23. data/lib/samagotchi/guardrails/protected_paths.rb +5 -2
  24. data/lib/samagotchi/guardrails/review_paths.rb +3 -2
  25. data/lib/samagotchi/guardrails/shell_paths.rb +0 -4
  26. data/lib/samagotchi/guardrails/verdict.rb +1 -1
  27. data/lib/samagotchi/version.rb +1 -1
  28. data/lib/samagotchi/web/message_display.rb +6 -3
  29. data/lib/samagotchi/web/participant_review.rb +2 -1
  30. data/lib/samagotchi/web/public/answer_fold.js +24 -6
  31. data/lib/samagotchi/web/public/app.js +24 -19
  32. data/lib/samagotchi/web/public/chains.js +6 -5
  33. data/lib/samagotchi/web/public/chip_popover.js +20 -42
  34. data/lib/samagotchi/web/public/composer.css +15 -11
  35. data/lib/samagotchi/web/public/composer_grip.js +11 -13
  36. data/lib/samagotchi/web/public/composer_mount.js +2 -2
  37. data/lib/samagotchi/web/public/context_chips.js +29 -98
  38. data/lib/samagotchi/web/public/copy.js +8 -5
  39. data/lib/samagotchi/web/public/data.js +2 -0
  40. data/lib/samagotchi/web/public/families_view.js +30 -38
  41. data/lib/samagotchi/web/public/index.html +176 -132
  42. data/lib/samagotchi/web/public/info_bar.js +19 -8
  43. data/lib/samagotchi/web/public/info_bar_model.js +3 -1
  44. data/lib/samagotchi/web/public/lists_view.js +69 -42
  45. data/lib/samagotchi/web/public/participants_panel.js +4 -4
  46. data/lib/samagotchi/web/public/popover_modal.js +57 -0
  47. data/lib/samagotchi/web/public/revise_stack.js +76 -6
  48. data/lib/samagotchi/web/public/revise_switch.js +55 -0
  49. data/lib/samagotchi/web/public/scope.js +3 -3
  50. data/lib/samagotchi/web/public/send_turn.js +4 -2
  51. data/lib/samagotchi/web/public/settings_chip.js +2 -2
  52. data/lib/samagotchi/web/public/stage_view.js +51 -2
  53. data/lib/samagotchi/web/public/stale_params.js +26 -0
  54. data/lib/samagotchi/web/public/start_settings.js +1 -1
  55. data/lib/samagotchi/web/public/start_toolkit.js +1 -1
  56. data/lib/samagotchi/web/public/stream_handlers.js +10 -5
  57. data/lib/samagotchi/web/public/working_set_rail.js +2 -0
  58. data/lib/samagotchi/web/public/working_set_view.js +2 -2
  59. data/lib/samagotchi/web/session_summary.rb +1 -1
  60. metadata +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5dbc0c078bedf4518127ba59fef2277480193f98de99dcbd8983bb79a3802d2
4
- data.tar.gz: e352d50d3b09a5e2f2a3ddd8dda3b79461e76c7e4743c306fc885e78fa670100
3
+ metadata.gz: 57a409114b8194ae2d17416055b7f52c468e6a2ab5a650d8f36a4b6a0e0b0b3f
4
+ data.tar.gz: 78f20cbc62ab65e54c300bad75ec2478388810c1328679c3c48d3842f80cc0a3
5
5
  SHA512:
6
- metadata.gz: c48493b22b5916b579d1f1fd386f0169f8a26ea71a29f5c77a567045e422f115b8b3ec421cb6f0499e0fc584da7e08840eeebf2650ad5d9bd75a61c41dc34c89
7
- data.tar.gz: b9e9e2c78d869fe803ac6dbf54fcacc305b60c4b4ccd354d78b0967d340ad54cbf3f18357bb24c26050d96e4cff3899e90755857979fd9ec6680cc9fd730f46a
6
+ metadata.gz: e285adda4382592d1cca2811e05eebdc5999d5808fc0f23a0ff7f73a87db6b4cbb504cbc43647feab56db006b66c7439d075398ef2a8eae6339bae3d42824887
7
+ data.tar.gz: c796ce3b9121c74bf838959f4f1ea97b9213930bd449922f42f617afab92c8eb38f685b7098f9c67c4d20ab1622e51b7fbed99de24a71964159fa892ecec827f
data/CHANGELOG.md CHANGED
@@ -7,6 +7,61 @@ 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.138.0] - 2026-10-30
11
+
12
+ ### Changed
13
+
14
+ - Web: All sessions' header is the top bar: the sessions pill shows pressed in the all view and takes you back (Esc
15
+ too); the search is a pill, "archived" a toggle chip, the count reads "4 · 5 delegates", the storage path a footer
16
+ under the list.
17
+ - Web: session cards show one mark per state and no status dots: a waiting card a warn outline and "question", an
18
+ error a red badge, "live" only when not waiting; titles one line; an untitled session reads "Untitled" over its
19
+ first message.
20
+ - Web: Stop turn is a button in the running turn's status row (the composer's and the bar's Stop buttons are gone);
21
+ after a reload that knows only that a turn runs, the stage shows that row alone.
22
+ - Web: the session bar reads in three groups (setup, relations, telemetry) with hairlines between, on one line on
23
+ a desktop; the parent chip reads "↑ id", the clock "7m 52s"; continue is a plain button unless archived.
24
+ - Web: the start composer keeps model, ⚙ and 🧰 left and 🖼 and Start right; the keyboard hint shows under the
25
+ placeholder only while the box is focused and empty.
26
+ - Web: the "+ URL" chip is gone (`chi context` still attaches URLs).
27
+ - Web: on a phone a tap no longer leaves a hover tint on a card, a bar button, a question option or a fold.
28
+
29
+ ### Fixed
30
+
31
+ - Web: on a phone the sessions search takes a whole line, so its placeholder reads; the start page keeps the
32
+ project chip's words.
33
+ - Web: a session with no turn yet shows no clock, not "0ms".
34
+ - Web: the cards' shadow is no longer cut into a band at the sessions list's edges (light theme).
35
+
36
+ Update with `chi update`, then restart `chi web`.
37
+
38
+ ## [0.137.0] - 2026-10-30
39
+
40
+ ### Changed
41
+
42
+ - Web: a Revise stack's top answer has a ‹ N/M › switcher that shows an earlier version in its place (view only;
43
+ Copy copies the version shown; a reload shows the latest).
44
+ - Web: a long prompt folds like a long answer (same threshold, open by default; this browser remembers the fold).
45
+ - Web: every popover behaves like the ⚙ panel: a modal dialog (Tab stays in it, Esc gives focus back to its chip),
46
+ and on a phone a tap no longer leaves a hover tint.
47
+ - Web: a delegate report's code blocks get copy buttons, like an answer's. On a phone or touch screen a code
48
+ block's copy button sits in a strip above the first line, not over it.
49
+ - Web: a phone's session bar shows ⚙ on first view, right after the status.
50
+ - Web: the open ⚙ panel's running-turn note follows a turn starting or ending.
51
+ - Web: a markdown delegate report in the running turn's stage reads as one plain row.
52
+ - A Verify or Revise sent during a running turn always queues behind it (it no longer cuts the model's thinking
53
+ for a line that waits anyway); the page says it queues.
54
+ - Guardrails: a write to the chi shim asks, and only you can allow it, like home.json. A pair turn's plugin tool
55
+ call that names no command or path asks the owner.
56
+
57
+ ### Fixed
58
+
59
+ - Web: other viewers of a Verify turn no longer see a Revise offer on its answer until they reload.
60
+ - Web: a stale `?view=` (the removed turn view's) leaves the address bar.
61
+ - Desktop: the helper checks the chi home the helper's chi actually reads.
62
+
63
+ Update with `chi update`, then restart `chi web`.
64
+
10
65
  ## [0.136.1] - 2026-10-30
11
66
 
12
67
  ### Changed
@@ -5230,7 +5285,9 @@ and long-lived sessions.
5230
5285
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
5231
5286
  hotkey panel that send selected text or the clipboard to your sessions.
5232
5287
 
5233
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.136.1...HEAD
5288
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.138.0...HEAD
5289
+ [0.138.0]: https://github.com/dm1try/samagotchi/compare/v0.137.0...v0.138.0
5290
+ [0.137.0]: https://github.com/dm1try/samagotchi/compare/v0.136.1...v0.137.0
5234
5291
  [0.136.1]: https://github.com/dm1try/samagotchi/compare/v0.136.0...v0.136.1
5235
5292
  [0.136.0]: https://github.com/dm1try/samagotchi/compare/v0.135.0...v0.136.0
5236
5293
  [0.135.0]: https://github.com/dm1try/samagotchi/compare/v0.134.0...v0.135.0
data/docs/architecture.md CHANGED
@@ -258,10 +258,10 @@ higher-level events so UIs get clean turn boundaries without inferring them:
258
258
  - `:turn_started` — `{ session_id:, prompt:, turn_id:, cut_after: }` (`cut_after`: the seconds a cut waits for this
259
259
  model's thinking, `0` = never; plus `continue: true` for a continue turn, `resumed: true` for a step-limit Continue,
260
260
  `images:` with the prompt's image refs)
261
- - `:turn_completed` — `{ result:, turn_summary:, display_pending: }` (`result`: the final
261
+ - `:turn_completed` — `{ turn_id:, result:, turn_summary:, display_pending: }` (`result`: the final
262
262
  `LLM::ModelResult`; Engine event (clients get it without `result`); `display_pending`: `after_turn` hooks may still
263
263
  present the answer)
264
- - `:turn_canceled` — `{ cancellation_reason:, cancelled_by:, duration_ms: }` (`cancelled_by`: the bundle or hook label that
264
+ - `:turn_canceled` — `{ turn_id:, cancellation_reason:, cancelled_by:, duration_ms: }` (`cancelled_by`: the bundle or hook label that
265
265
  stopped it, for `cancellation_reason: :hook`; else nil)
266
266
 
267
267
  These are the Engine's raw events. The worker's SSE projection omits `call` from
data/docs/cli.md CHANGED
@@ -585,7 +585,7 @@ list --archived` shows them too, marked `[archived]` (`archived: true` in
585
585
  `--format json`); `chi sessions unarchive ID...` brings them back, and so does
586
586
  a message you send to one (the web, an attached terminal, `chi send`), but not
587
587
  a delegate's follow-up or a reminder. The web archives from the info bar
588
- (`archive`, before `stop`); "include archived" by the all-sessions search
588
+ (`archive`, before `stop`); the `archived` toggle by the all-sessions search
589
589
  finds archived sessions. `/archive` in a terminal leaves the session and
590
590
  archives it (an empty session is discarded instead; `chi scratch` refuses
591
591
  it). See
@@ -1023,7 +1023,9 @@ hides it for that browser (`chi_markdown_tip_dismissed` in localStorage).
1023
1023
  Every prompt and answer has a copy button (on hover; always shown, dimmed, on a
1024
1024
  touch screen), and so does each code block of a rendered answer. An answer
1025
1025
  copies its Markdown source, not the rendered text; a code block copies just
1026
- its code; a prompt copies the text as you typed it.
1026
+ its code; a prompt copies the text as you typed it. On a phone or a touch
1027
+ screen a code block's button sits in a strip above its first line, clear of
1028
+ the code.
1027
1029
 
1028
1030
  A long answer (over 25 lines or 2,000 characters of its text) can be folded
1029
1031
  by hand: a "▾ N lines" row is its top edge once its turn has ended. Click
@@ -1031,8 +1033,10 @@ it and the answer closes to one line in its own box,
1031
1033
  "▸ its first line · N lines" (the
1032
1034
  first line of what a hook presented in its place, else its first heading,
1033
1035
  else its first line); click again to unfold. A delegate report's reply
1034
- folds the same way. Folding is this browser's choice, kept per session in
1035
- localStorage (`chi_answer_fold:<session id>`, the last 200 folded answers;
1036
+ folds the same way, and so does a long prompt you sent (over the same
1037
+ threshold; its folded row shows its first line as typed). A delegate
1038
+ report itself starts folded. Folding is this browser's choice, kept per session in
1039
+ localStorage (`chi_answer_fold:<session id>`, the last 200 folded answers and prompts;
1036
1040
  the keys of sessions gone from the list are dropped when the list loads);
1037
1041
  other viewers and the participant page are not affected. A jump into a
1038
1042
  folded answer (a claim link, say) unfolds it for that page load.
@@ -1050,7 +1054,11 @@ steps and its answer, all behind it (a warn card, a verdict card and the
1050
1054
  recap stay on view). Click the stub to unfold that version in place (kept per
1051
1055
  browser, with the folded answers); a time divider between two folded stubs
1052
1056
  moves into the later stub. The newest version has a chip, "v3 · 2 earlier ▾",
1053
- listing every version by its prompt; pick one to jump to its stub. Revise
1057
+ listing every version by its prompt; pick one to jump to its stub. Before
1058
+ that chip, a "‹ 3/3 ›" switcher reads an earlier version's answer in the
1059
+ newest one's place (its left edge dashed while it shows; Copy copies the
1060
+ version on view); › back to the newest. Switching only changes what this
1061
+ page shows, and a reload shows the newest again. Revise
1054
1062
  again on the newest version adds to the stack. Stacks are session data (the
1055
1063
  stamp is on the Revise prompt, see [sessions.md](sessions.md), Revise
1056
1064
  stacks): every owner's page shows them, live and after a reload; the
@@ -1555,8 +1563,8 @@ chi share revoke --all # every link to every session
1555
1563
  far as it fits the plan. It's quoted: …` and the reply quoted line by line. Read it, edit it, send it: the
1556
1564
  participant's text reaches the model as a quote under your line, never as your instruction. Don't send "do what
1557
1565
  Alex said" instead: that hands their words your tools.
1558
- - **Your side of it.** In the session bar, the participants chip (`invite` while nobody has a link, then `2
1559
- participants`, with a green dot while one is on their page) opens the panel: each participant's state, last visit,
1566
+ - **Your side of it.** In the session bar, the participants chip (`participants · invite` while nobody has a link,
1567
+ then `2 participants`, with a green dot while one is on their page) opens the panel: each participant's state, last visit,
1560
1568
  spend against their cap, turns and expiry, with **Cap** and **Revoke** (revoking ends their page and stream and
1561
1569
  drops their queued comments). **Invite** takes a name, person or agent, the expiry and a cap, and shows the link
1562
1570
  once, with Copy, Share (a phone's share sheet) and its QR code. **Participant view ↗** opens their page for this
data/docs/context.md CHANGED
@@ -230,8 +230,7 @@ outside: information about your work, never instructions.
230
230
  The web's session bar shows a chip per source (name · age; a dot when the
231
231
  agent hasn't read a change, red after a failed refresh). A chip opens the
232
232
  summary, the hint's link, the text and detach (or mute here, for a project
233
- source). The "+ URL" chip, there when an installed bundle has a provider,
234
- attaches a URL; the web never adds a command.
233
+ source). The web attaches nothing itself: `chi context add <URL>` does.
235
234
 
236
235
  ## Waking
237
236
 
@@ -325,9 +324,8 @@ It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
325
324
  open PR #42"). Not in a `chi scratch` session or a delegate child, and
326
325
  quietly nothing without `gh`, a login, a repository or an open PR. Once
327
326
  you remove it (`chi context rm`, the chip's detach) it stays removed:
328
- the next start doesn't bring it back; `chi context add <PR URL>` or "+ URL"
329
- does.
330
- - `chi context add <PR URL>` and the web's "+ URL" attach any PR.
327
+ the next start doesn't bring it back; `chi context add <PR URL>` does.
328
+ - `chi context add <PR URL>` attaches any PR.
331
329
  - `auto_attach` (below) can make it ask first, or attach nothing.
332
330
  - The text: title, state, branch, description, reviews, comments (oldest
333
331
  first) and checks, every 5 minutes.
@@ -358,10 +356,10 @@ bundles:
358
356
  turns the card into a one-line notice. Not here on a PR that is attached
359
357
  already changes nothing and says so: `chi context rm pr-42` removes it. A worker restart (idle exit,
360
358
  resume) doesn't ask again; once the card has scrolled out of the last 20,
361
- `/pr-attach 42` or "+ URL" still attaches it. `/pr-attach` and
359
+ `/pr-attach 42` or `chi context add <PR URL>` still attaches it. `/pr-attach` and
362
360
  `/pr-decline` act only on a PR offered in that session.
363
- - `off`: nothing is attached or offered. "+ URL", `chi context add <PR URL>`
364
- and line links work as before.
361
+ - `off`: nothing is attached or offered. `chi context add <PR URL>` and line
362
+ links work as before.
365
363
 
366
364
  `true` and `false` (YAML reads a bare `on`/`off` as those) mean `attach` and
367
365
  `off`; an unknown value is `attach`, with a warning in the log. The setting
@@ -392,7 +390,7 @@ links are display only (the model's text stays as it was), and terminals
392
390
  show the answer as it is.
393
391
 
394
392
  - **Which PR**: the session's attached PRs (the branch's, `chi context add
395
- <PR URL>`, "+ URL") and the PR URLs in your messages, newest first (a
393
+ <PR URL>`) and the PR URLs in your messages, newest first (a
396
394
  delegate child's task counts: a parent that names the PR there gets links
397
395
  in the child's answers). PR URLs in tool output or answers don't count.
398
396
  - **Which file**: a file the PR changes, by its path, a renamed file's old
data/docs/guardrails.md CHANGED
@@ -4,7 +4,7 @@ Every tool call the model makes passes one check before it runs, in both
4
4
  loops (native and chat hosts). The verdict is **allow**, **ask** or
5
5
  **deny**; the strictest vote wins, and a deny can't be undone by a later
6
6
  voter. Of two asks the first one names the rule, except a rule only the
7
- user may allow (`chi-config`, `chi-home`, `chi-hooks`, `shell-touches-chi`,
7
+ user may allow (`chi-config`, `chi-home`, `chi-shim`, `chi-hooks`, `shell-touches-chi`,
8
8
  `chi-context-cmd`, `chi-broadcast`): it takes over, and only the scopes both asks offer
9
9
  are offered.
10
10
 
@@ -221,7 +221,7 @@ rules off: `guardrails: {disable: [write-outside-repo, git-outside-repo]}`.
221
221
  Whatever the setting, a parent can't allow a call on chi's own files: a
222
222
  `write`, `edit` or `execute` on the config dir (config.yml, installed bundles'
223
223
  rules), the hooks dir or the approval store, by its rule (`chi-config`, `chi-home`,
224
- `chi-hooks`, `shell-touches-chi`), its paths or its command. "Allow once" on a
224
+ `chi-shim`, `chi-hooks`, `shell-touches-chi`), its paths or its command. "Allow once" on a
225
225
  config.yml rewrite would allow everything from then on, so those questions are
226
226
  the user's alone (`user_only`): `chi answer` and the worker refuse the allow
227
227
  with `only the user can allow it`. The same holds for the guardrails bundle's
@@ -285,7 +285,7 @@ Built in, for `write`, `edit` and `memory_write` (symlinks resolved; see
285
285
  - deny: the approval store's dir, installed bundles (`memories/.bundles/`),
286
286
  and [attached context](context.md) sources (`<state dir>/context/`: a
287
287
  source's command runs later, outside the gate; `chi context` changes them);
288
- - ask (once or for the session): `config.yml`, `home.json` (the [chi home](configuration.md#the-chi-home): the Ruby every chi launch boots) and the plain hooks dir.
288
+ - ask (once or for the session): `config.yml`, `home.json` (the [chi home](configuration.md#the-chi-home): the Ruby every chi launch boots), the chi shim (`$XDG_STATE_HOME/samagotchi/bin/chi`, the `chi` every tool runs: `chi-shim`) and the plain hooks dir.
289
289
 
290
290
  `execute` can still reach them; the guardrails bundle asks about shell
291
291
  commands that name them.
@@ -497,7 +497,10 @@ message:
497
497
  `ruby -run -e httpd`, `php -S`, a static server, a script's `listen(`/
498
498
  `HTTPServer`/`createServer`): the owner's browser sends its cookies (chi
499
499
  web's on 127.0.0.1 or the LAN address, kept for every port) to the page
500
- it opens (`PairBoundary::OPENER`, `PairBoundary::LISTENER`);
500
+ it opens (`PairBoundary::OPENER`, `PairBoundary::LISTENER`). The list
501
+ names generic servers only: a project's dev server (`npm run dev`, `npx
502
+ vite`, `bundle exec rails server`) runs and listens until the execute's
503
+ limit kills it (above);
501
504
  - a program whose name the shell expands: a `$var`, a `$(…)` or a glob in a
502
505
  command's name (`bin/ch? …`, `e=ch; ${e}i …`, `$CMD x`), whatever it would
503
506
  turn out to be (`PairBoundary::VERB`);
@@ -552,6 +555,10 @@ share`, `/bin/SH -c`, `NOHUP`: a disk that folds case, macOS's default, runs
552
555
  them as git, chi, sh, nohup; `ShellLex.program_name`), and so is chi or git
553
556
  config in a script's text.
554
557
 
558
+ It asks the owner, fresh and once, before a plugin or MCP tool's call whose
559
+ `targets:` name no command or path (`PairBoundary::UNTARGETED`: a browser
560
+ MCP's navigate, say), since nothing here can tell what it reaches.
561
+
555
562
  And it **asks the owner** before a `write` or `edit` that may land outside the
556
563
  session's project root, and before a command that may write there, every time:
557
564
  the ask is fresh (`Verdict#fresh_ask`: no stored approval answers it, and no
@@ -913,7 +920,7 @@ every surface answers for the same paths):
913
920
  | Primitive | Where | Used by |
914
921
  |---|---|---|
915
922
  | expand (`..` the kernel's way), real, lexical_real, reals | `PathResolve` (`ProtectedPaths` keeps the old names) | every surface |
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 |
923
+ | 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.under_folding_existing?` 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, ChiDirs.review (`ProtectedPaths.within?` is the same predicate) |
917
924
  | 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 |
918
925
  | a shell word's `~`, `$HOME`, XDG dirs, `$TMPDIR` | `PathResolve.expand_word` | ShellPaths::Walk, ShellGitDirs; PairCommand has its own `~`/`$HOME` (`expand_tilde`) |
919
926
  | 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 |
@@ -79,7 +79,7 @@ Engine#run_turn(session, prompt, on_event:, max_iterations:, cancel_controller:,
79
79
  │ # {cancellation_reason, cancelled_by, duration_ms} is emitted; before the replace,
80
80
  │ # stamp_opening puts a Verify turn's `verify` and a completed Revise turn's `revises`
81
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
82
+ │ # every end event carries `turn_id`, :turn_completed `verify` / `verdict` / `revises` when there are
83
83
  │ # then the :after_turn hook (status, messages, present: the AnswerDisplay presenter,
84
84
  │ # refs: the AnswerRefs adder)
85
85
  │ # store_answer_display (one replace of the answer: `display` and/or `display_refs`;
@@ -548,9 +548,9 @@ Turn events (the main ones; `Events` in `events.rb` keeps the shared sets):
548
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) |
549
549
  | `:hook_notice` | a hook's or plugin's notify during a turn | `{hook, text, level}` (announced with `between_turns: true` outside a turn) |
550
550
  | `:guardrail_warning` | a guardrails line during a turn | `{message}` |
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`) |
552
- | `:turn_canceled` | Stop, hook stop, Ctrl-C | `{cancellation_reason, cancelled_by?, duration_ms}` |
553
- | `:turn_failed` | an error | `{error_class, message, duration_ms, error_kind?, retryable?, host?, summary?}` |
551
+ | `:turn_completed` | normal end | Engine event `{turn_id, result, turn_summary, display_pending}`, plus `verify: true` (a Verify turn, verdict or not: the page's `data-verify`), `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`) |
552
+ | `:turn_canceled` | Stop, hook stop, Ctrl-C | `{turn_id, cancellation_reason, cancelled_by?, duration_ms}` |
553
+ | `:turn_failed` | an error | `{turn_id, error_class, message, duration_ms, error_kind?, retryable?, host?, summary?}` |
554
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) |
555
555
 
556
556
  A turn's origin is a `TurnOrigin` (`lib/samagotchi/turn_origin.rb`, a `Data.define` of `client_id`, `enqueued_id`
@@ -549,7 +549,8 @@ fields: a field added to an event later reaches nobody until it is named there.
549
549
  data's `viewer.id`), at most 50, forgotten when the link ends (a 401 with chi's words), never the owner's `GET
550
550
  /api/history` (the owner may open a link in the same browser). It lacks: the `/` list and plugin pills (a review turn
551
551
  refuses `/` lines), the delivery line and its steer/queue choices (every comment is `POST …/turn`, a queued review
552
- turn; Alt+Enter is Enter), the model picker, Stop turn, and images: the chips are mounted but `imagesOff` refuses a
552
+ turn; Alt+Enter is Enter), the model picker, Stop turn (the owner page's sits in its stage's status row; this
553
+ page has no stage), and images: the chips are mounted but `imagesOff` refuses a
553
554
  paste, drop or pick with one line (the image button is hidden) until participant images land. The grip's floor and
554
555
  the fold are the owner page's keys (`chi_composer_floor`, `chi_composer_collapsed`), per browser. The owner's
555
556
  participant view mounts none: its dock stays hidden.
@@ -10,6 +10,10 @@ 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
+ a little over its size today). A new feature goes into a module of its own, wired from `app.js` in a few lines; a
15
+ refactor that shrinks `app.js` ratchets the cap down with it. Never raise it: extract instead.
16
+
13
17
  **New exports go in new modules.** The static JS is served at unversioned URLs. Today every load revalidates
14
18
  (app.rb `serve_static` sends `Cache-Control: no-cache` with a content ETag, and `sw.js` has no fetch handler), but an
15
19
  open tab still holds the modules it loaded, and a proxy that ignores `no-cache` can serve an old one: a new export
@@ -19,7 +23,7 @@ page. A factory dep a new version stops passing is the same hazard the other way
19
23
  out of `send_turn.js`, `session_actions.js` and `lists_view.js`); a page loaded before keeps all its old modules
20
24
  until a reload, so the risk is a mixed cache, not an open tab. `answer_fold.js`, `details_motion.js`,
21
25
  `turn_range.js`, `history_reveal.js`, `latest.js`, `session_payload.js`, `method_api.js`, `session_store.js`,
22
- `session_fields.js`, `feature_gates.js`, `revise_stack.js` and `shell_command.js` are new modules for that reason.
26
+ `session_fields.js`, `feature_gates.js`, `revise_stack.js`, `revise_switch.js`, `shell_command.js`, `stale_params.js` and `popover_modal.js` are new modules for that reason.
23
27
 
24
28
  ## Two pages, two models
25
29
 
@@ -46,7 +50,7 @@ change.
46
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`) |
47
51
  | **`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` |
48
52
  | **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 |
49
- | **The history DOM** | the conversation as drawn: which turn a bubble belongs to (`data-turn-id`, `data-answer-turn`), kept command bubbles, which texts were shown, unnamed badges, folded answers (`.folded`), Revise stamps (`data-revises` on a prompt; a Verify turn's `data-verify`) and the stacks' folded versions (`.stack-hidden`, `.stack-stub.open`) | `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` 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) |
53
+ | **The history DOM** | the conversation as drawn: which turn a bubble belongs to (`data-turn-id`, `data-answer-turn`), 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) |
50
54
  | **localStorage** (`storage.js` `readStored`/`writeStored`, guarded) | per-browser choices only | the module that owns the choice | read at start (see Storage keys) |
51
55
  | **SSE** | see Streams | the server | owner page: events go into the DOM directly; participant page: an event marks the data stale |
52
56
 
@@ -104,10 +108,11 @@ a store write. A value written unchanged redraws nothing (a chunk with the same
104
108
  An input the store doesn't hold calls `store.touch(name)` where it changes: `infoBar` for the clock (liveTurn's
105
109
  `onTick`), the versions (`noticeVersions`), `restartingIds`, the stream (`startStream`, `workerGone`), the working
106
110
  set's and the participants' `onChange`, the continue and review in-flight sets
107
- (`session_actions.js`); `composer` and `infoBar` for `liveTurn.running` (`setTurnRunning`) and at the end of
108
- `renderSessionView` (a whole view drawn). Left as they are, on purpose: the `narrowScreen` listener calls
109
- `updateComposerMode()` (the bar doesn't read it), `applyStripHidden`/`applyView` call `updateSessionTitle()`, and
110
- `closeStream`/`stream.restart` set `streamControl = null` without a redraw (a resync or a workerGone follows).
111
+ (`session_actions.js`); `composer` and `infoBar` for `liveTurn.running` (`setTurnRunning`, with `settingsPanel`:
112
+ the open ⚙ panel's running-turn note) and at the end of `renderSessionView` (a whole view drawn). Left as they are,
113
+ on purpose: the `narrowScreen` listener calls `updateComposerMode()` (the bar doesn't read it),
114
+ `applyStripHidden`/`applyView` call `updateSessionTitle()`, and `closeStream`/`stream.restart` set
115
+ `streamControl = null` without a redraw (a resync or a workerGone follows).
111
116
 
112
117
  A function that writes more than once batches its synchronous part, so each redraw runs once: `select` (before its
113
118
  `await resync()`), `clearSelection`, the hub's `snapshot` and `session` events, `workerGone`, `setTurnRunning`,
@@ -197,8 +202,13 @@ Three, for one job. Pick by what the piece is:
197
202
 
198
203
  Listeners: every factory is a page-lifetime singleton, so `addEventListener` without a remove is fine; a per-render
199
204
  listener lands on a fresh node. A popover goes through `chip_popover.js createChipPopover` (its own outside-click,
200
- Esc, placement and resize); `context_chips.js`, `model_picker.js`, `ref_menu.js`, `command_list.js` and the lists'
201
- families/chains popovers still hand-roll theirs.
205
+ Esc, placement and resize); `context_chips.js` and the lists' families/chains popovers (`families_view.js
206
+ createFamilyPop`) still place their own. Every popover behaves the same (`popover_modal.js`): a modal dialog
207
+ (`modalDialog`: `aria-modal`, Tab wraps inside it), the opener's `aria-expanded` (a lit-drawn chip writes
208
+ `aria-expanded="false"` statically; the shell flips it), Esc closes it and focus goes back to the opener
209
+ (`focusBack`), and its `:hover` tints (the opener's too) sit in `@media(hover:hover)`, so a tap on a phone leaves
210
+ none behind. `model_picker.js`, `command_list.js` (listboxes: the focus stays in the input), `ref_menu.js` (a menu)
211
+ and `update_sheet.js` (a native modal `<dialog>`) aren't popovers in that sense.
202
212
 
203
213
  ## Stale answers
204
214
 
@@ -256,7 +266,7 @@ reach another device or the server.
256
266
  | `chi_markdown_tip_dismissed` | `markdown_tip.js` | the markdown tip dismissed |
257
267
  | `chi_update_available_shown` | app.js | update notices already shown |
258
268
  | `chi_notify`, `chi_push_device` | `notifications.js` (an injected storage) | the bell's state, this device's push id |
259
- | `chi_answer_fold:<session>` | `answer_fold.js`, `revise_stack.js` | the answers folded by hand, and the Revise stubs unfolded by hand (`stub:<turn id>`) (swept with the session list) |
269
+ | `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) |
260
270
  | `chi_review_history:<grant>`, `chi_review_history_session:<session>` | `review_history.js` (participant page) | a participant's composer history |
261
271
 
262
272
  sessionStorage: `chi_client_id` (`turn_events.js`: this tab's client id, kept over a reload mid-turn) and
data/docs/plugins.md CHANGED
@@ -1181,7 +1181,7 @@ a quiet `chi.init` task.
1181
1181
  ### Context providers in the manifest
1182
1182
 
1183
1183
  A bundle can turn URLs into sources for every process, without loading
1184
- plugins (`chi context add URL`, the web's "+ URL", `ctx.context.attach(url:)`):
1184
+ plugins (`chi context add URL`, `ctx.context.attach(url:)`):
1185
1185
 
1186
1186
  ```yaml
1187
1187
  # manifest.yml
@@ -2129,7 +2129,7 @@ chi bundle install github-pr # in the dev profile; needs gh, logged in
2129
2129
  Attaches the branch's open GitHub pull request to a session as `pr-<n>` when
2130
2130
  its worker starts (not in a scratch session or a delegate child; quietly
2131
2131
  nothing without `gh`, a repository or an open PR), and resolves PR URLs for
2132
- `chi context add` and the web's "+ URL". Its script prints the PR as text
2132
+ `chi context add`. Its script prints the PR as text
2133
2133
  and a summary of what changed in counts, authors and states only; it wakes
2134
2134
  the session for a review requesting changes, checks turning red, or the PR
2135
2135
  merged or closed. In the web, a `path:line` in a worker's answer links to