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
data/lib/kward/cli/rendering.rb
CHANGED
|
@@ -223,7 +223,10 @@ module Kward
|
|
|
223
223
|
# Writes the user transcript output for the terminal CLI flow.
|
|
224
224
|
def print_user_transcript(input, display_input: nil, attachment_references: nil, image_parts: nil)
|
|
225
225
|
visible_input = display_input.nil? ? input : display_input
|
|
226
|
-
write_prompt_transcript("\n#{colored("You>", :blue, :bold)} #{visible_input}\n")
|
|
226
|
+
write_prompt_transcript("\n#{colored("❯ You>", :blue, :bold)} #{visible_input}\n")
|
|
227
|
+
# System-turn instructions are plain text, never image attachments.
|
|
228
|
+
return if input.is_a?(PluginTurnRequest)
|
|
229
|
+
|
|
227
230
|
print_attachment_badges(input, references: attachment_references)
|
|
228
231
|
print_pasted_images(input, image_parts: image_parts)
|
|
229
232
|
end
|
|
@@ -347,8 +350,9 @@ module Kward
|
|
|
347
350
|
end
|
|
348
351
|
|
|
349
352
|
# Writes the tool result output for the terminal CLI flow.
|
|
350
|
-
def print_tool_result(tool_call, content, line_limit: nil)
|
|
353
|
+
def print_tool_result(tool_call, content, line_limit: nil, elapsed_ms: nil)
|
|
351
354
|
summary = tool_result_summary(tool_call, content)
|
|
355
|
+
summary = tool_summary_with_duration(summary, elapsed_ms)
|
|
352
356
|
summary = limit_tool_output_lines(summary, line_limit) if line_limit
|
|
353
357
|
display_summary = tool_summary_display_text(summary)
|
|
354
358
|
if prompt_interface?
|
|
@@ -380,7 +384,7 @@ module Kward
|
|
|
380
384
|
return if @stream_block == label
|
|
381
385
|
|
|
382
386
|
puts if @stream_block
|
|
383
|
-
print "\n#{colored(
|
|
387
|
+
print "\n#{colored(transcript_marker(label), *label_styles(label))} "
|
|
384
388
|
@stream_block = label
|
|
385
389
|
end
|
|
386
390
|
|
|
@@ -408,20 +412,25 @@ module Kward
|
|
|
408
412
|
end
|
|
409
413
|
end
|
|
410
414
|
|
|
415
|
+
def transcript_marker(label)
|
|
416
|
+
marker = "#{transcript_label(label)}>"
|
|
417
|
+
label == "Assistant" ? "✦ #{marker}" : marker
|
|
418
|
+
end
|
|
419
|
+
|
|
411
420
|
def label_styles(label)
|
|
412
421
|
case label
|
|
413
422
|
when "Reasoning", "Compaction summary"
|
|
414
|
-
[:
|
|
423
|
+
[:metadata, :bold]
|
|
415
424
|
when "Assistant", "Kward"
|
|
416
|
-
[:
|
|
425
|
+
[:activity, :bold]
|
|
417
426
|
when "Tool", "Tool output"
|
|
418
|
-
[:
|
|
427
|
+
[:tool, :bold]
|
|
419
428
|
when "Tool failed"
|
|
420
|
-
[:
|
|
429
|
+
[:failure, :bold]
|
|
421
430
|
when "Retry"
|
|
422
|
-
[:
|
|
431
|
+
[:caution, :bold]
|
|
423
432
|
else
|
|
424
|
-
[:
|
|
433
|
+
[:metadata, :bold]
|
|
425
434
|
end
|
|
426
435
|
end
|
|
427
436
|
|
|
@@ -47,6 +47,11 @@ module Kward
|
|
|
47
47
|
|
|
48
48
|
def runtime_output(text)
|
|
49
49
|
content = text.to_s.chomp
|
|
50
|
+
if (tab = Thread.current[:kward_local_busy_tab]) && !content.empty?
|
|
51
|
+
tab.append_pending_runtime_output(content)
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
50
55
|
label = colored(runtime_output_prompt, :gray, :bold)
|
|
51
56
|
separator = content.include?("\n") ? "\n" : " "
|
|
52
57
|
@prompt.say("\n#{label}#{separator}#{content}\n")
|
|
@@ -61,6 +66,7 @@ module Kward
|
|
|
61
66
|
workspace: workspace,
|
|
62
67
|
prompt: @prompt,
|
|
63
68
|
skills: ConfigFiles.skills(workspace_root: conversation.workspace_root, project_skill_paths: project_skill_paths_for(conversation.workspace_root)),
|
|
69
|
+
plugin_tools: plugin_registry.tools,
|
|
64
70
|
tool_approval: interactive_tool_approval_callback,
|
|
65
71
|
hook_manager: hook_manager,
|
|
66
72
|
hook_context: hook_context
|
|
@@ -102,19 +108,19 @@ module Kward
|
|
|
102
108
|
if editor_result
|
|
103
109
|
record_tab_transient_shell_output(editor_result.output, render: false)
|
|
104
110
|
@prompt.say(editor_result.output) unless editor_result.output.to_s.empty?
|
|
105
|
-
|
|
111
|
+
open_kwsh_editor(editor_result.open_editor_path, shell) if editor_result.open_editor_path
|
|
106
112
|
return true
|
|
107
113
|
end
|
|
108
114
|
|
|
109
115
|
expanded_command = shell.expand_alias(command, interactive: true)
|
|
110
|
-
run_user_interactive_pty_command(
|
|
116
|
+
result = run_user_interactive_pty_command(
|
|
111
117
|
expanded_command,
|
|
112
|
-
shell:
|
|
113
|
-
env: interactive_pty_environment(
|
|
118
|
+
shell: Kwsh::DEFAULT_SHELL,
|
|
119
|
+
env: interactive_pty_environment(shell.child_env, preserve_git_pager: true),
|
|
114
120
|
cwd: interactive_workspace_root(agent),
|
|
115
121
|
intro: "$ #{command}\n"
|
|
116
122
|
)
|
|
117
|
-
true
|
|
123
|
+
result&.tab_action ? :tab_action : true
|
|
118
124
|
end
|
|
119
125
|
|
|
120
126
|
def run_captured_shell_command(command, agent)
|
|
@@ -142,7 +148,7 @@ module Kward
|
|
|
142
148
|
completion = bang_shell(agent).complete(value[1..], cursor.to_i - 1)
|
|
143
149
|
return nil unless completion
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
Kwsh::Completion.new(
|
|
146
152
|
range: (completion.range.begin + 1)...(completion.range.end + 1),
|
|
147
153
|
replacement: completion.replacement,
|
|
148
154
|
candidates: completion.candidates
|
|
@@ -151,12 +157,14 @@ module Kward
|
|
|
151
157
|
|
|
152
158
|
def bang_shell(agent)
|
|
153
159
|
root = File.expand_path(interactive_workspace_root(agent).to_s)
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
config = ConfigFiles.read_kwsh_config
|
|
161
|
+
aliases = config[:aliases]
|
|
162
|
+
configured_env = config[:env]
|
|
163
|
+
cache_key = [root, ENV.fetch("PATH", ""), aliases.sort, configured_env.sort]
|
|
156
164
|
return @bang_shell if @bang_shell_key == cache_key
|
|
157
165
|
|
|
158
166
|
@bang_shell_key = cache_key
|
|
159
|
-
@bang_shell =
|
|
167
|
+
@bang_shell = Kwsh.new(cwd: root, env: ENV.to_h, configured_env: configured_env, aliases: aliases)
|
|
160
168
|
end
|
|
161
169
|
|
|
162
170
|
def install_bang_completion_provider(agent)
|
|
@@ -182,7 +190,7 @@ module Kward
|
|
|
182
190
|
@bang_completion_provider_installed = false
|
|
183
191
|
end
|
|
184
192
|
|
|
185
|
-
def
|
|
193
|
+
def run_kwsh(agent)
|
|
186
194
|
unless @prompt.respond_to?(:ask)
|
|
187
195
|
runtime_output("The embedded shell is only available in interactive mode.")
|
|
188
196
|
return
|
|
@@ -190,11 +198,11 @@ module Kward
|
|
|
190
198
|
|
|
191
199
|
tab = active_tab if respond_to?(:active_tab, true)
|
|
192
200
|
entering = tab.nil? || tab.shell.nil?
|
|
193
|
-
shell = tab&.shell ||
|
|
201
|
+
shell = tab&.shell || build_kwsh(agent)
|
|
194
202
|
tab.shell = shell if tab
|
|
195
203
|
shell_agent = shell_prompt_agent_for(agent, tab: tab)
|
|
196
|
-
runtime_output("Entering
|
|
197
|
-
|
|
204
|
+
runtime_output("Entering kwsh. Type exit or press Ctrl+D on an empty prompt to return.") if entering
|
|
205
|
+
run_kwsh_loop(shell, tab: tab, history: build_kwsh_history(agent), shell_agent: shell_agent)
|
|
198
206
|
end
|
|
199
207
|
|
|
200
208
|
def shell_prompt_agent_for(agent, tab: nil)
|
|
@@ -208,17 +216,20 @@ module Kward
|
|
|
208
216
|
|
|
209
217
|
def build_shell_prompt_agent(active_agent)
|
|
210
218
|
active_conversation = active_agent.conversation
|
|
219
|
+
config = ConfigFiles.read_config
|
|
220
|
+
configured_provider = ConfigFiles.shell_agent_provider(config)
|
|
211
221
|
conversation = Conversation.new(
|
|
212
222
|
system_message: ShellPrompt.system_message,
|
|
213
223
|
workspace_root: active_conversation.workspace_root,
|
|
214
|
-
provider: active_conversation.provider || current_model_provider,
|
|
215
|
-
model: active_conversation.model || current_model_id,
|
|
216
|
-
reasoning_effort: active_conversation.reasoning_effort || current_reasoning_effort
|
|
224
|
+
provider: configured_provider || active_conversation.provider || current_model_provider,
|
|
225
|
+
model: ConfigFiles.shell_agent_model(config) || (configured_provider ? nil : active_conversation.model || current_model_id),
|
|
226
|
+
reasoning_effort: ConfigFiles.shell_agent_reasoning_effort(config) || (configured_provider ? nil : active_conversation.reasoning_effort || current_reasoning_effort)
|
|
217
227
|
)
|
|
218
228
|
Agent.new(
|
|
219
229
|
client: @client,
|
|
220
230
|
tool_registry: active_agent.tool_registry,
|
|
221
231
|
conversation: conversation,
|
|
232
|
+
strict_provider: !configured_provider.nil?,
|
|
222
233
|
warning_sink: ConfigFiles.warning_sink
|
|
223
234
|
)
|
|
224
235
|
end
|
|
@@ -269,8 +280,8 @@ module Kward
|
|
|
269
280
|
input.to_s.start_with?("?")
|
|
270
281
|
end
|
|
271
282
|
|
|
272
|
-
def
|
|
273
|
-
config = ConfigFiles.
|
|
283
|
+
def build_kwsh(agent)
|
|
284
|
+
config = ConfigFiles.read_kwsh_config
|
|
274
285
|
PersistentShellSession.new(
|
|
275
286
|
cwd: interactive_workspace_root(agent),
|
|
276
287
|
configured_env: config[:env],
|
|
@@ -281,8 +292,8 @@ module Kward
|
|
|
281
292
|
)
|
|
282
293
|
end
|
|
283
294
|
|
|
284
|
-
def
|
|
285
|
-
config = ConfigFiles.
|
|
295
|
+
def build_kwsh_history(agent)
|
|
296
|
+
config = ConfigFiles.read_kwsh_config
|
|
286
297
|
PromptHistory.new(
|
|
287
298
|
cwd: interactive_workspace_root(agent),
|
|
288
299
|
limit: config[:history_limit],
|
|
@@ -297,7 +308,7 @@ module Kward
|
|
|
297
308
|
return
|
|
298
309
|
end
|
|
299
310
|
|
|
300
|
-
config = ConfigFiles.
|
|
311
|
+
config = ConfigFiles.read_kwsh_config
|
|
301
312
|
run_user_interactive_pty_command(
|
|
302
313
|
command,
|
|
303
314
|
shell: config[:shell],
|
|
@@ -314,8 +325,14 @@ module Kward
|
|
|
314
325
|
elsif @prompt.respond_to?(:say)
|
|
315
326
|
@prompt.say(intro_message)
|
|
316
327
|
end
|
|
317
|
-
|
|
318
|
-
|
|
328
|
+
origin_tab = active_tab if respond_to?(:active_tab, true)
|
|
329
|
+
result = run_interactive_pty_with_terminal_handoff(shell, command, env: env, cwd: cwd) do |sink, completed_result, background_state|
|
|
330
|
+
transcript_output = record_completed_pty_output(
|
|
331
|
+
sink,
|
|
332
|
+
completed_result,
|
|
333
|
+
tab: origin_tab,
|
|
334
|
+
background_state: background_state
|
|
335
|
+
)
|
|
319
336
|
yield(transcript_output, completed_result) if block_given?
|
|
320
337
|
end
|
|
321
338
|
refresh_composer_status
|
|
@@ -327,20 +344,35 @@ module Kward
|
|
|
327
344
|
|
|
328
345
|
def run_interactive_pty_with_terminal_handoff(shell, command, env:, cwd:, &on_complete)
|
|
329
346
|
runner = InteractivePtyRunner.new
|
|
347
|
+
origin_tab = active_tab if respond_to?(:active_tab, true)
|
|
330
348
|
with_interactive_terminal_handoff do |input, sink|
|
|
349
|
+
tab_action_handler = terminal_handoff_tab_action_handler
|
|
350
|
+
on_detach = if origin_tab
|
|
351
|
+
lambda do
|
|
352
|
+
BufferedPtyOutputSink.new(
|
|
353
|
+
max_capture_bytes: Kward::CLI::Tabs::TRANSIENT_SHELL_OUTPUT_LIMIT,
|
|
354
|
+
initial_output: sink.captured_output,
|
|
355
|
+
initial_truncated: sink.truncated?
|
|
356
|
+
)
|
|
357
|
+
end
|
|
358
|
+
end
|
|
331
359
|
result = if shell.respond_to?(:run_interactive)
|
|
332
|
-
shell.run_interactive(command, input: input, sink: sink)
|
|
360
|
+
shell.run_interactive(command, input: input, sink: sink, tab_action_handler: tab_action_handler, on_detach: on_detach)
|
|
361
|
+
else
|
|
362
|
+
runner.run(shell, "-c", command, env: env, cwd: cwd, input: input, sink: sink, tab_action_handler: tab_action_handler, on_detach: on_detach)
|
|
363
|
+
end
|
|
364
|
+
if result&.background
|
|
365
|
+
register_background_tab_run(origin_tab, result.background, output_kind: :pty, &on_complete)
|
|
333
366
|
else
|
|
334
|
-
|
|
367
|
+
on_complete.call(sink, result) if on_complete
|
|
335
368
|
end
|
|
336
|
-
on_complete.call(sink, result) if on_complete
|
|
337
369
|
result
|
|
338
370
|
end
|
|
339
371
|
end
|
|
340
372
|
|
|
341
373
|
def with_interactive_terminal_handoff
|
|
342
374
|
if @prompt.respond_to?(:with_inline_terminal_handoff)
|
|
343
|
-
|
|
375
|
+
with_tab_keybinding_handoff(:with_inline_terminal_handoff) do |input, output, transition|
|
|
344
376
|
sink = AdaptivePtyOutputSink.new(
|
|
345
377
|
output: output,
|
|
346
378
|
on_exclusive: transition,
|
|
@@ -349,7 +381,7 @@ module Kward
|
|
|
349
381
|
yield(input, sink)
|
|
350
382
|
end
|
|
351
383
|
elsif @prompt.respond_to?(:with_terminal_handoff)
|
|
352
|
-
|
|
384
|
+
with_tab_keybinding_handoff(:with_terminal_handoff) do |input, output|
|
|
353
385
|
sink = PassthroughPtyOutputSink.new(
|
|
354
386
|
output: output,
|
|
355
387
|
max_capture_bytes: MAX_TRANSIENT_TERMINAL_OUTPUT_BYTES
|
|
@@ -365,7 +397,100 @@ module Kward
|
|
|
365
397
|
end
|
|
366
398
|
end
|
|
367
399
|
|
|
368
|
-
def
|
|
400
|
+
def with_tab_keybinding_handoff(method_name)
|
|
401
|
+
method = @prompt.method(method_name)
|
|
402
|
+
supports_option = method.parameters.any? do |type, name|
|
|
403
|
+
type == :keyrest || (type == :key || type == :keyreq) && name == :preserve_tab_keybindings
|
|
404
|
+
end
|
|
405
|
+
if supports_option
|
|
406
|
+
@prompt.public_send(method_name, preserve_tab_keybindings: true) { |*args| yield(*args) }
|
|
407
|
+
else
|
|
408
|
+
@prompt.public_send(method_name) { |*args| yield(*args) }
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def terminal_handoff_tab_action_handler
|
|
413
|
+
return unless @prompt.respond_to?(:filter_terminal_handoff_input)
|
|
414
|
+
return unless respond_to?(:handle_tab_action, true)
|
|
415
|
+
|
|
416
|
+
lambda do |chunk|
|
|
417
|
+
filtered = @prompt.filter_terminal_handoff_input(chunk)
|
|
418
|
+
action = filtered[:tab_action]
|
|
419
|
+
handle_tab_action(action, @session_store) if action
|
|
420
|
+
filtered
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def record_completed_pty_output(sink, result, tab: nil, background_state: nil)
|
|
425
|
+
transcript_output = background_shell_transcript_output(sink, result)
|
|
426
|
+
return unless transcript_output
|
|
427
|
+
|
|
428
|
+
rendered_output = background_state&.fetch(:rendered_output, "").to_s
|
|
429
|
+
delta = if transcript_output.start_with?(rendered_output)
|
|
430
|
+
transcript_output[rendered_output.length..].to_s
|
|
431
|
+
else
|
|
432
|
+
transcript_output
|
|
433
|
+
end
|
|
434
|
+
if background_state
|
|
435
|
+
background_state[:rendered_output] = transcript_output
|
|
436
|
+
render_background_tab_delta(tab, delta) unless delta.empty?
|
|
437
|
+
else
|
|
438
|
+
record_tab_transient_shell_output(transcript_output, render: false, tab: tab)
|
|
439
|
+
if @prompt.respond_to?(:record_transient_terminal_output) && (!tab || tab == active_tab)
|
|
440
|
+
@prompt.record_transient_terminal_output(transcript_output, render: false)
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
transcript_output
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def flush_background_tab_output(tab, state, result: nil)
|
|
447
|
+
sink = state[:run].sink
|
|
448
|
+
output = sink&.captured_output.to_s
|
|
449
|
+
return if output.empty?
|
|
450
|
+
|
|
451
|
+
transcript_output = if state[:output_kind] == :streamed
|
|
452
|
+
background_stream_transcript_output(sink)
|
|
453
|
+
else
|
|
454
|
+
transcript_result = result || InteractivePtyRunner::Result.new(exit_status: 0, input_forwarded: false)
|
|
455
|
+
background_shell_transcript_output(sink, transcript_result)
|
|
456
|
+
end
|
|
457
|
+
return unless transcript_output
|
|
458
|
+
|
|
459
|
+
rendered_output = state[:rendered_output].to_s
|
|
460
|
+
if transcript_output.start_with?(rendered_output)
|
|
461
|
+
delta = transcript_output[rendered_output.length..].to_s
|
|
462
|
+
return if delta.empty?
|
|
463
|
+
|
|
464
|
+
state[:rendered_output] = transcript_output
|
|
465
|
+
render_background_tab_delta(tab, delta)
|
|
466
|
+
else
|
|
467
|
+
state[:rendered_output] = transcript_output
|
|
468
|
+
render_tab(tab, restore_composer: false) if tab == active_tab
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def render_background_tab_delta(tab, delta)
|
|
473
|
+
return unless tab == active_tab
|
|
474
|
+
|
|
475
|
+
if @prompt.respond_to?(:record_transient_terminal_output)
|
|
476
|
+
@prompt.record_transient_terminal_output(delta)
|
|
477
|
+
elsif @prompt.respond_to?(:say)
|
|
478
|
+
@prompt.say(delta)
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def background_stream_transcript_output(sink)
|
|
483
|
+
return if sink&.truncated?
|
|
484
|
+
|
|
485
|
+
text = ANSI.normalize_transcript_encoding(sink&.captured_output.to_s).gsub("\r\n", "\n")
|
|
486
|
+
sanitized = PtyTranscriptNormalizer.normalize(text)
|
|
487
|
+
return if sanitized.empty?
|
|
488
|
+
return if ANSI.strip_control_sequences(sanitized).match?(UNSAFE_TRANSCRIPT_CONTROL_PATTERN)
|
|
489
|
+
|
|
490
|
+
sanitized
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def background_shell_transcript_output(sink, result)
|
|
369
494
|
classified_output = sink.respond_to?(:transcript_safe?)
|
|
370
495
|
return if classified_output && !sink.transcript_safe?
|
|
371
496
|
|
|
@@ -373,20 +498,13 @@ module Kward
|
|
|
373
498
|
allow_input = classified_output &&
|
|
374
499
|
sink.respond_to?(:pre_input_capture_only?) &&
|
|
375
500
|
sink.pre_input_capture_only?
|
|
376
|
-
|
|
501
|
+
terminal_transcript_output(
|
|
377
502
|
output,
|
|
378
503
|
result,
|
|
379
504
|
truncated: sink&.truncated? || false,
|
|
380
505
|
allow_input: allow_input,
|
|
381
506
|
normalize_line_controls: classified_output
|
|
382
507
|
)
|
|
383
|
-
return unless transcript_output
|
|
384
|
-
|
|
385
|
-
record_tab_transient_shell_output(transcript_output, render: false)
|
|
386
|
-
if @prompt.respond_to?(:record_transient_terminal_output)
|
|
387
|
-
@prompt.record_transient_terminal_output(transcript_output, render: false)
|
|
388
|
-
end
|
|
389
|
-
transcript_output
|
|
390
508
|
end
|
|
391
509
|
|
|
392
510
|
def terminal_transcript_output(
|
|
@@ -422,14 +540,14 @@ module Kward
|
|
|
422
540
|
end
|
|
423
541
|
end
|
|
424
542
|
|
|
425
|
-
def
|
|
543
|
+
def run_kwsh_loop(shell, tab: nil, history: nil, shell_agent: nil)
|
|
426
544
|
shell_agent ||= shell_prompt_agent_for(tab.agent, tab: tab) if tab&.agent
|
|
427
|
-
|
|
428
|
-
|
|
545
|
+
with_kwsh_history(history) do
|
|
546
|
+
run_kwsh_loop_with_history(shell, tab: tab, shell_agent: shell_agent)
|
|
429
547
|
end
|
|
430
548
|
end
|
|
431
549
|
|
|
432
|
-
def
|
|
550
|
+
def run_kwsh_loop_with_history(shell, tab: nil, shell_agent: nil)
|
|
433
551
|
pending_shell_inputs = []
|
|
434
552
|
loop do
|
|
435
553
|
if @prompt.respond_to?(:editing_file?) && @prompt.editing_file?
|
|
@@ -440,7 +558,7 @@ module Kward
|
|
|
440
558
|
end
|
|
441
559
|
end
|
|
442
560
|
|
|
443
|
-
input = pending_shell_inputs.shift ||
|
|
561
|
+
input = pending_shell_inputs.shift || ask_kwsh(shell)
|
|
444
562
|
if input.is_a?(Hash) && input[:tab_action]
|
|
445
563
|
(@pending_inputs ||= []).unshift(input)
|
|
446
564
|
return :tab_action
|
|
@@ -452,7 +570,7 @@ module Kward
|
|
|
452
570
|
next
|
|
453
571
|
end
|
|
454
572
|
|
|
455
|
-
result =
|
|
573
|
+
result = run_kwsh_command(shell, input)
|
|
456
574
|
if result.clear
|
|
457
575
|
clear_active_tab_transient_shell_output
|
|
458
576
|
@prompt.clear_transcript if @prompt.respond_to?(:clear_transcript)
|
|
@@ -463,37 +581,39 @@ module Kward
|
|
|
463
581
|
return :tab_action if pending_tab_action?
|
|
464
582
|
|
|
465
583
|
if result.open_editor_path
|
|
466
|
-
editor_result =
|
|
584
|
+
editor_result = open_kwsh_editor(result.open_editor_path, shell)
|
|
467
585
|
return :tab_action if editor_result == :tab_action
|
|
468
586
|
|
|
469
587
|
next
|
|
470
588
|
end
|
|
471
589
|
if result.interactive_command
|
|
472
|
-
|
|
590
|
+
pty_result = run_kwsh_interactive_pty_command(shell, result)
|
|
591
|
+
return :tab_action if pty_result&.tab_action
|
|
592
|
+
|
|
473
593
|
next
|
|
474
594
|
end
|
|
475
595
|
if result.exit_shell
|
|
476
|
-
|
|
596
|
+
close_kwsh_session(shell)
|
|
477
597
|
tab.shell = nil if tab
|
|
478
598
|
tab.shell_agent = nil if tab
|
|
479
599
|
runtime_output("Shell exited.")
|
|
480
600
|
return :exited
|
|
481
601
|
end
|
|
482
602
|
end
|
|
483
|
-
|
|
603
|
+
close_kwsh_session(shell)
|
|
484
604
|
tab.shell = nil if tab
|
|
485
605
|
tab.shell_agent = nil if tab
|
|
486
606
|
runtime_output("Shell exited.")
|
|
487
607
|
:exited
|
|
488
608
|
end
|
|
489
609
|
|
|
490
|
-
def
|
|
610
|
+
def close_kwsh_session(shell)
|
|
491
611
|
shell.close if shell.respond_to?(:close)
|
|
492
612
|
rescue StandardError
|
|
493
613
|
nil
|
|
494
614
|
end
|
|
495
615
|
|
|
496
|
-
def
|
|
616
|
+
def with_kwsh_history(history)
|
|
497
617
|
if history && @prompt.respond_to?(:with_prompt_history)
|
|
498
618
|
@prompt.with_prompt_history(history) { yield }
|
|
499
619
|
else
|
|
@@ -501,7 +621,7 @@ module Kward
|
|
|
501
621
|
end
|
|
502
622
|
end
|
|
503
623
|
|
|
504
|
-
def
|
|
624
|
+
def open_kwsh_editor(path, shell)
|
|
505
625
|
unless @prompt.respond_to?(:edit_file)
|
|
506
626
|
runtime_output("Integrated editor is unavailable in this prompt.")
|
|
507
627
|
return false
|
|
@@ -516,7 +636,7 @@ module Kward
|
|
|
516
636
|
result
|
|
517
637
|
end
|
|
518
638
|
|
|
519
|
-
def
|
|
639
|
+
def ask_kwsh(shell)
|
|
520
640
|
provider = ->(input, cursor) { shell.complete(input, cursor) }
|
|
521
641
|
if @prompt.respond_to?(:with_completion_provider)
|
|
522
642
|
@prompt.with_completion_provider(provider, slash_overlay: false) { @prompt.ask(shell.prompt_label) }
|
|
@@ -525,7 +645,7 @@ module Kward
|
|
|
525
645
|
end
|
|
526
646
|
end
|
|
527
647
|
|
|
528
|
-
def
|
|
648
|
+
def run_kwsh_interactive_pty_command(shell, result)
|
|
529
649
|
shell_target = shell.respond_to?(:run_interactive) ? shell : shell.command_shell
|
|
530
650
|
run_user_interactive_pty_command(
|
|
531
651
|
result.interactive_command,
|
|
@@ -541,12 +661,12 @@ module Kward
|
|
|
541
661
|
end
|
|
542
662
|
end
|
|
543
663
|
|
|
544
|
-
def
|
|
664
|
+
def run_kwsh_command(shell, input)
|
|
545
665
|
if @prompt.respond_to?(:begin_busy_input)
|
|
546
666
|
@prompt.begin_busy_input(shell.prompt_label, activity: "running")
|
|
547
667
|
end
|
|
548
668
|
if @prompt.respond_to?(:write_transcript_delta) && @prompt.respond_to?(:poll_input)
|
|
549
|
-
|
|
669
|
+
run_streaming_kwsh_command(shell, input)
|
|
550
670
|
elsif @prompt.respond_to?(:write_transcript_delta)
|
|
551
671
|
shell.run(input) { |chunk| @prompt.write_transcript_delta(chunk) }
|
|
552
672
|
else
|
|
@@ -556,45 +676,70 @@ module Kward
|
|
|
556
676
|
@prompt.finish_busy_input if @prompt.respond_to?(:finish_busy_input)
|
|
557
677
|
end
|
|
558
678
|
|
|
559
|
-
def
|
|
679
|
+
def run_streaming_kwsh_command(shell, input)
|
|
560
680
|
cancellation = Cancellation.new
|
|
561
681
|
chunks = Queue.new
|
|
562
682
|
queued_inputs = []
|
|
683
|
+
origin_tab = active_tab if respond_to?(:active_tab, true)
|
|
684
|
+
streamed_output = BufferedPtyOutputSink.new(max_capture_bytes: Kward::CLI::Tabs::TRANSIENT_SHELL_OUTPUT_LIMIT)
|
|
685
|
+
detached_sink = nil
|
|
563
686
|
result = nil
|
|
564
687
|
error = nil
|
|
565
688
|
worker = Thread.new do
|
|
566
|
-
result = shell.run(input, cancellation: cancellation)
|
|
689
|
+
result = shell.run(input, cancellation: cancellation) do |chunk|
|
|
690
|
+
if detached_sink
|
|
691
|
+
detached_sink.write(chunk)
|
|
692
|
+
else
|
|
693
|
+
streamed_output.write(chunk)
|
|
694
|
+
chunks << chunk
|
|
695
|
+
end
|
|
696
|
+
end
|
|
567
697
|
rescue StandardError => e
|
|
568
698
|
error = e
|
|
569
699
|
end
|
|
570
700
|
worker.report_on_exception = false
|
|
571
701
|
|
|
572
702
|
while worker.alive?
|
|
573
|
-
|
|
703
|
+
drain_kwsh_chunks(chunks)
|
|
574
704
|
poll_result = collect_queued_input(queued_inputs)
|
|
575
705
|
if poll_result == PromptInterface::CANCEL_INPUT
|
|
576
706
|
cancellation.cancel!
|
|
577
707
|
elsif poll_result.is_a?(Hash) && poll_result[:tab_action]
|
|
578
708
|
(@pending_inputs ||= []).unshift(poll_result)
|
|
709
|
+
if origin_tab
|
|
710
|
+
detached_sink = BufferedPtyOutputSink.new(
|
|
711
|
+
max_capture_bytes: Kward::CLI::Tabs::TRANSIENT_SHELL_OUTPUT_LIMIT,
|
|
712
|
+
initial_output: streamed_output.captured_output,
|
|
713
|
+
initial_truncated: streamed_output.truncated?
|
|
714
|
+
)
|
|
715
|
+
background = DetachedRun.new(sink: detached_sink, canceler: -> { cancellation.cancel! }) do
|
|
716
|
+
worker.join
|
|
717
|
+
raise error if error
|
|
718
|
+
|
|
719
|
+
result
|
|
720
|
+
end
|
|
721
|
+
register_background_tab_run(origin_tab, background, output_kind: :streamed)
|
|
722
|
+
return Kwsh::Result.new(output: "", exit_status: 0, streamed: true)
|
|
723
|
+
end
|
|
579
724
|
cancellation.cancel!
|
|
580
725
|
end
|
|
581
726
|
sleep 0.01
|
|
582
727
|
end
|
|
583
728
|
worker.join
|
|
584
|
-
|
|
729
|
+
drain_kwsh_chunks(chunks)
|
|
585
730
|
raise error if error
|
|
586
731
|
|
|
587
732
|
queued_inputs.reverse_each { |pending_input| (@pending_inputs ||= []).unshift(pending_input) }
|
|
588
733
|
result
|
|
589
734
|
end
|
|
590
735
|
|
|
591
|
-
def record_tab_transient_shell_output(text, render: true)
|
|
736
|
+
def record_tab_transient_shell_output(text, render: true, tab: nil)
|
|
592
737
|
value = text.to_s
|
|
593
738
|
return if value.empty?
|
|
594
739
|
|
|
595
|
-
tab
|
|
740
|
+
tab ||= active_tab if respond_to?(:active_tab, true)
|
|
596
741
|
tab&.append_transient_shell_entry(value)
|
|
597
|
-
if render && @prompt.respond_to?(:record_transient_terminal_output)
|
|
742
|
+
if render && @prompt.respond_to?(:record_transient_terminal_output) && tab == active_tab
|
|
598
743
|
@prompt.record_transient_terminal_output(value)
|
|
599
744
|
end
|
|
600
745
|
end
|
|
@@ -641,7 +786,7 @@ module Kward
|
|
|
641
786
|
after
|
|
642
787
|
end
|
|
643
788
|
|
|
644
|
-
def
|
|
789
|
+
def drain_kwsh_chunks(chunks)
|
|
645
790
|
loop do
|
|
646
791
|
@prompt.write_transcript_delta(chunks.pop(true))
|
|
647
792
|
rescue ThreadError
|
|
@@ -674,44 +819,56 @@ module Kward
|
|
|
674
819
|
queued_inputs = []
|
|
675
820
|
result = nil
|
|
676
821
|
error = nil
|
|
677
|
-
|
|
822
|
+
cancelled = false
|
|
823
|
+
origin_tab = active_tab
|
|
824
|
+
cancellation = Cancellation.new
|
|
825
|
+
begin_local_busy_command(activity, tab: origin_tab)
|
|
678
826
|
|
|
679
827
|
worker = Thread.new do
|
|
680
|
-
|
|
828
|
+
Thread.current[:kward_local_busy_tab] = origin_tab
|
|
829
|
+
result = yield(cancellation)
|
|
681
830
|
rescue StandardError => e
|
|
682
831
|
error = e
|
|
832
|
+
ensure
|
|
833
|
+
Thread.current[:kward_local_busy_tab] = nil
|
|
683
834
|
end
|
|
684
835
|
|
|
685
836
|
while worker.alive?
|
|
686
837
|
poll_result = collect_queued_input(queued_inputs)
|
|
838
|
+
if poll_result == PromptInterface::CANCEL_INPUT && !cancelled
|
|
839
|
+
cancelled = true
|
|
840
|
+
cancellation.cancel!
|
|
841
|
+
end
|
|
687
842
|
handle_busy_local_tab_action(poll_result, activity: activity) if busy_local_tab_action?(poll_result)
|
|
688
843
|
sleep 0.02
|
|
689
844
|
end
|
|
690
845
|
worker.join
|
|
691
846
|
drain_queued_input(queued_inputs)
|
|
692
|
-
|
|
847
|
+
flushed = flush_pending_runtime_outputs(origin_tab)
|
|
848
|
+
origin_tab.unread = true if origin_tab && !flushed && origin_tab.pending_runtime_outputs.any?
|
|
849
|
+
raise error if error && !error.is_a?(Cancellation::CancelledError)
|
|
693
850
|
|
|
694
851
|
[result, queued_inputs]
|
|
695
852
|
ensure
|
|
696
|
-
finish_local_busy_command(activity)
|
|
853
|
+
finish_local_busy_command(activity, tab: origin_tab)
|
|
697
854
|
end
|
|
698
855
|
|
|
699
856
|
def run_busy_local_command_and_requeue(activity: "loading")
|
|
700
857
|
return yield unless prompt_interface?
|
|
701
858
|
|
|
702
|
-
result, queued_inputs = run_busy_local_command(activity: activity) { yield }
|
|
859
|
+
result, queued_inputs = run_busy_local_command(activity: activity) { |cancellation| yield(cancellation) }
|
|
703
860
|
queued_inputs.reverse_each { |pending_input| @pending_inputs.unshift(pending_input) }
|
|
704
861
|
result
|
|
705
862
|
end
|
|
706
863
|
|
|
707
|
-
def begin_local_busy_command(activity)
|
|
708
|
-
|
|
864
|
+
def begin_local_busy_command(activity, tab: active_tab)
|
|
865
|
+
tab.local_busy_activity = activity if tab
|
|
709
866
|
@prompt.begin_busy_input("You>", activity: activity) if @prompt.respond_to?(:begin_busy_input)
|
|
710
867
|
update_prompt_tabs if respond_to?(:update_prompt_tabs, true)
|
|
711
868
|
end
|
|
712
869
|
|
|
713
|
-
def finish_local_busy_command(activity)
|
|
714
|
-
tab
|
|
870
|
+
def finish_local_busy_command(activity, tab: nil)
|
|
871
|
+
tab ||= local_busy_tab(activity)
|
|
715
872
|
tab.local_busy_activity = nil if tab
|
|
716
873
|
update_prompt_tabs if respond_to?(:update_prompt_tabs, true)
|
|
717
874
|
@prompt.finish_busy_input if prompt_interface? && @prompt.respond_to?(:finish_busy_input) && (!active_tab || active_tab == tab)
|