samagotchi 0.93.0 → 0.95.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -1
  3. data/README.md +8 -8
  4. data/bin/chi +6 -2
  5. data/docs/architecture.md +4 -5
  6. data/docs/cli.md +4 -4
  7. data/docs/configuration.md +49 -86
  8. data/docs/hooks.md +2 -2
  9. data/docs/internals/bundles.md +1 -1
  10. data/docs/internals/context-telemetry.md +1 -1
  11. data/docs/internals/engine.md +7 -4
  12. data/docs/memory.md +26 -1
  13. data/docs/plugins.md +37 -0
  14. data/docs/releasing.md +2 -2
  15. data/docs/sessions.md +1 -1
  16. data/docs/testing.md +16 -11
  17. data/lib/samagotchi/bootstrap/config_writer.rb +1 -5
  18. data/lib/samagotchi/bootstrap_command.rb +1 -0
  19. data/lib/samagotchi/bundle_command/try/sandbox.rb +8 -2
  20. data/lib/samagotchi/bundle_command.rb +1 -0
  21. data/lib/samagotchi/bundles/dev/manifest.yml +2 -1
  22. data/lib/samagotchi/bundles/model-notes/manifest.yml +11 -0
  23. data/lib/samagotchi/bundles/model-notes/model_notes_deepseek.md +7 -0
  24. data/lib/samagotchi/bundles/model-notes/notes/README.md +37 -0
  25. data/lib/samagotchi/bundles/system/config_modification_protocol.md +6 -7
  26. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  27. data/lib/samagotchi/bundles/system/self_map.md +1 -1
  28. data/lib/samagotchi/client.rb +59 -196
  29. data/lib/samagotchi/config.rb +74 -15
  30. data/lib/samagotchi/engine.rb +7 -7
  31. data/lib/samagotchi/host_config.rb +20 -27
  32. data/lib/samagotchi/host_registry.rb +8 -8
  33. data/lib/samagotchi/kernel_loop.rb +46 -8
  34. data/lib/samagotchi/memory_bundle/profile.rb +22 -4
  35. data/lib/samagotchi/memory_bundle/shipped_update.rb +5 -1
  36. data/lib/samagotchi/model_profile.rb +2 -3
  37. data/lib/samagotchi/self_report.rb +0 -2
  38. data/lib/samagotchi/system_prompt.rb +7 -21
  39. data/lib/samagotchi/terminal_ui/thinking_line.rb +2 -4
  40. data/lib/samagotchi/testing/sandbox.rb +26 -0
  41. data/lib/samagotchi/thinking.rb +35 -2
  42. data/lib/samagotchi/thinking_notices.rb +11 -4
  43. data/lib/samagotchi/update_command.rb +1 -1
  44. data/lib/samagotchi/version.rb +1 -1
  45. data/lib/samagotchi/vision_support.rb +0 -3
  46. data/lib/samagotchi/web/public/app.js +15 -82
  47. data/lib/samagotchi/web/public/dock_reserve.js +103 -0
  48. data/lib/samagotchi/web/public/index.html +3 -3
  49. data/lib/samagotchi/web/public/toast_place.js +1 -1
  50. metadata +5 -1
@@ -36,9 +36,8 @@ import { createTurnView, turnHistoryNodes } from "./turn_view.js";
36
36
  import { elementOf, emptyAnswerTemplate } from "./turn_html.js";
37
37
  import { createBubbles, hintEl, outputBubbleTemplate } from "./bubbles.js";
38
38
  import { createCardsPlace } from "./cards_place.js";
39
- import { createStageView, FOLD_MS } from "./stage_view.js";
39
+ import { createStageView } from "./stage_view.js";
40
40
  import { createMarkdownPlace } from "./markdown_place.js";
41
- import { reducedMotion, STAGE_GAP } from "./stage_frame.js";
42
41
  import { openerText, openTitle } from "./chains.js";
43
42
  import { createTaskStopper } from "./task_stop.js";
44
43
  import { appendQuote } from "./annotations.js";
@@ -55,8 +54,8 @@ import { createCommandList } from "./command_list.js";
55
54
  import { createPromptHistory } from "./prompt_history.js";
56
55
  import { ALL_SESSIONS_HASH, sessionHash, sessionIdFromHash } from "./route.js";
57
56
  import { scopeDir, scopeTemplate } from "./scope.js";
58
- import { html, nothing, render } from "./vendor/lit-html.js";
59
- import { easeScrollTo, FOLLOW_MS, shouldFollowScroll } from "./scroll.js";
57
+ import { render } from "./vendor/lit-html.js";
58
+ import { shouldFollowScroll } from "./scroll.js";
60
59
  import { SHORT_WINDOW_QUERY, stripAutoHidden } from "./strip.js";
61
60
  import { createStripGrip } from "./strip_grip.js";
62
61
  import { waitingOn } from "./notify.js";
@@ -81,7 +80,7 @@ import { createDeliveryControl, QUEUE_NEEDS_WORKER } from "./delivery_line.js";
81
80
  import { liveSlots } from "./stage_model.js";
82
81
  import { currentGen } from "./turn_model.js";
83
82
  import { toolName } from "./activity.js";
84
- import { toastBottom } from "./toast_place.js";
83
+ import { createDockReserve } from "./dock_reserve.js";
85
84
  import { createToasts } from "./toasts.js";
86
85
  import { createUnqueue } from "./unqueue.js";
87
86
  import { createStartPage } from "./start_page.js";
@@ -218,6 +217,17 @@ const turnDom = view === "stage"
218
217
  renderMarkdown, linkRefs: linkRefsIn,
219
218
  })
220
219
  : createTurnView(viewArgs);
220
+ // The history's bottom reserve and the toast's place follow the composer
221
+ // card and the stage floating over the history (dock_reserve.js).
222
+ const { placeToast } = createDockReserve({
223
+ dockEl: $("#dock"),
224
+ centerEl: $("#center"),
225
+ stageEl: view === "stage" ? turnDom.root : null,
226
+ historyEl,
227
+ toastEl: $("#toast"),
228
+ modelPanelEl: $("#modelPanel"),
229
+ atHistoryEnd,
230
+ });
221
231
  // Where a running turn's rows can be: the history, and the stage while it
222
232
  // holds a turn (lookups by id, copy, annotate, card actions).
223
233
  function turnRoots() {
@@ -1441,55 +1451,6 @@ promptEl.addEventListener("input", fitPrompt);
1441
1451
  window.addEventListener("resize", fitPrompt);
1442
1452
  fitPrompt();
1443
1453
 
1444
- // The card floats over the history: keep the history's bottom padding at the
1445
- // card's height, and stay pinned to the end if the reader was there, since the
1446
- // card grows (text, chips, Cancel, a wrapping footer) under a reader at the end.
1447
- // In the stage view the stage floats above the card, out of the dock's flow
1448
- // (stage_view.js): the reserve counts it too, with its 10 px gap, while it
1449
- // shows.
1450
- const dockEl = $("#dock");
1451
- const centerEl = $("#center");
1452
- // A leaving stage (the hand-off's slide) no longer counts: the reserve eases
1453
- // down over the slide (index.html, #center.reserve-easing).
1454
- const stageEl = view === "stage" ? turnDom.root : null;
1455
- function dockReserve() {
1456
- const shown = stageEl && !stageEl.hidden && !stageEl.classList.contains("leaving");
1457
- return dockEl.offsetHeight + (shown ? stageEl.offsetHeight + STAGE_GAP : 0);
1458
- }
1459
- const historyEnd = () => historyEl.scrollHeight - historyEl.clientHeight;
1460
- // A reader at the end stays there when the reserve changes: at once for the
1461
- // composer card, eased for the stage (+fromStage+: it appeared, grew or
1462
- // leaves), so the history glides rather than jumps. An eased follow running
1463
- // counts as at the end.
1464
- function updateReserve({ fromStage = false } = {}) {
1465
- const follow = atHistoryEnd() || historyEl.dataset.following !== undefined;
1466
- const leaving = !!stageEl?.classList.contains("leaving");
1467
- centerEl.classList.toggle("reserve-easing", leaving);
1468
- const reserve = `${dockReserve()}px`;
1469
- const changed = centerEl.style.getPropertyValue("--dock-h") !== reserve;
1470
- if (changed) centerEl.style.setProperty("--dock-h", reserve);
1471
- if (changed && follow) {
1472
- if (fromStage) easeScrollTo(historyEl, historyEnd, { ms: leaving ? FOLD_MS : FOLLOW_MS, reduce: reducedMotion() });
1473
- else if (historyEl.dataset.following === undefined) historyEl.scrollTop = historyEl.scrollHeight;
1474
- }
1475
- placeToast();
1476
- }
1477
- const dockObserver = new ResizeObserver((entries) => updateReserve({ fromStage: entries.some((e) => e.target === stageEl) }));
1478
- dockObserver.observe(dockEl);
1479
- if (stageEl) {
1480
- dockObserver.observe(stageEl);
1481
- // Only showing, hiding and leaving change the reserve (not the stage's
1482
- // other classes: waiting, in-use…).
1483
- let stageShown = "";
1484
- new MutationObserver(() => {
1485
- const shown = `${stageEl.hidden}|${stageEl.classList.contains("leaving")}`;
1486
- if (shown === stageShown) return;
1487
- stageShown = shown;
1488
- updateReserve({ fromStage: true });
1489
- }).observe(stageEl, { attributes: true, attributeFilter: ["class", "hidden"] });
1490
- }
1491
- window.addEventListener("resize", () => placeToast());
1492
-
1493
1454
  promptEl.addEventListener("paste", (e) => {
1494
1455
  const files = imageFiles(e.clipboardData);
1495
1456
  // Text pasted along with an image still lands in the textarea.
@@ -1625,35 +1586,7 @@ function fillOpener(created) {
1625
1586
  promptEl.setSelectionRange(promptEl.value.length, promptEl.value.length);
1626
1587
  }
1627
1588
 
1628
- // The toast sits above the session's composer card (measured: it grows with
1629
- // a wrapping footer or a taller box), so it never covers the card's
1630
- // controls, and above the stage while it shows and a fresh link's open
1631
- // model panel (toast_place.js); with no card in sight (the start page, all
1632
- // sessions) the CSS's 24 px from the bottom.
1633
- function placeToast() {
1634
- const dock = document.getElementById("dock");
1635
- const r = dock.getBoundingClientRect();
1636
- const shownTop = (el) => {
1637
- const box = el && !el.hidden ? el.getBoundingClientRect() : null;
1638
- return box && box.height > 0 ? box.top : null;
1639
- };
1640
- const px = toastBottom({
1641
- inSession: document.body.classList.contains("has-session") && r.height > 0,
1642
- viewportHeight: window.innerHeight,
1643
- dockTop: r.top,
1644
- tops: [shownTop(document.getElementById("turnStage")), shownTop(document.getElementById("modelPanel"))],
1645
- toastHeight: document.getElementById("toast").offsetHeight,
1646
- });
1647
- const bottom = px === null ? "" : `${px}px`;
1648
- const root = document.documentElement.style;
1649
- if (root.getPropertyValue("--toast-bottom") !== bottom) {
1650
- if (bottom) root.setProperty("--toast-bottom", bottom);
1651
- else root.removeProperty("--toast-bottom");
1652
- }
1653
- }
1654
1589
  const toasts = createToasts({ el: $("#toast"), onChange: placeToast });
1655
- // The model panel opening, closing or changing height (its list filters).
1656
- new ResizeObserver(() => placeToast()).observe(document.getElementById("modelPanel"));
1657
1590
 
1658
1591
  function showToast(text, action = null, options = {}) { toasts.show(text, action, options); }
1659
1592
 
@@ -0,0 +1,103 @@
1
+ import { easeScrollTo, FOLLOW_MS } from "./scroll.js";
2
+ import { FOLD_MS } from "./stage_view.js";
3
+ import { reducedMotion, STAGE_GAP } from "./stage_frame.js";
4
+ import { toastBottom } from "./toast_place.js";
5
+
6
+ // What floats over the history (used by app.js): the composer card, and in
7
+ // the stage view the stage above it. Two measures follow it.
8
+ //
9
+ // The reserve: the card floats over the history, so the history's bottom
10
+ // padding (--dock-h on #center, index.html) is kept at the card's height,
11
+ // and a reader at the end stays pinned there, since the card grows (text,
12
+ // chips, Cancel, a wrapping footer) under a reader at the end. In the stage
13
+ // view the stage floats above the card, out of the dock's flow
14
+ // (stage_view.js): the reserve counts it too, with its STAGE_GAP, while it
15
+ // shows. A leaving stage (the hand-off's slide) no longer counts: the
16
+ // reserve eases down over the slide (#center.reserve-easing).
17
+ //
18
+ // The toast: it sits above the session's composer card (measured: it grows
19
+ // with a wrapping footer or a taller box), so it never covers the card's
20
+ // controls, and above the stage while it shows and a fresh link's open
21
+ // model panel (toast_place.js); with no card in sight (the start page, all
22
+ // sessions) the CSS's 24 px from the bottom (--toast-bottom unset).
23
+ //
24
+ // +stageEl+: the stage (stage view) or null (turn view). +atHistoryEnd()+:
25
+ // the reader is at the history's end (app.js, scroll.js's threshold).
26
+ // +reduce()+ / +ease+: prefers-reduced-motion and the eased follow
27
+ // (scroll.js), defaults the page's.
28
+ // The observers that call these are installed here: the dock's and the
29
+ // stage's size, the stage showing, hiding or leaving, the window's size and
30
+ // the model panel's. app.js calls placeToast() when the page enters or
31
+ // leaves a session and when a toast shows or goes (toasts.js onChange).
32
+ export function createDockReserve({
33
+ dockEl, centerEl, stageEl, historyEl, toastEl, modelPanelEl, atHistoryEnd,
34
+ reduce = reducedMotion, ease = easeScrollTo,
35
+ }) {
36
+ const doc = dockEl.ownerDocument;
37
+ const win = doc.defaultView;
38
+
39
+ function dockReserve() {
40
+ const shown = stageEl && !stageEl.hidden && !stageEl.classList.contains("leaving");
41
+ return dockEl.offsetHeight + (shown ? stageEl.offsetHeight + STAGE_GAP : 0);
42
+ }
43
+ const historyEnd = () => historyEl.scrollHeight - historyEl.clientHeight;
44
+
45
+ // A reader at the end stays there when the reserve changes: at once for the
46
+ // composer card, eased for the stage (+fromStage+: it appeared, grew or
47
+ // leaves), so the history glides rather than jumps. An eased follow running
48
+ // counts as at the end.
49
+ function updateReserve({ fromStage = false } = {}) {
50
+ const follow = atHistoryEnd() || historyEl.dataset.following !== undefined;
51
+ const leaving = !!stageEl?.classList.contains("leaving");
52
+ centerEl.classList.toggle("reserve-easing", leaving);
53
+ const reserve = `${dockReserve()}px`;
54
+ const changed = centerEl.style.getPropertyValue("--dock-h") !== reserve;
55
+ if (changed) centerEl.style.setProperty("--dock-h", reserve);
56
+ if (changed && follow) {
57
+ if (fromStage) ease(historyEl, historyEnd, { ms: leaving ? FOLD_MS : FOLLOW_MS, reduce: reduce() });
58
+ else if (historyEl.dataset.following === undefined) historyEl.scrollTop = historyEl.scrollHeight;
59
+ }
60
+ placeToast();
61
+ }
62
+
63
+ function placeToast() {
64
+ const r = dockEl.getBoundingClientRect();
65
+ const shownTop = (el) => {
66
+ const box = el && !el.hidden ? el.getBoundingClientRect() : null;
67
+ return box && box.height > 0 ? box.top : null;
68
+ };
69
+ const px = toastBottom({
70
+ inSession: doc.body.classList.contains("has-session") && r.height > 0,
71
+ viewportHeight: win.innerHeight,
72
+ dockTop: r.top,
73
+ tops: [shownTop(stageEl), shownTop(modelPanelEl)],
74
+ toastHeight: toastEl.offsetHeight,
75
+ });
76
+ const bottom = px === null ? "" : `${px}px`;
77
+ const root = doc.documentElement.style;
78
+ if (root.getPropertyValue("--toast-bottom") !== bottom) {
79
+ if (bottom) root.setProperty("--toast-bottom", bottom);
80
+ else root.removeProperty("--toast-bottom");
81
+ }
82
+ }
83
+
84
+ const dockObserver = new win.ResizeObserver((entries) => updateReserve({ fromStage: entries.some((e) => e.target === stageEl) }));
85
+ dockObserver.observe(dockEl);
86
+ if (stageEl) {
87
+ dockObserver.observe(stageEl);
88
+ // Only showing, hiding and leaving change the reserve (not the stage's
89
+ // other classes: waiting, in-use…).
90
+ let stageShown = "";
91
+ new win.MutationObserver(() => {
92
+ const shown = `${stageEl.hidden}|${stageEl.classList.contains("leaving")}`;
93
+ if (shown === stageShown) return;
94
+ stageShown = shown;
95
+ updateReserve({ fromStage: true });
96
+ }).observe(stageEl, { attributes: true, attributeFilter: ["class", "hidden"] });
97
+ }
98
+ win.addEventListener("resize", () => placeToast());
99
+ // The model panel opening, closing or changing height (its list filters).
100
+ new win.ResizeObserver(() => placeToast()).observe(modelPanelEl);
101
+
102
+ return { updateReserve, placeToast };
103
+ }
@@ -547,7 +547,7 @@ button.task-stop:hover:not(:disabled){background:var(--danger-bg-dim);filter:non
547
547
  @media (hover:none){.copy-btn{opacity:.55}}
548
548
  /* The dock is the composer card: the text field, a toolbar row, the info bar as its footer. */
549
549
  #dock{display:flex;flex-direction:column;flex-shrink:0;margin:0 12px 12px;border-radius:16px;position:relative;transition:border-color .12s ease,box-shadow .12s ease}
550
- /* In a session the card floats over the history, so the glass blurs it; the history's bottom padding follows the card (--dock-h, set from app.js). */
550
+ /* In a session the card floats over the history, so the glass blurs it; the history's bottom padding follows the card (--dock-h, set from dock_reserve.js). */
551
551
  /* The session panel lets the page's glow through (the history's own layers go clear), so the card and its footer read as glass over it, not as a flat box on black. */
552
552
  body.has-session #center{background:color-mix(in srgb,var(--bg) 35%,transparent);border-color:var(--glass-border);position:relative}
553
553
  body.has-session #stage,body.has-session #history{background:transparent}
@@ -719,7 +719,7 @@ body.stage-view #dock,body.stage-view #dock:focus-within{background:transparent;
719
719
  .turn-stage{display:flex;flex-direction:column;min-height:0;max-height:var(--stage-max,60vh);border-left:3px solid var(--accent);border-radius:14px;overflow:hidden}
720
720
  /* Out of the dock's flow: it floats 10 px above the composer card, so its rise, growth and leaving never move #dock (a
721
721
  bottom-pinned box changing height is a layout shift, animated or not). Painted before .dock-card (no z-index): the command
722
- list (z-index 20 inside .dock-card) opens over its lower edge. The history's bottom reserve counts it (app.js, --dock-h). */
722
+ list (z-index 20 inside .dock-card) opens over its lower edge. The history's bottom reserve counts it (dock_reserve.js, --dock-h). */
723
723
  body.stage-view .turn-stage{position:absolute;left:0;right:0;bottom:100%;margin-bottom:10px}
724
724
  .turn-stage[hidden]{display:none}
725
725
  .turn-stage.ended{border-left-color:var(--border-strong)}
@@ -1183,7 +1183,7 @@ body.all-view #center, body.all-view #topStrip{display:none}
1183
1183
  .card.archived{opacity:.6}
1184
1184
  .card.archived:hover{opacity:.85}
1185
1185
  .card .archived-badge,.family-row .archived-badge{font-size:10px;padding:1px 6px;border-radius:6px;border:1px solid var(--border-strong);color:var(--text-muted)}
1186
- /* bottom: above the session's composer card when one shows (--toast-bottom, app.js placeToast). */
1186
+ /* bottom: above the session's composer card when one shows (--toast-bottom, dock_reserve.js placeToast). */
1187
1187
  #toast{position:fixed;left:50%;bottom:var(--toast-bottom,24px);transform:translateX(-50%);width:max-content;max-width:calc(100vw - 32px);box-sizing:border-box;z-index:50;display:flex;flex-direction:column;align-items:center;gap:8px}
1188
1188
  #toast[hidden]{display:none}
1189
1189
  #toast .toast-item{display:flex;gap:12px;align-items:center;width:max-content;max-width:100%;box-sizing:border-box;padding:8px 14px;border-radius:10px;background:var(--bg-raised);color:var(--text);border:1px solid var(--border-strong);box-shadow:var(--toast-shadow);font-size:13px}
@@ -1,4 +1,4 @@
1
- // Where the toast sits (app.js placeToast measures, this decides): in a
1
+ // Where the toast sits (dock_reserve.js placeToast measures, this decides): in a
2
2
  // session, +gap+ above the highest of the composer card and what opens
3
3
  // over it (the stage while it shows, a fresh link's open model panel), so
4
4
  // it covers none of them; null elsewhere (the CSS's 24 px from the bottom).
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.93.0
4
+ version: 0.95.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -248,6 +248,9 @@ files:
248
248
  - lib/samagotchi/bundles/loop-guard/plugin.rb
249
249
  - lib/samagotchi/bundles/mcp/manifest.yml
250
250
  - lib/samagotchi/bundles/mcp/plugin.rb
251
+ - lib/samagotchi/bundles/model-notes/manifest.yml
252
+ - lib/samagotchi/bundles/model-notes/model_notes_deepseek.md
253
+ - lib/samagotchi/bundles/model-notes/notes/README.md
251
254
  - lib/samagotchi/bundles/ruby/manifest.yml
252
255
  - lib/samagotchi/bundles/ruby/plugin.rb
253
256
  - lib/samagotchi/bundles/skills/manifest.yml
@@ -675,6 +678,7 @@ files:
675
678
  - lib/samagotchi/web/public/data.js
676
679
  - lib/samagotchi/web/public/delivery_line.js
677
680
  - lib/samagotchi/web/public/diff_view.js
681
+ - lib/samagotchi/web/public/dock_reserve.js
678
682
  - lib/samagotchi/web/public/families_view.js
679
683
  - lib/samagotchi/web/public/favicon.svg
680
684
  - lib/samagotchi/web/public/format.js