samagotchi 0.143.0 → 0.145.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 +71 -1
- data/docs/bundle-authoring.md +1 -1
- data/docs/cli.md +21 -4
- data/docs/configuration.md +1 -1
- data/docs/guardrails.md +6 -3
- data/docs/hooks.md +5 -1
- data/docs/internals/bridge.md +2 -1
- data/docs/internals/bundles.md +1 -1
- data/docs/internals/engine.md +1 -1
- data/docs/internals/web-frontend.md +59 -2
- data/docs/plugins.md +4 -2
- data/docs/releasing.md +3 -2
- data/docs/testing.md +1 -1
- data/lib/samagotchi/bridge/sse_writer.rb +11 -1
- data/lib/samagotchi/bundle_command/check.rb +13 -2
- data/lib/samagotchi/bundle_settings_check.rb +34 -1
- data/lib/samagotchi/bundles/check-in/manifest.yml +4 -4
- data/lib/samagotchi/bundles/check-in/plugin.rb +13 -6
- data/lib/samagotchi/bundles/core/manifest.yml +2 -2
- data/lib/samagotchi/bundles/cost-cap/manifest.yml +4 -4
- data/lib/samagotchi/bundles/cost-cap/plugin.rb +4 -1
- data/lib/samagotchi/bundles/github-pr/manifest.yml +2 -2
- data/lib/samagotchi/bundles/github-pr/plugin.rb +4 -2
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +4 -2
- data/lib/samagotchi/bundles/source-links/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/bundles/work-cap/manifest.yml +4 -4
- data/lib/samagotchi/bundles/work-cap/plugin.rb +17 -8
- data/lib/samagotchi/context_status.rb +1 -1
- data/lib/samagotchi/engine.rb +17 -7
- data/lib/samagotchi/kernel_loop.rb +7 -0
- data/lib/samagotchi/live_versions.rb +28 -2
- data/lib/samagotchi/llm/chat_loop.rb +5 -0
- data/lib/samagotchi/llm/utf8_scrub.rb +5 -3
- data/lib/samagotchi/plugin/api.rb +2 -2
- data/lib/samagotchi/plugin/context.rb +3 -2
- data/lib/samagotchi/plugin/peer.rb +1 -1
- data/lib/samagotchi/plugin_session_state.rb +6 -3
- data/lib/samagotchi/self_report.rb +15 -13
- data/lib/samagotchi/session_access/local.rb +7 -10
- data/lib/samagotchi/session_commands.rb +2 -1
- data/lib/samagotchi/system_one/answer.rb +8 -5
- data/lib/samagotchi/system_one/client.rb +17 -0
- data/lib/samagotchi/system_one/http_backend.rb +23 -10
- data/lib/samagotchi/testing/boundary.rb +76 -0
- data/lib/samagotchi/testing/env_not_shown.rb +17 -0
- data/lib/samagotchi/testing.rb +1 -0
- data/lib/samagotchi/update_command.rb +8 -7
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/annotate_bar.js +1 -1
- data/lib/samagotchi/web/public/app.js +15 -3
- data/lib/samagotchi/web/public/chip_popover.js +14 -4
- data/lib/samagotchi/web/public/composer_fold.js +5 -2
- data/lib/samagotchi/web/public/composer_grip.js +45 -2
- data/lib/samagotchi/web/public/copy.js +1 -1
- data/lib/samagotchi/web/public/index.html +79 -40
- data/lib/samagotchi/web/public/info_bar_model.js +6 -4
- data/lib/samagotchi/web/public/more_sheet.js +212 -0
- data/lib/samagotchi/web/public/question_cards.js +26 -3
- data/lib/samagotchi/web/public/ref_scan.js +4 -2
- data/lib/samagotchi/web/public/stage_frame.js +58 -13
- data/lib/samagotchi/web/public/stage_model.js +2 -1
- data/lib/samagotchi/web/public/stage_view.js +52 -20
- data/lib/samagotchi/web/public/turn_block.js +43 -9
- data/lib/samagotchi/worker.rb +21 -4
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6055a05533de1a37a9e9393effa720eb2ca93f0db2715d7f806f83cae3086a8d
|
|
4
|
+
data.tar.gz: ac0a46f034b62cf4c0b6968d6bb9b07fdf8bae8ab331f4745b4a597ef45dd8c9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e0ab9f589c50aa6c3fa7bdb37b64e17d9963191fa1878e624dd33d68f296d31bd107a09eb8feb5ae7bd85b9c7f2e6bbf1ff1c296ae1fd938a828160c7c5a488
|
|
7
|
+
data.tar.gz: 34688115cd7554ed4f660ab131dfcccb4dd7d15e24f72c7e32f6802d1a2c422d80cfea3cef47f18afc689eb653fc36102b5726d45129202f157d91f1d1db09b7
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,74 @@ 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.145.0] - 2026-11-02
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- On a phone, a web approval uses more of the screen, and a short one shows its question, command and Submit without
|
|
15
|
+
scrolling. A tall card can use the stage's raised height, as a long question already could.
|
|
16
|
+
- The stage shrinks back once at the next step after you answer a question or approval, so no empty room stays under the
|
|
17
|
+
answered card. Otherwise it still only grows during a turn.
|
|
18
|
+
- The web's approval card has no Deny radio. The Deny button denies in one tap, and a reason typed in the box now goes
|
|
19
|
+
with it. Terminals, `chi answer` and relayed approvals still answer Deny by its label.
|
|
20
|
+
- On a phone or touch screen, the composer folds while a card's text field has focus and comes back afterwards. Your
|
|
21
|
+
saved fold and draft are untouched.
|
|
22
|
+
- The session bar says "waiting" instead of "running" while a card blocks the turn.
|
|
23
|
+
- A live answer's new line appears at once instead of fading in, which takes about a quarter off the CPU a streaming
|
|
24
|
+
answer uses on a Mac Studio.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- An approval card's folder and reason wrap at words instead of splitting them.
|
|
29
|
+
- The text fields on web cards ask iOS Safari not to offer its AutoFill Contact bar.
|
|
30
|
+
|
|
31
|
+
Update with `chi update`.
|
|
32
|
+
|
|
33
|
+
## [0.144.0] - 2026-11-02
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- `chi update`'s re-check of the bundles chi doesn't ship warns about a reference to chi's internals only when this chi
|
|
38
|
+
no longer has it: the constant doesn't resolve, or the method the name gives isn't there. The row reads
|
|
39
|
+
`warn boundary plugin.rb: Samagotchi::OldInternal::Thing is gone from this chi, so this bundle may fail`; a name that
|
|
40
|
+
still works (`Samagotchi::Log`) gets no row, so the table stops repeating warnings about bundles that load fine.
|
|
41
|
+
`chi bundle check` (the author's own run) stays strict.
|
|
42
|
+
- The web's session bar on a phone no longer swipes: id, status, model and ⚙ stay on the row, and ⋯ opens a bottom sheet with the
|
|
43
|
+
rest (toolkit, working set, participants, chain links, delegates, the telemetry, Continue, Archive, Stop, Delete). A dot on ⋯ says
|
|
44
|
+
when something there needs a look. See [docs/cli.md](docs/cli.md#the-session-bar-on-a-phone).
|
|
45
|
+
- chi web uses much less CPU during a turn: about a fifth of a core less on a Mac Studio while a question waits or the model
|
|
46
|
+
is silent, and less while text streams. The stage's dot and running tick are now a slow opacity blink instead of a pulsing
|
|
47
|
+
ring, the spinners turn in small steps, and while a question or an approval blocks the turn they stand still, so movement
|
|
48
|
+
means chi is working. An in-turn card like check-in's keeps its dot blinking, as that turn goes on.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- An event with bytes JSON can't take (a plugin's card, say) no longer ends every stream that reaches it: it goes out scrubbed.
|
|
53
|
+
- A waiting approval on the web page opens at its question and command, on a phone and on a desktop, when it arrives and when the page
|
|
54
|
+
loads on it. A card too tall for the stage showed its options with what it asks scrolled out of sight; the options are now a scroll
|
|
55
|
+
down the stage's one scroll.
|
|
56
|
+
- `chi update` tells you a `chi web` started with `--port` runs old code; it only looked on `web.port`.
|
|
57
|
+
- `chi self` finds a `chi web` started with `--port`; it said `not running on port N` for `web.port`.
|
|
58
|
+
- `/guardrails` in chi web or an attached terminal shows all of its listing: a command's reply was cut at 4 KB, so with the core
|
|
59
|
+
bundle's rules System 1's section never showed and the text stopped mid-word. A reply now goes up to 64 KB, and a longer one ends
|
|
60
|
+
with a line saying it was cut.
|
|
61
|
+
- A `!` command that prints bytes that aren't valid UTF-8 no longer breaks the session's page and attached terminals: its output
|
|
62
|
+
stopped every stream that reached it, on each reconnect. The bad bytes show as `?`, as they do in the saved conversation.
|
|
63
|
+
- `/guardrails` no longer shows `System 1 (on):` when the settings mean nothing can be judged: it reads
|
|
64
|
+
`System 1 (on, but nothing is judged: system_one.host is not set):`, and likewise for a host that isn't in `hosts:`,
|
|
65
|
+
isn't `api: openai` or names no model.
|
|
66
|
+
- A `bundles:` entry in config.yml for a bundle that isn't installed (or a key it doesn't take) warns once, not in every new session,
|
|
67
|
+
respawn and delegate; a changed finding warns again, as does one fixed and then back.
|
|
68
|
+
- An answer with a line that opens and closes backticks itself (```` ```foo``` first ````) no longer loses every link after it: the source-links
|
|
69
|
+
and github-pr bundles and the web's file refs read it as inline code, not as a code block left open.
|
|
70
|
+
- A session whose cost-cap, work-cap or check-in saved nothing no longer leaves an empty folder under `plugins/<bundle>/sessions/`.
|
|
71
|
+
`ctx.session_dir(create: false)` is the path with no folder made, for a bundle that only reads. `chi update` gets them
|
|
72
|
+
(cost-cap 0.2.5, work-cap 0.1.5, check-in 0.3.3).
|
|
73
|
+
- A Stop that arrives on an empty reply ends the turn cancelled. It used to end
|
|
74
|
+
as "no answer: the model returned nothing".
|
|
75
|
+
|
|
76
|
+
Update with `chi update` (the core profile 0.4.3): check-in 0.3.3, cost-cap 0.2.5 and work-cap 0.1.5 (these three need this chi), github-pr 0.6.2 and source-links 0.6.2.
|
|
77
|
+
|
|
10
78
|
## [0.143.0] - 2026-11-01
|
|
11
79
|
|
|
12
80
|
### Added
|
|
@@ -5481,7 +5549,9 @@ and long-lived sessions.
|
|
|
5481
5549
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
5482
5550
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
5483
5551
|
|
|
5484
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
5552
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.145.0...HEAD
|
|
5553
|
+
[0.145.0]: https://github.com/dm1try/samagotchi/compare/v0.144.0...v0.145.0
|
|
5554
|
+
[0.144.0]: https://github.com/dm1try/samagotchi/compare/v0.143.0...v0.144.0
|
|
5485
5555
|
[0.143.0]: https://github.com/dm1try/samagotchi/compare/v0.142.0...v0.143.0
|
|
5486
5556
|
[0.142.0]: https://github.com/dm1try/samagotchi/compare/v0.141.0...v0.142.0
|
|
5487
5557
|
[0.141.0]: https://github.com/dm1try/samagotchi/compare/v0.140.0...v0.141.0
|
data/docs/bundle-authoring.md
CHANGED
|
@@ -121,7 +121,7 @@ end
|
|
|
121
121
|
`bundle.http.stub` stubs `ctx.http`; `bundle.question_asked(text, question:)` fires `:question_asked` and returns
|
|
122
122
|
the refs; `sandbox.config`, `sandbox.fake_executable` and `bundle.install` cover setup.
|
|
123
123
|
The full list is in [plugins.md, Testing a bundle](plugins.md#testing-a-bundle-experimental). The kit is
|
|
124
|
-
experimental (it may change before chi 1.0). If a test needs a fake of chi's own (a stub of a `Samagotchi::`
|
|
124
|
+
experimental (it may change before chi 1.0). A failing matcher that meets `ENV` prints `#<the process ENV, contents not shown>`, not your keys. If a test needs a fake of chi's own (a stub of a `Samagotchi::`
|
|
125
125
|
class, a hand-built `ctx`), that is a gap in the kit: say so to the user rather than reaching into chi.
|
|
126
126
|
|
|
127
127
|
## Pitfalls
|
data/docs/cli.md
CHANGED
|
@@ -156,7 +156,8 @@ The loop for a bundle you're writing (the whole path: [bundle-authoring.md](bund
|
|
|
156
156
|
never fires), `settings` (warn: a `--set` the manifest's `settings:` rules out, as config.yml's `bundles:` is told), `api` (warn: a `ctx.*` / `chi.*` call or a keyword this chi's API lacks, read from the source with
|
|
157
157
|
Prism against the API's own classes, on a path that never runs too; written on a local `ctx` / `chi` itself, so a
|
|
158
158
|
`s = ctx.sessions` variable isn't followed; a name the plugin guards with `respond_to?` is left), `boundary` (warn: a `Samagotchi::` name outside "Stable for bundles"; a shipped bundle's listed
|
|
159
|
-
exceptions aside
|
|
159
|
+
exceptions aside. On an installed bundle (`chi update`) only a name this chi no longer has is a row: the constant
|
|
160
|
+
doesn't resolve, or the method named isn't there) and `tests` (info: how to run them, or "no tests"). `--set KEY=VALUE` (repeatable, typed as
|
|
160
161
|
`chi --set`) goes to `bundles: <name>:` in the sandbox's config.yml.
|
|
161
162
|
|
|
162
163
|
### Trying a bundle
|
|
@@ -314,7 +315,9 @@ done
|
|
|
314
315
|
and each warning or error is a row under the bundle's name: `requires_chi`
|
|
315
316
|
ruling this chi out, a plugin or hook that no longer loads, a hook on an event
|
|
316
317
|
chi no longer fires, a `ctx`/`chi` call the API lacks, a reference to chi's
|
|
317
|
-
internals
|
|
318
|
+
internals that this chi no longer has (`Samagotchi::OldInternal::Thing is gone from this chi, so this bundle may
|
|
319
|
+
fail` — a name that still resolves gets no row, so a bundle reaching `Samagotchi::Log` isn't warned about on every
|
|
320
|
+
update), a name chi now ships, or a hook or plugin edited since it was installed (it
|
|
318
321
|
doesn't load). What only an author can act on (shas, unlisted files, tests, a
|
|
319
322
|
missing `requires_chi`, its floor, `trust_level`) is left out, and so are
|
|
320
323
|
profiles. A clean bundle gets no row beyond its `skipped (not from chi)`.
|
|
@@ -350,8 +353,8 @@ done
|
|
|
350
353
|
restarts: `chi sessions stop ID`, and the next prompt starts it on the new
|
|
351
354
|
chi); it exits on its own only when nothing is attached (no web tab, no
|
|
352
355
|
terminal, no reminder) and `session.idle_exit_minutes` pass (never with 0).
|
|
353
|
-
A new or woken worker always starts on the newest chi installed. A
|
|
354
|
-
on `web.port`
|
|
356
|
+
A new or woken worker always starts on the newest chi installed. A running
|
|
357
|
+
`chi web` (on the port it was started with, else `web.port`) on an older chi needs a restart (Ctrl-C, then `chi web`):
|
|
355
358
|
its page says so too. One from before workers started on the newest chi
|
|
356
359
|
also starts its new sessions on its own version until then (the row says so).
|
|
357
360
|
|
|
@@ -1397,6 +1400,20 @@ access token", paste the token there (the part of the link after
|
|
|
1397
1400
|
phone. The same sheet lists the other devices (a long-turns-done switch each, Remove), and `chi web push ls|rm|test`
|
|
1398
1401
|
does it from the Mac (see [Phone notifications](configuration.md#phone-notifications-web-push)).
|
|
1399
1402
|
|
|
1403
|
+
#### The session bar on a phone
|
|
1404
|
+
|
|
1405
|
+
At a phone's width (600 px or less) the session bar is one row with no swiping: the id (a tap copies it), the status, the model
|
|
1406
|
+
(cut short first), ⚙ and ⋯. ⋯ opens a bottom sheet with the rest:
|
|
1407
|
+
|
|
1408
|
+
- a row for what the row has no room for, each opening what its chip opens on a wider screen: the model and the 🧰 toolkit (a tap
|
|
1409
|
+
shows the full text a tooltip gives elsewhere), the working set (or the memories with `web.working_set` off), and, when the
|
|
1410
|
+
session has them, participants, chain links, the parent, delegates, model notes and an older worker's Restart;
|
|
1411
|
+
- one line with ctx, speed, the clock and the folder;
|
|
1412
|
+
- Continue, Archive (or Unarchive), Stop and Delete, under the same rules and confirmations as the wider screen's buttons.
|
|
1413
|
+
|
|
1414
|
+
A dot on ⋯ says something behind it needs a look: the accent when the working set changed or a participant is here, the warning
|
|
1415
|
+
colour when a delegate waits or the worker runs an older chi. The attached context's chips keep their own second line under the row.
|
|
1416
|
+
|
|
1400
1417
|
### Remote sessions
|
|
1401
1418
|
|
|
1402
1419
|
The session commands also reach another machine's sessions, through its chi
|
data/docs/configuration.md
CHANGED
|
@@ -127,7 +127,7 @@ own subsystems: `hosts:` (below), `models:` (see "Prompt profile" and
|
|
|
127
127
|
`guardrails:` (see [Guardrails](guardrails.md)), `bundles:` (a bundle's
|
|
128
128
|
settings for its hooks, see [Hooks: Settings](hooks.md#settings); a session's own
|
|
129
129
|
`--set BUNDLE.KEY=VALUE`, for a key the bundle's manifest lists in `session_settings:`, lays over them, see
|
|
130
|
-
[Per-session settings](plugins.md#per-session-settings); a name no installed bundle has, or a key its manifest's `settings:` rules out, warns at a session
|
|
130
|
+
[Per-session settings](plugins.md#per-session-settings); a name no installed bundle has, or a key its manifest's `settings:` rules out, warns at the start of the first session to find it and again only when the findings change or come back (a digest of the lines shown is stamped in `$XDG_STATE_HOME/samagotchi/bundles_warned`; the log has them from every session); `--set KEY=VALUE` does the same for a few core keys,
|
|
131
131
|
see "Per-session settings" below),
|
|
132
132
|
`memories:` and `append_system_prompt:` (see "Appended prompt files"). Their entry names (host names, model ids, aliases) are yours to
|
|
133
133
|
choose. The `memories:` list is the persistent baseline for preloaded memory entries —
|
data/docs/guardrails.md
CHANGED
|
@@ -107,7 +107,9 @@ A file that doesn't parse is moved aside to `approvals.json.corrupt-<UTC time>`
|
|
|
107
107
|
with one warning, and chi starts with no stored approvals (more asks, nothing lost).
|
|
108
108
|
`/guardrails` lists the rules and approvals; `/guardrails revoke N` removes
|
|
109
109
|
approval N. When System 1 is enabled or has judgments, it also shows this
|
|
110
|
-
session's recent auto-allows and each rule's record summary
|
|
110
|
+
session's recent auto-allows and each rule's record summary, and its heading says
|
|
111
|
+
when the settings mean nothing can be judged (`System 1 (on, but nothing is judged:
|
|
112
|
+
system_one.host is not set):`; no request is made to check). In System 1 `on`
|
|
111
113
|
mode, `/guardrails wrong [N]` flags the Nth newest auto-allowed call in this
|
|
112
114
|
session as one System 1 should have asked about (default 1). The flag is
|
|
113
115
|
appended to the judgments log; the rule asks again until its record is rebuilt.
|
|
@@ -1163,8 +1165,9 @@ written, and the call is denied without a card; a turn that stopped as the
|
|
|
1163
1165
|
judgment came back opens no card either.
|
|
1164
1166
|
|
|
1165
1167
|
`ruby script/system-one-probe HOST [MODEL]` asks three fixed approval-like
|
|
1166
|
-
questions
|
|
1167
|
-
the call — how a host is checked before any of
|
|
1168
|
+
questions, then a choice and a score question, through the client and prints
|
|
1169
|
+
each answer and the latency of the call — how a host is checked before any of
|
|
1170
|
+
this is turned on.
|
|
1168
1171
|
|
|
1169
1172
|
### Answering for the user: `on` mode
|
|
1170
1173
|
|
data/docs/hooks.md
CHANGED
|
@@ -489,10 +489,14 @@ notice is only added on top. A config hook (config.yml's own files) keeps its
|
|
|
489
489
|
behaviour: no notice, just the log. A UI that joins later reads the same lines
|
|
490
490
|
from `Engine#plugin_warning`.
|
|
491
491
|
|
|
492
|
+
In the [bundle test kit](plugins.md#testing-a-bundle-experimental) a hook that raises raises
|
|
493
|
+
`Testing::HookFailed` out of the call that fired it, under every `on_error`.
|
|
494
|
+
|
|
492
495
|
## Runtime Hook Registration
|
|
493
496
|
|
|
494
497
|
You can also register hooks programmatically during a turn (they are cleared
|
|
495
|
-
automatically after each `run_turn
|
|
498
|
+
automatically after each `run_turn`; one that raises is logged, tells you once
|
|
499
|
+
per event in a worker, as a bundle's does, and the next hook runs):
|
|
496
500
|
|
|
497
501
|
```ruby
|
|
498
502
|
engine = Samagotchi::Engine.new
|
data/docs/internals/bridge.md
CHANGED
|
@@ -212,7 +212,8 @@ The Bridge exposes:
|
|
|
212
212
|
`404` for an unknown id. A POST with a body because routes match exact paths.
|
|
213
213
|
- `POST /session/:id/command` — a session command (`/model`, `/models`, `!rollback`, `!cmd`,
|
|
214
214
|
`/continue`) for the worker loop; `202` with a `command_id` its `:command_ran` names, `400` when
|
|
215
|
-
the line isn't one. A `:command_ran`
|
|
215
|
+
the line isn't one. A `:command_ran`'s `output` is the command's, up to `Worker::COMMAND_OUTPUT_LIMIT` (64 KiB of chars; past it
|
|
216
|
+
the text ends with a `[cut: N of M chars: …]` line, which the UIs print as they print the rest). It carries `thinking`, `reasoning_history` and `llm_context`
|
|
216
217
|
(the summaries the next turn runs under) when the command changed them or the model, and
|
|
217
218
|
`confirm: {line, text}` when a `/thinking` or `/reasoning-history` change from a `web:` or `tui:`
|
|
218
219
|
client asks first (`CacheConfirm`; nothing changed, `line` applies it).
|
data/docs/internals/bundles.md
CHANGED
|
@@ -42,7 +42,7 @@ user-facing side (what a bundle owns, profiles, needs) is in [memory.md](../memo
|
|
|
42
42
|
| `Builder` | `chi bundle build`: memories (+ the installed bundle's hooks, rules, plugin, scripts) → zip/tar/dir |
|
|
43
43
|
| `Profile` | meta bundles (`includes:`): install the members, record which; `InstallResult#kept_own` names a new member's memory files the user already had, with other bytes (`kept_own_line`, printed by `chi bundle`, `chi update` and `chi bootstrap`); a dry run runs each member's Installer as a dry run too, so it names them as well |
|
|
44
44
|
| `SystemBundle`, `ShippedUpdate` | the built-in system bundle (`ensure!` at Engine start, `sync` in `chi update`) and `chi update`'s upgrade of shipped bundles; both drive the Installer with `upgrade: true`. `ShippedUpdate.external` is the rest: what `chi update` only checks |
|
|
45
|
-
| `InstalledCopy`, `BundleCommand::InstalledCheck` | `chi update`'s re-check of the bundles chi didn't ship ([docs/cli.md](../cli.md#updating)). `InstalledCopy` lays a record and its installed files out as a source folder (manifest.yml from `Manifest.write`, the files' own shas, memories from `bases/`); `InstalledCheck` forks per bundle (a plugin's `exit` or hang is that bundle's row; stdio to /dev/null, so `--json` stays parseable; `TIMEOUT`, then KILL of its group) and runs `Check.new(dir, installed: true)` there, which leaves out what only suits a source tree. The fork is the point: `Sandbox` swaps ENV and the plugin's code is arbitrary. `UpdateCommand#external_rows` makes the rows (`level`, never `failed`) and holds the check back while a newer gem is yet to take over (`@not_checked`), since a fork is this chi's code |
|
|
45
|
+
| `InstalledCopy`, `BundleCommand::InstalledCheck` | `chi update`'s re-check of the bundles chi didn't ship ([docs/cli.md](../cli.md#updating)). `InstalledCopy` lays a record and its installed files out as a source folder (manifest.yml from `Manifest.write`, the files' own shas, memories from `bases/`); `InstalledCheck` forks per bundle (a plugin's `exit` or hang is that bundle's row; stdio to /dev/null, so `--json` stays parseable; `TIMEOUT`, then KILL of its group) and runs `Check.new(dir, installed: true)` there, which leaves out what only suits a source tree. Its `boundary` finding is narrower there than in a source tree: `Testing::Boundary.gone?` calls a reference gone only when its constant doesn't resolve or the object has no method the name names, so a bundle reaching `Samagotchi::Log` gets no row. lib loads lazily, so when a name doesn't resolve `gone?` requires every file of this chi's library in that same child and asks again (about 0.2 s, only for a bundle that reaches into chi at all); anything it can't read — a `const_get`, a require of a file that is there — is taken as still there. The fork is the point: `Sandbox` swaps ENV and the plugin's code is arbitrary. `UpdateCommand#external_rows` makes the rows (`level`, never `failed`) and holds the check back while a newer gem is yet to take over (`@not_checked`), since a fork is this chi's code |
|
|
46
46
|
| `Uninstaller`, `Trash`, `Status`, `Listing` | remove, keep removed files, `chi bundle status`, `chi bundle list` |
|
|
47
47
|
| `IndexUpdater`, `IndexSync` | the managed `index.md` lines (Installer; and the write/edit tools for memories) |
|
|
48
48
|
| `EntrySize` | one memory's size in tokens: `memory.entry_warn_tokens`' crossing note after a memory write, `chi self`'s `big memories` row |
|
data/docs/internals/engine.md
CHANGED
|
@@ -811,7 +811,7 @@ Both loops:
|
|
|
811
811
|
again, up to `retry.empty_answer` times (at most 3), with a hidden nudge (`TurnNote.empty_retry`) on the tail.
|
|
812
812
|
The retry request runs at temperature 0.6 when the configured sampling sets no temperature; a configured one
|
|
813
813
|
is kept. A length stop with the context at least 90% full is not retried. When the retries run out, the
|
|
814
|
-
Engine keeps a `TurnNote.empty` note as the turn's end.
|
|
814
|
+
Engine keeps a `TurnNote.empty` note as the turn's end. A Stop that lands on an empty reply ends the turn cancelled, in both loops.
|
|
815
815
|
- **A refusal** (chat loop only): a finish reason `refusal` (Messages), or `content_filter` (OpenAI) with no text, is
|
|
816
816
|
an empty answer never retried; text streamed before a refusal is not kept, and tool calls with it are not run. A
|
|
817
817
|
`content_filter` with text keeps it, marked `ChatLoop::FILTERED_MARK`, also never retried. `ModelResult#refusal` is `{category:}` (from the response's
|
|
@@ -23,7 +23,7 @@ page. A factory dep a new version stops passing is the same hazard the other way
|
|
|
23
23
|
out of `send_turn.js`, `session_actions.js` and `lists_view.js`); a page loaded before keeps all its old modules
|
|
24
24
|
until a reload, so the risk is a mixed cache, not an open tab. `answer_fold.js`, `details_motion.js`,
|
|
25
25
|
`turn_range.js`, `history_reveal.js`, `latest.js`, `session_payload.js`, `method_api.js`, `session_store.js`,
|
|
26
|
-
`session_fields.js`, `feature_gates.js`, `revise_stack.js`, `revise_switch.js`, `shell_command.js`, `stale_params.js` and `
|
|
26
|
+
`session_fields.js`, `feature_gates.js`, `revise_stack.js`, `revise_switch.js`, `shell_command.js`, `stale_params.js`, `popover_modal.js` and `more_sheet.js` are new modules for that reason.
|
|
27
27
|
|
|
28
28
|
## Two pages, two models
|
|
29
29
|
|
|
@@ -210,6 +210,63 @@ createFamilyPop`) still place their own. Every popover behaves the same (`popove
|
|
|
210
210
|
none behind. `model_picker.js`, `command_list.js` (listboxes: the focus stays in the input), `ref_menu.js` (a menu)
|
|
211
211
|
and `update_sheet.js` (a native modal `<dialog>`) aren't popovers in that sense.
|
|
212
212
|
|
|
213
|
+
**A phone's session bar** (`more_sheet.js`): CSS alone folds the chips and the bar's buttons away at 600 px or less
|
|
214
|
+
(`display:none`, so the same `.meta` draws both layouts and `info_bar.js` knows nothing of it); they stay in the DOM.
|
|
215
|
+
⋯'s sheet (`#infoMoreBtn`) draws its rows from the bar's model, and a row or an action clicks the real chip or button, so
|
|
216
|
+
each keeps one set of handlers, confirmations and in-flight rules. A chip the sheet opens has no box to be placed at, so
|
|
217
|
+
`chip_popover.js` places it, marks `aria-expanded` and gives focus back at the page's `[data-anchor-stand-in]` (⋯); that
|
|
218
|
+
matters at exactly 600 px, where the CSS is a phone's but the shell (`SHEET_MAX`, `<`) still draws a popover, not a sheet.
|
|
219
|
+
A new chip on the bar needs its place in that CSS's hidden list and a row in `moreRows`, and its attention state in the row, or
|
|
220
|
+
it is lost on a phone. `app.js`'s `updateInfoBar` feeds the sheet (`update`) on every redraw; its rows and actions draw into
|
|
221
|
+
fixed slots, so a row that joins keeps the focus where it was. The `narrowScreen` listener closes the sheet when the window grows.
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
## The stage's height
|
|
225
|
+
|
|
226
|
+
`stage_frame.js` is the one writer. The cap is a ceiling: 60% of the page, 80% with the cloud open, 85% while a card has
|
|
227
|
+
the room, and never above the session panel. The frame is grow-only while a turn runs, so a change inside the stage
|
|
228
|
+
never moves its top edge, with one exception: **the next step after a blocking card's answer lets the frame go, once**
|
|
229
|
+
(`stage_view.js releaseHeldWait`, from `generationStarted`). The `held-wait` class says it is due: set with `waiting`,
|
|
230
|
+
cleared there, so a later step, which calls it again, does nothing. What goes together: the live slots' waiting height, a
|
|
231
|
+
raised cap, the tallest content seen (`forgetContent`). At the tap nothing moves: the card collapses to its line inside
|
|
232
|
+
the held frame and the stage's scroll stays, unless the line would be above the view (a reader who scrolled down a card
|
|
233
|
+
taller than the stage), when it comes back to the view's top (`settleAnsweredCard`).
|
|
234
|
+
|
|
235
|
+
Two cards raise the cap: a model's own question whose reply overflows its slot (`drawRoom`, the question room, read from
|
|
236
|
+
the reply's start with a pill to the card) and a blocking approval too tall for the stage's view, a delegate's relayed
|
|
237
|
+
one too (`raiseForApproval`, `stage_frame.js fitsCap`). An approval gets the cap only: no room class and no pill, as it
|
|
238
|
+
opens at its question and command (`revealTop`).
|
|
239
|
+
|
|
240
|
+
## Frames
|
|
241
|
+
|
|
242
|
+
During a turn the cost is frames, not code: JS, layout and style are each a few percent of a core, while an animation
|
|
243
|
+
that updates at 60 fps keeps the compositor and GPU busy (25-50% of a core with nothing to show). Moving a pulse to
|
|
244
|
+
`transform`/`opacity` on the compositor still costs most of that, because the composites go on; the update rate is what
|
|
245
|
+
counts. So:
|
|
246
|
+
|
|
247
|
+
- **An infinite animation is stepped, moves only `opacity` or `transform`, and stops when the turn waits.**
|
|
248
|
+
`spec/web/public/frames.test.js` reads every page and stylesheet for the first two (`.card.skeleton` is the allowed
|
|
249
|
+
exception: loading only), and for its off switch under `prefers-reduced-motion`.
|
|
250
|
+
- The dot, a running tick and a live step's dot share `live-blink 2.4s steps(16)`. `steps()` applies per segment and the
|
|
251
|
+
keyframes have two, so that is 13 updates/s: a third keyframe would change the rate. The spinners are
|
|
252
|
+
`init-spin .8s steps(16)`, 20 updates/s.
|
|
253
|
+
- A blocking card (`.turn-stage.waiting`: a question, an approval, a delegate's relayed one, whose parent's turn blocks on
|
|
254
|
+
it) stops them all, so movement means chi is working; `stage.spec.js`'s question scenario checks it, also after a
|
|
255
|
+
reload. An asking card (check-in) has no `.waiting`, as its turn goes on: its dot keeps blinking. `.init-spin` is
|
|
256
|
+
outside the stage (plugin setup only).
|
|
257
|
+
- A one-shot fade on something that arrives many times a second is a recurring cost too. A live answer's new line
|
|
258
|
+
appears at once: its fade alone was about a quarter of the streaming answer's total on the Studio (24-25% of a core to
|
|
259
|
+
17-19%). The headline's `ts-fade` stays: turning it off saved nothing measurable.
|
|
260
|
+
|
|
261
|
+
A running turn's text reaches the page at most every 100 ms (`turn_block.js FLUSH_MS`): a chunk only marks its step dirty,
|
|
262
|
+
and `flush` writes the dirty steps, follows and runs `onChange`, which refreshes the stage on the next frame
|
|
263
|
+
(`stage_view.js scheduleRefresh`, unthrottled: a collapse or a card stays immediate). The first chunk after a quiet
|
|
264
|
+
interval flushes on the next frame, so a first token isn't late; one inside the interval sets a timer for the rest, which
|
|
265
|
+
then asks for a frame, so a hidden tab's flush still pauses. A chunk that opens a step applies at once. What must not wait
|
|
266
|
+
writes for itself: `generationCompleted` and a step's close write its text, a card's refresh reads the model.
|
|
267
|
+
`reset`, `turnEnded` and `flush` itself cancel the timer. A frame already requested can't be cancelled that way, so it may
|
|
268
|
+
run `flush` with nothing to write, which doesn't count as a flush for the interval.
|
|
269
|
+
|
|
213
270
|
## Stale answers
|
|
214
271
|
|
|
215
272
|
The page switches sessions, reopens a popover or asks again while a fetch is out. An answer for the old ask must be
|
|
@@ -258,7 +315,7 @@ reach another device or the server.
|
|
|
258
315
|
| Key | Module | What |
|
|
259
316
|
|---|---|---|
|
|
260
317
|
| `chi_model`, `chi_model_recent` | `model_pick.js`, `model_picker.js`, `start_page.js` | the start page's model and its recent list |
|
|
261
|
-
| `chi_composer_collapsed` | `composer_fold.js` | the composer folded |
|
|
318
|
+
| `chi_composer_collapsed` | `composer_fold.js` | the composer folded (a card's text field folds it for the time it has the focus on a narrow or touch screen, `composer_grip.js watchCardField`, on top of this and of the visit's state: neither is written) |
|
|
262
319
|
| `chi_composer_floor` | `composer_grip.js`, `composer_mount.js` | the composer's dragged height |
|
|
263
320
|
| `chi_strip_hidden` (`chi_zen`: the old name, read once) | app.js | the session strip hidden |
|
|
264
321
|
| `chi_stage_collapsed` | `stage_view.js` | the stage collapsed |
|
data/docs/plugins.md
CHANGED
|
@@ -845,7 +845,7 @@ full label, `plugin.rb (bundle my-bundle)`.
|
|
|
845
845
|
| `ctx.turn_author` | who wrote the running turn when it isn't the session's owner: a participant's `"p:<id>"` (`chi share`); nil for the owner's turns and between turns. Every hook event of such a turn carries the same as `author:`. The handler runs with the owner's power whatever the author ([hooks](hooks.md#participants-turns)). Needs chi 0.105.0 (guard it with `ctx.respond_to?(:turn_author)`) |
|
|
846
846
|
| `ctx.settings` | the bundle's settings, frozen: config.yml's `bundles: <bundle>:` with the session's own `--set` values laid over it ([Per-session settings](#per-session-settings)); the config alone until the session is bound (in `register` and `chi.ctx`) |
|
|
847
847
|
| `ctx.data_dir` | `$XDG_STATE_HOME/samagotchi/plugins/<bundle>/`, created on first use: what the bundle keeps across sessions |
|
|
848
|
-
| `ctx.session_dir` | this session's own folder there, created on use, read now; nil with no session yet. chi removes it when the session is deleted, discarded or pruned, so don't build the path yourself. A `sessions/<id>.json` an older version of your bundle kept moves in as `state.json` (chi still removes one left there). Needs chi newer than 0.142.0 |
|
|
848
|
+
| `ctx.session_dir` | this session's own folder there, created on use, read now; nil with no session yet. `ctx.session_dir(create: false)` is the same path with no folder made, for a read, so a session that saved nothing leaves none (a `File.read` of what isn't there raises `Errno::ENOENT`); needs chi newer than 0.143.0. chi removes it when the session is deleted, discarded or pruned, so don't build the path yourself. A `sessions/<id>.json` an older version of your bundle kept moves in as `state.json` (chi still removes one left there). Needs chi newer than 0.142.0 |
|
|
849
849
|
| `ctx.log` | `ctx.log.info(:event, key: value)`: debug-log records tagged `plugins`, with `bundle=<bundle>` |
|
|
850
850
|
| `ctx.messages` | the conversation, as a frozen copy, without the system prompt. While a turn runs, a session worker's (attached, web) adds that turn so far: its prompt, the model's text and the lines merged into it (no tool calls or thinking); the REPL's is the conversation before that turn. An entry may carry `at`, when it happened (docs/sessions.md, Message times) |
|
|
851
851
|
| `ctx.messages_partial?` | whether `ctx.messages` leaves out a running turn (the REPL mid-turn), so a plugin can say what its answer is about |
|
|
@@ -1384,7 +1384,9 @@ that calls it, or for a real external bundle that does. No method on the chance
|
|
|
1384
1384
|
and `Boundary::EXCEPTIONS` the per-bundle list of what each shipped bundle
|
|
1385
1385
|
(and its specs) reaches beyond it, each with why: the APIs to design before
|
|
1386
1386
|
that bundle could leave chi's repo. `spec/samagotchi/testing/boundary_spec.rb`
|
|
1387
|
-
fails on a reference in neither (docs/testing.md).
|
|
1387
|
+
fails on a reference in neither (docs/testing.md). In `chi update`'s re-check of
|
|
1388
|
+
an installed bundle the same scan warns only for a name this chi no longer has
|
|
1389
|
+
(`Boundary.gone?`): a reference that still resolves gets no row.
|
|
1388
1390
|
|
|
1389
1391
|
**Used by shipped bundles today, but internal** (may change without notice; a
|
|
1390
1392
|
bundle that needs one should ask for an API instead):
|
data/docs/releasing.md
CHANGED
|
@@ -28,7 +28,8 @@ can run the whole release; the user approves the notes before the tag and the
|
|
|
28
28
|
one-line string). `release:bump` touches no other `requires_chi` but a
|
|
29
29
|
profile's (next point);
|
|
30
30
|
- a new `ctx.*` / `chi.*` call a plugin may make gets its release's version in `Plugin::Api::SINCE` once
|
|
31
|
-
released (`chi bundle check`'s `requires-chi-floor` reads it); an unreleased one waits there as
|
|
31
|
+
released (`chi bundle check`'s `requires-chi-floor` reads it); an unreleased one waits there as an `# Unreleased` comment, and
|
|
32
|
+
`release:check` fails while that comment is left: move each call up with the new version;
|
|
32
33
|
- a new shipped bundle joins a profile (`includes:` in `core` or `dev`; a
|
|
33
34
|
spec checks every bundle is in exactly one), and that profile's version
|
|
34
35
|
goes up: `chi update` then installs only the new bundle for those who
|
|
@@ -71,7 +72,7 @@ as the GitHub release body.
|
|
|
71
72
|
| `rake gem:validate`, `gem:build`, `gem:install`, `gem:clean` | Check the gemspec; build the gem into `pkg/`; build and install it locally; remove the built gems. |
|
|
72
73
|
| `rake release:draft_changelog` | A draft Unreleased section from the commit subjects since the last tag, grouped. Stdout only. |
|
|
73
74
|
| `rake release:bump[X.Y.Z]` | VERSION, the system manifest, Gemfile.lock, CHANGELOG, and `requires_chi: ">= X.Y.Z"` for each bundle marked `requires_chi_next` (the marker dropped), then a profile left below a member's `requires_chi` raised to it, with a patch bump of its version (none when it moved since the last tag) and named in the CHANGELOG's "Update with `chi update`" line. No commit. |
|
|
74
|
-
| `rake release:check` | Clean tracked tree, VERSION == system bundle, a CHANGELOG section, no `requires_chi_next` left, `bundles:check`, `rake lint` (RuboCop) + `npm run lint` (the web frontend's ESLint), rspec (both roots: `spec` and the bundles' own `spec/`) + `rake bundles:specs` + npm test, `gem build`, then a clean install into a temp GEM_HOME that runs `chi --version`, `chi self` and `chi bundle list` with a temp HOME. Needs the network (gem dependencies). |
|
|
75
|
+
| `rake release:check` | Clean tracked tree, VERSION == system bundle, a CHANGELOG section, no `requires_chi_next` left, no `# Unreleased` comment left in `Plugin::Api::SINCE`, `bundles:check`, `rake lint` (RuboCop) + `npm run lint` (the web frontend's ESLint), rspec (both roots: `spec` and the bundles' own `spec/`) + `rake bundles:specs` + npm test, `gem build`, then a clean install into a temp GEM_HOME that runs `chi --version`, `chi self` and `chi bundle list` with a temp HOME. Needs the network (gem dependencies). |
|
|
75
76
|
| `rake release:notes[X.Y.Z]` | Prints the CHANGELOG section (default: the current VERSION). |
|
|
76
77
|
|
|
77
78
|
## Runbook
|
data/docs/testing.md
CHANGED
|
@@ -294,7 +294,7 @@ well formed (unique ids, known keys and labels, a rule that exists in its set).
|
|
|
294
294
|
|
|
295
295
|
```sh
|
|
296
296
|
script/affected-specs # the specs for this branch's change, best first, at most 16
|
|
297
|
-
|
|
297
|
+
script/affected-specs --run # runs them; with no Ruby picks it says so and runs nothing
|
|
298
298
|
script/affected-specs --why # with the reason for each pick; --all: no cap; --run: runs them
|
|
299
299
|
script/affected-specs A..B # another range (A...B: from their merge-base); a single REV: vs the working tree
|
|
300
300
|
```
|
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
|
|
5
5
|
require_relative "client_event"
|
|
6
6
|
require_relative "event_id"
|
|
7
|
+
require_relative "../llm/utf8_scrub"
|
|
7
8
|
|
|
8
9
|
module Samagotchi
|
|
9
10
|
class Bridge
|
|
@@ -231,11 +232,20 @@ module Samagotchi
|
|
|
231
232
|
lines << "id: #{EventId.format(seq, @epoch)}"
|
|
232
233
|
event_type = data.is_a?(Hash) && (data[:type] || data["type"])
|
|
233
234
|
lines << "event: #{event_type}" if event_type && !event_type.to_s.empty?
|
|
234
|
-
|
|
235
|
+
frame_json(data).each_line { |line| lines << "data: #{line.chomp}" }
|
|
235
236
|
io.write(lines.join("\r\n") + "\r\n\r\n")
|
|
236
237
|
io.flush
|
|
237
238
|
end
|
|
238
239
|
|
|
240
|
+
# An event holding bytes JSON can't take (a plugin's card, a tool's text)
|
|
241
|
+
# goes out scrubbed: raising here would end this stream, and every stream
|
|
242
|
+
# after it, for as long as the ring holds the event.
|
|
243
|
+
def frame_json(data)
|
|
244
|
+
JSON.generate(data)
|
|
245
|
+
rescue JSON::GeneratorError
|
|
246
|
+
JSON.generate(LLM::Utf8Scrub.call(data))
|
|
247
|
+
end
|
|
248
|
+
|
|
239
249
|
def write_heartbeat(io)
|
|
240
250
|
io.write(": ping\r\n\r\n")
|
|
241
251
|
io.flush
|
|
@@ -403,8 +403,19 @@ module Samagotchi
|
|
|
403
403
|
require_relative "../testing/boundary"
|
|
404
404
|
known = shipped? ? Testing::Boundary::EXCEPTIONS.fetch(manifest.name, {}) : {}
|
|
405
405
|
Testing::Boundary.scan(@dir).reject { |f| known.key?(f.reference) }.each do |finding|
|
|
406
|
-
|
|
407
|
-
|
|
406
|
+
if @installed
|
|
407
|
+
# `chi update` asks one question of a bundle it didn't write: what
|
|
408
|
+
# does this chi break of it. A name that still resolves is not a
|
|
409
|
+
# warning, so the table stays clear of rows that repeat while
|
|
410
|
+
# nothing is wrong.
|
|
411
|
+
next unless Testing::Boundary.gone?(finding.reference)
|
|
412
|
+
|
|
413
|
+
add(:warn, :boundary, finding.file, "#{finding.reference} is gone from this chi, so this bundle may fail",
|
|
414
|
+
fix: "use chi.* / ctx, or the names docs/plugins.md lists as stable")
|
|
415
|
+
else
|
|
416
|
+
add(:warn, :boundary, finding.file, "#{finding.reference} is internal to chi and may break on a chi upgrade",
|
|
417
|
+
fix: "use chi.* / ctx, or the names docs/plugins.md lists as stable")
|
|
418
|
+
end
|
|
408
419
|
end
|
|
409
420
|
end
|
|
410
421
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require_relative "atomic_file"
|
|
3
6
|
require_relative "config"
|
|
7
|
+
require_relative "paths"
|
|
4
8
|
require_relative "session_config"
|
|
5
9
|
require_relative "memory_bundle/provenance"
|
|
6
10
|
|
|
@@ -10,8 +14,11 @@ module Samagotchi
|
|
|
10
14
|
# value's type a manifest's settings: rules out. A config file only warns:
|
|
11
15
|
# the rest of it still runs, and `chi --set` is what refuses
|
|
12
16
|
# (SessionConfig.check!, in the same words). A manifest with no settings:
|
|
13
|
-
# (a record from before them too) has its keys left alone.
|
|
17
|
+
# (a record from before them too) has its keys left alone. A digest of the
|
|
18
|
+
# lines last shown is stamped in $XDG_STATE_HOME/samagotchi/bundles_warned.
|
|
14
19
|
module BundleSettingsCheck
|
|
20
|
+
STAMP_FILE = "bundles_warned"
|
|
21
|
+
|
|
15
22
|
module_function
|
|
16
23
|
|
|
17
24
|
# @param section [Hash{String => Hash}] ConfigFile.bundle_settings
|
|
@@ -29,6 +36,32 @@ module Samagotchi
|
|
|
29
36
|
end
|
|
30
37
|
end
|
|
31
38
|
|
|
39
|
+
# @return [String] the stamp of the lines last shown
|
|
40
|
+
def stamp_path(env: ENV) = File.join(Paths.state_dir(env: env), STAMP_FILE)
|
|
41
|
+
|
|
42
|
+
# The lines to show now: each worker checks the config, and all of them
|
|
43
|
+
# finding what the last one showed would repeat it in every session. None
|
|
44
|
+
# while +lines+ are the stamped ones (in any order); no lines clear the
|
|
45
|
+
# stamp, so the same typo made again is new. A stamp it can't read or
|
|
46
|
+
# write costs a repeated warning, never a session start.
|
|
47
|
+
# @param lines [Array<String>] #warnings
|
|
48
|
+
# @return [Array<String>] +lines+, or none
|
|
49
|
+
def unshown(lines)
|
|
50
|
+
stamp = stamp_path
|
|
51
|
+
if lines.empty?
|
|
52
|
+
FileUtils.rm_f(stamp)
|
|
53
|
+
return lines
|
|
54
|
+
end
|
|
55
|
+
digest = Digest::SHA256.hexdigest(lines.sort.join("\n"))
|
|
56
|
+
return [] if File.exist?(stamp) && File.read(stamp).strip == digest
|
|
57
|
+
|
|
58
|
+
FileUtils.mkdir_p(File.dirname(stamp))
|
|
59
|
+
AtomicFile.write(stamp, "#{digest}\n")
|
|
60
|
+
lines
|
|
61
|
+
rescue StandardError
|
|
62
|
+
lines
|
|
63
|
+
end
|
|
64
|
+
|
|
32
65
|
# One bundle's +values+ against its record (or a manifest as one); none
|
|
33
66
|
# while it declares no settings:.
|
|
34
67
|
# @param bundle [MemoryBundle::InstalledBundle]
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: check-in
|
|
3
|
-
version: 0.3.
|
|
3
|
+
version: 0.3.3
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Checks on a long turn: after N tool calls with no answer, a card asks the user (Nudge / Keep going / Stop), or the model is nudged to say what it has found"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
|
-
sha256: sha256:
|
|
9
|
+
sha256: sha256:6580d6d19ac9beda871d88d3955abc5a319dd8b5aea225a06dee90b6daeeddce
|
|
10
10
|
# check-in.progress asks System 1 (chi.judgment, new in 0.142.0);
|
|
11
|
-
# ctx.session_dir is new in
|
|
12
|
-
requires_chi: ">= 0.
|
|
11
|
+
# ctx.session_dir is new in 0.143.0
|
|
12
|
+
requires_chi: ">= 0.144.0"
|
|
13
13
|
# config.yml bundles: check-in: (another key warns; an older chi ignores this)
|
|
14
14
|
settings: [after, every, mode, message, ignore_tools]
|
|
@@ -236,15 +236,19 @@ class Plugin
|
|
|
236
236
|
end
|
|
237
237
|
|
|
238
238
|
def call_args(call)
|
|
239
|
-
|
|
240
|
-
return
|
|
239
|
+
return call if call.is_a?(String)
|
|
240
|
+
return JSON.generate(call) unless call.is_a?(Hash)
|
|
241
|
+
|
|
242
|
+
# A tool that isn't built in has its arguments on args: and, as JSON, in
|
|
243
|
+
# content: (or there alone, when none could be read): say them once.
|
|
244
|
+
args = call[:args]
|
|
245
|
+
return args.empty? ? call[:content].to_s : JSON.generate(args) if args.is_a?(Hash)
|
|
241
246
|
|
|
242
247
|
# The tool's name is already in the entry and nil fields say nothing:
|
|
243
248
|
# the cut should keep what the call asked for.
|
|
244
|
-
|
|
245
|
-
JSON.generate(args)
|
|
249
|
+
JSON.generate(call.compact.except(:name, "name"))
|
|
246
250
|
rescue StandardError
|
|
247
|
-
|
|
251
|
+
call.to_s
|
|
248
252
|
end
|
|
249
253
|
|
|
250
254
|
def cut(text, limit)
|
|
@@ -389,7 +393,7 @@ class Plugin
|
|
|
389
393
|
def read_state(ctx, id)
|
|
390
394
|
return {} unless id
|
|
391
395
|
|
|
392
|
-
data = JSON.parse(File.read(
|
|
396
|
+
data = JSON.parse(File.read(saved_file(ctx)))
|
|
393
397
|
data.is_a?(Hash) ? data : {}
|
|
394
398
|
rescue SystemCallError, JSON::ParserError
|
|
395
399
|
{}
|
|
@@ -410,6 +414,9 @@ class Plugin
|
|
|
410
414
|
|
|
411
415
|
def state_file(ctx) = File.join(ctx.session_dir, "state.json")
|
|
412
416
|
|
|
417
|
+
# For a read: a session with nothing saved keeps no folder.
|
|
418
|
+
def saved_file(ctx) = File.join(ctx.session_dir(create: false), "state.json")
|
|
419
|
+
|
|
413
420
|
# --- helpers ---------------------------------------------------------------
|
|
414
421
|
|
|
415
422
|
def calls = @mutex.synchronize { @count }
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: core
|
|
3
|
-
version: 0.4.
|
|
3
|
+
version: 0.4.3
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Recommended safety set"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
# Its members' highest floor (work-cap's: ctx.usage, raised at the release)
|
|
8
|
-
requires_chi: ">= 0.
|
|
8
|
+
requires_chi: ">= 0.144.0"
|
|
9
9
|
includes:
|
|
10
10
|
- loop-guard
|
|
11
11
|
- check-in
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cost-cap
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.5
|
|
4
4
|
scope: system
|
|
5
5
|
description: "A per-session $ cap: at the cap the turn pauses on Continue (another cap's worth) / Stop, a warning at 80% goes to the user only; /cap shows or sets it"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
|
-
sha256: sha256:
|
|
9
|
+
sha256: sha256:2d1f739ed0246d3d3040e0534cced2a1f621bdc5881fdde8b463df8a0501e477
|
|
10
10
|
# ctx.cost is new in chi 0.83.0; ask_user's effects: in the next chi
|
|
11
|
-
# ctx.session_dir is new in
|
|
12
|
-
requires_chi: ">= 0.
|
|
11
|
+
# ctx.session_dir is new in 0.143.0
|
|
12
|
+
requires_chi: ">= 0.144.0"
|
|
13
13
|
# session_settings: as a mapping of keys to types is new
|
|
14
14
|
# chi --set cost-cap.cap=3: a session's own cap (ctx.settings["cap"]), a number or off
|
|
15
15
|
session_settings: {cap: number}
|
|
@@ -247,7 +247,7 @@ class Plugin
|
|
|
247
247
|
def state(ctx, session)
|
|
248
248
|
return [nil, nil] unless session
|
|
249
249
|
|
|
250
|
-
path =
|
|
250
|
+
path = saved_file(ctx)
|
|
251
251
|
saved = JSON.parse(File.read(path))
|
|
252
252
|
raise JSON::ParserError, "not a JSON object" unless saved.is_a?(Hash)
|
|
253
253
|
return [nil, nil] unless saved.key?("cap")
|
|
@@ -309,6 +309,9 @@ class Plugin
|
|
|
309
309
|
|
|
310
310
|
def state_file(ctx) = File.join(ctx.session_dir, "state.json")
|
|
311
311
|
|
|
312
|
+
# For a read: a session with nothing saved keeps no folder.
|
|
313
|
+
def saved_file(ctx) = File.join(ctx.session_dir(create: false), "state.json")
|
|
314
|
+
|
|
312
315
|
def money(value) = self.class.money(value)
|
|
313
316
|
|
|
314
317
|
# Whether +key+ is new for the once-only notice kept in +ivar+ (and keep it).
|