samagotchi 0.76.0 → 0.78.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 +51 -1
- data/docs/cli.md +2 -1
- data/docs/configuration.md +10 -6
- data/docs/internals/anthropic.md +73 -5
- data/docs/internals/engine.md +2 -1
- data/docs/internals/prompt-caching.md +3 -1
- data/docs/plugins.md +42 -0
- data/docs/sessions.md +1 -0
- data/lib/samagotchi/answer_command.rb +5 -5
- data/lib/samagotchi/bridge.rb +3 -3
- data/lib/samagotchi/broadcast/recipients.rb +2 -2
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +12 -10
- data/lib/samagotchi/bundles/source-links/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/child_move.rb +2 -2
- data/lib/samagotchi/child_reports.rb +3 -3
- data/lib/samagotchi/children_status.rb +2 -2
- data/lib/samagotchi/engine.rb +21 -12
- data/lib/samagotchi/guardrails/parent_approvals.rb +4 -4
- data/lib/samagotchi/guardrails/parent_continue.rb +2 -2
- data/lib/samagotchi/llm/anthropic_assembly.rb +14 -0
- data/lib/samagotchi/llm/anthropic_messages.rb +210 -13
- data/lib/samagotchi/llm/anthropic_wire.rb +22 -0
- data/lib/samagotchi/llm/chat_loop.rb +49 -2
- data/lib/samagotchi/llm/errors.rb +6 -4
- data/lib/samagotchi/llm/http.rb +2 -1
- data/lib/samagotchi/llm/openai_chat.rb +12 -2
- data/lib/samagotchi/memory_bundle/builder.rb +2 -0
- data/lib/samagotchi/message_parts.rb +272 -0
- data/lib/samagotchi/note_delivery.rb +2 -2
- data/lib/samagotchi/parent_report.rb +30 -22
- data/lib/samagotchi/pending_question.rb +49 -0
- data/lib/samagotchi/plugin/sessions.rb +2 -2
- data/lib/samagotchi/question_desk.rb +39 -32
- data/lib/samagotchi/relay.rb +8 -9
- data/lib/samagotchi/relay_verifier.rb +2 -2
- data/lib/samagotchi/relay_watcher.rb +2 -2
- data/lib/samagotchi/reply_wait.rb +6 -7
- data/lib/samagotchi/row_refs.rb +1 -1
- data/lib/samagotchi/session.rb +22 -14
- data/lib/samagotchi/session_commands.rb +4 -1
- data/lib/samagotchi/session_manager.rb +0 -1
- data/lib/samagotchi/session_metrics/totals.rb +4 -3
- data/lib/samagotchi/session_metrics.rb +9 -0
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +3 -2
- data/lib/samagotchi/terminal_ui/attached_loop.rb +2 -2
- data/lib/samagotchi/terminal_ui/formatting.rb +6 -106
- data/lib/samagotchi/terminal_ui/model_segments_text.rb +118 -0
- data/lib/samagotchi/terminal_ui.rb +7 -5
- data/lib/samagotchi/tool_output_cut.rb +1 -1
- data/lib/samagotchi/tool_response.rb +13 -13
- data/lib/samagotchi/tool_row_fields.rb +1 -1
- data/lib/samagotchi/tool_runner.rb +16 -9
- data/lib/samagotchi/tools/delegate.rb +2 -2
- data/lib/samagotchi/tools/delegate_relay.rb +7 -7
- data/lib/samagotchi/tools/delegate_result.rb +2 -2
- data/lib/samagotchi/tools/delegate_wait.rb +1 -1
- data/lib/samagotchi/tools/list_sessions.rb +2 -2
- data/lib/samagotchi/tools/web_fetch.rb +3 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +2 -2
- data/lib/samagotchi/web/public/app.js +234 -74
- data/lib/samagotchi/web/public/composer_fold.js +58 -0
- data/lib/samagotchi/web/public/composer_size.js +10 -0
- data/lib/samagotchi/web/public/ctx.js +12 -1
- data/lib/samagotchi/web/public/index.html +29 -4
- data/lib/samagotchi/web/public/markdown_place.js +53 -0
- data/lib/samagotchi/web/public/stream_handlers.js +7 -3
- data/lib/samagotchi/web/public/toasts.js +69 -0
- data/lib/samagotchi/worker.rb +1 -0
- metadata +7 -2
- data/lib/samagotchi/web/message_parts.rb +0 -274
|
@@ -42,8 +42,8 @@ require_relative "children_status"
|
|
|
42
42
|
require_relative "log"
|
|
43
43
|
|
|
44
44
|
module Samagotchi
|
|
45
|
-
# Loaded on first use:
|
|
46
|
-
#
|
|
45
|
+
# Loaded on first use: this autoload keeps SessionManager and its ~80
|
|
46
|
+
# files unloaded until the delegate/session path needs them.
|
|
47
47
|
autoload :SessionManager, File.expand_path("session_manager", __dir__)
|
|
48
48
|
|
|
49
49
|
# The in-process terminal front end (`chi --no-shared`, `chi scratch`,
|
|
@@ -873,6 +873,7 @@ module Samagotchi
|
|
|
873
873
|
end
|
|
874
874
|
if command
|
|
875
875
|
show_command_result(command)
|
|
876
|
+
refresh_status_row(clear_unknown_context: true) if command.changed.include?(:model)
|
|
876
877
|
persist_recent_history(input) if command.shell
|
|
877
878
|
return
|
|
878
879
|
end
|
|
@@ -1400,9 +1401,10 @@ module Samagotchi
|
|
|
1400
1401
|
|
|
1401
1402
|
# The status row under the prompt, from the Engine's state (what a
|
|
1402
1403
|
# worker hands an attached TUI when it joins): drawn when it changed.
|
|
1403
|
-
def refresh_status_row
|
|
1404
|
+
def refresh_status_row(clear_unknown_context: false)
|
|
1404
1405
|
state = @engine.session_state_snapshot.merge(model_name: @effective_model_name)
|
|
1405
1406
|
@status_row.take_state(state, default_model: @default_model_name)
|
|
1407
|
+
@status_row.update(context: nil) if clear_unknown_context && state.key?(:context_status) && state[:context_status].nil?
|
|
1406
1408
|
refresh_children_status
|
|
1407
1409
|
end
|
|
1408
1410
|
|
|
@@ -1463,13 +1465,13 @@ module Samagotchi
|
|
|
1463
1465
|
def drain_pending_question?
|
|
1464
1466
|
pq = @pending_question_event
|
|
1465
1467
|
# Also check Engine's persisted pending (covers resume)
|
|
1466
|
-
pq ||= @engine.pending_question
|
|
1468
|
+
pq ||= @engine.pending_question&.to_h
|
|
1467
1469
|
return false unless pq
|
|
1468
1470
|
|
|
1469
1471
|
# If the pending no longer matches the engine's current pending (stale
|
|
1470
1472
|
# event after sync handler already answered and cleared), discard.
|
|
1471
1473
|
current = @engine.pending_question
|
|
1472
|
-
if current && (pq[:id] || pq["id"]).to_s !=
|
|
1474
|
+
if current && (pq[:id] || pq["id"]).to_s != current.id.to_s
|
|
1473
1475
|
@pending_question_event = nil
|
|
1474
1476
|
return false
|
|
1475
1477
|
end
|
|
@@ -38,7 +38,7 @@ module Samagotchi
|
|
|
38
38
|
HEAD_CUT = /\[cut: \d+ of \d+ chars; read it in parts\]\z/
|
|
39
39
|
|
|
40
40
|
# What a web row keeps of a tool's output, on a reload
|
|
41
|
-
# (
|
|
41
|
+
# (MessageParts) and a mid-turn join (Bridge::TurnAccumulator): the
|
|
42
42
|
# row shows 300 chars and the rest on hover. A longer one keeps its head
|
|
43
43
|
# and tail (preview).
|
|
44
44
|
WEB_PREVIEW_CHARS = 2000
|
|
@@ -29,7 +29,7 @@ module Samagotchi
|
|
|
29
29
|
# Runs +calls+ in order (ToolRunner#run each), yielding each run and its
|
|
30
30
|
# index as it finishes. +emit+ takes the dispatch events, the runner's
|
|
31
31
|
# own go to +on_stream_event+.
|
|
32
|
-
# @return [Array<
|
|
32
|
+
# @return [Array<ToolRun>] the runs
|
|
33
33
|
def run_batch(runner, calls, iteration:, emit:, on_stream_event:, cap:)
|
|
34
34
|
emit.call({ type: :tool_dispatch_started, iteration: iteration, call_count: calls.length })
|
|
35
35
|
runs = calls.each_with_index.map do |call, index|
|
|
@@ -45,7 +45,7 @@ module Samagotchi
|
|
|
45
45
|
# The runs' activity, for the turn's tool list (none for a call whose
|
|
46
46
|
# dispatcher raised).
|
|
47
47
|
def activities(runs)
|
|
48
|
-
runs.filter_map
|
|
48
|
+
runs.filter_map(&:activity)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# The native loop's entry for a batch: the capped outputs joined, every image
|
|
@@ -53,14 +53,14 @@ module Samagotchi
|
|
|
53
53
|
# each on its own tool row), and the saved-only fields one per call:
|
|
54
54
|
# +ids+ (ToolIds.next_ids) as tool_ids.
|
|
55
55
|
def joined(runs, ids:)
|
|
56
|
-
images = runs.flat_map { |run| Array(run
|
|
57
|
-
entry = { role: "tool_response", content: runs.map
|
|
56
|
+
images = runs.flat_map { |run| Array(run.images) }
|
|
57
|
+
entry = { role: "tool_response", content: runs.map(&:capped_output).join(SEPARATOR) }
|
|
58
58
|
unless images.empty?
|
|
59
59
|
entry[:images] = images
|
|
60
|
-
entry[:image_counts] = runs.map { |run| Array(run
|
|
60
|
+
entry[:image_counts] = runs.map { |run| Array(run.images).size }
|
|
61
61
|
end
|
|
62
|
-
saved = { tool_params: runs.map
|
|
63
|
-
tool_diffs: runs.map
|
|
62
|
+
saved = { tool_params: runs.map(&:shown_params), tool_labels: runs.map(&:shown_label),
|
|
63
|
+
tool_diffs: runs.map(&:diff), tool_refs: runs.map(&:refs) }
|
|
64
64
|
saved.each { |key, values| entry[key] = values if values.any? }
|
|
65
65
|
entry[:tool_ids] = ids
|
|
66
66
|
entry
|
|
@@ -70,12 +70,12 @@ module Samagotchi
|
|
|
70
70
|
# call by +tool_call_id+, and its chi-owned id (a one-id +ids+, as
|
|
71
71
|
# #joined's tool_ids).
|
|
72
72
|
def single(run, tool_call_id:, ids:)
|
|
73
|
-
entry = { role: "tool_response", content: run
|
|
74
|
-
entry[:images] = run
|
|
75
|
-
entry[:tool_params] = run
|
|
76
|
-
entry[:tool_labels] = run
|
|
77
|
-
entry[:tool_diffs] = run
|
|
78
|
-
entry[:tool_refs] = run
|
|
73
|
+
entry = { role: "tool_response", content: run.capped_output, tool_call_id: tool_call_id }
|
|
74
|
+
entry[:images] = run.images if run.images&.any?
|
|
75
|
+
entry[:tool_params] = run.shown_params if run.shown_params
|
|
76
|
+
entry[:tool_labels] = run.shown_label if run.shown_label
|
|
77
|
+
entry[:tool_diffs] = run.diff if run.diff
|
|
78
|
+
entry[:tool_refs] = run.refs if run.refs
|
|
79
79
|
entry[:tool_ids] = ids
|
|
80
80
|
entry
|
|
81
81
|
end
|
|
@@ -7,7 +7,7 @@ require_relative "file_ref"
|
|
|
7
7
|
module Samagotchi
|
|
8
8
|
# The fields a tool row shows beyond its params, built from the call in
|
|
9
9
|
# one place for every path: the live tool_call_started (ToolRunner#run)
|
|
10
|
-
# and a reload from the saved call (
|
|
10
|
+
# and a reload from the saved call (MessageParts#tool_part). The
|
|
11
11
|
# bridge's snapshot and replay (Bridge::TurnAccumulator) copy them by
|
|
12
12
|
# these lists, so a new field is added here and travels everywhere.
|
|
13
13
|
#
|
|
@@ -15,6 +15,17 @@ require_relative "memory_bundle/index_sync"
|
|
|
15
15
|
require_relative "memory_bundle/index_size"
|
|
16
16
|
|
|
17
17
|
module Samagotchi
|
|
18
|
+
# One call's result from ToolRunner#run, read by ToolResponse (the loops'
|
|
19
|
+
# tool_response entries) and the loops' activity list. Fields a call didn't
|
|
20
|
+
# produce (images, shown_params, shown_label, refs, diff, output_file) are nil.
|
|
21
|
+
ToolRun = Data.define(:output, :capped_output, :truncated, :activity, :images, :shown_params, :shown_label, :refs, :diff,
|
|
22
|
+
:output_file) do
|
|
23
|
+
def initialize(output: nil, capped_output: nil, truncated: nil, activity: nil, images: nil, shown_params: nil,
|
|
24
|
+
shown_label: nil, refs: nil, diff: nil, output_file: nil)
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
18
29
|
# The single per-call path both loops use: the tool_call_started and
|
|
19
30
|
# tool_call_completed events, the guardrail gate (before_tool_call hooks
|
|
20
31
|
# and their veto), dispatch through KernelLoop, the after_tool_call hook
|
|
@@ -40,7 +51,7 @@ module Samagotchi
|
|
|
40
51
|
end
|
|
41
52
|
|
|
42
53
|
# @param call_index [Integer] 1-based position of the call in its batch
|
|
43
|
-
# @return [
|
|
54
|
+
# @return [ToolRun] output:, capped_output:, truncated:, activity:,
|
|
44
55
|
# output_file: (the saved whole output of a cut execute result),
|
|
45
56
|
# images: (refs) when the tool returned images the model gets to see, and
|
|
46
57
|
# shown_params: the params line the live row showed, and shown_label:
|
|
@@ -135,14 +146,10 @@ module Samagotchi
|
|
|
135
146
|
fire(:after_tool_call, { type: :after_tool_call, iteration: iteration, tool: call[:name], output: capped,
|
|
136
147
|
status: status || "error", call: call.dup, elapsed_ms: elapsed_ms, result: tool_text })
|
|
137
148
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
run[:refs] = refs unless refs.empty?
|
|
143
|
-
run[:diff] = diff if diff
|
|
144
|
-
run[:output_file] = output_file if output_file
|
|
145
|
-
run
|
|
149
|
+
ToolRun.new(output: output, capped_output: capped, truncated: truncated, activity: result[:activity],
|
|
150
|
+
images: images&.any? ? images : nil,
|
|
151
|
+
shown_params: params && plugin_tool?(call[:name]) ? params : nil,
|
|
152
|
+
shown_label: label, refs: refs.empty? ? nil : refs, diff: diff, output_file: output_file)
|
|
146
153
|
end
|
|
147
154
|
|
|
148
155
|
private
|
|
@@ -10,8 +10,8 @@ require_relative "delegate_wait"
|
|
|
10
10
|
require_relative "../child_reports"
|
|
11
11
|
|
|
12
12
|
module Samagotchi
|
|
13
|
-
# Loaded on first use:
|
|
14
|
-
#
|
|
13
|
+
# Loaded on first use: this autoload keeps SessionManager and its ~80
|
|
14
|
+
# files unloaded until the delegate/session path needs them.
|
|
15
15
|
autoload :SessionManager, File.expand_path("../session_manager", __dir__)
|
|
16
16
|
|
|
17
17
|
module Tools
|
|
@@ -37,7 +37,7 @@ module Samagotchi
|
|
|
37
37
|
# @param relay [#open_question, #relay_desk] Peers#relay
|
|
38
38
|
# @return [Boolean] a relay can take this question
|
|
39
39
|
def relayable?(relay, question)
|
|
40
|
-
!relay.nil? && question.is_a?(
|
|
40
|
+
!relay.nil? && question.is_a?(PendingQuestion) && question.kind.to_s == Guardrails::Approval::KIND
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# Relay the child's pending approval and wait until it settles.
|
|
@@ -45,7 +45,7 @@ module Samagotchi
|
|
|
45
45
|
# @return [Outcome]
|
|
46
46
|
def call(child_id, question, relay:, state_dir:, more: 0)
|
|
47
47
|
child = Session.load(child_id, state_dir: state_dir)
|
|
48
|
-
qid = question
|
|
48
|
+
qid = question.id.to_s
|
|
49
49
|
desk = relay.relay_desk
|
|
50
50
|
relay_id = desk.open(child_id: child_id, child_question_id: qid)
|
|
51
51
|
client = BridgeClient.discover(child_id, session_dir: Session.session_dir(child_id, state_dir: state_dir))
|
|
@@ -70,7 +70,7 @@ module Samagotchi
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
pending = Session.load(child_id, state_dir: state_dir).pending_question
|
|
73
|
-
pending && pending
|
|
73
|
+
pending && pending.id.to_s == qid ? nil : "answered_on_child"
|
|
74
74
|
rescue ArgumentError
|
|
75
75
|
"child_gone"
|
|
76
76
|
end
|
|
@@ -175,7 +175,7 @@ module Samagotchi
|
|
|
175
175
|
return if waiting.empty?
|
|
176
176
|
|
|
177
177
|
child_id, question = waiting.first
|
|
178
|
-
@done[question
|
|
178
|
+
@done[question.id.to_s] = true
|
|
179
179
|
outcome = DelegateRelay.call(child_id, question, relay: @relay, state_dir: @state_dir, more: waiting.size - 1)
|
|
180
180
|
@on_outcome.call(child_id, outcome)
|
|
181
181
|
# The next one, if any, right after.
|
|
@@ -186,7 +186,7 @@ module Samagotchi
|
|
|
186
186
|
# @return [Integer]
|
|
187
187
|
def count = waiting.size
|
|
188
188
|
|
|
189
|
-
# @return [Array<Array(String,
|
|
189
|
+
# @return [Array<Array(String, PendingQuestion)>] [child id, pending question],
|
|
190
190
|
# oldest question first
|
|
191
191
|
def waiting
|
|
192
192
|
rows = SessionManager.children_of(@parent_id, state_dir: @state_dir).select do |row|
|
|
@@ -194,10 +194,10 @@ module Samagotchi
|
|
|
194
194
|
end
|
|
195
195
|
rows.filter_map do |row|
|
|
196
196
|
pending = Session.load(row[:id], state_dir: @state_dir).pending_question
|
|
197
|
-
[row[:id], pending] if pending && pending
|
|
197
|
+
[row[:id], pending] if pending && pending.id.to_s == row[:waiting_id].to_s
|
|
198
198
|
rescue ArgumentError
|
|
199
199
|
nil
|
|
200
|
-
end.sort_by { |_, pending| pending
|
|
200
|
+
end.sort_by { |_, pending| pending.created_at.to_s }
|
|
201
201
|
end
|
|
202
202
|
|
|
203
203
|
private
|
|
@@ -6,8 +6,8 @@ require_relative "delegate"
|
|
|
6
6
|
require_relative "delegate_wait"
|
|
7
7
|
|
|
8
8
|
module Samagotchi
|
|
9
|
-
# Loaded on first use:
|
|
10
|
-
#
|
|
9
|
+
# Loaded on first use: this autoload keeps SessionManager and its ~80
|
|
10
|
+
# files unloaded until the delegate/session path needs them.
|
|
11
11
|
autoload :SessionManager, File.expand_path("../session_manager", __dir__)
|
|
12
12
|
|
|
13
13
|
module Tools
|
|
@@ -91,7 +91,7 @@ module Samagotchi
|
|
|
91
91
|
# asked), the relayed question no longer reported: a turn that
|
|
92
92
|
# ends with no reply during the relay's POST or after it still
|
|
93
93
|
# ends the wait.
|
|
94
|
-
baseline = ReplyWait.baseline_of(wait.session, question_id: wait.question
|
|
94
|
+
baseline = ReplyWait.baseline_of(wait.session, question_id: wait.question.id)
|
|
95
95
|
end
|
|
96
96
|
rescue ArgumentError => e
|
|
97
97
|
"Error: #{e.message}"
|
|
@@ -6,8 +6,8 @@ require_relative "../session"
|
|
|
6
6
|
require_relative "peers"
|
|
7
7
|
|
|
8
8
|
module Samagotchi
|
|
9
|
-
# Loaded on first use:
|
|
10
|
-
#
|
|
9
|
+
# Loaded on first use: this autoload keeps SessionManager and its ~80
|
|
10
|
+
# files unloaded until the delegate/session path needs them.
|
|
11
11
|
autoload :SessionManager, File.expand_path("../session_manager", __dir__)
|
|
12
12
|
|
|
13
13
|
module Tools
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "uri"
|
|
5
5
|
require "ipaddr"
|
|
6
|
-
require "nokogiri"
|
|
7
6
|
require_relative "../version"
|
|
8
7
|
|
|
9
8
|
module Samagotchi
|
|
@@ -128,6 +127,9 @@ module Samagotchi
|
|
|
128
127
|
def self.extract_text_from_html(html)
|
|
129
128
|
return "Error: empty HTML response" if html.nil? || html.empty?
|
|
130
129
|
|
|
130
|
+
# Load lazily so workers don't pay for Nokogiri on every start.
|
|
131
|
+
require "nokogiri"
|
|
132
|
+
|
|
131
133
|
doc = Nokogiri::HTML(html)
|
|
132
134
|
|
|
133
135
|
# Extract noscript text before removing it
|
data/lib/samagotchi/version.rb
CHANGED
data/lib/samagotchi/web/app.rb
CHANGED
|
@@ -43,7 +43,7 @@ require_relative "../recap_store"
|
|
|
43
43
|
require_relative "markdown_renderer"
|
|
44
44
|
require_relative "capabilities"
|
|
45
45
|
require_relative "editor"
|
|
46
|
-
require_relative "message_parts"
|
|
46
|
+
require_relative "../message_parts"
|
|
47
47
|
require_relative "session_summary"
|
|
48
48
|
require_relative "../log"
|
|
49
49
|
require_relative "../paths"
|
|
@@ -855,7 +855,7 @@ module Samagotchi
|
|
|
855
855
|
current_turn = turn_snapshot["current_turn"]
|
|
856
856
|
current_turn ? current_turn["pending_question"] : turn_snapshot["pending_question"]
|
|
857
857
|
elsif owner&.worker?
|
|
858
|
-
session.pending_question
|
|
858
|
+
session.pending_question&.to_h
|
|
859
859
|
end
|
|
860
860
|
end
|
|
861
861
|
|