samagotchi 0.155.0 → 0.157.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 +57 -1
- data/bin/chi +2 -1
- data/docs/architecture.md +22 -13
- data/docs/bundles.md +3 -1
- data/docs/cli.md +9 -8
- data/docs/configuration.md +36 -4
- data/docs/guardrails.md +9 -5
- data/docs/hooks.md +11 -5
- data/docs/internals/anthropic.md +1 -1
- data/docs/internals/attached-context.md +3 -2
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/bridge.md +83 -6
- data/docs/internals/bundles.md +28 -18
- data/docs/internals/context-telemetry.md +4 -3
- data/docs/internals/engine.md +641 -937
- data/docs/internals/llm-context-forget.md +8 -3
- data/docs/internals/objectives.md +2 -2
- data/docs/internals/participants.md +3 -2
- data/docs/internals/prompt-caching.md +4 -1
- data/docs/internals/remote-sessions.md +8 -3
- data/docs/internals/web-frontend.md +3 -2
- data/docs/internals/working-set.md +2 -2
- data/docs/memory.md +13 -2
- data/docs/plugins.md +35 -2
- data/docs/sessions.md +11 -11
- data/docs/sub-agent.md +17 -2
- data/docs/system-one.md +3 -2
- data/docs/testing.md +53 -2
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +4 -3
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -1
- data/lib/samagotchi/bootstrap_command.rb +3 -2
- data/lib/samagotchi/bridge/card_places.rb +10 -4
- data/lib/samagotchi/bridge/card_store.rb +3 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +109 -9
- data/lib/samagotchi/bridge/turn_drawings.rb +265 -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/broadcast_command.rb +3 -3
- data/lib/samagotchi/bundle_command/check.rb +18 -0
- data/lib/samagotchi/bundle_command/try/sandbox.rb +2 -2
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +5 -2
- 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 +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
- data/lib/samagotchi/child_move.rb +13 -57
- data/lib/samagotchi/child_reports.rb +2 -2
- data/lib/samagotchi/children_status.rb +15 -21
- data/lib/samagotchi/cli/parent_wait.rb +9 -23
- data/lib/samagotchi/config.rb +20 -112
- data/lib/samagotchi/context_command.rb +1 -2
- data/lib/samagotchi/context_sources.rb +5 -4
- data/lib/samagotchi/context_window.rb +9 -12
- data/lib/samagotchi/desktop/macos.rb +1 -1
- data/lib/samagotchi/effective_model.rb +57 -22
- data/lib/samagotchi/engine/context_judge.rb +19 -11
- data/lib/samagotchi/engine/messages.rb +1 -1
- data/lib/samagotchi/engine/models.rb +5 -2
- data/lib/samagotchi/engine/objectives.rb +29 -11
- data/lib/samagotchi/engine/plugins.rb +1 -1
- data/lib/samagotchi/engine/recap.rb +8 -4
- data/lib/samagotchi/engine/side_requests.rb +21 -11
- data/lib/samagotchi/engine/toolkit.rb +10 -9
- data/lib/samagotchi/engine/turn_end.rb +1 -1
- data/lib/samagotchi/engine/turn_helpers.rb +7 -7
- data/lib/samagotchi/engine/verify.rb +1 -1
- data/lib/samagotchi/engine.rb +43 -12
- data/lib/samagotchi/events.rb +3 -2
- data/lib/samagotchi/extension_load.rb +6 -3
- data/lib/samagotchi/guardrails/targets.rb +24 -9
- data/lib/samagotchi/guardrails_command.rb +1 -1
- data/lib/samagotchi/host_address.rb +52 -0
- data/lib/samagotchi/host_config.rb +72 -1
- data/lib/samagotchi/host_model.rb +2 -6
- data/lib/samagotchi/host_registry.rb +58 -46
- data/lib/samagotchi/kernel_loop.rb +53 -124
- data/lib/samagotchi/llm/anthropic_wire.rb +2 -2
- data/lib/samagotchi/llm/chat_loop.rb +40 -21
- data/lib/samagotchi/llm/native_backend.rb +1 -1
- data/lib/samagotchi/llm_context_apply.rb +54 -1
- data/lib/samagotchi/llm_context_notice.rb +1 -1
- data/lib/samagotchi/llm_context_strategy.rb +29 -67
- data/lib/samagotchi/llm_context_view.rb +9 -0
- data/lib/samagotchi/memory_bundle/asset_installer.rb +71 -76
- data/lib/samagotchi/memory_bundle/builder.rb +2 -1
- 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_copy.rb +2 -1
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +8 -1
- data/lib/samagotchi/memory_bundle/installer.rb +185 -169
- data/lib/samagotchi/memory_bundle/manifest.rb +14 -4
- 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 +15 -1
- data/lib/samagotchi/memory_bundle/remerge.rb +25 -12
- data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -6
- data/lib/samagotchi/memory_bundle/status.rb +14 -20
- data/lib/samagotchi/memory_bundle/system_bundle.rb +24 -6
- data/lib/samagotchi/memory_bundle/trash.rb +23 -8
- data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
- data/lib/samagotchi/memory_usage.rb +1 -1
- data/lib/samagotchi/model_lookup.rb +75 -0
- data/lib/samagotchi/model_price.rb +4 -10
- data/lib/samagotchi/model_profile.rb +7 -6
- data/lib/samagotchi/model_settings.rb +37 -2
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/note_command.rb +2 -3
- data/lib/samagotchi/objectives_audit.rb +4 -3
- data/lib/samagotchi/participant_mode.rb +2 -1
- data/lib/samagotchi/participants.rb +2 -2
- data/lib/samagotchi/plugin/api.rb +41 -6
- data/lib/samagotchi/plugin/context.rb +1 -1
- data/lib/samagotchi/plugin/loader.rb +33 -10
- data/lib/samagotchi/plugin/sessions.rb +3 -6
- data/lib/samagotchi/plugin/tool_spec.rb +5 -3
- data/lib/samagotchi/project_env.rb +1 -1
- data/lib/samagotchi/reasoning_history.rb +14 -20
- data/lib/samagotchi/recap_place.rb +27 -0
- data/lib/samagotchi/reminder_carry.rb +1 -1
- 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/reprefill_meter.rb +45 -0
- data/lib/samagotchi/review_command.rb +1 -1
- data/lib/samagotchi/sampling_settings.rb +9 -16
- data/lib/samagotchi/self_report.rb +36 -37
- data/lib/samagotchi/send_command.rb +17 -23
- data/lib/samagotchi/session.rb +370 -445
- data/lib/samagotchi/session_access/local.rb +12 -11
- data/lib/samagotchi/session_access/remote.rb +22 -6
- data/lib/samagotchi/session_archive_command.rb +2 -2
- data/lib/samagotchi/session_chain.rb +7 -6
- data/lib/samagotchi/session_continue.rb +6 -5
- data/lib/samagotchi/session_delete_command.rb +1 -1
- data/lib/samagotchi/session_field.rb +133 -0
- data/lib/samagotchi/session_inbox.rb +49 -1
- data/lib/samagotchi/session_manager/delivery.rb +254 -0
- data/lib/samagotchi/session_manager/emptiness.rb +139 -0
- data/lib/samagotchi/session_manager/lifecycle.rb +274 -0
- data/lib/samagotchi/session_manager/listing.rb +118 -0
- data/lib/samagotchi/session_manager/removal.rb +190 -0
- data/lib/samagotchi/session_manager/spawn.rb +242 -0
- data/lib/samagotchi/session_manager/worker_command.rb +157 -0
- data/lib/samagotchi/session_manager.rb +28 -1541
- data/lib/samagotchi/session_metrics.rb +8 -5
- 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/share_command.rb +1 -1
- data/lib/samagotchi/starting_marker.rb +65 -0
- data/lib/samagotchi/steer_cut_after.rb +7 -14
- data/lib/samagotchi/system_one/recorder.rb +49 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
- data/lib/samagotchi/terminal_ui/exit_lines.rb +1 -1
- data/lib/samagotchi/terminal_ui/join_replay.rb +86 -8
- data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
- data/lib/samagotchi/terminal_ui.rb +5 -7
- data/lib/samagotchi/testing/install.rb +2 -1
- data/lib/samagotchi/testing/sessions.rb +1 -1
- data/lib/samagotchi/thinking.rb +16 -21
- data/lib/samagotchi/token_usage.rb +1 -1
- data/lib/samagotchi/tool_activity.rb +10 -4
- data/lib/samagotchi/tool_declarations.rb +2 -2
- data/lib/samagotchi/tool_output_cut.rb +17 -0
- data/lib/samagotchi/tool_response.rb +5 -3
- data/lib/samagotchi/tool_runner.rb +25 -5
- data/lib/samagotchi/toolkit_command.rb +1 -1
- data/lib/samagotchi/tools/delegate.rb +55 -45
- data/lib/samagotchi/tools/delegate_relay.rb +5 -5
- data/lib/samagotchi/tools/delegate_result.rb +5 -5
- data/lib/samagotchi/tools/delegate_wait.rb +41 -17
- data/lib/samagotchi/tools/index_hint.rb +14 -0
- data/lib/samagotchi/tools/list_sessions.rb +7 -7
- data/lib/samagotchi/tools/registry.rb +28 -11
- data/lib/samagotchi/tools/send_note.rb +4 -6
- data/lib/samagotchi/tools/tool_index.rb +52 -51
- data/lib/samagotchi/tools/tool_surface.rb +22 -8
- data/lib/samagotchi/turn_context.rb +2 -1
- data/lib/samagotchi/turn_stages/context_judge.rb +38 -36
- data/lib/samagotchi/turn_stages/objectives.rb +56 -52
- data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +13 -17
- data/lib/samagotchi/turn_stages/verify_anchors.rb +9 -10
- data/lib/samagotchi/turn_stages/warmup.rb +14 -15
- 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 +45 -28
- data/lib/samagotchi/web/message_display.rb +57 -7
- data/lib/samagotchi/web/participant_access.rb +3 -3
- 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/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 +6 -6
- data/lib/samagotchi/web/session_control.rb +4 -4
- data/lib/samagotchi/web/session_create.rb +3 -3
- data/lib/samagotchi/web/session_hub.rb +9 -17
- data/lib/samagotchi/web/session_show.rb +67 -5
- data/lib/samagotchi/web/session_summary.rb +39 -28
- data/lib/samagotchi/worker.rb +83 -32
- data/lib/samagotchi/worker_boot.rb +184 -0
- data/lib/samagotchi/worker_link.rb +3 -0
- data/lib/samagotchi/worker_signals.rb +1 -1
- data/lib/samagotchi/worker_task_wakes.rb +2 -2
- metadata +32 -4
- data/lib/samagotchi/send_command/wait_target.rb +0 -85
data/lib/samagotchi/web/app.rb
CHANGED
|
@@ -27,6 +27,7 @@ require_relative "../session_metrics"
|
|
|
27
27
|
require_relative "../session_commands"
|
|
28
28
|
require_relative "../steer"
|
|
29
29
|
require_relative "../host_registry"
|
|
30
|
+
require_relative "../effective_model"
|
|
30
31
|
require_relative "../llm_context_override"
|
|
31
32
|
require_relative "../working_set"
|
|
32
33
|
require_relative "../model_catalog"
|
|
@@ -75,6 +76,7 @@ require_relative "session_show"
|
|
|
75
76
|
require_relative "session_create"
|
|
76
77
|
require_relative "session_control"
|
|
77
78
|
require_relative "verify_fresh_routes"
|
|
79
|
+
require_relative "reload_check_route"
|
|
78
80
|
require_relative "push_routes"
|
|
79
81
|
require_relative "block_renderer_routes"
|
|
80
82
|
require_relative "update_check"
|
|
@@ -109,6 +111,7 @@ module Samagotchi
|
|
|
109
111
|
include SessionCreate
|
|
110
112
|
include SessionControl
|
|
111
113
|
include VerifyFreshRoutes
|
|
114
|
+
include ReloadCheckRoute
|
|
112
115
|
include PushRoutes
|
|
113
116
|
include BlockRendererRoutes
|
|
114
117
|
include ProxyGate
|
|
@@ -146,6 +149,9 @@ module Samagotchi
|
|
|
146
149
|
# freshly-spawned worker's bridge before answering POST /api/sessions.
|
|
147
150
|
# @param working_set [Boolean] the stage view's working set
|
|
148
151
|
# (web.working_set); ?working_set=1|0 overrides it for one page load
|
|
152
|
+
# @param reload_check [Boolean] the page checks each finished turn
|
|
153
|
+
# against a reload of it and posts the counts (web.reload_check,
|
|
154
|
+
# opt-in; POST /api/sessions/:id/reload_check logs them)
|
|
149
155
|
# @param annotate_presets [String, Array] the quick replies next to
|
|
150
156
|
# Annotate (web.annotate_presets, "|"-separated); "" shows none. The
|
|
151
157
|
# page parses them (annotate_presets.js).
|
|
@@ -184,6 +190,7 @@ module Samagotchi
|
|
|
184
190
|
events_heartbeat: EVENTS_HEARTBEAT, events_queue: EVENTS_QUEUE,
|
|
185
191
|
registry: nil, models_wait_timeout: MODELS_WAIT_TIMEOUT, lan: nil,
|
|
186
192
|
working_set: Config::BY_KEY["web.working_set"].default, wait_poll_interval: ReplyWait::POLL_INTERVAL,
|
|
193
|
+
reload_check: Config::BY_KEY["web.reload_check"].default,
|
|
187
194
|
grants: nil, public_url: nil, public_token: nil, push: nil, start_dir: nil, update_runner: nil,
|
|
188
195
|
update_blocked: nil, update_ops: nil)
|
|
189
196
|
@start_dir = start_dir
|
|
@@ -199,6 +206,8 @@ module Samagotchi
|
|
|
199
206
|
@bridge_wait_timeout = bridge_wait_timeout
|
|
200
207
|
@markdown_renderer = MarkdownRenderer.new(enabled: markdown)
|
|
201
208
|
@working_set = working_set
|
|
209
|
+
@reload_check = reload_check
|
|
210
|
+
@reload_checks = ReloadCheckRoute::Seen.new
|
|
202
211
|
@annotate_presets = annotate_presets.is_a?(Array) ? annotate_presets.join("|") : annotate_presets.to_s
|
|
203
212
|
@editor_url = Editor.template(editor)
|
|
204
213
|
@hub = hub
|
|
@@ -292,6 +301,7 @@ module Samagotchi
|
|
|
292
301
|
["GET", %r{\A/api/sessions/([^/]+)/worker\z}, :handle_worker, :owner, :session],
|
|
293
302
|
["POST", %r{\A/api/sessions/([^/]+)/exit\z}, :handle_exit, :owner, :session],
|
|
294
303
|
["POST", %r{\A/api/sessions/([^/]+)/recap\z}, :handle_recap, :owner, :session],
|
|
304
|
+
["POST", %r{\A/api/sessions/([^/]+)/reload_check\z}, :handle_reload_check, :owner, :session],
|
|
295
305
|
["POST", %r{\A/api/sessions/([^/]+)/guardrails/wrong\z}, :handle_system_one_wrong, :owner, :session],
|
|
296
306
|
["GET", %r{\A/api/sessions/([^/]+)/stats\z}, :handle_stats, :owner, :session],
|
|
297
307
|
["GET", %r{\A/api/sessions/([^/]+)/children/counts\z}, :handle_children_counts, :owner, :session],
|
|
@@ -353,10 +363,10 @@ module Samagotchi
|
|
|
353
363
|
# "..", a %2F) is an unknown session before any handler makes a path.
|
|
354
364
|
# (/api/sessions/resolve and /summaries take no id.)
|
|
355
365
|
if req.path_info.start_with?("/api/sessions/") && !m.captures.empty? && !Session.valid_id?(m.captures.first)
|
|
356
|
-
return
|
|
366
|
+
return session_not_found(m.captures.first)
|
|
357
367
|
end
|
|
358
368
|
|
|
359
|
-
return session_route(principal) { send(handler, req, *m.captures) } if kind == :session
|
|
369
|
+
return session_route(principal, m.captures.first) { send(handler, req, *m.captures) } if kind == :session
|
|
360
370
|
|
|
361
371
|
return send(handler, req, *m.captures)
|
|
362
372
|
end
|
|
@@ -383,11 +393,14 @@ module Samagotchi
|
|
|
383
393
|
# raises (Session.load, the manager's find) is a 404 not_found with
|
|
384
394
|
# its message. Only these routes: elsewhere an ArgumentError is a bug,
|
|
385
395
|
# #route's 500. A handler's own rescues run first.
|
|
386
|
-
def session_route(principal = nil)
|
|
396
|
+
def session_route(principal = nil, id = nil)
|
|
387
397
|
yield
|
|
388
398
|
rescue ArgumentError => e
|
|
389
|
-
|
|
390
|
-
|
|
399
|
+
return error_response(404, "not_found", e.message) if principal.nil? || principal.owner?
|
|
400
|
+
|
|
401
|
+
# A participant's 404 never carries a lookup's words (a path): it
|
|
402
|
+
# names their own session's id (valid_id? passed), nothing else.
|
|
403
|
+
session_not_found(id)
|
|
391
404
|
end
|
|
392
405
|
|
|
393
406
|
# API requests at debug level (not the page and its assets): method,
|
|
@@ -1152,32 +1165,31 @@ module Samagotchi
|
|
|
1152
1165
|
Degrade.call(:web, "model_sampling_unreadable") do
|
|
1153
1166
|
return if registry.nil? || models.empty?
|
|
1154
1167
|
|
|
1155
|
-
settings =
|
|
1168
|
+
settings = ModelSettings.all
|
|
1156
1169
|
models.each do |model|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
summary = SamplingSettings.summary(target, names: names, models: settings)
|
|
1170
|
+
effective = EffectiveModel.of(registry, ModelChoice.new(name: model[:name], typed: model[:name]), models: settings)
|
|
1171
|
+
summary = SamplingSettings.summary(effective.lookup)
|
|
1160
1172
|
model[:sampling] = summary if summary
|
|
1161
|
-
add_llm_context(model,
|
|
1162
|
-
add_thinking(model,
|
|
1173
|
+
add_llm_context(model, effective)
|
|
1174
|
+
add_thinking(model, effective)
|
|
1163
1175
|
end
|
|
1164
1176
|
end
|
|
1165
1177
|
end
|
|
1166
1178
|
|
|
1167
1179
|
# The LLM context +model+ starts under (the start page's ⚙ panel);
|
|
1168
|
-
# one that can't be read leaves this model without
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1180
|
+
# one that can't be read leaves this model without (the key absent),
|
|
1181
|
+
# not the others.
|
|
1182
|
+
def add_llm_context(model, effective)
|
|
1183
|
+
explained = effective.llm_context_explained({})
|
|
1184
|
+
model[:llm_context] = explained.summary(nil) if explained
|
|
1173
1185
|
end
|
|
1174
1186
|
|
|
1175
1187
|
# The thinking level +model+ starts at, with where it came from (the
|
|
1176
|
-
# start page's ⚙ panel; SettingExplained#summary, no session)
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1188
|
+
# start page's ⚙ panel; SettingExplained#summary, no session); one
|
|
1189
|
+
# that can't be read leaves this model without (the key absent).
|
|
1190
|
+
def add_thinking(model, effective)
|
|
1191
|
+
explained = effective.thinking_explained(own: nil)
|
|
1192
|
+
model[:thinking] = explained.summary if explained
|
|
1181
1193
|
end
|
|
1182
1194
|
|
|
1183
1195
|
# The served model a session's last generation reported (analytics.json,
|
|
@@ -1207,17 +1219,16 @@ module Samagotchi
|
|
|
1207
1219
|
end
|
|
1208
1220
|
end
|
|
1209
1221
|
|
|
1210
|
-
#
|
|
1211
|
-
#
|
|
1222
|
+
# The shared registry (HostRegistry.current: built again when
|
|
1223
|
+
# config.yml's hosts: changed), so the model picker lists a host added
|
|
1212
1224
|
# while chi web runs. An injected registry (specs) stays.
|
|
1213
1225
|
def host_registry
|
|
1214
1226
|
@models_mutex.synchronize do
|
|
1215
1227
|
next @registry if @registry_given
|
|
1216
1228
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
@registry =
|
|
1220
|
-
@registry_hosts = hosts
|
|
1229
|
+
current = HostRegistry.current
|
|
1230
|
+
unless current.equal?(@registry)
|
|
1231
|
+
@registry = current
|
|
1221
1232
|
@models_thread = nil # a listing of the old hosts
|
|
1222
1233
|
end
|
|
1223
1234
|
@registry
|
|
@@ -1560,7 +1571,7 @@ module Samagotchi
|
|
|
1560
1571
|
begin
|
|
1561
1572
|
session = @session_class.load(id, state_dir: default_state_dir)
|
|
1562
1573
|
rescue ArgumentError
|
|
1563
|
-
return
|
|
1574
|
+
return session_not_found(id)
|
|
1564
1575
|
end
|
|
1565
1576
|
# Its terminal's alone: a tab's stream would keep its worker up.
|
|
1566
1577
|
return scratch_refused(id) if session.scratch
|
|
@@ -1902,6 +1913,8 @@ module Samagotchi
|
|
|
1902
1913
|
attrs["home"] = Dir.home
|
|
1903
1914
|
# Only when on: the page reads its presence.
|
|
1904
1915
|
attrs["working-set"] = "1" if working_set?(req.params["working_set"])
|
|
1916
|
+
# The opt-in reload check (reload_check.js): only when on.
|
|
1917
|
+
attrs["reload-check"] = "1" if @reload_check
|
|
1905
1918
|
attrs.merge!(annotate_presets_data)
|
|
1906
1919
|
dir, = scope_dir(req.params["dir"])
|
|
1907
1920
|
unless dir
|
|
@@ -1976,6 +1989,10 @@ module Samagotchi
|
|
|
1976
1989
|
error_response(404, "not_found", "not found: #{path}")
|
|
1977
1990
|
end
|
|
1978
1991
|
|
|
1992
|
+
# 404 not_found for an id (or prefix) that names no session, in
|
|
1993
|
+
# Session::NotFound's words (SessionAccess::Remote maps it back).
|
|
1994
|
+
def session_not_found(given) = error_response(404, "not_found", Session::NotFound.new(given).message)
|
|
1995
|
+
|
|
1979
1996
|
# One tab's GET /api/events: a bounded queue behind a lambda sink,
|
|
1980
1997
|
# subscribed and snapshotted as one step under the hub's lock, then
|
|
1981
1998
|
# drained onto the socket. The loop ends when the hub stops or the
|
|
@@ -9,12 +9,14 @@ require_relative "../message_parts"
|
|
|
9
9
|
require_relative "../message_time"
|
|
10
10
|
require_relative "../objectives"
|
|
11
11
|
require_relative "../output_formatter"
|
|
12
|
+
require_relative "../recap_place"
|
|
12
13
|
require_relative "../recap_store"
|
|
13
14
|
require_relative "../ref"
|
|
14
15
|
require_relative "../revise_anchor"
|
|
15
16
|
require_relative "../steer"
|
|
16
17
|
require_relative "../turn_note"
|
|
17
18
|
require_relative "../bridge/card_store"
|
|
19
|
+
require_relative "../bridge/turn_drawings"
|
|
18
20
|
|
|
19
21
|
module Samagotchi
|
|
20
22
|
module Web
|
|
@@ -47,20 +49,26 @@ module Samagotchi
|
|
|
47
49
|
# a live worker's (it counts against its Engine's messages), else
|
|
48
50
|
# recap.json against the session file. A stopped session gets its
|
|
49
51
|
# recap without a worker being woken.
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
+
# The last turn it covers names its place (opening_id, RecapPlace);
|
|
53
|
+
# an older worker's saved_recap has none: the page counts turns_since.
|
|
54
|
+
# @return [Hash, nil] {text:, turns_since:, opening_id:, created_at:}
|
|
55
|
+
# (created_at: when it was written, UTC ISO; absent from an older
|
|
56
|
+
# recap)
|
|
52
57
|
def saved_recap_for(id, session, turn_snapshot)
|
|
53
58
|
if turn_snapshot
|
|
54
59
|
saved = turn_snapshot["saved_recap"]
|
|
55
|
-
return saved && { text: saved["text"], turns_since: saved["turns_since"].to_i,
|
|
60
|
+
return saved && { text: saved["text"], turns_since: saved["turns_since"].to_i, opening_id: saved["opening_id"],
|
|
61
|
+
created_at: saved["created_at"] }.compact
|
|
56
62
|
end
|
|
57
63
|
|
|
58
64
|
saved = RecapStore.read(@session_class.session_dir(id, state_dir: default_state_dir))
|
|
59
65
|
return nil unless saved
|
|
60
66
|
|
|
61
67
|
messages = Array(session.messages)
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
covered = saved[:covered].to_i
|
|
69
|
+
since = messages.drop(covered).count { |m| Samagotchi::Steer.turn_opening?(m) }
|
|
70
|
+
{ text: saved[:text], turns_since: since, opening_id: RecapPlace.opening_id(messages, covered),
|
|
71
|
+
created_at: saved[:created_at] }.compact
|
|
64
72
|
rescue StandardError
|
|
65
73
|
nil
|
|
66
74
|
end
|
|
@@ -91,14 +99,22 @@ module Samagotchi
|
|
|
91
99
|
# @param markdown [Boolean] false: no html for a delegate report or an
|
|
92
100
|
# answer (the participant review drops both and renders its replies
|
|
93
101
|
# from their redacted text, ParticipantReview#with_reply_html)
|
|
94
|
-
|
|
102
|
+
# @param drawings [Array<Bridge::TurnDrawings::Line>] the valid lines
|
|
103
|
+
# (TurnDrawings.valid against +msgs+): each item of a kind a drawing
|
|
104
|
+
# draws (#drawn_kind?) from a line's range gets `drawn: <turn_id>`
|
|
105
|
+
# @param strip_drawn [Boolean] a stamped item's parts go (the drawing
|
|
106
|
+
# has them); it keeps its ✂ marks as `marks: {tool_id => edit}`
|
|
107
|
+
def messages_for_display(msgs, parts: false, cwd: nil, markdown: true, drawings: [], strip_drawn: false)
|
|
95
108
|
filtered = []
|
|
96
109
|
list = Array(msgs)
|
|
110
|
+
# Each item's message index, for the drawings' stamps.
|
|
111
|
+
origins = []
|
|
97
112
|
# The outputs' ✂ marks (LLM context edits), by id, for the parts.
|
|
98
113
|
marks = parts ? MessageParts.edit_marks(list) : {}
|
|
99
114
|
# The list before each objectives note, for what its entry says changed.
|
|
100
115
|
objectives = nil
|
|
101
116
|
list.each_with_index do |m, index|
|
|
117
|
+
origins.fill(index - 1, origins.size...filtered.size)
|
|
102
118
|
role = (m[:role] || m["role"]).to_s
|
|
103
119
|
content = (m[:content] || m["content"]).to_s
|
|
104
120
|
if Samagotchi::ContextNote.note?(m)
|
|
@@ -205,11 +221,45 @@ module Samagotchi
|
|
|
205
221
|
message[:parts] = did if did
|
|
206
222
|
filtered << message
|
|
207
223
|
end
|
|
208
|
-
filtered
|
|
224
|
+
origins.fill(list.size - 1, origins.size...filtered.size)
|
|
225
|
+
stamp_drawn(filtered, origins, Array(drawings), strip: strip_drawn)
|
|
209
226
|
rescue StandardError
|
|
210
227
|
[]
|
|
211
228
|
end
|
|
212
229
|
|
|
230
|
+
# +items+ with a drawn turn's stamp on each item of a kind its drawing
|
|
231
|
+
# draws (the opening, steps, steers, merged lines, the empty answer):
|
|
232
|
+
# a context note, a `!cmd`'s record and anything else keep today's
|
|
233
|
+
# place. +origins+: each item's message index.
|
|
234
|
+
def stamp_drawn(items, origins, drawings, strip:)
|
|
235
|
+
return items if drawings.empty?
|
|
236
|
+
|
|
237
|
+
items.each_with_index.map do |item, i|
|
|
238
|
+
line = drawings.find { |l| l.range.cover?(origins[i]) }
|
|
239
|
+
next item unless line && drawn_kind?(item)
|
|
240
|
+
|
|
241
|
+
stamped = item.merge(drawn: line.turn_id)
|
|
242
|
+
strip ? without_parts(stamped) : stamped
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def drawn_kind?(item)
|
|
247
|
+
case item[:role]
|
|
248
|
+
when "note" then item[:turn_start] == true
|
|
249
|
+
when "user" then !(item[:turn_id].nil? && item[:content].match?(/\A!\(.*\)\n/))
|
|
250
|
+
else %w[assistant steer empty_answer].include?(item[:role])
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# A drawn item without its parts: its ✂ marks by output id stay (an
|
|
255
|
+
# overlay on the drawing's rows: later state, as a card's latest body).
|
|
256
|
+
def without_parts(item)
|
|
257
|
+
tools = Array(item.dig(:parts, :tools))
|
|
258
|
+
marks = tools.filter_map { |tool| [tool[:tool_id], tool[:edit]] if tool[:tool_id] && tool[:edit] }.to_h
|
|
259
|
+
out = item.except(:parts)
|
|
260
|
+
marks.empty? ? out : out.merge(marks: marks)
|
|
261
|
+
end
|
|
262
|
+
|
|
213
263
|
# A turn opening's Revise stamp as {revises: {turn_id:}}, checked again
|
|
214
264
|
# (a stored file is never trusted), or nothing.
|
|
215
265
|
def revises_of(message)
|
|
@@ -185,7 +185,7 @@ module Samagotchi
|
|
|
185
185
|
end
|
|
186
186
|
|
|
187
187
|
def unknown_session(id)
|
|
188
|
-
|
|
188
|
+
session_not_found(id)
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
# 403 not_permitted for a participant (the owner is never refused by
|
|
@@ -388,14 +388,14 @@ module Samagotchi
|
|
|
388
388
|
def participant_resolve(grant, given)
|
|
389
389
|
return json_response(200, { id: grant.session_id }) if grant.session_id.start_with?(given)
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
session_not_found(given)
|
|
392
392
|
end
|
|
393
393
|
|
|
394
394
|
# POST …/wait/prepare for a participant (an agent's chi --remote
|
|
395
395
|
# send --wait, D11): only for a turn of theirs (+turn+, the enqueued
|
|
396
396
|
# id their POST …/turn answered); the body's busy, queue and created
|
|
397
397
|
# don't count. The answer is what their wait on that turn needs
|
|
398
|
-
# (
|
|
398
|
+
# (ReplyWait::Target.wait_only with the turn asked, the wait's
|
|
399
399
|
# baseline), never the owner's wait state: the last turn's end (the
|
|
400
400
|
# baseline's last_turn), whether their turn is known, whether a worker
|
|
401
401
|
# runs it, and last_turn_id only when it is their turn. No cursor
|
|
@@ -50,8 +50,11 @@ export function newActivity() {
|
|
|
50
50
|
// refs: a plugin tool's refs (the server's RowRefs: [{kind, target,
|
|
51
51
|
// label?, actions}]): the first on the row's title, the others in its
|
|
52
52
|
// menu (refs.js bindRowRefs).
|
|
53
|
+
// tool_id: the id the call's output is saved under (the server's ToolIds,
|
|
54
|
+
// "t41"; not a row field there), so a row names the output a reload's
|
|
55
|
+
// part and a strategy's edit name.
|
|
53
56
|
export const ACTIVITY_FIELDS = Object.freeze(["title", "called_as", "via"]);
|
|
54
|
-
export const EVENT_FIELDS = Object.freeze(["view", "ref", "refs"]);
|
|
57
|
+
export const EVENT_FIELDS = Object.freeze(["view", "ref", "refs", "tool_id"]);
|
|
55
58
|
const ROW_FIELDS = Object.freeze([...ACTIVITY_FIELDS, ...EVENT_FIELDS]);
|
|
56
59
|
|
|
57
60
|
// The +fields+ of +source+ that are set, as an object (for a spread).
|
|
@@ -115,10 +118,10 @@ function markStart(row, event, now) {
|
|
|
115
118
|
|
|
116
119
|
// Update (or synthesize) a row from a :tool_call_completed event. Returns the row.
|
|
117
120
|
// If no matching :tool_call_started was seen (e.g. a replay gap), a row is
|
|
118
|
-
// created on the fly so the completion is never lost. Its duration
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
// the call waited for an approval (waited_ms)
|
|
121
|
+
// created on the fly so the completion is never lost. Its duration: the
|
|
122
|
+
// event's duration_ms (the worker's, the figure a reload's tool record and
|
|
123
|
+
// the terminal show), else (an older worker) from the start the page saw to
|
|
124
|
+
// +now+, less the time the call waited for an approval (waited_ms).
|
|
122
125
|
export function addCompleted(model, event = {}, now = null) {
|
|
123
126
|
const iteration = event.iteration ?? 0;
|
|
124
127
|
const callIndex = event.call_index ?? 0;
|
|
@@ -137,8 +140,10 @@ export function addCompleted(model, event = {}, now = null) {
|
|
|
137
140
|
row = synthesized.row;
|
|
138
141
|
}
|
|
139
142
|
row.status = completedStatus(event.activity?.status);
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
// A long output's web cut (the worker's preview: the text a reload's row
|
|
144
|
+
// has from the saved output), else the output itself.
|
|
145
|
+
row.output = event.preview ?? (event.output || "");
|
|
146
|
+
row.output_truncated = event.preview != null || !!event.output_truncated;
|
|
142
147
|
if (event.activity?.params) row.params = event.activity.params;
|
|
143
148
|
// The completed event carries the title (ACTIVITY_FIELDS) in its activity.
|
|
144
149
|
Object.assign(row, pickFields(event.activity, ACTIVITY_FIELDS));
|
|
@@ -160,6 +165,10 @@ export function addCompleted(model, event = {}, now = null) {
|
|
|
160
165
|
// What an edit/write changed in its file (a reload draws it from the
|
|
161
166
|
// saved parts, turn_html.js toolRowTemplate).
|
|
162
167
|
if (event.diff) row.diff = event.diff;
|
|
168
|
+
// An LLM context edit's ✂ mark on its output, later state a drawn turn's
|
|
169
|
+
// row takes from its saved item (turn_block.js drawnTurnNodes); no live
|
|
170
|
+
// event carries one.
|
|
171
|
+
if (event.edit) row.edit = event.edit;
|
|
163
172
|
const duration = callDuration(row, event, now);
|
|
164
173
|
if (duration !== null) row.duration_ms = duration;
|
|
165
174
|
return row;
|
|
@@ -53,6 +53,7 @@ import { elementOf, emptyAnswerTemplate } from "./turn_html.js";
|
|
|
53
53
|
import { createBubbles, hintEl, outputBubbleTemplate } from "./bubbles.js";
|
|
54
54
|
import { createCardsPlace } from "./cards_place.js";
|
|
55
55
|
import { createStageView } from "./stage_view.js";
|
|
56
|
+
import { createReloadCheck } from "./reload_check.js";
|
|
56
57
|
import { createWorkingSet, groupItems } from "./working_set_view.js";
|
|
57
58
|
import { createObjectives } from "./objectives_wiring.js";
|
|
58
59
|
import { turnGroups } from "./timing.js";
|
|
@@ -356,6 +357,17 @@ const turnDom = createStageView({
|
|
|
356
357
|
workingSet,
|
|
357
358
|
// Stop turn in its status row (disabled until the request settles).
|
|
358
359
|
onCancel: () => handleCancel(),
|
|
360
|
+
onHandedOff: (handed) => reloadCheck.handedOff(handed),
|
|
361
|
+
});
|
|
362
|
+
// The opt-in reload check (web.reload_check): a handed-off turn against a
|
|
363
|
+
// reload of it, the counts to chi web (reload_check.js).
|
|
364
|
+
const reloadCheck = createReloadCheck({
|
|
365
|
+
enabled: () => document.body.dataset.reloadCheck === "1",
|
|
366
|
+
sessionId: () => store.selected,
|
|
367
|
+
running: () => liveTurn.running,
|
|
368
|
+
read: (id) => getSession(id, { parts: true }),
|
|
369
|
+
render: (data) => turnHistoryNodes(data.messages || [], normalizeTiming(data.timing), historyOpts(data)),
|
|
370
|
+
post: (id, body) => api(`/api/sessions/${id}/reload_check`, { method: "POST", body: JSON.stringify(body) }),
|
|
359
371
|
});
|
|
360
372
|
// The history's bottom reserve and the toast's place follow the composer
|
|
361
373
|
// card and the stage floating over the history (dock_reserve.js).
|
|
@@ -1001,7 +1013,7 @@ function renderSessionView(data, { kept = [] } = {}) {
|
|
|
1001
1013
|
});
|
|
1002
1014
|
liveTurn.timing.ended = store.current.timing.turnRecords.length;
|
|
1003
1015
|
const activeTurnId = store.current.timing.activeTurn?.id || null;
|
|
1004
|
-
if (data.messages && data.messages.length) renderHistory(data.messages);
|
|
1016
|
+
if (data.messages && data.messages.length) renderHistory(data.messages, data);
|
|
1005
1017
|
else renderHistory(data.history || []);
|
|
1006
1018
|
seedWorkingSet(data);
|
|
1007
1019
|
objectives.seed(data.objectives);
|
|
@@ -1029,9 +1041,12 @@ function renderSessionView(data, { kept = [] } = {}) {
|
|
|
1029
1041
|
// The recap saved with the session (also a stopped one's), else a live
|
|
1030
1042
|
// worker's from before saved_recap existed. After the cards: the last
|
|
1031
1043
|
// turn's check-in notice goes to the end, and the recap closes the page.
|
|
1032
|
-
|
|
1044
|
+
const saved = data.saved_recap;
|
|
1045
|
+
if (saved) showRecap(saved.text, saved.turns_since, saved.created_at || null, saved.opening_id || null);
|
|
1033
1046
|
else showRecap(data.recap);
|
|
1034
|
-
|
|
1047
|
+
// chi web's unfold of the join (TurnAccumulator.join_events); the page's
|
|
1048
|
+
// own only for a mixed cache (an older chi web's payload has none).
|
|
1049
|
+
const replay = data.replay ?? snapshotEvents({
|
|
1035
1050
|
current_turn: data.current_turn,
|
|
1036
1051
|
queued: data.queued,
|
|
1037
1052
|
queued_commands: data.queued_commands,
|
|
@@ -1048,7 +1063,8 @@ function renderSessionView(data, { kept = [] } = {}) {
|
|
|
1048
1063
|
turnDom.runningWithoutTurn?.(liveTurn.running);
|
|
1049
1064
|
if (liveTurn.running && store.current.timing.activeTurn?.started_at) liveTurn.timing.start(store.current.timing.activeTurn.started_at, activeTurnId);
|
|
1050
1065
|
} else if (liveTurn.running) {
|
|
1051
|
-
// The
|
|
1066
|
+
// The live line's turn: the running turn's record's (the replayed
|
|
1067
|
+
// turn_started carries the same id, on both unfolds).
|
|
1052
1068
|
liveTurn.timing.turnId = activeTurnId;
|
|
1053
1069
|
}
|
|
1054
1070
|
turnDom.flush();
|
|
@@ -1079,7 +1095,7 @@ function renderSessionView(data, { kept = [] } = {}) {
|
|
|
1079
1095
|
function seedWorkingSet(data) {
|
|
1080
1096
|
if (!workingSet) return;
|
|
1081
1097
|
const items = data.messages || [];
|
|
1082
|
-
const groups = typeof items[0] === "object" && items[0]?.role ? turnGroups(items, store.current.timing).filter((g) => g.kind === "turn") : [];
|
|
1098
|
+
const groups = typeof items[0] === "object" && items[0]?.role ? turnGroups(items, store.current.timing, { drawings: data.turn_drawings }).filter((g) => g.kind === "turn") : [];
|
|
1083
1099
|
if (data.current_turn && groups.length) groups.pop();
|
|
1084
1100
|
workingSet.seed(groups.map((g, i) => {
|
|
1085
1101
|
const text = String(items[g.user]?.content ?? "").replace(/\s+/g, " ").trim();
|
|
@@ -1087,17 +1103,22 @@ function seedWorkingSet(data) {
|
|
|
1087
1103
|
}));
|
|
1088
1104
|
}
|
|
1089
1105
|
|
|
1090
|
-
|
|
1106
|
+
// turnHistoryNodes' options for a read's +data+: its turns' drawings
|
|
1107
|
+
// (turn_drawing.js) and cards (a drawn card's body).
|
|
1108
|
+
const historyOpts = (data = {}) => ({ thumbs: (images) => thumbsTemplate(store.selected, images), workingSet: !!workingSet,
|
|
1109
|
+
drawings: data.turn_drawings || null, cards: data.cards || null, capabilities: pageCapabilities });
|
|
1110
|
+
|
|
1111
|
+
function renderHistory(items, data = {}) {
|
|
1091
1112
|
gapClock.seed(null);
|
|
1092
1113
|
if (!items.length) {
|
|
1093
1114
|
historyEl.replaceChildren(hintEl("No output yet. Send a message."));
|
|
1094
1115
|
return;
|
|
1095
1116
|
}
|
|
1096
1117
|
if (typeof items[0] === "object" && items[0].role) {
|
|
1097
|
-
historyEl.replaceChildren(turnHistoryNodes(items, store.current.timing,
|
|
1118
|
+
historyEl.replaceChildren(turnHistoryNodes(items, store.current.timing, historyOpts(data)));
|
|
1098
1119
|
ownerVerify.renderSaved();
|
|
1099
1120
|
// The live dividers count from the history's last activity.
|
|
1100
|
-
gapClock.seed(lastActivity(turnGroups(items, store.current.timing), items));
|
|
1121
|
+
gapClock.seed(lastActivity(turnGroups(items, store.current.timing, { drawings: data.turn_drawings }), items));
|
|
1101
1122
|
linkTextRefs();
|
|
1102
1123
|
} else {
|
|
1103
1124
|
historyEl.replaceChildren(...items.map((c) => elementOf(outputBubbleTemplate(c))));
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// notices, plugins' steers, command and note bubbles. The markup is lit
|
|
5
5
|
// (turn_html.js elementOf); createBubbles puts them in the history.
|
|
6
6
|
import { html, nothing } from "./vendor/lit-html.js";
|
|
7
|
-
import { fullTime, normalize, recapLabel
|
|
7
|
+
import { fullTime, normalize, recapLabel } from "./format.js";
|
|
8
|
+
import { recapStampPlace } from "./recap_stamp.js";
|
|
8
9
|
import { BADGES, elementOf, noteBubbleTemplate, stateBadgeTemplate, steerRowTemplate, userBubbleTemplate } from "./turn_html.js";
|
|
9
10
|
import { thumbsTemplate } from "./images.js";
|
|
10
11
|
import { appendAboveLiveTiming, STOPPED_LINE_CLASS } from "./timing.js";
|
|
@@ -254,14 +255,16 @@ export function createBubbles({
|
|
|
254
255
|
// the end of the history. A turn starting leaves it in place, marked
|
|
255
256
|
// earlier; only a new recap replaces it.
|
|
256
257
|
|
|
257
|
-
|
|
258
|
+
// +openingId+: a saved recap's stamp, the last turn it covers (its place;
|
|
259
|
+
// without one, +turnsSince+ counts it).
|
|
260
|
+
function showRecap(text, turnsSince = 0, createdAt = null, openingId = null) {
|
|
258
261
|
if (!text) return;
|
|
259
262
|
removeRecap();
|
|
260
263
|
const details = elementOf(recapTemplate(text, turnsSince, createdAt));
|
|
261
264
|
// A stale one sits where it was written, above the turns it doesn't cover.
|
|
262
265
|
// Never in the stage: a recap is between turns.
|
|
263
266
|
turnDom.settleFold();
|
|
264
|
-
const prompt =
|
|
267
|
+
const prompt = recapStampPlace(turnPromptBubbles(historyEl), openingId, turnsSince);
|
|
265
268
|
// Above that turn's gap divider too: the recap was written before the quiet stretch ended. The
|
|
266
269
|
// snapshot's cards placed at that prompt (placed first) sit between the divider and the prompt.
|
|
267
270
|
// A delegate report's fold row (answer_fold.js) heads its prompt.
|
|
@@ -314,6 +317,8 @@ export function createBubbles({
|
|
|
314
317
|
// Page-only: kept across a resync of this session (keptCommandBubbles).
|
|
315
318
|
const id = selected();
|
|
316
319
|
if (id) div.dataset.keptFor = id;
|
|
320
|
+
// A reminder's: the turn it went into (restoreCommandBubbles).
|
|
321
|
+
if (view.turnId) div.dataset.turnId = view.turnId;
|
|
317
322
|
drawCommandBubble(div, view);
|
|
318
323
|
// When it happened (a reminder's at): the hover, as a reload's bubbles.
|
|
319
324
|
const when = fullTime(view.at);
|
|
@@ -92,6 +92,14 @@ export function createCardsPlace({
|
|
|
92
92
|
function placeSnapshotCards(entries, { before: end = null } = {}) {
|
|
93
93
|
const users = turnPromptBubbles(historyEl);
|
|
94
94
|
for (const entry of entries) {
|
|
95
|
+
// A drawn turn's own card or row is in its drawing, where live drew it
|
|
96
|
+
// (turn_block.js drawnTurnNodes): only its body refreshes, by id. Its
|
|
97
|
+
// question is still placed from here.
|
|
98
|
+
if (drawnEntry(entry)) {
|
|
99
|
+
const el = isCard(entry) ? cardElement(entry.id) : null;
|
|
100
|
+
if (el) refreshCardBody(el, entry);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
95
103
|
const before = snapshotPlace(users, entry) || (entry.after_failed ? null : end);
|
|
96
104
|
if (isCard(entry)) upsertCard(entry, { before });
|
|
97
105
|
else if (entry.type === "question") placeResolvedQuestion(entry, before);
|
|
@@ -101,6 +109,13 @@ export function createCardsPlace({
|
|
|
101
109
|
}
|
|
102
110
|
}
|
|
103
111
|
|
|
112
|
+
// An in-turn entry (not a question) of a turn the history drew from its
|
|
113
|
+
// drawing (its pieces carry data-drawn; the entry's of_turn, CardStore's).
|
|
114
|
+
function drawnEntry(entry) {
|
|
115
|
+
if (!entry?.in_turn || entry.type === "question" || typeof entry.of_turn !== "string") return false;
|
|
116
|
+
return [...historyEl.querySelectorAll("[data-drawn]")].some((el) => el.dataset.drawn === entry.of_turn);
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
// A finished turn's row, a hook's notice or the loop's asking again
|
|
105
120
|
// (+before+: the next turn's prompt, or null for the last turn): a row of
|
|
106
121
|
// its step, above the row of the call it came before, as live; else (a
|
|
@@ -268,11 +268,15 @@ export function keptCommandBubbles(historyEl, id) {
|
|
|
268
268
|
.filter(({ el }) => keepsCommandBubble(el.querySelector(":scope > .command-line")?.textContent));
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
// The kept bubbles back into the redrawn history, each after its turn
|
|
271
|
+
// The kept bubbles back into the redrawn history, each after its turn; a
|
|
272
|
+
// reminder's whose turn the history now draws from its drawing
|
|
273
|
+
// (data-drawn: the drawing has the reminder) stays out.
|
|
272
274
|
export function restoreCommandBubbles(historyEl, kept) {
|
|
273
275
|
if (!kept.length) return;
|
|
274
276
|
historyEl.querySelector(".hint")?.remove();
|
|
277
|
+
const drawn = new Set([...historyEl.querySelectorAll("[data-drawn]")].map((el) => el.dataset.drawn));
|
|
275
278
|
kept.forEach(({ el, anchor }) => {
|
|
279
|
+
if (el.dataset.turnId && drawn.has(el.dataset.turnId)) return;
|
|
276
280
|
const { els, kinds } = historyAnchors(historyEl);
|
|
277
281
|
const at = anchorInsertIndex(kinds, anchor);
|
|
278
282
|
if (at < els.length) els[at].before(el);
|
|
@@ -130,9 +130,11 @@ export function uploadImage(id, file, name, opts = {}) {
|
|
|
130
130
|
// (mergeTiming), and opts.turnId asks for that turn's answer.
|
|
131
131
|
// opts.timing: the whole timing alone (when the merge came up short).
|
|
132
132
|
// opts.parts: each saved message with what it did (thinking, tool calls
|
|
133
|
-
// and their output), for the turn block's reload
|
|
133
|
+
// and their output), for the turn block's reload; with it, drawings=1: a
|
|
134
|
+
// finished turn with a drawing comes as its turn_drawings, its items
|
|
135
|
+
// stamped (drawn) without their parts (turn_drawing.js).
|
|
134
136
|
export function getSession(id, { tail, recent, turnId, timing, parts, cards, ...opts } = {}) {
|
|
135
|
-
let query = cards ? "?cards=1" : tail ? "?tail=1" : timing ? "?timing=1" : parts ? "?parts=1" : "";
|
|
137
|
+
let query = cards ? "?cards=1" : tail ? "?tail=1" : timing ? "?timing=1" : parts ? "?parts=1&drawings=1" : "";
|
|
136
138
|
if (tail && recent) query += "&recent=1";
|
|
137
139
|
if (tail && turnId) query += `&turn_id=${encodeURIComponent(turnId)}`;
|
|
138
140
|
return api(`/api/sessions/${id}${query}`, opts);
|