samagotchi 0.151.0 → 0.152.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 +29 -1
- data/docs/bundle-authoring.md +4 -4
- data/docs/cli.md +32 -7
- data/docs/guardrails.md +4 -0
- data/docs/internals/block-renderers.md +3 -1
- data/docs/internals/bundles.md +48 -14
- data/docs/internals/objectives.md +6 -4
- data/docs/memory.md +6 -1
- data/docs/plugins.md +51 -14
- data/docs/releasing.md +7 -2
- data/docs/sessions.md +1 -1
- data/lib/samagotchi/block_renderers.rb +9 -8
- data/lib/samagotchi/bundle_command/check.rb +43 -3
- data/lib/samagotchi/bundle_command/installed_check.rb +6 -2
- data/lib/samagotchi/bundle_command/try/sandbox.rb +11 -3
- data/lib/samagotchi/bundle_command.rb +88 -10
- data/lib/samagotchi/bundle_needs.rb +4 -6
- data/lib/samagotchi/bundle_settings_check.rb +14 -13
- data/lib/samagotchi/bundles/agents-md/manifest.yml +2 -2
- data/lib/samagotchi/bundles/btw/manifest.yml +2 -2
- data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
- data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
- data/lib/samagotchi/bundles/skills/manifest.yml +2 -2
- data/lib/samagotchi/bundles/slow-commands/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +3 -3
- data/lib/samagotchi/child_env.rb +15 -0
- data/lib/samagotchi/context_providers.rb +42 -10
- data/lib/samagotchi/engine.rb +27 -4
- data/lib/samagotchi/extension_load.rb +4 -3
- data/lib/samagotchi/guardrail_wiring.rb +28 -19
- data/lib/samagotchi/guardrails/approval.rb +2 -2
- data/lib/samagotchi/guardrails/rules.rb +1 -0
- data/lib/samagotchi/guardrails/verdict.rb +20 -0
- data/lib/samagotchi/hooks/bundle_loader.rb +5 -7
- data/lib/samagotchi/memory_bundle/asset_installer.rb +11 -8
- data/lib/samagotchi/memory_bundle/builder.rb +36 -30
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +3 -2
- data/lib/samagotchi/memory_bundle/file_digest.rb +31 -0
- data/lib/samagotchi/memory_bundle/file_standing.rb +2 -2
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +55 -46
- data/lib/samagotchi/memory_bundle/installed_copy.rb +38 -15
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +224 -0
- data/lib/samagotchi/memory_bundle/installer.rb +90 -16
- data/lib/samagotchi/memory_bundle/manifest.rb +146 -6
- data/lib/samagotchi/memory_bundle/manifest_standing.rb +171 -0
- data/lib/samagotchi/memory_bundle/merger.rb +7 -8
- data/lib/samagotchi/memory_bundle/profile.rb +3 -2
- data/lib/samagotchi/memory_bundle/provenance.rb +217 -18
- data/lib/samagotchi/memory_bundle/shas.rb +4 -3
- data/lib/samagotchi/memory_bundle/shipped_update.rb +52 -24
- data/lib/samagotchi/memory_bundle/status.rb +11 -10
- data/lib/samagotchi/memory_bundle/system_bundle.rb +13 -2
- data/lib/samagotchi/plugin/loader.rb +1 -1
- data/lib/samagotchi/session_config.rb +5 -4
- data/lib/samagotchi/system_one/record.rb +10 -2
- data/lib/samagotchi/tools/delegate.rb +20 -2
- data/lib/samagotchi/update_command.rb +13 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +2 -1
- data/lib/samagotchi/web/session_access_routes.rb +2 -1
- data/lib/samagotchi/web/session_hub.rb +6 -0
- metadata +4 -1
data/docs/releasing.md
CHANGED
|
@@ -21,7 +21,9 @@ can run the whole release; the user approves the notes before the tag and the
|
|
|
21
21
|
- a bundle that uses something new in chi marks it in the same change with
|
|
22
22
|
`requires_chi_next: true` (or a short reason) in its `manifest.yml`, and
|
|
23
23
|
leaves `requires_chi` alone: raising it to a chi that isn't released yet
|
|
24
|
-
would make chi on main refuse its own bundle
|
|
24
|
+
would make chi on main refuse its own bundle outright — an unmet
|
|
25
|
+
`requires_chi` now stops the install, not just the load (`--force` is the
|
|
26
|
+
only way past it). `release:bump` raises a
|
|
25
27
|
marked bundle's `requires_chi` to `">= X.Y.Z"` and drops the marker;
|
|
26
28
|
`release:check` fails while a marker is left. chi itself ignores the key,
|
|
27
29
|
and `bundles:check` notes it (and fails a marker that isn't `true` or a
|
|
@@ -30,6 +32,9 @@ can run the whole release; the user approves the notes before the tag and the
|
|
|
30
32
|
- a new `ctx.*` / `chi.*` call a plugin may make gets its release's version in `Plugin::Api::SINCE` once
|
|
31
33
|
released (`chi bundle check`'s `requires-chi-floor` reads it); an unreleased one waits there as an `# Unreleased` comment, and
|
|
32
34
|
`release:check` fails while that comment is left: move each call up with the new version;
|
|
35
|
+
- a new manifest key or shape gets its release's version in `Manifest::SINCE` the same way (check's
|
|
36
|
+
`requires-chi-floor` reads it for the manifest too), waits as an `# Unreleased` comment while unreleased,
|
|
37
|
+
and `release:check` fails while that comment is left;
|
|
33
38
|
- a new shipped bundle joins a profile (`includes:` in `core` or `dev`; a
|
|
34
39
|
spec checks every bundle is in exactly one), and that profile's version
|
|
35
40
|
goes up: `chi update` then installs only the new bundle for those who
|
|
@@ -72,7 +77,7 @@ as the GitHub release body.
|
|
|
72
77
|
| `rake gem:validate`, `gem:build`, `gem:install`, `gem:clean` | Check the gemspec; build the gem into `pkg/`; build and install it locally; remove the built gems. |
|
|
73
78
|
| `rake release:draft_changelog` | A draft Unreleased section from the commit subjects since the last tag, grouped. Stdout only. |
|
|
74
79
|
| `rake release:bump[X.Y.Z]` | VERSION, the system manifest, Gemfile.lock, CHANGELOG, and `requires_chi: ">= X.Y.Z"` for each bundle marked `requires_chi_next` (the marker dropped), then a profile left below a member's `requires_chi` raised to it, with a patch bump of its version (none when it moved since the last tag) and named in the CHANGELOG's "Update with `chi update`" line. No commit. |
|
|
75
|
-
| `rake release:check` | Clean tracked tree, VERSION == system bundle, a CHANGELOG section, no `requires_chi_next` left, no `# Unreleased` comment left in `Plugin::Api::SINCE`, `bundles:check`, `rake lint` (RuboCop) + `npm run lint` (the web frontend's ESLint), rspec (both roots: `spec` and the bundles' own `spec/`) + `rake bundles:specs` + npm test, `gem build`, then a clean install into a temp GEM_HOME that runs `chi --version`, `chi self` and `chi bundle list` with a temp HOME. Needs the network (gem dependencies). |
|
|
80
|
+
| `rake release:check` | Clean tracked tree, VERSION == system bundle, a CHANGELOG section, no `requires_chi_next` left, no `# Unreleased` comment left in `Plugin::Api::SINCE` or `Manifest::SINCE`, `bundles:check`, `rake lint` (RuboCop) + `npm run lint` (the web frontend's ESLint), rspec (both roots: `spec` and the bundles' own `spec/`) + `rake bundles:specs` + npm test, `gem build`, then a clean install into a temp GEM_HOME that runs `chi --version`, `chi self` and `chi bundle list` with a temp HOME. Needs the network (gem dependencies). |
|
|
76
81
|
| `rake release:notes[X.Y.Z]` | Prints the CHANGELOG section (default: the current VERSION). |
|
|
77
82
|
|
|
78
83
|
## Runbook
|
data/docs/sessions.md
CHANGED
|
@@ -495,7 +495,7 @@ mise.toml pins Ruby 3.4 gets that `ruby`, `gem` and `bundle` in its tools while
|
|
|
495
495
|
The `delegate` tool hands a task to a **child session**: an ordinary chi session in a worker of its own, started in the parent's `working_directory` with the task, verbatim, as its first user message, on the parent's model unless the call names one (`model:` takes a name or an alias, resolved as `--model` is; an unknown host comes back as the tool's `Error:` line; an id the host's saved model list doesn't have starts the child with a `Warning: host 'h' doesn't list model 'x' (did you mean: …?)` line in the result). Children run in parallel and only their **final reply** comes back to the parent: the child's newest `output/<timestamp>.txt` file, which the worker writes at the end of each turn that produced visible text, cut head-and-tail above 64 KB (`DelegateWait.cut`); a reply longer than `max_tool_output_chars` keeps its start and its end (the conclusion), with a gap line naming that `output/` file, where the whole reply is (`ToolOutputCut`'s `:head_tail_ref`; see [Tool output guardrails](internals/tool-output-limits.md#the-runners-cut-max_tool_output_chars)). Nothing of the child's trace enters the parent's context. Nothing is hidden: a child shows in `chi sessions list` (`↳ <parent>`), the web (folded into its parent's card on the strip and in all sessions, whose `▸ N delegates` chip lists the delegates at any depth, see Web UI above; a card of its own with a `↳` chip when its parent isn't listed; `↑ <parent id>` in its info bar, its tooltip `delegated by session …`; the parent's info bar carries a `⑂ N` chip counting its delegates, with how many run, wait, are done, failed, stopped or idle in the tooltip and the waiting ones named in the attention colour; a click opens all sessions), the terminal's status row (a parent's `⑂ 2 running · 1 waiting`, see [Status Line](cli.md#status-line)) and the `list_sessions` tool (`child`; the parent shows as `parent` in the child's own list); the user can `chi --attach <child id>` and steer it mid-run, and `chi send` and `send_note` reach it like any session.
|
|
496
496
|
|
|
497
497
|
- `delegate(task, model:, session:, cwd:, wait:, timeout:)`: starts a child and, by default, waits for its reply. `wait: false` is the usual way for work that takes a while, several children side by side, or to keep talking with the user: it returns at once, the parent ends its turn or works on, and the reply comes back by itself as a *delegate report* (below), in a turn of its own if the parent is idle. The tool descriptions tell the model so, and to wait (`wait: true`, `delegate_result`) only when its turn can't go on without the reply. `timeout`, default 600 s, leaves a slow child running; 0 checks once without waiting. With `session:` (a child's id or prefix) the task goes to that child as a follow-up instead, delivered like `chi send` (client id `delegate:<parent short id>`, shown as `delegate>` in the child's UIs); only this session's children take one. The wait (`wait: true`, a later `delegate_result`) and the delegate report are for the reply of the child's turn the follow-up went to: its running turn, which it merges into, or the turn of its own it runs when the child is idle (`turn` in `delegates.json`, set when the child's worker names `turn_ids`), not a newer reply of a turn queued behind it. A follow-up merged as a steer into a turn the child started at that moment (a wake, a reminder, another message) gets that turn's reply or question. A child on its first turn, or with an older worker, gives its next reply, as before.
|
|
498
|
-
- `cwd:` starts the child in another folder of the parent's repository: a linked worktree (`git worktree add ../<repo>-<task> -b <branch>`, which the model runs with `execute` first, under guardrails; chi runs no git here) or a subfolder; a relative path is taken from the parent's folder. The folder must exist, have the parent's project root (the same common git dir, `MemoryPaths.project_root`) and be in a work tree: a git dir (`.git`, `.git/objects`, a bare layout's `.bare`) or a bare layout's container is refused (`git rev-parse --is-inside-work-tree`, read-only, decides). Outside git only a subfolder of the parent's folder is allowed. Otherwise the tool returns `Error: cwd must be a folder of this session's repository …` or `… is not in a work tree …`. A follow-up (`session:`) refuses `cwd:`: the child keeps its folder. The child's guardrails measure from its own worktree, and project memories, `list_sessions` and the web's project are the same as the parent's. With no `cwd:` and a task that names another checkout of the same repository (a path in the task belonging to a linked worktree, read from the files with `Guardrails::GitInfo.checkouts`, no git process), the result carries `Warning: this child runs in <folder>, but its task names <checkout>. If it should work there, stop it (chi sessions stop <id>) and call delegate again without session, with cwd: <checkout>.` One line however many are named; nothing for a path of the child's own checkout, a folder that isn't one,
|
|
498
|
+
- `cwd:` starts the child in another folder of the parent's repository: a linked worktree (`git worktree add ../<repo>-<task> -b <branch>`, which the model runs with `execute` first, under guardrails; chi runs no git here) or a subfolder; a relative path is taken from the parent's folder. The folder must exist, have the parent's project root (the same common git dir, `MemoryPaths.project_root`) and be in a work tree: a git dir (`.git`, `.git/objects`, a bare layout's `.bare`) or a bare layout's container is refused (`git rev-parse --is-inside-work-tree`, read-only, decides). Outside git only a subfolder of the parent's folder is allowed. Otherwise the tool returns `Error: cwd must be a folder of this session's repository …` or `… is not in a work tree …`. A follow-up (`session:`) refuses `cwd:`: the child keeps its folder. The child's guardrails measure from its own worktree, and project memories, `list_sessions` and the web's project are the same as the parent's. With no `cwd:` and a task that names another checkout of the same repository (a path in the task belonging to a linked worktree, read from the files with `Guardrails::GitInfo.checkouts`, no git process), the result carries `Warning: this child runs in <folder>, but its task names <checkout>. If it should work there, stop it (chi sessions stop <id>) and call delegate again without session, with cwd: <checkout>.` One line however many are named; nothing for a path of the child's own checkout, a folder that isn't one, a path with spaces, or the main checkout when the child runs in a linked worktree of it (the work belongs in the worktree, so naming a main-checkout file is not a forgotten `cwd:`). The child starts as it would anyway, and with `wait: true` (the default) the line comes after the child ran, so it only helps with `wait: false`. A `Warning:` for an unknown model id comes first, then this one. `Log.info(:worker, "delegate_other_checkout", parent:)` records each line.
|
|
499
499
|
- `delegate_result(session:, timeout:)`: waits for a child's next reply: the one named, or the newest running child (`timeout` as for `delegate`: 0 checks once and returns at once). The reply comes back once; a later call waits for the next one. After `wait: false` it isn't needed: the report brings the reply.
|
|
500
500
|
- A wait also ends early when a person's message waits for the parent's turn's next step (the `task_wait` rule: `Engine#input_waiting?`, `SessionInbox.steer_waiting?`, never in a participant's turn): `ReplyWait`'s `message_waiting:` probe gives `:message_waiting`, checked after every outcome the child can arrive at (a reply, a question, an end, a gone worker) and a cancel, before the deadline, and the result says `no reply yet: a message from the user is waiting; the child keeps running. Read the message first.` with the same last line as a timeout's. The child is never touched, the cursor doesn't move (the reply still comes, as a report or at the next `delegate_result`), a relayed approval's outcome line stays in the result, and a reserved report reads as for a timeout. A delegate child's report doesn't end a wait. This holds for `delegate`'s own wait too (both go through `DelegateWait`).
|
|
501
501
|
- A wait that timed out or was canceled still gets its delegate report: neither moves the parent's cursor, so the child's reply comes as a report when its turn ends. The result says so (`chi brings its reply here by itself as a delegate report …`; with `queue`, that it joins the next step or turn) and that `delegate_result` waits again only if the turn needs the reply now. A parent that gets no reports (`session.delegate_reports: off`, a `--no-shared` REPL, `--non-interactive`) is told `delegate_result <id> waits again` instead, and `wait: false`'s result says `delegate_result waits for its reply`.
|
|
@@ -76,10 +76,10 @@ module Samagotchi
|
|
|
76
76
|
def for_installed
|
|
77
77
|
taken = {}
|
|
78
78
|
MemoryBundle::Provenance.each_installed do |bundle, record|
|
|
79
|
-
|
|
80
|
-
next if
|
|
79
|
+
manifest = record.manifest
|
|
80
|
+
next if manifest.requires_chi_failure(VERSION)
|
|
81
81
|
|
|
82
|
-
renderers(bundle,
|
|
82
|
+
renderers(bundle, manifest).each do |renderer|
|
|
83
83
|
taken[renderer.lang] ||= Installed.new(lang: renderer.lang, bundle: bundle, files: renderer.load)
|
|
84
84
|
end
|
|
85
85
|
end
|
|
@@ -104,11 +104,12 @@ module Samagotchi
|
|
|
104
104
|
raise Invalid, "bundle #{bundle}'s scripts/#{file} differs from the installed one (reinstall the bundle)"
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
# Its parsed renderers; a list that doesn't parse is none (logged).
|
|
108
|
+
def renderers(bundle, manifest)
|
|
109
|
+
if (problem = manifest.problems["block_renderers"])
|
|
110
|
+
Log.warn(:web, "block_renderers_unreadable", bundle: bundle, msg: problem)
|
|
111
|
+
end
|
|
112
|
+
manifest.block_renderers
|
|
112
113
|
end
|
|
113
114
|
private_class_method :renderers
|
|
114
115
|
end
|
|
@@ -84,6 +84,7 @@ module Samagotchi
|
|
|
84
84
|
if manifest
|
|
85
85
|
missing = check_files(manifest)
|
|
86
86
|
check_source_tree(manifest, missing) unless @installed
|
|
87
|
+
check_keys(manifest)
|
|
87
88
|
check_requires_chi(manifest)
|
|
88
89
|
check_trust(manifest) unless @installed
|
|
89
90
|
check_clash(manifest)
|
|
@@ -147,6 +148,44 @@ module Samagotchi
|
|
|
147
148
|
index && (index + 1)
|
|
148
149
|
end
|
|
149
150
|
|
|
151
|
+
# ── the manifest's own keys ───────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
# A key this chi's parser never reads is one it ignores, and a key whose
|
|
154
|
+
# floor is above what requires_chi admits is one an older chi would
|
|
155
|
+
# ignore too. The floor is the author's fix, so an installed bundle gets
|
|
156
|
+
# only the unknown-key line, as check_floor's call floor does today.
|
|
157
|
+
def check_keys(manifest)
|
|
158
|
+
manifest.unknown_keys.each do |key|
|
|
159
|
+
add(:warn, :"unknown-key", "manifest.yml", MemoryBundle::Manifest.unknown_key_line(key), line: manifest_line(key),
|
|
160
|
+
fix: "update chi, or remove #{key}: if it's a typo")
|
|
161
|
+
end
|
|
162
|
+
check_key_floor(manifest) unless @installed
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# The manifest uses a key the oldest chi requires_chi admits never read.
|
|
166
|
+
# No plugin needed: the key is the bundle's own claim.
|
|
167
|
+
def check_key_floor(manifest)
|
|
168
|
+
key, version = manifest.floor_keys.max_by { |_, floor| Gem::Version.new(floor) }
|
|
169
|
+
return if key.nil?
|
|
170
|
+
|
|
171
|
+
bound = lower_bound(manifest.requires_chi)
|
|
172
|
+
return if bound && Gem::Version.new(version) <= bound
|
|
173
|
+
|
|
174
|
+
add(:warn, :"requires-chi-floor", "manifest.yml",
|
|
175
|
+
"#{manifest.requires_chi ? "requires_chi #{manifest.requires_chi.inspect} admits chi #{bound}," : "no requires_chi,"} " \
|
|
176
|
+
"but the manifest uses #{key_label(key)}, which needs chi #{version}",
|
|
177
|
+
line: manifest_line(key.split(".").first), fix: "requires_chi: \">= #{version}\"")
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# How to name a SINCE key in a message: a shape is not a key on its own.
|
|
181
|
+
def key_label(key)
|
|
182
|
+
case key
|
|
183
|
+
when "files.description" then "a files: entry given as a mapping"
|
|
184
|
+
when "session_settings.types", "settings.types" then "#{key.split(".").first}: as a mapping of keys to types"
|
|
185
|
+
else "#{key}:"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
150
189
|
# ── files ─────────────────────────────────────────────────────────
|
|
151
190
|
|
|
152
191
|
# @return [Boolean] whether a listed file is missing
|
|
@@ -340,15 +379,16 @@ module Samagotchi
|
|
|
340
379
|
return if manifest.settings.nil? || @settings.empty?
|
|
341
380
|
|
|
342
381
|
require_relative "../bundle_settings_check"
|
|
343
|
-
|
|
344
|
-
session_settings: manifest.session_settings)
|
|
345
|
-
BundleSettingsCheck.record_warnings(record, @settings).each { |line| add(:warn, :settings, "manifest.yml", line, line: manifest_line("settings")) }
|
|
382
|
+
BundleSettingsCheck.record_warnings(manifest, @settings).each { |line| add(:warn, :settings, "manifest.yml", line, line: manifest_line("settings")) }
|
|
346
383
|
end
|
|
347
384
|
|
|
348
385
|
def install_warnings(warnings)
|
|
349
386
|
stale_reported = @findings.any? { |f| f.code == :sha }
|
|
350
387
|
warnings.each do |warning|
|
|
351
388
|
next if stale_reported && warning.start_with?("Checksum mismatch")
|
|
389
|
+
# An unknown manifest key is check_keys' row, with the fix the install
|
|
390
|
+
# can't offer; the same condition twice would be two rows.
|
|
391
|
+
next if warning.include?("doesn't know the manifest key")
|
|
352
392
|
next if @too_new && warning.include?("won't load:")
|
|
353
393
|
# This machine's PATH, said at install; nothing a chi update changed.
|
|
354
394
|
next if @installed && warning.start_with?("needs ")
|
|
@@ -58,13 +58,17 @@ module Samagotchi
|
|
|
58
58
|
private_class_method :in_child
|
|
59
59
|
|
|
60
60
|
# What the child sends: an edited file, which the copy's own shas can't
|
|
61
|
-
# tell,
|
|
62
|
-
#
|
|
61
|
+
# tell, a manifest key that doesn't parse (the copy holds the mirror's
|
|
62
|
+
# value or none: InstalledManifest#key_problems), and Check's warnings
|
|
63
|
+
# and errors, naming the installed bundle's folder rather than the copy's.
|
|
63
64
|
def answer_of(bundle)
|
|
64
65
|
edited = MemoryBundle::ShippedUpdate.edited_executables(bundle.name, bundle).map do |file|
|
|
65
66
|
Check::Finding.new(level: :warn, code: :edited, file: file, line: nil, fix: "chi bundle upgrade #{bundle.name}, or reinstall it",
|
|
66
67
|
message: "changed since it was installed, so chi doesn't load it")
|
|
67
68
|
end
|
|
69
|
+
edited += bundle.manifest.key_problems.values.map do |message|
|
|
70
|
+
Check::Finding.new(level: :error, code: :manifest, file: "manifest.yml", line: nil, message: message, fix: nil)
|
|
71
|
+
end
|
|
68
72
|
edited = edited.map { |finding| { level: finding.level, text: finding.to_s } }
|
|
69
73
|
installed_dir = MemoryBundle::Provenance.new(name: bundle.name).bundle_dir
|
|
70
74
|
edited + MemoryBundle::InstalledCopy.with(bundle) do |dir|
|
|
@@ -101,6 +101,9 @@ module Samagotchi
|
|
|
101
101
|
raise
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# The environment to spawn a child with: what the sandbox keeps, with
|
|
105
|
+
# every key it dropped present as nil (ChildEnv.spawn_env), so the
|
|
106
|
+
# child doesn't inherit it from this process.
|
|
104
107
|
def child_env = @child_env.dup
|
|
105
108
|
|
|
106
109
|
# Installs the tried bundle, then the shipped guardrails and
|
|
@@ -219,7 +222,12 @@ module Samagotchi
|
|
|
219
222
|
# The user's environment (their hosts and keys) minus what no child
|
|
220
223
|
# chi takes from its spawner (ChildEnv), the worker markers, this
|
|
221
224
|
# chi's log and test settings, and the env forms of the config
|
|
222
|
-
# sections the sandbox drops.
|
|
225
|
+
# sections the sandbox drops. ChildEnv.spawn_env makes every dropped
|
|
226
|
+
# key an unset in the child: a Hash handed to Open3 or Process.spawn
|
|
227
|
+
# is merged onto this process's environment, so deleting a key from
|
|
228
|
+
# it alone still passes it on. The Bundler changes come from ENV, not
|
|
229
|
+
# from this copy: read from the copy, a key dropped above would look
|
|
230
|
+
# like one Bundler added and come back with its value.
|
|
223
231
|
def build_child_env
|
|
224
232
|
env = ChildEnv.scrub(ENV.to_h)
|
|
225
233
|
env.delete("SAMAGOTCHI_LOG_FILE")
|
|
@@ -235,8 +243,8 @@ module Samagotchi
|
|
|
235
243
|
"SAMAGOTCHI_KEYCHAIN" => "on",
|
|
236
244
|
**@layout.env
|
|
237
245
|
)
|
|
238
|
-
env.merge!(ChildEnv.unbundled
|
|
239
|
-
env
|
|
246
|
+
env.merge!(ChildEnv.unbundled)
|
|
247
|
+
ChildEnv.spawn_env(env)
|
|
240
248
|
end
|
|
241
249
|
end
|
|
242
250
|
end
|
|
@@ -16,11 +16,14 @@ module Samagotchi
|
|
|
16
16
|
Install a memory bundle from a directory, zip, tar archive, or git URL.
|
|
17
17
|
Source is the path/URL to the bundle (dir/zip/tar.gz/git).
|
|
18
18
|
--force overwrites existing entries (one that differs is copied to the trash first); default skips them.
|
|
19
|
+
A bundle whose requires_chi this chi doesn't meet is refused; --force installs it anyway.
|
|
19
20
|
|
|
20
|
-
upgrade <source> [--scope system|project] [--force] [--dry-run] [--agent|--no-agent]
|
|
21
|
+
upgrade <source|name> [--scope system|project] [--force] [--dry-run] [--agent|--no-agent]
|
|
21
22
|
Upgrade a bundle (3-way: a file you didn't edit updates; one you edited takes a text
|
|
22
23
|
merge, the old file in the bundle trash; a real conflict leaves it as it is).
|
|
24
|
+
A bare NAME upgrades the bundle installed under that name from the source it came from.
|
|
23
25
|
--dry-run shows what would change. --agent launches interactive session on conflict.
|
|
26
|
+
A bundle whose requires_chi this chi doesn't meet is refused; --force installs it anyway.
|
|
24
27
|
|
|
25
28
|
uninstall <bundle> [--scope system|project] [--force]
|
|
26
29
|
Remove a bundle and its index entries (--force moves a locally edited file to the trash).
|
|
@@ -69,13 +72,19 @@ module Samagotchi
|
|
|
69
72
|
Install a memory bundle from a directory, zip, tar archive, or git URL,
|
|
70
73
|
or a bundle shipped with chi by name (see: chi bundle list).
|
|
71
74
|
--force overwrites existing entries (one that differs is copied to the trash first); default skips them.
|
|
75
|
+
It also installs a bundle whose requires_chi this chi doesn't meet: without --force that install is
|
|
76
|
+
refused, and the bundle is not written anywhere.
|
|
72
77
|
install --dev PATH
|
|
73
78
|
Install the bundle in the local directory PATH as a link to it: its plugin, hooks, rules and scripts
|
|
74
79
|
are PATH's own files (an edit applies at the next worker or chi web restart, with no re-install) and
|
|
75
80
|
their sha256 isn't checked, for this bundle only. Its memories are copied; re-install with --force to
|
|
76
81
|
pick up memory edits. Upgrade refuses it; uninstall removes the link, never PATH's files.
|
|
77
82
|
TEXT
|
|
78
|
-
UPGRADE_HELP =
|
|
83
|
+
UPGRADE_HELP = <<~TEXT
|
|
84
|
+
Usage: chi bundle upgrade <source|name> [--scope system|project] [--force] [--dry-run] [--agent|--no-agent]
|
|
85
|
+
--force takes the bundle's file over an edited one, and installs a bundle whose requires_chi this chi
|
|
86
|
+
doesn't meet (without it such a bundle is refused).
|
|
87
|
+
TEXT
|
|
79
88
|
UNINSTALL_HELP = "Usage: chi bundle uninstall <bundle> [--scope system|project] [--force]"
|
|
80
89
|
BUILD_HELP = <<~TEXT
|
|
81
90
|
Usage: chi bundle build [--scope system|project] [--name NAME] [--version VER] [--description DESC] [--out PATH] [FILES...]
|
|
@@ -210,7 +219,13 @@ module Samagotchi
|
|
|
210
219
|
@stderr.puts UPGRADE_HELP
|
|
211
220
|
return CLI::Exit::USAGE
|
|
212
221
|
end
|
|
213
|
-
expanded_source =
|
|
222
|
+
expanded_source, record = upgrade_source(source)
|
|
223
|
+
return 1 if expanded_source.nil?
|
|
224
|
+
|
|
225
|
+
# The record's source: said once, so it is clear which bundle and where
|
|
226
|
+
# it comes from when the name alone was given.
|
|
227
|
+
@stdout.puts "Upgrading #{record.name} from #{expanded_source}" if record
|
|
228
|
+
|
|
214
229
|
if Samagotchi::MemoryBundle::Profile.shipped_meta?(expanded_source)
|
|
215
230
|
return install_profile(expanded_source, scope: scope, force: force, dry_run: dry_run, word: "Upgrade")
|
|
216
231
|
end
|
|
@@ -305,7 +320,11 @@ module Samagotchi
|
|
|
305
320
|
|
|
306
321
|
def print_hooks_and_plugin(manifest, plugin: true)
|
|
307
322
|
@stdout.puts "Hooks: #{manifest.hooks.size} hook(s) (#{manifest.hooks.keys.join(", ")})" if manifest&.hooks&.any?
|
|
308
|
-
|
|
323
|
+
return unless plugin && manifest&.plugin
|
|
324
|
+
|
|
325
|
+
# Installed with --force past its requires_chi: the Engine won't load it.
|
|
326
|
+
failure = Samagotchi::MemoryBundle::Manifest.requires_chi_failure(manifest.requires_chi, Samagotchi::VERSION)
|
|
327
|
+
@stdout.puts "Plugin: #{manifest.plugin[:file]} (#{failure ? "won't load on this chi: #{failure}" : "loads at the next chi start"})"
|
|
309
328
|
end
|
|
310
329
|
|
|
311
330
|
# @return [Integer] 0
|
|
@@ -499,9 +518,11 @@ module Samagotchi
|
|
|
499
518
|
label += "; #{info[:merge_note].line}#{" (old copy in #{info[:merge_note].trash})" if info[:merge_note].trash}" if info[:merge_note]
|
|
500
519
|
@stdout.puts " #{k}: #{label}"
|
|
501
520
|
end
|
|
521
|
+
@stdout.puts " #{manifest_line(st.provenance)}"
|
|
502
522
|
# Hooks
|
|
503
|
-
|
|
504
|
-
|
|
523
|
+
manifest = st.provenance.manifest
|
|
524
|
+
hooks = manifest.hooks
|
|
525
|
+
trust = manifest.trust_level || "experimental"
|
|
505
526
|
commit = st.provenance.source_commit
|
|
506
527
|
@stdout.puts " trust_level: #{trust}" if hooks.any? || trust
|
|
507
528
|
@stdout.puts " source_commit: #{commit}" if commit
|
|
@@ -512,7 +533,7 @@ module Samagotchi
|
|
|
512
533
|
exists = File.exist?(hook_path) ? "ok" : "missing"
|
|
513
534
|
@stdout.puts " #{hk}: #{hook.events_field} on_error=#{hook.on_error} priority=#{hook.priority} [#{exists}]"
|
|
514
535
|
end
|
|
515
|
-
@stdout.puts " requires_chi: #{
|
|
536
|
+
@stdout.puts " requires_chi: #{manifest.requires_chi}" if st.hooks_requires_failure
|
|
516
537
|
@stdout.puts " not loaded: #{st.hooks_requires_failure}" if st.hooks_requires_failure
|
|
517
538
|
end
|
|
518
539
|
if (plugin = st.plugin)
|
|
@@ -534,7 +555,7 @@ module Samagotchi
|
|
|
534
555
|
|
|
535
556
|
st = Samagotchi::MemoryBundle::Status.bundle_status(bname)
|
|
536
557
|
mods = st.files.values.count { |v| v[:behind] || v[:missing] || v[:unreadable] }
|
|
537
|
-
hooks_count = st.provenance.hooks.size
|
|
558
|
+
hooks_count = st.provenance.manifest.hooks.size
|
|
538
559
|
hook_info = hooks_count > 0 ? " hooks=#{hooks_count}" : ""
|
|
539
560
|
plugin = st.plugin
|
|
540
561
|
mods += 1 if plugin && (plugin[:state] != "ok" || plugin[:requires_failure])
|
|
@@ -549,6 +570,14 @@ module Samagotchi
|
|
|
549
570
|
0
|
|
550
571
|
end
|
|
551
572
|
|
|
573
|
+
# "Manifest: [kept]", or "[from the install record]", and what its case
|
|
574
|
+
# says (MemoryBundle::ManifestStanding).
|
|
575
|
+
def manifest_line(record)
|
|
576
|
+
require_relative "memory_bundle/manifest_standing"
|
|
577
|
+
standing = Samagotchi::MemoryBundle::ManifestStanding
|
|
578
|
+
["Manifest:", standing.label(record), standing.status_line(record)].compact.join(" ")
|
|
579
|
+
end
|
|
580
|
+
|
|
552
581
|
# " dev → /path (missing)" when a dev install's source isn't there, else "".
|
|
553
582
|
def dev_label(record)
|
|
554
583
|
return "" unless record.dev
|
|
@@ -577,7 +606,7 @@ module Samagotchi
|
|
|
577
606
|
rescue ArgumentError => e
|
|
578
607
|
@stderr.puts "Bundle '#{bname}': #{e.message}"; return 1
|
|
579
608
|
end
|
|
580
|
-
hooks = record.hooks
|
|
609
|
+
hooks = record.manifest.hooks
|
|
581
610
|
plugin_path = prov.plugin_path(record)
|
|
582
611
|
plugin_file = plugin_path && File.basename(plugin_path)
|
|
583
612
|
if file_arg
|
|
@@ -612,6 +641,26 @@ module Samagotchi
|
|
|
612
641
|
|
|
613
642
|
def diff_memory(prov, target_dir, key)
|
|
614
643
|
print_base_and_current(key, prov.base_path(key), File.join(target_dir, key))
|
|
644
|
+
print_incoming(prov, key)
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# The bundle's text a file stays behind (Provenance#incoming_path), so the
|
|
648
|
+
# sections the "stays behind" line names can be read. No saved copy: nothing.
|
|
649
|
+
def print_incoming(prov, key)
|
|
650
|
+
path = prov.incoming_path(key)
|
|
651
|
+
return unless File.exist?(path)
|
|
652
|
+
|
|
653
|
+
text = begin
|
|
654
|
+
File.read(path)
|
|
655
|
+
rescue SystemCallError
|
|
656
|
+
nil
|
|
657
|
+
end
|
|
658
|
+
if text
|
|
659
|
+
@stdout.puts "--- incoming (the bundle's #{prov.record.version}, which this file stays behind) ---"
|
|
660
|
+
@stdout.puts text
|
|
661
|
+
else
|
|
662
|
+
@stdout.puts "--- incoming: the bundle's saved copy can't be read ---"
|
|
663
|
+
end
|
|
615
664
|
end
|
|
616
665
|
|
|
617
666
|
def diff_hook(prov, key, hook)
|
|
@@ -622,7 +671,8 @@ module Samagotchi
|
|
|
622
671
|
def diff_plugin(prov, record, plugin_path)
|
|
623
672
|
file = File.basename(plugin_path)
|
|
624
673
|
print_base_and_current("plugin/#{file}", prov.plugin_base_path(file), plugin_path)
|
|
625
|
-
|
|
674
|
+
requires_chi = record.manifest.requires_chi
|
|
675
|
+
@stdout.puts "--- metadata: sha256=#{record.plugin.sha256}#{" requires_chi=#{requires_chi}" if requires_chi}"
|
|
626
676
|
end
|
|
627
677
|
|
|
628
678
|
def print_base_and_current(heading, base, current)
|
|
@@ -926,6 +976,34 @@ module Samagotchi
|
|
|
926
976
|
Samagotchi::MemoryBundle::SourceNormalizer.shipped_bundle_dir(src) || File.expand_path(src)
|
|
927
977
|
end
|
|
928
978
|
|
|
979
|
+
# Where an upgrade comes from: the source as given — a git URL, an
|
|
980
|
+
# existing path, a shipped bundle's name, as install takes them — or,
|
|
981
|
+
# for the bare name of an installed bundle, the source its record keeps.
|
|
982
|
+
# A dev install is refused (it already follows its source), and a record
|
|
983
|
+
# whose source is relative, empty or gone says so. nil after such a
|
|
984
|
+
# refusal, written to stderr.
|
|
985
|
+
# @return [Array(String, InstalledBundle, nil), nil] [the source, the
|
|
986
|
+
# record that named it (nil for a source as given)]
|
|
987
|
+
def upgrade_source(source)
|
|
988
|
+
normalizer = Samagotchi::MemoryBundle::SourceNormalizer
|
|
989
|
+
given = normalizer.git_url?(source) || normalizer.shipped_bundle_dir(source) || File.exist?(File.expand_path(source))
|
|
990
|
+
return [expand_source(source), nil] if given
|
|
991
|
+
|
|
992
|
+
record = Samagotchi::MemoryBundle::Provenance.new(name: source.to_s.strip).record
|
|
993
|
+
return [expand_source(source), nil] if record.nil? || record.error?
|
|
994
|
+
|
|
995
|
+
if record.dev
|
|
996
|
+
@stderr.puts "Upgrade failed: #{Samagotchi::MemoryBundle::Installer.dev_install_line(record.name, record.source)}"
|
|
997
|
+
return nil
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
recorded = record.source.to_s.strip
|
|
1001
|
+
return [recorded, record] if normalizer.git_url?(recorded) || (recorded.start_with?("/") && File.exist?(recorded))
|
|
1002
|
+
|
|
1003
|
+
@stderr.puts "Upgrade failed: #{Samagotchi::MemoryBundle::Installer.source_gone_line(record.name, recorded)}"
|
|
1004
|
+
nil
|
|
1005
|
+
end
|
|
1006
|
+
|
|
929
1007
|
def build_conflict_prompt(bundle_name, conflicts, expanded_source)
|
|
930
1008
|
lines = []
|
|
931
1009
|
lines << "Memory bundle upgrade has conflicts that need interactive resolution."
|
|
@@ -66,13 +66,11 @@ module Samagotchi
|
|
|
66
66
|
# missing need. A manifest.json or needs: that doesn't parse is skipped.
|
|
67
67
|
def self.entry_markers(scope, path:, bundles_dir:)
|
|
68
68
|
MemoryBundle::Provenance.each_installed(dir: bundles_dir).each_with_object({}) do |(_name, bundle), acc|
|
|
69
|
-
next if bundle.error? || bundle.effective_scope != scope.to_s
|
|
69
|
+
next if bundle.error? || bundle.effective_scope != scope.to_s
|
|
70
|
+
|
|
71
|
+
needs = bundle.manifest.needs
|
|
72
|
+
next if needs.empty?
|
|
70
73
|
|
|
71
|
-
needs = begin
|
|
72
|
-
MemoryBundle::Manifest.parse_needs(bundle.needs)
|
|
73
|
-
rescue MemoryBundle::Manifest::ValidationError
|
|
74
|
-
next
|
|
75
|
-
end
|
|
76
74
|
marker = marker(missing(needs, path: path))
|
|
77
75
|
next unless marker
|
|
78
76
|
|
|
@@ -32,7 +32,7 @@ module Samagotchi
|
|
|
32
32
|
next ["config.yml bundles: #{name}: #{SessionConfig.no_installed_bundle(name)}#{near(name, installed.keys)}"] unless bundle
|
|
33
33
|
next [] if bundle.error?
|
|
34
34
|
|
|
35
|
-
record_warnings(bundle, values)
|
|
35
|
+
record_warnings(bundle.manifest, values)
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -62,23 +62,24 @@ module Samagotchi
|
|
|
62
62
|
lines
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
# One bundle's +values+ against its
|
|
66
|
-
# while it declares no settings:.
|
|
67
|
-
# @param
|
|
65
|
+
# One bundle's +values+ against its manifest (an installed one's or a
|
|
66
|
+
# source's); none while it declares no settings:.
|
|
67
|
+
# @param manifest [MemoryBundle::InstalledManifest, MemoryBundle::Manifest]
|
|
68
|
+
# name, settings, session_settings, setting_types
|
|
68
69
|
# @return [Array<String>]
|
|
69
|
-
def record_warnings(
|
|
70
|
-
return [] if
|
|
70
|
+
def record_warnings(manifest, values)
|
|
71
|
+
return [] if manifest.settings.nil?
|
|
71
72
|
|
|
72
|
-
values.flat_map { |key, value| value_warnings(
|
|
73
|
+
values.flat_map { |key, value| value_warnings(manifest, key.to_s, value) }
|
|
73
74
|
end
|
|
74
75
|
|
|
75
|
-
def value_warnings(
|
|
76
|
-
where = "config.yml bundles: #{
|
|
77
|
-
known =
|
|
78
|
-
return ["#{where}: #{SessionConfig.takes_keys(
|
|
76
|
+
def value_warnings(manifest, key, value)
|
|
77
|
+
where = "config.yml bundles: #{manifest.name}.#{key}"
|
|
78
|
+
known = manifest.settings | manifest.session_settings
|
|
79
|
+
return ["#{where}: #{SessionConfig.takes_keys(manifest.name, known)}#{near(key, known)}"] unless known.include?(key)
|
|
79
80
|
|
|
80
|
-
takes, what = SessionConfig::TYPES[
|
|
81
|
-
takes && !takes.call(value) ? ["#{where}: #{SessionConfig.wrong_type(
|
|
81
|
+
takes, what = SessionConfig::TYPES[manifest.setting_types[key]]
|
|
82
|
+
takes && !takes.call(value) ? ["#{where}: #{SessionConfig.wrong_type(manifest.name, key, what, value)}"] : []
|
|
82
83
|
end
|
|
83
84
|
private_class_method :value_warnings
|
|
84
85
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agents-md
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.2
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Steers a nested AGENTS.md into the conversation the first time a tool touches a file under its directory"
|
|
6
6
|
trust_level: reviewed
|
|
@@ -8,6 +8,6 @@ plugin:
|
|
|
8
8
|
file: plugin.rb
|
|
9
9
|
sha256: sha256:56527b9d12cb9acd8f57ca00881ab159894ae2fb8def22bce66ac3774bb4187b
|
|
10
10
|
# ctx.prompt_files, read when the plugin loads (0.60.0)
|
|
11
|
-
requires_chi: ">= 0.
|
|
11
|
+
requires_chi: ">= 0.143.0"
|
|
12
12
|
# config.yml bundles: agents-md: (another key warns; an older chi ignores this)
|
|
13
13
|
settings: [tools, max_chars]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: btw
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.4
|
|
4
4
|
scope: system
|
|
5
5
|
description: "/btw <question>: a side answer about the conversation, as a card in every UI, even mid-turn; Keep as session forks it into a child session"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
9
|
sha256: sha256:6497129b06d1c3061c1d7d9d7a9c3dfdbcf790e8f2049addad0c26d24771015f
|
|
10
|
-
requires_chi: ">= 0.
|
|
10
|
+
requires_chi: ">= 0.143.0"
|
|
11
11
|
# config.yml bundles: btw: (another key warns; an older chi ignores this)
|
|
12
12
|
settings: [max_tokens, timeout]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: loop-guard
|
|
3
|
-
version: 0.3.
|
|
3
|
+
version: 0.3.10
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Breaks loops: the same tool call with the same result again and again in one turn is denied with advice (after a few denies the turn stops with a card), and thinking that repeats itself while it streams is cut and the model asked again"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
9
|
sha256: sha256:a4ac7c48a903716b7e1c4fa35348439e90ac97d3ad421378ce7d3ac819229a09
|
|
10
|
-
requires_chi: ">= 0.
|
|
10
|
+
requires_chi: ">= 0.143.0"
|
|
11
11
|
# config.yml bundles: loop-guard: (another key warns; an older chi ignores this)
|
|
12
12
|
settings: [deny_after, stop_after, ignore_tools, mode, thinking]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mcp
|
|
3
|
-
version: 0.7.
|
|
3
|
+
version: 0.7.6
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Tools from MCP servers (stdio): each server in config.yml bundles: mcp: servers: runs for the session; their tools go into chi's tool index: the model searches them with find_tools and calls one with call_tool (<server>/<tool>); /mcp lists them"
|
|
6
6
|
trust_level: reviewed
|
|
@@ -9,6 +9,6 @@ plugin:
|
|
|
9
9
|
file: plugin.rb
|
|
10
10
|
sha256: sha256:718e53db877eacd2714e69427c69cfc6c551aaf3417bb760a27dac3331363db5
|
|
11
11
|
# 0.67.0 is the release with chi.tool_source (the core tool index the bundle publishes into).
|
|
12
|
-
requires_chi: ">= 0.
|
|
12
|
+
requires_chi: ">= 0.143.0"
|
|
13
13
|
# config.yml bundles: mcp: (another key warns; an older chi ignores this)
|
|
14
14
|
settings: [servers, timeout, startup_timeout]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skills
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.9
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Skills (skill_<name> memories, the steps of a task done together): /skill save, list, show and diff; older versions kept with a short diff line on every update; a nudge when a followed skill's step fails"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
9
|
sha256: sha256:781d943749d59ade0e24d04ba774449d31267eb8ca435835c36c67a3e523e632
|
|
10
|
-
requires_chi: ">= 0.
|
|
10
|
+
requires_chi: ">= 0.143.0"
|
|
11
11
|
# config.yml bundles: skills: (another key warns; an older chi ignores this)
|
|
12
12
|
settings: [history_keep, nudge]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: slow-commands
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.5
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Remembers commands that ran past the execute limit in a project; the next identical execute is denied once a turn with advice to run it with task_create and task_wait; /slow-commands lists and forgets them"
|
|
6
6
|
trust_level: reviewed
|
|
@@ -9,6 +9,6 @@ plugin:
|
|
|
9
9
|
sha256: sha256:e51c099c1552bd0cd1fe46e4fd235a3fe8340693e6ca338eb7ed09d8398a6f0f
|
|
10
10
|
# Execute::HANDED_OVER, read when the plugin loads (0.59.0); after_tool_call's
|
|
11
11
|
# call:, elapsed_ms: and result: (0.57.0)
|
|
12
|
-
requires_chi: ">= 0.
|
|
12
|
+
requires_chi: ">= 0.143.0"
|
|
13
13
|
# config.yml bundles: slow-commands: (another key warns; an older chi ignores this)
|
|
14
14
|
settings: [mode, warn_ratio, ttl_days]
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: samagotchi-system
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.152.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:e695f78f858c29ccfef2ed6f9d6dd86d0ec067ed704a5a1956c01e53617948a1
|
|
9
9
|
config_modification_protocol.md: sha256:8896d1d364ce835040f69b504518d43dbdf959b93fdd9b10f87a8de61cae84a8
|
|
10
|
-
memory_guide.md: sha256:
|
|
10
|
+
memory_guide.md: sha256:8b7727b939b9c04486ca59859561023dcc319a73e0f5b47b3fcf5741edaa69e2
|
|
11
11
|
delegated.md: sha256:a0d6ddcd4a8be06526d964898611775fd251cfd9d145a9d16afaccaa503597bd
|
|
@@ -113,8 +113,8 @@ Bundles are versioned directories/zips/tar.gz/git URLs with a `manifest.yml` and
|
|
|
113
113
|
|
|
114
114
|
| Command | Purpose |
|
|
115
115
|
|---------|---------|
|
|
116
|
-
| `install <source> [--scope system\|project] [--force]` | Install from dir/zip/tar.gz/git URL. `--force` overwrites existing entries, otherwise skips them; a skipped file stays the user's (not recorded, never upgraded or removed by the bundle). Writes provenance to `.bundles/<name>/` (base snapshots + `manifest.json`) and updates `index.md`. Warns on double-brace placeholders and checksum mismatches (strict mode). |
|
|
117
|
-
| `upgrade <source> [--force] [--dry-run] [--agent]` | 3-way merge upgrade (base vs current vs incoming) per `Merger::Upgrade#outcome` (`Merger.classify`, then a text merge where both sides changed): `install` (new file), `noop` (current==incoming), `fast_forward` (current==base, not edited → auto-update), `keep` (incoming==base → preserve local edits), `merged` (both edited, a text merge with `git` comes out clean → the file takes it, the old one goes to the bundle trash, and entries both sides added under a `Changelog` heading are kept), `conflict` (both edited, a real conflict, or no `git` or base → the file stays untouched and marked, reported as `X stays behind 0.8.0: …` with why and what it missed, needs a retry, `--force`, which copies it to the trash first, or interactive `memory_write` resolution). A marked file is retried at the same version: the system bundle's at each chi start and on `chi update`, another shipped bundle's on `chi update`, any other bundle's on `chi bundle upgrade`. A file marked by an older chi merges normally when its recorded base is a version chi shipped (an installed samagotchi gem still has it); otherwise it merges only if the bundle takes out or rewords no line it still holds, and if it does, it stays behind until resolved by hand, with `--agent` or with `--force`, and the retry can't fix that. Pruned files (removed from new bundle) are kept if locally edited, otherwise warned. |
|
|
116
|
+
| `install <source> [--scope system\|project] [--force]` | Install from dir/zip/tar.gz/git URL. `--force` overwrites existing entries, otherwise skips them; a skipped file stays the user's (not recorded, never upgraded or removed by the bundle). It also installs a bundle whose `requires_chi` this chi doesn't meet: without `--force` that install is refused (`NAME can't be installed: it requires chi …`). Writes provenance to `.bundles/<name>/` (base snapshots + `manifest.json`) and updates `index.md`. Warns on double-brace placeholders and checksum mismatches (strict mode). |
|
|
117
|
+
| `upgrade <source\|name> [--force] [--dry-run] [--agent]` | A bare `name` is an installed bundle: chi upgrades it from the source its record keeps (its git URL with the `#ref` it recorded, its folder, or the archive it came from) — a dev install is refused, and a record whose source is gone says so and asks for the path or URL. Otherwise 3-way merge upgrade (base vs current vs incoming) per `Merger::Upgrade#outcome` (`Merger.classify`, then a text merge where both sides changed): `install` (new file), `noop` (current==incoming), `fast_forward` (current==base, not edited → auto-update), `keep` (incoming==base → preserve local edits), `merged` (both edited, a text merge with `git` comes out clean → the file takes it, the old one goes to the bundle trash, and entries both sides added under a `Changelog` heading are kept), `conflict` (both edited, a real conflict, or no `git` or base → the file stays untouched and marked, reported as `X stays behind 0.8.0: …` with why and what it missed, needs a retry, `--force`, which copies it to the trash first, or interactive `memory_write` resolution). A marked file is retried at the same version: the system bundle's at each chi start and on `chi update`, another shipped bundle's on `chi update`, any other bundle's on `chi bundle upgrade`. A file marked by an older chi merges normally when its recorded base is a version chi shipped (an installed samagotchi gem still has it); otherwise it merges only if the bundle takes out or rewords no line it still holds, and if it does, it stays behind until resolved by hand, with `--agent` or with `--force`, and the retry can't fix that. Pruned files (removed from new bundle) are kept if locally edited, otherwise warned. |
|
|
118
118
|
| `uninstall <bundle> [--force]` | Moves the bundle's memory files to `.bundles/.trash/<bundle>-<time>/` (refuses locally edited ones unless `--force`; keeps a file another installed bundle also lists), removes their `index.md` lines, deletes the provenance dir. |
|
|
119
119
|
| `status [<bundle>]` | Provenance + per-file `ok|modified|missing|unreadable|no-index` vs stored checksum and base snapshot, and what stays behind or merged at a chi start; `issues=` counts files behind, missing or unreadable, not edits; a model overlay (`<name>.<key>.md`) has no index line and reads `ok (model overlay)`. |
|
|
120
120
|
| `diff <bundle> [file]` | Prints `base` (provenance snapshot) vs `current` (on-disk) for each file. |
|
|
@@ -146,7 +146,7 @@ chi bundle build --scope system --out updated.zip
|
|
|
146
146
|
|
|
147
147
|
### Provenance internals (for debugging)
|
|
148
148
|
|
|
149
|
-
- Each installed bundle is recorded at `<system dir>/.bundles/<name>/manifest.json` + `bases/<file>.md` snapshots (`Provenance`). Used only for upgrade `Merger` and `status`/`diff`. Build does **not** write provenance.
|
|
149
|
+
- Each installed bundle is recorded at `<system dir>/.bundles/<name>/manifest.json` + `bases/<file>.md` snapshots (`Provenance`), with the bundle's own `manifest.yml` kept beside it (chi reads the manifest from there at load). Used only for upgrade `Merger` and `status`/`diff`. Build does **not** write provenance.
|
|
150
150
|
- A bundle records only the files it wrote. `index.md` updates aren't fatal: a failed one is a warning in the install/uninstall output.
|
|
151
151
|
|
|
152
152
|
## Best practices for the agent
|