samagotchi 0.153.0 → 0.154.1
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 +42 -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/bundles.md +9 -12
- data/docs/cli.md +82 -96
- data/docs/configuration.md +27 -26
- data/docs/context.md +3 -3
- data/docs/desktop.md +1 -1
- data/docs/guardrails.md +8 -10
- data/docs/hooks.md +9 -9
- data/docs/internals/attached-context.md +2 -2
- data/docs/internals/background-tasks.md +3 -4
- data/docs/internals/block-renderers.md +4 -0
- data/docs/internals/bridge.md +21 -8
- data/docs/internals/context-telemetry.md +1 -1
- data/docs/internals/engine.md +33 -7
- data/docs/internals/objectives.md +6 -5
- data/docs/internals/participants.md +7 -7
- data/docs/internals/prompt-caching.md +4 -2
- data/docs/internals/remote-sessions.md +7 -7
- data/docs/internals/web-frontend.md +2 -2
- data/docs/internals/web-push.md +1 -1
- data/docs/memory.md +2 -2
- data/docs/plugins.md +23 -28
- data/docs/sessions.md +24 -24
- data/docs/sub-agent.md +2 -2
- data/docs/system-one.md +1 -1
- data/docs/testing.md +31 -6
- data/lib/samagotchi/answer_text.rb +33 -0
- data/lib/samagotchi/bridge/card_places.rb +56 -7
- data/lib/samagotchi/bridge/card_store.rb +24 -9
- data/lib/samagotchi/bridge.rb +52 -32
- data/lib/samagotchi/bridge_client.rb +19 -14
- 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/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +2 -2
- data/lib/samagotchi/bundles/btw/manifest.yml +2 -2
- data/lib/samagotchi/bundles/btw/plugin.rb +1 -1
- data/lib/samagotchi/bundles/coordinator/manifest.yml +2 -2
- data/lib/samagotchi/bundles/coordinator/plugin.rb +3 -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/github-pr/manifest.yml +2 -2
- data/lib/samagotchi/bundles/github-pr/plugin.rb +3 -3
- data/lib/samagotchi/bundles/skills/manifest.yml +2 -2
- data/lib/samagotchi/bundles/skills/plugin.rb +3 -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/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 +3 -3
- 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 +62 -2646
- data/lib/samagotchi/engine_snapshots.rb +9 -8
- 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/hook_routes.rb +3 -3
- data/lib/samagotchi/idle_recap.rb +7 -6
- data/lib/samagotchi/idle_reminders.rb +3 -4
- data/lib/samagotchi/iteration_limit.rb +2 -2
- data/lib/samagotchi/kernel_loop.rb +13 -13
- data/lib/samagotchi/launch_mode.rb +13 -32
- data/lib/samagotchi/level_setting_command.rb +3 -2
- data/lib/samagotchi/llm/chat_loop.rb +3 -1
- data/lib/samagotchi/llm_context_command.rb +3 -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/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/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 +67 -49
- 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 +27 -7
- data/lib/samagotchi/sessions_command.rb +4 -4
- data/lib/samagotchi/share_command.rb +10 -10
- data/lib/samagotchi/steer.rb +13 -2
- data/lib/samagotchi/stream_event.rb +22 -0
- data/lib/samagotchi/system_prompt.rb +154 -145
- 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 +1 -11
- 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 +17 -22
- data/lib/samagotchi/terminal_ui/join_replay.rb +16 -12
- 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 +2 -12
- data/lib/samagotchi/terminal_ui/status_row.rb +6 -14
- data/lib/samagotchi/terminal_ui.rb +142 -1387
- data/lib/samagotchi/title_command.rb +2 -2
- data/lib/samagotchi/tool_runner.rb +15 -16
- data/lib/samagotchi/toolkit_command.rb +2 -1
- 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/send_note.rb +2 -1
- data/lib/samagotchi/turn_endings.rb +2 -2
- data/lib/samagotchi/turn_flow.rb +11 -7
- data/lib/samagotchi/update_hint.rb +2 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +42 -51
- data/lib/samagotchi/web/message_display.rb +7 -0
- 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/block_host.js +35 -8
- data/lib/samagotchi/web/public/block_renderers.css +9 -5
- data/lib/samagotchi/web/public/card_stamps.js +27 -0
- data/lib/samagotchi/web/public/cards_place.js +5 -3
- 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 +1 -1
- data/lib/samagotchi/web/public/format.js +4 -3
- data/lib/samagotchi/web/public/turn_html.js +8 -4
- data/lib/samagotchi/web/push/store.rb +1 -1
- data/lib/samagotchi/web/session_access_routes.rb +13 -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 +6 -2
- data/lib/samagotchi/web/session_summary.rb +1 -1
- data/lib/samagotchi/worker.rb +103 -9
- data/lib/samagotchi/worker_link.rb +3 -3
- data/lib/samagotchi/working_set_command.rb +3 -2
- data/lib/samagotchi.rb +0 -1
- metadata +31 -7
- data/lib/samagotchi/pending_input_queue.rb +0 -55
- 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
|
@@ -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"
|
|
@@ -33,6 +33,7 @@ require_relative "worker_idle_exit"
|
|
|
33
33
|
require_relative "worker_signals"
|
|
34
34
|
require_relative "chi_invocation"
|
|
35
35
|
require_relative "log"
|
|
36
|
+
require_relative "degrade"
|
|
36
37
|
require_relative "log_path"
|
|
37
38
|
require_relative "paths"
|
|
38
39
|
require_relative "project_env"
|
|
@@ -81,23 +82,32 @@ module Samagotchi
|
|
|
81
82
|
autoload :SessionRetention, File.expand_path("session_retention", __dir__)
|
|
82
83
|
# session_continue requires this file.
|
|
83
84
|
autoload :SessionContinue, File.expand_path("session_continue", __dir__)
|
|
84
|
-
# Needed only on an archive or a delete (it loads
|
|
85
|
+
# Needed only on an archive or a delete (it loads Grants).
|
|
85
86
|
autoload :Participants, File.expand_path("participants", __dir__)
|
|
86
87
|
|
|
87
88
|
class SessionManager
|
|
88
|
-
# Raised when
|
|
89
|
-
#
|
|
89
|
+
# Raised when a terminal's own chi owns the session (OwnerLock kind
|
|
90
|
+
# "tui": a -p --non-interactive one-shot, TerminalUI): it runs its own
|
|
91
|
+
# Engine and reads no input files, so a worker must not be spawned or
|
|
92
|
+
# signalled.
|
|
90
93
|
class OwnedByTUI < StandardError
|
|
91
|
-
# @return [String] the session
|
|
92
|
-
#
|
|
94
|
+
# @return [String] the session it owns (a delegate's, when an archive
|
|
95
|
+
# met one)
|
|
93
96
|
attr_reader :session_id
|
|
94
97
|
|
|
95
|
-
def initialize(session_id)
|
|
98
|
+
def initialize(session_id, words = nil)
|
|
96
99
|
@session_id = session_id
|
|
97
|
-
super("session #{session_id} is
|
|
100
|
+
super(words || "session #{session_id} is #{OwnerLock::TUI_BUSY}; let it finish first")
|
|
98
101
|
end
|
|
99
102
|
end
|
|
100
103
|
|
|
104
|
+
# A `chi scratch` session: its terminal's alone (Session::SCRATCH_REFUSED).
|
|
105
|
+
# An OwnedByTUI, so every caller that refuses a terminal's session
|
|
106
|
+
# (chi send, chi web's 409 owned_by_tui) refuses it the same way.
|
|
107
|
+
class ScratchSession < OwnedByTUI
|
|
108
|
+
def initialize(session_id) = super(session_id, Session::SCRATCH_REFUSED)
|
|
109
|
+
end
|
|
110
|
+
|
|
101
111
|
# A delete that would pull the session from under its live worker.
|
|
102
112
|
# reason: :worker_running (not asked to stop it) or :still_stopping
|
|
103
113
|
# (stopped, but the worker outlived the wait).
|
|
@@ -173,12 +183,15 @@ module Samagotchi
|
|
|
173
183
|
# requester, rules and claim (VerifyFresh.start)
|
|
174
184
|
# @param before_spawn [#call, nil] (session) runs once the session and
|
|
175
185
|
# its note are saved, before its worker spawns
|
|
186
|
+
# @param scratch [Boolean] a `chi scratch` session (its terminal's
|
|
187
|
+
# AttachLauncher): its worker runs a scratch Engine and deletes it
|
|
188
|
+
# once its last client is gone
|
|
176
189
|
# @return [Session] with #seed_images_dropped: refs whose file was gone,
|
|
177
190
|
# and #model_warning: an id its host's saved list doesn't have
|
|
178
191
|
def self.spawn_session(prompt:, mode: "assist", working_directory: nil, model_name: nil, state_dir: nil,
|
|
179
192
|
memories: [], muted_memories: [], parent_id: nil, messages: [], images_from: nil,
|
|
180
193
|
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)
|
|
194
|
+
note_source: "chi", before_spawn: nil, review: nil, verify_fresh: nil, scratch: false)
|
|
182
195
|
sd = state_dir || Session.default_state_dir
|
|
183
196
|
# The resolved ref is stored (a resumed session keeps its model when an
|
|
184
197
|
# alias is retargeted), with the name as typed beside it. A wrong host
|
|
@@ -201,7 +214,8 @@ module Samagotchi
|
|
|
201
214
|
delegate: delegate,
|
|
202
215
|
setup: setup,
|
|
203
216
|
continues: continues,
|
|
204
|
-
review: review
|
|
217
|
+
review: review,
|
|
218
|
+
scratch: scratch
|
|
205
219
|
)
|
|
206
220
|
session.toolkit = Toolkit.configured(delegate: delegate) if session.toolkit.nil?
|
|
207
221
|
session.toolkit_origin = setup.toolkit_origin || "default"
|
|
@@ -269,7 +283,7 @@ module Samagotchi
|
|
|
269
283
|
child_env[ModelProfile::MODEL_FROM_CLI_ENV] = "1" if child_env.key?(ModelProfile::MODEL_ENV)
|
|
270
284
|
# And the spawner's debug log, absolute: a relative log.file would
|
|
271
285
|
# otherwise land in the worker's (the session's) directory.
|
|
272
|
-
log_path = begin LogPath.resolve rescue nil end
|
|
286
|
+
log_path = begin LogPath.resolve rescue nil end # degrade-ok: no log to say it in; the worker's is off (below)
|
|
273
287
|
if log_path
|
|
274
288
|
child_env["SAMAGOTCHI_LOG_FILE"] = log_path
|
|
275
289
|
else
|
|
@@ -316,7 +330,8 @@ module Samagotchi
|
|
|
316
330
|
# +project_root+ unless asked for every project.
|
|
317
331
|
# @param live [Boolean] only sessions a worker owns now (the owner lock,
|
|
318
332
|
# not the saved status, which a dead worker leaves at "running"); a
|
|
319
|
-
#
|
|
333
|
+
# session a one-shot (or an older chi's REPL) owns is left out: it
|
|
334
|
+
# can't take notes
|
|
320
335
|
# @param cwd [String, nil] only sessions in this folder or below it
|
|
321
336
|
# @param project_root [String, nil] only this project's sessions
|
|
322
337
|
# (Session#project_root)
|
|
@@ -405,7 +420,7 @@ module Samagotchi
|
|
|
405
420
|
# stopped first; the marker is written even while it shuts down.
|
|
406
421
|
# @return [ArchiveResult]
|
|
407
422
|
# @raise [ArgumentError] unknown id (Session::AmbiguousId for a prefix of several)
|
|
408
|
-
# @raise [OwnedByTUI] a chi
|
|
423
|
+
# @raise [OwnedByTUI] a one-turn chi run owns it or one of its children
|
|
409
424
|
# @raise [ArchiveRefused] a turn runs in it or in a child, a prompt is
|
|
410
425
|
# queued in one (a later resume would run it), a live one offers to
|
|
411
426
|
# continue past the step limit, or it is a scratch session
|
|
@@ -447,7 +462,7 @@ module Samagotchi
|
|
|
447
462
|
# turn runs), :queued (input/ holds a prompt, which outlives the stop and
|
|
448
463
|
# runs on a resume, live worker or not), :offer (a live worker's continue
|
|
449
464
|
# offer, which the stop would drop), or nil.
|
|
450
|
-
# @raise [OwnedByTUI] a chi
|
|
465
|
+
# @raise [OwnedByTUI] a one-turn chi run owns it
|
|
451
466
|
def self.archive_refusal(sid, owner, state_dir)
|
|
452
467
|
raise OwnedByTUI, sid if owner&.tui?
|
|
453
468
|
|
|
@@ -555,6 +570,8 @@ module Samagotchi
|
|
|
555
570
|
def self.resume_session(session_id, state_dir: nil)
|
|
556
571
|
sd = state_dir || Session.default_state_dir
|
|
557
572
|
session = Session.load(session_id, state_dir: sd)
|
|
573
|
+
# Its terminal's worker came from spawn_session; nothing wakes another.
|
|
574
|
+
raise ScratchSession, session.id if session.scratch
|
|
558
575
|
return session if refuse_tui!(session.id, state_dir: sd)
|
|
559
576
|
|
|
560
577
|
# status is turn state, not liveness: a new worker runs no turn yet,
|
|
@@ -576,7 +593,7 @@ module Samagotchi
|
|
|
576
593
|
|
|
577
594
|
# A delegate child rang a parent that has no worker (ChildRing): start
|
|
578
595
|
# one, which runs a turn for the report. Not for a parent that is gone,
|
|
579
|
-
# stopped, archived, a scratch session,
|
|
596
|
+
# stopped, archived, a scratch session, a one-turn run's, or a
|
|
580
597
|
# continue's new link still taking its delegates (its own spawn comes):
|
|
581
598
|
# the ring waits on disk for whoever runs it next (#resume_session
|
|
582
599
|
# itself would clear the stop). A stop landing between the check and
|
|
@@ -686,7 +703,8 @@ module Samagotchi
|
|
|
686
703
|
reminders: "it has reminders, which its worker's older chi can't carry over a restart",
|
|
687
704
|
command_running: "a /btw (or another command) is still running",
|
|
688
705
|
relays_open: "a delegate's approval relayed here is open or just answered",
|
|
689
|
-
background_tasks: "background tasks it started are still running"
|
|
706
|
+
background_tasks: "background tasks it started are still running",
|
|
707
|
+
scratch: "a chi scratch session's worker ends with its terminal"
|
|
690
708
|
}.merge(WorkerIdleExit::LEAVING_WORDS).freeze
|
|
691
709
|
|
|
692
710
|
# What the CLI and the web say.
|
|
@@ -726,7 +744,7 @@ module Samagotchi
|
|
|
726
744
|
# be lost and starts its successor. If the worker cannot be asked, stop
|
|
727
745
|
# it without a stop marker and resume only when disk shows no work to lose.
|
|
728
746
|
# @return [Restarted]
|
|
729
|
-
# @raise [OwnedByTUI] a chi
|
|
747
|
+
# @raise [OwnedByTUI] a one-turn chi run owns the session
|
|
730
748
|
# @raise [RestartRefused]
|
|
731
749
|
# @raise [ArgumentError] no such session
|
|
732
750
|
def self.restart_session(session_id, state_dir: nil, wait: RESTART_WAIT, client_id: ClientId::CLI_RESTART)
|
|
@@ -917,34 +935,21 @@ module Samagotchi
|
|
|
917
935
|
EMPTY_SKELETON_DIRS = (EMPTY_DIRS + [SessionInbox::OUTPUT_DIR]).freeze
|
|
918
936
|
|
|
919
937
|
# session.keep_empty off: sessions left with nothing in them are deleted
|
|
920
|
-
# (the worker as it exits, the
|
|
938
|
+
# (the worker as it exits, the retention sweep).
|
|
921
939
|
def self.discard_empty?
|
|
922
|
-
Samagotchi::Config.get("session.keep_empty") != true
|
|
923
|
-
rescue StandardError
|
|
924
|
-
false
|
|
940
|
+
Degrade.call(:config, "keep_empty_unreadable", fallback: false) { Samagotchi::Config.get("session.keep_empty") != true }
|
|
925
941
|
end
|
|
926
942
|
|
|
927
943
|
# Whether a session being left (or found left) is deleted: session.keep_empty
|
|
928
944
|
# is off and nothing happened in it (#empty_session?). The worker as it
|
|
929
|
-
# leaves
|
|
945
|
+
# leaves and the retention sweep both ask this.
|
|
930
946
|
# @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
947
|
# @param used_memory_names [Array<String>] memory its owner's Engine used
|
|
934
948
|
# 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?
|
|
949
|
+
def self.discardable?(session_id, default_model:, state_dir: nil, used_memory_names: [])
|
|
950
|
+
return false unless discard_empty? && Array(used_memory_names).empty?
|
|
940
951
|
|
|
941
|
-
|
|
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
|
|
946
|
-
|
|
947
|
-
empty_session?(session_id, state_dir: sd, default_model: default_model)
|
|
952
|
+
empty_session?(session_id, state_dir: state_dir || Session.default_state_dir, default_model: default_model)
|
|
948
953
|
end
|
|
949
954
|
|
|
950
955
|
# A session nothing happened in: no conversation, no turn tried (a failed
|
|
@@ -971,12 +976,12 @@ module Samagotchi
|
|
|
971
976
|
return true if one.to_s == other.to_s
|
|
972
977
|
return false if one.nil? || other.nil?
|
|
973
978
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
979
|
+
Degrade.call(:config, "model_ref_unresolved", fallback: false) do
|
|
980
|
+
ConfigFile.model_ref(one).ref == ConfigFile.model_ref(other).ref
|
|
981
|
+
end
|
|
977
982
|
end
|
|
978
983
|
|
|
979
|
-
# Only the system prompt (
|
|
984
|
+
# Only the system prompt (an Engine seeds one, a saved session keeps
|
|
980
985
|
# it); a context note is a system message too, but with its kind.
|
|
981
986
|
# A turn note (the tail system message after a failed, cancelled or empty
|
|
982
987
|
# turn) is not a conversation either: a session whose only turn failed
|
|
@@ -1049,7 +1054,7 @@ module Samagotchi
|
|
|
1049
1054
|
# @param stop [Boolean] stop a live worker first (waits +wait+ seconds)
|
|
1050
1055
|
# @return [Hash] {id:, removed: [paths], stopped: whether a worker was stopped}
|
|
1051
1056
|
# @raise [ArgumentError] unknown id (Session::AmbiguousId for a prefix of several)
|
|
1052
|
-
# @raise [OwnedByTUI] a chi
|
|
1057
|
+
# @raise [OwnedByTUI] a one-turn chi run owns it (never stopped from here)
|
|
1053
1058
|
# @raise [DeleteRefused] a worker owns it and +stop+ is false, or it
|
|
1054
1059
|
# outlived the wait
|
|
1055
1060
|
# @param purge_runs [Boolean] also delete System 1's saved runs of it and
|
|
@@ -1130,7 +1135,7 @@ module Samagotchi
|
|
|
1130
1135
|
# ticks (nil: Worker::FALLBACK_TICK_SECONDS); queued turns wake it at once
|
|
1131
1136
|
# A stopped session's worker exits 0 (a signalled one too) and a
|
|
1132
1137
|
# crashed one 1, there.
|
|
1133
|
-
# @return [Symbol] :idle_exit, :exit_requested or :
|
|
1138
|
+
# @return [Symbol] :idle_exit, :exit_requested, :restart or :scratch_left
|
|
1134
1139
|
def self.run_session_loop(session_id, state_dir: nil, owner_wait: OwnerLock::DEFAULT_WAIT,
|
|
1135
1140
|
idle_exit_minutes: nil, poll_interval: nil)
|
|
1136
1141
|
# A worker inherits chi's locale (LC_ALL=C too): read files as UTF-8.
|
|
@@ -1175,6 +1180,13 @@ module Samagotchi
|
|
|
1175
1180
|
signals.restore!
|
|
1176
1181
|
end
|
|
1177
1182
|
Log.info(:worker, "stop", reason: result)
|
|
1183
|
+
# A scratch session goes with its worker however it leaves (its
|
|
1184
|
+
# terminal deletes it too, whichever comes first); nothing resumes it.
|
|
1185
|
+
if worker.scratch?
|
|
1186
|
+
delete_left_scratch(session_id, state_dir: sd)
|
|
1187
|
+
exit(result == :crashed ? 1 : 0) if %i[stopped signal crashed].include?(result)
|
|
1188
|
+
return result
|
|
1189
|
+
end
|
|
1178
1190
|
# A stopped session is left as it is: no resume for input that came
|
|
1179
1191
|
# in, no discard. One whose worker a signal stopped isn't discarded
|
|
1180
1192
|
# either; input that came in (queued behind the turn it stopped)
|
|
@@ -1231,6 +1243,16 @@ module Samagotchi
|
|
|
1231
1243
|
Process.exit!(0)
|
|
1232
1244
|
end
|
|
1233
1245
|
|
|
1246
|
+
# Delete a scratch session its worker left (its terminal gone:
|
|
1247
|
+
# Worker#leave_scratch, or any other way out), now the lock is free.
|
|
1248
|
+
# Its terminal may have deleted it first.
|
|
1249
|
+
private_class_method def self.delete_left_scratch(session_id, state_dir:)
|
|
1250
|
+
delete_session(session_id, state_dir: state_dir)
|
|
1251
|
+
Log.info(:worker, "scratch_deleted", sid: session_id)
|
|
1252
|
+
rescue DeleteRefused, ArgumentError, SystemCallError => e
|
|
1253
|
+
Log.info(:worker, "scratch_kept", sid: session_id, error: e.class.name, msg: e.message)
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1234
1256
|
# Delete a session its worker left empty. Checked again now the lock is
|
|
1235
1257
|
# free: a note or input may have come in since the worker looked, and a
|
|
1236
1258
|
# worker woken meanwhile owns it (delete_session refuses). A `chi send`
|
|
@@ -1258,9 +1280,7 @@ module Samagotchi
|
|
|
1258
1280
|
end
|
|
1259
1281
|
|
|
1260
1282
|
def self.config_idle_exit_minutes
|
|
1261
|
-
Samagotchi::Config.get("session.idle_exit_minutes")
|
|
1262
|
-
rescue StandardError
|
|
1263
|
-
nil
|
|
1283
|
+
Degrade.call(:config, "idle_exit_minutes_unreadable") { Samagotchi::Config.get("session.idle_exit_minutes") }
|
|
1264
1284
|
end
|
|
1265
1285
|
|
|
1266
1286
|
private_class_method def self.setup_session_directory(session_dir, session, state_dir:)
|
|
@@ -1477,7 +1497,7 @@ module Samagotchi
|
|
|
1477
1497
|
# a worker that doesn't queue (no file then: it would steer), :timeout
|
|
1478
1498
|
# when it took the request but never answered (no file then), or
|
|
1479
1499
|
# :failed when the input file couldn't be written
|
|
1480
|
-
# @raise [OwnedByTUI] a chi
|
|
1500
|
+
# @raise [OwnedByTUI] a one-turn chi run owns the session
|
|
1481
1501
|
# @raise [ArgumentError] no such session
|
|
1482
1502
|
def self.deliver_turn(session_id, prompt:, client_id: nil, images: [], delivery: nil, no_interrupt: false,
|
|
1483
1503
|
author: nil, pending_limit: nil, anchors: nil, state_dir: nil, manager: self, bridge: nil)
|
|
@@ -1532,7 +1552,7 @@ module Samagotchi
|
|
|
1532
1552
|
# A participant's file only for a worker started after it: a worker
|
|
1533
1553
|
# still holding the session with its Bridge gone (exiting) may be an
|
|
1534
1554
|
# older one, which would run it without the review boundary.
|
|
1535
|
-
# (A chi
|
|
1555
|
+
# (A one-turn chi run holding it is refused as for anyone, below.)
|
|
1536
1556
|
if Steer.participant_author?(author) && (holder = delivery_owner(manager, session_id, state_dir)) && !holder.tui?
|
|
1537
1557
|
return DeliverResult.new(status: :timeout, via: :file,
|
|
1538
1558
|
ack: { "error" => "worker_timeout",
|
|
@@ -1617,9 +1637,7 @@ module Samagotchi
|
|
|
1617
1637
|
end
|
|
1618
1638
|
|
|
1619
1639
|
private_class_method def self.delivery_owner(manager, session_id, state_dir)
|
|
1620
|
-
manager.session_owner(session_id, state_dir: state_dir)
|
|
1621
|
-
rescue StandardError
|
|
1622
|
-
nil
|
|
1640
|
+
Degrade.call(:worker, "owner_unreadable", sid: session_id) { manager.session_owner(session_id, state_dir: state_dir) }
|
|
1623
1641
|
end
|
|
1624
1642
|
|
|
1625
1643
|
def self.stopped_on_disk?(session_id, state_dir:)
|
|
@@ -25,8 +25,8 @@ module Samagotchi
|
|
|
25
25
|
# SessionMetrics is a persistent, error-isolated collector of per-session
|
|
26
26
|
# analytics. It implements the same #call(event) sink contract as
|
|
27
27
|
# SessionObserver subscribers, so Engine#run_turn feeds it (it forwards every
|
|
28
|
-
# event to its SessionObserver):
|
|
29
|
-
#
|
|
28
|
+
# event to its SessionObserver): a --non-interactive one-shot and the
|
|
29
|
+
# session workers.
|
|
30
30
|
#
|
|
31
31
|
# Each finished turn leaves a record (status, timings, model, prompt and
|
|
32
32
|
# completion tokens, tool calls, iterations, retries, cuts); session totals are
|