pwn 0.5.760 → 0.5.762
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/README.md +5 -5
- data/documentation/Swarm.md +113 -0
- data/documentation/pwn-ai-Agent.md +45 -11
- data/etc/default_skills/pwn/ai/agent/solve/SKILL.md +46 -0
- data/etc/default_skills/pwn/ai/agent/swarm/SKILL.md +1 -0
- data/etc/default_skills/pwn/banner/SKILL.md +1 -1
- data/lib/pwn/ai/agent/dispatch.rb +3 -0
- data/lib/pwn/ai/agent/loop.rb +18 -4
- data/lib/pwn/ai/agent/solve.rb +342 -0
- data/lib/pwn/ai/agent/swarm.rb +41 -1
- data/lib/pwn/ai/agent.rb +1 -0
- data/lib/pwn/ai/anthropic.rb +1 -0
- data/lib/pwn/ai/grok.rb +1 -0
- data/lib/pwn/ai/open_ai.rb +1 -0
- data/lib/pwn/banner.rb +593 -53
- data/lib/pwn/plugins/repl/ai.rb +6 -1
- data/lib/pwn/plugins/repl/ai_console.rb +81 -18
- data/lib/pwn/plugins/repl/ai_console_commands.rb +5 -4
- data/lib/pwn/plugins/repl/ai_swarm.rb +44 -11
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/loop_spec.rb +2 -1
- data/spec/lib/pwn/ai/agent/solve_spec.rb +290 -0
- data/spec/lib/pwn/banner_spec.rb +428 -20
- data/spec/lib/pwn/plugins/repl/ai_console_commands_spec.rb +37 -1
- data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +218 -27
- data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +67 -7
- data/third_party/pwn_rdoc.jsonl +39 -2
- metadata +4 -1
|
@@ -17,7 +17,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
17
17
|
allow(PWN::AI::Agent::Loop).to receive(:may_finalize?).and_return(true)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def launch(keys, &tick)
|
|
20
|
+
def launch(keys, startup_delay: 0, render_delay: 0, &tick)
|
|
21
21
|
require 'pty'
|
|
22
22
|
require 'timeout'
|
|
23
23
|
master, input = PTY.open
|
|
@@ -28,7 +28,10 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
28
28
|
frame = []
|
|
29
29
|
position = [0, 0]
|
|
30
30
|
allow(window).to receive(:erase) { frame = [] }
|
|
31
|
-
allow(window).to receive(:refresh)
|
|
31
|
+
allow(window).to receive(:refresh) do
|
|
32
|
+
sleep render_delay if render_delay.positive? && !keys.empty?
|
|
33
|
+
@rendered = frame.dup
|
|
34
|
+
end
|
|
32
35
|
@positions = []
|
|
33
36
|
allow(window).to receive(:setpos) do |*coords|
|
|
34
37
|
position = coords
|
|
@@ -42,6 +45,10 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
42
45
|
allow(window).to receive(:attron) { |_attribute, &block| block.call }
|
|
43
46
|
screen = double('curses', init_screen: window, raw: nil, noecho: nil, curs_set: nil, close_screen: nil,
|
|
44
47
|
lines: 30, cols: 110, stdscr: window, has_colors?: false, resizeterm: nil)
|
|
48
|
+
allow(screen).to receive(:init_screen) do
|
|
49
|
+
sleep startup_delay if startup_delay.positive?
|
|
50
|
+
window
|
|
51
|
+
end
|
|
45
52
|
@pry = Pry.new
|
|
46
53
|
@pry.config.pwn_ai_session_id = PWN::Sessions.create(title: 'console')[:id]
|
|
47
54
|
@pry.config.pwn_ai = true
|
|
@@ -258,25 +265,51 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
258
265
|
foreign&.join
|
|
259
266
|
end
|
|
260
267
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
268
|
+
[0, 3.1].each do |startup_delay|
|
|
269
|
+
it "leaves a provider blocked in native IO without waiting for that read to return (startup #{startup_delay}s)" do
|
|
270
|
+
entered = Queue.new
|
|
271
|
+
returned = Queue.new
|
|
272
|
+
provider = nil
|
|
273
|
+
started = nil
|
|
274
|
+
allow(PWN::AI::Agent::Loop).to receive(:call_engine) do
|
|
275
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
276
|
+
entered << Thread.current
|
|
277
|
+
sleep 8
|
|
278
|
+
returned << true
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
keys = "what color is a lemon?\n".chars
|
|
282
|
+
launched = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
283
|
+
launch(keys, startup_delay: startup_delay, render_delay: startup_delay.positive? ? 0.02 : 0) do
|
|
284
|
+
provider ||= entered.pop unless entered.empty?
|
|
285
|
+
if !started && provider&.status == 'sleep'
|
|
286
|
+
# Elapsed is painted before call_engine: it is not a provider barrier.
|
|
287
|
+
# Time from delivered Ctrl+C, excluding startup and per-key rendering.
|
|
288
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
289
|
+
"\u0003"
|
|
290
|
+
elsif @paint.any? { |row| row.include?('Request cancelled at a safe boundary') }
|
|
291
|
+
"\u0004"
|
|
292
|
+
else
|
|
293
|
+
Thread.pass
|
|
294
|
+
nil
|
|
295
|
+
end
|
|
277
296
|
end
|
|
297
|
+
expect(started).not_to be_nil
|
|
298
|
+
expect(started - launched).to be >= startup_delay
|
|
299
|
+
expect(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started).to be < 3
|
|
300
|
+
expect(returned).to be_empty
|
|
301
|
+
ensure
|
|
302
|
+
# Cancellation deliberately abandons masked IO. Release this fixture only
|
|
303
|
+
# after measuring return, then reap it before RSpec removes its mocks.
|
|
304
|
+
provider ||= entered.pop unless entered.empty?
|
|
305
|
+
begin
|
|
306
|
+
provider&.wakeup if provider&.alive?
|
|
307
|
+
rescue ThreadError
|
|
308
|
+
nil # The provider exited between alive? and wakeup.
|
|
309
|
+
end
|
|
310
|
+
provider&.join(2)
|
|
311
|
+
expect(provider).not_to be_alive if provider
|
|
278
312
|
end
|
|
279
|
-
expect(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started).to be < 3
|
|
280
313
|
end
|
|
281
314
|
|
|
282
315
|
it 'forwards tool input, preserves completed evidence before steering, and locks settings while running' do
|
|
@@ -342,7 +375,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
342
375
|
console.seed_request_history
|
|
343
376
|
'/verbose '.each_char { |key| console.handle(key) }
|
|
344
377
|
menu = console.instance_variable_get(:@menu)
|
|
345
|
-
expect(menu.map { |item| item[:label] }).to eq(%w[on
|
|
378
|
+
expect(menu.map { |item| item[:label] }).to eq(%w[off on])
|
|
346
379
|
draft = [editor.text.dup, editor.cursor]
|
|
347
380
|
console.handle(:up)
|
|
348
381
|
expect(console.instance_variable_get(:@menu_index)).to eq(1)
|
|
@@ -374,7 +407,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
374
407
|
'/verbose '.each_char { |key| console.handle(key) }
|
|
375
408
|
console.handle(:down)
|
|
376
409
|
console.handle("\t")
|
|
377
|
-
expect(editor.text.strip).to eq('/verbose
|
|
410
|
+
expect(editor.text.strip).to eq('/verbose on')
|
|
378
411
|
console.handle("\u0015")
|
|
379
412
|
'/verbose '.each_char { |key| console.handle(key) }
|
|
380
413
|
console.handle(:up)
|
|
@@ -503,11 +536,11 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
503
536
|
|
|
504
537
|
it 'centers capped retro artwork inside a larger cell-square canvas' do
|
|
505
538
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
506
|
-
allow(PWN::Banner).to receive(:mini_names).and_return([:
|
|
539
|
+
allow(PWN::Banner).to receive(:mini_names).and_return([:pacman])
|
|
507
540
|
allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0)
|
|
508
541
|
size = 24
|
|
509
542
|
canvas = console.banner_frame(size)
|
|
510
|
-
art = PWN::Banner.mini_frame(name: :
|
|
543
|
+
art = PWN::Banner.mini_frame(name: :pacman, frame: 0, width: size, height: size, branding: false)
|
|
511
544
|
expect(canvas.length).to eq(size)
|
|
512
545
|
expect(canvas.map(&:length)).to all(eq(size))
|
|
513
546
|
top = (size - art.length) / 2
|
|
@@ -535,6 +568,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
535
568
|
end
|
|
536
569
|
|
|
537
570
|
it 'carves a themed cell-square banner pane only when all settings still fit, keeping bounds and draft' do
|
|
571
|
+
allow(PWN::Banner).to receive(:mini_names).and_return([:falling_blocks])
|
|
538
572
|
PWN::Env[:ai] = { active: :ollama, tui: { theme: { category: 'magenta', header: 'white', border: 'blue', title: 'red' } },
|
|
539
573
|
ollama: { model: 'fixture', system_role_content: 'Complete functional settings.', reasoning_effort: 'high' } }
|
|
540
574
|
screen = double(erase: nil, refresh: nil, setpos: nil, addstr: nil)
|
|
@@ -577,7 +611,32 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
577
611
|
expect([console.instance_variable_get(:@editor).text, console.instance_variable_get(:@editor).cursor]).to eq(['retained draft', 4])
|
|
578
612
|
end
|
|
579
613
|
|
|
614
|
+
it 'keeps a full connected maze and one-cell actors in native minimum and typical console layouts' do
|
|
615
|
+
allow(PWN::Banner).to receive(:mini_names).and_return([:pacman])
|
|
616
|
+
PWN::Env[:ai] = { active: :ollama, ollama: { model: 'fixture' } }
|
|
617
|
+
[[100, 26], [120, 36]].each do |columns, rows|
|
|
618
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
619
|
+
console.instance_variable_set(:@width, columns)
|
|
620
|
+
console.instance_variable_set(:@height, rows)
|
|
621
|
+
console.instance_variable_set(:@focus, :animation)
|
|
622
|
+
allow(console).to receive(:put)
|
|
623
|
+
allow(console).to receive(:box)
|
|
624
|
+
allow(console).to receive(:mark_active)
|
|
625
|
+
expect(console.draw_header('ollama', 'fixture')).to eq(10)
|
|
626
|
+
game = console.instance_variable_get(:@banner_game)
|
|
627
|
+
frame = game.step
|
|
628
|
+
maze = frame[:state][:maze]
|
|
629
|
+
expect(maze.length).to eq(8)
|
|
630
|
+
expect(maze.map(&:length)).to all(eq(8))
|
|
631
|
+
expect(maze[2...-2].map { |row| row[2...-2] }.join.count('#')).to be >= 4
|
|
632
|
+
expect(game.cells(frame).flatten.count { |cell| cell[:foreground] == :red }).to eq(1)
|
|
633
|
+
game.key(:right)
|
|
634
|
+
expect(game.step[:state][:pacman]).to eq([2, 6])
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
|
|
580
638
|
it 'grows both game canvas dimensions with the header without recursively shrinking settings' do
|
|
639
|
+
allow(PWN::Banner).to receive(:mini_names).and_return([:falling_blocks])
|
|
581
640
|
PWN::Env[:ai] = { active: :ollama, ollama: { model: 'fixture', system_role_content: 'operator ' * 100, reasoning_effort: 'high' } }
|
|
582
641
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
583
642
|
console.instance_variable_set(:@width, 120)
|
|
@@ -669,9 +728,9 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
669
728
|
expect(console.instance_variable_get(:@banner_seed)).not_to eq(seed)
|
|
670
729
|
end
|
|
671
730
|
|
|
672
|
-
it 'samples Asteroids or
|
|
731
|
+
it 'samples Asteroids or Pac-Man once and caches the game-specific cadence outside the key path' do
|
|
673
732
|
allow(PWN::Banner).to receive(:mini_cells).and_call_original
|
|
674
|
-
%i[
|
|
733
|
+
%i[pacman asteroids].each do |name|
|
|
675
734
|
pry = Pry.new
|
|
676
735
|
pry.config.pwn_ai_session_id = 'fast-colored-session'
|
|
677
736
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: pry, input: StringIO.new, curses: nil, getch: nil)
|
|
@@ -684,7 +743,11 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
684
743
|
console.banner_frame(8, cells: true)
|
|
685
744
|
seed = console.instance_variable_get(:@banner_seed)
|
|
686
745
|
now += 1.21
|
|
687
|
-
|
|
746
|
+
if name == :pacman
|
|
747
|
+
expect(console).to receive(:automatic_banner).with(8, now).twice.and_call_original
|
|
748
|
+
else
|
|
749
|
+
expect(PWN::Banner).to receive(:mini_cells).with(name: name, frame: 24, width: 8, height: 8, seed: seed, branding: false).twice.and_call_original
|
|
750
|
+
end
|
|
688
751
|
2.times { console.banner_frame(8, cells: true) }
|
|
689
752
|
end
|
|
690
753
|
end
|
|
@@ -1078,6 +1141,111 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
1078
1141
|
expect([editor.text, editor.cursor]).to eq(['kept draft', 4])
|
|
1079
1142
|
end
|
|
1080
1143
|
|
|
1144
|
+
it 'paints an Asteroids turn on the next draw without waiting for its physics tick' do
|
|
1145
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1146
|
+
console.banner_frame(8, cells: true)
|
|
1147
|
+
console.instance_variable_set(:@banner_name, :asteroids)
|
|
1148
|
+
console.instance_variable_set(:@banner_frame_seconds, 0.05)
|
|
1149
|
+
console.instance_variable_set(:@focus, :animation)
|
|
1150
|
+
first = console.playable_banner(8, 10.001)
|
|
1151
|
+
console.handle(:left)
|
|
1152
|
+
expect(console.playable_banner(8, 10.002)).not_to eq(first)
|
|
1153
|
+
expect(console.playable_banner(8, 10.003)).to eq(console.playable_banner(8, 10.002))
|
|
1154
|
+
console.instance_variable_set(:@banner_game_frame, { state: { explosion: 12 } })
|
|
1155
|
+
game = console.instance_variable_get(:@banner_game)
|
|
1156
|
+
expect(game).not_to receive(:step)
|
|
1157
|
+
16.times { console.handle(:up) }
|
|
1158
|
+
console.playable_banner(8, 10.004)
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
it 'waits for Snake input and routes timed turns across the full pane instead of restarting' do
|
|
1162
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1163
|
+
console.banner_frame(8, cells: true)
|
|
1164
|
+
console.instance_variable_set(:@banner_name, :snake)
|
|
1165
|
+
console.instance_variable_set(:@banner_frame_seconds, 0.1)
|
|
1166
|
+
console.instance_variable_set(:@focus, :animation)
|
|
1167
|
+
first = console.playable_banner(8, 10.0)
|
|
1168
|
+
40.times { |tick| expect(console.playable_banner(8, 10.05 + (tick * 0.1))).to eq(first) }
|
|
1169
|
+
game = console.instance_variable_get(:@banner_game)
|
|
1170
|
+
frames = []
|
|
1171
|
+
allow(game).to(receive(:step).and_wrap_original { |method| method.call.tap { |frame| frames << frame } })
|
|
1172
|
+
console.handle(:right)
|
|
1173
|
+
console.playable_banner(8, 15.0)
|
|
1174
|
+
head = frames.last[:state][:body].first
|
|
1175
|
+
console.handle(:up)
|
|
1176
|
+
console.playable_banner(8, 15.05)
|
|
1177
|
+
expect(frames.length).to eq(1)
|
|
1178
|
+
console.playable_banner(8, 15.21)
|
|
1179
|
+
expect(frames.last[:state][:body].first).to eq([head[0], head[1] - 1])
|
|
1180
|
+
console.handle(:right)
|
|
1181
|
+
console.playable_banner(8, 15.41)
|
|
1182
|
+
expect(frames.last[:state][:body].first).to eq([head[0] + 1, head[1] - 1])
|
|
1183
|
+
expect(game.instance_variable_get(:@width)).to eq(8)
|
|
1184
|
+
expect(frames.last[:rows].drop(1).map(&:length)).to all(eq(16))
|
|
1185
|
+
expect(console.instance_variable_get(:@editor).text).to eq('')
|
|
1186
|
+
end
|
|
1187
|
+
|
|
1188
|
+
it 'cycles games with Ctrl+G only in animation focus and preserves swarm and modal priorities' do
|
|
1189
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1190
|
+
console.banner_frame(8, cells: true)
|
|
1191
|
+
console.instance_variable_set(:@banner_name, :falling_blocks)
|
|
1192
|
+
console.instance_variable_set(:@focus, :animation)
|
|
1193
|
+
editor = console.instance_variable_get(:@editor)
|
|
1194
|
+
editor.place('kept draft', 4)
|
|
1195
|
+
%i[snake pacman asteroids galaga frogger falling_blocks].each do |name|
|
|
1196
|
+
console.handle("\u0007")
|
|
1197
|
+
expect(console.instance_variable_get(:@banner_name)).to eq(name)
|
|
1198
|
+
expect(console.instance_variable_get(:@workspace)).to be_nil
|
|
1199
|
+
console.banner_frame(8, cells: true)
|
|
1200
|
+
expect(console.instance_variable_get(:@banner_game).instance_variable_get(:@name)).to eq(name.to_s)
|
|
1201
|
+
expect(console.footer_text).to include('^G=next game')
|
|
1202
|
+
expect(console.footer_text).to include('↑↓←→ start/steer · ● power') if name == :pacman
|
|
1203
|
+
expect(console.footer_text).to include('↑↓←→ move · Space=fire') if name == :galaga
|
|
1204
|
+
expect(console.footer_text).to include('↑↓←→ hop · logs → goals') if name == :frogger
|
|
1205
|
+
expect(console.operation_lines.join).to include('^S swarm')
|
|
1206
|
+
end
|
|
1207
|
+
console.instance_variable_set(:@details, 0)
|
|
1208
|
+
expect(console).to receive(:handle_details).with("\u0007")
|
|
1209
|
+
console.handle("\u0007")
|
|
1210
|
+
console.instance_variable_set(:@details, nil)
|
|
1211
|
+
editor.search_history
|
|
1212
|
+
expect(console).to receive(:handle_search).with("\u0007")
|
|
1213
|
+
console.handle("\u0007")
|
|
1214
|
+
editor.finish_search
|
|
1215
|
+
console.handle("\u0013")
|
|
1216
|
+
expect(console.instance_variable_get(:@workspace)).to be_a(Hash)
|
|
1217
|
+
console.handle("\u0007")
|
|
1218
|
+
expect(console.instance_variable_get(:@workspace)).to be_nil
|
|
1219
|
+
expect(console.instance_variable_get(:@banner_name)).to eq(:falling_blocks)
|
|
1220
|
+
console.handle("\e")
|
|
1221
|
+
console.handle("\u0007")
|
|
1222
|
+
expect(console.instance_variable_get(:@workspace)).to be_a(Hash)
|
|
1223
|
+
expect([editor.text, editor.cursor]).to eq(['kept draft', 4])
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
it 'delivers timed Pac-Man buffered arrows without consuming the mission draft or speeding up on repeats' do
|
|
1227
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1228
|
+
console.banner_frame(8, cells: true)
|
|
1229
|
+
console.instance_variable_set(:@banner_name, :pacman)
|
|
1230
|
+
console.instance_variable_set(:@banner_seed, 9)
|
|
1231
|
+
console.instance_variable_set(:@banner_frame_seconds, 0.1)
|
|
1232
|
+
console.instance_variable_set(:@focus, :animation)
|
|
1233
|
+
editor = console.instance_variable_get(:@editor)
|
|
1234
|
+
editor.place('kept draft', 4)
|
|
1235
|
+
console.playable_banner(7, 10.01)
|
|
1236
|
+
console.handle(:right)
|
|
1237
|
+
console.playable_banner(7, 10.11)
|
|
1238
|
+
expect(console.instance_variable_get(:@banner_game_frame)[:state][:pacman]).to eq([2, 5])
|
|
1239
|
+
console.handle(:up)
|
|
1240
|
+
10.times { console.playable_banner(7, 10.12) }
|
|
1241
|
+
expect(console.instance_variable_get(:@banner_game_frame)[:state][:pacman]).to eq([2, 5])
|
|
1242
|
+
[10.21, 10.31, 10.41, 10.51, 10.61, 10.71].each { |time| console.playable_banner(7, time) }
|
|
1243
|
+
expect(console.instance_variable_get(:@banner_game_frame)[:state][:pacman]).to eq([3, 4])
|
|
1244
|
+
console.handle("\e")
|
|
1245
|
+
expect([editor.text, editor.cursor]).to eq(['kept draft', 4])
|
|
1246
|
+
expect(console.instance_variable_get(:@focus)).to eq(:mission)
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1081
1249
|
it 'returns to the unchanged automatic animation when focus leaves the game' do
|
|
1082
1250
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1083
1251
|
allow(Process).to receive(:clock_gettime).and_return(10.0)
|
|
@@ -1092,6 +1260,28 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
1092
1260
|
expect(console.instance_variable_get(:@focus)).to eq(:mission)
|
|
1093
1261
|
end
|
|
1094
1262
|
|
|
1263
|
+
it 'streams the new arcade demos on the UI owner without a synchronous full replay build' do
|
|
1264
|
+
%i[pacman galaga frogger].each do |name|
|
|
1265
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1266
|
+
allow(PWN::Banner).to receive(:mini_names).and_return([name])
|
|
1267
|
+
allow(Process).to receive(:clock_gettime).and_return(10.0)
|
|
1268
|
+
expect(PWN::Banner).not_to receive(:mini_cells)
|
|
1269
|
+
first = console.banner_frame(32, cells: true)
|
|
1270
|
+
demo = console.instance_variable_get(:@banner_demo)
|
|
1271
|
+
expect(demo).to be_a(PWN::Banner::MiniGame)
|
|
1272
|
+
expect(console.instance_variable_get(:@banner_demo_frame)[:state][:started]).to be true
|
|
1273
|
+
expect(first.map(&:length)).to all(eq(32))
|
|
1274
|
+
expect(console.banner_frame(32, cells: true)).to eq(first)
|
|
1275
|
+
allow(Process).to receive(:clock_gettime).and_return(10.2)
|
|
1276
|
+
console.banner_frame(32, cells: true)
|
|
1277
|
+
expect(console.instance_variable_get(:@banner_demo)).to equal(demo)
|
|
1278
|
+
console.instance_variable_set(:@focus, :animation)
|
|
1279
|
+
console.banner_frame(32, cells: true)
|
|
1280
|
+
expect(console.instance_variable_get(:@banner_game)).not_to equal(demo)
|
|
1281
|
+
expect(console.instance_variable_get(:@banner_game_frame)[:state][:started]).to be false
|
|
1282
|
+
end
|
|
1283
|
+
end
|
|
1284
|
+
|
|
1095
1285
|
it 'keeps modal, search and cancellation priority above animation controls' do
|
|
1096
1286
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1097
1287
|
console.instance_variable_set(:@focus, :animation)
|
|
@@ -1330,7 +1520,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
1330
1520
|
it 'keeps slash parameter completion live across spaces and clears stale menus on submit' do
|
|
1331
1521
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1332
1522
|
'/verbose '.chars.each { |key| console.handle(key) }
|
|
1333
|
-
expect(console.instance_variable_get(:@menu).map { |item| item[:label] }).to eq(%w[on
|
|
1523
|
+
expect(console.instance_variable_get(:@menu).map { |item| item[:label] }).to eq(%w[off on])
|
|
1334
1524
|
console.handle('o')
|
|
1335
1525
|
console.handle('f')
|
|
1336
1526
|
console.handle('f')
|
|
@@ -1488,6 +1678,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
1488
1678
|
|
|
1489
1679
|
["\e", "\u0003"].each do |cancel_key|
|
|
1490
1680
|
it "keeps the rendered header and banner unchanged after reasoning cancellation #{cancel_key.inspect}" do
|
|
1681
|
+
allow(PWN::Banner).to receive(:mini_names).and_return([:falling_blocks])
|
|
1491
1682
|
allow(PWN::Plugins::REPL).to receive(:persist_ai_selection).and_return(false)
|
|
1492
1683
|
expect(PWN::Plugins::REPL).not_to receive(:pwn_ai_apply_model)
|
|
1493
1684
|
original = Marshal.dump(PWN::Env[:ai])
|
|
@@ -5,9 +5,69 @@ require 'spec_helper'
|
|
|
5
5
|
describe PWN::Plugins::REPL::AISwarm::Controller do
|
|
6
6
|
include_context 'pwn tmp sandbox'
|
|
7
7
|
|
|
8
|
+
it 'routes renamed commands and missing arguments through both console entry points' do
|
|
9
|
+
allow(PWN::AI::Agent::Swarm).to receive(:personas).and_return(scout: { role: 'Inspect' })
|
|
10
|
+
allow(PWN::AI::Agent::Swarm).to receive(:create).and_return(swarm_id: 'fixture')
|
|
11
|
+
allow(PWN::AI::Agent::Swarm).to receive(:ask).and_return(reply: 'Direct reply')
|
|
12
|
+
pry = Pry.new
|
|
13
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: pry, input: StringIO.new, curses: nil, getch: nil)
|
|
14
|
+
controller = described_class.new(session_id: 'fixture')
|
|
15
|
+
console.instance_variable_set(:@swarm, controller)
|
|
16
|
+
pry.config.pwn_ai_swarm = controller
|
|
17
|
+
['/swarm mission', '/swarm dm', '/swarm dm scout'].each do |line|
|
|
18
|
+
usage = line.include?('mission') ? 'usage: mission REQUEST' : 'usage: dm NAME REQUEST'
|
|
19
|
+
console.submit(line)
|
|
20
|
+
expect(console.instance_variable_get(:@timeline).last[1]).to include(usage)
|
|
21
|
+
expect { PWN::Plugins::REPL.pwn_ai_dispatch_slash!(request: line, pry: pry) }.to output(/#{usage}/).to_stdout
|
|
22
|
+
end
|
|
23
|
+
expect(controller.snapshot[:jobs]).to be_empty
|
|
24
|
+
%w[solve roster ask].each do |old|
|
|
25
|
+
expect(controller.execute(line: "/swarm #{old}")[:error]).to eq("unknown /swarm command: #{old}")
|
|
26
|
+
end
|
|
27
|
+
console.submit('/swarm agents')
|
|
28
|
+
expect(console.instance_variable_get(:@timeline).last[1]).to include('scout')
|
|
29
|
+
expect { PWN::Plugins::REPL.pwn_ai_dispatch_slash!(request: '/swarm agents', pry: pry) }.to output(/scout/).to_stdout
|
|
30
|
+
expect { PWN::Plugins::REPL.pwn_ai_dispatch_slash!(request: '/swarm dm scout inspect', pry: pry) }.to output(/queued/).to_stdout
|
|
31
|
+
controller.send(:workers).each(&:join)
|
|
32
|
+
expect(controller.snapshot[:jobs].last).to include(command: 'dm', result: 'Direct reply')
|
|
33
|
+
ensure
|
|
34
|
+
controller&.close
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'rejects a mission without an original request before starting work' do
|
|
38
|
+
controller = described_class.new(session_id: 'fixture')
|
|
39
|
+
expect(PWN::AI::Agent::Swarm).not_to receive(:create)
|
|
40
|
+
expect(controller.execute(line: '/swarm mission')).to include(ok: false, error: 'usage: mission REQUEST')
|
|
41
|
+
ensure
|
|
42
|
+
controller&.close
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'returns visible errors for malformed commands and synchronous backend failures' do
|
|
46
|
+
controller = described_class.new(session_id: 'fixture')
|
|
47
|
+
expect(controller.execute(line: '/swarm pause absent')).to include(ok: false, error: 'job not found')
|
|
48
|
+
ensure
|
|
49
|
+
controller&.close
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'preserves solve request bytes and exposes real team state and incomplete outcomes' do
|
|
53
|
+
request = "Write 'hello'\nthen verify --literal"
|
|
54
|
+
allow(PWN::AI::Agent::Swarm).to receive(:solve) do |opts|
|
|
55
|
+
expect(opts[:request]).to eq(request)
|
|
56
|
+
expect(opts[:steering]).to be_a(PWN::AI::Agent::Solve::Control)
|
|
57
|
+
opts[:on_state].call(roles: { protagonist: 'build', antagonist: 'done', verifier: 'waiting', integrator: 'waiting' })
|
|
58
|
+
{ status: 'incomplete', reply: 'INCOMPLETE: missing check' }
|
|
59
|
+
end
|
|
60
|
+
controller = described_class.new(session_id: 'fixture')
|
|
61
|
+
controller.execute(line: "/swarm mission #{request}")
|
|
62
|
+
controller.send(:workers).each(&:join)
|
|
63
|
+
expect(controller.snapshot[:jobs].first).to include(state: 'incomplete', team: include(roles: include(protagonist: 'build')))
|
|
64
|
+
ensure
|
|
65
|
+
controller&.close
|
|
66
|
+
end
|
|
67
|
+
|
|
8
68
|
it 'returns visible errors for malformed commands and synchronous backend failures' do
|
|
9
69
|
controller = described_class.new(session_id: 'fixture')
|
|
10
|
-
expect(controller.execute(line: '/swarm
|
|
70
|
+
expect(controller.execute(line: '/swarm dm scout "unfinished')[:ok]).to be(false)
|
|
11
71
|
allow(PWN::AI::Agent::Swarm).to receive(:create).and_raise(IOError, 'fixture storage unavailable')
|
|
12
72
|
expect(controller.execute(line: '/swarm create fixture')).to include(ok: false, error: 'IOError: fixture storage unavailable')
|
|
13
73
|
ensure
|
|
@@ -20,7 +80,7 @@ describe PWN::Plugins::REPL::AISwarm::Controller do
|
|
|
20
80
|
allow(PWN::AI::Agent::Swarm).to receive(:ask).and_return(reply: 'Observed fixture result')
|
|
21
81
|
controller = described_class.new(session_id: 'fixture')
|
|
22
82
|
expect(controller.roster).to include(include(name: 'scout', role: 'Inspect evidence', engine: 'ollama', model: 'fixture'))
|
|
23
|
-
queued = controller.execute(line: '/swarm
|
|
83
|
+
queued = controller.execute(line: '/swarm dm scout inspect')
|
|
24
84
|
controller.send(:workers).each(&:join)
|
|
25
85
|
expect(controller.snapshot[:jobs].first).to include(state: 'completed', result: 'Observed fixture result')
|
|
26
86
|
expect(controller.execute(line: "/swarm steer #{queued[:job_id]} again")[:ok]).to be(false)
|
|
@@ -40,7 +100,7 @@ describe PWN::Plugins::REPL::AISwarm::Controller do
|
|
|
40
100
|
{ reply: 'done' }
|
|
41
101
|
end
|
|
42
102
|
controller = described_class.new(session_id: 'fixture')
|
|
43
|
-
controller.execute(line: '/swarm
|
|
103
|
+
controller.execute(line: '/swarm dm scout inspect')
|
|
44
104
|
owner, worker, local, marker = entered.pop
|
|
45
105
|
expect(owner).to eq(worker)
|
|
46
106
|
expect(local).to eq(:copied)
|
|
@@ -64,7 +124,7 @@ describe PWN::Plugins::REPL::AISwarm::Controller do
|
|
|
64
124
|
controller.send(:workers).each(&:join)
|
|
65
125
|
expect(calls.first[:request]).to eq('--literal mission')
|
|
66
126
|
expect(calls.last[:request]).to include('reply from scout', '--literal mission')
|
|
67
|
-
controller.execute(line: '/swarm
|
|
127
|
+
controller.execute(line: '/swarm dm broken inspect')
|
|
68
128
|
controller.send(:workers).each(&:join)
|
|
69
129
|
expect(controller.snapshot[:jobs].last).to include(state: 'failed', error: 'IOError: fixture unavailable')
|
|
70
130
|
ensure
|
|
@@ -83,7 +143,7 @@ describe PWN::Plugins::REPL::AISwarm::Controller do
|
|
|
83
143
|
opts[:steering].checkpoint(messages: [])
|
|
84
144
|
end
|
|
85
145
|
controller = described_class.new(session_id: 'fixture')
|
|
86
|
-
job = controller.execute(line: '/swarm
|
|
146
|
+
job = controller.execute(line: '/swarm dm scout inspect')
|
|
87
147
|
entered.pop
|
|
88
148
|
expect(controller.execute(line: '/swarm create other')[:ok]).to be(false)
|
|
89
149
|
expect(controller.execute(line: '/swarm use other')[:ok]).to be(false)
|
|
@@ -92,7 +152,7 @@ describe PWN::Plugins::REPL::AISwarm::Controller do
|
|
|
92
152
|
expect(controller.snapshot[:jobs].first).to include(state: 'cancelled')
|
|
93
153
|
expect(foreign).to be_alive
|
|
94
154
|
controller.close
|
|
95
|
-
expect(controller.execute(line: '/swarm
|
|
155
|
+
expect(controller.execute(line: '/swarm dm scout again')[:ok]).to be(false)
|
|
96
156
|
ensure
|
|
97
157
|
controller&.close
|
|
98
158
|
foreign&.kill&.join
|
|
@@ -111,7 +171,7 @@ describe PWN::Plugins::REPL::AISwarm::Controller do
|
|
|
111
171
|
allow(PWN::AI::Agent::Swarm).to receive(:personas).and_return(scout: { role: 'scout' })
|
|
112
172
|
allow(PWN::AI::Agent::Swarm).to receive(:spawn).and_return(name: 'scout')
|
|
113
173
|
controller.execute(line: '/swarm spawn scout inspect evidence')
|
|
114
|
-
queued = controller.execute(line: '/swarm
|
|
174
|
+
queued = controller.execute(line: '/swarm dm scout inspect the fixture', on_event: ->(type, text) { events << [type, text] })
|
|
115
175
|
steering = entered.pop
|
|
116
176
|
expect(queued[:ok]).to eq(true)
|
|
117
177
|
expect(controller.snapshot[:jobs].first[:state]).to eq('running')
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -464,6 +464,7 @@
|
|
|
464
464
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.session_files Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.session_files`: "}]}
|
|
465
465
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.session_tool_budget Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.session_tool_budget`: "}]}
|
|
466
466
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.should_auto_introspect? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.should_auto_introspect?`: "}]}
|
|
467
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.solve_handoff? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.solve_handoff?`: "}]}
|
|
467
468
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.spill_tool_body Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.spill_tool_body`: "}]}
|
|
468
469
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.spill_tool_history! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.spill_tool_history!`: "}]}
|
|
469
470
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.squeeze_unquoted Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.squeeze_unquoted`: "}]}
|
|
@@ -855,6 +856,19 @@
|
|
|
855
856
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::SkillReview.sensitive? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::SkillReview.sensitive?`: "}]}
|
|
856
857
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::SkillReview.useful? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::SkillReview.useful?`: "}]}
|
|
857
858
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::SkillReview.verified_execution? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::SkillReview.verified_execution?`: "}]}
|
|
859
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve.authors`: "}]}
|
|
860
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve.help`: "}]}
|
|
861
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Control.new Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Control.new`: "}]}
|
|
862
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Control.begin_turn Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Control.begin_turn`: "}]}
|
|
863
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Control.check_stop Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Control.check_stop`: "}]}
|
|
864
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Control.checkpoint Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Control.checkpoint`: "}]}
|
|
865
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Control.guidance Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Control.guidance`: "}]}
|
|
866
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Control.model Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Control.model`: "}]}
|
|
867
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Control.pause Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Control.pause`: "}]}
|
|
868
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Control.stop Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Control.stop`: "}]}
|
|
869
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Control.submit Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Control.submit`: "}]}
|
|
870
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Run.new Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Run.new`: "}]}
|
|
871
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Solve::Run.run Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Solve::Run.run`: "}]}
|
|
858
872
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.ask Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.ask`: Supported Method Parameters\n\nreply = PWN::AI::Agent::Swarm.ask(\n\nname: 'required - persona name from ~/.pwn/agents.yml',\nrequest: 'required - what to ask/instruct the persona',\nswarm_id: 'optional - join an existing swarm (created if omitted)',\nto: 'optional - addressee recorded on the bus (default :all)',\non_tool: 'optional - ->(name, args, result) live-UI callback',\nsteering: 'optional - Loop::Steering for this persona turn',\nusage_observer: 'optional - callable receiving provider usage'\n\n)\n"}]}
|
|
859
873
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
860
874
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.broadcast Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.broadcast`: Supported Method Parameters\n\nresult = PWN::AI::Agent::Swarm.broadcast(\n\nrequest: 'required', names: 'optional - default all personas',\nswarm_id: 'optional'\n\n)\n"}]}
|
|
@@ -884,6 +898,7 @@
|
|
|
884
898
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.persona_session Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.persona_session`: "}]}
|
|
885
899
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.personas Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.personas`: Supported Method Parameters\n\npersonas = PWN::AI::Agent::Swarm.personas\n"}]}
|
|
886
900
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.retire Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.retire`: Supported Method Parameters\n\nPWN::AI::Agent::Swarm.retire(\n\nname: 'required - persona name',\nswarm_id: 'optional - swarm directory when local is true',\nlocal: 'optional - true retires only that swarm registry'\n\n)\n"}]}
|
|
901
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.solve Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.solve`: Supported Method Parameters\n\nPWN::AI::Agent::Swarm.solve(\n\nrequest: 'required - original objective, preserved for every role',\nworkspace: 'optional - artifact root, defaults to current directory',\nrounds: 'optional - bounded repair rounds, 1..10, default 3',\nswarm_id: 'optional - existing swarm registry',\nsteering: 'optional - Solve::Control coordinator control',\non_state: 'optional - observed role state callback',\non_tool: 'optional - tool observation callback',\nusage_observer: 'optional - provider usage callback'\n\n)\n"}]}
|
|
887
902
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.spawn Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.spawn`: Supported Method Parameters\n\nPWN::AI::Agent::Swarm.spawn(\n\nname: 'required - persona name (snake_case)',\nrole: 'required - system_role_content overlay for this persona',\ntoolsets: 'optional - Array of Registry toolset names',\nengine: 'optional - :openai / :anthropic / :grok / :gemini / :ollama / :openwebui',\nmodel: 'optional - exact model identifier (defaults to selected provider model)',\nmax_iters: 'optional - per-turn iteration cap for this persona'\n\n)\n"}]}
|
|
888
903
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.specialist_roles Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.specialist_roles`: "}]}
|
|
889
904
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Swarm.view_graph Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Swarm.view_graph`: "}]}
|
|
@@ -1736,16 +1751,33 @@
|
|
|
1736
1751
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_asteroids_snapshot Usage"},{"role":"assistant","content":"`PWN::Banner.mini_asteroids_snapshot`: "}]}
|
|
1737
1752
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_block_board Usage"},{"role":"assistant","content":"`PWN::Banner.mini_block_board`: "}]}
|
|
1738
1753
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_block_plan Usage"},{"role":"assistant","content":"`PWN::Banner.mini_block_plan`: "}]}
|
|
1739
|
-
{"messages":[{"role":"user","content":"PWN::Banner.mini_cells Usage"},{"role":"assistant","content":"`PWN::Banner.mini_cells`: Supported Method Parameters\n\nReturn rows of single-column Unicode block cells, with named terminal colors. Each fresh cell has glyph, foreground and background keys; no ANSI or IO. Square means terminal-cell dimensions, not physical pixels. Caller owns borders. Use mini_frame_seconds for cadence; 1800 frames span 90 or 180 seconds.
|
|
1754
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_cells Usage"},{"role":"assistant","content":"`PWN::Banner.mini_cells`: Supported Method Parameters\n\nReturn rows of single-column Unicode block cells, with named terminal colors. Each fresh cell has glyph, foreground and background keys; no ANSI or IO. Square means terminal-cell dimensions, not physical pixels. Caller owns borders. Use mini_frame_seconds for cadence; 1800 frames span 90 or 180 seconds. Blocks, Pac-Man, Galaga and Frogger fill up to 64 cells; other games cap at 16. PWN::Banner.mini_cells(\n\nname: 'optional - falling_blocks, snake, pacman, asteroids, galaga or frogger; legacy pong accepted; defaults to falling_blocks',\nframe: 'optional - integer index, defaults to zero and wraps at 1800',\nwidth: 'optional - columns clamped to 0..64 for blocks/Pac-Man/Galaga/Frogger, 0..16 otherwise; defaults to 16',\nheight: 'optional - rows clamped to 0..64 for blocks/Pac-Man/Galaga/Frogger, 0..16 otherwise; defaults to 16',\nseed: 'optional - integer local PRNG seed, defaults to 73; retain per session',\nbranding: 'optional - include the legacy wordmark, defaults to true; false reclaims its row'\n\n)\n"}]}
|
|
1740
1755
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_delta Usage"},{"role":"assistant","content":"`PWN::Banner.mini_delta`: "}]}
|
|
1756
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_dot Usage"},{"role":"assistant","content":"`PWN::Banner.mini_dot`: "}]}
|
|
1741
1757
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_emit Usage"},{"role":"assistant","content":"`PWN::Banner.mini_emit`: "}]}
|
|
1742
1758
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_falling_blocks Usage"},{"role":"assistant","content":"`PWN::Banner.mini_falling_blocks`: "}]}
|
|
1743
1759
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_fits? Usage"},{"role":"assistant","content":"`PWN::Banner.mini_fits?`: "}]}
|
|
1744
|
-
{"messages":[{"role":"user","content":"PWN::Banner.mini_frame Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frame`: Supported Method Parameters\n\nReturn fresh ASCII rows for decorative retro loops, not interactive games. Use equal width and height for a square; 5..16 retains the complete artwork. Smaller panes degrade to a wordmark. Caller owns randomness, clock and IO. PWN::Banner.mini_frame(\n\nname: 'optional - falling_blocks, snake,
|
|
1760
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_frame Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frame`: Supported Method Parameters\n\nReturn fresh ASCII rows for decorative retro loops, not interactive games. Use equal width and height for a square; 5..16 retains the complete artwork. Smaller panes degrade to a wordmark. Caller owns randomness, clock and IO. PWN::Banner.mini_frame(\n\nname: 'optional - falling_blocks, snake, pacman, asteroids, galaga or frogger; legacy pong accepted; defaults to falling_blocks',\nframe: 'optional - integer frame index, defaults to zero and wraps at 60',\nwidth: 'optional - output columns clamped to zero through 16, defaults to 16',\nheight: 'optional - output rows clamped to zero through 16, defaults to 16',\nbranding: 'optional - include the legacy wordmark, defaults to true; false reclaims its row'\n\n)\n"}]}
|
|
1745
1761
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_frame_seconds Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frame_seconds`: Supported Method Parameters\n\nReturn the presentation interval; simulations preserve their gameplay speed. PWN::Banner.mini_frame_seconds(\n\nname: 'optional - animation name, defaults to falling_blocks'\n\n)\n"}]}
|
|
1746
1762
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_freeze Usage"},{"role":"assistant","content":"`PWN::Banner.mini_freeze`: "}]}
|
|
1763
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_frogger_contact Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frogger_contact`: "}]}
|
|
1764
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_frogger_demo Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frogger_demo`: "}]}
|
|
1765
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_frogger_move Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frogger_move`: "}]}
|
|
1766
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_frogger_round Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frogger_round`: "}]}
|
|
1767
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_frogger_snapshot Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frogger_snapshot`: "}]}
|
|
1768
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_galaga_move Usage"},{"role":"assistant","content":"`PWN::Banner.mini_galaga_move`: "}]}
|
|
1769
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_galaga_round Usage"},{"role":"assistant","content":"`PWN::Banner.mini_galaga_round`: "}]}
|
|
1770
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_galaga_snapshot Usage"},{"role":"assistant","content":"`PWN::Banner.mini_galaga_snapshot`: "}]}
|
|
1747
1771
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_landing Usage"},{"role":"assistant","content":"`PWN::Banner.mini_landing`: "}]}
|
|
1748
1772
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_names Usage"},{"role":"assistant","content":"`PWN::Banner.mini_names`: Supported Method Parameters\n\nList animation names; callers may sample once and retain the session choice. PWN::Banner.mini_names\n"}]}
|
|
1773
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_pacman Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pacman`: "}]}
|
|
1774
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_pacman_contact Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pacman_contact`: "}]}
|
|
1775
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_pacman_ghosts Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pacman_ghosts`: "}]}
|
|
1776
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_pacman_move Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pacman_move`: "}]}
|
|
1777
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_pacman_neighbors Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pacman_neighbors`: "}]}
|
|
1778
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_pacman_round Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pacman_round`: "}]}
|
|
1779
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_pacman_route Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pacman_route`: "}]}
|
|
1780
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_pacman_snapshot Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pacman_snapshot`: "}]}
|
|
1749
1781
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_pixel Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pixel`: "}]}
|
|
1750
1782
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_play_piece Usage"},{"role":"assistant","content":"`PWN::Banner.mini_play_piece`: "}]}
|
|
1751
1783
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_pong Usage"},{"role":"assistant","content":"`PWN::Banner.mini_pong`: "}]}
|
|
@@ -1756,6 +1788,9 @@
|
|
|
1756
1788
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_rotations Usage"},{"role":"assistant","content":"`PWN::Banner.mini_rotations`: "}]}
|
|
1757
1789
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_simulate_asteroids Usage"},{"role":"assistant","content":"`PWN::Banner.mini_simulate_asteroids`: "}]}
|
|
1758
1790
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_simulate_falling_blocks Usage"},{"role":"assistant","content":"`PWN::Banner.mini_simulate_falling_blocks`: "}]}
|
|
1791
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_simulate_frogger Usage"},{"role":"assistant","content":"`PWN::Banner.mini_simulate_frogger`: "}]}
|
|
1792
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_simulate_galaga Usage"},{"role":"assistant","content":"`PWN::Banner.mini_simulate_galaga`: "}]}
|
|
1793
|
+
{"messages":[{"role":"user","content":"PWN::Banner.mini_simulate_pacman Usage"},{"role":"assistant","content":"`PWN::Banner.mini_simulate_pacman`: "}]}
|
|
1759
1794
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_simulate_pong Usage"},{"role":"assistant","content":"`PWN::Banner.mini_simulate_pong`: "}]}
|
|
1760
1795
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_simulate_snake Usage"},{"role":"assistant","content":"`PWN::Banner.mini_simulate_snake`: "}]}
|
|
1761
1796
|
{"messages":[{"role":"user","content":"PWN::Banner.mini_snake Usage"},{"role":"assistant","content":"`PWN::Banner.mini_snake`: "}]}
|
|
@@ -3317,6 +3352,7 @@
|
|
|
3317
3352
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.new Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.new`: "}]}
|
|
3318
3353
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.accept_completion Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.accept_completion`: "}]}
|
|
3319
3354
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.add Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.add`: "}]}
|
|
3355
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.automatic_banner Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.automatic_banner`: "}]}
|
|
3320
3356
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.banner_frame Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.banner_frame`: "}]}
|
|
3321
3357
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.begin_request_metrics Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.begin_request_metrics`: "}]}
|
|
3322
3358
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.bounded_event Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.bounded_event`: "}]}
|
|
@@ -3379,6 +3415,7 @@
|
|
|
3379
3415
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.seed_request_history Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.seed_request_history`: "}]}
|
|
3380
3416
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.send_tool_input Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.send_tool_input`: "}]}
|
|
3381
3417
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.setup_colors Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.setup_colors`: "}]}
|
|
3418
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.solve_rows Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.solve_rows`: "}]}
|
|
3382
3419
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.start_request Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.start_request`: "}]}
|
|
3383
3420
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.state Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.state`: "}]}
|
|
3384
3421
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.stop_worker Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.stop_worker`: "}]}
|