samagotchi 0.153.0 → 0.154.1

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 (226) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -1
  3. data/README.md +2 -3
  4. data/bin/chi +23 -24
  5. data/docs/architecture.md +30 -33
  6. data/docs/broadcast.md +5 -5
  7. data/docs/bundles.md +9 -12
  8. data/docs/cli.md +82 -96
  9. data/docs/configuration.md +27 -26
  10. data/docs/context.md +3 -3
  11. data/docs/desktop.md +1 -1
  12. data/docs/guardrails.md +8 -10
  13. data/docs/hooks.md +9 -9
  14. data/docs/internals/attached-context.md +2 -2
  15. data/docs/internals/background-tasks.md +3 -4
  16. data/docs/internals/block-renderers.md +4 -0
  17. data/docs/internals/bridge.md +21 -8
  18. data/docs/internals/context-telemetry.md +1 -1
  19. data/docs/internals/engine.md +33 -7
  20. data/docs/internals/objectives.md +6 -5
  21. data/docs/internals/participants.md +7 -7
  22. data/docs/internals/prompt-caching.md +4 -2
  23. data/docs/internals/remote-sessions.md +7 -7
  24. data/docs/internals/web-frontend.md +2 -2
  25. data/docs/internals/web-push.md +1 -1
  26. data/docs/memory.md +2 -2
  27. data/docs/plugins.md +23 -28
  28. data/docs/sessions.md +24 -24
  29. data/docs/sub-agent.md +2 -2
  30. data/docs/system-one.md +1 -1
  31. data/docs/testing.md +31 -6
  32. data/lib/samagotchi/answer_text.rb +33 -0
  33. data/lib/samagotchi/bridge/card_places.rb +56 -7
  34. data/lib/samagotchi/bridge/card_store.rb +24 -9
  35. data/lib/samagotchi/bridge.rb +52 -32
  36. data/lib/samagotchi/bridge_client.rb +19 -14
  37. data/lib/samagotchi/broadcast/recipients.rb +2 -2
  38. data/lib/samagotchi/broadcast/triage.rb +1 -1
  39. data/lib/samagotchi/broadcast_command.rb +5 -5
  40. data/lib/samagotchi/bundle_command/try/command.rb +1 -1
  41. data/lib/samagotchi/bundle_command/try/run.rb +1 -1
  42. data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
  43. data/lib/samagotchi/bundle_command.rb +2 -2
  44. data/lib/samagotchi/bundles/btw/manifest.yml +2 -2
  45. data/lib/samagotchi/bundles/btw/plugin.rb +1 -1
  46. data/lib/samagotchi/bundles/coordinator/manifest.yml +2 -2
  47. data/lib/samagotchi/bundles/coordinator/plugin.rb +3 -2
  48. data/lib/samagotchi/bundles/decision-card/manifest.yml +4 -4
  49. data/lib/samagotchi/bundles/decision-card/plugin.rb +1 -9
  50. data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
  51. data/lib/samagotchi/bundles/github-pr/manifest.yml +2 -2
  52. data/lib/samagotchi/bundles/github-pr/plugin.rb +3 -3
  53. data/lib/samagotchi/bundles/skills/manifest.yml +2 -2
  54. data/lib/samagotchi/bundles/skills/plugin.rb +3 -2
  55. data/lib/samagotchi/bundles/system/config_modification_protocol.md +3 -4
  56. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  57. data/lib/samagotchi/bundles/system/self_map.md +1 -1
  58. data/lib/samagotchi/child_move.rb +3 -3
  59. data/lib/samagotchi/commands/registry.rb +6 -6
  60. data/lib/samagotchi/composer_route.rb +4 -5
  61. data/lib/samagotchi/config.rb +14 -12
  62. data/lib/samagotchi/context_candidate_choice.rb +1 -1
  63. data/lib/samagotchi/context_candidates.rb +4 -3
  64. data/lib/samagotchi/continue_offer.rb +1 -1
  65. data/lib/samagotchi/debug_log.rb +1 -1
  66. data/lib/samagotchi/degrade.rb +75 -0
  67. data/lib/samagotchi/desktop/macos/ChiRunner.swift +3 -3
  68. data/lib/samagotchi/engine/cards.rb +87 -0
  69. data/lib/samagotchi/engine/context_judge.rb +138 -0
  70. data/lib/samagotchi/engine/events.rb +131 -0
  71. data/lib/samagotchi/engine/guardrails.rb +87 -0
  72. data/lib/samagotchi/engine/hooks.rb +119 -0
  73. data/lib/samagotchi/engine/messages.rb +124 -0
  74. data/lib/samagotchi/engine/model_settings.rb +127 -0
  75. data/lib/samagotchi/engine/models.rb +261 -0
  76. data/lib/samagotchi/engine/objectives.rb +116 -0
  77. data/lib/samagotchi/engine/participants.rb +86 -0
  78. data/lib/samagotchi/engine/plugins.rb +208 -0
  79. data/lib/samagotchi/engine/questions.rb +98 -0
  80. data/lib/samagotchi/engine/recap.rb +130 -0
  81. data/lib/samagotchi/engine/session.rb +131 -0
  82. data/lib/samagotchi/engine/side_requests.rb +135 -0
  83. data/lib/samagotchi/engine/steering.rb +134 -0
  84. data/lib/samagotchi/engine/toolkit.rb +269 -0
  85. data/lib/samagotchi/engine/turn_end.rb +180 -0
  86. data/lib/samagotchi/engine/turn_helpers.rb +160 -0
  87. data/lib/samagotchi/engine/verify.rb +153 -0
  88. data/lib/samagotchi/engine.rb +62 -2646
  89. data/lib/samagotchi/engine_snapshots.rb +9 -8
  90. data/lib/samagotchi/grants.rb +575 -0
  91. data/lib/samagotchi/guardrail_wiring.rb +2 -2
  92. data/lib/samagotchi/guardrails/context.rb +2 -3
  93. data/lib/samagotchi/guardrails/parent_approvals.rb +2 -2
  94. data/lib/samagotchi/guardrails/parent_policy.rb +2 -2
  95. data/lib/samagotchi/hook_routes.rb +3 -3
  96. data/lib/samagotchi/idle_recap.rb +7 -6
  97. data/lib/samagotchi/idle_reminders.rb +3 -4
  98. data/lib/samagotchi/iteration_limit.rb +2 -2
  99. data/lib/samagotchi/kernel_loop.rb +13 -13
  100. data/lib/samagotchi/launch_mode.rb +13 -32
  101. data/lib/samagotchi/level_setting_command.rb +3 -2
  102. data/lib/samagotchi/llm/chat_loop.rb +3 -1
  103. data/lib/samagotchi/llm_context_command.rb +3 -2
  104. data/lib/samagotchi/log.rb +7 -7
  105. data/lib/samagotchi/log_line.rb +1 -1
  106. data/lib/samagotchi/log_path.rb +1 -1
  107. data/lib/samagotchi/note_command.rb +1 -1
  108. data/lib/samagotchi/note_delivery.rb +18 -8
  109. data/lib/samagotchi/objectives.rb +24 -3
  110. data/lib/samagotchi/objectives_check.rb +8 -27
  111. data/lib/samagotchi/objectives_command.rb +4 -69
  112. data/lib/samagotchi/objectives_edit.rb +87 -0
  113. data/lib/samagotchi/output_formatter.rb +7 -1
  114. data/lib/samagotchi/owner_lock.rb +7 -1
  115. data/lib/samagotchi/parent_answers_command.rb +5 -4
  116. data/lib/samagotchi/participant_exposure.rb +42 -0
  117. data/lib/samagotchi/participant_mode.rb +1 -1
  118. data/lib/samagotchi/participant_name.rb +1 -1
  119. data/lib/samagotchi/participants.rb +17 -17
  120. data/lib/samagotchi/participants_watch.rb +1 -1
  121. data/lib/samagotchi/plugin/api.rb +3 -2
  122. data/lib/samagotchi/plugin/context.rb +21 -7
  123. data/lib/samagotchi/plugin/peer.rb +2 -1
  124. data/lib/samagotchi/plugin/sessions.rb +6 -4
  125. data/lib/samagotchi/plugin_tasks.rb +1 -2
  126. data/lib/samagotchi/private_files.rb +29 -0
  127. data/lib/samagotchi/project_env.rb +2 -2
  128. data/lib/samagotchi/question_desk.rb +8 -7
  129. data/lib/samagotchi/recap_command.rb +2 -1
  130. data/lib/samagotchi/recap_setup.rb +1 -1
  131. data/lib/samagotchi/recap_store.rb +1 -1
  132. data/lib/samagotchi/redact.rb +86 -0
  133. data/lib/samagotchi/reminder_queue.rb +2 -2
  134. data/lib/samagotchi/remote_attach_command.rb +1 -1
  135. data/lib/samagotchi/reply_wait/poll.rb +2 -2
  136. data/lib/samagotchi/review_start.rb +1 -1
  137. data/lib/samagotchi/run_options.rb +6 -6
  138. data/lib/samagotchi/self_report.rb +42 -39
  139. data/lib/samagotchi/send_command/busy.rb +1 -1
  140. data/lib/samagotchi/send_command.rb +4 -1
  141. data/lib/samagotchi/session.rb +11 -2
  142. data/lib/samagotchi/session_access/contract.rb +3 -3
  143. data/lib/samagotchi/session_access/local.rb +9 -9
  144. data/lib/samagotchi/session_access/remote.rb +17 -7
  145. data/lib/samagotchi/session_access.rb +4 -4
  146. data/lib/samagotchi/session_archive_command.rb +2 -2
  147. data/lib/samagotchi/session_chain.rb +8 -6
  148. data/lib/samagotchi/session_commands.rb +45 -48
  149. data/lib/samagotchi/session_continue.rb +1 -1
  150. data/lib/samagotchi/session_delete_command.rb +4 -4
  151. data/lib/samagotchi/session_manager.rb +67 -49
  152. data/lib/samagotchi/session_metrics.rb +2 -2
  153. data/lib/samagotchi/session_model_settings.rb +3 -2
  154. data/lib/samagotchi/session_participants.rb +1 -1
  155. data/lib/samagotchi/session_retention.rb +27 -7
  156. data/lib/samagotchi/sessions_command.rb +4 -4
  157. data/lib/samagotchi/share_command.rb +10 -10
  158. data/lib/samagotchi/steer.rb +13 -2
  159. data/lib/samagotchi/stream_event.rb +22 -0
  160. data/lib/samagotchi/system_prompt.rb +154 -145
  161. data/lib/samagotchi/task_watch.rb +1 -1
  162. data/lib/samagotchi/terminal_ui/attach_launcher.rb +72 -10
  163. data/lib/samagotchi/terminal_ui/attached_loop/composer.rb +12 -9
  164. data/lib/samagotchi/terminal_ui/attached_loop/exit_commands.rb +16 -0
  165. data/lib/samagotchi/terminal_ui/attached_loop/questions.rb +2 -3
  166. data/lib/samagotchi/terminal_ui/attached_loop.rb +17 -8
  167. data/lib/samagotchi/terminal_ui/attached_view.rb +1 -11
  168. data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -5
  169. data/lib/samagotchi/terminal_ui/formatting.rb +7 -7
  170. data/lib/samagotchi/terminal_ui/input_support.rb +17 -22
  171. data/lib/samagotchi/terminal_ui/join_replay.rb +16 -12
  172. data/lib/samagotchi/terminal_ui/line_reader.rb +2 -2
  173. data/lib/samagotchi/terminal_ui/live_region.rb +2 -2
  174. data/lib/samagotchi/terminal_ui/log_tail.rb +135 -0
  175. data/lib/samagotchi/terminal_ui/one_shot_sink.rb +2 -2
  176. data/lib/samagotchi/terminal_ui/plain_surface.rb +1 -1
  177. data/lib/samagotchi/terminal_ui/question_prompt.rb +4 -5
  178. data/lib/samagotchi/terminal_ui/reline_seam.rb +2 -12
  179. data/lib/samagotchi/terminal_ui/status_row.rb +6 -14
  180. data/lib/samagotchi/terminal_ui.rb +142 -1387
  181. data/lib/samagotchi/title_command.rb +2 -2
  182. data/lib/samagotchi/tool_runner.rb +15 -16
  183. data/lib/samagotchi/toolkit_command.rb +2 -1
  184. data/lib/samagotchi/tools/delegate.rb +3 -1
  185. data/lib/samagotchi/tools/delegate_cursor.rb +3 -3
  186. data/lib/samagotchi/tools/delegate_wait.rb +1 -1
  187. data/lib/samagotchi/tools/objectives_tool.rb +2 -2
  188. data/lib/samagotchi/tools/send_note.rb +2 -1
  189. data/lib/samagotchi/turn_endings.rb +2 -2
  190. data/lib/samagotchi/turn_flow.rb +11 -7
  191. data/lib/samagotchi/update_hint.rb +2 -2
  192. data/lib/samagotchi/version.rb +1 -1
  193. data/lib/samagotchi/web/app.rb +42 -51
  194. data/lib/samagotchi/web/message_display.rb +7 -0
  195. data/lib/samagotchi/web/participant_access.rb +2 -2
  196. data/lib/samagotchi/web/participant_review.rb +5 -5
  197. data/lib/samagotchi/web/participant_stream_filter.rb +8 -8
  198. data/lib/samagotchi/web/participant_view.rb +2 -82
  199. data/lib/samagotchi/web/participants_admin.rb +1 -1
  200. data/lib/samagotchi/web/public/block_host.js +35 -8
  201. data/lib/samagotchi/web/public/block_renderers.css +9 -5
  202. data/lib/samagotchi/web/public/card_stamps.js +27 -0
  203. data/lib/samagotchi/web/public/cards_place.js +5 -3
  204. data/lib/samagotchi/web/public/chains.js +2 -2
  205. data/lib/samagotchi/web/public/claim_link.js +4 -4
  206. data/lib/samagotchi/web/public/composer_mode.js +1 -1
  207. data/lib/samagotchi/web/public/format.js +4 -3
  208. data/lib/samagotchi/web/public/turn_html.js +8 -4
  209. data/lib/samagotchi/web/push/store.rb +1 -1
  210. data/lib/samagotchi/web/session_access_routes.rb +13 -4
  211. data/lib/samagotchi/web/session_control.rb +12 -8
  212. data/lib/samagotchi/web/session_create.rb +1 -1
  213. data/lib/samagotchi/web/session_hub.rb +2 -2
  214. data/lib/samagotchi/web/session_show.rb +6 -2
  215. data/lib/samagotchi/web/session_summary.rb +1 -1
  216. data/lib/samagotchi/worker.rb +103 -9
  217. data/lib/samagotchi/worker_link.rb +3 -3
  218. data/lib/samagotchi/working_set_command.rb +3 -2
  219. data/lib/samagotchi.rb +0 -1
  220. metadata +31 -7
  221. data/lib/samagotchi/pending_input_queue.rb +0 -55
  222. data/lib/samagotchi/terminal_ui/between_turns.rb +0 -138
  223. data/lib/samagotchi/terminal_ui/repl_input.rb +0 -152
  224. data/lib/samagotchi/web/grants.rb +0 -575
  225. data/lib/samagotchi/web/participant_exposure.rb +0 -44
  226. data/lib/samagotchi/web/private_files.rb +0 -31
@@ -7,6 +7,7 @@ require_relative "session_setting"
7
7
  require_relative "steer_cut"
8
8
  require_relative "steer_cut_after"
9
9
  require_relative "thinking"
10
+ require_relative "session"
10
11
 
11
12
  module Samagotchi
12
13
  # The session's settings on its effective model, as the Engine reads them:
@@ -163,7 +164,7 @@ module Samagotchi
163
164
  # @raise [ArgumentError] with no session yet
164
165
  def reasoning_history_override=(level)
165
166
  session = @session.call
166
- raise ArgumentError, "no session yet" unless session
167
+ raise ArgumentError, Session::NONE_YET unless session
167
168
 
168
169
  SessionSetting::REASONING_HISTORY.set(session, level)
169
170
  end
@@ -178,7 +179,7 @@ module Samagotchi
178
179
  # @raise [ArgumentError] with no session yet
179
180
  def thinking_override=(level)
180
181
  session = @session.call
181
- raise ArgumentError, "no session yet" unless session
182
+ raise ArgumentError, Session::NONE_YET unless session
182
183
 
183
184
  SessionSetting::THINKING.set(session, level)
184
185
  end
@@ -4,7 +4,7 @@ require_relative "steer"
4
4
  require_relative "participant_mode"
5
5
 
6
6
  module Samagotchi
7
- # Loaded only when a session is read: it brings Web::Grants (and Rack).
7
+ # Loaded only when a session is read: it brings Grants.
8
8
  autoload :Participants, File.expand_path("participants", __dir__)
9
9
 
10
10
  # What a session's Engine knows of its participants: participants.json
@@ -18,7 +18,7 @@ module Samagotchi
18
18
  # (session.retention_days), count (session.max_count, counting only the
19
19
  # sessions that stay), statuses kept (session.keep_status), sessions left
20
20
  # empty (SessionManager.discardable?, an hour on), leftover scratch
21
- # sessions and skeleton-only directories; a session with a live owner
21
+ # sessions (once past SCRATCH_START_GRACE) and skeleton-only directories; a session with a live owner
22
22
  # always stays.
23
23
  module SessionRetention
24
24
  # The session.retention_days / max_count / keep_status defaults.
@@ -30,7 +30,7 @@ module Samagotchi
30
30
  DEFAULT_KEEP_STATUS = [].freeze
31
31
 
32
32
  # How long a session may sit empty before the sweep takes it: its
33
- # worker (or a REPL) deletes it as it leaves, so the sweep only catches
33
+ # worker deletes it as it leaves, so the sweep only catches
34
34
  # those killed first (a reboot, kill -9).
35
35
  EMPTY_GRACE_SECONDS = 3600
36
36
 
@@ -69,7 +69,7 @@ module Samagotchi
69
69
  end
70
70
 
71
71
  # Directories with no session file and nothing but the skeleton, nobody
72
- # owning them: a REPL killed before its first save, or a worker woken
72
+ # owning them: a one-shot killed before its first save, or a worker woken
73
73
  # just as its session was discarded.
74
74
  # @return [Array<String>] their ids
75
75
  private_class_method def self.prune_orphan_dirs(state_dir, dry_run:)
@@ -93,6 +93,16 @@ module Samagotchi
93
93
  end
94
94
  end
95
95
 
96
+ # How long after its save an unowned scratch session may still be
97
+ # waiting for the worker just spawned for it to take its owner lock.
98
+ SCRATCH_START_GRACE = SessionManager::WORKER_START_GRACE
99
+
100
+ private_class_method def self.scratch_starting?(path, now)
101
+ now - File.mtime(path) < SCRATCH_START_GRACE
102
+ rescue SystemCallError
103
+ false
104
+ end
105
+
96
106
  # Lazy sweep guard: runs prune at most once per SWEEP_INTERVAL_HOURS
97
107
  # (session.sweep_interval_hours).
98
108
  MARKER = ".last_retention"
@@ -161,8 +171,10 @@ module Samagotchi
161
171
  # +any_age+ makes every session eligible, whatever its age and the
162
172
  # count (`chi sessions clean` with no --days: test runs are throwaway).
163
173
  #
164
- # A scratch session nobody owns (its REPL was killed) goes whatever its
165
- # age, its status and the count; +test_only+ takes it too.
174
+ # A scratch session nobody owns (its terminal and worker were killed) goes whatever its
175
+ # age, its status and the count; +test_only+ takes it too. One saved
176
+ # within SCRATCH_START_GRACE (its file's mtime, against +now+) stays: its
177
+ # worker may not have taken the owner lock yet.
166
178
  #
167
179
  # +max_count+ counts the sessions that stay: one deleted whatever its
168
180
  # place (scratch, left empty, expired, +any_age+) takes no slot.
@@ -173,7 +185,7 @@ module Samagotchi
173
185
  # @return [Hash] { deleted: [ids], kept: [ids], skipped: [ids] }
174
186
  def self.apply(state_dir: Session.default_state_dir, days: DEFAULT_RETENTION_DAYS, max_count: DEFAULT_MAX_COUNT,
175
187
  keep_status: DEFAULT_KEEP_STATUS, dry_run: false, test_only: false, alive_check: nil,
176
- empty_check: nil, any_age: false)
188
+ empty_check: nil, any_age: false, now: Time.now)
177
189
  keep_status = Array(keep_status).map(&:to_s)
178
190
  # Fetch all sessions sorted newest-first for count logic
179
191
  all = Session.list(state_dir: state_dir, sort: "updated_at", order: "desc")
@@ -182,7 +194,6 @@ module Samagotchi
182
194
  all = all.select { |session| session.test_run || session.scratch }
183
195
  end
184
196
 
185
- now = Time.now
186
197
  cutoff = days.to_i.positive? ? now - (days.to_i * 86_400) : nil
187
198
  max = max_count.to_i
188
199
 
@@ -217,6 +228,15 @@ module Samagotchi
217
228
  end
218
229
  end
219
230
 
231
+ # A scratch session saved within SCRATCH_START_GRACE: `chi scratch`
232
+ # saves it, then spawns the worker that takes its owner lock, so no
233
+ # owner yet doesn't mean a leftover. Kept, it takes no count slot.
234
+ if session.scratch && scratch_starting?(path, now)
235
+ kept << session.id
236
+ passed_over += 1
237
+ next
238
+ end
239
+
220
240
  left_empty = begin
221
241
  empty_check&.call(session.id)
222
242
  rescue StandardError
@@ -21,7 +21,7 @@ module Samagotchi
21
21
  # `chi sessions`: list, stop, restart, archive/unarchive, delete, prune,
22
22
  # clean and stats sessions from the shell (bin/chi dispatches here before
23
23
  # OptionParser; the subcommands have their own flags). Not SessionCommands,
24
- # the REPL's slash commands for sessions (session_commands.rb).
24
+ # the slash commands a session's worker runs (session_commands.rb).
25
25
  class SessionsCommand
26
26
  include CLI::Command
27
27
 
@@ -207,15 +207,15 @@ module Samagotchi
207
207
  )
208
208
  case format
209
209
  when "json"
210
- # owner: "worker", "tui" (a chi REPL: it takes no notes or messages)
210
+ # owner: "worker", "tui" (a one-turn chi run: it takes no notes or messages)
211
211
  # or nil; recap: the saved recap's first sentence, or nil; project:
212
212
  # its git project's root, or nil
213
213
  # parent_id: the session that delegated it (the delegate tool), or nil
214
214
  # delegate: started by the delegate tool (a fork has a parent_id too)
215
215
  # continues: the session it continues (its chain's previous link), or nil
216
216
  # archived: hidden from the lists (only with --archived can it be true)
217
- # scratch: a `chi scratch` session (deleted when its REPL ends, a
218
- # leftover one at the next sweep)
217
+ # scratch: a `chi scratch` session (deleted when its terminal leaves,
218
+ # a leftover one at the next sweep)
219
219
  # ctx_pct: how full the context was after the last turn, or nil
220
220
  # title: the session's own name (/title; desc shows it too), or nil;
221
221
  # auto_title: the one its recap gave it (RecapTitle), or nil
@@ -8,12 +8,12 @@ require_relative "cli/flags"
8
8
  require_relative "session"
9
9
  require_relative "session_access"
10
10
  require_relative "session_access/local"
11
- require_relative "web/grants"
11
+ require_relative "grants"
12
12
  require_relative "web/qr"
13
13
 
14
14
  module Samagotchi
15
15
  # `chi share`: invite a participant to review a session (the
16
- # shared-sessions plan: a Web::Grants grant, one session, one person or
16
+ # shared-sessions plan: a Grants grant, one session, one person or
17
17
  # agent, 7 days unless --expires says otherwise), list the participants,
18
18
  # revoke them. Local or through chi --remote NAME (SessionAccess). The
19
19
  # token is printed once, in the link; chi keeps only its digest. See
@@ -95,9 +95,9 @@ module Samagotchi
95
95
  return parsed if parsed.is_a?(Integer)
96
96
 
97
97
  send(:"run_#{@sub || "invite"}", parsed.options, parsed.args)
98
- rescue SessionAccess::BadRequest, Web::Grants::BadIdentity => e
98
+ rescue SessionAccess::BadRequest, Grants::BadIdentity => e
99
99
  usage_error(e.message)
100
- rescue Web::Grants::Unwritable => e
100
+ rescue Grants::Unwritable => e
101
101
  error_line("#{command_name}: #{e.message}")
102
102
  CLI::Exit::FAILED
103
103
  rescue ArgumentError => e
@@ -114,13 +114,13 @@ module Samagotchi
114
114
  return usage_error("give one session ID") unless args.size == 1
115
115
  return usage_error("--name NAME: who the link is for") if options[:name].to_s.strip.empty?
116
116
 
117
- ttl = Web::Grants.parse_ttl(options[:expires])
117
+ ttl = Grants.parse_ttl(options[:expires])
118
118
  return usage_error("--expires takes Nh or Nd, 1h to 30d (e.g. 3d)") if ttl == false
119
119
 
120
120
  cap = options[:cap] && dollars(options[:cap])
121
121
  return usage_error(CAP_USAGE) if cap == false
122
122
 
123
- identity = options[:identity] && Web::Grants.clean_identity(options[:identity])
123
+ identity = options[:identity] && Grants.clean_identity(options[:identity])
124
124
 
125
125
  id = @access.resolve(args.first)
126
126
  invitation = @access.invite(id, name: options[:name], kind: options[:agent] ? "agent" : "human", ttl: ttl, cap: cap,
@@ -149,13 +149,13 @@ module Samagotchi
149
149
  0
150
150
  end
151
151
 
152
- CAP_USAGE = "a cap is dollars, $0 to $#{Web::Grants::MAX_CAP.to_i} (e.g. --cap 5)".freeze
152
+ CAP_USAGE = "a cap is dollars, $0 to $#{Grants::MAX_CAP.to_i} (e.g. --cap 5)".freeze
153
153
 
154
154
  def run_cap(_options, args)
155
155
  return usage_error("give a session ID, a participant and dollars (chi share cap ID p_… 5)") unless args.size == 3
156
156
 
157
157
  participant = args[1]
158
- return usage_error("not a participant: #{participant} (chi share ls lists them)") unless Web::Grants::ID_RE.match?(participant)
158
+ return usage_error("not a participant: #{participant} (chi share ls lists them)") unless Grants::ID_RE.match?(participant)
159
159
 
160
160
  cap = dollars(args[2])
161
161
  return usage_error(CAP_USAGE) if cap == false
@@ -172,7 +172,7 @@ module Samagotchi
172
172
  return false unless text.match?(/\A\d+(?:\.\d+)?\z/)
173
173
 
174
174
  value = text.to_f
175
- Web::Grants.cap?(value) ? value : false
175
+ Grants.cap?(value) ? value : false
176
176
  end
177
177
 
178
178
  def money(value) = value.nil? ? "-" : format("$%.2f", value)
@@ -184,7 +184,7 @@ module Samagotchi
184
184
  return usage_error("give a session ID (and a participant), or --all")
185
185
  end
186
186
  participant = args[1]
187
- return usage_error("not a participant: #{participant} (chi share ls lists them)") if participant && !Web::Grants::ID_RE.match?(participant)
187
+ return usage_error("not a participant: #{participant} (chi share ls lists them)") if participant && !Grants::ID_RE.match?(participant)
188
188
 
189
189
  id = args.first && @access.resolve(args.first)
190
190
  revoked = @access.revoke_participants(id, participant: participant)
@@ -28,6 +28,12 @@ module Samagotchi
28
28
  module Steer
29
29
  KIND = "steer"
30
30
  INPUT_KIND = "input"
31
+ # KernelLoop's line asking a Qwen model to finish the tool call it left
32
+ # open: a user line inside the running turn, the model's alone. No turn
33
+ # opens with it (#turn_prompt?), and the page draws nothing for it, as it
34
+ # showed nothing live. Its kind adds no header (#header): its bytes go
35
+ # out as before it had one.
36
+ RECOVERY_KIND = "recovery"
31
37
 
32
38
  # A drain item for an input line with its sender (source nil = the user).
33
39
  # A plain String is still a valid item: the user's line. +mark+: fields
@@ -136,7 +142,7 @@ module Samagotchi
136
142
  PARTICIPANT_SOURCE = "participant"
137
143
  # The owner's author, when one is written out; absent means the same.
138
144
  OWNER_AUTHOR = "owner"
139
- # A participant's author: this prefix, then its id (Web::Grants'
145
+ # A participant's author: this prefix, then its id (Grants'
140
146
  # grant id), "p:<id>" (#author_for, #participant_id).
141
147
  PARTICIPANT_PREFIX = "p:"
142
148
  # A participant's message's keys, which every message copy keeps.
@@ -317,6 +323,11 @@ module Samagotchi
317
323
  message.is_a?(Hash) && (message[:role] || message["role"]).to_s == "user" && !steer?(message)
318
324
  end
319
325
 
326
+ # KernelLoop's recovery line (RECOVERY_KIND).
327
+ def recovery?(message)
328
+ message.is_a?(Hash) && (message[:kind] || message["kind"]).to_s == RECOVERY_KIND
329
+ end
330
+
320
331
  # The user's lines merged into a running turn (Merge#messages).
321
332
  def input?(message)
322
333
  message.is_a?(Hash) && (message[:kind] || message["kind"]).to_s == INPUT_KIND
@@ -333,7 +344,7 @@ module Samagotchi
333
344
  start = (message[:turn_start] || message["turn_start"]) == true
334
345
  return start if (message[:role] || message["role"]).to_s == "system"
335
346
 
336
- prompt?(message) && (!input?(message) || start)
347
+ prompt?(message) && !recovery?(message) && (!input?(message) || start)
337
348
  end
338
349
 
339
350
  # A stored `!shell` line: "!(cmd)\n<output>", no turn id. A user message,
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "degrade"
4
+
5
+ module Samagotchi
6
+ # The one rule for handing a loop event to its on_stream_event callback
7
+ # (KernelLoop#emit_stream_event, ToolRunner#emit): no callback is fine, and
8
+ # a failing one (a UI gone) must not break the turn or the tool call. Its
9
+ # failure is a Degrade record under :turn, named by the caller (+failure+,
10
+ # so a log still says which side lost the event), with the event's type.
11
+ module StreamEvent
12
+ module_function
13
+
14
+ # @param callback [#call, nil] the loop's on_stream_event
15
+ # @param event [Hash] the event
16
+ # @param failure [String] the Degrade event slug when +callback+ raises
17
+ # @return the callback's value, nil when it raised or there is none
18
+ def emit(callback, event, failure:)
19
+ Degrade.call(:turn, failure, type: (event[:type] if event.is_a?(Hash))) { callback&.call(event) }
20
+ end
21
+ end
22
+ end