kward 0.67.0 → 0.68.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.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/Gemfile.lock +2 -2
  4. data/README.md +5 -5
  5. data/doc/authentication.md +24 -1
  6. data/doc/configuration.md +9 -2
  7. data/doc/extensibility.md +1 -1
  8. data/doc/getting-started.md +4 -6
  9. data/doc/plugins.md +0 -2
  10. data/doc/releasing.md +7 -8
  11. data/doc/rpc.md +6 -6
  12. data/doc/usage.md +5 -2
  13. data/doc/web-search.md +2 -2
  14. data/kward.gemspec +4 -0
  15. data/lib/kward/agent.rb +29 -2
  16. data/lib/kward/ansi.rb +3 -0
  17. data/lib/kward/auth/anthropic_oauth.rb +291 -0
  18. data/lib/kward/auth/file.rb +2 -0
  19. data/lib/kward/auth/github_oauth.rb +3 -0
  20. data/lib/kward/auth/openai_oauth.rb +4 -0
  21. data/lib/kward/auth/openrouter_api_key.rb +2 -0
  22. data/lib/kward/cancellation.rb +3 -0
  23. data/lib/kward/cli/auth_commands.rb +82 -0
  24. data/lib/kward/cli/commands.rb +222 -0
  25. data/lib/kward/cli/compaction.rb +25 -0
  26. data/lib/kward/cli/doctor.rb +121 -0
  27. data/lib/kward/cli/interactive_turn.rb +225 -0
  28. data/lib/kward/cli/memory_commands.rb +133 -0
  29. data/lib/kward/cli/plugins.rb +112 -0
  30. data/lib/kward/cli/prompt_interface.rb +132 -0
  31. data/lib/kward/cli/rendering.rb +389 -0
  32. data/lib/kward/cli/runtime_helpers.rb +159 -0
  33. data/lib/kward/cli/sessions.rb +376 -0
  34. data/lib/kward/cli/settings.rb +663 -0
  35. data/lib/kward/cli/slash_commands.rb +112 -0
  36. data/lib/kward/cli/stats.rb +64 -0
  37. data/lib/kward/cli/tool_summaries.rb +153 -0
  38. data/lib/kward/cli.rb +38 -2790
  39. data/lib/kward/cli_transcript_formatter.rb +4 -7
  40. data/lib/kward/clipboard.rb +1 -0
  41. data/lib/kward/compaction/file_operation_tracker.rb +3 -0
  42. data/lib/kward/compactor.rb +29 -7
  43. data/lib/kward/config_files.rb +33 -24
  44. data/lib/kward/conversation.rb +70 -5
  45. data/lib/kward/events.rb +2 -0
  46. data/lib/kward/export_path.rb +2 -0
  47. data/lib/kward/image_attachments.rb +2 -0
  48. data/lib/kward/markdown_transcript.rb +2 -0
  49. data/lib/kward/memory/manager.rb +13 -0
  50. data/lib/kward/message_access.rb +23 -2
  51. data/lib/kward/message_text.rb +45 -0
  52. data/lib/kward/model/chat_invocation.rb +2 -0
  53. data/lib/kward/model/client.rb +295 -77
  54. data/lib/kward/model/context_overflow.rb +2 -0
  55. data/lib/kward/model/context_usage.rb +3 -0
  56. data/lib/kward/model/model_info.rb +143 -4
  57. data/lib/kward/model/payloads.rb +166 -13
  58. data/lib/kward/model/retry_message.rb +2 -0
  59. data/lib/kward/model/stream_parser.rb +129 -0
  60. data/lib/kward/pan/server.rb +3 -1
  61. data/lib/kward/plugin_registry.rb +12 -0
  62. data/lib/kward/private_file.rb +2 -0
  63. data/lib/kward/prompt_interface/banner.rb +3 -0
  64. data/lib/kward/prompt_interface/composer_controller.rb +262 -0
  65. data/lib/kward/prompt_interface/composer_renderer.rb +172 -0
  66. data/lib/kward/prompt_interface/composer_state.rb +221 -0
  67. data/lib/kward/prompt_interface/key_handler.rb +365 -0
  68. data/lib/kward/prompt_interface/layout.rb +31 -0
  69. data/lib/kward/prompt_interface/overlay_renderer.rb +111 -0
  70. data/lib/kward/prompt_interface/prompt_renderer.rb +91 -0
  71. data/lib/kward/prompt_interface/question_prompt.rb +328 -0
  72. data/lib/kward/prompt_interface/runtime_state.rb +59 -0
  73. data/lib/kward/prompt_interface/screen.rb +186 -0
  74. data/lib/kward/prompt_interface/selection_prompt.rb +242 -0
  75. data/lib/kward/prompt_interface/slash_overlay.rb +102 -0
  76. data/lib/kward/prompt_interface/stream_state.rb +65 -0
  77. data/lib/kward/prompt_interface/transcript_buffer.rb +85 -0
  78. data/lib/kward/prompt_interface/transcript_renderer.rb +142 -0
  79. data/lib/kward/prompt_interface.rb +69 -1832
  80. data/lib/kward/prompts/commands.rb +2 -0
  81. data/lib/kward/prompts/templates.rb +3 -0
  82. data/lib/kward/prompts.rb +2 -0
  83. data/lib/kward/question_contract.rb +66 -0
  84. data/lib/kward/resources/avatar_kward_logo.rb +2 -0
  85. data/lib/kward/resources/pixel_logo.rb +2 -0
  86. data/lib/kward/rpc/attachment_normalizer.rb +60 -0
  87. data/lib/kward/rpc/auth_manager.rb +65 -11
  88. data/lib/kward/rpc/config_manager.rb +11 -0
  89. data/lib/kward/rpc/prompt_bridge.rb +5 -26
  90. data/lib/kward/rpc/redactor.rb +3 -0
  91. data/lib/kward/rpc/runtime_payloads.rb +4 -1
  92. data/lib/kward/rpc/server.rb +37 -10
  93. data/lib/kward/rpc/session_manager.rb +123 -347
  94. data/lib/kward/rpc/session_metrics.rb +68 -0
  95. data/lib/kward/rpc/session_tree.rb +48 -0
  96. data/lib/kward/rpc/session_tree_rows.rb +208 -0
  97. data/lib/kward/rpc/tool_event_normalizer.rb +3 -0
  98. data/lib/kward/rpc/tool_metadata.rb +3 -0
  99. data/lib/kward/rpc/transcript_normalizer.rb +3 -0
  100. data/lib/kward/rpc/transport.rb +3 -0
  101. data/lib/kward/session_diff.rb +2 -0
  102. data/lib/kward/session_store.rb +125 -31
  103. data/lib/kward/session_trash.rb +1 -0
  104. data/lib/kward/session_tree_renderer.rb +8 -41
  105. data/lib/kward/session_tree_tool_display.rb +56 -0
  106. data/lib/kward/skills/registry.rb +3 -0
  107. data/lib/kward/starter_pack_installer.rb +1 -0
  108. data/lib/kward/steering.rb +2 -0
  109. data/lib/kward/telemetry/logger.rb +3 -0
  110. data/lib/kward/telemetry/stats.rb +3 -0
  111. data/lib/kward/tools/ask_user_question.rb +20 -32
  112. data/lib/kward/tools/base.rb +8 -0
  113. data/lib/kward/tools/code_search.rb +5 -0
  114. data/lib/kward/tools/edit_file.rb +5 -0
  115. data/lib/kward/tools/list_directory.rb +5 -0
  116. data/lib/kward/tools/read_file.rb +5 -0
  117. data/lib/kward/tools/read_skill.rb +5 -0
  118. data/lib/kward/tools/registry.rb +33 -2
  119. data/lib/kward/tools/run_shell_command.rb +5 -0
  120. data/lib/kward/tools/search/code.rb +7 -0
  121. data/lib/kward/tools/search/web.rb +17 -14
  122. data/lib/kward/tools/tool_call.rb +25 -5
  123. data/lib/kward/tools/web_search.rb +7 -1
  124. data/lib/kward/tools/write_file.rb +5 -0
  125. data/lib/kward/transcript_export.rb +2 -0
  126. data/lib/kward/version.rb +2 -1
  127. data/lib/kward/workspace.rb +45 -5
  128. metadata +43 -1
@@ -3,7 +3,19 @@ require "pathname"
3
3
  require "timeout"
4
4
  require_relative "session_diff"
5
5
 
6
+ # Namespace for the Kward CLI agent runtime.
6
7
  module Kward
8
+ # Filesystem and shell-command boundary for workspace tools.
9
+ #
10
+ # `Workspace` is deliberately low-level: it validates paths, enforces output
11
+ # limits, applies exact edits, writes files, and runs shell commands from one
12
+ # root directory. It should not know about model prompts, sessions, telemetry,
13
+ # or UI confirmation. Tool wrappers and frontends provide those policies.
14
+ #
15
+ # Guardrails are enabled by default and require all file paths to resolve under
16
+ # `root`. RPC may report when guardrails are disabled, but callers should avoid
17
+ # bypassing this class for local filesystem mutation so read-before-write and
18
+ # path safety remain consistent.
7
19
  class Workspace
8
20
  MAX_FILE_BYTES = 256 * 1024
9
21
  MAX_READ_OUTPUT_BYTES = 50 * 1024
@@ -12,6 +24,7 @@ module Kward
12
24
  MAX_EDIT_DIFF_BYTES = 8 * 1024
13
25
  DEFAULT_COMMAND_TIMEOUT_SECONDS = 30
14
26
 
27
+ # Creates an object for workspace filesystem and shell operations.
15
28
  def initialize(root: Dir.pwd, max_file_bytes: MAX_FILE_BYTES, max_read_output_bytes: MAX_READ_OUTPUT_BYTES, max_read_output_lines: MAX_READ_OUTPUT_LINES, max_command_output_bytes: MAX_COMMAND_OUTPUT_BYTES, guardrails: true)
16
29
  @root = Pathname.new(root).realpath
17
30
  @guardrails = guardrails
@@ -21,8 +34,10 @@ module Kward
21
34
  @max_command_output_bytes = max_command_output_bytes
22
35
  end
23
36
 
37
+ # @return [Pathname] canonical workspace root used as the base for file and shell tools
24
38
  attr_reader :root
25
39
 
40
+ # Lists immediate directory children after resolving `path` through workspace guardrails.
26
41
  def list_directory(path)
27
42
  resolved = workspace_path(path)
28
43
  return "Error: not a directory: #{path}" unless File.directory?(resolved)
@@ -34,6 +49,11 @@ module Kward
34
49
  "Error: #{e.message}"
35
50
  end
36
51
 
52
+ # Reads a bounded text slice from a workspace file.
53
+ #
54
+ # The returned string is user/model-facing and includes continuation notices
55
+ # when output is truncated. Errors are returned as `"Error: ..."` strings so
56
+ # tool calls can be persisted in the conversation without raising.
37
57
  def read_file(path, offset: nil, limit: nil)
38
58
  resolved = workspace_path(path)
39
59
  return "Error: not a file: #{path}" unless File.file?(resolved)
@@ -41,11 +61,20 @@ module Kward
41
61
  size = File.size(resolved)
42
62
  return "Error: file too large: #{path} is #{size} bytes; limit is #{@max_file_bytes} bytes" if size > @max_file_bytes
43
63
 
44
- read_file_slice(File.read(resolved), offset: offset, limit: limit)
64
+ content = File.read(resolved)
65
+ return "Error: not a text file: #{path}" if binary_content?(content)
66
+
67
+ read_file_slice(content, offset: offset, limit: limit)
45
68
  rescue SecurityError, Errno::ENOENT => e
46
69
  "Error: #{e.message}"
47
70
  end
48
71
 
72
+ # Writes complete file content after enforcing read-before-write for
73
+ # existing files.
74
+ #
75
+ # `read_paths` must contain resolved paths previously observed by
76
+ # `ReadFile`; this keeps tool-driven edits explicit and prevents overwriting
77
+ # unseen user files.
49
78
  def write_file(path, content, read_paths:)
50
79
  resolved = workspace_write_path(path)
51
80
 
@@ -53,10 +82,6 @@ module Kward
53
82
  return "Error: existing file must be read before writing: #{path}"
54
83
  end
55
84
 
56
- if block_given? && !yield(relative_path(resolved), content.bytesize)
57
- return "Declined: write_file was not approved for #{path}"
58
- end
59
-
60
85
  old_content = File.exist?(resolved) ? File.read(resolved) : nil
61
86
  File.write(resolved, content)
62
87
  output = "Wrote #{content.bytesize} bytes to #{path}"
@@ -66,6 +91,11 @@ module Kward
66
91
  "Error: #{e.message}"
67
92
  end
68
93
 
94
+ # Applies exact non-overlapping replacements to a previously read file.
95
+ #
96
+ # Each `old_text` must match exactly once. This favors predictable model edits
97
+ # over fuzzy patching and returns readable error strings when more context is
98
+ # needed.
69
99
  def edit_file(path, edits, read_paths:)
70
100
  resolved = workspace_path(path)
71
101
  return "Error: not a file: #{path}" unless File.file?(resolved)
@@ -84,6 +114,11 @@ module Kward
84
114
  "Error: #{e.message}"
85
115
  end
86
116
 
117
+ # Runs a shell command from the workspace root with timeout, cancellation,
118
+ # and bounded combined output.
119
+ #
120
+ # This method intentionally does not ask for confirmation; CLI/RPC policy
121
+ # must decide whether a command is allowed before reaching this boundary.
87
122
  def run_shell_command(command, timeout_seconds: DEFAULT_COMMAND_TIMEOUT_SECONDS, cancellation: nil)
88
123
  command = command.to_s.strip
89
124
  return "Error: command is required" if command.empty?
@@ -114,6 +149,7 @@ module Kward
114
149
  "Error: #{e.message}"
115
150
  end
116
151
 
152
+ # Resolves a path with the same guardrails used by file tools.
117
153
  def resolved_path(path)
118
154
  workspace_path(path)
119
155
  end
@@ -189,6 +225,10 @@ module Kward
189
225
  output
190
226
  end
191
227
 
228
+ def binary_content?(content)
229
+ content.include?("\x00")
230
+ end
231
+
192
232
  def read_start_index(offset)
193
233
  return 0 if offset.nil?
194
234
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kward
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.67.0
4
+ version: 0.68.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kai Wood
@@ -140,12 +140,28 @@ files:
140
140
  - lib/kward.rb
141
141
  - lib/kward/agent.rb
142
142
  - lib/kward/ansi.rb
143
+ - lib/kward/auth/anthropic_oauth.rb
143
144
  - lib/kward/auth/file.rb
144
145
  - lib/kward/auth/github_oauth.rb
145
146
  - lib/kward/auth/openai_oauth.rb
146
147
  - lib/kward/auth/openrouter_api_key.rb
147
148
  - lib/kward/cancellation.rb
148
149
  - lib/kward/cli.rb
150
+ - lib/kward/cli/auth_commands.rb
151
+ - lib/kward/cli/commands.rb
152
+ - lib/kward/cli/compaction.rb
153
+ - lib/kward/cli/doctor.rb
154
+ - lib/kward/cli/interactive_turn.rb
155
+ - lib/kward/cli/memory_commands.rb
156
+ - lib/kward/cli/plugins.rb
157
+ - lib/kward/cli/prompt_interface.rb
158
+ - lib/kward/cli/rendering.rb
159
+ - lib/kward/cli/runtime_helpers.rb
160
+ - lib/kward/cli/sessions.rb
161
+ - lib/kward/cli/settings.rb
162
+ - lib/kward/cli/slash_commands.rb
163
+ - lib/kward/cli/stats.rb
164
+ - lib/kward/cli/tool_summaries.rb
149
165
  - lib/kward/cli_transcript_formatter.rb
150
166
  - lib/kward/clipboard.rb
151
167
  - lib/kward/compaction/file_operation_tracker.rb
@@ -158,6 +174,7 @@ files:
158
174
  - lib/kward/markdown_transcript.rb
159
175
  - lib/kward/memory/manager.rb
160
176
  - lib/kward/message_access.rb
177
+ - lib/kward/message_text.rb
161
178
  - lib/kward/model/chat_invocation.rb
162
179
  - lib/kward/model/client.rb
163
180
  - lib/kward/model/context_overflow.rb
@@ -172,11 +189,28 @@ files:
172
189
  - lib/kward/private_file.rb
173
190
  - lib/kward/prompt_interface.rb
174
191
  - lib/kward/prompt_interface/banner.rb
192
+ - lib/kward/prompt_interface/composer_controller.rb
193
+ - lib/kward/prompt_interface/composer_renderer.rb
194
+ - lib/kward/prompt_interface/composer_state.rb
195
+ - lib/kward/prompt_interface/key_handler.rb
196
+ - lib/kward/prompt_interface/layout.rb
197
+ - lib/kward/prompt_interface/overlay_renderer.rb
198
+ - lib/kward/prompt_interface/prompt_renderer.rb
199
+ - lib/kward/prompt_interface/question_prompt.rb
200
+ - lib/kward/prompt_interface/runtime_state.rb
201
+ - lib/kward/prompt_interface/screen.rb
202
+ - lib/kward/prompt_interface/selection_prompt.rb
203
+ - lib/kward/prompt_interface/slash_overlay.rb
204
+ - lib/kward/prompt_interface/stream_state.rb
205
+ - lib/kward/prompt_interface/transcript_buffer.rb
206
+ - lib/kward/prompt_interface/transcript_renderer.rb
175
207
  - lib/kward/prompts.rb
176
208
  - lib/kward/prompts/commands.rb
177
209
  - lib/kward/prompts/templates.rb
210
+ - lib/kward/question_contract.rb
178
211
  - lib/kward/resources/avatar_kward_logo.rb
179
212
  - lib/kward/resources/pixel_logo.rb
213
+ - lib/kward/rpc/attachment_normalizer.rb
180
214
  - lib/kward/rpc/auth_manager.rb
181
215
  - lib/kward/rpc/config_manager.rb
182
216
  - lib/kward/rpc/prompt_bridge.rb
@@ -184,6 +218,9 @@ files:
184
218
  - lib/kward/rpc/runtime_payloads.rb
185
219
  - lib/kward/rpc/server.rb
186
220
  - lib/kward/rpc/session_manager.rb
221
+ - lib/kward/rpc/session_metrics.rb
222
+ - lib/kward/rpc/session_tree.rb
223
+ - lib/kward/rpc/session_tree_rows.rb
187
224
  - lib/kward/rpc/tool_event_normalizer.rb
188
225
  - lib/kward/rpc/tool_metadata.rb
189
226
  - lib/kward/rpc/transcript_normalizer.rb
@@ -192,6 +229,7 @@ files:
192
229
  - lib/kward/session_store.rb
193
230
  - lib/kward/session_trash.rb
194
231
  - lib/kward/session_tree_renderer.rb
232
+ - lib/kward/session_tree_tool_display.rb
195
233
  - lib/kward/skills/registry.rb
196
234
  - lib/kward/starter_pack_installer.rb
197
235
  - lib/kward/steering.rb
@@ -220,6 +258,10 @@ licenses:
220
258
  - MIT
221
259
  metadata:
222
260
  rubygems_mfa_required: 'true'
261
+ source_code_uri: https://github.com/kaiwood/kward
262
+ changelog_uri: https://github.com/kaiwood/kward/blob/main/CHANGELOG.md
263
+ documentation_uri: https://github.com/kaiwood/kward#readme
264
+ bug_tracker_uri: https://github.com/kaiwood/kward/issues
223
265
  rdoc_options: []
224
266
  require_paths:
225
267
  - lib