kward 0.82.0 → 0.84.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 +96 -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 +1 -0
- data/doc/api.md +4 -0
- data/doc/composer.md +2 -2
- data/doc/configuration.md +82 -20
- data/doc/editor.md +47 -22
- data/doc/files.md +12 -7
- data/doc/getting-started.md +3 -0
- data/doc/pan.md +19 -15
- data/doc/permissions.md +1 -0
- data/doc/platform-support.md +48 -0
- data/doc/releasing.md +12 -4
- data/doc/rpc.md +2 -2
- data/doc/sandboxing.md +5 -1
- data/doc/security.md +4 -3
- data/doc/shell.md +101 -60
- data/doc/tabs.md +3 -0
- data/doc/troubleshooting.md +12 -2
- data/doc/usage.md +7 -6
- data/kward.gemspec +5 -4
- data/lib/kward/agent.rb +7 -3
- data/lib/kward/ansi.rb +110 -10
- data/lib/kward/auth/anthropic_oauth.rb +7 -7
- data/lib/kward/cli/auth_commands.rb +34 -13
- data/lib/kward/cli/commands.rb +83 -62
- data/lib/kward/cli/doctor.rb +39 -17
- data/lib/kward/cli/hook_commands.rb +22 -12
- data/lib/kward/cli/interactive_turn.rb +144 -21
- data/lib/kward/cli/project_skills_commands.rb +8 -4
- data/lib/kward/cli/prompt_interface.rb +27 -0
- data/lib/kward/cli/rendering.rb +15 -9
- data/lib/kward/cli/runtime_helpers.rb +260 -55
- data/lib/kward/cli/slash_commands.rb +21 -12
- data/lib/kward/cli/tabs.rb +91 -13
- data/lib/kward/cli/tool_summaries.rb +14 -0
- data/lib/kward/cli.rb +55 -7
- data/lib/kward/cli_transcript_formatter.rb +11 -4
- data/lib/kward/compaction/token_estimator.rb +12 -6
- data/lib/kward/config_files.rb +94 -63
- data/lib/kward/detached_run.rb +44 -0
- data/lib/kward/editor_prompt.rb +46 -0
- data/lib/kward/editor_prompt_session.rb +28 -0
- data/lib/kward/interactive_pty_runner.rb +102 -28
- data/lib/kward/{ekwsh.rb → kwsh.rb} +103 -24
- data/lib/kward/kwshrc.rb +233 -0
- data/lib/kward/markdown_code_block.rb +136 -0
- data/lib/kward/model/client.rb +35 -39
- data/lib/kward/model/model_info.rb +3 -2
- data/lib/kward/model/payloads.rb +0 -2
- data/lib/kward/model/provider_catalog.rb +5 -0
- data/lib/kward/model/stream_parser.rb +20 -4
- data/lib/kward/pan/index.html.erb +3 -3
- data/lib/kward/pan/server.rb +23 -3
- data/lib/kward/persistent_shell_session.rb +843 -0
- data/lib/kward/project_files.rb +19 -6
- data/lib/kward/prompt_interface/composer_renderer.rb +45 -41
- data/lib/kward/prompt_interface/composer_state.rb +33 -24
- data/lib/kward/prompt_interface/editor/auto_indent.rb +27 -22
- data/lib/kward/prompt_interface/editor/controller.rb +99 -33
- data/lib/kward/prompt_interface/editor/endwise.rb +13 -4
- data/lib/kward/prompt_interface/editor/modes/modern.rb +4 -0
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +298 -47
- data/lib/kward/prompt_interface/editor/renderer.rb +109 -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/state.rb +21 -10
- 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/editor/word_completion.rb +124 -0
- data/lib/kward/prompt_interface/file_overlay.rb +92 -22
- data/lib/kward/prompt_interface/key_handler.rb +75 -0
- data/lib/kward/prompt_interface/overlay_renderer.rb +7 -5
- data/lib/kward/prompt_interface/project_browser.rb +443 -17
- data/lib/kward/prompt_interface/runtime_state.rb +55 -2
- data/lib/kward/prompt_interface/screen.rb +2 -2
- 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 +169 -18
- data/lib/kward/prompts/commands.rb +2 -1
- data/lib/kward/prompts.rb +1 -1
- data/lib/kward/pty_output_sink.rb +47 -0
- data/lib/kward/rpc/auth_manager.rb +1 -1
- data/lib/kward/rpc/server.rb +2 -1
- data/lib/kward/rpc/transcript_normalizer.rb +7 -3
- data/lib/kward/scratchpad_languages.rb +74 -0
- data/lib/kward/scratchpad_runner.rb +155 -29
- data/lib/kward/shell_prompt.rb +52 -0
- data/lib/kward/shell_prompt_session.rb +58 -0
- data/lib/kward/terminal_keys.rb +13 -0
- data/lib/kward/terminal_text.rb +121 -0
- data/lib/kward/text_matcher.rb +18 -0
- data/lib/kward/tools/open_editor.rb +41 -0
- data/lib/kward/tools/prepare_shell_command.rb +28 -0
- data/lib/kward/tools/registry.rb +75 -8
- data/lib/kward/tools/replace_editor_buffer.rb +30 -0
- data/lib/kward/tools/run_shell_command.rb +24 -6
- data/lib/kward/tools/tool_call.rb +2 -1
- data/lib/kward/version.rb +1 -1
- data/templates/default/fulldoc/html/css/kward.css +0 -125
- data/templates/default/fulldoc/html/images/kward_workflow.svg +52 -0
- data/templates/default/fulldoc/html/setup.rb +1 -1
- 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 +6 -18
- data/templates/default/layout/html/setup.rb +41 -2
- metadata +44 -8
- data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
|
@@ -6,9 +6,70 @@ module Kward
|
|
|
6
6
|
module InteractiveTurn
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
def run_editor_prompt_turn(action, agent)
|
|
10
|
+
unless agent.respond_to?(:tool_registry) && agent.tool_registry.respond_to?(:for_editor_prompt)
|
|
11
|
+
runtime_output("Editor prompts are unavailable in this tab.")
|
|
12
|
+
return []
|
|
13
|
+
end
|
|
14
|
+
unless @prompt.respond_to?(:editor_prompt_context)
|
|
15
|
+
runtime_output("Editor prompts are unavailable in this prompt.")
|
|
16
|
+
return []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context = @prompt.editor_prompt_context
|
|
20
|
+
unless context
|
|
21
|
+
runtime_output("Open a Modern or Vibe editor buffer before prompting the agent.")
|
|
22
|
+
return []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
unless @prompt.suspend_editor_for_agent
|
|
26
|
+
runtime_output("The editor is busy.")
|
|
27
|
+
return []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
session = EditorPromptSession.new(context)
|
|
31
|
+
registry = agent.tool_registry.for_editor_prompt(session)
|
|
32
|
+
editor_agent = build_editor_prompt_agent(agent, context, registry)
|
|
33
|
+
instruction = action[:instruction] || action["instruction"]
|
|
34
|
+
run_interactive_turn(
|
|
35
|
+
editor_agent,
|
|
36
|
+
EditorPrompt.input(instruction, context),
|
|
37
|
+
tool_registry: registry,
|
|
38
|
+
editor_prompt_session: session,
|
|
39
|
+
suppress_transcript: true
|
|
40
|
+
)
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
@prompt.resume_editor_for_agent(status: "Agent request failed") if @prompt.respond_to?(:resume_editor_for_agent)
|
|
43
|
+
runtime_output("Error: #{e.message}")
|
|
44
|
+
[]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_editor_prompt_agent(active_agent, context, registry)
|
|
48
|
+
active_conversation = active_agent.conversation
|
|
49
|
+
config = safely_read_config.to_h
|
|
50
|
+
configured_provider = ConfigFiles.editor_agent_provider(config)
|
|
51
|
+
provider = configured_provider || active_conversation.provider || current_model_provider
|
|
52
|
+
model = ConfigFiles.editor_agent_model(config) || (configured_provider ? nil : active_conversation.model || current_model_id)
|
|
53
|
+
reasoning = ConfigFiles.editor_agent_reasoning_effort(config) || (configured_provider ? nil : active_conversation.reasoning_effort || current_reasoning_effort)
|
|
54
|
+
conversation = Conversation.new(
|
|
55
|
+
system_message: EditorPrompt.system_message,
|
|
56
|
+
workspace_root: context[:workspace_root] || active_conversation.workspace_root,
|
|
57
|
+
provider: provider,
|
|
58
|
+
model: model,
|
|
59
|
+
reasoning_effort: reasoning
|
|
60
|
+
)
|
|
61
|
+
Agent.new(
|
|
62
|
+
client: @client,
|
|
63
|
+
tool_registry: registry,
|
|
64
|
+
conversation: conversation,
|
|
65
|
+
strict_provider: !configured_provider.nil?,
|
|
66
|
+
warning_sink: ConfigFiles.warning_sink
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def run_interactive_turn(agent, input, display_input: nil, tool_registry: nil, editor_prompt_session: nil, suppress_transcript: false, busy_label: "You>", on_complete: nil)
|
|
71
|
+
prepare_memory_context(agent.conversation, input) if agent.respond_to?(:conversation) && !suppress_transcript
|
|
72
|
+
return run_blocking_interactive_turn(agent, input, display_input: display_input, tool_registry: tool_registry, suppress_transcript: suppress_transcript, on_complete: on_complete) unless prompt_interface?
|
|
12
73
|
|
|
13
74
|
queued_inputs = []
|
|
14
75
|
cancellation = Cancellation.new
|
|
@@ -17,19 +78,21 @@ module Kward
|
|
|
17
78
|
event_queue = Queue.new
|
|
18
79
|
stream_state = {
|
|
19
80
|
streamed: false,
|
|
81
|
+
assistant_arrived: false,
|
|
20
82
|
last_flush: monotonic_now,
|
|
21
83
|
stream_block_open: false,
|
|
22
84
|
markdown_streams: {},
|
|
85
|
+
tool_elapsed_ms: {},
|
|
23
86
|
defer_assistant_streaming: defer_assistant_streaming?(agent)
|
|
24
87
|
}
|
|
25
88
|
markdown_chunks = []
|
|
26
89
|
answer = nil
|
|
27
90
|
error = nil
|
|
28
|
-
@prompt.begin_busy_input(
|
|
29
|
-
print_user_transcript(input, display_input: display_input)
|
|
91
|
+
@prompt.begin_busy_input(busy_label) if @prompt.respond_to?(:begin_busy_input)
|
|
92
|
+
print_user_transcript(input, display_input: display_input) unless suppress_transcript
|
|
30
93
|
|
|
31
94
|
worker = Thread.new do
|
|
32
|
-
options = agent_display_options(display_input)
|
|
95
|
+
options = agent_display_options(display_input, tool_registry: tool_registry)
|
|
33
96
|
options[:cancellation] = cancellation
|
|
34
97
|
options[:steering] = steering if steering
|
|
35
98
|
answer = agent.ask(input, **options) do |event|
|
|
@@ -52,7 +115,7 @@ module Kward
|
|
|
52
115
|
cancellation.cancel!
|
|
53
116
|
worker.raise(Cancellation::CancelledError, "cancelled") if worker.alive?
|
|
54
117
|
end
|
|
55
|
-
if busy_replacement_agent?
|
|
118
|
+
if busy_replacement_agent? || suppress_transcript
|
|
56
119
|
discard_interactive_events(event_queue, markdown_chunks, stream_state)
|
|
57
120
|
else
|
|
58
121
|
drain_interactive_events(event_queue, markdown_chunks, stream_state, agent)
|
|
@@ -64,19 +127,49 @@ module Kward
|
|
|
64
127
|
error ||= e
|
|
65
128
|
end
|
|
66
129
|
drain_busy_input(queued_inputs, nil) unless cancelled
|
|
67
|
-
if busy_replacement_agent?
|
|
130
|
+
if busy_replacement_agent? || suppress_transcript
|
|
68
131
|
discard_interactive_events(event_queue, markdown_chunks, stream_state, force: true)
|
|
69
132
|
else
|
|
70
|
-
|
|
133
|
+
drain_remaining_interactive_events(event_queue, markdown_chunks, stream_state, agent)
|
|
71
134
|
end
|
|
135
|
+
completed = !cancelled && !busy_replacement_agent? && !error
|
|
136
|
+
on_complete&.call(successful: completed, cancelled: cancelled, error: error, answer: answer)
|
|
72
137
|
raise error if error && !error.is_a?(Cancellation::CancelledError) && !busy_replacement_agent?
|
|
73
138
|
|
|
74
|
-
@prompt.
|
|
75
|
-
|
|
76
|
-
|
|
139
|
+
@prompt.commit_editor_prompt(editor_prompt_session) if editor_prompt_session && completed
|
|
140
|
+
@prompt.say("\n#{colored(transcript_marker("Assistant"), :activity, :bold)} #{render_markdown_transcript(answer)}\n") unless suppress_transcript || cancelled || busy_replacement_agent? || stream_state[:streamed] || answer.to_s.empty?
|
|
141
|
+
persist_memory_state(agent.conversation) if agent.respond_to?(:conversation) && !suppress_transcript
|
|
142
|
+
auto_summarize_memory(agent.conversation) if agent.respond_to?(:conversation) && !suppress_transcript && queued_inputs.empty? && !cancelled
|
|
77
143
|
queued_inputs
|
|
78
144
|
ensure
|
|
79
145
|
@prompt.finish_busy_input if @prompt.respond_to?(:finish_busy_input)
|
|
146
|
+
if @prompt.respond_to?(:show_completion) && !busy_replacement_agent?
|
|
147
|
+
completion = if cancelled
|
|
148
|
+
:cancelled
|
|
149
|
+
elsif error
|
|
150
|
+
:failed
|
|
151
|
+
elsif completed
|
|
152
|
+
:success
|
|
153
|
+
end
|
|
154
|
+
@prompt.show_completion(completion) if completion
|
|
155
|
+
end
|
|
156
|
+
if editor_prompt_session && @prompt.respond_to?(:resume_editor_for_agent)
|
|
157
|
+
status = if cancelled
|
|
158
|
+
"Agent request cancelled"
|
|
159
|
+
elsif error
|
|
160
|
+
"Agent request failed"
|
|
161
|
+
elsif editor_prompt_session && !editor_prompt_session.changed? && !answer.to_s.strip.empty?
|
|
162
|
+
editor_prompt_answer_status(answer)
|
|
163
|
+
end
|
|
164
|
+
@prompt.resume_editor_for_agent(status: status)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def editor_prompt_answer_status(answer)
|
|
169
|
+
text = answer.to_s.gsub(/\s+/, " ").strip
|
|
170
|
+
text = text[0, 120].to_s
|
|
171
|
+
text += "…" if answer.to_s.gsub(/\s+/, " ").strip.length > text.length
|
|
172
|
+
"Agent: #{text}"
|
|
80
173
|
end
|
|
81
174
|
|
|
82
175
|
def drain_interactive_events(event_queue, markdown_chunks, stream_state, agent = nil, force: false)
|
|
@@ -95,6 +188,14 @@ module Kward
|
|
|
95
188
|
flush_interactive_markdown_deltas(markdown_chunks, stream_state, force: force)
|
|
96
189
|
end
|
|
97
190
|
|
|
191
|
+
def drain_remaining_interactive_events(event_queue, markdown_chunks, stream_state, agent)
|
|
192
|
+
until event_queue.empty?
|
|
193
|
+
drain_interactive_events(event_queue, markdown_chunks, stream_state, agent)
|
|
194
|
+
sleep STREAM_RENDER_INTERVAL unless event_queue.empty?
|
|
195
|
+
end
|
|
196
|
+
drain_interactive_events(event_queue, markdown_chunks, stream_state, agent, force: true)
|
|
197
|
+
end
|
|
198
|
+
|
|
98
199
|
def discard_interactive_events(event_queue, markdown_chunks, stream_state, force: false)
|
|
99
200
|
drained = 0
|
|
100
201
|
loop do
|
|
@@ -114,12 +215,19 @@ module Kward
|
|
|
114
215
|
case event
|
|
115
216
|
when Events::ReasoningDelta
|
|
116
217
|
stream_state[:streamed] = true
|
|
218
|
+
update_busy_activity("reasoning")
|
|
117
219
|
append_markdown_delta(markdown_chunks, "Reasoning", event.delta)
|
|
118
220
|
when Events::ReasoningBoundary
|
|
119
221
|
stream_state[:streamed] = true
|
|
120
222
|
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
223
|
+
update_busy_activity("thinking")
|
|
121
224
|
when Events::AssistantDelta
|
|
122
225
|
stream_state[:streamed] = true
|
|
226
|
+
unless stream_state[:assistant_arrived]
|
|
227
|
+
@prompt.show_response_arrival if @prompt.respond_to?(:show_response_arrival)
|
|
228
|
+
stream_state[:assistant_arrived] = true
|
|
229
|
+
end
|
|
230
|
+
update_busy_activity("responding")
|
|
123
231
|
append_markdown_delta(markdown_chunks, "Assistant", event.delta)
|
|
124
232
|
when Events::Steering
|
|
125
233
|
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
@@ -129,18 +237,30 @@ module Kward
|
|
|
129
237
|
when Events::Retry
|
|
130
238
|
stream_state[:streamed] = true
|
|
131
239
|
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
240
|
+
update_busy_activity("retrying")
|
|
132
241
|
print_retry(event)
|
|
133
242
|
when Events::ToolCall
|
|
134
243
|
stream_state[:streamed] = true
|
|
135
244
|
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
245
|
+
update_busy_activity(tool_activity(event.tool_call))
|
|
246
|
+
when Events::ToolUpdate
|
|
247
|
+
key = tool_call_id(event.tool_call) || event.tool_call.object_id
|
|
248
|
+
stream_state[:tool_elapsed_ms][key] = event.elapsed_ms
|
|
136
249
|
when Events::ToolResult
|
|
137
250
|
stream_state[:streamed] = true
|
|
138
251
|
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
139
252
|
update_session_diff(event.content, tool_call: event.tool_call)
|
|
140
|
-
|
|
253
|
+
key = tool_call_id(event.tool_call) || event.tool_call.object_id
|
|
254
|
+
elapsed_ms = stream_state[:tool_elapsed_ms].delete(key)
|
|
255
|
+
print_tool_result(event.tool_call, event.content, line_limit: INTERACTIVE_TOOL_OUTPUT_LINE_LIMIT, elapsed_ms: elapsed_ms)
|
|
256
|
+
update_busy_activity("thinking")
|
|
141
257
|
end
|
|
142
258
|
end
|
|
143
259
|
|
|
260
|
+
def update_busy_activity(activity)
|
|
261
|
+
@prompt.update_busy_activity(activity) if @prompt.respond_to?(:update_busy_activity)
|
|
262
|
+
end
|
|
263
|
+
|
|
144
264
|
def flush_interactive_markdown_deltas(markdown_chunks, stream_state, force: false)
|
|
145
265
|
if force
|
|
146
266
|
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
@@ -261,21 +381,24 @@ module Kward
|
|
|
261
381
|
ModelInfo.reasoning_supported?(current_model_provider, model)
|
|
262
382
|
end
|
|
263
383
|
|
|
264
|
-
def run_blocking_interactive_turn(agent, input, display_input: nil)
|
|
384
|
+
def run_blocking_interactive_turn(agent, input, display_input: nil, tool_registry: nil, suppress_transcript: false, on_complete: nil)
|
|
265
385
|
streamed = false
|
|
266
386
|
markdown_chunks = []
|
|
267
|
-
answer = agent.ask(input, **agent_display_options(display_input)) do |event|
|
|
268
|
-
streamed = true if render_blocking_turn_event(event, markdown_chunks, tool_line_limit: INTERACTIVE_TOOL_OUTPUT_LINE_LIMIT)
|
|
387
|
+
answer = agent.ask(input, **agent_display_options(display_input, tool_registry: tool_registry)) do |event|
|
|
388
|
+
streamed = true if !suppress_transcript && render_blocking_turn_event(event, markdown_chunks, tool_line_limit: INTERACTIVE_TOOL_OUTPUT_LINE_LIMIT)
|
|
269
389
|
end
|
|
270
390
|
flush_markdown_deltas(markdown_chunks) if streamed
|
|
271
|
-
@prompt.say("\n#{colored(
|
|
272
|
-
persist_memory_state(agent.conversation) if agent.respond_to?(:conversation)
|
|
273
|
-
auto_summarize_memory(agent.conversation) if agent.respond_to?(:conversation)
|
|
391
|
+
@prompt.say("\n#{colored(transcript_marker("Assistant"), :activity, :bold)} #{render_markdown_transcript(answer)}\n") unless suppress_transcript || streamed || answer.to_s.empty?
|
|
392
|
+
persist_memory_state(agent.conversation) if agent.respond_to?(:conversation) && !suppress_transcript
|
|
393
|
+
auto_summarize_memory(agent.conversation) if agent.respond_to?(:conversation) && !suppress_transcript
|
|
394
|
+
on_complete&.call(successful: true, cancelled: false, error: nil, answer: answer)
|
|
274
395
|
[]
|
|
275
396
|
end
|
|
276
397
|
|
|
277
|
-
def agent_display_options(display_input)
|
|
278
|
-
display_input.nil? ? {} : { display_input: display_input }
|
|
398
|
+
def agent_display_options(display_input, tool_registry: nil)
|
|
399
|
+
options = display_input.nil? ? {} : { display_input: display_input }
|
|
400
|
+
options[:tool_registry] = tool_registry if tool_registry
|
|
401
|
+
options
|
|
279
402
|
end
|
|
280
403
|
|
|
281
404
|
end
|
|
@@ -7,20 +7,20 @@ module Kward
|
|
|
7
7
|
argument = Array(arguments).join(" ").strip.downcase
|
|
8
8
|
_workspace_root, candidates, coordinator = project_skill_trust_coordinator
|
|
9
9
|
if candidates.empty? && argument != "untrust"
|
|
10
|
-
|
|
10
|
+
project_skills_cli_output("No project skills found in the current workspace.")
|
|
11
11
|
return
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
case argument
|
|
15
15
|
when "", "status"
|
|
16
16
|
lines = candidates.empty? ? ["No project skills found in the current workspace."] : candidates.map { |candidate| "#{relative_workspace_path(candidate.path)}: #{coordinator.decision(candidate) || "needs review"}" }
|
|
17
|
-
|
|
17
|
+
project_skills_cli_output(lines.join("\n"))
|
|
18
18
|
when "trust"
|
|
19
19
|
coordinator.record!(candidates, "allow")
|
|
20
|
-
|
|
20
|
+
project_skills_cli_output("Project skills trusted for the current skill snapshots.")
|
|
21
21
|
when "untrust"
|
|
22
22
|
coordinator.remove_workspace!
|
|
23
|
-
|
|
23
|
+
project_skills_cli_output("Project skill trust removed for this workspace.")
|
|
24
24
|
when "review"
|
|
25
25
|
review_project_skills(candidates)
|
|
26
26
|
else
|
|
@@ -43,6 +43,10 @@ module Kward
|
|
|
43
43
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
|
+
def project_skills_cli_output(message)
|
|
47
|
+
@prompt.say("#{colored("Project skills", :green, :bold)}\n\n#{message}")
|
|
48
|
+
end
|
|
49
|
+
|
|
46
50
|
def project_skill_trust_coordinator
|
|
47
51
|
workspace_root = current_workspace_root
|
|
48
52
|
candidates = ConfigFiles.project_skill_candidates(workspace_root: workspace_root)
|
|
@@ -45,6 +45,7 @@ module Kward
|
|
|
45
45
|
editor_line_numbers_source: -> { ConfigFiles.editor_line_numbers },
|
|
46
46
|
diff_view: ConfigFiles.diff_view,
|
|
47
47
|
diff_view_source: -> { ConfigFiles.diff_view },
|
|
48
|
+
editor_runners_source: -> { ConfigFiles.editor_runners },
|
|
48
49
|
redraw_handler: method(:redraw_interactive_prompt)
|
|
49
50
|
)
|
|
50
51
|
if @prompt.method(:start).parameters.any? { |kind, name| [:key, :keyreq].include?(kind) && name == :render }
|
|
@@ -99,6 +100,13 @@ module Kward
|
|
|
99
100
|
@prompt.respond_to?(:start_stream_block) && @prompt.respond_to?(:write_delta)
|
|
100
101
|
end
|
|
101
102
|
|
|
103
|
+
def open_editor_for_agent(path, base_dir:, allow_new:)
|
|
104
|
+
return false unless prompt_interface?
|
|
105
|
+
return false unless @prompt.respond_to?(:edit_file)
|
|
106
|
+
|
|
107
|
+
@prompt.edit_file(path, base_dir: base_dir, allow_new: allow_new)
|
|
108
|
+
end
|
|
109
|
+
|
|
102
110
|
def emit_warning(message)
|
|
103
111
|
sink = ConfigFiles.warning_sink
|
|
104
112
|
sink ? sink.call(message) : warn(message)
|
|
@@ -129,6 +137,7 @@ module Kward
|
|
|
129
137
|
[
|
|
130
138
|
startup_status_line(refresh_update_check: refresh_update_check),
|
|
131
139
|
*startup_update_notice_lines,
|
|
140
|
+
*startup_authentication_notice_lines,
|
|
132
141
|
"",
|
|
133
142
|
startup_info_line("Workspace", startup_workspace_label),
|
|
134
143
|
startup_info_line("Branch", startup_branch_value),
|
|
@@ -198,6 +207,24 @@ module Kward
|
|
|
198
207
|
@startup_update_notice = @startup_update_check.notice(refresh: refresh)
|
|
199
208
|
end
|
|
200
209
|
|
|
210
|
+
def startup_authentication_notice_lines
|
|
211
|
+
return [] unless startup_authentication_required?
|
|
212
|
+
|
|
213
|
+
[
|
|
214
|
+
ANSI.colorize(" No model provider is connected.", :yellow, enabled: @color_enabled),
|
|
215
|
+
" Run /login to sign in, or /model to configure a local server."
|
|
216
|
+
]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def startup_authentication_required?
|
|
220
|
+
return false unless @client.is_a?(Client)
|
|
221
|
+
return false if @client.current_provider == "Local"
|
|
222
|
+
|
|
223
|
+
auth_credentials.none? { |credential| credential.fetch(:configured) }
|
|
224
|
+
rescue StandardError
|
|
225
|
+
false
|
|
226
|
+
end
|
|
227
|
+
|
|
201
228
|
def startup_info_line(label, value)
|
|
202
229
|
"#{ANSI.colorize(label.ljust(12), :gray, enabled: @color_enabled)}#{ANSI.colorize(value, :cyan, enabled: @color_enabled)}"
|
|
203
230
|
end
|
data/lib/kward/cli/rendering.rb
CHANGED
|
@@ -223,7 +223,7 @@ 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
227
|
print_attachment_badges(input, references: attachment_references)
|
|
228
228
|
print_pasted_images(input, image_parts: image_parts)
|
|
229
229
|
end
|
|
@@ -347,8 +347,9 @@ module Kward
|
|
|
347
347
|
end
|
|
348
348
|
|
|
349
349
|
# Writes the tool result output for the terminal CLI flow.
|
|
350
|
-
def print_tool_result(tool_call, content, line_limit: nil)
|
|
350
|
+
def print_tool_result(tool_call, content, line_limit: nil, elapsed_ms: nil)
|
|
351
351
|
summary = tool_result_summary(tool_call, content)
|
|
352
|
+
summary = tool_summary_with_duration(summary, elapsed_ms)
|
|
352
353
|
summary = limit_tool_output_lines(summary, line_limit) if line_limit
|
|
353
354
|
display_summary = tool_summary_display_text(summary)
|
|
354
355
|
if prompt_interface?
|
|
@@ -380,7 +381,7 @@ module Kward
|
|
|
380
381
|
return if @stream_block == label
|
|
381
382
|
|
|
382
383
|
puts if @stream_block
|
|
383
|
-
print "\n#{colored(
|
|
384
|
+
print "\n#{colored(transcript_marker(label), *label_styles(label))} "
|
|
384
385
|
@stream_block = label
|
|
385
386
|
end
|
|
386
387
|
|
|
@@ -408,20 +409,25 @@ module Kward
|
|
|
408
409
|
end
|
|
409
410
|
end
|
|
410
411
|
|
|
412
|
+
def transcript_marker(label)
|
|
413
|
+
marker = "#{transcript_label(label)}>"
|
|
414
|
+
label == "Assistant" ? "✦ #{marker}" : marker
|
|
415
|
+
end
|
|
416
|
+
|
|
411
417
|
def label_styles(label)
|
|
412
418
|
case label
|
|
413
419
|
when "Reasoning", "Compaction summary"
|
|
414
|
-
[:
|
|
420
|
+
[:metadata, :bold]
|
|
415
421
|
when "Assistant", "Kward"
|
|
416
|
-
[:
|
|
422
|
+
[:activity, :bold]
|
|
417
423
|
when "Tool", "Tool output"
|
|
418
|
-
[:
|
|
424
|
+
[:tool, :bold]
|
|
419
425
|
when "Tool failed"
|
|
420
|
-
[:
|
|
426
|
+
[:failure, :bold]
|
|
421
427
|
when "Retry"
|
|
422
|
-
[:
|
|
428
|
+
[:caution, :bold]
|
|
423
429
|
else
|
|
424
|
-
[:
|
|
430
|
+
[:metadata, :bold]
|
|
425
431
|
end
|
|
426
432
|
end
|
|
427
433
|
|