kward 0.66.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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +90 -0
  6. data/LICENSE +21 -0
  7. data/README.md +101 -0
  8. data/Rakefile +20 -0
  9. data/doc/authentication.md +105 -0
  10. data/doc/code-search.md +56 -0
  11. data/doc/configuration.md +310 -0
  12. data/doc/extensibility.md +186 -0
  13. data/doc/getting-started.md +127 -0
  14. data/doc/memory.md +192 -0
  15. data/doc/plugins.md +223 -0
  16. data/doc/releasing.md +36 -0
  17. data/doc/rpc.md +635 -0
  18. data/doc/usage.md +179 -0
  19. data/doc/web-search.md +28 -0
  20. data/exe/kward +5 -0
  21. data/kward.gemspec +33 -0
  22. data/lib/kward/agent.rb +234 -0
  23. data/lib/kward/ansi.rb +276 -0
  24. data/lib/kward/auth/file.rb +11 -0
  25. data/lib/kward/auth/github_oauth.rb +222 -0
  26. data/lib/kward/auth/openai_oauth.rb +323 -0
  27. data/lib/kward/auth/openrouter_api_key.rb +40 -0
  28. data/lib/kward/cancellation.rb +54 -0
  29. data/lib/kward/cli.rb +2122 -0
  30. data/lib/kward/clipboard.rb +84 -0
  31. data/lib/kward/compactor.rb +998 -0
  32. data/lib/kward/config_files.rb +564 -0
  33. data/lib/kward/conversation.rb +148 -0
  34. data/lib/kward/events.rb +13 -0
  35. data/lib/kward/export_path.rb +28 -0
  36. data/lib/kward/image_attachments.rb +331 -0
  37. data/lib/kward/markdown_transcript.rb +72 -0
  38. data/lib/kward/memory/manager.rb +652 -0
  39. data/lib/kward/message_access.rb +42 -0
  40. data/lib/kward/model/chat_invocation.rb +23 -0
  41. data/lib/kward/model/client.rb +875 -0
  42. data/lib/kward/model/context_overflow.rb +55 -0
  43. data/lib/kward/model/context_usage.rb +104 -0
  44. data/lib/kward/model/model_info.rb +188 -0
  45. data/lib/kward/model/retry_message.rb +11 -0
  46. data/lib/kward/model/stream_parser.rb +205 -0
  47. data/lib/kward/pan/index.html.erb +143 -0
  48. data/lib/kward/pan/server.rb +397 -0
  49. data/lib/kward/plugin_registry.rb +327 -0
  50. data/lib/kward/private_file.rb +18 -0
  51. data/lib/kward/prompt_interface.rb +2437 -0
  52. data/lib/kward/prompts/commands.rb +50 -0
  53. data/lib/kward/prompts/templates.rb +60 -0
  54. data/lib/kward/prompts.rb +58 -0
  55. data/lib/kward/resources/avatar_kward_logo.rb +48 -0
  56. data/lib/kward/resources/pixel_logo.rb +230 -0
  57. data/lib/kward/rpc/auth_manager.rb +265 -0
  58. data/lib/kward/rpc/config_manager.rb +58 -0
  59. data/lib/kward/rpc/prompt_bridge.rb +104 -0
  60. data/lib/kward/rpc/redactor.rb +47 -0
  61. data/lib/kward/rpc/server.rb +639 -0
  62. data/lib/kward/rpc/session_manager.rb +1122 -0
  63. data/lib/kward/rpc/tool_event_normalizer.rb +68 -0
  64. data/lib/kward/rpc/tool_metadata.rb +80 -0
  65. data/lib/kward/rpc/transcript_normalizer.rb +307 -0
  66. data/lib/kward/rpc/transport.rb +58 -0
  67. data/lib/kward/session_diff.rb +125 -0
  68. data/lib/kward/session_store.rb +493 -0
  69. data/lib/kward/skills/registry.rb +76 -0
  70. data/lib/kward/starter_pack_installer.rb +110 -0
  71. data/lib/kward/steering.rb +56 -0
  72. data/lib/kward/telemetry/logger.rb +195 -0
  73. data/lib/kward/telemetry/stats.rb +466 -0
  74. data/lib/kward/tools/ask_user_question.rb +107 -0
  75. data/lib/kward/tools/base.rb +45 -0
  76. data/lib/kward/tools/code_search.rb +65 -0
  77. data/lib/kward/tools/edit_file.rb +41 -0
  78. data/lib/kward/tools/list_directory.rb +21 -0
  79. data/lib/kward/tools/read_file.rb +30 -0
  80. data/lib/kward/tools/read_skill.rb +27 -0
  81. data/lib/kward/tools/registry.rb +117 -0
  82. data/lib/kward/tools/run_shell_command.rb +28 -0
  83. data/lib/kward/tools/search/code.rb +445 -0
  84. data/lib/kward/tools/search/web.rb +747 -0
  85. data/lib/kward/tools/tool_call.rb +87 -0
  86. data/lib/kward/tools/web_search.rb +48 -0
  87. data/lib/kward/tools/write_file.rb +29 -0
  88. data/lib/kward/transcript_export.rb +40 -0
  89. data/lib/kward/version.rb +4 -0
  90. data/lib/kward/workspace.rb +377 -0
  91. data/lib/kward.rb +6 -0
  92. data/lib/main.rb +3 -0
  93. metadata +232 -0
data/doc/memory.md ADDED
@@ -0,0 +1,192 @@
1
+ # Memory
2
+
3
+ Kward has an opt-in structured memory system for interactive sessions. Memory is designed to be inspectable, explainable, and removable. It is disabled by default.
4
+
5
+ Memory is not used for one-shot prompts such as `kward "..."`.
6
+
7
+ Use memory when you want Kward to remember stable preferences, recurring project facts, or personal workflow habits across interactive sessions. Leave it disabled when you want every session to start clean.
8
+
9
+ ## Enable or disable memory
10
+
11
+ In interactive chat:
12
+
13
+ ```text
14
+ /memory enable
15
+ /memory disable
16
+ ```
17
+
18
+ Enabling memory stores this config in `~/.kward/config.json`:
19
+
20
+ ```json
21
+ {
22
+ "memory": {
23
+ "enabled": true
24
+ }
25
+ }
26
+ ```
27
+
28
+ When memory is disabled, Kward does not inject retrieved memories into the prompt.
29
+
30
+ ## Auto-summary
31
+
32
+ Memory auto-summary is off by default. When both memory and auto-summary are enabled, Kward quietly runs the same learning flow as `/memory summarize` after each completed interactive agent turn, when it is ready for the next user prompt:
33
+
34
+ ```text
35
+ /memory auto-summary enable
36
+ /memory auto-summary disable
37
+ ```
38
+
39
+ The config value is stored as:
40
+
41
+ ```json
42
+ {
43
+ "memory": {
44
+ "enabled": true,
45
+ "auto_summary": true
46
+ }
47
+ }
48
+ ```
49
+
50
+ Auto-summary does not run when memory is disabled and is not used for one-shot prompts.
51
+
52
+ ## Memory layers
53
+
54
+ ### Core memories
55
+
56
+ Core memories are explicit user instructions. They are high-trust and persistent until removed. Add them only when you intentionally want Kward to remember something:
57
+
58
+ ```text
59
+ /memory core "Prefer small, focused patches with tests."
60
+ ```
61
+
62
+ Core memories are stored as human-readable JSON in:
63
+
64
+ ```text
65
+ ~/.kward/memory/core.json
66
+ ```
67
+
68
+ ### Soft memories
69
+
70
+ Soft memories are contextual hints, such as workflow preferences or recurring project facts. They are confidence-based and treated as non-authoritative. Add one manually with:
71
+
72
+ ```text
73
+ /memory add "This workspace usually uses Minitest."
74
+ ```
75
+
76
+ Soft memories are stored as JSON Lines in:
77
+
78
+ ```text
79
+ ~/.kward/memory/soft.jsonl
80
+ ```
81
+
82
+ Kward can also infer soft memories when auto-summary is enabled, or when you explicitly ask it to summarize/learn from the current session:
83
+
84
+ ```text
85
+ /memory summarize
86
+ ```
87
+
88
+ The v1 inference is conservative and heuristic-based, with optional model-based reformulation when summarization has an available client. Kward refuses to automatically persist inferred emotional, intimate, romantic, or dependency-forming memories.
89
+
90
+ ### Session memories
91
+
92
+ Session memories record memories learned during the current conversation so Kward can avoid learning the same item again when summarizing or resuming the session. They are stored with the session JSONL file, but they are not injected into the prompt as a separate memory layer and are not automatically promoted into core memories.
93
+
94
+ ## Inspect, explain, and remove memory
95
+
96
+ List memories:
97
+
98
+ ```text
99
+ /memory list
100
+ ```
101
+
102
+ Inspect memory state and file paths:
103
+
104
+ ```text
105
+ /memory inspect
106
+ ```
107
+
108
+ Explain why memories were retrieved for the most recent interactive turn:
109
+
110
+ ```text
111
+ /memory why
112
+ ```
113
+
114
+ Forget a memory:
115
+
116
+ ```text
117
+ /memory forget core_001
118
+ /memory forget soft_001
119
+ ```
120
+
121
+ For core memories, forget removes the record. For soft memories, forget marks the record inactive and redacts its stored text, tags, confidence, and hit count so inactive audit metadata can remain without retaining the memory content.
122
+
123
+ Promote a soft memory to a core memory:
124
+
125
+ ```text
126
+ /memory promote soft_001
127
+ ```
128
+
129
+ Promotion creates a new core memory and marks the soft memory forgotten.
130
+
131
+ ## Retrieval behavior
132
+
133
+ For each interactive turn, Kward selectively retrieves memories using:
134
+
135
+ - scope: `global` and `workspace:<canonical path>`
136
+ - core memories first
137
+ - soft-memory text or tag overlap with the current input; soft memories without overlap are not injected
138
+ - soft-memory confidence
139
+ - soft-memory recency/TTL, updated when a soft memory is retrieved
140
+ - hard limits on injected memory count
141
+
142
+ Retrieved memories are injected into the system prompt as a bounded block similar to:
143
+
144
+ ```text
145
+ <kward_memory>
146
+ Core Memories:
147
+ - [core_001] ...
148
+
149
+ Relevant Soft Memories:
150
+ - [soft_001] ...
151
+
152
+ Rules:
153
+ - Core memories override soft memories.
154
+ - Soft memories are contextual hints, not guaranteed facts.
155
+ </kward_memory>
156
+ ```
157
+
158
+ Kward never injects every stored memory.
159
+
160
+ ## Storage and audit trail
161
+
162
+ Default files:
163
+
164
+ ```text
165
+ ~/.kward/memory/core.json
166
+ ~/.kward/memory/soft.jsonl
167
+ ~/.kward/memory/events.jsonl
168
+ ```
169
+
170
+ `events.jsonl` records minimal audit events such as enable, disable, add, forget, promote, retrieve, and summarize. Audit events use IDs, scopes, tags, and timestamps rather than full memory text where practical. Forgotten soft memories keep inactive metadata in `soft.jsonl`, but their stored text is replaced with `[forgotten]`.
171
+
172
+ If `KWARD_CONFIG_PATH` is set, memory files live beside that config file instead of under `~/.kward`.
173
+
174
+ ## RPC methods
175
+
176
+ The experimental RPC backend exposes dedicated memory methods:
177
+
178
+ - `memory/status`
179
+ - `memory/enable`
180
+ - `memory/disable`
181
+ - `memory/autoSummary/enable`
182
+ - `memory/autoSummary/disable`
183
+ - `memory/list`
184
+ - `memory/add`
185
+ - `memory/addCore`
186
+ - `memory/forget`
187
+ - `memory/promote`
188
+ - `memory/inspect`
189
+ - `memory/why`
190
+ - `memory/summarize`
191
+
192
+ See [RPC protocol](rpc.md) for method details.
data/doc/plugins.md ADDED
@@ -0,0 +1,223 @@
1
+ # Plugins
2
+
3
+ Plugins are Kward's trusted local extension system. They let you add project or personal workflow features without changing Kward itself.
4
+
5
+ A plugin can:
6
+
7
+ - add interactive slash commands,
8
+ - expose commands through the RPC backend,
9
+ - render a custom terminal footer,
10
+ - inject concise prompt context into future model requests,
11
+ - observe live transcript events such as assistant output, tool calls, and retries.
12
+
13
+ Plugins are plain Ruby files. They run inside the Kward process with your local user permissions, so install only plugins you trust.
14
+
15
+ Use a prompt template when reusable text is enough. Use a skill when you need reusable instructions. Use a plugin when you need local Ruby behavior, file or network integration, custom commands, or transcript observers.
16
+
17
+ ## Where plugins live
18
+
19
+ Kward loads top-level Ruby files from:
20
+
21
+ ```text
22
+ ~/.kward/plugins/*.rb
23
+ ```
24
+
25
+ Plugins are intentionally **not** loaded from the current workspace, from a project repository, or from a custom `KWARD_CONFIG_PATH` directory. This keeps plugin loading tied to the local user account rather than to whatever project Kward is currently inspecting.
26
+
27
+ If a legacy plugin directory exists beside a custom config path, Kward warns and ignores it.
28
+
29
+ ## A first plugin
30
+
31
+ Create the plugin directory:
32
+
33
+ ```bash
34
+ mkdir -p ~/.kward/plugins
35
+ ```
36
+
37
+ Then create `~/.kward/plugins/hello.rb`:
38
+
39
+ ```ruby
40
+ Kward.plugin do |plugin|
41
+ plugin.command "hello", description: "Say hello", argument_hint: "[name]" do |args, ctx|
42
+ name = args.strip.empty? ? "captain" : args.strip
43
+ ctx.say("Hello, #{name}.")
44
+ end
45
+ end
46
+ ```
47
+
48
+ Start Kward and run:
49
+
50
+ ```text
51
+ /hello Kai
52
+ ```
53
+
54
+ Plugin commands appear in interactive slash completion and in RPC command listings.
55
+
56
+ ## Slash commands
57
+
58
+ Register a command with `plugin.command`:
59
+
60
+ ```ruby
61
+ Kward.plugin do |plugin|
62
+ plugin.command "session-info", description: "Show session details" do |_args, ctx|
63
+ ctx.say("Session: #{ctx.session_name || ctx.session_id || 'unnamed'}")
64
+ ctx.say("Path: #{ctx.session_path || 'not saved'}")
65
+ ctx.say("Workspace: #{ctx.workspace_root}")
66
+ end
67
+ end
68
+ ```
69
+
70
+ Command names:
71
+
72
+ - do not include the leading `/`,
73
+ - must start with a letter or number,
74
+ - may contain letters, numbers, `_`, and `-`,
75
+ - cannot replace built-in commands or prompt-template commands.
76
+
77
+ If a plugin command conflicts with a reserved or duplicate command, Kward skips it and prints a warning.
78
+
79
+ Command handlers receive:
80
+
81
+ - `args`: the raw text after the command name,
82
+ - `ctx`: a plugin context object.
83
+
84
+ Use `ctx.say(message)` for user-visible output.
85
+
86
+ ## Prompt context
87
+
88
+ Plugins can add concise context to future system prompts:
89
+
90
+ ```ruby
91
+ Kward.plugin do |plugin|
92
+ plugin.prompt_context do |ctx|
93
+ next unless File.exist?(File.join(ctx.workspace_root, "Gemfile"))
94
+
95
+ "Workspace note: this project uses Ruby. Prefer bundle exec for project commands."
96
+ end
97
+ end
98
+ ```
99
+
100
+ Prompt context is injected after personas and before skills/workspace instructions. Keep it short and stable. Long or noisy prompt context will be sent repeatedly to the model and can reduce useful context space.
101
+
102
+ If plugin state changes and the active conversation should rebuild its system message, call:
103
+
104
+ ```ruby
105
+ ctx.refresh_system_message!
106
+ ```
107
+
108
+ from a command or event handler.
109
+
110
+ ## Custom footer
111
+
112
+ A plugin can render one custom footer for the terminal UI:
113
+
114
+ ```ruby
115
+ Kward.plugin do |plugin|
116
+ plugin.footer do |ctx|
117
+ name = ctx.session_name || "unnamed"
118
+ messages = ctx.transcript.messages.length
119
+ "#{name} • #{messages} messages"
120
+ end
121
+ end
122
+ ```
123
+
124
+ Only one footer renderer is active. If multiple plugins register footers, the later one replaces the earlier one and Kward prints a warning.
125
+
126
+ Footer errors are caught and printed as warnings so they do not break the session.
127
+
128
+ ## Transcript events
129
+
130
+ Plugins can observe live transcript stream events:
131
+
132
+ ```ruby
133
+ Kward.plugin do |plugin|
134
+ plugin.on_transcript_event do |event, ctx|
135
+ next unless event.type == "assistant_delta"
136
+
137
+ File.open(File.join(ctx.workspace_root, ".assistant-stream.log"), "a") do |file|
138
+ file.write(event.payload[:delta])
139
+ end
140
+ end
141
+ end
142
+ ```
143
+
144
+ Supported event types include:
145
+
146
+ - `reasoning_delta`
147
+ - `assistant_delta`
148
+ - `assistant_message`
149
+ - `model_retry`
150
+ - `turn_steered`
151
+ - `tool_call`
152
+ - `tool_result`
153
+ - `answer`
154
+
155
+ Event payloads are deep-frozen copies. Treat them as read-only.
156
+
157
+ Transcript-event handler errors are caught and printed as warnings.
158
+
159
+ ## Plugin context API
160
+
161
+ Plugin handlers receive a `ctx` object with:
162
+
163
+ - `ctx.args` - raw command arguments for contexts that have them.
164
+ - `ctx.workspace_root` - active workspace path.
165
+ - `ctx.transcript.messages` - deep-frozen copy of active conversation messages.
166
+ - `ctx.say(message)` - emit output to the active frontend when available.
167
+ - `ctx.session_id` - current persisted session ID when available.
168
+ - `ctx.session_name` - current session name when available.
169
+ - `ctx.session_path` - current session file path when available.
170
+ - `ctx.refresh_system_message!` - rebuild the active conversation system message.
171
+
172
+ The transcript is read-only by design. Plugins should use Kward APIs exposed through the context rather than mutating conversation internals.
173
+
174
+ ## RPC support
175
+
176
+ Plugins are available to both the CLI and the experimental RPC backend.
177
+
178
+ RPC clients can:
179
+
180
+ - list plugin commands through `commands/list`,
181
+ - run plugin commands through `commands/run`,
182
+ - run plugin slash commands through `turns/start` input such as `/hello Kai`.
183
+
184
+ When a plugin command is run as a turn, Kward emits command output through normal turn events without calling the model.
185
+
186
+ ## Security model
187
+
188
+ Plugins are trusted local Ruby code. A plugin can read and write files, run commands, make network requests, and access process environment variables just like any Ruby code running as your user.
189
+
190
+ Recommended practices:
191
+
192
+ - Install plugins only from sources you trust.
193
+ - Keep plugins in your personal `~/.kward/plugins` directory.
194
+ - Do not place secrets directly in plugin files if they will be shared.
195
+ - Prefer user-specific config or environment variables for credentials.
196
+ - Keep prompt context concise and avoid injecting secrets into model prompts.
197
+ - Be careful when writing transcript observers that persist conversation content.
198
+
199
+ ## Complete example
200
+
201
+ ```ruby
202
+ Kward.plugin do |plugin|
203
+ plugin.command "last-message", description: "Show transcript size" do |_args, ctx|
204
+ ctx.say("Messages: #{ctx.transcript.messages.length}")
205
+ end
206
+
207
+ plugin.footer do |ctx|
208
+ "#{ctx.session_name || 'unnamed'} • #{ctx.transcript.messages.length} messages"
209
+ end
210
+
211
+ plugin.prompt_context do |_ctx|
212
+ "Project background: prefer small, focused changes."
213
+ end
214
+
215
+ plugin.on_transcript_event do |event, ctx|
216
+ next unless event.type == "assistant_delta"
217
+
218
+ File.open(File.join(ctx.workspace_root, ".assistant-stream.log"), "a") do |file|
219
+ file.write(event.payload[:delta])
220
+ end
221
+ end
222
+ end
223
+ ```
data/doc/releasing.md ADDED
@@ -0,0 +1,36 @@
1
+ # Releasing Kward
2
+
3
+ Release steps that can be completed before publishing:
4
+
5
+ 1. Update `CHANGELOG.md` for the version.
6
+ 2. Update `Kward::VERSION` in `lib/kward/version.rb`.
7
+ 3. Run the test suite:
8
+
9
+ ```bash
10
+ bundle exec rake test
11
+ ```
12
+
13
+ 4. Generate documentation:
14
+
15
+ ```bash
16
+ bundle exec rake rdoc
17
+ bundle exec yard doc
18
+ ```
19
+
20
+ 5. Build the gem locally:
21
+
22
+ ```bash
23
+ gem build kward.gemspec
24
+ ```
25
+
26
+ 6. Inspect the packaged files and confirm no local config, sessions, logs, or secrets are included.
27
+ 7. Install the built gem locally and smoke test the `kward` executable.
28
+
29
+ RubyGems setup before the first public release:
30
+
31
+ 1. Create or sign in to a RubyGems.org account.
32
+ 2. Enable multi-factor authentication for the account.
33
+ 3. Confirm the `kward` gem name is available.
34
+ 4. Add final homepage, source code, changelog, documentation, and bug tracker metadata to `kward.gemspec` once the public repository URLs are final.
35
+ 5. Prefer RubyGems trusted publishing for automated releases so long-lived API keys do not need to be stored in CI secrets.
36
+ 6. Push the built gem or publish through the trusted publishing workflow.