samagotchi 0.154.0 → 0.155.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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/docs/architecture.md +3 -2
  4. data/docs/bundles.md +21 -0
  5. data/docs/cli.md +10 -8
  6. data/docs/guardrails.md +2 -2
  7. data/docs/hooks.md +1 -1
  8. data/docs/internals/anthropic.md +4 -4
  9. data/docs/internals/attached-context.md +8 -6
  10. data/docs/internals/block-renderers.md +8 -1
  11. data/docs/internals/bridge.md +18 -5
  12. data/docs/internals/engine.md +221 -100
  13. data/docs/internals/inline-widgets.md +136 -0
  14. data/docs/internals/objectives.md +8 -6
  15. data/docs/internals/participants.md +5 -5
  16. data/docs/internals/prompt-caching.md +12 -6
  17. data/docs/internals/web-frontend.md +2 -2
  18. data/docs/internals/working-set.md +2 -1
  19. data/docs/plugins.md +2 -0
  20. data/docs/releasing.md +1 -1
  21. data/docs/sessions.md +3 -3
  22. data/docs/testing.md +16 -1
  23. data/lib/samagotchi/answer_text.rb +1 -2
  24. data/lib/samagotchi/block_renderers.rb +5 -0
  25. data/lib/samagotchi/bridge/card_places.rb +56 -7
  26. data/lib/samagotchi/bridge/card_store.rb +24 -9
  27. data/lib/samagotchi/bridge.rb +9 -0
  28. data/lib/samagotchi/bridge_client.rb +9 -5
  29. data/lib/samagotchi/bundle_command/check.rb +1 -1
  30. data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
  31. data/lib/samagotchi/bundles/btw/manifest.yml +2 -2
  32. data/lib/samagotchi/bundles/btw/plugin.rb +1 -1
  33. data/lib/samagotchi/bundles/coordinator/manifest.yml +2 -2
  34. data/lib/samagotchi/bundles/coordinator/plugin.rb +3 -2
  35. data/lib/samagotchi/bundles/dev/manifest.yml +3 -2
  36. data/lib/samagotchi/bundles/github-pr/manifest.yml +2 -2
  37. data/lib/samagotchi/bundles/github-pr/plugin.rb +3 -3
  38. data/lib/samagotchi/bundles/inline-widgets/manifest.yml +11 -0
  39. data/lib/samagotchi/bundles/inline-widgets/plugin.rb +76 -0
  40. data/lib/samagotchi/bundles/skills/manifest.yml +2 -2
  41. data/lib/samagotchi/bundles/skills/plugin.rb +3 -2
  42. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  43. data/lib/samagotchi/desktop/macos/ChiRunner.swift +1 -1
  44. data/lib/samagotchi/engine/context_judge.rb +19 -125
  45. data/lib/samagotchi/engine/guardrails.rb +4 -2
  46. data/lib/samagotchi/engine/messages.rb +9 -6
  47. data/lib/samagotchi/engine/objectives.rb +22 -90
  48. data/lib/samagotchi/engine/participants.rb +15 -10
  49. data/lib/samagotchi/engine/plugins.rb +3 -1
  50. data/lib/samagotchi/engine/questions.rb +1 -1
  51. data/lib/samagotchi/engine/side_requests.rb +5 -2
  52. data/lib/samagotchi/engine/toolkit.rb +33 -190
  53. data/lib/samagotchi/engine/turn_end.rb +1 -1
  54. data/lib/samagotchi/engine/turn_helpers.rb +16 -39
  55. data/lib/samagotchi/engine/verify.rb +7 -143
  56. data/lib/samagotchi/engine.rb +162 -99
  57. data/lib/samagotchi/engine_peers.rb +1 -1
  58. data/lib/samagotchi/engine_snapshots.rb +9 -8
  59. data/lib/samagotchi/event_fanout.rb +18 -12
  60. data/lib/samagotchi/guardrail_wiring.rb +83 -82
  61. data/lib/samagotchi/guardrails/gate.rb +41 -40
  62. data/lib/samagotchi/guardrails/pair_boundary.rb +2 -1
  63. data/lib/samagotchi/guardrails/review_boundary.rb +2 -1
  64. data/lib/samagotchi/guardrails/targets.rb +6 -4
  65. data/lib/samagotchi/guardrails_command.rb +1 -1
  66. data/lib/samagotchi/idle_recap.rb +3 -1
  67. data/lib/samagotchi/inline_widgets.rb +325 -0
  68. data/lib/samagotchi/kernel_loop.rb +194 -216
  69. data/lib/samagotchi/llm/backend.rb +3 -1
  70. data/lib/samagotchi/llm/chat_loop.rb +66 -42
  71. data/lib/samagotchi/llm/native_backend.rb +3 -2
  72. data/lib/samagotchi/llm_context_forget.rb +5 -3
  73. data/lib/samagotchi/model_binding.rb +3 -3
  74. data/lib/samagotchi/plugin/prompt_sections.rb +7 -0
  75. data/lib/samagotchi/question_desk.rb +1 -1
  76. data/lib/samagotchi/revise_anchor.rb +1 -1
  77. data/lib/samagotchi/session_manager.rb +8 -13
  78. data/lib/samagotchi/session_retention.rb +24 -4
  79. data/lib/samagotchi/steer.rb +12 -1
  80. data/lib/samagotchi/stream_event.rb +22 -0
  81. data/lib/samagotchi/system_prompt.rb +84 -61
  82. data/lib/samagotchi/terminal_ui/attached_view.rb +0 -9
  83. data/lib/samagotchi/terminal_ui/input_support.rb +16 -15
  84. data/lib/samagotchi/terminal_ui/join_replay.rb +16 -12
  85. data/lib/samagotchi/terminal_ui/reline_seam.rb +0 -10
  86. data/lib/samagotchi/terminal_ui/status_row.rb +0 -7
  87. data/lib/samagotchi/testing/boundary.rb +7 -0
  88. data/lib/samagotchi/testing/bundle.rb +7 -5
  89. data/lib/samagotchi/testing/install.rb +3 -2
  90. data/lib/samagotchi/tool_activity.rb +8 -6
  91. data/lib/samagotchi/tool_declarations.rb +4 -4
  92. data/lib/samagotchi/tool_response.rb +4 -3
  93. data/lib/samagotchi/tool_runner.rb +93 -101
  94. data/lib/samagotchi/toolkit_binding.rb +40 -14
  95. data/lib/samagotchi/tools/deferred_tools.rb +17 -21
  96. data/lib/samagotchi/tools/registry.rb +43 -40
  97. data/lib/samagotchi/tools/tool_index.rb +135 -88
  98. data/lib/samagotchi/tools/tool_surface.rb +173 -0
  99. data/lib/samagotchi/turn_context.rb +67 -0
  100. data/lib/samagotchi/turn_flow.rb +7 -2
  101. data/lib/samagotchi/turn_stages/context_judge.rb +170 -0
  102. data/lib/samagotchi/turn_stages/objectives.rb +146 -0
  103. data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +261 -0
  104. data/lib/samagotchi/turn_stages/verify_anchors.rb +169 -0
  105. data/lib/samagotchi/turn_stages/warmup.rb +83 -0
  106. data/lib/samagotchi/turn_state.rb +46 -4
  107. data/lib/samagotchi/version.rb +1 -1
  108. data/lib/samagotchi/web/app.rb +3 -1
  109. data/lib/samagotchi/web/markdown_renderer.rb +39 -14
  110. data/lib/samagotchi/web/message_display.rb +8 -1
  111. data/lib/samagotchi/web/public/app.js +5 -1
  112. data/lib/samagotchi/web/public/block_host.js +74 -199
  113. data/lib/samagotchi/web/public/block_renderers.css +2 -0
  114. data/lib/samagotchi/web/public/block_source.js +7 -4
  115. data/lib/samagotchi/web/public/block_watch.js +256 -0
  116. data/lib/samagotchi/web/public/card_stamps.js +27 -0
  117. data/lib/samagotchi/web/public/cards_place.js +5 -3
  118. data/lib/samagotchi/web/public/composer_fold.js +3 -0
  119. data/lib/samagotchi/web/public/composer_mode.js +1 -1
  120. data/lib/samagotchi/web/public/format.js +3 -2
  121. data/lib/samagotchi/web/public/index.html +28 -9
  122. data/lib/samagotchi/web/public/inline_widgets.js +146 -0
  123. data/lib/samagotchi/web/public/owner_verify.js +1 -1
  124. data/lib/samagotchi/web/public/participant.js +1 -1
  125. data/lib/samagotchi/web/public/revise_stack.js +1 -1
  126. data/lib/samagotchi/web/public/stage_watch.js +6 -2
  127. data/lib/samagotchi/web/public/text_roots.js +1 -1
  128. data/lib/samagotchi/web/public/turn_html.js +8 -4
  129. data/lib/samagotchi/web/public/verify_target.js +1 -1
  130. data/lib/samagotchi/web/public/widget_picks.js +130 -0
  131. data/lib/samagotchi/web/public/widget_walk.js +230 -0
  132. data/lib/samagotchi/web/public/widgets.css +70 -0
  133. data/lib/samagotchi/web/session_access_routes.rb +8 -0
  134. data/lib/samagotchi/web/session_show.rb +4 -0
  135. data/lib/samagotchi/worker.rb +8 -5
  136. data/lib/samagotchi.rb +0 -1
  137. metadata +19 -2
  138. data/lib/samagotchi/pending_input_queue.rb +0 -55
@@ -26,7 +26,9 @@ module Samagotchi
26
26
  # holds the id of the turn it belongs to or follows (+turn_id+: the running
27
27
  # turn's, else the last one's, nil before any) and lists with +turns_since+,
28
28
  # the turns drawn after it, counted from the messages the page draws
29
- # (CardPlaces, with the turn log the store keeps), and +current+ for a card
29
+ # (CardPlaces, with the turn log the store keeps), +opening_id+, the same
30
+ # place as a stamp (the id of the opening it comes after, which the page
31
+ # finds by its bubble; none where only the count can say), and +current+ for a card
30
32
  # of the turn running now. A card that isn't the turn's but comes while one
31
33
  # runs (an anytime command's, such as /btw) goes after that turn: its prompt
32
34
  # is already in the history. Listed against messages, an in-turn entry of a
@@ -70,7 +72,7 @@ module Samagotchi
70
72
  SAVED_ON = (%i[turn_started card hook_notice empty_answer_retry steer_cut llm_context_edited question_requested question_answered
71
73
  question_cancelled question_relay] + Events::TURN_END).freeze
72
74
  # What only the store reads: never listed.
73
- INTERNAL_FIELDS = %i[turn_id continued base_since past_idless during after_failed command].freeze
75
+ INTERNAL_FIELDS = %i[turn_id continued base_since past_idless after_opening during after_failed command].freeze
74
76
 
75
77
  # What a session's saved file lists, as a store started from it lists
76
78
  # (no turn running): the web's cards for a session no worker runs.
@@ -118,7 +120,8 @@ module Samagotchi
118
120
  # them (a body re-read, a trace): no turns_since:, nothing to tell a turn
119
121
  # that left nothing by.
120
122
  # @return [Array<Hash>] oldest first: the card's (or notice's) fields
121
- # plus current:, turns_since: with +messages+, earlier: true for one an
123
+ # plus current:, turns_since: and opening_id: (CardPlaces#opening_of,
124
+ # when it has one) with +messages+, earlier: true for one an
122
125
  # earlier worker saved, during: true for a card that came while the
123
126
  # running turn runs but isn't that turn's (it goes after the running
124
127
  # turn's prompt), and after_failed: true for one shown after the last
@@ -141,6 +144,8 @@ module Samagotchi
141
144
 
142
145
  out = shown.except(*INTERNAL_FIELDS)
143
146
  out[:turns_since] = places.turns_since(entry) if places
147
+ stamp = places&.opening_of(entry)
148
+ out[:opening_id] = stamp if stamp
144
149
  out[:current] = current
145
150
  out[:during] = true if entry[:during]
146
151
  out[:after_failed] = true if entry[:after_failed] && entry[:turn_id] == @failed_turn
@@ -255,7 +260,7 @@ module Samagotchi
255
260
  # A hand-edited or foreign file's entry goes, rather than break every later
256
261
  # listing of the session.
257
262
  def sound_place?(entry)
258
- return false unless entry[:turn_id].nil? || entry[:turn_id].is_a?(String)
263
+ return false unless [entry[:turn_id], entry[:after_opening]].all? { |id| id.nil? || id.is_a?(String) }
259
264
 
260
265
  entry.values_at(:base_since, :past_idless).compact.all? { |count| count.is_a?(Integer) && !count.negative? }
261
266
  end
@@ -315,8 +320,8 @@ module Samagotchi
315
320
  shown = @entries.select { |entry| entry[:turn_id] == @running_turn && !entry[:in_turn] }
316
321
  return if shown.empty? || !@messages
317
322
 
318
- run = CardPlaces.new(@turn_log, @messages.call).idless_after(@running_turn)
319
- shown.each { |entry| entry[:past_idless] = run if run.positive? }
323
+ places = CardPlaces.new(@turn_log, @messages.call)
324
+ shown.each { |entry| note_idless(entry, places) }
320
325
  end
321
326
 
322
327
  # The entry's anchor: the running turn, else the last one.
@@ -331,7 +336,7 @@ module Samagotchi
331
336
  # button's command replaced, which nobody typed).
332
337
  old = @entries[index]
333
338
  @entries[index] = card.except(:command).merge(updated: true, **old.slice(:in_turn, :turn_id, :during, :continued, :base_since,
334
- :past_idless, :after_failed, :command))
339
+ :past_idless, :after_opening, :after_failed, :command))
335
340
  else
336
341
  entry = card.merge(in_turn: event[:in_turn] ? true : false, turn_id: anchor)
337
342
  entry[:during] = true if @running_turn && !event[:in_turn]
@@ -393,8 +398,18 @@ module Samagotchi
393
398
  def note_past_idless(entry)
394
399
  return unless @messages
395
400
 
396
- run = CardPlaces.new(@turn_log, @messages.call).idless_after(entry[:turn_id])
397
- entry[:past_idless] = run if run.positive?
401
+ note_idless(entry, CardPlaces.new(@turn_log, @messages.call))
402
+ end
403
+
404
+ # How many openings with no turn id +entry+ came after, and the last
405
+ # one's own id when it has one (+after_opening+: its stamp, #list).
406
+ def note_idless(entry, places)
407
+ run = places.idless_after(entry[:turn_id])
408
+ return unless run.positive?
409
+
410
+ entry[:past_idless] = run
411
+ after = places.after_opening(entry[:turn_id])
412
+ entry[:after_opening] = after if after
398
413
  end
399
414
 
400
415
  # Which cap +entry+ counts against: :notice, :llm_context or :card.
@@ -196,6 +196,15 @@ module Samagotchi
196
196
  @mutex.synchronize { @open_streams - (client_id ? @streams_by_client[client_id] : 0) }
197
197
  end
198
198
 
199
+ # Streams held by the clients whose id starts with +prefix+
200
+ # (ClientId::TUI_PREFIX: the attached terminals, which a scratch worker
201
+ # counts, Worker#scratch_unattended?). A stream that named no client
202
+ # (a web tab's, through the chi web proxy) is none of them.
203
+ # @return [Integer]
204
+ def open_streams_of(prefix)
205
+ @mutex.synchronize { @streams_by_client.sum { |client_id, count| client_id.start_with?(prefix) ? count : 0 } }
206
+ end
207
+
199
208
  # Monotonic time of the last request, stream open or stream close. The
200
209
  # worker's idle exit counts from it. A connect that sends no request (the
201
210
  # sidecar liveness probe) doesn't count.
@@ -309,12 +309,16 @@ module Samagotchi
309
309
  # older worker's 404 for a route it lacks from a refused connect, a
310
310
  # timeout or a 500.
311
311
  # @return [Array(Integer, Object)] the status and the parsed JSON body
312
- # (nil when it isn't JSON); [nil, nil] when no reply came
312
+ # (nil when it isn't JSON); [nil, nil] when no reply came (a refused
313
+ # connect, a timeout, a cut or garbled reply), and when anything else
314
+ # failed, which is logged
313
315
  def get(path)
314
- response = get_response(path)
315
- [response.status, response.json]
316
- rescue StandardError
317
- [nil, nil]
316
+ Degrade.call(:bridge, "get_failed", fallback: [nil, nil], path: path) do
317
+ response = get_response(path)
318
+ [response.status, response.json]
319
+ rescue SystemCallError, IOError
320
+ [nil, nil]
321
+ end
318
322
  end
319
323
 
320
324
  # GET /session/:id/<path>, preserving the raw response and raising on I/O.
@@ -421,7 +421,7 @@ module Samagotchi
421
421
  end
422
422
  end
423
423
  { "init" => registered.inits, "toolkit" => registered.toolkits, "tool source" => registered.tool_sources,
424
- "peer source" => registered.peer_sources }.each do |what, names|
424
+ "peer source" => registered.peer_sources, "prompt section" => registered.prompt_sections }.each do |what, names|
425
425
  names.each { |name| add(:info, :registered, plugin, "#{what} #{name}") }
426
426
  end
427
427
  end
@@ -124,7 +124,7 @@ module Samagotchi
124
124
  end
125
125
 
126
126
  # The listing's placement fields: nothing to place in a one-turn run.
127
- LISTING_FIELDS = %w[turns_since current earlier after_failed label].freeze
127
+ LISTING_FIELDS = %w[turns_since opening_id current earlier after_failed label].freeze
128
128
 
129
129
  # The saved cards as the web reads them (CardStore.saved), with
130
130
  # string keys and values, as the JSON trace prints them.
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: btw
3
- version: 0.1.4
3
+ version: 0.1.5
4
4
  scope: system
5
5
  description: "/btw <question>: a side answer about the conversation, as a card in every UI, even mid-turn; Keep as session forks it into a child session"
6
6
  trust_level: reviewed
7
7
  plugin:
8
8
  file: plugin.rb
9
- sha256: sha256:6497129b06d1c3061c1d7d9d7a9c3dfdbcf790e8f2049addad0c26d24771015f
9
+ sha256: sha256:d4c0d6309308270a1430d64f9713e46c15d71ace1b068fefc22916aa8cc2d043
10
10
  requires_chi: ">= 0.143.0"
11
11
  # config.yml bundles: btw: (another key warns; an older chi ignores this)
12
12
  settings: [max_tokens, timeout]
@@ -15,7 +15,7 @@ class Plugin
15
15
  SYSTEM = "Answer the user's question about the conversation below, briefly. The user asks it on the side: " \
16
16
  "don't continue the conversation's task, and don't say what you would do next."
17
17
  # How many answers /btw keep can still fork: the last ones, for the
18
- # worker's (or the REPL's) life.
18
+ # worker's life.
19
19
  KEPT = 10
20
20
  TITLE_CHARS = 72
21
21
  EARLIER = "(About the conversation before the running turn.)"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: coordinator
3
- version: 0.8.0
3
+ version: 0.8.1
4
4
  scope: system
5
5
  description: "Coordinate parallel work: the coordinator skill (split the work, one worktree and child session per task, check each report, merges under a guardrail approval (the git-merge rule) or a question, a handoff memory to resume from), /coordinate <goal>, /coordinate resume, /coordinate end, /coordinate review (experimental: a sprint review session to share) and /children; the coordinator toolkit (lean + delegate, delegate_result, list_sessions, objectives) and the coding toolkit for build children (child + be concise)"
6
6
  trust_level: reviewed
@@ -10,7 +10,7 @@ files:
10
10
  description: "Coordinate parallel work: split it, delegate each task wait:false in its own worktree (cwd:), check each report, merge under the git-merge approval or ask the user; resume from a handoff_* memory"
11
11
  plugin:
12
12
  file: plugin.rb
13
- sha256: sha256:84535a86b754f3a12cdb2f2591027ea71bbf9ffb74734b1050899b9a4fde07ac
13
+ sha256: sha256:540dd6369342ea4dcbc6ba5ee17fb20d980b880df614a475668c70115e7ec6b8
14
14
  # the coordinator toolkit needs chi.toolkit and ctx.use_toolkit (0.66.0); the
15
15
  # coding toolkit (chi.toolkit prompt:) and the skill's delegate toolkit: need
16
16
  # the next chi.
@@ -16,8 +16,9 @@
16
16
  # (ctx.sessions.stop, by session id) and shows the card again.
17
17
  #
18
18
  # /coordinate <goal> asks the model, in this session, to follow the skill
19
- # for the goal. A REPL session (--no-shared) takes no messages: the request
20
- # is shown for the user to send.
19
+ # for the goal. A session a one-turn run holds (-p --non-interactive, or an
20
+ # older chi's terminal) takes no messages: the request is shown for the user
21
+ # to send.
21
22
  #
22
23
  # /coordinate resume picks up a coordinator handoff: the open handoff_*
23
24
  # memories of this project (an index line whose description doesn't start
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: dev
3
- version: 0.7.7
3
+ version: 0.7.8
4
4
  scope: system
5
5
  description: "Developer tools"
6
6
  trust_level: reviewed
7
- requires_chi: ">= 0.154.0"
7
+ requires_chi: ">= 0.155.0"
8
8
  includes:
9
9
  - known-names
10
10
  - mcp
@@ -18,3 +18,4 @@ includes:
18
18
  - ruby
19
19
  - model-notes
20
20
  - decision-card
21
+ - inline-widgets
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: github-pr
3
- version: 0.6.3
3
+ version: 0.6.4
4
4
  scope: system
5
5
  description: Proposes the branch's open GitHub pull request to a session, attached once you mention it (#N, the branch or its URL), and attaches any PR URL with chi context add or the web's "+ URL"; the agent gets a note when reviews, comments, checks or its state change; in the web, a path:line in an answer links to that line of the PR; the auto_attach setting can attach the PR at once instead, or do nothing
6
6
  trust_level: reviewed
@@ -11,7 +11,7 @@ needs:
11
11
  hint: brew install gh, then gh auth login
12
12
  plugin:
13
13
  file: plugin.rb
14
- sha256: sha256:82f8c76d38f314c3503eef3a5de0f8c0b20d0bb5bf4a3daebca43b60d00f8c7a
14
+ sha256: sha256:87678c39565a3d586b462d0449a27ee3da4a96f53ef5b352dea959256f6c1eda
15
15
  scripts:
16
16
  pr_context.rb: sha256:a883d2780e89c68c0d7b6d5e0fcb22c273f17bb167e79064e0e99db6bd2ee6f0
17
17
  context_providers:
@@ -29,11 +29,11 @@ require "samagotchi/git_env"
29
29
  # diff" (or "File at head") action the web's ref menu offers. The
30
30
  # after_turn hook links from a per-PR cache and never runs gh (the web holds the answer until the hooks are
31
31
  # done); before_turn, before_generation and the init task fill the cache
32
- # off the turn's thread (`gh api`), in a session worker only: the REPL and
33
- # -p --non-interactive show no web page while they run (ctx.frontend; a chi
32
+ # off the turn's thread (`gh api`), in a session worker only: -p
33
+ # --non-interactive shows no web page while it runs (ctx.frontend; a chi
34
34
  # without it counts as a worker), and a scratch session never fetches.
35
35
  # `bundles: github-pr: line_links:` false turns it off, "always" fetches in
36
- # the REPL and -p too (still not for a scratch session).
36
+ # -p --non-interactive too (still not for a scratch session).
37
37
  #
38
38
  # `bundles: github-pr: auto_attach:` says what the init task does with the
39
39
  # branch's open PR: `propose` (the default, above), `attach` (attached at
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: inline-widgets
3
+ version: 0.1.0
4
+ scope: system
5
+ description: "Teaches the model the chi-ui blocks chi web draws: picks (rows of options that fill your reply) and walk (a scenario stepped through)"
6
+ trust_level: reviewed
7
+ plugin:
8
+ file: plugin.rb
9
+ sha256: sha256:24ad0c3f0ffb592826b4a7389111d76723e9f28ea4d94ab63abda7986d3311bb
10
+ # chi.prompt_section is 0.132.0's; the web draws chi-ui blocks only from the next release
11
+ requires_chi: ">= 0.155.0"
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The inline-widgets bundle (docs/bundles.md, The inline-widgets bundle): two
4
+ # prompt sections that teach the model the ```chi-ui blocks chi web draws
5
+ # (docs/internals/inline-widgets.md): picks, rows of options that fill the
6
+ # user's reply, and walk, a scenario stepped through. The drawing is core's; this
7
+ # bundle only says the blocks exist. Uninstalled, the prompt loses the sections
8
+ # and blocks already in a history still draw.
9
+ #
10
+ # The text is eval 0013's round-2 hint, split in two. It is static, so the
11
+ # prompt cache keeps: same text at every build. A delegate child's answer is
12
+ # read by its parent, not drawn for a user, and a one-shot (-p --non-interactive)
13
+ # prints to a terminal: neither gets a section.
14
+ class Plugin
15
+ PICKS = <<~TEXT
16
+ The web UI draws fenced ```chi-ui code blocks (YAML inside) as interactive widgets; the terminal shows their source. Use one whenever it helps the user more than prose: a widget replaces the long explanation, it doesn't sit beside one. Never use one for what markdown shows well (tables, lists, key-value). At most one widget per answer; keep the prose around it to a few sentences and don't repeat the widget's content.
17
+
18
+ picks: whenever your answer leaves the user two or more things to decide (review findings, a plan's open decisions, which of several fixes to do), even if they didn't ask you to list them. The user clicks one option per row, can add a note, and the UI fills their reply.
19
+ ```chi-ui
20
+ type: picks
21
+ title: What to do with the findings
22
+ rows:
23
+ - id: "1" # unique, short; the user's reply names it
24
+ text: Bridge drops the replay buffer on reset # the item, <= 80 chars
25
+ options: # 2-4 short options, one per line
26
+ - fix now
27
+ - skip
28
+ - later
29
+ recommended: fix now # exactly one of the options
30
+ why: loses events a slow tab still needs # optional, one short line
31
+ ```
32
+ Every row id unique; a row's options unique. Quote a value that starts with a quote or contains ": ".
33
+ TEXT
34
+
35
+ WALK = <<~TEXT
36
+ walk is the other ```chi-ui widget (the same rules: the web UI draws it, at most one widget per answer): a scenario the user steps through one step at a time: who does what, what the user sees, what happens behind it. Use it for "when does this happen / how does this work / what does the user go through" questions.
37
+ ```chi-ui
38
+ type: walk
39
+ title: A tab reconnects
40
+ actors: [user, web, worker] # 2-5 short ids, unique
41
+ steps:
42
+ - id: s1 # unique
43
+ actor: user # one of actors
44
+ does: reopens the laptop # the action, <= 80 chars
45
+ sees: the page shows "reconnecting" # optional: what the user sees at this step
46
+ behind: the tab sends GET /events with Last-Event-ID # optional: what happens out of sight
47
+ - id: s2
48
+ actor: worker
49
+ does: checks the event id
50
+ cases: # optional: the step forks. List EVERY case, the usual one too
51
+ - when: the id is still buffered
52
+ goto: s3
53
+ - when: the id is too old
54
+ goto: s4
55
+ - id: s3
56
+ actor: worker
57
+ does: replays the missed events
58
+ next: end # optional: where to go after this step (default: the step below); end ends the walk here
59
+ - id: s4
60
+ actor: web
61
+ does: reloads the whole session
62
+ sees: the page redraws from the top
63
+ ```
64
+ A step needs `does` or `sees` (or both). Steps run in order unless `next` or `cases` say otherwise. Every id unique; every goto names a step; a next names another step or is `end`. Quote a value that starts with a quote or contains ": ".
65
+ TEXT
66
+
67
+ def register(chi)
68
+ chi.prompt_section("picks") { |ctx| PICKS if drawn?(ctx) }
69
+ chi.prompt_section("walk") { |ctx| WALK if drawn?(ctx) }
70
+ end
71
+
72
+ private
73
+
74
+ # Whether the session's answers reach a web page that draws them.
75
+ def drawn?(ctx) = ctx.frontend != :one_shot && !ctx.delegate?
76
+ end
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: skills
3
- version: 0.1.9
3
+ version: 0.1.10
4
4
  scope: system
5
5
  description: "Skills (skill_<name> memories, the steps of a task done together): /skill save, list, show and diff; older versions kept with a short diff line on every update; a nudge when a followed skill's step fails"
6
6
  trust_level: reviewed
7
7
  plugin:
8
8
  file: plugin.rb
9
- sha256: sha256:781d943749d59ade0e24d04ba774449d31267eb8ca435835c36c67a3e523e632
9
+ sha256: sha256:8d72d29286d0807c18d2c37225455ca06d45f4f974f9335b76cc77916043838c
10
10
  requires_chi: ">= 0.143.0"
11
11
  # config.yml bundles: skills: (another key warns; an older chi ignores this)
12
12
  settings: [history_keep, nudge]
@@ -242,8 +242,9 @@ class Plugin
242
242
  # what was just done (the model has seen the commands; a side answer
243
243
  # wouldn't). The request runs as a turn; sent while a turn runs it joins
244
244
  # that turn at its next step, and the request says to finish the task
245
- # first. A REPL session (--no-shared) takes no messages: the request is
246
- # shown for the user to send.
245
+ # first. A session a one-turn run holds (-p --non-interactive, or an older
246
+ # chi's terminal) takes no messages: the request is shown for the user to
247
+ # send.
247
248
  def save(rest, ctx)
248
249
  words = rest.split
249
250
  scope = words.delete("--system") ? "system" : "project"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: samagotchi-system
3
- version: 0.154.0
3
+ version: 0.155.0
4
4
  scope: system
5
5
  description: Default system memories — identity, self map, config modification protocol, memory guide and the delegated-session rules
6
6
  files:
@@ -199,7 +199,7 @@ final class ChiRunner {
199
199
  private static let recentFlags = ["--limit", "20"]
200
200
 
201
201
  /// The live rows, then up to +recentCount+ of the recent list that are
202
- /// neither live nor a REPL's; a failed recent list adds none.
202
+ /// neither live nor held by an owner (a one-turn run); a failed recent list adds none.
203
203
  private static func rows(live: [LiveSession], recent: Result<[LiveSession], ChiError>, recentCount: Int) -> [LiveSession] {
204
204
  let liveIds = Set(live.map(\.id))
205
205
  let stopped = ((try? recent.get()) ?? [])
@@ -2,137 +2,31 @@
2
2
 
3
3
  module Samagotchi
4
4
  class Engine
5
- # The attached-context candidate judge (ContextJudge): its pass at an
6
- # owner turn's start and for a late candidate, the asks it starts, and
7
- # the cards it shows after the turn. Reads @context_cards_after_turn
8
- # under @context_cards_mutex, @side_judgments, @host_registry, @session.
5
+ # The attached-context candidate judge (TurnStages::ContextJudge,
6
+ # forwarded): prepare_turn starts its pass, run_turn's ensure shows the
7
+ # cards it held, and a plugin's late candidate asks it. Reads
8
+ # @context_judge, @side_judgments, @host_registry.
9
9
  module ContextJudgePart
10
10
  private
11
11
 
12
- # The candidate judge (ContextJudge) at an owner turn's start, after
13
- # the toolkit's ask settled: the session's candidates' status changes
14
- # are logged (ContextCandidatesLog#look) and, unless
15
- # context.candidates.mode is off, the proposed candidates due an ask
16
- # are asked about the owner's prompts so far, this one included. Never
17
- # a participant's turn, a review session or a Continue (no new prompt).
18
- def start_context_judge(turn)
19
- session = turn.session
20
- return unless turn.prompt.is_a?(String) && OwnerPrompt.turn?(session: session, author: turn.origin.author)
21
-
22
- context_judge_pass(session, ContextCandidates.owner_texts(session.messages) + [turn.prompt], phase: "turn")
23
- end
24
- private :start_context_judge
12
+ # The judge's stage (TurnStages::ContextJudge).
13
+ attr_reader :context_judge
25
14
 
26
15
  # A late proposal no owner prompt mentions (Plugin::Host
27
- # #candidate_proposed: github-pr's init task runs beside the first
28
- # turn): judged on the owner's prompts so far, the running turn's too.
29
- def judge_late_candidates
30
- session = @session
31
- return if session.nil? || session.review?
32
-
33
- texts = ContextCandidates.owner_texts(plugin_messages)
34
- context_judge_pass(session, texts, phase: "late") unless texts.empty?
35
- end
36
- private :judge_late_candidates
37
-
38
- # One look at +session+'s candidates; the asks go to SideJudgments'
39
- # :context kind, one thread asking each due candidate in turn.
40
- def context_judge_pass(session, texts, phase:)
41
- return if session.id.nil? || session.scratch
42
-
43
- rows = ContextCandidates.with_status(session.id, project_root: session.project_root, state_dir: session_state_dir)
44
- return if rows.empty?
45
-
46
- log = ContextCandidatesLog.log_for(session_state_dir)
47
- records = ContextCandidatesLog.records_for(log, session.id)
48
- ContextCandidatesLog.look(log, session.id, rows, records) if phase == "turn"
49
- settings = ContextJudge::Settings.from_config
50
- return if settings.mode == "off"
51
-
52
- jobs = ContextJudge.jobs(rows, records, session_id: session.id, texts: texts, settings: settings, phase: phase,
53
- parent_attached: context_parent_attached(session))
54
- return if jobs.empty?
55
-
56
- client = system_one_recording(SystemOne::Client.from_config(host_registry: @host_registry, mode: "on", tag: :context),
57
- ContextJudge::JUDGMENT, session, mode: settings.mode)
58
- return unless client
59
-
60
- start_context_asks(client, jobs, log, settings, context_choice(session, log, settings))
61
- rescue StandardError => e
62
- ContextJudge.warn(e)
63
- end
64
- private :context_judge_pass
65
-
66
- # @return [Thread, nil] the asks' thread, nil when none started
67
- def start_context_asks(client, jobs, log, settings, choice)
68
- if @side_judgments.in_flight?(:context)
69
- Log.info(:context, "context_judge_in_flight_skipped", session_id: jobs.first.session_id)
70
- return nil
71
- end
72
-
73
- @side_judgments.start(:context, warn: ContextJudge.method(:warn)) do
74
- jobs.each { |job| choice.call(job, ContextJudge.ask(client: client, job: job, log: log, settings: settings)) }
75
- end
76
- end
77
- private :start_context_asks
78
-
79
- # What an answer does on the asks' thread (ContextCandidateChoice
80
- # #after_judged: on mode attaches or cards; audit nothing). Taken on
81
- # the caller's thread. The attach only writes the session's folder: its
82
- # note comes with the next between-turn delivery, never into a running
83
- # turn. A card shows only while this Engine is still on that session,
84
- # and not in a running turn (it isn't the turn's: no "waiting for you"
85
- # on it), but once it has ended.
86
- # @return [#call] (job, fields)
87
- def context_choice(session, log, settings)
88
- session_id = session.id
89
- project_root = session.project_root
90
- state_dir = session_state_dir
91
- show = lambda do |**card|
92
- current = @session
93
- next nil unless current.nil? || current.id == session_id
94
-
95
- @context_cards_mutex.synchronize do
96
- next @context_cards_after_turn << card if turn_running?
97
-
98
- show_card(**card)
99
- end
100
- end
101
- lambda do |job, fields|
102
- ContextCandidateChoice.after_judged(job: job, fields: fields, settings: settings, project_root: project_root,
103
- state_dir: state_dir, log: log, show_card: show)
104
- end
105
- end
106
- private :context_choice
107
-
108
- # The cards #context_choice held while a turn ran (#run_turn's ensure,
109
- # after the turn flag is released).
110
- def show_context_cards_after_turn
111
- cards = @context_cards_mutex.synchronize { @context_cards_after_turn.slice!(0..) }
112
- cards.each { |card| show_card(**card) }
113
- rescue StandardError => e
114
- ContextJudge.warn(e)
115
- end
116
- private :show_context_cards_after_turn
117
-
118
- # A delegate child's parent's sources, by title (else name): the
119
- # judge's started_from. nil for any other session (a chi send --new
120
- # session has no parent; a fork isn't a delegate).
121
- def context_parent_attached(session)
122
- return nil unless session.delegate? && session.parent_id
123
-
124
- parent_root = if Session.exist?(session.parent_id, state_dir: session_state_dir)
125
- Session.load(session.parent_id, state_dir: session_state_dir).project_root
126
- else
127
- session.project_root
128
- end
129
- ContextSources.attached(session.parent_id, project_root: parent_root, state_dir: session_state_dir)
130
- .map { |attached| attached.source.title || attached.name }
131
- rescue StandardError => e
132
- Log.warn(:context, "context_parent_attached_failed", error: e.class.name, msg: e.message.to_s[0, 200])
133
- []
16
+ # #candidate_proposed): TurnStages::ContextJudge#late_candidates.
17
+ def judge_late_candidates = @context_judge.late_candidates
18
+
19
+ # The stage over this Engine's side asks and its session (built in
20
+ # #build_tools_and_plugins, before the plugins load: one may propose a
21
+ # candidate as it loads).
22
+ def build_context_judge
23
+ TurnStages::ContextJudge.new(
24
+ side_judgments: @side_judgments, host_registry: @host_registry, state_dir: -> { session_state_dir },
25
+ recording: ->(client, judgment, session, mode:) { system_one_recording(client, judgment, session, mode: mode) },
26
+ session: -> { session }, messages: -> { plugin_messages }, turn_running: -> { turn_running? },
27
+ show_card: ->(**card) { show_card(**card) }
28
+ )
134
29
  end
135
- private :context_parent_attached
136
30
  end
137
31
  end
138
32
  end
@@ -76,11 +76,13 @@ module Samagotchi
76
76
  end
77
77
 
78
78
  # Ask the user to approve a call the gate voted `ask` on
79
- # (GuardrailWiring#request_approval).
79
+ # (GuardrailWiring#request_approval), for the running turn (its
80
+ # author), else the last one. The gate's own asks come with their
81
+ # call's TurnContext instead.
80
82
  # @param verdict [Guardrails::Verdict]
81
83
  # @return [Guardrails::Verdict]
82
84
  def request_approval(verdict)
83
- @guardrail_wiring.request_approval(verdict)
85
+ @guardrail_wiring.request_approval(verdict, turn_context || last_turn_context)
84
86
  end
85
87
  end
86
88
  end
@@ -46,7 +46,7 @@ module Samagotchi
46
46
  from_cwd: note[:from_cwd], text: note[:text], context_source: note[:context_source],
47
47
  created_at: note[:created_at], objectives: entry }.compact)
48
48
  announce_objectives(session) if entry
49
- objectives_audit.note(session, note)
49
+ @objectives_stage.note(session, note)
50
50
  message
51
51
  end
52
52
  end
@@ -76,14 +76,17 @@ module Samagotchi
76
76
  # register it on the kernel's llm_context_turn): the same array the loop
77
77
  # appends to, not a copy, so read it on the turn's thread (the pending
78
78
  # input drain, at an iteration boundary: TurnCheckpoint), or from another
79
- # only as a dup, read only (/objectives). nil when no
80
- # turn runs (llm_context_turn is never cleared, and still holds the
81
- # last turn's then).
79
+ # only as a dup, read only (/objectives). nil when no turn runs, and
80
+ # while the running turn's loop has yet to register its own
81
+ # (llm_context_turn is never cleared, and still holds the last turn's
82
+ # then: its turn_id tells them apart).
82
83
  # @return [Array<Hash>, nil]
83
84
  def live_conversation
84
- return nil unless turn_running? && @kernel.respond_to?(:llm_context_turn)
85
+ running = turn_context
86
+ return nil unless running && @kernel.respond_to?(:llm_context_turn)
85
87
 
86
- @kernel.llm_context_turn&.conversation
88
+ forget = @kernel.llm_context_turn
89
+ forget&.turn_id == running.id ? forget.conversation : nil
87
90
  end
88
91
 
89
92
  # Append messages to the current session's conversation.