pwn 0.5.757 → 0.5.759
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/Gemfile +1 -1
- data/lib/pwn/ai/agent/loop.rb +29 -14
- data/lib/pwn/ai/ollama.rb +80 -5
- data/lib/pwn/ai/open_web_ui.rb +80 -5
- data/lib/pwn/plugins/repl/ai_console.rb +6 -2
- data/lib/pwn/plugins/repl/ai_swarm.rb +4 -0
- data/lib/pwn/plugins/repl.rb +19 -3
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/loop_spec.rb +13 -16
- data/spec/lib/pwn/ai/ollama_spec.rb +2 -1
- data/spec/lib/pwn/ai/ollama_system_message_spec.rb +52 -0
- data/spec/lib/pwn/ai/open_web_ui_spec.rb +29 -1
- data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +13 -0
- data/third_party/pwn_rdoc.jsonl +7 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ef7228011e10bd0573cab148f192e8ef963608a002d4e3d833fcd2e380fd973
|
|
4
|
+
data.tar.gz: 8f238e16e7d0d521deb4b4abdad45fed2d15fb82d92bcc8448b0877bc23b8783
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d6eb621a41250c5332e00f941c8b6b7444f796fcb30c009d774d7a0814f3c268809152275f2c63f7f7076450c75bbe662c0248ec6b21abb9730b44b097fdb139
|
|
7
|
+
data.tar.gz: 8ea184fdfa2018bc59f00aef8585d190907236a3d71df302c21db3eb025813111cd5a7bb289513c5a7f51a816f1c7e094f5b6c0bf420ff50d9a7a27a11ffbc65
|
data/Gemfile
CHANGED
data/lib/pwn/ai/agent/loop.rb
CHANGED
|
@@ -1923,12 +1923,16 @@ module PWN
|
|
|
1923
1923
|
|
|
1924
1924
|
content = msg[:content]
|
|
1925
1925
|
tool_calls = Array(msg[:tool_calls])
|
|
1926
|
+
promoted = false
|
|
1926
1927
|
# Local/thinking models (Ollama Qwen3, DeepSeek-R1, etc.) sometimes
|
|
1927
1928
|
# return only :thinking with empty :content and no tool_calls. Promote
|
|
1928
1929
|
# thinking so the agent does not print a blank final answer.
|
|
1929
1930
|
if content.to_s.strip.empty? && tool_calls.empty?
|
|
1930
1931
|
thinking = msg[:thinking].to_s
|
|
1931
|
-
|
|
1932
|
+
unless thinking.strip.empty?
|
|
1933
|
+
content = thinking
|
|
1934
|
+
promoted = true
|
|
1935
|
+
end
|
|
1932
1936
|
end
|
|
1933
1937
|
|
|
1934
1938
|
out = {
|
|
@@ -1953,6 +1957,7 @@ module PWN
|
|
|
1953
1957
|
out[:_native_content] = msg[:_native_content] if msg[:_native_content]
|
|
1954
1958
|
thinking = msg[:thinking] || msg[:reasoning_content]
|
|
1955
1959
|
out[:thinking] = thinking if thinking.to_s.strip != ''
|
|
1960
|
+
out[:_thinking_promoted] = true if promoted
|
|
1956
1961
|
# Local/abliterated models sometimes emit shell(...) as plain content
|
|
1957
1962
|
# with empty tool_calls. Coerce registered call-shaped text into
|
|
1958
1963
|
# structured tool_calls so Loop dispatches instead of FINAL-answering.
|
|
@@ -2297,6 +2302,17 @@ module PWN
|
|
|
2297
2302
|
nil
|
|
2298
2303
|
end
|
|
2299
2304
|
|
|
2305
|
+
private_class_method def self.emit_thinking!(opts = {})
|
|
2306
|
+
text = opts[:thinking].to_s.strip
|
|
2307
|
+
Thread.current[:pwn_last_thinking] = text
|
|
2308
|
+
Thread.current[:pwn_thinking_promoted] = opts[:promoted] ? true : false
|
|
2309
|
+
return if text.empty?
|
|
2310
|
+
|
|
2311
|
+
opts[:on_tool]&.call('thinking', text, '')
|
|
2312
|
+
rescue StandardError
|
|
2313
|
+
nil
|
|
2314
|
+
end
|
|
2315
|
+
|
|
2300
2316
|
# Pre-dispatch: one high-level brief for the whole upcoming tool
|
|
2301
2317
|
# collection (pwn-ai → task is one-to-many with pwn-ai → <tool>).
|
|
2302
2318
|
# Prefer opts[:tools] = [{name:, args:}, ...]; falls back to single name.
|
|
@@ -2304,18 +2320,13 @@ module PWN
|
|
|
2304
2320
|
state = opts[:state]
|
|
2305
2321
|
return nil unless state && defined?(TaskSummarizer) && TaskSummarizer.enabled?
|
|
2306
2322
|
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
tools: opts[:tools]
|
|
2315
|
-
)
|
|
2316
|
-
else
|
|
2317
|
-
thinking
|
|
2318
|
-
end
|
|
2323
|
+
line = TaskSummarizer.about_to(
|
|
2324
|
+
name: opts[:name],
|
|
2325
|
+
args: opts[:args],
|
|
2326
|
+
state: state,
|
|
2327
|
+
request: opts[:request],
|
|
2328
|
+
tools: opts[:tools]
|
|
2329
|
+
)
|
|
2319
2330
|
# about_to returns nil when the brief is a duplicate of the last one
|
|
2320
2331
|
emit_task_summary(line: line, on_tool: opts[:on_tool]) if line
|
|
2321
2332
|
line
|
|
@@ -3409,6 +3420,11 @@ module PWN
|
|
|
3409
3420
|
|
|
3410
3421
|
calls = Array(msg[:tool_calls])
|
|
3411
3422
|
text = msg[:content].to_s
|
|
3423
|
+
emit_thinking!(
|
|
3424
|
+
thinking: msg[:thinking] || msg[:reasoning_content],
|
|
3425
|
+
promoted: msg[:_thinking_promoted],
|
|
3426
|
+
on_tool: on_tool
|
|
3427
|
+
)
|
|
3412
3428
|
|
|
3413
3429
|
# Belt-and-suspenders: plain-text shell(...) / tool forms from local
|
|
3414
3430
|
# models under weak TEMPLATE {{ .Prompt }} become real tool_calls.
|
|
@@ -3525,7 +3541,6 @@ module PWN
|
|
|
3525
3541
|
}
|
|
3526
3542
|
end,
|
|
3527
3543
|
request: request,
|
|
3528
|
-
thinking: msg[:thinking] || msg[:reasoning_content],
|
|
3529
3544
|
on_tool: on_tool
|
|
3530
3545
|
)
|
|
3531
3546
|
|
data/lib/pwn/ai/ollama.rb
CHANGED
|
@@ -436,6 +436,9 @@ module PWN
|
|
|
436
436
|
# - function.arguments must be a Hash/Array object, not a JSON string
|
|
437
437
|
# (string args → HTTP 400 "can't find closing '}' symbol")
|
|
438
438
|
# - assistant content nil + tool_calls → "" (Open WebUI form validation)
|
|
439
|
+
# - system/developer rows after the first message are merged into one
|
|
440
|
+
# leading system message. Qwen-style templates raise HTTP 500
|
|
441
|
+
# "System message must be at the beginning" otherwise.
|
|
439
442
|
private_class_method def self.parse_tool_arguments_object(opts = {})
|
|
440
443
|
raw = opts[:arguments]
|
|
441
444
|
case raw
|
|
@@ -458,7 +461,7 @@ module PWN
|
|
|
458
461
|
end
|
|
459
462
|
|
|
460
463
|
private_class_method def self.normalize_messages_for_ollama(opts = {})
|
|
461
|
-
Array(opts[:messages]).filter_map do |m|
|
|
464
|
+
rows = Array(opts[:messages]).filter_map do |m|
|
|
462
465
|
next unless m.is_a?(Hash)
|
|
463
466
|
|
|
464
467
|
role = (m[:role] || m['role']).to_s
|
|
@@ -508,6 +511,71 @@ module PWN
|
|
|
508
511
|
|
|
509
512
|
out
|
|
510
513
|
end
|
|
514
|
+
lead_system(messages: rows, fold_into_user: opts[:fold_into_user])
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# One system role is only safe when the server does not already prepend one.
|
|
518
|
+
# Models whose template raises "System message must be at the beginning"
|
|
519
|
+
# already carry a Modelfile SYSTEM prompt. Fold client system text into
|
|
520
|
+
# the first user message instead of sending a second system role.
|
|
521
|
+
private_class_method def self.lead_system(opts = {})
|
|
522
|
+
parts = []
|
|
523
|
+
rest = []
|
|
524
|
+
Array(opts[:messages]).each do |row|
|
|
525
|
+
next unless row.is_a?(Hash)
|
|
526
|
+
|
|
527
|
+
role = (row[:role] || row['role']).to_s
|
|
528
|
+
if %w[system developer].include?(role)
|
|
529
|
+
text = (row.key?(:content) ? row[:content] : row['content']).to_s
|
|
530
|
+
parts << text unless text.strip.empty?
|
|
531
|
+
else
|
|
532
|
+
rest << row
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
return rest if parts.empty?
|
|
536
|
+
|
|
537
|
+
text = parts.join("\n\n")
|
|
538
|
+
return [{ role: 'system', content: text }] + rest unless opts[:fold_into_user]
|
|
539
|
+
|
|
540
|
+
user = rest.find { |row| (row[:role] || row['role']).to_s == 'user' }
|
|
541
|
+
if user
|
|
542
|
+
body = (user.key?(:content) ? user[:content] : user['content']).to_s
|
|
543
|
+
user[:content] = "#{text}\n\n#{body}"
|
|
544
|
+
user.delete('content')
|
|
545
|
+
rest
|
|
546
|
+
else
|
|
547
|
+
[{ role: 'user', content: text }] + rest
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
private_class_method def self.strict_system_template?(opts = {})
|
|
552
|
+
name = opts[:model].to_s.strip
|
|
553
|
+
return false if name.empty?
|
|
554
|
+
|
|
555
|
+
@strict_system_cache ||= {}
|
|
556
|
+
return @strict_system_cache[name] if @strict_system_cache.key?(name)
|
|
557
|
+
|
|
558
|
+
@strict_system_cache[name] = template_rejects_later_system?(model: name)
|
|
559
|
+
rescue StandardError
|
|
560
|
+
false
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
private_class_method def self.template_rejects_later_system?(opts = {})
|
|
564
|
+
name = opts[:model].to_s
|
|
565
|
+
names = [name]
|
|
566
|
+
names << "#{name}:latest" unless name.include?(':')
|
|
567
|
+
names.each do |candidate|
|
|
568
|
+
raw = ollama_rest_call(
|
|
569
|
+
http_method: :post, rest_call: 'api/show',
|
|
570
|
+
http_body: { model: candidate }, timeout: 8, quiet: true, non_interactive: true
|
|
571
|
+
)
|
|
572
|
+
template = PWN::AI::ModelCatalog.parse_row(raw: raw)
|
|
573
|
+
text = template.is_a?(Hash) ? (template[:template] || template['template']).to_s : ''
|
|
574
|
+
return true if text.include?('System message must be at the beginning')
|
|
575
|
+
end
|
|
576
|
+
false
|
|
577
|
+
rescue StandardError
|
|
578
|
+
false
|
|
511
579
|
end
|
|
512
580
|
|
|
513
581
|
# Supported Method Parameters::
|
|
@@ -526,13 +594,16 @@ module PWN
|
|
|
526
594
|
# NDJSON chunks back into a single response.
|
|
527
595
|
|
|
528
596
|
public_class_method def self.chat_with_tools(opts = {})
|
|
529
|
-
engine
|
|
530
|
-
messages = normalize_messages_for_ollama(messages: opts[:messages])
|
|
531
|
-
raise 'ERROR: messages array is required' if messages.nil? || messages.empty?
|
|
532
|
-
|
|
597
|
+
engine = PWN::Env[:ai][:ollama]
|
|
533
598
|
model = opts[:model] ||= engine[:model]
|
|
534
599
|
raise 'ERROR: Model is required. Call #get_models method for details' if model.nil?
|
|
535
600
|
|
|
601
|
+
messages = normalize_messages_for_ollama(
|
|
602
|
+
messages: opts[:messages],
|
|
603
|
+
fold_into_user: strict_system_template?(model: model)
|
|
604
|
+
)
|
|
605
|
+
raise 'ERROR: messages array is required' if messages.nil? || messages.empty?
|
|
606
|
+
|
|
536
607
|
temp = opts[:temp].to_f
|
|
537
608
|
temp = engine[:temp].to_f.nonzero? || 1 if temp.zero?
|
|
538
609
|
|
|
@@ -656,6 +727,10 @@ module PWN
|
|
|
656
727
|
end
|
|
657
728
|
|
|
658
729
|
http_body[:messages].push(user_role)
|
|
730
|
+
http_body[:messages] = lead_system(
|
|
731
|
+
messages: http_body[:messages],
|
|
732
|
+
fold_into_user: strict_system_template?(model: model)
|
|
733
|
+
)
|
|
659
734
|
|
|
660
735
|
timeout = opts[:timeout]
|
|
661
736
|
spinner = opts[:spinner]
|
data/lib/pwn/ai/open_web_ui.rb
CHANGED
|
@@ -468,6 +468,9 @@ module PWN
|
|
|
468
468
|
# - function.arguments must be a Hash/Array object, not a JSON string
|
|
469
469
|
# (string args → HTTP 400 "can't find closing '}' symbol")
|
|
470
470
|
# - assistant content nil + tool_calls → "" (Open WebUI form validation)
|
|
471
|
+
# - system/developer rows after the first message are merged into one
|
|
472
|
+
# leading system message. Qwen-style templates raise HTTP 500
|
|
473
|
+
# "System message must be at the beginning" otherwise.
|
|
471
474
|
private_class_method def self.parse_tool_arguments_object(opts = {})
|
|
472
475
|
raw = opts[:arguments]
|
|
473
476
|
case raw
|
|
@@ -490,7 +493,7 @@ module PWN
|
|
|
490
493
|
end
|
|
491
494
|
|
|
492
495
|
private_class_method def self.normalize_messages_for_ollama(opts = {})
|
|
493
|
-
Array(opts[:messages]).filter_map do |m|
|
|
496
|
+
rows = Array(opts[:messages]).filter_map do |m|
|
|
494
497
|
next unless m.is_a?(Hash)
|
|
495
498
|
|
|
496
499
|
role = (m[:role] || m['role']).to_s
|
|
@@ -540,6 +543,71 @@ module PWN
|
|
|
540
543
|
|
|
541
544
|
out
|
|
542
545
|
end
|
|
546
|
+
lead_system(messages: rows, fold_into_user: opts[:fold_into_user])
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# One system role is only safe when the server does not already prepend one.
|
|
550
|
+
# mini-mythos-mission-ready has a Modelfile SYSTEM prompt and a template that
|
|
551
|
+
# raises if any later message has role system. Fold those rows into the
|
|
552
|
+
# first user message instead of sending a second system role.
|
|
553
|
+
private_class_method def self.lead_system(opts = {})
|
|
554
|
+
parts = []
|
|
555
|
+
rest = []
|
|
556
|
+
Array(opts[:messages]).each do |row|
|
|
557
|
+
next unless row.is_a?(Hash)
|
|
558
|
+
|
|
559
|
+
role = (row[:role] || row['role']).to_s
|
|
560
|
+
if %w[system developer].include?(role)
|
|
561
|
+
text = (row.key?(:content) ? row[:content] : row['content']).to_s
|
|
562
|
+
parts << text unless text.strip.empty?
|
|
563
|
+
else
|
|
564
|
+
rest << row
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
return rest if parts.empty?
|
|
568
|
+
|
|
569
|
+
text = parts.join("\n\n")
|
|
570
|
+
return [{ role: 'system', content: text }] + rest unless opts[:fold_into_user]
|
|
571
|
+
|
|
572
|
+
user = rest.find { |row| (row[:role] || row['role']).to_s == 'user' }
|
|
573
|
+
if user
|
|
574
|
+
body = (user.key?(:content) ? user[:content] : user['content']).to_s
|
|
575
|
+
user[:content] = "#{text}\n\n#{body}"
|
|
576
|
+
user.delete('content')
|
|
577
|
+
rest
|
|
578
|
+
else
|
|
579
|
+
[{ role: 'user', content: text }] + rest
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
private_class_method def self.strict_system_template?(opts = {})
|
|
584
|
+
name = opts[:model].to_s.strip
|
|
585
|
+
return false if name.empty?
|
|
586
|
+
|
|
587
|
+
@strict_system_cache ||= {}
|
|
588
|
+
return @strict_system_cache[name] if @strict_system_cache.key?(name)
|
|
589
|
+
|
|
590
|
+
@strict_system_cache[name] = template_rejects_later_system?(model: name)
|
|
591
|
+
rescue StandardError
|
|
592
|
+
false
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
private_class_method def self.template_rejects_later_system?(opts = {})
|
|
596
|
+
name = opts[:model].to_s
|
|
597
|
+
names = [name]
|
|
598
|
+
names << "#{name}:latest" unless name.include?(':')
|
|
599
|
+
names.each do |candidate|
|
|
600
|
+
raw = openwebui_rest_call(
|
|
601
|
+
http_method: :post, rest_call: 'ollama/api/show',
|
|
602
|
+
http_body: { model: candidate }, timeout: 8, quiet: true, non_interactive: true
|
|
603
|
+
)
|
|
604
|
+
template = PWN::AI::ModelCatalog.parse_row(raw: raw)
|
|
605
|
+
text = template.is_a?(Hash) ? (template[:template] || template['template']).to_s : ''
|
|
606
|
+
return true if text.include?('System message must be at the beginning')
|
|
607
|
+
end
|
|
608
|
+
false
|
|
609
|
+
rescue StandardError
|
|
610
|
+
false
|
|
543
611
|
end
|
|
544
612
|
|
|
545
613
|
# Supported Method Parameters::
|
|
@@ -559,13 +627,16 @@ module PWN
|
|
|
559
627
|
# Bare POST /api/chat is not an API route on stock Open WebUI (405).
|
|
560
628
|
|
|
561
629
|
public_class_method def self.chat_with_tools(opts = {})
|
|
562
|
-
engine
|
|
563
|
-
messages = normalize_messages_for_ollama(messages: opts[:messages])
|
|
564
|
-
raise 'ERROR: messages array is required' if messages.nil? || messages.empty?
|
|
565
|
-
|
|
630
|
+
engine = PWN::Env[:ai][:openwebui]
|
|
566
631
|
model = opts[:model] ||= engine[:model]
|
|
567
632
|
raise 'ERROR: Model is required. Call #get_models method for details' unless real_config_value?(value: model)
|
|
568
633
|
|
|
634
|
+
messages = normalize_messages_for_ollama(
|
|
635
|
+
messages: opts[:messages],
|
|
636
|
+
fold_into_user: strict_system_template?(model: model)
|
|
637
|
+
)
|
|
638
|
+
raise 'ERROR: messages array is required' if messages.nil? || messages.empty?
|
|
639
|
+
|
|
569
640
|
temp = opts[:temp].to_f
|
|
570
641
|
temp = engine[:temp].to_f.nonzero? || 1 if temp.zero?
|
|
571
642
|
|
|
@@ -682,6 +753,10 @@ module PWN
|
|
|
682
753
|
end
|
|
683
754
|
|
|
684
755
|
http_body[:messages].push(user_role)
|
|
756
|
+
http_body[:messages] = lead_system(
|
|
757
|
+
messages: http_body[:messages],
|
|
758
|
+
fold_into_user: strict_system_template?(model: model)
|
|
759
|
+
)
|
|
685
760
|
|
|
686
761
|
timeout = opts[:timeout]
|
|
687
762
|
spinner = opts[:spinner]
|
|
@@ -266,7 +266,7 @@ module PWN
|
|
|
266
266
|
|
|
267
267
|
# Own the terminal, event pump and the lifetime of exactly one request.
|
|
268
268
|
class Console # rubocop:disable Metrics/ClassLength -- cohesive single-owner terminal state machine
|
|
269
|
-
COLORS = { operator: :operator, task: :task, tool: :tool, result: :result, assistant: :assistant, notice: :notice, warning: :warning }.freeze
|
|
269
|
+
COLORS = { operator: :operator, task: :task, thinking: :notice, tool: :tool, result: :result, assistant: :assistant, notice: :notice, warning: :warning }.freeze
|
|
270
270
|
PALETTE = %w[cyan green yellow red white blue magenta black].freeze
|
|
271
271
|
|
|
272
272
|
def initialize(pry:, input:, curses:, getch:)
|
|
@@ -480,6 +480,8 @@ module PWN
|
|
|
480
480
|
on_tool = lambda do |name, args, result|
|
|
481
481
|
if name.to_s == 'task'
|
|
482
482
|
@events << [:task, args.to_s]
|
|
483
|
+
elsif name.to_s == 'thinking'
|
|
484
|
+
@events << [:thinking, args.to_s]
|
|
483
485
|
else
|
|
484
486
|
@events << [:tool, "#{name}\n#{args.is_a?(String) ? args : args.inspect}"]
|
|
485
487
|
@events << [:result, result.to_s]
|
|
@@ -503,7 +505,8 @@ module PWN
|
|
|
503
505
|
enabled_toolsets: PWN::Env.dig(:ai, :agent, :toolsets),
|
|
504
506
|
on_tool: on_tool, steering: control, debug: @pry.config.pwn_ai_debug, debug_tee: @output
|
|
505
507
|
)
|
|
506
|
-
|
|
508
|
+
promoted = Thread.current[:pwn_thinking_promoted] && final.to_s.strip == Thread.current[:pwn_last_thinking].to_s.strip
|
|
509
|
+
@events << [:assistant, final.to_s] unless promoted
|
|
507
510
|
end
|
|
508
511
|
rescue Control::Stopped => e
|
|
509
512
|
@events << [:warning, e.message]
|
|
@@ -1652,6 +1655,7 @@ module PWN
|
|
|
1652
1655
|
puts "USAGE:
|
|
1653
1656
|
# Ctrl+L clears only the session pane, including while busy.
|
|
1654
1657
|
# Ctrl+T toggles the active pane between SESSION and MISSION CONTROL. Ctrl+S opens swarm.
|
|
1658
|
+
# Thinking from a model that returns it is a THINKING row in the session pane, above the answer.
|
|
1655
1659
|
# Up/Down select in menus, recall ~/.pwn/pwn_history in the mission pane, and scroll the session pane when it is active.
|
|
1656
1660
|
# Search: Enter accepts without sending; Esc restores the draft.
|
|
1657
1661
|
# Launch the single-owner curses console; non-terminals return unavailable.
|
|
@@ -279,6 +279,10 @@ module PWN
|
|
|
279
279
|
|
|
280
280
|
def tool_callback(job, opts)
|
|
281
281
|
lambda do |name, args, result|
|
|
282
|
+
if name.to_s == 'thinking'
|
|
283
|
+
opts[:on_event]&.call(:thinking, args.to_s)
|
|
284
|
+
return
|
|
285
|
+
end
|
|
282
286
|
update(job, job[:state], last_tool: name)
|
|
283
287
|
opts[:on_event]&.call(:tool, "#{job[:agent]} [#{job[:id]}] #{name}\n#{args.inspect}")
|
|
284
288
|
opts[:on_event]&.call(:result, result.to_s)
|
data/lib/pwn/plugins/repl.rb
CHANGED
|
@@ -561,6 +561,19 @@ module PWN
|
|
|
561
561
|
mirror.call("#{header}\n#{body_out}")
|
|
562
562
|
next
|
|
563
563
|
end
|
|
564
|
+
if name.to_s == 'thinking'
|
|
565
|
+
body = args.is_a?(String) ? args.to_s : args.inspect
|
|
566
|
+
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S%z')
|
|
567
|
+
header = "[ #{timestamp} → pwn-ai → thinking ]"
|
|
568
|
+
puts "\e[36m#{header}\e[0m"
|
|
569
|
+
body_out = +''
|
|
570
|
+
body.to_s.each_line do |ln|
|
|
571
|
+
puts "\e[36m #{ln.rstrip}\e[0m"
|
|
572
|
+
body_out << " #{ln.rstrip}\n"
|
|
573
|
+
end
|
|
574
|
+
mirror.call("#{header}\n#{body_out}")
|
|
575
|
+
next
|
|
576
|
+
end
|
|
564
577
|
|
|
565
578
|
argv = args.is_a?(String) ? args.to_s : args.inspect
|
|
566
579
|
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S%z')
|
|
@@ -592,9 +605,12 @@ module PWN
|
|
|
592
605
|
debug_tee: $stdout
|
|
593
606
|
)
|
|
594
607
|
$stdout.flush
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
608
|
+
promoted = Thread.current[:pwn_thinking_promoted] && final.to_s.strip == Thread.current[:pwn_last_thinking].to_s.strip
|
|
609
|
+
unless promoted
|
|
610
|
+
puts "\n\e[32m#{final}\e[0m\n\n"
|
|
611
|
+
$stdout.flush
|
|
612
|
+
PWN::Plugins::Log.mirror_tui!(msg: "\n#{final}\n\n") if pi.config.pwn_ai_debug && defined?(PWN::Plugins::Log)
|
|
613
|
+
end
|
|
598
614
|
if pi.config.pwn_ai_debug && sess_id && PWN.const_defined?(:Sessions)
|
|
599
615
|
PWN::Plugins::Log.progress(
|
|
600
616
|
msg: "session=#{sess_id}",
|
data/lib/pwn/version.rb
CHANGED
|
@@ -2123,36 +2123,33 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
|
|
|
2123
2123
|
expect(src).to include('byteslice(0, 2_048)')
|
|
2124
2124
|
end
|
|
2125
2125
|
|
|
2126
|
-
describe 'thinking on the
|
|
2127
|
-
it '
|
|
2126
|
+
describe 'thinking on the session pane' do
|
|
2127
|
+
it 'keeps the task brief and emits thinking separately' do
|
|
2128
2128
|
allow(PWN::AI::Agent::TaskSummarizer).to receive(:enabled?).and_return(true)
|
|
2129
2129
|
allow(PWN::AI::Agent::TaskSummarizer).to receive(:about_to).and_return('task 1/3: scan')
|
|
2130
2130
|
seen = []
|
|
2131
2131
|
described_class.send(
|
|
2132
|
-
:
|
|
2133
|
-
state: { plan: ['scan'] },
|
|
2134
|
-
tools: [{ name: 'shell' }],
|
|
2135
|
-
request: 'scan the host',
|
|
2132
|
+
:emit_thinking!,
|
|
2136
2133
|
thinking: 'Need to fingerprint the service before scanning.',
|
|
2137
2134
|
on_tool: ->(name, args, _res) { seen << [name, args] }
|
|
2138
2135
|
)
|
|
2139
|
-
expect(seen).to eq([['task', 'Need to fingerprint the service before scanning.']])
|
|
2140
|
-
expect(PWN::AI::Agent::TaskSummarizer).not_to have_received(:about_to)
|
|
2141
|
-
end
|
|
2142
|
-
|
|
2143
|
-
it 'falls back to the TaskSummarizer brief when thinking is blank' do
|
|
2144
|
-
allow(PWN::AI::Agent::TaskSummarizer).to receive(:enabled?).and_return(true)
|
|
2145
|
-
allow(PWN::AI::Agent::TaskSummarizer).to receive(:about_to).and_return('task 1/3: scan')
|
|
2146
|
-
seen = []
|
|
2147
2136
|
described_class.send(
|
|
2148
2137
|
:task_summary_about_to!,
|
|
2149
2138
|
state: { plan: ['scan'] },
|
|
2150
2139
|
tools: [{ name: 'shell' }],
|
|
2151
2140
|
request: 'scan the host',
|
|
2152
|
-
thinking: ' ',
|
|
2153
2141
|
on_tool: ->(name, args, _res) { seen << [name, args] }
|
|
2154
2142
|
)
|
|
2155
|
-
expect(seen).to eq([
|
|
2143
|
+
expect(seen).to eq([
|
|
2144
|
+
['thinking', 'Need to fingerprint the service before scanning.'],
|
|
2145
|
+
['task', 'task 1/3: scan']
|
|
2146
|
+
])
|
|
2147
|
+
end
|
|
2148
|
+
|
|
2149
|
+
it 'does not emit a blank thinking row' do
|
|
2150
|
+
seen = []
|
|
2151
|
+
described_class.send(:emit_thinking!, thinking: ' ', on_tool: ->(name, args, _res) { seen << [name, args] })
|
|
2152
|
+
expect(seen).to eq([])
|
|
2156
2153
|
end
|
|
2157
2154
|
end
|
|
2158
2155
|
|
|
@@ -12,10 +12,11 @@ describe PWN::AI::Ollama do
|
|
|
12
12
|
allow(described_class).to receive(:sleep)
|
|
13
13
|
allow(described_class).to receive(:rand).and_return(0.5)
|
|
14
14
|
allow(PWN::AI::HttpRetry).to receive(:report_event)
|
|
15
|
+
described_class.instance_variable_set(:@strict_system_cache, nil)
|
|
15
16
|
expect do
|
|
16
17
|
described_class.chat_with_tools(messages: [{ role: 'user', content: 'hello' }], quiet: quiet)
|
|
17
18
|
end.to raise_error(RuntimeError, /Ollama.*429.*rate limit exceeded/)
|
|
18
|
-
expect(RestClient::Request).to have_received(:execute).exactly(
|
|
19
|
+
expect(RestClient::Request).to have_received(:execute).exactly(6).times
|
|
19
20
|
expect(described_class).to have_received(:sleep).with(2.5).exactly(4).times
|
|
20
21
|
end
|
|
21
22
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe 'Ollama and Open WebUI system-message placement' do
|
|
6
|
+
it 'puts a later Ollama system message first so the chat template does not 500' do
|
|
7
|
+
engine = { model: 'mini-mythos-mission-ready', num_ctx: 2048, keep_alive: '1m', temp: 0.1, tool_temp: 0.1 }
|
|
8
|
+
allow(PWN::Env).to receive(:[]).and_call_original
|
|
9
|
+
allow(PWN::Env).to receive(:[]).with(:ai).and_return({ ollama: engine, active: 'ollama' })
|
|
10
|
+
captured = nil
|
|
11
|
+
allow(PWN::AI::Ollama).to receive(:ollama_rest_call) do |**kwargs|
|
|
12
|
+
captured = kwargs[:http_body]
|
|
13
|
+
'{"message":{"role":"assistant","content":"ok"}}'
|
|
14
|
+
end
|
|
15
|
+
PWN::AI::Ollama.chat_with_tools(
|
|
16
|
+
model: 'mini-mythos-mission-ready',
|
|
17
|
+
messages: [
|
|
18
|
+
{ role: 'user', content: 'hello' },
|
|
19
|
+
{ role: 'system', content: 'must lead' },
|
|
20
|
+
{ role: 'developer', content: 'also lead' }
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
expect(captured[:messages].map { |row| row[:role] }).to eq(%w[system user])
|
|
24
|
+
expect(captured[:messages][0][:content]).to include('must lead', 'also lead')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'folds Open WebUI system text into the user message when the template rejects a second system role' do
|
|
28
|
+
engine = { model: 'mini-mythos-mission-ready:latest', num_ctx: 2048, keep_alive: '1m', temp: 0.1, tool_temp: 0.1 }
|
|
29
|
+
allow(PWN::Env).to receive(:[]).and_call_original
|
|
30
|
+
allow(PWN::Env).to receive(:[]).with(:ai).and_return({ openwebui: engine, active: 'openwebui' })
|
|
31
|
+
PWN::AI::OpenWebUI.instance_variable_set(:@strict_system_cache, nil)
|
|
32
|
+
captured = nil
|
|
33
|
+
allow(PWN::AI::OpenWebUI).to receive(:openwebui_rest_call) do |**kwargs|
|
|
34
|
+
if kwargs[:rest_call].to_s.end_with?('show')
|
|
35
|
+
{ template: "raise_exception('System message must be at the beginning.')" }.to_json
|
|
36
|
+
else
|
|
37
|
+
captured = kwargs[:http_body]
|
|
38
|
+
'{"message":{"role":"assistant","content":"ok"}}'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
PWN::AI::OpenWebUI.chat_with_tools(
|
|
42
|
+
model: 'mini-mythos-mission-ready:latest',
|
|
43
|
+
messages: [
|
|
44
|
+
{ role: 'system', content: 'operator rules' },
|
|
45
|
+
{ role: 'user', content: 'ping' },
|
|
46
|
+
{ role: 'system', content: 'ENGAGEMENT MEMORY' }
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
expect(captured[:messages].map { |row| row[:role] }).to eq(%w[user])
|
|
50
|
+
expect(captured[:messages][0][:content]).to include('operator rules', 'ENGAGEMENT MEMORY', 'ping')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -12,10 +12,11 @@ describe PWN::AI::OpenWebUI do
|
|
|
12
12
|
allow(described_class).to receive(:sleep)
|
|
13
13
|
allow(described_class).to receive(:rand).and_return(0.5)
|
|
14
14
|
allow(PWN::AI::HttpRetry).to receive(:report_event)
|
|
15
|
+
described_class.instance_variable_set(:@strict_system_cache, nil)
|
|
15
16
|
expect do
|
|
16
17
|
described_class.chat_with_tools(messages: [{ role: 'user', content: 'hello' }], quiet: quiet)
|
|
17
18
|
end.to raise_error(RuntimeError, /Open WebUI.*429.*rate limit exceeded/)
|
|
18
|
-
expect(RestClient::Request).to have_received(:execute).exactly(
|
|
19
|
+
expect(RestClient::Request).to have_received(:execute).exactly(6).times
|
|
19
20
|
expect(described_class).to have_received(:sleep).with(2.5).exactly(4).times
|
|
20
21
|
end
|
|
21
22
|
end
|
|
@@ -83,6 +84,33 @@ describe PWN::AI::OpenWebUI do
|
|
|
83
84
|
expect(captured_chat[:stream]).to be true
|
|
84
85
|
end
|
|
85
86
|
|
|
87
|
+
it 'merges later system and developer messages into one leading system message' do
|
|
88
|
+
engine = { model: 'mini-mythos-mission-ready', num_ctx: 2048, keep_alive: '1m', temp: 0.1, tool_temp: 0.1 }
|
|
89
|
+
allow(PWN::Env).to receive(:[]).and_call_original
|
|
90
|
+
allow(PWN::Env).to receive(:[]).with(:ai).and_return({ openwebui: engine, active: 'openwebui' })
|
|
91
|
+
captured = nil
|
|
92
|
+
allow(described_class).to receive(:openwebui_rest_call) do |**kwargs|
|
|
93
|
+
captured = kwargs[:http_body]
|
|
94
|
+
'{"message":{"role":"assistant","content":"ok"}}'
|
|
95
|
+
end
|
|
96
|
+
described_class.chat_with_tools(
|
|
97
|
+
messages: [
|
|
98
|
+
{ role: 'system', content: 'operator rules' },
|
|
99
|
+
{ role: 'user', content: 'hello' },
|
|
100
|
+
{ role: 'system', content: 'ENGAGEMENT MEMORY\nnotes' },
|
|
101
|
+
{ role: 'developer', content: 'mid-turn reminder' },
|
|
102
|
+
{ role: 'assistant', content: '', tool_calls: [{ id: 'c1', type: 'function', function: { name: 'shell', arguments: '{"cmd":"id"}' } }] }
|
|
103
|
+
],
|
|
104
|
+
model: 'mini-mythos-mission-ready'
|
|
105
|
+
)
|
|
106
|
+
roles = captured[:messages].map { |row| row[:role] }
|
|
107
|
+
expect(roles).to eq(%w[system user assistant])
|
|
108
|
+
expect(captured[:messages][0][:content]).to include('operator rules')
|
|
109
|
+
expect(captured[:messages][0][:content]).to include('ENGAGEMENT MEMORY')
|
|
110
|
+
expect(captured[:messages][0][:content]).to include('mid-turn reminder')
|
|
111
|
+
expect(captured[:messages][2][:tool_calls].first[:function][:arguments]).to eq({ cmd: 'id' })
|
|
112
|
+
end
|
|
113
|
+
|
|
86
114
|
it 'assembles native /api/chat NDJSON stream into a single message' do
|
|
87
115
|
body = [
|
|
88
116
|
{ model: 'm', message: { role: 'assistant', content: 'Hel' }, done: false }.to_json,
|
|
@@ -389,6 +389,19 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
389
389
|
expect(colors).to eq(operator_label: 4, operator_body: 5, assistant: 5, task: 2, tool: 1, result: 3)
|
|
390
390
|
end
|
|
391
391
|
|
|
392
|
+
it 'shows model thinking in the session pane even when compact' do
|
|
393
|
+
pry = Pry.new
|
|
394
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: pry, input: StringIO.new, curses: nil, getch: nil)
|
|
395
|
+
console.instance_variable_set(:@verbose, false)
|
|
396
|
+
console.add(:thinking, 'The lemon is yellow because of carotenoids.')
|
|
397
|
+
console.add(:assistant, 'Yellow.')
|
|
398
|
+
text = console.timeline_rows(60).map { |_color, row| row }.join("\n")
|
|
399
|
+
expect(text).to include('THINKING')
|
|
400
|
+
expect(text).to include('The lemon is yellow because of carotenoids.')
|
|
401
|
+
expect(text).to include('ASSISTANT')
|
|
402
|
+
expect(text).to include('Yellow.')
|
|
403
|
+
end
|
|
404
|
+
|
|
392
405
|
it 'uses template colors for every unconfigured role without mutating defaults or overrides' do
|
|
393
406
|
defaults = PWN::Config.env_template.dig(:ai, :tui, :theme)
|
|
394
407
|
[nil, {}, 'invalid'].each do |configured|
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -403,6 +403,7 @@
|
|
|
403
403
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.dispatch_fail_n Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.dispatch_fail_n`: "}]}
|
|
404
404
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.duration_unsatisfied? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.duration_unsatisfied?`: "}]}
|
|
405
405
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.emit_task_summary Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.emit_task_summary`: "}]}
|
|
406
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.emit_thinking! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.emit_thinking!`: "}]}
|
|
406
407
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.engine_transient? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.engine_transient?`: "}]}
|
|
407
408
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.escalate Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.escalate`: "}]}
|
|
408
409
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.evidence_blob Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.evidence_blob`: "}]}
|
|
@@ -1201,10 +1202,13 @@
|
|
|
1201
1202
|
{"messages":[{"role":"user","content":"PWN::AI::Ollama.get_model Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.get_model`: Supported Method Parameters\n\nmodel = PWN::AI::Ollama.get_model(name: ‘required - tag name’)\n\nLocal tags have size and family, not token prices. get_model reads /api/show for that tag and returns it. Missing pricing stays missing.\n"}]}
|
|
1202
1203
|
{"messages":[{"role":"user","content":"PWN::AI::Ollama.get_models Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.get_models`: Supported Method Parameters\n\nresponse = PWN::AI::Ollama.get_models\n"}]}
|
|
1203
1204
|
{"messages":[{"role":"user","content":"PWN::AI::Ollama.help Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.help`: "}]}
|
|
1205
|
+
{"messages":[{"role":"user","content":"PWN::AI::Ollama.lead_system Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.lead_system`: "}]}
|
|
1204
1206
|
{"messages":[{"role":"user","content":"PWN::AI::Ollama.normalize_messages_for_ollama Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.normalize_messages_for_ollama`: "}]}
|
|
1205
1207
|
{"messages":[{"role":"user","content":"PWN::AI::Ollama.ollama_rest_call Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.ollama_rest_call`: Supported Method Parameters\n\nollama_rest_call(\n\nhttp_method: 'optional HTTP method (defaults to GET)',\nrest_call: 'required rest call path relative to base_uri (e.g. api/chat)',\nparams: 'optional params passed in the URI or HTTP Headers',\nhttp_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST',\ntimeout: 'optional timeout in seconds (defaults to 900)',\nspinner: 'optional - display spinner (defaults to false)'\n\n)\n"}]}
|
|
1206
1208
|
{"messages":[{"role":"user","content":"PWN::AI::Ollama.parse_tool_arguments_object Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.parse_tool_arguments_object`: "}]}
|
|
1207
1209
|
{"messages":[{"role":"user","content":"PWN::AI::Ollama.real_config_value? Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.real_config_value?`: "}]}
|
|
1210
|
+
{"messages":[{"role":"user","content":"PWN::AI::Ollama.strict_system_template? Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.strict_system_template?`: "}]}
|
|
1211
|
+
{"messages":[{"role":"user","content":"PWN::AI::Ollama.template_rejects_later_system? Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.template_rejects_later_system?`: "}]}
|
|
1208
1212
|
{"messages":[{"role":"user","content":"PWN::AI::Ollama.visible_from_thinking Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.visible_from_thinking`: "}]}
|
|
1209
1213
|
{"messages":[{"role":"user","content":"PWN::AI::OpenAI.api_endpoint Usage"},{"role":"assistant","content":"`PWN::AI::OpenAI.api_endpoint`: "}]}
|
|
1210
1214
|
{"messages":[{"role":"user","content":"PWN::AI::OpenAI.authors Usage"},{"role":"assistant","content":"`PWN::AI::OpenAI.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
@@ -1252,10 +1256,13 @@
|
|
|
1252
1256
|
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.get_model Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.get_model`: Supported Method Parameters\n\nmodel = PWN::AI::OpenWebUI.get_model(name: ‘required - model id’)\n\nOpen WebUI mixes OpenAI-compat rows and proxied Ollama tags. A direct model route is tried first; otherwise the already-fetched catalog row is returned. Local tags without a price stay unpriced.\n"}]}
|
|
1253
1257
|
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.get_models Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.get_models`: Supported Method Parameters\n\nresponse = PWN::AI::OpenWebUI.get_models\n"}]}
|
|
1254
1258
|
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.help Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.help`: "}]}
|
|
1259
|
+
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.lead_system Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.lead_system`: "}]}
|
|
1255
1260
|
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.normalize_messages_for_ollama Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.normalize_messages_for_ollama`: "}]}
|
|
1256
1261
|
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.openwebui_rest_call Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.openwebui_rest_call`: Supported Method Parameters\n\nopenwebui_rest_call(\n\nhttp_method: 'optional HTTP method (defaults to GET)',\nrest_call: 'required rest call path relative to base_uri',\nparams: 'optional params passed in the URI or HTTP Headers',\nhttp_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST',\ntimeout: 'optional timeout in seconds (defaults to 900)',\nspinner: 'optional - display spinner (defaults to false)'\n\n)\n"}]}
|
|
1257
1262
|
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.parse_tool_arguments_object Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.parse_tool_arguments_object`: "}]}
|
|
1258
1263
|
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.real_config_value? Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.real_config_value?`: "}]}
|
|
1264
|
+
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.strict_system_template? Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.strict_system_template?`: "}]}
|
|
1265
|
+
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.template_rejects_later_system? Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.template_rejects_later_system?`: "}]}
|
|
1259
1266
|
{"messages":[{"role":"user","content":"PWN::AI::OpenWebUI.visible_from_thinking Usage"},{"role":"assistant","content":"`PWN::AI::OpenWebUI.visible_from_thinking`: "}]}
|
|
1260
1267
|
{"messages":[{"role":"user","content":"PWN::AI::RedTeam.authors Usage"},{"role":"assistant","content":"`PWN::AI::RedTeam.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
1261
1268
|
{"messages":[{"role":"user","content":"PWN::AI::RedTeam.help Usage"},{"role":"assistant","content":"`PWN::AI::RedTeam.help`: "}]}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pwn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.759
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 0day Inc.
|
|
@@ -771,14 +771,14 @@ dependencies:
|
|
|
771
771
|
requirements:
|
|
772
772
|
- - '='
|
|
773
773
|
- !ruby/object:Gem::Version
|
|
774
|
-
version: 1.
|
|
774
|
+
version: 1.7.0
|
|
775
775
|
type: :development
|
|
776
776
|
prerelease: false
|
|
777
777
|
version_requirements: !ruby/object:Gem::Requirement
|
|
778
778
|
requirements:
|
|
779
779
|
- - '='
|
|
780
780
|
- !ruby/object:Gem::Version
|
|
781
|
-
version: 1.
|
|
781
|
+
version: 1.7.0
|
|
782
782
|
- !ruby/object:Gem::Dependency
|
|
783
783
|
name: pry
|
|
784
784
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -6754,6 +6754,7 @@ files:
|
|
|
6754
6754
|
- spec/lib/pwn/ai/model_catalog_spec.rb
|
|
6755
6755
|
- spec/lib/pwn/ai/model_lookup_spec.rb
|
|
6756
6756
|
- spec/lib/pwn/ai/ollama_spec.rb
|
|
6757
|
+
- spec/lib/pwn/ai/ollama_system_message_spec.rb
|
|
6757
6758
|
- spec/lib/pwn/ai/open_ai_oauth_transport_spec.rb
|
|
6758
6759
|
- spec/lib/pwn/ai/open_ai_spec.rb
|
|
6759
6760
|
- spec/lib/pwn/ai/open_web_ui_spec.rb
|