kward 0.78.0 → 0.80.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 +13 -2
- data/.github/workflows/pages.yml +1 -1
- data/CHANGELOG.md +82 -1
- data/Gemfile.lock +2 -2
- data/README.md +33 -38
- data/doc/agent-tools.md +10 -11
- data/doc/api.md +3 -3
- data/doc/authentication.md +79 -110
- data/doc/code-search.md +1 -1
- data/doc/composer.md +1 -1
- data/doc/configuration.md +136 -27
- data/doc/context-budgeting.md +6 -6
- data/doc/context-tools.md +2 -2
- data/doc/editor.md +7 -7
- data/doc/extensibility.md +9 -29
- data/doc/files.md +6 -6
- data/doc/getting-started.md +3 -3
- data/doc/git.md +8 -2
- data/doc/lifecycle-hooks.md +5 -4
- data/doc/local-models.md +130 -0
- data/doc/mcp.md +5 -5
- data/doc/permissions.md +15 -5
- data/doc/plugins.md +39 -4
- data/doc/prompt-templates.md +105 -0
- data/doc/providers.md +140 -0
- data/doc/releasing.md +11 -3
- data/doc/rpc.md +65 -31
- data/doc/sandboxing.md +142 -0
- data/doc/security.md +31 -10
- data/doc/session-management.md +2 -4
- data/doc/skills.md +10 -0
- data/doc/tabs.md +37 -0
- data/doc/telegram.md +138 -0
- data/doc/transports.md +209 -0
- data/doc/usage.md +11 -3
- data/doc/web-search.md +3 -3
- data/doc/workspace-tools.md +4 -4
- data/examples/plugins/stardate_footer.rb +10 -0
- data/examples/plugins/telegram/plugin.rb +39 -0
- data/examples/plugins/telegram/telegram_api.rb +136 -0
- data/examples/plugins/telegram/telegram_transport.rb +304 -0
- data/kward.gemspec +1 -1
- data/lib/kward/ansi.rb +1 -0
- data/lib/kward/auth/api_key_store.rb +99 -0
- data/lib/kward/cli/auth_commands.rb +72 -13
- data/lib/kward/cli/commands.rb +7 -0
- data/lib/kward/cli/doctor.rb +21 -0
- data/lib/kward/cli/git.rb +31 -8
- data/lib/kward/cli/plugins.rb +1 -0
- data/lib/kward/cli/prompt_interface.rb +7 -0
- data/lib/kward/cli/rendering.rb +6 -2
- data/lib/kward/cli/runtime_helpers.rb +4 -2
- data/lib/kward/cli/sessions.rb +1 -1
- data/lib/kward/cli/settings.rb +162 -36
- data/lib/kward/cli/slash_commands.rb +109 -1
- data/lib/kward/cli/tabs.rb +102 -37
- data/lib/kward/cli/transports.rb +67 -0
- data/lib/kward/cli/worktrees.rb +360 -0
- data/lib/kward/cli.rb +22 -1
- data/lib/kward/config_files.rb +106 -4
- data/lib/kward/conversation.rb +27 -8
- data/lib/kward/git_worktree_manager.rb +291 -0
- data/lib/kward/local_command_runner.rb +2 -2
- data/lib/kward/model/azure_openai_config.rb +54 -0
- data/lib/kward/model/catalog.rb +113 -0
- data/lib/kward/model/client.rb +364 -23
- data/lib/kward/model/model_info.rb +60 -1
- data/lib/kward/model/payloads.rb +64 -4
- data/lib/kward/model/provider_catalog.rb +114 -0
- data/lib/kward/model/sources.rb +70 -0
- data/lib/kward/model/stream_parser.rb +141 -39
- data/lib/kward/pan/index.html.erb +50 -0
- data/lib/kward/pan/server.rb +49 -2
- data/lib/kward/permissions/policy.rb +18 -4
- data/lib/kward/plugin_chat_runtime.rb +374 -0
- data/lib/kward/plugin_registry.rb +81 -8
- data/lib/kward/private_file.rb +9 -3
- data/lib/kward/prompt_interface/approval_prompt.rb +2 -0
- data/lib/kward/prompt_interface/composer_renderer.rb +1 -5
- data/lib/kward/prompt_interface/editor/auto_indent.rb +46 -0
- data/lib/kward/prompt_interface/editor/buffer.rb +18 -4
- data/lib/kward/prompt_interface/editor/controller.rb +43 -10
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +2 -2
- data/lib/kward/prompt_interface/editor/renderer.rb +8 -8
- data/lib/kward/prompt_interface/editor/state.rb +3 -9
- data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +184 -9
- data/lib/kward/prompt_interface/file_overlay.rb +2 -2
- data/lib/kward/prompt_interface/git_prompt.rb +7 -3
- data/lib/kward/prompt_interface/interactive/controller.rb +4 -4
- data/lib/kward/prompt_interface/interactive/renderer.rb +4 -1
- data/lib/kward/prompt_interface/interactive/state.rb +2 -2
- data/lib/kward/prompt_interface/project_browser.rb +25 -6
- data/lib/kward/prompt_interface/runtime_state.rb +22 -6
- data/lib/kward/prompt_interface/screen.rb +3 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +7 -1
- data/lib/kward/prompt_interface/transcript_buffer.rb +24 -2
- data/lib/kward/prompt_interface.rb +71 -12
- data/lib/kward/prompts/commands.rb +3 -1
- data/lib/kward/prompts.rb +22 -10
- data/lib/kward/rpc/auth_manager.rb +112 -152
- data/lib/kward/rpc/config_manager.rb +46 -6
- data/lib/kward/rpc/plugin_chat_manager.rb +56 -194
- data/lib/kward/rpc/prompt_bridge.rb +8 -4
- data/lib/kward/rpc/redactor.rb +1 -1
- data/lib/kward/rpc/server.rb +102 -14
- data/lib/kward/rpc/session_manager.rb +151 -38
- data/lib/kward/sandbox/capabilities.rb +39 -0
- data/lib/kward/sandbox/command_runner.rb +28 -0
- data/lib/kward/sandbox/environment.rb +24 -0
- data/lib/kward/sandbox/linux_bubblewrap_runner.rb +71 -0
- data/lib/kward/sandbox/macos_seatbelt_runner.rb +96 -0
- data/lib/kward/sandbox/passthrough_runner.rb +13 -0
- data/lib/kward/sandbox/policy.rb +74 -0
- data/lib/kward/sandbox/runner_factory.rb +55 -0
- data/lib/kward/sandbox/unavailable_runner.rb +21 -0
- data/lib/kward/sandbox.rb +9 -0
- data/lib/kward/session_store.rb +110 -24
- data/lib/kward/skills/capture.rb +144 -0
- data/lib/kward/tab_driver.rb +6 -3
- data/lib/kward/tools/git_commit.rb +39 -0
- data/lib/kward/tools/registry.rb +26 -6
- data/lib/kward/tools/tool_call.rb +1 -0
- data/lib/kward/transport/gateway.rb +253 -0
- data/lib/kward/transport/host.rb +343 -0
- data/lib/kward/transport/manager.rb +179 -0
- data/lib/kward/transport/plugin_chat_gateway.rb +186 -0
- data/lib/kward/transport/runtime.rb +41 -0
- data/lib/kward/transport/store.rb +101 -0
- data/lib/kward/transport.rb +204 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workspace.rb +18 -3
- data/lib/kward/workspace_factory.rb +34 -0
- data/templates/default/fulldoc/html/js/kward.js +3 -0
- data/templates/default/kward_navigation.rb +8 -1
- data/templates/default/layout/html/setup.rb +5 -0
- metadata +41 -2
|
@@ -40,14 +40,30 @@ module Kward
|
|
|
40
40
|
|
|
41
41
|
def tick_footer_locked
|
|
42
42
|
return false unless @footer && @started && @asking
|
|
43
|
+
return false unless footer_refresh_due?
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
previous_text = @cached_footer_text
|
|
46
|
+
refresh_footer_text_locked
|
|
47
|
+
previous_text != @cached_footer_text
|
|
48
|
+
end
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
def cached_footer_text
|
|
51
|
+
return "" unless @footer
|
|
52
|
+
|
|
53
|
+
refresh_footer_text_locked if footer_refresh_due?
|
|
54
|
+
@cached_footer_text.to_s
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def footer_refresh_due?
|
|
58
|
+
@last_footer_refresh.nil? || monotonic_now - @last_footer_refresh >= FOOTER_REFRESH_INTERVAL
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def refresh_footer_text_locked
|
|
62
|
+
@cached_footer_text = @footer.call.to_s.gsub(/\s+/, " ").strip
|
|
63
|
+
rescue StandardError
|
|
64
|
+
@cached_footer_text = ""
|
|
65
|
+
ensure
|
|
66
|
+
@last_footer_refresh = monotonic_now
|
|
51
67
|
end
|
|
52
68
|
|
|
53
69
|
def cached_composer_status_text
|
|
@@ -114,6 +114,9 @@ module Kward
|
|
|
114
114
|
@reserved_rows = 0
|
|
115
115
|
@last_width = current_width
|
|
116
116
|
@last_height = current_height
|
|
117
|
+
if interactive_active_locked?
|
|
118
|
+
@interactive_state[:controller].resize(width: interactive_canvas_width(current_width))
|
|
119
|
+
end
|
|
117
120
|
redraw_screen_locked(width: current_width, height: current_height)
|
|
118
121
|
true
|
|
119
122
|
end
|
|
@@ -626,7 +626,13 @@ module Kward
|
|
|
626
626
|
|
|
627
627
|
def selection_matches
|
|
628
628
|
choices = @select_state ? @select_state[:choices] : []
|
|
629
|
-
filter =
|
|
629
|
+
filter = if select_input_active?
|
|
630
|
+
""
|
|
631
|
+
elsif select_search_active? || !composer_input.strip.empty?
|
|
632
|
+
composer_input.downcase.strip
|
|
633
|
+
else
|
|
634
|
+
""
|
|
635
|
+
end
|
|
630
636
|
matches = filter.empty? ? choices : choices.select { |choice| choice.downcase.include?(filter) }
|
|
631
637
|
clamp_selection_index(matches.length)
|
|
632
638
|
matches
|
|
@@ -4,10 +4,14 @@ module Kward
|
|
|
4
4
|
class PromptInterface
|
|
5
5
|
# Bounded in-memory transcript buffer used by the prompt interface.
|
|
6
6
|
class TranscriptBuffer
|
|
7
|
+
TRIM_RATIO = 0.9
|
|
8
|
+
TRIM_BOUNDARY_SEARCH = 1_024
|
|
9
|
+
|
|
7
10
|
attr_reader :text
|
|
8
11
|
|
|
9
12
|
def initialize(limit:)
|
|
10
|
-
@limit = limit
|
|
13
|
+
@limit = [limit.to_i, 1].max
|
|
14
|
+
@trim_target = [(@limit * TRIM_RATIO).floor, 1].max
|
|
11
15
|
@text = +""
|
|
12
16
|
@display_rows_cache_width = nil
|
|
13
17
|
@display_rows_cache = nil
|
|
@@ -42,7 +46,7 @@ module Kward
|
|
|
42
46
|
|
|
43
47
|
def append(text)
|
|
44
48
|
@text << ANSI.sanitize_transcript(text)
|
|
45
|
-
|
|
49
|
+
trim_to_limit if @text.length > @limit
|
|
46
50
|
invalidate_display_rows_cache
|
|
47
51
|
@text
|
|
48
52
|
end
|
|
@@ -77,6 +81,24 @@ module Kward
|
|
|
77
81
|
@display_rows_cache_width = nil
|
|
78
82
|
@display_rows_cache = nil
|
|
79
83
|
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def trim_to_limit
|
|
88
|
+
cutoff = @text.length - @trim_target
|
|
89
|
+
newline = @text.index("\n", cutoff)
|
|
90
|
+
cutoff = newline + 1 if newline && newline - cutoff <= TRIM_BOUNDARY_SEARCH
|
|
91
|
+
cutoff = safe_escape_boundary(cutoff)
|
|
92
|
+
@text = @text[cutoff..].to_s
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def safe_escape_boundary(cutoff)
|
|
96
|
+
escape_start = @text.rindex("\e", cutoff)
|
|
97
|
+
return cutoff unless escape_start
|
|
98
|
+
|
|
99
|
+
escape = ANSI.escape_sequence_at(@text, escape_start)
|
|
100
|
+
escape && escape_start + escape.length > cutoff ? escape_start + escape.length : cutoff
|
|
101
|
+
end
|
|
80
102
|
end
|
|
81
103
|
end
|
|
82
104
|
end
|
|
@@ -128,12 +128,13 @@ module Kward
|
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
-
def initialize(input: $stdin, output: $stdout, slash_commands: [], overlay_settings: nil, project_browser_icon_theme: "off", footer: nil, composer_status: nil, busy_help: true, attachment_badges: nil, attachment_parser: nil, banner_message: nil, tab_keybindings: nil, prompt_history: nil, editor_mode: nil, editor_mode_source: nil, editor_auto_indent: true, editor_auto_indent_source: nil, editor_auto_close_pairs: true, editor_auto_close_pairs_source: nil, editor_soft_wrap: true, editor_soft_wrap_source: nil, editor_bar_cursor: true, editor_bar_cursor_source: nil, editor_line_numbers: "absolute", editor_line_numbers_source: nil, diff_view: "auto", diff_view_source: nil)
|
|
131
|
+
def initialize(input: $stdin, output: $stdout, slash_commands: [], overlay_settings: nil, project_browser_icon_theme: "off", footer: nil, composer_status: nil, busy_help: true, attachment_badges: nil, attachment_parser: nil, banner_message: nil, tab_keybindings: nil, prompt_history: nil, workspace_root: Dir.pwd, editor_mode: nil, editor_mode_source: nil, editor_auto_indent: true, editor_auto_indent_source: nil, editor_auto_close_pairs: true, editor_auto_close_pairs_source: nil, editor_soft_wrap: true, editor_soft_wrap_source: nil, editor_bar_cursor: true, editor_bar_cursor_source: nil, editor_line_numbers: "absolute", editor_line_numbers_source: nil, diff_view: "auto", diff_view_source: nil)
|
|
132
132
|
@input_io = input
|
|
133
133
|
@output_io = output
|
|
134
134
|
@reader = TTY::Reader.new(input: input, output: output, interrupt: :error)
|
|
135
135
|
@mutex = Mutex.new
|
|
136
136
|
@prompt_history = prompt_history
|
|
137
|
+
@workspace_root = File.expand_path(workspace_root.to_s.empty? ? Dir.pwd : workspace_root)
|
|
137
138
|
@composer = ComposerState.new
|
|
138
139
|
load_history(@prompt_history.values) if @prompt_history
|
|
139
140
|
self.composer_input = @composer.input
|
|
@@ -146,7 +147,8 @@ module Kward
|
|
|
146
147
|
@steered_count = 0
|
|
147
148
|
@spinner_frame_index = 0
|
|
148
149
|
@last_spinner_tick = monotonic_now
|
|
149
|
-
@last_footer_refresh =
|
|
150
|
+
@last_footer_refresh = nil
|
|
151
|
+
@cached_footer_text = nil
|
|
150
152
|
@last_composer_status_refresh = 0.0
|
|
151
153
|
@cached_composer_status_text = nil
|
|
152
154
|
@prompt_label = "You>"
|
|
@@ -340,10 +342,23 @@ module Kward
|
|
|
340
342
|
@mutex.synchronize { editor_active? }
|
|
341
343
|
end
|
|
342
344
|
|
|
343
|
-
def
|
|
345
|
+
def update_workspace_root(root, prompt_history: nil)
|
|
346
|
+
@mutex.synchronize do
|
|
347
|
+
expanded_root = File.expand_path(root.to_s.empty? ? Dir.pwd : root)
|
|
348
|
+
return false if expanded_root == @workspace_root
|
|
349
|
+
|
|
350
|
+
@workspace_root = expanded_root
|
|
351
|
+
@prompt_history = prompt_history if prompt_history
|
|
352
|
+
load_history(@prompt_history.values) if @prompt_history
|
|
353
|
+
reset_workspace_file_state_locked
|
|
354
|
+
true
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def edit_file(path, base_dir: nil, allow_new: true)
|
|
344
359
|
start(render: false)
|
|
345
360
|
opened = @mutex.synchronize do
|
|
346
|
-
open_editor(path, allow_new: allow_new, base_dir: base_dir, restrict_to_workspace: false).tap do
|
|
361
|
+
open_editor(path, allow_new: allow_new, base_dir: base_dir || prompt_workspace_root, restrict_to_workspace: false).tap do
|
|
347
362
|
render_prompt_locked
|
|
348
363
|
end
|
|
349
364
|
end
|
|
@@ -364,6 +379,24 @@ module Kward
|
|
|
364
379
|
run_editor
|
|
365
380
|
end
|
|
366
381
|
|
|
382
|
+
# Opens an in-memory document editor. The callback receives edited content
|
|
383
|
+
# when the user saves; return an error message to keep the editor open.
|
|
384
|
+
def review_document(title:, content:, &on_save)
|
|
385
|
+
raise ArgumentError, "review_document requires a save callback" unless on_save
|
|
386
|
+
|
|
387
|
+
start(render: false)
|
|
388
|
+
opened = @mutex.synchronize do
|
|
389
|
+
@editor_save_callback = on_save
|
|
390
|
+
open_scratchpad(:markdown, content: content).tap do
|
|
391
|
+
@editor_state.status = "#{title} · Ctrl+S save · Ctrl+Q cancel"
|
|
392
|
+
render_prompt_locked
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
return false unless opened
|
|
396
|
+
|
|
397
|
+
run_editor
|
|
398
|
+
end
|
|
399
|
+
|
|
367
400
|
def run_editor
|
|
368
401
|
loop do
|
|
369
402
|
key = read_key(nonblock: true)
|
|
@@ -592,9 +625,14 @@ module Kward
|
|
|
592
625
|
|
|
593
626
|
def update_tabs(labels:, active_index: 0)
|
|
594
627
|
@mutex.synchronize do
|
|
595
|
-
|
|
596
|
-
|
|
628
|
+
normalized_labels = Array(labels).map { |label| normalize_tab_label(label) }
|
|
629
|
+
normalized_index = active_index.to_i
|
|
630
|
+
return false if @tabs == normalized_labels && @active_tab_index == normalized_index
|
|
631
|
+
|
|
632
|
+
@tabs = normalized_labels
|
|
633
|
+
@active_tab_index = normalized_index
|
|
597
634
|
render_prompt_locked if @started && @asking
|
|
635
|
+
true
|
|
598
636
|
end
|
|
599
637
|
end
|
|
600
638
|
|
|
@@ -607,16 +645,19 @@ module Kward
|
|
|
607
645
|
end
|
|
608
646
|
end
|
|
609
647
|
|
|
610
|
-
def tab_view_snapshot
|
|
648
|
+
def tab_view_snapshot(include_transcript: true)
|
|
611
649
|
@mutex.synchronize do
|
|
612
|
-
{
|
|
650
|
+
snapshot = {
|
|
613
651
|
composer: @composer.dup,
|
|
614
652
|
prompt_label: @prompt_label.dup,
|
|
615
|
-
editor_state: @editor_state&.dup
|
|
616
|
-
transcript_buffer: @transcript_buffer.dup,
|
|
617
|
-
transcript_viewport_rows: @transcript_viewport_rows,
|
|
618
|
-
stream_state: @stream_state.dup
|
|
653
|
+
editor_state: @editor_state&.dup
|
|
619
654
|
}
|
|
655
|
+
if include_transcript
|
|
656
|
+
snapshot[:transcript_buffer] = @transcript_buffer.dup
|
|
657
|
+
snapshot[:transcript_viewport_rows] = @transcript_viewport_rows
|
|
658
|
+
snapshot[:stream_state] = @stream_state.dup
|
|
659
|
+
end
|
|
660
|
+
snapshot
|
|
620
661
|
end
|
|
621
662
|
end
|
|
622
663
|
|
|
@@ -841,6 +882,8 @@ module Kward
|
|
|
841
882
|
|
|
842
883
|
def refresh_composer_status
|
|
843
884
|
@mutex.synchronize do
|
|
885
|
+
@cached_footer_text = nil
|
|
886
|
+
@last_footer_refresh = nil
|
|
844
887
|
@cached_composer_status_text = nil
|
|
845
888
|
@last_composer_status_refresh = 0.0
|
|
846
889
|
render_prompt_locked if @started && @asking
|
|
@@ -861,6 +904,22 @@ module Kward
|
|
|
861
904
|
|
|
862
905
|
private
|
|
863
906
|
|
|
907
|
+
def prompt_workspace_root
|
|
908
|
+
@workspace_root
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
def reset_workspace_file_state_locked
|
|
912
|
+
@file_mention_paths = nil
|
|
913
|
+
@file_mention_path_entries_paths = nil
|
|
914
|
+
@file_mention_path_entries = nil
|
|
915
|
+
@project_browser_state = nil
|
|
916
|
+
@project_browser_tree_paths = nil
|
|
917
|
+
@project_browser_tree = nil
|
|
918
|
+
@file_overlay_dismissed_token = nil
|
|
919
|
+
@file_open_dismissed_token = nil
|
|
920
|
+
@file_editor_open_status = nil
|
|
921
|
+
end
|
|
922
|
+
|
|
864
923
|
def modal_active_locked?
|
|
865
924
|
@question_prompt_active || !@question_state.nil? || !@select_state.nil? || !@git_state.nil?
|
|
866
925
|
end
|
|
@@ -30,10 +30,12 @@ module Kward
|
|
|
30
30
|
{ name: "shell", description: "Open the embedded Kward shell.", argument_hint: "" },
|
|
31
31
|
{ name: "scratchpad", description: "Open an unsaved editor buffer.", argument_hint: "[text|markdown|ruby]" },
|
|
32
32
|
{ name: "pty", description: "Run a command in an interactive PTY passthrough session.", argument_hint: "<command>" },
|
|
33
|
-
{ name: "tab", description: "Manage tabs.", argument_hint: "[1-n|move|close|new|name]" },
|
|
33
|
+
{ name: "tab", description: "Manage tabs.", argument_hint: "[1-n|move|close|new|name|worktree]" },
|
|
34
34
|
{ name: "status", description: "Show the current status message.", argument_hint: "" },
|
|
35
35
|
{ name: "stats", description: "Show telemetry logging stats.", argument_hint: "[range]" },
|
|
36
|
+
{ name: "sandbox", description: "Inspect or configure model command sandboxing.", argument_hint: "[status|off|read_only|workspace_write|network allow|network deny]" },
|
|
36
37
|
{ name: "hooks", description: "Inspect lifecycle hooks.", argument_hint: "[list|events|logs|doctor|trust|untrust]" },
|
|
38
|
+
{ name: "skill", description: "Activate a skill or capture one from a saved session.", argument_hint: "<name>|capture" },
|
|
37
39
|
{ name: "memory", description: "Inspect and manage Kward memory.", argument_hint: "[enable|disable|auto-summary|core|add|list|forget|promote|relax|inspect|why|summarize]" }
|
|
38
40
|
].freeze
|
|
39
41
|
BUILTIN_RESERVED_COMMAND_NAMES = BUILTIN_COMMANDS.map { |command| command[:name] }.freeze
|
data/lib/kward/prompts.rb
CHANGED
|
@@ -22,23 +22,26 @@ module Kward
|
|
|
22
22
|
# @param plugin_context [String, nil] trusted plugin-provided instructions
|
|
23
23
|
# @return [Hash] role/content system message
|
|
24
24
|
# @api public
|
|
25
|
-
def system_message(workspace_root: Dir.pwd, include_workspace_personality: true, model: nil, reasoning_effort: nil, now: Time.now, memory_context: nil, plugin_context: nil)
|
|
25
|
+
def system_message(workspace_root: Dir.pwd, include_workspace_personality: true, model: nil, reasoning_effort: nil, now: Time.now, memory_context: nil, plugin_context: nil, execution_profile_context: nil)
|
|
26
26
|
{
|
|
27
27
|
role: "system",
|
|
28
|
-
content: prompt_parts(workspace_root: workspace_root, include_workspace_personality: include_workspace_personality, model: model, reasoning_effort: reasoning_effort, now: now, memory_context: memory_context, plugin_context: plugin_context).compact.join("\n\n")
|
|
28
|
+
content: prompt_parts(workspace_root: workspace_root, include_workspace_personality: include_workspace_personality, model: model, reasoning_effort: reasoning_effort, now: now, memory_context: memory_context, plugin_context: plugin_context, execution_profile_context: execution_profile_context).compact.join("\n\n")
|
|
29
29
|
}
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def prompt_parts(workspace_root: Dir.pwd, include_workspace_personality: true, model: nil, reasoning_effort: nil, now: Time.now, memory_context: nil, plugin_context: nil)
|
|
33
|
-
prompt_sections(workspace_root: workspace_root, include_workspace_personality: include_workspace_personality, model: model, reasoning_effort: reasoning_effort, now: now, memory_context: memory_context, plugin_context: plugin_context).map { |section| section[:content] }
|
|
32
|
+
def prompt_parts(workspace_root: Dir.pwd, include_workspace_personality: true, model: nil, reasoning_effort: nil, now: Time.now, memory_context: nil, plugin_context: nil, execution_profile_context: nil)
|
|
33
|
+
prompt_sections(workspace_root: workspace_root, include_workspace_personality: include_workspace_personality, model: model, reasoning_effort: reasoning_effort, now: now, memory_context: memory_context, plugin_context: plugin_context, execution_profile_context: execution_profile_context).map { |section| section[:content] }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Returns labeled prompt sections for inspection by CLI and RPC frontends.
|
|
37
37
|
#
|
|
38
38
|
# @return [Array<Hash>] section hashes with label, content, and optional source
|
|
39
39
|
# @api public
|
|
40
|
-
def prompt_sections(workspace_root: Dir.pwd, include_workspace_personality: true, model: nil, reasoning_effort: nil, now: Time.now, memory_context: nil, plugin_context: nil)
|
|
40
|
+
def prompt_sections(workspace_root: Dir.pwd, include_workspace_personality: true, model: nil, reasoning_effort: nil, now: Time.now, memory_context: nil, plugin_context: nil, execution_profile_context: nil)
|
|
41
|
+
return replacement_prompt_sections if ConfigFiles.replacement_system_prompt?
|
|
42
|
+
|
|
41
43
|
sections = [prompt_section("Built-in system prompt", base_prompt)]
|
|
44
|
+
sections << prompt_section("Execution profile", execution_profile_context) unless execution_profile_context.to_s.empty?
|
|
42
45
|
sections << prompt_section(config_agents_prompt_label, config_agents_prompt, source: config_agents_prompt_source)
|
|
43
46
|
sections << prompt_section("Memory context", memory_context) unless memory_context.to_s.empty?
|
|
44
47
|
if include_workspace_personality
|
|
@@ -53,11 +56,11 @@ module Kward
|
|
|
53
56
|
|
|
54
57
|
def base_prompt
|
|
55
58
|
<<~PROMPT.strip
|
|
56
|
-
You are Kward, a concise practical CLI coding agent. Use tools to understand and modify software projects. Inspect files before changing them, make the smallest correct change, preserve existing style, and summarize what changed. Be honest about limitations.
|
|
59
|
+
You are Kward, a concise practical CLI coding agent. Use tools to understand and modify software projects. Only call tools advertised for the current turn. Inspect files before changing them, make the smallest correct change, preserve existing style, and summarize what changed. Be honest about limitations.
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
When web tools are available, use web_search to discover sources, fetch_content for important human-readable pages, and fetch_raw for machine-readable resources such as JSON, YAML, XML, RSS, OpenAPI specs, and plain text. Prefer official or primary sources and cite or mention the URLs you relied on. Use code_search for package, GitHub repository, and source-code research.
|
|
59
62
|
|
|
60
|
-
Manage code context deliberately. Prefer context_for_task, summarize_file_structure, and read_file mode="outline"/"preview" before broad reads. Escalate to read_file mode="range" for exact lines, and use mode="full" only when focused context is insufficient. Use context_budget_stats when asked about context savings.
|
|
63
|
+
Manage code context deliberately. Prefer context_for_task, summarize_file_structure, and read_file mode="outline"/"preview" before broad reads. Escalate to read_file mode="range" for exact lines, and use mode="full" only when focused context is insufficient. If output is compacted or replaced with a duplicate reference, use retrieve_tool_output to inspect the needed original detail. Use context_budget_stats when asked about context savings. When a material requirement is ambiguous, use ask_user_question when it is available; otherwise state the assumption.
|
|
61
64
|
PROMPT
|
|
62
65
|
end
|
|
63
66
|
|
|
@@ -73,6 +76,7 @@ module Kward
|
|
|
73
76
|
end
|
|
74
77
|
|
|
75
78
|
def config_agents_prompt_source
|
|
79
|
+
return nil unless ConfigFiles.include_config_principles?
|
|
76
80
|
return ConfigFiles.config_principles_path if File.exist?(ConfigFiles.config_principles_path)
|
|
77
81
|
return ConfigFiles.config_agents_path if File.exist?(ConfigFiles.config_agents_path)
|
|
78
82
|
|
|
@@ -112,6 +116,14 @@ module Kward
|
|
|
112
116
|
"Workspace AGENTS.md hint"
|
|
113
117
|
end
|
|
114
118
|
|
|
119
|
+
def replacement_prompt_sections
|
|
120
|
+
path = ConfigFiles.system_prompt_file_path
|
|
121
|
+
content = ConfigFiles.system_prompt_file
|
|
122
|
+
return [] if content.to_s.empty?
|
|
123
|
+
|
|
124
|
+
[prompt_section("Custom system prompt (replacement)", content, source: path)]
|
|
125
|
+
end
|
|
126
|
+
|
|
115
127
|
def prompt_section(label, content, source: nil)
|
|
116
128
|
return nil if content.to_s.empty?
|
|
117
129
|
|
|
@@ -123,8 +135,8 @@ module Kward
|
|
|
123
135
|
|
|
124
136
|
lines = [
|
|
125
137
|
"Agent Skills are available.",
|
|
126
|
-
"When a task matches a skill's description, use
|
|
127
|
-
"
|
|
138
|
+
"When read_skill is available and a task matches a skill's description, use it to load the instructions before proceeding.",
|
|
139
|
+
"When using read_skill, use a relative path to read referenced files inside a skill directory.",
|
|
128
140
|
"Available skills:"
|
|
129
141
|
]
|
|
130
142
|
skills.each do |skill|
|