kward 0.72.0 → 0.73.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -0
  3. data/CHANGELOG.md +53 -0
  4. data/Gemfile.lock +2 -2
  5. data/doc/configuration.md +1 -1
  6. data/doc/editor.md +23 -2
  7. data/doc/git.md +1 -0
  8. data/doc/rpc.md +2 -2
  9. data/doc/shell.md +56 -10
  10. data/doc/usage.md +27 -1
  11. data/lib/kward/ansi.rb +62 -23
  12. data/lib/kward/cli/plugins.rb +1 -1
  13. data/lib/kward/cli/rendering.rb +4 -1
  14. data/lib/kward/cli/runtime_helpers.rb +141 -7
  15. data/lib/kward/cli/settings.rb +0 -1
  16. data/lib/kward/cli/slash_commands.rb +213 -0
  17. data/lib/kward/cli/tabs.rb +34 -4
  18. data/lib/kward/cli/tool_summaries.rb +6 -0
  19. data/lib/kward/cli.rb +4 -12
  20. data/lib/kward/clipboard.rb +2 -3
  21. data/lib/kward/compactor.rb +7 -19
  22. data/lib/kward/config_files.rb +26 -4
  23. data/lib/kward/ekwsh.rb +239 -42
  24. data/lib/kward/image_attachments.rb +3 -1
  25. data/lib/kward/interactive_pty_runner.rb +151 -0
  26. data/lib/kward/local_command_runner.rb +155 -0
  27. data/lib/kward/local_pty_command_runner.rb +171 -0
  28. data/lib/kward/model/context_usage.rb +2 -2
  29. data/lib/kward/model/payloads.rb +2 -5
  30. data/lib/kward/prompt_history.rb +5 -3
  31. data/lib/kward/prompt_interface/editor/auto_indent.rb +5 -4
  32. data/lib/kward/prompt_interface/editor/controller.rb +262 -62
  33. data/lib/kward/prompt_interface/editor/modes/emacs.rb +21 -21
  34. data/lib/kward/prompt_interface/editor/modes/modern.rb +38 -37
  35. data/lib/kward/prompt_interface/editor/modes/vibe.rb +23 -173
  36. data/lib/kward/prompt_interface/editor/modes/vibe_insert_readline.rb +166 -0
  37. data/lib/kward/prompt_interface/editor/renderer.rb +6 -5
  38. data/lib/kward/prompt_interface/editor/state.rb +28 -6
  39. data/lib/kward/prompt_interface/editor/syntax_highlighter.rb +5 -3
  40. data/lib/kward/prompt_interface/git_prompt.rb +12 -23
  41. data/lib/kward/prompt_interface/interactive/controller.rb +1 -1
  42. data/lib/kward/prompt_interface/key_handler.rb +93 -51
  43. data/lib/kward/prompt_interface/question_prompt.rb +1 -6
  44. data/lib/kward/prompt_interface/screen.rb +3 -3
  45. data/lib/kward/prompt_interface/selection_prompt.rb +3 -6
  46. data/lib/kward/prompt_interface/slash_overlay.rb +2 -0
  47. data/lib/kward/prompt_interface.rb +87 -221
  48. data/lib/kward/prompts/commands.rb +4 -0
  49. data/lib/kward/rpc/memory_methods.rb +83 -0
  50. data/lib/kward/rpc/server.rb +130 -83
  51. data/lib/kward/rpc/session_manager.rb +10 -74
  52. data/lib/kward/rpc/tool_metadata.rb +11 -0
  53. data/lib/kward/rpc/transcript_normalizer.rb +4 -39
  54. data/lib/kward/scratchpad_runner.rb +56 -0
  55. data/lib/kward/session_diff.rb +20 -3
  56. data/lib/kward/session_naming.rb +11 -0
  57. data/lib/kward/terminal_keys.rb +84 -0
  58. data/lib/kward/terminal_sequences.rb +42 -0
  59. data/lib/kward/tools/context_for_task.rb +2 -0
  60. data/lib/kward/version.rb +1 -1
  61. data/lib/kward/workers/git_guard.rb +25 -0
  62. data/lib/kward/workers/job.rb +99 -0
  63. data/lib/kward/workers/queue_runner.rb +166 -0
  64. data/lib/kward/workers/queue_store.rb +112 -0
  65. data/lib/kward/workers.rb +3 -0
  66. data/lib/kward/workspace.rb +15 -63
  67. data/templates/default/fulldoc/html/css/kward.css +33 -0
  68. data/templates/default/fulldoc/html/images/kward_screen_1.png +0 -0
  69. data/templates/default/fulldoc/html/setup.rb +1 -0
  70. data/templates/default/layout/html/layout.erb +19 -32
  71. metadata +15 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32f9966cff49fb90d0c11f20277e77186fe439ede174a3d151a2a3dfdcddc726
4
- data.tar.gz: ebcdfd83477ce0e90857019a67f8bfdf57f2f2e9cb4e7accdd89b790d6002b5c
3
+ metadata.gz: b3b7a272f3d5171660fc2669a486830e334910cfef50af203b1eeedb989b6289
4
+ data.tar.gz: 3fb9b4ede65361609b6a1c594431d20136bb83c57e205e4b1b32332db75b2fbb
5
5
  SHA512:
6
- metadata.gz: 9c625a36cf64cb7348f1ff14a07bd27e6b311bcd2d5b219669885d105118977143f17d1321d0acfefddc81469c0a11311868b8e8905d2d5a68339065dddf0513
7
- data.tar.gz: 7de5e8b2887d654d57bd2032fa86b4fad7b3c0859d74d323e4d9f44cd682b7f7b616a11858e5e20bb4f2cc5a829d3746f94576fb114a35504581a45a66adbb16
6
+ metadata.gz: 9dec61bbf302b69eb5501a130c29d8104b342266cac1489f53608051db1cb03fb44a5b3d47388f43e2f271673f1ba2b4744637dc108701d13a37abd322913657
7
+ data.tar.gz: 5d057f370c989e973b2cd2bbef8464932251f7b2ffcddcfff6c86d3ecb28db4c5a1241e3eda1f5fa3b7f460cddbd7938dde6396e3335d6bbe9fb1b131d81f9a4
@@ -0,0 +1,30 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Checkout
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Set up Ruby
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: '3.3'
24
+ bundler-cache: true
25
+
26
+ - name: Check syntax
27
+ run: find lib test -name '*.rb' -print0 | xargs -0 ruby -c
28
+
29
+ - name: Run tests
30
+ run: bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -2,6 +2,59 @@
2
2
 
3
3
  All notable changes to Kward will be documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [0.73.0] - 2026-06-29
8
+
9
+ ### Added
10
+
11
+ - Added persistent session-backed worker queue job metadata as the first step toward tab-based worker queues.
12
+ - Added experimental `/queue add` and `/queue list` commands for enqueueing the current tab session into the worker queue.
13
+ - Added a first session-backed worker queue runner that executes one queued job and marks it ready for review after committing changes.
14
+ - Added clean-workspace blocking for queued workers so jobs do not start on top of existing local changes.
15
+ - Added `/queue run` to manually drain queued tab worker jobs sequentially until the queue is empty or a job needs attention.
16
+ - Added worker git stash helpers as groundwork for cooperative queue suspension.
17
+ - Added explicit queue runner suspend/resume primitives that stash and restore a running job's workspace changes.
18
+ - Added `/queue suspend <id>` and `/queue resume <id>` commands for manually parking and resuming queued worker jobs.
19
+ - Added `/queue open <id>` to open a queued worker's session for review or follow-up.
20
+ - Added `/diff` to open the chronological file changes recorded in the current session in the integrated diff viewer.
21
+ - Added `/scratchpad [text|markdown|ruby]` for opening unsaved editor buffers, including Vibe `:w filename` save-as support and Ruby `:run`/Modern `Ctrl+R` output written after `__END__`.
22
+ - Added `/pty <command>` and the `ekwsh` `pty <command>` built-in for explicit interactive PTY passthrough sessions, enabling terminal-owned tools such as pagers to run from Kward.
23
+ - Added minimal PTY execution for external `ekwsh` commands so terminal-aware tools can detect a TTY and terminal width.
24
+ - Added Ctrl+C cancellation for running `ekwsh` commands and preserved tab-switch actions while shell commands are active.
25
+ - Added quoted path completion and cached `$PATH` executable completion for `ekwsh`.
26
+ - Added streaming `ekwsh` command output in the TUI transcript while commands run.
27
+ - Added separate workspace-scoped `ekwsh` command history so embedded shell input no longer shares normal prompt history.
28
+ - Added structured RPC `runtime/updateSetting` `defaultModel` values so clients can send provider and model separately while keeping the existing string format.
29
+
30
+ ### Changed
31
+
32
+ - Changed PTY-backed `ekwsh` commands to refresh terminal window size while running so long-lived commands can adapt to resizes.
33
+ - Changed `ekwsh` to default `GIT_PAGER` to `cat`, while preserving user-provided values, so Git commands do not unexpectedly enter an interactive pager under PTY execution.
34
+ - Improved `ekwsh` POSIX-oriented built-ins, including `exit [status]`, stricter `cd`/`pwd`, `export NAME`, assignment persistence, `unalias`, and shared alias-name validation.
35
+ - Changed `ekwsh` configuration to prefer a POSIX `/bin/sh` default shell and validate runtime settings for command timeout, output cap, and shell history size.
36
+
37
+ ### Fixed
38
+
39
+ - Normalized ordinary PTY line endings in `ekwsh` command output so transcripts avoid stray carriage returns.
40
+ - Added `ekwsh` timeout and output-limit enforcement for external commands using the shared local command runner.
41
+ - Consolidated workspace shell command execution on a shared local command runner with timeout, cancellation, bounded capture, and optional streaming support.
42
+ - Fixed `ekwsh` shell output sanitization so unsafe terminal controls are stripped before command output is shown while SGR color is preserved.
43
+ - Split Vibe editor insert/readline key handling into a focused mixin without changing editor behavior.
44
+ - Consolidated compaction message-field reads through the shared message access helper.
45
+ - Consolidated RPC transcript tool metadata normalization with tool event metadata so tool names, args, diffs, and changed files stay aligned.
46
+ - Fixed RPC tool capabilities so `changedFiles` is advertised when emitted in tool results.
47
+ - Removed a stale `count-tests` CLI branch that could crash instead of treating the input as a prompt.
48
+ - Fixed tab failures and cancellations so red tab states always emit a runtime message explaining what happened.
49
+ - Fixed model and reasoning changes from CLI/RPC settings so active session runtime metadata is persisted before the next turn.
50
+ - Fixed `context_for_task` so candidate files with no task matches return a clear no-match message instead of only a header.
51
+ - Fixed workspace file tools so expected filesystem permission and path-type errors are returned as tool errors instead of aborting a turn.
52
+ - Fixed composer `Ctrl+C` so it no longer exits the app when no process is running.
53
+ - Fixed the Git diff viewer so `Ctrl+C` and terminal-forwarded `Cmd+C` copy selected text.
54
+ - Fixed pasted or dropped shell-escaped image paths so the composer hides the path text after adding the image badge.
55
+ - Fixed `read_skill` tool transcript rendering so skill frontmatter starts on the line after the tool label.
56
+ - Fixed built-in editor soft-wrap vertical movement so moving up or down preserves the visual column across wrapped visual rows and logical line boundaries.
57
+
5
58
  ## [0.72.0] - 2026-06-28
6
59
 
7
60
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kward (0.72.0)
4
+ kward (0.73.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.72.0)
149
+ kward (0.73.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/doc/configuration.md CHANGED
@@ -58,7 +58,7 @@ aliases:
58
58
 
59
59
  `env` values are applied when shell mode starts, after Kward's conservative color defaults. Keys must look like environment variable names (`A_Z`, digits after the first character, and underscores); invalid keys are ignored. Values are converted to strings.
60
60
 
61
- `aliases` expand the first word of a command once. For example, `ll lib` runs `ls -la lib`. Built-in `ekwsh` commands such as `cd`, `pwd`, `export`, `unset`, `alias`, `clear`, and `exit` take precedence over aliases. Run `alias` inside `ekwsh` to list configured aliases. Aliases are also included in command-name Tab completion.
61
+ `aliases` expand the first word of a command once. For example, `ll lib` runs `ls -la lib`. Built-in `ekwsh` commands such as `cd`, `pwd`, `export`, `unset`, `alias`, `clear`, `pty`, and `exit` take precedence over aliases. Run `alias` inside `ekwsh` to list configured aliases. Aliases are also included in command-name Tab completion.
62
62
 
63
63
  ## Provider and model settings
64
64
 
data/doc/editor.md CHANGED
@@ -32,12 +32,33 @@ For a nested project tree, run:
32
32
 
33
33
  In the tree browser, use `↑`/`↓` to move, `←`/`→` to collapse or expand directories, `Enter` to toggle a directory or open a file, `Tab` or `/` to search, `@` to insert the selected file as an `@path` mention, and `Esc` to close. When you open a file from `/files`, quitting the editor returns to the browser at the same position.
34
34
 
35
+ For an unsaved buffer, open a scratchpad:
36
+
37
+ ```text
38
+ /scratchpad
39
+ /scratchpad markdown
40
+ /scratchpad ruby
41
+ ```
42
+
43
+ Scratchpads start as virtual editor buffers named `scratchpad.txt`, `scratchpad.md`, or `scratchpad.rb`. In Vibe mode, save one to a real file with `:w filename`.
44
+ Ruby scratchpads can run with `:run` in Vibe mode or `Ctrl+R` in Modern mode; Kward executes the buffer and writes combined output after `__END__`, replacing any previous output there.
45
+
46
+ ```ruby
47
+ puts "foo"
48
+
49
+ __END__
50
+ foo
51
+ ```
52
+
53
+ The next run receives the current `__END__` section as Ruby `DATA`, then replaces it with the new output.
54
+
35
55
  You can also type a relative path yourself and press `Enter`. If the file does not exist, Kward asks whether to create it.
36
56
 
37
57
  A few things to know:
38
58
 
39
59
  - `$` only opens the editor when it is the first character in the composer.
40
- - Once a file opens, the composer becomes the editor.
60
+ - `/scratchpad` opens a plain-text scratchpad; pass `markdown` or `ruby` to pick another mode.
61
+ - Once a file or scratchpad opens, the composer becomes the editor.
41
62
  - Save or quit to return to normal chat.
42
63
  - If the file changed on disk while you were editing, Kward asks before overwriting it.
43
64
  - If you quit with unsaved changes, Kward asks before discarding them.
@@ -51,7 +72,7 @@ $doc/editor.md
51
72
  1. Type `$doc/editor.md` in the composer.
52
73
  2. Pick the file from the matching results, or press `Enter` if the path is already complete.
53
74
  3. Edit the file.
54
- 4. Save with `Ctrl+S` in Modern mode, `C-x C-s` in Emacs mode, or `:w` in Vibe mode.
75
+ 4. Save with `Ctrl+S` in Modern mode, `C-x C-s` in Emacs mode, or `:w` in Vibe mode. For an unsaved scratchpad in Vibe mode, use `:w filename`.
55
76
  5. Quit with `Ctrl+Q`, `C-x C-c`, or `:q`.
56
77
  6. Continue chatting with Kward.
57
78
 
data/doc/git.md CHANGED
@@ -54,6 +54,7 @@ Useful keys in the diff viewer:
54
54
  | `Page Up` / `Page Down` | Scroll by a page. |
55
55
  | `Home` / `End` | Move within the current line. |
56
56
  | `/` or `Ctrl+F` | Search within the diff. |
57
+ | `Ctrl+C` / `Cmd+C` | Copy the current selection. |
57
58
  | `Enter` | Confirm the current search. |
58
59
  | `Esc` | Cancel search, or close the diff viewer. |
59
60
  | `Ctrl+Q` | Close the diff viewer. |
data/doc/rpc.md CHANGED
@@ -50,7 +50,7 @@ Result fields:
50
50
  Detailed capability fields include:
51
51
 
52
52
  - `transcript`: Tauren transcript format support, including normalized messages, image/tool support, compaction summaries, and restored assistant reasoning as Pi-compatible `thinking` content blocks.
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, supported linear-session fork methods, supported compaction, supported tree navigation with labels and branch summarization, and explicit unsupported import/update features.
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, 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, provider-gated native busy-input steering, queued follow-up input, best-effort cancellation, and recent in-memory event replay behavior.
55
55
  - `events`: `turn/event` notification details, assistant/reasoning event names, normalized tool metadata, 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
56
  - `attachments`: supported input attachment contract for `turns/start`, with accepted base64 image MIME types and a stable max byte value.
@@ -405,7 +405,7 @@ Params:
405
405
 
406
406
  - `sessionId`: active RPC session ID.
407
407
  - `settingId`: currently `defaultModel` or `defaultThinkingLevel`.
408
- - `value`: setting value. `defaultModel` accepts `Provider/model-id` and preserves slashes after the provider separator.
408
+ - `value`: setting value. `defaultModel` accepts a structured object such as `{ "provider": "OpenRouter", "model": "anthropic/claude-sonnet-4.5" }`. For compatibility, it also accepts `Provider/model-id` strings and preserves slashes after the provider separator.
409
409
 
410
410
  Applies the setting live by updating config and refreshing client config. Unsupported setting IDs are rejected.
411
411
 
data/doc/shell.md CHANGED
@@ -12,7 +12,7 @@ This makes it good for:
12
12
  - using project aliases,
13
13
  - keeping command output visible beside the current Kward session.
14
14
 
15
- It is not intended for full-screen interactive programs such as `vim`, `less`, `top`, `ssh`, or REPLs.
15
+ Use `/pty <command>` when you intentionally want to hand the terminal to a full-screen interactive command such as `less`.
16
16
 
17
17
  ## Start shell mode
18
18
 
@@ -47,7 +47,7 @@ or press Ctrl+D on an empty shell prompt.
47
47
 
48
48
  ## How commands run
49
49
 
50
- `ekwsh` runs each command through your configured shell using the shell's `-c` mode. It uses `$SHELL` when set, otherwise `/bin/sh`. The shell is intentionally not started as a login shell so Kward-managed environment values, such as configured PATH entries, are not overwritten by login startup files.
50
+ `ekwsh` is POSIX-oriented and runs each command through `/bin/sh` using the shell's `-c` mode by default. You can configure another POSIX-compatible shell in `ekwsh.yml`. The shell is intentionally not started as a login shell so Kward-managed environment values, such as configured PATH entries, are not overwritten by login startup files.
51
51
 
52
52
  The current directory and exported environment are tracked by Kward between commands, so this works as expected:
53
53
 
@@ -61,7 +61,9 @@ unset FOO
61
61
 
62
62
  `cd` changes only the embedded shell's current directory. It does not change Kward's workspace root or the process directory used by the rest of Kward.
63
63
 
64
- Shell output is shown in the transcript area, but it is not added to the AI conversation history.
64
+ Shell output streams into the transcript area as commands run, but it is not added to the AI conversation history. Simple assignment-only commands such as `FOO=bar` persist into the embedded shell environment for later commands.
65
+
66
+ Shell commands are persisted in a separate workspace-scoped shell history. They do not share the normal Kward prompt history used for chat prompts. The shell history limit is controlled by `history_limit` in `ekwsh.yml`.
65
67
 
66
68
  ## Built-ins
67
69
 
@@ -71,9 +73,10 @@ Shell output is shown in the transcript area, but it is not added to the AI conv
71
73
  | --- | --- |
72
74
  | `cd [dir]` | Change the embedded shell directory. Supports `cd`, `cd -`, and normal relative paths. |
73
75
  | `pwd` | Print the embedded shell directory. |
74
- | `export KEY=value` | Set an environment variable for later commands. |
76
+ | `export KEY[=value]` | Set or mark an environment variable for later commands. `export` and `export -p` list variables. |
75
77
  | `unset KEY` | Remove an environment variable from later commands. |
76
- | `alias [name]` | List configured aliases, or show specific configured aliases. |
78
+ | `alias [name]` | List configured aliases, show specific configured aliases, or set `alias name=value`. |
79
+ | `unalias name` / `unalias -a` | Remove configured aliases. |
77
80
  | `clear` | Clear Kward's visible transcript. |
78
81
  | `exit` / `logout` | Leave shell mode. |
79
82
 
@@ -92,7 +95,7 @@ This means each tab can have its own:
92
95
  - visible shell transcript,
93
96
  - command prompt state.
94
97
 
95
- Kward's normal tab-switching shortcuts continue to work in shell mode.
98
+ Kward's normal tab-switching shortcuts continue to work in shell mode. If you switch tabs while a shell command is running, Kward cancels that foreground command and requeues the tab action so the TUI can switch cleanly.
96
99
 
97
100
  ## Tab completion
98
101
 
@@ -123,7 +126,14 @@ cat my<Tab>
123
126
  # => cat my\ file.txt
124
127
  ```
125
128
 
126
- If there are multiple candidates, Kward applies any common prefix. When there is no longer common prefix, it prints a compact candidate list in the transcript.
129
+ Quoted path tokens are completed in the same quoting style:
130
+
131
+ ```sh
132
+ cat "my<Tab>
133
+ # => cat "my file.txt
134
+ ```
135
+
136
+ If there are multiple candidates, Kward applies any common prefix. When there is no longer common prefix, it prints a compact candidate list in the transcript. Command-name completion caches `$PATH` executables and refreshes when `PATH` changes through shell assignment, `export`, or `unset`.
127
137
 
128
138
  ## Colors and ANSI output
129
139
 
@@ -168,6 +178,11 @@ If `KWARD_CONFIG_PATH` points to another config file, `ekwsh.yml` is read from t
168
178
  Example:
169
179
 
170
180
  ```yaml
181
+ shell: /bin/sh
182
+ # timeout_seconds: 300
183
+ # max_output_bytes: 1048576
184
+ # history_limit: 1000
185
+
171
186
  env:
172
187
  FORCE_COLOR: "1"
173
188
  CLICOLOR_FORCE: "1"
@@ -181,6 +196,19 @@ aliases:
181
196
  t: "bundle exec ruby -Itest"
182
197
  ```
183
198
 
199
+ ### Runtime settings
200
+
201
+ `ekwsh` accepts these top-level runtime settings:
202
+
203
+ | Setting | Default | What it does |
204
+ | --- | --- | --- |
205
+ | `shell` | `/bin/sh` | Absolute path to the POSIX-compatible shell used with `-c`. Invalid or relative paths fall back to `/bin/sh`. |
206
+ | `timeout_seconds` | `300` | Maximum runtime for one command. |
207
+ | `max_output_bytes` | `1048576` | Maximum captured output for one command. |
208
+ | `history_limit` | `1000` | Maximum persisted shell history entries per workspace. |
209
+
210
+ When a command exceeds `timeout_seconds`, `ekwsh` terminates it and reports the timeout. When command output exceeds `max_output_bytes`, `ekwsh` terminates the command, keeps bounded output, and reports the output limit. Press Ctrl+C while a command is running to terminate that command and return to the shell prompt without exiting Kward.
211
+
184
212
  ### Environment variables
185
213
 
186
214
  `env` values are applied when shell mode starts, after Kward's conservative color defaults.
@@ -252,6 +280,25 @@ alias ll gs
252
280
 
253
281
  Aliases also appear in command-name Tab completion.
254
282
 
283
+ ## Interactive PTY passthrough
284
+
285
+ Use `/pty <command>` for commands that need to own the terminal temporarily, such as pagers:
286
+
287
+ ```text
288
+ /pty git log
289
+ ```
290
+
291
+ Inside `/shell`, use the `pty` built-in so the command inherits the embedded shell's current directory and environment:
292
+
293
+ ```sh
294
+ pty git log
295
+ pty vim README.md
296
+ ```
297
+
298
+ Kward runs the command in a PTY, forwards your keyboard input to the process, and streams the process output directly to the terminal. This lets tools such as `less` receive keys like Space, `/`, `n`, and `q` normally. When the command exits, Kward restores its prompt and records only a short session summary in the transcript instead of raw full-screen terminal control output.
299
+
300
+ `/pty` and shell `pty` are explicit on purpose. Normal `/shell` commands remain captured and transcript-friendly; `pty` is for commands where the child process should temporarily own the terminal.
301
+
255
302
  ## `/shell` versus `!command`
256
303
 
257
304
  Kward has two ways to run local commands yourself:
@@ -279,8 +326,7 @@ Current limitations:
279
326
  - no job control,
280
327
  - no persistent shell functions,
281
328
  - no shell startup file sourcing,
282
- - no full-screen terminal applications,
283
329
  - no native readline from your login shell,
284
- - command output is captured through pipes, not a PTY.
330
+ - normal `/shell` command output is transcript-sanitized rather than treated as a full terminal UI.
285
331
 
286
- Some tools only emit color or progress UI when stdout is a real terminal. Prefer explicit command flags such as `--color=always` when needed.
332
+ External `/shell` commands run under a minimal PTY so terminal-aware tools can detect TTY output and terminal width. Full-screen interactive programs should still use explicit `/pty <command>` passthrough.
data/doc/usage.md CHANGED
@@ -78,7 +78,7 @@ For several commands, enter the embedded Kward shell:
78
78
  /shell
79
79
  ```
80
80
 
81
- `/shell` opens `ekwsh`, a Kward-native command mode. Kward keeps the tab bar, composer editing, and transcript rendering while each command runs through your configured shell. Built-ins such as `cd`, `pwd`, `export`, `unset`, `alias`, `clear`, and `exit` maintain shell-mode state between commands. Plain Tab completes built-in command names, configured aliases, executables from `$PATH`, and file paths from the shell's current directory; `cd` completion suggests directories only. `ekwsh` preserves safe ANSI SGR color/style output while stripping terminal-control sequences that could corrupt the TUI, and sets conservative color-friendly environment defaults such as `CLICOLOR=1`, `COLORTERM=truecolor`, and `TERM=xterm-256color` when needed. It does not force color by default; set `FORCE_COLOR`, `CLICOLOR_FORCE`, or command-specific flags such as `--color=always` if you want more aggressive color output. You can set global shell env vars and aliases in `~/.kward/ekwsh.yml`; see [Configuration](configuration.md). It is intended for command output, not full-screen interactive programs such as editors or pagers.
81
+ `/shell` opens `ekwsh`, a Kward-native command mode. Kward keeps the tab bar, composer editing, and transcript rendering while each command runs through your configured shell. Built-ins such as `cd`, `pwd`, `export`, `unset`, `alias`, `clear`, `pty`, and `exit` maintain shell-mode state between commands. Plain Tab completes built-in command names, configured aliases, executables from `$PATH`, and file paths from the shell's current directory; `cd` completion suggests directories only. `ekwsh` preserves safe ANSI SGR color/style output while stripping terminal-control sequences that could corrupt the TUI, and sets conservative color-friendly environment defaults such as `CLICOLOR=1`, `COLORTERM=truecolor`, and `TERM=xterm-256color` when needed. Use `pty git log` or `/pty git log` when you intentionally want to hand the terminal to an interactive tool such as `less` or `vim`. You can set global shell env vars and aliases in `~/.kward/ekwsh.yml`; see [Configuration](configuration.md).
82
82
 
83
83
  ## Shell commands
84
84
 
@@ -114,8 +114,10 @@ Slash commands run local actions in the current session. Most do not send a prom
114
114
  | `/model` | choose the active model. |
115
115
  | `/reasoning` | choose reasoning effort. |
116
116
  | `/git` | review uncommitted changes, stage files, and commit. |
117
+ | `/diff` | open the file changes recorded in the current session. |
117
118
  | `/files` | browse project files in a nested tree and open them in the editor. |
118
119
  | `/shell` | run workspace commands in the embedded Kward shell. |
120
+ | `/pty <command>` | hand the terminal to an interactive command such as `git log`/`less` or `vim`. |
119
121
  | `/settings` | configure prompt overlays. |
120
122
  | `/status` | see session, model, and context status. |
121
123
  | `/new` | start a fresh session in the current tab. |
@@ -141,10 +143,34 @@ Slash commands run local actions in the current session. Most do not send a prom
141
143
  | `/redraw` | fix terminal drawing after resize or glitches. |
142
144
  | `/reload` | reload installed plugins. |
143
145
  | `/workers` | open the experimental worker pipeline (`new`, `do <task>`, or `list`). |
146
+ | `/queue` | manage the experimental tab-backed worker queue (`add`, `list`, `open <id>`, `run`, `suspend <id>`, or `resume <id>`). |
144
147
  | `/exit` | leave Kward. |
145
148
 
146
149
  Prompt templates and plugins can add more slash commands.
147
150
 
151
+ ### Experimental tab-backed worker queue
152
+
153
+ Start Kward with `--experimental-workers` to try the tab-backed worker queue. The queue is an MVP for turning an existing tab/session into implementation work that can be reviewed later.
154
+
155
+ Typical flow:
156
+
157
+ ```text
158
+ /plan Add retry handling to webhook delivery
159
+ /queue add
160
+ /queue run
161
+ /queue open <id>
162
+ ```
163
+
164
+ `/queue add` stores the current tab session as a queued worker job. `/queue run` drains queued jobs one at a time. Each job continues its saved session as an implementation worker, starts only from a clean git workspace, commits any resulting changes, and then becomes `ready_for_review`. Use `/queue open <id>` to inspect the worker session, test the feature yourself, and continue the same session if a follow-up fix is needed.
165
+
166
+ Current MVP limitations:
167
+
168
+ - The queue is manual: run it with `/queue run` when you want it to work.
169
+ - Jobs run sequentially and stop when one becomes `blocked` or `failed`.
170
+ - A job will not start if the workspace is dirty; clean, commit, or stash your changes first.
171
+ - `/queue suspend <id>` and `/queue resume <id>` provide explicit stash-based parking primitives, but automatic foreground-tool-triggered yielding is not wired yet.
172
+ - The queue is experimental and stores metadata locally in Kward's config directory.
173
+
148
174
  ## Prompt history
149
175
 
150
176
  In interactive mode, Kward keeps prompt history per workspace under `~/.kward/history/`. Press Up/Down to recall previous prompts across restarts.
data/lib/kward/ansi.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # Namespace for the Kward CLI agent runtime.
2
2
  module Kward
3
- # ANSI color and terminal capability helpers.
3
+ # ANSI SGR styling and terminal-text helpers.
4
+ #
5
+ # Terminal control output sequences live in `TerminalSequences`, and input key
6
+ # sequences live in `TerminalKeys`. This module owns text-level concerns:
7
+ # colorizing strings, stripping/sanitizing escape sequences, visible wrapping,
8
+ # and lightweight Markdown rendering for terminal output.
4
9
  module ANSI
5
- ESCAPE_PATTERN = /\e\[[0-9;?]*[ -\/]*[@-~]/.freeze
6
10
  SGR_PATTERN = /\e\[[0-9;:]*m/.freeze
7
- OSC_PATTERN = /\e\][^\a]*(?:\a|\e\\)/m.freeze
8
- STRING_ESCAPE_PATTERN = /\e[P_X^][\s\S]*?\e\\/m.freeze
9
11
  STYLES = {
10
12
  reset: 0,
11
13
  bold: 1,
@@ -52,13 +54,24 @@ module Kward
52
54
  end
53
55
 
54
56
  def strip(text)
55
- text.to_s.gsub(ESCAPE_PATTERN, "")
57
+ strip_control_sequences(text)
56
58
  end
57
59
 
60
+ # Removes terminal escape/control sequences while preserving visible text.
61
+ def strip_control_sequences(text)
62
+ scan_escape_tokens(text).each_with_object(+"") do |token, stripped|
63
+ stripped << token[:text] unless token[:escape]
64
+ end
65
+ end
66
+
67
+ # Drops unsafe terminal controls from transcript text while preserving SGR color.
58
68
  def sanitize_transcript(text)
59
- string = text.to_s.gsub(OSC_PATTERN, "").gsub(STRING_ESCAPE_PATTERN, "")
60
- string.gsub(/\e(?:\[[0-9;:?]*[ -\/]*[@-~]|.)/m) do |sequence|
61
- sequence.match?(SGR_PATTERN) ? sequence : ""
69
+ scan_escape_tokens(text).each_with_object(+"") do |token, sanitized|
70
+ if token[:escape]
71
+ sanitized << token[:text] if token[:text].match?(SGR_PATTERN)
72
+ else
73
+ sanitized << token[:text]
74
+ end
62
75
  end
63
76
  end
64
77
 
@@ -67,28 +80,27 @@ module Kward
67
80
  rows = []
68
81
  current = +""
69
82
  visible_width = 0
70
- string = text.to_s
71
- index = 0
72
83
 
73
- while index < string.length
74
- if string[index] == "\e" && (match = string[index..].match(/\A\e\[[0-9;:]*m/))
84
+ scan_escape_tokens(text).each do |token|
85
+ if token[:escape]
86
+ next unless token[:text].match?(SGR_PATTERN)
87
+
75
88
  if current.empty? && rows.any?
76
- rows[-1] << match[0]
89
+ rows[-1] << token[:text]
77
90
  else
78
- current << match[0]
91
+ current << token[:text]
79
92
  end
80
- index += match[0].length
81
93
  next
82
94
  end
83
95
 
84
- char = string[index]
85
- current << char
86
- visible_width += 1
87
- index += 1
88
- if visible_width >= line_width
89
- rows << current
90
- current = +""
91
- visible_width = 0
96
+ token[:text].each_char do |char|
97
+ current << char
98
+ visible_width += 1
99
+ if visible_width >= line_width
100
+ rows << current
101
+ current = +""
102
+ visible_width = 0
103
+ end
92
104
  end
93
105
  end
94
106
 
@@ -96,6 +108,33 @@ module Kward
96
108
  rows
97
109
  end
98
110
 
111
+ # Splits text into visible chunks and terminal escape sequence chunks.
112
+ def scan_escape_tokens(text)
113
+ string = text.to_s
114
+ tokens = []
115
+ index = 0
116
+ while index < string.length
117
+ if string[index] == "\e" && (escape = escape_sequence_at(string, index))
118
+ tokens << { text: escape, escape: true }
119
+ index += escape.length
120
+ next
121
+ end
122
+
123
+ next_escape = string.index("\e", index) || string.length
124
+ tokens << { text: string[index...next_escape], escape: false } if next_escape > index
125
+ index = next_escape
126
+ end
127
+ tokens
128
+ end
129
+
130
+ def escape_sequence_at(string, index)
131
+ chunk = string[index..]
132
+ chunk.match(/\A\e\][^\a]*(?:\a|\e\\)/m)&.[](0) ||
133
+ chunk.match(/\A\e[P_X^][\s\S]*?\e\\/m)&.[](0) ||
134
+ chunk.match(/\A\e\[[0-9;:?]*[ -\/]*[@-~]/)&.[](0) ||
135
+ chunk[0, 2]
136
+ end
137
+
99
138
  def markdown(text, enabled: enabled?)
100
139
  string = text.to_s
101
140
  lines = string.lines(chomp: true)
@@ -88,7 +88,7 @@ module Kward
88
88
  def builtin_slash_commands
89
89
  return BUILTIN_SLASH_COMMANDS if experimental_workers_enabled?
90
90
 
91
- BUILTIN_SLASH_COMMANDS.reject { |command| command[:name] == "workers" }
91
+ BUILTIN_SLASH_COMMANDS.reject { |command| %w[workers queue].include?(command[:name]) }
92
92
  end
93
93
 
94
94
  def builtin_slash_command_names
@@ -318,7 +318,10 @@ module Kward
318
318
  end
319
319
 
320
320
  def tool_summary_display_text(summary)
321
- summary.to_s.sub("\n", "\n\n")
321
+ text = summary.to_s
322
+ return text if text.start_with?("read_skill:\n")
323
+
324
+ text.sub("\n", "\n\n")
322
325
  end
323
326
 
324
327
  def start_stream_block(label)