samagotchi 0.154.0 → 0.154.1
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 +15 -1
- data/docs/cli.md +9 -7
- data/docs/internals/bridge.md +18 -5
- data/docs/internals/engine.md +1 -1
- data/docs/internals/prompt-caching.md +3 -1
- data/docs/internals/web-frontend.md +2 -2
- data/docs/sessions.md +1 -1
- data/docs/testing.md +15 -0
- data/lib/samagotchi/answer_text.rb +1 -2
- data/lib/samagotchi/bridge/card_places.rb +56 -7
- data/lib/samagotchi/bridge/card_store.rb +24 -9
- data/lib/samagotchi/bridge.rb +9 -0
- data/lib/samagotchi/bridge_client.rb +9 -5
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundles/btw/manifest.yml +2 -2
- data/lib/samagotchi/bundles/btw/plugin.rb +1 -1
- data/lib/samagotchi/bundles/coordinator/manifest.yml +2 -2
- data/lib/samagotchi/bundles/coordinator/plugin.rb +3 -2
- data/lib/samagotchi/bundles/github-pr/manifest.yml +2 -2
- data/lib/samagotchi/bundles/github-pr/plugin.rb +3 -3
- data/lib/samagotchi/bundles/skills/manifest.yml +2 -2
- data/lib/samagotchi/bundles/skills/plugin.rb +3 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +1 -1
- data/lib/samagotchi/engine_snapshots.rb +9 -8
- data/lib/samagotchi/idle_recap.rb +3 -1
- data/lib/samagotchi/kernel_loop.rb +4 -3
- data/lib/samagotchi/llm/chat_loop.rb +3 -1
- data/lib/samagotchi/session_manager.rb +8 -13
- data/lib/samagotchi/session_retention.rb +24 -4
- data/lib/samagotchi/steer.rb +12 -1
- data/lib/samagotchi/stream_event.rb +22 -0
- data/lib/samagotchi/system_prompt.rb +52 -36
- data/lib/samagotchi/terminal_ui/attached_view.rb +0 -9
- data/lib/samagotchi/terminal_ui/input_support.rb +16 -15
- data/lib/samagotchi/terminal_ui/join_replay.rb +16 -12
- data/lib/samagotchi/terminal_ui/reline_seam.rb +0 -10
- data/lib/samagotchi/terminal_ui/status_row.rb +0 -7
- data/lib/samagotchi/tool_runner.rb +2 -1
- data/lib/samagotchi/turn_flow.rb +7 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +3 -1
- data/lib/samagotchi/web/message_display.rb +7 -0
- data/lib/samagotchi/web/public/card_stamps.js +27 -0
- data/lib/samagotchi/web/public/cards_place.js +5 -3
- data/lib/samagotchi/web/public/composer_mode.js +1 -1
- data/lib/samagotchi/web/public/format.js +3 -2
- data/lib/samagotchi/web/public/turn_html.js +8 -4
- data/lib/samagotchi/web/session_access_routes.rb +8 -0
- data/lib/samagotchi/web/session_show.rb +4 -0
- data/lib/samagotchi/worker.rb +8 -5
- data/lib/samagotchi.rb +0 -1
- metadata +3 -2
- data/lib/samagotchi/pending_input_queue.rb +0 -55
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9533994f61b5607aef4d2dc6318cd6e885a0331c163643bd06d3c16f5e6f03c0
|
|
4
|
+
data.tar.gz: b768811c50bb587fcf4d16696ab69b857c75168067fd521270d871954169874e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2064776b06d42184107924bc44391f46681d755437e2c98a2e7c24205e900aba33bd8d3d792c7cea4dd6bdbf2813870395112db7315dbec33f92e198267c4ddc
|
|
7
|
+
data.tar.gz: a826305fa133e9ca4d1bd62e112ca7464fcf932f91292dc4112286e7f8f501c167fc18ccfeeb12dd24bec649e24636c810a715417cac45f454a19b4ba56f2b05
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,19 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.154.1] - 2026-11-07
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- chi web refuses a scratch session's page and stream (409 `scratch`, in the words every other client gets) when a tab is opened at its id by hand, and a scratch worker counts only its terminal's stream: such a tab no longer keeps the worker up after its terminal is killed.
|
|
16
|
+
- A card in chi web comes back where it was after a reload: the page finds the turn it came after (or `/continue no`'s reason line) by its id instead of counting the turns drawn after it, so the page and the worker can no longer disagree on the count.
|
|
17
|
+
- A Qwen model's turn that was asked to finish an open tool call no longer splits in two after a reload of chi web: the request to finish it is part of the turn, drawn as nothing, as it was live.
|
|
18
|
+
- chi web starting just as `chi scratch` did no longer deletes the new scratch session before its worker takes it (the worker crashed with "Session not found" and the terminal said the Bridge did not start in time): the retention sweep, `chi sessions prune` and `clean` leave an unowned scratch session alone for 30 s after its last save.
|
|
19
|
+
- A terminal joining a session (`chi --attach`, `--resume`) shows the last turn's own prompt instead of a Qwen model's request to finish an open tool call, with the input merged into that turn on its `input>` lines; the idle recap no longer counts either as a turn of its own.
|
|
20
|
+
- A turn no longer fails when a plugin changes its tools or prompt section (or a profile change drops the system prompt) at the moment the system prompt is built.
|
|
21
|
+
|
|
22
|
+
Update with `chi update` (btw 0.1.5, coordinator 0.8.1, github-pr 0.6.4, skills 0.1.10: comments only; the desktop helper rebuilds once).
|
|
23
|
+
|
|
11
24
|
## [0.154.0] - 2026-11-07
|
|
12
25
|
|
|
13
26
|
### Changed
|
|
@@ -5759,7 +5772,8 @@ and long-lived sessions.
|
|
|
5759
5772
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
5760
5773
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
5761
5774
|
|
|
5762
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.154.
|
|
5775
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.154.1...HEAD
|
|
5776
|
+
[0.154.1]: https://github.com/dm1try/samagotchi/compare/v0.154.0...v0.154.1
|
|
5763
5777
|
[0.154.0]: https://github.com/dm1try/samagotchi/compare/v0.153.0...v0.154.0
|
|
5764
5778
|
[0.153.0]: https://github.com/dm1try/samagotchi/compare/v0.152.0...v0.153.0
|
|
5765
5779
|
[0.152.0]: https://github.com/dm1try/samagotchi/compare/v0.151.0...v0.152.0
|
data/docs/cli.md
CHANGED
|
@@ -460,7 +460,7 @@ invalid option (exit 1).
|
|
|
460
460
|
| `--llm-context-budget TOKENS` | This session's own context budget: `64000`, `64k` (4k to 10M), or `off` (wins over the model's budget). |
|
|
461
461
|
| `--no-interrupt` | Raise the tool-call limit to 1000 iterations for long tasks. |
|
|
462
462
|
| `--no-default-input` | Skip the default input (`SAMAGOTCHI_DEFAULT_INPUT`) typed into a new session's first prompt. |
|
|
463
|
-
| `-v`, `--verbose` | Log at debug level (raw LLM responses, tool call/result payloads). Attached, the terminal shows this session's debug records above the prompt as they come, and a worker this chi starts logs at debug too; a worker that was already running keeps its own level (a one-line note says so). With `--non-interactive`, every log record goes to stderr too. This machine only (exit 2 with `--remote`). |
|
|
463
|
+
| `-v`, `--verbose` | Log at debug level (raw LLM responses, tool call/result payloads). Attached, the terminal shows this session's debug records above the prompt as they come, and a worker this chi starts logs at debug too; a worker that was already running keeps its own level (a one-line note says so). `chi web -v` logs at debug too (its `/api/` requests among them) and so does every worker it starts from then on (a new session, a wake, a restart: they all take `SAMAGOTCHI_LOG_LEVEL=debug`, `Config.cli_env`), into the same log; a worker already running keeps its level. With `--non-interactive`, every log record goes to stderr too. This machine only (exit 2 with `--remote`). |
|
|
464
464
|
| `--version` | Print `chi <version>` and exit (`chi self` shows it with the paths). |
|
|
465
465
|
|
|
466
466
|
Every setting in the config registry (`lib/samagotchi/config.rb`) that exposes a CLI
|
|
@@ -537,13 +537,15 @@ refused. Its first line says it is a scratch session.
|
|
|
537
537
|
session (`Scratch session deleted.`). `/archive` is refused. The lines typed
|
|
538
538
|
are not added to the prompt history. With `-p … --non-interactive` it is one
|
|
539
539
|
turn in this process, and the session is deleted as the run ends.
|
|
540
|
-
- The worker deletes the session too once its terminal is gone (no
|
|
541
|
-
left for about
|
|
542
|
-
stream that reconnects keeps it; or none came within 30 s), so a
|
|
543
|
-
terminal window or a `kill -9` of the terminal leaves nothing: a turn
|
|
544
|
-
running then is cancelled first.
|
|
540
|
+
- The worker deletes the session too once its terminal is gone (no terminal
|
|
541
|
+
stream left for about 28 s, twice the terminal's reconnect retries, so a
|
|
542
|
+
dropped stream that reconnects keeps it; or none came within 30 s), so a
|
|
543
|
+
closed terminal window or a `kill -9` of the terminal leaves nothing: a turn
|
|
544
|
+
still running then is cancelled first. Another client's stream never keeps
|
|
545
|
+
it. It refuses `chi sessions restart`.
|
|
545
546
|
- It never shows in `chi web`, and only its terminal uses it: `chi send`, the
|
|
546
|
-
web's routes
|
|
547
|
+
web's routes (a tab opened at its id too: its page and stream answer 409
|
|
548
|
+
`scratch`), `chi --resume` and `--attach` refuse it (exit 1), in one
|
|
547
549
|
wording: "that's a chi scratch session: only the terminal that started it
|
|
548
550
|
uses it (a leftover one is deleted at the next sweep: chi sessions clean)".
|
|
549
551
|
A session left behind when both died is marked `"scratch": true` in its
|
data/docs/internals/bridge.md
CHANGED
|
@@ -16,7 +16,7 @@ rescues its own errors: an observer must never break the running turn.
|
|
|
16
16
|
| `RingBuffer` | `ring_buffer.rb` | The last `ring_capacity` (256) raw events by `event_seq`, in memory, for a reconnect's replay. The capture observer feeds it. |
|
|
17
17
|
| `TurnAccumulator` | `turn_accumulator.rb` | The turn in progress, folded from events. |
|
|
18
18
|
| `CardStore` | `card_store.rb` | The last cards and notices, saved in `cards.json`, each anchored to its turn. |
|
|
19
|
-
| `CardPlaces` | `card_places.rb` | Pure: where `CardStore`'s entries sit in the messages the page draws (`turns_since`). |
|
|
19
|
+
| `CardPlaces` | `card_places.rb` | Pure: where `CardStore`'s entries sit in the messages the page draws (`turns_since`, and the `opening_id` stamp). |
|
|
20
20
|
| `PendingCard` | `pending_card.rb` | `pending_card.json` `{id:, bundle:}`: the running turn's open card with actions (check-in's Nudge), written and removed with that card, so chi web's hub, which watches files and not workers, can notify. |
|
|
21
21
|
|
|
22
22
|
Per connection there is an `SSEWriter` (with a `BoundedQueue`); `ClientEvent` and `EventId` are pure helpers. The
|
|
@@ -57,7 +57,9 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
|
|
|
57
57
|
the page's `TURN_PROMPT` and the recap count) after the last one whose id is in the log at or before the entry's, else from
|
|
58
58
|
the first one whose id is later in the log, else 0. The log only orders; the messages count. So a turn with no opening (a
|
|
59
59
|
reminder, a Continue, a failed or rolled-back one, a prompt stopped before the model) moves nothing, a wake's note does, and
|
|
60
|
-
an opening with no id (`/continue no`'s reason,
|
|
60
|
+
an opening with no turn id (`/continue no`'s reason, which carries an `opening_id` of its own instead, `TurnFlow#abort_continue!`)
|
|
61
|
+
counts as on the page. KernelLoop's Qwen recovery line (kind `recovery`, `Steer.recovery?`) is a row of its turn, no opening:
|
|
62
|
+
the page draws nothing for it (`MessageDisplay`), as live. Nothing orders such an opening
|
|
61
63
|
against an entry, so the store reads the session's messages (its `messages:` lambda) when an entry is shown between turns, and
|
|
62
64
|
at the turn's end for what a running turn showed that isn't its own: `past_idless` is how many of them, right after the opening
|
|
63
65
|
it counts from, the entry already came after, and they don't count for it. A `!rollback` usually only shortens that run, and the count
|
|
@@ -66,7 +68,15 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
|
|
|
66
68
|
anchored to T2, now counts from T1 and reads above the reason and the first card). That rare inversion is accepted. A turn's own rows don't read it: they count such a line, which sits below them. `current` is a card of the running turn. A card that
|
|
67
69
|
comes mid-turn but isn't the turn's (an anytime command's, such as `/btw`) goes after that turn (`during`), and so does a
|
|
68
70
|
between-turns notice. A card with an earlier card's id replaces it in place and keeps its anchor. A `list` with no messages
|
|
69
|
-
(`?cards=1`'s bodies, `chi bundle try`'s trace) has no `turns_since`.
|
|
71
|
+
(`?cards=1`'s bodies, `chi bundle try`'s trace) has no `turns_since` and no `opening_id`.
|
|
72
|
+
- **The stamp.** Next to `turns_since`, an entry lists `opening_id` (`CardPlaces#opening_of`): the id of the opening its count
|
|
73
|
+
reaches, the one it comes after: a prompt's `turn_id` (a wake note's too), or `/continue no`'s reason line's `opening_id`. It is
|
|
74
|
+
resolved at `list` time from the same anchor (an entry's own turn often has no bubble: a Continue, a failed or rolled-back turn),
|
|
75
|
+
so the stamp and the count always name one place, and the page finds it by the bubble (`card_stamps.js`: `data-turn-id`, or `data-opening-id` on a wake's note or a reason line), not by counting
|
|
76
|
+
bubbles. An entry shown after reason lines also saves which one it came after (`after_opening`, beside `past_idless`): read
|
|
77
|
+
first while that line is still in the run, so a later release can drop the count. No stamp (the page counts `turns_since`) for
|
|
78
|
+
an entry before every opening, after a line an older chi wrote with no id, or from a version 1 file (`base_since`). A rollback
|
|
79
|
+
keeps today's rule: a rolled-back turn's cards name the opening before it.
|
|
70
80
|
- **A turn that leaves nothing** is read, not told: an in-turn entry not `current` whose turn has no opening in the messages
|
|
71
81
|
(a `!rollback`, a failed turn with nothing kept, a prompt stopped before the model) lists as a between-turns entry at its
|
|
72
82
|
place, a card without its actions and a hook's notice without its step; its other rows, having no step to sit in, aren't
|
|
@@ -84,7 +94,9 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
|
|
|
84
94
|
is what chi web reads for a session no worker runs. The file is `{version: 2, turn_log:, entries:}`. The log is pruned at
|
|
85
95
|
a turn's start and end and at an idle show (a running turn's rows reuse the start's, as finding it walks the messages) to start at the opening the earliest anchor counts from (all of it while an entry has none, or for a store with no
|
|
86
96
|
`messages:`), so a restart reads an entry as the live store did; an old card therefore keeps it whole. `seed` drops an entry
|
|
87
|
-
whose `turn_id`, `base_since` or `past_idless` isn't an id or a non-negative integer.
|
|
97
|
+
whose `turn_id`, `after_opening`, `base_since` or `past_idless` isn't an id or a non-negative integer. A file 0.154.0 wrote
|
|
98
|
+
(no `after_opening`) lists the same stamps, from its counts; an older chi reading this one keeps `after_opening` (it seeds
|
|
99
|
+
entries whole) and lists it, unused. Load warnings
|
|
88
100
|
are the worker's own (each start announces them) and an unanswered question waits for no one: neither is saved or seeded. An
|
|
89
101
|
entry from an earlier worker lists with `earlier: true`; one shown during a turn comes back **without its actions** (their
|
|
90
102
|
turn no longer runs), while a between-turns card keeps its buttons.
|
|
@@ -152,7 +164,8 @@ The Bridge exposes:
|
|
|
152
164
|
`?from_seq=` (a plain `event_seq` is still accepted); a `: ping` heartbeat keeps idle proxies alive;
|
|
153
165
|
too-old reconnects, and cursors from another worker's epoch, receive a `reset` marker carrying
|
|
154
166
|
`session_state_snapshot`. `?snapshot=1` joins with a snapshot frame instead of a replay;
|
|
155
|
-
`?client_id=` names whose stream it is (`Bridge#open_streams_except`, used by `POST /exit`
|
|
167
|
+
`?client_id=` names whose stream it is (`Bridge#open_streams_except`, used by `POST /exit`; `Bridge#open_streams_of`,
|
|
168
|
+
by a scratch worker, which counts only `tui:` streams: a web tab's, through chi web, names none).
|
|
156
169
|
Each event goes out as clients get it (`Bridge::ClientEvent.project`, in `SSEWriter#write_event`, live
|
|
157
170
|
and replayed alike); `generation_chunk`, `tool_call_started` and `turn_completed` are projected to the
|
|
158
171
|
contracts below. The ring retains raw events for replay and `?raw=1`.
|
data/docs/internals/engine.md
CHANGED
|
@@ -176,7 +176,7 @@ Stopped before the model was asked (prepare_turn returned false):
|
|
|
176
176
|
**Degrading.** Work that has a fallback when it fails (a prompt section left out, an empty list from the Bridge) runs
|
|
177
177
|
in `Degrade.call(area, event, fallback:, rescue:) { … }` (`degrade.rb`): same rescue class and fallback as a bare rescue,
|
|
178
178
|
plus one WARN in the debug log per process for each `[area, event]` (DEBUG after; nothing on stderr). A bare `rescue`
|
|
179
|
-
whose body is only a literal (`nil`, `false`, `[]`, `{}`, `""`, `0`) is flagged in lib by the `Samagotchi/SilentRescue`
|
|
179
|
+
whose body is only a literal (`nil`, `false`, `[]`, `{}`, `""`, `0`, or an array of only those: `[nil, nil]`) is flagged in lib by the `Samagotchi/SilentRescue`
|
|
180
180
|
cop; a silence that is right (a client gone, a routine probe, Log's own internals) carries `# degrade-ok: why`.
|
|
181
181
|
|
|
182
182
|
## The model↔tool loop
|
|
@@ -67,7 +67,9 @@ right-hand column happens.
|
|
|
67
67
|
the loop, the thinking level, the turn's LLM context layers; a `/thinking` change builds under another key). All
|
|
68
68
|
of them are dropped only on `@prompt_builder.reset!`: a model switch (`Engine#switch_model!`), a plugin's
|
|
69
69
|
`tools_changed!` or `prompt_changed!` (`Engine#prompt_changed!`), a profile change, or a toolkit change
|
|
70
|
-
(`Engine#apply_toolkit!`). A per-turn rebuild would break everything below it.
|
|
70
|
+
(`Engine#apply_toolkit!`). A per-turn rebuild would break everything below it. `reset!` swaps in a new cache
|
|
71
|
+
(no lock; it runs on plugin and Bridge threads, and inside a build through the profile lookup): a build it lands
|
|
72
|
+
in returns the same bytes but is not kept, so the next build reads the sources again.
|
|
71
73
|
- The tools layer is shared **per toolkit** (configuration.md, "Toolkits"): sessions on the same toolkit share it,
|
|
72
74
|
and a chain's links keep theirs. A session's toolkit is saved when it is created and never resolved again from
|
|
73
75
|
config. A change (`/toolkit`, `ctx.use_toolkit`) is applied only at a turn's start (`Engine#apply_toolkit!`,
|
|
@@ -24,7 +24,7 @@ page. A factory dep a new version stops passing is the same hazard the other way
|
|
|
24
24
|
out of `send_turn.js`, `session_actions.js` and `lists_view.js`); a page loaded before keeps all its old modules
|
|
25
25
|
until a reload, so the risk is a mixed cache, not an open tab. `answer_fold.js`, `details_motion.js`,
|
|
26
26
|
`turn_range.js`, `history_reveal.js`, `latest.js`, `session_payload.js`, `method_api.js`, `session_store.js`,
|
|
27
|
-
`session_fields.js`, `feature_gates.js`, `revise_stack.js`, `revise_switch.js`, `shell_command.js`, `stale_params.js`, `popover_modal.js`, `more_sheet.js`, `hash_intent.js` (what the hash asks for: session, all, start or keep), `worker_features.js`, `version_notices.js`, `owner_claim.js`, `owner_verify.js`, `composer_mode.js`, `strip_hidden.js`, `block_open.js`, `text_roots.js`, `block_source.js`, `block_frame.js`, `block_host.js` and the `objectives_*.js` modules are new modules for that reason. The last three `block_*` draw a bundle's fenced blocks: [block-renderers.md](block-renderers.md).
|
|
27
|
+
`session_fields.js`, `feature_gates.js`, `revise_stack.js`, `revise_switch.js`, `shell_command.js`, `stale_params.js`, `popover_modal.js`, `more_sheet.js`, `hash_intent.js` (what the hash asks for: session, all, start or keep), `worker_features.js`, `version_notices.js`, `owner_claim.js`, `owner_verify.js`, `composer_mode.js`, `strip_hidden.js`, `block_open.js`, `text_roots.js`, `block_source.js`, `block_frame.js`, `block_host.js`, `card_stamps.js` and the `objectives_*.js` modules are new modules for that reason. The last three `block_*` draw a bundle's fenced blocks: [block-renderers.md](block-renderers.md).
|
|
28
28
|
|
|
29
29
|
## Two pages, two models
|
|
30
30
|
|
|
@@ -51,7 +51,7 @@ change.
|
|
|
51
51
|
| **`store.current`** (`session_state.js`: `emptySessionState` / `sessionStateFrom`; per field: `session_fields.js FIELDS`) | the open session's fields, about 40 (info bar, chips, verdicts, timing, worker, stream cursor) | `store.update` / `setStatus` from app.js, `stream_handlers.js`, `send_turn.js` (`firstPreview`, `owner`), `session_actions.js` (`status`), `owner_verify.js` (`verdicts`, `verifyFresh`); `forgetSession`'s `replaceCurrent`. Two writers in place, on purpose (D8 in `FIELDS`): live_timing.js's `activeTurn`, verdict_fresh.js's running list | the store runs the field's redraws (`FIELDS[key].redraws`) |
|
|
52
52
|
| **`store.sessions`** (the hub's projection) | every card | `store.update({sessions})` from app.js: `openSessionEvents` (`applySessionEvent`), `refresh` (the list fallback), `addCreatedSession`, `applyView` (re-sorted), `replaceSelectedCard` (`setLiveStatus` through `setStatus`, `readTurnEnd`, `setUsedMemories`), the stream's close (`withoutWorker`); `session_actions.js` (`setSessions`) | the store: `lists` and `infoBar`. **Always a new array**; an entry is replaced, never changed in place. `followSelectedSession` copies the open one's owner and worker (`fieldsFromCard`) and, when not streaming, its status (`setStatus(…, "hub")`) into `store.current` |
|
|
53
53
|
| **Factory closures**: the `create*` factories (popovers, panels, views) | each component's own state: open-for, busy, error, its own fetched list | the factory | an `onChange` hook back into app.js, or a getter the factory reads at draw time |
|
|
54
|
-
| **The history DOM** | the conversation as drawn: which turn a bubble belongs to (`data-turn-id`, `data-answer-turn`), what opens a turn (`TURN_PROMPT`, `bubbles.js`: a prompt or a wake's note, the same turns `Steer.turn_opening?` counts for a card's or the recap's `turns_since`), kept command bubbles, which texts were shown, unnamed badges, folded answers and long prompts (`.folded`), Revise stamps (`data-revises` on a prompt; a Verify turn's `data-verify`) and the stacks' folded versions (`.stack-hidden`, `.stack-stub.open`); the switcher's version on view is `revise_stack.js`'s own (a copy `.stack-view` before the top answer, the top `.stack-swapped`; never stored) | `bubbles.js`, `cards_place.js`, `question_cards.js`, `turn_block.js`/`stage_view.js`, `answer_fold.js`, `revise_stack.js`, `stream_handlers.js` (a live `turn_completed`'s `revises` and `verify` onto its prompt) | read back as state: `turn_range.js turnElements` (one walker for a turn's elements), `keptCommandBubbles`, `seedSeenFromDom`, `nameUnnamedBadges`/`nameVerdictCards`, `answer_fold.js` and `revise_stack.js` (their observers decorate every path that fills `#history`; the stacks are `stacks()` of the stamps, read again on each change, never a store field), `history_reveal.js revealInHistory` (the one way a jump unfolds what hides its target) |
|
|
54
|
+
| **The history DOM** | the conversation as drawn: which turn a bubble belongs to (`data-turn-id`, `data-answer-turn`), what opens a turn (`TURN_PROMPT`, `bubbles.js`: a prompt or a wake's note, the same turns `Steer.turn_opening?` counts for a card's or the recap's `turns_since`), an opening's id for a card's stamp (`data-turn-id` on a prompt, `data-opening-id` on a wake's note or `/continue no`'s reason line; `card_stamps.js` places a snapshot's card after the stamped opening, by its count only without one), 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) |
|
|
55
55
|
| **localStorage** (`storage.js` `readStored`/`writeStored`, guarded) | per-browser choices only | the module that owns the choice | read at start (see Storage keys) |
|
|
56
56
|
| **SSE** | see Streams | the server | owner page: events go into the DOM directly; participant page: an event marks the data stale |
|
|
57
57
|
|
data/docs/sessions.md
CHANGED
|
@@ -110,7 +110,7 @@ chi sessions clean --dry-run --days 7 # test sessions older than 7 da
|
|
|
110
110
|
|
|
111
111
|
- New sessions set `test_run:true` when `SAMAGOTCHI_ENV=test` or `RACK_ENV=test` or `CI` is set (explicit flag, `metadata_version` 2). Old sessions without the flag load as `test_run:false`.
|
|
112
112
|
- Test runs are tagged and obey the same retention. `chi sessions clean` deletes every test session whatever its age (`--days N`: only those older than N days); a live worker or a `keep_status` status still keeps one. `chi sessions prune --test-only` applies the usual age and count rules to test sessions only.
|
|
113
|
-
- A `chi scratch` session (`"scratch": true`) is deleted when its terminal leaves (its worker deletes it too once its last client is gone); one left behind when both died is deleted by the next sweep, `prune` or `clean`, whatever its age or status, once nobody owns it. `list` shows it as `[scratch]` (every text form; `scratch: true` in `--format json`); `chi web` never shows one. See [CLI: Scratch sessions](cli.md#scratch-sessions).
|
|
113
|
+
- A `chi scratch` session (`"scratch": true`) is deleted when its terminal leaves (its worker deletes it too once its last client is gone); one left behind when both died is deleted by the next sweep, `prune` or `clean`, whatever its age or status, once nobody owns it and its file is 30 s old (`SessionRetention::SCRATCH_START_GRACE`, the worker start grace: `chi scratch` saves the session before the worker it spawns takes the owner lock). `list` shows it as `[scratch]` (every text form; `scratch: true` in `--format json`); `chi web` never shows one. See [CLI: Scratch sessions](cli.md#scratch-sessions).
|
|
114
114
|
- For ad-hoc manual QA use `SAMAGOTCHI_ENV=test XDG_STATE_HOME=/tmp/chi-test-$USER chi ...` to isolate from real state; the flag also marks sessions that `chi web` or an attached `chi` spawn (their workers inherit the environment), so `clean` finds them if they land in the real state.
|
|
115
115
|
|
|
116
116
|
## Session file
|
data/docs/testing.md
CHANGED
|
@@ -60,6 +60,21 @@ paths (replace them with placeholders), no PATH tools (the suite pins `rg` as fo
|
|
|
60
60
|
that sets PATH to test the lookup is tagged `:real_rg`), no Ruby-version-dependent `inspect` (a Hash's or an
|
|
61
61
|
exception's text changes between Ruby versions: write the string yourself).
|
|
62
62
|
|
|
63
|
+
The whole-turn goldens (`spec/engine_turn_golden_spec.rb`, `spec/fixtures/turn_goldens/`) run a real
|
|
64
|
+
`Engine#run_turn` (its own KernelLoop, ChatLoop, ToolRunner, Gate and GuardrailWiring) over `FakeProviderServer` on
|
|
65
|
+
the native (llama.cpp `/completion`, Qwen tool calls), `api: openai` and `api: anthropic` wires: a turn of two tool
|
|
66
|
+
iterations (a muted `memory_read` and a `read`, then a `write` a config rule asks about, allowed through the question
|
|
67
|
+
flow), a `before_turn` hook switching the model (the request names the new model with the old one's sampling), and a
|
|
68
|
+
review participant's turn whose `write` the review boundary refuses. Each golden pins every request (the chi-set
|
|
69
|
+
headers, the body in its key order, the native prompt as text), the turn's events (keys sorted; the context-status
|
|
70
|
+
estimates and clock values masked), each `before_tool_call` payload, the approval asked and the session's messages.
|
|
71
|
+
System 1, the toolkit suggestion, the context judge, the recap and the warm-up are off by config, so the order is the
|
|
72
|
+
turn's own. The arch P3 refactors (TurnContext, turn stages, ToolSurface) keep them byte-identical: a phase that
|
|
73
|
+
changes one stops and asks. An intended change elsewhere: `UPDATE_PROMPTS=1 bundle exec rspec
|
|
74
|
+
spec/engine_turn_golden_spec.rb`, then read `git diff spec/fixtures/turn_goldens/`; a failure writes what the run got
|
|
75
|
+
to `tmp/turn_goldens/<golden>.actual` and names the `diff` to run. Changes to the turn path (engine, kernel loop, chat loop,
|
|
76
|
+
tool runner, gate, guardrail wiring, system prompt, tool declarations, registry) pick it in `script/affected-specs`.
|
|
77
|
+
|
|
63
78
|
The web specs run in plain node. One that loads lit-html, itself or through a module it imports (`turn_block.js`,
|
|
64
79
|
`turn_html.js`, `card.js`, `copy.js`, `images.js`, `scope.js`, `command_view.js`, `diff_view.js`, `read_view.js`,
|
|
65
80
|
`question_cards.js`, `stream_handlers.js`, `stage_view.js`, `bubbles.js`, `cards_place.js`, the chips, `info_bar.js`, `info_bar_model.js`, `lists_view.js`, `context_chips.js`, `families_view.js`,
|
|
@@ -13,7 +13,6 @@ module Samagotchi
|
|
|
13
13
|
# thoughts and call markup inline: both hosts' thoughts come out, a thought
|
|
14
14
|
# cut off before it closed too, then the calls and the wire tokens.
|
|
15
15
|
module AnswerText
|
|
16
|
-
QWEN_THINK = %r{<think>.*?</think>}m
|
|
17
16
|
# The template opened the block, so the text starts inside it.
|
|
18
17
|
TEMPLATE_OPENED_THINK = %r{\A.*?</think>}m
|
|
19
18
|
|
|
@@ -25,7 +24,7 @@ module Samagotchi
|
|
|
25
24
|
text = text.to_s.scrub
|
|
26
25
|
template_opened = !text.include?("<think>")
|
|
27
26
|
gemma = ToolCallParser::Gemma.new(ModelProfile.gemma4)
|
|
28
|
-
text = gemma.strip_thought(text).gsub(
|
|
27
|
+
text = gemma.strip_thought(text).gsub(OutputFormatter::QWEN_THINK_RE, "")
|
|
29
28
|
text = text.sub(TEMPLATE_OPENED_THINK, "") if template_opened
|
|
30
29
|
text = ToolCallParser.strip_calls(gemma.strip_tool_calls(text), "<tool_call>", "</tool_call>")
|
|
31
30
|
OutputFormatter.strip_markup(text) # drops a thought cut off before it closed
|
|
@@ -9,16 +9,22 @@ module Samagotchi
|
|
|
9
9
|
# the recap share (Steer.turn_opening?); the store's turn log only orders
|
|
10
10
|
# the ids. So a turn with no opening (a reminder, a Continue, a failed or
|
|
11
11
|
# rolled-back one, a prompt stopped before the model) counts nothing, and
|
|
12
|
-
# an opening with no id (/continue no's reason
|
|
13
|
-
#
|
|
14
|
-
# an entry says how many it came after (+past_idless+, #idless_after)
|
|
15
|
-
#
|
|
12
|
+
# an opening with no turn id (/continue no's reason) counts as it does on
|
|
13
|
+
# the page. Nothing orders such an opening against an entry, so
|
|
14
|
+
# an entry says how many it came after (+past_idless+, #idless_after), and
|
|
15
|
+
# which one that is when it has an id (+after_opening+, #after_opening).
|
|
16
|
+
#
|
|
17
|
+
# The same place as a stamp (#opening_of): the id of the opening the entry
|
|
18
|
+
# comes after (a prompt's turn_id, a line's opening_id), which the page
|
|
19
|
+
# finds by its bubble, not by counting. Pure: no Engine, no file.
|
|
16
20
|
class CardPlaces
|
|
17
21
|
# @param turn_log [Array<String>] the turn ids, oldest first
|
|
18
22
|
# @param messages [Array<Hash>] the messages the page draws
|
|
19
23
|
def initialize(turn_log, messages)
|
|
20
24
|
positions = turn_log.each_with_index.to_h
|
|
21
|
-
|
|
25
|
+
openings = Array(messages).select { |message| Steer.turn_opening?(message) }
|
|
26
|
+
@ids = openings.map { |message| message[:turn_id] || message["turn_id"] }
|
|
27
|
+
@stamps = openings.map { |message| stamp_of(message) }
|
|
22
28
|
@openings = @ids.size
|
|
23
29
|
@opened = @ids.compact.to_set
|
|
24
30
|
@places = @ids.map { |id| positions[id] }
|
|
@@ -45,13 +51,44 @@ module Samagotchi
|
|
|
45
51
|
entry[:base_since].to_i + since
|
|
46
52
|
end
|
|
47
53
|
|
|
54
|
+
# The opening +entry+ comes after, as #turns_since counts: its id (a
|
|
55
|
+
# prompt's turn_id, a line's opening_id), the page's stamp. nil when it
|
|
56
|
+
# comes before every opening, when that opening has no id (a line an
|
|
57
|
+
# older chi wrote), or for an entry from an older file (+base_since+):
|
|
58
|
+
# the page counts those (turns_since).
|
|
59
|
+
# @param entry [Hash] a store's entry: :turn_id, :past_idless, :after_opening, :base_since
|
|
60
|
+
# @return [String, nil]
|
|
61
|
+
def opening_of(entry)
|
|
62
|
+
return nil if entry[:base_since]
|
|
63
|
+
|
|
64
|
+
at_or_before = counted_from(entry[:turn_id])
|
|
65
|
+
return nil unless at_or_before
|
|
66
|
+
|
|
67
|
+
run = at_or_before..(at_or_before + idless_run(at_or_before))
|
|
68
|
+
# The line it saved it came after, while that line is in the run (as
|
|
69
|
+
# the count past it, the two save together); else the count's.
|
|
70
|
+
after = entry[:after_opening] && @stamps.index(entry[:after_opening])
|
|
71
|
+
@stamps[after && run.cover?(after) ? after : [at_or_before + entry[:past_idless].to_i, run.end].min]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The id of the last opening with no turn id right after the one
|
|
75
|
+
# +turn_id+ counts from (#idless_after's run), nil when there is none or
|
|
76
|
+
# it has no id either.
|
|
77
|
+
# @return [String, nil]
|
|
78
|
+
def after_opening(turn_id)
|
|
79
|
+
at_or_before = counted_from(turn_id)
|
|
80
|
+
return nil unless at_or_before
|
|
81
|
+
|
|
82
|
+
run = idless_run(at_or_before)
|
|
83
|
+
run.positive? ? @stamps[at_or_before + run] : nil
|
|
84
|
+
end
|
|
85
|
+
|
|
48
86
|
# The openings with no id right after the one +turn_id+ counts from: what
|
|
49
87
|
# an entry shown now has come after. A rollback can only shorten the run, so
|
|
50
88
|
# a count read back against it never eats a later turn.
|
|
51
89
|
# @return [Integer]
|
|
52
90
|
def idless_after(turn_id)
|
|
53
|
-
|
|
54
|
-
at_or_before = position >= 0 ? @last_at_or_before[position] : nil
|
|
91
|
+
at_or_before = counted_from(turn_id)
|
|
55
92
|
at_or_before ? idless_run(at_or_before) : 0
|
|
56
93
|
end
|
|
57
94
|
|
|
@@ -70,6 +107,18 @@ module Samagotchi
|
|
|
70
107
|
|
|
71
108
|
private
|
|
72
109
|
|
|
110
|
+
# The last opening whose turn is at or before +turn_id+'s in the log
|
|
111
|
+
# (an index into the openings), nil for none.
|
|
112
|
+
def counted_from(turn_id)
|
|
113
|
+
position = @positions.fetch(turn_id, -1)
|
|
114
|
+
position >= 0 ? @last_at_or_before[position] : nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def stamp_of(message)
|
|
118
|
+
id = message[:turn_id] || message["turn_id"] || message[:opening_id] || message["opening_id"]
|
|
119
|
+
id.is_a?(String) && !id.empty? ? id : nil
|
|
120
|
+
end
|
|
121
|
+
|
|
73
122
|
def idless_run(opening)
|
|
74
123
|
@ids.drop(opening + 1).take_while(&:nil?).size
|
|
75
124
|
end
|
|
@@ -26,7 +26,9 @@ module Samagotchi
|
|
|
26
26
|
# holds the id of the turn it belongs to or follows (+turn_id+: the running
|
|
27
27
|
# turn's, else the last one's, nil before any) and lists with +turns_since+,
|
|
28
28
|
# the turns drawn after it, counted from the messages the page draws
|
|
29
|
-
# (CardPlaces, with the turn log the store keeps),
|
|
29
|
+
# (CardPlaces, with the turn log the store keeps), +opening_id+, the same
|
|
30
|
+
# place as a stamp (the id of the opening it comes after, which the page
|
|
31
|
+
# finds by its bubble; none where only the count can say), and +current+ for a card
|
|
30
32
|
# of the turn running now. A card that isn't the turn's but comes while one
|
|
31
33
|
# runs (an anytime command's, such as /btw) goes after that turn: its prompt
|
|
32
34
|
# is already in the history. Listed against messages, an in-turn entry of a
|
|
@@ -70,7 +72,7 @@ module Samagotchi
|
|
|
70
72
|
SAVED_ON = (%i[turn_started card hook_notice empty_answer_retry steer_cut llm_context_edited question_requested question_answered
|
|
71
73
|
question_cancelled question_relay] + Events::TURN_END).freeze
|
|
72
74
|
# What only the store reads: never listed.
|
|
73
|
-
INTERNAL_FIELDS = %i[turn_id continued base_since past_idless during after_failed command].freeze
|
|
75
|
+
INTERNAL_FIELDS = %i[turn_id continued base_since past_idless after_opening during after_failed command].freeze
|
|
74
76
|
|
|
75
77
|
# What a session's saved file lists, as a store started from it lists
|
|
76
78
|
# (no turn running): the web's cards for a session no worker runs.
|
|
@@ -118,7 +120,8 @@ module Samagotchi
|
|
|
118
120
|
# them (a body re-read, a trace): no turns_since:, nothing to tell a turn
|
|
119
121
|
# that left nothing by.
|
|
120
122
|
# @return [Array<Hash>] oldest first: the card's (or notice's) fields
|
|
121
|
-
# plus current:, turns_since:
|
|
123
|
+
# plus current:, turns_since: and opening_id: (CardPlaces#opening_of,
|
|
124
|
+
# when it has one) with +messages+, earlier: true for one an
|
|
122
125
|
# earlier worker saved, during: true for a card that came while the
|
|
123
126
|
# running turn runs but isn't that turn's (it goes after the running
|
|
124
127
|
# turn's prompt), and after_failed: true for one shown after the last
|
|
@@ -141,6 +144,8 @@ module Samagotchi
|
|
|
141
144
|
|
|
142
145
|
out = shown.except(*INTERNAL_FIELDS)
|
|
143
146
|
out[:turns_since] = places.turns_since(entry) if places
|
|
147
|
+
stamp = places&.opening_of(entry)
|
|
148
|
+
out[:opening_id] = stamp if stamp
|
|
144
149
|
out[:current] = current
|
|
145
150
|
out[:during] = true if entry[:during]
|
|
146
151
|
out[:after_failed] = true if entry[:after_failed] && entry[:turn_id] == @failed_turn
|
|
@@ -255,7 +260,7 @@ module Samagotchi
|
|
|
255
260
|
# A hand-edited or foreign file's entry goes, rather than break every later
|
|
256
261
|
# listing of the session.
|
|
257
262
|
def sound_place?(entry)
|
|
258
|
-
return false unless entry[:turn_id].nil? ||
|
|
263
|
+
return false unless [entry[:turn_id], entry[:after_opening]].all? { |id| id.nil? || id.is_a?(String) }
|
|
259
264
|
|
|
260
265
|
entry.values_at(:base_since, :past_idless).compact.all? { |count| count.is_a?(Integer) && !count.negative? }
|
|
261
266
|
end
|
|
@@ -315,8 +320,8 @@ module Samagotchi
|
|
|
315
320
|
shown = @entries.select { |entry| entry[:turn_id] == @running_turn && !entry[:in_turn] }
|
|
316
321
|
return if shown.empty? || !@messages
|
|
317
322
|
|
|
318
|
-
|
|
319
|
-
shown.each { |entry| entry
|
|
323
|
+
places = CardPlaces.new(@turn_log, @messages.call)
|
|
324
|
+
shown.each { |entry| note_idless(entry, places) }
|
|
320
325
|
end
|
|
321
326
|
|
|
322
327
|
# The entry's anchor: the running turn, else the last one.
|
|
@@ -331,7 +336,7 @@ module Samagotchi
|
|
|
331
336
|
# button's command replaced, which nobody typed).
|
|
332
337
|
old = @entries[index]
|
|
333
338
|
@entries[index] = card.except(:command).merge(updated: true, **old.slice(:in_turn, :turn_id, :during, :continued, :base_since,
|
|
334
|
-
:past_idless, :after_failed, :command))
|
|
339
|
+
:past_idless, :after_opening, :after_failed, :command))
|
|
335
340
|
else
|
|
336
341
|
entry = card.merge(in_turn: event[:in_turn] ? true : false, turn_id: anchor)
|
|
337
342
|
entry[:during] = true if @running_turn && !event[:in_turn]
|
|
@@ -393,8 +398,18 @@ module Samagotchi
|
|
|
393
398
|
def note_past_idless(entry)
|
|
394
399
|
return unless @messages
|
|
395
400
|
|
|
396
|
-
|
|
397
|
-
|
|
401
|
+
note_idless(entry, CardPlaces.new(@turn_log, @messages.call))
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# How many openings with no turn id +entry+ came after, and the last
|
|
405
|
+
# one's own id when it has one (+after_opening+: its stamp, #list).
|
|
406
|
+
def note_idless(entry, places)
|
|
407
|
+
run = places.idless_after(entry[:turn_id])
|
|
408
|
+
return unless run.positive?
|
|
409
|
+
|
|
410
|
+
entry[:past_idless] = run
|
|
411
|
+
after = places.after_opening(entry[:turn_id])
|
|
412
|
+
entry[:after_opening] = after if after
|
|
398
413
|
end
|
|
399
414
|
|
|
400
415
|
# Which cap +entry+ counts against: :notice, :llm_context or :card.
|
data/lib/samagotchi/bridge.rb
CHANGED
|
@@ -196,6 +196,15 @@ module Samagotchi
|
|
|
196
196
|
@mutex.synchronize { @open_streams - (client_id ? @streams_by_client[client_id] : 0) }
|
|
197
197
|
end
|
|
198
198
|
|
|
199
|
+
# Streams held by the clients whose id starts with +prefix+
|
|
200
|
+
# (ClientId::TUI_PREFIX: the attached terminals, which a scratch worker
|
|
201
|
+
# counts, Worker#scratch_unattended?). A stream that named no client
|
|
202
|
+
# (a web tab's, through the chi web proxy) is none of them.
|
|
203
|
+
# @return [Integer]
|
|
204
|
+
def open_streams_of(prefix)
|
|
205
|
+
@mutex.synchronize { @streams_by_client.sum { |client_id, count| client_id.start_with?(prefix) ? count : 0 } }
|
|
206
|
+
end
|
|
207
|
+
|
|
199
208
|
# Monotonic time of the last request, stream open or stream close. The
|
|
200
209
|
# worker's idle exit counts from it. A connect that sends no request (the
|
|
201
210
|
# sidecar liveness probe) doesn't count.
|
|
@@ -309,12 +309,16 @@ module Samagotchi
|
|
|
309
309
|
# older worker's 404 for a route it lacks from a refused connect, a
|
|
310
310
|
# timeout or a 500.
|
|
311
311
|
# @return [Array(Integer, Object)] the status and the parsed JSON body
|
|
312
|
-
# (nil when it isn't JSON); [nil, nil] when no reply came
|
|
312
|
+
# (nil when it isn't JSON); [nil, nil] when no reply came (a refused
|
|
313
|
+
# connect, a timeout, a cut or garbled reply), and when anything else
|
|
314
|
+
# failed, which is logged
|
|
313
315
|
def get(path)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
Degrade.call(:bridge, "get_failed", fallback: [nil, nil], path: path) do
|
|
317
|
+
response = get_response(path)
|
|
318
|
+
[response.status, response.json]
|
|
319
|
+
rescue SystemCallError, IOError
|
|
320
|
+
[nil, nil]
|
|
321
|
+
end
|
|
318
322
|
end
|
|
319
323
|
|
|
320
324
|
# GET /session/:id/<path>, preserving the raw response and raising on I/O.
|
|
@@ -124,7 +124,7 @@ module Samagotchi
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
# The listing's placement fields: nothing to place in a one-turn run.
|
|
127
|
-
LISTING_FIELDS = %w[turns_since current earlier after_failed label].freeze
|
|
127
|
+
LISTING_FIELDS = %w[turns_since opening_id current earlier after_failed label].freeze
|
|
128
128
|
|
|
129
129
|
# The saved cards as the web reads them (CardStore.saved), with
|
|
130
130
|
# string keys and values, as the JSON trace prints them.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: btw
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.5
|
|
4
4
|
scope: system
|
|
5
5
|
description: "/btw <question>: a side answer about the conversation, as a card in every UI, even mid-turn; Keep as session forks it into a child session"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
|
-
sha256: sha256:
|
|
9
|
+
sha256: sha256:d4c0d6309308270a1430d64f9713e46c15d71ace1b068fefc22916aa8cc2d043
|
|
10
10
|
requires_chi: ">= 0.143.0"
|
|
11
11
|
# config.yml bundles: btw: (another key warns; an older chi ignores this)
|
|
12
12
|
settings: [max_tokens, timeout]
|
|
@@ -15,7 +15,7 @@ class Plugin
|
|
|
15
15
|
SYSTEM = "Answer the user's question about the conversation below, briefly. The user asks it on the side: " \
|
|
16
16
|
"don't continue the conversation's task, and don't say what you would do next."
|
|
17
17
|
# How many answers /btw keep can still fork: the last ones, for the
|
|
18
|
-
# worker's
|
|
18
|
+
# worker's life.
|
|
19
19
|
KEPT = 10
|
|
20
20
|
TITLE_CHARS = 72
|
|
21
21
|
EARLIER = "(About the conversation before the running turn.)"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: coordinator
|
|
3
|
-
version: 0.8.
|
|
3
|
+
version: 0.8.1
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Coordinate parallel work: the coordinator skill (split the work, one worktree and child session per task, check each report, merges under a guardrail approval (the git-merge rule) or a question, a handoff memory to resume from), /coordinate <goal>, /coordinate resume, /coordinate end, /coordinate review (experimental: a sprint review session to share) and /children; the coordinator toolkit (lean + delegate, delegate_result, list_sessions, objectives) and the coding toolkit for build children (child + be concise)"
|
|
6
6
|
trust_level: reviewed
|
|
@@ -10,7 +10,7 @@ files:
|
|
|
10
10
|
description: "Coordinate parallel work: split it, delegate each task wait:false in its own worktree (cwd:), check each report, merge under the git-merge approval or ask the user; resume from a handoff_* memory"
|
|
11
11
|
plugin:
|
|
12
12
|
file: plugin.rb
|
|
13
|
-
sha256: sha256:
|
|
13
|
+
sha256: sha256:540dd6369342ea4dcbc6ba5ee17fb20d980b880df614a475668c70115e7ec6b8
|
|
14
14
|
# the coordinator toolkit needs chi.toolkit and ctx.use_toolkit (0.66.0); the
|
|
15
15
|
# coding toolkit (chi.toolkit prompt:) and the skill's delegate toolkit: need
|
|
16
16
|
# the next chi.
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
# (ctx.sessions.stop, by session id) and shows the card again.
|
|
17
17
|
#
|
|
18
18
|
# /coordinate <goal> asks the model, in this session, to follow the skill
|
|
19
|
-
# for the goal. A
|
|
20
|
-
# is shown for the user
|
|
19
|
+
# for the goal. A session a one-turn run holds (-p --non-interactive, or an
|
|
20
|
+
# older chi's terminal) takes no messages: the request is shown for the user
|
|
21
|
+
# to send.
|
|
21
22
|
#
|
|
22
23
|
# /coordinate resume picks up a coordinator handoff: the open handoff_*
|
|
23
24
|
# memories of this project (an index line whose description doesn't start
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: github-pr
|
|
3
|
-
version: 0.6.
|
|
3
|
+
version: 0.6.4
|
|
4
4
|
scope: system
|
|
5
5
|
description: Proposes the branch's open GitHub pull request to a session, attached once you mention it (#N, the branch or its URL), and attaches any PR URL with chi context add or the web's "+ URL"; the agent gets a note when reviews, comments, checks or its state change; in the web, a path:line in an answer links to that line of the PR; the auto_attach setting can attach the PR at once instead, or do nothing
|
|
6
6
|
trust_level: reviewed
|
|
@@ -11,7 +11,7 @@ needs:
|
|
|
11
11
|
hint: brew install gh, then gh auth login
|
|
12
12
|
plugin:
|
|
13
13
|
file: plugin.rb
|
|
14
|
-
sha256: sha256:
|
|
14
|
+
sha256: sha256:87678c39565a3d586b462d0449a27ee3da4a96f53ef5b352dea959256f6c1eda
|
|
15
15
|
scripts:
|
|
16
16
|
pr_context.rb: sha256:a883d2780e89c68c0d7b6d5e0fcb22c273f17bb167e79064e0e99db6bd2ee6f0
|
|
17
17
|
context_providers:
|
|
@@ -29,11 +29,11 @@ require "samagotchi/git_env"
|
|
|
29
29
|
# diff" (or "File at head") action the web's ref menu offers. The
|
|
30
30
|
# after_turn hook links from a per-PR cache and never runs gh (the web holds the answer until the hooks are
|
|
31
31
|
# done); before_turn, before_generation and the init task fill the cache
|
|
32
|
-
# off the turn's thread (`gh api`), in a session worker only:
|
|
33
|
-
#
|
|
32
|
+
# off the turn's thread (`gh api`), in a session worker only: -p
|
|
33
|
+
# --non-interactive shows no web page while it runs (ctx.frontend; a chi
|
|
34
34
|
# without it counts as a worker), and a scratch session never fetches.
|
|
35
35
|
# `bundles: github-pr: line_links:` false turns it off, "always" fetches in
|
|
36
|
-
#
|
|
36
|
+
# -p --non-interactive too (still not for a scratch session).
|
|
37
37
|
#
|
|
38
38
|
# `bundles: github-pr: auto_attach:` says what the init task does with the
|
|
39
39
|
# branch's open PR: `propose` (the default, above), `attach` (attached at
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skills
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.10
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Skills (skill_<name> memories, the steps of a task done together): /skill save, list, show and diff; older versions kept with a short diff line on every update; a nudge when a followed skill's step fails"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
|
-
sha256: sha256:
|
|
9
|
+
sha256: sha256:8d72d29286d0807c18d2c37225455ca06d45f4f974f9335b76cc77916043838c
|
|
10
10
|
requires_chi: ">= 0.143.0"
|
|
11
11
|
# config.yml bundles: skills: (another key warns; an older chi ignores this)
|
|
12
12
|
settings: [history_keep, nudge]
|
|
@@ -242,8 +242,9 @@ class Plugin
|
|
|
242
242
|
# what was just done (the model has seen the commands; a side answer
|
|
243
243
|
# wouldn't). The request runs as a turn; sent while a turn runs it joins
|
|
244
244
|
# that turn at its next step, and the request says to finish the task
|
|
245
|
-
# first. A
|
|
246
|
-
# shown for the user to
|
|
245
|
+
# first. A session a one-turn run holds (-p --non-interactive, or an older
|
|
246
|
+
# chi's terminal) takes no messages: the request is shown for the user to
|
|
247
|
+
# send.
|
|
247
248
|
def save(rest, ctx)
|
|
248
249
|
words = rest.split
|
|
249
250
|
scope = words.delete("--system") ? "system" : "project"
|
|
@@ -199,7 +199,7 @@ final class ChiRunner {
|
|
|
199
199
|
private static let recentFlags = ["--limit", "20"]
|
|
200
200
|
|
|
201
201
|
/// The live rows, then up to +recentCount+ of the recent list that are
|
|
202
|
-
/// neither live nor a
|
|
202
|
+
/// neither live nor held by an owner (a one-turn run); a failed recent list adds none.
|
|
203
203
|
private static func rows(live: [LiveSession], recent: Result<[LiveSession], ChiError>, recentCount: Int) -> [LiveSession] {
|
|
204
204
|
let liveIds = Set(live.map(\.id))
|
|
205
205
|
let stopped = ((try? recent.get()) ?? [])
|