ollama_chat 0.0.124 → 0.0.126

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +177 -0
  3. data/Rakefile +2 -2
  4. data/bin/ollama_chat_log +1 -1
  5. data/lib/ollama_chat/chat.rb +5 -13
  6. data/lib/ollama_chat/collection_search.rb +102 -6
  7. data/lib/ollama_chat/commands.rb +29 -19
  8. data/lib/ollama_chat/compaction.rb +7 -3
  9. data/lib/ollama_chat/database/migrations/011_add_trigger_to_sessions.rb +7 -0
  10. data/lib/ollama_chat/database/models/session.rb +11 -0
  11. data/lib/ollama_chat/database/session_locking.rb +3 -2
  12. data/lib/ollama_chat/follow_chat.rb +1 -1
  13. data/lib/ollama_chat/information.rb +41 -23
  14. data/lib/ollama_chat/ollama_chat_config/default_config.yml +45 -11
  15. data/lib/ollama_chat/pager.rb +5 -2
  16. data/lib/ollama_chat/rag_handling.rb +211 -22
  17. data/lib/ollama_chat/session_management.rb +104 -8
  18. data/lib/ollama_chat/source_fetching.rb +2 -2
  19. data/lib/ollama_chat/tool_calling.rb +3 -16
  20. data/lib/ollama_chat/tools/browse.rb +0 -1
  21. data/lib/ollama_chat/tools/compute_bmi.rb +1 -2
  22. data/lib/ollama_chat/tools/concern.rb +26 -0
  23. data/lib/ollama_chat/tools/copy_to_clipboard.rb +1 -3
  24. data/lib/ollama_chat/tools/delete_file.rb +4 -3
  25. data/lib/ollama_chat/tools/directory_structure.rb +0 -1
  26. data/lib/ollama_chat/tools/eval_ruby.rb +0 -1
  27. data/lib/ollama_chat/tools/execute_grep.rb +0 -2
  28. data/lib/ollama_chat/tools/execute_jira_twg.rb +0 -2
  29. data/lib/ollama_chat/tools/execute_ri.rb +0 -1
  30. data/lib/ollama_chat/tools/execute_shell.rb +4 -6
  31. data/lib/ollama_chat/tools/file_context.rb +0 -2
  32. data/lib/ollama_chat/tools/forget.rb +20 -8
  33. data/lib/ollama_chat/tools/gem_path_lookup.rb +0 -1
  34. data/lib/ollama_chat/tools/generate_image.rb +6 -8
  35. data/lib/ollama_chat/tools/generate_password.rb +0 -2
  36. data/lib/ollama_chat/tools/get_current_weather.rb +2 -4
  37. data/lib/ollama_chat/tools/get_cve.rb +0 -2
  38. data/lib/ollama_chat/tools/get_endoflife.rb +0 -2
  39. data/lib/ollama_chat/tools/get_ghr.rb +2 -4
  40. data/lib/ollama_chat/tools/get_jira_issue.rb +0 -2
  41. data/lib/ollama_chat/tools/get_location.rb +0 -2
  42. data/lib/ollama_chat/tools/get_rfc.rb +0 -2
  43. data/lib/ollama_chat/tools/get_time.rb +2 -3
  44. data/lib/ollama_chat/tools/get_url.rb +0 -2
  45. data/lib/ollama_chat/tools/lookup_group.rb +0 -2
  46. data/lib/ollama_chat/tools/memorize.rb +38 -13
  47. data/lib/ollama_chat/tools/move_file.rb +8 -4
  48. data/lib/ollama_chat/tools/open_file_in_editor.rb +0 -2
  49. data/lib/ollama_chat/tools/paste_from_clipboard.rb +1 -4
  50. data/lib/ollama_chat/tools/paste_into_editor.rb +3 -5
  51. data/lib/ollama_chat/tools/patch_file.rb +10 -7
  52. data/lib/ollama_chat/tools/read_file.rb +3 -5
  53. data/lib/ollama_chat/tools/resolve_tag.rb +1 -2
  54. data/lib/ollama_chat/tools/roll_dice.rb +0 -2
  55. data/lib/ollama_chat/tools/run_tests.rb +2 -2
  56. data/lib/ollama_chat/tools/search_knowledge.rb +14 -5
  57. data/lib/ollama_chat/tools/search_web.rb +0 -3
  58. data/lib/ollama_chat/tools/write_file.rb +4 -3
  59. data/lib/ollama_chat/tools.rb +50 -4
  60. data/lib/ollama_chat/utils/tag_resolver.rb +1 -1
  61. data/lib/ollama_chat/version.rb +1 -1
  62. data/lib/ollama_chat/web_searching.rb +3 -3
  63. data/ollama_chat.gemspec +8 -8
  64. data/spec/ollama_chat/chat_spec.rb +1 -1
  65. data/spec/ollama_chat/collection_search_spec.rb +134 -0
  66. data/spec/ollama_chat/command_concern_spec.rb +174 -0
  67. data/spec/ollama_chat/commands_spec.rb +353 -11
  68. data/spec/ollama_chat/compaction_spec.rb +1 -1
  69. data/spec/ollama_chat/parsing_spec.rb +5 -5
  70. data/spec/ollama_chat/rag_handling_spec.rb +191 -18
  71. data/spec/ollama_chat/session_management_spec.rb +71 -3
  72. data/spec/ollama_chat/source_fetching_spec.rb +2 -2
  73. data/spec/ollama_chat/tool_calling_spec.rb +0 -11
  74. data/spec/ollama_chat/tools/browse_spec.rb +11 -7
  75. data/spec/ollama_chat/tools/compute_bmi_spec.rb +8 -8
  76. data/spec/ollama_chat/tools/concern_spec.rb +7 -1
  77. data/spec/ollama_chat/tools/copy_to_clipboard_spec.rb +8 -8
  78. data/spec/ollama_chat/tools/delete_file_spec.rb +7 -7
  79. data/spec/ollama_chat/tools/directory_structure_spec.rb +7 -7
  80. data/spec/ollama_chat/tools/execute_grep_spec.rb +10 -10
  81. data/spec/ollama_chat/tools/execute_jira_twg_spec.rb +8 -8
  82. data/spec/ollama_chat/tools/execute_ri_spec.rb +6 -6
  83. data/spec/ollama_chat/tools/execute_shell_spec.rb +1 -1
  84. data/spec/ollama_chat/tools/file_context_spec.rb +4 -4
  85. data/spec/ollama_chat/tools/forget_spec.rb +25 -15
  86. data/spec/ollama_chat/tools/gem_path_lookup_spec.rb +5 -5
  87. data/spec/ollama_chat/tools/generate_image_spec.rb +10 -10
  88. data/spec/ollama_chat/tools/generate_password_spec.rb +8 -8
  89. data/spec/ollama_chat/tools/get_current_weather_spec.rb +6 -6
  90. data/spec/ollama_chat/tools/get_cve_spec.rb +5 -5
  91. data/spec/ollama_chat/tools/get_endoflife_spec.rb +5 -5
  92. data/spec/ollama_chat/tools/get_ghr_spec.rb +11 -11
  93. data/spec/ollama_chat/tools/get_jira_issue_spec.rb +6 -6
  94. data/spec/ollama_chat/tools/get_location_spec.rb +6 -6
  95. data/spec/ollama_chat/tools/get_rfc_spec.rb +5 -5
  96. data/spec/ollama_chat/tools/get_time_spec.rb +4 -4
  97. data/spec/ollama_chat/tools/get_url_spec.rb +17 -17
  98. data/spec/ollama_chat/tools/lookup_group_spec.rb +5 -5
  99. data/spec/ollama_chat/tools/memorize_spec.rb +67 -11
  100. data/spec/ollama_chat/tools/move_file_spec.rb +8 -8
  101. data/spec/ollama_chat/tools/open_file_in_editor_spec.rb +6 -6
  102. data/spec/ollama_chat/tools/paste_from_clipboard_spec.rb +7 -7
  103. data/spec/ollama_chat/tools/paste_into_editor_spec.rb +6 -6
  104. data/spec/ollama_chat/tools/patch_file_spec.rb +9 -9
  105. data/spec/ollama_chat/tools/read_file_spec.rb +12 -12
  106. data/spec/ollama_chat/tools/resolve_tag_spec.rb +11 -15
  107. data/spec/ollama_chat/tools/roll_dice_spec.rb +6 -6
  108. data/spec/ollama_chat/tools/run_tests_spec.rb +13 -13
  109. data/spec/ollama_chat/tools/search_knowledge_spec.rb +33 -28
  110. data/spec/ollama_chat/tools/search_web_spec.rb +6 -6
  111. data/spec/ollama_chat/tools/write_file_spec.rb +15 -15
  112. metadata +12 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d2664dce7c6742ff5c950e64330b84325bcbb573467edde9aceddc3fa209641
4
- data.tar.gz: 87baa515acb398c9da1b7cf6c7fce173d946e4bd4d79ad0f1eb9dfbd2526235a
3
+ metadata.gz: 93612bbed949bff6bad6cf89f581b324165c0aa672f6f92b675b63a6a2dce7e0
4
+ data.tar.gz: 79aac9f7dada8df87035af5bdfd2913d16012af44a781b7a20c44a457163b313
5
5
  SHA512:
6
- metadata.gz: a091addc87c893f700f405147386dc8fe8fcc5c3134ad12bb1ffda570ec8a0f64ac4acbaeaed70116a6c61d0765e14e2f085fa8d2c0b98496fb0881a56fdb0f8
7
- data.tar.gz: 6d078a9c69b50719dda02e9c81a32d51929627ab6e50c7cae023630543e717dd2c1a86166495f339875178108e0ded60f9f451ebbec1275f16f06771c9b1b215
6
+ metadata.gz: 2eb715b50338ddb686e6aab7debd264591e1018216adef57146237a862a5f37f217a31bb481a724b4bb8fcc99144c1a40679e3048c64cb88f0eaaabd546661fc
7
+ data.tar.gz: 8fa30b91ba125939cd506078bcaf6c9549bb43aa6323f9186e0acae218c7814fd8add54aa75b39f907d4affc78889c52a45b2479ec57c4218d76e61e67843ed3
data/CHANGES.md CHANGED
@@ -1,5 +1,182 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-10-05 v0.0.126
4
+
5
+ * **Tool Registry Refactor**: Refactored the tool registry to store tool
6
+ classes instead of pre-built instances.
7
+ * `registered_class(name)` now returns the class for `summary_template`
8
+ access.
9
+ * `registered_tool(name, chat:)` instantiates a fresh instance bound to
10
+ the current session.
11
+ * `Concern#initialize(chat)` injects the chat reference; all tools now
12
+ access config via `self.chat` instead of `opts[:chat]`.
13
+ * Helper methods in `execute_shell`, `generate_image`,
14
+ `get_current_weather`, `get_ghr`, and `patch_file` now use `self.chat`
15
+ directly.
16
+ * Migrated all tool specs to use `described_class.new(chat)` and
17
+ `.execute(tool_call)`.
18
+
19
+ * **Path Allowlists**: Moved path allowlists into tool parameter
20
+ descriptions.
21
+ * Added `allowed_paths` to `tools/concern.rb` to return expanded,
22
+ existing paths for the calling tool's `allowed` config.
23
+ * Removed `tool_paths_allowed` from `tool_calling.rb` and its spec.
24
+ * Injected `allowed_paths.map(&:inspect).join(', ')` into the `path`,
25
+ `source`, and `destination` parameter descriptions of `delete_file`,
26
+ `move_file`, `patch_file`, `read_file`, and `write_file`.
27
+
28
+ * **RAG Handling & Runtime Info**: Relocated `collection_descriptions` from
29
+ `information.rb` to `rag_handling.rb`.
30
+ * Removed `collections` key from `static_runtime_information_values` and
31
+ `default_config.yml`.
32
+ * Migrated `static_runtime_information` and `dynamic_runtime_information`
33
+ from `String#%` to `named_placeholders_interpolate`.
34
+
35
+ * **Collection Commands**: Reworked collection clearing into three distinct
36
+ commands.
37
+ * Split monolithic `clear_collection` into `clear_whole_collection`,
38
+ `clear_collection_tags`, and `clear_collection_sources`.
39
+ * `clear_collection_tags` implements a two-level tag-to-source browser.
40
+ * `clear_collection_sources` implements a source-to-tags browser.
41
+ * Added `use_pager(force:)` keyword in `pager.rb` to bypass size checks
42
+ using `Float::INFINITY`.
43
+ * Added `Kramdown::ANSI::Width.wrap(..., percentage: 90)` to pager views
44
+ for terminal-width wrapping.
45
+ * Bumped `documentrix` minimum to **0.8.0** in `Rakefile` and
46
+ `ollama_chat.gemspec` for the new `records(tags:, sources:)` API.
47
+ * Extended `/collection` regexp and completion for `clear sources`.
48
+
49
+ * **Memory Tools**: Enhanced memory management tools.
50
+ * `memorize` now accepts an optional `update` kwarg (ISO timestamp tag)
51
+ to replace an existing memory entry, returning `replaced_timestamp` and
52
+ `replaced_text`.
53
+ * `forget` now returns `forgotten_text` in the JSON response and includes
54
+ an 80-character snippet in the human-readable `message`.
55
+ * Renamed `set_current_collection` to `set_documents_collection` in
56
+ `rag_handling.rb`, `session_management.rb`, and specs.
57
+ * Made `current_collection` public in `RAGHandling`.
58
+ * Enriched `search_knowledge` tool `collection` param with allowed
59
+ collections from `collection_descriptions` and current default.
60
+
61
+ * **Session Management**:
62
+ * Derive memory trigger from persona at session apply by adding a
63
+ `trigger` block to `default_config.yml`.
64
+ * Extracted `create_memory_collection` into `RAGHandling` as the single
65
+ canonical entry point.
66
+ * In `SessionManagement#session_apply`, auto-derive the session trigger
67
+ from `initial_persona_name` and `config.trigger` when no explicit
68
+ trigger is set.
69
+ * Advertise `C-u` random shortcut in the session prompt to clear prefill
70
+ and obtain a random session name.
71
+
72
+ * **Commands & UX**:
73
+ * Added `-r` flag to `/compose` to quote the last assistant reply as an
74
+ `edit_text` pre-fill.
75
+ * Removed the `confirm?` prompt before `compact_with_retry` in
76
+ `/conversation compact`; compaction now runs immediately.
77
+ * Split `/collection clear` into `clear tags` subcommand and bare `clear`
78
+ routing to `clear_whole_collection`.
79
+
80
+ * **Tool Descriptions & Logging**:
81
+ * Made `paste_into_editor` description imperative.
82
+ * Documented bottom-up edit order in `patch_file` to prevent models from
83
+ incorrectly compensating for line shifts.
84
+ * Log full `edits` array in `patch_file` tool info log payload instead of
85
+ just `edits_count`.
86
+ * Strip ANSI codes from `run_tests` captured output using
87
+ `OllamaChat::Utils::StripANSI`.
88
+
89
+ * **Compatibility & Fixes**:
90
+ * Include `persona_name` in the `default` system prompt template in
91
+ `default_config.yml`.
92
+
93
+ ## 2026-10-03 v0.0.125
94
+
95
+ ### Added
96
+
97
+ * Session-scoped trigger injection mechanism:
98
+ * New migration `011_add_trigger_to_sessions` adding a `:text` JSON column to
99
+ `sessions`.
100
+ * `Session#trigger` attribute with `plugin :serialization, :json` and `nil`
101
+ default.
102
+ * `CollectionSearch#trigger_inject` dispatch logic including embedding
103
+ guards, collection switching, document finding, and reranking.
104
+ * New `/session trigger edit` command registered in `commands.rb`.
105
+ * `edit_session_trigger` method in `session_management.rb` supporting dual
106
+ chooser and prefill.
107
+ * Trigger configuration display (⚡ block) added to `info_rag` in
108
+ `information.rb`.
109
+ * `runtime_information_inject` extracted from `chat.rb` into
110
+ `information.rb`.
111
+ * New prompt contexts: `prompts.rerank` (`default`, `memory_rerank`,
112
+ `lorebook_rerank`), `prompts.prompt.snippets_retrieval`, and
113
+ `prompts.prompt.snippets_trigger`.
114
+ * `persona_name` line added to `prompts.system.persona`.
115
+ * `TagResult` changed from `Data.define` to `Struct.new` to allow mutable
116
+ fields.
117
+ * `∅` fallback for empty patterns in `collection_stats`.
118
+ * Info-level logging for embedding requests/responses and rerank survivors in
119
+ `collection_search.rb`.
120
+ * New spec file `collection_search_spec.rb` with 7 examples for
121
+ `#trigger_inject`.
122
+
123
+ ### Changed
124
+
125
+ * `SessionLocking#lock?` now accepts a required `chat:` keyword argument to
126
+ decouple feedback delegation from the host object.
127
+ * `search_knowledge.rb` updated to use `snippets_retrieval` DB prompt,
128
+ `collection` block, and `database_collection?` guard.
129
+ * `@embedding` instance variable swept to `embedding` accessor in
130
+ `information.rb`, `source_fetching.rb`, and `web_searching.rb`.
131
+ * `Tins::Duration#format` used for compaction voice timing in
132
+ `report_compaction`, replacing `%.1f seconds`.
133
+ * `tins` dependency bumped from `~> **1.58**` to `~> **1.60**` to support
134
+ unpadded `%H`, `%M`, and `%u` directives.
135
+ * `resolve_tag_spec.rb` updated to replace an unreachable rescue-path test with
136
+ a "tagged file does not exist" test.
137
+ * `search_knowledge_spec.rb` updated for new prompt names, `collection` block,
138
+ and `tags_set` doubles.
139
+ * `compaction_spec.rb` regex updated to match the new duration format.
140
+ * `Tins::GO.go` flag string reordered in `bin/ollama_chat_log`.
141
+
142
+ ### Fixed
143
+
144
+ * Spec expectations in `session_management_spec.rb`, `parsing_spec.rb`, and
145
+ `source_fetching_spec.rb` updated to use Ruby 3.1+ hash shorthand `chat:`.
146
+
147
+ ### Tests
148
+
149
+ * Added specs for `CommandConcern` class methods:
150
+ * `help_message` via `OllamaChat::Commands`: table structure, category
151
+ headers, pattern filtering, subcommand expansion, and optional/mandatory
152
+ asterisks.
153
+ * `command_completions`: array shape, bare names, subcommand variants, and
154
+ sort order.
155
+ * `Command` class: `execute_if_match?` (match, captures, nil-regexp path,
156
+ no-context error), `completions` (product, optional bare, strict),
157
+ `command_names`, and `optional?`.
158
+ * Duplicate registration guard against the real `:copy` entry in
159
+ `OllamaChat::Commands`.
160
+ * Added specs for remaining command dispatch gaps:
161
+ * `/paste -i` stdin path via `paste_from_stdin`.
162
+ * `/last -v` voice output via `speak_last`.
163
+ * `/regenerate` success path (drop + return content).
164
+ * `/prompt delete` routes to `choose_and_delete_prompt`.
165
+ * `/web` without explicit count passes `nil`.
166
+ * `/character` JSON success paths: `info` (pager), `load` (raw content),
167
+ `import` (delegation).
168
+ * `/character` PNG `load` with `PNGMetadataExtractor`.
169
+ * Added specs for `SessionManagement#edit_session_trigger`:
170
+ * Setting trigger from `nil` to a configured hash with `enabled`,
171
+ `prompt_name`, and `text_count`.
172
+ * Updating config for an already-configured collection.
173
+ * Switching the trigger to a different collection name.
174
+ * Added specs for session model options and trigger edit:
175
+ * `/session model options` delegates to `edit_session_model_options` and
176
+ returns `:next`.
177
+ * `/session trigger edit` delegates to `edit_session_trigger` and returns
178
+ `:next`.
179
+
3
180
  ## 2026-10-02 v0.0.124
4
181
 
5
182
  ### Added
data/Rakefile CHANGED
@@ -40,7 +40,7 @@ GemHadar do
40
40
 
41
41
  dependency 'excon', '~> 1.0'
42
42
  dependency 'ollama-ruby', '~> 1.23'
43
- dependency 'documentrix', '>= 0.7.0'
43
+ dependency 'documentrix', '>= 0.8.0'
44
44
  dependency 'unix_socks', '>= 0.4'
45
45
  dependency 'rss', '~> 0.3'
46
46
  dependency 'term-ansicolor', '~> 1.11'
@@ -49,7 +49,7 @@ GemHadar do
49
49
  dependency 'reverse_markdown', '~> 3.0'
50
50
  dependency 'kramdown-ansi', '~> 0.5'
51
51
  dependency 'complex_config', '~> 0.22', '>= 0.22.2'
52
- dependency 'tins', '~> 1.58'
52
+ dependency 'tins', '~> 1.60'
53
53
  dependency 'search_ui', '>= 0.2'
54
54
  dependency 'amatch', '~> 0.4'
55
55
  dependency 'pdf-reader', '~> 2.0'
data/bin/ollama_chat_log CHANGED
@@ -4,7 +4,7 @@ require 'ollama_chat'
4
4
  require 'file/tail'
5
5
  require 'tins/xt'
6
6
 
7
- opts = Tins::GO.go 'fm:n:h:', defaults: { ?n => 10 }
7
+ opts = Tins::GO.go 'hfm:n:', defaults: { ?n => 10 }
8
8
  log_path = ARGV.shift || OC::OLLAMA::CHAT::LOG::CHAT
9
9
 
10
10
  if opts[?h]
@@ -314,10 +314,11 @@ class OllamaChat::Chat
314
314
  # instance when a background thread was spawned (so callers can call
315
315
  # `cancel_speaking`), or `nil` for inline playback or when voice is off
316
316
  def speak(text, background: true)
317
+ session or return
317
318
  voice.on? or return
318
- voice = session.current_voice.full? or return
319
+ current_voice = session.current_voice.full? or return
319
320
  voice_handler.method_defined?(:speak) or return
320
- voice_handler.new(chat: self, voice:).speak(text, background:)
321
+ voice_handler.new(chat: self, voice: current_voice).speak(text, background:)
321
322
  end
322
323
 
323
324
  # Returns a human-readable string representation of the Chat object,
@@ -553,17 +554,8 @@ class OllamaChat::Chat
553
554
  group_uuid:
554
555
  )
555
556
 
556
- if runtime_info.on?
557
- tool_name = 'runtime_information'
558
- messages << OllamaChat::Message.new(
559
- role: 'user',
560
- tool_name: ,
561
- sender_name: tool_name,
562
- content: dynamic_runtime_information,
563
- images: @images.dup,
564
- group_uuid:
565
- )
566
- end
557
+ runtime_information_inject(group_uuid:)
558
+ trigger_inject(content, group_uuid:)
567
559
  end
568
560
  @images.clear
569
561
  handler = OllamaChat::FollowChat.new(
@@ -24,14 +24,37 @@ module OllamaChat::CollectionSearch
24
24
  tags = Documentrix::Utils::Tags.new(
25
25
  tags, valid_tag: /\A#*([-\w.\]\[]+)/
26
26
  )
27
- @documents.find_where(
28
- query.first(config.embedding.model.context_length),
27
+ effective_query = query.first(
28
+ config.embedding.model.context_length
29
+ )
30
+ embedding_prompt = config.embedding.model.prompt?
31
+ log(
32
+ :info, 'Embedding search request',
33
+ data: {
34
+ model: config.embedding.model.to_h,
35
+ collection: collection,
36
+ query: effective_query,
37
+ prompt: embedding_prompt,
38
+ }
39
+ )
40
+ records = @documents.find_where(
41
+ effective_query,
29
42
  tags:,
30
- prompt: config.embedding.model.prompt?,
43
+ prompt: embedding_prompt,
31
44
  text_size:,
32
45
  text_count:,
33
46
  min_similarity:
34
47
  )
48
+ log(
49
+ :info, 'Embedding search response',
50
+ data: {
51
+ collection: collection,
52
+ query: effective_query,
53
+ result_count: records.size,
54
+ top: records.first&.similarity&.round(4),
55
+ }
56
+ )
57
+ records
35
58
  end
36
59
 
37
60
  # Reranks a set of records using the LLM with the `rerank` prompt.
@@ -43,11 +66,11 @@ module OllamaChat::CollectionSearch
43
66
  # @return [Array<Documentrix::Utils::TagResult>] filtered records
44
67
  def rerank_records(query, records, prompt_name: nil)
45
68
  candidates = records.each_with_index.map { |r, i|
46
- "[#{i}] #{truncate(r.text.strip, length: 300)}"
69
+ "[#{i}] #{r.tags_set.to_s(link: false)} #{truncate(r.text.squeeze, length: 300)}"
47
70
  }.join("\n")
48
71
 
49
- prompt_name ||= 'rerank'
50
- rerank_prompt = prompt(prompt_name) or
72
+ prompt_name ||= 'default'
73
+ rerank_prompt = prompt(prompt_name, context: 'rerank') or
51
74
  raise 'missing prompt %s' % prompt_name.inspect
52
75
  rerank_prompt = rerank_prompt.to_s
53
76
  .named_placeholders_interpolate({ query:, candidates: })
@@ -63,4 +86,77 @@ module OllamaChat::CollectionSearch
63
86
  end
64
87
  records
65
88
  end
89
+
90
+ # Injects relevant snippets from the session's trigger collection
91
+ # into the conversation as a tool message.
92
+ #
93
+ # Reads the first entry of the session's `trigger` hash, performs a
94
+ # vector search in the configured collection, reranks the candidates
95
+ # via the LLM, and appends a `trigger_inject` tool message containing
96
+ # the surviving snippets (text, similarity, tags). The message is
97
+ # grouped with the current user message via `group_uuid`.
98
+ #
99
+ # @param content [String] the parsed user message text used as the
100
+ # search query
101
+ # @param group_uuid [String, nil] the group UUID for message grouping
102
+ # (shared with the user message and runtime-info message)
103
+ #
104
+ # @return [self, nil] self if snippets were injected, nil if no
105
+ # trigger is configured, the trigger or embedding is disabled, the
106
+ # collection or prompt is missing, or no snippets survived reranking
107
+ def trigger_inject(content, group_uuid:)
108
+ embedding.on? or return
109
+ trigger = session.trigger&.first or return
110
+ collection, config = trigger
111
+ unless config['enabled']
112
+ log(:info, 'Trigger for collection %s disabled.' % collection)
113
+ return
114
+ end
115
+ unless col = database_collection?(collection)
116
+ log(:error, 'Unknown collection named %s' % collection)
117
+ return
118
+ end
119
+ prompt_name = config['prompt_name']
120
+ unless rerank_prompt = prompt(prompt_name, context: 'rerank').full?(:to_s)
121
+ log(
122
+ :error, 'Unknown rerank prompt named %s' % prompt_name,
123
+ data: { config: }
124
+ )
125
+ return
126
+ end
127
+ records = []
128
+ switch_collection(collection) do
129
+ records = find_document_records(content, text_count: config['text_count'])
130
+ records.empty? and return
131
+ pre_rerank = records.size
132
+ 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
+ records.empty? and return
136
+ end
137
+ snippets = records.map { |record|
138
+ {
139
+ text: record.text,
140
+ similarity: record.similarity.to_f,
141
+ tags: record.tags_set.to_s(link: false),
142
+ }
143
+ }
144
+ message_content = {
145
+ prompt: prompt('snippets_trigger').to_s,
146
+ collection: {
147
+ name: collection,
148
+ description: col.description&.to_s,
149
+ },
150
+ snippets:
151
+ }.to_json
152
+ tool_name = 'trigger_inject'
153
+ messages << OllamaChat::Message.new(
154
+ role: 'user',
155
+ tool_name: ,
156
+ sender_name: tool_name,
157
+ content: message_content,
158
+ group_uuid:
159
+ )
160
+ self
161
+ end
66
162
  end
@@ -113,15 +113,16 @@ module OllamaChat::Commands
113
113
 
114
114
  command(
115
115
  name: :session,
116
- regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|delete|model options change|model options))?((?:\s+-(?:p\s*\w+))*)(?:\s+([^-].*))?$),
117
- complete: [ 'session', %w[ change previous list new duplicate rename delete model\ options\ change model\ options ] ],
116
+ regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|delete|model options change|model options|trigger edit))?((?:\s+-(?:p\s*\w+))*)(?:\s+([^-].*))?$),
117
+ complete: [ 'session', %w[ change previous list new duplicate rename delete model\ options\ change model\ options trigger\ edit ] ],
118
118
  optional: true,
119
119
  options: "[-p profile]\n[name]",
120
120
  help: <<~EOT
121
121
  💬 Manage sessions:
122
122
  - list/new/delete/rename/duplicate
123
123
  - change [name]/previous
124
- - model options/change
124
+ - model options/change
125
+ - trigger edit
125
126
  EOT
126
127
  ) do |subcommand, opts, name|
127
128
  case subcommand
@@ -152,6 +153,8 @@ module OllamaChat::Commands
152
153
  else
153
154
  feedback("No previous session defined.", type: :info)
154
155
  end
156
+ when 'trigger edit'
157
+ edit_session_trigger
155
158
  end
156
159
  :next
157
160
  end
@@ -424,15 +427,7 @@ module OllamaChat::Commands
424
427
  feedback("Denied.", type: :denied)
425
428
  end
426
429
  when 'compact'
427
- if confirm?(
428
- prompt: '🔔 Compact conversation? Old messages will be summarized. (y/n) ',
429
- yes: /\Ay/i
430
- )
431
- then
432
- compact_with_retry
433
- else
434
- feedback("Denied.", type: :denied)
435
- end
430
+ compact_with_retry
436
431
  when 'summarize'
437
432
  opts = go_command('s', opts)
438
433
  summarize_conversation(sentence: opts[?s])
@@ -645,12 +640,12 @@ module OllamaChat::Commands
645
640
 
646
641
  command(
647
642
  name: :collection,
648
- regexp: %r(^/collection(?:\s+(change|clear|list|rename|update(?: all)?|new|edit|delete|query))?((?:\s+-[er])*)?$),
649
- complete: [ 'collection', %w[ change clear list rename update update\ all new edit delete query ] ],
643
+ regexp: %r(^/collection(?:\s+(change|clear(?: (?:tags|sources))?|list|rename|update(?: all)?|new|edit|delete|query))?((?:\s+-[er])*)?$),
644
+ complete: [ 'collection', %w[ change clear\ tags clear\ sources clear list rename update update\ all new edit delete query ] ],
650
645
  optional: true,
651
646
  help: <<~EOT
652
647
  📚 Manage RAG collections:
653
- - change/clear/list/rename
648
+ - change/clear/clear tags/clear sources/list/rename
654
649
  - update: Re-index modified docs
655
650
  - update all: Re-index all collections
656
651
  - new: Interactively create a new collection
@@ -662,7 +657,11 @@ module OllamaChat::Commands
662
657
  ) do |subcommand, opts|
663
658
  case subcommand
664
659
  when 'clear'
665
- clear_collection
660
+ clear_whole_collection
661
+ when 'clear tags'
662
+ clear_collection_tags
663
+ when 'clear sources'
664
+ clear_collection_sources
666
665
  when 'change'
667
666
  choose_collection(collection)
668
667
  when 'list'
@@ -826,12 +825,23 @@ module OllamaChat::Commands
826
825
 
827
826
  command(
828
827
  name: :compose,
829
- regexp: %r(^/compose$),
828
+ regexp: %r(^/compose(\s+-r)?\s*$),
829
+ options: '[-r]',
830
830
  help: <<~EOT
831
831
  \u270D Compose message in external editor
832
+ Options: -r pre-fill with quoted last assistant reply
832
833
  EOT
833
- ) do
834
- edit_text.full? or :next
834
+ ) do |opts|
835
+ opts = go_command('r', opts)
836
+ prefill = if opts[?r]
837
+ if msg = messages.find_last(content: true) { !_1.tool? && _1.role == 'assistant' }
838
+ msg.content.to_s.lines.map { "> #{_1}" }.join + "\n\n"
839
+ else
840
+ feedback('No assistant message to quote.', type: :warn)
841
+ next :next
842
+ end
843
+ end
844
+ edit_text(prefill).full? or :next
835
845
  end
836
846
 
837
847
  command(
@@ -21,7 +21,7 @@ module OllamaChat::Compaction
21
21
  # @return [String] a short natural-language description of the call.
22
22
  def tool_summary_line(tool_name, result)
23
23
  default_summary = "was called."
24
- klass = OllamaChat::Tools.registered[tool_name.to_s]&.class
24
+ klass = OllamaChat::Tools.registered_class(tool_name)
25
25
  klass&.summary_template(result:) || default_summary
26
26
  rescue StandardError
27
27
  default_summary
@@ -295,11 +295,15 @@ module OllamaChat::Compaction
295
295
  Context: #{result.context_before} → #{result.context_after}
296
296
  Stored total: #{result.stored_total}
297
297
  EOT
298
+ duration = Tins::Duration.new(duration)
299
+ duration_string = duration.format(
300
+ duration.minutes? ? '%M minutes and %u seconds' : '%u seconds'
301
+ )
298
302
  speak(
299
- 'Compressed conversation context from %.f to %.f in %.1f seconds.' % [
303
+ 'Compressed conversation context from %.f to %.f in %s.' % [
300
304
  100 * result.context_filled_before,
301
305
  100 * result.context_filled_after,
302
- duration,
306
+ duration_string,
303
307
  ]
304
308
  )
305
309
  end
@@ -0,0 +1,7 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table :sessions do
4
+ add_column :trigger, :text
5
+ end
6
+ end
7
+ end
@@ -12,6 +12,7 @@ class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
12
12
  plugin :touch
13
13
  plugin :serialization, :json, :tools_default_enabled
14
14
  plugin :serialization, :json, :model_options
15
+ plugin :serialization, :json, :trigger
15
16
  plugin :validation_helpers
16
17
 
17
18
  # Validates the session instance.
@@ -133,6 +134,15 @@ class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
133
134
  # @!attribute [v] model_options
134
135
  # @return [String, nil] A JSON-serialized string containing model-specific options.
135
136
  #
137
+ # @!attribute [v] trigger
138
+ # @return [Hash, nil] Maps collection names to search/rerank
139
+ # overrides, e.g.
140
+ # `{ 'memory-miyu' => { 'prompt_name' => 'memory_rerank',
141
+ # 'text_count' => 5 } }`.
142
+ # Recognized keys: `prompt_name`, `text_count`, `text_size`,
143
+ # `min_similarity`.
144
+ # `nil` when the trigger mechanism is disabled for this session.
145
+ #
136
146
  # @!attribute [v] messages
137
147
  # @return [String] The full conversation history, stored in JSONL format.
138
148
  #
@@ -184,6 +194,7 @@ class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
184
194
  context_format: chat.config.context.format,
185
195
  working_directory: Dir.pwd,
186
196
  messages: '',
197
+ trigger: nil,
187
198
  }
188
199
  new(attributes)
189
200
  end
@@ -25,10 +25,11 @@ module OllamaChat::Database::SessionLocking
25
25
 
26
26
  # Attempts to acquire a lock on the session.
27
27
  #
28
+ # @param chat [OllamaChat] the chat instance that receives feedback
28
29
  # @return [Boolean] true if the lock was successfully acquired, false if already locked.
29
- def lock?
30
+ def lock?(chat:)
30
31
  if running_pid = locked?
31
- feedback("session #{id} locked by running process #{running_pid}", type: :warn)
32
+ chat.feedback("session #{id} locked by running process #{running_pid}", type: :warn)
32
33
  false
33
34
  else
34
35
  lock
@@ -180,7 +180,7 @@ class OllamaChat::FollowChat
180
180
  feedback(
181
181
  ("\n%s Execution of tool %s confirmed.\n\n" % [ symbol, bold { name } ])
182
182
  )
183
- result = OllamaChat::Tools.registered[name].execute(tool_call, chat:)
183
+ result = OllamaChat::Tools.registered_tool(name, chat:).execute(tool_call)
184
184
  chat.log(:info, "Tool execution confirmed", data: { tool: name, confirmed: })
185
185
  end
186
186