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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// The turn
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// The turn block of a running turn (the stage's cloud, stage_view.js): one
|
|
2
|
+
// <details class="turn-work"> per turn holding one <details class="gen">
|
|
3
|
+
// per generation, the current one live (open, on top while the turn runs),
|
|
4
4
|
// the earlier ones collapsed to one line each. At the turn's end the last
|
|
5
5
|
// generation's narration moves out of the block as the answer bubble, so
|
|
6
6
|
// markdown, annotate and the turn output dedupe work on a plain bubble.
|
|
@@ -38,7 +38,6 @@ import { formatDuration, turnGroups, turnTimingText } from "./timing.js";
|
|
|
38
38
|
import { createSentenceFeed } from "./sentences.js";
|
|
39
39
|
import { createTicker } from "./thinking_ticker.js";
|
|
40
40
|
import * as model from "./turn_model.js";
|
|
41
|
-
import { createHold } from "./hold.js";
|
|
42
41
|
import { linkedLine } from "./notice_links.js";
|
|
43
42
|
import { taskCommand, taskStopButton } from "./task_stop.js";
|
|
44
43
|
import { footprintTemplate, groupItems, stampHistory } from "./working_set_view.js";
|
|
@@ -188,32 +187,23 @@ function rowNodes(rows, thumbs) {
|
|
|
188
187
|
return nodes;
|
|
189
188
|
}
|
|
190
189
|
|
|
191
|
-
// ---- The live
|
|
190
|
+
// ---- The live block ----
|
|
192
191
|
|
|
193
|
-
// The container seams (the stage
|
|
194
|
-
// blockHost(el) puts the turn's block in the page (
|
|
195
|
-
// follow() keeps the live part in view
|
|
196
|
-
//
|
|
197
|
-
// the change;
|
|
192
|
+
// The container seams (the stage composes the block, stage_view.js):
|
|
193
|
+
// blockHost(el) puts the turn's block in the page (the cloud);
|
|
194
|
+
// follow() keeps the live part in view, called only when
|
|
195
|
+
// isNearBottom() said so before the change;
|
|
198
196
|
// placeAnswer(pieces, { plain, blockEl }) puts the promoted answer (a
|
|
199
|
-
// plain answer's thinking, cards, bubble) in the page
|
|
200
|
-
// (default: in the block's place for a plain answer,
|
|
201
|
-
// else after it in the history);
|
|
202
|
-
// useHold the answer's pop at the narration box's height (hold.js);
|
|
203
|
-
// newestFirst a new step goes on top of the block while the turn runs
|
|
204
|
-
// (chronological() restores the order);
|
|
197
|
+
// plain answer's thinking, cards, bubble) in the page;
|
|
205
198
|
// onChange() after anything in the turn changed (the stage redraws
|
|
206
199
|
// its slots from current());
|
|
207
200
|
// onThinkingLine(sentence) the live step's ticker showed a new line;
|
|
208
201
|
// taskStop the page's stopper (task_stop.js): a running task_wait
|
|
209
202
|
// row gets its stop-task button (none: no button).
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
placeAnswer = defaultPlaceAnswer(appendToHistory),
|
|
215
|
-
useHold = true,
|
|
216
|
-
newestFirst = false,
|
|
203
|
+
// A new step goes on top of the block while the turn runs (chronological()
|
|
204
|
+
// restores the order at its end).
|
|
205
|
+
export function createTurnBlock({
|
|
206
|
+
historyEl, isNearBottom, sawText, sessionId, blockHost, follow, placeAnswer,
|
|
217
207
|
onChange = () => {},
|
|
218
208
|
onThinkingLine = () => {},
|
|
219
209
|
taskStop = null,
|
|
@@ -229,9 +219,6 @@ export function createTurnView({
|
|
|
229
219
|
let pendingSteers = [];
|
|
230
220
|
const dirty = new Set();
|
|
231
221
|
let rafPending = false;
|
|
232
|
-
// The promoted answer bubble while it is held at the box's height (the
|
|
233
|
-
// pop, D6).
|
|
234
|
-
const hold = createHold({ isNearBottom, follow });
|
|
235
222
|
|
|
236
223
|
function removeHint() {
|
|
237
224
|
const hint = historyEl.querySelector(".hint");
|
|
@@ -256,8 +243,8 @@ export function createTurnView({
|
|
|
256
243
|
}
|
|
257
244
|
|
|
258
245
|
// The step rendered again from its state (stepTemplate). Each live step
|
|
259
|
-
// is its own lit root, not an item of one block template:
|
|
260
|
-
//
|
|
246
|
+
// is its own lit root, not an item of one block template: a new step
|
|
247
|
+
// goes on top and chronological() reorders them, and lit
|
|
261
248
|
// (without the keyed repeat directive, not vendored) matches list items
|
|
262
249
|
// by index, so a block-wide render would hand step N's <details> (and
|
|
263
250
|
// its hand toggle) to another gen. The root's part lives on entry.end
|
|
@@ -319,8 +306,7 @@ export function createTurnView({
|
|
|
319
306
|
draw(entry);
|
|
320
307
|
entry.el = entry.end.previousElementSibling || entry.frag.querySelector("details.gen");
|
|
321
308
|
const stick = isNearBottom();
|
|
322
|
-
|
|
323
|
-
else blockEl.appendChild(entry.frag);
|
|
309
|
+
blockEl.insertBefore(entry.frag, blockSummaryEl.nextSibling);
|
|
324
310
|
if (stick) follow();
|
|
325
311
|
return entry;
|
|
326
312
|
}
|
|
@@ -447,25 +433,8 @@ export function createTurnView({
|
|
|
447
433
|
if (k) sawText(k);
|
|
448
434
|
}
|
|
449
435
|
|
|
450
|
-
// A step that closes mid-turn (the next one starts) collapses: a warn card
|
|
451
|
-
// in it would go out of sight until the turn ends. It moves out of the
|
|
452
|
-
// step, right after it in the block (open while the turn runs), where
|
|
453
|
-
// turnEnded still finds it and takes it out of the block (leavesBlock).
|
|
454
|
-
// The card stays in entry.items while it sits outside the step: safe only
|
|
455
|
-
// because the step's items are never removed or reordered after this (a
|
|
456
|
-
// late row is appended, a no-op for the parts before it). A render whose
|
|
457
|
-
// items shifted would make lit insert the card back into the step (or
|
|
458
|
-
// another item into its slot).
|
|
459
|
-
function liftWarnCards(gen) {
|
|
460
|
-
const entry = genEls.get(gen);
|
|
461
|
-
if (!entry) return;
|
|
462
|
-
const cards = [...entry.el.querySelectorAll(".plugin-card.warn")];
|
|
463
|
-
if (cards.length) entry.end.after(...cards);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
436
|
function apply(r) {
|
|
467
437
|
if (!r) return;
|
|
468
|
-
if (r.closed && r.opened) liftWarnCards(r.closed);
|
|
469
438
|
if (r.closed) closeGenEl(r.closed);
|
|
470
439
|
if (r.opened) createGenEl(r.gen);
|
|
471
440
|
if (r.closed || r.opened) updateBlockSummary();
|
|
@@ -579,14 +548,11 @@ export function createTurnView({
|
|
|
579
548
|
return true;
|
|
580
549
|
}
|
|
581
550
|
|
|
582
|
-
// The answer: the gen's narration leaves the block as a bubble.
|
|
583
|
-
// +heldHeight+ (the live box's height, measured before the step closed) the
|
|
584
|
-
// bubble is held at that height, scrolled to its tail, until answerReady()
|
|
585
|
-
// (the rendered markdown landed) or 1.5 s reveals it.
|
|
551
|
+
// The answer: the gen's narration leaves the block as a bubble.
|
|
586
552
|
// +endNote+: the notice of a turn that ended with no answer (app.js
|
|
587
553
|
// builds it), placed where the answer bubble would go. +turnId+: a
|
|
588
554
|
// completed turn's id, the bubble's data-answer-turn as a reload's.
|
|
589
|
-
function promote(gen, {
|
|
555
|
+
function promote(gen, { endNote = null, turnId = null } = {}) {
|
|
590
556
|
const entry = genEls.get(gen);
|
|
591
557
|
// No text (an empty answer): no bubble.
|
|
592
558
|
let bubble = null;
|
|
@@ -596,8 +562,6 @@ export function createTurnView({
|
|
|
596
562
|
bubble.textContent = gen.text;
|
|
597
563
|
if (turnId) bubble.dataset.answerTurn = turnId;
|
|
598
564
|
}
|
|
599
|
-
// A hidden tab gets no pop: nobody sees it, and its timers are throttled.
|
|
600
|
-
const holding = useHold && heldHeight > 0 && !!bubble && !document.hidden;
|
|
601
565
|
model.takeAnswer(turn, gen);
|
|
602
566
|
draw(entry);
|
|
603
567
|
// Cards and hook notices in a step that goes (below, or with the whole
|
|
@@ -627,14 +591,6 @@ export function createTurnView({
|
|
|
627
591
|
} else {
|
|
628
592
|
placeAnswer(bubble ? [...cards, bubble] : [...cards, ...(endNote ? [endNote] : [])], { plain: false, blockEl });
|
|
629
593
|
}
|
|
630
|
-
if (holding) {
|
|
631
|
-
// Bubbles are border-box: the box's height plus the bubble's own
|
|
632
|
-
// padding and border keeps the same three lines in view.
|
|
633
|
-
const cs = getComputedStyle(bubble);
|
|
634
|
-
const chrome = ["paddingTop", "paddingBottom", "borderTopWidth", "borderBottomWidth"]
|
|
635
|
-
.reduce((sum, k) => sum + (parseFloat(cs[k]) || 0), 0);
|
|
636
|
-
hold.start(bubble, heldHeight + chrome);
|
|
637
|
-
}
|
|
638
594
|
}
|
|
639
595
|
|
|
640
596
|
// Returns the cards that left the block (card.js leavesBlock), detached:
|
|
@@ -652,16 +608,9 @@ export function createTurnView({
|
|
|
652
608
|
// turn without tools leaves the block in any case (canceled, it is the
|
|
653
609
|
// partial text bubble).
|
|
654
610
|
const promoting = last && (kind === "completed" || (turn.gens.length === 1 && last.tools.length === 0));
|
|
655
|
-
// The box's height, measured while it is still in the DOM and the step
|
|
656
|
-
// still open: the apply below swaps it for the full text and collapses
|
|
657
|
-
// the step. Only a completed turn's answer pops; a canceled or failed
|
|
658
|
-
// one shows at once.
|
|
659
|
-
const lastEntry = promoting && kind === "completed" ? genEls.get(last) : null;
|
|
660
|
-
const box = lastEntry ? narrationEl(lastEntry) : null;
|
|
661
|
-
const heldHeight = box ? box.getBoundingClientRect().height : 0;
|
|
662
611
|
if (pendingSteers.length) flushSteers();
|
|
663
612
|
apply(model.turnEnded(turn));
|
|
664
|
-
if (promoting) promote(last, {
|
|
613
|
+
if (promoting) promote(last, { endNote, turnId: kind === "completed" ? turnId : null });
|
|
665
614
|
else if (endNote) placeAnswer([endNote], { plain: false, blockEl });
|
|
666
615
|
const kept = [];
|
|
667
616
|
if (blockEl) {
|
|
@@ -669,7 +618,7 @@ export function createTurnView({
|
|
|
669
618
|
blockEl.classList.add("done");
|
|
670
619
|
// The outline (turn_model.js outlineRuns): open on its narrated steps'
|
|
671
620
|
// rows, as a reload draws it; a block with nothing said closes.
|
|
672
|
-
|
|
621
|
+
chronological();
|
|
673
622
|
const outlined = outlineBlock(blockEl, model.blockGens(turn));
|
|
674
623
|
if (!blockToggled) blockEl.open = outlined;
|
|
675
624
|
for (const el of blockEl.querySelectorAll(".plugin-card")) {
|
|
@@ -683,8 +632,8 @@ export function createTurnView({
|
|
|
683
632
|
return kept;
|
|
684
633
|
}
|
|
685
634
|
|
|
686
|
-
// The steps back in chronological order
|
|
687
|
-
//
|
|
635
|
+
// The steps back in chronological order (they ran newest first); a fold
|
|
636
|
+
// (outlineBlock) stays before its run's first step.
|
|
688
637
|
function chronological() {
|
|
689
638
|
if (!blockEl || !turn) return;
|
|
690
639
|
for (const gen of turn.gens) {
|
|
@@ -698,7 +647,6 @@ export function createTurnView({
|
|
|
698
647
|
}
|
|
699
648
|
|
|
700
649
|
function reset() {
|
|
701
|
-
hold.finish();
|
|
702
650
|
turn = null;
|
|
703
651
|
blockEl = null;
|
|
704
652
|
blockSummaryEl = null;
|
|
@@ -777,9 +725,6 @@ export function createTurnView({
|
|
|
777
725
|
steer,
|
|
778
726
|
card,
|
|
779
727
|
flush,
|
|
780
|
-
// The rendered answer landed (or the re-read failed): let the held
|
|
781
|
-
// bubble pop. A no-op with nothing held.
|
|
782
|
-
answerReady() { hold.reveal(); },
|
|
783
728
|
// The live gen's narration box shows complete sentences, its thinking is
|
|
784
729
|
// appended by delta (the ticker); a selection in either is refused while
|
|
785
730
|
// the step is live — the quote lands once it closes (then on the full
|
|
@@ -792,9 +737,8 @@ export function createTurnView({
|
|
|
792
737
|
return !!text?.contains(el) || !!entry.thinkingText?.parentNode?.contains(el);
|
|
793
738
|
},
|
|
794
739
|
reset,
|
|
795
|
-
// For
|
|
796
|
-
//
|
|
797
|
-
// chronological order after newestFirst.
|
|
740
|
+
// For the stage that composes the block: the turn's model and block, a
|
|
741
|
+
// step's elements by index, and the steps back in chronological order.
|
|
798
742
|
current() { return { turn, blockEl }; },
|
|
799
743
|
// The running turn's asking reply (turn_model.js askingText): what a
|
|
800
744
|
// question or approval card keeps once answered.
|
|
@@ -811,15 +755,6 @@ export function createTurnView({
|
|
|
811
755
|
};
|
|
812
756
|
}
|
|
813
757
|
|
|
814
|
-
// The turn view's answer placement: a plain answer's pieces in its block's
|
|
815
|
-
// place (the block goes), else after the block in the history.
|
|
816
|
-
function defaultPlaceAnswer(appendToHistory) {
|
|
817
|
-
return (pieces, { plain, blockEl }) => {
|
|
818
|
-
if (plain) blockEl.replaceWith(...pieces);
|
|
819
|
-
else pieces.forEach((el) => appendToHistory(el));
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
|
|
823
758
|
// ---- A reload ----
|
|
824
759
|
|
|
825
760
|
// The text of a reloaded block's step +iteration+ (1-based, as the snapshot's
|
|
@@ -842,15 +777,15 @@ function failedLineTemplate(record) {
|
|
|
842
777
|
return html`<div class="bubble cancel">${failedTurnText(failure)}</div>`;
|
|
843
778
|
}
|
|
844
779
|
|
|
845
|
-
// A reloaded history
|
|
780
|
+
// A reloaded history as turn blocks (renderHistory's object path), as
|
|
846
781
|
// nodes: per turn the prompt, a collapsed block of its steps when it had
|
|
847
782
|
// any (stepTemplate, the live page's: texts, and with the server's parts
|
|
848
783
|
// the thinking, params and output, from the saved messages; status and
|
|
849
784
|
// duration from the timing's tool records), then the answer and the turn's
|
|
850
785
|
// timing line under it.
|
|
851
786
|
// +thumbs(images)+ draws a prompt's or a tool row's image thumbs (a
|
|
852
|
-
// template, images.js thumbsTemplate). +workingSet+ (the stage
|
|
853
|
-
// web.working_set
|
|
787
|
+
// template, images.js thumbsTemplate). +workingSet+ (the stage's
|
|
788
|
+
// web.working_set): each block stamped with its
|
|
854
789
|
// turn's ordinal and its rows with theirs (working_set_view.js), and the
|
|
855
790
|
// turn's footprint line after it.
|
|
856
791
|
export function turnHistoryNodes(items, timing, { thumbs, workingSet = false, now = Date.now() }) {
|
|
@@ -905,6 +840,8 @@ export function turnHistoryNodes(items, timing, { thumbs, workingSet = false, no
|
|
|
905
840
|
if (group.kind === "note") { parts.push(noteBubbleTemplate(items[group.i])); continue; }
|
|
906
841
|
if (group.kind === "steer") { parts.push(steerTemplate(group.i, true)); continue; }
|
|
907
842
|
if (group.kind === "answer") { parts.push(answerTemplate(group.answer, nothing)); continue; }
|
|
843
|
+
// A `!cmd`'s record: its command bubble, no steps, no timing.
|
|
844
|
+
if (group.kind === "command") { parts.push(userTemplate(items[group.i])); continue; }
|
|
908
845
|
turnOrdinal += 1;
|
|
909
846
|
const prompt = items[group.user];
|
|
910
847
|
// A context wake turn starts with its note.
|
|
@@ -946,8 +883,11 @@ export function turnHistoryNodes(items, timing, { thumbs, workingSet = false, no
|
|
|
946
883
|
// followed by the text that answered it when that isn't the answer: a
|
|
947
884
|
// small model tells its user about a report before its next tool call,
|
|
948
885
|
// and that step's text would stay folded in the block.
|
|
886
|
+
// A report's fold key (answer_fold.js), as its reply's: the turn's id
|
|
887
|
+
// and its place after the opening; none in a turn saved before turn ids.
|
|
888
|
+
const lineKey = (i) => (group.turnId && items[i]?.source === "delegate_report" ? `${group.turnId}:input:${i - group.user}` : null);
|
|
949
889
|
parts.push((group.merged || []).map((i) =>
|
|
950
|
-
html`${userTemplate(items[i], { state: "steered", step: items[i].step })}${reportReplyTemplate(i, group)}`));
|
|
890
|
+
html`${userTemplate(items[i], { state: "steered", step: items[i].step, lineKey: lineKey(i) })}${reportReplyTemplate(i, group)}`));
|
|
951
891
|
// A turn that ended with no answer: chi's muted notice where the answer
|
|
952
892
|
// would be, before the timing, as live.
|
|
953
893
|
if (group.emptyAnswer != null) parts.push(html`${emptyAnswerTemplate(items[group.emptyAnswer])}${timingTemplate(group)}`);
|
|
@@ -21,7 +21,7 @@ export function workerGoneText({ turnRunning, stopped = false }) {
|
|
|
21
21
|
return stopped ? "\u25A0 canceled (session stopped)" : "\u2715 canceled (worker exited)";
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// Whether the turn
|
|
24
|
+
// Whether the running turn's DOM ends now, or waits for the session re-read: a stream
|
|
25
25
|
// that was open and dropped with no stop (a phone briefly offline) says
|
|
26
26
|
// nothing about the worker, and `resync` re-renders the turn and adds the end
|
|
27
27
|
// line only when no worker took it — ending the view first is a wrong flash.
|
|
@@ -328,7 +328,7 @@ export function promptOps(event, { myId, known, unmatchedMerge = false, wakeStar
|
|
|
328
328
|
case "pending_input_merged":
|
|
329
329
|
if (!unmatchedMerge || !event.content) return [];
|
|
330
330
|
if (unmatchedMerge === "report") {
|
|
331
|
-
return [{ op: "add", enqueuedId: null, prompt: event.content, state: wakeStart ? null : "steered", label: clientLabel(REPORT_CLIENT) }];
|
|
331
|
+
return [{ op: "add", enqueuedId: null, prompt: event.content, state: wakeStart ? null : "steered", label: clientLabel(REPORT_CLIENT), source: "delegate_report" }];
|
|
332
332
|
}
|
|
333
333
|
return [{ op: "add", enqueuedId: null, prompt: event.content, state: "steered", label: null }];
|
|
334
334
|
default:
|
|
@@ -380,7 +380,7 @@ export function snapshotEvents({ current_turn: turn = null, queued = [], queued_
|
|
|
380
380
|
closeText();
|
|
381
381
|
events.push({ type: "generation_started", iteration: part.iteration ?? null });
|
|
382
382
|
break;
|
|
383
|
-
// The chunks carry the part's iteration, so the turn
|
|
383
|
+
// The chunks carry the part's iteration, so the turn block groups a
|
|
384
384
|
// join like the live stream (turn_model.js).
|
|
385
385
|
case "thinking":
|
|
386
386
|
events.push({ type: "generation_chunk", text: "", thinking: part.text, iteration: part.iteration ?? null });
|
|
@@ -141,7 +141,7 @@ function tailClip(text) {
|
|
|
141
141
|
// edit's collapsed diff, and a reloaded row's ✂ mark above its output. A
|
|
142
142
|
// part with nothing to say is left out. A reload renders toolRowTemplate;
|
|
143
143
|
// the live view renders toolRowInnerTemplate into its own row element
|
|
144
|
-
// (
|
|
144
|
+
// (turn_block.js), again on each event: lit writes only what changed, so a
|
|
145
145
|
// part the user toggled (the raw command, the diff, the read lines) stays.
|
|
146
146
|
// +stop+: the live row's stop-task button (a node), after the title.
|
|
147
147
|
export function toolRowTemplate(row, opts = {}) {
|
|
@@ -214,18 +214,8 @@ export function stateBadgeTemplate(state, delivery = null) {
|
|
|
214
214
|
return html`<span class="state-badge">${text}${remove}</span>`;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
// A `!cmd`'s saved user message
|
|
218
|
-
|
|
219
|
-
// content is any other message. The command sits on the first line inside
|
|
220
|
-
// `!(` `)`; the output is everything after that first newline. A message
|
|
221
|
-
// that merely starts with "!" or "!(" without the newline shape is a plain
|
|
222
|
-
// message.
|
|
223
|
-
export function shellCommandMessage(content) {
|
|
224
|
-
const text = String(content ?? "");
|
|
225
|
-
const match = text.match(/^!\((.*)\)\n([\s\S]*)$/);
|
|
226
|
-
if (!match) return null;
|
|
227
|
-
return { line: `!${match[1]}`, text: match[2] };
|
|
228
|
-
}
|
|
217
|
+
// A `!cmd`'s saved user message as its line and output (shell_command.js).
|
|
218
|
+
export { shellCommandMessage } from "./shell_command.js";
|
|
229
219
|
|
|
230
220
|
// A `!cmd`'s history bubble: the same markup the live page draws for a
|
|
231
221
|
// command (app.js's drawCommandBubble): the line, the output in a
|
|
@@ -313,13 +303,27 @@ export function steerRowTemplate({ source, text } = {}, { bubble = false } = {})
|
|
|
313
303
|
// +delivery+: how it was sent into a running turn (next_step, cut, queue):
|
|
314
304
|
// its data-delivery and its queued badge's words. +author+: a
|
|
315
305
|
// participant's badge (participant_names.js authorBadge: their name and
|
|
316
|
-
// colour) in place of +label+.
|
|
317
|
-
|
|
306
|
+
// colour) in place of +label+. A turn's opening carries its Revise stamp
|
|
307
|
+
// (+message.revises+: data-revises, revise_stack.js) and whether it was a
|
|
308
|
+
// Verify turn (+message.verify+: data-verify, no Revise on its answer).
|
|
309
|
+
// +message.source+ (a delegate report's "delegate_report") is its
|
|
310
|
+
// data-source; +lineKey+, a merged line's fold key (answer_fold.js:
|
|
311
|
+
// data-line-key, the reload's "<turn id>:input:<place>").
|
|
312
|
+
export function userBubbleTemplate(message, { label = null, state = null, step = null, thumbs = nothing, own = false, enqueuedId = null, key = null, delivery = null, author = null, lineKey = null } = {}) {
|
|
318
313
|
const named = BADGES[state] ? state : null;
|
|
319
314
|
const from = author
|
|
320
315
|
? html`<span class="origin-label author-badge" title=${authorBadgeTitle(author.name, author.mode)} data-unnamed=${author.unnamed ? "1" : nothing}>${authorBadgeText(author.name, author.mode)}</span>`
|
|
321
316
|
: label ? html`<span class="origin-label">${label}</span>` : nothing;
|
|
322
|
-
return html`<div class=${`bubble user${named ? ` ${named}` : ""}${author ? " by-participant" : ""}`} title=${timeTitle(message?.at)} style=${author ? `--author-color:${author.color}` : nothing} data-author=${author?.author || nothing} data-participant-mode=${author?.mode || nothing} data-user-state=${named || nothing} data-step=${step ? Number(step) : nothing} data-copy-source=${copySource({ ...message, role: "user" })} data-user-content=${key !== null ? key : nothing} data-turn-id=${message?.turn_id || nothing} data-own=${own ? "1" : nothing} data-enqueued-id=${enqueuedId || nothing} data-delivery=${delivery || nothing}
|
|
317
|
+
return html`<div class=${`bubble user${named ? ` ${named}` : ""}${author ? " by-participant" : ""}`} title=${timeTitle(message?.at)} style=${author ? `--author-color:${author.color}` : nothing} data-author=${author?.author || nothing} data-participant-mode=${author?.mode || nothing} data-user-state=${named || nothing} data-step=${step ? Number(step) : nothing} data-copy-source=${copySource({ ...message, role: "user" })} data-user-content=${key !== null ? key : nothing} data-turn-id=${message?.turn_id || nothing} data-revises=${(message?.turn_id && message?.revises?.turn_id) || nothing} data-verify=${message?.turn_id && message?.verify ? "1" : nothing} data-own=${own ? "1" : nothing} data-enqueued-id=${enqueuedId || nothing} data-delivery=${delivery || nothing} data-source=${message?.source || nothing} data-line-key=${lineKey || nothing}>${from}${userMessageTemplate(message, thumbs)}${stateBadgeTemplate(named, delivery)}</div>`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// A user line's text: plain (its quotes set apart), or the server's
|
|
321
|
+
// rendered markdown when the line has it (a delegate report's,
|
|
322
|
+
// message_display.rb; a live line has none until a reload). Its copy
|
|
323
|
+
// source stays the raw text either way (data-copy-source).
|
|
324
|
+
function userMessageTemplate(message, thumbs) {
|
|
325
|
+
if (typeof message?.html === "string") return html`<div class="user-message markdown" .innerHTML=${message.html}></div>`;
|
|
326
|
+
return html`<div class="user-message">${userBodyTemplate(message?.content)}${thumbs}</div>`;
|
|
323
327
|
}
|
|
324
328
|
|
|
325
329
|
// The notice of a turn that ended with no answer (the server's
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// The generations of one turn, as the turn
|
|
1
|
+
// The generations of one turn, as the turn block shows them: a list of gens
|
|
2
2
|
// (one per model generation: its thinking, its narration, its tool rows),
|
|
3
3
|
// the last one live until the next opens or the turn ends. Pure: fed by the
|
|
4
4
|
// same events live and on a join (turn_events.js snapshotEvents), no DOM, so
|
|
5
|
-
// `node --test` covers the grouping rules.
|
|
5
|
+
// `node --test` covers the grouping rules. turn_block.js draws it.
|
|
6
6
|
//
|
|
7
7
|
// Rules:
|
|
8
8
|
// - turn_started opens the first gen (no iteration yet).
|
|
@@ -177,7 +177,7 @@ export function askingText(turn) {
|
|
|
177
177
|
// A plugin's refs on the reply that asks (a question's refs, from its
|
|
178
178
|
// pending question): kept on the asking step's gen, the last one, while
|
|
179
179
|
// the turn goes on, for the stage's asking slot (askingRefs) and the
|
|
180
|
-
// step's closed text (
|
|
180
|
+
// step's closed text (turn_block.js). A non-array or an empty list keeps
|
|
181
181
|
// what the gen has. The gen, or null.
|
|
182
182
|
export function questionRefs(turn, refs) {
|
|
183
183
|
if (!turn || turn.ended || !Array.isArray(refs) || !refs.length) return null;
|
|
@@ -202,7 +202,7 @@ export function turnEnded(turn) {
|
|
|
202
202
|
return { gen: null, opened: false, closed };
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
// The turn's answer is +gen+'s text, moved out of the block (
|
|
205
|
+
// The turn's answer is +gen+'s text, moved out of the block (turn_block.js
|
|
206
206
|
// promote): a gen left with no thinking and no tool rows is no step then,
|
|
207
207
|
// as it is no row, and as a reload counts it (turnGroups).
|
|
208
208
|
export function takeAnswer(turn, gen) {
|
|
@@ -244,7 +244,7 @@ export function genLabel(gen) {
|
|
|
244
244
|
// genLabel in its parts: +head+, +calls+ ("3 tool calls (1 failed)", or ""
|
|
245
245
|
// without calls) and +repeats+: the head is words the open step's body
|
|
246
246
|
// starts with (its narration, its first call's title), so an open step's
|
|
247
|
-
// summary can say only its calls (
|
|
247
|
+
// summary can say only its calls (turn_block.js labelHtml).
|
|
248
248
|
export function genLabelParts(gen) {
|
|
249
249
|
const line = String(gen.text || "").split("\n").map(plainLine).find(Boolean) || "";
|
|
250
250
|
const calls = gen.tools.length ? toolCallsText(gen.tools) : "";
|
|
@@ -299,7 +299,7 @@ export function outlineRuns(gens) {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
// A finished turn's steps as its block holds them: the answer's step left it
|
|
302
|
-
// when the answer was all it had (
|
|
302
|
+
// when the answer was all it had (turn_block.js promote).
|
|
303
303
|
export function blockGens(turn) {
|
|
304
304
|
return turn.gens.filter((gen) => !(gen.answer && !gen.thinking && gen.tools.length === 0));
|
|
305
305
|
}
|
|
@@ -132,6 +132,8 @@ async function startFresh(card, button, fresh) {
|
|
|
132
132
|
}
|
|
133
133
|
if (result?.ok) {
|
|
134
134
|
if (fresh.errors) delete fresh.errors[of];
|
|
135
|
+
// In place, on purpose: on the owner page fresh.running is the session
|
|
136
|
+
// store's verifyFresh (session_fields.js, D8).
|
|
135
137
|
if (fresh.running && !fresh.running.some((entry) => runningOf(entry) === of)) {
|
|
136
138
|
fresh.running.push(result.child_id ? { of, child_id: result.child_id } : of);
|
|
137
139
|
}
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
// finds the row by them, never by a row key.
|
|
40
40
|
import { html, nothing, render, svg } from "./vendor/lit-html.js";
|
|
41
41
|
import { createChipPopover } from "./chip_popover.js";
|
|
42
|
+
import { stillOn } from "./latest.js";
|
|
42
43
|
import { showRow } from "./refs.js";
|
|
43
44
|
import {
|
|
44
45
|
addedItems, addLine, BUDGETS, dropRefusal, fold, footprint, footprintText, forgetOn, GLYPHS, INVERSE, isFresh, itemsOfTurn, labelText,
|
|
@@ -487,9 +488,10 @@ export function createWorkingSet({ historyEl, stageRoot = () => null, state, ses
|
|
|
487
488
|
const id = session();
|
|
488
489
|
if (!getWorkingSet || !id) return;
|
|
489
490
|
const asked = events;
|
|
491
|
+
const still = stillOn(session, id);
|
|
490
492
|
Promise.resolve()
|
|
491
493
|
.then(() => getWorkingSet(id))
|
|
492
|
-
.then((snapshot) => { if (
|
|
494
|
+
.then((snapshot) => { if (still()) takeServer(snapshot, { overtaken: events !== asked }); })
|
|
493
495
|
.catch(() => {});
|
|
494
496
|
}
|
|
495
497
|
|
|
@@ -92,7 +92,7 @@ module Samagotchi
|
|
|
92
92
|
# on while the server runs
|
|
93
93
|
# @return [Integer] the exit status
|
|
94
94
|
def self.launch(port: nil, host: nil, scope: "project", start_dir: nil, open_browser: false, markdown: false,
|
|
95
|
-
|
|
95
|
+
annotate_presets: Config::BY_KEY["web.annotate_presets"].default,
|
|
96
96
|
editor: Config::BY_KEY["web.editor"].default, new_token: false,
|
|
97
97
|
working_set: Config::BY_KEY["web.working_set"].default, tunnel: nil, start_dir_from: :flag,
|
|
98
98
|
restarted: false, restart_argv: ["web"], exec: nil)
|
|
@@ -152,7 +152,7 @@ module Samagotchi
|
|
|
152
152
|
# would refuse, or a key store others can read, stops it here,
|
|
153
153
|
# before any tunnel goes up.
|
|
154
154
|
Push.preflight!(public_url)
|
|
155
|
-
options = { port: port, host: host, url: url, open_browser: open_browser, markdown: markdown,
|
|
155
|
+
options = { port: port, host: host, url: url, open_browser: open_browser, markdown: markdown,
|
|
156
156
|
annotate_presets: annotate_presets, editor: editor, lan: lan, working_set: working_set,
|
|
157
157
|
public_url: public_url, start_dir: start_dir }
|
|
158
158
|
started = if plan
|
|
@@ -345,7 +345,7 @@ module Samagotchi
|
|
|
345
345
|
# @return [Boolean, :restart] false when the port was taken (said so on stderr);
|
|
346
346
|
# :restart when the owner requested an in-place process replacement
|
|
347
347
|
def self.start(port: nil, host: nil, url: nil, open_browser: false, state_dir: nil, manager: nil, markdown: false,
|
|
348
|
-
|
|
348
|
+
annotate_presets: Config::BY_KEY["web.annotate_presets"].default, hub: nil, lan: nil,
|
|
349
349
|
editor: Config::BY_KEY["web.editor"].default, token_path: Token.path,
|
|
350
350
|
working_set: Config::BY_KEY["web.working_set"].default, public_url: nil, public_notes: [],
|
|
351
351
|
on_bound: nil, start_dir: Dir.pwd, update_check: nil)
|
|
@@ -362,7 +362,7 @@ module Samagotchi
|
|
|
362
362
|
lan_option = { ip: lan.ip, token: Token::Source.new(token_path) } if lan
|
|
363
363
|
public_token = Token::Source.new(token_path) if public_url
|
|
364
364
|
push = push_runtime(public_url, state_dir)
|
|
365
|
-
app = App.new(manager: manager, state_dir: state_dir, markdown: markdown,
|
|
365
|
+
app = App.new(manager: manager, state_dir: state_dir, markdown: markdown,
|
|
366
366
|
annotate_presets: annotate_presets, editor: editor, hub: hub, lan: lan_option, working_set: working_set,
|
|
367
367
|
public_url: public_url, public_token: public_token, push: push, start_dir: start_dir)
|
|
368
368
|
webrick = nil
|
|
@@ -196,11 +196,11 @@ module Samagotchi
|
|
|
196
196
|
|
|
197
197
|
# no_interrupt: an attached TUI's --no-interrupt (chi --remote --attach).
|
|
198
198
|
# author: the principal's (nil for the owner), never the body's.
|
|
199
|
-
|
|
199
|
+
# anchors: a Verify turn's claim, a Revise turn's draft (TurnAnchors).
|
|
200
200
|
result = SessionManager.deliver_turn(id, prompt: prompt.to_s, client_id: client_id, images: images,
|
|
201
201
|
delivery: delivery, no_interrupt: body["no_interrupt"] == true,
|
|
202
202
|
author: principal(req).author,
|
|
203
|
-
|
|
203
|
+
anchors: TurnAnchors.from(body),
|
|
204
204
|
state_dir: @state_dir, manager: @manager,
|
|
205
205
|
bridge: -> { live_bridge_client(id) })
|
|
206
206
|
case result.status
|
|
@@ -69,7 +69,7 @@ module Samagotchi
|
|
|
69
69
|
return error if error
|
|
70
70
|
|
|
71
71
|
# llm_context: the session's own LLM context values before its first
|
|
72
|
-
# turn (the start page's
|
|
72
|
+
# turn (the start page's ⚙ panel; chi --llm-context), read
|
|
73
73
|
# before anything spawns so a bad one spawns nothing.
|
|
74
74
|
llm_context, error = request_llm_context(body["llm_context"])
|
|
75
75
|
return error if error
|
|
@@ -13,6 +13,7 @@ require_relative "../session_commands"
|
|
|
13
13
|
require_relative "../session_metrics"
|
|
14
14
|
require_relative "../session_setting"
|
|
15
15
|
require_relative "../system_one/log"
|
|
16
|
+
require_relative "../toolkit"
|
|
16
17
|
|
|
17
18
|
module Samagotchi
|
|
18
19
|
module Web
|
|
@@ -151,14 +152,16 @@ module Samagotchi
|
|
|
151
152
|
else
|
|
152
153
|
session_json = session_json.merge(saved_served_model(session, persisted))
|
|
153
154
|
end
|
|
154
|
-
# The per-session settings' chips (the info bar's llm ctx, think
|
|
155
|
-
# reasoning
|
|
155
|
+
# The per-session settings' chips (the info bar's ⚙ chip: llm ctx, think
|
|
156
|
+
# and reasoning; the tools chip): the worker's, else worked out here from the
|
|
156
157
|
# session file (SessionSetting#snapshot_value, #saved_summary).
|
|
157
158
|
SessionSetting::ALL.each do |setting|
|
|
158
159
|
chip = setting.snapshot_value(snapshot) { setting.saved_summary(session, registry: host_registry) }
|
|
159
160
|
session_json = session_json.merge(setting.key => chip)
|
|
160
161
|
end
|
|
161
|
-
|
|
162
|
+
# The 🧰 chip reads the session's toolkit against config's default:
|
|
163
|
+
# on it, the chip is 🧰 alone (a toolkit summary is nil on full only).
|
|
164
|
+
session_json.merge(toolkit_default: Toolkit.configured.name)
|
|
162
165
|
end
|
|
163
166
|
|
|
164
167
|
# ?cards=1: the page's re-read when a card arrives, for its rendered
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -678,7 +678,7 @@ module Samagotchi
|
|
|
678
678
|
@initial_turn = true
|
|
679
679
|
fresh = @session.verify_fresh
|
|
680
680
|
run_prompt(WorkerInbound::Prompt.new(text: prompt, origin: TurnOrigin.new(enqueued_id: @session.first_turn_id),
|
|
681
|
-
verify: fresh&.anchor))
|
|
681
|
+
anchors: { verify: fresh&.anchor }))
|
|
682
682
|
ensure
|
|
683
683
|
@initial_turn = false
|
|
684
684
|
end
|
|
@@ -705,10 +705,10 @@ module Samagotchi
|
|
|
705
705
|
@continue_offer.drop(origin)
|
|
706
706
|
@turn_flow.before_prompt_turn
|
|
707
707
|
run_engine_turn(prompt.text, origin: origin, max_iterations: max_iterations(prompt.no_interrupt),
|
|
708
|
-
images: prompt.images, **
|
|
708
|
+
images: prompt.images, **prompt.anchors.kwargs) do |result, error|
|
|
709
709
|
if error
|
|
710
710
|
# The Engine announced :turn_failed (with the error's one line).
|
|
711
|
-
restore_failed_turn([[prompt.text, origin, prompt.images, prompt.
|
|
711
|
+
restore_failed_turn([[prompt.text, origin, prompt.images, prompt.anchors], *@merged_this_turn], error: error)
|
|
712
712
|
else
|
|
713
713
|
@continue_offer.after_turn(result, no_interrupt: prompt.no_interrupt)
|
|
714
714
|
end
|
|
@@ -1395,10 +1395,10 @@ module Samagotchi
|
|
|
1395
1395
|
# or the restored ones, never the one without the other. A turn that
|
|
1396
1396
|
# got somewhere stays instead (TurnFlow#prompt_turn_failed), with the
|
|
1397
1397
|
# prompts it took: nothing goes back.
|
|
1398
|
-
# @param prompts [Array<Array(String, TurnOrigin|nil, Array|nil,
|
|
1399
|
-
# [prompt, origin, images,
|
|
1400
|
-
# back,
|
|
1401
|
-
# a
|
|
1398
|
+
# @param prompts [Array<Array(String, TurnOrigin|nil, Array|nil, TurnAnchors|nil)>]
|
|
1399
|
+
# [prompt, origin, images, anchors] (a web client gets its image chips
|
|
1400
|
+
# back, a Verify turn's claim, so sending it again still records a
|
|
1401
|
+
# verdict, and a Revise turn's draft, so it still stacks)
|
|
1402
1402
|
# @param error [Exception, nil] what failed: its one line stays in the
|
|
1403
1403
|
# conversation as a turn note
|
|
1404
1404
|
# Not saved here: run_engine_turn saves after its block.
|
|
@@ -1406,10 +1406,10 @@ module Samagotchi
|
|
|
1406
1406
|
@engine.synchronize_events do
|
|
1407
1407
|
next if @turn_flow.prompt_turn_failed(error: error) == :kept
|
|
1408
1408
|
|
|
1409
|
-
prompts.each do |prompt, origin, images,
|
|
1409
|
+
prompts.each do |prompt, origin, images, anchors|
|
|
1410
1410
|
restored = { type: :prompt_restored, prompt: prompt, origin: origin.wire }
|
|
1411
1411
|
restored[:images] = images unless Array(images).empty?
|
|
1412
|
-
restored
|
|
1412
|
+
restored.merge!(anchors.to_h) if anchors
|
|
1413
1413
|
@engine.announce(restored)
|
|
1414
1414
|
end
|
|
1415
1415
|
end
|
|
@@ -39,10 +39,11 @@ module Samagotchi
|
|
|
39
39
|
# +no_interrupt+: an offer its turn makes keeps it for its continue
|
|
40
40
|
# turn; +images+: its image refs ({file:, name:}); +delivery+: the wire
|
|
41
41
|
# value the sender asked for ("queue", or nil for the default
|
|
42
|
-
# next_step); +origin+: a TurnOrigin (a Hash is read as one)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
# next_step); +origin+: a TurnOrigin (a Hash is read as one); +anchors+:
|
|
43
|
+
# a Verify turn's claim, a Revise turn's draft (TurnAnchors).
|
|
44
|
+
Prompt = Data.define(:text, :origin, :no_interrupt, :images, :delivery, :anchors) do
|
|
45
|
+
def initialize(text:, origin: nil, no_interrupt: false, images: [], delivery: nil, anchors: nil)
|
|
46
|
+
super(text:, origin: TurnOrigin.of(origin), no_interrupt:, images:, delivery:, anchors: TurnAnchors.from(anchors))
|
|
46
47
|
end
|
|
47
48
|
end
|
|
48
49
|
|
|
@@ -215,7 +216,7 @@ module Samagotchi
|
|
|
215
216
|
return if input.images.empty? && queue_as_command(input.prompt, input.origin, File.basename(input_file))
|
|
216
217
|
|
|
217
218
|
yield Prompt.new(text: input.prompt, origin: input.origin, no_interrupt: input.no_interrupt, images: input.images,
|
|
218
|
-
delivery: input.delivery,
|
|
219
|
+
delivery: input.delivery, anchors: input.anchors)
|
|
219
220
|
ensure
|
|
220
221
|
FileUtils.rm_f(claimed_file)
|
|
221
222
|
end
|