samagotchi 0.65.0 → 0.67.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 +107 -1
- data/README.md +2 -1
- data/bin/chi +51 -2
- data/docs/architecture.md +50 -28
- data/docs/cli.md +14 -2
- data/docs/configuration.md +101 -16
- data/docs/context.md +39 -2
- data/docs/desktop.md +90 -22
- data/docs/guardrails.md +8 -5
- data/docs/hooks.md +2 -2
- data/docs/internals/anthropic.md +156 -0
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/bundles.md +2 -0
- data/docs/internals/engine.md +75 -30
- data/docs/internals/gemma4-contract.md +1 -1
- data/docs/internals/llm-context-bench.md +5 -4
- data/docs/internals/prompt-caching.md +36 -13
- data/docs/memory.md +1 -1
- data/docs/plugins.md +289 -25
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +16 -6
- data/docs/sub-agent.md +6 -1
- data/docs/testing.md +17 -3
- data/lib/samagotchi/bootstrap/probe.rb +4 -3
- data/lib/samagotchi/bootstrap_command.rb +13 -3
- data/lib/samagotchi/bridge/ring_buffer.rb +0 -5
- data/lib/samagotchi/bridge.rb +6 -3
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +7 -6
- data/lib/samagotchi/bundles/claude-peer/plugin.rb +40 -302
- data/lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md +8 -3
- data/lib/samagotchi/bundles/claude-peer/skill_claude_peer.md +14 -6
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +22 -0
- data/lib/samagotchi/bundles/codex-peer/plugin.rb +189 -0
- data/lib/samagotchi/bundles/codex-peer/scripts/chi-peer-SKILL.md +136 -0
- data/lib/samagotchi/bundles/codex-peer/scripts/pair +92 -0
- data/lib/samagotchi/bundles/codex-peer/skill_codex_peer.md +76 -0
- data/lib/samagotchi/bundles/coordinator/manifest.yml +5 -5
- data/lib/samagotchi/bundles/coordinator/plugin.rb +24 -2
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -1
- data/lib/samagotchi/bundles/mcp/manifest.yml +5 -5
- data/lib/samagotchi/bundles/mcp/plugin.rb +82 -294
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +7 -3
- data/lib/samagotchi/bundles/system/delegated.md +2 -1
- data/lib/samagotchi/bundles/system/manifest.yml +5 -5
- data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/cache_confirm.rb +9 -6
- data/lib/samagotchi/commands/registry.rb +25 -8
- data/lib/samagotchi/config.rb +23 -8
- data/lib/samagotchi/context_absorber.rb +66 -13
- data/lib/samagotchi/desktop/macos/AgentTerminal.swift +260 -0
- data/lib/samagotchi/desktop/macos/App.swift +3 -3
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +5 -2
- data/lib/samagotchi/desktop/macos/Images.swift +2 -2
- data/lib/samagotchi/desktop/macos/Kitty.swift +33 -197
- data/lib/samagotchi/desktop/macos/Panel.swift +118 -82
- data/lib/samagotchi/desktop/macos/ProcessRunner.swift +1 -1
- data/lib/samagotchi/desktop/macos/Tmux.swift +239 -0
- data/lib/samagotchi/desktop/macos.rb +84 -13
- data/lib/samagotchi/desktop_command.rb +14 -3
- data/lib/samagotchi/engine.rb +120 -107
- data/lib/samagotchi/engine_peers.rb +3 -0
- data/lib/samagotchi/guardrail_wiring.rb +1 -1
- data/lib/samagotchi/guardrails/approval.rb +1 -1
- data/lib/samagotchi/guardrails/approvals.rb +1 -1
- data/lib/samagotchi/guardrails/rules.rb +1 -1
- data/lib/samagotchi/guardrails/targets.rb +1 -1
- data/lib/samagotchi/hook_routes.rb +151 -0
- data/lib/samagotchi/hooks/registry.rb +2 -2
- data/lib/samagotchi/host_keys.rb +147 -0
- data/lib/samagotchi/kernel_loop.rb +9 -1
- data/lib/samagotchi/llm/adapter_common.rb +138 -0
- data/lib/samagotchi/llm/anthropic_assembly.rb +4 -2
- data/lib/samagotchi/llm/anthropic_messages.rb +28 -105
- data/lib/samagotchi/llm/api_key.rb +23 -7
- data/lib/samagotchi/llm/chat_loop.rb +10 -6
- data/lib/samagotchi/llm/openai_chat.rb +9 -92
- data/lib/samagotchi/llm/refusal_memo.rb +29 -0
- data/lib/samagotchi/llm_context_override.rb +1 -0
- data/lib/samagotchi/peer_sources.rb +58 -0
- data/lib/samagotchi/plugin/api.rb +138 -156
- data/lib/samagotchi/plugin/context.rb +22 -1
- data/lib/samagotchi/plugin/loader.rb +5 -2
- data/lib/samagotchi/plugin/peer.rb +394 -0
- data/lib/samagotchi/plugin/tool_spec.rb +166 -0
- data/lib/samagotchi/plugin_tasks.rb +2 -2
- data/lib/samagotchi/row_refs.rb +1 -1
- data/lib/samagotchi/self_report.rb +5 -3
- data/lib/samagotchi/send_command.rb +41 -15
- data/lib/samagotchi/session.rb +18 -1
- data/lib/samagotchi/session_commands.rb +16 -1
- data/lib/samagotchi/session_continue.rb +58 -15
- data/lib/samagotchi/session_inbox.rb +0 -13
- data/lib/samagotchi/session_manager.rb +4 -1
- data/lib/samagotchi/session_setup.rb +14 -7
- data/lib/samagotchi/system_prompt.rb +16 -1
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +19 -9
- data/lib/samagotchi/terminal_ui.rb +18 -2
- data/lib/samagotchi/tool_call_parser.rb +1 -1
- data/lib/samagotchi/tool_runner.rb +13 -1
- data/lib/samagotchi/toolkit.rb +246 -0
- data/lib/samagotchi/toolkit_binding.rb +138 -0
- data/lib/samagotchi/toolkit_command.rb +117 -0
- data/lib/samagotchi/tools/context_read.rb +14 -0
- data/lib/samagotchi/tools/execute.rb +12 -12
- data/lib/samagotchi/tools/registry.rb +43 -6
- data/lib/samagotchi/tools/task_create.rb +6 -6
- data/lib/samagotchi/tools/task_get.rb +13 -13
- data/lib/samagotchi/tools/task_list.rb +6 -6
- data/lib/samagotchi/tools/task_record.rb +52 -0
- data/lib/samagotchi/tools/task_runtime.rb +55 -91
- data/lib/samagotchi/tools/task_stop.rb +6 -6
- data/lib/samagotchi/tools/task_wait.rb +12 -13
- data/lib/samagotchi/tools/tool_index.rb +454 -0
- data/lib/samagotchi/update_command.rb +9 -6
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +28 -9
- data/lib/samagotchi/web/capabilities.rb +1 -1
- data/lib/samagotchi/web/public/activity.js +2 -2
- data/lib/samagotchi/web/public/annotate_actions.js +109 -0
- data/lib/samagotchi/web/public/annotate_bar.js +84 -2
- data/lib/samagotchi/web/public/app.js +52 -43
- data/lib/samagotchi/web/public/chains.js +1 -1
- data/lib/samagotchi/web/public/commands.js +6 -4
- data/lib/samagotchi/web/public/composer_size.js +2 -2
- data/lib/samagotchi/web/public/copy.js +1 -1
- data/lib/samagotchi/web/public/ctx.js +1 -1
- data/lib/samagotchi/web/public/delivery_line.js +4 -4
- data/lib/samagotchi/web/public/families_view.js +1 -1
- data/lib/samagotchi/web/public/format.js +11 -7
- data/lib/samagotchi/web/public/images.js +25 -2
- data/lib/samagotchi/web/public/index.html +9 -0
- data/lib/samagotchi/web/public/info_bar.js +5 -3
- data/lib/samagotchi/web/public/llm_context_chip.js +2 -2
- data/lib/samagotchi/web/public/notify.js +6 -4
- data/lib/samagotchi/web/public/question_card.js +1 -1
- data/lib/samagotchi/web/public/ref_scan.js +1 -1
- data/lib/samagotchi/web/public/session_state.js +17 -0
- data/lib/samagotchi/web/public/session_title.js +2 -3
- data/lib/samagotchi/web/public/sessions_list.js +0 -8
- data/lib/samagotchi/web/public/stage_model.js +2 -2
- data/lib/samagotchi/web/public/stream_handlers.js +13 -0
- data/lib/samagotchi/web/public/strip.js +3 -3
- data/lib/samagotchi/web/public/task_stop.js +3 -2
- data/lib/samagotchi/web/public/toolkit_chip.js +29 -0
- data/lib/samagotchi/web/public/turn_events.js +1 -1
- data/lib/samagotchi/web/public/turn_html.js +2 -2
- data/lib/samagotchi/web/public/turn_model.js +2 -2
- data/lib/samagotchi/web/public/turn_view.js +1 -1
- data/lib/samagotchi/web/server.rb +0 -4
- data/lib/samagotchi/worker.rb +13 -8
- data/lib/samagotchi/worker_inbound.rb +36 -4
- data/lib/samagotchi/worker_wakes.rb +67 -7
- metadata +23 -1
|
@@ -39,8 +39,8 @@ module Samagotchi
|
|
|
39
39
|
|
|
40
40
|
USAGE = <<~TEXT
|
|
41
41
|
Usage: chi send [-m TEXT] [--image PATH]... [--cut | --queue] (ID|PREFIX)...
|
|
42
|
-
chi send --new [--dir DIR] [--model M] [--llm-context LAYERS] [--thinking LEVEL] [-m TEXT] [--image PATH]...
|
|
43
|
-
chi send --new --continues (ID|PREFIX|last:ID) [-m TEXT] [--image PATH]...
|
|
42
|
+
chi send --new [--dir DIR] [--model M] [--llm-context LAYERS] [--thinking LEVEL] [--toolkit NAME] [-m TEXT] [--image PATH]...
|
|
43
|
+
chi send --new --continues (ID|PREFIX|last:ID) [--model M] [-m TEXT] [--image PATH]...
|
|
44
44
|
chi send --wait [--timeout S] [--format json] [-m TEXT] [--image PATH]... (--new | ID)
|
|
45
45
|
chi send --queue --wait [--timeout S] [--format json] [-m TEXT] ID
|
|
46
46
|
chi send --wait [--timeout S] [--format json] ID
|
|
@@ -63,9 +63,11 @@ module Samagotchi
|
|
|
63
63
|
the current one
|
|
64
64
|
--continues ID
|
|
65
65
|
(--new) start the next link of that session's chain:
|
|
66
|
-
in its folder, on its model
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
in its folder, on its model (or --model's; one whose
|
|
67
|
+
host is gone falls back to the default, with a
|
|
68
|
+
warning), LLM context, thinking level and toolkit,
|
|
69
|
+
with a note carrying its recap; it is archived.
|
|
70
|
+
last:ID names the chain's latest link.
|
|
69
71
|
Without -m the new session waits idle. Refused for a
|
|
70
72
|
session continued already (naming the next link) or
|
|
71
73
|
with delegates still open
|
|
@@ -77,6 +79,11 @@ module Samagotchi
|
|
|
77
79
|
(--new) its own thinking level (off, low, medium,
|
|
78
80
|
high; default: none of its own), above a process
|
|
79
81
|
default such as SAMAGOTCHI_THINKING_LEVEL
|
|
82
|
+
--toolkit NAME, --tool NAME, --no-tool NAME
|
|
83
|
+
(--new) its toolkit, the tools its prompts declare
|
|
84
|
+
(full, lean, child, a config or bundle one; default
|
|
85
|
+
config's toolkit.default), and tools declared on top
|
|
86
|
+
of it or left out (repeatable, a comma list too)
|
|
80
87
|
--wait wait for the answer and print it (one session); the
|
|
81
88
|
other lines go to stderr. Exit 3: it waits for an
|
|
82
89
|
answer from you: the question, its options and the
|
|
@@ -126,6 +133,9 @@ module Samagotchi
|
|
|
126
133
|
f.value "--llm-context-apply", key: :llm_apply
|
|
127
134
|
f.value "--llm-context-budget", key: :llm_budget
|
|
128
135
|
f.value "--thinking"
|
|
136
|
+
f.value "--toolkit"
|
|
137
|
+
f.value "--tool", key: :tools, repeat: true
|
|
138
|
+
f.value "--no-tool", key: :no_tools, repeat: true
|
|
129
139
|
f.value "--image", key: :images, repeat: true
|
|
130
140
|
f.switch "--wait"
|
|
131
141
|
f.switch "--cut"
|
|
@@ -262,7 +272,8 @@ module Samagotchi
|
|
|
262
272
|
return usage_error("at most #{MAX_IMAGES} images") if options[:images].size > MAX_IMAGES
|
|
263
273
|
return new_options(options) if options[:new]
|
|
264
274
|
|
|
265
|
-
%i[dir model continues thinking].each { |key| return usage_error("--#{key} needs --new") if options[key] }
|
|
275
|
+
%i[dir model continues thinking toolkit].each { |key| return usage_error("--#{key} needs --new") if options[key] }
|
|
276
|
+
TOOLKIT_FLAGS.each { |key, flag| return usage_error("#{flag} needs --new") unless Array(options[key]).empty? }
|
|
266
277
|
LLM_CONTEXT_FLAGS.each { |key, flag| return usage_error("#{flag} needs --new") if options[key] }
|
|
267
278
|
return usage_error("give session ids") if options[:ids].empty?
|
|
268
279
|
|
|
@@ -273,15 +284,19 @@ module Samagotchi
|
|
|
273
284
|
LLM_CONTEXT_FLAGS = { llm_strategy: "--llm-context", llm_apply: "--llm-context-apply",
|
|
274
285
|
llm_budget: "--llm-context-budget" }.freeze
|
|
275
286
|
LLM_CONTEXT_FIELDS = { llm_strategy: :strategy, llm_apply: :apply, llm_budget: :budget_tokens }.freeze
|
|
287
|
+
# --tool / --no-tool's option keys.
|
|
288
|
+
TOOLKIT_FLAGS = { tools: "--tool", no_tools: "--no-tool" }.freeze
|
|
276
289
|
|
|
277
290
|
def new_options(options)
|
|
278
291
|
return usage_error("--new takes no session ids: it starts one session") unless options[:ids].empty?
|
|
279
292
|
|
|
280
293
|
if options[:continues]
|
|
281
|
-
taken = [("--dir" if options[:dir])
|
|
282
|
-
LLM_CONTEXT_FLAGS.filter_map { |key, flag| flag if options[key] } + [("--thinking" if options[:thinking])]
|
|
294
|
+
taken = [("--dir" if options[:dir])] +
|
|
295
|
+
LLM_CONTEXT_FLAGS.filter_map { |key, flag| flag if options[key] } + [("--thinking" if options[:thinking])] +
|
|
296
|
+
[("--toolkit" if options[:toolkit])] + TOOLKIT_FLAGS.filter_map { |key, flag| flag unless Array(options[key]).empty? }
|
|
283
297
|
unless taken.compact.empty?
|
|
284
|
-
return usage_error("--continues takes the previous session's folder,
|
|
298
|
+
return usage_error("--continues takes the previous session's folder, LLM context, thinking level and " \
|
|
299
|
+
"toolkit; " \
|
|
285
300
|
"leave out #{taken.compact.join(", ")}")
|
|
286
301
|
end
|
|
287
302
|
end
|
|
@@ -301,6 +316,8 @@ module Samagotchi
|
|
|
301
316
|
|
|
302
317
|
options[:thinking] = Thinking.session_level(word)
|
|
303
318
|
end
|
|
319
|
+
toolkit = Toolkit.from_flags(name: options[:toolkit], add: options[:tools], remove: options[:no_tools])
|
|
320
|
+
options[:toolkit_value] = toolkit if toolkit
|
|
304
321
|
|
|
305
322
|
if options[:dir]
|
|
306
323
|
options[:dir] = File.expand_path(options[:dir])
|
|
@@ -343,12 +360,14 @@ module Samagotchi
|
|
|
343
360
|
begin
|
|
344
361
|
start = through_worker ? { prompt: nil, title: prompt } : { prompt: prompt }
|
|
345
362
|
session = if options[:continues]
|
|
346
|
-
SessionManager.continue_session(options[:continues], **start,
|
|
363
|
+
SessionManager.continue_session(options[:continues], **start, model: options[:model],
|
|
364
|
+
state_dir: @state_dir)
|
|
347
365
|
else
|
|
348
366
|
SessionManager.spawn_session(**start, working_directory: options[:dir],
|
|
349
367
|
model_name: options[:model],
|
|
350
368
|
setup: SessionSetup.new(llm_context: options[:llm_context],
|
|
351
|
-
thinking: options[:thinking]
|
|
369
|
+
thinking: options[:thinking],
|
|
370
|
+
toolkit: options[:toolkit_value]),
|
|
352
371
|
state_dir: @state_dir)
|
|
353
372
|
end
|
|
354
373
|
rescue SessionContinue::Refused, SessionManager::ArchiveRefused, SessionManager::OwnedByTUI => e
|
|
@@ -372,15 +391,22 @@ module Samagotchi
|
|
|
372
391
|
timeout: options[:timeout])
|
|
373
392
|
end
|
|
374
393
|
|
|
375
|
-
#
|
|
376
|
-
#
|
|
394
|
+
# The model the new session will run, for the images check: --model
|
|
395
|
+
# (nil: the default), or with --continues the one the new link will run
|
|
396
|
+
# (SessionContinue.model_for: --model's, else the previous link's, else
|
|
397
|
+
# the default it falls back to).
|
|
377
398
|
# @return [Array, false] [model_name, model_typed], or false after the
|
|
378
|
-
# error line (no such session)
|
|
399
|
+
# error line (no such session, a model it can't start on)
|
|
379
400
|
def continued_model(options)
|
|
380
401
|
return [options[:model], nil] unless options[:continues]
|
|
381
402
|
|
|
382
403
|
previous = Session.load(SessionChain.resolve(options[:continues], state_dir: @state_dir), state_dir: @state_dir)
|
|
383
|
-
|
|
404
|
+
choice = SessionContinue.model_for(previous, options[:model])
|
|
405
|
+
inherited = options[:model].to_s.strip.empty? && choice.warning.nil?
|
|
406
|
+
inherited ? [previous.model_name, previous.model_typed] : [choice.model, nil]
|
|
407
|
+
rescue ModelProfile::MissingModel => e
|
|
408
|
+
error_line("chi send: could not start a session: #{e.message}")
|
|
409
|
+
false
|
|
384
410
|
rescue ArgumentError => e
|
|
385
411
|
error_line("chi send: #{e.message}")
|
|
386
412
|
false
|
data/lib/samagotchi/session.rb
CHANGED
|
@@ -184,6 +184,19 @@ module Samagotchi
|
|
|
184
184
|
# couldn't read; nil when there is neither.
|
|
185
185
|
def reasoning_history_file = @reasoning_history&.to_s || @reasoning_history_unread
|
|
186
186
|
|
|
187
|
+
# The session's toolkit (Toolkit: the tools its prompts declare), as
|
|
188
|
+
# given when it was created; nil for a session from before toolkits,
|
|
189
|
+
# which is on Toolkit::FULL (Toolkit.of). Saved and never resolved
|
|
190
|
+
# again from config, so a respawned worker declares the same tools; a
|
|
191
|
+
# continue and a fork copy it (SessionSetup.of), a delegate child gets
|
|
192
|
+
# toolkit.child (SessionManager.spawn_session).
|
|
193
|
+
attr_reader :toolkit
|
|
194
|
+
|
|
195
|
+
# @param value [Toolkit, Hash, String, nil] the session file's form too
|
|
196
|
+
def toolkit=(value)
|
|
197
|
+
@toolkit = value.is_a?(Toolkit) ? value : Toolkit.from_file(value)
|
|
198
|
+
end
|
|
199
|
+
|
|
187
200
|
# The hook that stopped the last turn (stop_turn, or a cut with no retry
|
|
188
201
|
# left), e.g. "loop-guard"; nil when it ended otherwise.
|
|
189
202
|
def stopped_by
|
|
@@ -213,7 +226,7 @@ module Samagotchi
|
|
|
213
226
|
preloaded_memory_names: [], muted_memory_names: [], parent_id: nil, scratch: false,
|
|
214
227
|
last_turn: nil, model_typed: nil, delegate: false, llm_context: nil, prompt_notes: [],
|
|
215
228
|
continues: nil, thinking: nil, reasoning_history: nil, appended_prompt_files: [],
|
|
216
|
-
interrupted_turn: nil)
|
|
229
|
+
toolkit: nil, interrupted_turn: nil)
|
|
217
230
|
@id = id
|
|
218
231
|
@metadata_version = metadata_version
|
|
219
232
|
@mode = mode
|
|
@@ -242,6 +255,7 @@ module Samagotchi
|
|
|
242
255
|
self.thinking = thinking
|
|
243
256
|
self.reasoning_history = reasoning_history
|
|
244
257
|
self.appended_prompt_files = appended_prompt_files
|
|
258
|
+
self.toolkit = toolkit
|
|
245
259
|
self.interrupted_turn = interrupted_turn
|
|
246
260
|
@turn_checkpoint = nil
|
|
247
261
|
@archived = false
|
|
@@ -336,6 +350,7 @@ module Samagotchi
|
|
|
336
350
|
thinking: setup.thinking,
|
|
337
351
|
reasoning_history: setup.reasoning_history,
|
|
338
352
|
appended_prompt_files: setup.appended_prompt_files,
|
|
353
|
+
toolkit: setup.toolkit,
|
|
339
354
|
continues: continues
|
|
340
355
|
)
|
|
341
356
|
end
|
|
@@ -400,6 +415,7 @@ module Samagotchi
|
|
|
400
415
|
"thinking" => nil,
|
|
401
416
|
"reasoning_history" => nil,
|
|
402
417
|
"appended_prompt_files" => [],
|
|
418
|
+
"toolkit" => nil,
|
|
403
419
|
"interrupted_turn" => nil
|
|
404
420
|
}.freeze
|
|
405
421
|
|
|
@@ -527,6 +543,7 @@ module Samagotchi
|
|
|
527
543
|
when "llm_context" then llm_context_file
|
|
528
544
|
when "thinking" then thinking_file
|
|
529
545
|
when "reasoning_history" then reasoning_history_file
|
|
546
|
+
when "toolkit" then @toolkit&.to_file
|
|
530
547
|
when "prompt_notes" then @prompt_notes.map(&:to_file)
|
|
531
548
|
when "last_turn" then @last_turn&.to_file
|
|
532
549
|
when "used_memory_names", "preloaded_memory_names", "muted_memory_names", "appended_prompt_files"
|
|
@@ -12,6 +12,7 @@ require_relative "tools/peers"
|
|
|
12
12
|
require_relative "commands/registry"
|
|
13
13
|
require_relative "llm_context_command"
|
|
14
14
|
require_relative "thinking_command"
|
|
15
|
+
require_relative "toolkit_command"
|
|
15
16
|
require_relative "reasoning_history_command"
|
|
16
17
|
|
|
17
18
|
module Samagotchi
|
|
@@ -32,6 +33,7 @@ module Samagotchi
|
|
|
32
33
|
HELP_COMMAND = "/help"
|
|
33
34
|
LLM_CONTEXT_COMMAND = LLMContextCommand::NAME
|
|
34
35
|
THINKING_COMMAND = ThinkingCommand::NAME
|
|
36
|
+
TOOLKIT_COMMAND = ToolkitCommand::NAME
|
|
35
37
|
REASONING_HISTORY_COMMAND = ReasoningHistoryCommand::NAME
|
|
36
38
|
# A remote catalog has hundreds of ids (OpenRouter ~380): plain /models
|
|
37
39
|
# shows this many per host; /models <text> lists every match.
|
|
@@ -48,7 +50,7 @@ module Samagotchi
|
|
|
48
50
|
|
|
49
51
|
# The commands that ask before a change that restarts the prompt cache
|
|
50
52
|
# (CacheConfirm): #run passes them its confirm flag.
|
|
51
|
-
CONFIRMING = %i[thinking reasoning_history].freeze
|
|
53
|
+
CONFIRMING = %i[thinking reasoning_history toolkit].freeze
|
|
52
54
|
|
|
53
55
|
# @!attribute status [Symbol] :ok, or :error when the line was refused
|
|
54
56
|
# @!attribute output [String, nil] what to tell the user
|
|
@@ -103,6 +105,9 @@ module Samagotchi
|
|
|
103
105
|
registry.register(REASONING_HISTORY_COMMAND, "show or set which of its reasoning this session's model gets back (drop|turn|all|default)",
|
|
104
106
|
id: :reasoning_history, mid_turn: show_mid_turn(REASONING_HISTORY_COMMAND),
|
|
105
107
|
match: ->(text) { text.match?(%r{\A/reasoning-history(?:\s+.*)?\z}) }) { |text| reasoning_history(text) }
|
|
108
|
+
registry.register(TOOLKIT_COMMAND, "show or change the tools this session declares (NAME, add TOOL, remove TOOL)",
|
|
109
|
+
id: :toolkit, mid_turn: show_mid_turn(TOOLKIT_COMMAND),
|
|
110
|
+
match: ->(text) { text.match?(%r{\A/toolkit(?:\s+.*)?\z}) }) { |text| toolkit(text) }
|
|
106
111
|
registry.register(HELP_COMMAND, "list the commands, the bundles' too", anytime: true) { |_text| reply(help_listing) }
|
|
107
112
|
registry.register("/stats", "show the session's stats", local: true)
|
|
108
113
|
registry.register("/recap", "show the session's recap", local: true)
|
|
@@ -467,6 +472,16 @@ module Samagotchi
|
|
|
467
472
|
reply(e.message, status: :error)
|
|
468
473
|
end
|
|
469
474
|
|
|
475
|
+
# /toolkit (ToolkitCommand); :toolkit in changed when it set the
|
|
476
|
+
# session's toolkit.
|
|
477
|
+
def toolkit(text, confirm: true)
|
|
478
|
+
output, changed, ask = ToolkitCommand.new(engine: @engine, save: ->(session) { save_setup(session) })
|
|
479
|
+
.run(text.delete_prefix(TOOLKIT_COMMAND), confirm: confirm)
|
|
480
|
+
reply(output, changed: changed ? [:toolkit] : [], confirm: ask)
|
|
481
|
+
rescue ArgumentError => e
|
|
482
|
+
reply(e.message, status: :error)
|
|
483
|
+
end
|
|
484
|
+
|
|
470
485
|
# A session setting changed (/llm-context, /thinking, /reasoning-history):
|
|
471
486
|
# saved with its model.
|
|
472
487
|
def save_setup(session)
|
|
@@ -56,11 +56,21 @@ module Samagotchi
|
|
|
56
56
|
# moved the chain's end while it waited for the lock.
|
|
57
57
|
TRIES = 3
|
|
58
58
|
|
|
59
|
+
# The model a new link runs on (#model_for): the name to spawn it with,
|
|
60
|
+
# and why it isn't the previous link's (nil when it is, or was given).
|
|
61
|
+
ModelChoice = Data.define(:model, :warning)
|
|
62
|
+
# What #check! decided before anything changes: the delegates to move
|
|
63
|
+
# (ChildMove::Plan) and the new link's model (ModelChoice).
|
|
64
|
+
Checked = Data.define(:plan, :choice)
|
|
65
|
+
|
|
59
66
|
# Start the next link of a chain: a new session that continues
|
|
60
67
|
# +id_or_ref+ (Session#continues) in its folder, on its model (the name
|
|
61
68
|
# it was typed as, so an alias holds) and with its own setup (SessionSetup),
|
|
62
69
|
# starting with a context note from chi: the link and the previous
|
|
63
|
-
# link's recap (SessionChain.note_text).
|
|
70
|
+
# link's recap (SessionChain.note_text). +model+, when given, is the
|
|
71
|
+
# new link's model instead; an inherited one whose host can't run it any
|
|
72
|
+
# more (gone, disabled, an alias naming another host) falls back to the
|
|
73
|
+
# default with a warning (#model_for). The previous link is archived
|
|
64
74
|
# first, with its finished delegates (archive_session's rules: a turn
|
|
65
75
|
# running, a prompt queued or the step-limit question refuse; an idle
|
|
66
76
|
# worker is stopped), so a refusal starts nothing. Its open delegates
|
|
@@ -81,7 +91,7 @@ module Samagotchi
|
|
|
81
91
|
# @raise [Refused] continued already, delegates open that can't
|
|
82
92
|
# move, or its folder is gone
|
|
83
93
|
# @raise [ArchiveRefused, OwnedByTUI] as archive_session
|
|
84
|
-
def self.run(id_or_ref, prompt: nil, title: nil, state_dir: nil, recap_wait: RECAP_WAIT, archive_wait: 5)
|
|
94
|
+
def self.run(id_or_ref, prompt: nil, title: nil, model: nil, state_dir: nil, recap_wait: RECAP_WAIT, archive_wait: 5)
|
|
85
95
|
sd = state_dir || Session.default_state_dir
|
|
86
96
|
TRIES.times do |try|
|
|
87
97
|
id = SessionChain.resolve(id_or_ref, state_dir: sd)
|
|
@@ -89,7 +99,7 @@ module Samagotchi
|
|
|
89
99
|
follow = id_or_ref.to_s.strip.start_with?(SessionChain::LAST_PREFIX) && try < TRIES - 1
|
|
90
100
|
next :moved if follow && SessionChain.next_of(id, state_dir: sd)
|
|
91
101
|
|
|
92
|
-
run_locked(id, prompt: prompt, title: title, state_dir: sd, recap_wait: recap_wait,
|
|
102
|
+
run_locked(id, prompt: prompt, title: title, model: model, state_dir: sd, recap_wait: recap_wait,
|
|
93
103
|
archive_wait: archive_wait)
|
|
94
104
|
end
|
|
95
105
|
return result unless result == :moved
|
|
@@ -105,9 +115,10 @@ module Samagotchi
|
|
|
105
115
|
end
|
|
106
116
|
end
|
|
107
117
|
|
|
108
|
-
private_class_method def self.run_locked(id, prompt:, title:, state_dir:, recap_wait:, archive_wait:)
|
|
118
|
+
private_class_method def self.run_locked(id, prompt:, title:, model:, state_dir:, recap_wait:, archive_wait:)
|
|
109
119
|
previous = Session.load(id, state_dir: state_dir)
|
|
110
|
-
|
|
120
|
+
checked = check!(previous, state_dir, model: model)
|
|
121
|
+
plan = checked.plan
|
|
111
122
|
# archive_session's refusals that can be known now, before the wait.
|
|
112
123
|
SessionManager.archivable!(id, state_dir, except: plan.move)
|
|
113
124
|
recap_before_archive(id, state_dir, wait: recap_wait)
|
|
@@ -120,7 +131,8 @@ module Samagotchi
|
|
|
120
131
|
archive = SessionManager.archive_session(id, state_dir: state_dir, wait: archive_wait, except: plan.move)
|
|
121
132
|
archived = archive[:archived] - already
|
|
122
133
|
begin
|
|
123
|
-
spawn_continuation(previous, prompt: prompt, title: title,
|
|
134
|
+
spawn_continuation(previous, prompt: prompt, title: title, choice: checked.choice, state_dir: state_dir,
|
|
135
|
+
move: plan.move)
|
|
124
136
|
rescue StandardError
|
|
125
137
|
# Nothing started: the delegates go back, what this archived goes
|
|
126
138
|
# back to the lists, and a new link saved before its worker failed
|
|
@@ -181,7 +193,7 @@ module Samagotchi
|
|
|
181
193
|
|
|
182
194
|
# The new link, holding off any worker but its own (ChildMove.starting?)
|
|
183
195
|
# while +move+ goes to it.
|
|
184
|
-
private_class_method def self.spawn_continuation(previous, prompt:, title:, state_dir:, move:)
|
|
196
|
+
private_class_method def self.spawn_continuation(previous, prompt:, title:, choice:, state_dir:, move:)
|
|
185
197
|
recap = RecapStore.read(Session.session_dir(previous.id, state_dir: state_dir))
|
|
186
198
|
start = prompt.to_s.strip.empty? ? nil : prompt
|
|
187
199
|
title = previous.first_preview if title.to_s.strip.empty? && start.nil?
|
|
@@ -191,33 +203,64 @@ module Samagotchi
|
|
|
191
203
|
end
|
|
192
204
|
link = SessionManager.spawn_session(
|
|
193
205
|
prompt: start, title: title, working_directory: previous.working_directory,
|
|
194
|
-
model_name:
|
|
206
|
+
model_name: choice.model, setup: SessionSetup.of(previous),
|
|
195
207
|
continues: previous.id, note: SessionChain.note_text(previous, recap: recap),
|
|
196
208
|
note_source: SessionChain::NOTE_SOURCE, state_dir: state_dir, before_spawn: before_spawn
|
|
197
209
|
)
|
|
198
210
|
link.moved_children = move
|
|
211
|
+
# The fallback's warning first: spawn_session set its own.
|
|
212
|
+
warnings = [choice.warning, link.model_warning].compact
|
|
213
|
+
link.model_warning = warnings.join("; ") unless warnings.empty?
|
|
199
214
|
link
|
|
200
215
|
end
|
|
201
216
|
|
|
217
|
+
# The model a new link of +previous+'s chain runs on: +given+ when
|
|
218
|
+
# there is one (its host checked: ModelProfile::UnknownHost as for a new
|
|
219
|
+
# session), else the previous link's, as typed (so an alias holds). An
|
|
220
|
+
# inherited one whose host can't run it any more (gone from config.yml,
|
|
221
|
+
# disabled, an alias naming another host: UnknownHost) falls back to the
|
|
222
|
+
# default, with a warning naming why. `chi send --new --continues`
|
|
223
|
+
# judges images by it before #run decides the same.
|
|
224
|
+
# @param previous [Session]
|
|
225
|
+
# @param given [String, nil] blank: inherit
|
|
226
|
+
# @return [ModelChoice]
|
|
227
|
+
# @raise [ModelProfile::MissingModel] a +given+ model it can't run, or
|
|
228
|
+
# a fallback with no default configured (or the default's host can't run it)
|
|
229
|
+
def self.model_for(previous, given)
|
|
230
|
+
unless given.to_s.strip.empty?
|
|
231
|
+
return ModelChoice.new(model: ModelProfile.check_host!(ModelProfile.required_model_name(given.strip)),
|
|
232
|
+
warning: nil)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
inherited = ModelProfile.required_model_name(previous.model_typed || previous.model_name)
|
|
236
|
+
begin
|
|
237
|
+
ModelChoice.new(model: ModelProfile.check_host!(inherited), warning: nil)
|
|
238
|
+
rescue ModelProfile::UnknownHost => e
|
|
239
|
+
default = ModelProfile.check_host!(ModelProfile.required_model_name(nil))
|
|
240
|
+
ModelChoice.new(model: default, warning: "#{inherited} can't run (#{e.message}); started on #{default}")
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
202
244
|
# The refusals that come before anything changes: continued already (no
|
|
203
245
|
# forks in a chain), an open delegate that can't move to the new link
|
|
204
246
|
# (ChildMove.plan), the folder gone (the worker would run elsewhere), a
|
|
205
|
-
# model whose host config.yml
|
|
206
|
-
#
|
|
247
|
+
# given model whose host config.yml doesn't have, or an inherited one
|
|
248
|
+
# that can't run with no default to fall back to (#model_for).
|
|
249
|
+
# @return [Checked]
|
|
207
250
|
# @raise [Refused, ModelProfile::MissingModel]
|
|
208
|
-
private_class_method def self.check!(previous, state_dir)
|
|
251
|
+
private_class_method def self.check!(previous, state_dir, model: nil)
|
|
209
252
|
if (following = SessionChain.next_of(previous.id, state_dir: state_dir))
|
|
210
253
|
raise Refused.new(previous.id, :continued, ids: [following])
|
|
211
254
|
end
|
|
212
255
|
|
|
213
256
|
plan = move_plan!(previous.id, state_dir)
|
|
214
|
-
#
|
|
215
|
-
# the archive stops the previous link's worker.
|
|
216
|
-
|
|
257
|
+
# The new link's model, its host checked as SessionManager.spawn_session
|
|
258
|
+
# checks it: before the archive stops the previous link's worker.
|
|
259
|
+
choice = model_for(previous, model)
|
|
217
260
|
dir = previous.working_directory.to_s
|
|
218
261
|
raise Refused.new(previous.id, :folder_gone, detail: dir) unless File.directory?(dir)
|
|
219
262
|
|
|
220
|
-
plan
|
|
263
|
+
Checked.new(plan: plan, choice: choice)
|
|
221
264
|
end
|
|
222
265
|
|
|
223
266
|
# A child still open below a session (#open_children); why: running,
|
|
@@ -236,15 +236,6 @@ module Samagotchi
|
|
|
236
236
|
UNREADABLE_INPUT
|
|
237
237
|
end
|
|
238
238
|
|
|
239
|
-
# Whether an unclaimed input file carries images (a mid-turn drain
|
|
240
|
-
# leaves it for its own turn).
|
|
241
|
-
def self.input_has_images?(input_file)
|
|
242
|
-
data = JSON.parse(File.read(input_file))
|
|
243
|
-
data.is_a?(Hash) && Array(data["images"]).any?
|
|
244
|
-
rescue JSON::ParserError, SystemCallError
|
|
245
|
-
false
|
|
246
|
-
end
|
|
247
|
-
|
|
248
239
|
# Whether a mid-turn drain leaves this unclaimed input file for a turn
|
|
249
240
|
# of its own instead of merging it at a step boundary: a queued message
|
|
250
241
|
# (`delivery: queue`) or an image message (steering merges text only).
|
|
@@ -258,10 +249,6 @@ module Samagotchi
|
|
|
258
249
|
false
|
|
259
250
|
end
|
|
260
251
|
|
|
261
|
-
# Whether an input file carrying +enqueued_id+ still waits unclaimed in
|
|
262
|
-
# input/ (no worker has taken it for a turn or a step yet).
|
|
263
|
-
def self.input_waiting?(session_dir, enqueued_id) = !waiting_input(session_dir, enqueued_id).nil?
|
|
264
|
-
|
|
265
252
|
# How the unclaimed input file carrying +enqueued_id+ waits: :turn_end
|
|
266
253
|
# (a queue or an image message: it runs as a turn of its own, never
|
|
267
254
|
# merged, #waits_for_turn_end?), :merges (a running turn's drain may
|
|
@@ -140,7 +140,9 @@ module Samagotchi
|
|
|
140
140
|
# @param title [String, nil] what the lists show before the first turn
|
|
141
141
|
# (the prompt's preview by default, else the seed's first user message)
|
|
142
142
|
# @param setup [SessionSetup] the session's own settings (chi
|
|
143
|
-
# --llm-context; a fork and a continue copy their session's)
|
|
143
|
+
# --llm-context; a fork and a continue copy their session's); without
|
|
144
|
+
# a toolkit the session is stored on config's toolkit.default, a
|
|
145
|
+
# delegate child always on toolkit.child (Toolkit.configured)
|
|
144
146
|
# @param continues [String, nil] the session this one continues (a
|
|
145
147
|
# session field; SessionContinue)
|
|
146
148
|
# @param note [String, nil] a context note the session starts with,
|
|
@@ -177,6 +179,7 @@ module Samagotchi
|
|
|
177
179
|
setup: setup,
|
|
178
180
|
continues: continues
|
|
179
181
|
)
|
|
182
|
+
session.toolkit = Toolkit.configured(delegate: delegate) if delegate || session.toolkit.nil?
|
|
180
183
|
session.model_warning = model_warning
|
|
181
184
|
# With no prompt there is no first turn to run (an attaching UI sends
|
|
182
185
|
# the prompts), so the session starts idle.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "llm_context_override"
|
|
4
4
|
require_relative "reasoning_history"
|
|
5
5
|
require_relative "thinking"
|
|
6
|
+
require_relative "toolkit"
|
|
6
7
|
|
|
7
8
|
module Samagotchi
|
|
8
9
|
# A session's own settings that come before its model's, carried as one
|
|
@@ -12,27 +13,33 @@ module Samagotchi
|
|
|
12
13
|
# +thinking+ a level Symbol (Thinking.session_level) or nil,
|
|
13
14
|
# +reasoning_history+ a level Symbol (ReasoningHistory.session_level) or nil,
|
|
14
15
|
# +appended_prompt_files+ the absolute paths chi --append-system-prompt
|
|
15
|
-
# named (their text goes into the system prompt; [] with none)
|
|
16
|
+
# named (their text goes into the system prompt; [] with none),
|
|
17
|
+
# +toolkit+ a Toolkit or nil (SessionManager.spawn_session gives the
|
|
18
|
+
# session config's toolkit.default then).
|
|
16
19
|
#
|
|
17
20
|
# It doesn't save itself: the session keeps each setting in its own field
|
|
18
21
|
# and file key, and resolves it on its own. A delegate child starts
|
|
19
22
|
# without one; a continue and a fork copy their session's (.of).
|
|
20
|
-
SessionSetup = Data.define(:llm_context, :thinking, :reasoning_history, :appended_prompt_files) do
|
|
21
|
-
def initialize(llm_context: nil, thinking: nil, reasoning_history: nil, appended_prompt_files: [])
|
|
23
|
+
SessionSetup = Data.define(:llm_context, :thinking, :reasoning_history, :appended_prompt_files, :toolkit) do
|
|
24
|
+
def initialize(llm_context: nil, thinking: nil, reasoning_history: nil, appended_prompt_files: [], toolkit: nil)
|
|
22
25
|
super(llm_context: llm_context, thinking: thinking, reasoning_history: reasoning_history,
|
|
23
|
-
appended_prompt_files: Array(appended_prompt_files).map(&:to_s).freeze)
|
|
26
|
+
appended_prompt_files: Array(appended_prompt_files).map(&:to_s).freeze, toolkit: toolkit)
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
def empty?
|
|
29
|
+
def empty?
|
|
30
|
+
llm_context.nil? && thinking.nil? && reasoning_history.nil? && appended_prompt_files.empty? && toolkit.nil?
|
|
31
|
+
end
|
|
27
32
|
|
|
28
|
-
# A session's own setup, for a session that starts from it.
|
|
33
|
+
# A session's own setup, for a session that starts from it. Its
|
|
34
|
+
# toolkit always: one from before toolkits is full (Toolkit.of), not
|
|
35
|
+
# whatever config's default is now.
|
|
29
36
|
# @param session [Session, nil]
|
|
30
37
|
# @return [SessionSetup]
|
|
31
38
|
def self.of(session)
|
|
32
39
|
return new if session.nil?
|
|
33
40
|
|
|
34
41
|
new(llm_context: session.llm_context, thinking: session.thinking, reasoning_history: session.reasoning_history,
|
|
35
|
-
appended_prompt_files: session.appended_prompt_files)
|
|
42
|
+
appended_prompt_files: session.appended_prompt_files, toolkit: Toolkit.of(session.toolkit))
|
|
36
43
|
end
|
|
37
44
|
end
|
|
38
45
|
end
|
|
@@ -317,7 +317,7 @@ module Samagotchi
|
|
|
317
317
|
"Project memories:\n#{indexes[:project]}",
|
|
318
318
|
"System memories:\n#{indexes[:system]}"
|
|
319
319
|
].join("\n\n")
|
|
320
|
-
stable = [thinking_token + base, rg_guidance, system_identity_section, model_notes_section, explicit_memory_section,
|
|
320
|
+
stable = [thinking_token + base, not_loaded_line(layers), rg_guidance, system_identity_section, model_notes_section, explicit_memory_section,
|
|
321
321
|
project_specific_description, appended_section, memory_sections]
|
|
322
322
|
stable_text = stable.compact.join("\n")
|
|
323
323
|
volatile = [current_model, project_location, current_session].compact
|
|
@@ -328,6 +328,21 @@ module Samagotchi
|
|
|
328
328
|
with_tail_declarations(prompt, chat: chat, layers: layers)
|
|
329
329
|
end
|
|
330
330
|
|
|
331
|
+
# The tools the session's toolkit leaves out, named once right after the
|
|
332
|
+
# base prompt: sessions on another toolkit already differ at the tools,
|
|
333
|
+
# and nothing later (a memory write) moves it. nil when none are (full):
|
|
334
|
+
# the prompt is what it was before toolkits.
|
|
335
|
+
def not_loaded_line(layers)
|
|
336
|
+
registry = @tools_lookup.call
|
|
337
|
+
return nil unless registry.respond_to?(:left_out_names)
|
|
338
|
+
|
|
339
|
+
names = registry.left_out_names(layers: layers)
|
|
340
|
+
return nil if names.empty?
|
|
341
|
+
|
|
342
|
+
"Not loaded in this session (toolkit #{registry.toolkit&.name}): #{names.join(", ")}. " \
|
|
343
|
+
"Only the user can load one (/toolkit add <name>)."
|
|
344
|
+
end
|
|
345
|
+
|
|
331
346
|
def with_tail_declarations(prompt, chat:, layers:)
|
|
332
347
|
chat || !gemma_tail_declarations? ? prompt : prompt.rstrip + tool_declarations(layers)
|
|
333
348
|
end
|
|
@@ -46,17 +46,23 @@ module Samagotchi
|
|
|
46
46
|
# @param thinking [String, nil] --thinking as typed: a new session
|
|
47
47
|
# starts with it as its own level; a resumed or attached one's
|
|
48
48
|
# worker gets a /thinking, after those
|
|
49
|
+
# @param toolkit [Toolkit, nil] --toolkit/--tool/--no-tool: a new
|
|
50
|
+
# session's toolkit (config's toolkit.default without); an existing
|
|
51
|
+
# session keeps its own
|
|
52
|
+
# @param toolkit_flags [Array<String>] which of those were given, for
|
|
53
|
+
# the line an existing session's run prints
|
|
49
54
|
# @param state_dir [String, nil] where the sessions are (the default
|
|
50
55
|
# state dir by default): the worker's, for the attached loop too
|
|
51
56
|
# @return [Symbol] :detached, :closed when the worker went away,
|
|
52
57
|
# :failed when the --model switch didn't go through, or (input from a
|
|
53
58
|
# pipe) :turn_failed / :empty_answer / :unanswered (AttachedLoop#run)
|
|
54
59
|
def run(attach: nil, shared: false, resume: nil, prompt: nil, model: nil, no_interrupt: false, default_input: true,
|
|
55
|
-
memories: [], muted_memories: [], llm_context: {}, thinking: nil, appended_prompt_files: [],
|
|
60
|
+
memories: [], muted_memories: [], llm_context: {}, thinking: nil, appended_prompt_files: [], toolkit: nil,
|
|
61
|
+
toolkit_flags: [], state_dir: nil)
|
|
56
62
|
state_dir ||= Session.default_state_dir
|
|
57
63
|
client = connect(attach: attach, shared: shared, resume: resume, model: model, state_dir: state_dir,
|
|
58
64
|
memories: memories, muted_memories: muted_memories, llm_context: llm_context, thinking: thinking,
|
|
59
|
-
appended_prompt_files: appended_prompt_files)
|
|
65
|
+
appended_prompt_files: appended_prompt_files, toolkit: toolkit, toolkit_flags: toolkit_flags)
|
|
60
66
|
commands = []
|
|
61
67
|
commands << "/model #{model}" if model && (attach || resume)
|
|
62
68
|
commands << LLMContextOverride.command_line(llm_context) if (attach || resume) && !llm_context.empty?
|
|
@@ -123,10 +129,12 @@ module Samagotchi
|
|
|
123
129
|
# @return [BridgeClient] a client for the live Bridge of the session
|
|
124
130
|
# @raise [Error]
|
|
125
131
|
def connect(attach: nil, shared: false, resume: nil, model: nil, state_dir: nil, wait: BRIDGE_WAIT,
|
|
126
|
-
memories: [], muted_memories: [], llm_context: {}, thinking: nil, appended_prompt_files: [],
|
|
132
|
+
memories: [], muted_memories: [], llm_context: {}, thinking: nil, appended_prompt_files: [], toolkit: nil,
|
|
133
|
+
toolkit_flags: [], err: $stderr)
|
|
127
134
|
sd = state_dir || Session.default_state_dir
|
|
128
135
|
if attach || resume
|
|
129
|
-
warn_memory_flags_ignored(attach || resume, memories, muted_memories, err, appended_prompt_files: appended_prompt_files
|
|
136
|
+
warn_memory_flags_ignored(attach || resume, memories, muted_memories, err, appended_prompt_files: appended_prompt_files,
|
|
137
|
+
toolkit_flags: toolkit_flags)
|
|
130
138
|
end
|
|
131
139
|
if attach
|
|
132
140
|
live = connect_existing(attach, sd)
|
|
@@ -143,7 +151,8 @@ module Samagotchi
|
|
|
143
151
|
memories: memories, muted_memories: muted_memories,
|
|
144
152
|
setup: SessionSetup.new(llm_context: llm_context.empty? ? nil : LLMContextOverride.update(nil, llm_context),
|
|
145
153
|
thinking: Thinking.session_level(thinking),
|
|
146
|
-
appended_prompt_files: appended_prompt_files
|
|
154
|
+
appended_prompt_files: appended_prompt_files,
|
|
155
|
+
toolkit: toolkit))
|
|
147
156
|
end
|
|
148
157
|
client = BridgeClient.wait_for(session.id, session_dir: Session.session_dir(session.id, state_dir: sd), timeout: wait)
|
|
149
158
|
client || raise(Error, "the worker for session #{session.id} did not start its Bridge in time")
|
|
@@ -159,14 +168,15 @@ module Samagotchi
|
|
|
159
168
|
end
|
|
160
169
|
|
|
161
170
|
# An existing session's prompt is built from its own session fields:
|
|
162
|
-
# --memory/--mute/--append-system-prompt
|
|
163
|
-
# --resume are ignored, with a line saying so (printed
|
|
164
|
-
# region opens).
|
|
165
|
-
def warn_memory_flags_ignored(session_id, memories, muted_memories, err, appended_prompt_files: [])
|
|
171
|
+
# --memory/--mute/--append-system-prompt and the toolkit flags given
|
|
172
|
+
# with --attach or --resume are ignored, with a line saying so (printed
|
|
173
|
+
# before the live region opens).
|
|
174
|
+
def warn_memory_flags_ignored(session_id, memories, muted_memories, err, appended_prompt_files: [], toolkit_flags: [])
|
|
166
175
|
flags = []
|
|
167
176
|
flags << "--memory" unless Array(memories).empty?
|
|
168
177
|
flags << "--mute" unless Array(muted_memories).empty?
|
|
169
178
|
flags << "--append-system-prompt" unless Array(appended_prompt_files).empty?
|
|
179
|
+
flags.concat(toolkit_flags)
|
|
170
180
|
return if flags.empty?
|
|
171
181
|
|
|
172
182
|
verb = flags.size == 1 ? "applies" : "apply"
|
|
@@ -97,10 +97,15 @@ module Samagotchi
|
|
|
97
97
|
# @param appended_prompt_files [Array<String>] --append-system-prompt's
|
|
98
98
|
# files (absolute): a new session saves them; a resumed one's own come
|
|
99
99
|
# first, and the merge goes only to the Engine (as --memory's does)
|
|
100
|
+
# @param toolkit [Toolkit, nil] --toolkit/--tool/--no-tool: a new
|
|
101
|
+
# session's toolkit (config's toolkit.default without); a resumed one
|
|
102
|
+
# keeps its own, with a line naming +toolkit_flags+ (the flags given)
|
|
100
103
|
def initialize(prompt: nil, client: nil, host_registry: nil, profile: nil, session_id: nil, no_interrupt: false, no_default_input: false, model_name: nil, memories: [], muted_memories: [], non_interactive: false, surface: nil,
|
|
101
104
|
spinner_tick_interval: AttachedView::TICK_INTERVAL, spinner_clock: nil, scratch: false, llm_context: {}, thinking: nil,
|
|
102
|
-
appended_prompt_files: [])
|
|
105
|
+
appended_prompt_files: [], toolkit: nil, toolkit_flags: [])
|
|
103
106
|
@scratch = scratch
|
|
107
|
+
@toolkit = toolkit
|
|
108
|
+
@toolkit_flags = toolkit_flags
|
|
104
109
|
@thinking_word = thinking
|
|
105
110
|
@prompt = prompt
|
|
106
111
|
@llm_context_words = llm_context
|
|
@@ -269,7 +274,7 @@ module Samagotchi
|
|
|
269
274
|
preloaded_memory_names: @requested_memories,
|
|
270
275
|
muted_memory_names: @muted_memory_names,
|
|
271
276
|
scratch: @scratch,
|
|
272
|
-
setup: SessionSetup.new(appended_prompt_files: @appended_prompt_files)
|
|
277
|
+
setup: SessionSetup.new(appended_prompt_files: @appended_prompt_files, toolkit: @toolkit || Toolkit.configured)
|
|
273
278
|
))
|
|
274
279
|
# --llm-context*: the session's own values, over a resumed one's.
|
|
275
280
|
# Saved at once on a resumed one: leaving before a turn keeps them.
|
|
@@ -279,6 +284,7 @@ module Samagotchi
|
|
|
279
284
|
session.thinking = Thinking.session_level(@thinking_word) if @thinking_word
|
|
280
285
|
session.save if @resume_session
|
|
281
286
|
end
|
|
287
|
+
warn_toolkit_flags_ignored(session) if @resume_session
|
|
282
288
|
claim_session!(session.id) unless @owner_lock
|
|
283
289
|
# A turn a worker died in (TurnCheckpoint): ended as canceled, with its
|
|
284
290
|
# note, before this REPL's first turn appends after it.
|
|
@@ -714,6 +720,16 @@ module Samagotchi
|
|
|
714
720
|
end
|
|
715
721
|
end
|
|
716
722
|
|
|
723
|
+
# The toolkit flags are a new session's: a resumed one keeps its own
|
|
724
|
+
# toolkit (as attached mode says it, AttachLauncher).
|
|
725
|
+
def warn_toolkit_flags_ignored(session)
|
|
726
|
+
return if @toolkit_flags.empty?
|
|
727
|
+
|
|
728
|
+
names = @toolkit_flags.size > 1 ? "#{@toolkit_flags[0..-2].join(", ")} and #{@toolkit_flags.last}" : @toolkit_flags.first
|
|
729
|
+
warn "(#{names} #{@toolkit_flags.size == 1 ? "applies" : "apply"} to a new session; #{session.id}'s prompt is already built)"
|
|
730
|
+
end
|
|
731
|
+
private :warn_toolkit_flags_ignored
|
|
732
|
+
|
|
717
733
|
private
|
|
718
734
|
|
|
719
735
|
# Interactive REPL loop. Session seed + messages are built by #run and
|
|
@@ -36,7 +36,7 @@ module Samagotchi
|
|
|
36
36
|
# <|channel>thought......<channel|>
|
|
37
37
|
class Gemma
|
|
38
38
|
# Digits after the first character: plugin tools may have them
|
|
39
|
-
# (Plugin::
|
|
39
|
+
# (Plugin::ToolSpec::TOOL_NAME).
|
|
40
40
|
TOOL_CALL_BODY_RE = /\Acall:([a-z_][a-z0-9_]{0,49})\{/
|
|
41
41
|
THOUGHT_CHANNEL_OPEN = ModelProfile::GEMMA_THOUGHT_CHANNEL_OPEN
|
|
42
42
|
THOUGHT_CHANNEL_CLOSE = ModelProfile::GEMMA_THOUGHT_CHANNEL_CLOSE
|