kward 0.84.0 → 0.85.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 +34 -0
- data/Gemfile.lock +2 -2
- data/README.md +2 -2
- data/doc/agent-tools.md +13 -1
- data/doc/api.md +17 -2
- data/doc/composer.md +1 -1
- data/doc/configuration.md +27 -4
- data/doc/extensibility.md +2 -1
- data/doc/git.md +3 -1
- data/doc/pan.md +6 -0
- data/doc/permissions.md +4 -4
- data/doc/plugins.md +464 -15
- data/doc/rpc.md +154 -16
- data/doc/sandboxing.md +11 -5
- data/doc/security.md +7 -1
- data/doc/session-management.md +5 -4
- data/doc/tabs.md +6 -2
- data/doc/transports.md +15 -0
- data/doc/usage.md +4 -1
- data/doc/workspace-tools.md +9 -0
- data/examples/plugins/space_invaders.rb +1 -1
- data/examples/plugins/stardate_footer.rb +2 -2
- data/examples/plugins/telegram/plugin.rb +1 -1
- data/lib/kward/agent.rb +24 -11
- data/lib/kward/cli/compaction.rb +9 -3
- data/lib/kward/cli/plugins.rb +81 -12
- data/lib/kward/cli/prompt_interface.rb +25 -5
- data/lib/kward/cli/rendering.rb +3 -0
- data/lib/kward/cli/runtime_helpers.rb +121 -27
- data/lib/kward/cli/sessions.rb +9 -5
- data/lib/kward/cli/settings/menus.rb +745 -0
- data/lib/kward/cli/settings/model.rb +327 -0
- data/lib/kward/cli/settings.rb +6 -1055
- data/lib/kward/cli/slash_commands.rb +45 -4
- data/lib/kward/cli/tabs.rb +167 -27
- data/lib/kward/{cli_transcript_formatter.rb → cli/transcript_formatter.rb} +3 -3
- data/lib/kward/cli/worktrees.rb +65 -2
- data/lib/kward/cli.rb +26 -24
- data/lib/kward/compactor.rb +18 -7
- data/lib/kward/config/core.rb +389 -0
- data/lib/kward/config/extensions.rb +96 -0
- data/lib/kward/config/prompts.rb +313 -0
- data/lib/kward/config/settings.rb +250 -0
- data/lib/kward/config_files.rb +14 -1008
- data/lib/kward/conversation.rb +31 -2
- data/lib/kward/image_attachments.rb +1 -1
- data/lib/kward/model/client.rb +2 -2
- data/lib/kward/model/copilot_models.rb +2 -2
- data/lib/kward/model/model_info.rb +20 -3
- data/lib/kward/{openrouter_model_cache.rb → model/openrouter_model_cache.rb} +3 -3
- data/lib/kward/model/payloads.rb +12 -3
- data/lib/kward/model/typesafe_client.rb +78 -0
- data/lib/kward/pan/server.rb +10 -7
- data/lib/kward/permissions/policy.rb +6 -2
- data/lib/kward/plugin_registry.rb +2 -659
- data/lib/kward/plugins/actions.rb +453 -0
- data/lib/kward/plugins/chat_contract.rb +121 -0
- data/lib/kward/{plugin_chat_runtime.rb → plugins/chat_runtime.rb} +62 -18
- data/lib/kward/plugins/host.rb +232 -0
- data/lib/kward/plugins/registry.rb +1190 -0
- data/lib/kward/plugins/resources.rb +206 -0
- data/lib/kward/plugins/turn_request.rb +36 -0
- data/lib/kward/plugins/ui.rb +219 -0
- data/lib/kward/prompt_interface/composer_renderer.rb +1 -1
- data/lib/kward/prompt_interface/composer_state.rb +1 -1
- data/lib/kward/prompt_interface/editor/controller.rb +1 -1
- data/lib/kward/prompt_interface/editor/runner.rb +1 -1
- data/lib/kward/prompt_interface/editor/state.rb +1 -1
- data/lib/kward/prompt_interface/layout.rb +1 -1
- data/lib/kward/prompt_interface/overlay_renderer.rb +1 -1
- data/lib/kward/prompt_interface/plugin_ui_requests.rb +82 -0
- data/lib/kward/prompt_interface/runtime_state.rb +6 -1
- data/lib/kward/prompt_interface/screen.rb +9 -2
- data/lib/kward/prompt_interface.rb +60 -11
- data/lib/kward/prompts/commands.rb +2 -1
- data/lib/kward/{adaptive_pty_output_sink.rb → pty/adaptive_output_sink.rb} +1 -1
- data/lib/kward/{interactive_pty_runner.rb → pty/interactive_runner.rb} +2 -2
- data/lib/kward/{local_command_runner.rb → pty/local_command_runner.rb} +1 -1
- data/lib/kward/{local_pty_command_runner.rb → pty/local_pty_runner.rb} +3 -9
- data/lib/kward/{pty_output_sink.rb → pty/output_sink.rb} +9 -4
- data/lib/kward/{pty_transcript_normalizer.rb → pty/transcript_normalizer.rb} +1 -1
- data/lib/kward/rpc/plugin_chat_manager.rb +30 -10
- data/lib/kward/rpc/prompt_bridge.rb +25 -0
- data/lib/kward/rpc/server.rb +91 -12
- data/lib/kward/rpc/session_manager.rb +147 -44
- data/lib/kward/rpc/session_tree_rows.rb +2 -2
- data/lib/kward/rpc/tool_metadata.rb +1 -1
- data/lib/kward/sandbox/command_runner.rb +1 -1
- data/lib/kward/{session_catalog.rb → sessions/catalog.rb} +1 -1
- data/lib/kward/{session_store.rb → sessions/store.rb} +9 -9
- data/lib/kward/{session_tree_nodes.rb → sessions/tree_nodes.rb} +3 -3
- data/lib/kward/{session_tree_renderer.rb → sessions/tree_renderer.rb} +4 -4
- data/lib/kward/{session_tree_tool_display.rb → sessions/tree_tool_display.rb} +1 -1
- data/lib/kward/{kwsh.rb → shell/kwsh.rb} +3 -3
- data/lib/kward/{persistent_shell_session.rb → shell/persistent_session.rb} +3 -3
- data/lib/kward/{shell_prompt_session.rb → shell/prompt_session.rb} +1 -1
- data/lib/kward/skills/trust_store.rb +1 -1
- data/lib/kward/tabs/driver.rb +194 -0
- data/lib/kward/{tab_store.rb → tabs/store.rb} +2 -2
- data/lib/kward/{clipboard.rb → terminal/clipboard.rb} +1 -1
- data/lib/kward/{terminal_image_support.rb → terminal/image_support.rb} +1 -1
- data/lib/kward/tools/base.rb +18 -0
- data/lib/kward/tools/context_for_task.rb +15 -6
- data/lib/kward/tools/edit_file.rb +9 -6
- data/lib/kward/tools/git_commit.rb +13 -7
- data/lib/kward/tools/list_directory.rb +4 -4
- data/lib/kward/tools/plugin_tool.rb +41 -0
- data/lib/kward/tools/prepare_shell_command.rb +1 -1
- data/lib/kward/tools/read_file.rb +7 -6
- data/lib/kward/tools/registry.rb +96 -13
- data/lib/kward/tools/run_shell_command.rb +10 -8
- data/lib/kward/tools/search/code.rb +1 -1
- data/lib/kward/tools/summarize_file_structure.rb +5 -5
- data/lib/kward/tools/tool_call.rb +2 -1
- data/lib/kward/tools/typesafe_evaluate.rb +81 -0
- data/lib/kward/tools/workspace_targets.rb +58 -0
- data/lib/kward/tools/write_file.rb +9 -6
- data/lib/kward/{export_path.rb → transcripts/export_path.rb} +1 -1
- data/lib/kward/{markdown_transcript.rb → transcripts/markdown_transcript.rb} +2 -2
- data/lib/kward/transport/contracts.rb +200 -0
- data/lib/kward/transport/gateway.rb +79 -35
- data/lib/kward/transport/plugin_chat_gateway.rb +3 -2
- data/lib/kward/transport.rb +1 -200
- data/lib/kward/version.rb +1 -1
- data/lib/kward/{workspace_factory.rb → workspace/factory.rb} +2 -2
- data/lib/kward/{git_worktree_manager.rb → workspace/git_worktree_manager.rb} +28 -0
- data/lib/kward/{workspace.rb → workspace/workspace.rb} +3 -3
- data/templates/default/fulldoc/html/css/kward.css +125 -0
- data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
- data/templates/default/fulldoc/html/setup.rb +1 -1
- data/templates/default/layout/html/layout.erb +16 -4
- metadata +67 -48
- data/lib/kward/tab_driver.rb +0 -90
- data/templates/default/fulldoc/html/images/kward_workflow.svg +0 -52
- /data/lib/kward/{editor_prompt.rb → cli/editor_prompt.rb} +0 -0
- /data/lib/kward/{editor_prompt_session.rb → cli/editor_prompt_session.rb} +0 -0
- /data/lib/kward/{diff_view_mode.rb → prompt_interface/editor/diff_view_mode.rb} +0 -0
- /data/lib/kward/{editor_mode.rb → prompt_interface/editor/editor_mode.rb} +0 -0
- /data/lib/kward/{markdown_code_block.rb → prompt_interface/editor/markdown_code_block.rb} +0 -0
- /data/lib/kward/{scratchpad_languages.rb → prompt_interface/editor/scratchpad_languages.rb} +0 -0
- /data/lib/kward/{scratchpad_runner.rb → prompt_interface/editor/scratchpad_runner.rb} +0 -0
- /data/lib/kward/{detached_run.rb → pty/detached_run.rb} +0 -0
- /data/lib/kward/{session_diff.rb → sessions/diff.rb} +0 -0
- /data/lib/kward/{session_naming.rb → sessions/naming.rb} +0 -0
- /data/lib/kward/{session_trash.rb → sessions/trash.rb} +0 -0
- /data/lib/kward/{kwshrc.rb → shell/kwshrc.rb} +0 -0
- /data/lib/kward/{shell_prompt.rb → shell/prompt.rb} +0 -0
- /data/lib/kward/{ansi.rb → terminal/ansi.rb} +0 -0
- /data/lib/kward/{terminal_keys.rb → terminal/keys.rb} +0 -0
- /data/lib/kward/{terminal_sequences.rb → terminal/sequences.rb} +0 -0
- /data/lib/kward/{terminal_text.rb → terminal/text.rb} +0 -0
- /data/lib/kward/{transcript_export.rb → transcripts/transcript_export.rb} +0 -0
- /data/lib/kward/{project_files.rb → workspace/files.rb} +0 -0
- /data/lib/kward/{path_guard.rb → workspace/path_guard.rb} +0 -0
|
@@ -3,20 +3,20 @@ require "io/console"
|
|
|
3
3
|
require "pathname"
|
|
4
4
|
require "rbconfig"
|
|
5
5
|
require "thread"
|
|
6
|
-
require_relative "
|
|
6
|
+
require_relative "workspace/files"
|
|
7
7
|
require_relative "image_attachments"
|
|
8
|
-
require_relative "
|
|
8
|
+
require_relative "terminal/image_support"
|
|
9
9
|
require_relative "prompt_history"
|
|
10
10
|
require "tty-cursor"
|
|
11
11
|
require "tty-reader"
|
|
12
12
|
require "tty-screen"
|
|
13
|
-
require_relative "ansi"
|
|
14
|
-
require_relative "
|
|
15
|
-
require_relative "
|
|
16
|
-
require_relative "editor_mode"
|
|
17
|
-
require_relative "diff_view_mode"
|
|
18
|
-
require_relative "scratchpad_languages"
|
|
19
|
-
require_relative "markdown_code_block"
|
|
13
|
+
require_relative "terminal/ansi"
|
|
14
|
+
require_relative "terminal/sequences"
|
|
15
|
+
require_relative "terminal/keys"
|
|
16
|
+
require_relative "prompt_interface/editor/editor_mode"
|
|
17
|
+
require_relative "prompt_interface/editor/diff_view_mode"
|
|
18
|
+
require_relative "prompt_interface/editor/scratchpad_languages"
|
|
19
|
+
require_relative "prompt_interface/editor/markdown_code_block"
|
|
20
20
|
require_relative "prompt_interface/banner"
|
|
21
21
|
require_relative "prompt_interface/composer_state"
|
|
22
22
|
require_relative "prompt_interface/editor/state"
|
|
@@ -29,6 +29,7 @@ require_relative "prompt_interface/file_overlay"
|
|
|
29
29
|
require_relative "prompt_interface/project_browser"
|
|
30
30
|
require_relative "prompt_interface/selection_prompt"
|
|
31
31
|
require_relative "prompt_interface/question_prompt"
|
|
32
|
+
require_relative "prompt_interface/plugin_ui_requests"
|
|
32
33
|
require_relative "prompt_interface/approval_prompt"
|
|
33
34
|
require_relative "prompt_interface/git_prompt"
|
|
34
35
|
require_relative "prompt_interface/overlay_renderer"
|
|
@@ -76,6 +77,7 @@ module Kward
|
|
|
76
77
|
COMPLETION_DISPLAY_SECONDS = 0.8
|
|
77
78
|
RESPONSE_ARRIVAL_SECONDS = 0.4
|
|
78
79
|
BUSY_ELAPSED_DELAY_SECONDS = 2.0
|
|
80
|
+
TRANSCRIPT_COMPOSER_GAP_ROWS = 1
|
|
79
81
|
COMPOSER_MAX_INPUT_ROWS = 6
|
|
80
82
|
IMAGE_VIEWER_MAX_ROWS = 8
|
|
81
83
|
TRANSCRIPT_BUFFER_LIMIT = 200_000
|
|
@@ -86,6 +88,7 @@ module Kward
|
|
|
86
88
|
include ProjectBrowser
|
|
87
89
|
include SelectionPrompt
|
|
88
90
|
include QuestionPrompt
|
|
91
|
+
include PluginUIRequests
|
|
89
92
|
include ApprovalPrompt
|
|
90
93
|
include GitPrompt
|
|
91
94
|
include OverlayRenderer
|
|
@@ -140,12 +143,48 @@ module Kward
|
|
|
140
143
|
end
|
|
141
144
|
end
|
|
142
145
|
|
|
146
|
+
# Forwards child output while tracking keyboard-protocol resets across chunks.
|
|
147
|
+
class TerminalHandoffOutput
|
|
148
|
+
def initialize(output, on_keyboard_protocol_restore:)
|
|
149
|
+
@output = output
|
|
150
|
+
@on_keyboard_protocol_restore = on_keyboard_protocol_restore
|
|
151
|
+
@pending = +"".b
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def write(value)
|
|
155
|
+
value = value.to_s.b
|
|
156
|
+
detect_keyboard_protocol_restore(value)
|
|
157
|
+
@output.write(value)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def print(*values)
|
|
161
|
+
values.each { |value| write(value) }
|
|
162
|
+
nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def flush
|
|
166
|
+
@output.flush if @output.respond_to?(:flush)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private
|
|
170
|
+
|
|
171
|
+
def detect_keyboard_protocol_restore(value)
|
|
172
|
+
@pending << value
|
|
173
|
+
return unless @pending.include?(TerminalSequences::KEYBOARD_PROTOCOL_RESTORE)
|
|
174
|
+
|
|
175
|
+
@on_keyboard_protocol_restore.call
|
|
176
|
+
keep = [TerminalSequences::KEYBOARD_PROTOCOL_RESTORE.bytesize - 1, @pending.bytesize].min
|
|
177
|
+
@pending = keep.positive? ? @pending.byteslice(-keep, keep) : +"".b
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
143
181
|
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)
|
|
144
182
|
@input_io = input
|
|
145
183
|
@output_io = output
|
|
146
184
|
# Logical state may change during a child handoff, but only the owner may render.
|
|
147
185
|
@terminal_owner = :kward
|
|
148
186
|
@terminal_handoff_mode = nil
|
|
187
|
+
@terminal_handoff_keyboard_protocol_reset = false
|
|
149
188
|
@reader = TTY::Reader.new(input: input, output: output, interrupt: :error)
|
|
150
189
|
@mutex = Mutex.new
|
|
151
190
|
@prompt_history = prompt_history
|
|
@@ -717,12 +756,19 @@ module Kward
|
|
|
717
756
|
print_output_locked(BRACKETED_PASTE_RESTORE)
|
|
718
757
|
print_output_locked(KEYBOARD_PROTOCOL_RESTORE) unless preserve_tab_keybindings
|
|
719
758
|
@handoff_input_buffer.clear
|
|
759
|
+
@terminal_handoff_keyboard_protocol_reset = false
|
|
720
760
|
restore_editor_cursor_shape_locked
|
|
721
761
|
set_cursor_visible_locked(true, force: true)
|
|
722
762
|
flush_output_locked
|
|
723
763
|
restore_console_mode_locked
|
|
724
764
|
input = @input_io
|
|
725
|
-
output =
|
|
765
|
+
output = if preserve_tab_keybindings
|
|
766
|
+
TerminalHandoffOutput.new(@output_io, on_keyboard_protocol_restore: -> {
|
|
767
|
+
@terminal_handoff_keyboard_protocol_reset = true
|
|
768
|
+
})
|
|
769
|
+
else
|
|
770
|
+
@output_io
|
|
771
|
+
end
|
|
726
772
|
transition = method(:switch_terminal_handoff_to_exclusive)
|
|
727
773
|
@terminal_handoff_mode = inline ? :inline : :exclusive
|
|
728
774
|
@terminal_owner = :child
|
|
@@ -736,9 +782,12 @@ module Kward
|
|
|
736
782
|
restore_scroll_region_locked
|
|
737
783
|
print_output_locked(TerminalSequences::SGR_RESET)
|
|
738
784
|
enter_raw_mode_locked
|
|
739
|
-
|
|
785
|
+
if !preserve_tab_keybindings || @terminal_handoff_keyboard_protocol_reset
|
|
786
|
+
print_output_locked(KEYBOARD_PROTOCOL_ENABLE)
|
|
787
|
+
end
|
|
740
788
|
print_output_locked(BRACKETED_PASTE_ENABLE)
|
|
741
789
|
@handoff_input_buffer.clear
|
|
790
|
+
@terminal_handoff_keyboard_protocol_reset = false
|
|
742
791
|
disable_editor_mouse_reporting(force: true)
|
|
743
792
|
restore_editor_cursor_shape_locked(force: true)
|
|
744
793
|
@cursor_visible = nil
|
|
@@ -11,6 +11,7 @@ module Kward
|
|
|
11
11
|
{ name: "session", description: "Open saved sessions or name the current session.", argument_hint: "[path]|name [name]" },
|
|
12
12
|
{ name: "resume", description: "Alias for /session.", argument_hint: "[path]" },
|
|
13
13
|
{ name: "rename", description: "Rename the current session.", argument_hint: "<name>" },
|
|
14
|
+
{ name: "name", description: "Rename the current session and tab.", argument_hint: "<name>" },
|
|
14
15
|
{ name: "clone", description: "Clone the current session.", argument_hint: "" },
|
|
15
16
|
{ name: "fork", description: "Fork from an earlier prompt into a new session.", argument_hint: "" },
|
|
16
17
|
{ name: "rewind", description: "Revisit an earlier prompt and try a different direction.", argument_hint: "" },
|
|
@@ -32,7 +33,7 @@ module Kward
|
|
|
32
33
|
{ name: "scratchpad", description: "Open an unsaved editor buffer.", argument_hint: "[language|help]" },
|
|
33
34
|
{ name: "pty", description: "Run a command in an interactive PTY passthrough session.", argument_hint: "<command>" },
|
|
34
35
|
{ name: "tab", description: "Manage tabs.", argument_hint: "[1-n|move|close|new|name|worktree]" },
|
|
35
|
-
{ name: "worktree", description: "Manage the active tab's linked Git worktree.", argument_hint: "[activate|detach|status|merge|
|
|
36
|
+
{ name: "worktree", description: "Manage the active tab's linked Git worktree.", argument_hint: "[activate|detach|status|merge [resolve|continue|abort]|remove]" },
|
|
36
37
|
{ name: "status", description: "Show the current status message.", argument_hint: "" },
|
|
37
38
|
{ name: "stats", description: "Show telemetry logging stats.", argument_hint: "[range]" },
|
|
38
39
|
{ name: "sandbox", description: "Inspect or configure model command sandboxing.", argument_hint: "[status|off|read_only|workspace_write|network allow|network deny]" },
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
require "io/console"
|
|
2
2
|
require "pty"
|
|
3
3
|
require "timeout"
|
|
4
|
-
require_relative "
|
|
5
|
-
require_relative "
|
|
4
|
+
require_relative "local_pty_runner"
|
|
5
|
+
require_relative "output_sink"
|
|
6
6
|
require_relative "detached_run"
|
|
7
7
|
|
|
8
8
|
# Namespace for the Kward CLI agent runtime.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require "io/console"
|
|
2
2
|
require "pty"
|
|
3
3
|
require "timeout"
|
|
4
|
-
require_relative "cancellation"
|
|
4
|
+
require_relative "../cancellation"
|
|
5
5
|
require_relative "local_command_runner"
|
|
6
6
|
|
|
7
7
|
# Namespace for the Kward CLI agent runtime.
|
|
@@ -52,7 +52,8 @@ module Kward
|
|
|
52
52
|
readable, = IO.select([reader], nil, nil, 0.02)
|
|
53
53
|
next unless readable
|
|
54
54
|
|
|
55
|
-
chunk =
|
|
55
|
+
chunk = reader.read_nonblock(READ_SIZE, exception: false)
|
|
56
|
+
next if chunk == :wait_readable
|
|
56
57
|
break if chunk.nil?
|
|
57
58
|
|
|
58
59
|
chunk = normalize_line_endings(chunk)
|
|
@@ -102,13 +103,6 @@ module Kward
|
|
|
102
103
|
[DEFAULT_ROWS, DEFAULT_COLUMNS]
|
|
103
104
|
end
|
|
104
105
|
|
|
105
|
-
def read_chunk(reader)
|
|
106
|
-
reader.read_nonblock(READ_SIZE, exception: false).tap do |chunk|
|
|
107
|
-
return nil if chunk.nil?
|
|
108
|
-
return nil if chunk == :wait_readable
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
106
|
def normalize_line_endings(chunk)
|
|
113
107
|
chunk.gsub("\r\r\n", "\n").gsub("\r\n", "\n")
|
|
114
108
|
end
|
|
@@ -4,17 +4,22 @@ require "thread"
|
|
|
4
4
|
module Kward
|
|
5
5
|
# Retains output while a detached PTY command runs without terminal ownership.
|
|
6
6
|
class BufferedPtyOutputSink
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def initialize(max_capture_bytes:)
|
|
7
|
+
def initialize(max_capture_bytes:, initial_output: nil, initial_truncated: false)
|
|
10
8
|
@max_capture_bytes = max_capture_bytes
|
|
11
9
|
@captured_output = +"".b
|
|
12
|
-
@truncated =
|
|
10
|
+
@truncated = initial_truncated
|
|
13
11
|
@mutex = Mutex.new
|
|
12
|
+
write(initial_output) unless initial_output.to_s.empty?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def captured_output
|
|
16
|
+
@mutex.synchronize { @captured_output.dup }
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def write(chunk)
|
|
17
20
|
value = chunk.to_s.b
|
|
21
|
+
return if value.empty?
|
|
22
|
+
|
|
18
23
|
@mutex.synchronize do
|
|
19
24
|
remaining = @max_capture_bytes - @captured_output.bytesize
|
|
20
25
|
if value.bytesize > remaining
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require_relative "../cancellation"
|
|
2
2
|
require_relative "../image_attachments"
|
|
3
|
-
require_relative "../
|
|
4
|
-
require_relative "../
|
|
3
|
+
require_relative "../plugins/chat_runtime"
|
|
4
|
+
require_relative "../plugins/registry"
|
|
5
5
|
require_relative "attachment_normalizer"
|
|
6
6
|
require_relative "transcript_normalizer"
|
|
7
7
|
|
|
@@ -30,21 +30,25 @@ module Kward
|
|
|
30
30
|
@runtime.supported_types(surface: :rpc)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def type_entries
|
|
34
|
+
supported_types.map { |type| type_payload(type) }
|
|
35
|
+
end
|
|
36
|
+
|
|
33
37
|
def list
|
|
34
|
-
{ chats:
|
|
38
|
+
{ chats: type_entries }
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
def open(type_id:)
|
|
38
42
|
chat = @runtime.open(type_id: type_id, surface: :rpc, scope_key: "owner")
|
|
39
43
|
payload = chat_payload(chat)
|
|
40
|
-
return payload if
|
|
44
|
+
return payload if transcript_paging?(chat)
|
|
41
45
|
|
|
42
46
|
payload.merge(messages: TranscriptNormalizer.new(chat.driver.messages).normalize)
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
def transcript(chat_id:, limit: nil, before: nil)
|
|
46
50
|
chat = fetch_chat(chat_id)
|
|
47
|
-
page = transcript_page(chat
|
|
51
|
+
page = transcript_page(chat, limit: limit, before: before)
|
|
48
52
|
{
|
|
49
53
|
chat: chat_payload(chat),
|
|
50
54
|
messages: TranscriptNormalizer.new(page.fetch(:messages)).normalize,
|
|
@@ -70,7 +74,7 @@ module Kward
|
|
|
70
74
|
normalized_attachments = AttachmentNormalizer.new.normalize(attachments)
|
|
71
75
|
turn = @runtime.start_turn(
|
|
72
76
|
chat_id: chat.id,
|
|
73
|
-
input: @runtime.input_with_attachments(input, normalized_attachments),
|
|
77
|
+
input: @runtime.input_with_attachments(input, normalized_attachments, capabilities: chat.type.capabilities),
|
|
74
78
|
display_input: input.to_s
|
|
75
79
|
)
|
|
76
80
|
turn_payload(turn)
|
|
@@ -121,19 +125,35 @@ module Kward
|
|
|
121
125
|
end
|
|
122
126
|
|
|
123
127
|
def type_payload(type)
|
|
124
|
-
{
|
|
128
|
+
payload = {
|
|
125
129
|
id: type.id,
|
|
126
130
|
name: type.name,
|
|
127
131
|
title: type.title,
|
|
128
132
|
singleton: type.singleton,
|
|
129
133
|
transport: type.transport == true ? true : nil
|
|
130
134
|
}.compact
|
|
135
|
+
return payload unless type.capabilities.declared?
|
|
136
|
+
|
|
137
|
+
capabilities = type.capabilities.to_h
|
|
138
|
+
payload.merge(
|
|
139
|
+
apiVersion: capabilities.fetch(:api_version),
|
|
140
|
+
capabilities: {
|
|
141
|
+
attachments: capabilities.fetch(:attachments),
|
|
142
|
+
steering: capabilities.fetch(:steering),
|
|
143
|
+
transcriptPaging: capabilities.fetch(:transcript_paging)
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def transcript_paging?(chat)
|
|
149
|
+
capabilities = chat.type.capabilities
|
|
150
|
+
capabilities.declared? ? capabilities.transcript_paging? : chat.driver.respond_to?(:transcript_page)
|
|
131
151
|
end
|
|
132
152
|
|
|
133
|
-
def transcript_page(
|
|
134
|
-
return { messages: driver.messages, has_more: false } unless limit &&
|
|
153
|
+
def transcript_page(chat, limit:, before:)
|
|
154
|
+
return { messages: chat.driver.messages, has_more: false } unless limit && transcript_paging?(chat)
|
|
135
155
|
|
|
136
|
-
driver.transcript_page(limit: limit, before: before)
|
|
156
|
+
chat.driver.transcript_page(limit: limit, before: before)
|
|
137
157
|
end
|
|
138
158
|
|
|
139
159
|
def chat_payload(chat)
|
|
@@ -74,6 +74,31 @@ module Kward
|
|
|
74
74
|
answer_raw(request_id, normalize_answers(answers))
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
def request_plugin_ui(kind, payload, cancellation: nil)
|
|
78
|
+
request_id = SecureRandom.uuid
|
|
79
|
+
@mutex.synchronize { @pending_requests[request_id] = true }
|
|
80
|
+
cancellation&.on_cancel { cancel_request(request_id) }
|
|
81
|
+
unless cancellation&.cancelled?
|
|
82
|
+
@notify.call("ui/request", {
|
|
83
|
+
sessionId: @session_id,
|
|
84
|
+
requestId: request_id,
|
|
85
|
+
kind: kind.to_s,
|
|
86
|
+
payload: payload
|
|
87
|
+
})
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
@mutex.synchronize do
|
|
91
|
+
@condition.wait(@mutex) until @answers.key?(request_id)
|
|
92
|
+
answer = @answers.delete(request_id)
|
|
93
|
+
@pending_requests.delete(request_id)
|
|
94
|
+
answer
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def answer_plugin_ui(request_id, value)
|
|
99
|
+
answer_raw(request_id, value)
|
|
100
|
+
end
|
|
101
|
+
|
|
77
102
|
def cancel_request(request_id)
|
|
78
103
|
@mutex.synchronize do
|
|
79
104
|
request_id = request_id.to_s
|
data/lib/kward/rpc/server.rb
CHANGED
|
@@ -2,11 +2,11 @@ require "json"
|
|
|
2
2
|
require_relative "../model/client"
|
|
3
3
|
require_relative "../config_files"
|
|
4
4
|
require_relative "../memory/manager"
|
|
5
|
-
require_relative "../
|
|
5
|
+
require_relative "../plugins/registry"
|
|
6
6
|
require_relative "../transport"
|
|
7
7
|
require_relative "../prompts/commands"
|
|
8
8
|
require_relative "../tools/registry"
|
|
9
|
-
require_relative "../
|
|
9
|
+
require_relative "../workspace/factory"
|
|
10
10
|
require_relative "../telemetry/logger"
|
|
11
11
|
require_relative "../telemetry/stats"
|
|
12
12
|
require_relative "auth_manager"
|
|
@@ -75,18 +75,22 @@ module Kward
|
|
|
75
75
|
"memory/why", "memory/summarize"
|
|
76
76
|
].freeze
|
|
77
77
|
COMMAND_METHODS = ["commands/list", "commands/run"].freeze
|
|
78
|
+
PLUGIN_ACTION_METHODS = ["pluginActions/list", "pluginActions/run"].freeze
|
|
78
79
|
SKILL_CAPTURE_METHODS = ["skills/captureSessions", "skills/captureDraft", "skills/saveCapturedDraft"].freeze
|
|
79
80
|
STARTUP_RESOURCE_METHODS = ["resources/startup"].freeze
|
|
80
81
|
CONFIG_METHODS = ["config/read", "config/update"].freeze
|
|
81
82
|
LOGGING_METHODS = ["logging/stats", "logging/tokenCsv"].freeze
|
|
82
83
|
LIFECYCLE_HOOK_METHODS = ["hooks/logs"].freeze
|
|
83
|
-
UI_METHODS = ["ui/answerQuestion"].freeze
|
|
84
|
+
UI_METHODS = ["ui/answerQuestion", "ui/answerRequest"].freeze
|
|
84
85
|
TOOL_APPROVAL_METHODS = ["tool/answerApproval"].freeze
|
|
85
86
|
TRANSPORT_METHODS = ["transports/list", "transports/status"].freeze
|
|
86
87
|
SESSION_EVENT_NOTIFICATION = "session/event"
|
|
87
88
|
SESSION_UPDATED_NOTIFICATION = "session/updated"
|
|
88
89
|
TURN_EVENT_NOTIFICATION = "turn/event"
|
|
89
90
|
UI_QUESTION_NOTIFICATION = "ui/question"
|
|
91
|
+
UI_REQUEST_NOTIFICATION = "ui/request"
|
|
92
|
+
UI_PROGRESS_NOTIFICATION = "ui/progress"
|
|
93
|
+
UI_NOTIFICATION = "ui/notification"
|
|
90
94
|
UI_FOOTER_NOTIFICATION = "ui/footer"
|
|
91
95
|
TOOL_APPROVAL_NOTIFICATION = "tool/approvalRequested"
|
|
92
96
|
HOOK_EVENT_NOTIFICATION = "hook/event"
|
|
@@ -105,6 +109,7 @@ module Kward
|
|
|
105
109
|
auth: AUTH_METHODS,
|
|
106
110
|
memory: MEMORY_METHODS,
|
|
107
111
|
commands: COMMAND_METHODS,
|
|
112
|
+
plugin_actions: PLUGIN_ACTION_METHODS,
|
|
108
113
|
skill_capture: SKILL_CAPTURE_METHODS,
|
|
109
114
|
startup_resources: STARTUP_RESOURCE_METHODS,
|
|
110
115
|
config: CONFIG_METHODS,
|
|
@@ -176,6 +181,7 @@ module Kward
|
|
|
176
181
|
@transport_manager.shutdown
|
|
177
182
|
@plugin_chat_manager.shutdown
|
|
178
183
|
@session_manager.shutdown_sessions
|
|
184
|
+
@session_manager.shutdown_plugins
|
|
179
185
|
@shutdown_complete = true
|
|
180
186
|
end
|
|
181
187
|
|
|
@@ -284,6 +290,10 @@ module Kward
|
|
|
284
290
|
commands_list(params)
|
|
285
291
|
when COMMAND_METHODS[1]
|
|
286
292
|
commands_run(params)
|
|
293
|
+
when PLUGIN_ACTION_METHODS[0]
|
|
294
|
+
plugin_actions_list(params)
|
|
295
|
+
when PLUGIN_ACTION_METHODS[1]
|
|
296
|
+
plugin_actions_run(params)
|
|
287
297
|
when SKILL_CAPTURE_METHODS[0]
|
|
288
298
|
{ sessions: @session_manager.skill_capture_sessions }
|
|
289
299
|
when SKILL_CAPTURE_METHODS[1]
|
|
@@ -421,6 +431,8 @@ module Kward
|
|
|
421
431
|
@plugin_chat_manager.list_turns(chat_id: params["chatId"], active: true)
|
|
422
432
|
when UI_METHODS[0]
|
|
423
433
|
@session_manager.answer_question(session_id: params.fetch("sessionId"), question_request_id: params.fetch("questionRequestId"), answers: params.fetch("answers"))
|
|
434
|
+
when UI_METHODS[1]
|
|
435
|
+
@session_manager.answer_plugin_ui(session_id: params.fetch("sessionId"), request_id: params.fetch("requestId"), value: params["value"])
|
|
424
436
|
when TOOL_APPROVAL_METHODS[0]
|
|
425
437
|
@session_manager.answer_tool_approval(session_id: params.fetch("sessionId"), approval_request_id: params.fetch("approvalRequestId"), approved: params.fetch("approved"))
|
|
426
438
|
else
|
|
@@ -469,13 +481,27 @@ module Kward
|
|
|
469
481
|
startMode: "cliOnly",
|
|
470
482
|
entries: @transport_manager.list
|
|
471
483
|
},
|
|
484
|
+
plugins: {
|
|
485
|
+
apiVersion: PluginRegistry::PLUGIN_API_VERSION,
|
|
486
|
+
identified: @session_manager.plugin_registry.plugins.map do |plugin|
|
|
487
|
+
{ id: plugin.id, version: plugin.version, apiVersion: plugin.api_version }
|
|
488
|
+
end
|
|
489
|
+
},
|
|
490
|
+
pluginTools: {
|
|
491
|
+
supported: true,
|
|
492
|
+
registered: @session_manager.plugin_registry.tools.length,
|
|
493
|
+
discoveryMethod: TOOL_METHODS.first,
|
|
494
|
+
source: "plugin",
|
|
495
|
+
executionProfileFiltering: true,
|
|
496
|
+
permissionPolicy: true
|
|
497
|
+
},
|
|
472
498
|
pluginChats: {
|
|
473
499
|
supported: @plugin_chat_manager.supported_types.any?,
|
|
474
500
|
methods: PLUGIN_CHAT_METHODS,
|
|
475
501
|
notification: "pluginChat/event",
|
|
476
502
|
subscriptions: { supported: true, methods: PLUGIN_CHAT_METHODS.values_at(3, 4), requiredForLiveEvents: true },
|
|
477
503
|
attachments: { supported: true, method: PLUGIN_CHAT_METHODS[5], encoding: "base64", mimeTypes: SessionManager::RPC_IMAGE_MIME_TYPES, maxBytes: SessionManager::RPC_ATTACHMENT_MAX_BYTES },
|
|
478
|
-
types: @plugin_chat_manager.
|
|
504
|
+
types: @plugin_chat_manager.type_entries
|
|
479
505
|
},
|
|
480
506
|
turns: {
|
|
481
507
|
mode: "async",
|
|
@@ -513,6 +539,7 @@ module Kward
|
|
|
513
539
|
reasoning: { start: false, delta: true, boundary: true, end: false },
|
|
514
540
|
modelRetry: { supported: true, event: "modelRetry" },
|
|
515
541
|
steering: { supported: @session_manager.in_flight_steer_supported?, event: "turnSteered", mode: @session_manager.in_flight_steer_supported? ? "native" : "unsupported" },
|
|
542
|
+
pluginCommands: { structuredResult: "pluginCommandResult" },
|
|
516
543
|
tools: { call: true, update: true, result: true, normalizedMetadata: true, diffs: true, firstChangedLine: true, changedFiles: true, workspaceGuardrails: workspace_guardrails_enabled?, focusedContext: true, contextBudgetStats: true },
|
|
517
544
|
errors: true,
|
|
518
545
|
sessionUpdates: false
|
|
@@ -577,7 +604,31 @@ module Kward
|
|
|
577
604
|
},
|
|
578
605
|
memory: { supported: true, optIn: true, defaultEnabled: false, autoSummaryDefaultEnabled: false, promptInjection: "interactive", storage: { core: "json", soft: "jsonl", events: "jsonl" }, methods: MEMORY_METHODS },
|
|
579
606
|
stability: { protocol: "stable", compatibility: "additive-fields-unless-protocol-version-changes", experimentalCapabilities: [] },
|
|
580
|
-
commands: {
|
|
607
|
+
commands: {
|
|
608
|
+
supported: true,
|
|
609
|
+
methods: COMMAND_METHODS,
|
|
610
|
+
method: COMMAND_METHODS[0],
|
|
611
|
+
runMethod: COMMAND_METHODS[1],
|
|
612
|
+
sources: ["builtin", "prompt", "skill", "plugin"],
|
|
613
|
+
executableSources: ["builtin", "plugin"],
|
|
614
|
+
typedArguments: { supported: true, schema: "jsonSchemaObject", textSyntax: "shellFlags", legacyRawStrings: true },
|
|
615
|
+
structuredResults: true,
|
|
616
|
+
modelTurns: {
|
|
617
|
+
supported: true, method: "turns/start", synchronous: false,
|
|
618
|
+
inputRole: "system", codexInputRole: "developer", scope: "turn", maxSystemBytes: PluginTurnRequest::MAX_SYSTEM_BYTES,
|
|
619
|
+
pluginChats: false, pan: false
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
pluginActions: {
|
|
623
|
+
supported: true,
|
|
624
|
+
methods: PLUGIN_ACTION_METHODS,
|
|
625
|
+
registered: @session_manager.plugin_actions.length,
|
|
626
|
+
namespace: "pluginId/actionName",
|
|
627
|
+
arguments: "jsonSchemaObject",
|
|
628
|
+
structuredResults: true,
|
|
629
|
+
blockingUi: false,
|
|
630
|
+
localTui: false
|
|
631
|
+
},
|
|
581
632
|
skillCapture: { supported: true, methods: SKILL_CAPTURE_METHODS, destination: "personal", source: "savedSessionActiveLeaf", reviewRequired: true, overwrite: "explicit", autoActivate: false },
|
|
582
633
|
projectSkillTrust: { supported: false, trustRequired: true, reason: "RPC has no interactive trust decision bridge; project skills remain skipped unless globally enabled." },
|
|
583
634
|
mcp: {
|
|
@@ -599,12 +650,14 @@ module Kward
|
|
|
599
650
|
scratchpad: { supported: false, reason: "interactiveTuiOnly" },
|
|
600
651
|
extensionUi: {
|
|
601
652
|
question: { supported: true, notification: UI_QUESTION_NOTIFICATION, method: UI_METHODS.first, maxQuestions: 4, multiSelect: false, preview: false },
|
|
602
|
-
select:
|
|
603
|
-
confirm:
|
|
604
|
-
input:
|
|
653
|
+
select: { supported: true, notification: UI_REQUEST_NOTIFICATION, method: UI_METHODS[1], maxOptions: PluginUI::MAX_OPTIONS },
|
|
654
|
+
confirm: { supported: true, notification: UI_REQUEST_NOTIFICATION, method: UI_METHODS[1] },
|
|
655
|
+
input: { supported: true, notification: UI_REQUEST_NOTIFICATION, method: UI_METHODS[1], maxBytes: PluginUI::MAX_TEXT_BYTES },
|
|
656
|
+
progress: { supported: true, notification: UI_PROGRESS_NOTIFICATION },
|
|
657
|
+
notify: { supported: true, notification: UI_NOTIFICATION, levels: PluginUI::NOTIFICATION_LEVELS.map(&:to_s) },
|
|
605
658
|
editor: false,
|
|
606
659
|
widgets: false,
|
|
607
|
-
footer: { supported: true, notification: UI_FOOTER_NOTIFICATION },
|
|
660
|
+
footer: { supported: true, notification: UI_FOOTER_NOTIFICATION, segments: true, tooltip: true, priorities: PluginRegistry::STATUS_PRIORITIES.keys.map(&:to_s) },
|
|
608
661
|
custom: false,
|
|
609
662
|
terminalInput: false
|
|
610
663
|
},
|
|
@@ -789,8 +842,12 @@ module Kward
|
|
|
789
842
|
argumentHint: command.argument_hint,
|
|
790
843
|
source: "plugin",
|
|
791
844
|
path: command.path,
|
|
792
|
-
|
|
793
|
-
|
|
845
|
+
pluginId: command.plugin_id,
|
|
846
|
+
executable: true,
|
|
847
|
+
typed: command.typed?,
|
|
848
|
+
schema: command.schema,
|
|
849
|
+
positionals: command.positionals
|
|
850
|
+
}.compact
|
|
794
851
|
end
|
|
795
852
|
{ commands: builtins + prompts + skills + plugins }
|
|
796
853
|
end
|
|
@@ -799,7 +856,29 @@ module Kward
|
|
|
799
856
|
@session_manager.run_command(
|
|
800
857
|
session_id: params.fetch("sessionId"),
|
|
801
858
|
command: params.fetch("name"),
|
|
802
|
-
arguments: params["arguments"]
|
|
859
|
+
arguments: params.key?("arguments") ? params["arguments"] : ""
|
|
860
|
+
)
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
def plugin_actions_list(params)
|
|
864
|
+
@session_manager.runtime_state(session_id: params.fetch("sessionId"))
|
|
865
|
+
actions = @session_manager.plugin_actions.map do |action|
|
|
866
|
+
{
|
|
867
|
+
id: action.id,
|
|
868
|
+
name: action.name,
|
|
869
|
+
pluginId: action.plugin_id,
|
|
870
|
+
description: action.description,
|
|
871
|
+
schema: action.schema
|
|
872
|
+
}
|
|
873
|
+
end
|
|
874
|
+
{ actions: actions }
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
def plugin_actions_run(params)
|
|
878
|
+
@session_manager.run_plugin_action(
|
|
879
|
+
session_id: params.fetch("sessionId"),
|
|
880
|
+
id: params.fetch("id"),
|
|
881
|
+
arguments: params.key?("arguments") ? params["arguments"] : {}
|
|
803
882
|
)
|
|
804
883
|
end
|
|
805
884
|
|