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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +89 -1
- data/docs/architecture.md +24 -9
- data/docs/cli.md +4 -3
- data/docs/guardrails.md +9 -2
- data/docs/hooks.md +44 -7
- data/docs/internals/gemma4-contract.md +1 -0
- data/docs/memory.md +5 -3
- data/docs/plugins.md +47 -17
- data/docs/sessions.md +1 -1
- data/lib/samagotchi/bootstrap_command.rb +29 -29
- data/lib/samagotchi/bridge/card_store.rb +4 -4
- data/lib/samagotchi/bridge/pending_card.rb +2 -2
- data/lib/samagotchi/bridge/sse_writer.rb +22 -28
- data/lib/samagotchi/bridge/turn_accumulator.rb +92 -5
- data/lib/samagotchi/bridge.rb +59 -112
- data/lib/samagotchi/bridge_client.rb +11 -28
- data/lib/samagotchi/bundle_command.rb +158 -212
- data/lib/samagotchi/bundles/check-in/manifest.yml +2 -2
- data/lib/samagotchi/bundles/check-in/plugin.rb +3 -4
- data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +7 -3
- data/lib/samagotchi/bundles/known-names/manifest.yml +2 -2
- data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
- data/lib/samagotchi/bundles/loop-guard/plugin.rb +10 -10
- data/lib/samagotchi/bundles/mcp/manifest.yml +3 -3
- data/lib/samagotchi/bundles/mcp/plugin.rb +2 -4
- data/lib/samagotchi/bundles/skills/manifest.yml +3 -3
- data/lib/samagotchi/bundles/skills/plugin.rb +65 -17
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +0 -1
- data/lib/samagotchi/bundles/source-links/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +2 -2
- data/lib/samagotchi/bundles/system/identity.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +5 -4
- data/lib/samagotchi/cli/command.rb +51 -0
- data/lib/samagotchi/cli/flags.rb +116 -0
- data/lib/samagotchi/config.rb +70 -55
- data/lib/samagotchi/config_text_edit.rb +107 -0
- data/lib/samagotchi/desktop_command.rb +24 -18
- data/lib/samagotchi/edit_preview.rb +2 -2
- data/lib/samagotchi/engine.rb +20 -75
- data/lib/samagotchi/events.rb +23 -0
- data/lib/samagotchi/guardrail_wiring.rb +2 -5
- data/lib/samagotchi/guardrails/targets.rb +7 -1
- data/lib/samagotchi/hooks/bundle_loader.rb +13 -30
- data/lib/samagotchi/hooks/loader.rb +23 -33
- data/lib/samagotchi/hooks/registry.rb +41 -0
- data/lib/samagotchi/hooks.rb +0 -3
- data/lib/samagotchi/host_registry.rb +15 -0
- data/lib/samagotchi/idle_reminders.rb +0 -7
- data/lib/samagotchi/image_store.rb +22 -0
- data/lib/samagotchi/kernel_loop.rb +12 -16
- data/lib/samagotchi/live_versions.rb +2 -13
- data/lib/samagotchi/llm/native_tool_normalizer.rb +10 -227
- data/lib/samagotchi/log_subscriber.rb +5 -1
- data/lib/samagotchi/memory_bundle/builder.rb +1 -14
- data/lib/samagotchi/memory_bundle/index_sync.rb +73 -0
- data/lib/samagotchi/memory_bundle/index_updater.rb +2 -28
- data/lib/samagotchi/memory_bundle/installer.rb +25 -30
- data/lib/samagotchi/memory_bundle/provenance.rb +2 -6
- data/lib/samagotchi/memory_bundle/status.rb +1 -14
- data/lib/samagotchi/memory_bundle/system_bundle.rb +2 -2
- data/lib/samagotchi/memory_bundle/uninstaller.rb +1 -24
- data/lib/samagotchi/memory_paths.rb +10 -0
- data/lib/samagotchi/note_command.rb +23 -37
- data/lib/samagotchi/owner_lock.rb +14 -3
- data/lib/samagotchi/plugin/api.rb +11 -17
- data/lib/samagotchi/plugin/context.rb +70 -15
- data/lib/samagotchi/plugin/loader.rb +2 -3
- data/lib/samagotchi/sampling_settings.rb +3 -5
- data/lib/samagotchi/self_report.rb +6 -9
- data/lib/samagotchi/send_command.rb +33 -52
- data/lib/samagotchi/session.rb +88 -92
- data/lib/samagotchi/session_archive_command.rb +8 -14
- data/lib/samagotchi/session_commands.rb +3 -21
- data/lib/samagotchi/session_delete_command.rb +15 -29
- data/lib/samagotchi/session_manager.rb +29 -15
- data/lib/samagotchi/session_metrics.rb +2 -1
- data/lib/samagotchi/sessions_command.rb +3 -2
- data/lib/samagotchi/system_prompt.rb +2 -1
- data/lib/samagotchi/terminal_ui/attached_loop.rb +30 -80
- data/lib/samagotchi/terminal_ui/attached_view.rb +1 -10
- data/lib/samagotchi/terminal_ui/between_turns.rb +133 -0
- data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -1
- data/lib/samagotchi/terminal_ui/formatting.rb +51 -1
- data/lib/samagotchi/terminal_ui/status_row.rb +32 -0
- data/lib/samagotchi/terminal_ui/thinking_line.rb +3 -3
- data/lib/samagotchi/terminal_ui.rb +156 -234
- data/lib/samagotchi/thinking.rb +2 -3
- data/lib/samagotchi/thought_stream_splitter.rb +10 -9
- data/lib/samagotchi/tool_call_parser.rb +56 -292
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tool_runner.rb +8 -0
- data/lib/samagotchi/tools/builtin_calls.rb +146 -0
- data/lib/samagotchi/tools/builtins.rb +2 -1
- data/lib/samagotchi/tools/edit.rb +21 -29
- data/lib/samagotchi/tools/memory.rb +2 -13
- data/lib/samagotchi/tools/send_note.rb +1 -1
- data/lib/samagotchi/update_command.rb +32 -22
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +4 -5
- data/lib/samagotchi/web/app.rb +89 -151
- data/lib/samagotchi/web/public/activity.js +1 -11
- data/lib/samagotchi/web/public/annotate_bar.js +120 -0
- data/lib/samagotchi/web/public/app.js +158 -785
- data/lib/samagotchi/web/public/chunk_router.js +7 -6
- data/lib/samagotchi/web/public/command_list.js +99 -0
- data/lib/samagotchi/web/public/data.js +22 -66
- data/lib/samagotchi/web/public/format.js +0 -23
- data/lib/samagotchi/web/public/index.html +0 -2
- data/lib/samagotchi/web/public/model_pick.js +0 -7
- data/lib/samagotchi/web/public/notifications.js +169 -0
- data/lib/samagotchi/web/public/question_cards.js +352 -0
- data/lib/samagotchi/web/public/session_state.js +34 -0
- data/lib/samagotchi/web/public/timing.js +1 -1
- data/lib/samagotchi/web/public/turn_events.js +11 -2
- data/lib/samagotchi/web/public/turn_model.js +0 -14
- data/lib/samagotchi/web/session_hub.rb +6 -4
- data/lib/samagotchi/web/session_summary.rb +5 -3
- data/lib/samagotchi/worker.rb +59 -53
- data/lib/samagotchi/worker_sidecar.rb +27 -1
- 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
|
+
}
|