samagotchi 0.152.0 → 0.154.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 +45 -1
- data/README.md +2 -3
- data/bin/chi +23 -24
- data/docs/architecture.md +30 -33
- data/docs/broadcast.md +5 -5
- data/docs/bundle-authoring.md +1 -1
- data/docs/bundles.md +93 -12
- data/docs/cli.md +83 -99
- data/docs/configuration.md +46 -40
- data/docs/context.md +3 -3
- data/docs/desktop.md +1 -1
- data/docs/guardrails.md +15 -15
- data/docs/hooks.md +13 -12
- data/docs/internals/anthropic.md +3 -3
- data/docs/internals/attached-context.md +5 -2
- data/docs/internals/background-tasks.md +3 -4
- data/docs/internals/block-renderers.md +4 -0
- data/docs/internals/bridge.md +3 -3
- data/docs/internals/context-telemetry.md +1 -1
- data/docs/internals/engine.md +42 -12
- data/docs/internals/objectives.md +6 -5
- data/docs/internals/participants.md +8 -8
- data/docs/internals/prompt-caching.md +8 -4
- data/docs/internals/remote-sessions.md +7 -7
- data/docs/internals/web-frontend.md +20 -17
- data/docs/internals/web-push.md +1 -1
- data/docs/memory.md +2 -2
- data/docs/plugins.md +52 -43
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +25 -25
- data/docs/sub-agent.md +2 -2
- data/docs/system-one.md +1 -1
- data/docs/testing.md +18 -8
- data/lib/samagotchi/answer_text.rb +34 -0
- data/lib/samagotchi/bridge.rb +43 -32
- data/lib/samagotchi/bridge_client.rb +10 -9
- data/lib/samagotchi/broadcast/recipients.rb +2 -2
- data/lib/samagotchi/broadcast/triage.rb +1 -1
- data/lib/samagotchi/broadcast_command.rb +5 -5
- data/lib/samagotchi/bundle_command/try/command.rb +1 -1
- data/lib/samagotchi/bundle_command/try/run.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +2 -2
- data/lib/samagotchi/bundles/decision-card/manifest.yml +4 -4
- data/lib/samagotchi/bundles/decision-card/plugin.rb +1 -9
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +3 -4
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/bundles/web-search/manifest.yml +13 -0
- data/lib/samagotchi/bundles/web-search/plugin.rb +174 -0
- data/lib/samagotchi/child_move.rb +3 -3
- data/lib/samagotchi/commands/registry.rb +6 -6
- data/lib/samagotchi/composer_route.rb +4 -5
- data/lib/samagotchi/config.rb +14 -12
- data/lib/samagotchi/context_candidate_choice.rb +1 -1
- data/lib/samagotchi/context_candidates.rb +4 -3
- data/lib/samagotchi/continue_offer.rb +1 -1
- data/lib/samagotchi/debug_log.rb +1 -1
- data/lib/samagotchi/degrade.rb +75 -0
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +2 -2
- data/lib/samagotchi/engine/cards.rb +87 -0
- data/lib/samagotchi/engine/context_judge.rb +138 -0
- data/lib/samagotchi/engine/events.rb +131 -0
- data/lib/samagotchi/engine/guardrails.rb +87 -0
- data/lib/samagotchi/engine/hooks.rb +119 -0
- data/lib/samagotchi/engine/messages.rb +124 -0
- data/lib/samagotchi/engine/model_settings.rb +127 -0
- data/lib/samagotchi/engine/models.rb +261 -0
- data/lib/samagotchi/engine/objectives.rb +116 -0
- data/lib/samagotchi/engine/participants.rb +86 -0
- data/lib/samagotchi/engine/plugins.rb +208 -0
- data/lib/samagotchi/engine/questions.rb +98 -0
- data/lib/samagotchi/engine/recap.rb +130 -0
- data/lib/samagotchi/engine/session.rb +131 -0
- data/lib/samagotchi/engine/side_requests.rb +135 -0
- data/lib/samagotchi/engine/steering.rb +134 -0
- data/lib/samagotchi/engine/toolkit.rb +269 -0
- data/lib/samagotchi/engine/turn_end.rb +180 -0
- data/lib/samagotchi/engine/turn_helpers.rb +160 -0
- data/lib/samagotchi/engine/verify.rb +153 -0
- data/lib/samagotchi/engine.rb +63 -2642
- data/lib/samagotchi/grants.rb +575 -0
- data/lib/samagotchi/guardrail_wiring.rb +2 -2
- data/lib/samagotchi/guardrails/context.rb +2 -3
- data/lib/samagotchi/guardrails/parent_approvals.rb +2 -2
- data/lib/samagotchi/guardrails/parent_policy.rb +2 -2
- data/lib/samagotchi/guardrails/review_boundary.rb +4 -3
- data/lib/samagotchi/hook_routes.rb +3 -3
- data/lib/samagotchi/idle_recap.rb +4 -5
- data/lib/samagotchi/idle_reminders.rb +3 -4
- data/lib/samagotchi/iteration_limit.rb +2 -2
- data/lib/samagotchi/kernel_loop.rb +10 -11
- data/lib/samagotchi/launch_mode.rb +13 -32
- data/lib/samagotchi/level_setting_command.rb +3 -2
- data/lib/samagotchi/llm_context_command.rb +3 -2
- data/lib/samagotchi/llm_context_stale.rb +2 -2
- data/lib/samagotchi/log.rb +7 -7
- data/lib/samagotchi/log_line.rb +1 -1
- data/lib/samagotchi/log_path.rb +1 -1
- data/lib/samagotchi/message_parts.rb +3 -2
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/note_delivery.rb +18 -8
- data/lib/samagotchi/objectives.rb +24 -3
- data/lib/samagotchi/objectives_check.rb +8 -27
- data/lib/samagotchi/objectives_command.rb +4 -69
- data/lib/samagotchi/objectives_edit.rb +87 -0
- data/lib/samagotchi/output_formatter.rb +7 -1
- data/lib/samagotchi/owner_lock.rb +7 -1
- data/lib/samagotchi/parent_answers_command.rb +5 -4
- data/lib/samagotchi/participant_exposure.rb +42 -0
- data/lib/samagotchi/participant_mode.rb +1 -1
- data/lib/samagotchi/participant_name.rb +1 -1
- data/lib/samagotchi/participants.rb +17 -17
- data/lib/samagotchi/participants_watch.rb +1 -1
- data/lib/samagotchi/pending_input_queue.rb +2 -2
- data/lib/samagotchi/plugin/api.rb +3 -2
- data/lib/samagotchi/plugin/context.rb +21 -7
- data/lib/samagotchi/plugin/peer.rb +2 -1
- data/lib/samagotchi/plugin/sessions.rb +6 -4
- data/lib/samagotchi/plugin_tasks.rb +1 -2
- data/lib/samagotchi/private_files.rb +29 -0
- data/lib/samagotchi/project_env.rb +2 -2
- data/lib/samagotchi/question_desk.rb +8 -7
- data/lib/samagotchi/recap_command.rb +2 -1
- data/lib/samagotchi/recap_setup.rb +1 -1
- data/lib/samagotchi/recap_store.rb +1 -1
- data/lib/samagotchi/redact.rb +86 -0
- data/lib/samagotchi/reminder_queue.rb +2 -2
- data/lib/samagotchi/remote_attach_command.rb +1 -1
- data/lib/samagotchi/reply_wait/poll.rb +2 -2
- data/lib/samagotchi/review_start.rb +1 -1
- data/lib/samagotchi/run_options.rb +6 -6
- data/lib/samagotchi/self_report.rb +42 -39
- data/lib/samagotchi/send_command/busy.rb +1 -1
- data/lib/samagotchi/send_command.rb +4 -1
- data/lib/samagotchi/session.rb +11 -2
- data/lib/samagotchi/session_access/contract.rb +3 -3
- data/lib/samagotchi/session_access/local.rb +9 -9
- data/lib/samagotchi/session_access/remote.rb +17 -7
- data/lib/samagotchi/session_access.rb +4 -4
- data/lib/samagotchi/session_archive_command.rb +2 -2
- data/lib/samagotchi/session_chain.rb +8 -6
- data/lib/samagotchi/session_commands.rb +45 -48
- data/lib/samagotchi/session_continue.rb +1 -1
- data/lib/samagotchi/session_delete_command.rb +4 -4
- data/lib/samagotchi/session_manager.rb +59 -36
- data/lib/samagotchi/session_metrics.rb +2 -2
- data/lib/samagotchi/session_model_settings.rb +3 -2
- data/lib/samagotchi/session_participants.rb +1 -1
- data/lib/samagotchi/session_retention.rb +3 -3
- data/lib/samagotchi/sessions_command.rb +4 -4
- data/lib/samagotchi/share_command.rb +10 -10
- data/lib/samagotchi/steer.rb +1 -1
- data/lib/samagotchi/system_prompt.rb +102 -109
- data/lib/samagotchi/task_watch.rb +1 -1
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +72 -10
- data/lib/samagotchi/terminal_ui/attached_loop/composer.rb +12 -9
- data/lib/samagotchi/terminal_ui/attached_loop/exit_commands.rb +16 -0
- data/lib/samagotchi/terminal_ui/attached_loop/questions.rb +2 -3
- data/lib/samagotchi/terminal_ui/attached_loop.rb +17 -8
- data/lib/samagotchi/terminal_ui/attached_view.rb +2 -3
- data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -5
- data/lib/samagotchi/terminal_ui/formatting.rb +7 -7
- data/lib/samagotchi/terminal_ui/input_support.rb +1 -7
- data/lib/samagotchi/terminal_ui/line_reader.rb +2 -2
- data/lib/samagotchi/terminal_ui/live_region.rb +2 -2
- data/lib/samagotchi/terminal_ui/log_tail.rb +135 -0
- data/lib/samagotchi/terminal_ui/one_shot_sink.rb +2 -2
- data/lib/samagotchi/terminal_ui/plain_surface.rb +1 -1
- data/lib/samagotchi/terminal_ui/question_prompt.rb +4 -5
- data/lib/samagotchi/terminal_ui/reline_seam.rb +3 -3
- data/lib/samagotchi/terminal_ui/status_row.rb +7 -8
- data/lib/samagotchi/terminal_ui.rb +142 -1387
- data/lib/samagotchi/title_command.rb +2 -2
- data/lib/samagotchi/tool_runner.rb +14 -16
- data/lib/samagotchi/toolkit.rb +8 -5
- data/lib/samagotchi/toolkit_binding.rb +30 -5
- data/lib/samagotchi/toolkit_command.rb +2 -1
- data/lib/samagotchi/toolkit_report_command.rb +9 -5
- data/lib/samagotchi/toolkit_suggest.rb +12 -6
- data/lib/samagotchi/tools/builtin_calls.rb +14 -10
- data/lib/samagotchi/tools/deferred_tools.rb +2 -2
- data/lib/samagotchi/tools/delegate.rb +3 -1
- data/lib/samagotchi/tools/delegate_cursor.rb +3 -3
- data/lib/samagotchi/tools/delegate_wait.rb +1 -1
- data/lib/samagotchi/tools/objectives_tool.rb +2 -2
- data/lib/samagotchi/tools/registry.rb +44 -20
- data/lib/samagotchi/tools/send_note.rb +2 -1
- data/lib/samagotchi/tools/tool_index.rb +27 -21
- data/lib/samagotchi/turn_endings.rb +2 -2
- data/lib/samagotchi/turn_flow.rb +4 -5
- data/lib/samagotchi/update_hint.rb +2 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +39 -50
- data/lib/samagotchi/web/participant_access.rb +2 -2
- data/lib/samagotchi/web/participant_review.rb +5 -5
- data/lib/samagotchi/web/participant_stream_filter.rb +8 -8
- data/lib/samagotchi/web/participant_view.rb +2 -82
- data/lib/samagotchi/web/participants_admin.rb +1 -1
- data/lib/samagotchi/web/public/app.js +61 -357
- data/lib/samagotchi/web/public/block_host.js +35 -8
- data/lib/samagotchi/web/public/block_renderers.css +9 -5
- data/lib/samagotchi/web/public/chains.js +2 -2
- data/lib/samagotchi/web/public/claim_link.js +4 -4
- data/lib/samagotchi/web/public/composer_mode.js +110 -0
- data/lib/samagotchi/web/public/format.js +3 -3
- data/lib/samagotchi/web/public/owner_claim.js +81 -0
- data/lib/samagotchi/web/public/owner_verify.js +112 -0
- data/lib/samagotchi/web/public/session_fields.js +3 -3
- data/lib/samagotchi/web/public/strip_grip.js +3 -3
- data/lib/samagotchi/web/public/strip_hidden.js +65 -0
- data/lib/samagotchi/web/public/version_notices.js +132 -0
- data/lib/samagotchi/web/push/store.rb +1 -1
- data/lib/samagotchi/web/session_access_routes.rb +5 -4
- data/lib/samagotchi/web/session_control.rb +12 -8
- data/lib/samagotchi/web/session_create.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +2 -2
- data/lib/samagotchi/web/session_show.rb +2 -2
- data/lib/samagotchi/web/session_summary.rb +1 -1
- data/lib/samagotchi/worker.rb +106 -9
- data/lib/samagotchi/worker_link.rb +3 -3
- data/lib/samagotchi/working_set_command.rb +3 -2
- metadata +36 -6
- data/lib/samagotchi/terminal_ui/between_turns.rb +0 -138
- data/lib/samagotchi/terminal_ui/repl_input.rb +0 -152
- data/lib/samagotchi/web/grants.rb +0 -575
- data/lib/samagotchi/web/participant_exposure.rb +0 -44
- data/lib/samagotchi/web/private_files.rb +0 -31
|
@@ -12,7 +12,7 @@ require_relative "../bridge_client"
|
|
|
12
12
|
require_relative "../session_continue"
|
|
13
13
|
require_relative "../guardrails/parent_policy"
|
|
14
14
|
require_relative "../model_profile"
|
|
15
|
-
require_relative "../
|
|
15
|
+
require_relative "../grants"
|
|
16
16
|
require_relative "http_transport"
|
|
17
17
|
require_relative "../chi_invocation"
|
|
18
18
|
|
|
@@ -59,7 +59,7 @@ module Samagotchi
|
|
|
59
59
|
def remote? = true
|
|
60
60
|
def name = @entry.name
|
|
61
61
|
|
|
62
|
-
#
|
|
62
|
+
# Grants::PREFIX, without loading chi web.
|
|
63
63
|
PARTICIPANT_PREFIX = "chp_"
|
|
64
64
|
|
|
65
65
|
# Whether the token is a participant's (chi share's link), not the
|
|
@@ -229,7 +229,12 @@ module Samagotchi
|
|
|
229
229
|
SessionManager::DeliverResult.new(status: :accepted, via: via, ack: ack.except("via"))
|
|
230
230
|
when 504 then SessionManager::DeliverResult.new(status: :timeout, ack: { "detail" => ack["detail"] })
|
|
231
231
|
when 409
|
|
232
|
-
|
|
232
|
+
if ack["error"] == "owned_by_tui"
|
|
233
|
+
# A chi scratch session (scratch: true; an older chi web sends none).
|
|
234
|
+
raise SessionManager::ScratchSession, ack["session_id"] || id if ack["scratch"] == true
|
|
235
|
+
|
|
236
|
+
raise SessionManager::OwnedByTUI, ack["session_id"] || id
|
|
237
|
+
end
|
|
233
238
|
|
|
234
239
|
SessionManager::DeliverResult.new(status: :refused, code: 409, ack: ack)
|
|
235
240
|
when 404 then raise_refusal(reply)
|
|
@@ -316,7 +321,9 @@ module Samagotchi
|
|
|
316
321
|
|
|
317
322
|
NoteDelivery::Result.new(id: id, status: json["status"].to_sym, queued: json["queued"].to_i)
|
|
318
323
|
when 409
|
|
319
|
-
|
|
324
|
+
if json["error"] == "owned_by_tui"
|
|
325
|
+
return NoteDelivery::Result.new(id: id, status: :refused, queued: 0, scratch: json["scratch"] == true)
|
|
326
|
+
end
|
|
320
327
|
|
|
321
328
|
raise_refusal(reply)
|
|
322
329
|
when 400
|
|
@@ -584,7 +591,7 @@ module Samagotchi
|
|
|
584
591
|
# when none runs, waited for up to +wait+ seconds there (a leaving
|
|
585
592
|
# worker first: +read_timeout+ covers that wait too).
|
|
586
593
|
# @return [WakeReply]
|
|
587
|
-
# @raise [SessionManager::OwnedByTUI] a chi
|
|
594
|
+
# @raise [SessionManager::OwnedByTUI] a one-turn chi run has it there
|
|
588
595
|
# @raise [Refused] a leftover scratch session (code scratch), or its
|
|
589
596
|
# Bridge didn't come in time (code worker_timeout): chi web's words,
|
|
590
597
|
# LaunchMode's constants
|
|
@@ -709,7 +716,10 @@ module Samagotchi
|
|
|
709
716
|
case error
|
|
710
717
|
when "not_found" then raise ArgumentError, message.to_s
|
|
711
718
|
when "ambiguous_id" then raise Session::AmbiguousId.new(message, ids: Array(json["ids"]))
|
|
712
|
-
when "owned_by_tui"
|
|
719
|
+
when "owned_by_tui"
|
|
720
|
+
raise SessionManager::ScratchSession, sid if json["scratch"] == true
|
|
721
|
+
|
|
722
|
+
raise SessionManager::OwnedByTUI, sid
|
|
713
723
|
when "worker_running", "still_stopping" then raise SessionManager::DeleteRefused.new(sid, reason)
|
|
714
724
|
when "busy", "scratch"
|
|
715
725
|
raise SessionManager::ArchiveRefused.new(sid, reason, busy_id: json["busy_id"]) if reason
|
|
@@ -728,7 +738,7 @@ module Samagotchi
|
|
|
728
738
|
# A toolkit whose config kit's groups: is invalid there: the same.
|
|
729
739
|
when "toolkit_refused" then raise Toolkit::Refused, message.to_s
|
|
730
740
|
# The grants store doesn't read there: chi share says what it says here.
|
|
731
|
-
when "grants_unwritable" then raise
|
|
741
|
+
when "grants_unwritable" then raise Grants::Unwritable, message.to_s
|
|
732
742
|
when /\Ainvalid_/, "missing_fields", "bad_images"
|
|
733
743
|
# invalid_participant: chi share's name, kind or ttl, an archived session
|
|
734
744
|
raise BadRequest.new(message.to_s, code: error)
|
|
@@ -98,7 +98,7 @@ module Samagotchi
|
|
|
98
98
|
class BadRequest < Refused; end
|
|
99
99
|
|
|
100
100
|
# One participant's grant as chi share ls shows it
|
|
101
|
-
# (
|
|
101
|
+
# (Grants::Grant#public_h): never its token or digest. +state+
|
|
102
102
|
# "active", "revoked" or "expired".
|
|
103
103
|
# +cap+ dollars and +queue+ turns (nil from a chi web before them).
|
|
104
104
|
# +identity+ the email the grant is bound to, nil for any.
|
|
@@ -163,7 +163,7 @@ module Samagotchi
|
|
|
163
163
|
# A session as a session command reads it (Local: the Session itself;
|
|
164
164
|
# Remote: chi web's brief read). The fields the commands use, and
|
|
165
165
|
# Session's #display_title.
|
|
166
|
-
# @!attribute owner [OwnerLock::Owner, nil] its worker or chi
|
|
166
|
+
# @!attribute owner [OwnerLock::Owner, nil] its worker or one-turn chi run
|
|
167
167
|
# @!attribute pending_question [PendingQuestion, nil] the file's
|
|
168
168
|
SessionView = Data.define(:id, :status, :title, :display_title, :project_root, :working_directory, :model_name,
|
|
169
169
|
:model_typed, :parent_answers, :owner, :pending_question)
|
|
@@ -189,7 +189,7 @@ module Samagotchi
|
|
|
189
189
|
# features, epoch; nil: none answers)
|
|
190
190
|
def sidecar = link.live? ? link.sidecar : nil
|
|
191
191
|
|
|
192
|
-
# @return [OwnerLock::Owner, nil] a worker, a chi
|
|
192
|
+
# @return [OwnerLock::Owner, nil] a worker, a one-turn chi run, nil
|
|
193
193
|
def owner = link.owner
|
|
194
194
|
|
|
195
195
|
def live? = link.live?
|
|
@@ -238,7 +238,7 @@ module Samagotchi
|
|
|
238
238
|
# @!attribute known_turn [Boolean, nil] with a turn asked for
|
|
239
239
|
# (--turn), ReplyWait.known_turn?; nil without one
|
|
240
240
|
# @!attribute running [Boolean] the session's status was running
|
|
241
|
-
# @!attribute live [Boolean] running, or a worker or a
|
|
241
|
+
# @!attribute live [Boolean] running, or a worker or a one-shot owns it
|
|
242
242
|
# @!attribute settled [Boolean] idle, a turn ended and no question
|
|
243
243
|
# stands (a continue offer waits for its answer)
|
|
244
244
|
# @!attribute last_turn_id [String, nil] the last ended turn's
|
|
@@ -18,7 +18,7 @@ module Samagotchi
|
|
|
18
18
|
Usage: chi sessions archive (ID|PREFIX)...
|
|
19
19
|
Hides each session (and its delegates) from every list and keeps it
|
|
20
20
|
for good: the retention sweep never deletes it. A live worker is
|
|
21
|
-
stopped first; a session running a turn, or
|
|
21
|
+
stopped first; a session running a turn, or in a one-turn chi run, is
|
|
22
22
|
refused. chi sessions list --archived shows them; a message you send
|
|
23
23
|
to one brings it back.
|
|
24
24
|
TEXT
|
|
@@ -76,7 +76,7 @@ module Samagotchi
|
|
|
76
76
|
true
|
|
77
77
|
rescue SessionManager::OwnedByTUI => e
|
|
78
78
|
where = e.session_id == id ? "it is" : "a delegate of it is"
|
|
79
|
-
@stdout.puts("#{short} refused: #{where}
|
|
79
|
+
@stdout.puts("#{short} refused: #{where} #{OwnerLock::TUI_BUSY}; let it finish first")
|
|
80
80
|
false
|
|
81
81
|
rescue SessionManager::ArchiveRefused => e
|
|
82
82
|
@stdout.puts("#{short} refused: #{e.message}")
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require "time"
|
|
4
4
|
require_relative "answer_tail"
|
|
5
|
+
require_relative "answer_text"
|
|
5
6
|
require_relative "config"
|
|
6
7
|
require_relative "log"
|
|
7
|
-
require_relative "output_formatter"
|
|
8
8
|
require_relative "session"
|
|
9
9
|
require_relative "session_inbox"
|
|
10
10
|
require_relative "steer"
|
|
@@ -109,10 +109,11 @@ module Samagotchi
|
|
|
109
109
|
nil
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
# The session's last word: the text of AnswerTail's newest bubble
|
|
113
|
-
#
|
|
114
|
-
# turn (it asked for tools)
|
|
115
|
-
# (one that failed or was cut before it
|
|
112
|
+
# The session's last word: the text of AnswerTail's newest bubble as the
|
|
113
|
+
# user saw it (AnswerText.visible: no thought, a cut-off one neither),
|
|
114
|
+
# unless that bubble is a step of a turn (it asked for tools), old text a
|
|
115
|
+
# later turn never replaced (one that failed or was cut before it
|
|
116
|
+
# answered) or a thought alone. A cut reply keeps
|
|
116
117
|
# its "[interrupted]": that is what happened.
|
|
117
118
|
# @param messages [Array<Hash>]
|
|
118
119
|
# @return [String, nil]
|
|
@@ -123,7 +124,8 @@ module Samagotchi
|
|
|
123
124
|
at = messages.rindex { |m| m.equal?(found) }
|
|
124
125
|
return nil if messages[(at + 1)..].any? { |m| m[:role].to_s == "tool_response" || later_turn?(m) }
|
|
125
126
|
|
|
126
|
-
|
|
127
|
+
text = AnswerText.visible(found[:content])
|
|
128
|
+
text.empty? ? nil : text
|
|
127
129
|
end
|
|
128
130
|
|
|
129
131
|
# A turn's opening, not a `!shell` line: a command the user ran after
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "config"
|
|
4
|
+
require_relative "degrade"
|
|
4
5
|
require_relative "model_profile"
|
|
5
6
|
require_relative "served_model"
|
|
6
7
|
require_relative "prompt_note"
|
|
@@ -31,7 +32,7 @@ module Samagotchi
|
|
|
31
32
|
# load budget (spec/samagotchi/testing/sandbox_spec.rb).
|
|
32
33
|
autoload :ObjectivesCommand, File.expand_path("objectives_command", __dir__)
|
|
33
34
|
|
|
34
|
-
# The session commands a
|
|
35
|
+
# The session commands a session worker runs (a -p one-shot too): /model,
|
|
35
36
|
# /models, /llm-context, /guardrails, /context, !rollback, !cmd and the answer to a continue offer. They act on
|
|
36
37
|
# the Engine and its TurnFlow; the host prints the result's output and
|
|
37
38
|
# runs a continue turn when asked to (#run never runs a turn).
|
|
@@ -159,24 +160,24 @@ module Samagotchi
|
|
|
159
160
|
registry.register("/quit", "leave, like /exit", id: :exit, local: true, match: exit_match("/quit"))
|
|
160
161
|
registry.register("/archive", "leave and archive the session: hidden from the lists, kept for good", local: true,
|
|
161
162
|
match: ->(text) { text.casecmp?("/archive") })
|
|
162
|
-
# The
|
|
163
|
+
# The terminal's: the web has no worker to leave running.
|
|
163
164
|
registry.register("/detach", "leave and keep the worker running", local: true, uis: [:attached],
|
|
164
165
|
match: ->(text) { text.casecmp?("/detach") })
|
|
165
166
|
# /cut TEXT and /queue TEXT while a turn runs: a delivery the UIs parse
|
|
166
167
|
# themselves (ComposerRoute.steer_choice) before any command check; the
|
|
167
168
|
# entries exist for Tab completion and /help. The web composer sends
|
|
168
|
-
# /cut too (its queue is Alt+Enter).
|
|
169
|
-
#
|
|
169
|
+
# /cut too (its queue is Alt+Enter). The attached TUI queues in its
|
|
170
|
+
# worker's input files.
|
|
170
171
|
registry.register("/cut", "cut the running generation and go in now (/cut TEXT)",
|
|
171
|
-
local: true, uis: %i[
|
|
172
|
+
local: true, uis: %i[attached web], match: ->(text) { text.casecmp?("/cut") || text.match?(%r{\A/cut\s+\S}) })
|
|
172
173
|
registry.register("/queue", "run as the next turn after this one (/queue TEXT)",
|
|
173
|
-
local: true, uis:
|
|
174
|
+
local: true, uis: [:attached], match: ->(text) { text.casecmp?("/queue") || text.match?(%r{\A/queue\s+\S}) })
|
|
174
175
|
# Takes back the newest message this attached TUI queued, before it
|
|
175
176
|
# runs (the worker's POST turn/dequeue); the web has × on the bubble.
|
|
176
177
|
registry.register("/unqueue", "remove your newest queued message before it runs", local: true, uis: [:attached],
|
|
177
178
|
match: ->(text) { text.casecmp?("/unqueue") })
|
|
178
179
|
# chi share for the attached session (TerminalUI::ParticipantCommands):
|
|
179
|
-
# a participant's turns run on its worker
|
|
180
|
+
# a participant's turns run on its worker.
|
|
180
181
|
registry.register("/share", "invite someone to review this session (/share NAME [--agent] [--expires 3d] [--cap 2])",
|
|
181
182
|
local: true, uis: [:attached], match: ->(text) { text.match?(%r{\A/share(?:\s.*)?\z}i) })
|
|
182
183
|
registry.register("/participants", "list this session's participants (revoke [P], cap P DOLLARS)",
|
|
@@ -286,15 +287,15 @@ module Samagotchi
|
|
|
286
287
|
"commands:\n#{lines.join("\n")}"
|
|
287
288
|
end
|
|
288
289
|
|
|
289
|
-
# Where a UI's own entry runs, for /help:
|
|
290
|
-
# "terminal" (also for no uis:
|
|
290
|
+
# Where a UI's own entry runs, for /help: the attached TUI is
|
|
291
|
+
# "terminal" (also for no uis: the terminal), then "web". An older
|
|
292
|
+
# worker's :repl is no UI any more.
|
|
291
293
|
# @param uis [Array<Symbol>, nil]
|
|
292
|
-
# @return [String] "terminal", "
|
|
294
|
+
# @return [String] "terminal", "web", "terminal and web"
|
|
293
295
|
def ui_words(uis)
|
|
294
296
|
return "terminal" if uis.nil?
|
|
295
297
|
|
|
296
|
-
|
|
297
|
-
words = terminals.size == 2 ? ["terminal"] : terminals.map(&:to_s)
|
|
298
|
+
words = uis.include?(:attached) ? ["terminal"] : []
|
|
298
299
|
words << "web" if uis.include?(:web)
|
|
299
300
|
words.join(" and ")
|
|
300
301
|
end
|
|
@@ -309,8 +310,8 @@ module Samagotchi
|
|
|
309
310
|
# @param as_parent [Boolean] a parent agent sent it (client cli:answer):
|
|
310
311
|
# a /continue yes is held to guardrails.parent_answers, as chi
|
|
311
312
|
# answer --option Continue is (Guardrails::ParentPolicy)
|
|
312
|
-
# @param user_ui [Boolean] a user at a UI sent it (
|
|
313
|
-
#
|
|
313
|
+
# @param user_ui [Boolean] a user at a UI sent it (an attached TUI, the
|
|
314
|
+
# web; not a piped or marked one): only such a line loosens
|
|
314
315
|
# /parent-answers
|
|
315
316
|
# @return [Result, nil] nil when +line+ is not one of these commands
|
|
316
317
|
def run(line, confirm: true, as_parent: false, user_ui: false)
|
|
@@ -427,7 +428,7 @@ module Samagotchi
|
|
|
427
428
|
end
|
|
428
429
|
|
|
429
430
|
# The output goes into the conversation for the next turn. Not saved
|
|
430
|
-
# here (
|
|
431
|
+
# here (the next turn saves it). No parent-session
|
|
431
432
|
# marker: the user runs this, not the model. A worker's `chi --model X`
|
|
432
433
|
# default is unset, though: it was that run's, not the default of a chi
|
|
433
434
|
# run here (Tools::Builtins.cli_model_unset). It runs in the session's
|
|
@@ -726,7 +727,7 @@ module Samagotchi
|
|
|
726
727
|
|
|
727
728
|
# /recap on|off|default (RecapCommand); :recap in changed when this
|
|
728
729
|
# session's own recap.enabled moved. It doesn't save: its host does on
|
|
729
|
-
# a changed result (Worker#run_command
|
|
730
|
+
# a changed result (Worker#run_command).
|
|
730
731
|
def recap_setting(text)
|
|
731
732
|
output, changed, status = RecapCommand.new(engine: @engine).run(text.delete_prefix(RECAP_COMMAND))
|
|
732
733
|
reply(output, status: status, changed: changed ? [:recap] : [])
|
|
@@ -884,65 +885,61 @@ module Samagotchi
|
|
|
884
885
|
return unless session
|
|
885
886
|
|
|
886
887
|
@engine.store_model!(session)
|
|
887
|
-
|
|
888
|
-
@save.call(session)
|
|
889
|
-
rescue StandardError
|
|
890
|
-
nil
|
|
891
|
-
end
|
|
888
|
+
Degrade.call(:model, "model_switch_save_failed") { @save.call(session) }
|
|
892
889
|
end
|
|
893
890
|
|
|
894
891
|
# "; served: <name>" when the server serves another model than asked
|
|
895
892
|
# and its host's served: doesn't expect it.
|
|
896
893
|
def served_note
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
894
|
+
Degrade.call(:model, "served_note_failed", fallback: "") do
|
|
895
|
+
served, asked = @engine.served_model
|
|
896
|
+
ServedModel.differs?(asked, served) && !@engine.served_expected?(served) ? "; served: #{served}" : ""
|
|
897
|
+
end
|
|
901
898
|
end
|
|
902
899
|
|
|
903
900
|
# "; sampling: temperature=0.6 (hosts.work)" when the model has any.
|
|
904
901
|
def sampling_note
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
902
|
+
Degrade.call(:model, "sampling_note_failed", fallback: "") do
|
|
903
|
+
summary = @engine.sampling_summary
|
|
904
|
+
summary ? "; sampling: #{summary}" : ""
|
|
905
|
+
end
|
|
909
906
|
end
|
|
910
907
|
|
|
911
908
|
# "; steer cut: after 60 s (models: qwen)", or "; steer cut: never
|
|
912
909
|
# (hosts.splash)" for 0 or less, when the model has one configured;
|
|
913
910
|
# "" for the default.
|
|
914
911
|
def steer_cut_after_note
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
912
|
+
Degrade.call(:model, "steer_cut_note_failed", fallback: "") do
|
|
913
|
+
summary = @engine.steer_cut_summary
|
|
914
|
+
summary ? "; steer cut: #{summary}" : ""
|
|
915
|
+
end
|
|
919
916
|
end
|
|
920
917
|
|
|
921
918
|
# "; notes: model_notes_deepseek (system, 612 chars)": the model notes
|
|
922
919
|
# the session's prompt carries (after a switch, the ones the new
|
|
923
920
|
# model's prompt loads); "" without any.
|
|
924
921
|
def notes_note
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
922
|
+
Degrade.call(:model, "notes_note_failed", fallback: "") do
|
|
923
|
+
text = PromptNote.text(@engine.prompt_notes)
|
|
924
|
+
text.empty? ? "" : "; notes: #{text}"
|
|
925
|
+
end
|
|
929
926
|
end
|
|
930
927
|
|
|
931
928
|
# "; thinking: off (models: qwen)" when the model has a level set.
|
|
932
929
|
def thinking_note
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
930
|
+
Degrade.call(:model, "thinking_note_failed", fallback: "") do
|
|
931
|
+
summary = @engine.thinking_summary
|
|
932
|
+
summary ? "; thinking: #{summary}" : ""
|
|
933
|
+
end
|
|
937
934
|
end
|
|
938
935
|
|
|
939
936
|
# "; reasoning history: drop (models: qwen)" when the chat model has a
|
|
940
937
|
# level set.
|
|
941
938
|
def reasoning_history_note
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
939
|
+
Degrade.call(:model, "reasoning_history_note_failed", fallback: "") do
|
|
940
|
+
summary = @engine.reasoning_history_summary
|
|
941
|
+
summary ? "; reasoning history: #{summary}" : ""
|
|
942
|
+
end
|
|
946
943
|
end
|
|
947
944
|
|
|
948
945
|
# " (default: x, profile=qwen36, name)": +extra+ and the prompt profile,
|
|
@@ -954,9 +951,9 @@ module Samagotchi
|
|
|
954
951
|
end
|
|
955
952
|
|
|
956
953
|
def chat_model?
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
954
|
+
Degrade.call(:model, "chat_model_unresolved", fallback: false) do
|
|
955
|
+
@engine.chat_model?
|
|
956
|
+
end
|
|
960
957
|
end
|
|
961
958
|
|
|
962
959
|
# "profile=qwen36, server (chat_template)": the profile and where it came from.
|
|
@@ -24,7 +24,7 @@ module Samagotchi
|
|
|
24
24
|
# A continue that can't happen (#run). reason: :continued
|
|
25
25
|
# (another session continues it already: +ids+ names it), :open_children
|
|
26
26
|
# (open children it can't move to the new link: on an older chi's
|
|
27
|
-
# worker or open in a chi
|
|
27
|
+
# worker or open in a one-turn chi run; +ids+ names them) or :folder_gone (its
|
|
28
28
|
# folder isn't there).
|
|
29
29
|
class Refused < StandardError
|
|
30
30
|
attr_reader :session_id, :reason, :ids
|
|
@@ -9,7 +9,7 @@ require_relative "session_access/local"
|
|
|
9
9
|
module Samagotchi
|
|
10
10
|
# `chi sessions delete`: remove sessions for good (SessionManager.delete_session),
|
|
11
11
|
# one line per id. A live worker's session is refused unless --force stops
|
|
12
|
-
# the worker first; a chi
|
|
12
|
+
# the worker first; a one-turn chi run's never is.
|
|
13
13
|
class SessionDeleteCommand
|
|
14
14
|
include CLI::Command
|
|
15
15
|
|
|
@@ -18,8 +18,8 @@ module Samagotchi
|
|
|
18
18
|
Deletes each session: its history, notes, images and queued input.
|
|
19
19
|
This can't be undone.
|
|
20
20
|
-f, --force stop a session's running worker first (without it,
|
|
21
|
-
a live session is refused); a session
|
|
22
|
-
chi
|
|
21
|
+
a live session is refused); a session in a one-turn
|
|
22
|
+
chi run (chi --non-interactive) is always refused
|
|
23
23
|
TEXT
|
|
24
24
|
PREVIEW_LIMIT = 60
|
|
25
25
|
FLAGS = CLI::Flags.new(help: CLI::Command::HELP_WORDS) { |f| f.switch "-f", "--force" }
|
|
@@ -67,7 +67,7 @@ module Samagotchi
|
|
|
67
67
|
@stdout.puts(["#{short} deleted#{" (stopped its worker)" if result[:stopped]}", preview].compact.join(" "))
|
|
68
68
|
true
|
|
69
69
|
rescue SessionManager::OwnedByTUI
|
|
70
|
-
@stdout.puts("#{short} refused: it is
|
|
70
|
+
@stdout.puts("#{short} refused: it is #{OwnerLock::TUI_BUSY}; let it finish first")
|
|
71
71
|
false
|
|
72
72
|
rescue SessionManager::DeleteRefused => e
|
|
73
73
|
line = if e.reason == :still_stopping then "its worker is still shutting down; try again in a moment"
|
|
@@ -81,23 +81,32 @@ module Samagotchi
|
|
|
81
81
|
autoload :SessionRetention, File.expand_path("session_retention", __dir__)
|
|
82
82
|
# session_continue requires this file.
|
|
83
83
|
autoload :SessionContinue, File.expand_path("session_continue", __dir__)
|
|
84
|
-
# Needed only on an archive or a delete (it loads
|
|
84
|
+
# Needed only on an archive or a delete (it loads Grants).
|
|
85
85
|
autoload :Participants, File.expand_path("participants", __dir__)
|
|
86
86
|
|
|
87
87
|
class SessionManager
|
|
88
|
-
# Raised when
|
|
89
|
-
#
|
|
88
|
+
# Raised when a terminal's own chi owns the session (OwnerLock kind
|
|
89
|
+
# "tui": a -p --non-interactive one-shot, TerminalUI): it runs its own
|
|
90
|
+
# Engine and reads no input files, so a worker must not be spawned or
|
|
91
|
+
# signalled.
|
|
90
92
|
class OwnedByTUI < StandardError
|
|
91
|
-
# @return [String] the session
|
|
92
|
-
#
|
|
93
|
+
# @return [String] the session it owns (a delegate's, when an archive
|
|
94
|
+
# met one)
|
|
93
95
|
attr_reader :session_id
|
|
94
96
|
|
|
95
|
-
def initialize(session_id)
|
|
97
|
+
def initialize(session_id, words = nil)
|
|
96
98
|
@session_id = session_id
|
|
97
|
-
super("session #{session_id} is
|
|
99
|
+
super(words || "session #{session_id} is #{OwnerLock::TUI_BUSY}; let it finish first")
|
|
98
100
|
end
|
|
99
101
|
end
|
|
100
102
|
|
|
103
|
+
# A `chi scratch` session: its terminal's alone (Session::SCRATCH_REFUSED).
|
|
104
|
+
# An OwnedByTUI, so every caller that refuses a terminal's session
|
|
105
|
+
# (chi send, chi web's 409 owned_by_tui) refuses it the same way.
|
|
106
|
+
class ScratchSession < OwnedByTUI
|
|
107
|
+
def initialize(session_id) = super(session_id, Session::SCRATCH_REFUSED)
|
|
108
|
+
end
|
|
109
|
+
|
|
101
110
|
# A delete that would pull the session from under its live worker.
|
|
102
111
|
# reason: :worker_running (not asked to stop it) or :still_stopping
|
|
103
112
|
# (stopped, but the worker outlived the wait).
|
|
@@ -173,12 +182,15 @@ module Samagotchi
|
|
|
173
182
|
# requester, rules and claim (VerifyFresh.start)
|
|
174
183
|
# @param before_spawn [#call, nil] (session) runs once the session and
|
|
175
184
|
# its note are saved, before its worker spawns
|
|
185
|
+
# @param scratch [Boolean] a `chi scratch` session (its terminal's
|
|
186
|
+
# AttachLauncher): its worker runs a scratch Engine and deletes it
|
|
187
|
+
# once its last client is gone
|
|
176
188
|
# @return [Session] with #seed_images_dropped: refs whose file was gone,
|
|
177
189
|
# and #model_warning: an id its host's saved list doesn't have
|
|
178
190
|
def self.spawn_session(prompt:, mode: "assist", working_directory: nil, model_name: nil, state_dir: nil,
|
|
179
191
|
memories: [], muted_memories: [], parent_id: nil, messages: [], images_from: nil,
|
|
180
192
|
preview: nil, title: nil, delegate: false, setup: SessionSetup.new, continues: nil, note: nil,
|
|
181
|
-
note_source: "chi", before_spawn: nil, review: nil, verify_fresh: nil)
|
|
193
|
+
note_source: "chi", before_spawn: nil, review: nil, verify_fresh: nil, scratch: false)
|
|
182
194
|
sd = state_dir || Session.default_state_dir
|
|
183
195
|
# The resolved ref is stored (a resumed session keeps its model when an
|
|
184
196
|
# alias is retargeted), with the name as typed beside it. A wrong host
|
|
@@ -201,7 +213,8 @@ module Samagotchi
|
|
|
201
213
|
delegate: delegate,
|
|
202
214
|
setup: setup,
|
|
203
215
|
continues: continues,
|
|
204
|
-
review: review
|
|
216
|
+
review: review,
|
|
217
|
+
scratch: scratch
|
|
205
218
|
)
|
|
206
219
|
session.toolkit = Toolkit.configured(delegate: delegate) if session.toolkit.nil?
|
|
207
220
|
session.toolkit_origin = setup.toolkit_origin || "default"
|
|
@@ -316,7 +329,8 @@ module Samagotchi
|
|
|
316
329
|
# +project_root+ unless asked for every project.
|
|
317
330
|
# @param live [Boolean] only sessions a worker owns now (the owner lock,
|
|
318
331
|
# not the saved status, which a dead worker leaves at "running"); a
|
|
319
|
-
#
|
|
332
|
+
# session a one-shot (or an older chi's REPL) owns is left out: it
|
|
333
|
+
# can't take notes
|
|
320
334
|
# @param cwd [String, nil] only sessions in this folder or below it
|
|
321
335
|
# @param project_root [String, nil] only this project's sessions
|
|
322
336
|
# (Session#project_root)
|
|
@@ -405,7 +419,7 @@ module Samagotchi
|
|
|
405
419
|
# stopped first; the marker is written even while it shuts down.
|
|
406
420
|
# @return [ArchiveResult]
|
|
407
421
|
# @raise [ArgumentError] unknown id (Session::AmbiguousId for a prefix of several)
|
|
408
|
-
# @raise [OwnedByTUI] a chi
|
|
422
|
+
# @raise [OwnedByTUI] a one-turn chi run owns it or one of its children
|
|
409
423
|
# @raise [ArchiveRefused] a turn runs in it or in a child, a prompt is
|
|
410
424
|
# queued in one (a later resume would run it), a live one offers to
|
|
411
425
|
# continue past the step limit, or it is a scratch session
|
|
@@ -447,7 +461,7 @@ module Samagotchi
|
|
|
447
461
|
# turn runs), :queued (input/ holds a prompt, which outlives the stop and
|
|
448
462
|
# runs on a resume, live worker or not), :offer (a live worker's continue
|
|
449
463
|
# offer, which the stop would drop), or nil.
|
|
450
|
-
# @raise [OwnedByTUI] a chi
|
|
464
|
+
# @raise [OwnedByTUI] a one-turn chi run owns it
|
|
451
465
|
def self.archive_refusal(sid, owner, state_dir)
|
|
452
466
|
raise OwnedByTUI, sid if owner&.tui?
|
|
453
467
|
|
|
@@ -555,6 +569,8 @@ module Samagotchi
|
|
|
555
569
|
def self.resume_session(session_id, state_dir: nil)
|
|
556
570
|
sd = state_dir || Session.default_state_dir
|
|
557
571
|
session = Session.load(session_id, state_dir: sd)
|
|
572
|
+
# Its terminal's worker came from spawn_session; nothing wakes another.
|
|
573
|
+
raise ScratchSession, session.id if session.scratch
|
|
558
574
|
return session if refuse_tui!(session.id, state_dir: sd)
|
|
559
575
|
|
|
560
576
|
# status is turn state, not liveness: a new worker runs no turn yet,
|
|
@@ -576,7 +592,7 @@ module Samagotchi
|
|
|
576
592
|
|
|
577
593
|
# A delegate child rang a parent that has no worker (ChildRing): start
|
|
578
594
|
# one, which runs a turn for the report. Not for a parent that is gone,
|
|
579
|
-
# stopped, archived, a scratch session,
|
|
595
|
+
# stopped, archived, a scratch session, a one-turn run's, or a
|
|
580
596
|
# continue's new link still taking its delegates (its own spawn comes):
|
|
581
597
|
# the ring waits on disk for whoever runs it next (#resume_session
|
|
582
598
|
# itself would clear the stop). A stop landing between the check and
|
|
@@ -686,7 +702,8 @@ module Samagotchi
|
|
|
686
702
|
reminders: "it has reminders, which its worker's older chi can't carry over a restart",
|
|
687
703
|
command_running: "a /btw (or another command) is still running",
|
|
688
704
|
relays_open: "a delegate's approval relayed here is open or just answered",
|
|
689
|
-
background_tasks: "background tasks it started are still running"
|
|
705
|
+
background_tasks: "background tasks it started are still running",
|
|
706
|
+
scratch: "a chi scratch session's worker ends with its terminal"
|
|
690
707
|
}.merge(WorkerIdleExit::LEAVING_WORDS).freeze
|
|
691
708
|
|
|
692
709
|
# What the CLI and the web say.
|
|
@@ -726,7 +743,7 @@ module Samagotchi
|
|
|
726
743
|
# be lost and starts its successor. If the worker cannot be asked, stop
|
|
727
744
|
# it without a stop marker and resume only when disk shows no work to lose.
|
|
728
745
|
# @return [Restarted]
|
|
729
|
-
# @raise [OwnedByTUI] a chi
|
|
746
|
+
# @raise [OwnedByTUI] a one-turn chi run owns the session
|
|
730
747
|
# @raise [RestartRefused]
|
|
731
748
|
# @raise [ArgumentError] no such session
|
|
732
749
|
def self.restart_session(session_id, state_dir: nil, wait: RESTART_WAIT, client_id: ClientId::CLI_RESTART)
|
|
@@ -917,7 +934,7 @@ module Samagotchi
|
|
|
917
934
|
EMPTY_SKELETON_DIRS = (EMPTY_DIRS + [SessionInbox::OUTPUT_DIR]).freeze
|
|
918
935
|
|
|
919
936
|
# session.keep_empty off: sessions left with nothing in them are deleted
|
|
920
|
-
# (the worker as it exits, the
|
|
937
|
+
# (the worker as it exits, the retention sweep).
|
|
921
938
|
def self.discard_empty?
|
|
922
939
|
Samagotchi::Config.get("session.keep_empty") != true
|
|
923
940
|
rescue StandardError
|
|
@@ -926,25 +943,14 @@ module Samagotchi
|
|
|
926
943
|
|
|
927
944
|
# Whether a session being left (or found left) is deleted: session.keep_empty
|
|
928
945
|
# is off and nothing happened in it (#empty_session?). The worker as it
|
|
929
|
-
# leaves
|
|
946
|
+
# leaves and the retention sweep both ask this.
|
|
930
947
|
# @param default_model [String, nil] what a new session starts on
|
|
931
|
-
# @param model_name [String, nil] the model its owner runs now (the REPL
|
|
932
|
-
# keeps /model in its Engine); the saved one is checked either way
|
|
933
948
|
# @param used_memory_names [Array<String>] memory its owner's Engine used
|
|
934
949
|
# before any save
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
def self.discardable?(session_id, default_model:, state_dir: nil, model_name: default_model, used_memory_names: [],
|
|
938
|
-
unsaved: nil)
|
|
939
|
-
return false unless discard_empty? && same_model?(model_name, default_model) && Array(used_memory_names).empty?
|
|
940
|
-
|
|
941
|
-
sd = state_dir || Session.default_state_dir
|
|
942
|
-
if unsaved && !File.exist?(Session.session_file(session_id, state_dir: sd))
|
|
943
|
-
return no_conversation?(unsaved.messages) && unsaved.last_prompt.to_s.strip.empty? &&
|
|
944
|
-
empty_session_dir?(Session.session_dir(session_id, state_dir: sd))
|
|
945
|
-
end
|
|
950
|
+
def self.discardable?(session_id, default_model:, state_dir: nil, used_memory_names: [])
|
|
951
|
+
return false unless discard_empty? && Array(used_memory_names).empty?
|
|
946
952
|
|
|
947
|
-
empty_session?(session_id, state_dir:
|
|
953
|
+
empty_session?(session_id, state_dir: state_dir || Session.default_state_dir, default_model: default_model)
|
|
948
954
|
end
|
|
949
955
|
|
|
950
956
|
# A session nothing happened in: no conversation, no turn tried (a failed
|
|
@@ -976,7 +982,7 @@ module Samagotchi
|
|
|
976
982
|
false
|
|
977
983
|
end
|
|
978
984
|
|
|
979
|
-
# Only the system prompt (
|
|
985
|
+
# Only the system prompt (an Engine seeds one, a saved session keeps
|
|
980
986
|
# it); a context note is a system message too, but with its kind.
|
|
981
987
|
# A turn note (the tail system message after a failed, cancelled or empty
|
|
982
988
|
# turn) is not a conversation either: a session whose only turn failed
|
|
@@ -1049,7 +1055,7 @@ module Samagotchi
|
|
|
1049
1055
|
# @param stop [Boolean] stop a live worker first (waits +wait+ seconds)
|
|
1050
1056
|
# @return [Hash] {id:, removed: [paths], stopped: whether a worker was stopped}
|
|
1051
1057
|
# @raise [ArgumentError] unknown id (Session::AmbiguousId for a prefix of several)
|
|
1052
|
-
# @raise [OwnedByTUI] a chi
|
|
1058
|
+
# @raise [OwnedByTUI] a one-turn chi run owns it (never stopped from here)
|
|
1053
1059
|
# @raise [DeleteRefused] a worker owns it and +stop+ is false, or it
|
|
1054
1060
|
# outlived the wait
|
|
1055
1061
|
# @param purge_runs [Boolean] also delete System 1's saved runs of it and
|
|
@@ -1130,7 +1136,7 @@ module Samagotchi
|
|
|
1130
1136
|
# ticks (nil: Worker::FALLBACK_TICK_SECONDS); queued turns wake it at once
|
|
1131
1137
|
# A stopped session's worker exits 0 (a signalled one too) and a
|
|
1132
1138
|
# crashed one 1, there.
|
|
1133
|
-
# @return [Symbol] :idle_exit, :exit_requested or :
|
|
1139
|
+
# @return [Symbol] :idle_exit, :exit_requested, :restart or :scratch_left
|
|
1134
1140
|
def self.run_session_loop(session_id, state_dir: nil, owner_wait: OwnerLock::DEFAULT_WAIT,
|
|
1135
1141
|
idle_exit_minutes: nil, poll_interval: nil)
|
|
1136
1142
|
# A worker inherits chi's locale (LC_ALL=C too): read files as UTF-8.
|
|
@@ -1175,6 +1181,13 @@ module Samagotchi
|
|
|
1175
1181
|
signals.restore!
|
|
1176
1182
|
end
|
|
1177
1183
|
Log.info(:worker, "stop", reason: result)
|
|
1184
|
+
# A scratch session goes with its worker however it leaves (its
|
|
1185
|
+
# terminal deletes it too, whichever comes first); nothing resumes it.
|
|
1186
|
+
if worker.scratch?
|
|
1187
|
+
delete_left_scratch(session_id, state_dir: sd)
|
|
1188
|
+
exit(result == :crashed ? 1 : 0) if %i[stopped signal crashed].include?(result)
|
|
1189
|
+
return result
|
|
1190
|
+
end
|
|
1178
1191
|
# A stopped session is left as it is: no resume for input that came
|
|
1179
1192
|
# in, no discard. One whose worker a signal stopped isn't discarded
|
|
1180
1193
|
# either; input that came in (queued behind the turn it stopped)
|
|
@@ -1231,6 +1244,16 @@ module Samagotchi
|
|
|
1231
1244
|
Process.exit!(0)
|
|
1232
1245
|
end
|
|
1233
1246
|
|
|
1247
|
+
# Delete a scratch session its worker left (its terminal gone:
|
|
1248
|
+
# Worker#leave_scratch, or any other way out), now the lock is free.
|
|
1249
|
+
# Its terminal may have deleted it first.
|
|
1250
|
+
private_class_method def self.delete_left_scratch(session_id, state_dir:)
|
|
1251
|
+
delete_session(session_id, state_dir: state_dir)
|
|
1252
|
+
Log.info(:worker, "scratch_deleted", sid: session_id)
|
|
1253
|
+
rescue DeleteRefused, ArgumentError, SystemCallError => e
|
|
1254
|
+
Log.info(:worker, "scratch_kept", sid: session_id, error: e.class.name, msg: e.message)
|
|
1255
|
+
end
|
|
1256
|
+
|
|
1234
1257
|
# Delete a session its worker left empty. Checked again now the lock is
|
|
1235
1258
|
# free: a note or input may have come in since the worker looked, and a
|
|
1236
1259
|
# worker woken meanwhile owns it (delete_session refuses). A `chi send`
|
|
@@ -1477,7 +1500,7 @@ module Samagotchi
|
|
|
1477
1500
|
# a worker that doesn't queue (no file then: it would steer), :timeout
|
|
1478
1501
|
# when it took the request but never answered (no file then), or
|
|
1479
1502
|
# :failed when the input file couldn't be written
|
|
1480
|
-
# @raise [OwnedByTUI] a chi
|
|
1503
|
+
# @raise [OwnedByTUI] a one-turn chi run owns the session
|
|
1481
1504
|
# @raise [ArgumentError] no such session
|
|
1482
1505
|
def self.deliver_turn(session_id, prompt:, client_id: nil, images: [], delivery: nil, no_interrupt: false,
|
|
1483
1506
|
author: nil, pending_limit: nil, anchors: nil, state_dir: nil, manager: self, bridge: nil)
|
|
@@ -1532,7 +1555,7 @@ module Samagotchi
|
|
|
1532
1555
|
# A participant's file only for a worker started after it: a worker
|
|
1533
1556
|
# still holding the session with its Bridge gone (exiting) may be an
|
|
1534
1557
|
# older one, which would run it without the review boundary.
|
|
1535
|
-
# (A chi
|
|
1558
|
+
# (A one-turn chi run holding it is refused as for anyone, below.)
|
|
1536
1559
|
if Steer.participant_author?(author) && (holder = delivery_owner(manager, session_id, state_dir)) && !holder.tui?
|
|
1537
1560
|
return DeliverResult.new(status: :timeout, via: :file,
|
|
1538
1561
|
ack: { "error" => "worker_timeout",
|