samagotchi 0.86.0 → 0.87.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 +17 -1
- data/bin/chi +39 -2
- data/docs/architecture.md +5 -3
- data/docs/cli.md +15 -2
- data/docs/configuration.md +1 -1
- data/docs/guardrails.md +47 -2
- data/docs/hooks.md +1 -1
- data/docs/plugins.md +1 -1
- data/docs/sessions.md +16 -4
- data/docs/sub-agent.md +6 -3
- data/lib/samagotchi/answer_command.rb +6 -6
- data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +3 -3
- data/lib/samagotchi/bundles/known-names/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/cli/parent_wait.rb +3 -1
- data/lib/samagotchi/guardrails/parent_policy.rb +74 -5
- data/lib/samagotchi/parent_answers_command.rb +80 -0
- data/lib/samagotchi/parent_report.rb +53 -34
- data/lib/samagotchi/question_desk.rb +3 -2
- data/lib/samagotchi/send_command.rb +75 -10
- data/lib/samagotchi/session.rb +26 -2
- data/lib/samagotchi/session_commands.rb +35 -9
- data/lib/samagotchi/session_setup.rb +10 -8
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +17 -7
- data/lib/samagotchi/terminal_ui/attached_loop.rb +16 -5
- data/lib/samagotchi/terminal_ui.rb +11 -5
- data/lib/samagotchi/tools/delegate.rb +4 -1
- data/lib/samagotchi/tools/delegate_wait.rb +11 -7
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/worker.rb +9 -1
- metadata +2 -1
|
@@ -18,24 +18,26 @@ module Samagotchi
|
|
|
18
18
|
# +toolkit+ a Toolkit or nil (SessionManager.spawn_session gives the
|
|
19
19
|
# session config's toolkit.default then), +bundle_settings+ the bundle
|
|
20
20
|
# settings chi --set gave ({bundle => {key => value}}, frozen; {} with
|
|
21
|
-
# none), which Plugin::Context#settings lays over config.yml's
|
|
21
|
+
# none), which Plugin::Context#settings lays over config.yml's,
|
|
22
|
+
# +parent_answers+ the session's own guardrails.parent_answers (a level
|
|
23
|
+
# String, tighten-only: Guardrails::ParentPolicy.effective) or nil.
|
|
22
24
|
#
|
|
23
25
|
# It doesn't save itself: the session keeps each setting in its own field
|
|
24
26
|
# and file key, and resolves it on its own. A delegate child starts
|
|
25
|
-
# without one (but the toolkit its delegate call named
|
|
26
|
-
# fork copy their session's (.of).
|
|
27
|
+
# without one (but the toolkit its delegate call named, and its parent's
|
|
28
|
+
# parent_answers); a continue and a fork copy their session's (.of).
|
|
27
29
|
SessionSetup = Data.define(:llm_context, :thinking, :reasoning_history, :appended_prompt_files, :toolkit,
|
|
28
|
-
:bundle_settings) do
|
|
30
|
+
:bundle_settings, :parent_answers) do
|
|
29
31
|
def initialize(llm_context: nil, thinking: nil, reasoning_history: nil, appended_prompt_files: [], toolkit: nil,
|
|
30
|
-
bundle_settings: {})
|
|
32
|
+
bundle_settings: {}, parent_answers: nil)
|
|
31
33
|
super(llm_context: llm_context, thinking: thinking, reasoning_history: reasoning_history,
|
|
32
34
|
appended_prompt_files: Array(appended_prompt_files).map(&:to_s).freeze, toolkit: toolkit,
|
|
33
|
-
bundle_settings: BundleSessionSettings.normalize(bundle_settings))
|
|
35
|
+
bundle_settings: BundleSessionSettings.normalize(bundle_settings), parent_answers: parent_answers&.to_s)
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def empty?
|
|
37
39
|
llm_context.nil? && thinking.nil? && reasoning_history.nil? && appended_prompt_files.empty? && toolkit.nil? &&
|
|
38
|
-
bundle_settings.empty?
|
|
40
|
+
bundle_settings.empty? && parent_answers.nil?
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
# A session's own setup, for a session that starts from it. Its
|
|
@@ -48,7 +50,7 @@ module Samagotchi
|
|
|
48
50
|
|
|
49
51
|
new(llm_context: session.llm_context, thinking: session.thinking, reasoning_history: session.reasoning_history,
|
|
50
52
|
appended_prompt_files: session.appended_prompt_files, toolkit: Toolkit.of(session.toolkit),
|
|
51
|
-
bundle_settings: session.bundle_settings)
|
|
53
|
+
bundle_settings: session.bundle_settings, parent_answers: session.parent_answers)
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
end
|
|
@@ -53,6 +53,10 @@ module Samagotchi
|
|
|
53
53
|
# the line an existing session's run prints (--set too)
|
|
54
54
|
# @param bundle_settings [Hash] --set's values (BundleSessionSettings):
|
|
55
55
|
# a new session's setup carries them; an existing session keeps its own
|
|
56
|
+
# @param parent_answers [String, nil] --parent-answers: a new session's
|
|
57
|
+
# own guardrails.parent_answers; an existing session keeps its own
|
|
58
|
+
# (never sent as a /parent-answers command: that would be a
|
|
59
|
+
# loosening route from a command line)
|
|
56
60
|
# @param state_dir [String, nil] where the sessions are (the default
|
|
57
61
|
# state dir by default): the worker's, for the attached loop too
|
|
58
62
|
# @return [Symbol] :detached, :closed when the worker went away,
|
|
@@ -60,12 +64,12 @@ module Samagotchi
|
|
|
60
64
|
# pipe) :turn_failed / :empty_answer / :unanswered (AttachedLoop#run)
|
|
61
65
|
def run(attach: nil, shared: false, resume: nil, prompt: nil, model: nil, no_interrupt: false, default_input: true,
|
|
62
66
|
memories: [], muted_memories: [], llm_context: {}, thinking: nil, appended_prompt_files: [], toolkit: nil,
|
|
63
|
-
toolkit_flags: [], bundle_settings: {}, state_dir: nil)
|
|
67
|
+
toolkit_flags: [], bundle_settings: {}, parent_answers: nil, state_dir: nil)
|
|
64
68
|
state_dir ||= Session.default_state_dir
|
|
65
69
|
client = connect(attach: attach, shared: shared, resume: resume, model: model, state_dir: state_dir,
|
|
66
70
|
memories: memories, muted_memories: muted_memories, llm_context: llm_context, thinking: thinking,
|
|
67
71
|
appended_prompt_files: appended_prompt_files, toolkit: toolkit, toolkit_flags: toolkit_flags,
|
|
68
|
-
bundle_settings: bundle_settings)
|
|
72
|
+
bundle_settings: bundle_settings, parent_answers: parent_answers)
|
|
69
73
|
commands = []
|
|
70
74
|
commands << "/model #{model}" if model && (attach || resume)
|
|
71
75
|
commands << LLMContextOverride.command_line(llm_context) if (attach || resume) && !llm_context.empty?
|
|
@@ -83,7 +87,9 @@ module Samagotchi
|
|
|
83
87
|
ensure
|
|
84
88
|
close_surface(surface)
|
|
85
89
|
end
|
|
86
|
-
|
|
90
|
+
if ended == :unanswered && attached.unanswered
|
|
91
|
+
report_unanswered(attached.unanswered, session_id: client.session_id, state_dir: state_dir)
|
|
92
|
+
end
|
|
87
93
|
report_ended(ended)
|
|
88
94
|
ended
|
|
89
95
|
end
|
|
@@ -92,9 +98,12 @@ module Samagotchi
|
|
|
92
98
|
# --wait` prints it (ParentReport), so a script or a parent agent
|
|
93
99
|
# can answer it with chi answer.
|
|
94
100
|
# @param pending [Hash] the pending question as the Bridge sent it
|
|
95
|
-
|
|
101
|
+
# @param state_dir [String, nil] where the session file is: what a
|
|
102
|
+
# parent may pick follows its own parent_answers too
|
|
103
|
+
def report_unanswered(pending, session_id:, err: $stderr, state_dir: nil)
|
|
96
104
|
question = PendingQuestion.from_file(pending)
|
|
97
|
-
|
|
105
|
+
strategy = ParentReport.strategy_for(session_id, state_dir: state_dir || Session.default_state_dir)
|
|
106
|
+
err.print("chi: #{ParentReport.question_text(question, session_id: session_id, strategy: strategy)}")
|
|
98
107
|
err.flush
|
|
99
108
|
end
|
|
100
109
|
|
|
@@ -134,7 +143,7 @@ module Samagotchi
|
|
|
134
143
|
# @raise [Error]
|
|
135
144
|
def connect(attach: nil, shared: false, resume: nil, model: nil, state_dir: nil, wait: BRIDGE_WAIT,
|
|
136
145
|
memories: [], muted_memories: [], llm_context: {}, thinking: nil, appended_prompt_files: [], toolkit: nil,
|
|
137
|
-
toolkit_flags: [], bundle_settings: {}, err: $stderr)
|
|
146
|
+
toolkit_flags: [], bundle_settings: {}, parent_answers: nil, err: $stderr)
|
|
138
147
|
sd = state_dir || Session.default_state_dir
|
|
139
148
|
if attach || resume
|
|
140
149
|
warn_memory_flags_ignored(attach || resume, memories, muted_memories, err, appended_prompt_files: appended_prompt_files,
|
|
@@ -156,7 +165,8 @@ module Samagotchi
|
|
|
156
165
|
setup: SessionSetup.new(llm_context: llm_context.empty? ? nil : LLMContextOverride.update(nil, llm_context),
|
|
157
166
|
thinking: Thinking.session_level(thinking),
|
|
158
167
|
appended_prompt_files: appended_prompt_files,
|
|
159
|
-
toolkit: toolkit, bundle_settings: bundle_settings
|
|
168
|
+
toolkit: toolkit, bundle_settings: bundle_settings,
|
|
169
|
+
parent_answers: parent_answers))
|
|
160
170
|
end
|
|
161
171
|
client = BridgeClient.wait_for(session.id, session_dir: Session.session_dir(session.id, state_dir: sd), timeout: wait)
|
|
162
172
|
client || raise(Error, "the worker for session #{session.id} did not start its Bridge in time")
|
|
@@ -592,10 +592,7 @@ module Samagotchi
|
|
|
592
592
|
# The worker runs it and every UI renders its :command_ran, this one
|
|
593
593
|
# too, so nothing waits here.
|
|
594
594
|
def send_command(line)
|
|
595
|
-
|
|
596
|
-
# holds it to guardrails.parent_answers.
|
|
597
|
-
client_id = @as_parent && TurnFlow.continue_yes?(line) ? ClientId::CLI_ANSWER : @client_id
|
|
598
|
-
reply = @client.post_command(line: line, client_id: client_id)
|
|
595
|
+
reply = @client.post_command(line: line, client_id: client_id_for(line))
|
|
599
596
|
if reply.status == 202
|
|
600
597
|
# Input from a pipe ends once its output is in (#command_ran).
|
|
601
598
|
command_id = reply.json&.fetch("command_id", nil)
|
|
@@ -808,13 +805,27 @@ module Samagotchi
|
|
|
808
805
|
steer_cut_note(ack["cut"].to_s.to_sym, ack["cut_after"])
|
|
809
806
|
end
|
|
810
807
|
|
|
808
|
+
# The client id +line+ goes with, as a command (#send_command) or as a
|
|
809
|
+
# message the Bridge may run as one (#send_prompt: /cut and /queue
|
|
810
|
+
# text). A parent agent's /continue yes and /parent-answers go as chi
|
|
811
|
+
# answer's: the worker holds the first to guardrails.parent_answers and
|
|
812
|
+
# never takes the second as a user's loosening. The command is the
|
|
813
|
+
# registry's own match (the worker's), not a pattern of ours.
|
|
814
|
+
def client_id_for(line)
|
|
815
|
+
return @client_id unless @as_parent
|
|
816
|
+
return ClientId::CLI_ANSWER if TurnFlow.continue_yes?(line)
|
|
817
|
+
return ClientId::CLI_ANSWER if command_registry.lookup(line)&.id == :parent_answers
|
|
818
|
+
|
|
819
|
+
@client_id
|
|
820
|
+
end
|
|
821
|
+
|
|
811
822
|
def send_prompt(text, delivery: nil)
|
|
812
823
|
persist_recent_history(text)
|
|
813
824
|
images = attach_images(text)
|
|
814
825
|
return own_prompt_failed if images.nil?
|
|
815
826
|
|
|
816
827
|
running = @running
|
|
817
|
-
options = { prompt: text, client_id:
|
|
828
|
+
options = { prompt: text, client_id: client_id_for(text) }
|
|
818
829
|
options[:no_interrupt] = true if @no_interrupt
|
|
819
830
|
options[:images] = images unless images.empty?
|
|
820
831
|
options[:delivery] = delivery if delivery
|
|
@@ -105,10 +105,14 @@ module Samagotchi
|
|
|
105
105
|
# @param bundle_settings [Hash] --set's values (BundleSessionSettings):
|
|
106
106
|
# a new session's own; a resumed one keeps its own (--set is in
|
|
107
107
|
# +toolkit_flags+ then)
|
|
108
|
+
# @param parent_answers [String, nil] --parent-answers: a new session's
|
|
109
|
+
# own guardrails.parent_answers; a resumed one keeps its own (named in
|
|
110
|
+
# +toolkit_flags+ then)
|
|
108
111
|
def initialize(prompt: nil, client: nil, host_registry: nil, profile: nil, session_id: nil, no_interrupt: false, no_default_input: false, model_name: nil, memories: [], muted_memories: [], non_interactive: false, surface: nil,
|
|
109
112
|
spinner_tick_interval: AttachedView::TICK_INTERVAL, spinner_clock: nil, scratch: false, llm_context: {}, thinking: nil,
|
|
110
|
-
appended_prompt_files: [], toolkit: nil, toolkit_flags: [], bundle_settings: {})
|
|
113
|
+
appended_prompt_files: [], toolkit: nil, toolkit_flags: [], bundle_settings: {}, parent_answers: nil)
|
|
111
114
|
@scratch = scratch
|
|
115
|
+
@parent_answers = parent_answers
|
|
112
116
|
@toolkit = toolkit
|
|
113
117
|
@toolkit_flags = toolkit_flags
|
|
114
118
|
@bundle_settings = bundle_settings
|
|
@@ -281,7 +285,7 @@ module Samagotchi
|
|
|
281
285
|
muted_memory_names: @muted_memory_names,
|
|
282
286
|
scratch: @scratch,
|
|
283
287
|
setup: SessionSetup.new(appended_prompt_files: @appended_prompt_files, toolkit: @toolkit || Toolkit.configured,
|
|
284
|
-
bundle_settings: @bundle_settings)
|
|
288
|
+
bundle_settings: @bundle_settings, parent_answers: @parent_answers)
|
|
285
289
|
))
|
|
286
290
|
# --llm-context*: the session's own values, over a resumed one's.
|
|
287
291
|
# Saved at once on a resumed one: leaving before a turn keeps them.
|
|
@@ -876,9 +880,11 @@ module Samagotchi
|
|
|
876
880
|
command = if command_registry.anytime?(input)
|
|
877
881
|
@engine.running_anytime { @commands.run(input, confirm: confirm) }
|
|
878
882
|
else
|
|
879
|
-
# A parent's /continue yes is held to guardrails.parent_answers
|
|
880
|
-
|
|
881
|
-
|
|
883
|
+
# A parent's /continue yes is held to guardrails.parent_answers;
|
|
884
|
+
# only a person here (not piped, no agent marker) loosens
|
|
885
|
+
# /parent-answers.
|
|
886
|
+
parent = as_parent?
|
|
887
|
+
@commands.run(input, confirm: confirm, as_parent: parent && TurnFlow.continue_yes?(input), user_ui: !parent)
|
|
882
888
|
end
|
|
883
889
|
if command
|
|
884
890
|
show_command_result(command)
|
|
@@ -134,7 +134,10 @@ module Samagotchi
|
|
|
134
134
|
"(the most is #{max}, #{MAX_CHILDREN_KEY}): #{ids}. #{full_hint(peers)}"
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
# The parent's own parent_answers, as it is now (a snapshot): the
|
|
138
|
+
# child's answers are held at least as tight.
|
|
139
|
+
setup = SessionSetup.new(toolkit: toolkit.empty? ? nil : Toolkit.new(name: toolkit),
|
|
140
|
+
parent_answers: parent.parent_answers)
|
|
138
141
|
child = SessionManager.spawn_session(prompt: task, working_directory: folder,
|
|
139
142
|
model_name: child_model(model, parent), memories: CHILD_MEMORIES,
|
|
140
143
|
parent_id: parent.id, delegate: true, setup: setup, state_dir: state_dir)
|
|
@@ -213,6 +213,10 @@ module Samagotchi
|
|
|
213
213
|
# @return [String]
|
|
214
214
|
def finish(wait, child_id, timeout:, report: false, reports: "off")
|
|
215
215
|
status = ParentReport.status(wait)
|
|
216
|
+
# What the parent may pick on the child's question: config.yml
|
|
217
|
+
# tightened by the child session's own parent_answers (the session
|
|
218
|
+
# the wait read; none: config.yml's).
|
|
219
|
+
strategy = Guardrails::ParentPolicy.for_session(wait.session)
|
|
216
220
|
case wait.status
|
|
217
221
|
when :done
|
|
218
222
|
reply_result(child_id, status, wait.text)
|
|
@@ -221,11 +225,11 @@ module Samagotchi
|
|
|
221
225
|
when :stopped
|
|
222
226
|
result(child_id, status, "the child was stopped (chi sessions stop); delegate with session: #{child_id} starts it again with a message")
|
|
223
227
|
when :waiting_for_answer
|
|
224
|
-
result(child_id, status, waiting_text(child_id, wait.question, report: report, reports: reports))
|
|
228
|
+
result(child_id, status, waiting_text(child_id, wait.question, report: report, reports: reports, strategy: strategy))
|
|
225
229
|
when :canceled
|
|
226
230
|
canceled_result(child_id, reports: reports)
|
|
227
231
|
when :no_reply
|
|
228
|
-
result(child_id, status, "#{no_reply_text(wait, child_id)}; its session shows what happened")
|
|
232
|
+
result(child_id, status, "#{no_reply_text(wait, child_id, strategy: strategy)}; its session shows what happened")
|
|
229
233
|
when :worker_gone
|
|
230
234
|
result(child_id, status,
|
|
231
235
|
"the child's worker is gone (it stopped or crashed); delegate with session: #{child_id} starts it again with a message")
|
|
@@ -266,8 +270,8 @@ module Samagotchi
|
|
|
266
270
|
end
|
|
267
271
|
|
|
268
272
|
# ParentReport's line for a turn that left no reply, about the child.
|
|
269
|
-
def no_reply_text(wait, child_id)
|
|
270
|
-
ParentReport.no_reply_line(wait, child_id).sub(/\Athe turn/, "the child's turn")
|
|
273
|
+
def no_reply_text(wait, child_id, strategy: nil)
|
|
274
|
+
ParentReport.no_reply_line(wait, child_id, strategy: strategy).sub(/\Athe turn/, "the child's turn")
|
|
271
275
|
end
|
|
272
276
|
|
|
273
277
|
def result(child_id, status, text)
|
|
@@ -303,9 +307,9 @@ module Samagotchi
|
|
|
303
307
|
# A report's last line differs (+report+): "waits again" made models
|
|
304
308
|
# call delegate_result right after answering Continue.
|
|
305
309
|
# The tool's last line follows #reports_mode.
|
|
306
|
-
def waiting_text(child_id, pending, report: false, reports: "off")
|
|
307
|
-
text = "Child #{child_id} is #{ParentReport.question_text(pending, session_id: child_id)}"
|
|
308
|
-
if ParentReport.continue?(pending) && ParentReport.may_continue?(pending)
|
|
310
|
+
def waiting_text(child_id, pending, report: false, reports: "off", strategy: nil)
|
|
311
|
+
text = "Child #{child_id} is #{ParentReport.question_text(pending, session_id: child_id, strategy: strategy)}"
|
|
312
|
+
if ParentReport.continue?(pending) && ParentReport.may_continue?(pending, strategy: strategy)
|
|
309
313
|
text += "The child ran out of steps before it answered. Decide: continue it (run the chi answer command " \
|
|
310
314
|
"above with execute) if it is getting somewhere; send it a narrower follow-up with delegate " \
|
|
311
315
|
"session: #{child_id} (that drops the question); or stop it and report back to your user.\n"
|
data/lib/samagotchi/version.rb
CHANGED
data/lib/samagotchi/worker.rb
CHANGED
|
@@ -794,7 +794,9 @@ module Samagotchi
|
|
|
794
794
|
# as its output
|
|
795
795
|
def run_command_line(command)
|
|
796
796
|
@engine.holding_announcements do
|
|
797
|
-
command_result(command)
|
|
797
|
+
command_result(command) do
|
|
798
|
+
@commands.run(command.line, confirm: confirm?(command), as_parent: as_parent?(command), user_ui: user_ui?(command))
|
|
799
|
+
end
|
|
798
800
|
end
|
|
799
801
|
end
|
|
800
802
|
|
|
@@ -808,6 +810,12 @@ module Samagotchi
|
|
|
808
810
|
# guardrails.parent_answers (SessionCommands#continue_answer).
|
|
809
811
|
def as_parent?(command) = command.client_id.to_s == ClientId::CLI_ANSWER
|
|
810
812
|
|
|
813
|
+
# Whether +command+ is a user's at a UI (a web tab, a chi TUI, a card's
|
|
814
|
+
# click), not a parent agent's: only such a line loosens
|
|
815
|
+
# /parent-answers. chi send, a delegate's follow-up, a plugin and an
|
|
816
|
+
# input with no client never do (ParentAnswersCommand).
|
|
817
|
+
def user_ui?(command) = ClientId.interactive?(command.client_id) && !as_parent?(command)
|
|
818
|
+
|
|
811
819
|
# The block's Result for +command+; one that raised, or found no
|
|
812
820
|
# command for the line, is an error Result saying so.
|
|
813
821
|
# @return [SessionCommands::Result]
|
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.
|
|
4
|
+
version: 0.87.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -422,6 +422,7 @@ files:
|
|
|
422
422
|
- lib/samagotchi/note_delivery.rb
|
|
423
423
|
- lib/samagotchi/output_formatter.rb
|
|
424
424
|
- lib/samagotchi/owner_lock.rb
|
|
425
|
+
- lib/samagotchi/parent_answers_command.rb
|
|
425
426
|
- lib/samagotchi/parent_report.rb
|
|
426
427
|
- lib/samagotchi/paths.rb
|
|
427
428
|
- lib/samagotchi/peer_sources.rb
|