samagotchi 0.67.0 → 0.69.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/docs/architecture.md +7 -4
- data/docs/cli.md +6 -1
- data/docs/configuration.md +28 -6
- data/docs/guardrails.md +23 -1
- data/docs/hooks.md +4 -0
- data/docs/internals/bundles.md +1 -1
- data/docs/internals/engine.md +73 -17
- data/docs/internals/prompt-caching.md +9 -2
- data/docs/plugins.md +42 -19
- data/docs/releasing.md +14 -6
- data/docs/sessions.md +3 -3
- data/docs/testing.md +8 -2
- data/lib/samagotchi/bootstrap/probe.rb +1 -3
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/config.rb +0 -19
- data/lib/samagotchi/effective_model.rb +283 -0
- data/lib/samagotchi/engine.rb +216 -360
- data/lib/samagotchi/guardrails/targets.rb +9 -2
- data/lib/samagotchi/host_config.rb +1 -29
- data/lib/samagotchi/host_model.rb +0 -7
- data/lib/samagotchi/kernel_loop.rb +24 -7
- data/lib/samagotchi/level_setting_command.rb +99 -0
- data/lib/samagotchi/llm/errors.rb +47 -1
- data/lib/samagotchi/llm/http.rb +20 -2
- data/lib/samagotchi/llm_context_stale.rb +6 -0
- data/lib/samagotchi/llm_context_strategy.rb +5 -10
- data/lib/samagotchi/memory_bundle/manifest.rb +3 -0
- data/lib/samagotchi/model_price.rb +2 -4
- data/lib/samagotchi/reasoning_history_command.rb +29 -90
- data/lib/samagotchi/session.rb +22 -8
- data/lib/samagotchi/session_metrics.rb +4 -1
- data/lib/samagotchi/session_setting.rb +86 -0
- data/lib/samagotchi/system_prompt.rb +19 -3
- data/lib/samagotchi/terminal_ui/formatting.rb +5 -2
- data/lib/samagotchi/thinking_command.rb +24 -76
- data/lib/samagotchi/thinking_notices.rb +169 -0
- data/lib/samagotchi/tool_activity.rb +3 -0
- data/lib/samagotchi/tool_row_fields.rb +4 -2
- data/lib/samagotchi/tool_runner.rb +24 -3
- data/lib/samagotchi/toolkit.rb +27 -14
- data/lib/samagotchi/toolkit_binding.rb +8 -2
- data/lib/samagotchi/toolkit_command.rb +20 -4
- data/lib/samagotchi/tools/builtin_calls.rb +45 -1
- data/lib/samagotchi/tools/registry.rb +95 -8
- data/lib/samagotchi/tools/tool_index.rb +223 -72
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +69 -93
- data/lib/samagotchi/web/message_parts.rb +5 -2
- data/lib/samagotchi/web/public/activity.js +3 -1
- data/lib/samagotchi/web/public/app.js +196 -526
- data/lib/samagotchi/web/public/bubbles.js +291 -0
- data/lib/samagotchi/web/public/cards_place.js +184 -0
- data/lib/samagotchi/web/public/chains.js +19 -2
- data/lib/samagotchi/web/public/chip_popover.js +111 -0
- data/lib/samagotchi/web/public/command_list.js +12 -10
- data/lib/samagotchi/web/public/families_view.js +15 -0
- data/lib/samagotchi/web/public/index.html +21 -11
- data/lib/samagotchi/web/public/info_bar.js +3 -3
- data/lib/samagotchi/web/public/info_bar_model.js +110 -0
- data/lib/samagotchi/web/public/init_row.js +21 -10
- data/lib/samagotchi/web/public/level_chip.js +29 -80
- data/lib/samagotchi/web/public/lists_view.js +64 -9
- data/lib/samagotchi/web/public/llm_context_chip.js +36 -105
- data/lib/samagotchi/web/public/model_picker.js +47 -28
- data/lib/samagotchi/web/public/ref_scan.js +1 -7
- data/lib/samagotchi/web/public/refs.js +13 -0
- data/lib/samagotchi/web/public/session_state.js +5 -0
- data/lib/samagotchi/web/public/stage_frame.js +179 -0
- data/lib/samagotchi/web/public/stage_model.js +33 -0
- data/lib/samagotchi/web/public/stage_slots.js +130 -0
- data/lib/samagotchi/web/public/stage_view.js +56 -335
- data/lib/samagotchi/web/public/stage_watch.js +65 -0
- data/lib/samagotchi/web/public/stream_handlers.js +5 -2
- data/lib/samagotchi/web/public/timing.js +3 -1
- data/lib/samagotchi/web/public/toast_place.js +16 -0
- data/lib/samagotchi/web/public/toolkit_chip.js +11 -4
- data/lib/samagotchi/web/public/turn_html.js +4 -2
- data/lib/samagotchi/web/public/vendor/directive-helpers.js +8 -0
- data/lib/samagotchi/web/public/vendor/directives/repeat.js +9 -0
- data/lib/samagotchi/worker.rb +11 -16
- metadata +15 -1
data/lib/samagotchi/web/app.rb
CHANGED
|
@@ -26,8 +26,8 @@ require_relative "../host_registry"
|
|
|
26
26
|
require_relative "../llm_context_override"
|
|
27
27
|
require_relative "../model_catalog"
|
|
28
28
|
require_relative "../model_profile"
|
|
29
|
+
require_relative "../session_setting"
|
|
29
30
|
require_relative "../thinking"
|
|
30
|
-
require_relative "../reasoning_history"
|
|
31
31
|
require_relative "../sampling_settings"
|
|
32
32
|
require_relative "../served_model"
|
|
33
33
|
require_relative "../project_scope"
|
|
@@ -557,52 +557,6 @@ module Samagotchi
|
|
|
557
557
|
nil
|
|
558
558
|
end
|
|
559
559
|
|
|
560
|
-
# A session's LLM context strategy, apply rule and budget, each with
|
|
561
|
-
# where it came from (LLMContextStrategy::Explained#summary), as its
|
|
562
|
-
# worker would resolve them; nil when they can't be.
|
|
563
|
-
# (LLMContextStrategy.for_session, shared with the ctx % the session
|
|
564
|
-
# lists count.)
|
|
565
|
-
def llm_context_for(session)
|
|
566
|
-
LLMContextStrategy.for_session(session, registry: host_registry)
|
|
567
|
-
end
|
|
568
|
-
|
|
569
|
-
# A session's toolkit as its file names it ({name:, label:}), nil on
|
|
570
|
-
# full: without a worker nothing resolves it (a bundle's kits load in
|
|
571
|
-
# the worker), so the tools it leaves out aren't known here.
|
|
572
|
-
def toolkit_for(session)
|
|
573
|
-
toolkit = session.toolkit
|
|
574
|
-
toolkit && !toolkit.full? ? { name: toolkit.name, label: toolkit.label } : nil
|
|
575
|
-
end
|
|
576
|
-
|
|
577
|
-
# A session's thinking level with where it came from and its own
|
|
578
|
-
# (SettingExplained#summary), as its worker would resolve it; nil
|
|
579
|
-
# when it can't be. Config and the registry only: no network.
|
|
580
|
-
def thinking_for(session)
|
|
581
|
-
registry = host_registry
|
|
582
|
-
name = session.model_name.to_s.strip.empty? ? ModelProfile.required_model_name(nil) : session.model_name
|
|
583
|
-
target = registry.resolve(name)
|
|
584
|
-
names = registry.lookup_names(session.model_typed || name, resolved: name, target: target)
|
|
585
|
-
Thinking.explain(target, names: names, session: session.thinking).summary
|
|
586
|
-
rescue StandardError
|
|
587
|
-
nil
|
|
588
|
-
end
|
|
589
|
-
|
|
590
|
-
# A session's reasoning_history level with where it came from and its
|
|
591
|
-
# own (SettingExplained#summary), as its worker would resolve it; nil
|
|
592
|
-
# on a native host (it ignores the level) or when it can't be. Config
|
|
593
|
-
# and the registry only: no network.
|
|
594
|
-
def reasoning_history_for(session)
|
|
595
|
-
registry = host_registry
|
|
596
|
-
name = session.model_name.to_s.strip.empty? ? ModelProfile.required_model_name(nil) : session.model_name
|
|
597
|
-
target = registry.resolve(name)
|
|
598
|
-
return nil unless target.entry&.chat?
|
|
599
|
-
|
|
600
|
-
names = registry.lookup_names(session.model_typed || name, resolved: name, target: target)
|
|
601
|
-
ReasoningHistory.explain(target, names: names, session: session.reasoning_history).summary
|
|
602
|
-
rescue StandardError
|
|
603
|
-
nil
|
|
604
|
-
end
|
|
605
|
-
|
|
606
560
|
# Built again when config.yml's hosts: changed (compared as read: the
|
|
607
561
|
# YAML read is mtime-cached), so the model picker lists a host added
|
|
608
562
|
# while chi web runs. An injected registry (specs) stays.
|
|
@@ -828,46 +782,15 @@ module Samagotchi
|
|
|
828
782
|
live = bridge_get_json(id, "snapshot")
|
|
829
783
|
turn_snapshot = live && live["snapshot"]
|
|
830
784
|
snapshot = live && live["session_state_snapshot"]
|
|
831
|
-
last_event_seq = snapshot
|
|
785
|
+
last_event_seq = live_or_saved(snapshot, "event_seq") { bridge_event_seq(id) }
|
|
832
786
|
current_turn = turn_snapshot && turn_snapshot["current_turn"]
|
|
833
787
|
owner = session_owner(id)
|
|
834
|
-
|
|
835
|
-
# dead worker saved can't be answered (its turn is gone), nor a chi
|
|
836
|
-
# REPL's (it shares nothing with the web).
|
|
837
|
-
# A question between turns (the step-limit one) is the snapshot's own.
|
|
838
|
-
pending = if turn_snapshot
|
|
839
|
-
current_turn ? current_turn["pending_question"] : turn_snapshot["pending_question"]
|
|
840
|
-
elsif owner&.worker?
|
|
841
|
-
session.pending_question
|
|
842
|
-
end
|
|
843
|
-
# A /model in the worker changes it before the file catches up.
|
|
844
|
-
session.model_name = snapshot["model_name"] if snapshot && !snapshot["model_name"].to_s.empty?
|
|
845
|
-
session_json = session_to_json(session, status: displayed_status(session, snapshot, owner: owner), owner: owner)
|
|
788
|
+
pending = shown_pending_question(session, turn_snapshot, owner)
|
|
846
789
|
# What the worker's server said it served for that model (after a turn);
|
|
847
790
|
# without a worker, what the session's analytics.json last saved.
|
|
848
791
|
persisted = snapshot ? nil : read_analytics(id)
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
served_expected_by: snapshot["served_expected_by"])
|
|
852
|
-
# The model notes its prompt carries now (a /model there changes them).
|
|
853
|
-
session_json = session_json.merge(prompt_notes: snapshot["prompt_notes"]) if snapshot["prompt_notes"].is_a?(Array)
|
|
854
|
-
else
|
|
855
|
-
session_json = session_json.merge(saved_served_model(session, persisted))
|
|
856
|
-
end
|
|
857
|
-
# The LLM context strategy the next turn runs under (the info bar's
|
|
858
|
-
# chip): the worker's, else worked out here from the session file.
|
|
859
|
-
session_json = session_json.merge(llm_context: (snapshot && snapshot["llm_context"]) || llm_context_for(session))
|
|
860
|
-
# The thinking level the next turn runs at (the think chip), the same way.
|
|
861
|
-
session_json = session_json.merge(thinking: (snapshot && snapshot["thinking"]) || thinking_for(session))
|
|
862
|
-
# Its reasoning history (the reasoning chip): the worker's (nil on a
|
|
863
|
-
# native host), else the server's reading (an older worker, none).
|
|
864
|
-
history = snapshot&.key?("reasoning_history") ? snapshot["reasoning_history"] : reasoning_history_for(session)
|
|
865
|
-
session_json = session_json.merge(reasoning_history: history)
|
|
866
|
-
# Its toolkit when it isn't full (the tools chip): the worker's, which
|
|
867
|
-
# resolved it, else the name saved in the session file.
|
|
868
|
-
toolkit = snapshot&.key?("toolkit") ? snapshot["toolkit"] : toolkit_for(session)
|
|
869
|
-
session_json = session_json.merge(toolkit: toolkit)
|
|
870
|
-
raw_messages = turn_snapshot ? turn_snapshot["messages"] : session.messages
|
|
792
|
+
session_json = shown_session_json(session, snapshot, owner, persisted)
|
|
793
|
+
raw_messages = live_or_saved(turn_snapshot, "messages") { session.messages }
|
|
871
794
|
timing = timing_payload(id, live_metrics: snapshot && snapshot["metrics"], persisted: persisted)
|
|
872
795
|
json_response(200, {
|
|
873
796
|
session: session_json,
|
|
@@ -876,17 +799,17 @@ module Samagotchi
|
|
|
876
799
|
cwd: session.working_directory),
|
|
877
800
|
failed_turn: failed_turn_for(session, raw_messages),
|
|
878
801
|
current_turn: current_turn,
|
|
879
|
-
queued: turn_snapshot
|
|
802
|
+
queued: snapshot_list(turn_snapshot, "queued"),
|
|
880
803
|
# Commands waiting for the running turn's end (their bubbles).
|
|
881
|
-
queued_commands: turn_snapshot
|
|
804
|
+
queued_commands: snapshot_list(turn_snapshot, "queued_commands"),
|
|
882
805
|
recap: turn_snapshot && turn_snapshot["recap"],
|
|
883
806
|
saved_recap: saved_recap_for(id, session, turn_snapshot),
|
|
884
807
|
continue_offer: turn_snapshot && turn_snapshot["continue_offer"],
|
|
885
808
|
guardrail_warning: turn_snapshot && turn_snapshot["guardrail_warning"],
|
|
886
809
|
plugin_warning: turn_snapshot && turn_snapshot["plugin_warning"],
|
|
887
810
|
# Plugins' slow setup still running (chi.init): the page's init row.
|
|
888
|
-
init_tasks: turn_snapshot
|
|
889
|
-
cards: cards_for_display(turn_snapshot
|
|
811
|
+
init_tasks: snapshot_list(turn_snapshot, "init_tasks"),
|
|
812
|
+
cards: cards_for_display(live_or_saved(turn_snapshot, "cards") { saved_cards(id) }),
|
|
890
813
|
# The composer's / autocomplete; the built-ins until a worker
|
|
891
814
|
# names its plugins' too.
|
|
892
815
|
commands: turn_snapshot&.fetch("commands", nil) || SessionCommands.builtin_registry.listing,
|
|
@@ -903,6 +826,58 @@ module Samagotchi
|
|
|
903
826
|
error_response(404, "not_found", e.message)
|
|
904
827
|
end
|
|
905
828
|
|
|
829
|
+
# A list in the worker's turn +snapshot+ (queued prompts, commands,
|
|
830
|
+
# init tasks); [] without a worker.
|
|
831
|
+
def snapshot_list(snapshot, key) = Array(snapshot&.[](key))
|
|
832
|
+
|
|
833
|
+
# A live worker's +key+ as its reading (+live+: a snapshot, a tail)
|
|
834
|
+
# says it, nil included; without a worker (+live+ nil) the block's,
|
|
835
|
+
# the server's own reading of the saved session. A nil value from a
|
|
836
|
+
# worker is not a missing one (contrast SessionSetting#snapshot_value's
|
|
837
|
+
# :or).
|
|
838
|
+
def live_or_saved(live, key)
|
|
839
|
+
live ? live[key] : yield
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
# The file's question only while a worker owns the session: one a
|
|
843
|
+
# dead worker saved can't be answered (its turn is gone), nor a chi
|
|
844
|
+
# REPL's (it shares nothing with the web).
|
|
845
|
+
# A question between turns (the step-limit one) is the snapshot's own.
|
|
846
|
+
def shown_pending_question(session, turn_snapshot, owner)
|
|
847
|
+
if turn_snapshot
|
|
848
|
+
current_turn = turn_snapshot["current_turn"]
|
|
849
|
+
current_turn ? current_turn["pending_question"] : turn_snapshot["pending_question"]
|
|
850
|
+
elsif owner&.worker?
|
|
851
|
+
session.pending_question
|
|
852
|
+
end
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
# The full show's session part: the file's, with what a live worker's
|
|
856
|
+
# state +snapshot+ knows first (its model, status, served model,
|
|
857
|
+
# prompt notes and chips); without one, the served model from
|
|
858
|
+
# +persisted+ (analytics.json). Sets a worker's model on +session+:
|
|
859
|
+
# a /model in the worker changes it before the file catches up.
|
|
860
|
+
def shown_session_json(session, snapshot, owner, persisted)
|
|
861
|
+
session.model_name = snapshot["model_name"] if snapshot && !snapshot["model_name"].to_s.empty?
|
|
862
|
+
session_json = session_to_json(session, status: displayed_status(session, snapshot, owner: owner), owner: owner)
|
|
863
|
+
if snapshot
|
|
864
|
+
session_json = session_json.merge(served_model: snapshot["served_model"], served_model_for: snapshot["served_model_for"],
|
|
865
|
+
served_expected_by: snapshot["served_expected_by"])
|
|
866
|
+
# The model notes its prompt carries now (a /model there changes them).
|
|
867
|
+
session_json = session_json.merge(prompt_notes: snapshot["prompt_notes"]) if snapshot["prompt_notes"].is_a?(Array)
|
|
868
|
+
else
|
|
869
|
+
session_json = session_json.merge(saved_served_model(session, persisted))
|
|
870
|
+
end
|
|
871
|
+
# The per-session settings' chips (the info bar's llm ctx, think,
|
|
872
|
+
# reasoning and tools): the worker's, else worked out here from the
|
|
873
|
+
# session file (SessionSetting#snapshot_value, #saved_summary).
|
|
874
|
+
SessionSetting::ALL.each do |setting|
|
|
875
|
+
chip = setting.snapshot_value(snapshot) { setting.saved_summary(session, registry: host_registry) }
|
|
876
|
+
session_json = session_json.merge(setting.key => chip)
|
|
877
|
+
end
|
|
878
|
+
session_json
|
|
879
|
+
end
|
|
880
|
+
|
|
906
881
|
# ?cards=1: the page's re-read when a card arrives, for its rendered
|
|
907
882
|
# body (the stream carries the card's plain text only). The worker's
|
|
908
883
|
# cards from its light GET tail, else the saved ones; the session file
|
|
@@ -911,7 +886,7 @@ module Samagotchi
|
|
|
911
886
|
return error_response(404, "not_found", "Session not found: #{id}") unless session_exists?(id)
|
|
912
887
|
|
|
913
888
|
tail = live_tail(id)
|
|
914
|
-
json_response(200, { cards: cards_for_display(tail
|
|
889
|
+
json_response(200, { cards: cards_for_display(live_or_saved(tail, "cards") { saved_cards(id) }) })
|
|
915
890
|
end
|
|
916
891
|
|
|
917
892
|
# ?tail=1: the page's re-read at the end of a turn wants the final
|
|
@@ -931,17 +906,18 @@ module Samagotchi
|
|
|
931
906
|
turn_id = nil if turn_id.empty?
|
|
932
907
|
tail = live_tail(id, turn_id: turn_id)
|
|
933
908
|
state = tail && tail["session_state_snapshot"]
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
909
|
+
state = nil unless state.is_a?(Hash)
|
|
910
|
+
if state
|
|
911
|
+
status, used, notes = state.values_at("status", "used_memory_names", "prompt_notes")
|
|
937
912
|
messages = last_assistant_for_display([tail["answer"]].compact)
|
|
938
913
|
else
|
|
939
914
|
session = @session_class.load(id, state_dir: default_state_dir)
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
915
|
+
status = displayed_status(session)
|
|
916
|
+
used = session.used_memory_names
|
|
917
|
+
notes = session.prompt_notes.map(&:to_h)
|
|
943
918
|
messages = last_assistant_for_display(session.messages, turn_id: turn_id, cwd: session.working_directory)
|
|
944
919
|
end
|
|
920
|
+
session_json = { id: id, status: status, used_memory_names: Array(used), prompt_notes: Array(notes) }
|
|
945
921
|
live_metrics = state && state["metrics"]
|
|
946
922
|
# Without recent=1: a tab opened before this release (it replaces its
|
|
947
923
|
# timing with the reply's). Remove this branch after the next release.
|
|
@@ -234,12 +234,15 @@ module Samagotchi
|
|
|
234
234
|
# +tool_id+: its output's ToolIds id, with its ✂ mark in +marks+.
|
|
235
235
|
def tool_part(call, output, registry, shown = nil, images = nil, label: nil, diff: nil, refs: nil, cwd: nil,
|
|
236
236
|
tool_id: nil, marks: {})
|
|
237
|
+
# A built-in reached through call_tool reloads as that built-in's
|
|
238
|
+
# row, with via (its saved params and label are the built-in's).
|
|
239
|
+
call = Tools::BuiltinCalls.unwrap_saved(call, output)
|
|
237
240
|
name = call[:name].to_s
|
|
238
241
|
params = shown.is_a?(String) ? shown : ToolActivity.tool_activity_params(name, call, registry: registry)
|
|
239
242
|
part = { tool: name, params: params.to_s }
|
|
240
|
-
# Its row fields (ToolRowFields: title, called_as, view, ref), built from the saved
|
|
243
|
+
# Its row fields (ToolRowFields: title, called_as, via, view, ref), built from the saved
|
|
241
244
|
# call (never saved themselves), so older sessions get them too. The
|
|
242
|
-
# saved call is rebuilt by BuiltinCalls, which sets called_as again.
|
|
245
|
+
# saved call is rebuilt by BuiltinCalls, which sets called_as and via again.
|
|
243
246
|
part.merge!(ToolRowFields.for(name, call, cwd: cwd))
|
|
244
247
|
part[:label] = label if label.is_a?(String) && !label.empty?
|
|
245
248
|
unless output.nil?
|
|
@@ -40,6 +40,8 @@ export function newActivity() {
|
|
|
40
40
|
// it in place of the params when there is one.
|
|
41
41
|
// called_as: the name the model called the tool by when it was an
|
|
42
42
|
// alias's ("bash" run as execute); the row says so.
|
|
43
|
+
// via: the tool it was reached through ("call_tool": a built-in the
|
|
44
|
+
// session's toolkit doesn't declare); the row says so.
|
|
43
45
|
// view: the call's full arguments for the expanded row and the hovers
|
|
44
46
|
// (the server's ToolView: an execute/task_create's command, its cwd).
|
|
45
47
|
// ref: what the row traces to (the server's FileRef: {kind: "file",
|
|
@@ -48,7 +50,7 @@ export function newActivity() {
|
|
|
48
50
|
// refs: a plugin tool's refs (the server's RowRefs: [{kind, target,
|
|
49
51
|
// label?, actions}]): the first on the row's title, the others in its
|
|
50
52
|
// menu (refs.js bindRowRefs).
|
|
51
|
-
export const ACTIVITY_FIELDS = Object.freeze(["title", "called_as"]);
|
|
53
|
+
export const ACTIVITY_FIELDS = Object.freeze(["title", "called_as", "via"]);
|
|
52
54
|
export const EVENT_FIELDS = Object.freeze(["view", "ref", "refs"]);
|
|
53
55
|
const ROW_FIELDS = Object.freeze([...ACTIVITY_FIELDS, ...EVENT_FIELDS]);
|
|
54
56
|
|