samagotchi 0.102.0 → 0.104.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 +93 -1
- data/bin/chi +60 -191
- data/docs/architecture.md +67 -1
- data/docs/cli.md +190 -9
- data/docs/configuration.md +7 -1
- data/docs/guardrails.md +4 -2
- data/docs/internals/context-telemetry.md +6 -2
- data/docs/internals/engine.md +5 -2
- data/docs/internals/llm-context-forget.md +11 -1
- data/docs/internals/participants.md +137 -0
- data/docs/internals/remote-sessions.md +429 -0
- data/docs/internals/working-set.md +95 -11
- data/docs/plugins.md +33 -3
- data/docs/sessions.md +16 -7
- data/docs/sub-agent.md +16 -0
- data/docs/testing.md +68 -4
- data/lib/samagotchi/answer_command.rb +27 -25
- data/lib/samagotchi/bridge.rb +31 -7
- data/lib/samagotchi/bridge_client/event_stream.rb +86 -12
- data/lib/samagotchi/bridge_client/stream_request.rb +77 -0
- data/lib/samagotchi/bridge_client.rb +26 -39
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/claude-peer/scripts/pair +30 -1
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/codex-peer/scripts/pair +49 -4
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +7 -4
- data/lib/samagotchi/bundles/guardrails/manifest.yml +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/chi_invocation.rb +40 -0
- data/lib/samagotchi/cli/command.rb +3 -2
- data/lib/samagotchi/cli/flags.rb +5 -0
- data/lib/samagotchi/cli/parent_wait.rb +22 -13
- data/lib/samagotchi/context_command.rb +80 -73
- data/lib/samagotchi/context_status.rb +15 -2
- data/lib/samagotchi/engine.rb +28 -14
- data/lib/samagotchi/guardrail_wiring.rb +7 -1
- data/lib/samagotchi/idle_recap.rb +8 -2
- data/lib/samagotchi/idle_scheduler.rb +34 -9
- data/lib/samagotchi/kernel_loop.rb +21 -2
- data/lib/samagotchi/launch_mode.rb +26 -0
- data/lib/samagotchi/llm/chat_loop.rb +8 -1
- data/lib/samagotchi/llm_context_forget.rb +13 -6
- data/lib/samagotchi/note_command.rb +8 -7
- data/lib/samagotchi/owner_lock.rb +65 -5
- data/lib/samagotchi/parent_report.rb +55 -43
- data/lib/samagotchi/participants.rb +122 -0
- data/lib/samagotchi/remote_attach_command.rb +119 -0
- data/lib/samagotchi/remote_command.rb +170 -0
- data/lib/samagotchi/remotes.rb +130 -0
- data/lib/samagotchi/run_options.rb +245 -0
- data/lib/samagotchi/send_command/busy.rb +58 -0
- data/lib/samagotchi/send_command/parent_gate.rb +23 -10
- data/lib/samagotchi/send_command/wait_target.rb +16 -3
- data/lib/samagotchi/send_command.rb +173 -108
- data/lib/samagotchi/session.rb +38 -9
- data/lib/samagotchi/session_access/contract.rb +167 -0
- data/lib/samagotchi/session_access/dispatch.rb +152 -0
- data/lib/samagotchi/session_access/http_transport.rb +109 -0
- data/lib/samagotchi/session_access/local.rb +400 -0
- data/lib/samagotchi/session_access/remote.rb +734 -0
- data/lib/samagotchi/session_access/web_bridge_client.rb +236 -0
- data/lib/samagotchi/session_access.rb +223 -0
- data/lib/samagotchi/session_archive_command.rb +9 -5
- data/lib/samagotchi/session_commands.rb +20 -15
- data/lib/samagotchi/session_continue.rb +1 -1
- data/lib/samagotchi/session_delete_command.rb +8 -4
- data/lib/samagotchi/session_inbox.rb +18 -9
- data/lib/samagotchi/session_manager.rb +27 -6
- data/lib/samagotchi/session_setup.rb +11 -6
- data/lib/samagotchi/sessions_command.rb +36 -14
- data/lib/samagotchi/share_command.rb +174 -0
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +66 -17
- data/lib/samagotchi/terminal_ui/attach_link.rb +114 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +155 -87
- data/lib/samagotchi/terminal_ui/formatting.rb +13 -1
- data/lib/samagotchi/terminal_ui/local_link.rb +135 -0
- data/lib/samagotchi/terminal_ui/remote_connector.rb +66 -0
- data/lib/samagotchi/terminal_ui/remote_link.rb +245 -0
- data/lib/samagotchi/terminal_ui/version_lines.rb +28 -3
- data/lib/samagotchi/terminal_ui.rb +6 -3
- data/lib/samagotchi/testing/waiting.rb +92 -9
- data/lib/samagotchi/title_command.rb +9 -11
- data/lib/samagotchi/tools/task_runtime.rb +17 -3
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +530 -126
- data/lib/samagotchi/web/grants.rb +302 -0
- data/lib/samagotchi/web/participant_access.rb +286 -0
- data/lib/samagotchi/web/principal.rb +29 -0
- data/lib/samagotchi/web/public/data.js +3 -2
- data/lib/samagotchi/web/public/index.html +20 -4
- data/lib/samagotchi/web/public/stage_frame.js +7 -2
- data/lib/samagotchi/web/public/stage_view.js +3 -2
- data/lib/samagotchi/web/public/stream_handlers.js +3 -1
- data/lib/samagotchi/web/public/working_set.js +159 -28
- data/lib/samagotchi/web/public/working_set_rail.js +39 -12
- data/lib/samagotchi/web/public/working_set_view.js +91 -33
- data/lib/samagotchi/web/server.rb +3 -1
- data/lib/samagotchi/web/session_access_routes.rb +459 -0
- data/lib/samagotchi/worker.rb +26 -5
- data/lib/samagotchi/worker_sidecar.rb +12 -5
- data/lib/samagotchi/working_set.rb +88 -14
- data/lib/samagotchi/working_set_command.rb +133 -30
- data/lib/samagotchi/working_set_pin.rb +34 -0
- data/lib/samagotchi/working_set_steer.rb +191 -27
- metadata +28 -1
|
@@ -8,6 +8,7 @@ require_relative "../thinking_command"
|
|
|
8
8
|
require_relative "../bridge_client"
|
|
9
9
|
require_relative "../parent_report"
|
|
10
10
|
require_relative "../cli/exit"
|
|
11
|
+
require_relative "../launch_mode"
|
|
11
12
|
require_relative "live_region"
|
|
12
13
|
require_relative "plain_surface"
|
|
13
14
|
require_relative "attached_loop"
|
|
@@ -23,6 +24,9 @@ module Samagotchi
|
|
|
23
24
|
class Error < StandardError; end
|
|
24
25
|
|
|
25
26
|
BRIDGE_WAIT = 10.0
|
|
27
|
+
# A connector that answered no session (its own failures raise Error
|
|
28
|
+
# with their words).
|
|
29
|
+
NOTHING_CONNECTED = "could not attach: no session was found or started"
|
|
26
30
|
|
|
27
31
|
module_function
|
|
28
32
|
|
|
@@ -59,17 +63,27 @@ module Samagotchi
|
|
|
59
63
|
# loosening route from a command line)
|
|
60
64
|
# @param state_dir [String, nil] where the sessions are (the default
|
|
61
65
|
# state dir by default): the worker's, for the attached loop too
|
|
66
|
+
# @param connector [#connect, nil] finds or starts the session
|
|
67
|
+
# elsewhere: its #connect returns the AttachLink the loop runs on
|
|
68
|
+
# (nil: this machine's, through .connect; a connector never falls
|
|
69
|
+
# back to it), and its #client_id, when it has one, names this
|
|
70
|
+
# TUI's streams and requests there (#client_id)
|
|
62
71
|
# @return [Symbol] :detached, :closed when the worker went away,
|
|
63
72
|
# :failed when the --model switch didn't go through, or (input from a
|
|
64
73
|
# pipe) :turn_failed / :empty_answer / :unanswered (AttachedLoop#run)
|
|
65
74
|
def run(attach: nil, shared: false, resume: nil, prompt: nil, model: nil, no_interrupt: false, default_input: true,
|
|
66
75
|
memories: [], muted_memories: [], llm_context: {}, thinking: nil, appended_prompt_files: [], toolkit: nil,
|
|
67
|
-
toolkit_flags: [], settings: nil, parent_answers: nil, state_dir: nil)
|
|
76
|
+
toolkit_flags: [], settings: nil, parent_answers: nil, state_dir: nil, connector: nil)
|
|
68
77
|
state_dir ||= Session.default_state_dir
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
link = connector && (connector.connect || raise(Error, NOTHING_CONNECTED))
|
|
79
|
+
client = if connector
|
|
80
|
+
link.client
|
|
81
|
+
else
|
|
82
|
+
connect(attach: attach, shared: shared, resume: resume, model: model, state_dir: state_dir,
|
|
83
|
+
memories: memories, muted_memories: muted_memories, llm_context: llm_context,
|
|
84
|
+
thinking: thinking, appended_prompt_files: appended_prompt_files, toolkit: toolkit,
|
|
85
|
+
toolkit_flags: toolkit_flags, settings: settings, parent_answers: parent_answers)
|
|
86
|
+
end
|
|
73
87
|
commands = []
|
|
74
88
|
commands << "/model #{model}" if model && (attach || resume)
|
|
75
89
|
commands << LLMContextOverride.command_line(llm_context) if (attach || resume) && !llm_context.empty?
|
|
@@ -77,21 +91,39 @@ module Samagotchi
|
|
|
77
91
|
commands << "#{ThinkingCommand::NAME} #{thinking} --yes" if (attach || resume) && thinking
|
|
78
92
|
first_command = commands.size > 1 ? commands : commands.first
|
|
79
93
|
surface = open_surface
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
options = { screen: surface, client_id: client_id(connector), first_prompt: prompt,
|
|
95
|
+
first_command: first_command, no_interrupt: no_interrupt,
|
|
96
|
+
default_input: default_input && !prompt && !attach && !resume,
|
|
97
|
+
wait_at_eof: !$stdin.tty?,
|
|
98
|
+
as_parent: Guardrails::ParentApprovals.parent_process? }
|
|
99
|
+
attached = if connector
|
|
100
|
+
AttachedLoop.new(link: link, **options)
|
|
101
|
+
else
|
|
102
|
+
AttachedLoop.new(client: client, **options, state_dir: state_dir)
|
|
103
|
+
end
|
|
85
104
|
ended = begin
|
|
86
105
|
attached.run
|
|
87
106
|
ensure
|
|
88
107
|
close_surface(surface)
|
|
89
108
|
end
|
|
90
109
|
if ended == :unanswered && attached.unanswered
|
|
91
|
-
|
|
110
|
+
# A link's strategy is read where the session is, now (L4), its
|
|
111
|
+
# commands spelled its way.
|
|
112
|
+
where = connector ? { strategy: link.parent_strategy, via: link.via } : {}
|
|
113
|
+
report_unanswered(attached.unanswered, session_id: client.session_id, state_dir: state_dir, **where)
|
|
92
114
|
end
|
|
93
115
|
report_ended(ended)
|
|
94
116
|
ended
|
|
117
|
+
ensure
|
|
118
|
+
link&.close
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# This TUI's client id: tui:PID, or the connector's own (a remote one
|
|
122
|
+
# adds the host, for the UIs there).
|
|
123
|
+
def client_id(connector)
|
|
124
|
+
return connector.client_id if connector.respond_to?(:client_id)
|
|
125
|
+
|
|
126
|
+
"tui:#{Process.pid}"
|
|
95
127
|
end
|
|
96
128
|
|
|
97
129
|
# A question left waiting (exit 3), in full on stderr as `chi send
|
|
@@ -100,10 +132,16 @@ module Samagotchi
|
|
|
100
132
|
# @param pending [Hash] the pending question as the Bridge sent it
|
|
101
133
|
# @param state_dir [String, nil] where the session file is: what a
|
|
102
134
|
# parent may pick follows its own parent_answers too
|
|
103
|
-
|
|
135
|
+
# @param strategy [Guardrails::ParentPolicy::Strategy, nil] that
|
|
136
|
+
# strategy, read already (an AttachLink's); nil:
|
|
137
|
+
# ParentReport.strategy_for here
|
|
138
|
+
# @param via [ChiInvocation, nil] how its commands are spelled (nil:
|
|
139
|
+
# this machine's)
|
|
140
|
+
def report_unanswered(pending, session_id:, err: $stderr, state_dir: nil, strategy: nil, via: nil)
|
|
104
141
|
question = PendingQuestion.from_file(pending)
|
|
105
|
-
strategy
|
|
106
|
-
|
|
142
|
+
strategy ||= ParentReport.strategy_for(session_id, state_dir: state_dir || Session.default_state_dir)
|
|
143
|
+
words = via ? { via: via } : {}
|
|
144
|
+
err.print("chi: #{ParentReport.question_text(question, session_id: session_id, strategy: strategy, **words)}")
|
|
107
145
|
err.flush
|
|
108
146
|
end
|
|
109
147
|
|
|
@@ -157,6 +195,7 @@ module Samagotchi
|
|
|
157
195
|
resume = attach
|
|
158
196
|
end
|
|
159
197
|
|
|
198
|
+
await_leaving_worker(resume, sd, err) if resume
|
|
160
199
|
session = if resume
|
|
161
200
|
SessionManager.resume_session(resume, state_dir: sd)
|
|
162
201
|
else
|
|
@@ -169,13 +208,23 @@ module Samagotchi
|
|
|
169
208
|
parent_answers: parent_answers))
|
|
170
209
|
end
|
|
171
210
|
client = BridgeClient.wait_for(session.id, session_dir: Session.session_dir(session.id, state_dir: sd), timeout: wait)
|
|
172
|
-
client || raise(Error,
|
|
173
|
-
rescue SessionManager::OwnedByTUI
|
|
174
|
-
raise Error,
|
|
211
|
+
client || raise(Error, format(LaunchMode::BRIDGE_LATE, session.id))
|
|
212
|
+
rescue SessionManager::OwnedByTUI => e
|
|
213
|
+
raise Error, e.message
|
|
175
214
|
rescue ArgumentError => e
|
|
176
215
|
raise Error, e.message
|
|
177
216
|
end
|
|
178
217
|
|
|
218
|
+
# The session's previous worker may still be leaving: its Bridge
|
|
219
|
+
# closed, the lock held while it writes its recap on leave (a resume
|
|
220
|
+
# now would start nothing, the lock taken). Wait for it to let go
|
|
221
|
+
# (OwnerLock.await_release), with one line saying so; any other owner
|
|
222
|
+
# is left to resume_session as before.
|
|
223
|
+
def await_leaving_worker(session_id, state_dir, err)
|
|
224
|
+
OwnerLock.await_release(Session.session_dir(session_id, state_dir: state_dir),
|
|
225
|
+
on_wait: ->(_owner) { err.puts(LaunchMode::RECAP_WAIT) })
|
|
226
|
+
end
|
|
227
|
+
|
|
179
228
|
# --model as typed: spawn_session stores the resolved ref and this name.
|
|
180
229
|
def model_ref(model)
|
|
181
230
|
ModelProfile.required_model_name(model)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Samagotchi
|
|
4
|
+
class TerminalUI
|
|
5
|
+
# What the attached TUI (AttachedLoop) knows about *where* its session
|
|
6
|
+
# is, beside the client it sends requests with: how the worker is found
|
|
7
|
+
# again, what the worker can do, the session's files it reads (images,
|
|
8
|
+
# saved records, children), the session calls it makes (delete,
|
|
9
|
+
# archive), and how the commands it prints are spelled. LocalLink is
|
|
10
|
+
# this machine's (the session's state dir, its worker's Bridge); a
|
|
11
|
+
# remote attach plugs in another.
|
|
12
|
+
#
|
|
13
|
+
# Each method here raises NotImplementedError until a link defines it.
|
|
14
|
+
module AttachLink
|
|
15
|
+
# Which Tab completions the TUI offers beside the / commands.
|
|
16
|
+
# @!attribute paths [Boolean] @path: this machine's files
|
|
17
|
+
# @!attribute memories [Boolean] #name: this machine's memories
|
|
18
|
+
Completions = Data.define(:paths, :memories)
|
|
19
|
+
|
|
20
|
+
class Completions
|
|
21
|
+
ALL = new(paths: true, memories: true)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The port a worker_changed event names (string or symbol keys; nil
|
|
25
|
+
# for a worker reached another way).
|
|
26
|
+
def self.event_port(event) = event[:port] || event["port"]
|
|
27
|
+
|
|
28
|
+
# @return [#session_id, #follow, #post_turn, ...] the session's client
|
|
29
|
+
# (a BridgeClient on this machine), the newest after #switched
|
|
30
|
+
def client = raise(NotImplementedError)
|
|
31
|
+
|
|
32
|
+
# The session's live worker for the stream to follow (EventStream's
|
|
33
|
+
# rediscover).
|
|
34
|
+
# @return [Array(Object, String)] [a client, nil] for a live one,
|
|
35
|
+
# [nil, nil] while there is none yet, or [:gone, reason] once no
|
|
36
|
+
# worker is coming
|
|
37
|
+
def live_worker = raise(NotImplementedError)
|
|
38
|
+
|
|
39
|
+
# The stream moved to a new worker (its worker_changed event, string
|
|
40
|
+
# or symbol keys): requests go there too.
|
|
41
|
+
# @return [Object] the client from now on (#client)
|
|
42
|
+
def switched(_event) = raise(NotImplementedError)
|
|
43
|
+
|
|
44
|
+
# Whether the worker runs delivery: queue (/queue).
|
|
45
|
+
def queues? = raise(NotImplementedError)
|
|
46
|
+
|
|
47
|
+
# Whether the worker takes queued messages back (/unqueue).
|
|
48
|
+
def unqueues? = raise(NotImplementedError)
|
|
49
|
+
|
|
50
|
+
# The session's metrics for /stats, nil when the worker did not answer.
|
|
51
|
+
# @return [Hash, nil] string keys
|
|
52
|
+
# @raise [IOError] the session's machine couldn't be asked (a remote's
|
|
53
|
+
# Unreachable: the loop shows its message)
|
|
54
|
+
def stats_metrics = raise(NotImplementedError)
|
|
55
|
+
|
|
56
|
+
# Store a prompt's @path images where the worker reads them.
|
|
57
|
+
# @param paths [Array<String>] the files as typed
|
|
58
|
+
# @return [Array<Hash>] refs ({file:, name:}) for the turn's images
|
|
59
|
+
# @raise [ImageStore::Error] one can't be used
|
|
60
|
+
def ingest_images(_paths) = raise(NotImplementedError)
|
|
61
|
+
|
|
62
|
+
# A turn's saved tool records ([] when none, never raises).
|
|
63
|
+
# @return [Array<Hash>]
|
|
64
|
+
def saved_tool_records(_turn_id) = raise(NotImplementedError)
|
|
65
|
+
|
|
66
|
+
# A turn's saved record (nil when none, never raises).
|
|
67
|
+
# @return [Hash, nil]
|
|
68
|
+
def saved_turn_record(_turn_id) = raise(NotImplementedError)
|
|
69
|
+
|
|
70
|
+
# The session's delegates' counts, the status row's children segment.
|
|
71
|
+
# @return [ChildrenStatus::Counts]
|
|
72
|
+
def children_counts = raise(NotImplementedError)
|
|
73
|
+
|
|
74
|
+
# /exit --delete, once the worker let go.
|
|
75
|
+
def delete_session = raise(NotImplementedError)
|
|
76
|
+
|
|
77
|
+
# /archive: SessionManager.archive_session's result ({discarded: […]}).
|
|
78
|
+
# @return [Hash]
|
|
79
|
+
def archive_session = raise(NotImplementedError)
|
|
80
|
+
|
|
81
|
+
# The newest chi installed where the worker runs, for the version line.
|
|
82
|
+
# @return [String, nil]
|
|
83
|
+
def installed_version = raise(NotImplementedError)
|
|
84
|
+
|
|
85
|
+
# The config's default model where the worker runs, as its /model
|
|
86
|
+
# names it.
|
|
87
|
+
# @return [String, nil]
|
|
88
|
+
def default_model_name = raise(NotImplementedError)
|
|
89
|
+
|
|
90
|
+
# What a parent agent may pick on the session's questions, read now
|
|
91
|
+
# (ParentReport.strategy_for).
|
|
92
|
+
def parent_strategy = raise(NotImplementedError)
|
|
93
|
+
|
|
94
|
+
# @return [Completions]
|
|
95
|
+
def completions = raise(NotImplementedError)
|
|
96
|
+
|
|
97
|
+
# The stream's sleeps between failed reconnects.
|
|
98
|
+
# @return [Array<Float>]
|
|
99
|
+
def reconnect_delays = raise(NotImplementedError)
|
|
100
|
+
|
|
101
|
+
# How the commands the TUI prints are spelled.
|
|
102
|
+
# @return [ChiInvocation]
|
|
103
|
+
def via = raise(NotImplementedError)
|
|
104
|
+
|
|
105
|
+
# Why the session's machine couldn't be asked the last time (for the
|
|
106
|
+
# stream's closing line), nil when there is nothing more to say.
|
|
107
|
+
# @return [String, nil]
|
|
108
|
+
def failure = raise(NotImplementedError)
|
|
109
|
+
|
|
110
|
+
# The attach is over (AttachLauncher.run): let go of what it held.
|
|
111
|
+
def close = raise(NotImplementedError)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|