kward 0.72.0 → 0.73.1

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 +59 -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 +12 -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
@@ -1,4 +1,3 @@
1
- require "base64"
2
1
  require "find"
3
2
  require "io/console"
4
3
  require "pathname"
@@ -10,6 +9,8 @@ require "tty-cursor"
10
9
  require "tty-reader"
11
10
  require "tty-screen"
12
11
  require_relative "ansi"
12
+ require_relative "terminal_sequences"
13
+ require_relative "terminal_keys"
13
14
  require_relative "editor_mode"
14
15
  require_relative "prompt_interface/banner"
15
16
  require_relative "prompt_interface/composer_state"
@@ -34,6 +35,7 @@ require_relative "prompt_interface/composer_renderer"
34
35
  require_relative "prompt_interface/composer_controller"
35
36
  require_relative "prompt_interface/editor/modes/modern"
36
37
  require_relative "prompt_interface/editor/modes/emacs"
38
+ require_relative "prompt_interface/editor/modes/vibe_insert_readline"
37
39
  require_relative "prompt_interface/editor/modes/vibe"
38
40
  require_relative "prompt_interface/editor/controller"
39
41
  require_relative "prompt_interface/interactive/controller"
@@ -83,6 +85,7 @@ module Kward
83
85
  include ComposerController
84
86
  include ModernEditorMode
85
87
  include EmacsEditorMode
88
+ include VibeInsertReadline
86
89
  include VibeEditorMode
87
90
  include EditorController
88
91
  include InteractiveRenderer
@@ -93,19 +96,19 @@ module Kward
93
96
  include RuntimeState
94
97
  include TranscriptRenderer
95
98
  include PromptRenderer
96
- KEYBOARD_PROTOCOL_ENABLE = "\e[>25u".freeze
97
- KEYBOARD_PROTOCOL_RESTORE = "\e[<u".freeze
98
- BRACKETED_PASTE_ENABLE = "\e[?2004h".freeze
99
- BRACKETED_PASTE_RESTORE = "\e[?2004l".freeze
100
- BRACKETED_PASTE_START = "\e[200~".freeze
101
- BRACKETED_PASTE_END = "\e[201~".freeze
102
- SYNCHRONIZED_OUTPUT_ENABLE = "\e[?2026h".freeze
103
- SYNCHRONIZED_OUTPUT_DISABLE = "\e[?2026l".freeze
104
- CURSOR_SHOW = "\e[?25h".freeze
105
- CURSOR_HIDE = "\e[?25l".freeze
106
- CURSOR_SHAPE_DEFAULT = "\e[0 q".freeze
107
- CURSOR_SHAPE_BAR = "\e[6 q".freeze
108
- SHIFT_ENTER_SEQUENCES = ["\e[13;2u", "\e[13;2~", "\e[27;2;13~", "\e\r", "\e\n"].freeze
99
+ KEYBOARD_PROTOCOL_ENABLE = TerminalSequences::KEYBOARD_PROTOCOL_ENABLE
100
+ KEYBOARD_PROTOCOL_RESTORE = TerminalSequences::KEYBOARD_PROTOCOL_RESTORE
101
+ BRACKETED_PASTE_ENABLE = TerminalSequences::BRACKETED_PASTE_ENABLE
102
+ BRACKETED_PASTE_RESTORE = TerminalSequences::BRACKETED_PASTE_RESTORE
103
+ BRACKETED_PASTE_START = TerminalSequences::BRACKETED_PASTE_START
104
+ BRACKETED_PASTE_END = TerminalSequences::BRACKETED_PASTE_END
105
+ SYNCHRONIZED_OUTPUT_ENABLE = TerminalSequences::SYNCHRONIZED_OUTPUT_ENABLE
106
+ SYNCHRONIZED_OUTPUT_DISABLE = TerminalSequences::SYNCHRONIZED_OUTPUT_DISABLE
107
+ CURSOR_SHOW = TerminalSequences::CURSOR_SHOW
108
+ CURSOR_HIDE = TerminalSequences::CURSOR_HIDE
109
+ CURSOR_SHAPE_DEFAULT = TerminalSequences::CURSOR_SHAPE_DEFAULT
110
+ CURSOR_SHAPE_BAR = TerminalSequences::CURSOR_SHAPE_BAR
111
+ SHIFT_ENTER_SEQUENCES = TerminalKeys::SHIFT_ENTER
109
112
  EXIT_INPUT = :exit_input
110
113
  CANCEL_INPUT = :cancel_input
111
114
  SELECT_CANCEL = :select_cancel
@@ -159,6 +162,7 @@ module Kward
159
162
  @slash_commands = normalize_slash_commands(slash_commands)
160
163
  @slash_selection_index = 0
161
164
  @slash_overlay_dismissed_input = nil
165
+ @slash_overlay_disabled = false
162
166
  @file_selection_index = 0
163
167
  @file_overlay_dismissed_token = nil
164
168
  @file_open_dismissed_token = nil
@@ -296,12 +300,25 @@ module Kward
296
300
  end
297
301
  end
298
302
 
299
- def with_completion_provider(provider)
300
- previous = @completion_provider
303
+ def with_completion_provider(provider, slash_overlay: true)
304
+ previous_provider = @completion_provider
305
+ previous_slash_overlay_disabled = @slash_overlay_disabled
301
306
  @completion_provider = provider
307
+ @slash_overlay_disabled = !slash_overlay
302
308
  yield
303
309
  ensure
304
- @completion_provider = previous
310
+ @completion_provider = previous_provider
311
+ @slash_overlay_disabled = previous_slash_overlay_disabled
312
+ end
313
+
314
+ def with_prompt_history(history)
315
+ previous_history = @prompt_history
316
+ @prompt_history = history
317
+ load_history(@prompt_history.values) if @prompt_history
318
+ yield
319
+ ensure
320
+ @prompt_history = previous_history
321
+ load_history(@prompt_history.values) if @prompt_history
305
322
  end
306
323
 
307
324
  def editing_file?
@@ -320,6 +337,18 @@ module Kward
320
337
  run_editor
321
338
  end
322
339
 
340
+ def scratchpad(language = :text)
341
+ start(render: false)
342
+ opened = @mutex.synchronize do
343
+ open_scratchpad(language).tap do
344
+ render_prompt_locked
345
+ end
346
+ end
347
+ return false unless opened
348
+
349
+ run_editor
350
+ end
351
+
323
352
  def run_editor
324
353
  loop do
325
354
  key = read_key(nonblock: true)
@@ -496,6 +525,36 @@ module Kward
496
525
  end
497
526
  end
498
527
 
528
+ def with_terminal_handoff
529
+ start
530
+ input = nil
531
+ output = nil
532
+ @mutex.synchronize do
533
+ clear_prompt_for_output_locked
534
+ restore_scroll_region_locked
535
+ disable_editor_mouse_reporting(force: true)
536
+ @output_io.print(BRACKETED_PASTE_RESTORE)
537
+ @output_io.print(KEYBOARD_PROTOCOL_RESTORE)
538
+ restore_editor_cursor_shape_locked
539
+ set_cursor_visible_locked(true, force: true)
540
+ @output_io.flush
541
+ restore_console_mode_locked
542
+ input = @input_io
543
+ output = @output_io
544
+ end
545
+
546
+ yield(input, output)
547
+ ensure
548
+ @mutex.synchronize do
549
+ enter_raw_mode_locked
550
+ @output_io.print(KEYBOARD_PROTOCOL_ENABLE)
551
+ @output_io.print(BRACKETED_PASTE_ENABLE)
552
+ @last_composer_rows = []
553
+ render_prompt_locked if @started && @asking
554
+ @output_io.flush
555
+ end
556
+ end
557
+
499
558
  def start_interactive(title:, rows:, fps:)
500
559
  snapshot = composer_snapshot
501
560
  controller = InteractiveController.new(width: interactive_canvas_width, height: rows, fps: fps)
@@ -733,6 +792,17 @@ module Kward
733
792
  end
734
793
  end
735
794
 
795
+ def write_transcript_delta(delta)
796
+ @mutex.synchronize do
797
+ with_synchronized_output_locked do
798
+ prepare_transcript_output_locked unless @restoring_transcript
799
+ write_transcript_text_locked(delta.to_s)
800
+ restore_composer_cursor_locked unless @restoring_transcript
801
+ end
802
+ @output_io.flush unless @restoring_transcript
803
+ end
804
+ end
805
+
736
806
  def write_stream_block(label, delta, finish: false)
737
807
  @mutex.synchronize do
738
808
  write_stream_block_locked(label, delta.to_s, finish: finish)
@@ -772,209 +842,5 @@ module Kward
772
842
  def modal_active_locked?
773
843
  @question_prompt_active || !@question_state.nil? || !@select_state.nil? || !@git_state.nil?
774
844
  end
775
-
776
-
777
-
778
-
779
-
780
-
781
-
782
-
783
-
784
-
785
-
786
-
787
-
788
-
789
-
790
-
791
-
792
-
793
-
794
-
795
-
796
-
797
-
798
-
799
-
800
-
801
-
802
-
803
-
804
-
805
-
806
-
807
-
808
-
809
-
810
-
811
-
812
-
813
-
814
-
815
-
816
-
817
-
818
-
819
-
820
-
821
-
822
-
823
-
824
-
825
-
826
-
827
-
828
-
829
-
830
-
831
-
832
-
833
-
834
-
835
-
836
-
837
-
838
-
839
-
840
-
841
-
842
-
843
-
844
-
845
-
846
-
847
-
848
-
849
-
850
-
851
-
852
-
853
-
854
-
855
-
856
-
857
-
858
-
859
-
860
-
861
-
862
-
863
-
864
-
865
-
866
-
867
-
868
-
869
-
870
-
871
-
872
-
873
-
874
-
875
-
876
-
877
-
878
-
879
-
880
-
881
-
882
-
883
-
884
-
885
-
886
-
887
-
888
-
889
-
890
-
891
-
892
-
893
-
894
-
895
-
896
-
897
-
898
-
899
-
900
-
901
-
902
-
903
-
904
-
905
-
906
-
907
-
908
-
909
-
910
-
911
-
912
-
913
-
914
-
915
-
916
-
917
-
918
-
919
-
920
-
921
-
922
-
923
-
924
-
925
-
926
-
927
-
928
-
929
-
930
-
931
-
932
-
933
-
934
-
935
-
936
-
937
-
938
-
939
-
940
-
941
-
942
-
943
-
944
-
945
-
946
-
947
-
948
-
949
-
950
-
951
-
952
-
953
-
954
-
955
-
956
-
957
-
958
-
959
-
960
-
961
-
962
-
963
-
964
-
965
-
966
-
967
-
968
-
969
-
970
-
971
-
972
-
973
-
974
-
975
-
976
-
977
-
978
-
979
845
  end
980
846
  end
@@ -26,9 +26,13 @@ module Kward
26
26
  { name: "reasoning", description: "Select reasoning effort.", argument_hint: "" },
27
27
  { name: "reload", description: "Reload installed plugins.", argument_hint: "" },
28
28
  { name: "workers", description: "Open the worker pipeline.", argument_hint: "[new|do <task>]" },
29
+ { name: "queue", description: "Manage the tab-backed worker queue.", argument_hint: "[add|list|open|run|suspend|resume]" },
29
30
  { name: "git", description: "Review uncommitted changes and commit them.", argument_hint: "" },
31
+ { name: "diff", description: "Open the file changes recorded in this session.", argument_hint: "" },
30
32
  { name: "files", description: "Browse project files.", argument_hint: "" },
31
33
  { name: "shell", description: "Open the embedded Kward shell.", argument_hint: "" },
34
+ { name: "scratchpad", description: "Open an unsaved editor buffer.", argument_hint: "[text|markdown|ruby]" },
35
+ { name: "pty", description: "Run a command in an interactive PTY passthrough session.", argument_hint: "<command>" },
32
36
  { name: "tab", description: "Manage tabs.", argument_hint: "[1-n|move|close|new|name]" },
33
37
  { name: "status", description: "Show the current status message.", argument_hint: "" },
34
38
  { name: "stats", description: "Show telemetry logging stats.", argument_hint: "[range]" },
@@ -0,0 +1,83 @@
1
+ require_relative "../memory/manager"
2
+
3
+ # Namespace for the Kward CLI agent runtime.
4
+ module Kward
5
+ # JSON-RPC backend namespace used by UI clients.
6
+ module RPC
7
+ # Memory-related RPC method implementations mixed into SessionManager.
8
+ module MemoryMethods
9
+ def memory_manager
10
+ Memory::Manager.for_config_dir(@config_dir)
11
+ end
12
+
13
+ def memory_status
14
+ manager = memory_manager
15
+ { enabled: manager.enabled?, autoSummary: manager.auto_summary_enabled?, paths: manager.paths }
16
+ end
17
+
18
+ def memory_enable
19
+ memory_manager.enable
20
+ { enabled: true }
21
+ end
22
+
23
+ def memory_disable
24
+ memory_manager.disable
25
+ { enabled: false }
26
+ end
27
+
28
+ def memory_auto_summary_enable
29
+ memory_manager.auto_summary_enable
30
+ { autoSummary: true }
31
+ end
32
+
33
+ def memory_auto_summary_disable
34
+ memory_manager.auto_summary_disable
35
+ { autoSummary: false }
36
+ end
37
+
38
+ def memory_list(include_inactive: false, workspace_root: Dir.pwd)
39
+ memory_manager.hierarchy(include_inactive: include_inactive, workspace_root: workspace_root)
40
+ end
41
+
42
+ def memory_add(text:, scope: nil, tags: [])
43
+ { memory: memory_manager.add_soft(text, scope: scope || "global", tags: tags) }
44
+ end
45
+
46
+ def memory_add_core(text:, scope: nil, tags: [])
47
+ { memory: memory_manager.add_core(text, scope: scope || "global", tags: tags) }
48
+ end
49
+
50
+ def memory_forget(id:)
51
+ { forgotten: memory_manager.forget_memory(id) }
52
+ end
53
+
54
+ def memory_promote(id:)
55
+ { memory: memory_manager.promote_memory(id) }
56
+ end
57
+
58
+ def memory_relax(id:, workspace_root: Dir.pwd)
59
+ { memory: memory_manager.relax_core(id, workspace_root: workspace_root) }
60
+ end
61
+
62
+ def memory_inspect
63
+ memory_manager.inspect_memory
64
+ end
65
+
66
+ def memory_why(session_id: nil)
67
+ if session_id
68
+ rpc_session = fetch_session(session_id)
69
+ return rpc_session.conversation.last_memory_retrieval || memory_manager.explain_retrieval
70
+ end
71
+
72
+ memory_manager.explain_retrieval
73
+ end
74
+
75
+ def memory_summarize(session_id:)
76
+ rpc_session = fetch_session(session_id)
77
+ records = memory_manager.summarize_conversation(rpc_session.conversation, client: @client)
78
+ persist_memory_state(rpc_session)
79
+ { memories: records }
80
+ end
81
+ end
82
+ end
83
+ end