samagotchi 0.98.0 → 0.100.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 +40 -1
- data/README.md +2 -2
- data/docs/architecture.md +6 -3
- data/docs/bundle-authoring.md +1 -1
- data/docs/cli.md +1 -1
- data/docs/configuration.md +8 -0
- data/docs/guardrails.md +10 -2
- data/docs/hooks.md +9 -1
- data/docs/internals/anthropic.md +79 -8
- data/docs/internals/engine.md +13 -3
- data/docs/internals/llm-context-forget.md +2 -1
- data/docs/internals/prompt-caching.md +9 -0
- data/docs/memory.md +1 -1
- data/docs/plugins.md +83 -3
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +10 -0
- data/docs/sub-agent.md +7 -2
- data/docs/testing.md +27 -0
- data/lib/samagotchi/bundles/core/manifest.yml +4 -3
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/bundles/work-cap/manifest.yml +13 -0
- data/lib/samagotchi/bundles/work-cap/plugin.rb +391 -0
- data/lib/samagotchi/engine.rb +67 -9
- data/lib/samagotchi/hooks/loader.rb +17 -4
- data/lib/samagotchi/kernel_loop.rb +15 -2
- data/lib/samagotchi/llm/anthropic_messages.rb +85 -4
- data/lib/samagotchi/llm/anthropic_wire.rb +90 -11
- data/lib/samagotchi/llm/chat_loop.rb +76 -7
- data/lib/samagotchi/llm/dialect.rb +8 -5
- data/lib/samagotchi/llm/refusal_memo.rb +6 -0
- data/lib/samagotchi/llm/turn_settings.rb +13 -5
- data/lib/samagotchi/llm_context_stale.rb +2 -2
- data/lib/samagotchi/message_parts.rb +2 -1
- data/lib/samagotchi/plugin/api.rb +2 -1
- data/lib/samagotchi/plugin/context.rb +31 -4
- data/lib/samagotchi/prompt.rb +3 -0
- data/lib/samagotchi/session_metrics/usage.rb +31 -0
- data/lib/samagotchi/session_metrics.rb +48 -5
- data/lib/samagotchi/testing/bundle.rb +2 -0
- data/lib/samagotchi/testing/host.rb +31 -1
- data/lib/samagotchi/testing/recorder.rb +18 -3
- data/lib/samagotchi/tool_loads.rb +62 -0
- data/lib/samagotchi/tool_response.rb +5 -1
- data/lib/samagotchi/tool_runner.rb +29 -7
- data/lib/samagotchi/tools/builtin_calls.rb +16 -0
- data/lib/samagotchi/tools/deferred_tools.rb +80 -0
- data/lib/samagotchi/tools/registry.rb +7 -2
- data/lib/samagotchi/tools/tool_index.rb +88 -6
- data/lib/samagotchi/version.rb +1 -1
- metadata +6 -1
|
@@ -56,6 +56,8 @@ module Samagotchi
|
|
|
56
56
|
group: { type: "string", description: "search only this group" }
|
|
57
57
|
}.freeze
|
|
58
58
|
CALL_DESCRIPTION = "Call a tool #{FIND_NAME} found.".freeze
|
|
59
|
+
# A keyword answer's first line.
|
|
60
|
+
CALL_HINT = "Call one with #{CALL_NAME}(tool: \"<group>/<tool>\", args: {…}).".freeze
|
|
59
61
|
CALL_PARAMS = {
|
|
60
62
|
tool: { type: "string", required: true, description: "the tool as <group>/<tool>" },
|
|
61
63
|
args: { type: "object", description: "the arguments, per the inputSchema #{FIND_NAME} gave" }
|
|
@@ -84,6 +86,19 @@ module Samagotchi
|
|
|
84
86
|
# A Group's states.
|
|
85
87
|
STATES = %i[ready starting failed].freeze
|
|
86
88
|
|
|
89
|
+
# A search's answer (#find): its text, and the tools it loads on a host
|
|
90
|
+
# that loads tools (+loaded+: the wire names of a keyword answer's
|
|
91
|
+
# callable matches, the ones a request can declare deferred; none for
|
|
92
|
+
# an empty query, a miss or the names listing).
|
|
93
|
+
class FoundText < String
|
|
94
|
+
attr_reader :loaded
|
|
95
|
+
|
|
96
|
+
def initialize(text, loaded = [])
|
|
97
|
+
super(text)
|
|
98
|
+
@loaded = loaded.freeze
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
87
102
|
# One group of a source's tools (an MCP server): +summary+ its line in
|
|
88
103
|
# the search tool's description (nil: its name and tool count only),
|
|
89
104
|
# +state+ :ready, :starting (its tools may come: a lookup waits) or
|
|
@@ -236,7 +251,8 @@ module Samagotchi
|
|
|
236
251
|
registry.register(spec.name, source: SOURCE, unwrap: unwrap, **spec.entry_fields(nil))
|
|
237
252
|
spec
|
|
238
253
|
end
|
|
239
|
-
registry.index = Registry::Index.new(source: SOURCE, call_name: CALL_NAME, sourced: !@sources.empty
|
|
254
|
+
registry.index = Registry::Index.new(source: SOURCE, call_name: CALL_NAME, sourced: !@sources.empty?,
|
|
255
|
+
deferrable: -> { deferrable }, wrap: ->(call) { wrap(call) })
|
|
240
256
|
declared!(declared)
|
|
241
257
|
declared.map(&:name)
|
|
242
258
|
end
|
|
@@ -286,11 +302,12 @@ module Samagotchi
|
|
|
286
302
|
# empty query every group's tool names. +server+ is the parameter's
|
|
287
303
|
# name before it was +group+: a model that still gives it is told
|
|
288
304
|
# every group was searched.
|
|
305
|
+
# @return [FoundText]
|
|
289
306
|
def find(query, only = nil, server: nil)
|
|
290
307
|
answer = find_in(query, only)
|
|
291
308
|
return answer unless (only.nil? || only.strip.empty?) && !server.to_s.strip.empty?
|
|
292
309
|
|
|
293
|
-
"(searched every group)\n#{answer}"
|
|
310
|
+
FoundText.new("(searched every group)\n#{answer}", answer.loaded)
|
|
294
311
|
end
|
|
295
312
|
|
|
296
313
|
# ── The call ────────────────────────────────────────────────────────
|
|
@@ -348,6 +365,56 @@ module Samagotchi
|
|
|
348
365
|
inner && BuiltinCalls.build(entry.name, inner).merge(via: CALL_NAME)
|
|
349
366
|
end
|
|
350
367
|
|
|
368
|
+
# The sources' tools a host that loads tools may declare deferred
|
|
369
|
+
# (Tools::DeferredTools), by their acts_as: callable (no refusal),
|
|
370
|
+
# the only tool with that acts_as, a name Messages takes, and no
|
|
371
|
+
# registered tool's. Sorted by acts_as, so the same index gives the
|
|
372
|
+
# same list in any session and after a respawn.
|
|
373
|
+
# @return [Array<Tool>]
|
|
374
|
+
def deferrable
|
|
375
|
+
tools = groups.reject { |group| own?(group) }.flat_map(&:tools)
|
|
376
|
+
counts = tools.map(&:acts_as).tally
|
|
377
|
+
tools.select do |tool|
|
|
378
|
+
tool.refusal.nil? && counts[tool.acts_as] == 1 && tool.acts_as.match?(DEFERRABLE_NAME) &&
|
|
379
|
+
!@registry&.key?(tool.acts_as)
|
|
380
|
+
end.sort_by(&:acts_as)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# The name a request declares +name+'s tool by when it goes out
|
|
384
|
+
# deferred (Tools::DeferredTools): a reachable built-in's (by its
|
|
385
|
+
# name or chi/<name>), a deferrable source tool's acts_as (by it or
|
|
386
|
+
# <group>/<tool>). Nil for any other: declared, removed, unknown, or a
|
|
387
|
+
# source tool that can't be deferred. Never waits for a start.
|
|
388
|
+
# @return [String, nil]
|
|
389
|
+
def deferred_name(name)
|
|
390
|
+
name = name.to_s.strip
|
|
391
|
+
bare = name.delete_prefix("#{CHI_GROUP}/")
|
|
392
|
+
return bare if @registry&.indexed(bare)
|
|
393
|
+
|
|
394
|
+
tool = lookup_quietly(name) or return nil
|
|
395
|
+
return nil if tool.entry || tool.refusal
|
|
396
|
+
|
|
397
|
+
deferrable.any? { |t| t.acts_as == tool.acts_as } ? tool.acts_as : nil
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# What a deferred source tool's wire name may be (Messages' tool
|
|
401
|
+
# name rule).
|
|
402
|
+
DEFERRABLE_NAME = /\A[a-zA-Z0-9_-]{1,128}\z/
|
|
403
|
+
|
|
404
|
+
# A direct call of a deferrable tool (#deferrable) by its acts_as, as
|
|
405
|
+
# the call_tool call it runs as (ToolRunner#run, under a host that
|
|
406
|
+
# loads tools): the call tool's row, rules and approvals. Nil for any
|
|
407
|
+
# other call.
|
|
408
|
+
# @return [Hash, nil]
|
|
409
|
+
def wrap(call)
|
|
410
|
+
name = call[:name].to_s
|
|
411
|
+
return nil if name.empty? || @registry&.key?(name)
|
|
412
|
+
|
|
413
|
+
tool = deferrable.find { |t| t.acts_as == name } or return nil
|
|
414
|
+
args = call[:args].is_a?(Hash) ? call[:args] : {}
|
|
415
|
+
BuiltinCalls.build(CALL_NAME, { "tool" => tool.full_name, "args" => args })
|
|
416
|
+
end
|
|
417
|
+
|
|
351
418
|
# The call tool's activity row: <group>/<tool> and its arguments.
|
|
352
419
|
def call_preview(args)
|
|
353
420
|
tool = lookup_quietly(args["tool"].to_s)
|
|
@@ -403,17 +470,32 @@ module Samagotchi
|
|
|
403
470
|
searched = all
|
|
404
471
|
if only && !only.strip.empty?
|
|
405
472
|
searched = all.select { |group| group.name == only.strip }
|
|
406
|
-
return "Error: no group #{only.strip}. Groups: #{all.map(&:name).join(", ")}" if searched.empty?
|
|
473
|
+
return FoundText.new("Error: no group #{only.strip}. Groups: #{all.map(&:name).join(", ")}") if searched.empty?
|
|
407
474
|
end
|
|
408
|
-
return names_text(searched) if words(query).empty?
|
|
475
|
+
return FoundText.new(names_text(searched)) if words(query).empty?
|
|
409
476
|
|
|
410
477
|
matches = ranked(query, searched.flat_map(&:tools)).take(FIND_RESULTS)
|
|
411
|
-
return "No tool matches \"#{query.strip}\".\n\n#{names_text(searched)}" if matches.empty?
|
|
478
|
+
return FoundText.new("No tool matches \"#{query.strip}\".\n\n#{names_text(searched)}") if matches.empty?
|
|
412
479
|
|
|
413
480
|
blocks = matches.map { |tool| match_text(tool, all) }
|
|
414
481
|
unsearched = searched.filter_map { |group| "#{group.name} #{group.note}" if group.tools.empty? && group.note }
|
|
415
482
|
blocks << "Not searched: #{unsearched.join("; ")}." unless unsearched.empty?
|
|
416
|
-
"
|
|
483
|
+
FoundText.new("#{CALL_HINT}\n\n#{blocks.join("\n\n")}",
|
|
484
|
+
loadable(matches))
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# The wire names of +matches+ a request can declare deferred: a
|
|
488
|
+
# built-in of the index's own groups by its name, a source's tool by
|
|
489
|
+
# its acts_as when it is #deferrable.
|
|
490
|
+
def loadable(matches)
|
|
491
|
+
sourced = nil
|
|
492
|
+
matches.filter_map do |tool|
|
|
493
|
+
next tool.name if tool.entry
|
|
494
|
+
next if tool.refusal
|
|
495
|
+
|
|
496
|
+
sourced ||= deferrable.map(&:acts_as)
|
|
497
|
+
tool.acts_as if sourced.include?(tool.acts_as)
|
|
498
|
+
end
|
|
417
499
|
end
|
|
418
500
|
|
|
419
501
|
# What the sources hear of the declared tools (/mcp's last line).
|
data/lib/samagotchi/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: samagotchi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.100.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -266,6 +266,8 @@ files:
|
|
|
266
266
|
- lib/samagotchi/bundles/system/manifest.yml
|
|
267
267
|
- lib/samagotchi/bundles/system/memory_guide.md
|
|
268
268
|
- lib/samagotchi/bundles/system/self_map.md
|
|
269
|
+
- lib/samagotchi/bundles/work-cap/manifest.yml
|
|
270
|
+
- lib/samagotchi/bundles/work-cap/plugin.rb
|
|
269
271
|
- lib/samagotchi/cache_confirm.rb
|
|
270
272
|
- lib/samagotchi/cancellation_controller.rb
|
|
271
273
|
- lib/samagotchi/child_env.rb
|
|
@@ -523,6 +525,7 @@ files:
|
|
|
523
525
|
- lib/samagotchi/session_metrics/model_segment.rb
|
|
524
526
|
- lib/samagotchi/session_metrics/side_totals.rb
|
|
525
527
|
- lib/samagotchi/session_metrics/totals.rb
|
|
528
|
+
- lib/samagotchi/session_metrics/usage.rb
|
|
526
529
|
- lib/samagotchi/session_model_settings.rb
|
|
527
530
|
- lib/samagotchi/session_observer.rb
|
|
528
531
|
- lib/samagotchi/session_retention.rb
|
|
@@ -590,6 +593,7 @@ files:
|
|
|
590
593
|
- lib/samagotchi/tool_call_parser.rb
|
|
591
594
|
- lib/samagotchi/tool_declarations.rb
|
|
592
595
|
- lib/samagotchi/tool_ids.rb
|
|
596
|
+
- lib/samagotchi/tool_loads.rb
|
|
593
597
|
- lib/samagotchi/tool_output_cut.rb
|
|
594
598
|
- lib/samagotchi/tool_output_files.rb
|
|
595
599
|
- lib/samagotchi/tool_response.rb
|
|
@@ -605,6 +609,7 @@ files:
|
|
|
605
609
|
- lib/samagotchi/tools/builtins.rb
|
|
606
610
|
- lib/samagotchi/tools/cancel_reminder.rb
|
|
607
611
|
- lib/samagotchi/tools/context_read.rb
|
|
612
|
+
- lib/samagotchi/tools/deferred_tools.rb
|
|
608
613
|
- lib/samagotchi/tools/delegate.rb
|
|
609
614
|
- lib/samagotchi/tools/delegate_cursor.rb
|
|
610
615
|
- lib/samagotchi/tools/delegate_relay.rb
|