ollama_chat 0.0.115 → 0.0.116
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.
- checksums.yaml +4 -4
- data/CHANGES.md +67 -0
- data/lib/ollama_chat/commands.rb +18 -50
- data/lib/ollama_chat/conversation.rb +2 -6
- data/lib/ollama_chat/dialog.rb +34 -0
- data/lib/ollama_chat/file_editing.rb +2 -5
- data/lib/ollama_chat/information.rb +1 -0
- data/lib/ollama_chat/message_output.rb +3 -11
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +56 -30
- data/lib/ollama_chat/prompt_management.rb +1 -1
- data/lib/ollama_chat/rag_handling.rb +4 -3
- data/lib/ollama_chat/session_management.rb +116 -15
- data/lib/ollama_chat/tool_calling.rb +13 -10
- data/lib/ollama_chat/version.rb +1 -1
- data/ollama_chat.gemspec +2 -2
- data/spec/ollama_chat/chat_spec.rb +1 -1
- data/spec/ollama_chat/commands_spec.rb +30 -0
- data/spec/ollama_chat/compaction_spec.rb +5 -4
- data/spec/ollama_chat/conversation_spec.rb +3 -3
- data/spec/ollama_chat/session_management_spec.rb +86 -0
- data/spec/ollama_chat/tool_calling_spec.rb +12 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: beebfc1a29093b596fe008ba59b2f377922e2f7beeeb529ebed899fc374c6e02
|
|
4
|
+
data.tar.gz: e704bce9b15101283e802a6010ee08c347cf94d8fcdc2ee785da3880debde736
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7cab4e5d47d97dd215e058ab9960ad8801252a2ab950d3b30ce1e514fad30b22e87f917e3b1764592070f385fcfec748cc3b59398cadf8e3553f41e6ec48e9bb
|
|
7
|
+
data.tar.gz: 0fb71bb0ab178cb1fed41ea2b257360fd19d3d180a2fc2f497d1372874d4665c3b82676ad8c056ab80a22e21e95deebb8afee8ef5f2809a07671e6cfb53ba5d7
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-09-03 v0.0.116
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
* `session.roleplay_story` prompt in `default_config.yml` for narrative
|
|
8
|
+
retelling of roleplay sessions via `/conversation report`.
|
|
9
|
+
* `session.coding_brief` prompt in `default_config.yml`.
|
|
10
|
+
* `summarize` and `report` subcommands to `/conversation`.
|
|
11
|
+
* `summarize_conversation(sentence:, save:)` and `report_conversation(name:,
|
|
12
|
+
save:)` methods in `session_management.rb` to handle filename prompts,
|
|
13
|
+
overwrite checks, and I/O.
|
|
14
|
+
* `report_session(name:, &block)` method using `messages.compacted_messages` to
|
|
15
|
+
keep interpolated content bounded after compaction.
|
|
16
|
+
* `Status:` line with `✅`/`⛔` indicators to `collection_stats` in
|
|
17
|
+
`information.rb`.
|
|
18
|
+
* `✅`/`⛔` prefixes to `list_collections` in `rag_handling.rb` to replace
|
|
19
|
+
`[DISABLED]` text suffixes.
|
|
20
|
+
* `✅`/`⛔` indicators for enabled/disabled states and `❔`/`⭕` for
|
|
21
|
+
confirmation requirements in `list_tools` in `tool_calling.rb`.
|
|
22
|
+
* `ask_for_filename?(action:)` and `should_overwrite?(filename)` helpers in
|
|
23
|
+
`dialog.rb` with YARD documentation.
|
|
24
|
+
* Specs for `summarize_conversation`, `report_session`, and
|
|
25
|
+
`report_conversation` in `session_management_spec.rb`.
|
|
26
|
+
* Routing specs for `/conversation summarize` and `/conversation report` in
|
|
27
|
+
`commands_spec.rb`.
|
|
28
|
+
* YARD documentation for `repair_group_uuids`.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
* Moved `summarize` subcommand from `/session` to `/conversation`.
|
|
33
|
+
* Reworked compaction prompt to a draft-rewrite model, treating previous
|
|
34
|
+
summaries as drafts to be rewritten rather than ambient context.
|
|
35
|
+
* Replaced "6-12 sentences" constraint in compaction prompt with "Be concise;
|
|
36
|
+
skip filler and repetition".
|
|
37
|
+
* Changed `assemble: |-` to `assemble: |` in compaction prompt to preserve
|
|
38
|
+
trailing newlines.
|
|
39
|
+
* Widened factuality scope in compaction prompt to include "groups or previous
|
|
40
|
+
summary above".
|
|
41
|
+
* Wrapped `list_tools` body in `use_pager`, redirecting writes through the
|
|
42
|
+
yielded `output` IO.
|
|
43
|
+
* Passed `output:` keyword argument to `tools_support.show(output:)`.
|
|
44
|
+
* Replaced inline `File.exist?` and `confirm?` blocks in
|
|
45
|
+
`conversation.rb#save_conversation`,
|
|
46
|
+
`message_output.rb#attempt_to_write_file`, `session_management.rb`, and
|
|
47
|
+
`file_editing.rb#determine_valid_output_filename` with the shared
|
|
48
|
+
`should_overwrite?` helper.
|
|
49
|
+
* Updated `chat_spec.rb` `collection_stats` regex to include the new `Status:
|
|
50
|
+
✅` line.
|
|
51
|
+
* Updated `tool_calling_spec.rb` `list_tools` regex to use `✅`/`⛔` instead of
|
|
52
|
+
`✓`/`☐`.
|
|
53
|
+
* Updated `tool_calling_spec.rb` `#list_tools` example to stub `use_pager` with
|
|
54
|
+
a `StringIO` yield.
|
|
55
|
+
* Updated `conversation_spec.rb` expectations for `Pathname` type and
|
|
56
|
+
`should_overwrite?` STDERR output.
|
|
57
|
+
* Updated comment in `lib/ollama_chat/prompt_management.rb` to reflect new
|
|
58
|
+
naming convention for prompt templates (e.g., `suggest.coding`).
|
|
59
|
+
* Rewrapped `prompts.suggest.roleplaying` few-shot examples to stay within 80
|
|
60
|
+
columns in `default_config.yml`.
|
|
61
|
+
* Fixed stale YARD example in `session_management.rb#report_session`
|
|
62
|
+
(`roleplay_report` to `roleplay_story`).
|
|
63
|
+
|
|
64
|
+
### Removed
|
|
65
|
+
|
|
66
|
+
* `pretty:` keyword argument from `summarize_session` and
|
|
67
|
+
`summarize_conversation`; markdown formatting is now the only path.
|
|
68
|
+
* `%{message_thinking}` from the `session_summarize` prompt.
|
|
69
|
+
|
|
3
70
|
## 2026-09-01 v0.0.115
|
|
4
71
|
|
|
5
72
|
* Added `OllamaChat::Tools::ExecuteShell` in
|
data/lib/ollama_chat/commands.rb
CHANGED
|
@@ -291,15 +291,14 @@ module OllamaChat::Commands
|
|
|
291
291
|
|
|
292
292
|
command(
|
|
293
293
|
name: :session,
|
|
294
|
-
regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|
|
|
295
|
-
complete: [ 'session', %w[ change previous list new duplicate rename
|
|
294
|
+
regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|delete|model options change|model options))?((?:\s+-(?:p\s*\w+))*)(?:\s+([^-].*))?$),
|
|
295
|
+
complete: [ 'session', %w[ change previous list new duplicate rename delete model\ options\ change model\ options ] ],
|
|
296
296
|
optional: true,
|
|
297
|
-
options: "[-
|
|
297
|
+
options: "[-p profile]\n[name]",
|
|
298
298
|
help: <<~EOT
|
|
299
299
|
💬 Manage sessions:
|
|
300
300
|
- list/new/delete/rename/duplicate
|
|
301
301
|
- change [name]/previous
|
|
302
|
-
- summarize (-s sentence, -f save to file)
|
|
303
302
|
- model options/change
|
|
304
303
|
EOT
|
|
305
304
|
) do |subcommand, opts, name|
|
|
@@ -316,44 +315,6 @@ module OllamaChat::Commands
|
|
|
316
315
|
delete_session
|
|
317
316
|
when 'rename'
|
|
318
317
|
rename_session
|
|
319
|
-
when 'summarize'
|
|
320
|
-
opts = go_command('fs', opts)
|
|
321
|
-
if opts[?f] and
|
|
322
|
-
filename = switch_history(:filename) {
|
|
323
|
-
ask?(prompt: "❓ Enter filename: ")
|
|
324
|
-
}.full? { Pathname.new(_1) }
|
|
325
|
-
then
|
|
326
|
-
if filename.exist? && !confirm?(
|
|
327
|
-
prompt: "🔔 File #{filename.to_s.inspect} already exists, overwrite? (y/n) ",
|
|
328
|
-
yes: /\Ay/i
|
|
329
|
-
)
|
|
330
|
-
then
|
|
331
|
-
STDERR.puts "File not written!"
|
|
332
|
-
next :next
|
|
333
|
-
end
|
|
334
|
-
summary = summarize_session(pretty: true, sentence: opts[?s]) do |content|
|
|
335
|
-
infobar.puts kramdown_ansi_parse(content)
|
|
336
|
-
end
|
|
337
|
-
if summary.full?
|
|
338
|
-
filename.write(summary)
|
|
339
|
-
STDOUT.puts "File successfully written."
|
|
340
|
-
next :next
|
|
341
|
-
else
|
|
342
|
-
STDERR.puts "Nothing to summarize!"
|
|
343
|
-
next :next
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
summary = summarize_session(pretty: true, sentence: opts[?s]) do |content|
|
|
347
|
-
infobar.puts kramdown_ansi_parse(content) << ?\n
|
|
348
|
-
end
|
|
349
|
-
if summary.full?
|
|
350
|
-
use_pager do |output|
|
|
351
|
-
output.puts kramdown_ansi_parse(summary)
|
|
352
|
-
end
|
|
353
|
-
else
|
|
354
|
-
STDERR.puts "Nothing to summarize!"
|
|
355
|
-
next :next
|
|
356
|
-
end
|
|
357
318
|
when 'change'
|
|
358
319
|
change_session(name)
|
|
359
320
|
when 'model options'
|
|
@@ -598,15 +559,16 @@ module OllamaChat::Commands
|
|
|
598
559
|
|
|
599
560
|
command(
|
|
600
561
|
name: :conversation,
|
|
601
|
-
regexp: %r(^/conversation\s+(clean|compact|save|load)(
|
|
602
|
-
complete: [ 'conversation', %w[ save load clean compact ] ],
|
|
603
|
-
options: '[-c] [FILENAME]',
|
|
562
|
+
regexp: %r(^/conversation\s+(clean|compact|save|load|summarize|report)((?:\s+-(?:[sf]|c))*)(?:\s+([^-].*\.jsonl?))?$),
|
|
563
|
+
complete: [ 'conversation', %w[ save load clean compact summarize report ] ],
|
|
564
|
+
options: '[-s|-f|-c] [FILENAME]',
|
|
604
565
|
help: <<~EOT
|
|
605
|
-
💾
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
566
|
+
💾 Manage conversation content:
|
|
567
|
+
- save/load: Export/import as .json or .jsonl
|
|
568
|
+
- clean: Remove tool content, images, thinking
|
|
569
|
+
- compact: Summarize old messages, keep recent
|
|
570
|
+
- summarize: Per-message narrative (-s sentence, -f save)
|
|
571
|
+
- report: Generate a session report document
|
|
610
572
|
EOT
|
|
611
573
|
) do |subcommand,opts,path|
|
|
612
574
|
if %w[ save load ].include?(subcommand) && path.blank?
|
|
@@ -642,6 +604,12 @@ module OllamaChat::Commands
|
|
|
642
604
|
else
|
|
643
605
|
STDOUT.puts 'Cancelled.'
|
|
644
606
|
end
|
|
607
|
+
when 'summarize'
|
|
608
|
+
opts = go_command('sf', opts)
|
|
609
|
+
summarize_conversation(save: opts[?f], sentence: opts[?s])
|
|
610
|
+
when 'report'
|
|
611
|
+
opts = go_command('f', opts)
|
|
612
|
+
report_conversation(save: opts[?f])
|
|
645
613
|
end
|
|
646
614
|
:next
|
|
647
615
|
end
|
|
@@ -26,12 +26,8 @@ module OllamaChat::Conversation
|
|
|
26
26
|
# @example Save conversation with explicit filename
|
|
27
27
|
# chat.save_conversation('conversations/2023-10-15_my_session.json')
|
|
28
28
|
def save_conversation(filename, clean: false)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
prompt: "🔔 File #{filename.to_s.inspect} already exists, overwrite? (y/n) ",
|
|
32
|
-
yes: /\Ay/i
|
|
33
|
-
) or return
|
|
34
|
-
end
|
|
29
|
+
filename = Pathname.new(filename)
|
|
30
|
+
should_overwrite?(filename) or return
|
|
35
31
|
if messages.save_conversation(filename, messages: clean ? messages.clean_messages : messages.messages)
|
|
36
32
|
STDOUT.puts "Saved conversation to #{filename.to_s.inspect}."
|
|
37
33
|
else
|
data/lib/ollama_chat/dialog.rb
CHANGED
|
@@ -149,4 +149,38 @@ module OllamaChat::Dialog
|
|
|
149
149
|
def go_command(s, opt, defaults: {})
|
|
150
150
|
Tins::GO.go(s, opt.to_s.strip.split(/\s+/), defaults:)
|
|
151
151
|
end
|
|
152
|
+
|
|
153
|
+
# Prompts the user for a filename with history support.
|
|
154
|
+
#
|
|
155
|
+
# @param action [String, nil] an optional action descriptor appended to the
|
|
156
|
+
# prompt, e.g. "for summarization" ("❓ Enter filename for summarization…")
|
|
157
|
+
#
|
|
158
|
+
# @return [Pathname, nil] the user-supplied filename as a Pathname, or nil
|
|
159
|
+
# if the user cancels (C-c) or provides empty input
|
|
160
|
+
def ask_for_filename?(action: nil)
|
|
161
|
+
action and action = " #{action}"
|
|
162
|
+
switch_history(:filename) {
|
|
163
|
+
ask?(prompt: "❓ Enter filename#{action}, C-c ⇒ cancel: ")
|
|
164
|
+
}.full? { Pathname.new(_1) }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Checks whether a file can be written, prompting for overwrite
|
|
168
|
+
# confirmation if the file already exists.
|
|
169
|
+
#
|
|
170
|
+
# @param filename [Pathname] the target file to check
|
|
171
|
+
#
|
|
172
|
+
# @return [Boolean] `true` if the file does not exist or the user confirms
|
|
173
|
+
# overwriting; `false` if the file exists and the user declines
|
|
174
|
+
def should_overwrite?(filename)
|
|
175
|
+
if filename.exist? && !confirm?(
|
|
176
|
+
prompt: "🔔 File #{filename.to_s.inspect} already exists, overwrite? (y/n) ",
|
|
177
|
+
yes: /\Ay/i
|
|
178
|
+
)
|
|
179
|
+
then
|
|
180
|
+
STDERR.puts "File not written!"
|
|
181
|
+
false
|
|
182
|
+
else
|
|
183
|
+
true
|
|
184
|
+
end
|
|
185
|
+
end
|
|
152
186
|
end
|
|
@@ -113,15 +113,12 @@ module OllamaChat::FileEditing
|
|
|
113
113
|
def determine_valid_output_filename(action)
|
|
114
114
|
switch_history(:filename) do
|
|
115
115
|
loop do
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
if filename_str.nil?
|
|
116
|
+
filename = ask_for_filename?(action:)
|
|
117
|
+
unless filename
|
|
120
118
|
STDOUT.puts "Cancelled."
|
|
121
119
|
return nil
|
|
122
120
|
end
|
|
123
121
|
|
|
124
|
-
filename = Pathname.new(filename_str)
|
|
125
122
|
if filename.exist?
|
|
126
123
|
STDERR.puts "File #{filename.to_path.inspect} already exists!"
|
|
127
124
|
else
|
|
@@ -89,17 +89,9 @@ module OllamaChat::MessageOutput
|
|
|
89
89
|
# error occurred
|
|
90
90
|
def attempt_to_write_file(filename, content)
|
|
91
91
|
path = Pathname.new(filename.to_s).expand_path
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
yes: /\Ay/i
|
|
96
|
-
)
|
|
97
|
-
then
|
|
98
|
-
File.open(path, ?w) do |output|
|
|
99
|
-
output.write(content)
|
|
100
|
-
end
|
|
101
|
-
else
|
|
102
|
-
return
|
|
92
|
+
should_overwrite?(path) or return
|
|
93
|
+
File.open(path, ?w) do |output|
|
|
94
|
+
output.write(content)
|
|
103
95
|
end
|
|
104
96
|
true
|
|
105
97
|
end
|
|
@@ -151,21 +151,22 @@ prompts:
|
|
|
151
151
|
- **Format**: [Markdown, plain text, code blocks, etc.]
|
|
152
152
|
- **Vocabulary**: [Technical terms, slang, formal language]
|
|
153
153
|
- **Structure**: [How you organize your responses]
|
|
154
|
+
context_template_suggest: |
|
|
155
|
+
Conversation History:
|
|
156
|
+
%{history}
|
|
157
|
+
|
|
158
|
+
Create and output text following this instruction:
|
|
159
|
+
%{instruction}
|
|
154
160
|
session_summarize: |
|
|
155
|
-
|
|
161
|
+
Narrative so far:
|
|
156
162
|
|
|
157
163
|
%{context}
|
|
158
164
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
in the
|
|
163
|
-
|
|
164
|
-
Message
|
|
165
|
-
|
|
166
|
-
Thinking content was
|
|
167
|
-
|
|
168
|
-
%{message_thinking}
|
|
165
|
+
Extend the narrative above with the key contribution of the
|
|
166
|
+
%{sender_name} message below in one %{unit}. Skip tool-call
|
|
167
|
+
plumbing; focus on decisions, findings, and state changes. Do
|
|
168
|
+
not repeat information already present in the narrative.
|
|
169
|
+
Output only the summary text, without any sender name or prefix.
|
|
169
170
|
|
|
170
171
|
Response content was
|
|
171
172
|
|
|
@@ -175,12 +176,26 @@ prompts:
|
|
|
175
176
|
|
|
176
177
|
Create a title with a length of **less than %{length}** characters for
|
|
177
178
|
the conversation above. Output only the title and nothing else.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
%{
|
|
179
|
+
session:
|
|
180
|
+
coding_brief: |
|
|
181
|
+
%{content}
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
Write a brief of the session above. Your brief MUST:
|
|
184
|
+
- Describe the goal and how it evolved.
|
|
185
|
+
- Name key decisions, files, and architectural choices.
|
|
186
|
+
- State the current state: what works, what is pending.
|
|
187
|
+
- Be factual. Be concise.
|
|
188
|
+
roleplay_story: |
|
|
189
|
+
%{content}
|
|
190
|
+
|
|
191
|
+
Write a narrative retelling of the roleplay session
|
|
192
|
+
above. Your story MUST:
|
|
193
|
+
- Capture the emotional arc and key plot beats.
|
|
194
|
+
- Stay in the established setting and character voices.
|
|
195
|
+
- Use vivid, evocative prose; show, don't tell.
|
|
196
|
+
- Highlight turning points, revelations, and character
|
|
197
|
+
growth.
|
|
198
|
+
- Read it like a short story chapter, not a log.
|
|
184
199
|
suggest:
|
|
185
200
|
coding: |
|
|
186
201
|
## Context
|
|
@@ -318,25 +333,33 @@ prompts:
|
|
|
318
333
|
|
|
319
334
|
## Few-Shot Examples
|
|
320
335
|
|
|
321
|
-
Input: [Scene where a party is arguing in a rainy forest while being
|
|
322
|
-
Output:
|
|
323
|
-
|
|
336
|
+
Input: [Scene where a party is arguing in a rainy forest while being
|
|
337
|
+
hunted] Output: Demand that everyone stop shouting and listen for the
|
|
338
|
+
sound of footsteps in the brush.
|
|
324
339
|
|
|
325
|
-
Quietly admit to the party leader that you are terrified, leaning into
|
|
340
|
+
Quietly admit to the party leader that you are terrified, leaning into
|
|
341
|
+
their shoulder for support.
|
|
326
342
|
|
|
327
|
-
Examine the muddy tracks near the campfire to see if the hunters are
|
|
343
|
+
Examine the muddy tracks near the campfire to see if the hunters are
|
|
344
|
+
human or something else. Roll 1d20.
|
|
328
345
|
|
|
329
|
-
Secretly slip the map out of the leader's pocket while they are
|
|
346
|
+
Secretly slip the map out of the leader's pocket while they are
|
|
347
|
+
distracted by the argument. Roll 1d20.
|
|
330
348
|
|
|
331
|
-
Raise your voice and challenge every single person present to state their
|
|
349
|
+
Raise your voice and challenge every single person present to state their
|
|
350
|
+
true loyalty right now.
|
|
332
351
|
|
|
333
|
-
Attempt to leap across the ravine to flank the hunters, risking a fall
|
|
352
|
+
Attempt to leap across the ravine to flank the hunters, risking a fall
|
|
353
|
+
into the rapids. Roll 1d20.
|
|
334
354
|
|
|
335
|
-
Obsessively polish your sword with a rag, ignoring the argument entirely
|
|
355
|
+
Obsessively polish your sword with a rag, ignoring the argument entirely
|
|
356
|
+
until it is perfectly mirrored.
|
|
336
357
|
|
|
337
|
-
Suddenly reveal that you were actually sent here by the enemy as a double
|
|
358
|
+
Suddenly reveal that you were actually sent here by the enemy as a double
|
|
359
|
+
agent.
|
|
338
360
|
|
|
339
|
-
A sudden lightning strike hits a nearby tree, knocking everyone off
|
|
361
|
+
A sudden lightning strike hits a nearby tree, knocking everyone off
|
|
362
|
+
balance and creating a deafening blast.
|
|
340
363
|
|
|
341
364
|
## Output Format
|
|
342
365
|
|
|
@@ -356,7 +379,10 @@ prompts:
|
|
|
356
379
|
conversation:
|
|
357
380
|
%{groups}
|
|
358
381
|
|
|
359
|
-
|
|
382
|
+
The previous summary (if present) is a draft. Rewrite it
|
|
383
|
+
to incorporate the new conversation above. The result must
|
|
384
|
+
read as one continuous account of the entire session, not a
|
|
385
|
+
concatenation of two separate summaries. Your narrative MUST:
|
|
360
386
|
- Name every file created or significantly modified (with
|
|
361
387
|
path), and the key methods or classes they define.
|
|
362
388
|
- State the architectural or design decisions made (e.g.
|
|
@@ -366,8 +392,8 @@ prompts:
|
|
|
366
392
|
- Do NOT merely report test counts or coverage percentages;
|
|
367
393
|
those are outcomes, not accomplishments.
|
|
368
394
|
- Be factual; do not invent details not present in the groups
|
|
369
|
-
above.
|
|
370
|
-
assemble:
|
|
395
|
+
or previous summary above. Be concise; skip filler and repetition.
|
|
396
|
+
assemble: |
|
|
371
397
|
%{narrative}
|
|
372
398
|
|
|
373
399
|
tool_calls:
|
|
@@ -333,7 +333,7 @@ module OllamaChat::PromptManagement
|
|
|
333
333
|
# Let the user write a suggestion instruction on the fly
|
|
334
334
|
instruction = edit_text('').full? or return
|
|
335
335
|
else
|
|
336
|
-
# Let the user pick a prompt template (e.g.,
|
|
336
|
+
# Let the user pick a prompt template (e.g., suggest.coding, suggest.roleplaying)
|
|
337
337
|
instruction = choose_prompt(
|
|
338
338
|
prompt: 'Which suggestion strategy shall we employ? %s',
|
|
339
339
|
context: 'suggest'
|
|
@@ -163,10 +163,11 @@ module OllamaChat::RAGHandling
|
|
|
163
163
|
collections = all_collections.select(:name, :description, :enabled)
|
|
164
164
|
use_pager do |output|
|
|
165
165
|
collections.each { |c|
|
|
166
|
-
|
|
166
|
+
enabled = c.enabled ? '✅' : '⛔'
|
|
167
|
+
collection_name = current_collection == c.name ? bold { c.name } : c.name
|
|
167
168
|
collection_description = c.description
|
|
168
|
-
|
|
169
|
-
output.puts '%s
|
|
169
|
+
|
|
170
|
+
output.puts '%s %s: %s' % [ enabled, collection_name, collection_description ]
|
|
170
171
|
}
|
|
171
172
|
end
|
|
172
173
|
end
|
|
@@ -348,11 +348,10 @@ module OllamaChat::SessionManagement
|
|
|
348
348
|
|
|
349
349
|
# Generates a summary of the current session's conversation.
|
|
350
350
|
#
|
|
351
|
-
# @param pretty [Boolean] whether to format the summary in markdown (default: false)
|
|
352
351
|
# @param sentence [Boolean] whether to summarize each message in one sentence (default: false)
|
|
353
352
|
# @param block [Proc] a block to handle each summary fragment
|
|
354
353
|
# @return [String, nil] the session summary or nil if empty
|
|
355
|
-
def summarize_session(
|
|
354
|
+
def summarize_session(sentence: false, &block)
|
|
356
355
|
unit = sentence ? 'sentence' : 'paragraph'
|
|
357
356
|
contents = []
|
|
358
357
|
messages_to_summarize = messages.each_message
|
|
@@ -362,9 +361,8 @@ module OllamaChat::SessionManagement
|
|
|
362
361
|
message: infobar_message,
|
|
363
362
|
)
|
|
364
363
|
messages_to_summarize.each do |message|
|
|
365
|
-
message_content
|
|
366
|
-
|
|
367
|
-
unless message_content || message_thinking
|
|
364
|
+
message_content = message.content.full?
|
|
365
|
+
unless message_content
|
|
368
366
|
-infobar
|
|
369
367
|
next
|
|
370
368
|
end
|
|
@@ -373,25 +371,113 @@ module OllamaChat::SessionManagement
|
|
|
373
371
|
context = contents * "\n\n"
|
|
374
372
|
summary = generate(
|
|
375
373
|
prompt: prompt(:session_summarize).to_s % {
|
|
376
|
-
sender_name:, unit:, message_content:,
|
|
374
|
+
sender_name:, unit:, message_content:, context:
|
|
377
375
|
}
|
|
378
376
|
)
|
|
379
|
-
content =
|
|
380
|
-
'**%s**: %s' % [ sender_name_output, summary ]
|
|
381
|
-
else
|
|
382
|
-
'%s: %s' % [ sender_name_output, summary ]
|
|
383
|
-
end
|
|
377
|
+
content = '**%s**: %s' % [ sender_name_output, summary ]
|
|
384
378
|
block&.(content)
|
|
385
379
|
contents << content
|
|
386
380
|
+infobar
|
|
387
381
|
end
|
|
388
382
|
contents.empty? and return
|
|
389
383
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
384
|
+
contents.unshift(%{# Summary of session "#{session.name}"})
|
|
385
|
+
contents * "\n\n"
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Summarizes the conversation and displays or saves the result.
|
|
389
|
+
#
|
|
390
|
+
# @param sentence [Boolean] summarize each message in one sentence (default: false)
|
|
391
|
+
# @param save [Boolean] save to file instead of pager (default: false)
|
|
392
|
+
def summarize_conversation(sentence: false, save: false)
|
|
393
|
+
if save
|
|
394
|
+
filename = ask_for_filename?(action: 'for summarization') or return
|
|
395
|
+
should_overwrite?(filename) or return
|
|
396
|
+
summary = summarize_session(sentence:) do |content|
|
|
397
|
+
infobar.puts kramdown_ansi_parse(content)
|
|
398
|
+
end
|
|
399
|
+
if summary.full?
|
|
400
|
+
filename.write(summary)
|
|
401
|
+
STDOUT.puts "File successfully written."
|
|
402
|
+
else
|
|
403
|
+
STDERR.puts "Nothing to summarize!"
|
|
404
|
+
end
|
|
405
|
+
else
|
|
406
|
+
summary = summarize_session(sentence:) do |content|
|
|
407
|
+
infobar.puts kramdown_ansi_parse(content) << ?\n
|
|
408
|
+
end
|
|
409
|
+
if summary.full?
|
|
410
|
+
use_pager do |output|
|
|
411
|
+
output.puts kramdown_ansi_parse(summary)
|
|
412
|
+
end
|
|
413
|
+
else
|
|
414
|
+
STDERR.puts "Nothing to summarize!"
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Generates a report document for the current session's conversation.
|
|
420
|
+
#
|
|
421
|
+
# Uses a user-selected prompt template from the 'session' context
|
|
422
|
+
# (e.g. coding_brief, roleplay_story) to produce a single cohesive
|
|
423
|
+
# document describing the session. Uses `compacted_messages` (summary +
|
|
424
|
+
# recent tail) rather than `each_message` so the interpolated content
|
|
425
|
+
# stays bounded even after multiple compaction rounds.
|
|
426
|
+
#
|
|
427
|
+
# @param name [String, nil] specific template name (skips chooser)
|
|
428
|
+
# @param block [Proc] a block to handle the report content
|
|
429
|
+
# @return [String, nil] the report or nil if empty
|
|
430
|
+
def report_session(name: nil, &block)
|
|
431
|
+
content = messages.compacted_messages.inject('') do |c, message|
|
|
432
|
+
message.content.present? or next c
|
|
433
|
+
sender = sender_name_displayed(message)
|
|
434
|
+
c << "%s: %s\n\n" % [ sender, message.content ]
|
|
435
|
+
end
|
|
436
|
+
content.empty? and return
|
|
437
|
+
|
|
438
|
+
template = if name
|
|
439
|
+
prompt(name, context: 'session')
|
|
440
|
+
else
|
|
441
|
+
choose_prompt(
|
|
442
|
+
context: 'session',
|
|
443
|
+
prompt: 'Which report template? %s'
|
|
444
|
+
)
|
|
445
|
+
end
|
|
446
|
+
template or return
|
|
447
|
+
|
|
448
|
+
result = generate(prompt: template.to_s % { content: })
|
|
449
|
+
block&.(result)
|
|
450
|
+
result
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Generates a report and displays or saves the result.
|
|
454
|
+
#
|
|
455
|
+
# @param name [String, nil] specific report template name (skips chooser)
|
|
456
|
+
# @param save [Boolean] save to file instead of pager (default: false)
|
|
457
|
+
def report_conversation(name: nil, save: false)
|
|
458
|
+
if save
|
|
459
|
+
filename = ask_for_filename?(action: 'for report') or return
|
|
460
|
+
should_overwrite?(filename) or return
|
|
461
|
+
result = report_session(name:) do |content|
|
|
462
|
+
infobar.puts kramdown_ansi_parse(content)
|
|
463
|
+
end
|
|
464
|
+
if result.full?
|
|
465
|
+
filename.write(result)
|
|
466
|
+
STDOUT.puts "File successfully written."
|
|
467
|
+
else
|
|
468
|
+
STDERR.puts "Nothing to report!"
|
|
469
|
+
end
|
|
393
470
|
else
|
|
394
|
-
|
|
471
|
+
result = report_session(name:) do |content|
|
|
472
|
+
infobar.puts kramdown_ansi_parse(content) << ?\n
|
|
473
|
+
end
|
|
474
|
+
if result.full?
|
|
475
|
+
use_pager do |output|
|
|
476
|
+
output.puts kramdown_ansi_parse(result)
|
|
477
|
+
end
|
|
478
|
+
else
|
|
479
|
+
STDERR.puts "Nothing to report!"
|
|
480
|
+
end
|
|
395
481
|
end
|
|
396
482
|
end
|
|
397
483
|
|
|
@@ -575,6 +661,21 @@ module OllamaChat::SessionManagement
|
|
|
575
661
|
end
|
|
576
662
|
end
|
|
577
663
|
|
|
664
|
+
# Repairs `group_uuid` assignments in the current message list.
|
|
665
|
+
#
|
|
666
|
+
# Walks the message array backwards looking for user messages (that are not
|
|
667
|
+
# tool calls) lacking a `group_uuid`. For each such anchor it:
|
|
668
|
+
#
|
|
669
|
+
# 1. Swaps a preceding `runtime_information` tool message so the user
|
|
670
|
+
# message comes first (structural invariant).
|
|
671
|
+
# 2. Propagates the anchor's UUID forward to all subsequent messages until
|
|
672
|
+
# the next user message or the end of the list.
|
|
673
|
+
#
|
|
674
|
+
# After the backward pass, any remaining orphaned messages (e.g. system
|
|
675
|
+
# messages not part of a user-led exchange) are assigned fresh UUIDs via
|
|
676
|
+
# `initialize_group_uuid`.
|
|
677
|
+
#
|
|
678
|
+
# Persists the repaired message list via `store_messages_in_session`.
|
|
578
679
|
def repair_group_uuids
|
|
579
680
|
msgs = messages.messages # Direct reference to the internal array
|
|
580
681
|
msgs.empty? and return
|
|
@@ -103,17 +103,20 @@ module OllamaChat::ToolCalling
|
|
|
103
103
|
# This method outputs to standard output the alphabetically sorted list of
|
|
104
104
|
# tool names that are currently enabled in the chat session.
|
|
105
105
|
def list_tools
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
106
|
+
use_pager do |output|
|
|
107
|
+
output.puts "Registered tools:"
|
|
108
|
+
configured_tools.each do |tool|
|
|
109
|
+
enabled = tool_enabled?(tool) ? '✅' : '⛔'
|
|
110
|
+
require_confirmation = tool_function(tool).require_confirmation? ? '❔' : '⭕'
|
|
111
|
+
|
|
112
|
+
output.printf(
|
|
113
|
+
"%s %s %s\n",
|
|
114
|
+
enabled, require_confirmation, (enabled ? bold { tool } : tool)
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
output.puts ?┉ * Tins::Terminal.columns
|
|
118
|
+
tools_support.show(output:)
|
|
114
119
|
end
|
|
115
|
-
STDOUT.puts ?┉ * Tins::Terminal.columns
|
|
116
|
-
tools_support.show
|
|
117
120
|
end
|
|
118
121
|
|
|
119
122
|
# The enable_tool method allows the user to select and enable a tool from a
|
data/lib/ollama_chat/version.rb
CHANGED
data/ollama_chat.gemspec
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: ollama_chat 0.0.
|
|
2
|
+
# stub: ollama_chat 0.0.116 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "ollama_chat".freeze
|
|
6
|
-
s.version = "0.0.
|
|
6
|
+
s.version = "0.0.116".freeze
|
|
7
7
|
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
9
9
|
s.require_paths = ["lib".freeze]
|
|
@@ -52,7 +52,7 @@ describe OllamaChat::Chat, protect_env: true do
|
|
|
52
52
|
it 'can display collection_stats' do
|
|
53
53
|
chat
|
|
54
54
|
expect(STDOUT).to receive(:puts).with(
|
|
55
|
-
/Current Collection\n Name: \e\[1mdefault\e\[0m\n Patterns: \e\[3m\e\[0m\n #Embeddings: 0\n #Tags: 0\n Tags:/
|
|
55
|
+
/Current Collection\n Name: \e\[1mdefault\e\[0m\n Status: ✅\n Patterns: \e\[3m\e\[0m\n #Embeddings: 0\n #Tags: 0\n Tags:/
|
|
56
56
|
)
|
|
57
57
|
expect(chat.collection_stats).to be_nil
|
|
58
58
|
end
|
|
@@ -557,6 +557,36 @@ describe OllamaChat::Commands, protect_env: true do
|
|
|
557
557
|
expect(chat).to receive(:confirm?).and_return false
|
|
558
558
|
expect(chat.handle_input("/conversation compact")).to eq :next
|
|
559
559
|
end
|
|
560
|
+
|
|
561
|
+
it 'returns :next when input is "/conversation summarize"' do
|
|
562
|
+
expect(chat).to receive(:summarize_conversation)
|
|
563
|
+
.with(save: false, sentence: false)
|
|
564
|
+
expect(chat.handle_input("/conversation summarize")).to eq :next
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
it 'returns :next when input is "/conversation summarize -s"' do
|
|
568
|
+
expect(chat).to receive(:summarize_conversation)
|
|
569
|
+
.with(save: false, sentence: 1)
|
|
570
|
+
expect(chat.handle_input("/conversation summarize -s")).to eq :next
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
it 'returns :next when input is "/conversation summarize -f"' do
|
|
574
|
+
expect(chat).to receive(:summarize_conversation)
|
|
575
|
+
.with(save: 1, sentence: false)
|
|
576
|
+
expect(chat.handle_input("/conversation summarize -f")).to eq :next
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
it 'returns :next when input is "/conversation report"' do
|
|
580
|
+
expect(chat).to receive(:report_conversation)
|
|
581
|
+
.with(save: false)
|
|
582
|
+
expect(chat.handle_input("/conversation report")).to eq :next
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
it 'returns :next when input is "/conversation report -f"' do
|
|
586
|
+
expect(chat).to receive(:report_conversation).
|
|
587
|
+
with(save: 1)
|
|
588
|
+
expect(chat.handle_input("/conversation report -f")).to eq :next
|
|
589
|
+
end
|
|
560
590
|
end
|
|
561
591
|
|
|
562
592
|
describe 'tools' do
|
|
@@ -226,9 +226,10 @@ describe OllamaChat::Compaction do
|
|
|
226
226
|
)
|
|
227
227
|
# last line is the directive's final sentence
|
|
228
228
|
expect(captured)
|
|
229
|
-
.to match(/details\s+not\s+present\s+in\s+the\s+groups\s+above./m)
|
|
229
|
+
.to match(/details\s+not\s+present\s+in\s+the\s+groups\s+or\s+previous\s+summary\s+above./m)
|
|
230
230
|
# directive comes after content
|
|
231
|
-
expect(captured.
|
|
231
|
+
expect(captured).to match(/is\s+a\s+draft/)
|
|
232
|
+
expect(captured.index(/is\s+a\s+draft/))
|
|
232
233
|
.to be > captured.index('[user]')
|
|
233
234
|
end
|
|
234
235
|
|
|
@@ -253,7 +254,7 @@ describe OllamaChat::Compaction do
|
|
|
253
254
|
expect(content).to include 'tool_calls:'
|
|
254
255
|
expect(content).to include '"uuid":"aaaa"'
|
|
255
256
|
expect(content).to include '"uuid":"bbbb"'
|
|
256
|
-
expect(content).to end_with
|
|
257
|
+
expect(content).to end_with "with the group UUID in parentheses.\n"
|
|
257
258
|
expect(tools).to eq entries
|
|
258
259
|
end
|
|
259
260
|
|
|
@@ -261,7 +262,7 @@ describe OllamaChat::Compaction do
|
|
|
261
262
|
content, tools = chat.assemble_summary('Just narrative.', [])
|
|
262
263
|
expect(content).to start_with 'Just narrative.'
|
|
263
264
|
expect(content).to include 'tool_calls:'
|
|
264
|
-
expect(content).to end_with
|
|
265
|
+
expect(content).to end_with "with the group UUID in parentheses.\n"
|
|
265
266
|
expect(tools).to eq []
|
|
266
267
|
end
|
|
267
268
|
|
|
@@ -8,7 +8,7 @@ describe OllamaChat::Conversation do
|
|
|
8
8
|
describe '#save_conversation' do
|
|
9
9
|
it 'saves a new conversation (clean: false) and confirms' do
|
|
10
10
|
expect(chat.messages).to receive(:save_conversation)
|
|
11
|
-
.with('./new_chat.jsonl', messages: chat.messages.messages)
|
|
11
|
+
.with(Pathname.new('./new_chat.jsonl'), messages: chat.messages.messages)
|
|
12
12
|
.and_return(true)
|
|
13
13
|
expect(STDOUT).to receive(:puts)
|
|
14
14
|
.with('Saved conversation to "./new_chat.jsonl".')
|
|
@@ -17,7 +17,7 @@ describe OllamaChat::Conversation do
|
|
|
17
17
|
|
|
18
18
|
it 'saves a new conversation (clean: true) using cleaned messages' do
|
|
19
19
|
expect(chat.messages).to receive(:save_conversation)
|
|
20
|
-
.with('./new_chat.jsonl', messages: chat.messages.clean_messages)
|
|
20
|
+
.with(Pathname.new('./new_chat.jsonl'), messages: chat.messages.clean_messages)
|
|
21
21
|
.and_return(true)
|
|
22
22
|
expect(STDOUT).to receive(:puts)
|
|
23
23
|
.with('Saved conversation to "./new_chat.jsonl".')
|
|
@@ -56,7 +56,7 @@ describe OllamaChat::Conversation do
|
|
|
56
56
|
.and_return(false)
|
|
57
57
|
expect(chat.messages).not_to receive(:save_conversation)
|
|
58
58
|
expect(STDOUT).not_to receive(:puts)
|
|
59
|
-
expect(STDERR).
|
|
59
|
+
expect(STDERR).to receive(:puts).with('File not written!')
|
|
60
60
|
chat.save_conversation(tmpfile, clean: false)
|
|
61
61
|
ensure
|
|
62
62
|
FileUtils.rm_f(tmpfile)
|
|
@@ -316,6 +316,92 @@ describe OllamaChat::SessionManagement do
|
|
|
316
316
|
end
|
|
317
317
|
end
|
|
318
318
|
|
|
319
|
+
describe '#summarize_conversation' do
|
|
320
|
+
it 'displays summary in pager when not saving' do
|
|
321
|
+
chat.messages << OllamaChat::Message.new(role: 'user', content: 'hello')
|
|
322
|
+
expect(chat).to receive(:summarize_session).and_return('# Summary content')
|
|
323
|
+
expect(chat).to receive(:use_pager)
|
|
324
|
+
chat.summarize_conversation
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
it 'reports nothing when no messages' do
|
|
328
|
+
expect(chat).to receive(:summarize_session).and_return(nil)
|
|
329
|
+
expect(STDERR).to receive(:puts).with('Nothing to summarize!')
|
|
330
|
+
chat.summarize_conversation
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'saves summary to file when save: true' do
|
|
334
|
+
chat.messages << OllamaChat::Message.new(role: 'user', content: 'hello')
|
|
335
|
+
tmpfile = Pathname.new(File.join(Dir.tmpdir, "summ_#{$$}_test.md"))
|
|
336
|
+
expect(chat).to receive(:ask_for_filename?).and_return(tmpfile)
|
|
337
|
+
expect(chat).to receive(:should_overwrite?).and_return(true)
|
|
338
|
+
expect(chat).to receive(:summarize_session).and_return('# Summary content')
|
|
339
|
+
expect(STDOUT).to receive(:puts).with('File successfully written.')
|
|
340
|
+
chat.summarize_conversation(save: true)
|
|
341
|
+
expect(tmpfile.exist?).to be_truthy
|
|
342
|
+
expect(tmpfile.read).to include('Summary content')
|
|
343
|
+
ensure
|
|
344
|
+
FileUtils.rm_f(tmpfile)
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
describe '#report_session' do
|
|
349
|
+
it 'generates a report from compacted messages' do
|
|
350
|
+
chat.messages << OllamaChat::Message.new(role: 'user', content: 'hello world')
|
|
351
|
+
expect(chat).to receive(:sender_name_displayed).and_return('User')
|
|
352
|
+
expect(chat).to receive(:choose_prompt).and_return(double(to_s: '%{content}'))
|
|
353
|
+
expect(chat).to receive(:generate).and_return('Generated report')
|
|
354
|
+
|
|
355
|
+
results = []
|
|
356
|
+
result = chat.report_session { |c| results << c }
|
|
357
|
+
expect(result).to eq('Generated report')
|
|
358
|
+
expect(results).to eq(['Generated report'])
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
it 'returns nil when no messages have content' do
|
|
362
|
+
expect(chat.messages).to receive(:compacted_messages).and_return([])
|
|
363
|
+
expect(chat.report_session).to be_nil
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
it 'uses named prompt when name is provided' do
|
|
367
|
+
chat.messages << OllamaChat::Message.new(role: 'user', content: 'hello')
|
|
368
|
+
expect(chat).to receive(:sender_name_displayed).and_return('User')
|
|
369
|
+
expect(chat).to receive(:prompt).with('coding_brief', context: 'session')
|
|
370
|
+
.and_return(double(to_s: '%{content}'))
|
|
371
|
+
expect(chat).to receive(:generate).and_return('Brief')
|
|
372
|
+
expect(chat.report_session(name: 'coding_brief')).to eq('Brief')
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
describe '#report_conversation' do
|
|
377
|
+
it 'displays report in pager when not saving' do
|
|
378
|
+
chat.messages << OllamaChat::Message.new(role: 'user', content: 'hello')
|
|
379
|
+
expect(chat).to receive(:report_session).and_return('# Report content')
|
|
380
|
+
expect(chat).to receive(:use_pager)
|
|
381
|
+
chat.report_conversation
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
it 'reports nothing when no content' do
|
|
385
|
+
expect(chat).to receive(:report_session).and_return(nil)
|
|
386
|
+
expect(STDERR).to receive(:puts).with('Nothing to report!')
|
|
387
|
+
chat.report_conversation
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
it 'saves report to file when save: true' do
|
|
391
|
+
chat.messages << OllamaChat::Message.new(role: 'user', content: 'hello')
|
|
392
|
+
tmpfile = Pathname.new(File.join(Dir.tmpdir, "report_#{$$}_test.md"))
|
|
393
|
+
expect(chat).to receive(:ask_for_filename?).and_return(tmpfile)
|
|
394
|
+
expect(chat).to receive(:should_overwrite?).and_return(true)
|
|
395
|
+
expect(chat).to receive(:report_session).and_return('# Report content')
|
|
396
|
+
expect(STDOUT).to receive(:puts).with('File successfully written.')
|
|
397
|
+
chat.report_conversation(save: true)
|
|
398
|
+
expect(tmpfile.exist?).to be_truthy
|
|
399
|
+
expect(tmpfile.read).to include('Report content')
|
|
400
|
+
ensure
|
|
401
|
+
FileUtils.rm_f(tmpfile)
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
319
405
|
describe '#change_session' do
|
|
320
406
|
it 'switches to a different session and updates state' do
|
|
321
407
|
new_s = chat.new_session.tap { |s| s.name = 'change_to_me'; s.save }
|
|
@@ -97,10 +97,19 @@ describe OllamaChat::ToolCalling do
|
|
|
97
97
|
|
|
98
98
|
describe '#list_tools' do
|
|
99
99
|
it 'prints a formatted list of tools with their status' do
|
|
100
|
-
chat.session.tools_default_enabled = {
|
|
100
|
+
chat.session.tools_default_enabled = {
|
|
101
|
+
'read_file' => true, 'write_file' => false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
output_io = StringIO.new
|
|
105
|
+
expect(chat).to receive(:use_pager).and_yield(output_io)
|
|
106
|
+
expect(chat).to receive(:tools_support).
|
|
107
|
+
and_return(double(show: true))
|
|
108
|
+
|
|
109
|
+
chat.list_tools
|
|
101
110
|
|
|
102
|
-
# We expect to see
|
|
103
|
-
expect
|
|
111
|
+
# We expect to see ✅ for read_file and ⛔ for write_file
|
|
112
|
+
expect(output_io.string).to match(/✅ .*read_file.*⛔ .*write_file/m)
|
|
104
113
|
end
|
|
105
114
|
end
|
|
106
115
|
|