samagotchi 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +198 -1
  3. data/README.md +56 -4
  4. data/bin/chi +118 -50
  5. data/docs/cli.md +184 -9
  6. data/docs/configuration.md +333 -47
  7. data/docs/desktop.md +45 -4
  8. data/docs/guardrails.md +11 -0
  9. data/docs/hooks.md +208 -5
  10. data/docs/plugins.md +68 -2
  11. data/docs/releasing.md +23 -13
  12. data/docs/sessions.md +45 -17
  13. data/lib/samagotchi/answer_display.rb +95 -0
  14. data/lib/samagotchi/archive_store.rb +90 -0
  15. data/lib/samagotchi/bootstrap/config_writer.rb +342 -0
  16. data/lib/samagotchi/bootstrap/probe.rb +262 -0
  17. data/lib/samagotchi/bootstrap_command.rb +347 -0
  18. data/lib/samagotchi/bridge/pending_card.rb +89 -0
  19. data/lib/samagotchi/bridge/turn_accumulator.rb +15 -3
  20. data/lib/samagotchi/bridge.rb +13 -1
  21. data/lib/samagotchi/bridge_client.rb +6 -2
  22. data/lib/samagotchi/bundles/check-in/manifest.yml +10 -0
  23. data/lib/samagotchi/bundles/check-in/plugin.rb +244 -0
  24. data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +531 -0
  25. data/lib/samagotchi/bundles/source-links/manifest.yml +14 -0
  26. data/lib/samagotchi/bundles/source-links/source_links.md +5 -0
  27. data/lib/samagotchi/bundles/system/config_modification_protocol.md +10 -6
  28. data/lib/samagotchi/bundles/system/delegated.md +6 -7
  29. data/lib/samagotchi/bundles/system/manifest.yml +4 -4
  30. data/lib/samagotchi/bundles/system/self_map.md +8 -2
  31. data/lib/samagotchi/client.rb +81 -19
  32. data/lib/samagotchi/commands/registry.rb +8 -0
  33. data/lib/samagotchi/config.rb +252 -48
  34. data/lib/samagotchi/desktop/macos/App.swift +12 -8
  35. data/lib/samagotchi/desktop/macos/ChiRunner.swift +17 -9
  36. data/lib/samagotchi/desktop/macos/Images.swift +113 -0
  37. data/lib/samagotchi/desktop/macos/Info.plist.erb +6 -0
  38. data/lib/samagotchi/desktop/macos/Panel.swift +180 -25
  39. data/lib/samagotchi/desktop/macos.rb +59 -8
  40. data/lib/samagotchi/desktop_command.rb +6 -3
  41. data/lib/samagotchi/edit_preview.rb +82 -0
  42. data/lib/samagotchi/empty_answer_retry.rb +43 -0
  43. data/lib/samagotchi/engine.rb +434 -140
  44. data/lib/samagotchi/gem_update.rb +89 -0
  45. data/lib/samagotchi/guardrails/approval.rb +35 -4
  46. data/lib/samagotchi/guardrails/load_failures.rb +9 -3
  47. data/lib/samagotchi/guardrails/scratch_writes.rb +40 -0
  48. data/lib/samagotchi/guardrails.rb +1 -0
  49. data/lib/samagotchi/hooks/registry.rb +24 -5
  50. data/lib/samagotchi/host_registry.rb +9 -12
  51. data/lib/samagotchi/idle_client.rb +24 -15
  52. data/lib/samagotchi/idle_recap.rb +5 -1
  53. data/lib/samagotchi/idle_reminders.rb +2 -2
  54. data/lib/samagotchi/image_store.rb +10 -6
  55. data/lib/samagotchi/kernel_loop.rb +73 -94
  56. data/lib/samagotchi/live_versions.rb +59 -0
  57. data/lib/samagotchi/llm/api_key.rb +41 -0
  58. data/lib/samagotchi/llm/chat_loop.rb +132 -29
  59. data/lib/samagotchi/llm/errors.rb +41 -9
  60. data/lib/samagotchi/llm/http.rb +57 -17
  61. data/lib/samagotchi/llm/openai_chat.rb +17 -30
  62. data/lib/samagotchi/log_subscriber.rb +18 -3
  63. data/lib/samagotchi/memory_bundle/installer.rb +65 -63
  64. data/lib/samagotchi/memory_bundle/provenance.rb +51 -12
  65. data/lib/samagotchi/memory_bundle/shipped_update.rb +157 -0
  66. data/lib/samagotchi/memory_bundle/status.rb +4 -1
  67. data/lib/samagotchi/memory_bundle/system_bundle.rb +81 -53
  68. data/lib/samagotchi/model_profile.rb +24 -1
  69. data/lib/samagotchi/plugin/context.rb +22 -1
  70. data/lib/samagotchi/plugin/sessions.rb +3 -1
  71. data/lib/samagotchi/prompt.rb +4 -2
  72. data/lib/samagotchi/reminder_store.rb +1 -9
  73. data/lib/samagotchi/reply_wait.rb +126 -0
  74. data/lib/samagotchi/sampling_settings.rb +58 -0
  75. data/lib/samagotchi/self_report.rb +18 -3
  76. data/lib/samagotchi/send_command.rb +252 -11
  77. data/lib/samagotchi/session.rb +52 -11
  78. data/lib/samagotchi/session_archive_command.rb +107 -0
  79. data/lib/samagotchi/session_commands.rb +46 -7
  80. data/lib/samagotchi/session_manager.rb +115 -25
  81. data/lib/samagotchi/session_metrics.rb +222 -106
  82. data/lib/samagotchi/steer.rb +72 -0
  83. data/lib/samagotchi/terminal_ui/attached_loop.rb +57 -28
  84. data/lib/samagotchi/terminal_ui/event_renderer.rb +21 -11
  85. data/lib/samagotchi/terminal_ui/formatting.rb +40 -8
  86. data/lib/samagotchi/terminal_ui/input_support.rb +7 -19
  87. data/lib/samagotchi/terminal_ui/question_prompt.rb +35 -0
  88. data/lib/samagotchi/terminal_ui.rb +134 -247
  89. data/lib/samagotchi/text_diff.rb +181 -0
  90. data/lib/samagotchi/thinking.rb +115 -0
  91. data/lib/samagotchi/tool_activity.rb +3 -1
  92. data/lib/samagotchi/tool_runner.rb +34 -1
  93. data/lib/samagotchi/tools/ask_user_question.rb +41 -33
  94. data/lib/samagotchi/tools/builtins.rb +15 -4
  95. data/lib/samagotchi/tools/delegate_wait.rb +26 -69
  96. data/lib/samagotchi/tools/edit.rb +23 -9
  97. data/lib/samagotchi/tools/execute.rb +52 -14
  98. data/lib/samagotchi/tools/task_runtime.rb +19 -0
  99. data/lib/samagotchi/tools/task_wait.rb +27 -3
  100. data/lib/samagotchi/tools/write.rb +4 -0
  101. data/lib/samagotchi/turn_flow.rb +12 -2
  102. data/lib/samagotchi/turn_note.rb +60 -6
  103. data/lib/samagotchi/update_command.rb +308 -0
  104. data/lib/samagotchi/update_hint.rb +59 -0
  105. data/lib/samagotchi/version.rb +1 -1
  106. data/lib/samagotchi/vision_support.rb +7 -9
  107. data/lib/samagotchi/web/app.rb +91 -7
  108. data/lib/samagotchi/web/message_parts.rb +8 -3
  109. data/lib/samagotchi/web/public/activity.js +13 -1
  110. data/lib/samagotchi/web/public/annotate_presets.js +26 -0
  111. data/lib/samagotchi/web/public/annotations.js +13 -0
  112. data/lib/samagotchi/web/public/app.js +472 -111
  113. data/lib/samagotchi/web/public/card.js +5 -3
  114. data/lib/samagotchi/web/public/chat_view.js +13 -1
  115. data/lib/samagotchi/web/public/copy.js +20 -4
  116. data/lib/samagotchi/web/public/ctx.js +15 -0
  117. data/lib/samagotchi/web/public/data.js +23 -6
  118. data/lib/samagotchi/web/public/diff_view.js +58 -0
  119. data/lib/samagotchi/web/public/format.js +9 -0
  120. data/lib/samagotchi/web/public/index.html +60 -3
  121. data/lib/samagotchi/web/public/notify.js +175 -0
  122. data/lib/samagotchi/web/public/question_card.js +5 -2
  123. data/lib/samagotchi/web/public/sessions_list.js +7 -0
  124. data/lib/samagotchi/web/public/timing.js +39 -14
  125. data/lib/samagotchi/web/public/turn_events.js +75 -5
  126. data/lib/samagotchi/web/public/turn_view.js +49 -8
  127. data/lib/samagotchi/web/server.rb +8 -4
  128. data/lib/samagotchi/web/session_hub.rb +2 -1
  129. data/lib/samagotchi/web/session_summary.rb +24 -1
  130. data/lib/samagotchi/worker.rb +16 -4
  131. metadata +31 -1
data/docs/cli.md CHANGED
@@ -2,22 +2,120 @@
2
2
 
3
3
  ## Commands
4
4
 
5
+ - `chi bootstrap [HOST[:PORT]|URL]` — first setup: find the model server (llama.cpp or OpenAI-compatible), pick the model, send a test request and write config.yml, or add a `hosts:` entry to an existing one (see [First setup](#first-setup))
5
6
  - `chi` — start a session in a background worker and attach the terminal to it, so the Web UI (or another terminal) can share it (see [Sharing a session](#sharing-a-session))
6
7
  - `chi -p "your prompt"` — run a prompt, then stay attached
7
8
  - `chi -p "your prompt" --non-interactive` — run a prompt, print the answer, exit
8
9
  - `chi --resume <session-id>` — resume a prior session (in its worker)
9
10
  - `chi --no-shared [--resume <session-id>]` — the plain in-process REPL instead, for this run
11
+ - `chi scratch [options]` — a one-time session in the plain in-process REPL, in this folder, that leaves nothing behind (see [Scratch sessions](#scratch-sessions))
10
12
  - `chi --attach <session-id>` — attach the terminal to a session's worker (e.g. one started from the Web UI), waking one if it has exited
11
- - A session id can be shortened to any unique prefix (like git): `chi --attach 2ea8`. `--resume`, `--attach`, `sessions stop` and `sessions delete` take one; an ambiguous prefix lists the sessions it matches.
13
+ - A session id can be shortened to any unique prefix (like git): `chi --attach 2ea8`. `--resume`, `--attach`, `sessions stop`, `sessions archive` and `sessions delete` take one; an ambiguous prefix lists the sessions it matches.
12
14
  - `chi web [--port 4567] [--open] [--scope=all]` — start the Web UI (single localhost port session control plane) on this git project's sessions (`--scope=all`, or a folder in no repo: every session); if a chi web already runs on the port, print (with `--open`, open) its page for this folder and exit. Something else on the port (an older chi web too) exits 1 with "port N is in use"
13
15
  - `chi web --web-markdown` — opt in to sanitized Markdown rendering for completed assistant messages
14
16
  - `chi web --no-web-turn-view` — show turns as the classic row of bubbles instead of the default turn view (each turn as one block of steps, the running one at the bottom); `?view=turn|chat` on the page URL overrides it (see [Web turn view](#web-turn-view))
15
- - `chi sessions list|stop|delete|prune|clean` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>` (see [Sessions](sessions.md))
17
+ - `chi sessions list|stop|archive|unarchive|delete|prune|clean` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>`, `list --archived` the archived ones too (see [Sessions](sessions.md))
16
18
  - `chi note [--source NAME] [-m TEXT] (ID|PREFIX)... | --all` — add a context note (TEXT or stdin) to sessions: background the model sees on its next turn; it starts no turn (see [Sessions: Context notes](sessions.md#context-notes))
17
- - `chi send [-m TEXT] (ID|PREFIX)...` — send a message to sessions as if typed there: a turn starts (or a running one picks it up); piped stdin goes above `-m` as quoted context (see [Sessions: Sending a message](sessions.md#sending-a-message))
19
+ - `chi send [-m TEXT] [--image PATH]... (ID|PREFIX)...` — send a message to sessions as if typed there: a turn starts (or a running one picks it up); piped stdin goes above `-m` as quoted context, and `--image` attaches images (see [Sessions: Sending a message](sessions.md#sending-a-message)); `--new` starts a session with it instead, and `--wait` prints the answer (`--wait ID` with no message waits for the next reply without sending; see [Starting a session](sessions.md#starting-a-session))
18
20
  - `chi desktop install|upgrade|uninstall|status` — the macOS "Send to chi" helper: a Service and a ⌃⌥⌘N hotkey that send text to live sessions as context notes (see [Desktop helper](desktop.md))
19
21
  - `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles
20
- - `chi bundle install|upgrade|uninstall|status|diff|list|build` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs (see [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) and [the loop-guard bundle](plugins.md#the-loop-guard-bundle))
22
+ - `chi update [--dry-run] [--no-gem] [--no-bundles] [--no-desktop]` — update an installed chi: the gem, the system bundle, the shipped bundles you installed and the desktop helper, in one table (see [Updating](#updating))
23
+ - `chi bundle install|upgrade|uninstall|status|diff|list|build` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs (see [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle) and [the check-in bundle](plugins.md#the-check-in-bundle))
24
+
25
+ ### First setup
26
+
27
+ `chi bootstrap TARGET` names the model server and writes the config for it:
28
+
29
+ ```sh
30
+ chi bootstrap 192.168.1.29:8081 # host:port (port 8080 when none)
31
+ chi bootstrap https://openrouter.ai/api/v1 --key-env OPENROUTER_API_KEY
32
+ chi bootstrap # try localhost 8080, 11434, 1234, 8000
33
+ ```
34
+
35
+ - **What it is.** llama.cpp's `/props` answering means the native API (no
36
+ `api:`); otherwise `GET /v1/models` answering means an OpenAI-compatible
37
+ server (`api: openai`). A URL with a path is the API base as given
38
+ (`…/api/v1`); a domain without a scheme is tried over https, then http.
39
+ Each request is tried once, with 5 s timeouts, so a refused port answers
40
+ at once.
41
+ - **The key.** A server that answers 401/403 wants an API key: `--key-env VAR`
42
+ names the environment variable holding it (on a terminal chi asks for the
43
+ name). Only the variable's name is written, never the key.
44
+ - **The model.** One model is taken; with several, `--model ID` picks one
45
+ (a terminal gets a numbered list, a script the ids and exit 2). A llama.cpp
46
+ server also shows its context size and the prompt profile its chat template
47
+ matches.
48
+ - **The test.** One short chat request ("test: answered in 1.1 s"); `--no-test`
49
+ skips it. A failed test still writes the config, says so and exits 1.
50
+ - **The file.** With no config.yml it writes a small commented one:
51
+ `default.model` as `<host>:<model>` and one `hosts:` entry named `local`
52
+ (localhost), `lan` (an IP) or after the domain (`openrouter`); `--name`
53
+ sets it. An existing file is left as it is apart from the new entry, added
54
+ at the end of its `hosts:` block (it gets a `hosts:` block, with a
55
+ `default` entry for its `server:` first, when it has none), after a backup
56
+ to `config.yml.bak-<time>`. `default.model` is set only when the file has
57
+ none. A server already in the file writes nothing; a file in YAML flow style
58
+ or with anchors gets the lines printed to paste instead. `--dry-run` shows
59
+ what it would write.
60
+
61
+ ### Updating
62
+
63
+ `chi update` brings an installed chi up to date and prints one table:
64
+
65
+ ```
66
+ component from to status
67
+ chi (gem) 0.2.0 0.3.0 updated
68
+ system bundle 0.2.0 0.3.0 updated (kept your edits in identity.md: chi bundle diff samagotchi-system identity.md)
69
+ btw 0.1.1 up to date
70
+ known-names 0.1.0 0.1.1 updated
71
+ infra_tools 1.0.0 skipped (not from chi)
72
+ Chi Helper 0.2.0 up to date (launch file refreshed)
73
+ workers 2 live on 0.2.0: they move to 0.3.0 at idle exit (30 min) or chi sessions stop 2ea8c1f0 91b0d2aa
74
+ Also shipped, not installed: check-in, source-links (chi bundle install NAME)
75
+ done
76
+ ```
77
+
78
+ - **The gem.** It asks rubygems.org for the newest samagotchi (5 s timeout)
79
+ and, when that's newer, runs `gem install samagotchi` with the gem command
80
+ of the Ruby chi runs on (the real one, not a mise/rbenv/asdf shim). Then it
81
+ hands over to the new chi, which does the rest and prints the table. Old
82
+ versions stay installed: running workers and an old `chi web` still use
83
+ them (so don't `gem cleanup` while they run). Offline, the row says
84
+ "couldn't check" and the rest still runs; a failed install fails the row
85
+ and the rest runs on the current version. Under Bundler (`bundle exec`)
86
+ the row says `bundle update samagotchi` instead.
87
+ - **The system bundle** normally updated itself when the new chi started;
88
+ the row says what it did.
89
+ - **Shipped bundles**: each one you installed from chi (`chi bundle install
90
+ NAME`) is upgraded when chi ships a newer version. Memory files get the
91
+ 3-way merge of `chi bundle upgrade`: an unedited file is updated, an edited
92
+ one that the new version also changes is kept, and the row says so (`chi
93
+ bundle diff NAME FILE` shows it; `chi bundle upgrade NAME --force` takes the
94
+ bundle's). Hooks, rules and the plugin are replaced; an edited one didn't
95
+ load anyway (its sha no longer matched) and the row says it was replaced.
96
+ A bundle of the same name from elsewhere (a zip, git) is skipped ("not from
97
+ chi"), a newer installed one is left, one whose new version needs a newer
98
+ chi is skipped, and bundles you didn't install stay uninstalled.
99
+ - **The desktop helper** (macOS) is rebuilt and restarted only when its Swift
100
+ sources changed (or the Ruby it runs moved); otherwise only its launch file
101
+ is refreshed. See [Desktop helper](desktop.md).
102
+ - **Running processes** are reported, never stopped: live workers on another
103
+ version, and a `chi web` on `web.port` running an older chi (sessions it
104
+ starts run that version too: restart it).
105
+
106
+ `--dry-run` shows the table with "would update" and changes nothing. It is
107
+ this version's view: bundles that only a newer gem ships newer show up once
108
+ that gem is installed (the real run installs it first and hands over).
109
+ `--no-gem`, `--no-bundles` and `--no-desktop` leave a part alone for one run;
110
+ `update.gem`, `update.bundles` and `update.desktop: false` in config.yml turn
111
+ one off for good. It exits 0 when nothing failed (kept edits and skips are
112
+ fine), 1 when a part failed, 2 on a usage error. A second run changes nothing
113
+ and ends with "everything is up to date".
114
+
115
+ From a checkout it refuses (`git pull`, or `chi bundle upgrade NAME` for one
116
+ bundle). After a gem update, the first interactive start of the new version
117
+ (`chi`, `chi web`; not `-p` or `--non-interactive`) says in one line when
118
+ bundles or the helper can be updated.
21
119
 
22
120
  ## Flags
23
121
 
@@ -33,6 +131,7 @@ controls exit behavior (`--non-interactive`); `--resume` composes with both.
33
131
  | `--no-shared` | Run the plain in-process REPL for this run. |
34
132
  | `--attach SESSION_ID` | Attach to a session's worker, waking one if it has exited. |
35
133
  | `--model NAME` | Use this model for the run (overrides the configured default and a resumed session's model). |
134
+ | `--thinking LEVEL` | How much the model thinks this run: `off`, `low`, `medium`, `high` or `default` (env `SAMAGOTCHI_THINKING_LEVEL`), over the config's levels. A session already running keeps its own. See "Thinking" in configuration.md. |
36
135
  | `--profile NAME` | Prompt profile (`qwen36` or `gemma4`) for every model in this run, over config and the server's template (same as `--model-profile`, env `SAMAGOTCHI_MODEL_PROFILE`). See "Prompt profile" in configuration.md. |
37
136
  | `--memory NAME` | Preload a memory entry into the system prompt (repeatable; a comma list too). Merged under the config.yml `memories:` baseline. Works attached: the list is stored on the session, so its worker builds the same prompt on every respawn. |
38
137
  | `--mute NAME` | Hide a memory from this session (repeatable; a comma list too): its index line is not in the prompt, `memory_read` refuses it, the identity auto-load skips it, and it is dropped from the preloads (config baseline or `--memory`). A name matches in both scopes (`gh-helper`, `project/gh-helper` and `gh-helper.md` all hide `gh-helper`). Nothing on disk changes. See [Muting a memory](#muting-a-memory). |
@@ -64,6 +163,7 @@ it sees one.
64
163
  | `chi --resume ID -p "next step" --non-interactive` | Resume `ID`, run the prompt, save, exit. |
65
164
  | `chi --resume ID -p "next step"` | Resume `ID`, send the prompt, **stay attached** to that session. |
66
165
  | `chi --no-shared [...]` | The same, in the plain in-process REPL. |
166
+ | `chi scratch [-p ...] [--non-interactive]` | A new session in the plain REPL, deleted when it ends. |
67
167
 
68
168
  Notes:
69
169
 
@@ -75,6 +175,28 @@ Notes:
75
175
  - Non-interactive runs (`-p` with `--non-interactive`, or bare `--non-interactive`)
76
176
  print only the final result output — no spinner, status line, or REPL.
77
177
 
178
+ ### Scratch sessions
179
+
180
+ `chi scratch` is `chi --no-shared` for a session you won't keep: a quick
181
+ question, a try-out. It takes the run options (`-p`, `--non-interactive`,
182
+ `--model`, `--profile`, `--memory`, `--mute`, `-v`, …); `--resume`, `--attach`
183
+ and `--shared` are refused. Its first line says it is a scratch session.
184
+
185
+ - The session is deleted however it ends: `/exit`, Ctrl-D, Ctrl-C at the
186
+ prompt, an error, SIGTERM or SIGHUP. There is no recap, and the lines typed
187
+ are not added to the prompt history.
188
+ - It never shows in `chi web`. A process killed with `kill -9` leaves its
189
+ session behind, marked `"scratch": true` in its session.json: `chi sessions
190
+ list` shows it as `[scratch]`, and the next sweep or `chi sessions clean`
191
+ deletes it. `chi --resume` and `--attach` refuse it (exit 1), so it never
192
+ turns into a kept session.
193
+ - Memories are read and preloaded as usual, but nothing is saved: `memory_write`
194
+ answers "scratch session: nothing is saved", and `write`/`edit` into the
195
+ memories folder are denied (a guardrail, rule `scratch-session`). `execute`
196
+ can still write files anywhere, memories included.
197
+ - No child sessions: the `delegate` tools are not offered, and a plugin's
198
+ `ctx.sessions.fork` (btw's side session) refuses, since they would outlive it.
199
+
78
200
  ### Sharing a session
79
201
 
80
202
  Plain `chi` runs the session in a background worker and attaches the terminal
@@ -122,7 +244,8 @@ A worker nobody uses exits after `session.idle_exit_minutes` (30 by default, `0`
122
244
  for never): no turn running or queued, no UI attached (an open web tab or an
123
245
  attached terminal counts, even an idle one) and no reminder registered. The next
124
246
  prompt or `--attach` wakes a new worker with the conversation intact; `/stats`
125
- counters start over (the recap is saved with the session).
247
+ keeps counting from the turns before (they are saved in the session's
248
+ `analytics.json`, one record per turn), and the recap is saved with the session.
126
249
 
127
250
  A session you leave with nothing in it (no prompt sent, no `/model` switch, no
128
251
  note or image) is deleted as its worker exits, and `/exit` says so; set
@@ -134,6 +257,21 @@ a `chi --resume ID` after it starts a fresh one. A worker still running an
134
257
  older chi (from before an upgrade) takes turns but not commands; the attached
135
258
  terminal and the Web UI say so, with that restart line.
136
259
 
260
+ `chi sessions archive ID...` hides sessions from every list (the terminal's,
261
+ the web's, `list_sessions`) and keeps them for good: the retention sweep never
262
+ deletes an archived session, nor counts it. Its delegates go with it. A live
263
+ worker is stopped first; a session running a turn (or with a delegate running
264
+ one), open in a plain REPL, or a `chi scratch` one is refused. `chi sessions
265
+ list --archived` shows them too, marked `[archived]` (`archived: true` in
266
+ `--format json`); `chi sessions unarchive ID...` brings them back, and so does
267
+ a message you send to one (the web, an attached terminal, `chi send`), but not
268
+ a delegate's follow-up or a reminder. The web archives from the info bar
269
+ (`archive`, before `stop`); "include archived" by the all-sessions search
270
+ finds archived sessions. `/archive` in a terminal leaves the session and
271
+ archives it (an empty session is discarded instead; `chi scratch` refuses
272
+ it). See
273
+ [Sessions](sessions.md#archiving-a-session).
274
+
137
275
  `chi sessions delete [--force] ID...` deletes sessions for good: the
138
276
  session file and its whole directory (notes, images, queued input). Each id
139
277
  (or unique prefix) gets one line: `deleted`, or `refused` with the reason. A
@@ -202,7 +340,7 @@ REPL alike:
202
340
  typed comes back once it closes. The choices then go, and one line stays:
203
341
  `? Pick a fruit → Banana`.
204
342
  - Ctrl-C cancels the turn and keeps what you typed.
205
- - In the plain REPL, Ctrl-D on an empty prompt (or `exit`, `/exit`) mid-turn
343
+ - In the plain REPL, Ctrl-D on an empty prompt (or `exit`, `/exit`, `/quit`) mid-turn
206
344
  exits once the turn ends: `(exits after this turn; Ctrl-C cancels it)`
207
345
  (`/exit --delete` deletes the session then too). In an
208
346
  attached terminal it detaches at once and the turn goes on in the worker
@@ -213,7 +351,7 @@ turns.
213
351
 
214
352
  ### Images
215
353
 
216
- A model that can see images gets them three ways:
354
+ A model that can see images gets them these ways:
217
355
 
218
356
  - **`@path` in a prompt** (REPL, attached terminal, `-p`): `what's wrong in
219
357
  @shot.png?`, `@~/Desktop/a.jpg`, `@"my shot.png"`. Each `@` token that names an
@@ -228,6 +366,12 @@ A model that can see images gets them three ways:
228
366
  - **The Web UI**: paste or drop images into the composer. Each shows as a chip
229
367
  (× removes it) and is sent with the message; an image alone is sent as
230
368
  `[image: name]`. Messages show thumbnails; a click opens one full size.
369
+ - **`chi send --image PATH`** (repeatable, up to 20) with a message, from a
370
+ script or another terminal: `chi send --image shot.png -m "why is this red?"
371
+ 3fa2`. The attached terminal and the web show it like an image typed there.
372
+ - **The desktop panel** (macOS, [Desktop](desktop.md#images)): a screenshot on
373
+ the clipboard, an image selected in Finder, or one dropped on the panel goes
374
+ as an attachment with the message.
231
375
 
232
376
  Images are downscaled to a 1568 px long side (with `sips` on macOS or
233
377
  ImageMagick; without either, a larger image is refused with a hint) and stored
@@ -298,7 +442,10 @@ from the saved messages (each step's thinking, narration, tool parameters
298
442
  and output, the output capped at 2000 characters) and the timing records
299
443
  (status and duration per row). On an `api: openai` host the model's
300
444
  reasoning is saved with each step for this (never sent back to the model);
301
- steps saved before that have none, so they show no thinking.
445
+ steps saved before that have none, so they show no thinking. An `edit` or
446
+ `write` row has a closed `diff +3 −1` under it that opens to the change it
447
+ made (up to 120 lines or 8 KB), live and after a reload (see
448
+ [Guardrails](guardrails.md#ask) for the diff an approval shows first).
302
449
 
303
450
  ```sh
304
451
  chi web --no-web-turn-view # the classic chat view; --web-turn-view is the default
@@ -316,6 +463,32 @@ and `?view=turn` the turn view, whatever the config says; the parameter is dropp
316
463
  when you switch between the project and all-sessions views. The terminal
317
464
  UIs are not affected.
318
465
 
466
+ ### Web annotate presets
467
+
468
+ Selecting text in an answer, a thinking block, a tool row or one of your
469
+ messages shows **Annotate**, which quotes the selection into the composer
470
+ for a note under it. Next to it sit quick replies, by default `Agreed` and
471
+ `Could you please elaborate?`: a click quotes the selection the same way
472
+ with that text already written as the note. It only fills the composer,
473
+ never sends, so you can collect several quotes and edit before sending.
474
+
475
+ The list is `web.annotate_presets`, `|`-separated (at most five; a preset
476
+ can't contain `|`; a YAML list works too):
477
+
478
+ ```sh
479
+ chi web --web-annotate-presets "Yes|No|Why this way?"
480
+ chi web --web-annotate-presets "" # only Annotate
481
+ ```
482
+
483
+ ```yaml
484
+ web:
485
+ annotate_presets: "Agreed|Could you please elaborate?"
486
+ ```
487
+
488
+ `SAMAGOTCHI_WEB_ANNOTATE_PRESETS` overrides the file, but an empty value
489
+ there means the default, not "none": use `""` in the file or on the command
490
+ line. A `chi web` that already runs keeps its list; restart it.
491
+
319
492
  ## Runtime Model Switch (Assist Mode)
320
493
 
321
494
  In interactive assist mode, you can switch the request model without restarting:
@@ -479,7 +652,9 @@ During assist-mode thinking (while the spinner is active), you can cancel an in-
479
652
  Behavior notes:
480
653
 
481
654
  - Cancellation returns control to the prompt immediately; what you typed there stays.
482
- - Partial model output from the canceled request is not committed as a completed model turn.
655
+ - Visible text the canceled request had streamed stays in the conversation, marked `[interrupted]`, so the next
656
+ message (or a continue) picks up from the half-finished reply; the canceled request's thinking and any unfinished
657
+ tool call are dropped.
483
658
 
484
659
  ## Iteration Limit Behavior
485
660