pwn 0.5.618 → 0.5.620

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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +14 -6
  4. data/bin/pwn_gqrx_scanner +25 -5
  5. data/documentation/Agent-Tool-Registry.md +3 -3
  6. data/documentation/Diagrams.md +6 -2
  7. data/documentation/Extrospection.md +30 -11
  8. data/documentation/Home.md +5 -4
  9. data/documentation/How-PWN-Works.md +7 -4
  10. data/documentation/Mistakes.md +96 -0
  11. data/documentation/Persistence.md +3 -1
  12. data/documentation/SDR.md +23 -5
  13. data/documentation/Skills-Memory-Learning.md +15 -5
  14. data/documentation/diagrams/agent-tool-registry.svg +94 -92
  15. data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
  16. data/documentation/diagrams/dot/extrospection-world-awareness.dot +9 -7
  17. data/documentation/diagrams/dot/memory-skills-detailed.dot +12 -5
  18. data/documentation/diagrams/dot/mistakes-negative-feedback.dot +93 -0
  19. data/documentation/diagrams/dot/overall-pwn-architecture.dot +7 -5
  20. data/documentation/diagrams/dot/persistence-filesystem.dot +3 -2
  21. data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +19 -7
  22. data/documentation/diagrams/dot/sdr-radio-flow.dot +13 -11
  23. data/documentation/diagrams/extrospection-world-awareness.svg +111 -93
  24. data/documentation/diagrams/memory-skills-detailed.svg +146 -97
  25. data/documentation/diagrams/mistakes-negative-feedback.svg +266 -0
  26. data/documentation/diagrams/overall-pwn-architecture.svg +184 -173
  27. data/documentation/diagrams/persistence-filesystem.svg +77 -62
  28. data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +217 -149
  29. data/documentation/diagrams/sdr-radio-flow.svg +76 -56
  30. data/documentation/pwn-ai-Agent.md +28 -15
  31. data/lib/pwn/ai/agent/extrospection.rb +65 -6
  32. data/lib/pwn/ai/agent/learning.rb +67 -7
  33. data/lib/pwn/ai/agent/loop.rb +72 -6
  34. data/lib/pwn/ai/agent/mistakes.rb +369 -0
  35. data/lib/pwn/ai/agent/prompt_builder.rb +10 -1
  36. data/lib/pwn/ai/agent/tools/extrospection.rb +2 -2
  37. data/lib/pwn/ai/agent/tools/mistakes.rb +132 -0
  38. data/lib/pwn/ai/agent.rb +1 -0
  39. data/lib/pwn/plugins/jira_data_center.rb +2 -2
  40. data/lib/pwn/sdr/decoder/adsb.rb +101 -0
  41. data/lib/pwn/sdr/decoder/apt.rb +75 -0
  42. data/lib/pwn/sdr/decoder/bluetooth.rb +78 -0
  43. data/lib/pwn/sdr/decoder/dect.rb +84 -0
  44. data/lib/pwn/sdr/decoder/gps.rb +83 -0
  45. data/lib/pwn/sdr/decoder/gsm.rb +50 -29
  46. data/lib/pwn/sdr/decoder/iridium.rb +81 -0
  47. data/lib/pwn/sdr/decoder/lora.rb +87 -0
  48. data/lib/pwn/sdr/decoder/lte.rb +88 -0
  49. data/lib/pwn/sdr/decoder/morse.rb +64 -0
  50. data/lib/pwn/sdr/decoder/p25.rb +71 -0
  51. data/lib/pwn/sdr/decoder/pager.rb +77 -0
  52. data/lib/pwn/sdr/decoder/pocsag.rb +26 -11
  53. data/lib/pwn/sdr/decoder/rfid.rb +88 -0
  54. data/lib/pwn/sdr/decoder/rtl433.rb +95 -0
  55. data/lib/pwn/sdr/decoder/rtty.rb +66 -0
  56. data/lib/pwn/sdr/decoder/wifi.rb +98 -0
  57. data/lib/pwn/sdr/decoder/zigbee.rb +96 -0
  58. data/lib/pwn/sdr/decoder.rb +67 -7
  59. data/lib/pwn/sdr/frequency_allocation.rb +82 -41
  60. data/lib/pwn/sdr/gqrx.rb +186 -95
  61. data/lib/pwn/version.rb +1 -1
  62. data/spec/lib/pwn/ai/agent/mistakes_spec.rb +36 -0
  63. data/spec/lib/pwn/ai/agent/tools/mistakes_spec.rb +12 -0
  64. data/spec/lib/pwn/sdr/decoder/adsb_spec.rb +15 -0
  65. data/spec/lib/pwn/sdr/decoder/apt_spec.rb +15 -0
  66. data/spec/lib/pwn/sdr/decoder/bluetooth_spec.rb +15 -0
  67. data/spec/lib/pwn/sdr/decoder/dect_spec.rb +15 -0
  68. data/spec/lib/pwn/sdr/decoder/gps_spec.rb +15 -0
  69. data/spec/lib/pwn/sdr/decoder/iridium_spec.rb +15 -0
  70. data/spec/lib/pwn/sdr/decoder/lora_spec.rb +15 -0
  71. data/spec/lib/pwn/sdr/decoder/lte_spec.rb +15 -0
  72. data/spec/lib/pwn/sdr/decoder/morse_spec.rb +15 -0
  73. data/spec/lib/pwn/sdr/decoder/p25_spec.rb +15 -0
  74. data/spec/lib/pwn/sdr/decoder/pager_spec.rb +15 -0
  75. data/spec/lib/pwn/sdr/decoder/rfid_spec.rb +15 -0
  76. data/spec/lib/pwn/sdr/decoder/rtl433_spec.rb +15 -0
  77. data/spec/lib/pwn/sdr/decoder/rtty_spec.rb +15 -0
  78. data/spec/lib/pwn/sdr/decoder/wifi_spec.rb +15 -0
  79. data/spec/lib/pwn/sdr/decoder/zigbee_spec.rb +15 -0
  80. data/third_party/pwn_rdoc.jsonl +92 -3
  81. metadata +44 -6
  82. data/README.md.bak +0 -200
@@ -4,143 +4,163 @@
4
4
  <!-- Generated by graphviz version 14.1.2 (0)
5
5
  -->
6
6
  <!-- Title: PWN_SDR Pages: 1 -->
7
- <svg width="784pt" height="368pt"
8
- viewBox="0.00 0.00 784.00 368.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9
- <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(43.2 325.2)">
7
+ <svg width="917pt" height="599pt"
8
+ viewBox="0.00 0.00 917.00 599.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9
+ <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(43.2 556.2)">
10
10
  <title>PWN_SDR</title>
11
- <polygon fill="#0f172a" stroke="none" points="-43.2,43.2 -43.2,-325.2 740.45,-325.2 740.45,43.2 -43.2,43.2"/>
12
- <text xml:space="preserve" text-anchor="start" x="103.38" y="-260" font-family="sans-Serif" font-weight="bold" font-size="20.00" fill="#e2e8f0">PWN::SDR — Software&#45;Defined Radio &amp; RF Hacking</text>
13
- <text xml:space="preserve" text-anchor="start" x="196" y="-248.55" font-family="sans-Serif" font-size="11.00" fill="#94a3b8">GQRX · FlipperZero · RFIDler · SonMicro · FrequencyAllocation</text>
11
+ <polygon fill="#0f172a" stroke="none" points="-43.2,43.2 -43.2,-556.2 873.45,-556.2 873.45,43.2 -43.2,43.2"/>
12
+ <text xml:space="preserve" text-anchor="start" x="169.88" y="-491" font-family="sans-Serif" font-weight="bold" font-size="20.00" fill="#e2e8f0">PWN::SDR — Software&#45;Defined Radio &amp; RF Hacking</text>
13
+ <text xml:space="preserve" text-anchor="start" x="197.62" y="-479.55" font-family="sans-Serif" font-size="11.00" fill="#94a3b8">GQRX · FlipperZero · RFIDler · SonMicro · FrequencyAllocation · Decoder::* (20 protocols)</text>
14
14
  <g id="clust1" class="cluster">
15
15
  <title>cluster_hw</title>
16
- <path fill="#022c22" stroke="#047857" d="M183.25,-16C183.25,-16 280.5,-16 280.5,-16 286.5,-16 292.5,-22 292.5,-28 292.5,-28 292.5,-223 292.5,-223 292.5,-229 286.5,-235 280.5,-235 280.5,-235 183.25,-235 183.25,-235 177.25,-235 171.25,-229 171.25,-223 171.25,-223 171.25,-28 171.25,-28 171.25,-22 177.25,-16 183.25,-16"/>
17
- <text xml:space="preserve" text-anchor="middle" x="231.88" y="-212" font-family="sans-Serif" font-size="20.00" fill="#a7f3d0">Hardware</text>
16
+ <path fill="#022c22" stroke="#047857" d="M228.5,-239C228.5,-239 346,-239 346,-239 352,-239 358,-245 358,-251 358,-251 358,-446 358,-446 358,-452 352,-458 346,-458 346,-458 228.5,-458 228.5,-458 222.5,-458 216.5,-452 216.5,-446 216.5,-446 216.5,-251 216.5,-251 216.5,-245 222.5,-239 228.5,-239"/>
17
+ <text xml:space="preserve" text-anchor="middle" x="287.25" y="-435" font-family="sans-Serif" font-size="20.00" fill="#a7f3d0">Hardware</text>
18
18
  </g>
19
19
  <g id="clust2" class="cluster">
20
20
  <title>cluster_ctrl</title>
21
- <path fill="#422006" stroke="#a16207" d="M374.5,-8C374.5,-8 498.75,-8 498.75,-8 504.75,-8 510.75,-14 510.75,-20 510.75,-20 510.75,-223 510.75,-223 510.75,-229 504.75,-235 498.75,-235 498.75,-235 374.5,-235 374.5,-235 368.5,-235 362.5,-229 362.5,-223 362.5,-223 362.5,-20 362.5,-20 362.5,-14 368.5,-8 374.5,-8"/>
22
- <text xml:space="preserve" text-anchor="middle" x="436.62" y="-212" font-family="sans-Serif" font-size="20.00" fill="#fde68a">Control</text>
21
+ <path fill="#422006" stroke="#a16207" d="M440,-219C440,-219 564.25,-219 564.25,-219 570.25,-219 576.25,-225 576.25,-231 576.25,-231 576.25,-454 576.25,-454 576.25,-460 570.25,-466 564.25,-466 564.25,-466 440,-466 440,-466 434,-466 428,-460 428,-454 428,-454 428,-231 428,-231 428,-225 434,-219 440,-219"/>
22
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-443" font-family="sans-Serif" font-size="20.00" fill="#fde68a">Control · Scan</text>
23
23
  </g>
24
24
  <g id="clust3" class="cluster">
25
25
  <title>cluster_dec</title>
26
- <path fill="#2e1065" stroke="#6d28d9" d="M592.75,-84C592.75,-84 677.25,-84 677.25,-84 683.25,-84 689.25,-90 689.25,-96 689.25,-96 689.25,-223 689.25,-223 689.25,-229 683.25,-235 677.25,-235 677.25,-235 592.75,-235 592.75,-235 586.75,-235 580.75,-229 580.75,-223 580.75,-223 580.75,-96 580.75,-96 580.75,-90 586.75,-84 592.75,-84"/>
27
- <text xml:space="preserve" text-anchor="middle" x="635" y="-212" font-family="sans-Serif" font-size="20.00" fill="#ddd6fe">Decode</text>
26
+ <path fill="#2e1065" stroke="#6d28d9" d="M20,-8C20,-8 810.25,-8 810.25,-8 816.25,-8 822.25,-14 822.25,-20 822.25,-20 822.25,-199 822.25,-199 822.25,-205 816.25,-211 810.25,-211 810.25,-211 20,-211 20,-211 14,-211 8,-205 8,-199 8,-199 8,-20 8,-20 8,-14 14,-8 20,-8"/>
27
+ <text xml:space="preserve" text-anchor="middle" x="415.12" y="-188" font-family="sans-Serif" font-size="20.00" fill="#ddd6fe">Decode · Record</text>
28
28
  </g>
29
29
  <!-- RF -->
30
30
  <g id="node1" class="node">
31
31
  <title>RF</title>
32
- <path fill="#fda4af" stroke="#334155" stroke-width="1.3" d="M81.25,-128C81.25,-128 12,-128 12,-128 6,-128 0,-122 0,-116 0,-116 0,-104 0,-104 0,-98 6,-92 12,-92 12,-92 81.25,-92 81.25,-92 87.25,-92 93.25,-98 93.25,-104 93.25,-104 93.25,-116 93.25,-116 93.25,-122 87.25,-128 81.25,-128"/>
33
- <text xml:space="preserve" text-anchor="middle" x="46.62" y="-106.88" font-family="sans-Serif" font-size="10.00" fill="#0f172a">📡 &#160;RF Spectrum</text>
32
+ <path fill="#fda4af" stroke="#334155" stroke-width="1.3" d="M111.88,-351C111.88,-351 42.62,-351 42.62,-351 36.62,-351 30.62,-345 30.62,-339 30.62,-339 30.62,-327 30.62,-327 30.62,-321 36.62,-315 42.62,-315 42.62,-315 111.88,-315 111.88,-315 117.88,-315 123.88,-321 123.88,-327 123.88,-327 123.88,-339 123.88,-339 123.88,-345 117.88,-351 111.88,-351"/>
33
+ <text xml:space="preserve" text-anchor="middle" x="77.25" y="-329.88" font-family="sans-Serif" font-size="10.00" fill="#0f172a">📡 &#160;RF Spectrum</text>
34
34
  </g>
35
35
  <!-- SDRhw -->
36
36
  <g id="node2" class="node">
37
37
  <title>SDRhw</title>
38
- <path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="M272.5,-60C272.5,-60 191.25,-60 191.25,-60 185.25,-60 179.25,-54 179.25,-48 179.25,-48 179.25,-36 179.25,-36 179.25,-30 185.25,-24 191.25,-24 191.25,-24 272.5,-24 272.5,-24 278.5,-24 284.5,-30 284.5,-36 284.5,-36 284.5,-48 284.5,-48 284.5,-54 278.5,-60 272.5,-60"/>
39
- <text xml:space="preserve" text-anchor="middle" x="231.88" y="-44.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">RTL&#45;SDR / HackRF</text>
40
- <text xml:space="preserve" text-anchor="middle" x="231.88" y="-32.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">(via GQRX)</text>
38
+ <path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="M338,-283C338,-283 236.5,-283 236.5,-283 230.5,-283 224.5,-277 224.5,-271 224.5,-271 224.5,-259 224.5,-259 224.5,-253 230.5,-247 236.5,-247 236.5,-247 338,-247 338,-247 344,-247 350,-253 350,-259 350,-259 350,-271 350,-271 350,-277 344,-283 338,-283"/>
39
+ <text xml:space="preserve" text-anchor="middle" x="287.25" y="-267.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">RTL&#45;SDR / HackRF</text>
40
+ <text xml:space="preserve" text-anchor="middle" x="287.25" y="-255.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">(via GQRX / SoapySDR)</text>
41
41
  </g>
42
42
  <!-- RF&#45;&gt;SDRhw -->
43
43
  <g id="edge1" class="edge">
44
44
  <title>RF&#45;&gt;SDRhw</title>
45
- <path fill="none" stroke="#fb7185" stroke-width="1.3" d="M93.87,-92.84C117.47,-84.08 146.46,-73.32 171.68,-63.97"/>
46
- <polygon fill="#fb7185" stroke="#fb7185" stroke-width="1.3" points="172.42,-66.68 178.94,-61.27 170.47,-61.43 172.42,-66.68"/>
45
+ <path fill="none" stroke="#fb7185" stroke-width="1.3" d="M124.41,-317.91C152.63,-308.69 189.17,-296.74 220.38,-286.53"/>
46
+ <polygon fill="#fb7185" stroke="#fb7185" stroke-width="1.3" points="220.86,-289.32 227.59,-284.18 219.12,-284 220.86,-289.32"/>
47
47
  </g>
48
48
  <!-- Flipper -->
49
49
  <g id="node3" class="node">
50
50
  <title>Flipper</title>
51
- <path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="M266.12,-128C266.12,-128 197.62,-128 197.62,-128 191.62,-128 185.62,-122 185.62,-116 185.62,-116 185.62,-104 185.62,-104 185.62,-98 191.62,-92 197.62,-92 197.62,-92 266.12,-92 266.12,-92 272.12,-92 278.12,-98 278.12,-104 278.12,-104 278.12,-116 278.12,-116 278.12,-122 272.12,-128 266.12,-128"/>
52
- <text xml:space="preserve" text-anchor="middle" x="231.88" y="-106.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::FlipperZero</text>
51
+ <path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="M321.5,-351C321.5,-351 253,-351 253,-351 247,-351 241,-345 241,-339 241,-339 241,-327 241,-327 241,-321 247,-315 253,-315 253,-315 321.5,-315 321.5,-315 327.5,-315 333.5,-321 333.5,-327 333.5,-327 333.5,-339 333.5,-339 333.5,-345 327.5,-351 321.5,-351"/>
52
+ <text xml:space="preserve" text-anchor="middle" x="287.25" y="-329.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::FlipperZero</text>
53
53
  </g>
54
54
  <!-- RF&#45;&gt;Flipper -->
55
55
  <g id="edge2" class="edge">
56
56
  <title>RF&#45;&gt;Flipper</title>
57
- <path fill="none" stroke="#fb7185" stroke-width="1.3" d="M93.87,-110C118.69,-110 149.45,-110 175.54,-110"/>
58
- <polygon fill="#fb7185" stroke="#fb7185" stroke-width="1.3" points="175.32,-112.8 183.32,-110 175.32,-107.2 175.32,-112.8"/>
57
+ <path fill="none" stroke="#fb7185" stroke-width="1.3" d="M124.41,-333C155.89,-333 197.74,-333 231,-333"/>
58
+ <polygon fill="#fb7185" stroke="#fb7185" stroke-width="1.3" points="230.61,-335.8 238.61,-333 230.61,-330.2 230.61,-335.8"/>
59
59
  </g>
60
60
  <!-- RFID -->
61
61
  <g id="node4" class="node">
62
62
  <title>RFID</title>
63
- <path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="M260.5,-196C260.5,-196 203.25,-196 203.25,-196 197.25,-196 191.25,-190 191.25,-184 191.25,-184 191.25,-172 191.25,-172 191.25,-166 197.25,-160 203.25,-160 203.25,-160 260.5,-160 260.5,-160 266.5,-160 272.5,-166 272.5,-172 272.5,-172 272.5,-184 272.5,-184 272.5,-190 266.5,-196 260.5,-196"/>
64
- <text xml:space="preserve" text-anchor="middle" x="231.88" y="-180.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::RFIDler</text>
65
- <text xml:space="preserve" text-anchor="middle" x="231.88" y="-168.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SonMicroRFID</text>
63
+ <path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="M315.88,-419C315.88,-419 258.62,-419 258.62,-419 252.62,-419 246.62,-413 246.62,-407 246.62,-407 246.62,-395 246.62,-395 246.62,-389 252.62,-383 258.62,-383 258.62,-383 315.88,-383 315.88,-383 321.88,-383 327.88,-389 327.88,-395 327.88,-395 327.88,-407 327.88,-407 327.88,-413 321.88,-419 315.88,-419"/>
64
+ <text xml:space="preserve" text-anchor="middle" x="287.25" y="-403.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::RFIDler</text>
65
+ <text xml:space="preserve" text-anchor="middle" x="287.25" y="-391.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SonMicroRFID</text>
66
66
  </g>
67
67
  <!-- RF&#45;&gt;RFID -->
68
68
  <g id="edge3" class="edge">
69
69
  <title>RF&#45;&gt;RFID</title>
70
- <path fill="none" stroke="#fb7185" stroke-width="1.3" d="M93.87,-127.16C120.73,-137.13 154.56,-149.68 181.88,-159.82"/>
71
- <polygon fill="#fb7185" stroke="#fb7185" stroke-width="1.3" points="180.55,-162.31 189.03,-162.47 182.5,-157.06 180.55,-162.31"/>
70
+ <path fill="none" stroke="#fb7185" stroke-width="1.3" d="M124.27,-348.51C151.14,-357.52 185.7,-369.02 216.5,-379 223.06,-381.13 229.98,-383.34 236.8,-385.51"/>
71
+ <polygon fill="#fb7185" stroke="#fb7185" stroke-width="1.3" points="235.66,-388.09 244.14,-387.83 237.36,-382.75 235.66,-388.09"/>
72
72
  </g>
73
73
  <!-- GQRX -->
74
74
  <g id="node5" class="node">
75
75
  <title>GQRX</title>
76
- <path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="M478.75,-60C478.75,-60 394.5,-60 394.5,-60 388.5,-60 382.5,-54 382.5,-48 382.5,-48 382.5,-28 382.5,-28 382.5,-22 388.5,-16 394.5,-16 394.5,-16 478.75,-16 478.75,-16 484.75,-16 490.75,-22 490.75,-28 490.75,-28 490.75,-48 490.75,-48 490.75,-54 484.75,-60 478.75,-60"/>
77
- <text xml:space="preserve" text-anchor="middle" x="436.62" y="-46.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::GQRX</text>
78
- <text xml:space="preserve" text-anchor="middle" x="436.62" y="-34.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">remote control · scan</text>
79
- <text xml:space="preserve" text-anchor="middle" x="436.62" y="-22.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">spectrum snapshot</text>
76
+ <path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="M548.75,-283C548.75,-283 455.5,-283 455.5,-283 449.5,-283 443.5,-277 443.5,-271 443.5,-271 443.5,-239 443.5,-239 443.5,-233 449.5,-227 455.5,-227 455.5,-227 548.75,-227 548.75,-227 554.75,-227 560.75,-233 560.75,-239 560.75,-239 560.75,-271 560.75,-271 560.75,-277 554.75,-283 548.75,-283"/>
77
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-269.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::GQRX</text>
78
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-257.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">remote control</text>
79
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-245.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">spectrum snapshot</text>
80
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-233.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">(median floor · DC null)</text>
80
81
  </g>
81
82
  <!-- SDRhw&#45;&gt;GQRX -->
82
83
  <g id="edge4" class="edge">
83
84
  <title>SDRhw&#45;&gt;GQRX</title>
84
- <path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M285.09,-40.97C311.75,-40.44 344.39,-39.8 372.46,-39.25"/>
85
- <polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="372.27,-42.05 380.21,-39.09 372.16,-36.45 372.27,-42.05"/>
85
+ <path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M350.39,-262.08C376.41,-260.86 406.72,-259.43 433.31,-258.19"/>
86
+ <polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="433.09,-261 440.95,-257.83 432.83,-255.41 433.09,-261"/>
87
+ </g>
88
+ <!-- Xrf -->
89
+ <g id="node10" class="node">
90
+ <title>Xrf</title>
91
+ <path fill="#c4b5fd" stroke="#334155" stroke-width="1.3" d="M126.5,-112C126.5,-112 28,-112 28,-112 22,-112 16,-106 16,-100 16,-100 16,-88 16,-88 16,-82 22,-76 28,-76 28,-76 126.5,-76 126.5,-76 132.5,-76 138.5,-82 138.5,-88 138.5,-88 138.5,-100 138.5,-100 138.5,-106 132.5,-112 126.5,-112"/>
92
+ <text xml:space="preserve" text-anchor="middle" x="77.25" y="-96.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">extro_snapshot :rf</text>
93
+ <text xml:space="preserve" text-anchor="middle" x="77.25" y="-84.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">probe_rf → hw inventory</text>
94
+ </g>
95
+ <!-- SDRhw&#45;&gt;Xrf -->
96
+ <g id="edge10" class="edge">
97
+ <title>SDRhw&#45;&gt;Xrf</title>
98
+ <path fill="none" stroke="#fbbf24" stroke-width="1.3" stroke-dasharray="5,2" d="M223.87,-247.2C221.33,-245.89 218.86,-244.49 216.5,-243 164.82,-210.3 119.75,-153.58 96.03,-120.36"/>
99
+ <polygon fill="#fbbf24" stroke="#fbbf24" stroke-width="1.3" points="98.49,-118.99 91.6,-114.05 93.91,-122.21 98.49,-118.99"/>
86
100
  </g>
87
101
  <!-- Freq -->
88
102
  <g id="node6" class="node">
89
103
  <title>Freq</title>
90
- <path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="M490.75,-128C490.75,-128 382.5,-128 382.5,-128 376.5,-128 370.5,-122 370.5,-116 370.5,-116 370.5,-104 370.5,-104 370.5,-98 376.5,-92 382.5,-92 382.5,-92 490.75,-92 490.75,-92 496.75,-92 502.75,-98 502.75,-104 502.75,-104 502.75,-116 502.75,-116 502.75,-122 496.75,-128 490.75,-128"/>
91
- <text xml:space="preserve" text-anchor="middle" x="436.62" y="-112.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::FrequencyAllocation</text>
92
- <text xml:space="preserve" text-anchor="middle" x="436.62" y="-100.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">band lookup</text>
104
+ <path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="M556.25,-351C556.25,-351 448,-351 448,-351 442,-351 436,-345 436,-339 436,-339 436,-327 436,-327 436,-321 442,-315 448,-315 448,-315 556.25,-315 556.25,-315 562.25,-315 568.25,-321 568.25,-327 568.25,-327 568.25,-339 568.25,-339 568.25,-345 562.25,-351 556.25,-351"/>
105
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-335.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::FrequencyAllocation</text>
106
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-323.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">band lookup</text>
93
107
  </g>
94
108
  <!-- Flipper&#45;&gt;Freq -->
95
109
  <g id="edge5" class="edge">
96
110
  <title>Flipper&#45;&gt;Freq</title>
97
- <path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M278.63,-110C302.66,-110 332.72,-110 360.1,-110"/>
98
- <polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="360.08,-112.8 368.08,-110 360.08,-107.2 360.08,-112.8"/>
111
+ <path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M333.92,-333C360.7,-333 395.22,-333 425.92,-333"/>
112
+ <polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="425.77,-335.8 433.77,-333 425.77,-330.2 425.77,-335.8"/>
99
113
  </g>
100
114
  <!-- Scan -->
101
115
  <g id="node7" class="node">
102
116
  <title>Scan</title>
103
- <path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="M481.38,-196C481.38,-196 391.88,-196 391.88,-196 385.88,-196 379.88,-190 379.88,-184 379.88,-184 379.88,-172 379.88,-172 379.88,-166 385.88,-160 391.88,-160 391.88,-160 481.38,-160 481.38,-160 487.38,-160 493.38,-166 493.38,-172 493.38,-172 493.38,-184 493.38,-184 493.38,-190 487.38,-196 481.38,-196"/>
104
- <text xml:space="preserve" text-anchor="middle" x="436.62" y="-180.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">bin/pwn_gqrx_scanner</text>
105
- <text xml:space="preserve" text-anchor="middle" x="436.62" y="-168.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">fast · iterative</text>
117
+ <path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="M546.88,-427C546.88,-427 457.38,-427 457.38,-427 451.38,-427 445.38,-421 445.38,-415 445.38,-415 445.38,-395 445.38,-395 445.38,-389 451.38,-383 457.38,-383 457.38,-383 546.88,-383 546.88,-383 552.88,-383 558.88,-389 558.88,-395 558.88,-395 558.88,-415 558.88,-415 558.88,-421 552.88,-427 546.88,-427"/>
118
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-413.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">bin/pwn_gqrx_scanner</text>
119
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-401.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">&#45;&#45;fft&#45;scan · iterative</text>
120
+ <text xml:space="preserve" text-anchor="middle" x="502.12" y="-389.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">&#45;&#45;min&#45;snr&#45;db · &#45;&#45;avg</text>
106
121
  </g>
107
122
  <!-- RFID&#45;&gt;Scan -->
108
123
  <g id="edge6" class="edge">
109
124
  <title>RFID&#45;&gt;Scan</title>
110
- <path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M272.93,-178C300.54,-178 337.87,-178 369.81,-178"/>
111
- <polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="369.57,-180.8 377.57,-178 369.57,-175.2 369.57,-180.8"/>
125
+ <path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M328.29,-401.75C358.34,-402.32 400.16,-403.1 435.13,-403.76"/>
126
+ <polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="434.78,-406.55 442.83,-403.9 434.88,-400.95 434.78,-406.55"/>
112
127
  </g>
113
128
  <!-- Dec -->
114
129
  <g id="node8" class="node">
115
130
  <title>Dec</title>
116
- <path fill="#c4b5fd" stroke="#334155" stroke-width="1.3" d="M668.12,-128C668.12,-128 601.88,-128 601.88,-128 595.88,-128 589.88,-122 589.88,-116 589.88,-116 589.88,-104 589.88,-104 589.88,-98 595.88,-92 601.88,-92 601.88,-92 668.12,-92 668.12,-92 674.12,-92 680.12,-98 680.12,-104 680.12,-104 680.12,-116 680.12,-116 680.12,-122 674.12,-128 668.12,-128"/>
117
- <text xml:space="preserve" text-anchor="middle" x="635" y="-112.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::Decoder::*</text>
118
- <text xml:space="preserve" text-anchor="middle" x="635" y="-100.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">demod · protocol</text>
131
+ <path fill="#c4b5fd" stroke="#334155" stroke-width="1.3" d="M802.25,-96C802.25,-96 666.25,-96 666.25,-96 660.25,-96 654.25,-90 654.25,-84 654.25,-84 654.25,-28 654.25,-28 654.25,-22 660.25,-16 666.25,-16 666.25,-16 802.25,-16 802.25,-16 808.25,-16 814.25,-22 814.25,-28 814.25,-28 814.25,-84 814.25,-84 814.25,-90 808.25,-96 802.25,-96"/>
132
+ <text xml:space="preserve" text-anchor="middle" x="734.25" y="-82.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">SDR::Decoder::*</text>
133
+ <text xml:space="preserve" text-anchor="middle" x="734.25" y="-70.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">ADSB · APT · BT · DECT · GPS</text>
134
+ <text xml:space="preserve" text-anchor="middle" x="734.25" y="-58.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">GSM · Iridium · LoRa · LTE</text>
135
+ <text xml:space="preserve" text-anchor="middle" x="734.25" y="-46.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">Morse · P25 · POCSAG/FLEX</text>
136
+ <text xml:space="preserve" text-anchor="middle" x="734.25" y="-34.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">RDS · RFID · rtl_433 · RTTY</text>
137
+ <text xml:space="preserve" text-anchor="middle" x="734.25" y="-22.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">WiFi · ZigBee</text>
119
138
  </g>
120
139
  <!-- GQRX&#45;&gt;Dec -->
121
140
  <g id="edge7" class="edge">
122
141
  <title>GQRX&#45;&gt;Dec</title>
123
- <path fill="none" stroke="#a78bfa" stroke-width="1.3" d="M491.29,-57.67C518.93,-67.81 552.56,-80.14 580.23,-90.28"/>
124
- <polygon fill="#a78bfa" stroke="#a78bfa" stroke-width="1.3" points="579.04,-92.83 587.52,-92.96 580.97,-87.57 579.04,-92.83"/>
142
+ <path fill="none" stroke="#a78bfa" stroke-width="1.3" d="M561.17,-233.24C566.51,-230.18 571.64,-226.77 576.25,-223 622.93,-184.83 611.7,-154.72 654.25,-112 657.34,-108.9 660.61,-105.85 664,-102.88"/>
143
+ <polygon fill="#a78bfa" stroke="#a78bfa" stroke-width="1.3" points="665.77,-105.05 670.09,-97.76 662.16,-100.77 665.77,-105.05"/>
125
144
  </g>
126
145
  <!-- Freq&#45;&gt;Dec -->
127
146
  <g id="edge8" class="edge">
128
147
  <title>Freq&#45;&gt;Dec</title>
129
- <path fill="none" stroke="#a78bfa" stroke-width="1.3" d="M503.29,-110C528.07,-110 555.97,-110 579.6,-110"/>
130
- <polygon fill="#a78bfa" stroke="#a78bfa" stroke-width="1.3" points="579.48,-112.8 587.48,-110 579.48,-107.2 579.48,-112.8"/>
148
+ <path fill="none" stroke="#a78bfa" stroke-width="1.3" d="M568.68,-316.86C571.37,-315.1 573.91,-313.16 576.25,-311 646.1,-246.61 595.72,-186.82 654.25,-112 656.7,-108.87 659.37,-105.84 662.19,-102.92"/>
149
+ <polygon fill="#a78bfa" stroke="#a78bfa" stroke-width="1.3" points="663.82,-105.25 667.64,-97.68 659.93,-101.22 663.82,-105.25"/>
131
150
  </g>
132
151
  <!-- Obs -->
133
152
  <g id="node9" class="node">
134
153
  <title>Obs</title>
135
- <path fill="#c4b5fd" stroke="#334155" stroke-width="1.3" d="M669.25,-196C669.25,-196 600.75,-196 600.75,-196 594.75,-196 588.75,-190 588.75,-184 588.75,-184 588.75,-172 588.75,-172 588.75,-166 594.75,-160 600.75,-160 600.75,-160 669.25,-160 669.25,-160 675.25,-160 681.25,-166 681.25,-172 681.25,-172 681.25,-184 681.25,-184 681.25,-190 675.25,-196 669.25,-196"/>
136
- <text xml:space="preserve" text-anchor="middle" x="635" y="-180.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">extro_observe</text>
137
- <text xml:space="preserve" text-anchor="middle" x="635" y="-168.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">record signal intel</text>
154
+ <path fill="#c4b5fd" stroke="#334155" stroke-width="1.3" d="M768.5,-172C768.5,-172 700,-172 700,-172 694,-172 688,-166 688,-160 688,-160 688,-140 688,-140 688,-134 694,-128 700,-128 700,-128 768.5,-128 768.5,-128 774.5,-128 780.5,-134 780.5,-140 780.5,-140 780.5,-160 780.5,-160 780.5,-166 774.5,-172 768.5,-172"/>
155
+ <text xml:space="preserve" text-anchor="middle" x="734.25" y="-158.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">extro_observe</text>
156
+ <text xml:space="preserve" text-anchor="middle" x="734.25" y="-146.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">category: :rf</text>
157
+ <text xml:space="preserve" text-anchor="middle" x="734.25" y="-134.5" font-family="sans-Serif" font-size="10.00" fill="#0f172a">record signal intel</text>
138
158
  </g>
139
159
  <!-- Scan&#45;&gt;Obs -->
140
160
  <g id="edge9" class="edge">
141
161
  <title>Scan&#45;&gt;Obs</title>
142
- <path fill="none" stroke="#a78bfa" stroke-width="1.3" d="M493.9,-178C520.43,-178 551.99,-178 578.43,-178"/>
143
- <polygon fill="#a78bfa" stroke="#a78bfa" stroke-width="1.3" points="578.32,-180.8 586.32,-178 578.32,-175.2 578.32,-180.8"/>
162
+ <path fill="none" stroke="#a78bfa" stroke-width="1.3" d="M559.35,-389.21C565.35,-386.32 571.12,-382.94 576.25,-379 646.6,-324.88 696.97,-230.14 719.71,-181.21"/>
163
+ <polygon fill="#a78bfa" stroke="#a78bfa" stroke-width="1.3" points="722.17,-182.58 722.94,-174.14 717.07,-180.25 722.17,-182.58"/>
144
164
  </g>
145
165
  </g>
146
166
  </svg>
@@ -2,15 +2,16 @@
2
2
 
3
3
  `pwn-ai` is a natural-language front end to everything in `PWN::`. You describe
4
4
  the goal; the agent plans a sequence of tool calls (`pwn_eval`, `shell`,
5
- `memory_*`, `skill_*`, `extro_*`, `agent_*`, …), executes them against the live
6
- process, observes the results, and loops until it can give you a final answer.
5
+ `memory_*`, `skill_*`, `mistakes_*`, `extro_*`, `agent_*`, …), executes them
6
+ against the live process, observes the results, and loops until it can give you
7
+ a final answer — **learning from every failure so it doesn't repeat it**.
7
8
 
8
9
  ## Two ways to run it
9
10
 
10
11
  ```text
11
12
  # 1. Interactive TUI (inside the pwn REPL)
12
- pwn[v0.5.616]:001 >>> pwn-ai
13
- ✨ pwn-ai · anthropic · session 20260707_220802_3f39791f
13
+ pwn[v0.5.618]:001 >>> pwn-ai
14
+ ✨ pwn-ai · anthropic · session 20260707_225041_d7f2f3bb
14
15
  > Use NmapIt to sweep 10.0.0.0/24, then TransparentBrowser via Burp on any
15
16
  host with 443 open, active-scan, and give me a Reports::SAST summary.
16
17
  ```
@@ -23,12 +24,21 @@ $ pwn --ai "run bin/pwn_sast against ./src and push findings to DefectDojo"
23
24
  ## Anatomy of a turn
24
25
 
25
26
  1. **PromptBuilder** assembles the system prompt: your request + MEMORY block +
26
- SKILLS list + LEARNING stats + EXTROSPECTION block.
27
- 2. **Loop** sends it to the active `PWN::AI::<Engine>` client.
28
- 3. Provider replies with `tool_calls` **Dispatch** executes each one via the
29
- [Registry](Agent-Tool-Registry.md), **Metrics** records duration/success.
30
- 4. Results are appended to the message list; go to 2.
31
- 5. When the reply has *no* tool_calls it's the **final answer**
27
+ SKILLS list + LEARNING stats + **KNOWN MISTAKES / KNOWN FIXES** +
28
+ TOOL EFFECTIVENESS + EXTROSPECTION block.
29
+ 2. **Loop** checks the incoming message against `Mistakes::CORRECTION_RX` if
30
+ it reads like *"no, that's wrong"* the previous outcome is flipped to
31
+ `success:false` and fingerprinted.
32
+ 3. Loop sends the prompt to the active `PWN::AI::<Engine>` client.
33
+ 4. Provider replies with `tool_calls` → **Dispatch** executes each one via the
34
+ [Registry](Agent-Tool-Registry.md); **Metrics** records duration/success.
35
+ Any *failure* is fingerprinted into **Mistakes** (`count++`, cross-session)
36
+ and the tool result gets an inline `correction_hint` (`seen N×, sig=…,
37
+ KNOWN FIX: …`) so the very next iteration self-corrects. If the persistent
38
+ count ≥ 3, `guard_repeated_failure` interrupts with an explicit
39
+ *change-approach* instruction.
40
+ 5. Results are appended to the message list; go to 3.
41
+ 6. When the reply has *no* tool_calls it's the **final answer** →
32
42
  `Learning.auto_reflect` and `Extrospection.auto_extrospect` fire (if
33
43
  enabled), transcript is flushed to `~/.pwn/sessions/`.
34
44
 
@@ -36,7 +46,7 @@ $ pwn --ai "run bin/pwn_sast against ./src and push findings to DefectDojo"
36
46
 
37
47
  ## What the agent can call
38
48
 
39
- 10 toolsets · 45+ tools — full table at
49
+ 10 toolsets · 52 tools — full table at
40
50
  [Agent Tool Registry](Agent-Tool-Registry.md).
41
51
 
42
52
  The two that matter most:
@@ -46,9 +56,9 @@ The two that matter most:
46
56
  | `pwn_eval` | **Any** Ruby in-process — the whole `PWN::` namespace, `require`, monkey-patch, everything |
47
57
  | `shell` | **Any** OS command on the host |
48
58
 
49
- Everything else (memory, skills, learning, extrospection, cron, swarm,
50
- sessions, metrics) is a convenience wrapper the model can discover from the
51
- schema alone.
59
+ Everything else (memory, skills, learning, **mistakes**, extrospection, cron,
60
+ swarm, sessions, metrics) is a convenience wrapper the model can discover from
61
+ the schema alone.
52
62
 
53
63
  ## Delegating to other agents
54
64
 
@@ -64,9 +74,12 @@ full `Loop.run` under a persona overlay) that share a JSONL bus. See
64
74
  - Disable `auto_reflect` during noisy fuzz loops
65
75
  (`learning_auto_reflect_toggle(enabled: false)`), re-enable for the summary
66
76
  turn.
77
+ - Run `mistakes_list` before retrying something that failed last session — the
78
+ fix may already be recorded.
67
79
 
68
80
  **See also:** [AI Integration](AI-Integration.md) ·
69
81
  [Skills, Memory & Learning](Skills-Memory-Learning.md) ·
70
- [Extrospection](Extrospection.md) · [Swarm](Swarm.md) · [Cron](Cron.md)
82
+ [Mistakes](Mistakes.md) · [Extrospection](Extrospection.md) ·
83
+ [Swarm](Swarm.md) · [Cron](Cron.md)
71
84
 
72
85
  [← Home](Home.md)
@@ -41,7 +41,8 @@ module PWN
41
41
  module Extrospection
42
42
  EXTRO_FILE = File.join(Dir.home, '.pwn', 'extrospection.json')
43
43
  MAX_OBSERVATIONS = 500
44
- PROBE_BINS = %w[nmap curl git ruby python3 gcc msfconsole sqlmap burpsuite zaproxy openssl docker].freeze
44
+ RF_BINS = %w[rtl_sdr rtl_test rtl_433 hackrf_info gqrx dump1090 multimon-ng SoapySDRUtil].freeze
45
+ PROBE_BINS = (%w[nmap curl git ruby python3 gcc msfconsole sqlmap burpsuite zaproxy openssl docker] + RF_BINS).freeze
45
46
 
46
47
  # Supported Method Parameters::
47
48
  # store = PWN::AI::Agent::Extrospection.load
@@ -71,7 +72,7 @@ module PWN
71
72
  # Supported Method Parameters::
72
73
  # snap = PWN::AI::Agent::Extrospection.snapshot(
73
74
  # persist: 'optional - Boolean, write snapshot to disk & rotate previous (default true)',
74
- # sections: 'optional - Array subset of [:host, :net, :toolchain, :repo, :env] (default all)'
75
+ # sections: 'optional - Array subset of [:host, :net, :toolchain, :repo, :env, :rf] (default all)'
75
76
  # )
76
77
  #
77
78
  # Captures a fingerprint of the OUTSIDE world. When persist:true the
@@ -84,7 +85,7 @@ module PWN
84
85
  true
85
86
  end
86
87
  sections = Array(opts[:sections]).map(&:to_sym)
87
- sections = %i[host net toolchain repo env] if sections.empty?
88
+ sections = %i[host net toolchain repo env rf] if sections.empty?
88
89
 
89
90
  snap = {}
90
91
  snap[:host] = probe_host if sections.include?(:host)
@@ -92,6 +93,7 @@ module PWN
92
93
  snap[:toolchain] = probe_toolchain if sections.include?(:toolchain)
93
94
  snap[:repo] = probe_repo if sections.include?(:repo)
94
95
  snap[:env] = probe_env if sections.include?(:env)
96
+ snap[:rf] = probe_rf if sections.include?(:rf)
95
97
  snap[:captured_at] = Time.now.utc.iso8601
96
98
  snap[:fingerprint] = Digest::SHA256.hexdigest(JSON.generate(snap.except(:captured_at)))[0, 16]
97
99
 
@@ -110,7 +112,7 @@ module PWN
110
112
  # obs = PWN::AI::Agent::Extrospection.observe(
111
113
  # source: 'required - where the observation came from (nmap, shodan, burp, cve, human, ...)',
112
114
  # data: 'required - the observation payload (String or Hash)',
113
- # category: 'optional - :recon, :vuln, :intel, :target, :network, :misc (default :misc)',
115
+ # category: 'optional - :recon, :vuln, :intel, :target, :network, :env, :rf, :misc (default :misc)',
114
116
  # target: 'optional - host/ip/url/asset the observation is about',
115
117
  # tags: 'optional - Array of String labels',
116
118
  # ttl: 'optional - seconds until this observation is considered stale (default nil = forever)'
@@ -288,7 +290,21 @@ module PWN
288
290
  end
289
291
  end
290
292
 
291
- # 4) raw drift as low-priority findings when nothing else matched
293
+ # 4) :rf observations vs missing SDR hardware / binaries
294
+ rf = snap[:rf] || {}
295
+ hw_present = %i[rtl_sdr hackrf flipper gqrx_sock].any? { |k| rf_present?(val: rf[k]) }
296
+ observations(category: 'rf', limit: 50).each do |ob|
297
+ miss = RF_BINS.select { |b| pkgs[b.to_sym].to_s.empty? }
298
+ if !hw_present
299
+ findings << { kind: :rf_no_hardware, observation: ob[:data], source: ob[:source], target: ob[:target], advice: 'RF observation recorded but no SDR hardware detected in snapshot — plug in RTL-SDR/HackRF/Flipper or start gqrx (`-r`) before trusting RF results.' }
300
+ elsif rf[:gqrx_sock] == false && ob[:source].to_s == 'gqrx'
301
+ findings << { kind: :rf_gqrx_down, observation: ob[:data], target: ob[:target], advice: 'gqrx remote-control socket (127.0.0.1:7356) is closed — start gqrx with remote control enabled before re-running the scan.' }
302
+ elsif !miss.empty?
303
+ findings << { kind: :rf_toolchain_gap, missing: miss, observation: ob[:data], advice: "SDR toolchain gap: install #{miss.join(', ')} to decode/act on this RF observation." }
304
+ end
305
+ end
306
+
307
+ # 5) raw drift as low-priority findings when nothing else matched
292
308
  Array(delta[:added]).first(5).each { |c| findings << { kind: :env_added, detail: c } } if findings.empty?
293
309
  Array(delta[:removed]).first(5).each { |c| findings << { kind: :env_removed, detail: c } } if findings.empty?
294
310
 
@@ -346,7 +362,8 @@ module PWN
346
362
  drift_added: Array(delta[:added]).length,
347
363
  drift_removed: Array(delta[:removed]).length,
348
364
  toolchain_bins: (snap[:toolchain] || {}).count { |_, v| !v.to_s.empty? },
349
- listening_ports: Array(snap.dig(:net, :listening)).length
365
+ listening_ports: Array(snap.dig(:net, :listening)).length,
366
+ rf_devices: (snap[:rf] || {}).values_at(:rtl_sdr, :hackrf, :flipper, :gqrx_sock).count { |v| rf_present?(val: v) }
350
367
  }
351
368
  end
352
369
 
@@ -478,6 +495,47 @@ module PWN
478
495
  { cwd: Dir.pwd, ruby: RUBY_VERSION }
479
496
  end
480
497
 
498
+ # Passive RF / SDR hardware inventory. NO transmit, NO active spectrum
499
+ # scan — this is the RF analogue of probe_toolchain: "what radios and
500
+ # SDR plumbing are attached / reachable right now?" so drift can flag
501
+ # "HackRF unplugged", "gqrx remote-control down", "new RTL dongle".
502
+ private_class_method def self.probe_rf
503
+ {
504
+ rtl_sdr: sh(cmd: 'timeout 3 rtl_test -t 2>&1 | head -5'),
505
+ hackrf: sh(cmd: 'timeout 3 hackrf_info 2>&1 | head -8'),
506
+ soapy: sh(cmd: 'timeout 3 SoapySDRUtil --find 2>&1 | head -10'),
507
+ gqrx_sock: tcp_open?(host: '127.0.0.1', port: 7356),
508
+ flipper: Dir.glob('/dev/serial/by-id/*Flipper*').any?,
509
+ serial_devs: Dir.glob('/dev/{ttyUSB,ttyACM}*'),
510
+ band_plans: rf_band_plan_keys.length
511
+ }
512
+ rescue StandardError => e
513
+ { error: "#{e.class}: #{e.message}" }
514
+ end
515
+
516
+ private_class_method def self.tcp_open?(opts = {})
517
+ host = opts[:host] || '127.0.0.1'
518
+ port = opts[:port].to_i
519
+ Socket.tcp(host, port, connect_timeout: 1, &:close)
520
+ true
521
+ rescue StandardError
522
+ false
523
+ end
524
+
525
+ private_class_method def self.rf_present?(opts = {})
526
+ v = opts[:val]
527
+ return false if v.nil? || v == false || v.to_s.strip.empty?
528
+
529
+ v.to_s !~ /no\s+.*devices|no\s+hackrf|not\s+found|no\s+such\s+file|command\s+not\s+found|^false$/i
530
+ end
531
+
532
+ private_class_method def self.rf_band_plan_keys
533
+ require 'pwn/sdr/frequency_allocation' unless defined?(PWN::SDR::FrequencyAllocation)
534
+ PWN::SDR::FrequencyAllocation.band_plans.keys.map(&:to_s).sort
535
+ rescue StandardError
536
+ []
537
+ end
538
+
481
539
  private_class_method def self.flatten(opts = {})
482
540
  hash = opts[:hash] || {}
483
541
  prefix = opts[:prefix].to_s
@@ -598,6 +656,7 @@ module PWN
598
656
  PWN::AI::Agent::Extrospection.snapshot # probe host, persist, return {snapshot:, drift:}
599
657
  PWN::AI::Agent::Extrospection.drift(live: true) # what changed vs last snapshot
600
658
  PWN::AI::Agent::Extrospection.observe(source: 'nmap', category: :recon, target: '10.0.0.5', data: '22/tcp open ssh 9.6')
659
+ PWN::AI::Agent::Extrospection.observe(source: 'gqrx', category: :rf, target: '433.920MHz', data: 'peak -34.2 dBFS bw=200k')
601
660
  PWN::AI::Agent::Extrospection.observations(category: 'recon', target: '10.0.0.5')
602
661
  PWN::AI::Agent::Extrospection.intel(query: 'openssl 3.0', record: true)
603
662
  PWN::AI::Agent::Extrospection.correlate # introspection x extrospection findings
@@ -112,16 +112,19 @@ module PWN
112
112
 
113
113
  public_class_method def self.to_context(opts = {})
114
114
  limit = opts[:limit] || 5
115
- rows = outcomes(limit: limit)
116
- return '' if rows.empty?
115
+ rows = outcomes(limit: limit)
116
+ fails = outcomes(limit: 200, success: false).first(limit)
117
+ return '' if rows.empty? && fails.empty?
117
118
 
118
- lines = rows.map do |r|
119
+ fmt = lambda do |r|
119
120
  flag = r[:success] ? '✓' : '✗'
120
- " #{flag} #{r[:task][0, 100]} (#{r[:timestamp]})"
121
+ " #{flag} #{r[:task].to_s[0, 100]} (#{r[:timestamp]})"
121
122
  end
122
- s = stats
123
+ s = stats
123
124
  hdr = "RECENT OUTCOMES (success_rate=#{(s[:success_rate] * 100).round(1)}% over #{s[:total_outcomes]} attempts)"
124
- "#{hdr}\n#{lines.join("\n")}\n\n"
125
+ out = "#{hdr}\n#{rows.map(&fmt).join("\n")}\n"
126
+ out += "RECENT FAILURES (learn from these — do not repeat)\n#{fails.map(&fmt).join("\n")}\n" unless fails.empty?
127
+ "#{out}\n"
125
128
  end
126
129
 
127
130
  # Supported Method Parameters::
@@ -204,9 +207,10 @@ module PWN
204
207
  return unless session_id
205
208
  return unless auto_reflect_enabled?
206
209
 
210
+ ok = infer_success(session_id: session_id, final: opts[:final])
207
211
  note_outcome(
208
212
  task: opts[:request].to_s[0, 120],
209
- success: !opts[:final].to_s.strip.empty?,
213
+ success: ok,
210
214
  details: opts[:final].to_s[0, 300],
211
215
  session_id: session_id,
212
216
  tags: %w[auto loop]
@@ -218,6 +222,37 @@ module PWN
218
222
  nil
219
223
  end
220
224
 
225
+ # Supported Method Parameters::
226
+ # PWN::AI::Agent::Learning.flip_last_outcome(
227
+ # session_id: 'optional - only flip if the newest outcome belongs to this session',
228
+ # reason: 'optional - why it is being flipped (usually the user correction text)'
229
+ # )
230
+ #
231
+ # Rewrites the most-recently-appended learning.jsonl entry from
232
+ # success:true to success:false. Called by Mistakes.check_user_correction
233
+ # when the user's next message rejects the previous answer, so the
234
+ # 100 %-success illusion is broken and the failure enters the corpus.
235
+
236
+ public_class_method def self.flip_last_outcome(opts = {})
237
+ return { flipped: false } unless File.exist?(LEARNING_FILE)
238
+
239
+ lines = File.readlines(LEARNING_FILE)
240
+ return { flipped: false } if lines.empty?
241
+
242
+ last = JSON.parse(lines.last, symbolize_names: true)
243
+ return { flipped: false } if opts[:session_id] && last[:session_id] && last[:session_id] != opts[:session_id]
244
+ return { flipped: false } unless last[:success]
245
+
246
+ last[:success] = false
247
+ last[:flipped_by] = 'user_correction'
248
+ last[:details] = "#{last[:details]} | CORRECTED: #{opts[:reason].to_s[0, 200]}".strip
249
+ lines[-1] = "#{JSON.generate(last)}\n"
250
+ File.write(LEARNING_FILE, lines.join)
251
+ { flipped: true, id: last[:id] }
252
+ rescue StandardError
253
+ { flipped: false }
254
+ end
255
+
221
256
  # Supported Method Parameters::
222
257
  # removed = PWN::AI::Agent::Learning.consolidate(
223
258
  # max_entries: 'optional - hard cap on PWN::Memory size (default MAX_MEMORY_ENTRIES)'
@@ -274,6 +309,31 @@ module PWN
274
309
  false
275
310
  end
276
311
 
312
+ FAILURE_FINAL_RX = /\[pwn-ai\] (iteration budget exhausted|engine returned no message)|\b(i (was )?unable to|i could not|i couldn'?t|cannot proceed|failed to)\b/i
313
+
314
+ # Derive a success signal stronger than "final answer non-empty":
315
+ # look at the tool-failure ratio inside the just-completed turn AND
316
+ # scan the final text for self-reported failure language. Without
317
+ # this, auto_reflect logs ~100 % success and the negative-feedback
318
+ # side of the learning loop never fires.
319
+ private_class_method def self.infer_success(opts = {})
320
+ final = opts[:final].to_s
321
+ return false if final.strip.empty?
322
+ return false if final.match?(FAILURE_FINAL_RX)
323
+
324
+ sid = opts[:session_id]
325
+ return true unless sid && defined?(PWN::Sessions)
326
+
327
+ entries = PWN::Sessions.load(session_id: sid).last(200)
328
+ tool = entries.select { |e| e[:role].to_s == 'tool' }
329
+ return true if tool.empty?
330
+
331
+ bad = tool.count { |e| e[:content].to_s.include?('"success":false') || e[:content].to_s.match?(/"exit":[1-9]/) }
332
+ (bad.to_f / tool.length) < 0.5
333
+ rescue StandardError
334
+ !final.strip.empty?
335
+ end
336
+
277
337
  private_class_method def self.skills_dir
278
338
  if defined?(PWN::Config) && PWN::Config.respond_to?(:pwn_skills_path)
279
339
  PWN::Config.pwn_skills_path