samagotchi 0.71.0 → 0.73.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 +70 -1
- data/docs/architecture.md +15 -9
- data/docs/cli.md +12 -5
- data/docs/configuration.md +23 -13
- data/docs/hooks.md +5 -3
- data/docs/internals/anthropic.md +1 -1
- data/docs/internals/background-tasks.md +2 -2
- data/docs/internals/engine.md +38 -13
- data/docs/internals/llm-context-live.md +1 -1
- data/docs/internals/prompt-caching.md +6 -0
- data/docs/plugins.md +18 -7
- data/docs/sessions.md +5 -3
- data/docs/testing.md +10 -1
- data/lib/samagotchi/bootstrap/config_writer.rb +2 -3
- data/lib/samagotchi/bridge.rb +5 -2
- data/lib/samagotchi/broadcast/scope_card.rb +2 -2
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -7
- data/lib/samagotchi/bundles/claude-peer/plugin.rb +2 -2
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -6
- data/lib/samagotchi/bundles/codex-peer/plugin.rb +2 -2
- data/lib/samagotchi/bundles/coordinator/manifest.yml +8 -6
- data/lib/samagotchi/bundles/coordinator/plugin.rb +10 -0
- data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +2 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/config.rb +7 -25
- data/lib/samagotchi/context_note.rb +2 -6
- data/lib/samagotchi/effective_model.rb +1 -1
- data/lib/samagotchi/engine.rb +91 -213
- data/lib/samagotchi/engine_peers.rb +2 -0
- data/lib/samagotchi/event_routes.rb +83 -0
- data/lib/samagotchi/hook_routes.rb +16 -37
- data/lib/samagotchi/host_config.rb +1 -1
- data/lib/samagotchi/host_keys.rb +0 -2
- data/lib/samagotchi/paths.rb +13 -0
- data/lib/samagotchi/plugin/api.rb +11 -5
- data/lib/samagotchi/plugin/peer.rb +5 -8
- data/lib/samagotchi/prompt_files.rb +2 -4
- data/lib/samagotchi/session.rb +7 -1
- data/lib/samagotchi/session_chain.rb +24 -0
- data/lib/samagotchi/session_continue.rb +3 -1
- data/lib/samagotchi/session_manager.rb +6 -16
- data/lib/samagotchi/session_model_settings.rb +227 -0
- data/lib/samagotchi/session_setup.rb +2 -1
- data/lib/samagotchi/system_prompt.rb +18 -13
- data/lib/samagotchi/terminal_ui/attached_loop.rb +30 -14
- data/lib/samagotchi/terminal_ui/formatting.rb +4 -0
- data/lib/samagotchi/tool_activity.rb +1 -0
- data/lib/samagotchi/tool_declarations.rb +4 -0
- data/lib/samagotchi/toolkit.rb +55 -24
- data/lib/samagotchi/toolkit_binding.rb +7 -5
- data/lib/samagotchi/toolkit_command.rb +28 -5
- data/lib/samagotchi/tools/builtins.rb +2 -1
- data/lib/samagotchi/tools/delegate.rb +32 -6
- data/lib/samagotchi/tools/execute.rb +1 -15
- data/lib/samagotchi/tools/list_sessions.rb +2 -2
- data/lib/samagotchi/tools/peers.rb +5 -2
- data/lib/samagotchi/tools/register_reminder.rb +1 -0
- data/lib/samagotchi/tools/registry.rb +1 -1
- data/lib/samagotchi/tools/task_runtime.rb +69 -6
- data/lib/samagotchi/tools/tool_index.rb +15 -4
- data/lib/samagotchi/turn_note.rb +4 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +40 -13
- data/lib/samagotchi/web/public/app.js +53 -28
- data/lib/samagotchi/web/public/bubbles.js +5 -3
- data/lib/samagotchi/web/public/cards_place.js +6 -10
- data/lib/samagotchi/web/public/chains.js +22 -2
- data/lib/samagotchi/web/public/chip_popover.js +3 -1
- data/lib/samagotchi/web/public/composer.js +13 -6
- data/lib/samagotchi/web/public/context_chips.js +25 -12
- data/lib/samagotchi/web/public/data.js +14 -1
- data/lib/samagotchi/web/public/format.js +5 -0
- data/lib/samagotchi/web/public/index.html +39 -2
- data/lib/samagotchi/web/public/notice_links.js +55 -0
- data/lib/samagotchi/web/public/question_cards.js +33 -1
- data/lib/samagotchi/web/public/sessions_list.js +2 -3
- data/lib/samagotchi/web/public/stage_frame.js +1 -1
- data/lib/samagotchi/web/public/stage_slots.js +35 -3
- data/lib/samagotchi/web/public/stage_view.js +17 -6
- data/lib/samagotchi/web/public/stream_handlers.js +6 -3
- data/lib/samagotchi/web/public/timing.js +1 -1
- data/lib/samagotchi/web/public/toolkit_chip.js +3 -1
- data/lib/samagotchi/web/public/turn_view.js +3 -2
- data/lib/samagotchi/web/session_hub.rb +8 -0
- metadata +4 -1
data/docs/plugins.md
CHANGED
|
@@ -568,19 +568,25 @@ end
|
|
|
568
568
|
- Each task runs once per session start. A `-p … --non-interactive` run
|
|
569
569
|
starts them with its turn and shows nothing but the answer.
|
|
570
570
|
|
|
571
|
-
### `chi.toolkit(name, base:, add: [], remove: [])`
|
|
571
|
+
### `chi.toolkit(name, base:, add: [], remove: [], prompt: nil)`
|
|
572
572
|
|
|
573
573
|
A toolkit sessions can be put on: `base`'s tools (`full`, `lean`, `child`, a
|
|
574
|
-
config or bundle toolkit) with `add` declared and `remove` left out
|
|
574
|
+
config or bundle toolkit) with `add` declared and `remove` left out, and
|
|
575
|
+
`prompt`: text the sessions on it get in their system prompt, verbatim (nil
|
|
576
|
+
inherits the base's, `""` clears it; at most 2000 characters, and one that
|
|
577
|
+
isn't a String or is longer is a load error). Users pick
|
|
575
578
|
it like chi's own (`chi --toolkit NAME`, `toolkit.default`, `/toolkit NAME`),
|
|
576
579
|
and the plugin with `ctx.use_toolkit` (below). Names are `a-z`, `0-9`, `_` and
|
|
577
580
|
`-`; one chi has (`full`, `lean`, `child`) or another bundle declared is a
|
|
578
581
|
load error. A session saved on a toolkit whose bundle is gone declares every
|
|
579
582
|
tool (full) until it comes back, with a warning at its first turn. See
|
|
580
|
-
[Toolkits](configuration.md#toolkits).
|
|
583
|
+
[Toolkits](configuration.md#toolkits). `prompt:` needs chi 0.72.0 (an older
|
|
584
|
+
chi refuses the keyword, a load error: a bundle that passes it sets
|
|
585
|
+
`requires_chi: ">= 0.72.0"`).
|
|
581
586
|
|
|
582
587
|
```ruby
|
|
583
588
|
chi.toolkit "review", base: "lean", add: %w[delegate delegate_result]
|
|
589
|
+
chi.toolkit "coding", base: "child", prompt: "- Be concise in your responses"
|
|
584
590
|
```
|
|
585
591
|
|
|
586
592
|
### `chi.tool_source(call:, acts_as:, label:, wait:, prefix:)`
|
|
@@ -645,9 +651,10 @@ index = Samagotchi::Tools::ToolIndex
|
|
|
645
651
|
([acts_as:](#guardrails)), even when another bundle has a tool of that
|
|
646
652
|
name (only the core-tool drop applies). A call made before its tool is published is
|
|
647
653
|
named by `acts_as` from the names alone; a bare name with `prefix` is
|
|
648
|
-
taken as an `acts_as` as given
|
|
654
|
+
taken as an `acts_as` as given (without `prefix`, a bare name is refused:
|
|
655
|
+
call it as `<group>/<tool>`). The `acts_as:` hook must give exactly the
|
|
649
656
|
`acts_as` the source publishes for that tool: a call whose tool isn't the
|
|
650
|
-
one guardrails were given is refused ("the
|
|
657
|
+
one guardrails were given is refused ("the tool list changed while the call
|
|
651
658
|
waited"), so a hook that disagrees refuses every call.
|
|
652
659
|
- **The row** shows `label` (default: the bundle's name) and
|
|
653
660
|
`<group>/<tool> args…`. `@source.declared_names` and `declared_tokens` say
|
|
@@ -1672,6 +1679,11 @@ by themselves there.
|
|
|
1672
1679
|
or a turn already there) and the session's toolkit has no `delegate`, the
|
|
1673
1680
|
reply says to run `/toolkit coordinator`. A continue of a coordinator link
|
|
1674
1681
|
keeps the toolkit.
|
|
1682
|
+
- **The coding toolkit.** The bundle (0.5.0) also declares `coding`: `child`
|
|
1683
|
+
with the prompt text `- Be concise in your responses` / `- Show file paths
|
|
1684
|
+
clearly when working with files`. The skill starts build and fix children
|
|
1685
|
+
on it (`delegate` with `toolkit: "coding"`); reviews, research and plans
|
|
1686
|
+
stay on `toolkit.child`, as they need the length.
|
|
1675
1687
|
- **`/children [all]`** shows the session's children as a card, newest first,
|
|
1676
1688
|
one line each: `` `ab12cd34` · running · fix/flaky · "fix the flaky spec" ``,
|
|
1677
1689
|
`` `9a8b7c6d` · done · feat/x · reported · "All 12 specs pass" `` (`not
|
|
@@ -1761,8 +1773,7 @@ Claude pairs; without one the bundle costs one tool declaration.
|
|
|
1761
1773
|
- In the web, selecting text offers **Confirm with Claude**
|
|
1762
1774
|
([`chi.annotation`](#chiannotationlabel-command-template-quote-send-false)):
|
|
1763
1775
|
it fills the composer with `/claude validate` and the quote, for you to
|
|
1764
|
-
add what needs confirming.
|
|
1765
|
-
the bundle loads without the pill.
|
|
1776
|
+
add what needs confirming.
|
|
1766
1777
|
- `/claude last` sends the model's last completed answer as context. Mid-turn
|
|
1767
1778
|
it is the answer before the running turn, never the turn's partial text;
|
|
1768
1779
|
a cancelled or failed turn has no answer, so the one before it goes.
|
data/docs/sessions.md
CHANGED
|
@@ -215,8 +215,8 @@ A session can **continue** an earlier one: "the next day of the same routine" (a
|
|
|
215
215
|
- **Its open delegates move to the new link** instead: each direct delegate that is running, waiting for an answer, has a live worker or a reply its parent wasn't given, or has such a delegate below it (checked again after the recap wait, so one delegated meanwhile moves too). It moves with its own delegates, stays unarchived, and from then on is the new link's: its card folds under the new link, its reports ring the new link (an idle one runs a wake turn for them, as for its own delegates), `delegate_result` and the `⑂` chip count it there, and what its parent was already told (`delegates.json`) and any report still waiting come along, so nothing is reported twice. A moved delegate gets a context note from `session chain`, `Your parent session is now 4b7e9a01 (…), the next link of its session chain: …`, as its system prompt keeps naming the parent it started with. The move writes `<child>/parent.json` (`{parent_id, from, at}`) beside the child's session file, never the file itself, so a live child worker saving its own copy changes nothing; every reader applies it (a later move overwrites it; it is never removed). A continue is **refused for an open delegate that can't move** (`SessionContinue::Refused`, naming it): one whose live worker runs an older chi (`older chi worker 0.46.1; restart it`: `chi sessions restart <id>`, or stop it) or that is open in a chi REPL (close it there). An older `chi web` or CLI still running shows a moved delegate under the archived link until it is restarted.
|
|
216
216
|
- **If the continue fails part-way:** a new link that fails to start gives its moved delegates back before it is deleted (each gets a note saying so; its worker is stopped first, if one started, and a worker that won't stop is reported, the link left), with any report one of them rang it with meanwhile; the previous link, back in the lists, is woken for reports waiting. A continue that dies in the middle leaves a `move.json` in the new link's folder: the new link's next worker finishes that move (only the delegates it names, and without a second note to one already told) as it starts. Until the new link's own worker holds it, a ring from a moved delegate doesn't start another (a `starting` marker in its folder, which that worker removes; a stale one, over 60 s old or from a process that is gone, is ignored). An open delegate below an archived delegate refuses the continue (`under archived …`): unarchive that one, or archive this one.
|
|
217
217
|
- **Refused too:** a link that is continued already (naming the link that continues it; continue that one, or `last:<id>`), a previous link whose folder is gone, and one whose model's host can't run it when no default model is configured to fall back to. A refusal starts nothing and archives nothing; a new link that fails to start puts back what was archived. Every refusal that can be known up front (the archive's ones included) comes before the recap wait. One continue of a session runs at a time (a lock in its folder; other chains don't wait), so a double click finds the first one's link and is refused, while a `last:<id>` continue that waited follows the chain to its new end.
|
|
218
|
-
- **Web:** `POST /api/sessions` takes `continues: "<id, prefix or last:id>"`, with `idle: true` (a new link waiting for its first message, previewed by the previous link's, or `preview`) or a `prompt`, and `model` (or `model_name`) for the new link's model, but not `dir`, `llm_context` or `thinking` (400 `invalid_continues`: the previous link's are taken). 201 is the new session's summary, `continues` set, with `moved`: the ids of the delegates that moved to it (`[]` for none), and `model_warning`: the default-model fallback's warning, then any about the model id, joined by `; ` (`null` for none); 404 `not_found`; 400 `ambiguous_id` for a prefix of several sessions, `invalid_model` for a given model whose host config.yml doesn't have (or a fallback with no default model); 409 `continued` with `next_id` (the link that continues it already, which the page opens instead), `open_children` with `ids` (open delegates that can't move), `folder_gone`, `busy`, `scratch` or `owned_by_tui`. Every session summary carries `continues`.
|
|
219
|
-
- **The page:** the info bar's `continue →` continues the open session (`continues: <id>, idle: true`; it reads `continuing…` through the recap wait) and opens the new link with `Continue where we left off
|
|
218
|
+
- **Web:** `POST /api/sessions` takes `continues: "<id, prefix or last:id>"`, with `idle: true` (a new link waiting for its first message, previewed by the previous link's, or `preview`) or a `prompt`, and `model` (or `model_name`) for the new link's model, but not `dir`, `llm_context` or `thinking` (400 `invalid_continues`: the previous link's are taken). 201 is the new session's summary, `continues` set, with `moved`: the ids of the delegates that moved to it (`[]` for none), and `model_warning`: the default-model fallback's warning, then any about the model id, joined by `; ` (`null` for none), and `opener`: the previous link's first typed message, in full, when that link continues one itself (`null` otherwise: a chain's first link's first message is its task); 404 `not_found`; 400 `ambiguous_id` for a prefix of several sessions, `invalid_model` for a given model whose host config.yml doesn't have (or a fallback with no default model); 409 `continued` with `next_id` (the link that continues it already, which the page opens instead), `open_children` with `ids` (open delegates that can't move), `folder_gone`, `busy`, `scratch` or `owned_by_tui`. Every session summary carries `continues`.
|
|
219
|
+
- **The page:** the info bar's `continue →` continues the open session (`continues: <id>, idle: true`; it reads `continuing…` through the recap wait) and opens the new link with an opener in the composer, not sent (a draft already there stays instead): the previous link's own first typed message when that link continues one (the 201's `opener`; a `!shell` line isn't taken), else `Continue where we left off.`. So whatever you sent on the last link comes back on the next one, and editing it once changes it for every later link; the previous link is archived, a toast says so (`Continued 3fa2c1d0 in 4b7e9a01; 3fa2c1d0 is archived; 1 open delegate moved`), and a moved delegate's card folds under the new link. When the previous link's model's host is gone the link starts on the default model and the toast carries the server's `model_warning` too, and then stays until closed. A fresh link (one with no turn yet, however it was made: `continue →`, `chi send --new --continues`, a reload) shows the model picker in its composer with the link's model, until its first turn starts (a failed or canceled first turn ends it too); a pick runs `/model <name>` in the link (a command bubble; the info bar follows) and changes neither the start page's remembered model nor its Recent list, and a Send right after it waits for the `/model` to be taken. A link a chi REPL owns, or one continued already, shows none. A link continued already has no `continue →`: its `→` link goes to the next link (a stale page's continue answered 409 `continued` opens `next_id`). A continue refused for open delegates that can't move shows a toast that names each one (its preview and what the server says to do: `older chi worker 0.46.1; restart it`, `open in a chi REPL; close it there`), each a link to its page; any other refusal shows the server's words. The info bar of a link carries `← 2026-10-08` (the previous link's day) and, on an earlier link, `2026-10-10 →`, each a link with its preview and id in the tooltip. A link made by `continue →` inherits the previous link's title when it is created, so its card keeps its chain's title whatever opener it is sent, and its top bar and browser tab show its card's title. A link whose title is the opener and nothing else (a `chi send --new --continues` link whose first message is `Continue where we left off.`) shows its chain's first link's title instead, on its card, in the popover and in its info bar's title line, display only (its messages and data keep the opener; its own title when the first link is gone). The lists fold a chain into the card of its latest listed link, whatever the earlier links' archived state (typing in an archived link brings it back, folded there still), their delegates with them: the card's `↩ day N` chip (`day N of M` when later links aren't listed) opens a popover of the other links, newest first (`day N`, its date, its recap's first sentence or its preview, `archived`), a click opens one; the chip shows a live dot or `waiting` for a link that has a worker or waits on the user, and a waiting link puts its chain's card first. All sessions folds nothing while a search is typed or "include archived" is on: every link is a card of its own then, its chip `day N of M`, with its own delegates folded in, so a search finds an earlier link and its delegates.
|
|
220
220
|
- **CLI:** `chi send --new --continues (ID|PREFIX|last:ID) [--model M] [-m TEXT]` (see *Starting a session*).
|
|
221
221
|
- **Which link:** an id, a unique prefix, or `last:<id or prefix>`, the latest link of the chain that session is in. It walks the links that still exist: a deleted link in the middle (`chi sessions delete`, the retention sweep of an unarchived one) ends the walk before it, so `last:` of an earlier link then names that earlier link, and continuing it starts a second line beside the links after the gap; name a link after the gap instead. A trigger names the chain this way: `chi send` to an archived link would bring it back (a human's input), not continue the chain.
|
|
222
222
|
|
|
@@ -226,7 +226,9 @@ A session file's `toolkit` (`{"name", "add", "remove"}`) is the toolkit it was c
|
|
|
226
226
|
`toolkit.default`, `toolkit.child` for a delegate child, or `chi --toolkit` / `chi send --new --toolkit` (with
|
|
227
227
|
`--tool` / `--no-tool`). It is never resolved again from config: a respawned or woken worker declares the same
|
|
228
228
|
tools. `null` (a session from before toolkits) is `full`. A continue (`continue →`, `chi send --new --continues`)
|
|
229
|
-
and a fork (`ctx.sessions.fork`) keep it, as they keep the thinking level; a delegate child gets
|
|
229
|
+
and a fork (`ctx.sessions.fork`) keep it, as they keep the thinking level; a delegate child gets the kit its
|
|
230
|
+
`delegate` call names (`toolkit:`, any kit the parent knows) or `toolkit.child`; one its worker can't resolve
|
|
231
|
+
declares every tool (full). The file stores only the name: a kit's tools and prompt text come from its definition.
|
|
230
232
|
`/toolkit` changes it, saved at once, from the next turn's start. See
|
|
231
233
|
[Toolkits](configuration.md#toolkits).
|
|
232
234
|
|
data/docs/testing.md
CHANGED
|
@@ -36,7 +36,10 @@ A select bound with `.value` instead reads right in both, but lit sets it before
|
|
|
36
36
|
`npm test` runs every file in one process (`--test-isolation=none`; CI and local runs use Node 24): the files share
|
|
37
37
|
`globalThis`, the shim's one happy-dom window and every module's state: a global a spec sets (stage_view's
|
|
38
38
|
`ResizeObserver` stand-in) is there for the files after it, so set only harmless stand-ins and put anything else
|
|
39
|
-
back (`t.mock` restores itself).
|
|
39
|
+
back (`t.mock` restores itself). A test that sets off a module's timer (a flash, a highlight's end) fakes it
|
|
40
|
+
(`t.mock.timers.enable({ apis: ["setTimeout"] })`, then `tick`): a real one still pending after the last test keeps
|
|
41
|
+
node running until it fires (a 4 s one made the run 6 s for 2 s of work). One file:
|
|
42
|
+
`node --test spec/web/public/NAME.test.js`.
|
|
40
43
|
|
|
41
44
|
Where a composer line goes is decided twice, in Ruby (`ComposerRoute`, both terminals) and in the web
|
|
42
45
|
(`composer.js#routeComposerLine`). `spec/fixtures/composer_routes.json` holds the cases both answer: rspec
|
|
@@ -44,6 +47,12 @@ Where a composer line goes is decided twice, in Ruby (`ComposerRoute`, both term
|
|
|
44
47
|
either router adds its row there; a row where a UI differs on purpose (or a found divergence) carries a `web` or
|
|
45
48
|
`repl` override and a `note`.
|
|
46
49
|
|
|
50
|
+
A spec that waits for something uses `wait_until(timeout:)`, or `wait_until!(what, timeout:)` where no check
|
|
51
|
+
follows (stubs, helper threads: it raises "timed out after N s waiting for <what>") — never a loop with no deadline.
|
|
52
|
+
Every example also runs under a 120 s limit (`spec_helper`'s `around`; `integration`, `macos_build`, `warmup` and
|
|
53
|
+
`recap` examples are exempt), so a hang fails its example instead of the run. `SPEC_EXAMPLE_TIMEOUT=N` changes the
|
|
54
|
+
limit, `0` turns it off (a debugger session).
|
|
55
|
+
|
|
47
56
|
## While you work: affected specs
|
|
48
57
|
|
|
49
58
|
```sh
|
|
@@ -60,12 +60,11 @@ module Samagotchi
|
|
|
60
60
|
attr_reader :path
|
|
61
61
|
|
|
62
62
|
# @param path [String] config.yml (a symlink is written through)
|
|
63
|
-
# @param env [Hash] the environment for the check
|
|
64
|
-
# is dropped: it would replace the file's hosts)
|
|
63
|
+
# @param env [Hash] the environment for the check
|
|
65
64
|
# @param now [Proc] the time for the backup's name and the header
|
|
66
65
|
def initialize(path:, env: ENV, now: -> { Time.now })
|
|
67
66
|
@path = path
|
|
68
|
-
@env = env.to_h
|
|
67
|
+
@env = env.to_h
|
|
69
68
|
@now = now
|
|
70
69
|
end
|
|
71
70
|
|
data/lib/samagotchi/bridge.rb
CHANGED
|
@@ -289,13 +289,16 @@ module Samagotchi
|
|
|
289
289
|
# session state, the last answer (Engine#last_answer_message: one
|
|
290
290
|
# message, not the conversation) and the cards, as one step of the event
|
|
291
291
|
# log (one event_seq). Light whatever the session's length: GET tail.
|
|
292
|
-
#
|
|
292
|
+
# The session's folder (working_directory) rides along: what the web
|
|
293
|
+
# checks the answer's file refs against without reading the session file.
|
|
294
|
+
# @return [Hash] {session_id:, working_directory:, session_state_snapshot:, answer:, cards:, event_seq:, event_id:}
|
|
293
295
|
# +turn_id+: that turn's answer, not the newest (Engine#last_answer_message).
|
|
294
296
|
def tail_frame(turn_id: nil)
|
|
295
297
|
@engine.synchronize_events do
|
|
296
298
|
seq = @engine.event_count
|
|
297
299
|
state = @engine.session_state_snapshot.merge(event_seq: seq, event_id: event_id(seq))
|
|
298
|
-
{ session_id: @session_id,
|
|
300
|
+
{ session_id: @session_id, working_directory: @engine.session&.working_directory,
|
|
301
|
+
session_state_snapshot: state, answer: @engine.last_answer_message(turn_id: turn_id),
|
|
299
302
|
cards: @cards.list, event_seq: seq, event_id: event_id(seq) }
|
|
300
303
|
end
|
|
301
304
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "pathname"
|
|
4
4
|
require_relative "../session"
|
|
5
|
-
require_relative "../
|
|
5
|
+
require_relative "../paths"
|
|
6
6
|
require_relative "../context_sources"
|
|
7
7
|
require_relative "../git_head"
|
|
8
8
|
require_relative "../recap_store"
|
|
@@ -78,7 +78,7 @@ module Samagotchi
|
|
|
78
78
|
# back resolved (/private/tmp/… on macOS) while the session's folder
|
|
79
79
|
# may go through a symlink (/tmp/…).
|
|
80
80
|
def folder(cwd, project)
|
|
81
|
-
return
|
|
81
|
+
return Paths.tilde(cwd) if project.nil?
|
|
82
82
|
|
|
83
83
|
cwd = real(cwd)
|
|
84
84
|
project = real(project)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: claude-peer
|
|
3
|
-
version: 0.4.
|
|
3
|
+
version: 0.4.1
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Pair this session with a Claude Code session you work with: the claude_send tool and /claude send it a part of the work (a plan step, a diff, a finding) to validate or as context, and Claude's answer comes back as the claude-outbox context; /claude install-skill installs Claude's chi-peer skill, which pairs"
|
|
6
6
|
trust_level: reviewed
|
|
@@ -10,15 +10,10 @@ files:
|
|
|
10
10
|
description: "Send a part of the work to the paired Claude Code session (claude_send): only when the user asks, or to reply to its answer in a peer turn; validate, context or question; Claude's answer comes as the claude-outbox context"
|
|
11
11
|
plugin:
|
|
12
12
|
file: plugin.rb
|
|
13
|
-
sha256: sha256:
|
|
13
|
+
sha256: sha256:50f427d3862ae9d0d15693b4d887c528162b96c0320cc663df89d59d830d4660
|
|
14
14
|
scripts:
|
|
15
15
|
pair: sha256:35f3be2b466a5ba07591d64040bc5780fdd843b9b08737f8ea8a2fccb2102832
|
|
16
16
|
chi-peer-SKILL.md: sha256:e991a91139a127a60f9a216043eb5864945fb211952a1e3c8db2068c6cc254f1
|
|
17
|
-
# Two things need chi 0.63.0, and the bundle works without them on 0.62:
|
|
18
|
-
# ctx.context.list's summary: (the /claude card's last answer; without it
|
|
19
|
-
# the card shows no summary), and a pushed source's first wake: true push
|
|
20
|
-
# waking the session (on 0.62 Claude's first answer arrives as a note
|
|
21
|
-
# without waking; later ones wake).
|
|
22
17
|
# The plugin needs Samagotchi::Plugin::Peer and chi.peer_source (chi 0.67.0).
|
|
23
18
|
# An agreeing answer's closing wake (the skills say an OK wakes chi once)
|
|
24
19
|
# and the plugin's OUTBOX (Plugin::Peer's rename of REVIEW) need chi 0.70.0.
|
|
@@ -40,8 +40,8 @@ require "samagotchi/plugin/peer"
|
|
|
40
40
|
# sees it. The skill is scripts/chi-peer-SKILL.md beside this file, with the
|
|
41
41
|
# installed scripts/pair path and the payload folder written in.
|
|
42
42
|
#
|
|
43
|
-
# In the web, selecting text offers "Confirm with Claude" (chi.annotation
|
|
44
|
-
#
|
|
43
|
+
# In the web, selecting text offers "Confirm with Claude" (chi.annotation):
|
|
44
|
+
# it fills the composer with
|
|
45
45
|
# `/claude validate` and the quote, for the user to add what to confirm.
|
|
46
46
|
# Such a text is titled by its first quoted line, not the quote's
|
|
47
47
|
# `From your thinking:` label.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: codex-peer
|
|
3
|
-
version: 0.3.
|
|
3
|
+
version: 0.3.1
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Pair this session with an OpenAI Codex CLI session you work with: the codex_send tool and /codex send it a part of the work (a plan step, a diff, a finding) to validate or as context, with codex queue, and Codex's answer comes back as the codex-outbox context; /codex install-skill installs Codex's chi-peer skill, which pairs"
|
|
6
6
|
trust_level: reviewed
|
|
@@ -10,14 +10,10 @@ files:
|
|
|
10
10
|
description: "Send a part of the work to the paired Codex CLI session (codex_send): only when the user asks, or to reply to its answer in a peer turn; validate, context or question; Codex's answer comes as the codex-outbox context"
|
|
11
11
|
plugin:
|
|
12
12
|
file: plugin.rb
|
|
13
|
-
sha256: sha256:
|
|
13
|
+
sha256: sha256:eb6888e3611b8ed42498cfecab5b430e0ef7a4c2dc7b3c090d015336aa039b11
|
|
14
14
|
scripts:
|
|
15
15
|
pair: sha256:251448a31959b5c916400eff7909e85c696855d5c16b6b10973ada40915c8051
|
|
16
16
|
chi-peer-SKILL.md: sha256:7c392ac04c115ea1e762ad562b5c4c44c3eca71558ba5d9dfeaeabdd533b6a82
|
|
17
|
-
# Codex's answer is a pushed source's first wake: true push, which wakes the
|
|
18
|
-
# session from chi 0.63.0 (before, the first answer arrives as a note
|
|
19
|
-
# without waking); ctx.context.list's summary: (the /codex card's last
|
|
20
|
-
# answer) is 0.63.0 too.
|
|
21
17
|
# The plugin needs Samagotchi::Plugin::Peer and chi.peer_source (chi 0.67.0).
|
|
22
18
|
# An agreeing answer's closing wake (the skills say an OK wakes chi once)
|
|
23
19
|
# and the plugin's OUTBOX (Plugin::Peer's rename of REVIEW) need chi 0.70.0.
|
|
@@ -42,8 +42,8 @@ require "samagotchi/plugin/peer"
|
|
|
42
42
|
# pairing and replies (its default sandbox can't write chi's state); chi
|
|
43
43
|
# never edits Codex's config.
|
|
44
44
|
#
|
|
45
|
-
# In the web, selecting text offers "Confirm with Codex" (chi.annotation
|
|
46
|
-
#
|
|
45
|
+
# In the web, selecting text offers "Confirm with Codex" (chi.annotation):
|
|
46
|
+
# it fills the composer with
|
|
47
47
|
# `/codex validate` and the quote, for the user to add what to confirm.
|
|
48
48
|
# Such a text is titled by its first quoted line, not the quote's
|
|
49
49
|
# `From your thinking:` label.
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: coordinator
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.5.0
|
|
4
4
|
scope: system
|
|
5
|
-
description: "Coordinate parallel work: the coordinator skill (split the work, one worktree and child session per task, check each report, ask before each merge, a handoff memory to resume from), /coordinate <goal>, /coordinate resume, /coordinate end and /children; the coordinator toolkit (lean + delegate, delegate_result, list_sessions)"
|
|
5
|
+
description: "Coordinate parallel work: the coordinator skill (split the work, one worktree and child session per task, check each report, ask before each merge, a handoff memory to resume from), /coordinate <goal>, /coordinate resume, /coordinate end and /children; the coordinator toolkit (lean + delegate, delegate_result, list_sessions) and the coding toolkit for build children (child + be concise)"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
files:
|
|
8
8
|
skill_coordinator.md:
|
|
9
|
-
sha256: sha256:
|
|
9
|
+
sha256: sha256:718cb76da10f9610a7582eddf1441590566836ebc6a9cb127b7f7847b17ceb92
|
|
10
10
|
description: "Coordinate parallel work: split it, delegate each task wait:false in its own worktree (cwd:), check each report, ask the user before each merge; resume from a handoff_* memory"
|
|
11
11
|
plugin:
|
|
12
12
|
file: plugin.rb
|
|
13
|
-
sha256: sha256:
|
|
14
|
-
# the coordinator toolkit needs chi.toolkit and ctx.use_toolkit (0.66.0)
|
|
15
|
-
|
|
13
|
+
sha256: sha256:3b5f766ad198862243c494e4c5aec7c4a0972e7644a6388bed843c5651f5ca0d
|
|
14
|
+
# the coordinator toolkit needs chi.toolkit and ctx.use_toolkit (0.66.0); the
|
|
15
|
+
# coding toolkit (chi.toolkit prompt:) and the skill's delegate toolkit: need
|
|
16
|
+
# the next chi.
|
|
17
|
+
requires_chi: ">= 0.72.0"
|
|
@@ -38,6 +38,13 @@
|
|
|
38
38
|
# before the session's first model reply and outside a turn: otherwise it
|
|
39
39
|
# says to run /toolkit coordinator, unless the session declares delegate
|
|
40
40
|
# anyway.
|
|
41
|
+
#
|
|
42
|
+
# The coding toolkit is for build and fix children (the skill's delegate
|
|
43
|
+
# toolkit: "coding"): child's tools plus a prompt text, pi's two plain
|
|
44
|
+
# lines. In the harness evals (r5, r7: Qwen3.6-35B-A3B, lean, N=3) they cut
|
|
45
|
+
# the median time by a third at the same quality. Plain wording only: a
|
|
46
|
+
# scoped wording ("in this task, …") backfired (+50% thinking). Reviews,
|
|
47
|
+
# research and plans stay on child (no toolkit:): they need the length.
|
|
41
48
|
class Plugin
|
|
42
49
|
CARD_ID = "children"
|
|
43
50
|
# A card takes 6 actions: Refresh and up to 5 Stops.
|
|
@@ -56,9 +63,12 @@ class Plugin
|
|
|
56
63
|
|
|
57
64
|
TOOLKIT = "coordinator"
|
|
58
65
|
TOOLKIT_TOOLS = %w[delegate delegate_result list_sessions].freeze
|
|
66
|
+
CODING_TOOLKIT = "coding"
|
|
67
|
+
CODING_PROMPT = "- Be concise in your responses\n- Show file paths clearly when working with files"
|
|
59
68
|
|
|
60
69
|
def register(chi)
|
|
61
70
|
chi.toolkit TOOLKIT, base: "lean", add: TOOLKIT_TOOLS
|
|
71
|
+
chi.toolkit CODING_TOOLKIT, base: "child", prompt: CODING_PROMPT
|
|
62
72
|
chi.command "/children", "this session's child sessions: state, branch, last reply; all, stop <id>",
|
|
63
73
|
anytime: true do |args, ctx|
|
|
64
74
|
children_command(args.to_s.strip, ctx)
|
|
@@ -46,7 +46,8 @@ If the request is a new goal, say which handoffs are open (one line each) and go
|
|
|
46
46
|
user if unsure. Split the work into independent tasks (at most session.max_children at once,
|
|
47
47
|
default 4). Tell the user the split in a few lines and go on unless they object.
|
|
48
48
|
2. For each task: from your own folder, `git worktree add ../<repo>-<task> -b <type>/<task>` with
|
|
49
|
-
execute; then `delegate` with `wait: false`, `cwd:` that worktree's absolute path,
|
|
49
|
+
execute; then `delegate` with `wait: false`, `cwd:` that worktree's absolute path, `toolkit:
|
|
50
|
+
"coding"` for a build or fix task (none for a review, research or a plan), and a
|
|
50
51
|
self-contained task. The task starts with "Work only in <absolute worktree path>: every cd, read,
|
|
51
52
|
write, test and commit goes there, never the project root" and says what to report (the result,
|
|
52
53
|
evidence: commands and what they printed, what isn't done) and "commit on the branch, don't merge,
|
|
@@ -57,7 +57,7 @@ Only the nested form is read. An env name used as a top-level key (`SAMAGOTCHI_D
|
|
|
57
57
|
**Excluded maps** (YAML-only, not part of the flat registry; skipped by scalar loader):
|
|
58
58
|
|
|
59
59
|
- `model_aliases:` map of alias → model id (`ModelRef.parse`, through `HostRegistry#resolve`). Keys lowercased on write (`ConfigFile.write_model_alias!`). Values may be bare `model` or qualified `host:model` (hybrid; only `:` names a host, `org/model` is an id). Aliases apply once: never point an alias at another alias.
|
|
60
|
-
- `hosts:` map of `name → {host, port | url, transport, api, api_key_env, auth, dialect, profile, first_token_timeout, vision, sampling, thinking, enabled, models}` (`ConfigFile.hosts_config`, `host_registry.rb` `HostEntry`). Names lowercased; `url:` (http/https, optional path) replaces host/port, never both; `api_key_env:` names the env var holding the API key (never write a key into config.yml); `auth:` (`bearer|x-api-key`) is the header the key goes in (default `x-api-key` on `api: anthropic`, Bearer elsewhere); `dialect:` (`fireworks`, or `standard` for the default; `api: anthropic` only) adjusts the Messages wire for that provider (Bearer by default, no thinking `display`, no `cache_control`, `x-session-affinity`; `LLM::Dialect`, docs/configuration.md "Anthropic Messages hosts"); `transport` overrides `server.transport`; `first_token_timeout` (seconds, `0` = off; a negative or non-number warns and is ignored) overrides `server.first_token_timeout` for that host; `models:` (a map of model ids, or a plain list) declares ids the host serves whatever its `/v1/models` lists: no "doesn't list" warning, and a bare id routes there as if listed (`HostModel`); an entry may hold `price: {input, output, cache_read?, cache_write?}` in USD per 1M tokens (`ModelPrice`; an unset cache rate is the input rate), which estimates a cost the provider doesn't report or reports as 0 (`cost_estimate`, shown as `~$`), and `served: [ids] | any`, the models a gateway may answer that id with (no served-model warning for them); model settings stay in the root `models:`; a worker reads `hosts:` from the file as it starts (
|
|
60
|
+
- `hosts:` map of `name → {host, port | url, transport, api, api_key_env, auth, dialect, profile, first_token_timeout, vision, sampling, thinking, enabled, models}` (`ConfigFile.hosts_config`, `host_registry.rb` `HostEntry`). Names lowercased; `url:` (http/https, optional path) replaces host/port, never both; `api_key_env:` names the env var holding the API key (never write a key into config.yml); `auth:` (`bearer|x-api-key`) is the header the key goes in (default `x-api-key` on `api: anthropic`, Bearer elsewhere); `dialect:` (`fireworks`, or `standard` for the default; `api: anthropic` only) adjusts the Messages wire for that provider (Bearer by default, no thinking `display`, no `cache_control`, `x-session-affinity`; `LLM::Dialect`, docs/configuration.md "Anthropic Messages hosts"); `transport` overrides `server.transport`; `first_token_timeout` (seconds, `0` = off; a negative or non-number warns and is ignored) overrides `server.first_token_timeout` for that host; `models:` (a map of model ids, or a plain list) declares ids the host serves whatever its `/v1/models` lists: no "doesn't list" warning, and a bare id routes there as if listed (`HostModel`); an entry may hold `price: {input, output, cache_read?, cache_write?}` in USD per 1M tokens (`ModelPrice`; an unset cache rate is the input rate), which estimates a cost the provider doesn't report or reports as 0 (`cost_estimate`, shown as `~$`), and `served: [ids] | any`, the models a gateway may answer that id with (no served-model warning for them); model settings stay in the root `models:`; a worker reads `hosts:` from the file as it starts (no environment variable sets hosts).
|
|
61
61
|
- `models:` map of model id or alias → `{profile, vision, sampling, thinking, reasoning_history}` (`ConfigFile.model_settings`). `thinking:` (here, on a host, or `thinking.level` for every model) is `off|low|medium|high|default` (`Thinking`; unquoted `off` works, `on` is not a level; `default` = send nothing). Precedence: the session's own level (`/thinking LEVEL`, saved in the session; `chi --thinking` on a session run, `chi send --new --thinking`) > `chi web --thinking`/`SAMAGOTCHI_THINKING_LEVEL` > `models:` > `hosts.<name>.thinking` > `thinking.level` in config.yml > `default`. A `sampling:` key wins over the fields a level sends (`null` drops one); `docs/configuration.md` "Thinking". `reasoning_history:` (here or on a host) is `drop|turn|all`, which of its reasoning a chat host's model gets back. Precedence: the session's own level (`/reasoning-history LEVEL`, saved in the session) > `SAMAGOTCHI_REASONING_HISTORY` > `models:` > `hosts.<name>.reasoning_history` > `turn`; `docs/configuration.md` "Reasoning history". `/thinking` and `/reasoning-history` ask before a change that restarts the prompt cache; `--yes` applies it (`chi send` commands apply directly). `sampling:` (here or on a host) is a map of request fields passed to the provider as written (`temperature`, `top_p`, `presence_penalty`, `repeat_penalty`, …; a model's fields win over its host's per field; `null` = don't send; chi's own fields like `max_tokens`/`stream` are refused with a warning; `docs/configuration.md` "Sampling"). Keys match case-insensitively. `profile` (here or on a host) is `qwen36|gemma4`: the raw prompt format for native hosts. Precedence: `--profile`/`SAMAGOTCHI_MODEL_PROFILE` > `models:` > `hosts.<name>.profile` > the llama.cpp server's chat template > the name (`qwen`/`gemma`) > `qwen36` (`ModelProfile.resolve`). Set one when a model's name hides its family (e.g. a Qwen fine-tune under another name on mlx, which has no template to read).
|
|
62
62
|
- `hooks:` map of `hooks_dir` + per-event lists `{path, on_error}` (`Hooks::Loader.load`). `hooks_dir` may start with `~`.
|
|
63
63
|
- `guardrails:` tool-call rules (`docs/guardrails.md`; read in `Engine#guardrail_rules`, parsed by `Guardrails::Rules.parse`): `enabled` (bool, default true; `false` drops rules and hooks' asks, a deny still applies; config.yml only, like every `guardrails` setting: no env variable), `parent_approvals` (`off`, the default: a parent agent's `chi answer` can only deny an approval; `once`: it may give "Allow once" too; a convention for an honest parent, not a security boundary, and the user's choice: set it only when the user asks), `rules:` (list), `disable:` (a rule id or a list of them, `id` or `bundle:id`, switching off a bundle's or config rule without editing it), `small_models` (`auto`, the default: small = 32B or less read from the name; a glob or list of globs; `[]` for none). A rule takes only `id`, `tool`, `command`, `path`, `git`, `models`, `verdict`, `reason`, `scopes`: `id` required; at least one of `tool` (a name, `shell` = execute + task_create, a `File.fnmatch` glob like `"mcp_*"`, or a list), `command` (a Ruby regex on the shell command), `path` (a glob, or `outside_repo`: outside the session's repo, tmp folders and memory files excepted), `git` (only `outside_repo`: a shell call runs commit/add/reset/… in another checkout); `models` (optional: `small`, a model-name glob, or a list; without it the rule is for every model); `verdict` `ask|deny`; `scopes` (for `ask`) a subset of `once, session, repo, rule`. **Any parse error (an unknown key, a bad regex, no verdict) makes chi deny every tool call** until fixed, so validate with `YAML.safe_load` and keep the list shape. Rules are read again on the next tool call after config.yml or a bundle's rules change: no restart needed. Installed bundles' rule files (`chi bundle install guardrails`) add to them; `/guardrails` lists what loaded.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: samagotchi-system
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.73.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
8
|
self_map.md: sha256:b5b3166c91f6f2af2563dd9db5f9959cda66747d907017acd0776fcd2fe29049
|
|
9
|
-
config_modification_protocol.md: sha256:
|
|
9
|
+
config_modification_protocol.md: sha256:66792f788cdbb8ee0252f2c387c96bd75266817bb75f721aaa181b602db97137
|
|
10
10
|
memory_guide.md: sha256:c48eea744db69e4f3e9bb415e9a03a8c78bbcaa1205ad3b1fb157a079f92d22e
|
|
11
11
|
delegated.md: sha256:a0d6ddcd4a8be06526d964898611775fd251cfd9d145a9d16afaccaa503597bd
|
data/lib/samagotchi/config.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
|
-
require "json"
|
|
5
4
|
require "fileutils"
|
|
6
5
|
require "uri"
|
|
7
6
|
require_relative "atomic_file"
|
|
@@ -877,13 +876,13 @@ module Samagotchi
|
|
|
877
876
|
HOST_NAME_RE = /\A[a-z0-9][a-z0-9._-]*\z/i
|
|
878
877
|
ENV_NAME_RE = /\A[A-Za-z_][A-Za-z0-9_]*\z/
|
|
879
878
|
|
|
880
|
-
# config.yml's hosts:
|
|
881
|
-
#
|
|
882
|
-
#
|
|
879
|
+
# config.yml's hosts: by lowercased name; an entry that is disabled or
|
|
880
|
+
# invalid (warned) is left out. No entry left: "default", from
|
|
881
|
+
# server.host/port/transport.
|
|
883
882
|
# @return [Hash{String => HostConfig}]
|
|
884
883
|
def hosts_config(env: ENV, path: global_path(env: env))
|
|
885
884
|
data = read_yaml(env: env, path: path)
|
|
886
|
-
raw_hosts =
|
|
885
|
+
raw_hosts = data[HOSTS_KEY] if data.is_a?(Hash)
|
|
887
886
|
|
|
888
887
|
normalized = {}
|
|
889
888
|
if raw_hosts.is_a?(Hash)
|
|
@@ -933,11 +932,11 @@ module Samagotchi
|
|
|
933
932
|
private_class_method :warn_duplicate_host_models
|
|
934
933
|
|
|
935
934
|
# The names (lowercased) of the hosts entries with enabled: false, which
|
|
936
|
-
# hosts_config leaves out
|
|
937
|
-
# hosts:, or a SAMAGOTCHI_HOSTS_JSON override.
|
|
935
|
+
# hosts_config leaves out, from config.yml's hosts:.
|
|
938
936
|
# @return [Array<String>]
|
|
939
937
|
def disabled_host_names(env: ENV, path: global_path(env: env))
|
|
940
|
-
|
|
938
|
+
data = read_yaml(env: env, path: path)
|
|
939
|
+
raw = data[HOSTS_KEY] if data.is_a?(Hash)
|
|
941
940
|
return [] unless raw.is_a?(Hash)
|
|
942
941
|
|
|
943
942
|
raw.filter_map { |name, cfg| name.to_s.strip.downcase if cfg.is_a?(Hash) && HostConfig.disabled?(cfg) }
|
|
@@ -945,23 +944,6 @@ module Samagotchi
|
|
|
945
944
|
[]
|
|
946
945
|
end
|
|
947
946
|
|
|
948
|
-
# config.yml's hosts: as written, or the SAMAGOTCHI_HOSTS_JSON override
|
|
949
|
-
# set on this process (specs; SessionManager.spawn_options unsets it for
|
|
950
|
-
# a worker, which reads the file).
|
|
951
|
-
def hosts_source(data, env)
|
|
952
|
-
raw = data[HOSTS_KEY] if data.is_a?(Hash)
|
|
953
|
-
env_json = env["SAMAGOTCHI_HOSTS_JSON"].to_s.strip
|
|
954
|
-
return raw if env_json.empty?
|
|
955
|
-
|
|
956
|
-
parsed = begin
|
|
957
|
-
JSON.parse(env_json)
|
|
958
|
-
rescue StandardError
|
|
959
|
-
nil
|
|
960
|
-
end
|
|
961
|
-
parsed.is_a?(Hash) ? parsed : raw
|
|
962
|
-
end
|
|
963
|
-
private_class_method :hosts_source
|
|
964
|
-
|
|
965
947
|
# Resolve the `recap:` section through the Config registry so scalar
|
|
966
948
|
# recap settings share the single precedence path (CLI > ENV > file >
|
|
967
949
|
# default). Returns false when explicitly disabled (file `recap: false`
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "time"
|
|
4
|
+
require_relative "paths"
|
|
4
5
|
|
|
5
6
|
module Samagotchi
|
|
6
7
|
# A context note: background text pushed into a session from outside (the
|
|
@@ -39,7 +40,7 @@ module Samagotchi
|
|
|
39
40
|
def label(source:, from_session: nil, from_cwd: nil)
|
|
40
41
|
return source.to_s unless from_session
|
|
41
42
|
|
|
42
|
-
"session #{from_session.to_s[0, 6]}#{" (#{
|
|
43
|
+
"session #{from_session.to_s[0, 6]}#{" (#{Paths.tilde(from_cwd)})" if from_cwd}"
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
def label_of(message)
|
|
@@ -78,10 +79,5 @@ module Samagotchi
|
|
|
78
79
|
rescue ArgumentError
|
|
79
80
|
nil
|
|
80
81
|
end
|
|
81
|
-
|
|
82
|
-
def home_relative(path)
|
|
83
|
-
home = Dir.home
|
|
84
|
-
path.to_s.start_with?("#{home}/") || path.to_s == home ? path.to_s.sub(home, "~") : path.to_s
|
|
85
|
-
end
|
|
86
82
|
end
|
|
87
83
|
end
|
|
@@ -30,7 +30,7 @@ module Samagotchi
|
|
|
30
30
|
#
|
|
31
31
|
# Each setting reader rescues its own failure and gives the fallback the
|
|
32
32
|
# Engine has always given (Unresolved); building one raises as
|
|
33
|
-
# HostRegistry#resolve does (
|
|
33
|
+
# HostRegistry#resolve does (SessionModelSettings#model_setting gives the
|
|
34
34
|
# fallback then).
|
|
35
35
|
EffectiveModel = Data.define(:registry, :name, :typed, :target, :names) do
|
|
36
36
|
# @param registry [HostRegistry]
|