samagotchi 0.8.1 → 0.10.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -1
  3. data/docs/architecture.md +24 -9
  4. data/docs/cli.md +4 -3
  5. data/docs/guardrails.md +9 -2
  6. data/docs/hooks.md +44 -7
  7. data/docs/internals/gemma4-contract.md +1 -0
  8. data/docs/memory.md +5 -3
  9. data/docs/plugins.md +47 -17
  10. data/docs/sessions.md +1 -1
  11. data/lib/samagotchi/bootstrap_command.rb +29 -29
  12. data/lib/samagotchi/bridge/card_store.rb +4 -4
  13. data/lib/samagotchi/bridge/pending_card.rb +2 -2
  14. data/lib/samagotchi/bridge/sse_writer.rb +22 -28
  15. data/lib/samagotchi/bridge/turn_accumulator.rb +92 -5
  16. data/lib/samagotchi/bridge.rb +59 -112
  17. data/lib/samagotchi/bridge_client.rb +11 -28
  18. data/lib/samagotchi/bundle_command.rb +158 -212
  19. data/lib/samagotchi/bundles/check-in/manifest.yml +2 -2
  20. data/lib/samagotchi/bundles/check-in/plugin.rb +3 -4
  21. data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +7 -3
  22. data/lib/samagotchi/bundles/known-names/manifest.yml +2 -2
  23. data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
  24. data/lib/samagotchi/bundles/loop-guard/plugin.rb +10 -10
  25. data/lib/samagotchi/bundles/mcp/manifest.yml +3 -3
  26. data/lib/samagotchi/bundles/mcp/plugin.rb +2 -4
  27. data/lib/samagotchi/bundles/skills/manifest.yml +3 -3
  28. data/lib/samagotchi/bundles/skills/plugin.rb +65 -17
  29. data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +0 -1
  30. data/lib/samagotchi/bundles/source-links/manifest.yml +2 -2
  31. data/lib/samagotchi/bundles/system/config_modification_protocol.md +2 -2
  32. data/lib/samagotchi/bundles/system/identity.md +1 -1
  33. data/lib/samagotchi/bundles/system/manifest.yml +4 -4
  34. data/lib/samagotchi/bundles/system/memory_guide.md +5 -4
  35. data/lib/samagotchi/cli/command.rb +51 -0
  36. data/lib/samagotchi/cli/flags.rb +116 -0
  37. data/lib/samagotchi/config.rb +70 -55
  38. data/lib/samagotchi/config_text_edit.rb +107 -0
  39. data/lib/samagotchi/desktop_command.rb +24 -18
  40. data/lib/samagotchi/edit_preview.rb +2 -2
  41. data/lib/samagotchi/engine.rb +20 -75
  42. data/lib/samagotchi/events.rb +23 -0
  43. data/lib/samagotchi/guardrail_wiring.rb +2 -5
  44. data/lib/samagotchi/guardrails/targets.rb +7 -1
  45. data/lib/samagotchi/hooks/bundle_loader.rb +13 -30
  46. data/lib/samagotchi/hooks/loader.rb +23 -33
  47. data/lib/samagotchi/hooks/registry.rb +41 -0
  48. data/lib/samagotchi/hooks.rb +0 -3
  49. data/lib/samagotchi/host_registry.rb +15 -0
  50. data/lib/samagotchi/idle_reminders.rb +0 -7
  51. data/lib/samagotchi/image_store.rb +22 -0
  52. data/lib/samagotchi/kernel_loop.rb +12 -16
  53. data/lib/samagotchi/live_versions.rb +2 -13
  54. data/lib/samagotchi/llm/native_tool_normalizer.rb +10 -227
  55. data/lib/samagotchi/log_subscriber.rb +5 -1
  56. data/lib/samagotchi/memory_bundle/builder.rb +1 -14
  57. data/lib/samagotchi/memory_bundle/index_sync.rb +73 -0
  58. data/lib/samagotchi/memory_bundle/index_updater.rb +2 -28
  59. data/lib/samagotchi/memory_bundle/installer.rb +25 -30
  60. data/lib/samagotchi/memory_bundle/provenance.rb +2 -6
  61. data/lib/samagotchi/memory_bundle/status.rb +1 -14
  62. data/lib/samagotchi/memory_bundle/system_bundle.rb +2 -2
  63. data/lib/samagotchi/memory_bundle/uninstaller.rb +1 -24
  64. data/lib/samagotchi/memory_paths.rb +10 -0
  65. data/lib/samagotchi/note_command.rb +23 -37
  66. data/lib/samagotchi/owner_lock.rb +14 -3
  67. data/lib/samagotchi/plugin/api.rb +11 -17
  68. data/lib/samagotchi/plugin/context.rb +70 -15
  69. data/lib/samagotchi/plugin/loader.rb +2 -3
  70. data/lib/samagotchi/sampling_settings.rb +3 -5
  71. data/lib/samagotchi/self_report.rb +6 -9
  72. data/lib/samagotchi/send_command.rb +33 -52
  73. data/lib/samagotchi/session.rb +88 -92
  74. data/lib/samagotchi/session_archive_command.rb +8 -14
  75. data/lib/samagotchi/session_commands.rb +3 -21
  76. data/lib/samagotchi/session_delete_command.rb +15 -29
  77. data/lib/samagotchi/session_manager.rb +29 -15
  78. data/lib/samagotchi/session_metrics.rb +2 -1
  79. data/lib/samagotchi/sessions_command.rb +3 -2
  80. data/lib/samagotchi/system_prompt.rb +2 -1
  81. data/lib/samagotchi/terminal_ui/attached_loop.rb +30 -80
  82. data/lib/samagotchi/terminal_ui/attached_view.rb +1 -10
  83. data/lib/samagotchi/terminal_ui/between_turns.rb +133 -0
  84. data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -1
  85. data/lib/samagotchi/terminal_ui/formatting.rb +51 -1
  86. data/lib/samagotchi/terminal_ui/status_row.rb +32 -0
  87. data/lib/samagotchi/terminal_ui/thinking_line.rb +3 -3
  88. data/lib/samagotchi/terminal_ui.rb +156 -234
  89. data/lib/samagotchi/thinking.rb +2 -3
  90. data/lib/samagotchi/thought_stream_splitter.rb +10 -9
  91. data/lib/samagotchi/tool_call_parser.rb +56 -292
  92. data/lib/samagotchi/tool_declarations.rb +1 -1
  93. data/lib/samagotchi/tool_runner.rb +8 -0
  94. data/lib/samagotchi/tools/builtin_calls.rb +146 -0
  95. data/lib/samagotchi/tools/builtins.rb +2 -1
  96. data/lib/samagotchi/tools/edit.rb +21 -29
  97. data/lib/samagotchi/tools/memory.rb +2 -13
  98. data/lib/samagotchi/tools/send_note.rb +1 -1
  99. data/lib/samagotchi/update_command.rb +32 -22
  100. data/lib/samagotchi/version.rb +1 -1
  101. data/lib/samagotchi/vision_support.rb +4 -5
  102. data/lib/samagotchi/web/app.rb +89 -151
  103. data/lib/samagotchi/web/public/activity.js +1 -11
  104. data/lib/samagotchi/web/public/annotate_bar.js +120 -0
  105. data/lib/samagotchi/web/public/app.js +158 -785
  106. data/lib/samagotchi/web/public/chunk_router.js +7 -6
  107. data/lib/samagotchi/web/public/command_list.js +99 -0
  108. data/lib/samagotchi/web/public/data.js +22 -66
  109. data/lib/samagotchi/web/public/format.js +0 -23
  110. data/lib/samagotchi/web/public/index.html +0 -2
  111. data/lib/samagotchi/web/public/model_pick.js +0 -7
  112. data/lib/samagotchi/web/public/notifications.js +169 -0
  113. data/lib/samagotchi/web/public/question_cards.js +352 -0
  114. data/lib/samagotchi/web/public/session_state.js +34 -0
  115. data/lib/samagotchi/web/public/timing.js +1 -1
  116. data/lib/samagotchi/web/public/turn_events.js +11 -2
  117. data/lib/samagotchi/web/public/turn_model.js +0 -14
  118. data/lib/samagotchi/web/session_hub.rb +6 -4
  119. data/lib/samagotchi/web/session_summary.rb +5 -3
  120. data/lib/samagotchi/worker.rb +59 -53
  121. data/lib/samagotchi/worker_sidecar.rb +27 -1
  122. metadata +13 -1
@@ -0,0 +1,120 @@
1
+ // ── Annotate ───────────────────────────────────────────────────────────────
2
+ // Selecting text in an answer, thinking, a tool row or an own message shows
3
+ // "Annotate" and the presets (web.annotate_presets); Annotate appends the
4
+ // quote (with where it came from) to the composer, a preset the quote with
5
+ // its text as the note. Nothing is sent. The bar lives outside #history,
6
+ // which is re-rendered whole. The selection is read when it changes: a live
7
+ // bubble rewrites its text every frame, which would collapse it by the time
8
+ // a button is clicked.
9
+ //
10
+ // The quoting rules are annotations.js'; this is the bar.
11
+
12
+ import { annotateBarLeft, annotationSource, quoteBlock } from "./annotations.js";
13
+ import { presetNote } from "./annotate_presets.js";
14
+
15
+ // @param presets the preset texts (annotate_presets.js parsePresets)
16
+ // @param canAnnotate () => whether the open session takes annotations (one
17
+ // is open and the composer can send)
18
+ // @param roots () => the elements a selection may start in (the history,
19
+ // the stage)
20
+ // @param isLive (root) => whether a source root is still streaming (refused
21
+ // until its step closes)
22
+ // @param onQuote (text) runs with the quote (and a preset's note) to append
23
+ // to the composer; the selection is cleared and the bar hidden first
24
+ // @param doc, win the page's document and window
25
+ // @return {schedule(), hide(), el}; schedule() re-reads the selection on the
26
+ // next frame (call it on scroll)
27
+ export function createAnnotateBar({
28
+ presets, canAnnotate, roots, isLive, onQuote, doc = globalThis.document, win = globalThis.window,
29
+ }) {
30
+ const bar = doc.createElement("div");
31
+ bar.id = "annotateBar";
32
+ bar.hidden = true;
33
+ bar.setAttribute("role", "toolbar");
34
+ bar.setAttribute("aria-label", "Annotate");
35
+ function button(text, cls) {
36
+ const btn = doc.createElement("button");
37
+ btn.type = "button";
38
+ btn.className = cls;
39
+ btn.textContent = text;
40
+ return btn;
41
+ }
42
+ bar.appendChild(button("Annotate", "annotate-main"));
43
+ presets.forEach((text, i) => {
44
+ const btn = button(text, "annotate-preset");
45
+ btn.dataset.index = String(i);
46
+ btn.title = text;
47
+ bar.appendChild(btn);
48
+ });
49
+ doc.body.appendChild(bar);
50
+ let pendingQuote = null;
51
+
52
+ function hide() {
53
+ pendingQuote = null;
54
+ bar.hidden = true;
55
+ }
56
+
57
+ function update() {
58
+ const sel = win.getSelection();
59
+ if (!canAnnotate() || !sel || sel.isCollapsed || !sel.rangeCount) return hide();
60
+ const range = sel.getRangeAt(0);
61
+ if (!roots().some((root) => root.contains(range.startContainer))) return hide();
62
+ const source = annotationSource(range.startContainer);
63
+ // A running turn's narration box shows complete sentences and its thinking
64
+ // is appended by delta; a selection in either is refused while the step is
65
+ // live — the quote lands once it closes (then on the full text).
66
+ if (!source || isLive(source.root)) return hide();
67
+ // Clip to where the selection starts: one tool row, one bubble.
68
+ const clipped = range.cloneRange();
69
+ if (!source.root.contains(range.endContainer)) clipped.setEnd(source.root, source.root.childNodes.length);
70
+ const block = quoteBlock(clipped.toString(), source);
71
+ if (!block) return hide();
72
+ pendingQuote = block;
73
+ const rects = clipped.getClientRects();
74
+ const rect = rects.length ? rects[rects.length - 1] : clipped.getBoundingClientRect();
75
+ bar.hidden = false;
76
+ // Measured at the left edge: a fixed element's shrink-to-fit width is
77
+ // capped by what's right of it, so at the last (right) spot it would
78
+ // measure wrapped.
79
+ bar.style.left = "8px";
80
+ const w = bar.offsetWidth;
81
+ const h = bar.offsetHeight;
82
+ const maxTop = win.innerHeight - h - 8;
83
+ const top = Math.min(rect.bottom + 6, maxTop);
84
+ bar.style.top = `${Math.max(top, 8)}px`;
85
+ bar.style.left = `${annotateBarLeft(rect, w, win.innerWidth)}px`;
86
+ // Not over a copy button (this bubble's, one inside it, the next
87
+ // bubble's).
88
+ const box = bar.getBoundingClientRect();
89
+ const bubble = source.root.closest(".bubble");
90
+ const near = [bubble, bubble?.nextElementSibling].filter(Boolean);
91
+ for (const btn of near.flatMap((el) => [...el.querySelectorAll(".copy-btn")])) {
92
+ const r = btn.getBoundingClientRect();
93
+ if (r.bottom < box.top || r.top > box.bottom || r.right < box.left || r.left > box.right) continue;
94
+ bar.style.top = `${Math.max(Math.min(r.bottom + 4, maxTop), 8)}px`;
95
+ break;
96
+ }
97
+ }
98
+
99
+ let raf = false;
100
+ function schedule() {
101
+ if (raf) return;
102
+ raf = true;
103
+ win.requestAnimationFrame(() => { raf = false; update(); });
104
+ }
105
+ doc.addEventListener("selectionchange", schedule);
106
+ win.addEventListener("resize", schedule);
107
+ // Keep the selection when a button is pressed.
108
+ bar.addEventListener("mousedown", (e) => e.preventDefault());
109
+ bar.addEventListener("click", (e) => {
110
+ const btn = e.target.closest("button");
111
+ if (!btn || !pendingQuote) return;
112
+ const preset = btn.dataset.index === undefined ? null : presets[Number(btn.dataset.index)];
113
+ const text = preset ? presetNote(pendingQuote, preset) : pendingQuote;
114
+ win.getSelection()?.removeAllRanges();
115
+ hide();
116
+ onQuote(text);
117
+ });
118
+
119
+ return { schedule, hide, el: bar };
120
+ }