samagotchi 0.71.0 → 0.72.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 +45 -1
- data/docs/architecture.md +14 -8
- data/docs/cli.md +3 -2
- data/docs/configuration.md +23 -13
- data/docs/hooks.md +1 -1
- data/docs/internals/anthropic.md +1 -1
- 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 +3 -1
- 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 +2 -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/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/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 +13 -10
- data/lib/samagotchi/web/public/app.js +20 -18
- data/lib/samagotchi/web/public/bubbles.js +1 -1
- data/lib/samagotchi/web/public/cards_place.js +6 -10
- data/lib/samagotchi/web/public/chains.js +1 -1
- 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 +5 -1
- data/lib/samagotchi/web/public/format.js +5 -0
- 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_view.js +8 -2
- 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/session_hub.rb +8 -0
- metadata +3 -1
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.72.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]
|