pwn 0.5.778 → 0.5.780

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3bd715a7f2fae3e39f56f1c2e0d47629de11144461591acbfcbc7399e8a1da9a
4
- data.tar.gz: 80b713ca6f117ba5f9862416f2796f777e3c4a1e1bba68fdf05fda02ea758635
3
+ metadata.gz: 3207a135c108d0af20cc351965b217a3235abb0eaf67a31d244e3779ed6b72e5
4
+ data.tar.gz: 750df1371f8eb0fbab32b66e88b7568dbac4a49de7dc4020fc018a9957053acc
5
5
  SHA512:
6
- metadata.gz: e17ff89a41133b10913a559880f90220940fa02ae6e74694e6c914d1c74af793d94907b05bd5655f7a9ee27afac498e896888c2d8a0c206381e7910ed8233dca
7
- data.tar.gz: acc1ceac0c1e58354e730a115e06d312d5873cabaf1775ab7f39149ebfa99bb20094e83eae12b9e33108d046bcba61ab0887b7c869471fe9c0a9c655d29a2c72
6
+ metadata.gz: b10627e1329fa50ac482a49b1013ab455f52eac1a943d92ef7f9144bb93625e742935e0ad2caa1294f3d10a3c46d758b0de50695463e138af76ed7f61871438b
7
+ data.tar.gz: 3f55e834e447b8d51b8a28bd0c71d97498e577d49d7dadfea2a5ceda5e5bfa3876225d27f0c238b02dce690ea82d48612d29fd8a45130cea8213fc4a5650ce0a
data/Gemfile CHANGED
@@ -49,7 +49,7 @@ gem 'jwt', '3.3.0'
49
49
  gem 'libusb', '0.8.0'
50
50
  gem 'luhn', '3.0.0'
51
51
  gem 'mail', '2.9.1'
52
- gem 'mcp', '1.8.0'
52
+ gem 'mcp', '1.8.1'
53
53
  gem 'meshtastic', '0.0.188'
54
54
  gem 'metasm', '1.0.6'
55
55
  gem 'mongo', '2.26.0'
@@ -69,7 +69,7 @@ gem 'packetfu', '2.0.0'
69
69
  gem 'packetgen', '4.1.1'
70
70
  gem 'pdf-reader', '2.16.0'
71
71
  gem 'pg', '1.7.0'
72
- gem 'pry', '0.16.0'
72
+ gem 'pry', '0.16.1'
73
73
  gem 'pry-doc', '1.7.0'
74
74
  gem 'rake', '13.4.2'
75
75
  gem 'rb-readline', '0.5.5'
@@ -94,7 +94,7 @@ gem 'selenium-webdriver', '4.51.0'
94
94
  gem 'slack-ruby-client', '3.3.0'
95
95
  gem 'socksify', '1.8.1'
96
96
  gem 'spreadsheet', '1.3.5'
97
- gem 'sqlite3', '2.10.0'
97
+ gem 'sqlite3', '2.10.1'
98
98
  gem 'thin', '2.0.1'
99
99
  gem 'tty-prompt', '0.23.1'
100
100
  gem 'tty-spinner', '0.9.3'
@@ -2195,7 +2195,8 @@ module PWN
2195
2195
  pairs.each do |pair|
2196
2196
  tool = pair.find { |m| m[:role].to_s == 'tool' }
2197
2197
  prev = collapsed.last&.find { |m| m[:role].to_s == 'tool' }
2198
- next if tool && prev && tool[:content].to_s.strip == prev[:content].to_s.strip
2198
+ # Compare raw bytes: existing history may contain malformed tool output.
2199
+ next if tool && prev && tool[:content].to_s.b.strip == prev[:content].to_s.b.strip
2199
2200
 
2200
2201
  collapsed << pair
2201
2202
  end
@@ -2241,8 +2242,10 @@ module PWN
2241
2242
  FileUtils.mkdir_p(dir)
2242
2243
  path = File.join(dir, "#{digest[0, 12]}.txt")
2243
2244
  File.binwrite(path, text) unless File.file?(path)
2244
- head = text.byteslice(0, 2_048).to_s
2245
- tail = text.bytesize > 3_072 ? text.byteslice(-1_024, 1_024).to_s : ''
2245
+ # Keep the artifact/digest byte-exact; only the displayed excerpt is text.
2246
+ # Byte slicing can bisect a multibyte character at either boundary.
2247
+ head = text.byteslice(0, 2_048).to_s.dup.force_encoding(Encoding::UTF_8).scrub
2248
+ tail = text.bytesize > 3_072 ? text.byteslice(-1_024, 1_024).to_s.dup.force_encoding(Encoding::UTF_8).scrub : ''
2246
2249
  mid = tail.empty? ? '' : "\n...\n#{tail}"
2247
2250
  "#{head}#{mid}\n[compacted path=#{path} sha256=#{digest} bytes=#{text.bytesize} ref=#{path}]"
2248
2251
  rescue StandardError
@@ -2624,9 +2624,9 @@ class << PWN::Plugins::REPL # rubocop:disable Metrics/ClassLength
2624
2624
  value = opts[:value].to_s
2625
2625
  value = '—' if value.empty?
2626
2626
  prefix = "#{opts[:label]}: "
2627
- return [{ text: "#{prefix}#{value}" }] if Unicode::DisplayWidth.of("#{prefix}#{value}") <= width
2627
+ return [{ text: "#{prefix}#{value}", label: prefix.rstrip }] if Unicode::DisplayWidth.of("#{prefix}#{value}") <= width
2628
2628
 
2629
- [{ text: prefix.rstrip }].concat(mesh_wrap_text(text: value, width: width).map { |line| { text: line } })
2629
+ [{ text: prefix.rstrip, label: prefix.rstrip }].concat(mesh_wrap_text(text: value, width: width).map { |line| { text: line } })
2630
2630
  end
2631
2631
 
2632
2632
  def mesh_gps_labeled_rows(opts = {})
@@ -2637,9 +2637,9 @@ class << PWN::Plugins::REPL # rubocop:disable Metrics/ClassLength
2637
2637
  coord, url = label.split(' >> ', 2)
2638
2638
  prefix = 'GPS: '
2639
2639
  if Unicode::DisplayWidth.of("#{prefix}#{coord}") <= width
2640
- [{ text: "#{prefix}#{coord}", link: true, link_at: prefix.length, url: url }]
2640
+ [{ text: "#{prefix}#{coord}", label: 'GPS:', link: true, link_at: prefix.length, url: url }]
2641
2641
  else
2642
- [{ text: 'GPS:' }, { text: coord, link: true, url: url }]
2642
+ [{ text: 'GPS:', label: 'GPS:' }, { text: coord, link: true, url: url }]
2643
2643
  end
2644
2644
  end
2645
2645
 
@@ -2964,7 +2964,12 @@ class << PWN::Plugins::REPL # rubocop:disable Metrics/ClassLength
2964
2964
  at = line[:link_at].to_i
2965
2965
  prefix = text[0, at].to_s
2966
2966
  coord = text[at..] || ''
2967
- win.addstr(prefix) unless prefix.empty?
2967
+ unless prefix.empty?
2968
+ win.attron(Curses.color_pair(24) | Curses::A_BOLD)
2969
+ win.addstr(prefix.rstrip)
2970
+ win.attroff(Curses.color_pair(24) | Curses::A_BOLD)
2971
+ win.addstr(prefix[prefix.rstrip.length..])
2972
+ end
2968
2973
  win.attron(Curses::A_UNDERLINE)
2969
2974
  win.addstr(coord)
2970
2975
  win.attroff(Curses::A_UNDERLINE)
@@ -2973,9 +2978,11 @@ class << PWN::Plugins::REPL # rubocop:disable Metrics/ClassLength
2973
2978
  hit[:pane] = opts[:pane]
2974
2979
  opts[:hits] << hit
2975
2980
  elsif line[:label] && win.respond_to?(:attron)
2976
- win.attron(Curses.color_pair(20) | Curses::A_BOLD)
2977
- win.addstr(text)
2978
- win.attroff(Curses.color_pair(20) | Curses::A_BOLD)
2981
+ label = text[0, line[:label].to_s.length]
2982
+ win.attron(Curses.color_pair(24) | Curses::A_BOLD)
2983
+ win.addstr(label)
2984
+ win.attroff(Curses.color_pair(24) | Curses::A_BOLD)
2985
+ win.addstr(text[label.length..].to_s)
2979
2986
  else
2980
2987
  win.addstr(text)
2981
2988
  end
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.778'
4
+ VERSION = '0.5.780'
5
5
  end
@@ -1304,6 +1304,45 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
1304
1304
  expect(src).not_to match(/compact_history!\(messages: messages\) if local/)
1305
1305
  end
1306
1306
 
1307
+ it 'keeps UTF-8 previews valid across repeated history compaction' do
1308
+ Dir.mktmpdir('pwn-history-encoding') do |dir|
1309
+ allow(Dir).to receive(:home).and_return(dir)
1310
+ messages = [{ role: 'system', content: 'sys' }, { role: 'user', content: 'goal' }]
1311
+ 8.times do |n|
1312
+ messages << { role: 'assistant', content: '', tool_calls: [{ id: "c#{n}" }] }
1313
+ messages << { role: 'tool', tool_call_id: "c#{n}", content: "a#{'é' * 3000}#{n}" }
1314
+ end
1315
+ 2.times do
1316
+ expect { described_class.send(:compact_history!, messages: messages) }.not_to output(/compact_history swallowed/).to_stderr
1317
+ expect(messages.all? { |msg| msg[:content].valid_encoding? }).to be(true)
1318
+ end
1319
+ end
1320
+ end
1321
+
1322
+ it 'preserves raw spilled bytes while rendering malformed and binary-tagged text safely' do
1323
+ Dir.mktmpdir('pwn-history-binary') do |dir|
1324
+ allow(Dir).to receive(:home).and_return(dir)
1325
+ ["a#{'é' * 3000}".b, "a\xff#{'é' * 3000}"].each do |text|
1326
+ preview = described_class.send(:spill_tool_body, text: text)
1327
+ expect(preview.encoding).to eq(Encoding::UTF_8)
1328
+ expect(preview.valid_encoding?).to be(true)
1329
+ expect(preview).to include('é', '[compacted path=')
1330
+ path = preview[/\[compacted path=(.*?) sha256=/, 1]
1331
+ expect(File.binread(path)).to eq(text.b)
1332
+ end
1333
+ end
1334
+ end
1335
+
1336
+ it 'compacts existing malformed tool history without swallowing encoding errors' do
1337
+ messages = [{ role: 'system', content: 'sys' }, { role: 'user', content: 'goal' }]
1338
+ 8.times do |n|
1339
+ messages << { role: 'assistant', tool_calls: [{ id: "c#{n}" }] }
1340
+ messages << { role: 'tool', tool_call_id: "c#{n}", content: "invalid\xff#{n}" }
1341
+ end
1342
+ expect { described_class.send(:compact_history!, messages: messages) }.not_to output(/compact_history swallowed/).to_stderr
1343
+ expect(messages.count { |msg| msg[:role] == 'tool' }).to eq(6)
1344
+ end
1345
+
1307
1346
  it 'shrinks tool bodies when history is fat' do
1308
1347
  msgs = [
1309
1348
  { role: 'system', content: 'sys' },
@@ -4,6 +4,21 @@ require 'spec_helper'
4
4
  require 'meshtastic'
5
5
 
6
6
  describe PWN::Plugins::REPL, 'mesh primary display, MQTT sender, and GPS link' do
7
+ it 'paints field labels bold white without changing value attributes' do
8
+ attrs = 0
9
+ painted = []
10
+ allow(Curses).to receive(:color_pair).with(20).and_return(0)
11
+ allow(Curses).to receive(:color_pair).with(24).and_return(256)
12
+ allow(details).to receive(:attron) { |value| attrs |= value }
13
+ allow(details).to receive(:attroff) { |value| attrs &= ~value }
14
+ allow(details).to receive(:addstr) { |text| painted << [text, attrs] }
15
+ rows = described_class.send(:mesh_value_rows, label: 'Node ID', value: '!00001337', width: 28)
16
+ rows += described_class.send(:mesh_value_rows, label: 'GPS', value: 'N/A', width: 28)
17
+ described_class.send(:mesh_paint_side, win: details, title: 'SENDER', focus: :sndr, rows: rows, hits: [])
18
+ expect(painted).to include(['Node ID:', 256 | Curses::A_BOLD], ['GPS:', 256 | Curses::A_BOLD])
19
+ expect(painted).to include([' !00001337', 0], [' N/A', 0])
20
+ end
21
+
7
22
  let(:env) do
8
23
  { transport: 'serial', mqtt: { host: 'offline.invalid', region: 'US' },
9
24
  channel: { active: 'LongFast', LongFast: { psk: 'AQ==', region: 'US', topic: '2/e/LongFast/#', from: '!00001337' } } }
@@ -224,7 +239,7 @@ describe PWN::Plugins::REPL, 'mesh primary display, MQTT sender, and GPS link' d
224
239
  colors = []
225
240
  allow(details).to receive(:attron) { |attr| colors << attr }
226
241
  described_class.send(:mesh_handle_rx, env: env, obj: radio, msg: { packet: packet, mesh_transport: :serial })
227
- painted = details.lines.join("\n")
242
+ painted = details.lines.join
228
243
  expect(painted).to include('SENDER', 'Node ID: !421f6f22', 'GPS:')
229
244
  expect(painted).not_to include('VIA')
230
245
  node_num = 0x421f6f22
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.778
4
+ version: 0.5.780
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -505,14 +505,14 @@ dependencies:
505
505
  requirements:
506
506
  - - '='
507
507
  - !ruby/object:Gem::Version
508
- version: 1.8.0
508
+ version: 1.8.1
509
509
  type: :runtime
510
510
  prerelease: false
511
511
  version_requirements: !ruby/object:Gem::Requirement
512
512
  requirements:
513
513
  - - '='
514
514
  - !ruby/object:Gem::Version
515
- version: 1.8.0
515
+ version: 1.8.1
516
516
  - !ruby/object:Gem::Dependency
517
517
  name: meshtastic
518
518
  requirement: !ruby/object:Gem::Requirement
@@ -785,14 +785,14 @@ dependencies:
785
785
  requirements:
786
786
  - - '='
787
787
  - !ruby/object:Gem::Version
788
- version: 0.16.0
788
+ version: 0.16.1
789
789
  type: :runtime
790
790
  prerelease: false
791
791
  version_requirements: !ruby/object:Gem::Requirement
792
792
  requirements:
793
793
  - - '='
794
794
  - !ruby/object:Gem::Version
795
- version: 0.16.0
795
+ version: 0.16.1
796
796
  - !ruby/object:Gem::Dependency
797
797
  name: pry-doc
798
798
  requirement: !ruby/object:Gem::Requirement
@@ -1121,14 +1121,14 @@ dependencies:
1121
1121
  requirements:
1122
1122
  - - '='
1123
1123
  - !ruby/object:Gem::Version
1124
- version: 2.10.0
1124
+ version: 2.10.1
1125
1125
  type: :development
1126
1126
  prerelease: false
1127
1127
  version_requirements: !ruby/object:Gem::Requirement
1128
1128
  requirements:
1129
1129
  - - '='
1130
1130
  - !ruby/object:Gem::Version
1131
- version: 2.10.0
1131
+ version: 2.10.1
1132
1132
  - !ruby/object:Gem::Dependency
1133
1133
  name: thin
1134
1134
  requirement: !ruby/object:Gem::Requirement