kward 0.74.0 → 0.75.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/Gemfile.lock +2 -2
  4. data/README.md +4 -2
  5. data/Rakefile +18 -0
  6. data/doc/configuration.md +56 -3
  7. data/doc/extensibility.md +6 -19
  8. data/doc/getting-started.md +1 -1
  9. data/doc/lifecycle-hooks.md +424 -0
  10. data/doc/memory.md +1 -1
  11. data/doc/personas.md +2 -2
  12. data/doc/plugins.md +1 -1
  13. data/doc/releasing.md +7 -15
  14. data/doc/rpc.md +108 -16
  15. data/doc/session-management.md +9 -9
  16. data/doc/skills.md +199 -0
  17. data/doc/usage.md +7 -7
  18. data/lib/kward/agent.rb +72 -22
  19. data/lib/kward/auth/anthropic_oauth.rb +5 -87
  20. data/lib/kward/auth/oauth_helpers.rb +105 -0
  21. data/lib/kward/auth/openai_oauth.rb +4 -86
  22. data/lib/kward/cli/commands.rb +9 -2
  23. data/lib/kward/cli/compaction.rb +7 -0
  24. data/lib/kward/cli/git.rb +30 -3
  25. data/lib/kward/cli/hook_commands.rb +258 -0
  26. data/lib/kward/cli/memory_commands.rb +1 -5
  27. data/lib/kward/cli/plugins.rb +41 -1
  28. data/lib/kward/cli/prompt_interface.rb +32 -3
  29. data/lib/kward/cli/rendering.rb +1 -1
  30. data/lib/kward/cli/runtime_helpers.rb +44 -5
  31. data/lib/kward/cli/sessions.rb +13 -0
  32. data/lib/kward/cli/settings.rb +226 -152
  33. data/lib/kward/cli/slash_commands.rb +81 -42
  34. data/lib/kward/cli/tabs.rb +26 -6
  35. data/lib/kward/cli.rb +26 -3
  36. data/lib/kward/cli_transcript_formatter.rb +0 -7
  37. data/lib/kward/compaction/token_estimator.rb +112 -0
  38. data/lib/kward/compactor.rb +42 -116
  39. data/lib/kward/config_files.rb +117 -25
  40. data/lib/kward/deep_copy.rb +42 -0
  41. data/lib/kward/diff_view_mode.rb +36 -0
  42. data/lib/kward/events.rb +1 -0
  43. data/lib/kward/export_path.rb +2 -6
  44. data/lib/kward/frontmatter.rb +43 -0
  45. data/lib/kward/hooks/audit_log.rb +121 -0
  46. data/lib/kward/hooks/catalog.rb +110 -0
  47. data/lib/kward/hooks/command_handler.rb +66 -0
  48. data/lib/kward/hooks/config_loader.rb +104 -0
  49. data/lib/kward/hooks/decision.rb +102 -0
  50. data/lib/kward/hooks/event.rb +60 -0
  51. data/lib/kward/hooks/http_handler.rb +72 -0
  52. data/lib/kward/hooks/manager.rb +158 -0
  53. data/lib/kward/hooks/matcher.rb +75 -0
  54. data/lib/kward/hooks.rb +16 -0
  55. data/lib/kward/markdown_transcript.rb +4 -3
  56. data/lib/kward/mcp/client.rb +1 -1
  57. data/lib/kward/mcp/server_config.rb +7 -3
  58. data/lib/kward/mcp/stdio_transport.rb +7 -1
  59. data/lib/kward/memory/turn_context.rb +19 -0
  60. data/lib/kward/message_access.rb +2 -2
  61. data/lib/kward/message_text.rb +19 -0
  62. data/lib/kward/model/client.rb +13 -45
  63. data/lib/kward/model/context_usage.rb +10 -4
  64. data/lib/kward/model/copilot_models.rb +44 -0
  65. data/lib/kward/model/model_info.rb +24 -0
  66. data/lib/kward/model/payloads.rb +15 -17
  67. data/lib/kward/model/stream_parser.rb +178 -13
  68. data/lib/kward/pan/server.rb +51 -2
  69. data/lib/kward/path_guard.rb +13 -0
  70. data/lib/kward/plugin_registry.rb +86 -25
  71. data/lib/kward/prompt_interface/editor/controller.rb +60 -1
  72. data/lib/kward/prompt_interface/editor/renderer.rb +11 -0
  73. data/lib/kward/prompt_interface/editor/state.rb +5 -1
  74. data/lib/kward/prompt_interface/selection_prompt.rb +26 -6
  75. data/lib/kward/prompt_interface.rb +4 -1
  76. data/lib/kward/prompts/commands.rb +3 -3
  77. data/lib/kward/prompts.rb +7 -7
  78. data/lib/kward/rpc/mcp_status.rb +50 -0
  79. data/lib/kward/rpc/prompt_bridge.rb +38 -4
  80. data/lib/kward/rpc/runtime_payloads.rb +1 -1
  81. data/lib/kward/rpc/server.rb +94 -14
  82. data/lib/kward/rpc/session_manager.rb +258 -33
  83. data/lib/kward/rpc/session_metrics.rb +1 -1
  84. data/lib/kward/rpc/session_tree_rows.rb +13 -64
  85. data/lib/kward/rpc/tool_event_normalizer.rb +16 -2
  86. data/lib/kward/rpc/transcript_normalizer.rb +9 -10
  87. data/lib/kward/rpc/turn_context.rb +109 -0
  88. data/lib/kward/session_store.rb +6 -20
  89. data/lib/kward/session_tree_nodes.rb +98 -18
  90. data/lib/kward/session_tree_renderer.rb +6 -61
  91. data/lib/kward/skills/registry.rb +109 -17
  92. data/lib/kward/tools/mcp_tool.rb +4 -0
  93. data/lib/kward/tools/read_skill.rb +9 -4
  94. data/lib/kward/tools/registry.rb +266 -8
  95. data/lib/kward/tools/search/code.rb +4 -22
  96. data/lib/kward/tools/search/web.rb +28 -2
  97. data/lib/kward/update_check.rb +117 -0
  98. data/lib/kward/version.rb +1 -1
  99. data/lib/kward/workers/manager.rb +43 -3
  100. data/lib/kward/workspace.rb +3 -1
  101. data/templates/default/fulldoc/html/css/kward.css +22 -0
  102. data/templates/default/fulldoc/html/js/kward.js +2 -0
  103. data/templates/default/kward_navigation.rb +4 -2
  104. data/templates/default/layout/html/setup.rb +2 -0
  105. metadata +25 -1
@@ -2,6 +2,7 @@ require "timeout"
2
2
  require_relative "../agent"
3
3
  require_relative "../cancellation"
4
4
  require_relative "../conversation"
5
+ require_relative "../hooks"
5
6
  require_relative "../model/client"
6
7
  require_relative "../session_store"
7
8
  require_relative "../tools/registry"
@@ -16,7 +17,7 @@ module Kward
16
17
  class Manager
17
18
  DEFAULT_TIMEOUT_SECONDS = 180
18
19
 
19
- def initialize(client_factory: -> { Client.new }, prompt: nil, workspace_root: Dir.pwd, timeout_seconds: DEFAULT_TIMEOUT_SECONDS, on_status_change: nil, session_store: nil, provider: nil, model: nil, reasoning_effort: nil, write_lock: nil, worker_store: nil, git_guard: nil, write_lane_available: -> { true })
20
+ def initialize(client_factory: -> { Client.new }, prompt: nil, workspace_root: Dir.pwd, timeout_seconds: DEFAULT_TIMEOUT_SECONDS, on_status_change: nil, session_store: nil, provider: nil, model: nil, reasoning_effort: nil, write_lock: nil, worker_store: nil, git_guard: nil, write_lane_available: -> { true }, hook_manager: nil, hook_context: nil)
20
21
  @client_factory = client_factory
21
22
  @prompt = prompt
22
23
  @workspace_root = ConfigFiles.canonical_workspace_root(workspace_root)
@@ -30,12 +31,15 @@ module Kward
30
31
  @worker_store = worker_store
31
32
  @git_guard = git_guard || GitGuard.new(root: @workspace_root)
32
33
  @write_lane_available = write_lane_available
34
+ @hook_manager = hook_manager
35
+ @hook_context = hook_context
33
36
  @workers = {}
34
37
  @mutex = Mutex.new
35
38
  end
36
39
 
37
40
  def start(role:, prompt:, title: nil, id: nil)
38
41
  worker = build_worker(role: role, prompt: prompt, title: title, id: id)
42
+ run_blocking_hook("worker_job_create", worker)
39
43
  enqueue(worker)
40
44
  end
41
45
 
@@ -105,15 +109,19 @@ module Kward
105
109
  worker.conversation = conversation
106
110
  attach_session(worker, conversation)
107
111
  writer_id = wait_for_worker_writer(worker)
112
+ run_blocking_hook("worker_job_start_before", worker)
108
113
  update_status(worker, "running")
114
+ run_hook("worker_job_start_after", worker)
109
115
  registry = ToolRegistry.new(
110
116
  workspace: Workspace.new(root: worker.workspace_root),
111
117
  prompt: @prompt,
112
118
  allowed_tool_names: ToolPolicy.allowed_tool_names(worker.role),
113
119
  write_lock: @write_lock,
114
- writer_id: writer_id
120
+ writer_id: writer_id,
121
+ hook_manager: @hook_manager,
122
+ hook_context: @hook_context
115
123
  )
116
- agent = Agent.new(client: @client_factory.call, tool_registry: registry, conversation: conversation)
124
+ agent = Agent.new(client: @client_factory.call, tool_registry: registry, conversation: conversation, hook_manager: @hook_manager, hook_context: @hook_context)
117
125
  report = Timeout.timeout(@timeout_seconds, WorkerTimeoutError) do
118
126
  agent.ask(worker_prompt(worker), cancellation: worker.cancellation) do |event|
119
127
  worker.record_event(event)
@@ -121,16 +129,48 @@ module Kward
121
129
  end
122
130
  report = finalize_write_worker(worker, report)
123
131
  update_status(worker, "ready", report: report, error: "")
132
+ run_hook("worker_job_ready_for_review", worker)
124
133
  rescue WorkerTimeoutError
125
134
  update_status(worker, "failed", error: "Worker timed out after #{@timeout_seconds} seconds")
135
+ run_hook("worker_job_failed", worker, error: "Worker timed out after #{@timeout_seconds} seconds")
126
136
  rescue Cancellation::CancelledError
127
137
  update_status(worker, "cancelled")
128
138
  rescue StandardError => e
129
139
  update_status(worker, "failed", error: e.message)
140
+ run_hook("worker_job_failed", worker, error: e.message)
130
141
  ensure
131
142
  release_worker_writer(worker)
132
143
  end
133
144
 
145
+ def run_blocking_hook(name, worker, payload = {})
146
+ result = run_hook(name, worker, payload)
147
+ return result unless result && !result.allowed?
148
+
149
+ raise HookDenied, result.decision.message || "Worker lifecycle hook #{name} blocked the job"
150
+ end
151
+
152
+ def run_hook(name, worker, payload = {})
153
+ return unless @hook_manager
154
+
155
+ @hook_manager.run(Hooks::Event.new(
156
+ name: name,
157
+ workspace: { root: worker.workspace_root },
158
+ payload: worker_payload(worker).merge(payload)
159
+ ), context: @hook_context)
160
+ end
161
+
162
+ HookDenied = Class.new(StandardError)
163
+
164
+ def worker_payload(worker)
165
+ {
166
+ worker_id: worker.id,
167
+ role: worker.role,
168
+ title: worker.title,
169
+ status: worker.status,
170
+ session_path: worker.session&.path
171
+ }.compact
172
+ end
173
+
134
174
  def update_status(worker, status, **values)
135
175
  return worker if worker.status == "archived" && status.to_s != "archived"
136
176
 
@@ -2,6 +2,8 @@ require "pathname"
2
2
  require_relative "local_command_runner"
3
3
  require_relative "session_diff"
4
4
 
5
+ require_relative "path_guard"
6
+
5
7
  # Namespace for the Kward CLI agent runtime.
6
8
  module Kward
7
9
  # Filesystem and shell-command boundary for workspace tools.
@@ -212,7 +214,7 @@ module Kward
212
214
  end
213
215
 
214
216
  def inside_workspace?(path)
215
- path.to_s == @root.to_s || path.to_s.start_with?("#{@root}/")
217
+ PathGuard.inside?(path, @root)
216
218
  end
217
219
 
218
220
  def relative_path(path)
@@ -1030,6 +1030,21 @@ body.kward-docs .kward-home #footer p {
1030
1030
  }
1031
1031
 
1032
1032
  @media (max-width: 620px) {
1033
+ .kward-hero > *,
1034
+ .kward-install-card > *,
1035
+ .kward-feature-strip > *,
1036
+ .kward-capabilities li {
1037
+ min-width: 0;
1038
+ }
1039
+
1040
+ .kward-hero h1 {
1041
+ font-size: clamp(32px, 10vw, 38px);
1042
+ }
1043
+
1044
+ .kward-swosh {
1045
+ max-width: 100%;
1046
+ }
1047
+
1033
1048
  .kward-feature-strip,
1034
1049
  .kward-capabilities ul {
1035
1050
  grid-template-columns: 1fr;
@@ -1370,6 +1385,7 @@ body.kward-docs .kward-home #footer {
1370
1385
  flex-direction: column;
1371
1386
  gap: 2px;
1372
1387
  height: auto !important;
1388
+ justify-content: flex-start;
1373
1389
  left: 12px;
1374
1390
  max-height: calc(100vh - 100px);
1375
1391
  overflow-y: auto;
@@ -1810,6 +1826,12 @@ body.kward-docs #toc li > ol {
1810
1826
  margin-top: 6px;
1811
1827
  }
1812
1828
 
1829
+ body.kward-docs #content code,
1830
+ body.kward-docs #filecontents code,
1831
+ body.kward-docs #toc code {
1832
+ overflow-wrap: anywhere;
1833
+ }
1834
+
1813
1835
 
1814
1836
  /* Keep TOC hover states readable on the dark theme. */
1815
1837
  body.kward-docs #toc a:hover,
@@ -8,8 +8,10 @@ const guideLinks = {
8
8
  'doc/session-management.md': 'file.session-management.html',
9
9
  'doc/memory.md': 'file.memory.html',
10
10
  'doc/personas.md': 'file.personas.html',
11
+ 'doc/skills.md': 'file.skills.html',
11
12
  'doc/extensibility.md': 'file.extensibility.html',
12
13
  'doc/plugins.md': 'file.plugins.html',
14
+ 'doc/lifecycle-hooks.md': 'file.lifecycle-hooks.html',
13
15
  'doc/agent-tools.md': 'file.agent-tools.html',
14
16
  'doc/workspace-tools.md': 'file.workspace-tools.html',
15
17
  'doc/web-search.md': 'file.web-search.html',
@@ -18,7 +18,8 @@ module KwardDocsNavigationData
18
18
  ["Sessions", "file.session-management.html"],
19
19
  ["Tabs", "file.tabs.html"],
20
20
  ["Memory", "file.memory.html"],
21
- ["Personas", "file.personas.html"]
21
+ ["Personas", "file.personas.html"],
22
+ ["Skills", "file.skills.html"]
22
23
  ]
23
24
  ],
24
25
  [
@@ -37,7 +38,8 @@ module KwardDocsNavigationData
37
38
  "Customize",
38
39
  [
39
40
  ["Extensibility", "file.extensibility.html"],
40
- ["Plugins", "file.plugins.html"]
41
+ ["Plugins", "file.plugins.html"],
42
+ ["Lifecycle hooks", "file.lifecycle-hooks.html"]
41
43
  ]
42
44
  ],
43
45
  [
@@ -38,8 +38,10 @@ module KwardDocsNavigation
38
38
  "doc/shell.md" => "file.shell.html",
39
39
  "doc/memory.md" => "file.memory.html",
40
40
  "doc/personas.md" => "file.personas.html",
41
+ "doc/skills.md" => "file.skills.html",
41
42
  "doc/extensibility.md" => "file.extensibility.html",
42
43
  "doc/plugins.md" => "file.plugins.html",
44
+ "doc/lifecycle-hooks.md" => "file.lifecycle-hooks.html",
43
45
  "doc/agent-tools.md" => "file.agent-tools.html",
44
46
  "doc/workspace-tools.md" => "file.workspace-tools.html",
45
47
  "doc/context-budgeting.md" => "file.context-budgeting.html",
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.74.0
4
+ version: 0.75.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kai Wood
@@ -137,6 +137,7 @@ files:
137
137
  - doc/files.md
138
138
  - doc/getting-started.md
139
139
  - doc/git.md
140
+ - doc/lifecycle-hooks.md
140
141
  - doc/mcp.md
141
142
  - doc/memory.md
142
143
  - doc/personas.md
@@ -145,6 +146,7 @@ files:
145
146
  - doc/rpc.md
146
147
  - doc/session-management.md
147
148
  - doc/shell.md
149
+ - doc/skills.md
148
150
  - doc/tabs.md
149
151
  - doc/troubleshooting.md
150
152
  - doc/usage.md
@@ -159,6 +161,7 @@ files:
159
161
  - lib/kward/auth/anthropic_oauth.rb
160
162
  - lib/kward/auth/file.rb
161
163
  - lib/kward/auth/github_oauth.rb
164
+ - lib/kward/auth/oauth_helpers.rb
162
165
  - lib/kward/auth/openai_oauth.rb
163
166
  - lib/kward/auth/openrouter_api_key.rb
164
167
  - lib/kward/cancellation.rb
@@ -168,6 +171,7 @@ files:
168
171
  - lib/kward/cli/compaction.rb
169
172
  - lib/kward/cli/doctor.rb
170
173
  - lib/kward/cli/git.rb
174
+ - lib/kward/cli/hook_commands.rb
171
175
  - lib/kward/cli/interactive_turn.rb
172
176
  - lib/kward/cli/memory_commands.rb
173
177
  - lib/kward/cli/openrouter_commands.rb
@@ -185,14 +189,28 @@ files:
185
189
  - lib/kward/cli_transcript_formatter.rb
186
190
  - lib/kward/clipboard.rb
187
191
  - lib/kward/compaction/file_operation_tracker.rb
192
+ - lib/kward/compaction/token_estimator.rb
188
193
  - lib/kward/compactor.rb
189
194
  - lib/kward/config_files.rb
190
195
  - lib/kward/context_budget_meter.rb
191
196
  - lib/kward/conversation.rb
197
+ - lib/kward/deep_copy.rb
198
+ - lib/kward/diff_view_mode.rb
192
199
  - lib/kward/editor_mode.rb
193
200
  - lib/kward/ekwsh.rb
194
201
  - lib/kward/events.rb
195
202
  - lib/kward/export_path.rb
203
+ - lib/kward/frontmatter.rb
204
+ - lib/kward/hooks.rb
205
+ - lib/kward/hooks/audit_log.rb
206
+ - lib/kward/hooks/catalog.rb
207
+ - lib/kward/hooks/command_handler.rb
208
+ - lib/kward/hooks/config_loader.rb
209
+ - lib/kward/hooks/decision.rb
210
+ - lib/kward/hooks/event.rb
211
+ - lib/kward/hooks/http_handler.rb
212
+ - lib/kward/hooks/manager.rb
213
+ - lib/kward/hooks/matcher.rb
196
214
  - lib/kward/image_attachments.rb
197
215
  - lib/kward/interactive_pty_runner.rb
198
216
  - lib/kward/local_command_runner.rb
@@ -202,12 +220,14 @@ files:
202
220
  - lib/kward/mcp/server_config.rb
203
221
  - lib/kward/mcp/stdio_transport.rb
204
222
  - lib/kward/memory/manager.rb
223
+ - lib/kward/memory/turn_context.rb
205
224
  - lib/kward/message_access.rb
206
225
  - lib/kward/message_text.rb
207
226
  - lib/kward/model/chat_invocation.rb
208
227
  - lib/kward/model/client.rb
209
228
  - lib/kward/model/context_overflow.rb
210
229
  - lib/kward/model/context_usage.rb
230
+ - lib/kward/model/copilot_models.rb
211
231
  - lib/kward/model/model_info.rb
212
232
  - lib/kward/model/payloads.rb
213
233
  - lib/kward/model/retry_message.rb
@@ -215,6 +235,7 @@ files:
215
235
  - lib/kward/openrouter_model_cache.rb
216
236
  - lib/kward/pan/index.html.erb
217
237
  - lib/kward/pan/server.rb
238
+ - lib/kward/path_guard.rb
218
239
  - lib/kward/plugin_registry.rb
219
240
  - lib/kward/private_file.rb
220
241
  - lib/kward/project_files.rb
@@ -269,6 +290,7 @@ files:
269
290
  - lib/kward/rpc/attachment_normalizer.rb
270
291
  - lib/kward/rpc/auth_manager.rb
271
292
  - lib/kward/rpc/config_manager.rb
293
+ - lib/kward/rpc/mcp_status.rb
272
294
  - lib/kward/rpc/memory_methods.rb
273
295
  - lib/kward/rpc/prompt_bridge.rb
274
296
  - lib/kward/rpc/redactor.rb
@@ -282,6 +304,7 @@ files:
282
304
  - lib/kward/rpc/tool_metadata.rb
283
305
  - lib/kward/rpc/transcript_normalizer.rb
284
306
  - lib/kward/rpc/transport.rb
307
+ - lib/kward/rpc/turn_context.rb
285
308
  - lib/kward/scratchpad_runner.rb
286
309
  - lib/kward/session_diff.rb
287
310
  - lib/kward/session_naming.rb
@@ -323,6 +346,7 @@ files:
323
346
  - lib/kward/tools/web_search.rb
324
347
  - lib/kward/tools/write_file.rb
325
348
  - lib/kward/transcript_export.rb
349
+ - lib/kward/update_check.rb
326
350
  - lib/kward/version.rb
327
351
  - lib/kward/workers.rb
328
352
  - lib/kward/workers/git_guard.rb