samagotchi 0.154.1 → 0.156.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/bin/chi +1 -1
- data/docs/architecture.md +12 -7
- data/docs/bundles.md +21 -0
- data/docs/cli.md +1 -1
- data/docs/guardrails.md +2 -2
- data/docs/hooks.md +1 -1
- data/docs/internals/anthropic.md +5 -5
- data/docs/internals/attached-context.md +9 -6
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/block-renderers.md +8 -1
- data/docs/internals/engine.md +642 -831
- data/docs/internals/inline-widgets.md +136 -0
- data/docs/internals/llm-context-forget.md +8 -3
- data/docs/internals/objectives.md +8 -6
- data/docs/internals/participants.md +5 -5
- data/docs/internals/prompt-caching.md +13 -6
- data/docs/internals/remote-sessions.md +3 -1
- data/docs/internals/web-frontend.md +1 -1
- data/docs/internals/working-set.md +2 -1
- data/docs/plugins.md +2 -0
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +4 -4
- data/docs/system-one.md +3 -2
- data/docs/testing.md +1 -1
- data/lib/samagotchi/block_renderers.rb +5 -0
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -1
- data/lib/samagotchi/broadcast_command.rb +3 -3
- data/lib/samagotchi/bundle_command/check.rb +1 -1
- data/lib/samagotchi/bundle_command/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundles/dev/manifest.yml +3 -2
- data/lib/samagotchi/bundles/inline-widgets/manifest.yml +11 -0
- data/lib/samagotchi/bundles/inline-widgets/plugin.rb +76 -0
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/child_move.rb +7 -51
- data/lib/samagotchi/children_status.rb +1 -1
- data/lib/samagotchi/cli/parent_wait.rb +1 -1
- data/lib/samagotchi/config.rb +17 -112
- data/lib/samagotchi/context_command.rb +1 -2
- data/lib/samagotchi/context_window.rb +3 -3
- data/lib/samagotchi/desktop/macos.rb +1 -1
- data/lib/samagotchi/engine/context_judge.rb +21 -119
- data/lib/samagotchi/engine/guardrails.rb +4 -2
- data/lib/samagotchi/engine/messages.rb +9 -6
- data/lib/samagotchi/engine/models.rb +5 -2
- data/lib/samagotchi/engine/objectives.rb +30 -80
- data/lib/samagotchi/engine/participants.rb +15 -10
- data/lib/samagotchi/engine/plugins.rb +3 -1
- data/lib/samagotchi/engine/questions.rb +1 -1
- data/lib/samagotchi/engine/side_requests.rb +26 -13
- data/lib/samagotchi/engine/toolkit.rb +35 -191
- data/lib/samagotchi/engine/turn_end.rb +1 -1
- data/lib/samagotchi/engine/turn_helpers.rb +16 -39
- data/lib/samagotchi/engine/verify.rb +7 -143
- data/lib/samagotchi/engine.rb +166 -98
- data/lib/samagotchi/engine_peers.rb +1 -1
- data/lib/samagotchi/event_fanout.rb +18 -12
- data/lib/samagotchi/guardrail_wiring.rb +83 -82
- data/lib/samagotchi/guardrails/gate.rb +41 -40
- data/lib/samagotchi/guardrails/pair_boundary.rb +2 -1
- data/lib/samagotchi/guardrails/review_boundary.rb +2 -1
- data/lib/samagotchi/guardrails/targets.rb +28 -11
- data/lib/samagotchi/guardrails_command.rb +1 -1
- data/lib/samagotchi/host_config.rb +72 -1
- data/lib/samagotchi/host_model.rb +2 -6
- data/lib/samagotchi/host_registry.rb +1 -1
- data/lib/samagotchi/inline_widgets.rb +325 -0
- data/lib/samagotchi/kernel_loop.rb +215 -313
- data/lib/samagotchi/llm/anthropic_wire.rb +2 -2
- data/lib/samagotchi/llm/backend.rb +3 -1
- data/lib/samagotchi/llm/chat_loop.rb +87 -49
- data/lib/samagotchi/llm/native_backend.rb +3 -2
- data/lib/samagotchi/llm_context_apply.rb +54 -1
- data/lib/samagotchi/llm_context_forget.rb +5 -3
- data/lib/samagotchi/llm_context_notice.rb +1 -1
- data/lib/samagotchi/llm_context_strategy.rb +7 -9
- data/lib/samagotchi/llm_context_view.rb +9 -0
- data/lib/samagotchi/model_binding.rb +3 -3
- data/lib/samagotchi/model_price.rb +4 -10
- data/lib/samagotchi/model_profile.rb +6 -6
- data/lib/samagotchi/model_settings.rb +37 -2
- data/lib/samagotchi/note_command.rb +1 -2
- data/lib/samagotchi/objectives_audit.rb +4 -3
- data/lib/samagotchi/participants.rb +2 -2
- data/lib/samagotchi/plugin/context.rb +1 -1
- data/lib/samagotchi/plugin/prompt_sections.rb +7 -0
- data/lib/samagotchi/plugin/sessions.rb +2 -5
- data/lib/samagotchi/plugin/tool_spec.rb +5 -3
- data/lib/samagotchi/project_env.rb +1 -1
- data/lib/samagotchi/question_desk.rb +1 -1
- data/lib/samagotchi/reasoning_history.rb +3 -3
- data/lib/samagotchi/reminder_carry.rb +1 -1
- data/lib/samagotchi/reprefill_meter.rb +45 -0
- data/lib/samagotchi/review_command.rb +1 -1
- data/lib/samagotchi/revise_anchor.rb +1 -1
- data/lib/samagotchi/sampling_settings.rb +3 -3
- data/lib/samagotchi/self_report.rb +7 -7
- data/lib/samagotchi/session.rb +50 -7
- data/lib/samagotchi/session_access/local.rb +7 -5
- data/lib/samagotchi/session_access/remote.rb +17 -3
- data/lib/samagotchi/session_archive_command.rb +2 -2
- data/lib/samagotchi/session_chain.rb +7 -6
- data/lib/samagotchi/session_continue.rb +6 -5
- data/lib/samagotchi/session_delete_command.rb +1 -1
- data/lib/samagotchi/session_manager/delivery.rb +254 -0
- data/lib/samagotchi/session_manager/emptiness.rb +138 -0
- data/lib/samagotchi/session_manager/lifecycle.rb +274 -0
- data/lib/samagotchi/session_manager/listing.rb +189 -0
- data/lib/samagotchi/session_manager/removal.rb +190 -0
- data/lib/samagotchi/session_manager/spawn.rb +242 -0
- data/lib/samagotchi/session_manager/worker_command.rb +157 -0
- data/lib/samagotchi/session_manager.rb +26 -1540
- data/lib/samagotchi/session_metrics.rb +1 -1
- data/lib/samagotchi/sessions_command.rb +1 -1
- data/lib/samagotchi/share_command.rb +1 -1
- data/lib/samagotchi/starting_marker.rb +65 -0
- data/lib/samagotchi/steer_cut_after.rb +3 -3
- data/lib/samagotchi/system_one/recorder.rb +49 -0
- data/lib/samagotchi/system_prompt.rb +37 -30
- data/lib/samagotchi/terminal_ui/exit_lines.rb +1 -1
- data/lib/samagotchi/terminal_ui.rb +2 -6
- data/lib/samagotchi/testing/boundary.rb +7 -0
- data/lib/samagotchi/testing/bundle.rb +7 -5
- data/lib/samagotchi/testing/install.rb +3 -2
- data/lib/samagotchi/testing/sessions.rb +1 -1
- data/lib/samagotchi/thinking.rb +3 -3
- data/lib/samagotchi/token_usage.rb +1 -1
- data/lib/samagotchi/tool_activity.rb +15 -7
- data/lib/samagotchi/tool_declarations.rb +6 -6
- data/lib/samagotchi/tool_output_cut.rb +17 -0
- data/lib/samagotchi/tool_response.rb +4 -3
- data/lib/samagotchi/tool_runner.rb +96 -102
- data/lib/samagotchi/toolkit_binding.rb +40 -14
- data/lib/samagotchi/toolkit_command.rb +1 -1
- data/lib/samagotchi/tools/deferred_tools.rb +17 -21
- data/lib/samagotchi/tools/delegate.rb +10 -5
- data/lib/samagotchi/tools/delegate_result.rb +4 -4
- data/lib/samagotchi/tools/index_hint.rb +14 -0
- data/lib/samagotchi/tools/registry.rb +64 -44
- data/lib/samagotchi/tools/send_note.rb +4 -6
- data/lib/samagotchi/tools/tool_index.rb +150 -102
- data/lib/samagotchi/tools/tool_surface.rb +187 -0
- data/lib/samagotchi/turn_context.rb +68 -0
- data/lib/samagotchi/turn_stages/context_judge.rb +172 -0
- data/lib/samagotchi/turn_stages/objectives.rb +150 -0
- data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +257 -0
- data/lib/samagotchi/turn_stages/verify_anchors.rb +168 -0
- data/lib/samagotchi/turn_stages/warmup.rb +82 -0
- data/lib/samagotchi/turn_state.rb +46 -4
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +3 -3
- data/lib/samagotchi/web/app.rb +15 -8
- data/lib/samagotchi/web/markdown_renderer.rb +39 -14
- data/lib/samagotchi/web/message_display.rb +1 -1
- data/lib/samagotchi/web/participant_access.rb +2 -2
- data/lib/samagotchi/web/public/app.js +5 -1
- data/lib/samagotchi/web/public/block_host.js +74 -199
- data/lib/samagotchi/web/public/block_renderers.css +2 -0
- data/lib/samagotchi/web/public/block_source.js +7 -4
- data/lib/samagotchi/web/public/block_watch.js +256 -0
- data/lib/samagotchi/web/public/composer_fold.js +3 -0
- data/lib/samagotchi/web/public/index.html +28 -9
- data/lib/samagotchi/web/public/inline_widgets.js +146 -0
- data/lib/samagotchi/web/public/owner_verify.js +1 -1
- data/lib/samagotchi/web/public/participant.js +1 -1
- data/lib/samagotchi/web/public/revise_stack.js +1 -1
- data/lib/samagotchi/web/public/stage_watch.js +6 -2
- data/lib/samagotchi/web/public/text_roots.js +1 -1
- data/lib/samagotchi/web/public/verify_target.js +1 -1
- data/lib/samagotchi/web/public/widget_picks.js +130 -0
- data/lib/samagotchi/web/public/widget_walk.js +230 -0
- data/lib/samagotchi/web/public/widgets.css +70 -0
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +3 -3
- data/lib/samagotchi/web/session_create.rb +3 -3
- data/lib/samagotchi/web/session_hub.rb +1 -1
- data/lib/samagotchi/web/session_show.rb +3 -3
- data/lib/samagotchi/web/session_summary.rb +3 -4
- data/lib/samagotchi/worker.rb +55 -27
- data/lib/samagotchi/worker_boot.rb +184 -0
- data/lib/samagotchi/worker_signals.rb +1 -1
- data/lib/samagotchi/worker_task_wakes.rb +2 -2
- metadata +29 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require_relative "args"
|
|
5
5
|
require_relative "builtin_calls"
|
|
6
|
+
require_relative "index_hint"
|
|
6
7
|
require_relative "registry"
|
|
7
8
|
require_relative "../log"
|
|
8
9
|
require_relative "../token_usage"
|
|
@@ -11,6 +12,10 @@ require_relative "../plugin/tool_spec"
|
|
|
11
12
|
|
|
12
13
|
module Samagotchi
|
|
13
14
|
module Tools
|
|
15
|
+
# Read only when a surface is built (#declare, #refresh!): the bundle
|
|
16
|
+
# test kit loads this file for ToolIndex.new and never builds one.
|
|
17
|
+
autoload :ToolSurface, File.expand_path("tool_surface", __dir__)
|
|
18
|
+
|
|
14
19
|
# The tools a session knows but doesn't declare one by one, and the two
|
|
15
20
|
# tools that reach them (docs/plugins.md, "Publishing tools to the
|
|
16
21
|
# index"): find_tools, a keyword search that answers each match's
|
|
@@ -18,11 +23,20 @@ module Samagotchi
|
|
|
18
23
|
# A source (the mcp bundle, chi.tool_source) publishes its groups (an
|
|
19
24
|
# MCP server each) and runs the calls; the index's own group, chi, is
|
|
20
25
|
# the tools the session's toolkit leaves out but doesn't remove, chi's
|
|
21
|
-
# built-ins and its bundles' (chi.tool) alike (
|
|
26
|
+
# built-ins and its bundles' (chi.tool) alike (ToolSurface#index_only),
|
|
22
27
|
# and a call_tool of one runs as that tool (#unwrap, before the
|
|
23
28
|
# guardrail gate). The index searches the
|
|
24
29
|
# groups, looks a call's tool up and checks it.
|
|
25
30
|
#
|
|
31
|
+
# What it reads of the session's tools is a ToolSurface, always given:
|
|
32
|
+
# the turn's (TurnContext#surface) on the turn's path (the two tools'
|
|
33
|
+
# handlers, through their ToolContext; call_tool's row and targets,
|
|
34
|
+
# from the row and the gate; #unwrap, #wrap, #deferrable and
|
|
35
|
+
# #unreached_tool, from the runner and the deferred list); one built
|
|
36
|
+
# from the Registry #declare registered in at load and at #refresh!;
|
|
37
|
+
# ToolkitBinding#published's for ctx.load_tools (#deferred_name). None
|
|
38
|
+
# where one is needed raises MissingSurface.
|
|
39
|
+
#
|
|
26
40
|
# The two tools are registered once, after the plugins load, and
|
|
27
41
|
# declared per toolkit (Registry#declared?): when a source registered
|
|
28
42
|
# then, or the toolkit leaves a tool reachable. The search tool's
|
|
@@ -58,8 +72,6 @@ module Samagotchi
|
|
|
58
72
|
group: { type: "string", description: "search only this group" }
|
|
59
73
|
}.freeze
|
|
60
74
|
CALL_DESCRIPTION = "Call a tool #{FIND_NAME} found.".freeze
|
|
61
|
-
# A keyword answer's first line.
|
|
62
|
-
CALL_HINT = "Call one with #{CALL_NAME}(tool: \"<group>/<tool>\", args: {…}).".freeze
|
|
63
75
|
CALL_PARAMS = {
|
|
64
76
|
tool: { type: "string", required: true, description: "the tool as <group>/<tool>" },
|
|
65
77
|
args: { type: "object", description: "the arguments, per the inputSchema #{FIND_NAME} gave" }
|
|
@@ -70,10 +82,11 @@ module Samagotchi
|
|
|
70
82
|
class ToolkitGroupRefusal < StandardError
|
|
71
83
|
RULE = "toolkit-group"
|
|
72
84
|
|
|
73
|
-
# The refusal of +tool+ under +
|
|
74
|
-
# reaches its group when
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
# The refusal of +tool+ under +surface+'s toolkit (a ToolSurface),
|
|
86
|
+
# naming a kit that reaches its group when it can tell
|
|
87
|
+
# (ToolSurface#reaching_kit).
|
|
88
|
+
def self.for(tool, surface)
|
|
89
|
+
new(tool, surface.toolkit, kit: surface.reaching_kit&.call(tool.group))
|
|
77
90
|
end
|
|
78
91
|
|
|
79
92
|
def initialize(tool, toolkit, kit: nil)
|
|
@@ -248,12 +261,12 @@ module Samagotchi
|
|
|
248
261
|
# @return [Array<Source>] the sources added, in order
|
|
249
262
|
def sources = @sources.dup
|
|
250
263
|
|
|
251
|
-
# Every group, now: the index's own (#registry_groups
|
|
252
|
-
# sources'. A source group with a reserved name,
|
|
253
|
-
# group has, is left out of search, lookup and the
|
|
254
|
-
# once per name: a source publishes after load too).
|
|
255
|
-
def all_groups
|
|
256
|
-
own = registry_groups
|
|
264
|
+
# Every group, now: the index's own (#registry_groups, from
|
|
265
|
+
# +surface+), then the sources'. A source group with a reserved name,
|
|
266
|
+
# or one an earlier group has, is left out of search, lookup and the
|
|
267
|
+
# description (said once per name: a source publishes after load too).
|
|
268
|
+
def all_groups(surface)
|
|
269
|
+
own = registry_groups(surface)
|
|
257
270
|
taken = own.map(&:name) | RESERVED_GROUPS
|
|
258
271
|
kept = @sources.flat_map(&:groups).select do |group|
|
|
259
272
|
why = if RESERVED_GROUPS.include?(group.name) then "reserved"
|
|
@@ -266,9 +279,11 @@ module Samagotchi
|
|
|
266
279
|
own + kept
|
|
267
280
|
end
|
|
268
281
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
282
|
+
# The groups +surface+'s toolkit reaches (#all_groups' own, and the
|
|
283
|
+
# source groups #declare found that it reaches).
|
|
284
|
+
def reached_groups(surface)
|
|
285
|
+
groups = all_groups(surface)
|
|
286
|
+
toolkit = surface.toolkit
|
|
272
287
|
return groups if toolkit.nil? || toolkit.groups == :all
|
|
273
288
|
|
|
274
289
|
declared = @declared_group_names || groups.reject { |group| own?(group) }.map(&:name)
|
|
@@ -285,10 +300,11 @@ module Samagotchi
|
|
|
285
300
|
# @return [Array<String>] the names registered
|
|
286
301
|
def declare(registry, failures: nil)
|
|
287
302
|
@registry = registry
|
|
288
|
-
|
|
303
|
+
at_load = ToolSurface.for(registry)
|
|
304
|
+
@described = description_lines(all_groups(at_load).reject { |group| own?(group) }).freeze
|
|
289
305
|
@declared_group_names = @described.keys.freeze
|
|
290
306
|
loaded = registry.registered
|
|
291
|
-
specs = [find_spec, call_spec]
|
|
307
|
+
specs = [find_spec(at_load), call_spec]
|
|
292
308
|
declared = specs.filter_map do |spec|
|
|
293
309
|
if (taken = registry[spec.name])
|
|
294
310
|
failures&.add("tool #{spec.name}", "already registered (#{taken.source}); the tool index's is left out",
|
|
@@ -296,14 +312,17 @@ module Samagotchi
|
|
|
296
312
|
next
|
|
297
313
|
end
|
|
298
314
|
|
|
299
|
-
unwrap = (->(call,
|
|
300
|
-
registry.register(spec.name, source: SOURCE, unwrap: unwrap,
|
|
315
|
+
unwrap = (->(call, surface) { unwrap(call, surface) } if spec.name == CALL_NAME)
|
|
316
|
+
registry.register(spec.name, source: SOURCE, unwrap: unwrap,
|
|
317
|
+
**spec.entry_fields(nil).merge(own_fields(spec)))
|
|
301
318
|
spec
|
|
302
319
|
end
|
|
303
320
|
registry.index = Registry::Index.new(source: SOURCE, call_name: CALL_NAME, groups: @declared_group_names,
|
|
304
|
-
deferrable: ->
|
|
305
|
-
|
|
306
|
-
|
|
321
|
+
deferrable: ->(surface) { deferrable(surface) },
|
|
322
|
+
wrap: ->(call, surface) { wrap(call, surface) },
|
|
323
|
+
unreached: ->(name, surface) { unreached_tool(name, surface) },
|
|
324
|
+
loaded: loaded)
|
|
325
|
+
declared!(declared, ToolSurface.for(registry))
|
|
307
326
|
declared.map(&:name)
|
|
308
327
|
end
|
|
309
328
|
|
|
@@ -314,8 +333,9 @@ module Samagotchi
|
|
|
314
333
|
entry = @registry && @registry[FIND_NAME]
|
|
315
334
|
return unless entry&.source == SOURCE
|
|
316
335
|
|
|
317
|
-
|
|
318
|
-
|
|
336
|
+
surface = ToolSurface.for(@registry)
|
|
337
|
+
entry.schema = entry.schema.merge(description: find_description(surface))
|
|
338
|
+
declared!([find_spec(surface), call_spec].select { |spec| @registry[spec.name]&.source == SOURCE }, surface)
|
|
319
339
|
end
|
|
320
340
|
|
|
321
341
|
# A checked tool declaration's (Plugin::ToolSpec) definition as
|
|
@@ -340,10 +360,10 @@ module Samagotchi
|
|
|
340
360
|
# server's: its name, and its summary if it has one). Never every
|
|
341
361
|
# tool: knowing the names, a model searches less and guesses
|
|
342
362
|
# arguments.
|
|
343
|
-
def find_description
|
|
363
|
+
def find_description(surface)
|
|
344
364
|
"Search tools this session doesn't declare, by keywords. It answers up to #{FIND_RESULTS} tools, each with " \
|
|
345
365
|
"its name (<group>/<tool>) and inputSchema; call one with #{CALL_NAME}. An empty query lists every tool's " \
|
|
346
|
-
"name.\nGroups:\n#{(description_lines(registry_groups).values + described_groups.values).join("\n")}"
|
|
366
|
+
"name.\nGroups:\n#{(description_lines(registry_groups(surface)).values + described_groups(surface).values).join("\n")}"
|
|
347
367
|
end
|
|
348
368
|
|
|
349
369
|
# ── Search ──────────────────────────────────────────────────────────
|
|
@@ -351,10 +371,10 @@ module Samagotchi
|
|
|
351
371
|
# The search tool: the best matches with their schemas, or with an
|
|
352
372
|
# empty query every group's tool names. +server+ is the parameter's
|
|
353
373
|
# name before it was +group+: a model that still gives it is told
|
|
354
|
-
# every group was searched.
|
|
374
|
+
# every group was searched. +surface+: the turn's (the handler's).
|
|
355
375
|
# @return [FoundText]
|
|
356
|
-
def find(query, only = nil, server: nil)
|
|
357
|
-
answer = find_in(query, only)
|
|
376
|
+
def find(query, only = nil, surface:, server: nil)
|
|
377
|
+
answer = find_in(query, only, surface)
|
|
358
378
|
return answer unless (only.nil? || only.strip.empty?) && !server.to_s.strip.empty?
|
|
359
379
|
|
|
360
380
|
FoundText.new("(searched every group)\n#{answer}", answer.loaded)
|
|
@@ -366,11 +386,12 @@ module Samagotchi
|
|
|
366
386
|
# run by its source. It fails closed: a tool that isn't the one
|
|
367
387
|
# guardrails were given (#claimed) is refused, and so is one of the
|
|
368
388
|
# index's own (a chi tool runs as itself, #unwrap, or not at all).
|
|
369
|
-
|
|
370
|
-
|
|
389
|
+
# +surface+: the turn's (the handler's).
|
|
390
|
+
def call(name, args, surface:)
|
|
391
|
+
tool = lookup(name, surface)
|
|
371
392
|
return tool if tool.is_a?(String)
|
|
372
393
|
|
|
373
|
-
unless claimed(name.strip)&.first == tool.acts_as
|
|
394
|
+
unless claimed(name.strip, surface)&.first == tool.acts_as
|
|
374
395
|
# A bare acts_as of a source with no prefix: guardrails can't
|
|
375
396
|
# name it before its tool is listed (#claimed), so only
|
|
376
397
|
# <group>/<tool> is taken.
|
|
@@ -399,16 +420,16 @@ module Samagotchi
|
|
|
399
420
|
# any registered built-in or plugin tool: one the toolkit removes or declares
|
|
400
421
|
# is then refused by its name before the gate (ToolRunner#left_out?),
|
|
401
422
|
# with no hook or card. Nil, so the call runs as call_tool, when the
|
|
402
|
-
# turn
|
|
423
|
+
# turn's +surface+ doesn't declare call_tool, for an MCP tool, a miss
|
|
403
424
|
# or arguments that aren't an object.
|
|
404
425
|
# @return [Hash, nil]
|
|
405
|
-
def unwrap(call,
|
|
406
|
-
return nil unless
|
|
426
|
+
def unwrap(call, surface)
|
|
427
|
+
return nil unless surface.declared_entry(CALL_NAME)
|
|
407
428
|
|
|
408
429
|
args = call[:args].is_a?(Hash) ? call[:args] : {}
|
|
409
430
|
name = args["tool"].to_s.strip
|
|
410
|
-
tool = lookup_quietly(name)
|
|
411
|
-
entry = tool&.entry || builtin_named(name)
|
|
431
|
+
tool = lookup_quietly(name, surface)
|
|
432
|
+
entry = tool&.entry || builtin_named(name, surface)
|
|
412
433
|
return nil unless entry
|
|
413
434
|
|
|
414
435
|
inner = inner_args(args["args"], tool)
|
|
@@ -421,13 +442,13 @@ module Samagotchi
|
|
|
421
442
|
# registered tool's. Sorted by acts_as, so the same index gives the
|
|
422
443
|
# same list in any session and after a respawn.
|
|
423
444
|
# @return [Array<Tool>]
|
|
424
|
-
def deferrable
|
|
425
|
-
all = all_groups.reject { |group| own?(group) }.flat_map(&:tools)
|
|
426
|
-
reached = reached_groups.reject { |group| own?(group) }.flat_map(&:tools)
|
|
445
|
+
def deferrable(surface)
|
|
446
|
+
all = all_groups(surface).reject { |group| own?(group) }.flat_map(&:tools)
|
|
447
|
+
reached = reached_groups(surface).reject { |group| own?(group) }.flat_map(&:tools)
|
|
427
448
|
counts = all.map(&:acts_as).tally
|
|
428
449
|
reached.select do |tool|
|
|
429
450
|
tool.refusal.nil? && counts[tool.acts_as] == 1 && tool.acts_as.match?(DEFERRABLE_NAME) &&
|
|
430
|
-
|
|
451
|
+
!surface.registered?(tool.acts_as)
|
|
431
452
|
end.sort_by(&:acts_as)
|
|
432
453
|
end
|
|
433
454
|
|
|
@@ -437,29 +458,29 @@ module Samagotchi
|
|
|
437
458
|
# <group>/<tool>). Nil for any other: declared, removed, unknown, or a
|
|
438
459
|
# source tool that can't be deferred. Never waits for a start.
|
|
439
460
|
# @return [String, nil]
|
|
440
|
-
def deferred_name(name)
|
|
461
|
+
def deferred_name(name, surface)
|
|
441
462
|
name = name.to_s.strip
|
|
442
463
|
bare = name.delete_prefix("#{CHI_GROUP}/")
|
|
443
|
-
return bare if
|
|
464
|
+
return bare if surface.indexed_entry(bare)
|
|
444
465
|
|
|
445
|
-
tool = lookup_quietly(name) or return nil
|
|
466
|
+
tool = lookup_quietly(name, surface) or return nil
|
|
446
467
|
return nil if tool.entry || tool.refusal
|
|
447
468
|
|
|
448
|
-
deferrable.any? { |t| t.acts_as == tool.acts_as } ? tool.acts_as : nil
|
|
469
|
+
deferrable(surface).any? { |t| t.acts_as == tool.acts_as } ? tool.acts_as : nil
|
|
449
470
|
end
|
|
450
471
|
|
|
451
472
|
# Finds a source tool regardless of the active toolkit, without
|
|
452
473
|
# waiting for a starting group. Used only to refuse a call or explain
|
|
453
474
|
# a deferred stub for a group the session cannot reach.
|
|
454
|
-
def unreached_tool(name)
|
|
475
|
+
def unreached_tool(name, surface)
|
|
455
476
|
name = name.to_s.strip
|
|
456
477
|
return nil if name.empty?
|
|
457
478
|
|
|
458
|
-
groups = all_groups.reject { |group| own?(group) }
|
|
479
|
+
groups = all_groups(surface).reject { |group| own?(group) }
|
|
459
480
|
groups.select! { |group| @declared_group_names.include?(group.name) } if @declared_group_names
|
|
460
481
|
tool = tool_in(name, groups)
|
|
461
482
|
return nil unless tool
|
|
462
|
-
return nil if reached_groups.any? { |group| group.name == tool.group }
|
|
483
|
+
return nil if reached_groups(surface).any? { |group| group.name == tool.group }
|
|
463
484
|
|
|
464
485
|
tool
|
|
465
486
|
end
|
|
@@ -473,18 +494,19 @@ module Samagotchi
|
|
|
473
494
|
# loads tools): the call tool's row, rules and approvals. Nil for any
|
|
474
495
|
# other call.
|
|
475
496
|
# @return [Hash, nil]
|
|
476
|
-
def wrap(call)
|
|
497
|
+
def wrap(call, surface)
|
|
477
498
|
name = call[:name].to_s
|
|
478
|
-
return nil if name.empty? ||
|
|
499
|
+
return nil if name.empty? || surface.registered?(name)
|
|
479
500
|
|
|
480
|
-
tool = deferrable.find { |t| t.acts_as == name } or return nil
|
|
501
|
+
tool = deferrable(surface).find { |t| t.acts_as == name } or return nil
|
|
481
502
|
args = call[:args].is_a?(Hash) ? call[:args] : {}
|
|
482
503
|
BuiltinCalls.build(CALL_NAME, { "tool" => tool.full_name, "args" => args })
|
|
483
504
|
end
|
|
484
505
|
|
|
485
|
-
# The call tool's activity row: <group>/<tool> and its arguments
|
|
486
|
-
|
|
487
|
-
|
|
506
|
+
# The call tool's activity row: <group>/<tool> and its arguments, as
|
|
507
|
+
# +surface+ (the turn's) reaches it.
|
|
508
|
+
def call_preview(args, surface)
|
|
509
|
+
tool = lookup_quietly(args["tool"].to_s, surface)
|
|
488
510
|
name = tool ? tool.full_name : args["tool"].to_s.strip
|
|
489
511
|
inner = tool && inner_args(args["args"], tool)
|
|
490
512
|
inner ? [name, preview(inner)].reject(&:empty?).join(" ") : name
|
|
@@ -495,11 +517,11 @@ module Samagotchi
|
|
|
495
517
|
# indexed yet (its server still starting) is named from what the
|
|
496
518
|
# model gave (#claimed), so a rule on it fires before the call waits
|
|
497
519
|
# for the server. Nothing for a name that can't be a tool (the call
|
|
498
|
-
# refuses it).
|
|
499
|
-
def call_targets(args)
|
|
520
|
+
# refuses it). +surface+: the turn's (the gate's).
|
|
521
|
+
def call_targets(args, surface)
|
|
500
522
|
name = args["tool"].to_s.strip
|
|
501
|
-
tool = lookup_quietly(name)
|
|
502
|
-
acts_as, label = tool ? [tool.acts_as, "#{tool.group}: #{tool.name}"] : claimed(name)
|
|
523
|
+
tool = lookup_quietly(name, surface)
|
|
524
|
+
acts_as, label = tool ? [tool.acts_as, "#{tool.group}: #{tool.name}"] : claimed(name, surface)
|
|
503
525
|
return {} unless acts_as
|
|
504
526
|
|
|
505
527
|
{ acts_as: acts_as, args: inner_args(args["args"], tool) || {}, label: label }.compact
|
|
@@ -507,20 +529,41 @@ module Samagotchi
|
|
|
507
529
|
|
|
508
530
|
private
|
|
509
531
|
|
|
510
|
-
|
|
511
|
-
|
|
532
|
+
# The fields +spec+'s tool registers with beside its entry_fields: a
|
|
533
|
+
# handler whose block is given the call's typed args and the turn's
|
|
534
|
+
# surface (its ToolContext's), and call_tool's preview and targets,
|
|
535
|
+
# given the turn's surface by the row and the gate
|
|
536
|
+
# (Registry::Entry#preview_of / #targets_of). Without a surface each
|
|
537
|
+
# raises MissingSurface: none reads the Registry as it is now.
|
|
538
|
+
def own_fields(spec)
|
|
539
|
+
parameters = spec.schema[:parameters]
|
|
540
|
+
args = ->(call) { Plugin::ToolSpec.args_of(call, parameters) }
|
|
541
|
+
fields = { handler: lambda { |call, kctx|
|
|
542
|
+
Plugin::ToolSpec.result_text(spec.block.call(args.call(call), kctx&.surface || raise(MissingSurface)))
|
|
543
|
+
} }
|
|
544
|
+
return fields unless spec.name == CALL_NAME
|
|
545
|
+
|
|
546
|
+
fields.merge(preview: ->(call, surface = nil) { call_preview(args.call(call), surface || raise(MissingSurface)) },
|
|
547
|
+
targets: ->(call, surface = nil) { call_targets(args.call(call), surface || raise(MissingSurface)) })
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# The search tool's declaration, its description read from +surface+
|
|
551
|
+
# (the Registry's now: #declare, #refresh!); its block searches the
|
|
552
|
+
# turn's (own_fields' handler gives it).
|
|
553
|
+
def find_spec(surface)
|
|
554
|
+
Plugin::ToolSpec.build(FIND_NAME, find_description(surface), params: FIND_PARAMS,
|
|
512
555
|
preview: lambda { |args|
|
|
513
556
|
[args["query"].to_s.strip, args["group"] && "in #{args["group"]}"].compact.join(" ")
|
|
514
|
-
}) do |args,
|
|
515
|
-
find(args["query"].to_s, args["group"], server: args["server"])
|
|
557
|
+
}) do |args, turn|
|
|
558
|
+
find(args["query"].to_s, args["group"], server: args["server"], surface: turn)
|
|
516
559
|
end
|
|
517
560
|
end
|
|
518
561
|
|
|
562
|
+
# The call tool's declaration; its preview and targets are own_fields'.
|
|
519
563
|
def call_spec
|
|
520
|
-
Plugin::ToolSpec.build(CALL_NAME, CALL_DESCRIPTION, params: CALL_PARAMS,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
call(args["tool"].to_s, args["args"])
|
|
564
|
+
Plugin::ToolSpec.build(CALL_NAME, CALL_DESCRIPTION, params: CALL_PARAMS,
|
|
565
|
+
label: @sources.first&.label) do |args, turn|
|
|
566
|
+
call(args["tool"].to_s, args["args"], surface: turn)
|
|
524
567
|
end
|
|
525
568
|
end
|
|
526
569
|
|
|
@@ -532,8 +575,8 @@ module Samagotchi
|
|
|
532
575
|
@sources.find { |source| source.groups.any? { |g| g.name == group } }
|
|
533
576
|
end
|
|
534
577
|
|
|
535
|
-
def find_in(query, only)
|
|
536
|
-
all = reached_groups
|
|
578
|
+
def find_in(query, only, surface)
|
|
579
|
+
all = reached_groups(surface)
|
|
537
580
|
searched = all
|
|
538
581
|
if only && !only.strip.empty?
|
|
539
582
|
searched = all.select { |group| group.name == only.strip }
|
|
@@ -547,62 +590,63 @@ module Samagotchi
|
|
|
547
590
|
blocks = matches.map { |tool| match_text(tool, all) }
|
|
548
591
|
unsearched = searched.filter_map { |group| "#{group.name} #{group.note}" if group.tools.empty? && group.note }
|
|
549
592
|
blocks << "Not searched: #{unsearched.join("; ")}." unless unsearched.empty?
|
|
550
|
-
FoundText.new("#{CALL_HINT}\n\n#{blocks.join("\n\n")}",
|
|
551
|
-
loadable(matches))
|
|
593
|
+
FoundText.new("#{IndexHint::CALL_HINT}\n\n#{blocks.join("\n\n")}",
|
|
594
|
+
loadable(matches, surface))
|
|
552
595
|
end
|
|
553
596
|
|
|
554
597
|
# The wire names of +matches+ a request can declare deferred: a
|
|
555
598
|
# chi tool of the index's own groups by its name, a source's tool by
|
|
556
599
|
# its acts_as when it is #deferrable.
|
|
557
|
-
def loadable(matches)
|
|
600
|
+
def loadable(matches, surface)
|
|
558
601
|
sourced = nil
|
|
559
602
|
matches.filter_map do |tool|
|
|
560
603
|
next tool.name if tool.entry
|
|
561
604
|
next if tool.refusal
|
|
562
605
|
|
|
563
|
-
sourced ||= deferrable.map(&:acts_as)
|
|
606
|
+
sourced ||= deferrable(surface).map(&:acts_as)
|
|
564
607
|
tool.acts_as if sourced.include?(tool.acts_as)
|
|
565
608
|
end
|
|
566
609
|
end
|
|
567
610
|
|
|
568
|
-
# What the sources hear of the declared tools (/mcp's last line)
|
|
569
|
-
|
|
611
|
+
# What the sources hear of the declared tools (/mcp's last line), the
|
|
612
|
+
# groups +surface+ (the Registry's now) reaches.
|
|
613
|
+
def declared!(specs, surface)
|
|
570
614
|
names = specs.map(&:name)
|
|
571
615
|
tokens = specs.sum { |spec| ToolIndex.spec_tokens(spec) }
|
|
572
|
-
groups = reached_groups.reject { |group| own?(group) }.map(&:name)
|
|
616
|
+
groups = reached_groups(surface).reject { |group| own?(group) }.map(&:name)
|
|
573
617
|
@sources.each { |source| source.declared!(names, tokens, groups) }
|
|
574
618
|
end
|
|
575
619
|
|
|
576
|
-
def described_groups
|
|
577
|
-
|
|
620
|
+
def described_groups(surface)
|
|
621
|
+
toolkit = surface.toolkit
|
|
622
|
+
return @described unless toolkit && toolkit.groups != :all
|
|
578
623
|
|
|
579
|
-
@described.select { |name, _line|
|
|
624
|
+
@described.select { |name, _line| toolkit.reaches_group?(name) }
|
|
580
625
|
end
|
|
581
626
|
|
|
582
627
|
def own?(group) = RESERVED_GROUPS.include?(group.name)
|
|
583
628
|
|
|
584
629
|
# The registered chi tool chi/<name> names (a built-in or a plugin's,
|
|
585
630
|
# never the index's own), reachable or not.
|
|
586
|
-
def builtin_named(name)
|
|
631
|
+
def builtin_named(name, surface)
|
|
587
632
|
return nil unless name.start_with?("#{CHI_GROUP}/")
|
|
588
633
|
|
|
589
|
-
entry =
|
|
634
|
+
entry = surface.registered[name.delete_prefix("#{CHI_GROUP}/")]
|
|
590
635
|
entry unless entry.nil? || entry.source == SOURCE
|
|
591
636
|
end
|
|
592
637
|
|
|
593
638
|
# The index's own groups: chi, the tools (built-ins and plugin tools)
|
|
594
|
-
#
|
|
595
|
-
#
|
|
596
|
-
# it acts as itself.
|
|
639
|
+
# +surface+'s toolkit leaves out but reaches (ToolSurface#index_only;
|
|
640
|
+
# none: no group). Each tool's schema is its registered entry's (a
|
|
641
|
+
# config's execute description included), and it acts as itself.
|
|
597
642
|
# @return [Array<Group>]
|
|
598
|
-
def registry_groups
|
|
599
|
-
|
|
600
|
-
return [] if
|
|
643
|
+
def registry_groups(surface)
|
|
644
|
+
entries = surface.index_only
|
|
645
|
+
return [] if entries.empty?
|
|
601
646
|
|
|
602
|
-
tools =
|
|
603
|
-
entry = @registry[name]
|
|
647
|
+
tools = entries.map do |entry|
|
|
604
648
|
schema = ToolDeclarations.chat_schemas([entry.schema]).first
|
|
605
|
-
Tool.new(group: CHI_GROUP, name: name, acts_as: name, description: schema[:description].to_s,
|
|
649
|
+
Tool.new(group: CHI_GROUP, name: entry.name, acts_as: entry.name, description: schema[:description].to_s,
|
|
606
650
|
schema: JSON.parse(JSON.generate(schema[:parameters])), entry: entry)
|
|
607
651
|
end
|
|
608
652
|
[Group.new(name: CHI_GROUP, summary: CHI_SUMMARY, tools: tools)]
|
|
@@ -687,9 +731,9 @@ module Samagotchi
|
|
|
687
731
|
# miss is the error text: a failed group's error, else the closest
|
|
688
732
|
# tools.
|
|
689
733
|
# @return [Tool, String]
|
|
690
|
-
def lookup(name, waited: false)
|
|
734
|
+
def lookup(name, surface, waited: false)
|
|
691
735
|
name = name.strip
|
|
692
|
-
all = reached_groups
|
|
736
|
+
all = reached_groups(surface)
|
|
693
737
|
tool = all.lazy.filter_map do |group|
|
|
694
738
|
next unless name.start_with?("#{group.name}/")
|
|
695
739
|
|
|
@@ -703,17 +747,21 @@ module Samagotchi
|
|
|
703
747
|
return "Error: #{name} names #{same.map(&:full_name).join(" and ")}; call one as <group>/<tool>" if same.size > 1
|
|
704
748
|
|
|
705
749
|
# A chi tool the session declares (a built-in or a plugin's) is no
|
|
706
|
-
# starting server's tool.
|
|
707
|
-
|
|
708
|
-
|
|
750
|
+
# starting server's tool. A layer's tool (forget_outputs) isn't
|
|
751
|
+
# counted, as before the surface (the Registry was asked without
|
|
752
|
+
# the turn's layers).
|
|
753
|
+
declared = surface.declared_entry(name)
|
|
754
|
+
if declared && declared.source != SOURCE && declared.layer.nil?
|
|
755
|
+
return "Error: #{name} is declared; call it directly"
|
|
756
|
+
end
|
|
709
757
|
|
|
710
758
|
# A name with a group's <group>/ waits for that group only; any
|
|
711
759
|
# other name (an acts_as) for every one starting.
|
|
712
|
-
named = reached_groups.select { |group| name.start_with?("#{group.name}/") }
|
|
713
|
-
starting = (named.empty? ? reached_groups : named).select { |group| group.state == :starting }
|
|
760
|
+
named = reached_groups(surface).select { |group| name.start_with?("#{group.name}/") }
|
|
761
|
+
starting = (named.empty? ? reached_groups(surface) : named).select { |group| group.state == :starting }
|
|
714
762
|
unless waited || starting.empty?
|
|
715
763
|
starting.each { |group| source_of(group.name)&.wait(group.name) }
|
|
716
|
-
return lookup(name, waited: true)
|
|
764
|
+
return lookup(name, surface, waited: true)
|
|
717
765
|
end
|
|
718
766
|
|
|
719
767
|
failed = all.find { |group| group.state == :failed && name.start_with?("#{group.name}/") }
|
|
@@ -726,8 +774,8 @@ module Samagotchi
|
|
|
726
774
|
end
|
|
727
775
|
|
|
728
776
|
# #lookup's tool, without waiting for a start: nil on a miss.
|
|
729
|
-
def lookup_quietly(name)
|
|
730
|
-
tool = lookup(name, waited: true)
|
|
777
|
+
def lookup_quietly(name, surface)
|
|
778
|
+
tool = lookup(name, surface, waited: true)
|
|
731
779
|
tool.is_a?(Tool) ? tool : nil
|
|
732
780
|
end
|
|
733
781
|
|
|
@@ -750,8 +798,8 @@ module Samagotchi
|
|
|
750
798
|
# bare name with a source's prefix as it is (no label). Nil for
|
|
751
799
|
# anything else.
|
|
752
800
|
# @return [Array(String, String), Array(String, nil), nil]
|
|
753
|
-
def claimed(name)
|
|
754
|
-
all = all_groups
|
|
801
|
+
def claimed(name, surface)
|
|
802
|
+
all = all_groups(surface)
|
|
755
803
|
if (own = all.select { |g| own?(g) }.find { |g| g.tools.any? { |t| t.acts_as == name } })
|
|
756
804
|
return [name, "#{own.name}: #{name}"]
|
|
757
805
|
end
|