samagotchi 0.92.0 → 0.94.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 +53 -1
- data/README.md +1 -1
- data/bin/chi +4 -4
- data/docs/architecture.md +4 -4
- data/docs/cli.md +1 -1
- data/docs/guardrails.md +4 -5
- data/docs/hooks.md +1 -1
- data/docs/internals/bundles.md +1 -1
- data/docs/internals/engine.md +6 -0
- data/docs/memory.md +26 -1
- data/docs/plugins.md +37 -0
- data/docs/releasing.md +2 -2
- data/docs/sessions.md +1 -1
- data/docs/testing.md +11 -2
- data/lib/samagotchi/bootstrap/probe.rb +2 -2
- data/lib/samagotchi/bootstrap_command.rb +1 -0
- data/lib/samagotchi/bundle_command.rb +1 -0
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -1
- data/lib/samagotchi/bundles/model-notes/manifest.yml +11 -0
- data/lib/samagotchi/bundles/model-notes/model_notes_deepseek.md +7 -0
- data/lib/samagotchi/bundles/model-notes/notes/README.md +37 -0
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/child_reports.rb +12 -14
- data/lib/samagotchi/client_id.rb +18 -0
- data/lib/samagotchi/config/restart_watch.rb +112 -0
- data/lib/samagotchi/config.rb +18 -59
- data/lib/samagotchi/engine.rb +4 -14
- data/lib/samagotchi/guardrails/data_readers.rb +34 -30
- data/lib/samagotchi/guardrails/parent_levels.rb +31 -0
- data/lib/samagotchi/guardrails/parent_policy.rb +9 -20
- data/lib/samagotchi/guardrails/shell_lex.rb +28 -7
- data/lib/samagotchi/hook_question.rb +21 -0
- data/lib/samagotchi/hook_routes.rb +6 -6
- data/lib/samagotchi/hooks/registry.rb +5 -5
- data/lib/samagotchi/host_registry.rb +1 -1
- data/lib/samagotchi/llm/anthropic_messages.rb +5 -0
- data/lib/samagotchi/memory_bundle/profile.rb +22 -4
- data/lib/samagotchi/memory_bundle/shipped_update.rb +5 -1
- data/lib/samagotchi/parent_answers_command.rb +6 -6
- data/lib/samagotchi/plugin/context.rb +6 -5
- data/lib/samagotchi/plugin/http_transport.rb +14 -3
- data/lib/samagotchi/plugin/sessions.rb +2 -3
- data/lib/samagotchi/question_desk.rb +12 -9
- data/lib/samagotchi/reply_wait/poll.rb +267 -0
- data/lib/samagotchi/reply_wait.rb +47 -190
- data/lib/samagotchi/send_command/parent_gate.rb +83 -0
- data/lib/samagotchi/send_command/wait_target.rb +55 -0
- data/lib/samagotchi/send_command.rb +31 -100
- data/lib/samagotchi/session.rb +10 -9
- data/lib/samagotchi/session_inbox.rb +31 -8
- data/lib/samagotchi/session_manager.rb +48 -15
- data/lib/samagotchi/session_metrics.rb +22 -3
- data/lib/samagotchi/sessions_command.rb +4 -4
- data/lib/samagotchi/terminal_ui/attached_loop.rb +3 -3
- data/lib/samagotchi/terminal_ui/formatting.rb +8 -5
- data/lib/samagotchi/testing/bundle.rb +1 -1
- data/lib/samagotchi/testing/host.rb +2 -8
- data/lib/samagotchi/testing/recorder.rb +6 -9
- data/lib/samagotchi/testing/sandbox.rb +26 -0
- data/lib/samagotchi/tools/delegate.rb +10 -10
- data/lib/samagotchi/tools/delegate_cursor.rb +32 -8
- data/lib/samagotchi/tools/delegate_relay.rb +31 -16
- data/lib/samagotchi/tools/delegate_wait.rb +4 -4
- data/lib/samagotchi/update_command.rb +1 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +46 -63
- data/lib/samagotchi/web/public/app.js +137 -638
- data/lib/samagotchi/web/public/dock_reserve.js +103 -0
- data/lib/samagotchi/web/public/index.html +4 -4
- data/lib/samagotchi/web/public/send_turn.js +173 -0
- data/lib/samagotchi/web/public/session_actions.js +203 -0
- data/lib/samagotchi/web/public/start_page.js +236 -0
- data/lib/samagotchi/web/public/toast_place.js +1 -1
- metadata +14 -1
|
@@ -85,7 +85,7 @@ module Samagotchi
|
|
|
85
85
|
|
|
86
86
|
fields = Relay.card(child, question, relay_id: relay_id, more: more)
|
|
87
87
|
answer = relay.open_question(fields, watch: watch(child_id, qid, state_dir))
|
|
88
|
-
settle(answer, fields, client: client, desk: desk, relay_id: relay_id, qid: qid)
|
|
88
|
+
settle(answer, fields, label: label(question), client: client, desk: desk, relay_id: relay_id, qid: qid)
|
|
89
89
|
ensure
|
|
90
90
|
# A relay that failed before it settled closes, so the desk (which
|
|
91
91
|
# prunes only settled ones) keeps none open; a settled one stays.
|
|
@@ -108,12 +108,11 @@ module Samagotchi
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
# Record the parent's answer, tell the child, and say what came of it.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
hook = fields[:kind] == HOOK_KIND
|
|
111
|
+
# @param label [String] the outcome line's start (#label)
|
|
112
|
+
def settle(answer, fields, label:, client:, desk:, relay_id:, qid:)
|
|
114
113
|
if answer.is_a?(Hash) && answer[:error] == "cancelled"
|
|
115
114
|
desk.close(relay_id)
|
|
116
|
-
return closed(answer[:reason].to_s,
|
|
115
|
+
return closed(answer[:reason].to_s, label, client: client, relay_id: relay_id, qid: qid)
|
|
117
116
|
end
|
|
118
117
|
|
|
119
118
|
dismissed = !answer.is_a?(QuestionAnswer)
|
|
@@ -121,18 +120,18 @@ module Samagotchi
|
|
|
121
120
|
desk.record(relay_id, selected_indices: dismissed ? [] : answer.selected_indices,
|
|
122
121
|
freeform: dismissed ? nil : answer.freeform, dismissed: dismissed, by: by)
|
|
123
122
|
response = post(client, "answered", relay_id, qid)
|
|
124
|
-
Outcome.new(line: line(
|
|
123
|
+
Outcome.new(line: line(label, delivered(response, answer, fields, dismissed)), stopped: false)
|
|
125
124
|
end
|
|
126
125
|
|
|
127
|
-
def closed(reason,
|
|
126
|
+
def closed(reason, label, client:, relay_id:, qid:)
|
|
128
127
|
case reason
|
|
129
|
-
when "answered_on_child" then Outcome.new(line: line(
|
|
128
|
+
when "answered_on_child" then Outcome.new(line: line(label, "answered on the child"), stopped: false)
|
|
130
129
|
when "child_gone"
|
|
131
|
-
Outcome.new(line: line(
|
|
130
|
+
Outcome.new(line: line(label, "not delivered (the child's worker is gone)"), stopped: false)
|
|
132
131
|
else
|
|
133
132
|
# A Stop: the child's question stays open there.
|
|
134
133
|
post(client, "closed", relay_id, qid, reason: "stopped")
|
|
135
|
-
Outcome.new(line: line(
|
|
134
|
+
Outcome.new(line: line(label, "still open (your turn was stopped)"), stopped: true)
|
|
136
135
|
end
|
|
137
136
|
end
|
|
138
137
|
|
|
@@ -172,14 +171,30 @@ module Samagotchi
|
|
|
172
171
|
why.empty? ? "denied" : "denied (#{why.inspect})"
|
|
173
172
|
end
|
|
174
173
|
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
# One relayed question's outcome line: #label → +outcome+.
|
|
175
|
+
def line(label, outcome) = "#{label} → #{outcome}"
|
|
176
|
+
|
|
177
|
+
# What an outcome line starts with: "hook question relayed to your
|
|
178
|
+
# user: Cost cap", "approval relayed to your user: execute: git push".
|
|
179
|
+
# @param question [PendingQuestion] the child's
|
|
180
|
+
def label(question)
|
|
181
|
+
"#{question.kind.to_s == HOOK_KIND ? "hook question" : "approval"} relayed to your user: #{what(question)}"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# What a relayed question is about, as its card has it (Relay.card): a
|
|
185
|
+
# hook's header as the asking hook gave it (kept down a chain: a relay
|
|
186
|
+
# of a relay), else its hook; an approval's call (#what_text).
|
|
187
|
+
# @param question [PendingQuestion] the child's
|
|
188
|
+
def what(question)
|
|
189
|
+
return what_text(symbolized(question.approval)) unless question.kind.to_s == HOOK_KIND
|
|
190
|
+
|
|
191
|
+
inner = symbolized(question.relay, nil)
|
|
192
|
+
header = (inner ? inner[:header] : question.header).to_s
|
|
193
|
+
header.empty? ? question.hook.to_s : header
|
|
177
194
|
end
|
|
178
195
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
header = fields.dig(:relay, :header).to_s
|
|
182
|
-
header.empty? ? fields[:hook].to_s : header
|
|
196
|
+
def symbolized(value, none = {})
|
|
197
|
+
value.is_a?(Hash) ? Relay.deep_symbolize(value) : none
|
|
183
198
|
end
|
|
184
199
|
|
|
185
200
|
# execute: git push origin main
|
|
@@ -69,7 +69,7 @@ module Samagotchi
|
|
|
69
69
|
cursor = DelegateCursors.get(peers.session_id, child_id, state_dir: sd)
|
|
70
70
|
report_claim = cursor.report_claim
|
|
71
71
|
if report_claim
|
|
72
|
-
cursor =
|
|
72
|
+
cursor = report_claim.cursor.with(report_claim: report_claim)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
baseline = cursor.baseline
|
|
@@ -88,10 +88,10 @@ module Samagotchi
|
|
|
88
88
|
outcomes.concat(relayed_outcomes.delete(key) || [])
|
|
89
89
|
movement = advance(peers.session_id, child_id, wait, state_dir: sd)
|
|
90
90
|
if movement&.already_reported
|
|
91
|
-
file = movement.cursor.report_claim&.
|
|
91
|
+
file = movement.cursor.report_claim&.file || wait.file
|
|
92
92
|
return already_reported_result(child_id, file, state_dir: sd)
|
|
93
93
|
end
|
|
94
|
-
return already_reported_result(child_id, report_claim
|
|
94
|
+
return already_reported_result(child_id, report_claim.file, state_dir: sd) if report_claim && wait.status == :timeout
|
|
95
95
|
|
|
96
96
|
text = with_outcomes(finish(wait, child_id, timeout: timeout, reports: reports), outcomes)
|
|
97
97
|
return RefText.wrap(text, reply_path(child_id, wait, state_dir: sd))
|
|
@@ -179,7 +179,7 @@ module Samagotchi
|
|
|
179
179
|
|
|
180
180
|
already_reported = false
|
|
181
181
|
cursor = DelegateCursors.update(parent_id, child_id, state_dir: state_dir) do |current|
|
|
182
|
-
if (current.report_claim && current.report_claim
|
|
182
|
+
if (current.report_claim && current.report_claim.file == wait.file) ||
|
|
183
183
|
(wait.status == :done && current.reply_file == wait.file)
|
|
184
184
|
already_reported = true
|
|
185
185
|
next current
|
|
@@ -203,7 +203,7 @@ module Samagotchi
|
|
|
203
203
|
row = Row.new(component: update.name, from: update.from, to: update.to)
|
|
204
204
|
case update.status
|
|
205
205
|
when :would_update, :updated
|
|
206
|
-
notes = [adds(update.adds), kept(update.kept, update.name), replaced(update.replaced)].compact
|
|
206
|
+
notes = [adds(update.adds), kept(update.kept, update.name), replaced(update.replaced), *update.kept_own].compact
|
|
207
207
|
done(row, :update, notes.empty? ? nil : notes.join("; "))
|
|
208
208
|
when :up_to_date then up_to_date(row)
|
|
209
209
|
when :skipped then skipped(row, update.note)
|
data/lib/samagotchi/version.rb
CHANGED
data/lib/samagotchi/web/app.rb
CHANGED
|
@@ -152,7 +152,8 @@ module Samagotchi
|
|
|
152
152
|
private
|
|
153
153
|
|
|
154
154
|
# The routes, first match wins: a handler takes the request and the
|
|
155
|
-
# path's captures (the session id, an image's name).
|
|
155
|
+
# path's captures (the session id, an image's name). A :session route
|
|
156
|
+
# runs in #session_route: an ArgumentError is its unknown session.
|
|
156
157
|
ROUTES = [
|
|
157
158
|
["GET", %r{\A/(?:index\.html)?\z}, :serve_index],
|
|
158
159
|
["GET", %r{\A/api/sessions\z}, :handle_list],
|
|
@@ -163,26 +164,26 @@ module Samagotchi
|
|
|
163
164
|
["GET", %r{\A/api/events\z}, :handle_events],
|
|
164
165
|
["POST", %r{\A/api/render\z}, :handle_render],
|
|
165
166
|
["GET", %r{\A/api/sessions/([^/]+)/stream\z}, :handle_stream],
|
|
166
|
-
["GET", %r{\A/api/sessions/([^/]+)/output\z}, :handle_output],
|
|
167
|
-
["POST", %r{\A/api/sessions/([^/]+)/cancel\z}, :handle_cancel],
|
|
168
|
-
["POST", %r{\A/api/sessions/([^/]+)/tasks/([^/]+)/stop\z}, :handle_task_stop],
|
|
169
|
-
["POST", %r{\A/api/sessions/([^/]+)/turn/([^/]+)/dequeue\z}, :handle_dequeue_turn],
|
|
170
|
-
["POST", %r{\A/api/sessions/([^/]+)/stop\z}, :handle_stop],
|
|
171
|
-
["POST", %r{\A/api/sessions/([^/]+)/restart\z}, :handle_restart],
|
|
172
|
-
["POST", %r{\A/api/sessions/([^/]+)/archive\z}, :handle_archive],
|
|
173
|
-
["POST", %r{\A/api/sessions/([^/]+)/unarchive\z}, :handle_unarchive],
|
|
174
|
-
["POST", %r{\A/api/sessions/([^/]+)/turn\z}, :handle_turn],
|
|
167
|
+
["GET", %r{\A/api/sessions/([^/]+)/output\z}, :handle_output, :session],
|
|
168
|
+
["POST", %r{\A/api/sessions/([^/]+)/cancel\z}, :handle_cancel, :session],
|
|
169
|
+
["POST", %r{\A/api/sessions/([^/]+)/tasks/([^/]+)/stop\z}, :handle_task_stop, :session],
|
|
170
|
+
["POST", %r{\A/api/sessions/([^/]+)/turn/([^/]+)/dequeue\z}, :handle_dequeue_turn, :session],
|
|
171
|
+
["POST", %r{\A/api/sessions/([^/]+)/stop\z}, :handle_stop, :session],
|
|
172
|
+
["POST", %r{\A/api/sessions/([^/]+)/restart\z}, :handle_restart, :session],
|
|
173
|
+
["POST", %r{\A/api/sessions/([^/]+)/archive\z}, :handle_archive, :session],
|
|
174
|
+
["POST", %r{\A/api/sessions/([^/]+)/unarchive\z}, :handle_unarchive, :session],
|
|
175
|
+
["POST", %r{\A/api/sessions/([^/]+)/turn\z}, :handle_turn, :session],
|
|
175
176
|
["POST", %r{\A/api/sessions/([^/]+)/answer\z}, :handle_question_answer],
|
|
176
177
|
["POST", %r{\A/api/sessions/([^/]+)/question/dismiss\z}, :handle_question_dismiss],
|
|
177
|
-
["POST", %r{\A/api/sessions/([^/]+)/command\z}, :handle_command],
|
|
178
|
+
["POST", %r{\A/api/sessions/([^/]+)/command\z}, :handle_command, :session],
|
|
178
179
|
["POST", %r{\A/api/sessions/([^/]+)/images\z}, :handle_image_upload],
|
|
179
180
|
["GET", %r{\A/api/sessions/([^/]+)/images/([^/]+)\z}, :handle_image],
|
|
180
|
-
["GET", %r{\A/api/sessions/([^/]+)/context\z}, :handle_context_list],
|
|
181
|
-
["GET", %r{\A/api/sessions/([^/]+)/context/([^/]+)\z}, :handle_context_show],
|
|
182
|
-
["POST", %r{\A/api/sessions/([^/]+)/context\z}, :handle_context_add],
|
|
183
|
-
["DELETE", %r{\A/api/sessions/([^/]+)/context/([^/]+)\z}, :handle_context_delete],
|
|
184
|
-
["GET", %r{\A/api/sessions/([^/]+)\z}, :handle_show],
|
|
185
|
-
["DELETE", %r{\A/api/sessions/([^/]+)\z}, :handle_delete]
|
|
181
|
+
["GET", %r{\A/api/sessions/([^/]+)/context\z}, :handle_context_list, :session],
|
|
182
|
+
["GET", %r{\A/api/sessions/([^/]+)/context/([^/]+)\z}, :handle_context_show, :session],
|
|
183
|
+
["POST", %r{\A/api/sessions/([^/]+)/context\z}, :handle_context_add, :session],
|
|
184
|
+
["DELETE", %r{\A/api/sessions/([^/]+)/context/([^/]+)\z}, :handle_context_delete, :session],
|
|
185
|
+
["GET", %r{\A/api/sessions/([^/]+)\z}, :handle_show, :session],
|
|
186
|
+
["DELETE", %r{\A/api/sessions/([^/]+)\z}, :handle_delete, :session]
|
|
186
187
|
].freeze
|
|
187
188
|
|
|
188
189
|
def route(env)
|
|
@@ -195,7 +196,7 @@ module Samagotchi
|
|
|
195
196
|
return denied if denied
|
|
196
197
|
end
|
|
197
198
|
|
|
198
|
-
ROUTES.each do |verb, pattern, handler|
|
|
199
|
+
ROUTES.each do |verb, pattern, handler, kind|
|
|
199
200
|
next unless req.request_method == verb && (m = pattern.match(req.path_info))
|
|
200
201
|
# Every /api/sessions/:id route: an id that isn't one (Session.valid_id?,
|
|
201
202
|
# "..", a %2F) is an unknown session before any handler makes a path.
|
|
@@ -203,6 +204,8 @@ module Samagotchi
|
|
|
203
204
|
return error_response(404, "not_found", "Session not found: #{m.captures.first.to_s[0, 80]}")
|
|
204
205
|
end
|
|
205
206
|
|
|
207
|
+
return session_route { send(handler, req, *m.captures) } if kind == :session
|
|
208
|
+
|
|
206
209
|
return send(handler, req, *m.captures)
|
|
207
210
|
end
|
|
208
211
|
# Anything else is a file under public_dir (/assets/*, /public/* or
|
|
@@ -213,6 +216,16 @@ module Samagotchi
|
|
|
213
216
|
error_response(500, "internal_error", e.message)
|
|
214
217
|
end
|
|
215
218
|
|
|
219
|
+
# A :session route's handler: the ArgumentError its session lookup
|
|
220
|
+
# raises (Session.load, the manager's find) is a 404 not_found with
|
|
221
|
+
# its message. Only these routes: elsewhere an ArgumentError is a bug,
|
|
222
|
+
# #route's 500. A handler's own rescues run first.
|
|
223
|
+
def session_route
|
|
224
|
+
yield
|
|
225
|
+
rescue ArgumentError => e
|
|
226
|
+
error_response(404, "not_found", e.message)
|
|
227
|
+
end
|
|
228
|
+
|
|
216
229
|
# API requests at debug level (not the page and its assets): method,
|
|
217
230
|
# path, status, time. Never a body or the query; only a tail=1 read
|
|
218
231
|
# (the page's end-of-turn re-read) and a timing=1 one (its fallback
|
|
@@ -737,7 +750,7 @@ module Samagotchi
|
|
|
737
750
|
rescue SessionManager::ArchiveRefused => e
|
|
738
751
|
[nil, error_response(409, e.reason == :scratch ? "scratch" : "busy", e.message)]
|
|
739
752
|
rescue SessionManager::OwnedByTUI
|
|
740
|
-
[nil,
|
|
753
|
+
[nil, owned_by_repl(given)]
|
|
741
754
|
rescue ModelProfile::MissingModel => e
|
|
742
755
|
[nil, error_response(400, "invalid_model", e.message)]
|
|
743
756
|
rescue Session::AmbiguousId => e
|
|
@@ -839,8 +852,6 @@ module Samagotchi
|
|
|
839
852
|
last_event_id: turn_snapshot && turn_snapshot["event_id"],
|
|
840
853
|
timing: timing
|
|
841
854
|
})
|
|
842
|
-
rescue ArgumentError => e
|
|
843
|
-
error_response(404, "not_found", e.message)
|
|
844
855
|
end
|
|
845
856
|
|
|
846
857
|
# A list in the worker's turn +snapshot+ (queued prompts, commands,
|
|
@@ -948,8 +959,6 @@ module Samagotchi
|
|
|
948
959
|
**viewer_capabilities(req),
|
|
949
960
|
timing: timing
|
|
950
961
|
})
|
|
951
|
-
rescue ArgumentError => e
|
|
952
|
-
error_response(404, "not_found", e.message)
|
|
953
962
|
end
|
|
954
963
|
|
|
955
964
|
# The folder the live tail's answer's file refs must stay under: the
|
|
@@ -1132,8 +1141,6 @@ module Samagotchi
|
|
|
1132
1141
|
end
|
|
1133
1142
|
rescue SessionManager::OwnedByTUI => e
|
|
1134
1143
|
error_response(409, "owned_by_tui", e.message)
|
|
1135
|
-
rescue ArgumentError => e
|
|
1136
|
-
error_response(404, "not_found", e.message)
|
|
1137
1144
|
end
|
|
1138
1145
|
|
|
1139
1146
|
# Hand a request to the session's live Bridge (+client+, nil without
|
|
@@ -1169,6 +1176,12 @@ module Samagotchi
|
|
|
1169
1176
|
not_live(id)
|
|
1170
1177
|
end
|
|
1171
1178
|
|
|
1179
|
+
# 409 owned_by_tui in the words a refused archive, delete or continue
|
|
1180
|
+
# shows (stop, restart, a turn and a command show OwnedByTUI's own).
|
|
1181
|
+
def owned_by_repl(id)
|
|
1182
|
+
error_response(409, "owned_by_tui", SessionManager::OwnedByTUI.new(id).message)
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1172
1185
|
def not_live(id)
|
|
1173
1186
|
error_response(503, "not_live", "no live bridge for session #{id}")
|
|
1174
1187
|
end
|
|
@@ -1204,8 +1217,6 @@ module Samagotchi
|
|
|
1204
1217
|
# Strip wire tokens server-side so the client never sees <|tool_call> etc.
|
|
1205
1218
|
rendered = raw.map { |chunk| OutputFormatter.strip(chunk) }.reject(&:empty?)
|
|
1206
1219
|
json_response(200, { session_id: session.id, output: rendered })
|
|
1207
|
-
rescue ArgumentError => e
|
|
1208
|
-
error_response(404, "not_found", e.message)
|
|
1209
1220
|
end
|
|
1210
1221
|
|
|
1211
1222
|
def handle_turn(req, id)
|
|
@@ -1231,18 +1242,16 @@ module Samagotchi
|
|
|
1231
1242
|
result = SessionManager.deliver_turn(id, prompt: prompt.to_s, client_id: client_id, images: images,
|
|
1232
1243
|
delivery: delivery, state_dir: @state_dir, manager: @manager,
|
|
1233
1244
|
bridge: -> { live_bridge_client(id) })
|
|
1234
|
-
case result
|
|
1245
|
+
case result.status
|
|
1235
1246
|
when :accepted
|
|
1236
1247
|
record_history(body, prompt)
|
|
1237
|
-
json_response(202, result
|
|
1238
|
-
when :refused then json_response(result
|
|
1239
|
-
when :timeout then error_response(504, "worker_timeout", result
|
|
1248
|
+
json_response(202, result.ack)
|
|
1249
|
+
when :refused then json_response(result.code, result.ack)
|
|
1250
|
+
when :timeout then error_response(504, "worker_timeout", result.detail)
|
|
1240
1251
|
else error_response(500, "enqueue_failed", "could not write turn input")
|
|
1241
1252
|
end
|
|
1242
1253
|
rescue SessionManager::OwnedByTUI => e
|
|
1243
1254
|
error_response(409, "owned_by_tui", e.message)
|
|
1244
|
-
rescue ArgumentError => e
|
|
1245
|
-
error_response(404, "not_found", e.message)
|
|
1246
1255
|
end
|
|
1247
1256
|
|
|
1248
1257
|
# A turn's images as [{file:, name:}] (refs to uploads already in the
|
|
@@ -1323,8 +1332,7 @@ module Samagotchi
|
|
|
1323
1332
|
def handle_cancel(req, id)
|
|
1324
1333
|
@session_class.load(id, state_dir: default_state_dir)
|
|
1325
1334
|
|
|
1326
|
-
|
|
1327
|
-
parsed = body.nil? || body.strip.empty? ? nil : parse_json(body)
|
|
1335
|
+
parsed = parse_json(request_body(req))
|
|
1328
1336
|
reason = (parsed["reason"] if parsed.is_a?(Hash)).to_s.strip
|
|
1329
1337
|
reason = "user" if reason.empty?
|
|
1330
1338
|
|
|
@@ -1337,8 +1345,6 @@ module Samagotchi
|
|
|
1337
1345
|
when 409 then json_response(409, { error: "not_running", detail: "no active turn to cancel", session_id: id })
|
|
1338
1346
|
end
|
|
1339
1347
|
end
|
|
1340
|
-
rescue ArgumentError => e
|
|
1341
|
-
error_response(404, "not_found", e.message)
|
|
1342
1348
|
end
|
|
1343
1349
|
|
|
1344
1350
|
# A task id as TaskRuntime.generate_task_id makes it.
|
|
@@ -1361,8 +1367,6 @@ module Samagotchi
|
|
|
1361
1367
|
when 409 then json_response(409, { error: "not_running", status: body["status"], task_id: task_id, session_id: id })
|
|
1362
1368
|
end
|
|
1363
1369
|
end
|
|
1364
|
-
rescue ArgumentError => e
|
|
1365
|
-
error_response(404, "not_found", e.message)
|
|
1366
1370
|
end
|
|
1367
1371
|
|
|
1368
1372
|
# POST /api/sessions/:id/turn/:enqueued_id/dequeue {client_id?}: the ×
|
|
@@ -1374,8 +1378,7 @@ module Samagotchi
|
|
|
1374
1378
|
# 404 unknown_enqueued_id pass through; the rest as every relay.
|
|
1375
1379
|
def handle_dequeue_turn(req, id, enqueued_id)
|
|
1376
1380
|
@session_class.load(id, state_dir: default_state_dir)
|
|
1377
|
-
|
|
1378
|
-
parsed = body.nil? || body.strip.empty? ? nil : parse_json(body)
|
|
1381
|
+
parsed = parse_json(request_body(req))
|
|
1379
1382
|
client_id = (parsed["client_id"] if parsed.is_a?(Hash))
|
|
1380
1383
|
|
|
1381
1384
|
request = ->(client) { client.dequeue(enqueued_id: enqueued_id, client_id: client_id) }
|
|
@@ -1388,8 +1391,6 @@ module Samagotchi
|
|
|
1388
1391
|
json_response(404, { error: "unknown_enqueued_id", enqueued_id: enqueued_id, session_id: id }) if body["error"] == "unknown_enqueued_id"
|
|
1389
1392
|
end
|
|
1390
1393
|
end
|
|
1391
|
-
rescue ArgumentError => e
|
|
1392
|
-
error_response(404, "not_found", e.message)
|
|
1393
1394
|
end
|
|
1394
1395
|
|
|
1395
1396
|
def handle_stop(_req, id)
|
|
@@ -1400,8 +1401,6 @@ module Samagotchi
|
|
|
1400
1401
|
json_response(200, { status: "stopped", session_id: id })
|
|
1401
1402
|
rescue SessionManager::OwnedByTUI => e
|
|
1402
1403
|
error_response(409, "owned_by_tui", e.message)
|
|
1403
|
-
rescue ArgumentError => e
|
|
1404
|
-
error_response(404, "not_found", e.message)
|
|
1405
1404
|
end
|
|
1406
1405
|
|
|
1407
1406
|
# POST /api/sessions/:id/restart: hand the session to a new worker on
|
|
@@ -1420,8 +1419,6 @@ module Samagotchi
|
|
|
1420
1419
|
json_response(409, { error: error, reason: e.reason.to_s, detail: e.message, session_id: id })
|
|
1421
1420
|
rescue SessionManager::OwnedByTUI => e
|
|
1422
1421
|
error_response(409, "owned_by_tui", e.message)
|
|
1423
|
-
rescue ArgumentError => e
|
|
1424
|
-
error_response(404, "not_found", e.message)
|
|
1425
1422
|
end
|
|
1426
1423
|
|
|
1427
1424
|
# POST /api/sessions/:id/archive: hide it (and its delegates) from the
|
|
@@ -1434,11 +1431,9 @@ module Samagotchi
|
|
|
1434
1431
|
json_response(200, { status: "archived", session_id: result[:id], archived: result[:archived],
|
|
1435
1432
|
stopped: result[:stopped], discarded: result[:discarded] })
|
|
1436
1433
|
rescue SessionManager::OwnedByTUI
|
|
1437
|
-
|
|
1434
|
+
owned_by_repl(id)
|
|
1438
1435
|
rescue SessionManager::ArchiveRefused => e
|
|
1439
1436
|
error_response(409, e.reason == :scratch ? "scratch" : "busy", e.message)
|
|
1440
|
-
rescue ArgumentError => e
|
|
1441
|
-
error_response(404, "not_found", e.message)
|
|
1442
1437
|
end
|
|
1443
1438
|
|
|
1444
1439
|
# POST /api/sessions/:id/unarchive: back to the lists, delegates too.
|
|
@@ -1446,8 +1441,6 @@ module Samagotchi
|
|
|
1446
1441
|
result = @manager.unarchive_session(id, state_dir: @state_dir)
|
|
1447
1442
|
result[:unarchived].each { |sid| @hub&.touch(sid) }
|
|
1448
1443
|
json_response(200, { status: "unarchived", session_id: result[:id], unarchived: result[:unarchived] })
|
|
1449
|
-
rescue ArgumentError => e
|
|
1450
|
-
error_response(404, "not_found", e.message)
|
|
1451
1444
|
end
|
|
1452
1445
|
|
|
1453
1446
|
# DELETE /api/sessions/:id: the session for good. A live worker is
|
|
@@ -1458,11 +1451,9 @@ module Samagotchi
|
|
|
1458
1451
|
@hub&.touch(result[:id])
|
|
1459
1452
|
json_response(200, { status: "deleted", session_id: result[:id], stopped: result[:stopped] })
|
|
1460
1453
|
rescue SessionManager::OwnedByTUI
|
|
1461
|
-
|
|
1454
|
+
owned_by_repl(id)
|
|
1462
1455
|
rescue SessionManager::DeleteRefused => e
|
|
1463
1456
|
error_response(409, e.reason.to_s, "#{e.message}; try again in a moment")
|
|
1464
|
-
rescue ArgumentError => e
|
|
1465
|
-
error_response(404, "not_found", e.message)
|
|
1466
1457
|
end
|
|
1467
1458
|
|
|
1468
1459
|
# ── Attached context (ContextSources): the session bar's chips ───────
|
|
@@ -1478,8 +1469,6 @@ module Samagotchi
|
|
|
1478
1469
|
subs = own.subscriptions
|
|
1479
1470
|
rows = attached_context(session).map { |attached| context_row(attached, own, subs) }
|
|
1480
1471
|
json_response(200, { session_id: id, sources: rows, can_add_url: ContextProviders.any? })
|
|
1481
|
-
rescue ArgumentError => e
|
|
1482
|
-
error_response(404, "not_found", e.message)
|
|
1483
1472
|
end
|
|
1484
1473
|
|
|
1485
1474
|
# GET /api/sessions/:id/context/:name: one source with its text.
|
|
@@ -1488,8 +1477,6 @@ module Samagotchi
|
|
|
1488
1477
|
attached = find_context(session, name) or return error_response(404, "not_found", "no source #{name[0, 40]}")
|
|
1489
1478
|
own = ContextSources.session_location(id, state_dir: default_state_dir)
|
|
1490
1479
|
json_response(200, context_row(attached, own, own.subscriptions).merge(text: attached.snapshot.text))
|
|
1491
|
-
rescue ArgumentError => e
|
|
1492
|
-
error_response(404, "not_found", e.message)
|
|
1493
1480
|
end
|
|
1494
1481
|
|
|
1495
1482
|
# POST /api/sessions/:id/context {url, why}: the source an installed
|
|
@@ -1512,8 +1499,6 @@ module Samagotchi
|
|
|
1512
1499
|
error_response(422, "no_provider", e.message)
|
|
1513
1500
|
rescue ContextSources::Invalid => e
|
|
1514
1501
|
error_response(409, "exists", e.message)
|
|
1515
|
-
rescue ArgumentError => e
|
|
1516
|
-
error_response(404, "not_found", e.message)
|
|
1517
1502
|
end
|
|
1518
1503
|
|
|
1519
1504
|
def web_source(resolved, why, scope)
|
|
@@ -1535,8 +1520,6 @@ module Samagotchi
|
|
|
1535
1520
|
ContextSources.session_location(id, state_dir: default_state_dir).mute(attached.name)
|
|
1536
1521
|
json_response(200, { status: "muted", name: attached.name })
|
|
1537
1522
|
end
|
|
1538
|
-
rescue ArgumentError => e
|
|
1539
|
-
error_response(404, "not_found", e.message)
|
|
1540
1523
|
end
|
|
1541
1524
|
|
|
1542
1525
|
def attached_context(session)
|