kward 0.75.0 → 0.77.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/ci.yml +8 -1
- data/CHANGELOG.md +48 -0
- data/Gemfile.lock +2 -2
- data/README.md +12 -4
- data/doc/agent-tools.md +0 -3
- data/doc/api.md +4 -0
- data/doc/composer.md +219 -0
- data/doc/configuration.md +87 -27
- data/doc/editor.md +18 -2
- data/doc/files.md +1 -1
- data/doc/getting-started.md +4 -0
- data/doc/git.md +2 -2
- data/doc/lifecycle-hooks.md +2 -14
- data/doc/pan.md +167 -0
- data/doc/plugins.md +1 -0
- data/doc/rpc.md +3 -22
- data/doc/security.md +180 -0
- data/doc/usage.md +15 -32
- data/lib/kward/agent.rb +5 -0
- data/lib/kward/ansi.rb +2 -2
- data/lib/kward/cancellation.rb +2 -2
- data/lib/kward/cli/commands.rb +1 -3
- data/lib/kward/cli/interactive_turn.rb +3 -17
- data/lib/kward/cli/plugins.rb +2 -7
- data/lib/kward/cli/prompt_interface.rb +10 -17
- data/lib/kward/cli/rendering.rb +7 -3
- data/lib/kward/cli/runtime_helpers.rb +0 -38
- data/lib/kward/cli/sessions.rb +14 -5
- data/lib/kward/cli/settings.rb +22 -0
- data/lib/kward/cli/slash_commands.rb +1 -561
- data/lib/kward/cli/tabs.rb +14 -2
- data/lib/kward/cli.rb +1 -17
- data/lib/kward/cli_transcript_formatter.rb +21 -11
- data/lib/kward/compactor.rb +3 -3
- data/lib/kward/config_files.rb +21 -0
- data/lib/kward/ekwsh.rb +0 -4
- data/lib/kward/events.rb +1 -0
- data/lib/kward/hooks/catalog.rb +0 -5
- data/lib/kward/image_attachments.rb +1 -1
- data/lib/kward/model/client.rb +12 -9
- data/lib/kward/model/model_info.rb +14 -5
- data/lib/kward/model/stream_parser.rb +12 -12
- data/lib/kward/pan/index.html.erb +460 -64
- data/lib/kward/pan/kward_logo.png +0 -0
- data/lib/kward/pan/server.rb +250 -23
- data/lib/kward/plugin_registry.rb +20 -0
- data/lib/kward/prompt_interface/editor/auto_indent.rb +31 -23
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +72 -3
- data/lib/kward/prompt_interface/layout.rb +0 -4
- data/lib/kward/prompt_interface/project_browser.rb +40 -1
- data/lib/kward/prompt_interface/runtime_state.rb +4 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +0 -1
- data/lib/kward/prompt_interface/transcript_renderer.rb +1 -13
- data/lib/kward/prompt_interface.rb +18 -1
- data/lib/kward/prompts/commands.rb +1 -3
- data/lib/kward/prompts.rb +20 -0
- data/lib/kward/rpc/server.rb +14 -42
- data/lib/kward/rpc/session_manager.rb +77 -25
- data/lib/kward/rpc/tool_event_normalizer.rb +2 -0
- data/lib/kward/rpc/tool_metadata.rb +23 -0
- data/lib/kward/rpc/transcript_normalizer.rb +14 -10
- data/lib/kward/session_store.rb +55 -16
- data/lib/kward/skills/registry.rb +21 -1
- data/lib/kward/tools/base.rb +14 -0
- data/lib/kward/tools/registry.rb +11 -16
- data/lib/kward/tools/search/code.rb +1 -1
- data/lib/kward/tools/search/web.rb +1 -1
- data/lib/kward/tools/tool_call.rb +0 -4
- data/lib/kward/transcript_export.rb +1 -1
- data/lib/kward/update_check.rb +12 -7
- data/lib/kward/version.rb +1 -1
- data/templates/default/kward_navigation.rb +5 -1
- data/templates/default/layout/html/layout.erb +0 -2
- metadata +5 -12
- data/lib/kward/workers/git_guard.rb +0 -93
- data/lib/kward/workers/job.rb +0 -99
- data/lib/kward/workers/live_view.rb +0 -49
- data/lib/kward/workers/manager.rb +0 -328
- data/lib/kward/workers/queue_runner.rb +0 -166
- data/lib/kward/workers/queue_store.rb +0 -112
- data/lib/kward/workers/store.rb +0 -72
- data/lib/kward/workers/tool_policy.rb +0 -23
- data/lib/kward/workers/worker.rb +0 -82
- data/lib/kward/workers/write_lock.rb +0 -38
- data/lib/kward/workers.rb +0 -10
|
@@ -452,9 +452,10 @@ module Kward
|
|
|
452
452
|
return true if command.match?(/\A\d*g\z/)
|
|
453
453
|
return true if command.match?(/\A\d*g[uU~]\z/)
|
|
454
454
|
return true if command.match?(/\A\d*z\z/)
|
|
455
|
-
return true if command.match?(/\A\d*[cdy]\d
|
|
456
|
-
return true if command.match?(/\A\d*[cdy]\d*
|
|
457
|
-
return true if command.match?(/\A\d*[cdy]\d*[
|
|
455
|
+
return true if command.match?(/\A\d*[=cdy](?:[1-9]\d*)?\z/)
|
|
456
|
+
return true if command.match?(/\A\d*[=cdy]\d*g\z/)
|
|
457
|
+
return true if command.match?(/\A\d*[=cdy]\d*[ai]\z/)
|
|
458
|
+
return true if command.match?(/\A\d*[=cdy]\d*[fFtT]\z/)
|
|
458
459
|
return true if command.match?(/\A\d*[fFtT]\z/)
|
|
459
460
|
return true if command.match?(/\A\d*r\z/)
|
|
460
461
|
return true if command.match?(/\Am\z/)
|
|
@@ -635,6 +636,10 @@ module Kward
|
|
|
635
636
|
when "<<"
|
|
636
637
|
vibe_indent_lines(count, :left)
|
|
637
638
|
vibe_remember_change(command)
|
|
639
|
+
when "=="
|
|
640
|
+
vibe_reindent_lines(count, command)
|
|
641
|
+
when /\A=(.+)\z/
|
|
642
|
+
vibe_reindent_operator(Regexp.last_match(1), count, command)
|
|
638
643
|
when "p"
|
|
639
644
|
vibe_active_register_linewise? ? vibe_paste_line(:below, original_command) : vibe_paste_after(original_command)
|
|
640
645
|
when "P"
|
|
@@ -704,6 +709,8 @@ module Kward
|
|
|
704
709
|
vibe_indent_visual_selection(:right)
|
|
705
710
|
when "<"
|
|
706
711
|
vibe_indent_visual_selection(:left)
|
|
712
|
+
when "="
|
|
713
|
+
vibe_reindent_visual_selection
|
|
707
714
|
when "J"
|
|
708
715
|
vibe_join_visual_selection
|
|
709
716
|
when "~"
|
|
@@ -1077,6 +1084,18 @@ module Kward
|
|
|
1077
1084
|
vibe_cancel_visual_mode
|
|
1078
1085
|
end
|
|
1079
1086
|
|
|
1087
|
+
def vibe_reindent_visual_selection
|
|
1088
|
+
ranges = @editor_state.selection_ranges
|
|
1089
|
+
return false if ranges.empty?
|
|
1090
|
+
|
|
1091
|
+
start_index = ranges.map(&:first).min
|
|
1092
|
+
end_index = ranges.map(&:last).max
|
|
1093
|
+
start_line, = @editor_state.cursor_line_and_column_for(start_index)
|
|
1094
|
+
end_line, = @editor_state.cursor_line_and_column_for([end_index - 1, start_index].max)
|
|
1095
|
+
vibe_reindent_line_range(start_line, end_line)
|
|
1096
|
+
vibe_cancel_visual_mode
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1080
1099
|
def vibe_count_and_body(command)
|
|
1081
1100
|
return [0, "0"] if command == "0"
|
|
1082
1101
|
|
|
@@ -1284,6 +1303,56 @@ module Kward
|
|
|
1284
1303
|
true
|
|
1285
1304
|
end
|
|
1286
1305
|
|
|
1306
|
+
def vibe_reindent_lines(count, command = nil)
|
|
1307
|
+
line, = @editor_state.cursor_line_and_column
|
|
1308
|
+
vibe_reindent_line_range(line, line + count - 1, command)
|
|
1309
|
+
end
|
|
1310
|
+
|
|
1311
|
+
def vibe_reindent_operator(motion, count, command = nil)
|
|
1312
|
+
motion_count, motion = vibe_count_and_body(motion)
|
|
1313
|
+
return vibe_reindent_to_line(motion_count, command) if motion == "G"
|
|
1314
|
+
return vibe_reindent_to_line(motion_count.positive? ? motion_count : 1, command) if motion == "gg"
|
|
1315
|
+
return vibe_reindent_lines(count, command) if motion == "=" || motion == "g_"
|
|
1316
|
+
|
|
1317
|
+
count *= motion_count if motion_count.positive?
|
|
1318
|
+
target = vibe_operator_target(motion, count)
|
|
1319
|
+
return false unless target
|
|
1320
|
+
|
|
1321
|
+
vibe_reindent_target(target, motion, command)
|
|
1322
|
+
end
|
|
1323
|
+
|
|
1324
|
+
def vibe_reindent_to_line(line_count, command)
|
|
1325
|
+
line, = @editor_state.cursor_line_and_column
|
|
1326
|
+
target_line = line_count.positive? ? line_count - 1 : @editor_state.lines.length - 1
|
|
1327
|
+
vibe_reindent_line_range(line, target_line, command)
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
def vibe_reindent_target(target, motion, command)
|
|
1331
|
+
start_line, = @editor_state.cursor_line_and_column_for(target.start_index)
|
|
1332
|
+
end_line = if motion.match?(/\A[ai].\z/)
|
|
1333
|
+
@editor_state.cursor_line_and_column_for([target.end_index - 1, target.start_index].max).first
|
|
1334
|
+
else
|
|
1335
|
+
@editor_state.cursor_line_and_column.first
|
|
1336
|
+
end
|
|
1337
|
+
vibe_reindent_line_range(start_line, end_line, command)
|
|
1338
|
+
end
|
|
1339
|
+
|
|
1340
|
+
def vibe_reindent_line_range(first_line, last_line, command = nil)
|
|
1341
|
+
start_line, end_line = [first_line, last_line].minmax
|
|
1342
|
+
end_line = [end_line, @editor_state.lines.length - 1].min
|
|
1343
|
+
vibe_record_undo do
|
|
1344
|
+
(start_line..end_line).each do |line_index|
|
|
1345
|
+
next if @editor_state.lines[line_index].to_s.strip.empty?
|
|
1346
|
+
|
|
1347
|
+
editor_reindent_line(line_index, editor_expected_indent_for_line(line_index))
|
|
1348
|
+
end
|
|
1349
|
+
end
|
|
1350
|
+
@editor_state.move_to_line_first_non_blank(end_line)
|
|
1351
|
+
@editor_state.status = "Reindented #{end_line - start_line + 1} line#{end_line == start_line ? "" : "s"}"
|
|
1352
|
+
vibe_remember_change(command) if command
|
|
1353
|
+
true
|
|
1354
|
+
end
|
|
1355
|
+
|
|
1287
1356
|
def vibe_change_lines(count, command = nil)
|
|
1288
1357
|
start_index, end_index = vibe_linewise_change_range(count)
|
|
1289
1358
|
@editor_state.copy_range(start_index, end_index)
|
|
@@ -10,10 +10,6 @@ module Kward
|
|
|
10
10
|
@banner.rows(width, message: message)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def banner_logo_rows
|
|
14
|
-
@banner.logo_rows(screen_width)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
13
|
def transcript_redraw_row_count(height = screen_height)
|
|
18
14
|
[[@transcript_viewport_rows, transcript_bottom_row(height)].max, height].min
|
|
19
15
|
end
|
|
@@ -12,6 +12,28 @@ module Kward
|
|
|
12
12
|
PROJECT_BROWSER_ROOT = "".freeze
|
|
13
13
|
PROJECT_BROWSER_RESULT_LIMIT = 200
|
|
14
14
|
PROJECT_BROWSER_STATE_VERSION = 1
|
|
15
|
+
PROJECT_BROWSER_FILENAME_ICONS = {
|
|
16
|
+
".gitignore" => "",
|
|
17
|
+
"Gemfile" => "",
|
|
18
|
+
"Rakefile" => "",
|
|
19
|
+
"README" => "",
|
|
20
|
+
"README.md" => "",
|
|
21
|
+
"package.json" => ""
|
|
22
|
+
}.freeze
|
|
23
|
+
PROJECT_BROWSER_EXTENSION_ICONS = {
|
|
24
|
+
"css" => "",
|
|
25
|
+
"html" => "",
|
|
26
|
+
"js" => "",
|
|
27
|
+
"json" => "",
|
|
28
|
+
"md" => "",
|
|
29
|
+
"rb" => "",
|
|
30
|
+
"sh" => "",
|
|
31
|
+
"ts" => "",
|
|
32
|
+
"yaml" => "",
|
|
33
|
+
"yml" => ""
|
|
34
|
+
}.freeze
|
|
35
|
+
PROJECT_BROWSER_DIRECTORY_ICON = ""
|
|
36
|
+
PROJECT_BROWSER_FILE_ICON = ""
|
|
15
37
|
|
|
16
38
|
def open_project_browser
|
|
17
39
|
@mutex.synchronize do
|
|
@@ -399,8 +421,25 @@ module Kward
|
|
|
399
421
|
else
|
|
400
422
|
" "
|
|
401
423
|
end
|
|
424
|
+
icon = project_browser_icon(row)
|
|
402
425
|
suffix = row[:directory] ? "/" : ""
|
|
403
|
-
"#{indent}#{marker}#{row[:name]}#{suffix}"
|
|
426
|
+
"#{indent}#{marker}#{icon}#{row[:name]}#{suffix}"
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def project_browser_icon(row)
|
|
430
|
+
return "" unless @project_browser_icon_theme == "nerd-font"
|
|
431
|
+
|
|
432
|
+
icon = if row[:directory]
|
|
433
|
+
PROJECT_BROWSER_DIRECTORY_ICON
|
|
434
|
+
else
|
|
435
|
+
project_browser_file_icon(row[:path])
|
|
436
|
+
end
|
|
437
|
+
"#{icon} "
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def project_browser_file_icon(path)
|
|
441
|
+
name = File.basename(path.to_s)
|
|
442
|
+
PROJECT_BROWSER_FILENAME_ICONS[name] || PROJECT_BROWSER_EXTENSION_ICONS[File.extname(name).delete_prefix(".").downcase] || PROJECT_BROWSER_FILE_ICON
|
|
404
443
|
end
|
|
405
444
|
|
|
406
445
|
def saved_project_browser_state
|
|
@@ -74,6 +74,10 @@ module Kward
|
|
|
74
74
|
ANSI.colorize(text, *styles, enabled: @color_enabled)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
def normalize_project_browser_icon_theme(value)
|
|
78
|
+
value.to_s == "nerd-font" ? "nerd-font" : "off"
|
|
79
|
+
end
|
|
80
|
+
|
|
77
81
|
def normalize_tab_keybindings(value)
|
|
78
82
|
text = value.to_s.downcase
|
|
79
83
|
return "ctrl" if text == "ctrl"
|
|
@@ -15,7 +15,7 @@ module Kward
|
|
|
15
15
|
@stream_state.start_block(label)
|
|
16
16
|
end
|
|
17
17
|
write_transcript_text_locked(delta) unless delta.empty?
|
|
18
|
-
write_transcript_text_locked("\n") if finish && @stream_state.block
|
|
18
|
+
write_transcript_text_locked("\n") if finish && @stream_state.block && !@transcript_buffer.end_with?("\n")
|
|
19
19
|
@stream_state.finish_block if finish
|
|
20
20
|
restore_composer_cursor_locked unless @restoring_transcript
|
|
21
21
|
end
|
|
@@ -40,10 +40,6 @@ module Kward
|
|
|
40
40
|
@transcript_buffer.append(text.to_s)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
def invalidate_transcript_display_rows_cache
|
|
44
|
-
@transcript_buffer.invalidate_display_rows_cache
|
|
45
|
-
end
|
|
46
|
-
|
|
47
43
|
def ensure_transcript_block_separator_locked
|
|
48
44
|
return if @transcript_buffer.empty? || @transcript_buffer.end_with?("\n\n")
|
|
49
45
|
|
|
@@ -65,10 +61,6 @@ module Kward
|
|
|
65
61
|
@output_io.print(terminal_newlines(rows.join("\n")))
|
|
66
62
|
end
|
|
67
63
|
|
|
68
|
-
def transcript_viewport_text(row_count, width)
|
|
69
|
-
transcript_viewport_rows(row_count, width).join("\n")
|
|
70
|
-
end
|
|
71
|
-
|
|
72
64
|
def transcript_viewport_rows(row_count, width)
|
|
73
65
|
return [] unless row_count.positive?
|
|
74
66
|
|
|
@@ -89,10 +81,6 @@ module Kward
|
|
|
89
81
|
@transcript_buffer.display_rows(width)
|
|
90
82
|
end
|
|
91
83
|
|
|
92
|
-
def transcript_text_display_rows(width)
|
|
93
|
-
@transcript_buffer.text_display_rows(width)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
84
|
def reset_stream_position_from_transcript_locked(width = screen_width)
|
|
97
85
|
@stream_state.reset_position_from_rows(transcript_display_rows(width), width)
|
|
98
86
|
end
|
|
@@ -126,7 +126,7 @@ module Kward
|
|
|
126
126
|
end
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
def initialize(input: $stdin, output: $stdout, slash_commands: [], overlay_settings: nil, footer: nil, composer_status: nil, busy_help: true, attachment_badges: nil, attachment_parser: nil, banner_message: nil, tab_keybindings: nil, prompt_history: nil, 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)
|
|
129
|
+
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, 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)
|
|
130
130
|
@input_io = input
|
|
131
131
|
@output_io = output
|
|
132
132
|
@reader = TTY::Reader.new(input: input, output: output, interrupt: :error)
|
|
@@ -186,6 +186,7 @@ module Kward
|
|
|
186
186
|
@editor_bar_cursor_active = false
|
|
187
187
|
@synchronized_output_depth = 0
|
|
188
188
|
@overlay_settings = normalize_overlay_settings(overlay_settings)
|
|
189
|
+
@project_browser_icon_theme = normalize_project_browser_icon_theme(project_browser_icon_theme)
|
|
189
190
|
@footer = footer
|
|
190
191
|
@composer_status = composer_status
|
|
191
192
|
@busy_help = busy_help
|
|
@@ -211,6 +212,15 @@ module Kward
|
|
|
211
212
|
@diff_view_source = diff_view_source
|
|
212
213
|
end
|
|
213
214
|
|
|
215
|
+
def update_slash_commands(commands)
|
|
216
|
+
@mutex.synchronize do
|
|
217
|
+
@slash_commands = normalize_slash_commands(commands)
|
|
218
|
+
reset_slash_selection
|
|
219
|
+
@slash_overlay_dismissed_input = nil
|
|
220
|
+
render_prompt_locked if @started
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
214
224
|
def start(render: true)
|
|
215
225
|
@mutex.synchronize do
|
|
216
226
|
return if @started
|
|
@@ -661,6 +671,13 @@ module Kward
|
|
|
661
671
|
end
|
|
662
672
|
end
|
|
663
673
|
|
|
674
|
+
def update_project_browser_icon_theme(theme)
|
|
675
|
+
@mutex.synchronize do
|
|
676
|
+
@project_browser_icon_theme = normalize_project_browser_icon_theme(theme)
|
|
677
|
+
render_prompt_locked if @started && @asking && project_browser_visible?
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
|
|
664
681
|
def begin_busy_input(message = "You>", activity: "streaming")
|
|
665
682
|
start
|
|
666
683
|
@mutex.synchronize do
|
|
@@ -19,13 +19,11 @@ module Kward
|
|
|
19
19
|
{ name: "export", description: "Export the current session as Markdown.", argument_hint: "[path]" },
|
|
20
20
|
{ name: "compact", description: "Compact the current conversation context.", argument_hint: "[instructions]" },
|
|
21
21
|
{ name: "redraw", description: "Refresh the visible terminal.", argument_hint: "" },
|
|
22
|
-
{ name: "settings", description: "Configure
|
|
22
|
+
{ name: "settings", description: "Configure Kward interactively.", argument_hint: "" },
|
|
23
23
|
{ name: "login", description: "Log in with an OAuth provider.", argument_hint: "" },
|
|
24
24
|
{ name: "model", description: "Select the default model.", argument_hint: "" },
|
|
25
25
|
{ name: "reasoning", description: "Select reasoning effort.", argument_hint: "" },
|
|
26
26
|
{ name: "reload", description: "Reload installed plugins.", argument_hint: "" },
|
|
27
|
-
{ name: "workers", description: "Open the worker pipeline.", argument_hint: "[new|do <task>]" },
|
|
28
|
-
{ name: "queue", description: "Manage the tab-backed worker queue.", argument_hint: "[add|list|open|run|suspend|resume]" },
|
|
29
27
|
{ name: "git", description: "Review uncommitted changes and commit them.", argument_hint: "" },
|
|
30
28
|
{ name: "diff", description: "Open the file changes recorded in this session.", argument_hint: "" },
|
|
31
29
|
{ name: "files", description: "Browse project files.", argument_hint: "" },
|
data/lib/kward/prompts.rb
CHANGED
|
@@ -3,9 +3,25 @@ require_relative "config_files"
|
|
|
3
3
|
# Namespace for the Kward CLI agent runtime.
|
|
4
4
|
module Kward
|
|
5
5
|
# System prompt assembly from config, workspace instructions, memory, and plugins.
|
|
6
|
+
#
|
|
7
|
+
# Call {system_message} for the provider-ready message or {prompt_sections}
|
|
8
|
+
# when a frontend needs to inspect the labeled sources separately.
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
6
11
|
module Prompts
|
|
7
12
|
module_function
|
|
8
13
|
|
|
14
|
+
# Builds the system message sent to a model provider.
|
|
15
|
+
#
|
|
16
|
+
# @param workspace_root [String] active workspace root
|
|
17
|
+
# @param include_workspace_personality [Boolean] include persona and plugin context
|
|
18
|
+
# @param model [String, nil] model used for persona matching
|
|
19
|
+
# @param reasoning_effort [String, nil] reasoning effort used for persona matching
|
|
20
|
+
# @param now [Time] time used for persona modifiers
|
|
21
|
+
# @param memory_context [String, nil] retrieved memory text
|
|
22
|
+
# @param plugin_context [String, nil] trusted plugin-provided instructions
|
|
23
|
+
# @return [Hash] role/content system message
|
|
24
|
+
# @api public
|
|
9
25
|
def system_message(workspace_root: Dir.pwd, include_workspace_personality: true, model: nil, reasoning_effort: nil, now: Time.now, memory_context: nil, plugin_context: nil)
|
|
10
26
|
{
|
|
11
27
|
role: "system",
|
|
@@ -17,6 +33,10 @@ module Kward
|
|
|
17
33
|
prompt_sections(workspace_root: workspace_root, include_workspace_personality: include_workspace_personality, model: model, reasoning_effort: reasoning_effort, now: now, memory_context: memory_context, plugin_context: plugin_context).map { |section| section[:content] }
|
|
18
34
|
end
|
|
19
35
|
|
|
36
|
+
# Returns labeled prompt sections for inspection by CLI and RPC frontends.
|
|
37
|
+
#
|
|
38
|
+
# @return [Array<Hash>] section hashes with label, content, and optional source
|
|
39
|
+
# @api public
|
|
20
40
|
def prompt_sections(workspace_root: Dir.pwd, include_workspace_personality: true, model: nil, reasoning_effort: nil, now: Time.now, memory_context: nil, plugin_context: nil)
|
|
21
41
|
sections = [prompt_section("Built-in system prompt", base_prompt)]
|
|
22
42
|
sections << prompt_section(config_agents_prompt_label, config_agents_prompt, source: config_agents_prompt_source)
|
data/lib/kward/rpc/server.rb
CHANGED
|
@@ -5,7 +5,6 @@ require_relative "../memory/manager"
|
|
|
5
5
|
require_relative "../plugin_registry"
|
|
6
6
|
require_relative "../prompts/commands"
|
|
7
7
|
require_relative "../tools/registry"
|
|
8
|
-
require_relative "../workers"
|
|
9
8
|
require_relative "../workspace"
|
|
10
9
|
require_relative "../telemetry/logger"
|
|
11
10
|
require_relative "../telemetry/stats"
|
|
@@ -33,6 +32,7 @@ module Kward
|
|
|
33
32
|
# `SessionManager`, `AuthManager`, and `ConfigManager`. When adding an RPC
|
|
34
33
|
# feature, update dispatch, capabilities, docs, and tests together so clients
|
|
35
34
|
# can trust `initialize` as the source of supported behavior.
|
|
35
|
+
# @api public
|
|
36
36
|
class Server
|
|
37
37
|
PROTOCOL_VERSION = 1
|
|
38
38
|
JSONRPC_VERSION = "2.0"
|
|
@@ -70,7 +70,6 @@ module Kward
|
|
|
70
70
|
"memory/forget", "memory/promote", "memory/relax", "memory/inspect",
|
|
71
71
|
"memory/why", "memory/summarize"
|
|
72
72
|
].freeze
|
|
73
|
-
WORKER_METHODS = ["workers/list", "workers/show"].freeze
|
|
74
73
|
COMMAND_METHODS = ["commands/list", "commands/run"].freeze
|
|
75
74
|
STARTUP_RESOURCE_METHODS = ["resources/startup"].freeze
|
|
76
75
|
CONFIG_METHODS = ["config/read", "config/update"].freeze
|
|
@@ -98,7 +97,6 @@ module Kward
|
|
|
98
97
|
runtime_settings: RUNTIME_SETTING_METHODS,
|
|
99
98
|
auth: AUTH_METHODS,
|
|
100
99
|
memory: MEMORY_METHODS,
|
|
101
|
-
workers: WORKER_METHODS,
|
|
102
100
|
commands: COMMAND_METHODS,
|
|
103
101
|
startup_resources: STARTUP_RESOURCE_METHODS,
|
|
104
102
|
config: CONFIG_METHODS,
|
|
@@ -110,15 +108,18 @@ module Kward
|
|
|
110
108
|
RPC_METHODS = METHOD_GROUPS.values.flatten.freeze
|
|
111
109
|
|
|
112
110
|
# Creates the RPC server and its stateful managers.
|
|
113
|
-
|
|
111
|
+
#
|
|
112
|
+
# @param input [IO] framed JSON-RPC input stream
|
|
113
|
+
# @param output [IO] framed JSON-RPC output stream
|
|
114
|
+
# @param error_output [IO, nil] redacted diagnostic stream
|
|
115
|
+
# @param client [Client] model-provider client shared by sessions
|
|
116
|
+
def initialize(input: $stdin, output: $stdout, error_output: $stderr, client: Client.new)
|
|
114
117
|
@transport = Transport.new(input: input, output: output)
|
|
115
118
|
@error_output = error_output
|
|
116
119
|
@client = client
|
|
117
120
|
@config_manager = ConfigManager.new
|
|
118
121
|
@session_manager = SessionManager.new(server: self, client: client, config_manager: @config_manager)
|
|
119
122
|
@auth_manager = AuthManager.new(server: self, config_manager: @config_manager)
|
|
120
|
-
@worker_store = Workers::Store.new
|
|
121
|
-
@experimental_workers = experimental_workers
|
|
122
123
|
@shutdown = false
|
|
123
124
|
end
|
|
124
125
|
|
|
@@ -162,6 +163,10 @@ module Kward
|
|
|
162
163
|
})
|
|
163
164
|
end
|
|
164
165
|
|
|
166
|
+
# Writes a redacted server diagnostic without exposing error payload data.
|
|
167
|
+
#
|
|
168
|
+
# @param error [Exception]
|
|
169
|
+
# @return [void]
|
|
165
170
|
def log_error(error)
|
|
166
171
|
@error_output.puts("Kward RPC error: #{Redactor.redact_string(error.message)}") if @error_output
|
|
167
172
|
end
|
|
@@ -277,12 +282,6 @@ module Kward
|
|
|
277
282
|
@session_manager.memory_why(session_id: params["sessionId"])
|
|
278
283
|
when MEMORY_METHODS[13]
|
|
279
284
|
@session_manager.memory_summarize(session_id: params.fetch("sessionId"))
|
|
280
|
-
when WORKER_METHODS[0]
|
|
281
|
-
require_experimental_workers!
|
|
282
|
-
workers_list(params)
|
|
283
|
-
when WORKER_METHODS[1]
|
|
284
|
-
require_experimental_workers!
|
|
285
|
-
workers_show(params)
|
|
286
285
|
when AUTH_METHODS[0]
|
|
287
286
|
@auth_manager.status
|
|
288
287
|
when AUTH_METHODS[1]
|
|
@@ -426,10 +425,10 @@ module Kward
|
|
|
426
425
|
events: {
|
|
427
426
|
notification: TURN_EVENT_NOTIFICATION,
|
|
428
427
|
assistantText: "assistantDelta",
|
|
429
|
-
reasoning: { start: false, delta: true, end: false },
|
|
428
|
+
reasoning: { start: false, delta: true, boundary: true, end: false },
|
|
430
429
|
modelRetry: { supported: true, event: "modelRetry" },
|
|
431
430
|
steering: { supported: @session_manager.in_flight_steer_supported?, event: "turnSteered", mode: @session_manager.in_flight_steer_supported? ? "native" : "unsupported" },
|
|
432
|
-
tools: { call: true, update: true, result: true, normalizedMetadata: true, diffs: true, changedFiles: true, workspaceGuardrails: workspace_guardrails_enabled?, focusedContext: true, contextBudgetStats: true },
|
|
431
|
+
tools: { call: true, update: true, result: true, normalizedMetadata: true, diffs: true, firstChangedLine: true, changedFiles: true, workspaceGuardrails: workspace_guardrails_enabled?, focusedContext: true, contextBudgetStats: true },
|
|
433
432
|
errors: true,
|
|
434
433
|
sessionUpdates: false
|
|
435
434
|
},
|
|
@@ -484,8 +483,7 @@ module Kward
|
|
|
484
483
|
logout: true
|
|
485
484
|
},
|
|
486
485
|
memory: { supported: true, optIn: true, defaultEnabled: false, autoSummaryDefaultEnabled: false, promptInjection: "interactive", storage: { core: "json", soft: "jsonl", events: "jsonl" }, methods: MEMORY_METHODS },
|
|
487
|
-
|
|
488
|
-
stability: { protocol: "stable", compatibility: "additive-fields-unless-protocol-version-changes", experimentalCapabilities: ["workers"] },
|
|
486
|
+
stability: { protocol: "stable", compatibility: "additive-fields-unless-protocol-version-changes", experimentalCapabilities: [] },
|
|
489
487
|
commands: { supported: true, methods: COMMAND_METHODS, method: COMMAND_METHODS[0], runMethod: COMMAND_METHODS[1], sources: ["builtin", "prompt", "skill", "plugin"], executableSources: ["builtin", "plugin"] },
|
|
490
488
|
mcp: {
|
|
491
489
|
supported: true,
|
|
@@ -543,18 +541,6 @@ module Kward
|
|
|
543
541
|
}
|
|
544
542
|
end
|
|
545
543
|
|
|
546
|
-
def workers_capability
|
|
547
|
-
return { supported: false, reason: "experimentalWorkersFlagRequired", flag: "--experimental-workers" } unless @experimental_workers
|
|
548
|
-
|
|
549
|
-
{ supported: true, methods: WORKER_METHODS, roles: ["implementation", "request"], statuses: Workers::Worker::STATUSES, transcriptStorage: "sessions", metadataStorage: "json" }
|
|
550
|
-
end
|
|
551
|
-
|
|
552
|
-
def require_experimental_workers!
|
|
553
|
-
return if @experimental_workers
|
|
554
|
-
|
|
555
|
-
raise NoMethodError, "workers require --experimental-workers"
|
|
556
|
-
end
|
|
557
|
-
|
|
558
544
|
def workspace_info(root)
|
|
559
545
|
root = @session_manager.validate_workspace_root(root)
|
|
560
546
|
{ root: root, basename: File.basename(root), writable: File.writable?(root) }
|
|
@@ -715,20 +701,6 @@ module Kward
|
|
|
715
701
|
{ sections: sections }
|
|
716
702
|
end
|
|
717
703
|
|
|
718
|
-
def workers_list(params)
|
|
719
|
-
include_archived = params["includeArchived"] == true
|
|
720
|
-
workers = @worker_store.list(include_archived: include_archived)
|
|
721
|
-
{ workers: workers }
|
|
722
|
-
end
|
|
723
|
-
|
|
724
|
-
def workers_show(params)
|
|
725
|
-
id = params.fetch("id").to_s.delete_prefix("#")
|
|
726
|
-
worker = @worker_store.find(id)
|
|
727
|
-
return { worker: worker } if worker
|
|
728
|
-
|
|
729
|
-
raise ArgumentError, "Unknown worker: #{id}"
|
|
730
|
-
end
|
|
731
|
-
|
|
732
704
|
def auth_login_with_api_key(params)
|
|
733
705
|
result = @auth_manager.login_with_api_key(provider_id: params.fetch("providerId"), api_key: params.fetch("apiKey"))
|
|
734
706
|
@session_manager.refresh_client_config
|