samagotchi 0.155.0 → 0.157.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 (234) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/bin/chi +2 -1
  4. data/docs/architecture.md +22 -13
  5. data/docs/bundles.md +3 -1
  6. data/docs/cli.md +9 -8
  7. data/docs/configuration.md +36 -4
  8. data/docs/guardrails.md +9 -5
  9. data/docs/hooks.md +11 -5
  10. data/docs/internals/anthropic.md +1 -1
  11. data/docs/internals/attached-context.md +3 -2
  12. data/docs/internals/background-tasks.md +1 -1
  13. data/docs/internals/bridge.md +83 -6
  14. data/docs/internals/bundles.md +28 -18
  15. data/docs/internals/context-telemetry.md +4 -3
  16. data/docs/internals/engine.md +641 -937
  17. data/docs/internals/llm-context-forget.md +8 -3
  18. data/docs/internals/objectives.md +2 -2
  19. data/docs/internals/participants.md +3 -2
  20. data/docs/internals/prompt-caching.md +4 -1
  21. data/docs/internals/remote-sessions.md +8 -3
  22. data/docs/internals/web-frontend.md +3 -2
  23. data/docs/internals/working-set.md +2 -2
  24. data/docs/memory.md +13 -2
  25. data/docs/plugins.md +35 -2
  26. data/docs/sessions.md +11 -11
  27. data/docs/sub-agent.md +17 -2
  28. data/docs/system-one.md +3 -2
  29. data/docs/testing.md +53 -2
  30. data/lib/samagotchi/answer_command.rb +2 -1
  31. data/lib/samagotchi/archive_store.rb +4 -3
  32. data/lib/samagotchi/bootstrap/config_writer.rb +1 -1
  33. data/lib/samagotchi/bootstrap_command.rb +3 -2
  34. data/lib/samagotchi/bridge/card_places.rb +10 -4
  35. data/lib/samagotchi/bridge/card_store.rb +3 -0
  36. data/lib/samagotchi/bridge/turn_accumulator.rb +109 -9
  37. data/lib/samagotchi/bridge/turn_drawings.rb +265 -0
  38. data/lib/samagotchi/bridge.rb +31 -6
  39. data/lib/samagotchi/broadcast/recipients.rb +8 -8
  40. data/lib/samagotchi/broadcast/triage_model.rb +1 -1
  41. data/lib/samagotchi/broadcast_command.rb +3 -3
  42. data/lib/samagotchi/bundle_command/check.rb +18 -0
  43. data/lib/samagotchi/bundle_command/try/sandbox.rb +2 -2
  44. data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
  45. data/lib/samagotchi/bundle_command.rb +5 -2
  46. data/lib/samagotchi/bundles/known-names/manifest.yml +4 -7
  47. data/lib/samagotchi/bundles/known-names/{hooks/known_names.rb → plugin.rb} +17 -0
  48. data/lib/samagotchi/bundles/source-links/manifest.yml +4 -7
  49. data/lib/samagotchi/bundles/source-links/{hooks/source_links.rb → plugin.rb} +23 -4
  50. data/lib/samagotchi/bundles/system/config_modification_protocol.md +2 -2
  51. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  52. data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
  53. data/lib/samagotchi/child_move.rb +13 -57
  54. data/lib/samagotchi/child_reports.rb +2 -2
  55. data/lib/samagotchi/children_status.rb +15 -21
  56. data/lib/samagotchi/cli/parent_wait.rb +9 -23
  57. data/lib/samagotchi/config.rb +20 -112
  58. data/lib/samagotchi/context_command.rb +1 -2
  59. data/lib/samagotchi/context_sources.rb +5 -4
  60. data/lib/samagotchi/context_window.rb +9 -12
  61. data/lib/samagotchi/desktop/macos.rb +1 -1
  62. data/lib/samagotchi/effective_model.rb +57 -22
  63. data/lib/samagotchi/engine/context_judge.rb +19 -11
  64. data/lib/samagotchi/engine/messages.rb +1 -1
  65. data/lib/samagotchi/engine/models.rb +5 -2
  66. data/lib/samagotchi/engine/objectives.rb +29 -11
  67. data/lib/samagotchi/engine/plugins.rb +1 -1
  68. data/lib/samagotchi/engine/recap.rb +8 -4
  69. data/lib/samagotchi/engine/side_requests.rb +21 -11
  70. data/lib/samagotchi/engine/toolkit.rb +10 -9
  71. data/lib/samagotchi/engine/turn_end.rb +1 -1
  72. data/lib/samagotchi/engine/turn_helpers.rb +7 -7
  73. data/lib/samagotchi/engine/verify.rb +1 -1
  74. data/lib/samagotchi/engine.rb +43 -12
  75. data/lib/samagotchi/events.rb +3 -2
  76. data/lib/samagotchi/extension_load.rb +6 -3
  77. data/lib/samagotchi/guardrails/targets.rb +24 -9
  78. data/lib/samagotchi/guardrails_command.rb +1 -1
  79. data/lib/samagotchi/host_address.rb +52 -0
  80. data/lib/samagotchi/host_config.rb +72 -1
  81. data/lib/samagotchi/host_model.rb +2 -6
  82. data/lib/samagotchi/host_registry.rb +58 -46
  83. data/lib/samagotchi/kernel_loop.rb +53 -124
  84. data/lib/samagotchi/llm/anthropic_wire.rb +2 -2
  85. data/lib/samagotchi/llm/chat_loop.rb +40 -21
  86. data/lib/samagotchi/llm/native_backend.rb +1 -1
  87. data/lib/samagotchi/llm_context_apply.rb +54 -1
  88. data/lib/samagotchi/llm_context_notice.rb +1 -1
  89. data/lib/samagotchi/llm_context_strategy.rb +29 -67
  90. data/lib/samagotchi/llm_context_view.rb +9 -0
  91. data/lib/samagotchi/memory_bundle/asset_installer.rb +71 -76
  92. data/lib/samagotchi/memory_bundle/builder.rb +2 -1
  93. data/lib/samagotchi/memory_bundle/file_actions.rb +193 -0
  94. data/lib/samagotchi/memory_bundle/file_digest.rb +13 -0
  95. data/lib/samagotchi/memory_bundle/file_state.rb +152 -0
  96. data/lib/samagotchi/memory_bundle/installed_copy.rb +2 -1
  97. data/lib/samagotchi/memory_bundle/installed_manifest.rb +8 -1
  98. data/lib/samagotchi/memory_bundle/installer.rb +185 -169
  99. data/lib/samagotchi/memory_bundle/manifest.rb +14 -4
  100. data/lib/samagotchi/memory_bundle/merger.rb +69 -65
  101. data/lib/samagotchi/memory_bundle/plugin_ref.rb +6 -5
  102. data/lib/samagotchi/memory_bundle/profile.rb +5 -5
  103. data/lib/samagotchi/memory_bundle/provenance.rb +15 -1
  104. data/lib/samagotchi/memory_bundle/remerge.rb +25 -12
  105. data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -6
  106. data/lib/samagotchi/memory_bundle/status.rb +14 -20
  107. data/lib/samagotchi/memory_bundle/system_bundle.rb +24 -6
  108. data/lib/samagotchi/memory_bundle/trash.rb +23 -8
  109. data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
  110. data/lib/samagotchi/memory_usage.rb +1 -1
  111. data/lib/samagotchi/model_lookup.rb +75 -0
  112. data/lib/samagotchi/model_price.rb +4 -10
  113. data/lib/samagotchi/model_profile.rb +7 -6
  114. data/lib/samagotchi/model_settings.rb +37 -2
  115. data/lib/samagotchi/models_command.rb +1 -1
  116. data/lib/samagotchi/note_command.rb +2 -3
  117. data/lib/samagotchi/objectives_audit.rb +4 -3
  118. data/lib/samagotchi/participant_mode.rb +2 -1
  119. data/lib/samagotchi/participants.rb +2 -2
  120. data/lib/samagotchi/plugin/api.rb +41 -6
  121. data/lib/samagotchi/plugin/context.rb +1 -1
  122. data/lib/samagotchi/plugin/loader.rb +33 -10
  123. data/lib/samagotchi/plugin/sessions.rb +3 -6
  124. data/lib/samagotchi/plugin/tool_spec.rb +5 -3
  125. data/lib/samagotchi/project_env.rb +1 -1
  126. data/lib/samagotchi/reasoning_history.rb +14 -20
  127. data/lib/samagotchi/recap_place.rb +27 -0
  128. data/lib/samagotchi/reminder_carry.rb +1 -1
  129. data/lib/samagotchi/reply_wait/liveness.rb +38 -0
  130. data/lib/samagotchi/reply_wait/poll.rb +44 -15
  131. data/lib/samagotchi/reply_wait/target.rb +113 -0
  132. data/lib/samagotchi/reply_wait.rb +22 -2
  133. data/lib/samagotchi/reprefill_meter.rb +45 -0
  134. data/lib/samagotchi/review_command.rb +1 -1
  135. data/lib/samagotchi/sampling_settings.rb +9 -16
  136. data/lib/samagotchi/self_report.rb +36 -37
  137. data/lib/samagotchi/send_command.rb +17 -23
  138. data/lib/samagotchi/session.rb +370 -445
  139. data/lib/samagotchi/session_access/local.rb +12 -11
  140. data/lib/samagotchi/session_access/remote.rb +22 -6
  141. data/lib/samagotchi/session_archive_command.rb +2 -2
  142. data/lib/samagotchi/session_chain.rb +7 -6
  143. data/lib/samagotchi/session_continue.rb +6 -5
  144. data/lib/samagotchi/session_delete_command.rb +1 -1
  145. data/lib/samagotchi/session_field.rb +133 -0
  146. data/lib/samagotchi/session_inbox.rb +49 -1
  147. data/lib/samagotchi/session_manager/delivery.rb +254 -0
  148. data/lib/samagotchi/session_manager/emptiness.rb +139 -0
  149. data/lib/samagotchi/session_manager/lifecycle.rb +274 -0
  150. data/lib/samagotchi/session_manager/listing.rb +118 -0
  151. data/lib/samagotchi/session_manager/removal.rb +190 -0
  152. data/lib/samagotchi/session_manager/spawn.rb +242 -0
  153. data/lib/samagotchi/session_manager/worker_command.rb +157 -0
  154. data/lib/samagotchi/session_manager.rb +28 -1541
  155. data/lib/samagotchi/session_metrics.rb +8 -5
  156. data/lib/samagotchi/session_paths.rb +57 -0
  157. data/lib/samagotchi/session_retention.rb +2 -2
  158. data/lib/samagotchi/session_row.rb +200 -0
  159. data/lib/samagotchi/session_setup.rb +15 -12
  160. data/lib/samagotchi/sessions_command.rb +11 -129
  161. data/lib/samagotchi/share_command.rb +1 -1
  162. data/lib/samagotchi/starting_marker.rb +65 -0
  163. data/lib/samagotchi/steer_cut_after.rb +7 -14
  164. data/lib/samagotchi/system_one/recorder.rb +49 -0
  165. data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
  166. data/lib/samagotchi/terminal_ui/exit_lines.rb +1 -1
  167. data/lib/samagotchi/terminal_ui/join_replay.rb +86 -8
  168. data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
  169. data/lib/samagotchi/terminal_ui.rb +5 -7
  170. data/lib/samagotchi/testing/install.rb +2 -1
  171. data/lib/samagotchi/testing/sessions.rb +1 -1
  172. data/lib/samagotchi/thinking.rb +16 -21
  173. data/lib/samagotchi/token_usage.rb +1 -1
  174. data/lib/samagotchi/tool_activity.rb +10 -4
  175. data/lib/samagotchi/tool_declarations.rb +2 -2
  176. data/lib/samagotchi/tool_output_cut.rb +17 -0
  177. data/lib/samagotchi/tool_response.rb +5 -3
  178. data/lib/samagotchi/tool_runner.rb +25 -5
  179. data/lib/samagotchi/toolkit_command.rb +1 -1
  180. data/lib/samagotchi/tools/delegate.rb +55 -45
  181. data/lib/samagotchi/tools/delegate_relay.rb +5 -5
  182. data/lib/samagotchi/tools/delegate_result.rb +5 -5
  183. data/lib/samagotchi/tools/delegate_wait.rb +41 -17
  184. data/lib/samagotchi/tools/index_hint.rb +14 -0
  185. data/lib/samagotchi/tools/list_sessions.rb +7 -7
  186. data/lib/samagotchi/tools/registry.rb +28 -11
  187. data/lib/samagotchi/tools/send_note.rb +4 -6
  188. data/lib/samagotchi/tools/tool_index.rb +52 -51
  189. data/lib/samagotchi/tools/tool_surface.rb +22 -8
  190. data/lib/samagotchi/turn_context.rb +2 -1
  191. data/lib/samagotchi/turn_stages/context_judge.rb +38 -36
  192. data/lib/samagotchi/turn_stages/objectives.rb +56 -52
  193. data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +13 -17
  194. data/lib/samagotchi/turn_stages/verify_anchors.rb +9 -10
  195. data/lib/samagotchi/turn_stages/warmup.rb +14 -15
  196. data/lib/samagotchi/update_command.rb +11 -7
  197. data/lib/samagotchi/verify_fresh.rb +7 -11
  198. data/lib/samagotchi/version.rb +1 -1
  199. data/lib/samagotchi/vision_support.rb +11 -14
  200. data/lib/samagotchi/web/app.rb +45 -28
  201. data/lib/samagotchi/web/message_display.rb +57 -7
  202. data/lib/samagotchi/web/participant_access.rb +3 -3
  203. data/lib/samagotchi/web/public/activity.js +16 -7
  204. data/lib/samagotchi/web/public/app.js +29 -8
  205. data/lib/samagotchi/web/public/bubbles.js +8 -3
  206. data/lib/samagotchi/web/public/cards_place.js +15 -0
  207. data/lib/samagotchi/web/public/commands.js +5 -1
  208. data/lib/samagotchi/web/public/data.js +4 -2
  209. data/lib/samagotchi/web/public/history_shape.js +220 -0
  210. data/lib/samagotchi/web/public/recap_stamp.js +17 -0
  211. data/lib/samagotchi/web/public/reload_check.js +71 -0
  212. data/lib/samagotchi/web/public/stage_model.js +7 -5
  213. data/lib/samagotchi/web/public/stage_view.js +7 -3
  214. data/lib/samagotchi/web/public/stream_handlers.js +8 -2
  215. data/lib/samagotchi/web/public/timing.js +43 -2
  216. data/lib/samagotchi/web/public/turn_block.js +215 -30
  217. data/lib/samagotchi/web/public/turn_drawing.js +43 -0
  218. data/lib/samagotchi/web/public/turn_events.js +10 -1
  219. data/lib/samagotchi/web/public/turn_pieces.js +29 -0
  220. data/lib/samagotchi/web/reload_check_route.rb +95 -0
  221. data/lib/samagotchi/web/server.rb +4 -2
  222. data/lib/samagotchi/web/session_access_routes.rb +6 -6
  223. data/lib/samagotchi/web/session_control.rb +4 -4
  224. data/lib/samagotchi/web/session_create.rb +3 -3
  225. data/lib/samagotchi/web/session_hub.rb +9 -17
  226. data/lib/samagotchi/web/session_show.rb +67 -5
  227. data/lib/samagotchi/web/session_summary.rb +39 -28
  228. data/lib/samagotchi/worker.rb +83 -32
  229. data/lib/samagotchi/worker_boot.rb +184 -0
  230. data/lib/samagotchi/worker_link.rb +3 -0
  231. data/lib/samagotchi/worker_signals.rb +1 -1
  232. data/lib/samagotchi/worker_task_wakes.rb +2 -2
  233. metadata +32 -4
  234. data/lib/samagotchi/send_command/wait_target.rb +0 -85
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "session_manager"
4
+
5
+ module Samagotchi
6
+ # WorkerBoot: the worker process, from run_session_loop (what the boot
7
+ # string SessionManager#worker_command writes runs, through
8
+ # SessionManager.run_session_loop) to what it does as it leaves (CROSS-VERSION
9
+ # CONTRACT: spec/worker_contract_spec.rb). It takes the session's OwnerLock,
10
+ # leaves the spawner's process tree, installs the signals and the project
11
+ # env, and runs the Worker (worker.rb loads only then, after the lock). Calls
12
+ # SessionManager's resume_session, discardable? and delete_session; the env
13
+ # keys it reads are SessionManager's, which the spawner sets.
14
+ module WorkerBoot
15
+ class << self
16
+ # Run the session loop inside the forked process: the worker's main,
17
+ # which the boot string reaches through SessionManager.run_session_loop.
18
+ #
19
+ # Every worker starts its per-session Bridge (the single live client
20
+ # transport) on a port bound to 127.0.0.1 before the loop and stops it on
21
+ # exit. If the bridge fails to start the worker degrades: turns still flow
22
+ # through the input-dir loop, but there is no live SSE or in-process
23
+ # cancel/answer.
24
+ #
25
+ # The worker first takes the session's OwnerLock; when another owner holds
26
+ # it (a racing resume spawned two workers, or the TUI has the session) it
27
+ # exits quietly.
28
+ #
29
+ # A worker nobody uses returns once session.idle_exit_minutes have passed
30
+ # (see WorkerIdleExit), and one a client asked to exit (Bridge POST /exit)
31
+ # returns as soon as nothing keeps it. The next send wakes a new one.
32
+ # @param idle_exit_minutes [Numeric, nil] nil: session.idle_exit_minutes
33
+ # @param poll_interval [Numeric, nil] seconds between the loop's fallback
34
+ # ticks (nil: Worker::FALLBACK_TICK_SECONDS); queued turns wake it at once
35
+ # A stopped session's worker exits 0 (a signalled one too) and a
36
+ # crashed one 1, there.
37
+ # @return [Symbol] :idle_exit, :exit_requested, :restart or :scratch_left
38
+ def run_session_loop(session_id, state_dir: nil, owner_wait: OwnerLock::DEFAULT_WAIT,
39
+ idle_exit_minutes: nil, poll_interval: nil)
40
+ # A worker inherits chi's locale (LC_ALL=C too): read files as UTF-8.
41
+ Utf8Default.apply!
42
+ sd = state_dir || Session.default_state_dir
43
+ # The spawner passed the file and level through ENV; a worker's stderr
44
+ # is /dev/null, so warnings only reach the file.
45
+ Log.configure(stderr: false)
46
+ Log.session_id = session_id
47
+ session_dir = Session.session_dir(session_id, state_dir: sd)
48
+ # Read (and taken out of ENV, so nothing the worker runs inherits it)
49
+ # before anything else: set by spawn_options for a spawned worker.
50
+ spawned = ENV.delete(SessionManager::WORKER_PROCESS_ENV) == "1"
51
+ # Out of the spawner's process tree before the lock (it records this
52
+ # process's pid) and before any thread starts.
53
+ detach_from_spawner(session_dir) if ENV.delete(SessionManager::WORKER_DETACH_ENV) == "1"
54
+ # Kept in a module ivar so the lock's File lives as long as the worker.
55
+ @owner_lock = OwnerLock.acquire(session_dir, kind: "worker", wait: owner_wait)
56
+ exit(0) unless @owner_lock
57
+ # Set by the boot string when the newest installed chi didn't load.
58
+ fallback = ENV.delete(SessionManager::BOOT_FALLBACK_ENV)
59
+ Log.info(:worker, "start", cwd: Dir.pwd, version: VERSION)
60
+ Log.warn(:worker, "boot_fallback", version: VERSION, error: fallback) if fallback
61
+ # Tools follow the project (its `mise env`, computed once per worker;
62
+ # this process's ENV is left as it is); never in a loop run in-process.
63
+ apply_project_env(session_id, state_dir: sd) if spawned
64
+ # A spawned worker stops its turn on TERM/HUP/INT and saves it; a
65
+ # loop run in-process (specs) traps nothing.
66
+ signals = spawned ? WorkerSignals.new.install! : WorkerSignals::NONE
67
+ # Only now, after the detach and the lock: the Engine and all it loads.
68
+ require_relative "worker"
69
+ worker = Worker.new(session_id: session_id, state_dir: sd, session_dir: session_dir,
70
+ idle_exit_minutes: idle_exit_minutes, poll_interval: poll_interval, signals: signals,
71
+ owner_lock: @owner_lock)
72
+ result = begin
73
+ worker.run
74
+ rescue StandardError, ScriptError => e
75
+ # The worker's stderr is /dev/null: the log is the only trace.
76
+ Log.exception(:worker, "crashed", e)
77
+ raise
78
+ ensure
79
+ @owner_lock.release
80
+ # A signal from here on ends the process as Ruby's default does.
81
+ signals.restore!
82
+ end
83
+ Log.info(:worker, "stop", reason: result)
84
+ # A scratch session goes with its worker however it leaves (its
85
+ # terminal deletes it too, whichever comes first); nothing resumes it.
86
+ if worker.scratch?
87
+ delete_left_scratch(session_id, state_dir: sd)
88
+ exit(result == :crashed ? 1 : 0) if %i[stopped signal crashed].include?(result)
89
+ return result
90
+ end
91
+ # A stopped session is left as it is: no resume for input that came
92
+ # in, no discard. One whose worker a signal stopped isn't discarded
93
+ # either; input that came in (queued behind the turn it stopped)
94
+ # wakes a new worker, as after an idle exit.
95
+ exit(0) if result == :stopped
96
+ if result == :signal
97
+ SessionManager.resume_session(session_id, state_dir: sd) unless SessionInbox.find_new_input_files(session_dir).empty?
98
+ exit(0)
99
+ end
100
+ exit(1) if result == :crashed
101
+ # Only after the release: a writer that still saw this worker as the
102
+ # owner may have queued input since the last check. Either it finds no
103
+ # owner after its write and wakes one, or this finds its input.
104
+ # A restart starts its successor here, on the newest chi installed
105
+ # (worker_command), once this one's lock is free.
106
+ # Delegate reports that rang while it left wake one too.
107
+ if result == :restart && Session.stopped_marker?(session_id, state_dir: sd)
108
+ # A stop landed as it left: no successor (resume_session would clear
109
+ # the stop), and the reminders go with it.
110
+ ReminderCarry.remove(session_dir)
111
+ Log.info(:worker, "restart_dropped", why: :stopped)
112
+ elsif result == :restart ||
113
+ (%i[idle_exit exit_requested].include?(result) &&
114
+ (!SessionInbox.find_new_input_files(session_dir).empty? || worker.wake_due?))
115
+ SessionManager.resume_session(session_id, state_dir: sd)
116
+ elsif worker.discard?
117
+ discard_left_session(session_id, state_dir: sd, default_model: worker.default_model)
118
+ end
119
+ result
120
+ end
121
+
122
+ # A spawned worker leaves its spawner's process tree: a killer that
123
+ # walks the tree by parent pid (a terminal closing, a tool's teardown
124
+ # of a `chi send --wait` it ran in the background) no longer reaches it.
125
+ # This process (the spawner's child, which its Process.detach waits on)
126
+ # forks the worker, which starts a session of its own (setsid: no
127
+ # controlling terminal, no SIGHUP from one) and returns to run the loop;
128
+ # this one waits until the worker holds the session's OwnerLock, or has
129
+ # exited, or WORKER_START_GRACE passed, and exits: the spawner's waiter
130
+ # stays alive exactly while the worker starts (#still_starting?). The
131
+ # worker is reparented to init (launchd), which reaps it.
132
+ private def detach_from_spawner(session_dir)
133
+ worker = Process.fork
134
+ unless worker
135
+ Process.setsid
136
+ return
137
+ end
138
+
139
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + SessionManager::WORKER_START_GRACE
140
+ until Process.waitpid(worker, Process::WNOHANG) || OwnerLock.owner(session_dir)&.pid == worker ||
141
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
142
+ sleep(OwnerLock::RETRY_INTERVAL)
143
+ end
144
+ Process.exit!(0)
145
+ end
146
+
147
+ # Delete a scratch session its worker left (its terminal gone:
148
+ # Worker#leave_scratch, or any other way out), now the lock is free.
149
+ # Its terminal may have deleted it first.
150
+ private def delete_left_scratch(session_id, state_dir:)
151
+ SessionManager.delete_session(session_id, state_dir: state_dir)
152
+ Log.info(:worker, "scratch_deleted", sid: session_id)
153
+ rescue SessionManager::DeleteRefused, ArgumentError, SystemCallError => e
154
+ Log.info(:worker, "scratch_kept", sid: session_id, error: e.class.name, msg: e.message)
155
+ end
156
+
157
+ # Delete a session its worker left empty. Checked again now the lock is
158
+ # free: a note or input may have come in since the worker looked, and a
159
+ # worker woken meanwhile owns it (delete_session refuses). A `chi send`
160
+ # landing between this check and the delete fails with "no session"
161
+ # (a window of an empty session's last moments, left as is).
162
+ private def discard_left_session(session_id, state_dir:, default_model:)
163
+ return unless SessionManager.discardable?(session_id, state_dir: state_dir, default_model: default_model)
164
+
165
+ SessionManager.delete_session(session_id, state_dir: state_dir, purge_runs: false)
166
+ Log.info(:worker, "discarded_empty", sid: session_id)
167
+ rescue SessionManager::DeleteRefused, SessionManager::OwnedByTUI, ArgumentError, SystemCallError => e
168
+ Log.info(:worker, "kept_session", sid: session_id, error: e.class.name, msg: e.message)
169
+ end
170
+
171
+ # The tools' ProjectEnv for the session's folder (its working_directory;
172
+ # spawn_options chdirs there unless it is gone), installed for this
173
+ # process's tool spawns. Never fatal: a worker without it still runs.
174
+ private def apply_project_env(session_id, state_dir:)
175
+ session = Session.summary_from_file(Session.session_file(session_id, state_dir: state_dir))
176
+ dir = session&.working_directory.to_s
177
+ overlay = ProjectEnv.install!(ProjectEnv.compute(dir))
178
+ Log.info(:worker, "project_env_applied", dir: dir, ruby: overlay.ruby) if overlay.applied
179
+ rescue StandardError => e
180
+ Log.warn(:worker, "project_env", reason: "error", error: e.class.name, msg: e.message)
181
+ end
182
+ end
183
+ end
184
+ end
@@ -113,6 +113,9 @@ module Samagotchi
113
113
 
114
114
  def not_live(session_id) = "no live worker for session #{session_id}"
115
115
  def unanswered(session_id) = "the worker for session #{session_id} didn't answer"
116
+ # A worker chi started that never came up within its wait (chi send
117
+ # --new's, a delegate child's).
118
+ def did_not_start = "its worker did not start"
116
119
 
117
120
  # @param session_dir [String]
118
121
  # @param unlink [Boolean] remove a Stale sidecar (the clients that would
@@ -3,7 +3,7 @@
3
3
  module Samagotchi
4
4
  # TERM, HUP and INT in a worker process: the running turn stops as a Stop
5
5
  # does (reason :signal), is saved, and the worker leaves (Worker#run
6
- # returns :signal). SessionManager.run_session_loop installs the traps in
6
+ # returns :signal). WorkerBoot.run_session_loop installs the traps in
7
7
  # a spawned worker only (WORKER_PROCESS_ENV), never in a process that runs
8
8
  # the loop in-process (the specs, which fire #fire directly).
9
9
  #
@@ -3,10 +3,10 @@
3
3
  require "securerandom"
4
4
 
5
5
  require_relative "child_reports"
6
- require_relative "kernel_loop"
7
6
  require_relative "log"
8
7
  require_relative "task_finish_note"
9
8
  require_relative "task_watch"
9
+ require_relative "tool_output_cut"
10
10
  require_relative "tools/task_runtime"
11
11
  require_relative "worker_inbound"
12
12
  require_relative "worker_wakes"
@@ -130,7 +130,7 @@ module Samagotchi
130
130
 
131
131
  def note(entry, record, wake:, turn_id: nil)
132
132
  TaskFinishNote.note(record, limit_sec: entry.limit_sec, early_sec: entry.early_sec, wake: wake, turn_id: turn_id,
133
- cap: KernelLoop.resolve_output_char_cap(nil))
133
+ cap: ToolOutputCut.max_chars(nil))
134
134
  end
135
135
 
136
136
  def add_note(entry, record)
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.155.0
4
+ version: 0.157.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -204,6 +204,7 @@ files:
204
204
  - lib/samagotchi/bridge/ring_buffer.rb
205
205
  - lib/samagotchi/bridge/sse_writer.rb
206
206
  - lib/samagotchi/bridge/turn_accumulator.rb
207
+ - lib/samagotchi/bridge/turn_drawings.rb
207
208
  - lib/samagotchi/bridge/turn_notice.rb
208
209
  - lib/samagotchi/bridge_client.rb
209
210
  - lib/samagotchi/bridge_client/event_stream.rb
@@ -267,9 +268,9 @@ files:
267
268
  - lib/samagotchi/bundles/guardrails/manifest.yml
268
269
  - lib/samagotchi/bundles/inline-widgets/manifest.yml
269
270
  - lib/samagotchi/bundles/inline-widgets/plugin.rb
270
- - lib/samagotchi/bundles/known-names/hooks/known_names.rb
271
271
  - lib/samagotchi/bundles/known-names/known_names.md
272
272
  - lib/samagotchi/bundles/known-names/manifest.yml
273
+ - lib/samagotchi/bundles/known-names/plugin.rb
273
274
  - lib/samagotchi/bundles/loop-guard/manifest.yml
274
275
  - lib/samagotchi/bundles/loop-guard/plugin.rb
275
276
  - lib/samagotchi/bundles/mcp/manifest.yml
@@ -284,8 +285,8 @@ files:
284
285
  - lib/samagotchi/bundles/skills/plugin.rb
285
286
  - lib/samagotchi/bundles/slow-commands/manifest.yml
286
287
  - lib/samagotchi/bundles/slow-commands/plugin.rb
287
- - lib/samagotchi/bundles/source-links/hooks/source_links.rb
288
288
  - lib/samagotchi/bundles/source-links/manifest.yml
289
+ - lib/samagotchi/bundles/source-links/plugin.rb
289
290
  - lib/samagotchi/bundles/source-links/source_links.md
290
291
  - lib/samagotchi/bundles/system/config_modification_protocol.md
291
292
  - lib/samagotchi/bundles/system/delegated.md
@@ -438,6 +439,7 @@ files:
438
439
  - lib/samagotchi/hooks/loader.rb
439
440
  - lib/samagotchi/hooks/registry.rb
440
441
  - lib/samagotchi/hooks/stream_watch.rb
442
+ - lib/samagotchi/host_address.rb
441
443
  - lib/samagotchi/host_config.rb
442
444
  - lib/samagotchi/host_keys.rb
443
445
  - lib/samagotchi/host_model.rb
@@ -501,8 +503,10 @@ files:
501
503
  - lib/samagotchi/memory_bundle/builder.rb
502
504
  - lib/samagotchi/memory_bundle/bundle_hook.rb
503
505
  - lib/samagotchi/memory_bundle/entry_size.rb
506
+ - lib/samagotchi/memory_bundle/file_actions.rb
504
507
  - lib/samagotchi/memory_bundle/file_digest.rb
505
508
  - lib/samagotchi/memory_bundle/file_standing.rb
509
+ - lib/samagotchi/memory_bundle/file_state.rb
506
510
  - lib/samagotchi/memory_bundle/index_size.rb
507
511
  - lib/samagotchi/memory_bundle/index_sync.rb
508
512
  - lib/samagotchi/memory_bundle/index_updater.rb
@@ -538,6 +542,7 @@ files:
538
542
  - lib/samagotchi/model_binding.rb
539
543
  - lib/samagotchi/model_catalog.rb
540
544
  - lib/samagotchi/model_list_store.rb
545
+ - lib/samagotchi/model_lookup.rb
541
546
  - lib/samagotchi/model_match.rb
542
547
  - lib/samagotchi/model_notes.rb
543
548
  - lib/samagotchi/model_overlay.rb
@@ -604,6 +609,7 @@ files:
604
609
  - lib/samagotchi/reasoning_history.rb
605
610
  - lib/samagotchi/reasoning_history_command.rb
606
611
  - lib/samagotchi/recap_command.rb
612
+ - lib/samagotchi/recap_place.rb
607
613
  - lib/samagotchi/recap_setup.rb
608
614
  - lib/samagotchi/recap_store.rb
609
615
  - lib/samagotchi/recap_title.rb
@@ -621,7 +627,10 @@ files:
621
627
  - lib/samagotchi/remote_command.rb
622
628
  - lib/samagotchi/remotes.rb
623
629
  - lib/samagotchi/reply_wait.rb
630
+ - lib/samagotchi/reply_wait/liveness.rb
624
631
  - lib/samagotchi/reply_wait/poll.rb
632
+ - lib/samagotchi/reply_wait/target.rb
633
+ - lib/samagotchi/reprefill_meter.rb
625
634
  - lib/samagotchi/review_apply.rb
626
635
  - lib/samagotchi/review_command.rb
627
636
  - lib/samagotchi/review_start.rb
@@ -634,7 +643,6 @@ files:
634
643
  - lib/samagotchi/send_command.rb
635
644
  - lib/samagotchi/send_command/busy.rb
636
645
  - lib/samagotchi/send_command/parent_gate.rb
637
- - lib/samagotchi/send_command/wait_target.rb
638
646
  - lib/samagotchi/served_model.rb
639
647
  - lib/samagotchi/session.rb
640
648
  - lib/samagotchi/session_access.rb
@@ -650,8 +658,16 @@ files:
650
658
  - lib/samagotchi/session_config.rb
651
659
  - lib/samagotchi/session_continue.rb
652
660
  - lib/samagotchi/session_delete_command.rb
661
+ - lib/samagotchi/session_field.rb
653
662
  - lib/samagotchi/session_inbox.rb
654
663
  - lib/samagotchi/session_manager.rb
664
+ - lib/samagotchi/session_manager/delivery.rb
665
+ - lib/samagotchi/session_manager/emptiness.rb
666
+ - lib/samagotchi/session_manager/lifecycle.rb
667
+ - lib/samagotchi/session_manager/listing.rb
668
+ - lib/samagotchi/session_manager/removal.rb
669
+ - lib/samagotchi/session_manager/spawn.rb
670
+ - lib/samagotchi/session_manager/worker_command.rb
655
671
  - lib/samagotchi/session_metrics.rb
656
672
  - lib/samagotchi/session_metrics/cost.rb
657
673
  - lib/samagotchi/session_metrics/model_segment.rb
@@ -661,8 +677,10 @@ files:
661
677
  - lib/samagotchi/session_model_settings.rb
662
678
  - lib/samagotchi/session_observer.rb
663
679
  - lib/samagotchi/session_participants.rb
680
+ - lib/samagotchi/session_paths.rb
664
681
  - lib/samagotchi/session_retention.rb
665
682
  - lib/samagotchi/session_review.rb
683
+ - lib/samagotchi/session_row.rb
666
684
  - lib/samagotchi/session_setting.rb
667
685
  - lib/samagotchi/session_setup.rb
668
686
  - lib/samagotchi/sessions_command.rb
@@ -670,6 +688,7 @@ files:
670
688
  - lib/samagotchi/share_command.rb
671
689
  - lib/samagotchi/side_judgments.rb
672
690
  - lib/samagotchi/side_usage.rb
691
+ - lib/samagotchi/starting_marker.rb
673
692
  - lib/samagotchi/steer.rb
674
693
  - lib/samagotchi/steer_cut.rb
675
694
  - lib/samagotchi/steer_cut_after.rb
@@ -687,6 +706,7 @@ files:
687
706
  - lib/samagotchi/system_one/log.rb
688
707
  - lib/samagotchi/system_one/question.rb
689
708
  - lib/samagotchi/system_one/record.rb
709
+ - lib/samagotchi/system_one/recorder.rb
690
710
  - lib/samagotchi/system_one/recording.rb
691
711
  - lib/samagotchi/system_one/runs.rb
692
712
  - lib/samagotchi/system_one/runs_store.rb
@@ -791,6 +811,7 @@ files:
791
811
  - lib/samagotchi/tools/edit.rb
792
812
  - lib/samagotchi/tools/execute.rb
793
813
  - lib/samagotchi/tools/forget_outputs.rb
814
+ - lib/samagotchi/tools/index_hint.rb
794
815
  - lib/samagotchi/tools/list_reminders.rb
795
816
  - lib/samagotchi/tools/list_sessions.rb
796
817
  - lib/samagotchi/tools/memory.rb
@@ -911,6 +932,7 @@ files:
911
932
  - lib/samagotchi/web/public/forms.css
912
933
  - lib/samagotchi/web/public/hash_intent.js
913
934
  - lib/samagotchi/web/public/history_reveal.js
935
+ - lib/samagotchi/web/public/history_shape.js
914
936
  - lib/samagotchi/web/public/icons/chi-180.png
915
937
  - lib/samagotchi/web/public/icons/chi-192.png
916
938
  - lib/samagotchi/web/public/icons/chi-512.png
@@ -962,9 +984,11 @@ files:
962
984
  - lib/samagotchi/web/public/question_cards.js
963
985
  - lib/samagotchi/web/public/read_view.js
964
986
  - lib/samagotchi/web/public/reasoning_history_chip.js
987
+ - lib/samagotchi/web/public/recap_stamp.js
965
988
  - lib/samagotchi/web/public/ref_menu.js
966
989
  - lib/samagotchi/web/public/ref_scan.js
967
990
  - lib/samagotchi/web/public/refs.js
991
+ - lib/samagotchi/web/public/reload_check.js
968
992
  - lib/samagotchi/web/public/restore.js
969
993
  - lib/samagotchi/web/public/review_apply.js
970
994
  - lib/samagotchi/web/public/review_history.js
@@ -1019,9 +1043,11 @@ files:
1019
1043
  - lib/samagotchi/web/public/toolkit_chip.js
1020
1044
  - lib/samagotchi/web/public/tools_overview.js
1021
1045
  - lib/samagotchi/web/public/turn_block.js
1046
+ - lib/samagotchi/web/public/turn_drawing.js
1022
1047
  - lib/samagotchi/web/public/turn_events.js
1023
1048
  - lib/samagotchi/web/public/turn_html.js
1024
1049
  - lib/samagotchi/web/public/turn_model.js
1050
+ - lib/samagotchi/web/public/turn_pieces.js
1025
1051
  - lib/samagotchi/web/public/turn_range.js
1026
1052
  - lib/samagotchi/web/public/unqueue.js
1027
1053
  - lib/samagotchi/web/public/update_notice.js
@@ -1054,6 +1080,7 @@ files:
1054
1080
  - lib/samagotchi/web/push/store.rb
1055
1081
  - lib/samagotchi/web/push_routes.rb
1056
1082
  - lib/samagotchi/web/qr.rb
1083
+ - lib/samagotchi/web/reload_check_route.rb
1057
1084
  - lib/samagotchi/web/running.rb
1058
1085
  - lib/samagotchi/web/server.rb
1059
1086
  - lib/samagotchi/web/session_access_routes.rb
@@ -1070,6 +1097,7 @@ files:
1070
1097
  - lib/samagotchi/web/update_ops.rb
1071
1098
  - lib/samagotchi/web/verify_fresh_routes.rb
1072
1099
  - lib/samagotchi/worker.rb
1100
+ - lib/samagotchi/worker_boot.rb
1073
1101
  - lib/samagotchi/worker_idle_exit.rb
1074
1102
  - lib/samagotchi/worker_inbound.rb
1075
1103
  - lib/samagotchi/worker_link.rb
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../delivery"
4
- require_relative "../reply_wait"
5
- require_relative "../session"
6
-
7
- module Samagotchi
8
- class SendCommand
9
- # Which turn chi send --wait waits for and names in its report:
10
- # ReplyWait's +turn+ (an enqueued_id; nil: the next reply), +queued+
11
- # and +ended_before+, and ParentWait's +report_turn_id+ (the turn_id
12
- # of the --format json line). Each run path builds it its way.
13
- WaitTarget = Data.define(:turn, :report_turn_id, :queued, :ended_before) do
14
- def initialize(turn: nil, report_turn_id: nil, queued: false, ended_before: false) = super
15
-
16
- # A --new session's first message: its own turn, the ack's id (a
17
- # file-delivered one's also names the outcome of a session command
18
- # that runs no turn), so a worker that dies in it and the next one's
19
- # settle end the wait (TurnCheckpoint.settled_turn). A message merged
20
- # into a turn starting then falls back to that turn's reply
21
- # (ReplyWait's turn: without queued:); no id (an older worker), the
22
- # next reply. A prompt the session was created with has no delivery:
23
- # its turn is the id the create gave it (+direct_turn+,
24
- # Session#first_turn_id), and nothing can merge into it, so the wait
25
- # is for that turn alone (ReplyWait's queued:).
26
- # @param delivered [SessionManager::DeliverResult, nil] nil when the
27
- # message started the session itself (no delivery)
28
- # @param direct_turn [String, nil] that first turn's id, for a direct
29
- # start
30
- def self.first_message(delivered, direct_turn: nil)
31
- return new unless delivered || !direct_turn.to_s.strip.empty?
32
-
33
- return new(turn: direct_turn, report_turn_id: direct_turn, queued: true) if delivered.nil?
34
-
35
- new(turn: delivered.enqueued_id, report_turn_id: delivered.enqueued_id)
36
- end
37
-
38
- # A message to an existing session. --queue: its own turn, the ack's
39
- # id, also when the turn running at the check ended before it went in
40
- # (an idle worker's ack says next_step, and the file still runs as
41
- # that turn). A file fallback's id names its turn too, and so does a
42
- # plain message to an idle session, as #first_message's. Otherwise
43
- # (a session left running by a dead worker, the busy check's owner
44
- # gone) the next reply, reported as the turn that was running when it
45
- # went in (+running_turn+, ReplyWait.own_turn). A plain message the
46
- # worker queued (its ack says queue: a participant's review turn was
47
- # running, which takes no steer) is a --queue's.
48
- # @param running [Boolean] the session was running when it went in
49
- def self.message(delivered, queue:, running:, running_turn:)
50
- queue ||= delivered.delivery == Delivery::QUEUE
51
- turn = delivered.enqueued_id if queue || delivered.file? || !running
52
- report = if queue then turn
53
- elsif running then running_turn
54
- else delivered.enqueued_id
55
- end
56
- new(turn: turn, report_turn_id: report, queued: queue && !turn.nil?)
57
- end
58
-
59
- # --wait with no message: --turn's (+requested+), or the turn running
60
- # now or whose continue offer stands (ReplyWait.own_turn). Settled
61
- # (idle, a turn ended, no question standing: a continue offer waits
62
- # for its answer), its last turn's end is the answer (ended_before);
63
- # with --turn, when that turn is the last one.
64
- def self.wait_only(session, session_dir:, requested:)
65
- settled = session.status == Session::STATUS_IDLE && !session.last_turn.nil? && session.pending_question.nil?
66
- own = requested ? nil : ReplyWait.own_turn(session, session_dir: session_dir)
67
- wait_only_of(own_turn: own, settled: settled, last_turn_id: session.last_turn&.enqueued_id, requested: requested)
68
- end
69
-
70
- # #wait_only from what SessionAccess#prepare_wait read
71
- # (SessionAccess::WaitStart): its own_turn, settled and last_turn_id.
72
- def self.after(start, requested:)
73
- wait_only_of(own_turn: start.own_turn, settled: start.settled, last_turn_id: start.last_turn_id,
74
- requested: requested)
75
- end
76
-
77
- def self.wait_only_of(own_turn:, settled:, last_turn_id:, requested:)
78
- turn = requested || own_turn
79
- ended_before = requested ? last_turn_id == requested : settled
80
- new(turn: turn, report_turn_id: turn || (last_turn_id if settled), ended_before: ended_before)
81
- end
82
- private_class_method :wait_only_of
83
- end
84
- end
85
- end