samagotchi 0.153.0 → 0.154.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 +28 -1
- data/README.md +2 -3
- data/bin/chi +23 -24
- data/docs/architecture.md +30 -33
- data/docs/broadcast.md +5 -5
- data/docs/bundles.md +9 -12
- data/docs/cli.md +80 -96
- data/docs/configuration.md +27 -26
- data/docs/context.md +3 -3
- data/docs/desktop.md +1 -1
- data/docs/guardrails.md +8 -10
- data/docs/hooks.md +9 -9
- data/docs/internals/attached-context.md +2 -2
- data/docs/internals/background-tasks.md +3 -4
- data/docs/internals/block-renderers.md +4 -0
- data/docs/internals/bridge.md +3 -3
- data/docs/internals/context-telemetry.md +1 -1
- data/docs/internals/engine.md +33 -7
- data/docs/internals/objectives.md +6 -5
- data/docs/internals/participants.md +7 -7
- data/docs/internals/prompt-caching.md +1 -1
- data/docs/internals/remote-sessions.md +7 -7
- data/docs/internals/web-push.md +1 -1
- data/docs/memory.md +2 -2
- data/docs/plugins.md +23 -28
- data/docs/sessions.md +24 -24
- data/docs/sub-agent.md +2 -2
- data/docs/system-one.md +1 -1
- data/docs/testing.md +16 -6
- data/lib/samagotchi/answer_text.rb +34 -0
- data/lib/samagotchi/bridge.rb +43 -32
- data/lib/samagotchi/bridge_client.rb +10 -9
- data/lib/samagotchi/broadcast/recipients.rb +2 -2
- data/lib/samagotchi/broadcast/triage.rb +1 -1
- data/lib/samagotchi/broadcast_command.rb +5 -5
- data/lib/samagotchi/bundle_command/try/command.rb +1 -1
- data/lib/samagotchi/bundle_command/try/run.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +2 -2
- data/lib/samagotchi/bundles/decision-card/manifest.yml +4 -4
- data/lib/samagotchi/bundles/decision-card/plugin.rb +1 -9
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +3 -4
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/child_move.rb +3 -3
- data/lib/samagotchi/commands/registry.rb +6 -6
- data/lib/samagotchi/composer_route.rb +4 -5
- data/lib/samagotchi/config.rb +14 -12
- data/lib/samagotchi/context_candidate_choice.rb +1 -1
- data/lib/samagotchi/context_candidates.rb +4 -3
- data/lib/samagotchi/continue_offer.rb +1 -1
- data/lib/samagotchi/debug_log.rb +1 -1
- data/lib/samagotchi/degrade.rb +75 -0
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +2 -2
- data/lib/samagotchi/engine/cards.rb +87 -0
- data/lib/samagotchi/engine/context_judge.rb +138 -0
- data/lib/samagotchi/engine/events.rb +131 -0
- data/lib/samagotchi/engine/guardrails.rb +87 -0
- data/lib/samagotchi/engine/hooks.rb +119 -0
- data/lib/samagotchi/engine/messages.rb +124 -0
- data/lib/samagotchi/engine/model_settings.rb +127 -0
- data/lib/samagotchi/engine/models.rb +261 -0
- data/lib/samagotchi/engine/objectives.rb +116 -0
- data/lib/samagotchi/engine/participants.rb +86 -0
- data/lib/samagotchi/engine/plugins.rb +208 -0
- data/lib/samagotchi/engine/questions.rb +98 -0
- data/lib/samagotchi/engine/recap.rb +130 -0
- data/lib/samagotchi/engine/session.rb +131 -0
- data/lib/samagotchi/engine/side_requests.rb +135 -0
- data/lib/samagotchi/engine/steering.rb +134 -0
- data/lib/samagotchi/engine/toolkit.rb +269 -0
- data/lib/samagotchi/engine/turn_end.rb +180 -0
- data/lib/samagotchi/engine/turn_helpers.rb +160 -0
- data/lib/samagotchi/engine/verify.rb +153 -0
- data/lib/samagotchi/engine.rb +62 -2646
- data/lib/samagotchi/grants.rb +575 -0
- data/lib/samagotchi/guardrail_wiring.rb +2 -2
- data/lib/samagotchi/guardrails/context.rb +2 -3
- data/lib/samagotchi/guardrails/parent_approvals.rb +2 -2
- data/lib/samagotchi/guardrails/parent_policy.rb +2 -2
- data/lib/samagotchi/hook_routes.rb +3 -3
- data/lib/samagotchi/idle_recap.rb +4 -5
- data/lib/samagotchi/idle_reminders.rb +3 -4
- data/lib/samagotchi/iteration_limit.rb +2 -2
- data/lib/samagotchi/kernel_loop.rb +10 -11
- data/lib/samagotchi/launch_mode.rb +13 -32
- data/lib/samagotchi/level_setting_command.rb +3 -2
- data/lib/samagotchi/llm_context_command.rb +3 -2
- data/lib/samagotchi/log.rb +7 -7
- data/lib/samagotchi/log_line.rb +1 -1
- data/lib/samagotchi/log_path.rb +1 -1
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/note_delivery.rb +18 -8
- data/lib/samagotchi/objectives.rb +24 -3
- data/lib/samagotchi/objectives_check.rb +8 -27
- data/lib/samagotchi/objectives_command.rb +4 -69
- data/lib/samagotchi/objectives_edit.rb +87 -0
- data/lib/samagotchi/output_formatter.rb +7 -1
- data/lib/samagotchi/owner_lock.rb +7 -1
- data/lib/samagotchi/parent_answers_command.rb +5 -4
- data/lib/samagotchi/participant_exposure.rb +42 -0
- data/lib/samagotchi/participant_mode.rb +1 -1
- data/lib/samagotchi/participant_name.rb +1 -1
- data/lib/samagotchi/participants.rb +17 -17
- data/lib/samagotchi/participants_watch.rb +1 -1
- data/lib/samagotchi/pending_input_queue.rb +2 -2
- data/lib/samagotchi/plugin/api.rb +3 -2
- data/lib/samagotchi/plugin/context.rb +21 -7
- data/lib/samagotchi/plugin/peer.rb +2 -1
- data/lib/samagotchi/plugin/sessions.rb +6 -4
- data/lib/samagotchi/plugin_tasks.rb +1 -2
- data/lib/samagotchi/private_files.rb +29 -0
- data/lib/samagotchi/project_env.rb +2 -2
- data/lib/samagotchi/question_desk.rb +8 -7
- data/lib/samagotchi/recap_command.rb +2 -1
- data/lib/samagotchi/recap_setup.rb +1 -1
- data/lib/samagotchi/recap_store.rb +1 -1
- data/lib/samagotchi/redact.rb +86 -0
- data/lib/samagotchi/reminder_queue.rb +2 -2
- data/lib/samagotchi/remote_attach_command.rb +1 -1
- data/lib/samagotchi/reply_wait/poll.rb +2 -2
- data/lib/samagotchi/review_start.rb +1 -1
- data/lib/samagotchi/run_options.rb +6 -6
- data/lib/samagotchi/self_report.rb +42 -39
- data/lib/samagotchi/send_command/busy.rb +1 -1
- data/lib/samagotchi/send_command.rb +4 -1
- data/lib/samagotchi/session.rb +11 -2
- data/lib/samagotchi/session_access/contract.rb +3 -3
- data/lib/samagotchi/session_access/local.rb +9 -9
- data/lib/samagotchi/session_access/remote.rb +17 -7
- data/lib/samagotchi/session_access.rb +4 -4
- data/lib/samagotchi/session_archive_command.rb +2 -2
- data/lib/samagotchi/session_chain.rb +8 -6
- data/lib/samagotchi/session_commands.rb +45 -48
- data/lib/samagotchi/session_continue.rb +1 -1
- data/lib/samagotchi/session_delete_command.rb +4 -4
- data/lib/samagotchi/session_manager.rb +59 -36
- data/lib/samagotchi/session_metrics.rb +2 -2
- data/lib/samagotchi/session_model_settings.rb +3 -2
- data/lib/samagotchi/session_participants.rb +1 -1
- data/lib/samagotchi/session_retention.rb +3 -3
- data/lib/samagotchi/sessions_command.rb +4 -4
- data/lib/samagotchi/share_command.rb +10 -10
- data/lib/samagotchi/steer.rb +1 -1
- data/lib/samagotchi/system_prompt.rb +102 -109
- data/lib/samagotchi/task_watch.rb +1 -1
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +72 -10
- data/lib/samagotchi/terminal_ui/attached_loop/composer.rb +12 -9
- data/lib/samagotchi/terminal_ui/attached_loop/exit_commands.rb +16 -0
- data/lib/samagotchi/terminal_ui/attached_loop/questions.rb +2 -3
- data/lib/samagotchi/terminal_ui/attached_loop.rb +17 -8
- data/lib/samagotchi/terminal_ui/attached_view.rb +2 -3
- data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -5
- data/lib/samagotchi/terminal_ui/formatting.rb +7 -7
- data/lib/samagotchi/terminal_ui/input_support.rb +1 -7
- data/lib/samagotchi/terminal_ui/line_reader.rb +2 -2
- data/lib/samagotchi/terminal_ui/live_region.rb +2 -2
- data/lib/samagotchi/terminal_ui/log_tail.rb +135 -0
- data/lib/samagotchi/terminal_ui/one_shot_sink.rb +2 -2
- data/lib/samagotchi/terminal_ui/plain_surface.rb +1 -1
- data/lib/samagotchi/terminal_ui/question_prompt.rb +4 -5
- data/lib/samagotchi/terminal_ui/reline_seam.rb +3 -3
- data/lib/samagotchi/terminal_ui/status_row.rb +7 -8
- data/lib/samagotchi/terminal_ui.rb +142 -1387
- data/lib/samagotchi/title_command.rb +2 -2
- data/lib/samagotchi/tool_runner.rb +14 -16
- data/lib/samagotchi/toolkit_command.rb +2 -1
- data/lib/samagotchi/tools/delegate.rb +3 -1
- data/lib/samagotchi/tools/delegate_cursor.rb +3 -3
- data/lib/samagotchi/tools/delegate_wait.rb +1 -1
- data/lib/samagotchi/tools/objectives_tool.rb +2 -2
- data/lib/samagotchi/tools/send_note.rb +2 -1
- data/lib/samagotchi/turn_endings.rb +2 -2
- data/lib/samagotchi/turn_flow.rb +4 -5
- data/lib/samagotchi/update_hint.rb +2 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +39 -50
- data/lib/samagotchi/web/participant_access.rb +2 -2
- data/lib/samagotchi/web/participant_review.rb +5 -5
- data/lib/samagotchi/web/participant_stream_filter.rb +8 -8
- data/lib/samagotchi/web/participant_view.rb +2 -82
- data/lib/samagotchi/web/participants_admin.rb +1 -1
- data/lib/samagotchi/web/public/block_host.js +35 -8
- data/lib/samagotchi/web/public/block_renderers.css +9 -5
- data/lib/samagotchi/web/public/chains.js +2 -2
- data/lib/samagotchi/web/public/claim_link.js +4 -4
- data/lib/samagotchi/web/public/composer_mode.js +1 -1
- data/lib/samagotchi/web/public/format.js +3 -3
- data/lib/samagotchi/web/push/store.rb +1 -1
- data/lib/samagotchi/web/session_access_routes.rb +5 -4
- data/lib/samagotchi/web/session_control.rb +12 -8
- data/lib/samagotchi/web/session_create.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +2 -2
- data/lib/samagotchi/web/session_show.rb +2 -2
- data/lib/samagotchi/web/session_summary.rb +1 -1
- data/lib/samagotchi/worker.rb +100 -9
- data/lib/samagotchi/worker_link.rb +3 -3
- data/lib/samagotchi/working_set_command.rb +3 -2
- metadata +29 -6
- data/lib/samagotchi/terminal_ui/between_turns.rb +0 -138
- data/lib/samagotchi/terminal_ui/repl_input.rb +0 -152
- data/lib/samagotchi/web/grants.rb +0 -575
- data/lib/samagotchi/web/participant_exposure.rb +0 -44
- data/lib/samagotchi/web/private_files.rb +0 -31
data/docs/testing.md
CHANGED
|
@@ -30,6 +30,11 @@ stage's hand-off dwell in stage.spec.js); a pause that is the behaviour outside
|
|
|
30
30
|
route) carries `// eslint-disable-line no-restricted-syntax -- why`. And in the seven
|
|
31
31
|
modules that hold the owner page's session state, one more: no write into `current`, `store.current` or `state()`
|
|
32
32
|
in place (`samagotchi/session-writes`; the store is the write path, see internals/web-frontend.md, The session store).
|
|
33
|
+
In `spec/web/public/`, one rule only (`samagotchi/dom-null-asserts`): no `assert.equal(domNode, null)`. When it fails,
|
|
34
|
+
node's assert formats the happy-dom node, whose graph (its document, its window) takes minutes and gigabytes, so
|
|
35
|
+
`npm test` hangs instead of failing (`--test-timeout` doesn't end it). Write `assert.ok(x === null)` (or `=== undefined`),
|
|
36
|
+
which names the expression and fails at once. The rule sees a DOM lookup by its shape (`querySelector`, `closest`,
|
|
37
|
+
`parentElement`…); a helper that returns a node (`h.input()`) isn't seen, so the same form goes there by hand.
|
|
33
38
|
|
|
34
39
|
CI's e2e job keeps the tests, chi and the fake model on the runner but runs Chromium in Playwright's image
|
|
35
40
|
(`mcr.microsoft.com/playwright:v<version>-noble`, `playwright run-server` on the host network); the tests connect
|
|
@@ -39,7 +44,9 @@ moves the image too (`spec/ci_playwright_image_spec.rb` keeps a pinned tag out o
|
|
|
39
44
|
that way locally: `npx playwright run-server --port 3333`, then `PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:3333/ npm run e2e`.
|
|
40
45
|
|
|
41
46
|
No spec reads your own setup. The suite points `XDG_CONFIG_HOME` and `XDG_STATE_HOME` at temp folders,
|
|
42
|
-
clears your `SAMAGOTCHI_*` environment, and keeps every spec off the network (WebMock).
|
|
47
|
+
clears your `SAMAGOTCHI_*` environment, and keeps every spec off the network (WebMock). `TMPDIR` is a folder of the
|
|
48
|
+
run's own (`SPEC_TMPDIR`, removed at the end), so a spec's bare `Dir.mktmpdir` or `Dir.tmpdir`, and what chi writes or
|
|
49
|
+
sweeps beside it, never touches your real `$TMPDIR`. A spec can't assume that path's length: it is your `$TMPDIR`'s plus one folder.
|
|
43
50
|
It also clears what tells git which repo to act on (`GIT_DIR`, `GIT_WORK_TREE`, the rest of
|
|
44
51
|
`git rev-parse --local-env-vars`: `GitEnv`), first thing in `spec_helper.rb` and as the bundle test kit's
|
|
45
52
|
adapters load, so a run from `git rebase --exec`, `bisect run` or a hook can't reach the real repo through a spec's git.
|
|
@@ -74,11 +81,11 @@ back (`t.mock` restores itself). A test that sets off a module's timer (a flash,
|
|
|
74
81
|
node running until it fires (a 4 s one made the run 6 s for 2 s of work). One file:
|
|
75
82
|
`node --test spec/web/public/NAME.test.js`.
|
|
76
83
|
|
|
77
|
-
Where a composer line goes is decided twice, in Ruby (`ComposerRoute`,
|
|
84
|
+
Where a composer line goes is decided twice, in Ruby (`ComposerRoute`, the attached terminal) and in the web
|
|
78
85
|
(`composer.js#routeComposerLine`). `spec/fixtures/composer_routes.json` holds the cases both answer: rspec
|
|
79
86
|
(`spec/composer_route_spec.rb`) and `npm test` (`spec/web/public/composer.test.js`) run every row. A change to
|
|
80
|
-
either router adds its row there; a row where a UI differs on purpose (or a found divergence) carries a `web`
|
|
81
|
-
|
|
87
|
+
either router adds its row there; a row where a UI differs on purpose (or a found divergence) carries a `web`
|
|
88
|
+
override and a `note`. The Ruby side runs each row with every core command (a new one fails its
|
|
82
89
|
`CORE_ROUTES` check until listed there); a row's names that core lacks stand for plugin commands.
|
|
83
90
|
|
|
84
91
|
## Levels
|
|
@@ -174,6 +181,9 @@ whose value is dropped, an example's last line included, `expect(wait_until {
|
|
|
174
181
|
`Timeout.timeout`, or a statement `thread.join(N)`, whose N is unscaled and whose nil is dropped: use `join!`) live in `rubocop/` and run in `rake lint`, so CI fails on them. RSpec counts `wait_for`,
|
|
175
182
|
`eventually`, `join!` and `pop!` as an example's expectation (`RSpec/NoExpectationExample`), so a wait can be an
|
|
176
183
|
example's last line. What the cops can't see, `script/affected-specs` hints at (`timing:`, below).
|
|
184
|
+
One lib cop sits beside them: `Samagotchi/SilentRescue` (a `rescue` of StandardError whose body is only a literal,
|
|
185
|
+
`nil`, `[]`…: use `Degrade`, or tag a right silence `# degrade-ok: why`; docs/internals/engine.md, Degrading). The
|
|
186
|
+
files that had such rescues when it came are excluded in `.rubocop.yml`: that list only shrinks.
|
|
177
187
|
|
|
178
188
|
### Flakes
|
|
179
189
|
|
|
@@ -308,8 +318,8 @@ A builder runs these while working, not the full suite: the specs the change tou
|
|
|
308
318
|
files it changed (`lib/samagotchi/foo/bar.rb` → `spec/foo/bar_spec.rb`, `bar_*_spec.rb`…;
|
|
309
319
|
`lib/samagotchi/foo/bar/baz.rb` → `spec/foo/bar_baz_spec.rb`), the specs that read
|
|
310
320
|
docs or source as text (its `RULES`: add one when a full run catches what the names missed) and the golden of a changed
|
|
311
|
-
file's layer (any `
|
|
312
|
-
words the
|
|
321
|
+
file's layer (any `foo/*.rb` → `spec/foo_golden_spec.rb` when there is one; `turn_flow.rb` and `turn_note.rb`, whose
|
|
322
|
+
words the attached TUI prints, pick its `event_renderer` and `attached_loop` specs by a rule). Changes to `tool_declarations.rb` and `system_prompt.rb` also pick the
|
|
313
323
|
effective-model, request, prompt-snapshot and full system-prompt goldens because they hash the request's prompt and
|
|
314
324
|
tools. Then come the other name matches (a spec named after several changed files first, e.g. `engine_toolkit_spec.rb` for `engine.rb` and
|
|
315
325
|
`toolkit.rb`; then one beside the file, so `plugin/context.rb` picks `spec/samagotchi/plugin/*context*` before
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "model_profile"
|
|
4
|
+
require_relative "output_formatter"
|
|
5
|
+
require_relative "tool_call_parser"
|
|
6
|
+
|
|
7
|
+
module Samagotchi
|
|
8
|
+
# What the user saw of a model entry's text: the one rule for the continue
|
|
9
|
+
# note's last answer (SessionChain.last_answer), a check's record of the
|
|
10
|
+
# agent's own words (ObjectivesCheck) and a bundle's ctx.last_answer. A chat
|
|
11
|
+
# entry's content is that already (its reasoning is the separate :thinking,
|
|
12
|
+
# its calls :tool_calls); a native entry's is the raw response, with its
|
|
13
|
+
# thoughts and call markup inline: both hosts' thoughts come out, a thought
|
|
14
|
+
# cut off before it closed too, then the calls and the wire tokens.
|
|
15
|
+
module AnswerText
|
|
16
|
+
QWEN_THINK = %r{<think>.*?</think>}m
|
|
17
|
+
# The template opened the block, so the text starts inside it.
|
|
18
|
+
TEMPLATE_OPENED_THINK = %r{\A.*?</think>}m
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# @param text [String, nil]
|
|
23
|
+
# @return [String] "" when nothing was visible
|
|
24
|
+
def visible(text)
|
|
25
|
+
text = text.to_s.scrub
|
|
26
|
+
template_opened = !text.include?("<think>")
|
|
27
|
+
gemma = ToolCallParser::Gemma.new(ModelProfile.gemma4)
|
|
28
|
+
text = gemma.strip_thought(text).gsub(QWEN_THINK, "")
|
|
29
|
+
text = text.sub(TEMPLATE_OPENED_THINK, "") if template_opened
|
|
30
|
+
text = ToolCallParser.strip_calls(gemma.strip_tool_calls(text), "<tool_call>", "</tool_call>")
|
|
31
|
+
OutputFormatter.strip_markup(text) # drops a thought cut off before it closed
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/samagotchi/bridge.rb
CHANGED
|
@@ -9,6 +9,7 @@ require "openssl"
|
|
|
9
9
|
require "time"
|
|
10
10
|
require_relative "atomic_file"
|
|
11
11
|
require_relative "bridge_secret"
|
|
12
|
+
require_relative "degrade"
|
|
12
13
|
require_relative "delivery"
|
|
13
14
|
require_relative "session_inbox"
|
|
14
15
|
require_relative "turn_anchors"
|
|
@@ -156,6 +157,9 @@ module Samagotchi
|
|
|
156
157
|
# Open streams by the client_id a stream named (`?client_id=`); web
|
|
157
158
|
# tabs, through the `chi web` proxy, name none.
|
|
158
159
|
@streams_by_client = Hash.new(0)
|
|
160
|
+
# Streams served since the start, open or closed (a scratch worker
|
|
161
|
+
# leaves once its terminal's has gone: Worker#scratch_unattended?).
|
|
162
|
+
@streams_served = 0
|
|
159
163
|
@last_client_activity_at = monotonic_now
|
|
160
164
|
end
|
|
161
165
|
|
|
@@ -178,6 +182,11 @@ module Samagotchi
|
|
|
178
182
|
@mutex.synchronize { @open_streams }
|
|
179
183
|
end
|
|
180
184
|
|
|
185
|
+
# @return [Integer] SSE streams served since the Bridge started
|
|
186
|
+
def streams_served
|
|
187
|
+
@mutex.synchronize { @streams_served }
|
|
188
|
+
end
|
|
189
|
+
|
|
181
190
|
# Streams held by anyone but +client_id+: a client's own stream (or two,
|
|
182
191
|
# while it reconnects and the old one isn't noticed dead yet) doesn't
|
|
183
192
|
# count. A closed stream stops counting once its writer reads the
|
|
@@ -236,7 +245,7 @@ module Samagotchi
|
|
|
236
245
|
@mutex.synchronize { @stopped = true }
|
|
237
246
|
begin
|
|
238
247
|
@server&.close
|
|
239
|
-
rescue StandardError
|
|
248
|
+
rescue StandardError # degrade-ok: stopping: a server socket already closed is fine
|
|
240
249
|
nil
|
|
241
250
|
end
|
|
242
251
|
wake_acceptor
|
|
@@ -254,7 +263,7 @@ module Samagotchi
|
|
|
254
263
|
@mutex.synchronize { @stopped = true }
|
|
255
264
|
begin
|
|
256
265
|
@server&.close
|
|
257
|
-
rescue StandardError
|
|
266
|
+
rescue StandardError # degrade-ok: stopping: a server socket already closed is fine
|
|
258
267
|
nil
|
|
259
268
|
end
|
|
260
269
|
wake_acceptor
|
|
@@ -268,13 +277,13 @@ module Samagotchi
|
|
|
268
277
|
# the web, which then queues the prompt again from the input file.
|
|
269
278
|
await_answers(REQUEST_GRACE_SECONDS)
|
|
270
279
|
remove_sidecar
|
|
271
|
-
@connection_threads.each { |t| t.kill rescue nil }
|
|
280
|
+
@connection_threads.each { |t| t.kill rescue nil } # degrade-ok: stopping: a thread already gone is fine
|
|
272
281
|
@connection_threads.clear
|
|
273
282
|
# Its relay watchers (RelayWatcher) end with it.
|
|
274
283
|
watchers = @mutex.synchronize { (@relay_watchers || []).tap { @relay_watchers = [] } }
|
|
275
284
|
watchers.each { |thread| thread.kill.join(RELAY_WATCHER_JOIN_SECONDS) }
|
|
276
285
|
@accept_thread&.join(2)
|
|
277
|
-
[@wake_reader, @wake_writer].each { |io| io&.close rescue nil }
|
|
286
|
+
[@wake_reader, @wake_writer].each { |io| io&.close rescue nil } # degrade-ok: stopping: a closed pipe is fine
|
|
278
287
|
nil
|
|
279
288
|
end
|
|
280
289
|
|
|
@@ -399,9 +408,10 @@ module Samagotchi
|
|
|
399
408
|
# O(1) and non-blocking.
|
|
400
409
|
def capture_observer
|
|
401
410
|
@capture_proc ||= proc do |event|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
411
|
+
# Per event: the first failure warns, the rest log at debug.
|
|
412
|
+
Degrade.call(:bridge, "event_capture_failed") do
|
|
413
|
+
@ring.push(seq: event[:event_seq], data: event)
|
|
414
|
+
end
|
|
405
415
|
end
|
|
406
416
|
end
|
|
407
417
|
|
|
@@ -527,7 +537,7 @@ module Samagotchi
|
|
|
527
537
|
Thread.current[:bridge_answering] = false
|
|
528
538
|
begin
|
|
529
539
|
io.close
|
|
530
|
-
rescue StandardError
|
|
540
|
+
rescue StandardError # degrade-ok: the client may be gone already; closing is all that's left
|
|
531
541
|
nil
|
|
532
542
|
end
|
|
533
543
|
end
|
|
@@ -555,6 +565,7 @@ module Samagotchi
|
|
|
555
565
|
)
|
|
556
566
|
@mutex.synchronize do
|
|
557
567
|
@open_streams += 1
|
|
568
|
+
@streams_served += 1
|
|
558
569
|
@streams_by_client[client_id] += 1 if client_id
|
|
559
570
|
end
|
|
560
571
|
begin
|
|
@@ -860,7 +871,7 @@ module Samagotchi
|
|
|
860
871
|
end
|
|
861
872
|
end
|
|
862
873
|
|
|
863
|
-
# Dismiss the pending question (an empty answer
|
|
874
|
+
# Dismiss the pending question (an empty answer at a terminal): the
|
|
864
875
|
# tool returns without an answer and every UI gets :question_cancelled.
|
|
865
876
|
# Only the question the client saw, and only while nobody has answered
|
|
866
877
|
# it: Engine#cancel_question checks both under the question lock. A past
|
|
@@ -1425,44 +1436,44 @@ module Samagotchi
|
|
|
1425
1436
|
# else fall back to the explicit `?from_seq=` query param used by clients
|
|
1426
1437
|
# that remember their own cursor.
|
|
1427
1438
|
def reconnect_cursor(headers, query)
|
|
1428
|
-
|
|
1429
|
-
|
|
1439
|
+
Degrade.call(:bridge, "sse_cursor_unparsable") do
|
|
1440
|
+
cursor = headers["last-event-id"]
|
|
1441
|
+
return cursor if cursor && !cursor.empty?
|
|
1430
1442
|
|
|
1431
|
-
|
|
1443
|
+
return nil unless query
|
|
1432
1444
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
nil
|
|
1445
|
+
URI.decode_www_form(query).to_h["from_seq"]
|
|
1446
|
+
end
|
|
1436
1447
|
end
|
|
1437
1448
|
|
|
1438
1449
|
# `?snapshot=1`: join with a snapshot frame instead of a replay.
|
|
1439
1450
|
def snapshot_requested?(query)
|
|
1440
|
-
|
|
1451
|
+
Degrade.call(:bridge, "snapshot_query_unparsable", fallback: false) do
|
|
1452
|
+
return false unless query
|
|
1441
1453
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
false
|
|
1454
|
+
URI.decode_www_form(query).to_h["snapshot"].to_s == "1"
|
|
1455
|
+
end
|
|
1445
1456
|
end
|
|
1446
1457
|
|
|
1447
1458
|
# `?raw=1`: events as the engine emitted them (a chunk's provider
|
|
1448
1459
|
# payload), not as clients get them (ClientEvent). For debugging on the
|
|
1449
1460
|
# worker's port: chi web never passes it on.
|
|
1450
1461
|
def raw_requested?(query)
|
|
1451
|
-
|
|
1462
|
+
Degrade.call(:bridge, "raw_query_unparsable", fallback: false) do
|
|
1463
|
+
return false unless query
|
|
1452
1464
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
false
|
|
1465
|
+
URI.decode_www_form(query).to_h["raw"].to_s == "1"
|
|
1466
|
+
end
|
|
1456
1467
|
end
|
|
1457
1468
|
|
|
1458
1469
|
# `?client_id=`: whose stream it is (see #open_streams_except).
|
|
1459
1470
|
def stream_client_id(query)
|
|
1460
|
-
|
|
1471
|
+
Degrade.call(:bridge, "client_id_query_unparsable") do
|
|
1472
|
+
return nil unless query
|
|
1461
1473
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
nil
|
|
1474
|
+
id = URI.decode_www_form(query).to_h["client_id"].to_s
|
|
1475
|
+
id.empty? ? nil : id
|
|
1476
|
+
end
|
|
1466
1477
|
end
|
|
1467
1478
|
|
|
1468
1479
|
def close_after_request?(headers)
|
|
@@ -1534,11 +1545,11 @@ module Samagotchi
|
|
|
1534
1545
|
# same session may have written its own since (a file without an epoch
|
|
1535
1546
|
# is only ever an older worker's).
|
|
1536
1547
|
def remove_sidecar
|
|
1537
|
-
|
|
1548
|
+
Degrade.call(:bridge, "sidecar_remove_failed") do
|
|
1549
|
+
return unless @server
|
|
1538
1550
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
nil
|
|
1551
|
+
File.unlink(WorkerSidecar.path(session_dir)) if WorkerSidecar.read(session_dir)&.epoch == @epoch.to_s
|
|
1552
|
+
end
|
|
1542
1553
|
end
|
|
1543
1554
|
|
|
1544
1555
|
def session_dir
|
|
@@ -4,6 +4,7 @@ require "digest"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "socket"
|
|
6
6
|
|
|
7
|
+
require_relative "degrade"
|
|
7
8
|
require_relative "worker_sidecar"
|
|
8
9
|
require_relative "bridge_secret"
|
|
9
10
|
require_relative "delivery"
|
|
@@ -329,17 +330,17 @@ module Samagotchi
|
|
|
329
330
|
body = response.split("\r\n\r\n", 2)[1] || ""
|
|
330
331
|
Response.new(status: status.to_i, body: body)
|
|
331
332
|
ensure
|
|
332
|
-
sock&.close rescue nil
|
|
333
|
+
sock&.close rescue nil # degrade-ok: done with the socket; one already closed is fine
|
|
333
334
|
end
|
|
334
335
|
|
|
335
336
|
# Monotonic SSE cursor of the live Engine, from GET /session/:id/state.
|
|
336
337
|
# @return [Integer, nil]
|
|
337
338
|
def event_seq
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
339
|
+
Degrade.call(:bridge, "event_seq_unreadable") do
|
|
340
|
+
state = get_json("state")
|
|
341
|
+
seq = state && state.dig("session_state_snapshot", "event_seq")
|
|
342
|
+
seq.nil? ? nil : seq.to_i
|
|
343
|
+
end
|
|
343
344
|
end
|
|
344
345
|
|
|
345
346
|
# Open GET /session/:id/stream and classify the response before the
|
|
@@ -358,7 +359,7 @@ module Samagotchi
|
|
|
358
359
|
if connection
|
|
359
360
|
sock, status, headers = connection
|
|
360
361
|
unless !block_given? || yield(sock)
|
|
361
|
-
sock.close rescue nil
|
|
362
|
+
sock.close rescue nil # degrade-ok: the stream is given up; a socket already closed is fine
|
|
362
363
|
return StreamOpen::Unreachable.new(attempts: count)
|
|
363
364
|
end
|
|
364
365
|
return StreamOpen.classify(sock, status, headers)
|
|
@@ -389,7 +390,7 @@ module Samagotchi
|
|
|
389
390
|
break
|
|
390
391
|
end
|
|
391
392
|
ensure
|
|
392
|
-
sock&.close rescue nil
|
|
393
|
+
sock&.close rescue nil # degrade-ok: done with the socket; one already closed is fine
|
|
393
394
|
end
|
|
394
395
|
end
|
|
395
396
|
|
|
@@ -435,7 +436,7 @@ module Samagotchi
|
|
|
435
436
|
body = read_body ? reply.split("\r\n\r\n", 2)[1] : nil
|
|
436
437
|
Response.new(status: reply[STATUS_LINE, 1].to_i, body: body)
|
|
437
438
|
ensure
|
|
438
|
-
sock&.close rescue nil
|
|
439
|
+
sock&.close rescue nil # degrade-ok: done with the socket; one already closed is fine
|
|
439
440
|
end
|
|
440
441
|
|
|
441
442
|
# The reply up to the Bridge's close (or its first line only), within
|
|
@@ -12,7 +12,7 @@ module Samagotchi
|
|
|
12
12
|
# A session a broadcast may concern.
|
|
13
13
|
# @!attribute project [String, nil] its git project's root
|
|
14
14
|
# @!attribute live [Boolean] a worker runs it now
|
|
15
|
-
# @!attribute owner [String, nil] "worker", "tui" (a chi
|
|
15
|
+
# @!attribute owner [String, nil] "worker", "tui" (a one-turn chi run, which
|
|
16
16
|
# takes no notes) or nil
|
|
17
17
|
Recipient = Data.define(:id, :short_id, :project, :cwd, :desc, :live, :owner) do
|
|
18
18
|
def repl? = owner == "tui"
|
|
@@ -20,7 +20,7 @@ module Samagotchi
|
|
|
20
20
|
|
|
21
21
|
# Who a broadcast goes to: the user's own sessions, not delegate
|
|
22
22
|
# children (their parent relays what concerns them) and not scratch
|
|
23
|
-
# ones, that a worker or a chi
|
|
23
|
+
# ones, that a worker or a one-turn chi run runs now or that ended a turn in
|
|
24
24
|
# the last +active_hours+. Forks count: they are the user's sessions.
|
|
25
25
|
# Archived sessions don't; test runs only when chi runs as one.
|
|
26
26
|
module Recipients
|
|
@@ -23,7 +23,7 @@ module Samagotchi
|
|
|
23
23
|
# @!attribute by [String] who decided: "tags" (a shared tag),
|
|
24
24
|
# "model", "fallback" (delivered unchecked: deadline, error, an
|
|
25
25
|
# answer that is no yes or no), "scope" (the note's first line
|
|
26
|
-
# names another project), "all" (--all) or "repl" (a chi
|
|
26
|
+
# names another project), "all" (--all) or "repl" (a one-turn chi run owns
|
|
27
27
|
# it: no notes)
|
|
28
28
|
# @!attribute match [Match, nil] the shared tag (by "tags")
|
|
29
29
|
Verdict = Data.define(:relevant, :p, :reason, :by, :match) do
|
|
@@ -46,7 +46,7 @@ module Samagotchi
|
|
|
46
46
|
note from "broadcast": background the model sees on its next turn,
|
|
47
47
|
not a prompt. It starts no turn.
|
|
48
48
|
Recipients: your own sessions (not delegate children, not scratch
|
|
49
|
-
ones) that a worker or a chi
|
|
49
|
+
ones) that a worker or a one-turn chi run runs now, or that ended a turn in
|
|
50
50
|
the last broadcast.active_hours (8). One gets the note when it shares
|
|
51
51
|
a tag with it: a ticket id (PAY-123) in its branch or your prompts
|
|
52
52
|
there, a pull request (PR #42, its URL) attached to it, a link in
|
|
@@ -190,13 +190,13 @@ module Samagotchi
|
|
|
190
190
|
corrections: []), decisions, cards)
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
-
# Every recipient's verdict: a chi
|
|
193
|
+
# Every recipient's verdict: a one-turn chi run takes no notes, --all takes the
|
|
194
194
|
# rest, else Broadcast::Triage (tags, the scope line, the model).
|
|
195
195
|
# @return [Array<Decision>] in +recipients+' order
|
|
196
196
|
def decide(text, recipients, cards, note_tags, all:)
|
|
197
197
|
fixed = recipients.to_h do |r|
|
|
198
198
|
verdict = if r.repl?
|
|
199
|
-
Broadcast::Triage::Verdict.new(relevant: false, p: nil, reason:
|
|
199
|
+
Broadcast::Triage::Verdict.new(relevant: false, p: nil, reason: OwnerLock::TUI_BUSY, by: "repl")
|
|
200
200
|
elsif all
|
|
201
201
|
Broadcast::Triage::Verdict.new(relevant: true, p: nil, reason: "--all", by: "all")
|
|
202
202
|
end
|
|
@@ -254,7 +254,7 @@ module Samagotchi
|
|
|
254
254
|
|
|
255
255
|
result = NoteDelivery.deliver(d.recipient.id, text: body(broadcast.text, d.verdict), source: SOURCE,
|
|
256
256
|
state_dir: @state_dir)
|
|
257
|
-
next [d, "skipped",
|
|
257
|
+
next [d, "skipped", OwnerLock::TUI_BUSY] unless result.delivered?
|
|
258
258
|
|
|
259
259
|
[d, "delivered", "#{d.verdict.reason}#{"; waits for its next start" if result.status == :waits}"]
|
|
260
260
|
rescue SessionInbox::NoteRejected, SystemCallError => e
|
|
@@ -369,7 +369,7 @@ module Samagotchi
|
|
|
369
369
|
|
|
370
370
|
result = NoteDelivery.deliver(id, text: passed_on_body(record), source: SOURCE, state_dir: @state_dir)
|
|
371
371
|
unless result.delivered?
|
|
372
|
-
@stdout.puts(line_for(id, "skipped",
|
|
372
|
+
@stdout.puts(line_for(id, "skipped", OwnerLock::TUI_BUSY))
|
|
373
373
|
return Broadcast::TriageLog::Delivery.new(session: id, result: "refused")
|
|
374
374
|
end
|
|
375
375
|
|
|
@@ -42,7 +42,7 @@ module Samagotchi
|
|
|
42
42
|
f.switch "--json"
|
|
43
43
|
f.switch "--keep"
|
|
44
44
|
f.value "--timeout"
|
|
45
|
-
f.refuse "--attach", "
|
|
45
|
+
f.refuse "--attach", "chi bundle try can't use --attach"
|
|
46
46
|
end
|
|
47
47
|
DEFAULT_TIMEOUT = 300
|
|
48
48
|
# The sandbox's cost-cap bundle setting (--set cost-cap.cap=N).
|
|
@@ -30,7 +30,7 @@ module Samagotchi
|
|
|
30
30
|
|
|
31
31
|
def run(prompt:, options:, timeout:)
|
|
32
32
|
command = [RbConfig.ruby, "-I", Sandbox::LIB_DIR, Sandbox::CHI,
|
|
33
|
-
"--
|
|
33
|
+
"--non-interactive", "-p", prompt, *child_options(options)]
|
|
34
34
|
trap_state, old_traps = trap_forwarding
|
|
35
35
|
pid = nil
|
|
36
36
|
streams = []
|
|
@@ -367,8 +367,8 @@ module Samagotchi
|
|
|
367
367
|
prompt = build_conflict_prompt(bundle_name, installer.conflicts, expanded_source)
|
|
368
368
|
before = installer.conflicts.to_h { |key, info| [key, Samagotchi::MemoryBundle::Merger.digest(info[:current])] }
|
|
369
369
|
@stdout.puts "Launching an interactive session to merge them… (/exit when done)"
|
|
370
|
-
require "samagotchi/terminal_ui"
|
|
371
|
-
Samagotchi::TerminalUI.
|
|
370
|
+
require "samagotchi/terminal_ui/attach_launcher"
|
|
371
|
+
Samagotchi::TerminalUI::AttachLauncher.run(prompt: prompt)
|
|
372
372
|
settle_conflicts(installer.conflicts, before, bundle_name)
|
|
373
373
|
ensure
|
|
374
374
|
# An owned source (git/zip/tar) was kept for the incoming files above;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: decision-card
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.1
|
|
4
4
|
scope: system
|
|
5
5
|
description: "In a session with objectives, a long answer ending in a question gets a card: a short summary and the question, answered in the composer"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
|
-
sha256: sha256:
|
|
10
|
-
# ctx.
|
|
11
|
-
requires_chi: ">= 0.
|
|
9
|
+
sha256: sha256:b85fd2162e41d1ea059ff6ca89ce6bb543e4f283d18337e9d4ac6936477d3770
|
|
10
|
+
# ctx.last_answer is new after 0.153.0 (ctx.objectives, ctx.turn_running? after 0.148.0)
|
|
11
|
+
requires_chi: ">= 0.154.0"
|
|
12
12
|
# config.yml bundles: decision-card: (another key warns; an older chi ignores this)
|
|
13
13
|
settings: [min_chars, tail_chars, model, timeout, max_tokens]
|
|
@@ -135,7 +135,7 @@ class Plugin
|
|
|
135
135
|
return SHORT_BUSY if ctx.turn_running?
|
|
136
136
|
|
|
137
137
|
messages = ctx.messages
|
|
138
|
-
answer = last_answer
|
|
138
|
+
answer = ctx.last_answer or return "no answer to shorten yet"
|
|
139
139
|
|
|
140
140
|
id = ctx.card(title: SHORT_TITLE, body: "…")
|
|
141
141
|
@mutex.synchronize { @shorts[id] = ctx }
|
|
@@ -158,14 +158,6 @@ class Plugin
|
|
|
158
158
|
ctx.card(id: id, title: SHORT_TITLE, body: body)
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
-
# @return [String, nil] the newest non-empty answer's text
|
|
162
|
-
def last_answer(messages)
|
|
163
|
-
found = Array(messages).reverse_each.find do |message|
|
|
164
|
-
%w[model assistant].include?(field(message, :role).to_s) && !field(message, :content).to_s.strip.empty?
|
|
165
|
-
end
|
|
166
|
-
found && field(found, :content).to_s
|
|
167
|
-
end
|
|
168
|
-
|
|
169
161
|
def turn_ended(event, ctx)
|
|
170
162
|
return unless event[:status].to_s == "completed"
|
|
171
163
|
# A participant's question is for the participant, on the owner's model; a
|
|
@@ -18,7 +18,7 @@ Single registry `Samagotchi::Config` (`Config::ENTRIES` in `lib/samagotchi/confi
|
|
|
18
18
|
|
|
19
19
|
Sections forbid `_`/`-` (`SECTION_RE` `/\A[a-z0-9]+\z/`), since an env or CLI name is derived from them; a config-only section (`system_one`: `expose: [:config]`, no derived name) may carry `_`. Leaves keep `snake_case` in YAML (`base_url`) and become kebab in CLI (`base-url`) via registry derivation — no generic string split, registry lookup avoids flat vs nested collision.
|
|
20
20
|
|
|
21
|
-
**Universal entries** (`expose: [:env,:config,:cli]`): `default.model`, `server.host/port/open_timeout/read_timeout`, `server.first_token_timeout` (env and config only), `recap.model/base_url/host_ref/inactivity/timeout/min_user_turns/sentences`, `session.retention_days/max_count/keep_status/sweep_interval_hours/idle_exit_minutes`, `session.
|
|
21
|
+
**Universal entries** (`expose: [:env,:config,:cli]`): `default.model`, `server.host/port/open_timeout/read_timeout`, `server.first_token_timeout` (env and config only), `recap.model/base_url/host_ref/inactivity/timeout/min_user_turns/sentences`, `session.retention_days/max_count/keep_status/sweep_interval_hours/idle_exit_minutes`, `session.keep_empty/max_children` (env and config only), `log.file/disable`, `status.line`, `context.status/window_tokens/chars_per_token/status_thresholds`, `memory.index_warn_tokens/entry_warn_tokens/read_max_chars`, `default.max_tokens`, `max_tool_output_chars`, `retry.max/base_delay/max_delay`, `read.*`, `execute.*`, `web.port/host`, `no_interrupt`, `no_default_input` etc. (`Config::ENTRIES`; `expose` says which of env/config/cli each takes). Precedence is `CLI > ENV > file > default`.
|
|
22
22
|
|
|
23
23
|
Example `config.yml` (new nested form, preferred):
|
|
24
24
|
|
|
@@ -40,10 +40,9 @@ recap:
|
|
|
40
40
|
session:
|
|
41
41
|
retention_days: 14
|
|
42
42
|
max_count: 500
|
|
43
|
-
keep_status: "" # the default: no status protects a session from pruning; a live worker or
|
|
43
|
+
keep_status: "" # the default: no status protects a session from pruning; a live worker or a one-turn run always does
|
|
44
44
|
sweep_interval_hours: 24
|
|
45
45
|
idle_exit_minutes: 30 # a background worker nobody uses exits; 0 = never
|
|
46
|
-
shared: true # the default: plain `chi` runs its session in a background worker and attaches (as `chi --shared`); false keeps the in-process REPL (env SAMAGOTCHI_SESSION_SHARED; no CLI flag, `--no-shared` opts out per run)
|
|
47
46
|
keep_empty: false # the default: a session nothing happened in (no prompt, default model, no note/image) is deleted when left; true keeps it (env SAMAGOTCHI_SESSION_KEEP_EMPTY)
|
|
48
47
|
max_children: 4 # running child sessions one session may have delegated at a time (the delegate tool; env SAMAGOTCHI_SESSION_MAX_CHILDREN)
|
|
49
48
|
log:
|
|
@@ -77,7 +76,7 @@ guardrails:
|
|
|
77
76
|
disable: [guardrails:git-rebase]
|
|
78
77
|
```
|
|
79
78
|
|
|
80
|
-
- `bundles:` map of installed bundle name → its settings (one Hash, string keys, handed to the bundle's hook/plugin `initialize(settings)`). Read when a session's Engine starts: after a change restart the session's worker (`chi sessions stop <id>`)
|
|
79
|
+
- `bundles:` map of installed bundle name → its settings (one Hash, string keys, handed to the bundle's hook/plugin `initialize(settings)`). Read when a session's Engine starts: after a change restart the session's worker (`chi sessions stop <id>`). Unknown keys are ignored by the bundle, not validated. `chi bundle list` names the bundles; each bundle's keys are in `docs/bundles.md` / `docs/guardrails.md`.
|
|
81
80
|
|
|
82
81
|
```yaml
|
|
83
82
|
bundles:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: samagotchi-system
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.154.0
|
|
4
4
|
scope: system
|
|
5
5
|
description: Default system memories — identity, self map, config modification protocol, memory guide and the delegated-session rules
|
|
6
6
|
files:
|
|
7
7
|
identity.md: sha256:29c2f7df9f6c5acbc5ec17000a4490741e55e426af15ca0d81c8706edd9bd027
|
|
8
|
-
self_map.md: sha256:
|
|
9
|
-
config_modification_protocol.md: sha256:
|
|
8
|
+
self_map.md: sha256:709bd52710bf6c50334a89a10742ea1554c5985cc2d7c4e7571e526cc4479526
|
|
9
|
+
config_modification_protocol.md: sha256:f3f6545c93d59b624a2bf1766e26059520ffc7b236470dd9f1b51ab77312376d
|
|
10
10
|
memory_guide.md: sha256:8b7727b939b9c04486ca59859561023dcc319a73e0f5b47b3fcf5741edaa69e2
|
|
11
11
|
delegated.md: sha256:a0d6ddcd4a8be06526d964898611775fd251cfd9d145a9d16afaccaa503597bd
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
`docs/bundle-authoring.md` in my source dir first.
|
|
68
68
|
|
|
69
69
|
## Where things live (relative to the source dir)
|
|
70
|
-
- `docs/`: `cli.md` (flags, subcommands,
|
|
70
|
+
- `docs/`: `cli.md` (flags, subcommands, the terminal's commands), `configuration.md` (config.yml keys,
|
|
71
71
|
hosts, the server api), `hooks.md` (hook events table), `plugins.md` (plugin API), `bundles.md` (each shipped bundle: btw, mcp, cost-cap…),
|
|
72
72
|
`guardrails.md`, `memory.md`, `sessions.md`. `README.md` is only the quickstart.
|
|
73
73
|
- `lib/samagotchi/tools/<tool>.rb`: each tool's real limits and defaults (e.g. `execute.rb`,
|
|
@@ -47,7 +47,7 @@ module Samagotchi
|
|
|
47
47
|
# What a continue does with the previous link's children: +move+ the
|
|
48
48
|
# direct children whose subtree has an open node (they go to the new
|
|
49
49
|
# link, their subtrees with them), +refuse+ the open ones it can't move
|
|
50
|
-
# (SessionContinue::OpenChild, why: an older chi worker or a chi
|
|
50
|
+
# (SessionContinue::OpenChild, why: an older chi worker or a one-turn chi run).
|
|
51
51
|
Plan = Data.define(:move, :refuse)
|
|
52
52
|
# A move under way: from the session, the children moved, and those
|
|
53
53
|
# already sent their note (so a move finished again notes no one twice).
|
|
@@ -87,14 +87,14 @@ module Samagotchi
|
|
|
87
87
|
Plan.new(move: move, refuse: refuse)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
# Why a child's live owner keeps it from moving: a chi
|
|
90
|
+
# Why a child's live owner keeps it from moving: a one-turn chi run holds its
|
|
91
91
|
# own Session and never reads the override; a worker on an older chi
|
|
92
92
|
# neither. A worker with no sidecar yet is starting, on the newest chi
|
|
93
93
|
# installed (SessionManager.worker_command): it moves.
|
|
94
94
|
# @return [String, nil]
|
|
95
95
|
def unmovable(child_id, state_dir:)
|
|
96
96
|
link = WorkerLink.resolve(Session.session_dir(child_id, state_dir: state_dir), unlink: false)
|
|
97
|
-
return "
|
|
97
|
+
return "#{OwnerLock::TUI_BUSY}; let it finish" if link.owner&.tui?
|
|
98
98
|
return nil if !link.live? || link.features.include?(FEATURE)
|
|
99
99
|
|
|
100
100
|
"older chi worker#{" #{link.version}" if link.version}; restart it"
|
|
@@ -29,9 +29,9 @@ module Samagotchi
|
|
|
29
29
|
# @!attribute mid_turn [Symbol, #call] what a line of it does while a
|
|
30
30
|
# turn runs (MID_TURN): a symbol, or a lambda line (stripped) → one
|
|
31
31
|
# @!attribute local [Boolean] the UI runs it; completion and help only
|
|
32
|
-
# @!attribute uis [Array<Symbol>, nil] a local entry's UIs (:
|
|
33
|
-
# :
|
|
34
|
-
#
|
|
32
|
+
# @!attribute uis [Array<Symbol>, nil] a local entry's UIs (:attached,
|
|
33
|
+
# :web) for completion and /help; nil means every UI (for a local
|
|
34
|
+
# entry, the terminal)
|
|
35
35
|
# @!attribute match [#call] line (stripped) → whether it is this command
|
|
36
36
|
# @!attribute handler [Proc, nil] runs it; SessionCommands instance_execs
|
|
37
37
|
# a built-in's with the line, and calls a bundle's with the text
|
|
@@ -165,8 +165,8 @@ module Samagotchi
|
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
# @return [Entry, nil] the first entry the UI runs itself (local) that
|
|
168
|
-
# matches +line+, whatever its uis (a UI answers the others' too
|
|
169
|
-
#
|
|
168
|
+
# matches +line+, whatever its uis (a UI answers the others' too,
|
|
169
|
+
# with a note)
|
|
170
170
|
def lookup_local(line)
|
|
171
171
|
text = line.to_s.strip
|
|
172
172
|
@entries.find { |entry| entry.local && entry.match?(text) }
|
|
@@ -190,7 +190,7 @@ module Samagotchi
|
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
-
# @param ui [Symbol] :
|
|
193
|
+
# @param ui [Symbol] :attached
|
|
194
194
|
# @return [Array<String>] the /names Tab offers in +ui+, sorted
|
|
195
195
|
def completions(ui)
|
|
196
196
|
@entries.select { |entry| entry.slash? && entry.in_ui?(ui) }.map(&:name).uniq.sort
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
require_relative "delivery"
|
|
4
4
|
|
|
5
5
|
module Samagotchi
|
|
6
|
-
# Where a line typed at a terminal's prompt goes
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# attached TUI's continue offer and question). The web's composer.js
|
|
6
|
+
# Where a line typed at a terminal's prompt goes (AttachedLoop#submit; a
|
|
7
|
+
# -p prompt's /cut or /queue: TerminalUI#run). It only classifies; each
|
|
8
|
+
# UI keeps what it does with the answer (the attached TUI's continue
|
|
9
|
+
# offer and question). The web's composer.js
|
|
11
10
|
# routeComposerLine decides the same; spec/fixtures/composer_routes.json
|
|
12
11
|
# holds the cases both answer (docs/testing.md).
|
|
13
12
|
#
|