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
@@ -15,6 +15,7 @@ require_relative "terminal_ui/formatting"
15
15
  require_relative "terminal_ui/event_renderer"
16
16
  require_relative "cli/command"
17
17
  require_relative "cli/flags"
18
+ require_relative "session_access/local"
18
19
 
19
20
  module Samagotchi
20
21
  # `chi sessions`: list, stop, restart, archive/unarchive, delete, prune,
@@ -81,10 +82,15 @@ module Samagotchi
81
82
  INTEGER_OPTIONS = %i[days keep limit].freeze
82
83
 
83
84
  # @param argv [Array<String>] the arguments after "sessions"
84
- def initialize(argv, stdout: $stdout, stderr: $stderr)
85
+ # @param access [SessionAccess::Local, nil] where the sessions are;
86
+ # default this machine's (delete, archive and unarchive build their
87
+ # own unless one is given)
88
+ def initialize(argv, stdout: $stdout, stderr: $stderr, access: nil)
85
89
  @argv = argv.dup
86
90
  @stdout = stdout
87
91
  @stderr = stderr
92
+ @given_access = access
93
+ @access = access || Samagotchi::SessionAccess::Local.new
88
94
  end
89
95
 
90
96
  # @return [Integer] exit status
@@ -99,9 +105,10 @@ module Samagotchi
99
105
 
100
106
  case @sub
101
107
  when "delete"
102
- return Samagotchi::SessionDeleteCommand.new(@argv[1..], stdout: @stdout, stderr: @stderr).run
108
+ return Samagotchi::SessionDeleteCommand.new(@argv[1..], stdout: @stdout, stderr: @stderr, access: @given_access).run
103
109
  when "archive", "unarchive"
104
- return Samagotchi::SessionArchiveCommand.new(@sub, @argv[1..], stdout: @stdout, stderr: @stderr).run
110
+ return Samagotchi::SessionArchiveCommand.new(@sub, @argv[1..], stdout: @stdout, stderr: @stderr,
111
+ access: @given_access).run
105
112
  end
106
113
 
107
114
  parsed = parse_flags(FLAGS.fetch(@sub), @argv[1..])
@@ -166,7 +173,16 @@ module Samagotchi
166
173
 
167
174
  # The current git project's sessions (Samagotchi::ProjectScope), like
168
175
  # chi web's; --scope=all, a folder in no repo or an explicit --cwd: every
169
- # project's.
176
+ # project's. Machine-local (this cwd's project): remotely every
177
+ # project's (D4), and --scope=project is --cwd's project there (the
178
+ # server finds its root). The rows are the server's (list_summaries).
179
+ if @access.remote?
180
+ return usage_error("--scope=project on a remote needs --cwd: a folder there") if scope == "project" && !cwd
181
+
182
+ project = scope == "project" ? cwd : nil
183
+ scope_note = ->(count) { "#{count} session(s) in #{File.basename(project)} on #{@access.name} (--scope=all: every project)" }
184
+ return list_summaries(project, scope_note)
185
+ end
170
186
  project = scope == "all" || cwd ? nil : Samagotchi::ProjectScope.root_for(Dir.pwd)
171
187
  scope_note = ->(count) { "#{count} session(s) in #{File.basename(project)} (--scope=all: every project)" }
172
188
 
@@ -181,7 +197,7 @@ module Samagotchi
181
197
  def list_summaries(project, scope_note)
182
198
  live, cwd, limit, include_archived, format, sort, order =
183
199
  @opts.values_at(:live, :cwd, :limit, :include_archived, :format, :sort, :order)
184
- summaries = Samagotchi::SessionManager.session_summaries(
200
+ summaries = @access.summaries(
185
201
  live: live, cwd: cwd, limit: limit || (live ? 10 : nil), include_tests: Samagotchi::Session.test_session_env?,
186
202
  project_root: project,
187
203
  include_archived: include_archived, sort: sort, order: order
@@ -245,10 +261,12 @@ module Samagotchi
245
261
  sort, order, limit, include_archived = @opts.values_at(:sort, :order, :limit, :include_archived)
246
262
  sort ||= "updated_at"
247
263
  order ||= "desc"
248
- sessions = Samagotchi::SessionManager.list_sessions(sort: sort, order: order, limit: limit, project_root: project,
249
- include_archived: include_archived)
264
+ sessions = @access.list(sort: sort, order: order, limit: limit, project_root: project, include_archived: include_archived)
250
265
  # The session's title, else the saved recap's first sentence, else the
251
266
  # last prompt (or the first message, before any turn), cut as before.
267
+ # These rows read each session's folder here (its recap, saved
268
+ # context, owner): machine-local, the server's rows remotely (P2's
269
+ # list parity).
252
270
  state_dir = Samagotchi::Session.default_state_dir
253
271
  list_text = lambda do |s|
254
272
  recap = s.title ? nil : Samagotchi::RecapStore.preview(Samagotchi::Session.session_dir(s.id, state_dir: state_dir))
@@ -297,7 +315,8 @@ module Samagotchi
297
315
  # One session's cost, tokens and progress, for a script or an agent that
298
316
  # drives sessions (chi send --wait): a live worker's GET stats, else the
299
317
  # snapshot its saved analytics.json rebuilds. Never starts a worker, and
300
- # never sends the model anything.
318
+ # never sends the model anything. Local only (SessionAccess::Contract):
319
+ # the Bridge's stats and analytics.json on this machine.
301
320
  def stats(ids)
302
321
  return usage_error("give a session id") if ids.empty?
303
322
  return usage_error("stats takes one session id") if ids.size > 1
@@ -369,8 +388,8 @@ module Samagotchi
369
388
  # error line, so the output keeps the order of the ids given.
370
389
  ok = ids.uniq.map do |given|
371
390
  begin
372
- id = Samagotchi::Session.resolve_id(given)
373
- released = Samagotchi::SessionManager.stop_session(id, wait: 10)
391
+ id = @access.resolve_id(given)
392
+ released = @access.stop(id, wait: 10)
374
393
  rescue Samagotchi::SessionManager::OwnedByTUI => e
375
394
  @stdout.flush
376
395
  @stderr.puts e.message
@@ -380,11 +399,13 @@ module Samagotchi
380
399
  @stderr.puts e.message
381
400
  next false
382
401
  end
402
+ # Remotely no chi --resume (remote attach is its own phase).
403
+ resume = @access.invocation.resume(id)
383
404
  if released
384
- @stdout.puts "Stopped session #{id}. chi --resume #{id} starts a fresh worker."
405
+ @stdout.puts "Stopped session #{id}.#{" #{resume} starts a fresh worker." if resume}"
385
406
  else
386
407
  @stdout.flush
387
- @stderr.puts "Stopped session #{id}, but its worker is still shutting down; try chi --resume #{id} in a moment."
408
+ @stderr.puts "Stopped session #{id}, but its worker is still shutting down#{"; try #{resume} in a moment" if resume}."
388
409
  end
389
410
  true
390
411
  end
@@ -398,8 +419,8 @@ module Samagotchi
398
419
 
399
420
  ok = ids.uniq.map do |given|
400
421
  begin
401
- id = Samagotchi::Session.resolve_id(given)
402
- result = Samagotchi::SessionManager.restart_session(id)
422
+ id = @access.resolve_id(given)
423
+ result = @access.restart(id)
403
424
  rescue Samagotchi::SessionManager::OwnedByTUI => e
404
425
  @stdout.flush
405
426
  @stderr.puts e.message
@@ -426,6 +447,7 @@ module Samagotchi
426
447
  "Restarted session #{result.session_id} on chi #{result.version}#{from}."
427
448
  end
428
449
 
450
+ # Local only (SessionAccess::Contract): this machine's retention sweep.
429
451
  def prune(sub)
430
452
  days, keep, keep_status, dry_run, test_only = @opts.values_at(:days, :keep, :keep_status, :dry_run, :test_only)
431
453
  test_only = true if sub == "clean" && !@opts[:all]
@@ -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
- client = connect(attach: attach, shared: shared, resume: resume, model: model, state_dir: state_dir,
70
- memories: memories, muted_memories: muted_memories, llm_context: llm_context, thinking: thinking,
71
- appended_prompt_files: appended_prompt_files, toolkit: toolkit, toolkit_flags: toolkit_flags,
72
- settings: settings, parent_answers: parent_answers)
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,33 +91,55 @@ 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
- attached = AttachedLoop.new(client: client, screen: surface, client_id: "tui:#{Process.pid}", first_prompt: prompt,
81
- first_command: first_command, no_interrupt: no_interrupt,
82
- default_input: default_input && !prompt && !attach && !resume,
83
- wait_at_eof: !$stdin.tty?,
84
- as_parent: Guardrails::ParentApprovals.parent_process?, state_dir: state_dir)
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
- report_unanswered(attached.unanswered, session_id: client.session_id, state_dir: state_dir)
110
+ # A link's strategy is read where the session is, its commands
111
+ # 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
95
117
  end
96
118
 
119
+ # This TUI's client id: tui:PID, or the connector's own (a remote one
120
+ # adds the host, for the UIs there).
121
+ def client_id(connector)
122
+ return connector.client_id if connector.respond_to?(:client_id)
123
+
124
+ "tui:#{Process.pid}"
125
+ end
126
+
97
127
  # A question left waiting (exit 3), in full on stderr as `chi send
98
128
  # --wait` prints it (ParentReport), so a script or a parent agent
99
129
  # can answer it with chi answer.
100
130
  # @param pending [Hash] the pending question as the Bridge sent it
101
131
  # @param state_dir [String, nil] where the session file is: what a
102
132
  # parent may pick follows its own parent_answers too
103
- def report_unanswered(pending, session_id:, err: $stderr, state_dir: nil)
133
+ # @param strategy [Guardrails::ParentPolicy::Strategy, nil] that
134
+ # strategy, read already (an AttachLink's); nil:
135
+ # ParentReport.strategy_for here
136
+ # @param via [ChiInvocation, nil] how its commands are spelled (nil:
137
+ # this machine's)
138
+ def report_unanswered(pending, session_id:, err: $stderr, state_dir: nil, strategy: nil, via: nil)
104
139
  question = PendingQuestion.from_file(pending)
105
- strategy = ParentReport.strategy_for(session_id, state_dir: state_dir || Session.default_state_dir)
106
- err.print("chi: #{ParentReport.question_text(question, session_id: session_id, strategy: strategy)}")
140
+ strategy ||= ParentReport.strategy_for(session_id, state_dir: state_dir || Session.default_state_dir)
141
+ words = via ? { via: via } : {}
142
+ err.print("chi: #{ParentReport.question_text(question, session_id: session_id, strategy: strategy, **words)}")
107
143
  err.flush
108
144
  end
109
145
 
@@ -169,7 +205,7 @@ module Samagotchi
169
205
  parent_answers: parent_answers))
170
206
  end
171
207
  client = BridgeClient.wait_for(session.id, session_dir: Session.session_dir(session.id, state_dir: sd), timeout: wait)
172
- client || raise(Error, "the worker for session #{session.id} did not start its Bridge in time")
208
+ client || raise(Error, format(LaunchMode::BRIDGE_LATE, session.id))
173
209
  rescue SessionManager::OwnedByTUI
174
210
  raise Error, "session #{resume} is open in a chi REPL; close it there first"
175
211
  rescue ArgumentError => e
@@ -0,0 +1,104 @@
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
+ def stats_metrics = raise(NotImplementedError)
53
+
54
+ # Store a prompt's @path images where the worker reads them.
55
+ # @param paths [Array<String>] the files as typed
56
+ # @return [Array<Hash>] refs ({file:, name:}) for the turn's images
57
+ # @raise [ImageStore::Error] one can't be used
58
+ def ingest_images(_paths) = raise(NotImplementedError)
59
+
60
+ # A turn's saved tool records ([] when none, never raises).
61
+ # @return [Array<Hash>]
62
+ def saved_tool_records(_turn_id) = raise(NotImplementedError)
63
+
64
+ # A turn's saved record (nil when none, never raises).
65
+ # @return [Hash, nil]
66
+ def saved_turn_record(_turn_id) = raise(NotImplementedError)
67
+
68
+ # The session's delegates' counts, the status row's children segment.
69
+ # @return [ChildrenStatus::Counts]
70
+ def children_counts = raise(NotImplementedError)
71
+
72
+ # /exit --delete, once the worker let go.
73
+ def delete_session = raise(NotImplementedError)
74
+
75
+ # /archive: SessionManager.archive_session's result ({discarded: […]}).
76
+ # @return [Hash]
77
+ def archive_session = raise(NotImplementedError)
78
+
79
+ # The newest chi installed where the worker runs, for the version line.
80
+ # @return [String, nil]
81
+ def installed_version = raise(NotImplementedError)
82
+
83
+ # The config's default model where the worker runs, as its /model
84
+ # names it.
85
+ # @return [String, nil]
86
+ def default_model_name = raise(NotImplementedError)
87
+
88
+ # What a parent agent may pick on the session's questions, read now
89
+ # (ParentReport.strategy_for).
90
+ def parent_strategy = raise(NotImplementedError)
91
+
92
+ # @return [Completions]
93
+ def completions = raise(NotImplementedError)
94
+
95
+ # The stream's sleeps between failed reconnects.
96
+ # @return [Array<Float>]
97
+ def reconnect_delays = raise(NotImplementedError)
98
+
99
+ # How the commands the TUI prints are spelled.
100
+ # @return [ChiInvocation]
101
+ def via = raise(NotImplementedError)
102
+ end
103
+ end
104
+ end