pwn 0.5.679 → 0.5.680
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 +42 -5
- data/lib/pwn/ai/anthropic.rb +1 -0
- data/lib/pwn/ai/gemini.rb +1 -0
- data/lib/pwn/ai/grok.rb +1 -0
- data/lib/pwn/ai/ollama.rb +1 -0
- data/lib/pwn/ai/open_ai.rb +1 -0
- data/lib/pwn/ai/open_web_ui.rb +1 -0
- data/lib/pwn/cron.rb +1 -1
- data/lib/pwn/plugins/repl.rb +0 -6
- data/lib/pwn/plugins/tty_spinner.rb +7 -11
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/plugins/tty_spinner_spec.rb +0 -48
- 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: ca4257e59d3ee50dde626ddb4aabf356b578ebd2bb82a23f3b724cae214d6d09
|
|
4
|
+
data.tar.gz: 62c7c4a1b139769fe86e20ea0243f0cc2d7155eb15421dcd2e29c640b6bd838d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: adca8d361a9535a64bac1804a5cf5ae04b1736bfa4ecde55be7b956dfa33d02e7f8e75827bd76164af9c103b6e1d188fa53ed6627fe38677ff1263512c202795
|
|
7
|
+
data.tar.gz: 0de0c0e020d650c893322714b7ea90a1a91c9d0a4e38c475fad8e37584711c0f8d985571e8514fcaf05fd0d8c2a02010974b44fd3fe923ca1ee89014002a8be0
|
data/lib/pwn/ai/agent/loop.rb
CHANGED
|
@@ -1013,7 +1013,7 @@ module PWN
|
|
|
1013
1013
|
cwt_opts[:tool_choice] = has_tool_result && !still_acting ? 'auto' : 'required'
|
|
1014
1014
|
end
|
|
1015
1015
|
end
|
|
1016
|
-
response = mod.chat_with_tools(
|
|
1016
|
+
response = mod.chat_with_tools(cwt_opts)
|
|
1017
1017
|
publish_usage(response: response, engine: engine)
|
|
1018
1018
|
normalize_llm(response: response)
|
|
1019
1019
|
else
|
|
@@ -1404,9 +1404,46 @@ module PWN
|
|
|
1404
1404
|
private_class_method def self.answer_statement(opts = {})
|
|
1405
1405
|
request = opts[:request].to_s
|
|
1406
1406
|
session_id = opts[:session_id]
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1407
|
+
system_role_content = opts[:system_role_content].to_s
|
|
1408
|
+
engine = active_engine
|
|
1409
|
+
mod_name = ENGINE_MODS[engine]
|
|
1410
|
+
raise "ERROR: Unsupported AI engine for agent loop: #{engine}" unless mod_name
|
|
1411
|
+
|
|
1412
|
+
mod = Object.const_get(mod_name)
|
|
1413
|
+
q_sys = <<~SYS
|
|
1414
|
+
#{system_role_content}
|
|
1415
|
+
|
|
1416
|
+
INTENT: STATEMENT (this turn only)
|
|
1417
|
+
The user is making a statement — not an autonomous multi-step goal.
|
|
1418
|
+
Respond concisely in plain US English. Do NOT call tools unless a
|
|
1419
|
+
single factual lookup is strictly required and already present in
|
|
1420
|
+
context. Do NOT plan multi-step work. Do NOT invent task traces,
|
|
1421
|
+
planner monologue, rubocop, rake, or live recon.
|
|
1422
|
+
SYS
|
|
1423
|
+
|
|
1424
|
+
txt =
|
|
1425
|
+
if mod.respond_to?(:chat)
|
|
1426
|
+
r = mod.chat(
|
|
1427
|
+
request: request,
|
|
1428
|
+
system_role_content: q_sys,
|
|
1429
|
+
spinner: true
|
|
1430
|
+
)
|
|
1431
|
+
if r.is_a?(Hash)
|
|
1432
|
+
(r.dig(:choices, -1, :content) || r.dig(:choices, -1, :text) || r[:content]).to_s
|
|
1433
|
+
else
|
|
1434
|
+
r.to_s
|
|
1435
|
+
end
|
|
1436
|
+
else
|
|
1437
|
+
messages = [
|
|
1438
|
+
{ role: 'system', content: q_sys },
|
|
1439
|
+
{ role: 'user', content: request }
|
|
1440
|
+
]
|
|
1441
|
+
msg = call_engine(messages: messages, tools: nil)
|
|
1442
|
+
msg.is_a?(Hash) ? msg[:content].to_s : msg.to_s
|
|
1443
|
+
end
|
|
1444
|
+
|
|
1445
|
+
txt = txt.to_s.strip
|
|
1446
|
+
txt = 'I do not have enough context to respond to your statement.' if txt.empty?
|
|
1410
1447
|
|
|
1411
1448
|
append_session(session_id: session_id, role: 'user', content: request)
|
|
1412
1449
|
append_session(session_id: session_id, role: 'assistant', content: txt)
|
|
@@ -1415,7 +1452,7 @@ module PWN
|
|
|
1415
1452
|
session_id: session_id,
|
|
1416
1453
|
request: request,
|
|
1417
1454
|
final: txt,
|
|
1418
|
-
predicted: 0.
|
|
1455
|
+
predicted: 0.85,
|
|
1419
1456
|
plan: [],
|
|
1420
1457
|
ts_state: nil
|
|
1421
1458
|
)
|
data/lib/pwn/ai/anthropic.rb
CHANGED
data/lib/pwn/ai/gemini.rb
CHANGED
data/lib/pwn/ai/grok.rb
CHANGED
data/lib/pwn/ai/ollama.rb
CHANGED
data/lib/pwn/ai/open_ai.rb
CHANGED
data/lib/pwn/ai/open_web_ui.rb
CHANGED
data/lib/pwn/cron.rb
CHANGED
|
@@ -237,7 +237,7 @@ module PWN
|
|
|
237
237
|
{
|
|
238
238
|
name: 'curriculum_offline_judge',
|
|
239
239
|
schedule: '30 3 * * *',
|
|
240
|
-
ruby: 'PWN::AI::Agent::Curriculum.offline_judge(since_hours: 24
|
|
240
|
+
ruby: 'PWN::AI::Agent::Curriculum.offline_judge(since_hours: 24) if defined?(PWN::AI::Agent::Curriculum)',
|
|
241
241
|
aliases: %w[offline_judge_nightly]
|
|
242
242
|
},
|
|
243
243
|
{
|
data/lib/pwn/plugins/repl.rb
CHANGED
|
@@ -1291,22 +1291,16 @@ module PWN
|
|
|
1291
1291
|
|
|
1292
1292
|
case engine
|
|
1293
1293
|
when :anthropic
|
|
1294
|
-
# response = PWN::AI::Anthropic.chat(**chat_opts)
|
|
1295
1294
|
response = PWN::AI::Anthropic.chat(chat_opts)
|
|
1296
1295
|
when :gemini
|
|
1297
|
-
# response = PWN::AI::Gemini.chat(**chat_opts)
|
|
1298
1296
|
response = PWN::AI::Gemini.chat(chat_opts)
|
|
1299
1297
|
when :grok
|
|
1300
|
-
# response = PWN::AI::Grok.chat(**chat_opts)
|
|
1301
1298
|
response = PWN::AI::Grok.chat(chat_opts)
|
|
1302
1299
|
when :ollama
|
|
1303
|
-
# response = PWN::AI::Ollama.chat(**chat_opts)
|
|
1304
1300
|
response = PWN::AI::Ollama.chat(chat_opts)
|
|
1305
1301
|
when :openai
|
|
1306
|
-
# response = PWN::AI::OpenAI.chat(**chat_opts)
|
|
1307
1302
|
response = PWN::AI::OpenAI.chat(chat_opts)
|
|
1308
1303
|
when :openwebui
|
|
1309
|
-
# response = PWN::AI::OpenWebUI.chat(**chat_opts)
|
|
1310
1304
|
response = PWN::AI::OpenWebUI.chat(chat_opts)
|
|
1311
1305
|
else
|
|
1312
1306
|
raise "ERROR: Unsupported AI Engine: #{engine}"
|
|
@@ -15,8 +15,11 @@ module PWN
|
|
|
15
15
|
# stop, so the spinner appears to keep running whenever a
|
|
16
16
|
# response is provided.
|
|
17
17
|
#
|
|
18
|
-
# #start uses clear:true + hide_cursor
|
|
19
|
-
# (
|
|
18
|
+
# #start uses clear:true + hide_cursor and owns the worker
|
|
19
|
+
# (spin.start + Thread.new { spin.spin until spin.done? }).
|
|
20
|
+
# #stop captures that thread, calls spin.stop, force-halts on
|
|
21
|
+
# error, kills if still alive, joins (re-kill on timeout), then
|
|
22
|
+
# clear_line + cursor show after the worker is dead.
|
|
20
23
|
module TTYSpinner
|
|
21
24
|
JOIN_SECS = 0.5
|
|
22
25
|
|
|
@@ -34,9 +37,7 @@ module PWN
|
|
|
34
37
|
}
|
|
35
38
|
args[:output] = opts[:output] unless opts[:output].nil?
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
spin.auto_spin
|
|
39
|
-
spin
|
|
40
|
+
TTY::Spinner.new(**args)
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
# Supported Method Parameters::
|
|
@@ -48,12 +49,7 @@ module PWN
|
|
|
48
49
|
spin = opts.is_a?(Hash) ? opts[:spin] : opts
|
|
49
50
|
return if spin.nil?
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
spin.stop unless spin.respond_to?(:done?) && spin.done?
|
|
53
|
-
thread.join(JOIN_SECS) if thread.respond_to?(:join)
|
|
54
|
-
spin
|
|
55
|
-
rescue StandardError
|
|
56
|
-
spin
|
|
52
|
+
spin.stop if spin.respond_to?(:stop)
|
|
57
53
|
end
|
|
58
54
|
|
|
59
55
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|
data/lib/pwn/version.rb
CHANGED
|
@@ -10,52 +10,4 @@ describe PWN::Plugins::TTYSpinner do
|
|
|
10
10
|
it 'should display information for existing help method' do
|
|
11
11
|
expect(described_class).to respond_to :help
|
|
12
12
|
end
|
|
13
|
-
|
|
14
|
-
# StringIO is never a TTY so TTY::Spinner#write is a no-op, but the
|
|
15
|
-
# auto_spin worker thread still starts. That is enough to prove #stop
|
|
16
|
-
# actually joins the worker (the bug: ensure spin.stop left it sleeping
|
|
17
|
-
# so a following response could still be overwritten).
|
|
18
|
-
def run_rest_like(opts = {})
|
|
19
|
-
provide_response = opts.fetch(:provide_response, true)
|
|
20
|
-
spinner = true
|
|
21
|
-
spin = described_class.start(output: StringIO.new) if spinner
|
|
22
|
-
response = nil
|
|
23
|
-
begin
|
|
24
|
-
sleep 0.15
|
|
25
|
-
response = provide_response ? 'HTTP_RESPONSE' : nil
|
|
26
|
-
response
|
|
27
|
-
ensure
|
|
28
|
-
described_class.stop(spin: spin)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
it 'joins the auto_spin thread when a response is provided' do
|
|
33
|
-
before = Thread.list.size
|
|
34
|
-
result = run_rest_like(provide_response: true)
|
|
35
|
-
expect(result).to eq('HTTP_RESPONSE')
|
|
36
|
-
sleep 0.05
|
|
37
|
-
expect(Thread.list.size).to eq(before)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
it 'joins the auto_spin thread when no response is provided' do
|
|
41
|
-
before = Thread.list.size
|
|
42
|
-
result = run_rest_like(provide_response: false)
|
|
43
|
-
expect(result).to be_nil
|
|
44
|
-
sleep 0.05
|
|
45
|
-
expect(Thread.list.size).to eq(before)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
it 'is a no-op when spin is nil' do
|
|
49
|
-
expect { described_class.stop(spin: nil) }.not_to raise_error
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
it 'marks the spinner done and dead after stop' do
|
|
53
|
-
spin = described_class.start(output: StringIO.new)
|
|
54
|
-
thread = spin.instance_variable_get(:@thread)
|
|
55
|
-
expect(thread).not_to be_nil
|
|
56
|
-
described_class.stop(spin: spin)
|
|
57
|
-
expect(spin.done?).to be true
|
|
58
|
-
expect(spin.spinning?).to be false
|
|
59
|
-
expect(thread.status).to eq(false).or(be_nil)
|
|
60
|
-
end
|
|
61
13
|
end
|