kward 0.84.0 → 0.85.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/CHANGELOG.md +34 -0
- data/Gemfile.lock +2 -2
- data/README.md +2 -2
- data/doc/agent-tools.md +13 -1
- data/doc/api.md +17 -2
- data/doc/composer.md +1 -1
- data/doc/configuration.md +27 -4
- data/doc/extensibility.md +2 -1
- data/doc/git.md +3 -1
- data/doc/pan.md +6 -0
- data/doc/permissions.md +4 -4
- data/doc/plugins.md +464 -15
- data/doc/rpc.md +154 -16
- data/doc/sandboxing.md +11 -5
- data/doc/security.md +7 -1
- data/doc/session-management.md +5 -4
- data/doc/tabs.md +6 -2
- data/doc/transports.md +15 -0
- data/doc/usage.md +4 -1
- data/doc/workspace-tools.md +9 -0
- data/examples/plugins/space_invaders.rb +1 -1
- data/examples/plugins/stardate_footer.rb +2 -2
- data/examples/plugins/telegram/plugin.rb +1 -1
- data/lib/kward/agent.rb +24 -11
- data/lib/kward/cli/compaction.rb +9 -3
- data/lib/kward/cli/plugins.rb +81 -12
- data/lib/kward/cli/prompt_interface.rb +25 -5
- data/lib/kward/cli/rendering.rb +3 -0
- data/lib/kward/cli/runtime_helpers.rb +121 -27
- data/lib/kward/cli/sessions.rb +9 -5
- data/lib/kward/cli/settings/menus.rb +745 -0
- data/lib/kward/cli/settings/model.rb +327 -0
- data/lib/kward/cli/settings.rb +6 -1055
- data/lib/kward/cli/slash_commands.rb +45 -4
- data/lib/kward/cli/tabs.rb +167 -27
- data/lib/kward/{cli_transcript_formatter.rb → cli/transcript_formatter.rb} +3 -3
- data/lib/kward/cli/worktrees.rb +65 -2
- data/lib/kward/cli.rb +26 -24
- data/lib/kward/compactor.rb +18 -7
- data/lib/kward/config/core.rb +389 -0
- data/lib/kward/config/extensions.rb +96 -0
- data/lib/kward/config/prompts.rb +313 -0
- data/lib/kward/config/settings.rb +250 -0
- data/lib/kward/config_files.rb +14 -1008
- data/lib/kward/conversation.rb +31 -2
- data/lib/kward/image_attachments.rb +1 -1
- data/lib/kward/model/client.rb +2 -2
- data/lib/kward/model/copilot_models.rb +2 -2
- data/lib/kward/model/model_info.rb +20 -3
- data/lib/kward/{openrouter_model_cache.rb → model/openrouter_model_cache.rb} +3 -3
- data/lib/kward/model/payloads.rb +12 -3
- data/lib/kward/model/typesafe_client.rb +78 -0
- data/lib/kward/pan/server.rb +10 -7
- data/lib/kward/permissions/policy.rb +6 -2
- data/lib/kward/plugin_registry.rb +2 -659
- data/lib/kward/plugins/actions.rb +453 -0
- data/lib/kward/plugins/chat_contract.rb +121 -0
- data/lib/kward/{plugin_chat_runtime.rb → plugins/chat_runtime.rb} +62 -18
- data/lib/kward/plugins/host.rb +232 -0
- data/lib/kward/plugins/registry.rb +1190 -0
- data/lib/kward/plugins/resources.rb +206 -0
- data/lib/kward/plugins/turn_request.rb +36 -0
- data/lib/kward/plugins/ui.rb +219 -0
- data/lib/kward/prompt_interface/composer_renderer.rb +1 -1
- data/lib/kward/prompt_interface/composer_state.rb +1 -1
- data/lib/kward/prompt_interface/editor/controller.rb +1 -1
- data/lib/kward/prompt_interface/editor/runner.rb +1 -1
- data/lib/kward/prompt_interface/editor/state.rb +1 -1
- data/lib/kward/prompt_interface/layout.rb +1 -1
- data/lib/kward/prompt_interface/overlay_renderer.rb +1 -1
- data/lib/kward/prompt_interface/plugin_ui_requests.rb +82 -0
- data/lib/kward/prompt_interface/runtime_state.rb +6 -1
- data/lib/kward/prompt_interface/screen.rb +9 -2
- data/lib/kward/prompt_interface.rb +60 -11
- data/lib/kward/prompts/commands.rb +2 -1
- data/lib/kward/{adaptive_pty_output_sink.rb → pty/adaptive_output_sink.rb} +1 -1
- data/lib/kward/{interactive_pty_runner.rb → pty/interactive_runner.rb} +2 -2
- data/lib/kward/{local_command_runner.rb → pty/local_command_runner.rb} +1 -1
- data/lib/kward/{local_pty_command_runner.rb → pty/local_pty_runner.rb} +3 -9
- data/lib/kward/{pty_output_sink.rb → pty/output_sink.rb} +9 -4
- data/lib/kward/{pty_transcript_normalizer.rb → pty/transcript_normalizer.rb} +1 -1
- data/lib/kward/rpc/plugin_chat_manager.rb +30 -10
- data/lib/kward/rpc/prompt_bridge.rb +25 -0
- data/lib/kward/rpc/server.rb +91 -12
- data/lib/kward/rpc/session_manager.rb +147 -44
- data/lib/kward/rpc/session_tree_rows.rb +2 -2
- data/lib/kward/rpc/tool_metadata.rb +1 -1
- data/lib/kward/sandbox/command_runner.rb +1 -1
- data/lib/kward/{session_catalog.rb → sessions/catalog.rb} +1 -1
- data/lib/kward/{session_store.rb → sessions/store.rb} +9 -9
- data/lib/kward/{session_tree_nodes.rb → sessions/tree_nodes.rb} +3 -3
- data/lib/kward/{session_tree_renderer.rb → sessions/tree_renderer.rb} +4 -4
- data/lib/kward/{session_tree_tool_display.rb → sessions/tree_tool_display.rb} +1 -1
- data/lib/kward/{kwsh.rb → shell/kwsh.rb} +3 -3
- data/lib/kward/{persistent_shell_session.rb → shell/persistent_session.rb} +3 -3
- data/lib/kward/{shell_prompt_session.rb → shell/prompt_session.rb} +1 -1
- data/lib/kward/skills/trust_store.rb +1 -1
- data/lib/kward/tabs/driver.rb +194 -0
- data/lib/kward/{tab_store.rb → tabs/store.rb} +2 -2
- data/lib/kward/{clipboard.rb → terminal/clipboard.rb} +1 -1
- data/lib/kward/{terminal_image_support.rb → terminal/image_support.rb} +1 -1
- data/lib/kward/tools/base.rb +18 -0
- data/lib/kward/tools/context_for_task.rb +15 -6
- data/lib/kward/tools/edit_file.rb +9 -6
- data/lib/kward/tools/git_commit.rb +13 -7
- data/lib/kward/tools/list_directory.rb +4 -4
- data/lib/kward/tools/plugin_tool.rb +41 -0
- data/lib/kward/tools/prepare_shell_command.rb +1 -1
- data/lib/kward/tools/read_file.rb +7 -6
- data/lib/kward/tools/registry.rb +96 -13
- data/lib/kward/tools/run_shell_command.rb +10 -8
- data/lib/kward/tools/search/code.rb +1 -1
- data/lib/kward/tools/summarize_file_structure.rb +5 -5
- data/lib/kward/tools/tool_call.rb +2 -1
- data/lib/kward/tools/typesafe_evaluate.rb +81 -0
- data/lib/kward/tools/workspace_targets.rb +58 -0
- data/lib/kward/tools/write_file.rb +9 -6
- data/lib/kward/{export_path.rb → transcripts/export_path.rb} +1 -1
- data/lib/kward/{markdown_transcript.rb → transcripts/markdown_transcript.rb} +2 -2
- data/lib/kward/transport/contracts.rb +200 -0
- data/lib/kward/transport/gateway.rb +79 -35
- data/lib/kward/transport/plugin_chat_gateway.rb +3 -2
- data/lib/kward/transport.rb +1 -200
- data/lib/kward/version.rb +1 -1
- data/lib/kward/{workspace_factory.rb → workspace/factory.rb} +2 -2
- data/lib/kward/{git_worktree_manager.rb → workspace/git_worktree_manager.rb} +28 -0
- data/lib/kward/{workspace.rb → workspace/workspace.rb} +3 -3
- data/templates/default/fulldoc/html/css/kward.css +125 -0
- data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
- data/templates/default/fulldoc/html/setup.rb +1 -1
- data/templates/default/layout/html/layout.erb +16 -4
- metadata +67 -48
- data/lib/kward/tab_driver.rb +0 -90
- data/templates/default/fulldoc/html/images/kward_workflow.svg +0 -52
- /data/lib/kward/{editor_prompt.rb → cli/editor_prompt.rb} +0 -0
- /data/lib/kward/{editor_prompt_session.rb → cli/editor_prompt_session.rb} +0 -0
- /data/lib/kward/{diff_view_mode.rb → prompt_interface/editor/diff_view_mode.rb} +0 -0
- /data/lib/kward/{editor_mode.rb → prompt_interface/editor/editor_mode.rb} +0 -0
- /data/lib/kward/{markdown_code_block.rb → prompt_interface/editor/markdown_code_block.rb} +0 -0
- /data/lib/kward/{scratchpad_languages.rb → prompt_interface/editor/scratchpad_languages.rb} +0 -0
- /data/lib/kward/{scratchpad_runner.rb → prompt_interface/editor/scratchpad_runner.rb} +0 -0
- /data/lib/kward/{detached_run.rb → pty/detached_run.rb} +0 -0
- /data/lib/kward/{session_diff.rb → sessions/diff.rb} +0 -0
- /data/lib/kward/{session_naming.rb → sessions/naming.rb} +0 -0
- /data/lib/kward/{session_trash.rb → sessions/trash.rb} +0 -0
- /data/lib/kward/{kwshrc.rb → shell/kwshrc.rb} +0 -0
- /data/lib/kward/{shell_prompt.rb → shell/prompt.rb} +0 -0
- /data/lib/kward/{ansi.rb → terminal/ansi.rb} +0 -0
- /data/lib/kward/{terminal_keys.rb → terminal/keys.rb} +0 -0
- /data/lib/kward/{terminal_sequences.rb → terminal/sequences.rb} +0 -0
- /data/lib/kward/{terminal_text.rb → terminal/text.rb} +0 -0
- /data/lib/kward/{transcript_export.rb → transcripts/transcript_export.rb} +0 -0
- /data/lib/kward/{project_files.rb → workspace/files.rb} +0 -0
- /data/lib/kward/{path_guard.rb → workspace/path_guard.rb} +0 -0
data/lib/kward/conversation.rb
CHANGED
|
@@ -3,7 +3,7 @@ require "set"
|
|
|
3
3
|
require_relative "context_budget_meter"
|
|
4
4
|
require_relative "image_attachments"
|
|
5
5
|
require_relative "message_access"
|
|
6
|
-
require_relative "
|
|
6
|
+
require_relative "plugins/registry"
|
|
7
7
|
require_relative "prompts"
|
|
8
8
|
|
|
9
9
|
# Namespace for the Kward CLI agent runtime.
|
|
@@ -176,9 +176,38 @@ module Kward
|
|
|
176
176
|
"toolout_#{digest}"
|
|
177
177
|
end
|
|
178
178
|
|
|
179
|
+
# Scopes plugin instructions to one host-owned turn, including retries.
|
|
180
|
+
# The overlay is deliberately not persisted or folded into the base prompt.
|
|
181
|
+
def with_system_turn(request)
|
|
182
|
+
previous = @system_turn
|
|
183
|
+
raise ArgumentError, "A system-instruction turn is already active" if previous
|
|
184
|
+
|
|
185
|
+
@system_turn = request
|
|
186
|
+
yield
|
|
187
|
+
ensure
|
|
188
|
+
@system_turn = previous
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def append_system_turn(request)
|
|
192
|
+
append_message(request.history_message)
|
|
193
|
+
end
|
|
194
|
+
|
|
179
195
|
# @return [Array<Hash>] provider request context: current system prompt plus durable transcript
|
|
180
196
|
def context_messages
|
|
181
|
-
|
|
197
|
+
instruction = { role: "system", content: @system_turn.system, system_turn: true } if @system_turn
|
|
198
|
+
inserted = false
|
|
199
|
+
messages = @messages.filter_map do |message|
|
|
200
|
+
record = message[:plugin_system_turn] || message["plugin_system_turn"]
|
|
201
|
+
next message unless record
|
|
202
|
+
next unless @system_turn && (record[:id] || record["id"]) == @system_turn.id
|
|
203
|
+
|
|
204
|
+
inserted = true
|
|
205
|
+
instruction
|
|
206
|
+
end
|
|
207
|
+
# Compaction can remove the history marker; reissue the current directive
|
|
208
|
+
# after the compacted context rather than demoting it to the base prompt.
|
|
209
|
+
messages << instruction if instruction && !inserted
|
|
210
|
+
@system_message ? [@system_message] + messages : messages
|
|
182
211
|
end
|
|
183
212
|
|
|
184
213
|
# Rebuilds the system message from current config, memory, plugins, and
|
data/lib/kward/model/client.rb
CHANGED
|
@@ -8,7 +8,7 @@ require_relative "../auth/openai_oauth"
|
|
|
8
8
|
require_relative "../cancellation"
|
|
9
9
|
require_relative "../config_files"
|
|
10
10
|
require_relative "../http"
|
|
11
|
-
require_relative "
|
|
11
|
+
require_relative "openrouter_model_cache"
|
|
12
12
|
require_relative "context_overflow"
|
|
13
13
|
require_relative "azure_openai_config"
|
|
14
14
|
require_relative "copilot_models"
|
|
@@ -712,7 +712,7 @@ module Kward
|
|
|
712
712
|
end
|
|
713
713
|
|
|
714
714
|
def copilot_responses_model?(model)
|
|
715
|
-
model.to_s.match?(/\Agpt-5(?:\.|-|\z)/)
|
|
715
|
+
model.to_s.match?(/\Agpt-(?:5|6)(?:\.|-|\z)/)
|
|
716
716
|
end
|
|
717
717
|
|
|
718
718
|
def model_entry(provider, id, current: false, metadata: nil)
|
|
@@ -14,7 +14,7 @@ module Kward
|
|
|
14
14
|
|
|
15
15
|
def supported?(model)
|
|
16
16
|
text = model.to_s
|
|
17
|
-
text.match?(/\Agpt-5(?:\.|-|\z)/) || text.match?(/\A(?:gemini-|gpt-4\.1|oswe-)/)
|
|
17
|
+
text.match?(/\Agpt-(?:5|6)(?:\.|-|\z)/) || text.match?(/\A(?:gemini-|gpt-4\.1|oswe-)/)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def supported_choices(choices)
|
|
@@ -24,7 +24,7 @@ module Kward
|
|
|
24
24
|
def resolved_chat_model(configured_model, choices)
|
|
25
25
|
return configured_model if choices.empty? || choices.include?(configured_model)
|
|
26
26
|
|
|
27
|
-
choices.find { |model| supported?(model) } || raise("No Copilot models supported by Kward are available for this account. Kward currently supports Copilot GPT-5 Responses and Gemini/GPT-4.1 chat models.")
|
|
27
|
+
choices.find { |model| supported?(model) } || raise("No Copilot models supported by Kward are available for this account. Kward currently supports Copilot GPT-5/GPT-6 Responses and Gemini/GPT-4.1 chat models.")
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def catalog_entries(body)
|
|
@@ -9,7 +9,7 @@ module Kward
|
|
|
9
9
|
DEFAULT_COPILOT_MODEL = "gpt-5-mini"
|
|
10
10
|
DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-5"
|
|
11
11
|
DEFAULT_REASONING_EFFORT = "medium"
|
|
12
|
-
OPENAI_MODEL_CHOICES = %w[gpt-5.6-sol gpt-5.6-terra gpt-5.6-luna gpt-5.5 gpt-5.4 gpt-5.4-mini gpt-5.3-codex-spark].freeze
|
|
12
|
+
OPENAI_MODEL_CHOICES = %w[gpt-6-astra gpt-6.1-sol gpt-6-luna gpt-5.6-sol gpt-5.6-terra gpt-5.6-luna gpt-5.5 gpt-5.4 gpt-5.4-mini gpt-5.3-codex-spark].freeze
|
|
13
13
|
ANTHROPIC_MODEL_CHOICES = %w[
|
|
14
14
|
claude-fable-5
|
|
15
15
|
claude-opus-5
|
|
@@ -24,6 +24,7 @@ module Kward
|
|
|
24
24
|
].freeze
|
|
25
25
|
COPILOT_MODEL_CHOICES = %w[
|
|
26
26
|
gpt-5-mini
|
|
27
|
+
gpt-6-astra
|
|
27
28
|
gpt-5.3-codex
|
|
28
29
|
gpt-5.4
|
|
29
30
|
gpt-5.4-mini
|
|
@@ -54,6 +55,14 @@ module Kward
|
|
|
54
55
|
*OPENAI_REASONING_EFFORT_CHOICES,
|
|
55
56
|
["max", "Max"]
|
|
56
57
|
].freeze
|
|
58
|
+
GPT_6_REASONING_EFFORT_CHOICES = [
|
|
59
|
+
*REASONING_EFFORT_CHOICES,
|
|
60
|
+
["max", "Max"]
|
|
61
|
+
].freeze
|
|
62
|
+
GPT_6_LUNA_REASONING_EFFORT_CHOICES = [
|
|
63
|
+
*OPENAI_REASONING_EFFORT_CHOICES,
|
|
64
|
+
["max", "Max"]
|
|
65
|
+
].freeze
|
|
57
66
|
ANTHROPIC_HIGH_REASONING_EFFORT_CHOICES = [
|
|
58
67
|
["low", "Low"],
|
|
59
68
|
["medium", "Medium"],
|
|
@@ -78,6 +87,7 @@ module Kward
|
|
|
78
87
|
].freeze
|
|
79
88
|
|
|
80
89
|
CODEX_CONTEXT_WINDOWS = [
|
|
90
|
+
[/\Agpt-6(?:\.1)?-(?:astra|sol|luna)(?:\z|-)/, 1_050_000],
|
|
81
91
|
[/\Agpt-5\.6/, 1_050_000],
|
|
82
92
|
[/\Agpt-5\.5/, 400_000],
|
|
83
93
|
[/\Agpt-5\.4-mini/, 400_000],
|
|
@@ -96,6 +106,7 @@ module Kward
|
|
|
96
106
|
[/\Agpt-3\.5-turbo/, 16_385]
|
|
97
107
|
].freeze
|
|
98
108
|
OPENAI_CONTEXT_WINDOWS = [
|
|
109
|
+
[/\Agpt-6(?:\.1)?-(?:astra|sol|luna)(?:\z|-)/, 1_050_000],
|
|
99
110
|
[/\Agpt-5\.6/, 1_050_000],
|
|
100
111
|
[/\Agpt-5\.5/, 1_050_000],
|
|
101
112
|
[/\Agpt-5\.4-mini/, 400_000],
|
|
@@ -423,10 +434,12 @@ module Kward
|
|
|
423
434
|
case provider
|
|
424
435
|
when "Codex", "OpenRouter"
|
|
425
436
|
true
|
|
437
|
+
when "OpenAI"
|
|
438
|
+
id.to_s.match?(/\Agpt-6(?:\.1)?-(?:astra|sol|luna)(?:\z|-)/)
|
|
426
439
|
when "Anthropic"
|
|
427
440
|
!reasoning_effort_choices(provider, id).empty?
|
|
428
441
|
when "Copilot"
|
|
429
|
-
id.to_s.match?(/\Agpt-5(?:\.|-|\z)/)
|
|
442
|
+
id.to_s.match?(/\Agpt-(?:5|6)(?:\.|-|\z)/)
|
|
430
443
|
else
|
|
431
444
|
false
|
|
432
445
|
end
|
|
@@ -436,10 +449,12 @@ module Kward
|
|
|
436
449
|
case provider
|
|
437
450
|
when "Codex", "OpenRouter"
|
|
438
451
|
openai_reasoning_effort_choices(id)
|
|
452
|
+
when "OpenAI"
|
|
453
|
+
reasoning_supported?(provider, id) ? openai_reasoning_effort_choices(id) : []
|
|
439
454
|
when "Anthropic"
|
|
440
455
|
anthropic_reasoning_effort_choices(id)
|
|
441
456
|
when "Copilot"
|
|
442
|
-
id.to_s.match?(/\Agpt-5(?:\.|-|\z)/) ? openai_reasoning_effort_choices(id) : []
|
|
457
|
+
id.to_s.match?(/\Agpt-(?:5|6)(?:\.|-|\z)/) ? openai_reasoning_effort_choices(id) : []
|
|
443
458
|
else
|
|
444
459
|
[]
|
|
445
460
|
end
|
|
@@ -447,6 +462,8 @@ module Kward
|
|
|
447
462
|
|
|
448
463
|
def openai_reasoning_effort_choices(id)
|
|
449
464
|
text = id.to_s.delete_prefix("openai/")
|
|
465
|
+
return GPT_6_LUNA_REASONING_EFFORT_CHOICES if text.match?(/\Agpt-6-luna(?:\z|-)/)
|
|
466
|
+
return GPT_6_REASONING_EFFORT_CHOICES if text.match?(/\Agpt-6\.1-sol(?:\z|-)|\Agpt-6-astra(?:\z|-)/)
|
|
450
467
|
return REASONING_EFFORT_CHOICES if text.match?(/\Agpt-5\.[23]-codex/)
|
|
451
468
|
return OPENAI_MAX_REASONING_EFFORT_CHOICES if text.match?(/\Agpt-5\.6(?:\z|-)/)
|
|
452
469
|
|
|
@@ -3,9 +3,9 @@ require "json"
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "time"
|
|
5
5
|
require "uri"
|
|
6
|
-
require_relative "config_files"
|
|
7
|
-
require_relative "http"
|
|
8
|
-
require_relative "private_file"
|
|
6
|
+
require_relative "../config_files"
|
|
7
|
+
require_relative "../http"
|
|
8
|
+
require_relative "../private_file"
|
|
9
9
|
|
|
10
10
|
# Namespace for the Kward CLI agent runtime.
|
|
11
11
|
module Kward
|
data/lib/kward/model/payloads.rb
CHANGED
|
@@ -95,6 +95,8 @@ module Kward
|
|
|
95
95
|
|
|
96
96
|
def anthropic_payload(messages, tools, max_tokens: nil, model: nil, reasoning: nil)
|
|
97
97
|
parts = build_context_parts("Anthropic", messages, tools, model: model)
|
|
98
|
+
raise ArgumentError, "Anthropic requires conversation messages; a system-only turn is unavailable in an empty session" if parts[:messages].empty?
|
|
99
|
+
|
|
98
100
|
system = [{ type: "text", text: "You are Claude Code, Anthropic's official CLI for Claude." }]
|
|
99
101
|
system << { type: "text", text: parts[:system] } unless parts[:system].to_s.empty?
|
|
100
102
|
payload = {
|
|
@@ -122,6 +124,8 @@ module Kward
|
|
|
122
124
|
contents: messages.filter_map { |message| gemini_message(message) },
|
|
123
125
|
tools: tools.empty? ? nil : [{ functionDeclarations: tools.map { |tool| gemini_tool_schema(tool) } }]
|
|
124
126
|
}.compact
|
|
127
|
+
raise ArgumentError, "Gemini requires conversation contents; a system-only turn is unavailable in an empty session" if payload[:contents].empty?
|
|
128
|
+
|
|
125
129
|
payload[:systemInstruction] = { parts: system_parts } unless system_parts.empty?
|
|
126
130
|
payload[:generationConfig] = { maxOutputTokens: max_tokens.to_i } if max_tokens.to_i.positive?
|
|
127
131
|
payload
|
|
@@ -219,7 +223,8 @@ module Kward
|
|
|
219
223
|
tools: tools.map { |tool| codex_tool_schema(tool) }
|
|
220
224
|
}
|
|
221
225
|
elsif ["Codex", "OpenAI"].include?(provider)
|
|
222
|
-
|
|
226
|
+
# The Codex subscription endpoint rejects inline system messages.
|
|
227
|
+
instructions, input = codex_messages(messages, system_turn_role: provider == "Codex" ? "developer" : "system")
|
|
223
228
|
{
|
|
224
229
|
provider: provider,
|
|
225
230
|
model: model_for(provider, override_model: model),
|
|
@@ -335,7 +340,7 @@ module Kward
|
|
|
335
340
|
}
|
|
336
341
|
end
|
|
337
342
|
|
|
338
|
-
def codex_messages(messages)
|
|
343
|
+
def codex_messages(messages, system_turn_role: "system")
|
|
339
344
|
instructions = []
|
|
340
345
|
input = []
|
|
341
346
|
|
|
@@ -344,7 +349,11 @@ module Kward
|
|
|
344
349
|
content = MessageAccess.content(message) || ""
|
|
345
350
|
case role.to_s
|
|
346
351
|
when "system"
|
|
347
|
-
|
|
352
|
+
if MessageAccess.value(message, :system_turn)
|
|
353
|
+
input << codex_message(system_turn_role, plain_content(content).to_s)
|
|
354
|
+
else
|
|
355
|
+
instructions << plain_content(content).to_s
|
|
356
|
+
end
|
|
348
357
|
when "tool", "toolResult"
|
|
349
358
|
input << {
|
|
350
359
|
type: "function_call_output",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "net/http"
|
|
3
|
+
require "uri"
|
|
4
|
+
require_relative "../cancellation"
|
|
5
|
+
require_relative "../http"
|
|
6
|
+
|
|
7
|
+
module Kward
|
|
8
|
+
# HTTP client for TypeSafe's structured System One evaluation endpoint.
|
|
9
|
+
class TypeSafeClient
|
|
10
|
+
DEFAULT_ENDPOINT = "https://api.typesafe.ai/v1/systemone"
|
|
11
|
+
DEFAULT_MODEL = "jev-latest"
|
|
12
|
+
MAX_STATE_BYTES = 100_000
|
|
13
|
+
MAX_QUESTIONS = 100
|
|
14
|
+
HTTP_TIMEOUT_SECONDS = 30
|
|
15
|
+
|
|
16
|
+
class RequestError < StandardError
|
|
17
|
+
attr_reader :status
|
|
18
|
+
|
|
19
|
+
def initialize(status:, message:)
|
|
20
|
+
@status = status.to_i
|
|
21
|
+
super(message)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(api_key: ENV["TYPESAFE_API_KEY"], endpoint: DEFAULT_ENDPOINT, http: Net::HTTP)
|
|
26
|
+
@api_key = api_key.to_s.strip
|
|
27
|
+
@endpoint = URI(endpoint)
|
|
28
|
+
@http = http
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def available?
|
|
32
|
+
!@api_key.empty?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def evaluate(state:, questions:, model: DEFAULT_MODEL, cancellation: nil)
|
|
36
|
+
raise ArgumentError, "TypeSafe API key is not configured" unless available?
|
|
37
|
+
|
|
38
|
+
cancellation&.raise_if_cancelled!
|
|
39
|
+
request = Net::HTTP::Post.new(@endpoint)
|
|
40
|
+
request["Authorization"] = "Bearer #{@api_key}"
|
|
41
|
+
request["Content-Type"] = "application/json"
|
|
42
|
+
request["Accept"] = "application/json"
|
|
43
|
+
Http.apply_user_agent(request)
|
|
44
|
+
request.body = JSON.dump({ state: state, model: model, questions: questions })
|
|
45
|
+
|
|
46
|
+
response_body = nil
|
|
47
|
+
@http.start(@endpoint.hostname, @endpoint.port, use_ssl: @endpoint.scheme == "https", read_timeout: HTTP_TIMEOUT_SECONDS) do |http|
|
|
48
|
+
cancellation&.on_cancel { close_http(http) }
|
|
49
|
+
cancellation&.raise_if_cancelled!
|
|
50
|
+
response = http.request(request)
|
|
51
|
+
response_body = response.body.to_s
|
|
52
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
53
|
+
raise RequestError.new(status: response.code, message: error_message(response.code, response_body))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
cancellation&.raise_if_cancelled!
|
|
57
|
+
JSON.parse(response_body)
|
|
58
|
+
rescue JSON::ParserError
|
|
59
|
+
raise RequestError.new(status: 200, message: "TypeSafe returned invalid JSON")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def error_message(status, body)
|
|
65
|
+
detail = JSON.parse(body).fetch("error", nil) rescue nil
|
|
66
|
+
detail = detail["message"] if detail.is_a?(Hash)
|
|
67
|
+
detail = detail.to_s unless detail.nil?
|
|
68
|
+
detail = "request failed" if detail.nil? || detail.empty?
|
|
69
|
+
"TypeSafe request failed (#{status}): #{detail[0, 500]}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def close_http(http)
|
|
73
|
+
http.finish if http&.started?
|
|
74
|
+
rescue IOError
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/kward/pan/server.rb
CHANGED
|
@@ -10,16 +10,16 @@ require_relative "../agent"
|
|
|
10
10
|
require_relative "../config_files"
|
|
11
11
|
require_relative "../events"
|
|
12
12
|
require_relative "../model/retry_message"
|
|
13
|
-
require_relative "../
|
|
13
|
+
require_relative "../plugins/registry"
|
|
14
14
|
require_relative "../prompts/commands"
|
|
15
15
|
require_relative "../rpc/transcript_normalizer"
|
|
16
|
-
require_relative "../
|
|
16
|
+
require_relative "../sessions/store"
|
|
17
17
|
require_relative "../skills/capture"
|
|
18
|
-
require_relative "../
|
|
18
|
+
require_relative "../sessions/trash"
|
|
19
19
|
require_relative "../tools/tool_call"
|
|
20
20
|
require_relative "../tools/registry"
|
|
21
21
|
require_relative "../version"
|
|
22
|
-
require_relative "../
|
|
22
|
+
require_relative "../workspace/factory"
|
|
23
23
|
|
|
24
24
|
# Namespace for the Kward CLI agent runtime.
|
|
25
25
|
module Kward
|
|
@@ -95,6 +95,8 @@ module Kward
|
|
|
95
95
|
@server&.close unless @server&.closed?
|
|
96
96
|
rescue IOError
|
|
97
97
|
nil
|
|
98
|
+
ensure
|
|
99
|
+
@plugin_registry&.shutdown!
|
|
98
100
|
end
|
|
99
101
|
|
|
100
102
|
def enqueue_prompt(prompt)
|
|
@@ -130,7 +132,7 @@ module Kward
|
|
|
130
132
|
hook_manager = lifecycle_hook_manager
|
|
131
133
|
Agent.new(
|
|
132
134
|
client: @client,
|
|
133
|
-
tool_registry: ToolRegistry.new(workspace: @workspace, ask_user_question_enabled: false, hook_manager: hook_manager, hook_context: hook_context),
|
|
135
|
+
tool_registry: ToolRegistry.new(workspace: @workspace, ask_user_question_enabled: false, plugin_tools: plugin_registry.tools, hook_manager: hook_manager, hook_context: hook_context),
|
|
134
136
|
conversation: @conversation,
|
|
135
137
|
hook_manager: hook_manager,
|
|
136
138
|
hook_context: hook_context
|
|
@@ -177,7 +179,7 @@ module Kward
|
|
|
177
179
|
end
|
|
178
180
|
|
|
179
181
|
def plugin_registry
|
|
180
|
-
@plugin_registry ||= PluginRegistry.load(reserved_commands: PromptCommands::BUILTIN_RESERVED_COMMAND_NAMES)
|
|
182
|
+
@plugin_registry ||= PluginRegistry.load(reserved_commands: PromptCommands::BUILTIN_RESERVED_COMMAND_NAMES).tap(&:start!)
|
|
181
183
|
end
|
|
182
184
|
|
|
183
185
|
def start_worker
|
|
@@ -241,7 +243,8 @@ module Kward
|
|
|
241
243
|
when ["GET", "/kward-logo.png"]
|
|
242
244
|
write_response(socket, 200, { "Content-Type" => "image/png", "Cache-Control" => "public, max-age=86400" }, File.binread(File.join(__dir__, "kward_logo.png")))
|
|
243
245
|
when ["GET", "/transcript"]
|
|
244
|
-
write_json(socket, 200, transcript: transcript_items, session: active_session_payload, workspace: @workspace.root.to_s
|
|
246
|
+
write_json(socket, 200, transcript: transcript_items, session: active_session_payload, workspace: @workspace.root.to_s,
|
|
247
|
+
capabilities: { pluginCommandTurns: { supported: false, reason: "Pan has no plugin slash-command dispatcher; use the TUI or RPC turns/start." } })
|
|
245
248
|
when ["GET", "/sessions"]
|
|
246
249
|
write_json(socket, 200, sessions: session_payloads, activeSessionId: @session.id)
|
|
247
250
|
when ["GET", "/skill-capture/sessions"]
|
|
@@ -160,11 +160,15 @@ module Kward
|
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
def network?(request)
|
|
163
|
-
NETWORK_TOOLS.include?(request.fetch("tool"))
|
|
163
|
+
NETWORK_TOOLS.include?(request.fetch("tool"))
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def extension_tool?(request)
|
|
167
|
+
%w[mcp plugin].include?(request["source"])
|
|
164
168
|
end
|
|
165
169
|
|
|
166
170
|
def risky?(request)
|
|
167
|
-
mutating?(request) || request.fetch("tool") == "run_shell_command" || network?(request)
|
|
171
|
+
mutating?(request) || request.fetch("tool") == "run_shell_command" || network?(request) || extension_tool?(request)
|
|
168
172
|
end
|
|
169
173
|
|
|
170
174
|
def outside_write_scopes?(request)
|