kward 0.68.0 → 0.69.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 +48 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +34 -0
- data/Gemfile.lock +8 -2
- data/README.md +32 -25
- data/Rakefile +14 -1
- data/doc/authentication.md +74 -56
- data/doc/code-search.md +55 -28
- data/doc/configuration.md +18 -0
- data/doc/extensibility.md +89 -128
- data/doc/getting-started.md +52 -54
- data/doc/memory.md +51 -118
- data/doc/personas.md +417 -0
- data/doc/plugins.md +55 -97
- data/doc/releasing.md +3 -1
- data/doc/rpc.md +1 -1
- data/doc/usage.md +125 -144
- data/doc/web-search.md +80 -14
- data/exe/kward +2 -0
- data/lib/kward/agent.rb +1 -1
- data/lib/kward/cli/commands.rb +10 -3
- data/lib/kward/cli/compaction.rb +3 -3
- data/lib/kward/cli/interactive_turn.rb +3 -1
- data/lib/kward/cli/memory_commands.rb +16 -16
- data/lib/kward/cli/plugins.rb +3 -3
- data/lib/kward/cli/prompt_interface.rb +15 -13
- data/lib/kward/cli/rendering.rb +35 -46
- data/lib/kward/cli/runtime_helpers.rb +13 -2
- data/lib/kward/cli/sessions.rb +21 -21
- data/lib/kward/cli/settings.rb +49 -43
- data/lib/kward/cli/slash_commands.rb +6 -4
- data/lib/kward/cli/stats.rb +2 -2
- data/lib/kward/cli/sysprompt.rb +57 -0
- data/lib/kward/cli/tool_summaries.rb +5 -1
- data/lib/kward/cli.rb +14 -2
- data/lib/kward/cli_transcript_formatter.rb +36 -5
- data/lib/kward/compactor.rb +2 -2
- data/lib/kward/config_files.rb +45 -10
- data/lib/kward/conversation.rb +41 -9
- data/lib/kward/memory/manager.rb +131 -14
- data/lib/kward/message_access.rb +6 -0
- data/lib/kward/model/context_usage.rb +11 -10
- data/lib/kward/model/model_info.rb +18 -1
- data/lib/kward/model/payloads.rb +89 -10
- data/lib/kward/model/stream_parser.rb +258 -25
- data/lib/kward/prompt_interface/question_prompt.rb +1 -1
- data/lib/kward/prompt_interface/transcript_renderer.rb +20 -11
- data/lib/kward/prompts.rb +61 -7
- data/lib/kward/rpc/server.rb +7 -2
- data/lib/kward/rpc/session_manager.rb +18 -2
- data/lib/kward/rpc/session_metrics.rb +2 -2
- data/lib/kward/rpc/transcript_normalizer.rb +47 -0
- data/lib/kward/session_store.rb +40 -1
- data/lib/kward/starter_pack_installer.rb +2 -2
- data/lib/kward/tools/fetch_content.rb +41 -0
- data/lib/kward/tools/fetch_raw.rb +40 -0
- data/lib/kward/tools/registry.rb +9 -2
- data/lib/kward/tools/search/web.rb +3 -3
- data/lib/kward/tools/search/web_fetch.rb +202 -0
- data/lib/kward/tools/tool_call.rb +2 -0
- data/lib/kward/version.rb +1 -1
- data/templates/default/fulldoc/html/css/kward.css +1501 -0
- data/templates/default/fulldoc/html/images/kward_logo.png +0 -0
- data/templates/default/fulldoc/html/js/kward.js +296 -0
- data/templates/default/fulldoc/html/setup.rb +8 -0
- data/templates/default/layout/html/breadcrumb.erb +11 -0
- data/templates/default/layout/html/layout.erb +141 -0
- data/templates/default/layout/html/setup.rb +139 -0
- metadata +14 -1
data/doc/web-search.md
CHANGED
|
@@ -1,28 +1,94 @@
|
|
|
1
1
|
# Web search
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use web search when the answer depends on current or external information:
|
|
4
|
+
|
|
5
|
+
- current framework or dependency docs,
|
|
6
|
+
- release notes and migration guides,
|
|
7
|
+
- security advisories,
|
|
8
|
+
- pricing or provider pages,
|
|
9
|
+
- bug reports and issue discussions,
|
|
10
|
+
- a specific URL you want Kward to inspect.
|
|
4
11
|
|
|
5
12
|
Example prompts:
|
|
6
13
|
|
|
7
14
|
```text
|
|
8
|
-
|
|
15
|
+
Check the current Rails release notes and summarize migration risks for this project.
|
|
9
16
|
Find the official OpenRouter docs for model configuration.
|
|
10
17
|
Check whether this dependency has a recent security advisory.
|
|
18
|
+
Read this URL and explain the setup steps: https://example.com/docs
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How Kward researches
|
|
22
|
+
|
|
23
|
+
Kward has three web tools:
|
|
24
|
+
|
|
25
|
+
1. `web_search` finds candidate sources.
|
|
26
|
+
2. `fetch_content` reads human-readable pages.
|
|
27
|
+
3. `fetch_raw` reads machine-readable files such as JSON, YAML, XML, RSS, OpenAPI specs, or plain text.
|
|
28
|
+
|
|
29
|
+
A good research flow is:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
Search for the official docs, fetch the relevant page, then answer with the source URL.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Kward should search first, then fetch important pages before relying on them.
|
|
36
|
+
|
|
37
|
+
## Network behavior
|
|
38
|
+
|
|
39
|
+
Web tools are advertised to the model by default. Queries and fetched URLs are sent over the network to the selected provider or target host.
|
|
40
|
+
|
|
41
|
+
In automatic mode, provider fallback is:
|
|
42
|
+
|
|
43
|
+
1. Exa API when `EXA_API_KEY` is configured, otherwise keyless Exa MCP.
|
|
44
|
+
2. Perplexity API when configured and model-provider fallback is allowed.
|
|
45
|
+
3. Gemini API with Google Search grounding when configured and model-provider fallback is allowed.
|
|
46
|
+
4. DuckDuckGo HTML search, then bundled public SearXNG instances.
|
|
47
|
+
|
|
48
|
+
You do not need an API key for basic web search, but keys can improve limits or provider choice.
|
|
49
|
+
|
|
50
|
+
## Disable web tools
|
|
51
|
+
|
|
52
|
+
Hide all web tools:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"web_search": {
|
|
57
|
+
"enabled": false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
11
60
|
```
|
|
12
61
|
|
|
13
|
-
|
|
62
|
+
Use this when working on private projects where no prompt should trigger external lookup.
|
|
63
|
+
|
|
64
|
+
## Tool details
|
|
65
|
+
|
|
66
|
+
### `web_search`
|
|
67
|
+
|
|
68
|
+
Finds candidate sources. Arguments:
|
|
69
|
+
|
|
70
|
+
- `queries`: one to four search strings.
|
|
71
|
+
- `max_results`: results per query, default 5, capped at 20.
|
|
72
|
+
- `provider`: optional `auto`, `exa`, `perplexity`, `gemini`, or `duckduckgo`.
|
|
73
|
+
- `recency_filter`: optional `day`, `week`, `month`, or `year`.
|
|
74
|
+
- `domain_filter`: optional domains to include, or domains prefixed with `-` to exclude.
|
|
75
|
+
|
|
76
|
+
### `fetch_content`
|
|
77
|
+
|
|
78
|
+
Reads a specific HTTP or HTTPS page and extracts readable text. Use it for docs pages, articles, issues, and release notes.
|
|
79
|
+
|
|
80
|
+
Arguments:
|
|
81
|
+
|
|
82
|
+
- `url`
|
|
83
|
+
- `max_bytes`: default 16384, capped at 131072.
|
|
84
|
+
- `extract`: optional `auto`, `text`, or `markdown`.
|
|
14
85
|
|
|
15
|
-
|
|
16
|
-
2. Perplexity API when configured and `allow_model_providers` is true
|
|
17
|
-
3. Gemini API with Google Search grounding when configured and `allow_model_providers` is true
|
|
18
|
-
4. DuckDuckGo HTML search, then bundled public SearXNG instances
|
|
86
|
+
### `fetch_raw`
|
|
19
87
|
|
|
20
|
-
|
|
88
|
+
Reads a specific HTTP or HTTPS resource without readability extraction. Use it for JSON, YAML, XML, RSS, OpenAPI specs, and plain text.
|
|
21
89
|
|
|
22
|
-
|
|
90
|
+
Arguments:
|
|
23
91
|
|
|
24
|
-
- `
|
|
25
|
-
- `
|
|
26
|
-
- `
|
|
27
|
-
- `recency_filter`: optional `day`, `week`, `month`, or `year`
|
|
28
|
-
- `domain_filter`: optional list of included domains, or excluded domains prefixed with `-`
|
|
92
|
+
- `url`
|
|
93
|
+
- `max_bytes`: default 16384, capped at 131072.
|
|
94
|
+
- `accept`: optional HTTP `Accept` header.
|
data/exe/kward
CHANGED
data/lib/kward/agent.rb
CHANGED
data/lib/kward/cli/commands.rb
CHANGED
|
@@ -50,8 +50,9 @@ module Kward
|
|
|
50
50
|
#{command.call("kward")} #{option.call('"Explain this project"')} Run a one-shot prompt
|
|
51
51
|
#{command.call("kward login")} Sign in or save provider credentials
|
|
52
52
|
#{command.call("kward auth status")} Show saved credential status
|
|
53
|
-
#{command.call("kward init")} Install starter prompts and
|
|
53
|
+
#{command.call("kward init")} Install starter prompts and PRINCIPLES.md
|
|
54
54
|
#{command.call("kward doctor")} Check local Kward setup
|
|
55
|
+
#{command.call("kward sysprompt")} Inspect the effective system prompt
|
|
55
56
|
#{command.call("kward pan")} Start Pan mode web UI
|
|
56
57
|
#{command.call("kward rpc")} Start the experimental JSON-RPC backend
|
|
57
58
|
|
|
@@ -60,8 +61,9 @@ module Kward
|
|
|
60
61
|
#{command.call("version")} Show the installed Kward version
|
|
61
62
|
#{command.call("login")} [anthropic|openrouter|github] Sign in with OpenAI, Anthropic, OpenRouter, or GitHub
|
|
62
63
|
#{command.call("auth status|logout")} Show or clear saved credentials
|
|
63
|
-
#{command.call("init")} Install starter prompts and
|
|
64
|
+
#{command.call("init")} Install starter prompts and PRINCIPLES.md
|
|
64
65
|
#{command.call("doctor")} Check local Kward setup
|
|
66
|
+
#{command.call("sysprompt")} [--raw] Inspect the effective system prompt
|
|
65
67
|
#{command.call("stats tokens")} [range] [options] Export local token telemetry as CSV
|
|
66
68
|
#{command.call("pan")} Start Pan mode web UI
|
|
67
69
|
#{command.call("rpc")} Run the JSON-RPC backend for UI clients
|
|
@@ -106,7 +108,7 @@ module Kward
|
|
|
106
108
|
},
|
|
107
109
|
"init" => {
|
|
108
110
|
usage: "kward init",
|
|
109
|
-
description: "Install starter prompts and base
|
|
111
|
+
description: "Install starter prompts and base PRINCIPLES.md into your config directory.",
|
|
110
112
|
examples: ["kward init"]
|
|
111
113
|
},
|
|
112
114
|
"doctor" => {
|
|
@@ -114,6 +116,11 @@ module Kward
|
|
|
114
116
|
description: "Check local Kward configuration, workspace, auth hints, and writable directories.",
|
|
115
117
|
examples: ["kward doctor", "kward --working-directory ~/code/project doctor"]
|
|
116
118
|
},
|
|
119
|
+
"sysprompt" => {
|
|
120
|
+
usage: "kward sysprompt [--raw]",
|
|
121
|
+
description: "Inspect the effective system prompt for a new conversation in the current workspace.",
|
|
122
|
+
examples: ["kward sysprompt", "kward sysprompt --raw", "kward --working-directory ~/code/project sysprompt"]
|
|
123
|
+
},
|
|
117
124
|
"stats" => {
|
|
118
125
|
usage: "kward stats tokens [range] [--bucket second|minute|hour|day|week|month|year] [--output path]",
|
|
119
126
|
description: "Export local token telemetry as CSV.",
|
data/lib/kward/cli/compaction.rb
CHANGED
|
@@ -12,12 +12,12 @@ module Kward
|
|
|
12
12
|
client: @client,
|
|
13
13
|
tool_result_summarizer: lambda { |tool_call, content| tool_result_summary(tool_call, content) }
|
|
14
14
|
).compact(custom_instructions: argument)
|
|
15
|
-
|
|
15
|
+
runtime_output("Compacted context: #{result.old_message_count} messages -> #{result.new_message_count} messages.")
|
|
16
16
|
render_transcript_block("Assistant", result.summary)
|
|
17
17
|
rescue Compactor::NothingToCompact, Compactor::AlreadyCompacted, Compactor::EmptySummary => e
|
|
18
|
-
|
|
18
|
+
runtime_output(e.message)
|
|
19
19
|
rescue StandardError => e
|
|
20
|
-
|
|
20
|
+
runtime_output("Compaction error: #{e.message}")
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
end
|
|
@@ -95,6 +95,9 @@ module Kward
|
|
|
95
95
|
when Events::AssistantDelta
|
|
96
96
|
stream_state[:streamed] = true
|
|
97
97
|
append_markdown_delta(markdown_chunks, "Assistant", event.delta)
|
|
98
|
+
when Events::Steering
|
|
99
|
+
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
100
|
+
print_user_transcript(event.input)
|
|
98
101
|
when Events::SteeringApplied
|
|
99
102
|
@prompt.clear_steered_count if @prompt.respond_to?(:clear_steered_count)
|
|
100
103
|
when Events::Retry
|
|
@@ -104,7 +107,6 @@ module Kward
|
|
|
104
107
|
when Events::ToolCall
|
|
105
108
|
stream_state[:streamed] = true
|
|
106
109
|
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
107
|
-
print_tool_call(event.tool_call)
|
|
108
110
|
when Events::ToolResult
|
|
109
111
|
stream_state[:streamed] = true
|
|
110
112
|
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
@@ -18,53 +18,53 @@ module Kward
|
|
|
18
18
|
when "enable"
|
|
19
19
|
manager.enable
|
|
20
20
|
agent.conversation.refresh_system_message!
|
|
21
|
-
|
|
21
|
+
runtime_output("Memory enabled.")
|
|
22
22
|
when "disable"
|
|
23
23
|
manager.disable
|
|
24
24
|
agent.conversation.memory_context = nil
|
|
25
25
|
agent.conversation.refresh_system_message!
|
|
26
|
-
|
|
26
|
+
runtime_output("Memory disabled.")
|
|
27
27
|
when "auto-summary"
|
|
28
28
|
case rest.to_s.strip
|
|
29
29
|
when "enable", "on"
|
|
30
30
|
manager.auto_summary_enable
|
|
31
|
-
|
|
31
|
+
runtime_output("Memory auto-summary enabled.")
|
|
32
32
|
when "disable", "off"
|
|
33
33
|
manager.auto_summary_disable
|
|
34
|
-
|
|
34
|
+
runtime_output("Memory auto-summary disabled.")
|
|
35
35
|
else
|
|
36
|
-
|
|
36
|
+
runtime_output("Usage: /memory auto-summary enable|disable")
|
|
37
37
|
end
|
|
38
38
|
when "core"
|
|
39
39
|
record = manager.add_core(unquote_argument(rest))
|
|
40
|
-
|
|
40
|
+
runtime_output("Added core memory #{record["id"]}.")
|
|
41
41
|
when "add"
|
|
42
42
|
record = manager.add_soft(unquote_argument(rest), scope: "workspace:#{agent.conversation.workspace_root}")
|
|
43
|
-
|
|
43
|
+
runtime_output("Added soft memory #{record["id"]}.")
|
|
44
44
|
when "list"
|
|
45
|
-
|
|
45
|
+
runtime_output(format_memory_list(manager.hierarchy(workspace_root: agent.conversation.workspace_root)))
|
|
46
46
|
when "forget"
|
|
47
47
|
forgotten = manager.forget_memory(rest.to_s.strip)
|
|
48
|
-
|
|
48
|
+
runtime_output(forgotten ? "Forgot #{rest.to_s.strip}." : "No memory found for #{rest.to_s.strip}.")
|
|
49
49
|
when "promote"
|
|
50
50
|
record = manager.promote_memory(rest.to_s.strip)
|
|
51
|
-
|
|
51
|
+
runtime_output("Promoted memory #{record["id"]}.")
|
|
52
52
|
when "relax"
|
|
53
53
|
record = manager.relax_core(rest.to_s.strip, workspace_root: agent.conversation.workspace_root)
|
|
54
|
-
|
|
54
|
+
runtime_output("Relaxed memory #{record["id"]}.")
|
|
55
55
|
when "inspect"
|
|
56
|
-
|
|
56
|
+
runtime_output(JSON.pretty_generate(manager.inspect_memory))
|
|
57
57
|
when "why"
|
|
58
58
|
explanation = agent.conversation.last_memory_retrieval || manager.explain_retrieval
|
|
59
|
-
|
|
59
|
+
runtime_output(format_memory_why(explanation))
|
|
60
60
|
when "summarize", "learn"
|
|
61
61
|
records = summarize_memory(agent.conversation, manager: manager)
|
|
62
|
-
|
|
62
|
+
runtime_output("Learned #{records.length} soft #{records.length == 1 ? "memory" : "memories"}.")
|
|
63
63
|
else
|
|
64
|
-
|
|
64
|
+
runtime_output("Usage: /memory enable|disable|auto-summary enable|disable|core <text>|add <text>|list|forget <id>|promote <id>|relax <id>|inspect|why|summarize")
|
|
65
65
|
end
|
|
66
66
|
rescue StandardError => e
|
|
67
|
-
|
|
67
|
+
runtime_output("Memory command failed: #{e.message}")
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def summarize_memory(conversation, manager: Memory::Manager.new)
|
data/lib/kward/cli/plugins.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Kward
|
|
|
26
26
|
@plugin_registry = PluginRegistry.load(reserved_commands: reserved_slash_command_names)
|
|
27
27
|
conversation.plugin_registry = @plugin_registry if conversation.respond_to?(:plugin_registry=)
|
|
28
28
|
conversation.refresh_system_message! if conversation.respond_to?(:refresh_system_message!)
|
|
29
|
-
|
|
29
|
+
runtime_output("Plugins reloaded.")
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def reserved_slash_command_names
|
|
@@ -62,7 +62,7 @@ module Kward
|
|
|
62
62
|
command.handler.call(argument, context)
|
|
63
63
|
[true, nil]
|
|
64
64
|
rescue StandardError => e
|
|
65
|
-
|
|
65
|
+
runtime_output("Plugin command /#{name} error: #{e.message}")
|
|
66
66
|
[true, nil]
|
|
67
67
|
end
|
|
68
68
|
|
|
@@ -72,7 +72,7 @@ module Kward
|
|
|
72
72
|
args: args,
|
|
73
73
|
session: @active_session,
|
|
74
74
|
workspace_root: conversation.workspace_root,
|
|
75
|
-
say_callback: lambda { |message|
|
|
75
|
+
say_callback: lambda { |message| runtime_output(message) }
|
|
76
76
|
)
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -52,10 +52,12 @@ module Kward
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def prompt_footer_renderer
|
|
55
|
-
|
|
56
|
-
return nil unless renderer
|
|
55
|
+
return nil unless plugin_registry.footer_renderer
|
|
57
56
|
|
|
58
57
|
lambda do
|
|
58
|
+
renderer = plugin_registry.footer_renderer
|
|
59
|
+
next "" unless renderer
|
|
60
|
+
|
|
59
61
|
context = plugin_context(current_footer_conversation, "")
|
|
60
62
|
renderer.call(context).to_s
|
|
61
63
|
rescue StandardError => e
|
|
@@ -65,9 +67,10 @@ module Kward
|
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
def composer_status_text
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
conversation = current_footer_conversation
|
|
71
|
+
provider = conversation.provider || (@client.respond_to?(:current_provider) ? @client.current_provider : "Codex")
|
|
72
|
+
model = conversation.model || (@client.respond_to?(:current_model) ? @client.current_model : ModelInfo::DEFAULT_OPENAI_MODEL)
|
|
73
|
+
reasoning = conversation.reasoning_effort || (@client.respond_to?(:current_reasoning_effort) ? @client.current_reasoning_effort : ModelInfo::DEFAULT_REASONING_EFFORT)
|
|
71
74
|
reasoning = "n/a" unless ModelInfo.reasoning_supported?(provider, model) && !reasoning.to_s.empty?
|
|
72
75
|
text = "#{provider} #{model} · #{reasoning}"
|
|
73
76
|
parts = []
|
|
@@ -97,19 +100,18 @@ module Kward
|
|
|
97
100
|
ANSI.colorize("#{value}%", color, enabled: @color_enabled)
|
|
98
101
|
end
|
|
99
102
|
|
|
100
|
-
def composer_context_window
|
|
101
|
-
provider
|
|
102
|
-
model
|
|
103
|
-
|
|
104
|
-
@client.respond_to?(:current_context_window) ? @client.current_context_window : ModelInfo.context_window(provider, model)
|
|
103
|
+
def composer_context_window(provider = nil, model = nil)
|
|
104
|
+
provider ||= current_footer_conversation.provider || (@client.respond_to?(:current_provider) ? @client.current_provider : "Codex")
|
|
105
|
+
model ||= current_footer_conversation.model || (@client.respond_to?(:current_model) ? @client.current_model : ModelInfo::DEFAULT_OPENAI_MODEL)
|
|
106
|
+
ModelInfo.context_window(ModelInfo.provider_label(provider), model)
|
|
105
107
|
end
|
|
106
108
|
|
|
107
109
|
def composer_context_usage(provider, model)
|
|
108
|
-
context_window = composer_context_window
|
|
110
|
+
context_window = composer_context_window(provider, model)
|
|
109
111
|
context_parts = if @client.respond_to?(:current_context_parts)
|
|
110
|
-
@client.current_context_parts(current_footer_conversation.
|
|
112
|
+
@client.current_context_parts(current_footer_conversation.context_messages, footer_tool_schemas)
|
|
111
113
|
else
|
|
112
|
-
{ provider: provider, model: model, messages: current_footer_conversation.
|
|
114
|
+
{ provider: provider, model: model, messages: current_footer_conversation.context_messages, tools: footer_tool_schemas }
|
|
113
115
|
end
|
|
114
116
|
@context_usage.call(
|
|
115
117
|
provider: provider,
|
data/lib/kward/cli/rendering.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Kward
|
|
|
8
8
|
|
|
9
9
|
def render_conversation_transcript(conversation)
|
|
10
10
|
tool_calls_by_id = {}
|
|
11
|
-
@prompt.say("\n#{colored("Transcript", :
|
|
11
|
+
@prompt.say("\n#{colored("Transcript", :gray, :bold)}\n")
|
|
12
12
|
conversation.messages.each do |message|
|
|
13
13
|
role = message_role(message)
|
|
14
14
|
next if role == "system"
|
|
@@ -26,7 +26,6 @@ module Kward
|
|
|
26
26
|
render_assistant_message(message)
|
|
27
27
|
message_tool_calls(message).each do |tool_call|
|
|
28
28
|
tool_calls_by_id[tool_call_id(tool_call)] = tool_call
|
|
29
|
-
render_tool_call(tool_call)
|
|
30
29
|
end
|
|
31
30
|
when "tool"
|
|
32
31
|
render_tool_message(message, tool_calls_by_id)
|
|
@@ -44,7 +43,7 @@ module Kward
|
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
def render_assistant_message(message)
|
|
47
|
-
content = CLITranscriptFormatter.
|
|
46
|
+
content = CLITranscriptFormatter.assistant_content_text(message)
|
|
48
47
|
return if content.empty?
|
|
49
48
|
|
|
50
49
|
render_transcript_block("Assistant", content)
|
|
@@ -55,20 +54,12 @@ module Kward
|
|
|
55
54
|
render_tool_result(tool_call, message_content(message).to_s)
|
|
56
55
|
end
|
|
57
56
|
|
|
58
|
-
def render_tool_call(tool_call)
|
|
59
|
-
if prompt_interface?
|
|
60
|
-
print_tool_call(tool_call)
|
|
61
|
-
else
|
|
62
|
-
@prompt.say("\n#{colored("Tool>", :magenta, :bold)}\n#{tool_command(tool_call)}\n")
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
57
|
def render_tool_result(tool_call, content)
|
|
67
58
|
summary = limit_tool_output_lines(tool_result_summary(tool_call, content), INTERACTIVE_TOOL_OUTPUT_LINE_LIMIT)
|
|
68
59
|
if prompt_interface?
|
|
69
60
|
print_tool_result(tool_call, content, line_limit: INTERACTIVE_TOOL_OUTPUT_LINE_LIMIT)
|
|
70
61
|
else
|
|
71
|
-
@prompt.say("\n#{colored("Tool
|
|
62
|
+
@prompt.say("\n#{colored("Tool>", *tool_label_styles(content))}\n#{summary}\n")
|
|
72
63
|
end
|
|
73
64
|
end
|
|
74
65
|
|
|
@@ -80,7 +71,7 @@ module Kward
|
|
|
80
71
|
print_block_delta(label, rendered)
|
|
81
72
|
finish_stream_block
|
|
82
73
|
else
|
|
83
|
-
@prompt.say("\n#{colored("#{transcript_label(label)}>",
|
|
74
|
+
@prompt.say("\n#{colored("#{transcript_label(label)}>", *label_styles(label))}\n#{rendered}\n")
|
|
84
75
|
end
|
|
85
76
|
end
|
|
86
77
|
|
|
@@ -102,7 +93,6 @@ module Kward
|
|
|
102
93
|
:streamed
|
|
103
94
|
when Events::ToolCall
|
|
104
95
|
flush_markdown_deltas(markdown_chunks)
|
|
105
|
-
print_tool_call(event.tool_call)
|
|
106
96
|
:streamed
|
|
107
97
|
when Events::ToolResult
|
|
108
98
|
flush_markdown_deltas(markdown_chunks)
|
|
@@ -304,24 +294,6 @@ module Kward
|
|
|
304
294
|
RetryMessage.format(event)
|
|
305
295
|
end
|
|
306
296
|
|
|
307
|
-
# Writes the tool call output for the terminal CLI flow.
|
|
308
|
-
def print_tool_call(tool_call)
|
|
309
|
-
if prompt_interface?
|
|
310
|
-
if @prompt.respond_to?(:write_stream_block)
|
|
311
|
-
@prompt.write_stream_block("Tool", "#{tool_command(tool_call)}\n", finish: true)
|
|
312
|
-
else
|
|
313
|
-
@prompt.start_stream_block("Tool")
|
|
314
|
-
@prompt.write_delta("#{tool_command(tool_call)}\n")
|
|
315
|
-
@prompt.finish_stream_block
|
|
316
|
-
end
|
|
317
|
-
else
|
|
318
|
-
start_stream_block("Tool")
|
|
319
|
-
puts tool_command(tool_call)
|
|
320
|
-
$stdout.flush
|
|
321
|
-
@stream_block = nil
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
|
|
325
297
|
# Writes the tool result output for the terminal CLI flow.
|
|
326
298
|
def print_tool_result(tool_call, content, line_limit: nil)
|
|
327
299
|
summary = tool_result_summary(tool_call, content)
|
|
@@ -329,14 +301,14 @@ module Kward
|
|
|
329
301
|
if prompt_interface?
|
|
330
302
|
summary = summary.end_with?("\n") ? summary : "#{summary}\n"
|
|
331
303
|
if @prompt.respond_to?(:write_stream_block)
|
|
332
|
-
@prompt.write_stream_block("Tool
|
|
304
|
+
@prompt.write_stream_block("Tool", summary, finish: true)
|
|
333
305
|
else
|
|
334
|
-
@prompt.start_stream_block("Tool
|
|
306
|
+
@prompt.start_stream_block("Tool")
|
|
335
307
|
@prompt.write_delta(summary)
|
|
336
308
|
@prompt.finish_stream_block
|
|
337
309
|
end
|
|
338
310
|
else
|
|
339
|
-
start_stream_block(
|
|
311
|
+
start_stream_block(tool_stream_label(content))
|
|
340
312
|
print summary
|
|
341
313
|
puts unless summary.end_with?("\n")
|
|
342
314
|
$stdout.flush
|
|
@@ -348,7 +320,7 @@ module Kward
|
|
|
348
320
|
return if @stream_block == label
|
|
349
321
|
|
|
350
322
|
puts if @stream_block
|
|
351
|
-
puts "\n#{colored("#{transcript_label(label)}>",
|
|
323
|
+
puts "\n#{colored("#{transcript_label(label)}>", *label_styles(label))}"
|
|
352
324
|
@stream_block = label
|
|
353
325
|
end
|
|
354
326
|
|
|
@@ -366,24 +338,41 @@ module Kward
|
|
|
366
338
|
end
|
|
367
339
|
|
|
368
340
|
def transcript_label(label)
|
|
369
|
-
|
|
341
|
+
case label
|
|
342
|
+
when "Assistant"
|
|
343
|
+
assistant_prompt_name
|
|
344
|
+
when "Tool failed"
|
|
345
|
+
"Tool"
|
|
346
|
+
else
|
|
347
|
+
label
|
|
348
|
+
end
|
|
370
349
|
end
|
|
371
350
|
|
|
372
|
-
def
|
|
351
|
+
def label_styles(label)
|
|
373
352
|
case label
|
|
374
|
-
when "Reasoning"
|
|
375
|
-
:
|
|
353
|
+
when "Reasoning", "Compaction summary"
|
|
354
|
+
[:gray, :bold]
|
|
376
355
|
when "Assistant", "Kward"
|
|
377
|
-
:green
|
|
378
|
-
when "Tool"
|
|
379
|
-
:
|
|
380
|
-
when "Tool
|
|
381
|
-
:
|
|
356
|
+
[:green, :bold]
|
|
357
|
+
when "Tool", "Tool output"
|
|
358
|
+
[:cyan, :bold]
|
|
359
|
+
when "Tool failed"
|
|
360
|
+
[:red, :bold]
|
|
361
|
+
when "Retry"
|
|
362
|
+
[:yellow, :bold]
|
|
382
363
|
else
|
|
383
|
-
:
|
|
364
|
+
[:gray, :bold]
|
|
384
365
|
end
|
|
385
366
|
end
|
|
386
367
|
|
|
368
|
+
def tool_stream_label(content)
|
|
369
|
+
tool_result_failed?(content) ? "Tool failed" : "Tool"
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def tool_label_styles(content)
|
|
373
|
+
label_styles(tool_stream_label(content))
|
|
374
|
+
end
|
|
375
|
+
|
|
387
376
|
end
|
|
388
377
|
end
|
|
389
378
|
end
|
|
@@ -31,6 +31,17 @@ module Kward
|
|
|
31
31
|
@assistant_prompt || "Assistant>"
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
def runtime_output_prompt
|
|
35
|
+
"Runtime>"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def runtime_output(text)
|
|
39
|
+
content = text.to_s.chomp
|
|
40
|
+
label = colored(runtime_output_prompt, :gray, :bold)
|
|
41
|
+
separator = content.include?("\n") ? "\n" : " "
|
|
42
|
+
@prompt.say("\n#{label}#{separator}#{content}\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
34
45
|
def build_interactive_agent(conversation)
|
|
35
46
|
conversation.plugin_registry ||= plugin_registry if conversation.respond_to?(:plugin_registry)
|
|
36
47
|
workspace = configured_workspace(root: conversation.workspace_root)
|
|
@@ -47,13 +58,13 @@ module Kward
|
|
|
47
58
|
def handle_interactive_shell_command(input, agent)
|
|
48
59
|
command = input.to_s.sub(/\A!\s*/, "")
|
|
49
60
|
if command.strip.empty?
|
|
50
|
-
|
|
61
|
+
runtime_output("Shell command is required after !")
|
|
51
62
|
return true
|
|
52
63
|
end
|
|
53
64
|
|
|
54
65
|
run_busy_local_command_and_requeue(activity: "running") do
|
|
55
66
|
result = configured_workspace(root: interactive_workspace_root(agent)).run_shell_command(command)
|
|
56
|
-
@prompt.say("\n#{colored("Shell>", :
|
|
67
|
+
@prompt.say("\n#{colored("Shell>", :cyan, :bold)} #{command}\n#{result}\n")
|
|
57
68
|
end
|
|
58
69
|
true
|
|
59
70
|
end
|