kward 0.70.0 → 0.71.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/.github/workflows/pages.yml +1 -1
- data/CHANGELOG.md +48 -2
- data/Gemfile +2 -0
- data/Gemfile.lock +90 -2
- data/README.md +30 -6
- data/Rakefile +96 -0
- data/doc/agent-tools.md +43 -0
- data/doc/api.md +92 -0
- data/doc/authentication.md +39 -25
- data/doc/configuration.md +1 -15
- data/doc/context-tools.md +70 -0
- data/doc/plugins.md +2 -2
- data/doc/releasing.md +14 -5
- data/doc/rpc.md +3 -11
- data/doc/session-management.md +220 -0
- data/doc/usage.md +7 -8
- data/doc/workspace-tools.md +105 -0
- data/lib/kward/cli/commands.rb +8 -0
- data/lib/kward/cli/openrouter_commands.rb +55 -0
- data/lib/kward/cli/prompt_interface.rb +80 -6
- data/lib/kward/cli/rendering.rb +11 -6
- data/lib/kward/cli/sessions.rb +260 -11
- data/lib/kward/cli/settings.rb +0 -30
- data/lib/kward/cli/slash_commands.rb +24 -6
- data/lib/kward/cli.rb +13 -0
- data/lib/kward/compactor.rb +4 -1
- data/lib/kward/config_files.rb +4 -6
- data/lib/kward/conversation.rb +49 -20
- data/lib/kward/model/client.rb +37 -50
- data/lib/kward/model/context_usage.rb +13 -6
- data/lib/kward/model/model_info.rb +92 -16
- data/lib/kward/model/payloads.rb +2 -0
- data/lib/kward/openrouter_model_cache.rb +120 -0
- data/lib/kward/plugin_registry.rb +47 -1
- data/lib/kward/prompt_interface/banner.rb +16 -51
- data/lib/kward/prompt_interface/composer_controller.rb +60 -87
- data/lib/kward/prompt_interface/composer_renderer.rb +7 -1
- data/lib/kward/prompt_interface/key_handler.rb +31 -10
- data/lib/kward/prompt_interface/layout.rb +2 -2
- data/lib/kward/prompt_interface/prompt_renderer.rb +32 -13
- data/lib/kward/prompt_interface/question_prompt.rb +34 -42
- data/lib/kward/prompt_interface/runtime_state.rb +6 -1
- data/lib/kward/prompt_interface/screen.rb +1 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +513 -54
- data/lib/kward/prompt_interface/transcript_buffer.rb +7 -16
- data/lib/kward/prompt_interface/transcript_renderer.rb +3 -3
- data/lib/kward/prompt_interface.rb +22 -28
- data/lib/kward/prompts/commands.rb +2 -1
- data/lib/kward/prompts.rb +2 -2
- data/lib/kward/rpc/server.rb +3 -8
- data/lib/kward/rpc/session_manager.rb +17 -6
- data/lib/kward/session_store.rb +23 -4
- data/lib/kward/telemetry/logger.rb +5 -3
- data/lib/kward/tool_output_compactor.rb +127 -0
- data/lib/kward/tools/base.rb +8 -2
- data/lib/kward/tools/registry.rb +37 -6
- data/lib/kward/tools/retrieve_tool_output.rb +71 -0
- data/lib/kward/tools/search/web.rb +2 -2
- data/lib/kward/tools/summarize_file_structure.rb +29 -0
- data/lib/kward/tools/tool_call.rb +2 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workspace.rb +58 -2
- data/templates/default/fulldoc/html/css/kward.css +256 -7
- data/templates/default/fulldoc/html/full_list.erb +107 -0
- data/templates/default/fulldoc/html/js/kward.js +161 -2
- data/templates/default/fulldoc/html/setup.rb +8 -0
- data/templates/default/kward_navigation.rb +91 -0
- data/templates/default/layout/html/layout.erb +39 -8
- data/templates/default/layout/html/setup.rb +33 -38
- metadata +13 -3
- data/lib/kward/resources/avatar_kward_logo.rb +0 -50
- data/lib/kward/resources/pixel_logo.rb +0 -232
|
@@ -6,25 +6,44 @@ module Kward
|
|
|
6
6
|
module PromptRenderer
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
-
def render_prompt_locked
|
|
9
|
+
def render_prompt_locked(synchronized: false)
|
|
10
10
|
return unless @started && @asking
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
width, height = screen_size
|
|
13
|
+
if width != @last_width || height != @last_height
|
|
14
|
+
with_synchronized_output_locked { render_prompt_body_locked }
|
|
15
|
+
@output_io.flush
|
|
16
|
+
return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
rows, cursor_row, cursor_col = composer_layout(width, height)
|
|
20
|
+
synchronized ||= rows.length != @reserved_rows
|
|
21
|
+
if synchronized
|
|
22
|
+
with_synchronized_output_locked { render_prompt_layout_locked(rows, cursor_row, cursor_col, width, height) }
|
|
23
|
+
else
|
|
24
|
+
render_prompt_layout_locked(rows, cursor_row, cursor_col, width, height)
|
|
24
25
|
end
|
|
25
26
|
@output_io.flush
|
|
26
27
|
end
|
|
27
28
|
|
|
29
|
+
def render_prompt_body_locked
|
|
30
|
+
handle_resize_locked
|
|
31
|
+
width, height = screen_size
|
|
32
|
+
rows, cursor_row, cursor_col = composer_layout(width, height)
|
|
33
|
+
render_prompt_layout_locked(rows, cursor_row, cursor_col, width, height)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def render_prompt_layout_locked(rows, cursor_row, cursor_col, width, height)
|
|
37
|
+
ensure_scroll_region_locked(rows.length, width: width, height: height)
|
|
38
|
+
@rendered_rows = rows.length
|
|
39
|
+
render_composer_rows_locked(rows, height: height)
|
|
40
|
+
@cursor_rendered_row = cursor_row
|
|
41
|
+
@last_width = width
|
|
42
|
+
@last_height = height
|
|
43
|
+
move_to_screen(composer_top_row(height) + cursor_row, cursor_col + 1)
|
|
44
|
+
render_cursor_visibility_locked
|
|
45
|
+
end
|
|
46
|
+
|
|
28
47
|
def render_prompt_after_output_locked
|
|
29
48
|
render_prompt_locked
|
|
30
49
|
end
|
|
@@ -8,13 +8,7 @@ module Kward
|
|
|
8
8
|
|
|
9
9
|
def ask_single_user_question(question, index, total)
|
|
10
10
|
@mutex.synchronize do
|
|
11
|
-
|
|
12
|
-
self.composer_input = ""
|
|
13
|
-
self.composer_cursor = 0
|
|
14
|
-
@pending_keys.clear
|
|
15
|
-
@asking = true
|
|
16
|
-
@busy = false
|
|
17
|
-
@queued_count = 0
|
|
11
|
+
prepare_modal_input_locked("Answer>")
|
|
18
12
|
@question_state = {
|
|
19
13
|
question: question[:question] || question["question"],
|
|
20
14
|
header: question[:header] || question["header"],
|
|
@@ -23,7 +17,6 @@ module Kward
|
|
|
23
17
|
index: index,
|
|
24
18
|
total: total
|
|
25
19
|
}
|
|
26
|
-
reset_history_navigation
|
|
27
20
|
render_prompt_locked
|
|
28
21
|
end
|
|
29
22
|
|
|
@@ -37,6 +30,7 @@ module Kward
|
|
|
37
30
|
render_prompt_locked if resized || footer_refreshed
|
|
38
31
|
else
|
|
39
32
|
result = handle_question_key(key)
|
|
33
|
+
result = drain_pending_question_keys_locked(result)
|
|
40
34
|
render_prompt_locked unless result.is_a?(Hash) || result == SELECT_CANCEL
|
|
41
35
|
end
|
|
42
36
|
end
|
|
@@ -47,6 +41,13 @@ module Kward
|
|
|
47
41
|
end
|
|
48
42
|
end
|
|
49
43
|
|
|
44
|
+
def drain_pending_question_keys_locked(result)
|
|
45
|
+
until result.is_a?(Hash) || result == SELECT_CANCEL || @pending_keys.empty?
|
|
46
|
+
result = handle_question_key(@pending_keys.shift)
|
|
47
|
+
end
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
|
|
50
51
|
def begin_question_prompt_state
|
|
51
52
|
{
|
|
52
53
|
prompt_label: @prompt_label,
|
|
@@ -101,13 +102,13 @@ module Kward
|
|
|
101
102
|
when :delete
|
|
102
103
|
question_delete_at_cursor
|
|
103
104
|
when :left
|
|
104
|
-
|
|
105
|
+
@composer.move_cursor_left
|
|
105
106
|
when :right
|
|
106
|
-
|
|
107
|
+
@composer.move_cursor_right
|
|
107
108
|
when :home
|
|
108
|
-
|
|
109
|
+
@composer.move_to_start_of_line
|
|
109
110
|
when :end
|
|
110
|
-
|
|
111
|
+
@composer.move_to_end_of_line
|
|
111
112
|
when :up
|
|
112
113
|
question_previous_choice
|
|
113
114
|
when :down
|
|
@@ -127,12 +128,11 @@ module Kward
|
|
|
127
128
|
end
|
|
128
129
|
|
|
129
130
|
def handle_question_csi_u_key(key)
|
|
130
|
-
|
|
131
|
-
return false unless
|
|
131
|
+
sequence = parse_csi_u_key(key)
|
|
132
|
+
return false unless sequence
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
queue_pending_keys(key[sequence.length..]) if key.length > sequence.length
|
|
134
|
+
code = sequence[:code]
|
|
135
|
+
queue_pending_keys(sequence[:remaining]) if sequence[:remaining] && !sequence[:remaining].empty?
|
|
136
136
|
|
|
137
137
|
case code
|
|
138
138
|
when 13
|
|
@@ -148,38 +148,34 @@ module Kward
|
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
def handle_question_escape_sequence
|
|
151
|
-
|
|
152
|
-
return SELECT_CANCEL if
|
|
151
|
+
pending_sequence = read_pending_escape_sequence
|
|
152
|
+
return SELECT_CANCEL if pending_sequence.empty?
|
|
153
|
+
|
|
154
|
+
full_sequence = "\e#{pending_sequence}"
|
|
155
|
+
sequence = next_key_token(full_sequence)
|
|
156
|
+
queue_pending_keys(full_sequence[sequence.length..]) if full_sequence.length > sequence.length
|
|
157
|
+
return SELECT_CANCEL if sequence == "\e"
|
|
153
158
|
|
|
154
|
-
key_name = @reader.console.keys[
|
|
159
|
+
key_name = @reader.console.keys[sequence]
|
|
155
160
|
case key_name
|
|
156
161
|
when :up
|
|
157
162
|
question_previous_choice
|
|
158
163
|
when :down
|
|
159
164
|
question_next_choice
|
|
160
165
|
when :left
|
|
161
|
-
|
|
166
|
+
@composer.move_cursor_left
|
|
162
167
|
when :right
|
|
163
|
-
|
|
168
|
+
@composer.move_cursor_right
|
|
164
169
|
end
|
|
165
170
|
true
|
|
166
171
|
end
|
|
167
172
|
|
|
168
173
|
def handle_question_bracketed_paste_key(key)
|
|
169
|
-
|
|
170
|
-
return false unless
|
|
171
|
-
|
|
172
|
-
pasted = text[BRACKETED_PASTE_START.length..] || ""
|
|
173
|
-
until pasted.include?(BRACKETED_PASTE_END)
|
|
174
|
-
chunk = @reader.read_keypress(echo: false, raw: true)
|
|
175
|
-
break if chunk.nil?
|
|
176
|
-
|
|
177
|
-
pasted << chunk.to_s
|
|
178
|
-
end
|
|
174
|
+
paste = read_bracketed_paste(key)
|
|
175
|
+
return false unless paste
|
|
179
176
|
|
|
180
|
-
content
|
|
181
|
-
|
|
182
|
-
queue_pending_keys(remaining) if remaining && !remaining.empty?
|
|
177
|
+
question_insert_string(normalize_paste(paste[:content]))
|
|
178
|
+
queue_pending_keys(paste[:remaining]) if paste[:remaining] && !paste[:remaining].empty?
|
|
183
179
|
true
|
|
184
180
|
end
|
|
185
181
|
|
|
@@ -251,23 +247,19 @@ module Kward
|
|
|
251
247
|
def question_insert_string(string)
|
|
252
248
|
return if string.empty?
|
|
253
249
|
|
|
254
|
-
|
|
255
|
-
self.composer_cursor += string.length
|
|
250
|
+
@composer.insert_string(string)
|
|
256
251
|
@question_state[:selection_index] = question_choices.length - 1 if @question_state
|
|
257
252
|
end
|
|
258
253
|
|
|
259
254
|
def question_delete_before_cursor
|
|
260
|
-
return unless
|
|
255
|
+
return unless @composer.delete_before_cursor
|
|
261
256
|
|
|
262
|
-
self.composer_input = composer_input[0...(composer_cursor - 1)] + composer_input[composer_cursor..]
|
|
263
|
-
self.composer_cursor -= 1
|
|
264
257
|
@question_state[:selection_index] = question_choices.length - 1 if @question_state && !composer_input.empty?
|
|
265
258
|
end
|
|
266
259
|
|
|
267
260
|
def question_delete_at_cursor
|
|
268
|
-
return unless
|
|
261
|
+
return unless @composer.delete_at_cursor
|
|
269
262
|
|
|
270
|
-
self.composer_input = composer_input[0...composer_cursor] + composer_input[(composer_cursor + 1)..]
|
|
271
263
|
@question_state[:selection_index] = question_choices.length - 1 if @question_state && !composer_input.empty?
|
|
272
264
|
end
|
|
273
265
|
|
|
@@ -17,7 +17,7 @@ module Kward
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def tick_spinner_locked
|
|
20
|
-
return false unless
|
|
20
|
+
return false unless spinner_active?
|
|
21
21
|
|
|
22
22
|
now = monotonic_now
|
|
23
23
|
elapsed = now - @last_spinner_tick
|
|
@@ -33,6 +33,11 @@ module Kward
|
|
|
33
33
|
SPINNER_FRAMES[@spinner_frame_index % SPINNER_FRAMES.length]
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
def spinner_active?
|
|
37
|
+
busy = @busy || (@select_state && @select_state[:busy_activity])
|
|
38
|
+
busy && @queued_count.zero? && @started && @asking
|
|
39
|
+
end
|
|
40
|
+
|
|
36
41
|
def tick_footer_locked
|
|
37
42
|
return false unless @footer && @started && @asking
|
|
38
43
|
|