samagotchi 0.3.0 → 0.5.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 +162 -1
- data/README.md +29 -2
- data/bin/chi +60 -69
- data/docs/cli.md +211 -77
- data/docs/configuration.md +118 -21
- data/docs/desktop.md +39 -4
- data/docs/guardrails.md +11 -0
- data/docs/hooks.md +89 -7
- data/docs/memory.md +40 -0
- data/docs/plugins.md +50 -0
- data/docs/releasing.md +15 -12
- data/docs/sessions.md +20 -18
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -2
- data/lib/samagotchi/bridge/sse_writer.rb +0 -3
- data/lib/samagotchi/bridge/turn_accumulator.rb +2 -0
- data/lib/samagotchi/bridge.rb +20 -12
- data/lib/samagotchi/bundles/skills/manifest.yml +10 -0
- data/lib/samagotchi/bundles/skills/plugin.rb +419 -0
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +178 -5
- data/lib/samagotchi/bundles/source-links/manifest.yml +3 -3
- data/lib/samagotchi/bundles/source-links/source_links.md +1 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +9 -10
- data/lib/samagotchi/bundles/system/delegated.md +6 -7
- data/lib/samagotchi/bundles/system/identity.md +5 -0
- data/lib/samagotchi/bundles/system/manifest.yml +6 -6
- data/lib/samagotchi/bundles/system/memory_guide.md +26 -0
- data/lib/samagotchi/bundles/system/self_map.md +2 -1
- data/lib/samagotchi/client.rb +25 -26
- data/lib/samagotchi/commands/registry.rb +8 -0
- data/lib/samagotchi/config.rb +97 -113
- data/lib/samagotchi/desktop/macos/App.swift +12 -8
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +4 -2
- data/lib/samagotchi/desktop/macos/Images.swift +113 -0
- data/lib/samagotchi/desktop/macos/Info.plist.erb +6 -0
- data/lib/samagotchi/desktop/macos/Panel.swift +112 -9
- data/lib/samagotchi/desktop/macos.rb +59 -8
- data/lib/samagotchi/desktop_command.rb +6 -3
- data/lib/samagotchi/edit_preview.rb +82 -0
- data/lib/samagotchi/engine.rb +236 -443
- data/lib/samagotchi/gem_update.rb +89 -0
- data/lib/samagotchi/guardrails/approval.rb +26 -4
- data/lib/samagotchi/guardrails/load_failures.rb +9 -3
- data/lib/samagotchi/host_registry.rb +8 -12
- data/lib/samagotchi/idle_client.rb +24 -15
- data/lib/samagotchi/idle_reminders.rb +2 -2
- data/lib/samagotchi/image_store.rb +10 -6
- data/lib/samagotchi/kernel_loop.rb +59 -123
- data/lib/samagotchi/live_versions.rb +65 -0
- data/lib/samagotchi/llm/api_key.rb +41 -0
- data/lib/samagotchi/llm/chat_loop.rb +77 -13
- data/lib/samagotchi/llm/errors.rb +38 -7
- data/lib/samagotchi/llm/http.rb +19 -22
- data/lib/samagotchi/llm/openai_chat.rb +22 -26
- data/lib/samagotchi/memory_bundle/installer.rb +65 -63
- data/lib/samagotchi/memory_bundle/provenance.rb +51 -12
- data/lib/samagotchi/memory_bundle/shipped_update.rb +157 -0
- data/lib/samagotchi/memory_bundle/status.rb +4 -1
- data/lib/samagotchi/memory_bundle/system_bundle.rb +81 -53
- data/lib/samagotchi/model_profile.rb +27 -10
- data/lib/samagotchi/note_command.rb +2 -1
- data/lib/samagotchi/prompt.rb +4 -2
- data/lib/samagotchi/reminder_store.rb +1 -9
- data/lib/samagotchi/reply_wait.rb +48 -4
- data/lib/samagotchi/self_report.rb +37 -5
- data/lib/samagotchi/send_command.rb +190 -17
- data/lib/samagotchi/session.rb +4 -2
- data/lib/samagotchi/session_commands.rb +38 -8
- data/lib/samagotchi/session_manager.rb +19 -53
- data/lib/samagotchi/system_prompt.rb +403 -0
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +5 -3
- data/lib/samagotchi/terminal_ui/attached_loop.rb +141 -108
- data/lib/samagotchi/terminal_ui/attached_view.rb +27 -12
- data/lib/samagotchi/terminal_ui/event_renderer.rb +29 -8
- data/lib/samagotchi/terminal_ui/formatting.rb +41 -22
- data/lib/samagotchi/terminal_ui/input_support.rb +7 -23
- data/lib/samagotchi/terminal_ui/plain_surface.rb +13 -7
- data/lib/samagotchi/terminal_ui/question_prompt.rb +35 -0
- data/lib/samagotchi/terminal_ui/status_row.rb +81 -0
- data/lib/samagotchi/terminal_ui/surface.rb +1 -1
- data/lib/samagotchi/terminal_ui.rb +142 -923
- data/lib/samagotchi/text_diff.rb +181 -0
- data/lib/samagotchi/thinking.rb +126 -0
- data/lib/samagotchi/tool_activity.rb +52 -2
- data/lib/samagotchi/tool_runner.rb +37 -1
- data/lib/samagotchi/tools/ask_user_question.rb +41 -33
- data/lib/samagotchi/tools/edit.rb +23 -9
- data/lib/samagotchi/tools/execute.rb +3 -3
- data/lib/samagotchi/tools/output_guardrails.rb +8 -7
- data/lib/samagotchi/tools/read.rb +4 -4
- data/lib/samagotchi/tools/write.rb +4 -0
- data/lib/samagotchi/turn_flow.rb +12 -2
- data/lib/samagotchi/update_command.rb +309 -0
- data/lib/samagotchi/update_hint.rb +59 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +6 -4
- data/lib/samagotchi/web/app.rb +173 -38
- data/lib/samagotchi/web/lan.rb +99 -0
- data/lib/samagotchi/web/message_parts.rb +19 -10
- data/lib/samagotchi/web/public/activity.js +10 -0
- data/lib/samagotchi/web/public/app.js +135 -78
- data/lib/samagotchi/web/public/chat_view.js +8 -1
- data/lib/samagotchi/web/public/data.js +2 -0
- data/lib/samagotchi/web/public/diff_view.js +58 -0
- data/lib/samagotchi/web/public/index.html +185 -18
- data/lib/samagotchi/web/public/model_pick.js +136 -0
- data/lib/samagotchi/web/public/model_picker.js +224 -0
- data/lib/samagotchi/web/public/notify.js +10 -0
- data/lib/samagotchi/web/public/question_card.js +3 -1
- data/lib/samagotchi/web/public/stage_model.js +110 -0
- data/lib/samagotchi/web/public/stage_view.js +580 -0
- data/lib/samagotchi/web/public/timing.js +6 -2
- data/lib/samagotchi/web/public/turn_events.js +38 -10
- data/lib/samagotchi/web/public/turn_model.js +11 -3
- data/lib/samagotchi/web/public/turn_view.js +76 -20
- data/lib/samagotchi/web/qr.rb +40 -0
- data/lib/samagotchi/web/server.rb +101 -11
- data/lib/samagotchi/web/token.rb +97 -0
- data/lib/samagotchi/worker.rb +5 -4
- metadata +38 -3
- data/lib/samagotchi/terminal_ui/legacy_surface.rb +0 -111
|
@@ -50,6 +50,15 @@ module Samagotchi
|
|
|
50
50
|
" #{paint(refs.map { |ref| "→ image #{ref[:width]}×#{ref[:height]}" }.join(", "), 90)}"
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# An edit/write row's change, after the row: " +3 −1" (green, red).
|
|
54
|
+
# +diff+ is EditPreview's hash, symbol or string keys (SSE, snapshot).
|
|
55
|
+
def format_tool_diff_suffix(diff)
|
|
56
|
+
return "" unless diff.is_a?(Hash)
|
|
57
|
+
|
|
58
|
+
get = ->(key) { diff[key] || diff[key.to_s] }
|
|
59
|
+
" #{paint("+#{get.(:added).to_i}", 32)} #{paint("\u2212#{get.(:removed).to_i}", 31)}"
|
|
60
|
+
end
|
|
61
|
+
|
|
53
62
|
# A card (Engine#show_card) as a framed block: the title and its
|
|
54
63
|
# source, the body as wrapped plain text (no terminal markdown), and
|
|
55
64
|
# one `→ <command>` line per action, the label after it when it says
|
|
@@ -126,6 +135,33 @@ module Samagotchi
|
|
|
126
135
|
text.strip.empty? ? [] : lines
|
|
127
136
|
end
|
|
128
137
|
|
|
138
|
+
# ── Turn ends: one set of words for the REPL, attached mode and the
|
|
139
|
+
# web (timing.js cancelLineText, format.js failedTurnText) ──────────
|
|
140
|
+
|
|
141
|
+
# A cancel's reason as the UIs name it (anything else as it is).
|
|
142
|
+
CANCEL_REASONS = { "ctrl_c" => "Ctrl-C", "user" => "stopped", "hook" => "by a hook" }.freeze
|
|
143
|
+
# Under a canceled prompt turn (a canceled continue is back where it
|
|
144
|
+
# started): its partial progress stays in the conversation.
|
|
145
|
+
ROLLBACK_HINT = "partial progress kept; !rollback restores the pre-turn state"
|
|
146
|
+
|
|
147
|
+
# "✕ turn canceled (Ctrl-C) · 3.1s"
|
|
148
|
+
def turn_canceled_line(reason, duration_ms)
|
|
149
|
+
label = reason.to_s.empty? ? nil : CANCEL_REASONS.fetch(reason.to_s, reason.to_s)
|
|
150
|
+
"#{paint("✕", 33)} turn canceled#{" (#{label})" if label}#{turn_duration_suffix(duration_ms)}"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# "✕ turn failed: <summary> · 2.0s"
|
|
154
|
+
def turn_failed_line(detail, duration_ms)
|
|
155
|
+
"#{paint("✕", 31)} turn failed: #{detail}#{turn_duration_suffix(duration_ms)}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# A dim, indented line under a turn's end: what became of its prompt.
|
|
159
|
+
def turn_end_hint(text) = " #{paint(text, 90)}"
|
|
160
|
+
|
|
161
|
+
def turn_duration_suffix(duration_ms)
|
|
162
|
+
duration_ms.nil? ? "" : " · #{format_elapsed_duration(duration_ms)}"
|
|
163
|
+
end
|
|
164
|
+
|
|
129
165
|
private
|
|
130
166
|
|
|
131
167
|
# The recap shown on return: dim, one "recap>" block, noting how many
|
|
@@ -165,9 +201,11 @@ module Samagotchi
|
|
|
165
201
|
|
|
166
202
|
# ── The status line (the REPL's, and attached mode's idle one) ────────
|
|
167
203
|
|
|
168
|
-
#
|
|
204
|
+
# status.line: off hides it.
|
|
205
|
+
STATUS_LINE_OFF = "off"
|
|
206
|
+
|
|
169
207
|
def status_line_enabled?
|
|
170
|
-
value =
|
|
208
|
+
value = Samagotchi::Config.get("status.line").to_s.strip.downcase
|
|
171
209
|
!(value.empty? || value == STATUS_LINE_OFF || value == "0" || value == "false")
|
|
172
210
|
end
|
|
173
211
|
|
|
@@ -195,10 +233,8 @@ module Samagotchi
|
|
|
195
233
|
"model=#{model} (default: #{default_model})"
|
|
196
234
|
end
|
|
197
235
|
|
|
198
|
-
# @param server [Hash, nil] the server's own numbers ({ctx_pct:, prompt_tokens:, ...})
|
|
199
236
|
# @param estimate [Hash, nil] the kernel's estimate ({est_pct:, bucket:})
|
|
200
|
-
def status_context_text(
|
|
201
|
-
return format_server_context_segment(server) if server.is_a?(Hash)
|
|
237
|
+
def status_context_text(estimate: nil)
|
|
202
238
|
return "" unless estimate.is_a?(Hash)
|
|
203
239
|
|
|
204
240
|
pct = format("%.1f", estimate[:est_pct].to_f)
|
|
@@ -208,23 +244,6 @@ module Samagotchi
|
|
|
208
244
|
"ctx=#{pct}% (#{bucket})"
|
|
209
245
|
end
|
|
210
246
|
|
|
211
|
-
def format_server_context_segment(status)
|
|
212
|
-
pct = status[:ctx_pct]
|
|
213
|
-
base = pct ? "ctx=#{format('%.1f', pct)}%" : "ctx=srv"
|
|
214
|
-
|
|
215
|
-
tokens = []
|
|
216
|
-
prompt_tokens = status[:prompt_tokens]
|
|
217
|
-
completion_tokens = status[:completion_tokens]
|
|
218
|
-
total_tokens = status[:total_tokens]
|
|
219
|
-
tokens << "p=#{prompt_tokens}" if prompt_tokens
|
|
220
|
-
tokens << "c=#{completion_tokens}" if completion_tokens
|
|
221
|
-
tokens << "t=#{total_tokens}" if total_tokens
|
|
222
|
-
|
|
223
|
-
return base if tokens.empty?
|
|
224
|
-
|
|
225
|
-
"#{base} (#{tokens.join(' ')})"
|
|
226
|
-
end
|
|
227
|
-
|
|
228
247
|
# @param label [String] "mem" for the used memories, "muted" for the
|
|
229
248
|
# session's --mute list
|
|
230
249
|
def status_memory_text(names, limit, label: "mem")
|
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "fileutils"
|
|
5
5
|
require "reline"
|
|
6
6
|
|
|
7
|
+
require_relative "../config"
|
|
7
8
|
require_relative "../memory_paths"
|
|
8
9
|
require_relative "../tools/memory"
|
|
9
10
|
require_relative "../session_commands"
|
|
@@ -11,20 +12,18 @@ require_relative "../session_commands"
|
|
|
11
12
|
module Samagotchi
|
|
12
13
|
class TerminalUI
|
|
13
14
|
# Input the REPL and the attached TUI share: the persistent prompt
|
|
14
|
-
# history, Tab completion (/commands, @path in the cwd, #memory
|
|
15
|
-
# #
|
|
16
|
-
# one-shot prefill of the next read (the default
|
|
17
|
-
# back after a failed turn).
|
|
15
|
+
# history, Tab completion (/commands, @path in the cwd, #memory, which
|
|
16
|
+
# inserts #name and the model reads it as typed), multiline reads at the
|
|
17
|
+
# main prompt, and a one-shot prefill of the next read (the default
|
|
18
|
+
# input, a prompt given back after a failed turn).
|
|
18
19
|
#
|
|
19
20
|
# Included for private use; it keeps state in @next_input_prefill and
|
|
20
21
|
# reads @no_default_input.
|
|
21
22
|
module InputSupport
|
|
22
|
-
PROMPT_HISTORY_ENV = "SAMAGOTCHI_HISTORY_FILE"
|
|
23
23
|
XDG_STATE_HOME_ENV = "XDG_STATE_HOME"
|
|
24
24
|
PROMPT_HISTORY_FILE = "history.json"
|
|
25
25
|
PROMPT_HISTORY_STATE_DIR = "samagotchi"
|
|
26
26
|
PROMPT_HISTORY_LIMIT = 20
|
|
27
|
-
DEFAULT_INPUT_ENV = "SAMAGOTCHI_DEFAULT_INPUT"
|
|
28
27
|
AT_PATH_COMPLETION_PREFIX = "@"
|
|
29
28
|
MEMORY_COMPLETION_PREFIX = "#"
|
|
30
29
|
AT_PATH_COMPLETION_MAX_CANDIDATES = 200
|
|
@@ -204,23 +203,8 @@ module Samagotchi
|
|
|
204
203
|
}
|
|
205
204
|
end
|
|
206
205
|
|
|
207
|
-
def normalize_model_input(input)
|
|
208
|
-
input.to_s.gsub(/(^|[^\w\/])#((?:project|system)\/)?([a-zA-Z0-9][a-zA-Z0-9_-]*)/) do
|
|
209
|
-
prefix = Regexp.last_match(1)
|
|
210
|
-
scoped = Regexp.last_match(2).to_s
|
|
211
|
-
name = Regexp.last_match(3)
|
|
212
|
-
scope = scoped.delete_suffix("/")
|
|
213
|
-
normalized = if scope.empty?
|
|
214
|
-
"memory \"#{name}\""
|
|
215
|
-
else
|
|
216
|
-
"memory \"#{name}\" in #{scope} scope"
|
|
217
|
-
end
|
|
218
|
-
"#{prefix}#{normalized}"
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
|
|
222
206
|
def history_file_path
|
|
223
|
-
explicit =
|
|
207
|
+
explicit = Samagotchi::Config.get("history.file").to_s.strip
|
|
224
208
|
return explicit unless explicit.empty?
|
|
225
209
|
|
|
226
210
|
File.join(xdg_state_home, PROMPT_HISTORY_STATE_DIR, PROMPT_HISTORY_FILE)
|
|
@@ -288,7 +272,7 @@ module Samagotchi
|
|
|
288
272
|
def default_input_text
|
|
289
273
|
return nil unless default_input_wanted?
|
|
290
274
|
|
|
291
|
-
default =
|
|
275
|
+
default = Samagotchi::Config.get("default.input")
|
|
292
276
|
return nil if default.nil? || default.strip.empty?
|
|
293
277
|
|
|
294
278
|
default
|
|
@@ -6,17 +6,19 @@ require_relative "surface"
|
|
|
6
6
|
|
|
7
7
|
module Samagotchi
|
|
8
8
|
class TerminalUI
|
|
9
|
-
#
|
|
9
|
+
# Both TUIs' Surface when a live region can't be drawn (output or
|
|
10
10
|
# input not a terminal, TERM=dumb, or a Reline the seam doesn't support):
|
|
11
11
|
# append-only output. Committed text and the notes, status and hints rows
|
|
12
12
|
# print as lines. The activity slot is dropped, because redrawing it in
|
|
13
13
|
# place needs cursor movement (the turn's lines still print as they end).
|
|
14
|
-
# The prompt is Reline's
|
|
14
|
+
# The prompt is Reline's own, so output may cross it; the editor slot
|
|
15
|
+
# prints the prompt of a plain read (the REPL's ? off a terminal).
|
|
15
16
|
class PlainSurface
|
|
16
17
|
include Surface
|
|
17
18
|
|
|
18
|
-
# @param out [IO]
|
|
19
|
-
|
|
19
|
+
# @param out [IO, nil] nil writes to $stdout as it is at each write
|
|
20
|
+
# (specs swap it after the UI is built)
|
|
21
|
+
def initialize(out: nil)
|
|
20
22
|
@out = out
|
|
21
23
|
@lock = Monitor.new
|
|
22
24
|
end
|
|
@@ -27,15 +29,15 @@ module Samagotchi
|
|
|
27
29
|
def columns = IO.console&.winsize&.last || 80
|
|
28
30
|
|
|
29
31
|
def commit(text)
|
|
30
|
-
synchronize {
|
|
32
|
+
synchronize { out.puts(text) }
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def set_slot(name, rows)
|
|
34
36
|
check_slot!(name)
|
|
35
|
-
raise ArgumentError, "Reline draws the editor slot" if name == :editor
|
|
36
37
|
return if name == :activity
|
|
38
|
+
return synchronize { out.print(Array(rows).join("\n")) && out.flush } if name == :editor
|
|
37
39
|
|
|
38
|
-
synchronize { lay_out(rows, width: columns).each { |row|
|
|
40
|
+
synchronize { lay_out(rows, width: columns).each { |row| out.puts(row) } }
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
# Nothing printed can be taken back.
|
|
@@ -46,6 +48,10 @@ module Samagotchi
|
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def close; end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def out = @out || $stdout
|
|
49
55
|
end
|
|
50
56
|
end
|
|
51
57
|
end
|
|
@@ -14,6 +14,8 @@ module Samagotchi
|
|
|
14
14
|
# an optional "; reason"; a substring would pick the wrong one ("y" is
|
|
15
15
|
# in "Deny").
|
|
16
16
|
class QuestionPrompt
|
|
17
|
+
DIFF_CODES = { "+" => 32, "-" => 31, "@" => 2, "\\" => 2 }.freeze
|
|
18
|
+
|
|
17
19
|
# A parsed answer. +error+ set: re-ask. +note+: print, then accept.
|
|
18
20
|
Answer = Struct.new(:selected, :freeform, :error, :note, keyword_init: true) do
|
|
19
21
|
def ok? = error.nil?
|
|
@@ -24,6 +26,8 @@ module Samagotchi
|
|
|
24
26
|
# @param pending [Hash] Engine#pending_question (symbol or string keys)
|
|
25
27
|
def initialize(pending)
|
|
26
28
|
field = ->(key) { pending[key] || pending[key.to_s] }
|
|
29
|
+
approval = field.(:approval)
|
|
30
|
+
@preview = approval.is_a?(Hash) ? (approval[:preview] || approval["preview"]) : nil
|
|
27
31
|
@id = field.(:id).to_s
|
|
28
32
|
@question = field.(:question).to_s
|
|
29
33
|
@options = Array(field.(:options)).map { |v| v.to_s.strip }.reject(&:empty?)
|
|
@@ -49,6 +53,34 @@ module Samagotchi
|
|
|
49
53
|
hint: slot_hint, question_code: approval? ? 33 : 94, paint: paint)
|
|
50
54
|
end
|
|
51
55
|
|
|
56
|
+
# The most diff lines #preview_lines prints; the web shows them all.
|
|
57
|
+
PREVIEW_LINES = 40
|
|
58
|
+
|
|
59
|
+
# An edit/write approval's dry-run diff (approval.preview, symbol or
|
|
60
|
+
# string keys: nested keys are strings after a reload or SSE), as
|
|
61
|
+
# lines to print above the slot: coloured +/-, dim @@, cut at
|
|
62
|
+
# PREVIEW_LINES. [] for none.
|
|
63
|
+
def preview_lines(paint: ->(text, _code) { text })
|
|
64
|
+
preview = @preview
|
|
65
|
+
return [] unless preview.is_a?(Hash)
|
|
66
|
+
|
|
67
|
+
get = ->(key) { preview[key] || preview[key.to_s] }
|
|
68
|
+
return [paint.("this edit would fail: #{get.(:error)}", 33)] if get.(:error)
|
|
69
|
+
return [paint.("diff not shown: #{get.(:skipped)}", 2)] if get.(:skipped)
|
|
70
|
+
|
|
71
|
+
lines = get.(:text).to_s.split("\n")
|
|
72
|
+
return [] if lines.empty?
|
|
73
|
+
|
|
74
|
+
# TextDiff's own cut note ("… N more lines") counts toward the rest.
|
|
75
|
+
cut = lines.last.to_s[/\A… (\d+) more lines\z/, 1]
|
|
76
|
+
lines.pop if cut
|
|
77
|
+
hidden = [lines.size - PREVIEW_LINES, 0].max + cut.to_i
|
|
78
|
+
shown = lines.first(PREVIEW_LINES).map { |line| paint.(line, diff_code(line)) }
|
|
79
|
+
shown.unshift(paint.("new file", 2)) if get.(:new_file)
|
|
80
|
+
shown << paint.("… #{hidden} more lines (full diff on the web)", 2) if hidden.positive?
|
|
81
|
+
shown
|
|
82
|
+
end
|
|
83
|
+
|
|
52
84
|
# The one line that stays in the scrollback once the question closes:
|
|
53
85
|
# the question and what became of it.
|
|
54
86
|
# @param outcome [String] the answer (#answer_text) or what closed it
|
|
@@ -148,6 +180,9 @@ module Samagotchi
|
|
|
148
180
|
found = options.find { |o| o.downcase == tok.downcase || o.downcase.include?(tok.downcase) }
|
|
149
181
|
found ? [found, nil] : [nil, "Unknown option '#{tok}'. Use numbers 1-#{options.size} or exact labels."]
|
|
150
182
|
end
|
|
183
|
+
|
|
184
|
+
# "+" green, "-" red, "@@" and "\ No newline" dim, context plain.
|
|
185
|
+
def diff_code(line) = DIFF_CODES.fetch(line[0], 0)
|
|
151
186
|
end
|
|
152
187
|
end
|
|
153
188
|
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "formatting"
|
|
4
|
+
|
|
5
|
+
module Samagotchi
|
|
6
|
+
class TerminalUI
|
|
7
|
+
# The status row under the prompt, the same for both TUIs:
|
|
8
|
+
# `status> model=… | ↳ parent | ctx=12.3% (under20) | mem: … | muted: …`.
|
|
9
|
+
# A UI feeds it what it learns (attached mode from the Bridge's events,
|
|
10
|
+
# the REPL from its Engine's) through #update, and the row is redrawn in
|
|
11
|
+
# the surface's status slot only when its text changes.
|
|
12
|
+
class StatusRow
|
|
13
|
+
include Formatting
|
|
14
|
+
|
|
15
|
+
# Memory names shown before ", +N".
|
|
16
|
+
MEMORY_LIMIT = 8
|
|
17
|
+
# What the row shows:
|
|
18
|
+
# model, default_model the model in use, and the config's default
|
|
19
|
+
# (named beside another model)
|
|
20
|
+
# served [served, asked]: what the server said it
|
|
21
|
+
# served for the name asked (ServedModel)
|
|
22
|
+
# parent_id the session that delegated this one
|
|
23
|
+
# context the kernel's estimate {est_pct:, bucket:}
|
|
24
|
+
# used_memories the memories the session read
|
|
25
|
+
# preloaded its --memory list (shown until a turn
|
|
26
|
+
# records them as used)
|
|
27
|
+
# muted its --mute list
|
|
28
|
+
FIELDS = %i[model default_model served parent_id context used_memories preloaded muted].freeze
|
|
29
|
+
|
|
30
|
+
# @param surface [Surface] with #columns
|
|
31
|
+
def initialize(surface)
|
|
32
|
+
@surface = surface
|
|
33
|
+
@values = { used_memories: [], preloaded: [], muted: [] }
|
|
34
|
+
@shown = nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The surface it draws on (the REPL swaps in a live region): the row
|
|
38
|
+
# is drawn there at the next change.
|
|
39
|
+
def surface=(surface)
|
|
40
|
+
@surface = surface
|
|
41
|
+
@shown = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def [](field) = @values[field]
|
|
45
|
+
|
|
46
|
+
# Change some fields; the row is redrawn when its text changed.
|
|
47
|
+
def update(**fields)
|
|
48
|
+
unknown = fields.keys - FIELDS
|
|
49
|
+
raise ArgumentError, "unknown status field(s): #{unknown.join(", ")}" if unknown.any?
|
|
50
|
+
|
|
51
|
+
@values.merge!(fields)
|
|
52
|
+
refresh
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Draw the row unless it shows that already (status.line: off draws
|
|
56
|
+
# nothing).
|
|
57
|
+
def refresh
|
|
58
|
+
return unless status_line_enabled?
|
|
59
|
+
|
|
60
|
+
rows = rows(@surface.columns - 1)
|
|
61
|
+
return if rows == @shown
|
|
62
|
+
|
|
63
|
+
@shown = rows
|
|
64
|
+
rows.empty? ? @surface.clear_slot(:status) : @surface.set_slot(:status, rows)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [Array<String>] the row cut to +width+ (none with nothing to say)
|
|
68
|
+
def rows(width)
|
|
69
|
+
served, served_for = @values[:served]
|
|
70
|
+
model = @values[:model]
|
|
71
|
+
parent = @values[:parent_id]
|
|
72
|
+
segments = [model ? status_model_text(model, @values[:default_model], served: served, served_for: served_for) : "",
|
|
73
|
+
parent ? "↳ #{parent.to_s[0, 8]}" : "",
|
|
74
|
+
status_context_text(estimate: @values[:context]),
|
|
75
|
+
status_memory_text(Array(@values[:used_memories]) | Array(@values[:preloaded]), MEMORY_LIMIT),
|
|
76
|
+
status_memory_text(@values[:muted], MEMORY_LIMIT, label: "muted")].reject(&:empty?)
|
|
77
|
+
status_rows(segments, width)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -15,7 +15,7 @@ module Samagotchi
|
|
|
15
15
|
#
|
|
16
16
|
# The slots, top down around the prompt: +activity+ (spinner, thinking
|
|
17
17
|
# preview), +editor+ (the prompt), +status+, +notes+ (question choices),
|
|
18
|
-
# +hints+. An implementation without a live region (
|
|
18
|
+
# +hints+. An implementation without a live region (PlainSurface) may
|
|
19
19
|
# print a slot's rows as plain output instead, so the rows stay on screen.
|
|
20
20
|
#
|
|
21
21
|
# Instead of rows, a slot may hold content that lays itself out: anything
|