samagotchi 0.153.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 +28 -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 +80 -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 +3 -3
- 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 +1 -1
- data/docs/internals/remote-sessions.md +7 -7
- 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 +16 -6
- 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/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 +62 -2646
- 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 +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/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/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_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 +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/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 +1 -1
- data/lib/samagotchi/web/public/format.js +3 -3
- 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 +100 -9
- data/lib/samagotchi/worker_link.rb +3 -3
- data/lib/samagotchi/working_set_command.rb +3 -2
- metadata +29 -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,10 +12,11 @@ module Samagotchi
|
|
|
12
12
|
# Input composition and Ctrl-C handling for the attached loop.
|
|
13
13
|
# Reads @client, @clock, @command_registry, @continue_offer, @exit_ask,
|
|
14
14
|
# @gaps, @link, @prefilled, @question, @queue, @queued_ids, @reader,
|
|
15
|
-
# @running, @screen; writes @prefilled, @last_idle_interrupt_at.
|
|
15
|
+
# @running, @scratch, @screen; writes @prefilled, @last_idle_interrupt_at.
|
|
16
16
|
# Calls the loop helpers #answer_continue, #answer_exit_ask, #answer_question,
|
|
17
17
|
# #archive_after_turn, #command_registry, #detach, #echo_answer, #exit_and_archive,
|
|
18
|
-
# #
|
|
18
|
+
# #end_scratch, #exit_and_delete, #exit_worker, #refuse_scratch_archive, #gap_divider,
|
|
19
|
+
# #participant_commands, #persist_recent_history,
|
|
19
20
|
# #queue_input_prefill, #read_prompt_line, #send_command, #send_prompt,
|
|
20
21
|
# #show_recap, #show_stats, #submit_delivery, #unqueue_newest, #via, and
|
|
21
22
|
# #worker_down.
|
|
@@ -46,6 +47,7 @@ module Samagotchi
|
|
|
46
47
|
return answer_exit_ask(line)
|
|
47
48
|
end
|
|
48
49
|
|
|
50
|
+
return end_scratch if line.nil? && @scratch
|
|
49
51
|
return detach("Detached; the session keeps running. Re-attach with: #{via.attach(@client.session_id)}") if line.nil?
|
|
50
52
|
|
|
51
53
|
text = line.strip
|
|
@@ -104,11 +106,12 @@ module Samagotchi
|
|
|
104
106
|
route = ComposerRoute.call(text, running: @running, registry: command_registry)
|
|
105
107
|
return submit_delivery(route) if route.choice
|
|
106
108
|
|
|
107
|
-
# The terminal's own commands (SessionCommands registers them
|
|
108
|
-
# REPL reads the same words). Before the continue offer: the
|
|
109
|
+
# The terminal's own commands (SessionCommands registers them). Before the continue offer: the
|
|
109
110
|
# leaving ones are no answer to it.
|
|
110
111
|
local = route.local
|
|
111
112
|
return submit(nil) if local == :detach
|
|
113
|
+
return end_scratch if local == :exit && @scratch
|
|
114
|
+
return refuse_scratch_archive if local == :archive && @scratch
|
|
112
115
|
return SessionCommands.delete_on_exit?(text) ? exit_and_delete : exit_worker if local == :exit
|
|
113
116
|
return archive_after_turn if local == :archive && (@running || !@queued_ids.empty?)
|
|
114
117
|
return exit_and_archive if local == :archive
|
|
@@ -130,7 +133,7 @@ module Samagotchi
|
|
|
130
133
|
elsif %i[share participants].include?(local)
|
|
131
134
|
@screen.commit(participant_commands.run(text))
|
|
132
135
|
elsif route.kind == :command
|
|
133
|
-
# The history keeps !cmds
|
|
136
|
+
# The history keeps !cmds (prompts: #send_prompt).
|
|
134
137
|
persist_recent_history(text) if PromptHistory.shell_line?(text)
|
|
135
138
|
send_command(text)
|
|
136
139
|
elsif route.kind == :typo
|
|
@@ -142,7 +145,7 @@ module Samagotchi
|
|
|
142
145
|
nil
|
|
143
146
|
end
|
|
144
147
|
|
|
145
|
-
# One read on the reader thread:
|
|
148
|
+
# One read on the reader thread: a multiline read with Tab
|
|
146
149
|
# completion at the main prompt, a plain line for a question's choice
|
|
147
150
|
# or the continue offer's answer.
|
|
148
151
|
def read_input_line(prompt, prefill)
|
|
@@ -169,7 +172,7 @@ module Samagotchi
|
|
|
169
172
|
|
|
170
173
|
# The session's commands as its worker's snapshot names them (its
|
|
171
174
|
# plugins' too); the built-ins until one arrives, or from a worker too
|
|
172
|
-
# old to name them. An unknown /foo is a prompt
|
|
175
|
+
# old to name them. An unknown /foo is a prompt.
|
|
173
176
|
def command_registry = @command_registry || SessionCommands.builtin_registry
|
|
174
177
|
|
|
175
178
|
# Ctrl-C cancels the running turn (whoever started it). At an idle
|
|
@@ -197,13 +200,13 @@ module Samagotchi
|
|
|
197
200
|
return submit(nil) if @last_idle_interrupt_at && now - @last_idle_interrupt_at <= DETACH_WINDOW
|
|
198
201
|
|
|
199
202
|
@last_idle_interrupt_at = now
|
|
200
|
-
@screen.commit(DETACH_HINT)
|
|
203
|
+
@screen.commit(@scratch ? SCRATCH_DETACH_HINT : DETACH_HINT)
|
|
201
204
|
nil
|
|
202
205
|
end
|
|
203
206
|
|
|
204
207
|
# On the reader thread, from Reline's Ctrl-C: what the line held, and
|
|
205
208
|
# when. During a turn the press goes to the loop (it cancels the turn)
|
|
206
|
-
# and the read goes on with the typed text still in it
|
|
209
|
+
# and the read goes on with the typed text still in it;
|
|
207
210
|
# otherwise it is noted for the Interrupt the LineReader queues next.
|
|
208
211
|
# @return [Boolean] true: handled, the read goes on; false: Reline ends
|
|
209
212
|
# the read with Interrupt
|
|
@@ -19,6 +19,22 @@ module Samagotchi
|
|
|
19
19
|
:detach
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# A scratch session's way out (Ctrl-D, /detach, /exit, a second
|
|
23
|
+
# Ctrl-C): the loop ends with no line; its AttachLauncher stops the
|
|
24
|
+
# worker and deletes the session, and says so.
|
|
25
|
+
# @return [Symbol] :detach
|
|
26
|
+
def end_scratch
|
|
27
|
+
@view.finish_thinking_spinner
|
|
28
|
+
:detach
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# /archive in a scratch session: nothing to archive (ArchiveRefused's words).
|
|
32
|
+
# @return [nil]
|
|
33
|
+
def refuse_scratch_archive
|
|
34
|
+
@screen.commit(SessionManager::ArchiveRefused.new(@client.session_id, :scratch).message)
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
22
38
|
# Detach, and ask the worker to exit now. The worker decides: it stays
|
|
23
39
|
# up while anything still needs it (a turn, queued input, a continue
|
|
24
40
|
# offer, another UI, reminders), and the line says which. When the only
|
|
@@ -27,8 +27,7 @@ module Samagotchi
|
|
|
27
27
|
nil
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
# A bare answer at the continue prompt
|
|
31
|
-
# one is yes).
|
|
30
|
+
# A bare answer at the continue prompt (an empty one is yes).
|
|
32
31
|
def continue_line(text)
|
|
33
32
|
text.empty? ? SessionCommands::CONTINUE_COMMAND : "#{SessionCommands::CONTINUE_COMMAND} #{text}"
|
|
34
33
|
end
|
|
@@ -149,7 +148,7 @@ module Samagotchi
|
|
|
149
148
|
nil
|
|
150
149
|
end
|
|
151
150
|
|
|
152
|
-
# An empty answer dismisses the question
|
|
151
|
+
# An empty answer dismisses the question: the tool
|
|
153
152
|
# returns unanswered (an approval: denied) and the turn goes on. The
|
|
154
153
|
# :question_cancelled every UI gets finds it already closed here.
|
|
155
154
|
def dismiss_question
|
|
@@ -52,7 +52,7 @@ module Samagotchi
|
|
|
52
52
|
# #handle_event turns the worker's event stream into terminal output:
|
|
53
53
|
# the snapshot a join starts with (the last exchange, queued prompts,
|
|
54
54
|
# the turn so far), prompts other clients sent, and each turn through the
|
|
55
|
-
#
|
|
55
|
+
# EventRenderer, drawn by an AttachedView.
|
|
56
56
|
class AttachedLoop
|
|
57
57
|
include Formatting
|
|
58
58
|
include InputSupport
|
|
@@ -63,9 +63,14 @@ module Samagotchi
|
|
|
63
63
|
include Sending
|
|
64
64
|
|
|
65
65
|
PROMPT = "> "
|
|
66
|
+
# The prompt while a question or a continue offer waits for its answer
|
|
67
|
+
# (its choices are in the notes slot).
|
|
68
|
+
QUESTION_PROMPT = "? "
|
|
66
69
|
# A second Ctrl-C at an empty idle prompt within this many seconds detaches.
|
|
67
70
|
DETACH_WINDOW = 2.0
|
|
68
71
|
DETACH_HINT = "Ctrl-D to detach, /exit stops the worker"
|
|
72
|
+
# A `chi scratch` session's: every way out ends it.
|
|
73
|
+
SCRATCH_DETACH_HINT = "Ctrl-D or /exit ends the scratch session (nothing is kept)"
|
|
69
74
|
attr_reader :client_id
|
|
70
75
|
|
|
71
76
|
# @return [String, nil] the model the worker's turns run on, as the
|
|
@@ -92,7 +97,7 @@ module Samagotchi
|
|
|
92
97
|
# through, the launch stops
|
|
93
98
|
# @param no_interrupt [Boolean] post every turn with no_interrupt
|
|
94
99
|
# @param default_input [Boolean] type SAMAGOTCHI_DEFAULT_INPUT into
|
|
95
|
-
# the first read (a new session with no -p
|
|
100
|
+
# the first read (a new session with no -p)
|
|
96
101
|
# @param clock [#call] monotonic seconds (the Ctrl-C detach window)
|
|
97
102
|
# @param now [#call] the wall clock, a Time (the gap dividers)
|
|
98
103
|
# @param delete_session [#call] session id -> deletes it (/exit --delete)
|
|
@@ -107,8 +112,11 @@ module Samagotchi
|
|
|
107
112
|
# session folder (@path images, saved records) is under it
|
|
108
113
|
# @param link [AttachLink, nil] where the session is, instead of
|
|
109
114
|
# +client+ and the keywords that make a LocalLink
|
|
115
|
+
# @param scratch [Boolean] a `chi scratch` session: Ctrl-D, /detach,
|
|
116
|
+
# /exit and a second Ctrl-C all end it (its AttachLauncher deletes
|
|
117
|
+
# it), /archive is refused, and typed lines stay out of the history
|
|
110
118
|
def initialize(screen:, client_id:, client: nil, first_prompt: nil, first_command: nil, no_interrupt: false,
|
|
111
|
-
default_input: false, wait_at_eof: false, as_parent: false,
|
|
119
|
+
default_input: false, wait_at_eof: false, as_parent: false, scratch: false,
|
|
112
120
|
clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }, now: -> { MessageTime.now },
|
|
113
121
|
delete_session: ->(id) { SessionManager.delete_session(id, stop: true, wait: LocalLink::DELETE_WAIT) },
|
|
114
122
|
archive_session: ->(id) { SessionManager.archive_session(id, wait: LocalLink::DELETE_WAIT) },
|
|
@@ -119,6 +127,7 @@ module Samagotchi
|
|
|
119
127
|
archive_session: archive_session, installed_version: installed_version,
|
|
120
128
|
children_counts: children_counts)
|
|
121
129
|
@client = @link.client
|
|
130
|
+
@scratch = scratch
|
|
122
131
|
@screen = screen
|
|
123
132
|
@client_id = client_id
|
|
124
133
|
# A parent agent drives this attach (AttachLauncher:
|
|
@@ -285,7 +294,7 @@ module Samagotchi
|
|
|
285
294
|
@gaps.touch
|
|
286
295
|
refresh_children
|
|
287
296
|
own_turn_ended(event)
|
|
288
|
-
# The renderer says how it ended (
|
|
297
|
+
# The renderer says how it ended (a one-shot's words too).
|
|
289
298
|
when :turn_canceled, :turn_failed
|
|
290
299
|
@status.take_event(event)
|
|
291
300
|
@renderer.call(event)
|
|
@@ -413,7 +422,7 @@ module Samagotchi
|
|
|
413
422
|
|
|
414
423
|
# Here a stopped session has always read as unreachable.
|
|
415
424
|
reason = "unreachable" if reason == "stopped"
|
|
416
|
-
"Lost the session's worker (#{reason}). Resume it with: #{via.command("--
|
|
425
|
+
"Lost the session's worker (#{reason}). Resume it with: #{via.command("--resume #{id}")}"
|
|
417
426
|
end
|
|
418
427
|
|
|
419
428
|
# Each reason in its own words (the remote's name, its chi web's
|
|
@@ -478,7 +487,7 @@ module Samagotchi
|
|
|
478
487
|
end
|
|
479
488
|
|
|
480
489
|
# The saved recap, and a new one asked for at once (it arrives as
|
|
481
|
-
# :recap_ready),
|
|
490
|
+
# :recap_ready), in Formatting's /recap words.
|
|
482
491
|
def show_recap
|
|
483
492
|
reply = @client.request_recap
|
|
484
493
|
answer = reply.status == 200 ? reply.json : nil
|
|
@@ -504,11 +513,11 @@ module Samagotchi
|
|
|
504
513
|
end
|
|
505
514
|
|
|
506
515
|
# The worker rolled a failed turn back and handed its prompt back: ours
|
|
507
|
-
# goes back into the input
|
|
516
|
+
# goes back into the input for a retry.
|
|
508
517
|
def restore_prompt(event)
|
|
509
518
|
origin = origin_of(event[:origin])
|
|
510
519
|
return unless own?(origin.client_id) && @own_turns.sent?(origin.enqueued_id)
|
|
511
|
-
# Lines from a pipe: nobody to retry it
|
|
520
|
+
# Lines from a pipe: nobody to retry it.
|
|
512
521
|
return if @wait_at_eof
|
|
513
522
|
|
|
514
523
|
if @reader&.prefill(event[:prompt].to_s)
|
|
@@ -10,7 +10,7 @@ require_relative "thinking_line"
|
|
|
10
10
|
|
|
11
11
|
module Samagotchi
|
|
12
12
|
class TerminalUI
|
|
13
|
-
# EventRenderer's view in
|
|
13
|
+
# EventRenderer's view in the attached TUI, drawn
|
|
14
14
|
# on a Screen: the thinking feedback is the activity slot, one row right
|
|
15
15
|
# above the prompt (spinner frame, then the running tool or the newest
|
|
16
16
|
# sentence of the model's thinking or text, ThinkingLine), and every
|
|
@@ -60,8 +60,7 @@ module Samagotchi
|
|
|
60
60
|
@screen.commit(text)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
# Draw on +surface+ from now on (the REPL
|
|
64
|
-
# after the view is built, and back when it ends): what the slot shows
|
|
63
|
+
# Draw on +surface+ from now on (no caller since the REPL went): what the slot shows
|
|
65
64
|
# moves there.
|
|
66
65
|
def surface=(surface)
|
|
67
66
|
@lock.synchronize do
|
|
@@ -8,9 +8,8 @@ module Samagotchi
|
|
|
8
8
|
# Renders one UI's view of Engine turn events: the thinking spinner while
|
|
9
9
|
# the model generates, a `tool>` line per completed tool call, and the
|
|
10
10
|
# answer (with any tool activity not already shown) from :turn_completed's
|
|
11
|
-
# turn_summary, and how a turn ended. It
|
|
12
|
-
#
|
|
13
|
-
# Bridge.
|
|
11
|
+
# turn_summary, and how a turn ended. It renders what attached mode gets
|
|
12
|
+
# over the Bridge.
|
|
14
13
|
#
|
|
15
14
|
# Drawing is delegated to a +view+ (an AttachedView in both TUIs). The
|
|
16
15
|
# renderer keeps
|
|
@@ -158,8 +157,7 @@ module Samagotchi
|
|
|
158
157
|
end
|
|
159
158
|
|
|
160
159
|
# Start a turn's bookkeeping and reset the view's per-turn feedback.
|
|
161
|
-
# Also called
|
|
162
|
-
# Engine#run_turn (so no :turn_started).
|
|
160
|
+
# Also called from #call on a :turn_started.
|
|
163
161
|
def begin_turn
|
|
164
162
|
@streamed_tool_activity = Hash.new(0)
|
|
165
163
|
@tool_started_at = {}
|
|
@@ -17,14 +17,14 @@ require_relative "model_segments_text"
|
|
|
17
17
|
|
|
18
18
|
module Samagotchi
|
|
19
19
|
class TerminalUI
|
|
20
|
-
# Line formatting shared by the
|
|
20
|
+
# Line formatting shared by the attached view and a one-shot: colour, the
|
|
21
21
|
# `tool>` line, elapsed durations. Pure apart from reading whether
|
|
22
22
|
# $stdout is a colour terminal.
|
|
23
23
|
module Formatting
|
|
24
24
|
include ModelSegmentsText
|
|
25
25
|
|
|
26
|
-
# The /stats text for a metrics snapshot, for the
|
|
27
|
-
#
|
|
26
|
+
# The /stats text for a metrics snapshot, for the attached view (an
|
|
27
|
+
# instance includes the module) and for `chi sessions stats`
|
|
28
28
|
# (no UI: the same text from the CLI).
|
|
29
29
|
# @return [String] "(no metrics yet)" without a snapshot
|
|
30
30
|
def self.format_session_metrics(snapshot)
|
|
@@ -420,7 +420,7 @@ module Samagotchi
|
|
|
420
420
|
text.strip.empty? ? [] : lines
|
|
421
421
|
end
|
|
422
422
|
|
|
423
|
-
# ── Turn ends: one set of words for
|
|
423
|
+
# ── Turn ends: one set of words for a one-shot, attached mode and the
|
|
424
424
|
# web (timing.js cancelLineText, format.js failedTurnText) ──────────
|
|
425
425
|
|
|
426
426
|
# A cancel's reason as the UIs name it (anything else as it is);
|
|
@@ -552,7 +552,7 @@ module Samagotchi
|
|
|
552
552
|
# Off by the session's own recap.enabled (/recap off, --set recap.enabled=false).
|
|
553
553
|
RECAP_SESSION_OFF_TEXT = "recap is off for this session (/recap on turns it on, /recap default goes back to config.yml)"
|
|
554
554
|
|
|
555
|
-
# What /recap says (
|
|
555
|
+
# What /recap says (attached mode's words): the saved
|
|
556
556
|
# recap, then what came of asking for a new one.
|
|
557
557
|
# @param saved [Hash, nil] {text:, turns_since:}
|
|
558
558
|
# @param request [Symbol, String, nil] IdleRecap#request_now's answer
|
|
@@ -573,7 +573,7 @@ module Samagotchi
|
|
|
573
573
|
lines.compact.join("\n")
|
|
574
574
|
end
|
|
575
575
|
|
|
576
|
-
# ── The status line (
|
|
576
|
+
# ── The status line (attached mode's idle one) ───────────────────────
|
|
577
577
|
|
|
578
578
|
# status.line: off hides it.
|
|
579
579
|
STATUS_LINE_OFF = "off"
|
|
@@ -662,7 +662,7 @@ module Samagotchi
|
|
|
662
662
|
end
|
|
663
663
|
|
|
664
664
|
# Render the analytics snapshot as a compact, user-facing report. Raw event
|
|
665
|
-
# logs (debug-only) are intentionally excluded; this surface is for
|
|
665
|
+
# logs (debug-only) are intentionally excluded; this surface is for /stats
|
|
666
666
|
# and `chi sessions stats` (the module function above).
|
|
667
667
|
def format_session_metrics(snapshot)
|
|
668
668
|
return "(no metrics yet)" unless snapshot.is_a?(Hash)
|
|
@@ -14,7 +14,7 @@ require_relative "reline_history_hook"
|
|
|
14
14
|
|
|
15
15
|
module Samagotchi
|
|
16
16
|
class TerminalUI
|
|
17
|
-
#
|
|
17
|
+
# The attached TUI's input: the persistent prompt
|
|
18
18
|
# history, Tab completion (/commands, @path in the cwd, #memory, which
|
|
19
19
|
# inserts #name and the model reads it as typed), multiline reads at the
|
|
20
20
|
# main prompt, and a one-shot prefill of the next read (the default
|
|
@@ -29,12 +29,6 @@ module Samagotchi
|
|
|
29
29
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
|
-
# The /commands Tab offers: the session's registry, the REPL's own
|
|
33
|
-
# among them.
|
|
34
|
-
def slash_commands = command_registry.completions(:repl)
|
|
35
|
-
|
|
36
|
-
def command_registry = @commands&.registry || SessionCommands.builtin_registry
|
|
37
|
-
|
|
38
32
|
# Whether a new session's first read gets the default input
|
|
39
33
|
# (SAMAGOTCHI_DEFAULT_INPUT); --no-default-input says no.
|
|
40
34
|
def default_input_wanted? = !@no_default_input
|
|
@@ -6,8 +6,8 @@ module Samagotchi
|
|
|
6
6
|
class TerminalUI
|
|
7
7
|
# Reads input lines on its own thread, so output keeps rendering while
|
|
8
8
|
# the user types, and hands each line (nil = Ctrl-D) or Ctrl-C to a sink
|
|
9
|
-
# (anything with #<<, such as a Thread::Queue); the attached loop
|
|
10
|
-
#
|
|
9
|
+
# (anything with #<<, such as a Thread::Queue); the attached loop reads
|
|
10
|
+
# this way. #reprompt makes it drop the open read and start
|
|
11
11
|
# again with the current prompt (a question opened or closed); #prefill
|
|
12
12
|
# starts it again with text already typed in (a failed prompt).
|
|
13
13
|
class LineReader
|
|
@@ -7,8 +7,8 @@ require_relative "reline_seam"
|
|
|
7
7
|
module Samagotchi
|
|
8
8
|
class TerminalUI
|
|
9
9
|
# Opens and closes a live region: a Screen with Reline drawing into it
|
|
10
|
-
# through RelineSeam.
|
|
11
|
-
#
|
|
10
|
+
# through RelineSeam. Attached mode uses one when the terminal can show
|
|
11
|
+
# it, and falls back to a PlainSurface otherwise.
|
|
12
12
|
module LiveRegion
|
|
13
13
|
module_function
|
|
14
14
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../log"
|
|
4
|
+
require_relative "../log_line"
|
|
5
|
+
require_relative "../degrade"
|
|
6
|
+
|
|
7
|
+
module Samagotchi
|
|
8
|
+
class TerminalUI
|
|
9
|
+
# `chi -v` attached: the debug log's new records of one session (its
|
|
10
|
+
# sid), followed from where the file ends as it starts, each handed to
|
|
11
|
+
# +on_record+ (AttachLauncher commits it above the live region). The
|
|
12
|
+
# worker writes to the file this chi spawned it with
|
|
13
|
+
# (SAMAGOTCHI_LOG_FILE); a rotation (DebugLog renames the file to .1) is
|
|
14
|
+
# seen as another inode or a shorter file, and the new one is read from
|
|
15
|
+
# its start.
|
|
16
|
+
class LogTail
|
|
17
|
+
POLL_INTERVAL = 0.2
|
|
18
|
+
|
|
19
|
+
# @param path [String] the debug log (Log.path)
|
|
20
|
+
# @param session_id [String] whose records are shown (its first
|
|
21
|
+
# Log::SID_LENGTH chars are the records' sid)
|
|
22
|
+
# @param on_record [#call] LogLine::Record ->, on the tail's thread
|
|
23
|
+
def initialize(path:, session_id:, on_record:, poll_interval: POLL_INTERVAL)
|
|
24
|
+
@path = path
|
|
25
|
+
@sid = session_id.to_s[0, Log::SID_LENGTH]
|
|
26
|
+
@on_record = on_record
|
|
27
|
+
@poll_interval = poll_interval
|
|
28
|
+
@io = nil
|
|
29
|
+
@ino = nil
|
|
30
|
+
@buffer = +""
|
|
31
|
+
@record = nil
|
|
32
|
+
@stopping = false
|
|
33
|
+
@wake = Thread::Queue.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Start following from the file's current end.
|
|
37
|
+
# @return [self]
|
|
38
|
+
def start
|
|
39
|
+
open_at(:end)
|
|
40
|
+
@thread = Thread.new { follow }
|
|
41
|
+
@thread.report_on_exception = false
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# How long #stop waits for the record being handed on.
|
|
46
|
+
STOP_WAIT = 1
|
|
47
|
+
|
|
48
|
+
# The records written since the last look are shown first. The tail
|
|
49
|
+
# ends after the look it is in (a record half committed is finished);
|
|
50
|
+
# one stuck past STOP_WAIT is killed.
|
|
51
|
+
def stop
|
|
52
|
+
if @thread
|
|
53
|
+
@stopping = true
|
|
54
|
+
@wake << :stop
|
|
55
|
+
@thread.kill unless @thread.join(STOP_WAIT)
|
|
56
|
+
Degrade.call(:attached, "log_tail_failed") { poll }
|
|
57
|
+
end
|
|
58
|
+
@io&.close
|
|
59
|
+
@io = nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# One look: the lines written since the last (tests call it directly).
|
|
63
|
+
def poll
|
|
64
|
+
reopen_if_rotated
|
|
65
|
+
return unless @io
|
|
66
|
+
|
|
67
|
+
drain
|
|
68
|
+
take_lines
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def follow
|
|
74
|
+
until @stopping
|
|
75
|
+
Degrade.call(:attached, "log_tail_failed") { poll }
|
|
76
|
+
@wake.pop(timeout: @poll_interval)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# What the open file holds past the last read, into the buffer.
|
|
81
|
+
def drain
|
|
82
|
+
chunk = @io.read_nonblock(1 << 16, exception: false)
|
|
83
|
+
while chunk.is_a?(String)
|
|
84
|
+
@buffer << chunk
|
|
85
|
+
chunk = @io.read_nonblock(1 << 16, exception: false)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def open_at(where)
|
|
90
|
+
@io = File.open(@path, "rb")
|
|
91
|
+
@ino = @io.stat.ino
|
|
92
|
+
@io.seek(0, IO::SEEK_END) if where == :end
|
|
93
|
+
rescue SystemCallError
|
|
94
|
+
@io = nil # not there yet: the next look opens it
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def reopen_if_rotated
|
|
98
|
+
return open_at(:start) unless @io
|
|
99
|
+
|
|
100
|
+
stat = File.stat(@path)
|
|
101
|
+
return if stat.ino == @ino && stat.size >= @io.pos
|
|
102
|
+
|
|
103
|
+
# Renamed: what the worker wrote to it since the last look is still
|
|
104
|
+
# there, read before the new file (a truncated one has nothing left).
|
|
105
|
+
drain if stat.ino != @ino
|
|
106
|
+
@io.close
|
|
107
|
+
open_at(:start)
|
|
108
|
+
rescue SystemCallError
|
|
109
|
+
nil # renamed and not written again yet
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Whole lines only; a payload line joins the record above it. DebugLog
|
|
113
|
+
# writes a record and its payload in one write, so a look ends on a
|
|
114
|
+
# whole record: each one is handed on by the end of the look.
|
|
115
|
+
def take_lines
|
|
116
|
+
while (cut = @buffer.index("\n"))
|
|
117
|
+
line = LogLine.clean(@buffer.slice!(0, cut + 1)).chomp
|
|
118
|
+
if LogLine.payload_line?(line)
|
|
119
|
+
@record.payload = [@record.payload, line.delete_prefix(LogLine::INDENT)].compact.join("\n") if @record
|
|
120
|
+
next
|
|
121
|
+
end
|
|
122
|
+
flush
|
|
123
|
+
@record = LogLine.parse(line)
|
|
124
|
+
end
|
|
125
|
+
flush
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def flush
|
|
129
|
+
record = @record
|
|
130
|
+
@record = nil
|
|
131
|
+
@on_record.call(record) if record && record.sid == @sid
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -10,7 +10,7 @@ module Samagotchi
|
|
|
10
10
|
# stderr. stdout is the answer's alone, printed after the turn: a
|
|
11
11
|
# script or a parent agent reads it.
|
|
12
12
|
#
|
|
13
|
-
# It prints the retry lines the
|
|
13
|
+
# It prints the retry lines the attached terminal shows: the loop asking again after
|
|
14
14
|
# an empty answer (:empty_answer_retry), and a generation retried after
|
|
15
15
|
# a failed request (:generation_retrying). And the hooks' notices
|
|
16
16
|
# (:hook_notice): one during the turn as it comes, one the after_turn
|
|
@@ -18,7 +18,7 @@ module Samagotchi
|
|
|
18
18
|
# #flush, which the caller runs after it has printed the answer. And
|
|
19
19
|
# the load warnings the first turn announces (:guardrail_warning: the
|
|
20
20
|
# guardrails, the plugins, an orphaned project memory folder), as the
|
|
21
|
-
#
|
|
21
|
+
# attached terminal's "<label>> …" lines.
|
|
22
22
|
class OneShotSink
|
|
23
23
|
include Formatting
|
|
24
24
|
|
|
@@ -12,7 +12,7 @@ module Samagotchi
|
|
|
12
12
|
# print as lines. The activity slot is dropped, because redrawing it in
|
|
13
13
|
# place needs cursor movement (the turn's lines still print as they end).
|
|
14
14
|
# The prompt is Reline's own, so output may cross it; the editor slot
|
|
15
|
-
# prints the prompt of a plain read (
|
|
15
|
+
# prints the prompt of a plain read (a question's ? off a terminal).
|
|
16
16
|
class PlainSurface
|
|
17
17
|
include Surface
|
|
18
18
|
|
|
@@ -11,8 +11,7 @@ module Samagotchi
|
|
|
11
11
|
# slot content (#slot), the line it leaves (#summary) and the parsing of
|
|
12
12
|
# a typed answer ("2", "1,3", "1 3; text", option labels). No I/O:
|
|
13
13
|
# callers draw the slot and print the messages, and
|
|
14
|
-
# record the answer (the
|
|
15
|
-
# Bridge). An approval (kind "approval") takes only a number, an exact
|
|
14
|
+
# record the answer (an attached UI over the Bridge). An approval (kind "approval") takes only a number, an exact
|
|
16
15
|
# label, y (the first option, Allow once) or n (the last, Deny), plus
|
|
17
16
|
# an optional "; reason"; a substring would pick the wrong one ("y" is
|
|
18
17
|
# in "Deny"). The step-limit question (kind "continue") reads the
|
|
@@ -86,8 +85,8 @@ module Samagotchi
|
|
|
86
85
|
end
|
|
87
86
|
|
|
88
87
|
# The line a question dismissed in a --non-interactive run leaves (no
|
|
89
|
-
# one could answer it there): the
|
|
90
|
-
# its cancel (QuestionDesk::UNANSWERABLE_REASON).
|
|
88
|
+
# one could answer it there): the one-shot's own, and an attached UI's
|
|
89
|
+
# on its cancel (QuestionDesk::UNANSWERABLE_REASON).
|
|
91
90
|
UNANSWERABLE_TEXT = "(dismissed: no one to answer in a non-interactive run)"
|
|
92
91
|
|
|
93
92
|
# What a question closed with no answer here says: a relayed approval
|
|
@@ -220,7 +219,7 @@ module Samagotchi
|
|
|
220
219
|
end
|
|
221
220
|
|
|
222
221
|
# The continue words, or an option's number. Empty is Continue (Enter
|
|
223
|
-
# alone
|
|
222
|
+
# alone).
|
|
224
223
|
def parse_continue(raw)
|
|
225
224
|
text = raw.to_s.strip
|
|
226
225
|
if text.match?(/\A\d+\z/)
|
|
@@ -39,13 +39,13 @@ module Samagotchi
|
|
|
39
39
|
attr_reader :screen
|
|
40
40
|
|
|
41
41
|
# Asked first on Ctrl-C during a read, with or without a screen: a
|
|
42
|
-
# truthy answer means it handled the key (the
|
|
43
|
-
# turn) and the read goes on with the typed text as it is.
|
|
42
|
+
# truthy answer means it handled the key (the attached loop cancelled a
|
|
43
|
+
# running turn) and the read goes on with the typed text as it is.
|
|
44
44
|
# @return [#call, nil]
|
|
45
45
|
attr_accessor :interrupt_handler
|
|
46
46
|
|
|
47
47
|
# Called with no arguments for each key a read takes (typing is
|
|
48
|
-
# activity for
|
|
48
|
+
# activity for an idle clock; nobody sets it now).
|
|
49
49
|
# @return [#call, nil]
|
|
50
50
|
attr_accessor :key_handler
|
|
51
51
|
|
|
@@ -6,8 +6,8 @@ module Samagotchi
|
|
|
6
6
|
class TerminalUI
|
|
7
7
|
# The status row under the prompt, the same for both TUIs:
|
|
8
8
|
# `status> Title | model=… | ↳ parent | ⑂ 2 running · 1 waiting | 2 participants | ctx=12.3% (under20) | mem: … | muted: …`.
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# The attached TUI feeds it what it learns from the Bridge's events
|
|
10
|
+
# through #update, and the row is redrawn in
|
|
11
11
|
# the surface's status slot only when its text changes.
|
|
12
12
|
class StatusRow
|
|
13
13
|
include Formatting
|
|
@@ -54,7 +54,7 @@ module Samagotchi
|
|
|
54
54
|
@shown = nil
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
# The surface it draws on (
|
|
57
|
+
# The surface it draws on (no caller since the REPL went): the row
|
|
58
58
|
# is drawn there at the next change.
|
|
59
59
|
def surface=(surface)
|
|
60
60
|
@surface = surface
|
|
@@ -72,8 +72,8 @@ module Samagotchi
|
|
|
72
72
|
refresh
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
# Take a session's state: the
|
|
76
|
-
#
|
|
75
|
+
# Take a session's state: the one a worker sends an attached TUI when
|
|
76
|
+
# it joins (Engine#session_state_snapshot). A key the state
|
|
77
77
|
# lacks (an older worker's) leaves its field as it was; the served pair
|
|
78
78
|
# is cleared without one, and the model is kept without a name.
|
|
79
79
|
# @param default_model [String, nil] the config's default, named beside
|
|
@@ -94,8 +94,7 @@ module Samagotchi
|
|
|
94
94
|
STATE_LISTS = { used_memories: :used_memory_names, preloaded: :preloaded_memory_names,
|
|
95
95
|
muted: :muted_memory_names }.freeze
|
|
96
96
|
|
|
97
|
-
# Take what a turn event says about the row (the
|
|
98
|
-
# the REPL, the Bridge's in attached mode): ctx, the memories used,
|
|
97
|
+
# Take what a turn event says about the row (the Bridge's events): ctx, the memories used,
|
|
99
98
|
# the model the server served, whether a turn runs (the steer
|
|
100
99
|
# hint). Other events change nothing.
|
|
101
100
|
def take_event(event)
|
|
@@ -178,7 +177,7 @@ module Samagotchi
|
|
|
178
177
|
end
|
|
179
178
|
|
|
180
179
|
# `⑂ 2 running · 1 waiting`, the counts there are; "" with neither (no
|
|
181
|
-
# children, or all ended). Not "not reported": a
|
|
180
|
+
# children, or all ended). Not "not reported": a one-shot parent is never
|
|
182
181
|
# given its children's replies (no wakes there), and a worker moves
|
|
183
182
|
# the cursors only after the turn_completed a terminal refreshes on.
|
|
184
183
|
def children_text(counts)
|