samagotchi 0.66.0 → 0.67.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 +60 -1
- data/docs/architecture.md +50 -28
- data/docs/cli.md +3 -1
- data/docs/configuration.md +50 -9
- data/docs/context.md +35 -1
- data/docs/desktop.md +90 -22
- data/docs/guardrails.md +8 -5
- data/docs/hooks.md +2 -2
- data/docs/internals/anthropic.md +156 -0
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/bundles.md +2 -0
- data/docs/internals/engine.md +71 -29
- data/docs/internals/gemma4-contract.md +1 -1
- data/docs/internals/llm-context-bench.md +5 -4
- data/docs/internals/prompt-caching.md +26 -11
- data/docs/plugins.md +126 -23
- data/docs/sessions.md +6 -6
- data/docs/sub-agent.md +4 -1
- data/docs/testing.md +17 -3
- data/lib/samagotchi/bootstrap/probe.rb +4 -3
- data/lib/samagotchi/bootstrap_command.rb +13 -3
- data/lib/samagotchi/bridge/ring_buffer.rb +0 -5
- data/lib/samagotchi/bridge.rb +6 -3
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +7 -6
- data/lib/samagotchi/bundles/claude-peer/plugin.rb +34 -313
- data/lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md +8 -3
- data/lib/samagotchi/bundles/claude-peer/skill_claude_peer.md +14 -6
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +7 -6
- data/lib/samagotchi/bundles/codex-peer/plugin.rb +37 -312
- data/lib/samagotchi/bundles/codex-peer/scripts/chi-peer-SKILL.md +8 -3
- data/lib/samagotchi/bundles/codex-peer/skill_codex_peer.md +14 -6
- data/lib/samagotchi/bundles/mcp/manifest.yml +5 -5
- data/lib/samagotchi/bundles/mcp/plugin.rb +82 -294
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +7 -3
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/config.rb +15 -7
- data/lib/samagotchi/context_absorber.rb +66 -13
- data/lib/samagotchi/desktop/macos/AgentTerminal.swift +260 -0
- data/lib/samagotchi/desktop/macos/App.swift +3 -3
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +5 -2
- data/lib/samagotchi/desktop/macos/Images.swift +2 -2
- data/lib/samagotchi/desktop/macos/Kitty.swift +33 -197
- data/lib/samagotchi/desktop/macos/Panel.swift +118 -82
- data/lib/samagotchi/desktop/macos/ProcessRunner.swift +1 -1
- data/lib/samagotchi/desktop/macos/Tmux.swift +239 -0
- data/lib/samagotchi/desktop/macos.rb +84 -13
- data/lib/samagotchi/desktop_command.rb +14 -3
- data/lib/samagotchi/engine.rb +96 -191
- data/lib/samagotchi/engine_peers.rb +3 -0
- data/lib/samagotchi/guardrail_wiring.rb +1 -1
- data/lib/samagotchi/guardrails/approval.rb +1 -1
- data/lib/samagotchi/guardrails/approvals.rb +1 -1
- data/lib/samagotchi/guardrails/rules.rb +1 -1
- data/lib/samagotchi/guardrails/targets.rb +1 -1
- data/lib/samagotchi/hook_routes.rb +151 -0
- data/lib/samagotchi/hooks/registry.rb +2 -2
- data/lib/samagotchi/host_keys.rb +147 -0
- data/lib/samagotchi/llm/adapter_common.rb +138 -0
- data/lib/samagotchi/llm/anthropic_messages.rb +28 -105
- data/lib/samagotchi/llm/api_key.rb +23 -7
- data/lib/samagotchi/llm/chat_loop.rb +10 -6
- data/lib/samagotchi/llm/openai_chat.rb +9 -92
- data/lib/samagotchi/llm/refusal_memo.rb +29 -0
- data/lib/samagotchi/llm_context_override.rb +1 -0
- data/lib/samagotchi/peer_sources.rb +58 -0
- data/lib/samagotchi/plugin/api.rb +64 -155
- data/lib/samagotchi/plugin/loader.rb +4 -2
- data/lib/samagotchi/plugin/peer.rb +394 -0
- data/lib/samagotchi/plugin/tool_spec.rb +166 -0
- data/lib/samagotchi/plugin_tasks.rb +2 -2
- data/lib/samagotchi/row_refs.rb +1 -1
- data/lib/samagotchi/self_report.rb +5 -3
- data/lib/samagotchi/send_command.rb +21 -11
- data/lib/samagotchi/session_continue.rb +58 -15
- data/lib/samagotchi/session_inbox.rb +0 -13
- data/lib/samagotchi/tool_call_parser.rb +1 -1
- data/lib/samagotchi/toolkit_binding.rb +138 -0
- data/lib/samagotchi/tools/context_read.rb +14 -0
- data/lib/samagotchi/tools/execute.rb +12 -12
- data/lib/samagotchi/tools/registry.rb +1 -1
- data/lib/samagotchi/tools/task_create.rb +6 -6
- data/lib/samagotchi/tools/task_get.rb +13 -13
- data/lib/samagotchi/tools/task_list.rb +6 -6
- data/lib/samagotchi/tools/task_record.rb +52 -0
- data/lib/samagotchi/tools/task_runtime.rb +55 -91
- data/lib/samagotchi/tools/task_stop.rb +6 -6
- data/lib/samagotchi/tools/task_wait.rb +12 -13
- data/lib/samagotchi/tools/tool_index.rb +454 -0
- data/lib/samagotchi/update_command.rb +9 -6
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +16 -9
- data/lib/samagotchi/web/capabilities.rb +1 -1
- data/lib/samagotchi/web/public/activity.js +2 -2
- data/lib/samagotchi/web/public/annotate_actions.js +1 -1
- data/lib/samagotchi/web/public/app.js +31 -36
- data/lib/samagotchi/web/public/chains.js +1 -1
- data/lib/samagotchi/web/public/commands.js +2 -2
- data/lib/samagotchi/web/public/composer_size.js +2 -2
- data/lib/samagotchi/web/public/copy.js +1 -1
- data/lib/samagotchi/web/public/ctx.js +1 -1
- data/lib/samagotchi/web/public/delivery_line.js +4 -4
- data/lib/samagotchi/web/public/families_view.js +1 -1
- data/lib/samagotchi/web/public/format.js +11 -7
- data/lib/samagotchi/web/public/images.js +25 -2
- data/lib/samagotchi/web/public/llm_context_chip.js +2 -2
- data/lib/samagotchi/web/public/notify.js +6 -4
- data/lib/samagotchi/web/public/question_card.js +1 -1
- data/lib/samagotchi/web/public/ref_scan.js +1 -1
- data/lib/samagotchi/web/public/session_state.js +14 -0
- data/lib/samagotchi/web/public/session_title.js +2 -3
- data/lib/samagotchi/web/public/sessions_list.js +0 -8
- data/lib/samagotchi/web/public/stage_model.js +2 -2
- data/lib/samagotchi/web/public/strip.js +3 -3
- data/lib/samagotchi/web/public/task_stop.js +3 -2
- data/lib/samagotchi/web/public/turn_events.js +1 -1
- data/lib/samagotchi/web/public/turn_html.js +2 -2
- data/lib/samagotchi/web/public/turn_model.js +2 -2
- data/lib/samagotchi/web/public/turn_view.js +1 -1
- data/lib/samagotchi/web/server.rb +0 -4
- data/lib/samagotchi/worker.rb +11 -8
- data/lib/samagotchi/worker_inbound.rb +36 -4
- data/lib/samagotchi/worker_wakes.rb +67 -7
- metadata +14 -1
|
@@ -8,11 +8,13 @@ require "time"
|
|
|
8
8
|
require_relative "../atomic_file"
|
|
9
9
|
require_relative "../process_group"
|
|
10
10
|
require_relative "shell"
|
|
11
|
+
require_relative "task_record"
|
|
11
12
|
|
|
12
13
|
module Samagotchi
|
|
13
14
|
module Tools
|
|
14
|
-
# Execute requires
|
|
15
|
-
#
|
|
15
|
+
# Execute requires this file (it hands a command over with adopt), and
|
|
16
|
+
# this one needs Execute only at run time: autoload keeps the two from
|
|
17
|
+
# requiring each other.
|
|
16
18
|
autoload :Execute, File.expand_path("execute", __dir__)
|
|
17
19
|
|
|
18
20
|
module TaskRuntime
|
|
@@ -22,7 +24,7 @@ module Samagotchi
|
|
|
22
24
|
EXIT_CODE_FILENAME = "exit_code"
|
|
23
25
|
STOP_GRACE_SEC = 3.0
|
|
24
26
|
STOP_POLL_INTERVAL_SEC = 0.1
|
|
25
|
-
METADATA_VERSION =
|
|
27
|
+
METADATA_VERSION = TaskRecord::METADATA_VERSION
|
|
26
28
|
OUTPUT_TAIL_READ_BYTES = 16 * 1024
|
|
27
29
|
SANITIZED_ENV_KEYS = %w[RUBYOPT RUBYLIB BUNDLE_GEMFILE BUNDLE_BIN_PATH BUNDLER_VERSION].freeze
|
|
28
30
|
# Set by chi (Builtins.parent_env); the model's env can't change it.
|
|
@@ -74,24 +76,9 @@ module Samagotchi
|
|
|
74
76
|
# Read now, compared before each signal: a pid reused later is not this task.
|
|
75
77
|
pid_started = ProcessGroup.start_time(pid)
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"id" => task_id,
|
|
81
|
-
"command" => normalized_command,
|
|
82
|
-
"cwd" => resolved_cwd,
|
|
83
|
-
"workspace_root" => Dir.pwd,
|
|
84
|
-
"status" => "running",
|
|
85
|
-
"pid" => pid,
|
|
86
|
-
"pid_started" => pid_started,
|
|
87
|
-
"created_at" => now,
|
|
88
|
-
"started_at" => now,
|
|
89
|
-
"finished_at" => nil,
|
|
90
|
-
"exit_code" => nil,
|
|
91
|
-
"stop_reason" => nil,
|
|
92
|
-
"output_path" => output_path,
|
|
93
|
-
"exit_code_path" => exit_code_path
|
|
94
|
-
}
|
|
79
|
+
record = TaskRecord.running(id: task_id, command: normalized_command, cwd: resolved_cwd, pid: pid,
|
|
80
|
+
pid_started: pid_started, started_at: timestamp,
|
|
81
|
+
output_path: output_path, exit_code_path: exit_code_path)
|
|
95
82
|
|
|
96
83
|
write_record(record)
|
|
97
84
|
[record, nil]
|
|
@@ -102,41 +89,29 @@ module Samagotchi
|
|
|
102
89
|
end
|
|
103
90
|
|
|
104
91
|
# A record for a command chi already runs: an execute still running at
|
|
105
|
-
# its limit, handed over as a task (Execute::HANDED_OVER)
|
|
92
|
+
# its limit, handed over as a task (Execute::HANDED_OVER), given as a
|
|
93
|
+
# TaskRecord.running whose +started_at+ is the spawn's. Its logs and
|
|
106
94
|
# exit_code file stay where execute put them, so the paths must be
|
|
107
95
|
# absolute; +stderr_path+ is a log of its own (task_wait tails it).
|
|
108
96
|
# The pid's start time is read here, at the hand-over, not at the
|
|
109
97
|
# spawn (a quick execute is spared the ps): read while execute's
|
|
110
98
|
# detach waiter is still running, so the pid is still its child.
|
|
111
|
-
# @param
|
|
112
|
-
# @return [Array(
|
|
113
|
-
def adopt(
|
|
99
|
+
# @param task [TaskRecord]
|
|
100
|
+
# @return [Array(TaskRecord, nil), Array(nil, String)] the record, or an error
|
|
101
|
+
def adopt(task)
|
|
102
|
+
id = task.id
|
|
114
103
|
return [nil, "Error: invalid task id: #{id}"] unless TASK_ID_SHAPE.match?(id.to_s)
|
|
115
104
|
return [nil, "Error: task already exists: #{id}"] if File.exist?(metadata_path(id))
|
|
116
105
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
106
|
+
relative = %i[output_path stderr_path exit_code_path].find { |key| !File.absolute_path?(task.public_send(key).to_s) }
|
|
107
|
+
return [nil, "Error: #{relative} must be absolute: #{task.public_send(relative)}"] if relative
|
|
108
|
+
|
|
109
|
+
pid = task.pid
|
|
120
110
|
return [nil, "Error: not a process chi can stop: #{pid.inspect}"] unless ProcessGroup.signalable?(pid)
|
|
121
111
|
|
|
122
|
-
|
|
123
|
-
record =
|
|
124
|
-
|
|
125
|
-
"id" => id,
|
|
126
|
-
"command" => command.to_s.strip,
|
|
127
|
-
"cwd" => File.expand_path(cwd.to_s.empty? ? Dir.pwd : cwd),
|
|
128
|
-
"workspace_root" => Dir.pwd,
|
|
129
|
-
"status" => "running",
|
|
130
|
-
"pid" => pid,
|
|
131
|
-
"pid_started" => ProcessGroup.start_time(pid),
|
|
132
|
-
"created_at" => started,
|
|
133
|
-
"started_at" => started,
|
|
134
|
-
"finished_at" => nil,
|
|
135
|
-
"exit_code" => nil,
|
|
136
|
-
"stop_reason" => nil,
|
|
137
|
-
**paths,
|
|
138
|
-
"handed_over_by" => Execute::NAME
|
|
139
|
-
}
|
|
112
|
+
cwd = task.cwd
|
|
113
|
+
record = task.with(command: task.command.to_s.strip, cwd: File.expand_path(cwd.to_s.empty? ? Dir.pwd : cwd),
|
|
114
|
+
pid_started: ProcessGroup.start_time(pid), handed_over_by: Execute::NAME)
|
|
140
115
|
write_record(record)
|
|
141
116
|
[record, nil]
|
|
142
117
|
rescue StandardError => e
|
|
@@ -154,7 +129,7 @@ module Samagotchi
|
|
|
154
129
|
next nil unless record
|
|
155
130
|
|
|
156
131
|
refresh_record(record)
|
|
157
|
-
end.compact.sort_by { |record| record
|
|
132
|
+
end.compact.sort_by { |record| record.created_at.to_s }.reverse
|
|
158
133
|
end
|
|
159
134
|
|
|
160
135
|
# The tasks this conversation started that are still running: its
|
|
@@ -166,7 +141,7 @@ module Samagotchi
|
|
|
166
141
|
def running_created_in(messages)
|
|
167
142
|
created_ids_in(messages).filter_map do |id|
|
|
168
143
|
record, _error = get_record(id)
|
|
169
|
-
{ id: id, command: record
|
|
144
|
+
{ id: id, command: record.command.to_s } if record&.running?
|
|
170
145
|
end
|
|
171
146
|
end
|
|
172
147
|
|
|
@@ -232,14 +207,12 @@ module Samagotchi
|
|
|
232
207
|
path = File.join(root || Dir.pwd, TASKS_DIR, id, METADATA_FILENAME)
|
|
233
208
|
return nil unless File.file?(path)
|
|
234
209
|
|
|
235
|
-
command = JSON.parse(File.read(path))
|
|
210
|
+
command = TaskRecord.from_file(JSON.parse(File.read(path)))&.command
|
|
236
211
|
command.is_a?(String) && !command.strip.empty? ? command : nil
|
|
237
212
|
rescue StandardError
|
|
238
213
|
nil
|
|
239
214
|
end
|
|
240
215
|
|
|
241
|
-
def stopped_by_user?(record) = record["stop_reason"] == "stopped_by_user"
|
|
242
|
-
|
|
243
216
|
def get_record(task_id)
|
|
244
217
|
record = load_record(task_id)
|
|
245
218
|
return [nil, not_found(task_id)] unless record
|
|
@@ -271,19 +244,18 @@ module Samagotchi
|
|
|
271
244
|
return [nil, not_found(task_id)] unless record
|
|
272
245
|
|
|
273
246
|
refreshed = refresh_record(record)
|
|
274
|
-
return [refreshed, nil] unless refreshed
|
|
247
|
+
return [refreshed, nil] unless refreshed.running?
|
|
275
248
|
|
|
276
|
-
pid = refreshed["pid"]
|
|
277
249
|
# Checked again just before the signal: never a group chi didn't start.
|
|
278
|
-
return [mark_not_chis(refreshed), nil] unless chis_group?(refreshed, started: refreshed
|
|
250
|
+
return [mark_not_chis(refreshed), nil] unless chis_group?(refreshed, started: refreshed.pid_started)
|
|
279
251
|
|
|
280
|
-
refreshed
|
|
281
|
-
write_record(
|
|
252
|
+
flagged = refreshed.with(stop_requested_by: by.to_s)
|
|
253
|
+
write_record(flagged)
|
|
282
254
|
|
|
283
255
|
# A group that isn't ours (EPERM) is an error, not a stop.
|
|
284
|
-
ProcessGroup.stop(pid, grace: STOP_GRACE_SEC, poll: STOP_POLL_INTERVAL_SEC, quiet: [Errno::ESRCH])
|
|
256
|
+
ProcessGroup.stop(flagged.pid, grace: STOP_GRACE_SEC, poll: STOP_POLL_INTERVAL_SEC, quiet: [Errno::ESRCH])
|
|
285
257
|
|
|
286
|
-
updated = mark_stopped(refresh_record(
|
|
258
|
+
updated = mark_stopped(refresh_record(flagged))
|
|
287
259
|
write_record(updated)
|
|
288
260
|
|
|
289
261
|
[updated, nil]
|
|
@@ -292,10 +264,10 @@ module Samagotchi
|
|
|
292
264
|
end
|
|
293
265
|
|
|
294
266
|
def refresh_record(record)
|
|
295
|
-
return record unless record
|
|
267
|
+
return record unless record.running?
|
|
296
268
|
|
|
297
|
-
pid = record
|
|
298
|
-
return record if chis_group?(record, started: record
|
|
269
|
+
pid = record.pid
|
|
270
|
+
return record if chis_group?(record, started: record.pid_started)
|
|
299
271
|
# Gone: its exit code says how it ended.
|
|
300
272
|
return mark_finished_without_exit_code(record) if ProcessGroup.signalable?(pid) && !ProcessGroup.alive?(pid)
|
|
301
273
|
|
|
@@ -307,7 +279,7 @@ module Samagotchi
|
|
|
307
279
|
# ps per call on macOS (~5 ms), asked by each refresh (task_list,
|
|
308
280
|
# task_get, each task_wait poll) so a reused pid reads as exited, and
|
|
309
281
|
# by task_stop again just before the signal.
|
|
310
|
-
def chis_group?(record, started: nil) = ProcessGroup.leader?(record
|
|
282
|
+
def chis_group?(record, started: nil) = ProcessGroup.leader?(record.pid, started: started)
|
|
311
283
|
|
|
312
284
|
# A running record whose pid isn't a group chi started (NOT_CHIS_PROCESS)
|
|
313
285
|
# ends as failed, unless its exit code or a stop on disk says more.
|
|
@@ -338,18 +310,18 @@ module Samagotchi
|
|
|
338
310
|
path = metadata_path(task_id)
|
|
339
311
|
return nil unless File.exist?(path)
|
|
340
312
|
|
|
341
|
-
JSON.parse(File.read(path))
|
|
313
|
+
TaskRecord.from_file(JSON.parse(File.read(path)))
|
|
342
314
|
rescue JSON::ParserError
|
|
343
315
|
nil
|
|
344
316
|
end
|
|
345
317
|
|
|
346
318
|
def write_record(record)
|
|
347
319
|
ensure_tasks_dir
|
|
348
|
-
dir = task_dir_for(record.
|
|
320
|
+
dir = task_dir_for(record.id)
|
|
349
321
|
FileUtils.mkdir_p(dir)
|
|
350
322
|
|
|
351
323
|
path = File.join(dir, METADATA_FILENAME)
|
|
352
|
-
AtomicFile.write(path, JSON.pretty_generate(record) + "\n")
|
|
324
|
+
AtomicFile.write(path, JSON.pretty_generate(record.to_file) + "\n")
|
|
353
325
|
end
|
|
354
326
|
|
|
355
327
|
def metadata_path(task_id)
|
|
@@ -434,26 +406,20 @@ module Samagotchi
|
|
|
434
406
|
|
|
435
407
|
def mark_finished_without_exit_code(record, missing_reason: "process_ended_without_exit_code")
|
|
436
408
|
record = current_record(record)
|
|
437
|
-
return record unless record
|
|
438
|
-
|
|
439
|
-
exit_code = read_exit_code(record
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
else
|
|
452
|
-
record["status"] = "failed"
|
|
453
|
-
record["exit_code"] = exit_code
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
record["finished_at"] ||= timestamp
|
|
409
|
+
return record unless record.running?
|
|
410
|
+
|
|
411
|
+
exit_code = read_exit_code(record.exit_code_path)
|
|
412
|
+
record =
|
|
413
|
+
if record.stop_requested_by
|
|
414
|
+
mark_stopped(record.with(exit_code: exit_code))
|
|
415
|
+
elsif exit_code.nil?
|
|
416
|
+
record.with(status: "failed", exit_code: nil, stop_reason: record.stop_reason || missing_reason)
|
|
417
|
+
elsif exit_code.zero?
|
|
418
|
+
record.with(status: "completed", exit_code: 0)
|
|
419
|
+
else
|
|
420
|
+
record.with(status: "failed", exit_code: exit_code)
|
|
421
|
+
end
|
|
422
|
+
record = record.with(finished_at: record.finished_at || timestamp)
|
|
457
423
|
write_record(record)
|
|
458
424
|
record
|
|
459
425
|
end
|
|
@@ -461,18 +427,16 @@ module Samagotchi
|
|
|
461
427
|
# The disk copy when it is newer than +record+: already finished, or
|
|
462
428
|
# flagged by a stop that started after +record+ was read.
|
|
463
429
|
def current_record(record)
|
|
464
|
-
disk = load_record(record.
|
|
430
|
+
disk = load_record(record.id)
|
|
465
431
|
return record unless disk
|
|
466
|
-
return disk if disk
|
|
432
|
+
return disk if !disk.running? || disk.stop_requested_by
|
|
467
433
|
|
|
468
434
|
record
|
|
469
435
|
end
|
|
470
436
|
|
|
471
437
|
def mark_stopped(record)
|
|
472
|
-
record
|
|
473
|
-
|
|
474
|
-
record["finished_at"] ||= timestamp
|
|
475
|
-
record
|
|
438
|
+
record.with(status: "stopped", stop_reason: "stopped_by_#{record.stop_requested_by}",
|
|
439
|
+
finished_at: record.finished_at || timestamp)
|
|
476
440
|
end
|
|
477
441
|
|
|
478
442
|
def read_exit_code(path)
|
|
@@ -14,12 +14,12 @@ module Samagotchi
|
|
|
14
14
|
return error if error
|
|
15
15
|
|
|
16
16
|
[
|
|
17
|
-
"task_id: #{record.
|
|
18
|
-
"status: #{record.
|
|
19
|
-
"exit_code: #{record
|
|
20
|
-
"finished_at: #{record
|
|
21
|
-
"stop_reason: #{record
|
|
22
|
-
"output_path: #{record.
|
|
17
|
+
"task_id: #{record.id}",
|
|
18
|
+
"status: #{record.status}",
|
|
19
|
+
"exit_code: #{record.exit_code}",
|
|
20
|
+
"finished_at: #{record.finished_at}",
|
|
21
|
+
"stop_reason: #{record.stop_reason}",
|
|
22
|
+
"output_path: #{record.output_path}"
|
|
23
23
|
].join("\n")
|
|
24
24
|
rescue StandardError => e
|
|
25
25
|
"Error: #{e.message}"
|
|
@@ -39,8 +39,7 @@ module Samagotchi
|
|
|
39
39
|
record, error = TaskRuntime.get_record(task_id)
|
|
40
40
|
return error if error
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
return finished_response(record, tail_lines) unless status == "running"
|
|
42
|
+
return finished_response(record, tail_lines) unless record.running?
|
|
44
43
|
|
|
45
44
|
if completion_pattern
|
|
46
45
|
output_tail = output_tail(record, tail_lines)
|
|
@@ -56,7 +55,7 @@ module Samagotchi
|
|
|
56
55
|
|
|
57
56
|
# Timeout reached — do one final refresh to capture the latest status
|
|
58
57
|
refreshed, = TaskRuntime.get_record(task_id)
|
|
59
|
-
return finished_response(refreshed, tail_lines) unless refreshed.
|
|
58
|
+
return finished_response(refreshed, tail_lines) unless refreshed.running?
|
|
60
59
|
|
|
61
60
|
format_response(refreshed, wait_result: "timeout", output_tail: output_tail(refreshed, tail_lines), tail_lines: tail_lines)
|
|
62
61
|
rescue StandardError => e
|
|
@@ -68,7 +67,7 @@ module Samagotchi
|
|
|
68
67
|
def self.cancelled_response(task_id, tail_lines, started)
|
|
69
68
|
record, error = TaskRuntime.get_record(task_id)
|
|
70
69
|
return error if error
|
|
71
|
-
return finished_response(record, tail_lines) unless record.
|
|
70
|
+
return finished_response(record, tail_lines) unless record.running?
|
|
72
71
|
|
|
73
72
|
note = [
|
|
74
73
|
"note: the user stopped the turn while waiting; the task is STILL RUNNING.",
|
|
@@ -83,7 +82,7 @@ module Samagotchi
|
|
|
83
82
|
# A task the user stopped also says so, with the tail of its output:
|
|
84
83
|
# the model sees where the job was cut off.
|
|
85
84
|
def self.finished_response(record, tail_lines)
|
|
86
|
-
return format_response(record) unless
|
|
85
|
+
return format_response(record) unless record.stopped_by_user?
|
|
87
86
|
|
|
88
87
|
format_response(record, output_tail: output_tail(record, tail_lines), tail_lines: tail_lines,
|
|
89
88
|
extra: [TaskRuntime::USER_STOP_NOTE])
|
|
@@ -91,14 +90,14 @@ module Samagotchi
|
|
|
91
90
|
private_class_method :finished_response
|
|
92
91
|
|
|
93
92
|
def self.output_tail(record, tail_lines)
|
|
94
|
-
TaskRuntime.output_tail_lines(record.
|
|
93
|
+
TaskRuntime.output_tail_lines(record.output_path, tail_lines)
|
|
95
94
|
end
|
|
96
95
|
private_class_method :output_tail
|
|
97
96
|
|
|
98
97
|
# The last lines of a handed-over execute's stderr log (stderr_path),
|
|
99
98
|
# "" for a task_create task (one log) or an empty stderr.
|
|
100
99
|
def self.stderr_tail(record, tail_lines)
|
|
101
|
-
path = record
|
|
100
|
+
path = record.stderr_path
|
|
102
101
|
return "" if path.to_s.empty? || tail_lines.nil?
|
|
103
102
|
|
|
104
103
|
TaskRuntime.output_tail_lines(path, [tail_lines, STDERR_TAIL_LINES_MAX].min)
|
|
@@ -107,13 +106,13 @@ module Samagotchi
|
|
|
107
106
|
|
|
108
107
|
def self.format_response(record, wait_result: nil, output_tail: nil, tail_lines: nil, extra: [])
|
|
109
108
|
lines = [
|
|
110
|
-
"task_id: #{record.
|
|
111
|
-
"status: #{record.
|
|
112
|
-
"exit_code: #{record
|
|
113
|
-
"stop_reason: #{record
|
|
114
|
-
"output_path: #{record.
|
|
109
|
+
"task_id: #{record.id}",
|
|
110
|
+
"status: #{record.status}",
|
|
111
|
+
"exit_code: #{record.exit_code}",
|
|
112
|
+
"stop_reason: #{record.stop_reason}",
|
|
113
|
+
"output_path: #{record.output_path}"
|
|
115
114
|
]
|
|
116
|
-
lines << "stderr_path: #{record
|
|
115
|
+
lines << "stderr_path: #{record.stderr_path}" if record.stderr_path
|
|
117
116
|
return lines.join("\n") unless output_tail
|
|
118
117
|
|
|
119
118
|
lines << "wait_result: #{wait_result}" if wait_result
|