samagotchi 0.7.0 → 0.8.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 +59 -1
- data/README.md +7 -3
- data/docs/architecture.md +4 -4
- data/docs/cli.md +19 -4
- data/docs/guardrails.md +2 -2
- data/docs/hooks.md +2 -2
- data/docs/internals/context-telemetry.md +3 -1
- data/docs/memory.md +48 -1
- data/docs/plugins.md +7 -5
- data/docs/releasing.md +5 -1
- data/docs/sessions.md +2 -2
- data/lib/samagotchi/archive_store.rb +3 -3
- data/lib/samagotchi/atomic_file.rb +34 -0
- data/lib/samagotchi/bootstrap/bundles.rb +37 -0
- data/lib/samagotchi/bootstrap/config_writer.rb +4 -6
- data/lib/samagotchi/bootstrap/probe.rb +9 -5
- data/lib/samagotchi/bootstrap_command.rb +78 -8
- data/lib/samagotchi/bridge/pending_card.rb +2 -3
- data/lib/samagotchi/bridge.rb +56 -116
- data/lib/samagotchi/bridge_client.rb +7 -11
- data/lib/samagotchi/bundle_command.rb +81 -21
- data/lib/samagotchi/bundle_needs.rb +2 -11
- data/lib/samagotchi/bundles/core/manifest.yml +11 -0
- data/lib/samagotchi/bundles/dev/manifest.yml +13 -0
- data/lib/samagotchi/bundles/system/identity.md +1 -0
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/memory_guide.md +3 -2
- data/lib/samagotchi/client.rb +3 -23
- data/lib/samagotchi/config.rb +6 -11
- data/lib/samagotchi/context_note.rb +1 -1
- data/lib/samagotchi/context_status.rb +255 -0
- data/lib/samagotchi/desktop/macos.rb +2 -3
- data/lib/samagotchi/engine.rb +6 -7
- data/lib/samagotchi/guardrail_wiring.rb +2 -5
- data/lib/samagotchi/guardrails/approvals.rb +2 -7
- data/lib/samagotchi/hooks/bundle_loader.rb +6 -3
- data/lib/samagotchi/hooks/loader.rb +2 -1
- data/lib/samagotchi/hooks/registry.rb +21 -0
- data/lib/samagotchi/image_store.rb +2 -3
- data/lib/samagotchi/kernel_loop.rb +21 -283
- data/lib/samagotchi/live_versions.rb +9 -13
- data/lib/samagotchi/llm/chat_loop.rb +1 -1
- data/lib/samagotchi/llm/native_backend.rb +8 -8
- data/lib/samagotchi/llm/openai_chat.rb +4 -17
- data/lib/samagotchi/llm/utf8_scrub.rb +32 -0
- data/lib/samagotchi/log_path.rb +2 -3
- data/lib/samagotchi/memory_bundle/index_updater.rb +2 -11
- data/lib/samagotchi/memory_bundle/installer.rb +5 -7
- data/lib/samagotchi/memory_bundle/listing.rb +27 -20
- data/lib/samagotchi/memory_bundle/manifest.rb +32 -1
- data/lib/samagotchi/memory_bundle/profile.rb +145 -0
- data/lib/samagotchi/memory_bundle/provenance.rb +42 -53
- data/lib/samagotchi/memory_bundle/shipped_update.rb +52 -27
- data/lib/samagotchi/memory_bundle/source.rb +2 -0
- data/lib/samagotchi/memory_bundle/status.rb +1 -2
- data/lib/samagotchi/memory_bundle.rb +1 -0
- data/lib/samagotchi/note_command.rb +5 -4
- data/lib/samagotchi/owner_lock.rb +0 -6
- data/lib/samagotchi/paths.rb +32 -0
- data/lib/samagotchi/plugin/api.rb +3 -6
- data/lib/samagotchi/plugin/context.rb +2 -3
- data/lib/samagotchi/plugin/loader.rb +1 -1
- data/lib/samagotchi/question_desk.rb +3 -2
- data/lib/samagotchi/recap_store.rb +2 -2
- data/lib/samagotchi/reply_wait.rb +2 -1
- data/lib/samagotchi/self_report.rb +2 -3
- data/lib/samagotchi/send_command.rb +3 -2
- data/lib/samagotchi/session.rb +41 -143
- data/lib/samagotchi/session_inbox.rb +175 -0
- data/lib/samagotchi/session_manager.rb +62 -363
- data/lib/samagotchi/session_metrics.rb +2 -3
- data/lib/samagotchi/session_retention.rb +259 -0
- data/lib/samagotchi/sessions_command.rb +3 -2
- data/lib/samagotchi/terminal_ui/attached_loop.rb +1 -2
- data/lib/samagotchi/terminal_ui/input_support.rb +2 -9
- data/lib/samagotchi/terminal_ui.rb +10 -20
- data/lib/samagotchi/tools/memory.rb +3 -2
- data/lib/samagotchi/tools/send_note.rb +4 -3
- data/lib/samagotchi/tools/task_runtime.rb +2 -3
- data/lib/samagotchi/turn_note.rb +13 -2
- data/lib/samagotchi/update_command.rb +9 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +44 -76
- data/lib/samagotchi/web/info_probe.rb +33 -0
- data/lib/samagotchi/web/public/app.js +1 -1
- data/lib/samagotchi/web/public/index.html +1 -0
- data/lib/samagotchi/web/public/stage_model.js +22 -3
- data/lib/samagotchi/web/public/stage_view.js +25 -10
- data/lib/samagotchi/web/server.rb +8 -11
- data/lib/samagotchi/web/session_hub.rb +8 -7
- data/lib/samagotchi/web/session_summary.rb +4 -3
- data/lib/samagotchi/web/token.rb +2 -5
- data/lib/samagotchi/worker.rb +34 -20
- data/lib/samagotchi/worker_sidecar.rb +42 -0
- metadata +13 -1
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
|
|
5
5
|
require_relative "session"
|
|
6
|
+
require_relative "session_inbox"
|
|
6
7
|
require_relative "turn_note"
|
|
7
8
|
require_relative "context_note"
|
|
8
9
|
require_relative "worker_idle_exit"
|
|
@@ -24,9 +25,9 @@ module Samagotchi
|
|
|
24
25
|
# disk exits the process; an error outside a turn marks the session and
|
|
25
26
|
# exits with 1.
|
|
26
27
|
#
|
|
27
|
-
# The file IPC stays behind
|
|
28
|
-
#
|
|
29
|
-
# stub as seams.
|
|
28
|
+
# The file IPC stays behind SessionInbox's functions (find_new_input_files,
|
|
29
|
+
# claim_input_file, ...) and the Bridge behind SessionManager.start_bridge,
|
|
30
|
+
# which specs stub as seams.
|
|
30
31
|
#
|
|
31
32
|
# The loop sleeps on a Waker, which the Bridge wakes when it queues a turn
|
|
32
33
|
# or a command, and the reminder callback when it queues one. A fallback
|
|
@@ -98,6 +99,7 @@ module Samagotchi
|
|
|
98
99
|
# to exit (Bridge POST /exit)
|
|
99
100
|
def run
|
|
100
101
|
@session = Session.load(@session_id, state_dir: @state_dir)
|
|
102
|
+
drop_dead_question
|
|
101
103
|
@engine = build_engine
|
|
102
104
|
# Before the Bridge serves anything: a UI joining a resumed worker's
|
|
103
105
|
# stream gets the session's history and status in its snapshot, not
|
|
@@ -142,7 +144,7 @@ module Samagotchi
|
|
|
142
144
|
@idle_exit = WorkerIdleExit.new(
|
|
143
145
|
engine: @engine, bridge: @bridge,
|
|
144
146
|
timeout_minutes: @idle_exit_minutes || SessionManager.config_idle_exit_minutes,
|
|
145
|
-
input_pending: -> { !@command_queue.empty? || !
|
|
147
|
+
input_pending: -> { !@command_queue.empty? || !SessionInbox.find_new_input_files(@session_dir).empty? },
|
|
146
148
|
awaiting_continue: -> { @turn_flow.awaiting_continue? }
|
|
147
149
|
)
|
|
148
150
|
|
|
@@ -170,7 +172,7 @@ module Samagotchi
|
|
|
170
172
|
next
|
|
171
173
|
end
|
|
172
174
|
|
|
173
|
-
input_files =
|
|
175
|
+
input_files = SessionInbox.find_new_input_files(@session_dir)
|
|
174
176
|
if input_files.empty?
|
|
175
177
|
next if run_due_reminders
|
|
176
178
|
return left(:idle_exit) if @idle_exit.due? && leave_idle
|
|
@@ -208,7 +210,6 @@ module Samagotchi
|
|
|
208
210
|
waker = @waker
|
|
209
211
|
engine = nil
|
|
210
212
|
engine = Samagotchi::Engine.new(
|
|
211
|
-
mode: @session.mode.to_sym,
|
|
212
213
|
model_name: @session.model_name,
|
|
213
214
|
# The session's --memory and --mute lists: the same prompt on every
|
|
214
215
|
# (re)spawn.
|
|
@@ -231,6 +232,19 @@ module Samagotchi
|
|
|
231
232
|
engine
|
|
232
233
|
end
|
|
233
234
|
|
|
235
|
+
# A question saved by a worker that died while it waited (an
|
|
236
|
+
# ask_user_question, an approval, a hook's question): the turn that
|
|
237
|
+
# asked is gone, so nobody can answer it, and left in the file it
|
|
238
|
+
# would keep the hub's "needs you" badge on for good. Dropped and saved
|
|
239
|
+
# before the Engine and the Bridge exist (no question lock, no events).
|
|
240
|
+
def drop_dead_question
|
|
241
|
+
return unless @session.pending_question
|
|
242
|
+
|
|
243
|
+
Log.info(:worker, "dead_question_dropped", id: @session.pending_question[:id])
|
|
244
|
+
@session.pending_question = nil
|
|
245
|
+
@session.save(state_dir: @state_dir)
|
|
246
|
+
end
|
|
247
|
+
|
|
234
248
|
# spawn_session hands the first prompt over in last_prompt, but
|
|
235
249
|
# last_prompt also records every later turn's prompt (and mark_error's
|
|
236
250
|
# reason), so only a session with no conversation yet has one pending; a
|
|
@@ -255,12 +269,12 @@ module Samagotchi
|
|
|
255
269
|
# the saved conversation already holds. Not activity: a note alone
|
|
256
270
|
# neither starts a turn nor keeps an idle worker up.
|
|
257
271
|
def absorb_notes
|
|
258
|
-
files =
|
|
272
|
+
files = SessionInbox.find_new_note_files(@session_dir)
|
|
259
273
|
return if files.empty? || stopped_on_disk?
|
|
260
274
|
|
|
261
|
-
claimed = files.filter_map { |file|
|
|
275
|
+
claimed = files.filter_map { |file| SessionInbox.claim_note_file(file) }
|
|
262
276
|
claimed.each do |file|
|
|
263
|
-
note =
|
|
277
|
+
note = SessionInbox.read_note(file)
|
|
264
278
|
@engine.add_context_note(@session, note) if note
|
|
265
279
|
end
|
|
266
280
|
@session.save(state_dir: @state_dir)
|
|
@@ -268,11 +282,11 @@ module Samagotchi
|
|
|
268
282
|
end
|
|
269
283
|
|
|
270
284
|
def run_input_file(input_file)
|
|
271
|
-
claimed_file =
|
|
285
|
+
claimed_file = SessionInbox.claim_input_file(input_file)
|
|
272
286
|
return unless claimed_file
|
|
273
287
|
|
|
274
288
|
begin
|
|
275
|
-
message, origin, no_interrupt, images =
|
|
289
|
+
message, origin, no_interrupt, images = SessionInbox.read_input(claimed_file)
|
|
276
290
|
run_prompt(message, origin, no_interrupt: !!no_interrupt, images: images || []) unless message.to_s.strip.empty?
|
|
277
291
|
ensure
|
|
278
292
|
FileUtils.rm_f(claimed_file)
|
|
@@ -303,7 +317,7 @@ module Samagotchi
|
|
|
303
317
|
end
|
|
304
318
|
after_turn(result, no_interrupt: no_interrupt)
|
|
305
319
|
response = result.respond_to?(:output) ? result.output : nil
|
|
306
|
-
|
|
320
|
+
SessionInbox.write_output(@session_dir, response) unless response.nil? || response.strip.empty?
|
|
307
321
|
@session.save(state_dir: @state_dir) unless stopped_on_disk?
|
|
308
322
|
end
|
|
309
323
|
|
|
@@ -326,7 +340,7 @@ module Samagotchi
|
|
|
326
340
|
origin: { client_id: SessionManager::REMINDER_CLIENT_ID },
|
|
327
341
|
max_iterations: DEFAULT_MAX_ITERATIONS)
|
|
328
342
|
response = result.respond_to?(:output) ? result.output : nil
|
|
329
|
-
|
|
343
|
+
SessionInbox.write_output(@session_dir, response) unless response.nil? || response.strip.empty?
|
|
330
344
|
rescue StandardError
|
|
331
345
|
nil # the Engine announced :turn_failed; there is no prompt to hand back
|
|
332
346
|
ensure
|
|
@@ -462,7 +476,7 @@ module Samagotchi
|
|
|
462
476
|
end
|
|
463
477
|
after_turn(result, continue: true, no_interrupt: offer[:no_interrupt])
|
|
464
478
|
response = result.respond_to?(:output) ? result.output : nil
|
|
465
|
-
|
|
479
|
+
SessionInbox.write_output(@session_dir, response) unless response.nil? || response.strip.empty?
|
|
466
480
|
@session.save(state_dir: @state_dir) unless stopped_on_disk?
|
|
467
481
|
end
|
|
468
482
|
|
|
@@ -527,14 +541,14 @@ module Samagotchi
|
|
|
527
541
|
refuse_queued_commands(mid_turn: true)
|
|
528
542
|
# A line with images runs as its own next turn (steering merges text
|
|
529
543
|
# only), and so does anything queued after it, to keep the order.
|
|
530
|
-
files =
|
|
531
|
-
|
|
544
|
+
files = SessionInbox.find_new_input_files(@session_dir).sort
|
|
545
|
+
.take_while { |input_file| !SessionInbox.input_has_images?(input_file) }
|
|
532
546
|
merged = files.filter_map do |input_file|
|
|
533
|
-
claimed_file =
|
|
547
|
+
claimed_file = SessionInbox.claim_input_file(input_file)
|
|
534
548
|
next unless claimed_file
|
|
535
549
|
|
|
536
550
|
begin
|
|
537
|
-
prompt, origin =
|
|
551
|
+
prompt, origin = SessionInbox.read_input(claimed_file)
|
|
538
552
|
prompt = prompt.to_s.strip
|
|
539
553
|
prompt.empty? ? nil : [prompt, origin]
|
|
540
554
|
ensure
|
|
@@ -626,8 +640,8 @@ module Samagotchi
|
|
|
626
640
|
|
|
627
641
|
# Memory used before any turn saved it lives only in the Engine.
|
|
628
642
|
def empty_session?
|
|
629
|
-
SessionManager.
|
|
630
|
-
|
|
643
|
+
SessionManager.discardable?(@session_id, state_dir: @state_dir, default_model: @default_model,
|
|
644
|
+
used_memory_names: @engine.used_memory_names)
|
|
631
645
|
end
|
|
632
646
|
|
|
633
647
|
def log_idle_exit
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require_relative "atomic_file"
|
|
6
|
+
|
|
7
|
+
module Samagotchi
|
|
8
|
+
# A worker's bridge.json in its session directory: how clients reach its
|
|
9
|
+
# Bridge (port, bind), when it started (a new worker behind the same
|
|
10
|
+
# "worker" owner), the chi it runs (chi update) and the input-file format
|
|
11
|
+
# it reads (SessionInbox::INPUT_FORMAT). The Bridge writes it on start
|
|
12
|
+
# and removes it on stop; one a dead worker left stays until a client's
|
|
13
|
+
# probe finds its port closed (BridgeClient.sidecar_port).
|
|
14
|
+
class WorkerSidecar < Data.define(:port, :bind, :session_id, :started_at, :version, :input_format)
|
|
15
|
+
FILE = "bridge.json"
|
|
16
|
+
|
|
17
|
+
def self.path(session_dir) = File.join(session_dir, FILE)
|
|
18
|
+
|
|
19
|
+
# @return [WorkerSidecar, nil] nil when there is none, or it is not a
|
|
20
|
+
# JSON object; port is 0 when it names none
|
|
21
|
+
def self.read(session_dir)
|
|
22
|
+
data = JSON.parse(File.read(path(session_dir)))
|
|
23
|
+
return nil unless data.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
port = data["port"]
|
|
26
|
+
new(port: port.respond_to?(:to_i) ? port.to_i : 0, bind: data["bind"], session_id: data["session_id"],
|
|
27
|
+
started_at: data["started_at"], version: data["version"], input_format: data["input_format"])
|
|
28
|
+
rescue JSON::ParserError, SystemCallError
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(port:, bind: nil, session_id: nil, started_at: nil, version: nil, input_format: nil) = super
|
|
33
|
+
|
|
34
|
+
# Written whole or not at all; input_format only when it names one.
|
|
35
|
+
def write(session_dir)
|
|
36
|
+
FileUtils.mkdir_p(session_dir)
|
|
37
|
+
record = to_h.transform_keys(&:to_s)
|
|
38
|
+
record.delete("input_format") if input_format.nil?
|
|
39
|
+
AtomicFile.write(self.class.path(session_dir), "#{JSON.pretty_generate(record)}\n")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
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.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -139,6 +139,8 @@ files:
|
|
|
139
139
|
- lib/samagotchi.rb
|
|
140
140
|
- lib/samagotchi/answer_display.rb
|
|
141
141
|
- lib/samagotchi/archive_store.rb
|
|
142
|
+
- lib/samagotchi/atomic_file.rb
|
|
143
|
+
- lib/samagotchi/bootstrap/bundles.rb
|
|
142
144
|
- lib/samagotchi/bootstrap/config_writer.rb
|
|
143
145
|
- lib/samagotchi/bootstrap/probe.rb
|
|
144
146
|
- lib/samagotchi/bootstrap_command.rb
|
|
@@ -160,6 +162,8 @@ files:
|
|
|
160
162
|
- lib/samagotchi/bundles/btw/plugin.rb
|
|
161
163
|
- lib/samagotchi/bundles/check-in/manifest.yml
|
|
162
164
|
- lib/samagotchi/bundles/check-in/plugin.rb
|
|
165
|
+
- lib/samagotchi/bundles/core/manifest.yml
|
|
166
|
+
- lib/samagotchi/bundles/dev/manifest.yml
|
|
163
167
|
- lib/samagotchi/bundles/guardrails/guardrails.md
|
|
164
168
|
- lib/samagotchi/bundles/guardrails/guardrails/rules.yml
|
|
165
169
|
- lib/samagotchi/bundles/guardrails/manifest.yml
|
|
@@ -187,6 +191,7 @@ files:
|
|
|
187
191
|
- lib/samagotchi/config.rb
|
|
188
192
|
- lib/samagotchi/context_note.rb
|
|
189
193
|
- lib/samagotchi/context_quote.rb
|
|
194
|
+
- lib/samagotchi/context_status.rb
|
|
190
195
|
- lib/samagotchi/context_usage.rb
|
|
191
196
|
- lib/samagotchi/context_window.rb
|
|
192
197
|
- lib/samagotchi/debug_log.rb
|
|
@@ -242,6 +247,7 @@ files:
|
|
|
242
247
|
- lib/samagotchi/llm/native_tool_normalizer.rb
|
|
243
248
|
- lib/samagotchi/llm/openai_chat.rb
|
|
244
249
|
- lib/samagotchi/llm/usage.rb
|
|
250
|
+
- lib/samagotchi/llm/utf8_scrub.rb
|
|
245
251
|
- lib/samagotchi/log.rb
|
|
246
252
|
- lib/samagotchi/log_line.rb
|
|
247
253
|
- lib/samagotchi/log_path.rb
|
|
@@ -254,6 +260,7 @@ files:
|
|
|
254
260
|
- lib/samagotchi/memory_bundle/manifest.rb
|
|
255
261
|
- lib/samagotchi/memory_bundle/merger.rb
|
|
256
262
|
- lib/samagotchi/memory_bundle/placeholder.rb
|
|
263
|
+
- lib/samagotchi/memory_bundle/profile.rb
|
|
257
264
|
- lib/samagotchi/memory_bundle/provenance.rb
|
|
258
265
|
- lib/samagotchi/memory_bundle/shipped_update.rb
|
|
259
266
|
- lib/samagotchi/memory_bundle/source.rb
|
|
@@ -267,6 +274,7 @@ files:
|
|
|
267
274
|
- lib/samagotchi/note_command.rb
|
|
268
275
|
- lib/samagotchi/output_formatter.rb
|
|
269
276
|
- lib/samagotchi/owner_lock.rb
|
|
277
|
+
- lib/samagotchi/paths.rb
|
|
270
278
|
- lib/samagotchi/pending_input_queue.rb
|
|
271
279
|
- lib/samagotchi/plugin/api.rb
|
|
272
280
|
- lib/samagotchi/plugin/context.rb
|
|
@@ -292,9 +300,11 @@ files:
|
|
|
292
300
|
- lib/samagotchi/session_archive_command.rb
|
|
293
301
|
- lib/samagotchi/session_commands.rb
|
|
294
302
|
- lib/samagotchi/session_delete_command.rb
|
|
303
|
+
- lib/samagotchi/session_inbox.rb
|
|
295
304
|
- lib/samagotchi/session_manager.rb
|
|
296
305
|
- lib/samagotchi/session_metrics.rb
|
|
297
306
|
- lib/samagotchi/session_observer.rb
|
|
307
|
+
- lib/samagotchi/session_retention.rb
|
|
298
308
|
- lib/samagotchi/sessions_command.rb
|
|
299
309
|
- lib/samagotchi/steer.rb
|
|
300
310
|
- lib/samagotchi/system_prompt.rb
|
|
@@ -362,6 +372,7 @@ files:
|
|
|
362
372
|
- lib/samagotchi/vision_context.rb
|
|
363
373
|
- lib/samagotchi/vision_support.rb
|
|
364
374
|
- lib/samagotchi/web/app.rb
|
|
375
|
+
- lib/samagotchi/web/info_probe.rb
|
|
365
376
|
- lib/samagotchi/web/lan.rb
|
|
366
377
|
- lib/samagotchi/web/markdown_renderer.rb
|
|
367
378
|
- lib/samagotchi/web/message_parts.rb
|
|
@@ -407,6 +418,7 @@ files:
|
|
|
407
418
|
- lib/samagotchi/web/token.rb
|
|
408
419
|
- lib/samagotchi/worker.rb
|
|
409
420
|
- lib/samagotchi/worker_idle_exit.rb
|
|
421
|
+
- lib/samagotchi/worker_sidecar.rb
|
|
410
422
|
homepage: https://github.com/dm1try/samagotchi
|
|
411
423
|
licenses:
|
|
412
424
|
- MIT
|