openclacky 1.3.3 → 1.3.4
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 +26 -0
- data/docs/rich_ui_guide.md +277 -0
- data/docs/rich_ui_refactor_plan.md +396 -0
- data/lib/clacky/agent/llm_caller.rb +10 -4
- data/lib/clacky/agent/session_serializer.rb +3 -2
- data/lib/clacky/agent.rb +3 -2
- data/lib/clacky/agent_config.rb +2 -14
- data/lib/clacky/api_extension.rb +262 -0
- data/lib/clacky/api_extension_loader.rb +156 -0
- data/lib/clacky/cli.rb +93 -3
- data/lib/clacky/client.rb +38 -13
- data/lib/clacky/default_agents/_panels/git/panel.js +1 -1
- data/lib/clacky/default_agents/_panels/time_machine/panel.js +1 -1
- data/lib/clacky/default_skills/media-gen/SKILL.md +9 -6
- data/lib/clacky/idle_compression_timer.rb +3 -1
- data/lib/clacky/locales/en.rb +26 -0
- data/lib/clacky/locales/i18n.rb +26 -0
- data/lib/clacky/locales/zh.rb +26 -0
- data/lib/clacky/rich_ui/components/base_component.rb +50 -0
- data/lib/clacky/rich_ui/components/dialogs/approval_dialog.rb +142 -0
- data/lib/clacky/rich_ui/components/dialogs/config_menu_dialog.rb +106 -0
- data/lib/clacky/rich_ui/components/dialogs/form_dialog.rb +128 -0
- data/lib/clacky/rich_ui/components/sidebar.rb +119 -0
- data/lib/clacky/rich_ui/components/sidebar_panels.rb +134 -0
- data/lib/clacky/rich_ui/components/status_view.rb +58 -0
- data/lib/clacky/rich_ui/components/thinking_live_view.rb +79 -0
- data/lib/clacky/rich_ui/entry_tracker.rb +56 -0
- data/lib/clacky/rich_ui/layout_adapter.rb +16 -0
- data/lib/clacky/rich_ui/progress_handle_adapter.rb +24 -0
- data/lib/clacky/rich_ui/rich_ui_controller.rb +868 -0
- data/lib/clacky/rich_ui/shell/rich_agent_shell.rb +184 -0
- data/lib/clacky/rich_ui/view_renderer.rb +291 -0
- data/lib/clacky/rich_ui.rb +57 -0
- data/lib/clacky/rich_ui_controller.rb +3 -1549
- data/lib/clacky/server/api_extension_dispatcher.rb +120 -0
- data/lib/clacky/server/http_server.rb +150 -103
- data/lib/clacky/server/session_registry.rb +1 -1
- data/lib/clacky/shell_hook_loader.rb +1 -1
- data/lib/clacky/tools/edit.rb +14 -2
- data/lib/clacky/ui2/ui_controller.rb +7 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +56 -59
- data/lib/clacky/web/app.js +65 -7
- data/lib/clacky/web/components/onboard.js +18 -2
- data/lib/clacky/web/core/aside.js +8 -3
- data/lib/clacky/web/core/ext.js +1 -1
- data/lib/clacky/web/features/skills/store.js +30 -2
- data/lib/clacky/web/features/skills/view.js +32 -1
- data/lib/clacky/web/features/workspace/view.js +1 -1
- data/lib/clacky/web/i18n.js +32 -20
- data/lib/clacky/web/index.html +9 -17
- data/lib/clacky/web/sessions.js +286 -28
- data/lib/clacky/web/settings.js +109 -111
- data/lib/clacky/web/ws-dispatcher.js +7 -3
- data/lib/clacky.rb +17 -2
- metadata +38 -2
- data/lib/clacky/media/output_dir.rb +0 -43
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_rich"
|
|
4
|
+
require_relative "sidebar_panels"
|
|
5
|
+
|
|
6
|
+
module Clacky
|
|
7
|
+
module RichUI
|
|
8
|
+
class RichSidebar
|
|
9
|
+
MODES = %i[work tasks context auto hidden].freeze
|
|
10
|
+
PANEL_HEIGHT_RATIOS = { 1 => [1.0], 2 => [0.5, 0.5], 3 => [0.34, 0.33, 0.33] }.freeze
|
|
11
|
+
PANEL_NAMES = { work: "Work", tasks: "Tasks", context: "Context" }.freeze
|
|
12
|
+
|
|
13
|
+
attr_accessor :width, :height
|
|
14
|
+
attr_reader :mode
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
@mode = :auto
|
|
18
|
+
@panels = {
|
|
19
|
+
work: RichWorkPanel.new,
|
|
20
|
+
tasks: RichTasksPanel.new,
|
|
21
|
+
context: RichContextPanel.new
|
|
22
|
+
}
|
|
23
|
+
@width = 0
|
|
24
|
+
@height = 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def update_plan(text)
|
|
28
|
+
@panels[:work].update_plan(text)
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def set_tasks(tasks)
|
|
33
|
+
@panels[:tasks].set_tasks(tasks)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update_context(token_data)
|
|
38
|
+
@panels[:context].update_tokens(token_data)
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def update_work_activities(activities)
|
|
43
|
+
@panels[:work].update_activities(activities)
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def update_work_stats(tasks, cost)
|
|
48
|
+
@panels[:work].update_stats(tasks, cost)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the tasks list from the tasks panel (for tests/assertions)
|
|
52
|
+
def tasks
|
|
53
|
+
@panels[:tasks].instance_variable_get(:@tasks)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def set_mode(mode)
|
|
57
|
+
@mode = MODES.include?(mode) ? mode : :auto
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def render
|
|
61
|
+
visible = visible_panels
|
|
62
|
+
return [""] if visible.empty?
|
|
63
|
+
|
|
64
|
+
heights = panel_heights(visible)
|
|
65
|
+
panel_lines = visible.each_with_index.flat_map do |key, i|
|
|
66
|
+
panel = @panels[key]
|
|
67
|
+
panel.width = [@width - 2, 1].max
|
|
68
|
+
panel.height = heights[i]
|
|
69
|
+
p = RubyRich::Panel.new(panel.render, title: PANEL_NAMES[key], border_style: :blue, title_align: :left)
|
|
70
|
+
p.width = @width
|
|
71
|
+
p.height = heights[i]
|
|
72
|
+
p.render
|
|
73
|
+
end
|
|
74
|
+
panel_lines.first(@height)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private def visible_panels
|
|
78
|
+
case @mode
|
|
79
|
+
when :work then [:work]
|
|
80
|
+
when :tasks then [:tasks]
|
|
81
|
+
when :context then [:context]
|
|
82
|
+
when :hidden then []
|
|
83
|
+
when :auto
|
|
84
|
+
@panels.select { |_key, panel| panel_has_content?(panel) }.keys
|
|
85
|
+
else
|
|
86
|
+
[]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def panel_heights(visible)
|
|
91
|
+
max_h = [@height, 1].max
|
|
92
|
+
# Context panel gets exactly 6 lines; remaining space split among others
|
|
93
|
+
ctx_idx = visible.index(:context)
|
|
94
|
+
if ctx_idx
|
|
95
|
+
ctx_h = [6, max_h / [visible.length, 1].max].min
|
|
96
|
+
other_count = visible.length - 1
|
|
97
|
+
other_h = other_count > 0 ? (max_h - ctx_h) / other_count : 0
|
|
98
|
+
visible.each_with_index.map { |_, i| i == ctx_idx ? ctx_h : [other_h, 1].max }
|
|
99
|
+
else
|
|
100
|
+
h = max_h / visible.length
|
|
101
|
+
visible.map { [h, 1].max }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def panel_has_content?(panel)
|
|
106
|
+
case panel
|
|
107
|
+
when RichWorkPanel
|
|
108
|
+
true # Always show — shows "0 tasks · $0.0000" when empty
|
|
109
|
+
when RichTasksPanel
|
|
110
|
+
panel.has_tasks?
|
|
111
|
+
when RichContextPanel
|
|
112
|
+
true # Always show — shows "No token data" when empty
|
|
113
|
+
else
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_rich"
|
|
4
|
+
require_relative "base_component"
|
|
5
|
+
|
|
6
|
+
module Clacky
|
|
7
|
+
module RichUI
|
|
8
|
+
class RichWorkPanel
|
|
9
|
+
include Components::BaseComponent
|
|
10
|
+
|
|
11
|
+
attr_accessor :width, :height
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@plan = ""
|
|
15
|
+
@activities = []
|
|
16
|
+
@tasks = 0
|
|
17
|
+
@cost = 0.0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update_plan(text)
|
|
21
|
+
@plan = text.to_s
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def update_activities(activities)
|
|
25
|
+
@activities = Array(activities).last(8)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update_stats(tasks, cost)
|
|
29
|
+
@tasks = tasks.to_i
|
|
30
|
+
@cost = cost.to_f
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def render
|
|
34
|
+
lines = []
|
|
35
|
+
lines << @plan unless @plan.empty?
|
|
36
|
+
unless @activities.empty?
|
|
37
|
+
lines << "" unless lines.empty?
|
|
38
|
+
@activities.each do |a|
|
|
39
|
+
marker = status_marker(a[:status] || :pending)
|
|
40
|
+
lines << "#{marker} #{a[:label]}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
lines << "" unless lines.empty?
|
|
44
|
+
lines << muted("#{@tasks} tasks · $#{@cost.round(4)}")
|
|
45
|
+
lines.join("\n")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class RichTasksPanel
|
|
50
|
+
include Components::BaseComponent
|
|
51
|
+
|
|
52
|
+
attr_accessor :width, :height
|
|
53
|
+
|
|
54
|
+
def initialize
|
|
55
|
+
@tasks = []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def set_tasks(tasks)
|
|
59
|
+
@tasks = Array(tasks)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def has_tasks?
|
|
63
|
+
!@tasks.empty?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def render
|
|
67
|
+
return muted("No active tasks") if @tasks.empty?
|
|
68
|
+
|
|
69
|
+
lines = []
|
|
70
|
+
done_count = 0
|
|
71
|
+
total = @tasks.length
|
|
72
|
+
@tasks.each do |task|
|
|
73
|
+
label = task_label(task)
|
|
74
|
+
status = task_status(task)
|
|
75
|
+
done_count += 1 if %i[done completed].include?(status)
|
|
76
|
+
lines << "#{status_marker(status)} #{label}"
|
|
77
|
+
end
|
|
78
|
+
lines << "" unless lines.empty?
|
|
79
|
+
lines << muted("#{done_count}/#{total} done")
|
|
80
|
+
lines.join("\n")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private def task_label(task)
|
|
84
|
+
case task
|
|
85
|
+
when Hash
|
|
86
|
+
(task[:label] || task["label"] || task[:title] || task["title"] ||
|
|
87
|
+
task[:content] || task["content"] || task[:task] || task["task"]).to_s
|
|
88
|
+
else
|
|
89
|
+
task.to_s
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def task_status(task)
|
|
94
|
+
case task
|
|
95
|
+
when Hash then (task[:status] || task["status"] || :pending).to_sym
|
|
96
|
+
else :pending
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class RichContextPanel
|
|
102
|
+
include Components::BaseComponent
|
|
103
|
+
|
|
104
|
+
attr_accessor :width, :height
|
|
105
|
+
|
|
106
|
+
def initialize
|
|
107
|
+
@token_usage = nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def update_tokens(data)
|
|
111
|
+
@token_usage = data
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def render
|
|
115
|
+
return muted("No token data") unless @token_usage
|
|
116
|
+
|
|
117
|
+
input = @token_usage[:prompt_tokens] || @token_usage[:input] || 0
|
|
118
|
+
output = @token_usage[:completion_tokens] || @token_usage[:output] || 0
|
|
119
|
+
total = @token_usage[:total_tokens] || @token_usage[:total] || (input + output)
|
|
120
|
+
cost = @token_usage[:cost]
|
|
121
|
+
|
|
122
|
+
lines = []
|
|
123
|
+
lines << "#{muted("prompt:")} #{input} tok"
|
|
124
|
+
lines << "#{muted("output:")} #{output} tok"
|
|
125
|
+
lines << "#{muted("total:")} #{total} tok"
|
|
126
|
+
if cost
|
|
127
|
+
lines << ""
|
|
128
|
+
lines << "#{muted("cost:")} $#{cost.round(4)}"
|
|
129
|
+
end
|
|
130
|
+
lines.join("\n")
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_rich"
|
|
4
|
+
|
|
5
|
+
module Clacky
|
|
6
|
+
module RichUI
|
|
7
|
+
class RichStatusView
|
|
8
|
+
SPINNER = ['|', '/', '-', '\\'].freeze
|
|
9
|
+
|
|
10
|
+
attr_accessor :width, :height
|
|
11
|
+
|
|
12
|
+
def initialize(shell)
|
|
13
|
+
@shell = shell
|
|
14
|
+
@spinner_index = 0
|
|
15
|
+
@width = 0
|
|
16
|
+
@height = 1
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def render
|
|
20
|
+
theme = @shell.theme
|
|
21
|
+
clacky = @shell.clacky_controller
|
|
22
|
+
return [""] unless clacky
|
|
23
|
+
|
|
24
|
+
status = clacky.status || "idle"
|
|
25
|
+
tasks = clacky.tasks_count || 0
|
|
26
|
+
cost = clacky.total_cost || 0.0
|
|
27
|
+
turn = clacky.turn_active
|
|
28
|
+
ctrlc = clacky.ctrl_c_warning
|
|
29
|
+
|
|
30
|
+
mode = clacky.config&.dig(:mode) || "agent"
|
|
31
|
+
model = clacky.config&.dig(:model) || "—"
|
|
32
|
+
latency = clacky.latest_latency
|
|
33
|
+
model_str = latency ? "#{model} (#{latency})" : model
|
|
34
|
+
meta_right = "#{mode} · #{model_str}"
|
|
35
|
+
|
|
36
|
+
if ctrlc
|
|
37
|
+
line = "#{theme.style("⏎", :error)} #{theme.style(ctrlc, :error)}"
|
|
38
|
+
elsif turn
|
|
39
|
+
@spinner_index = (@spinner_index + 1) % SPINNER.length
|
|
40
|
+
spinner = theme.style(SPINNER[@spinner_index], :accent)
|
|
41
|
+
label = clacky.work_label || "working…"
|
|
42
|
+
right = "#{meta_right} · #{tasks} tasks · $#{cost.round(4)}"
|
|
43
|
+
left = "#{spinner} #{theme.style(label, :body)}"
|
|
44
|
+
else
|
|
45
|
+
right = "#{meta_right} · #{tasks} tasks · $#{cost.round(4)} · Ctrl+C quit"
|
|
46
|
+
left = theme.style(status || "idle", :accent)
|
|
47
|
+
end
|
|
48
|
+
space = [@width - visible_len(left) - visible_len(right) - 2, 1].max
|
|
49
|
+
line = "#{left}#{" " * space}#{theme.style(right, :muted)}"
|
|
50
|
+
[line]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private def visible_len(text)
|
|
54
|
+
text.to_s.gsub(/\e\[[0-9;:]*m/, "").length
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_rich"
|
|
4
|
+
|
|
5
|
+
module Clacky
|
|
6
|
+
module RichUI
|
|
7
|
+
class ThinkingLiveView
|
|
8
|
+
SPINNER = ['|', '/', '-', '\\'].freeze
|
|
9
|
+
|
|
10
|
+
attr_accessor :width, :height
|
|
11
|
+
attr_reader :start_time
|
|
12
|
+
|
|
13
|
+
def initialize(shell)
|
|
14
|
+
@shell = shell
|
|
15
|
+
@status = :idle # :idle, :thinking, :done
|
|
16
|
+
@text = +""
|
|
17
|
+
@start_time = nil
|
|
18
|
+
@spinner_index = 0
|
|
19
|
+
@width = 0
|
|
20
|
+
@height = 0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def desired_height
|
|
24
|
+
@status == :idle ? 0 : 6
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start_thinking
|
|
28
|
+
@status = :thinking
|
|
29
|
+
@start_time = Time.now
|
|
30
|
+
@text = +""
|
|
31
|
+
@shell.live&.refresh
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def append_text(delta)
|
|
35
|
+
@text << delta.to_s
|
|
36
|
+
@shell.live&.refresh
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def finish_thinking
|
|
40
|
+
@status = :done
|
|
41
|
+
@shell.live&.refresh
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def idle!
|
|
45
|
+
@status = :idle
|
|
46
|
+
@text = +""
|
|
47
|
+
@start_time = nil
|
|
48
|
+
@shell.live&.refresh
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def render
|
|
52
|
+
theme = @shell.theme
|
|
53
|
+
case @status
|
|
54
|
+
when :idle
|
|
55
|
+
[""]
|
|
56
|
+
when :thinking
|
|
57
|
+
elapsed = @start_time ? (Time.now - @start_time).round(1) : 0.0
|
|
58
|
+
@spinner_index = (@spinner_index + 1) % SPINNER.length
|
|
59
|
+
spinner = theme.style(SPINNER[@spinner_index], :thinking)
|
|
60
|
+
time_str = theme.style("#{elapsed}s", :accent)
|
|
61
|
+
header = " #{spinner} #{theme.style("Thinking", :thinking)} #{time_str}"
|
|
62
|
+
lines = [header]
|
|
63
|
+
visible = @text.to_s.split("\n").last(5)
|
|
64
|
+
visible.each { |l| lines << " #{theme.style(l, :thinking)}" }
|
|
65
|
+
(5 - visible.length).times { lines << "" }
|
|
66
|
+
lines
|
|
67
|
+
when :done
|
|
68
|
+
elapsed = @start_time ? (Time.now - @start_time).round(1) : 0.0
|
|
69
|
+
header = " #{theme.style("Thinking done", :thinking)} #{theme.style("#{elapsed}s", :accent)}"
|
|
70
|
+
lines = [header]
|
|
71
|
+
visible = @text.to_s.split("\n").last(4)
|
|
72
|
+
visible.each { |l| lines << " #{theme.style(l, :muted)}" }
|
|
73
|
+
(4 - visible.length).times { lines << "" }
|
|
74
|
+
lines
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clacky
|
|
4
|
+
module RichUI
|
|
5
|
+
# Lightweight id-based entry tracker for RubyRich Transcript entries.
|
|
6
|
+
#
|
|
7
|
+
# Replaces the fragile @tool_ids stack with explicit id-based tracking.
|
|
8
|
+
# RubyRich's AgentShell already returns stable ids from start_tool_call
|
|
9
|
+
# and accepts ids on finish_tool_call / update_tool_call / remove_entry.
|
|
10
|
+
# EntryTracker wraps these with a semantic API and is ready for future
|
|
11
|
+
# expansion (tracking markdown blocks, thinking entries, etc.).
|
|
12
|
+
class EntryTracker
|
|
13
|
+
def initialize
|
|
14
|
+
@tool_stack = [] # ordered tool_call ids (push on start, pop on finish/error)
|
|
15
|
+
@entries = {} # id => { type:, ... } for future cross-type tracking
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Record a newly started tool call.
|
|
19
|
+
# Returns the id for chaining convenience.
|
|
20
|
+
def register_tool(id)
|
|
21
|
+
@tool_stack << id
|
|
22
|
+
@entries[id] = { type: :tool_call }
|
|
23
|
+
id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Pop and return the most recent tool_call id.
|
|
27
|
+
# Returns nil when the stack is empty (tool output without a preceding call).
|
|
28
|
+
def pop_tool_id
|
|
29
|
+
id = @tool_stack.pop
|
|
30
|
+
@entries.delete(id) if id
|
|
31
|
+
id
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The most recent tool_call id without popping.
|
|
35
|
+
def current_tool_id
|
|
36
|
+
@tool_stack.last
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Are there any pending (unfinished) tool calls?
|
|
40
|
+
def pending_tool?
|
|
41
|
+
!@tool_stack.empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Remove a specific entry by id.
|
|
45
|
+
def remove(id)
|
|
46
|
+
@entries.delete(id)
|
|
47
|
+
@tool_stack.delete(id)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Number of pending tool calls.
|
|
51
|
+
def pending_count
|
|
52
|
+
@tool_stack.length
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clacky
|
|
4
|
+
module RichUI
|
|
5
|
+
class LayoutAdapter
|
|
6
|
+
def initialize(shell)
|
|
7
|
+
@shell = shell
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def clear_output
|
|
11
|
+
@shell.transcript.store.entries.clear
|
|
12
|
+
@shell.viewport.scroll_to_bottom
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clacky
|
|
4
|
+
module RichUI
|
|
5
|
+
class ProgressHandleAdapter
|
|
6
|
+
def initialize(handle)
|
|
7
|
+
@handle = handle
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def update(message: nil, metadata: nil)
|
|
11
|
+
_ = metadata
|
|
12
|
+
@handle.update(message.to_s) if message
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def finish(final_message: nil)
|
|
16
|
+
final_message ? @handle.finish(final_message.to_s) : @handle.finish
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def cancel
|
|
20
|
+
@handle.cancel
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|