samagotchi 0.134.0 → 0.136.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 +78 -1
- data/bin/chi +2 -3
- data/docs/architecture.md +6 -3
- data/docs/cli.md +43 -38
- data/docs/configuration.md +11 -9
- data/docs/guardrails.md +53 -9
- data/docs/internals/engine.md +6 -3
- data/docs/internals/participants.md +1 -1
- data/docs/internals/prompt-caching.md +1 -1
- data/docs/internals/web-frontend.md +263 -0
- data/docs/internals/working-set.md +2 -2
- data/docs/sessions.md +5 -3
- data/docs/testing.md +36 -6
- 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 +6 -18
- data/lib/samagotchi/web/message_display.rb +33 -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 +100 -31
- data/lib/samagotchi/web/public/app.js +451 -367
- data/lib/samagotchi/web/public/bubbles.js +30 -10
- data/lib/samagotchi/web/public/cards_place.js +4 -2
- data/lib/samagotchi/web/public/chip_popover.js +2 -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 +10 -6
- data/lib/samagotchi/web/public/index.html +105 -53
- data/lib/samagotchi/web/public/info_bar.js +11 -12
- data/lib/samagotchi/web/public/info_bar_model.js +8 -10
- data/lib/samagotchi/web/public/latest.js +46 -0
- data/lib/samagotchi/web/public/level_chip.js +33 -95
- 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/llm_context_chip.js +13 -144
- 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/reasoning_history_chip.js +8 -34
- data/lib/samagotchi/web/public/restore.js +2 -0
- data/lib/samagotchi/web/public/revise_stack.js +378 -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 +9 -5
- data/lib/samagotchi/web/public/session_store.js +191 -0
- data/lib/samagotchi/web/public/settings_chip.js +212 -0
- data/lib/samagotchi/web/public/settings_panel.js +121 -0
- data/lib/samagotchi/web/public/shell_command.js +26 -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/start_settings.js +25 -88
- 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/thinking_chip.js +7 -45
- data/lib/samagotchi/web/public/time_gaps.js +5 -2
- data/lib/samagotchi/web/public/timing.js +15 -5
- data/lib/samagotchi/web/public/toolkit_chip.js +37 -21
- data/lib/samagotchi/web/public/{turn_view.js → turn_block.js} +33 -93
- data/lib/samagotchi/web/public/turn_events.js +3 -3
- data/lib/samagotchi/web/public/turn_html.js +20 -16
- 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/web/session_create.rb +1 -1
- data/lib/samagotchi/web/session_show.rb +6 -3
- data/lib/samagotchi/worker.rb +9 -9
- data/lib/samagotchi/worker_inbound.rb +6 -5
- metadata +15 -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)
|
|
@@ -1292,7 +1289,7 @@ module Samagotchi
|
|
|
1292
1289
|
nil
|
|
1293
1290
|
end
|
|
1294
1291
|
|
|
1295
|
-
# The LLM context +model+ starts under (the start page's
|
|
1292
|
+
# The LLM context +model+ starts under (the start page's ⚙ panel);
|
|
1296
1293
|
# one that can't be read leaves this model without, not the others.
|
|
1297
1294
|
def add_llm_context(model, target, names, settings)
|
|
1298
1295
|
model[:llm_context] = LLMContextStrategy.explain(target, names: names, models: settings).summary(nil)
|
|
@@ -1301,7 +1298,7 @@ module Samagotchi
|
|
|
1301
1298
|
end
|
|
1302
1299
|
|
|
1303
1300
|
# The thinking level +model+ starts at, with where it came from (the
|
|
1304
|
-
# start page's
|
|
1301
|
+
# start page's ⚙ panel; SettingExplained#summary, no session).
|
|
1305
1302
|
def add_thinking(model, target, names, settings)
|
|
1306
1303
|
model[:thinking] = Thinking.explain(target, names: names, models: settings).summary
|
|
1307
1304
|
rescue StandardError
|
|
@@ -1476,7 +1473,7 @@ module Samagotchi
|
|
|
1476
1473
|
# set when it seeds a session's and when the expanded view opens (a
|
|
1477
1474
|
# file changed while the session sat idle). A live worker's snapshot
|
|
1478
1475
|
# messages are the truth, as #handle_show's, else the file's; the
|
|
1479
|
-
# strategy is the session's
|
|
1476
|
+
# strategy is the session's llm_context summary's, the folder its working
|
|
1480
1477
|
# directory. running: a turn runs. The snapshot's messages lag it, so
|
|
1481
1478
|
# the answer is then the turn's last :working_set_updated (the
|
|
1482
1479
|
# snapshot's working_set, TurnAccumulator#working_set); before the
|
|
@@ -1509,7 +1506,7 @@ module Samagotchi
|
|
|
1509
1506
|
{ items: set["items"], at: set["at"] }
|
|
1510
1507
|
end
|
|
1511
1508
|
|
|
1512
|
-
# The strategy an
|
|
1509
|
+
# The strategy an llm_context summary names ("none", "stale,forget"), as
|
|
1513
1510
|
# LLMContextView takes it; none when there is no chip.
|
|
1514
1511
|
def working_set_strategy(llm_context)
|
|
1515
1512
|
named = llm_context.is_a?(Hash) ? (llm_context[:strategy] || llm_context["strategy"]).to_s : ""
|
|
@@ -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"
|
|
@@ -28,9 +29,19 @@ module Samagotchi
|
|
|
28
29
|
# A POST /api/render body may be this big: a decision message is a few
|
|
29
30
|
# KB, and the cap bounds the work a page can ask for.
|
|
30
31
|
MAX_RENDER_BYTES = 64 * 1024
|
|
32
|
+
# A delegate report's leading "session: <id>" / "status: <s>" lines
|
|
33
|
+
# (Tools::DelegateWait.result).
|
|
34
|
+
REPORT_HEADER = /\A((?:(?:session|status): [^\n]*\n)+)/
|
|
31
35
|
|
|
32
36
|
private
|
|
33
37
|
|
|
38
|
+
# A delegate report's text as the markdown it renders from: its header
|
|
39
|
+
# lines one to a line and a paragraph of their own, so a "---" after
|
|
40
|
+
# them is a rule, not a heading's underline.
|
|
41
|
+
def report_markdown(text)
|
|
42
|
+
text.sub(REPORT_HEADER) { "#{Regexp.last_match(1).lines.map(&:chomp).join(" \n")}\n\n" }
|
|
43
|
+
end
|
|
44
|
+
|
|
34
45
|
# The recap saved with the session and how many user turns came since:
|
|
35
46
|
# a live worker's (it counts against its Engine's messages), else
|
|
36
47
|
# recap.json against the session file. A stopped session gets its
|
|
@@ -70,7 +81,7 @@ module Samagotchi
|
|
|
70
81
|
|
|
71
82
|
# @param msgs [Array<Hash>] a session's messages; symbol keys from disk,
|
|
72
83
|
# string keys from a Bridge snapshot
|
|
73
|
-
# @param parts [Boolean] the turn
|
|
84
|
+
# @param parts [Boolean] the turn block's reload (?parts=1): each
|
|
74
85
|
# assistant message also carries what it did (MessageParts: thinking,
|
|
75
86
|
# tool calls with params and output), and a message with nothing to
|
|
76
87
|
# show but that (a text-less step) is kept with empty content
|
|
@@ -91,6 +102,7 @@ module Samagotchi
|
|
|
91
102
|
if Samagotchi::Steer.turn_prompt?(m)
|
|
92
103
|
note[:turn_start] = true
|
|
93
104
|
note[:turn_id] = (m[:turn_id] || m["turn_id"]).to_s
|
|
105
|
+
note.merge!(revises_of(m))
|
|
94
106
|
end
|
|
95
107
|
filtered << note
|
|
96
108
|
next
|
|
@@ -130,11 +142,24 @@ module Samagotchi
|
|
|
130
142
|
message.merge!(merged: true, step: steer_step(list, index)) unless Samagotchi::Steer.turn_prompt?(m)
|
|
131
143
|
source = (m[:source] || m["source"]).to_s
|
|
132
144
|
message[:source] = source unless source.empty?
|
|
145
|
+
# A delegate report is the child's markdown: rendered as an
|
|
146
|
+
# answer is (its copy source stays the text). Another user line
|
|
147
|
+
# stays plain.
|
|
148
|
+
if source == Samagotchi::Steer::DELEGATE_REPORT && @markdown_renderer.available?
|
|
149
|
+
message[:html] = @markdown_renderer.render(report_markdown(stripped))
|
|
150
|
+
end
|
|
133
151
|
end
|
|
134
152
|
# The turn it started (Engine): the page pairs it with that turn's
|
|
135
153
|
# record by this, not by its place.
|
|
136
154
|
turn_id = m[:turn_id] || m["turn_id"]
|
|
137
155
|
message[:turn_id] = turn_id.to_s if norm_role == "user" && turn_id
|
|
156
|
+
# The turn it revised (a Revise stack, Engine#stamp_opening), and
|
|
157
|
+
# whether it was a Verify turn (the page offers no Revise on its
|
|
158
|
+
# answer).
|
|
159
|
+
if norm_role == "user" && turn_id && !message[:merged]
|
|
160
|
+
message.merge!(revises_of(m))
|
|
161
|
+
message[:verify] = true if Samagotchi::VerifyAnchor.from(m[:verify] || m["verify"])
|
|
162
|
+
end
|
|
138
163
|
# A participant's message names them (the page's author badge).
|
|
139
164
|
if norm_role == "user"
|
|
140
165
|
message.merge!(Samagotchi::Steer.author_fields(m[:author] || m["author"], m[:author_name] || m["author_name"]))
|
|
@@ -166,6 +191,13 @@ module Samagotchi
|
|
|
166
191
|
[]
|
|
167
192
|
end
|
|
168
193
|
|
|
194
|
+
# A turn opening's Revise stamp as {revises: {turn_id:}}, checked again
|
|
195
|
+
# (a stored file is never trusted), or nothing.
|
|
196
|
+
def revises_of(message)
|
|
197
|
+
anchor = Samagotchi::ReviseAnchor.from(message[:revises] || message["revises"])
|
|
198
|
+
anchor ? { revises: anchor.to_h } : {}
|
|
199
|
+
end
|
|
200
|
+
|
|
169
201
|
# The entry's wall time (MessageTime: the page's hover and gap
|
|
170
202
|
# dividers) as {at:}, or nothing for an entry saved without one.
|
|
171
203
|
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");
|