samagotchi 0.100.1 → 0.101.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 +23 -1
- data/bin/chi +1 -0
- data/docs/architecture.md +6 -0
- data/docs/cli.md +55 -0
- data/docs/configuration.md +1 -0
- data/docs/internals/engine.md +1 -0
- data/docs/internals/working-set.md +130 -0
- data/docs/testing.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/engine.rb +30 -0
- data/lib/samagotchi/host_keys.rb +35 -18
- data/lib/samagotchi/testing/fake_executable.rb +6 -16
- data/lib/samagotchi/testing/fake_executable_launcher +6 -0
- data/lib/samagotchi/tool_response.rb +10 -6
- data/lib/samagotchi/tool_runner.rb +25 -6
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +49 -1
- data/lib/samagotchi/web/public/app.js +33 -3
- data/lib/samagotchi/web/public/chip_popover.js +56 -17
- data/lib/samagotchi/web/public/data.js +6 -0
- data/lib/samagotchi/web/public/index.html +127 -1
- data/lib/samagotchi/web/public/info_bar.js +16 -4
- data/lib/samagotchi/web/public/info_bar_model.js +7 -3
- data/lib/samagotchi/web/public/refs.js +10 -1
- data/lib/samagotchi/web/public/stage_model.js +6 -5
- data/lib/samagotchi/web/public/stage_view.js +34 -2
- data/lib/samagotchi/web/public/stream_handlers.js +6 -1
- data/lib/samagotchi/web/public/turn_view.js +16 -4
- data/lib/samagotchi/web/public/working_set.js +520 -0
- data/lib/samagotchi/web/public/working_set_rail.js +278 -0
- data/lib/samagotchi/web/public/working_set_view.js +426 -0
- data/lib/samagotchi/web/server.rb +6 -4
- data/lib/samagotchi/working_set.rb +200 -0
- metadata +7 -1
|
@@ -23,6 +23,8 @@ import { createInUseWatch } from "./stage_watch.js";
|
|
|
23
23
|
import { formatDuration } from "./timing.js";
|
|
24
24
|
import { easeScrollTo } from "./scroll.js";
|
|
25
25
|
import { readStored, writeStored } from "./storage.js";
|
|
26
|
+
import { createWorkingSetRail } from "./working_set_rail.js";
|
|
27
|
+
import { stampRows } from "./working_set_view.js";
|
|
26
28
|
|
|
27
29
|
const COLLAPSE_KEY = "chi_stage_collapsed";
|
|
28
30
|
// The turn-end read (markdown, answer_display) settles within this, or the
|
|
@@ -54,10 +56,12 @@ function readCollapsed() {
|
|
|
54
56
|
// +composerEl+ size the stage's cap.
|
|
55
57
|
// +taskStop+: the page's stopper (task_stop.js), for the stop-task button
|
|
56
58
|
// on a running task_wait. +renderMarkdown+ and +linkRefs+: the asking
|
|
57
|
-
// slot's markdown read and ref pass (stage_slots.js).
|
|
59
|
+
// slot's markdown read and ref pass (stage_slots.js). +workingSet+: the
|
|
60
|
+
// page's working set (working_set_view.js, web.working_set; null: off): its
|
|
61
|
+
// rail in the live slots, its footprint at the hand-off.
|
|
58
62
|
export function createStageView({
|
|
59
63
|
historyEl, appendToHistory, isNearBottom: historyNearBottom, sawText, sessionId, dockEl, centerEl, composerEl, taskStop = null,
|
|
60
|
-
renderMarkdown, linkRefs,
|
|
64
|
+
renderMarkdown, linkRefs, workingSet = null,
|
|
61
65
|
}) {
|
|
62
66
|
// The dock becomes a clear column: the stage's card, then the composer's
|
|
63
67
|
// (its rows moved into .dock-card, which wears the glass and the focus ring).
|
|
@@ -97,6 +101,16 @@ export function createStageView({
|
|
|
97
101
|
asking.hidden = true;
|
|
98
102
|
const now = node("div", "ts-now", live);
|
|
99
103
|
const trail = node("div", "ts-trail", live);
|
|
104
|
+
// The working set's rail: out of the live slots' flow on their right (a
|
|
105
|
+
// row under the trail in a narrow stage), so it never sets their height.
|
|
106
|
+
const rail = workingSet
|
|
107
|
+
? createWorkingSetRail({ live, stage, onOpen: (el) => workingSet.openTurn(el), onChange: () => scheduleRefresh() })
|
|
108
|
+
: null;
|
|
109
|
+
if (rail) {
|
|
110
|
+
workingSet.setSeen(rail.seenAt);
|
|
111
|
+
// The server's states change the lines (never the fold): a redraw.
|
|
112
|
+
workingSet.onServer(() => scheduleRefresh());
|
|
113
|
+
}
|
|
100
114
|
const extras = node("div", "ts-extras", scroller);
|
|
101
115
|
const answer = node("div", "ts-answer", scroller);
|
|
102
116
|
const tail = node("div", "ts-tail", scroller);
|
|
@@ -324,6 +338,11 @@ export function createStageView({
|
|
|
324
338
|
slotsView.drawNow(slots.tool, slots.quiet);
|
|
325
339
|
slotsView.drawTrail(slots.trail, flash);
|
|
326
340
|
slotsView.drawTicks(slots.ticks);
|
|
341
|
+
if (rail) {
|
|
342
|
+
const { items, ordinal } = workingSet.liveItems(turn, promptWords());
|
|
343
|
+
stampRows(blockEl, turn, ordinal);
|
|
344
|
+
rail.draw(items, { ctxPct: workingSet.ctxPct() });
|
|
345
|
+
}
|
|
327
346
|
const calls = slots.ticks.length;
|
|
328
347
|
// An ended turn says each thing once: its steps in the chip (the label
|
|
329
348
|
// of what the cloud holds), its time in the status row's timing line
|
|
@@ -348,6 +367,13 @@ export function createStageView({
|
|
|
348
367
|
tail.hidden = !tail.firstChild;
|
|
349
368
|
}
|
|
350
369
|
|
|
370
|
+
// The prompt's first words (the working set's session tags say whose
|
|
371
|
+
// turn a step was).
|
|
372
|
+
function promptWords() {
|
|
373
|
+
const text = (promptSlot.querySelector(".user-message")?.textContent || "").replace(/\s+/g, " ").trim();
|
|
374
|
+
return text.length > 60 ? `${text.slice(0, 59)}\u2026` : text;
|
|
375
|
+
}
|
|
376
|
+
|
|
351
377
|
// The next step: the live slots' waiting height is let go. In a turn
|
|
352
378
|
// with the question room that height was the whole reply's: it goes as
|
|
353
379
|
// far as the stage's scroll absorbs it (the scroll moves up by as much,
|
|
@@ -539,6 +565,7 @@ export function createStageView({
|
|
|
539
565
|
function emptySlots() {
|
|
540
566
|
for (const slot of [promptSlot, extras, answer, tail, cloudBody, queuedSlot, timingSlot]) slot.replaceChildren();
|
|
541
567
|
slotsView.reset();
|
|
568
|
+
rail?.reset();
|
|
542
569
|
promptSlot.classList.remove("full");
|
|
543
570
|
thinkingLine = null;
|
|
544
571
|
flash = null;
|
|
@@ -584,6 +611,7 @@ export function createStageView({
|
|
|
584
611
|
}
|
|
585
612
|
|
|
586
613
|
function release() {
|
|
614
|
+
workingSet?.release();
|
|
587
615
|
releaseTurn();
|
|
588
616
|
clearStage();
|
|
589
617
|
}
|
|
@@ -604,9 +632,13 @@ export function createStageView({
|
|
|
604
632
|
const { blockEl } = view.current();
|
|
605
633
|
view.chronological();
|
|
606
634
|
const block = blockEl && blockEl.parentNode === cloudBody ? blockEl : null;
|
|
635
|
+
// The turn's set joins the page's turns (a plain one's too: the turns
|
|
636
|
+
// keep their places); its footprint goes under the block.
|
|
637
|
+
const footprint = workingSet ? workingSet.handOff(view.current().turn, promptWords()) : null;
|
|
607
638
|
const pieces = handOffOrder({
|
|
608
639
|
prompt: promptSlot.firstElementChild,
|
|
609
640
|
block,
|
|
641
|
+
footprint: block ? footprint : null,
|
|
610
642
|
extras: [...extras.children],
|
|
611
643
|
answer: [...answer.children],
|
|
612
644
|
timing: timingSlot.firstElementChild,
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
// liveTurn, turnDom, shownTexts, questionCards, contextChips,
|
|
18
18
|
// llmContextControl, thinkingControl,
|
|
19
19
|
// reasoningHistoryControl the page's views
|
|
20
|
+
// workingSet the stage view's working set (web.working_set),
|
|
21
|
+
// null when off
|
|
20
22
|
// sentIds, earlyRestores restore.js's bookkeeping
|
|
21
23
|
// stream: {takeGuess(), restart()} the stream was opened from a
|
|
22
24
|
// guess (takeGuess: whether, and forget it); restart:
|
|
@@ -51,7 +53,7 @@ export function createStreamHandlers(deps) {
|
|
|
51
53
|
const {
|
|
52
54
|
selected, state, clientId, capabilities, historyEl,
|
|
53
55
|
liveTurn, turnDom, shownTexts, questionCards, contextChips, llmContextControl, thinkingControl, reasoningHistoryControl,
|
|
54
|
-
sentIds, earlyRestores, stream, composer, getSession,
|
|
56
|
+
workingSet = null, sentIds, earlyRestores, stream, composer, getSession,
|
|
55
57
|
resync, updateInfoBar, applyPromptOps, showSteers, markRecapStale, setTurnRunning, setLiveStatus, endTurnView,
|
|
56
58
|
appendChunk, readTurnEnd, applyFinalMarkdown, refreshTimingFromSession, makeUserBubble, addEndBubble,
|
|
57
59
|
commandBubble, addCommandBubble, addNoteBubble, findInTurnRoots, setUsedMemories, showRecap,
|
|
@@ -307,6 +309,9 @@ export function createStreamHandlers(deps) {
|
|
|
307
309
|
used_memories_updated: (data) => {
|
|
308
310
|
if (Array.isArray(data.used_memory_names)) setUsedMemories(data.used_memory_names);
|
|
309
311
|
},
|
|
312
|
+
// The server's working set (WorkingSet), at a request and at turn end;
|
|
313
|
+
// ignored with web.working_set off.
|
|
314
|
+
working_set_updated: (data) => { workingSet?.update({ items: data.items, at: data.at }); },
|
|
310
315
|
context_status: (data) => {
|
|
311
316
|
const current = state();
|
|
312
317
|
// After /model (Engine#announce_switched_context!): the new model's
|
|
@@ -39,6 +39,7 @@ import * as model from "./turn_model.js";
|
|
|
39
39
|
import { createHold } from "./hold.js";
|
|
40
40
|
import { linkedLine } from "./notice_links.js";
|
|
41
41
|
import { taskCommand, taskStopButton } from "./task_stop.js";
|
|
42
|
+
import { footprintTemplate, groupItems, stampHistory } from "./working_set_view.js";
|
|
42
43
|
|
|
43
44
|
// ---- The templates: one step, its thinking, its label ----
|
|
44
45
|
|
|
@@ -840,8 +841,11 @@ function failedLineTemplate(record) {
|
|
|
840
841
|
// duration from the timing's tool records), then the answer and the turn's
|
|
841
842
|
// timing line under it.
|
|
842
843
|
// +thumbs(images)+ draws a prompt's or a tool row's image thumbs (a
|
|
843
|
-
// template, images.js thumbsTemplate).
|
|
844
|
-
|
|
844
|
+
// template, images.js thumbsTemplate). +workingSet+ (the stage view's
|
|
845
|
+
// web.working_set, never the turn view's): each block stamped with its
|
|
846
|
+
// turn's ordinal and its rows with theirs (working_set_view.js), and the
|
|
847
|
+
// turn's footprint line after it.
|
|
848
|
+
export function turnHistoryNodes(items, timing, { thumbs, workingSet = false }) {
|
|
845
849
|
const timingTemplate = (group) => {
|
|
846
850
|
const record = group.record;
|
|
847
851
|
// Numbered by its place in the turn records, as live (a failed turn
|
|
@@ -875,12 +879,16 @@ export function turnHistoryNodes(items, timing, { thumbs }) {
|
|
|
875
879
|
};
|
|
876
880
|
const steerTemplate = (i, bubble) => steerRowTemplate({ source: items[i].source, text: items[i].content }, { bubble });
|
|
877
881
|
const parts = [];
|
|
878
|
-
// Each block's steps, in order: its outline once the nodes exist
|
|
882
|
+
// Each block's steps, in order: its outline once the nodes exist; and
|
|
883
|
+
// its turn's place among the turns (the working set's stamp).
|
|
879
884
|
const outlines = [];
|
|
885
|
+
const blockTurns = [];
|
|
886
|
+
let turnOrdinal = -1;
|
|
880
887
|
for (const group of turnGroups(items, timing)) {
|
|
881
888
|
if (group.kind === "note") { parts.push(noteBubbleTemplate(items[group.i])); continue; }
|
|
882
889
|
if (group.kind === "steer") { parts.push(steerTemplate(group.i, true)); continue; }
|
|
883
890
|
if (group.kind === "answer") { parts.push(answerTemplate(group.answer, nothing)); continue; }
|
|
891
|
+
turnOrdinal += 1;
|
|
884
892
|
const prompt = items[group.user];
|
|
885
893
|
// A context wake turn starts with its note.
|
|
886
894
|
parts.push(prompt.role === "note" ? noteBubbleTemplate(prompt) : userTemplate(prompt));
|
|
@@ -909,6 +917,8 @@ export function turnHistoryNodes(items, timing, { thumbs }) {
|
|
|
909
917
|
}));
|
|
910
918
|
parts.push(html`<details class="turn-work done"><summary>${model.blockSummary({ gens, ended: true })}</summary>${steps}</details>`);
|
|
911
919
|
outlines.push(gens);
|
|
920
|
+
blockTurns.push(turnOrdinal);
|
|
921
|
+
if (workingSet) parts.push(footprintTemplate(groupItems(group, turnOrdinal), turnOrdinal));
|
|
912
922
|
}
|
|
913
923
|
// Steers with no step to sit in, or in a turn that shows no block: on
|
|
914
924
|
// their own after the prompt.
|
|
@@ -935,7 +945,9 @@ export function turnHistoryNodes(items, timing, { thumbs }) {
|
|
|
935
945
|
}
|
|
936
946
|
const out = nodesOf(parts);
|
|
937
947
|
// A finished turn's block as the live end leaves it: its outline, open.
|
|
938
|
-
[...out.children].filter((el) => el.matches(".turn-work"))
|
|
948
|
+
const blocks = [...out.children].filter((el) => el.matches(".turn-work"));
|
|
949
|
+
blocks.forEach((block, i) => outlineBlock(block, outlines[i] || []));
|
|
950
|
+
if (workingSet) stampHistory(blocks, blockTurns);
|
|
939
951
|
// Each answer bubble and step text knows its message's refs
|
|
940
952
|
// (answer_refs.js; the page pairs them after the render), the index
|
|
941
953
|
// stays out of the DOM.
|