samagotchi 0.18.1 → 0.19.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 +85 -1
- data/README.md +4 -3
- data/docs/architecture.md +15 -2
- data/docs/cli.md +34 -8
- data/docs/configuration.md +8 -4
- data/docs/memory.md +4 -0
- data/docs/sessions.md +6 -4
- data/lib/samagotchi/bridge.rb +25 -9
- data/lib/samagotchi/bridge_client/event_stream.rb +37 -1
- data/lib/samagotchi/bridge_client.rb +9 -5
- data/lib/samagotchi/bundle_command.rb +2 -1
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +7 -4
- data/lib/samagotchi/bundles/source-links/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
- data/lib/samagotchi/engine.rb +9 -3
- data/lib/samagotchi/guardrail_wiring.rb +8 -2
- data/lib/samagotchi/guardrails/approval.rb +10 -4
- data/lib/samagotchi/guardrails/approvals.rb +60 -12
- data/lib/samagotchi/host_registry.rb +22 -4
- data/lib/samagotchi/idle_scheduler.rb +1 -1
- data/lib/samagotchi/installed_versions.rb +91 -0
- data/lib/samagotchi/live_versions.rb +5 -3
- data/lib/samagotchi/memory_bundle/builder.rb +24 -1
- data/lib/samagotchi/memory_bundle/index_sync.rb +1 -9
- data/lib/samagotchi/memory_bundle/installer.rb +33 -0
- data/lib/samagotchi/memory_bundle/status.rb +5 -1
- data/lib/samagotchi/memory_bundle/uninstaller.rb +6 -2
- data/lib/samagotchi/model_overlay.rb +39 -0
- data/lib/samagotchi/model_profile.rb +4 -0
- data/lib/samagotchi/relay_desk.rb +9 -0
- data/lib/samagotchi/session_manager.rb +166 -11
- data/lib/samagotchi/session_metrics.rb +36 -13
- data/lib/samagotchi/sessions_command.rb +43 -4
- data/lib/samagotchi/system_prompt.rb +10 -1
- data/lib/samagotchi/terminal_ui/attached_loop.rb +48 -3
- data/lib/samagotchi/terminal_ui/image_input.rb +11 -4
- data/lib/samagotchi/terminal_ui/version_lines.rb +48 -0
- data/lib/samagotchi/tools/builtins.rb +21 -5
- data/lib/samagotchi/turn_note.rb +3 -2
- data/lib/samagotchi/update_command.rb +33 -12
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +64 -11
- data/lib/samagotchi/web/public/app.js +102 -29
- data/lib/samagotchi/web/public/data.js +6 -0
- data/lib/samagotchi/web/public/format.js +4 -2
- data/lib/samagotchi/web/public/index.html +11 -0
- data/lib/samagotchi/web/public/question_card.js +12 -0
- data/lib/samagotchi/web/public/question_cards.js +17 -11
- data/lib/samagotchi/web/public/scope.js +15 -6
- data/lib/samagotchi/web/public/session_state.js +4 -0
- data/lib/samagotchi/web/public/turn_events.js +107 -3
- data/lib/samagotchi/web/public/update_notice.js +87 -14
- data/lib/samagotchi/web/server.rb +28 -1
- data/lib/samagotchi/web/session_hub.rb +30 -3
- data/lib/samagotchi/web/session_summary.rb +5 -0
- data/lib/samagotchi/worker.rb +25 -8
- data/lib/samagotchi/worker_idle_exit.rb +31 -2
- data/lib/samagotchi/worker_sidecar.rb +7 -5
- metadata +3 -1
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative "continue_offer"
|
|
|
16
16
|
require_relative "iteration_limit"
|
|
17
17
|
require_relative "session_commands"
|
|
18
18
|
require_relative "model_profile"
|
|
19
|
+
require_relative "tools/task_runtime"
|
|
19
20
|
|
|
20
21
|
module Samagotchi
|
|
21
22
|
# The loop of a background session worker, once it owns the session (see
|
|
@@ -85,6 +86,8 @@ module Samagotchi
|
|
|
85
86
|
@exit_requested_by = nil
|
|
86
87
|
# Set as it leaves: nothing happened in the session (#discard?).
|
|
87
88
|
@discard = false
|
|
89
|
+
# The exit asked for is a restart (POST /exit restart: true).
|
|
90
|
+
@exit_restart = false
|
|
88
91
|
end
|
|
89
92
|
|
|
90
93
|
# Whether the session was empty as the worker left it, so the caller
|
|
@@ -96,7 +99,8 @@ module Samagotchi
|
|
|
96
99
|
attr_reader :default_model
|
|
97
100
|
|
|
98
101
|
# @return [Symbol] :idle_exit, :exit_requested when a client asked it
|
|
99
|
-
# to exit (Bridge POST /exit), :
|
|
102
|
+
# to exit (Bridge POST /exit), :restart when it asked for a restart
|
|
103
|
+
# (the caller starts a new worker), :stopped when the session was stopped
|
|
100
104
|
# (chi stop), :crashed when the loop raised (the session is marked
|
|
101
105
|
# errored); SessionManager.run_session_loop turns it into the exit
|
|
102
106
|
def run
|
|
@@ -154,7 +158,8 @@ module Samagotchi
|
|
|
154
158
|
engine: @engine, bridge: @bridge,
|
|
155
159
|
timeout_minutes: @idle_exit_minutes || SessionManager.config_idle_exit_minutes,
|
|
156
160
|
input_pending: -> { !@command_queue.empty? || !SessionInbox.find_new_input_files(@session_dir).empty? },
|
|
157
|
-
awaiting_continue: -> { @turn_flow.awaiting_continue? }
|
|
161
|
+
awaiting_continue: -> { @turn_flow.awaiting_continue? },
|
|
162
|
+
running_tasks: -> { Tools::TaskRuntime.running_created_in(@engine.messages_checkpoint) }
|
|
158
163
|
)
|
|
159
164
|
|
|
160
165
|
begin
|
|
@@ -188,7 +193,7 @@ module Samagotchi
|
|
|
188
193
|
if input_files.empty?
|
|
189
194
|
next if run_due_reminders
|
|
190
195
|
return left(:idle_exit) if @idle_exit.due? && leave_idle
|
|
191
|
-
return left(:exit_requested) if @exit_requested && leave_on_request
|
|
196
|
+
return left(@exit_restart ? :restart : :exit_requested) if @exit_requested && leave_on_request
|
|
192
197
|
|
|
193
198
|
@waker.wait(@poll_interval)
|
|
194
199
|
next
|
|
@@ -581,17 +586,20 @@ module Samagotchi
|
|
|
581
586
|
|
|
582
587
|
# Bridge POST /exit, on a Bridge thread with the event log held.
|
|
583
588
|
# +delete+: the session is deleted after (/exit --delete), so no recap.
|
|
589
|
+
# +restart+: a new worker takes over (on the newest chi installed), so
|
|
590
|
+
# WorkerIdleExit#hold_for_restart's rules, and no recap or discard.
|
|
584
591
|
# @return [Symbol, nil] what keeps the worker up, nil when it will leave
|
|
585
|
-
def exit_request(client_id, delete: false)
|
|
592
|
+
def exit_request(client_id, delete: false, restart: false)
|
|
586
593
|
# The Bridge serves before the idle-exit policy exists.
|
|
587
594
|
return :starting unless @idle_exit
|
|
588
595
|
|
|
589
|
-
hold = @idle_exit.hold_for_request(requester: client_id)
|
|
596
|
+
hold = restart ? @idle_exit.hold_for_restart : @idle_exit.hold_for_request(requester: client_id)
|
|
590
597
|
return hold if hold
|
|
591
598
|
|
|
592
599
|
@exit_requested = true
|
|
593
600
|
@exit_requested_by = client_id
|
|
594
|
-
@exit_deletes = delete
|
|
601
|
+
@exit_deletes = delete && !restart
|
|
602
|
+
@exit_restart = restart
|
|
595
603
|
@waker.wake
|
|
596
604
|
nil
|
|
597
605
|
end
|
|
@@ -604,16 +612,21 @@ module Samagotchi
|
|
|
604
612
|
# @return [Boolean] false when something came in since the request
|
|
605
613
|
def leave_on_request
|
|
606
614
|
@engine.synchronize_events do
|
|
607
|
-
hold =
|
|
615
|
+
hold = if @exit_restart
|
|
616
|
+
@idle_exit.hold_for_restart
|
|
617
|
+
else
|
|
618
|
+
@idle_exit.hold_for_request(requester: @exit_requested_by, streams: false)
|
|
619
|
+
end
|
|
608
620
|
if hold
|
|
609
621
|
@exit_requested = nil
|
|
622
|
+
@exit_restart = false
|
|
610
623
|
Log.info(:worker, "exit_held", reason: hold)
|
|
611
624
|
next false
|
|
612
625
|
end
|
|
613
626
|
|
|
614
627
|
@bridge&.stop
|
|
615
628
|
@engine.stop_idle
|
|
616
|
-
Log.info(:worker, "exit_requested", by: @exit_requested_by || "a client")
|
|
629
|
+
Log.info(:worker, @exit_restart ? "restart_requested" : "exit_requested", by: @exit_requested_by || "a client")
|
|
617
630
|
true
|
|
618
631
|
end
|
|
619
632
|
end
|
|
@@ -623,7 +636,11 @@ module Samagotchi
|
|
|
623
636
|
# stopped by now, and nothing waits on this (the TUI has detached). A
|
|
624
637
|
# `chi send` meanwhile is picked up after the exit (run_session_loop);
|
|
625
638
|
# a `chi --attach` finds no Bridge until then.
|
|
639
|
+
# A restart keeps even an empty session (a new worker takes it over at
|
|
640
|
+
# once) and leaves the recap to the conversation's next real pause.
|
|
626
641
|
def left(result)
|
|
642
|
+
return result if result == :restart
|
|
643
|
+
|
|
627
644
|
@discard = empty_session?
|
|
628
645
|
write_recap_on_leave unless @discard || (result == :exit_requested && @exit_deletes)
|
|
629
646
|
result
|
|
@@ -18,7 +18,8 @@ module Samagotchi
|
|
|
18
18
|
#
|
|
19
19
|
# A client can also ask the worker to exit now (`/exit` in the attached
|
|
20
20
|
# TUI, Bridge POST /exit): #hold_for_request applies the same rules without
|
|
21
|
-
# the timeout and leaves out the asking client's own stream.
|
|
21
|
+
# the timeout and leaves out the asking client's own stream. A restart
|
|
22
|
+
# (POST /exit restart: true) has its own rules, #hold_for_restart.
|
|
22
23
|
class WorkerIdleExit
|
|
23
24
|
# @param engine [Engine] #turn_running?, #last_activity_at, #reminder_store
|
|
24
25
|
# @param bridge [Bridge, nil] #open_streams, #last_client_activity_at; nil
|
|
@@ -26,14 +27,17 @@ module Samagotchi
|
|
|
26
27
|
# @param timeout_minutes [Numeric, nil] 0 or nil: never exit
|
|
27
28
|
# @param input_pending [#call] true while input files wait
|
|
28
29
|
# @param awaiting_continue [#call] true while a continue offer waits
|
|
30
|
+
# @param running_tasks [#call] the background tasks this conversation
|
|
31
|
+
# started that still run (Tools::TaskRuntime.running_created_in)
|
|
29
32
|
# @param clock [#call] monotonic seconds, the Engine's and Bridge's clock
|
|
30
33
|
def initialize(engine:, bridge:, timeout_minutes:, input_pending:, awaiting_continue: -> { false },
|
|
31
|
-
clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) })
|
|
34
|
+
running_tasks: -> { [] }, clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) })
|
|
32
35
|
@engine = engine
|
|
33
36
|
@bridge = bridge
|
|
34
37
|
@timeout = timeout_minutes.to_f * 60
|
|
35
38
|
@input_pending = input_pending
|
|
36
39
|
@awaiting_continue = awaiting_continue
|
|
40
|
+
@running_tasks = running_tasks
|
|
37
41
|
@clock = clock
|
|
38
42
|
@started_at = clock.call
|
|
39
43
|
end
|
|
@@ -78,6 +82,31 @@ module Samagotchi
|
|
|
78
82
|
nil
|
|
79
83
|
end
|
|
80
84
|
|
|
85
|
+
# What keeps the worker from restarting now (a client asked: POST /exit
|
|
86
|
+
# restart: true), or nil. A restart drops what lives only in this
|
|
87
|
+
# process, so whatever would be lost or cut holds it: a question or
|
|
88
|
+
# approval waiting (a turn's), a /btw (or another anytime command)
|
|
89
|
+
# running, a delegate's approval relay this worker still answers for,
|
|
90
|
+
# background tasks this conversation started (their output and end are
|
|
91
|
+
# this worker's to report). Clients' streams don't hold: a web tab
|
|
92
|
+
# reconnects on the new worker's bridge_up, an attached terminal finds
|
|
93
|
+
# the new worker's Bridge.
|
|
94
|
+
# @return [Symbol, nil] :turn_running, :input_queued, :continue_offered,
|
|
95
|
+
# :question_pending, :reminders, :command_running, :relays_open or
|
|
96
|
+
# :background_tasks
|
|
97
|
+
def hold_for_restart
|
|
98
|
+
return :turn_running if @engine.turn_running?
|
|
99
|
+
return :input_queued if @input_pending.call
|
|
100
|
+
return :continue_offered if @awaiting_continue.call
|
|
101
|
+
return :question_pending if @engine.pending_question
|
|
102
|
+
return :reminders if @engine.reminder_store&.any?
|
|
103
|
+
return :command_running if @engine.anytime_running?
|
|
104
|
+
return :relays_open if @engine.relay_desk.active?
|
|
105
|
+
return :background_tasks if @running_tasks.call.any?
|
|
106
|
+
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
81
110
|
# @return [Float] seconds since the latest activity of any kind
|
|
82
111
|
def idle_seconds
|
|
83
112
|
last = [@started_at, @engine.last_activity_at, @bridge&.last_client_activity_at].compact.max
|
|
@@ -8,11 +8,12 @@ require_relative "atomic_file"
|
|
|
8
8
|
module Samagotchi
|
|
9
9
|
# A worker's bridge.json in its session directory: how clients reach its
|
|
10
10
|
# Bridge (port, bind), when it started (a new worker behind the same
|
|
11
|
-
# "worker" owner), the chi it runs (chi update
|
|
12
|
-
# it reads (SessionInbox::INPUT_FORMAT)
|
|
11
|
+
# "worker" owner), the chi it runs (chi update, the web's stale-worker
|
|
12
|
+
# badge), the input-file format it reads (SessionInbox::INPUT_FORMAT) and
|
|
13
|
+
# what else it can do (Bridge::FEATURES). The Bridge writes it on start
|
|
13
14
|
# and removes it on stop; one a dead worker left stays until a client's
|
|
14
15
|
# probe finds its port closed (.live with unlink: true).
|
|
15
|
-
class WorkerSidecar < Data.define(:port, :bind, :session_id, :started_at, :version, :input_format)
|
|
16
|
+
class WorkerSidecar < Data.define(:port, :bind, :session_id, :started_at, :version, :input_format, :features)
|
|
16
17
|
FILE = "bridge.json"
|
|
17
18
|
# Seconds a liveness probe waits for the port to take a connect.
|
|
18
19
|
PROBE_TIMEOUT = 0.2
|
|
@@ -27,7 +28,8 @@ module Samagotchi
|
|
|
27
28
|
|
|
28
29
|
port = data["port"]
|
|
29
30
|
new(port: port.respond_to?(:to_i) ? port.to_i : 0, bind: data["bind"], session_id: data["session_id"],
|
|
30
|
-
started_at: data["started_at"], version: data["version"], input_format: data["input_format"]
|
|
31
|
+
started_at: data["started_at"], version: data["version"], input_format: data["input_format"],
|
|
32
|
+
features: Array(data["features"]).map(&:to_s))
|
|
31
33
|
rescue JSON::ParserError, SystemCallError
|
|
32
34
|
nil
|
|
33
35
|
end
|
|
@@ -61,7 +63,7 @@ module Samagotchi
|
|
|
61
63
|
# @return [Integer, nil] .live's port
|
|
62
64
|
def self.live_port(session_dir, unlink:, host: "127.0.0.1") = live(session_dir, unlink: unlink, host: host)&.port
|
|
63
65
|
|
|
64
|
-
def initialize(port:, bind: nil, session_id: nil, started_at: nil, version: nil, input_format: nil) = super
|
|
66
|
+
def initialize(port:, bind: nil, session_id: nil, started_at: nil, version: nil, input_format: nil, features: []) = super
|
|
65
67
|
|
|
66
68
|
# Written whole or not at all; input_format only when it names one.
|
|
67
69
|
def write(session_dir)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: samagotchi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -252,6 +252,7 @@ files:
|
|
|
252
252
|
- lib/samagotchi/idle_scheduler.rb
|
|
253
253
|
- lib/samagotchi/image_store.rb
|
|
254
254
|
- lib/samagotchi/installed_gem.rb
|
|
255
|
+
- lib/samagotchi/installed_versions.rb
|
|
255
256
|
- lib/samagotchi/iteration_limit.rb
|
|
256
257
|
- lib/samagotchi/kernel_loop.rb
|
|
257
258
|
- lib/samagotchi/launch_mode.rb
|
|
@@ -363,6 +364,7 @@ files:
|
|
|
363
364
|
- lib/samagotchi/terminal_ui/status_row.rb
|
|
364
365
|
- lib/samagotchi/terminal_ui/surface.rb
|
|
365
366
|
- lib/samagotchi/terminal_ui/thinking_line.rb
|
|
367
|
+
- lib/samagotchi/terminal_ui/version_lines.rb
|
|
366
368
|
- lib/samagotchi/text_diff.rb
|
|
367
369
|
- lib/samagotchi/thinking.rb
|
|
368
370
|
- lib/samagotchi/thinking_ticker.rb
|