pwn 0.5.754 → 0.5.756
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/Gemfile +2 -2
- data/documentation/pwn-ai-Agent.md +4 -2
- data/lib/pwn/ai/anthropic.rb +23 -10
- data/lib/pwn/plugins/repl/ai_console.rb +77 -10
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/anthropic_spec.rb +27 -0
- data/spec/lib/pwn/plugins/binary_parser_spec.rb +11 -2
- data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +56 -0
- data/third_party/pwn_rdoc.jsonl +2 -0
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7fac5f5a9f5b01e9c4ce830db72a7e454f1a2dce37b44e57f98e290c154cfbbf
|
|
4
|
+
data.tar.gz: 1c508a671f0eb88f704bd38e58b7f3ad7154eca3e0e8d6b4d62852c7bf217a1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 311ad0755488c66ad2c92c10751c52c5b86b4b725aae976fa697b0f206fb3c88afc954371d8ec8628fcc9431449fe58b71948f4ec2e3ad73b189e88165bcf3b4
|
|
7
|
+
data.tar.gz: e0055f29efafc74e550e84fd504debb71400acbfaf768eb1be82caa1d57cc1d79d49addfb78bb31608fdd0fd2716a4f68277b0d88272960e6b4dffb990b829f9
|
data/Gemfile
CHANGED
|
@@ -89,8 +89,8 @@ gem 'ruby-nmap', '1.0.3'
|
|
|
89
89
|
gem 'ruby-saml', '1.18.1'
|
|
90
90
|
gem 'rvm', '1.11.3.9'
|
|
91
91
|
gem 'savon', '2.17.5'
|
|
92
|
-
gem 'selenium-devtools', '0.
|
|
93
|
-
gem 'selenium-webdriver', '4.
|
|
92
|
+
gem 'selenium-devtools', '0.154.0'
|
|
93
|
+
gem 'selenium-webdriver', '4.50.0'
|
|
94
94
|
gem 'slack-ruby-client', '3.2.0'
|
|
95
95
|
gem 'socksify', '1.8.1'
|
|
96
96
|
gem 'spreadsheet', '1.3.5'
|
|
@@ -115,8 +115,10 @@ changes only default values, not the configuration schema; no migration is neede
|
|
|
115
115
|
| --- | --- |
|
|
116
116
|
| Enter | Submit the mission |
|
|
117
117
|
| Shift+Enter, or trailing `\\` + Enter | Insert a newline. Terminals that cannot distinguish Shift+Enter need `tmux set -s extended-keys on` or the backslash fallback. |
|
|
118
|
-
| Up/Down |
|
|
119
|
-
|
|
|
118
|
+
| Up/Down | In MISSION CONTROL, move the completion highlight while the Command menu is visible (wrapping at either end). Otherwise recall requests from `~/.pwn/pwn_history`, including prior runs; Down past the newest restores the draft and cursor. In SESSION, move one transcript row. Esc closes the menu to resume history recall. |
|
|
119
|
+
| Home/End | In MISSION CONTROL, move the composer cursor to the start or end of the line. In SESSION, jump to the top of the transcript or back to the live bottom. |
|
|
120
|
+
| Ctrl+X | Toggle the active pane between SESSION and MISSION CONTROL. The active title is reversed and marked `active`. Typing or Enter returns to MISSION CONTROL; Enter does not submit while SESSION is active. |
|
|
121
|
+
| Ctrl+P / Ctrl+N | Alternative completion selection keys. In MISSION CONTROL they recall history when no menu is visible. In SESSION they scroll one row. Tab still accepts the highlight. |
|
|
120
122
|
| Tab | Accept the highlighted as-you-type parameter |
|
|
121
123
|
| Ctrl+O or `/status` | Inspect full status/settings, including overflowing header text; Esc or Ctrl+O closes |
|
|
122
124
|
| Ctrl+L | Clear only the Session pane, including while a request runs; preserve stored conversation, token totals, and draft |
|
data/lib/pwn/ai/anthropic.rb
CHANGED
|
@@ -578,7 +578,7 @@ module PWN
|
|
|
578
578
|
input_schema: anthropic_input_schema(schema: fn[:parameters] || fn['parameters'])
|
|
579
579
|
}
|
|
580
580
|
end
|
|
581
|
-
http_body[:tool_choice] = anth_tool_choice(choice: opts[:tool_choice]) if opts[:tool_choice]
|
|
581
|
+
http_body[:tool_choice] = anth_tool_choice(choice: opts[:tool_choice], model: model) if opts[:tool_choice]
|
|
582
582
|
end
|
|
583
583
|
|
|
584
584
|
response = anthropic_rest_call(
|
|
@@ -794,15 +794,28 @@ module PWN
|
|
|
794
794
|
|
|
795
795
|
private_class_method def self.anth_tool_choice(opts = {})
|
|
796
796
|
choice = opts[:choice]
|
|
797
|
-
case choice
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
797
|
+
mapped = case choice
|
|
798
|
+
when 'none', :none then { type: 'none' }
|
|
799
|
+
when 'required', :required, 'any', :any then { type: 'any' }
|
|
800
|
+
when Hash
|
|
801
|
+
fn = choice[:function] || choice['function'] || choice
|
|
802
|
+
{ type: 'tool', name: fn[:name] || fn['name'] }
|
|
803
|
+
else
|
|
804
|
+
{ type: 'auto' }
|
|
805
|
+
end
|
|
806
|
+
return { type: 'auto' } if !forced_tool_choice?(model: opts[:model]) && %w[any tool].include?(mapped[:type].to_s)
|
|
807
|
+
|
|
808
|
+
mapped
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
# Claude Mythos 5.1, Fable 5.1, Opus 5.5, and Sonnet 5.5 reject forced tool use.
|
|
812
|
+
# Mythos 5 still accepts type any/tool, so match the documented ids, not every Mythos model.
|
|
813
|
+
private_class_method def self.forced_tool_choice?(opts = {})
|
|
814
|
+
model = opts[:model].to_s
|
|
815
|
+
return false if model.match?(/claude-(?:mythos|fable)-5-1(?:-|\z)/i)
|
|
816
|
+
return false if model.match?(/claude-(?:opus|sonnet)-5-5(?:-|\z)/i)
|
|
817
|
+
|
|
818
|
+
true
|
|
806
819
|
end
|
|
807
820
|
|
|
808
821
|
# Supported Method Parameters::
|
|
@@ -279,6 +279,7 @@ module PWN
|
|
|
279
279
|
@editor = Editor.new
|
|
280
280
|
@timeline = []
|
|
281
281
|
@scroll = nil
|
|
282
|
+
@focus = :mission
|
|
282
283
|
@event_count = @completed_tools = @unseen = 0
|
|
283
284
|
@verbose = true
|
|
284
285
|
@usage = AIConsoleUsage::Tracker.new
|
|
@@ -652,6 +653,28 @@ module PWN
|
|
|
652
653
|
@menu = nil if @menu.empty?
|
|
653
654
|
end
|
|
654
655
|
|
|
656
|
+
# Live view uses a nil scroll. Up from that view pins one row above the bottom.
|
|
657
|
+
def scroll_session(key)
|
|
658
|
+
bottom = [@total_rows.to_i - @page_size.to_i, 0].max
|
|
659
|
+
case key
|
|
660
|
+
when :up, "\u0010"
|
|
661
|
+
@scroll = [(@scroll || bottom) - 1, 0].max
|
|
662
|
+
when :down, "\u000e"
|
|
663
|
+
return if @scroll.nil?
|
|
664
|
+
|
|
665
|
+
@scroll += 1
|
|
666
|
+
if @scroll >= bottom
|
|
667
|
+
@scroll = nil
|
|
668
|
+
@unseen = 0
|
|
669
|
+
end
|
|
670
|
+
when :home
|
|
671
|
+
@scroll = bottom.zero? ? nil : 0
|
|
672
|
+
when :end
|
|
673
|
+
@scroll = nil
|
|
674
|
+
@unseen = 0
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
|
|
655
678
|
def handle(key)
|
|
656
679
|
return if key.nil?
|
|
657
680
|
return handle_system_role(key) if @role_editor && (!@too_small || ["\e", "\u0003", "\u0004", :eof].include?(key))
|
|
@@ -681,10 +704,17 @@ module PWN
|
|
|
681
704
|
if key == "\u0012"
|
|
682
705
|
@menu = nil
|
|
683
706
|
@paste = false
|
|
707
|
+
@focus = :mission
|
|
684
708
|
@editor.search_history
|
|
685
709
|
return
|
|
686
710
|
end
|
|
687
711
|
|
|
712
|
+
if key == "\u0018"
|
|
713
|
+
@menu = nil
|
|
714
|
+
@focus = @focus == :session ? :mission : :session
|
|
715
|
+
return
|
|
716
|
+
end
|
|
717
|
+
|
|
688
718
|
if key == "\t" && @menu
|
|
689
719
|
accept_completion
|
|
690
720
|
return
|
|
@@ -702,7 +732,9 @@ module PWN
|
|
|
702
732
|
when :paste_end then @paste = false
|
|
703
733
|
when :newline then @editor.insert("\n")
|
|
704
734
|
when "\r", "\n"
|
|
705
|
-
if @paste
|
|
735
|
+
if @focus == :session && !@paste
|
|
736
|
+
@focus = :mission
|
|
737
|
+
elsif @paste
|
|
706
738
|
@editor.insert("\n")
|
|
707
739
|
elsif @editor.text.end_with?('\\')
|
|
708
740
|
@editor.edit("\b")
|
|
@@ -718,6 +750,8 @@ module PWN
|
|
|
718
750
|
direction = [:up, "\u0010"].include?(key) ? :up : :down
|
|
719
751
|
if @menu
|
|
720
752
|
@menu_index = (@menu_index + (direction == :up ? -1 : 1)) % @menu.length
|
|
753
|
+
elsif @focus == :session
|
|
754
|
+
scroll_session(key)
|
|
721
755
|
else
|
|
722
756
|
@editor.recall(direction)
|
|
723
757
|
end
|
|
@@ -727,11 +761,21 @@ module PWN
|
|
|
727
761
|
@scroll = nil if @scroll >= [@total_rows.to_i - @page_size.to_i, 0].max
|
|
728
762
|
@unseen = 0 unless @scroll
|
|
729
763
|
when "\t" then accept_completion
|
|
730
|
-
when :
|
|
731
|
-
@
|
|
732
|
-
|
|
764
|
+
when :home, :end
|
|
765
|
+
if @focus == :session
|
|
766
|
+
scroll_session(key)
|
|
767
|
+
else
|
|
768
|
+
@editor.edit(key)
|
|
769
|
+
refresh_completion
|
|
770
|
+
end
|
|
771
|
+
when :left, :right, :delete, "\b", "\u007f", "\u0001", "\u0005", "\u0015"
|
|
772
|
+
unless @focus == :session
|
|
773
|
+
@editor.edit(key)
|
|
774
|
+
refresh_completion
|
|
775
|
+
end
|
|
733
776
|
else
|
|
734
777
|
if key.is_a?(String) && key.ord >= 32
|
|
778
|
+
@focus = :mission
|
|
735
779
|
@editor.insert(key)
|
|
736
780
|
refresh_completion
|
|
737
781
|
end
|
|
@@ -1045,6 +1089,13 @@ module PWN
|
|
|
1045
1089
|
put(top, left + 2, fit(" #{title} ", box_width - 4), tone(:title)) unless title.empty?
|
|
1046
1090
|
end
|
|
1047
1091
|
|
|
1092
|
+
def mark_active(top, left, box_width, title)
|
|
1093
|
+
return unless @screen.respond_to?(:attron)
|
|
1094
|
+
|
|
1095
|
+
text = fit(" #{title} ", box_width - 4)
|
|
1096
|
+
@screen.attron(Curses::A_REVERSE) { put(top, left + 2, text, tone(:title)) }
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1048
1099
|
def seed_request_history
|
|
1049
1100
|
# REPL configures ~/.pwn/pwn_history and Pry loads it at startup.
|
|
1050
1101
|
# Reuse that owner rather than loading twice or opening another writer.
|
|
@@ -1262,21 +1313,36 @@ module PWN
|
|
|
1262
1313
|
@total_rows = rows.length
|
|
1263
1314
|
@scroll = @scroll.clamp(0, [rows.length - @page_size, 0].max) if @scroll
|
|
1264
1315
|
session_id = @pry.config.pwn_ai_session_id.to_s
|
|
1265
|
-
|
|
1316
|
+
session_title = "SESSION #{session_id}"
|
|
1317
|
+
session_title += ' · active' if @focus == :session
|
|
1318
|
+
box(header_height, 0, timeline_height, pane_width, session_title)
|
|
1319
|
+
mark_active(header_height, 0, pane_width, session_title) if @focus == :session
|
|
1266
1320
|
start = @scroll || [rows.length - @page_size, 0].max
|
|
1267
1321
|
rows.slice(start, @page_size).to_a.each_with_index { |(color, row), index| put(header_height + 1 + index, 2, row, color) }
|
|
1268
1322
|
draw_sidebar(header_height, pane_width + 1, timeline_height) if sidebar.positive?
|
|
1269
1323
|
compose_y = @height - compose_height - 1
|
|
1270
|
-
|
|
1324
|
+
mission_title = @focus == :session ? 'MISSION CONTROL' : 'MISSION CONTROL · active'
|
|
1325
|
+
box(compose_y, 0, compose_height, @width - 1, mission_title)
|
|
1326
|
+
mark_active(compose_y, 0, @width - 1, mission_title) if @focus != :session
|
|
1271
1327
|
draw_composer(compose_y)
|
|
1272
|
-
hint = @menu
|
|
1328
|
+
hint = if @menu
|
|
1329
|
+
'↑↓/^P/^N select · Tab accept · Esc close'
|
|
1330
|
+
elsif @focus == :session
|
|
1331
|
+
'↑↓/Home/End scroll · ^X mission'
|
|
1332
|
+
else
|
|
1333
|
+
'↑↓ history · ^X session'
|
|
1334
|
+
end
|
|
1273
1335
|
put(@height - 1, 1, "^C cancel ^D back · #{hint} · ^G swarm ^L clear ^R search ^O status · Enter send", tone(:footer))
|
|
1274
1336
|
draw_menu if @menu
|
|
1275
1337
|
draw_model_prompt if @model_prompt
|
|
1276
1338
|
draw_details(engine, model) if @details
|
|
1277
1339
|
draw_swarm if @workspace
|
|
1278
1340
|
draw_system_role if @role_editor
|
|
1279
|
-
|
|
1341
|
+
if @focus == :session && !(@details || @workspace || @role_editor)
|
|
1342
|
+
@screen.setpos(header_height + 1, 2)
|
|
1343
|
+
elsif @role_editor || !(@details || @workspace)
|
|
1344
|
+
@screen.setpos(*@cursor_position)
|
|
1345
|
+
end
|
|
1280
1346
|
@screen.refresh
|
|
1281
1347
|
end
|
|
1282
1348
|
|
|
@@ -1291,7 +1357,7 @@ module PWN
|
|
|
1291
1357
|
first = [cursor_row - 2, 0].max
|
|
1292
1358
|
put(top + 1, 2, '›', tone(:prompt)) if first.zero?
|
|
1293
1359
|
rows.slice(first, 3).to_a.each_with_index { |row, index| put(top + 1 + index, 4, row, tone(:composer)) }
|
|
1294
|
-
hint = @menu ? 'Tab accept · ↑↓/^P/^N select · Esc close' : '^G swarm · Shift+Enter newline · / commands · ^O status'
|
|
1360
|
+
hint = @menu ? 'Tab accept · ↑↓/^P/^N select · Esc close' : '^X pane · ^G swarm · Shift+Enter newline · / commands · ^O status'
|
|
1295
1361
|
put(top + 4, 4, fit(hint, @width - 7), tone(:footer))
|
|
1296
1362
|
@cursor_position = [top + 1 + cursor_row - first, [4 + width(prefix.last), @width - 3].min]
|
|
1297
1363
|
end
|
|
@@ -1557,7 +1623,8 @@ module PWN
|
|
|
1557
1623
|
public_class_method def self.help
|
|
1558
1624
|
puts "USAGE:
|
|
1559
1625
|
# Ctrl+L clears only the session pane, including while busy.
|
|
1560
|
-
#
|
|
1626
|
+
# Ctrl+X switches the active pane between SESSION and MISSION CONTROL.
|
|
1627
|
+
# Up/Down select in menus, recall ~/.pwn/pwn_history in the mission pane, and scroll the session pane when it is active.
|
|
1561
1628
|
# Search: Enter accepts without sending; Esc restores the draft.
|
|
1562
1629
|
# Launch the single-owner curses console; non-terminals return unavailable.
|
|
1563
1630
|
#{self}.run(
|
data/lib/pwn/version.rb
CHANGED
|
@@ -106,4 +106,31 @@ describe PWN::AI::Anthropic do
|
|
|
106
106
|
expect(flat[:properties].keys.map(&:to_s)).to include('handle', 'path')
|
|
107
107
|
expect(flat[:description]).to include('handle', 'path')
|
|
108
108
|
end
|
|
109
|
+
|
|
110
|
+
it 'does not force tool use on Anthropic models that reject tool_choice any and tool' do
|
|
111
|
+
allow(PWN::Env).to receive(:[]).with(:ai).and_return(
|
|
112
|
+
anthropic: { key: 'test-key', model: 'claude-mythos-5', temp: 1, max_tokens: 100 }
|
|
113
|
+
)
|
|
114
|
+
allow(PWN::AI::Agent::PromptCache).to receive(:enabled?).and_return(false) if defined?(PWN::AI::Agent::PromptCache)
|
|
115
|
+
captured = nil
|
|
116
|
+
allow(described_class).to receive(:anthropic_rest_call) do |opts|
|
|
117
|
+
captured = opts[:http_body]
|
|
118
|
+
'{"id":"msg_choice","type":"message","role":"assistant","content":[{"type":"text","text":"ok"}],"stop_reason":"end_turn"}'
|
|
119
|
+
end
|
|
120
|
+
tools = [{ type: 'function', function: { name: 'memory_recall', description: 'recall', parameters: { type: 'object', properties: {} } } }]
|
|
121
|
+
messages = [{ role: 'user', content: 'hello' }]
|
|
122
|
+
|
|
123
|
+
described_class.chat_with_tools(messages: messages, tools: tools, model: 'claude-mythos-5', tool_choice: 'required')
|
|
124
|
+
expect(captured[:tool_choice]).to eq(type: 'any')
|
|
125
|
+
|
|
126
|
+
%w[claude-mythos-5-1 claude-fable-5-1 claude-opus-5-5 claude-sonnet-5-5].each do |model|
|
|
127
|
+
described_class.chat_with_tools(messages: messages, tools: tools, model: model, tool_choice: 'required')
|
|
128
|
+
expect(captured[:tool_choice]).to eq(type: 'auto'), model
|
|
129
|
+
described_class.chat_with_tools(messages: messages, tools: tools, model: model, tool_choice: { type: 'function', function: { name: 'memory_recall' } })
|
|
130
|
+
expect(captured[:tool_choice]).to eq(type: 'auto'), model
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
described_class.chat_with_tools(messages: messages, tools: tools, model: 'claude-mythos-5-1', tool_choice: 'none')
|
|
134
|
+
expect(captured[:tool_choice]).to eq(type: 'none')
|
|
135
|
+
end
|
|
109
136
|
end
|
|
@@ -91,13 +91,22 @@ describe PWN::Plugins::BinaryParser do
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
it 'elf_resolve points system at the .plt.sec stub, not the next slot' do
|
|
94
|
+
x86_64 = RbConfig::CONFIG['host_cpu'].to_s.match?(/amd64|x86_64/)
|
|
94
95
|
Dir.mktmpdir('pwn-elf-ibt-') do |dir|
|
|
95
96
|
src = File.join(dir, 't.c')
|
|
96
97
|
path = File.join(dir, 't')
|
|
97
98
|
File.write(src, "#include <stdlib.h>\nint main(void) { return system(\"x\"); }\n")
|
|
98
|
-
|
|
99
|
+
flags = ['-O0', '-fno-pie', '-no-pie']
|
|
100
|
+
flags << '-Wl,-z,ibt' if x86_64
|
|
101
|
+
out, status = Open3.capture2e('cc', *flags, '-o', path, src)
|
|
99
102
|
expect(status.success?).to eq(true), out
|
|
100
|
-
|
|
103
|
+
binary = File.binread(path)
|
|
104
|
+
unless x86_64
|
|
105
|
+
expect(binary).not_to include('.plt.sec')
|
|
106
|
+
next
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
expect(binary).to include('.plt.sec')
|
|
101
110
|
row = described_class.elf_resolve(path: path)
|
|
102
111
|
dump, dump_status = Open3.capture2('objdump', '-d', path)
|
|
103
112
|
expect(dump_status.success?).to eq(true), dump
|
|
@@ -947,6 +947,62 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
947
947
|
expect(console.instance_variable_get(:@scroll)).to be_nil
|
|
948
948
|
end
|
|
949
949
|
|
|
950
|
+
it 'toggles the active pane so session arrows, Home and End scroll without changing the draft' do
|
|
951
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
952
|
+
editor = console.instance_variable_get(:@editor)
|
|
953
|
+
editor.place('kept draft', 4)
|
|
954
|
+
console.instance_variable_set(:@total_rows, 100)
|
|
955
|
+
console.instance_variable_set(:@page_size, 20)
|
|
956
|
+
console.instance_variable_set(:@unseen, 3)
|
|
957
|
+
console.handle(:up)
|
|
958
|
+
expect(editor.text).to eq('kept draft')
|
|
959
|
+
expect(console.instance_variable_get(:@scroll)).to be_nil
|
|
960
|
+
console.handle("\u0018")
|
|
961
|
+
expect(console.instance_variable_get(:@focus)).to eq(:session)
|
|
962
|
+
console.handle(:up)
|
|
963
|
+
expect(console.instance_variable_get(:@scroll)).to eq(79)
|
|
964
|
+
console.handle("\u0010")
|
|
965
|
+
expect(console.instance_variable_get(:@scroll)).to eq(78)
|
|
966
|
+
console.handle(:home)
|
|
967
|
+
expect(console.instance_variable_get(:@scroll)).to eq(0)
|
|
968
|
+
console.handle(:end)
|
|
969
|
+
expect(console.instance_variable_get(:@scroll)).to be_nil
|
|
970
|
+
expect(console.instance_variable_get(:@unseen)).to eq(0)
|
|
971
|
+
console.handle(:up)
|
|
972
|
+
console.handle(:down)
|
|
973
|
+
expect(console.instance_variable_get(:@scroll)).to be_nil
|
|
974
|
+
expect([editor.text, editor.cursor]).to eq(['kept draft', 4])
|
|
975
|
+
console.handle("\n")
|
|
976
|
+
expect(console.instance_variable_get(:@focus)).to eq(:mission)
|
|
977
|
+
expect(editor.text).to eq('kept draft')
|
|
978
|
+
console.handle("\u0018")
|
|
979
|
+
console.handle('x')
|
|
980
|
+
expect(console.instance_variable_get(:@focus)).to eq(:mission)
|
|
981
|
+
expect(editor.text).to eq('keptx draft')
|
|
982
|
+
console.handle(:up)
|
|
983
|
+
expect(console.instance_variable_get(:@scroll)).to be_nil
|
|
984
|
+
expect(console.instance_variable_get(:@focus)).to eq(:mission)
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
it 'marks the active pane and parks the cursor in the session when that pane is selected' do
|
|
988
|
+
screen = double(erase: nil, refresh: nil, setpos: nil, addstr: nil, attron: nil)
|
|
989
|
+
allow(screen).to receive(:attron).and_yield
|
|
990
|
+
curses = double(lines: 36, cols: 120)
|
|
991
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: curses, getch: nil)
|
|
992
|
+
console.instance_variable_set(:@screen, screen)
|
|
993
|
+
painted = []
|
|
994
|
+
allow(console).to receive(:put) { |*args| painted << args }
|
|
995
|
+
console.draw
|
|
996
|
+
expect(painted.map { |row| row[2] }.join).to include('MISSION CONTROL · active')
|
|
997
|
+
expect(painted.map { |row| row[2] }.join).not_to include('SESSION · active')
|
|
998
|
+
expect(screen).to have_received(:attron).with(Curses::A_REVERSE).at_least(:once)
|
|
999
|
+
console.handle("\u0018")
|
|
1000
|
+
painted.clear
|
|
1001
|
+
console.draw
|
|
1002
|
+
expect(painted.map { |row| row[2] }.join).to include('SESSION · active')
|
|
1003
|
+
expect(screen).to have_received(:setpos).with(a_kind_of(Integer), 2)
|
|
1004
|
+
end
|
|
1005
|
+
|
|
950
1006
|
it 'wraps at words without discarding code whitespace or splitting graphemes' do
|
|
951
1007
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
952
1008
|
expect(console.wrap('some evidence', 12)).to eq(['some ', 'evidence'])
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -3324,6 +3324,7 @@
|
|
|
3324
3324
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.handle_system_role Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.handle_system_role`: "}]}
|
|
3325
3325
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.header_layout Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.header_layout`: "}]}
|
|
3326
3326
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.header_lines Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.header_lines`: "}]}
|
|
3327
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.mark_active Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.mark_active`: "}]}
|
|
3327
3328
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.move_role_cursor Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.move_role_cursor`: "}]}
|
|
3328
3329
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.open_swarm Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.open_swarm`: "}]}
|
|
3329
3330
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.open_system_role Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.open_system_role`: "}]}
|
|
@@ -3336,6 +3337,7 @@
|
|
|
3336
3337
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.restore_streams Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.restore_streams`: "}]}
|
|
3337
3338
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.run Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.run`: "}]}
|
|
3338
3339
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.run_swarm Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.run_swarm`: "}]}
|
|
3340
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.scroll_session Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.scroll_session`: "}]}
|
|
3339
3341
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.scroll_status Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.scroll_status`: "}]}
|
|
3340
3342
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.seed_request_history Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.seed_request_history`: "}]}
|
|
3341
3343
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.send_tool_input Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.send_tool_input`: "}]}
|
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.
|
|
4
|
+
version: 0.5.756
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 0day Inc.
|
|
@@ -1051,28 +1051,28 @@ dependencies:
|
|
|
1051
1051
|
requirements:
|
|
1052
1052
|
- - '='
|
|
1053
1053
|
- !ruby/object:Gem::Version
|
|
1054
|
-
version: 0.
|
|
1054
|
+
version: 0.154.0
|
|
1055
1055
|
type: :runtime
|
|
1056
1056
|
prerelease: false
|
|
1057
1057
|
version_requirements: !ruby/object:Gem::Requirement
|
|
1058
1058
|
requirements:
|
|
1059
1059
|
- - '='
|
|
1060
1060
|
- !ruby/object:Gem::Version
|
|
1061
|
-
version: 0.
|
|
1061
|
+
version: 0.154.0
|
|
1062
1062
|
- !ruby/object:Gem::Dependency
|
|
1063
1063
|
name: selenium-webdriver
|
|
1064
1064
|
requirement: !ruby/object:Gem::Requirement
|
|
1065
1065
|
requirements:
|
|
1066
1066
|
- - '='
|
|
1067
1067
|
- !ruby/object:Gem::Version
|
|
1068
|
-
version: 4.
|
|
1068
|
+
version: 4.50.0
|
|
1069
1069
|
type: :runtime
|
|
1070
1070
|
prerelease: false
|
|
1071
1071
|
version_requirements: !ruby/object:Gem::Requirement
|
|
1072
1072
|
requirements:
|
|
1073
1073
|
- - '='
|
|
1074
1074
|
- !ruby/object:Gem::Version
|
|
1075
|
-
version: 4.
|
|
1075
|
+
version: 4.50.0
|
|
1076
1076
|
- !ruby/object:Gem::Dependency
|
|
1077
1077
|
name: slack-ruby-client
|
|
1078
1078
|
requirement: !ruby/object:Gem::Requirement
|