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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "../memory_paths"
|
|
5
|
+
|
|
6
|
+
module Samagotchi
|
|
7
|
+
module MemoryBundle
|
|
8
|
+
# Where a bundle operation puts the memory files it takes away
|
|
9
|
+
# (uninstall, an upgrade's dropped file): moved, never deleted, into
|
|
10
|
+
# <system memories>/.bundles/.trash/<bundle>-<YYYYmmdd-HHMMSS>/, one dir
|
|
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
|
+
#
|
|
14
|
+
# `chi bundle trash` lists and empties the trash.
|
|
15
|
+
class Trash
|
|
16
|
+
# A single trash entry (one directory in the trash).
|
|
17
|
+
Entry = Data.define(:name, :path, :time, :files, :bytes)
|
|
18
|
+
|
|
19
|
+
def self.root(bundles_dir: MemoryPaths.bundles_dir)
|
|
20
|
+
File.join(bundles_dir, ".trash")
|
|
21
|
+
end
|
|
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
|
+
|
|
58
|
+
# The dir, once a file was moved (nil before).
|
|
59
|
+
attr_reader :dir
|
|
60
|
+
|
|
61
|
+
def initialize(name, now: Time.now, bundles_dir: MemoryPaths.bundles_dir)
|
|
62
|
+
@name = name.to_s
|
|
63
|
+
@now = now
|
|
64
|
+
@bundles_dir = bundles_dir
|
|
65
|
+
@dir = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Moves +path+ into the trash dir (created on the first call).
|
|
69
|
+
# @return [String] the file's new path
|
|
70
|
+
def move(path)
|
|
71
|
+
dest = File.join(ensure_dir, File.basename(path))
|
|
72
|
+
FileUtils.mv(path, dest)
|
|
73
|
+
dest
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def ensure_dir
|
|
79
|
+
return @dir if @dir
|
|
80
|
+
|
|
81
|
+
base = File.join(self.class.root(bundles_dir: @bundles_dir), "#{@name}-#{@now.strftime("%Y%m%d-%H%M%S")}")
|
|
82
|
+
FileUtils.mkdir_p(File.dirname(base))
|
|
83
|
+
candidate = base
|
|
84
|
+
n = 1
|
|
85
|
+
loop do
|
|
86
|
+
Dir.mkdir(candidate)
|
|
87
|
+
break
|
|
88
|
+
rescue Errno::EEXIST
|
|
89
|
+
n += 1
|
|
90
|
+
candidate = "#{base}-#{n}"
|
|
91
|
+
end
|
|
92
|
+
@dir = candidate
|
|
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
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -5,13 +5,17 @@ require_relative "../memory_paths"
|
|
|
5
5
|
require_relative "provenance"
|
|
6
6
|
require_relative "index_updater"
|
|
7
7
|
require_relative "merger"
|
|
8
|
+
require_relative "trash"
|
|
8
9
|
|
|
9
10
|
module Samagotchi
|
|
10
11
|
module MemoryBundle
|
|
11
12
|
class Uninstaller
|
|
12
13
|
class UninstallError < StandardError; end
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
# removed_files: the bundle's own hooks/ and plugin/ files (deleted);
|
|
16
|
+
# trashed_files: its memory files, moved to trash_dir (Trash), nil
|
|
17
|
+
# when none was moved.
|
|
18
|
+
attr_reader :warnings, :removed_files, :trashed_files
|
|
15
19
|
|
|
16
20
|
def initialize(name:, scope: nil, force: false)
|
|
17
21
|
@name = name
|
|
@@ -19,8 +23,12 @@ module Samagotchi
|
|
|
19
23
|
@force = force
|
|
20
24
|
@warnings = []
|
|
21
25
|
@removed_files = []
|
|
26
|
+
@trashed_files = []
|
|
27
|
+
@trash = Trash.new(name)
|
|
22
28
|
end
|
|
23
29
|
|
|
30
|
+
def trash_dir = @trash.dir
|
|
31
|
+
|
|
24
32
|
def run
|
|
25
33
|
provenance = Provenance.new(name: @name)
|
|
26
34
|
data = provenance.read
|
|
@@ -40,12 +48,18 @@ module Samagotchi
|
|
|
40
48
|
return true
|
|
41
49
|
end
|
|
42
50
|
|
|
51
|
+
# A file another installed bundle lists too stays, edited or not.
|
|
52
|
+
shared = files.keys.map(&:to_s).to_h do |key|
|
|
53
|
+
[key, Provenance.claimants(key, scope: target_scope, except: @name)]
|
|
54
|
+
end.reject { |_, others| others.empty? }
|
|
55
|
+
|
|
43
56
|
blocked = []
|
|
44
57
|
files.each do |file_key, meta|
|
|
45
58
|
file_key_str = file_key.to_s
|
|
46
59
|
target_path = File.join(target_dir, file_key_str)
|
|
47
60
|
base_path = provenance.base_path(file_key_str)
|
|
48
61
|
next unless File.exist?(target_path)
|
|
62
|
+
next if shared.key?(file_key_str)
|
|
49
63
|
if !@force && File.exist?(base_path) && Merger.current_modified?(base_path, target_path)
|
|
50
64
|
blocked << file_key_str
|
|
51
65
|
@warnings << "Skipped #{file_key_str}: local edits detected (use --force to remove)"
|
|
@@ -57,15 +71,23 @@ module Samagotchi
|
|
|
57
71
|
files.each do |file_key, _meta|
|
|
58
72
|
file_key_str = file_key.to_s
|
|
59
73
|
target_path = File.join(target_dir, file_key_str)
|
|
74
|
+
if (others = shared[file_key_str])
|
|
75
|
+
next unless File.exist?(target_path)
|
|
76
|
+
|
|
77
|
+
@warnings << "Kept #{file_key_str}: bundle #{others.join(", ")} has it too"
|
|
78
|
+
retag(target_scope, target_path, file_key_str, others.first)
|
|
79
|
+
next
|
|
80
|
+
end
|
|
60
81
|
if File.exist?(target_path)
|
|
61
|
-
|
|
62
|
-
@
|
|
82
|
+
@trash.move(target_path)
|
|
83
|
+
@trashed_files << file_key_str
|
|
63
84
|
end
|
|
64
|
-
# Remove index line
|
|
85
|
+
# Remove index line (not fatal: a warning says so)
|
|
65
86
|
begin
|
|
66
87
|
IndexUpdater.remove_index(target_scope, file_key_str.delete_suffix(".md"))
|
|
67
88
|
IndexUpdater.remove_index(target_scope, file_key_str) # legacy "name.md" line
|
|
68
|
-
rescue =>
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
@warnings << "index.md: line for #{file_key_str.delete_suffix(".md")} not removed (#{e.message})"
|
|
69
91
|
end
|
|
70
92
|
end
|
|
71
93
|
|
|
@@ -97,6 +119,13 @@ module Samagotchi
|
|
|
97
119
|
|
|
98
120
|
private
|
|
99
121
|
|
|
122
|
+
# A kept shared file's index line names the bundle that still has it.
|
|
123
|
+
def retag(scope, path, file_key, bundle)
|
|
124
|
+
IndexUpdater.update_index(scope, file_key.delete_suffix(".md"), File.size(path), source: bundle)
|
|
125
|
+
rescue StandardError => e
|
|
126
|
+
@warnings << "index.md: line for #{file_key.delete_suffix(".md")} not updated (#{e.message})"
|
|
127
|
+
end
|
|
128
|
+
|
|
100
129
|
def resolve_target_dir(scope)
|
|
101
130
|
MemoryPaths.scope_dir(scope) or raise UninstallError, "invalid scope: #{scope}"
|
|
102
131
|
end
|
|
@@ -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
|
|
@@ -18,11 +18,14 @@ module Samagotchi
|
|
|
18
18
|
# (chat messages, timeout:, max_tokens:, cancel_controller:) and returns
|
|
19
19
|
# the answer text.
|
|
20
20
|
# +messages_partial+ says whether +messages+ leaves out a running turn;
|
|
21
|
-
# +model_name+
|
|
21
|
+
# +model_name+ (the session's resolved model ref, live after /model) and
|
|
22
|
+
# +state_dir+ are what ctx.sessions forks with; +model_key+ is the
|
|
23
|
+
# model's memory overlay key (ctx.model, ctx.model_key).
|
|
22
24
|
# +steer+ takes (text, label), +stop_turn+ and +stop_generation+
|
|
23
25
|
# (reason, label), each true when it acted on a running turn.
|
|
24
26
|
Host = Struct.new(:session_id, :cwd, :messages, :messages_partial, :notify, :ask_user, :cancelled, :card,
|
|
25
|
-
:ask_model, :model_name, :state_dir, :scratch, :steer, :stop_turn, :stop_generation,
|
|
27
|
+
:ask_model, :model_name, :model_key, :state_dir, :scratch, :steer, :stop_turn, :stop_generation,
|
|
28
|
+
keyword_init: true)
|
|
26
29
|
|
|
27
30
|
# ctx.ask_model failed: the model couldn't be reached, timed out, or
|
|
28
31
|
# sent nothing usable. The message says why, for the user.
|
|
@@ -86,6 +89,15 @@ module Samagotchi
|
|
|
86
89
|
# @return [String, nil] the session's id (nil before the first one)
|
|
87
90
|
def session_id = @host.session_id.call
|
|
88
91
|
|
|
92
|
+
# @return [String, nil] the model the session runs on now: its
|
|
93
|
+
# resolved ref ("host:id"), right after /model too
|
|
94
|
+
def model = @host.model_name&.call
|
|
95
|
+
|
|
96
|
+
# @return [String, nil] the model's memory overlay key
|
|
97
|
+
# (`<name>.<key>.md`, ModelOverlay.key_for), as memory_write
|
|
98
|
+
# current_model_only names it
|
|
99
|
+
def model_key = @host.model_key&.call
|
|
100
|
+
|
|
89
101
|
# @return [String] the session's working directory
|
|
90
102
|
def cwd = @host.cwd.call || Dir.pwd
|
|
91
103
|
|