samagotchi 0.74.0 → 0.76.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 +68 -1
- data/docs/architecture.md +11 -4
- data/docs/cli.md +13 -1
- data/docs/configuration.md +4 -3
- data/docs/guardrails.md +9 -0
- data/docs/internals/anthropic.md +3 -1
- data/docs/internals/bundles.md +21 -7
- data/docs/internals/context-telemetry.md +22 -0
- data/docs/internals/engine.md +14 -3
- data/docs/internals/prompt-caching.md +2 -1
- data/docs/memory.md +48 -1
- data/docs/plugins.md +2 -1
- data/docs/releasing.md +6 -2
- data/docs/sessions.md +7 -5
- data/lib/samagotchi/bundle_command.rb +52 -39
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/codex-peer/plugin.rb +19 -2
- data/lib/samagotchi/bundles/core/manifest.yml +2 -2
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/desktop/macos.rb +1 -2
- data/lib/samagotchi/effective_model.rb +5 -0
- data/lib/samagotchi/engine.rb +84 -4
- data/lib/samagotchi/kernel_loop.rb +13 -10
- data/lib/samagotchi/llm_context_override.rb +8 -6
- data/lib/samagotchi/memory_bundle/asset_installer.rb +34 -5
- data/lib/samagotchi/memory_bundle/builder.rb +11 -14
- data/lib/samagotchi/memory_bundle/installer.rb +140 -9
- data/lib/samagotchi/memory_bundle/manifest.rb +20 -1
- data/lib/samagotchi/memory_bundle/memory_files.rb +65 -0
- data/lib/samagotchi/memory_bundle/profile.rb +1 -1
- data/lib/samagotchi/memory_bundle/shipped_update.rb +2 -1
- data/lib/samagotchi/memory_bundle/source.rb +66 -3
- data/lib/samagotchi/memory_bundle/system_bundle.rb +4 -2
- data/lib/samagotchi/model_price.rb +4 -3
- data/lib/samagotchi/paths.rb +15 -1
- data/lib/samagotchi/session.rb +1 -1
- data/lib/samagotchi/session_chain.rb +2 -8
- data/lib/samagotchi/session_commands.rb +2 -0
- data/lib/samagotchi/session_manager.rb +1 -2
- data/lib/samagotchi/session_metrics/model_segment.rb +109 -0
- data/lib/samagotchi/session_metrics/totals.rb +127 -0
- data/lib/samagotchi/session_metrics.rb +118 -83
- data/lib/samagotchi/terminal_ui/formatting.rb +127 -10
- data/lib/samagotchi/terminal_ui/status_row.rb +3 -1
- data/lib/samagotchi/toolkit.rb +9 -8
- data/lib/samagotchi/toolkit_command.rb +2 -2
- data/lib/samagotchi/tools/builtins.rb +1 -5
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +3 -0
- data/lib/samagotchi/web/public/app.js +4 -3
- data/lib/samagotchi/web/public/chains.js +42 -21
- data/lib/samagotchi/web/public/composer.js +9 -5
- data/lib/samagotchi/web/public/ctx.js +73 -11
- data/lib/samagotchi/web/public/index.html +32 -35
- data/lib/samagotchi/web/public/info_bar.js +4 -4
- data/lib/samagotchi/web/public/info_bar_model.js +18 -8
- data/lib/samagotchi/web/public/lists_view.js +2 -2
- data/lib/samagotchi/web/public/question_cards.js +1 -1
- data/lib/samagotchi/web/public/session_state.js +7 -0
- data/lib/samagotchi/web/public/stage_slots.js +3 -3
- data/lib/samagotchi/web/public/stream_handlers.js +32 -1
- data/lib/samagotchi/web/public/timing.js +11 -2
- metadata +4 -1
|
@@ -326,22 +326,35 @@ button.task-stop:hover:not(:disabled){background:var(--danger-bg-dim);filter:non
|
|
|
326
326
|
.bubble.question>summary .question-said{max-width:72ch;margin:2px 0 1px 1.05em;font-size:12.5px;font-weight:400;line-height:1.5;color:var(--text-muted);white-space:normal;overflow-wrap:anywhere;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden}
|
|
327
327
|
.bubble.question[open]>summary .question-said{display:none}
|
|
328
328
|
.question-said-full{margin:4px 0 6px;font-size:13px;line-height:1.55;color:var(--text-muted);white-space:pre-wrap;overflow-wrap:anywhere}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
.
|
|
336
|
-
.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
.question-said-full
|
|
342
|
-
.
|
|
343
|
-
.
|
|
344
|
-
.
|
|
329
|
+
.question-said-full.md-compact{
|
|
330
|
+
--md-heading-size:13.5px;--md-heading-color:var(--text-2);
|
|
331
|
+
--md-subheading-size:13.5px;--md-subheading-color:var(--text-2);
|
|
332
|
+
--md-strong-color:currentColor;--md-blockquote-color:currentColor;--md-code-color:currentColor;
|
|
333
|
+
--md-pre-size:12px;--md-cell-size:12.5px
|
|
334
|
+
}
|
|
335
|
+
.ts-asking.md-compact{
|
|
336
|
+
--md-heading-size:14.5px;--md-heading-color:var(--text-bright);
|
|
337
|
+
--md-subheading-size:14px;--md-subheading-color:var(--text-2);
|
|
338
|
+
--md-strong-color:var(--text-bright);--md-blockquote-color:var(--text-muted);--md-code-color:var(--text);
|
|
339
|
+
--md-pre-size:12.5px;--md-cell-size:13px
|
|
340
|
+
}
|
|
341
|
+
.md-compact:is(.ts-asking,.question-said-full){white-space:normal}
|
|
342
|
+
.md-compact :is(h1,h2,h3,h4,h5,h6){font-size:var(--md-heading-size);font-weight:600;line-height:1.4;margin:8px 0 2px;color:var(--md-heading-color)}
|
|
343
|
+
.md-compact :is(h4,h5,h6){font-size:var(--md-subheading-size);color:var(--md-subheading-color)}
|
|
344
|
+
.md-compact :is(p,ul,ol,blockquote,table,pre){margin:0 0 6px}
|
|
345
|
+
.md-compact>:first-child{margin-top:0}
|
|
346
|
+
.md-compact>:last-child{margin-bottom:0}
|
|
347
|
+
.md-compact :is(ul,ol){padding-left:1.3em}
|
|
348
|
+
.md-compact li>:is(ul,ol){margin:0}
|
|
349
|
+
.md-compact hr{border:0;border-top:1px solid var(--border);margin:8px 0}
|
|
350
|
+
.md-compact blockquote{border-left:3px solid var(--border-strong);padding-left:10px;color:var(--md-blockquote-color)}
|
|
351
|
+
.md-compact code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.88em}
|
|
352
|
+
.md-compact :not(pre)>code{background:var(--bg-inset);border:1px solid var(--border);border-radius:4px;padding:0 4px;color:var(--md-code-color)}
|
|
353
|
+
.md-compact strong{color:var(--md-strong-color)}
|
|
354
|
+
.md-compact pre{overflow:auto;padding:6px 8px;border-radius:6px;background:var(--bg-inset);border:1px solid var(--border);font-size:var(--md-pre-size);line-height:1.45;white-space:pre}
|
|
355
|
+
.md-compact table{border-collapse:collapse;display:block;max-width:100%;overflow:auto}
|
|
356
|
+
.md-compact :is(th,td){border:1px solid var(--border-strong);padding:3px 7px;font-size:var(--md-cell-size)}
|
|
357
|
+
.md-compact th{background:var(--bg-inset);font-weight:600}
|
|
345
358
|
.bubble.command{border-left:3px solid var(--text-faint);background:var(--bg-panel);font-size:13px}
|
|
346
359
|
.bubble.note{border-left:3px dotted var(--text-faint);background:transparent;font-size:12px;color:var(--text-muted);opacity:.85}
|
|
347
360
|
.note-line{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text-faint);margin-bottom:2px}
|
|
@@ -730,24 +743,6 @@ body.stage-view .turn-stage{position:absolute;left:0;right:0;bottom:100%;margin-
|
|
|
730
743
|
text, then its markdown when web.markdown is on (.markdown, stage_slots.js drawAsking); capped so the card keeps
|
|
731
744
|
its room, read from the top. */
|
|
732
745
|
.ts-asking{max-height:calc(var(--stage-max,60vh) * .35);overflow:auto;scrollbar-width:thin;white-space:pre-wrap;overflow-wrap:anywhere;font-size:14px;line-height:1.55;color:var(--narr);border-left:2px solid var(--border-strong);padding-left:10px}
|
|
733
|
-
/* Its markdown, compact: about the raw text's metrics, so the swap barely moves the slot. */
|
|
734
|
-
.ts-asking.markdown{white-space:normal}
|
|
735
|
-
.ts-asking.markdown :is(h1,h2,h3,h4,h5,h6){font-size:14.5px;font-weight:600;line-height:1.4;margin:8px 0 2px;color:var(--text-bright)}
|
|
736
|
-
.ts-asking.markdown :is(h4,h5,h6){font-size:14px;color:var(--text-2)}
|
|
737
|
-
.ts-asking.markdown :is(p,ul,ol,blockquote,table,pre){margin:0 0 6px}
|
|
738
|
-
.ts-asking.markdown>:first-child{margin-top:0}
|
|
739
|
-
.ts-asking.markdown>:last-child{margin-bottom:0}
|
|
740
|
-
.ts-asking.markdown :is(ul,ol){padding-left:1.3em}
|
|
741
|
-
.ts-asking.markdown li>:is(ul,ol){margin:0}
|
|
742
|
-
.ts-asking.markdown strong{color:var(--text-bright)}
|
|
743
|
-
.ts-asking.markdown hr{border:0;border-top:1px solid var(--border);margin:8px 0}
|
|
744
|
-
.ts-asking.markdown blockquote{border-left:3px solid var(--border-strong);padding-left:10px;color:var(--text-muted)}
|
|
745
|
-
.ts-asking.markdown code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.88em}
|
|
746
|
-
.ts-asking.markdown :not(pre)>code{background:var(--bg-inset);border:1px solid var(--border);border-radius:4px;padding:0 4px;color:var(--text)}
|
|
747
|
-
.ts-asking.markdown pre{overflow:auto;padding:6px 8px;border-radius:6px;background:var(--bg-inset);border:1px solid var(--border);font-size:12.5px;line-height:1.45;white-space:pre}
|
|
748
|
-
.ts-asking.markdown table{border-collapse:collapse;display:block;max-width:100%;overflow:auto}
|
|
749
|
-
.ts-asking.markdown :is(th,td){border:1px solid var(--border-strong);padding:3px 7px;font-size:13px}
|
|
750
|
-
.ts-asking.markdown th{background:var(--bg-inset);font-weight:600}
|
|
751
746
|
.turn-stage.asking .ts-headline{display:none}
|
|
752
747
|
@keyframes ts-fade{from{opacity:.35}}
|
|
753
748
|
.ts-now{height:var(--ts-now-h);display:flex;align-items:center;gap:8px;min-width:0;padding:0 9px;border-radius:8px;background:var(--accent-tint);font-size:13px}
|
|
@@ -1031,8 +1026,10 @@ body.no-glass :is(#familyPop,#chainPop){background:var(--bg-panel);-webkit-backd
|
|
|
1031
1026
|
.chain-chip .cc-arrow{display:inline-block;margin-right:4px}
|
|
1032
1027
|
.chain-chip .live-dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--ok);margin-right:3px;vertical-align:1px}
|
|
1033
1028
|
.chain-chip .cc-waiting{font-family:system-ui,sans-serif;font-weight:600}
|
|
1034
|
-
.chain-row .cr-
|
|
1029
|
+
.chain-row .cr-n{font-family:ui-monospace,monospace;font-size:11px;color:var(--accent);white-space:nowrap}
|
|
1035
1030
|
.chain-row .cr-date{font-family:ui-monospace,monospace;font-size:11px;color:var(--text-muted);white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
1031
|
+
/* A row with its time (chainStamp): every row's date as wide, so the previews line up. */
|
|
1032
|
+
.chain-rows:has(.cr-date.timed) .cr-date{min-width:16ch}
|
|
1036
1033
|
/* The session page's links to the previous and the next link of its chain. */
|
|
1037
1034
|
#infoBar .meta a.chain-link{text-decoration:none;font-family:ui-monospace,monospace;white-space:nowrap;color:var(--accent)}
|
|
1038
1035
|
#infoBar .meta a.chain-link:hover{background:var(--accent-bg)}
|
|
@@ -21,12 +21,12 @@ import { chipTemplate as toolkitChip } from "./toolkit_chip.js";
|
|
|
21
21
|
export const PREV = "←";
|
|
22
22
|
export const NEXT = "→";
|
|
23
23
|
|
|
24
|
-
// A chain's ← / → link (chains.js chainNeighbours): its date (else
|
|
25
|
-
// id), its
|
|
26
|
-
// archived}.
|
|
24
|
+
// A chain's ← / → link (chains.js chainNeighbours): its date or time (else
|
|
25
|
+
// its short id), its id, full start and preview in the tooltip. +link+:
|
|
26
|
+
// {id, date, stamp, preview, archived} (info_bar_model.js chainLinkView).
|
|
27
27
|
export function chainLinkTemplate(link, arrow, what) {
|
|
28
28
|
const short = String(link.id).slice(0, 8);
|
|
29
|
-
const title = `${what}: ${short}${link.preview ? ` · ${link.preview}` : ""}${link.archived ? " (archived)" : ""}`;
|
|
29
|
+
const title = `${what}: ${short}${link.stamp ? ` · ${link.stamp}` : ""}${link.preview ? ` · ${link.preview}` : ""}${link.archived ? " (archived)" : ""}`;
|
|
30
30
|
const text = arrow === PREV ? `${arrow} ${link.date || short}` : `${link.date || short} ${arrow}`;
|
|
31
31
|
return html`<a class="model chain-link" href=${sessionHash(link.id)} title=${title}>${text}</a>`;
|
|
32
32
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// its list and its in-flight actions. Pure: app.js's updateInfoBar applies
|
|
5
5
|
// it, every second while a turn runs (the session clock).
|
|
6
6
|
import { canStopSession, childrenSummary, delegatedBy, modelLabel, promptNotesChip } from "./format.js";
|
|
7
|
-
import { chainDate, chainNeighbours } from "./chains.js";
|
|
7
|
+
import { chainClock, chainDate, chainNeighbours } from "./chains.js";
|
|
8
8
|
import { ctxBarText, ctxBarTip, lastSpeedText, speedText } from "./ctx.js";
|
|
9
9
|
import { formatDuration, sessionDurationNow } from "./timing.js";
|
|
10
10
|
import { workerBadge } from "./update_notice.js";
|
|
@@ -21,12 +21,18 @@ export function workerBadgeFor(id, state, versions) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// A chain's link (chains.js chainNeighbours) as the info bar's ← / →
|
|
24
|
-
// shows it (info_bar.js chainLinkTemplate), or null.
|
|
25
|
-
//
|
|
26
|
-
|
|
24
|
+
// shows it (info_bar.js chainLinkTemplate), or null. Its date: its local
|
|
25
|
+
// HH:MM when it started on the open link's day (+selfIso+: the open
|
|
26
|
+
// session's created_at; the day is implied), else its YYYY-MM-DD; its
|
|
27
|
+
// stamp (the tooltip's) always both. +cardTitle+: the lists' title for a
|
|
28
|
+
// session (a chain's opener reads its first link's).
|
|
29
|
+
export function chainLinkView(link, cardTitle, selfIso = null) {
|
|
27
30
|
if (!link) return null;
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
const iso = link.session?.created_at;
|
|
32
|
+
const day = chainDate(iso);
|
|
33
|
+
const clock = chainClock(iso);
|
|
34
|
+
return { id: link.id, date: day && day === chainDate(selfIso) ? clock : day, stamp: day ? `${day} ${clock}` : "",
|
|
35
|
+
preview: link.session ? cardTitle(link.session) : "", archived: !!link.session?.archived };
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
// The bar's buttons: continue (not on a link continued already, which has
|
|
@@ -61,11 +67,15 @@ export function infoBarModel({ id, state, sessions, chain, cardTitle, badge, res
|
|
|
61
67
|
const tokens = state.timing.tokens;
|
|
62
68
|
const ctxTip = ctxBarTip(tokens, state.timing.memoryIndex,
|
|
63
69
|
{ tokens: state.ctxWindow, source: state.ctxWindowSource, pct: state.ctxPct,
|
|
64
|
-
budget: state.llmContext?.budget_tokens }
|
|
70
|
+
budget: state.llmContext?.budget_tokens, afterSwitch: state.ctxUnknownAfterSwitch },
|
|
71
|
+
state.timing.modelSegments);
|
|
65
72
|
const speedTextNow = lastSpeedText(tokens);
|
|
66
73
|
const avgSpeed = speedText(tokens?.avg_decode_tps, tokens?.tps_source);
|
|
67
74
|
const sessionDuration = sessionDurationNow(state.timing, running);
|
|
68
75
|
// A delegated session names its parent, as a link to it.
|
|
76
|
+
// The open link's start: the payload's, else its list entry's (a fresh
|
|
77
|
+
// link may not be listed yet: then neither, and its links show dates).
|
|
78
|
+
const selfIso = state.createdAt || sessions.find((x) => x.id === id)?.created_at || null;
|
|
69
79
|
const parentChip = delegatedBy(state.parentId, sessions.find((x) => x.id === state.parentId) || null);
|
|
70
80
|
return {
|
|
71
81
|
id,
|
|
@@ -74,7 +84,7 @@ export function infoBarModel({ id, state, sessions, chain, cardTitle, badge, res
|
|
|
74
84
|
model: modelLabel(state.model, ...(state.served || [])),
|
|
75
85
|
// A link of a session chain: ← the previous link, → the next one (each
|
|
76
86
|
// only while the list holds it).
|
|
77
|
-
chain: { prev: chainLinkView(chain.prev, cardTitle), next: chainLinkView(chain.next, cardTitle) },
|
|
87
|
+
chain: { prev: chainLinkView(chain.prev, cardTitle, selfIso), next: chainLinkView(chain.next, cardTitle, selfIso) },
|
|
78
88
|
parent: parentChip ? { id: state.parentId, title: parentChip.title } : null,
|
|
79
89
|
// A parent names its delegates; the link opens all sessions.
|
|
80
90
|
kids: childrenSummary({ id }, sessions),
|
|
@@ -129,7 +129,7 @@ export function cardAnimStyle({ enterMs = null, now = null } = {}, period = DOT_
|
|
|
129
129
|
// member does; with rows (all sessions) an open family lists its members
|
|
130
130
|
// inline, the strip's opens in a popover; matchIds: a search's matching
|
|
131
131
|
// members. A family in a session chain (chains.js foldChains) carries its
|
|
132
|
-
//
|
|
132
|
+
// `#N` chip, which opens the other links in a popover. entering: a card
|
|
133
133
|
// new to its list (enteringCards: it fades in); anim: its inline animation
|
|
134
134
|
// phases (cardAnimStyle).
|
|
135
135
|
// +ctx+: title (cardTitle), projectName (the page's scope), activeId (the
|
|
@@ -342,7 +342,7 @@ export function createListsView(deps) {
|
|
|
342
342
|
doc.addEventListener("scroll", () => familyPop.reposition(), true);
|
|
343
343
|
|
|
344
344
|
// A chain's popover (chains.js): the other links of the chain a card's
|
|
345
|
-
//
|
|
345
|
+
// `#N` chip stands for, under the chip, in the strip or all sessions
|
|
346
346
|
// (the list it was opened from: chainFrom). A row opens that link.
|
|
347
347
|
const chainFamilies = new Map(); // head id → family, as the lists last drew it
|
|
348
348
|
let chainFrom = null; // the list element the open popover's chip is in
|
|
@@ -450,7 +450,7 @@ export function createQuestionCards({
|
|
|
450
450
|
state = "done";
|
|
451
451
|
// Sanitised on the server (POST /api/render).
|
|
452
452
|
full.innerHTML = html;
|
|
453
|
-
full.classList.add("markdown");
|
|
453
|
+
full.classList.add("markdown", "md-compact");
|
|
454
454
|
linkRefs(full);
|
|
455
455
|
});
|
|
456
456
|
};
|
|
@@ -23,11 +23,17 @@ export function emptySessionState() {
|
|
|
23
23
|
parentId: null,
|
|
24
24
|
// The previous link of its chain (its `continues`), or null.
|
|
25
25
|
continues: null,
|
|
26
|
+
// Its start (the summary's created_at; the info bar's chain links are
|
|
27
|
+
// times on its day), or null.
|
|
28
|
+
createdAt: null,
|
|
26
29
|
ctxPct: null,
|
|
27
30
|
// Window the kernel resolved for the running generation (:generation_started).
|
|
28
31
|
ctxWindow: null,
|
|
29
32
|
// Where it came from ("server", "config", … "default": chi's guess).
|
|
30
33
|
ctxWindowSource: null,
|
|
34
|
+
// A /model's new window didn't resolve and no request has started since
|
|
35
|
+
// (stream_handlers.js context_status): the ctx tooltip says it waits.
|
|
36
|
+
ctxUnknownAfterSwitch: false,
|
|
31
37
|
status: "",
|
|
32
38
|
model: "",
|
|
33
39
|
// What the worker's server said it served for a model name: [served, asked].
|
|
@@ -107,6 +113,7 @@ export function sessionStateFrom(data) {
|
|
|
107
113
|
state.toolkit = s.toolkit || null;
|
|
108
114
|
state.parentId = s.parent_id || null;
|
|
109
115
|
state.continues = s.continues || null;
|
|
116
|
+
state.createdAt = s.created_at || null;
|
|
110
117
|
state.commands = Array.isArray(data.commands) ? data.commands : [];
|
|
111
118
|
return state;
|
|
112
119
|
}
|
|
@@ -58,7 +58,7 @@ export function createStageSlots({
|
|
|
58
58
|
asking.hidden = !text;
|
|
59
59
|
if (shown.asking === text) return;
|
|
60
60
|
shown.asking = text;
|
|
61
|
-
asking.classList.remove("markdown");
|
|
61
|
+
asking.classList.remove("markdown", "md-compact");
|
|
62
62
|
asking.textContent = text;
|
|
63
63
|
asking.scrollTop = 0;
|
|
64
64
|
if (!text) return;
|
|
@@ -76,7 +76,7 @@ export function createStageSlots({
|
|
|
76
76
|
if (!html || shown.asking !== text || asking.classList.contains("markdown")) return;
|
|
77
77
|
// Sanitised on the server (POST /api/render), as an answer's HTML is.
|
|
78
78
|
asking.innerHTML = html;
|
|
79
|
-
asking.classList.add("markdown");
|
|
79
|
+
asking.classList.add("markdown", "md-compact");
|
|
80
80
|
asking.scrollTop = 0;
|
|
81
81
|
linkRefs(asking);
|
|
82
82
|
});
|
|
@@ -154,7 +154,7 @@ export function createStageSlots({
|
|
|
154
154
|
function reset() {
|
|
155
155
|
for (const slot of [headline, asking, now, trail, ticks]) slot.replaceChildren();
|
|
156
156
|
asking.hidden = true;
|
|
157
|
-
asking.classList.remove("markdown");
|
|
157
|
+
asking.classList.remove("markdown", "md-compact");
|
|
158
158
|
shown.headline = shown.now = shown.trail = shown.asking = null;
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -73,6 +73,9 @@ export function createStreamHandlers(deps) {
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
generation_started: (data) => {
|
|
76
|
+
// The request after a /model's unresolved window started: whatever it
|
|
77
|
+
// brings, the ctx tooltip no longer waits for it (ctxBarTip).
|
|
78
|
+
state().ctxUnknownAfterSwitch = false;
|
|
76
79
|
if (data?.context_window_tokens) {
|
|
77
80
|
const current = state();
|
|
78
81
|
current.ctxWindow = data.context_window_tokens;
|
|
@@ -91,6 +94,10 @@ export function createStreamHandlers(deps) {
|
|
|
91
94
|
if (data?.served_model) current.served = [data.served_model, data.requested_model, data.served_expected_by || null];
|
|
92
95
|
// The generation's speed and the session's token totals, from the server.
|
|
93
96
|
current.timing.tokens = generationTokens(current.timing.tokens, data);
|
|
97
|
+
// The rows per model, the server's whole list (the open one with this
|
|
98
|
+
// generation in); an event without them (a replay, an older worker)
|
|
99
|
+
// keeps the page's.
|
|
100
|
+
if (Array.isArray(data?.model_segments)) current.timing.modelSegments = data.model_segments;
|
|
94
101
|
if (data?.served_model || data?.tokens) updateInfoBar();
|
|
95
102
|
turnDom.generationCompleted();
|
|
96
103
|
},
|
|
@@ -185,6 +192,7 @@ export function createStreamHandlers(deps) {
|
|
|
185
192
|
// then show the prompt once more, marked failed (with why). This tab's own
|
|
186
193
|
// prompt goes back into an empty composer.
|
|
187
194
|
prompt_restored: async (data) => {
|
|
195
|
+
const id = selected();
|
|
188
196
|
keepEarlyRestore(data, earlyRestores, { myId: clientId, sentIds });
|
|
189
197
|
const { refill, label, images: restoredImages } = restoreAction(data, { myId: clientId, sentIds, composerEmpty: composer.empty() });
|
|
190
198
|
if (refill) composer.refill(refill);
|
|
@@ -192,6 +200,9 @@ export function createStreamHandlers(deps) {
|
|
|
192
200
|
const failedText = liveTurn.failedText;
|
|
193
201
|
liveTurn.failedText = null;
|
|
194
202
|
await resync();
|
|
203
|
+
// Another session was opened during the re-read: its history isn't
|
|
204
|
+
// this one's (the re-read drew nothing either).
|
|
205
|
+
if (selected() !== id) return;
|
|
195
206
|
// The re-render drew it from the session (failed_turn): drawn again
|
|
196
207
|
// below with this event's label and images.
|
|
197
208
|
const drawn = historyEl.querySelector(".bubble.user[data-failed-turn]");
|
|
@@ -207,6 +218,7 @@ export function createStreamHandlers(deps) {
|
|
|
207
218
|
// A session command ran in the worker (any client's). One that changed
|
|
208
219
|
// the conversation (!rollback, !cmd, a continue answered no) re-reads it.
|
|
209
220
|
command_ran: async (data) => {
|
|
221
|
+
const id = selected();
|
|
210
222
|
const view = commandView(data, clientId);
|
|
211
223
|
if (view.modelName && view.modelName !== state().model) {
|
|
212
224
|
state().model = view.modelName;
|
|
@@ -240,7 +252,11 @@ export function createStreamHandlers(deps) {
|
|
|
240
252
|
state().toolkit = data.toolkit || null;
|
|
241
253
|
updateInfoBar();
|
|
242
254
|
}
|
|
243
|
-
if (view.resync)
|
|
255
|
+
if (view.resync) {
|
|
256
|
+
await resync();
|
|
257
|
+
// Another session was opened during the re-read: the bubble isn't its.
|
|
258
|
+
if (selected() !== id) return;
|
|
259
|
+
}
|
|
244
260
|
// A card's action that isn't anytime (the step-limit question's
|
|
245
261
|
// /continue) has no bubble: nothing to remove. An anytime or queued
|
|
246
262
|
// one's bubble was drawn at its command_queued: filled in here. One
|
|
@@ -286,6 +302,21 @@ export function createStreamHandlers(deps) {
|
|
|
286
302
|
},
|
|
287
303
|
context_status: (data) => {
|
|
288
304
|
const current = state();
|
|
305
|
+
// After /model (Engine#announce_switched_context!): the new model's
|
|
306
|
+
// window, and usage null when it didn't resolve (the % clears, "ctx ?").
|
|
307
|
+
// Only that event carries the key: a null drops the old model's window,
|
|
308
|
+
// so the chunks before the next window count no % against it.
|
|
309
|
+
if (data && "context_window_tokens" in data) {
|
|
310
|
+
current.ctxWindow = data.context_window_tokens || null;
|
|
311
|
+
current.ctxWindowSource = data.context_window_tokens ? data.context_window_source || null : null;
|
|
312
|
+
// The ctx tooltip's "until the next request" (ctxBarTip).
|
|
313
|
+
current.ctxUnknownAfterSwitch = !data.context_window_tokens;
|
|
314
|
+
}
|
|
315
|
+
if (data && "usage" in data && data.usage === null) {
|
|
316
|
+
current.ctxPct = null;
|
|
317
|
+
updateInfoBar();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
289
320
|
const pct = extractCtxPct(data, current.ctxWindow, current.llmContext?.budget_tokens);
|
|
290
321
|
if (pct !== null) {
|
|
291
322
|
current.ctxPct = pct;
|
|
@@ -81,6 +81,10 @@ export function normalizeTiming(data = {}) {
|
|
|
81
81
|
// The memory indexes the session's prompt holds (SessionMetrics'
|
|
82
82
|
// memory_index), for the ctx tooltip.
|
|
83
83
|
memoryIndex: validRecord(data.memory_index) ? data.memory_index : null,
|
|
84
|
+
// The session's tokens, cost and speeds per model after a /model
|
|
85
|
+
// (SessionMetrics' model_segments; [] with one model), for the ctx
|
|
86
|
+
// tooltip.
|
|
87
|
+
modelSegments: Array.isArray(data.model_segments) ? data.model_segments.filter(validRecord) : [],
|
|
84
88
|
};
|
|
85
89
|
}
|
|
86
90
|
|
|
@@ -91,7 +95,7 @@ export function normalizeTiming(data = {}) {
|
|
|
91
95
|
// dates keep arrival order), an unknown tool record at the end (they come
|
|
92
96
|
// grouped by turn, in call order). startedAt, sessionDurationMs and
|
|
93
97
|
// activeTurn are the reply's, and so are its tokens (a reply without them
|
|
94
|
-
// keeps the page's). `short`: the reply's turn_count says the page
|
|
98
|
+
// keeps the page's) and its model_segments (present: authoritative). `short`: the reply's turn_count says the page
|
|
95
99
|
// misses a turn (it then reads the whole timing). A reply without
|
|
96
100
|
// turn_count (an older server: the whole timing) replaces, as before; more
|
|
97
101
|
// records than the count (a worker died before saving a turn the page saw)
|
|
@@ -117,7 +121,12 @@ export function mergeTiming(timing, data = {}) {
|
|
|
117
121
|
});
|
|
118
122
|
const tokens = fresh.tokens || timing?.tokens || null;
|
|
119
123
|
const memoryIndex = fresh.memoryIndex || timing?.memoryIndex || null;
|
|
120
|
-
|
|
124
|
+
// A reply that has the rows is the answer, an empty list too (one model
|
|
125
|
+
// now): a stale list never survives it; one without them (an older
|
|
126
|
+
// server) keeps the page's.
|
|
127
|
+
const modelSegments = data && "model_segments" in data ? fresh.modelSegments : (timing?.modelSegments || []);
|
|
128
|
+
return { timing: { ...fresh, turnRecords, toolRecords, tokens, memoryIndex, modelSegments },
|
|
129
|
+
short: turnRecords.length < count };
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
function replaceById(records, record) {
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: samagotchi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.76.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -380,6 +380,7 @@ files:
|
|
|
380
380
|
- lib/samagotchi/memory_bundle/installer.rb
|
|
381
381
|
- lib/samagotchi/memory_bundle/listing.rb
|
|
382
382
|
- lib/samagotchi/memory_bundle/manifest.rb
|
|
383
|
+
- lib/samagotchi/memory_bundle/memory_files.rb
|
|
383
384
|
- lib/samagotchi/memory_bundle/merger.rb
|
|
384
385
|
- lib/samagotchi/memory_bundle/placeholder.rb
|
|
385
386
|
- lib/samagotchi/memory_bundle/plugin_ref.rb
|
|
@@ -460,6 +461,8 @@ files:
|
|
|
460
461
|
- lib/samagotchi/session_inbox.rb
|
|
461
462
|
- lib/samagotchi/session_manager.rb
|
|
462
463
|
- lib/samagotchi/session_metrics.rb
|
|
464
|
+
- lib/samagotchi/session_metrics/model_segment.rb
|
|
465
|
+
- lib/samagotchi/session_metrics/totals.rb
|
|
463
466
|
- lib/samagotchi/session_model_settings.rb
|
|
464
467
|
- lib/samagotchi/session_observer.rb
|
|
465
468
|
- lib/samagotchi/session_retention.rb
|