samagotchi 0.150.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 +50 -1
- data/docs/architecture.md +2 -2
- data/docs/bundle-authoring.md +5 -5
- data/docs/cli.md +32 -7
- data/docs/configuration.md +1 -1
- data/docs/guardrails.md +4 -0
- data/docs/internals/block-renderers.md +98 -0
- data/docs/internals/bridge.md +53 -17
- data/docs/internals/bundles.md +49 -14
- data/docs/internals/objectives.md +6 -4
- data/docs/internals/web-frontend.md +2 -2
- data/docs/memory.md +7 -2
- data/docs/plugins.md +91 -14
- data/docs/releasing.md +7 -2
- data/docs/sessions.md +1 -1
- data/lib/samagotchi/block_renderers.rb +116 -0
- data/lib/samagotchi/bridge/card_places.rb +93 -0
- data/lib/samagotchi/bridge/card_store.rb +219 -122
- data/lib/samagotchi/bridge.rb +12 -4
- 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/try/trace.rb +1 -1
- 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 +47 -6
- 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 +18 -9
- data/lib/samagotchi/memory_bundle/builder.rb +39 -31
- 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 -43
- data/lib/samagotchi/memory_bundle/installed_copy.rb +38 -14
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +224 -0
- data/lib/samagotchi/memory_bundle/installer.rb +91 -16
- data/lib/samagotchi/memory_bundle/manifest.rb +166 -5
- 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 +220 -19
- 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_chain.rb +2 -7
- data/lib/samagotchi/session_commands.rb +15 -9
- data/lib/samagotchi/session_config.rb +5 -4
- data/lib/samagotchi/session_manager.rb +10 -7
- data/lib/samagotchi/steer.rb +17 -0
- data/lib/samagotchi/system_one/record.rb +10 -2
- data/lib/samagotchi/terminal_ui.rb +4 -4
- data/lib/samagotchi/tools/delegate.rb +20 -2
- data/lib/samagotchi/turn_checkpoint.rb +11 -5
- data/lib/samagotchi/turn_flow.rb +2 -3
- data/lib/samagotchi/update_command.rb +13 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +24 -13
- data/lib/samagotchi/web/block_renderer_routes.rb +32 -0
- data/lib/samagotchi/web/markdown_renderer.rb +39 -6
- data/lib/samagotchi/web/message_display.rb +3 -3
- data/lib/samagotchi/web/participant_view.rb +1 -1
- data/lib/samagotchi/web/public/app.js +2 -0
- data/lib/samagotchi/web/public/block_frame.js +202 -0
- data/lib/samagotchi/web/public/block_host.js +502 -0
- data/lib/samagotchi/web/public/block_renderers.css +20 -0
- data/lib/samagotchi/web/public/block_source.js +37 -0
- data/lib/samagotchi/web/public/bubbles.js +9 -5
- data/lib/samagotchi/web/public/card.js +17 -10
- data/lib/samagotchi/web/public/cards_place.js +2 -2
- data/lib/samagotchi/web/public/claim_link.js +4 -2
- data/lib/samagotchi/web/public/history_reveal.js +4 -1
- data/lib/samagotchi/web/public/index.html +2 -0
- data/lib/samagotchi/web/public/markdown_place.js +2 -3
- data/lib/samagotchi/web/public/text_roots.js +6 -0
- data/lib/samagotchi/web/public/turn_html.js +4 -1
- data/lib/samagotchi/web/session_access_routes.rb +2 -1
- data/lib/samagotchi/web/session_hub.rb +6 -0
- data/lib/samagotchi/web/session_show.rb +1 -1
- data/lib/samagotchi/worker.rb +4 -2
- metadata +13 -1
|
@@ -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
|
|
@@ -124,7 +124,7 @@ module Samagotchi
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
# The listing's placement fields: nothing to place in a one-turn run.
|
|
127
|
-
LISTING_FIELDS = %w[turns_since current earlier].freeze
|
|
127
|
+
LISTING_FIELDS = %w[turns_since current earlier after_failed label].freeze
|
|
128
128
|
|
|
129
129
|
# The saved cards as the web reads them (CardStore.saved), with
|
|
130
130
|
# string keys and values, as the JSON trace prints them.
|
|
@@ -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
|
data/lib/samagotchi/child_env.rb
CHANGED
|
@@ -48,6 +48,21 @@ module Samagotchi
|
|
|
48
48
|
# @return [Hash]
|
|
49
49
|
def scrub(env) = env.except(*spawner_keys(env))
|
|
50
50
|
|
|
51
|
+
# +env+ as a spawn argument that really removes what it leaves out. A Hash
|
|
52
|
+
# given to Process.spawn, Open3 or exec is merged onto the spawner's
|
|
53
|
+
# environment: a key only deleted from it is still inherited by the child.
|
|
54
|
+
# Every key +spawner+ has and +env+ doesn't is mapped to nil, which
|
|
55
|
+
# Process.spawn unsets, so the child sees exactly +env+ (and #scrub is
|
|
56
|
+
# safe to use with it). +env+ is not changed.
|
|
57
|
+
# @param env [Hash]
|
|
58
|
+
# @param spawner [Hash] the spawner's environment (default: ENV)
|
|
59
|
+
# @return [Hash]
|
|
60
|
+
def spawn_env(env, spawner: ENV)
|
|
61
|
+
out = env.dup
|
|
62
|
+
spawner.each_key { |key| out[key] = nil unless out.key?(key) }
|
|
63
|
+
out
|
|
64
|
+
end
|
|
65
|
+
|
|
51
66
|
# In gem mode, what Bundler changed in +env+ mapped back
|
|
52
67
|
# (InstalledGem.unbundled_env_changes): a child activates the installed
|
|
53
68
|
# gem itself rather than inherit this app's bundle. {} in a checkout.
|
|
@@ -46,6 +46,11 @@ module Samagotchi
|
|
|
46
46
|
RUBY = "{ruby}"
|
|
47
47
|
URL = "{url}"
|
|
48
48
|
MIN_EVERY_SECONDS = 30
|
|
49
|
+
# A bundle whose requires_chi this chi doesn't meet, already logged: said
|
|
50
|
+
# once per process per bundle and version, the way GuardrailWiring says a
|
|
51
|
+
# bundle's kept rules are.
|
|
52
|
+
TOO_NEW_LOGGED = Set.new
|
|
53
|
+
TOO_NEW_LOG_LOCK = Mutex.new
|
|
49
54
|
|
|
50
55
|
module_function
|
|
51
56
|
|
|
@@ -79,15 +84,16 @@ module Samagotchi
|
|
|
79
84
|
|
|
80
85
|
# The source the installed bundles' providers make of +url+: the first
|
|
81
86
|
# bundle (by name) whose provider matches. A bundle whose providers
|
|
82
|
-
# don't parse is skipped
|
|
87
|
+
# don't parse, or whose requires_chi this chi doesn't meet, is skipped
|
|
88
|
+
# (logged).
|
|
83
89
|
# @return [Resolved, nil] nil when none matches
|
|
84
90
|
# @raise [Invalid] the provider makes a name that isn't a source name
|
|
85
91
|
def resolve(url)
|
|
86
92
|
url = url.to_s.strip
|
|
87
93
|
MemoryBundle::Provenance.each_installed do |bundle, record|
|
|
88
|
-
next if record.error? ||
|
|
94
|
+
next if record.error? || too_new?(bundle, record.manifest)
|
|
89
95
|
|
|
90
|
-
providers(bundle, record).each do |provider|
|
|
96
|
+
providers(bundle, record.manifest).each do |provider|
|
|
91
97
|
match = provider.regexp.match(url) or next
|
|
92
98
|
return resolved(bundle, provider, match)
|
|
93
99
|
end
|
|
@@ -97,14 +103,33 @@ module Samagotchi
|
|
|
97
103
|
|
|
98
104
|
# Whether an installed bundle declares a provider (the web's "+ URL").
|
|
99
105
|
def any?
|
|
100
|
-
MemoryBundle::Provenance.each_installed.any?
|
|
106
|
+
MemoryBundle::Provenance.each_installed.any? do |bundle, record|
|
|
107
|
+
!record.error? && !too_new?(bundle, record.manifest) && record.manifest.context_providers?
|
|
108
|
+
end
|
|
101
109
|
end
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
# A bundle this chi is too old for has no usable providers: skip it, and
|
|
112
|
+
# say so once per process per bundle and version.
|
|
113
|
+
# @return [Boolean]
|
|
114
|
+
def too_new?(bundle, manifest)
|
|
115
|
+
return false unless manifest.context_providers?
|
|
116
|
+
|
|
117
|
+
failure = manifest.requires_chi_failure
|
|
118
|
+
return false if failure.nil?
|
|
119
|
+
|
|
120
|
+
if TOO_NEW_LOG_LOCK.synchronize { TOO_NEW_LOGGED.add?([bundle, manifest.version.to_s]) }
|
|
121
|
+
Log.warn(:context, "providers_too_new", bundle: bundle, version: manifest.version.to_s,
|
|
122
|
+
requires_chi: manifest.requires_chi.to_s, msg: failure)
|
|
123
|
+
end
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Its parsed providers; a list that doesn't parse is none (logged).
|
|
128
|
+
def providers(bundle, manifest)
|
|
129
|
+
if (problem = manifest.problems["context_providers"])
|
|
130
|
+
Log.warn(:context, "providers_unreadable", bundle: bundle, msg: problem)
|
|
131
|
+
end
|
|
132
|
+
manifest.context_providers
|
|
108
133
|
end
|
|
109
134
|
|
|
110
135
|
def resolved(bundle, provider, match)
|
|
@@ -124,7 +149,8 @@ module Samagotchi
|
|
|
124
149
|
# installed (they run outside the guardrails gate, as a plugin loads:
|
|
125
150
|
# checked the same way).
|
|
126
151
|
# @return [String]
|
|
127
|
-
# @raise [Invalid] the bundle is gone
|
|
152
|
+
# @raise [Invalid] the bundle is gone, a script changed, or this chi
|
|
153
|
+
# doesn't meet the bundle's requires_chi
|
|
128
154
|
def command_for(source)
|
|
129
155
|
return source.cmd unless source.provider
|
|
130
156
|
|
|
@@ -132,6 +158,12 @@ module Samagotchi
|
|
|
132
158
|
record = provenance.record
|
|
133
159
|
raise Invalid, "bundle #{source.provider} isn't installed" unless record
|
|
134
160
|
|
|
161
|
+
# Its bundle is too new for this chi (installed with --force, or chi
|
|
162
|
+
# was downgraded): its script won't run, as its plugin wouldn't load.
|
|
163
|
+
if (failure = record.manifest.requires_chi_failure)
|
|
164
|
+
raise Invalid, "bundle #{source.provider}: #{failure}"
|
|
165
|
+
end
|
|
166
|
+
|
|
135
167
|
record.scripts.each do |file, sha|
|
|
136
168
|
path = File.join(provenance.scripts_dir, file)
|
|
137
169
|
next if File.file?(path) && MemoryBundle::Provenance.sha_matches?(path, sha)
|