samagotchi 0.57.0 → 0.59.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 +56 -1
- data/docs/architecture.md +3 -3
- data/docs/cli.md +5 -4
- data/docs/configuration.md +40 -23
- data/docs/hooks.md +2 -2
- data/docs/internals/background-tasks.md +7 -0
- data/docs/internals/engine.md +5 -5
- data/docs/internals/prompt-caching.md +2 -2
- data/docs/internals/tool-guardrails.md +2 -1
- data/docs/plugins.md +26 -6
- data/docs/sessions.md +3 -3
- data/lib/samagotchi/bridge/turn_accumulator.rb +1 -0
- data/lib/samagotchi/bridge.rb +6 -2
- data/lib/samagotchi/bundles/slow-commands/manifest.yml +5 -4
- data/lib/samagotchi/bundles/slow-commands/plugin.rb +169 -16
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/config.rb +11 -5
- data/lib/samagotchi/engine.rb +28 -3
- data/lib/samagotchi/host_config.rb +3 -1
- data/lib/samagotchi/host_registry.rb +2 -1
- data/lib/samagotchi/model_profile.rb +14 -3
- data/lib/samagotchi/model_settings.rb +4 -2
- data/lib/samagotchi/process_group.rb +76 -3
- data/lib/samagotchi/redacted_inspect.rb +21 -6
- data/lib/samagotchi/send_command.rb +1 -1
- data/lib/samagotchi/session_commands.rb +19 -6
- data/lib/samagotchi/steer_cut.rb +12 -8
- data/lib/samagotchi/steer_cut_after.rb +69 -0
- data/lib/samagotchi/system_prompt.rb +5 -5
- data/lib/samagotchi/terminal_ui/attached_loop.rb +2 -4
- data/lib/samagotchi/terminal_ui/formatting.rb +9 -5
- data/lib/samagotchi/terminal_ui.rb +15 -6
- data/lib/samagotchi/tool_activity.rb +18 -1
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tools/execute.rb +318 -70
- data/lib/samagotchi/tools/task_get.rb +2 -0
- data/lib/samagotchi/tools/task_runtime.rb +96 -13
- data/lib/samagotchi/tools/task_wait.rb +23 -5
- data/lib/samagotchi/utf8_default.rb +1 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +8 -2
- data/lib/samagotchi/web/public/app.js +7 -6
- data/lib/samagotchi/web/public/index.html +20 -7
- data/lib/samagotchi/web/public/read_view.js +3 -1
- data/lib/samagotchi/web/public/ref_menu.js +188 -0
- data/lib/samagotchi/web/public/ref_scan.js +11 -1
- data/lib/samagotchi/web/public/refs.js +271 -51
- data/lib/samagotchi/web/public/stage_view.js +21 -0
- data/lib/samagotchi/web/public/task_stop.js +25 -7
- data/lib/samagotchi/worker.rb +2 -2
- 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: 399c5b5b4ad3423bd266545c1f190e9e269f070a26bc48350640bf774eea7c4b
|
|
4
|
+
data.tar.gz: 26bfce90ed8fe75c15c777943f07699803f70160f54559ae057ba31eb168d956
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 042a3460c486987f7bd120fa6615c5cdd65d1ca6da0edfe9548746b83d9b66c6ad532b7088c20af082751b9bfad64252fba93123845fb36cfa0875d1a54ec0bf
|
|
7
|
+
data.tar.gz: f441e37232ea17b6af0ca047aea5331d7e062b3563382656fb323118f8607459cf97540858fccae714691aabfe989dd666f8de379f2024bc7a4b024864b2a694
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,59 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.59.0] - 2026-10-11
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `hosts.<name>.steer_cut_after` and `models.<key>.steer_cut_after`: how long a message sent as a cut waits, per host
|
|
16
|
+
or model (`0` = never, e.g. a slow local thinker); `/model` shows the resolved one.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- At its limit (`execute.timeout_sec`, 120 s), `execute` hands a command still running over as a background task
|
|
21
|
+
instead of killing it: the result names its `task_id` and the output so far, and `task_wait` waits for the rest.
|
|
22
|
+
The execute tool's description says so (the prompt changes once: a cached prompt prefix is rebuilt after the
|
|
23
|
+
upgrade). `execute.on_timeout: kill` keeps the old kill. The `!cmd` bang still kills at the limit. Command output
|
|
24
|
+
now goes through log files under the state dir, and the command's stdin is `/dev/null`. On Linux, a command that
|
|
25
|
+
writes through `/dev/stdout` or `/dev/stderr` loses its earlier output (known gap).
|
|
26
|
+
- slow-commands bundle 0.2.0 (needs chi 0.59.0): a command `execute` handed over as a task at its limit counts as
|
|
27
|
+
slow, and the "a finished task took …" advice also reads handed-over tasks. A command is now remembered without
|
|
28
|
+
the `time` / `timeout N` wrappers and `env` a model adds (`timeout 300 rspec` and `rspec` are one entry), its
|
|
29
|
+
`X=…` assignments sorted and `NO_COLOR`, `FORCE_COLOR` and `TERM` left out; `/slow-commands` shows each as last
|
|
30
|
+
typed. With `execute.on_timeout: kill`, a `timeout N` past the execute limit is denied up front.
|
|
31
|
+
- chi reads a project's AGENTS.md (the name other agent tools read) instead of AGENT.md: rename yours. AGENT.md is no
|
|
32
|
+
longer read. The setting that skips it is now `skip_agents_md` (env `SAMAGOTCHI_SKIP_AGENTS_MD`).
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- A `timeout N cmd` (GNU coreutils) that moved to a process group of its own is now stopped with the command: by the
|
|
37
|
+
user's Stop, at the execute limit and by `task_stop`. It used to run on, orphaned.
|
|
38
|
+
- A `task_create` the guardrails corrected (its result starts with a `ran as:` line) now counts as the session's own
|
|
39
|
+
task: the turn-end and cancel notes list it, a worker doesn't idle-exit under it, and the web's stop-task button
|
|
40
|
+
works for it.
|
|
41
|
+
- Web: a blank line in a read's numbered file lines is empty, not a space, so copying the lines keeps the file
|
|
42
|
+
as it is.
|
|
43
|
+
|
|
44
|
+
Update with `chi update`: it updates slow-commands (0.2.0: hand-overs and command wrappers). Then restart `chi web`.
|
|
45
|
+
If your project has an AGENT.md, rename it to AGENTS.md.
|
|
46
|
+
|
|
47
|
+
## [0.58.0] - 2026-10-11
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Web: a ref's menu. A ref with more than one target shows a `▾` on hover (a tap on a touch screen, ↓ on a focused
|
|
52
|
+
ref) with **Open in editor**, **Show in turn**, **Copy path** and, on github-pr's link, **PR diff**; the plain click
|
|
53
|
+
stays the first. **Show in turn** scrolls to the row that touched the file, opens its diff at the line and flashes
|
|
54
|
+
it. It needs no editor, so a phone on the LAN now gets an answer's refs too.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- A session with its own model resumes when the config names no default.model: `chi send`, attached, the plain REPL
|
|
59
|
+
(`chi --resume ID --no-shared`) and `-p … --non-interactive` crashed with "no model configured". `/model clear`
|
|
60
|
+
then says there is no default.
|
|
61
|
+
- Web: the LAN access token no longer shows when the web app or a stream proxy is inspected (an error message, a log
|
|
62
|
+
line).
|
|
63
|
+
|
|
11
64
|
## [0.57.0] - 2026-10-11
|
|
12
65
|
|
|
13
66
|
### Added
|
|
@@ -2523,7 +2576,9 @@ and long-lived sessions.
|
|
|
2523
2576
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
2524
2577
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
2525
2578
|
|
|
2526
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
2579
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.59.0...HEAD
|
|
2580
|
+
[0.59.0]: https://github.com/dm1try/samagotchi/compare/v0.58.0...v0.59.0
|
|
2581
|
+
[0.58.0]: https://github.com/dm1try/samagotchi/compare/v0.57.0...v0.58.0
|
|
2527
2582
|
[0.57.0]: https://github.com/dm1try/samagotchi/compare/v0.56.0...v0.57.0
|
|
2528
2583
|
[0.56.0]: https://github.com/dm1try/samagotchi/compare/v0.55.0...v0.56.0
|
|
2529
2584
|
[0.55.0]: https://github.com/dm1try/samagotchi/compare/v0.54.0...v0.55.0
|
data/docs/architecture.md
CHANGED
|
@@ -176,8 +176,8 @@ engine.run_turn(session, "hello", on_event: nil) # => LLM::ModelResult (`.outp
|
|
|
176
176
|
forwards the raw `KernelLoop` events unchanged (the low-level contract) and adds a few
|
|
177
177
|
higher-level events so UIs get clean turn boundaries without inferring them:
|
|
178
178
|
|
|
179
|
-
- `:turn_started` — `{ session_id:, prompt:, turn_id: }` (
|
|
180
|
-
turn, `images:` with the prompt's image refs)
|
|
179
|
+
- `:turn_started` — `{ session_id:, prompt:, turn_id:, cut_after: }` (`cut_after`: the seconds a cut waits for this
|
|
180
|
+
model's thinking, `0` = never; plus `continue: true` for a continue turn, `images:` with the prompt's image refs)
|
|
181
181
|
- `:turn_completed` — `{ result:, turn_summary:, display_pending: }` (`result`: the final
|
|
182
182
|
`LLM::ModelResult`; `display_pending`: `after_turn` hooks may still present the answer)
|
|
183
183
|
- `:turn_canceled` — `{ cancellation_reason:, cancelled_by:, duration_ms: }` (`cancelled_by`: the bundle or hook label that
|
|
@@ -198,7 +198,7 @@ engine.session # current session (Engine owns create/resume)
|
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
The system prompt (`SystemPrompt#build`) is the base prompt, then rg guidance, the
|
|
201
|
-
identity and the preloaded memories,
|
|
201
|
+
identity and the preloaded memories, AGENTS.md, the memory indexes, and last **the
|
|
202
202
|
model** (`Model: this session runs on <ref> (host …; model key …)` and a line telling
|
|
203
203
|
the model to answer "which model are you" from it, not from training), the working
|
|
204
204
|
directory and the session id and log (Gemma's native tool declarations still follow).
|
data/docs/cli.md
CHANGED
|
@@ -291,7 +291,7 @@ of UIs at once: the Web UI and attached terminals (`chi`, `--resume`,
|
|
|
291
291
|
a prompt, also while a turn runs (it merges into that turn as steering: the model
|
|
292
292
|
is told it is a steer and who sent it). Steering waits for the model's next step
|
|
293
293
|
and cuts nothing. `/cut TEXT` in a terminal or the web composer (or `chi send --cut`) asks for a cut:
|
|
294
|
-
once the model has streamed only thinking for `steer.cut_after` seconds (default
|
|
294
|
+
once the model has streamed only thinking for `steer.cut_after` seconds (per host/model: `steer_cut_after`; default
|
|
295
295
|
20), now or while your message waits, that generation is cut and the step starts again with your message, marked
|
|
296
296
|
`↪ cut in for your message` (see
|
|
297
297
|
[configuration.md](configuration.md#messages-during-a-running-turn-steer-and-cut)). The web
|
|
@@ -530,11 +530,12 @@ REPL alike:
|
|
|
530
530
|
does one sent after Ctrl-C: it doesn't merge into the turn being cancelled.
|
|
531
531
|
Reminder turns take merged lines too.
|
|
532
532
|
- `/cut TEXT` asks for a cut: a generation that has streamed only thinking for
|
|
533
|
-
`steer.cut_after` seconds is cut and the
|
|
533
|
+
`steer.cut_after` seconds (per host/model: `steer_cut_after`) is cut and the
|
|
534
|
+
step starts again with TEXT. The dim
|
|
534
535
|
line says what happened: `(cut in now)`, `(cuts in once the thinking passes
|
|
535
536
|
20 s)` (it waits for the thinking; the step may end first) or `(cutting is
|
|
536
|
-
off; goes in at the next step)` (`steer.cut_after
|
|
537
|
-
cut).
|
|
537
|
+
off; goes in at the next step)` (`steer.cut_after` is 0, per host/model:
|
|
538
|
+
`steer_cut_after`, or no generation to cut).
|
|
538
539
|
- `/queue TEXT` (plain REPL only) runs TEXT as a turn of its own after this
|
|
539
540
|
one: `(queued: runs after this turn)`. An attached terminal says
|
|
540
541
|
`(/queue isn't available in an attached session yet)` and sends nothing (the web
|
data/docs/configuration.md
CHANGED
|
@@ -876,26 +876,39 @@ Assist-mode UX:
|
|
|
876
876
|
## Messages during a running turn: steer and cut
|
|
877
877
|
|
|
878
878
|
A message for a running turn goes in at its next step and cuts nothing. A message sent as a cut (`/cut TEXT` in a
|
|
879
|
-
terminal or the web composer, `chi send --cut`) cuts the model's generation once it has streamed only thinking for
|
|
880
|
-
seconds
|
|
881
|
-
wait for a busy server doesn't count) and still does, and the model starts the step again with the message
|
|
882
|
-
(`↪ cut in for your message`). One that comes earlier cuts once the thinking passes
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
879
|
+
terminal or the web composer, `chi send --cut`) cuts the model's generation once it has streamed only thinking for
|
|
880
|
+
that model's cut wait (`steer.cut_after` seconds, default `20`, `0` = never; counted from its first thinking token,
|
|
881
|
+
so the wait for a busy server doesn't count) and still does, and the model starts the step again with the message
|
|
882
|
+
(`↪ cut in for your message`). One that comes earlier cuts once the thinking passes it, unless the step ends first
|
|
883
|
+
and the message goes in there. A plain message (from the web composer, `chi send` without `--cut`, a parent agent)
|
|
884
|
+
never asks for a cut. Such a cut spends no `retry.empty_answer` attempt and sends no hidden note; only the cut
|
|
885
|
+
thinking is lost. A generation that has streamed visible text or a tool call is never cut, and a plugin's steer
|
|
886
886
|
(`ctx.steer`, `ctx.sessions.send`) never cuts.
|
|
887
887
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
`steer.cut_after` (default `20`, env `SAMAGOTCHI_STEER_CUT_AFTER`):
|
|
888
|
+
The wait resolves per model, in this order: env `SAMAGOTCHI_STEER_CUT_AFTER`, then
|
|
889
|
+
`models.<key>.steer_cut_after`, `hosts.<name>.steer_cut_after`, `steer.cut_after` in config.yml, then 20. `0` means
|
|
890
|
+
never cut (a slow local thinker is left alone); a bad value warns and is ignored, like the other keys here.
|
|
893
891
|
|
|
894
892
|
```yaml
|
|
895
893
|
steer:
|
|
896
894
|
cut_after: 20 # seconds of thinking-only output before a cut may land; 0 = never
|
|
895
|
+
|
|
896
|
+
hosts:
|
|
897
|
+
splash:
|
|
898
|
+
host: splash.lan
|
|
899
|
+
steer_cut_after: 0 # never cut: a slow local thinker
|
|
900
|
+
|
|
901
|
+
models:
|
|
902
|
+
qwen:
|
|
903
|
+
steer_cut_after: 60 # a big model thinks a while before it answers
|
|
897
904
|
```
|
|
898
905
|
|
|
906
|
+
`/model` shows the resolved wait (`; steer cut: after 60 s (models: qwen)`, `; steer cut: never (hosts.splash)`).
|
|
907
|
+
|
|
908
|
+
`/queue TEXT` in the plain REPL runs TEXT as a turn of its own after the running one; the attached terminal and the web
|
|
909
|
+
don't queue yet, and a request that asks the Bridge or the web for `delivery: "queue"` is refused (400
|
|
910
|
+
`delivery_unavailable`).
|
|
911
|
+
|
|
899
912
|
## Server errors
|
|
900
913
|
|
|
901
914
|
An error status or a server's error event fails the turn with the server's
|
|
@@ -1058,15 +1071,15 @@ The helper reads these from its launch file: after editing them run
|
|
|
1058
1071
|
|
|
1059
1072
|
## Project specific description
|
|
1060
1073
|
|
|
1061
|
-
If an
|
|
1074
|
+
If an AGENTS.md file is present in the project root, samagotchi injects its
|
|
1062
1075
|
contents into the system prompt under a "Project specific description:" section.
|
|
1063
1076
|
The project root is the top of the git work tree chi runs in (a linked
|
|
1064
1077
|
worktree's own checkout), or the current directory outside a repository. An
|
|
1065
|
-
|
|
1078
|
+
AGENTS.md in the current directory is read instead when there is one; only one
|
|
1066
1079
|
file is read.
|
|
1067
1080
|
|
|
1068
|
-
To skip loading
|
|
1069
|
-
`config.yml` (env `
|
|
1081
|
+
To skip loading AGENTS.md, set `skip_agents_md: true` at the top level of
|
|
1082
|
+
`config.yml` (env `SAMAGOTCHI_SKIP_AGENTS_MD=true`).
|
|
1070
1083
|
|
|
1071
1084
|
## LLM context: the forget layer
|
|
1072
1085
|
|
|
@@ -1191,9 +1204,11 @@ web:
|
|
|
1191
1204
|
and Chrome ask per origin, so another `chi web` port asks again), and VS Code asks to allow opening local files.
|
|
1192
1205
|
- It opens the file as it is now, not the agent's copy: after later edits a diff line's number can point elsewhere,
|
|
1193
1206
|
and a file in a worktree that was removed won't open. The row's diff and output are what the agent saw.
|
|
1194
|
-
- Only a viewer on this machine
|
|
1195
|
-
(`web.host: lan`, the LAN URL or its QR code opened here). A phone
|
|
1196
|
-
|
|
1207
|
+
- Only a viewer on this machine opens the editor: a loopback request, or this machine on its own LAN address
|
|
1208
|
+
(`web.host: lan`, the LAN URL or its QR code opened here). A phone gets the targets that need no editor (an
|
|
1209
|
+
answer's ref shows its row in the turn; see the refs in [sessions.md](sessions.md)); a row's title stays plain
|
|
1210
|
+
text there. An SSH tunnel or a local reverse proxy (`tailscale serve`) looks like loopback, so a remote viewer
|
|
1211
|
+
behind one gets an editor target that can't open.
|
|
1197
1212
|
- `GET /api/sessions/:id` (and its `?tail=1`) carries `capabilities.editor` (true for such a viewer) and
|
|
1198
1213
|
`editor_url`, the template (null otherwise).
|
|
1199
1214
|
|
|
@@ -1289,7 +1304,8 @@ described in their own sections.
|
|
|
1289
1304
|
| `retry.base_delay` | `0.5` | yes | |
|
|
1290
1305
|
| `retry.max_delay` | `8.0` | yes | |
|
|
1291
1306
|
| `retry.empty_answer` | `1` | | Times a turn asks again after an empty answer (at most 3, `0` = off). See "Llama Network Retry Behavior". |
|
|
1292
|
-
| `steer.cut_after` | `20` | | Seconds a generation must have streamed only thinking before a message sent as a cut (`/cut TEXT`, `chi send --cut`) cuts it; other messages go in at the next step; `0` = never.
|
|
1307
|
+
| `steer.cut_after` | `20` | | Seconds a generation must have streamed only thinking before a message sent as a cut (`/cut TEXT`, `chi send --cut`) cuts it; other messages go in at the next step; `0` = never. `models.<key>.steer_cut_after` and `hosts.<name>.steer_cut_after` come first; see [Messages during a running turn](#messages-during-a-running-turn-steer-and-cut). |
|
|
1308
|
+
| `models.<key>.steer_cut_after`, `hosts.<name>.steer_cut_after` | none | | A model's or host's seconds a cut waits for its thinking: whole seconds, `0` = never (e.g. a slow local thinker). `/model` shows the resolved one. See [Messages during a running turn](#messages-during-a-running-turn-steer-and-cut). |
|
|
1293
1309
|
| `turn.max_iterations` | `100` | | A turn's step limit: model ↔ tool rounds before it stops and asks to continue (an integer ≥ 1). `--no-interrupt` turns get the larger of 1000 and this. See [CLI: Iteration Limit Behavior](cli.md#iteration-limit-behavior). |
|
|
1294
1310
|
| `turn.parent_continue` | `true` | | Whether a parent agent (`chi answer`) may answer Continue to a session's step-limit question; `false`: Stop only. config.yml only: no environment variable. See [Sub-agent](sub-agent.md) and [Guardrails](guardrails.md#approvals-from-a-parent-agent). |
|
|
1295
1311
|
| `update.gem` | `true` | | `false`: `chi update` never installs a newer gem (`--no-gem` for one run). See [CLI: Updating](cli.md#updating). |
|
|
@@ -1305,7 +1321,8 @@ described in their own sections.
|
|
|
1305
1321
|
| `execute.truncate_at_bytes` | `65536` | yes | The same for `execute` output. |
|
|
1306
1322
|
| `execute.preview_bytes` | `12288` | yes | |
|
|
1307
1323
|
| `execute.telemetry_threshold_pct` | `80` | yes | |
|
|
1308
|
-
| `execute.timeout_sec` | `120` | yes | Seconds one `execute` command may run before
|
|
1324
|
+
| `execute.timeout_sec` | `120` | yes | Seconds one `execute` command may run before `execute.on_timeout` applies. |
|
|
1325
|
+
| `execute.on_timeout` | `task` | yes | What happens to a command still running at that limit: `task` keeps it running as a background task (the result names its `task_id`; `task_wait` waits for it, `task_stop` stops it), `kill` stops it (`Error: command timed out after …s`). The `!cmd` bang always kills. See [Background tasks](internals/background-tasks.md). |
|
|
1309
1326
|
| `execute.description` | `true` | | `execute` offers the model an optional `description` (a few words on what the command does), shown as the tool row's title in `chi web` and in place of the command in the TUI's tool line. `false`: the parameter isn't declared, and rows show the command's first step. |
|
|
1310
1327
|
| `web.port` | `4567` | `--port` | `chi web`'s port. See [CLI](cli.md). |
|
|
1311
1328
|
| `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token (`chi web --new-token` replaces it). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
|
|
@@ -1316,8 +1333,8 @@ described in their own sections.
|
|
|
1316
1333
|
| `history.file` | state dir | | Prompt history path, shared by the TUI and the web composer. |
|
|
1317
1334
|
| `no_interrupt` | `false` | `--no-interrupt` | Raise the step limit of a turn to 1000 (or `turn.max_iterations`, when larger); a top-level key. |
|
|
1318
1335
|
| `no_default_input` | `false` | `--no-default-input` | Don't pre-fill `default.input`; a top-level key. |
|
|
1319
|
-
| `
|
|
1336
|
+
| `skip_agents_md` | `false` | | Don't load AGENTS.md; a top-level key (see below). |
|
|
1320
1337
|
|
|
1321
|
-
`max_tool_output_chars`, `
|
|
1338
|
+
`max_tool_output_chars`, `skip_agents_md`, `no_interrupt` and
|
|
1322
1339
|
`no_default_input` have no section: in `config.yml` they are top-level keys as
|
|
1323
1340
|
written (`max_tool_output_chars: 20000`).
|
data/docs/hooks.md
CHANGED
|
@@ -56,7 +56,7 @@ The plugin class must respond to `#call(event)` — duck-typed, no base class re
|
|
|
56
56
|
| `:after_generation` | After LLM returns (both loops); not after a generation a hook cut (`stop_generation`) | `{ type: :after_generation, iteration: N, response: "...", messages: [...] (the conversation as sent) }` |
|
|
57
57
|
| `:generation_progress` | While the response streams, in batches (see [Watching the stream](#watching-the-stream)) | `{ type: :generation_progress, iteration: N, thinking: "..." (new since the last fire), text: "..." (new visible text), thinking_chars: N, text_chars: N (this generation so far), elapsed_ms: N, restarted: true (only on the first fire after a dropped stream was asked again) }` |
|
|
58
58
|
| `:before_tool_call` | Before tool dispatch (and before `tool_call_started`) | `{ type: :before_tool_call, iteration: N, call: {...}, params: "...", guardrail: Verdict, context: {...}, targets: {...}, blocked: false, block_reason: nil }` |
|
|
59
|
-
| `:after_tool_call` | After tool execution | `{ type: :after_tool_call, iteration: N, tool: "read", output: "..." (the text the model gets: capped at max_tool_output_chars, a cut one ending in a [cut: N of M chars …] line), status: "ok" \| "error" \| "blocked" \| "stopped", call: {...} (the call that ran, as the gate left it), elapsed_ms: N (the tool's own run, without an approval wait; nil for a denied call), result: "..." (the tool's own text: no "[read]" lead, no "ran as:" line, never cut) }`; `stopped` is a `task_wait` the turn's Stop ended (the task runs on) or whose task was stopped. Match on `result:`, not `output:`: it is what the tool said (`Error: command timed out after 120s` for an execute killed at its limit, `Samagotchi::Tools::Execute::TIMEOUT_PREFIX`) |
|
|
59
|
+
| `:after_tool_call` | After tool execution | `{ type: :after_tool_call, iteration: N, tool: "read", output: "..." (the text the model gets: capped at max_tool_output_chars, a cut one ending in a [cut: N of M chars …] line), status: "ok" \| "error" \| "blocked" \| "stopped", call: {...} (the call that ran, as the gate left it), elapsed_ms: N (the tool's own run, without an approval wait; nil for a denied call), result: "..." (the tool's own text: no "[read]" lead, no "ran as:" line, never cut) }`; `stopped` is a `task_wait` the turn's Stop ended (the task runs on) or whose task was stopped. Match on `result:`, not `output:`: it is what the tool said (`Error: command timed out after 120s` for an execute killed at its limit in kill mode, `Samagotchi::Tools::Execute::TIMEOUT_PREFIX`; `Still running at the 120s limit: …` for one handed over as a task, `Samagotchi::Tools::Execute::HANDED_OVER`) |
|
|
60
60
|
| `:session_end` | After each turn `:after_turn` fires for, after it (turn-level lifecycle) | `{ type: :session_end, session_id: "..." }` |
|
|
61
61
|
|
|
62
62
|
Every event also carries the hook runtime (next section): `hook:` (the label
|
|
@@ -222,7 +222,7 @@ What a cut is, in detail:
|
|
|
222
222
|
- A Stop from the user right after a cut is a plain cancel.
|
|
223
223
|
- A cut for a message (one sent as a cut, `/cut TEXT` or `chi send --cut`,
|
|
224
224
|
into a generation that has streamed only thinking for `steer.cut_after`
|
|
225
|
-
seconds
|
|
225
|
+
seconds, per host/model: `steer_cut_after`; other messages go in at the next step and never cut)
|
|
226
226
|
is not a plugin's cut: it sends no hidden note and spends no retry; the
|
|
227
227
|
message goes in and the UIs print `↪ cut in for your message`. The
|
|
228
228
|
stream's `generation_completed` says `stopped_by: "steer"`, and
|
|
@@ -20,7 +20,14 @@ Behavior:
|
|
|
20
20
|
- `execute`, `task_create` and the `!cmd` shell bang run the command in a non-login shell: `/bin/sh -c` on Linux; on macOS, when `/bin/zsh` is there, `/bin/zsh --emulate sh +o ignore_braces +o sh_glob -o bash_rematch +o bsd_echo -c` (macOS's `/bin/sh` is bash 3.2, which can't parse an apostrophe in a heredoc inside `"$( )"`, the usual commit message shape). That mode reads no rc files and keeps POSIX word splitting, unmatched globs left as is, `{1..3}`, `[[ … =~ (…) ]]` with `BASH_REMATCH`, arrays, `$'…'`, `&>` and echo expanding `\t`. Bash-only builtins and variables (`shopt`, `BASH_VERSION`, `${!prefix*}`, `read -p`) are not there.
|
|
21
21
|
- Task metadata and output are persisted under `tmp/tasks/`.
|
|
22
22
|
- A task record keeps its process's pid and start time (`pid_started`). Before `task_stop` signals, the pid must still lead its own process group and must not have started at another time; otherwise the pid was reused by another process, nothing is signalled and the task ends as failed. A start time that can't be read at that moment (a failed `ps`) falls back to the group check, as does a record without `pid_started` (from an older chi). `task_get`, `task_list`, `task_wait`'s polls and the running-task lists (a cancelled turn's note, a worker's idle exit) make the same check, so a task whose pid was reused reads as exited (failed) at once, not as running until a `task_stop`; on macOS that is one `ps` per running task per check (~5 ms).
|
|
23
|
+
- A stop (`task_stop`, the user's Stop of an `execute`, the execute limit in kill mode) TERMs the task's process group, then KILLs it after a grace. The groups its processes started are stopped too: a GNU `timeout N cmd` (without `--foreground`) moves itself and its command to a group of its own. `ProcessGroup.stop` finds them with one `ps -A -o pid=,ppid=,pgid=` (~40 ms on macOS) before the TERM and again before the KILL, walking down from the group's members. A process whose parent already exited (reparented to init, e.g. a setsid'd `cmd &`, or a backgrounded `timeout N cmd &` after its shell exited) is not found and runs on.
|
|
23
24
|
- Task listing is workspace-scoped (current project only).
|
|
25
|
+
- `execute` runs its command with stdout and stderr in two logs under `$XDG_STATE_HOME/samagotchi/execute/<id>/` (outside the workspace: `git status` and `rm -rf tmp` never see them), stdin `/dev/null` and the same `exit_code` wrapper as a task. Its own copies of the logs hold a shared `flock` across the spawn: while a fresh exclusive `flock` fails, some process the command started still has them open. When the shell exits, a background child still holding them (`server &`) gets 1.5 s, then its group is stopped; one holding none (`nohup server >log 2>&1 &`) is left alone. A holder that left the group (setsid) is out of the stop's reach: it runs on, the result ends with the "left running" hint, and its dir stays for it to write to (the sweep removes it once no one holds the logs). Otherwise a finished command's dir is removed. Known gap on Linux: a command that writes through `/dev/stdout` or `/dev/stderr` (`echo b >/dev/stdout`) reopens the log file itself with `O_TRUNC`, so the output before it is lost (macOS dups the descriptor; the fix would be a relay pipe). The check needs the state dir on a local file system: over NFS, `flock` is emulated with `fcntl` locks, which children don't inherit.
|
|
26
|
+
- The exit code: the command runs after an EXIT trap that writes the shell's status to the `exit_code` file (`TaskRuntime.wrapped_shell_command`, shared with `task_create`), so `exit N`, a `set -e` failure and a trailing `\` or `|` in the command all record what the shell exited with. A command that sets its own EXIT trap replaces it: `execute` still reports the shell's status, and a handed-over one gets its code from chi's waiter while that chi runs; a `task_create` task ends failed with no exit code.
|
|
27
|
+
- A stop of an `execute` (the user's Stop, the limit in kill mode) is done once the shell is reaped and nothing holds the logs: a child that ignores TERM after its shell died gets the KILL.
|
|
28
|
+
- A command still running at `execute.timeout_sec` is handed over as a task (`execute.on_timeout: task`, the default; `kill` stops it instead, and the `!cmd` bang always kills): the result starts `Still running at the 120s limit: moved to a background task, not killed.`, then `task_id: …` and the tails of both logs so far. A timeout during the 1.5 s background grace is a kill (the shell is gone, nothing to hand over). If the record can't be written, the command is killed. `TaskRuntime.adopt` writes a record for the process execute already started, with `handed_over_by: "execute"`. Its logs stay where execute wrote them, so the record's paths are absolute: `output_path` (stdout), `stderr_path` (stderr, a log of its own) and `exit_code_path`. `started_at` (and `created_at`) is the spawn time, not the hand-over's; `pid_started` is read at the hand-over, while execute's detach waiter is still running. From there it is an ordinary task: `task_wait`, `task_get`, `task_list` and `task_stop` treat it as any other. Its dir gets a `task` back-pointer file holding the record's absolute path. Once per process, the first `execute` sweeps `…/execute/`: a dir whose logs no process holds is removed when it has no back-pointer and is a day old (chi died mid-command), when its record is gone, or when it is 14 days old. The records live in each project's `tmp/tasks`, which the state dir can't list: hence the back-pointer.
|
|
29
|
+
- `task_wait` and `task_get` show `stderr_path` when the record has one; where `task_wait` returns an output tail, a `stderr_tail:` of at most 10 lines follows it when that log isn't empty. A `task_create` task has one log (stdout and stderr together) and no `stderr_path`.
|
|
30
|
+
- Which tasks are a session's own (a cancelled turn's note, the turn-end note, a worker's idle exit, the web's stop-task button): the ids its `task_create` results and its handed-over executes name (`TaskRuntime.created_ids_in`, `created_id`). An execute's id is read only after its first line (`Execute::HANDED_OVER`, through `TaskRuntime.handed_over_id`; the web has a JS twin in `task_stop.js`), not from a `task_id:` line inside a run, which the command can print. (A command that prints the `---` separator chi joins a turn's results with, then a hand-over line, still fakes one: a known gap.) Either result may start with the `ran as: …` line of a gate-corrected call.
|
|
24
31
|
- `task_get` returns metadata and `output_path`; use `read` for output contents.
|
|
25
32
|
- `task_wait` accepts `timeout`, `tail_lines` (maximum 100), and `done_pattern` (a regular expression string).
|
|
26
33
|
- `task_create` accepts `env` as a JSON object string for deterministic overrides such as `PATH`; use an absolute interpreter path when that is simpler. Ruby/Bundler isolation variables remain protected.
|
data/docs/internals/engine.md
CHANGED
|
@@ -29,7 +29,7 @@ Engine#run_turn(session, prompt, on_event:, max_iterations:, cancel_controller:,
|
|
|
29
29
|
│
|
|
30
30
|
├── prepare_turn(turn, images)
|
|
31
31
|
│ # Emits :turn_started {session_id, prompt, turn_id, continue: true (only on a continue),
|
|
32
|
-
│ # images (only when there are any)}
|
|
32
|
+
│ # images (only when there are any), cut_after (the model's cut wait, s)}
|
|
33
33
|
│ # refresh_profile! (may ask the server's /props once)
|
|
34
34
|
│ # turn.settings = LLM::TurnSettings (vision, sampling, thinking; model_name is set in generate)
|
|
35
35
|
│ # announces guardrail load failures, starts and awaits plugin init tasks, applies staged tools
|
|
@@ -298,12 +298,12 @@ What the running generation streams, fed by the Engine's stream handler: whether
|
|
|
298
298
|
`cut`: `/cut TEXT`, `chi send --cut`) may cut it. `SteerCut#cut_for_steer` answers `:now`, `:waits` or `:off`; the
|
|
299
299
|
Bridge acks it as `cut`.
|
|
300
300
|
A generation is cuttable while it has streamed only thinking, is still thinking, and has thought for at least
|
|
301
|
-
`steer.cut_after` seconds (`cuttable?(min_age)`). It has `started!`, `chunk!`, `retrying!` and `finished!`.
|
|
301
|
+
the model's cut wait (`SteerCutAfter.resolve`: `steer_cut_after` per models:/hosts: entry, else `steer.cut_after`) seconds (`cuttable?(min_age)`). It has `started!`, `chunk!`, `retrying!` and `finished!`.
|
|
302
302
|
|
|
303
303
|
### WaitingSteer
|
|
304
304
|
|
|
305
305
|
A message that may cut the generation but came too early (the generation hadn't been thinking long enough). It
|
|
306
|
-
waits until the thinking passes
|
|
306
|
+
waits until the thinking passes the model's cut wait (the Engine checks on each thinking chunk), until a drain hands
|
|
307
307
|
the message to the model at a boundary, or until the turn ends (`clear!` in `release_turn`).
|
|
308
308
|
|
|
309
309
|
## Events
|
|
@@ -322,7 +322,7 @@ Turn events (the main ones; `Events` in `events.rb` keeps the shared sets):
|
|
|
322
322
|
|
|
323
323
|
| Event | When | Payload |
|
|
324
324
|
|---|---|---|
|
|
325
|
-
| `:turn_started` | `prepare_turn` | `{session_id, prompt, turn_id, continue?, images
|
|
325
|
+
| `:turn_started` | `prepare_turn` | `{session_id, prompt, turn_id, continue?, images?, cut_after}` |
|
|
326
326
|
| `:reminder_injected` | due reminders went into the turn | `{reminders}` |
|
|
327
327
|
| `:context_status` | every request (unless `context.status` is off), the current estimate before it | `{iteration, status, usage, bucket, source}` (internals/context-telemetry.md) |
|
|
328
328
|
| `:generation_started` | a request begins | `{iteration, context_window_tokens, context_window_source, price}` (`price`: the turn's `ModelPrice#to_h`, nil without one; native adds `profile`, `profile_source`) |
|
|
@@ -495,7 +495,7 @@ thought after the cue, kept in the model message so the next prompt starts with
|
|
|
495
495
|
5. **A stable system prompt for the cache.** `SystemPrompt#build` caches the prompt per Engine, keyed by
|
|
496
496
|
`[chat, thinking, layers]` (chat/native, the thinking level, the turn's LLM context layers), and is reset
|
|
497
497
|
only on a model switch, a tools change or a profile change. The stable part (base prompt, identity,
|
|
498
|
-
memories,
|
|
498
|
+
memories, AGENTS.md, memory indexes) comes before the volatile tail (model, working directory, session).
|
|
499
499
|
prompt-caching.md covers this in detail.
|
|
500
500
|
|
|
501
501
|
## Key files
|
|
@@ -29,9 +29,9 @@ right-hand column happens.
|
|
|
29
29
|
│ [native thinking token] base prompt │
|
|
30
30
|
│ rg guidance │ /thinking level,
|
|
31
31
|
│ system identity (+ model overlay) │ /model, editing a
|
|
32
|
-
│ model notes (model_notes_* matching the model) │ memory /
|
|
32
|
+
│ model notes (model_notes_* matching the model) │ memory / AGENTS.md
|
|
33
33
|
│ explicit (preloaded) memories │
|
|
34
|
-
│
|
|
34
|
+
│ AGENTS.md │ (picked up by a new
|
|
35
35
|
│ project + system memory indexes │ process or a rebuild)
|
|
36
36
|
│ ◄── remote Claude: cache_control breakpoint #1 │
|
|
37
37
|
├──────────────────────────────────────────────────────────────────────────┤
|
|
@@ -39,7 +39,8 @@ Configuration (config.yml `execute.*` keys and CLI flags too, as for `read`):
|
|
|
39
39
|
- `SAMAGOTCHI_EXECUTE_TRUNCATE_AT_BYTES` (default `65536`): output above this size is truncated.
|
|
40
40
|
- `SAMAGOTCHI_EXECUTE_PREVIEW_BYTES` (default `12288`): total preview budget split across head and tail.
|
|
41
41
|
- `SAMAGOTCHI_EXECUTE_TELEMETRY_THRESHOLD_PCT` (default `80`): include estimated output token impact only when threshold is crossed.
|
|
42
|
-
- `SAMAGOTCHI_EXECUTE_TIMEOUT_SEC` (`execute.timeout_sec`, default `120`): a command running
|
|
42
|
+
- `SAMAGOTCHI_EXECUTE_TIMEOUT_SEC` (`execute.timeout_sec`, default `120`): what a command still running then gets is `execute.on_timeout`.
|
|
43
|
+
- `SAMAGOTCHI_EXECUTE_ON_TIMEOUT` (`execute.on_timeout`, default `task`): `task` hands the command over as a background task, not killed; the result starts `Still running at the 120s limit: moved to a background task, not killed.` (`Execute::HANDED_OVER`), then its `task_id:` and the tails of stdout and stderr so far, and `task_wait` waits for it. `kill` stops it and answers `Error: command timed out after …s` (`Execute::TIMEOUT_PREFIX`) with the hint to use `task_create`. The `!cmd` bang always kills. See [Background tasks](background-tasks.md).
|
|
43
44
|
|
|
44
45
|
Implementation note:
|
|
45
46
|
|
data/docs/plugins.md
CHANGED
|
@@ -1178,12 +1178,14 @@ bootstrap` installs it with the `core` profile). It is written only against
|
|
|
1178
1178
|
this API (`lib/samagotchi/bundles/slow-commands/plugin.rb`), with `chi.on`
|
|
1179
1179
|
hooks and a `/slow-commands` command, and has no memory file.
|
|
1180
1180
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1181
|
+
At its limit (120 s, `execute.timeout_sec`) `execute` hands a command still
|
|
1182
|
+
running over as a background task (`execute.on_timeout: task`, the default),
|
|
1183
|
+
or kills it (`kill`). A full test suite or a build takes longer, so each run
|
|
1184
|
+
first blocks the turn for those two minutes (and, in kill mode, loses them,
|
|
1185
|
+
then starts again with `task_create` and `task_wait`); the next session does
|
|
1186
|
+
the same. slow-commands remembers such a command for the project:
|
|
1187
|
+
|
|
1188
|
+
- An `execute` that **timed out** or was **handed over** at the limit, or ran **`warn_ratio` of the limit** or
|
|
1187
1189
|
longer (default 0.8, so 96 s of 120), is recorded as slow. A later run of
|
|
1188
1190
|
it under half the limit **with exit 0** drops the entry; a fast failure (a
|
|
1189
1191
|
load error) doesn't, since it says nothing about how long the suite takes.
|
|
@@ -1203,6 +1205,10 @@ does the same. slow-commands remembers such a command for the project:
|
|
|
1203
1205
|
- **Once per command per turn**: the same `execute` again in that turn runs.
|
|
1204
1206
|
The model insisting is a signal (maybe the suite got faster), and that run
|
|
1205
1207
|
records the new truth. A new turn denies again.
|
|
1208
|
+
- In kill mode (`execute.on_timeout: kill`), a **`timeout N` past the limit**
|
|
1209
|
+
is denied up front, with no history: execute stops the command first, so
|
|
1210
|
+
`timeout` never fires. In task mode it runs on as a task until `timeout`
|
|
1211
|
+
fires. `timeout`'s exit codes (124, 137) mean nothing special here.
|
|
1206
1212
|
|
|
1207
1213
|
A command is the same when its project and its text match. The project is
|
|
1208
1214
|
the session's git checkout (else its working directory). The text is the
|
|
@@ -1216,6 +1222,20 @@ one with a `$(…)`, backticks or a heredoc `cat` or `tee` reads (the key
|
|
|
1216
1222
|
can't tell two of those apart). A heredoc script (`python3 - <<'EOF'`)
|
|
1217
1223
|
keeps its body in the key, so each script is its own command.
|
|
1218
1224
|
|
|
1225
|
+
Wrappers a model adds by habit don't make a new command: at the start of
|
|
1226
|
+
each simple command (so after a leading `cd X &&` too), `time [-p]` and
|
|
1227
|
+
`timeout`/`gtimeout` with its options and duration are left out, so
|
|
1228
|
+
`timeout 300 bundle exec rspec` and `bundle exec rspec` are one entry. `X=…`
|
|
1229
|
+
assignments stay, sorted (`SPEC=… rake spec` is not `rake spec`), except
|
|
1230
|
+
`NO_COLOR`, `FORCE_COLOR` and `TERM` (`CI` stays: jest or vitest without it
|
|
1231
|
+
can sit in watch mode); a plain `env X=… cmd` is `X=…
|
|
1232
|
+
cmd`, and an `env` with an option (`-C`, `-i`, `-u`) stays as typed. The
|
|
1233
|
+
list shows each entry as it was last typed.
|
|
1234
|
+
|
|
1235
|
+
Since 0.2.0 (chi 0.59.0) a hand-over counts as timed out, and the advice's
|
|
1236
|
+
duration also reads a handed-over task, whose `exit_code` file is in the
|
|
1237
|
+
state dir (the record's `exit_code_path`).
|
|
1238
|
+
|
|
1219
1239
|
Entries live in `$XDG_STATE_HOME/samagotchi/plugins/slow-commands/commands.json`,
|
|
1220
1240
|
shared by every session (another session's record counts at once) and
|
|
1221
1241
|
forgotten after `ttl_days`.
|
data/docs/sessions.md
CHANGED
|
@@ -68,9 +68,9 @@ chi sessions clean --dry-run --days 7 # test sessions older than 7 da
|
|
|
68
68
|
**Ordering:**
|
|
69
69
|
|
|
70
70
|
- `Session.list` / `SessionManager.list_sessions` / `GET /api/sessions?sort=&order=&limit=&offset=` default to `updated_at desc` (newest activity first). Also supports `created_at`, `asc`. `X-Total-Count` header when paginated.
|
|
71
|
-
- Web UI (`chi web`): the page's scope is in its URL. Started in a git repo, `chi web` opens `/?dir=<that folder>`: that project's sessions, and new chats start in that folder; the header chip says `<project> · all`, and `all` opens the same place without `?dir` (every session; a new chat there starts in the server's own folder, shown on the start page, and cards name their folder; `← <project>` goes back to the project view it came from, or to the server's own project). One server serves every project: a second `chi web` (from another repo) finds it through `GET /api/info` and prints (with `--open`, opens) its page for its own folder instead of starting another. The 3 latest sessions sit above the chat (`hide ▴` puts them away behind a `▾ N sessions` pill, remembered; in a window under 900 px wide, where the strip scrolls sideways, `▴ hide` sits in the top bar where the pill comes back; a window under 700 px tall hides them by itself, so a question or approval card in the stage keeps room, and the pill shows them until the window is tall again). A parent and its delegates are one card there: its `▸ 2 delegates · 1 live · 1 waiting` chip (the waiting part in the attention colour, the card's border too) opens their rows in a popover under it (a row per delegate at any depth, indented; a click opens that one, Esc or a click outside closes it, and the strip's order holds while it is open). A fork is a card of its own, and so is a delegate whose parent isn't listed (archived, another project), with its `↳ <parent>` chip; the pill counts cards, and every live or waiting session. "All sessions" (or `/`) opens every session at `#/sessions`, with a search over preview, id and status (Esc or Back returns). There a parent's card holds its delegates the same way, the chip opening their rows inside the card (each with an archive button on hover), open by itself while one of them waits on you; the count says `14 (+7 delegates)`, a search that matches a delegate shows its parent's card with the list open and the matching rows marked, and select mode picks cards (archiving a parent takes its delegates). The open session is in the URL (`#/s/<id>`), so a reload or a copied link opens it again; the chi logo top left goes back to the empty start for a new chat. The message box grows with its text; drag its top edge to keep it taller (double-click resets). A file in a tool row (a read, edit or write's title) and its diff lines are refs: hover one and click to open the current file in your editor at that line, on this machine only (`web.editor`; the browser's "always allow" for the page's port and VS Code's "allow opening local files" are asked once; see [Opening files from chi web](configuration.md#opening-files-from-chi-web)). So are an edit or write approval card's paths (at the first line the dry run changes) and its diff lines, before you approve, and the numbered lines of a read's output: `lines 40–60` under it opens the text as read, numbered, when the output is the file as-is (not the head/tail preview of a big file, an error or an image; no numbers after a cut). A `path:line` (or `path:line-line`) in an answer or a step's text is one too once the turn has ended, and after a reload, when it names a file a row or card on the page touched: the path under the session's folder, else the one touched file it ends with (a bare `app.js:12` when only one touched file is named so; two such files make it none); a path without a line counts only when it is a touched file exactly. Code blocks, links (a github-pr link to the PR stays the PR's), bare URLs and inline code with more than the ref in it are left alone, and a file the session didn't touch is never linked. The info bar copies `chi --attach <id>` for a terminal. A session whose prompt carried model notes has a `notes: deepseek` chip there (the names without `model_notes_`; the tooltip lists each with its scope and size; `session.prompt_notes` in `GET /api/sessions/:id`, the live worker's, else the session file's; see [Memory](memory.md#model-notes)). Its `llm ctx` chip names the LLM context strategy the next turn runs under (`llm ctx stale,forget · session`, in the accent colour when the session set its own; the tooltip gives each value's source), and its popover sets the session's own strategy, apply rule and budget, or follows the model again, by running `/llm-context` in the worker (the reply shows as a command bubble); `GET /api/sessions/:id`'s `session.llm_context` carries the values (the live worker's, else resolved from the session file), and a `command_ran` that changed them carries the new ones. Its `think` chip does the same for the thinking level (`/thinking`), and on a chat host its `reasoning` chip for the reasoning history (`reasoning all · session`, `/reasoning-history`; `session.reasoning_history`, null on a native host). A `/thinking` or `/reasoning-history` change that restarts the prompt cache asks first: the reply bubble has an **Apply (restarts the cache)** button that sends its `--yes` line (the chips' Set sends that line itself; see [Changes that restart the prompt cache](configuration.md#changes-that-restart-the-prompt-cache)). It shows the context used (`ctx 42%`) and the last generation's decode speed (`87 tok/s`, `~64 tok/s` when estimated; see `/stats` in [cli.md](cli.md#session-commands)); hovering `ctx` there or on a session card lists the session's tokens in (summed over every request, with the cached share), out (with reasoning) and the cost when the provider reports one, for this session only (one estimated from a `hosts.<name>.models` price shows with `~`: `cost: ~$0.54 ($0.42 reported, ~$0.12 estimated)`), and the memory indexes its prompt holds (`memory index: ~3.4k tokens in this session's prompt (system 2.0k, project 1.4k)`; see [Memory](memory.md#the-indexs-size)). The start page's model picker (bottom left of the composer, the chosen name `▾`) chooses the model a new chat starts on; it opens a search list over the conversation (↓ or a click; downward when the start page leaves no room above): an empty search shows the last 5 picks (Recent) and then each host's model ids A–Z under the host, the default host first (an id the host declares under `hosts.<name>.models` first, noted `config`, its tooltip `served by config (hosts.<name>.models)`; a host that failed to list its models keeps those ids, greyed and noted `host down`, still pickable; a remembered or default model stays chosen even then, greyed on the button); typed words must each match the host or the id (a substring, else for 3+ characters the letters in order, so `deepseek4.1 fla` finds `openrouter · deepseek/deepseek-v4.1-flash`), ⏎ picks, Esc/Tab close, a pick puts the focus in the message box: `GET /api/models` lists the hosts' models as chi spells them (`{default, models: [{name, host, id, configured?, unavailable?, sampling?, llm_context?}], warning?}`; `configured: true` marks an id declared under `hosts.<name>.models`, listed first under its host, `unavailable: true` one whose host failed to list its models; `sampling` is the model's configured request parameters as `/model` words them, the row's tooltip in the list; bare for the default host, `host:model` for the others, from the same cached lists as `/models`, a bounded wait, a host that is down noted in `warning`), and `POST /api/sessions` takes `model` (blank means the default). Beside it, an `llm ctx` chip shows the LLM context the picked model starts under (each `GET /api/models` row's `llm_context`, as the info bar's); its form sets one for the new chat only, sent as `POST /api/sessions`'s `llm_context` and never remembered (see [configuration.md](configuration.md#llm-context-a-sessions-own-strategy)). The start page's first message goes as later ones do: `POST /api/sessions` with `idle: true` and `preview` (the message, which names the session until its turn is saved), then `POST /api/sessions/:id/turn`, so a failed first turn puts its prompt back in the composer too; a first line that is one of the new session's commands (`/model …`) goes to `POST /api/sessions/:id/command` instead, as later ones do. The browser remembers the last model picked; a running session's model is in the info bar and changes only with `/model`. In the composer a line is a command when it is `!…` or starts with one of the session's command names (its `commands` list; `/stats`, `/exit` and the other terminal words get the page's own reply); an unknown `/word` (`/usr/bin/env is missing`) goes to the model as a prompt, as in a terminal; a one-word `/word` no command answers (`/modle`) is not sent: the page shows `Unknown command /modle. Did you mean /model? /help lists the commands.` (the "Did you mean" part only when a name is close). A `POST /turn` (or a session's first `prompt`) that is one of the worker's commands runs as that command too (`202 {status, command_id, session_id}`), so `chi send`, `chi -p` and a page whose command list lags all get the command. ↑/↓ in the composer walk the prompt history the TUI's ↑ walks ([Persistent Prompt History](cli.md#persistent-prompt-history)): ↑ with the caret on the first line, ↓ from the last line while browsing, ↓ past the newest brings the draft back; `GET /api/history` returns `{entries: [...]}`, oldest first (the page fetches it on focus and after each send). The server records a sent line in the routes once the worker took it (`POST /turn` accepted, a `!command` through `POST /command` or as a session's first line); `"history": false` in the body keeps it out (an image-only send).
|
|
71
|
+
- Web UI (`chi web`): the page's scope is in its URL. Started in a git repo, `chi web` opens `/?dir=<that folder>`: that project's sessions, and new chats start in that folder; the header chip says `<project> · all`, and `all` opens the same place without `?dir` (every session; a new chat there starts in the server's own folder, shown on the start page, and cards name their folder; `← <project>` goes back to the project view it came from, or to the server's own project). One server serves every project: a second `chi web` (from another repo) finds it through `GET /api/info` and prints (with `--open`, opens) its page for its own folder instead of starting another. The 3 latest sessions sit above the chat (`hide ▴` puts them away behind a `▾ N sessions` pill, remembered; in a window under 900 px wide, where the strip scrolls sideways, `▴ hide` sits in the top bar where the pill comes back; a window under 700 px tall hides them by itself, so a question or approval card in the stage keeps room, and the pill shows them until the window is tall again). A parent and its delegates are one card there: its `▸ 2 delegates · 1 live · 1 waiting` chip (the waiting part in the attention colour, the card's border too) opens their rows in a popover under it (a row per delegate at any depth, indented; a click opens that one, Esc or a click outside closes it, and the strip's order holds while it is open). A fork is a card of its own, and so is a delegate whose parent isn't listed (archived, another project), with its `↳ <parent>` chip; the pill counts cards, and every live or waiting session. "All sessions" (or `/`) opens every session at `#/sessions`, with a search over preview, id and status (Esc or Back returns). There a parent's card holds its delegates the same way, the chip opening their rows inside the card (each with an archive button on hover), open by itself while one of them waits on you; the count says `14 (+7 delegates)`, a search that matches a delegate shows its parent's card with the list open and the matching rows marked, and select mode picks cards (archiving a parent takes its delegates). The open session is in the URL (`#/s/<id>`), so a reload or a copied link opens it again; the chi logo top left goes back to the empty start for a new chat. The message box grows with its text; drag its top edge to keep it taller (double-click resets). A file in a tool row (a read, edit or write's title) and its diff lines are refs: hover one and click to open the current file in your editor at that line, on this machine only (`web.editor`; the browser's "always allow" for the page's port and VS Code's "allow opening local files" are asked once; see [Opening files from chi web](configuration.md#opening-files-from-chi-web)). So are an edit or write approval card's paths (at the first line the dry run changes) and its diff lines, before you approve, and the numbered lines of a read's output: `lines 40–60` under it opens the text as read, numbered, when the output is the file as-is (not the head/tail preview of a big file, an error or an image; no numbers after a cut). A `path:line` (or `path:line-line`) in an answer or a step's text is one too once the turn has ended, and after a reload, when it names a file a row or card on the page touched: the path under the session's folder, else the one touched file it ends with (a bare `app.js:12` when only one touched file is named so; two such files make it none); a path without a line counts only when it is a touched file exactly. Code blocks, links, bare URLs and inline code with more than the ref in it are left alone, and a file the session didn't touch is never linked; a link whose text is such a ref alone (github-pr's link to the PR diff) becomes a ref too, and its plain click still goes to the PR. A ref with more than one target shows a small `▾` on hover: it opens a menu of them, in order **PR diff** (a link's), **Open in editor**, **Show in turn** and **Copy path**, and a plain click runs the first (a link's own, else the editor, else show in turn). **Show in turn** scrolls to the tool row that touched the file (looked for in the ref's own step, then its own turn, then the rows before it, then the nearest after it; there, the last one showing the ref's line, else the last one), opens the steps, the stage's steps and the row's diff (or the read showing the line) and flashes the row and the line; an answer's or a step's ref and an approval card's path have it, not a row's own title or lines. It needs no editor, so a viewer without one (a phone on the LAN) gets an answer's refs with **Show in turn** as their click; a row's title and diff lines stay plain text there. On a touch screen a tap opens the menu. A ref with a target is a tab stop: Enter runs its first target, ↓ opens the menu (↑/↓ move, Enter picks, Esc closes back to the ref). Diff and read lines have their click only. The info bar copies `chi --attach <id>` for a terminal. A session whose prompt carried model notes has a `notes: deepseek` chip there (the names without `model_notes_`; the tooltip lists each with its scope and size; `session.prompt_notes` in `GET /api/sessions/:id`, the live worker's, else the session file's; see [Memory](memory.md#model-notes)). Its `llm ctx` chip names the LLM context strategy the next turn runs under (`llm ctx stale,forget · session`, in the accent colour when the session set its own; the tooltip gives each value's source), and its popover sets the session's own strategy, apply rule and budget, or follows the model again, by running `/llm-context` in the worker (the reply shows as a command bubble); `GET /api/sessions/:id`'s `session.llm_context` carries the values (the live worker's, else resolved from the session file), and a `command_ran` that changed them carries the new ones. Its `think` chip does the same for the thinking level (`/thinking`), and on a chat host its `reasoning` chip for the reasoning history (`reasoning all · session`, `/reasoning-history`; `session.reasoning_history`, null on a native host). A `/thinking` or `/reasoning-history` change that restarts the prompt cache asks first: the reply bubble has an **Apply (restarts the cache)** button that sends its `--yes` line (the chips' Set sends that line itself; see [Changes that restart the prompt cache](configuration.md#changes-that-restart-the-prompt-cache)). It shows the context used (`ctx 42%`) and the last generation's decode speed (`87 tok/s`, `~64 tok/s` when estimated; see `/stats` in [cli.md](cli.md#session-commands)); hovering `ctx` there or on a session card lists the session's tokens in (summed over every request, with the cached share), out (with reasoning) and the cost when the provider reports one, for this session only (one estimated from a `hosts.<name>.models` price shows with `~`: `cost: ~$0.54 ($0.42 reported, ~$0.12 estimated)`), and the memory indexes its prompt holds (`memory index: ~3.4k tokens in this session's prompt (system 2.0k, project 1.4k)`; see [Memory](memory.md#the-indexs-size)). The start page's model picker (bottom left of the composer, the chosen name `▾`) chooses the model a new chat starts on; it opens a search list over the conversation (↓ or a click; downward when the start page leaves no room above): an empty search shows the last 5 picks (Recent) and then each host's model ids A–Z under the host, the default host first (an id the host declares under `hosts.<name>.models` first, noted `config`, its tooltip `served by config (hosts.<name>.models)`; a host that failed to list its models keeps those ids, greyed and noted `host down`, still pickable; a remembered or default model stays chosen even then, greyed on the button); typed words must each match the host or the id (a substring, else for 3+ characters the letters in order, so `deepseek4.1 fla` finds `openrouter · deepseek/deepseek-v4.1-flash`), ⏎ picks, Esc/Tab close, a pick puts the focus in the message box: `GET /api/models` lists the hosts' models as chi spells them (`{default, models: [{name, host, id, configured?, unavailable?, sampling?, llm_context?}], warning?}`; `configured: true` marks an id declared under `hosts.<name>.models`, listed first under its host, `unavailable: true` one whose host failed to list its models; `sampling` is the model's configured request parameters as `/model` words them, the row's tooltip in the list; bare for the default host, `host:model` for the others, from the same cached lists as `/models`, a bounded wait, a host that is down noted in `warning`), and `POST /api/sessions` takes `model` (blank means the default). Beside it, an `llm ctx` chip shows the LLM context the picked model starts under (each `GET /api/models` row's `llm_context`, as the info bar's); its form sets one for the new chat only, sent as `POST /api/sessions`'s `llm_context` and never remembered (see [configuration.md](configuration.md#llm-context-a-sessions-own-strategy)). The start page's first message goes as later ones do: `POST /api/sessions` with `idle: true` and `preview` (the message, which names the session until its turn is saved), then `POST /api/sessions/:id/turn`, so a failed first turn puts its prompt back in the composer too; a first line that is one of the new session's commands (`/model …`) goes to `POST /api/sessions/:id/command` instead, as later ones do. The browser remembers the last model picked; a running session's model is in the info bar and changes only with `/model`. In the composer a line is a command when it is `!…` or starts with one of the session's command names (its `commands` list; `/stats`, `/exit` and the other terminal words get the page's own reply); an unknown `/word` (`/usr/bin/env is missing`) goes to the model as a prompt, as in a terminal; a one-word `/word` no command answers (`/modle`) is not sent: the page shows `Unknown command /modle. Did you mean /model? /help lists the commands.` (the "Did you mean" part only when a name is close). A `POST /turn` (or a session's first `prompt`) that is one of the worker's commands runs as that command too (`202 {status, command_id, session_id}`), so `chi send`, `chi -p` and a page whose command list lags all get the command. ↑/↓ in the composer walk the prompt history the TUI's ↑ walks ([Persistent Prompt History](cli.md#persistent-prompt-history)): ↑ with the caret on the first line, ↓ from the last line while browsing, ↓ past the newest brings the draft back; `GET /api/history` returns `{entries: [...]}`, oldest first (the page fetches it on focus and after each send). The server records a sent line in the routes once the worker took it (`POST /turn` accepted, a `!command` through `POST /command` or as a session's first line); `"history": false` in the body keeps it out (an image-only send).
|
|
72
72
|
- Web notifications: when a session needs you while the tab is hidden or behind another window (a question or a guardrail approval, a plugin card with buttons during a turn such as check-in's, a failed turn, a turn done after 10 s or more; a delegated session only for its questions, a canceled turn never), the page title counts it, `(N) Chi`, until you come back to the tab. The bell at the right of the top bar also turns on OS notifications ("needs an answer", "needs approval", "needs you", "turn failed", "done in 42 s", under the session's first message; a click opens the session): its first click asks the browser for the permission, and the choice is kept per browser. Any session of the page's scope counts, not only the open one. What was already so when the page loaded never counts, and several chi tabs show one notification per event. A chi tab in front tells the others (a `BroadcastChannel`), so while you look at one, the ones behind neither notify nor count what it shows; coming back to a tab clears those events in the other tabs' titles too. The hub's `session` frames carry what this needs: `pending_question` (`{id, kind}`, kind `question` or `approval`), `pending_card` (`{id, bundle}`: the running turn's card with actions, from `pending_card.json` that the worker keeps in the session's folder while one is open; a card without actions, such as loop-guard's, never counts) and `last_turn` (`{outcome, ended_at, seconds, origin}`, saved in the session's JSON as the engine ends each turn).
|
|
73
|
-
- The web frontend is a zero-build ES-module stack in `lib/samagotchi/web/public/`: `data.js` (retrieval, typed SSE `openStream` for a session and `openEvents` for the session list), `sessions_list.js` (the list as a pure reducer over `GET /api/events`: a snapshot replaces it, an upsert keeps a known card in place, a removal drops it; `families()` groups delegates under their parent), `families_view.js` (a parent's family chip, its rows and the strip's popover), `chains.js` (session chains: the links from `continues`, the cards folded into the latest link's, its `day N` chip and rows, the info bar's ← → links, a refused continue), `notify.js` (which of those events needs the user, for the notifications), `app.js` (presentation/state; a session with no live stream gets one when its `session` event says `bridge_up`, after one re-read: `event_seq` starts over in each worker, so a dropped stream is never resumed with its old cursor), `format.js` (pure formatters such as `previewOf`), `prompt_history.js` (the composer's ↑/↓ over the shared prompt history), `commands.js` (session commands: which composer lines are commands, the page's own replies, a `command_ran`'s bubble, the page's command bubbles kept across a re-read), `composer.js` (where a composer line goes, `routeComposerLine`: /cut and /queue, the start page's own replies, a session command or `!cmd`, a typo hint, else a turn; and a session command sent from the composer or a chip), `restore.js` (a failed turn's prompt handed back into this tab's composer, before or after its `/turn` ack), `lists_view.js` (the session lists: the strip and its pill, all sessions with its search, count and select mode, their cards, the family and chain popovers; the cards, tile and pill as lit-html templates, drawn as new nodes each time, and the lists' orders as pure functions), `stream_handlers.js` (the open session's stream event handlers, by event type, over the page's views and hooks; a snapshot replays through them too), `info_bar.js` (the session bar's info line as a lit-html template over a plain model: id, status, model, chain links, chips, ctx, speed, clock, worker badge), `session_state.js` (what the page knows about the open session: its empty state, and `sessionStateFrom`, the fields a `GET /sessions/:id` payload sets), `refs.js` (refs: a kind and a target on the element, the
|
|
73
|
+
- The web frontend is a zero-build ES-module stack in `lib/samagotchi/web/public/`: `data.js` (retrieval, typed SSE `openStream` for a session and `openEvents` for the session list), `sessions_list.js` (the list as a pure reducer over `GET /api/events`: a snapshot replaces it, an upsert keeps a known card in place, a removal drops it; `families()` groups delegates under their parent), `families_view.js` (a parent's family chip, its rows and the strip's popover), `chains.js` (session chains: the links from `continues`, the cards folded into the latest link's, its `day N` chip and rows, the info bar's ← → links, a refused continue), `notify.js` (which of those events needs the user, for the notifications), `app.js` (presentation/state; a session with no live stream gets one when its `session` event says `bridge_up`, after one re-read: `event_seq` starts over in each worker, so a dropped stream is never resumed with its old cursor), `format.js` (pure formatters such as `previewOf`), `prompt_history.js` (the composer's ↑/↓ over the shared prompt history), `commands.js` (session commands: which composer lines are commands, the page's own replies, a `command_ran`'s bubble, the page's command bubbles kept across a re-read), `composer.js` (where a composer line goes, `routeComposerLine`: /cut and /queue, the start page's own replies, a session command or `!cmd`, a typo hint, else a turn; and a session command sent from the composer or a chip), `restore.js` (a failed turn's prompt handed back into this tab's composer, before or after its `/turn` ack), `lists_view.js` (the session lists: the strip and its pill, all sessions with its search, count and select mode, their cards, the family and chain popovers; the cards, tile and pill as lit-html templates, drawn as new nodes each time, and the lists' orders as pure functions), `stream_handlers.js` (the open session's stream event handlers, by event type, over the page's views and hooks; a snapshot replays through them too), `info_bar.js` (the session bar's info line as a lit-html template over a plain model: id, status, model, chain links, chips, ctx, speed, clock, worker badge), `session_state.js` (what the page knows about the open session: its empty state, and `sessionStateFrom`, the fields a `GET /sessions/:id` payload sets), `refs.js` (refs: a kind and a target on the element, the targets per kind, show in turn, one delegated set of click, key and hover listeners), `ref_menu.js` (a ref's `▾` and its menu). Unit-tested via `npm test` (`node --test spec/web/public/*.test.js`).
|
|
74
74
|
- Selecting a session in the web UI is read-only: `GET /api/sessions/:id` never spawns a worker (it reads a live worker's snapshot when one runs, else the session file). A prompt (`POST /turn`) or a command (`POST /command`) wakes the worker, and `/stream` briefly waits for a freshly-spawned bridge before answering. A caught-up SSE reconnect holds the stream open; `reset` markers are only sent for reconnects behind the ring window, or with a cursor from another worker (event ids are `<event_seq>-<epoch>`, one epoch per worker).
|
|
75
75
|
|
|
76
76
|
**Test-session hygiene:**
|
|
@@ -158,7 +158,7 @@ pbpaste | chi send 3fa2 # the clipboard is the messa
|
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
- With both stdin and `-m`, stdin is context: each line becomes a `> ` quote (as web annotations quote a selection), then a blank line, then the message. With only one, it goes in as is. A terminal on stdin is ignored. Both empty is a usage error; over 16 KiB is refused, never cut.
|
|
161
|
-
- It goes through the same path as the web composer (`SessionManager.deliver_turn`): the worker's Bridge when it is up, so every attached UI shows it as your message (client id `cli:send`, shown like any user message); the input file when the worker is on its way out. During a running turn it is merged into that turn at the next step, like a message typed then, and cuts nothing. `--cut` asks for a cut, as `/cut TEXT` in a terminal does: a generation that has streamed only thinking for `steer.cut_after` seconds is cut (now, or once its thinking passes that), and the step starts again with the message. To an idle session `--cut` sends normally. `--cut --wait` into a running turn is refused like any `--wait` there (`busy: a turn is running`).
|
|
161
|
+
- It goes through the same path as the web composer (`SessionManager.deliver_turn`): the worker's Bridge when it is up, so every attached UI shows it as your message (client id `cli:send`, shown like any user message); the input file when the worker is on its way out. During a running turn it is merged into that turn at the next step, like a message typed then, and cuts nothing. `--cut` asks for a cut, as `/cut TEXT` in a terminal does: a generation that has streamed only thinking for `steer.cut_after` seconds (per host/model: `steer_cut_after`) is cut (now, or once its thinking passes that), and the step starts again with the message. To an idle session `--cut` sends normally. `--cut --wait` into a running turn is refused like any `--wait` there (`busy: a turn is running`).
|
|
162
162
|
- A session with no worker gets one started (like `--attach` or the web composer). A session open in a plain REPL (`--no-shared`) refuses it. Only sessions on this machine: Bridges listen on 127.0.0.1.
|
|
163
163
|
- Fire and forget: it returns once the message is queued and never prints the answer; that shows in whatever is attached. A guardrail "ask" waits for a UI to answer it, so with nothing attached the turn stalls there until one attaches (`chi --attach ID`).
|
|
164
164
|
- `--image PATH` (repeatable, up to 20) sends images with the message, as the web composer's chips do. Each file is read once before anything is sent (bmp, tiff and heic converted to png, large ones downscaled, as for `@path`); a missing file or one that isn't an image stops the send (exit 1; with `--wait --format json`, an `error` line) and nothing is sent; more than 20 is a usage error (exit 2). Each session gets its own copy in `<session>/images/`. Text is still required (`-m` or stdin; context alone counts), also with `--wait`. The session's model must see images: one known not to (the host's model list says text-only, `vision: false` in `models:` or `hosts:`, a native llama.cpp without a vision model) is refused before anything is sent, with the reason (`<id> refused: gemma can't take images (…); send text only or switch the model (/model)`); the other sessions of the same send still get it, and the exit is 1. With `--new` nothing is started. When chi can't tell (a native host whose `/props` doesn't answer, a list that doesn't say), the message is sent as before and a text-only model fails the turn in the session. A running turn doesn't take images mid-turn: the message runs as the next turn, and the line says `(runs after the current turn)`. With `--new` the session starts idle with the message as its preview, then the message goes in as its first turn once its worker is up (`<id> started with 1 image`); if the worker doesn't come up in 5 s the session is kept, with its id on the `failed:` line.
|
|
@@ -240,6 +240,7 @@ module Samagotchi
|
|
|
240
240
|
parts: [], pending_question: nil }
|
|
241
241
|
@tool_started_at = {}
|
|
242
242
|
@turn[:images] = event[:images] if event[:images]
|
|
243
|
+
@turn[:cut_after] = event[:cut_after] if event[:cut_after]
|
|
243
244
|
when :input_merged
|
|
244
245
|
@merged_origins = Array(event[:origins])
|
|
245
246
|
dequeue(@merged_origins)
|
data/lib/samagotchi/bridge.rb
CHANGED
|
@@ -29,7 +29,6 @@ require_relative "version"
|
|
|
29
29
|
require_relative "worker_sidecar"
|
|
30
30
|
require_relative "relay_verifier"
|
|
31
31
|
require_relative "relay_watcher"
|
|
32
|
-
require_relative "tools/task_create"
|
|
33
32
|
require_relative "tools/task_runtime"
|
|
34
33
|
|
|
35
34
|
module Samagotchi
|
|
@@ -581,9 +580,13 @@ module Samagotchi
|
|
|
581
580
|
[{}, 200, { status: record["status"], stop_reason: record["stop_reason"], task_id: task_id }]
|
|
582
581
|
end
|
|
583
582
|
|
|
583
|
+
# The tasks this session started: its history's (created_ids_in) and
|
|
584
|
+
# this turn's rows, a task_create's or an execute handed over at its
|
|
585
|
+
# limit (only after its HANDED_OVER line: its output can print a
|
|
586
|
+
# "task_id:" line of its own).
|
|
584
587
|
def own_task_ids
|
|
585
588
|
running = Array(@accumulator.current_turn&.dig(:parts)).filter_map do |part|
|
|
586
|
-
part[:
|
|
589
|
+
Tools::TaskRuntime.created_id(part[:tool], part[:output]) if part[:kind] == "tool"
|
|
587
590
|
end
|
|
588
591
|
Tools::TaskRuntime.created_ids_in(@engine.messages_checkpoint) | running
|
|
589
592
|
end
|
|
@@ -873,6 +876,7 @@ module Samagotchi
|
|
|
873
876
|
cut = @engine.cut_for_steer(Steer.source_for_client(client_id), epoch: epoch) if images.empty? && Delivery.cut?(delivery)
|
|
874
877
|
ack = { status: "accepted", enqueued_id: enqueued_id, session_id: @session_id, delivery: delivery }
|
|
875
878
|
ack[:cut] = cut.to_s if cut
|
|
879
|
+
ack[:cut_after] = @engine.steer_cut_after if cut == :waits
|
|
876
880
|
[{}, 202, ack]
|
|
877
881
|
end
|
|
878
882
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: slow-commands
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.2.0
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Remembers commands that ran past the execute limit in a project; the next identical execute is denied once a turn with advice to run it with task_create and task_wait; /slow-commands lists and forgets them"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
|
-
sha256: sha256:
|
|
10
|
-
#
|
|
11
|
-
|
|
9
|
+
sha256: sha256:bf2cfccbcadedcd85ea0b53f2d263c8729e14ed8cf310bca4a56affe50181292
|
|
10
|
+
# Execute::HANDED_OVER, read when the plugin loads (0.59.0); after_tool_call's
|
|
11
|
+
# call:, elapsed_ms: and result: (0.57.0)
|
|
12
|
+
requires_chi: ">= 0.59.0"
|