samagotchi 0.121.0 → 0.121.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 +82 -1
- data/bin/chi +3 -2
- data/docs/architecture.md +36 -11
- data/docs/bundle-authoring.md +2 -1
- data/docs/cli.md +10 -4
- data/docs/configuration.md +7 -3
- data/docs/guardrails.md +48 -19
- data/docs/internals/anthropic.md +1 -1
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/participants.md +3 -1
- data/docs/internals/remote-sessions.md +15 -11
- data/docs/internals/web-push.md +22 -11
- data/docs/plugins.md +6 -2
- data/docs/sessions.md +15 -0
- data/docs/testing.md +41 -1
- data/lib/samagotchi/answer_command.rb +1 -1
- data/lib/samagotchi/bridge.rb +8 -8
- data/lib/samagotchi/bridge_client/event_stream.rb +11 -27
- data/lib/samagotchi/bridge_client/stream_open.rb +79 -0
- data/lib/samagotchi/bridge_client/stream_request.rb +8 -2
- data/lib/samagotchi/bridge_client.rb +52 -65
- data/lib/samagotchi/bridge_secret.rb +9 -7
- data/lib/samagotchi/bundle_command/check.rb +27 -2
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/claude-peer/scripts/pair +20 -6
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/codex-peer/scripts/pair +14 -3
- data/lib/samagotchi/bundles/github-pr/manifest.yml +2 -2
- data/lib/samagotchi/bundles/github-pr/plugin.rb +2 -6
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/child_move.rb +5 -7
- data/lib/samagotchi/children_status.rb +2 -1
- data/lib/samagotchi/guardrail_wiring.rb +19 -2
- data/lib/samagotchi/guardrails/parent_approvals.rb +9 -0
- data/lib/samagotchi/guardrails_command.rb +41 -6
- data/lib/samagotchi/image_store.rb +6 -3
- data/lib/samagotchi/interrupted_turn.rb +6 -3
- data/lib/samagotchi/live_versions.rb +6 -7
- data/lib/samagotchi/memory_bundle/profile.rb +1 -1
- data/lib/samagotchi/memory_bundle/uninstaller.rb +6 -2
- data/lib/samagotchi/plugin/api.rb +8 -5
- data/lib/samagotchi/plugin/sessions.rb +13 -4
- data/lib/samagotchi/push_command.rb +1 -1
- data/lib/samagotchi/relay_verifier.rb +2 -3
- data/lib/samagotchi/relay_watcher.rb +2 -2
- data/lib/samagotchi/remote_attach_command.rb +1 -1
- data/lib/samagotchi/reply_wait/poll.rb +8 -2
- data/lib/samagotchi/reply_wait.rb +4 -3
- data/lib/samagotchi/review_command.rb +1 -1
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/session.rb +4 -0
- data/lib/samagotchi/session_access/local.rb +18 -8
- data/lib/samagotchi/session_access/remote.rb +5 -0
- data/lib/samagotchi/session_access/web_bridge_client.rb +31 -19
- data/lib/samagotchi/session_access.rb +21 -9
- data/lib/samagotchi/session_continue.rb +2 -3
- data/lib/samagotchi/session_manager.rb +118 -28
- data/lib/samagotchi/sessions_command.rb +10 -5
- data/lib/samagotchi/system_one/client.rb +6 -1
- data/lib/samagotchi/system_one/fake_backend.rb +0 -2
- data/lib/samagotchi/system_one/log.rb +21 -6
- data/lib/samagotchi/system_one/self_protect.rb +39 -43
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +8 -3
- data/lib/samagotchi/terminal_ui/attached_loop.rb +4 -2
- data/lib/samagotchi/terminal_ui/local_link.rb +28 -19
- data/lib/samagotchi/toolkit.rb +68 -2
- data/lib/samagotchi/tools/delegate.rb +1 -1
- data/lib/samagotchi/tools/delegate_relay.rb +9 -7
- data/lib/samagotchi/turn_checkpoint.rb +15 -9
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +123 -65
- data/lib/samagotchi/web/participant_access.rb +26 -7
- data/lib/samagotchi/web/participant_review.rb +2 -1
- data/lib/samagotchi/web/participant_view.rb +7 -3
- data/lib/samagotchi/web/participants_admin.rb +13 -0
- data/lib/samagotchi/web/public/app.js +1 -1
- data/lib/samagotchi/web/public/notifications.js +21 -1
- data/lib/samagotchi/web/public/participant.html +3 -2
- data/lib/samagotchi/web/public/participant.js +23 -9
- data/lib/samagotchi/web/public/participants_panel.js +46 -11
- data/lib/samagotchi/web/public/push.js +4 -3
- data/lib/samagotchi/web/public/push_devices.js +17 -3
- data/lib/samagotchi/web/public/review_history.js +14 -2
- data/lib/samagotchi/web/public/review_start.js +1 -3
- data/lib/samagotchi/web/public/session_actions.js +14 -2
- data/lib/samagotchi/web/public/storage.js +16 -0
- data/lib/samagotchi/web/push/client.rb +5 -0
- data/lib/samagotchi/web/push/store.rb +16 -3
- data/lib/samagotchi/web/push_routes.rb +3 -2
- data/lib/samagotchi/web/session_access_routes.rb +25 -17
- data/lib/samagotchi/web/session_create.rb +4 -4
- data/lib/samagotchi/web/session_hub.rb +6 -4
- data/lib/samagotchi/web/session_summary.rb +8 -16
- data/lib/samagotchi/worker.rb +1 -1
- data/lib/samagotchi/worker_link.rb +213 -0
- data/lib/samagotchi/worker_sidecar.rb +15 -37
- metadata +3 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "time"
|
|
5
|
+
require_relative "owner_lock"
|
|
6
|
+
require_relative "version"
|
|
7
|
+
require_relative "worker_sidecar"
|
|
8
|
+
require_relative "bridge_client"
|
|
9
|
+
require_relative "chi_invocation"
|
|
10
|
+
|
|
11
|
+
module Samagotchi
|
|
12
|
+
# Whether a session's worker can be reached, and how: the one question
|
|
13
|
+
# every client asks before it talks to a worker's Bridge (a TUI attaching,
|
|
14
|
+
# chi web's relays and stream, `chi send`, a restart, `chi update`). It
|
|
15
|
+
# answers a typed outcome instead of a sidecar or nil, so nobody reads
|
|
16
|
+
# bridge.json by hand. The rule is the owner lock plus the sidecar's pid,
|
|
17
|
+
# never a port probe:
|
|
18
|
+
# - Live: a worker holds the lock and its sidecar names its pid (a sidecar
|
|
19
|
+
# with no pid: an older worker's, the lock alone) (#client makes the one
|
|
20
|
+
# client for it);
|
|
21
|
+
# - Starting: a worker holds the lock but has no sidecar of its own yet (or
|
|
22
|
+
# the lock's record is still being written); stuck when it has held it
|
|
23
|
+
# past START_LIMIT without one;
|
|
24
|
+
# - Leaving: the worker closed its Bridge and holds the lock for its last work;
|
|
25
|
+
# - Skew: a worker whose sidecar speaks a newer PROTOCOL than this chi;
|
|
26
|
+
# - Stale: a sidecar, but the lock is free (or a chi REPL holds it, which
|
|
27
|
+
# writes none): its worker died;
|
|
28
|
+
# - None: no worker and no sidecar, or a chi REPL.
|
|
29
|
+
# A kernel releases a dead worker's lock, so a free lock is the proof of
|
|
30
|
+
# death a TCP probe never gave (a port can be reused by another session's
|
|
31
|
+
# Bridge).
|
|
32
|
+
module WorkerLink
|
|
33
|
+
# The sidecar protocol this chi speaks; a sidecar with a bigger one is a
|
|
34
|
+
# newer worker's (Skew).
|
|
35
|
+
PROTOCOL = 1
|
|
36
|
+
|
|
37
|
+
# Seconds a worker may hold the lock without publishing its Bridge before
|
|
38
|
+
# Starting is stuck (SessionManager::WORKER_START_GRACE).
|
|
39
|
+
START_LIMIT = 30.0
|
|
40
|
+
|
|
41
|
+
# The sidecar's file name in a session folder.
|
|
42
|
+
FILE = WorkerSidecar::FILE
|
|
43
|
+
|
|
44
|
+
# A worker whose Bridge answers: its sidecar says how to reach it.
|
|
45
|
+
Live = Data.define(:sidecar, :owner) do
|
|
46
|
+
def initialize(sidecar:, owner: nil) = super
|
|
47
|
+
|
|
48
|
+
def live? = true
|
|
49
|
+
def worker_owned? = true
|
|
50
|
+
|
|
51
|
+
# The client for this worker (BridgeClient.for_sidecar: its port and its
|
|
52
|
+
# secret from this one read).
|
|
53
|
+
# @return [BridgeClient]
|
|
54
|
+
def client(session_id, host: BridgeClient::HOST, read_timeout: BridgeClient::READ_TIMEOUT)
|
|
55
|
+
BridgeClient.for_sidecar(session_id, sidecar, host: host, read_timeout: read_timeout)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def port = sidecar.port
|
|
59
|
+
def epoch = sidecar.epoch
|
|
60
|
+
def version = sidecar.version
|
|
61
|
+
def features = sidecar.features
|
|
62
|
+
def started_at = sidecar.started_at
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# A worker holds the lock and has not published its Bridge (yet).
|
|
66
|
+
Starting = Data.define(:owner, :stuck) do
|
|
67
|
+
def initialize(owner:, stuck: false) = super
|
|
68
|
+
|
|
69
|
+
def live? = false
|
|
70
|
+
def worker_owned? = true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# A worker holds the lock with its Bridge closed (OwnerLock::Owner#leaving?).
|
|
74
|
+
Leaving = Data.define(:owner) do
|
|
75
|
+
def live? = false
|
|
76
|
+
def worker_owned? = true
|
|
77
|
+
|
|
78
|
+
# Seconds of its leave still to run.
|
|
79
|
+
def left = owner.leave_left
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# No worker: no sidecar in the session folder, and no worker's lock (a
|
|
83
|
+
# chi REPL's, +owner+, is no worker's).
|
|
84
|
+
None = Data.define(:owner) do
|
|
85
|
+
def initialize(owner: nil) = super
|
|
86
|
+
|
|
87
|
+
def live? = false
|
|
88
|
+
def worker_owned? = false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# A sidecar no worker stands behind: the lock is free (reason :lock_free).
|
|
92
|
+
Stale = Data.define(:sidecar, :reason, :owner) do
|
|
93
|
+
def initialize(sidecar:, reason:, owner: nil) = super
|
|
94
|
+
|
|
95
|
+
def live? = false
|
|
96
|
+
def worker_owned? = false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# A worker whose sidecar's protocol is newer than this chi's (reason
|
|
100
|
+
# :protocol): it must not be driven by this chi.
|
|
101
|
+
Skew = Data.define(:sidecar, :owner, :reason) do
|
|
102
|
+
def live? = false
|
|
103
|
+
def worker_owned? = true
|
|
104
|
+
|
|
105
|
+
def message(session_id, via: ChiInvocation::HERE)
|
|
106
|
+
"session #{session_id}'s worker runs chi #{sidecar.version || "?"} (protocol #{sidecar.protocol}), " \
|
|
107
|
+
"newer than this chi #{Samagotchi::VERSION} (protocol #{PROTOCOL}): restart " \
|
|
108
|
+
"#{via.remote? ? via.web : "chi web"} or update this chi"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
module_function
|
|
113
|
+
|
|
114
|
+
def not_live(session_id) = "no live worker for session #{session_id}"
|
|
115
|
+
|
|
116
|
+
# @param session_dir [String]
|
|
117
|
+
# @param unlink [Boolean] remove a Stale sidecar (the clients that would
|
|
118
|
+
# talk to it; never one a worker may be writing); false is a read-only
|
|
119
|
+
# look (`chi update`, a restart's poll)
|
|
120
|
+
# @param owner [OwnerLock::Owner, nil, :read] the lock's holder as the
|
|
121
|
+
# caller already read it (nil: free); :read reads it here
|
|
122
|
+
# @return [Live, Starting, Leaving, None, Stale, Skew]
|
|
123
|
+
def resolve(session_dir, unlink:, owner: :read, host: BridgeClient::HOST)
|
|
124
|
+
owner = OwnerLock.owner(session_dir) if owner == :read
|
|
125
|
+
stamp = file_stamp(session_dir)
|
|
126
|
+
sidecar = WorkerSidecar.read(session_dir)
|
|
127
|
+
return unowned(session_dir, sidecar, owner, stamp, unlink) if owner.nil? || owner.tui?
|
|
128
|
+
return Starting.new(owner: owner, stuck: false) unless owner.worker?
|
|
129
|
+
return Leaving.new(owner: owner) if owner.leaving?
|
|
130
|
+
return Starting.new(owner: owner, stuck: stuck?(owner)) unless ours?(sidecar, owner)
|
|
131
|
+
return Skew.new(sidecar: sidecar, owner: owner, reason: :protocol) if sidecar.protocol.to_i > PROTOCOL
|
|
132
|
+
|
|
133
|
+
Live.new(sidecar: sidecar, owner: owner)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @return [BridgeClient, nil] a client for the session's Live worker
|
|
137
|
+
def client(session_id, session_dir:, unlink: true, host: BridgeClient::HOST, read_timeout: BridgeClient::READ_TIMEOUT)
|
|
138
|
+
link = resolve(session_dir, unlink: unlink, host: host)
|
|
139
|
+
link.live? ? link.client(session_id, host: host, read_timeout: read_timeout) : nil
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Wait for a worker (a freshly spawned one) to publish its Bridge: the
|
|
143
|
+
# first Live for which +until+ holds within +timeout+ seconds (polled
|
|
144
|
+
# every +interval+), else the last outcome seen. +until+ tells a new
|
|
145
|
+
# worker from the one a restart asked to leave (its sidecar is Live too).
|
|
146
|
+
# @param _session_id [String] the session waited for (as .client's; the
|
|
147
|
+
# outcome's #client takes it again)
|
|
148
|
+
# @param until [#call, nil] ->(live) { … }; nil takes any Live
|
|
149
|
+
# @param give_up [#call, nil] ->(outcome) { … }: stop at the first
|
|
150
|
+
# outcome it holds for (a waker's "no worker owns it any more")
|
|
151
|
+
# @return [Live, Starting, Leaving, None, Stale, Skew] the outcome it
|
|
152
|
+
# stopped at, else the last one seen
|
|
153
|
+
def await(_session_id, session_dir:, timeout:, unlink: true, interval: BridgeClient::SPAWN_POLL_INTERVAL,
|
|
154
|
+
until: nil, give_up: nil, host: BridgeClient::HOST)
|
|
155
|
+
wanted = binding.local_variable_get(:until) # a keyword: no plain read
|
|
156
|
+
last = nil
|
|
157
|
+
found = BridgeClient.poll(timeout, interval: interval) do
|
|
158
|
+
last = resolve(session_dir, unlink: unlink, host: host)
|
|
159
|
+
last if (last.live? && (wanted.nil? || wanted.call(last))) || give_up&.call(last)
|
|
160
|
+
end
|
|
161
|
+
found || last
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# The session folders under +state_dir+ that hold a sidecar (live or not),
|
|
165
|
+
# in order.
|
|
166
|
+
# @return [Array<String>]
|
|
167
|
+
def each_dir(state_dir)
|
|
168
|
+
Dir[File.join(state_dir, "*", WorkerSidecar::FILE)].sort.map { |path| File.dirname(path) }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# A sidecar is the lock holder's own when it names its pid (or none: an
|
|
172
|
+
# older worker's) and a port.
|
|
173
|
+
def ours?(sidecar, owner)
|
|
174
|
+
return false unless sidecar&.port&.positive?
|
|
175
|
+
|
|
176
|
+
sidecar.pid.nil? || sidecar.pid == owner.pid
|
|
177
|
+
end
|
|
178
|
+
private_class_method :ours?
|
|
179
|
+
|
|
180
|
+
def stuck?(owner)
|
|
181
|
+
started = Time.iso8601(owner.started_at.to_s)
|
|
182
|
+
Time.now - started > START_LIMIT
|
|
183
|
+
rescue ArgumentError
|
|
184
|
+
false
|
|
185
|
+
end
|
|
186
|
+
private_class_method :stuck?
|
|
187
|
+
|
|
188
|
+
def unowned(session_dir, sidecar, owner, stamp, unlink)
|
|
189
|
+
return None.new(owner: owner) unless sidecar
|
|
190
|
+
|
|
191
|
+
unlink_unchanged(session_dir, stamp) if unlink
|
|
192
|
+
Stale.new(sidecar: sidecar, reason: :lock_free, owner: owner)
|
|
193
|
+
end
|
|
194
|
+
private_class_method :unowned
|
|
195
|
+
|
|
196
|
+
def file_stamp(session_dir)
|
|
197
|
+
stat = File.stat(WorkerSidecar.path(session_dir))
|
|
198
|
+
[stat.ino, stat.mtime]
|
|
199
|
+
rescue SystemCallError
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
private_class_method :file_stamp
|
|
203
|
+
|
|
204
|
+
# Remove the sidecar only if it is the file that was read: a new worker's
|
|
205
|
+
# write is a new inode (AtomicFile renames).
|
|
206
|
+
def unlink_unchanged(session_dir, stamp)
|
|
207
|
+
return if stamp.nil? || file_stamp(session_dir) != stamp
|
|
208
|
+
|
|
209
|
+
FileUtils.rm_f(WorkerSidecar.path(session_dir))
|
|
210
|
+
end
|
|
211
|
+
private_class_method :unlink_unchanged
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "fileutils"
|
|
5
|
-
require "socket"
|
|
6
5
|
require_relative "atomic_file"
|
|
7
6
|
|
|
8
7
|
module Samagotchi
|
|
@@ -14,15 +13,15 @@ module Samagotchi
|
|
|
14
13
|
# one worker identity: the epoch in every event id, so a remote client
|
|
15
14
|
# tells a restarted worker apart; nil from a worker before it), and its
|
|
16
15
|
# Bridge's secret (every request names it in X-Chi-Bridge, Bridge#secret_ok?;
|
|
17
|
-
# nil from a worker before it, which asks for none)
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
16
|
+
# nil from a worker before it, which asks for none), the pid of the worker
|
|
17
|
+
# process that wrote it (WorkerLink: it is Live only under the owner lock
|
|
18
|
+
# of that pid; nil from an older worker) and the WorkerLink::PROTOCOL it
|
|
19
|
+
# speaks (nil from an older one). The Bridge writes it on start and removes
|
|
20
|
+
# it on stop (only its own, by epoch); one a dead worker left stays until
|
|
21
|
+
# a client finds the lock free (WorkerLink.resolve with unlink: true).
|
|
21
22
|
class WorkerSidecar < Data.define(:port, :bind, :session_id, :started_at, :version, :input_format, :features,
|
|
22
|
-
:epoch, :secret)
|
|
23
|
+
:epoch, :secret, :pid, :protocol)
|
|
23
24
|
FILE = "bridge.json"
|
|
24
|
-
# Seconds a liveness probe waits for the port to take a connect.
|
|
25
|
-
PROBE_TIMEOUT = 0.2
|
|
26
25
|
|
|
27
26
|
def self.path(session_dir) = File.join(session_dir, FILE)
|
|
28
27
|
|
|
@@ -35,7 +34,8 @@ module Samagotchi
|
|
|
35
34
|
port = data["port"]
|
|
36
35
|
new(port: port.respond_to?(:to_i) ? port.to_i : 0, bind: data["bind"], session_id: data["session_id"],
|
|
37
36
|
started_at: data["started_at"], version: data["version"], input_format: data["input_format"],
|
|
38
|
-
features: Array(data["features"]).map(&:to_s), epoch: data["epoch"]&.to_s, secret: secret_in(data["secret"])
|
|
37
|
+
features: Array(data["features"]).map(&:to_s), epoch: data["epoch"]&.to_s, secret: secret_in(data["secret"]),
|
|
38
|
+
pid: integer_in(data["pid"]), protocol: integer_in(data["protocol"]))
|
|
39
39
|
rescue JSON::ParserError, SystemCallError
|
|
40
40
|
nil
|
|
41
41
|
end
|
|
@@ -46,34 +46,10 @@ module Samagotchi
|
|
|
46
46
|
|
|
47
47
|
def self.secret_in(value) = value.is_a?(String) && SECRET_SHAPE.match?(value) ? value : nil
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
# nil. One whose port refuses (its worker died) is stale: +unlink+
|
|
51
|
-
# removes it (the clients that would talk to it), else it stays (a
|
|
52
|
-
# read-only look, like `chi update`'s).
|
|
53
|
-
# @return [WorkerSidecar, nil]
|
|
54
|
-
def self.live(session_dir, unlink:, host: "127.0.0.1", timeout: PROBE_TIMEOUT)
|
|
55
|
-
sidecar = read(session_dir)
|
|
56
|
-
return nil unless sidecar&.port&.positive?
|
|
57
|
-
|
|
58
|
-
begin
|
|
59
|
-
# Not Socket.tcp: with connect_timeout on macOS 26 it hands back the
|
|
60
|
-
# socket of a refused connect (the kernel's second connect() says
|
|
61
|
-
# EISCONN, SO_ERROR is unchecked), so a dead worker's port read as
|
|
62
|
-
# live. Ruby bug #22223, fixed in 3.4.11 / 4.0.7, not in 3.3.
|
|
63
|
-
# TCPSocket checks SO_ERROR and raises ECONNREFUSED on every Ruby,
|
|
64
|
-
# so this stays even once 3.3 is dropped.
|
|
65
|
-
TCPSocket.new(host, sidecar.port, connect_timeout: timeout).close
|
|
66
|
-
sidecar
|
|
67
|
-
rescue StandardError
|
|
68
|
-
FileUtils.rm_f(path(session_dir)) if unlink
|
|
69
|
-
nil
|
|
70
|
-
end
|
|
71
|
-
rescue StandardError
|
|
72
|
-
nil
|
|
73
|
-
end
|
|
49
|
+
def self.integer_in(value) = value.is_a?(Integer) ? value : nil
|
|
74
50
|
|
|
75
51
|
def initialize(port:, bind: nil, session_id: nil, started_at: nil, version: nil, input_format: nil, features: [],
|
|
76
|
-
epoch: nil, secret: nil) = super
|
|
52
|
+
epoch: nil, secret: nil, pid: nil, protocol: nil) = super
|
|
77
53
|
|
|
78
54
|
# The secret never shows in an inspect (a log line, an error's message).
|
|
79
55
|
def inspect = "#<#{self.class.name} port=#{port} session_id=#{session_id.inspect} epoch=#{epoch.inspect}>"
|
|
@@ -81,14 +57,16 @@ module Samagotchi
|
|
|
81
57
|
|
|
82
58
|
# Written whole or not at all, readable by this user only (0600 from the
|
|
83
59
|
# moment the file exists, AtomicFile: no other account learns the
|
|
84
|
-
# worker's port or its secret from it); input_format, epoch
|
|
85
|
-
# only when they name one.
|
|
60
|
+
# worker's port or its secret from it); input_format, epoch, secret, pid
|
|
61
|
+
# and protocol only when they name one.
|
|
86
62
|
def write(session_dir)
|
|
87
63
|
FileUtils.mkdir_p(session_dir)
|
|
88
64
|
record = to_h.transform_keys(&:to_s)
|
|
89
65
|
record.delete("input_format") if input_format.nil?
|
|
90
66
|
record.delete("epoch") if epoch.nil?
|
|
91
67
|
record.delete("secret") if secret.nil?
|
|
68
|
+
record.delete("pid") if pid.nil?
|
|
69
|
+
record.delete("protocol") if protocol.nil?
|
|
92
70
|
AtomicFile.write(self.class.path(session_dir), "#{JSON.pretty_generate(record)}\n", perm: 0o600)
|
|
93
71
|
end
|
|
94
72
|
end
|
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.121.
|
|
4
|
+
version: 0.121.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -197,6 +197,7 @@ files:
|
|
|
197
197
|
- lib/samagotchi/bridge_client.rb
|
|
198
198
|
- lib/samagotchi/bridge_client/event_stream.rb
|
|
199
199
|
- lib/samagotchi/bridge_client/sse_parser.rb
|
|
200
|
+
- lib/samagotchi/bridge_client/stream_open.rb
|
|
200
201
|
- lib/samagotchi/bridge_client/stream_request.rb
|
|
201
202
|
- lib/samagotchi/bridge_secret.rb
|
|
202
203
|
- lib/samagotchi/broadcast/recipients.rb
|
|
@@ -890,6 +891,7 @@ files:
|
|
|
890
891
|
- lib/samagotchi/worker.rb
|
|
891
892
|
- lib/samagotchi/worker_idle_exit.rb
|
|
892
893
|
- lib/samagotchi/worker_inbound.rb
|
|
894
|
+
- lib/samagotchi/worker_link.rb
|
|
893
895
|
- lib/samagotchi/worker_sidecar.rb
|
|
894
896
|
- lib/samagotchi/worker_signals.rb
|
|
895
897
|
- lib/samagotchi/worker_task_wakes.rb
|