samagotchi 0.72.0 → 0.74.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 +50 -1
- data/docs/architecture.md +1 -1
- data/docs/cli.md +9 -3
- data/docs/desktop.md +2 -1
- data/docs/guardrails.md +2 -1
- data/docs/hooks.md +4 -2
- data/docs/internals/background-tasks.md +4 -3
- data/docs/plugins.md +9 -2
- data/docs/sessions.md +3 -3
- data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
- data/lib/samagotchi/bundles/mcp/plugin.rb +41 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/desktop/macos.rb +5 -0
- data/lib/samagotchi/paths.rb +8 -6
- data/lib/samagotchi/session.rb +5 -0
- data/lib/samagotchi/session_chain.rb +24 -0
- data/lib/samagotchi/session_continue.rb +3 -1
- data/lib/samagotchi/session_manager.rb +7 -0
- data/lib/samagotchi/tool_output_cut.rb +2 -1
- data/lib/samagotchi/tools/builtins.rb +17 -4
- data/lib/samagotchi/tools/execute.rb +1 -15
- data/lib/samagotchi/tools/task_runtime.rb +75 -6
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +27 -3
- data/lib/samagotchi/web/public/app.js +33 -10
- data/lib/samagotchi/web/public/bubbles.js +4 -2
- data/lib/samagotchi/web/public/chains.js +21 -1
- data/lib/samagotchi/web/public/data.js +9 -0
- data/lib/samagotchi/web/public/index.html +39 -2
- data/lib/samagotchi/web/public/notice_links.js +55 -0
- data/lib/samagotchi/web/public/plain_line.js +55 -0
- data/lib/samagotchi/web/public/question_cards.js +37 -3
- data/lib/samagotchi/web/public/stage_model.js +3 -33
- data/lib/samagotchi/web/public/stage_slots.js +35 -3
- data/lib/samagotchi/web/public/stage_view.js +9 -4
- data/lib/samagotchi/web/public/turn_html.js +3 -2
- data/lib/samagotchi/web/public/turn_model.js +7 -4
- data/lib/samagotchi/web/public/turn_view.js +3 -2
- metadata +3 -1
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { sessionHash } from "./route.js";
|
|
23
23
|
import { clientLabel, isOwn } from "./turn_events.js";
|
|
24
24
|
import { continueLine } from "./commands.js";
|
|
25
|
+
import { plainText } from "./plain_line.js";
|
|
25
26
|
|
|
26
27
|
// @param sessionId () => the open session's id (answers go there)
|
|
27
28
|
// @param clientId this tab's id in the session's events
|
|
@@ -31,6 +32,9 @@ import { continueLine } from "./commands.js";
|
|
|
31
32
|
// @param removeHintIfEmpty () drops the history's placeholder hint
|
|
32
33
|
// @param askingText () => the running turn's asking reply (turn_model.js
|
|
33
34
|
// askingText via the page's view), kept by a question or own approval card
|
|
35
|
+
// @param renderMarkdown (text) => Promise<html|null>: the kept reply's
|
|
36
|
+
// markdown (the page's POST /api/render; null keeps the raw text)
|
|
37
|
+
// @param linkRefs (el) the page's path:line ref pass on it
|
|
34
38
|
// @param api {sendAnswer, dismissQuestion, sendCommand} (data.js)
|
|
35
39
|
// @param doc the page's document
|
|
36
40
|
// @return {renderQuestion(pq, {said}), resolveQuestion(id, opts), removeQuestion(),
|
|
@@ -38,6 +42,7 @@ import { continueLine } from "./commands.js";
|
|
|
38
42
|
// renderContinue(offer), resolveContinue(data), forgetContinue()}
|
|
39
43
|
export function createQuestionCards({
|
|
40
44
|
sessionId, clientId, place, isNearBottom, scrollToEnd, removeHintIfEmpty, askingText = () => "",
|
|
45
|
+
renderMarkdown = () => Promise.resolve(null), linkRefs = () => {},
|
|
41
46
|
api = { sendAnswer, dismissQuestion, sendCommand }, doc = globalThis.document,
|
|
42
47
|
}) {
|
|
43
48
|
// Live ask_user_question card, keyed by pending question id. Re-rendered from
|
|
@@ -400,16 +405,18 @@ export function createQuestionCards({
|
|
|
400
405
|
// The reply that asked, kept with the card: what made the question
|
|
401
406
|
// answerable stays with it in the history. Muted under the "Q → A" line
|
|
402
407
|
// in two lines while the card is closed (a copy screen readers skip: not
|
|
403
|
-
// the toggle's label
|
|
408
|
+
// the toggle's label; plain text, plain_line.js: no "##" or "**" in a
|
|
409
|
+
// clamped line), and whole, selectable, at the top of the body
|
|
404
410
|
// when it is open (a drag in the summary would toggle the card).
|
|
405
411
|
const asked = saidOf.get(questionEl);
|
|
412
|
+
let full = null;
|
|
406
413
|
if (summary && asked) {
|
|
407
414
|
const line = doc.createElement("div");
|
|
408
415
|
line.className = "question-said";
|
|
409
416
|
line.setAttribute("aria-hidden", "true");
|
|
410
|
-
line.textContent = asked;
|
|
417
|
+
line.textContent = plainText(asked);
|
|
411
418
|
summary.appendChild(line);
|
|
412
|
-
|
|
419
|
+
full = doc.createElement("div");
|
|
413
420
|
full.className = "question-said-full";
|
|
414
421
|
full.textContent = asked;
|
|
415
422
|
questionEl.insertBefore(full, summary.nextSibling);
|
|
@@ -421,9 +428,36 @@ export function createQuestionCards({
|
|
|
421
428
|
const keepNote = questionEl.dataset.toggled || (!cancelled && deniedReason(pending, answer));
|
|
422
429
|
if (summary && !keepNote) note.classList.add("hidden");
|
|
423
430
|
if (!questionEl.dataset.toggled) questionEl.open = false;
|
|
431
|
+
// After the collapse: a card left open by hand renders at once.
|
|
432
|
+
if (full) renderSaidOnOpen(questionEl, full, asked);
|
|
424
433
|
pending = null;
|
|
425
434
|
}
|
|
426
435
|
|
|
436
|
+
// The body's copy as markdown, from the card's first open on (a reload
|
|
437
|
+
// draws every card in the history: rendering them all would ask once per
|
|
438
|
+
// card). Raw until then, and when the render answers null (then the next
|
|
439
|
+
// open asks again).
|
|
440
|
+
function renderSaidOnOpen(card, full, asked) {
|
|
441
|
+
let state = "raw";
|
|
442
|
+
const draw = () => {
|
|
443
|
+
if (!card.open || state !== "raw") return;
|
|
444
|
+
state = "asking";
|
|
445
|
+
Promise.resolve()
|
|
446
|
+
.then(() => renderMarkdown(asked))
|
|
447
|
+
.catch(() => null)
|
|
448
|
+
.then((html) => {
|
|
449
|
+
if (!html) { state = "raw"; return; }
|
|
450
|
+
state = "done";
|
|
451
|
+
// Sanitised on the server (POST /api/render).
|
|
452
|
+
full.innerHTML = html;
|
|
453
|
+
full.classList.add("markdown");
|
|
454
|
+
linkRefs(full);
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
card.addEventListener("toggle", draw);
|
|
458
|
+
draw();
|
|
459
|
+
}
|
|
460
|
+
|
|
427
461
|
function markQuestionSelection(sel) {
|
|
428
462
|
if (!questionEl || !sel.length) return;
|
|
429
463
|
questionEl.querySelectorAll(".question-choice").forEach((button) => {
|
|
@@ -7,6 +7,7 @@ import { splitSentences } from "./sentences.js";
|
|
|
7
7
|
import { steerSender } from "./format.js";
|
|
8
8
|
import { hookNoticeLabel } from "./turn_events.js";
|
|
9
9
|
import { askingText } from "./turn_model.js";
|
|
10
|
+
import { plainLine } from "./plain_line.js";
|
|
10
11
|
|
|
11
12
|
const READ_LIKE = new Set(["read", "memory_read", "web_fetch", "task_get", "task_list", "list_sessions"]);
|
|
12
13
|
const EDITS = new Set(["edit", "write", "memory_write"]);
|
|
@@ -19,37 +20,6 @@ const QUIET_MS = 1500;
|
|
|
19
20
|
// A touch, wheel or scroll counts as use this long (phones have no hover).
|
|
20
21
|
const TOUCH_MS = 4000;
|
|
21
22
|
|
|
22
|
-
// One headline sentence as plain text: the markdown a streaming answer
|
|
23
|
-
// carries (headings, quotes, bullets, table pipes, emphasis, code ticks,
|
|
24
|
-
// links) goes, and a line that is only syntax (a code fence, a table's
|
|
25
|
-
// separator row, a rule) is empty. A small pass, no renderer: a live line
|
|
26
|
-
// only needs to read well, and an unclosed "**" mid-stream goes too.
|
|
27
|
-
export function plainHeadline(line) {
|
|
28
|
-
let s = String(line ?? "").trim();
|
|
29
|
-
if (/^(```|~~~)/.test(s)) return "";
|
|
30
|
-
if (/^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?$/.test(s)) return "";
|
|
31
|
-
if (/^([-*_])(\s*\1){2,}$/.test(s)) return "";
|
|
32
|
-
s = s.replace(/^(>\s*)+/, "").replace(/^#{1,6}\s+/, "").replace(/^[-*+]\s+(\[[ xX]\]\s+)?/, "");
|
|
33
|
-
// A table row: a pipe at either end (a pipe mid-prose, "a || b", stays).
|
|
34
|
-
if (/^\||\|$/.test(s)) {
|
|
35
|
-
s = s.replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim()).filter(Boolean).join(" · ");
|
|
36
|
-
}
|
|
37
|
-
// Code spans keep their text as is; an unclosed tick just goes.
|
|
38
|
-
const code = [];
|
|
39
|
-
s = s.replace(/`+([^`]+)`+/g, (_, c) => `\u0000${code.push(c) - 1}\u0000`);
|
|
40
|
-
s = s
|
|
41
|
-
.replace(/!\[([^\]]*)\]\([^)]*\)?/g, "$1")
|
|
42
|
-
.replace(/\[([^\]]*)\]\([^)]*\)?/g, "$1")
|
|
43
|
-
.replace(/<(https?:\/\/[^>\s]+)>/g, "$1")
|
|
44
|
-
.replace(/`+/g, "")
|
|
45
|
-
.replace(/(^|[^\w])__(?=\S)(.*?\S)__(?=$|[^\w])/g, "$1$2")
|
|
46
|
-
.replace(/(\*\*|~~)/g, "")
|
|
47
|
-
.replace(/(^|[^\w*])\*(?=\S)([^*]*?\S)\*(?=$|[^\w*])/g, "$1$2")
|
|
48
|
-
.replace(/(^|[^\w])_(?=\S)([^_]*?\S)_(?=$|[^\w])/g, "$1$2")
|
|
49
|
-
.replace(/\u0000(\d+)\u0000/g, (_, i) => code[Number(i)]);
|
|
50
|
-
return s.replace(/\s+/g, " ").trim();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
23
|
// The step's newest narration sentence, as plain text: the end of a
|
|
54
24
|
// finished text is a boundary (its last line may have no period), a live
|
|
55
25
|
// one's not yet. A sentence that is only markdown syntax is skipped, and so
|
|
@@ -61,9 +31,9 @@ export function headlineOf(text, done) {
|
|
|
61
31
|
const plain = [];
|
|
62
32
|
for (const sentence of sentences) {
|
|
63
33
|
if (/^(```|~~~)/.test(sentence)) fenced = !fenced;
|
|
64
|
-
else if (!fenced &&
|
|
34
|
+
else if (!fenced && plainLine(sentence)) plain.push(plainLine(sentence));
|
|
65
35
|
}
|
|
66
|
-
const fragment = fenced ? "" :
|
|
36
|
+
const fragment = fenced ? "" : plainLine(rest);
|
|
67
37
|
if (done && fragment) return fragment;
|
|
68
38
|
if (plain.length) return plain[plain.length - 1];
|
|
69
39
|
return fragment.length > 160 ? fragment : "";
|
|
@@ -19,10 +19,19 @@ export function node(tag, cls, parent = null) {
|
|
|
19
19
|
// +taskStop+: the page's stopper (task_stop.js), for the stop-task button
|
|
20
20
|
// on a running task_wait; +gens()+ the running turn's gens (the button's
|
|
21
21
|
// command, read at its click); +onChange+ redraws (the stage's
|
|
22
|
-
// scheduleRefresh).
|
|
23
|
-
|
|
22
|
+
// scheduleRefresh). +renderMarkdown(text)+: the asking text's HTML
|
|
23
|
+
// (Promise<string|null>; null keeps the raw text: markdown off, a failed
|
|
24
|
+
// read); +linkRefs(el)+: the page's path:line ref pass on the slot.
|
|
25
|
+
export function createStageSlots({
|
|
26
|
+
headline, asking, now, trail, ticks, taskStop = null, gens, onChange,
|
|
27
|
+
renderMarkdown = () => Promise.resolve(null), linkRefs = () => {},
|
|
28
|
+
}) {
|
|
24
29
|
// What each slot shows, so an unchanged slot is left alone.
|
|
25
30
|
const shown = { headline: null, now: null, trail: null, asking: null };
|
|
31
|
+
// The last asking text sent to render and its answer (in flight or
|
|
32
|
+
// done, HTML only): the same question drawn again (after a reset, a
|
|
33
|
+
// blank between) asks no second time.
|
|
34
|
+
let rendering = { text: null, html: null };
|
|
26
35
|
|
|
27
36
|
function drawHeadline({ text, thinking }) {
|
|
28
37
|
const key = `${thinking ? "t" : "n"}|${text}`;
|
|
@@ -42,13 +51,35 @@ export function createStageSlots({ headline, asking, now, trail, ticks, taskStop
|
|
|
42
51
|
|
|
43
52
|
// Patched only when the text changes (a late chunk to the closed gen
|
|
44
53
|
// rewrites the same text): a selection in it survives. A new text reads
|
|
45
|
-
// from its top
|
|
54
|
+
// from its top: drawn raw at once, then swapped for its markdown when
|
|
55
|
+
// that comes back while the slot still shows the same text (the text is
|
|
56
|
+
// final: it shows only once the call that asks runs).
|
|
46
57
|
function drawAsking(text) {
|
|
47
58
|
asking.hidden = !text;
|
|
48
59
|
if (shown.asking === text) return;
|
|
49
60
|
shown.asking = text;
|
|
61
|
+
asking.classList.remove("markdown");
|
|
50
62
|
asking.textContent = text;
|
|
51
63
|
asking.scrollTop = 0;
|
|
64
|
+
if (!text) return;
|
|
65
|
+
linkRefs(asking);
|
|
66
|
+
if (rendering.text !== text) {
|
|
67
|
+
let html;
|
|
68
|
+
try { html = Promise.resolve(renderMarkdown(text)).catch(() => null); } catch (_) { html = Promise.resolve(null); }
|
|
69
|
+
rendering = { text, html };
|
|
70
|
+
}
|
|
71
|
+
const pending = rendering;
|
|
72
|
+
pending.html.then((html) => {
|
|
73
|
+
// A null isn't kept: the next draw of this text asks again.
|
|
74
|
+
if (!html && rendering === pending) rendering = { text: null, html: null };
|
|
75
|
+
// Swapped once: a blank-then-same draw while in flight waits twice.
|
|
76
|
+
if (!html || shown.asking !== text || asking.classList.contains("markdown")) return;
|
|
77
|
+
// Sanitised on the server (POST /api/render), as an answer's HTML is.
|
|
78
|
+
asking.innerHTML = html;
|
|
79
|
+
asking.classList.add("markdown");
|
|
80
|
+
asking.scrollTop = 0;
|
|
81
|
+
linkRefs(asking);
|
|
82
|
+
});
|
|
52
83
|
}
|
|
53
84
|
|
|
54
85
|
function drawNow(tool, quiet) {
|
|
@@ -123,6 +154,7 @@ export function createStageSlots({ headline, asking, now, trail, ticks, taskStop
|
|
|
123
154
|
function reset() {
|
|
124
155
|
for (const slot of [headline, asking, now, trail, ticks]) slot.replaceChildren();
|
|
125
156
|
asking.hidden = true;
|
|
157
|
+
asking.classList.remove("markdown");
|
|
126
158
|
shown.headline = shown.now = shown.trail = shown.asking = null;
|
|
127
159
|
}
|
|
128
160
|
|
|
@@ -48,8 +48,12 @@ function readCollapsed() {
|
|
|
48
48
|
// the history only for a reader at its end). +dockEl+, +centerEl+ and
|
|
49
49
|
// +composerEl+ size the stage's cap.
|
|
50
50
|
// +taskStop+: the page's stopper (task_stop.js), for the stop-task button
|
|
51
|
-
// on a running task_wait.
|
|
52
|
-
|
|
51
|
+
// on a running task_wait. +renderMarkdown+ and +linkRefs+: the asking
|
|
52
|
+
// slot's markdown read and ref pass (stage_slots.js).
|
|
53
|
+
export function createStageView({
|
|
54
|
+
historyEl, appendToHistory, isNearBottom: historyNearBottom, sawText, sessionId, dockEl, centerEl, composerEl, taskStop = null,
|
|
55
|
+
renderMarkdown, linkRefs,
|
|
56
|
+
}) {
|
|
53
57
|
// The dock becomes a clear column: the stage's card, then the composer's
|
|
54
58
|
// (its rows moved into .dock-card, which wears the glass and the focus ring).
|
|
55
59
|
document.body.classList.add("stage-view");
|
|
@@ -82,7 +86,8 @@ export function createStageView({ historyEl, appendToHistory, isNearBottom: hist
|
|
|
82
86
|
headline.setAttribute("role", "button");
|
|
83
87
|
headline.title = "Show this step's reasoning";
|
|
84
88
|
// While a question or approval waits: the whole reply that asked it, in
|
|
85
|
-
// the headline's place (the card says what to answer, this says why)
|
|
89
|
+
// the headline's place (the card says what to answer, this says why),
|
|
90
|
+
// as markdown when web.markdown is on.
|
|
86
91
|
const asking = node("div", "ts-asking", live);
|
|
87
92
|
asking.hidden = true;
|
|
88
93
|
const now = node("div", "ts-now", live);
|
|
@@ -111,7 +116,7 @@ export function createStageView({ historyEl, appendToHistory, isNearBottom: hist
|
|
|
111
116
|
// "queued"), each to run as a turn of its own; they hand off last.
|
|
112
117
|
const queuedSlot = node("div", "ts-queued", stage);
|
|
113
118
|
const slotsView = createStageSlots({
|
|
114
|
-
headline, asking, now, trail, ticks, taskStop,
|
|
119
|
+
headline, asking, now, trail, ticks, taskStop, renderMarkdown, linkRefs,
|
|
115
120
|
gens: () => view.current().turn?.gens,
|
|
116
121
|
onChange: () => scheduleRefresh(),
|
|
117
122
|
});
|
|
@@ -83,8 +83,9 @@ function llmEditTemplate(row) {
|
|
|
83
83
|
// A gate-corrected call's `ran as:` line shows on hover only.
|
|
84
84
|
const INLINE_CHARS = 300;
|
|
85
85
|
// The JS twin of ToolOutputCut::EXIT_LINE (tool_output_cut.rb): an exit line, then maybe a hint; a
|
|
86
|
-
// signal-killed command's has no number ("exit: ").
|
|
87
|
-
|
|
86
|
+
// signal-killed command's has no number ("exit: "). spec/fixtures/exit_lines.json holds the cases both
|
|
87
|
+
// must answer alike (exported for turn_html.test.js).
|
|
88
|
+
export const EXIT_LINE = /\nexit: (?:-?\d+)?[^\n]*(?:\n\([^\n]*\))?$/;
|
|
88
89
|
// A bare success exit line; group 1 is the hint line after it, if any.
|
|
89
90
|
const SUCCESS_EXIT_LINE = /\nexit: 0(\n\([^\n]*\))?$/;
|
|
90
91
|
const EXECUTE_BLOCK = /(?:^|\n)(?:stdout|stderr):\n/;
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { addCompleted, addStarted, newActivity, toolName } from "./activity.js";
|
|
27
27
|
import { tallyText } from "./tally.js";
|
|
28
28
|
import { clip, leadTrimmed } from "./format.js";
|
|
29
|
+
import { plainLine } from "./plain_line.js";
|
|
29
30
|
|
|
30
31
|
const LABEL_MAX = 80;
|
|
31
32
|
const LABEL_TOOLS = 3;
|
|
@@ -210,9 +211,11 @@ export function failedCount(rows) {
|
|
|
210
211
|
return rows.filter((r) => r.status === "error").length;
|
|
211
212
|
}
|
|
212
213
|
|
|
213
|
-
// The collapsed row of a gen: its narration's first line
|
|
214
|
-
//
|
|
215
|
-
//
|
|
214
|
+
// The collapsed row of a gen: its narration's first line as plain text
|
|
215
|
+
// (plain_line.js: "## Plan" reads "Plan"; a line that is only syntax, a rule
|
|
216
|
+
// or a fence, gives way to the next), else its first call's title ("edit
|
|
217
|
+
// lib/a.rb"), else the tools it worked with, else "thinking"; then its call
|
|
218
|
+
// count and how many failed.
|
|
216
219
|
export function genLabel(gen) {
|
|
217
220
|
const { head, calls } = genLabelParts(gen);
|
|
218
221
|
return calls ? `${head} · ${calls}` : head;
|
|
@@ -223,7 +226,7 @@ export function genLabel(gen) {
|
|
|
223
226
|
// starts with (its narration, its first call's title), so an open step's
|
|
224
227
|
// summary can say only its calls (turn_view.js labelHtml).
|
|
225
228
|
export function genLabelParts(gen) {
|
|
226
|
-
const line = String(gen.text || "").
|
|
229
|
+
const line = String(gen.text || "").split("\n").map(plainLine).find(Boolean) || "";
|
|
227
230
|
const calls = gen.tools.length ? toolCallsText(gen.tools) : "";
|
|
228
231
|
const first = gen.tools[0];
|
|
229
232
|
if (line) return { head: cut(line), calls, repeats: true };
|
|
@@ -37,6 +37,7 @@ import { createSentenceFeed } from "./sentences.js";
|
|
|
37
37
|
import { createTicker } from "./thinking_ticker.js";
|
|
38
38
|
import * as model from "./turn_model.js";
|
|
39
39
|
import { createHold } from "./hold.js";
|
|
40
|
+
import { linkedLine } from "./notice_links.js";
|
|
40
41
|
import { taskCommand, taskStopButton } from "./task_stop.js";
|
|
41
42
|
|
|
42
43
|
// ---- The templates: one step, its thinking, its label ----
|
|
@@ -99,10 +100,10 @@ export function stepTemplate(gen, st = {}) {
|
|
|
99
100
|
|
|
100
101
|
// A hook's line (hook_notice) as a step's row. data.line is a whole row of
|
|
101
102
|
// chi's own (an empty-answer retry), shown as is; data.cls a class of its
|
|
102
|
-
// own and data.title its hover (a ✂ row's).
|
|
103
|
+
// own and data.title its hover (a ✂ row's). Its http(s) URLs are links.
|
|
103
104
|
function noticeTemplate(data) {
|
|
104
105
|
const cls = "hook-notice" + (data.level === "warn" ? " warn" : "") + (data.cls ? ` ${data.cls}` : "");
|
|
105
|
-
return html`<div class=${cls} title=${data.title || nothing}>${data.line || `${hookNoticeLabel(data.hook)}: ${data.text || ""}`}</div>`;
|
|
106
|
+
return html`<div class=${cls} title=${data.title || nothing}>${linkedLine(data.line || `${hookNoticeLabel(data.hook)}: ${data.text || ""}`)}</div>`;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
// +template+ rendered as detached nodes (a fragment).
|
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.74.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -614,8 +614,10 @@ files:
|
|
|
614
614
|
- lib/samagotchi/web/public/llm_context_chip.js
|
|
615
615
|
- lib/samagotchi/web/public/model_pick.js
|
|
616
616
|
- lib/samagotchi/web/public/model_picker.js
|
|
617
|
+
- lib/samagotchi/web/public/notice_links.js
|
|
617
618
|
- lib/samagotchi/web/public/notifications.js
|
|
618
619
|
- lib/samagotchi/web/public/notify.js
|
|
620
|
+
- lib/samagotchi/web/public/plain_line.js
|
|
619
621
|
- lib/samagotchi/web/public/prompt_history.js
|
|
620
622
|
- lib/samagotchi/web/public/question_card.js
|
|
621
623
|
- lib/samagotchi/web/public/question_cards.js
|