samagotchi 0.102.0 → 0.103.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -1
  3. data/bin/chi +23 -191
  4. data/docs/architecture.md +54 -0
  5. data/docs/cli.md +114 -7
  6. data/docs/configuration.md +7 -1
  7. data/docs/guardrails.md +2 -1
  8. data/docs/internals/engine.md +1 -1
  9. data/docs/internals/llm-context-forget.md +11 -1
  10. data/docs/internals/remote-sessions.md +303 -0
  11. data/docs/internals/working-set.md +95 -11
  12. data/docs/plugins.md +26 -2
  13. data/docs/sessions.md +12 -5
  14. data/docs/sub-agent.md +8 -0
  15. data/lib/samagotchi/answer_command.rb +27 -25
  16. data/lib/samagotchi/bridge.rb +24 -4
  17. data/lib/samagotchi/bridge_client/event_stream.rb +81 -12
  18. data/lib/samagotchi/bridge_client/stream_request.rb +77 -0
  19. data/lib/samagotchi/bridge_client.rb +22 -38
  20. data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -2
  21. data/lib/samagotchi/bundles/claude-peer/scripts/pair +30 -1
  22. data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
  23. data/lib/samagotchi/bundles/codex-peer/scripts/pair +49 -4
  24. data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +7 -4
  25. data/lib/samagotchi/bundles/guardrails/manifest.yml +1 -1
  26. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  27. data/lib/samagotchi/chi_invocation.rb +43 -0
  28. data/lib/samagotchi/cli/command.rb +3 -2
  29. data/lib/samagotchi/cli/flags.rb +5 -0
  30. data/lib/samagotchi/cli/parent_wait.rb +22 -13
  31. data/lib/samagotchi/context_command.rb +80 -73
  32. data/lib/samagotchi/engine.rb +12 -8
  33. data/lib/samagotchi/idle_recap.rb +7 -1
  34. data/lib/samagotchi/kernel_loop.rb +13 -1
  35. data/lib/samagotchi/launch_mode.rb +22 -0
  36. data/lib/samagotchi/llm_context_forget.rb +13 -6
  37. data/lib/samagotchi/note_command.rb +8 -7
  38. data/lib/samagotchi/parent_report.rb +55 -43
  39. data/lib/samagotchi/remote_command.rb +170 -0
  40. data/lib/samagotchi/remotes.rb +130 -0
  41. data/lib/samagotchi/run_options.rb +233 -0
  42. data/lib/samagotchi/send_command/busy.rb +58 -0
  43. data/lib/samagotchi/send_command/parent_gate.rb +23 -10
  44. data/lib/samagotchi/send_command/wait_target.rb +16 -3
  45. data/lib/samagotchi/send_command.rb +173 -108
  46. data/lib/samagotchi/session.rb +30 -5
  47. data/lib/samagotchi/session_access/contract.rb +129 -0
  48. data/lib/samagotchi/session_access/dispatch.rb +133 -0
  49. data/lib/samagotchi/session_access/http_transport.rb +109 -0
  50. data/lib/samagotchi/session_access/local.rb +349 -0
  51. data/lib/samagotchi/session_access/remote.rb +638 -0
  52. data/lib/samagotchi/session_access/web_bridge_client.rb +181 -0
  53. data/lib/samagotchi/session_access.rb +141 -0
  54. data/lib/samagotchi/session_archive_command.rb +9 -5
  55. data/lib/samagotchi/session_commands.rb +20 -15
  56. data/lib/samagotchi/session_delete_command.rb +8 -4
  57. data/lib/samagotchi/session_manager.rb +7 -2
  58. data/lib/samagotchi/session_setup.rb +11 -6
  59. data/lib/samagotchi/sessions_command.rb +36 -14
  60. data/lib/samagotchi/terminal_ui/attach_launcher.rb +51 -15
  61. data/lib/samagotchi/terminal_ui/attach_link.rb +104 -0
  62. data/lib/samagotchi/terminal_ui/attached_loop.rb +80 -75
  63. data/lib/samagotchi/terminal_ui/local_link.rb +130 -0
  64. data/lib/samagotchi/terminal_ui/version_lines.rb +5 -3
  65. data/lib/samagotchi/terminal_ui.rb +2 -2
  66. data/lib/samagotchi/title_command.rb +9 -11
  67. data/lib/samagotchi/version.rb +1 -1
  68. data/lib/samagotchi/web/app.rb +360 -69
  69. data/lib/samagotchi/web/public/data.js +3 -2
  70. data/lib/samagotchi/web/public/index.html +20 -4
  71. data/lib/samagotchi/web/public/stage_frame.js +7 -2
  72. data/lib/samagotchi/web/public/stage_view.js +3 -2
  73. data/lib/samagotchi/web/public/stream_handlers.js +3 -1
  74. data/lib/samagotchi/web/public/working_set.js +159 -28
  75. data/lib/samagotchi/web/public/working_set_rail.js +39 -12
  76. data/lib/samagotchi/web/public/working_set_view.js +91 -33
  77. data/lib/samagotchi/web/session_access_routes.rb +343 -0
  78. data/lib/samagotchi/worker_sidecar.rb +12 -5
  79. data/lib/samagotchi/working_set.rb +88 -14
  80. data/lib/samagotchi/working_set_command.rb +133 -30
  81. data/lib/samagotchi/working_set_pin.rb +34 -0
  82. data/lib/samagotchi/working_set_steer.rb +191 -27
  83. metadata +19 -1
@@ -6,11 +6,14 @@ require_relative "llm_context_view"
6
6
  require_relative "llm_context_apply"
7
7
  require_relative "token_usage"
8
8
  require_relative "working_set"
9
+ require_relative "working_set_pin"
9
10
 
10
11
  module Samagotchi
11
12
  # The user steering the working set (/working-set, the web's popover):
12
13
  # drop an item (every content output of it still sent goes out of the
13
- # prompt as a stub) and undrop it. Pure, on a conversation the caller
14
+ # prompt as a stub) and undrop it; pin one (the model's forget_outputs
15
+ # refuses its outputs, LLMContextForget) and unpin it. Pure, on a
16
+ # conversation the caller
14
17
  # owns (Engine#clone_messages' copies: an op stores new edits Hashes,
15
18
  # LLMContextEdit.store) and runs between turns, so the next turn's first
16
19
  # request is the first with the change (prompt caching: one break where
@@ -24,6 +27,20 @@ module Samagotchi
24
27
  # puts it back. The outputs are WorkingSet.outputs_of's, the projection's
25
28
  # own pairing, so the set and the op never disagree.
26
29
  #
30
+ # A pin is the session's (Session#working_set_pins, a WorkingSetPin per
31
+ # item), not an edit: it sends nothing different, so it breaks no cache
32
+ # and survives a strategy switch. Pin and drop exclude each other: a
33
+ # drop asks for an unpin first, and a pin takes a pending drop off (one
34
+ # never sent: free), else asks for an undrop first.
35
+ #
36
+ # An add is a request: the turn-start note asks the model to read the
37
+ # item (read, memory_read, web_fetch) before the task, so its output is
38
+ # a real tool run (stamps, ids, forget, the projection), on both loops.
39
+ # Nothing sent changes until then; it costs the model one step. An item
40
+ # the model has whole already is refused; a stale or forgotten one is
41
+ # its refresh. Pending until the next turn sends the note
42
+ # (WorkingSet.pending_adds); an unadd takes it back.
43
+ #
27
44
  # Every op since the last turn is listed in one tail system message
28
45
  # (kind "working_set", #record!), so the model learns what changed and
29
46
  # doesn't read a dropped file again by reflex. An inverse op before the
@@ -31,14 +48,18 @@ module Samagotchi
31
48
  # nothing noted).
32
49
  module WorkingSetSteer
33
50
  BY = "user"
34
- NOTE_KIND = "working_set"
51
+ NOTE_KIND = WorkingSet::NOTE_KIND
35
52
  # The tail note's ops: [{"op", "kind", "key", "label"}], until the turn
36
53
  # that sends it (the chat loop's kept entries leave the key out).
37
- OPS = :working_set_ops
54
+ OPS = WorkingSet::NOTE_OPS
38
55
  NOTE_OPEN = "[WORKING SET: "
39
56
  DROP = "drop"
40
57
  UNDROP = "undrop"
41
- INVERSE = { DROP => UNDROP, UNDROP => DROP }.freeze
58
+ PIN = "pin"
59
+ UNPIN = "unpin"
60
+ ADD = WorkingSet::ADD
61
+ UNADD = "unadd"
62
+ INVERSE = { DROP => UNDROP, UNDROP => DROP, PIN => UNPIN, UNPIN => PIN, ADD => UNADD, UNADD => ADD }.freeze
42
63
  MEMORY_PREFIX = "memory:"
43
64
  URL = %r{\A[a-z][a-z0-9+.-]*://}i
44
65
 
@@ -47,12 +68,25 @@ module Samagotchi
47
68
  class Refused < StandardError
48
69
  MESSAGES = {
49
70
  dropped: "dropped by the user",
71
+ pinned_by_user: "pinned by the user: it stays in your context",
72
+ unpin_first: "pinned by the user: unpin it first",
73
+ undrop_first: "dropped by the user: undrop it first",
74
+ already_pinned: "already pinned",
75
+ not_pinned: "not pinned",
50
76
  not_in_set: "not in the working set",
51
77
  only_edited: "nothing to drop: only edited or written (no read of it is sent)",
52
78
  derived: "nothing to drop: an older session's output that chi can't name",
53
79
  already_dropped: "already dropped",
54
80
  not_sent: "nothing to drop: none of it is sent now (forgotten or stubbed)",
55
- not_dropped: "not dropped"
81
+ not_dropped: "not dropped",
82
+ no_file: "no such file",
83
+ directory: "a directory: add its files",
84
+ no_memory: "no such memory",
85
+ bad_memory: "not a memory name (no /, \\ or ..)",
86
+ not_http: "not an http(s) URL: web_fetch can't fetch it",
87
+ already_sent: "already in the model's context (sent whole): nothing to add",
88
+ already_added: "already added: the next turn reads it",
89
+ not_added: "not added (no add of it is pending)"
56
90
  }.freeze
57
91
 
58
92
  # @return [Symbol] a key of MESSAGES
@@ -73,11 +107,44 @@ module Samagotchi
73
107
 
74
108
  # What an op did: the ids it changed, and the chars it frees (a drop)
75
109
  # and the tail the server reads again from its earliest entry on.
76
- # +free+: an undrop that took back a drop never sent (nothing changes).
77
- Done = Data.define(:target, :op, :ids, :freed_chars, :tail_chars, :free)
110
+ # +free+: an undrop that took back a drop never sent (nothing changes),
111
+ # a pin or an unpin (nothing sent changes). +undone+: the pending drop
112
+ # a pin took off (a Done), else nil. +shared_with+: the other memories a
113
+ # memory_read of several names holds in an output the op acted on (the
114
+ # op acts on that output whole, so on them too).
115
+ Done = Data.define(:target, :op, :ids, :freed_chars, :tail_chars, :free, :undone, :shared_with) do
116
+ def initialize(undone: nil, shared_with: [], **fields) = super
117
+ end
78
118
 
79
119
  module_function
80
120
 
121
+ # A word quoted ("…" or '…'): runs to its closing quote, spaces and
122
+ # all; a backslash escapes the quote or itself. Else a bare word.
123
+ WORD = /\G\s*(?:"((?:[^"\\]|\\.)*)"?|'((?:[^'\\]|\\.)*)'?|(\S+))/m
124
+ # What a quoted word unescapes (\" \' \\); any other backslash stays.
125
+ ESCAPE = /\\(["'\\])/
126
+ # A word that needs quoting on a line: whitespace, or a leading quote.
127
+ QUOTE = /\s|\A["']/
128
+
129
+ # The KEY words of a /working-set line (what follows the verb):
130
+ # whitespace-separated, a quoted word keeps its spaces ("my dir/a.rb").
131
+ # An unclosed quote runs to the end. The web's working_set.js
132
+ # (steerWords) reads lines the same way.
133
+ # @return [Array<String>]
134
+ def words(text)
135
+ text.to_s.scan(WORD).filter_map do |double, single, bare|
136
+ quoted = double || single
137
+ quoted ? quoted.gsub(ESCAPE, '\\1') : bare
138
+ end.reject(&:empty?)
139
+ end
140
+
141
+ # +word+ as a line names it: quoted when it has whitespace or starts
142
+ # with a quote (#words reads it back), else as it is.
143
+ def quote(word)
144
+ word = word.to_s
145
+ word.match?(QUOTE) ? %("#{word.gsub(/["\\]/) { |char| "\\#{char}" }}") : word
146
+ end
147
+
81
148
  # +word+ (a path, absolute or the session's folder's; memory:NAME; a
82
149
  # URL) as a Target.
83
150
  def target(word, cwd:)
@@ -103,9 +170,12 @@ module Samagotchi
103
170
  # Drops +target+'s content outputs that are still sent under
104
171
  # +strategy+ (the next turn's: LLMContextView's form), saving an applied
105
172
  # :drop on each.
173
+ # +pins+: the session's (a pinned item asks for an unpin first).
106
174
  # @return [Done]
107
175
  # @raise [Refused]
108
- def drop(conversation, target, strategy:, cwd:, now: Time.now.utc.iso8601(3))
176
+ def drop(conversation, target, strategy:, cwd:, pins: [], now: Time.now.utc.iso8601(3))
177
+ raise Refused, :unpin_first if pinned?(pins, target)
178
+
109
179
  outputs = WorkingSet.outputs_of(conversation, kind: target.kind, key: target.key, strategy: strategy, cwd: cwd)
110
180
  raise Refused, (known?(conversation, target, strategy, cwd) ? :only_edited : :not_in_set) if outputs.empty?
111
181
 
@@ -124,7 +194,7 @@ module Samagotchi
124
194
  freed, tail = LLMContextApply.weigh(conversation, layers: layers(strategy), edits: edits)
125
195
  edits.each { |index, edit| LLMContextEdit.store(conversation[index], edit) }
126
196
  Done.new(target: target, op: DROP, ids: edits.map { |_, edit| edit.id }, freed_chars: freed, tail_chars: tail,
127
- free: false)
197
+ free: false, shared_with: shared_with(sent))
128
198
  end
129
199
 
130
200
  # Takes +target+'s drops off, putting back what each output held before
@@ -149,7 +219,85 @@ module Samagotchi
149
219
  view = LLMContextView.new(strategy: strategy)
150
220
  tail = sent ? LLMContextView.chars(view.messages(conversation)[first..]) : 0
151
221
  Done.new(target: target, op: UNDROP, ids: dropped.map { |output, _| output.id }, freed_chars: 0, tail_chars: tail,
152
- free: !sent)
222
+ free: !sent, shared_with: shared_with(dropped.map(&:first)))
223
+ end
224
+
225
+ # Pins +target+ (an item the set holds; an edit-only one too: its later
226
+ # reads are covered). A drop of it still pending (made since the last
227
+ # turn) is taken off first, free, as Done#undone; a sent one asks for
228
+ # an undrop first. The caller adds the pin (#pinning).
229
+ # @param pins [Array<WorkingSetPin>] the session's
230
+ # @return [Done] +ids+: the outputs the pin holds now
231
+ # @raise [Refused]
232
+ def pin(conversation, pins, target, strategy:, cwd:)
233
+ outputs = WorkingSet.outputs_of(conversation, kind: target.kind, key: target.key, strategy: strategy, cwd: cwd)
234
+ raise Refused, :not_in_set if outputs.empty? && !known?(conversation, target, strategy, cwd)
235
+ raise Refused, :already_pinned if pinned?(pins, target)
236
+
237
+ undone = nil
238
+ if outputs.any? { |output| drop_of(conversation, output) }
239
+ raise Refused, :undrop_first unless pending?(conversation, DROP, target)
240
+
241
+ undone = undrop(conversation, target, strategy: strategy, cwd: cwd, sent: false)
242
+ end
243
+ named = outputs.reject(&:derived)
244
+ Done.new(target: target, op: PIN, ids: named.map(&:id), freed_chars: 0, tail_chars: 0, free: true, undone: undone,
245
+ shared_with: shared_with(named))
246
+ end
247
+
248
+ # Unpins +target+: the model may forget it again. Only the pins are
249
+ # asked (a rollback may have taken the item out of the set).
250
+ # @return [Done]
251
+ # @raise [Refused]
252
+ def unpin(pins, target)
253
+ raise Refused, :not_pinned unless pinned?(pins, target)
254
+
255
+ Done.new(target: target, op: UNPIN, ids: [], freed_chars: 0, tail_chars: 0, free: true)
256
+ end
257
+
258
+ # Adds +target+: the next turn's note asks the model to read it. +found+
259
+ # answers whether the item exists (a file, a memory; the caller's
260
+ # look). Refused for an item the model is sent whole now (live, a read
261
+ # of it sent), a dropped one (undrop it), and one added already.
262
+ # @param found [#call] target → nil when it exists, else a Refused reason
263
+ # @return [Done]
264
+ # @raise [Refused]
265
+ def add(conversation, target, strategy:, cwd:, found: ->(_) {})
266
+ raise Refused, :already_added if pending?(conversation, ADD, target)
267
+
268
+ reason = target.kind == WorkingSet::URL && !target.key.match?(%r{\Ahttps?://}i) ? :not_http : found.call(target)
269
+ raise Refused, reason if reason
270
+
271
+ outputs = WorkingSet.outputs_of(conversation, kind: target.kind, key: target.key, strategy: strategy, cwd: cwd)
272
+ raise Refused, :undrop_first if outputs.any? { |output| drop_of(conversation, output) }
273
+ raise Refused, :already_sent if outputs.any?(&:sent) && live?(conversation, target, strategy, cwd)
274
+
275
+ Done.new(target: target, op: ADD, ids: [], freed_chars: 0, tail_chars: 0, free: true)
276
+ end
277
+
278
+ # Takes back +target+'s pending add (never sent: free).
279
+ # @return [Done]
280
+ # @raise [Refused]
281
+ def unadd(conversation, target)
282
+ raise Refused, :not_added unless pending?(conversation, ADD, target)
283
+
284
+ Done.new(target: target, op: UNADD, ids: [], freed_chars: 0, tail_chars: 0, free: true)
285
+ end
286
+
287
+ # +pins+ after +done+ (a pin or an unpin). A new frozen Array.
288
+ # @return [Array<WorkingSetPin>]
289
+ def pinning(pins, done)
290
+ pin = WorkingSetPin.new(kind: done.target.kind, key: done.target.key)
291
+ rest = Array(pins).reject { |held| held == pin }
292
+ (done.op == PIN ? rest + [pin] : rest).freeze
293
+ end
294
+
295
+ # The other memory names +outputs+ hold (WorkingSet::Output#shared_with).
296
+ def shared_with(outputs) = outputs.flat_map(&:shared_with).uniq
297
+
298
+ # Whether +pins+ hold +target+.
299
+ def pinned?(pins, target)
300
+ Array(pins).any? { |pin| pin.kind == target.kind && pin.key == target.key }
153
301
  end
154
302
 
155
303
  # +output+'s drop (an LLMContextEdit), or nil.
@@ -165,26 +313,23 @@ module Samagotchi
165
313
  end
166
314
  end
167
315
 
316
+ # Whether the set holds +target+ live (not stale, forgotten, dropped).
317
+ def live?(conversation, target, strategy, cwd)
318
+ WorkingSet.build(conversation, cwd: cwd, strategy: strategy).items.any? do |item|
319
+ item.kind == target.kind && item.key == target.key && item.state == WorkingSet::LIVE
320
+ end
321
+ end
322
+
168
323
  def layers(strategy) = LLMContextView.new(strategy: strategy).layers
169
324
 
170
325
  # ── the tail note ───────────────────────────────────────────────────
171
326
 
172
- # The index of the note the next turn will send (one after the last
173
- # turn's entries: only system messages follow it), or nil.
174
- def pending_note_index(messages)
175
- (messages.size - 1).downto(0) do |index|
176
- message = messages[index]
177
- return nil unless message[:role].to_s == "system"
178
- return index if message[:kind].to_s == NOTE_KIND
179
- end
180
- nil
181
- end
327
+ # The index of the note the next turn will send, or nil
328
+ # (WorkingSet.pending_note_index).
329
+ def pending_note_index(messages) = WorkingSet.pending_note_index(messages)
182
330
 
183
331
  # The ops the pending note lists ([{"op", "kind", "key", "label"}]).
184
- def pending_ops(messages)
185
- index = pending_note_index(messages)
186
- index ? Array(messages[index][OPS]).select { |op| op.is_a?(Hash) } : []
187
- end
332
+ def pending_ops(messages) = WorkingSet.pending_ops(messages)
188
333
 
189
334
  # Whether +target+'s +op+ is pending (made since the last turn).
190
335
  def pending?(messages, op, target)
@@ -224,8 +369,8 @@ module Samagotchi
224
369
  # The note listing +ops+: a tail system message, escaped like a context
225
370
  # note (Prompt::ESCAPED_KINDS: it carries paths and URLs).
226
371
  def note(ops)
227
- drops = ops.select { |entry| entry["op"] == DROP }.map { |entry| entry["label"] }
228
- undrops = ops.select { |entry| entry["op"] == UNDROP }.map { |entry| entry["label"] }
372
+ drops = labels(ops, DROP)
373
+ undrops = labels(ops, UNDROP)
229
374
  parts = []
230
375
  unless drops.empty?
231
376
  parts << "the user dropped #{drops.join(", ")} from your context: you are sent a stub instead of " \
@@ -236,15 +381,34 @@ module Samagotchi
236
381
  parts << "the user brought back #{undrops.join(", ")}: #{undrops.size == 1 ? "its outputs are" : "their outputs are"} " \
237
382
  "sent whole again"
238
383
  end
384
+ pins = labels(ops, PIN)
385
+ parts << "the user pinned #{pins.join(", ")}: forget_outputs won't forget #{pins.size == 1 ? "it" : "them"}" unless pins.empty?
386
+ unpins = labels(ops, UNPIN)
387
+ parts << "the user unpinned #{unpins.join(", ")}: you may forget #{unpins.size == 1 ? "it" : "them"} again" unless unpins.empty?
388
+ adds = ops.select { |entry| entry["op"] == ADD }
389
+ unless adds.empty?
390
+ parts << "the user added #{adds.map { |entry| entry["label"] }.join(", ")} to your context: before anything " \
391
+ "else, load #{adds.size == 1 ? "it" : "them"} with #{adds.map { |entry| call_of(entry) }.join(", ")}"
392
+ end
239
393
  { role: "system", kind: NOTE_KIND, content: "#{NOTE_OPEN}since your last turn #{parts.join("; ")}.]", OPS => ops }
240
394
  end
241
395
 
396
+ def labels(ops, op) = ops.select { |entry| entry["op"] == op }.map { |entry| entry["label"] }
397
+
398
+ # The tool call that loads an added item, as the note asks for it: a
399
+ # file by its absolute path (a read resolves a relative one against
400
+ # the process's folder, which may not be the session's).
401
+ def call_of(entry)
402
+ tool = { WorkingSet::MEMORY => "memory_read", WorkingSet::URL => "web_fetch" }.fetch(entry["kind"], "read")
403
+ %(#{tool} "#{entry["key"]}")
404
+ end
405
+
242
406
  # "12.4k tokens", chars/4.
243
407
  def k(chars)
244
408
  tokens = (chars / TokenUsage::CHARS_PER_TOKEN).ceil
245
409
  tokens >= 1000 ? format("%.1fk tokens", tokens / 1000.0) : "#{tokens} tokens"
246
410
  end
247
411
 
248
- private_class_method :relative, :drop_of, :known?, :layers, :same?
412
+ private_class_method :relative, :shared_with, :drop_of, :known?, :live?, :layers, :same?, :labels, :call_of
249
413
  end
250
414
  end
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.102.0
4
+ version: 0.103.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov
@@ -161,6 +161,7 @@ files:
161
161
  - docs/internals/llm-context-forget.md
162
162
  - docs/internals/llm-context-live.md
163
163
  - docs/internals/prompt-caching.md
164
+ - docs/internals/remote-sessions.md
164
165
  - docs/internals/tool-guardrails.md
165
166
  - docs/internals/working-set.md
166
167
  - docs/memory.md
@@ -194,6 +195,7 @@ files:
194
195
  - lib/samagotchi/bridge_client.rb
195
196
  - lib/samagotchi/bridge_client/event_stream.rb
196
197
  - lib/samagotchi/bridge_client/sse_parser.rb
198
+ - lib/samagotchi/bridge_client/stream_request.rb
197
199
  - lib/samagotchi/broadcast/recipients.rb
198
200
  - lib/samagotchi/broadcast/scope_card.rb
199
201
  - lib/samagotchi/broadcast/tags.rb
@@ -270,6 +272,7 @@ files:
270
272
  - lib/samagotchi/bundles/work-cap/plugin.rb
271
273
  - lib/samagotchi/cache_confirm.rb
272
274
  - lib/samagotchi/cancellation_controller.rb
275
+ - lib/samagotchi/chi_invocation.rb
273
276
  - lib/samagotchi/child_env.rb
274
277
  - lib/samagotchi/child_move.rb
275
278
  - lib/samagotchi/child_reports.rb
@@ -503,17 +506,28 @@ files:
503
506
  - lib/samagotchi/relay_watcher.rb
504
507
  - lib/samagotchi/reminder_queue.rb
505
508
  - lib/samagotchi/reminder_store.rb
509
+ - lib/samagotchi/remote_command.rb
510
+ - lib/samagotchi/remotes.rb
506
511
  - lib/samagotchi/reply_wait.rb
507
512
  - lib/samagotchi/reply_wait/poll.rb
508
513
  - lib/samagotchi/row_refs.rb
514
+ - lib/samagotchi/run_options.rb
509
515
  - lib/samagotchi/sampling_settings.rb
510
516
  - lib/samagotchi/sandbox_layout.rb
511
517
  - lib/samagotchi/self_report.rb
512
518
  - lib/samagotchi/send_command.rb
519
+ - lib/samagotchi/send_command/busy.rb
513
520
  - lib/samagotchi/send_command/parent_gate.rb
514
521
  - lib/samagotchi/send_command/wait_target.rb
515
522
  - lib/samagotchi/served_model.rb
516
523
  - lib/samagotchi/session.rb
524
+ - lib/samagotchi/session_access.rb
525
+ - lib/samagotchi/session_access/contract.rb
526
+ - lib/samagotchi/session_access/dispatch.rb
527
+ - lib/samagotchi/session_access/http_transport.rb
528
+ - lib/samagotchi/session_access/local.rb
529
+ - lib/samagotchi/session_access/remote.rb
530
+ - lib/samagotchi/session_access/web_bridge_client.rb
517
531
  - lib/samagotchi/session_archive_command.rb
518
532
  - lib/samagotchi/session_chain.rb
519
533
  - lib/samagotchi/session_commands.rb
@@ -544,6 +558,7 @@ files:
544
558
  - lib/samagotchi/task_watch.rb
545
559
  - lib/samagotchi/terminal_ui.rb
546
560
  - lib/samagotchi/terminal_ui/attach_launcher.rb
561
+ - lib/samagotchi/terminal_ui/attach_link.rb
547
562
  - lib/samagotchi/terminal_ui/attached_loop.rb
548
563
  - lib/samagotchi/terminal_ui/attached_view.rb
549
564
  - lib/samagotchi/terminal_ui/between_turns.rb
@@ -553,6 +568,7 @@ files:
553
568
  - lib/samagotchi/terminal_ui/input_support.rb
554
569
  - lib/samagotchi/terminal_ui/line_reader.rb
555
570
  - lib/samagotchi/terminal_ui/live_region.rb
571
+ - lib/samagotchi/terminal_ui/local_link.rb
556
572
  - lib/samagotchi/terminal_ui/model_segments_text.rb
557
573
  - lib/samagotchi/terminal_ui/one_shot_sink.rb
558
574
  - lib/samagotchi/terminal_ui/plain_surface.rb
@@ -767,6 +783,7 @@ files:
767
783
  - lib/samagotchi/web/public/working_set_view.js
768
784
  - lib/samagotchi/web/qr.rb
769
785
  - lib/samagotchi/web/server.rb
786
+ - lib/samagotchi/web/session_access_routes.rb
770
787
  - lib/samagotchi/web/session_hub.rb
771
788
  - lib/samagotchi/web/session_summary.rb
772
789
  - lib/samagotchi/web/token.rb
@@ -779,6 +796,7 @@ files:
779
796
  - lib/samagotchi/worker_wakes.rb
780
797
  - lib/samagotchi/working_set.rb
781
798
  - lib/samagotchi/working_set_command.rb
799
+ - lib/samagotchi/working_set_pin.rb
782
800
  - lib/samagotchi/working_set_steer.rb
783
801
  - lib/samagotchi/yaml_lines.rb
784
802
  homepage: https://github.com/dm1try/samagotchi