samagotchi 0.34.0 → 0.36.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -1
  3. data/README.md +2 -0
  4. data/bin/chi +3 -0
  5. data/docs/broadcast.md +129 -0
  6. data/docs/cli.md +8 -0
  7. data/docs/configuration.md +2 -0
  8. data/docs/context.md +37 -0
  9. data/docs/guardrails.md +14 -4
  10. data/docs/hooks.md +25 -4
  11. data/docs/memory.md +3 -0
  12. data/docs/plugins.md +29 -3
  13. data/docs/sessions.md +6 -6
  14. data/lib/samagotchi/bridge/card_store.rb +1 -1
  15. data/lib/samagotchi/bridge/turn_notice.rb +1 -1
  16. data/lib/samagotchi/broadcast/recipients.rb +63 -0
  17. data/lib/samagotchi/broadcast/scope_card.rb +94 -0
  18. data/lib/samagotchi/broadcast/tags.rb +154 -0
  19. data/lib/samagotchi/broadcast_command.rb +228 -0
  20. data/lib/samagotchi/bundles/coordinator/manifest.yml +7 -6
  21. data/lib/samagotchi/bundles/coordinator/plugin.rb +72 -4
  22. data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +59 -11
  23. data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
  24. data/lib/samagotchi/bundles/github-pr/plugin.rb +444 -3
  25. data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +22 -0
  26. data/lib/samagotchi/bundles/guardrails/manifest.yml +5 -4
  27. data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
  28. data/lib/samagotchi/bundles/mcp/plugin.rb +44 -11
  29. data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +32 -12
  30. data/lib/samagotchi/bundles/source-links/manifest.yml +4 -3
  31. data/lib/samagotchi/bundles/source-links/source_links.md +1 -1
  32. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  33. data/lib/samagotchi/bundles/system/memory_guide.md +4 -2
  34. data/lib/samagotchi/bundles/system/self_map.md +4 -0
  35. data/lib/samagotchi/children_status.rb +2 -16
  36. data/lib/samagotchi/config.rb +6 -0
  37. data/lib/samagotchi/engine.rb +29 -5
  38. data/lib/samagotchi/git_head.rb +24 -0
  39. data/lib/samagotchi/guardrails/parent_approvals.rb +6 -0
  40. data/lib/samagotchi/guardrails/rules.rb +15 -4
  41. data/lib/samagotchi/guardrails/targets.rb +9 -2
  42. data/lib/samagotchi/guardrails/verdict.rb +4 -3
  43. data/lib/samagotchi/hooks/registry.rb +11 -5
  44. data/lib/samagotchi/log_subscriber.rb +1 -1
  45. data/lib/samagotchi/memory_bundle/index_updater.rb +6 -3
  46. data/lib/samagotchi/note_command.rb +13 -13
  47. data/lib/samagotchi/note_delivery.rb +42 -0
  48. data/lib/samagotchi/plugin/context.rb +4 -4
  49. data/lib/samagotchi/session_commands.rb +1 -0
  50. data/lib/samagotchi/session_manager.rb +4 -3
  51. data/lib/samagotchi/sessions_command.rb +3 -2
  52. data/lib/samagotchi/system_prompt.rb +1 -0
  53. data/lib/samagotchi/terminal_ui/attached_loop.rb +25 -2
  54. data/lib/samagotchi/terminal_ui/status_row.rb +23 -2
  55. data/lib/samagotchi/terminal_ui.rb +14 -0
  56. data/lib/samagotchi/tool_activity.rb +14 -3
  57. data/lib/samagotchi/tool_declarations.rb +8 -4
  58. data/lib/samagotchi/tools/builtins.rb +1 -1
  59. data/lib/samagotchi/tools/memory.rb +103 -2
  60. data/lib/samagotchi/tools/send_note.rb +5 -11
  61. data/lib/samagotchi/version.rb +1 -1
  62. data/lib/samagotchi/web/app.rb +3 -0
  63. data/lib/samagotchi/web/capabilities.rb +19 -0
  64. data/lib/samagotchi/web/public/activity.js +26 -6
  65. data/lib/samagotchi/web/public/app.js +515 -288
  66. data/lib/samagotchi/web/public/card.js +12 -3
  67. data/lib/samagotchi/web/public/command_view.js +2 -3
  68. data/lib/samagotchi/web/public/families_view.js +171 -0
  69. data/lib/samagotchi/web/public/format.js +52 -0
  70. data/lib/samagotchi/web/public/index.html +94 -21
  71. data/lib/samagotchi/web/public/live_timing.js +154 -0
  72. data/lib/samagotchi/web/public/live_turn.js +44 -0
  73. data/lib/samagotchi/web/public/sessions_list.js +228 -32
  74. data/lib/samagotchi/web/public/strip.js +5 -2
  75. data/lib/samagotchi/web/public/turn_html.js +107 -0
  76. data/lib/samagotchi/web/public/turn_model.js +5 -4
  77. data/lib/samagotchi/web/public/turn_view.js +43 -129
  78. data/lib/samagotchi/web/session_summary.rb +4 -0
  79. data/lib/samagotchi/worker.rb +166 -307
  80. data/lib/samagotchi/worker_inbound.rb +251 -0
  81. data/lib/samagotchi/worker_wakes.rb +120 -0
  82. metadata +15 -1
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "securerandom"
5
+ require "time"
6
+
7
+ require_relative "client_id"
8
+ require_relative "config"
9
+ require_relative "context_note"
10
+ require_relative "log"
11
+ require_relative "session"
12
+ require_relative "session_inbox"
13
+
14
+ module Samagotchi
15
+ # What comes into a worker's session between turns, each kind with its
16
+ # own save rule (the Worker runs the turns they start):
17
+ #
18
+ # - context notes (#absorb_notes): into the conversation, saved, then
19
+ # their files deleted;
20
+ # - attached context (#absorb_context): once the conversation started,
21
+ # saved, then the subscriptions written; a source asking to wake may
22
+ # start a context wake turn (a ContextWake);
23
+ # - the first prompt (#take_initial_prompt): taken once and saved
24
+ # cleared; as a command (#initial_command?) the session is saved idle;
25
+ # - an input file (#take_input): a Prompt for a turn, whose own save is
26
+ # the turn's, or a command queued.
27
+ class WorkerInbound
28
+ # One context wake per source in this long (D5): later changes inside
29
+ # it arrive as notes.
30
+ CONTEXT_WAKE_WINDOW = 600
31
+
32
+ # A prompt to run a turn for: an input file's, or the first prompt.
33
+ # +no_interrupt+: an offer its turn makes keeps it for its continue
34
+ # turn; +images+: its image refs ({file:, name:}).
35
+ Prompt = Data.define(:text, :origin, :no_interrupt, :images) do
36
+ def initialize(text:, origin: nil, no_interrupt: false, images: []) = super
37
+ end
38
+
39
+ # A context wake turn to run: the ContextAbsorber::Delivery that woke
40
+ # it, whose wake note is already in the conversation as the start of
41
+ # the turn +turn_id+.
42
+ ContextWake = Data.define(:delivery, :turn_id) do
43
+ def name = delivery.name
44
+ def origin = { client_id: "#{ClientId::CONTEXT_PREFIX}#{name}" }
45
+ end
46
+
47
+ # A save between turns (the turn's own, a command's, the notes', the
48
+ # first prompt's): what it saves is in memory, so one that fails (disk
49
+ # full, permissions; the next save writes it) is logged and must not
50
+ # take the worker down with it.
51
+ # @param at [Symbol] which save, for the log
52
+ # @return [Boolean] whether it saved
53
+ def self.save_or_log(at)
54
+ yield
55
+ true
56
+ rescue SystemCallError, IOError => e
57
+ Log.exception(:worker, "save_failed", e, at: at)
58
+ false
59
+ end
60
+
61
+ # @param wakes [WorkerWakes] the wake budget a context wake asks
62
+ # @param awaiting_continue [#call] whether a continue offer waits
63
+ # @param stopped [#call] whether the session was stopped on disk
64
+ # @param queue_command [#call] (line, client_id) queues a session
65
+ # command for the loop's next pass
66
+ def initialize(session:, state_dir:, session_dir:, engine:, context_absorber:, wakes:, awaiting_continue:,
67
+ stopped:, queue_command:)
68
+ @session = session
69
+ @state_dir = state_dir
70
+ @session_dir = session_dir
71
+ @engine = engine
72
+ @context_absorber = context_absorber
73
+ @wakes = wakes
74
+ @awaiting_continue = awaiting_continue
75
+ @stopped = stopped
76
+ @queue_command = queue_command
77
+ @initial_prompt_taken = false
78
+ end
79
+
80
+ # Add the queued context notes to the conversation (between turns only,
81
+ # on the loop's thread), save, then delete their files: a crash before
82
+ # the delete leaves them claimed, and Engine#add_context_note skips a
83
+ # note the saved conversation already holds. A failed save keeps the
84
+ # files too: the next pass claims them again, finds them in the
85
+ # conversation and saves again. Not activity: a note alone neither
86
+ # starts a turn nor keeps an idle worker up.
87
+ def absorb_notes
88
+ files = SessionInbox.find_new_note_files(@session_dir)
89
+ return if files.empty? || @stopped.call
90
+
91
+ claimed = files.filter_map { |file| SessionInbox.claim_note_file(file) }
92
+ claimed.each do |file|
93
+ note = SessionInbox.read_note(file)
94
+ @engine.add_context_note(@session, note) if note
95
+ end
96
+ return unless save(:notes)
97
+
98
+ claimed.each { |file| FileUtils.rm_f(file) }
99
+ end
100
+
101
+ # The attached context's notes (ContextAbsorber), between turns. Not
102
+ # into a session with no turn yet (+before_turn+: one is about to
103
+ # run): auto-attached context alone doesn't make a session worth
104
+ # keeping. Saved, then the subscriptions written (a crash between the
105
+ # two re-delivers; the note ids dedupe).
106
+ # +wake+: the session is idle with nothing queued, so an update whose
107
+ # source asked to wake may start a turn (#context_wake_for): its note
108
+ # goes in as the turn's first message.
109
+ # @return [ContextWake, nil] the wake turn to run now
110
+ # @raise [SystemCallError, IOError] the caller logs it (context_failed)
111
+ def absorb_context(before_turn: false, wake: false)
112
+ return nil unless before_turn || conversation_started?
113
+ return nil if @stopped.call
114
+
115
+ batch = @context_absorber.pending
116
+ return nil unless batch
117
+
118
+ waking = wake ? context_wake_for(batch) : nil
119
+ turn_id = SecureRandom.uuid if waking
120
+ batch.deliveries.each do |delivery|
121
+ next unless delivery.note
122
+
123
+ note = delivery == waking ? delivery.wake_note.merge(turn_start: true, turn_id: turn_id) : delivery.note
124
+ @engine.add_context_note(@session, note)
125
+ end
126
+ # Saved even when every note was there already: after a failed save
127
+ # they are in memory only.
128
+ return nil if batch.notes.any? && !save(:context)
129
+
130
+ @context_absorber.commit(waking ? woken(batch, waking) : batch)
131
+ waking && ContextWake.new(delivery: waking, turn_id: turn_id)
132
+ end
133
+
134
+ # A failed wake turn kept nothing but its note: the update goes back to
135
+ # a plain note (no turn start, the background wording), so a reload
136
+ # draws no empty turn for it.
137
+ def unmark_wake(context_wake)
138
+ messages = @engine.messages_checkpoint
139
+ index = messages.index { |m| m[:turn_start] && m[:turn_id] == context_wake.turn_id }
140
+ return unless index
141
+
142
+ messages[index] = ContextNote.message(**context_wake.delivery.note)
143
+ @engine.rollback_to(messages)
144
+ end
145
+
146
+ # spawn_session hands the first prompt over in last_prompt, but
147
+ # last_prompt also records every later turn's prompt (and mark_error's
148
+ # reason), so only a session with no conversation yet has one pending; a
149
+ # resumed session must not replay its last turn. Taken once.
150
+ # @return [String, nil]
151
+ def take_initial_prompt
152
+ return nil if @initial_prompt_taken
153
+
154
+ @initial_prompt_taken = true
155
+ # A context note may have come before the first prompt ran.
156
+ return nil unless @session.messages.all? { |m| ContextNote.note?(m) } && !@session.last_prompt.to_s.strip.empty?
157
+
158
+ prompt = @session.last_prompt
159
+ @session.last_prompt = ""
160
+ save(:initial_prompt)
161
+ prompt
162
+ end
163
+
164
+ # The first prompt as a command (queued): the session was saved as
165
+ # running for a turn that won't run.
166
+ # @return [Boolean] whether it was one
167
+ def initial_command?(prompt)
168
+ return false unless queue_as_command(prompt, nil)
169
+
170
+ @session.status = Session::STATUS_IDLE
171
+ save(:initial_command, unless_stopped: true)
172
+ true
173
+ end
174
+
175
+ # Claim +input_file+ and yield its Prompt (the turn runs in the block),
176
+ # deleting the claimed file after. Nothing is yielded for a file
177
+ # another reader claimed, an empty line or a command (queued).
178
+ def take_input(input_file)
179
+ claimed_file = SessionInbox.claim_input_file(input_file)
180
+ return unless claimed_file
181
+
182
+ begin
183
+ message, origin, no_interrupt, images = SessionInbox.read_input(claimed_file)
184
+ return if message.to_s.strip.empty?
185
+ return if Array(images).empty? && queue_as_command(message, origin)
186
+
187
+ yield Prompt.new(text: message, origin: origin, no_interrupt: !!no_interrupt, images: images || [])
188
+ ensure
189
+ FileUtils.rm_f(claimed_file)
190
+ end
191
+ end
192
+
193
+ # Whether the session has had a turn: a user or assistant message.
194
+ def conversation_started?
195
+ Array(@session.messages).any? { |m| %w[user assistant model].include?((m[:role] || m["role"]).to_s) }
196
+ end
197
+
198
+ private
199
+
200
+ # +unless_stopped+: not when the session was stopped meanwhile: the stop
201
+ # (chi stop, from another process) saved its status, and this save
202
+ # would write the worker's over it.
203
+ def save(at, unless_stopped: false)
204
+ self.class.save_or_log(at) do
205
+ @session.save(state_dir: @state_dir) unless unless_stopped && @stopped.call
206
+ end
207
+ end
208
+
209
+ # A session command sent as a message (chi send -m "/model x", a web
210
+ # page's first message) runs as the command, as the Bridge does for a
211
+ # POST /turn; an unknown /word stays a prompt. Queued: the loop's next
212
+ # pass runs it.
213
+ # @return [Boolean] whether +text+ was one
214
+ def queue_as_command(text, origin)
215
+ return false unless @engine.command_registry.command?(text.to_s)
216
+
217
+ @queue_command.call(text, origin&.dig(:client_id))
218
+ true
219
+ end
220
+
221
+ # The update in +batch+ that may start a wake turn now: its source
222
+ # asked (wake: true), context.wake is on, the wake budget is open
223
+ # (WorkerWakes#context_open?: no continue offer, not paused, under
224
+ # session.max_wakes, past the start grace) and the source hasn't woken
225
+ # the session in the last CONTEXT_WAKE_WINDOW. The others stay plain
226
+ # notes. Within the start grace a change is a note: it came while the
227
+ # session was away.
228
+ def context_wake_for(batch, now: Time.now)
229
+ candidates = batch.deliveries.select(&:wake_note)
230
+ return nil if candidates.empty? || !context_wakes_on?
231
+ return nil unless @wakes.context_open?(awaiting_continue: @awaiting_continue.call, names: candidates.map(&:name))
232
+
233
+ candidates.find { |delivery| !woke_lately?(delivery.subscription, now) }
234
+ end
235
+
236
+ def context_wakes_on? = Config.get("context.wake") != false
237
+
238
+ def woke_lately?(subscription, now)
239
+ at = subscription&.wakes_at && Time.iso8601(subscription.wakes_at)
240
+ at ? now - at < CONTEXT_WAKE_WINDOW : false
241
+ rescue ArgumentError
242
+ false
243
+ end
244
+
245
+ # +batch+ with +waking+'s subscription recording the wake (the 10-minute window).
246
+ def woken(batch, waking)
247
+ stamped = waking.with(subscription: waking.subscription.with(wakes_at: Time.now.iso8601))
248
+ batch.with(deliveries: batch.deliveries.map { |delivery| delivery == waking ? stamped : delivery })
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "config"
4
+ require_relative "log"
5
+ require_relative "child_reports"
6
+
7
+ module Samagotchi
8
+ # A worker's wake budget: turns nobody typed (a delegate report's wake
9
+ # turn, an attached context source's) run only past the start grace, at
10
+ # most session.max_wakes in a row with no human input, and not after a
11
+ # failed one until a human's input (a provider outage doesn't loop).
12
+ # Shared by both kinds of wake; the Worker asks it before each and tells
13
+ # it what happened. It also holds the reports a delegate wake turn was
14
+ # started for until the turn's first boundary takes them (#hand_over,
15
+ # #take_handed).
16
+ class WorkerWakes
17
+ # @param grace [Numeric] seconds after the start with no wake turn
18
+ # @param clock [#call] monotonic seconds
19
+ # @param max [#call, nil] the budget; nil reads session.max_wakes
20
+ def initialize(grace:, clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }, max: nil)
21
+ @grace = grace
22
+ @clock = clock
23
+ @max = max
24
+ @started_at = clock.call
25
+ @in_a_row = 0
26
+ @paused = false
27
+ @budget_noticed = false
28
+ @handed = nil
29
+ end
30
+
31
+ # Wake turns run since the last human input.
32
+ attr_reader :in_a_row
33
+
34
+ def paused? = @paused
35
+
36
+ def grace_left = @grace - (@clock.call - @started_at)
37
+
38
+ def in_grace? = grace_left.positive?
39
+
40
+ def max
41
+ return @max.call if @max
42
+
43
+ value = Integer(Config.get(ChildRing::MAX_WAKES_KEY), exception: false)
44
+ value&.positive? ? value : ChildRing::MAX_WAKES_DEFAULT
45
+ rescue StandardError
46
+ ChildRing::MAX_WAKES_DEFAULT
47
+ end
48
+
49
+ # For delegate reports: :due when a wake turn may run now (the block
50
+ # says reports wait; asked only when nothing else holds), :budget when
51
+ # only the budget stops it, nil otherwise. The start grace is the
52
+ # caller's (it shortens the idle sleep instead).
53
+ def delegate_state(awaiting_continue:)
54
+ return nil if @paused || awaiting_continue
55
+ return nil unless yield
56
+
57
+ @in_a_row < max ? :due : :budget
58
+ end
59
+
60
+ # For an attached context source asking to wake: no continue offer,
61
+ # not paused, past the start grace and under the budget. +names+: the
62
+ # sources asking, for the log line when the budget holds them.
63
+ def context_open?(awaiting_continue:, names:)
64
+ return false if @paused || awaiting_continue || in_grace?
65
+
66
+ if @in_a_row >= max
67
+ Log.info(:worker, "context_wake_held", reason: "max_wakes", names: names.join(","))
68
+ return false
69
+ end
70
+ true
71
+ end
72
+
73
+ # A wake turn starts. @return [Integer] the wake turns in a row now
74
+ def count! = @in_a_row += 1
75
+
76
+ # A wake turn failed: none starts again until a human's input.
77
+ def pause!
78
+ @paused = true
79
+ end
80
+
81
+ # A human's input resets the budget, the pause and the budget notice.
82
+ def human_input!
83
+ @in_a_row = 0
84
+ @paused = false
85
+ @budget_noticed = false
86
+ end
87
+
88
+ # @return [Boolean] true the first time the budget is spent since the
89
+ # last human input: the UIs hear it once
90
+ def notice_budget!
91
+ return false if @budget_noticed
92
+
93
+ @budget_noticed = true
94
+ end
95
+
96
+ # The idle loop's sleep: +poll+, or less while a wake (the block says
97
+ # one is due) waits out the start grace.
98
+ def idle_wait(poll)
99
+ left = grace_left
100
+ left.positive? && yield ? [left, poll].min : poll
101
+ end
102
+
103
+ # The reports a delegate wake turn runs for, until its first boundary
104
+ # takes them (#take_handed); #drop_handed when the turn is over.
105
+ def hand_over(reports)
106
+ @handed = reports
107
+ end
108
+
109
+ # @return [Array, nil] the handed reports, once
110
+ def take_handed
111
+ reports = @handed
112
+ @handed = nil
113
+ reports
114
+ end
115
+
116
+ def drop_handed
117
+ @handed = nil
118
+ end
119
+ end
120
+ 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.34.0
4
+ version: 0.36.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -123,6 +123,7 @@ files:
123
123
  - README.md
124
124
  - bin/chi
125
125
  - docs/architecture.md
126
+ - docs/broadcast.md
126
127
  - docs/cli.md
127
128
  - docs/configuration.md
128
129
  - docs/context.md
@@ -163,6 +164,10 @@ files:
163
164
  - lib/samagotchi/bridge_client.rb
164
165
  - lib/samagotchi/bridge_client/event_stream.rb
165
166
  - lib/samagotchi/bridge_client/sse_parser.rb
167
+ - lib/samagotchi/broadcast/recipients.rb
168
+ - lib/samagotchi/broadcast/scope_card.rb
169
+ - lib/samagotchi/broadcast/tags.rb
170
+ - lib/samagotchi/broadcast_command.rb
166
171
  - lib/samagotchi/bundle_command.rb
167
172
  - lib/samagotchi/bundle_needs.rb
168
173
  - lib/samagotchi/bundles/btw/manifest.yml
@@ -245,6 +250,7 @@ files:
245
250
  - lib/samagotchi/events.rb
246
251
  - lib/samagotchi/gem_update.rb
247
252
  - lib/samagotchi/generation_phase.rb
253
+ - lib/samagotchi/git_head.rb
248
254
  - lib/samagotchi/guardrail_wiring.rb
249
255
  - lib/samagotchi/guardrails.rb
250
256
  - lib/samagotchi/guardrails/approval.rb
@@ -328,6 +334,7 @@ files:
328
334
  - lib/samagotchi/models_command.rb
329
335
  - lib/samagotchi/muted_memories.rb
330
336
  - lib/samagotchi/note_command.rb
337
+ - lib/samagotchi/note_delivery.rb
331
338
  - lib/samagotchi/output_formatter.rb
332
339
  - lib/samagotchi/owner_lock.rb
333
340
  - lib/samagotchi/parent_report.rb
@@ -456,6 +463,7 @@ files:
456
463
  - lib/samagotchi/vision_support.rb
457
464
  - lib/samagotchi/waiting_steer.rb
458
465
  - lib/samagotchi/web/app.rb
466
+ - lib/samagotchi/web/capabilities.rb
459
467
  - lib/samagotchi/web/info_probe.rb
460
468
  - lib/samagotchi/web/lan.rb
461
469
  - lib/samagotchi/web/markdown_renderer.rb
@@ -476,11 +484,14 @@ files:
476
484
  - lib/samagotchi/web/public/ctx.js
477
485
  - lib/samagotchi/web/public/data.js
478
486
  - lib/samagotchi/web/public/diff_view.js
487
+ - lib/samagotchi/web/public/families_view.js
479
488
  - lib/samagotchi/web/public/format.js
480
489
  - lib/samagotchi/web/public/hold.js
481
490
  - lib/samagotchi/web/public/images.js
482
491
  - lib/samagotchi/web/public/index.html
483
492
  - lib/samagotchi/web/public/init_row.js
493
+ - lib/samagotchi/web/public/live_timing.js
494
+ - lib/samagotchi/web/public/live_turn.js
484
495
  - lib/samagotchi/web/public/model_pick.js
485
496
  - lib/samagotchi/web/public/model_picker.js
486
497
  - lib/samagotchi/web/public/notifications.js
@@ -504,6 +515,7 @@ files:
504
515
  - lib/samagotchi/web/public/thinking_ticker.js
505
516
  - lib/samagotchi/web/public/timing.js
506
517
  - lib/samagotchi/web/public/turn_events.js
518
+ - lib/samagotchi/web/public/turn_html.js
507
519
  - lib/samagotchi/web/public/turn_model.js
508
520
  - lib/samagotchi/web/public/turn_view.js
509
521
  - lib/samagotchi/web/public/update_notice.js
@@ -514,7 +526,9 @@ files:
514
526
  - lib/samagotchi/web/token.rb
515
527
  - lib/samagotchi/worker.rb
516
528
  - lib/samagotchi/worker_idle_exit.rb
529
+ - lib/samagotchi/worker_inbound.rb
517
530
  - lib/samagotchi/worker_sidecar.rb
531
+ - lib/samagotchi/worker_wakes.rb
518
532
  homepage: https://github.com/dm1try/samagotchi
519
533
  licenses:
520
534
  - MIT