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
|
@@ -47,7 +47,7 @@ module Samagotchi
|
|
|
47
47
|
|
|
48
48
|
def handle_question_answer(req, id)
|
|
49
49
|
session = @session_class.load(id, state_dir: default_state_dir) rescue nil
|
|
50
|
-
return
|
|
50
|
+
return session_not_found(id) unless session
|
|
51
51
|
|
|
52
52
|
body = parse_json(request_body(req))
|
|
53
53
|
return error_response(400, "invalid_json", "invalid JSON body") unless body.is_a?(Hash)
|
|
@@ -207,7 +207,7 @@ module Samagotchi
|
|
|
207
207
|
when :accepted
|
|
208
208
|
record_history(body, prompt)
|
|
209
209
|
# via: bridge or file (the worker was gone: its input file), which
|
|
210
|
-
# names the turn a remote chi send --wait follows (
|
|
210
|
+
# names the turn a remote chi send --wait follows (ReplyWait::Target).
|
|
211
211
|
json_response(202, result.ack.merge("via" => result.via.to_s))
|
|
212
212
|
when :refused then json_response(result.code, result.ack)
|
|
213
213
|
when :timeout then error_response(504, "worker_timeout", result.detail)
|
|
@@ -239,7 +239,7 @@ module Samagotchi
|
|
|
239
239
|
return [] if raw.nil?
|
|
240
240
|
|
|
241
241
|
session_dir = image_session_dir(id)
|
|
242
|
-
return
|
|
242
|
+
return Session::NotFound.new(id).message unless session_dir
|
|
243
243
|
|
|
244
244
|
ImageStore.check_refs(session_dir, raw)
|
|
245
245
|
end
|
|
@@ -259,7 +259,7 @@ module Samagotchi
|
|
|
259
259
|
# (converted, downscaled); answers the ref the turn then names.
|
|
260
260
|
def handle_image_upload(req, id)
|
|
261
261
|
session_dir = image_session_dir(id)
|
|
262
|
-
return
|
|
262
|
+
return session_not_found(id) unless session_dir
|
|
263
263
|
|
|
264
264
|
length = req.content_length.to_i
|
|
265
265
|
if length > MAX_IMAGE_UPLOAD_BYTES
|
|
@@ -140,7 +140,7 @@ module Samagotchi
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
# POST /api/sessions with continues: <id, prefix or last:id>: the next
|
|
143
|
-
# link of that session's chain (
|
|
143
|
+
# link of that session's chain (SessionContinue.run), in its
|
|
144
144
|
# folder and with its own setup (SessionSetup: llm_context, thinking
|
|
145
145
|
# and reasoning_history levels, settings), so dir, llm_context, thinking and settings are
|
|
146
146
|
# refused beside it (a create takes no reasoning_history). It runs on
|
|
@@ -328,11 +328,11 @@ module Samagotchi
|
|
|
328
328
|
[level, nil]
|
|
329
329
|
end
|
|
330
330
|
|
|
331
|
-
#
|
|
331
|
+
# SessionContinue.run, its refusals as responses: [session,
|
|
332
332
|
# nil] or [nil, the response]. Only its own errors: what fails after
|
|
333
333
|
# the new link started is not a refusal of it.
|
|
334
334
|
def continue_from(given, prompt:, preview:, model:)
|
|
335
|
-
[
|
|
335
|
+
[SessionContinue.run(given, prompt: prompt, preview: preview, model: model, state_dir: @state_dir), nil]
|
|
336
336
|
rescue SessionContinue::Refused => e
|
|
337
337
|
extra = e.reason == :continued ? { next_id: e.ids.first } : {}
|
|
338
338
|
[nil, refusal_response(409, e.reason.to_s, e, session_id: e.session_id, reason: e.reason.to_s, ids: e.ids, **extra)]
|
|
@@ -343,8 +343,8 @@ module Samagotchi
|
|
|
343
343
|
return if dir_stat.mtime == @dir_mtime
|
|
344
344
|
|
|
345
345
|
@dir_mtime = dir_stat.mtime
|
|
346
|
-
on_disk = Dir.glob(File.join(@state_dir, "*#{
|
|
347
|
-
[File.basename(path,
|
|
346
|
+
on_disk = Dir.glob(File.join(@state_dir, "*#{SessionPaths::FILE_EXT}")).to_h do |path|
|
|
347
|
+
[File.basename(path, SessionPaths::FILE_EXT), path]
|
|
348
348
|
end.select { |id, _| Session.valid_id?(id) }
|
|
349
349
|
(@sessions.keys - on_disk.keys).each { |id| drop(id) }
|
|
350
350
|
on_disk.each_key { |id| @sessions[id] ||= Entry.new }
|
|
@@ -404,21 +404,13 @@ module Samagotchi
|
|
|
404
404
|
emit("session", session: summary)
|
|
405
405
|
end
|
|
406
406
|
|
|
407
|
-
#
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
|
|
414
|
-
def hub_registry
|
|
415
|
-
hosts = ConfigFile.hosts_config
|
|
416
|
-
if @hub_registry.nil? || hosts != @hub_registry_hosts
|
|
417
|
-
@hub_registry = HostRegistry.new(hosts_config: hosts)
|
|
418
|
-
@hub_registry_hosts = hosts
|
|
419
|
-
end
|
|
420
|
-
@hub_registry
|
|
421
|
-
end
|
|
407
|
+
# The registry kept across scans (HostRegistry.current, built again
|
|
408
|
+
# when config.yml's hosts: changed): every session's ctx_pct may need
|
|
409
|
+
# its llm_context budget resolved, and the registry's config read is
|
|
410
|
+
# what would otherwise repeat per scan and per session; a host added
|
|
411
|
+
# or a window changed while chi web runs is picked up on the next
|
|
412
|
+
# refresh of a card.
|
|
413
|
+
def hub_registry = HostRegistry.current
|
|
422
414
|
|
|
423
415
|
def drop(id)
|
|
424
416
|
entry = @sessions.delete(id)
|
|
@@ -4,6 +4,8 @@ require "time" # Time.iso8601
|
|
|
4
4
|
require "uri"
|
|
5
5
|
|
|
6
6
|
require_relative "../analytics_file"
|
|
7
|
+
require_relative "../bridge/turn_accumulator"
|
|
8
|
+
require_relative "../bridge/turn_drawings"
|
|
7
9
|
require_relative "../answer_refs"
|
|
8
10
|
require_relative "../answer_tail"
|
|
9
11
|
require_relative "../objectives"
|
|
@@ -67,13 +69,27 @@ module Samagotchi
|
|
|
67
69
|
session_json = shown_session_json(session, snapshot, owner, persisted)
|
|
68
70
|
raw_messages = live_or_saved(turn_snapshot, "messages") { session.messages }
|
|
69
71
|
timing = timing_payload(id, live_metrics: snapshot && snapshot["metrics"], persisted: persisted)
|
|
72
|
+
parts = req.params["parts"] == "1"
|
|
73
|
+
# A page that draws finished turns from their drawings (drawings=1,
|
|
74
|
+
# with parts=1): the valid lines stamp their items, which lose their
|
|
75
|
+
# parts. An older page (no drawings=1) reads what it always did.
|
|
76
|
+
# A live worker from before turn drawings lists no of_turn on its
|
|
77
|
+
# cards: its turns all draw as before.
|
|
78
|
+
drawings = parts && req.params["drawings"] == "1" && (!live || worker_draws?(id))
|
|
79
|
+
drawn = drawings ? drawn_lines(id, raw_messages) : []
|
|
80
|
+
messages = messages_for_display(raw_messages, parts: parts, cwd: session.working_directory,
|
|
81
|
+
drawings: drawn, strip_drawn: true)
|
|
70
82
|
json_response(200, {
|
|
71
83
|
session: session_json,
|
|
72
84
|
history: read_history(id),
|
|
73
|
-
messages:
|
|
74
|
-
|
|
85
|
+
messages: messages,
|
|
86
|
+
**turn_drawings_field(drawn, messages),
|
|
75
87
|
failed_turn: failed_turn_for(session, raw_messages),
|
|
76
88
|
current_turn: current_turn,
|
|
89
|
+
# The page's join: the turn in progress and the queue behind it as
|
|
90
|
+
# the live events that draw them (TurnAccumulator.join_events, one
|
|
91
|
+
# unfold, here: any worker's snapshot works).
|
|
92
|
+
replay: replay_events_for(turn_snapshot, timing),
|
|
77
93
|
queued: snapshot_list(turn_snapshot, "queued"),
|
|
78
94
|
# Commands waiting for the running turn's end (their bubbles).
|
|
79
95
|
queued_commands: snapshot_list(turn_snapshot, "queued_commands"),
|
|
@@ -110,6 +126,52 @@ module Samagotchi
|
|
|
110
126
|
view ? { objectives: view } : {}
|
|
111
127
|
end
|
|
112
128
|
|
|
129
|
+
# The live events a joining page replays (`replay`): the worker's
|
|
130
|
+
# +turn_snapshot+'s turn, queued prompts and commands, the turn's start
|
|
131
|
+
# from +timing+'s active_turn; [] without a worker or with nothing.
|
|
132
|
+
def replay_events_for(turn_snapshot, timing)
|
|
133
|
+
return [] unless turn_snapshot
|
|
134
|
+
|
|
135
|
+
wire = Bridge::TurnAccumulator.from_wire(turn_snapshot.slice("current_turn", "queued", "queued_commands"))
|
|
136
|
+
active = timing[:active_turn]
|
|
137
|
+
Bridge::TurnAccumulator.join_events(current_turn: wire[:current_turn], queued: wire[:queued],
|
|
138
|
+
queued_commands: wire[:queued_commands],
|
|
139
|
+
started_at: active.is_a?(Hash) ? active["started_at"] : nil)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Whether the session's live worker names turn drawings in its
|
|
143
|
+
# sidecar's features (Bridge::TurnDrawings::FEATURE); false when its
|
|
144
|
+
# link can't be read as live.
|
|
145
|
+
def worker_draws?(id)
|
|
146
|
+
link = worker_link(id)
|
|
147
|
+
link.live? && Array(link.sidecar.features).include?(Bridge::TurnDrawings::FEATURE)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The session's turn drawings that hold against +messages+ (the live
|
|
151
|
+
# snapshot's, else the file's): Bridge::TurnDrawings, cached by the
|
|
152
|
+
# file's size and mtime; the newest SHOWN_MAX of them.
|
|
153
|
+
# A drawing that can't be read draws its turn as before.
|
|
154
|
+
def drawn_lines(id, messages)
|
|
155
|
+
Degrade.call(:web, "turn_drawings_unreadable", fallback: []) do
|
|
156
|
+
dir = @session_class.session_dir(id, state_dir: default_state_dir)
|
|
157
|
+
Bridge::TurnDrawings.valid(Bridge::TurnDrawings.read(dir), messages).last(Bridge::TurnDrawings::SHOWN_MAX)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# {turn_drawings: {turn_id => {events:, end:}}} for the stamped turns
|
|
162
|
+
# (chi web's one unfold of a drawing, its cards too); nothing without
|
|
163
|
+
# one.
|
|
164
|
+
def turn_drawings_field(lines, messages)
|
|
165
|
+
stamped = messages.filter_map { |item| item[:drawn] }.uniq
|
|
166
|
+
shown = lines.select { |line| stamped.include?(line.turn_id) }
|
|
167
|
+
return {} if shown.empty?
|
|
168
|
+
|
|
169
|
+
{ turn_drawings: shown.to_h do |line|
|
|
170
|
+
turn = Bridge::TurnAccumulator.from_wire(line.drawing)
|
|
171
|
+
[line.turn_id, { events: Bridge::TurnAccumulator.replay_events(turn, cards: true), end: line.end }]
|
|
172
|
+
end }
|
|
173
|
+
end
|
|
174
|
+
|
|
113
175
|
# A list in the worker's turn +snapshot+ (queued prompts, commands,
|
|
114
176
|
# init tasks); [] without a worker.
|
|
115
177
|
def snapshot_list(snapshot, key) = Array(snapshot&.[](key))
|
|
@@ -184,7 +246,7 @@ module Samagotchi
|
|
|
184
246
|
# cards from its light GET tail, else the saved ones; the session file
|
|
185
247
|
# is only stat'ed (the 404), never parsed.
|
|
186
248
|
def handle_cards_read(id)
|
|
187
|
-
return
|
|
249
|
+
return session_not_found(id) unless session_exists?(id)
|
|
188
250
|
|
|
189
251
|
tail = live_tail(id)
|
|
190
252
|
json_response(200, { cards: cards_for_display(live_or_saved(tail, "cards") { saved_cards(id) }) })
|
|
@@ -201,7 +263,7 @@ module Samagotchi
|
|
|
201
263
|
# are the newest turn's (#recent_timing), which the page merges by id
|
|
202
264
|
# (the page still sends ?recent=1, which is accepted and not needed).
|
|
203
265
|
def handle_tail_read(req, id)
|
|
204
|
-
return
|
|
266
|
+
return session_not_found(id) unless session_exists?(id)
|
|
205
267
|
|
|
206
268
|
turn_id = req.params["turn_id"].to_s.strip
|
|
207
269
|
turn_id = nil if turn_id.empty?
|
|
@@ -259,7 +321,7 @@ module Samagotchi
|
|
|
259
321
|
# the live ones) and turn_count, when the page's merge comes up short.
|
|
260
322
|
# The worker's GET state, never its messages.
|
|
261
323
|
def handle_timing_read(id)
|
|
262
|
-
return
|
|
324
|
+
return session_not_found(id) unless session_exists?(id)
|
|
263
325
|
|
|
264
326
|
state = bridge_get_json(id, "state")
|
|
265
327
|
json_response(200, { timing: timing_payload(id, live_metrics: state && state["session_state_snapshot"]&.fetch("metrics", nil)) })
|
|
@@ -7,10 +7,11 @@ require_relative "../verify_fresh"
|
|
|
7
7
|
require_relative "../recap_store"
|
|
8
8
|
require_relative "../archive_store"
|
|
9
9
|
require_relative "../session_metrics"
|
|
10
|
-
require_relative "../
|
|
10
|
+
require_relative "../effective_model"
|
|
11
11
|
require_relative "../host_registry"
|
|
12
12
|
require_relative "../bridge_client"
|
|
13
13
|
require_relative "../worker_link"
|
|
14
|
+
require_relative "../session_row"
|
|
14
15
|
require_relative "../bridge/pending_card"
|
|
15
16
|
|
|
16
17
|
module Samagotchi
|
|
@@ -20,6 +21,14 @@ module Samagotchi
|
|
|
20
21
|
# session is answered (GET /api/sessions, the session view, the session
|
|
21
22
|
# hub's events), so every path agrees on status, owner and bridge_up.
|
|
22
23
|
module SessionSummary
|
|
24
|
+
# The page's keys, in the order it has always had them (a hub event
|
|
25
|
+
# compares with ==, a 0.156 page reads them by name).
|
|
26
|
+
WEB_KEYS = %i[id status mode model_name working_directory created_at updated_at last_prompt short_id test_run
|
|
27
|
+
used_memory_names preloaded_memory_names muted_memory_names prompt_notes parent_id delegate
|
|
28
|
+
continues review first_preview title owner recap auto_title ctx_pct tokens memory_index side
|
|
29
|
+
archived project_root bridge_up worker_version worker_features pending_question pending_card
|
|
30
|
+
last_turn verdicts verify_fresh last_verdict].freeze
|
|
31
|
+
|
|
23
32
|
module_function
|
|
24
33
|
|
|
25
34
|
# @param session [Session] a saved session (messages may be empty)
|
|
@@ -39,64 +48,66 @@ module Samagotchi
|
|
|
39
48
|
# @return [Hash] symbol keys
|
|
40
49
|
def build(session, owner:, session_dir:, status: displayed_status(session, owner: owner), root_cache: nil,
|
|
41
50
|
registry: nil)
|
|
42
|
-
used = Array(session.used_memory_names)
|
|
43
51
|
link = WorkerLink.resolve(session_dir, unlink: false, owner: owner)
|
|
44
52
|
up = link.live?
|
|
45
53
|
sidecar = up ? link.sidecar : nil
|
|
46
54
|
saved = SessionMetrics.saved_summary(session_dir, budget_tokens: budget_resolver(session, registry))
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
# The facts every list shares (SessionRow), from this one link,
|
|
56
|
+
# analytics and recap read.
|
|
57
|
+
row = SessionRow.build(session, session_dir: session_dir, owner: owner, link: link, roots: root_cache || {},
|
|
58
|
+
saved: saved, card: RecapStore.card(session_dir))
|
|
59
|
+
values = {
|
|
60
|
+
id: row.id,
|
|
50
61
|
status: status,
|
|
51
62
|
mode: session.mode,
|
|
52
63
|
model_name: session.model_name,
|
|
53
|
-
working_directory:
|
|
64
|
+
working_directory: row.cwd,
|
|
54
65
|
created_at: session.created_at,
|
|
55
|
-
updated_at:
|
|
66
|
+
updated_at: row.updated_at,
|
|
56
67
|
last_prompt: session.last_prompt,
|
|
57
|
-
short_id:
|
|
58
|
-
test_run: !!
|
|
59
|
-
used_memory_names:
|
|
68
|
+
short_id: row.short_id,
|
|
69
|
+
test_run: !!row.test_run,
|
|
70
|
+
used_memory_names: Array(session.used_memory_names),
|
|
60
71
|
# The session's --memory and --mute lists (info-bar tooltip).
|
|
61
72
|
preloaded_memory_names: Array(session.preloaded_memory_names),
|
|
62
73
|
muted_memory_names: Array(session.muted_memory_names),
|
|
63
74
|
# The model notes its prompt carried (the info bar's notes chip).
|
|
64
75
|
prompt_notes: Array(session.prompt_notes).map(&:to_h),
|
|
65
76
|
# The session that delegated this one (the `delegate` tool), else nil.
|
|
66
|
-
parent_id:
|
|
77
|
+
parent_id: row.parent_id,
|
|
67
78
|
# Started by the delegate tool (Session#delegate?); a fork has a
|
|
68
79
|
# parent_id too but isn't one. The parent's children chip counts
|
|
69
80
|
# delegates only, as the TUI's segment and /children do.
|
|
70
|
-
delegate:
|
|
81
|
+
delegate: row.delegate,
|
|
71
82
|
# The session this one continues (the previous link of its
|
|
72
83
|
# chain), else nil: the lists draw chains from it.
|
|
73
|
-
continues:
|
|
84
|
+
continues: row.continues,
|
|
74
85
|
# A review session's {subjects, from} (SessionReview), else nil:
|
|
75
86
|
# the session bar offers Start review on the others.
|
|
76
87
|
review: session.review&.to_file,
|
|
77
88
|
first_preview: first_preview_for(session),
|
|
78
89
|
# The session's own name (/title), else nil: the pages show it in
|
|
79
90
|
# place of the first message.
|
|
80
|
-
title:
|
|
81
|
-
owner: owner
|
|
91
|
+
title: row.title,
|
|
92
|
+
owner: row.owner,
|
|
82
93
|
# The saved recap's first sentence, for the session card, and the
|
|
83
94
|
# auto title its reply led with (RecapTitle), else nil: an untitled
|
|
84
95
|
# session's name on the pages and its pushes (never in a model
|
|
85
96
|
# request).
|
|
86
|
-
recap:
|
|
87
|
-
auto_title:
|
|
97
|
+
recap: row.recap,
|
|
98
|
+
auto_title: row.auto_title,
|
|
88
99
|
# How full the context was after the last turn (%), or nil, and
|
|
89
100
|
# the session's token sums and cost (the card's ctx tooltip), from
|
|
90
101
|
# one read of analytics.json.
|
|
91
|
-
ctx_pct:
|
|
102
|
+
ctx_pct: row.ctx_pct,
|
|
92
103
|
tokens: saved&.tokens,
|
|
93
104
|
# The memory indexes its prompt held (the card's ctx tooltip).
|
|
94
105
|
memory_index: saved&.memory_index,
|
|
95
106
|
# Its side requests, apart from the tokens (the tooltip's own line).
|
|
96
107
|
side: saved&.side,
|
|
97
108
|
# Hidden from the strip and the list unless the all view's "archived" toggle is on.
|
|
98
|
-
archived:
|
|
99
|
-
project_root:
|
|
109
|
+
archived: row.archived,
|
|
110
|
+
project_root: row.project,
|
|
100
111
|
# A worker is reachable: WorkerLink answers Live (the lock held by
|
|
101
112
|
# a worker whose sidecar names its pid). A sidecar a dead worker
|
|
102
113
|
# left is not up.
|
|
@@ -108,8 +119,8 @@ module Samagotchi
|
|
|
108
119
|
# For the tab's notifications (notify.js): an open question, the
|
|
109
120
|
# running turn's open card with actions, and how the last turn
|
|
110
121
|
# ended. A question saved by a worker that died is not open, nor a
|
|
111
|
-
# one-turn chi run's (only a worker shares it).
|
|
112
|
-
pending_question: session.waiting_question(live:
|
|
122
|
+
# one-turn chi run's (only a worker shares it): the row's live.
|
|
123
|
+
pending_question: session.waiting_question(live: row.live),
|
|
113
124
|
pending_card: up ? Bridge::PendingCard.read(session_dir) : nil,
|
|
114
125
|
last_turn: session.last_turn&.to_file,
|
|
115
126
|
# Its Verify verdicts, the fresh ones its worker hasn't taken yet
|
|
@@ -117,9 +128,10 @@ module Samagotchi
|
|
|
117
128
|
# ({of, child_id}: the card's "verifying fresh" and its link).
|
|
118
129
|
verdicts: VerifyFresh.verdicts_of(session, session_dir),
|
|
119
130
|
verify_fresh: VerifyFresh.running(session.id, state_dir: File.dirname(session_dir))
|
|
120
|
-
.map { |
|
|
131
|
+
.map { |entry| { of: entry[:of], child_id: entry[:child_id] } },
|
|
121
132
|
last_verdict: last_verdict(session, session_dir)
|
|
122
133
|
}
|
|
134
|
+
WEB_KEYS.to_h { |key| [key, values.fetch(key)] }
|
|
123
135
|
end
|
|
124
136
|
|
|
125
137
|
def last_verdict(session, session_dir)
|
|
@@ -141,18 +153,17 @@ module Samagotchi
|
|
|
141
153
|
# no network) is resolved only for a session that has a saved context
|
|
142
154
|
# to count. One registry per caller (the hub) or one built here.
|
|
143
155
|
def budget_resolver(session, registry)
|
|
144
|
-
-> {
|
|
156
|
+
-> { EffectiveModel.session_budget(session, registry: registry ||= HostRegistry.current) }
|
|
145
157
|
end
|
|
146
158
|
private_class_method :budget_resolver
|
|
147
159
|
|
|
148
160
|
# status is turn state (idle/running). The live worker's snapshot is the
|
|
149
|
-
# truth; on disk, a "running" with no live
|
|
150
|
-
# that died mid-turn.
|
|
161
|
+
# truth; on disk, Session#displayed_status (a "running" with no live
|
|
162
|
+
# owner was left by a worker that died mid-turn).
|
|
151
163
|
def displayed_status(session, snapshot = nil, owner:)
|
|
152
164
|
return snapshot["status"] if snapshot.is_a?(Hash) && snapshot["status"]
|
|
153
|
-
return session.status unless session.status == Session::STATUS_RUNNING
|
|
154
165
|
|
|
155
|
-
|
|
166
|
+
session.displayed_status(owner: owner)
|
|
156
167
|
end
|
|
157
168
|
|
|
158
169
|
def first_preview_for(session)
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -37,6 +37,7 @@ require_relative "model_profile"
|
|
|
37
37
|
require_relative "child_reports"
|
|
38
38
|
require_relative "verify_fresh"
|
|
39
39
|
require_relative "child_move"
|
|
40
|
+
require_relative "starting_marker"
|
|
40
41
|
require_relative "context_absorber"
|
|
41
42
|
require_relative "context_candidates"
|
|
42
43
|
require_relative "context_poller"
|
|
@@ -47,7 +48,7 @@ require_relative "participant_name"
|
|
|
47
48
|
|
|
48
49
|
module Samagotchi
|
|
49
50
|
# The loop of a background session worker, once it owns the session (see
|
|
50
|
-
#
|
|
51
|
+
# WorkerBoot.run_session_loop, which takes the OwnerLock around #run).
|
|
51
52
|
#
|
|
52
53
|
# It runs the session's Engine and Bridge, takes queued prompts from the
|
|
53
54
|
# input dir one turn at a time, and returns when nobody has used it for the
|
|
@@ -57,8 +58,8 @@ module Samagotchi
|
|
|
57
58
|
# exits with 1.
|
|
58
59
|
#
|
|
59
60
|
# The file IPC stays behind SessionInbox's functions (find_new_input_files,
|
|
60
|
-
# claim_input_file, ...) and the Bridge behind
|
|
61
|
-
#
|
|
61
|
+
# claim_input_file, ...) and the Bridge behind #start_bridge, which specs
|
|
62
|
+
# stub as seams.
|
|
62
63
|
#
|
|
63
64
|
# The loop sleeps on a Waker, which the Bridge wakes when it queues a turn
|
|
64
65
|
# or a command, and the reminder callback when it queues one. A fallback
|
|
@@ -132,11 +133,11 @@ module Samagotchi
|
|
|
132
133
|
# @param idle_exit_minutes [Numeric, nil] nil: session.idle_exit_minutes
|
|
133
134
|
# @param poll_interval [Numeric, nil] seconds between fallback ticks
|
|
134
135
|
# @param signals [WorkerSignals, WorkerSignals::None] the process's
|
|
135
|
-
# trapped signals (
|
|
136
|
+
# trapped signals (WorkerBoot.run_session_loop); none by default
|
|
136
137
|
# @param signal_grace [Array(Numeric, Numeric, Numeric)] the grace, the
|
|
137
138
|
# short grace and the hard exit's delay (specs shorten them)
|
|
138
139
|
# @param owner_lock [OwnerLock, nil] the session's, which this process
|
|
139
|
-
# holds (
|
|
140
|
+
# holds (WorkerBoot.run_session_loop): marked leaving as the Bridge
|
|
140
141
|
# stops, so an attach waits for the recap on leave (OwnerLock.await_release)
|
|
141
142
|
def initialize(session_id:, state_dir:, session_dir:, idle_exit_minutes: nil, poll_interval: nil,
|
|
142
143
|
signals: WorkerSignals::NONE, signal_grace: [SIGNAL_GRACE, SIGNAL_SHORT_GRACE, SIGNAL_HARD_EXIT],
|
|
@@ -185,16 +186,16 @@ module Samagotchi
|
|
|
185
186
|
end
|
|
186
187
|
|
|
187
188
|
# Whether the session was empty as the worker left it, so the caller
|
|
188
|
-
# deletes it once the lock is free (
|
|
189
|
+
# deletes it once the lock is free (WorkerBoot.run_session_loop
|
|
189
190
|
# checks again then).
|
|
190
191
|
def discard? = @discard
|
|
191
192
|
|
|
192
193
|
# Whether this worker runs a `chi scratch` session: deleted as it leaves
|
|
193
|
-
# (
|
|
194
|
+
# (WorkerBoot.run_session_loop).
|
|
194
195
|
def scratch? = !!@session&.scratch
|
|
195
196
|
|
|
196
197
|
# Whether delegate reports wait that an idle parent runs a turn for
|
|
197
|
-
# (#wake_state):
|
|
198
|
+
# (#wake_state): WorkerBoot.run_session_loop starts a new worker
|
|
198
199
|
# for them after this one leaves. Reports the budget or a failure held
|
|
199
200
|
# back wait for the next human input instead.
|
|
200
201
|
def wake_due? = wake_state == :due
|
|
@@ -208,7 +209,7 @@ module Samagotchi
|
|
|
208
209
|
# (chi stop), :signal when a signal stopped it (WorkerSignals),
|
|
209
210
|
# :crashed when the loop raised (the session is marked errored),
|
|
210
211
|
# :scratch_left when a scratch session's terminal is gone (the caller
|
|
211
|
-
# deletes it);
|
|
212
|
+
# deletes it); WorkerBoot.run_session_loop turns it into the exit
|
|
212
213
|
def run
|
|
213
214
|
# The thread a signal's expired grace raises in (the main thread in a
|
|
214
215
|
# worker process).
|
|
@@ -261,7 +262,7 @@ module Samagotchi
|
|
|
261
262
|
# This worker holds the session now (a continue's starting marker held
|
|
262
263
|
# off any other until it did), and a continue that died moving
|
|
263
264
|
# delegates here left its intent: finish it.
|
|
264
|
-
|
|
265
|
+
StartingMarker.clear(@session_id, state_dir: @state_dir)
|
|
265
266
|
ChildMove.adopt(@session_id, state_dir: @state_dir)
|
|
266
267
|
@session = Session.load(@session_id, state_dir: @state_dir)
|
|
267
268
|
# A newer chi saved it: this worker keeps the fields it doesn't know
|
|
@@ -362,25 +363,25 @@ module Samagotchi
|
|
|
362
363
|
# The Bridge (UIs and clients reach the worker from here on), then the
|
|
363
364
|
# idle-exit policy.
|
|
364
365
|
def serve
|
|
365
|
-
@bridge =
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
366
|
+
@bridge = start_bridge(engine: @engine, state_dir: @state_dir, session_id: @session_id,
|
|
367
|
+
on_input: -> { @waker.wake },
|
|
368
|
+
on_command: method(:on_command),
|
|
369
|
+
on_exit_request: method(:exit_request),
|
|
370
|
+
# Decided again as it leaves (a note may still come in).
|
|
371
|
+
exit_discards: method(:empty_session?),
|
|
372
|
+
# What a stop-all /exit stopped, for its 200.
|
|
373
|
+
exit_stopped: method(:stopped_holds),
|
|
374
|
+
# chi web's Stop asks about these before TERMed-ing
|
|
375
|
+
# the worker (its own holds routes).
|
|
376
|
+
on_holds: -> { @idle_exit&.stoppable_holds },
|
|
377
|
+
on_holds_stop: -> { @hold_stop&.stop_all })
|
|
377
378
|
# What failed to load and what plugins showed as they loaded, now that
|
|
378
379
|
# a UI can be there (a late one gets them in the snapshot).
|
|
379
380
|
@engine.announce_load_events!
|
|
380
381
|
announce_session_warnings
|
|
381
382
|
@idle_exit = WorkerIdleExit.new(
|
|
382
383
|
engine: @engine, bridge: @bridge,
|
|
383
|
-
timeout_minutes: @idle_exit_minutes ||
|
|
384
|
+
timeout_minutes: @idle_exit_minutes || config_idle_exit_minutes,
|
|
384
385
|
input_pending: method(:input_pending?),
|
|
385
386
|
awaiting_continue: -> { @turn_flow.awaiting_continue? },
|
|
386
387
|
running_tasks: -> { Tools::TaskRuntime.running_created_in(@engine.messages_checkpoint) },
|
|
@@ -398,6 +399,33 @@ module Samagotchi
|
|
|
398
399
|
state_dir: @state_dir)
|
|
399
400
|
end
|
|
400
401
|
|
|
402
|
+
# Start the in-process Bridge transport for this worker. The bridge is
|
|
403
|
+
# the single live client transport, so every worker starts it. Bridge
|
|
404
|
+
# creation happens *before* the loop so the capture observer is in place
|
|
405
|
+
# for the whole session; the caller stops the returned instance on exit
|
|
406
|
+
# (see #run's ensure).
|
|
407
|
+
#
|
|
408
|
+
# @return [Samagotchi::Bridge, nil] nil when the transport failed to start
|
|
409
|
+
# (the worker degrades: turns still flow through the input-dir loop, but
|
|
410
|
+
# there is no live SSE or in-process cancel/answer).
|
|
411
|
+
# @param on_input [#call, nil] called after the Bridge queues a turn
|
|
412
|
+
def start_bridge(engine:, state_dir:, session_id:, on_input: nil, on_command: nil, on_exit_request: nil,
|
|
413
|
+
exit_discards: nil, exit_stopped: nil, on_holds: nil, on_holds_stop: nil)
|
|
414
|
+
require_relative "bridge"
|
|
415
|
+
Samagotchi::Bridge.new(
|
|
416
|
+
engine: engine, state_dir: state_dir, session_id: session_id, input_format: SessionInbox::INPUT_FORMAT,
|
|
417
|
+
on_input: on_input, on_command: on_command, on_exit_request: on_exit_request, exit_discards: exit_discards,
|
|
418
|
+
exit_stopped: exit_stopped, on_holds: on_holds, on_holds_stop: on_holds_stop
|
|
419
|
+
).start
|
|
420
|
+
rescue StandardError => e
|
|
421
|
+
Log.error(:bridge, "start_failed", echo: "Bridge: failed to start for session #{session_id}: #{e.class}: #{e.message}", sid: session_id, error: e.class.name)
|
|
422
|
+
nil
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def config_idle_exit_minutes
|
|
426
|
+
Degrade.call(:config, "idle_exit_minutes_unreadable") { Samagotchi::Config.get("session.idle_exit_minutes") }
|
|
427
|
+
end
|
|
428
|
+
|
|
401
429
|
# The delegate children of this session that are running a turn or have
|
|
402
430
|
# a live worker. ChildrenStatus.of scans the session files; this runs on
|
|
403
431
|
# an exit request, not every pass.
|
|
@@ -648,13 +676,14 @@ module Samagotchi
|
|
|
648
676
|
save_or_log(:interrupted_turn) { save_session }
|
|
649
677
|
end
|
|
650
678
|
|
|
651
|
-
# The per-input tombstones (removed, settled) older than
|
|
679
|
+
# The per-input tombstones (removed, settled, merged) older than
|
|
652
680
|
# SessionInbox::TOMBSTONE_MAX_AGE, once per worker start: they only go
|
|
653
681
|
# with the session dir otherwise.
|
|
654
682
|
def prune_tombstones
|
|
655
683
|
removed = SessionInbox.prune_removed(@session_dir)
|
|
656
684
|
settled = TurnCheckpoint.prune_settled(@session_dir)
|
|
657
|
-
|
|
685
|
+
merged = SessionInbox.prune_merged(@session_dir)
|
|
686
|
+
Log.info(:worker, "tombstones_pruned", removed: removed, settled: settled, merged: merged) if (removed + settled + merged).positive?
|
|
658
687
|
end
|
|
659
688
|
|
|
660
689
|
# A saved parent_answers that isn't a level reads as hard on every
|
|
@@ -882,14 +911,31 @@ module Samagotchi
|
|
|
882
911
|
# A failure has no prompt to hand back (the Engine announced
|
|
883
912
|
# :turn_failed). The offer went before the turn
|
|
884
913
|
# (ContinueOffer#drop); either way the rollback window closes.
|
|
885
|
-
run_engine_turn(nil, continue: true, origin: REMINDER_ORIGIN,
|
|
914
|
+
run_engine_turn(nil, continue: true, origin: wake_origin(REMINDER_ORIGIN),
|
|
886
915
|
max_iterations: IterationLimit.for) { @turn_flow.after_reminder_turn }
|
|
887
916
|
true
|
|
888
917
|
end
|
|
889
918
|
|
|
890
|
-
# A reminder turn's origin: chi's own (system:reminder).
|
|
919
|
+
# A reminder turn's origin: chi's own (system:reminder). Its run gets
|
|
920
|
+
# an id of its own (#wake_origin); this constant has none.
|
|
891
921
|
REMINDER_ORIGIN = TurnOrigin.new(client_id: ClientId::REMINDER)
|
|
892
922
|
|
|
923
|
+
# A turn nobody typed (a reminder, a wake) with an enqueued_id of its
|
|
924
|
+
# own, minted here as it runs, so its checkpoint mark, last_turn, reply
|
|
925
|
+
# file and settle name it (a wait with no message binds it:
|
|
926
|
+
# ReplyWait.own_turn). Never on the origin constants nor on
|
|
927
|
+
# ChildReports::Report#origin: a report merged into a turn keeps an
|
|
928
|
+
# id-less origin, which the web page reads as a report (turn_events.js
|
|
929
|
+
# promptOps), not as an unmatched steer.
|
|
930
|
+
# @return [TurnOrigin] +origin+ with an id ("wake-<16 hex>")
|
|
931
|
+
def wake_origin(origin)
|
|
932
|
+
origin.enqueued_id ? origin : origin.with(enqueued_id: "#{WAKE_ID_PREFIX}#{SecureRandom.hex(8)}")
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
# A wake turn's minted enqueued_id starts with this
|
|
936
|
+
# (SessionInbox::ENQUEUED_ID_NAME).
|
|
937
|
+
WAKE_ID_PREFIX = "wake-"
|
|
938
|
+
|
|
893
939
|
# :due when an idle parent should run a turn for its delegate
|
|
894
940
|
# children's reports now (session.delegate_reports: wake): rings wait,
|
|
895
941
|
# no continue offer does (they merge into its turn), wakes aren't paused
|
|
@@ -948,7 +994,7 @@ module Samagotchi
|
|
|
948
994
|
def run_wake(origin:, failed:, kept: nil, mark: nil)
|
|
949
995
|
@turn_flow.before_prompt_turn
|
|
950
996
|
restores = @turn_flow.restores
|
|
951
|
-
run_engine_turn(nil, continue: true, origin: origin, id: mark&.turn_id,
|
|
997
|
+
run_engine_turn(nil, continue: true, origin: wake_origin(origin), id: mark&.turn_id,
|
|
952
998
|
max_iterations: IterationLimit.for) do |result, error|
|
|
953
999
|
if error
|
|
954
1000
|
steps_kept = @turn_flow.prompt_turn_failed(error: error, wake: failed, wake_kept: kept) == :kept
|
|
@@ -1303,6 +1349,8 @@ module Samagotchi
|
|
|
1303
1349
|
origin = fresh_origin(turn_args[:origin])
|
|
1304
1350
|
# Settled here: the checkpoint's prompt carries it from the start.
|
|
1305
1351
|
turn_args = turn_args.merge(origin: origin, id: turn_args[:id] || SecureRandom.uuid)
|
|
1352
|
+
# The id its drain's merge records name (SessionInbox.mark_merged).
|
|
1353
|
+
@turn_enqueued_id = origin.enqueued_id
|
|
1306
1354
|
verdicts_before = @session.verdicts.size
|
|
1307
1355
|
@reporting_turn = reports_to_parent?(origin)
|
|
1308
1356
|
# A participant's review turn merges no input (#pending_input_drain).
|
|
@@ -1512,6 +1560,9 @@ module Samagotchi
|
|
|
1512
1560
|
begin
|
|
1513
1561
|
input = SessionInbox.read_input(claimed_file)
|
|
1514
1562
|
prompt = input.prompt.to_s.strip
|
|
1563
|
+
# Which turn took it, on disk before its file goes (a wait for
|
|
1564
|
+
# it reads this, not an inference: ReplyWait::Poll#follow_merge).
|
|
1565
|
+
SessionInbox.mark_merged(@session_dir, input.origin.enqueued_id, into: @turn_enqueued_id) unless prompt.empty?
|
|
1515
1566
|
prompt.empty? ? nil : [prompt, input.origin]
|
|
1516
1567
|
ensure
|
|
1517
1568
|
FileUtils.rm_f(claimed_file)
|
|
@@ -1634,7 +1685,7 @@ module Samagotchi
|
|
|
1634
1685
|
|
|
1635
1686
|
# #leave_idle for a scratch worker its terminal left: checked again with
|
|
1636
1687
|
# the event log held, then the intake closes. The session is deleted
|
|
1637
|
-
# once the lock is free (
|
|
1688
|
+
# once the lock is free (WorkerBoot.run_session_loop: :scratch_left).
|
|
1638
1689
|
# @return [Boolean] false when a client came back since
|
|
1639
1690
|
def leave_scratch
|
|
1640
1691
|
stop_bridge_after do
|
|
@@ -1842,8 +1893,8 @@ module Samagotchi
|
|
|
1842
1893
|
# A restart hands the reminders to the next worker (ReminderCarry), while
|
|
1843
1894
|
# the event log is held: no turn can register or fire one from here. A
|
|
1844
1895
|
# write that fails keeps this worker up with them rather than losing them.
|
|
1845
|
-
# A session stopped meanwhile drops them (run_session_loop
|
|
1846
|
-
# successor): nothing is written.
|
|
1896
|
+
# A session stopped meanwhile drops them (WorkerBoot.run_session_loop
|
|
1897
|
+
# then starts no successor): nothing is written.
|
|
1847
1898
|
# @return [Boolean] false when the write failed
|
|
1848
1899
|
def carry_reminders
|
|
1849
1900
|
if stopped_on_disk?
|
|
@@ -1881,7 +1932,7 @@ module Samagotchi
|
|
|
1881
1932
|
# @return [Symbol] +result+, with #discard? decided and, for a session
|
|
1882
1933
|
# kept, its recap written: the Bridge is closed and the idle jobs are
|
|
1883
1934
|
# stopped by now, and nothing waits on this (the TUI has detached). A
|
|
1884
|
-
# `chi send` meanwhile is picked up after the exit (run_session_loop);
|
|
1935
|
+
# `chi send` meanwhile is picked up after the exit (WorkerBoot.run_session_loop);
|
|
1885
1936
|
# a `chi --attach` finds no Bridge until then.
|
|
1886
1937
|
# A restart keeps even an empty session (a new worker takes it over at
|
|
1887
1938
|
# once) and leaves the recap to the conversation's next real pause.
|