kward 0.69.1 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/pages.yml +1 -1
- data/CHANGELOG.md +68 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +90 -2
- data/README.md +30 -6
- data/Rakefile +96 -0
- data/doc/agent-tools.md +43 -0
- data/doc/api.md +92 -0
- data/doc/authentication.md +39 -25
- data/doc/configuration.md +2 -16
- data/doc/context-tools.md +70 -0
- data/doc/getting-started.md +3 -1
- data/doc/plugins.md +2 -2
- data/doc/releasing.md +14 -5
- data/doc/rpc.md +3 -11
- data/doc/session-management.md +220 -0
- data/doc/usage.md +13 -7
- data/doc/workspace-tools.md +105 -0
- data/lib/kward/cli/commands.rb +8 -0
- data/lib/kward/cli/openrouter_commands.rb +55 -0
- data/lib/kward/cli/prompt_interface.rb +85 -7
- data/lib/kward/cli/rendering.rb +11 -6
- data/lib/kward/cli/sessions.rb +454 -15
- data/lib/kward/cli/settings.rb +0 -30
- data/lib/kward/cli/slash_commands.rb +38 -11
- data/lib/kward/cli.rb +14 -0
- data/lib/kward/compactor.rb +4 -1
- data/lib/kward/config_files.rb +4 -6
- data/lib/kward/conversation.rb +49 -5
- data/lib/kward/model/client.rb +37 -50
- data/lib/kward/model/context_usage.rb +13 -6
- data/lib/kward/model/model_info.rb +92 -9
- data/lib/kward/model/payloads.rb +2 -0
- data/lib/kward/openrouter_model_cache.rb +120 -0
- data/lib/kward/plugin_registry.rb +47 -1
- data/lib/kward/prompt_interface/banner.rb +16 -51
- data/lib/kward/prompt_interface/composer_controller.rb +60 -87
- data/lib/kward/prompt_interface/composer_renderer.rb +7 -1
- data/lib/kward/prompt_interface/key_handler.rb +31 -10
- data/lib/kward/prompt_interface/layout.rb +2 -2
- data/lib/kward/prompt_interface/overlay_renderer.rb +24 -0
- data/lib/kward/prompt_interface/prompt_renderer.rb +23 -2
- data/lib/kward/prompt_interface/question_prompt.rb +34 -42
- data/lib/kward/prompt_interface/runtime_state.rb +6 -1
- data/lib/kward/prompt_interface/screen.rb +10 -4
- data/lib/kward/prompt_interface/selection_prompt.rb +518 -61
- data/lib/kward/prompt_interface/slash_overlay.rb +4 -4
- data/lib/kward/prompt_interface/transcript_buffer.rb +7 -16
- data/lib/kward/prompt_interface/transcript_renderer.rb +3 -3
- data/lib/kward/prompt_interface.rb +31 -32
- data/lib/kward/prompts/commands.rb +6 -3
- data/lib/kward/prompts.rb +2 -2
- data/lib/kward/rpc/server.rb +3 -8
- data/lib/kward/rpc/session_manager.rb +19 -8
- data/lib/kward/session_diff.rb +106 -9
- data/lib/kward/session_store.rb +23 -4
- data/lib/kward/session_tree_renderer.rb +2 -1
- data/lib/kward/telemetry/logger.rb +5 -3
- data/lib/kward/tool_output_compactor.rb +127 -0
- data/lib/kward/tools/base.rb +8 -2
- data/lib/kward/tools/registry.rb +37 -6
- data/lib/kward/tools/retrieve_tool_output.rb +71 -0
- data/lib/kward/tools/search/web.rb +2 -2
- data/lib/kward/tools/summarize_file_structure.rb +29 -0
- data/lib/kward/tools/tool_call.rb +2 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workspace.rb +58 -2
- data/templates/default/fulldoc/html/css/kward.css +570 -78
- data/templates/default/fulldoc/html/full_list.erb +107 -0
- data/templates/default/fulldoc/html/js/kward.js +259 -97
- data/templates/default/fulldoc/html/setup.rb +8 -0
- data/templates/default/kward_navigation.rb +91 -0
- data/templates/default/layout/html/layout.erb +59 -13
- data/templates/default/layout/html/setup.rb +34 -39
- metadata +13 -3
- data/lib/kward/resources/avatar_kward_logo.rb +0 -50
- data/lib/kward/resources/pixel_logo.rb +0 -232
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Workspace tools
|
|
2
|
+
|
|
3
|
+
Workspace tools let Kward inspect and change the local project. They are the tools behind prompts such as:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
Find where configuration is loaded.
|
|
7
|
+
Add a focused test for this behavior.
|
|
8
|
+
Run the related test file.
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Kward normally chooses these tools itself. You do not need to know their exact names to use them, but understanding the boundaries helps explain why Kward sometimes reads before editing or asks before running broad checks.
|
|
12
|
+
|
|
13
|
+
## Guardrails
|
|
14
|
+
|
|
15
|
+
Workspace tools use the active workspace as their boundary. File paths are workspace-relative by default, and file tools are guarded so Kward does not edit arbitrary unread files.
|
|
16
|
+
|
|
17
|
+
Important behavior:
|
|
18
|
+
|
|
19
|
+
- Existing files must be read in the current conversation before `write_file` or `edit_file` can change them.
|
|
20
|
+
- Reads are bounded to avoid pulling very large files into context by accident.
|
|
21
|
+
- Edits use exact text replacement, so accidental partial or fuzzy changes fail instead of guessing.
|
|
22
|
+
- Shell commands run as your operating-system user from the workspace. They are powerful and should be treated like commands you run yourself.
|
|
23
|
+
|
|
24
|
+
## Reading the workspace
|
|
25
|
+
|
|
26
|
+
### `list_directory`
|
|
27
|
+
|
|
28
|
+
Lists entries in a workspace-relative directory. Kward uses it to discover project structure before reading specific files.
|
|
29
|
+
|
|
30
|
+
Arguments:
|
|
31
|
+
|
|
32
|
+
- `path`: workspace-relative directory.
|
|
33
|
+
|
|
34
|
+
### `read_file`
|
|
35
|
+
|
|
36
|
+
Reads a workspace text file. Output is capped, and Kward can continue with line offsets when it needs more detail.
|
|
37
|
+
|
|
38
|
+
Arguments:
|
|
39
|
+
|
|
40
|
+
- `path`: workspace-relative file path.
|
|
41
|
+
- `offset`: optional 1-indexed start line.
|
|
42
|
+
- `limit`: optional maximum number of lines.
|
|
43
|
+
|
|
44
|
+
A successful read marks the resolved file path as read for the conversation, allowing later edits to that file.
|
|
45
|
+
|
|
46
|
+
### `summarize_file_structure`
|
|
47
|
+
|
|
48
|
+
Returns a compact outline of classes, modules, methods, and functions in a source file. Kward uses it when a file may be too large to read fully at first.
|
|
49
|
+
|
|
50
|
+
Arguments:
|
|
51
|
+
|
|
52
|
+
- `path`: workspace-relative source file path.
|
|
53
|
+
|
|
54
|
+
This tool saves tokens by letting Kward identify relevant entry points before requesting exact line ranges with `read_file`.
|
|
55
|
+
|
|
56
|
+
## Changing files
|
|
57
|
+
|
|
58
|
+
### `write_file`
|
|
59
|
+
|
|
60
|
+
Writes complete file content. Existing files must be read first. New files can be created when the path is inside the workspace.
|
|
61
|
+
|
|
62
|
+
Arguments:
|
|
63
|
+
|
|
64
|
+
- `path`: workspace-relative file path.
|
|
65
|
+
- `content`: complete file content.
|
|
66
|
+
|
|
67
|
+
Use full writes when replacing generated content or creating a new file. For small edits to existing files, Kward should usually prefer `edit_file`.
|
|
68
|
+
|
|
69
|
+
### `edit_file`
|
|
70
|
+
|
|
71
|
+
Applies one or more exact replacements to a file that has already been read.
|
|
72
|
+
|
|
73
|
+
Arguments:
|
|
74
|
+
|
|
75
|
+
- `path`: workspace-relative file path.
|
|
76
|
+
- `edits`: array of replacements:
|
|
77
|
+
- `old_text`: unique exact text to replace.
|
|
78
|
+
- `new_text`: replacement text.
|
|
79
|
+
|
|
80
|
+
Each `old_text` must match exactly once, and replacements must not overlap. This keeps edits deterministic and avoids broad fuzzy rewriting.
|
|
81
|
+
|
|
82
|
+
## Running commands
|
|
83
|
+
|
|
84
|
+
### `run_shell_command`
|
|
85
|
+
|
|
86
|
+
Runs a shell command from the workspace root.
|
|
87
|
+
|
|
88
|
+
Arguments:
|
|
89
|
+
|
|
90
|
+
- `command`: command to run.
|
|
91
|
+
- `timeout_seconds`: optional timeout, default 30 seconds.
|
|
92
|
+
|
|
93
|
+
Kward uses shell commands for tests, linters, build checks, and simple repository inspection. Command output is bounded and may be compacted before it is sent back into model context, while the original output remains available in the session record.
|
|
94
|
+
|
|
95
|
+
## Token behavior
|
|
96
|
+
|
|
97
|
+
Workspace tools are intentionally incremental:
|
|
98
|
+
|
|
99
|
+
1. list directories to find likely files,
|
|
100
|
+
2. summarize large source files before reading everything,
|
|
101
|
+
3. read focused line ranges,
|
|
102
|
+
4. make exact edits,
|
|
103
|
+
5. run focused verification commands.
|
|
104
|
+
|
|
105
|
+
This keeps the model's context window focused on relevant evidence instead of flooding it with entire repositories or long command output.
|
data/lib/kward/cli/commands.rb
CHANGED
|
@@ -53,6 +53,7 @@ module Kward
|
|
|
53
53
|
#{command.call("kward init")} Install starter prompts and PRINCIPLES.md
|
|
54
54
|
#{command.call("kward doctor")} Check local Kward setup
|
|
55
55
|
#{command.call("kward sysprompt")} Inspect the effective system prompt
|
|
56
|
+
#{command.call("kward openrouter refresh")} Refresh cached OpenRouter models
|
|
56
57
|
#{command.call("kward pan")} Start Pan mode web UI
|
|
57
58
|
#{command.call("kward rpc")} Start the experimental JSON-RPC backend
|
|
58
59
|
|
|
@@ -65,6 +66,7 @@ module Kward
|
|
|
65
66
|
#{command.call("doctor")} Check local Kward setup
|
|
66
67
|
#{command.call("sysprompt")} [--raw] Inspect the effective system prompt
|
|
67
68
|
#{command.call("stats tokens")} [range] [options] Export local token telemetry as CSV
|
|
69
|
+
#{command.call("openrouter refresh|list")} Refresh or list cached OpenRouter models
|
|
68
70
|
#{command.call("pan")} Start Pan mode web UI
|
|
69
71
|
#{command.call("rpc")} Run the JSON-RPC backend for UI clients
|
|
70
72
|
|
|
@@ -78,6 +80,7 @@ module Kward
|
|
|
78
80
|
#{command.call("kward")} #{option.call('"Review this diff"')}
|
|
79
81
|
#{command.call("git diff | kward")} #{option.call('"Review this diff"')}
|
|
80
82
|
#{command.call("kward login openrouter")}
|
|
83
|
+
#{command.call("kward openrouter refresh")}
|
|
81
84
|
#{command.call("kward stats tokens today --bucket hour")}
|
|
82
85
|
|
|
83
86
|
Command names take precedence. Anything else is sent as a one-shot prompt.
|
|
@@ -126,6 +129,11 @@ module Kward
|
|
|
126
129
|
description: "Export local token telemetry as CSV.",
|
|
127
130
|
examples: ["kward stats tokens today", "kward stats tokens today --bucket hour", "kward stats tokens week --output tokens.csv"]
|
|
128
131
|
},
|
|
132
|
+
"openrouter" => {
|
|
133
|
+
usage: "kward openrouter refresh|list",
|
|
134
|
+
description: "Refresh or list cached text-capable OpenRouter models available to your API key.",
|
|
135
|
+
examples: ["kward openrouter refresh", "kward openrouter --refresh", "kward openrouter list"]
|
|
136
|
+
},
|
|
129
137
|
"pan" => {
|
|
130
138
|
usage: "kward pan",
|
|
131
139
|
description: "Start Pan mode, a minimal LAN web UI with a prompt textarea and transcript.",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Namespace for the Kward CLI agent runtime.
|
|
2
|
+
module Kward
|
|
3
|
+
# Command-line frontend that coordinates terminal interaction, sessions, tools, and model turns.
|
|
4
|
+
class CLI
|
|
5
|
+
# OpenRouter cache management commands for the terminal CLI flow.
|
|
6
|
+
module OpenRouterCommands
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def handle_openrouter_command(arguments)
|
|
10
|
+
case arguments
|
|
11
|
+
when ["refresh"], ["--refresh"]
|
|
12
|
+
refresh_openrouter_models
|
|
13
|
+
when ["list"], ["--list"]
|
|
14
|
+
list_openrouter_models
|
|
15
|
+
else
|
|
16
|
+
raise ArgumentError, command_usage("openrouter")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def refresh_openrouter_models
|
|
21
|
+
cache = OpenRouterModelCache.new(api_key: configured_openrouter_api_key, path: openrouter_models_cache_path)
|
|
22
|
+
data = cache.refresh
|
|
23
|
+
count = Array(data["models"]).length
|
|
24
|
+
@client.reload_config if @client.respond_to?(:reload_config)
|
|
25
|
+
@prompt.say("Refreshed #{count} OpenRouter text model#{count == 1 ? "" : "s"} for this key.")
|
|
26
|
+
@prompt.say("Cached at: #{cache.path}")
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
warn e.message
|
|
29
|
+
exit 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def list_openrouter_models
|
|
33
|
+
cache = OpenRouterModelCache.new(api_key: configured_openrouter_api_key, path: openrouter_models_cache_path)
|
|
34
|
+
data = cache.read
|
|
35
|
+
unless data
|
|
36
|
+
@prompt.say("No OpenRouter model cache found. Run `kward openrouter refresh` first.")
|
|
37
|
+
return
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
models = Array(data["models"])
|
|
41
|
+
lines = ["OpenRouter models cached at #{data["refreshed_at"]}:"]
|
|
42
|
+
lines.concat(models.map { |model| model["id"].to_s }.reject(&:empty?))
|
|
43
|
+
@prompt.say(lines.join("\n"))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def configured_openrouter_api_key
|
|
47
|
+
ENV["OPENROUTER_API_KEY"].to_s.empty? ? ConfigFiles.config_value(ConfigFiles.read_config, "openrouter_api_key").to_s : ENV["OPENROUTER_API_KEY"].to_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def openrouter_models_cache_path
|
|
51
|
+
File.join(File.dirname(ConfigFiles.config_path), "cache", "openrouter_models.json")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
|
|
1
3
|
# Namespace for the Kward CLI agent runtime.
|
|
2
4
|
module Kward
|
|
3
5
|
# Command-line frontend that coordinates terminal interaction, sessions, tools, and model turns.
|
|
@@ -13,7 +15,6 @@ module Kward
|
|
|
13
15
|
prompt_interface = load_prompt_interface
|
|
14
16
|
return unless prompt_interface
|
|
15
17
|
|
|
16
|
-
banner_enabled = ConfigFiles.banner_enabled?
|
|
17
18
|
@prompt = prompt_interface.new(
|
|
18
19
|
slash_commands: slash_command_entries,
|
|
19
20
|
overlay_settings: ConfigFiles.overlay_settings,
|
|
@@ -22,10 +23,13 @@ module Kward
|
|
|
22
23
|
busy_help: ConfigFiles.composer_busy_help?,
|
|
23
24
|
attachment_badges: method(:composer_attachment_badges),
|
|
24
25
|
attachment_parser: method(:composer_attachment_parser),
|
|
25
|
-
|
|
26
|
-
banner_message: banner_enabled ? Kward::PromptInterface::BANNER_MESSAGE : nil
|
|
26
|
+
banner_message: Kward::PromptInterface::BANNER_MESSAGE
|
|
27
27
|
)
|
|
28
|
-
@prompt.start
|
|
28
|
+
if @prompt.method(:start).parameters.any? { |kind, name| [:key, :keyreq].include?(kind) && name == :render }
|
|
29
|
+
@prompt.start(render: false)
|
|
30
|
+
else
|
|
31
|
+
@prompt.start
|
|
32
|
+
end
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
def load_prompt_interface
|
|
@@ -46,9 +50,80 @@ module Kward
|
|
|
46
50
|
@prompt.respond_to?(:start_stream_block) && @prompt.respond_to?(:write_delta)
|
|
47
51
|
end
|
|
48
52
|
|
|
49
|
-
# Writes the
|
|
53
|
+
# Writes the startup info screen output for the terminal CLI flow.
|
|
50
54
|
def print_visual_banner
|
|
51
|
-
|
|
55
|
+
return unless @prompt.respond_to?(:print_visual_banner)
|
|
56
|
+
|
|
57
|
+
@prompt.print_visual_banner(startup_info_screen)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def startup_info_screen
|
|
61
|
+
[
|
|
62
|
+
startup_status_line,
|
|
63
|
+
"",
|
|
64
|
+
startup_info_line("Workspace", startup_workspace_label),
|
|
65
|
+
startup_info_line("Branch", startup_branch_value),
|
|
66
|
+
startup_info_line("Plugins", startup_plugins_value),
|
|
67
|
+
"",
|
|
68
|
+
startup_brand_line
|
|
69
|
+
].join("\n")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def startup_workspace_label
|
|
73
|
+
root = File.expand_path(current_workspace_root)
|
|
74
|
+
home = begin
|
|
75
|
+
Dir.home
|
|
76
|
+
rescue StandardError
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
if home && (root == home || root.start_with?("#{home}/"))
|
|
80
|
+
relative = root.delete_prefix(home).sub(%r{\A/}, "")
|
|
81
|
+
return "~" if relative.empty?
|
|
82
|
+
return "~/#{relative}" unless relative.include?("/")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
parent = File.basename(File.dirname(root))
|
|
86
|
+
name = File.basename(root)
|
|
87
|
+
parent.empty? || parent == "." ? name : "#{parent}/#{name}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def startup_branch_value
|
|
91
|
+
git_root = startup_git_root(current_workspace_root)
|
|
92
|
+
return "not a repository" if git_root.to_s.empty?
|
|
93
|
+
|
|
94
|
+
branch = startup_git_output(%w[git branch --show-current], root: git_root)
|
|
95
|
+
branch = startup_git_output(%w[git rev-parse --short HEAD], root: git_root) if branch.empty?
|
|
96
|
+
branch.empty? ? "unknown" : branch
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def startup_plugins_value
|
|
100
|
+
filenames = plugin_registry.paths.map { |path| File.basename(path) }
|
|
101
|
+
filenames.empty? ? "none" : filenames.join(", ")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def startup_status_line
|
|
105
|
+
"#{ANSI.colorize("●", :green, enabled: @color_enabled)} Kward v#{Kward::VERSION} is online."
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def startup_info_line(label, value)
|
|
109
|
+
"#{ANSI.colorize(label.ljust(12), :gray, enabled: @color_enabled)}#{ANSI.colorize(value, :cyan, enabled: @color_enabled)}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def startup_brand_line
|
|
113
|
+
ANSI.colorize(Kward::PromptInterface::BANNER_MESSAGE, :bold, enabled: @color_enabled)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def startup_git_root(root)
|
|
117
|
+
startup_git_output(%w[git rev-parse --show-toplevel], root: root)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def startup_git_output(command, root:)
|
|
121
|
+
output, status = Open3.capture2e(*command, chdir: root.to_s)
|
|
122
|
+
return "" unless status.success?
|
|
123
|
+
|
|
124
|
+
output.lines.first.to_s.strip
|
|
125
|
+
rescue StandardError
|
|
126
|
+
""
|
|
52
127
|
end
|
|
53
128
|
|
|
54
129
|
def prompt_footer_renderer
|
|
@@ -103,7 +178,10 @@ module Kward
|
|
|
103
178
|
def composer_context_window(provider = nil, model = nil)
|
|
104
179
|
provider ||= current_footer_conversation.provider || (@client.respond_to?(:current_provider) ? @client.current_provider : "Codex")
|
|
105
180
|
model ||= current_footer_conversation.model || (@client.respond_to?(:current_model) ? @client.current_model : ModelInfo::DEFAULT_OPENAI_MODEL)
|
|
106
|
-
ModelInfo.
|
|
181
|
+
provider = ModelInfo.provider_label(provider)
|
|
182
|
+
return @client.context_window(provider, model) if @client.respond_to?(:context_window) && @client.method(:context_window).arity != 0
|
|
183
|
+
|
|
184
|
+
ModelInfo.context_window(provider, model)
|
|
107
185
|
end
|
|
108
186
|
|
|
109
187
|
def composer_context_usage(provider, model)
|
data/lib/kward/cli/rendering.rb
CHANGED
|
@@ -59,7 +59,7 @@ module Kward
|
|
|
59
59
|
if prompt_interface?
|
|
60
60
|
print_tool_result(tool_call, content, line_limit: INTERACTIVE_TOOL_OUTPUT_LINE_LIMIT)
|
|
61
61
|
else
|
|
62
|
-
@prompt.say("\n#{colored("Tool>", *tool_label_styles(content))}
|
|
62
|
+
@prompt.say("\n#{colored("Tool>", *tool_label_styles(content))} #{tool_summary_display_text(summary)}\n")
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -71,7 +71,7 @@ module Kward
|
|
|
71
71
|
print_block_delta(label, rendered)
|
|
72
72
|
finish_stream_block
|
|
73
73
|
else
|
|
74
|
-
@prompt.say("\n#{colored("#{transcript_label(label)}>", *label_styles(label))}
|
|
74
|
+
@prompt.say("\n#{colored("#{transcript_label(label)}>", *label_styles(label))} #{rendered}\n")
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
|
|
@@ -298,8 +298,9 @@ module Kward
|
|
|
298
298
|
def print_tool_result(tool_call, content, line_limit: nil)
|
|
299
299
|
summary = tool_result_summary(tool_call, content)
|
|
300
300
|
summary = limit_tool_output_lines(summary, line_limit) if line_limit
|
|
301
|
+
display_summary = tool_summary_display_text(summary)
|
|
301
302
|
if prompt_interface?
|
|
302
|
-
summary =
|
|
303
|
+
summary = display_summary.end_with?("\n") ? display_summary : "#{display_summary}\n"
|
|
303
304
|
if @prompt.respond_to?(:write_stream_block)
|
|
304
305
|
@prompt.write_stream_block("Tool", summary, finish: true)
|
|
305
306
|
else
|
|
@@ -309,18 +310,22 @@ module Kward
|
|
|
309
310
|
end
|
|
310
311
|
else
|
|
311
312
|
start_stream_block(tool_stream_label(content))
|
|
312
|
-
print
|
|
313
|
-
puts unless
|
|
313
|
+
print display_summary
|
|
314
|
+
puts unless display_summary.end_with?("\n")
|
|
314
315
|
$stdout.flush
|
|
315
316
|
@stream_block = nil
|
|
316
317
|
end
|
|
317
318
|
end
|
|
318
319
|
|
|
320
|
+
def tool_summary_display_text(summary)
|
|
321
|
+
summary.to_s.sub("\n", "\n\n")
|
|
322
|
+
end
|
|
323
|
+
|
|
319
324
|
def start_stream_block(label)
|
|
320
325
|
return if @stream_block == label
|
|
321
326
|
|
|
322
327
|
puts if @stream_block
|
|
323
|
-
|
|
328
|
+
print "\n#{colored("#{transcript_label(label)}>", *label_styles(label))} "
|
|
324
329
|
@stream_block = label
|
|
325
330
|
end
|
|
326
331
|
|