samagotchi 0.62.0 → 0.64.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -1
  3. data/README.md +4 -1
  4. data/bin/chi +3 -2
  5. data/docs/architecture.md +12 -2
  6. data/docs/cli.md +20 -9
  7. data/docs/configuration.md +16 -9
  8. data/docs/context.md +13 -3
  9. data/docs/hooks.md +6 -3
  10. data/docs/internals/engine.md +10 -3
  11. data/docs/memory.md +1 -1
  12. data/docs/plugins.md +96 -2
  13. data/docs/releasing.md +1 -1
  14. data/docs/sessions.md +11 -8
  15. data/docs/sub-agent.md +27 -4
  16. data/docs/testing.md +13 -4
  17. data/lib/samagotchi/answer_command.rb +5 -1
  18. data/lib/samagotchi/answer_refs.rb +18 -26
  19. data/lib/samagotchi/bridge/turn_accumulator.rb +1 -0
  20. data/lib/samagotchi/bridge.rb +41 -5
  21. data/lib/samagotchi/bundles/claude-peer/manifest.yml +22 -0
  22. data/lib/samagotchi/bundles/claude-peer/plugin.rb +406 -0
  23. data/lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md +104 -0
  24. data/lib/samagotchi/bundles/claude-peer/scripts/pair +66 -0
  25. data/lib/samagotchi/bundles/claude-peer/skill_claude_peer.md +60 -0
  26. data/lib/samagotchi/bundles/dev/manifest.yml +2 -1
  27. data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
  28. data/lib/samagotchi/bundles/loop-guard/plugin.rb +17 -6
  29. data/lib/samagotchi/bundles/slow-commands/manifest.yml +2 -2
  30. data/lib/samagotchi/bundles/slow-commands/plugin.rb +5 -3
  31. data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
  32. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  33. data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
  34. data/lib/samagotchi/child_reports.rb +7 -2
  35. data/lib/samagotchi/cli/parent_wait.rb +9 -2
  36. data/lib/samagotchi/composer_route.rb +90 -0
  37. data/lib/samagotchi/config.rb +6 -3
  38. data/lib/samagotchi/context_absorber.rb +25 -6
  39. data/lib/samagotchi/continue_offer.rb +17 -3
  40. data/lib/samagotchi/delivery.rb +13 -8
  41. data/lib/samagotchi/engine.rb +2 -0
  42. data/lib/samagotchi/host_config.rb +6 -6
  43. data/lib/samagotchi/host_model.rb +2 -2
  44. data/lib/samagotchi/idle_recap.rb +2 -3
  45. data/lib/samagotchi/image_store.rb +8 -0
  46. data/lib/samagotchi/interrupted_turn.rb +7 -3
  47. data/lib/samagotchi/last_turn.rb +5 -2
  48. data/lib/samagotchi/pending_input_queue.rb +10 -3
  49. data/lib/samagotchi/plugin/attached_context.rb +7 -4
  50. data/lib/samagotchi/plugin/sessions.rb +27 -5
  51. data/lib/samagotchi/recap_setup.rb +3 -3
  52. data/lib/samagotchi/reply_wait.rb +171 -6
  53. data/lib/samagotchi/row_refs.rb +3 -4
  54. data/lib/samagotchi/send_command.rb +63 -19
  55. data/lib/samagotchi/session.rb +12 -0
  56. data/lib/samagotchi/session_commands.rb +1 -1
  57. data/lib/samagotchi/session_inbox.rb +63 -7
  58. data/lib/samagotchi/session_manager.rb +49 -20
  59. data/lib/samagotchi/terminal_ui/attach_launcher.rb +6 -3
  60. data/lib/samagotchi/terminal_ui/attached_loop.rb +73 -35
  61. data/lib/samagotchi/terminal_ui/formatting.rb +3 -19
  62. data/lib/samagotchi/terminal_ui.rb +103 -68
  63. data/lib/samagotchi/tool_activity.rb +5 -5
  64. data/lib/samagotchi/tools/delegate.rb +28 -1
  65. data/lib/samagotchi/tools/delegate_cursor.rb +9 -4
  66. data/lib/samagotchi/tools/delegate_wait.rb +17 -5
  67. data/lib/samagotchi/tools/execute.rb +33 -8
  68. data/lib/samagotchi/turn_checkpoint.rb +9 -3
  69. data/lib/samagotchi/version.rb +1 -1
  70. data/lib/samagotchi/web/app.rb +6 -4
  71. data/lib/samagotchi/web/public/activity.js +2 -2
  72. data/lib/samagotchi/web/public/app.js +53 -91
  73. data/lib/samagotchi/web/public/card.js +73 -23
  74. data/lib/samagotchi/web/public/chains.js +6 -4
  75. data/lib/samagotchi/web/public/command_view.js +39 -35
  76. data/lib/samagotchi/web/public/commands.js +8 -8
  77. data/lib/samagotchi/web/public/composer.js +3 -3
  78. data/lib/samagotchi/web/public/copy.js +7 -15
  79. data/lib/samagotchi/web/public/diff_view.js +20 -23
  80. data/lib/samagotchi/web/public/format.js +0 -48
  81. data/lib/samagotchi/web/public/images.js +12 -16
  82. data/lib/samagotchi/web/public/question_cards.js +3 -2
  83. data/lib/samagotchi/web/public/read_view.js +10 -9
  84. data/lib/samagotchi/web/public/scope.js +23 -0
  85. data/lib/samagotchi/web/public/timing.js +2 -8
  86. data/lib/samagotchi/web/public/turn_html.js +119 -59
  87. data/lib/samagotchi/web/public/turn_model.js +13 -3
  88. data/lib/samagotchi/web/public/turn_view.js +73 -116
  89. data/lib/samagotchi/web/public/vendor/directive.js +8 -0
  90. data/lib/samagotchi/web/public/vendor/directives/unsafe-html.js +8 -0
  91. data/lib/samagotchi/worker.rb +53 -35
  92. data/lib/samagotchi/worker_inbound.rb +12 -11
  93. metadata +9 -1
@@ -468,10 +468,10 @@ module Samagotchi
468
468
  # would keep the hub's "needs you" badge on for good. Dropped and saved
469
469
  # before the Engine and the Bridge exist (no question lock, no events).
470
470
  def drop_dead_question
471
- return unless @session.pending_question
471
+ id = @session.drop_dead_question
472
+ return unless id
472
473
 
473
- Log.info(:worker, "dead_question_dropped", id: @session.pending_question[:id])
474
- @session.pending_question = nil
474
+ Log.info(:worker, "dead_question_dropped", id: id)
475
475
  save_or_log(:dead_question) { @session.save(state_dir: @state_dir) }
476
476
  end
477
477
 
@@ -900,7 +900,7 @@ module Samagotchi
900
900
  @reporting_turn = reports_to_parent?(turn_args[:origin])
901
901
  restores = @turn_flow.restores
902
902
  begin
903
- @checkpoint.begin_turn(prompt: prompt)
903
+ @checkpoint.begin_turn(prompt: prompt, enqueued_id: turn_args[:origin]&.dig(:enqueued_id))
904
904
  @session.status = Session::STATUS_RUNNING
905
905
  # A continue that moved this session to the chain's new link wrote
906
906
  # the new parent beside it (Session::PARENT_FILE): this turn's
@@ -929,7 +929,7 @@ module Samagotchi
929
929
  # no reply: its text is what came before its last tool calls, and a
930
930
  # wait (chi send --wait) gets the question instead.
931
931
  unless response.nil? || response.strip.empty? || @continue_offer.awaiting?
932
- SessionInbox.write_output(@session_dir, response)
932
+ SessionInbox.write_output(@session_dir, response, enqueued_id: turn_args[:origin]&.dig(:enqueued_id))
933
933
  end
934
934
  save_or_log(:turn) { save_session }
935
935
  # The reports the turn merged were read unless its end took the turn
@@ -1045,10 +1045,12 @@ module Samagotchi
1045
1045
  @pending_input_drain ||= lambda do
1046
1046
  # An iteration boundary: tell whoever sent a command now that it waits.
1047
1047
  refuse_queued_commands(mid_turn: true)
1048
- # A line with images runs as its own next turn (steering merges text
1049
- # only), and so does anything queued after it, to keep the order.
1048
+ # A queued line or one with images runs as its own turn after this
1049
+ # one (steering merges text only): skipped here, not a barrier, so a
1050
+ # line sent after it still goes in at this boundary. Only the files
1051
+ # claimed below reach @merged_this_turn and the checkpoint.
1050
1052
  files = SessionInbox.find_new_input_files(@session_dir).sort
1051
- .take_while { |input_file| !SessionInbox.waits_for_turn_end?(input_file) }
1053
+ .reject { |input_file| SessionInbox.waits_for_turn_end?(input_file) }
1052
1054
  merged = files.filter_map do |input_file|
1053
1055
  claimed_file = SessionInbox.claim_input_file(input_file)
1054
1056
  next unless claimed_file
@@ -1103,22 +1105,36 @@ module Samagotchi
1103
1105
  end
1104
1106
 
1105
1107
  # Check again with the event log held, which the Bridge holds while it
1106
- # queues a POST /turn, then close the Bridge so no client can queue one
1107
- # after the check, and stop the idle jobs (reminder callback, recap).
1108
+ # queues a POST /turn, then close the Bridge's intake so no client can
1109
+ # queue one after the check, and stop the idle jobs (reminder callback,
1110
+ # recap). The Bridge stops after the log is let go (#stop_bridge_after).
1108
1111
  # A client connecting from here on finds no worker: `chi --attach` fails
1109
1112
  # and the web stream answers 503 (a small window, left as is).
1110
1113
  # @return [Boolean] false when something came in since #due?
1111
1114
  def leave_idle
1112
- @engine.synchronize_events do
1113
- next false unless @idle_exit.due?
1114
-
1115
- @bridge&.stop
1116
- @engine.stop_idle
1117
- log_idle_exit
1118
- true
1115
+ stop_bridge_after do
1116
+ @engine.synchronize_events do
1117
+ next false unless @idle_exit.due?
1118
+
1119
+ @bridge&.close_intake
1120
+ @engine.stop_idle
1121
+ log_idle_exit
1122
+ true
1123
+ end
1119
1124
  end
1120
1125
  end
1121
1126
 
1127
+ # The Bridge stops once the block (which closed its intake with the
1128
+ # event log held) answers true, outside the log: Bridge#stop waits for
1129
+ # the requests it is answering, and one waiting on the log would hold
1130
+ # it for the whole grace before being killed.
1131
+ # @return [Boolean] what the block answered
1132
+ def stop_bridge_after
1133
+ leaving = yield
1134
+ @bridge&.stop if leaving
1135
+ leaving
1136
+ end
1137
+
1122
1138
  # Bridge POST /exit, on a Bridge thread with the event log held.
1123
1139
  # +delete+: the session is deleted after (/exit --delete), so no recap.
1124
1140
  # +restart+: a new worker takes over (on the newest chi installed), so
@@ -1140,29 +1156,31 @@ module Samagotchi
1140
1156
  end
1141
1157
 
1142
1158
  # #leave_idle for an exit a client asked for: check again with the event
1143
- # log held (a prompt may have come in since), then close the Bridge and
1144
- # stop the idle jobs. Other clients' streams no longer hold: the asker's
1159
+ # log held (a prompt may have come in since), then close the Bridge's
1160
+ # intake and stop the idle jobs (the Bridge stops after the log). Other clients' streams no longer hold: the asker's
1145
1161
  # may not have closed yet, and a UI joining now finds the worker gone
1146
1162
  # (:stream_closed), the window #leave_idle has too.
1147
1163
  # @return [Boolean] false when something came in since the request
1148
1164
  def leave_on_request
1149
- @engine.synchronize_events do
1150
- hold = if @exit_restart
1151
- @idle_exit.hold_for_restart
1152
- else
1153
- @idle_exit.hold_for_request(requester: @exit_requested_by, streams: false)
1154
- end
1155
- if hold
1156
- @exit_requested = nil
1157
- @exit_restart = false
1158
- Log.info(:worker, "exit_held", reason: hold)
1159
- next false
1160
- end
1165
+ stop_bridge_after do
1166
+ @engine.synchronize_events do
1167
+ hold = if @exit_restart
1168
+ @idle_exit.hold_for_restart
1169
+ else
1170
+ @idle_exit.hold_for_request(requester: @exit_requested_by, streams: false)
1171
+ end
1172
+ if hold
1173
+ @exit_requested = nil
1174
+ @exit_restart = false
1175
+ Log.info(:worker, "exit_held", reason: hold)
1176
+ next false
1177
+ end
1161
1178
 
1162
- @bridge&.stop
1163
- @engine.stop_idle
1164
- Log.info(:worker, @exit_restart ? "restart_requested" : "exit_requested", by: @exit_requested_by || "a client")
1165
- true
1179
+ @bridge&.close_intake
1180
+ @engine.stop_idle
1181
+ Log.info(:worker, @exit_restart ? "restart_requested" : "exit_requested", by: @exit_requested_by || "a client")
1182
+ true
1183
+ end
1166
1184
  end
1167
1185
  end
1168
1186
 
@@ -106,9 +106,10 @@ module Samagotchi
106
106
  # run): auto-attached context alone doesn't make a session worth
107
107
  # keeping. Saved, then the subscriptions written (a crash between the
108
108
  # two re-delivers; the note ids dedupe).
109
- # +wake+: the session is idle with nothing queued, so an update whose
110
- # source asked to wake may start a turn (#context_wake_for): its note
111
- # goes in as the turn's first message.
109
+ # +wake+: the session is idle with nothing queued, so a delivery with a
110
+ # wake note (an update whose source asked to wake, or a session push
111
+ # source's first text that asked) may start a turn (#context_wake_for):
112
+ # its wake note goes in as the turn's first message.
112
113
  # @return [ContextWake, nil] the wake turn to run now
113
114
  # @raise [SystemCallError, IOError] the caller logs it (context_failed)
114
115
  def absorb_context(before_turn: false, wake: false)
@@ -134,7 +135,7 @@ module Samagotchi
134
135
  waking && ContextWake.new(delivery: waking, turn_id: turn_id)
135
136
  end
136
137
 
137
- # A failed wake turn kept nothing but its note: the update goes back to
138
+ # A failed wake turn kept nothing but its note: the note goes back to
138
139
  # a plain note (no turn start, the background wording), so a reload
139
140
  # draws no empty turn for it.
140
141
  def unmark_wake(context_wake)
@@ -224,13 +225,13 @@ module Samagotchi
224
225
  true
225
226
  end
226
227
 
227
- # The update in +batch+ that may start a wake turn now: its source
228
- # asked (wake: true), context.wake is on, the wake budget is open
229
- # (WorkerWakes#context_open?: no continue offer, not paused, under
230
- # session.max_wakes, past the start grace) and the source hasn't woken
231
- # the session in the last CONTEXT_WAKE_WINDOW. The others stay plain
232
- # notes. Within the start grace a change is a note: it came while the
233
- # session was away.
228
+ # The delivery in +batch+ that may start a wake turn now: it has a wake
229
+ # note (ContextAbsorber decides: its source asked, wake: true),
230
+ # context.wake is on, the wake budget is open (WorkerWakes#context_open?:
231
+ # no continue offer, not paused, under session.max_wakes, past the start
232
+ # grace) and the source hasn't woken the session in the last
233
+ # CONTEXT_WAKE_WINDOW. The others stay plain notes. Within the start
234
+ # grace a change is a note: it came while the session was away.
234
235
  def context_wake_for(batch, now: Time.now)
235
236
  candidates = batch.deliveries.select(&:wake_note)
236
237
  return nil if candidates.empty? || !context_wakes_on?
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.62.0
4
+ version: 0.64.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -185,6 +185,11 @@ files:
185
185
  - lib/samagotchi/bundles/btw/plugin.rb
186
186
  - lib/samagotchi/bundles/check-in/manifest.yml
187
187
  - lib/samagotchi/bundles/check-in/plugin.rb
188
+ - lib/samagotchi/bundles/claude-peer/manifest.yml
189
+ - lib/samagotchi/bundles/claude-peer/plugin.rb
190
+ - lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md
191
+ - lib/samagotchi/bundles/claude-peer/scripts/pair
192
+ - lib/samagotchi/bundles/claude-peer/skill_claude_peer.md
188
193
  - lib/samagotchi/bundles/coordinator/manifest.yml
189
194
  - lib/samagotchi/bundles/coordinator/plugin.rb
190
195
  - lib/samagotchi/bundles/coordinator/skill_coordinator.md
@@ -230,6 +235,7 @@ files:
230
235
  - lib/samagotchi/client_id.rb
231
236
  - lib/samagotchi/command_steps.rb
232
237
  - lib/samagotchi/commands/registry.rb
238
+ - lib/samagotchi/composer_route.rb
233
239
  - lib/samagotchi/config.rb
234
240
  - lib/samagotchi/config_text_edit.rb
235
241
  - lib/samagotchi/context_absorber.rb
@@ -600,6 +606,8 @@ files:
600
606
  - lib/samagotchi/web/public/turn_model.js
601
607
  - lib/samagotchi/web/public/turn_view.js
602
608
  - lib/samagotchi/web/public/update_notice.js
609
+ - lib/samagotchi/web/public/vendor/directive.js
610
+ - lib/samagotchi/web/public/vendor/directives/unsafe-html.js
603
611
  - lib/samagotchi/web/public/vendor/lit-html.js
604
612
  - lib/samagotchi/web/qr.rb
605
613
  - lib/samagotchi/web/server.rb