samagotchi 0.158.0 → 0.160.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -1
  3. data/bin/chi +3 -1
  4. data/docs/architecture.md +7 -7
  5. data/docs/cli.md +57 -2
  6. data/docs/configuration.md +9 -0
  7. data/docs/context.md +7 -1
  8. data/docs/guardrails.md +24 -3
  9. data/docs/hooks.md +3 -0
  10. data/docs/internals/attached-context.md +4 -2
  11. data/docs/internals/bridge.md +8 -0
  12. data/docs/internals/bundles.md +9 -7
  13. data/docs/internals/engine.md +7 -1
  14. data/docs/memory.md +46 -14
  15. data/docs/scheduling.md +241 -0
  16. data/docs/sessions.md +28 -2
  17. data/lib/samagotchi/bridge.rb +37 -4
  18. data/lib/samagotchi/broadcast/scope_card.rb +2 -1
  19. data/lib/samagotchi/bundle_command.rb +39 -1
  20. data/lib/samagotchi/bundles/system/manifest.yml +2 -2
  21. data/lib/samagotchi/bundles/system/memory_guide.md +2 -2
  22. data/lib/samagotchi/client_id.rb +17 -3
  23. data/lib/samagotchi/command_intake.rb +56 -0
  24. data/lib/samagotchi/config.rb +12 -0
  25. data/lib/samagotchi/context_absorber.rb +6 -3
  26. data/lib/samagotchi/context_candidate_choice.rb +8 -5
  27. data/lib/samagotchi/context_candidates.rb +11 -8
  28. data/lib/samagotchi/context_command.rb +9 -3
  29. data/lib/samagotchi/context_move.rb +74 -0
  30. data/lib/samagotchi/context_poller.rb +5 -2
  31. data/lib/samagotchi/context_sources.rb +6 -4
  32. data/lib/samagotchi/engine/events.rb +13 -2
  33. data/lib/samagotchi/engine/guardrails.rb +3 -4
  34. data/lib/samagotchi/engine.rb +1 -0
  35. data/lib/samagotchi/engine_peers.rb +1 -0
  36. data/lib/samagotchi/events.rb +71 -0
  37. data/lib/samagotchi/guardrails/approvals.rb +27 -3
  38. data/lib/samagotchi/guardrails/pair_boundary.rb +1 -1
  39. data/lib/samagotchi/hook_routes.rb +0 -1
  40. data/lib/samagotchi/memory_bundle/asset_installer.rb +106 -104
  41. data/lib/samagotchi/memory_bundle/asset_state.rb +81 -0
  42. data/lib/samagotchi/memory_bundle/bundle_hook.rb +7 -27
  43. data/lib/samagotchi/memory_bundle/file_actions.rb +50 -0
  44. data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
  45. data/lib/samagotchi/memory_bundle/installer.rb +14 -11
  46. data/lib/samagotchi/memory_bundle/manifest.rb +6 -0
  47. data/lib/samagotchi/memory_bundle/shipped_update.rb +22 -15
  48. data/lib/samagotchi/memory_bundle/status.rb +22 -3
  49. data/lib/samagotchi/memory_bundle/uninstaller.rb +12 -4
  50. data/lib/samagotchi/memory_orphans.rb +14 -4
  51. data/lib/samagotchi/memory_paths.rb +29 -6
  52. data/lib/samagotchi/memory_usage.rb +23 -12
  53. data/lib/samagotchi/plugin/attached_context.rb +3 -1
  54. data/lib/samagotchi/plugin/sessions.rb +4 -1
  55. data/lib/samagotchi/project_command.rb +58 -20
  56. data/lib/samagotchi/project_id.rb +102 -0
  57. data/lib/samagotchi/project_id_clash.rb +62 -0
  58. data/lib/samagotchi/project_init.rb +214 -10
  59. data/lib/samagotchi/project_scope.rb +11 -2
  60. data/lib/samagotchi/run_options.rb +1 -0
  61. data/lib/samagotchi/schedule_command.rb +252 -0
  62. data/lib/samagotchi/schedules.rb +1008 -0
  63. data/lib/samagotchi/session.rb +17 -2
  64. data/lib/samagotchi/session_access/contract.rb +1 -1
  65. data/lib/samagotchi/session_access/local.rb +28 -5
  66. data/lib/samagotchi/session_access/remote.rb +2 -1
  67. data/lib/samagotchi/session_access.rb +4 -1
  68. data/lib/samagotchi/session_commands.rb +7 -3
  69. data/lib/samagotchi/session_continue.rb +10 -6
  70. data/lib/samagotchi/session_manager/spawn.rb +34 -13
  71. data/lib/samagotchi/session_manager/worker_command.rb +11 -23
  72. data/lib/samagotchi/state_format.rb +118 -0
  73. data/lib/samagotchi/terminal_ui/formatting.rb +2 -0
  74. data/lib/samagotchi/tool_declarations.rb +43 -0
  75. data/lib/samagotchi/tools/builtin_calls.rb +2 -0
  76. data/lib/samagotchi/tools/builtins.rb +17 -0
  77. data/lib/samagotchi/tools/cancel_schedule.rb +43 -0
  78. data/lib/samagotchi/tools/context_read.rb +7 -2
  79. data/lib/samagotchi/tools/delegate.rb +10 -2
  80. data/lib/samagotchi/tools/list_schedules.rb +43 -0
  81. data/lib/samagotchi/tools/schedule_task.rb +39 -0
  82. data/lib/samagotchi/turn_stages/context_judge.rb +7 -7
  83. data/lib/samagotchi/update_command.rb +2 -2
  84. data/lib/samagotchi/version.rb +1 -1
  85. data/lib/samagotchi/web/app.rb +5 -4
  86. data/lib/samagotchi/web/public/chains.js +32 -8
  87. data/lib/samagotchi/web/public/index.html +4 -1
  88. data/lib/samagotchi/web/public/info_bar.js +4 -3
  89. data/lib/samagotchi/web/public/turn_events.js +4 -0
  90. data/lib/samagotchi/web/session_access_routes.rb +3 -3
  91. data/lib/samagotchi/web/session_hub.rb +20 -0
  92. data/lib/samagotchi/worker.rb +42 -12
  93. data/lib/samagotchi/worker_boot.rb +3 -4
  94. data/lib/samagotchi/worker_inbound.rb +44 -2
  95. data/lib/samagotchi/worker_link.rb +5 -0
  96. data/lib/samagotchi/worker_wakes.rb +9 -1
  97. metadata +12 -1
@@ -3,9 +3,8 @@
3
3
  require_relative "session_manager"
4
4
 
5
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
6
+ # WorkerBoot: the worker process, from the entry the boot string names
7
+ # (WorkerBoot.run_session_loop) to what it does as it leaves (CROSS-VERSION
9
8
  # CONTRACT: spec/worker_contract_spec.rb). It takes the session's OwnerLock,
10
9
  # leaves the spawner's process tree, installs the signals and the project
11
10
  # env, and runs the Worker (worker.rb loads only then, after the lock). Calls
@@ -14,7 +13,7 @@ module Samagotchi
14
13
  module WorkerBoot
15
14
  class << self
16
15
  # Run the session loop inside the forked process: the worker's main,
17
- # which the boot string reaches through SessionManager.run_session_loop.
16
+ # which the boot string names directly.
18
17
  #
19
18
  # Every worker starts its per-session Bridge (the single live client
20
19
  # transport) on a port bound to 127.0.0.1 before the loop and stops it on
@@ -6,6 +6,7 @@ require "time"
6
6
 
7
7
  require_relative "child_reports"
8
8
  require_relative "client_id"
9
+ require_relative "command_intake"
9
10
  require_relative "config"
10
11
  require_relative "context_note"
11
12
  require_relative "log"
@@ -106,6 +107,7 @@ module Samagotchi
106
107
  @stopped = stopped
107
108
  @queue_command = queue_command
108
109
  @initial_prompt_taken = false
110
+ @context_wake_waiting = false
109
111
  end
110
112
 
111
113
  # Add the queued context notes to the conversation (between turns only,
@@ -148,12 +150,18 @@ module Samagotchi
148
150
  # @return [ContextWake, nil] the wake turn to run now
149
151
  # @raise [SystemCallError, IOError] the caller logs it (context_failed)
150
152
  def absorb_context(before_turn: false, wake: false)
153
+ @context_wake_waiting = false
151
154
  return nil unless before_turn || conversation_started?
152
155
  return nil if @stopped.call
153
156
 
154
157
  batch = @context_absorber.pending
155
158
  return nil unless batch
156
159
 
160
+ if wake && context_wake_waiting_for?(batch)
161
+ @context_wake_waiting = true
162
+ return nil
163
+ end
164
+
157
165
  waking = wake ? context_wake_for(batch) : nil
158
166
  turn_id = SecureRandom.uuid if waking
159
167
  batch.deliveries.each do |delivery|
@@ -170,6 +178,10 @@ module Samagotchi
170
178
  waking && ContextWake.new(delivery: waking, turn_id: turn_id)
171
179
  end
172
180
 
181
+ # Whether #absorb_context left an eligible wake batch uncommitted for
182
+ # the start grace, so the idle loop can revisit it when grace expires.
183
+ def context_wake_waiting? = @context_wake_waiting
184
+
173
185
  # A failed wake turn kept nothing but its note: the note goes back to
174
186
  # a plain note (no turn start, the background wording), so a reload
175
187
  # draws no empty turn for it. +wake+: a ContextWake or a TaskWake.
@@ -200,11 +212,24 @@ module Samagotchi
200
212
  prompt
201
213
  end
202
214
 
215
+ # The first prompt's origin: the turn id spawn_session gave it (a wait
216
+ # for it, --new --wait, names it) and who sent it when no person did
217
+ # (Session#first_turn_client_id: a delegating parent, a schedule). A
218
+ # delegate child's task is its parent's even without the field (one
219
+ # spawned by an older chi, or by a caller that names no sender). NONE
220
+ # for a person's session from before either field.
221
+ # @return [TurnOrigin]
222
+ def initial_origin
223
+ client_id = @session.first_turn_client_id
224
+ client_id ||= ClientId.delegate(@session.parent_id) if @session.delegate? && @session.parent_id
225
+ TurnOrigin.of(client_id: client_id, enqueued_id: @session.first_turn_id)
226
+ end
227
+
203
228
  # The first prompt as a command (queued): the session was saved as
204
229
  # running for a turn that won't run.
205
230
  # @return [Boolean] whether it was one
206
231
  def initial_command?(prompt)
207
- return false unless queue_as_command(prompt, TurnOrigin::NONE)
232
+ return false unless queue_as_command(prompt, initial_origin)
208
233
 
209
234
  @session.status = Session::STATUS_IDLE
210
235
  save(:initial_command, unless_stopped: true)
@@ -253,7 +278,11 @@ module Samagotchi
253
278
  # POST /turn; an unknown /word stays a prompt. Queued: the loop's next
254
279
  # pass runs it. A participant's is refused (dropped, with its audit
255
280
  # line): commands and shell lines are the owner's (chi web and the
256
- # Bridge refuse them first; this is the worker's own layer).
281
+ # Bridge refuse them first; this is the worker's own layer). From a
282
+ # sender that may not send commands (CommandIntake.allowed?: a
283
+ # delegate, a schedule, a plugin...) a /word line is a prompt, and a
284
+ # shell line is queued for Worker#run_command_line to refuse, so its
285
+ # sender and the UIs see the refusal as the command's error.
257
286
  # @return [Boolean] whether +text+ was one
258
287
  def queue_as_command(text, origin, after_file = nil)
259
288
  return false unless @engine.command_registry.command?(text.to_s)
@@ -264,6 +293,7 @@ module Samagotchi
264
293
  what: "command", reason: "not_permitted")
265
294
  return true
266
295
  end
296
+ return false unless CommandIntake.allowed?(origin.client_id) || CommandIntake.shell_line?(text)
267
297
 
268
298
  @queue_command.call(text, origin.client_id, after_file, origin.enqueued_id)
269
299
  true
@@ -300,6 +330,18 @@ module Samagotchi
300
330
  waking.first
301
331
  end
302
332
 
333
+ def context_wake_waiting_for?(batch)
334
+ return false unless @wakes.in_grace?
335
+
336
+ candidates = batch.deliveries.select(&:wake_note)
337
+ return false if candidates.empty? || !context_wakes_on?
338
+ return false unless @wakes.context_ready?(awaiting_continue: @awaiting_continue.call, names: candidates.map(&:name))
339
+
340
+ candidates.any? do |delivery|
341
+ delivery.peer ? @wakes.peer_in_a_row < @wakes.peer_turns : !woke_lately?(delivery.subscription, Time.now)
342
+ end
343
+ end
344
+
303
345
  # A peer's answer waits as a note: the UIs hear it once per
304
346
  # interactive input (WorkerWakes#notice_peer_budget!). +key+: the
305
347
  # budget that holds it.
@@ -34,6 +34,11 @@ module Samagotchi
34
34
  # newer worker's (Skew).
35
35
  PROTOCOL = 1
36
36
 
37
+ # A worker whose sidecar publishes this may be asked what it is busy with
38
+ # (Bridge's GET /activity, Worker#activity_snapshot): a sidecar without it
39
+ # is an older chi's, and asking it would only cost a 404.
40
+ ACTIVITY_FEATURE = "activity"
41
+
37
42
  # Seconds a worker may hold the lock without publishing its Bridge before
38
43
  # Starting is stuck (SessionManager::WORKER_START_GRACE).
39
44
  START_LIMIT = 30.0
@@ -101,7 +101,15 @@ module Samagotchi
101
101
  # not paused, past the start grace and under the budget. +names+: the
102
102
  # sources asking, for the log line when the budget holds them.
103
103
  def context_open?(awaiting_continue:, names:)
104
- return false if @paused || awaiting_continue || in_grace?
104
+ return false if in_grace?
105
+
106
+ context_ready?(awaiting_continue:, names:)
107
+ end
108
+
109
+ # The context wake gates apart from start grace; used to decide whether
110
+ # a wake must remain pending until that grace ends.
111
+ def context_ready?(awaiting_continue:, names:)
112
+ return false if @paused || awaiting_continue
105
113
 
106
114
  if @in_a_row >= max
107
115
  Log.info(:worker, "context_wake_held", reason: "max_wakes", names: names.join(","))
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.158.0
4
+ version: 0.160.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -176,6 +176,7 @@ files:
176
176
  - docs/memory.md
177
177
  - docs/plugins.md
178
178
  - docs/releasing.md
179
+ - docs/scheduling.md
179
180
  - docs/sessions.md
180
181
  - docs/sub-agent.md
181
182
  - docs/system-one.md
@@ -313,6 +314,7 @@ files:
313
314
  - lib/samagotchi/cli/parent_wait.rb
314
315
  - lib/samagotchi/client.rb
315
316
  - lib/samagotchi/client_id.rb
317
+ - lib/samagotchi/command_intake.rb
316
318
  - lib/samagotchi/command_steps.rb
317
319
  - lib/samagotchi/commands/registry.rb
318
320
  - lib/samagotchi/composer_route.rb
@@ -499,6 +501,7 @@ files:
499
501
  - lib/samagotchi/log_subscriber.rb
500
502
  - lib/samagotchi/memory_bundle.rb
501
503
  - lib/samagotchi/memory_bundle/asset_installer.rb
504
+ - lib/samagotchi/memory_bundle/asset_state.rb
502
505
  - lib/samagotchi/memory_bundle/builder.rb
503
506
  - lib/samagotchi/memory_bundle/bundle_hook.rb
504
507
  - lib/samagotchi/memory_bundle/entry_size.rb
@@ -593,6 +596,8 @@ files:
593
596
  - lib/samagotchi/process_group.rb
594
597
  - lib/samagotchi/project_command.rb
595
598
  - lib/samagotchi/project_env.rb
599
+ - lib/samagotchi/project_id.rb
600
+ - lib/samagotchi/project_id_clash.rb
596
601
  - lib/samagotchi/project_init.rb
597
602
  - lib/samagotchi/project_scope.rb
598
603
  - lib/samagotchi/prompt.rb
@@ -640,6 +645,8 @@ files:
640
645
  - lib/samagotchi/run_options.rb
641
646
  - lib/samagotchi/sampling_settings.rb
642
647
  - lib/samagotchi/sandbox_layout.rb
648
+ - lib/samagotchi/schedule_command.rb
649
+ - lib/samagotchi/schedules.rb
643
650
  - lib/samagotchi/self_report.rb
644
651
  - lib/samagotchi/send_command.rb
645
652
  - lib/samagotchi/send_command/busy.rb
@@ -690,6 +697,7 @@ files:
690
697
  - lib/samagotchi/side_judgments.rb
691
698
  - lib/samagotchi/side_usage.rb
692
699
  - lib/samagotchi/starting_marker.rb
700
+ - lib/samagotchi/state_format.rb
693
701
  - lib/samagotchi/steer.rb
694
702
  - lib/samagotchi/steer_cut.rb
695
703
  - lib/samagotchi/steer_cut_after.rb
@@ -802,6 +810,7 @@ files:
802
810
  - lib/samagotchi/tools/builtin_calls.rb
803
811
  - lib/samagotchi/tools/builtins.rb
804
812
  - lib/samagotchi/tools/cancel_reminder.rb
813
+ - lib/samagotchi/tools/cancel_schedule.rb
805
814
  - lib/samagotchi/tools/context_read.rb
806
815
  - lib/samagotchi/tools/deferred_tools.rb
807
816
  - lib/samagotchi/tools/delegate.rb
@@ -814,6 +823,7 @@ files:
814
823
  - lib/samagotchi/tools/forget_outputs.rb
815
824
  - lib/samagotchi/tools/index_hint.rb
816
825
  - lib/samagotchi/tools/list_reminders.rb
826
+ - lib/samagotchi/tools/list_schedules.rb
817
827
  - lib/samagotchi/tools/list_sessions.rb
818
828
  - lib/samagotchi/tools/memory.rb
819
829
  - lib/samagotchi/tools/objectives_tool.rb
@@ -823,6 +833,7 @@ files:
823
833
  - lib/samagotchi/tools/ref_text.rb
824
834
  - lib/samagotchi/tools/register_reminder.rb
825
835
  - lib/samagotchi/tools/registry.rb
836
+ - lib/samagotchi/tools/schedule_task.rb
826
837
  - lib/samagotchi/tools/send_note.rb
827
838
  - lib/samagotchi/tools/shell.rb
828
839
  - lib/samagotchi/tools/task_create.rb