samagotchi 0.55.0 → 0.56.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 +30 -1
- data/docs/architecture.md +6 -2
- data/docs/cli.md +29 -7
- data/docs/configuration.md +84 -8
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/engine.md +14 -0
- data/docs/internals/prompt-caching.md +8 -0
- data/docs/sessions.md +1 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/cache_confirm.rb +51 -0
- data/lib/samagotchi/client_id.rb +12 -0
- data/lib/samagotchi/config.rb +7 -3
- data/lib/samagotchi/engine.rb +102 -8
- data/lib/samagotchi/engine_peers.rb +1 -0
- data/lib/samagotchi/host_config.rb +6 -3
- data/lib/samagotchi/host_registry.rb +4 -3
- data/lib/samagotchi/kernel_loop.rb +99 -16
- data/lib/samagotchi/llm/chat_loop.rb +122 -71
- data/lib/samagotchi/llm/openai_chat.rb +2 -1
- data/lib/samagotchi/llm/stream_drops.rb +63 -0
- data/lib/samagotchi/llm/turn_settings.rb +9 -5
- data/lib/samagotchi/model_settings.rb +5 -2
- data/lib/samagotchi/reasoning_history.rb +132 -0
- data/lib/samagotchi/reasoning_history_command.rb +107 -0
- data/lib/samagotchi/self_report.rb +21 -0
- data/lib/samagotchi/session.rb +23 -2
- data/lib/samagotchi/session_commands.rb +55 -15
- data/lib/samagotchi/session_manager.rb +2 -1
- data/lib/samagotchi/session_setup.rb +7 -5
- data/lib/samagotchi/setting_explained.rb +20 -0
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +2 -1
- data/lib/samagotchi/terminal_ui/attached_loop.rb +11 -0
- data/lib/samagotchi/terminal_ui/formatting.rb +8 -5
- data/lib/samagotchi/terminal_ui.rb +13 -6
- data/lib/samagotchi/thinking.rb +20 -25
- data/lib/samagotchi/thinking_command.rb +33 -11
- data/lib/samagotchi/tools/builtins.rb +6 -2
- data/lib/samagotchi/tools/peers.rb +4 -2
- data/lib/samagotchi/tools/task_runtime.rb +2 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +23 -2
- data/lib/samagotchi/web/public/app.js +20 -1
- data/lib/samagotchi/web/public/commands.js +36 -3
- data/lib/samagotchi/web/public/composer.js +5 -1
- data/lib/samagotchi/web/public/index.html +6 -3
- data/lib/samagotchi/web/public/info_bar.js +5 -3
- data/lib/samagotchi/web/public/level_chip.js +174 -0
- data/lib/samagotchi/web/public/model_pick.js +1 -1
- data/lib/samagotchi/web/public/reasoning_history_chip.js +49 -0
- data/lib/samagotchi/web/public/session_state.js +5 -0
- data/lib/samagotchi/web/public/stream_handlers.js +10 -2
- data/lib/samagotchi/web/public/thinking_chip.js +33 -138
- data/lib/samagotchi/worker.rb +17 -5
- metadata +8 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The session bar's reasoning history chip: which of its own reasoning the
|
|
2
|
+
// session's model gets back from the next turn on ("reasoning all";
|
|
3
|
+
// "· session" when the session set its own), and a popover with a level
|
|
4
|
+
// select that runs /reasoning-history in the session's worker. The summary
|
|
5
|
+
// is the session's reasoning_history (SettingExplained#summary; null on a
|
|
6
|
+
// native host, which ignores it, or from an older worker: no chip then).
|
|
7
|
+
// A config on level_chip.js.
|
|
8
|
+
|
|
9
|
+
import * as level from "./level_chip.js";
|
|
10
|
+
import { html, nothing } from "./vendor/lit-html.js";
|
|
11
|
+
|
|
12
|
+
export const LEVELS = ["drop", "turn", "all"];
|
|
13
|
+
|
|
14
|
+
export const CONFIG = Object.freeze({
|
|
15
|
+
name: "Reasoning history",
|
|
16
|
+
prefix: "reasoning",
|
|
17
|
+
command: "/reasoning-history",
|
|
18
|
+
levels: LEVELS,
|
|
19
|
+
anchor: "[data-rh-chip]",
|
|
20
|
+
formClass: "rh-form",
|
|
21
|
+
popoverClass: "rh-popover",
|
|
22
|
+
nowClass: "rh-now",
|
|
23
|
+
when: () => "From the next turn's start. A change to or from all makes the server read the conversation again.",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export function chipModel(summary) {
|
|
27
|
+
return level.chipModel(CONFIG, summary);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The session bar's chip (a lit-html template; info_bar.js), nothing
|
|
31
|
+
// without a summary.
|
|
32
|
+
export function chipTemplate(summary) {
|
|
33
|
+
const chip = chipModel(summary);
|
|
34
|
+
if (!chip) return nothing;
|
|
35
|
+
return html`<button type="button" class=${`model rh-chip${chip.own ? " own" : ""}`} title=${chip.title} data-rh-chip aria-haspopup="dialog">${chip.text}</button>`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function commandLine(choice, initial = null) {
|
|
39
|
+
return level.commandLine(CONFIG, choice, initial);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formTemplate(summary, opts) {
|
|
43
|
+
return level.formTemplate(CONFIG, summary, opts);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The chip's popover in the session bar (level_chip.js createLevelControl).
|
|
47
|
+
export function createReasoningHistoryControl({ id = "reasoningHistoryPopover", ...opts }) {
|
|
48
|
+
return level.createLevelControl({ cfg: CONFIG, id, ...opts });
|
|
49
|
+
}
|
|
@@ -45,6 +45,9 @@ export function emptySessionState() {
|
|
|
45
45
|
// The LLM context strategy, apply rule and budget the next turn runs
|
|
46
46
|
// under, each with its source (the info bar's llm ctx chip), or null.
|
|
47
47
|
llmContext: null,
|
|
48
|
+
// Which of its reasoning the model gets back from the next turn on, with
|
|
49
|
+
// its source (the reasoning history chip), or null (a native host).
|
|
50
|
+
reasoningHistory: null,
|
|
48
51
|
timing: normalizeTiming(),
|
|
49
52
|
};
|
|
50
53
|
}
|
|
@@ -78,6 +81,8 @@ export function sessionStateFrom(data) {
|
|
|
78
81
|
state.promptNotes = Array.isArray(s.prompt_notes) ? s.prompt_notes : [];
|
|
79
82
|
state.llmContext = s.llm_context || null;
|
|
80
83
|
state.thinking = s.thinking || null;
|
|
84
|
+
// null on a native host (it ignores the level) and from an older worker.
|
|
85
|
+
state.reasoningHistory = s.reasoning_history || null;
|
|
81
86
|
state.parentId = s.parent_id || null;
|
|
82
87
|
state.continues = s.continues || null;
|
|
83
88
|
state.commands = Array.isArray(data.commands) ? data.commands : [];
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
// capabilities() the page's capabilities (card.js shownElsewhere)
|
|
16
16
|
// historyEl the history element (keepFollowing, the failed-turn redraw)
|
|
17
17
|
// liveTurn, turnDom, shownTexts, questionCards, contextChips,
|
|
18
|
-
// llmContextControl, thinkingControl
|
|
18
|
+
// llmContextControl, thinkingControl,
|
|
19
|
+
// reasoningHistoryControl the page's views
|
|
19
20
|
// sentIds, earlyRestores restore.js's bookkeeping
|
|
20
21
|
// stream: {takeGuess(), restart()} the stream was opened from a
|
|
21
22
|
// guess (takeGuess: whether, and forget it); restart:
|
|
@@ -49,7 +50,7 @@ import { keepEarlyRestore, restoreAction } from "./restore.js";
|
|
|
49
50
|
export function createStreamHandlers(deps) {
|
|
50
51
|
const {
|
|
51
52
|
selected, state, clientId, capabilities, historyEl,
|
|
52
|
-
liveTurn, turnDom, shownTexts, questionCards, contextChips, llmContextControl, thinkingControl,
|
|
53
|
+
liveTurn, turnDom, shownTexts, questionCards, contextChips, llmContextControl, thinkingControl, reasoningHistoryControl,
|
|
53
54
|
sentIds, earlyRestores, stream, composer, getSession,
|
|
54
55
|
resync, updateInfoBar, applyPromptOps, showSteers, markRecapStale, setTurnRunning, setLiveStatus, endTurnView,
|
|
55
56
|
appendChunk, readTurnEnd, applyFinalMarkdown, refreshTimingFromSession, makeUserBubble, addEndBubble,
|
|
@@ -213,6 +214,13 @@ export function createStreamHandlers(deps) {
|
|
|
213
214
|
updateInfoBar();
|
|
214
215
|
thinkingControl.refresh();
|
|
215
216
|
}
|
|
217
|
+
// /reasoning-history (or /model) changed it; null: the model now runs
|
|
218
|
+
// on a native host, which ignores it (the chip goes).
|
|
219
|
+
if ("reasoning_history" in data) {
|
|
220
|
+
state().reasoningHistory = data.reasoning_history || null;
|
|
221
|
+
updateInfoBar();
|
|
222
|
+
reasoningHistoryControl.refresh();
|
|
223
|
+
}
|
|
216
224
|
if (view.resync) await resync();
|
|
217
225
|
// A card's action that isn't anytime (the step-limit question's
|
|
218
226
|
// /continue) has no bubble: nothing to remove. An anytime or queued
|
|
@@ -2,20 +2,35 @@
|
|
|
2
2
|
// low"; "· session" when the session set its own), and a popover with a
|
|
3
3
|
// level select that runs /thinking in the session's worker (the command's
|
|
4
4
|
// reply shows in the conversation, like a typed one). The level and where
|
|
5
|
-
// it came from are the session's thinking (
|
|
5
|
+
// it came from are the session's thinking (SettingExplained#summary:
|
|
6
6
|
// {level, source, own}; the worker's snapshot, a command_ran, or the
|
|
7
|
-
// server's own reading of the session file).
|
|
7
|
+
// server's own reading of the session file). A config on level_chip.js.
|
|
8
8
|
//
|
|
9
9
|
// The start page has one too (kind "new chat"): it shows the picked
|
|
10
10
|
// model's level (GET /api/models' thinking) and keeps a choice for the
|
|
11
11
|
// next create only (POST /api/sessions' thinking), never remembered:
|
|
12
12
|
// nothing is sent unless the user sets one.
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
14
|
+
import { SESSION } from "./llm_context_chip.js";
|
|
15
|
+
import * as level from "./level_chip.js";
|
|
16
|
+
import { html, nothing } from "./vendor/lit-html.js";
|
|
16
17
|
|
|
17
18
|
export const LEVELS = ["off", "low", "medium", "high"];
|
|
18
|
-
|
|
19
|
+
|
|
20
|
+
export const CONFIG = Object.freeze({
|
|
21
|
+
name: "Thinking",
|
|
22
|
+
prefix: "think",
|
|
23
|
+
command: "/thinking",
|
|
24
|
+
levels: LEVELS,
|
|
25
|
+
anchor: "[data-think-chip]",
|
|
26
|
+
formClass: "think-form",
|
|
27
|
+
popoverClass: "think-popover",
|
|
28
|
+
nowClass: "think-now",
|
|
29
|
+
when: (fresh) => (fresh ? "For the new chat's first turn on; not remembered for the next one."
|
|
30
|
+
: "From the next turn's start. On Gemma the whole prompt is read again; a hosted API reads the whole conversation again."),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const { nowText } = level;
|
|
19
34
|
|
|
20
35
|
// The new chat's choice (a level, or null) laid over the model's summary:
|
|
21
36
|
// what the chip shows. The model's own summary without a choice.
|
|
@@ -28,13 +43,8 @@ export function withChoice(summary, choice) {
|
|
|
28
43
|
// set its own; null without a summary (an older worker). The default with
|
|
29
44
|
// nothing set anywhere shows too ("think default"): the chip is where the
|
|
30
45
|
// level is set.
|
|
31
|
-
export function chipModel(summary,
|
|
32
|
-
|
|
33
|
-
const own = !!summary.own;
|
|
34
|
-
const what = kind === NEW_CHAT ? "the new chat" : "this session";
|
|
35
|
-
const from = own ? what : summary.source || "the model's own";
|
|
36
|
-
const title = `Thinking: ${summary.level} (${from}). Click to change it for ${what}.`;
|
|
37
|
-
return { text: `think ${summary.level}${own ? ` · ${kind}` : ""}`, title, own };
|
|
46
|
+
export function chipModel(summary, opts) {
|
|
47
|
+
return level.chipModel(CONFIG, summary, opts);
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
// The session bar's chip (a lit-html template; info_bar.js), nothing
|
|
@@ -45,134 +55,19 @@ export function chipTemplate(summary) {
|
|
|
45
55
|
return html`<button type="button" class=${`model think-chip${chip.own ? " own" : ""}`} title=${chip.title} data-think-chip aria-haspopup="dialog">${chip.text}</button>`;
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
// The /thinking line for a select value
|
|
49
|
-
//
|
|
50
|
-
export function commandLine(
|
|
51
|
-
|
|
52
|
-
return `/thinking ${level}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// "Now: low (session)", "Now: default (the model's own)".
|
|
56
|
-
export function nowText(summary) {
|
|
57
|
-
return `Now: ${summary.level} (${summary.source || "the model's own"})`;
|
|
58
|
+
// The /thinking line for a select value, with --yes (level_chip.js
|
|
59
|
+
// commandLine); null when it is what the form opened with.
|
|
60
|
+
export function commandLine(choice, initial = null) {
|
|
61
|
+
return level.commandLine(CONFIG, choice, initial);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// NEW_CHAT), +summary+ is the model's and +choice+ the level the form
|
|
64
|
-
// shows; +now+ is the summary its "Now:" line reads (refresh passes a
|
|
65
|
-
// newer one, the rest stays as the form opened).
|
|
66
|
-
export function formTemplate(summary, { running = false, kind = SESSION, choice = null, now = summary } = {}) {
|
|
67
|
-
const fresh = kind === NEW_CHAT;
|
|
68
|
-
const chosen = (fresh ? choice : summary.own) || DEFAULT;
|
|
69
|
-
const follow = summary.own && !fresh ? "follow the model" : `follow the model (${summary.level})`;
|
|
70
|
-
const when = fresh ? "For the new chat's first turn on; not remembered for the next one."
|
|
71
|
-
: "From the next turn's start. On Gemma the whole prompt is read again; a hosted API may restart its cache.";
|
|
72
|
-
return html`<form class="think-form llmctx-form"><div class="ctx-pop-head"><strong>Thinking</strong
|
|
73
|
-
><span class="ctx-pop-kind">${fresh ? NEW_CHAT : "this session"}</span
|
|
74
|
-
><button type="button" class="ctx-pop-close" data-act="close" aria-label="Close">×</button></div
|
|
75
|
-
><div class="ctx-pop-meta think-now">${nowText(now)}</div
|
|
76
|
-
><label class="llmctx-field"><span>Level</span><select name="level">${option(DEFAULT, follow, chosen)}${
|
|
77
|
-
LEVELS.map((level) => option(level, level, chosen))}</select></label
|
|
78
|
-
><div class="ctx-pop-meta">${when}${running ? " A turn is running: it applies after this turn." : ""}</div
|
|
79
|
-
><div class="ctx-pop-actions"><button type="submit" class="llmctx-set">Set</button></div></form>`;
|
|
64
|
+
// The popover's form (level_chip.js formTemplate).
|
|
65
|
+
export function formTemplate(summary, opts) {
|
|
66
|
+
return level.formTemplate(CONFIG, summary, opts);
|
|
80
67
|
}
|
|
81
68
|
|
|
82
|
-
// The chip's popover
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// NEW_CHAT): summary() is the picked model's, choice() the pending level
|
|
87
|
-
// and choose(next) takes a Set's (null for "follow the model"). id and
|
|
88
|
-
// anchor (the chip's selector) keep it apart from the llm ctx control on
|
|
89
|
-
// the same element.
|
|
90
|
-
export function createThinkingControl({ el, summary, session = () => null, running = () => false, run,
|
|
91
|
-
kind = SESSION, choice = () => null, choose = null,
|
|
92
|
-
id = "thinkingPopover", anchor: anchorSel = "[data-think-chip]",
|
|
93
|
-
doc = document, win = window }) {
|
|
94
|
-
const fresh = kind === NEW_CHAT;
|
|
95
|
-
const pop = doc.createElement("div");
|
|
96
|
-
pop.id = id;
|
|
97
|
-
pop.className = "ctx-popover think-popover";
|
|
98
|
-
pop.setAttribute("role", "dialog");
|
|
99
|
-
pop.hidden = true;
|
|
100
|
-
doc.body.appendChild(pop);
|
|
101
|
-
let initial = null;
|
|
102
|
-
let openFor = null;
|
|
103
|
-
// What the open form was drawn from: the model's or session's summary
|
|
104
|
-
// and the form's options. A closed popover is empty, so each open draws
|
|
105
|
-
// a fresh form (an unsent pick is gone).
|
|
106
|
-
let shown = null;
|
|
107
|
-
const draw = (now) => render(formTemplate(shown.data, { ...shown.opts, now }), pop);
|
|
108
|
-
|
|
109
|
-
function place(anchor) {
|
|
110
|
-
const width = Math.min(320, win.innerWidth - 32);
|
|
111
|
-
pop.style.width = `${width}px`;
|
|
112
|
-
pop.style.maxHeight = "";
|
|
113
|
-
const at = popoverPlace(anchor.getBoundingClientRect(), { width, height: pop.offsetHeight,
|
|
114
|
-
innerWidth: win.innerWidth, innerHeight: win.innerHeight });
|
|
115
|
-
pop.style.left = `${at.left}px`;
|
|
116
|
-
pop.style.top = at.top === undefined ? "" : `${at.top}px`;
|
|
117
|
-
pop.style.bottom = at.bottom === undefined ? "" : `${at.bottom}px`;
|
|
118
|
-
if (at.maxHeight !== undefined) pop.style.maxHeight = `${at.maxHeight}px`;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function open(anchor) {
|
|
122
|
-
const data = summary();
|
|
123
|
-
if (!data) return;
|
|
124
|
-
initial = (fresh ? choice() : data.own) || DEFAULT;
|
|
125
|
-
openFor = session();
|
|
126
|
-
shown = { data, opts: { running: running(), kind, choice: choice() } };
|
|
127
|
-
draw(data);
|
|
128
|
-
pop.hidden = false;
|
|
129
|
-
place(anchor);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function close() {
|
|
133
|
-
pop.hidden = true;
|
|
134
|
-
initial = null;
|
|
135
|
-
openFor = null;
|
|
136
|
-
shown = null;
|
|
137
|
-
render(nothing, pop);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function isOpen() { return !pop.hidden; }
|
|
141
|
-
|
|
142
|
-
// A new summary: an open form keeps the user's pick; only "Now:" follows
|
|
143
|
-
// (the same template again: lit writes that line alone).
|
|
144
|
-
function refresh() {
|
|
145
|
-
if (!isOpen()) return;
|
|
146
|
-
const data = summary();
|
|
147
|
-
if (!data || session() !== openFor) return close();
|
|
148
|
-
draw(data);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
el.addEventListener("click", (e) => {
|
|
152
|
-
const anchor = e.target.closest(anchorSel);
|
|
153
|
-
if (!anchor) return;
|
|
154
|
-
e.stopPropagation();
|
|
155
|
-
if (isOpen()) close();
|
|
156
|
-
else open(anchor);
|
|
157
|
-
});
|
|
158
|
-
pop.addEventListener("submit", (e) => {
|
|
159
|
-
e.preventDefault();
|
|
160
|
-
const level = e.target.elements.level.value;
|
|
161
|
-
const was = initial;
|
|
162
|
-
close();
|
|
163
|
-
if (fresh) return choose(level === DEFAULT ? null : level);
|
|
164
|
-
const line = commandLine(level, was);
|
|
165
|
-
if (line) run(line);
|
|
166
|
-
});
|
|
167
|
-
pop.addEventListener("click", (e) => {
|
|
168
|
-
if (e.target.closest("[data-act]")?.dataset.act === "close") close();
|
|
169
|
-
});
|
|
170
|
-
doc.addEventListener("click", (e) => {
|
|
171
|
-
if (isOpen() && !pop.contains(e.target) && !e.target.closest?.(anchorSel)) close();
|
|
172
|
-
});
|
|
173
|
-
doc.addEventListener("keydown", (e) => {
|
|
174
|
-
if (e.key === "Escape" && isOpen()) close();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
return { close, refresh, get open() { return isOpen(); } };
|
|
69
|
+
// The chip's popover (level_chip.js createLevelControl); the start page's
|
|
70
|
+
// passes kind NEW_CHAT, choice and choose.
|
|
71
|
+
export function createThinkingControl({ id = "thinkingPopover", ...opts }) {
|
|
72
|
+
return level.createLevelControl({ cfg: CONFIG, id, ...opts });
|
|
178
73
|
}
|
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -693,7 +693,8 @@ module Samagotchi
|
|
|
693
693
|
resolved = @continue_offer.resolved?(awaiting, result)
|
|
694
694
|
@engine.synchronize_events do
|
|
695
695
|
@continue_offer.announce_resolved(result, command) if resolved
|
|
696
|
-
announce_command(command, status: result.status.to_s, output: result.output, changed: Array(result.changed)
|
|
696
|
+
announce_command(command, status: result.status.to_s, output: result.output, changed: Array(result.changed),
|
|
697
|
+
confirm: result.confirm)
|
|
697
698
|
shown.each { |event| @engine.announce(event) }
|
|
698
699
|
end
|
|
699
700
|
save_or_log(:command) { save_session } unless Array(result.changed).empty?
|
|
@@ -706,9 +707,14 @@ module Samagotchi
|
|
|
706
707
|
# the cards and notices it showed, held: they follow its command_ran,
|
|
707
708
|
# as its output
|
|
708
709
|
def run_command_line(command)
|
|
709
|
-
@engine.holding_announcements { command_result(command) { @commands.run(command.line) } }
|
|
710
|
+
@engine.holding_announcements { command_result(command) { @commands.run(command.line, confirm: confirm?(command)) } }
|
|
710
711
|
end
|
|
711
712
|
|
|
713
|
+
# Whether +command+ asks before a costly /thinking or /reasoning-history
|
|
714
|
+
# change (CacheConfirm): only a person at a web tab or a TUI sees the
|
|
715
|
+
# question; chi send, a delegate, a plugin and a card's action apply it.
|
|
716
|
+
def confirm?(command) = ClientId.interactive?(command.client_id) && !command.card
|
|
717
|
+
|
|
712
718
|
# The block's Result for +command+; one that raised, or found no
|
|
713
719
|
# command for the line, is an error Result saying so.
|
|
714
720
|
# @return [SessionCommands::Result]
|
|
@@ -767,9 +773,9 @@ module Samagotchi
|
|
|
767
773
|
# saved.
|
|
768
774
|
def start_anytime_command(command)
|
|
769
775
|
@engine.spawn_anytime do
|
|
770
|
-
result = command_result(command) { @engine.running_anytime { @commands.run(command.line) } }
|
|
776
|
+
result = command_result(command) { @engine.running_anytime { @commands.run(command.line, confirm: confirm?(command)) } }
|
|
771
777
|
announce_command(command, status: result.status.to_s, output: result.output, changed: Array(result.changed),
|
|
772
|
-
anytime: true)
|
|
778
|
+
anytime: true, confirm: result.confirm)
|
|
773
779
|
rescue StandardError => e
|
|
774
780
|
Log.warn(:worker, "anytime_command_failed", line: command.line, error: e.class.name, msg: e.message)
|
|
775
781
|
end
|
|
@@ -777,7 +783,9 @@ module Samagotchi
|
|
|
777
783
|
|
|
778
784
|
# @param anytime [Boolean] an anytime command's: its line was shown at
|
|
779
785
|
# its command_queued already
|
|
780
|
-
|
|
786
|
+
# @param confirm [CacheConfirm::Confirm, nil] the change asks first: the
|
|
787
|
+
# line that applies it (the TUI's prefill, the web's Apply button)
|
|
788
|
+
def announce_command(command, status:, output:, changed:, anytime: false, confirm: nil)
|
|
781
789
|
text = output.to_s
|
|
782
790
|
event = { type: :command_ran, **command.event_fields, status: status, output: text[0, COMMAND_OUTPUT_LIMIT],
|
|
783
791
|
changed: changed.map(&:to_s), model_name: @engine.effective_model_name }
|
|
@@ -788,6 +796,10 @@ module Samagotchi
|
|
|
788
796
|
event[:llm_context] = @engine.llm_context_summary if changed.intersect?(%i[llm_context model])
|
|
789
797
|
# /thinking or /model: the level the next turn runs at (the web's chip).
|
|
790
798
|
event[:thinking] = @engine.thinking_explained&.summary if changed.intersect?(%i[thinking model])
|
|
799
|
+
# /reasoning-history or /model: the level the next turn runs under
|
|
800
|
+
# (the web's chip); nil on a native host, which ignores it.
|
|
801
|
+
event[:reasoning_history] = @engine.reasoning_history_explained&.summary if changed.intersect?(%i[reasoning_history model])
|
|
802
|
+
event[:confirm] = confirm.to_h if confirm
|
|
791
803
|
event[:output_truncated] = true if text.length > COMMAND_OUTPUT_LIMIT
|
|
792
804
|
@engine.announce(event)
|
|
793
805
|
end
|
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.56.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -212,6 +212,7 @@ files:
|
|
|
212
212
|
- lib/samagotchi/bundles/system/manifest.yml
|
|
213
213
|
- lib/samagotchi/bundles/system/memory_guide.md
|
|
214
214
|
- lib/samagotchi/bundles/system/self_map.md
|
|
215
|
+
- lib/samagotchi/cache_confirm.rb
|
|
215
216
|
- lib/samagotchi/cancellation_controller.rb
|
|
216
217
|
- lib/samagotchi/child_move.rb
|
|
217
218
|
- lib/samagotchi/child_reports.rb
|
|
@@ -318,6 +319,7 @@ files:
|
|
|
318
319
|
- lib/samagotchi/llm/native_tool_normalizer.rb
|
|
319
320
|
- lib/samagotchi/llm/openai_chat.rb
|
|
320
321
|
- lib/samagotchi/llm/prompt_cache.rb
|
|
322
|
+
- lib/samagotchi/llm/stream_drops.rb
|
|
321
323
|
- lib/samagotchi/llm/turn_settings.rb
|
|
322
324
|
- lib/samagotchi/llm/usage.rb
|
|
323
325
|
- lib/samagotchi/llm/utf8_scrub.rb
|
|
@@ -394,6 +396,8 @@ files:
|
|
|
394
396
|
- lib/samagotchi/prompt_warmup.rb
|
|
395
397
|
- lib/samagotchi/question_desk.rb
|
|
396
398
|
- lib/samagotchi/queued_command.rb
|
|
399
|
+
- lib/samagotchi/reasoning_history.rb
|
|
400
|
+
- lib/samagotchi/reasoning_history_command.rb
|
|
397
401
|
- lib/samagotchi/recap_setup.rb
|
|
398
402
|
- lib/samagotchi/recap_store.rb
|
|
399
403
|
- lib/samagotchi/relay.rb
|
|
@@ -420,6 +424,7 @@ files:
|
|
|
420
424
|
- lib/samagotchi/session_retention.rb
|
|
421
425
|
- lib/samagotchi/session_setup.rb
|
|
422
426
|
- lib/samagotchi/sessions_command.rb
|
|
427
|
+
- lib/samagotchi/setting_explained.rb
|
|
423
428
|
- lib/samagotchi/steer.rb
|
|
424
429
|
- lib/samagotchi/steer_cut.rb
|
|
425
430
|
- lib/samagotchi/system_prompt.rb
|
|
@@ -540,6 +545,7 @@ files:
|
|
|
540
545
|
- lib/samagotchi/web/public/info_bar.js
|
|
541
546
|
- lib/samagotchi/web/public/init_row.js
|
|
542
547
|
- lib/samagotchi/web/public/kept_commands.js
|
|
548
|
+
- lib/samagotchi/web/public/level_chip.js
|
|
543
549
|
- lib/samagotchi/web/public/lists_view.js
|
|
544
550
|
- lib/samagotchi/web/public/live_timing.js
|
|
545
551
|
- lib/samagotchi/web/public/live_turn.js
|
|
@@ -551,6 +557,7 @@ files:
|
|
|
551
557
|
- lib/samagotchi/web/public/prompt_history.js
|
|
552
558
|
- lib/samagotchi/web/public/question_card.js
|
|
553
559
|
- lib/samagotchi/web/public/question_cards.js
|
|
560
|
+
- lib/samagotchi/web/public/reasoning_history_chip.js
|
|
554
561
|
- lib/samagotchi/web/public/refs.js
|
|
555
562
|
- lib/samagotchi/web/public/restore.js
|
|
556
563
|
- lib/samagotchi/web/public/route.js
|