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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -1
- data/README.md +2 -0
- data/bin/chi +3 -0
- data/docs/broadcast.md +129 -0
- data/docs/cli.md +8 -0
- data/docs/configuration.md +2 -0
- data/docs/context.md +37 -0
- data/docs/guardrails.md +14 -4
- data/docs/hooks.md +25 -4
- data/docs/memory.md +3 -0
- data/docs/plugins.md +29 -3
- data/docs/sessions.md +6 -6
- data/lib/samagotchi/bridge/card_store.rb +1 -1
- data/lib/samagotchi/bridge/turn_notice.rb +1 -1
- data/lib/samagotchi/broadcast/recipients.rb +63 -0
- data/lib/samagotchi/broadcast/scope_card.rb +94 -0
- data/lib/samagotchi/broadcast/tags.rb +154 -0
- data/lib/samagotchi/broadcast_command.rb +228 -0
- data/lib/samagotchi/bundles/coordinator/manifest.yml +7 -6
- data/lib/samagotchi/bundles/coordinator/plugin.rb +72 -4
- data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +59 -11
- data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
- data/lib/samagotchi/bundles/github-pr/plugin.rb +444 -3
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +22 -0
- data/lib/samagotchi/bundles/guardrails/manifest.yml +5 -4
- data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
- data/lib/samagotchi/bundles/mcp/plugin.rb +44 -11
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +32 -12
- data/lib/samagotchi/bundles/source-links/manifest.yml +4 -3
- data/lib/samagotchi/bundles/source-links/source_links.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/memory_guide.md +4 -2
- data/lib/samagotchi/bundles/system/self_map.md +4 -0
- data/lib/samagotchi/children_status.rb +2 -16
- data/lib/samagotchi/config.rb +6 -0
- data/lib/samagotchi/engine.rb +29 -5
- data/lib/samagotchi/git_head.rb +24 -0
- data/lib/samagotchi/guardrails/parent_approvals.rb +6 -0
- data/lib/samagotchi/guardrails/rules.rb +15 -4
- data/lib/samagotchi/guardrails/targets.rb +9 -2
- data/lib/samagotchi/guardrails/verdict.rb +4 -3
- data/lib/samagotchi/hooks/registry.rb +11 -5
- data/lib/samagotchi/log_subscriber.rb +1 -1
- data/lib/samagotchi/memory_bundle/index_updater.rb +6 -3
- data/lib/samagotchi/note_command.rb +13 -13
- data/lib/samagotchi/note_delivery.rb +42 -0
- data/lib/samagotchi/plugin/context.rb +4 -4
- data/lib/samagotchi/session_commands.rb +1 -0
- data/lib/samagotchi/session_manager.rb +4 -3
- data/lib/samagotchi/sessions_command.rb +3 -2
- data/lib/samagotchi/system_prompt.rb +1 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +25 -2
- data/lib/samagotchi/terminal_ui/status_row.rb +23 -2
- data/lib/samagotchi/terminal_ui.rb +14 -0
- data/lib/samagotchi/tool_activity.rb +14 -3
- data/lib/samagotchi/tool_declarations.rb +8 -4
- data/lib/samagotchi/tools/builtins.rb +1 -1
- data/lib/samagotchi/tools/memory.rb +103 -2
- data/lib/samagotchi/tools/send_note.rb +5 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +3 -0
- data/lib/samagotchi/web/capabilities.rb +19 -0
- data/lib/samagotchi/web/public/activity.js +26 -6
- data/lib/samagotchi/web/public/app.js +515 -288
- data/lib/samagotchi/web/public/card.js +12 -3
- data/lib/samagotchi/web/public/command_view.js +2 -3
- data/lib/samagotchi/web/public/families_view.js +171 -0
- data/lib/samagotchi/web/public/format.js +52 -0
- data/lib/samagotchi/web/public/index.html +94 -21
- data/lib/samagotchi/web/public/live_timing.js +154 -0
- data/lib/samagotchi/web/public/live_turn.js +44 -0
- data/lib/samagotchi/web/public/sessions_list.js +228 -32
- data/lib/samagotchi/web/public/strip.js +5 -2
- data/lib/samagotchi/web/public/turn_html.js +107 -0
- data/lib/samagotchi/web/public/turn_model.js +5 -4
- data/lib/samagotchi/web/public/turn_view.js +43 -129
- data/lib/samagotchi/web/session_summary.rb +4 -0
- data/lib/samagotchi/worker.rb +166 -307
- data/lib/samagotchi/worker_inbound.rb +251 -0
- data/lib/samagotchi/worker_wakes.rb +120 -0
- 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.
|
|
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
|