kward 0.76.0 → 0.78.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 +62 -0
- data/Gemfile.lock +2 -2
- data/README.md +13 -4
- data/doc/agent-tools.md +0 -3
- data/doc/api.md +4 -0
- data/doc/code-search.md +9 -6
- data/doc/composer.md +219 -0
- data/doc/configuration.md +114 -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/permissions.md +179 -0
- data/doc/plugins.md +58 -0
- data/doc/rpc.md +61 -22
- data/doc/security.md +184 -0
- data/doc/tabs.md +4 -3
- data/doc/usage.md +15 -32
- data/doc/web-search.md +4 -2
- data/lib/kward/ansi.rb +2 -2
- data/lib/kward/auth/anthropic_oauth.rb +2 -2
- data/lib/kward/auth/github_oauth.rb +3 -3
- data/lib/kward/auth/oauth_helpers.rb +4 -2
- data/lib/kward/auth/openai_oauth.rb +2 -1
- 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 +24 -7
- data/lib/kward/cli/prompt_interface.rb +1 -0
- data/lib/kward/cli/rendering.rb +7 -1
- data/lib/kward/cli/runtime_helpers.rb +11 -35
- 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 +154 -36
- data/lib/kward/cli.rb +30 -26
- data/lib/kward/compactor.rb +3 -3
- data/lib/kward/config_files.rb +28 -0
- data/lib/kward/ekwsh.rb +0 -4
- data/lib/kward/hooks/catalog.rb +0 -5
- data/lib/kward/hooks/http_handler.rb +2 -1
- data/lib/kward/http.rb +18 -0
- data/lib/kward/image_attachments.rb +1 -1
- data/lib/kward/model/client.rb +33 -11
- data/lib/kward/model/payloads.rb +6 -1
- data/lib/kward/openrouter_model_cache.rb +2 -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/permissions/policy.rb +171 -0
- data/lib/kward/plugin_registry.rb +71 -1
- data/lib/kward/prompt_interface/approval_prompt.rb +62 -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/question_prompt.rb +12 -3
- 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 +20 -1
- data/lib/kward/prompts/commands.rb +1 -3
- data/lib/kward/prompts.rb +20 -0
- data/lib/kward/rpc/plugin_chat_manager.rb +299 -0
- data/lib/kward/rpc/server.rb +48 -41
- data/lib/kward/rpc/session_manager.rb +76 -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 +18 -5
- data/lib/kward/session_store.rb +55 -16
- data/lib/kward/skills/registry.rb +21 -1
- data/lib/kward/starter_pack_installer.rb +3 -1
- data/lib/kward/tab_driver.rb +87 -0
- data/lib/kward/tab_store.rb +74 -12
- data/lib/kward/tools/base.rb +14 -0
- data/lib/kward/tools/code_search.rb +8 -2
- data/lib/kward/tools/fetch_content.rb +4 -2
- data/lib/kward/tools/fetch_raw.rb +3 -1
- data/lib/kward/tools/registry.rb +45 -17
- data/lib/kward/tools/search/code.rb +47 -13
- data/lib/kward/tools/search/web.rb +61 -18
- data/lib/kward/tools/search/web_fetch.rb +206 -38
- data/lib/kward/tools/tool_call.rb +0 -4
- data/lib/kward/tools/web_search.rb +3 -1
- data/lib/kward/transcript_export.rb +1 -1
- data/lib/kward/update_check.rb +2 -1
- data/lib/kward/version.rb +1 -1
- data/templates/default/kward_navigation.rb +7 -2
- data/templates/default/layout/html/layout.erb +2 -2
- data/templates/default/layout/html/setup.rb +1 -0
- metadata +11 -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/hooks/catalog.rb
CHANGED
|
@@ -45,11 +45,6 @@ module Kward
|
|
|
45
45
|
EventDefinition.new(name: "git_stage_after", failure_policy: "warn"),
|
|
46
46
|
EventDefinition.new(name: "git_commit_before", failure_policy: "deny"),
|
|
47
47
|
EventDefinition.new(name: "git_commit_after", failure_policy: "warn"),
|
|
48
|
-
EventDefinition.new(name: "worker_job_create", failure_policy: "warn"),
|
|
49
|
-
EventDefinition.new(name: "worker_job_start_before", failure_policy: "warn"),
|
|
50
|
-
EventDefinition.new(name: "worker_job_start_after", failure_policy: "warn"),
|
|
51
|
-
EventDefinition.new(name: "worker_job_ready_for_review", failure_policy: "warn"),
|
|
52
|
-
EventDefinition.new(name: "worker_job_failed", failure_policy: "warn")
|
|
53
48
|
].each_with_object({}) { |definition, result| result[definition.name] = definition }.freeze
|
|
54
49
|
|
|
55
50
|
module_function
|
|
@@ -3,6 +3,7 @@ require "net/http"
|
|
|
3
3
|
require "uri"
|
|
4
4
|
require_relative "catalog"
|
|
5
5
|
require_relative "decision"
|
|
6
|
+
require_relative "../http"
|
|
6
7
|
|
|
7
8
|
# Namespace for the Kward CLI agent runtime.
|
|
8
9
|
module Kward
|
|
@@ -33,7 +34,7 @@ module Kward
|
|
|
33
34
|
private
|
|
34
35
|
|
|
35
36
|
def post(body)
|
|
36
|
-
request = Net::HTTP::Post.new(@uri)
|
|
37
|
+
request = Http.apply_user_agent(Net::HTTP::Post.new(@uri))
|
|
37
38
|
request["Content-Type"] = "application/json"
|
|
38
39
|
request["Accept"] = "application/json"
|
|
39
40
|
@headers.each { |key, value| request[key] = value }
|
data/lib/kward/http.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative "version"
|
|
2
|
+
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
|
+
module Kward
|
|
5
|
+
# Shared HTTP request headers for Kward-owned network traffic.
|
|
6
|
+
module Http
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def user_agent
|
|
10
|
+
"Kward/#{VERSION}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def apply_user_agent(request)
|
|
14
|
+
request["User-Agent"] = user_agent
|
|
15
|
+
request
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/kward/model/client.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "../auth/github_oauth"
|
|
|
6
6
|
require_relative "../auth/openai_oauth"
|
|
7
7
|
require_relative "../cancellation"
|
|
8
8
|
require_relative "../config_files"
|
|
9
|
+
require_relative "../http"
|
|
9
10
|
require_relative "../openrouter_model_cache"
|
|
10
11
|
require_relative "context_overflow"
|
|
11
12
|
require_relative "copilot_models"
|
|
@@ -35,6 +36,7 @@ module Kward
|
|
|
35
36
|
DEFAULT_OPENAI_MODEL = ModelInfo::DEFAULT_OPENAI_MODEL
|
|
36
37
|
DEFAULT_REASONING_EFFORT = ModelInfo::DEFAULT_REASONING_EFFORT
|
|
37
38
|
RETRY_DELAYS = [1, 2].freeze
|
|
39
|
+
DEFAULT_STREAM_IDLE_TIMEOUT_SECONDS = 120
|
|
38
40
|
NON_RETRYABLE_PROVIDER_LIMIT_PATTERNS = [
|
|
39
41
|
/GoUsageLimitError/i,
|
|
40
42
|
/FreeUsageLimitError/i,
|
|
@@ -335,7 +337,7 @@ module Kward
|
|
|
335
337
|
end
|
|
336
338
|
|
|
337
339
|
def chat_anthropic_provider(url:, token:, request_body:, current_model:, on_reasoning_delta:, on_assistant_delta:, cancellation:)
|
|
338
|
-
request = Net::HTTP::Post.new(url)
|
|
340
|
+
request = Http.apply_user_agent(Net::HTTP::Post.new(url))
|
|
339
341
|
request["Authorization"] = "Bearer #{token}"
|
|
340
342
|
request["Content-Type"] = "application/json"
|
|
341
343
|
request["Accept"] = "text/event-stream"
|
|
@@ -343,7 +345,7 @@ module Kward
|
|
|
343
345
|
request.body = request_body
|
|
344
346
|
|
|
345
347
|
message = nil
|
|
346
|
-
Net::HTTP.start(url.hostname, url.port, use_ssl: true, read_timeout:
|
|
348
|
+
Net::HTTP.start(url.hostname, url.port, use_ssl: true, read_timeout: stream_idle_timeout_seconds) do |http|
|
|
347
349
|
cancellation&.on_cancel { close_http(http) }
|
|
348
350
|
cancellation&.raise_if_cancelled!
|
|
349
351
|
http.request(request) do |response|
|
|
@@ -361,7 +363,7 @@ module Kward
|
|
|
361
363
|
end
|
|
362
364
|
|
|
363
365
|
def chat_openrouter_provider(url:, token:, request_body:, current_model:, on_assistant_delta:, cancellation:)
|
|
364
|
-
request = Net::HTTP::Post.new(url)
|
|
366
|
+
request = Http.apply_user_agent(Net::HTTP::Post.new(url))
|
|
365
367
|
request["Authorization"] = "Bearer #{token}"
|
|
366
368
|
request["Content-Type"] = "application/json"
|
|
367
369
|
request.body = request_body
|
|
@@ -547,7 +549,7 @@ module Kward
|
|
|
547
549
|
return [] if token.empty?
|
|
548
550
|
|
|
549
551
|
url = URI("#{@github_oauth.base_url}/models")
|
|
550
|
-
request = Net::HTTP::Get.new(url)
|
|
552
|
+
request = Http.apply_user_agent(Net::HTTP::Get.new(url))
|
|
551
553
|
request["Authorization"] = "Bearer #{token}"
|
|
552
554
|
request["Accept"] = "application/json"
|
|
553
555
|
copilot_headers([]).each { |key, value| request[key] = value }
|
|
@@ -577,7 +579,7 @@ module Kward
|
|
|
577
579
|
|
|
578
580
|
def copilot_responses_chat(token, request_body:, on_assistant_delta: nil, cancellation: nil)
|
|
579
581
|
url = URI("#{@github_oauth.base_url}/responses")
|
|
580
|
-
request = Net::HTTP::Post.new(url)
|
|
582
|
+
request = Http.apply_user_agent(Net::HTTP::Post.new(url))
|
|
581
583
|
request["Authorization"] = "Bearer #{token}"
|
|
582
584
|
request["Content-Type"] = "application/json"
|
|
583
585
|
request["Accept"] = "text/event-stream"
|
|
@@ -585,7 +587,7 @@ module Kward
|
|
|
585
587
|
request.body = request_body
|
|
586
588
|
|
|
587
589
|
message = nil
|
|
588
|
-
Net::HTTP.start(url.hostname, url.port, use_ssl: true, read_timeout:
|
|
590
|
+
Net::HTTP.start(url.hostname, url.port, use_ssl: true, read_timeout: stream_idle_timeout_seconds) do |http|
|
|
589
591
|
cancellation&.on_cancel { close_http(http) }
|
|
590
592
|
cancellation&.raise_if_cancelled!
|
|
591
593
|
http.request(request) do |response|
|
|
@@ -603,7 +605,7 @@ module Kward
|
|
|
603
605
|
end
|
|
604
606
|
|
|
605
607
|
def copilot_chat(url, token, messages, tools, request_body: nil, on_assistant_delta: nil, cancellation: nil)
|
|
606
|
-
request = Net::HTTP::Post.new(url)
|
|
608
|
+
request = Http.apply_user_agent(Net::HTTP::Post.new(url))
|
|
607
609
|
request["Authorization"] = "Bearer #{token}"
|
|
608
610
|
request["Content-Type"] = "application/json"
|
|
609
611
|
request["Accept"] = "text/event-stream"
|
|
@@ -633,7 +635,6 @@ module Kward
|
|
|
633
635
|
"anthropic-version" => "2023-06-01",
|
|
634
636
|
"anthropic-beta" => "claude-code-20250219,oauth-2025-04-20",
|
|
635
637
|
"anthropic-dangerous-direct-browser-access" => "true",
|
|
636
|
-
"user-agent" => "claude-cli/2.1.75",
|
|
637
638
|
"x-app" => "cli"
|
|
638
639
|
}
|
|
639
640
|
end
|
|
@@ -653,16 +654,16 @@ module Kward
|
|
|
653
654
|
end
|
|
654
655
|
|
|
655
656
|
def codex_chat(url, token, account_id, messages, tools, request_body: nil, on_reasoning_delta: nil, on_reasoning_boundary: nil, on_assistant_delta: nil, cancellation: nil, max_tokens: nil)
|
|
656
|
-
request = Net::HTTP::Post.new(url)
|
|
657
|
+
request = Http.apply_user_agent(Net::HTTP::Post.new(url))
|
|
657
658
|
request["Authorization"] = "Bearer #{token}"
|
|
658
659
|
request["ChatGPT-Account-Id"] = account_id if account_id
|
|
659
660
|
request["Content-Type"] = "application/json"
|
|
660
661
|
request["Accept"] = "text/event-stream"
|
|
661
|
-
request["originator"] = "codex_cli_rs"
|
|
662
662
|
request.body = request_body || JSON.dump(codex_payload(messages, tools, max_tokens: max_tokens))
|
|
663
|
+
apply_codex_identity(request, luna: luna_request?(request.body))
|
|
663
664
|
|
|
664
665
|
message = nil
|
|
665
|
-
Net::HTTP.start(url.hostname, url.port, use_ssl: true, read_timeout:
|
|
666
|
+
Net::HTTP.start(url.hostname, url.port, use_ssl: true, read_timeout: stream_idle_timeout_seconds) do |http|
|
|
666
667
|
cancellation&.on_cancel { close_http(http) }
|
|
667
668
|
cancellation&.raise_if_cancelled!
|
|
668
669
|
http.request(request) do |response|
|
|
@@ -684,6 +685,22 @@ module Kward
|
|
|
684
685
|
raise e
|
|
685
686
|
end
|
|
686
687
|
|
|
688
|
+
def apply_codex_identity(request, luna:)
|
|
689
|
+
request["originator"] = "kward"
|
|
690
|
+
return unless luna
|
|
691
|
+
|
|
692
|
+
# TODO: Remove this Luna-specific Responses Lite workaround when Codex accepts Kward's own client identity.
|
|
693
|
+
request["originator"] = "codex_cli_rs"
|
|
694
|
+
request["User-Agent"] = "codex_cli_rs/0.144.1"
|
|
695
|
+
request["x-openai-internal-codex-responses-lite"] = "true"
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def luna_request?(request_body)
|
|
699
|
+
JSON.parse(request_body)["model"] == "gpt-5.6-luna"
|
|
700
|
+
rescue JSON::ParserError
|
|
701
|
+
false
|
|
702
|
+
end
|
|
703
|
+
|
|
687
704
|
def parse_codex_sse(body, on_reasoning_delta: nil, on_reasoning_boundary: nil, on_assistant_delta: nil)
|
|
688
705
|
ModelStreamParser.parse_codex_sse(body, on_reasoning_delta: on_reasoning_delta, on_reasoning_boundary: on_reasoning_boundary, on_assistant_delta: on_assistant_delta, show_raw_reasoning: codex_show_raw_reasoning?, usage_normalizer: method(:normalized_usage), request_error_class: RequestError)
|
|
689
706
|
end
|
|
@@ -842,6 +859,11 @@ module Kward
|
|
|
842
859
|
@config["codex_show_raw_reasoning"] == true
|
|
843
860
|
end
|
|
844
861
|
|
|
862
|
+
def stream_idle_timeout_seconds
|
|
863
|
+
value = @config["stream_idle_timeout_seconds"].to_i
|
|
864
|
+
value.positive? ? value : DEFAULT_STREAM_IDLE_TIMEOUT_SECONDS
|
|
865
|
+
end
|
|
866
|
+
|
|
845
867
|
def load_config
|
|
846
868
|
ConfigFiles.read_config(@config_path)
|
|
847
869
|
end
|
data/lib/kward/model/payloads.rb
CHANGED
|
@@ -126,7 +126,12 @@ module Kward
|
|
|
126
126
|
store: false,
|
|
127
127
|
include: []
|
|
128
128
|
}
|
|
129
|
-
|
|
129
|
+
unless reasoning == false
|
|
130
|
+
effort = reasoning || reasoning_effort("Codex")
|
|
131
|
+
payload[:reasoning] = { effort: effort, summary: "auto" }
|
|
132
|
+
# TODO: Remove this Luna-specific Responses Lite workaround when Codex accepts Kward's own client identity.
|
|
133
|
+
payload[:reasoning][:context] = "all_turns" if parts[:model] == "gpt-5.6-luna"
|
|
134
|
+
end
|
|
130
135
|
payload
|
|
131
136
|
end
|
|
132
137
|
|
|
@@ -4,6 +4,7 @@ require "net/http"
|
|
|
4
4
|
require "time"
|
|
5
5
|
require "uri"
|
|
6
6
|
require_relative "config_files"
|
|
7
|
+
require_relative "http"
|
|
7
8
|
require_relative "private_file"
|
|
8
9
|
|
|
9
10
|
# Namespace for the Kward CLI agent runtime.
|
|
@@ -71,7 +72,7 @@ module Kward
|
|
|
71
72
|
private
|
|
72
73
|
|
|
73
74
|
def refresh_request
|
|
74
|
-
Net::HTTP::Get.new(MODELS_URL).tap do |request|
|
|
75
|
+
Http.apply_user_agent(Net::HTTP::Get.new(MODELS_URL)).tap do |request|
|
|
75
76
|
request["Authorization"] = "Bearer #{@api_key}"
|
|
76
77
|
request["Accept"] = "application/json"
|
|
77
78
|
end
|