samagotchi 0.156.0 → 0.158.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 +70 -1
- data/README.md +1 -1
- data/bin/chi +4 -1
- data/docs/architecture.md +18 -12
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +4 -1
- data/docs/cli.md +29 -24
- data/docs/configuration.md +43 -9
- data/docs/context.md +5 -3
- data/docs/guardrails.md +12 -7
- data/docs/hooks.md +53 -83
- data/docs/internals/bridge.md +84 -7
- data/docs/internals/bundles.md +69 -46
- data/docs/internals/context-telemetry.md +4 -3
- data/docs/internals/engine.md +23 -7
- data/docs/internals/participants.md +3 -2
- data/docs/internals/remote-sessions.md +5 -2
- data/docs/internals/web-frontend.md +3 -2
- data/docs/internals/working-set.md +1 -1
- data/docs/memory.md +51 -10
- data/docs/plugins.md +48 -11
- data/docs/releasing.md +6 -5
- data/docs/sessions.md +13 -13
- data/docs/sub-agent.md +17 -2
- data/docs/testing.md +56 -5
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +4 -3
- data/lib/samagotchi/bootstrap_command.rb +3 -2
- data/lib/samagotchi/bridge/card_places.rb +5 -7
- data/lib/samagotchi/bridge/card_store.rb +3 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +113 -10
- data/lib/samagotchi/bridge/turn_drawings.rb +271 -0
- data/lib/samagotchi/bridge.rb +31 -6
- data/lib/samagotchi/broadcast/recipients.rb +8 -8
- data/lib/samagotchi/broadcast/triage_model.rb +1 -1
- data/lib/samagotchi/bundle_command/check.rb +41 -10
- data/lib/samagotchi/bundle_command/installed_check.rb +13 -2
- data/lib/samagotchi/bundle_command/try/run.rb +4 -1
- data/lib/samagotchi/bundle_command/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +20 -45
- data/lib/samagotchi/bundles/known-names/manifest.yml +4 -7
- data/lib/samagotchi/bundles/known-names/{hooks/known_names.rb → plugin.rb} +17 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +4 -7
- data/lib/samagotchi/bundles/source-links/{hooks/source_links.rb → plugin.rb} +23 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +7 -7
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/child_move.rb +6 -6
- data/lib/samagotchi/child_reports.rb +2 -2
- data/lib/samagotchi/children_status.rb +15 -21
- data/lib/samagotchi/cli/parent_wait.rb +8 -22
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/context_command.rb +7 -5
- data/lib/samagotchi/context_sources.rb +9 -6
- data/lib/samagotchi/context_window.rb +9 -12
- data/lib/samagotchi/effective_model.rb +57 -22
- data/lib/samagotchi/engine/plugins.rb +1 -1
- data/lib/samagotchi/engine/recap.rb +8 -4
- data/lib/samagotchi/engine.rb +30 -9
- data/lib/samagotchi/events.rb +3 -2
- data/lib/samagotchi/extension_load.rb +26 -44
- data/lib/samagotchi/guardrails/load_failures.rb +3 -2
- data/lib/samagotchi/guardrails/path_resolve.rb +11 -0
- data/lib/samagotchi/guardrails/review_paths.rb +1 -5
- data/lib/samagotchi/guardrails_command.rb +1 -1
- data/lib/samagotchi/hook_routes.rb +2 -2
- data/lib/samagotchi/hooks.rb +2 -3
- data/lib/samagotchi/host_address.rb +52 -0
- data/lib/samagotchi/host_registry.rb +57 -45
- data/lib/samagotchi/kernel_loop.rb +7 -3
- data/lib/samagotchi/llm/chat_loop.rb +6 -3
- data/lib/samagotchi/llm_context_strategy.rb +27 -63
- data/lib/samagotchi/memory_bundle/asset_installer.rb +92 -177
- data/lib/samagotchi/memory_bundle/builder.rb +12 -35
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
- data/lib/samagotchi/memory_bundle/file_actions.rb +193 -0
- data/lib/samagotchi/memory_bundle/file_digest.rb +13 -0
- data/lib/samagotchi/memory_bundle/file_state.rb +152 -0
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
- data/lib/samagotchi/memory_bundle/installed_copy.rb +13 -19
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +10 -17
- data/lib/samagotchi/memory_bundle/installer.rb +212 -178
- data/lib/samagotchi/memory_bundle/manifest.rb +51 -58
- data/lib/samagotchi/memory_bundle/merger.rb +69 -65
- data/lib/samagotchi/memory_bundle/plugin_ref.rb +6 -5
- data/lib/samagotchi/memory_bundle/profile.rb +5 -5
- data/lib/samagotchi/memory_bundle/provenance.rb +26 -52
- data/lib/samagotchi/memory_bundle/remerge.rb +31 -14
- data/lib/samagotchi/memory_bundle/shas.rb +5 -8
- data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -11
- data/lib/samagotchi/memory_bundle/status.rb +26 -30
- data/lib/samagotchi/memory_bundle/system_bundle.rb +39 -10
- data/lib/samagotchi/memory_bundle/trash.rb +28 -18
- data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
- data/lib/samagotchi/memory_orphans.rb +4 -4
- data/lib/samagotchi/memory_paths.rb +102 -13
- data/lib/samagotchi/memory_usage.rb +1 -1
- data/lib/samagotchi/model_lookup.rb +75 -0
- data/lib/samagotchi/model_profile.rb +1 -0
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/participant_mode.rb +2 -1
- data/lib/samagotchi/plugin/api.rb +42 -10
- data/lib/samagotchi/plugin/loader.rb +75 -15
- data/lib/samagotchi/plugin/sessions.rb +1 -1
- data/lib/samagotchi/project_command.rb +167 -0
- data/lib/samagotchi/project_init.rb +69 -0
- data/lib/samagotchi/project_scope.rb +6 -4
- data/lib/samagotchi/prompt_files.rb +10 -7
- data/lib/samagotchi/reasoning_history.rb +14 -20
- data/lib/samagotchi/recap_place.rb +26 -0
- data/lib/samagotchi/reply_wait/liveness.rb +38 -0
- data/lib/samagotchi/reply_wait/poll.rb +44 -15
- data/lib/samagotchi/reply_wait/target.rb +113 -0
- data/lib/samagotchi/reply_wait.rb +22 -2
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/sampling_settings.rb +9 -16
- data/lib/samagotchi/self_report.rb +34 -35
- data/lib/samagotchi/send_command.rb +17 -23
- data/lib/samagotchi/session.rb +349 -441
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +5 -6
- data/lib/samagotchi/session_access/remote.rb +5 -3
- data/lib/samagotchi/session_field.rb +134 -0
- data/lib/samagotchi/session_inbox.rb +49 -1
- data/lib/samagotchi/session_manager/emptiness.rb +2 -1
- data/lib/samagotchi/session_manager/listing.rb +29 -100
- data/lib/samagotchi/session_manager.rb +2 -1
- data/lib/samagotchi/session_metrics.rb +7 -4
- data/lib/samagotchi/session_paths.rb +57 -0
- data/lib/samagotchi/session_retention.rb +2 -2
- data/lib/samagotchi/session_row.rb +200 -0
- data/lib/samagotchi/session_setup.rb +15 -12
- data/lib/samagotchi/sessions_command.rb +11 -129
- data/lib/samagotchi/steer.rb +10 -0
- data/lib/samagotchi/steer_cut_after.rb +7 -14
- data/lib/samagotchi/system_prompt.rb +20 -11
- data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
- data/lib/samagotchi/terminal_ui/join_replay.rb +91 -8
- data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
- data/lib/samagotchi/terminal_ui.rb +3 -1
- data/lib/samagotchi/testing/bundle.rb +5 -12
- data/lib/samagotchi/testing/install.rb +5 -4
- data/lib/samagotchi/testing/recorder.rb +1 -1
- data/lib/samagotchi/testing.rb +1 -1
- data/lib/samagotchi/thinking.rb +16 -21
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tool_response.rb +5 -3
- data/lib/samagotchi/tool_runner.rb +18 -1
- data/lib/samagotchi/toolkit_binding.rb +1 -4
- data/lib/samagotchi/toolkit_command.rb +1 -4
- data/lib/samagotchi/tools/delegate.rb +60 -51
- data/lib/samagotchi/tools/delegate_relay.rb +5 -5
- data/lib/samagotchi/tools/delegate_result.rb +1 -1
- data/lib/samagotchi/tools/delegate_wait.rb +41 -17
- data/lib/samagotchi/tools/list_sessions.rb +7 -7
- data/lib/samagotchi/update_command.rb +11 -7
- data/lib/samagotchi/verify_fresh.rb +7 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +11 -14
- data/lib/samagotchi/web/app.rb +66 -21
- data/lib/samagotchi/web/message_display.rb +57 -7
- data/lib/samagotchi/web/participant_access.rb +1 -1
- data/lib/samagotchi/web/project_list.rb +3 -2
- data/lib/samagotchi/web/public/activity.js +16 -7
- data/lib/samagotchi/web/public/app.js +29 -8
- data/lib/samagotchi/web/public/bubbles.js +8 -3
- data/lib/samagotchi/web/public/cards_place.js +15 -0
- data/lib/samagotchi/web/public/commands.js +5 -1
- data/lib/samagotchi/web/public/data.js +4 -2
- data/lib/samagotchi/web/public/history_shape.js +220 -0
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/project_picker.js +86 -4
- data/lib/samagotchi/web/public/recap_stamp.js +17 -0
- data/lib/samagotchi/web/public/reload_check.js +71 -0
- data/lib/samagotchi/web/public/stage_model.js +7 -5
- data/lib/samagotchi/web/public/stage_view.js +7 -3
- data/lib/samagotchi/web/public/stream_handlers.js +8 -2
- data/lib/samagotchi/web/public/timing.js +43 -2
- data/lib/samagotchi/web/public/turn_block.js +215 -30
- data/lib/samagotchi/web/public/turn_drawing.js +43 -0
- data/lib/samagotchi/web/public/turn_events.js +10 -1
- data/lib/samagotchi/web/public/turn_pieces.js +29 -0
- data/lib/samagotchi/web/reload_check_route.rb +95 -0
- data/lib/samagotchi/web/server.rb +4 -2
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +9 -17
- data/lib/samagotchi/web/session_show.rb +64 -2
- data/lib/samagotchi/web/session_summary.rb +36 -24
- data/lib/samagotchi/worker.rb +28 -5
- data/lib/samagotchi/worker_link.rb +3 -0
- metadata +22 -5
- data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
- data/lib/samagotchi/send_command/wait_target.rb +0 -85
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../bridge/turn_accumulator"
|
|
4
|
+
require_relative "../bridge/turn_drawings"
|
|
3
5
|
require_relative "../context_note"
|
|
4
6
|
require_relative "../image_store"
|
|
5
7
|
require_relative "../log"
|
|
@@ -31,11 +33,32 @@ module Samagotchi
|
|
|
31
33
|
@renderer = renderer
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
# A snapshot's last_turn_drawing (Bridge#snapshot: the last exchange's
|
|
37
|
+
# turn as live drew it) draws that turn's rows; without one (an older
|
|
38
|
+
# worker, a turn before drawings) they are rebuilt from the messages.
|
|
34
39
|
def render_join(snapshot, saved)
|
|
35
|
-
render_join_header(Array(snapshot[:messages]))
|
|
40
|
+
render_join_header(Array(snapshot[:messages]), drawing: snapshot[:last_turn_drawing])
|
|
36
41
|
render_join_gap(snapshot, saved)
|
|
37
42
|
end
|
|
38
43
|
|
|
44
|
+
# One replayed event of a turn's rows (TurnAccumulator.replay_events),
|
|
45
|
+
# as live drew it: a joined running turn's (AttachedLoop#render_current_turn)
|
|
46
|
+
# and a drawn finished one's (#render_drawn_turn). Its tool rows with
|
|
47
|
+
# their action and duration, merged input, reminders, a plugin's steer,
|
|
48
|
+
# a hook's notice, a retry's, a steer cut's and a ✂ row's line. The
|
|
49
|
+
# step text, the turn's start and questions are the caller's (the
|
|
50
|
+
# pending one is asked after the replay, answered ones showed no line).
|
|
51
|
+
def replay_row(event)
|
|
52
|
+
case event[:type]
|
|
53
|
+
when :tool_call_started then @renderer.call(event)
|
|
54
|
+
when :tool_call_completed then replay_tool_completed(event)
|
|
55
|
+
when :merged_input then @screen.commit("input> #{event[:content]}")
|
|
56
|
+
when :reminder_injected then @screen.commit(reminder_line(event[:reminders]))
|
|
57
|
+
when :pending_input_merged, :hook_notice, :empty_answer_retry, :steer_cut, :llm_context_edited
|
|
58
|
+
@renderer.call(event)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
39
62
|
# The snapshot's cards and between-turns notices (Bridge CardStore):
|
|
40
63
|
# on join the ones since the last turn (and the running turn's), as
|
|
41
64
|
# the join header shows the last exchange; on a resync only cards not
|
|
@@ -60,6 +83,18 @@ module Samagotchi
|
|
|
60
83
|
|
|
61
84
|
private
|
|
62
85
|
|
|
86
|
+
# A finished tool call: the live row (action, duration: none when the
|
|
87
|
+
# part has none, never the replay's own clock). A snapshot from an
|
|
88
|
+
# older worker has no action: its row names the tool only.
|
|
89
|
+
def replay_tool_completed(event)
|
|
90
|
+
return @renderer.call({ duration_ms: nil }.merge(event)) if event.dig(:activity, :action)
|
|
91
|
+
|
|
92
|
+
activity = EventRenderer.described(event) || {}
|
|
93
|
+
@screen.commit(snapshot_tool_line({ tool: event[:tool], params: activity[:params], status: activity[:status],
|
|
94
|
+
description: activity[:description], output_truncated: event[:output_truncated],
|
|
95
|
+
images: event[:images], diff: event[:diff] }))
|
|
96
|
+
end
|
|
97
|
+
|
|
63
98
|
# Where the session's last activity was: its newest stamped entry,
|
|
64
99
|
# else its saved recap's time. A gap ago (and no turn running): one
|
|
65
100
|
# line after the join block says when.
|
|
@@ -71,7 +106,7 @@ module Samagotchi
|
|
|
71
106
|
@screen.commit(gap_divider(line)) if line
|
|
72
107
|
end
|
|
73
108
|
|
|
74
|
-
def render_join_header(messages)
|
|
109
|
+
def render_join_header(messages, drawing: nil)
|
|
75
110
|
exchange = messages.select { |m| %w[user model assistant].include?(m[:role].to_s) }
|
|
76
111
|
# The id stays findable: the detach line and chi sessions list show it.
|
|
77
112
|
Log.session_id = @link.client.session_id
|
|
@@ -89,6 +124,11 @@ module Samagotchi
|
|
|
89
124
|
unless ImageRef.placeholder_only?(exchange[last_user][:content])
|
|
90
125
|
Array(exchange[last_user][:images]).each { |ref| @screen.commit(format_image_line(ref)) }
|
|
91
126
|
end
|
|
127
|
+
if (drawn = drawn_turn(drawing, exchange[last_user]))
|
|
128
|
+
render_drawn_turn(*drawn, exchange[(last_user + 1)..])
|
|
129
|
+
return render_join_notes(messages, from, after_exchange: true)
|
|
130
|
+
end
|
|
131
|
+
|
|
92
132
|
render_join_steps(messages, from, durations: join_tool_records(exchange[last_user]))
|
|
93
133
|
record = @link.saved_turn_record(exchange[last_user][:turn_id] || exchange[last_user]["turn_id"])
|
|
94
134
|
failed = join_failed_record(record)
|
|
@@ -109,16 +149,59 @@ module Samagotchi
|
|
|
109
149
|
@screen.commit(@view.turn_canceled_line(record["cancellation_reason"], record["duration_ms"], by: record["cancelled_by"]))
|
|
110
150
|
@screen.commit(@view.turn_end_hint(ROLLBACK_HINT)) unless record["continue"]
|
|
111
151
|
else
|
|
112
|
-
|
|
113
|
-
# be only a tool call.
|
|
114
|
-
answer = exchange[(last_user + 1)..].reverse_each
|
|
115
|
-
.map { |m| OutputFormatter.strip_markup(m[:content]) }
|
|
116
|
-
.find { |text| !text.empty? }
|
|
117
|
-
@screen.commit(last_lines(answer)) if answer
|
|
152
|
+
render_join_answer(exchange[(last_user + 1)..])
|
|
118
153
|
end
|
|
119
154
|
render_join_notes(messages, from, after_exchange: true)
|
|
120
155
|
end
|
|
121
156
|
|
|
157
|
+
# The last answer of the messages after the prompt (+after+). The saved
|
|
158
|
+
# answer is raw: the latest keeps its thinking, and one may be only a
|
|
159
|
+
# tool call.
|
|
160
|
+
def render_join_answer(after)
|
|
161
|
+
answer = after.reverse_each.map { |m| OutputFormatter.strip_markup(m[:content]) }.find { |text| !text.empty? }
|
|
162
|
+
@screen.commit(last_lines(answer)) if answer
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# The snapshot's drawing as [turn, end event] when it is +prompt+'s
|
|
166
|
+
# turn (a Continue's or a reminder's drawing isn't the prompt's: the
|
|
167
|
+
# rows are rebuilt), else nil; nil too for one whose parts aren't a
|
|
168
|
+
# list of rows.
|
|
169
|
+
def drawn_turn(drawing, prompt)
|
|
170
|
+
return nil unless drawing.is_a?(Hash) && drawing[:drawing].is_a?(Hash) && drawing[:end].is_a?(Hash)
|
|
171
|
+
|
|
172
|
+
turn = Bridge::TurnAccumulator.from_wire(drawing[:drawing])
|
|
173
|
+
id = prompt[:turn_id] || prompt["turn_id"]
|
|
174
|
+
return nil unless id && turn[:turn_id].to_s == id.to_s
|
|
175
|
+
# An older worker sends a line its parse didn't check (the unfold
|
|
176
|
+
# would raise after the prompt line is drawn).
|
|
177
|
+
return nil unless Bridge::TurnDrawings.rows?(turn[:parts])
|
|
178
|
+
|
|
179
|
+
[turn, EventRenderer.symbolize(Bridge::TurnAccumulator.from_wire(drawing[:end]))]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# The last turn from its drawing, through the renderer as live drew it
|
|
183
|
+
# (the running turn's join path, AttachedLoop#render_current_turn): its
|
|
184
|
+
# rows with their status and duration, a retry's line, a hook's notice,
|
|
185
|
+
# steers, merged input and reminders; no step text (the live ticker's)
|
|
186
|
+
# and no cards (the snapshot's cards are drawn after). Then its end: a
|
|
187
|
+
# cancel's or a failure's line from the end event, else the empty
|
|
188
|
+
# answer's notice or the saved answer (+after+, the messages after the
|
|
189
|
+
# prompt).
|
|
190
|
+
def render_drawn_turn(turn, ending, after)
|
|
191
|
+
Bridge::TurnAccumulator.replay_events(turn).each do |event|
|
|
192
|
+
# The prompt line named its images already.
|
|
193
|
+
event[:type] == :turn_started ? @renderer.call(event.except(:images)) : replay_row(event)
|
|
194
|
+
end
|
|
195
|
+
case ending[:type]
|
|
196
|
+
when :turn_canceled, :turn_failed then @renderer.call(ending)
|
|
197
|
+
else
|
|
198
|
+
empty = ending.dig(:turn_summary, :empty_answer)
|
|
199
|
+
empty ? @screen.commit(format_empty_answer_line(empty[:retries], empty[:refusal])) : render_join_answer(after)
|
|
200
|
+
end
|
|
201
|
+
# A call the turn never answered left the tool slot running.
|
|
202
|
+
@view.finish_thinking_spinner
|
|
203
|
+
end
|
|
204
|
+
|
|
122
205
|
# What the last exchange did, as the live turn drew it: each saved
|
|
123
206
|
# call's tool row (action, params, status from its result, and its
|
|
124
207
|
# duration from +durations+, the turn's saved tool records) and a
|
|
@@ -112,13 +112,13 @@ module Samagotchi
|
|
|
112
112
|
# no turn id (an older session) or a session with no analytics.
|
|
113
113
|
def saved_tool_records(turn_id)
|
|
114
114
|
SessionMetrics.saved_tool_records(session_dir, turn_id)
|
|
115
|
-
rescue
|
|
115
|
+
rescue SessionPaths::InvalidId
|
|
116
116
|
[]
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def saved_turn_record(turn_id)
|
|
120
120
|
SessionMetrics.saved_turn_record(session_dir, turn_id)
|
|
121
|
-
rescue
|
|
121
|
+
rescue SessionPaths::InvalidId
|
|
122
122
|
nil
|
|
123
123
|
end
|
|
124
124
|
|
|
@@ -90,7 +90,9 @@ module Samagotchi
|
|
|
90
90
|
# As typed: the Engine applies an alias where it resolves the model.
|
|
91
91
|
flag_model_name = model_name.to_s.strip.empty? ? nil : ModelProfile.required_model_name(model_name)
|
|
92
92
|
@effective_model_name = flag_model_name || @default_model_name
|
|
93
|
-
|
|
93
|
+
# Taken once (as the Engine's); a client (specs) goes on a registry of
|
|
94
|
+
# its own, never the shared one.
|
|
95
|
+
@host_registry = host_registry || (client ? Samagotchi::HostRegistry.new : Samagotchi::HostRegistry.current)
|
|
94
96
|
# An injected client (specs) stands in for every host's client.
|
|
95
97
|
@host_registry.client_override = client if client
|
|
96
98
|
@client = @host_registry.resolve(@effective_model_name).client
|
|
@@ -25,7 +25,7 @@ require_relative "recording_api"
|
|
|
25
25
|
|
|
26
26
|
module Samagotchi
|
|
27
27
|
module Testing
|
|
28
|
-
# A hook (a plugin's chi.on block
|
|
28
|
+
# A hook (a plugin's chi.on block) raised while the kit
|
|
29
29
|
# fired it: chi would log it and go on, a test fails with it.
|
|
30
30
|
class HookFailed < StandardError
|
|
31
31
|
# @return [Hooks::Failure]
|
|
@@ -53,8 +53,7 @@ module Samagotchi
|
|
|
53
53
|
|
|
54
54
|
# One bundle loaded for a test through chi's own seams: its plugin
|
|
55
55
|
# through Plugin::Loader.register_file (Plugin::Api, a real
|
|
56
|
-
# Plugin::Context)
|
|
57
|
-
# Hooks::BundleLoader.load_hook_file, into real registries (commands
|
|
56
|
+
# Plugin::Context), into real registries (commands
|
|
58
57
|
# and tools with chi's built-ins in them, so a name clash fails as in
|
|
59
58
|
# chi). Only the edges are recorded: the Host and hook Runtime
|
|
60
59
|
# (Recorder), ctx.sessions (Testing::Sessions), chi.init tasks and
|
|
@@ -130,8 +129,6 @@ module Samagotchi
|
|
|
130
129
|
# @return [Module, nil] the module the plugin was evaluated in
|
|
131
130
|
# (bundle.namespace::ThinkingWatch)
|
|
132
131
|
attr_reader :namespace
|
|
133
|
-
# @return [Module] the module the hook files were evaluated in
|
|
134
|
-
attr_reader :hooks_namespace
|
|
135
132
|
# @return [Array<Init>]
|
|
136
133
|
attr_reader :inits
|
|
137
134
|
# @return [Array<StagedTools>]
|
|
@@ -152,6 +149,9 @@ module Samagotchi
|
|
|
152
149
|
@session_moves = 1
|
|
153
150
|
own = session_settings.nil? || session_settings.empty? ? {} : { setup: setup_with(session_settings, host[:setup]) }
|
|
154
151
|
@own_sandbox = sandbox.nil? && Sandbox.current.nil?
|
|
152
|
+
# With no active sandbox, this one saves ENV at construction and
|
|
153
|
+
# restores that copy at shutdown; don't lazily build it inside a block
|
|
154
|
+
# that sets ENV for one example (your own helper, or ClimateControl).
|
|
155
155
|
@sandbox = sandbox || Sandbox.current || Sandbox.enter
|
|
156
156
|
build(host, own)
|
|
157
157
|
rescue Exception # rubocop:disable Lint/RescueException -- leave the sandbox it entered, whatever failed
|
|
@@ -184,13 +184,6 @@ module Samagotchi
|
|
|
184
184
|
@plugin = built
|
|
185
185
|
end
|
|
186
186
|
end
|
|
187
|
-
@hooks_namespace = Module.new
|
|
188
|
-
@manifest.hooks.each do |basename, hook|
|
|
189
|
-
next if hook.events.empty?
|
|
190
|
-
|
|
191
|
-
Hooks::BundleLoader.load_hook_file(@name, basename, File.join(@dir, "hooks", basename), hook, hooks,
|
|
192
|
-
settings: @settings, namespace: @hooks_namespace)
|
|
193
|
-
end
|
|
194
187
|
self
|
|
195
188
|
rescue Exception # rubocop:disable Lint/RescueException -- leave the sandbox it entered, whatever failed
|
|
196
189
|
shutdown
|
|
@@ -15,7 +15,7 @@ module Samagotchi
|
|
|
15
15
|
# A bundle as chi installs and loads it, unlike Bundle (its source as it
|
|
16
16
|
# is): MemoryBundle::Installer copies it into the sandbox's state (sha256
|
|
17
17
|
# and requires_chi checked), then chi's own loaders load what is
|
|
18
|
-
# installed (ExtensionLoad:
|
|
18
|
+
# installed (ExtensionLoad: config hooks, then the plugins). What
|
|
19
19
|
# failed names the bundle's source files, not the installed copies.
|
|
20
20
|
#
|
|
21
21
|
# result = Samagotchi::Testing::Install.run("path/to/bundle", settings: { "every" => 3 })
|
|
@@ -23,7 +23,8 @@ module Samagotchi
|
|
|
23
23
|
module Install
|
|
24
24
|
# warnings: the Installer's; plugin_failures / hook_failures:
|
|
25
25
|
# [Guardrails::LoadFailures::Failure] of this bundle, each location
|
|
26
|
-
# in the source dir
|
|
26
|
+
# in the source dir (hook_failures: the guardrails': a required
|
|
27
|
+
# plugin's load failure, a config hook's); registered: what it registered (#registered).
|
|
27
28
|
Result = Data.define(:name, :warnings, :plugin_failures, :hook_failures, :registered) do
|
|
28
29
|
def failures = plugin_failures + hook_failures
|
|
29
30
|
end
|
|
@@ -34,7 +35,7 @@ module Samagotchi
|
|
|
34
35
|
Registered = Data.define(:commands, :tools, :hooks, :inits, :toolkits, :tool_sources, :peer_sources, :prompt_sections)
|
|
35
36
|
# A command: its mid_turn policy and its selection-bar labels (chi.annotation).
|
|
36
37
|
Command = Data.define(:name, :mid_turn, :labels)
|
|
37
|
-
# A hook on an event: the bundle, the hook (
|
|
38
|
+
# A hook on an event: the bundle, the hook (the plugin's label) and its priority.
|
|
38
39
|
Hook = Data.define(:bundle, :hook, :priority)
|
|
39
40
|
|
|
40
41
|
module_function
|
|
@@ -118,7 +119,7 @@ module Samagotchi
|
|
|
118
119
|
# installed copy to the source dir.
|
|
119
120
|
def own(failures, name, installed, dir)
|
|
120
121
|
failures.list.select { |f| f.what.end_with?("(bundle #{name})") }.map do |failure|
|
|
121
|
-
# The plugin is installed into plugin
|
|
122
|
+
# The plugin is installed into plugin/.
|
|
122
123
|
location = failure.location&.sub(%r{\A#{Regexp.escape(installed)}/(?:plugin/)?}, "#{dir}/")
|
|
123
124
|
failure.dup.tap { |copy| copy.location = location }
|
|
124
125
|
end
|
|
@@ -9,7 +9,7 @@ require_relative "judgments"
|
|
|
9
9
|
module Samagotchi
|
|
10
10
|
module Testing
|
|
11
11
|
# A line to the user (ctx.notify, event[:notify]); +hook+ is the
|
|
12
|
-
# plugin's
|
|
12
|
+
# plugin's label ("plugin.rb (bundle x)").
|
|
13
13
|
Notice = Data.define(:text, :level, :hook, :fallback_for)
|
|
14
14
|
# Text put into the running turn (ctx.steer, event[:steer]).
|
|
15
15
|
Steer = Data.define(:text, :hook)
|
data/lib/samagotchi/testing.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# The bundle test kit (docs/plugins.md, "Testing a bundle"): load a bundle's
|
|
4
|
-
# plugin
|
|
4
|
+
# plugin through chi's own loaders, registries, Context and
|
|
5
5
|
# guardrail Gate, with only the edges recorded (the session's Host, the hook
|
|
6
6
|
# runtime, ctx.sessions, init tasks), in a sandboxed environment.
|
|
7
7
|
#
|
data/lib/samagotchi/thinking.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "config"
|
|
4
|
-
require_relative "model_profile"
|
|
5
4
|
require_relative "setting_explained"
|
|
6
5
|
|
|
7
6
|
module Samagotchi
|
|
@@ -79,30 +78,23 @@ module Samagotchi
|
|
|
79
78
|
nil
|
|
80
79
|
end
|
|
81
80
|
|
|
82
|
-
# The level for +
|
|
81
|
+
# The level for +lookup+'s model and where it came from ("session",
|
|
83
82
|
# "--thinking", "models: qwen", "hosts.work", "thinking.level"; nil for
|
|
84
83
|
# the default).
|
|
85
|
-
# @param
|
|
86
|
-
# @param names [Array<String>] the model's lookup names (HostRegistry#lookup_names)
|
|
87
|
-
# @param models [Hash{String => ModelSettings}, nil] ModelSettings.all (specs)
|
|
84
|
+
# @param lookup [ModelLookup] the model's layers (EffectiveModel#lookup)
|
|
88
85
|
# @param session [Symbol, nil] the session's own level (Session#thinking)
|
|
89
86
|
# @return [Array(Symbol, String|nil)]
|
|
90
|
-
def resolve(
|
|
87
|
+
def resolve(lookup, session: nil)
|
|
91
88
|
return [session, SESSION_SOURCE] if session
|
|
92
89
|
|
|
93
|
-
global, origin = global_level
|
|
90
|
+
global, origin = global_level(lookup)
|
|
94
91
|
return [global, origin == :cli ? "--thinking" : "SAMAGOTCHI_THINKING_LEVEL"] if global && %i[cli env].include?(origin)
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
ModelSettings.all
|
|
98
|
-
rescue StandardError
|
|
99
|
-
{}
|
|
100
|
-
end
|
|
101
|
-
setting = ModelSettings.find(names, :thinking, models: models)
|
|
93
|
+
setting = lookup.model(:thinking)
|
|
102
94
|
return [setting.value, "models: #{setting.key}"] if setting
|
|
103
95
|
|
|
104
|
-
host =
|
|
105
|
-
return [host, "hosts.#{
|
|
96
|
+
host = lookup.host(:thinking)
|
|
97
|
+
return [host, "hosts.#{lookup.host_name}"] if host
|
|
106
98
|
return [global, "thinking.level"] if global
|
|
107
99
|
|
|
108
100
|
[DEFAULT, nil]
|
|
@@ -110,14 +102,15 @@ module Samagotchi
|
|
|
110
102
|
|
|
111
103
|
# #resolve, as a SettingExplained with the session's own level beside it.
|
|
112
104
|
# @return [SettingExplained]
|
|
113
|
-
def explain(
|
|
114
|
-
level, source = resolve(
|
|
105
|
+
def explain(lookup, session: nil)
|
|
106
|
+
level, source = resolve(lookup, session: session)
|
|
115
107
|
SettingExplained.new(level: level, source: source, own: session)
|
|
116
108
|
end
|
|
117
109
|
|
|
118
110
|
# thinking.level as a level and its origin (:cli, :env, :file), or nil.
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
# @param lookup [ModelLookup] whose env the key is read with
|
|
112
|
+
def global_level(lookup)
|
|
113
|
+
raw, origin = lookup.global("thinking.level")
|
|
121
114
|
return nil if origin == :default
|
|
122
115
|
|
|
123
116
|
where = { cli: "--thinking", env: "SAMAGOTCHI_THINKING_LEVEL" }.fetch(origin, "thinking.level")
|
|
@@ -139,13 +132,15 @@ module Samagotchi
|
|
|
139
132
|
# only the tail changes (llama.cpp and Splash keep the rest).
|
|
140
133
|
# @param target [HostRegistry::ModelTarget]
|
|
141
134
|
# @param profile [String, nil] the native prompt profile's name
|
|
135
|
+
# @param inferred [String, nil] the profile a local chat host's model
|
|
136
|
+
# name says (ModelProfile.inferred_profile_name of its bare id)
|
|
142
137
|
# @return [Symbol]
|
|
143
|
-
def cache_cost(target, profile: nil)
|
|
138
|
+
def cache_cost(target, profile: nil, inferred: nil)
|
|
144
139
|
entry = target.entry
|
|
145
140
|
if entry&.chat?
|
|
146
141
|
return :provider if entry.remote?
|
|
147
142
|
|
|
148
|
-
return
|
|
143
|
+
return inferred == "gemma4" ? :full : :tail
|
|
149
144
|
end
|
|
150
145
|
|
|
151
146
|
profile == "gemma4" ? :full : :tail
|
|
@@ -375,7 +375,7 @@ module Samagotchi
|
|
|
375
375
|
},
|
|
376
376
|
cwd: {
|
|
377
377
|
type: "string",
|
|
378
|
-
description: "The new child's working folder: a worktree or subfolder of this session's repository
|
|
378
|
+
description: "The new child's working folder: a worktree or subfolder of this session's project (a repository, or a `chi project init` folder; create a worktree with execute `git worktree add ../<repo>-<name> -b <branch>` first). Default: this session's folder"
|
|
379
379
|
},
|
|
380
380
|
toolkit: {
|
|
381
381
|
type: "string",
|
|
@@ -35,15 +35,17 @@ module Samagotchi
|
|
|
35
35
|
# #interrupted run for every call left (the one it cut short too), so
|
|
36
36
|
# the loop's conversation answers each call, then goes on up: the turn
|
|
37
37
|
# keeps its progress (Engine#ctrl_c_messages). +context+: the calls'
|
|
38
|
-
# turn (TurnContext), each run's (ToolRunner#run).
|
|
38
|
+
# turn (TurnContext), each run's (ToolRunner#run). +ids+: the calls'
|
|
39
|
+
# ToolIds, in call order (ToolIds.next_ids before the batch: the ids the
|
|
40
|
+
# entries are saved under), each on its run's tool events; nil: none.
|
|
39
41
|
# @return [Array<ToolRun>] the runs
|
|
40
|
-
def run_batch(runner, calls, context:, iteration:, emit:, on_stream_event:, cap:)
|
|
42
|
+
def run_batch(runner, calls, context:, iteration:, emit:, on_stream_event:, cap:, ids: nil)
|
|
41
43
|
# Before anything can be interrupted: the rescue counts it.
|
|
42
44
|
runs = []
|
|
43
45
|
emit.call({ type: :tool_dispatch_started, iteration: iteration, call_count: calls.length })
|
|
44
46
|
calls.each_with_index do |call, index|
|
|
45
47
|
runs << runner.run(call, context: context, iteration: iteration, call_index: index + 1, call_count: calls.length,
|
|
46
|
-
on_stream_event: on_stream_event, max_tool_output_chars: cap)
|
|
48
|
+
on_stream_event: on_stream_event, max_tool_output_chars: cap, tool_id: ids&.[](index))
|
|
47
49
|
yield runs.last, index if block_given?
|
|
48
50
|
end
|
|
49
51
|
emit.call({ type: :tool_dispatch_completed, iteration: iteration, call_count: calls.length })
|
|
@@ -86,7 +86,10 @@ module Samagotchi
|
|
|
86
86
|
# file (EditPreview.change; also on tool_call_completed, never in the
|
|
87
87
|
# model's output)
|
|
88
88
|
# @param context [TurnContext] the call's turn
|
|
89
|
-
|
|
89
|
+
# @param tool_id [String, nil] the id the run's output is saved under
|
|
90
|
+
# (ToolIds, handed out before the batch runs), on both tool events
|
|
91
|
+
# so a UI's row and the saved output share it
|
|
92
|
+
def run(call, context:, iteration:, call_index:, call_count:, on_stream_event:, max_tool_output_chars:, tool_id: nil)
|
|
90
93
|
# A call_tool of one of chi's own tools runs as that tool, from here
|
|
91
94
|
# on (the gate, the row, the result): rules, approvals and hooks see
|
|
92
95
|
# the real call, with via: (Tools::ToolIndex#unwrap).
|
|
@@ -114,6 +117,7 @@ module Samagotchi
|
|
|
114
117
|
started = { type: :tool_call_started, iteration: iteration, call_count: call_count, call_index: call_index,
|
|
115
118
|
tool: call[:name], call: call.dup, params: params }
|
|
116
119
|
started[:label] = label if label
|
|
120
|
+
started[:tool_id] = tool_id if tool_id
|
|
117
121
|
# Its row fields (ToolRowFields: title, called_as, via, view, ref, and a
|
|
118
122
|
# plugin tool's refs); the worker runs in its session's working
|
|
119
123
|
# directory.
|
|
@@ -121,6 +125,8 @@ module Samagotchi
|
|
|
121
125
|
refs = RowRefs.for(tools && !call[:name].nil? ? tools[call[:name]]&.refs : nil, call, cwd: session_cwd(context))
|
|
122
126
|
fields[:refs] = refs unless refs.empty?
|
|
123
127
|
started.merge!(fields)
|
|
128
|
+
# The row's clock: tool_call_completed's duration_ms runs from here.
|
|
129
|
+
started_clock = @clock.call
|
|
124
130
|
emit(on_stream_event, started)
|
|
125
131
|
|
|
126
132
|
# The ask comes after tool_call_started: the UI shows the tool line,
|
|
@@ -175,9 +181,20 @@ module Samagotchi
|
|
|
175
181
|
completed[:diff] = diff if diff
|
|
176
182
|
completed[:waited_ms] = waited_ms if waited_ms
|
|
177
183
|
completed[:output_file] = output_file if output_file
|
|
184
|
+
completed[:tool_id] = tool_id if tool_id
|
|
185
|
+
# A long output's web row (its hover): the cut a reload draws from the
|
|
186
|
+
# saved output (MessageParts#tool_part), so live and reload show the
|
|
187
|
+
# same text. The TUI keeps +output+.
|
|
188
|
+
if capped.length > ToolOutputCut::WEB_PREVIEW_CHARS
|
|
189
|
+
completed[:preview] = ToolOutputCut.preview(capped, cap: ToolOutputCut::WEB_PREVIEW_CHARS, tool: call[:name])
|
|
190
|
+
end
|
|
178
191
|
# Also here: a UI that missed the start (a replay gap) builds its row
|
|
179
192
|
# from this event (its title is in the activity).
|
|
180
193
|
completed.merge!(fields.slice(*ToolRowFields::EVENT_KEYS))
|
|
194
|
+
# The row's duration, the one every UI shows (live, a reload's tool
|
|
195
|
+
# record, the attached terminal): SessionMetrics' definition and
|
|
196
|
+
# rounding order, from the started emit to this one, less the wait.
|
|
197
|
+
completed[:duration_ms] = [((@clock.call - started_clock) * 1000).round - (waited_ms || 0), 0].max
|
|
181
198
|
emit(on_stream_event, completed)
|
|
182
199
|
# After the completed event: a hook's card (check-in, skills) prints
|
|
183
200
|
# under the call's tool row, not before it.
|
|
@@ -139,10 +139,7 @@ module Samagotchi
|
|
|
139
139
|
|
|
140
140
|
toolkit = Toolkit.new(name: name)
|
|
141
141
|
if session.toolkit == toolkit
|
|
142
|
-
if session.
|
|
143
|
-
session.toolkit_origin = "chosen"
|
|
144
|
-
session.save(state_dir: @state_dir.call)
|
|
145
|
-
end
|
|
142
|
+
session.save(state_dir: @state_dir.call) if session.choose_toolkit_origin!
|
|
146
143
|
return true
|
|
147
144
|
end
|
|
148
145
|
|
|
@@ -47,10 +47,7 @@ module Samagotchi
|
|
|
47
47
|
# Nothing the prompt carries changes (full +delegate on full): only
|
|
48
48
|
# the origin is saved, so a no-op choice still counts as user-selected.
|
|
49
49
|
if after.prompt_key == before.prompt_key
|
|
50
|
-
if session.
|
|
51
|
-
session.toolkit_origin = "chosen"
|
|
52
|
-
@save.call(session)
|
|
53
|
-
end
|
|
50
|
+
@save.call(session) if session.choose_toolkit_origin!
|
|
54
51
|
return [listing(before) + "\n(no change to what the next turn declares)", false, nil]
|
|
55
52
|
end
|
|
56
53
|
|