samagotchi 0.151.0 → 0.153.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 +46 -1
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +84 -0
- data/docs/cli.md +35 -10
- data/docs/configuration.md +19 -14
- data/docs/guardrails.md +11 -5
- data/docs/hooks.md +4 -3
- data/docs/internals/anthropic.md +3 -3
- data/docs/internals/attached-context.md +3 -0
- data/docs/internals/block-renderers.md +3 -1
- data/docs/internals/bundles.md +48 -14
- data/docs/internals/engine.md +9 -5
- data/docs/internals/objectives.md +6 -4
- data/docs/internals/participants.md +1 -1
- data/docs/internals/prompt-caching.md +7 -3
- data/docs/internals/web-frontend.md +20 -17
- data/docs/memory.md +6 -1
- data/docs/plugins.md +80 -29
- data/docs/releasing.md +8 -3
- data/docs/sessions.md +2 -2
- data/docs/testing.md +2 -2
- 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/bundles/web-search/manifest.yml +13 -0
- data/lib/samagotchi/bundles/web-search/plugin.rb +174 -0
- data/lib/samagotchi/child_env.rb +15 -0
- data/lib/samagotchi/context_providers.rb +42 -10
- data/lib/samagotchi/engine.rb +37 -9
- 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/review_boundary.rb +4 -3
- 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/llm_context_stale.rb +2 -2
- 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/message_parts.rb +3 -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/toolkit.rb +8 -5
- data/lib/samagotchi/toolkit_binding.rb +30 -5
- data/lib/samagotchi/toolkit_report_command.rb +9 -5
- data/lib/samagotchi/toolkit_suggest.rb +12 -6
- data/lib/samagotchi/tools/builtin_calls.rb +14 -10
- data/lib/samagotchi/tools/deferred_tools.rb +2 -2
- data/lib/samagotchi/tools/delegate.rb +20 -2
- data/lib/samagotchi/tools/registry.rb +44 -20
- data/lib/samagotchi/tools/tool_index.rb +27 -21
- 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/public/app.js +61 -357
- data/lib/samagotchi/web/public/composer_mode.js +110 -0
- data/lib/samagotchi/web/public/owner_claim.js +81 -0
- data/lib/samagotchi/web/public/owner_verify.js +112 -0
- data/lib/samagotchi/web/public/session_fields.js +3 -3
- data/lib/samagotchi/web/public/strip_grip.js +3 -3
- data/lib/samagotchi/web/public/strip_hidden.js +65 -0
- data/lib/samagotchi/web/public/version_notices.js +132 -0
- data/lib/samagotchi/web/session_access_routes.rb +2 -1
- data/lib/samagotchi/web/session_hub.rb +6 -0
- data/lib/samagotchi/worker.rb +6 -0
- metadata +11 -1
data/lib/samagotchi/toolkit.rb
CHANGED
|
@@ -249,10 +249,13 @@ module Samagotchi
|
|
|
249
249
|
Kit.new(name: REVIEW, tools: REVIEW_TOOLS, groups: [])
|
|
250
250
|
].to_h { |kit| [kit.name, kit] }.freeze
|
|
251
251
|
|
|
252
|
-
# What a toolkit declares, the Registry's filter: every
|
|
253
|
-
#
|
|
254
|
-
# an LLM context layer gates
|
|
255
|
-
# +added+ declares any registered
|
|
252
|
+
# What a toolkit declares, the Registry's filter: every registered tool,
|
|
253
|
+
# built-ins and plugin tools (chi.tool) alike (+all_core+, full), or the
|
|
254
|
+
# built-ins in +core+, and every tool an LLM context layer gates
|
|
255
|
+
# (forget_outputs), but for +removed+; +added+ declares any registered
|
|
256
|
+
# tool. A tool it leaves out without removing is reachable through the
|
|
257
|
+
# tool index (Registry#reachable_names). The index's own tools
|
|
258
|
+
# (find_tools, call_tool) are the Registry's to declare. +fallback+: why the session's
|
|
256
259
|
# own toolkit couldn't be resolved (this is FULL then), nil when it was.
|
|
257
260
|
# +prompt+: the kit's prompt text (its own or its base's), nil without.
|
|
258
261
|
# +groups+: :all or the sorted source group names/globs it reaches.
|
|
@@ -272,7 +275,7 @@ module Samagotchi
|
|
|
272
275
|
# @param entry [Tools::Registry::Entry]
|
|
273
276
|
def declares?(entry)
|
|
274
277
|
return false if removed.include?(entry.name)
|
|
275
|
-
return true if added.include?(entry.name) || !entry.
|
|
278
|
+
return true if added.include?(entry.name) || !entry.layer.nil?
|
|
276
279
|
|
|
277
280
|
all_core || core.include?(entry.name)
|
|
278
281
|
end
|
|
@@ -38,6 +38,8 @@ module Samagotchi
|
|
|
38
38
|
# config ones (#kits); Plugin::Registries appends to it.
|
|
39
39
|
@plugin_kits = []
|
|
40
40
|
@warned = []
|
|
41
|
+
# The tools #warn_stranded said once.
|
|
42
|
+
@stranded = []
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
# @return [Array<Toolkit::Kit>] the plugins' kits (Plugin::Registries' toolkits:)
|
|
@@ -192,13 +194,36 @@ module Samagotchi
|
|
|
192
194
|
resolved = nil if resolved.everything?
|
|
193
195
|
tools = @tools.call
|
|
194
196
|
tools.reaching_kit = ->(group) { reaching_kit(group) }
|
|
195
|
-
|
|
197
|
+
changed = resolved != tools.toolkit
|
|
198
|
+
if changed
|
|
199
|
+
tools.toolkit = resolved
|
|
200
|
+
@refresh_index.call
|
|
201
|
+
@reset_prompt.call
|
|
202
|
+
end
|
|
203
|
+
warn_stranded(tools)
|
|
204
|
+
changed
|
|
205
|
+
end
|
|
196
206
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
207
|
+
# "web_x isn't declared on toolkit lean, and call_tool can't reach it
|
|
208
|
+
# this session: it arrived after the session's tools were set ·
|
|
209
|
+
# /toolkit add web_x declares it"
|
|
210
|
+
def self.stranded_notice(tool, toolkit)
|
|
211
|
+
"#{tool} isn't declared on toolkit #{toolkit}, and call_tool can't reach it this session: it arrived after " \
|
|
212
|
+
"the session's tools were set · /toolkit add #{tool} declares it"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# A notice (by its bundle, once per tool and worker) for each plugin
|
|
216
|
+
# tool that arrived mid-session on a kit that leaves it out without
|
|
217
|
+
# the tool index to reach it (Tools::Registry#stranded_names).
|
|
218
|
+
def warn_stranded(tools)
|
|
219
|
+
return unless tools.respond_to?(:stranded_names)
|
|
220
|
+
|
|
221
|
+
(tools.stranded_names - @stranded).each do |name|
|
|
222
|
+
@stranded << name
|
|
223
|
+
@notify.call(self.class.stranded_notice(name, tools.toolkit.name), :warn, tools[name]&.source.to_s, extra: {})
|
|
224
|
+
end
|
|
201
225
|
end
|
|
226
|
+
private :warn_stranded
|
|
202
227
|
|
|
203
228
|
# The first kit that resolves and reaches source group +group+, the
|
|
204
229
|
# user's own (config's, the plugins') before the built-ins, so a hint
|
|
@@ -241,7 +241,9 @@ module Samagotchi
|
|
|
241
241
|
def grade(entry, kit, uses)
|
|
242
242
|
kits = entry["kits"].is_a?(Hash) ? entry["kits"] : {}
|
|
243
243
|
rules = kits[kit]
|
|
244
|
-
|
|
244
|
+
# The tools a kit's rules declare: the built-ins and the plugin tools
|
|
245
|
+
# (chi.tool) logged when the ask started (an older line has none).
|
|
246
|
+
core_tools = Array(entry["core_tools"]) | Array(entry["plugin_tools"])
|
|
245
247
|
return ["ungradable", " (no rules logged for #{kit})"] unless rules.is_a?(Hash)
|
|
246
248
|
return ["ungradable", " (tool calls not readable)"] if uses == :ungradable
|
|
247
249
|
|
|
@@ -259,8 +261,10 @@ module Samagotchi
|
|
|
259
261
|
end
|
|
260
262
|
|
|
261
263
|
# Whether a kit's rules declare +use+ (Toolkit::Resolved#declares?): a
|
|
262
|
-
# tool-index group by the kit's groups; a built-in
|
|
263
|
-
#
|
|
264
|
+
# tool-index group by the kit's groups; a logged built-in or plugin
|
|
265
|
+
# tool (+core_tools+) by the kit's list (all of them on full) and its
|
|
266
|
+
# adds; any other (the index's own, a layer's, one that registered
|
|
267
|
+
# after the ask) unless the kit removes it.
|
|
264
268
|
def covers?(rules, core_tools, use)
|
|
265
269
|
return group_reached?(rules["groups"], use.group) if use.group && use.group != "chi"
|
|
266
270
|
|
|
@@ -276,8 +280,8 @@ module Samagotchi
|
|
|
276
280
|
(declared & core_tools) - Array(rules["removed"])
|
|
277
281
|
end
|
|
278
282
|
|
|
279
|
-
# +other+ declares no more built-ins and
|
|
280
|
-
# +rules+, and less of one of them.
|
|
283
|
+
# +other+ declares no more logged tools (built-ins and plugin tools)
|
|
284
|
+
# and reaches no more groups than +rules+, and less of one of them.
|
|
281
285
|
def narrower?(other, rules, core_tools)
|
|
282
286
|
mine = core_set(rules, core_tools)
|
|
283
287
|
theirs = core_set(other, core_tools)
|
|
@@ -31,8 +31,13 @@ module Samagotchi
|
|
|
31
31
|
# A kit as the ask saw it: its name, classifier description and #rules.
|
|
32
32
|
Candidate = Data.define(:name, :description, :rules)
|
|
33
33
|
# +default+ is a Candidate (no description needed); +core_tools+ the
|
|
34
|
-
# built-ins registered when the ask started
|
|
35
|
-
|
|
34
|
+
# built-ins registered when the ask started, +plugin_tools+ the plugin
|
|
35
|
+
# tools (chi.tool) registered then: a kit declares both by its rules.
|
|
36
|
+
Snapshot = Data.define(:session_id, :turn_number, :default, :candidates, :core_tools, :plugin_tools, :state) do
|
|
37
|
+
def initialize(session_id:, turn_number:, default:, candidates:, core_tools:, state:, plugin_tools: [])
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
end
|
|
36
41
|
Suggestion = Data.define(:kit, :raw, :confidence, :decided)
|
|
37
42
|
# One ask's outcome: its Suggestion, what on mode would do with it
|
|
38
43
|
# (suggested, default, defer, below_threshold, invalid, no_judgment)
|
|
@@ -164,6 +169,7 @@ module Samagotchi
|
|
|
164
169
|
decided: !!decided,
|
|
165
170
|
would: would,
|
|
166
171
|
core_tools: snapshot.core_tools,
|
|
172
|
+
plugin_tools: snapshot.plugin_tools,
|
|
167
173
|
kits: [snapshot.default, *snapshot.candidates].to_h { |kit| [kit.name, kit.rules] }
|
|
168
174
|
}
|
|
169
175
|
Judged.new(suggestion: Suggestion.new(kit: selected, raw: raw, confidence: confidence, decided: !!decided),
|
|
@@ -319,10 +325,10 @@ module Samagotchi
|
|
|
319
325
|
end
|
|
320
326
|
|
|
321
327
|
# A resolved kit (Toolkit::Resolved) as frozen, JSON-ready rules, not
|
|
322
|
-
# a tool list: plugin and MCP tools may still be
|
|
323
|
-
# starts,
|
|
324
|
-
#
|
|
325
|
-
#
|
|
328
|
+
# a tool list: a plugin's replaced tools and MCP tools may still be
|
|
329
|
+
# loading when the ask starts, so the report expands these against
|
|
330
|
+
# the tools a turn really called (ToolkitReportCommand), the logged
|
|
331
|
+
# core_tools and plugin_tools by the same rule (Resolved#declares?).
|
|
326
332
|
# @return [Hash{String => Object}]
|
|
327
333
|
def self.rules(resolved)
|
|
328
334
|
{
|
|
@@ -149,20 +149,23 @@ module Samagotchi
|
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
# A saved call as it ran (a reload, the stale-read pairing): a call_tool
|
|
152
|
-
# of a built-in
|
|
153
|
-
#
|
|
154
|
-
# result) shows it ran as
|
|
155
|
-
# no lead, an error that names
|
|
156
|
-
#
|
|
157
|
-
#
|
|
152
|
+
# of a chi tool, a built-in or a plugin's ("chi/delegate",
|
|
153
|
+
# "chi/web_search", or a bare "delegate") is that tool's call with
|
|
154
|
+
# via: "call_tool" when +output+ (its saved result) shows it ran as
|
|
155
|
+
# that tool: its "[name]" lead, or, with no lead, an error that names
|
|
156
|
+
# it (refused by its name before the gate; a plugin tool's only by
|
|
157
|
+
# its chi/ name, the one no MCP tool has); any other call as it is (a
|
|
158
|
+
# call_tool that ran, refused or not, leads with "[call_tool]"). No
|
|
159
|
+
# registry is asked: a reload without the plugin reads the same.
|
|
158
160
|
# @param call [Hash] as #build gives it
|
|
159
161
|
# @param output [String, nil]
|
|
160
162
|
# @return [Hash]
|
|
161
163
|
def unwrap_saved(call, output)
|
|
162
164
|
return call unless call[:name] == VIA && call[:args].is_a?(Hash)
|
|
163
165
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
named = call[:args]["tool"].to_s.strip
|
|
167
|
+
name = named.delete_prefix(VIA_GROUP)
|
|
168
|
+
return call if name.empty? || name == VIA || !ran_as?(output.to_s, name, error: row(name) || named != name)
|
|
166
169
|
|
|
167
170
|
build(name, saved_args(call[:args]["args"])).merge(via: VIA)
|
|
168
171
|
end
|
|
@@ -183,9 +186,10 @@ module Samagotchi
|
|
|
183
186
|
build(VIA, { "tool" => name, "args" => call[:args] })
|
|
184
187
|
end
|
|
185
188
|
|
|
186
|
-
|
|
189
|
+
# +error+: whether a lead-less error that names the tool counts.
|
|
190
|
+
def ran_as?(output, name, error: true)
|
|
187
191
|
lead = output[SAVED_LEAD, 1]
|
|
188
|
-
lead ? lead == name : output.start_with?("Error: #{name} ")
|
|
192
|
+
lead ? lead == name : error && output.start_with?("Error: #{name} ")
|
|
189
193
|
end
|
|
190
194
|
|
|
191
195
|
# call_tool's inner args as saved: an object, or its JSON text.
|
|
@@ -9,8 +9,8 @@ module Samagotchi
|
|
|
9
9
|
# that loads tools (LLM::AnthropicMessages#loads_tools?): every tool the
|
|
10
10
|
# session reaches but doesn't declare, so a find_tools hit can load it
|
|
11
11
|
# and the model calls it by its own name. In order:
|
|
12
|
-
# 1. the built-ins the toolkit leaves out
|
|
13
|
-
# (Registry#reachable_names), in registry order;
|
|
12
|
+
# 1. the chi tools (built-ins and plugin tools) the toolkit leaves out
|
|
13
|
+
# but doesn't remove (Registry#reachable_names), in registry order;
|
|
14
14
|
# 2. the tool index's source tools (an MCP server's, by acts_as), only
|
|
15
15
|
# while call_tool is declared (ToolIndex#deferrable: sorted by name);
|
|
16
16
|
# 3. every other name the conversation loaded (a find result's
|
|
@@ -159,7 +159,8 @@ module Samagotchi
|
|
|
159
159
|
# repository (a worktree the parent meant to pass as cwd:), which makes
|
|
160
160
|
# every write there an ask for the user (child-boundary). One warning
|
|
161
161
|
# line, however many are named; nil when the task names none, or names
|
|
162
|
-
# only the checkout holding the child's folder (or a subfolder of it)
|
|
162
|
+
# only the checkout holding the child's folder (or a subfolder of it),
|
|
163
|
+
# or main while the child itself runs in a worktree of it.
|
|
163
164
|
# Never raises: a started child must not come back as an Error:.
|
|
164
165
|
# @return [String, nil]
|
|
165
166
|
def self.other_checkout(task, folder, parent:, child_id:, no_cwd:)
|
|
@@ -180,6 +181,9 @@ module Samagotchi
|
|
|
180
181
|
# not the one holding +folder+: the first path token in the task that
|
|
181
182
|
# belongs to one (the deepest containing it), else nil. Reads files
|
|
182
183
|
# only, no git process, so a spec stubbing Process.spawn is unaffected.
|
|
184
|
+
# A child started in a linked worktree is never pointed at main: its
|
|
185
|
+
# work belongs in the worktree, so naming a main-checkout file - mostly
|
|
186
|
+
# to read it - is not a cwd: the parent forgot.
|
|
183
187
|
# @return [String, nil] an absolute checkout folder
|
|
184
188
|
def self.foreign_checkout(task, folder)
|
|
185
189
|
common = MemoryPaths.common_git_dir(folder)
|
|
@@ -187,17 +191,31 @@ module Samagotchi
|
|
|
187
191
|
|
|
188
192
|
checkouts = Guardrails::GitInfo.checkouts(common).map { |checkout| File.realpath(checkout) }
|
|
189
193
|
own = deepest_checkout(File.realpath(folder), checkouts)
|
|
194
|
+
main = main_checkout(common)
|
|
190
195
|
task.to_s.scan(PATH_TOKEN) do |token|
|
|
191
196
|
path = existing_ancestor(File.expand_path(token.sub(/[.:!?]+\z/, ""), folder))
|
|
192
197
|
next if path.nil?
|
|
193
198
|
|
|
194
199
|
found = deepest_checkout(path, checkouts)
|
|
195
|
-
|
|
200
|
+
next if found.nil? || found == own || found == main
|
|
201
|
+
|
|
202
|
+
return found
|
|
196
203
|
end
|
|
197
204
|
nil
|
|
198
205
|
end
|
|
199
206
|
private_class_method :foreign_checkout
|
|
200
207
|
|
|
208
|
+
# The main checkout of a common git dir (the folder holding it), nil for
|
|
209
|
+
# a bare or separate-git-dir layout, which has none.
|
|
210
|
+
def self.main_checkout(common)
|
|
211
|
+
return nil unless File.basename(common) == ".git"
|
|
212
|
+
|
|
213
|
+
File.realpath(File.dirname(common))
|
|
214
|
+
rescue StandardError
|
|
215
|
+
nil
|
|
216
|
+
end
|
|
217
|
+
private_class_method :main_checkout
|
|
218
|
+
|
|
201
219
|
# The checkout containing +path+, matched by whole components (so
|
|
202
220
|
# /x/app is not found in /x/app-fix): the deepest, so a nested worktree's
|
|
203
221
|
# path belongs to that worktree, not to main; nil when none does.
|
|
@@ -41,14 +41,16 @@ module Samagotchi
|
|
|
41
41
|
# What Tools::ToolIndex#declare registered: the entries of +source+
|
|
42
42
|
# are the index's search and call tools, +call_name+ the call tool's
|
|
43
43
|
# name, and +groups+ the source groups registered when the plugins
|
|
44
|
-
# loaded
|
|
44
|
+
# loaded; +loaded+ the entries registered then (nil: whatever is
|
|
45
|
+
# registered now), which alone decide whether the index's tools are
|
|
46
|
+
# needed (Registry#index_needed?). +deferrable+ (→ the sources'
|
|
45
47
|
# tools a host that loads tools may declare deferred) and +wrap+
|
|
46
48
|
# (call → the call_tool call a direct call of one of them runs as, or
|
|
47
49
|
# nil) are nil when nothing reaches them.
|
|
48
|
-
Index = Data.define(:source, :call_name, :groups, :deferrable, :wrap, :unreached) do
|
|
49
|
-
def initialize(source:, call_name:, groups:, deferrable: nil, wrap: nil, unreached: nil)
|
|
50
|
+
Index = Data.define(:source, :call_name, :groups, :deferrable, :wrap, :unreached, :loaded) do
|
|
51
|
+
def initialize(source:, call_name:, groups:, deferrable: nil, wrap: nil, unreached: nil, loaded: nil)
|
|
50
52
|
super(source: source, call_name: call_name, groups: Array(groups).map(&:to_s).uniq.freeze,
|
|
51
|
-
deferrable: deferrable, wrap: wrap, unreached: unreached)
|
|
53
|
+
deferrable: deferrable, wrap: wrap, unreached: unreached, loaded: loaded&.dup&.freeze)
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def unreached_tool(name) = unreached&.call(name)
|
|
@@ -116,18 +118,18 @@ module Samagotchi
|
|
|
116
118
|
ordered(@entries.values.select { |entry| left_out?(entry, layers, toolkit) }).map(&:name)
|
|
117
119
|
end
|
|
118
120
|
|
|
119
|
-
# The
|
|
120
|
-
# but the tool index reaches it (#reachable_names).
|
|
121
|
+
# The tool +name+ names (a built-in or a plugin's) when the session's
|
|
122
|
+
# toolkit leaves it out but the tool index reaches it (#reachable_names).
|
|
121
123
|
# @return [Entry, nil]
|
|
122
124
|
def indexed(name, layers: [], toolkit: @toolkit)
|
|
123
125
|
entry = self[name]
|
|
124
126
|
entry if entry && call_offered?(toolkit) && soft?(entry, layers, toolkit)
|
|
125
127
|
end
|
|
126
128
|
|
|
127
|
-
# The
|
|
128
|
-
#
|
|
129
|
-
# removed either (remove:, --no-tool, /toolkit
|
|
130
|
-
# itself declared.
|
|
129
|
+
# The tools +toolkit+ leaves out of a turn under +layers+ that the tool
|
|
130
|
+
# index reaches (call_tool), built-ins and plugin tools alike: not in
|
|
131
|
+
# its kit's list, but not removed either (remove:, --no-tool, /toolkit
|
|
132
|
+
# remove), with call_tool itself declared. Never the index's own.
|
|
131
133
|
# @return [Array<String>] in declaration order
|
|
132
134
|
def reachable_names(layers: [], toolkit: @toolkit)
|
|
133
135
|
return [] unless call_offered?(toolkit)
|
|
@@ -145,6 +147,25 @@ module Samagotchi
|
|
|
145
147
|
.map(&:name)
|
|
146
148
|
end
|
|
147
149
|
|
|
150
|
+
# The tools +toolkit+ leaves out of a turn under +layers+ that a
|
|
151
|
+
# plugin registered after the index was declared (chi.replace_tools)
|
|
152
|
+
# and that nothing reaches: call_tool isn't declared (the index's
|
|
153
|
+
# need is the load-time one, so the declared tools don't grow
|
|
154
|
+
# mid-session), nor removed by the user. /toolkit add declares one.
|
|
155
|
+
# @return [Array<String>] in declaration order
|
|
156
|
+
def stranded_names(layers: [], toolkit: @toolkit)
|
|
157
|
+
return [] if @index&.loaded.nil? || toolkit.nil? || toolkit.removed.include?(@index.call_name)
|
|
158
|
+
return [] if call_offered?(toolkit)
|
|
159
|
+
|
|
160
|
+
loaded = @index.loaded.map(&:name)
|
|
161
|
+
ordered(@entries.values.select { |entry| !loaded.include?(entry.name) && soft?(entry, layers, toolkit) })
|
|
162
|
+
.map(&:name)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Every entry registered now, declared or not, in registration order.
|
|
166
|
+
# @return [Array<Entry>]
|
|
167
|
+
def registered = @entries.values
|
|
168
|
+
|
|
148
169
|
# Every tool +source+ registered, declared or not (a plugin's tool set
|
|
149
170
|
# changing: Plugin::ToolSpec.apply).
|
|
150
171
|
# @return [Array<Entry>]
|
|
@@ -175,7 +196,7 @@ module Samagotchi
|
|
|
175
196
|
|
|
176
197
|
# Declared by +toolkit+ (nil: every tool, full). The tool index's
|
|
177
198
|
# tools are declared when they reach something, whatever the toolkit:
|
|
178
|
-
# a source registered at load, or a
|
|
199
|
+
# a source registered at load, or a tool it leaves out but
|
|
179
200
|
# doesn't remove (#index_needed?); unless it removes them.
|
|
180
201
|
def declared?(entry, toolkit = @toolkit)
|
|
181
202
|
if index_entry?(entry)
|
|
@@ -187,8 +208,10 @@ module Samagotchi
|
|
|
187
208
|
|
|
188
209
|
def index_entry?(entry) = !@index.nil? && entry.source == @index.source
|
|
189
210
|
|
|
190
|
-
# From what the plugins registered at load and the
|
|
191
|
-
# the declared tools stay the same for a session's
|
|
211
|
+
# From what the plugins registered at load (Index#loaded) and the
|
|
212
|
+
# toolkit alone, so the declared tools stay the same for a session's
|
|
213
|
+
# life: a plugin tool registered later on a kit that leaves it out
|
|
214
|
+
# doesn't bring find_tools and call_tool in (#stranded_names). A tool
|
|
192
215
|
# left out counts only while call_tool can reach it (not removed).
|
|
193
216
|
def index_needed?(toolkit)
|
|
194
217
|
return true if reached_groups?(toolkit)
|
|
@@ -198,10 +221,10 @@ module Samagotchi
|
|
|
198
221
|
end
|
|
199
222
|
|
|
200
223
|
# Whether the index's tools would reach something under +toolkit+,
|
|
201
|
-
# removed or not: a source registered at load, or a built-in
|
|
202
|
-
# toolkit leaves out without removing it.
|
|
224
|
+
# removed or not: a source registered at load, or a tool (a built-in
|
|
225
|
+
# or a plugin's) the toolkit leaves out without removing it.
|
|
203
226
|
def index_reaches?(toolkit)
|
|
204
|
-
reached_groups?(toolkit) || @entries.values.any? { |entry| soft?(entry, [], toolkit) }
|
|
227
|
+
reached_groups?(toolkit) || (@index.loaded || @entries.values).any? { |entry| soft?(entry, [], toolkit) }
|
|
205
228
|
end
|
|
206
229
|
|
|
207
230
|
def reached_groups?(toolkit)
|
|
@@ -218,11 +241,12 @@ module Samagotchi
|
|
|
218
241
|
toolkit&.removed&.include?(entry.name) == true && index_reaches?(toolkit)
|
|
219
242
|
end
|
|
220
243
|
|
|
221
|
-
# A
|
|
222
|
-
#
|
|
244
|
+
# A tool +toolkit+ leaves out of a turn under +layers+ without removing
|
|
245
|
+
# it: a built-in or a plugin's, never one of the index's own (they are
|
|
246
|
+
# declared when they reach something) or a layer's (always declared).
|
|
223
247
|
def soft?(entry, layers, toolkit)
|
|
224
|
-
!toolkit.nil? && entry
|
|
225
|
-
!toolkit.declares?(entry)
|
|
248
|
+
!toolkit.nil? && !index_entry?(entry) && entry.layer.nil? && entry.offered?(layers) &&
|
|
249
|
+
!toolkit.removed.include?(entry.name) && !toolkit.declares?(entry)
|
|
226
250
|
end
|
|
227
251
|
|
|
228
252
|
# Whether +toolkit+ declares the index's call tool.
|
|
@@ -17,14 +17,15 @@ module Samagotchi
|
|
|
17
17
|
# inputSchema, and call_tool, which calls one as <group>/<tool>.
|
|
18
18
|
# A source (the mcp bundle, chi.tool_source) publishes its groups (an
|
|
19
19
|
# MCP server each) and runs the calls; the index's own group, chi, is
|
|
20
|
-
# the
|
|
21
|
-
#
|
|
22
|
-
#
|
|
20
|
+
# the tools the session's toolkit leaves out but doesn't remove, chi's
|
|
21
|
+
# built-ins and its bundles' (chi.tool) alike (Registry#reachable_names),
|
|
22
|
+
# and a call_tool of one runs as that tool (#unwrap, before the
|
|
23
|
+
# guardrail gate). The index searches the
|
|
23
24
|
# groups, looks a call's tool up and checks it.
|
|
24
25
|
#
|
|
25
26
|
# The two tools are registered once, after the plugins load, and
|
|
26
27
|
# declared per toolkit (Registry#declared?): when a source registered
|
|
27
|
-
# then, or the toolkit leaves a
|
|
28
|
+
# then, or the toolkit leaves a tool reachable. The search tool's
|
|
28
29
|
# description has the source groups' lines as they were at load and a
|
|
29
30
|
# chi line per toolkit (#refresh!): nothing a source publishes later
|
|
30
31
|
# changes the request's tools, so the prompt cache keeps.
|
|
@@ -44,9 +45,10 @@ module Samagotchi
|
|
|
44
45
|
# times one in its description.
|
|
45
46
|
NAME_WEIGHT = 3
|
|
46
47
|
PREVIEW_CHARS = 60
|
|
47
|
-
# The index's own group: chi's built-ins
|
|
48
|
+
# The index's own group: chi's tools (built-ins and bundles' chi.tool)
|
|
49
|
+
# a toolkit leaves out.
|
|
48
50
|
CHI_GROUP = "chi"
|
|
49
|
-
CHI_SUMMARY = "chi's
|
|
51
|
+
CHI_SUMMARY = "chi's and its bundles' tools not declared in this session"
|
|
50
52
|
# Group names a source can't publish: the index's own groups'.
|
|
51
53
|
RESERVED_GROUPS = [CHI_GROUP].freeze
|
|
52
54
|
|
|
@@ -276,7 +278,7 @@ module Samagotchi
|
|
|
276
278
|
end
|
|
277
279
|
|
|
278
280
|
# Register the search and call tools in +registry+, once, after the
|
|
279
|
-
# plugins load (or without them: the
|
|
281
|
+
# plugins load (or without them: the tools a toolkit leaves out
|
|
280
282
|
# are reachable either way); +registry+ declares them per toolkit
|
|
281
283
|
# (Registry::Index). A name another tool has is left out (a line in
|
|
282
284
|
# +failures+).
|
|
@@ -285,6 +287,7 @@ module Samagotchi
|
|
|
285
287
|
@registry = registry
|
|
286
288
|
@described = description_lines(all_groups.reject { |group| own?(group) }).freeze
|
|
287
289
|
@declared_group_names = @described.keys.freeze
|
|
290
|
+
loaded = registry.registered
|
|
288
291
|
specs = [find_spec, call_spec]
|
|
289
292
|
declared = specs.filter_map do |spec|
|
|
290
293
|
if (taken = registry[spec.name])
|
|
@@ -299,7 +302,7 @@ module Samagotchi
|
|
|
299
302
|
end
|
|
300
303
|
registry.index = Registry::Index.new(source: SOURCE, call_name: CALL_NAME, groups: @declared_group_names,
|
|
301
304
|
deferrable: -> { deferrable }, wrap: ->(call) { wrap(call) },
|
|
302
|
-
unreached: ->(name) { unreached_tool(name) })
|
|
305
|
+
unreached: ->(name) { unreached_tool(name) }, loaded: loaded)
|
|
303
306
|
declared!(declared)
|
|
304
307
|
declared.map(&:name)
|
|
305
308
|
end
|
|
@@ -362,7 +365,7 @@ module Samagotchi
|
|
|
362
365
|
# A call of the tool +name+ names, with +args+ typed by its schema,
|
|
363
366
|
# run by its source. It fails closed: a tool that isn't the one
|
|
364
367
|
# guardrails were given (#claimed) is refused, and so is one of the
|
|
365
|
-
# index's own (a
|
|
368
|
+
# index's own (a chi tool runs as itself, #unwrap, or not at all).
|
|
366
369
|
def call(name, args)
|
|
367
370
|
tool = lookup(name)
|
|
368
371
|
return tool if tool.is_a?(String)
|
|
@@ -388,12 +391,12 @@ module Samagotchi
|
|
|
388
391
|
source.run(tool, inner)
|
|
389
392
|
end
|
|
390
393
|
|
|
391
|
-
# The
|
|
394
|
+
# The chi tool a call_tool +call+ names, as the call that runs in its
|
|
392
395
|
# place (ToolRunner#run's first step, before the guardrail gate):
|
|
393
396
|
# built from its arguments as a direct call would be
|
|
394
397
|
# (BuiltinCalls.build), with via: "call_tool". The name is a chi tool
|
|
395
|
-
# (a bare one only while the toolkit reaches it), or chi/<
|
|
396
|
-
#
|
|
398
|
+
# (a bare one only while the toolkit reaches it), or chi/<name> for
|
|
399
|
+
# any registered built-in or plugin tool: one the toolkit removes or declares
|
|
397
400
|
# is then refused by its name before the gate (ToolRunner#left_out?),
|
|
398
401
|
# with no hook or card. Nil, so the call runs as call_tool, when the
|
|
399
402
|
# turn under +layers+ doesn't offer call_tool, for an MCP tool, a miss
|
|
@@ -429,8 +432,8 @@ module Samagotchi
|
|
|
429
432
|
end
|
|
430
433
|
|
|
431
434
|
# The name a request declares +name+'s tool by when it goes out
|
|
432
|
-
# deferred (Tools::DeferredTools): a reachable built-in
|
|
433
|
-
# name or chi/<name>), a deferrable source tool's acts_as (by it or
|
|
435
|
+
# deferred (Tools::DeferredTools): a reachable chi tool's, built-in or
|
|
436
|
+
# plugin (by its name or chi/<name>), a deferrable source tool's acts_as (by it or
|
|
434
437
|
# <group>/<tool>). Nil for any other: declared, removed, unknown, or a
|
|
435
438
|
# source tool that can't be deferred. Never waits for a start.
|
|
436
439
|
# @return [String, nil]
|
|
@@ -549,7 +552,7 @@ module Samagotchi
|
|
|
549
552
|
end
|
|
550
553
|
|
|
551
554
|
# The wire names of +matches+ a request can declare deferred: a
|
|
552
|
-
#
|
|
555
|
+
# chi tool of the index's own groups by its name, a source's tool by
|
|
553
556
|
# its acts_as when it is #deferrable.
|
|
554
557
|
def loadable(matches)
|
|
555
558
|
sourced = nil
|
|
@@ -578,16 +581,17 @@ module Samagotchi
|
|
|
578
581
|
|
|
579
582
|
def own?(group) = RESERVED_GROUPS.include?(group.name)
|
|
580
583
|
|
|
581
|
-
# The registered
|
|
584
|
+
# The registered chi tool chi/<name> names (a built-in or a plugin's,
|
|
585
|
+
# never the index's own), reachable or not.
|
|
582
586
|
def builtin_named(name)
|
|
583
587
|
return nil unless name.start_with?("#{CHI_GROUP}/")
|
|
584
588
|
|
|
585
589
|
entry = @registry[name.delete_prefix("#{CHI_GROUP}/")]
|
|
586
|
-
entry
|
|
590
|
+
entry unless entry.nil? || entry.source == SOURCE
|
|
587
591
|
end
|
|
588
592
|
|
|
589
|
-
# The index's own groups: chi, the built-ins
|
|
590
|
-
# leaves out but reaches (none: no group). Each tool's schema is its
|
|
593
|
+
# The index's own groups: chi, the tools (built-ins and plugin tools)
|
|
594
|
+
# the Registry's toolkit leaves out but reaches (none: no group). Each tool's schema is its
|
|
591
595
|
# registered entry's (a config's execute description included), and
|
|
592
596
|
# it acts as itself.
|
|
593
597
|
# @return [Array<Group>]
|
|
@@ -698,8 +702,10 @@ module Samagotchi
|
|
|
698
702
|
return same.first if same.size == 1
|
|
699
703
|
return "Error: #{name} names #{same.map(&:full_name).join(" and ")}; call one as <group>/<tool>" if same.size > 1
|
|
700
704
|
|
|
701
|
-
# A
|
|
702
|
-
|
|
705
|
+
# A chi tool the session declares (a built-in or a plugin's) is no
|
|
706
|
+
# starting server's tool.
|
|
707
|
+
declared = @registry&.offered(name)
|
|
708
|
+
return "Error: #{name} is declared; call it directly" if declared && declared.source != SOURCE
|
|
703
709
|
|
|
704
710
|
# A name with a group's <group>/ waits for that group only; any
|
|
705
711
|
# other name (an acts_as) for every one starting.
|
|
@@ -10,6 +10,7 @@ require_relative "gem_update"
|
|
|
10
10
|
require_relative "installed_gem"
|
|
11
11
|
require_relative "live_versions"
|
|
12
12
|
require_relative "memory_bundle/file_standing"
|
|
13
|
+
require_relative "memory_bundle/manifest_standing"
|
|
13
14
|
require_relative "memory_bundle/shipped_update"
|
|
14
15
|
require_relative "memory_bundle/system_bundle"
|
|
15
16
|
require_relative "session"
|
|
@@ -331,10 +332,20 @@ module Samagotchi
|
|
|
331
332
|
|
|
332
333
|
require_relative "bundle_command/installed_check"
|
|
333
334
|
bundles.flat_map do |bundle|
|
|
334
|
-
|
|
335
|
+
kept = MemoryBundle::ManifestStanding.keep(bundle, dry_run: @dry_run)
|
|
336
|
+
[manifest_row(kept)].compact +
|
|
337
|
+
BundleCommand::InstalledCheck.run(kept.bundle).map { |found| Row.new(component: bundle.name, status: found.text, level: found.level) }
|
|
335
338
|
end
|
|
336
339
|
end
|
|
337
340
|
|
|
341
|
+
# What an external bundle's manifest.yml came to (ManifestStanding.keep):
|
|
342
|
+
# kept (or would be) is a change, anything else to say a warning.
|
|
343
|
+
def manifest_row(kept)
|
|
344
|
+
return nil unless kept.line
|
|
345
|
+
|
|
346
|
+
Row.new(component: kept.bundle.name, status: kept.line, level: kept.kept ? nil : "warn")
|
|
347
|
+
end
|
|
348
|
+
|
|
338
349
|
def bundle_row(update)
|
|
339
350
|
row = Row.new(component: update.name, from: update.from, to: update.to)
|
|
340
351
|
earlier = update.status == :failed ? [] : Array(update.earlier)
|
|
@@ -343,7 +354,7 @@ module Samagotchi
|
|
|
343
354
|
when :would_update, :updated
|
|
344
355
|
notes = [adds(update.adds), *notes, replaced(update.replaced), *update.kept_own].compact
|
|
345
356
|
done(row, :update, notes.empty? ? nil : notes.join("; "))
|
|
346
|
-
when :would_merge, :merged, :up_to_date then same_version(row, [*notes, update.note].compact, changed: Array(update.merged).any?)
|
|
357
|
+
when :would_merge, :merged, :up_to_date then same_version(row, [*notes, update.note, update.manifest].compact, changed: Array(update.merged).any?)
|
|
347
358
|
when :skipped then skipped(row, [update.note, *notes].join("; "))
|
|
348
359
|
else failed(row, [update.note, *notes].compact.join("; "))
|
|
349
360
|
end
|
data/lib/samagotchi/version.rb
CHANGED
data/lib/samagotchi/web/app.rb
CHANGED
|
@@ -62,6 +62,7 @@ require_relative "editor"
|
|
|
62
62
|
require_relative "../message_parts"
|
|
63
63
|
require_relative "../launch_mode"
|
|
64
64
|
require_relative "session_summary"
|
|
65
|
+
require_relative "session_hub"
|
|
65
66
|
require_relative "project_list"
|
|
66
67
|
require_relative "session_access_routes"
|
|
67
68
|
require_relative "principal"
|
|
@@ -1025,7 +1026,7 @@ module Samagotchi
|
|
|
1025
1026
|
def handle_info(req)
|
|
1026
1027
|
return participant_info unless principal(req).owner?
|
|
1027
1028
|
|
|
1028
|
-
chi_state = @hub&.chi_state ||
|
|
1029
|
+
chi_state = @hub&.chi_state || SessionHub::HUBLESS_STATE
|
|
1029
1030
|
json_response(200, { app: "chi-web", **chi_state, pid: Process.pid,
|
|
1030
1031
|
cwd: Dir.pwd, start_dir: start_dir, features: INFO_FEATURES, lan: @lan && @lan[:ip],
|
|
1031
1032
|
proxy: @public_url && { identity_header: @public_url.identity_header } })
|