samagotchi 0.101.0 → 0.103.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 +67 -1
- data/bin/chi +31 -196
- data/docs/architecture.md +62 -6
- data/docs/cli.md +154 -7
- data/docs/configuration.md +32 -5
- data/docs/guardrails.md +2 -1
- data/docs/internals/bundles.md +1 -1
- data/docs/internals/engine.md +1 -1
- data/docs/internals/llm-context-forget.md +25 -1
- data/docs/internals/prompt-caching.md +8 -1
- data/docs/internals/remote-sessions.md +303 -0
- data/docs/internals/working-set.md +174 -15
- data/docs/plugins.md +32 -5
- data/docs/sessions.md +38 -14
- data/docs/sub-agent.md +8 -0
- data/docs/testing.md +7 -0
- data/lib/samagotchi/answer_command.rb +27 -25
- data/lib/samagotchi/bridge/turn_accumulator.rb +16 -0
- data/lib/samagotchi/bridge.rb +33 -7
- data/lib/samagotchi/bridge_client/event_stream.rb +81 -12
- data/lib/samagotchi/bridge_client/stream_request.rb +77 -0
- data/lib/samagotchi/bridge_client.rb +22 -38
- data/lib/samagotchi/bundle_command/check.rb +5 -1
- data/lib/samagotchi/bundle_command/try/command.rb +6 -6
- data/lib/samagotchi/bundle_command/try/sandbox.rb +3 -3
- data/lib/samagotchi/bundle_command.rb +4 -4
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/claude-peer/scripts/pair +30 -1
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/codex-peer/scripts/pair +49 -4
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +7 -4
- data/lib/samagotchi/bundles/guardrails/manifest.yml +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/chi_invocation.rb +43 -0
- data/lib/samagotchi/cli/command.rb +3 -2
- data/lib/samagotchi/cli/flags.rb +5 -0
- data/lib/samagotchi/cli/parent_wait.rb +22 -13
- data/lib/samagotchi/commands/registry.rb +12 -1
- data/lib/samagotchi/config.rb +89 -53
- data/lib/samagotchi/context_command.rb +80 -73
- data/lib/samagotchi/engine.rb +138 -15
- data/lib/samagotchi/idle_recap.rb +7 -1
- data/lib/samagotchi/idle_scheduler.rb +16 -1
- data/lib/samagotchi/kernel_loop.rb +13 -1
- data/lib/samagotchi/launch_mode.rb +22 -0
- data/lib/samagotchi/llm_context_edit.rb +32 -10
- data/lib/samagotchi/llm_context_forget.rb +24 -8
- data/lib/samagotchi/llm_context_view.rb +19 -7
- data/lib/samagotchi/memory_bundle/installer.rb +2 -0
- data/lib/samagotchi/memory_bundle/manifest.rb +13 -2
- data/lib/samagotchi/message_parts.rb +8 -3
- data/lib/samagotchi/note_command.rb +8 -7
- data/lib/samagotchi/parent_report.rb +55 -43
- data/lib/samagotchi/plugin/context.rb +2 -2
- data/lib/samagotchi/prompt.rb +1 -1
- data/lib/samagotchi/recap_command.rb +68 -0
- data/lib/samagotchi/recap_setup.rb +27 -14
- data/lib/samagotchi/remote_command.rb +170 -0
- data/lib/samagotchi/remotes.rb +130 -0
- data/lib/samagotchi/run_options.rb +233 -0
- data/lib/samagotchi/send_command/busy.rb +58 -0
- data/lib/samagotchi/send_command/parent_gate.rb +23 -10
- data/lib/samagotchi/send_command/wait_target.rb +16 -3
- data/lib/samagotchi/send_command.rb +186 -118
- data/lib/samagotchi/session.rb +48 -16
- data/lib/samagotchi/session_access/contract.rb +129 -0
- data/lib/samagotchi/session_access/dispatch.rb +133 -0
- data/lib/samagotchi/session_access/http_transport.rb +109 -0
- data/lib/samagotchi/session_access/local.rb +349 -0
- data/lib/samagotchi/session_access/remote.rb +638 -0
- data/lib/samagotchi/session_access/web_bridge_client.rb +181 -0
- data/lib/samagotchi/session_access.rb +141 -0
- data/lib/samagotchi/session_archive_command.rb +9 -5
- data/lib/samagotchi/session_commands.rb +53 -6
- data/lib/samagotchi/session_config.rb +411 -0
- data/lib/samagotchi/session_delete_command.rb +8 -4
- data/lib/samagotchi/session_manager.rb +7 -2
- data/lib/samagotchi/session_setup.rb +15 -10
- data/lib/samagotchi/sessions_command.rb +42 -16
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +54 -18
- data/lib/samagotchi/terminal_ui/attach_link.rb +104 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +82 -76
- data/lib/samagotchi/terminal_ui/formatting.rb +10 -3
- data/lib/samagotchi/terminal_ui/local_link.rb +130 -0
- data/lib/samagotchi/terminal_ui/version_lines.rb +5 -3
- data/lib/samagotchi/terminal_ui.rb +8 -6
- data/lib/samagotchi/testing/bundle.rb +3 -2
- data/lib/samagotchi/title_command.rb +9 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +387 -79
- data/lib/samagotchi/web/public/app.js +2 -0
- data/lib/samagotchi/web/public/commands.js +8 -2
- data/lib/samagotchi/web/public/data.js +6 -4
- data/lib/samagotchi/web/public/index.html +37 -3
- data/lib/samagotchi/web/public/stage_frame.js +7 -2
- data/lib/samagotchi/web/public/stage_view.js +3 -2
- data/lib/samagotchi/web/public/stream_handlers.js +7 -1
- data/lib/samagotchi/web/public/turn_html.js +5 -0
- data/lib/samagotchi/web/public/working_set.js +247 -24
- data/lib/samagotchi/web/public/working_set_rail.js +39 -12
- data/lib/samagotchi/web/public/working_set_view.js +198 -25
- data/lib/samagotchi/web/session_access_routes.rb +343 -0
- data/lib/samagotchi/worker.rb +9 -6
- data/lib/samagotchi/worker_sidecar.rb +12 -5
- data/lib/samagotchi/working_set.rb +132 -20
- data/lib/samagotchi/working_set_command.rb +222 -0
- data/lib/samagotchi/working_set_pin.rb +34 -0
- data/lib/samagotchi/working_set_steer.rb +414 -0
- metadata +23 -2
- data/lib/samagotchi/bundle_session_settings.rb +0 -251
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c4a19357a9f5c94b3c76052abc018a034968ad8baf3520d50ace0ab581943b6
|
|
4
|
+
data.tar.gz: ec637848fd8dc33b292d836618753699bc8159962c6051d7b9b669beb31eb50f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5ca1a63b9c68e9b18b21fb2f2bf6503eb3cec3368e10410c4efce78dc5a853e842fc8ddd74bb5387d21f6b0d38c69f4abdb58a5320bf5c9e8272232ebf56234c
|
|
7
|
+
data.tar.gz: 7c86172b9193fc68c5c639989573c17f99af9621d118624684ace1483628a01cde8e609c2438368c297e534043fb86a165e94511e34dfd880171d77f2a75eb57
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,70 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.103.0] - 2026-10-23
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Remote sessions: `chi --remote NAME send|answer|note|context|sessions …` (or `CHI_REMOTE=NAME`) runs the session
|
|
16
|
+
commands against another machine's sessions, through its `chi web --web-host lan`. `chi remote add NAME URL
|
|
17
|
+
--token-file FILE` checks the address and token and saves the remote (`chi remote ls`, `chi remote rm`).
|
|
18
|
+
`send --new` there needs `--dir`, a folder on that machine; what runs on this machine only (attach, `sessions
|
|
19
|
+
stats`, `context mute`, `broadcast`, …) is refused with exit 2. The next-step commands it prints (`chi answer …`,
|
|
20
|
+
`chi send --wait …`) carry `--remote NAME`; where chi points at `chi --attach` here it names that machine's chi web.
|
|
21
|
+
See [docs/cli.md](docs/cli.md#remote-sessions).
|
|
22
|
+
- `/working-set pin KEY…` keeps an item from the model's `forget_outputs`: its reads, those to come too, stay
|
|
23
|
+
whole until `/working-set unpin KEY…`. `/working-set add KEY…` brings an item in: the next turn's line asks the
|
|
24
|
+
model to read it first (one tool call; what it reads is an ordinary read); `/working-set unadd KEY…` takes an
|
|
25
|
+
add back before then. In the web's working set list (still experimental, `web.working_set: true`), rows offer
|
|
26
|
+
`pin` / `unpin`, `+` adds an item, and a stale or forgotten row offers a refresh. See [docs/cli.md](docs/cli.md).
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- The guardrails bundle (0.9.3) asks for `chi --remote NAME context add --cmd …` and `chi --remote NAME broadcast`
|
|
31
|
+
as it does without `--remote`. Update with `chi update`.
|
|
32
|
+
- claude-peer (0.4.2): `pair` through a remote (`CHI_REMOTE`, or `--remote NAME` in `CHI`) attaches only
|
|
33
|
+
`claude-outbox` and says chi there can't send to that Claude. Update with `chi update`.
|
|
34
|
+
- codex-peer (0.3.3): the same for `pair` through a remote: it attaches only `codex-outbox`, says chi there can't
|
|
35
|
+
send to that Codex, and when chi can't reach the remote names Codex's sandbox `network_access` setting as a
|
|
36
|
+
possible cause. Update with `chi update`.
|
|
37
|
+
- chi web's `POST /api/sessions/:id/stop` also answers `released`; `DELETE /api/sessions/:id` takes `force` (the
|
|
38
|
+
page sends it). Both are additive.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- The web's stage shrinks back when you close its steps list by hand, instead of keeping its grown height.
|
|
43
|
+
- `/working-set`: a `memory_read` of several names is dropped, pinned or added as a whole (the reply names the
|
|
44
|
+
others); a quoted KEY keeps its spaces, in the TUI and the web; a file in the session's folder is shown by its
|
|
45
|
+
relative path.
|
|
46
|
+
- The idle recap's summarize thread ends quietly, logged, on a failure.
|
|
47
|
+
- On Linux a worker stops at once (not ~0.5 s later), and its address is gone before its streams end, so an attached UI follows a restarted worker straight away.
|
|
48
|
+
|
|
49
|
+
## [0.102.0] - 2026-10-23
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- `/working-set` lists the session's working set as the model has it: each file, memory and URL its turns read,
|
|
54
|
+
edited or fetched, with its state (`stale`, `forgotten`, `dropped`) and its size as sent. `/working-set drop KEY…`
|
|
55
|
+
takes an item out of what the model is sent (its reads go as a stub saying you dropped it, under any LLM context
|
|
56
|
+
strategy); `/working-set undrop KEY…` sends it whole again. A change applies from the next turn. In the web's
|
|
57
|
+
working set list (still experimental, `web.working_set: true`), rows offer `drop` / `undrop`; mid-turn they wait
|
|
58
|
+
for the turn's end, with an `undo`. See [docs/cli.md](docs/cli.md).
|
|
59
|
+
- `--set` takes chi's own session settings too: `--set recap.enabled=false` or `--set recap.sentences=3` for this
|
|
60
|
+
session, over config.yml. A bad value or a key no session sets is an error (exit 2). See
|
|
61
|
+
[docs/configuration.md](docs/configuration.md#per-session-settings).
|
|
62
|
+
- `/recap on|off|default` turns the idle recap on or off for this session, at once and in any UI (mid-turn too);
|
|
63
|
+
saved with the session.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- `POST /api/sessions`: the field `bundle_settings` is now `settings` (it takes core keys too). No alias.
|
|
68
|
+
- An attached TUI started before this release treats `/recap off` as the plain `/recap`; restart it.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- The web's working set no longer keeps the last item when the set is emptied, and a reply to an older request can
|
|
73
|
+
no longer overwrite a newer one.
|
|
74
|
+
|
|
11
75
|
## [0.101.0] - 2026-10-22
|
|
12
76
|
|
|
13
77
|
### Added
|
|
@@ -3828,7 +3892,9 @@ and long-lived sessions.
|
|
|
3828
3892
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
3829
3893
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
3830
3894
|
|
|
3831
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
3895
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.103.0...HEAD
|
|
3896
|
+
[0.103.0]: https://github.com/dm1try/samagotchi/compare/v0.102.0...v0.103.0
|
|
3897
|
+
[0.102.0]: https://github.com/dm1try/samagotchi/compare/v0.101.0...v0.102.0
|
|
3832
3898
|
[0.101.0]: https://github.com/dm1try/samagotchi/compare/v0.100.1...v0.101.0
|
|
3833
3899
|
[0.100.1]: https://github.com/dm1try/samagotchi/compare/v0.100.0...v0.100.1
|
|
3834
3900
|
[0.100.0]: https://github.com/dm1try/samagotchi/compare/v0.99.0...v0.100.0
|
data/bin/chi
CHANGED
|
@@ -8,7 +8,6 @@ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
|
8
8
|
require "samagotchi/utf8_default"
|
|
9
9
|
Samagotchi::Utf8Default.apply!
|
|
10
10
|
|
|
11
|
-
require "optparse"
|
|
12
11
|
require "samagotchi/config"
|
|
13
12
|
|
|
14
13
|
begin
|
|
@@ -64,8 +63,23 @@ SUBCOMMANDS = {
|
|
|
64
63
|
# list and remove memories (ls, rm; --scope, --format)
|
|
65
64
|
"memory" => ["samagotchi/memory_command", "MemoryCommand"],
|
|
66
65
|
# install, upgrade, uninstall, status, diff, list, build (--scope, --force, …)
|
|
67
|
-
"bundle" => ["samagotchi/bundle_command", "BundleCommand"]
|
|
66
|
+
"bundle" => ["samagotchi/bundle_command", "BundleCommand"],
|
|
67
|
+
# other machines' chi web, for chi --remote NAME (add, ls, rm)
|
|
68
|
+
"remote" => ["samagotchi/remote_command", "RemoteCommand"]
|
|
68
69
|
}.freeze
|
|
70
|
+
# chi --remote NAME <command> (or CHI_REMOTE=NAME): the session commands
|
|
71
|
+
# against another machine's chi web; what runs here only is refused (exit 2).
|
|
72
|
+
# chi remote, --version and --help run here as always.
|
|
73
|
+
require "samagotchi/session_access/dispatch"
|
|
74
|
+
begin
|
|
75
|
+
remote_run = Samagotchi::SessionAccess::Dispatch.from(ARGV)
|
|
76
|
+
rescue Samagotchi::SessionAccess::Dispatch::Usage => e
|
|
77
|
+
warn "Error: #{e.message}"
|
|
78
|
+
exit 2
|
|
79
|
+
end
|
|
80
|
+
ARGV.replace(remote_run.argv)
|
|
81
|
+
exit remote_run.run(SUBCOMMANDS) if remote_run.remote? && !remote_run.here?
|
|
82
|
+
|
|
69
83
|
if (path, klass = SUBCOMMANDS[ARGV[0]])
|
|
70
84
|
require path
|
|
71
85
|
exit Samagotchi.const_get(klass).new(ARGV[1..] || []).run
|
|
@@ -76,183 +90,10 @@ end
|
|
|
76
90
|
scratch = ARGV[0] == "scratch"
|
|
77
91
|
ARGV.shift if scratch
|
|
78
92
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# Strict kebab enforcement: --recap_base_url is unknown (OptionParser would otherwise accept _ as -)
|
|
84
|
-
known = %w[--prompt --resume --attach --shared --no-shared --model --thinking --toolkit --tool --no-tool --set --parent-answers --memory --mute --append-system-prompt --llm-context --llm-context-apply --llm-context-budget --no-interrupt --non-interactive --verbose --no-default-input --port --open --new-token] + Samagotchi::Config.cli_entries.map(&:cli_flag)
|
|
85
|
-
ARGV.each do |arg|
|
|
86
|
-
next unless arg.start_with?("--") && arg.include?("_")
|
|
87
|
-
|
|
88
|
-
# Allow --no-interrupt etc. already dash; underscore is always rejected for config flags
|
|
89
|
-
# We check raw ARGV before OptionParser normalizes
|
|
90
|
-
base = arg.split("=", 2).first
|
|
91
|
-
# If dashed version is a known flag (registry or hardcoded), reject underscore variant
|
|
92
|
-
dashed = base.tr("_", "-")
|
|
93
|
-
if known.include?(dashed) || known.include?(base)
|
|
94
|
-
warn "Unknown option: #{arg} (did you mean #{dashed}?)"
|
|
95
|
-
exit 1
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
parser = OptionParser.new do |opts|
|
|
100
|
-
opts.banner = <<~BANNER
|
|
101
|
-
Usage: chi [options]
|
|
102
|
-
chi bootstrap [HOST[:PORT]|URL] first setup: find the model server, write config.yml
|
|
103
|
-
chi scratch [options] a one-time session in this terminal: nothing is kept
|
|
104
|
-
chi web [--port PORT] [--open] [--scope=all] the web UI
|
|
105
|
-
chi sessions <list|stop|restart|archive|unarchive|delete|prune|clean|stats>
|
|
106
|
-
chi note [--source NAME] [-m TEXT] ID...|--all background context for sessions
|
|
107
|
-
chi broadcast [-m TEXT] [--all] [--dry-run] | log | deliver a note for every session it may concern
|
|
108
|
-
chi context <add|push|ls|show|refresh|rm|mute|unmute> live external text attached to sessions
|
|
109
|
-
chi send [-m TEXT] ID...|--new a message into sessions, as if typed there
|
|
110
|
-
chi answer ID --question QID --option N answer a question a session waits on
|
|
111
|
-
chi memory <ls|rm> [--scope project|system] the memories; rm moves one to the bundle trash
|
|
112
|
-
chi bundle <install|upgrade|uninstall|status|diff|list|build|trash|new|test|check|try>
|
|
113
|
-
chi desktop <install|upgrade|uninstall|status> macOS "Send to chi" helper
|
|
114
|
-
chi models [--format json] [TEXT] the models --model takes, from every host
|
|
115
|
-
chi self version, paths, model and bundles
|
|
116
|
-
chi update [--dry-run] update this chi, its bundles and the desktop helper
|
|
117
|
-
(each subcommand takes --help)
|
|
118
|
-
|
|
119
|
-
BANNER
|
|
120
|
-
opts.on("-p", "--prompt PROMPT", "Run PROMPT as the first turn, then stay in the REPL") { |p| options[:prompt] = p }
|
|
121
|
-
opts.on("--resume SESSION_ID", "Resume a previous session by its ID (or a unique prefix of it)") { |s| options[:resume] = s }
|
|
122
|
-
opts.on("--attach SESSION_ID", "Attach to a session's worker, waking one if it has exited; ID or a unique prefix") { |s| options[:attach] = s }
|
|
123
|
-
opts.on("--[no-]shared", "Run the session in a background worker and attach to it, so `chi web` can share it (the default, config session.shared; with --resume: that session); --no-shared runs a plain in-process REPL") { |v| options[:shared] = v }
|
|
124
|
-
opts.on("--model NAME", "Model for this run, not saved (overrides --resume's; config default.model) [env SAMAGOTCHI_DEFAULT_MODEL]") { |m| options[:model] = m; cli_overrides["default.model"] = m }
|
|
125
|
-
opts.on("--profile NAME", Samagotchi::ModelProfile::NAMES,
|
|
126
|
-
"Prompt profile for every model in this run (#{Samagotchi::ModelProfile::NAMES.join("|")}) [alias for --model-profile]") { |p| cli_overrides["model.profile"] = p }
|
|
127
|
-
opts.on("--thinking LEVEL", Samagotchi::Thinking::LEVELS.map(&:to_s), "This session's own thinking level: off|low|medium|high|default (saved in the session, " \
|
|
128
|
-
"before the model's; default unsets it; /thinking changes it). With chi web: the default for the sessions " \
|
|
129
|
-
"its workers run, below a session's own [env SAMAGOTCHI_THINKING_LEVEL]") { |l| options[:thinking] = l }
|
|
130
|
-
opts.on("--toolkit NAME", "This session's toolkit, the tools its prompts declare: full|lean|child|a config or bundle one " \
|
|
131
|
-
"(saved in the session; config toolkit.default; /toolkit changes it)") { |n| options[:toolkit] = n }
|
|
132
|
-
opts.on("--tool NAME", "Also declare this tool on top of the toolkit (repeatable; a comma list too)") { |t| (options[:tools] ||= []) << t }
|
|
133
|
-
# --no-tool is taken out of ARGV before parse! (OptionParser would read it
|
|
134
|
-
# as --tool's negation); this is its help line.
|
|
135
|
-
opts.separator " --no-tool NAME Leave this tool out of the toolkit (repeatable; a comma list too)"
|
|
136
|
-
opts.on("--set BUNDLE.KEY=VALUE", "A bundle setting for this session, over config.yml's bundles: (repeatable; a key the " \
|
|
137
|
-
"bundle's manifest lists in session_settings:; numbers and booleans (true, off, no) are typed, the rest is " \
|
|
138
|
-
"text; a value of the wrong type is refused; saved in the session: --set cost-cap.cap=3)") do |v|
|
|
139
|
-
(options[:sets] ||= []) << v
|
|
140
|
-
end
|
|
141
|
-
opts.on("--parent-answers LEVEL", "What a parent agent may answer on this session's questions: " \
|
|
142
|
-
"hard|standard|soft, only stricter than config.yml's guardrails.parent_answers (saved in the " \
|
|
143
|
-
"session; only you loosen it, with /parent-answers in the terminal or the web)") do |v|
|
|
144
|
-
options[:parent_answers] = v
|
|
145
|
-
end
|
|
146
|
-
opts.on("--memory NAME", "Preload a memory entry into the system prompt (repeatable; a comma list too)") { |m| (options[:memories] ||= []) << m }
|
|
147
|
-
opts.on("--append-system-prompt FILE", "Put FILE's text into the system prompt, after AGENTS.md (repeatable; saved in the session; " \
|
|
148
|
-
"config append_system_prompt: is the baseline)") { |f| (options[:append_system_prompt] ||= []) << f }
|
|
149
|
-
opts.on("--mute NAME", "Hide a memory from this session: not in the prompt, refused by memory_read (repeatable; a comma list too)") { |m| (options[:muted] ||= []) << m }
|
|
150
|
-
opts.on("--llm-context LAYERS", "This session's own LLM context strategy: none, or layers such as stale,forget (saved in the session, " \
|
|
151
|
-
"before the model's; /llm-context changes it)") { |v| (options[:llm_context] ||= {})[:strategy] = v }
|
|
152
|
-
opts.on("--llm-context-apply RULE", "This session's own apply rule for it: payoff|next_request|turn_end") { |v| (options[:llm_context] ||= {})[:apply] = v }
|
|
153
|
-
opts.on("--llm-context-budget TOKENS", "This session's own context budget: tokens (64000, 64k) or off") { |v| (options[:llm_context] ||= {})[:budget_tokens] = v }
|
|
154
|
-
opts.on("--no-interrupt", "Raise the tool call limit to 1000 iterations (useful for long tasks)") { options[:no_interrupt] = true; cli_overrides["no_interrupt"] = true }
|
|
155
|
-
opts.on("--non-interactive", "Run a single prompt and exit (no REPL, implies --no-interrupt)") { options[:non_interactive] = true }
|
|
156
|
-
opts.on("-v", "--verbose", "Print raw LLM responses and tool calls/results (runs a plain REPL, not attached)") { options[:verbose] = true }
|
|
157
|
-
opts.on("--no-default-input", "Skip default REPL input prefill") { options[:no_default_input] = true; cli_overrides["no_default_input"] = true }
|
|
158
|
-
opts.on("--port PORT", "Port for `chi web` (default: 4567, env SAMAGOTCHI_WEB_PORT)") { |p| options[:web_port] = p.to_i; cli_overrides["web.port"] = p.to_s }
|
|
159
|
-
opts.on("--open", "Open browser for `chi web`") { options[:web_open] = true }
|
|
160
|
-
opts.on("--new-token", "`chi web`: replace the LAN access token (web.host: lan); links and QR codes made with the old one stop working") { options[:new_token] = true }
|
|
161
|
-
opts.on("--scope VALUE", %w[project all],
|
|
162
|
-
"Which sessions `chi web` and `chi sessions list` show: project (the default: this folder's git project; " \
|
|
163
|
-
"every session outside a repo) or all") { |v| options[:scope] = v }
|
|
164
|
-
# Accepted values of string entries that the registry doesn't type as enums.
|
|
165
|
-
value_hints = { "status.line" => "on|off", "web.host" => "127.0.0.1|::1|localhost|lan|IP", "recap.sentences" => "N|N-M",
|
|
166
|
-
"web.annotate_presets" => "text|text", "web.editor" => "vscode|vscode-insiders|cursor|zed|none|URL" }
|
|
167
|
-
# Universal config flags (implicit convention: ENV SAMAGOTCHI_* ↔ YAML dotted ↔ CLI --kebab)
|
|
168
|
-
# Generated from Samagotchi::Config registry — Option A: snake leaf in YAML, kebab in CLI.
|
|
169
|
-
Samagotchi::Config.cli_entries.each do |entry|
|
|
170
|
-
next if %w[no_interrupt no_default_input].include?(entry.key)
|
|
171
|
-
next if entry.key == "web.port" # already handled as --port
|
|
172
|
-
next if entry.key == "default.model" # alias --model above
|
|
173
|
-
next if entry.key == "web.markdown" # handled below with explicit inverse flags
|
|
174
|
-
|
|
175
|
-
case entry.type
|
|
176
|
-
when :bool
|
|
177
|
-
# --[no-]: a bool that defaults to on is only useful switched off.
|
|
178
|
-
opts.on("--[no-]#{entry.cli_flag.delete_prefix("--")}", "Set #{entry.key} (bool, default #{entry.default ? "on" : "off"}) [env #{entry.env_key}]") { |v| cli_overrides[entry.key] = v }
|
|
179
|
-
when :integer, :float, :string, :enum
|
|
180
|
-
desc = "Set #{entry.key} [env #{entry.env_key}]"
|
|
181
|
-
desc += " (#{entry.enum_values.join("|")})" if entry.type == :enum
|
|
182
|
-
desc += " (#{value_hints[entry.key]})" if value_hints.key?(entry.key)
|
|
183
|
-
desc += "; only without a hosts: config" if %w[server.host server.port].include?(entry.key)
|
|
184
|
-
if entry.type == :enum
|
|
185
|
-
opts.on("#{entry.cli_flag} VALUE", entry.enum_values, desc) { |v| cli_overrides[entry.key] = v }
|
|
186
|
-
else
|
|
187
|
-
opts.on("#{entry.cli_flag} VALUE", desc) { |v| cli_overrides[entry.key] = v }
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
opts.on("--web-markdown", "Render finalized LLM Markdown in `chi web` (requires optional commonmarker gem)") { options[:web_markdown] = true; cli_overrides["web.markdown"] = true }
|
|
192
|
-
opts.on("--no-web-markdown", "Disable Markdown rendering in `chi web`") { options[:web_markdown] = false; cli_overrides["web.markdown"] = false }
|
|
193
|
-
# Strict underscore rejection: --recap_base_url etc. are not registered and will raise OptionParser::InvalidOption
|
|
194
|
-
opts.on("--version", "Print chi's version") { puts "chi #{Samagotchi::VERSION}"; exit }
|
|
195
|
-
opts.on("-h", "--help", "Show help") { puts opts; exit }
|
|
196
|
-
end
|
|
197
|
-
# --no-tool NAME (or --no-tool=NAME), up to a "--". A flag that takes a
|
|
198
|
-
# value (-p, --model …: the parser's own list) keeps its next word, so
|
|
199
|
-
# `chi -p "--no-tool=x is broken"` is a prompt. A tool name never starts
|
|
200
|
-
# with "-": `--no-tool --` or `--no-tool --model` is a missing argument.
|
|
201
|
-
switches = parser.instance_variable_get(:@stack).flat_map { |list| list.list }
|
|
202
|
-
.select { |switch| switch.is_a?(OptionParser::Switch) }
|
|
203
|
-
switch_flags = switches.flat_map { |switch| switch.long + switch.short }
|
|
204
|
-
known_flags = (known + switch_flags).uniq
|
|
205
|
-
takes_value = switches.select { |switch| switch.is_a?(OptionParser::Switch::RequiredArgument) }
|
|
206
|
-
.flat_map { |switch| switch.long + switch.short }
|
|
207
|
-
reject_prompt_flag = lambda do |flag, value|
|
|
208
|
-
if known_flags.include?(value)
|
|
209
|
-
warn "Error: #{flag} needs the prompt text right after it (got the flag #{value}) (see chi --help)"
|
|
210
|
-
exit 2
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
index = 0
|
|
214
|
-
while index < ARGV.size && ARGV[index] != "--"
|
|
215
|
-
arg = ARGV[index]
|
|
216
|
-
if %w[-p --prompt].include?(arg)
|
|
217
|
-
reject_prompt_flag.call(arg, ARGV[index + 1])
|
|
218
|
-
index += 2
|
|
219
|
-
elsif arg.start_with?("--prompt=")
|
|
220
|
-
reject_prompt_flag.call("--prompt", arg.delete_prefix("--prompt="))
|
|
221
|
-
index += 1
|
|
222
|
-
elsif arg.start_with?("-p") && arg != "-p"
|
|
223
|
-
reject_prompt_flag.call("-p", arg.delete_prefix("-p").delete_prefix("="))
|
|
224
|
-
index += 1
|
|
225
|
-
else
|
|
226
|
-
index += takes_value.include?(arg) ? 2 : 1
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
missing_no_tool = lambda do
|
|
230
|
-
warn "Error: missing argument: --no-tool (see chi --help)"
|
|
231
|
-
exit 1
|
|
232
|
-
end
|
|
233
|
-
index = 0
|
|
234
|
-
while index < ARGV.size && ARGV[index] != "--"
|
|
235
|
-
arg = ARGV[index]
|
|
236
|
-
if arg == "--no-tool"
|
|
237
|
-
value = ARGV[index + 1]
|
|
238
|
-
missing_no_tool.call if value.nil? || value.start_with?("-")
|
|
239
|
-
(options[:no_tools] ||= []) << ARGV.delete_at(index + 1)
|
|
240
|
-
ARGV.delete_at(index)
|
|
241
|
-
elsif arg.start_with?("--no-tool=")
|
|
242
|
-
value = arg.delete_prefix("--no-tool=")
|
|
243
|
-
missing_no_tool.call if value.empty? || value.start_with?("-")
|
|
244
|
-
options[:no_tools] = (options[:no_tools] || []) << value
|
|
245
|
-
ARGV.delete_at(index)
|
|
246
|
-
else
|
|
247
|
-
index += takes_value.include?(arg) ? 2 : 1
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
begin
|
|
251
|
-
parser.parse!
|
|
252
|
-
rescue OptionParser::ParseError => e
|
|
253
|
-
warn "Error: #{e.message} (see chi --help)"
|
|
254
|
-
exit 1
|
|
255
|
-
end
|
|
93
|
+
# The run flags (RunOptions: the parser and its checks); what is left in
|
|
94
|
+
# ARGV is `web` or a stray word.
|
|
95
|
+
require "samagotchi/run_options"
|
|
96
|
+
options, cli_overrides = Samagotchi::RunOptions.parse(ARGV)
|
|
256
97
|
|
|
257
98
|
# The subcommands with their own parsers ran above; what parse! leaves is
|
|
258
99
|
# `web` or nothing. Anything else is a typo, not a prompt: refuse it rather
|
|
@@ -358,7 +199,8 @@ end
|
|
|
358
199
|
end
|
|
359
200
|
next unless leftover
|
|
360
201
|
|
|
361
|
-
|
|
202
|
+
require "samagotchi/launch_mode"
|
|
203
|
+
warn "Error: #{Samagotchi::LaunchMode::SCRATCH_LEFTOVER}"
|
|
362
204
|
exit 1
|
|
363
205
|
end
|
|
364
206
|
|
|
@@ -435,13 +277,14 @@ if options[:toolkit] || options[:tools] || options[:no_tools]
|
|
|
435
277
|
remove: options[:no_tools])
|
|
436
278
|
end
|
|
437
279
|
|
|
438
|
-
# --set BUNDLE.KEY=VALUE: a new session's
|
|
439
|
-
# against
|
|
280
|
+
# --set KEY=VALUE / BUNDLE.KEY=VALUE: a new session's own settings, checked
|
|
281
|
+
# here (a core key against its Config entry, a bundle's against the installed
|
|
282
|
+
# bundles), so a typo isn't saved.
|
|
440
283
|
if options[:sets]
|
|
441
|
-
require "samagotchi/
|
|
284
|
+
require "samagotchi/session_config"
|
|
442
285
|
begin
|
|
443
|
-
options[:
|
|
444
|
-
rescue Samagotchi::
|
|
286
|
+
options[:settings] = Samagotchi::SessionConfig.from_flags(options[:sets])
|
|
287
|
+
rescue Samagotchi::SessionConfig::Error => e
|
|
445
288
|
warn "Error: #{e.message}"
|
|
446
289
|
exit 2
|
|
447
290
|
end
|
|
@@ -473,15 +316,7 @@ launch, launch_note = if scratch
|
|
|
473
316
|
warn launch_note if launch_note
|
|
474
317
|
|
|
475
318
|
if launch == :attached
|
|
476
|
-
|
|
477
|
-
.find { |key, _flag| options[key] }
|
|
478
|
-
error = if options[:attach] && options[:shared]
|
|
479
|
-
"use either --attach ID or --shared"
|
|
480
|
-
elsif options[:attach] && options[:resume]
|
|
481
|
-
"--attach takes the session id; --resume goes with --shared"
|
|
482
|
-
elsif conflict
|
|
483
|
-
"--attach/--shared can't be combined with #{conflict.last}"
|
|
484
|
-
end
|
|
319
|
+
error = Samagotchi::LaunchMode.attached_conflict(options)
|
|
485
320
|
if error
|
|
486
321
|
warn "Error: #{error}"
|
|
487
322
|
exit 1
|
|
@@ -500,7 +335,7 @@ if launch == :attached
|
|
|
500
335
|
appended_prompt_files: options[:appended_prompt_files] || [],
|
|
501
336
|
toolkit: options[:toolkit_value],
|
|
502
337
|
toolkit_flags: options[:toolkit_flags] || [],
|
|
503
|
-
|
|
338
|
+
settings: options[:settings],
|
|
504
339
|
parent_answers: options[:parent_answers])
|
|
505
340
|
rescue Samagotchi::TerminalUI::AttachLauncher::Error => e
|
|
506
341
|
warn "Error: #{e.message}"
|
|
@@ -523,7 +358,7 @@ begin
|
|
|
523
358
|
appended_prompt_files: options[:appended_prompt_files] || [],
|
|
524
359
|
toolkit: options[:toolkit_value],
|
|
525
360
|
toolkit_flags: options[:toolkit_flags] || [],
|
|
526
|
-
|
|
361
|
+
settings: options[:settings],
|
|
527
362
|
parent_answers: options[:parent_answers],
|
|
528
363
|
non_interactive: options[:non_interactive],
|
|
529
364
|
model_name: options[:model],
|
data/docs/architecture.md
CHANGED
|
@@ -125,6 +125,11 @@ terminal renders the events it gets over the Bridge.
|
|
|
125
125
|
- `bin/chi web` → builds `Web::Server` (Rack+WEBrick on `127.0.0.1:4567`, `--port`/`SAMAGOTCHI_WEB_PORT`, `--open`;
|
|
126
126
|
`web.host: lan` adds a LAN address, `--new-token` replaces its access token).
|
|
127
127
|
- `bin/chi sessions {list,stop,restart,archive,unarchive,delete,prune,clean,stats}` (`SessionsCommand`; not the REPL's `SessionCommands`) → retention & ordering (`SessionRetention`, `updated_at desc`, dry-run, test-only); `stop` is `SessionManager.stop_session(wait:)`, which waits for the worker to release `owner.lock`; `delete` (`SessionDeleteCommand`) is `SessionManager.delete_session(stop:)`, which the TUI's `/exit --delete` and the web's `DELETE /api/sessions/:id` use too.
|
|
128
|
+
- Session commands (`chi send`, `answer`, `note`, a session's `context`, `sessions list/stop/restart/archive/unarchive/delete`) →
|
|
129
|
+
`SessionAccess` (`session_access.rb`): what they ask of a session goes through one backend object,
|
|
130
|
+
`SessionAccess::Local` (`session_access/local.rb`) on this machine's files and workers; a `--wait`'s pre-send state
|
|
131
|
+
(cursor, baseline, own turn) is `prepare_wait`'s `WaitStart`. `SessionAccess::Contract` lists which commands and flags
|
|
132
|
+
run against a remote and which on this machine only.
|
|
128
133
|
- In REPL mode `TerminalUI` builds the `Engine` in process (`-p` and `--non-interactive` too);
|
|
129
134
|
otherwise the session's `Worker` (`lib/samagotchi/worker.rb`, spawned by `SessionManager`) builds it.
|
|
130
135
|
|
|
@@ -311,7 +316,8 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
311
316
|
refused connection is `503 not_live`.
|
|
312
317
|
- `POST /session/:id/cancel` — cancel the running turn; `202`, or `409` when none runs.
|
|
313
318
|
- `POST /session/:id/recap` — `/recap` in an attached TUI: `200 {enabled, saved, request,
|
|
314
|
-
min_user_turns}` (`{enabled: false}` without recaps
|
|
319
|
+
min_user_turns}` (`{enabled: false, session_off}` without recaps; `session_off` when the session's own
|
|
320
|
+
`/recap off` turned it off); a new recap is asked for at once and
|
|
315
321
|
arrives as `:recap_ready`. Answers mid-turn too.
|
|
316
322
|
- `POST /session/:id/answer` — answer the pending question; `200`, `409` when another client
|
|
317
323
|
answered first or it is gone, `400` for an invalid selection. With `client_id: "cli:answer"`
|
|
@@ -446,10 +452,11 @@ The page's frequent reads of `GET /api/sessions/:id` stay light too:
|
|
|
446
452
|
- `?cards=1`: the cards, from `/tail`; the session file is only stat'ed for the 404.
|
|
447
453
|
|
|
448
454
|
The stage view's working set (`web.working_set`) has its own read, `GET /api/sessions/:id/working_set`: the
|
|
449
|
-
`WorkingSet` projection (`{items, at}`: each file, memory and URL live, stale or forgotten, with its tokens)
|
|
450
|
-
worker's `/snapshot` messages, else the session file, under the session's LLM context strategy
|
|
451
|
-
|
|
452
|
-
|
|
455
|
+
`WorkingSet` projection (`{items, at, running}`: each file, memory and URL live, stale or forgotten, with its tokens)
|
|
456
|
+
of the worker's `/snapshot` messages, else the session file, under the session's LLM context strategy; while a turn
|
|
457
|
+
runs, the snapshot's `working_set` (the running turn's last event, which the messages lag) when it has one. Its live form
|
|
458
|
+
is the `:working_set_updated` event, which always wins over a GET's answer it overtook. The page overlays it on the
|
|
459
|
+
set it builds from its tool rows ([internals/working-set.md](internals/working-set.md)).
|
|
453
460
|
|
|
454
461
|
A worker without `/tail` (an older chi; `404 not_found`) is read through `/snapshot` in the same
|
|
455
462
|
shape; any other failure (no worker, a 500, a timeout) reads the disk, trimmed to the newest
|
|
@@ -497,6 +504,54 @@ names its `chi_version` too.
|
|
|
497
504
|
`GET /api/sessions` is the hub's projection too (its sort, paging and total). `Server` always builds
|
|
498
505
|
a hub; an `App.new` without one answers both routes `503 no_hub`.
|
|
499
506
|
|
|
507
|
+
**chi web's routes for the session commands.** chi's session commands (`chi send`, `answer`, `note`, a session's
|
|
508
|
+
`context`, `sessions list/stop/restart/archive/unarchive/delete`) run against another machine's sessions through its
|
|
509
|
+
chi web (the remote-sessions plan; `SessionAccess::Contract` says which commands and flags may). The page's routes
|
|
510
|
+
cover most of it; `Web::SessionAccessRoutes` (`web/session_access_routes.rb`, in `App`'s `ROUTES`) adds the rest, each
|
|
511
|
+
calling the `SessionAccess::Local` operation of the same name, so a remote backend maps 1:1 onto them. Every one is
|
|
512
|
+
token-gated like the rest (LAN mode: `Authorization: Bearer`), and one that takes an id is a `:session` route (an id
|
|
513
|
+
that isn't one, or no such session: 404 `not_found`). How a request flows end to end, which checks run where and the trust
|
|
514
|
+
model: [internals/remote-sessions.md](internals/remote-sessions.md).
|
|
515
|
+
|
|
516
|
+
| Route | Body / params | Answer | Refusals |
|
|
517
|
+
|---|---|---|---|
|
|
518
|
+
| `GET /api/sessions/resolve?id=` | an id or a unique prefix | 200 `{id}` | 404 `not_found`; 400 `ambiguous_id` with `ids` |
|
|
519
|
+
| `GET /api/sessions/summaries` | `dir` (a folder: its project; none: every project), `cwd`, `live=1`, `archived=1`, `tests=0`, `sort`, `order`, `limit` | 200: the rows `chi sessions list` prints (`SessionManager.session_summaries`: `status`, `live`, `busy`, `owner`, `recap`, `ctx_pct`, `waiting`, `waiting_id`, `title`, `preview`, …) | 400 `invalid_dir`, `invalid_cwd` |
|
|
520
|
+
| `POST /api/sessions` (more fields) | `toolkit`, `tools`, `no_tools`, `parent_answers`; `client_id` (`cli:send`/`cli:answer`: the first line is checked as chi send checks it); `check_vision: true` (images follow) | 201 adds `model_warning` and `parent_answers_warning` (a level looser than config.yml's, capped) | 400 `invalid_toolkit`, `invalid_parent_answers`, `invalid_continues` (beside `continues`); 403 `parent_answers_refused`, `parent_continue_refused`; 422 `no_vision` |
|
|
521
|
+
| `POST /api/sessions/:id/turn` (more fields) | `client_id` as above; `check_vision: true` with `images` | as before | 403 `parent_answers_refused`, `parent_continue_refused`; 422 `no_vision` |
|
|
522
|
+
| `POST /api/sessions/:id/wait/prepare` | `question_id` (absent: the pending one; `null`: none), `turn`, `created`, `busy` (+ `queue`) | 200 `{session_id, cursor, baseline: {messages, question_id, last_turn}, own_turn, known_turn, running, live, settled, last_turn_id}` (`WaitStart`) | 409 `busy`, `waiting` (`SendCommand::Busy`'s words, and its fields: `question`, `parent_strategy`) |
|
|
523
|
+
| `POST /api/sessions/:id/wait` | `cursor`, `baseline`, `turn`, `queued`, `ended_before`, `owner_grace`, `timeout` (capped at 30 s) | 200: `ReplyWait::Result`'s fields (`status`: `done`, `waiting_for_answer`, `no_reply`, `error`, `stopped`, `worker_gone`, or `timeout`: ask again; `text`, `file`, `question` as `PendingQuestion#to_file`, `outcome`, `limit`, `error_kind`, `retryable`, `cancel_reason`, `stopped_by`, `kept_steps`, `turn_id`, `origin`; never the session) and `parent_strategy` `{name, allowed, origin}` (`ParentReport.strategy_for`, this server's config.yml) | 400 `invalid_wait`; 503 `server_stopping` |
|
|
524
|
+
| `POST /api/sessions/:id/notes` | `text`, `source` (default `cli`, never `broadcast`) | 200 `{status: queued \| waits, session_id, queued}` (`NoteDelivery`) | 409 `owned_by_tui` (`status: refused`); 400 `note_rejected`, `missing_fields`, `invalid_source` |
|
|
525
|
+
| `POST /api/sessions/:id/context` (more bodies) | `{name, push: true, why, hint}` or `{name, cmd, every, why, hint}` beside the page's `{url, why, hint}`; `added_by` `cli`/`agent` (else `web`) | 201 `{status: attached, name}` | 400 `invalid_source`; 409 `exists` |
|
|
526
|
+
| `POST /api/sessions/:id/context/:name/push` | `text`, `summary`, `wake`, `hint` (a source's JSON output, checked the same way) | 200 `{status: pushed, name, before, revision, changed}` | 404 `not_found`; 409 `not_push`; 400 `invalid_text` |
|
|
527
|
+
| `DELETE /api/sessions/:id/context/:name?own=1` | | 200 `{status: detached}`: the session's own only | 409 `project_source` (chi context rm's words); 404 |
|
|
528
|
+
| `POST /api/sessions/:id/stop` | | 200 `{status: stopped, session_id, released}`: `released`, whether the worker let go within `STOP_WAIT_SECONDS` (2 s) | 409 `owned_by_tui` |
|
|
529
|
+
| `DELETE /api/sessions/:id?force=` | `force=true` stops a live worker first (the page sends it) | as before | 409 `worker_running` without it |
|
|
530
|
+
| `GET /api/sessions/:id?brief=1` | | 200 `{brief, session: {id, status (the file's), title, display_title, project_root, working_directory, model_name, model_typed, parent_answers, owner}, pending_question}` (the file's; no messages): `Remote#show` | 404 |
|
|
531
|
+
| `POST /api/sessions/:id/turn` (its 202) | | the ack with `via`: `bridge` or `file` (the input file: a remote `chi send --wait` follows that turn) | |
|
|
532
|
+
| `GET /api/sessions/:id/context?full=1`, `…/context/:name?full=1` | | each row with `source` (`Source#to_h`, its command too; the page's rows never carry one), `snapshot` (`Snapshot#to_h`, no text) and `shadowed`, shadowed project sources included | |
|
|
533
|
+
| `POST /api/sessions/:id/wake` | `wait` (seconds, default and most 10: `WAKE_MAX_WAIT`) | 200 `{session_id, woke, worker: {epoch, started_at, version, features}, model_name, default_model, parent_strategy, installed}`: the live worker as is (`woke: false`), else resumed and its Bridge waited for (`Local#wake`); `default_model` and `installed` are this machine's | 409 `owned_by_tui`, `scratch` (`LaunchMode::SCRATCH_LEFTOVER`); 504 `worker_timeout` (`LaunchMode::BRIDGE_LATE`); 400 `invalid_wait`; 404 |
|
|
534
|
+
| `GET /api/sessions/:id/worker` | | 200 `{session_id, live, stopped, owner: worker \| tui \| null, worker: {epoch, started_at, version, features} \| null, parent_strategy}` (`Local#worker`; never starts one) | 404 |
|
|
535
|
+
| `POST /api/sessions/:id/turn` (`no_interrupt`) | `no_interrupt: true`: the turn runs with the raised iteration limit (`SessionManager.deliver_turn(no_interrupt:)`, to the Bridge or the input file) | as before | |
|
|
536
|
+
| `POST /api/sessions/:id/command` (its 501) | | | 501 `not_supported` for a builtin the worker lacks adds `command` (`"/title"`) and `message` |
|
|
537
|
+
|
|
538
|
+
`GET /api/info` names the feature `session_commands` (`SessionAccessRoutes::FEATURE`): `SessionAccess::Remote` asks it
|
|
539
|
+
once before its first request, and a chi web without it (or `serve_static`'s 404 for a path no route takes) is "remote NAME runs chi X, too
|
|
540
|
+
old for this command through --remote: update chi there (chi update)". The feature `remote_attach`
|
|
541
|
+
(`SessionAccessRoutes::ATTACH_FEATURE`) names the wake and worker routes a remote attached TUI needs.
|
|
542
|
+
|
|
543
|
+
`POST …/answer` passes on `client_id` only when it is chi answer's (`cli:answer`), so the worker holds the answer to
|
|
544
|
+
its `guardrails.parent_answers`; any other is dropped, so a forwarded id can only tighten. The worker's 403
|
|
545
|
+
`parent_answer_refused` (with `reason`) and a dismissal's 409 `not_dismissable` pass through; a 408 (the worker read it
|
|
546
|
+
past its deadline) is sent once more, as `SessionAccess::Local#answer` does, and a second is `504 worker_timeout`.
|
|
547
|
+
|
|
548
|
+
A refusal an exception raises carries, next to `error` and `detail` (the page's words), `message` (the exception's own,
|
|
549
|
+
verbatim) and the fields its class is built from, so a remote client raises the same class with the same text:
|
|
550
|
+
`owned_by_tui` (`session_id`), `worker_running`/`still_stopping` (`DeleteRefused`: `session_id`, `reason`), `busy`/
|
|
551
|
+
`scratch` (`ArchiveRefused`: `session_id`, `reason`, `busy_id`), a restart's `held`/`not_running`/`unsupported`/
|
|
552
|
+
`failed` (`RestartRefused`: `session_id`, `reason`, `version`), a continue's `continued`/`open_children`/`folder_gone`
|
|
553
|
+
(`SessionContinue::Refused`: `session_id`, `reason`, `ids`) and `ambiguous_id` (`Session::AmbiguousId`: `ids`).
|
|
554
|
+
|
|
500
555
|
## Model loops and adapters
|
|
501
556
|
|
|
502
557
|
A model ref (`--model`, `/model`, `default.model`, an alias's target) is parsed
|
|
@@ -609,7 +664,7 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
|
|
|
609
664
|
| Paths | `paths.rb` (`Paths`) | The XDG base dirs chi's files live under, read from the env on every call: `Paths.state_home` (`$XDG_STATE_HOME`, else `~/.local/state`) and `Paths.config_home` (`$XDG_CONFIG_HOME`, else `~/.config`), both defaults under `Dir.home`; a blank variable counts as unset, a relative one is made absolute from the current directory. `Paths.tilde` shows a path with `~` for the home folder. `SessionManager` hands a spawned worker the spawner's `XDG_STATE_HOME` / `XDG_CONFIG_HOME`, when set, made absolute, so a relative one isn't read from the session's folder. |
|
|
610
665
|
| MemoryBundle | `memory_bundle.rb`, `memory_bundle/{installer,builder,installed_bundle,plugin_ref,bundle_hook}.rb` (`Installer`, `Builder`, `InstalledBundle`, `PluginRef`, `BundleHook`) | Installing, building and reading bundles of memories, hooks and plugins. |
|
|
611
666
|
| Bundle authoring | `bundle_command.rb` (`BundleCommand`), `bundle_command/{scaffold,check,test_run}.rb`, `bundle_command/try/{run,sandbox,trace}.rb`, `testing.rb` + `testing/` (the test kit) | `chi bundle new`, `check`, `test` and `try`, and the kit a bundle's tests load it with ([bundle-authoring.md](bundle-authoring.md), [internals/bundles.md](internals/bundles.md)). |
|
|
612
|
-
|
|
|
667
|
+
| Session config (`--set`) | `session_config.rb` (`SessionConfig`, `SessionConfig::Settings`) | A session's own settings as one value (`Session#settings`, `SessionSetup#settings`): `core`, the `--set KEY=VALUE` values of the core keys marked `session: true` (`Config::Entry`), typed by `Config.coerce!` and read first through `Config.get(key, session:)` (`RecapSetup`; `Engine#session=` rebuilds the recap job, `IdleScheduler#replace_job`), file key `config_settings`; `bundles`, the `--set BUNDLE.KEY=VALUE` values, checked against the manifest's `session_settings:`, laid over `bundles: <name>:` in `ctx.settings` ([plugins.md](plugins.md)), file key `bundle_settings`. `SessionConfig.clash` refuses a bundle setting named like a core one (install, `chi bundle check`). See [configuration.md](configuration.md#per-session-settings). |
|
|
613
668
|
| Plugin API | `plugin/api.rb` (`Plugin::Api`), `plugin/tool_spec.rb` (`Plugin::ToolSpec`: a checked tool declaration, how it is registered), `plugin/loader.rb`, `plugin/context.rb` (`ctx`), `plugin/http.rb` + `plugin/http_transport.rb` (`ctx.http`), `plugin/card.rb` (`Plugin::Card`) | What a bundle's plugin registers: tools, commands, annotation actions, hooks, services, toolkits, peer sources; what its handlers get as `ctx` ([plugins.md](plugins.md)). |
|
|
614
669
|
| Peer bundles' base | `plugin/peer.rb` (`Plugin::Peer`) | What claude-peer and codex-peer share (the send tool, the command, the cards, payloads); each bundle's subclass sets its strings and transport ([plugins.md](plugins.md#the-claude-peer-bundle)). |
|
|
615
670
|
| Shipped bundles | `bundles/` (`mcp`, `github-pr`, `loop-guard`, `skills`, `source-links`, …) | Bundles that ship with chi. |
|
|
@@ -617,6 +672,7 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
|
|
|
617
672
|
| Guardrails | `guardrails.rb`, `guardrails/shell_lex.rb`, `guardrails/data_readers.rb`, `guardrails/rules.rb`, `guardrails/protected_paths.rb`, `guardrails/parent_policy.rb` + `guardrails/parent_approvals.rb`, `guardrail_wiring.rb` | The gate before every tool call: shell lexing (and the commands that only read a heredoc as data), YAML rules, the core checks on chi's own files, approvals, what a parent agent may answer (`guardrails.parent_answers`) ([guardrails.md](guardrails.md)). |
|
|
618
673
|
| Delegation | `tools/delegate.rb`, `tools/delegate_result.rb`, `tools/delegate_wait.rb`, `child_reports.rb`, `tools/delegate_relay.rb`, `relay_desk.rb`, `relay_verifier.rb`, `relay_watcher.rb` | Child sessions a session starts and waits on, and their approval relay ([sessions.md](sessions.md#delegating)). |
|
|
619
674
|
| send / broadcast | `send_command.rb` (`SendCommand`), `broadcast_command.rb` (`BroadcastCommand`), `broadcast/` | `chi send` and `chi broadcast` ([broadcast.md](broadcast.md), [sub-agent.md](sub-agent.md)). |
|
|
675
|
+
| Session access | `session_access.rb` (`SessionAccess`: `NewSession`, `SetupWords`, `WaitStart`, `SessionView`, `Created`, `ContextEntry`, `ContextPush`, the errors `RemoteFailed`, `RemoteTooOld`, `Refused`, `BadRequest`), `session_access/local.rb` (`SessionAccess::Local`), `session_access/remote.rb` (`SessionAccess::Remote`), `session_access/http_transport.rb`, `session_access/web_bridge_client.rb` (`SessionAccess::WebBridgeClient`, a remote attach's worker through chi web), `session_access/dispatch.rb` (`SessionAccess::Dispatch`), `session_access/contract.rb` (`SessionAccess::Contract`), `remotes.rb` (`Remotes`), `remote_command.rb` (`chi remote`), `web/session_access_routes.rb` (`Web::SessionAccessRoutes`) | The seam between the session commands and where the sessions live: `Local` is this machine's (the `SessionManager`, `ReplyWait`, `NoteDelivery`, `ContextSources` and Bridge calls the commands made); `Remote` is another machine's, the same methods as requests to its chi web's routes of the same name (Bearer token, `HTTPTransport`), results as Local's value objects and refusals as the classes Local's callees raise, built from the fields chi web sends; waits and prefixes run on the server, a wait asked again per 30 s slice. `Dispatch` is bin/chi's front for `--remote NAME` / `CHI_REMOTE` (taken off before the subcommand lookup): it refuses what the contract table lists `local_only` (exit 2, one message) and runs the command with a `Remote` on the `Remotes` entry (`$XDG_CONFIG_HOME/samagotchi/remotes.yml`, 0600). chi web's routes answer from `Local` (*chi web's routes for the session commands* above). Checks that read this machine's config or files (vision, parent answers, the cwd's project, `SAMAGOTCHI_PARENT_SESSION`) stay in the commands and are skipped under a `Remote`, where chi web's create, turn and wait routes run the vision, parent-answers and busy checks with the server's config and the worker checks a parent's answer. See [internals/remote-sessions.md](internals/remote-sessions.md). |
|
|
620
676
|
| Idle work | `idle_scheduler.rb` (`IdleScheduler`), `idle_recap.rb` (`IdleRecap`), `idle_reminders.rb` | One poller for an idle session's recap and reminders. |
|
|
621
677
|
| Attached context | `context_sources.rb` (`ContextSources`), `context_poller.rb`, `context_absorber.rb`, `peer_sources.rb` (`PeerSources`: the sources peer bundles declare) | External sources a session keeps fresh, and the peer loop's wakes ([context.md](context.md)). |
|
|
622
678
|
| LLM context strategies | `llm_context_strategy.rb`, `llm_context_view.rb`, `llm_context_stale.rb`, `llm_context_forget.rb` | What a request sends of the history (none, stale, forget) ([internals/llm-context-forget.md](internals/llm-context-forget.md)). |
|