samagotchi 0.24.0 → 0.25.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -1
  3. data/docs/architecture.md +16 -9
  4. data/docs/cli.md +4 -3
  5. data/docs/configuration.md +21 -13
  6. data/docs/hooks.md +1 -1
  7. data/docs/internals/background-tasks.md +1 -0
  8. data/docs/internals/prompt-caching.md +94 -0
  9. data/docs/plugins.md +16 -2
  10. data/docs/testing.md +12 -0
  11. data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
  12. data/lib/samagotchi/bundles/loop-guard/plugin.rb +43 -7
  13. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  14. data/lib/samagotchi/command_steps.rb +233 -0
  15. data/lib/samagotchi/config.rb +5 -2
  16. data/lib/samagotchi/empty_answer_retry.rb +2 -2
  17. data/lib/samagotchi/engine.rb +23 -4
  18. data/lib/samagotchi/guardrails/shell_lex.rb +49 -5
  19. data/lib/samagotchi/idle_client.rb +14 -2
  20. data/lib/samagotchi/kernel_loop.rb +21 -3
  21. data/lib/samagotchi/llm/chat_loop.rb +17 -3
  22. data/lib/samagotchi/llm/errors.rb +26 -1
  23. data/lib/samagotchi/llm/openai_chat.rb +5 -4
  24. data/lib/samagotchi/llm/prompt_cache.rb +34 -9
  25. data/lib/samagotchi/llm/usage.rb +18 -3
  26. data/lib/samagotchi/log_subscriber.rb +2 -1
  27. data/lib/samagotchi/plugin/api.rb +3 -2
  28. data/lib/samagotchi/session_metrics.rb +38 -4
  29. data/lib/samagotchi/system_prompt.rb +30 -9
  30. data/lib/samagotchi/terminal_ui/attached_loop.rb +5 -2
  31. data/lib/samagotchi/terminal_ui/attached_view.rb +6 -2
  32. data/lib/samagotchi/terminal_ui/event_renderer.rb +14 -2
  33. data/lib/samagotchi/terminal_ui/formatting.rb +31 -9
  34. data/lib/samagotchi/thinking_tails.rb +103 -0
  35. data/lib/samagotchi/token_usage.rb +18 -6
  36. data/lib/samagotchi/tool_activity.rb +37 -3
  37. data/lib/samagotchi/tool_declarations.rb +14 -0
  38. data/lib/samagotchi/tool_view.rb +40 -4
  39. data/lib/samagotchi/tools/builtins.rb +4 -1
  40. data/lib/samagotchi/tools/execute.rb +2 -1
  41. data/lib/samagotchi/tools/registry.rb +11 -4
  42. data/lib/samagotchi/tools/shell.rb +32 -0
  43. data/lib/samagotchi/tools/task_runtime.rb +3 -3
  44. data/lib/samagotchi/turn_tally.rb +17 -6
  45. data/lib/samagotchi/version.rb +1 -1
  46. data/lib/samagotchi/web/public/command_view.js +71 -3
  47. data/lib/samagotchi/web/public/format.js +11 -1
  48. data/lib/samagotchi/web/public/index.html +25 -0
  49. data/lib/samagotchi/web/public/stage_model.js +5 -1
  50. data/lib/samagotchi/web/public/stage_view.js +1 -1
  51. data/lib/samagotchi/web/public/turn_view.js +3 -3
  52. metadata +5 -1
@@ -141,6 +141,31 @@ main{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px;overflow:au
141
141
  .activity-command-cwd{margin-bottom:2px}
142
142
  .activity-command-cwd>span{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text-muted)}
143
143
  .activity-command-cut{margin-top:2px}
144
+ .activity-command-desc{font-size:12px;line-height:1.4;color:var(--text-2);margin-bottom:3px;overflow-wrap:anywhere}
145
+ /* 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
+ .activity-command.has-steps{background:var(--bg-inset);border:1px solid var(--border);border-radius:6px;padding:4px 8px 5px}
147
+ .activity-command-head{display:flex;align-items:center;gap:8px;min-height:20px;padding-right:30px}
148
+ .has-steps .activity-command-cwd{flex:1 1 auto;min-width:0;margin:0;overflow-wrap:anywhere}
149
+ .activity-command-cwd>.activity-command-sep{font-family:inherit;color:var(--text-faint)}
150
+ .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}
151
+ .activity-command-raw:hover{color:var(--text-2)}
152
+ .activity-command-raw input{position:absolute;opacity:0;width:0;height:0;margin:0}
153
+ .activity-command-raw:has(input:focus-visible){outline:2px solid var(--accent);outline-offset:1px}
154
+ .activity-command-raw:has(input:checked){color:var(--accent);border-color:var(--accent-border);background:var(--accent-tint)}
155
+ .activity-steps{list-style:none;margin:2px 0 0;padding:0;max-height:220px;overflow:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;color:var(--text-2)}
156
+ .activity-step{display:flex;align-items:baseline;gap:6px;min-width:0}
157
+ .step-body{flex:1 1 auto;min-width:0;display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 6px}
158
+ .step-body>code{min-width:0;font:inherit;white-space:pre-wrap;overflow-wrap:anywhere}
159
+ .step-op{flex:none;width:2.4em;text-align:right;font-family:ui-sans-serif,system-ui,sans-serif;font-size:11px;color:var(--text-faint)}
160
+ .step-op.else{color:var(--warn-text)}
161
+ .step-label{margin:5px 0 0 calc(2.4em + 6px);font-family:ui-sans-serif,system-ui,sans-serif;font-size:11px;font-weight:600;color:var(--text-muted)}
162
+ .step-label:first-child{margin-top:1px}
163
+ .step-chip{flex:none;align-self:center;padding:0 6px;border:1px solid var(--border-strong);border-radius:8px;font-family:ui-sans-serif,system-ui,sans-serif;font-size:10.5px;line-height:15px;color:var(--text-muted);white-space:nowrap}
164
+ .step-chip.heredoc{border-style:dashed}
165
+ .step-plumbing{color:var(--text-faint)}
166
+ .activity-command.has-steps>pre{display:none;max-height:220px;margin-top:2px;padding:0 4px;background:none;border:0}
167
+ .has-steps:has(.activity-command-raw input:checked)>pre{display:block}
168
+ .has-steps:has(.activity-command-raw input:checked)>.activity-steps{display:none}
144
169
  @media(max-width:600px){
145
170
  .activity-params{flex:1 1 0}
146
171
  .activity-output{margin-left:0}
@@ -4,6 +4,7 @@
4
4
  // covers the rules; stage_view.js draws them.
5
5
  import { toolName } from "./activity.js";
6
6
  import { splitSentences } from "./sentences.js";
7
+ import { steerSender } from "./format.js";
7
8
  import { hookNoticeLabel } from "./turn_events.js";
8
9
 
9
10
  const READ_LIKE = new Set(["read", "memory_read", "web_fetch", "task_get", "task_list", "list_sessions"]);
@@ -174,7 +175,10 @@ export function handOffOrder({ prompt = null, block = null, extras = [], answer
174
175
  // chi's own asking-again row (a notice with a line and no hook) does not:
175
176
  // the status line tells that already. null: nothing flashes.
176
177
  export function flashOf(what, data = {}) {
177
- if (what === "steer") return { kind: "steer", text: data.source ? `${data.source} nudged the model` : "nudged the model" };
178
+ if (what === "steer") {
179
+ const sender = steerSender(data.source);
180
+ return { kind: "steer", text: sender ? `${sender} nudged the model` : "nudged the model" };
181
+ }
178
182
  if (what !== "notice" || !data.hook) return null;
179
183
  return { kind: data.level === "warn" ? "warn" : "info", text: data.line || `${hookNoticeLabel(data.hook)}: ${data.text || ""}` };
180
184
  }
@@ -234,7 +234,7 @@ export function createStageView({ historyEl, appendToHistory, isNearBottom: hist
234
234
  name.textContent = tool.name;
235
235
  const title = node("span", "ts-title", now);
236
236
  title.textContent = tool.title;
237
- // The full command when the call has one (the title is its first line).
237
+ // The full command when the call has one (the title is its first step +N).
238
238
  title.title = tool.command || tool.title;
239
239
  } else {
240
240
  const word = node("span", "ts-title quiet", now);
@@ -54,8 +54,8 @@ function fillActivityRow(el, row, sessionId) {
54
54
  const outEl = el.querySelector(".activity-output");
55
55
  // The call's full command, above its output (running too).
56
56
  if (row.view?.command && !el.querySelector(".activity-command")) {
57
- if (outEl) outEl.insertAdjacentHTML("beforebegin", commandBlockHtml(row.view));
58
- else el.insertAdjacentHTML("beforeend", commandBlockHtml(row.view));
57
+ if (outEl) outEl.insertAdjacentHTML("beforebegin", commandBlockHtml(row.view, row.title));
58
+ else el.insertAdjacentHTML("beforeend", commandBlockHtml(row.view, row.title));
59
59
  }
60
60
  if (outEl) {
61
61
  if (row.status === "running") {
@@ -691,7 +691,7 @@ function reloadRowHtml(r, thumbs) {
691
691
  : "";
692
692
  return `<div class="activity-row" data-key="${escapeHtml(r.key)}"><span class="activity-status ${activityStatusClass(r.status)}">${activityStatusLabel(r.status)}</span>` +
693
693
  `<span class="activity-tool">${escapeHtml(toolName(r))}</span>${params}${duration ? `<span class="activity-duration">${escapeHtml(duration)}</span>` : ""}` +
694
- `${commandBlockHtml(r.view)}${output}` +
694
+ `${commandBlockHtml(r.view, r.title)}${output}` +
695
695
  `${r.images?.length ? thumbs(r.images) : ""}${r.diff ? activityDiffHtml(r.diff) : ""}</div>`;
696
696
  }
697
697
 
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.24.0
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -131,6 +131,7 @@ files:
131
131
  - docs/internals/background-tasks.md
132
132
  - docs/internals/context-telemetry.md
133
133
  - docs/internals/gemma4-contract.md
134
+ - docs/internals/prompt-caching.md
134
135
  - docs/internals/tool-guardrails.md
135
136
  - docs/memory.md
136
137
  - docs/plugins.md
@@ -196,6 +197,7 @@ files:
196
197
  - lib/samagotchi/cli/flags.rb
197
198
  - lib/samagotchi/cli/parent_wait.rb
198
199
  - lib/samagotchi/client.rb
200
+ - lib/samagotchi/command_steps.rb
199
201
  - lib/samagotchi/commands/registry.rb
200
202
  - lib/samagotchi/config.rb
201
203
  - lib/samagotchi/config_text_edit.rb
@@ -373,6 +375,7 @@ files:
373
375
  - lib/samagotchi/terminal_ui/version_lines.rb
374
376
  - lib/samagotchi/text_diff.rb
375
377
  - lib/samagotchi/thinking.rb
378
+ - lib/samagotchi/thinking_tails.rb
376
379
  - lib/samagotchi/thinking_ticker.rb
377
380
  - lib/samagotchi/thought_stream_splitter.rb
378
381
  - lib/samagotchi/token_usage.rb
@@ -402,6 +405,7 @@ files:
402
405
  - lib/samagotchi/tools/register_reminder.rb
403
406
  - lib/samagotchi/tools/registry.rb
404
407
  - lib/samagotchi/tools/send_note.rb
408
+ - lib/samagotchi/tools/shell.rb
405
409
  - lib/samagotchi/tools/task_create.rb
406
410
  - lib/samagotchi/tools/task_get.rb
407
411
  - lib/samagotchi/tools/task_list.rb