samagotchi 0.156.0 → 0.158.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 +70 -1
- data/README.md +1 -1
- data/bin/chi +4 -1
- data/docs/architecture.md +18 -12
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +4 -1
- data/docs/cli.md +29 -24
- data/docs/configuration.md +43 -9
- data/docs/context.md +5 -3
- data/docs/guardrails.md +12 -7
- data/docs/hooks.md +53 -83
- data/docs/internals/bridge.md +84 -7
- data/docs/internals/bundles.md +69 -46
- data/docs/internals/context-telemetry.md +4 -3
- data/docs/internals/engine.md +23 -7
- data/docs/internals/participants.md +3 -2
- data/docs/internals/remote-sessions.md +5 -2
- data/docs/internals/web-frontend.md +3 -2
- data/docs/internals/working-set.md +1 -1
- data/docs/memory.md +51 -10
- data/docs/plugins.md +48 -11
- data/docs/releasing.md +6 -5
- data/docs/sessions.md +13 -13
- data/docs/sub-agent.md +17 -2
- data/docs/testing.md +56 -5
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +4 -3
- data/lib/samagotchi/bootstrap_command.rb +3 -2
- data/lib/samagotchi/bridge/card_places.rb +5 -7
- data/lib/samagotchi/bridge/card_store.rb +3 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +113 -10
- data/lib/samagotchi/bridge/turn_drawings.rb +271 -0
- data/lib/samagotchi/bridge.rb +31 -6
- data/lib/samagotchi/broadcast/recipients.rb +8 -8
- data/lib/samagotchi/broadcast/triage_model.rb +1 -1
- data/lib/samagotchi/bundle_command/check.rb +41 -10
- data/lib/samagotchi/bundle_command/installed_check.rb +13 -2
- data/lib/samagotchi/bundle_command/try/run.rb +4 -1
- data/lib/samagotchi/bundle_command/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +20 -45
- data/lib/samagotchi/bundles/known-names/manifest.yml +4 -7
- data/lib/samagotchi/bundles/known-names/{hooks/known_names.rb → plugin.rb} +17 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +4 -7
- data/lib/samagotchi/bundles/source-links/{hooks/source_links.rb → plugin.rb} +23 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +7 -7
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/child_move.rb +6 -6
- data/lib/samagotchi/child_reports.rb +2 -2
- data/lib/samagotchi/children_status.rb +15 -21
- data/lib/samagotchi/cli/parent_wait.rb +8 -22
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/context_command.rb +7 -5
- data/lib/samagotchi/context_sources.rb +9 -6
- data/lib/samagotchi/context_window.rb +9 -12
- data/lib/samagotchi/effective_model.rb +57 -22
- data/lib/samagotchi/engine/plugins.rb +1 -1
- data/lib/samagotchi/engine/recap.rb +8 -4
- data/lib/samagotchi/engine.rb +30 -9
- data/lib/samagotchi/events.rb +3 -2
- data/lib/samagotchi/extension_load.rb +26 -44
- data/lib/samagotchi/guardrails/load_failures.rb +3 -2
- data/lib/samagotchi/guardrails/path_resolve.rb +11 -0
- data/lib/samagotchi/guardrails/review_paths.rb +1 -5
- data/lib/samagotchi/guardrails_command.rb +1 -1
- data/lib/samagotchi/hook_routes.rb +2 -2
- data/lib/samagotchi/hooks.rb +2 -3
- data/lib/samagotchi/host_address.rb +52 -0
- data/lib/samagotchi/host_registry.rb +57 -45
- data/lib/samagotchi/kernel_loop.rb +7 -3
- data/lib/samagotchi/llm/chat_loop.rb +6 -3
- data/lib/samagotchi/llm_context_strategy.rb +27 -63
- data/lib/samagotchi/memory_bundle/asset_installer.rb +92 -177
- data/lib/samagotchi/memory_bundle/builder.rb +12 -35
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
- data/lib/samagotchi/memory_bundle/file_actions.rb +193 -0
- data/lib/samagotchi/memory_bundle/file_digest.rb +13 -0
- data/lib/samagotchi/memory_bundle/file_state.rb +152 -0
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
- data/lib/samagotchi/memory_bundle/installed_copy.rb +13 -19
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +10 -17
- data/lib/samagotchi/memory_bundle/installer.rb +212 -178
- data/lib/samagotchi/memory_bundle/manifest.rb +51 -58
- data/lib/samagotchi/memory_bundle/merger.rb +69 -65
- data/lib/samagotchi/memory_bundle/plugin_ref.rb +6 -5
- data/lib/samagotchi/memory_bundle/profile.rb +5 -5
- data/lib/samagotchi/memory_bundle/provenance.rb +26 -52
- data/lib/samagotchi/memory_bundle/remerge.rb +31 -14
- data/lib/samagotchi/memory_bundle/shas.rb +5 -8
- data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -11
- data/lib/samagotchi/memory_bundle/status.rb +26 -30
- data/lib/samagotchi/memory_bundle/system_bundle.rb +39 -10
- data/lib/samagotchi/memory_bundle/trash.rb +28 -18
- data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
- data/lib/samagotchi/memory_orphans.rb +4 -4
- data/lib/samagotchi/memory_paths.rb +102 -13
- data/lib/samagotchi/memory_usage.rb +1 -1
- data/lib/samagotchi/model_lookup.rb +75 -0
- data/lib/samagotchi/model_profile.rb +1 -0
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/participant_mode.rb +2 -1
- data/lib/samagotchi/plugin/api.rb +42 -10
- data/lib/samagotchi/plugin/loader.rb +75 -15
- data/lib/samagotchi/plugin/sessions.rb +1 -1
- data/lib/samagotchi/project_command.rb +167 -0
- data/lib/samagotchi/project_init.rb +69 -0
- data/lib/samagotchi/project_scope.rb +6 -4
- data/lib/samagotchi/prompt_files.rb +10 -7
- data/lib/samagotchi/reasoning_history.rb +14 -20
- data/lib/samagotchi/recap_place.rb +26 -0
- data/lib/samagotchi/reply_wait/liveness.rb +38 -0
- data/lib/samagotchi/reply_wait/poll.rb +44 -15
- data/lib/samagotchi/reply_wait/target.rb +113 -0
- data/lib/samagotchi/reply_wait.rb +22 -2
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/sampling_settings.rb +9 -16
- data/lib/samagotchi/self_report.rb +34 -35
- data/lib/samagotchi/send_command.rb +17 -23
- data/lib/samagotchi/session.rb +349 -441
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +5 -6
- data/lib/samagotchi/session_access/remote.rb +5 -3
- data/lib/samagotchi/session_field.rb +134 -0
- data/lib/samagotchi/session_inbox.rb +49 -1
- data/lib/samagotchi/session_manager/emptiness.rb +2 -1
- data/lib/samagotchi/session_manager/listing.rb +29 -100
- data/lib/samagotchi/session_manager.rb +2 -1
- data/lib/samagotchi/session_metrics.rb +7 -4
- data/lib/samagotchi/session_paths.rb +57 -0
- data/lib/samagotchi/session_retention.rb +2 -2
- data/lib/samagotchi/session_row.rb +200 -0
- data/lib/samagotchi/session_setup.rb +15 -12
- data/lib/samagotchi/sessions_command.rb +11 -129
- data/lib/samagotchi/steer.rb +10 -0
- data/lib/samagotchi/steer_cut_after.rb +7 -14
- data/lib/samagotchi/system_prompt.rb +20 -11
- data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
- data/lib/samagotchi/terminal_ui/join_replay.rb +91 -8
- data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
- data/lib/samagotchi/terminal_ui.rb +3 -1
- data/lib/samagotchi/testing/bundle.rb +5 -12
- data/lib/samagotchi/testing/install.rb +5 -4
- data/lib/samagotchi/testing/recorder.rb +1 -1
- data/lib/samagotchi/testing.rb +1 -1
- data/lib/samagotchi/thinking.rb +16 -21
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tool_response.rb +5 -3
- data/lib/samagotchi/tool_runner.rb +18 -1
- data/lib/samagotchi/toolkit_binding.rb +1 -4
- data/lib/samagotchi/toolkit_command.rb +1 -4
- data/lib/samagotchi/tools/delegate.rb +60 -51
- data/lib/samagotchi/tools/delegate_relay.rb +5 -5
- data/lib/samagotchi/tools/delegate_result.rb +1 -1
- data/lib/samagotchi/tools/delegate_wait.rb +41 -17
- data/lib/samagotchi/tools/list_sessions.rb +7 -7
- data/lib/samagotchi/update_command.rb +11 -7
- data/lib/samagotchi/verify_fresh.rb +7 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +11 -14
- data/lib/samagotchi/web/app.rb +66 -21
- data/lib/samagotchi/web/message_display.rb +57 -7
- data/lib/samagotchi/web/participant_access.rb +1 -1
- data/lib/samagotchi/web/project_list.rb +3 -2
- data/lib/samagotchi/web/public/activity.js +16 -7
- data/lib/samagotchi/web/public/app.js +29 -8
- data/lib/samagotchi/web/public/bubbles.js +8 -3
- data/lib/samagotchi/web/public/cards_place.js +15 -0
- data/lib/samagotchi/web/public/commands.js +5 -1
- data/lib/samagotchi/web/public/data.js +4 -2
- data/lib/samagotchi/web/public/history_shape.js +220 -0
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/project_picker.js +86 -4
- data/lib/samagotchi/web/public/recap_stamp.js +17 -0
- data/lib/samagotchi/web/public/reload_check.js +71 -0
- data/lib/samagotchi/web/public/stage_model.js +7 -5
- data/lib/samagotchi/web/public/stage_view.js +7 -3
- data/lib/samagotchi/web/public/stream_handlers.js +8 -2
- data/lib/samagotchi/web/public/timing.js +43 -2
- data/lib/samagotchi/web/public/turn_block.js +215 -30
- data/lib/samagotchi/web/public/turn_drawing.js +43 -0
- data/lib/samagotchi/web/public/turn_events.js +10 -1
- data/lib/samagotchi/web/public/turn_pieces.js +29 -0
- data/lib/samagotchi/web/reload_check_route.rb +95 -0
- data/lib/samagotchi/web/server.rb +4 -2
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +9 -17
- data/lib/samagotchi/web/session_show.rb +64 -2
- data/lib/samagotchi/web/session_summary.rb +36 -24
- data/lib/samagotchi/worker.rb +28 -5
- data/lib/samagotchi/worker_link.rb +3 -0
- metadata +22 -5
- data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
- data/lib/samagotchi/send_command/wait_target.rb +0 -85
|
@@ -10,6 +10,7 @@ require_relative "../guardrails/context"
|
|
|
10
10
|
require_relative "peers"
|
|
11
11
|
require_relative "delegate_wait"
|
|
12
12
|
require_relative "../child_reports"
|
|
13
|
+
require_relative "../worker_link"
|
|
13
14
|
|
|
14
15
|
module Samagotchi
|
|
15
16
|
# Loaded on first use: this autoload keeps SessionManager and its ~80
|
|
@@ -32,11 +33,6 @@ module Samagotchi
|
|
|
32
33
|
# A path in a task: absolute, ~/ or ../, not glued to a word (a URL's
|
|
33
34
|
# /api is not one) and stopping at quotes, backticks or punctuation.
|
|
34
35
|
PATH_TOKEN = %r{(?<![\w./~-])(?:~/|\.\./|/(?!/))[^\s`'"<>()\[\]{},;&|]*}
|
|
35
|
-
# A child spawned this many seconds ago counts as running even before
|
|
36
|
-
# its worker holds the owner lock: two delegate calls in one model
|
|
37
|
-
# message come milliseconds apart, and the first child's worker is
|
|
38
|
-
# still starting when the second call counts.
|
|
39
|
-
STARTING_GRACE_SECONDS = 15
|
|
40
36
|
# The memory every child starts with, scoped so a project memory of
|
|
41
37
|
# the same name cannot shadow it.
|
|
42
38
|
CHILD_MEMORIES = [Session::DELEGATE_MEMORY].freeze
|
|
@@ -79,7 +75,11 @@ module Samagotchi
|
|
|
79
75
|
|
|
80
76
|
note = Array(warnings).map { |warning| "Warning: #{warning}\n" }.join
|
|
81
77
|
if wait
|
|
82
|
-
|
|
78
|
+
started = monotonic
|
|
79
|
+
boot = await_worker(child_id, state_dir: sd, timeout: timeout, cancelled: -> { peers.cancelled? })
|
|
80
|
+
return note + DelegateWait.boot_result(child_id, boot, timeout: timeout, peers: peers) unless boot == :up
|
|
81
|
+
|
|
82
|
+
result = DelegateWait.call(child_id, peers: peers, timeout: timeout, waited: monotonic - started)
|
|
83
83
|
return RefText.wrap(note + result, result.respond_to?(:ref_path) ? result.ref_path : nil)
|
|
84
84
|
end
|
|
85
85
|
|
|
@@ -90,6 +90,32 @@ module Samagotchi
|
|
|
90
90
|
"Error: #{e.message}"
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
# Before a wait on a child this call started or woke: its worker
|
|
94
|
+
# takes the owner lock only once it has booted, so the wait's one
|
|
95
|
+
# dead-worker rule (ReplyWait::GONE_AFTER) runs from there. A child a
|
|
96
|
+
# worker (or a one-turn run) owns already goes straight on; else its
|
|
97
|
+
# worker gets WorkerLink::START_LIMIT, the boot limit, to come up,
|
|
98
|
+
# within the call's +timeout+ (its time counts against it) and
|
|
99
|
+
# until the parent's turn is canceled.
|
|
100
|
+
# @param cancelled [#call] the parent's cancel (Peers#cancelled?)
|
|
101
|
+
# @return [Symbol] :up (a worker owns it), :gone (none came up within
|
|
102
|
+
# the boot limit), :timeout (+timeout+ ran out first) or :canceled
|
|
103
|
+
def self.await_worker(child_id, state_dir:, timeout:, cancelled: -> { false })
|
|
104
|
+
dir = Session.session_dir(child_id, state_dir: state_dir)
|
|
105
|
+
return :up if OwnerLock.owner(dir)
|
|
106
|
+
|
|
107
|
+
limit = [WorkerLink::START_LIMIT, timeout.to_f].min
|
|
108
|
+
link = WorkerLink.await(child_id, session_dir: dir, timeout: limit, unlink: false,
|
|
109
|
+
give_up: ->(found) { found.worker_owned? || cancelled.call })
|
|
110
|
+
return :up if link&.worker_owned?
|
|
111
|
+
return :canceled if cancelled.call
|
|
112
|
+
|
|
113
|
+
limit < WorkerLink::START_LIMIT ? :timeout : :gone
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
117
|
+
private_class_method :monotonic
|
|
118
|
+
|
|
93
119
|
# How the reply of a child left running comes back: a report only
|
|
94
120
|
# reaches a parent that can get one (DelegateWait.reports_mode).
|
|
95
121
|
def self.running_hint(peers)
|
|
@@ -135,7 +161,7 @@ module Samagotchi
|
|
|
135
161
|
running = running_children(parent.id, state_dir: state_dir)
|
|
136
162
|
max = max_children
|
|
137
163
|
if running.size >= max
|
|
138
|
-
ids = running.map
|
|
164
|
+
ids = running.map(&:short_id).join(", ")
|
|
139
165
|
return "Error: #{running.size} delegate#{"s" if running.size != 1} of this session #{running.size == 1 ? "is" : "are"} running " \
|
|
140
166
|
"(the most is #{max}, #{MAX_CHILDREN_KEY}): #{ids}. #{full_hint(peers)}"
|
|
141
167
|
end
|
|
@@ -255,9 +281,10 @@ module Samagotchi
|
|
|
255
281
|
private_class_method :unknown_toolkit
|
|
256
282
|
|
|
257
283
|
# The new child's folder: the parent's, or +cwd+ (relative to the
|
|
258
|
-
# parent's) when it is a folder of the parent's
|
|
259
|
-
#
|
|
260
|
-
#
|
|
284
|
+
# parent's) when it is a folder of the parent's project: in any of its
|
|
285
|
+
# repository's worktrees (one project_root, from the common git dir), or
|
|
286
|
+
# in its marked folder. Outside both only a subfolder of the parent's
|
|
287
|
+
# folder. Nothing is created
|
|
261
288
|
# here: the model makes a worktree with execute, where guardrails see it.
|
|
262
289
|
# @return [String] an absolute folder, or an Error: line
|
|
263
290
|
def self.child_cwd(cwd, parent)
|
|
@@ -268,8 +295,8 @@ module Samagotchi
|
|
|
268
295
|
return "Error: cwd #{folder} is not a folder (create the worktree first)" unless File.directory?(folder)
|
|
269
296
|
|
|
270
297
|
folder = File.realpath(folder)
|
|
271
|
-
unless
|
|
272
|
-
return "Error: cwd must be a folder of this session's
|
|
298
|
+
unless same_project?(folder, base)
|
|
299
|
+
return "Error: cwd must be a folder of this session's project (a worktree or subfolder of it), not #{folder}"
|
|
273
300
|
end
|
|
274
301
|
if MemoryPaths.in_repo?(base) && !work_tree?(folder)
|
|
275
302
|
return "Error: cwd #{folder} is not in a work tree of this session's repository (a git dir, or a bare " \
|
|
@@ -301,8 +328,11 @@ module Samagotchi
|
|
|
301
328
|
def self.within?(path, dir) = path == dir || path.start_with?("#{dir}/")
|
|
302
329
|
private_class_method :within?
|
|
303
330
|
|
|
304
|
-
|
|
305
|
-
|
|
331
|
+
# One project (a git repository or a marked folder): the same project
|
|
332
|
+
# root, so a nested repository or marked project is another one. A base
|
|
333
|
+
# in no project: a subfolder of it.
|
|
334
|
+
def self.same_project?(folder, base)
|
|
335
|
+
if MemoryPaths.in_project?(base)
|
|
306
336
|
MemoryPaths.project_root(folder) == MemoryPaths.project_root(base)
|
|
307
337
|
else
|
|
308
338
|
within?(folder, File.realpath(base))
|
|
@@ -310,7 +340,7 @@ module Samagotchi
|
|
|
310
340
|
rescue SystemCallError
|
|
311
341
|
false
|
|
312
342
|
end
|
|
313
|
-
private_class_method :
|
|
343
|
+
private_class_method :same_project?
|
|
314
344
|
|
|
315
345
|
# @return [String] the child's id, or an Error: line
|
|
316
346
|
def self.follow_up(task, session:, parent:, state_dir:)
|
|
@@ -349,28 +379,17 @@ module Samagotchi
|
|
|
349
379
|
end
|
|
350
380
|
private_class_method :follow_up
|
|
351
381
|
|
|
352
|
-
# The child's turn a delivered follow-up
|
|
353
|
-
# (DelegateCursor#turn): the
|
|
354
|
-
#
|
|
355
|
-
#
|
|
356
|
-
#
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
360
|
-
# its own turn, yet the wait is on the ended one (LOW, as before).
|
|
361
|
-
# One the child's idle look missed being merged into a turn starting
|
|
362
|
-
# then never runs as its own: ReplyWait falls back to that turn's
|
|
363
|
-
# reply (merged_elsewhere?).
|
|
382
|
+
# The child's turn a delivered follow-up's wait is bound to
|
|
383
|
+
# (DelegateCursor#turn): its own, the delivery's +enqueued_id+,
|
|
384
|
+
# whether it runs as a turn of its own (the child idle, or its
|
|
385
|
+
# running turn ended before taking it) or a running turn's drain
|
|
386
|
+
# merges it (that turn's end is its answer: the drain's merge record,
|
|
387
|
+
# or ReplyWait's inference for a 0.156 child, ReplyWait::Poll#
|
|
388
|
+
# follow_merge). Only from a worker that names turn_ids; nil
|
|
389
|
+
# otherwise: the wait takes the next reply.
|
|
364
390
|
# @return [String, nil]
|
|
365
391
|
def self.follow_up_turn(id, enqueued_id, state_dir:)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
child = Session.load(id, state_dir: state_dir)
|
|
369
|
-
return child.interrupted_turn&.enqueued_id if child.status == Session::STATUS_RUNNING
|
|
370
|
-
|
|
371
|
-
enqueued_id
|
|
372
|
-
rescue ArgumentError
|
|
373
|
-
nil
|
|
392
|
+
ReplyWait.turn_ids?(Session.session_dir(id, state_dir: state_dir)) ? enqueued_id : nil
|
|
374
393
|
end
|
|
375
394
|
private_class_method :follow_up_turn
|
|
376
395
|
|
|
@@ -388,23 +407,13 @@ module Samagotchi
|
|
|
388
407
|
|
|
389
408
|
# The children of +parent_id+ that count against session.max_children
|
|
390
409
|
# (a plugin's ctx.sessions.fork counts them too).
|
|
391
|
-
#
|
|
410
|
+
# A child spawned moments ago counts as running even before its worker
|
|
411
|
+
# holds the owner lock (SessionRow#running_or_starting?): two delegate
|
|
412
|
+
# calls in one model message come milliseconds apart, and the first
|
|
413
|
+
# child's worker is still starting when the second call counts.
|
|
414
|
+
# @return [Array<SessionRow>] SessionManager.children_of rows
|
|
392
415
|
def self.running_children(parent_id, state_dir:)
|
|
393
|
-
SessionManager.children_of(parent_id, state_dir: state_dir).select
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
# busy (a worker runs its turn), or running with a worker on its way
|
|
397
|
-
# (ChildrenStatus asks it too).
|
|
398
|
-
# @param summary [Hash] a children_of row, or the same keys: busy,
|
|
399
|
-
# saved_status (the file's: the displayed status shows a worker on
|
|
400
|
-
# its way as idle), live, updated_at
|
|
401
|
-
def self.running?(summary)
|
|
402
|
-
return true if summary[:busy]
|
|
403
|
-
return false unless summary[:saved_status] == Session::STATUS_RUNNING && !summary[:live]
|
|
404
|
-
|
|
405
|
-
Time.now - Time.iso8601(summary[:updated_at].to_s) < STARTING_GRACE_SECONDS
|
|
406
|
-
rescue ArgumentError
|
|
407
|
-
false
|
|
416
|
+
SessionManager.children_of(parent_id, state_dir: state_dir).select(&:running_or_starting?)
|
|
408
417
|
end
|
|
409
418
|
|
|
410
419
|
def self.max_children
|
|
@@ -262,16 +262,16 @@ module Samagotchi
|
|
|
262
262
|
# oldest question first
|
|
263
263
|
def waiting
|
|
264
264
|
rows = SessionManager.children_of(@parent_id, state_dir: @state_dir).select do |row|
|
|
265
|
-
row
|
|
265
|
+
row.id != @except && KINDS.include?(row.waiting.to_s) && !@done[row.waiting_id.to_s]
|
|
266
266
|
end
|
|
267
267
|
rows.filter_map do |row|
|
|
268
|
-
child = Session.load(row
|
|
268
|
+
child = Session.load(row.id, state_dir: @state_dir)
|
|
269
269
|
pending = child.pending_question
|
|
270
|
-
next nil unless pending && pending.id.to_s == row
|
|
270
|
+
next nil unless pending && pending.id.to_s == row.waiting_id.to_s
|
|
271
271
|
next nil unless DelegateRelay.relayable?(@relay, pending, strategy: Guardrails::ParentPolicy.for_session(child),
|
|
272
|
-
child_id: row
|
|
272
|
+
child_id: row.id)
|
|
273
273
|
|
|
274
|
-
[row
|
|
274
|
+
[row.id, pending]
|
|
275
275
|
rescue ArgumentError
|
|
276
276
|
nil
|
|
277
277
|
end.sort_by { |_, pending| pending.created_at.to_s }
|
|
@@ -45,7 +45,7 @@ module Samagotchi
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def self.newest_running_child(parent_id, state_dir:)
|
|
48
|
-
SessionManager.children_of(parent_id, state_dir: state_dir).find
|
|
48
|
+
SessionManager.children_of(parent_id, state_dir: state_dir).find(&:busy)&.id
|
|
49
49
|
end
|
|
50
50
|
private_class_method :newest_running_child
|
|
51
51
|
end
|
|
@@ -8,6 +8,7 @@ require_relative "peers"
|
|
|
8
8
|
require_relative "delegate_relay"
|
|
9
9
|
require_relative "delegate_cursor"
|
|
10
10
|
require_relative "ref_text"
|
|
11
|
+
require_relative "../worker_link"
|
|
11
12
|
|
|
12
13
|
module Samagotchi
|
|
13
14
|
# Loaded on first use: child_reports requires this file (a circular
|
|
@@ -27,10 +28,6 @@ module Samagotchi
|
|
|
27
28
|
Advance = Data.define(:cursor, :already_reported)
|
|
28
29
|
TIMEOUT_DEFAULT = 600
|
|
29
30
|
POLL_INTERVAL = 0.5
|
|
30
|
-
# Seconds with no worker before the child counts as gone: as long as
|
|
31
|
-
# Delegate gives a starting child (its worker takes the owner lock
|
|
32
|
-
# only once it has booted).
|
|
33
|
-
OWNER_GRACE = 15
|
|
34
31
|
# Cut like an execute result: full up to TRUNCATE_AT bytes, else a
|
|
35
32
|
# head-and-tail preview of PREVIEW bytes.
|
|
36
33
|
TRUNCATE_AT_BYTES = OutputGuardrails::DEFAULT_TRUNCATE_AT_BYTES
|
|
@@ -50,7 +47,9 @@ module Samagotchi
|
|
|
50
47
|
# @param timeout [Integer] seconds
|
|
51
48
|
# @param poll_interval [Float]
|
|
52
49
|
# @param owner_grace [Numeric] seconds with no live worker before
|
|
53
|
-
# the child's worker counts as gone
|
|
50
|
+
# the child's worker counts as gone (ReplyWait::GONE_AFTER; a child
|
|
51
|
+
# whose worker is on its way was awaited first:
|
|
52
|
+
# Delegate.await_worker)
|
|
54
53
|
# @return [String] the tool result
|
|
55
54
|
#
|
|
56
55
|
# A child's approval, when this session can host a relay (Peers#relay),
|
|
@@ -59,7 +58,10 @@ module Samagotchi
|
|
|
59
58
|
# against +timeout+, and the result gets one outcome line per relayed
|
|
60
59
|
# approval. Without a relay, and for the model's and hooks' questions,
|
|
61
60
|
# the question comes back to the model as before.
|
|
62
|
-
|
|
61
|
+
# @param waited [Numeric] seconds of +timeout+ already spent before
|
|
62
|
+
# this wait (Delegate's boot await); the result still names +timeout+
|
|
63
|
+
def call(child_id, peers:, timeout: TIMEOUT_DEFAULT, poll_interval: POLL_INTERVAL, owner_grace: ReplyWait::GONE_AFTER,
|
|
64
|
+
waited: 0)
|
|
63
65
|
sd = peers.state_dir || Session.default_state_dir
|
|
64
66
|
key = [peers.session_id, child_id]
|
|
65
67
|
cancelled = -> { peers.cancelled? }
|
|
@@ -73,15 +75,15 @@ module Samagotchi
|
|
|
73
75
|
cursor = report_claim.cursor.with(report_claim: report_claim)
|
|
74
76
|
end
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
target = ReplyWait::Target.from_cursor(cursor, child: Session.summary_from_file(Session.session_file(child_id, state_dir: sd)),
|
|
79
|
+
owner_grace: owner_grace)
|
|
80
|
+
left = timeout.to_i - waited
|
|
78
81
|
outcomes = relayed_outcomes.delete(key) || []
|
|
79
82
|
loop do
|
|
80
83
|
started = monotonic
|
|
81
|
-
wait = ReplyWait.call(child_id, state_dir: sd,
|
|
82
|
-
poll_interval: poll_interval, cancelled: cancelled,
|
|
83
|
-
|
|
84
|
-
turn: cursor.turn, message_waiting: message_waiting)
|
|
84
|
+
wait = ReplyWait.call(child_id, state_dir: sd, **target.to_h, timeout: [left, 0].max,
|
|
85
|
+
poll_interval: poll_interval, cancelled: cancelled,
|
|
86
|
+
interject: others && -> { others.poll }, message_waiting: message_waiting)
|
|
85
87
|
left -= monotonic - started
|
|
86
88
|
unless wait.status == :waiting_for_answer &&
|
|
87
89
|
DelegateRelay.relayable?(relay, wait.question, strategy: Guardrails::ParentPolicy.for_session(wait.session),
|
|
@@ -108,7 +110,7 @@ module Samagotchi
|
|
|
108
110
|
# asked), the relayed question no longer reported: a turn that
|
|
109
111
|
# ends with no reply during the relay's POST or after it still
|
|
110
112
|
# ends the wait.
|
|
111
|
-
|
|
113
|
+
target = target.with(baseline: ReplyWait.baseline_of(wait.session, question_id: wait.question.id))
|
|
112
114
|
end
|
|
113
115
|
rescue ArgumentError => e
|
|
114
116
|
"Error: #{e.message}"
|
|
@@ -179,6 +181,10 @@ module Samagotchi
|
|
|
179
181
|
|
|
180
182
|
def monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
181
183
|
|
|
184
|
+
# A worker_gone wait's outcome when the child's worker never came up
|
|
185
|
+
# (#boot_result).
|
|
186
|
+
DID_NOT_START = "did_not_start"
|
|
187
|
+
|
|
182
188
|
REPORT_NEXT_REPLY = "chi brings the child's next reply here by itself; don't wait for it with delegate_result."
|
|
183
189
|
|
|
184
190
|
# Outcomes that tell the parent something about the child's state.
|
|
@@ -254,8 +260,8 @@ module Samagotchi
|
|
|
254
260
|
when :no_reply
|
|
255
261
|
result(child_id, status, "#{no_reply_text(wait, child_id, strategy: strategy)}; its session shows what happened")
|
|
256
262
|
when :worker_gone
|
|
257
|
-
|
|
258
|
-
|
|
263
|
+
gone = wait.outcome == DID_NOT_START ? WorkerLink.did_not_start : "the child's worker is gone (it stopped or crashed)"
|
|
264
|
+
result(child_id, status, "#{gone}; delegate with session: #{child_id} starts it again with a message")
|
|
259
265
|
else
|
|
260
266
|
timeout_result(child_id, timeout, reports: reports)
|
|
261
267
|
end
|
|
@@ -280,11 +286,29 @@ module Samagotchi
|
|
|
280
286
|
DelegateCursors.update(parent_id, child_id, state_dir: state_dir) { |cursor| cursor.with(turn: turn) }
|
|
281
287
|
end
|
|
282
288
|
|
|
283
|
-
# A new child's baseline: as spawned, before its first turn ran
|
|
289
|
+
# A new child's baseline: as spawned, before its first turn ran, and
|
|
290
|
+
# its first turn (Session#first_turn_id, the prompt's turn) as the
|
|
291
|
+
# one waited for: queued (ReplyWait::Target.from_cursor), so the
|
|
292
|
+
# window between its worker going live and that turn's mark doesn't
|
|
293
|
+
# read as a merge into another turn.
|
|
284
294
|
# @param child [Session] the session spawn_session returned
|
|
285
295
|
def mark_started(parent_id, child, state_dir:)
|
|
286
296
|
DelegateCursors.update(parent_id, child.id, state_dir: state_dir) do |cursor|
|
|
287
|
-
cursor.with(turn:
|
|
297
|
+
cursor.with(turn: child.first_turn_id).with_baseline(ReplyWait.baseline_of(child, question_id: nil))
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# How Delegate's boot await ended when no worker came up
|
|
302
|
+
# (Delegate.await_worker): :gone is worker_gone ("its worker did not
|
|
303
|
+
# start"), :timeout and :canceled read as a wait's own.
|
|
304
|
+
# @param boot [Symbol] :gone, :timeout or :canceled
|
|
305
|
+
def boot_result(child_id, boot, timeout:, peers:)
|
|
306
|
+
reports = reports_mode(peers)
|
|
307
|
+
case boot
|
|
308
|
+
when :canceled then canceled_result(child_id, reports: reports)
|
|
309
|
+
when :timeout then timeout_result(child_id, timeout, reports: reports)
|
|
310
|
+
else finish(ReplyWait::Result.new(status: :worker_gone, outcome: DID_NOT_START), child_id, timeout: timeout,
|
|
311
|
+
reports: reports)
|
|
288
312
|
end
|
|
289
313
|
end
|
|
290
314
|
|
|
@@ -31,9 +31,9 @@ module Samagotchi
|
|
|
31
31
|
folder = cwd.to_s.strip.empty? ? nil : File.expand_path(cwd.to_s.strip)
|
|
32
32
|
own = own_session(peers)
|
|
33
33
|
project = folder ? nil : own_project(peers, own)
|
|
34
|
-
summaries = SessionManager.
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
summaries = SessionManager.session_rows(cwd: folder, limit: LIMIT, include_tests: false,
|
|
35
|
+
exclude: peers.session_id, state_dir: peers.state_dir,
|
|
36
|
+
project_root: project)
|
|
37
37
|
if summaries.empty?
|
|
38
38
|
return "No other chi sessions in this project (#{File.basename(project)}); cwd \"/\" lists every project's." if project
|
|
39
39
|
|
|
@@ -41,12 +41,12 @@ module Samagotchi
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
lines = summaries.map do |s|
|
|
44
|
-
state = s
|
|
45
|
-
relation = if s
|
|
46
|
-
elsif own&.parent_id && s
|
|
44
|
+
state = s.live ? "live" : "not live"
|
|
45
|
+
relation = if s.parent_id == peers.session_id then " child"
|
|
46
|
+
elsif own&.parent_id && s.id == own.parent_id then " parent"
|
|
47
47
|
else ""
|
|
48
48
|
end
|
|
49
|
-
"#{s
|
|
49
|
+
"#{s.short_id} #{state} #{s.busy ? "running" : "idle"}#{relation} #{Paths.tilde(s.cwd)} #{s.preview.inspect}"
|
|
50
50
|
end
|
|
51
51
|
scope = project ? " in this project (#{File.basename(project)}; cwd \"/\" for every project)" : ""
|
|
52
52
|
<<~TEXT.chomp
|
|
@@ -22,8 +22,8 @@ require "json"
|
|
|
22
22
|
module Samagotchi
|
|
23
23
|
# `chi update`: brings an installed chi up to date in one go: the system
|
|
24
24
|
# bundle, the shipped bundles the user installed, and the macOS helper.
|
|
25
|
-
# Unedited things update; edited ones merge or stay behind (.md) or are replaced
|
|
26
|
-
# and reported (hooks, plugin, rules,
|
|
25
|
+
# Unedited things update; edited ones merge or stay behind (.md) or are replaced,
|
|
26
|
+
# copied to the trash first, and reported (hooks, plugin, rules, scripts). Live
|
|
27
27
|
# workers and a running chi web are reported, never restarted. The bundles
|
|
28
28
|
# chi didn't ship are only checked, under the chi that will run them
|
|
29
29
|
# (BundleCommand::InstalledCheck): what it finds is rows that fail nothing.
|
|
@@ -297,10 +297,14 @@ module Samagotchi
|
|
|
297
297
|
earlier = [] if %i[installed skipped failed].include?(result.status)
|
|
298
298
|
row = Row.new(component: "system bundle", from: result.from, to: result.to)
|
|
299
299
|
notes = file_notes(row, system::BUNDLE_NAME, result.to, result.merged, result.behind, result.trash_copy, earlier: earlier)
|
|
300
|
+
# What the run warned of (a Checksum mismatch, a file it skipped) goes
|
|
301
|
+
# on its row, as ensure! echoes it.
|
|
302
|
+
warnings = Array(result.warnings)
|
|
303
|
+
warned = [*notes, *warnings]
|
|
300
304
|
case result.status
|
|
301
|
-
when :installed then done(row, :install)
|
|
302
|
-
when :updated then done(row, :update,
|
|
303
|
-
when :restored then done(row, :restore,
|
|
305
|
+
when :installed then done(row, :install, warnings.empty? ? nil : warnings.join("; "))
|
|
306
|
+
when :updated then done(row, :update, warned.empty? ? nil : warned.join("; "))
|
|
307
|
+
when :restored then done(row, :restore, warned.empty? ? nil : warned.join("; ")).tap { |r| r.to = nil }
|
|
304
308
|
when :merged, :up_to_date then same_version(row, notes + result.warnings, changed: result.merged.any?)
|
|
305
309
|
when :newer_installed then skipped(row, ["newer than this chi's #{result.to}: left", *notes].join("; ")).tap { |r| r.to = nil }
|
|
306
310
|
when :skipped then skipped(row, result.error)
|
|
@@ -352,7 +356,7 @@ module Samagotchi
|
|
|
352
356
|
notes = file_notes(row, update.name, update.to || update.from, update.merged, update.behind, update.trash_copy, earlier: earlier)
|
|
353
357
|
case update.status
|
|
354
358
|
when :would_update, :updated
|
|
355
|
-
notes = [adds(update.adds), *notes, replaced(update.replaced), *update.kept_own].compact
|
|
359
|
+
notes = [adds(update.adds), *notes, replaced(update.replaced), *update.kept_own, update.note].compact
|
|
356
360
|
done(row, :update, notes.empty? ? nil : notes.join("; "))
|
|
357
361
|
when :would_merge, :merged, :up_to_date then same_version(row, [*notes, update.note, update.manifest].compact, changed: Array(update.merged).any?)
|
|
358
362
|
when :skipped then skipped(row, [update.note, *notes].join("; "))
|
|
@@ -526,7 +530,7 @@ module Samagotchi
|
|
|
526
530
|
def replaced(files)
|
|
527
531
|
return nil if files.empty?
|
|
528
532
|
|
|
529
|
-
"#{@dry_run ? "would replace" : "replaced"} edited #{files.join(", ")} (
|
|
533
|
+
"#{@dry_run ? "would replace" : "replaced"} edited #{files.join(", ")} (a copy goes to the trash first)"
|
|
530
534
|
end
|
|
531
535
|
|
|
532
536
|
def print_table(rows)
|
|
@@ -7,6 +7,8 @@ require_relative "atomic_file"
|
|
|
7
7
|
require_relative "log"
|
|
8
8
|
require_relative "session"
|
|
9
9
|
require_relative "session_metrics"
|
|
10
|
+
require_relative "session_row"
|
|
11
|
+
require_relative "worker_link"
|
|
10
12
|
require_relative "steer"
|
|
11
13
|
require_relative "verify_anchor"
|
|
12
14
|
require_relative "verify_fresh_origin"
|
|
@@ -37,10 +39,6 @@ module Samagotchi
|
|
|
37
39
|
# The child's first message, after the claim.
|
|
38
40
|
ASK = "Verify this claim against the files here. You have none of the original session's context. " \
|
|
39
41
|
"End with a line `Verdict: confirmed|refuted|unclear — <one-line reason>`."
|
|
40
|
-
# How long a child whose worker hasn't come up yet counts as running
|
|
41
|
-
# (Tools::Delegate::STARTING_GRACE_SECONDS).
|
|
42
|
-
STARTING_GRACE_SECONDS = 15
|
|
43
|
-
|
|
44
42
|
# A start refused: the HTTP +status+, the error +code+ and its words.
|
|
45
43
|
class Refused < StandardError
|
|
46
44
|
attr_reader :status, :code
|
|
@@ -257,15 +255,13 @@ module Samagotchi
|
|
|
257
255
|
private_class_method :locked
|
|
258
256
|
|
|
259
257
|
# A child runs while its worker runs a turn, or while it is marked
|
|
260
|
-
# running and its worker is on its way (
|
|
258
|
+
# running and its worker is on its way (SessionRow.running_or_starting?,
|
|
259
|
+
# the delegates' rule).
|
|
261
260
|
def child_running?(child_id, state_dir)
|
|
262
261
|
child = Session.summary_from_file(Session.session_file(child_id, state_dir: state_dir)) or return false
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
Time.now - Time.iso8601(child.updated_at.to_s) < STARTING_GRACE_SECONDS
|
|
267
|
-
rescue ArgumentError
|
|
268
|
-
false
|
|
262
|
+
live = WorkerLink.resolve(Session.session_dir(child_id, state_dir: state_dir), unlink: false).worker_owned?
|
|
263
|
+
SessionRow.running_or_starting?(saved_status: child.status, live: live,
|
|
264
|
+
busy: live && child.status == Session::STATUS_RUNNING, updated_at: child.updated_at)
|
|
269
265
|
end
|
|
270
266
|
private_class_method :child_running?
|
|
271
267
|
|
data/lib/samagotchi/version.rb
CHANGED
|
@@ -21,15 +21,15 @@ module Samagotchi
|
|
|
21
21
|
def no? = value == false
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
# @param
|
|
24
|
+
# @param lookup [ModelLookup] the model's layers (EffectiveModel#lookup);
|
|
25
|
+
# its target is the one the probes ask
|
|
25
26
|
# @param profile [ModelProfile, nil] the model's prompt profile (native)
|
|
26
27
|
# @param adapter [LLM::OpenAIChat, nil] the chat host's adapter
|
|
27
|
-
# @param models [Hash{String => ModelSettings}, nil] ModelSettings.all (specs)
|
|
28
|
-
# @param names [Array<String>] the model's lookup names (HostRegistry#lookup_names)
|
|
29
28
|
# @return [Answer]
|
|
30
|
-
def self.for(
|
|
29
|
+
def self.for(lookup, profile: nil, adapter: nil)
|
|
30
|
+
target = lookup.target
|
|
31
31
|
entry = target.entry
|
|
32
|
-
configured = configured(
|
|
32
|
+
configured = configured(lookup)
|
|
33
33
|
return configured if configured&.no?
|
|
34
34
|
|
|
35
35
|
if entry.chat?
|
|
@@ -45,18 +45,15 @@ module Samagotchi
|
|
|
45
45
|
marker.is_a?(String) && !marker.empty? ? marker : nil
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
def self.configured(
|
|
49
|
-
|
|
50
|
-
ModelSettings.all
|
|
51
|
-
rescue StandardError
|
|
52
|
-
{}
|
|
53
|
-
end
|
|
54
|
-
if (setting = ModelSettings.find(names, :vision, models: models))
|
|
48
|
+
def self.configured(lookup)
|
|
49
|
+
if (setting = lookup.model(:vision))
|
|
55
50
|
return Answer.new(value: setting.value, reason: setting.value ? nil : "models: #{setting.key} sets vision: false")
|
|
56
51
|
end
|
|
57
|
-
return nil if entry.vision.nil?
|
|
58
52
|
|
|
59
|
-
|
|
53
|
+
vision = lookup.host(:vision)
|
|
54
|
+
return nil if vision.nil?
|
|
55
|
+
|
|
56
|
+
Answer.new(value: vision, reason: vision ? nil : "hosts.#{lookup.host_name} sets vision: false")
|
|
60
57
|
end
|
|
61
58
|
|
|
62
59
|
def self.native(target, _entry, profile, trust_config:)
|