kward 0.83.0 → 0.84.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 +68 -15
- data/CONTRIBUTING.md +74 -0
- data/Gemfile.lock +8 -2
- data/README.md +21 -1
- data/Rakefile +46 -2
- data/SECURITY.md +31 -0
- data/doc/api.md +4 -0
- data/doc/composer.md +1 -1
- data/doc/configuration.md +68 -21
- data/doc/editor.md +28 -13
- data/doc/files.md +8 -4
- data/doc/getting-started.md +3 -0
- data/doc/pan.md +19 -15
- data/doc/platform-support.md +48 -0
- data/doc/security.md +3 -2
- data/doc/shell.md +62 -45
- data/doc/troubleshooting.md +12 -2
- data/doc/usage.md +5 -5
- data/kward.gemspec +5 -4
- data/lib/kward/agent.rb +6 -3
- data/lib/kward/ansi.rb +110 -10
- data/lib/kward/cli/auth_commands.rb +34 -13
- data/lib/kward/cli/commands.rb +83 -62
- data/lib/kward/cli/doctor.rb +39 -17
- data/lib/kward/cli/hook_commands.rb +22 -12
- data/lib/kward/cli/interactive_turn.rb +48 -7
- data/lib/kward/cli/project_skills_commands.rb +8 -4
- data/lib/kward/cli/prompt_interface.rb +27 -0
- data/lib/kward/cli/rendering.rb +15 -9
- data/lib/kward/cli/runtime_helpers.rb +118 -55
- data/lib/kward/cli/slash_commands.rb +12 -14
- data/lib/kward/cli/tabs.rb +83 -12
- data/lib/kward/cli/tool_summaries.rb +14 -0
- data/lib/kward/cli.rb +45 -7
- data/lib/kward/cli_transcript_formatter.rb +11 -4
- data/lib/kward/config_files.rb +82 -68
- data/lib/kward/detached_run.rb +44 -0
- data/lib/kward/interactive_pty_runner.rb +102 -28
- data/lib/kward/{ekwsh.rb → kwsh.rb} +35 -16
- data/lib/kward/kwshrc.rb +233 -0
- data/lib/kward/markdown_code_block.rb +136 -0
- data/lib/kward/model/client.rb +34 -22
- data/lib/kward/model/provider_catalog.rb +5 -0
- data/lib/kward/model/stream_parser.rb +20 -4
- data/lib/kward/pan/index.html.erb +3 -3
- data/lib/kward/pan/server.rb +23 -3
- data/lib/kward/persistent_shell_session.rb +119 -26
- data/lib/kward/project_files.rb +2 -2
- data/lib/kward/prompt_interface/composer_renderer.rb +44 -40
- data/lib/kward/prompt_interface/composer_state.rb +33 -24
- data/lib/kward/prompt_interface/editor/auto_indent.rb +24 -22
- data/lib/kward/prompt_interface/editor/controller.rb +30 -33
- data/lib/kward/prompt_interface/editor/endwise.rb +13 -4
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +289 -44
- data/lib/kward/prompt_interface/editor/renderer.rb +108 -6
- data/lib/kward/prompt_interface/editor/runner.rb +362 -0
- data/lib/kward/prompt_interface/editor/runner_state.rb +78 -0
- data/lib/kward/prompt_interface/editor/state.rb +10 -10
- data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +68 -6
- data/lib/kward/prompt_interface/editor/vibe_state.rb +3 -3
- data/lib/kward/prompt_interface/file_overlay.rb +71 -15
- data/lib/kward/prompt_interface/key_handler.rb +67 -0
- data/lib/kward/prompt_interface/overlay_renderer.rb +7 -5
- data/lib/kward/prompt_interface/project_browser.rb +415 -14
- data/lib/kward/prompt_interface/runtime_state.rb +50 -1
- data/lib/kward/prompt_interface/screen.rb +2 -2
- data/lib/kward/prompt_interface/selection_prompt.rb +3 -1
- data/lib/kward/prompt_interface/slash_overlay.rb +19 -4
- data/lib/kward/prompt_interface/transcript_renderer.rb +12 -7
- data/lib/kward/prompt_interface.rb +93 -18
- data/lib/kward/prompts/commands.rb +1 -1
- data/lib/kward/prompts.rb +1 -1
- data/lib/kward/pty_output_sink.rb +47 -0
- data/lib/kward/rpc/transcript_normalizer.rb +7 -3
- data/lib/kward/scratchpad_languages.rb +74 -0
- data/lib/kward/scratchpad_runner.rb +155 -29
- data/lib/kward/shell_prompt.rb +2 -0
- data/lib/kward/terminal_keys.rb +12 -0
- data/lib/kward/terminal_text.rb +121 -0
- data/lib/kward/text_matcher.rb +18 -0
- data/lib/kward/tools/open_editor.rb +41 -0
- data/lib/kward/tools/registry.rb +13 -4
- data/lib/kward/tools/tool_call.rb +2 -1
- data/lib/kward/version.rb +1 -1
- data/templates/default/fulldoc/html/css/kward.css +0 -125
- data/templates/default/fulldoc/html/images/kward_workflow.svg +52 -0
- data/templates/default/fulldoc/html/setup.rb +1 -1
- data/templates/default/kward_navigation.rb +1 -0
- data/templates/default/layout/html/footer.erb +10 -0
- data/templates/default/layout/html/headers.erb +23 -0
- data/templates/default/layout/html/layout.erb +6 -18
- data/templates/default/layout/html/setup.rb +41 -2
- metadata +36 -8
- data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
|
@@ -11,7 +11,7 @@ module Kward
|
|
|
11
11
|
prepare_transcript_output_locked unless @restoring_transcript
|
|
12
12
|
if label && @stream_state.block != label
|
|
13
13
|
ensure_transcript_block_separator_locked
|
|
14
|
-
write_transcript_text_locked("#{colored(
|
|
14
|
+
write_transcript_text_locked("#{colored(transcript_marker(label), *label_styles(label))} ")
|
|
15
15
|
@stream_state.start_block(label)
|
|
16
16
|
end
|
|
17
17
|
write_transcript_text_locked(delta) unless delta.empty?
|
|
@@ -117,20 +117,25 @@ module Kward
|
|
|
117
117
|
end
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
+
def transcript_marker(label)
|
|
121
|
+
marker = "#{transcript_label(label)}>"
|
|
122
|
+
label == "Assistant" ? "✦ #{marker}" : marker
|
|
123
|
+
end
|
|
124
|
+
|
|
120
125
|
def label_styles(label)
|
|
121
126
|
case label
|
|
122
127
|
when "Reasoning", "Compaction summary"
|
|
123
|
-
[:
|
|
128
|
+
[:metadata, :bold]
|
|
124
129
|
when "Assistant", "Kward"
|
|
125
|
-
[:
|
|
130
|
+
[:activity, :bold]
|
|
126
131
|
when "Tool", "Tool output"
|
|
127
|
-
[:
|
|
132
|
+
[:tool, :bold]
|
|
128
133
|
when "Tool failed"
|
|
129
|
-
[:
|
|
134
|
+
[:failure, :bold]
|
|
130
135
|
when "Retry"
|
|
131
|
-
[:
|
|
136
|
+
[:caution, :bold]
|
|
132
137
|
else
|
|
133
|
-
[:
|
|
138
|
+
[:metadata, :bold]
|
|
134
139
|
end
|
|
135
140
|
end
|
|
136
141
|
|
|
@@ -15,6 +15,8 @@ require_relative "terminal_sequences"
|
|
|
15
15
|
require_relative "terminal_keys"
|
|
16
16
|
require_relative "editor_mode"
|
|
17
17
|
require_relative "diff_view_mode"
|
|
18
|
+
require_relative "scratchpad_languages"
|
|
19
|
+
require_relative "markdown_code_block"
|
|
18
20
|
require_relative "prompt_interface/banner"
|
|
19
21
|
require_relative "prompt_interface/composer_state"
|
|
20
22
|
require_relative "prompt_interface/editor/state"
|
|
@@ -32,6 +34,7 @@ require_relative "prompt_interface/git_prompt"
|
|
|
32
34
|
require_relative "prompt_interface/overlay_renderer"
|
|
33
35
|
require_relative "prompt_interface/editor/renderer"
|
|
34
36
|
require_relative "prompt_interface/editor/syntax_highlighter"
|
|
37
|
+
require_relative "prompt_interface/editor/runner"
|
|
35
38
|
require_relative "prompt_interface/editor/auto_close_pairs"
|
|
36
39
|
require_relative "prompt_interface/editor/endwise"
|
|
37
40
|
require_relative "prompt_interface/editor/word_completion"
|
|
@@ -64,12 +67,15 @@ module Kward
|
|
|
64
67
|
# `prompt_interface/` to keep rendering, layout, keyboard handling, and runtime
|
|
65
68
|
# state readable while sharing one terminal state object.
|
|
66
69
|
class PromptInterface
|
|
67
|
-
HELP_TEXT = "Enter sends • Shift+Enter
|
|
70
|
+
HELP_TEXT = "Enter sends • Shift+Enter newline • / commands • @ files • ↑/↓ history • Ctrl+D exits".freeze
|
|
68
71
|
BUSY_HELP_TEXT = "Ctrl+C cancels".freeze
|
|
69
|
-
SPINNER_FRAMES = %w[
|
|
70
|
-
SPINNER_INTERVAL = 0.
|
|
72
|
+
SPINNER_FRAMES = %w[· • ● •].freeze
|
|
73
|
+
SPINNER_INTERVAL = 0.16
|
|
71
74
|
FOOTER_REFRESH_INTERVAL = 1.0
|
|
72
75
|
COMPOSER_STATUS_REFRESH_INTERVAL = 1.0
|
|
76
|
+
COMPLETION_DISPLAY_SECONDS = 0.8
|
|
77
|
+
RESPONSE_ARRIVAL_SECONDS = 0.4
|
|
78
|
+
BUSY_ELAPSED_DELAY_SECONDS = 2.0
|
|
73
79
|
COMPOSER_MAX_INPUT_ROWS = 6
|
|
74
80
|
IMAGE_VIEWER_MAX_ROWS = 8
|
|
75
81
|
TRANSCRIPT_BUFFER_LIMIT = 200_000
|
|
@@ -89,6 +95,7 @@ module Kward
|
|
|
89
95
|
include EditorEndwise
|
|
90
96
|
include EditorWordCompletion
|
|
91
97
|
include EditorAutoIndent
|
|
98
|
+
include EditorRunner
|
|
92
99
|
include ComposerRenderer
|
|
93
100
|
include ComposerController
|
|
94
101
|
include ModernEditorMode
|
|
@@ -133,7 +140,7 @@ module Kward
|
|
|
133
140
|
end
|
|
134
141
|
end
|
|
135
142
|
|
|
136
|
-
def initialize(input: $stdin, output: $stdout, slash_commands: [], overlay_settings: nil, project_browser_icon_theme: "off", footer: nil, composer_status: nil, busy_help: true, attachment_badges: nil, attachment_parser: nil, banner_message: nil, tab_keybindings: nil, prompt_history: nil, workspace_root: Dir.pwd, editor_mode: nil, editor_mode_source: nil, editor_auto_indent: true, editor_auto_indent_source: nil, editor_auto_close_pairs: true, editor_auto_close_pairs_source: nil, editor_soft_wrap: true, editor_soft_wrap_source: nil, editor_bar_cursor: true, editor_bar_cursor_source: nil, editor_line_numbers: "absolute", editor_line_numbers_source: nil, diff_view: "auto", diff_view_source: nil, redraw_handler: nil)
|
|
143
|
+
def initialize(input: $stdin, output: $stdout, slash_commands: [], overlay_settings: nil, project_browser_icon_theme: "off", footer: nil, composer_status: nil, busy_help: true, attachment_badges: nil, attachment_parser: nil, banner_message: nil, tab_keybindings: nil, prompt_history: nil, workspace_root: Dir.pwd, editor_mode: nil, editor_mode_source: nil, editor_auto_indent: true, editor_auto_indent_source: nil, editor_auto_close_pairs: true, editor_auto_close_pairs_source: nil, editor_soft_wrap: true, editor_soft_wrap_source: nil, editor_bar_cursor: true, editor_bar_cursor_source: nil, editor_line_numbers: "absolute", editor_line_numbers_source: nil, diff_view: "auto", diff_view_source: nil, editor_runners_source: nil, redraw_handler: nil)
|
|
137
144
|
@input_io = input
|
|
138
145
|
@output_io = output
|
|
139
146
|
# Logical state may change during a child handoff, but only the owner may render.
|
|
@@ -145,12 +152,14 @@ module Kward
|
|
|
145
152
|
@workspace_root = File.expand_path(workspace_root.to_s.empty? ? Dir.pwd : workspace_root)
|
|
146
153
|
@composer = ComposerState.new
|
|
147
154
|
load_history(@prompt_history.values) if @prompt_history
|
|
148
|
-
self.composer_input = @composer.input
|
|
149
|
-
self.composer_cursor = @composer.cursor
|
|
150
155
|
@started = false
|
|
151
156
|
@asking = false
|
|
152
157
|
@busy = false
|
|
153
|
-
@busy_activity = "
|
|
158
|
+
@busy_activity = "thinking"
|
|
159
|
+
@busy_started_at = nil
|
|
160
|
+
@last_busy_elapsed = nil
|
|
161
|
+
@completion_status = nil
|
|
162
|
+
@response_arrival_until = nil
|
|
154
163
|
@queued_count = 0
|
|
155
164
|
@steered_count = 0
|
|
156
165
|
@spinner_frame_index = 0
|
|
@@ -169,6 +178,7 @@ module Kward
|
|
|
169
178
|
@transcript_viewport_rows = 0
|
|
170
179
|
@restoring_transcript = false
|
|
171
180
|
@pending_keys = []
|
|
181
|
+
@handoff_input_buffer = +"".b
|
|
172
182
|
@completion_provider = nil
|
|
173
183
|
@completion_cycle = nil
|
|
174
184
|
@completion_overlay = nil
|
|
@@ -185,12 +195,22 @@ module Kward
|
|
|
185
195
|
@file_open_dismissed_token = nil
|
|
186
196
|
@file_editor_open_status = nil
|
|
187
197
|
@file_mention_paths = nil
|
|
198
|
+
@file_mention_discovery_thread = nil
|
|
199
|
+
@file_mention_discovery_root = nil
|
|
200
|
+
@file_mention_discovery_token = nil
|
|
188
201
|
@project_browser_file_paths = nil
|
|
189
202
|
@project_browser_state = nil
|
|
190
203
|
@image_viewer_state = nil
|
|
191
204
|
@terminal_image_protocol = nil
|
|
192
205
|
@project_browser_restore_after_editor = false
|
|
193
206
|
@editor_state = nil
|
|
207
|
+
@editor_runner_state = nil
|
|
208
|
+
@editor_runner_completion = nil
|
|
209
|
+
@editor_runner_output_visible = false
|
|
210
|
+
@editor_runner_selection_anchor = nil
|
|
211
|
+
@editor_runner_selection_cursor = nil
|
|
212
|
+
@editor_runner_mouse_dragging = false
|
|
213
|
+
@editor_runner_last_click = nil
|
|
194
214
|
@editor_agent_suspended = false
|
|
195
215
|
@editor_prompt_active = false
|
|
196
216
|
@editor_prompt_input = ""
|
|
@@ -232,6 +252,7 @@ module Kward
|
|
|
232
252
|
@editor_line_numbers_source = editor_line_numbers_source
|
|
233
253
|
@diff_view = DiffViewMode.normalize(diff_view)
|
|
234
254
|
@diff_view_source = diff_view_source
|
|
255
|
+
@editor_runners_source = editor_runners_source
|
|
235
256
|
end
|
|
236
257
|
|
|
237
258
|
def update_slash_commands(commands)
|
|
@@ -439,6 +460,12 @@ module Kward
|
|
|
439
460
|
return false unless opened
|
|
440
461
|
|
|
441
462
|
run_editor
|
|
463
|
+
rescue StandardError
|
|
464
|
+
@mutex.synchronize do
|
|
465
|
+
close_editor if editor_active?
|
|
466
|
+
render_prompt_locked if @started
|
|
467
|
+
end
|
|
468
|
+
raise
|
|
442
469
|
end
|
|
443
470
|
|
|
444
471
|
def scratchpad(language = :text)
|
|
@@ -524,7 +551,8 @@ module Kward
|
|
|
524
551
|
if key.nil?
|
|
525
552
|
resized = handle_resize_locked
|
|
526
553
|
footer_refreshed = tick_footer_locked
|
|
527
|
-
|
|
554
|
+
completion_changed = tick_completion_locked
|
|
555
|
+
render_prompt_locked if resized || footer_refreshed || completion_changed
|
|
528
556
|
else
|
|
529
557
|
result = handle_key(key)
|
|
530
558
|
render_prompt_locked unless result.is_a?(String) || result == EXIT_INPUT || prompt_action_result?(result)
|
|
@@ -667,11 +695,11 @@ module Kward
|
|
|
667
695
|
end
|
|
668
696
|
end
|
|
669
697
|
|
|
670
|
-
def with_inline_terminal_handoff(&block)
|
|
671
|
-
with_terminal_handoff(inline: true, &block)
|
|
698
|
+
def with_inline_terminal_handoff(preserve_tab_keybindings: false, &block)
|
|
699
|
+
with_terminal_handoff(inline: true, preserve_tab_keybindings: preserve_tab_keybindings, &block)
|
|
672
700
|
end
|
|
673
701
|
|
|
674
|
-
def with_terminal_handoff(inline: false)
|
|
702
|
+
def with_terminal_handoff(inline: false, preserve_tab_keybindings: false)
|
|
675
703
|
start
|
|
676
704
|
input = nil
|
|
677
705
|
output = nil
|
|
@@ -687,7 +715,8 @@ module Kward
|
|
|
687
715
|
end
|
|
688
716
|
disable_editor_mouse_reporting(force: true)
|
|
689
717
|
print_output_locked(BRACKETED_PASTE_RESTORE)
|
|
690
|
-
print_output_locked(KEYBOARD_PROTOCOL_RESTORE)
|
|
718
|
+
print_output_locked(KEYBOARD_PROTOCOL_RESTORE) unless preserve_tab_keybindings
|
|
719
|
+
@handoff_input_buffer.clear
|
|
691
720
|
restore_editor_cursor_shape_locked
|
|
692
721
|
set_cursor_visible_locked(true, force: true)
|
|
693
722
|
flush_output_locked
|
|
@@ -709,6 +738,7 @@ module Kward
|
|
|
709
738
|
enter_raw_mode_locked
|
|
710
739
|
print_output_locked(KEYBOARD_PROTOCOL_ENABLE)
|
|
711
740
|
print_output_locked(BRACKETED_PASTE_ENABLE)
|
|
741
|
+
@handoff_input_buffer.clear
|
|
712
742
|
disable_editor_mouse_reporting(force: true)
|
|
713
743
|
restore_editor_cursor_shape_locked(force: true)
|
|
714
744
|
@cursor_visible = nil
|
|
@@ -802,8 +832,6 @@ module Kward
|
|
|
802
832
|
@completion_cycle = nil
|
|
803
833
|
@completion_overlay = nil
|
|
804
834
|
@prompt_label = snapshot[:prompt_label].to_s.empty? ? "You>" : snapshot[:prompt_label].to_s
|
|
805
|
-
self.composer_input = @composer.input
|
|
806
|
-
self.composer_cursor = @composer.cursor
|
|
807
835
|
@last_composer_rows = []
|
|
808
836
|
end
|
|
809
837
|
|
|
@@ -815,6 +843,11 @@ module Kward
|
|
|
815
843
|
|
|
816
844
|
def restore_editor_snapshot_locked(snapshot)
|
|
817
845
|
editor_was_active = editor_active?
|
|
846
|
+
cancel_editor_runner
|
|
847
|
+
@editor_runner_state = nil
|
|
848
|
+
@editor_runner_completion = nil
|
|
849
|
+
@editor_runner_output_visible = false
|
|
850
|
+
clear_editor_runner_selection
|
|
818
851
|
@editor_state = snapshot[:editor_state]&.dup
|
|
819
852
|
editor_is_active = editor_active?
|
|
820
853
|
|
|
@@ -839,17 +872,21 @@ module Kward
|
|
|
839
872
|
end
|
|
840
873
|
end
|
|
841
874
|
|
|
842
|
-
def begin_busy_input(message = "You>", activity: "
|
|
875
|
+
def begin_busy_input(message = "You>", activity: "thinking")
|
|
843
876
|
start
|
|
844
877
|
@mutex.synchronize do
|
|
845
878
|
@prompt_label = message.to_s
|
|
846
879
|
@busy_activity = normalize_busy_activity(activity)
|
|
880
|
+
@completion_status = nil
|
|
881
|
+
@response_arrival_until = nil
|
|
847
882
|
self.composer_input = ""
|
|
848
883
|
self.composer_cursor = 0
|
|
849
884
|
@composer.clear_attachments
|
|
850
885
|
@pending_keys.clear
|
|
851
886
|
@asking = true
|
|
852
887
|
@busy = true
|
|
888
|
+
@busy_started_at = monotonic_now
|
|
889
|
+
@last_busy_elapsed = nil
|
|
853
890
|
@queued_count = 0
|
|
854
891
|
@steered_count = 0
|
|
855
892
|
reset_spinner_locked
|
|
@@ -858,6 +895,17 @@ module Kward
|
|
|
858
895
|
end
|
|
859
896
|
end
|
|
860
897
|
|
|
898
|
+
def update_busy_activity(activity)
|
|
899
|
+
@mutex.synchronize do
|
|
900
|
+
value = normalize_busy_activity(activity)
|
|
901
|
+
return false if value == @busy_activity
|
|
902
|
+
|
|
903
|
+
@busy_activity = value
|
|
904
|
+
render_prompt_locked if @started && @asking && @busy
|
|
905
|
+
true
|
|
906
|
+
end
|
|
907
|
+
end
|
|
908
|
+
|
|
861
909
|
def set_queued_count(count)
|
|
862
910
|
@mutex.synchronize do
|
|
863
911
|
@queued_count = count.to_i
|
|
@@ -877,15 +925,18 @@ module Kward
|
|
|
877
925
|
def clear_steered_count
|
|
878
926
|
@mutex.synchronize do
|
|
879
927
|
@steered_count = 0
|
|
880
|
-
@busy_activity = "
|
|
928
|
+
@busy_activity = "thinking"
|
|
881
929
|
render_prompt_locked if @asking
|
|
882
930
|
end
|
|
883
931
|
end
|
|
884
932
|
|
|
885
933
|
def finish_busy_input
|
|
886
934
|
@mutex.synchronize do
|
|
935
|
+
@last_busy_elapsed = monotonic_now - @busy_started_at if @busy_started_at
|
|
936
|
+
@busy_started_at = nil
|
|
887
937
|
@busy = false
|
|
888
|
-
@busy_activity = "
|
|
938
|
+
@busy_activity = "thinking"
|
|
939
|
+
@response_arrival_until = nil
|
|
889
940
|
@queued_count = 0
|
|
890
941
|
@steered_count = 0
|
|
891
942
|
@asking = true
|
|
@@ -893,6 +944,24 @@ module Kward
|
|
|
893
944
|
end
|
|
894
945
|
end
|
|
895
946
|
|
|
947
|
+
def show_response_arrival
|
|
948
|
+
@mutex.synchronize do
|
|
949
|
+
@response_arrival_until = monotonic_now + RESPONSE_ARRIVAL_SECONDS
|
|
950
|
+
render_prompt_locked if @started && @asking && @busy
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
def show_completion(status)
|
|
955
|
+
@mutex.synchronize do
|
|
956
|
+
@completion_status = completion_status(status)
|
|
957
|
+
render_prompt_locked if @started && @asking
|
|
958
|
+
end
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
def filter_terminal_handoff_input(chunk)
|
|
962
|
+
@mutex.synchronize { filter_terminal_handoff_input_locked(chunk) }
|
|
963
|
+
end
|
|
964
|
+
|
|
896
965
|
def poll_input
|
|
897
966
|
key = read_key(nonblock: true)
|
|
898
967
|
@mutex.synchronize do
|
|
@@ -916,7 +985,8 @@ module Kward
|
|
|
916
985
|
resized = handle_resize_locked
|
|
917
986
|
spun = tick_spinner_locked
|
|
918
987
|
footer_refreshed = tick_footer_locked
|
|
919
|
-
|
|
988
|
+
completion_changed = tick_completion_locked
|
|
989
|
+
render_prompt_locked if resized || spun || footer_refreshed || completion_changed
|
|
920
990
|
return nil
|
|
921
991
|
end
|
|
922
992
|
|
|
@@ -1106,6 +1176,11 @@ module Kward
|
|
|
1106
1176
|
|
|
1107
1177
|
def reset_workspace_file_state_locked
|
|
1108
1178
|
@file_mention_paths = nil
|
|
1179
|
+
@file_mention_discovery_thread = nil
|
|
1180
|
+
@file_mention_discovery_root = nil
|
|
1181
|
+
@file_mention_discovery_token = nil
|
|
1182
|
+
@file_overlay_match_cache = nil
|
|
1183
|
+
@project_browser_search_cache = nil
|
|
1109
1184
|
@project_browser_file_paths = nil
|
|
1110
1185
|
@file_mention_path_entries_paths = nil
|
|
1111
1186
|
@file_mention_path_entries = nil
|
|
@@ -29,7 +29,7 @@ module Kward
|
|
|
29
29
|
{ name: "files", description: "Browse project files.", argument_hint: "" },
|
|
30
30
|
{ name: "shell", description: "Open the embedded Kward shell.", argument_hint: "" },
|
|
31
31
|
{ name: "capture", description: "Run a command with bounded transcript capture.", argument_hint: "<command>" },
|
|
32
|
-
{ name: "scratchpad", description: "Open an unsaved editor buffer.", argument_hint: "[
|
|
32
|
+
{ name: "scratchpad", description: "Open an unsaved editor buffer.", argument_hint: "[language|help]" },
|
|
33
33
|
{ name: "pty", description: "Run a command in an interactive PTY passthrough session.", argument_hint: "<command>" },
|
|
34
34
|
{ name: "tab", description: "Manage tabs.", argument_hint: "[1-n|move|close|new|name|worktree]" },
|
|
35
35
|
{ name: "worktree", description: "Manage the active tab's linked Git worktree.", argument_hint: "[activate|detach|status|merge|merge abort|remove]" },
|
data/lib/kward/prompts.rb
CHANGED
|
@@ -60,7 +60,7 @@ module Kward
|
|
|
60
60
|
|
|
61
61
|
When web tools are available, use web_search to discover sources, fetch_content for important human-readable pages, and fetch_raw for machine-readable resources such as JSON, YAML, XML, RSS, OpenAPI specs, and plain text. Prefer official or primary sources and cite or mention the URLs you relied on. Use code_search for package, GitHub repository, and source-code research.
|
|
62
62
|
|
|
63
|
-
Manage code context deliberately. Prefer context_for_task, summarize_file_structure, and read_file mode="outline"/"preview" before broad reads. Escalate to read_file mode="range" for exact lines, and use mode="full" only when focused context is insufficient. If output is compacted or replaced with a duplicate reference, use retrieve_tool_output to inspect the needed original detail. Use context_budget_stats when asked about context savings. When a material requirement is ambiguous, use ask_user_question when it is available; otherwise state the assumption.
|
|
63
|
+
Manage code context deliberately. Prefer context_for_task, summarize_file_structure, and read_file mode="outline"/"preview" before broad reads. Escalate to read_file mode="range" for exact lines, and use mode="full" only when focused context is insufficient. If output is compacted or replaced with a duplicate reference, use retrieve_tool_output to inspect the needed original detail. Use context_budget_stats when asked about context savings. When the user asks to open a file in the built-in editor and open_editor is advertised, use open_editor rather than read_file; use read_file when they ask you to inspect or display contents. Opening the editor is a user-facing action and does not itself modify or save the file. When a material requirement is ambiguous, use ask_user_question when it is available; otherwise state the assumption.
|
|
64
64
|
PROMPT
|
|
65
65
|
end
|
|
66
66
|
|
|
@@ -1,5 +1,52 @@
|
|
|
1
|
+
require "thread"
|
|
2
|
+
|
|
1
3
|
# Namespace for the Kward CLI agent runtime.
|
|
2
4
|
module Kward
|
|
5
|
+
# Retains output while a detached PTY command runs without terminal ownership.
|
|
6
|
+
class BufferedPtyOutputSink
|
|
7
|
+
attr_reader :captured_output
|
|
8
|
+
|
|
9
|
+
def initialize(max_capture_bytes:)
|
|
10
|
+
@max_capture_bytes = max_capture_bytes
|
|
11
|
+
@captured_output = +"".b
|
|
12
|
+
@truncated = false
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write(chunk)
|
|
17
|
+
value = chunk.to_s.b
|
|
18
|
+
@mutex.synchronize do
|
|
19
|
+
remaining = @max_capture_bytes - @captured_output.bytesize
|
|
20
|
+
if value.bytesize > remaining
|
|
21
|
+
@captured_output << value.byteslice(0, remaining) if remaining.positive?
|
|
22
|
+
@truncated = true
|
|
23
|
+
else
|
|
24
|
+
@captured_output << value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def flush
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def finish
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def transcript_safe?
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def pre_input_capture_only?
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def truncated?
|
|
46
|
+
@mutex.synchronize { @truncated }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
3
50
|
# Forwards PTY output immediately and optionally keeps a bounded byte copy.
|
|
4
51
|
#
|
|
5
52
|
# The runner only depends on the sink's `write` method and optional `flush`
|
|
@@ -173,10 +173,14 @@ module Kward
|
|
|
173
173
|
when "reasoning"
|
|
174
174
|
reasoning_item_blocks(item).map { |thinking| { type: "thinking", thinking: thinking } }
|
|
175
175
|
when "message"
|
|
176
|
-
next [] if ToolCall.value(item, :phase).to_s == "commentary"
|
|
177
|
-
|
|
178
176
|
text = response_message_item_text(item)
|
|
179
|
-
|
|
177
|
+
next [] if text.empty?
|
|
178
|
+
|
|
179
|
+
if ToolCall.value(item, :phase).to_s == "commentary"
|
|
180
|
+
[{ type: "thinking", thinking: text }]
|
|
181
|
+
else
|
|
182
|
+
[{ type: "text", text: text }]
|
|
183
|
+
end
|
|
180
184
|
else
|
|
181
185
|
[]
|
|
182
186
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Namespace for the Kward CLI agent runtime.
|
|
2
|
+
module Kward
|
|
3
|
+
# Names, aliases, and display paths accepted by the scratchpad command.
|
|
4
|
+
module ScratchpadLanguages
|
|
5
|
+
LANGUAGES = {
|
|
6
|
+
text: { aliases: %w[text txt], display_path: "scratchpad.txt" },
|
|
7
|
+
ruby: { aliases: %w[ruby rb], display_path: "scratchpad.rb", runner: :ruby },
|
|
8
|
+
erb: { aliases: %w[erb], display_path: "scratchpad.erb" },
|
|
9
|
+
crystal: { aliases: %w[crystal cr], display_path: "scratchpad.cr", runner: :crystal },
|
|
10
|
+
elixir: { aliases: %w[elixir ex exs], display_path: "scratchpad.ex", runner: :elixir },
|
|
11
|
+
julia: { aliases: %w[julia jl], display_path: "scratchpad.jl", runner: :julia },
|
|
12
|
+
javascript: { aliases: %w[javascript js jsx mjs cjs], display_path: "scratchpad.js", runner: :node },
|
|
13
|
+
typescript: { aliases: %w[typescript ts tsx], display_path: "scratchpad.ts", runner: :node },
|
|
14
|
+
json: { aliases: %w[json], display_path: "scratchpad.json" },
|
|
15
|
+
markdown: { aliases: %w[markdown md], display_path: "scratchpad.md" },
|
|
16
|
+
yaml: { aliases: %w[yaml yml], display_path: "scratchpad.yml" },
|
|
17
|
+
shell: { aliases: %w[shell sh bash zsh fish], display_path: "scratchpad.sh", runner: :shell },
|
|
18
|
+
makefile: { aliases: %w[makefile make mk], display_path: "scratchpad.mk" },
|
|
19
|
+
html: { aliases: %w[html htm], display_path: "scratchpad.html" },
|
|
20
|
+
css: { aliases: %w[css], display_path: "scratchpad.css" },
|
|
21
|
+
scss: { aliases: %w[scss], display_path: "scratchpad.scss" },
|
|
22
|
+
python: { aliases: %w[python py pyw], display_path: "scratchpad.py", runner: :python },
|
|
23
|
+
go: { aliases: %w[go], display_path: "scratchpad.go", runner: :go },
|
|
24
|
+
rust: { aliases: %w[rust rs], display_path: "scratchpad.rs" },
|
|
25
|
+
java: { aliases: %w[java], display_path: "scratchpad.java" },
|
|
26
|
+
csharp: { aliases: %w[csharp cs c#], display_path: "scratchpad.cs" },
|
|
27
|
+
c: { aliases: %w[c], display_path: "scratchpad.c" },
|
|
28
|
+
cpp: { aliases: %w[cpp cc cxx hpp hh hxx c++], display_path: "scratchpad.cpp" },
|
|
29
|
+
swift: { aliases: %w[swift], display_path: "scratchpad.swift", runner: :swift },
|
|
30
|
+
kotlin: { aliases: %w[kotlin kt kts], display_path: "scratchpad.kt" },
|
|
31
|
+
lua: { aliases: %w[lua], display_path: "scratchpad.lua", runner: :lua },
|
|
32
|
+
sql: { aliases: %w[sql], display_path: "scratchpad.sql" }
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
ALIASES = LANGUAGES.each_with_object({}) do |(language, definition), aliases|
|
|
36
|
+
([language.to_s] + definition.fetch(:aliases)).each do |name|
|
|
37
|
+
aliases[name] = language
|
|
38
|
+
end
|
|
39
|
+
end.freeze
|
|
40
|
+
|
|
41
|
+
module_function
|
|
42
|
+
|
|
43
|
+
def normalize(language)
|
|
44
|
+
value = language.to_s.strip.downcase
|
|
45
|
+
return :text if value.empty?
|
|
46
|
+
|
|
47
|
+
ALIASES.fetch(value) do
|
|
48
|
+
raise ArgumentError, "Unknown scratchpad language #{language.inspect}. Use /scratchpad help for supported languages."
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def display_path(language)
|
|
53
|
+
LANGUAGES.fetch(normalize(language)).fetch(:display_path)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def runner(language)
|
|
57
|
+
LANGUAGES.fetch(normalize(language))[:runner]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def runnable?(language)
|
|
61
|
+
!runner(language).nil?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def help_text
|
|
65
|
+
lines = ["Supported scratchpad languages:"]
|
|
66
|
+
LANGUAGES.each do |language, definition|
|
|
67
|
+
aliases = definition.fetch(:aliases).reject { |name| name == language.to_s }
|
|
68
|
+
suffix = aliases.empty? ? "" : " (#{aliases.join(", ")})"
|
|
69
|
+
lines << " #{language}#{suffix}"
|
|
70
|
+
end
|
|
71
|
+
lines.join("\n")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|