kward 0.77.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/CHANGELOG.md +26 -0
- data/Gemfile.lock +2 -2
- data/README.md +1 -0
- data/doc/code-search.md +9 -6
- data/doc/configuration.md +39 -0
- data/doc/permissions.md +179 -0
- data/doc/plugins.md +58 -0
- data/doc/rpc.md +61 -0
- data/doc/security.md +4 -0
- data/doc/tabs.md +4 -3
- data/doc/web-search.md +4 -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/cli/plugins.rb +22 -0
- data/lib/kward/cli/rendering.rb +7 -1
- data/lib/kward/cli/runtime_helpers.rb +14 -0
- data/lib/kward/cli/tabs.rb +140 -34
- data/lib/kward/cli.rb +29 -9
- data/lib/kward/config_files.rb +10 -0
- data/lib/kward/hooks/http_handler.rb +2 -1
- data/lib/kward/http.rb +18 -0
- 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/permissions/policy.rb +171 -0
- data/lib/kward/plugin_registry.rb +53 -1
- data/lib/kward/prompt_interface/approval_prompt.rb +62 -0
- data/lib/kward/prompt_interface/question_prompt.rb +12 -3
- data/lib/kward/prompt_interface.rb +2 -0
- data/lib/kward/rpc/plugin_chat_manager.rb +299 -0
- data/lib/kward/rpc/server.rb +35 -0
- data/lib/kward/rpc/session_manager.rb +1 -0
- data/lib/kward/rpc/transcript_normalizer.rb +14 -5
- 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/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 +37 -4
- data/lib/kward/tools/search/code.rb +46 -12
- data/lib/kward/tools/search/web.rb +60 -17
- data/lib/kward/tools/search/web_fetch.rb +206 -38
- data/lib/kward/tools/web_search.rb +3 -1
- data/lib/kward/update_check.rb +2 -1
- data/lib/kward/version.rb +1 -1
- data/templates/default/kward_navigation.rb +2 -1
- data/templates/default/layout/html/layout.erb +2 -0
- data/templates/default/layout/html/setup.rb +1 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e77436e5f94e314fcab35e347c16994eb8f92f990ad4c3ebdc6ab76a393cb1b4
|
|
4
|
+
data.tar.gz: 50b73b48c76c737d5b7d2534380547d94e357cac6dec4528a80cfe36ea8f6bdf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bc17d7227fa1910ee107b840ec138ee19c8ed905d137d7aeed3fc552e2ec6d3aca1fb3d8df4febce7c7e84380a7e4f4378c3177415b3ab7a59fb22d344b92d2c
|
|
7
|
+
data.tar.gz: 56521cdb264f5745f2023a010892b99ec91693f4444c17c0f1aa7b9375e9d0727ba7c9ce280d4e0af9141ae5466073393fbf9d112d53bf7ea12872aa68c7f617
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable changes to Kward will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.78.0] - 2026-07-15
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added opt-in transcript-observer delivery for plugin-owned tabs, including RPC plugin chats, so local integrations such as text-to-speech can receive permitted streamed replies.
|
|
12
|
+
- Added opt-in RPC support for plugin-owned chats, including explicit subscriptions, transcript snapshots, attachment-capable turns, event replay, and cancellation. Plugins remain disabled from this surface unless their tab type explicitly opts in.
|
|
13
|
+
- Added plugin-owned tab types, opened with `/tab open <plugin-tab>`, with typed tab persistence and the normal interactive composer, transcript, streaming, and image-input behavior. Plugin tabs are CLI-only in this release and own their own storage rather than Kward sessions.
|
|
14
|
+
- Added an opt-in permission policy for model-requested tools, with allow/ask/deny rules, read-only and workspace-write modes, write scopes, and interactive approval overlays. Permissions remain disabled by default and are not an OS sandbox.
|
|
15
|
+
- Added a custom-response path to permission approval overlays: choosing `Type something` denies the requested tool call and returns the entered guidance to the agent.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Plugin-chat transcript RPC supports optional bounded, cursor-based pages for plugin drivers that opt in, avoiding full-history payloads for long-lived chats.
|
|
20
|
+
- Code search now synchronizes mutable repository refs before every read and search, reports the resolved commit, and accepts GitHub blob URLs as file inputs.
|
|
21
|
+
- Web and code research tools now cooperate with turn cancellation between requests, provider attempts, redirects, and repository scan files.
|
|
22
|
+
- Kward-owned HTTP requests now identify themselves with `User-Agent: Kward/<version>`.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Fixed `fetch_content` site navigation by preferring semantic main content over nested article cards, preserving inline and standalone link destinations, listing bounded discovered navigation links, and extracting ordered lists and simple tables.
|
|
27
|
+
- Prevented duplicate session-backed tabs from restoring as mirrored conversations after restart.
|
|
28
|
+
- Fixed `fetch_content` on documentation pages with large scripts or metadata before the main content by separating bounded page downloads from extracted output limits; plain-text extraction now parses HTML instead of returning cleaned markup.
|
|
29
|
+
- Fixed raw fetches so their response limit is enforced while reading the network body.
|
|
30
|
+
- Fixed stale code-search reads and searches by treating cached repositories as reusable local storage rather than authoritative snapshots.
|
|
31
|
+
- Restored GPT-5.6 Luna requests through Codex with a scoped Responses Lite compatibility workaround; normal Codex requests retain Kward's own identity.
|
|
32
|
+
|
|
7
33
|
## [0.77.0] - 2026-07-11
|
|
8
34
|
|
|
9
35
|
### Added
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
kward (0.
|
|
4
|
+
kward (0.78.0)
|
|
5
5
|
base64
|
|
6
6
|
nokogiri
|
|
7
7
|
tiktoken_ruby
|
|
@@ -146,7 +146,7 @@ CHECKSUMS
|
|
|
146
146
|
html-proofer (5.2.1) sha256=fdd958a7cbf9c3255fb96fe7cfc4e611f64e2706e469488a3326309ad007d2fd
|
|
147
147
|
io-event (1.16.2) sha256=9f9cb0a96ea5c3850a672606c65f27bc96d7621399ef6196acbfe2be0cd1279c
|
|
148
148
|
json (2.19.9) sha256=9b9025b7cdddafa38d316eca0b2358488e42d417045c1b90d216a9fefe46b79a
|
|
149
|
-
kward (0.
|
|
149
|
+
kward (0.78.0)
|
|
150
150
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
151
151
|
metrics (0.15.0) sha256=61ded5bac95118e995b1bc9ed4a5f19bc9814928a312a85b200abbdac9039072
|
|
152
152
|
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
|
data/README.md
CHANGED
|
@@ -76,6 +76,7 @@ Start here:
|
|
|
76
76
|
- [Configuration](doc/configuration.md): config files, providers, models, web search, logging, and color output.
|
|
77
77
|
- [Authentication](doc/authentication.md): OpenAI OAuth, Anthropic OAuth, OpenRouter API keys, and Copilot/GitHub setup.
|
|
78
78
|
- [Security and trust](doc/security.md): local permissions, external data flow, trusted extensions, and safe work in unfamiliar repositories.
|
|
79
|
+
- [Permissions](doc/permissions.md): opt-in tool approval, write scopes, policy rules, and current limits.
|
|
79
80
|
- [Troubleshooting](doc/troubleshooting.md): environment-specific install and runtime issues.
|
|
80
81
|
|
|
81
82
|
Feature guides:
|
data/doc/code-search.md
CHANGED
|
@@ -85,9 +85,9 @@ The tool uses an `action` parameter:
|
|
|
85
85
|
| --- | --- |
|
|
86
86
|
| `package_search` | find package metadata and likely source repositories. |
|
|
87
87
|
| `github_search` | search public GitHub repositories. |
|
|
88
|
-
| `repo_clone` |
|
|
89
|
-
| `repo_search` | search files in a
|
|
90
|
-
| `repo_read` | read a bounded line range from a
|
|
88
|
+
| `repo_clone` | prewarm the local Git repository store. |
|
|
89
|
+
| `repo_search` | synchronize and search files in a repository. |
|
|
90
|
+
| `repo_read` | synchronize and read a bounded line range from a file. |
|
|
91
91
|
| `list_cache` | show cached repositories. |
|
|
92
92
|
| `refresh_cache` | fetch updates for a cached repository. |
|
|
93
93
|
| `clear_cache` | remove a cached repository. |
|
|
@@ -99,13 +99,16 @@ The tool uses an `action` parameter:
|
|
|
99
99
|
| `package_search` | `ecosystem`, `package` | — |
|
|
100
100
|
| `github_search` | `query` | `ecosystem`, `max_results` |
|
|
101
101
|
| `repo_clone` | `repo` | — |
|
|
102
|
-
| `repo_search` | `repo`, `query` | `max_results`, `context_lines` |
|
|
103
|
-
| `repo_read` | `repo
|
|
102
|
+
| `repo_search` | `repo`, `query` | `ref`, `max_results`, `context_lines` |
|
|
103
|
+
| `repo_read` | `repo` | `path`, `ref`, `start_line`, `line_count` |
|
|
104
104
|
| `list_cache` | — | — |
|
|
105
105
|
| `refresh_cache` | `repo` | — |
|
|
106
106
|
| `clear_cache` | `repo` | — |
|
|
107
107
|
|
|
108
|
-
- `repo` is a GitHub URL or `owner/name`.
|
|
108
|
+
- `repo` is a GitHub URL or `owner/name`. A GitHub `blob` URL can supply the file path and ref for `repo_read`.
|
|
109
|
+
- `ref` selects a branch, tag, or commit. Mutable refs are fetched before every read or search; without `ref`, Kward synchronizes the remote default branch.
|
|
110
|
+
- Successful reads and searches report the exact commit revision used. The local Git store reduces repeated downloads but is never silently treated as current source.
|
|
111
|
+
- If synchronization fails, Kward returns an error instead of silently answering from stale files.
|
|
109
112
|
- `query` is a plain-text search string (not regex). Matching is case-sensitive substring.
|
|
110
113
|
- `start_line` is 1-indexed.
|
|
111
114
|
- `context_lines` controls how many lines before and after a match are included in search snippets.
|
data/doc/configuration.md
CHANGED
|
@@ -184,6 +184,8 @@ Model settings:
|
|
|
184
184
|
|
|
185
185
|
Set `codex_show_raw_reasoning` to `true` to display raw Codex `reasoning_text` when the API does not provide reasoning summary text. It defaults to `false`; raw reasoning can include internal or unstable model output, so enable it only when you explicitly want to inspect that stream.
|
|
186
186
|
|
|
187
|
+
`stream_idle_timeout_seconds` limits how long a streamed Codex or Anthropic response may go without receiving data. It defaults to `120`; set a positive value to override it. When the provider is silent longer than this limit, Kward closes the request and applies its normal transient-network retry behavior.
|
|
188
|
+
|
|
187
189
|
Defaults:
|
|
188
190
|
|
|
189
191
|
- OpenAI/Codex: `gpt-5.6-sol`
|
|
@@ -528,6 +530,43 @@ Workspace guardrails are enabled by default. File tools such as `read_file`, `wr
|
|
|
528
530
|
|
|
529
531
|
This is not a sandbox setting. Shell commands already run as your OS user from the workspace directory and can access anything that user can access.
|
|
530
532
|
|
|
533
|
+
## Permissions
|
|
534
|
+
|
|
535
|
+
Permissions are off by default. When enabled, Kward evaluates model-requested tools before execution. This is a permission policy, not an operating-system sandbox: permitted shell commands still run with your user account's access.
|
|
536
|
+
|
|
537
|
+
```json
|
|
538
|
+
{
|
|
539
|
+
"permissions": {
|
|
540
|
+
"enabled": true,
|
|
541
|
+
"mode": "ask"
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Available modes are:
|
|
547
|
+
|
|
548
|
+
| Mode | Behavior |
|
|
549
|
+
| --- | --- |
|
|
550
|
+
| `ask` | Read-only tools run normally; file changes, shell commands, web tools, and MCP tools need approval. |
|
|
551
|
+
| `workspace-write` | File changes within `write_scopes` run without approval; shell and network tools still need approval. |
|
|
552
|
+
| `read-only` | Denies file changes, shell commands, web tools, and MCP tools. |
|
|
553
|
+
| `deny-by-default` | Denies risky tools unless an `allow` rule matches. |
|
|
554
|
+
|
|
555
|
+
`allow`, `ask`, and `deny` rules are arrays of objects matching `tool`, `path`, `host`, `command`, or `source`. Deny rules always take precedence, then ask, then allow. Use `write_scopes` to restrict writes in `workspace-write` mode:
|
|
556
|
+
|
|
557
|
+
```json
|
|
558
|
+
{
|
|
559
|
+
"permissions": {
|
|
560
|
+
"enabled": true,
|
|
561
|
+
"mode": "workspace-write",
|
|
562
|
+
"write_scopes": ["lib/**", "test/**"],
|
|
563
|
+
"deny": [{ "tool": "run_shell_command", "command": "git push*" }]
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
The interactive CLI presents an approval overlay for an `ask` decision. RPC clients can use their existing `approvalMode: "ask"` bridge. Frontends without an approval bridge, including Pan, fail closed for policy approvals.
|
|
569
|
+
|
|
531
570
|
## Logging and stats
|
|
532
571
|
|
|
533
572
|
Local telemetry logs are off by default. Enable logging with the master flag and each category you want:
|
data/doc/permissions.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Permissions
|
|
2
|
+
|
|
3
|
+
Kward's permission policy lets you put a human decision between a model-requested tool call and its side effect. It is useful when you want the agent to inspect a project freely, but want to confirm edits, commands, web requests, or MCP calls as they arise.
|
|
4
|
+
|
|
5
|
+
Permissions are **off by default**. Enabling them does not sandbox Kward or its subprocesses: an allowed shell command still runs with your operating-system user permissions.
|
|
6
|
+
|
|
7
|
+
## Start with approval mode
|
|
8
|
+
|
|
9
|
+
Add this to `~/.kward/config.json`, or enable it through your usual configuration workflow:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"permissions": {
|
|
14
|
+
"enabled": true,
|
|
15
|
+
"mode": "ask"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
In `ask` mode, Kward allows ordinary read-only tools and asks before the agent:
|
|
21
|
+
|
|
22
|
+
- writes or edits a workspace file,
|
|
23
|
+
- runs `run_shell_command`,
|
|
24
|
+
- searches or fetches content on the web,
|
|
25
|
+
- calls an MCP tool.
|
|
26
|
+
|
|
27
|
+
When approval is required in the interactive CLI, Kward opens a modal overlay. It shows the complete tool arguments, so an approval for `read_skill` includes the requested skill name and relative path, and an approval for a write includes its path and content. Choose **Allow once** to run that one call, **Allow this tool for this session** to allow later calls to the same tool in the current Kward session, or **Deny** to stop it. Pressing `Esc`, cancelling the overlay, or losing the approval bridge denies the call.
|
|
28
|
+
|
|
29
|
+
### Steer the agent with `Type something`
|
|
30
|
+
|
|
31
|
+
**Type something** is more than a denial. It stops the requested tool call and sends your text back to the agent as the tool result, so you can redirect its workflow without waiting for it to finish a wrong turn.
|
|
32
|
+
|
|
33
|
+
For example, the agent may propose this command:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
run_shell_command: grep -R "Permission" lib test
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Choose **Type something** and enter:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
Use ack instead of grep; it is installed here and respects our ignore files.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Kward does not run `grep`. The agent receives:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
Declined: Use ack instead of grep; it is installed here and respects our ignore files.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
It can then continue by choosing an `ack` command. This is useful for steering details that are difficult to encode in a static rule, such as:
|
|
52
|
+
|
|
53
|
+
- use the project’s preferred formatter or test runner;
|
|
54
|
+
- run a focused test rather than the full suite;
|
|
55
|
+
- use a local mirror or a documented API endpoint;
|
|
56
|
+
- explain a proposed database command before running it;
|
|
57
|
+
- choose an existing project script instead of inventing a new command.
|
|
58
|
+
|
|
59
|
+
The typed response is part of the conversation, just like a normal follow-up message. It is stored in the session and may be sent to the active model provider. Do not put secrets in it unless that is acceptable for your selected provider and retained session data.
|
|
60
|
+
|
|
61
|
+
Use this mode first when working in an unfamiliar repository or when you want to review an agent's actions without preventing ordinary exploration.
|
|
62
|
+
|
|
63
|
+
## Choose a mode
|
|
64
|
+
|
|
65
|
+
Set `permissions.mode` to one of these values:
|
|
66
|
+
|
|
67
|
+
| Mode | Good for | Default behavior |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `ask` | Interactive supervised work | Asks before file changes, shell commands, web tools, and MCP tools. |
|
|
70
|
+
| `workspace-write` | Routine edits in selected paths | Allows file changes in `write_scopes`; still asks before shell, web, and MCP tools. |
|
|
71
|
+
| `read-only` | Code review and investigation | Denies risky tools by default. |
|
|
72
|
+
| `deny-by-default` | Automation or tightly controlled runs | Denies risky tools unless an `allow` rule matches. |
|
|
73
|
+
|
|
74
|
+
All modes leave ordinary read-only Kward tools available. Rules can make a decision more specific; see [Rules and precedence](#Rules_and_precedence).
|
|
75
|
+
|
|
76
|
+
## Let Kward edit only selected paths
|
|
77
|
+
|
|
78
|
+
Use `workspace-write` when you want routine changes in known source and test directories without an approval prompt for each edit:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"permissions": {
|
|
83
|
+
"enabled": true,
|
|
84
|
+
"mode": "workspace-write",
|
|
85
|
+
"write_scopes": ["lib/**", "test/**"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
With this configuration, the agent may edit `lib/` and `test/` without asking. A proposed edit to `README.md` or `config/` is denied by the policy.
|
|
91
|
+
|
|
92
|
+
`write_scopes` narrows the policy's default workspace-write behavior; it does not broaden Kward's built-in workspace boundary. File tools still stay inside the active workspace by default, and existing files must still be read before Kward can edit them. See [Workspace tools](workspace-tools.md) for those guardrails.
|
|
93
|
+
|
|
94
|
+
An empty `write_scopes` array permits no default workspace writes. Omit `write_scopes` if you want the `workspace-write` mode to cover the normal workspace file-tool boundary.
|
|
95
|
+
|
|
96
|
+
## Rules and precedence
|
|
97
|
+
|
|
98
|
+
Use `allow`, `ask`, and `deny` arrays to describe exceptions. A rule can match these fields:
|
|
99
|
+
|
|
100
|
+
- `tool` — a model tool name, such as `run_shell_command` or `write_file`;
|
|
101
|
+
- `path` — the file-tool path supplied by the model;
|
|
102
|
+
- `command` — the requested shell command text;
|
|
103
|
+
- `host` — the host in a `fetch_content` or `fetch_raw` URL;
|
|
104
|
+
- `source` — currently useful for `mcp` tools.
|
|
105
|
+
|
|
106
|
+
Patterns support `*` within a path segment and `**` across directories. Rule matching is case-sensitive.
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"permissions": {
|
|
111
|
+
"enabled": true,
|
|
112
|
+
"mode": "ask",
|
|
113
|
+
"allow": [
|
|
114
|
+
{ "tool": "write_file", "path": "doc/**" },
|
|
115
|
+
{ "tool": "fetch_content", "host": "docs.ruby-lang.org" }
|
|
116
|
+
],
|
|
117
|
+
"ask": [
|
|
118
|
+
{ "tool": "run_shell_command", "command": "bundle exec *" }
|
|
119
|
+
],
|
|
120
|
+
"deny": [
|
|
121
|
+
{ "tool": "run_shell_command", "command": "git push*" },
|
|
122
|
+
{ "tool": "write_file", "path": "**/.env" }
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Kward evaluates matching rules in this order:
|
|
129
|
+
|
|
130
|
+
1. `deny`
|
|
131
|
+
2. `ask`
|
|
132
|
+
3. `allow`
|
|
133
|
+
4. the selected mode's default
|
|
134
|
+
|
|
135
|
+
A matching deny always wins. An ask rule wins over an allow rule. Keep rules narrow and easy to explain; prefer an exact command or a small path scope over a broad wildcard.
|
|
136
|
+
|
|
137
|
+
## A practical workflow
|
|
138
|
+
|
|
139
|
+
For a normal coding task:
|
|
140
|
+
|
|
141
|
+
1. Enable `ask` mode.
|
|
142
|
+
2. Ask Kward to inspect the project and propose a change.
|
|
143
|
+
3. Approve the file edit after checking its path and intent.
|
|
144
|
+
4. Approve a focused test command, or choose **Type something** to redirect it—for example, “run `bin/test unit` instead of the full suite.”
|
|
145
|
+
5. Review the diff and `git status` as usual.
|
|
146
|
+
|
|
147
|
+
If you repeatedly work in one source tree, move to `workspace-write` with narrow write scopes. Keep shell and network calls in approval mode unless you have a well-understood policy for them.
|
|
148
|
+
|
|
149
|
+
## RPC and Pan
|
|
150
|
+
|
|
151
|
+
RPC clients can continue to use the existing per-turn approval bridge:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"approvalMode": "ask"
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
That requests an approval notification for every tool call in that turn. The global permission policy is an additional restriction: a deny rule prevents the call even if the RPC client would approve it. If the global policy requires approval, a frontend without an approval bridge fails closed.
|
|
160
|
+
|
|
161
|
+
Pan currently has no dedicated approval interface. With permissions enabled, a Pan session cannot approve a policy `ask` decision, so Kward denies it. Use `read-only`, explicit allows, or a CLI/RPC frontend when approvals are needed.
|
|
162
|
+
|
|
163
|
+
## Limits: policy is not a sandbox
|
|
164
|
+
|
|
165
|
+
The policy runs before Kward dispatches a model-requested tool. It does not constrain what happens after a shell command starts. In particular:
|
|
166
|
+
|
|
167
|
+
- a permitted shell command can access files, processes, credentials, and network services available to your user account;
|
|
168
|
+
- command-text rules are useful review controls, not a reliable way to enforce network destinations or all subprocess behavior;
|
|
169
|
+
- direct commands that you type yourself—`!command`, `/shell`, and `/pty`—are treated as your actions and are outside this first policy scope;
|
|
170
|
+
- plugins, hooks, and MCP servers are trusted local extensions with their own process access.
|
|
171
|
+
|
|
172
|
+
For sensitive work, use a restricted operating-system account, container, virtual machine, or disposable checkout. A future OS-enforced sandbox may provide stronger filesystem and network isolation; it is not part of the current permissions feature.
|
|
173
|
+
|
|
174
|
+
## Related guides
|
|
175
|
+
|
|
176
|
+
- [Security and trust](security.md): trust boundaries, extensions, and safe work in unfamiliar repositories.
|
|
177
|
+
- [Configuration](configuration.md#Permissions): the complete configuration reference.
|
|
178
|
+
- [Lifecycle hooks](lifecycle-hooks.md): add trusted local policy or automation around Kward events.
|
|
179
|
+
- [RPC protocol](rpc.md#Tool_approval_bridge): implement the approval bridge in an RPC client.
|
data/doc/plugins.md
CHANGED
|
@@ -178,6 +178,64 @@ Returning `:exit` from the tick callback ends the loop, same as calling
|
|
|
178
178
|
Interactive commands require the TUI prompt interface. They are not available
|
|
179
179
|
in piped/non-interactive mode or through RPC.
|
|
180
180
|
|
|
181
|
+
## Add a plugin-owned tab
|
|
182
|
+
|
|
183
|
+
A plugin can provide a persistent tab with Kward's normal composer, transcript
|
|
184
|
+
rendering, streaming, image input, cancellation, and tab switching. The plugin
|
|
185
|
+
owns its transcript, storage, model behavior, and any global state; it does not
|
|
186
|
+
need to use a Kward workspace session.
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
Kward.plugin do |plugin|
|
|
190
|
+
plugin.tab_type "example", id: "com.example.chat", title: "Example", singleton: :global do |host, descriptor|
|
|
191
|
+
ExampleChat.new(client: host.client, descriptor: descriptor)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Open it from interactive Kward:
|
|
197
|
+
|
|
198
|
+
```text
|
|
199
|
+
/tab open example
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`id` is a stable persisted identifier: do not change it after release.
|
|
203
|
+
Use `singleton: :global` for one plugin-managed chat shared by all tab views.
|
|
204
|
+
|
|
205
|
+
Plugin tabs do not notify global transcript observers by default. Set
|
|
206
|
+
`transcript_events: true` only when the tab explicitly permits its streamed
|
|
207
|
+
content to be delivered to every installed `on_transcript_event` handler, such
|
|
208
|
+
as a local text-to-speech plugin:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
plugin.tab_type "example", id: "com.example.chat", transcript_events: true do |host, descriptor|
|
|
212
|
+
ExampleChat.new(client: host.client, descriptor: descriptor)
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The observer context exposes the plugin tab's `messages` through
|
|
217
|
+
`ctx.transcript.messages`; it has no workspace session. The tab driver returned by the block must provide:
|
|
218
|
+
|
|
219
|
+
- `messages` — renderable transcript messages;
|
|
220
|
+
- `submit(input, display_input:, cancellation:, steering:)` — a turn method
|
|
221
|
+
that returns the final response and yields stream events;
|
|
222
|
+
- `descriptor` — the durable tab descriptor;
|
|
223
|
+
- `supports_steering?` and `assistant_label`.
|
|
224
|
+
|
|
225
|
+
A driver may optionally implement `handles_command?(input)` and
|
|
226
|
+
`handle_command(input)` for its own slash commands. Other Kward session and
|
|
227
|
+
workspace commands stay unavailable inside plugin tabs.
|
|
228
|
+
|
|
229
|
+
Set `rpc: true` when the plugin tab can also be exposed as a trusted local RPC chat:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
plugin.tab_type "example", id: "com.example.chat", rpc: true do |host, descriptor|
|
|
233
|
+
ExampleChat.new(client: host.client, descriptor: descriptor)
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
RPC clients discover opted-in types through `initialize.capabilities.pluginChats`, open a chat with `pluginChats/open`, and must explicitly subscribe before receiving live `pluginChat/event` notifications. See [RPC](rpc.md) for the protocol. Plugin tabs remain CLI-only unless they opt in.
|
|
238
|
+
|
|
181
239
|
## Observe transcript events
|
|
182
240
|
|
|
183
241
|
Use transcript events when you need to log or react to live activity:
|
data/doc/rpc.md
CHANGED
|
@@ -52,6 +52,7 @@ Detailed capability fields include:
|
|
|
52
52
|
- `transcript`: Kward transcript format support, including normalized messages, image/tool support, compaction summaries, and restored assistant reasoning as Pi-compatible `thinking` content blocks.
|
|
53
53
|
- `sessions`: explicit RPC session mode, JSONL persistence, supported session methods, startup auto-resume capability/default, immediate transcript support for auto-resume, RPC list support, active live-session discovery, supported linear-session fork methods, supported compaction, supported tree navigation with labels and branch summarization, explicit unsupported import support, and unsupported live session updates reported with `notification: "session/updated"`.
|
|
54
54
|
- `turns`: async turn mode, per-session concurrency, active/recent turn listing, provider-gated native busy-input steering, queued follow-up input, best-effort cancellation, recent in-memory event replay behavior, per-turn options for model/reasoning/tool scope/tool approval, and structured client context for editor integrations.
|
|
55
|
+
- `pluginChats`: optional plugin-owned chats. The capability lists opted-in chat types and methods. Clients must explicitly subscribe before receiving `pluginChat/event` notifications; plugin chats are independent from workspace sessions.
|
|
55
56
|
- `events`: `turn/event` notification details, assistant/reasoning event names, normalized tool metadata, tool update/result events, diff result support, configured workspace guardrail status, focused context and context-budget stats tool support, and explicit unsupported shell changed-file detection/session update flags.
|
|
56
57
|
- `attachments`: supported input attachment contract for `turns/start`, with accepted base64 image MIME types and a stable max byte value.
|
|
57
58
|
- `models`: model/reasoning RPC methods, explicit OpenRouter catalog listing, exposed model fields, and no scoped model support.
|
|
@@ -276,6 +277,66 @@ Moves the active branch to the selected tree entry. If the entry is a user messa
|
|
|
276
277
|
|
|
277
278
|
Returns `{ "session": {}, "editorText": "...", "cancelled": false, "aborted": false }`. Fields with no value are omitted.
|
|
278
279
|
|
|
280
|
+
## Plugin chat methods
|
|
281
|
+
|
|
282
|
+
Plugin chats are optional trusted-plugin capabilities, not Kward workspace sessions. When `initialize.capabilities.pluginChats.supported` is true, use `pluginChats/list` to discover available types.
|
|
283
|
+
|
|
284
|
+
### `pluginChats/open`
|
|
285
|
+
|
|
286
|
+
Params:
|
|
287
|
+
|
|
288
|
+
- `typeId`: an opted-in plugin chat type, such as `kward.maria`.
|
|
289
|
+
|
|
290
|
+
Opens the plugin-owned chat in this RPC process and returns its metadata plus normalized transcript.
|
|
291
|
+
|
|
292
|
+
### `pluginChats/transcript`
|
|
293
|
+
|
|
294
|
+
Params:
|
|
295
|
+
|
|
296
|
+
- `chatId`;
|
|
297
|
+
- `limit`: optional bounded page size for plugin drivers that support transcript paging;
|
|
298
|
+
- `before`: optional opaque cursor returned by a preceding page.
|
|
299
|
+
|
|
300
|
+
Returns chat metadata and its normalized transcript. Paging-capable plugin chats return the newest page first plus `hasMore` and, when more history is available, `nextBefore`. Clients send that cursor as `before` to load the preceding page. Plugins that do not opt in retain the original complete-transcript behavior.
|
|
301
|
+
|
|
302
|
+
### `pluginChats/subscribe` and `pluginChats/unsubscribe`
|
|
303
|
+
|
|
304
|
+
Params:
|
|
305
|
+
|
|
306
|
+
- `chatId`.
|
|
307
|
+
|
|
308
|
+
Subscriptions are opt-in. `pluginChats/subscribe` enables live `pluginChat/event` notifications for that chat on the current RPC connection. `pluginChats/unsubscribe` disables them without affecting the chat or archive.
|
|
309
|
+
|
|
310
|
+
### `pluginChats/turns/start`
|
|
311
|
+
|
|
312
|
+
Params:
|
|
313
|
+
|
|
314
|
+
- `chatId`;
|
|
315
|
+
- `input`;
|
|
316
|
+
- `attachments`: optional base64 image attachments using the same MIME and size limits as `turns/start`.
|
|
317
|
+
|
|
318
|
+
Queues a plugin-chat turn and returns `{ id, chatId, status, ... }`. Plugin chat turns are serialized per chat and do not use workspace sessions, agents, or model overrides.
|
|
319
|
+
|
|
320
|
+
### `pluginChats/turns/cancel`, `pluginChats/turns/status`, `pluginChats/turns/events`, `pluginChats/turns/list`, `pluginChats/turns/listActive`
|
|
321
|
+
|
|
322
|
+
These methods mirror the corresponding `turns/*` lifecycle methods, using `chatId` where a list filter is needed. Event replay is bounded in memory and subscriptions are required for live notifications.
|
|
323
|
+
|
|
324
|
+
## Plugin chat notifications
|
|
325
|
+
|
|
326
|
+
Subscribed clients receive `pluginChat/event` notifications:
|
|
327
|
+
|
|
328
|
+
```json
|
|
329
|
+
{
|
|
330
|
+
"chatId": "kward.maria",
|
|
331
|
+
"turnId": "...",
|
|
332
|
+
"sequence": 1,
|
|
333
|
+
"type": "assistantDelta",
|
|
334
|
+
"payload": { "delta": "text" }
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Known event types are `turnQueued`, `turnStarted`, `reasoningDelta`, `reasoningBoundary`, `assistantDelta`, `assistantMessage`, `toolCall`, `toolResult`, `answer`, `turnCancelRequested`, and `turnFinished`.
|
|
339
|
+
|
|
279
340
|
## Turn methods
|
|
280
341
|
|
|
281
342
|
Turns are asynchronous. A session queues turns sequentially; only one turn runs per session at a time.
|
data/doc/security.md
CHANGED
|
@@ -69,6 +69,10 @@ Pan mode does not provide a dedicated hook-approval UI. Use `deny` or `warn` for
|
|
|
69
69
|
|
|
70
70
|
See [Lifecycle hooks](lifecycle-hooks.md) for policy examples and [RPC protocol](rpc.md#Tool_approval_bridge) for the approval contract.
|
|
71
71
|
|
|
72
|
+
## Opt-in permission policy
|
|
73
|
+
|
|
74
|
+
`permissions.enabled` is off by default. When enabled, Kward can allow, ask, or deny model-requested file changes, shell commands, web tools, and MCP tools before execution. The interactive CLI uses an approval overlay; an unavailable approval bridge fails closed. The policy is not a sandbox: after Kward permits a shell command, it still runs with the permissions of your user account. See [Permissions](permissions.md) for workflows, modes, rules, and limits.
|
|
75
|
+
|
|
72
76
|
## Know what you are trusting
|
|
73
77
|
|
|
74
78
|
### Workspace instructions
|
data/doc/tabs.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use tabs when you want more than one Kward conversation open in the same interactive terminal session.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Normal tabs have their own session-backed conversation, transcript, composer state, and running agent turn. Plugins can also provide their own persistent tab types while Kward continues to supply the same composer and tab behavior. This makes tabs useful for splitting work without losing your place: one tab can investigate a bug, another can draft docs, and a third can wait on a long-running answer.
|
|
6
6
|
|
|
7
7
|
## Quick start
|
|
8
8
|
|
|
@@ -56,6 +56,7 @@ Tabs keep the conversations separate, so context from one tab does not automatic
|
|
|
56
56
|
| Command | Action |
|
|
57
57
|
| ------- | ------ |
|
|
58
58
|
| `/tab new` | Open a new tab with a fresh conversation |
|
|
59
|
+
| `/tab open <plugin-tab>` | Open a plugin-provided tab, such as a persistent companion chat. |
|
|
59
60
|
| `/tab 1` through `/tab 9` | Switch to a numbered tab |
|
|
60
61
|
| `/tab close` | Close the current tab |
|
|
61
62
|
| `/tab name <label>` | Rename the current tab |
|
|
@@ -110,9 +111,9 @@ Slash commands such as `/tab` still work while a tab is busy, so you can switch
|
|
|
110
111
|
|
|
111
112
|
## Persistence
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
Normal tabs are backed by Kward sessions. Plugins may also provide their own tab types; those tabs own their transcript and persistence independently of Kward sessions.
|
|
114
115
|
|
|
115
|
-
The tab layout is stored in Kward's config directory under `tabs/`. Session transcripts stay in the normal sessions directory. See [Sessions](session-management.md) for session naming, resuming, forking, exporting, and cleanup.
|
|
116
|
+
Kward stores the open tab list, labels, and active tab per workspace, then restores them the next time you start interactive Kward in that workspace. The tab layout is stored in Kward's config directory under `tabs/`. Session transcripts stay in the normal sessions directory. See [Sessions](session-management.md) for session naming, resuming, forking, exporting, and cleanup.
|
|
116
117
|
|
|
117
118
|
## Notes and limitations
|
|
118
119
|
|
data/doc/web-search.md
CHANGED
|
@@ -85,7 +85,9 @@ Arguments:
|
|
|
85
85
|
- `max_bytes`: default 16384, capped at 131072.
|
|
86
86
|
- `extract`: optional `auto`, `text`, or `markdown`.
|
|
87
87
|
|
|
88
|
-
In `auto` mode (default), Kward detects HTML content and extracts readable text
|
|
88
|
+
In `auto` mode (default), Kward detects HTML content and extracts readable text while stripping scripts, styles, navigation prose, and forms. It preserves headings, paragraphs, ordered and unordered lists, code blocks, blockquotes, simple tables, and inline link destinations. A bounded `Links` section lists discovered main-content and navigation destinations so an agent can continue through a site without raw HTML. Relative links are resolved against the final fetched URL. Non-HTML content is returned as cleaned text. Use `markdown` to retain Markdown structure, or `text` for plain text with link destinations in parentheses.
|
|
89
|
+
|
|
90
|
+
Kward downloads and parses up to 2 MB of a page before applying `max_bytes` to the extracted result. This lets pages with large scripts or metadata before their main content remain readable while keeping model-facing output bounded. Responses above the download limit are rejected rather than parsed as incomplete HTML.
|
|
89
91
|
|
|
90
92
|
Fetches follow up to 5 redirects and use a 10-second HTTP timeout.
|
|
91
93
|
|
|
@@ -99,4 +101,4 @@ Arguments:
|
|
|
99
101
|
- `max_bytes`: default 16384, capped at 131072.
|
|
100
102
|
- `accept`: optional HTTP `Accept` header.
|
|
101
103
|
|
|
102
|
-
Fetches follow up to 5 redirects and use a 10-second HTTP timeout.
|
|
104
|
+
Fetches follow up to 5 redirects, enforce the requested byte limit while reading the response, and use a 10-second HTTP timeout.
|
|
@@ -202,8 +202,8 @@ module Kward
|
|
|
202
202
|
parse_successful_json(response, "Anthropic OAuth token exchange")
|
|
203
203
|
end
|
|
204
204
|
|
|
205
|
-
def post_json(uri, params)
|
|
206
|
-
super(uri, params, headers: { "Accept" => "application/json" })
|
|
205
|
+
def post_json(uri, params = nil, **keyword_params)
|
|
206
|
+
super(uri, params, headers: { "Accept" => "application/json" }, **keyword_params)
|
|
207
207
|
end
|
|
208
208
|
end
|
|
209
209
|
end
|
|
@@ -4,6 +4,7 @@ require "time"
|
|
|
4
4
|
require "uri"
|
|
5
5
|
require_relative "file"
|
|
6
6
|
require_relative "../config_files"
|
|
7
|
+
require_relative "../http"
|
|
7
8
|
|
|
8
9
|
# Namespace for the Kward CLI agent runtime.
|
|
9
10
|
module Kward
|
|
@@ -15,7 +16,6 @@ module Kward
|
|
|
15
16
|
DEFAULT_SCOPE = "read:user"
|
|
16
17
|
DEFAULT_CLIENT_ID = "Iv1.b507a08c87ecfe98"
|
|
17
18
|
COPILOT_HEADERS = {
|
|
18
|
-
"User-Agent" => "GitHubCopilotChat/0.35.0",
|
|
19
19
|
"Editor-Version" => "vscode/1.107.0",
|
|
20
20
|
"Editor-Plugin-Version" => "copilot-chat/0.35.0",
|
|
21
21
|
"Copilot-Integration-Id" => "vscode-chat"
|
|
@@ -150,7 +150,7 @@ module Kward
|
|
|
150
150
|
end
|
|
151
151
|
|
|
152
152
|
def post_form(uri, params)
|
|
153
|
-
request = Net::HTTP::Post.new(uri)
|
|
153
|
+
request = Http.apply_user_agent(Net::HTTP::Post.new(uri))
|
|
154
154
|
request["Content-Type"] = "application/x-www-form-urlencoded"
|
|
155
155
|
request["Accept"] = "application/json"
|
|
156
156
|
request.body = URI.encode_www_form(params)
|
|
@@ -160,7 +160,7 @@ module Kward
|
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
def get_json(uri, headers = {})
|
|
163
|
-
request = Net::HTTP::Get.new(uri)
|
|
163
|
+
request = Http.apply_user_agent(Net::HTTP::Get.new(uri))
|
|
164
164
|
request["Accept"] = "application/json"
|
|
165
165
|
COPILOT_HEADERS.merge(headers).each { |key, value| request[key] = value }
|
|
166
166
|
|
|
@@ -6,6 +6,7 @@ require "securerandom"
|
|
|
6
6
|
require "socket"
|
|
7
7
|
require "time"
|
|
8
8
|
require "uri"
|
|
9
|
+
require_relative "../http"
|
|
9
10
|
|
|
10
11
|
# Namespace for the Kward CLI agent runtime.
|
|
11
12
|
module Kward
|
|
@@ -58,8 +59,9 @@ module Kward
|
|
|
58
59
|
socket&.close
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
def post_json(uri, params, headers: {})
|
|
62
|
-
|
|
62
|
+
def post_json(uri, params = nil, headers: {}, **keyword_params)
|
|
63
|
+
params = (params || {}).merge(keyword_params)
|
|
64
|
+
request = Http.apply_user_agent(Net::HTTP::Post.new(uri))
|
|
63
65
|
request["Content-Type"] = "application/json"
|
|
64
66
|
headers.each { |key, value| request[key] = value }
|
|
65
67
|
request.body = JSON.dump(params)
|
|
@@ -5,6 +5,7 @@ require "time"
|
|
|
5
5
|
require "uri"
|
|
6
6
|
require_relative "file"
|
|
7
7
|
require_relative "oauth_helpers"
|
|
8
|
+
require_relative "../http"
|
|
8
9
|
|
|
9
10
|
# Namespace for the Kward CLI agent runtime.
|
|
10
11
|
module Kward
|
|
@@ -215,7 +216,7 @@ module Kward
|
|
|
215
216
|
end
|
|
216
217
|
|
|
217
218
|
def post_form(uri, params)
|
|
218
|
-
request = Net::HTTP::Post.new(uri)
|
|
219
|
+
request = Http.apply_user_agent(Net::HTTP::Post.new(uri))
|
|
219
220
|
request["Content-Type"] = "application/x-www-form-urlencoded"
|
|
220
221
|
request.body = URI.encode_www_form(params)
|
|
221
222
|
|