samagotchi 0.21.0 → 0.23.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 +62 -1
- data/bin/chi +9 -4
- data/docs/architecture.md +3 -2
- data/docs/configuration.md +15 -4
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +1 -1
- data/docs/sub-agent.md +11 -5
- data/docs/testing.md +6 -0
- data/lib/samagotchi/answer_command.rb +11 -5
- data/lib/samagotchi/bootstrap/config_writer.rb +2 -1
- data/lib/samagotchi/bootstrap/probe.rb +1 -1
- data/lib/samagotchi/bootstrap_command.rb +2 -2
- data/lib/samagotchi/bridge/bounded_queue.rb +0 -2
- data/lib/samagotchi/bridge/sse_writer.rb +0 -6
- data/lib/samagotchi/bridge/turn_accumulator.rb +2 -2
- data/lib/samagotchi/bridge.rb +12 -9
- data/lib/samagotchi/bridge_client/event_stream.rb +2 -2
- data/lib/samagotchi/bridge_client.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +39 -29
- data/lib/samagotchi/bundles/btw/manifest.yml +2 -2
- data/lib/samagotchi/bundles/btw/plugin.rb +2 -0
- data/lib/samagotchi/bundles/check-in/manifest.yml +2 -2
- data/lib/samagotchi/bundles/check-in/plugin.rb +2 -0
- data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +6 -5
- data/lib/samagotchi/bundles/known-names/manifest.yml +2 -2
- data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
- data/lib/samagotchi/bundles/loop-guard/plugin.rb +2 -0
- data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
- data/lib/samagotchi/bundles/mcp/plugin.rb +2 -0
- data/lib/samagotchi/bundles/skills/manifest.yml +2 -2
- data/lib/samagotchi/bundles/skills/plugin.rb +5 -3
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +7 -7
- data/lib/samagotchi/bundles/source-links/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/cancellation_controller.rb +1 -1
- data/lib/samagotchi/cli/command.rb +1 -1
- data/lib/samagotchi/cli/parent_wait.rb +9 -2
- data/lib/samagotchi/client.rb +3 -5
- data/lib/samagotchi/commands/registry.rb +2 -2
- data/lib/samagotchi/config.rb +38 -14
- data/lib/samagotchi/context_note.rb +1 -1
- data/lib/samagotchi/context_status.rb +6 -6
- data/lib/samagotchi/continue_offer.rb +44 -12
- data/lib/samagotchi/debug_log.rb +1 -1
- data/lib/samagotchi/desktop/macos.rb +5 -4
- data/lib/samagotchi/empty_answer_retry.rb +1 -1
- data/lib/samagotchi/engine.rb +49 -19
- data/lib/samagotchi/gem_update.rb +2 -1
- data/lib/samagotchi/guardrail_wiring.rb +1 -1
- data/lib/samagotchi/guardrails/approval.rb +5 -5
- data/lib/samagotchi/guardrails/approvals.rb +2 -2
- data/lib/samagotchi/guardrails/gate.rb +2 -2
- data/lib/samagotchi/guardrails/read_only_shell.rb +5 -3
- data/lib/samagotchi/guardrails/shell_git_dirs.rb +1 -1
- data/lib/samagotchi/guardrails/shell_lex.rb +2 -2
- data/lib/samagotchi/guardrails/shell_paths.rb +2 -2
- data/lib/samagotchi/hooks/bundle_loader.rb +9 -6
- data/lib/samagotchi/hooks/loader.rb +18 -16
- data/lib/samagotchi/hooks/registry.rb +10 -2
- data/lib/samagotchi/host_registry.rb +1 -3
- data/lib/samagotchi/idle_recap.rb +18 -15
- data/lib/samagotchi/idle_reminders.rb +6 -3
- data/lib/samagotchi/image_store.rb +5 -2
- data/lib/samagotchi/kernel_loop.rb +31 -31
- data/lib/samagotchi/llm/chat_loop.rb +9 -6
- data/lib/samagotchi/llm/errors.rb +33 -2
- data/lib/samagotchi/llm/http.rb +12 -12
- data/lib/samagotchi/llm/openai_chat.rb +53 -30
- data/lib/samagotchi/llm/prompt_cache.rb +83 -0
- data/lib/samagotchi/log.rb +2 -2
- data/lib/samagotchi/log_subscriber.rb +2 -1
- data/lib/samagotchi/memory_bundle/builder.rb +9 -3
- data/lib/samagotchi/memory_bundle/index_updater.rb +13 -1
- data/lib/samagotchi/memory_bundle/installer.rb +67 -57
- data/lib/samagotchi/memory_bundle/listing.rb +5 -1
- data/lib/samagotchi/memory_bundle/manifest.rb +14 -4
- data/lib/samagotchi/memory_bundle/merger.rb +6 -4
- data/lib/samagotchi/memory_bundle/provenance.rb +16 -13
- data/lib/samagotchi/memory_bundle/source.rb +7 -3
- data/lib/samagotchi/memory_bundle/status.rb +3 -1
- data/lib/samagotchi/memory_bundle/system_bundle.rb +3 -2
- data/lib/samagotchi/memory_bundle/trash.rb +5 -2
- data/lib/samagotchi/memory_bundle/uninstaller.rb +9 -6
- data/lib/samagotchi/memory_bundle.rb +1 -0
- data/lib/samagotchi/model_overlay.rb +3 -5
- data/lib/samagotchi/model_profile.rb +12 -8
- data/lib/samagotchi/note_command.rb +2 -1
- data/lib/samagotchi/output_formatter.rb +18 -17
- data/lib/samagotchi/owner_lock.rb +2 -1
- data/lib/samagotchi/parent_report.rb +24 -7
- data/lib/samagotchi/plugin/api.rb +8 -5
- data/lib/samagotchi/plugin/context.rb +1 -3
- data/lib/samagotchi/plugin/loader.rb +1 -1
- data/lib/samagotchi/plugin/service.rb +1 -1
- data/lib/samagotchi/plugin/sessions.rb +1 -0
- data/lib/samagotchi/plugin_tasks.rb +1 -1
- data/lib/samagotchi/prompt.rb +68 -12
- data/lib/samagotchi/prompt_literal_guard.rb +1 -1
- data/lib/samagotchi/question_desk.rb +7 -13
- data/lib/samagotchi/recap_setup.rb +1 -1
- data/lib/samagotchi/relay_desk.rb +1 -1
- data/lib/samagotchi/reminder_store.rb +2 -1
- data/lib/samagotchi/reply_wait.rb +12 -2
- data/lib/samagotchi/send_command.rb +4 -2
- data/lib/samagotchi/session.rb +2 -2
- data/lib/samagotchi/session_archive_command.rb +5 -1
- data/lib/samagotchi/session_commands.rb +1 -3
- data/lib/samagotchi/session_manager.rb +2 -4
- data/lib/samagotchi/session_metrics.rb +10 -10
- data/lib/samagotchi/session_observer.rb +3 -2
- data/lib/samagotchi/session_retention.rb +2 -2
- data/lib/samagotchi/sessions_command.rb +14 -5
- data/lib/samagotchi/system_prompt.rb +19 -8
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +5 -5
- data/lib/samagotchi/terminal_ui/attached_loop.rb +2 -2
- data/lib/samagotchi/terminal_ui/event_renderer.rb +1 -2
- data/lib/samagotchi/terminal_ui/formatting.rb +10 -10
- data/lib/samagotchi/terminal_ui/input_support.rb +2 -2
- data/lib/samagotchi/terminal_ui/line_reader.rb +3 -3
- data/lib/samagotchi/terminal_ui/question_prompt.rb +23 -23
- data/lib/samagotchi/terminal_ui/question_slot.rb +10 -10
- data/lib/samagotchi/terminal_ui/reline_seam.rb +1 -1
- data/lib/samagotchi/terminal_ui/repl_input.rb +3 -2
- data/lib/samagotchi/terminal_ui.rb +9 -5
- data/lib/samagotchi/text_diff.rb +12 -12
- data/lib/samagotchi/thinking.rb +6 -2
- data/lib/samagotchi/thought_stream_splitter.rb +6 -3
- data/lib/samagotchi/tool_activity.rb +1 -0
- data/lib/samagotchi/tool_call_parser.rb +13 -14
- data/lib/samagotchi/tool_declarations.rb +36 -35
- data/lib/samagotchi/tools/ask_user_question.rb +3 -4
- data/lib/samagotchi/tools/builtin_calls.rb +1 -1
- data/lib/samagotchi/tools/delegate.rb +1 -1
- data/lib/samagotchi/tools/delegate_relay.rb +1 -1
- data/lib/samagotchi/tools/delegate_wait.rb +0 -1
- data/lib/samagotchi/tools/edit.rb +14 -13
- data/lib/samagotchi/tools/execute.rb +3 -5
- data/lib/samagotchi/tools/list_reminders.rb +1 -1
- data/lib/samagotchi/tools/memory.rb +21 -22
- data/lib/samagotchi/tools/read.rb +7 -7
- data/lib/samagotchi/tools/task_create.rb +1 -1
- data/lib/samagotchi/tools/task_get.rb +1 -1
- data/lib/samagotchi/tools/task_list.rb +1 -1
- data/lib/samagotchi/tools/task_runtime.rb +8 -9
- data/lib/samagotchi/tools/task_stop.rb +1 -1
- data/lib/samagotchi/tools/task_wait.rb +3 -4
- data/lib/samagotchi/tools/web_fetch.rb +2 -4
- data/lib/samagotchi/tools/write.rb +1 -1
- data/lib/samagotchi/turn_flow.rb +8 -8
- data/lib/samagotchi/turn_note.rb +5 -1
- data/lib/samagotchi/turn_state.rb +22 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +2 -2
- data/lib/samagotchi/web/app.rb +13 -6
- data/lib/samagotchi/web/markdown_renderer.rb +2 -1
- data/lib/samagotchi/web/qr.rb +7 -1
- data/lib/samagotchi/web/server.rb +6 -2
- data/lib/samagotchi/worker.rb +10 -2
- metadata +21 -20
|
@@ -80,7 +80,7 @@ module Samagotchi
|
|
|
80
80
|
# A line that is one word: a slash and a name, no spaces, no second
|
|
81
81
|
# slash. `/modle` is one; `/foo bar` and `/usr/bin/env` are not (they
|
|
82
82
|
# are prompts).
|
|
83
|
-
UNKNOWN_COMMAND_WORD =
|
|
83
|
+
UNKNOWN_COMMAND_WORD = %r{\A/[A-Za-z][A-Za-z0-9_-]*\z}
|
|
84
84
|
|
|
85
85
|
# Whether +line+ is a command word no command answers: a typo like
|
|
86
86
|
# `/modle`, which a UI must not send to the model as a prompt.
|
|
@@ -100,7 +100,7 @@ module Samagotchi
|
|
|
100
100
|
|
|
101
101
|
text = line.to_s.strip
|
|
102
102
|
close = DidYouMean::SpellChecker.new(dictionary: @entries.map(&:name)).correct(text).first
|
|
103
|
-
"Unknown command #{text}. #{
|
|
103
|
+
"Unknown command #{text}. #{"Did you mean #{close}? " if close}/help lists the commands."
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
# @return [Entry, nil] the first entry the UI runs itself (local) that
|
data/lib/samagotchi/config.rb
CHANGED
|
@@ -4,7 +4,6 @@ require "yaml"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "fileutils"
|
|
6
6
|
require "uri"
|
|
7
|
-
require "set"
|
|
8
7
|
require_relative "atomic_file"
|
|
9
8
|
require_relative "config_text_edit"
|
|
10
9
|
require_relative "log"
|
|
@@ -56,8 +55,8 @@ module Samagotchi
|
|
|
56
55
|
end
|
|
57
56
|
|
|
58
57
|
# Section names: lower alnum only, no _ or -.
|
|
59
|
-
SECTION_RE = /\A[a-z0-9]+\z
|
|
60
|
-
LEAF_RE = /\A[a-z0-9_]+\z
|
|
58
|
+
SECTION_RE = /\A[a-z0-9]+\z/
|
|
59
|
+
LEAF_RE = /\A[a-z0-9_]+\z/
|
|
61
60
|
|
|
62
61
|
ENTRIES = [
|
|
63
62
|
# universal – env+config+cli
|
|
@@ -126,7 +125,7 @@ module Samagotchi
|
|
|
126
125
|
Entry.new(key: "context.status", yaml_path: %w[context status], type: :bool, default: true, expose: %i[env config cli]),
|
|
127
126
|
Entry.new(key: "context.window_tokens", yaml_path: %w[context window_tokens], type: :integer, default: nil, expose: %i[env config cli]),
|
|
128
127
|
Entry.new(key: "context.chars_per_token", yaml_path: %w[context chars_per_token], type: :float, default: 4.0, expose: %i[env config cli]),
|
|
129
|
-
Entry.new(key: "context.status_thresholds", yaml_path: %w[context status_thresholds],type: :string, default: "20,40,60,80", expose: %i[env config cli]),
|
|
128
|
+
Entry.new(key: "context.status_thresholds", yaml_path: %w[context status_thresholds], type: :string, default: "20,40,60,80", expose: %i[env config cli]),
|
|
130
129
|
Entry.new(key: "context.status_cadence", yaml_path: %w[context status_cadence], type: :integer, default: 0, expose: %i[env config cli]),
|
|
131
130
|
|
|
132
131
|
Entry.new(key: "thinking.turn_preamble", yaml_path: %w[thinking turn_preamble], type: :bool, default: true, expose: %i[env config cli]),
|
|
@@ -136,7 +135,7 @@ module Samagotchi
|
|
|
136
135
|
# the flag and the env, above config.yml's value.
|
|
137
136
|
Entry.new(key: "thinking.level", yaml_path: %w[thinking level], type: :string, default: "default", expose: %i[env config]),
|
|
138
137
|
|
|
139
|
-
Entry.new(key: "default.
|
|
138
|
+
Entry.new(key: "default.max_tokens", yaml_path: %w[default max_tokens], type: :integer, default: nil, expose: %i[env config cli]),
|
|
140
139
|
Entry.new(key: "max_tool_output_chars", yaml_path: %w[max_tool_output_chars], type: :integer, default: 10_000, expose: %i[env config cli]),
|
|
141
140
|
|
|
142
141
|
Entry.new(key: "retry.max", yaml_path: %w[retry max], type: :integer, default: 5, expose: %i[env config cli]),
|
|
@@ -186,7 +185,7 @@ module Samagotchi
|
|
|
186
185
|
# env+config only
|
|
187
186
|
Entry.new(key: "default.input", yaml_path: %w[default input], type: :string, default: nil, expose: %i[env config]),
|
|
188
187
|
Entry.new(key: "history.file", yaml_path: %w[history file], type: :string, default: nil, expose: %i[env config]),
|
|
189
|
-
Entry.new(key: "skip_agent_md", yaml_path: %w[skip_agent_md], type: :bool, default: false, expose: %i[env config])
|
|
188
|
+
Entry.new(key: "skip_agent_md", yaml_path: %w[skip_agent_md], type: :bool, default: false, expose: %i[env config])
|
|
190
189
|
].freeze
|
|
191
190
|
|
|
192
191
|
# Top-level maps read by their own code, whose entry names and contents
|
|
@@ -228,11 +227,13 @@ module Samagotchi
|
|
|
228
227
|
# Coercion helpers
|
|
229
228
|
def coerce(entry, raw)
|
|
230
229
|
return nil if raw.nil?
|
|
230
|
+
|
|
231
231
|
# For string, preserve as-is (including trailing spaces like "Please ")
|
|
232
232
|
if entry.type == :string
|
|
233
233
|
# A YAML list for a "|"-separated setting (web.annotate_presets).
|
|
234
234
|
str = raw.is_a?(Array) ? raw.join("|") : raw.to_s
|
|
235
235
|
return nil if str.empty? && entry.type != :string
|
|
236
|
+
|
|
236
237
|
return str
|
|
237
238
|
end
|
|
238
239
|
str = raw.to_s.strip
|
|
@@ -267,7 +268,7 @@ module Samagotchi
|
|
|
267
268
|
lowered = str.downcase
|
|
268
269
|
allowed = entry.enum_values.map(&:downcase)
|
|
269
270
|
unless allowed.include?(lowered)
|
|
270
|
-
Log.warn(:config, "invalid_value", echo: "Warning: invalid value for #{entry.key}: #{raw.inspect} (allowed: #{entry.enum_values.join(
|
|
271
|
+
Log.warn(:config, "invalid_value", echo: "Warning: invalid value for #{entry.key}: #{raw.inspect} (allowed: #{entry.enum_values.join(", ")}) — using default", key: entry.key)
|
|
271
272
|
return entry.default
|
|
272
273
|
end
|
|
273
274
|
# return canonical casing from enum_values
|
|
@@ -296,6 +297,7 @@ module Samagotchi
|
|
|
296
297
|
raw = cli_overrides[entry.key]
|
|
297
298
|
# cli_overrides may already be coerced; detect by type
|
|
298
299
|
return [raw, :cli] if already_coerced?(entry, raw)
|
|
300
|
+
|
|
299
301
|
return [coerce(entry, raw), :cli]
|
|
300
302
|
end
|
|
301
303
|
if cli_overrides.key?(entry.cli_flag)
|
|
@@ -323,7 +325,7 @@ module Samagotchi
|
|
|
323
325
|
|
|
324
326
|
def already_coerced?(entry, val)
|
|
325
327
|
case entry.type
|
|
326
|
-
when :bool then
|
|
328
|
+
when :bool then [true, false].include?(val)
|
|
327
329
|
when :integer then val.is_a?(Integer)
|
|
328
330
|
when :float then val.is_a?(Float) || val.is_a?(Integer)
|
|
329
331
|
when :enum then entry.enum_values.include?(val)
|
|
@@ -343,6 +345,7 @@ module Samagotchi
|
|
|
343
345
|
cur = data
|
|
344
346
|
yaml_path.each_with_index do |seg, idx|
|
|
345
347
|
return nil unless cur.is_a?(Hash)
|
|
348
|
+
|
|
346
349
|
last = idx == yaml_path.size - 1
|
|
347
350
|
if last
|
|
348
351
|
candidates = [seg, seg.to_sym]
|
|
@@ -361,6 +364,7 @@ module Samagotchi
|
|
|
361
364
|
# also try kebab alias for section (should not exist per spec, but be lenient)
|
|
362
365
|
nxt ||= cur[seg.tr("_", "-")] || cur[seg.tr("_", "-").to_sym]
|
|
363
366
|
return nil if nxt.nil?
|
|
367
|
+
|
|
364
368
|
cur = nxt
|
|
365
369
|
end
|
|
366
370
|
end
|
|
@@ -404,6 +408,13 @@ module Samagotchi
|
|
|
404
408
|
get_with_origin(key).first
|
|
405
409
|
end
|
|
406
410
|
|
|
411
|
+
# default.max_tokens: the most tokens one generation may produce, or
|
|
412
|
+
# nil when unset or not positive (the server's own limit).
|
|
413
|
+
def max_tokens
|
|
414
|
+
value = get("default.max_tokens").to_i
|
|
415
|
+
value if value.positive?
|
|
416
|
+
end
|
|
417
|
+
|
|
407
418
|
# A timeout setting (server.open_timeout, server.read_timeout) in
|
|
408
419
|
# seconds, +given+ (a caller's own value) first; one that isn't
|
|
409
420
|
# positive (0, a word) is the key's default, on every kind of host.
|
|
@@ -419,6 +430,7 @@ module Samagotchi
|
|
|
419
430
|
def get_with_origin(key)
|
|
420
431
|
entry = find_by_key(key)
|
|
421
432
|
raise ArgumentError, "unknown config key: #{key}" unless entry
|
|
433
|
+
|
|
422
434
|
# Live resolve so ENV changes (as in specs) are reflected without explicit reload
|
|
423
435
|
# Use current ENV and file (via ConfigFile's cached reader), plus any CLI
|
|
424
436
|
# overrides captured via reload!
|
|
@@ -546,7 +558,7 @@ module Samagotchi
|
|
|
546
558
|
candidates = candidates.uniq
|
|
547
559
|
near = near_names(probe, candidates)
|
|
548
560
|
close = (near + candidates.select { |c| c.split(".").last == probe.split(".").last }).uniq.first(3)
|
|
549
|
-
hint = close.empty? ? "" : " (did you mean #{close.map { |c| "'#{prefix}#{c}'" }.join(
|
|
561
|
+
hint = close.empty? ? "" : " (did you mean #{close.map { |c| "'#{prefix}#{c}'" }.join(" or ")}?)"
|
|
550
562
|
"config: unknown key '#{key}'#{hint}"
|
|
551
563
|
end
|
|
552
564
|
|
|
@@ -582,6 +594,7 @@ module Samagotchi
|
|
|
582
594
|
# parsed Hash or nil (missing file / parse error / non-mapping top level).
|
|
583
595
|
def read_yaml(env: ENV, path: global_path(env: env))
|
|
584
596
|
return nil if path.nil?
|
|
597
|
+
|
|
585
598
|
cache = @yaml_cache ||= {}
|
|
586
599
|
unless File.file?(path)
|
|
587
600
|
cache.delete(path)
|
|
@@ -593,7 +606,7 @@ module Samagotchi
|
|
|
593
606
|
return hit[1] if hit && hit[0] == key
|
|
594
607
|
|
|
595
608
|
data = begin
|
|
596
|
-
YAML.
|
|
609
|
+
YAML.safe_load_file(path, permitted_classes: [], aliases: false)
|
|
597
610
|
rescue StandardError
|
|
598
611
|
nil
|
|
599
612
|
end
|
|
@@ -631,7 +644,7 @@ module Samagotchi
|
|
|
631
644
|
# else warns once and counts as unset.
|
|
632
645
|
def bool_flag(value, where, key)
|
|
633
646
|
return nil if value.nil?
|
|
634
|
-
return value if
|
|
647
|
+
return value if [true, false].include?(value)
|
|
635
648
|
|
|
636
649
|
text = value.to_s.strip.downcase
|
|
637
650
|
return true if text == "true"
|
|
@@ -653,6 +666,7 @@ module Samagotchi
|
|
|
653
666
|
# nil. Reserved keys warn once and are dropped. nil when nothing is left.
|
|
654
667
|
def sampling_map(value, where)
|
|
655
668
|
return nil if value.nil?
|
|
669
|
+
|
|
656
670
|
unless value.is_a?(Hash)
|
|
657
671
|
warn_once "Warning: #{where}: sampling must be a map of request parameters; ignored"
|
|
658
672
|
return nil
|
|
@@ -661,6 +675,7 @@ module Samagotchi
|
|
|
661
675
|
result = value.each_with_object({}) do |(raw_key, raw_value), acc|
|
|
662
676
|
key = raw_key.to_s.strip
|
|
663
677
|
next if key.empty?
|
|
678
|
+
|
|
664
679
|
if SAMPLING_RESERVED_KEYS.include?(key)
|
|
665
680
|
warn_once "Warning: #{where}: sampling.#{key} is set by chi; ignored"
|
|
666
681
|
next
|
|
@@ -687,6 +702,7 @@ module Samagotchi
|
|
|
687
702
|
def clear_yaml_cache!(path = nil)
|
|
688
703
|
cache = @yaml_cache
|
|
689
704
|
return if cache.nil?
|
|
705
|
+
|
|
690
706
|
if path
|
|
691
707
|
cache.delete(path)
|
|
692
708
|
else
|
|
@@ -734,6 +750,7 @@ module Samagotchi
|
|
|
734
750
|
raw_hosts.each do |raw_name, raw_cfg|
|
|
735
751
|
name = raw_name.to_s.strip
|
|
736
752
|
next if name.empty?
|
|
753
|
+
|
|
737
754
|
unless name.match?(HOST_NAME_RE)
|
|
738
755
|
warn_once "Warning: ignoring hosts entry '#{name}': must match /[a-z0-9][a-z0-9._-]*/i"
|
|
739
756
|
next
|
|
@@ -795,7 +812,7 @@ module Samagotchi
|
|
|
795
812
|
next
|
|
796
813
|
end
|
|
797
814
|
port_val = port.to_s.strip.empty? ? 8080 : port.to_i
|
|
798
|
-
if port_val <= 0 || port_val >
|
|
815
|
+
if port_val <= 0 || port_val > 65_535
|
|
799
816
|
warn_once "Warning: ignoring hosts entry '#{name}': invalid port"
|
|
800
817
|
next
|
|
801
818
|
end
|
|
@@ -835,7 +852,7 @@ module Samagotchi
|
|
|
835
852
|
default_host = Samagotchi::Config.resolve("server.host", **opts).to_s.strip
|
|
836
853
|
default_host = "localhost" if default_host.empty?
|
|
837
854
|
default_port = Samagotchi::Config.resolve("server.port", **opts).to_i
|
|
838
|
-
default_port = 8080 if default_port <= 0 || default_port >
|
|
855
|
+
default_port = 8080 if default_port <= 0 || default_port > 65_535
|
|
839
856
|
transport_sym = Samagotchi::Config.resolve_with_origin("server.transport", **opts).then do |value, origin|
|
|
840
857
|
origin == :default ? nil : value.to_sym
|
|
841
858
|
end
|
|
@@ -877,7 +894,11 @@ module Samagotchi
|
|
|
877
894
|
|
|
878
895
|
# enabled: false (or "false", any case) in a hosts entry.
|
|
879
896
|
def host_disabled?(cfg)
|
|
880
|
-
enabled = cfg.key?("enabled")
|
|
897
|
+
enabled = if cfg.key?("enabled")
|
|
898
|
+
cfg["enabled"]
|
|
899
|
+
else
|
|
900
|
+
(cfg.key?(:enabled) ? cfg[:enabled] : true)
|
|
901
|
+
end
|
|
881
902
|
enabled == false || enabled.to_s.strip.downcase == "false"
|
|
882
903
|
end
|
|
883
904
|
private_class_method :host_disabled?
|
|
@@ -959,6 +980,7 @@ module Samagotchi
|
|
|
959
980
|
# Only serialize if non-default or explicitly configured hosts:
|
|
960
981
|
# include when hosts file exists with hosts: section or when workers need propagation
|
|
961
982
|
return nil if hosts.nil? || hosts.empty?
|
|
983
|
+
|
|
962
984
|
# Serialize to JSON with string keys
|
|
963
985
|
simple = hosts.transform_values do |v|
|
|
964
986
|
# A url entry travels as its url (host/port come from it); the API
|
|
@@ -990,6 +1012,7 @@ module Samagotchi
|
|
|
990
1012
|
def bundle_settings(env: ENV, path: global_path(env: env))
|
|
991
1013
|
raw = section("bundles", env: env, path: path)
|
|
992
1014
|
return {} if raw.nil?
|
|
1015
|
+
|
|
993
1016
|
unless raw.is_a?(Hash)
|
|
994
1017
|
Log.warn(:hooks, "bundles_section_invalid", echo: "[samagotchi:hooks] config.yml bundles: must be a mapping of bundle name to settings; ignored")
|
|
995
1018
|
return {}
|
|
@@ -1025,6 +1048,7 @@ module Samagotchi
|
|
|
1025
1048
|
raw.each_with_object({}) do |(k, v), result|
|
|
1026
1049
|
key = k.to_s.strip
|
|
1027
1050
|
next if key.empty?
|
|
1051
|
+
|
|
1028
1052
|
val = v.to_s.strip
|
|
1029
1053
|
next if val.empty?
|
|
1030
1054
|
|
|
@@ -42,7 +42,7 @@ module Samagotchi
|
|
|
42
42
|
|
|
43
43
|
# The note's own text, without its frame.
|
|
44
44
|
def text_of(message)
|
|
45
|
-
fetch(message, :content).to_s.sub(/\A\[CONTEXT NOTE[^\n]*\n/, "").
|
|
45
|
+
fetch(message, :content).to_s.sub(/\A\[CONTEXT NOTE[^\n]*\n/, "").delete_suffix("\n[END NOTE]")
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def fetch(message, key)
|
|
@@ -64,7 +64,7 @@ module Samagotchi
|
|
|
64
64
|
def self.last_bucket(conversation)
|
|
65
65
|
message = conversation.reverse.find do |entry|
|
|
66
66
|
content = entry[:content].to_s
|
|
67
|
-
entry[:role] == "system" &&
|
|
67
|
+
entry[:role] == "system" && content.start_with?(STATUS_PREFIX, LINE_PREFIX)
|
|
68
68
|
end
|
|
69
69
|
return nil unless message
|
|
70
70
|
|
|
@@ -221,11 +221,11 @@ module Samagotchi
|
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
def emit?(bucket:, iteration_index:)
|
|
224
|
-
bucket_changed = if @last_bucket.nil?
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
bucket_changed = bucket != if @last_bucket.nil?
|
|
225
|
+
"under#{@thresholds.first}"
|
|
226
|
+
else
|
|
227
|
+
@last_bucket
|
|
228
|
+
end
|
|
229
229
|
cadence_due = @cadence.positive? && ((iteration_index + 1) % @cadence).zero?
|
|
230
230
|
bucket_changed || cadence_due
|
|
231
231
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "time"
|
|
4
|
+
require_relative "log"
|
|
4
5
|
|
|
5
6
|
module Samagotchi
|
|
6
7
|
# A session worker's continue offer: what it announces when a turn runs out
|
|
@@ -41,6 +42,9 @@ module Samagotchi
|
|
|
41
42
|
# A question was posted since the last close (it may have been
|
|
42
43
|
# answered or superseded meanwhile).
|
|
43
44
|
@asked = false
|
|
45
|
+
# The text a Continue answer came with ({text:, source:}): it steers
|
|
46
|
+
# the continue turn (#run_continue_turn).
|
|
47
|
+
@continue_steer = nil
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
attr_writer :queue_command
|
|
@@ -52,9 +56,9 @@ module Samagotchi
|
|
|
52
56
|
# (or kept, after a cancelled continue turn) is announced and asked.
|
|
53
57
|
def after_turn(result, continue: false, no_interrupt: false)
|
|
54
58
|
outcome = @turn_flow.after_turn(result, continue: continue, no_interrupt: no_interrupt)
|
|
55
|
-
return unless
|
|
59
|
+
return unless %i[continue_offered continue_cancelled].include?(outcome)
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
announce_offer
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
# A prompt (or a reminder turn) taken while a continue is offered
|
|
@@ -62,6 +66,7 @@ module Samagotchi
|
|
|
62
66
|
def drop(origin)
|
|
63
67
|
return unless awaiting?
|
|
64
68
|
|
|
69
|
+
@continue_steer = nil
|
|
65
70
|
close("dropped")
|
|
66
71
|
@engine.synchronize_events do
|
|
67
72
|
@turn_flow.drop_offer!
|
|
@@ -95,6 +100,10 @@ module Samagotchi
|
|
|
95
100
|
close(resolved ? "answered" : "dropped") unless awaiting?
|
|
96
101
|
return unless resolved
|
|
97
102
|
|
|
103
|
+
# Answered without a turn (a typed /continue no that got there before
|
|
104
|
+
# the answer's /continue yes): the answer's text steers nothing.
|
|
105
|
+
@continue_steer = nil unless result.resume
|
|
106
|
+
|
|
98
107
|
# An offer answered without a turn ("no") is activity: the recap
|
|
99
108
|
# written at the offer (it says the turn stopped) gets rewritten.
|
|
100
109
|
@engine.record_activity unless result.resume
|
|
@@ -103,18 +112,32 @@ module Samagotchi
|
|
|
103
112
|
# The continue offer was answered yes: resume the conversation without a
|
|
104
113
|
# user message, with the iteration limit the offer's turn had. A failure
|
|
105
114
|
# keeps the offer, as the REPL does ("continue prompt preserved"), and
|
|
106
|
-
# asks again.
|
|
115
|
+
# asks again, one before the turn began too (logged, no :turn_failed).
|
|
116
|
+
# The answer's text is carried into the turn as it begins; a turn that
|
|
117
|
+
# never began leaves it to no other.
|
|
107
118
|
def run_continue_turn(command)
|
|
108
119
|
offer = @turn_flow.offer
|
|
109
120
|
close("answered")
|
|
110
|
-
@
|
|
111
|
-
@
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
steer = @continue_steer
|
|
122
|
+
@continue_steer = nil
|
|
123
|
+
begin
|
|
124
|
+
begin
|
|
125
|
+
@engine.steer_next_turn(steer[:text], source: steer[:source]) if steer
|
|
126
|
+
@turn_flow.before_continue_turn
|
|
127
|
+
rescue StandardError => e
|
|
128
|
+
Log.warn(:worker, "turn_not_begun", continue: true, error: e.class.name, msg: e.message)
|
|
129
|
+
return announce_offer(offer)
|
|
130
|
+
end
|
|
131
|
+
@run_turn.call(nil, continue: true, origin: { client_id: command[:client_id] }.compact,
|
|
132
|
+
max_iterations: @max_iterations.call(offer[:no_interrupt])) do |result, error|
|
|
133
|
+
if error
|
|
134
|
+
announce_offer(offer)
|
|
135
|
+
else
|
|
136
|
+
after_turn(result, continue: true, no_interrupt: offer[:no_interrupt])
|
|
137
|
+
end
|
|
117
138
|
end
|
|
139
|
+
ensure
|
|
140
|
+
@engine.drop_carried_steers if steer
|
|
118
141
|
end
|
|
119
142
|
end
|
|
120
143
|
|
|
@@ -129,7 +152,7 @@ module Samagotchi
|
|
|
129
152
|
|
|
130
153
|
private
|
|
131
154
|
|
|
132
|
-
def
|
|
155
|
+
def announce_offer(offer = @turn_flow.offer)
|
|
133
156
|
@engine.announce(type: :continue_offered, context: offer[:context], no_interrupt: offer[:no_interrupt])
|
|
134
157
|
ask
|
|
135
158
|
end
|
|
@@ -164,9 +187,18 @@ module Samagotchi
|
|
|
164
187
|
end
|
|
165
188
|
|
|
166
189
|
# The question's answer as the /continue line a typed answer would be:
|
|
167
|
-
# Continue → yes
|
|
190
|
+
# Continue → yes, and a text with it steers the continue turn (from the
|
|
191
|
+
# parent agent when the answer says so); Stop → no; a text (with Stop,
|
|
192
|
+
# or alone) → no, <text>.
|
|
168
193
|
def answered(answer, client_id:)
|
|
194
|
+
# The /continue line takes the text on one line; the steer keeps its
|
|
195
|
+
# line breaks.
|
|
169
196
|
text = answer[:freeform].to_s.gsub(/\s+/, " ").strip
|
|
197
|
+
@continue_steer = nil
|
|
198
|
+
if Array(answer[:selected]).include?(CONTINUE) && !text.empty?
|
|
199
|
+
@continue_steer = { text: answer[:freeform].to_s.strip,
|
|
200
|
+
source: answer[:by] == "parent_agent" ? "parent_agent" : "user" }
|
|
201
|
+
end
|
|
170
202
|
line = if Array(answer[:selected]).include?(CONTINUE) then "/continue yes"
|
|
171
203
|
elsif text.empty? then "/continue no"
|
|
172
204
|
else "/continue no, #{text}"
|
data/lib/samagotchi/debug_log.rb
CHANGED
|
@@ -86,7 +86,7 @@ module Samagotchi
|
|
|
86
86
|
|
|
87
87
|
def rotate
|
|
88
88
|
File.open("#{@path}.lock", File::RDWR | File::CREAT, 0o600) do |lock|
|
|
89
|
-
|
|
89
|
+
next unless lock.flock(File::LOCK_EX | File::LOCK_NB)
|
|
90
90
|
|
|
91
91
|
stat = File.stat(@path)
|
|
92
92
|
File.rename(@path, "#{@path}.1") if stat.ino == @ino && stat.size >= @max_bytes
|
|
@@ -22,7 +22,8 @@ module Samagotchi
|
|
|
22
22
|
# command goes through the runner, and the dirs are injectable, so specs
|
|
23
23
|
# (and smokes with register: false) never touch the real system.
|
|
24
24
|
class MacOS
|
|
25
|
-
Error
|
|
25
|
+
class Error < StandardError
|
|
26
|
+
end
|
|
26
27
|
|
|
27
28
|
APP_NAME = "Chi Helper"
|
|
28
29
|
BUNDLE_ID = "dev.samagotchi.chi-helper"
|
|
@@ -166,9 +167,9 @@ module Samagotchi
|
|
|
166
167
|
end
|
|
167
168
|
|
|
168
169
|
# Keeps the login item as it was.
|
|
169
|
-
def upgrade(&
|
|
170
|
+
def upgrade(&)
|
|
170
171
|
was_login = installed? && login == "enabled"
|
|
171
|
-
install(force: true, login: was_login, &
|
|
172
|
+
install(force: true, login: was_login, &)
|
|
172
173
|
end
|
|
173
174
|
|
|
174
175
|
# SMAppService status as the app reports it ("enabled",
|
|
@@ -212,7 +213,7 @@ module Samagotchi
|
|
|
212
213
|
env = launch["env"].is_a?(Hash) ? launch["env"] : {}
|
|
213
214
|
dump, = @runner.run([PBS, "-dump"])
|
|
214
215
|
_, running = @runner.run(["pgrep", "-x", EXECUTABLE])
|
|
215
|
-
hotkey =
|
|
216
|
+
hotkey = JSON.parse(File.read(hotkey_path)) rescue nil
|
|
216
217
|
result.merge(
|
|
217
218
|
app_version: app_version,
|
|
218
219
|
login: login,
|
|
@@ -30,7 +30,7 @@ module Samagotchi
|
|
|
30
30
|
# The request options of a retry generation: the turn's sampling, with
|
|
31
31
|
# TEMPERATURE when it sets none (a configured nil keeps "don't send").
|
|
32
32
|
def self.sampling(base)
|
|
33
|
-
base
|
|
33
|
+
base ||= {}
|
|
34
34
|
base.key?(:temperature) ? base : base.merge(temperature: TEMPERATURE)
|
|
35
35
|
end
|
|
36
36
|
|
data/lib/samagotchi/engine.rb
CHANGED
|
@@ -325,6 +325,21 @@ module Samagotchi
|
|
|
325
325
|
@turn_state.steer(text, source: source)
|
|
326
326
|
end
|
|
327
327
|
|
|
328
|
+
# Like #steer, for the turn that begins next (a continue turn answered
|
|
329
|
+
# with a text): it joins that turn at its first boundary. Callable from
|
|
330
|
+
# any thread; a turn that never begins keeps it for the next one, unless
|
|
331
|
+
# #drop_carried_steers drops it.
|
|
332
|
+
# @return [Boolean] whether it was queued (false: blank)
|
|
333
|
+
def steer_next_turn(text, source:)
|
|
334
|
+
@turn_state.steer_next_turn(text, source: source)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Drop the #steer_next_turn steers no turn began with (the continue turn
|
|
338
|
+
# failed before it began), logged; a no-op once a turn took them.
|
|
339
|
+
def drop_carried_steers
|
|
340
|
+
log_dropped_steers(@turn_state.drop_carried_steers, "not_begun")
|
|
341
|
+
end
|
|
342
|
+
|
|
328
343
|
# The drain a turn's loop gets: the caller's lines (a UI's steering; nil
|
|
329
344
|
# in a --non-interactive run) and the plugin steers. At the after-answer
|
|
330
345
|
# boundary the steers are dropped: the model answered, and a nudge would
|
|
@@ -542,8 +557,8 @@ module Samagotchi
|
|
|
542
557
|
|
|
543
558
|
# Run the block with the event log held: no event is numbered or
|
|
544
559
|
# delivered meanwhile, and events the block emits keep their order.
|
|
545
|
-
def synchronize_events(&
|
|
546
|
-
@session_observer.synchronize(&
|
|
560
|
+
def synchronize_events(&)
|
|
561
|
+
@session_observer.synchronize(&)
|
|
547
562
|
end
|
|
548
563
|
|
|
549
564
|
# Add a context note to the session's conversation, between turns: a
|
|
@@ -623,8 +638,8 @@ module Samagotchi
|
|
|
623
638
|
# Start an anytime command on its own thread (D8), one #shutdown waits
|
|
624
639
|
# for, so its command_ran is announced before the process leaves.
|
|
625
640
|
# @return [Thread]
|
|
626
|
-
def spawn_anytime(&
|
|
627
|
-
thread = Thread.new(&
|
|
641
|
+
def spawn_anytime(&)
|
|
642
|
+
thread = Thread.new(&)
|
|
628
643
|
@lifecycle_mutex.synchronize do
|
|
629
644
|
@anytime_threads.select!(&:alive?)
|
|
630
645
|
@anytime_threads << thread
|
|
@@ -641,9 +656,9 @@ module Samagotchi
|
|
|
641
656
|
|
|
642
657
|
# Add a plugin's init task (Plugin::Api#init at commit); it starts with
|
|
643
658
|
# #start_init_tasks!.
|
|
644
|
-
def add_init_task(bundle:, label:, plugin_label:, provides_tools:, quiet:, timeout:, failed: nil, &
|
|
659
|
+
def add_init_task(bundle:, label:, plugin_label:, provides_tools:, quiet:, timeout:, failed: nil, &)
|
|
645
660
|
@plugin_tasks.add(bundle: bundle, label: label, plugin_label: plugin_label, provides_tools: provides_tools,
|
|
646
|
-
quiet: quiet, timeout: timeout, failed: failed, &
|
|
661
|
+
quiet: quiet, timeout: timeout, failed: failed, &)
|
|
647
662
|
end
|
|
648
663
|
|
|
649
664
|
# Start the init tasks not started yet, each on its own thread
|
|
@@ -821,8 +836,8 @@ module Samagotchi
|
|
|
821
836
|
# @param name [Symbol] one of the hook names (see Hooks module)
|
|
822
837
|
# @param block [Proc] receives an event hash (may mutate in place)
|
|
823
838
|
# @return [void]
|
|
824
|
-
def register_hook(name, &
|
|
825
|
-
@hooks.register(name, &
|
|
839
|
+
def register_hook(name, &)
|
|
840
|
+
@hooks.register(name, &)
|
|
826
841
|
end
|
|
827
842
|
|
|
828
843
|
# Unregister a previously registered hook.
|
|
@@ -1089,7 +1104,8 @@ module Samagotchi
|
|
|
1089
1104
|
# @param limit [Integer, nil] the turn ran out of iterations at this
|
|
1090
1105
|
# limit: "exhausted" => true, "limit" => N, so a wait with nobody to
|
|
1091
1106
|
# answer the continue offer says so (ReplyWait, ParentReport "limit")
|
|
1092
|
-
|
|
1107
|
+
# @param event [Hash] the turn's end event (#stop_facts)
|
|
1108
|
+
def record_last_turn(session, outcome, seconds, origin, event, limit: nil)
|
|
1093
1109
|
client_id = origin.is_a?(Hash) ? origin[:client_id].to_s : ""
|
|
1094
1110
|
source = if client_id.start_with?("#{Tools::Delegate::CLIENT_PREFIX}:") then "delegate"
|
|
1095
1111
|
elsif client_id == SessionManager::REMINDER_CLIENT_ID then "reminder"
|
|
@@ -1098,9 +1114,23 @@ module Samagotchi
|
|
|
1098
1114
|
session.last_turn = { "outcome" => outcome, "ended_at" => Time.now.iso8601(3),
|
|
1099
1115
|
"seconds" => seconds.round(1), "origin" => source }
|
|
1100
1116
|
session.last_turn.merge!("exhausted" => true, "limit" => limit) if limit
|
|
1117
|
+
session.last_turn.merge!(stop_facts(event))
|
|
1101
1118
|
end
|
|
1102
1119
|
private :record_last_turn
|
|
1103
1120
|
|
|
1121
|
+
# Why a turn stopped, from its end event, for a parent agent's wait
|
|
1122
|
+
# (ReplyWait): a provider error's kind and whether a retry may help, a
|
|
1123
|
+
# cancel's reason and the hook that stopped it. Only what is known.
|
|
1124
|
+
def stop_facts(event)
|
|
1125
|
+
case event&.dig(:type)
|
|
1126
|
+
when :turn_failed then { "error_kind" => event[:error_kind]&.to_s, "retryable" => event[:retryable] }.compact
|
|
1127
|
+
when :turn_canceled
|
|
1128
|
+
{ "cancel_reason" => event[:cancellation_reason]&.to_s, "stopped_by" => event[:cancelled_by]&.to_s }.compact
|
|
1129
|
+
else {}
|
|
1130
|
+
end
|
|
1131
|
+
end
|
|
1132
|
+
private :stop_facts
|
|
1133
|
+
|
|
1104
1134
|
# How every turn ends (completed, canceled, failed): the session gets
|
|
1105
1135
|
# what the turn kept, goes idle and records the ending, and the end
|
|
1106
1136
|
# event is announced, as one step of the event log (a snapshot taken
|
|
@@ -1118,7 +1148,7 @@ module Samagotchi
|
|
|
1118
1148
|
kept, event = yield(seconds)
|
|
1119
1149
|
replace_session_messages(turn.session, kept) if kept
|
|
1120
1150
|
turn.session.status = Session::STATUS_IDLE
|
|
1121
|
-
record_last_turn(turn.session, outcome, seconds, turn.origin, limit: exhausted ? turn.limit : nil)
|
|
1151
|
+
record_last_turn(turn.session, outcome, seconds, turn.origin, event, limit: exhausted ? turn.limit : nil)
|
|
1122
1152
|
emit_event(turn.on_event, turn.tag(event))
|
|
1123
1153
|
end
|
|
1124
1154
|
turn.ended = true
|
|
@@ -1339,9 +1369,7 @@ module Samagotchi
|
|
|
1339
1369
|
end
|
|
1340
1370
|
|
|
1341
1371
|
# @return [Session] current session (Engine owns create/resume)
|
|
1342
|
-
|
|
1343
|
-
@session
|
|
1344
|
-
end
|
|
1372
|
+
attr_reader :session
|
|
1345
1373
|
|
|
1346
1374
|
# The kernel's Tools::Peers, following the current session. cancelled?
|
|
1347
1375
|
# is the running turn's cancel, for a tool that waits (delegate_result):
|
|
@@ -1394,9 +1422,7 @@ module Samagotchi
|
|
|
1394
1422
|
end
|
|
1395
1423
|
|
|
1396
1424
|
# @return [IdleRecap, nil] the idle recap detector, or nil when disabled
|
|
1397
|
-
|
|
1398
|
-
@recap
|
|
1399
|
-
end
|
|
1425
|
+
attr_reader :recap
|
|
1400
1426
|
|
|
1401
1427
|
# Write the recap now, before the session is left (IdleRecap#write_now).
|
|
1402
1428
|
# @return [String, nil] the recap written, nil when none was (recap off,
|
|
@@ -2098,6 +2124,7 @@ module Samagotchi
|
|
|
2098
2124
|
config_path = Samagotchi::ConfigFile.global_path
|
|
2099
2125
|
data = Samagotchi::ConfigFile.read_yaml(path: config_path)
|
|
2100
2126
|
return Hooks::Loader.load(data, failures: @guardrail_failures) if data.is_a?(Hash)
|
|
2127
|
+
|
|
2101
2128
|
Hooks::Registry.new
|
|
2102
2129
|
end
|
|
2103
2130
|
|
|
@@ -2120,11 +2147,15 @@ module Samagotchi
|
|
|
2120
2147
|
Hooks::BundleLoader.load(bundle_name: bundle_name, hooks_dir: hooks_dir, metadata: data[:hooks], registry: @hooks,
|
|
2121
2148
|
failures: @guardrail_failures, settings: settings[bundle_name.to_s] || {},
|
|
2122
2149
|
requires_chi: data[:requires_chi])
|
|
2123
|
-
rescue Exception => e
|
|
2150
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- a hook's SyntaxError or exit must not stop chi
|
|
2151
|
+
raise if e.is_a?(SignalException) # Ctrl-C and kill signals are the user's
|
|
2152
|
+
|
|
2124
2153
|
Log.error(:hooks, "bundle_load_failed", echo: "[samagotchi:hooks] bundle '#{bundle_name}' failed to load hooks: #{e.class}: #{e.message}", bundle: bundle_name, error: e.class.name)
|
|
2125
2154
|
end
|
|
2126
2155
|
end
|
|
2127
|
-
rescue Exception => e
|
|
2156
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- a hook's SyntaxError or exit must not stop chi
|
|
2157
|
+
raise if e.is_a?(SignalException) # Ctrl-C and kill signals are the user's
|
|
2158
|
+
|
|
2128
2159
|
Log.error(:hooks, "bundles_load_failed", echo: "[samagotchi:hooks] failed to load bundle hooks: #{e.class}: #{e.message}", error: e.class.name)
|
|
2129
2160
|
end
|
|
2130
2161
|
|
|
@@ -2248,7 +2279,6 @@ module Samagotchi
|
|
|
2248
2279
|
end
|
|
2249
2280
|
private :bundle_settings
|
|
2250
2281
|
|
|
2251
|
-
|
|
2252
2282
|
# The session's current model as a recap target: its host's OpenAI API
|
|
2253
2283
|
# (native llama.cpp hosts serve /v1/chat/completions too), key variable
|
|
2254
2284
|
# and bare model name, as a turn resolves them.
|
|
@@ -15,7 +15,8 @@ module Samagotchi
|
|
|
15
15
|
# SAMAGOTCHI_UPDATE_GEM_FILE=<path to a .gem> (hidden, for smokes) makes that
|
|
16
16
|
# file the newest version and installs it instead.
|
|
17
17
|
class GemUpdate
|
|
18
|
-
Error
|
|
18
|
+
class Error < StandardError
|
|
19
|
+
end
|
|
19
20
|
|
|
20
21
|
LATEST_URL = URI("https://rubygems.org/api/v1/versions/samagotchi/latest.json")
|
|
21
22
|
# Not SAMAGOTCHI_UPDATE_GEM: that is the env form of config update.gem.
|
|
@@ -159,7 +159,7 @@ module Samagotchi
|
|
|
159
159
|
raise Guardrails::Rules::ParseError, "its sha256 differs from the installed one (edited after install? reinstall the bundle)"
|
|
160
160
|
end
|
|
161
161
|
|
|
162
|
-
doc = YAML.
|
|
162
|
+
doc = YAML.safe_load_file(path)
|
|
163
163
|
raise Guardrails::Rules::ParseError, "expected a mapping with rules:" unless doc.is_a?(Hash)
|
|
164
164
|
|
|
165
165
|
loaded.concat(Guardrails::Rules.parse(doc["rules"], source: "bundle #{bundle_name}"))
|
|
@@ -150,13 +150,13 @@ module Samagotchi
|
|
|
150
150
|
return nil unless preview.is_a?(Hash)
|
|
151
151
|
|
|
152
152
|
get = ->(key) { preview[key] || preview[key.to_s] }
|
|
153
|
-
return "would fail: #{get.(:error)}" if get.(:error)
|
|
154
|
-
return "not shown (#{get.(:skipped)})" if get.(:skipped)
|
|
153
|
+
return "would fail: #{get.call(:error)}" if get.call(:error)
|
|
154
|
+
return "not shown (#{get.call(:skipped)})" if get.call(:skipped)
|
|
155
155
|
|
|
156
|
-
added = get.(:added).to_i
|
|
157
|
-
return "new file, #{added} #{added == 1 ? "line" : "lines"}" if get.(:new_file)
|
|
156
|
+
added = get.call(:added).to_i
|
|
157
|
+
return "new file, #{added} #{added == 1 ? "line" : "lines"}" if get.call(:new_file)
|
|
158
158
|
|
|
159
|
-
"+#{added} −#{get.(:removed).to_i}"
|
|
159
|
+
"+#{added} −#{get.call(:removed).to_i}"
|
|
160
160
|
end
|
|
161
161
|
end
|
|
162
162
|
end
|