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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "config"
|
|
4
4
|
require_relative "log"
|
|
5
|
+
require_relative "degrade"
|
|
5
6
|
require_relative "log_path"
|
|
6
7
|
require_relative "memory_paths"
|
|
7
8
|
require_relative "paths"
|
|
@@ -74,12 +75,12 @@ module Samagotchi
|
|
|
74
75
|
@requested_memories = effective_preload_list(preload_memory_list(memories))
|
|
75
76
|
# Read once, as the memories: list is: a config edit is picked up by
|
|
76
77
|
# a new process. The files' text is read at each build.
|
|
77
|
-
@config_appended =
|
|
78
|
-
ConfigFile.appended_prompt_files
|
|
79
|
-
rescue StandardError
|
|
80
|
-
[]
|
|
81
|
-
end
|
|
78
|
+
@config_appended = Degrade.call(:prompt, "appended_list_unreadable", fallback: []) { ConfigFile.appended_prompt_files }
|
|
82
79
|
@cli_appended = Array(appended_files).map { |path| File.expand_path(path.to_s) }
|
|
80
|
+
# { [chat, thinking, layers] => Built }, swapped for a new one by #reset!
|
|
81
|
+
@cache = {}
|
|
82
|
+
# The Built #build returned last; nil before a build and after #reset!.
|
|
83
|
+
@last = nil
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
# @return [Array<String>] the names the session preloads, known before
|
|
@@ -88,37 +89,35 @@ module Samagotchi
|
|
|
88
89
|
@requested_memories.map { |raw| split_memory_scope(raw).last }.uniq
|
|
89
90
|
end
|
|
90
91
|
|
|
92
|
+
# A prompt #build made, with what it read while it did: the memory
|
|
93
|
+
# index sizes (#memory_index), the model notes, the files and the
|
|
94
|
+
# plugins' sections it holds, and its stable part's length (chat).
|
|
95
|
+
Built = Data.define(:prompt, :memory_index, :notes, :files, :sections, :stable_length)
|
|
96
|
+
|
|
91
97
|
# The full prompt: #base wrapped with the index and the rest. Built once
|
|
92
98
|
# per loop and level (until #reset!) so the prompt prefix, and the
|
|
93
99
|
# server's KV cache for it, stay stable.
|
|
100
|
+
#
|
|
101
|
+
# #reset! runs on other threads (a plugin's tools_changed! /
|
|
102
|
+
# prompt_changed!, a /stats re-resolving the profile) and, through the
|
|
103
|
+
# profile lookup, inside a build on this one. So a build reads @cache
|
|
104
|
+
# once and computes into locals: a reset meanwhile swaps in a new cache
|
|
105
|
+
# and this build's result goes into the dropped one, so it is returned
|
|
106
|
+
# (the same bytes as without the reset) but not kept, and the next
|
|
107
|
+
# build reads the sources again. No lock: the build reads memory files
|
|
108
|
+
# and calls the lookups, one of which takes ModelBinding's mutex, whose
|
|
109
|
+
# holder (a /stats thread) may itself call #reset!.
|
|
94
110
|
# @param chat [Boolean] for the chat loop
|
|
95
111
|
# @param thinking [Symbol, nil] the level (Thinking); nil: the effective model's
|
|
96
112
|
# @param layers [Array<Symbol>] the turn's LLM context layers: the
|
|
97
113
|
# native prompt declares the tools they add (Tools::Registry#entries)
|
|
98
114
|
def build(chat: false, thinking: nil, layers: [])
|
|
99
115
|
key = [chat, thinking, layers]
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@
|
|
103
|
-
@
|
|
104
|
-
|
|
105
|
-
unless @built.key?(key)
|
|
106
|
-
@section_notes = nil
|
|
107
|
-
@section_files = []
|
|
108
|
-
@section_plugins = []
|
|
109
|
-
@built[key] = system_prompt_with_index(
|
|
110
|
-
assist_system_prompt(chat: chat, layers: layers), chat: chat, thinking: thinking, layers: layers
|
|
111
|
-
)
|
|
112
|
-
@index_sizes[key] = @measured_index
|
|
113
|
-
@notes_loaded[key] = @section_notes || []
|
|
114
|
-
@files_loaded[key] = @section_files.freeze
|
|
115
|
-
@sections_loaded[key] = @section_plugins.freeze
|
|
116
|
-
end
|
|
117
|
-
@memory_index = @index_sizes[key]
|
|
118
|
-
@prompt_notes = @notes_loaded[key]
|
|
119
|
-
@prompt_files = @files_loaded[key]
|
|
120
|
-
@prompt_sections = @sections_loaded[key]
|
|
121
|
-
@built[key]
|
|
116
|
+
cache = @cache
|
|
117
|
+
built = cache[key] || (cache[key] = build_prompt(chat: chat, thinking: thinking, layers: layers))
|
|
118
|
+
@memory_index = built.memory_index
|
|
119
|
+
@last = built
|
|
120
|
+
built.prompt
|
|
122
121
|
end
|
|
123
122
|
|
|
124
123
|
# The memory indexes the prompt #build returned last holds, per scope
|
|
@@ -133,7 +132,7 @@ module Samagotchi
|
|
|
133
132
|
# (Array<ModelNotes::Note>, [] with none), nil before a build and after
|
|
134
133
|
# #reset!. Read when the prompt is built, as the section is.
|
|
135
134
|
# @return [Array<ModelNotes::Note>, nil]
|
|
136
|
-
|
|
135
|
+
def prompt_notes = @last&.notes
|
|
137
136
|
|
|
138
137
|
# The files whose text the prompt #build returned last holds, as
|
|
139
138
|
# absolute paths in prompt order: the AGENTS.md chain, then the
|
|
@@ -141,26 +140,21 @@ module Samagotchi
|
|
|
141
140
|
# build and after #reset!. Read when the prompt is built, as the
|
|
142
141
|
# sections are.
|
|
143
142
|
# @return [Array<String>]
|
|
144
|
-
def prompt_files = @
|
|
143
|
+
def prompt_files = @last&.files || []
|
|
145
144
|
|
|
146
145
|
# The plugins' prompt sections the prompt #build returned last holds
|
|
147
146
|
# (Plugin::PromptSections::Built, prompt order), [] before a build and
|
|
148
147
|
# after #reset!.
|
|
149
148
|
# @return [Array<Plugin::PromptSections::Built>]
|
|
150
|
-
def prompt_sections = @
|
|
149
|
+
def prompt_sections = @last&.sections || []
|
|
151
150
|
|
|
152
151
|
# Drops the built prompts: the next #build reads the profile, tools,
|
|
153
152
|
# indexes and memories again (a model or profile switch, changed tools).
|
|
154
153
|
def reset!
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
@
|
|
158
|
-
@
|
|
159
|
-
@files_loaded = nil
|
|
160
|
-
@prompt_files = nil
|
|
161
|
-
@sections_loaded = nil
|
|
162
|
-
@prompt_sections = nil
|
|
163
|
-
@stable_lengths = nil
|
|
154
|
+
# Single assignments: a build in flight keeps the cache it read
|
|
155
|
+
# (#build), so it neither fails nor is kept.
|
|
156
|
+
@cache = {}
|
|
157
|
+
@last = nil
|
|
164
158
|
end
|
|
165
159
|
|
|
166
160
|
# The length of +prompt+'s stable part when it is a chat prompt #build
|
|
@@ -168,7 +162,12 @@ module Samagotchi
|
|
|
168
162
|
# a remote prompt cache can keep for every session (PromptCache). nil
|
|
169
163
|
# for any other text, or a prompt without those lines.
|
|
170
164
|
# @return [Integer, nil]
|
|
171
|
-
def stable_length(prompt)
|
|
165
|
+
def stable_length(prompt)
|
|
166
|
+
last = @last
|
|
167
|
+
return last.stable_length if last&.prompt == prompt
|
|
168
|
+
|
|
169
|
+
@cache.each_value.find { |built| built.prompt == prompt }&.stable_length
|
|
170
|
+
end
|
|
172
171
|
|
|
173
172
|
# The base prompt (specs, plugins' declarations), with Gemma's tool
|
|
174
173
|
# declarations at its end as #build has them.
|
|
@@ -185,6 +184,20 @@ module Samagotchi
|
|
|
185
184
|
|
|
186
185
|
private
|
|
187
186
|
|
|
187
|
+
# One #build's work: the prompt and what it read, in a Built. The
|
|
188
|
+
# @section_* and @measured_* scratch is this build's (the turn thread's)
|
|
189
|
+
# alone: #reset! never touches it.
|
|
190
|
+
def build_prompt(chat:, thinking:, layers:)
|
|
191
|
+
@section_notes = nil
|
|
192
|
+
@section_files = []
|
|
193
|
+
@section_plugins = []
|
|
194
|
+
@measured_stable_length = nil
|
|
195
|
+
prompt = system_prompt_with_index(assist_system_prompt(chat: chat, layers: layers),
|
|
196
|
+
chat: chat, thinking: thinking, layers: layers)
|
|
197
|
+
Built.new(prompt: prompt, memory_index: @measured_index, notes: @section_notes || [],
|
|
198
|
+
files: @section_files.freeze, sections: @section_plugins.freeze, stable_length: @measured_stable_length)
|
|
199
|
+
end
|
|
200
|
+
|
|
188
201
|
def profile = @profile_lookup.call
|
|
189
202
|
|
|
190
203
|
def turn_thinking = @thinking_lookup.call
|
|
@@ -334,7 +347,7 @@ module Samagotchi
|
|
|
334
347
|
return with_tail_declarations(stable_text, chat: chat, layers: layers) if volatile.empty?
|
|
335
348
|
|
|
336
349
|
prompt = "#{stable_text}\n#{volatile.join("\n")}"
|
|
337
|
-
|
|
350
|
+
@measured_stable_length = stable_text.length + 1 if chat
|
|
338
351
|
with_tail_declarations(prompt, chat: chat, layers: layers)
|
|
339
352
|
end
|
|
340
353
|
|
|
@@ -386,11 +399,11 @@ module Samagotchi
|
|
|
386
399
|
# (Plugin::PromptSections). nil with none: the prompt is what it was
|
|
387
400
|
# before prompt sections.
|
|
388
401
|
def plugin_prompt_section
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
402
|
+
Degrade.call(:prompt, "plugin_section_failed") do
|
|
403
|
+
built = Array(@sections_lookup.call)
|
|
404
|
+
@section_plugins = built
|
|
405
|
+
Plugin::PromptSections.text(built)
|
|
406
|
+
end
|
|
394
407
|
end
|
|
395
408
|
|
|
396
409
|
def with_tail_declarations(prompt, chat:, layers:)
|
|
@@ -405,35 +418,35 @@ module Samagotchi
|
|
|
405
418
|
# Identity is not tracked as an "activated" memory for the sticky status line
|
|
406
419
|
# to avoid always showing `mem: identity`.
|
|
407
420
|
def system_identity_section
|
|
408
|
-
|
|
409
|
-
|
|
421
|
+
Degrade.call(:prompt, "identity_unreadable") do
|
|
422
|
+
DEFAULT_SYSTEM_MEMORIES.each do |name|
|
|
423
|
+
next if memory_muted?(name)
|
|
410
424
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
425
|
+
body = Tools::MemoryRead.call(name, scope: "system", **overlay_keys)
|
|
426
|
+
next if body.start_with?("Error:")
|
|
427
|
+
next if body.strip.empty?
|
|
414
428
|
|
|
415
|
-
|
|
429
|
+
return "System identity (auto-loaded, scope=system):\n#{body}"
|
|
430
|
+
end
|
|
431
|
+
nil
|
|
416
432
|
end
|
|
417
|
-
nil
|
|
418
|
-
rescue StandardError
|
|
419
|
-
nil
|
|
420
433
|
end
|
|
421
434
|
|
|
422
435
|
# The model notes for the session's model (ModelNotes), right after
|
|
423
436
|
# identity: a heading per scope, each note under its name, the models:
|
|
424
437
|
# line left out. nil without any, so the prompt stays as it was.
|
|
425
438
|
def model_notes_section
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
439
|
+
Degrade.call(:prompt, "model_notes_failed") do
|
|
440
|
+
@section_notes = []
|
|
441
|
+
notes = Array(@model_notes_lookup.call)
|
|
442
|
+
return nil if notes.empty?
|
|
443
|
+
|
|
444
|
+
ref = @model_lookup.call&.ref
|
|
445
|
+
notes.group_by(&:scope).map do |scope, list|
|
|
446
|
+
heading = ref.to_s.empty? ? "Model notes (scope=#{scope}):" : "Model notes (for #{ref}, scope=#{scope}):"
|
|
447
|
+
[heading, *list.map { |note| "## #{note.name}\n#{note.body}" }].join("\n\n")
|
|
448
|
+
end.join("\n\n").tap { @section_notes = notes }
|
|
449
|
+
end
|
|
437
450
|
end
|
|
438
451
|
|
|
439
452
|
# ── Memory helpers ─────────────────────────────────────────────────────────
|
|
@@ -441,23 +454,23 @@ module Samagotchi
|
|
|
441
454
|
# The session model's overlay keys, so the prompt's own memory bodies
|
|
442
455
|
# (identity, preloads) get `<name>.<key>.md` as memory_read gives it.
|
|
443
456
|
def overlay_keys
|
|
444
|
-
|
|
445
|
-
|
|
457
|
+
Degrade.call(:prompt, "overlay_keys_failed", fallback: {}) do
|
|
458
|
+
model = @model_lookup.call
|
|
459
|
+
return {} unless model&.key
|
|
446
460
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
{}
|
|
461
|
+
{ model_key: model.key, fallback_model_key: model.fallback_key }
|
|
462
|
+
end
|
|
450
463
|
end
|
|
451
464
|
|
|
452
465
|
# { system: { tokens:, lines: }, project: { … } } of the index texts
|
|
453
466
|
# the prompt holds; nil when they can't be measured.
|
|
454
467
|
def measure_indexes(indexes)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
[
|
|
468
|
+
Degrade.call(:prompt, "index_size_failed") do
|
|
469
|
+
chars_per_token = MemoryBundle::IndexSize.chars_per_token
|
|
470
|
+
%i[system project].to_h do |scope|
|
|
471
|
+
[scope, MemoryBundle::IndexSize.of_text(scope, indexes[scope], chars_per_token: chars_per_token).figures]
|
|
472
|
+
end
|
|
458
473
|
end
|
|
459
|
-
rescue StandardError
|
|
460
|
-
nil
|
|
461
474
|
end
|
|
462
475
|
|
|
463
476
|
# The scope's index text without the muted memories' lines, nor the
|
|
@@ -473,11 +486,7 @@ module Samagotchi
|
|
|
473
486
|
# comma-split and appended without duplicates (same ref shape as --memory:
|
|
474
487
|
# bare name or scope/name).
|
|
475
488
|
def preload_memory_list(cli_memories)
|
|
476
|
-
baseline =
|
|
477
|
-
ConfigFile.preloaded_memories
|
|
478
|
-
rescue StandardError
|
|
479
|
-
[]
|
|
480
|
-
end
|
|
489
|
+
baseline = Degrade.call(:prompt, "preload_list_unreadable", fallback: []) { ConfigFile.preloaded_memories }
|
|
481
490
|
|
|
482
491
|
merged = Array(baseline).dup
|
|
483
492
|
# For the warning when one can't be loaded: it names where it came from.
|
|
@@ -555,15 +564,15 @@ module Samagotchi
|
|
|
555
564
|
# The AGENTS.md chain from the top of the git work tree down to the cwd
|
|
556
565
|
# (PromptFiles.agents_sections), one section per file with text.
|
|
557
566
|
def project_specific_description
|
|
558
|
-
|
|
567
|
+
Degrade.call(:prompt, "agents_md_unreadable") do
|
|
568
|
+
return nil if skip_agent_description?
|
|
559
569
|
|
|
560
|
-
|
|
561
|
-
|
|
570
|
+
sections = PromptFiles.agents_sections(Dir.pwd)
|
|
571
|
+
return nil if sections.empty?
|
|
562
572
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
nil
|
|
573
|
+
@section_files&.concat(sections.map(&:path))
|
|
574
|
+
sections.map(&:text).join("\n\n")
|
|
575
|
+
end
|
|
567
576
|
end
|
|
568
577
|
|
|
569
578
|
# The appended files' text, right after AGENTS.md (the stable, cached
|
|
@@ -572,26 +581,26 @@ module Samagotchi
|
|
|
572
581
|
# then --append-system-prompt's. A file the AGENTS.md chain holds, a
|
|
573
582
|
# repeat and an empty or vanished file are left out.
|
|
574
583
|
def appended_section
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
584
|
+
Degrade.call(:prompt, "appended_files_failed") do
|
|
585
|
+
cwd = Dir.pwd
|
|
586
|
+
top = MemoryPaths.work_tree_root(cwd)
|
|
587
|
+
seen = Set.new((skip_agent_description? ? [] : PromptFiles.agents_paths(cwd)).map { |path| canonical_path(path) })
|
|
588
|
+
sections = []
|
|
589
|
+
appended_candidates(cwd).each do |path, source|
|
|
590
|
+
next unless seen.add?(canonical_path(path))
|
|
591
|
+
|
|
592
|
+
unless File.file?(path)
|
|
593
|
+
Log.debug(:config, "appended_prompt_file_missing", path: path, source: source)
|
|
594
|
+
next
|
|
595
|
+
end
|
|
596
|
+
text = PromptFiles.section(path, "Appended from #{PromptFiles.display_path(path, top: top)}:")
|
|
597
|
+
next unless text
|
|
588
598
|
|
|
589
|
-
|
|
590
|
-
|
|
599
|
+
@section_files&.push(path)
|
|
600
|
+
sections << text
|
|
601
|
+
end
|
|
602
|
+
sections.empty? ? nil : sections.join("\n\n")
|
|
591
603
|
end
|
|
592
|
-
sections.empty? ? nil : sections.join("\n\n")
|
|
593
|
-
rescue StandardError
|
|
594
|
-
nil
|
|
595
604
|
end
|
|
596
605
|
|
|
597
606
|
# [absolute path, "config" | "cli"], in prompt order.
|
|
@@ -617,27 +626,27 @@ module Samagotchi
|
|
|
617
626
|
# The home directory is spelled out once so the model copies the right
|
|
618
627
|
# sequence, with the advice to write it as ~ or $HOME instead.
|
|
619
628
|
def project_location
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
629
|
+
Degrade.call(:prompt, "location_failed") do
|
|
630
|
+
cwd = Dir.pwd
|
|
631
|
+
top = MemoryPaths.work_tree_root(cwd)
|
|
632
|
+
lines = ["Current working directory:", cwd]
|
|
633
|
+
if top && top != cwd
|
|
634
|
+
lines << "Top of this checkout (where the project's commands run from; stay inside it):"
|
|
635
|
+
lines << top
|
|
636
|
+
elsif top
|
|
637
|
+
lines << "This checkout (the project's files; its commands run here):"
|
|
638
|
+
lines << top
|
|
639
|
+
end
|
|
640
|
+
if top && MemoryPaths.project_root(cwd) != top
|
|
641
|
+
lines << "This checkout is a linked git worktree: read, edit, run and commit only in it, never in the " \
|
|
642
|
+
"repository's other checkouts."
|
|
643
|
+
end
|
|
644
|
+
home = Dir.home
|
|
645
|
+
lines << "Home directory: #{home} (write it as ~ or $HOME in commands and paths)" unless home.to_s.empty?
|
|
646
|
+
lines << "Project memories folder (your notes about the project, not its files; never work or run commands there):"
|
|
647
|
+
lines << Paths.tilde(Tools::MemoryRead.memories_dir("project"))
|
|
648
|
+
lines.join("\n")
|
|
633
649
|
end
|
|
634
|
-
home = Dir.home
|
|
635
|
-
lines << "Home directory: #{home} (write it as ~ or $HOME in commands and paths)" unless home.to_s.empty?
|
|
636
|
-
lines << "Project memories folder (your notes about the project, not its files; never work or run commands there):"
|
|
637
|
-
lines << Paths.tilde(Tools::MemoryRead.memories_dir("project"))
|
|
638
|
-
lines.join("\n")
|
|
639
|
-
rescue StandardError
|
|
640
|
-
nil
|
|
641
650
|
end
|
|
642
651
|
|
|
643
652
|
# Only while the session has participants (people its owner invited to
|
|
@@ -662,12 +671,12 @@ module Samagotchi
|
|
|
662
671
|
# once a participant invited to act is in a working session, the review
|
|
663
672
|
# line for review participants; nothing with none (nil, false).
|
|
664
673
|
def participants_section
|
|
665
|
-
|
|
666
|
-
|
|
674
|
+
Degrade.call(:prompt, "participants_line_failed") do
|
|
675
|
+
mode = @participants_lookup.call
|
|
676
|
+
return nil unless mode
|
|
667
677
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
nil
|
|
678
|
+
mode == ParticipantMode::PAIR ? PAIR_PARTICIPANTS_LINE : PARTICIPANTS_LINE
|
|
679
|
+
end
|
|
671
680
|
end
|
|
672
681
|
|
|
673
682
|
# The model this session runs on. A model guesses its name from
|
|
@@ -675,19 +684,19 @@ module Samagotchi
|
|
|
675
684
|
# says to answer from here. It depends only on the effective model, and
|
|
676
685
|
# the prompt is rebuilt only on a switch (#reset!): no per-turn churn.
|
|
677
686
|
def current_model
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
"
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
687
|
+
Degrade.call(:prompt, "model_line_failed") do
|
|
688
|
+
model = @model_lookup.call
|
|
689
|
+
return nil unless model && !model.ref.to_s.empty?
|
|
690
|
+
|
|
691
|
+
details = [model.host, model.key && "model key #{model.key}", model.served && "the server says it serves #{model.served}"]
|
|
692
|
+
details = details.compact.reject(&:empty?)
|
|
693
|
+
where = details.empty? ? "" : " (#{details.join("; ")})"
|
|
694
|
+
"Model: this session runs on #{model.ref}#{where}.\n" \
|
|
695
|
+
"Asked which model you are, answer with this line, not from training: a fine-tuned model often knows only " \
|
|
696
|
+
"its base model's name, but this session runs what is named here; it changes only with /model. " \
|
|
697
|
+
"Guidance for this model only goes in a model note: a memory named model_notes_<name> whose first line is " \
|
|
698
|
+
"`models: <glob on this model's id or key>`."
|
|
699
|
+
end
|
|
691
700
|
end
|
|
692
701
|
|
|
693
702
|
# Fixed for the session's lifetime, so it doesn't churn the prompt cache.
|
|
@@ -701,7 +710,7 @@ module Samagotchi
|
|
|
701
710
|
line = "Current session id: #{id} (resume later with `chi --resume #{id}`)"
|
|
702
711
|
# The log path too: asked what went wrong, a model that has to look
|
|
703
712
|
# it up guesses ~/.local/state first (the self-awareness probes).
|
|
704
|
-
log =
|
|
713
|
+
log = Degrade.call(:prompt, "log_path_failed") { LogPath.resolve }
|
|
705
714
|
line = "#{line}\nMy debug log: #{log} (one record per line; this session's carry sid=#{id[0, Log::SID_LENGTH]})" if log
|
|
706
715
|
parent = session.parent_id.to_s
|
|
707
716
|
return line if parent.empty?
|
|
@@ -9,7 +9,7 @@ require_relative "tools/task_runtime"
|
|
|
9
9
|
module Samagotchi
|
|
10
10
|
# The background tasks a worker's session started that the model hasn't
|
|
11
11
|
# seen finish (docs/internals/background-tasks.md, Finish wake). Held by
|
|
12
|
-
# the Worker (
|
|
12
|
+
# the Worker (a --non-interactive one-shot has none) and fed by the Engine's raw
|
|
13
13
|
# :tool_call_completed events (#observe), the input Bridge#own_task_ids
|
|
14
14
|
# reads too; the idle loop takes the finished ones (#take_finished) for a
|
|
15
15
|
# wake turn or a plain note. In memory only: a crash loses them, and they
|
|
@@ -13,14 +13,19 @@ require_relative "../launch_mode"
|
|
|
13
13
|
require_relative "live_region"
|
|
14
14
|
require_relative "plain_surface"
|
|
15
15
|
require_relative "attached_loop"
|
|
16
|
+
require_relative "log_tail"
|
|
17
|
+
require_relative "formatting"
|
|
16
18
|
|
|
17
19
|
module Samagotchi
|
|
18
20
|
class TerminalUI
|
|
19
|
-
# `chi --attach ID` and `chi --
|
|
21
|
+
# `chi`, `chi --attach ID` and `chi --resume ID`: find or start the
|
|
20
22
|
# session's worker, then run the TUI as a client of its Bridge
|
|
21
23
|
# (AttachedLoop). It takes no OwnerLock and builds no Engine: the worker
|
|
22
24
|
# owns the session, and any number of UIs can attach to it.
|
|
23
25
|
module AttachLauncher
|
|
26
|
+
# #paint, for the -v records.
|
|
27
|
+
extend Formatting
|
|
28
|
+
|
|
24
29
|
# Why attaching failed, worded for the terminal.
|
|
25
30
|
class Error < StandardError; end
|
|
26
31
|
|
|
@@ -28,6 +33,13 @@ module Samagotchi
|
|
|
28
33
|
# A connector that answered no session (its own failures raise Error
|
|
29
34
|
# with their words).
|
|
30
35
|
NOTHING_CONNECTED = "could not attach: no session was found or started"
|
|
36
|
+
# `chi scratch`'s first line, and its last once the session is gone.
|
|
37
|
+
SCRATCH_BANNER = "Scratch session: nothing is kept, it is deleted when you leave."
|
|
38
|
+
SCRATCH_DELETED = "Scratch session deleted."
|
|
39
|
+
# chi -v on a worker that was running before this launch (%s: the
|
|
40
|
+
# debug log): its level is its own.
|
|
41
|
+
VERBOSE_RUNNING = "(--verbose: this worker was already running and logs at the level it started with; " \
|
|
42
|
+
"its records show here only if it writes to %s: one chi web started may log elsewhere)"
|
|
31
43
|
|
|
32
44
|
module_function
|
|
33
45
|
|
|
@@ -62,6 +74,13 @@ module Samagotchi
|
|
|
62
74
|
# own guardrails.parent_answers; an existing session keeps its own
|
|
63
75
|
# (never sent as a /parent-answers command: that would be a
|
|
64
76
|
# loosening route from a command line)
|
|
77
|
+
# @param scratch [Boolean] `chi scratch`: a new scratch session (its
|
|
78
|
+
# worker's Engine saves nothing), stopped and deleted however the
|
|
79
|
+
# attach ends; its worker deletes it too once no client is left
|
|
80
|
+
# @param verbose [Boolean] -v: the session's debug records in the
|
|
81
|
+
# debug log (Log.path), shown above the live region as they come
|
|
82
|
+
# (LogTail); a worker this launch spawns logs at debug (bin/chi's
|
|
83
|
+
# log.level override reaches it through Config.cli_env)
|
|
65
84
|
# @param state_dir [String, nil] where the sessions are (the default
|
|
66
85
|
# state dir by default): the worker's, for the attached loop too
|
|
67
86
|
# @param connector [#connect, nil] finds or starts the session
|
|
@@ -73,19 +92,23 @@ module Samagotchi
|
|
|
73
92
|
# :failed when the --model switch didn't go through, or (input from a
|
|
74
93
|
# pipe) :turn_failed / :empty_answer / :removed / :unanswered
|
|
75
94
|
# (AttachedLoop#run)
|
|
76
|
-
def run(attach: nil,
|
|
95
|
+
def run(attach: nil, resume: nil, prompt: nil, model: nil, no_interrupt: false, default_input: true,
|
|
77
96
|
memories: [], muted_memories: [], llm_context: {}, thinking: nil, appended_prompt_files: [], toolkit: nil,
|
|
78
|
-
toolkit_flags: [], settings: nil, parent_answers: nil,
|
|
97
|
+
toolkit_flags: [], settings: nil, parent_answers: nil, scratch: false, verbose: false, state_dir: nil,
|
|
98
|
+
connector: nil)
|
|
79
99
|
state_dir ||= Session.default_state_dir
|
|
100
|
+
running_before = verbose && !connector && worker_running?(attach || resume, state_dir)
|
|
80
101
|
link = connector && (connector.connect || raise(Error, NOTHING_CONNECTED))
|
|
81
102
|
client = if connector
|
|
82
103
|
link.client
|
|
83
104
|
else
|
|
84
|
-
connect(attach: attach,
|
|
105
|
+
connect(attach: attach, resume: resume, model: model, state_dir: state_dir,
|
|
85
106
|
memories: memories, muted_memories: muted_memories, llm_context: llm_context,
|
|
86
107
|
thinking: thinking, appended_prompt_files: appended_prompt_files, toolkit: toolkit,
|
|
87
|
-
toolkit_flags: toolkit_flags, settings: settings, parent_answers: parent_answers
|
|
108
|
+
toolkit_flags: toolkit_flags, settings: settings, parent_answers: parent_answers,
|
|
109
|
+
scratch: scratch)
|
|
88
110
|
end
|
|
111
|
+
$stdout.puts(SCRATCH_BANNER) if scratch
|
|
89
112
|
commands = []
|
|
90
113
|
# --yes on /model and /thinking: typing the flag is the choice (no
|
|
91
114
|
# confirm step); its applied reply ends the launch's command.
|
|
@@ -93,11 +116,13 @@ module Samagotchi
|
|
|
93
116
|
commands << LLMContextOverride.command_line(llm_context) if (attach || resume) && !llm_context.empty?
|
|
94
117
|
commands << "#{ThinkingCommand::NAME} #{thinking} --yes" if (attach || resume) && thinking
|
|
95
118
|
first_command = commands.size > 1 ? commands : commands.first
|
|
119
|
+
$stderr.puts(format(VERBOSE_RUNNING, Log.path)) if running_before
|
|
96
120
|
surface = open_surface
|
|
121
|
+
tail = (start_log_tail(surface, client.session_id) if verbose && Log.path)
|
|
97
122
|
options = { screen: surface, client_id: client_id(connector), first_prompt: prompt,
|
|
98
123
|
first_command: first_command, no_interrupt: no_interrupt,
|
|
99
124
|
default_input: default_input && !prompt && !attach && !resume,
|
|
100
|
-
wait_at_eof: !$stdin.tty?,
|
|
125
|
+
wait_at_eof: !$stdin.tty?, scratch: scratch,
|
|
101
126
|
as_parent: Guardrails::ParentApprovals.parent_process? }
|
|
102
127
|
attached = if connector
|
|
103
128
|
AttachedLoop.new(link: link, **options)
|
|
@@ -107,6 +132,7 @@ module Samagotchi
|
|
|
107
132
|
ended = begin
|
|
108
133
|
attached.run
|
|
109
134
|
ensure
|
|
135
|
+
tail&.stop
|
|
110
136
|
close_surface(surface)
|
|
111
137
|
end
|
|
112
138
|
if ended == :unanswered && attached.unanswered
|
|
@@ -119,6 +145,34 @@ module Samagotchi
|
|
|
119
145
|
ended
|
|
120
146
|
ensure
|
|
121
147
|
link&.close
|
|
148
|
+
# Every way out (Ctrl-D, /exit, the worker gone, an error, Ctrl-C or
|
|
149
|
+
# a signal): only kill -9 leaves it to its worker (and the sweep).
|
|
150
|
+
end_scratch(client.session_id, state_dir: state_dir, quiet: !$!.nil?) if scratch && client
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Whether +session_id+'s worker runs now (nil: a new session).
|
|
154
|
+
def worker_running?(session_id, state_dir)
|
|
155
|
+
return false unless session_id && Session.valid_id?(session_id)
|
|
156
|
+
|
|
157
|
+
WorkerLink.resolve(Session.session_dir(session_id, state_dir: state_dir), unlink: false).live?
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# The session's debug records, dim, above the live region.
|
|
161
|
+
def start_log_tail(surface, session_id)
|
|
162
|
+
LogTail.new(path: Log.path, session_id: session_id, on_record: lambda { |record|
|
|
163
|
+
surface.commit(paint(LogLine.format(record).chomp, 2))
|
|
164
|
+
}).start
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Stop the scratch session's worker and delete the session; its worker
|
|
168
|
+
# may have deleted it first (it found no client left).
|
|
169
|
+
def end_scratch(session_id, state_dir:, quiet: false, out: $stdout, err: $stderr)
|
|
170
|
+
SessionManager.delete_session(session_id, state_dir: state_dir, stop: true, wait: LocalLink::DELETE_WAIT)
|
|
171
|
+
out.puts(SCRATCH_DELETED) unless quiet
|
|
172
|
+
rescue ArgumentError
|
|
173
|
+
out.puts(SCRATCH_DELETED) unless quiet
|
|
174
|
+
rescue SessionManager::DeleteRefused, SystemCallError => e
|
|
175
|
+
err.puts("Scratch session #{session_id} was not deleted (#{e.message}): chi sessions delete #{session_id}")
|
|
122
176
|
end
|
|
123
177
|
|
|
124
178
|
# This TUI's client id: tui:PID, or the connector's own (a remote one
|
|
@@ -185,10 +239,11 @@ module Samagotchi
|
|
|
185
239
|
|
|
186
240
|
# @return [BridgeClient] a client for the live Bridge of the session
|
|
187
241
|
# @raise [Error]
|
|
188
|
-
def connect(attach: nil,
|
|
242
|
+
def connect(attach: nil, resume: nil, model: nil, state_dir: nil, wait: BRIDGE_WAIT,
|
|
189
243
|
memories: [], muted_memories: [], llm_context: {}, thinking: nil, appended_prompt_files: [], toolkit: nil,
|
|
190
|
-
toolkit_flags: [], settings: nil, parent_answers: nil, err: $stderr)
|
|
244
|
+
toolkit_flags: [], settings: nil, parent_answers: nil, scratch: false, err: $stderr)
|
|
191
245
|
sd = state_dir || Session.default_state_dir
|
|
246
|
+
connected = false
|
|
192
247
|
if attach || resume
|
|
193
248
|
warn_memory_flags_ignored(attach || resume, memories, muted_memories, err, appended_prompt_files: appended_prompt_files,
|
|
194
249
|
toolkit_flags: toolkit_flags)
|
|
@@ -211,14 +266,21 @@ module Samagotchi
|
|
|
211
266
|
thinking: Thinking.session_level(thinking),
|
|
212
267
|
appended_prompt_files: appended_prompt_files,
|
|
213
268
|
toolkit: toolkit, settings: settings,
|
|
214
|
-
parent_answers: parent_answers)
|
|
269
|
+
parent_answers: parent_answers),
|
|
270
|
+
scratch: scratch)
|
|
215
271
|
end
|
|
216
272
|
link = WorkerLink.await(session.id, session_dir: Session.session_dir(session.id, state_dir: sd), timeout: wait)
|
|
217
|
-
|
|
273
|
+
raise(Error, format(LaunchMode::BRIDGE_LATE, session.id)) unless link.live?
|
|
274
|
+
|
|
275
|
+
connected = true
|
|
276
|
+
link.client(session.id)
|
|
218
277
|
rescue SessionManager::OwnedByTUI => e
|
|
219
278
|
raise Error, e.message
|
|
220
279
|
rescue ArgumentError => e
|
|
221
280
|
raise Error, e.message
|
|
281
|
+
ensure
|
|
282
|
+
# A scratch session whose Bridge never came (or Ctrl-C meanwhile).
|
|
283
|
+
end_scratch(session.id, state_dir: sd, quiet: true, err: err) if scratch && session && !connected
|
|
222
284
|
end
|
|
223
285
|
|
|
224
286
|
# The session's previous worker may still be leaving: its Bridge
|