samagotchi 0.122.0 → 0.124.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 +58 -1
- data/bin/chi +13 -2
- data/docs/architecture.md +36 -7
- data/docs/cli.md +48 -3
- data/docs/configuration.md +31 -6
- data/docs/desktop.md +51 -3
- data/docs/guardrails.md +112 -15
- data/docs/internals/engine.md +11 -4
- data/docs/internals/participants.md +67 -17
- data/docs/internals/prompt-caching.md +19 -7
- data/docs/internals/web-push.md +7 -3
- data/docs/sessions.md +7 -3
- data/docs/testing.md +2 -1
- data/lib/samagotchi/bridge.rb +4 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/config.rb +7 -1
- data/lib/samagotchi/delivery.rb +7 -0
- data/lib/samagotchi/desktop/macos/App.swift +9 -1
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +129 -17
- data/lib/samagotchi/desktop/macos/Images.swift +4 -3
- data/lib/samagotchi/desktop/macos/Panel.swift +315 -80
- data/lib/samagotchi/desktop/macos/Sessions.swift +272 -0
- data/lib/samagotchi/engine.rb +200 -29
- data/lib/samagotchi/gem_update.rb +41 -4
- data/lib/samagotchi/guardrail_wiring.rb +45 -12
- data/lib/samagotchi/guardrails/approval.rb +42 -4
- data/lib/samagotchi/guardrails/gate.rb +1 -1
- data/lib/samagotchi/guardrails/pair_boundary.rb +172 -0
- data/lib/samagotchi/guardrails/pair_command.rb +365 -0
- data/lib/samagotchi/guardrails/review_paths.rb +41 -7
- data/lib/samagotchi/guardrails/shell_lex.rb +7 -5
- data/lib/samagotchi/guardrails/shell_paths.rb +10 -1
- data/lib/samagotchi/guardrails/verdict.rb +7 -0
- data/lib/samagotchi/guardrails.rb +1 -0
- data/lib/samagotchi/guardrails_command.rb +6 -1
- data/lib/samagotchi/kernel_loop.rb +20 -0
- data/lib/samagotchi/llm/chat_loop.rb +4 -0
- data/lib/samagotchi/participant_mode.rb +77 -3
- data/lib/samagotchi/participants.rb +7 -5
- data/lib/samagotchi/review_apply.rb +10 -2
- data/lib/samagotchi/review_start.rb +38 -10
- data/lib/samagotchi/run_options.rb +43 -2
- data/lib/samagotchi/session_access/local.rb +5 -2
- data/lib/samagotchi/session_manager.rb +11 -0
- data/lib/samagotchi/session_participants.rb +18 -3
- data/lib/samagotchi/share_command.rb +2 -2
- data/lib/samagotchi/steer.rb +7 -1
- data/lib/samagotchi/system_one/client.rb +10 -2
- data/lib/samagotchi/system_prompt.rb +20 -4
- data/lib/samagotchi/terminal_ui/question_prompt.rb +17 -1
- data/lib/samagotchi/tool_runner.rb +10 -1
- data/lib/samagotchi/toolkit_binding.rb +34 -0
- data/lib/samagotchi/toolkit_report_command.rb +6 -4
- data/lib/samagotchi/toolkit_suggest.rb +181 -15
- data/lib/samagotchi/tools/builtins.rb +14 -1
- data/lib/samagotchi/update_command.rb +47 -19
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +204 -9
- data/lib/samagotchi/web/grants.rb +17 -5
- data/lib/samagotchi/web/message_display.rb +6 -1
- data/lib/samagotchi/web/participant_review.rb +3 -1
- data/lib/samagotchi/web/participant_view.rb +12 -4
- data/lib/samagotchi/web/participants_admin.rb +10 -6
- data/lib/samagotchi/web/public/app.js +73 -10
- data/lib/samagotchi/web/public/data.js +17 -0
- data/lib/samagotchi/web/public/delivery_line.js +8 -6
- data/lib/samagotchi/web/public/index.html +21 -2
- data/lib/samagotchi/web/public/participant.html +1 -1
- data/lib/samagotchi/web/public/participant.js +10 -12
- data/lib/samagotchi/web/public/participant_model.js +20 -8
- data/lib/samagotchi/web/public/participant_names.js +29 -8
- data/lib/samagotchi/web/public/participants_panel.js +17 -3
- data/lib/samagotchi/web/public/question_card.js +12 -2
- data/lib/samagotchi/web/public/question_cards.js +7 -0
- data/lib/samagotchi/web/public/stream_handlers.js +6 -2
- data/lib/samagotchi/web/public/toasts.js +13 -5
- data/lib/samagotchi/web/public/tools_overview.js +42 -0
- data/lib/samagotchi/web/public/turn_events.js +5 -4
- data/lib/samagotchi/web/public/turn_html.js +2 -2
- data/lib/samagotchi/web/public/turn_view.js +6 -4
- data/lib/samagotchi/web/public/update_notice.js +33 -4
- data/lib/samagotchi/web/public/update_sheet.js +158 -0
- data/lib/samagotchi/web/push/notifier.rb +28 -1
- data/lib/samagotchi/web/server.rb +121 -8
- data/lib/samagotchi/web/session_access_routes.rb +2 -1
- data/lib/samagotchi/web/session_hub.rb +52 -3
- data/lib/samagotchi/web/tunnel/tailscale.rb +8 -2
- data/lib/samagotchi/web/update_check.rb +186 -0
- data/lib/samagotchi/worker.rb +44 -6
- metadata +7 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "time"
|
|
6
|
+
require_relative "../atomic_file"
|
|
7
|
+
require_relative "../config"
|
|
8
|
+
require_relative "../gem_update"
|
|
9
|
+
require_relative "../installed_gem"
|
|
10
|
+
require_relative "../installed_versions"
|
|
11
|
+
require_relative "../log"
|
|
12
|
+
require_relative "../paths"
|
|
13
|
+
require_relative "../version"
|
|
14
|
+
|
|
15
|
+
module Samagotchi
|
|
16
|
+
module Web
|
|
17
|
+
# Checks RubyGems away from the session hub's Monitor and publishes the
|
|
18
|
+
# result as chi web's version state. The state file preserves cooldowns
|
|
19
|
+
# and version-level notice deduplication across chi web restarts.
|
|
20
|
+
class UpdateCheck
|
|
21
|
+
FIRST_CHECK = 30
|
|
22
|
+
# A failed check (offline at start) tries again this soon, not a whole check_hours later.
|
|
23
|
+
RETRY = 3600
|
|
24
|
+
|
|
25
|
+
def initialize(hub:, enabled: Config.get("update.notify") == "on",
|
|
26
|
+
check_hours: Config.get("update.check_hours"),
|
|
27
|
+
min_age_hours: Config.get("update.min_age_hours"),
|
|
28
|
+
fetcher: -> { GemUpdate.new.releases }, clock: -> { Time.now },
|
|
29
|
+
state_file: File.join(Paths.state_dir, "update_check.json"),
|
|
30
|
+
gem_spec: InstalledGem.spec, bundler: nil, gem_dir: Gem.dir,
|
|
31
|
+
installed_versions: InstalledVersions.new)
|
|
32
|
+
@hub = hub
|
|
33
|
+
@enabled = enabled
|
|
34
|
+
@check_seconds = [check_hours.to_i, 1].max * 3600
|
|
35
|
+
@min_age_seconds = [min_age_hours.to_i, 0].max * 3600
|
|
36
|
+
@fetcher = fetcher
|
|
37
|
+
@clock = clock
|
|
38
|
+
@state_file = state_file
|
|
39
|
+
@gem_spec = gem_spec
|
|
40
|
+
@bundler = bundler.nil? ? (defined?(::Bundler) || !ENV["BUNDLE_GEMFILE"].to_s.empty?) : bundler
|
|
41
|
+
@gem_dir = gem_dir
|
|
42
|
+
@installed_versions = installed_versions
|
|
43
|
+
@state = load_state
|
|
44
|
+
@blocked = blocked_reason
|
|
45
|
+
@checking = Mutex.new
|
|
46
|
+
@wakeup = Queue.new
|
|
47
|
+
@thread = nil
|
|
48
|
+
@stopped = false
|
|
49
|
+
@failed = false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def start
|
|
53
|
+
return self unless @enabled
|
|
54
|
+
return self if @thread&.alive?
|
|
55
|
+
|
|
56
|
+
@stopped = false
|
|
57
|
+
@hub.update_available(available: still_newer(@state[:available]), blocked: @blocked)
|
|
58
|
+
@thread = Thread.new { run_loop }
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def stop
|
|
63
|
+
@stopped = true
|
|
64
|
+
@wakeup << :stop
|
|
65
|
+
thread = @thread
|
|
66
|
+
@thread = nil
|
|
67
|
+
thread&.join(11.0) # a fetch may take its 5 s open + 5 s read
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [Boolean] whether a check was due and completed
|
|
72
|
+
def check
|
|
73
|
+
return false unless @enabled
|
|
74
|
+
|
|
75
|
+
@checking.synchronize do
|
|
76
|
+
now = @clock.call
|
|
77
|
+
return false unless due?(now)
|
|
78
|
+
|
|
79
|
+
releases = @fetcher.call
|
|
80
|
+
available = available_release(releases, now)&.version
|
|
81
|
+
notified = @state[:notified]
|
|
82
|
+
notify = available && !notified.include?(available)
|
|
83
|
+
next_state = { checked_at: now.iso8601, available: available,
|
|
84
|
+
notified: notify ? (notified + [available]) : notified }
|
|
85
|
+
persist(next_state)
|
|
86
|
+
@hub.update_available(available: available, blocked: @blocked, notify: !!notify)
|
|
87
|
+
@failed = false
|
|
88
|
+
true
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
Log.warn(:web, "update_check_failed", error: e.class.name, msg: e.message)
|
|
91
|
+
@failed = true
|
|
92
|
+
false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Why chi web can't install a gem here (the notice's and POST /api/update's
|
|
97
|
+
# one answer), or nil: a source checkout, a Bundler run, a gem dir it
|
|
98
|
+
# can't write.
|
|
99
|
+
# @return [String, nil]
|
|
100
|
+
def self.blocked_reason(gem_spec: InstalledGem.spec, bundler: nil, gem_dir: Gem.dir)
|
|
101
|
+
bundler = defined?(::Bundler) || !ENV["BUNDLE_GEMFILE"].to_s.empty? if bundler.nil?
|
|
102
|
+
return "checkout" unless gem_spec
|
|
103
|
+
return "bundler" if bundler
|
|
104
|
+
return "gem_dir_not_writable" unless File.writable?(gem_dir)
|
|
105
|
+
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def run_loop
|
|
112
|
+
wait = FIRST_CHECK
|
|
113
|
+
until @stopped
|
|
114
|
+
break if @wakeup.pop(timeout: wait) == :stop
|
|
115
|
+
|
|
116
|
+
check
|
|
117
|
+
wait = next_wait(@clock.call)
|
|
118
|
+
end
|
|
119
|
+
rescue StandardError => e
|
|
120
|
+
Log.warn(:web, "update_check_failed", error: e.class.name, msg: e.message)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Until the check is due again (at least FIRST_CHECK), or RETRY after a failure.
|
|
124
|
+
def next_wait(now)
|
|
125
|
+
return [RETRY, @check_seconds].min if @failed
|
|
126
|
+
|
|
127
|
+
checked_at = parsed_time(@state[:checked_at])
|
|
128
|
+
return FIRST_CHECK unless checked_at
|
|
129
|
+
|
|
130
|
+
[checked_at + @check_seconds - now, FIRST_CHECK].max
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def due?(now)
|
|
134
|
+
checked_at = parsed_time(@state[:checked_at])
|
|
135
|
+
checked_at.nil? || now >= checked_at + @check_seconds
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def available_release(releases, now)
|
|
139
|
+
floor = version_floor
|
|
140
|
+
releases.select do |release|
|
|
141
|
+
version = Gem::Version.new(release.version)
|
|
142
|
+
!release.prerelease && version > floor && now - release.created_at >= @min_age_seconds
|
|
143
|
+
end.max_by { |release| Gem::Version.new(release.version) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# The newest of this chi and the installed ones: a release counts only above it.
|
|
147
|
+
def version_floor
|
|
148
|
+
[VERSION, @installed_versions.newest].compact.select { |version| Gem::Version.correct?(version) }
|
|
149
|
+
.map { |version| Gem::Version.new(version) }.max
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# A saved answer the owner has since installed (or passed) is no longer available.
|
|
153
|
+
def still_newer(version)
|
|
154
|
+
version if version && Gem::Version.correct?(version) && Gem::Version.new(version) > version_floor
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def blocked_reason = self.class.blocked_reason(gem_spec: @gem_spec, bundler: @bundler, gem_dir: @gem_dir)
|
|
158
|
+
|
|
159
|
+
def load_state
|
|
160
|
+
return { checked_at: nil, available: nil, notified: [] } unless File.file?(@state_file)
|
|
161
|
+
|
|
162
|
+
data = JSON.parse(File.read(@state_file))
|
|
163
|
+
{
|
|
164
|
+
checked_at: data["checked_at"],
|
|
165
|
+
available: data["available"],
|
|
166
|
+
notified: Array(data["notified"]).select { |version| version.is_a?(String) }
|
|
167
|
+
}
|
|
168
|
+
rescue JSON::ParserError, SystemCallError => e
|
|
169
|
+
Log.warn(:web, "update_check_failed", error: e.class.name, msg: e.message)
|
|
170
|
+
{ checked_at: nil, available: nil, notified: [] }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def persist(state)
|
|
174
|
+
FileUtils.mkdir_p(File.dirname(@state_file))
|
|
175
|
+
AtomicFile.write(@state_file, JSON.generate(state), perm: 0o600)
|
|
176
|
+
@state = state
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def parsed_time(value)
|
|
180
|
+
Time.iso8601(value) if value
|
|
181
|
+
rescue ArgumentError
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -386,6 +386,9 @@ module Samagotchi
|
|
|
386
386
|
# the background, shown by the UIs; a turn waits only for the ones
|
|
387
387
|
# that bring tools.
|
|
388
388
|
@engine.start_init_tasks!
|
|
389
|
+
# Toolkit suggestion's on mode: System 1's question, asked once with a
|
|
390
|
+
# stand-in prompt so its prefix is cached before the first prompt.
|
|
391
|
+
@engine.warm_toolkit_suggestion!
|
|
389
392
|
# Attached context's commands, in the background; a new text wakes
|
|
390
393
|
# the loop to absorb it. Not activity: the idle exit stops it.
|
|
391
394
|
@context_poller = ContextPoller.new(session_id: @session_id, state_dir: @state_dir,
|
|
@@ -564,6 +567,8 @@ module Samagotchi
|
|
|
564
567
|
# The attached TUI and the web answer approvals; with none attached
|
|
565
568
|
# one waits, like ask_user_question.
|
|
566
569
|
engine.interface = :worker
|
|
570
|
+
# A pair turn stops mid-turn at its participant's cap.
|
|
571
|
+
engine.participant_stop = ->(author) { participant_turn_stop(author) }
|
|
567
572
|
engine.guardrail_state_dir = @state_dir if @state_dir
|
|
568
573
|
engine.session_state_dir = @state_dir if @state_dir
|
|
569
574
|
engine
|
|
@@ -699,28 +704,61 @@ module Samagotchi
|
|
|
699
704
|
entry = Participants.read(@session_id, state_dir: @state_dir).find { |e| Steer.author_for(e.id) == author }
|
|
700
705
|
return "revoked" unless entry&.active?(Time.now)
|
|
701
706
|
|
|
702
|
-
cap = entry
|
|
707
|
+
cap = participant_cap(entry)
|
|
703
708
|
return nil if @engine.metrics.spend(author) < cap
|
|
704
709
|
|
|
705
|
-
cap_notice(author, entry
|
|
710
|
+
cap_notice(author, entry, cap)
|
|
706
711
|
"cap_reached"
|
|
707
712
|
end
|
|
708
713
|
|
|
714
|
+
# Why a participant's running pair turn stops (Engine#participant_stop,
|
|
715
|
+
# asked before each of its generations): their link ended (revoked,
|
|
716
|
+
# expired, the session archived) or their cap reached
|
|
717
|
+
# (#participant_refusal, which gives the owner the cap notice), else nil.
|
|
718
|
+
def participant_turn_stop(author)
|
|
719
|
+
reason = participant_refusal(author) or return nil
|
|
720
|
+
|
|
721
|
+
entry = Participants.read(@session_id, state_dir: @state_dir).find { |e| Steer.author_for(e.id) == author }
|
|
722
|
+
name = participant_label(author, entry&.name)
|
|
723
|
+
return format(REVOKED_STOP, name: name) if reason == "revoked"
|
|
724
|
+
|
|
725
|
+
format(CAP_STOP, name: name, cap: participant_cap(entry))
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
# A participant's cap in dollars: their own, else the config's.
|
|
729
|
+
def participant_cap(entry) = entry&.cap.is_a?(Numeric) ? entry.cap.to_f : Web::Grants.default_cap
|
|
730
|
+
|
|
731
|
+
# A participant's cleaned name, else their id.
|
|
732
|
+
def participant_label(author, name)
|
|
733
|
+
who = ParticipantName.clean(name)
|
|
734
|
+
who.empty? ? Steer.participant_id(author) : who
|
|
735
|
+
end
|
|
736
|
+
|
|
709
737
|
# The owner's notice that a participant reached their cap: once per
|
|
710
738
|
# participant and cap (a raised cap that is reached again says so again).
|
|
711
|
-
|
|
739
|
+
# A pair participant's says turns (their turns act), a review one's
|
|
740
|
+
# comments.
|
|
741
|
+
def cap_notice(author, entry, cap)
|
|
712
742
|
key = [author, cap]
|
|
713
743
|
return if (@cap_noticed ||= Set.new).include?(key)
|
|
714
744
|
|
|
715
745
|
@cap_noticed << key
|
|
716
746
|
id = Steer.participant_id(author)
|
|
717
|
-
|
|
718
|
-
@engine.announce(type: :hook_notice, hook:
|
|
719
|
-
text: format(
|
|
747
|
+
text = ParticipantMode.for_turn(@session, entry) == ParticipantMode::PAIR ? PAIR_CAP_NOTICE : CAP_NOTICE
|
|
748
|
+
@engine.announce(type: :hook_notice, hook: Engine::PARTICIPANTS_HOOK, level: :warn, between_turns: true,
|
|
749
|
+
text: format(text, name: participant_label(author, entry.name), cap: cap, session: @session_id[0, 8], id: id))
|
|
720
750
|
end
|
|
721
751
|
|
|
722
752
|
CAP_NOTICE = "%<name>s reached their $%<cap>.2f review cap: their comments are refused until you raise it " \
|
|
723
753
|
"(chi share cap %<session>s %<id>s AMOUNT)."
|
|
754
|
+
PAIR_CAP_NOTICE = "%<name>s reached their $%<cap>.2f cap: their turns stop and are refused until you raise it " \
|
|
755
|
+
"(chi share cap %<session>s %<id>s AMOUNT)."
|
|
756
|
+
# The reason a pair turn stopped at its participant's cap (the turn's
|
|
757
|
+
# "stopped the turn: …" notice).
|
|
758
|
+
CAP_STOP = "%<name>s reached their $%<cap>.2f cap"
|
|
759
|
+
# The reason a pair turn stopped because its participant's link ended
|
|
760
|
+
# (revoked, expired, or the session archived).
|
|
761
|
+
REVOKED_STOP = "%<name>s's link was revoked or expired"
|
|
724
762
|
|
|
725
763
|
# A participant's queued turn that may not start (#participant_refusal):
|
|
726
764
|
# taken off the queue (:turn_dequeued, so every UI drops its bubble)
|
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.124.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -321,6 +321,7 @@ files:
|
|
|
321
321
|
- lib/samagotchi/desktop/macos/Models.swift
|
|
322
322
|
- lib/samagotchi/desktop/macos/Panel.swift
|
|
323
323
|
- lib/samagotchi/desktop/macos/ProcessRunner.swift
|
|
324
|
+
- lib/samagotchi/desktop/macos/Sessions.swift
|
|
324
325
|
- lib/samagotchi/desktop/macos/Tmux.swift
|
|
325
326
|
- lib/samagotchi/desktop_command.rb
|
|
326
327
|
- lib/samagotchi/edit_preview.rb
|
|
@@ -348,6 +349,8 @@ files:
|
|
|
348
349
|
- lib/samagotchi/guardrails/load_failures.rb
|
|
349
350
|
- lib/samagotchi/guardrails/model_size.rb
|
|
350
351
|
- lib/samagotchi/guardrails/outside.rb
|
|
352
|
+
- lib/samagotchi/guardrails/pair_boundary.rb
|
|
353
|
+
- lib/samagotchi/guardrails/pair_command.rb
|
|
351
354
|
- lib/samagotchi/guardrails/parent_approvals.rb
|
|
352
355
|
- lib/samagotchi/guardrails/parent_levels.rb
|
|
353
356
|
- lib/samagotchi/guardrails/parent_policy.rb
|
|
@@ -855,12 +858,14 @@ files:
|
|
|
855
858
|
- lib/samagotchi/web/public/token_fragment.js
|
|
856
859
|
- lib/samagotchi/web/public/tokens.css
|
|
857
860
|
- lib/samagotchi/web/public/toolkit_chip.js
|
|
861
|
+
- lib/samagotchi/web/public/tools_overview.js
|
|
858
862
|
- lib/samagotchi/web/public/turn_events.js
|
|
859
863
|
- lib/samagotchi/web/public/turn_html.js
|
|
860
864
|
- lib/samagotchi/web/public/turn_model.js
|
|
861
865
|
- lib/samagotchi/web/public/turn_view.js
|
|
862
866
|
- lib/samagotchi/web/public/unqueue.js
|
|
863
867
|
- lib/samagotchi/web/public/update_notice.js
|
|
868
|
+
- lib/samagotchi/web/public/update_sheet.js
|
|
864
869
|
- lib/samagotchi/web/public/vendor/directive-helpers.js
|
|
865
870
|
- lib/samagotchi/web/public/vendor/directive.js
|
|
866
871
|
- lib/samagotchi/web/public/vendor/directives/repeat.js
|
|
@@ -891,6 +896,7 @@ files:
|
|
|
891
896
|
- lib/samagotchi/web/token.rb
|
|
892
897
|
- lib/samagotchi/web/tunnel.rb
|
|
893
898
|
- lib/samagotchi/web/tunnel/tailscale.rb
|
|
899
|
+
- lib/samagotchi/web/update_check.rb
|
|
894
900
|
- lib/samagotchi/worker.rb
|
|
895
901
|
- lib/samagotchi/worker_idle_exit.rb
|
|
896
902
|
- lib/samagotchi/worker_inbound.rb
|