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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/docs/architecture.md +6 -2
  4. data/docs/cli.md +29 -7
  5. data/docs/configuration.md +84 -8
  6. data/docs/internals/background-tasks.md +1 -1
  7. data/docs/internals/engine.md +14 -0
  8. data/docs/internals/prompt-caching.md +8 -0
  9. data/docs/sessions.md +1 -1
  10. data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
  11. data/lib/samagotchi/bundles/system/manifest.yml +2 -2
  12. data/lib/samagotchi/cache_confirm.rb +51 -0
  13. data/lib/samagotchi/client_id.rb +12 -0
  14. data/lib/samagotchi/config.rb +7 -3
  15. data/lib/samagotchi/engine.rb +102 -8
  16. data/lib/samagotchi/engine_peers.rb +1 -0
  17. data/lib/samagotchi/host_config.rb +6 -3
  18. data/lib/samagotchi/host_registry.rb +4 -3
  19. data/lib/samagotchi/kernel_loop.rb +99 -16
  20. data/lib/samagotchi/llm/chat_loop.rb +122 -71
  21. data/lib/samagotchi/llm/openai_chat.rb +2 -1
  22. data/lib/samagotchi/llm/stream_drops.rb +63 -0
  23. data/lib/samagotchi/llm/turn_settings.rb +9 -5
  24. data/lib/samagotchi/model_settings.rb +5 -2
  25. data/lib/samagotchi/reasoning_history.rb +132 -0
  26. data/lib/samagotchi/reasoning_history_command.rb +107 -0
  27. data/lib/samagotchi/self_report.rb +21 -0
  28. data/lib/samagotchi/session.rb +23 -2
  29. data/lib/samagotchi/session_commands.rb +55 -15
  30. data/lib/samagotchi/session_manager.rb +2 -1
  31. data/lib/samagotchi/session_setup.rb +7 -5
  32. data/lib/samagotchi/setting_explained.rb +20 -0
  33. data/lib/samagotchi/terminal_ui/attach_launcher.rb +2 -1
  34. data/lib/samagotchi/terminal_ui/attached_loop.rb +11 -0
  35. data/lib/samagotchi/terminal_ui/formatting.rb +8 -5
  36. data/lib/samagotchi/terminal_ui.rb +13 -6
  37. data/lib/samagotchi/thinking.rb +20 -25
  38. data/lib/samagotchi/thinking_command.rb +33 -11
  39. data/lib/samagotchi/tools/builtins.rb +6 -2
  40. data/lib/samagotchi/tools/peers.rb +4 -2
  41. data/lib/samagotchi/tools/task_runtime.rb +2 -1
  42. data/lib/samagotchi/version.rb +1 -1
  43. data/lib/samagotchi/web/app.rb +23 -2
  44. data/lib/samagotchi/web/public/app.js +20 -1
  45. data/lib/samagotchi/web/public/commands.js +36 -3
  46. data/lib/samagotchi/web/public/composer.js +5 -1
  47. data/lib/samagotchi/web/public/index.html +6 -3
  48. data/lib/samagotchi/web/public/info_bar.js +5 -3
  49. data/lib/samagotchi/web/public/level_chip.js +174 -0
  50. data/lib/samagotchi/web/public/model_pick.js +1 -1
  51. data/lib/samagotchi/web/public/reasoning_history_chip.js +49 -0
  52. data/lib/samagotchi/web/public/session_state.js +5 -0
  53. data/lib/samagotchi/web/public/stream_handlers.js +10 -2
  54. data/lib/samagotchi/web/public/thinking_chip.js +33 -138
  55. data/lib/samagotchi/worker.rb +17 -5
  56. 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 the page's views
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 (Thinking::Explained#summary:
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 { NEW_CHAT, SESSION, popoverPlace } from "./llm_context_chip.js";
15
- import { html, nothing, render } from "./vendor/lit-html.js";
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
- const DEFAULT = "default";
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, { kind = SESSION } = {}) {
32
- if (!summary || !summary.level) return null;
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 ("default" unsets the session's
49
- // own); null when it is what the form opened with.
50
- export function commandLine(level, initial = null) {
51
- if (initial !== null && level === initial) return null;
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
- const option = (value, label, chosen) => html`<option value=${value} ?selected=${value === chosen}>${label}</option>`;
61
-
62
- // The popover's form (a lit-html template). For the new chat (kind
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, as createLlmContextControl's: el is where the chip
83
- // is drawn (clicks are delegated: #infoText, info_bar.js);
84
- // summary(): the open session's thinking; session(): its id; running():
85
- // a turn runs; run(line): sends the command. The start page's (kind
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
  }
@@ -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
- def announce_command(command, status:, output:, changed:, anytime: false)
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.55.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