samagotchi 0.58.0 → 0.59.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -1
  3. data/docs/architecture.md +3 -3
  4. data/docs/cli.md +5 -4
  5. data/docs/configuration.md +35 -20
  6. data/docs/hooks.md +2 -2
  7. data/docs/internals/background-tasks.md +7 -0
  8. data/docs/internals/engine.md +5 -5
  9. data/docs/internals/prompt-caching.md +2 -2
  10. data/docs/internals/tool-guardrails.md +2 -1
  11. data/docs/plugins.md +26 -6
  12. data/docs/sessions.md +1 -1
  13. data/lib/samagotchi/bridge/turn_accumulator.rb +1 -0
  14. data/lib/samagotchi/bridge.rb +6 -2
  15. data/lib/samagotchi/bundles/slow-commands/manifest.yml +5 -4
  16. data/lib/samagotchi/bundles/slow-commands/plugin.rb +169 -16
  17. data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
  18. data/lib/samagotchi/bundles/system/manifest.yml +2 -2
  19. data/lib/samagotchi/config.rb +11 -5
  20. data/lib/samagotchi/engine.rb +26 -1
  21. data/lib/samagotchi/host_config.rb +3 -1
  22. data/lib/samagotchi/host_registry.rb +2 -1
  23. data/lib/samagotchi/model_settings.rb +4 -2
  24. data/lib/samagotchi/process_group.rb +76 -3
  25. data/lib/samagotchi/send_command.rb +1 -1
  26. data/lib/samagotchi/session_commands.rb +15 -3
  27. data/lib/samagotchi/steer_cut.rb +12 -8
  28. data/lib/samagotchi/steer_cut_after.rb +69 -0
  29. data/lib/samagotchi/system_prompt.rb +5 -5
  30. data/lib/samagotchi/terminal_ui/attached_loop.rb +1 -1
  31. data/lib/samagotchi/terminal_ui/formatting.rb +9 -5
  32. data/lib/samagotchi/terminal_ui.rb +1 -1
  33. data/lib/samagotchi/tool_activity.rb +18 -1
  34. data/lib/samagotchi/tool_declarations.rb +1 -1
  35. data/lib/samagotchi/tools/execute.rb +318 -70
  36. data/lib/samagotchi/tools/task_get.rb +2 -0
  37. data/lib/samagotchi/tools/task_runtime.rb +96 -13
  38. data/lib/samagotchi/tools/task_wait.rb +23 -5
  39. data/lib/samagotchi/utf8_default.rb +1 -1
  40. data/lib/samagotchi/version.rb +1 -1
  41. data/lib/samagotchi/web/public/read_view.js +3 -1
  42. data/lib/samagotchi/web/public/task_stop.js +25 -7
  43. metadata +2 -1
@@ -10,6 +10,9 @@ module Samagotchi
10
10
  TAIL_LINES_DEFAULT = 10
11
11
  TAIL_LINES_MAX = 100
12
12
  POLL_INTERVAL = 0.5
13
+ # A handed-over execute keeps stderr in a log of its own: at most this
14
+ # many of its last lines follow the output tail.
15
+ STDERR_TAIL_LINES_MAX = 10
13
16
 
14
17
  def self.name = NAME
15
18
 
@@ -41,7 +44,7 @@ module Samagotchi
41
44
 
42
45
  if completion_pattern
43
46
  output_tail = output_tail(record, tail_lines)
44
- return format_response(record, wait_result: "pattern_matched", output_tail: output_tail) if completion_pattern.match?(output_tail)
47
+ return format_response(record, wait_result: "pattern_matched", output_tail: output_tail, tail_lines: tail_lines) if completion_pattern.match?(output_tail)
45
48
  end
46
49
 
47
50
  return cancelled_response(task_id, tail_lines, started) if cancelled.call
@@ -55,7 +58,7 @@ module Samagotchi
55
58
  refreshed, = TaskRuntime.get_record(task_id)
56
59
  return finished_response(refreshed, tail_lines) unless refreshed.fetch("status") == "running"
57
60
 
58
- format_response(refreshed, wait_result: "timeout", output_tail: output_tail(refreshed, tail_lines))
61
+ format_response(refreshed, wait_result: "timeout", output_tail: output_tail(refreshed, tail_lines), tail_lines: tail_lines)
59
62
  rescue StandardError => e
60
63
  "Error: #{e.message}"
61
64
  end
@@ -72,7 +75,8 @@ module Samagotchi
72
75
  " To keep waiting: task_wait #{task_id}. If it is stuck or no longer needed: task_stop #{task_id}.",
73
76
  "waited: #{(monotonic_time - started).round}s"
74
77
  ]
75
- format_response(record, wait_result: "canceled", output_tail: output_tail(record, tail_lines), extra: note)
78
+ format_response(record, wait_result: "canceled", output_tail: output_tail(record, tail_lines), tail_lines: tail_lines,
79
+ extra: note)
76
80
  end
77
81
  private_class_method :cancelled_response
78
82
 
@@ -81,7 +85,8 @@ module Samagotchi
81
85
  def self.finished_response(record, tail_lines)
82
86
  return format_response(record) unless TaskRuntime.stopped_by_user?(record)
83
87
 
84
- format_response(record, output_tail: output_tail(record, tail_lines), extra: [TaskRuntime::USER_STOP_NOTE])
88
+ format_response(record, output_tail: output_tail(record, tail_lines), tail_lines: tail_lines,
89
+ extra: [TaskRuntime::USER_STOP_NOTE])
85
90
  end
86
91
  private_class_method :finished_response
87
92
 
@@ -90,7 +95,17 @@ module Samagotchi
90
95
  end
91
96
  private_class_method :output_tail
92
97
 
93
- def self.format_response(record, wait_result: nil, output_tail: nil, extra: [])
98
+ # The last lines of a handed-over execute's stderr log (stderr_path),
99
+ # "" for a task_create task (one log) or an empty stderr.
100
+ def self.stderr_tail(record, tail_lines)
101
+ path = record["stderr_path"]
102
+ return "" if path.to_s.empty? || tail_lines.nil?
103
+
104
+ TaskRuntime.output_tail_lines(path, [tail_lines, STDERR_TAIL_LINES_MAX].min)
105
+ end
106
+ private_class_method :stderr_tail
107
+
108
+ def self.format_response(record, wait_result: nil, output_tail: nil, tail_lines: nil, extra: [])
94
109
  lines = [
95
110
  "task_id: #{record.fetch("id")}",
96
111
  "status: #{record.fetch("status")}",
@@ -98,12 +113,15 @@ module Samagotchi
98
113
  "stop_reason: #{record["stop_reason"]}",
99
114
  "output_path: #{record.fetch("output_path")}"
100
115
  ]
116
+ lines << "stderr_path: #{record["stderr_path"]}" if record["stderr_path"]
101
117
  return lines.join("\n") unless output_tail
102
118
 
103
119
  lines << "wait_result: #{wait_result}" if wait_result
104
120
  lines.concat(extra)
105
121
  lines << "output_tail:"
106
122
  lines << output_tail.rstrip unless output_tail.empty?
123
+ stderr_tail = stderr_tail(record, tail_lines)
124
+ lines.concat(["stderr_tail:", stderr_tail.rstrip]) unless stderr_tail.empty?
107
125
  lines.join("\n")
108
126
  end
109
127
  private_class_method :format_response
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Samagotchi
4
- # chi's files (memories, AGENT.md, sessions, config.yml) are UTF-8,
4
+ # chi's files (memories, AGENTS.md, sessions, config.yml) are UTF-8,
5
5
  # whatever the locale. Under LC_ALL=C (or with no locale at all: an app
6
6
  # started from Finder or launchd, `env -i`) Ruby reads files as US-ASCII,
7
7
  # and a UTF-8 memory index then breaks the system prompt
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Samagotchi
4
- VERSION = "0.58.0"
4
+ VERSION = "0.59.0"
5
5
  # Sent as the User-Agent of every HTTP request chi makes.
6
6
  USER_AGENT = "chi/#{VERSION}"
7
7
  end
@@ -67,8 +67,10 @@ export function readLinesHtml(row) {
67
67
  const first = numbered[0].line;
68
68
  const last = numbered.at(-1).line;
69
69
  const label = first === last ? `line ${first}` : `lines ${first}–${last}`;
70
+ // A blank line of the file holds no text, only a <br>: an empty block
71
+ // drops out of a copied selection, a space would be copied as one.
70
72
  const spans = lines.map(({ text, line }) => line === null
71
73
  ? `<span class="read-line read-meta">${escapeHtml(text)}</span>`
72
- : `<span class="read-line" data-line="${line}">${escapeHtml(text) || " "}</span>`).join("");
74
+ : `<span class="read-line" data-line="${line}">${escapeHtml(text) || "<br>"}</span>`).join("");
73
75
  return `<details class="activity-read"><summary>${label}</summary><pre class="read-lines"><code>${spans}</code></pre></details>`;
74
76
  }
@@ -22,10 +22,30 @@ export function stopTarget(row, features) {
22
22
  // A task_wait row's title without its wait ("npm test · up to 600s").
23
23
  const WAIT_SUFFIX = / · up to \d+s$/;
24
24
 
25
- // The command a task_create of this turn started +taskId+ with (its row's
26
- // view), else a task_wait row's title for it (the server titles one by its
27
- // task's command, also for a task from an earlier turn: ToolActivity.
28
- // task_title), or null.
25
+ // The first line of an execute that was still running at its limit and
26
+ // went on as a task (Tools::Execute::HANDED_OVER), then its task_id line.
27
+ // Anchored at the start, an optional "ran as: …" line and "[execute]" lead
28
+ // before it: a "task_id:" line anywhere else is the command's own output.
29
+ // The twin of TaskRuntime.handed_over_id.
30
+ const HANDED_OVER = /^(?:ran as: [^\n]*\n)?(?:\[execute\]\n)?Still running at the \d+s limit:[^\n]*\ntask_id: (\S+)/;
31
+
32
+ // The task id an execute row's output hands over, else null.
33
+ export function handedOverId(output) {
34
+ return HANDED_OVER.exec(output || "")?.[1] || null;
35
+ }
36
+
37
+ // The task id a row of this turn started: a task_create's, or an execute's
38
+ // hand-over (handedOverId); else null.
39
+ function startedId(row) {
40
+ if (row.tool === "execute") return handedOverId(row.output);
41
+ if (row.tool !== "task_create") return null;
42
+ return /(?:^|\n)task_id: (\S+)/.exec(row.output || "")?.[1] || null;
43
+ }
44
+
45
+ // The command a task_create or a handed-over execute of this turn started
46
+ // +taskId+ with (its row's view), else a task_wait row's title for it (the
47
+ // server titles one by its task's command, also for a task from an earlier
48
+ // turn: ToolActivity.task_title), or null.
29
49
  export function taskCommand(gens, taskId) {
30
50
  let titled = null;
31
51
  for (const gen of gens || []) {
@@ -33,9 +53,7 @@ export function taskCommand(gens, taskId) {
33
53
  if (row.tool === "task_wait" && row.view?.task_id === taskId && row.title) {
34
54
  titled ||= row.title.replace(WAIT_SUFFIX, "");
35
55
  }
36
- if (row.tool !== "task_create") continue;
37
- const id = /(?:^|\n)task_id: (\S+)/.exec(row.output || "")?.[1];
38
- if (id === taskId) return row.view?.command || null;
56
+ if (startedId(row) === taskId) return row.view?.command || null;
39
57
  }
40
58
  }
41
59
  return titled;
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.58.0
4
+ version: 0.59.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -430,6 +430,7 @@ files:
430
430
  - lib/samagotchi/setting_explained.rb
431
431
  - lib/samagotchi/steer.rb
432
432
  - lib/samagotchi/steer_cut.rb
433
+ - lib/samagotchi/steer_cut_after.rb
433
434
  - lib/samagotchi/system_prompt.rb
434
435
  - lib/samagotchi/terminal_ui.rb
435
436
  - lib/samagotchi/terminal_ui/attach_launcher.rb