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
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Samagotchi
4
+ class Engine
5
+ # Verify and Revise anchors at a turn's end: the opening's stamp, a Revise
6
+ # turn's draft checked, a Verify turn's quote checked against its source
7
+ # session and its verdict recorded. No ivars of its own: it reads the turn,
8
+ # and the sessions it loads through Engine methods.
9
+ module VerifyPart
10
+ # The turn's anchors, stamped on its opening message (the prompt, or a
11
+ # wake turn's note) in the messages it keeps: display metadata, never
12
+ # in the prompt (LLM::ChatLoop#plain keeps them, no wire sends them).
13
+ # A Verify turn's claim however it ended; a Revise turn's draft only
14
+ # once it completed with an answer (#revision_target). Returns the
15
+ # stamped ReviseAnchor (the end event's revises), or nil.
16
+ def stamp_opening(messages, turn, outcome)
17
+ return unless messages.is_a?(Array)
18
+
19
+ revises = revision_target(messages, turn, outcome)
20
+ fields = { verify: turn.verify&.to_h, revises: revises&.to_h }.compact
21
+ return if fields.empty?
22
+
23
+ opening = messages.reverse.find do |message|
24
+ Steer.turn_prompt?(message) && (message[:turn_id] || message["turn_id"]).to_s == turn.id
25
+ end
26
+ return unless opening
27
+
28
+ opening.merge!(fields)
29
+ revises
30
+ end
31
+ private :stamp_opening
32
+
33
+ # The turn a Revise turn revised (D6: "this turn revised that one"):
34
+ # only a completed turn with an answer (AnswerTail) has revised
35
+ # anything. A draft no prompt in the messages carries is no target (a
36
+ # stale page, a rolled-back turn); one a stamped turn already revised
37
+ # is re-targeted to its stack's top, the newest version, which the
38
+ # person meant ("this draft").
39
+ def revision_target(messages, turn, outcome)
40
+ return unless turn.revises && outcome == "completed"
41
+
42
+ slice = AnswerTail.turn_slice(messages, turn.id)
43
+ return unless slice&.any? { |message| AnswerTail.answer?(message) }
44
+
45
+ target = turn.revises.turn_id
46
+ seen = [turn.id]
47
+ until seen.include?(target)
48
+ return reject_revise_anchor(turn.revises, :turn_missing) unless AnswerTail.turn_slice(messages, target)
49
+
50
+ seen << target
51
+ newer = messages.reverse.find do |message|
52
+ Steer.turn_prompt?(message) && ReviseAnchor.from(message[:revises] || message["revises"])&.turn_id == target
53
+ end
54
+ return ReviseAnchor.new(turn_id: target) unless newer
55
+
56
+ target = (newer[:turn_id] || newer["turn_id"]).to_s
57
+ end
58
+ reject_revise_anchor(turn.revises, :cycle)
59
+ end
60
+ private :revision_target
61
+
62
+ def reject_revise_anchor(anchor, reason)
63
+ Log.warn(:turn, "revise_anchor_rejected", turn_id: anchor.turn_id, reason:)
64
+ nil
65
+ end
66
+ private :reject_revise_anchor
67
+
68
+ def verified_anchor(session, anchor)
69
+ return unless anchor
70
+
71
+ source = verify_anchor_source_session(session, anchor)
72
+ return unless source
73
+
74
+ turn = AnswerTail.turn_slice(Array(source.messages), anchor.turn_id)
75
+ return reject_verify_anchor(anchor, :turn_missing) unless turn
76
+
77
+ reply = turn.reverse.find { |message| AnswerTail.answer?(message) }
78
+ return reject_verify_anchor(anchor, :reply_missing) unless reply
79
+
80
+ quote_text = verify_comparison_text(anchor.quote)
81
+ quoted = !quote_text.empty? && verify_quote_in?(quote_text, (reply[:content] || reply["content"]).to_s, source)
82
+ return reject_verify_anchor(anchor, :quote_missing) unless quoted
83
+
84
+ anchor
85
+ end
86
+ private :verified_anchor
87
+
88
+ # Whether +quote_text+ is in +answer+ as the model wrote it, or as the
89
+ # pages show it: a page quotes its own text, which reads the session's
90
+ # folder relative, the home folder as ~ and a secret shape as
91
+ # [redacted] (a participant's page gets it so, Redact;
92
+ # the owner's page rewrites its quote the same way, claim_link.js
93
+ # normalizeClaim, so claim links match both pages). +source+ is the
94
+ # session whose folder that is.
95
+ def verify_quote_in?(quote_text, answer, source)
96
+ return true if verify_comparison_text(answer).include?(quote_text)
97
+
98
+ viewer = Redact::Viewer.new(cwd: source.working_directory)
99
+ verify_comparison_text(Redact.text(answer, viewer)).include?(quote_text)
100
+ end
101
+ private :verify_quote_in?
102
+
103
+ def verify_anchor_source_session(session, anchor)
104
+ return session unless session.verify_fresh
105
+
106
+ fresh = session.verify_fresh
107
+ return reject_verify_anchor(anchor, :fresh_anchor_mismatch) unless fresh.anchor == anchor
108
+ return reject_verify_anchor(anchor, :fresh_parent_missing) unless session.parent_id
109
+
110
+ Session.load(session.parent_id, state_dir: session_state_dir)
111
+ rescue ArgumentError, SystemCallError, IOError => e
112
+ Log.warn(:turn, "verify_anchor_rejected", turn_id: anchor.turn_id, reason: :source_session_unavailable,
113
+ error: e.class.name)
114
+ nil
115
+ end
116
+ private :verify_anchor_source_session
117
+
118
+ def verify_comparison_text(text)
119
+ OutputFormatter.strip_markup(text)
120
+ .gsub(/!\[([^\]]*)\]\([^)]+\)/, '\1')
121
+ .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
122
+ .gsub(/^[ \t]*(?:\#{1,6}[ \t]+|>[ \t]?|[-+*][ \t]+|\d+[.)][ \t]+)/, "")
123
+ .gsub(/(\*\*|__|~~|`+)(.+?)\1/m, '\2')
124
+ .gsub(/(?<!\w)\*(\S(?:.*?\S)?)\*(?!\w)/m, '\1')
125
+ .gsub(/(?<!\w)_(\S(?:.*?\S)?)_(?!\w)/m, '\1')
126
+ .tr("|", " ") # a table row's cells; the page copies them without the pipes
127
+ .gsub(/\s+/, " ")
128
+ .strip
129
+ end
130
+ private :verify_comparison_text
131
+
132
+ def reject_verify_anchor(anchor, reason)
133
+ Log.warn(:turn, "verify_anchor_rejected", turn_id: anchor.turn_id, reason:)
134
+ nil
135
+ end
136
+ private :reject_verify_anchor
137
+
138
+ def record_verdict(turn, outcome)
139
+ return unless outcome == "completed" && turn.verify
140
+
141
+ answer = AnswerTail.find(turn.session.messages, turn_id: turn.id)
142
+ parsed = Verdict.parse(answer && (answer[:content] || answer["content"]))
143
+ return unless parsed
144
+
145
+ record = { anchor: turn.verify.to_h, verdict: parsed.verdict, reason: parsed.reason,
146
+ by: turn.origin.author, check_turn_id: turn.id, at: Time.now.iso8601(3) }
147
+ turn.session.verdicts = turn.session.verdicts + [record]
148
+ record
149
+ end
150
+ private :record_verdict
151
+ end
152
+ end
153
+ end