kward 0.83.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 +102 -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/agent-tools.md +13 -1
- data/doc/api.md +21 -2
- data/doc/composer.md +2 -2
- data/doc/configuration.md +95 -25
- data/doc/editor.md +28 -13
- data/doc/extensibility.md +2 -1
- data/doc/files.md +8 -4
- data/doc/getting-started.md +3 -0
- data/doc/git.md +3 -1
- data/doc/pan.md +25 -15
- data/doc/permissions.md +4 -4
- data/doc/platform-support.md +48 -0
- data/doc/plugins.md +464 -15
- data/doc/rpc.md +154 -16
- data/doc/sandboxing.md +11 -5
- data/doc/security.md +10 -3
- data/doc/session-management.md +5 -4
- data/doc/shell.md +62 -45
- data/doc/tabs.md +6 -2
- data/doc/transports.md +15 -0
- data/doc/troubleshooting.md +12 -2
- data/doc/usage.md +9 -6
- 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/kward.gemspec +5 -4
- data/lib/kward/agent.rb +30 -14
- data/lib/kward/cli/auth_commands.rb +34 -13
- data/lib/kward/cli/commands.rb +83 -62
- data/lib/kward/cli/compaction.rb +9 -3
- 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/plugins.rb +81 -12
- data/lib/kward/cli/project_skills_commands.rb +8 -4
- data/lib/kward/cli/prompt_interface.rb +52 -5
- data/lib/kward/cli/rendering.rb +18 -9
- data/lib/kward/cli/runtime_helpers.rb +228 -71
- 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 +56 -17
- data/lib/kward/cli/tabs.rb +244 -33
- data/lib/kward/cli/tool_summaries.rb +14 -0
- data/lib/kward/{cli_transcript_formatter.rb → cli/transcript_formatter.rb} +14 -7
- data/lib/kward/cli/worktrees.rb +65 -2
- data/lib/kward/cli.rb +70 -30
- 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 -994
- data/lib/kward/conversation.rb +31 -2
- data/lib/kward/image_attachments.rb +1 -1
- data/lib/kward/model/client.rb +36 -24
- 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/provider_catalog.rb +5 -0
- data/lib/kward/model/stream_parser.rb +20 -4
- data/lib/kward/model/typesafe_client.rb +78 -0
- data/lib/kward/pan/index.html.erb +3 -3
- data/lib/kward/pan/server.rb +33 -10
- 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 +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/markdown_code_block.rb +136 -0
- 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/scratchpad_languages.rb +74 -0
- data/lib/kward/prompt_interface/editor/scratchpad_runner.rb +182 -0
- data/lib/kward/prompt_interface/editor/state.rb +11 -11
- 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/layout.rb +1 -1
- data/lib/kward/prompt_interface/overlay_renderer.rb +7 -5
- data/lib/kward/prompt_interface/plugin_ui_requests.rb +82 -0
- data/lib/kward/prompt_interface/project_browser.rb +415 -14
- data/lib/kward/prompt_interface/runtime_state.rb +56 -2
- data/lib/kward/prompt_interface/screen.rb +11 -4
- 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 +151 -27
- data/lib/kward/prompts/commands.rb +3 -2
- data/lib/kward/prompts.rb +1 -1
- data/lib/kward/{adaptive_pty_output_sink.rb → pty/adaptive_output_sink.rb} +1 -1
- data/lib/kward/pty/detached_run.rb +44 -0
- data/lib/kward/{interactive_pty_runner.rb → pty/interactive_runner.rb} +104 -30
- 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} +52 -0
- 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/rpc/transcript_normalizer.rb +7 -3
- 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/{ekwsh.rb → shell/kwsh.rb} +38 -19
- data/lib/kward/shell/kwshrc.rb +233 -0
- data/lib/kward/{persistent_shell_session.rb → shell/persistent_session.rb} +121 -28
- data/lib/kward/{shell_prompt.rb → shell/prompt.rb} +2 -0
- 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/{ansi.rb → terminal/ansi.rb} +110 -10
- 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/{terminal_keys.rb → 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/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/open_editor.rb +41 -0
- 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 +109 -17
- 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 +3 -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/{project_files.rb → workspace/files.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/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 +2 -2
- data/templates/default/layout/html/setup.rb +41 -2
- metadata +94 -47
- data/lib/kward/scratchpad_runner.rb +0 -56
- data/lib/kward/tab_driver.rb +0 -90
- /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/{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/{terminal_sequences.rb → terminal/sequences.rb} +0 -0
- /data/lib/kward/{transcript_export.rb → transcripts/transcript_export.rb} +0 -0
- /data/lib/kward/{path_guard.rb → workspace/path_guard.rb} +0 -0
|
@@ -6,6 +6,20 @@ module Kward
|
|
|
6
6
|
module ToolSummaries
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
+
def tool_activity(tool_call)
|
|
10
|
+
"running #{tool_call_name(tool_call).tr("_", " ")}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def tool_summary_with_duration(summary, elapsed_ms)
|
|
14
|
+
return summary.to_s unless elapsed_ms
|
|
15
|
+
|
|
16
|
+
lines = summary.to_s.lines
|
|
17
|
+
first = lines.shift.to_s.chomp
|
|
18
|
+
duration = elapsed_ms < 1_000 ? "#{elapsed_ms.round} ms" : format("%.1f s", elapsed_ms / 1_000.0)
|
|
19
|
+
first = "#{first} · #{duration}"
|
|
20
|
+
lines.empty? ? first : (["#{first}\n"] + lines).join
|
|
21
|
+
end
|
|
22
|
+
|
|
9
23
|
def tool_result_summary(tool_call, content)
|
|
10
24
|
name = tool_call_name(tool_call)
|
|
11
25
|
args = tool_call_args(tool_call)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require "base64"
|
|
2
|
-
require_relative "image_attachments"
|
|
3
|
-
require_relative "message_access"
|
|
4
|
-
require_relative "message_text"
|
|
2
|
+
require_relative "../image_attachments"
|
|
3
|
+
require_relative "../message_access"
|
|
4
|
+
require_relative "../message_text"
|
|
5
5
|
|
|
6
6
|
# Namespace for the Kward CLI agent runtime.
|
|
7
7
|
module Kward
|
|
@@ -15,10 +15,17 @@ module Kward
|
|
|
15
15
|
|
|
16
16
|
def reasoning_blocks(message)
|
|
17
17
|
response_blocks = response_items(message).flat_map do |item|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
case MessageAccess.value(item, :type)
|
|
19
|
+
when "reasoning"
|
|
20
|
+
summary = response_item_text_parts(MessageAccess.value(item, :summary))
|
|
21
|
+
summary.empty? ? response_item_text_parts(MessageAccess.value(item, :content)) : summary
|
|
22
|
+
when "message"
|
|
23
|
+
next [] unless MessageAccess.value(item, :phase).to_s == "commentary"
|
|
24
|
+
|
|
25
|
+
response_item_text_parts(MessageAccess.value(item, :content))
|
|
26
|
+
else
|
|
27
|
+
[]
|
|
28
|
+
end
|
|
22
29
|
end
|
|
23
30
|
return response_blocks unless response_blocks.empty?
|
|
24
31
|
|
data/lib/kward/cli/worktrees.rb
CHANGED
|
@@ -101,10 +101,14 @@ module Kward
|
|
|
101
101
|
remove_active_worktree
|
|
102
102
|
when "merge"
|
|
103
103
|
merge_active_worktree
|
|
104
|
+
when "merge resolve"
|
|
105
|
+
resolve_active_worktree_merge
|
|
106
|
+
when "merge continue"
|
|
107
|
+
continue_active_worktree_merge
|
|
104
108
|
when "merge abort"
|
|
105
109
|
abort_active_worktree_merge
|
|
106
110
|
else
|
|
107
|
-
runtime_output("Usage: /tab worktree [activate|detach|status|merge|
|
|
111
|
+
runtime_output("Usage: /tab worktree [activate|detach|status|merge [resolve|continue|abort]|remove]")
|
|
108
112
|
end
|
|
109
113
|
end
|
|
110
114
|
|
|
@@ -222,6 +226,11 @@ module Kward
|
|
|
222
226
|
raise GitWorktreeManager::Error, "Worktree branch changed: expected #{binding.branch}, found #{info.branch || "detached HEAD"}" unless info.branch == binding.branch
|
|
223
227
|
status = git_worktree_manager.status(binding.path)
|
|
224
228
|
lines << "Changes: #{status.clean? ? "clean" : "#{status.entries.length} local change(s)"}"
|
|
229
|
+
if git_worktree_manager.merge_in_progress?(binding.origin_root)
|
|
230
|
+
conflicts = git_worktree_manager.merge_conflict_marker_paths(binding.origin_root, cached: false)
|
|
231
|
+
lines << "Merge: in progress in origin#{conflicts.empty? ? "; ready to continue" : "; #{conflicts.length} unresolved conflict(s)"}"
|
|
232
|
+
lines << "Conflicts: #{conflicts.join(", ")}" unless conflicts.empty?
|
|
233
|
+
end
|
|
225
234
|
rescue GitWorktreeManager::Error => e
|
|
226
235
|
lines << "State: unavailable (#{e.message})"
|
|
227
236
|
end
|
|
@@ -236,6 +245,10 @@ module Kward
|
|
|
236
245
|
return runtime_output("Activate the worktree before merging it.") unless binding.active?
|
|
237
246
|
|
|
238
247
|
validate_worktree_binding!(binding)
|
|
248
|
+
if git_worktree_manager.merge_in_progress?(binding.origin_root)
|
|
249
|
+
return runtime_output("A merge is already in progress in #{binding.origin_root}. Resolve it with /worktree merge resolve, continue it with /worktree merge continue, or abort it with /worktree merge abort.")
|
|
250
|
+
end
|
|
251
|
+
|
|
239
252
|
source_status = git_worktree_manager.status(binding.path)
|
|
240
253
|
return runtime_output("Worktree has local changes; commit or clean it before merging.") if source_status.dirty?
|
|
241
254
|
|
|
@@ -270,12 +283,62 @@ module Kward
|
|
|
270
283
|
if result.merged?
|
|
271
284
|
runtime_output("Merged #{binding.branch} into #{target_branch}.")
|
|
272
285
|
else
|
|
273
|
-
runtime_output(
|
|
286
|
+
runtime_output(<<~MESSAGE.strip)
|
|
287
|
+
Merge conflicts in #{result.conflicts.join(", ")}.
|
|
288
|
+
Merge paused in #{binding.origin_root}; this tab remains on #{binding.branch}.
|
|
289
|
+
Run /worktree merge resolve to let the agent inspect and fix the conflicts here, or /worktree merge abort to cancel.
|
|
290
|
+
MESSAGE
|
|
274
291
|
end
|
|
275
292
|
rescue GitWorktreeManager::Error => e
|
|
276
293
|
runtime_output("Worktree error: #{e.message}")
|
|
277
294
|
end
|
|
278
295
|
|
|
296
|
+
def resolve_active_worktree_merge
|
|
297
|
+
tab = active_tab
|
|
298
|
+
binding = worktree_binding_for(tab)
|
|
299
|
+
return runtime_output("Tab #{active_tab_number} has no worktree binding.") unless binding
|
|
300
|
+
return runtime_output("Tab #{active_tab_number} is running and cannot resolve its worktree merge yet.") if tab.running? || tab.local_busy? || tab.shell
|
|
301
|
+
return runtime_output("Activate the worktree before resolving its merge.") unless binding.active?
|
|
302
|
+
return runtime_output("No merge is in progress in the original workspace.") unless git_worktree_manager.merge_in_progress?(binding.origin_root)
|
|
303
|
+
|
|
304
|
+
validate_worktree_binding!(binding)
|
|
305
|
+
conflicts = git_worktree_manager.unmerged_paths(binding.origin_root)
|
|
306
|
+
return runtime_output("No unresolved conflicts remain in #{binding.origin_root}; run /worktree merge continue to finish the merge.") if conflicts.empty?
|
|
307
|
+
|
|
308
|
+
submit_tab_input(
|
|
309
|
+
tab,
|
|
310
|
+
<<~PROMPT.strip,
|
|
311
|
+
Resolve the in-progress Git merge in the verified origin workspace. Use target: "origin" for every workspace inspection, edit, and test operation. Read the applicable AGENTS.md files first, inspect the unmerged paths, resolve the conflicts, and run the relevant tests. Do not commit the merge; leave it ready for /worktree merge continue after all conflicts are resolved.
|
|
312
|
+
|
|
313
|
+
Unmerged paths: #{conflicts.join(", ")}
|
|
314
|
+
PROMPT
|
|
315
|
+
display_input: "Resolve merge conflicts in origin"
|
|
316
|
+
)
|
|
317
|
+
rescue GitWorktreeManager::Error => e
|
|
318
|
+
runtime_output("Worktree error: #{e.message}")
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def continue_active_worktree_merge
|
|
322
|
+
tab = active_tab
|
|
323
|
+
binding = worktree_binding_for(tab)
|
|
324
|
+
return runtime_output("Tab #{active_tab_number} has no worktree binding.") unless binding
|
|
325
|
+
return runtime_output("Tab #{active_tab_number} is running and cannot continue its worktree merge yet.") if tab.running? || tab.local_busy? || tab.shell
|
|
326
|
+
return runtime_output("Activate the worktree before continuing its merge.") unless binding.active?
|
|
327
|
+
return runtime_output("No merge is in progress in the original workspace.") unless git_worktree_manager.merge_in_progress?(binding.origin_root)
|
|
328
|
+
|
|
329
|
+
validate_worktree_binding!(binding)
|
|
330
|
+
markers = git_worktree_manager.merge_conflict_marker_paths(binding.origin_root, cached: false)
|
|
331
|
+
return runtime_output("Merge still contains conflict markers: #{markers.join(", ")}.") unless markers.empty?
|
|
332
|
+
target_branch = git_worktree_manager.current_branch(binding.origin_root)
|
|
333
|
+
return runtime_output("Original workspace is in detached HEAD state; resolve that before continuing the merge.") if target_branch.empty?
|
|
334
|
+
return unless confirm_worktree_action("Complete the merge of #{binding.branch} into #{target_branch} in #{binding.origin_root}?")
|
|
335
|
+
|
|
336
|
+
git_worktree_manager.continue_merge(binding.origin_root)
|
|
337
|
+
runtime_output("Completed the merge of #{binding.branch} into #{target_branch}.")
|
|
338
|
+
rescue GitWorktreeManager::Error => e
|
|
339
|
+
runtime_output("Worktree error: #{e.message}")
|
|
340
|
+
end
|
|
341
|
+
|
|
279
342
|
def abort_active_worktree_merge
|
|
280
343
|
tab = active_tab
|
|
281
344
|
binding = worktree_binding_for(tab)
|
data/lib/kward/cli.rb
CHANGED
|
@@ -3,26 +3,26 @@ require "json"
|
|
|
3
3
|
require "thread"
|
|
4
4
|
require "tty-prompt"
|
|
5
5
|
require_relative "agent"
|
|
6
|
-
require_relative "ansi"
|
|
6
|
+
require_relative "terminal/ansi"
|
|
7
7
|
require_relative "version"
|
|
8
8
|
require_relative "model/client"
|
|
9
9
|
require_relative "compactor"
|
|
10
10
|
require_relative "config_files"
|
|
11
|
-
require_relative "clipboard"
|
|
11
|
+
require_relative "terminal/clipboard"
|
|
12
12
|
require_relative "cancellation"
|
|
13
|
-
require_relative "editor_prompt"
|
|
14
|
-
require_relative "editor_prompt_session"
|
|
15
|
-
require_relative "
|
|
16
|
-
require_relative "
|
|
17
|
-
require_relative "
|
|
13
|
+
require_relative "cli/editor_prompt"
|
|
14
|
+
require_relative "cli/editor_prompt_session"
|
|
15
|
+
require_relative "shell/prompt"
|
|
16
|
+
require_relative "shell/prompt_session"
|
|
17
|
+
require_relative "cli/transcript_formatter"
|
|
18
18
|
require_relative "model/context_usage"
|
|
19
19
|
require_relative "events"
|
|
20
|
-
require_relative "export_path"
|
|
21
|
-
require_relative "
|
|
22
|
-
require_relative "
|
|
23
|
-
require_relative "
|
|
24
|
-
require_relative "
|
|
25
|
-
require_relative "
|
|
20
|
+
require_relative "transcripts/export_path"
|
|
21
|
+
require_relative "shell/kwsh"
|
|
22
|
+
require_relative "shell/persistent_session"
|
|
23
|
+
require_relative "pty/interactive_runner"
|
|
24
|
+
require_relative "pty/adaptive_output_sink"
|
|
25
|
+
require_relative "pty/transcript_normalizer"
|
|
26
26
|
require_relative "auth/anthropic_oauth"
|
|
27
27
|
require_relative "auth/api_key_store"
|
|
28
28
|
require_relative "auth/github_oauth"
|
|
@@ -30,7 +30,7 @@ require_relative "auth/openrouter_api_key"
|
|
|
30
30
|
require_relative "image_attachments"
|
|
31
31
|
require_relative "memory/manager"
|
|
32
32
|
require_relative "memory/turn_context"
|
|
33
|
-
require_relative "transcript_export"
|
|
33
|
+
require_relative "transcripts/transcript_export"
|
|
34
34
|
require_relative "message_access"
|
|
35
35
|
require_relative "model/model_info"
|
|
36
36
|
require_relative "model/azure_openai_config"
|
|
@@ -38,18 +38,18 @@ require_relative "model/provider_catalog"
|
|
|
38
38
|
require_relative "model/catalog"
|
|
39
39
|
require_relative "auth/openai_oauth"
|
|
40
40
|
require_relative "pan/server"
|
|
41
|
-
require_relative "
|
|
41
|
+
require_relative "plugins/registry"
|
|
42
42
|
require_relative "prompts/commands"
|
|
43
43
|
require_relative "model/retry_message"
|
|
44
44
|
require_relative "rpc/server"
|
|
45
45
|
require_relative "transport/runtime"
|
|
46
|
-
require_relative "
|
|
47
|
-
require_relative "
|
|
48
|
-
require_relative "
|
|
49
|
-
require_relative "
|
|
50
|
-
require_relative "
|
|
51
|
-
require_relative "
|
|
52
|
-
require_relative "
|
|
46
|
+
require_relative "sessions/diff"
|
|
47
|
+
require_relative "sessions/store"
|
|
48
|
+
require_relative "sessions/naming"
|
|
49
|
+
require_relative "tabs/store"
|
|
50
|
+
require_relative "tabs/driver"
|
|
51
|
+
require_relative "sessions/trash"
|
|
52
|
+
require_relative "sessions/tree_renderer"
|
|
53
53
|
require_relative "skills/capture"
|
|
54
54
|
require_relative "starter_pack_installer"
|
|
55
55
|
require_relative "steering"
|
|
@@ -57,8 +57,8 @@ require_relative "update_check"
|
|
|
57
57
|
require_relative "tools/tool_call"
|
|
58
58
|
require_relative "tools/registry"
|
|
59
59
|
require_relative "telemetry/stats"
|
|
60
|
-
require_relative "
|
|
61
|
-
require_relative "git_worktree_manager"
|
|
60
|
+
require_relative "workspace/factory"
|
|
61
|
+
require_relative "workspace/git_worktree_manager"
|
|
62
62
|
require_relative "cli/commands"
|
|
63
63
|
require_relative "cli/auth_commands"
|
|
64
64
|
require_relative "cli/doctor"
|
|
@@ -92,7 +92,7 @@ module Kward
|
|
|
92
92
|
RESTORED_TOOL_OUTPUT_LIMIT = 2_000
|
|
93
93
|
INTERACTIVE_TOOL_OUTPUT_LINE_LIMIT = 10
|
|
94
94
|
STREAM_RENDER_INTERVAL = 0.025
|
|
95
|
-
INTERACTIVE_EVENT_DRAIN_LIMIT =
|
|
95
|
+
INTERACTIVE_EVENT_DRAIN_LIMIT = 12
|
|
96
96
|
BUILTIN_SLASH_COMMANDS = PromptCommands::BUILTIN_COMMANDS
|
|
97
97
|
BUILTIN_SLASH_COMMAND_NAMES = PromptCommands::BUILTIN_RESERVED_COMMAND_NAMES
|
|
98
98
|
|
|
@@ -156,7 +156,18 @@ module Kward
|
|
|
156
156
|
warn e.message
|
|
157
157
|
warn "Run `kward help` for available commands."
|
|
158
158
|
exit 1
|
|
159
|
+
rescue Client::AuthenticationError => e
|
|
160
|
+
raise if debug_errors?
|
|
161
|
+
|
|
162
|
+
warn authentication_error_message(e)
|
|
163
|
+
exit 1
|
|
164
|
+
rescue StandardError => e
|
|
165
|
+
raise if debug_errors?
|
|
166
|
+
|
|
167
|
+
warn runtime_error_message(e)
|
|
168
|
+
exit 1
|
|
159
169
|
ensure
|
|
170
|
+
shutdown_plugins if respond_to?(:shutdown_plugins, true)
|
|
160
171
|
ConfigFiles.skip_config = false
|
|
161
172
|
end
|
|
162
173
|
|
|
@@ -184,6 +195,30 @@ module Kward
|
|
|
184
195
|
MESSAGE
|
|
185
196
|
end
|
|
186
197
|
|
|
198
|
+
def authentication_error_message(error)
|
|
199
|
+
<<~MESSAGE.rstrip
|
|
200
|
+
Authentication required
|
|
201
|
+
|
|
202
|
+
#{error.message}
|
|
203
|
+
|
|
204
|
+
Run `kward doctor` to inspect your setup.
|
|
205
|
+
MESSAGE
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def runtime_error_message(error)
|
|
209
|
+
<<~MESSAGE.rstrip
|
|
210
|
+
Kward could not complete the request.
|
|
211
|
+
|
|
212
|
+
#{error.message}
|
|
213
|
+
|
|
214
|
+
Set KWARD_DEBUG=1 to show a backtrace.
|
|
215
|
+
MESSAGE
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def debug_errors?
|
|
219
|
+
ENV["KWARD_DEBUG"] == "1"
|
|
220
|
+
end
|
|
221
|
+
|
|
187
222
|
def dispatch
|
|
188
223
|
if @prompt_delimited
|
|
189
224
|
ConfigFiles.ensure_default_config!
|
|
@@ -229,7 +264,7 @@ module Kward
|
|
|
229
264
|
raise ArgumentError, command_usage("doctor") unless @argv.length == 1
|
|
230
265
|
|
|
231
266
|
ensure_client!
|
|
232
|
-
print_doctor
|
|
267
|
+
exit 1 unless print_doctor
|
|
233
268
|
return
|
|
234
269
|
end
|
|
235
270
|
|
|
@@ -239,7 +274,7 @@ module Kward
|
|
|
239
274
|
return
|
|
240
275
|
end
|
|
241
276
|
|
|
242
|
-
handle_hooks_command((@argv[1..] || []).join(" "))
|
|
277
|
+
handle_hooks_command((@argv[1..] || []).join(" "), command_line: true)
|
|
243
278
|
return
|
|
244
279
|
end
|
|
245
280
|
|
|
@@ -390,6 +425,7 @@ module Kward
|
|
|
390
425
|
tool_registry: ToolRegistry.new(
|
|
391
426
|
workspace: configured_workspace,
|
|
392
427
|
prompt: @prompt,
|
|
428
|
+
plugin_tools: plugin_registry.tools,
|
|
393
429
|
hook_manager: hook_manager,
|
|
394
430
|
hook_context: hook_context
|
|
395
431
|
),
|
|
@@ -478,8 +514,9 @@ module Kward
|
|
|
478
514
|
install_bang_completion_provider(agent)
|
|
479
515
|
|
|
480
516
|
loop do
|
|
481
|
-
if
|
|
482
|
-
|
|
517
|
+
service_background_tab_runs if respond_to?(:service_background_tab_runs, true)
|
|
518
|
+
if @pending_inputs.empty? && active_tab&.shell && !active_tab.local_busy?
|
|
519
|
+
run_kwsh_loop(active_tab.shell, tab: active_tab, history: build_kwsh_history(active_tab.agent))
|
|
483
520
|
end
|
|
484
521
|
input = @pending_inputs.shift || (active_tab ? poll_active_tab_input : @prompt.ask("You>"))
|
|
485
522
|
if input.is_a?(Hash) && input[:editor_prompt]
|
|
@@ -537,7 +574,10 @@ module Kward
|
|
|
537
574
|
end
|
|
538
575
|
end
|
|
539
576
|
next if handled
|
|
540
|
-
|
|
577
|
+
if shell_command_input?(command_input)
|
|
578
|
+
shell_result = handle_interactive_shell_command(command_input, agent)
|
|
579
|
+
next if shell_result == true || shell_result == :tab_action
|
|
580
|
+
end
|
|
541
581
|
|
|
542
582
|
flush_pending_reasoning_config(conversation: active_tab.agent.conversation) if active_tab&.agent
|
|
543
583
|
flush_pending_reasoning_config(conversation: agent.conversation) unless active_tab
|
data/lib/kward/compactor.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require_relative "model/chat_invocation"
|
|
3
|
+
require_relative "cancellation"
|
|
3
4
|
require_relative "compaction/file_operation_tracker"
|
|
4
5
|
require_relative "compaction/token_estimator"
|
|
5
6
|
require_relative "config_files"
|
|
@@ -671,29 +672,33 @@ module Kward
|
|
|
671
672
|
@prompt_builder = prompt_builder
|
|
672
673
|
end
|
|
673
674
|
|
|
674
|
-
def summarize(preparation, custom_instructions: nil)
|
|
675
|
+
def summarize(preparation, custom_instructions: nil, cancellation: nil)
|
|
675
676
|
summary = chat(
|
|
676
677
|
@prompt_builder.build(preparation, custom_instructions: custom_instructions),
|
|
677
|
-
max_tokens: @prompt_builder.normal_summary_max_tokens(preparation.settings, model_max_tokens: model_max_tokens)
|
|
678
|
+
max_tokens: @prompt_builder.normal_summary_max_tokens(preparation.settings, model_max_tokens: model_max_tokens),
|
|
679
|
+
cancellation: cancellation
|
|
678
680
|
)
|
|
679
681
|
if preparation.split_turn && !preparation.turn_prefix_messages.empty?
|
|
680
682
|
turn_summary = chat(
|
|
681
683
|
@prompt_builder.build_split(preparation),
|
|
682
|
-
max_tokens: @prompt_builder.split_turn_max_tokens(preparation.settings, model_max_tokens: model_max_tokens)
|
|
684
|
+
max_tokens: @prompt_builder.split_turn_max_tokens(preparation.settings, model_max_tokens: model_max_tokens),
|
|
685
|
+
cancellation: cancellation
|
|
683
686
|
)
|
|
684
687
|
summary = "#{summary}\n\n---\n\n**Turn Context (split turn):**\n\n#{turn_summary}"
|
|
685
688
|
end
|
|
686
689
|
summary.to_s.strip
|
|
687
690
|
rescue Cancelled
|
|
688
691
|
raise
|
|
692
|
+
rescue Kward::Cancellation::CancelledError
|
|
693
|
+
raise
|
|
689
694
|
rescue StandardError => e
|
|
690
695
|
raise SummarizationFailed, e.message
|
|
691
696
|
end
|
|
692
697
|
|
|
693
698
|
private
|
|
694
699
|
|
|
695
|
-
def chat(messages, max_tokens: nil)
|
|
696
|
-
message = ChatInvocation.call(@client, messages, { tools: [], max_tokens: max_tokens })
|
|
700
|
+
def chat(messages, max_tokens: nil, cancellation: nil)
|
|
701
|
+
message = ChatInvocation.call(@client, messages, { tools: [], max_tokens: max_tokens, cancellation: cancellation })
|
|
697
702
|
content = message_content(message)
|
|
698
703
|
text = message_content_text(content).strip
|
|
699
704
|
raise SummarizationFailed, "Compaction produced an empty summary; context was not changed." if text.empty?
|
|
@@ -752,13 +757,19 @@ module Kward
|
|
|
752
757
|
false
|
|
753
758
|
end
|
|
754
759
|
|
|
755
|
-
def compact(custom_instructions: nil, compaction_summary: true)
|
|
760
|
+
def compact(custom_instructions: nil, compaction_summary: true, cancellation: nil)
|
|
761
|
+
cancellation&.raise_if_cancelled!
|
|
756
762
|
old_count = @conversation.messages.length
|
|
757
763
|
preparation = prepare
|
|
758
|
-
summary =
|
|
764
|
+
summary = if cancellation
|
|
765
|
+
@summarizer.summarize(preparation, custom_instructions: custom_instructions, cancellation: cancellation)
|
|
766
|
+
else
|
|
767
|
+
@summarizer.summarize(preparation, custom_instructions: custom_instructions)
|
|
768
|
+
end
|
|
759
769
|
summary = append_files_section(summary, preparation.file_ops)
|
|
760
770
|
raise Compaction::SummarizationFailed, "Compaction produced an empty summary; context was not changed." if summary.strip.empty?
|
|
761
771
|
|
|
772
|
+
cancellation&.raise_if_cancelled!
|
|
762
773
|
@conversation.compact!(
|
|
763
774
|
summary,
|
|
764
775
|
compaction_summary: compaction_summary,
|