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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -1
- data/docs/architecture.md +2 -2
- data/docs/cli.md +15 -7
- data/docs/context.md +7 -9
- data/docs/guardrails.md +12 -5
- data/docs/internals/engine.md +4 -4
- data/docs/internals/participants.md +2 -1
- data/docs/internals/web-frontend.md +19 -9
- data/docs/plugins.md +2 -2
- data/docs/sessions.md +8 -8
- data/docs/testing.md +2 -1
- data/lib/samagotchi/bridge.rb +4 -0
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/desktop/macos.rb +3 -2
- data/lib/samagotchi/engine.rb +6 -1
- data/lib/samagotchi/guardrail_wiring.rb +3 -1
- data/lib/samagotchi/guardrails/chi_dirs.rb +2 -2
- data/lib/samagotchi/guardrails/mise_paths.rb +36 -0
- data/lib/samagotchi/guardrails/outside.rb +5 -5
- data/lib/samagotchi/guardrails/pair_boundary.rb +11 -4
- data/lib/samagotchi/guardrails/pair_command.rb +3 -19
- data/lib/samagotchi/guardrails/protected_paths.rb +5 -2
- data/lib/samagotchi/guardrails/review_paths.rb +3 -2
- data/lib/samagotchi/guardrails/shell_paths.rb +0 -4
- data/lib/samagotchi/guardrails/verdict.rb +1 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/message_display.rb +6 -3
- data/lib/samagotchi/web/participant_review.rb +2 -1
- data/lib/samagotchi/web/public/answer_fold.js +24 -6
- data/lib/samagotchi/web/public/app.js +24 -19
- data/lib/samagotchi/web/public/chains.js +6 -5
- data/lib/samagotchi/web/public/chip_popover.js +20 -42
- data/lib/samagotchi/web/public/composer.css +15 -11
- data/lib/samagotchi/web/public/composer_grip.js +11 -13
- data/lib/samagotchi/web/public/composer_mount.js +2 -2
- data/lib/samagotchi/web/public/context_chips.js +29 -98
- data/lib/samagotchi/web/public/copy.js +8 -5
- data/lib/samagotchi/web/public/data.js +2 -0
- data/lib/samagotchi/web/public/families_view.js +30 -38
- data/lib/samagotchi/web/public/index.html +176 -132
- data/lib/samagotchi/web/public/info_bar.js +19 -8
- data/lib/samagotchi/web/public/info_bar_model.js +3 -1
- data/lib/samagotchi/web/public/lists_view.js +69 -42
- data/lib/samagotchi/web/public/participants_panel.js +4 -4
- data/lib/samagotchi/web/public/popover_modal.js +57 -0
- data/lib/samagotchi/web/public/revise_stack.js +76 -6
- data/lib/samagotchi/web/public/revise_switch.js +55 -0
- data/lib/samagotchi/web/public/scope.js +3 -3
- data/lib/samagotchi/web/public/send_turn.js +4 -2
- data/lib/samagotchi/web/public/settings_chip.js +2 -2
- data/lib/samagotchi/web/public/stage_view.js +51 -2
- data/lib/samagotchi/web/public/stale_params.js +26 -0
- data/lib/samagotchi/web/public/start_settings.js +1 -1
- data/lib/samagotchi/web/public/start_toolkit.js +1 -1
- data/lib/samagotchi/web/public/stream_handlers.js +10 -5
- data/lib/samagotchi/web/public/working_set_rail.js +2 -0
- data/lib/samagotchi/web/public/working_set_view.js +2 -2
- data/lib/samagotchi/web/session_summary.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 57a409114b8194ae2d17416055b7f52c468e6a2ab5a650d8f36a4b6a0e0b0b3f
|
|
4
|
+
data.tar.gz: 78f20cbc62ab65e54c300bad75ec2478388810c1328679c3c48d3842f80cc0a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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`);
|
|
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
|
|
1035
|
-
|
|
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.
|
|
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,
|
|
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
|
|
234
|
-
attaches a URL; the web never adds a command.
|
|
233
|
+
source). The web attaches nothing itself: `chi context add <URL>` does.
|
|
235
234
|
|
|
236
235
|
## Waking
|
|
237
236
|
|
|
@@ -325,9 +324,8 @@ It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
|
|
|
325
324
|
open PR #42"). Not in a `chi scratch` session or a delegate child, and
|
|
326
325
|
quietly nothing without `gh`, a login, a repository or an open PR. Once
|
|
327
326
|
you remove it (`chi context rm`, the chip's detach) it stays removed:
|
|
328
|
-
the next start doesn't bring it back; `chi context add <PR URL>`
|
|
329
|
-
|
|
330
|
-
- `chi context add <PR URL>` and the web's "+ URL" attach any PR.
|
|
327
|
+
the next start doesn't bring it back; `chi context add <PR URL>` does.
|
|
328
|
+
- `chi context add <PR URL>` attaches any PR.
|
|
331
329
|
- `auto_attach` (below) can make it ask first, or attach nothing.
|
|
332
330
|
- The text: title, state, branch, description, reviews, comments (oldest
|
|
333
331
|
first) and checks, every 5 minutes.
|
|
@@ -358,10 +356,10 @@ bundles:
|
|
|
358
356
|
turns the card into a one-line notice. Not here on a PR that is attached
|
|
359
357
|
already changes nothing and says so: `chi context rm pr-42` removes it. A worker restart (idle exit,
|
|
360
358
|
resume) doesn't ask again; once the card has scrolled out of the last 20,
|
|
361
|
-
`/pr-attach 42` or
|
|
359
|
+
`/pr-attach 42` or `chi context add <PR URL>` still attaches it. `/pr-attach` and
|
|
362
360
|
`/pr-decline` act only on a PR offered in that session.
|
|
363
|
-
- `off`: nothing is attached or offered.
|
|
364
|
-
|
|
361
|
+
- `off`: nothing is attached or offered. `chi context add <PR URL>` and line
|
|
362
|
+
links work as before.
|
|
365
363
|
|
|
366
364
|
`true` and `false` (YAML reads a bare `on`/`off` as those) mean `attach` and
|
|
367
365
|
`off`; an unknown value is `attach`, with a warning in the log. The setting
|
|
@@ -392,7 +390,7 @@ links are display only (the model's text stays as it was), and terminals
|
|
|
392
390
|
show the answer as it is.
|
|
393
391
|
|
|
394
392
|
- **Which PR**: the session's attached PRs (the branch's, `chi context add
|
|
395
|
-
<PR URL
|
|
393
|
+
<PR URL>`) and the PR URLs in your messages, newest first (a
|
|
396
394
|
delegate child's task counts: a parent that names the PR there gets links
|
|
397
395
|
in the child's answers). PR URLs in tool output or answers don't count.
|
|
398
396
|
- **Which file**: a file the PR changes, by its path, a renamed file's old
|
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.
|
|
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 |
|
data/docs/internals/engine.md
CHANGED
|
@@ -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
|
|
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
|
|
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 `
|
|
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
|
|
108
|
-
`renderSessionView` (a whole view drawn). Left as they are,
|
|
109
|
-
`updateComposerMode()` (the bar doesn't read it),
|
|
110
|
-
`
|
|
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
|
|
201
|
-
|
|
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`,
|
|
1184
|
+
plugins (`chi context add URL`, `ctx.context.attach(url:)`):
|
|
1185
1185
|
|
|
1186
1186
|
```yaml
|
|
1187
1187
|
# manifest.yml
|
|
@@ -2129,7 +2129,7 @@ chi bundle install github-pr # in the dev profile; needs gh, logged in
|
|
|
2129
2129
|
Attaches the branch's open GitHub pull request to a session as `pr-<n>` when
|
|
2130
2130
|
its worker starts (not in a scratch session or a delegate child; quietly
|
|
2131
2131
|
nothing without `gh`, a repository or an open PR), and resolves PR URLs for
|
|
2132
|
-
`chi context add
|
|
2132
|
+
`chi context add`. Its script prints the PR as text
|
|
2133
2133
|
and a summary of what changed in counts, authors and states only; it wakes
|
|
2134
2134
|
the session for a review requesting changes, checks turning red, or the PR
|
|
2135
2135
|
merged or closed. In the web, a `path:line` in a worker's answer links to
|