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
|
@@ -9,24 +9,24 @@ require_relative "../config_files"
|
|
|
9
9
|
require_relative "../model/context_usage"
|
|
10
10
|
require_relative "../conversation"
|
|
11
11
|
require_relative "../events"
|
|
12
|
-
require_relative "../export_path"
|
|
12
|
+
require_relative "../transcripts/export_path"
|
|
13
13
|
require_relative "../memory/manager"
|
|
14
14
|
require_relative "../memory/turn_context"
|
|
15
15
|
require_relative "../message_access"
|
|
16
16
|
require_relative "../message_text"
|
|
17
|
-
require_relative "../
|
|
17
|
+
require_relative "../sessions/tree_tool_display"
|
|
18
18
|
require_relative "../skills/capture"
|
|
19
19
|
require_relative "../model/model_info"
|
|
20
|
-
require_relative "../
|
|
20
|
+
require_relative "../plugins/registry"
|
|
21
21
|
require_relative "../prompts/commands"
|
|
22
|
-
require_relative "../
|
|
23
|
-
require_relative "../
|
|
24
|
-
require_relative "../
|
|
22
|
+
require_relative "../sessions/store"
|
|
23
|
+
require_relative "../sessions/naming"
|
|
24
|
+
require_relative "../sessions/trash"
|
|
25
25
|
require_relative "../steering"
|
|
26
26
|
require_relative "../tools/tool_call"
|
|
27
27
|
require_relative "../tools/registry"
|
|
28
|
-
require_relative "../transcript_export"
|
|
29
|
-
require_relative "../
|
|
28
|
+
require_relative "../transcripts/transcript_export"
|
|
29
|
+
require_relative "../workspace/factory"
|
|
30
30
|
require_relative "attachment_normalizer"
|
|
31
31
|
require_relative "config_manager"
|
|
32
32
|
require_relative "memory_methods"
|
|
@@ -66,7 +66,7 @@ module Kward
|
|
|
66
66
|
WORKER_STOP_TIMEOUT = 2.0
|
|
67
67
|
WORKER_STOP = Object.new.freeze
|
|
68
68
|
|
|
69
|
-
RpcSession = Struct.new(:id, :workspace_root, :store, :session, :conversation, :agent, :tool_registry, :execution_profile, :prompt, :plugin_output, :queue, :worker, :running_turn_id, :footer_worker, :last_footer_text, keyword_init: true)
|
|
69
|
+
RpcSession = Struct.new(:id, :workspace_root, :store, :session, :conversation, :agent, :tool_registry, :execution_profile, :prompt, :plugin_output, :queue, :worker, :running_turn_id, :footer_worker, :last_footer_text, :last_footer_segments, keyword_init: true)
|
|
70
70
|
Turn = Struct.new(:id, :session_id, :input, :display_input, :status, :cancel_requested, :cancellation, :created_at, :started_at, :finished_at, :events, :next_sequence, :error, :streaming_behavior, :plugin_command_name, :plugin_arguments, :steering, :options, :tool_registry, :execution_profile, :mutex, keyword_init: true)
|
|
71
71
|
|
|
72
72
|
# Creates an object for RPC session lifecycle and turn coordination.
|
|
@@ -215,7 +215,7 @@ module Kward
|
|
|
215
215
|
|
|
216
216
|
# Returns the plugin registry shared by RPC sessions and plugin chats.
|
|
217
217
|
def plugin_registry
|
|
218
|
-
@plugin_registry ||= PluginRegistry.load(reserved_commands: reserved_plugin_command_names)
|
|
218
|
+
@plugin_registry ||= PluginRegistry.load(reserved_commands: reserved_plugin_command_names).tap(&:start!)
|
|
219
219
|
end
|
|
220
220
|
|
|
221
221
|
# Renames the persisted session attached to an RPC session id.
|
|
@@ -392,6 +392,11 @@ module Kward
|
|
|
392
392
|
{ closed: true }
|
|
393
393
|
end
|
|
394
394
|
|
|
395
|
+
def shutdown_plugins
|
|
396
|
+
@plugin_registry&.shutdown!
|
|
397
|
+
{ closed: true }
|
|
398
|
+
end
|
|
399
|
+
|
|
395
400
|
# Returns the normalized transcript for the active RPC session.
|
|
396
401
|
def transcript(session_id:)
|
|
397
402
|
rpc_session = fetch_session(session_id)
|
|
@@ -490,6 +495,12 @@ module Kward
|
|
|
490
495
|
{ ok: true }
|
|
491
496
|
end
|
|
492
497
|
|
|
498
|
+
def answer_plugin_ui(session_id:, request_id:, value:)
|
|
499
|
+
rpc_session = fetch_session(session_id)
|
|
500
|
+
rpc_session.prompt.answer_plugin_ui(request_id, value)
|
|
501
|
+
{ ok: true }
|
|
502
|
+
end
|
|
503
|
+
|
|
493
504
|
def answer_tool_approval(session_id:, approval_request_id:, approved:)
|
|
494
505
|
rpc_session = fetch_session(session_id)
|
|
495
506
|
rpc_session.prompt.answer_tool_approval(approval_request_id, approved: approved)
|
|
@@ -525,18 +536,39 @@ module Kward
|
|
|
525
536
|
|
|
526
537
|
def run_plugin_command(session_id:, command:, arguments: "")
|
|
527
538
|
rpc_session = fetch_session(session_id)
|
|
539
|
+
raise ArgumentError, "Plugin commands are disabled for this session" if rpc_session.execution_profile && !rpc_session.execution_profile.plugin_commands
|
|
540
|
+
|
|
528
541
|
command = plugin_registry.command_for(command.to_s.delete_prefix("/")) || raise(ArgumentError, "Unknown plugin command: #{command}")
|
|
542
|
+
arguments = command.parse_arguments(arguments)
|
|
529
543
|
output = []
|
|
530
|
-
context = plugin_context(rpc_session, args: arguments
|
|
531
|
-
result = command.handler.call(arguments
|
|
544
|
+
context = plugin_context(rpc_session, args: arguments, say_callback: lambda { |message| output << message.to_s })
|
|
545
|
+
result = command.normalize_result(command.handler.call(arguments, context))
|
|
532
546
|
output = rpc_session.plugin_output.shift(rpc_session.plugin_output.length) + output
|
|
533
|
-
|
|
547
|
+
serialized_result = command.typed? ? result.to_h : (result.nil? ? nil : result.to_s)
|
|
548
|
+
{ command: command.name, output: output, result: serialized_result }
|
|
534
549
|
end
|
|
535
550
|
|
|
536
551
|
def plugin_commands
|
|
537
552
|
plugin_registry.commands
|
|
538
553
|
end
|
|
539
554
|
|
|
555
|
+
def plugin_actions
|
|
556
|
+
plugin_registry.actions
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def run_plugin_action(session_id:, id:, arguments: {})
|
|
560
|
+
rpc_session = fetch_session(session_id)
|
|
561
|
+
raise ArgumentError, "Plugin actions are disabled for this session" if rpc_session.execution_profile && !rpc_session.execution_profile.plugin_commands
|
|
562
|
+
|
|
563
|
+
action = plugin_registry.action_for(id) || raise(ArgumentError, "Unknown plugin action: #{id}")
|
|
564
|
+
arguments = action.parse_arguments(arguments)
|
|
565
|
+
output = []
|
|
566
|
+
context = plugin_context(rpc_session, args: arguments, say_callback: lambda { |message| output << message.to_s })
|
|
567
|
+
result = action.normalize_result(action.handler.call(arguments, context))
|
|
568
|
+
output = rpc_session.plugin_output.shift(rpc_session.plugin_output.length) + output
|
|
569
|
+
{ action: action.id, output: output, result: result.to_h }
|
|
570
|
+
end
|
|
571
|
+
|
|
540
572
|
def available_models
|
|
541
573
|
models = @client.respond_to?(:available_models) ? Array(@client.available_models) : []
|
|
542
574
|
normalized = models.map { |model| normalize_model(model) }
|
|
@@ -628,6 +660,8 @@ module Kward
|
|
|
628
660
|
|
|
629
661
|
def reload_plugins
|
|
630
662
|
registry = PluginRegistry.load(reserved_commands: reserved_plugin_command_names)
|
|
663
|
+
@plugin_registry&.reload!
|
|
664
|
+
registry.start!
|
|
631
665
|
sessions = @mutex.synchronize do
|
|
632
666
|
@plugin_registry = registry
|
|
633
667
|
@sessions.values
|
|
@@ -636,7 +670,7 @@ module Kward
|
|
|
636
670
|
rpc_session.conversation.plugin_registry = registry if rpc_session.conversation.respond_to?(:plugin_registry=)
|
|
637
671
|
rpc_session.conversation.refresh_system_message! if rpc_session.conversation.respond_to?(:refresh_system_message!)
|
|
638
672
|
rebuild_session_tools(rpc_session)
|
|
639
|
-
if registry.
|
|
673
|
+
if registry.status?
|
|
640
674
|
start_footer_worker(rpc_session)
|
|
641
675
|
emit_footer_update(rpc_session)
|
|
642
676
|
else
|
|
@@ -714,7 +748,9 @@ module Kward
|
|
|
714
748
|
hook_context = lifecycle_hook_context(
|
|
715
749
|
conversation: rpc_session.conversation,
|
|
716
750
|
session: rpc_session.session,
|
|
717
|
-
workspace_root: rpc_session.workspace_root
|
|
751
|
+
workspace_root: rpc_session.workspace_root,
|
|
752
|
+
prompt: rpc_session.prompt,
|
|
753
|
+
session_id: rpc_session.id
|
|
718
754
|
)
|
|
719
755
|
hook_manager = lifecycle_hook_manager(rpc_session.workspace_root)
|
|
720
756
|
tool_registry = build_tool_registry(
|
|
@@ -1042,7 +1078,13 @@ module Kward
|
|
|
1042
1078
|
conversation.plugin_registry ||= plugin_registry if conversation.respond_to?(:plugin_registry)
|
|
1043
1079
|
id = SecureRandom.uuid
|
|
1044
1080
|
prompt = PromptBridge.new(notify: method(:notify), session_id: id)
|
|
1045
|
-
hook_context = lifecycle_hook_context(
|
|
1081
|
+
hook_context = lifecycle_hook_context(
|
|
1082
|
+
conversation: conversation,
|
|
1083
|
+
session: session,
|
|
1084
|
+
workspace_root: workspace_root,
|
|
1085
|
+
prompt: prompt,
|
|
1086
|
+
session_id: id
|
|
1087
|
+
)
|
|
1046
1088
|
hook_manager = lifecycle_hook_manager(workspace_root)
|
|
1047
1089
|
tool_registry = build_tool_registry(workspace_root, prompt, hook_manager: hook_manager, hook_context: hook_context)
|
|
1048
1090
|
agent = Agent.new(
|
|
@@ -1074,6 +1116,7 @@ module Kward
|
|
|
1074
1116
|
workspace: configured_workspace(workspace_root),
|
|
1075
1117
|
prompt: prompt,
|
|
1076
1118
|
allowed_tool_names: allowed_tool_names,
|
|
1119
|
+
plugin_tools: plugin_registry.tools,
|
|
1077
1120
|
tool_approval: tool_approval,
|
|
1078
1121
|
approval_for_allowed_tools: !tool_approval.nil?,
|
|
1079
1122
|
hook_manager: hook_manager,
|
|
@@ -1115,13 +1158,16 @@ module Kward
|
|
|
1115
1158
|
})
|
|
1116
1159
|
end
|
|
1117
1160
|
|
|
1118
|
-
def lifecycle_hook_context(conversation:, session:, workspace_root:)
|
|
1161
|
+
def lifecycle_hook_context(conversation:, session:, workspace_root:, prompt: nil, session_id: nil)
|
|
1162
|
+
say_callback = lambda { |message| notify("hook/message", { message: message.to_s }) }
|
|
1119
1163
|
PluginRegistry::Context.new(
|
|
1120
1164
|
conversation: conversation,
|
|
1121
1165
|
args: "",
|
|
1122
1166
|
session: session,
|
|
1123
1167
|
workspace_root: workspace_root,
|
|
1124
|
-
say_callback:
|
|
1168
|
+
say_callback: say_callback,
|
|
1169
|
+
ui: rpc_plugin_ui(prompt: prompt, session_id: session_id, say_callback: say_callback, requests: false),
|
|
1170
|
+
tool_ui: rpc_plugin_ui(prompt: prompt, session_id: session_id, say_callback: say_callback, requests: !prompt.nil?)
|
|
1125
1171
|
)
|
|
1126
1172
|
end
|
|
1127
1173
|
|
|
@@ -1212,7 +1258,7 @@ module Kward
|
|
|
1212
1258
|
end
|
|
1213
1259
|
|
|
1214
1260
|
def start_footer_worker(rpc_session)
|
|
1215
|
-
return unless plugin_registry.
|
|
1261
|
+
return unless plugin_registry.status?
|
|
1216
1262
|
return if rpc_session.footer_worker&.alive?
|
|
1217
1263
|
|
|
1218
1264
|
rpc_session.footer_worker = Thread.new do
|
|
@@ -1290,18 +1336,7 @@ module Kward
|
|
|
1290
1336
|
if turn.plugin_command_name
|
|
1291
1337
|
run_plugin_turn(rpc_session, turn)
|
|
1292
1338
|
else
|
|
1293
|
-
|
|
1294
|
-
prepare_memory_context(rpc_session.conversation, turn.input) unless memory_disabled?(turn)
|
|
1295
|
-
rpc_session.agent.ask(turn.input, display_input: turn_display_input(turn), cancellation: turn.cancellation, steering: turn.steering, options: turn.options || {}, tool_registry: turn.tool_registry) do |event|
|
|
1296
|
-
next if turn.cancel_requested
|
|
1297
|
-
|
|
1298
|
-
notify_plugin_transcript_event(rpc_session, event)
|
|
1299
|
-
handle_agent_event(turn, event)
|
|
1300
|
-
end
|
|
1301
|
-
unless memory_disabled?(turn)
|
|
1302
|
-
persist_memory_state(rpc_session)
|
|
1303
|
-
auto_summarize_memory(rpc_session) unless turn.cancel_requested
|
|
1304
|
-
end
|
|
1339
|
+
run_session_model_turn(rpc_session, turn, input: turn.input, display_input: turn_display_input(turn))
|
|
1305
1340
|
finish_turn(turn, turn.cancel_requested ? "canceled" : "completed")
|
|
1306
1341
|
end
|
|
1307
1342
|
rescue Cancellation::CancelledError
|
|
@@ -1316,6 +1351,22 @@ module Kward
|
|
|
1316
1351
|
rpc_session.running_turn_id = nil
|
|
1317
1352
|
end
|
|
1318
1353
|
|
|
1354
|
+
def run_session_model_turn(rpc_session, turn, input:, display_input: nil)
|
|
1355
|
+
auto_name_session(rpc_session, display_input || turn.display_input || input)
|
|
1356
|
+
memory_input = input.is_a?(PluginTurnRequest) ? input.to_s : input
|
|
1357
|
+
prepare_memory_context(rpc_session.conversation, memory_input) unless memory_disabled?(turn)
|
|
1358
|
+
rpc_session.agent.ask(input, display_input: display_input, cancellation: turn.cancellation, steering: turn.steering, options: turn.options || {}, tool_registry: turn.tool_registry) do |event|
|
|
1359
|
+
next if turn.cancel_requested
|
|
1360
|
+
|
|
1361
|
+
notify_plugin_transcript_event(rpc_session, event)
|
|
1362
|
+
handle_agent_event(turn, event)
|
|
1363
|
+
end
|
|
1364
|
+
unless memory_disabled?(turn)
|
|
1365
|
+
persist_memory_state(rpc_session)
|
|
1366
|
+
auto_summarize_memory(rpc_session) unless turn.cancel_requested
|
|
1367
|
+
end
|
|
1368
|
+
end
|
|
1369
|
+
|
|
1319
1370
|
def build_steering(_turn)
|
|
1320
1371
|
Steering.new
|
|
1321
1372
|
end
|
|
@@ -1371,12 +1422,44 @@ module Kward
|
|
|
1371
1422
|
turn_payload(turn)
|
|
1372
1423
|
end
|
|
1373
1424
|
|
|
1374
|
-
def plugin_context(rpc_session, args: nil, say_callback:)
|
|
1425
|
+
def plugin_context(rpc_session, args: nil, say_callback:, requests: false, cancellation: nil, turn_command: nil)
|
|
1375
1426
|
PluginRegistry::Context.new(
|
|
1376
1427
|
conversation: rpc_session.conversation,
|
|
1377
1428
|
args: args,
|
|
1378
1429
|
session: rpc_session.session,
|
|
1379
1430
|
workspace_root: rpc_session.workspace_root,
|
|
1431
|
+
say_callback: say_callback,
|
|
1432
|
+
cancellation: cancellation,
|
|
1433
|
+
turn_command: turn_command,
|
|
1434
|
+
ui: rpc_plugin_ui(
|
|
1435
|
+
prompt: rpc_session.prompt,
|
|
1436
|
+
session_id: rpc_session.id,
|
|
1437
|
+
say_callback: say_callback,
|
|
1438
|
+
requests: requests
|
|
1439
|
+
)
|
|
1440
|
+
)
|
|
1441
|
+
end
|
|
1442
|
+
|
|
1443
|
+
def rpc_plugin_ui(prompt:, session_id:, say_callback:, requests:)
|
|
1444
|
+
capabilities = { progress: true, notify: true }
|
|
1445
|
+
requester = nil
|
|
1446
|
+
if requests && prompt
|
|
1447
|
+
capabilities.merge!(question: true, select: true, confirm: true, input: true)
|
|
1448
|
+
requester = lambda do |kind, payload, cancellation: nil|
|
|
1449
|
+
if kind == :question
|
|
1450
|
+
prompt.ask_user_question(payload.fetch(:questions), cancellation: cancellation)
|
|
1451
|
+
else
|
|
1452
|
+
prompt.request_plugin_ui(kind, payload, cancellation: cancellation)
|
|
1453
|
+
end
|
|
1454
|
+
end
|
|
1455
|
+
end
|
|
1456
|
+
emitter = lambda do |kind, payload|
|
|
1457
|
+
method = kind == :progress ? "ui/progress" : "ui/notification"
|
|
1458
|
+
payload = payload.merge(level: payload[:level].to_s) if payload[:level]
|
|
1459
|
+
notify(method, { sessionId: session_id }.merge(payload))
|
|
1460
|
+
end
|
|
1461
|
+
PluginUI.new(
|
|
1462
|
+
backend: PluginUI::Backend.new(capabilities: capabilities, requester: requester, emitter: emitter),
|
|
1380
1463
|
say_callback: say_callback
|
|
1381
1464
|
)
|
|
1382
1465
|
end
|
|
@@ -1384,13 +1467,30 @@ module Kward
|
|
|
1384
1467
|
def run_plugin_turn(rpc_session, turn)
|
|
1385
1468
|
turn.cancellation&.raise_if_cancelled!
|
|
1386
1469
|
command = plugin_registry.command_for(turn.plugin_command_name) || raise(ArgumentError, "Unknown plugin command: #{turn.plugin_command_name}")
|
|
1470
|
+
arguments = command.parse_arguments(turn.plugin_arguments)
|
|
1387
1471
|
output = []
|
|
1388
|
-
context = plugin_context(
|
|
1389
|
-
|
|
1472
|
+
context = plugin_context(
|
|
1473
|
+
rpc_session,
|
|
1474
|
+
args: arguments,
|
|
1475
|
+
say_callback: lambda { |message| output << message.to_s },
|
|
1476
|
+
requests: true,
|
|
1477
|
+
cancellation: turn.cancellation,
|
|
1478
|
+
turn_command: command
|
|
1479
|
+
)
|
|
1480
|
+
result = command.normalize_result(command.handler.call(arguments, context))
|
|
1481
|
+
turn.cancellation&.raise_if_cancelled!
|
|
1482
|
+
if command.typed?
|
|
1483
|
+
emit_turn_event(turn, "pluginCommandResult", { command: command.name, result: result.to_h })
|
|
1484
|
+
result = result.message
|
|
1485
|
+
end
|
|
1390
1486
|
answer = (output + [result]).compact.map(&:to_s).reject(&:empty?).join("\n")
|
|
1391
1487
|
unless answer.empty?
|
|
1392
1488
|
emit_turn_event(turn, "assistantDelta", { delta: answer })
|
|
1393
|
-
emit_turn_event(turn, "answer", { content: answer })
|
|
1489
|
+
emit_turn_event(turn, "answer", { content: answer }) unless context.requested_turn
|
|
1490
|
+
end
|
|
1491
|
+
if context.requested_turn
|
|
1492
|
+
turn.steering = build_steering(turn) if supports_in_flight_steer?
|
|
1493
|
+
run_session_model_turn(rpc_session, turn, input: context.requested_turn, display_input: context.requested_turn.to_s)
|
|
1394
1494
|
end
|
|
1395
1495
|
finish_turn(turn, turn.cancel_requested ? "canceled" : "completed")
|
|
1396
1496
|
end
|
|
@@ -1457,27 +1557,30 @@ module Kward
|
|
|
1457
1557
|
end
|
|
1458
1558
|
|
|
1459
1559
|
def emit_footer_update(rpc_session)
|
|
1460
|
-
|
|
1461
|
-
return clear_footer_update(rpc_session) unless renderer
|
|
1560
|
+
return clear_footer_update(rpc_session) unless plugin_registry.status?
|
|
1462
1561
|
|
|
1463
|
-
|
|
1562
|
+
segments = begin
|
|
1464
1563
|
context = plugin_context(rpc_session, say_callback: lambda { |message| rpc_session.plugin_output << message.to_s })
|
|
1465
|
-
|
|
1564
|
+
plugin_registry.status_segments(context)
|
|
1466
1565
|
rescue StandardError => e
|
|
1467
1566
|
warn "Warning: Kward plugin footer error: #{e.message}"
|
|
1468
|
-
|
|
1567
|
+
[]
|
|
1469
1568
|
end
|
|
1470
|
-
|
|
1569
|
+
segment_payloads = segments.map(&:to_h)
|
|
1570
|
+
text = plugin_registry.compose_status(segments)
|
|
1571
|
+
return if rpc_session.last_footer_text == text && rpc_session.last_footer_segments == segment_payloads
|
|
1471
1572
|
|
|
1472
1573
|
rpc_session.last_footer_text = text
|
|
1473
|
-
|
|
1574
|
+
rpc_session.last_footer_segments = segment_payloads
|
|
1575
|
+
notify("ui/footer", { sessionId: rpc_session.id, text: text, segments: segment_payloads })
|
|
1474
1576
|
end
|
|
1475
1577
|
|
|
1476
1578
|
def clear_footer_update(rpc_session)
|
|
1477
|
-
return if rpc_session.last_footer_text.to_s.empty?
|
|
1579
|
+
return if rpc_session.last_footer_text.to_s.empty? && Array(rpc_session.last_footer_segments).empty?
|
|
1478
1580
|
|
|
1479
1581
|
rpc_session.last_footer_text = ""
|
|
1480
|
-
|
|
1582
|
+
rpc_session.last_footer_segments = []
|
|
1583
|
+
notify("ui/footer", { sessionId: rpc_session.id, text: "", segments: [] })
|
|
1481
1584
|
end
|
|
1482
1585
|
|
|
1483
1586
|
def emit_turn_event(turn, type, payload)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require_relative "../message_access"
|
|
2
2
|
require_relative "../message_text"
|
|
3
|
-
require_relative "../
|
|
4
|
-
require_relative "../
|
|
3
|
+
require_relative "../sessions/tree_nodes"
|
|
4
|
+
require_relative "../sessions/tree_tool_display"
|
|
5
5
|
|
|
6
6
|
# Namespace for the Kward CLI agent runtime.
|
|
7
7
|
module Kward
|
|
@@ -173,10 +173,14 @@ module Kward
|
|
|
173
173
|
when "reasoning"
|
|
174
174
|
reasoning_item_blocks(item).map { |thinking| { type: "thinking", thinking: thinking } }
|
|
175
175
|
when "message"
|
|
176
|
-
next [] if ToolCall.value(item, :phase).to_s == "commentary"
|
|
177
|
-
|
|
178
176
|
text = response_message_item_text(item)
|
|
179
|
-
|
|
177
|
+
next [] if text.empty?
|
|
178
|
+
|
|
179
|
+
if ToolCall.value(item, :phase).to_s == "commentary"
|
|
180
|
+
[{ type: "thinking", thinking: text }]
|
|
181
|
+
else
|
|
182
|
+
[{ type: "text", text: text }]
|
|
183
|
+
end
|
|
180
184
|
else
|
|
181
185
|
[]
|
|
182
186
|
end
|
|
@@ -3,15 +3,15 @@ require "digest"
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "securerandom"
|
|
5
5
|
require "time"
|
|
6
|
-
require_relative "config_files"
|
|
7
|
-
require_relative "conversation"
|
|
8
|
-
require_relative "message_access"
|
|
9
|
-
require_relative "message_text"
|
|
10
|
-
require_relative "private_file"
|
|
11
|
-
require_relative "rpc/tool_event_normalizer"
|
|
12
|
-
require_relative "
|
|
13
|
-
require_relative "tools/tool_call"
|
|
14
|
-
require_relative "workspace"
|
|
6
|
+
require_relative "../config_files"
|
|
7
|
+
require_relative "../conversation"
|
|
8
|
+
require_relative "../message_access"
|
|
9
|
+
require_relative "../message_text"
|
|
10
|
+
require_relative "../private_file"
|
|
11
|
+
require_relative "../rpc/tool_event_normalizer"
|
|
12
|
+
require_relative "catalog"
|
|
13
|
+
require_relative "../tools/tool_call"
|
|
14
|
+
require_relative "../workspace/workspace"
|
|
15
15
|
|
|
16
16
|
# Namespace for the Kward CLI agent runtime.
|
|
17
17
|
module Kward
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
require_relative "message_access"
|
|
2
|
-
require_relative "message_text"
|
|
3
|
-
require_relative "tools/tool_call"
|
|
1
|
+
require_relative "../message_access"
|
|
2
|
+
require_relative "../message_text"
|
|
3
|
+
require_relative "../tools/tool_call"
|
|
4
4
|
|
|
5
5
|
# Namespace for the Kward CLI agent runtime.
|
|
6
6
|
module Kward
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
require_relative "message_access"
|
|
2
|
-
require_relative "message_text"
|
|
3
|
-
require_relative "
|
|
4
|
-
require_relative "
|
|
1
|
+
require_relative "../message_access"
|
|
2
|
+
require_relative "../message_text"
|
|
3
|
+
require_relative "tree_nodes"
|
|
4
|
+
require_relative "tree_tool_display"
|
|
5
5
|
|
|
6
6
|
# Namespace for the Kward CLI agent runtime.
|
|
7
7
|
module Kward
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
require "shellwords"
|
|
2
|
-
require_relative "ansi"
|
|
3
|
-
require_relative "local_command_runner"
|
|
2
|
+
require_relative "../terminal/ansi"
|
|
3
|
+
require_relative "../pty/local_command_runner"
|
|
4
4
|
begin
|
|
5
|
-
require_relative "
|
|
5
|
+
require_relative "../pty/local_pty_runner"
|
|
6
6
|
rescue LoadError
|
|
7
7
|
nil
|
|
8
8
|
end
|
|
@@ -10,10 +10,10 @@ end
|
|
|
10
10
|
# Namespace for the Kward CLI agent runtime.
|
|
11
11
|
module Kward
|
|
12
12
|
# Kward-native embedded shell command runner.
|
|
13
|
-
class
|
|
13
|
+
class Kwsh
|
|
14
14
|
Result = Struct.new(:output, :exit_status, :exit_shell, :clear, :open_editor_path, :interactive_command, :streamed, keyword_init: true)
|
|
15
15
|
Completion = Struct.new(:range, :replacement, :candidates, keyword_init: true)
|
|
16
|
-
BUILTINS = %w[alias capture cd pwd export unset unalias clear exit logout pty].freeze
|
|
16
|
+
BUILTINS = %w[alias capture cd pwd export source unset unalias clear exit logout pty].freeze
|
|
17
17
|
DEFAULT_SHELL = "/bin/sh"
|
|
18
18
|
DEFAULT_TIMEOUT_SECONDS = 300
|
|
19
19
|
DEFAULT_MAX_OUTPUT_BYTES = 1_048_576
|
|
@@ -60,7 +60,7 @@ module Kward
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
# Runs a command for the shell assistant without handing it the terminal.
|
|
63
|
-
# Built-ins still execute in this
|
|
63
|
+
# Built-ins still execute in this Kwsh instance so their state persists.
|
|
64
64
|
def run_for_agent(input, timeout_seconds: nil, cancellation: nil)
|
|
65
65
|
command = input.to_s.strip
|
|
66
66
|
return run(command, cancellation: cancellation) if command.empty?
|
|
@@ -322,12 +322,12 @@ module Kward
|
|
|
322
322
|
return nil unless %w[exit logout].include?(words.first)
|
|
323
323
|
|
|
324
324
|
if words.length > 2 || (words[1] && !words[1].match?(/\A\d+\z/))
|
|
325
|
-
return Result.new(output: "#{command_echo(display_command)}
|
|
325
|
+
return Result.new(output: "#{command_echo(display_command)}kwsh: #{words.first}: numeric status expected\n", exit_status: 2)
|
|
326
326
|
end
|
|
327
327
|
|
|
328
328
|
Result.new(output: command_echo(display_command), exit_status: words[1].to_i, exit_shell: true)
|
|
329
329
|
rescue ArgumentError => e
|
|
330
|
-
Result.new(output: "#{command_echo(display_command)}
|
|
330
|
+
Result.new(output: "#{command_echo(display_command)}kwsh: #{e.message}\n", exit_status: 2)
|
|
331
331
|
end
|
|
332
332
|
|
|
333
333
|
def builtin_result(command, display_command: command, cancellation: nil, &block)
|
|
@@ -347,6 +347,8 @@ module Kward
|
|
|
347
347
|
print_working_directory(display_command, words)
|
|
348
348
|
when "export"
|
|
349
349
|
export_variables(display_command, words)
|
|
350
|
+
when "source", "."
|
|
351
|
+
source_file(display_command, words)
|
|
350
352
|
when "unset"
|
|
351
353
|
unset_variables(display_command, words)
|
|
352
354
|
when "clear"
|
|
@@ -359,7 +361,7 @@ module Kward
|
|
|
359
361
|
nil
|
|
360
362
|
end
|
|
361
363
|
rescue ArgumentError => e
|
|
362
|
-
Result.new(output: "#{command_echo(display_command)}
|
|
364
|
+
Result.new(output: "#{command_echo(display_command)}kwsh: #{e.message}\n", exit_status: 2)
|
|
363
365
|
end
|
|
364
366
|
|
|
365
367
|
def captured_command_result(command, display_command:, cancellation: nil, &block)
|
|
@@ -399,7 +401,7 @@ module Kward
|
|
|
399
401
|
invalid << name
|
|
400
402
|
end
|
|
401
403
|
end
|
|
402
|
-
return Result.new(output: "#{command_echo(command)}
|
|
404
|
+
return Result.new(output: "#{command_echo(command)}kwsh: alias: invalid name: #{invalid.join(" ")}\n", exit_status: 2) unless invalid.empty?
|
|
403
405
|
|
|
404
406
|
names = @aliases.keys.sort if names.empty? && assignments.empty?
|
|
405
407
|
lines = names.filter_map { |name| @aliases[name] ? "alias #{name}=#{Shellwords.escape(@aliases.fetch(name))}" : nil }
|
|
@@ -417,7 +419,7 @@ module Kward
|
|
|
417
419
|
end
|
|
418
420
|
|
|
419
421
|
missing = words.drop(1).reject { |name| @aliases.delete(name) }
|
|
420
|
-
return Result.new(output: "#{command_echo(command)}
|
|
422
|
+
return Result.new(output: "#{command_echo(command)}kwsh: unalias: not found: #{missing.join(" ")}\n", exit_status: 1) unless missing.empty?
|
|
421
423
|
|
|
422
424
|
Result.new(output: command_echo(command), exit_status: 0)
|
|
423
425
|
end
|
|
@@ -455,7 +457,7 @@ module Kward
|
|
|
455
457
|
path = File.expand_path(words[2], @cwd)
|
|
456
458
|
Result.new(output: command_echo(display_command), exit_status: 0, open_editor_path: path)
|
|
457
459
|
rescue ArgumentError => e
|
|
458
|
-
Result.new(output: "#{command_echo(display_command)}
|
|
460
|
+
Result.new(output: "#{command_echo(display_command)}kwsh: #{e.message}\n", exit_status: 2)
|
|
459
461
|
end
|
|
460
462
|
|
|
461
463
|
def kward_edit_command?(words)
|
|
@@ -489,7 +491,7 @@ module Kward
|
|
|
489
491
|
|
|
490
492
|
def change_directory(command, words)
|
|
491
493
|
if words.length > 2
|
|
492
|
-
return Result.new(output: "#{command_echo(command)}
|
|
494
|
+
return Result.new(output: "#{command_echo(command)}kwsh: cd: too many arguments\n", exit_status: 2)
|
|
493
495
|
end
|
|
494
496
|
|
|
495
497
|
target = words[1]
|
|
@@ -497,7 +499,7 @@ module Kward
|
|
|
497
499
|
target = @previous_cwd || @cwd if target == "-"
|
|
498
500
|
path = File.expand_path(target, @cwd)
|
|
499
501
|
unless File.directory?(path)
|
|
500
|
-
return Result.new(output: "#{command_echo(command)}
|
|
502
|
+
return Result.new(output: "#{command_echo(command)}kwsh: cd: no such directory: #{target}\n", exit_status: 1)
|
|
501
503
|
end
|
|
502
504
|
|
|
503
505
|
@previous_cwd = @cwd
|
|
@@ -528,10 +530,27 @@ module Kward
|
|
|
528
530
|
if invalid.empty?
|
|
529
531
|
Result.new(output: command_echo(command), exit_status: 0)
|
|
530
532
|
else
|
|
531
|
-
Result.new(output: "#{command_echo(command)}
|
|
533
|
+
Result.new(output: "#{command_echo(command)}kwsh: export: invalid assignment: #{invalid.join(" ")}\n", exit_status: 2)
|
|
532
534
|
end
|
|
533
535
|
end
|
|
534
536
|
|
|
537
|
+
def source_file(command, words)
|
|
538
|
+
unless words.length == 2
|
|
539
|
+
return Result.new(output: "#{command_echo(command)}Usage: source <file>\n", exit_status: 2)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
require_relative "kwshrc"
|
|
543
|
+
path = Kwshrc.resolve_path(words[1], cwd: @cwd, env: @env)
|
|
544
|
+
config = Kwshrc.read_file(path, env: @env)
|
|
545
|
+
path_changed = config[:env].key?("PATH")
|
|
546
|
+
@env.merge!(config[:env])
|
|
547
|
+
@aliases.merge!(config[:aliases])
|
|
548
|
+
invalidate_path_executables_cache if path_changed
|
|
549
|
+
Result.new(output: command_echo(command), exit_status: 0)
|
|
550
|
+
rescue Kwshrc::ParseError => e
|
|
551
|
+
Result.new(output: "#{command_echo(command)}kwsh: source: #{e.message}\n", exit_status: 1)
|
|
552
|
+
end
|
|
553
|
+
|
|
535
554
|
def unset_variables(command, words)
|
|
536
555
|
names = words.drop(1)
|
|
537
556
|
names.shift if names.first == "--"
|
|
@@ -541,7 +560,7 @@ module Kward
|
|
|
541
560
|
if invalid.empty?
|
|
542
561
|
Result.new(output: command_echo(command), exit_status: 0)
|
|
543
562
|
else
|
|
544
|
-
Result.new(output: "#{command_echo(command)}
|
|
563
|
+
Result.new(output: "#{command_echo(command)}kwsh: unset: invalid name: #{invalid.join(" ")}\n", exit_status: 2)
|
|
545
564
|
end
|
|
546
565
|
end
|
|
547
566
|
|
|
@@ -575,8 +594,8 @@ module Kward
|
|
|
575
594
|
end
|
|
576
595
|
append_output_newline(output) { |text| yield text if streamed }
|
|
577
596
|
exit_status = result.timed_out || result.truncated ? 1 : (result.exit_status || 1)
|
|
578
|
-
append_streamed(output, "
|
|
579
|
-
append_streamed(output, "
|
|
597
|
+
append_streamed(output, "kwsh: command timed out after #{timeout} seconds\n", streamed) { |text| yield text } if result.timed_out
|
|
598
|
+
append_streamed(output, "kwsh: output exceeded #{@max_output_bytes} bytes; command terminated\n", streamed) { |text| yield text } if result.truncated
|
|
580
599
|
append_streamed(output, "Exit status: #{exit_status}\n", streamed) { |text| yield text } unless exit_status.zero?
|
|
581
600
|
Result.new(output: output, exit_status: exit_status, streamed: streamed)
|
|
582
601
|
rescue Cancellation::CancelledError
|
|
@@ -584,7 +603,7 @@ module Kward
|
|
|
584
603
|
append_streamed(output, "^C\nExit status: 130\n", streamed) { |text| yield text }
|
|
585
604
|
Result.new(output: output, exit_status: 130, streamed: streamed)
|
|
586
605
|
rescue Errno::ENOENT => e
|
|
587
|
-
Result.new(output: "#{command_echo(display_command)}
|
|
606
|
+
Result.new(output: "#{command_echo(display_command)}kwsh: #{e.message}\n", exit_status: 127)
|
|
588
607
|
end
|
|
589
608
|
|
|
590
609
|
def effective_timeout(timeout_seconds)
|