pwn 0.5.705 → 0.5.706
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/lib/pwn/ai/agent/loop.rb +165 -11
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/loop_spec.rb +107 -0
- data/third_party/pwn_rdoc.jsonl +11 -0
- 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: f9233042c1c9518d8cfdcbfe6499eb521cb0348384554e03ab6641abdda253b9
|
|
4
|
+
data.tar.gz: 51b81fd36f292bc54e762d4bb47f5641c9b263addf9f37997aaa4c2f6a00c49f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 370d8b9305f7dcd2a692061b6708feca282546109533131d244cd97d207d1b216afac16baf44847a8a098c9847467208dd30e81084729f79ee7e3c16c050ae32
|
|
7
|
+
data.tar.gz: 97725299f78baef12d73614b7258ebf5cc3664316c82d2bf53d6a82e3d9e0e0d2542a3143cbc686fc75b46b6883fb8edc0174cfd38ce74b88a0c908ab4864642
|
data/lib/pwn/ai/agent/loop.rb
CHANGED
|
@@ -584,6 +584,10 @@ module PWN
|
|
|
584
584
|
live = effects.reject { |fx| %i[recall store].include?(fx) }
|
|
585
585
|
return true if live.empty?
|
|
586
586
|
return true if duration_unsatisfied?(request: request)
|
|
587
|
+
return true if declared_contract_unsatisfied?(
|
|
588
|
+
request: request,
|
|
589
|
+
messages: opts[:messages]
|
|
590
|
+
)
|
|
587
591
|
return true if need == :write && !write_verified?(effects: effects)
|
|
588
592
|
return true if need == :browse && !effects.include?(:browse)
|
|
589
593
|
return true if need == :any && !effects.intersect?(%i[write browse eval])
|
|
@@ -613,23 +617,157 @@ module PWN
|
|
|
613
617
|
}.freeze
|
|
614
618
|
|
|
615
619
|
private_class_method def self.duration_unsatisfied?(opts = {})
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
hours =
|
|
620
|
-
|
|
621
|
-
|
|
620
|
+
secs = declared_min_seconds(request: opts[:request])
|
|
621
|
+
if secs <= 0
|
|
622
|
+
req = opts[:request].to_s
|
|
623
|
+
hours = nil
|
|
624
|
+
if (m = req.match(/\b(\d+)\s*(?:hours?|hrs?)\b/i))
|
|
625
|
+
hours = m[1].to_i
|
|
626
|
+
elsif (m = req.match(/\b(twenty-four|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)\s*(?:hours?|hrs?)\b/i))
|
|
627
|
+
hours = HOUR_WORDS[m[1].downcase]
|
|
628
|
+
end
|
|
629
|
+
secs = hours.to_i * 3600
|
|
622
630
|
end
|
|
623
|
-
return false if
|
|
631
|
+
return false if secs <= 0
|
|
624
632
|
|
|
625
633
|
t0 = Thread.current[:pwn_loop_t0]
|
|
626
634
|
return false unless t0
|
|
627
635
|
|
|
628
|
-
(Time.now - t0) <
|
|
636
|
+
(Time.now - t0) < secs
|
|
637
|
+
rescue StandardError
|
|
638
|
+
false
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
EMPTY_CONTRACT = {
|
|
642
|
+
paths: [],
|
|
643
|
+
min_seconds: 0,
|
|
644
|
+
skills: [],
|
|
645
|
+
proofs: [],
|
|
646
|
+
hosts: []
|
|
647
|
+
}.freeze
|
|
648
|
+
|
|
649
|
+
private_class_method def self.declared_min_seconds(opts = {})
|
|
650
|
+
declared_contract(request: opts[:request])[:min_seconds].to_i
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
private_class_method def self.declared_contract_unsatisfied?(opts = {})
|
|
654
|
+
contract = declared_contract(request: opts[:request])
|
|
655
|
+
files = Array(contract[:paths]) + Array(contract[:proofs])
|
|
656
|
+
return true if files.any? { |path| deliverable_missing?(path: path) }
|
|
657
|
+
return true if declared_skills_missing?(skills: contract[:skills])
|
|
658
|
+
return true if declared_hosts_missing?(hosts: contract[:hosts], messages: opts[:messages])
|
|
659
|
+
|
|
660
|
+
false
|
|
629
661
|
rescue StandardError
|
|
630
662
|
false
|
|
631
663
|
end
|
|
632
664
|
|
|
665
|
+
private_class_method def self.declared_skills_missing?(opts = {})
|
|
666
|
+
names = Array(opts[:skills]).map(&:to_s).reject(&:empty?)
|
|
667
|
+
return false if names.empty?
|
|
668
|
+
return true unless defined?(PWN::Skills) && PWN::Skills.is_a?(Hash)
|
|
669
|
+
|
|
670
|
+
have = PWN::Skills.keys.map(&:to_s)
|
|
671
|
+
names.any? { |name| !have.include?(name) }
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
private_class_method def self.declared_hosts_missing?(opts = {})
|
|
675
|
+
hosts = Array(opts[:hosts]).map(&:to_s).reject(&:empty?)
|
|
676
|
+
return false if hosts.empty?
|
|
677
|
+
|
|
678
|
+
blob = Array(opts[:messages]).select { |msg| msg.is_a?(Hash) && msg[:role].to_s == 'tool' }
|
|
679
|
+
.map { |msg| msg[:content].to_s }
|
|
680
|
+
.join("\n")
|
|
681
|
+
.downcase
|
|
682
|
+
hosts.any? { |host| !blob.include?(host.to_s.downcase) }
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
private_class_method def self.declared_deliverables(opts = {})
|
|
686
|
+
Array(declared_contract(request: opts[:request])[:paths])
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
private_class_method def self.declared_contract(opts = {})
|
|
690
|
+
cached = Thread.current[:pwn_loop_deliverables]
|
|
691
|
+
return normalize_contract(raw: cached) if cached.is_a?(Array) || cached.is_a?(Hash)
|
|
692
|
+
return EMPTY_CONTRACT.dup unless Thread.current[:pwn_loop_active]
|
|
693
|
+
|
|
694
|
+
contract = infer_deliverables(request: opts[:request])
|
|
695
|
+
Thread.current[:pwn_loop_deliverables] = contract
|
|
696
|
+
contract
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
private_class_method def self.normalize_contract(opts = {})
|
|
700
|
+
raw = opts[:raw]
|
|
701
|
+
return EMPTY_CONTRACT.merge(paths: raw.map(&:to_s).select { |p| p.start_with?('/') }) if raw.is_a?(Array)
|
|
702
|
+
return EMPTY_CONTRACT.dup unless raw.is_a?(Hash)
|
|
703
|
+
|
|
704
|
+
hours = raw[:hours] || raw['hours']
|
|
705
|
+
secs = (raw[:min_seconds] || raw['min_seconds']).to_i
|
|
706
|
+
secs = hours.to_i * 3600 if secs <= 0 && hours.to_i.positive?
|
|
707
|
+
{
|
|
708
|
+
paths: abs_paths(rows: raw[:paths] || raw['paths']),
|
|
709
|
+
min_seconds: secs,
|
|
710
|
+
skills: Array(raw[:skills] || raw['skills']).map(&:to_s).reject(&:empty?).uniq,
|
|
711
|
+
proofs: abs_paths(rows: raw[:proofs] || raw['proofs']),
|
|
712
|
+
hosts: Array(raw[:hosts] || raw['hosts']).map(&:to_s).reject(&:empty?).uniq
|
|
713
|
+
}
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
private_class_method def self.abs_paths(opts = {})
|
|
717
|
+
Array(opts[:rows]).map(&:to_s).select { |path| path.start_with?('/') }.uniq
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
private_class_method def self.infer_deliverables(opts = {})
|
|
721
|
+
request = opts[:request].to_s
|
|
722
|
+
return EMPTY_CONTRACT.dup if request.strip.empty?
|
|
723
|
+
|
|
724
|
+
reply = call_engine(
|
|
725
|
+
messages: [
|
|
726
|
+
{
|
|
727
|
+
role: 'system',
|
|
728
|
+
content: 'Reply with JSON only. No markdown. No tools.'
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
role: 'user',
|
|
732
|
+
content: "Operator request:\n#{request}\n\n" \
|
|
733
|
+
'When that request is complete, what must be true on this host? ' \
|
|
734
|
+
'JSON only: {"paths":["/abs/file"],"min_seconds":0,"skills":["name"],' \
|
|
735
|
+
'"proofs":["/abs/poc"],"hosts":["ip-or-hostname"]}. ' \
|
|
736
|
+
'Use [] or 0 when a field is not required. Do not invent work. Paths must be absolute.'
|
|
737
|
+
}
|
|
738
|
+
],
|
|
739
|
+
tools: nil
|
|
740
|
+
)
|
|
741
|
+
text = reply.is_a?(Hash) ? (reply[:content] || reply['content']).to_s : reply.to_s
|
|
742
|
+
parse_contract(text: text)
|
|
743
|
+
rescue StandardError
|
|
744
|
+
EMPTY_CONTRACT.dup
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
private_class_method def self.parse_contract(opts = {})
|
|
748
|
+
text = opts[:text].to_s
|
|
749
|
+
json = nil
|
|
750
|
+
begin
|
|
751
|
+
json = JSON.parse(text, symbolize_names: true)
|
|
752
|
+
rescue JSON::ParserError
|
|
753
|
+
start = text.index('{')
|
|
754
|
+
stop = text.rindex('}')
|
|
755
|
+
return EMPTY_CONTRACT.dup unless start && stop && stop > start
|
|
756
|
+
|
|
757
|
+
json = JSON.parse(text[start..stop], symbolize_names: true)
|
|
758
|
+
end
|
|
759
|
+
normalize_contract(raw: json)
|
|
760
|
+
rescue StandardError
|
|
761
|
+
EMPTY_CONTRACT.dup
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
private_class_method def self.deliverable_missing?(opts = {})
|
|
765
|
+
path = opts[:path].to_s
|
|
766
|
+
path.empty? || !File.file?(path) || File.size(path) <= 0
|
|
767
|
+
rescue StandardError
|
|
768
|
+
true
|
|
769
|
+
end
|
|
770
|
+
|
|
633
771
|
private_class_method def self.may_finalize?(opts = {})
|
|
634
772
|
return false if incomplete_final?(text: opts[:text], last_iter: false)
|
|
635
773
|
return false if request_unsatisfied?(
|
|
@@ -2434,6 +2572,10 @@ module PWN
|
|
|
2434
2572
|
Thread.current[:pwn_extinguished] = {}
|
|
2435
2573
|
Thread.current[:pwn_same_payload] = Hash.new(0)
|
|
2436
2574
|
Thread.current[:pwn_loop_t0] = Time.now unless nested
|
|
2575
|
+
unless nested
|
|
2576
|
+
Thread.current[:pwn_loop_active] = true
|
|
2577
|
+
Thread.current[:pwn_loop_deliverables] = nil
|
|
2578
|
+
end
|
|
2437
2579
|
debug_progress(msg: "intent=#{intent} engine=#{engine}", debug: opts[:debug])
|
|
2438
2580
|
expose_current_session(session_id: session_id)
|
|
2439
2581
|
Mistakes.check_user_correction(request: request, session_id: session_id) if defined?(Mistakes)
|
|
@@ -2672,12 +2814,20 @@ module PWN
|
|
|
2672
2814
|
# commit that as the answer; drop the empty assistant turn,
|
|
2673
2815
|
# inject a one-shot nudge, and keep iterating.
|
|
2674
2816
|
if calls.empty? && text.strip.empty?
|
|
2817
|
+
unsat = request_unsatisfied?(request: request, messages: messages)
|
|
2675
2818
|
warn "[pwn-ai/loop] empty final from #{engine} on iter=#{i}; nudging" if local
|
|
2819
|
+
empty_nudge = if unsat
|
|
2820
|
+
'Your previous reply was empty (no tool_calls and no content). ' \
|
|
2821
|
+
'The original request is not evidenced yet. Emit NATIVE tool_calls NOW. ' \
|
|
2822
|
+
'Do not write a final answer until that request is done or a tool returned failure evidence.'
|
|
2823
|
+
else
|
|
2824
|
+
'Your previous reply was empty (no tool_calls and no content). ' \
|
|
2825
|
+
'Either call a tool now, or write the final answer for the user as plain text. ' \
|
|
2826
|
+
'Do not reply with an empty message.'
|
|
2827
|
+
end
|
|
2676
2828
|
messages << {
|
|
2677
2829
|
role: 'user',
|
|
2678
|
-
content:
|
|
2679
|
-
'Either call a tool now, or write the final answer for the user as plain text. ' \
|
|
2680
|
-
'Do not reply with an empty message.'
|
|
2830
|
+
content: empty_nudge
|
|
2681
2831
|
}
|
|
2682
2832
|
turn_fails['empty_final'] += 1
|
|
2683
2833
|
debug_progress(msg: "bounce empty_final snippet=#{debug_snippet(text: text)}")
|
|
@@ -2830,6 +2980,10 @@ module PWN
|
|
|
2830
2980
|
end
|
|
2831
2981
|
raise
|
|
2832
2982
|
ensure
|
|
2983
|
+
unless nested
|
|
2984
|
+
Thread.current[:pwn_loop_active] = nil
|
|
2985
|
+
Thread.current[:pwn_loop_deliverables] = nil
|
|
2986
|
+
end
|
|
2833
2987
|
Thread.current[:pwn_loop_no_tools] = nil
|
|
2834
2988
|
finish_debug_request!(
|
|
2835
2989
|
iter: i,
|
data/lib/pwn/version.rb
CHANGED
|
@@ -910,6 +910,113 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
|
|
|
910
910
|
Thread.current[:pwn_loop_t0] = nil
|
|
911
911
|
end
|
|
912
912
|
|
|
913
|
+
it 'keeps LLM-declared deliverable paths unsatisfied until those files exist' do
|
|
914
|
+
path = "/tmp/pwn-deliv-#{Process.pid}.bin"
|
|
915
|
+
FileUtils.rm_f(path)
|
|
916
|
+
Thread.current[:pwn_loop_deliverables] = [path]
|
|
917
|
+
req = 'Exhaustively analyze the docker app. 100% coverage. Store the PDF in /tmp/container_pentest-p4-PWN-122B.pdf'
|
|
918
|
+
recap = 'Good! Let me continue the penetration test with more aggressive testing now.'
|
|
919
|
+
msgs = [
|
|
920
|
+
{ role: 'user', content: req },
|
|
921
|
+
{
|
|
922
|
+
role: 'assistant',
|
|
923
|
+
tool_calls: [{ function: { name: 'pwn_eval', arguments: '{"code":"File.write(\"/tmp/pentest_report.json\",\"x\")"}' } }]
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
role: 'tool',
|
|
927
|
+
name: 'pwn_eval',
|
|
928
|
+
content: '{"success":true,"result":{"stdout":"Report saved to /tmp/pentest_report.json"},"effect":"write"}'
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
role: 'assistant',
|
|
932
|
+
tool_calls: [{ function: { name: 'shell', arguments: '{"command":"curl -s http://127.0.0.1:5000/auth/login"}' } }]
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
role: 'tool',
|
|
936
|
+
name: 'shell',
|
|
937
|
+
content: '{"success":true,"result":{"stdout":"<form>"},"effect":"read"}'
|
|
938
|
+
},
|
|
939
|
+
{ role: 'assistant', content: recap }
|
|
940
|
+
]
|
|
941
|
+
expect(described_class.send(:request_unsatisfied?, request: req, messages: msgs)).to eq(true)
|
|
942
|
+
expect(described_class.send(:may_finalize?, request: req, messages: msgs, text: recap)).to eq(false)
|
|
943
|
+
File.write(path, 'any bytes')
|
|
944
|
+
expect(described_class.send(:request_unsatisfied?, request: req, messages: msgs)).to eq(false)
|
|
945
|
+
ensure
|
|
946
|
+
FileUtils.rm_f(path)
|
|
947
|
+
Thread.current[:pwn_loop_deliverables] = nil
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
it 'parses a full acceptance contract from one engine JSON hop' do
|
|
951
|
+
src = File.read(described_class.method(:run).source_location.first)
|
|
952
|
+
expect(src).not_to match(/NAMED_PATH_RX/)
|
|
953
|
+
expect(src).not_to include('%PDF')
|
|
954
|
+
expect(src).to match(/infer_deliverables|pwn_loop_deliverables/)
|
|
955
|
+
payload = {
|
|
956
|
+
paths: ['/tmp/out.json'],
|
|
957
|
+
min_seconds: 28_800,
|
|
958
|
+
skills: ['penetration-testing'],
|
|
959
|
+
proofs: ['/tmp/poc.sh'],
|
|
960
|
+
hosts: ['127.0.0.1']
|
|
961
|
+
}
|
|
962
|
+
allow(described_class).to receive(:call_engine).and_return(payload.to_json)
|
|
963
|
+
Thread.current[:pwn_loop_active] = true
|
|
964
|
+
Thread.current[:pwn_loop_deliverables] = nil
|
|
965
|
+
paths = described_class.send(:declared_deliverables, request: 'write a report somewhere')
|
|
966
|
+
expect(paths).to eq(['/tmp/out.json'])
|
|
967
|
+
contract = described_class.send(:declared_contract, request: 'write a report somewhere')
|
|
968
|
+
expect(contract[:min_seconds]).to eq(28_800)
|
|
969
|
+
expect(contract[:skills]).to eq(['penetration-testing'])
|
|
970
|
+
expect(contract[:proofs]).to eq(['/tmp/poc.sh'])
|
|
971
|
+
expect(contract[:hosts]).to eq(['127.0.0.1'])
|
|
972
|
+
expect(described_class).to have_received(:call_engine).once
|
|
973
|
+
ensure
|
|
974
|
+
Thread.current[:pwn_loop_active] = nil
|
|
975
|
+
Thread.current[:pwn_loop_deliverables] = nil
|
|
976
|
+
end
|
|
977
|
+
|
|
978
|
+
it 'verifies LLM-declared duration, skills, proofs, and hosts in the world' do
|
|
979
|
+
proof = "/tmp/pwn-proof-#{Process.pid}.txt"
|
|
980
|
+
FileUtils.rm_f(proof)
|
|
981
|
+
req = 'black-box test 127.0.0.1 using penetration-testing'
|
|
982
|
+
recap = 'done'
|
|
983
|
+
msgs = [
|
|
984
|
+
{ role: 'user', content: req },
|
|
985
|
+
{
|
|
986
|
+
role: 'assistant',
|
|
987
|
+
tool_calls: [{ function: { name: 'pwn_eval', arguments: '{"code":"1"}' } }]
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
role: 'tool',
|
|
991
|
+
name: 'pwn_eval',
|
|
992
|
+
content: '{"success":true,"result":{"value":"1","stdout":"scanned 10.0.0.1"},"effect":"eval"}'
|
|
993
|
+
},
|
|
994
|
+
{ role: 'assistant', content: recap }
|
|
995
|
+
]
|
|
996
|
+
Thread.current[:pwn_loop_t0] = Time.now
|
|
997
|
+
Thread.current[:pwn_loop_deliverables] = {
|
|
998
|
+
paths: [],
|
|
999
|
+
min_seconds: 60,
|
|
1000
|
+
skills: ['no-such-bundled-skill'],
|
|
1001
|
+
proofs: [proof],
|
|
1002
|
+
hosts: ['127.0.0.1']
|
|
1003
|
+
}
|
|
1004
|
+
expect(described_class.send(:request_unsatisfied?, request: req, messages: msgs)).to eq(true)
|
|
1005
|
+
Thread.current[:pwn_loop_deliverables][:min_seconds] = 0
|
|
1006
|
+
expect(described_class.send(:request_unsatisfied?, request: req, messages: msgs)).to eq(true)
|
|
1007
|
+
Thread.current[:pwn_loop_deliverables][:skills] = []
|
|
1008
|
+
expect(described_class.send(:request_unsatisfied?, request: req, messages: msgs)).to eq(true)
|
|
1009
|
+
File.write(proof, 'poc')
|
|
1010
|
+
expect(described_class.send(:request_unsatisfied?, request: req, messages: msgs)).to eq(true)
|
|
1011
|
+
msgs[2][:content] = '{"success":true,"result":{"value":"1","stdout":"open 127.0.0.1:5000"},"effect":"eval"}'
|
|
1012
|
+
expect(described_class.send(:request_unsatisfied?, request: req, messages: msgs)).to eq(false)
|
|
1013
|
+
expect(described_class.send(:may_finalize?, request: req, messages: msgs, text: recap)).to eq(true)
|
|
1014
|
+
ensure
|
|
1015
|
+
FileUtils.rm_f(proof)
|
|
1016
|
+
Thread.current[:pwn_loop_t0] = nil
|
|
1017
|
+
Thread.current[:pwn_loop_deliverables] = nil
|
|
1018
|
+
end
|
|
1019
|
+
|
|
913
1020
|
it 'forces tool_choice required on host-work before any tool result, for every engine' do
|
|
914
1021
|
src = File.read(described_class.method(:run).source_location.first)
|
|
915
1022
|
expect(src).to match(/tool_choice/)
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -309,6 +309,7 @@
|
|
|
309
309
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Learning.update_skill Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Learning.update_skill`: "}]}
|
|
310
310
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Learning.verdict_for_score Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Learning.verdict_for_score`: "}]}
|
|
311
311
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Learning.weighted_judge_mean Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Learning.weighted_judge_mean`: "}]}
|
|
312
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.abs_paths Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.abs_paths`: "}]}
|
|
312
313
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.active_engine Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.active_engine`: "}]}
|
|
313
314
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.agent_flag Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.agent_flag`: "}]}
|
|
314
315
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.answer_greeting Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.answer_greeting`: "}]}
|
|
@@ -335,8 +336,15 @@
|
|
|
335
336
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.debug_snippet Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.debug_snippet`: "}]}
|
|
336
337
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.debug_tool_io! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.debug_tool_io!`: "}]}
|
|
337
338
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.debug_tools_line Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.debug_tools_line`: "}]}
|
|
339
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.declared_contract Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.declared_contract`: "}]}
|
|
340
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.declared_contract_unsatisfied? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.declared_contract_unsatisfied?`: "}]}
|
|
341
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.declared_deliverables Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.declared_deliverables`: "}]}
|
|
342
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.declared_hosts_missing? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.declared_hosts_missing?`: "}]}
|
|
343
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.declared_min_seconds Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.declared_min_seconds`: "}]}
|
|
344
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.declared_skills_missing? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.declared_skills_missing?`: "}]}
|
|
338
345
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.default_interactive_toolsets Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.default_interactive_toolsets`: "}]}
|
|
339
346
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.degrade_text_only Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.degrade_text_only`: "}]}
|
|
347
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.deliverable_missing? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.deliverable_missing?`: "}]}
|
|
340
348
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.dispatch_fail_n Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.dispatch_fail_n`: "}]}
|
|
341
349
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.duration_unsatisfied? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.duration_unsatisfied?`: "}]}
|
|
342
350
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.emit_task_summary Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.emit_task_summary`: "}]}
|
|
@@ -349,6 +357,7 @@
|
|
|
349
357
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.guard_repeated_failure Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.guard_repeated_failure`: "}]}
|
|
350
358
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.help`: "}]}
|
|
351
359
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.incomplete_final? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.incomplete_final?`: "}]}
|
|
360
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.infer_deliverables Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.infer_deliverables`: "}]}
|
|
352
361
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.inject_task_focus! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.inject_task_focus!`: "}]}
|
|
353
362
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.local_engine? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.local_engine?`: "}]}
|
|
354
363
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.loud_debug_tui! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.loud_debug_tui!`: "}]}
|
|
@@ -361,12 +370,14 @@
|
|
|
361
370
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.mistake_ts Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.mistake_ts`: "}]}
|
|
362
371
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.needs_host_work? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.needs_host_work?`: "}]}
|
|
363
372
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.no_progress_result Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.no_progress_result`: "}]}
|
|
373
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.normalize_contract Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.normalize_contract`: "}]}
|
|
364
374
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.normalize_llm Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.normalize_llm`: Supported Method Parameters\n\nmsg = PWN::AI::Agent::Loop.normalize_llm(\n\nresponse: 'required - chat_with_tools response Hash from any provider'\n\n)\n"}]}
|
|
365
375
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.note_same_payload! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.note_same_payload!`: "}]}
|
|
366
376
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.ollama_wire_messages Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.ollama_wire_messages`: Supported Method Parameters\n\nwire = PWN::AI::Agent::Loop.ollama_wire_messages(\n\nmessages: 'required - in-memory OpenAI-ish messages (may have String args)'\n\n)\n\nReturns a deep-copied array safe for Ollama / Open WebUI ollama/api/chat:\n\nparses JSON-string function.arguments into Hash/Array objects\n\ncoerces nil assistant content to ” when tool_calls present (Open WebUI GenerateChatCompletionForm rejects content:null alone)\n\ndrops _native_content / _text_tool_coerced / thinking private keys\n\nstringifies Hash/Array message content (tool results) to JSON text\n\n"}]}
|
|
367
377
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.ollama_wire_tool_call Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.ollama_wire_tool_call`: "}]}
|
|
368
378
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.openai_wire_messages Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.openai_wire_messages`: Supported Method Parameters\n\nwire = PWN::AI::Agent::Loop.openai_wire_messages(\n\nmessages: 'required - in-memory OpenAI-ish messages (may have Hash args / internal keys)'\n\n)\n\nReturns a deep-copied array safe for OpenAI / xAI chat.completions:\n\ndrops _native_content / _text_tool_coerced / thinking private keys\n\nstringifies function.arguments maps\n\ncoerces Hash/non-string content to JSON/string (nil kept for assistant tool turns)\n\n"}]}
|
|
369
379
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.openai_wire_tool_call Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.openai_wire_tool_call`: "}]}
|
|
380
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.parse_contract Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.parse_contract`: "}]}
|
|
370
381
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.parse_tool_arguments Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.parse_tool_arguments`: "}]}
|
|
371
382
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.payload_sig Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.payload_sig`: "}]}
|
|
372
383
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.plan_first Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.plan_first`: "}]}
|