ollama_chat 0.0.126 → 0.0.128

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +151 -0
  3. data/README.md +1 -0
  4. data/lib/ollama_chat/chat.rb +46 -46
  5. data/lib/ollama_chat/collection_search.rb +16 -2
  6. data/lib/ollama_chat/commands.rb +32 -39
  7. data/lib/ollama_chat/compaction.rb +28 -1
  8. data/lib/ollama_chat/conversation.rb +48 -0
  9. data/lib/ollama_chat/database/models/session.rb +21 -0
  10. data/lib/ollama_chat/dialog.rb +14 -7
  11. data/lib/ollama_chat/information.rb +3 -0
  12. data/lib/ollama_chat/message_list.rb +7 -5
  13. data/lib/ollama_chat/model_handling.rb +14 -13
  14. data/lib/ollama_chat/oc.rb +7 -0
  15. data/lib/ollama_chat/rag_handling.rb +126 -17
  16. data/lib/ollama_chat/source_fetching.rb +1 -1
  17. data/lib/ollama_chat/switches.rb +79 -9
  18. data/lib/ollama_chat/tools/concern.rb +33 -18
  19. data/lib/ollama_chat/tools/delete_file.rb +2 -2
  20. data/lib/ollama_chat/tools/directory_structure.rb +2 -2
  21. data/lib/ollama_chat/tools/execute_grep.rb +3 -5
  22. data/lib/ollama_chat/tools/execute_shell.rb +1 -1
  23. data/lib/ollama_chat/tools/file_context.rb +1 -2
  24. data/lib/ollama_chat/tools/forget.rb +2 -2
  25. data/lib/ollama_chat/tools/generate_image.rb +4 -6
  26. data/lib/ollama_chat/tools/get_current_weather.rb +1 -1
  27. data/lib/ollama_chat/tools/get_cve.rb +2 -2
  28. data/lib/ollama_chat/tools/get_endoflife.rb +2 -2
  29. data/lib/ollama_chat/tools/get_rfc.rb +2 -2
  30. data/lib/ollama_chat/tools/get_url.rb +1 -2
  31. data/lib/ollama_chat/tools/memorize.rb +1 -1
  32. data/lib/ollama_chat/tools/move_file.rb +3 -3
  33. data/lib/ollama_chat/tools/patch_file.rb +7 -2
  34. data/lib/ollama_chat/tools/read_file.rb +2 -2
  35. data/lib/ollama_chat/tools/roll_dice.rb +1 -1
  36. data/lib/ollama_chat/tools/run_tests.rb +3 -5
  37. data/lib/ollama_chat/tools/search_knowledge.rb +17 -14
  38. data/lib/ollama_chat/tools/search_web.rb +1 -2
  39. data/lib/ollama_chat/tools/write_file.rb +2 -2
  40. data/lib/ollama_chat/version.rb +1 -1
  41. data/ollama_chat.gemspec +2 -2
  42. data/spec/ollama_chat/commands_spec.rb +49 -23
  43. data/spec/ollama_chat/conversation_spec.rb +71 -0
  44. data/spec/ollama_chat/model_handling_spec.rb +9 -9
  45. data/spec/ollama_chat/rag_handling_spec.rb +113 -0
  46. data/spec/ollama_chat/switches_spec.rb +28 -0
  47. data/spec/ollama_chat/tools/concern_spec.rb +1 -1
  48. data/spec/ollama_chat/tools/execute_shell_spec.rb +1 -0
  49. data/spec/ollama_chat/tools/memorize_spec.rb +4 -4
  50. data/spec/ollama_chat/tools/run_tests_spec.rb +3 -3
  51. data/spec/ollama_chat/tools/search_knowledge_spec.rb +5 -8
  52. data/spec/spec_helper.rb +1 -0
  53. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93612bbed949bff6bad6cf89f581b324165c0aa672f6f92b675b63a6a2dce7e0
4
- data.tar.gz: 79aac9f7dada8df87035af5bdfd2913d16012af44a781b7a20c44a457163b313
3
+ metadata.gz: a8c69119a9431fed33aea9fe4f2f23594eea70c570fb5f08b39b88daee8bd31a
4
+ data.tar.gz: efd37e4ba7c7971369bf4bd3f7ea95dd1a031f21f54f8a1b438a516db5b577c1
5
5
  SHA512:
6
- metadata.gz: 2eb715b50338ddb686e6aab7debd264591e1018216adef57146237a862a5f37f217a31bb481a724b4bb8fcc99144c1a40679e3048c64cb88f0eaaabd546661fc
7
- data.tar.gz: 8fa30b91ba125939cd506078bcaf6c9549bb43aa6323f9186e0acae218c7814fd8add54aa75b39f907d4affc78889c52a45b2479ec57c4218d76e61e67843ed3
6
+ metadata.gz: cadf4af1255ea0232708e463b4c48f8b7497a556e4e14766f83c2291da8de7c9f77591da2a1d43678502d41bf414ed2e806ca46ef744d09ca85a33212474891a
7
+ data.tar.gz: 3e286668006435fe9fc00fdafbaa21d1d422ac99b00631838a69b2e334de30a50e3421a5172763827021370d3eb20eacdf16f7ca5f01a60a4baa4634d678b448
data/CHANGES.md CHANGED
@@ -1,5 +1,156 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-10-07 v0.0.128
4
+
5
+ ### Features
6
+
7
+ * Migrate `/clear` to `/conversation clean` chooser:
8
+ * Removed `clean` method from `chat.rb` and the `/clear` command from
9
+ `commands.rb`.
10
+ * Added `conversation_clean` to `conversation.rb` with an accumulating
11
+ `choose_with_state` chooser (options: `tools`, `images`, `thinking`,
12
+ `messages`, `history`, `links`), a `confirm?` gate, and per-field
13
+ dispatch.
14
+ * Added `what:` keyword argument to `clean_messages` and
15
+ `clean_messages!` in `message_list.rb` supporting `:tools`, `:images`,
16
+ and `:thinking` selection.
17
+ * Updated `/conversation clean` command handler to delegate to
18
+ `conversation_clean`.
19
+ * Add `OLLAMA_EMBEDDING_URL` for dedicated embedding host:
20
+ * Add `OC::OLLAMA::EMBEDDING_URL` const_conf setting (optional,
21
+ `sensitive true`, decoded to `URI`, scheme-checked).
22
+ * Add `connect_embedding_ollama` + public `embedding_ollama` accessor in
23
+ `chat.rb`; fallback to primary chat client when unset.
24
+ * Propagate `ollama:` kwarg through `model_present?`,
25
+ `pull_model_from_remote`, `pull_model_unless_present`, `prepare_model`,
26
+ `use_model` in `model_handling.rb`.
27
+ * Refactor `connect_message` to keyword-arg signature (`base_url:`,
28
+ `prefix:`) + new `connect_message_done` in `dialog.rb`; remove old
29
+ positional-arg version.
30
+ * Remove `ensure connect_message` from `choose_model`.
31
+ * `/reconnect` now calls both `connect_ollama` +
32
+ `connect_embedding_ollama`; `info_rag` shows embedding URL.
33
+ * Fix `source_fetching.rb` Semantic splitter to route chunking
34
+ calls to `embedding_ollama` instead of the primary chat client.
35
+ * Add `url` + `model` to log payloads in `collection_search.rb` and
36
+ `search_knowledge.rb`.
37
+ * Update `README.md` with `OLLAMA_EMBEDDING_URL` entry.
38
+ * Add `/memory dump` and `/memory restore` command:
39
+ * Add `/memory` command in `commands.rb` routing `dump` and `restore`
40
+ subcommands with an optional trailing `.jsonl` path.
41
+ * Add `memory_dump` in `rag_handling.rb` with accumulating multi-select
42
+ chooser (`[ALL]`/`[DONE]`) writing JSONL via `JSONJSONLIO`.
43
+ * Add `memory_restore` in `rag_handling.rb` grouping records by
44
+ `collection` field and re-embedding with original tags and
45
+ `source` association.
46
+ * Extend `create_memory_collection` to accept either a bare persona stem
47
+ or a full `memory-*` collection name via `start_with?` guard.
48
+ * Update `create_memory_collection` YARD prose to reflect dual-input
49
+ behavior.
50
+ * Swap `miyu_pairing` → `personal_assistant` in `forget.rb`,
51
+ `memorize.rb`, and `memorize_spec.rb` to decouple docs from active
52
+ persona.
53
+
54
+ ### Refactoring
55
+
56
+ * Refactor all tools to use `tool_config`:
57
+ * Replace 20 `config.tools.functions.<name>` call sites across 14 tool
58
+ files with the `tool_config` accessor from `Tools::Concern`.
59
+ * Remove 12 dead `config = chat.config` locals that existed solely to
60
+ reach the tool's own config.
61
+ * Drop `config` parameter from `eval_template` in `execute_grep.rb`,
62
+ `poll_for_image` in `generate_image.rb`, and `check_path` in
63
+ `run_tests.rb`.
64
+ * Move `tool_config` and `allowed_paths` below `private` in `concern.rb`;
65
+ add YARD documentation to `tool_config`.
66
+
67
+ ### Documentation
68
+
69
+ * Document automatic syntax checking in `patch_file` description:
70
+ * Added a `SYNTAX CHECK` note to the `OllamaChat::Tools::PatchFile`
71
+ description, informing users that the tool automatically runs
72
+ language-appropriate checks (e.g. `ruby -wc` for `.rb` files) and
73
+ reports errors in the result message.
74
+
75
+ ### Tests
76
+
77
+ * Removed `/clear` describe block from `commands_spec.rb`; replaced
78
+ `/conversation clean` specs with delegation test.
79
+ * Added 6 unit examples for `#conversation_clean` in `conversation_spec.rb`
80
+ covering cancel, deny, field-only, messages+fields, links-only, and
81
+ history-only paths.
82
+ * Fix `model_handling_spec.rb` call sites for new `ollama:` kwarg.
83
+ * Stub `connect_message_done` in `spec_helper.rb`.
84
+ * Add 5 routing specs in `commands_spec.rb` for `/memory` (with-path,
85
+ without-path, spaces-in-path).
86
+ * Add 7 specs in `rag_handling_spec.rb` for `create_memory_collection`,
87
+ `memory_dump`, and `memory_restore`.
88
+ * Update 3 `check_path` mocks in `run_tests_spec.rb` to match the new
89
+ single-argument signature; use `.expose` in `concern_spec.rb` for private
90
+ method access.
91
+
92
+ ## 2026-10-06 v0.0.127
93
+
94
+ ## Changes
95
+
96
+ * **Switches and Configuration**
97
+ * Added `memory_trigger` switch, registered in `/toggle` (regexp,
98
+ completion list, help string, and fallback feedback).
99
+ * Refactored `Switch` to accept callable values alongside plain values;
100
+ plain values are now wrapped via a new private `adhoc_closure` method
101
+ using a `UNSET` sentinel for read/write disambiguation.
102
+ * Hardened `Switch#initialize` with an arity guard: callable values
103
+ responding to `:arity` must return `-1` (optional single parameter
104
+ defaulting to `UNSET`), otherwise a `TypeError` is raised.
105
+ * Updated `set` methods in `OllamaChat::Switches` to explicitly return
106
+ `nil` for consistent behavior.
107
+ * Added `@raise [TypeError]` tag to `Switch#initialize` and expanded
108
+ `@param value` YARD documentation for `CombinedSwitch`.
109
+ * Added arity guard (`ask_and_send(:arity) <= 0`) to
110
+ `CombinedSwitch#initialize`.
111
+
112
+ * **Session and Model Updates**
113
+ * Added `trigger_enabled?` and `set_trigger_enabled` to the `Session`
114
+ model to encapsulate trigger hash shape.
115
+ * Added YARD documentation for `adhoc_closure` and the `:memory_trigger`
116
+ reader.
117
+
118
+ * **Conversation and Compaction**
119
+ * Added `/conversation compact summary` command.
120
+ * Introduced `show_compaction_summary` in `Compaction` to retrieve the
121
+ most recent summary message via `messages.find_summary` and render its
122
+ content (narrative + `tool_calls` lookup index) through the pager,
123
+ prefixed with a size header and entry count.
124
+ * Registered the new `compact summary` subcommand in the `/conversation`
125
+ regexp alternation, completion list, help text, and dispatch block.
126
+ * Excluded system-injected messages (`runtime_information`,
127
+ `trigger_inject`) from the compaction summary's `tool_calls` block to
128
+ keep the index focused on user-initiated tool calls.
129
+
130
+ * **RAG and Search**
131
+ * Moved `set_documents_collection` above the `private` keyword in
132
+ `rag_handling.rb` to make it callable from outside `RAGHandling`.
133
+ * Updated `choose_collection` to call `set_documents_collection` instead
134
+ of assigning `@documents.collection` directly.
135
+ * Replaced direct `chat.documents.collection` reads/writes in `chat.rb`
136
+ and `search_knowledge.rb` with public accessors `chat.collection` and
137
+ `chat.set_documents_collection`.
138
+ * Consolidated two `chat.log` calls in `search_knowledge.rb` into one,
139
+ adding `rerank:` and `pre_rerank:` to the structured data hash with
140
+ `.compact`.
141
+ * Removed `String#%` interpolation in the old rerank log line and
142
+ eliminated the `collection_name` local variable.
143
+ * Updated `trigger_inject` in `collection_search.rb` to include the
144
+ surviving `records` array (text, similarity, tags) in the log `data:`
145
+ hash instead of only logging the count.
146
+
147
+ * **Shell Execution**
148
+ * Forced pager for all `execute_shell` output by passing `force: true` to
149
+ `use_pager`, ensuring consistent UX with other pager-based tools.
150
+ * Updated test-mode spec examples to include a `stub_success` call with
151
+ sample stdout to exercise the always-active pager path with real
152
+ content.
153
+
3
154
  ## 2026-10-05 v0.0.126
4
155
 
5
156
  * **Tool Registry Refactor**: Refactored the tool registry to store tool
data/README.md CHANGED
@@ -61,6 +61,7 @@ The following environment variables can be used to configure behavior:
61
61
  #### Ollama Core
62
62
  - `OLLAMA_URL` - Base URL for Ollama server (default: `http://localhost:11434`)
63
63
  - `OLLAMA_HOST` - Base hostname for Ollama server (default: `localhost:11434`)
64
+ - `OLLAMA_EMBEDDING_URL` - Optional base URL for a dedicated Ollama server used exclusively for embedding operations. When set, the embedding model runs on this host while the chat model stays on `OLLAMA_URL`. Falls back to `OLLAMA_URL` when unset.
64
65
  - `OLLAMA_SEARXNG_URL` - SearxNG search endpoint URL
65
66
  - `OLLAMA_CHAT_TTS_URL` - Base URL for the [audio.cpp](https://github.com/0xshug0/audio.cpp) TTS server (default: `http://localhost:8880`)
66
67
 
@@ -2,6 +2,7 @@ require 'tins'
2
2
  require 'tins/xt/string_version'
3
3
  require 'tins/xt/full'
4
4
  require 'json'
5
+ require 'set'
5
6
  require 'term/ansicolor'
6
7
  require 'reline'
7
8
  require 'reverse_markdown'
@@ -116,6 +117,7 @@ class OllamaChat::Chat
116
117
  setup_session
117
118
  setup_state_selectors(config)
118
119
  connect_ollama
120
+ connect_embedding_ollama
119
121
  @documents = setup_documents
120
122
  models::Collection.sync(self)
121
123
  @cache = setup_cache
@@ -321,6 +323,16 @@ class OllamaChat::Chat
321
323
  voice_handler.new(chat: self, voice: current_voice).speak(text, background:)
322
324
  end
323
325
 
326
+ # Returns the Ollama client to use for embedding operations.
327
+ #
328
+ # Falls back to the primary chat client when no dedicated embedding
329
+ # URL has been configured.
330
+ #
331
+ # @return [Ollama::Client] the embedding or chat Ollama client
332
+ def embedding_ollama
333
+ @embedding_ollama || ollama
334
+ end
335
+
324
336
  # Returns a human-readable string representation of the Chat object,
325
337
  # including the class name and the base URL.
326
338
  #
@@ -427,47 +439,6 @@ class OllamaChat::Chat
427
439
  content
428
440
  end
429
441
 
430
- # The clean method clears various parts of the chat session based on the
431
- # specified parameter.
432
- #
433
- # @param what [ String, nil ] the type of data to clear, defaults to
434
- # 'messages' if nil
435
- def clean(what)
436
- persona_profile = nil
437
- case what
438
- when 'messages', nil
439
- messages.clear
440
- feedback("Cleared messages.", type: :info)
441
- when 'links'
442
- links.clear
443
- feedback("Cleared links.", type: :info)
444
- when 'history'
445
- clear_history
446
- feedback("Cleared history.", type: :info)
447
- when 'tags'
448
- @documents.clear
449
- feedback("Cleared all tags.", type: :info)
450
- when 'images'
451
- messages.clear_images
452
- feedback("Cleared all images.", type: :info)
453
- when 'all'
454
- if confirm?(
455
- prompt: '🔔 Are you sure to clear messages and collection? (y/n) ',
456
- yes: /\Ay/i
457
- )
458
- then
459
- messages.clear
460
- @documents.clear
461
- links.clear
462
- clear_history
463
- feedback("Cleared messages and collection #{bold{collection}}.", type: :info)
464
- else
465
- feedback("Denied.", type: :denied)
466
- end
467
- end
468
- persona_profile
469
- end
470
-
471
442
  # The interact_with_user method manages the interactive loop for user input
472
443
  # and chat processing.
473
444
  # It handles reading user input, processing commands, managing messages, and
@@ -646,12 +617,13 @@ class OllamaChat::Chat
646
617
  # @raise [RuntimeError] if the connected Ollama server API version is less
647
618
  # than 0.9.0
648
619
  def connect_ollama
620
+ connect_message(base_url:)
649
621
  @server_version = nil
650
622
  @ollama = Ollama::Client.new(
651
623
  connect_timeout: config.timeouts.connect_timeout?,
652
624
  read_timeout: config.timeouts.read_timeout?,
653
625
  write_timeout: config.timeouts.write_timeout?,
654
- base_url: base_url,
626
+ base_url: ,
655
627
  debug: ,
656
628
  user_agent:
657
629
  )
@@ -659,6 +631,7 @@ class OllamaChat::Chat
659
631
  raise 'require ollama API version 0.9.0 or higher'
660
632
  end
661
633
  log(:info, "Connection established", data: { base_url: })
634
+ connect_message_done
662
635
  @ollama
663
636
  end
664
637
 
@@ -673,10 +646,10 @@ class OllamaChat::Chat
673
646
  if embedding.on?
674
647
  @embedding_model = config.embedding.model.name
675
648
  @embedding_model_options = Ollama::Options[config.embedding.model.options]
676
- pull_model_unless_present(@embedding_model)
649
+ pull_model_unless_present(@embedding_model, ollama: embedding_ollama)
677
650
  collection = initial_collection
678
651
  @documents = Documentrix::Documents.new(
679
- ollama:,
652
+ ollama: embedding_ollama,
680
653
  model: @embedding_model,
681
654
  model_options: config.embedding.model.options,
682
655
  embedding_length: config.embedding.model.embedding_length,
@@ -691,6 +664,33 @@ class OllamaChat::Chat
691
664
  end
692
665
  end
693
666
 
667
+ # Establishes a dedicated Ollama connection for embedding operations.
668
+ #
669
+ # When OC::OLLAMA::EMBEDDING_URL is configured, creates a separate
670
+ # Ollama::Client targeting that host so the embedding model can run
671
+ # on a different machine (e.g. a GPU box running audio.cpp alongside
672
+ # the embedding model) while the chat model stays on the primary
673
+ # Ollama server.
674
+ #
675
+ # @return [Ollama::Client, nil] the embedding client, or nil when no
676
+ # dedicated embedding URL is configured
677
+ def connect_embedding_ollama
678
+ if base_url = OC::OLLAMA::EMBEDDING_URL?
679
+ connect_message(base_url:, prefix: 'embedding ')
680
+ @embedding_ollama = Ollama::Client.new(
681
+ connect_timeout: config.timeouts.connect_timeout?,
682
+ read_timeout: config.timeouts.read_timeout?,
683
+ write_timeout: config.timeouts.write_timeout?,
684
+ base_url: ,
685
+ debug: ,
686
+ user_agent:
687
+ )
688
+ log(:info, "Embedding connection established", data: { base_url: })
689
+ connect_message_done
690
+ @embedding_ollama
691
+ end
692
+ end
693
+
694
694
  # Adds documents from command line arguments to the document collection.
695
695
  #
696
696
  # Processes a list of document paths or URLs, handling both local files and
@@ -712,7 +712,7 @@ class OllamaChat::Chat
712
712
  # @note Progress is reported to STDOUT during processing
713
713
  def add_documents_from_argv(document_list)
714
714
  if document_list.any?(&:empty?)
715
- feedback("Clearing collection #{bold{documents.collection}}.", type: :info)
715
+ feedback("Clearing collection #{bold{collection}}.", type: :info)
716
716
  documents.clear
717
717
  document_list.reject!(&:empty?)
718
718
  end
@@ -724,7 +724,7 @@ class OllamaChat::Chat
724
724
  File.expand_path(doc)
725
725
  end
726
726
  end
727
- feedback("Collection #{bold{documents.collection}}: Adding #{document_list.size} documents…", type: :info)
727
+ feedback("Collection #{bold{collection}}: Adding #{document_list.size} documents…", type: :info)
728
728
  count = 1
729
729
  document_list.each_slice(25) do |docs|
730
730
  docs.each do |doc|
@@ -31,6 +31,7 @@ module OllamaChat::CollectionSearch
31
31
  log(
32
32
  :info, 'Embedding search request',
33
33
  data: {
34
+ url: embedding_ollama.base_url,
34
35
  model: config.embedding.model.to_h,
35
36
  collection: collection,
36
37
  query: effective_query,
@@ -48,6 +49,7 @@ module OllamaChat::CollectionSearch
48
49
  log(
49
50
  :info, 'Embedding search response',
50
51
  data: {
52
+ url: embedding_ollama.base_url,
51
53
  collection: collection,
52
54
  query: effective_query,
53
55
  result_count: records.size,
@@ -130,8 +132,20 @@ module OllamaChat::CollectionSearch
130
132
  records.empty? and return
131
133
  pre_rerank = records.size
132
134
  records = rerank_records(content, records, prompt_name:)
133
- log(:info, 'Trigger: %d/%d passed rerank for %s' % [ records.size, pre_rerank, collection ],
134
- data: { collection:, prompt_name: })
135
+ log(
136
+ :info,
137
+ 'Trigger: %d/%d passed rerank for %s' % [ records.size, pre_rerank, collection ],
138
+ data: {
139
+ collection:,
140
+ prompt_name:,
141
+ records: records.map { |r|
142
+ {
143
+ text: r.text,
144
+ similarity: r.similarity&.round(4),
145
+ tags: r.tags_set.to_s(link: false)
146
+ }
147
+ }
148
+ })
135
149
  records.empty? and return
136
150
  end
137
151
  snippets = records.map { |record|
@@ -161,13 +161,13 @@ module OllamaChat::Commands
161
161
 
162
162
  command(
163
163
  name: :toggle,
164
- regexp: %r(^/toggle(?:\s+(markdown|stream|location|runtime_info|voice|think_loud|think_strip|embedding)(?:\s+(-[yn]))?)?$),
165
- complete: [ 'toggle', %w[ markdown stream location runtime_info voice think_loud think_strip embedding ] ],
164
+ regexp: %r(^/toggle(?:\s+(markdown|stream|location|runtime_info|voice|think_loud|think_strip|embedding|memory_trigger)(?:\s+(-[yn]))?)?$),
165
+ complete: [ 'toggle', %w[ markdown stream location runtime_info voice think_loud think_strip embedding memory_trigger ] ],
166
166
  options: '[-y|-n]',
167
167
  help: <<~EOT
168
168
  🎛️ Toggle feature switches
169
- (markdown, stream, location, runtime_info,
170
- voice, think_loud, think_strip, embedding)
169
+ (markdown, stream, location, runtime_info,
170
+ voice, think_loud, think_strip, embedding, memory_trigger)
171
171
  Options: -y (on), -n (off)
172
172
  EOT
173
173
  ) do |toggle_name, flag|
@@ -191,7 +191,7 @@ module OllamaChat::Commands
191
191
  end
192
192
  else
193
193
  feedback(
194
- "Available toggles: markdown|stream|location|runtime_info|voice|think_loud|think_strip|embedding"
194
+ "Available toggles: markdown|stream|location|runtime_info|voice|think_loud|think_strip|embedding|memory_trigger"
195
195
  )
196
196
  end
197
197
  :next
@@ -324,24 +324,6 @@ module OllamaChat::Commands
324
324
  :next
325
325
  end
326
326
 
327
- command(
328
- name: :clear,
329
- regexp: %r(^/clear(?:\s+(messages|images|links|history|tags|all))?$),
330
- complete: [ 'clear', %w[ messages images links history tags all ] ],
331
- optional: true,
332
- help: <<~EOT
333
- 🧹 Clear chat state (messages, images, links,
334
- history, tags, all)
335
- EOT
336
- ) do |subcommand|
337
- if result = clean(subcommand)
338
- disable_content_parsing
339
- result
340
- else
341
- :next
342
- end
343
- end
344
-
345
327
  command(
346
328
  name: :links,
347
329
  regexp: %r(^/links(?:\s+(clear))?$),
@@ -391,14 +373,15 @@ module OllamaChat::Commands
391
373
 
392
374
  command(
393
375
  name: :conversation,
394
- regexp: %r(^/conversation\s+(clean|compact|save|load|summarize|report)((?:\s+-[sc])*)(?:\s+([^-].*\.jsonl?))?$),
395
- complete: [ 'conversation', %w[ save load clean compact summarize report ] ],
376
+ regexp: %r(^/conversation\s+(clean|compact summary|compact|save|load|summarize|report)((?:\s+-[sc])*)(?:\s+([^-].*\.jsonl?))?$),
377
+ complete: [ 'conversation', %w[ compact clean report compact\ summary summarize save load ] ],
396
378
  options: '[-s|-c] [FILENAME]',
397
379
  help: <<~EOT
398
380
  💾 Manage conversation content:
399
381
  - save/load: Export/import as .json or .jsonl
400
- - clean: Remove tool content, images, thinking
382
+ - clean: Interactively clean conversation content
401
383
  - compact: Summarize old messages, keep recent
384
+ - compact summary: Show last compaction summary
402
385
  - summarize: Per-message narrative (-s sentence)
403
386
  - report: Generate a session report document
404
387
  EOT
@@ -415,17 +398,9 @@ module OllamaChat::Commands
415
398
  load_conversation(path)
416
399
  repair_group_uuids
417
400
  when 'clean'
418
- if confirm?(
419
- prompt: '🔔 Clean tool content, images, and thinking from conversation? (y/n) ',
420
- yes: /\Ay/i
421
- )
422
- then
423
- messages.clean_messages!
424
- session_sync
425
- feedback("Conversation cleaned.", type: :info)
426
- else
427
- feedback("Denied.", type: :denied)
428
- end
401
+ conversation_clean
402
+ when 'compact summary'
403
+ show_compaction_summary
429
404
  when 'compact'
430
405
  compact_with_retry
431
406
  when 'summarize'
@@ -696,6 +671,25 @@ module OllamaChat::Commands
696
671
  :next
697
672
  end
698
673
 
674
+ command(
675
+ name: :memory,
676
+ regexp: %r{^/memory(?:\s+(dump|restore))(?:\s+([^-].*\.jsonl?))?$},
677
+ complete: [ 'memory', %w[ dump restore ] ],
678
+ options: '[FILENAME]',
679
+ help: <<~EOT
680
+ 💾 Manage persona memory (dump/restore)
681
+ optional path must end in .json or .jsonl
682
+ EOT
683
+ ) do |subcommand, path|
684
+ case subcommand
685
+ when 'dump'
686
+ memory_dump(path)
687
+ when 'restore'
688
+ memory_restore(path)
689
+ end
690
+ :next
691
+ end
692
+
699
693
  category :Persona
700
694
 
701
695
  command(
@@ -1039,9 +1033,8 @@ module OllamaChat::Commands
1039
1033
  🔌 Reconnect to Ollama server
1040
1034
  EOT
1041
1035
  ) do
1042
- feedback green { "Reconnecting to ollama #{base_url.to_s.inspect}…" }, type: :info
1043
1036
  connect_ollama
1044
- feedback green { "Done." }, type: :success
1037
+ connect_embedding_ollama
1045
1038
  :next
1046
1039
  end
1047
1040
 
@@ -134,6 +134,33 @@ module OllamaChat::Compaction
134
134
  false
135
135
  end
136
136
 
137
+ # Displays the most recent compaction summary in the pager.
138
+ #
139
+ # Shows the narrative and the tool-call lookup index (the
140
+ # `lookup_group` entries with short UUIDs) carried in the summary
141
+ # message, prefixed with size metadata.
142
+ #
143
+ # @return [nil] always returns nil (command block handles :next).
144
+ def show_compaction_summary
145
+ unless msg = messages.find_summary
146
+ feedback('No compaction summary yet.', type: :info)
147
+ return
148
+ end
149
+ es = OllamaChat::TokenEstimator.estimate(msg.content.to_s)
150
+ use_pager do |output|
151
+ output.puts <<~EOT
152
+ #{bold{'Compaction Summary'}}
153
+ #{'─' * Tins::Terminal.columns}
154
+
155
+ #{kramdown_ansi_parse(msg.content.to_s)}
156
+
157
+ #{'─' * Tins::Terminal.columns}
158
+ Size: 👾#{es.bytes_formatted} 🧩#{es.tokens_formatted}
159
+ Tools: #{msg.tool_calls&.size || 0} lookup_group entries
160
+ EOT
161
+ end
162
+ end
163
+
137
164
  private
138
165
 
139
166
  # Serializes non-system messages into a readable block for the prompt.
@@ -172,7 +199,7 @@ module OllamaChat::Compaction
172
199
  def build_tool_entries(messages)
173
200
  messages.filter_map do |msg|
174
201
  next unless tool_name = msg.tool_name.full?
175
- next if tool_name == 'runtime_information'
202
+ next if %w[ runtime_information trigger_inject ].include?(tool_name)
176
203
  uuid = msg.group_uuid.to_s[-8..]
177
204
  summary = OllamaChat::Compaction.tool_summary_line(
178
205
  tool_name, msg.content.to_s
@@ -63,4 +63,52 @@ module OllamaChat::Conversation
63
63
  "#{filename.to_s.inspect} failed.", type: :warn)
64
64
  end
65
65
  end
66
+
67
+ # Selectively cleans parts of the current conversation.
68
+ #
69
+ # Presents an accumulating chooser of cleanable targets:
70
+ # tool content, images, thinking, messages, history, links.
71
+ # The user picks one or more (or [ALL]), confirms, and the
72
+ # selected items are cleared.
73
+ def conversation_clean
74
+ options = %w[ tools images thinking messages history links ]
75
+
76
+ selected = Set.new
77
+ choose_with_state do
78
+ loop do
79
+ remaining = options - selected.to_a
80
+ entries = (remaining.empty? ? [] : ['[ALL]'] + remaining) + ['[DONE]']
81
+ choice = choose_entry(entries, prompt: 'What to clean from conversation? %s')
82
+ case choice
83
+ when nil, '[DONE]'
84
+ break
85
+ when '[ALL]'
86
+ selected.merge(remaining)
87
+ break
88
+ else
89
+ selected.add(choice)
90
+ end
91
+ end
92
+ end
93
+
94
+ return feedback('Cancelled, nothing selected.', type: :cancel) if selected.empty?
95
+
96
+ what = selected.map { |s| bold{s} }.to_a
97
+
98
+ unless confirm?(
99
+ prompt: "🔔 Clean #{what * ', '} from conversation? (y/n) ",
100
+ yes: /\Ay/i
101
+ )
102
+ return feedback('Denied.', type: :denied)
103
+ end
104
+
105
+ field_options = %w[ tools images thinking ].select { selected.include?(_1) }
106
+ messages.clear if selected.include?('messages')
107
+ messages.clean_messages!(what: field_options.map(&:to_sym)) unless field_options.empty?
108
+ clear_history if selected.include?('history')
109
+ links.clear if selected.include?('links')
110
+
111
+ session_sync
112
+ feedback("Cleaned #{what * ', '} from conversation.", type: :info)
113
+ end
66
114
  end
@@ -65,6 +65,27 @@ class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
65
65
  messages.to_s.count(?\n)
66
66
  end
67
67
 
68
+ # Returns the `enabled` flag of the first configured trigger entry.
69
+ #
70
+ # @return [Boolean, nil] true when the trigger is active, false when
71
+ # explicitly disabled, nil when no trigger is configured
72
+ def trigger_enabled?
73
+ trigger&.values&.first&.[]('enabled')
74
+ end
75
+
76
+ # Sets the `enabled` flag of the first configured trigger entry and
77
+ # persists the change.
78
+ #
79
+ # No-op when no trigger is configured.
80
+ #
81
+ # @param value [Boolean] the new enabled state
82
+ def set_trigger_enabled(value)
83
+ if hash = trigger&.values&.first
84
+ hash['enabled'] = value
85
+ save
86
+ end
87
+ end
88
+
68
89
  # @!attribute [v] id
69
90
  # @return [Integer] The primary key for the session.
70
91
  #
@@ -118,14 +118,21 @@ module OllamaChat::Dialog
118
118
  files
119
119
  end
120
120
 
121
- # The connect_message method displays a connection status message.
121
+ # Displays an info-level feedback message indicating that a connection
122
+ # to an Ollama server is being established.
122
123
  #
123
- # @param model [String] the model name to connect to
124
- # @param base_url [String] the base URL of the connection
125
- def connect_message(model, base_url)
126
- msg = "Connecting to #{model}@#{base_url} now…"
127
- log(:info, msg, data: { model:, base_url: })
128
- feedback green { msg }, type: :info
124
+ # @param base_url [URI, String] the target Ollama server URL
125
+ # @param prefix [String] an optional label prepended to "ollama" to
126
+ # distinguish the connection type, e.g. `"embedding "` for a dedicated
127
+ # embedding host (default: `""`)
128
+ def connect_message(base_url:, prefix: '')
129
+ feedback green { "Connecting to #{prefix}ollama #{base_url.to_s.inspect}…" }, type: :info
130
+ end
131
+
132
+ # Displays a success-level feedback message confirming that a
133
+ # connection was established. Counterpart to {#connect_message}.
134
+ def connect_message_done
135
+ feedback green { "Done." }, type: :success
129
136
  end
130
137
 
131
138
  # The change_voice method allows the user to select a voice from a list of
@@ -191,6 +191,9 @@ module OllamaChat::Information
191
191
  # @param output [IO] the output stream to write the information to, defaults to STDOUT
192
192
  def info_rag(output: STDOUT)
193
193
  if embedding.on?
194
+ if base_url = OC::OLLAMA::EMBEDDING_URL?
195
+ output.puts "🔌 Connected to embedding ollama server on: #{base_url.to_s.inspect}"
196
+ end
194
197
  output.puts "🗄️ Current RAG model is #{bold{@embedding_model}}"
195
198
  if @embedding_model_options.present?
196
199
  output.puts " Options: #{JSON.pretty_generate(@embedding_model_options).gsub(/(?<!\A)^/, ' ')}"