kward 0.76.0 → 0.77.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/ci.yml +8 -1
- data/CHANGELOG.md +36 -0
- data/Gemfile.lock +2 -2
- data/README.md +12 -4
- data/doc/agent-tools.md +0 -3
- data/doc/api.md +4 -0
- data/doc/composer.md +219 -0
- data/doc/configuration.md +75 -27
- data/doc/editor.md +18 -2
- data/doc/files.md +1 -1
- data/doc/getting-started.md +4 -0
- data/doc/git.md +2 -2
- data/doc/lifecycle-hooks.md +2 -14
- data/doc/pan.md +167 -0
- data/doc/rpc.md +0 -22
- data/doc/security.md +180 -0
- data/doc/usage.md +15 -32
- data/lib/kward/ansi.rb +2 -2
- data/lib/kward/cancellation.rb +2 -2
- data/lib/kward/cli/commands.rb +1 -3
- data/lib/kward/cli/interactive_turn.rb +0 -17
- data/lib/kward/cli/plugins.rb +2 -7
- data/lib/kward/cli/prompt_interface.rb +1 -0
- data/lib/kward/cli/runtime_helpers.rb +0 -38
- data/lib/kward/cli/sessions.rb +14 -5
- data/lib/kward/cli/settings.rb +22 -0
- data/lib/kward/cli/slash_commands.rb +1 -561
- data/lib/kward/cli/tabs.rb +14 -2
- data/lib/kward/cli.rb +1 -17
- data/lib/kward/compactor.rb +3 -3
- data/lib/kward/config_files.rb +18 -0
- data/lib/kward/ekwsh.rb +0 -4
- data/lib/kward/hooks/catalog.rb +0 -5
- data/lib/kward/image_attachments.rb +1 -1
- data/lib/kward/pan/index.html.erb +459 -64
- data/lib/kward/pan/kward_logo.png +0 -0
- data/lib/kward/pan/server.rb +248 -23
- data/lib/kward/plugin_registry.rb +18 -0
- data/lib/kward/prompt_interface/editor/auto_indent.rb +31 -23
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +72 -3
- data/lib/kward/prompt_interface/layout.rb +0 -4
- data/lib/kward/prompt_interface/project_browser.rb +40 -1
- data/lib/kward/prompt_interface/runtime_state.rb +4 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +0 -1
- data/lib/kward/prompt_interface/transcript_renderer.rb +0 -12
- data/lib/kward/prompt_interface.rb +18 -1
- data/lib/kward/prompts/commands.rb +1 -3
- data/lib/kward/prompts.rb +20 -0
- data/lib/kward/rpc/server.rb +13 -41
- data/lib/kward/rpc/session_manager.rb +75 -25
- data/lib/kward/rpc/tool_event_normalizer.rb +2 -0
- data/lib/kward/rpc/tool_metadata.rb +23 -0
- data/lib/kward/rpc/transcript_normalizer.rb +4 -0
- data/lib/kward/session_store.rb +55 -16
- data/lib/kward/skills/registry.rb +21 -1
- data/lib/kward/tools/base.rb +14 -0
- data/lib/kward/tools/registry.rb +11 -16
- data/lib/kward/tools/search/code.rb +1 -1
- data/lib/kward/tools/search/web.rb +1 -1
- data/lib/kward/tools/tool_call.rb +0 -4
- data/lib/kward/transcript_export.rb +1 -1
- data/lib/kward/version.rb +1 -1
- data/templates/default/kward_navigation.rb +5 -1
- data/templates/default/layout/html/layout.erb +0 -2
- metadata +5 -12
- data/lib/kward/workers/git_guard.rb +0 -93
- data/lib/kward/workers/job.rb +0 -99
- data/lib/kward/workers/live_view.rb +0 -49
- data/lib/kward/workers/manager.rb +0 -328
- data/lib/kward/workers/queue_runner.rb +0 -166
- data/lib/kward/workers/queue_store.rb +0 -112
- data/lib/kward/workers/store.rb +0 -72
- data/lib/kward/workers/tool_policy.rb +0 -23
- data/lib/kward/workers/worker.rb +0 -82
- data/lib/kward/workers/write_lock.rb +0 -38
- data/lib/kward/workers.rb +0 -10
data/lib/kward/cli/plugins.rb
CHANGED
|
@@ -32,6 +32,7 @@ module Kward
|
|
|
32
32
|
|
|
33
33
|
def reload_plugins(conversation)
|
|
34
34
|
@plugin_registry = PluginRegistry.load(reserved_commands: reserved_slash_command_names)
|
|
35
|
+
@prompt.update_slash_commands(slash_command_entries) if @prompt.respond_to?(:update_slash_commands)
|
|
35
36
|
conversation.plugin_registry = @plugin_registry if conversation.respond_to?(:plugin_registry=)
|
|
36
37
|
conversation.refresh_system_message! if conversation.respond_to?(:refresh_system_message!)
|
|
37
38
|
runtime_output("Plugins reloaded.")
|
|
@@ -126,19 +127,13 @@ module Kward
|
|
|
126
127
|
end
|
|
127
128
|
|
|
128
129
|
def builtin_slash_commands
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
BUILTIN_SLASH_COMMANDS.reject { |command| %w[workers queue].include?(command[:name]) }
|
|
130
|
+
BUILTIN_SLASH_COMMANDS
|
|
132
131
|
end
|
|
133
132
|
|
|
134
133
|
def builtin_slash_command_names
|
|
135
134
|
builtin_slash_commands.map { |command| command[:name] }
|
|
136
135
|
end
|
|
137
136
|
|
|
138
|
-
def experimental_workers_enabled?
|
|
139
|
-
@experimental_workers == true
|
|
140
|
-
end
|
|
141
|
-
|
|
142
137
|
def expand_prompt_template(input)
|
|
143
138
|
PromptCommands.expand(input, templates: prompt_templates, reserved_commands: builtin_slash_command_names)
|
|
144
139
|
end
|
|
@@ -18,6 +18,7 @@ module Kward
|
|
|
18
18
|
@prompt = prompt_interface.new(
|
|
19
19
|
slash_commands: slash_command_entries,
|
|
20
20
|
overlay_settings: ConfigFiles.overlay_settings,
|
|
21
|
+
project_browser_icon_theme: ConfigFiles.project_browser_icon_theme,
|
|
21
22
|
footer: prompt_footer_renderer,
|
|
22
23
|
composer_status: method(:composer_status_text),
|
|
23
24
|
busy_help: ConfigFiles.composer_busy_help?,
|
|
@@ -43,23 +43,13 @@ module Kward
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def build_interactive_agent(conversation)
|
|
46
|
-
@active_worker_role = "implementation"
|
|
47
|
-
set_visible_worker("implementation", status: "active")
|
|
48
|
-
build_worker_agent(conversation, role: "implementation")
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def build_worker_agent(conversation, role: "implementation")
|
|
52
46
|
conversation.plugin_registry ||= plugin_registry if conversation.respond_to?(:plugin_registry)
|
|
53
47
|
workspace = configured_workspace(root: conversation.workspace_root)
|
|
54
|
-
writer_id = worker_writer_id(role)
|
|
55
48
|
hook_manager = lifecycle_hook_manager(conversation)
|
|
56
49
|
hook_context = lifecycle_hook_context(conversation)
|
|
57
50
|
tool_registry = ToolRegistry.new(
|
|
58
51
|
workspace: workspace,
|
|
59
52
|
prompt: @prompt,
|
|
60
|
-
allowed_tool_names: Workers::ToolPolicy.allowed_tool_names(role),
|
|
61
|
-
write_lock: @worker_write_lock,
|
|
62
|
-
writer_id: writer_id,
|
|
63
53
|
hook_manager: hook_manager,
|
|
64
54
|
hook_context: hook_context
|
|
65
55
|
)
|
|
@@ -74,34 +64,6 @@ module Kward
|
|
|
74
64
|
)
|
|
75
65
|
end
|
|
76
66
|
|
|
77
|
-
def set_visible_worker(id, status: nil, worker: nil)
|
|
78
|
-
@visible_worker_id = id.to_s
|
|
79
|
-
@visible_worker_status = status
|
|
80
|
-
@visible_worker = worker
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def worker_writer_id(role)
|
|
84
|
-
return nil unless Workers::ToolPolicy.write_capable?(role)
|
|
85
|
-
|
|
86
|
-
@worker_write_lock ||= Workers::WriteLock.new
|
|
87
|
-
owner_id = role.to_s.empty? ? "implementation" : role.to_s
|
|
88
|
-
return owner_id if @worker_write_lock.acquire(owner_id)
|
|
89
|
-
|
|
90
|
-
nil
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def refresh_implementation_writer(agent)
|
|
94
|
-
return agent unless @active_worker_role == "implementation"
|
|
95
|
-
return agent unless agent&.respond_to?(:tool_registry)
|
|
96
|
-
return agent if agent.tool_registry.writer_id && @worker_write_lock&.owned_by?(agent.tool_registry.writer_id)
|
|
97
|
-
|
|
98
|
-
build_interactive_agent(agent.conversation)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def release_implementation_writer
|
|
102
|
-
@worker_write_lock&.release("implementation")
|
|
103
|
-
end
|
|
104
|
-
|
|
105
67
|
def handle_interactive_shell_command(input, agent)
|
|
106
68
|
command = input.to_s.sub(/\A!\s*/, "")
|
|
107
69
|
if command.strip.empty?
|
data/lib/kward/cli/sessions.rb
CHANGED
|
@@ -80,6 +80,15 @@ module Kward
|
|
|
80
80
|
@cleanup_sessions.clear
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
def live_session_paths
|
|
84
|
+
(Array(@tabs).filter_map { |tab| tab.session&.path } + [@active_session&.path]).compact.uniq
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def recent_sessions(session_store, tree: false, limit: nil)
|
|
88
|
+
method = tree ? :recent_tree : :recent
|
|
89
|
+
session_store.public_send(method, limit: limit, keep_empty_path: live_session_paths)
|
|
90
|
+
end
|
|
91
|
+
|
|
83
92
|
def cleanup_replaced_session(previous_session)
|
|
84
93
|
return unless previous_session
|
|
85
94
|
return if @active_session && File.expand_path(previous_session.path) == File.expand_path(@active_session.path)
|
|
@@ -590,8 +599,8 @@ module Kward
|
|
|
590
599
|
end
|
|
591
600
|
|
|
592
601
|
def insert_session_copy(session_store, sessions, labels, source, copy_path)
|
|
593
|
-
copy_info = session_store
|
|
594
|
-
copy_info ||= session_store
|
|
602
|
+
copy_info = recent_sessions(session_store, tree: true).find { |session| File.expand_path(session.path) == File.expand_path(copy_path) }
|
|
603
|
+
copy_info ||= recent_sessions(session_store).find { |session| File.expand_path(session.path) == File.expand_path(copy_path) }
|
|
595
604
|
return nil unless copy_info
|
|
596
605
|
|
|
597
606
|
source_index = sessions.index(source) || 0
|
|
@@ -618,7 +627,7 @@ module Kward
|
|
|
618
627
|
return nil unless source
|
|
619
628
|
|
|
620
629
|
session_store.load(source.path).first.rename(name)
|
|
621
|
-
updated = session_store
|
|
630
|
+
updated = recent_sessions(session_store, tree: true)
|
|
622
631
|
sessions.replace(updated)
|
|
623
632
|
labels.replace(session_picker_labels(sessions))
|
|
624
633
|
index = sessions.index { |session| File.expand_path(session.path) == File.expand_path(source.path) } || 0
|
|
@@ -716,7 +725,7 @@ module Kward
|
|
|
716
725
|
end
|
|
717
726
|
|
|
718
727
|
def select_session_path(session_store)
|
|
719
|
-
select_session_path_from_sessions(session_store
|
|
728
|
+
select_session_path_from_sessions(recent_sessions(session_store, tree: true), session_store: session_store)
|
|
720
729
|
end
|
|
721
730
|
|
|
722
731
|
def reopen_sessions_after_fork(session_store, source_path, source_label)
|
|
@@ -724,7 +733,7 @@ module Kward
|
|
|
724
733
|
fork_session_from_picker(session_store, source_path)
|
|
725
734
|
end
|
|
726
735
|
|
|
727
|
-
sessions = session_store
|
|
736
|
+
sessions = recent_sessions(session_store, tree: true)
|
|
728
737
|
labels = session_picker_labels(sessions)
|
|
729
738
|
initial_index = if fork_path
|
|
730
739
|
sessions.index { |session| File.expand_path(session.path) == File.expand_path(fork_path) }
|
data/lib/kward/cli/settings.rb
CHANGED
|
@@ -243,6 +243,8 @@ module Kward
|
|
|
243
243
|
next unless width
|
|
244
244
|
|
|
245
245
|
update_overlay_settings("width" => width)
|
|
246
|
+
when /\Afile icons/
|
|
247
|
+
configure_project_browser_icon_theme
|
|
246
248
|
when /\Ashow busy help/, /\Ahide busy help/
|
|
247
249
|
set_composer_busy_help(!composer_busy_help?)
|
|
248
250
|
runtime_output("Busy help #{composer_busy_help? ? "enabled" : "disabled"}. Restart the TUI to apply this setting.")
|
|
@@ -277,6 +279,7 @@ module Kward
|
|
|
277
279
|
[
|
|
278
280
|
"Overlay alignment (#{settings["alignment"]})",
|
|
279
281
|
"Overlay width (#{settings["width"]})",
|
|
282
|
+
"File icons (#{project_browser_icon_theme})",
|
|
280
283
|
"#{composer_busy_help? ? "Hide" : "Show"} busy help (currently #{on_off(composer_busy_help?)})",
|
|
281
284
|
"Tab keybindings (#{composer_tab_keybindings})",
|
|
282
285
|
"Editor mode (#{editor_mode})",
|
|
@@ -290,6 +293,25 @@ module Kward
|
|
|
290
293
|
]
|
|
291
294
|
end
|
|
292
295
|
|
|
296
|
+
def project_browser_icon_theme
|
|
297
|
+
ConfigFiles.project_browser_icon_theme(safely_read_config.to_h)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def configure_project_browser_icon_theme
|
|
301
|
+
selected = @prompt.select("File icons", project_browser_icon_theme_choices, title: "Settings")
|
|
302
|
+
theme = selected.to_s.split.first.to_s.downcase
|
|
303
|
+
return unless ConfigFiles::PROJECT_BROWSER_ICON_THEMES.include?(theme)
|
|
304
|
+
|
|
305
|
+
update_nested_config("project_browser", "icons" => theme)
|
|
306
|
+
@prompt.update_project_browser_icon_theme(theme) if @prompt.respond_to?(:update_project_browser_icon_theme)
|
|
307
|
+
runtime_output("File icons set to #{theme}.")
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def project_browser_icon_theme_choices
|
|
311
|
+
current = project_browser_icon_theme
|
|
312
|
+
ConfigFiles::PROJECT_BROWSER_ICON_THEMES.map { |theme| theme == current ? "#{theme} (current)" : theme }
|
|
313
|
+
end
|
|
314
|
+
|
|
293
315
|
def composer_busy_help?
|
|
294
316
|
ConfigFiles.composer_busy_help?(safely_read_config.to_h)
|
|
295
317
|
end
|