samagotchi 0.70.0 → 0.72.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -1
  3. data/docs/architecture.md +16 -10
  4. data/docs/cli.md +7 -4
  5. data/docs/configuration.md +24 -14
  6. data/docs/hooks.md +1 -1
  7. data/docs/internals/anthropic.md +1 -1
  8. data/docs/internals/background-tasks.md +2 -2
  9. data/docs/internals/engine.md +38 -13
  10. data/docs/internals/llm-context-live.md +1 -1
  11. data/docs/internals/prompt-caching.md +6 -0
  12. data/docs/plugins.md +18 -7
  13. data/docs/sessions.md +4 -2
  14. data/docs/testing.md +12 -3
  15. data/lib/samagotchi/bootstrap/config_writer.rb +2 -3
  16. data/lib/samagotchi/bridge/turn_accumulator.rb +4 -1
  17. data/lib/samagotchi/bridge.rb +5 -2
  18. data/lib/samagotchi/broadcast/scope_card.rb +2 -2
  19. data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -7
  20. data/lib/samagotchi/bundles/claude-peer/plugin.rb +2 -2
  21. data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -6
  22. data/lib/samagotchi/bundles/codex-peer/plugin.rb +2 -2
  23. data/lib/samagotchi/bundles/coordinator/manifest.yml +8 -6
  24. data/lib/samagotchi/bundles/coordinator/plugin.rb +10 -0
  25. data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +2 -1
  26. data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
  27. data/lib/samagotchi/bundles/system/manifest.yml +2 -2
  28. data/lib/samagotchi/config.rb +7 -25
  29. data/lib/samagotchi/context_note.rb +2 -6
  30. data/lib/samagotchi/effective_model.rb +1 -1
  31. data/lib/samagotchi/engine.rb +91 -213
  32. data/lib/samagotchi/engine_peers.rb +2 -0
  33. data/lib/samagotchi/event_routes.rb +83 -0
  34. data/lib/samagotchi/hook_routes.rb +16 -37
  35. data/lib/samagotchi/host_config.rb +1 -1
  36. data/lib/samagotchi/host_keys.rb +0 -2
  37. data/lib/samagotchi/paths.rb +13 -0
  38. data/lib/samagotchi/plugin/api.rb +11 -5
  39. data/lib/samagotchi/plugin/peer.rb +5 -8
  40. data/lib/samagotchi/prompt_files.rb +2 -4
  41. data/lib/samagotchi/session.rb +2 -1
  42. data/lib/samagotchi/session_manager.rb +6 -16
  43. data/lib/samagotchi/session_model_settings.rb +227 -0
  44. data/lib/samagotchi/session_setup.rb +2 -1
  45. data/lib/samagotchi/sessions_command.rb +12 -3
  46. data/lib/samagotchi/system_prompt.rb +18 -13
  47. data/lib/samagotchi/terminal_ui/attached_loop.rb +36 -15
  48. data/lib/samagotchi/terminal_ui/formatting.rb +4 -0
  49. data/lib/samagotchi/tool_activity.rb +20 -1
  50. data/lib/samagotchi/tool_declarations.rb +4 -0
  51. data/lib/samagotchi/tool_output_cut.rb +11 -2
  52. data/lib/samagotchi/toolkit.rb +60 -22
  53. data/lib/samagotchi/toolkit_binding.rb +7 -5
  54. data/lib/samagotchi/toolkit_command.rb +28 -5
  55. data/lib/samagotchi/tools/builtins.rb +2 -1
  56. data/lib/samagotchi/tools/delegate.rb +32 -6
  57. data/lib/samagotchi/tools/list_sessions.rb +2 -2
  58. data/lib/samagotchi/tools/peers.rb +5 -2
  59. data/lib/samagotchi/tools/register_reminder.rb +1 -0
  60. data/lib/samagotchi/tools/registry.rb +13 -2
  61. data/lib/samagotchi/tools/task_wait.rb +4 -6
  62. data/lib/samagotchi/tools/tool_index.rb +15 -4
  63. data/lib/samagotchi/turn_note.rb +4 -1
  64. data/lib/samagotchi/version.rb +1 -1
  65. data/lib/samagotchi/web/app.rb +18 -17
  66. data/lib/samagotchi/web/message_parts.rb +1 -1
  67. data/lib/samagotchi/web/public/answer_refs.js +1 -0
  68. data/lib/samagotchi/web/public/app.js +20 -18
  69. data/lib/samagotchi/web/public/bubbles.js +1 -1
  70. data/lib/samagotchi/web/public/cards_place.js +6 -10
  71. data/lib/samagotchi/web/public/chains.js +1 -1
  72. data/lib/samagotchi/web/public/chip_popover.js +3 -1
  73. data/lib/samagotchi/web/public/composer.js +13 -6
  74. data/lib/samagotchi/web/public/context_chips.js +25 -12
  75. data/lib/samagotchi/web/public/data.js +5 -1
  76. data/lib/samagotchi/web/public/format.js +5 -0
  77. data/lib/samagotchi/web/public/index.html +1 -0
  78. data/lib/samagotchi/web/public/sessions_list.js +2 -3
  79. data/lib/samagotchi/web/public/stage_frame.js +1 -1
  80. data/lib/samagotchi/web/public/stage_view.js +8 -2
  81. data/lib/samagotchi/web/public/stream_handlers.js +6 -3
  82. data/lib/samagotchi/web/public/timing.js +1 -1
  83. data/lib/samagotchi/web/public/toolkit_chip.js +3 -1
  84. data/lib/samagotchi/web/public/turn_html.js +55 -3
  85. data/lib/samagotchi/web/session_hub.rb +8 -0
  86. metadata +3 -1
@@ -11,6 +11,7 @@ import { activityDiffTemplate } from "./diff_view.js";
11
11
  import { clip, steerSender } from "./format.js";
12
12
  import { rowTitleRef } from "./refs.js";
13
13
  import { readLinesTemplate } from "./read_view.js";
14
+ import { handedOverId } from "./task_stop.js";
14
15
  import { STOPPED_LINE_CLASS, cancelLineText, formatDuration } from "./timing.js";
15
16
  import { emptyAnswerLine } from "./turn_events.js";
16
17
 
@@ -73,12 +74,63 @@ function llmEditTemplate(row) {
73
74
  return html`<div class="activity-edit" title=${hover}>${line}</div>`;
74
75
  }
75
76
 
77
+ // What a done row shows of its output inline: its first INLINE_CHARS, else
78
+ // for an execute's result and a task_wait with its log's last lines its
79
+ // last ones (a test run's summary, a failing exit line). An execute drops a
80
+ // bare success "exit: 0" (the mark says it) and, with no stderr block, the
81
+ // lone "stdout:" label. A guardrail deny, an approval refused or an
82
+ // execute's error has its reason first, a hand-over its task_id: the head.
83
+ // A gate-corrected call's `ran as:` line shows on hover only.
84
+ const INLINE_CHARS = 300;
85
+ // The JS twin of ToolOutputCut::EXIT_LINE (tool_output_cut.rb): an exit line, then maybe a hint; a
86
+ // signal-killed command's has no number ("exit: ").
87
+ const EXIT_LINE = /\nexit: (?:-?\d+)?[^\n]*(?:\n\([^\n]*\))?$/;
88
+ // A bare success exit line; group 1 is the hint line after it, if any.
89
+ const SUCCESS_EXIT_LINE = /\nexit: 0(\n\([^\n]*\))?$/;
90
+ const EXECUTE_BLOCK = /(?:^|\n)(?:stdout|stderr):\n/;
91
+ const OUTPUT_TAIL = /^output_tail:$/m;
92
+ // A gate-corrected call's first line (ToolRunner#changed_call_line) and the "[tool]" lead after it, which
93
+ // the row's lead strip misses (it isn't first). The inline text drops both; the hover keeps them.
94
+ const RAN_AS_LEAD = /^ran as: [^\n]*\n(?:\[[^\]\s]+\](?:\n| ))?/;
95
+
96
+ export function inlineOutput(tool, full) {
97
+ const text = String(full ?? "").replace(RAN_AS_LEAD, "");
98
+ if (!tailMode(tool, text)) return clip(text, INLINE_CHARS + 1);
99
+ let shown = text;
100
+ if (tool === "execute") {
101
+ shown = shown.replace(SUCCESS_EXIT_LINE, (_, hint) => hint || "");
102
+ if (!text.includes("\nstderr:\n") && shown.startsWith("stdout:\n")) shown = shown.slice("stdout:\n".length);
103
+ }
104
+ shown = shown.replace(/\n+$/, "");
105
+ return tailClip(shown.trim() ? shown : text);
106
+ }
107
+
108
+ // Whether a row's output shows its end (its div opens scrolled to the end).
109
+ export function tailMode(tool, text) {
110
+ if (tool === "task_wait") return OUTPUT_TAIL.test(text);
111
+ if (tool !== "execute" || handedOverId(text)) return false;
112
+ return EXECUTE_BLOCK.test(text) || EXIT_LINE.test(text);
113
+ }
114
+
115
+ // The text's end, as clip keeps its start: whole when it fits, else "…" and
116
+ // its last INLINE_CHARS, never from half an emoji, from a line's start when
117
+ // that keeps at least half of them (ToolOutputCut.cut_block's tail rule).
118
+ function tailClip(text) {
119
+ if (text.length <= INLINE_CHARS + 1) return text;
120
+ let kept = text.slice(-INLINE_CHARS);
121
+ const first = kept.charCodeAt(0);
122
+ if (first >= 0xdc00 && first <= 0xdfff) kept = kept.slice(1);
123
+ const nl = kept.indexOf("\n");
124
+ if (nl >= 0 && kept.length - nl - 1 >= INLINE_CHARS / 2) kept = kept.slice(nl + 1);
125
+ return `\u2026${kept}`;
126
+ }
127
+
76
128
  // A tool row: its status mark, tool, title (else params; the full command,
77
129
  // else the full params line, on hover), the word an exception shows, the
78
130
  // call's duration, the full command (an execute/task_create's view, also
79
131
  // while it runs), the output once done (its "[tool]" tag dropped, 300 chars,
80
- // the rest on hover; a read of the file as-is also its numbered lines,
81
- // closed: read_view.js), its images (+thumbs+(images) draws them) and an
132
+ // an execute/task_wait's last ones (inlineOutput), the rest on hover; a read
133
+ // of the file as-is also its numbered lines, closed: read_view.js), its images (+thumbs+(images) draws them) and an
82
134
  // edit's collapsed diff, and a reloaded row's ✂ mark above its output. A
83
135
  // part with nothing to say is left out. A reload renders toolRowTemplate;
84
136
  // the live view renders toolRowInnerTemplate into its own row element
@@ -107,7 +159,7 @@ export function toolRowInnerTemplate(row, { thumbs = () => nothing, stop = nothi
107
159
  const duration = formatDuration(row.duration_ms);
108
160
  const full = row.status === "running" ? "" : String(row.output || "").replace(/^\[[^\]]*\]\s*/, "");
109
161
  const output = full
110
- ? html`<div class="activity-output" title=${full}>${clip(full, 301)}</div>`
162
+ ? html`<div class=${tailMode(row.tool, full) ? "activity-output tail" : "activity-output"} title=${full}>${inlineOutput(row.tool, full)}</div>`
111
163
  : nothing;
112
164
  const status = statusParts(row.status, { noMatch: !!row.no_match });
113
165
  return html`${statusMarkTemplate(status)}<span class="activity-tool">${toolName(row)}</span>${params}${stop}${alias}${statusWordTemplate(status)}${duration ? html`<span class="activity-duration">${duration}</span>` : nothing}${commandBlockTemplate(row.view, row.title)}${llmEditTemplate(row)}${output}${full ? readLinesTemplate(row) : nothing}${row.images?.length ? thumbs(row.images) : nothing}${row.diff ? activityDiffTemplate(row.diff) : nothing}`;
@@ -172,6 +172,14 @@ module Samagotchi
172
172
  end
173
173
  end
174
174
 
175
+ # A session's folder as the projection holds it (its file's, read at
176
+ # the last scan or touch): what a live read that loads no session
177
+ # file checks refs against. nil for a session the hub hasn't seen.
178
+ # @return [String, nil]
179
+ def working_directory(id)
180
+ @monitor.synchronize { @sessions[id]&.session&.working_directory }
181
+ end
182
+
175
183
  # Register a sink (#call(event)). With +snapshot:+ the current
176
184
  # projection comes back with the handle, taken in the same step, so
177
185
  # every event after it is newer than it.
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.70.0
4
+ version: 0.72.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -279,6 +279,7 @@ files:
279
279
  - lib/samagotchi/empty_answer_retry.rb
280
280
  - lib/samagotchi/engine.rb
281
281
  - lib/samagotchi/engine_peers.rb
282
+ - lib/samagotchi/event_routes.rb
282
283
  - lib/samagotchi/events.rb
283
284
  - lib/samagotchi/extension_load.rb
284
285
  - lib/samagotchi/file_ref.rb
@@ -459,6 +460,7 @@ files:
459
460
  - lib/samagotchi/session_inbox.rb
460
461
  - lib/samagotchi/session_manager.rb
461
462
  - lib/samagotchi/session_metrics.rb
463
+ - lib/samagotchi/session_model_settings.rb
462
464
  - lib/samagotchi/session_observer.rb
463
465
  - lib/samagotchi/session_retention.rb
464
466
  - lib/samagotchi/session_setting.rb