kward 0.78.0 → 0.80.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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +13 -2
  3. data/.github/workflows/pages.yml +1 -1
  4. data/CHANGELOG.md +82 -1
  5. data/Gemfile.lock +2 -2
  6. data/README.md +33 -38
  7. data/doc/agent-tools.md +10 -11
  8. data/doc/api.md +3 -3
  9. data/doc/authentication.md +79 -110
  10. data/doc/code-search.md +1 -1
  11. data/doc/composer.md +1 -1
  12. data/doc/configuration.md +136 -27
  13. data/doc/context-budgeting.md +6 -6
  14. data/doc/context-tools.md +2 -2
  15. data/doc/editor.md +7 -7
  16. data/doc/extensibility.md +9 -29
  17. data/doc/files.md +6 -6
  18. data/doc/getting-started.md +3 -3
  19. data/doc/git.md +8 -2
  20. data/doc/lifecycle-hooks.md +5 -4
  21. data/doc/local-models.md +130 -0
  22. data/doc/mcp.md +5 -5
  23. data/doc/permissions.md +15 -5
  24. data/doc/plugins.md +39 -4
  25. data/doc/prompt-templates.md +105 -0
  26. data/doc/providers.md +140 -0
  27. data/doc/releasing.md +11 -3
  28. data/doc/rpc.md +65 -31
  29. data/doc/sandboxing.md +142 -0
  30. data/doc/security.md +31 -10
  31. data/doc/session-management.md +2 -4
  32. data/doc/skills.md +10 -0
  33. data/doc/tabs.md +37 -0
  34. data/doc/telegram.md +138 -0
  35. data/doc/transports.md +209 -0
  36. data/doc/usage.md +11 -3
  37. data/doc/web-search.md +3 -3
  38. data/doc/workspace-tools.md +4 -4
  39. data/examples/plugins/stardate_footer.rb +10 -0
  40. data/examples/plugins/telegram/plugin.rb +39 -0
  41. data/examples/plugins/telegram/telegram_api.rb +136 -0
  42. data/examples/plugins/telegram/telegram_transport.rb +304 -0
  43. data/kward.gemspec +1 -1
  44. data/lib/kward/ansi.rb +1 -0
  45. data/lib/kward/auth/api_key_store.rb +99 -0
  46. data/lib/kward/cli/auth_commands.rb +72 -13
  47. data/lib/kward/cli/commands.rb +7 -0
  48. data/lib/kward/cli/doctor.rb +21 -0
  49. data/lib/kward/cli/git.rb +31 -8
  50. data/lib/kward/cli/plugins.rb +1 -0
  51. data/lib/kward/cli/prompt_interface.rb +7 -0
  52. data/lib/kward/cli/rendering.rb +6 -2
  53. data/lib/kward/cli/runtime_helpers.rb +4 -2
  54. data/lib/kward/cli/sessions.rb +1 -1
  55. data/lib/kward/cli/settings.rb +162 -36
  56. data/lib/kward/cli/slash_commands.rb +109 -1
  57. data/lib/kward/cli/tabs.rb +102 -37
  58. data/lib/kward/cli/transports.rb +67 -0
  59. data/lib/kward/cli/worktrees.rb +360 -0
  60. data/lib/kward/cli.rb +22 -1
  61. data/lib/kward/config_files.rb +106 -4
  62. data/lib/kward/conversation.rb +27 -8
  63. data/lib/kward/git_worktree_manager.rb +291 -0
  64. data/lib/kward/local_command_runner.rb +2 -2
  65. data/lib/kward/model/azure_openai_config.rb +54 -0
  66. data/lib/kward/model/catalog.rb +113 -0
  67. data/lib/kward/model/client.rb +364 -23
  68. data/lib/kward/model/model_info.rb +60 -1
  69. data/lib/kward/model/payloads.rb +64 -4
  70. data/lib/kward/model/provider_catalog.rb +114 -0
  71. data/lib/kward/model/sources.rb +70 -0
  72. data/lib/kward/model/stream_parser.rb +141 -39
  73. data/lib/kward/pan/index.html.erb +50 -0
  74. data/lib/kward/pan/server.rb +49 -2
  75. data/lib/kward/permissions/policy.rb +18 -4
  76. data/lib/kward/plugin_chat_runtime.rb +374 -0
  77. data/lib/kward/plugin_registry.rb +81 -8
  78. data/lib/kward/private_file.rb +9 -3
  79. data/lib/kward/prompt_interface/approval_prompt.rb +2 -0
  80. data/lib/kward/prompt_interface/composer_renderer.rb +1 -5
  81. data/lib/kward/prompt_interface/editor/auto_indent.rb +46 -0
  82. data/lib/kward/prompt_interface/editor/buffer.rb +18 -4
  83. data/lib/kward/prompt_interface/editor/controller.rb +43 -10
  84. data/lib/kward/prompt_interface/editor/modes/vibe.rb +2 -2
  85. data/lib/kward/prompt_interface/editor/renderer.rb +8 -8
  86. data/lib/kward/prompt_interface/editor/state.rb +3 -9
  87. data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +184 -9
  88. data/lib/kward/prompt_interface/file_overlay.rb +2 -2
  89. data/lib/kward/prompt_interface/git_prompt.rb +7 -3
  90. data/lib/kward/prompt_interface/interactive/controller.rb +4 -4
  91. data/lib/kward/prompt_interface/interactive/renderer.rb +4 -1
  92. data/lib/kward/prompt_interface/interactive/state.rb +2 -2
  93. data/lib/kward/prompt_interface/project_browser.rb +25 -6
  94. data/lib/kward/prompt_interface/runtime_state.rb +22 -6
  95. data/lib/kward/prompt_interface/screen.rb +3 -0
  96. data/lib/kward/prompt_interface/selection_prompt.rb +7 -1
  97. data/lib/kward/prompt_interface/transcript_buffer.rb +24 -2
  98. data/lib/kward/prompt_interface.rb +71 -12
  99. data/lib/kward/prompts/commands.rb +3 -1
  100. data/lib/kward/prompts.rb +22 -10
  101. data/lib/kward/rpc/auth_manager.rb +112 -152
  102. data/lib/kward/rpc/config_manager.rb +46 -6
  103. data/lib/kward/rpc/plugin_chat_manager.rb +56 -194
  104. data/lib/kward/rpc/prompt_bridge.rb +8 -4
  105. data/lib/kward/rpc/redactor.rb +1 -1
  106. data/lib/kward/rpc/server.rb +102 -14
  107. data/lib/kward/rpc/session_manager.rb +151 -38
  108. data/lib/kward/sandbox/capabilities.rb +39 -0
  109. data/lib/kward/sandbox/command_runner.rb +28 -0
  110. data/lib/kward/sandbox/environment.rb +24 -0
  111. data/lib/kward/sandbox/linux_bubblewrap_runner.rb +71 -0
  112. data/lib/kward/sandbox/macos_seatbelt_runner.rb +96 -0
  113. data/lib/kward/sandbox/passthrough_runner.rb +13 -0
  114. data/lib/kward/sandbox/policy.rb +74 -0
  115. data/lib/kward/sandbox/runner_factory.rb +55 -0
  116. data/lib/kward/sandbox/unavailable_runner.rb +21 -0
  117. data/lib/kward/sandbox.rb +9 -0
  118. data/lib/kward/session_store.rb +110 -24
  119. data/lib/kward/skills/capture.rb +144 -0
  120. data/lib/kward/tab_driver.rb +6 -3
  121. data/lib/kward/tools/git_commit.rb +39 -0
  122. data/lib/kward/tools/registry.rb +26 -6
  123. data/lib/kward/tools/tool_call.rb +1 -0
  124. data/lib/kward/transport/gateway.rb +253 -0
  125. data/lib/kward/transport/host.rb +343 -0
  126. data/lib/kward/transport/manager.rb +179 -0
  127. data/lib/kward/transport/plugin_chat_gateway.rb +186 -0
  128. data/lib/kward/transport/runtime.rb +41 -0
  129. data/lib/kward/transport/store.rb +101 -0
  130. data/lib/kward/transport.rb +204 -0
  131. data/lib/kward/version.rb +1 -1
  132. data/lib/kward/workspace.rb +18 -3
  133. data/lib/kward/workspace_factory.rb +34 -0
  134. data/templates/default/fulldoc/html/js/kward.js +3 -0
  135. data/templates/default/kward_navigation.rb +8 -1
  136. data/templates/default/layout/html/setup.rb +5 -0
  137. metadata +41 -2
data/doc/security.md CHANGED
@@ -6,8 +6,8 @@ This guide explains the trust boundaries and gives you a safe way to start work
6
6
 
7
7
  ## The short version
8
8
 
9
- - Kward and anything it launches run with your operating-system permissions.
10
- - File tools stay inside the active workspace by default, but shell commands are not sandboxed.
9
+ - Kward and its trusted host-process extensions run with your operating-system permissions.
10
+ - File tools stay inside the active workspace by default. Worktree-backed tabs force workspace guardrails and an OS-enforced sandbox for model-requested shell commands when the platform supports it.
11
11
  - Existing files must be read before Kward's file tools can edit or overwrite them.
12
12
  - Plugins, MCP servers, command hooks, and HTTP hooks should be configured only from sources and endpoints you trust.
13
13
  - Project skills and workspace hooks are disabled until you opt in or trust them.
@@ -16,6 +16,12 @@ This guide explains the trust boundaries and gives you a safe way to start work
16
16
 
17
17
  If a task involves secrets, production credentials, customer data, or an untrusted repository, narrow the workspace and tools before asking Kward to act.
18
18
 
19
+ ## Local model endpoints and replacement prompts
20
+
21
+ The Local provider defaults to loopback endpoints. If you set `local_base_url` to another host, Kward can send prompts, selected workspace content, attachments, and tool results to that host. Treat a non-loopback endpoint as a model provider: use TLS and authentication where appropriate, and do not assume a private-network address is harmless.
22
+
23
+ A `system_prompt.file` replacement is model-visible and may be retained in session prompt snapshots. Do not put tokens, credentials, customer data, or other secrets in that file. Replacement mode intentionally removes Kward's normal prompt layers, including workspace guidance and skill descriptions; verify it with `kward sysprompt` before using a permissive local model.
24
+
19
25
  ## A safe first run in an unfamiliar repository
20
26
 
21
27
  Start with project-provided extensions disabled, which is the default:
@@ -42,7 +48,7 @@ Before enabling or running anything:
42
48
  6. Do not add repository-provided Ruby files to `~/.kward/plugins` unless you are willing to run them as your user.
43
49
  7. Ask Kward to explain proposed edits and commands before applying them when the impact is unclear.
44
50
 
45
- For especially sensitive work, use a disposable checkout, container, virtual machine, or restricted operating-system account. Kward's own guardrails do not replace operating-system isolation.
51
+ For especially sensitive work, use a disposable checkout, container, virtual machine, or restricted operating-system account. Enable [command sandboxing](sandboxing.md) for model-requested shell commands, but remember that it does not sandbox the Kward host process, plugins, MCP servers, hooks, or interactive shell features.
46
52
 
47
53
  ## Workspace access is a guardrail, not a sandbox
48
54
 
@@ -50,12 +56,20 @@ Kward's built-in file tools normally resolve paths inside the active workspace.
50
56
 
51
57
  These protections reduce accidental edits. They do not contain the whole process:
52
58
 
53
- - `run_shell_command`, `!command`, `/shell`, and `/pty` run with your user permissions.
54
- - A shell command can access files, environment variables, processes, and networks available to your account.
59
+ - With `sandbox.mode: off` (the default), `run_shell_command`, `!command`, `/shell`, and `/pty` run with your user permissions.
60
+ - A non-off [command sandbox](sandboxing.md) restricts only model-requested `run_shell_command` workers and their descendants. It does not cover `!command`, `/shell`, or `/pty`.
55
61
  - Plugins, command hooks, and MCP servers are local processes with the same general operating-system access.
56
62
  - Read-before-edit applies to Kward's file tools, not to arbitrary shell commands or extension code.
57
63
 
58
- You can disable the file boundary with `tools.workspace_guardrails: false`, but doing so broadens file-tool access and is rarely needed. See [Workspace tools](workspace-tools.md) for exact limits and [Configuration](configuration.md#Tool_workspace_guardrails) for the setting.
64
+ You can disable the file boundary with `tools.workspace_guardrails: false`, but worktree-backed tabs do not honor that weakening: they always keep file guardrails enabled. See [Workspace tools](workspace-tools.md) for exact limits and [Configuration](configuration.md#Tool_workspace_guardrails) for the setting.
65
+
66
+ ## Worktree tab boundaries
67
+
68
+ A worktree-backed tab uses its linked worktree as the active workspace and requires an OS-enforced `workspace_write` command sandbox. If the platform cannot provide filesystem enforcement, Kward refuses to activate the worktree rather than running model-requested shell commands unrestricted.
69
+
70
+ The strict worktree agent disables configured MCP clients and lifecycle hooks for that tab because those extensions run outside the command-worker sandbox. The interactive `/shell`, `!command`, and `/pty` features remain host-process features and are not covered by the worktree boundary. Use them only when you intentionally want to run a user-directed command outside the model command sandbox.
71
+
72
+ Git worktrees share repository metadata. Kward keeps Git metadata protection enabled for generic model-requested commands. Active worktree tabs provide a narrow host-side `git_commit` tool for explicit agent commits; the interactive `/git` flow remains the manual review, staging, and commit path.
59
73
 
60
74
  ## Tool approvals and policy hooks
61
75
 
@@ -95,7 +109,11 @@ Project hooks in `.kward/hooks.json` run only after `/hooks trust`. Trust is tie
95
109
 
96
110
  ### Plugins
97
111
 
98
- Ruby plugins in `~/.kward/plugins/*.rb` run inside the Kward process with your permissions. They can read files and environment variables, write files, run commands, and make network requests. Kward intentionally does not load plugins from a workspace directory.
112
+ Ruby plugins in `~/.kward/plugins/*.rb` or package entrypoints at
113
+ `~/.kward/plugins/*/plugin.rb` run inside the Kward process with your permissions.
114
+ They can read files and environment variables, write files, run commands, and
115
+ make network requests. Kward intentionally does not load plugins from a
116
+ workspace directory.
99
117
 
100
118
  ### MCP servers
101
119
 
@@ -116,7 +134,7 @@ To answer a turn, Kward sends the assembled conversation context to the active m
116
134
  - images you attach,
117
135
  - summaries produced during compaction.
118
136
 
119
- Provider retention and training policies are controlled by the provider and your account or organization. Do not give Kward content you are not permitted to send to that provider.
137
+ Provider retention and training policies are controlled by the provider and your account or organization. Do not give Kward content you are not permitted to send to that provider. Review [Model providers](providers.md) before switching endpoints, especially when moving between direct, routed, cloud, and local runtimes.
120
138
 
121
139
  ### Web tools
122
140
 
@@ -144,7 +162,8 @@ Kward keeps user data under `~/.kward` by default, or mostly beside `KWARD_CONFI
144
162
 
145
163
  | Data | Typical location | Notes |
146
164
  | --- | --- | --- |
147
- | Main config and OpenRouter key | `~/.kward/config.json` | Do not commit or share it. |
165
+ | Main config | `~/.kward/config.json` | Non-secret provider/model setup; do not commit environment-specific configuration blindly. |
166
+ | Provider API keys | `~/.kward/api_keys.json` | Separate private store, written with mode `0600` when possible; environment variables take precedence. |
148
167
  | OAuth credentials | `~/.kward/auth.json`, `anthropic_auth.json`, `github_auth.json` | Written with mode `0600` when possible. |
149
168
  | Sessions and tool results | `~/.kward/sessions/` | Conversation, file/tool output, and compaction history; files use mode `0600`. |
150
169
  | Prompt history | `~/.kward/history/` | Workspace-scoped submitted prompts; files use mode `0600`. |
@@ -155,6 +174,8 @@ Kward keeps user data under `~/.kward` by default, or mostly beside `KWARD_CONFI
155
174
 
156
175
  Private file modes help on normal Unix-like systems but do not protect data from your own account, privileged users, backups, malware, or a compromised machine. Session exports are written to the path you choose and should be protected separately.
157
176
 
177
+ RPC authentication status and errors are sanitized, and API-key values are never returned. RPC is still a trusted-local interface: a client allowed to call `auth/loginWithApiKey`, `config/update`, or model/tool methods can change credentials and send workspace context to external providers. Do not expose the RPC process directly to untrusted users or networks.
178
+
158
179
  Use these commands to inspect or remove stored credentials and context:
159
180
 
160
181
  ```bash
@@ -173,7 +194,7 @@ See [Authentication](authentication.md), [Sessions](session-management.md), [Mem
173
194
  ## Practical habits
174
195
 
175
196
  - Keep secrets out of prompts, screenshots, shell output, plugin context, and hook messages.
176
- - Prefer temporary environment variables to storing short-lived API keys in config.
197
+ - Prefer temporary environment variables for short-lived API keys; saved keys belong in Kward's private key store, never in prompts or project config.
177
198
  - Disable web search for private work that should not trigger external requests.
178
199
  - Keep memory off unless cross-session recall is useful and appropriate.
179
200
  - Review diffs and `git status` before committing agent changes.
@@ -1,10 +1,8 @@
1
1
  # Sessions
2
2
 
3
- Sessions are Kward's way of keeping real work alive across time. An interactive chat is not just a scrollback buffer: it is saved as a session with messages, tool calls, tool results, compaction checkpoints, branches, and enough metadata for Kward to resume the work later.
3
+ Kward saves each interactive chat as a session, so you can close the terminal and continue the work later. Sessions keep the conversation, tool activity, branches, and compaction history needed to restore the right context.
4
4
 
5
- Think of sessions as **context management**. They let you decide which conversation context Kward should use next.
6
-
7
- Think of rewinding and forking as **context time travel**. You can go back to an earlier prompt, try a different path, and keep both versions instead of losing the original work.
5
+ Rewinding and forking are a kind of **context time travel**: go back to an earlier prompt, try another path, and keep both versions instead of losing the original work.
8
6
 
9
7
  ## Where sessions live
10
8
 
data/doc/skills.md CHANGED
@@ -42,6 +42,16 @@ The `name` and `description` fields are required. The description matters: Kward
42
42
 
43
43
  Skill names should be lowercase letters, numbers, and hyphens, such as `testing`, `code-review`, or `release-checklist`.
44
44
 
45
+ ## Capture a skill from a session
46
+
47
+ Use `/skill capture` after a useful session. Choose any saved session; Kward uses its active branch and asks your active model to draft a reusable `SKILL.md`. Review and edit the draft in the terminal editor, then save it explicitly. Captured skills always go to your personal Kward skill directory and are not activated automatically.
48
+
49
+ The draft request includes the complete persisted branch, including available raw tool output and saved session context. Treat it like any other model request: do not capture a session whose contents you are not willing to send to the active provider. Kward rejects a source that does not fit the active model context instead of silently truncating it.
50
+
51
+ If the reviewed name already exists, Kward requires an explicit overwrite confirmation. Activate a saved draft later with `/skill <name>`.
52
+
53
+ Pan provides the same saved-session picker and editable review flow. RPC clients use `skills/captureSessions`, `skills/captureDraft`, and `skills/saveCapturedDraft`; see the [RPC protocol](rpc.md).
54
+
45
55
  ## Skill locations
46
56
 
47
57
  User-level skills are available in every project:
data/doc/tabs.md CHANGED
@@ -33,6 +33,36 @@ Close the current tab:
33
33
 
34
34
  The tab bar appears at the bottom of the composer. The active tab is framed, and each tab label starts with its number.
35
35
 
36
+ ## Worktree tabs
37
+
38
+ A normal session tab can be activated in a linked Git worktree after you have researched a task and decide that implementation should be isolated:
39
+
40
+ ```text
41
+ /tab worktree
42
+ ```
43
+
44
+ When enabled, Kward keeps the same tab and transcript but rebuilds its agent against a new worktree. The tab label includes the worktree branch. The worktree is created from `HEAD`, so Kward warns when the original workspace is dirty and leaves those existing changes in the original checkout; it does not copy them automatically. `/tab worktree activate` is an explicit alias for the same action.
45
+
46
+ Running `/tab worktree` again leaves an active worktree unchanged. To return the tab to its original workspace while keeping the linked worktree and branch, detach it explicitly:
47
+
48
+ ```text
49
+ /tab worktree detach
50
+ ```
51
+
52
+ Inspect, merge, or remove the binding explicitly:
53
+
54
+ ```text
55
+ /tab worktree status
56
+ /tab worktree merge
57
+ /tab worktree remove
58
+ ```
59
+
60
+ `/tab worktree merge` merges the active worktree's clean, committed branch directly into the branch currently checked out in its original workspace. Kward shows the source and target revisions and requires confirmation. Both worktrees must be clean. If Git reports conflicts, Kward leaves the original workspace in its normal merge state; resolve the conflicts there or cancel them with `/tab worktree merge abort`.
61
+
62
+ Removal refuses a dirty worktree and keeps its branch. A worktree that is missing or no longer points at the recorded branch is restored as unavailable rather than silently falling back to the original workspace.
63
+
64
+ Worktree tabs are available for normal session tabs, not plugin-owned tabs. Kward's file tools, `@`/`$` completion, `/files` browser, integrated editor, and model-requested shell workers use the active worktree root. Model operations retain strict workspace guardrails. The interactive `/shell`, `!command`, and `/pty` features remain user-controlled and are not contained by the model command sandbox; use them only when that is intentional. Generic shell Git writes remain protected. When explicitly asked to commit, the agent can use the active tab's narrow `git_commit` tool; use the interactive `/git` flow when you want to review and commit changes yourself.
65
+
36
66
  ## Common workflow
37
67
 
38
68
  A typical multi-tab flow looks like this:
@@ -61,6 +91,13 @@ Tabs keep the conversations separate, so context from one tab does not automatic
61
91
  | `/tab close` | Close the current tab |
62
92
  | `/tab name <label>` | Rename the current tab |
63
93
  | `/tab rename <label>` | Same as `/tab name` |
94
+ | `/tab worktree` | Create or activate the current session tab's linked Git worktree |
95
+ | `/tab worktree activate` | Explicitly create or activate the current session tab's linked Git worktree |
96
+ | `/tab worktree detach` | Return to the original workspace while keeping the linked worktree and branch |
97
+ | `/tab worktree status` | Show the current tab's worktree binding and Git status |
98
+ | `/tab worktree merge` | Merge the current worktree branch into the branch checked out in its original workspace |
99
+ | `/tab worktree merge abort` | Abort a conflicted worktree merge in the original workspace |
100
+ | `/tab worktree remove` | Remove a clean linked worktree and keep its branch |
64
101
  | `/tab move left` | Move the current tab one slot left |
65
102
  | `/tab move right` | Move the current tab one slot right |
66
103
  | `/tab move <number>` | Move the current tab to a numbered position |
data/doc/telegram.md ADDED
@@ -0,0 +1,138 @@
1
+ # Telegram transport
2
+
3
+ Use the first-party Telegram transport to talk to a normal Kward session from a
4
+ private Telegram chat. The example lives under `examples/plugins/` and uses the
5
+ Telegram Bot API's long-polling interface.
6
+
7
+ The transport runs as trusted local Ruby code. Install it only on a machine
8
+ where the bot can safely access its configured workspace and Kward tools.
9
+
10
+ ## Install the plugin
11
+
12
+ Copy the packaged plugin into the user plugin directory:
13
+
14
+ ```bash
15
+ mkdir -p ~/.kward/plugins
16
+ cp -R examples/plugins/telegram ~/.kward/plugins/
17
+ ```
18
+
19
+ Kward loads the package through `~/.kward/plugins/telegram/plugin.rb`.
20
+
21
+ Installing the plugin does not connect to Telegram. Start it explicitly with
22
+ `kward transport run com.kward.telegram`.
23
+
24
+ ## Configuration
25
+
26
+ Configure one fixed workspace and explicit numeric allowlists:
27
+
28
+ ```json
29
+ {
30
+ "transports": {
31
+ "com.kward.telegram": {
32
+ "workspace": "/Users/me/src/project",
33
+ "allowed_user_ids": [123456789],
34
+ "allowed_chat_ids": [123456789],
35
+ "poll_timeout_seconds": 25
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ Do not put the bot token in the config file. Set it in the environment of the
42
+ foreground process instead:
43
+
44
+ ```bash
45
+ export TELEGRAM_BOT_TOKEN='replace-with-the-token-from-botfather'
46
+ ```
47
+
48
+ The plugin also accepts the transport-specific secret name exposed by the
49
+ host, but `TELEGRAM_BOT_TOKEN` is the recommended explicit variable.
50
+
51
+ ## Run it
52
+
53
+ First check that Kward discovers the plugin:
54
+
55
+ ```bash
56
+ kward transport list
57
+ ```
58
+
59
+ Then run the personal-assistant transport in the foreground:
60
+
61
+ ```bash
62
+ kward transport run com.kward.telegram
63
+ ```
64
+
65
+ To override the workspace configured for this process:
66
+
67
+ ```bash
68
+ kward transport run com.kward.telegram /Users/me/src/project
69
+ # or:
70
+ kward --working-directory /Users/me/src/project transport run com.kward.telegram
71
+ ```
72
+
73
+ The plugin also registers `com.kward.telegram.isolated`. It uses the generic
74
+ `isolated_chat` execution profile: no tools, no plugin commands, no memory, no
75
+ attachments, and no approval interactions. Configure that transport with a
76
+ separate empty workspace:
77
+
78
+ ```json
79
+ {
80
+ "transports": {
81
+ "com.kward.telegram.isolated": {
82
+ "workspace": "/var/lib/kward-chat/workspace",
83
+ "allowed_user_ids": [123456789],
84
+ "allowed_chat_ids": [123456789],
85
+ "poll_timeout_seconds": 25
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ For stronger separation, run it with a dedicated home and config directory
92
+ that contain only this plugin:
93
+
94
+ ```bash
95
+ HOME=/var/lib/kward-chat \
96
+ KWARD_CONFIG_PATH=/var/lib/kward-chat/.kward/config.json \
97
+ TELEGRAM_BOT_TOKEN="$TELEGRAM_BOT_TOKEN" \
98
+ kward transport run com.kward.telegram.isolated
99
+ ```
100
+
101
+ The process removes any existing Telegram webhook, validates the bot token,
102
+ and begins long polling. Run only one polling process for a bot token; multiple
103
+ pollers conflict with each other. Supervise the process externally for
104
+ restart-on-failure behavior.
105
+
106
+ ## Current scope
107
+
108
+ The adapter supports:
109
+
110
+ - private text messages,
111
+ - one or more explicitly allowlisted numeric users and chats,
112
+ - fixed workspace routing,
113
+ - normal Kward session persistence,
114
+ - idempotent Telegram update claims,
115
+ - final responses split at Telegram's message length limit,
116
+ - tool approval buttons and single-question choice buttons for the personal
117
+ transport.
118
+
119
+ The isolated transport intentionally has no tool or interaction capabilities.
120
+
121
+ It intentionally does not yet support groups, media uploads, webhooks,
122
+ streaming message edits, inline mode, or multiple independent workspace
123
+ policies.
124
+
125
+ The transport allowlist is enforced before external messages reach a Kward
126
+ session. Keep the first deployment restricted to one user, one private chat,
127
+ one workspace, and conservative tool permissions. Never use incoming Telegram
128
+ text to select a workspace or tool scope.
129
+
130
+ ## Account setup boundary
131
+
132
+ The code is ready for account setup, but no Telegram account, bot token, or
133
+ network test is required to run the automated tests. After creating a bot,
134
+ set `TELEGRAM_BOT_TOKEN`, replace the placeholder numeric IDs, and test the
135
+ foreground process with a private chat first.
136
+
137
+ See the official [Telegram Bot API](https://core.telegram.org/bots/api) for
138
+ BotFather setup, polling behavior, message limits, and callback queries.
data/doc/transports.md ADDED
@@ -0,0 +1,209 @@
1
+ # Transports
2
+
3
+ A transport lets people use Kward through another service, such as Telegram,
4
+ Slack, Discord, email, or HTTP. It receives messages from the service and sends
5
+ Kward's responses back.
6
+
7
+ A transport can target either a normal Kward session or an explicitly
8
+ transport-capable plugin-owned chat. The transport handles service-specific
9
+ details such as identities and message formatting. The target continues to
10
+ own sessions, transcripts, tools, memory, and policy.
11
+
12
+ Start with the first-party [Telegram transport](telegram.md) to see a complete
13
+ example. It uses long polling and starts explicitly rather than with the normal
14
+ interactive CLI.
15
+
16
+ ## How transports fit into Kward
17
+
18
+ A transport should use the public transport host API rather than creating an
19
+ `Agent`, opening a `SessionStore`, or calling the RPC server directly. This
20
+ keeps session and tool behavior consistent across the CLI, RPC clients, and
21
+ external services.
22
+
23
+ Transport plugins are different from plugin-owned tabs:
24
+
25
+ - A plugin tab runs its own chat and stores its own data.
26
+ - A normal-session transport connects an external conversation to a Kward session.
27
+ - A plugin-chat transport connects an external conversation to an explicitly
28
+ opted-in plugin chat and preserves that plugin's storage and model behavior.
29
+
30
+ Transports run as trusted local Ruby code. Install only transports you trust.
31
+
32
+ ## Registration
33
+
34
+ Transport registration is intentionally separate from transport startup. A
35
+ plugin declares a transport and returns an adapter when Kward starts it:
36
+
37
+ ```ruby
38
+ Kward.plugin do |plugin|
39
+ plugin.transport(
40
+ "example",
41
+ id: "com.example.transport",
42
+ capabilities: {
43
+ inbound: %i[text],
44
+ outbound: %i[text],
45
+ streaming: :aggregate
46
+ }
47
+ ) do |host, config|
48
+ ExampleTransport.new(host: host, config: config)
49
+ end
50
+ end
51
+ ```
52
+
53
+ Loading a plugin must not make network connections. Transports are started
54
+ explicitly by the transport runtime.
55
+
56
+ The `id` is a stable identifier used for configuration and persisted transport
57
+ state. It must not change after release.
58
+
59
+ ## Message flow
60
+
61
+ Inbound platform messages are normalized before they reach Kward. A normalized
62
+ message contains an external transport ID, conversation ID, actor identity,
63
+ message ID, text, attachments, reply context, and an idempotency key.
64
+
65
+ The transport resolves the external conversation to a Kward session or an
66
+ opted-in plugin chat, submits a turn, and subscribes to normalized turn events.
67
+ The transport decides whether to stream, edit, aggregate, or otherwise render
68
+ those events for its platform.
69
+
70
+ A delivery failure must not fail the underlying model turn. The transport is
71
+ responsible for retrying or reporting delivery failures, while the transcript
72
+ and final turn status remain authoritative.
73
+
74
+ ### Targeting a plugin-owned chat
75
+
76
+ A plugin must explicitly opt into external targeting:
77
+
78
+ ```ruby
79
+ plugin.tab_type(
80
+ "example-bot",
81
+ id: "com.example.bot",
82
+ rpc: true,
83
+ transport: true
84
+ ) do |host, descriptor|
85
+ ExampleBot::Chat.new(client: host.client, descriptor: descriptor)
86
+ end
87
+ ```
88
+
89
+ A transport resolves it through the host instead of opening a workspace session:
90
+
91
+ ```ruby
92
+ chat = host.plugin_chats.resolve(
93
+ type_id: "com.example.bot",
94
+ conversation: conversation,
95
+ actor: actor,
96
+ scope_key: "conversation:#{conversation.external_id}"
97
+ )
98
+
99
+ turn = chat.start_turn(message.text)
100
+ turn.subscribe { |event| render_event(event) }
101
+ ```
102
+
103
+ Plugin-chat drivers may accept a `context:` keyword on `submit`. Transport turns
104
+ provide the authenticated actor there. Existing drivers that do not accept the
105
+ keyword continue to work, but cannot use actor-specific context.
106
+
107
+ Plugin-chat transport IDs, turn events, transcript storage, and authorization
108
+ remain separate from normal workspace sessions. A plugin's `singleton: :global`
109
+ setting also means that all transport conversations share that one plugin
110
+ runtime, so use scoped plugin drivers when participants must be isolated.
111
+
112
+ ## Interactions
113
+
114
+ Questions and tool approvals are exposed as transport-neutral interaction
115
+ requests. A transport may render them as buttons, forms, replies, or another
116
+ platform-specific control. It submits the selected answer through the host
117
+ API.
118
+
119
+ Transports that cannot support interactive approvals must use an explicit
120
+ configured fallback policy; they must not leave an agent turn waiting forever.
121
+
122
+ ## Storage and routing
123
+
124
+ Transport plugins receive namespaced durable storage for state such as:
125
+
126
+ - external conversation to Kward session bindings,
127
+ - polling cursors and webhook update IDs,
128
+ - external message IDs used for edits,
129
+ - pending interaction mappings, and
130
+ - delivery retry state.
131
+
132
+ Transport storage is separate from Kward session files. External update IDs
133
+ should be recorded so duplicate webhook or polling deliveries do not start
134
+ duplicate turns.
135
+
136
+ ## Identity and policy
137
+
138
+ The transport authenticates the external actor, but Kward policy controls what
139
+ that actor may do. Policy should constrain allowed actors and conversations,
140
+ workspace selection, tools, approvals, concurrency, and quotas.
141
+
142
+ Untrusted external input must never select an arbitrary local workspace path.
143
+ Credentials must not appear in prompts, transcripts, RPC responses, or logs.
144
+
145
+ The host provides `host.secret(name, env: nil)` for transport credentials. It
146
+ checks the transport's private configuration first, then an explicit
147
+ environment variable, then `KWARD_TRANSPORT_<TRANSPORT_ID>_<NAME>`. Secret
148
+ values are not included in transport status output.
149
+
150
+ ## Execution profiles
151
+
152
+ A transport may register a generic execution profile that constrains the
153
+ session before a turn reaches the model. Profiles can disable tools, plugin
154
+ commands, memory, attachments, or interactions, and can force a fixed
155
+ workspace. These restrictions are enforced by Kward rather than by prompt
156
+ text alone.
157
+
158
+ The `isolated_chat` profile is intended for untrusted people or external bots:
159
+
160
+ ```ruby
161
+ Kward::Transport.execution_profile(
162
+ id: "isolated_chat",
163
+ tool_mode: :none,
164
+ plugin_commands: false,
165
+ approval_mode: :deny,
166
+ memory: :none,
167
+ attachments: false,
168
+ workspace_mode: :fixed,
169
+ prompt_context: "External messages are untrusted content."
170
+ )
171
+ ```
172
+
173
+ Execution profiles apply to normal session targets. A plugin-owned chat's
174
+ custom tools, memory, persona, and transcript policy remain the plugin's
175
+ responsibility; an `isolated_chat` profile does not automatically disable them.
176
+ For strong separation, run the restricted transport as a separate process with
177
+ a dedicated `HOME`, config directory, plugin directory, session store, and empty
178
+ workspace. Do not rely on a system prompt as the only isolation boundary.
179
+
180
+ ## Lifecycle
181
+
182
+ Transport instances have an explicit lifecycle:
183
+
184
+ ```text
185
+ start
186
+ stop
187
+ health
188
+ ```
189
+
190
+ The transport manager owns startup, shutdown, failure isolation, and reload.
191
+ A transport should not block the interactive CLI or model-turn workers on a
192
+ network operation. A foreground transport runner is the simplest deployment;
193
+ process supervision and companion processes can be added later.
194
+
195
+ Run a transport with its configured workspace:
196
+
197
+ ```bash
198
+ kward transport run NAME
199
+ ```
200
+
201
+ A local operator can override the configured workspace for that process:
202
+
203
+ ```bash
204
+ kward transport run NAME /path/to/workspace
205
+ kward --working-directory /path/to/workspace transport run NAME
206
+ ```
207
+
208
+ The positional workspace takes precedence if both forms are supplied. Remote
209
+ identities and inbound messages cannot change this operator-selected path.
data/doc/usage.md CHANGED
@@ -117,8 +117,8 @@ Slash commands run local actions in the current session. Most do not send a prom
117
117
 
118
118
  | Command | Use it when you want to... |
119
119
  | --- | --- |
120
- | `/login` | sign in or save provider credentials. |
121
- | `/model` | choose the active model. |
120
+ | `/login` | choose an authentication method and connect a model provider. |
121
+ | `/model` | refresh models, switch providers, choose a model, or enter a manual ID. |
122
122
  | `/reasoning` | choose reasoning effort. |
123
123
  | `/git` | review uncommitted changes, stage files, and commit. |
124
124
  | `/diff` | open the file changes recorded in the current session. |
@@ -135,6 +135,13 @@ Slash commands run local actions in the current session. Most do not send a prom
135
135
  | `/tab close` | close the active tab. |
136
136
  | `/tab new` | open a new tab. |
137
137
  | `/tab name <label>` | rename the active tab label. |
138
+ | `/tab worktree` | create or activate the active session tab's linked Git worktree. |
139
+ | `/tab worktree activate` | explicitly create or activate the active session tab's linked Git worktree. |
140
+ | `/tab worktree detach` | return to the original workspace while keeping the linked worktree and branch. |
141
+ | `/tab worktree status` | inspect the active tab's worktree binding and changes. |
142
+ | `/tab worktree merge` | merge a clean worktree branch into the branch checked out in its original workspace. |
143
+ | `/tab worktree merge abort` | abort a conflicted worktree merge in the original workspace. |
144
+ | `/tab worktree remove` | remove a clean linked worktree while keeping its branch. |
138
145
  | `/session` | open the saved sessions picker or continue a previous session by path. |
139
146
  | `/resume` | alias for `/session`. |
140
147
  | `/session name <name>` | name or clear the current session. |
@@ -237,8 +244,9 @@ During a turn, Kward can inspect and change the workspace with tools for:
237
244
 
238
245
  Important guardrails:
239
246
 
240
- - Existing files must be read before Kward can edit or overwrite them.
247
+ - Workspace guardrails limit file tools to the active workspace; existing files must be read before Kward can edit or overwrite them. They do not constrain shell commands. See [Workspace tools](workspace-tools.md).
241
248
  - File reads and edits are bounded to avoid loading very large files by accident.
249
+ - The permission policy can decide whether a model-requested tool starts. The command sandbox is a separate, opt-in operating-system boundary for model-requested shell commands. See [Permissions](permissions.md) and [Command sandboxing](sandboxing.md).
242
250
  - Shell commands run from the workspace and should be treated like commands you run yourself.
243
251
 
244
252
  ## Images
data/doc/web-search.md CHANGED
@@ -26,17 +26,17 @@ Kward has three web tools:
26
26
  2. `fetch_content` reads human-readable pages.
27
27
  3. `fetch_raw` reads machine-readable files such as JSON, YAML, XML, RSS, OpenAPI specs, or plain text.
28
28
 
29
- A good research flow is:
29
+ For reliable results, ask Kward to find the source, read the important page, and include its URL:
30
30
 
31
31
  ```text
32
32
  Search for the official docs, fetch the relevant page, then answer with the source URL.
33
33
  ```
34
34
 
35
- Kward should search first, then fetch important pages before relying on them.
35
+ Kward searches for likely sources first, then reads the important pages before relying on them.
36
36
 
37
37
  ## Network behavior
38
38
 
39
- Web tools are advertised to the model by default. Queries and fetched URLs are sent over the network to the selected provider or target host. See [Configuration](configuration.md) for the full `web_search` config reference including API key storage and the `provider` config setting.
39
+ Web tools are available by default. Search queries and fetched URLs leave your machine and go to the selected search provider or target host. See [Configuration](configuration.md) for API key storage, provider selection, and the full `web_search` reference.
40
40
 
41
41
  In automatic mode, provider fallback is:
42
42
 
@@ -12,14 +12,14 @@ Kward normally chooses these tools itself. You do not need to know their exact n
12
12
 
13
13
  ## Guardrails
14
14
 
15
- Workspace tools use the active workspace as their boundary. File paths are workspace-relative by default, and file tools are guarded so Kward does not edit arbitrary unread files. Guardrails can be disabled with the `tools.workspace_guardrails` setting — see [Configuration](configuration.md). When disabled, file tools can access paths outside the workspace, but shell commands are unaffected since they already run as your OS user.
15
+ Workspace guardrails limit Kward file tools to the active workspace. File paths are workspace-relative by default, and existing files must be read before Kward can change them. Guardrails can be disabled with the `tools.workspace_guardrails` setting — see [Configuration](configuration.md). When disabled, file tools can access paths outside the workspace. Guardrails are not an operating-system sandbox and do not constrain shell commands; model-requested commands can instead use the separate, opt-in [command sandbox](sandboxing.md).
16
16
 
17
17
  Important behavior:
18
18
 
19
19
  - Existing files must be read in the current conversation before `write_file` or `edit_file` can change them.
20
20
  - Reads are bounded to avoid pulling very large files into context by accident. Files larger than 256 KB cannot be read or edited. Read output is capped at 50 KB or 2,000 lines, whichever comes first.
21
21
  - Edits use exact text replacement, so accidental partial or fuzzy changes fail instead of guessing.
22
- - Shell commands run as your operating-system user from the workspace. They are powerful and should be treated like commands you run yourself. Command output is capped at 128 KB.
22
+ - With sandboxing off (the default), shell commands run as your operating-system user from the workspace. Enable [command sandboxing](sandboxing.md) to apply an OS boundary to model-requested `run_shell_command` workers. Command output is capped at 128 KB.
23
23
 
24
24
  ## Reading the workspace
25
25
 
@@ -94,7 +94,7 @@ Arguments:
94
94
  - `path`: workspace-relative file path.
95
95
  - `content`: complete file content.
96
96
 
97
- Use full writes when replacing generated content or creating a new file. For small edits to existing files, Kward should usually prefer `edit_file`.
97
+ Full writes are useful for generated content and new files. For a small change to an existing file, `edit_file` is usually the better fit.
98
98
 
99
99
  The result includes a unified diff of the changes, capped at 8 KB with a summary of additions and deletions when truncated.
100
100
 
@@ -124,7 +124,7 @@ Arguments:
124
124
  - `command`: command to run.
125
125
  - `timeout_seconds`: optional timeout, default 30 seconds.
126
126
 
127
- Kward uses shell commands for tests, linters, build checks, and simple repository inspection. Command output is bounded at 128 KB and may be compacted before it is sent back into model context, while the original output remains available in the session record.
127
+ Kward uses shell commands for tests, linters, build checks, and simple repository inspection. When configured, the [command sandbox](sandboxing.md) applies to this tool and its descendants. Command output is bounded at 128 KB and may be compacted before it is sent back into model context, while the original output remains available in the session record.
128
128
 
129
129
  The output format is `Exit status: N` followed by `STDOUT:` and `STDERR:` sections. On timeout, Kward sends SIGTERM then SIGKILL after 0.2 seconds and returns a timeout error. Commands support cooperative cancellation when the session is cancelled.
130
130
 
@@ -0,0 +1,10 @@
1
+ # Displays the current Federation stardate in Kward's interactive footer.
2
+ Kward.plugin do |plugin|
3
+ plugin.footer do |_ctx|
4
+ now = Time.now.utc
5
+ reference = Time.utc(1987, 7, 15)
6
+ stardate = 41_000 + ((now - reference) / (365.25 * 24 * 60 * 60) * 1_000)
7
+
8
+ "Stardate: #{format('%.1f', stardate)}"
9
+ end
10
+ end