pwn 0.5.686 → 0.5.688
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/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/Gemfile +3 -3
- data/README.md +3 -4
- data/documentation/Agent-Tool-Registry.md +2 -2
- data/documentation/Configuration.md +5 -4
- data/documentation/General-PWN-Usage.md +1 -1
- data/documentation/Home.md +1 -1
- data/documentation/How-PWN-Works.md +1 -1
- data/documentation/Mistakes.md +1 -1
- data/documentation/Reinforcement-Learning.md +4 -6
- data/documentation/Troubleshooting.md +1 -1
- data/documentation/pwn-REPL.md +1 -1
- data/documentation/pwn-ai-Agent.md +2 -3
- data/lib/pwn/ai/agent/curriculum.rb +2 -2
- data/lib/pwn/ai/agent/dispatch.rb +1 -1
- data/lib/pwn/ai/agent/learning.rb +150 -32
- data/lib/pwn/ai/agent/loop.rb +291 -129
- data/lib/pwn/ai/agent/prompt_builder.rb +47 -29
- data/lib/pwn/ai/agent/registry.rb +2 -1
- data/lib/pwn/ai/agent/reward.rb +9 -0
- data/lib/pwn/ai/agent/swarm.rb +3 -1
- data/lib/pwn/ai/agent/task_summarizer.rb +23 -6
- data/lib/pwn/ai/agent/tool_guard.rb +109 -0
- data/lib/pwn/ai/agent/tools/ruby_eval.rb +44 -20
- data/lib/pwn/ai/agent/tools/shell.rb +17 -4
- data/lib/pwn/ai/agent/tools/skills.rb +63 -2
- data/lib/pwn/ai/anthropic.rb +25 -5
- data/lib/pwn/ai/gemini.rb +25 -5
- data/lib/pwn/ai/grok.rb +30 -13
- data/lib/pwn/ai/http_retry.rb +78 -0
- data/lib/pwn/ai/ollama.rb +25 -5
- data/lib/pwn/ai/open_ai.rb +26 -13
- data/lib/pwn/ai/open_web_ui.rb +25 -5
- data/lib/pwn/ai.rb +1 -0
- data/lib/pwn/config.rb +1 -1
- data/lib/pwn/plugins/log.rb +183 -41
- data/lib/pwn/plugins/repl.rb +50 -15
- data/lib/pwn/plugins/transparent_browser.rb +16 -2
- data/lib/pwn/sessions.rb +50 -1
- data/lib/pwn/version.rb +1 -1
- data/spec/integration/reinforced_feedback_loop_spec.rb +9 -11
- data/spec/lib/pwn/ai/agent/loop_spec.rb +180 -20
- data/spec/lib/pwn/ai/agent/prompt_builder_spec.rb +3 -0
- data/spec/lib/pwn/ai/agent/registry_spec.rb +1 -1
- data/spec/lib/pwn/ai/agent/reward_spec.rb +13 -0
- data/spec/lib/pwn/ai/agent/swarm_spec.rb +6 -0
- data/spec/lib/pwn/ai/agent/task_summarizer_spec.rb +36 -0
- data/spec/lib/pwn/ai/agent/tool_guard_spec.rb +46 -0
- data/spec/lib/pwn/ai/agent/tools/ruby_eval_spec.rb +24 -0
- data/spec/lib/pwn/ai/agent/tools/shell_spec.rb +10 -0
- data/spec/lib/pwn/ai/agent/tools/skills_spec.rb +29 -0
- data/spec/lib/pwn/ai/grok_spec.rb +22 -0
- data/spec/lib/pwn/ai/http_retry_spec.rb +55 -0
- data/spec/lib/pwn/plugins/log_spec.rb +170 -19
- data/spec/lib/pwn/plugins/repl_pwn_vault_spec.rb +14 -4
- data/spec/lib/pwn/plugins/transparent_browser_spec.rb +8 -0
- data/spec/lib/pwn/sessions_spec.rb +24 -0
- data/third_party/pwn_rdoc.jsonl +41 -5
- data/tmp_pwn_critic_skills_flaw.txt +9 -0
- metadata +11 -8
data/lib/pwn/ai/gemini.rb
CHANGED
|
@@ -46,8 +46,8 @@ module PWN
|
|
|
46
46
|
http_body = opts[:http_body]
|
|
47
47
|
http_body ||= {}
|
|
48
48
|
|
|
49
|
-
timeout = opts
|
|
50
|
-
|
|
49
|
+
timeout = PWN::AI::HttpRetry.timeout_s(opts)
|
|
50
|
+
max_attempts = PWN::AI::HttpRetry.max_attempts(opts)
|
|
51
51
|
|
|
52
52
|
spinner = opts[:spinner] || false
|
|
53
53
|
|
|
@@ -84,11 +84,31 @@ module PWN
|
|
|
84
84
|
|
|
85
85
|
response.body
|
|
86
86
|
rescue RestClient::TooManyRequests => e
|
|
87
|
-
retry_after = e.response.headers[:retry_after]&.to_i || (0.5 * (retry_count + 1))
|
|
88
|
-
sleep(retry_after + rand(0.3..5.0))
|
|
89
87
|
retry_count += 1
|
|
90
|
-
|
|
88
|
+
if retry_count >= max_attempts
|
|
89
|
+
unless opts[:quiet]
|
|
90
|
+
PWN::AI::HttpRetry.report_event(
|
|
91
|
+
label: 'gemini', which_self: self, quiet: opts[:quiet],
|
|
92
|
+
http_method: http_method, rest_call: rest_call,
|
|
93
|
+
extra: '429 retries exhausted', error: e
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
raise "Gemini API Error: #{e.message}: #{e.response}"
|
|
97
|
+
end
|
|
98
|
+
sleep(PWN::AI::HttpRetry.retry_after_s(response: e.response, retry_count: retry_count) + rand(0.3..5.0))
|
|
91
99
|
retry
|
|
100
|
+
rescue RestClient::Exceptions::Timeout => e
|
|
101
|
+
retry_count += 1
|
|
102
|
+
unless opts[:quiet]
|
|
103
|
+
PWN::AI::HttpRetry.report_event(
|
|
104
|
+
label: 'gemini', which_self: self, quiet: opts[:quiet],
|
|
105
|
+
http_method: http_method, rest_call: rest_call,
|
|
106
|
+
extra: "timeout=#{timeout}s attempt=#{retry_count}/#{max_attempts}", error: e
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
retry if retry_count < max_attempts
|
|
110
|
+
|
|
111
|
+
raise "Gemini API Error: #{e.class}: #{e.message}"
|
|
92
112
|
rescue RestClient::ExceptionWithResponse => e
|
|
93
113
|
raise "Gemini API Error: #{e.message}: #{e.response}"
|
|
94
114
|
rescue StandardError => e
|
data/lib/pwn/ai/grok.rb
CHANGED
|
@@ -341,7 +341,7 @@ module PWN
|
|
|
341
341
|
# rest_call: 'required rest call to make per the schema',
|
|
342
342
|
# params: 'optional params passed in the URI or HTTP Headers',
|
|
343
343
|
# http_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST',
|
|
344
|
-
# timeout: 'optional timeout in seconds (defaults to
|
|
344
|
+
# timeout: 'optional timeout in seconds (defaults to 180; 5 attempts)',
|
|
345
345
|
# spinner: 'optional - display spinner (defaults to false)'
|
|
346
346
|
# )
|
|
347
347
|
private_class_method def self.grok_rest_call(opts = {})
|
|
@@ -415,8 +415,8 @@ module PWN
|
|
|
415
415
|
http_body = opts[:http_body]
|
|
416
416
|
http_body ||= {}
|
|
417
417
|
|
|
418
|
-
timeout = opts
|
|
419
|
-
|
|
418
|
+
timeout = PWN::AI::HttpRetry.timeout_s(opts)
|
|
419
|
+
max_attempts = PWN::AI::HttpRetry.max_attempts(opts)
|
|
420
420
|
|
|
421
421
|
spinner = opts[:spinner] || false
|
|
422
422
|
|
|
@@ -466,18 +466,35 @@ module PWN
|
|
|
466
466
|
|
|
467
467
|
response
|
|
468
468
|
rescue RestClient::TooManyRequests => e
|
|
469
|
-
retry_after = e.response.headers[:retry_after]&.to_i ||= (0.5 * (retry_count + 1))
|
|
470
|
-
sleep(retry_after + rand(0.3..5.0))
|
|
471
469
|
retry_count += 1
|
|
472
|
-
|
|
470
|
+
if retry_count >= max_attempts
|
|
471
|
+
unless opts[:quiet]
|
|
472
|
+
PWN::AI::HttpRetry.report_event(
|
|
473
|
+
label: 'grok', which_self: self, quiet: opts[:quiet],
|
|
474
|
+
http_method: http_method, rest_call: rest_call,
|
|
475
|
+
extra: '429 retries exhausted', error: e
|
|
476
|
+
)
|
|
477
|
+
end
|
|
478
|
+
return "#{e.message}: #{e.response}"
|
|
479
|
+
end
|
|
480
|
+
sleep(PWN::AI::HttpRetry.retry_after_s(response: e.response, retry_count: retry_count) + rand(0.3..5.0))
|
|
473
481
|
retry
|
|
482
|
+
rescue RestClient::Exceptions::Timeout => e
|
|
483
|
+
# Sidecar hops (judge / kind / plan) pass quiet:true and a short
|
|
484
|
+
# timeout. Do not print ERROR: Timed out reading data from server:
|
|
485
|
+
# — ReadTimeout is ExceptionWithResponse with a nil body.
|
|
486
|
+
retry_count += 1
|
|
487
|
+
unless opts[:quiet]
|
|
488
|
+
PWN::AI::HttpRetry.report_event(
|
|
489
|
+
label: 'grok', which_self: self, quiet: opts[:quiet],
|
|
490
|
+
http_method: http_method, rest_call: rest_call,
|
|
491
|
+
extra: "timeout=#{timeout}s attempt=#{retry_count}/#{max_attempts}", error: e
|
|
492
|
+
)
|
|
493
|
+
end
|
|
494
|
+
retry if retry_count < max_attempts
|
|
495
|
+
|
|
496
|
+
nil
|
|
474
497
|
end
|
|
475
|
-
rescue RestClient::Exceptions::Timeout => e
|
|
476
|
-
# Sidecar hops (judge / kind / plan) pass quiet:true and a short
|
|
477
|
-
# timeout. Do not print ERROR: Timed out reading data from server:
|
|
478
|
-
# — ReadTimeout is ExceptionWithResponse with a nil body.
|
|
479
|
-
warn "[pwn-ai/grok] #{e.class}: #{e.message} (#{http_method.to_s.upcase} #{rest_call} timeout=#{timeout}s)" unless opts[:quiet]
|
|
480
|
-
nil
|
|
481
498
|
rescue RestClient::ExceptionWithResponse => e
|
|
482
499
|
puts "ERROR: #{e.message}: #{e.response}" unless opts[:quiet]
|
|
483
500
|
"#{e.message}: #{e.response}" if opts[:quiet]
|
|
@@ -512,7 +529,7 @@ module PWN
|
|
|
512
529
|
# tool_choice: 'optional - "auto" | "none" | "required" | {type:"function", function:{name:..}}',
|
|
513
530
|
# model: 'optional - overrides PWN::Env[:ai][:grok][:model]',
|
|
514
531
|
# temp: 'optional - temperature (defaults to PWN::Env[:ai][:grok][:temp] || 1)',
|
|
515
|
-
# timeout: 'optional - seconds (default
|
|
532
|
+
# timeout: 'optional - seconds (default 180)',
|
|
516
533
|
# spinner: 'optional - display spinner (default false)'
|
|
517
534
|
# )
|
|
518
535
|
#
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PWN
|
|
4
|
+
module AI
|
|
5
|
+
# Shared REST timeout / 429 / ReadTimeout policy for every AI provider.
|
|
6
|
+
# Default wall clock per attempt is 180s with up to 5 attempts (≈900s total).
|
|
7
|
+
# Short quiet sidecar hops stay single-shot.
|
|
8
|
+
module HttpRetry
|
|
9
|
+
DEFAULT_TIMEOUT_S = 180
|
|
10
|
+
DEFAULT_MAX_ATTEMPTS = 5
|
|
11
|
+
|
|
12
|
+
public_class_method def self.timeout_s(opts = {})
|
|
13
|
+
return DEFAULT_TIMEOUT_S unless opts.is_a?(Hash)
|
|
14
|
+
|
|
15
|
+
t = opts[:timeout].to_i
|
|
16
|
+
t.positive? ? t : DEFAULT_TIMEOUT_S
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
public_class_method def self.max_attempts(opts = {})
|
|
20
|
+
return 1 unless opts.is_a?(Hash)
|
|
21
|
+
|
|
22
|
+
n = opts[:max_attempts].to_i
|
|
23
|
+
return n if n.positive?
|
|
24
|
+
return 1 if opts[:quiet]
|
|
25
|
+
return 1 if opts[:timeout].to_i.positive? && opts[:timeout].to_i < DEFAULT_TIMEOUT_S
|
|
26
|
+
|
|
27
|
+
DEFAULT_MAX_ATTEMPTS
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
public_class_method def self.retry_after_s(opts = {})
|
|
31
|
+
retry_count = [opts[:retry_count].to_i, 1].max
|
|
32
|
+
retry_after = 0
|
|
33
|
+
resp = opts[:response]
|
|
34
|
+
retry_after = resp.headers[:retry_after].to_i if resp.respond_to?(:headers) && resp.headers
|
|
35
|
+
retry_after = (0.5 * retry_count) if retry_after.to_f <= 0
|
|
36
|
+
retry_after
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Tees provider REST events into the open pwn-ai DEBUG RN log and STDERR.
|
|
40
|
+
public_class_method def self.report_event(opts = {})
|
|
41
|
+
return unless opts.is_a?(Hash)
|
|
42
|
+
|
|
43
|
+
label = opts[:label].to_s
|
|
44
|
+
label = 'ai' if label.empty?
|
|
45
|
+
err = opts[:error]
|
|
46
|
+
extra = opts[:extra].to_s
|
|
47
|
+
meth = opts[:http_method].to_s.upcase
|
|
48
|
+
call = opts[:rest_call].to_s
|
|
49
|
+
klass = err ? err.class : 'Error'
|
|
50
|
+
msg = err ? err.message : extra
|
|
51
|
+
line = "[pwn-ai/#{label}] #{klass}: #{msg} (#{meth} #{call} #{extra})".strip
|
|
52
|
+
which = opts[:which_self] || self
|
|
53
|
+
PWN::Plugins::Log.progress(msg: line, which_self: which, cap: 0) if defined?(PWN::Plugins::Log) && PWN::Plugins::Log.debug_enabled?
|
|
54
|
+
warn line unless opts[:quiet]
|
|
55
|
+
line
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
public_class_method def self.authors
|
|
59
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
public_class_method def self.help
|
|
63
|
+
puts <<~USAGE
|
|
64
|
+
USAGE:
|
|
65
|
+
PWN::AI::HttpRetry.timeout_s(timeout: nil) # => 180
|
|
66
|
+
PWN::AI::HttpRetry.max_attempts(quiet: false) # => 5
|
|
67
|
+
PWN::AI::HttpRetry.report_event(
|
|
68
|
+
label: 'grok',
|
|
69
|
+
error: e,
|
|
70
|
+
http_method: :post,
|
|
71
|
+
rest_call: 'chat/completions'
|
|
72
|
+
)
|
|
73
|
+
#{self}.authors
|
|
74
|
+
USAGE
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/pwn/ai/ollama.rb
CHANGED
|
@@ -65,8 +65,8 @@ module PWN
|
|
|
65
65
|
http_body = opts[:http_body]
|
|
66
66
|
http_body ||= {}
|
|
67
67
|
|
|
68
|
-
timeout = opts
|
|
69
|
-
|
|
68
|
+
timeout = PWN::AI::HttpRetry.timeout_s(opts)
|
|
69
|
+
max_attempts = PWN::AI::HttpRetry.max_attempts(opts)
|
|
70
70
|
|
|
71
71
|
spinner = opts[:spinner] || false
|
|
72
72
|
stream = http_body.is_a?(Hash) && http_body[:stream] == true
|
|
@@ -173,11 +173,31 @@ module PWN
|
|
|
173
173
|
end
|
|
174
174
|
response
|
|
175
175
|
rescue RestClient::TooManyRequests => e
|
|
176
|
-
retry_after = e.response.headers[:retry_after]&.to_i ||= (0.5 * (retry_count + 1))
|
|
177
|
-
sleep(retry_after + rand(0.3..5.0))
|
|
178
176
|
retry_count += 1
|
|
179
|
-
|
|
177
|
+
if retry_count >= max_attempts
|
|
178
|
+
unless opts[:quiet]
|
|
179
|
+
PWN::AI::HttpRetry.report_event(
|
|
180
|
+
label: 'ollama', which_self: self, quiet: opts[:quiet],
|
|
181
|
+
http_method: http_method, rest_call: rest_call,
|
|
182
|
+
extra: '429 retries exhausted', error: e
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
return "#{e.message}: #{e.response}"
|
|
186
|
+
end
|
|
187
|
+
sleep(PWN::AI::HttpRetry.retry_after_s(response: e.response, retry_count: retry_count) + rand(0.3..5.0))
|
|
180
188
|
retry
|
|
189
|
+
rescue RestClient::Exceptions::Timeout => e
|
|
190
|
+
retry_count += 1
|
|
191
|
+
unless opts[:quiet]
|
|
192
|
+
PWN::AI::HttpRetry.report_event(
|
|
193
|
+
label: 'ollama', which_self: self, quiet: opts[:quiet],
|
|
194
|
+
http_method: http_method, rest_call: rest_call,
|
|
195
|
+
extra: "timeout=#{timeout}s attempt=#{retry_count}/#{max_attempts}", error: e
|
|
196
|
+
)
|
|
197
|
+
end
|
|
198
|
+
retry if retry_count < max_attempts
|
|
199
|
+
|
|
200
|
+
nil
|
|
181
201
|
end
|
|
182
202
|
rescue RestClient::ExceptionWithResponse => e
|
|
183
203
|
body = begin
|
data/lib/pwn/ai/open_ai.rb
CHANGED
|
@@ -451,8 +451,8 @@ module PWN
|
|
|
451
451
|
http_body = opts[:http_body]
|
|
452
452
|
http_body ||= {}
|
|
453
453
|
|
|
454
|
-
timeout = opts
|
|
455
|
-
|
|
454
|
+
timeout = PWN::AI::HttpRetry.timeout_s(opts)
|
|
455
|
+
max_attempts = PWN::AI::HttpRetry.max_attempts(opts)
|
|
456
456
|
|
|
457
457
|
spinner = opts[:spinner] || false
|
|
458
458
|
|
|
@@ -502,21 +502,34 @@ module PWN
|
|
|
502
502
|
end
|
|
503
503
|
response
|
|
504
504
|
rescue RestClient::TooManyRequests => e
|
|
505
|
-
|
|
506
|
-
if
|
|
507
|
-
|
|
508
|
-
|
|
505
|
+
retry_count += 1
|
|
506
|
+
if retry_count >= max_attempts
|
|
507
|
+
unless opts[:quiet]
|
|
508
|
+
PWN::AI::HttpRetry.report_event(
|
|
509
|
+
label: 'openai', which_self: self, quiet: opts[:quiet],
|
|
510
|
+
http_method: http_method, rest_call: rest_call,
|
|
511
|
+
extra: '429 retries exhausted', error: e
|
|
512
|
+
)
|
|
513
|
+
end
|
|
514
|
+
return "#{e.message}: #{e.response}"
|
|
509
515
|
end
|
|
510
|
-
sleep(
|
|
516
|
+
sleep(PWN::AI::HttpRetry.retry_after_s(response: e.response, retry_count: retry_count) + rand(0.3..5.0))
|
|
517
|
+
retry
|
|
518
|
+
rescue RestClient::Exceptions::Timeout => e
|
|
519
|
+
# Sidecar hops pass quiet:true. Never print
|
|
520
|
+
# ERROR: Timed out reading data from server:
|
|
511
521
|
retry_count += 1
|
|
522
|
+
unless opts[:quiet]
|
|
523
|
+
PWN::AI::HttpRetry.report_event(
|
|
524
|
+
label: 'openai', which_self: self, quiet: opts[:quiet],
|
|
525
|
+
http_method: http_method, rest_call: rest_call,
|
|
526
|
+
extra: "timeout=#{timeout}s attempt=#{retry_count}/#{max_attempts}", error: e
|
|
527
|
+
)
|
|
528
|
+
end
|
|
529
|
+
retry if retry_count < max_attempts
|
|
512
530
|
|
|
513
|
-
|
|
531
|
+
nil
|
|
514
532
|
end
|
|
515
|
-
rescue RestClient::Exceptions::Timeout => e
|
|
516
|
-
# Sidecar hops pass quiet:true. Never print
|
|
517
|
-
# ERROR: Timed out reading data from server:
|
|
518
|
-
warn "[pwn-ai/openai] #{e.class}: #{e.message} (#{http_method.to_s.upcase} #{rest_call} timeout=#{timeout}s)" unless opts[:quiet]
|
|
519
|
-
nil
|
|
520
533
|
rescue RestClient::ExceptionWithResponse => e
|
|
521
534
|
puts "ERROR: #{e.message}: #{e.response}" unless opts[:quiet]
|
|
522
535
|
"#{e.message}: #{e.response}" if opts[:quiet]
|
data/lib/pwn/ai/open_web_ui.rb
CHANGED
|
@@ -77,8 +77,8 @@ module PWN
|
|
|
77
77
|
http_body = opts[:http_body]
|
|
78
78
|
http_body ||= {}
|
|
79
79
|
|
|
80
|
-
timeout = opts
|
|
81
|
-
|
|
80
|
+
timeout = PWN::AI::HttpRetry.timeout_s(opts)
|
|
81
|
+
max_attempts = PWN::AI::HttpRetry.max_attempts(opts)
|
|
82
82
|
|
|
83
83
|
spinner = opts[:spinner] || false
|
|
84
84
|
stream = http_body.is_a?(Hash) && http_body[:stream] == true
|
|
@@ -198,11 +198,31 @@ module PWN
|
|
|
198
198
|
|
|
199
199
|
response
|
|
200
200
|
rescue RestClient::TooManyRequests => e
|
|
201
|
-
retry_after = e.response.headers[:retry_after]&.to_i ||= (0.5 * (retry_count + 1))
|
|
202
|
-
sleep(retry_after + rand(0.3..5.0))
|
|
203
201
|
retry_count += 1
|
|
204
|
-
|
|
202
|
+
if retry_count >= max_attempts
|
|
203
|
+
unless opts[:quiet]
|
|
204
|
+
PWN::AI::HttpRetry.report_event(
|
|
205
|
+
label: 'openwebui', which_self: self, quiet: opts[:quiet],
|
|
206
|
+
http_method: http_method, rest_call: rest_call,
|
|
207
|
+
extra: '429 retries exhausted', error: e
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
return "#{e.message}: #{e.response}"
|
|
211
|
+
end
|
|
212
|
+
sleep(PWN::AI::HttpRetry.retry_after_s(response: e.response, retry_count: retry_count) + rand(0.3..5.0))
|
|
205
213
|
retry
|
|
214
|
+
rescue RestClient::Exceptions::Timeout => e
|
|
215
|
+
retry_count += 1
|
|
216
|
+
unless opts[:quiet]
|
|
217
|
+
PWN::AI::HttpRetry.report_event(
|
|
218
|
+
label: 'openwebui', which_self: self, quiet: opts[:quiet],
|
|
219
|
+
http_method: http_method, rest_call: rest_call,
|
|
220
|
+
extra: "timeout=#{timeout}s attempt=#{retry_count}/#{max_attempts}", error: e
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
retry if retry_count < max_attempts
|
|
224
|
+
|
|
225
|
+
nil
|
|
206
226
|
end
|
|
207
227
|
rescue RestClient::ExceptionWithResponse => e
|
|
208
228
|
# Never return nil here — chat_with_tools used to `return nil if response.nil?`
|
data/lib/pwn/ai.rb
CHANGED
|
@@ -9,6 +9,7 @@ module PWN
|
|
|
9
9
|
autoload :Anthropic, 'pwn/ai/anthropic'
|
|
10
10
|
autoload :Gemini, 'pwn/ai/gemini'
|
|
11
11
|
autoload :Grok, 'pwn/ai/grok'
|
|
12
|
+
autoload :HttpRetry, 'pwn/ai/http_retry'
|
|
12
13
|
autoload :Ollama, 'pwn/ai/ollama'
|
|
13
14
|
autoload :OpenAI, 'pwn/ai/open_ai'
|
|
14
15
|
autoload :OpenWebUI, 'pwn/ai/open_web_ui'
|
data/lib/pwn/config.rb
CHANGED
|
@@ -160,7 +160,7 @@ module PWN
|
|
|
160
160
|
# LLM tangible-task decomposition for autonomous goals (default on).
|
|
161
161
|
task_summary_llm: nil,
|
|
162
162
|
tool_router: nil, # nil = auto (true when :active is local :ollama/:openwebui) — cuts ~11k→~3k schema tokens
|
|
163
|
-
tool_preference: %w[memory_recall session_recall skills_recall pwn_eval shell mistakes_record mistakes_resolve learning_note_outcome memory_remember],
|
|
163
|
+
tool_preference: %w[memory_recall session_recall skills_recall pwn_eval shell mistakes_record mistakes_resolve learning_note_outcome memory_remember skills_update],
|
|
164
164
|
escalation_persona: 'escalator', # Swarm persona for frontier corrective hints when a local model is stuck
|
|
165
165
|
# sample E3 verify_as_reward: true|false|nil(auto: ~10% local / always frontier when CLAIM_RX hits)
|
|
166
166
|
verify_as_reward: nil,
|