samagotchi 0.153.0 → 0.154.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 (205) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -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 +80 -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 +3 -3
  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 +1 -1
  23. data/docs/internals/remote-sessions.md +7 -7
  24. data/docs/internals/web-push.md +1 -1
  25. data/docs/memory.md +2 -2
  26. data/docs/plugins.md +23 -28
  27. data/docs/sessions.md +24 -24
  28. data/docs/sub-agent.md +2 -2
  29. data/docs/system-one.md +1 -1
  30. data/docs/testing.md +16 -6
  31. data/lib/samagotchi/answer_text.rb +34 -0
  32. data/lib/samagotchi/bridge.rb +43 -32
  33. data/lib/samagotchi/bridge_client.rb +10 -9
  34. data/lib/samagotchi/broadcast/recipients.rb +2 -2
  35. data/lib/samagotchi/broadcast/triage.rb +1 -1
  36. data/lib/samagotchi/broadcast_command.rb +5 -5
  37. data/lib/samagotchi/bundle_command/try/command.rb +1 -1
  38. data/lib/samagotchi/bundle_command/try/run.rb +1 -1
  39. data/lib/samagotchi/bundle_command.rb +2 -2
  40. data/lib/samagotchi/bundles/decision-card/manifest.yml +4 -4
  41. data/lib/samagotchi/bundles/decision-card/plugin.rb +1 -9
  42. data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
  43. data/lib/samagotchi/bundles/system/config_modification_protocol.md +3 -4
  44. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  45. data/lib/samagotchi/bundles/system/self_map.md +1 -1
  46. data/lib/samagotchi/child_move.rb +3 -3
  47. data/lib/samagotchi/commands/registry.rb +6 -6
  48. data/lib/samagotchi/composer_route.rb +4 -5
  49. data/lib/samagotchi/config.rb +14 -12
  50. data/lib/samagotchi/context_candidate_choice.rb +1 -1
  51. data/lib/samagotchi/context_candidates.rb +4 -3
  52. data/lib/samagotchi/continue_offer.rb +1 -1
  53. data/lib/samagotchi/debug_log.rb +1 -1
  54. data/lib/samagotchi/degrade.rb +75 -0
  55. data/lib/samagotchi/desktop/macos/ChiRunner.swift +2 -2
  56. data/lib/samagotchi/engine/cards.rb +87 -0
  57. data/lib/samagotchi/engine/context_judge.rb +138 -0
  58. data/lib/samagotchi/engine/events.rb +131 -0
  59. data/lib/samagotchi/engine/guardrails.rb +87 -0
  60. data/lib/samagotchi/engine/hooks.rb +119 -0
  61. data/lib/samagotchi/engine/messages.rb +124 -0
  62. data/lib/samagotchi/engine/model_settings.rb +127 -0
  63. data/lib/samagotchi/engine/models.rb +261 -0
  64. data/lib/samagotchi/engine/objectives.rb +116 -0
  65. data/lib/samagotchi/engine/participants.rb +86 -0
  66. data/lib/samagotchi/engine/plugins.rb +208 -0
  67. data/lib/samagotchi/engine/questions.rb +98 -0
  68. data/lib/samagotchi/engine/recap.rb +130 -0
  69. data/lib/samagotchi/engine/session.rb +131 -0
  70. data/lib/samagotchi/engine/side_requests.rb +135 -0
  71. data/lib/samagotchi/engine/steering.rb +134 -0
  72. data/lib/samagotchi/engine/toolkit.rb +269 -0
  73. data/lib/samagotchi/engine/turn_end.rb +180 -0
  74. data/lib/samagotchi/engine/turn_helpers.rb +160 -0
  75. data/lib/samagotchi/engine/verify.rb +153 -0
  76. data/lib/samagotchi/engine.rb +62 -2646
  77. data/lib/samagotchi/grants.rb +575 -0
  78. data/lib/samagotchi/guardrail_wiring.rb +2 -2
  79. data/lib/samagotchi/guardrails/context.rb +2 -3
  80. data/lib/samagotchi/guardrails/parent_approvals.rb +2 -2
  81. data/lib/samagotchi/guardrails/parent_policy.rb +2 -2
  82. data/lib/samagotchi/hook_routes.rb +3 -3
  83. data/lib/samagotchi/idle_recap.rb +4 -5
  84. data/lib/samagotchi/idle_reminders.rb +3 -4
  85. data/lib/samagotchi/iteration_limit.rb +2 -2
  86. data/lib/samagotchi/kernel_loop.rb +10 -11
  87. data/lib/samagotchi/launch_mode.rb +13 -32
  88. data/lib/samagotchi/level_setting_command.rb +3 -2
  89. data/lib/samagotchi/llm_context_command.rb +3 -2
  90. data/lib/samagotchi/log.rb +7 -7
  91. data/lib/samagotchi/log_line.rb +1 -1
  92. data/lib/samagotchi/log_path.rb +1 -1
  93. data/lib/samagotchi/note_command.rb +1 -1
  94. data/lib/samagotchi/note_delivery.rb +18 -8
  95. data/lib/samagotchi/objectives.rb +24 -3
  96. data/lib/samagotchi/objectives_check.rb +8 -27
  97. data/lib/samagotchi/objectives_command.rb +4 -69
  98. data/lib/samagotchi/objectives_edit.rb +87 -0
  99. data/lib/samagotchi/output_formatter.rb +7 -1
  100. data/lib/samagotchi/owner_lock.rb +7 -1
  101. data/lib/samagotchi/parent_answers_command.rb +5 -4
  102. data/lib/samagotchi/participant_exposure.rb +42 -0
  103. data/lib/samagotchi/participant_mode.rb +1 -1
  104. data/lib/samagotchi/participant_name.rb +1 -1
  105. data/lib/samagotchi/participants.rb +17 -17
  106. data/lib/samagotchi/participants_watch.rb +1 -1
  107. data/lib/samagotchi/pending_input_queue.rb +2 -2
  108. data/lib/samagotchi/plugin/api.rb +3 -2
  109. data/lib/samagotchi/plugin/context.rb +21 -7
  110. data/lib/samagotchi/plugin/peer.rb +2 -1
  111. data/lib/samagotchi/plugin/sessions.rb +6 -4
  112. data/lib/samagotchi/plugin_tasks.rb +1 -2
  113. data/lib/samagotchi/private_files.rb +29 -0
  114. data/lib/samagotchi/project_env.rb +2 -2
  115. data/lib/samagotchi/question_desk.rb +8 -7
  116. data/lib/samagotchi/recap_command.rb +2 -1
  117. data/lib/samagotchi/recap_setup.rb +1 -1
  118. data/lib/samagotchi/recap_store.rb +1 -1
  119. data/lib/samagotchi/redact.rb +86 -0
  120. data/lib/samagotchi/reminder_queue.rb +2 -2
  121. data/lib/samagotchi/remote_attach_command.rb +1 -1
  122. data/lib/samagotchi/reply_wait/poll.rb +2 -2
  123. data/lib/samagotchi/review_start.rb +1 -1
  124. data/lib/samagotchi/run_options.rb +6 -6
  125. data/lib/samagotchi/self_report.rb +42 -39
  126. data/lib/samagotchi/send_command/busy.rb +1 -1
  127. data/lib/samagotchi/send_command.rb +4 -1
  128. data/lib/samagotchi/session.rb +11 -2
  129. data/lib/samagotchi/session_access/contract.rb +3 -3
  130. data/lib/samagotchi/session_access/local.rb +9 -9
  131. data/lib/samagotchi/session_access/remote.rb +17 -7
  132. data/lib/samagotchi/session_access.rb +4 -4
  133. data/lib/samagotchi/session_archive_command.rb +2 -2
  134. data/lib/samagotchi/session_chain.rb +8 -6
  135. data/lib/samagotchi/session_commands.rb +45 -48
  136. data/lib/samagotchi/session_continue.rb +1 -1
  137. data/lib/samagotchi/session_delete_command.rb +4 -4
  138. data/lib/samagotchi/session_manager.rb +59 -36
  139. data/lib/samagotchi/session_metrics.rb +2 -2
  140. data/lib/samagotchi/session_model_settings.rb +3 -2
  141. data/lib/samagotchi/session_participants.rb +1 -1
  142. data/lib/samagotchi/session_retention.rb +3 -3
  143. data/lib/samagotchi/sessions_command.rb +4 -4
  144. data/lib/samagotchi/share_command.rb +10 -10
  145. data/lib/samagotchi/steer.rb +1 -1
  146. data/lib/samagotchi/system_prompt.rb +102 -109
  147. data/lib/samagotchi/task_watch.rb +1 -1
  148. data/lib/samagotchi/terminal_ui/attach_launcher.rb +72 -10
  149. data/lib/samagotchi/terminal_ui/attached_loop/composer.rb +12 -9
  150. data/lib/samagotchi/terminal_ui/attached_loop/exit_commands.rb +16 -0
  151. data/lib/samagotchi/terminal_ui/attached_loop/questions.rb +2 -3
  152. data/lib/samagotchi/terminal_ui/attached_loop.rb +17 -8
  153. data/lib/samagotchi/terminal_ui/attached_view.rb +2 -3
  154. data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -5
  155. data/lib/samagotchi/terminal_ui/formatting.rb +7 -7
  156. data/lib/samagotchi/terminal_ui/input_support.rb +1 -7
  157. data/lib/samagotchi/terminal_ui/line_reader.rb +2 -2
  158. data/lib/samagotchi/terminal_ui/live_region.rb +2 -2
  159. data/lib/samagotchi/terminal_ui/log_tail.rb +135 -0
  160. data/lib/samagotchi/terminal_ui/one_shot_sink.rb +2 -2
  161. data/lib/samagotchi/terminal_ui/plain_surface.rb +1 -1
  162. data/lib/samagotchi/terminal_ui/question_prompt.rb +4 -5
  163. data/lib/samagotchi/terminal_ui/reline_seam.rb +3 -3
  164. data/lib/samagotchi/terminal_ui/status_row.rb +7 -8
  165. data/lib/samagotchi/terminal_ui.rb +142 -1387
  166. data/lib/samagotchi/title_command.rb +2 -2
  167. data/lib/samagotchi/tool_runner.rb +14 -16
  168. data/lib/samagotchi/toolkit_command.rb +2 -1
  169. data/lib/samagotchi/tools/delegate.rb +3 -1
  170. data/lib/samagotchi/tools/delegate_cursor.rb +3 -3
  171. data/lib/samagotchi/tools/delegate_wait.rb +1 -1
  172. data/lib/samagotchi/tools/objectives_tool.rb +2 -2
  173. data/lib/samagotchi/tools/send_note.rb +2 -1
  174. data/lib/samagotchi/turn_endings.rb +2 -2
  175. data/lib/samagotchi/turn_flow.rb +4 -5
  176. data/lib/samagotchi/update_hint.rb +2 -2
  177. data/lib/samagotchi/version.rb +1 -1
  178. data/lib/samagotchi/web/app.rb +39 -50
  179. data/lib/samagotchi/web/participant_access.rb +2 -2
  180. data/lib/samagotchi/web/participant_review.rb +5 -5
  181. data/lib/samagotchi/web/participant_stream_filter.rb +8 -8
  182. data/lib/samagotchi/web/participant_view.rb +2 -82
  183. data/lib/samagotchi/web/participants_admin.rb +1 -1
  184. data/lib/samagotchi/web/public/block_host.js +35 -8
  185. data/lib/samagotchi/web/public/block_renderers.css +9 -5
  186. data/lib/samagotchi/web/public/chains.js +2 -2
  187. data/lib/samagotchi/web/public/claim_link.js +4 -4
  188. data/lib/samagotchi/web/public/composer_mode.js +1 -1
  189. data/lib/samagotchi/web/public/format.js +3 -3
  190. data/lib/samagotchi/web/push/store.rb +1 -1
  191. data/lib/samagotchi/web/session_access_routes.rb +5 -4
  192. data/lib/samagotchi/web/session_control.rb +12 -8
  193. data/lib/samagotchi/web/session_create.rb +1 -1
  194. data/lib/samagotchi/web/session_hub.rb +2 -2
  195. data/lib/samagotchi/web/session_show.rb +2 -2
  196. data/lib/samagotchi/web/session_summary.rb +1 -1
  197. data/lib/samagotchi/worker.rb +100 -9
  198. data/lib/samagotchi/worker_link.rb +3 -3
  199. data/lib/samagotchi/working_set_command.rb +3 -2
  200. metadata +29 -6
  201. data/lib/samagotchi/terminal_ui/between_turns.rb +0 -138
  202. data/lib/samagotchi/terminal_ui/repl_input.rb +0 -152
  203. data/lib/samagotchi/web/grants.rb +0 -575
  204. data/lib/samagotchi/web/participant_exposure.rb +0 -44
  205. data/lib/samagotchi/web/private_files.rb +0 -31
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.153.0
4
+ version: 0.154.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -185,6 +185,7 @@ files:
185
185
  - lib/samagotchi/answer_display.rb
186
186
  - lib/samagotchi/answer_refs.rb
187
187
  - lib/samagotchi/answer_tail.rb
188
+ - lib/samagotchi/answer_text.rb
188
189
  - lib/samagotchi/archive_store.rb
189
190
  - lib/samagotchi/atomic_file.rb
190
191
  - lib/samagotchi/block_renderers.rb
@@ -335,6 +336,7 @@ files:
335
336
  - lib/samagotchi/cut.rb
336
337
  - lib/samagotchi/cut_policy.rb
337
338
  - lib/samagotchi/debug_log.rb
339
+ - lib/samagotchi/degrade.rb
338
340
  - lib/samagotchi/delivery.rb
339
341
  - lib/samagotchi/desktop.rb
340
342
  - lib/samagotchi/desktop/macos.rb
@@ -356,6 +358,26 @@ files:
356
358
  - lib/samagotchi/effective_model.rb
357
359
  - lib/samagotchi/empty_answer_retry.rb
358
360
  - lib/samagotchi/engine.rb
361
+ - lib/samagotchi/engine/cards.rb
362
+ - lib/samagotchi/engine/context_judge.rb
363
+ - lib/samagotchi/engine/events.rb
364
+ - lib/samagotchi/engine/guardrails.rb
365
+ - lib/samagotchi/engine/hooks.rb
366
+ - lib/samagotchi/engine/messages.rb
367
+ - lib/samagotchi/engine/model_settings.rb
368
+ - lib/samagotchi/engine/models.rb
369
+ - lib/samagotchi/engine/objectives.rb
370
+ - lib/samagotchi/engine/participants.rb
371
+ - lib/samagotchi/engine/plugins.rb
372
+ - lib/samagotchi/engine/questions.rb
373
+ - lib/samagotchi/engine/recap.rb
374
+ - lib/samagotchi/engine/session.rb
375
+ - lib/samagotchi/engine/side_requests.rb
376
+ - lib/samagotchi/engine/steering.rb
377
+ - lib/samagotchi/engine/toolkit.rb
378
+ - lib/samagotchi/engine/turn_end.rb
379
+ - lib/samagotchi/engine/turn_helpers.rb
380
+ - lib/samagotchi/engine/verify.rb
359
381
  - lib/samagotchi/engine_peers.rb
360
382
  - lib/samagotchi/engine_snapshots.rb
361
383
  - lib/samagotchi/event_fanout.rb
@@ -367,6 +389,7 @@ files:
367
389
  - lib/samagotchi/generation_phase.rb
368
390
  - lib/samagotchi/git_env.rb
369
391
  - lib/samagotchi/git_head.rb
392
+ - lib/samagotchi/grants.rb
370
393
  - lib/samagotchi/guardrail_wiring.rb
371
394
  - lib/samagotchi/guardrails.rb
372
395
  - lib/samagotchi/guardrails/approval.rb
@@ -526,11 +549,13 @@ files:
526
549
  - lib/samagotchi/objectives_audit.rb
527
550
  - lib/samagotchi/objectives_check.rb
528
551
  - lib/samagotchi/objectives_command.rb
552
+ - lib/samagotchi/objectives_edit.rb
529
553
  - lib/samagotchi/output_formatter.rb
530
554
  - lib/samagotchi/owner_lock.rb
531
555
  - lib/samagotchi/owner_prompt.rb
532
556
  - lib/samagotchi/parent_answers_command.rb
533
557
  - lib/samagotchi/parent_report.rb
558
+ - lib/samagotchi/participant_exposure.rb
534
559
  - lib/samagotchi/participant_messages.rb
535
560
  - lib/samagotchi/participant_mode.rb
536
561
  - lib/samagotchi/participant_name.rb
@@ -557,6 +582,7 @@ files:
557
582
  - lib/samagotchi/plugin/tool_spec.rb
558
583
  - lib/samagotchi/plugin_session_state.rb
559
584
  - lib/samagotchi/plugin_tasks.rb
585
+ - lib/samagotchi/private_files.rb
560
586
  - lib/samagotchi/process_group.rb
561
587
  - lib/samagotchi/project_env.rb
562
588
  - lib/samagotchi/project_scope.rb
@@ -578,6 +604,7 @@ files:
578
604
  - lib/samagotchi/recap_setup.rb
579
605
  - lib/samagotchi/recap_store.rb
580
606
  - lib/samagotchi/recap_title.rb
607
+ - lib/samagotchi/redact.rb
581
608
  - lib/samagotchi/redacted_inspect.rb
582
609
  - lib/samagotchi/ref.rb
583
610
  - lib/samagotchi/relay.rb
@@ -674,7 +701,6 @@ files:
674
701
  - lib/samagotchi/terminal_ui/attached_loop/queueing.rb
675
702
  - lib/samagotchi/terminal_ui/attached_loop/sending.rb
676
703
  - lib/samagotchi/terminal_ui/attached_view.rb
677
- - lib/samagotchi/terminal_ui/between_turns.rb
678
704
  - lib/samagotchi/terminal_ui/event_renderer.rb
679
705
  - lib/samagotchi/terminal_ui/exit_lines.rb
680
706
  - lib/samagotchi/terminal_ui/formatting.rb
@@ -685,6 +711,7 @@ files:
685
711
  - lib/samagotchi/terminal_ui/line_reader.rb
686
712
  - lib/samagotchi/terminal_ui/live_region.rb
687
713
  - lib/samagotchi/terminal_ui/local_link.rb
714
+ - lib/samagotchi/terminal_ui/log_tail.rb
688
715
  - lib/samagotchi/terminal_ui/model_segments_text.rb
689
716
  - lib/samagotchi/terminal_ui/one_shot_sink.rb
690
717
  - lib/samagotchi/terminal_ui/own_turns.rb
@@ -696,7 +723,6 @@ files:
696
723
  - lib/samagotchi/terminal_ui/reline_seam.rb
697
724
  - lib/samagotchi/terminal_ui/remote_connector.rb
698
725
  - lib/samagotchi/terminal_ui/remote_link.rb
699
- - lib/samagotchi/terminal_ui/repl_input.rb
700
726
  - lib/samagotchi/terminal_ui/screen.rb
701
727
  - lib/samagotchi/terminal_ui/status_row.rb
702
728
  - lib/samagotchi/terminal_ui/surface.rb
@@ -810,20 +836,17 @@ files:
810
836
  - lib/samagotchi/web/block_renderer_routes.rb
811
837
  - lib/samagotchi/web/capabilities.rb
812
838
  - lib/samagotchi/web/editor.rb
813
- - lib/samagotchi/web/grants.rb
814
839
  - lib/samagotchi/web/info_probe.rb
815
840
  - lib/samagotchi/web/lan.rb
816
841
  - lib/samagotchi/web/link_codes.rb
817
842
  - lib/samagotchi/web/markdown_renderer.rb
818
843
  - lib/samagotchi/web/message_display.rb
819
844
  - lib/samagotchi/web/participant_access.rb
820
- - lib/samagotchi/web/participant_exposure.rb
821
845
  - lib/samagotchi/web/participant_review.rb
822
846
  - lib/samagotchi/web/participant_stream_filter.rb
823
847
  - lib/samagotchi/web/participant_view.rb
824
848
  - lib/samagotchi/web/participants_admin.rb
825
849
  - lib/samagotchi/web/principal.rb
826
- - lib/samagotchi/web/private_files.rb
827
850
  - lib/samagotchi/web/project_list.rb
828
851
  - lib/samagotchi/web/proxy_gate.rb
829
852
  - lib/samagotchi/web/public/activity.js
@@ -1,138 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "formatting"
4
- require_relative "event_renderer"
5
-
6
- module Samagotchi
7
- class TerminalUI
8
- # What the REPL prints between turns: cards and plugins' notices shown
9
- # outside a turn, anytime commands' output, plugins' init lines and load
10
- # warnings, and a recap written while idle.
11
- #
12
- # They are announced on other threads (the Engine's observers, the idle
13
- # scheduler, an anytime command's own), where they are only kept; the
14
- # main thread prints them at the open prompt (#flush_recap,
15
- # #flush_cards), after the command that showed them. One that comes
16
- # while a turn runs prints at once (above the live region) where the
17
- # turn wouldn't print it itself.
18
- class BetweenTurns
19
- include Formatting
20
-
21
- # A plugin's init task and a load warning: shown between turns too.
22
- INIT_EVENTS = %i[plugin_init_started plugin_init_finished guardrail_warning].freeze
23
-
24
- # @param surface [Surface] where lines print (swapped with #surface=)
25
- # @param view [AttachedView] turns the activity row for init tasks
26
- # @param renderer [EventRenderer] draws cards
27
- # @param turn_running [#call] whether a turn runs now
28
- # @param quiet [Boolean] a --non-interactive run: no init lines
29
- # @param gaps [GapClock, nil] the session's last activity: a recap
30
- # after a quiet gap has its divider
31
- def initialize(surface:, view:, renderer:, turn_running:, quiet: false, gaps: nil)
32
- @surface = surface
33
- @view = view
34
- @renderer = renderer
35
- @turn_running = turn_running
36
- @quiet = quiet
37
- @cards = Queue.new
38
- @recap = nil
39
- @gaps = gaps
40
- end
41
-
42
- attr_writer :surface
43
-
44
- # The Engine's observer for cards, notices and init events (any
45
- # thread). A card or a plugin's notice shown outside a turn is kept
46
- # for the next flush; one shown during a turn is a turn event, the
47
- # turn's sink prints it where it happens (EventRenderer).
48
- #
49
- # An anytime command's (event[:anytime]) print as it shows them: on
50
- # the main thread (the command runs at the prompt) or beside a running
51
- # turn, else at the next flush.
52
- #
53
- # A plugin's init task (chi.init) turns the activity row while it runs
54
- # and prints a line when it is done; a load warning announced before
55
- # the first turn, one. Beside a running turn they print at once.
56
- def observe(event)
57
- if INIT_EVENTS.include?(event[:type])
58
- return if @quiet
59
- return @view.init_started(event) if event[:type] == :plugin_init_started
60
-
61
- @view.init_finished(event) if event[:type] == :plugin_init_finished
62
- return show(event) if turn_running?
63
-
64
- return @cards << event
65
- end
66
- return unless (event[:type] == :card && !event[:in_turn]) || (event[:type] == :hook_notice && event[:between_turns])
67
- return show(event) if event[:anytime] && (Thread.current == Thread.main || turn_running?)
68
-
69
- @cards << event
70
- end
71
-
72
- # Keep an item for the next flush (any thread).
73
- def keep(item)
74
- @cards << item
75
- end
76
-
77
- # An anytime command's output: beside a running turn at once, else at
78
- # the next flush.
79
- def show_or_keep(item)
80
- turn_running? ? show(item) : keep(item)
81
- end
82
-
83
- # The Engine's observer for :recap_ready (the idle scheduler's thread):
84
- # kept for #flush_recap. One collected just as a turn started describes
85
- # the chat before it, and is dropped.
86
- def take_recap(event)
87
- return unless event[:type] == :recap_ready
88
- return if event[:recap].to_s.strip.empty? || turn_running?
89
-
90
- @recap = { text: event[:recap].to_s, at: event[:created_at] }
91
- end
92
-
93
- # Print a recap written while idle (main thread, at the open prompt).
94
- def flush_recap
95
- recap = @recap
96
- return unless recap
97
-
98
- @recap = nil
99
- line = @gaps&.divider
100
- @surface.commit(gap_divider(line)) if line
101
- @surface.commit(recap_block(recap[:text], at: recap[:at]))
102
- end
103
-
104
- # Print the cards, notices and anytime commands' output kept since the
105
- # last flush (main thread). A card replaced later in the same batch
106
- # (the same id: btw's "thinking…", then its answer) prints once, as its
107
- # last.
108
- def flush_cards
109
- items = []
110
- loop { items << @cards.pop(true) }
111
- rescue ThreadError
112
- items.each_with_index do |item, index|
113
- replaced = item[:type] == :card && items.drop(index + 1).any? { |later| later[:type] == :card && later[:id] == item[:id] }
114
- show(item) unless replaced
115
- end
116
- nil
117
- end
118
-
119
- # A card, a notice, an anytime command's output (:command_output), a
120
- # plugin init task's done line or a load warning.
121
- def show(item)
122
- case item[:type]
123
- when :card then @renderer.render_card(item)
124
- when :command_output then @surface.commit(item[:text])
125
- when :guardrail_warning then @surface.commit(EventRenderer.load_warning_line(item))
126
- when :plugin_init_finished
127
- line = EventRenderer.init_line(item)
128
- @surface.commit(line) if line
129
- else @surface.commit(EventRenderer.hook_notice_line(item))
130
- end
131
- end
132
-
133
- private
134
-
135
- def turn_running? = @turn_running.call
136
- end
137
- end
138
- end
@@ -1,152 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "monitor"
4
- require_relative "line_reader"
5
-
6
- module Samagotchi
7
- class TerminalUI
8
- # The REPL's input on a terminal: one LineReader keeps a prompt open for
9
- # the whole session, turns included, and this routes what it reads. A
10
- # line goes to a question waiting at the prompt (asked at ? ), to
11
- # the running turn when its handler takes it (steering), else to the
12
- # inbox the REPL loop takes its next line from.
13
- class ReplInput
14
- # @param prompt [#call] -> the prompt when no question waits
15
- # @param read [#call] (prompt, prefill) -> line (nil = Ctrl-D)
16
- # @param surface [Surface] the editor slot a dropped read leaves is
17
- # cleared under its lock
18
- def initialize(prompt:, read:, surface:)
19
- @prompt = prompt
20
- @read = read
21
- @surface = surface
22
- @inbox = Thread::Queue.new
23
- # Lines a turn left over: they come before the inbox.
24
- @front = []
25
- @turn = nil
26
- @lock = Monitor.new
27
- @answers = nil
28
- end
29
-
30
- def start(prefill: nil)
31
- @reader = LineReader.new(self, prompt: method(:prompt_text), read: @read, prefill: prefill).start
32
- self
33
- end
34
-
35
- def open? = @reader&.alive? || false
36
-
37
- def stop
38
- @reader&.stop
39
- @surface.clear_slot(:editor)
40
- end
41
-
42
- # From the reader thread: [:line, text] or [:interrupt, info].
43
- def <<(item)
44
- @lock.synchronize do
45
- next @answers << item if @answers
46
-
47
- taken = @turn && item.first == :line && @turn.call(item.last)
48
- # Not now: the line goes back into the prompt.
49
- next @reader&.prefill_next(item.last) if taken == :back
50
- next if taken
51
-
52
- @inbox << item
53
- end
54
- self
55
- end
56
-
57
- # @return [Array, nil] the next item, nil after +timeout+ seconds
58
- def pop(timeout:)
59
- @lock.synchronize { return @front.shift unless @front.empty? }
60
- @inbox.pop(timeout: timeout)
61
- end
62
-
63
- # While a turn runs, +handler+ (line -> truthy when it took the line,
64
- # :back to put it back into the prompt) sees each line first. Once it ends, +leftovers+ (-> lines it took that the
65
- # turn never merged) come next, in order, before anything in the inbox.
66
- def during_turn(handler, leftovers:)
67
- @lock.synchronize { @turn = handler }
68
- yield
69
- ensure
70
- @lock.synchronize do
71
- @turn = nil
72
- @front.concat(Array(leftovers.call).map { |line| [:line, line] })
73
- end
74
- end
75
-
76
- def prompt_text
77
- @lock.synchronize { @answers ? @choice_prompt : @prompt.call }
78
- end
79
-
80
- # Start the open read again when its prompt changed (a continue offer
81
- # came or went); what was typed is kept.
82
- def sync_prompt
83
- return unless open?
84
-
85
- with_surface_lock do
86
- next if @reader.current == prompt_text
87
-
88
- @surface.clear_slot(:editor)
89
- @reader.reprompt(keep_text: true)
90
- end
91
- end
92
-
93
- # Put +text+ into the open prompt, unless something is typed there.
94
- # @return [Boolean] whether it went in
95
- def prefill(text) = open? && @reader.prefill(text)
96
-
97
- # What the open prompt holds, nil when none is open: a line typed
98
- # ahead there is the next turn's input.
99
- def typed_text = @reader&.typed_text
100
-
101
- # Whether the next turn's input is already here: text typed ahead at
102
- # the open prompt, or a line a turn took as steering but never merged
103
- # (it runs as the next turn).
104
- def waiting_input?
105
- @lock.synchronize { return true unless @front.empty? }
106
-
107
- !typed_text.to_s.strip.empty?
108
- end
109
-
110
- # A question answered at the open prompt: the lines submitted from now
111
- # on go to it, at the ? prompt. What was typed at the prompt is put aside
112
- # and comes back once the question closes.
113
- # @yield [Thread::Queue] the answers ([:line, text] or [:interrupt, info])
114
- # A prompt closed by Ctrl-D mid-turn (the REPL exits after the turn)
115
- # opens for the question and closes again after it.
116
- def ask(choice_prompt)
117
- was_open = open?
118
- typed = was_open ? @reader.typed_text : nil
119
- answers = Thread::Queue.new
120
- @lock.synchronize do
121
- @answers = answers
122
- @choice_prompt = choice_prompt
123
- end
124
- was_open ? reprompt : start
125
- yield answers
126
- ensure
127
- @lock.synchronize { @answers = nil }
128
- if !was_open
129
- stop
130
- elsif open?
131
- reprompt(prefill: typed)
132
- else
133
- # Ctrl-D at the question ended the reader: start it again.
134
- start(prefill: typed)
135
- end
136
- end
137
-
138
- private
139
-
140
- def reprompt(prefill: nil)
141
- with_surface_lock do
142
- @surface.clear_slot(:editor)
143
- @reader.reprompt(prefill: prefill)
144
- end
145
- end
146
-
147
- def with_surface_lock(&)
148
- @surface.respond_to?(:synchronize) ? @surface.synchronize(&) : yield
149
- end
150
- end
151
- end
152
- end