pwn 0.5.697 → 0.5.698

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4218feff0995079766c3b31b6beec85c9b45b1f971ac0bc78a9794c67ea8016e
4
- data.tar.gz: 5a47207b366764a60b8c560bdcf77c5b12288d7fdbeb3e2d74c655a8e92e12e3
3
+ metadata.gz: c333795b07ea88fb7ea338e6b01faf768c62bd07757328fc1698972d43a6c5e8
4
+ data.tar.gz: 0cfbe454002f7ffc79571162a96367aaf3d69017d997cb024c4a475ac3bbab7b
5
5
  SHA512:
6
- metadata.gz: 326c061669305b7069c4a66e68d958b6935ef3a9c3f90e02ee1242ad6bedfed9e5e31170fd4ae8b7b9570ab7c63fb1e7198c0cc97a5a8e3eeb96400bc329c611
7
- data.tar.gz: ed8cdb221e58fdb00ae799d902121e0f2b6f629871ceb458a54ef35ee5e2ad326070c431d9c17346db442e0d62374f90f85cba074a00967d3193d41753a091a7
6
+ metadata.gz: 7a699a8308cb00f04bdf85869647ec381009daf04fe4d8c972c62d5ed57b819b4b719672fe4ad64966c316520a2496bdeb4914c002f526e886b877f7b5d11928
7
+ data.tar.gz: 5b2d1fa1f979bc4852304ed09b7122817a71689114f4c4f6a1a46798b01e9d3bb186b8af28aa8d08288c470b01d323735fb252ccc9c45426835d8b717ca8e382
@@ -35,6 +35,7 @@ PWN::AI::HttpRetry.timeout_s(opts)
35
35
 
36
36
  - `timeout_s`
37
37
  - `max_attempts`
38
+ - `retryable`
38
39
  - `retry_after_s`
39
40
  - `report_event`
40
41
  - `authors`
@@ -851,6 +851,17 @@ module PWN
851
851
  opts[:session_id].to_s
852
852
  end
853
853
 
854
+ private_class_method def self.engine_transient?(opts = {})
855
+ err = opts[:error]
856
+ return false unless err
857
+
858
+ return PWN::AI::HttpRetry.retryable?(error: err) if defined?(PWN::AI::HttpRetry) && PWN::AI::HttpRetry.respond_to?(:retryable?)
859
+
860
+ err.message.to_s.match?(/HTTP 50[234]|Gateway Time-out|stream absolute timeout/i)
861
+ rescue StandardError
862
+ false
863
+ end
864
+
854
865
  # E1 — did the environment change under this tool? If Metrics CUSUM
855
866
  # tripped for it in the last hour AND Extrospection.drift shows a
856
867
  # toolchain/net/repo change, blame the WORLD not the AGENT.
@@ -2473,6 +2484,7 @@ module PWN
2473
2484
 
2474
2485
  turn_fails = Hash.new(0)
2475
2486
  escalated = false
2487
+ engine_blips = 0
2476
2488
  maybe_park_budget_scars!
2477
2489
  maybe_extinguish_parked!
2478
2490
 
@@ -2486,7 +2498,27 @@ module PWN
2486
2498
  inject_task_focus!(messages: messages, state: ts_state, request: request) unless skip_compass
2487
2499
 
2488
2500
  t0 = Time.now
2489
- msg = call_engine(messages: messages, tools: tools, ts_state: ts_state)
2501
+ begin
2502
+ msg = call_engine(messages: messages, tools: tools, ts_state: ts_state)
2503
+ rescue StandardError => e
2504
+ if engine_transient?(error: e)
2505
+ engine_blips += 1
2506
+ debug_progress(msg: "engine hop failed #{e.class}: #{e.message.to_s[0, 240]} blip=#{engine_blips}")
2507
+ if engine_blips >= 5
2508
+ txt = "[pwn-ai] engine hop failed after #{engine_blips} tries: #{e.message.to_s[0, 240]}"
2509
+ debug_final_text!(text: txt)
2510
+ final_chars = txt.length
2511
+ return txt
2512
+ end
2513
+ messages << {
2514
+ role: 'user',
2515
+ content: '[pwn-ai] engine hop failed (transient). Keep calling CORE_TOOLS. ' \
2516
+ "Do not stop. (#{e.message.to_s[0, 180]})"
2517
+ }
2518
+ next
2519
+ end
2520
+ raise
2521
+ end
2490
2522
  engine_s += (Time.now - t0)
2491
2523
  PWN::Plugins::TTYSpinner.halt_all! if defined?(PWN::Plugins::TTYSpinner)
2492
2524
  if msg.nil?
@@ -573,8 +573,8 @@ module PWN
573
573
  # model cannot stall the user's real answer, and stay quiet on timeout.
574
574
  private_class_method def self.sidecar_timeout
575
575
  n = (PWN::Env.dig(:ai, :agent, :sidecar_llm_timeout) if defined?(PWN::Env)).to_i
576
- n = 20 if n < 5
577
- n = 60 if n > 60
576
+ n = 180 if n < 30
577
+ n = 180 if n > 180
578
578
  n
579
579
  rescue StandardError
580
580
  20
@@ -27,6 +27,15 @@ module PWN
27
27
  DEFAULT_MAX_ATTEMPTS
28
28
  end
29
29
 
30
+ public_class_method def self.retryable?(opts = {})
31
+ err = opts[:error]
32
+ msg = err.respond_to?(:message) ? err.message.to_s : err.to_s
33
+ msg = opts[:message].to_s if msg.empty?
34
+ msg.match?(/HTTP 50[234]|Gateway Time-out|stream absolute timeout|ReadTimeout|Net::ReadTimeout|Timed out reading/i)
35
+ rescue StandardError
36
+ false
37
+ end
38
+
30
39
  public_class_method def self.retry_after_s(opts = {})
31
40
  retry_count = [opts[:retry_count].to_i, 1].max
32
41
  retry_after = 0
data/lib/pwn/ai/ollama.rb CHANGED
@@ -198,6 +198,21 @@ module PWN
198
198
  retry if retry_count < max_attempts
199
199
 
200
200
  nil
201
+ rescue StandardError => e
202
+ if PWN::AI::HttpRetry.retryable?(error: e) && retry_count + 1 < max_attempts
203
+ retry_count += 1
204
+ unless opts[:quiet]
205
+ PWN::AI::HttpRetry.report_event(
206
+ label: 'ollama', which_self: self, quiet: opts[:quiet],
207
+ http_method: http_method, rest_call: rest_call,
208
+ extra: "retryable attempt=#{retry_count}/#{max_attempts}",
209
+ error: e
210
+ )
211
+ end
212
+ sleep(PWN::AI::HttpRetry.retry_after_s(retry_count: retry_count) + rand(0.3..2.0))
213
+ retry
214
+ end
215
+ raise e
201
216
  end
202
217
  rescue RestClient::ExceptionWithResponse => e
203
218
  body = begin
@@ -1284,8 +1284,9 @@ module PWN
1284
1284
  raise
1285
1285
  rescue StandardError => e
1286
1286
  PWN::Plugins::Log.note_exception!(error: e, where: 'native agent loop', which_self: PWN::Plugins::REPL) if defined?(PWN::Plugins::Log) && PWN::Plugins::Log.respond_to?(:note_exception!)
1287
- warn "[pwn-ai] native agent loop failed (#{e.class}: #{e.message}\n#{e.backtrace}); " \
1288
- 'falling back to legacy regex-ReAct.'
1287
+ warn "[pwn-ai] native agent loop failed (#{e.class}: #{e.message.to_s.split("\n").first})"
1288
+ request.replace('nil')
1289
+ next
1289
1290
  ensure
1290
1291
  PWN::Plugins::REPL.ready_tty!
1291
1292
  end
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.697'
4
+ VERSION = '0.5.698'
5
5
  end
@@ -424,9 +424,16 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
424
424
  expect(src).to match(/note_exception!/)
425
425
  expect(src).to match(/rescue StandardError/)
426
426
  expect(src).to include("debug_progress(msg: 'engine returned no message')")
427
+ expect(src).to match(/engine_transient|retryable\?/)
427
428
  expect(src).not_to match(/dispatch #\{name\} ok_len=/)
428
429
  end
429
430
 
431
+ it 'keeps Loop.run going after a transient engine 504' do
432
+ src = File.read(described_class.method(:run).source_location.first)
433
+ expect(src).to match(/engine_transient\?|HttpRetry\.retryable\?/)
434
+ expect(src).to match(/engine hop failed|engine_blip/)
435
+ end
436
+
430
437
  it 'records a timeout increment mistake instead of treating success:true as ok' do
431
438
  tmp = Dir.mktmpdir
432
439
  stub_const('PWN::AI::Agent::Mistakes::MISTAKES_FILE', File.join(tmp, 'mistakes.json'))
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'spec_helper'
4
4
  require 'stringio'
5
+ require 'rest-client'
5
6
 
6
7
  describe PWN::AI::HttpRetry do
7
8
  it 'defaults timeout to 180 and max_attempts to 5' do
@@ -14,6 +15,12 @@ describe PWN::AI::HttpRetry do
14
15
  expect(described_class.max_attempts(max_attempts: 3)).to eq(3)
15
16
  end
16
17
 
18
+ it 'treats gateway 504 and stream absolute timeouts as retryable' do
19
+ expect(described_class.retryable?(error: 'ERROR: Ollama HTTP 504 for api/chat: Gateway Time-out')).to eq(true)
20
+ expect(described_class.retryable?(error: 'ERROR: Ollama stream absolute timeout after 20s for api/chat')).to eq(true)
21
+ expect(described_class.retryable?(error: '400 Bad Request')).to eq(false)
22
+ end
23
+
17
24
  it 'tees emit lines into the open debug request log' do
18
25
  path = PWN::Plugins::Log.start_debug(
19
26
  tee: StringIO.new,
@@ -50,6 +57,7 @@ describe PWN::AI::HttpRetry do
50
57
  expect(src).not_to match(/timeout \|\|= 900/)
51
58
  expect(src).to match(/Exceptions::Timeout/)
52
59
  expect(src).to match(/HttpRetry\.report_event/)
60
+ expect(src).to match(/retryable\?|HTTP 50/) if name == 'PWN::AI::Ollama'
53
61
  end
54
62
  end
55
63
  end
@@ -174,4 +174,14 @@ describe 'pwn-ai debug final answer placement' do
174
174
  expect(hook).to include('→ pwn-ai →')
175
175
  expect(hook).to include('→ result')
176
176
  end
177
+
178
+ it 'does not fall through to regex-ReAct after a native Loop.run error' do
179
+ native_start = hook.index('final = PWN::AI::Agent::Loop.run')
180
+ rescue_at = hook.index('rescue StandardError', native_start)
181
+ expect(rescue_at).not_to be_nil
182
+ chunk = hook[rescue_at, 900]
183
+ expect(chunk).to match(/request\.replace\('nil'\)/)
184
+ expect(chunk).to match(/\bnext\b/)
185
+ expect(chunk).not_to include('legacy regex-ReAct')
186
+ end
177
187
  end
@@ -339,6 +339,7 @@
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
341
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.emit_task_summary Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.emit_task_summary`: "}]}
342
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.engine_transient? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.engine_transient?`: "}]}
342
343
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.escalate Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.escalate`: "}]}
343
344
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.evidence_enough_to_finalize? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.evidence_enough_to_finalize?`: "}]}
344
345
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.expose_current_session Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.expose_current_session`: "}]}
@@ -793,6 +794,7 @@
793
794
  {"messages":[{"role":"user","content":"PWN::AI::HttpRetry.max_attempts Usage"},{"role":"assistant","content":"`PWN::AI::HttpRetry.max_attempts`: "}]}
794
795
  {"messages":[{"role":"user","content":"PWN::AI::HttpRetry.report_event Usage"},{"role":"assistant","content":"`PWN::AI::HttpRetry.report_event`: "}]}
795
796
  {"messages":[{"role":"user","content":"PWN::AI::HttpRetry.retry_after_s Usage"},{"role":"assistant","content":"`PWN::AI::HttpRetry.retry_after_s`: "}]}
797
+ {"messages":[{"role":"user","content":"PWN::AI::HttpRetry.retryable? Usage"},{"role":"assistant","content":"`PWN::AI::HttpRetry.retryable?`: "}]}
796
798
  {"messages":[{"role":"user","content":"PWN::AI::HttpRetry.timeout_s Usage"},{"role":"assistant","content":"`PWN::AI::HttpRetry.timeout_s`: "}]}
797
799
  {"messages":[{"role":"user","content":"PWN::AI::Ollama.assemble_native_chat_stream Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.assemble_native_chat_stream`: "}]}
798
800
  {"messages":[{"role":"user","content":"PWN::AI::Ollama.assemble_ollama_stream Usage"},{"role":"assistant","content":"`PWN::AI::Ollama.assemble_ollama_stream`: "}]}
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.697
4
+ version: 0.5.698
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.