pwn 0.5.701 → 0.5.702
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 +38 -18
- data/lib/pwn/ai/agent/task_summarizer.rb +22 -22
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/loop_spec.rb +39 -14
- data/spec/lib/pwn/ai/agent/task_summarizer_spec.rb +48 -9
- data/third_party/pwn_rdoc.jsonl +2 -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: 5e0f1934072cdfc2dd334d22bcd15774e5756a0bd02bb68962759ceb2e436b98
|
|
4
|
+
data.tar.gz: b092c265b35a9000293ce787252452c8045c4604677a3bd5416a2e4e732c2cce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61bdd9c6f370aa44f1cb3e5b00a928cd57acb5f1a40154769079ecec39ac4f0d8ce81c8daac5c5a1f5040b96cff7b7503de230645920531a854f10762bf75058
|
|
7
|
+
data.tar.gz: bc978a06cf9dd7e109696b6e95bfba64dcbc73d3092a9fbdcfe19cdf488dcec0b3c463866a90703050705fdf01bdd0f6a34e93c5f02812631682669499632461
|
data/lib/pwn/ai/agent/loop.rb
CHANGED
|
@@ -172,6 +172,7 @@ module PWN
|
|
|
172
172
|
private_class_method def self.finish_debug_request!(opts = {})
|
|
173
173
|
return unless debug_on?(opts)
|
|
174
174
|
return unless defined?(PWN::Plugins::Log)
|
|
175
|
+
return if opts[:nested]
|
|
175
176
|
|
|
176
177
|
PWN::Plugins::Log.finish_request_log!(
|
|
177
178
|
iter: opts[:iter],
|
|
@@ -572,6 +573,7 @@ module PWN
|
|
|
572
573
|
|
|
573
574
|
live = effects.reject { |fx| %i[recall store].include?(fx) }
|
|
574
575
|
return true if live.empty?
|
|
576
|
+
return true if duration_unsatisfied?(request: request)
|
|
575
577
|
return true if need == :write && !write_verified?(effects: effects)
|
|
576
578
|
return true if need == :browse && !effects.include?(:browse)
|
|
577
579
|
return true if need == :any && !effects.intersect?(%i[write browse eval])
|
|
@@ -592,6 +594,32 @@ module PWN
|
|
|
592
594
|
false
|
|
593
595
|
end
|
|
594
596
|
|
|
597
|
+
HOUR_WORDS = {
|
|
598
|
+
'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5,
|
|
599
|
+
'six' => 6, 'seven' => 7, 'eight' => 8, 'nine' => 9, 'ten' => 10,
|
|
600
|
+
'eleven' => 11, 'twelve' => 12, 'thirteen' => 13, 'fourteen' => 14,
|
|
601
|
+
'fifteen' => 15, 'sixteen' => 16, 'seventeen' => 17, 'eighteen' => 18,
|
|
602
|
+
'nineteen' => 19, 'twenty' => 20, 'twenty-four' => 24
|
|
603
|
+
}.freeze
|
|
604
|
+
|
|
605
|
+
private_class_method def self.duration_unsatisfied?(opts = {})
|
|
606
|
+
req = opts[:request].to_s
|
|
607
|
+
hours = nil
|
|
608
|
+
if (m = req.match(/\b(\d+)\s*(?:hours?|hrs?)\b/i))
|
|
609
|
+
hours = m[1].to_i
|
|
610
|
+
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))
|
|
611
|
+
hours = HOUR_WORDS[m[1].downcase]
|
|
612
|
+
end
|
|
613
|
+
return false if hours.to_i <= 0
|
|
614
|
+
|
|
615
|
+
t0 = Thread.current[:pwn_loop_t0]
|
|
616
|
+
return false unless t0
|
|
617
|
+
|
|
618
|
+
(Time.now - t0) < (hours * 3600)
|
|
619
|
+
rescue StandardError
|
|
620
|
+
false
|
|
621
|
+
end
|
|
622
|
+
|
|
595
623
|
private_class_method def self.may_finalize?(opts = {})
|
|
596
624
|
return false if incomplete_final?(text: opts[:text], last_iter: false)
|
|
597
625
|
return false if request_unsatisfied?(
|
|
@@ -1589,23 +1617,13 @@ module PWN
|
|
|
1589
1617
|
# Uses TaskSummarizer.active_task_prompt (full plan_context on first
|
|
1590
1618
|
# force, compact focus thereafter). No-ops when already injected.
|
|
1591
1619
|
private_class_method def self.inject_task_focus!(opts = {})
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
return nil
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
req = opts[:request]
|
|
1600
|
-
req = state[:original_request] || state[:request] if req.to_s.strip.empty? && state.is_a?(Hash)
|
|
1601
|
-
text =
|
|
1602
|
-
(TaskSummarizer.active_task_prompt(state: state, force: opts[:force], request: req) if TaskSummarizer.respond_to?(:active_task_prompt))
|
|
1603
|
-
return nil if text.to_s.strip.empty?
|
|
1604
|
-
|
|
1605
|
-
messages << { role: 'user', content: text }
|
|
1606
|
-
text
|
|
1607
|
-
rescue StandardError => e
|
|
1608
|
-
warn "[pwn-ai/loop] inject_task_focus! swallowed: #{e.class}: #{e.message}"
|
|
1620
|
+
# Original request is the only model-facing goal. TaskSummarizer
|
|
1621
|
+
# remains TUI (emit_plan / about_to). Do not append a compass
|
|
1622
|
+
# user message that can replace the operator ask.
|
|
1623
|
+
return nil if opts.is_a?(Hash)
|
|
1624
|
+
|
|
1625
|
+
nil
|
|
1626
|
+
rescue StandardError
|
|
1609
1627
|
nil
|
|
1610
1628
|
end
|
|
1611
1629
|
|
|
@@ -2396,6 +2414,7 @@ module PWN
|
|
|
2396
2414
|
Thread.current[:pwn_request_intent] = intent
|
|
2397
2415
|
Thread.current[:pwn_extinguished] = {}
|
|
2398
2416
|
Thread.current[:pwn_same_payload] = Hash.new(0)
|
|
2417
|
+
Thread.current[:pwn_loop_t0] = Time.now unless nested
|
|
2399
2418
|
debug_progress(msg: "intent=#{intent} engine=#{engine}", debug: opts[:debug])
|
|
2400
2419
|
expose_current_session(session_id: session_id)
|
|
2401
2420
|
Mistakes.check_user_correction(request: request, session_id: session_id) if defined?(Mistakes)
|
|
@@ -2795,7 +2814,8 @@ module PWN
|
|
|
2795
2814
|
iter: i,
|
|
2796
2815
|
tools_called: tools_called,
|
|
2797
2816
|
engine_s: engine_s,
|
|
2798
|
-
final_chars: final_chars
|
|
2817
|
+
final_chars: final_chars,
|
|
2818
|
+
nested: nested
|
|
2799
2819
|
)
|
|
2800
2820
|
TurnFinalizer.leave_user_path! if defined?(TurnFinalizer)
|
|
2801
2821
|
end
|
|
@@ -328,6 +328,7 @@ module PWN
|
|
|
328
328
|
source = :injected
|
|
329
329
|
else
|
|
330
330
|
tasks = llm_decompose(goal: goal, llm_tasks: opts[:llm_tasks], has_llm_tasks: opts.key?(:llm_tasks))
|
|
331
|
+
tasks = reject_scaffold_tasks(tasks: tasks)
|
|
331
332
|
source = tasks.any? ? :llm : nil
|
|
332
333
|
if tasks.length < MIN_PLAN_TASKS
|
|
333
334
|
tasks = fallback_decompose(goal: goal)
|
|
@@ -677,19 +678,9 @@ module PWN
|
|
|
677
678
|
return tasks
|
|
678
679
|
end
|
|
679
680
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
if goal_lc.match?(/\b(json|ya?ml|table|csv|tsv)\b/)
|
|
684
|
-
fmt = goal_lc[/\b(json|ya?ml|table|csv|tsv)\b/]
|
|
685
|
-
tasks << "Present the results in #{fmt} format"
|
|
686
|
-
elsif goal_lc.match?(/\b(display|show|print|output|format|present|report|export)\b/)
|
|
687
|
-
tasks << 'Present the final results in the requested format'
|
|
688
|
-
end
|
|
689
|
-
|
|
690
|
-
# Only when the user actually asked about tests/lint — not bare "verify".
|
|
691
|
-
tasks << 'Run specs, rubocop, and/or rake to verify' if goal_lc.match?(/\b(test|spec|rubocop|rake|lint)\b/) &&
|
|
692
|
-
goal_lc.match?(%r{\b(/opt/pwn|code|patch|refactor|commit)\b})
|
|
681
|
+
shaped = request_clause_tasks(goal: goal_text)
|
|
682
|
+
return shaped if shaped.length >= MIN_PLAN_TASKS
|
|
683
|
+
return [goal_text] unless goal_text.strip.empty?
|
|
693
684
|
|
|
694
685
|
tasks
|
|
695
686
|
rescue StandardError
|
|
@@ -706,6 +697,20 @@ module PWN
|
|
|
706
697
|
fallback_decompose(goal: opts[:goal])
|
|
707
698
|
end
|
|
708
699
|
|
|
700
|
+
private_class_method def self.request_clause_tasks(opts = {})
|
|
701
|
+
goal = opts[:goal].to_s.gsub(/\s+/, ' ').strip
|
|
702
|
+
return [] if goal.empty?
|
|
703
|
+
|
|
704
|
+
parts = goal.split(/(?<=[.!?])\s+|(?<=;)\s+|\s+(?:ensuring|then|and then)\s+/i)
|
|
705
|
+
parts = parts.map { |p| p.gsub(/\s+/, ' ').strip.sub(/\A(?:so|and|then)\s+/i, '') }
|
|
706
|
+
parts = reject_scaffold_tasks(tasks: parts).reject { |p| p.length < 24 }
|
|
707
|
+
return parts.first(8) if parts.length >= MIN_PLAN_TASKS
|
|
708
|
+
|
|
709
|
+
[goal]
|
|
710
|
+
rescue StandardError
|
|
711
|
+
[]
|
|
712
|
+
end
|
|
713
|
+
|
|
709
714
|
private_class_method def self.truncate_goal(opts = {})
|
|
710
715
|
# Task summaries are displayed in full — do not ellipsize goals or
|
|
711
716
|
# plan items. (:len retained for call-site compatibility.)
|
|
@@ -987,14 +992,7 @@ module PWN
|
|
|
987
992
|
state = opts[:state]
|
|
988
993
|
request = opts[:request].to_s
|
|
989
994
|
request = state[:request].to_s if request.empty? && state.is_a?(Hash)
|
|
990
|
-
|
|
991
|
-
if state.is_a?(Hash)
|
|
992
|
-
info = active_task(state: state)
|
|
993
|
-
parts << info[:item] if info && !info[:item].to_s.empty?
|
|
994
|
-
Array(state[:plan]).each { |t| parts << t.to_s }
|
|
995
|
-
end
|
|
996
|
-
parts << request unless request.empty?
|
|
997
|
-
parts.map { |p| p.to_s.gsub(/\s+/, ' ').strip }.reject(&:empty?).uniq.join(' ')
|
|
995
|
+
request.gsub(/\s+/, ' ').strip
|
|
998
996
|
rescue StandardError
|
|
999
997
|
opts[:request].to_s
|
|
1000
998
|
end
|
|
@@ -1081,6 +1079,8 @@ module PWN
|
|
|
1081
1079
|
return true if s.empty?
|
|
1082
1080
|
return true if s.match?(/\A(?:GOAL|PLAN|REQUEST|ANSWER|FLAW|PATCH)\s*:/i)
|
|
1083
1081
|
return true if s.match?(/\A\w+\s+command\s*=/i)
|
|
1082
|
+
return true if s.match?(/\Aunderstand the request\z/i)
|
|
1083
|
+
return true if s.match?(/\Acarry out the core work(?:\s+for:.*)?\z/i)
|
|
1084
1084
|
|
|
1085
1085
|
false
|
|
1086
1086
|
rescue StandardError
|
|
@@ -1393,7 +1393,7 @@ module PWN
|
|
|
1393
1393
|
# Soft "verify the result and report" is a closer, not a test runner.
|
|
1394
1394
|
return :present if s.match?(/\bverif\w*\b/) && !s.match?(/\b(rspec|rubocop|rake|lint|spec|test)\b/)
|
|
1395
1395
|
return :mutate if s.match?(
|
|
1396
|
-
/\b(implement\w*|fix|patch\w*|chang\w*|improv\w*|write|apply|wire|refactor\w
|
|
1396
|
+
/\b(implement\w*|fix|patch\w*|chang\w*|improv\w*|write|apply|wire|refactor\w*|core work|requested duration|eligible issues)\b/
|
|
1397
1397
|
)
|
|
1398
1398
|
return :discover if s.match?(
|
|
1399
1399
|
/\b(locat\w*|find|read|inspect|recon\w*|understand|decompos\w*|map|identif\w*|gather|discover|enumerat\w*|scan|probe|determin\w*|analy[sz]e|analysis|root cause|where and why|track|navigat\w*|browse|goto)\b/
|
data/lib/pwn/version.rb
CHANGED
|
@@ -51,27 +51,28 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
|
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
it '
|
|
54
|
+
it 'does not inject English task focus as a competing user goal' do
|
|
55
55
|
src = File.read(described_class.method(:run).source_location.first)
|
|
56
56
|
expect(src).to match(/inject_task_focus!/)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
expect(
|
|
57
|
+
req = 'using hping3 what live hosts can you find in this subnet?'
|
|
58
|
+
focus = File.read(described_class.method(:inject_task_focus!).source_location.first)
|
|
59
|
+
expect(focus).to match(/original request is the only/)
|
|
60
|
+
st = { plan: ['Carry out the core work'], plan_idx: 0, original_request: req }
|
|
61
|
+
msgs = [{ role: 'user', content: req }]
|
|
62
|
+
out = described_class.send(:inject_task_focus!, state: st, messages: msgs, request: req, force: true)
|
|
63
|
+
expect(out).to eq nil
|
|
64
|
+
expect(msgs.length).to eq 1
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
it 'does not keep injecting English focus after the plan is covered' do
|
|
63
68
|
src = File.read(described_class.method(:inject_task_focus!).source_location.first)
|
|
64
|
-
|
|
65
|
-
focus ||= src
|
|
66
|
-
expect(focus).to match(/plan_open\?/)
|
|
69
|
+
expect(src).to match(/original request is the only/)
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
it 'does not tell an open English plan to stop after 3 tools just because budget is hot' do
|
|
70
73
|
src = File.read(described_class.method(:run).source_location.first)
|
|
71
74
|
expect(src).to match(/budget_exhaustion_hot\?/)
|
|
72
|
-
expect(src).to match(/
|
|
73
|
-
# local ≤3-tool abort is only for a closed/short plan, not mid-goal.
|
|
74
|
-
expect(src).to match(/english_open|plan_open\?/)
|
|
75
|
+
expect(src).to match(/original request is the completion signal/i)
|
|
75
76
|
end
|
|
76
77
|
|
|
77
78
|
it 'parks stale extra budget scars even while the host is hot' do
|
|
@@ -97,12 +98,15 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
|
|
|
97
98
|
FileUtils.remove_entry(tmp) if tmp && Dir.exist?(tmp)
|
|
98
99
|
end
|
|
99
100
|
|
|
100
|
-
it '
|
|
101
|
+
it 'ranks Registry tools from the original request, not the English plan' do
|
|
101
102
|
src = File.read(described_class.method(:run).source_location.first)
|
|
102
103
|
expect(src).to match(/TaskSummarizer\.relevance_query/)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
q = PWN::AI::Agent::TaskSummarizer.relevance_query(
|
|
105
|
+
state: { plan: ['Carry out the core work', 'Present the result'], request: 'inventory sockets' },
|
|
106
|
+
request: 'inventory sockets'
|
|
107
|
+
)
|
|
108
|
+
expect(q).to eq 'inventory sockets'
|
|
109
|
+
expect(q).not_to match(/core work/i)
|
|
106
110
|
end
|
|
107
111
|
|
|
108
112
|
it 'P17 evidence_enough does not early-final on bare success while plan open' do
|
|
@@ -885,6 +889,27 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
|
|
|
885
889
|
expect(described_class.send(:incomplete_final?, text: poc)).to eq(false)
|
|
886
890
|
end
|
|
887
891
|
|
|
892
|
+
it 'keeps an N-hour host-work request unsatisfied until that duration elapses' do
|
|
893
|
+
req = 'perform unauthenticated analysis for Critical issues for the next eight hours. Eight hours non-stop is a requirement.'
|
|
894
|
+
recap = 'Alternative executed via pwn_eval. Probed unauth GraphQL mutations; wrote graphql/alt.json. Notable: createPriorAuthSupportUploadURL still reachable.'
|
|
895
|
+
msgs = [
|
|
896
|
+
{ role: 'user', content: req },
|
|
897
|
+
{
|
|
898
|
+
role: 'assistant',
|
|
899
|
+
tool_calls: [{ function: { name: 'pwn_eval', arguments: '{"code":"1"}' } }]
|
|
900
|
+
},
|
|
901
|
+
{ role: 'tool', name: 'pwn_eval', content: '{"success":true,"result":{"value":"1"},"effect":"eval"}' },
|
|
902
|
+
{ role: 'assistant', content: recap }
|
|
903
|
+
]
|
|
904
|
+
Thread.current[:pwn_loop_t0] = Time.now
|
|
905
|
+
expect(described_class.send(:request_unsatisfied?, request: req, messages: msgs)).to eq(true)
|
|
906
|
+
expect(described_class.send(:may_finalize?, request: req, messages: msgs, text: recap)).to eq(false)
|
|
907
|
+
Thread.current[:pwn_loop_t0] = Time.now - (9 * 3600)
|
|
908
|
+
expect(described_class.send(:request_unsatisfied?, request: req, messages: msgs)).to eq(false)
|
|
909
|
+
ensure
|
|
910
|
+
Thread.current[:pwn_loop_t0] = nil
|
|
911
|
+
end
|
|
912
|
+
|
|
888
913
|
it 'forces tool_choice required on host-work before any tool result, for every engine' do
|
|
889
914
|
src = File.read(described_class.method(:run).source_location.first)
|
|
890
915
|
expect(src).to match(/tool_choice/)
|
|
@@ -416,10 +416,11 @@ describe PWN::AI::Agent::TaskSummarizer do
|
|
|
416
416
|
expect(described_class).not_to receive(:chat_for_plan)
|
|
417
417
|
tasks = described_class.plan(request: subnet_req)
|
|
418
418
|
expect(tasks).to be_a(Array)
|
|
419
|
-
expect(tasks.length).to be >=
|
|
419
|
+
expect(tasks.length).to be >= 1
|
|
420
420
|
joined = tasks.join(' | ').downcase
|
|
421
|
-
|
|
422
|
-
expect(
|
|
421
|
+
expect(joined).to match(/subnet|host|json/)
|
|
422
|
+
expect(tasks).not_to include('Understand the request')
|
|
423
|
+
expect(tasks).not_to include('Carry out the core work')
|
|
423
424
|
expect(described_class.heuristic_decompose(goal: subnet_req)).to eq(
|
|
424
425
|
described_class.fallback_decompose(goal: subnet_req)
|
|
425
426
|
)
|
|
@@ -695,11 +696,10 @@ describe PWN::AI::Agent::TaskSummarizer do
|
|
|
695
696
|
expect(described_class.tool_jargon_task?(item: 'fix the truncation bug')).to eq false
|
|
696
697
|
end
|
|
697
698
|
|
|
698
|
-
it 'relevance_query
|
|
699
|
+
it 'relevance_query is the original request, not the English plan' do
|
|
699
700
|
q = described_class.relevance_query(state: state, request: state[:request])
|
|
700
|
-
expect(q).to
|
|
701
|
-
expect(q).
|
|
702
|
-
expect(q).to include('run rspec to verify')
|
|
701
|
+
expect(q).to eq state[:request].to_s.gsub(/\s+/, ' ').strip
|
|
702
|
+
expect(q).not_to include('Carry out the core work')
|
|
703
703
|
end
|
|
704
704
|
|
|
705
705
|
it 'apply_prm_advancement! holds on +1 search streak and on -1; advances on next-task handoff' do
|
|
@@ -981,14 +981,53 @@ describe PWN::AI::Agent::TaskSummarizer do
|
|
|
981
981
|
expect(st[:plan_idx]).to eq 1
|
|
982
982
|
end
|
|
983
983
|
|
|
984
|
+
it 'does not complete Carry out the core work on three directory listings' do
|
|
985
|
+
st = described_class.fresh(request: 'perform unauthenticated analysis for eight hours')
|
|
986
|
+
st[:plan] = [
|
|
987
|
+
'Understand the request',
|
|
988
|
+
'Carry out the core work',
|
|
989
|
+
'Present the result and report completion'
|
|
990
|
+
]
|
|
991
|
+
st[:plan_idx] = 1
|
|
992
|
+
3.times do |i|
|
|
993
|
+
described_class.record!(
|
|
994
|
+
state: st,
|
|
995
|
+
name: 'shell',
|
|
996
|
+
args: { 'command' => "cat /opt/bugbounty/programs/curative/POLICY.md #{i}" },
|
|
997
|
+
result: '{"success":true,"result":{"stdout":"Curative Inc. looks forward to working with the security community to find security vulnerabilities. POLICY.md body easily over forty characters.","exit":0}}'
|
|
998
|
+
)
|
|
999
|
+
end
|
|
1000
|
+
expect(st[:plan_idx]).to eq 1
|
|
1001
|
+
end
|
|
1002
|
+
|
|
984
1003
|
it 'does not paste the full operator goal into fallback understand/carry-out tasks' do
|
|
985
1004
|
goal = 'Until we can claim credentials perform unauthenticated analysis for Critical / High severity issues ' \
|
|
986
1005
|
'eligible for submission leveraging ~/.pwn/skills for all subdomains in scope for the next eight hours'
|
|
987
1006
|
tasks = described_class.fallback_decompose(goal: goal)
|
|
988
|
-
expect(tasks.length).to be >=
|
|
1007
|
+
expect(tasks.length).to be >= 1
|
|
989
1008
|
expect(tasks.grep(/Understand the request:/)).to eq([])
|
|
990
1009
|
expect(tasks.grep(/Carry out the core work for:/)).to eq([])
|
|
991
|
-
expect(tasks
|
|
1010
|
+
expect(tasks).not_to include('Understand the request')
|
|
1011
|
+
expect(tasks).not_to include('Carry out the core work')
|
|
1012
|
+
joined = tasks.join("\n")
|
|
1013
|
+
expect(joined).to match(/unauth/i)
|
|
1014
|
+
expect(joined).to match(/critical|high/i)
|
|
1015
|
+
expect(joined).to match(/eight hours|requested duration/i)
|
|
1016
|
+
expect(joined).to match(/PoC|severity|attack chain/i)
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
it 'keeps distinctive tokens from unrelated unique goals instead of a stub plan' do
|
|
1020
|
+
[
|
|
1021
|
+
'Inventory every listening TCP socket on this host and print pid user and port',
|
|
1022
|
+
'Name every Ruby constant under PWN::AI::Agent and print them as a JSON array'
|
|
1023
|
+
].each do |goal|
|
|
1024
|
+
tasks = described_class.fallback_decompose(goal: goal)
|
|
1025
|
+
expect(tasks).not_to include('Understand the request')
|
|
1026
|
+
expect(tasks).not_to include('Carry out the core work')
|
|
1027
|
+
joined = tasks.join(' ')
|
|
1028
|
+
tokens = goal.scan(%r{[A-Za-z0-9_./-]{5,}}).uniq
|
|
1029
|
+
expect(tokens.count { |tok| joined.downcase.include?(tok.downcase) }).to be >= 2
|
|
1030
|
+
end
|
|
992
1031
|
end
|
|
993
1032
|
|
|
994
1033
|
it 'a verify task is covered after the verifier ran, even with remaining offenses' do
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -338,6 +338,7 @@
|
|
|
338
338
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.default_interactive_toolsets Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.default_interactive_toolsets`: "}]}
|
|
339
339
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.degrade_text_only Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.degrade_text_only`: "}]}
|
|
340
340
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.dispatch_fail_n Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.dispatch_fail_n`: "}]}
|
|
341
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.duration_unsatisfied? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.duration_unsatisfied?`: "}]}
|
|
341
342
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.emit_task_summary Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.emit_task_summary`: "}]}
|
|
342
343
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.engine_transient? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.engine_transient?`: "}]}
|
|
343
344
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.escalate Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.escalate`: "}]}
|
|
@@ -700,6 +701,7 @@
|
|
|
700
701
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.reject_scaffold_tasks Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.reject_scaffold_tasks`: "}]}
|
|
701
702
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.relevance_query Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.relevance_query`: Supported Method Parameters\n\nq = PWN::AI::Agent::TaskSummarizer.relevance_query(\n\nstate: 'optional - fresh() hash',\nrequest: 'optional - original user goal fallback'\n\n)\n"}]}
|
|
702
703
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.remember_brief! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.remember_brief!`: "}]}
|
|
704
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.request_clause_tasks Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.request_clause_tasks`: "}]}
|
|
703
705
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.sidecar_timeout Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.sidecar_timeout`: "}]}
|
|
704
706
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.squeeze_request_ws Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.squeeze_request_ws`: "}]}
|
|
705
707
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.task_complete_enough? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.task_complete_enough?`: "}]}
|