samagotchi 0.134.0 → 0.135.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -1
- data/bin/chi +2 -3
- data/docs/architecture.md +6 -3
- data/docs/cli.md +39 -34
- data/docs/configuration.md +1 -2
- data/docs/guardrails.md +53 -9
- data/docs/internals/engine.md +6 -3
- data/docs/internals/participants.md +1 -1
- data/docs/internals/web-frontend.md +262 -0
- data/docs/sessions.md +2 -0
- data/docs/testing.md +27 -2
- data/lib/samagotchi/bridge.rb +11 -6
- data/lib/samagotchi/bridge_client.rb +8 -5
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/config.rb +0 -2
- data/lib/samagotchi/engine.rb +69 -12
- data/lib/samagotchi/guardrail_wiring.rb +8 -6
- data/lib/samagotchi/guardrails/approval.rb +41 -5
- data/lib/samagotchi/guardrails/implicit_writers.rb +19 -4
- data/lib/samagotchi/guardrails/pair_boundary.rb +5 -1
- data/lib/samagotchi/guardrails/pair_command.rb +387 -78
- data/lib/samagotchi/guardrails/parent_approvals.rb +12 -15
- data/lib/samagotchi/guardrails/read_only_shell.rb +24 -2
- data/lib/samagotchi/guardrails/shared_git.rb +1 -1
- data/lib/samagotchi/guardrails/shell_git_dirs.rb +1 -1
- data/lib/samagotchi/guardrails/shell_lex.rb +22 -0
- data/lib/samagotchi/guardrails_command.rb +13 -10
- data/lib/samagotchi/llm/chat_loop.rb +4 -3
- data/lib/samagotchi/message_parts.rb +1 -1
- data/lib/samagotchi/revise_anchor.rb +33 -0
- data/lib/samagotchi/session_inbox.rb +20 -12
- data/lib/samagotchi/session_manager.rb +13 -11
- data/lib/samagotchi/system_one/approval_question.rb +21 -16
- data/lib/samagotchi/system_one/self_protect.rb +13 -15
- data/lib/samagotchi/tools/delegate_relay.rb +6 -5
- data/lib/samagotchi/tools/delegate_wait.rb +5 -5
- data/lib/samagotchi/tools/execute.rb +6 -6
- data/lib/samagotchi/tools/output_guardrails.rb +9 -2
- data/lib/samagotchi/tools/read.rb +5 -5
- data/lib/samagotchi/turn_anchors.rb +49 -0
- data/lib/samagotchi/verify_anchor.rb +5 -5
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +2 -14
- data/lib/samagotchi/web/message_display.rb +17 -1
- data/lib/samagotchi/web/participant_access.rb +2 -2
- data/lib/samagotchi/web/participant_view.rb +8 -0
- data/lib/samagotchi/web/public/activity.js +1 -1
- data/lib/samagotchi/web/public/annotations.js +1 -1
- data/lib/samagotchi/web/public/answer_fold.js +3 -3
- data/lib/samagotchi/web/public/app.js +447 -340
- data/lib/samagotchi/web/public/bubbles.js +3 -3
- data/lib/samagotchi/web/public/cards_place.js +4 -2
- data/lib/samagotchi/web/public/composer.css +8 -0
- data/lib/samagotchi/web/public/context_chips.js +9 -6
- data/lib/samagotchi/web/public/data.js +4 -2
- data/lib/samagotchi/web/public/dock_reserve.js +13 -15
- data/lib/samagotchi/web/public/feature_gates.js +33 -0
- data/lib/samagotchi/web/public/history_reveal.js +8 -4
- data/lib/samagotchi/web/public/index.html +38 -9
- data/lib/samagotchi/web/public/latest.js +46 -0
- data/lib/samagotchi/web/public/lists_view.js +15 -13
- data/lib/samagotchi/web/public/live_timing.js +2 -0
- data/lib/samagotchi/web/public/markdown_place.js +1 -1
- data/lib/samagotchi/web/public/method_api.js +14 -0
- data/lib/samagotchi/web/public/notifications.js +4 -11
- data/lib/samagotchi/web/public/participant.js +11 -6
- data/lib/samagotchi/web/public/participant_names.js +1 -1
- data/lib/samagotchi/web/public/participants_panel.js +4 -2
- data/lib/samagotchi/web/public/project_picker.js +18 -22
- data/lib/samagotchi/web/public/question_cards.js +2 -2
- data/lib/samagotchi/web/public/restore.js +2 -0
- data/lib/samagotchi/web/public/revise_stack.js +372 -0
- data/lib/samagotchi/web/public/scope.js +12 -20
- data/lib/samagotchi/web/public/send_turn.js +29 -16
- data/lib/samagotchi/web/public/session_actions.js +15 -14
- data/lib/samagotchi/web/public/session_fields.js +152 -0
- data/lib/samagotchi/web/public/session_payload.js +35 -0
- data/lib/samagotchi/web/public/session_state.js +2 -0
- data/lib/samagotchi/web/public/session_store.js +191 -0
- data/lib/samagotchi/web/public/stage_model.js +4 -4
- data/lib/samagotchi/web/public/stage_view.js +11 -16
- data/lib/samagotchi/web/public/stream_handlers.js +88 -90
- data/lib/samagotchi/web/public/task_stop.js +1 -1
- data/lib/samagotchi/web/public/timing.js +2 -2
- data/lib/samagotchi/web/public/{turn_view.js → turn_block.js} +27 -92
- data/lib/samagotchi/web/public/turn_events.js +2 -2
- data/lib/samagotchi/web/public/turn_html.js +5 -3
- data/lib/samagotchi/web/public/turn_model.js +6 -6
- data/lib/samagotchi/web/public/verdict_fresh.js +2 -0
- data/lib/samagotchi/web/public/working_set_view.js +3 -1
- data/lib/samagotchi/web/server.rb +4 -4
- data/lib/samagotchi/web/session_control.rb +2 -2
- data/lib/samagotchi/worker.rb +9 -9
- data/lib/samagotchi/worker_inbound.rb +6 -5
- metadata +12 -3
- data/lib/samagotchi/web/public/hold.js +0 -78
|
@@ -192,7 +192,7 @@ module Samagotchi
|
|
|
192
192
|
# of a relay), else its hook; an approval's call (#what_text).
|
|
193
193
|
# @param question [PendingQuestion] the child's
|
|
194
194
|
def what(question)
|
|
195
|
-
return what_text(
|
|
195
|
+
return what_text(Guardrails::ApprovalFacts.from_h(question.approval)) unless question.kind.to_s == HOOK_KIND
|
|
196
196
|
|
|
197
197
|
inner = symbolized(question.relay, nil)
|
|
198
198
|
header = (inner ? inner[:header] : question.header).to_s
|
|
@@ -204,11 +204,12 @@ module Samagotchi
|
|
|
204
204
|
end
|
|
205
205
|
|
|
206
206
|
# execute: git push origin main
|
|
207
|
+
# @param facts [Guardrails::ApprovalFacts, nil] the saved card's
|
|
207
208
|
def what_text(facts)
|
|
208
|
-
facts
|
|
209
|
-
tool = facts
|
|
210
|
-
what = facts
|
|
211
|
-
what = facts
|
|
209
|
+
facts ||= Guardrails::ApprovalFacts.new
|
|
210
|
+
tool = facts.label || facts.tool || "tool"
|
|
211
|
+
what = facts.command || Array(facts.paths).join(", ")
|
|
212
|
+
what = facts.args.to_s if what.to_s.empty?
|
|
212
213
|
what.to_s.empty? ? tool.to_s : "#{tool}: #{what}"
|
|
213
214
|
end
|
|
214
215
|
|
|
@@ -337,13 +337,13 @@ module Samagotchi
|
|
|
337
337
|
"truncated=true",
|
|
338
338
|
"preview_strategy=head_tail",
|
|
339
339
|
"reply_bytes=#{text.bytesize}",
|
|
340
|
-
"returned_preview_bytes=#{preview
|
|
341
|
-
"omitted_bytes=#{preview
|
|
340
|
+
"returned_preview_bytes=#{preview.returned_preview_bytes}",
|
|
341
|
+
"omitted_bytes=#{preview.omitted_bytes}",
|
|
342
342
|
"[TRUNCATED_PREVIEW_HEAD]",
|
|
343
|
-
preview
|
|
344
|
-
"[... omitted #{preview
|
|
343
|
+
preview.head,
|
|
344
|
+
"[... omitted #{preview.omitted_bytes} bytes ...]",
|
|
345
345
|
"[TRUNCATED_PREVIEW_TAIL]",
|
|
346
|
-
preview
|
|
346
|
+
preview.tail
|
|
347
347
|
].join("\n")
|
|
348
348
|
end
|
|
349
349
|
end
|
|
@@ -469,13 +469,13 @@ module Samagotchi
|
|
|
469
469
|
"truncated=true",
|
|
470
470
|
"preview_strategy=head_tail",
|
|
471
471
|
"#{label}_bytes=#{bytes}",
|
|
472
|
-
"returned_preview_bytes=#{preview
|
|
473
|
-
"omitted_bytes=#{preview
|
|
472
|
+
"returned_preview_bytes=#{preview.returned_preview_bytes}",
|
|
473
|
+
"omitted_bytes=#{preview.omitted_bytes}",
|
|
474
474
|
"[TRUNCATED_PREVIEW_HEAD]",
|
|
475
|
-
preview
|
|
476
|
-
"[... omitted #{preview
|
|
475
|
+
preview.head,
|
|
476
|
+
"[... omitted #{preview.omitted_bytes} bytes ...]",
|
|
477
477
|
"[TRUNCATED_PREVIEW_TAIL]",
|
|
478
|
-
preview
|
|
478
|
+
preview.tail
|
|
479
479
|
].join("\n")
|
|
480
480
|
end
|
|
481
481
|
|
|
@@ -492,7 +492,7 @@ module Samagotchi
|
|
|
492
492
|
# A big log: its size, and the head and tail read by seek, which
|
|
493
493
|
# output_block shows as it shows a long string's.
|
|
494
494
|
class LogPreview
|
|
495
|
-
attr_reader :bytesize, :preview
|
|
495
|
+
attr_reader :bytesize, :preview # Integer, OutputGuardrails::PreviewParts
|
|
496
496
|
|
|
497
497
|
def initialize(bytesize, preview)
|
|
498
498
|
@bytesize = bytesize
|
|
@@ -11,6 +11,11 @@ module Samagotchi
|
|
|
11
11
|
DEFAULT_TELEMETRY_THRESHOLD_PCT = 80.0
|
|
12
12
|
DEFAULT_CHARS_PER_TOKEN = 4.0
|
|
13
13
|
|
|
14
|
+
# A large output's head and tail (UTF-8 safe) with the byte counts the
|
|
15
|
+
# truncated-preview result lines report.
|
|
16
|
+
PreviewParts = Data.define(:head, :tail, :head_bytes, :tail_bytes, :returned_preview_bytes,
|
|
17
|
+
:omitted_bytes, :total_bytes)
|
|
18
|
+
|
|
14
19
|
module_function
|
|
15
20
|
|
|
16
21
|
# The config setting +key+ (read.*, execute.*, context.*) when positive, else +default+.
|
|
@@ -41,6 +46,7 @@ module Samagotchi
|
|
|
41
46
|
.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "?")
|
|
42
47
|
end
|
|
43
48
|
|
|
49
|
+
# @return [PreviewParts]
|
|
44
50
|
def head_tail_from_file(path:, file_size:, preview_bytes:)
|
|
45
51
|
half = [preview_bytes / 2, 1].max
|
|
46
52
|
head = File.binread(path, half, 0)
|
|
@@ -50,6 +56,7 @@ module Samagotchi
|
|
|
50
56
|
build_preview_parts(head: head, tail: tail, total_bytes: file_size)
|
|
51
57
|
end
|
|
52
58
|
|
|
59
|
+
# @return [PreviewParts]
|
|
53
60
|
def head_tail_from_string(content:, preview_bytes:)
|
|
54
61
|
half = [preview_bytes / 2, 1].max
|
|
55
62
|
head = content.byteslice(0, half) || ""
|
|
@@ -78,7 +85,7 @@ module Samagotchi
|
|
|
78
85
|
tail_bytes = tail.bytesize
|
|
79
86
|
omitted_bytes = [total_bytes - (head_bytes + tail_bytes), 0].max
|
|
80
87
|
|
|
81
|
-
|
|
88
|
+
PreviewParts.new(
|
|
82
89
|
head: safe_utf8(head),
|
|
83
90
|
tail: safe_utf8(tail),
|
|
84
91
|
head_bytes: head_bytes,
|
|
@@ -86,7 +93,7 @@ module Samagotchi
|
|
|
86
93
|
returned_preview_bytes: head_bytes + tail_bytes,
|
|
87
94
|
omitted_bytes: omitted_bytes,
|
|
88
95
|
total_bytes: total_bytes
|
|
89
|
-
|
|
96
|
+
)
|
|
90
97
|
end
|
|
91
98
|
private_class_method :build_preview_parts
|
|
92
99
|
end
|
|
@@ -81,10 +81,10 @@ module Samagotchi
|
|
|
81
81
|
|
|
82
82
|
preview_block = [
|
|
83
83
|
"[TRUNCATED_PREVIEW_HEAD]",
|
|
84
|
-
preview
|
|
85
|
-
"[... omitted #{preview
|
|
84
|
+
preview.head,
|
|
85
|
+
"[... omitted #{preview.omitted_bytes} bytes ...]",
|
|
86
86
|
"[TRUNCATED_PREVIEW_TAIL]",
|
|
87
|
-
preview
|
|
87
|
+
preview.tail
|
|
88
88
|
].join("\n")
|
|
89
89
|
|
|
90
90
|
metadata_lines = [
|
|
@@ -92,8 +92,8 @@ module Samagotchi
|
|
|
92
92
|
"path=#{path}",
|
|
93
93
|
"preview_strategy=head_tail",
|
|
94
94
|
"file_bytes=#{file_size}",
|
|
95
|
-
"returned_preview_bytes=#{preview
|
|
96
|
-
"omitted_bytes=#{preview
|
|
95
|
+
"returned_preview_bytes=#{preview.returned_preview_bytes}",
|
|
96
|
+
"omitted_bytes=#{preview.omitted_bytes}"
|
|
97
97
|
]
|
|
98
98
|
|
|
99
99
|
telemetry = telemetry_lines_for(preview_block)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "revise_anchor"
|
|
4
|
+
require_relative "verify_anchor"
|
|
5
|
+
|
|
6
|
+
module Samagotchi
|
|
7
|
+
# What a turn points at in an earlier turn: a Verify turn's claim
|
|
8
|
+
# (VerifyAnchor) and a Revise turn's draft (ReviseAnchor). One carrier
|
|
9
|
+
# through every layer a turn's input travels (chi web's POST /turn, the
|
|
10
|
+
# Bridge, the input file, SessionInbox::Input, WorkerInbound::Prompt, a
|
|
11
|
+
# failed turn's restore), so a new anchor is one field here. On the wire
|
|
12
|
+
# and in the input file each is its own key ("verify", "revises"); a
|
|
13
|
+
# missing or malformed one is nil. The Engine takes them as run_turn's
|
|
14
|
+
# keywords (#kwargs): what each means is its own.
|
|
15
|
+
TurnAnchors = Data.define(:verify, :revises) do
|
|
16
|
+
def self.from(value)
|
|
17
|
+
return value if value.is_a?(self)
|
|
18
|
+
|
|
19
|
+
fields = value.is_a?(Hash) ? value.to_h { |key, entry| [key.to_s, entry] } : {}
|
|
20
|
+
new(verify: fields["verify"], revises: fields["revises"])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Whether a parsed input file names an anchor (a Hash under its key,
|
|
24
|
+
# well-formed or not): SessionInbox.turn_end_data? holds it.
|
|
25
|
+
def self.named_in?(data) = %w[verify revises].any? { |key| data[key].is_a?(Hash) }
|
|
26
|
+
|
|
27
|
+
def initialize(verify: nil, revises: nil)
|
|
28
|
+
super(verify: VerifyAnchor.from(verify), revises: ReviseAnchor.from(revises))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def empty? = verify.nil? && revises.nil?
|
|
32
|
+
|
|
33
|
+
# The anchors given, each answering FEATURE (the worker's Bridge
|
|
34
|
+
# feature it needs) and UNAVAILABLE (the refusal without it).
|
|
35
|
+
def present = [verify, revises].compact
|
|
36
|
+
|
|
37
|
+
# The body's and the input file's keys.
|
|
38
|
+
def to_file = { "verify" => verify&.to_file, "revises" => revises&.to_file }.compact
|
|
39
|
+
|
|
40
|
+
# A :prompt_restored event's fields.
|
|
41
|
+
def to_h = { verify: verify&.to_h, revises: revises&.to_h }.compact
|
|
42
|
+
|
|
43
|
+
# Engine#run_turn's keywords.
|
|
44
|
+
def kwargs = { verify:, revises: }.compact
|
|
45
|
+
|
|
46
|
+
# A participant's: they can't Revise (participant_access.rb, the Bridge).
|
|
47
|
+
def without_revises = with(revises: nil)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
module Samagotchi
|
|
4
4
|
# A claim selected in an earlier turn, carried only by a Verify turn.
|
|
5
5
|
VerifyAnchor = Data.define(:turn_id, :quote) do
|
|
6
|
-
UUID = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
|
|
7
|
-
QUOTE_MAX = 200
|
|
8
|
-
|
|
9
6
|
def self.from(value)
|
|
10
7
|
return value if value.is_a?(self)
|
|
11
8
|
return nil unless value.is_a?(Hash)
|
|
@@ -15,8 +12,8 @@ module Samagotchi
|
|
|
15
12
|
|
|
16
13
|
turn_id = fields["turn_id"]
|
|
17
14
|
quote = fields["quote"]
|
|
18
|
-
return nil unless turn_id.is_a?(String) && UUID.match?(turn_id)
|
|
19
|
-
return nil unless quote.is_a?(String) && quote.length.between?(1, QUOTE_MAX) && !quote.strip.empty?
|
|
15
|
+
return nil unless turn_id.is_a?(String) && self::UUID.match?(turn_id)
|
|
16
|
+
return nil unless quote.is_a?(String) && quote.length.between?(1, self::QUOTE_MAX) && !quote.strip.empty?
|
|
20
17
|
|
|
21
18
|
new(turn_id:, quote:)
|
|
22
19
|
end
|
|
@@ -26,6 +23,9 @@ module Samagotchi
|
|
|
26
23
|
def to_file = { "turn_id" => turn_id, "quote" => quote }
|
|
27
24
|
end
|
|
28
25
|
|
|
26
|
+
# The shape of the turn id an anchor names (ReviseAnchor's too).
|
|
27
|
+
VerifyAnchor::UUID = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
|
|
28
|
+
VerifyAnchor::QUOTE_MAX = 200
|
|
29
29
|
VerifyAnchor::FEATURE = "verify"
|
|
30
30
|
VerifyAnchor::UNAVAILABLE = {
|
|
31
31
|
"error" => "verify_unavailable",
|
data/lib/samagotchi/version.rb
CHANGED
data/lib/samagotchi/web/app.rb
CHANGED
|
@@ -134,8 +134,6 @@ module Samagotchi
|
|
|
134
134
|
|
|
135
135
|
# @param bridge_wait_timeout [Float] bounded seconds to wait for a
|
|
136
136
|
# freshly-spawned worker's bridge before answering POST /api/sessions.
|
|
137
|
-
# @param view ["stage", "turn"] the page's view of a turn (web.view);
|
|
138
|
-
# ?view=stage|turn overrides it for one page load
|
|
139
137
|
# @param working_set [Boolean] the stage view's working set
|
|
140
138
|
# (web.working_set); ?working_set=1|0 overrides it for one page load
|
|
141
139
|
# @param annotate_presets [String, Array] the quick replies next to
|
|
@@ -168,7 +166,7 @@ module Samagotchi
|
|
|
168
166
|
# @param start_dir [String] the folder chi web starts in (StartDir):
|
|
169
167
|
# where a new session without a dir starts (nil: the cwd)
|
|
170
168
|
def initialize(manager: nil, session_class: nil, state_dir: nil, public_dir: nil,
|
|
171
|
-
bridge_wait_timeout: BRIDGE_WAIT_TIMEOUT, markdown: false,
|
|
169
|
+
bridge_wait_timeout: BRIDGE_WAIT_TIMEOUT, markdown: false, hub: nil,
|
|
172
170
|
annotate_presets: Config::BY_KEY["web.annotate_presets"].default,
|
|
173
171
|
editor: Config::BY_KEY["web.editor"].default,
|
|
174
172
|
events_heartbeat: EVENTS_HEARTBEAT, events_queue: EVENTS_QUEUE,
|
|
@@ -188,7 +186,6 @@ module Samagotchi
|
|
|
188
186
|
@public_dir = public_dir || File.expand_path("public", __dir__)
|
|
189
187
|
@bridge_wait_timeout = bridge_wait_timeout
|
|
190
188
|
@markdown_renderer = MarkdownRenderer.new(enabled: markdown)
|
|
191
|
-
@view = view
|
|
192
189
|
@working_set = working_set
|
|
193
190
|
@annotate_presets = annotate_presets.is_a?(Array) ? annotate_presets.join("|") : annotate_presets.to_s
|
|
194
191
|
@editor_url = Editor.template(editor)
|
|
@@ -2015,8 +2012,7 @@ module Samagotchi
|
|
|
2015
2012
|
# The owner's home: a claim link's quote reads it "~", as the
|
|
2016
2013
|
# participant page shows it (claim_link.js).
|
|
2017
2014
|
attrs["home"] = Dir.home
|
|
2018
|
-
|
|
2019
|
-
# Only when on: the page reads its presence (the turn view ignores it).
|
|
2015
|
+
# Only when on: the page reads its presence.
|
|
2020
2016
|
attrs["working-set"] = "1" if working_set?(req.params["working_set"])
|
|
2021
2017
|
attrs.merge!(annotate_presets_data)
|
|
2022
2018
|
dir, = scope_dir(req.params["dir"])
|
|
@@ -2060,14 +2056,6 @@ module Samagotchi
|
|
|
2060
2056
|
File.directory?(dir) && ProjectScope.root_for(dir) ? dir : nil
|
|
2061
2057
|
end
|
|
2062
2058
|
|
|
2063
|
-
VIEWS = Config::BY_KEY["web.view"].enum_values
|
|
2064
|
-
|
|
2065
|
-
# The view for this page load: ?view=stage|turn wins, anything
|
|
2066
|
-
# else leaves the config's choice (web.view).
|
|
2067
|
-
def view_name(param)
|
|
2068
|
-
VIEWS.include?(param) ? param : @view
|
|
2069
|
-
end
|
|
2070
|
-
|
|
2071
2059
|
# The working set for this page load: ?working_set=1|0 wins, anything
|
|
2072
2060
|
# else leaves the config's choice (web.working_set).
|
|
2073
2061
|
def working_set?(param)
|
|
@@ -10,6 +10,7 @@ require_relative "../message_time"
|
|
|
10
10
|
require_relative "../output_formatter"
|
|
11
11
|
require_relative "../recap_store"
|
|
12
12
|
require_relative "../ref"
|
|
13
|
+
require_relative "../revise_anchor"
|
|
13
14
|
require_relative "../steer"
|
|
14
15
|
require_relative "../turn_note"
|
|
15
16
|
require_relative "../bridge/card_store"
|
|
@@ -70,7 +71,7 @@ module Samagotchi
|
|
|
70
71
|
|
|
71
72
|
# @param msgs [Array<Hash>] a session's messages; symbol keys from disk,
|
|
72
73
|
# string keys from a Bridge snapshot
|
|
73
|
-
# @param parts [Boolean] the turn
|
|
74
|
+
# @param parts [Boolean] the turn block's reload (?parts=1): each
|
|
74
75
|
# assistant message also carries what it did (MessageParts: thinking,
|
|
75
76
|
# tool calls with params and output), and a message with nothing to
|
|
76
77
|
# show but that (a text-less step) is kept with empty content
|
|
@@ -91,6 +92,7 @@ module Samagotchi
|
|
|
91
92
|
if Samagotchi::Steer.turn_prompt?(m)
|
|
92
93
|
note[:turn_start] = true
|
|
93
94
|
note[:turn_id] = (m[:turn_id] || m["turn_id"]).to_s
|
|
95
|
+
note.merge!(revises_of(m))
|
|
94
96
|
end
|
|
95
97
|
filtered << note
|
|
96
98
|
next
|
|
@@ -135,6 +137,13 @@ module Samagotchi
|
|
|
135
137
|
# record by this, not by its place.
|
|
136
138
|
turn_id = m[:turn_id] || m["turn_id"]
|
|
137
139
|
message[:turn_id] = turn_id.to_s if norm_role == "user" && turn_id
|
|
140
|
+
# The turn it revised (a Revise stack, Engine#stamp_opening), and
|
|
141
|
+
# whether it was a Verify turn (the page offers no Revise on its
|
|
142
|
+
# answer).
|
|
143
|
+
if norm_role == "user" && turn_id && !message[:merged]
|
|
144
|
+
message.merge!(revises_of(m))
|
|
145
|
+
message[:verify] = true if Samagotchi::VerifyAnchor.from(m[:verify] || m["verify"])
|
|
146
|
+
end
|
|
138
147
|
# A participant's message names them (the page's author badge).
|
|
139
148
|
if norm_role == "user"
|
|
140
149
|
message.merge!(Samagotchi::Steer.author_fields(m[:author] || m["author"], m[:author_name] || m["author_name"]))
|
|
@@ -166,6 +175,13 @@ module Samagotchi
|
|
|
166
175
|
[]
|
|
167
176
|
end
|
|
168
177
|
|
|
178
|
+
# A turn opening's Revise stamp as {revises: {turn_id:}}, checked again
|
|
179
|
+
# (a stored file is never trusted), or nothing.
|
|
180
|
+
def revises_of(message)
|
|
181
|
+
anchor = Samagotchi::ReviseAnchor.from(message[:revises] || message["revises"])
|
|
182
|
+
anchor ? { revises: anchor.to_h } : {}
|
|
183
|
+
end
|
|
184
|
+
|
|
169
185
|
# The entry's wall time (MessageTime: the page's hover and gap
|
|
170
186
|
# dividers) as {at:}, or nothing for an entry saved without one.
|
|
171
187
|
def time_of(message)
|
|
@@ -272,10 +272,10 @@ module Samagotchi
|
|
|
272
272
|
reason = participant_turn_refusal(grant, prompt, body)
|
|
273
273
|
return participant_turn_refused(grant, prompt, *reason) if reason
|
|
274
274
|
|
|
275
|
-
|
|
275
|
+
# A Verify turn's claim; never a Revise (the owner's stacks).
|
|
276
276
|
result = SessionManager.deliver_turn(id, prompt: prompt, client_id: ClientId.review(grant.id),
|
|
277
277
|
delivery: Delivery::QUEUE, author: grant.author,
|
|
278
|
-
|
|
278
|
+
anchors: TurnAnchors.from(body).without_revises,
|
|
279
279
|
pending_limit: grant.queue_limit + 1,
|
|
280
280
|
state_dir: @state_dir, manager: @manager,
|
|
281
281
|
bridge: -> { live_bridge_client(id) })
|
|
@@ -4,6 +4,7 @@ require_relative "../output_formatter"
|
|
|
4
4
|
require_relative "../steer"
|
|
5
5
|
require_relative "../turn_origin"
|
|
6
6
|
require_relative "../participant_mode"
|
|
7
|
+
require_relative "../revise_anchor"
|
|
7
8
|
require_relative "../verify_anchor"
|
|
8
9
|
|
|
9
10
|
module Samagotchi
|
|
@@ -134,6 +135,10 @@ module Samagotchi
|
|
|
134
135
|
out = { "type" => field(event, :type).to_s, **id_fields(event) }
|
|
135
136
|
verdict = verdict_record(field(event, :verdict), viewer)
|
|
136
137
|
out["verdict"] = verdict if verdict
|
|
138
|
+
# The turn a completed Revise turn revised (as on its prompt; the
|
|
139
|
+
# page doesn't draw stacks yet).
|
|
140
|
+
revises = ReviseAnchor.from(field(event, :revises))
|
|
141
|
+
out["revises"] = revises.to_file if revises
|
|
137
142
|
out
|
|
138
143
|
end
|
|
139
144
|
|
|
@@ -249,6 +254,9 @@ module Samagotchi
|
|
|
249
254
|
out["content"] = field(message, :content).to_s
|
|
250
255
|
out["turn_id"] = turn_id.to_s if turn_id
|
|
251
256
|
out["merged"] = true if merged
|
|
257
|
+
# The turn it revised (a Revise stack; the page doesn't draw it yet).
|
|
258
|
+
revises = ReviseAnchor.from(field(message, :revises))
|
|
259
|
+
out["revises"] = revises.to_file if revises && turn_id && !merged
|
|
252
260
|
out.merge!(author_fields(field(message, :author), field(message, :author_name), viewer,
|
|
253
261
|
field(message, :participant_mode)))
|
|
254
262
|
images = field(message, :images)
|
|
@@ -154,7 +154,7 @@ export function addCompleted(model, event = {}, now = null) {
|
|
|
154
154
|
...(guardrail.judgment_id ? { judgment_id: guardrail.judgment_id } : {}),
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
-
// The images the tool returned (a reload
|
|
157
|
+
// The images the tool returned (a reload's turn block draws them from
|
|
158
158
|
// the saved parts, turn_html.js toolRowTemplate).
|
|
159
159
|
if (event.images?.length) row.images = event.images;
|
|
160
160
|
// What an edit/write changed in its file (a reload draws it from the
|
|
@@ -27,7 +27,7 @@ export function annotationSource(node) {
|
|
|
27
27
|
}
|
|
28
28
|
const thinking = el.closest(".thinking-body");
|
|
29
29
|
if (thinking) return { kind: "thinking", root: thinking };
|
|
30
|
-
// A step's narration in
|
|
30
|
+
// A step's narration in a turn block (the live one streams).
|
|
31
31
|
const step = el.closest(".gen-text");
|
|
32
32
|
if (step) return step.classList.contains("streaming") ? null : { kind: "step", root: step };
|
|
33
33
|
const own = el.closest(".bubble.user .user-message");
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// gets a fold row: a sibling just before the bubble, never inside it (the
|
|
4
4
|
// bubble's copy text, its claims and a live end's innerHTML stay the
|
|
5
5
|
// bubble's own). Folded, the bubble has the class `.folded` (CSS hides it;
|
|
6
|
-
// no inline style
|
|
6
|
+
// no inline style) and the row reads its first line
|
|
7
7
|
// and its size, "‹first line› · N lines ▸"; unfolded, the row stays as a
|
|
8
8
|
// slim "▾ N lines" header.
|
|
9
9
|
//
|
|
@@ -37,8 +37,8 @@ const LABEL_CHARS = 160;
|
|
|
37
37
|
|
|
38
38
|
const ROW_CLASS = "answer-fold";
|
|
39
39
|
const ANSWERS = ".bubble.output";
|
|
40
|
-
// Not done yet: still streaming
|
|
41
|
-
const UNFINISHED = ".streaming
|
|
40
|
+
// Not done yet: still streaming.
|
|
41
|
+
const UNFINISHED = ".streaming";
|
|
42
42
|
|
|
43
43
|
// +text+'s lines (a trailing newline ends the last one, it adds none).
|
|
44
44
|
export function lineCount(text) {
|