samagotchi 0.91.0 → 0.93.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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -1
  3. data/README.md +5 -2
  4. data/bin/chi +8 -8
  5. data/docs/architecture.md +20 -16
  6. data/docs/bundle-authoring.md +2 -1
  7. data/docs/cli.md +5 -5
  8. data/docs/configuration.md +3 -1
  9. data/docs/context.md +5 -3
  10. data/docs/guardrails.md +4 -5
  11. data/docs/hooks.md +3 -3
  12. data/docs/internals/background-tasks.md +2 -2
  13. data/docs/internals/bundles.md +4 -4
  14. data/docs/internals/engine.md +27 -8
  15. data/docs/internals/tool-guardrails.md +10 -2
  16. data/docs/memory.md +6 -0
  17. data/docs/plugins.md +30 -13
  18. data/docs/releasing.md +3 -0
  19. data/docs/sessions.md +4 -4
  20. data/docs/testing.md +2 -1
  21. data/lib/samagotchi/bootstrap/probe.rb +2 -2
  22. data/lib/samagotchi/bridge.rb +8 -9
  23. data/lib/samagotchi/bundle_command/check.rb +43 -12
  24. data/lib/samagotchi/bundle_command/scaffold.rb +12 -0
  25. data/lib/samagotchi/bundle_command/test_run.rb +12 -0
  26. data/lib/samagotchi/bundle_command/try/command.rb +164 -0
  27. data/lib/samagotchi/bundle_command/try/render.rb +80 -0
  28. data/lib/samagotchi/bundle_command/try/run.rb +35 -17
  29. data/lib/samagotchi/bundle_command/try/sandbox.rb +113 -34
  30. data/lib/samagotchi/bundle_command/try/trace.rb +20 -26
  31. data/lib/samagotchi/bundle_command.rb +11 -213
  32. data/lib/samagotchi/bundle_session_settings.rb +82 -45
  33. data/lib/samagotchi/bundles/core/manifest.yml +2 -2
  34. data/lib/samagotchi/bundles/cost-cap/manifest.yml +6 -5
  35. data/lib/samagotchi/bundles/cost-cap/plugin.rb +57 -12
  36. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  37. data/lib/samagotchi/child_env.rb +50 -0
  38. data/lib/samagotchi/child_reports.rb +12 -14
  39. data/lib/samagotchi/client_id.rb +18 -0
  40. data/lib/samagotchi/config/restart_watch.rb +112 -0
  41. data/lib/samagotchi/config.rb +18 -59
  42. data/lib/samagotchi/engine.rb +26 -26
  43. data/lib/samagotchi/guardrails/data_readers.rb +34 -30
  44. data/lib/samagotchi/guardrails/parent_levels.rb +31 -0
  45. data/lib/samagotchi/guardrails/parent_policy.rb +9 -20
  46. data/lib/samagotchi/guardrails/shell_lex.rb +28 -7
  47. data/lib/samagotchi/hook_question.rb +21 -0
  48. data/lib/samagotchi/hook_routes.rb +6 -6
  49. data/lib/samagotchi/hooks/registry.rb +7 -5
  50. data/lib/samagotchi/host_registry.rb +1 -1
  51. data/lib/samagotchi/kernel_loop.rb +14 -3
  52. data/lib/samagotchi/llm/anthropic_messages.rb +5 -0
  53. data/lib/samagotchi/llm/chat_loop.rb +3 -1
  54. data/lib/samagotchi/llm/errors.rb +4 -4
  55. data/lib/samagotchi/memory_bundle/builder.rb +3 -2
  56. data/lib/samagotchi/memory_bundle/installed_bundle.rb +7 -2
  57. data/lib/samagotchi/memory_bundle/installer.rb +1 -0
  58. data/lib/samagotchi/memory_bundle/manifest.rb +42 -8
  59. data/lib/samagotchi/memory_bundle/provenance.rb +2 -1
  60. data/lib/samagotchi/parent_answers_command.rb +6 -6
  61. data/lib/samagotchi/parent_report.rb +12 -0
  62. data/lib/samagotchi/plugin/context.rb +6 -5
  63. data/lib/samagotchi/plugin/http_transport.rb +14 -3
  64. data/lib/samagotchi/plugin/sessions.rb +2 -3
  65. data/lib/samagotchi/question_desk.rb +12 -9
  66. data/lib/samagotchi/reply_wait/poll.rb +267 -0
  67. data/lib/samagotchi/reply_wait.rb +51 -190
  68. data/lib/samagotchi/sandbox_layout.rb +66 -0
  69. data/lib/samagotchi/send_command/parent_gate.rb +83 -0
  70. data/lib/samagotchi/send_command/wait_target.rb +55 -0
  71. data/lib/samagotchi/send_command.rb +34 -103
  72. data/lib/samagotchi/session.rb +10 -9
  73. data/lib/samagotchi/session_inbox.rb +31 -8
  74. data/lib/samagotchi/session_manager.rb +60 -35
  75. data/lib/samagotchi/session_metrics.rb +22 -3
  76. data/lib/samagotchi/sessions_command.rb +4 -4
  77. data/lib/samagotchi/task_watch.rb +64 -21
  78. data/lib/samagotchi/terminal_ui/attached_loop.rb +3 -3
  79. data/lib/samagotchi/terminal_ui/formatting.rb +8 -5
  80. data/lib/samagotchi/testing/bundle.rb +33 -20
  81. data/lib/samagotchi/testing/host.rb +2 -8
  82. data/lib/samagotchi/testing/install.rb +26 -17
  83. data/lib/samagotchi/testing/recorder.rb +6 -9
  84. data/lib/samagotchi/testing/sandbox.rb +45 -37
  85. data/lib/samagotchi/testing/waiting.rb +5 -7
  86. data/lib/samagotchi/tool_response.rb +28 -6
  87. data/lib/samagotchi/tools/delegate.rb +10 -10
  88. data/lib/samagotchi/tools/delegate_cursor.rb +32 -8
  89. data/lib/samagotchi/tools/delegate_relay.rb +31 -16
  90. data/lib/samagotchi/tools/delegate_wait.rb +4 -4
  91. data/lib/samagotchi/tools/task_runtime.rb +2 -2
  92. data/lib/samagotchi/turn_checkpoint.rb +23 -10
  93. data/lib/samagotchi/version.rb +1 -1
  94. data/lib/samagotchi/web/app.rb +46 -63
  95. data/lib/samagotchi/web/public/app.js +130 -554
  96. data/lib/samagotchi/web/public/cards_place.js +4 -1
  97. data/lib/samagotchi/web/public/images.js +6 -0
  98. data/lib/samagotchi/web/public/index.html +1 -1
  99. data/lib/samagotchi/web/public/model_pick.js +10 -0
  100. data/lib/samagotchi/web/public/send_turn.js +173 -0
  101. data/lib/samagotchi/web/public/session_actions.js +203 -0
  102. data/lib/samagotchi/web/public/start_page.js +236 -0
  103. data/lib/samagotchi/web/public/toasts.js +17 -9
  104. data/lib/samagotchi/web/public/unqueue.js +20 -4
  105. data/lib/samagotchi/worker.rb +77 -185
  106. data/lib/samagotchi/worker_idle_exit.rb +20 -5
  107. data/lib/samagotchi/worker_inbound.rb +5 -5
  108. data/lib/samagotchi/worker_task_wakes.rb +160 -0
  109. data/lib/samagotchi/worker_wakes.rb +0 -3
  110. metadata +15 -1
@@ -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, error_response(409, "owned_by_tui", "session #{given} is open in a chi REPL; close it there first")]
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[:status]
1245
+ case result.status
1235
1246
  when :accepted
1236
1247
  record_history(body, prompt)
1237
- json_response(202, result[:ack])
1238
- when :refused then json_response(result[:code], result[:ack])
1239
- when :timeout then error_response(504, "worker_timeout", result[:ack]["detail"])
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
- body = request_body(req)
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
- body = request_body(req)
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
- error_response(409, "owned_by_tui", "session #{id} is open in a chi REPL; close it there first")
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
- error_response(409, "owned_by_tui", "session #{id} is open in a chi REPL; close it there first")
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)