samagotchi 0.17.0 → 0.18.1
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 +98 -1
- data/bin/chi +5 -2
- data/docs/architecture.md +25 -2
- data/docs/cli.md +41 -9
- data/docs/configuration.md +4 -2
- data/docs/guardrails.md +59 -8
- data/docs/hooks.md +4 -1
- data/docs/memory.md +5 -3
- data/docs/plugins.md +3 -2
- data/docs/sessions.md +6 -5
- data/docs/sub-agent.md +42 -4
- data/lib/samagotchi/answer_command.rb +23 -2
- data/lib/samagotchi/answer_tail.rb +52 -0
- data/lib/samagotchi/bridge.rb +49 -8
- data/lib/samagotchi/bridge_client.rb +18 -3
- data/lib/samagotchi/bundle_command.rb +108 -2
- 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 +2 -2
- data/lib/samagotchi/client.rb +58 -10
- 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 +68 -22
- data/lib/samagotchi/guardrail_wiring.rb +56 -3
- 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/http.rb +9 -3
- data/lib/samagotchi/llm/native_backend.rb +2 -1
- data/lib/samagotchi/memory_bundle/trash.rb +81 -2
- data/lib/samagotchi/model_profile.rb +2 -2
- data/lib/samagotchi/output_formatter.rb +9 -4
- data/lib/samagotchi/parent_report.rb +39 -9
- data/lib/samagotchi/question_desk.rb +205 -46
- data/lib/samagotchi/reply_wait.rb +10 -4
- data/lib/samagotchi/send_command.rb +2 -1
- data/lib/samagotchi/served_model.rb +14 -4
- 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/sessions_command.rb +3 -2
- data/lib/samagotchi/system_prompt.rb +1 -1
- data/lib/samagotchi/terminal_ui/attached_loop.rb +33 -8
- data/lib/samagotchi/terminal_ui/one_shot_sink.rb +63 -0
- data/lib/samagotchi/terminal_ui/question_prompt.rb +34 -2
- data/lib/samagotchi/terminal_ui.rb +75 -17
- data/lib/samagotchi/tool_declarations.rb +4 -4
- data/lib/samagotchi/tools/delegate_wait.rb +14 -5
- data/lib/samagotchi/tools/edit.rb +6 -1
- data/lib/samagotchi/tools/execute.rb +102 -23
- data/lib/samagotchi/tools/read.rb +2 -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 +187 -42
- data/lib/samagotchi/web/public/app.js +122 -25
- 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 +5 -2
- 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/timing.js +41 -0
- data/lib/samagotchi/web/public/turn_events.js +61 -0
- data/lib/samagotchi/web/public/update_notice.js +15 -0
- data/lib/samagotchi/worker.rb +30 -58
- data/lib/samagotchi/worker_sidecar.rb +6 -3
- metadata +9 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "iteration_limit"
|
|
3
4
|
require_relative "model_profile"
|
|
4
5
|
require_relative "tool_call_parser"
|
|
5
6
|
require_relative "config"
|
|
@@ -174,7 +175,7 @@ module Samagotchi
|
|
|
174
175
|
# at the conversation tail (prefix KV cache preserved) and a
|
|
175
176
|
# :pending_input_merged stream event is emitted.
|
|
176
177
|
# @return [LLM::ModelResult] final visible response with continuation metadata
|
|
177
|
-
def run(messages, max_iterations:
|
|
178
|
+
def run(messages, max_iterations: IterationLimit::DEFAULT, on_stream_event: nil, cancel_controller: nil, model_name: nil, max_tool_output_chars: nil, pending_input: nil)
|
|
178
179
|
@turn_settings = @turn_settings.with(model_name: completion_model_name(model_name))
|
|
179
180
|
turn = start_turn(messages, on_stream_event: on_stream_event, cancel_controller: cancel_controller,
|
|
180
181
|
pending_input: pending_input, cap: resolve_output_char_cap(max_tool_output_chars))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../iteration_limit"
|
|
3
4
|
require_relative "model_result"
|
|
4
5
|
|
|
5
6
|
module Samagotchi
|
|
@@ -14,7 +15,7 @@ module Samagotchi
|
|
|
14
15
|
#
|
|
15
16
|
# The raw-prompt KernelLoop is NativeBackend; the chat loop is ChatLoop.
|
|
16
17
|
class ModelBackend
|
|
17
|
-
def complete(messages:, max_iterations:
|
|
18
|
+
def complete(messages:, max_iterations: IterationLimit::DEFAULT, on_stream_event: nil, cancel_controller: nil,
|
|
18
19
|
model_name: nil, max_tool_output_chars: nil, pending_input: nil)
|
|
19
20
|
raise NotImplementedError, "#{self.class}#complete must be implemented"
|
|
20
21
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../iteration_limit"
|
|
3
4
|
require "json"
|
|
4
5
|
require_relative "backend"
|
|
5
6
|
require_relative "model_result"
|
|
@@ -56,7 +57,7 @@ module Samagotchi
|
|
|
56
57
|
|
|
57
58
|
def provider = :chat
|
|
58
59
|
|
|
59
|
-
def complete(messages:, max_iterations:
|
|
60
|
+
def complete(messages:, max_iterations: IterationLimit::DEFAULT, on_stream_event: nil, cancel_controller: nil,
|
|
60
61
|
model_name: nil, max_tool_output_chars: nil, pending_input: nil)
|
|
61
62
|
raise ArgumentError, "ChatLoop has no adapter" unless @adapter
|
|
62
63
|
|
data/lib/samagotchi/llm/http.rb
CHANGED
|
@@ -168,7 +168,7 @@ module Samagotchi
|
|
|
168
168
|
|
|
169
169
|
# Send +request+ and return the response with its body read.
|
|
170
170
|
# @param retries [Boolean] false: one attempt, network errors raised as
|
|
171
|
-
# is
|
|
171
|
+
# is; either way Net::HTTP's own silent retry is off (see #start)
|
|
172
172
|
# @param check_status [Boolean] false: return an error response instead
|
|
173
173
|
# of raising its ProviderError
|
|
174
174
|
# @raise [RequestCancelled] when +cancel_controller+ cancels (with or
|
|
@@ -178,7 +178,7 @@ module Samagotchi
|
|
|
178
178
|
identify(request)
|
|
179
179
|
current = new_attempt_state(uri, request, log_fields, stream: false)
|
|
180
180
|
attempt = lambda do |state|
|
|
181
|
-
start(uri, open_timeout: open_timeout, read_timeout: read_timeout, max_retries:
|
|
181
|
+
start(uri, open_timeout: open_timeout, read_timeout: read_timeout, max_retries: 0) do |http|
|
|
182
182
|
state[:http] = http
|
|
183
183
|
http.request(request).tap do |response|
|
|
184
184
|
state[:status] = response.code.to_i
|
|
@@ -210,7 +210,13 @@ module Samagotchi
|
|
|
210
210
|
|
|
211
211
|
def start(uri, open_timeout: nil, read_timeout: nil, max_retries: nil, &block)
|
|
212
212
|
options = { open_timeout: open_timeout || @open_timeout, read_timeout: read_timeout || @read_timeout }
|
|
213
|
-
|
|
213
|
+
# Net::HTTP retries an idempotent request once on its own (max_retries
|
|
214
|
+
# defaults to 1) when the connection drops before an answer: chi's
|
|
215
|
+
# RetryPolicy owns retries (and the log line says how many were sent),
|
|
216
|
+
# so every request here gets Net::HTTP's silent one off. Callers may
|
|
217
|
+
# still ask for it explicitly (a fetch whose retries: false passes 0).
|
|
218
|
+
retries = max_retries || 0
|
|
219
|
+
options[:max_retries] = retries
|
|
214
220
|
options[:use_ssl] = true if uri.scheme == "https"
|
|
215
221
|
Net::HTTP.start(uri.host, uri.port, **options, &block)
|
|
216
222
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../iteration_limit"
|
|
3
4
|
require_relative "backend"
|
|
4
5
|
require_relative "model_result"
|
|
5
6
|
|
|
@@ -15,7 +16,7 @@ module Samagotchi
|
|
|
15
16
|
|
|
16
17
|
def provider = :native
|
|
17
18
|
|
|
18
|
-
def complete(messages:, max_iterations:
|
|
19
|
+
def complete(messages:, max_iterations: IterationLimit::DEFAULT, on_stream_event: nil, cancel_controller: nil,
|
|
19
20
|
model_name: nil, max_tool_output_chars: nil, pending_input: nil)
|
|
20
21
|
@kernel.run(
|
|
21
22
|
messages,
|
|
@@ -9,13 +9,52 @@ module Samagotchi
|
|
|
9
9
|
# (uninstall, an upgrade's dropped file): moved, never deleted, into
|
|
10
10
|
# <system memories>/.bundles/.trash/<bundle>-<YYYYmmdd-HHMMSS>/, one dir
|
|
11
11
|
# per operation (-2, -3… when one already has that name). A dot dir, so
|
|
12
|
-
# Provenance.each_installed never reads it as a bundle.
|
|
13
|
-
#
|
|
12
|
+
# Provenance.each_installed never reads it as a bundle.
|
|
13
|
+
#
|
|
14
|
+
# `chi bundle trash` lists and empties the trash.
|
|
14
15
|
class Trash
|
|
16
|
+
# A single trash entry (one directory in the trash).
|
|
17
|
+
Entry = Data.define(:name, :path, :time, :files, :bytes)
|
|
18
|
+
|
|
15
19
|
def self.root(bundles_dir: MemoryPaths.bundles_dir)
|
|
16
20
|
File.join(bundles_dir, ".trash")
|
|
17
21
|
end
|
|
18
22
|
|
|
23
|
+
# Returns an array of {Entry} for every real directory directly inside
|
|
24
|
+
# the trash, oldest first. A missing or empty trash gives [].
|
|
25
|
+
#
|
|
26
|
+
# +now+ is the reference time (default: Time.now); used only when a
|
|
27
|
+
# directory name has no timestamp (mtime fallback).
|
|
28
|
+
def self.entries(bundles_dir: MemoryPaths.bundles_dir, now: Time.now)
|
|
29
|
+
trash_dir = root(bundles_dir: bundles_dir)
|
|
30
|
+
return [] unless Dir.exist?(trash_dir)
|
|
31
|
+
|
|
32
|
+
Dir.children(trash_dir)
|
|
33
|
+
.select { |c| real_dir?(File.join(trash_dir, c)) }
|
|
34
|
+
.map { |name| entry_for(trash_dir, name, now) }
|
|
35
|
+
.sort_by(&:time)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Deletes every entry that matches the filter, returning the entries
|
|
39
|
+
# that were (or would be) deleted.
|
|
40
|
+
#
|
|
41
|
+
# +older_than_days+ nil = all; a positive integer keeps newer dirs.
|
|
42
|
+
# +dry_run+ true = only return what would be deleted.
|
|
43
|
+
# +now+ reference time (default: Time.now).
|
|
44
|
+
def self.empty!(bundles_dir: MemoryPaths.bundles_dir, older_than_days: nil, now: Time.now, dry_run: false)
|
|
45
|
+
entries = self.entries(bundles_dir: bundles_dir, now: now)
|
|
46
|
+
if older_than_days
|
|
47
|
+
cutoff = now - older_than_days.to_i * 86_400
|
|
48
|
+
entries = entries.select { |e| e.time < cutoff }
|
|
49
|
+
end
|
|
50
|
+
return entries if dry_run
|
|
51
|
+
|
|
52
|
+
entries.each do |e|
|
|
53
|
+
FileUtils.rm_rf(e.path)
|
|
54
|
+
end
|
|
55
|
+
entries
|
|
56
|
+
end
|
|
57
|
+
|
|
19
58
|
# The dir, once a file was moved (nil before).
|
|
20
59
|
attr_reader :dir
|
|
21
60
|
|
|
@@ -52,6 +91,46 @@ module Samagotchi
|
|
|
52
91
|
end
|
|
53
92
|
@dir = candidate
|
|
54
93
|
end
|
|
94
|
+
|
|
95
|
+
# Class methods below.
|
|
96
|
+
|
|
97
|
+
def self.real_dir?(path)
|
|
98
|
+
File.directory?(path) && !File.symlink?(path)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.entry_for(trash_dir, name, now)
|
|
102
|
+
path = File.join(trash_dir, name)
|
|
103
|
+
time = parse_timestamp(name) || File.mtime(path)
|
|
104
|
+
files, bytes = count_files(path)
|
|
105
|
+
Entry.new(name: name, path: path, time: time, files: files, bytes: bytes)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Extracts a Time from "bundle-YYYYmmdd-HHMMSS" or "bundle-YYYYmmdd-HHMMSS-2".
|
|
109
|
+
# Returns nil if the pattern doesn't match.
|
|
110
|
+
TIMESTAMP_RE = /\A.*?-(\d{8})-(\d{6})(?:-\d+)?\z/
|
|
111
|
+
|
|
112
|
+
def self.parse_timestamp(name)
|
|
113
|
+
m = name.match(TIMESTAMP_RE)
|
|
114
|
+
return nil unless m
|
|
115
|
+
|
|
116
|
+
begin
|
|
117
|
+
Time.strptime("#{m[1]}-#{m[2]}", "%Y%m%d-%H%M%S")
|
|
118
|
+
rescue ArgumentError
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Recursively counts files and sums their sizes.
|
|
124
|
+
def self.count_files(dir)
|
|
125
|
+
files = 0
|
|
126
|
+
bytes = 0
|
|
127
|
+
Dir.glob(File.join(dir, "**", "*"), File::FNM_DOTMATCH).each do |f|
|
|
128
|
+
next if File.directory?(f)
|
|
129
|
+
files += 1
|
|
130
|
+
bytes += File.size(f) rescue 0
|
|
131
|
+
end
|
|
132
|
+
[files, bytes]
|
|
133
|
+
end
|
|
55
134
|
end
|
|
56
135
|
end
|
|
57
136
|
end
|
|
@@ -112,7 +112,7 @@ module Samagotchi
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def self.default
|
|
115
|
-
|
|
115
|
+
named(DEFAULT_NAME)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
def self.normalize(value)
|
|
@@ -124,7 +124,7 @@ module Samagotchi
|
|
|
124
124
|
when "gemma", "gemma4", "gemma4o"
|
|
125
125
|
gemma4
|
|
126
126
|
else
|
|
127
|
-
|
|
127
|
+
named(DEFAULT_NAME)
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
130
|
|
|
@@ -45,13 +45,17 @@ module Samagotchi
|
|
|
45
45
|
)
|
|
46
46
|
INDIVIDUAL_RE.freeze
|
|
47
47
|
|
|
48
|
-
# Angle-bracketed literal tokens stripped wholesale (including the thought
|
|
49
|
-
# channel's trailing word). Applied before INDIVIDUAL_RE so multi-part tokens
|
|
50
|
-
# are removed as a unit.
|
|
51
48
|
# Angle-bracketed literal tokens stripped wholesale (including the thought
|
|
52
49
|
# channel's trailing word). Applied before INDIVIDUAL_RE so multi-part tokens
|
|
53
50
|
# are removed as a unit. Pipes are escaped; angle brackets are literal.
|
|
54
|
-
LITERALS_RE = /<\|"
|
|
51
|
+
LITERALS_RE = /<\|"\|>|<end_of_turn>/.freeze
|
|
52
|
+
|
|
53
|
+
# Gemma 4's thought channel, as a whole block: `<|channel>thought` to its
|
|
54
|
+
# `<channel|>` close, or to the end of the text when the close is missing
|
|
55
|
+
# (ToolCallParser::Gemma strips the same pair). Removing only the marker
|
|
56
|
+
# would leave the model's private reasoning in the answer; the channel is
|
|
57
|
+
# the profile's thinking lane, not output.
|
|
58
|
+
GEMMA_THOUGHT_RE = /<\|\w*channel>thought.*?(?:<channel\|>|\z)/m.freeze
|
|
55
59
|
|
|
56
60
|
# Qwen prompt-literal placeholders, e.g. </think>.
|
|
57
61
|
PROMPT_LITERALS = /\[\[SAMAGOTCHI_LITERAL_[A-Z_]+\]\]/.freeze
|
|
@@ -88,6 +92,7 @@ module Samagotchi
|
|
|
88
92
|
def remove_tokens(text)
|
|
89
93
|
text.to_s
|
|
90
94
|
.gsub(QWEN_THINK_RE, '')
|
|
95
|
+
.gsub(GEMMA_THOUGHT_RE, '')
|
|
91
96
|
.gsub(QWEN_TOOL_CALL_RE, '')
|
|
92
97
|
.gsub(LITERALS_RE, '')
|
|
93
98
|
.gsub(INDIVIDUAL_RE, '')
|
|
@@ -31,8 +31,10 @@ module Samagotchi
|
|
|
31
31
|
module_function
|
|
32
32
|
|
|
33
33
|
# @param result [ReplyWait::Result]
|
|
34
|
-
# @return [String] answered, question, failed, canceled,
|
|
35
|
-
#
|
|
34
|
+
# @return [String] answered, question, failed, canceled, limit (the
|
|
35
|
+
# turn ran out of iterations, nobody asked), not_continued (a Stop
|
|
36
|
+
# answered the step-limit question), no_answer, error, worker_gone,
|
|
37
|
+
# stopped or running
|
|
36
38
|
def status(result)
|
|
37
39
|
case result.status
|
|
38
40
|
when :done then "answered"
|
|
@@ -41,6 +43,8 @@ module Samagotchi
|
|
|
41
43
|
case result.outcome
|
|
42
44
|
when "failed" then "failed"
|
|
43
45
|
when "canceled" then "canceled"
|
|
46
|
+
when "exhausted" then "limit"
|
|
47
|
+
when "not_continued" then "not_continued"
|
|
44
48
|
else "no_answer"
|
|
45
49
|
end
|
|
46
50
|
when :error then "error"
|
|
@@ -52,11 +56,12 @@ module Samagotchi
|
|
|
52
56
|
end
|
|
53
57
|
end
|
|
54
58
|
|
|
55
|
-
# @return [Integer] 0 answered
|
|
56
|
-
# 1 anything
|
|
59
|
+
# @return [Integer] 0 answered (or not continued: the Stop asked for
|
|
60
|
+
# was carried out), 3 a question waits, 4 still running, 1 anything
|
|
61
|
+
# else
|
|
57
62
|
def exit_status(result)
|
|
58
63
|
case status(result)
|
|
59
|
-
when "answered" then CLI::Exit::OK
|
|
64
|
+
when "answered", "not_continued" then CLI::Exit::OK
|
|
60
65
|
when "question" then CLI::Exit::QUESTION
|
|
61
66
|
when "running" then CLI::Exit::RUNNING
|
|
62
67
|
else CLI::Exit::FAILED
|
|
@@ -116,7 +121,8 @@ module Samagotchi
|
|
|
116
121
|
|
|
117
122
|
# A pending question (Session#pending_question) as a parent sees it.
|
|
118
123
|
# kind is "question" for the model's own (the desk stores none),
|
|
119
|
-
# "hook" or "
|
|
124
|
+
# "hook", "approval" or "continue" (the step-limit question, with the
|
|
125
|
+
# turn's limit).
|
|
120
126
|
# @return [Hash]
|
|
121
127
|
def question(pending)
|
|
122
128
|
pending ||= {}
|
|
@@ -129,15 +135,18 @@ module Samagotchi
|
|
|
129
135
|
options: Array(pending[:options]).map(&:to_s),
|
|
130
136
|
multi_select: !!pending[:multi_select],
|
|
131
137
|
allow_freeform: !!pending[:allow_freeform],
|
|
138
|
+
limit: pending[:limit],
|
|
132
139
|
approval: approval&.slice(*APPROVAL_KEYS)
|
|
133
140
|
}.compact
|
|
134
141
|
end
|
|
135
142
|
|
|
136
143
|
# The command that answers +question+; N is the parent's to fill in.
|
|
137
144
|
# An approval's is a deny: allowing it is the user's
|
|
138
|
-
# (Guardrails::ParentApprovals).
|
|
145
|
+
# (Guardrails::ParentApprovals). A step-limit question's continues the
|
|
146
|
+
# turn (its Stop form is in #question_text).
|
|
139
147
|
def answer_with(session_id, question)
|
|
140
148
|
return "chi answer #{session_id} --question #{question[:id]} --option Deny --text WHY" if approval?(question)
|
|
149
|
+
return "chi answer #{session_id} --question #{question[:id]} --option Continue" if continue?(question)
|
|
141
150
|
|
|
142
151
|
"chi answer #{session_id} --question #{question[:id]} --option N"
|
|
143
152
|
end
|
|
@@ -156,6 +165,16 @@ module Samagotchi
|
|
|
156
165
|
question.is_a?(Hash) && (question[:kind] || question["kind"]).to_s == "approval"
|
|
157
166
|
end
|
|
158
167
|
|
|
168
|
+
# The step-limit question (ContinueOffer).
|
|
169
|
+
def continue?(question)
|
|
170
|
+
question.is_a?(Hash) && (question[:kind] || question["kind"]).to_s == "continue"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# What a message does to a step-limit question.
|
|
174
|
+
def message_drops(session_id)
|
|
175
|
+
"a message instead (chi send #{session_id} -m …) drops it and starts a new turn"
|
|
176
|
+
end
|
|
177
|
+
|
|
159
178
|
# What a wait that ended without an answer says, one line.
|
|
160
179
|
def detail(result, session_id:, timeout: nil)
|
|
161
180
|
attach = "chi --attach #{session_id}"
|
|
@@ -163,10 +182,13 @@ module Samagotchi
|
|
|
163
182
|
when :waiting_for_answer
|
|
164
183
|
if approval?(result.question)
|
|
165
184
|
"waiting for an approval: #{first_line(result.question[:question])}; #{DENY_AND_TELL}"
|
|
185
|
+
elsif continue?(result.question)
|
|
186
|
+
"waiting at the step limit: #{first_line(result.question[:question])}; " \
|
|
187
|
+
"answer Continue or Stop: chi answer #{session_id} --question #{result.question[:id]} --option Continue"
|
|
166
188
|
else
|
|
167
189
|
"waiting for an answer: #{first_line(result.question&.dig(:question))}; open it: #{attach} or the web"
|
|
168
190
|
end
|
|
169
|
-
when :no_reply then "#{no_reply_line(result)}; #{attach} shows it"
|
|
191
|
+
when :no_reply then "#{no_reply_line(result, session_id)}; #{attach} shows it"
|
|
170
192
|
when :error then "the worker failed: #{result.text}; #{attach} shows what happened"
|
|
171
193
|
when :worker_gone then "the worker is gone; #{attach} shows what happened"
|
|
172
194
|
when :stopped then "the session was stopped (chi sessions stop)"
|
|
@@ -188,6 +210,10 @@ module Samagotchi
|
|
|
188
210
|
lines << " allowing it is up to your user: #{DENY_AND_TELL}"
|
|
189
211
|
lines << " deny: #{answer_with(session_id, q)}"
|
|
190
212
|
lines << " #{leave_open(session_id)}"
|
|
213
|
+
elsif continue?(q)
|
|
214
|
+
lines << " continue: #{answer_with(session_id, q)}"
|
|
215
|
+
lines << " stop: chi answer #{session_id} --question #{q[:id]} --option Stop --text WHY (the text is optional; the model reads it)"
|
|
216
|
+
lines << " #{message_drops(session_id)}"
|
|
191
217
|
else
|
|
192
218
|
lines << " more than one allowed: repeat --option" if q[:multi_select]
|
|
193
219
|
lines << " free text allowed: --text" if q[:allow_freeform]
|
|
@@ -197,11 +223,15 @@ module Samagotchi
|
|
|
197
223
|
lines.map { |line| "#{line}\n" }.join
|
|
198
224
|
end
|
|
199
225
|
|
|
200
|
-
def no_reply_line(result)
|
|
226
|
+
def no_reply_line(result, session_id = nil)
|
|
201
227
|
case result.outcome
|
|
202
228
|
when "failed" then result.text.to_s.strip.empty? ? "the turn failed" : "the turn failed: #{result.text.strip}"
|
|
203
229
|
when "canceled" then "the turn was canceled"
|
|
204
230
|
when "completed" then "the turn ended with no visible answer"
|
|
231
|
+
when "not_continued" then "the turn was not continued (Stop); its work so far stays"
|
|
232
|
+
when "exhausted"
|
|
233
|
+
"the turn ran out of iterations#{" (#{result.limit} steps)" if result.limit} before it answered; " \
|
|
234
|
+
"chi send #{session_id} -m '/continue yes' continues it, a message drops it"
|
|
205
235
|
else "the turn ended without a reply (canceled, failed or empty)"
|
|
206
236
|
end
|
|
207
237
|
end
|