pwn 0.5.685 → 0.5.686
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/documentation/General-PWN-Usage.md +1 -1
- data/documentation/pwn-REPL.md +1 -1
- data/lib/pwn/ai/agent/loop.rb +76 -13
- data/lib/pwn/ai/agent/prompt_builder.rb +3 -0
- data/lib/pwn/ai/anthropic.rb +0 -118
- data/lib/pwn/ai/gemini.rb +0 -15
- data/lib/pwn/ai/grok.rb +0 -158
- data/lib/pwn/ai/ollama.rb +0 -12
- data/lib/pwn/ai/open_ai.rb +0 -110
- data/lib/pwn/ai/open_web_ui.rb +0 -12
- data/lib/pwn/ai.rb +0 -86
- data/lib/pwn/plugins/log.rb +302 -0
- data/lib/pwn/plugins/repl.rb +28 -8
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/loop_spec.rb +43 -5
- data/spec/lib/pwn/ai/anthropic_spec.rb +2 -2
- data/spec/lib/pwn/ai/gemini_spec.rb +2 -2
- data/spec/lib/pwn/ai/grok_spec.rb +2 -2
- data/spec/lib/pwn/ai/ollama_spec.rb +2 -7
- data/spec/lib/pwn/ai/open_ai_spec.rb +2 -23
- data/spec/lib/pwn/ai/open_web_ui_spec.rb +2 -7
- data/spec/lib/pwn/ai_spec.rb +4 -21
- data/spec/lib/pwn/plugins/log_spec.rb +139 -0
- data/spec/lib/pwn/plugins/repl_pwn_vault_spec.rb +31 -0
- data/spec/lib/pwn/plugins/repl_spec.rb +4 -3
- data/third_party/pwn_rdoc.jsonl +26 -20
- metadata +1 -1
|
@@ -633,12 +633,42 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
|
|
|
633
633
|
expect(described_class).not_to respond_to :request_kind
|
|
634
634
|
end
|
|
635
635
|
|
|
636
|
-
it '
|
|
636
|
+
it 'does not invent a request type for world-knowledge; the full loop still accepts a text final' do
|
|
637
|
+
expect(described_class.world_knowledge?(request: 'what color is a passion fruit?')).to eq true
|
|
637
638
|
src = File.read(described_class.method(:run).source_location.first)
|
|
638
|
-
expect(src).not_to match(/
|
|
639
|
-
expect(src).not_to match(/
|
|
640
|
-
expect(src).
|
|
641
|
-
|
|
639
|
+
expect(src).not_to match(/request_kind/)
|
|
640
|
+
expect(src).not_to match(/world_knowledge\?.*?return answer_question/m)
|
|
641
|
+
expect(src).to match(/world_knowledge\?/)
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
it 'full tool loop accepts a text final for world-knowledge with no host-work tools' do
|
|
645
|
+
expect(described_class.world_knowledge?(request: 'what color is a passion fruit?')).to eq true
|
|
646
|
+
expect(described_class.send(:request_need, request: 'what color is a passion fruit?')).to eq :none
|
|
647
|
+
expect(
|
|
648
|
+
described_class.send(
|
|
649
|
+
:request_unsatisfied?,
|
|
650
|
+
request: 'what color is a passion fruit?',
|
|
651
|
+
messages: [{ role: 'assistant', content: 'Purple when ripe.', tool_calls: [] }]
|
|
652
|
+
)
|
|
653
|
+
).to eq false
|
|
654
|
+
sess_tmp = Dir.mktmpdir('pwn-sess-fruit')
|
|
655
|
+
stub_const('PWN::Sessions::SESSIONS_DIR', sess_tmp)
|
|
656
|
+
sid = 'fruit_spec'
|
|
657
|
+
PWN::Sessions.create(id: sid, title: 'fruit')
|
|
658
|
+
allow(described_class).to receive(:should_auto_introspect?).and_return(false)
|
|
659
|
+
allow(described_class).to receive(:plan_first).and_raise('plan_first must not run on world-knowledge')
|
|
660
|
+
allow(PWN::AI::Agent::TaskSummarizer).to receive(:enabled?).and_return(false) if defined?(PWN::AI::Agent::TaskSummarizer)
|
|
661
|
+
allow(described_class).to receive(:call_engine).and_return(
|
|
662
|
+
{ role: 'assistant', content: 'Purple when ripe.', tool_calls: [] }
|
|
663
|
+
)
|
|
664
|
+
out = described_class.run(
|
|
665
|
+
request: 'what color is a passion fruit?',
|
|
666
|
+
session_id: sid,
|
|
667
|
+
system_role_content: 'test system'
|
|
668
|
+
)
|
|
669
|
+
expect(out).to include('Purple when ripe.')
|
|
670
|
+
ensure
|
|
671
|
+
FileUtils.rm_rf(sess_tmp) if sess_tmp
|
|
642
672
|
end
|
|
643
673
|
|
|
644
674
|
it 'classifies pure prior-turn recall and vague memory cues as :recall' do
|
|
@@ -979,4 +1009,12 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
|
|
|
979
1009
|
expect(src).not_to include('return false if kind == :statement')
|
|
980
1010
|
expect(src).not_to include('return false if kind == :question && fails.zero?')
|
|
981
1011
|
end
|
|
1012
|
+
|
|
1013
|
+
it 'streams request-processing progress through PWN::Plugins::Log when debug is on' do
|
|
1014
|
+
src = File.read(described_class.method(:run).source_location.first)
|
|
1015
|
+
expect(src).to match(/PWN::Plugins::Log\.start_debug/)
|
|
1016
|
+
expect(src).to match(/PWN::Plugins::Log\.progress/)
|
|
1017
|
+
expect(src).to match(/quiet_debug_tui!/)
|
|
1018
|
+
expect(described_class).to respond_to(:debug_on?)
|
|
1019
|
+
end
|
|
982
1020
|
end # rubocop:enable Metrics/BlockLength
|
|
@@ -47,8 +47,8 @@ describe PWN::AI::Anthropic do
|
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
it '
|
|
51
|
-
expect(described_class).
|
|
50
|
+
it 'does not expose get_plan_usage' do
|
|
51
|
+
expect(described_class).not_to respond_to(:get_plan_usage)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
describe 'Hermes prompt-cache breakpoint' do
|
|
@@ -13,8 +13,8 @@ describe PWN::AI::Gemini do
|
|
|
13
13
|
expect(help_response).to respond_to :help
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
it '
|
|
17
|
-
expect(described_class).
|
|
16
|
+
it 'does not expose get_plan_usage' do
|
|
17
|
+
expect(described_class).not_to respond_to(:get_plan_usage)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
it 'chat_with_tools honors PromptCache when prompt_cache is on' do
|
|
@@ -18,8 +18,8 @@ describe PWN::AI::Grok do
|
|
|
18
18
|
expect(src).to match(/openai_wire_messages/)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
it '
|
|
22
|
-
expect(described_class).
|
|
21
|
+
it 'does not expose get_plan_usage' do
|
|
22
|
+
expect(described_class).not_to respond_to(:get_plan_usage)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
it 'grok_rest_call swallows ReadTimeout quietly without ERROR: print' do
|
|
@@ -321,12 +321,7 @@ describe PWN::AI::Ollama do
|
|
|
321
321
|
expect(args[:command] || args['command']).to eq('id')
|
|
322
322
|
end
|
|
323
323
|
|
|
324
|
-
it '
|
|
325
|
-
expect(described_class).
|
|
326
|
-
end
|
|
327
|
-
it 'marks local plan usage as unlimited so the PS1 shows infinity' do
|
|
328
|
-
usage = described_class.get_plan_usage
|
|
329
|
-
expect(usage[:available]).to be false
|
|
330
|
-
expect(usage[:unlimited]).to be true
|
|
324
|
+
it 'does not expose get_plan_usage' do
|
|
325
|
+
expect(described_class).not_to respond_to(:get_plan_usage)
|
|
331
326
|
end
|
|
332
327
|
end
|
|
@@ -18,29 +18,8 @@ describe PWN::AI::OpenAI do
|
|
|
18
18
|
expect(src).to match(/openai_wire_messages/)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
it '
|
|
22
|
-
expect(described_class).
|
|
23
|
-
end
|
|
24
|
-
it 'computes a percentage from dashboard billing used vs hard limit' do
|
|
25
|
-
allow(described_class).to receive(:plan_usage_credentials?).and_return(true)
|
|
26
|
-
allow(described_class).to receive(:open_ai_rest_call) do |**kwargs|
|
|
27
|
-
case kwargs[:rest_call]
|
|
28
|
-
when 'dashboard/billing/subscription'
|
|
29
|
-
'{"hard_limit_usd":100.0}'
|
|
30
|
-
when 'dashboard/billing/usage'
|
|
31
|
-
'{"total_usage":1900}'
|
|
32
|
-
else
|
|
33
|
-
'{"error":"no"}'
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
usage = described_class.get_plan_usage
|
|
37
|
-
expect(usage[:available]).to be true
|
|
38
|
-
expect(usage[:percent]).to eq(19)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
it 'returns unavailable when credentials are missing' do
|
|
42
|
-
allow(described_class).to receive(:plan_usage_credentials?).and_return(false)
|
|
43
|
-
expect(described_class.get_plan_usage[:available]).to be false
|
|
21
|
+
it 'does not expose get_plan_usage' do
|
|
22
|
+
expect(described_class).not_to respond_to(:get_plan_usage)
|
|
44
23
|
end
|
|
45
24
|
|
|
46
25
|
it 'open_ai_rest_call swallows ReadTimeout quietly without ERROR: print' do
|
|
@@ -300,12 +300,7 @@ describe PWN::AI::OpenWebUI do
|
|
|
300
300
|
expect(args[:command] || args['command']).to eq('id')
|
|
301
301
|
end
|
|
302
302
|
|
|
303
|
-
it '
|
|
304
|
-
expect(described_class).
|
|
305
|
-
end
|
|
306
|
-
it 'marks local plan usage as unlimited so the PS1 shows infinity' do
|
|
307
|
-
usage = described_class.get_plan_usage
|
|
308
|
-
expect(usage[:available]).to be false
|
|
309
|
-
expect(usage[:unlimited]).to be true
|
|
303
|
+
it 'does not expose get_plan_usage' do
|
|
304
|
+
expect(described_class).not_to respond_to(:get_plan_usage)
|
|
310
305
|
end
|
|
311
306
|
end
|
data/spec/lib/pwn/ai_spec.rb
CHANGED
|
@@ -8,26 +8,9 @@ describe PWN::AI do
|
|
|
8
8
|
expect(help_response).not_to be_nil
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
it '
|
|
12
|
-
|
|
13
|
-
expect(
|
|
14
|
-
expect(
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it 'returns the infinity glyph for ollama and openwebui' do
|
|
18
|
-
expect(described_class.plan_usage_glyph(engine: :ollama)).to eq("\u221E")
|
|
19
|
-
expect(described_class.plan_usage_glyph(engine: :openwebui)).to eq("\u221E")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it 'returns a percent glyph when usage data is available' do
|
|
23
|
-
glyph = described_class.plan_usage_glyph(
|
|
24
|
-
engine: :grok,
|
|
25
|
-
usage: { available: true, percent: 19 }
|
|
26
|
-
)
|
|
27
|
-
expect(glyph).to eq('19%')
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it 'returns infinity when a hosted engine has no usage data' do
|
|
31
|
-
expect(described_class.plan_usage_glyph(engine: :gemini, usage: { available: false })).to eq("\u221E")
|
|
11
|
+
it 'does not expose plan-usage helpers' do
|
|
12
|
+
expect(described_class).not_to respond_to(:normalize_plan_usage)
|
|
13
|
+
expect(described_class).not_to respond_to(:plan_usage)
|
|
14
|
+
expect(described_class).not_to respond_to(:plan_usage_glyph)
|
|
32
15
|
end
|
|
33
16
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
|
+
require 'stringio'
|
|
4
5
|
|
|
5
6
|
describe PWN::Plugins::Log do
|
|
6
7
|
it 'should display information for authors' do
|
|
@@ -12,4 +13,142 @@ describe PWN::Plugins::Log do
|
|
|
12
13
|
help_response = PWN::Plugins::Log
|
|
13
14
|
expect(help_response).to respond_to :help
|
|
14
15
|
end
|
|
16
|
+
|
|
17
|
+
describe 'pwn-ai debug session' do
|
|
18
|
+
after do
|
|
19
|
+
PWN::Plugins::Log.stop_debug if PWN::Plugins::Log.respond_to?(:stop_debug)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'starts a /tmp/pwn-ai-DEBUG-TIMESTAMP.log and tees progress to TUI + file' do
|
|
23
|
+
expect(PWN::Plugins::Log).to respond_to(:start_debug)
|
|
24
|
+
expect(PWN::Plugins::Log).to respond_to(:stop_debug)
|
|
25
|
+
expect(PWN::Plugins::Log).to respond_to(:progress)
|
|
26
|
+
expect(PWN::Plugins::Log).to respond_to(:debug_enabled?)
|
|
27
|
+
expect(PWN::Plugins::Log).to respond_to(:debug_log_path)
|
|
28
|
+
|
|
29
|
+
tee = StringIO.new
|
|
30
|
+
path = PWN::Plugins::Log.start_debug(tee: tee, path: nil)
|
|
31
|
+
expect(path).to match(%r{\A/tmp/pwn-ai-DEBUG-.+\.log\z})
|
|
32
|
+
expect(File.file?(path)).to eq(true)
|
|
33
|
+
expect(PWN::Plugins::Log.debug_enabled?).to eq(true)
|
|
34
|
+
expect(PWN::Plugins::Log.debug_log_path).to eq(path)
|
|
35
|
+
|
|
36
|
+
ok = PWN::Plugins::Log.progress(msg: 'Loop.run start request=hi', which_self: PWN::AI::Agent::Loop)
|
|
37
|
+
expect(ok).to eq(true)
|
|
38
|
+
body = File.read(path)
|
|
39
|
+
expect(body).to include('Loop.run start request=hi')
|
|
40
|
+
expect(body).to include('PWN::AI::Agent::Loop')
|
|
41
|
+
expect(tee.string).to include('Loop.run start request=hi')
|
|
42
|
+
|
|
43
|
+
PWN::Plugins::Log.stop_debug
|
|
44
|
+
expect(PWN::Plugins::Log.debug_enabled?).to eq(false)
|
|
45
|
+
expect(PWN::Plugins::Log.progress(msg: 'after stop', which_self: self)).to eq(false)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'quiet_tui keeps writing the file but stops teeing to the TUI' do
|
|
49
|
+
expect(PWN::Plugins::Log).to respond_to(:quiet_tui!)
|
|
50
|
+
expect(PWN::Plugins::Log).to respond_to(:loud_tui!)
|
|
51
|
+
tee = StringIO.new
|
|
52
|
+
path = PWN::Plugins::Log.start_debug(tee: tee, path: "/tmp/pwn-ai-DEBUG-#{Process.pid}-quiet.log")
|
|
53
|
+
PWN::Plugins::Log.progress(msg: 'before-quiet', which_self: PWN::AI::Agent::Loop)
|
|
54
|
+
PWN::Plugins::Log.quiet_tui!
|
|
55
|
+
PWN::Plugins::Log.progress(msg: 'after-quiet', which_self: PWN::AI::Agent::Loop)
|
|
56
|
+
expect(tee.string).to include('before-quiet')
|
|
57
|
+
expect(tee.string).not_to include('after-quiet')
|
|
58
|
+
expect(File.read(path)).to include('after-quiet')
|
|
59
|
+
PWN::Plugins::Log.loud_tui!
|
|
60
|
+
PWN::Plugins::Log.progress(msg: 'after-loud', which_self: PWN::AI::Agent::Loop)
|
|
61
|
+
expect(tee.string).to include('after-loud')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'rolls the debug log to .N+1 when the file exceeds 1MB' do
|
|
65
|
+
stem = "/tmp/pwn-ai-DEBUG-#{Process.pid}-roll"
|
|
66
|
+
first = "#{stem}.1.log"
|
|
67
|
+
File.write(first, 'x' * (1_024_000 + 10))
|
|
68
|
+
path = PWN::Plugins::Log.start_debug(tee: StringIO.new, path: first)
|
|
69
|
+
expect(path).to eq(first)
|
|
70
|
+
PWN::Plugins::Log.progress(msg: 'overflow-line', which_self: PWN::AI::Agent::Loop)
|
|
71
|
+
second = "#{stem}.2.log"
|
|
72
|
+
expect(PWN::Plugins::Log.debug_log_path).to eq(second)
|
|
73
|
+
expect(File.file?(second)).to eq(true)
|
|
74
|
+
expect(File.read(second)).to include('overflow-line')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'traces PWN module calls into the same debug log while the session is on' do
|
|
78
|
+
path = PWN::Plugins::Log.start_debug(tee: StringIO.new, path: "/tmp/pwn-ai-DEBUG-#{Process.pid}-#{Time.now.to_i}.log")
|
|
79
|
+
PWN::AI::Agent::OpenGoal.current if defined?(PWN::AI::Agent::OpenGoal)
|
|
80
|
+
body = File.read(path)
|
|
81
|
+
expect(body).to match(/PWN::AI::Agent::OpenGoal/)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'does not raise when a traced module #name is a Symbol' do
|
|
85
|
+
probe = Module.new do
|
|
86
|
+
def self.name
|
|
87
|
+
:symname
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.ping
|
|
91
|
+
:ok
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
stub_const('PWN::AI::Agent::SymNameProbe', probe)
|
|
95
|
+
PWN::Plugins::Log.start_debug(
|
|
96
|
+
tee: StringIO.new,
|
|
97
|
+
path: "/tmp/pwn-ai-DEBUG-#{Process.pid}-sym.log"
|
|
98
|
+
)
|
|
99
|
+
expect { PWN::AI::Agent::SymNameProbe.ping }.not_to raise_error
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'logs the complete traced call including parameters' do
|
|
103
|
+
probe = Module.new do
|
|
104
|
+
def self.echo(opts = {})
|
|
105
|
+
opts[:msg]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
stub_const('PWN::AI::Agent::ArgProbe', probe)
|
|
109
|
+
path = PWN::Plugins::Log.start_debug(
|
|
110
|
+
tee: StringIO.new,
|
|
111
|
+
path: "/tmp/pwn-ai-DEBUG-#{Process.pid}-args.log"
|
|
112
|
+
)
|
|
113
|
+
PWN::AI::Agent::ArgProbe.echo(
|
|
114
|
+
msg: 'hello-browser',
|
|
115
|
+
url: 'https://example.test/open',
|
|
116
|
+
password: 's3cret-token'
|
|
117
|
+
)
|
|
118
|
+
body = File.read(path)
|
|
119
|
+
expect(body).to include('PWN::AI::Agent::ArgProbe.echo')
|
|
120
|
+
expect(body).to include('hello-browser')
|
|
121
|
+
expect(body).to include('https://example.test/open')
|
|
122
|
+
expect(body).not_to include('s3cret-token')
|
|
123
|
+
expect(body).to match(/\[REDACTED\]/)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'redacts secret-shaped values even when the key is not a password name' do
|
|
127
|
+
probe = Module.new do
|
|
128
|
+
def self.echo(opts = {})
|
|
129
|
+
opts[:msg]
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
stub_const('PWN::AI::Agent::ArgProbe', probe)
|
|
133
|
+
path = PWN::Plugins::Log.start_debug(
|
|
134
|
+
tee: StringIO.new,
|
|
135
|
+
path: "/tmp/pwn-ai-DEBUG-#{Process.pid}-redact.log"
|
|
136
|
+
)
|
|
137
|
+
PWN::AI::Agent::ArgProbe.echo(
|
|
138
|
+
header: 'Bearer eyJhbGciOiJIUzI1NiJ9.aaa.bbb',
|
|
139
|
+
blob: "-----BEGIN PRIVATE KEY-----\nMIIB\n-----END PRIVATE KEY-----",
|
|
140
|
+
note: 'xai-abcdefghijklmnopqrstuvwxyz0123456789ABCD'
|
|
141
|
+
)
|
|
142
|
+
PWN::Plugins::Log.progress(
|
|
143
|
+
msg: 'Loop.run start request=use key sk-live-SUPERSECRET999',
|
|
144
|
+
which_self: PWN::AI::Agent::Loop
|
|
145
|
+
)
|
|
146
|
+
body = File.read(path)
|
|
147
|
+
expect(body).not_to include('eyJhbGciOiJIUzI1NiJ9')
|
|
148
|
+
expect(body).not_to include('BEGIN PRIVATE KEY')
|
|
149
|
+
expect(body).not_to include('xai-abcdefghijklmnopqrstuvwxyz0123456789ABCD')
|
|
150
|
+
expect(body).not_to include('sk-live-SUPERSECRET999')
|
|
151
|
+
expect(body).to match(/\[REDACTED\]/)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
15
154
|
end
|
|
@@ -132,3 +132,34 @@ describe 'pwn-vault Config RuntimeError retry' do
|
|
|
132
132
|
end
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
|
+
|
|
136
|
+
describe 'toggle-pwn-ai-debug' do
|
|
137
|
+
let(:repl_src) { File.read(PWN::Plugins::REPL.method(:add_commands).source_location.first) }
|
|
138
|
+
let(:block) do
|
|
139
|
+
start = repl_src.index("Pry::Commands.create_command 'toggle-pwn-ai-debug'")
|
|
140
|
+
stop = repl_src.index("Pry::Commands.create_command 'toggle-pwn-ai-speaks'")
|
|
141
|
+
repl_src[start...stop]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'starts and stops PWN::Plugins::Log debug session on the TUI toggle' do
|
|
145
|
+
expect(block).to include('PWN::Plugins::Log.start_debug')
|
|
146
|
+
expect(block).to include('PWN::Plugins::Log.stop_debug')
|
|
147
|
+
expect(block).to include('pwn-ai-DEBUG')
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe 'pwn-ai debug final answer placement' do
|
|
152
|
+
let(:repl_src) { File.read(PWN::Plugins::REPL.method(:add_commands).source_location.first) }
|
|
153
|
+
let(:hook) do
|
|
154
|
+
start = repl_src.index('Pry.config.hooks.add_hook(:after_read, :pwn_ai_hook)')
|
|
155
|
+
repl_src[start..]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'prints the green final after Loop.run and does not pp the session after it' do
|
|
159
|
+
expect(hook).to match(/Loop\.run\(/)
|
|
160
|
+
expect(hook).to match(/\\e\[32m\\002#\{final\}/)
|
|
161
|
+
native = hook[hook.index('final = PWN::AI::Agent::Loop.run')..hook.index("request.replace('nil')")]
|
|
162
|
+
expect(native).not_to match(/pp PWN::Sessions\.load/)
|
|
163
|
+
expect(native).to match(/\$stdout\.flush/)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -13,10 +13,11 @@ describe PWN::Plugins::REPL do
|
|
|
13
13
|
expect(help_response).to respond_to :help
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
it '
|
|
16
|
+
it 'does not append plan usage to the pwn.ai PS1' do
|
|
17
17
|
src = File.read(described_class.method(:refresh_ps1_proc).source_location.first)
|
|
18
|
-
expect(src).
|
|
19
|
-
expect(src).
|
|
18
|
+
expect(src).not_to include('plan_usage_glyph')
|
|
19
|
+
expect(src).not_to include('PWN::AI.plan_usage')
|
|
20
|
+
expect(src).to include('current_context_length')
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
it 'formats compact token counts for the PS1 budget' do
|