samagotchi 0.25.0 → 0.26.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 +27 -1
- data/docs/configuration.md +1 -0
- data/docs/internals/engine.md +478 -0
- data/docs/internals/prompt-caching.md +33 -7
- data/docs/plugins.md +3 -2
- data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
- data/lib/samagotchi/bundles/loop-guard/plugin.rb +17 -5
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/cancellation_controller.rb +8 -0
- data/lib/samagotchi/client.rb +53 -3
- data/lib/samagotchi/config.rb +9 -2
- data/lib/samagotchi/engine.rb +50 -7
- data/lib/samagotchi/kernel_loop.rb +39 -3
- data/lib/samagotchi/llm/chat_loop.rb +1 -1
- data/lib/samagotchi/llm/http.rb +5 -4
- data/lib/samagotchi/prompt_warmup.rb +95 -0
- data/lib/samagotchi/terminal_ui.rb +2 -0
- data/lib/samagotchi/thinking_tails.rb +3 -2
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/turn_note.rb +26 -2
- data/lib/samagotchi/turn_state.rb +6 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/command_view.js +6 -3
- data/lib/samagotchi/web/public/index.html +20 -13
- data/lib/samagotchi/worker.rb +3 -0
- metadata +3 -1
|
@@ -5,14 +5,20 @@
|
|
|
5
5
|
<title>Chi</title>
|
|
6
6
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7
7
|
<style>
|
|
8
|
-
:root{
|
|
8
|
+
:root{color-scheme:dark;--bg:#0b0d11;--bg-panel:#151821;--bg-inset:#0f1115;--bg-raised:#22262f;--bg-hover:#1b1f27;--border:#1f232b;--border-strong:#2d333f;--text:#e6e8eb;--text-bright:#f0f3f6;--text-2:#c9d1d9;--text-3:#9aa4b2;--text-muted:#8b949e;--text-faint:#6e7681;--accent:#7aa5ff;--accent-strong:#1f6feb;--accent-bg:#1b2336;--accent-ring:rgba(122,165,255,.18);--primary:#2a68d1;--ok:#3fb950;--ok-text:#7ee787;--ok-bg:#1a2e1a;--warn:#d29922;--warn-text:#e3b341;--warn-bright:#f2cc60;--warn-bg:#2b2108;--warn-border:#6b5416;--danger:#f85149;--danger-bg:#da3633;--danger-text:#ff7b72;--danger-bg-dim:#3d1f1f;--think:#8957e5;--think-text:#d2a8ff;--accent-border:#223150;--accent-tint:color-mix(in srgb,var(--accent) 7%,transparent);--narr:color-mix(in srgb,var(--accent) 38%,var(--text-muted));
|
|
9
9
|
--glass-bg:color-mix(in srgb,var(--bg-panel) 70%,transparent);--glass-border:rgba(255,255,255,.08);--glass-highlight:rgba(255,255,255,.05);--glass-shadow:0 8px 30px rgba(0,0,0,.35);
|
|
10
|
-
--diff-add-bg:rgba(63,185,80,.14);--diff-add-text:#aff5b4;--diff-del-bg:rgba(248,81,73,.14);--diff-del-text:#ffc1bc
|
|
10
|
+
--diff-add-bg:rgba(63,185,80,.14);--diff-add-text:#aff5b4;--diff-del-bg:rgba(248,81,73,.14);--diff-del-text:#ffc1bc;
|
|
11
|
+
--card-hover-border:rgba(255,255,255,.14);--on-solid:#fff;--on-warn:#fff;--text-hover:#fff;--badge-bg:rgba(255,255,255,.08);--badge-ring:rgba(255,255,255,.18);--shadow-strong:rgba(0,0,0,.5);--backdrop:rgba(0,0,0,.85);--toast-shadow:0 6px 24px rgba(0,0,0,.25)}
|
|
12
|
+
/* Light palette: follows the system setting. :not([data-theme="dark"]) leaves room for a later in-page toggle (data-theme on <html>); there is none yet. Each palette sets color-scheme, so native radios, inputs and scrollbars follow it. */
|
|
13
|
+
@media (prefers-color-scheme:light){:root:not([data-theme="dark"]){color-scheme:light;--bg:#f5f6f8;--bg-panel:#ffffff;--bg-inset:#f1f3f6;--bg-raised:#e8ebf0;--bg-hover:#eceff4;--border:#dde1e7;--border-strong:#c3c9d2;--text:#1f2328;--text-bright:#0d1117;--text-2:#2f363d;--text-3:#475160;--text-muted:#57606a;--text-faint:#636c76;--accent:#0b5cd5;--accent-strong:#0550ae;--accent-bg:#e2ecfd;--accent-ring:rgba(11,92,213,.2);--primary:#1f6feb;--ok:#1a7f37;--ok-text:#116329;--ok-bg:#dafbe1;--warn:#9a6700;--warn-text:#7d4e00;--warn-bright:#875c00;--warn-bg:#fff6cc;--warn-border:#d4a72c;--danger:#cf222e;--danger-bg:#cf222e;--danger-text:#a40e26;--danger-bg-dim:#ffebe9;--think:#8250df;--think-text:#6639ba;--accent-border:#b8cef5;
|
|
14
|
+
--glass-border:rgba(31,35,40,.10);--glass-highlight:rgba(255,255,255,.7);--glass-shadow:0 8px 30px rgba(31,35,40,.10);
|
|
15
|
+
--diff-add-bg:rgba(26,127,55,.12);--diff-add-text:#116329;--diff-del-bg:rgba(207,34,46,.10);--diff-del-text:#82071e;
|
|
16
|
+
--card-hover-border:rgba(31,35,40,.18);--on-solid:#fff;--on-warn:#1f2328;--text-hover:#0d1117;--badge-bg:rgba(31,35,40,.06);--badge-ring:rgba(31,35,40,.15);--shadow-strong:rgba(31,35,40,.18);--backdrop:rgba(13,17,23,.85);--toast-shadow:0 6px 24px rgba(31,35,40,.14)}}
|
|
11
17
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
12
18
|
body{font-family: ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:radial-gradient(1100px 700px at 8% 0%,color-mix(in srgb,var(--accent) 13%,transparent),transparent 70%),radial-gradient(900px 700px at 100% 100%,color-mix(in srgb,var(--think) 9%,transparent),transparent 70%),var(--bg);background-attachment:fixed;color:var(--text);height:100vh;display:flex;flex-direction:column}
|
|
13
19
|
/* Glass: the composer card, the strip cards, the all-sessions cards and the question cards (by selector, so app.js is untouched). */
|
|
14
20
|
#dock,.turn-stage,.dock-card,#topStrip .card,#allList .card,.bubble.question,.bubble.plugin-card{background:var(--glass-bg);-webkit-backdrop-filter:blur(18px) saturate(1.35);backdrop-filter:blur(18px) saturate(1.35);border:1px solid var(--glass-border);box-shadow:inset 0 1px 0 var(--glass-highlight),var(--glass-shadow)}
|
|
15
|
-
#topStrip .card:hover,#allList .card:hover{background:color-mix(in srgb,var(--bg-hover) 75%,transparent);border-color:
|
|
21
|
+
#topStrip .card:hover,#allList .card:hover{background:color-mix(in srgb,var(--bg-hover) 75%,transparent);border-color:var(--card-hover-border)}
|
|
16
22
|
#topStrip .card.active,#allList .card.active{background:color-mix(in srgb,var(--accent-bg) 75%,transparent);border-color:color-mix(in srgb,var(--accent) 35%,transparent)}
|
|
17
23
|
/* A session that waits on the user (a question, an approval, a card with actions): the warning colour of its question card, also when active or hovered. */
|
|
18
24
|
#topStrip .card.waiting,#allList .card.waiting{border-color:color-mix(in srgb,var(--warn) 75%,transparent)}
|
|
@@ -23,7 +29,7 @@ body.no-glass #dock,body.no-glass .turn-stage,body.no-glass .dock-card,body.no-g
|
|
|
23
29
|
body.no-glass .bubble.question{background:color-mix(in srgb,var(--warn) 7%,var(--bg-panel));-webkit-backdrop-filter:none;backdrop-filter:none}
|
|
24
30
|
body.no-glass .bubble.plugin-card{background:color-mix(in srgb,var(--accent) 6%,var(--bg-panel));-webkit-backdrop-filter:none;backdrop-filter:none}
|
|
25
31
|
button,input,textarea,select{font:inherit}
|
|
26
|
-
button{background:var(--primary);border:0;color
|
|
32
|
+
button{background:var(--primary);border:0;color:var(--on-solid);padding:8px 12px;border-radius:8px;cursor:pointer;transition:filter .12s ease,background .12s ease}
|
|
27
33
|
button:hover:not(:disabled){filter:brightness(1.12)}
|
|
28
34
|
button:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
29
35
|
button.ghost{background:var(--bg-raised);color:var(--text-2)}
|
|
@@ -91,8 +97,8 @@ main{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px;overflow:au
|
|
|
91
97
|
.bubble.user.queued{border-left-style:dashed;opacity:.8}
|
|
92
98
|
.bubble.user.steered{border-left-style:dashed;border-left-color:var(--accent);opacity:.9}
|
|
93
99
|
.bubble.user.failed{border-left-color:var(--danger);opacity:.8}
|
|
94
|
-
.bubble.user .state-badge{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:2px 7px;border-radius:999px;background:
|
|
95
|
-
.bubble.user .state-badge::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:currentColor;opacity:.8;box-shadow:0 0 0 1px
|
|
100
|
+
.bubble.user .state-badge{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:2px 7px;border-radius:999px;background:var(--badge-bg);color:var(--text);font-size:11px;line-height:1.2;white-space:nowrap;flex-shrink:0}
|
|
101
|
+
.bubble.user .state-badge::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:currentColor;opacity:.8;box-shadow:0 0 0 1px var(--badge-ring)}
|
|
96
102
|
.bubble.user.queued .state-badge::before{background:var(--accent);box-shadow:0 0 0 1px var(--accent-ring)}
|
|
97
103
|
.bubble.user.steered .state-badge::before{background:var(--accent);box-shadow:0 0 0 1px var(--accent-ring)}
|
|
98
104
|
.bubble.user.failed .state-badge::before{background:var(--danger);box-shadow:0 0 0 1px color-mix(in srgb,var(--danger) 40%,transparent)}
|
|
@@ -145,6 +151,7 @@ main{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px;overflow:au
|
|
|
145
151
|
/* The command as its steps (CommandSteps): the op that links each to the one before, labels as small headings, limits and heredocs as chips; "raw" swaps in the command as written (CSS only). */
|
|
146
152
|
.activity-command.has-steps{background:var(--bg-inset);border:1px solid var(--border);border-radius:6px;padding:4px 8px 5px}
|
|
147
153
|
.activity-command-head{display:flex;align-items:center;gap:8px;min-height:20px;padding-right:30px}
|
|
154
|
+
.activity-command-head.bare{float:right;min-height:0;margin-left:8px}
|
|
148
155
|
.has-steps .activity-command-cwd{flex:1 1 auto;min-width:0;margin:0;overflow-wrap:anywhere}
|
|
149
156
|
.activity-command-cwd>.activity-command-sep{font-family:inherit;color:var(--text-faint)}
|
|
150
157
|
.activity-command-raw{flex:none;display:inline-flex;align-items:center;height:18px;padding:0 7px;border:1px solid var(--border-strong);border-radius:9px;font-size:11px;color:var(--text-muted);cursor:pointer;user-select:none;-webkit-user-select:none}
|
|
@@ -366,7 +373,7 @@ main{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px;overflow:au
|
|
|
366
373
|
/* The selection bubble (app.js Annotate): Annotate + the presets (web.annotate_presets), small pills with a gap (they wrap on a narrow page). */
|
|
367
374
|
#annotateBar{position:fixed;z-index:20;display:flex;flex-wrap:wrap;gap:4px;max-width:calc(100vw - 16px)}
|
|
368
375
|
#annotateBar[hidden]{display:none}
|
|
369
|
-
#annotateBar button{font-size:12px;padding:4px 10px;border-radius:999px;box-shadow:0 2px 8px
|
|
376
|
+
#annotateBar button{font-size:12px;padding:4px 10px;border-radius:999px;box-shadow:0 2px 8px var(--shadow-strong)}
|
|
370
377
|
#annotateBar .annotate-preset{background:var(--bg-raised);color:var(--text-2);border:1px solid var(--border-strong);padding:3px 10px;max-width:32ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
371
378
|
/* Copy buttons (copy.js): top-right in an answer and a code block, left of a prompt (its right edge is the page's); shown on hover or focus, always (dim) on touch. They hold no text: "copied ✓" is CSS content. */
|
|
372
379
|
.bubble.output,.bubble.user{position:relative}
|
|
@@ -407,7 +414,7 @@ body.has-session #history{padding-bottom:calc(var(--dock-h,160px) + 32px)}
|
|
|
407
414
|
.thumbs{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
|
|
408
415
|
.thumb{max-height:120px;max-width:200px;border-radius:6px;border:1px solid var(--border-strong);cursor:zoom-in;object-fit:contain;background:var(--bg)}
|
|
409
416
|
.activity-row .thumbs{flex-basis:100%}
|
|
410
|
-
#lightbox{position:fixed;inset:0;z-index:50;background:
|
|
417
|
+
#lightbox{position:fixed;inset:0;z-index:50;background:var(--backdrop);display:flex;align-items:center;justify-content:center;cursor:zoom-out}
|
|
411
418
|
#lightbox[hidden]{display:none}
|
|
412
419
|
#lightbox img{max-width:96vw;max-height:94vh;border-radius:6px}
|
|
413
420
|
#composer textarea{flex:1 1 auto;min-height:72px;resize:none;background:transparent;border:0;color:var(--text);padding:2px;border-radius:0;font-size:15px;line-height:1.45}
|
|
@@ -598,7 +605,7 @@ body.stage-view #dock,body.stage-view #dock:focus-within{background:transparent;
|
|
|
598
605
|
#infoBar .meta{font-size:11px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
|
599
606
|
#infoBar .meta .id{font-family:ui-monospace,monospace;font-weight:700;font-size:12px;color:var(--accent)}
|
|
600
607
|
#infoBar .meta .copy-attach{padding:2px 8px;border-radius:6px;font-size:11px;font-weight:600;background:var(--bg-raised);color:var(--text-2);border:1px solid var(--border-strong)}
|
|
601
|
-
#infoBar .meta .copy-attach:hover{color
|
|
608
|
+
#infoBar .meta .copy-attach:hover{color:var(--text-hover);border-color:var(--border-strong)}
|
|
602
609
|
#infoBar .meta .status{font-size:11px;padding:2px 6px;border-radius:999px}
|
|
603
610
|
#infoBar .meta .dir{font-family:ui-monospace,monospace;font-size:11px;color:var(--text-muted);word-break:break-all}
|
|
604
611
|
/* The footer sits on the card's glass: its chips are tinted, not solid, so the glass reads through them. */
|
|
@@ -610,7 +617,7 @@ body.stage-view #dock,body.stage-view #dock:focus-within{background:transparent;
|
|
|
610
617
|
#infoBar .meta .worker-badge{display:inline-flex;align-items:center;gap:6px;font-size:11px;padding:2px 2px 2px 6px;border-radius:6px;background:var(--warn-bg);color:var(--warn-bright);border:1px solid var(--warn-border);white-space:nowrap}
|
|
611
618
|
#infoBar .meta .worker-badge:not(:has(button)){padding-right:6px}
|
|
612
619
|
#infoBar .meta .worker-restart{padding:1px 8px;border-radius:5px;font-size:11px;font-weight:600;background:var(--bg-raised);color:var(--warn-bright);border:1px solid var(--warn-border)}
|
|
613
|
-
#infoBar .meta .worker-restart:hover:not(:disabled){background:var(--warn-border);color
|
|
620
|
+
#infoBar .meta .worker-restart:hover:not(:disabled){background:var(--warn-border);color:var(--on-warn)}
|
|
614
621
|
#infoBar .meta .worker-restart:disabled{opacity:.7;cursor:default}
|
|
615
622
|
#infoBar .preview-line{font-size:13px;word-break:break-word;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden;line-height:1.4;max-height:2.8em}
|
|
616
623
|
#infoBar .preview-line .preview, #infoBar .preview-line .mem{word-break:break-word}
|
|
@@ -621,7 +628,7 @@ body.stage-view #dock,body.stage-view #dock:focus-within{background:transparent;
|
|
|
621
628
|
#infoBar #infoArchiveBtn{background:transparent;color:var(--text-muted);border:1px solid var(--border-strong)}
|
|
622
629
|
#infoBar #infoArchiveBtn:hover{color:var(--text);background:var(--bg-hover)}
|
|
623
630
|
#infoBar #infoDeleteBtn{background:transparent;color:var(--text-muted);border:1px solid transparent}
|
|
624
|
-
#infoBar #infoDeleteBtn:hover{color
|
|
631
|
+
#infoBar #infoDeleteBtn:hover{color:var(--on-solid);background:var(--danger-bg);border-color:var(--danger-bg)}
|
|
625
632
|
/* Phones: the footer is one line; its meta scrolls sideways (all of it stays reachable) and the first-message line, the strip card's title, goes. */
|
|
626
633
|
@media(max-width:600px){
|
|
627
634
|
/* The key hint: cut off at this width, and phones have no Shift+Enter. */
|
|
@@ -743,7 +750,7 @@ body.all-view #center, body.all-view #topStrip{display:none}
|
|
|
743
750
|
.card.archived{opacity:.6}
|
|
744
751
|
.card.archived:hover{opacity:.85}
|
|
745
752
|
.card .archived-badge{font-size:10px;padding:1px 6px;border-radius:6px;border:1px solid var(--border-strong);color:var(--text-muted)}
|
|
746
|
-
#toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);width:max-content;max-width:calc(100vw - 32px);box-sizing:border-box;z-index:50;display:flex;gap:12px;align-items:center;padding:8px 14px;border-radius:10px;background:var(--bg-raised);color:var(--text);border:1px solid var(--border-strong);box-shadow:
|
|
753
|
+
#toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);width:max-content;max-width:calc(100vw - 32px);box-sizing:border-box;z-index:50;display:flex;gap:12px;align-items:center;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}
|
|
747
754
|
#toast[hidden]{display:none}
|
|
748
755
|
#toast .toast-action{background:transparent;border:0;color:var(--accent);font-weight:600;cursor:pointer;padding:2px 4px}
|
|
749
756
|
#toast .toast-close{background:transparent;border:0;color:var(--text-muted);cursor:pointer;padding:2px 4px;margin-left:-6px}
|
|
@@ -761,7 +768,7 @@ body.all-view #center, body.all-view #topStrip{display:none}
|
|
|
761
768
|
#allList .card{position:relative}
|
|
762
769
|
.card-del{position:absolute;top:6px;right:6px;padding:2px 7px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--text-muted);font-size:12px;line-height:1.2;cursor:pointer;opacity:0;transition:opacity .15s}
|
|
763
770
|
#allList .card:hover .card-del, .card-del:focus-visible{opacity:1}
|
|
764
|
-
.card-del:hover{color
|
|
771
|
+
.card-del:hover{color:var(--on-solid);background:var(--danger-bg);border-color:var(--danger-bg)}
|
|
765
772
|
@media (hover:none){.card-del{opacity:1}}
|
|
766
773
|
.card.active{background:var(--accent-bg);border-color:var(--accent-border)}
|
|
767
774
|
.card .preview{font-size:14px;line-height:1.4;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -112,6 +112,9 @@ module Samagotchi
|
|
|
112
112
|
# stream gets the session's history and status in its snapshot, not
|
|
113
113
|
# an empty session until the first turn.
|
|
114
114
|
@engine.session = @session
|
|
115
|
+
# Input already waiting in the inbox starts the next turn at once: no
|
|
116
|
+
# turn-end warm-up then.
|
|
117
|
+
@engine.next_turn_waiting = -> { SessionInbox.find_new_input_files(@session_dir).any? }
|
|
115
118
|
@turn_flow = TurnFlow.new(engine: @engine)
|
|
116
119
|
# Its question's answer is queued as the matching /continue (the
|
|
117
120
|
# Bridge exists by the time anyone answers).
|
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.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -130,6 +130,7 @@ files:
|
|
|
130
130
|
- docs/hooks.md
|
|
131
131
|
- docs/internals/background-tasks.md
|
|
132
132
|
- docs/internals/context-telemetry.md
|
|
133
|
+
- docs/internals/engine.md
|
|
133
134
|
- docs/internals/gemma4-contract.md
|
|
134
135
|
- docs/internals/prompt-caching.md
|
|
135
136
|
- docs/internals/tool-guardrails.md
|
|
@@ -324,6 +325,7 @@ files:
|
|
|
324
325
|
- lib/samagotchi/prompt.rb
|
|
325
326
|
- lib/samagotchi/prompt_history.rb
|
|
326
327
|
- lib/samagotchi/prompt_literal_guard.rb
|
|
328
|
+
- lib/samagotchi/prompt_warmup.rb
|
|
327
329
|
- lib/samagotchi/question_desk.rb
|
|
328
330
|
- lib/samagotchi/recap_setup.rb
|
|
329
331
|
- lib/samagotchi/recap_store.rb
|