samagotchi 0.16.0 → 0.18.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 +149 -1
- data/README.md +2 -0
- data/docs/architecture.md +38 -2
- data/docs/cli.md +37 -9
- data/docs/configuration.md +8 -6
- data/docs/guardrails.md +51 -8
- data/docs/hooks.md +4 -1
- data/docs/internals/background-tasks.md +1 -0
- data/docs/memory.md +41 -0
- data/docs/plugins.md +5 -2
- data/docs/sessions.md +8 -6
- data/docs/sub-agent.md +43 -4
- data/docs/testing.md +47 -0
- data/lib/samagotchi/answer_command.rb +23 -2
- data/lib/samagotchi/answer_tail.rb +52 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +20 -5
- data/lib/samagotchi/bridge.rb +86 -14
- data/lib/samagotchi/bridge_client.rb +18 -3
- data/lib/samagotchi/bundle_command.rb +121 -4
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +18 -3
- data/lib/samagotchi/bundles/guardrails/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +5 -5
- data/lib/samagotchi/bundles/system/self_map.md +1 -0
- data/lib/samagotchi/commands/registry.rb +28 -0
- data/lib/samagotchi/config.rb +7 -1
- data/lib/samagotchi/continue_offer.rb +177 -0
- data/lib/samagotchi/engine.rb +73 -19
- data/lib/samagotchi/guardrails/outside.rb +46 -0
- data/lib/samagotchi/guardrails/parent_approvals.rb +2 -0
- data/lib/samagotchi/guardrails/parent_continue.rb +43 -0
- data/lib/samagotchi/guardrails/rules.rb +17 -5
- data/lib/samagotchi/guardrails/shell_git_dirs.rb +267 -0
- data/lib/samagotchi/guardrails/targets.rb +30 -13
- data/lib/samagotchi/guardrails.rb +2 -0
- data/lib/samagotchi/iteration_limit.rb +38 -0
- data/lib/samagotchi/kernel_loop.rb +2 -1
- data/lib/samagotchi/llm/backend.rb +2 -1
- data/lib/samagotchi/llm/chat_loop.rb +2 -1
- data/lib/samagotchi/llm/native_backend.rb +2 -1
- data/lib/samagotchi/memory_bundle/builder.rb +22 -0
- data/lib/samagotchi/memory_bundle/index_updater.rb +17 -6
- data/lib/samagotchi/memory_bundle/installer.rb +60 -21
- data/lib/samagotchi/memory_bundle/profile.rb +9 -4
- data/lib/samagotchi/memory_bundle/provenance.rb +14 -0
- data/lib/samagotchi/memory_bundle/shipped_update.rb +5 -3
- data/lib/samagotchi/memory_bundle/system_bundle.rb +5 -3
- data/lib/samagotchi/memory_bundle/trash.rb +136 -0
- data/lib/samagotchi/memory_bundle/uninstaller.rb +34 -5
- data/lib/samagotchi/parent_report.rb +39 -9
- data/lib/samagotchi/plugin/context.rb +14 -2
- data/lib/samagotchi/question_desk.rb +205 -46
- data/lib/samagotchi/reply_wait.rb +10 -4
- data/lib/samagotchi/self_report.rb +49 -3
- data/lib/samagotchi/send_command.rb +46 -6
- data/lib/samagotchi/session.rb +3 -2
- data/lib/samagotchi/session_commands.rb +3 -2
- data/lib/samagotchi/session_manager.rb +4 -1
- data/lib/samagotchi/session_metrics.rb +137 -48
- data/lib/samagotchi/sessions_command.rb +3 -2
- data/lib/samagotchi/system_prompt.rb +46 -5
- data/lib/samagotchi/terminal_ui/attached_loop.rb +39 -6
- data/lib/samagotchi/terminal_ui/event_renderer.rb +2 -1
- data/lib/samagotchi/terminal_ui/question_prompt.rb +34 -2
- data/lib/samagotchi/terminal_ui.rb +35 -2
- data/lib/samagotchi/tool_declarations.rb +2 -2
- data/lib/samagotchi/tools/builtins.rb +11 -3
- data/lib/samagotchi/tools/delegate_relay.rb +4 -0
- data/lib/samagotchi/tools/delegate_wait.rb +14 -5
- data/lib/samagotchi/tools/edit.rb +6 -1
- data/lib/samagotchi/tools/execute.rb +5 -4
- data/lib/samagotchi/tools/peers.rb +3 -2
- data/lib/samagotchi/tools/read.rb +2 -1
- data/lib/samagotchi/tools/task_runtime.rb +1 -1
- data/lib/samagotchi/turn_flow.rb +8 -7
- data/lib/samagotchi/turn_note.rb +7 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +189 -43
- data/lib/samagotchi/web/public/app.js +157 -51
- data/lib/samagotchi/web/public/data.js +12 -13
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/notify.js +12 -5
- data/lib/samagotchi/web/public/question_card.js +28 -1
- data/lib/samagotchi/web/public/question_cards.js +74 -3
- data/lib/samagotchi/web/public/sessions_list.js +1 -0
- data/lib/samagotchi/web/public/stage_view.js +13 -1
- data/lib/samagotchi/web/public/strip.js +11 -0
- data/lib/samagotchi/web/public/timing.js +67 -12
- data/lib/samagotchi/web/public/turn_events.js +74 -3
- data/lib/samagotchi/web/public/update_notice.js +15 -0
- data/lib/samagotchi/worker.rb +58 -60
- data/lib/samagotchi/worker_sidecar.rb +7 -1
- metadata +10 -1
|
@@ -5,6 +5,7 @@ require "fileutils"
|
|
|
5
5
|
require "io/console"
|
|
6
6
|
require "reline"
|
|
7
7
|
|
|
8
|
+
require_relative "iteration_limit"
|
|
8
9
|
require_relative "model_profile"
|
|
9
10
|
require_relative "turn_note"
|
|
10
11
|
require_relative "config"
|
|
@@ -232,6 +233,10 @@ module Samagotchi
|
|
|
232
233
|
@engine.session = session
|
|
233
234
|
messages = messages_for(session)
|
|
234
235
|
|
|
236
|
+
# As typed at the prompt: a session command (/models, !ls) runs as
|
|
237
|
+
# the command, not as a turn.
|
|
238
|
+
return run_one_shot_command(session) if @prompt && @non_interactive && command_registry.command?(@prompt)
|
|
239
|
+
|
|
235
240
|
if @prompt && @non_interactive
|
|
236
241
|
# Headless / CI mode: run directly without TTY rendering.
|
|
237
242
|
ArchiveStore.user_input(session.id, state_dir: Session.default_state_dir)
|
|
@@ -239,7 +244,7 @@ module Samagotchi
|
|
|
239
244
|
session,
|
|
240
245
|
@prompt,
|
|
241
246
|
on_event: nil,
|
|
242
|
-
max_iterations:
|
|
247
|
+
max_iterations: IterationLimit.for(no_interrupt: true),
|
|
243
248
|
cancel_controller: nil,
|
|
244
249
|
images: ImageInput.extract(@prompt)
|
|
245
250
|
)
|
|
@@ -281,6 +286,22 @@ module Samagotchi
|
|
|
281
286
|
|
|
282
287
|
EMPTY_ANSWER_ERROR = "chi: the model gave an empty answer"
|
|
283
288
|
|
|
289
|
+
# `chi -p "/models" --non-interactive`: the command's output; a refused
|
|
290
|
+
# one fails the run (bin/chi exits 1). The session is saved only when
|
|
291
|
+
# the command changed it.
|
|
292
|
+
# @return [Symbol, nil] :turn_failed when the command was refused
|
|
293
|
+
def run_one_shot_command(session)
|
|
294
|
+
line = @prompt.strip
|
|
295
|
+
result = begin
|
|
296
|
+
@commands.run(line)
|
|
297
|
+
rescue StandardError => e
|
|
298
|
+
SessionCommands::Result.new(status: :error, output: "#{line.split.first}: #{e.message}", changed: [])
|
|
299
|
+
end
|
|
300
|
+
@surface.commit(result.output) unless result.output.to_s.empty?
|
|
301
|
+
session.save unless Array(result.changed).empty?
|
|
302
|
+
:turn_failed if result.status == :error
|
|
303
|
+
end
|
|
304
|
+
|
|
284
305
|
# Delete the scratch session, the last thing the REPL does. Quiet after
|
|
285
306
|
# a one-shot or on the way out of an error or a signal.
|
|
286
307
|
def delete_scratch(session, quiet:)
|
|
@@ -465,7 +486,7 @@ module Samagotchi
|
|
|
465
486
|
# Run one REPL turn (a prompt, or a continue/reminder turn with nil) through
|
|
466
487
|
# Engine#run_turn, rendering via @renderer.
|
|
467
488
|
# @param images [Array<Hash>] the prompt's `@path` images ({path:})
|
|
468
|
-
def run_engine_turn(session, prompt, continue: false, max_iterations:
|
|
489
|
+
def run_engine_turn(session, prompt, continue: false, max_iterations: IterationLimit.for, images: [])
|
|
469
490
|
cancellation_controller = CancellationController.new
|
|
470
491
|
@active_cancel_controller = cancellation_controller
|
|
471
492
|
@renderer.begin_turn
|
|
@@ -635,6 +656,9 @@ module Samagotchi
|
|
|
635
656
|
return
|
|
636
657
|
end
|
|
637
658
|
return if show_local_command(input)
|
|
659
|
+
# A typo of a command (/modle) is not a prompt: say so, send nothing.
|
|
660
|
+
hint = command_registry.unknown_command_hint(input)
|
|
661
|
+
return @surface.commit(hint) if hint
|
|
638
662
|
|
|
639
663
|
@turn_flow.before_prompt_turn
|
|
640
664
|
persist_recent_history(input)
|
|
@@ -921,6 +945,11 @@ module Samagotchi
|
|
|
921
945
|
# A command, never steering text (/archive waits, back in the prompt).
|
|
922
946
|
return command_during_turn(line) if local || command_registry.command?(line)
|
|
923
947
|
return true if line.strip.empty?
|
|
948
|
+
# A typo of a command (/modle) is not steering text: say so, take it.
|
|
949
|
+
if (hint = command_registry.unknown_command_hint(line))
|
|
950
|
+
@surface.commit(hint)
|
|
951
|
+
return true
|
|
952
|
+
end
|
|
924
953
|
# Steering merges text only: a line with images runs as the next turn.
|
|
925
954
|
return image_line_waits if ImageInput.extract(line).any?
|
|
926
955
|
|
|
@@ -1184,6 +1213,10 @@ module Samagotchi
|
|
|
1184
1213
|
|
|
1185
1214
|
# A ? read of its own, off a terminal (specs, pipes): $stdin.gets.
|
|
1186
1215
|
def read_choice_line(question_prompt)
|
|
1216
|
+
# Off a terminal the ? prompt can't be taken back: at the input's end
|
|
1217
|
+
# it would stay bare above the summary. eof? waits as gets would.
|
|
1218
|
+
return nil if !$stdin.tty? && $stdin.eof?
|
|
1219
|
+
|
|
1187
1220
|
@surface.set_slot(:editor, [question_prompt])
|
|
1188
1221
|
line = $stdin.gets
|
|
1189
1222
|
# A pipe doesn't echo: the line completes the ? prompt here, once,
|
|
@@ -46,11 +46,11 @@ module Samagotchi
|
|
|
46
46
|
},
|
|
47
47
|
start_line: {
|
|
48
48
|
type: "integer",
|
|
49
|
-
description: "Optional start line (1-based, inclusive).
|
|
49
|
+
description: "Optional start line (1-based, inclusive). Without end_line, reads to the end of the file."
|
|
50
50
|
},
|
|
51
51
|
end_line: {
|
|
52
52
|
type: "integer",
|
|
53
|
-
description: "Optional end line (1-based, inclusive).
|
|
53
|
+
description: "Optional end line (1-based, inclusive). Without start_line, reads from line 1."
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
required: ["path"]
|
|
@@ -105,12 +105,20 @@ module Samagotchi
|
|
|
105
105
|
# What execute and task_create export into the commands they run: a
|
|
106
106
|
# chi started there answers as a parent agent
|
|
107
107
|
# (Guardrails::ParentApprovals.parent_process?), even under a PTY
|
|
108
|
-
# wrapper. The session's id, "chi" without one.
|
|
108
|
+
# wrapper. The session's id, "chi" without one. And the model the
|
|
109
|
+
# session runs on (SAMAGOTCHI_SESSION_MODEL, read live, so right after
|
|
110
|
+
# /model), which chi self reports; unset when unknown, so a value
|
|
111
|
+
# inherited from an outer session never passes for this one.
|
|
109
112
|
def parent_env(kctx)
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
peers = kctx.peers
|
|
114
|
+
id = peers.respond_to?(:session_id) ? peers.session_id.to_s : ""
|
|
115
|
+
model = peers.respond_to?(:model_ref) ? peers.model_ref.to_s : ""
|
|
116
|
+
{ Guardrails::ParentApprovals::PARENT_SESSION_ENV => id.empty? ? "chi" : id,
|
|
117
|
+
SESSION_MODEL_ENV => model.empty? ? nil : model }
|
|
112
118
|
end
|
|
113
119
|
|
|
120
|
+
SESSION_MODEL_ENV = "SAMAGOTCHI_SESSION_MODEL"
|
|
121
|
+
|
|
114
122
|
# Stop flips the turn's controller, seen through the Engine's PeerView
|
|
115
123
|
# (as DelegateWait does); a bare kernel has no peers: never cancelled.
|
|
116
124
|
def cancelled_proc(kctx)
|
|
@@ -55,6 +55,10 @@ module Samagotchi
|
|
|
55
55
|
fields = Relay.card(child, question, relay_id: relay_id, more: more)
|
|
56
56
|
answer = relay.open_question(fields, watch: watch(child_id, qid, state_dir))
|
|
57
57
|
settle(answer, fields, client: client, desk: desk, relay_id: relay_id, qid: qid)
|
|
58
|
+
ensure
|
|
59
|
+
# A relay that failed before it settled closes, so the desk (which
|
|
60
|
+
# prunes only settled ones) keeps none open; a settled one stays.
|
|
61
|
+
desk&.close(relay_id) if relay_id
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
# Closes the card when the child's question is no longer pending (it
|
|
@@ -139,7 +139,7 @@ module Samagotchi
|
|
|
139
139
|
when :canceled
|
|
140
140
|
canceled_result(child_id)
|
|
141
141
|
when :no_reply
|
|
142
|
-
result(child_id, status, "#{no_reply_text(wait)}; its session shows what happened")
|
|
142
|
+
result(child_id, status, "#{no_reply_text(wait, child_id)}; its session shows what happened")
|
|
143
143
|
when :worker_gone
|
|
144
144
|
result(child_id, status,
|
|
145
145
|
"the child's worker is gone (it stopped or crashed); delegate with session: #{child_id} starts it again with a message")
|
|
@@ -168,8 +168,8 @@ module Samagotchi
|
|
|
168
168
|
end
|
|
169
169
|
|
|
170
170
|
# ParentReport's line for a turn that left no reply, about the child.
|
|
171
|
-
def no_reply_text(wait)
|
|
172
|
-
ParentReport.no_reply_line(wait).sub(/\Athe turn/, "the child's turn")
|
|
171
|
+
def no_reply_text(wait, child_id)
|
|
172
|
+
ParentReport.no_reply_line(wait, child_id).sub(/\Athe turn/, "the child's turn")
|
|
173
173
|
end
|
|
174
174
|
|
|
175
175
|
def result(child_id, status, text)
|
|
@@ -184,9 +184,18 @@ module Samagotchi
|
|
|
184
184
|
|
|
185
185
|
# The whole question, as `chi send --wait` prints it (ParentReport):
|
|
186
186
|
# its text, options and the commands that answer it.
|
|
187
|
+
# A step-limit question (kind continue) is the parent model's to
|
|
188
|
+
# decide (it isn't relayed: Continue grants no permission): continue
|
|
189
|
+
# it with chi answer through execute, send a follow-up instead, or
|
|
190
|
+
# report back.
|
|
187
191
|
def waiting_text(child_id, pending)
|
|
188
|
-
"Child #{child_id} is #{ParentReport.question_text(pending, session_id: child_id)}"
|
|
189
|
-
|
|
192
|
+
text = "Child #{child_id} is #{ParentReport.question_text(pending, session_id: child_id)}"
|
|
193
|
+
if ParentReport.continue?(pending)
|
|
194
|
+
text += "The child ran out of steps before it answered. Decide: continue it (run the chi answer command " \
|
|
195
|
+
"above with execute) if it is getting somewhere; send it a narrower follow-up with delegate " \
|
|
196
|
+
"session: #{child_id} (that drops the question); or stop it and report back to your user.\n"
|
|
197
|
+
end
|
|
198
|
+
"#{text}delegate_result #{child_id} waits again once it is answered."
|
|
190
199
|
end
|
|
191
200
|
|
|
192
201
|
def cut(text)
|
|
@@ -62,6 +62,9 @@ module Samagotchi
|
|
|
62
62
|
"Error: #{e.message}"
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
START_LINE_REQUIRED = "Error: end_line alone isn't enough for a range edit; " \
|
|
66
|
+
"pass start_line too (1-based, the first line to replace)"
|
|
67
|
+
|
|
65
68
|
def self.apply_range_mode(new_text, path:, start_line:, end_line:, read:)
|
|
66
69
|
return "Error: file not found: #{path}" unless File.exist?(path)
|
|
67
70
|
return NEW_TEXT_REQUIRED if new_text.nil?
|
|
@@ -70,7 +73,9 @@ module Samagotchi
|
|
|
70
73
|
|
|
71
74
|
start_num = parse_positive_line_number(start_line, "start_line")
|
|
72
75
|
return start_num if start_num.is_a?(String)
|
|
73
|
-
|
|
76
|
+
# Not defaulted to 1 like read: a range edit replaces the span, so a
|
|
77
|
+
# guessed start would silently overwrite the top of the file.
|
|
78
|
+
return START_LINE_REQUIRED if start_num.nil?
|
|
74
79
|
|
|
75
80
|
source = read.(path)
|
|
76
81
|
lines = source.lines
|
|
@@ -15,11 +15,12 @@ module Samagotchi
|
|
|
15
15
|
# <tool name="execute">ruby path/to/script.rb</tool>
|
|
16
16
|
class Execute
|
|
17
17
|
NAME = "execute"
|
|
18
|
-
TIMEOUT_SEC =
|
|
18
|
+
TIMEOUT_SEC = 120
|
|
19
19
|
STOP_GRACE_SEC = 1.0
|
|
20
20
|
STOP_POLL_INTERVAL_SEC = 0.05
|
|
21
21
|
WAIT_SLICE_SEC = 0.2
|
|
22
22
|
NOT_RUN_ON_STOP = "Error: not run, the user stopped the turn"
|
|
23
|
+
TIMEOUT_HINT = "(killed at the limit; for a long command use task_create, then task_wait)"
|
|
23
24
|
|
|
24
25
|
def self.name = NAME
|
|
25
26
|
|
|
@@ -51,9 +52,9 @@ module Samagotchi
|
|
|
51
52
|
parts << (silent ? "exit: #{status.exitstatus} (no output)" : "exit: #{status.exitstatus}")
|
|
52
53
|
parts.join("\n")
|
|
53
54
|
rescue CommandTimedOut => e
|
|
54
|
-
# Keep the "Error:" first line (callers classify on it)
|
|
55
|
-
#
|
|
56
|
-
stopped_result("Error: command timed out after #{timeout_sec}s", e)
|
|
55
|
+
# Keep the "Error:" first line (callers classify on it), say how to run
|
|
56
|
+
# a long command, and return whatever it printed before it was killed.
|
|
57
|
+
stopped_result("Error: command timed out after #{timeout_sec}s\n#{TIMEOUT_HINT}", e)
|
|
57
58
|
rescue CommandCancelled => e
|
|
58
59
|
stopped_result("Error: command stopped by the user after #{e.elapsed.round}s (killed; rerun it if still needed)", e)
|
|
59
60
|
rescue => e
|
|
@@ -7,10 +7,11 @@ module Samagotchi
|
|
|
7
7
|
# folder, where sessions live, and whether the running turn was
|
|
8
8
|
# canceled (a waiting tool returns on it), and the question flow a
|
|
9
9
|
# delegate's approval is relayed through (relay: nil when this session
|
|
10
|
-
# can't host one)
|
|
10
|
+
# can't host one), and the model the session runs on (model_ref: the
|
|
11
|
+
# resolved ref, live after /model). Engine hands KernelLoop one that follows its current
|
|
11
12
|
# session (Engine::PeerView); this plain one is for specs and a kernel
|
|
12
13
|
# without an Engine.
|
|
13
|
-
Peers = Struct.new(:session_id, :cwd, :state_dir, :cancelled, :relay, keyword_init: true) do
|
|
14
|
+
Peers = Struct.new(:session_id, :cwd, :state_dir, :cancelled, :relay, :model_ref, keyword_init: true) do
|
|
14
15
|
# @return [Boolean] the turn was canceled (:cancelled is a proc or a value)
|
|
15
16
|
def cancelled?
|
|
16
17
|
cancelled.respond_to?(:call) ? !!cancelled.call : !!cancelled
|
|
@@ -124,7 +124,8 @@ module Samagotchi
|
|
|
124
124
|
|
|
125
125
|
start_num = parse_positive_line_number(start_line, "start_line")
|
|
126
126
|
return start_num if start_num.is_a?(String)
|
|
127
|
-
|
|
127
|
+
# end_line alone reads from the top of the file.
|
|
128
|
+
start_num ||= 1
|
|
128
129
|
|
|
129
130
|
end_provided = !blank?(end_line)
|
|
130
131
|
end_num = end_provided ? parse_positive_line_number(end_line, "end_line") : nil
|
|
@@ -20,7 +20,7 @@ module Samagotchi
|
|
|
20
20
|
OUTPUT_TAIL_READ_BYTES = 16 * 1024
|
|
21
21
|
SANITIZED_ENV_KEYS = %w[RUBYOPT RUBYLIB BUNDLE_GEMFILE BUNDLE_BIN_PATH BUNDLER_VERSION].freeze
|
|
22
22
|
# Set by chi (Builtins.parent_env); the model's env can't change it.
|
|
23
|
-
MARKER_ENV_KEYS = %w[SAMAGOTCHI_PARENT_SESSION].freeze
|
|
23
|
+
MARKER_ENV_KEYS = %w[SAMAGOTCHI_PARENT_SESSION SAMAGOTCHI_SESSION_MODEL].freeze
|
|
24
24
|
|
|
25
25
|
module_function
|
|
26
26
|
|
data/lib/samagotchi/turn_flow.rb
CHANGED
|
@@ -108,12 +108,13 @@ module Samagotchi
|
|
|
108
108
|
@checkpoint = nil
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
# Answer the offer with no: the interrupted turn
|
|
112
|
-
#
|
|
111
|
+
# Answer the offer with no (Stop): the interrupted turn stays, as
|
|
112
|
+
# after a Ctrl-C or a new prompt (!rollback still erases it), and the
|
|
113
|
+
# model reads that it wasn't continued: a note, or the user's reason
|
|
114
|
+
# (with a summary of that turn).
|
|
113
115
|
def abort_continue!(reason: nil)
|
|
114
|
-
|
|
115
|
-
@
|
|
116
|
-
@engine.append_messages([{ role: "user", content: reason_message(reason) }]) if reason
|
|
116
|
+
message = reason ? { role: "user", content: reason_message(reason) } : TurnNote.not_continued
|
|
117
|
+
@engine.append_messages([message])
|
|
117
118
|
@offer = nil
|
|
118
119
|
end
|
|
119
120
|
|
|
@@ -212,9 +213,9 @@ module Samagotchi
|
|
|
212
213
|
|
|
213
214
|
def reason_message(reason)
|
|
214
215
|
context = @offer && @offer[:context]
|
|
215
|
-
lines = ["I chose not to continue the
|
|
216
|
+
lines = ["I chose not to continue the turn that ran out of steps because: #{reason}"]
|
|
216
217
|
lines << ""
|
|
217
|
-
lines << "Interrupted turn summary:"
|
|
218
|
+
lines << "Its work so far stays. Interrupted turn summary:"
|
|
218
219
|
original_prompt = context && context[:original_prompt]
|
|
219
220
|
lines << "- original_prompt: #{original_prompt.to_s.empty? ? "(unavailable)" : original_prompt}"
|
|
220
221
|
|
data/lib/samagotchi/turn_note.rb
CHANGED
|
@@ -131,6 +131,13 @@ module Samagotchi
|
|
|
131
131
|
(entry[:kind] || entry["kind"]).to_s == KIND
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
+
# The step-limit offer answered Stop (no reason): the partial turn
|
|
135
|
+
# stays, and the model reads that it wasn't continued.
|
|
136
|
+
def not_continued
|
|
137
|
+
message("the previous turn ran out of steps before it answered, and the user chose not to continue it. " \
|
|
138
|
+
"Its work so far (tool calls, file changes) stays; wait for the user's next message.")
|
|
139
|
+
end
|
|
140
|
+
|
|
134
141
|
def message(text)
|
|
135
142
|
{ role: "system", content: "#{OPEN}#{text}#{CLOSE}", kind: KIND }
|
|
136
143
|
end
|