pwn 0.5.677 → 0.5.679
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 +2 -2
- data/documentation/AI-Integration.md +4 -3
- data/documentation/Agent-Tool-Registry.md +27 -5
- data/documentation/CLI-Drivers.md +1 -1
- data/documentation/Configuration.md +65 -25
- data/documentation/Cron.md +12 -2
- data/documentation/Home.md +1 -1
- data/documentation/How-PWN-Works.md +1 -1
- data/documentation/Installation.md +12 -7
- data/documentation/Reinforcement-Learning.md +10 -6
- data/documentation/Skills-Memory-Learning.md +1 -1
- data/documentation/What-is-PWN.md +1 -1
- data/documentation/diagrams/agent-tool-registry.svg +156 -147
- data/documentation/diagrams/cron-scheduling.svg +149 -126
- data/documentation/diagrams/dot/agent-tool-registry.dot +2 -1
- data/documentation/diagrams/dot/cron-scheduling.dot +6 -3
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +1 -1
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +14 -13
- data/documentation/pwn-REPL.md +1 -1
- data/documentation/pwn-ai-Agent.md +16 -9
- data/lib/pwn/ai/agent/policy.rb +9 -3
- data/lib/pwn/ai/agent/registry.rb +95 -6
- data/lib/pwn/ai/anthropic.rb +2 -6
- data/lib/pwn/ai/gemini.rb +2 -6
- data/lib/pwn/ai/grok.rb +7 -8
- data/lib/pwn/ai/ollama.rb +2 -6
- data/lib/pwn/ai/open_ai.rb +2 -6
- data/lib/pwn/ai/open_web_ui.rb +2 -6
- data/lib/pwn/blockchain/btc.rb +2 -6
- data/lib/pwn/blockchain/eth.rb +2 -6
- data/lib/pwn/config.rb +4 -3
- data/lib/pwn/plugins/black_duck_binary_analysis.rb +2 -4
- data/lib/pwn/plugins/jira_data_center.rb +2 -4
- data/lib/pwn/plugins/repl.rb +12 -6
- data/lib/pwn/plugins/tty_spinner.rb +75 -0
- data/lib/pwn/plugins.rb +1 -0
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/registry_spec.rb +6 -0
- data/spec/lib/pwn/ai/grok_spec.rb +7 -0
- data/spec/lib/pwn/plugins/tty_spinner_spec.rb +61 -0
- data/third_party/pwn_rdoc.jsonl +8 -2
- metadata +3 -1
|
@@ -249,10 +249,10 @@
|
|
|
249
249
|
<!-- Dispatch->Curric -->
|
|
250
250
|
<g id="edge67" class="edge">
|
|
251
251
|
<title>Dispatch->Curric</title>
|
|
252
|
-
<path fill="none" stroke="#fb7185" stroke-width="1.3" stroke-dasharray="5,2" d="M982.
|
|
252
|
+
<path fill="none" stroke="#fb7185" stroke-width="1.3" stroke-dasharray="5,2" d="M982.71,-511.61C962.63,-506.78 940.54,-501.98 920,-498.62 885.68,-493.02 631.72,-496.29 608.25,-470.62 601.95,-463.74 601.66,-456.24 608.25,-449.62 618.31,-439.53 2647.42,-445.97 2661,-441.62 2703.86,-427.9 2744.93,-396.11 2771.77,-371.93"/>
|
|
253
253
|
<polygon fill="#fb7185" stroke="#fb7185" stroke-width="1.3" points="2773.51,-374.14 2777.51,-366.66 2769.72,-370.01 2773.51,-374.14"/>
|
|
254
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
255
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
254
|
+
<text xml:space="preserve" text-anchor="middle" x="637.12" y="-462.07" font-family="sans-Serif" font-size="9.00" fill="#cbd5e1">counterfactual</text>
|
|
255
|
+
<text xml:space="preserve" text-anchor="middle" x="637.12" y="-451.57" font-family="sans-Serif" font-size="9.00" fill="#cbd5e1">[REPEATING]</text>
|
|
256
256
|
</g>
|
|
257
257
|
<!-- Policy -->
|
|
258
258
|
<g id="node17" class="node">
|
|
@@ -273,9 +273,9 @@
|
|
|
273
273
|
<!-- Dispatch->Snapshot -->
|
|
274
274
|
<g id="edge17" class="edge">
|
|
275
275
|
<title>Dispatch->Snapshot</title>
|
|
276
|
-
<path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M982.79,-511.
|
|
277
|
-
<polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="834.
|
|
278
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
276
|
+
<path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M982.79,-511.08C962.71,-506.24 940.6,-501.57 920,-498.62 857.67,-489.73 397.41,-516.24 354,-470.62 347.57,-463.86 347.47,-456.29 354,-449.62 369.32,-433.99 727.57,-449.48 748,-441.62 781.44,-428.77 811.75,-401.69 832.99,-378.88"/>
|
|
277
|
+
<polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="834.94,-380.91 838.24,-373.11 830.79,-377.15 834.94,-380.91"/>
|
|
278
|
+
<text xml:space="preserve" text-anchor="middle" x="366" y="-456.82" font-family="sans-Serif" font-size="9.00" fill="#cbd5e1">probe</text>
|
|
279
279
|
</g>
|
|
280
280
|
<!-- Dispatch->Observe -->
|
|
281
281
|
<g id="edge23" class="edge">
|
|
@@ -354,9 +354,10 @@
|
|
|
354
354
|
<!-- Result->Learning -->
|
|
355
355
|
<g id="edge13" class="edge">
|
|
356
356
|
<title>Result->Learning</title>
|
|
357
|
-
<path fill="none" stroke="#34d399" stroke-width="1.3" d="M1436.68,-515.
|
|
358
|
-
<polygon fill="#34d399" stroke="#34d399" stroke-width="1.3" points="1837.52,-381.18 1840.
|
|
359
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
357
|
+
<path fill="none" stroke="#34d399" stroke-width="1.3" d="M1436.68,-515.78C1417.86,-509.34 1394.53,-502.33 1373,-498.62 1335.07,-492.1 707.41,-498.39 680.75,-470.62 674.29,-463.89 674.17,-456.25 680.75,-449.62 691.27,-439.03 1742.12,-447.14 1756,-441.62 1787.86,-428.96 1815.93,-402.06 1835.47,-379.27"/>
|
|
358
|
+
<polygon fill="#34d399" stroke="#34d399" stroke-width="1.3" points="1837.52,-381.18 1840.5,-373.24 1833.22,-377.59 1837.52,-381.18"/>
|
|
359
|
+
<text xml:space="preserve" text-anchor="middle" x="722.38" y="-462.07" font-family="sans-Serif" font-size="9.00" fill="#cbd5e1">auto_introspect</text>
|
|
360
|
+
<text xml:space="preserve" text-anchor="middle" x="722.38" y="-451.57" font-family="sans-Serif" font-size="9.00" fill="#cbd5e1">(deferred after reply)</text>
|
|
360
361
|
</g>
|
|
361
362
|
<!-- Reward -->
|
|
362
363
|
<g id="node15" class="node">
|
|
@@ -391,10 +392,10 @@
|
|
|
391
392
|
<!-- Result->Observe -->
|
|
392
393
|
<g id="edge18" class="edge">
|
|
393
394
|
<title>Result->Observe</title>
|
|
394
|
-
<path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M1436.69,-515.
|
|
395
|
-
<polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="1056.27,-368.
|
|
396
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
397
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
395
|
+
<path fill="none" stroke="#f59e0b" stroke-width="1.3" d="M1436.69,-515.68C1417.89,-509.21 1394.55,-502.21 1373,-498.62 1321.07,-489.99 462.42,-508.4 425.75,-470.62 419.25,-463.93 419.2,-456.27 425.75,-449.62 436.56,-438.66 966.02,-448.09 980,-441.62 1013.37,-426.2 1039.16,-391.34 1053.96,-367.03"/>
|
|
396
|
+
<polygon fill="#f59e0b" stroke="#f59e0b" stroke-width="1.3" points="1056.27,-368.62 1057.91,-360.3 1051.44,-365.78 1056.27,-368.62"/>
|
|
397
|
+
<text xml:space="preserve" text-anchor="middle" x="476.38" y="-462.07" font-family="sans-Serif" font-size="9.00" fill="#cbd5e1">auto_extrospect</text>
|
|
398
|
+
<text xml:space="preserve" text-anchor="middle" x="476.38" y="-451.57" font-family="sans-Serif" font-size="9.00" fill="#cbd5e1">(AUTO_SECTIONS only)</text>
|
|
398
399
|
</g>
|
|
399
400
|
<!-- Result->Verify -->
|
|
400
401
|
<g id="edge49" class="edge">
|
data/documentation/pwn-REPL.md
CHANGED
|
@@ -19,7 +19,7 @@ commands.
|
|
|
19
19
|
| Command | Implemented in | Purpose |
|
|
20
20
|
|---|---|---|
|
|
21
21
|
| `pwn-ai` | `Agent::Loop` | Enter the AI agent TUI |
|
|
22
|
-
| `pwn-vault` | `PWN::Plugins::Vault` | Decrypt
|
|
22
|
+
| `pwn-vault` | `PWN::Plugins::Vault` | Decrypt, edit `~/.pwn/pwn.yaml`, re-encrypt. Stays in the editor until `PWN::Config` accepts the file. |
|
|
23
23
|
| `pwn-asm` | `Plugins::Assembly` | Multiline asm ↔ opcodes workbench |
|
|
24
24
|
| `pwn-mesh` | `Meshtastic` gem | Meshtastic serial / MQTT client (Ruby ≥ 4.0; installed via `pwn setup`) |
|
|
25
25
|
| `pwn-ai-memory` | `PWN::Memory` | View/edit persistent memory |
|
|
@@ -38,9 +38,10 @@ $ pwn --ai "run bin/pwn_sast against ./src and push findings to DefectDojo"
|
|
|
38
38
|
chosen)` DPO preference pair** in `~/.pwn/preferences.jsonl`.
|
|
39
39
|
3. **Registry** hands Loop the tool schemas - the full set for frontier
|
|
40
40
|
engines, or `CORE_TOOLS` + top-K keyword-relevant when
|
|
41
|
-
`ai.agent.tool_router` is on
|
|
42
|
-
`Policy` after a pair has been visited
|
|
43
|
-
|
|
41
|
+
`ai.agent.tool_router` is on (default on for `ollama` / `openwebui`). Rank
|
|
42
|
+
can include a Q-advantage term from `Policy` after a pair has been visited
|
|
43
|
+
at least twice, plus `ai.agent.tool_preference` as a smaller tie-break.
|
|
44
|
+
Planning still owns the task list. *(local)* `Learning.exemplars_for` splices a compressed
|
|
44
45
|
prior-success trace as few-shot; *(local)* `plan_first` forces a numbered
|
|
45
46
|
tool plan before the first dispatch (optionally red-teamed by
|
|
46
47
|
`Curriculum.red_team_plan`).
|
|
@@ -80,8 +81,10 @@ $ pwn --ai "run bin/pwn_sast against ./src and push findings to DefectDojo"
|
|
|
80
81
|
outside world; a `:refuted` verify is itself recorded as a Mistakes
|
|
81
82
|
`assumption` fingerprint.
|
|
82
83
|
7. Results are appended to the message list; go to 5.
|
|
83
|
-
8. When the reply has *no* tool_calls it's the **final answer
|
|
84
|
-
`
|
|
84
|
+
8. When the reply has *no* tool_calls it's the **final answer**. With
|
|
85
|
+
`defer_introspect` on (default), the user-visible reply returns first and
|
|
86
|
+
`Learning.auto_introspect` runs on a background thread. Specs and cron stay
|
|
87
|
+
inline. Then *(local)*
|
|
85
88
|
`fact_check_local_final` auto-`extro_verify`s every CVE / version-shaped
|
|
86
89
|
claim in the answer; **`Reward.judge`** scores (request, final) with a cheap LLM ORM →
|
|
87
90
|
`{score, verdict, rationale, source}` (heuristic overlap only if the engine is unavailable); **`Policy.finish`** applies that judge score as
|
|
@@ -162,7 +165,7 @@ task_summary_interval_s: 8.0 # or every N seconds (verbose)
|
|
|
162
165
|
task_summary_verbose: false # mid-flight Progress: lines
|
|
163
166
|
task_summary_llm: true # LLM task decompose for autonomous goals (default on)
|
|
164
167
|
request_kind_llm: null # null = follow task_summary_llm; false = heuristic-only
|
|
165
|
-
max_iters:
|
|
168
|
+
max_iters: 75 # budget pressure may lower the effective cap (stricter on local engines)
|
|
166
169
|
```
|
|
167
170
|
|
|
168
171
|
|
|
@@ -176,8 +179,9 @@ max_iters: 25 # budget pressure may lower the effective cap (s
|
|
|
176
179
|
summary turn.
|
|
177
180
|
- Run `mistakes_list` before retrying something that failed last session -
|
|
178
181
|
the fix may already be recorded.
|
|
179
|
-
- `ai.agent.tool_router
|
|
180
|
-
a local model - that cuts mis-routing a lot.
|
|
182
|
+
- Leave `ai.agent.tool_router` and `ai.agent.plan_first` on auto when running
|
|
183
|
+
a local model - that cuts mis-routing a lot. Tune `ai.agent.tool_preference`
|
|
184
|
+
if you want recall / sessions ahead of `shell` on ties.
|
|
181
185
|
- Set `ai.reflect_engine:` to a frontier provider so lessons written to
|
|
182
186
|
`~/.pwn/memory.json` stay high-signal even when the executing engine is
|
|
183
187
|
local.
|
|
@@ -201,7 +205,10 @@ max_iters: 25 # budget pressure may lower the effective cap (s
|
|
|
201
205
|
| `reward_model` | `nil` | optional cheaper model id for `Reward.judge` / `.prm` (nil = active engine default) |
|
|
202
206
|
| `reward_llm_timeout` | `12` | seconds for the cheap ORM chat (clamped 2..30) |
|
|
203
207
|
| `verify_as_reward` | `nil` (auto) | browser-grounded claim sample policy |
|
|
204
|
-
| `local_introspect` | `:failure_only` | ollama end-of-turn introspect policy |
|
|
208
|
+
| `local_introspect` | `:failure_only` | ollama / openwebui end-of-turn introspect policy |
|
|
209
|
+
| `tool_preference` | memory_recall-first list | Rank bonus + Policy suggested-action order |
|
|
210
|
+
| `defer_introspect` | `true` | Post-answer Learning on a background thread |
|
|
211
|
+
| `prompt_cache` | `true` | Engine-native prefix cache (not Ollama / Open WebUI) |
|
|
205
212
|
|
|
206
213
|
Full detail: [Reinforcement Learning](Reinforcement-Learning.md).
|
|
207
214
|
|
data/lib/pwn/ai/agent/policy.rb
CHANGED
|
@@ -572,16 +572,22 @@ module PWN
|
|
|
572
572
|
end
|
|
573
573
|
|
|
574
574
|
ev = evaluate(limit: opts[:limit] || 40)
|
|
575
|
+
fallback = %w[memory_recall sessions_view pwn_eval shell mistakes_record mistakes_resolve learning_note_outcome memory_remember]
|
|
576
|
+
pref = begin
|
|
577
|
+
PWN::AI::Agent::Registry.preference_order
|
|
578
|
+
rescue StandardError
|
|
579
|
+
[]
|
|
580
|
+
end
|
|
581
|
+
actions = pref.empty? ? fallback : pref
|
|
575
582
|
rec = begin
|
|
576
|
-
|
|
577
|
-
recs[:action]
|
|
583
|
+
recommend(actions: actions, epsilon: 0.0)[:action]
|
|
578
584
|
rescue StandardError
|
|
579
585
|
nil
|
|
580
586
|
end
|
|
581
587
|
lines << 'POLICY (R5 tabular Q / REINFORCE — advisory only, does not replace planning)'
|
|
582
588
|
lines << " episodes=#{s[:n_episodes]} states=#{s[:n_states]} pairs=#{s[:n_pairs]} updates=#{s[:n_updates]}"
|
|
583
589
|
lines << " mean_return=#{s[:mean_return] || '-'} mean|TD|=#{s[:mean_abs_td] || '-'} greedy_match=#{ev[:greedy_match] || '-'}"
|
|
584
|
-
lines << " current_state=#{current_state || '(none)'} suggest=#{rec || '-'}"
|
|
590
|
+
lines << " current_state=#{current_state || '(none)'} suggest=#{rec || '-'} tool_preference=#{actions.join(',')}"
|
|
585
591
|
"#{lines.join("\n")}\n"
|
|
586
592
|
rescue StandardError
|
|
587
593
|
''
|
|
@@ -43,6 +43,12 @@ module PWN
|
|
|
43
43
|
CORE_TOOLS = %w[shell pwn_eval memory_remember memory_recall
|
|
44
44
|
mistakes_record mistakes_resolve learning_note_outcome].freeze
|
|
45
45
|
|
|
46
|
+
# Operator-tunable tool order. Prefer these names when keyword-fit
|
|
47
|
+
# (or other rank features) tie. Overridable via opts[:order],
|
|
48
|
+
# opts[:preference], or PWN::Env[:ai][:agent][:tool_preference].
|
|
49
|
+
# Explicit nil/empty order disables preference (no Env/DEFAULT fallback).
|
|
50
|
+
DEFAULT_PREFERENCE = %w[memory_recall sessions_view pwn_eval shell mistakes_record mistakes_resolve learning_note_outcome memory_remember].freeze
|
|
51
|
+
|
|
46
52
|
@entries = {}
|
|
47
53
|
@discovered = false
|
|
48
54
|
|
|
@@ -100,7 +106,9 @@ module PWN
|
|
|
100
106
|
# tools = PWN::AI::Agent::Registry.definitions(
|
|
101
107
|
# enabled: 'optional - Array of toolset names to include; nil = all whose check passes',
|
|
102
108
|
# relevance: 'optional - user request; when set AND :tool_router is enabled, slim to CORE + top-K keyword matches',
|
|
103
|
-
# top_k: 'optional - keyword-ranked tools to keep beyond CORE (default 10)'
|
|
109
|
+
# top_k: 'optional - keyword-ranked tools to keep beyond CORE (default 10)',
|
|
110
|
+
# order: 'optional - preference list forwarded only when this key is present',
|
|
111
|
+
# preference: 'optional - alias of :order; same key-present rule'
|
|
104
112
|
# )
|
|
105
113
|
|
|
106
114
|
public_class_method def self.definitions(opts = {})
|
|
@@ -108,19 +116,84 @@ module PWN
|
|
|
108
116
|
enabled = enabled.map(&:to_s) if enabled
|
|
109
117
|
pool = @entries.values.select { |e| (enabled.nil? || enabled.include?(e.toolset)) && safe_check(entry: e) }
|
|
110
118
|
|
|
119
|
+
pref_fwd = {}
|
|
120
|
+
pref_fwd[:order] = opts[:order] if opts.key?(:order)
|
|
121
|
+
pref_fwd[:preference] = opts[:preference] if opts.key?(:preference)
|
|
122
|
+
|
|
111
123
|
if opts[:relevance] && router_enabled?
|
|
112
|
-
keep
|
|
124
|
+
keep = rank({ query: opts[:relevance], entries: pool }.merge(pref_fwd)).first(opts[:top_k] || 10).map(&:name)
|
|
113
125
|
names = (CORE_TOOLS + keep).uniq
|
|
114
126
|
pool = pool.select { |e| names.include?(e.name) }
|
|
115
127
|
end
|
|
116
128
|
|
|
129
|
+
pool = apply_preference({ items: pool }.merge(pref_fwd))
|
|
117
130
|
pool.map { |e| { type: 'function', function: e.schema } }
|
|
118
131
|
end
|
|
119
132
|
|
|
133
|
+
# Supported Method Parameters::
|
|
134
|
+
# order = PWN::AI::Agent::Registry.preference_order(
|
|
135
|
+
# order: 'optional - Array of tool names; explicit nil/[] disables',
|
|
136
|
+
# preference: 'optional - alias of :order. Explicit key disables Env/DEFAULT fallback'
|
|
137
|
+
# )
|
|
138
|
+
|
|
139
|
+
public_class_method def self.preference_order(opts = {})
|
|
140
|
+
explicit = opts.key?(:order) || opts.key?(:preference)
|
|
141
|
+
if explicit
|
|
142
|
+
raw = opts.key?(:preference) ? opts[:preference] : opts[:order]
|
|
143
|
+
return [] if raw.nil? || (raw.respond_to?(:empty?) && raw.empty?)
|
|
144
|
+
|
|
145
|
+
return Array(raw).map(&:to_s).reject(&:empty?)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
raw = nil
|
|
149
|
+
raw = PWN::Env.dig(:ai, :agent, :tool_preference) if defined?(PWN::Env) && PWN::Env.is_a?(Hash)
|
|
150
|
+
raw = DEFAULT_PREFERENCE if raw.nil? || (raw.respond_to?(:empty?) && raw.empty?)
|
|
151
|
+
Array(raw).map(&:to_s).reject(&:empty?)
|
|
152
|
+
rescue StandardError
|
|
153
|
+
DEFAULT_PREFERENCE.dup
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Supported Method Parameters::
|
|
157
|
+
# sorted = PWN::AI::Agent::Registry.apply_preference(
|
|
158
|
+
# items: 'required - Array of Entry objects or OpenAI tool hashes',
|
|
159
|
+
# order: 'optional - forwarded to preference_order only when the key is set',
|
|
160
|
+
# preference: 'optional - alias of :order; same key-present rule'
|
|
161
|
+
# )
|
|
162
|
+
#
|
|
163
|
+
# Stable sort: preferred names first, original index as the tie-break
|
|
164
|
+
# so equal-preference items keep their incoming order.
|
|
165
|
+
# Empty preference (explicit [] / nil) leaves items unsorted.
|
|
166
|
+
|
|
167
|
+
public_class_method def self.apply_preference(opts = {})
|
|
168
|
+
items = Array(opts[:items] || opts[:entries])
|
|
169
|
+
po = {}
|
|
170
|
+
po[:preference] = opts[:preference] if opts.key?(:preference)
|
|
171
|
+
po[:order] = opts[:order] if opts.key?(:order)
|
|
172
|
+
order = preference_order(po)
|
|
173
|
+
return items if order.empty?
|
|
174
|
+
|
|
175
|
+
index = {}
|
|
176
|
+
order.each_with_index { |name, i| index[name.to_s] = i }
|
|
177
|
+
fallback = order.length
|
|
178
|
+
items.sort_by.with_index do |item, orig|
|
|
179
|
+
name = if item.respond_to?(:name)
|
|
180
|
+
item.name.to_s
|
|
181
|
+
elsif item.is_a?(Hash)
|
|
182
|
+
(item.dig(:function, :name) ||
|
|
183
|
+
item.dig('function', 'name') ||
|
|
184
|
+
item[:name] || item['name']).to_s
|
|
185
|
+
else
|
|
186
|
+
item.to_s
|
|
187
|
+
end
|
|
188
|
+
[index.fetch(name, fallback), orig]
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
120
192
|
# Supported Method Parameters::
|
|
121
193
|
# ranked = PWN::AI::Agent::Registry.rank(
|
|
122
194
|
# query: 'required - user request text',
|
|
123
|
-
# entries: 'optional - Entry pool to rank (default .all)'
|
|
195
|
+
# entries: 'optional - Entry pool to rank (default .all)',
|
|
196
|
+
# order: 'optional - preference list forwarded to preference_order'
|
|
124
197
|
# )
|
|
125
198
|
#
|
|
126
199
|
# C1 advantage-weighted router: score = α·keyword_sim + β·(tool
|
|
@@ -136,11 +209,23 @@ module PWN
|
|
|
136
209
|
|
|
137
210
|
tokens = query.scan(/[a-z0-9_]{3,}/).uniq
|
|
138
211
|
# C1 — advantage-weighted router:
|
|
139
|
-
# score =
|
|
212
|
+
# score = alpha*keyword_sim + beta*advantage + gamma*UCB(tool)
|
|
213
|
+
# + delta*prm_advantage + eps_q*Q_adv + zeta*pref_score
|
|
140
214
|
# UCB gives untried / low-N tools an exploration bonus so a single
|
|
141
215
|
# early failure (before its dep was installed) does not blacklist
|
|
142
216
|
# it forever; advantage prefers tools that outperform the fleet.
|
|
217
|
+
# zeta * pref_score (zeta=0.35) boosts Env/opts tool preference;
|
|
218
|
+
# alpha stays 1.0 so keyword fit remains the primary signal.
|
|
219
|
+
# zeta is 0 when preference is empty so ties are not pref-boosted.
|
|
143
220
|
alpha = 1.0
|
|
221
|
+
po = {}
|
|
222
|
+
po[:preference] = opts[:preference] if opts.key?(:preference)
|
|
223
|
+
po[:order] = opts[:order] if opts.key?(:order)
|
|
224
|
+
pref_order = preference_order(po)
|
|
225
|
+
zeta = pref_order.empty? ? 0.0 : 0.35
|
|
226
|
+
pref_index = {}
|
|
227
|
+
pref_order.each_with_index { |n, i| pref_index[n.to_s] = i }
|
|
228
|
+
pref_n = [pref_order.length, 1].max
|
|
144
229
|
# P4 — haircut advantage weight when reward proxy is hacked
|
|
145
230
|
trust = defined?(Metrics) && Metrics.respond_to?(:proxy_trust) ? Metrics.proxy_trust : 1.0
|
|
146
231
|
beta = 0.3 * trust
|
|
@@ -187,10 +272,14 @@ module PWN
|
|
|
187
272
|
else
|
|
188
273
|
0.0
|
|
189
274
|
end
|
|
190
|
-
[e
|
|
275
|
+
pidx = pref_index[e.name]
|
|
276
|
+
pref = pidx.nil? ? 0.0 : ((pref_n - pidx).to_f / pref_n)
|
|
277
|
+
total = (alpha * sim) + (beta * adv) + (gamma * ucb) + (delta * prm) + (eps_q * qadv) + (zeta * pref)
|
|
278
|
+
[e, sim, total]
|
|
191
279
|
end
|
|
280
|
+
# Equal keyword-fit (and equal total): preferred name first.
|
|
192
281
|
scored.reject { |_, sim, _| sim.zero? }
|
|
193
|
-
.sort_by { |
|
|
282
|
+
.sort_by { |e, _, s| [-s, pref_index.fetch(e.name, pref_n), e.name] }
|
|
194
283
|
.map(&:first)
|
|
195
284
|
end
|
|
196
285
|
|
data/lib/pwn/ai/anthropic.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'rest-client'
|
|
5
|
-
require 'tty-spinner'
|
|
6
5
|
require 'securerandom'
|
|
7
6
|
require 'base64'
|
|
8
7
|
require 'digest'
|
|
@@ -418,10 +417,7 @@ module PWN
|
|
|
418
417
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
419
418
|
rest_client = browser_obj[:browser]::Request
|
|
420
419
|
|
|
421
|
-
if spinner
|
|
422
|
-
spin = TTY::Spinner.new(format: :dots)
|
|
423
|
-
spin.auto_spin
|
|
424
|
-
end
|
|
420
|
+
spin = PWN::Plugins::TTYSpinner.start if spinner
|
|
425
421
|
|
|
426
422
|
retry_count = 0
|
|
427
423
|
begin
|
|
@@ -479,7 +475,7 @@ module PWN
|
|
|
479
475
|
raise e
|
|
480
476
|
end
|
|
481
477
|
ensure
|
|
482
|
-
|
|
478
|
+
PWN::Plugins::TTYSpinner.stop(spin: spin)
|
|
483
479
|
end
|
|
484
480
|
end
|
|
485
481
|
|
data/lib/pwn/ai/gemini.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'rest-client'
|
|
5
|
-
require 'tty-spinner'
|
|
6
5
|
require 'securerandom'
|
|
7
6
|
|
|
8
7
|
module PWN
|
|
@@ -55,10 +54,7 @@ module PWN
|
|
|
55
54
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
56
55
|
rest_client = browser_obj[:browser]::Request
|
|
57
56
|
|
|
58
|
-
if spinner
|
|
59
|
-
spin = TTY::Spinner.new(format: :dots)
|
|
60
|
-
spin.auto_spin
|
|
61
|
-
end
|
|
57
|
+
spin = PWN::Plugins::TTYSpinner.start if spinner
|
|
62
58
|
|
|
63
59
|
retry_count = 0
|
|
64
60
|
begin
|
|
@@ -103,7 +99,7 @@ module PWN
|
|
|
103
99
|
raise e
|
|
104
100
|
end
|
|
105
101
|
ensure
|
|
106
|
-
|
|
102
|
+
PWN::Plugins::TTYSpinner.stop(spin: spin)
|
|
107
103
|
end
|
|
108
104
|
end
|
|
109
105
|
|
data/lib/pwn/ai/grok.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'rest-client'
|
|
5
|
-
require 'tty-spinner'
|
|
6
5
|
require 'uri'
|
|
7
6
|
require 'base64'
|
|
8
7
|
|
|
@@ -424,10 +423,7 @@ module PWN
|
|
|
424
423
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
425
424
|
rest_client = browser_obj[:browser]::Request
|
|
426
425
|
|
|
427
|
-
if spinner
|
|
428
|
-
spin = TTY::Spinner.new(format: :dots)
|
|
429
|
-
spin.auto_spin
|
|
430
|
-
end
|
|
426
|
+
spin = PWN::Plugins::TTYSpinner.start if spinner
|
|
431
427
|
|
|
432
428
|
retry_count = 0
|
|
433
429
|
begin
|
|
@@ -495,7 +491,7 @@ module PWN
|
|
|
495
491
|
end
|
|
496
492
|
"#{e.message}: #{e.response}"
|
|
497
493
|
ensure
|
|
498
|
-
|
|
494
|
+
PWN::Plugins::TTYSpinner.stop(spin: spin)
|
|
499
495
|
end
|
|
500
496
|
|
|
501
497
|
# Supported Method Parameters::
|
|
@@ -695,6 +691,9 @@ module PWN
|
|
|
695
691
|
temp = opts[:temp].to_f
|
|
696
692
|
temp = engine[:temp].to_f.nonzero? || 1 if temp.zero?
|
|
697
693
|
|
|
694
|
+
spinner = opts[:spinner]
|
|
695
|
+
timeout = opts[:timeout]
|
|
696
|
+
|
|
698
697
|
conv_id = nil
|
|
699
698
|
if defined?(PWN::AI::Agent::PromptCache) &&
|
|
700
699
|
PWN::AI::Agent::PromptCache.enabled?(engine: :grok)
|
|
@@ -722,8 +721,8 @@ module PWN
|
|
|
722
721
|
http_method: :post,
|
|
723
722
|
rest_call: 'chat/completions',
|
|
724
723
|
http_body: http_body,
|
|
725
|
-
timeout:
|
|
726
|
-
spinner:
|
|
724
|
+
timeout: timeout,
|
|
725
|
+
spinner: spinner,
|
|
727
726
|
quiet: opts[:quiet],
|
|
728
727
|
headers: (conv_id ? { 'x-grok-conv-id' => conv_id } : nil)
|
|
729
728
|
)
|
data/lib/pwn/ai/ollama.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'base64'
|
|
5
5
|
require 'securerandom'
|
|
6
|
-
require 'tty-spinner'
|
|
7
6
|
|
|
8
7
|
module PWN
|
|
9
8
|
module AI
|
|
@@ -75,10 +74,7 @@ module PWN
|
|
|
75
74
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
76
75
|
rest_client = browser_obj[:browser]::Request
|
|
77
76
|
|
|
78
|
-
if spinner
|
|
79
|
-
spin = TTY::Spinner.new(format: :dots)
|
|
80
|
-
spin.auto_spin
|
|
81
|
-
end
|
|
77
|
+
spin = PWN::Plugins::TTYSpinner.start if spinner
|
|
82
78
|
|
|
83
79
|
retry_count = 0
|
|
84
80
|
begin
|
|
@@ -198,7 +194,7 @@ module PWN
|
|
|
198
194
|
raise e
|
|
199
195
|
end
|
|
200
196
|
ensure
|
|
201
|
-
|
|
197
|
+
PWN::Plugins::TTYSpinner.stop(spin: spin)
|
|
202
198
|
end
|
|
203
199
|
|
|
204
200
|
# Pull a user-visible answer out of a reasoning/"thinking" channel.
|
data/lib/pwn/ai/open_ai.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'base64'
|
|
5
5
|
require 'securerandom'
|
|
6
|
-
require 'tty-spinner'
|
|
7
6
|
require 'digest'
|
|
8
7
|
require 'uri'
|
|
9
8
|
|
|
@@ -460,10 +459,7 @@ module PWN
|
|
|
460
459
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
461
460
|
rest_client = browser_obj[:browser]::Request
|
|
462
461
|
|
|
463
|
-
if spinner
|
|
464
|
-
spin = TTY::Spinner.new(format: :dots)
|
|
465
|
-
spin.auto_spin
|
|
466
|
-
end
|
|
462
|
+
spin = PWN::Plugins::TTYSpinner.start if spinner
|
|
467
463
|
|
|
468
464
|
retry_count = 0
|
|
469
465
|
begin
|
|
@@ -534,7 +530,7 @@ module PWN
|
|
|
534
530
|
end
|
|
535
531
|
"#{e.message}: #{e.response}"
|
|
536
532
|
ensure
|
|
537
|
-
|
|
533
|
+
PWN::Plugins::TTYSpinner.stop(spin: spin)
|
|
538
534
|
end
|
|
539
535
|
|
|
540
536
|
# Supported Method Parameters::
|
data/lib/pwn/ai/open_web_ui.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'base64'
|
|
5
5
|
require 'securerandom'
|
|
6
|
-
require 'tty-spinner'
|
|
7
6
|
|
|
8
7
|
module PWN
|
|
9
8
|
module AI
|
|
@@ -87,10 +86,7 @@ module PWN
|
|
|
87
86
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
88
87
|
rest_client = browser_obj[:browser]::Request
|
|
89
88
|
|
|
90
|
-
if spinner
|
|
91
|
-
spin = TTY::Spinner.new(format: :dots)
|
|
92
|
-
spin.auto_spin
|
|
93
|
-
end
|
|
89
|
+
spin = PWN::Plugins::TTYSpinner.start if spinner
|
|
94
90
|
|
|
95
91
|
retry_count = 0
|
|
96
92
|
begin
|
|
@@ -226,7 +222,7 @@ module PWN
|
|
|
226
222
|
raise e
|
|
227
223
|
end
|
|
228
224
|
ensure
|
|
229
|
-
|
|
225
|
+
PWN::Plugins::TTYSpinner.stop(spin: spin)
|
|
230
226
|
end
|
|
231
227
|
|
|
232
228
|
# Pull a user-visible answer out of a reasoning/"thinking" channel.
|
data/lib/pwn/blockchain/btc.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'base64'
|
|
4
4
|
require 'json'
|
|
5
5
|
require 'rest-client'
|
|
6
|
-
require 'tty-spinner'
|
|
7
6
|
require 'date'
|
|
8
7
|
|
|
9
8
|
module PWN
|
|
@@ -54,10 +53,7 @@ module PWN
|
|
|
54
53
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
55
54
|
rest_client = browser_obj[:browser]::Request
|
|
56
55
|
|
|
57
|
-
if spinner
|
|
58
|
-
spin = TTY::Spinner.new(format: :dots)
|
|
59
|
-
spin.auto_spin
|
|
60
|
-
end
|
|
56
|
+
spin = PWN::Plugins::TTYSpinner.start if spinner
|
|
61
57
|
|
|
62
58
|
retries = 0
|
|
63
59
|
case http_method.to_s.scrub.downcase.to_sym
|
|
@@ -109,7 +105,7 @@ module PWN
|
|
|
109
105
|
rescue StandardError => e
|
|
110
106
|
raise e
|
|
111
107
|
ensure
|
|
112
|
-
|
|
108
|
+
PWN::Plugins::TTYSpinner.stop(spin: spin)
|
|
113
109
|
end
|
|
114
110
|
|
|
115
111
|
private_class_method def self.btc_rpc_call(opts = {})
|
data/lib/pwn/blockchain/eth.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'rest-client'
|
|
5
|
-
require 'tty-spinner'
|
|
6
5
|
|
|
7
6
|
module PWN
|
|
8
7
|
module Blockchain
|
|
@@ -41,10 +40,7 @@ module PWN
|
|
|
41
40
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
42
41
|
rest_client = browser_obj[:browser]::Request
|
|
43
42
|
|
|
44
|
-
if spinner
|
|
45
|
-
spin = TTY::Spinner.new(format: :dots)
|
|
46
|
-
spin.auto_spin
|
|
47
|
-
end
|
|
43
|
+
spin = PWN::Plugins::TTYSpinner.start if spinner
|
|
48
44
|
|
|
49
45
|
retries = 0
|
|
50
46
|
case http_method
|
|
@@ -96,7 +92,7 @@ module PWN
|
|
|
96
92
|
rescue StandardError => e
|
|
97
93
|
raise e
|
|
98
94
|
ensure
|
|
99
|
-
|
|
95
|
+
PWN::Plugins::TTYSpinner.stop(spin: spin)
|
|
100
96
|
end
|
|
101
97
|
|
|
102
98
|
# Supported Method Parameters::
|
data/lib/pwn/config.rb
CHANGED
|
@@ -160,6 +160,7 @@ module PWN
|
|
|
160
160
|
# LLM tangible-task decomposition for autonomous goals (default on).
|
|
161
161
|
task_summary_llm: nil,
|
|
162
162
|
tool_router: nil, # nil = auto (true when :active is local :ollama/:openwebui) — cuts ~11k→~3k schema tokens
|
|
163
|
+
tool_preference: %w[memory_recall sessions_view pwn_eval shell mistakes_record mistakes_resolve learning_note_outcome memory_remember],
|
|
163
164
|
escalation_persona: 'escalator', # Swarm persona for frontier corrective hints when a local model is stuck
|
|
164
165
|
# sample E3 verify_as_reward: true|false|nil(auto: ~10% local / always frontier when CLAIM_RX hits)
|
|
165
166
|
verify_as_reward: nil,
|
|
@@ -568,9 +569,9 @@ module PWN
|
|
|
568
569
|
PWN::Cron.install_defaults if defined?(PWN::Cron) && PWN::Cron.respond_to?(:install_defaults)
|
|
569
570
|
|
|
570
571
|
# Fill missing ai.agent / ai.ollama knobs from code defaults so older
|
|
571
|
-
# vault files pick up Ollama/RL fixes (tool_router nil-auto,
|
|
572
|
-
# introspect policy, history compaction, result_max,
|
|
573
|
-
# default) without requiring a full pwn-vault rewrite. Explicit
|
|
572
|
+
# vault files pick up Ollama/RL fixes (tool_router nil-auto, tool_preference
|
|
573
|
+
# memory_recall-first, local introspect policy, history compaction, result_max,
|
|
574
|
+
# escalation default) without requiring a full pwn-vault rewrite. Explicit
|
|
574
575
|
# vault values always win — deep_merge only supplies ABSENT keys.
|
|
575
576
|
merge_ai_defaults!(env: env)
|
|
576
577
|
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'cgi'
|
|
4
4
|
require 'json'
|
|
5
5
|
require 'securerandom'
|
|
6
|
-
require 'tty-spinner'
|
|
7
6
|
|
|
8
7
|
module PWN
|
|
9
8
|
module Plugins
|
|
@@ -45,8 +44,7 @@ module PWN
|
|
|
45
44
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
46
45
|
rest_client = browser_obj[:browser]::Request
|
|
47
46
|
|
|
48
|
-
spinner =
|
|
49
|
-
spinner.auto_spin
|
|
47
|
+
spinner = PWN::Plugins::TTYSpinner.start
|
|
50
48
|
|
|
51
49
|
max_request_attempts = 3
|
|
52
50
|
tot_request_attempts ||= 1
|
|
@@ -111,7 +109,7 @@ module PWN
|
|
|
111
109
|
rescue StandardError => e
|
|
112
110
|
raise e
|
|
113
111
|
ensure
|
|
114
|
-
|
|
112
|
+
PWN::Plugins::TTYSpinner.stop(spin: spinner)
|
|
115
113
|
end
|
|
116
114
|
|
|
117
115
|
# Supported Method Parameters::
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
-
require 'tty-spinner'
|
|
5
4
|
|
|
6
5
|
module PWN
|
|
7
6
|
module Plugins
|
|
@@ -47,8 +46,7 @@ module PWN
|
|
|
47
46
|
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
48
47
|
rest_client = browser_obj[:browser]::Request
|
|
49
48
|
|
|
50
|
-
spinner =
|
|
51
|
-
spinner.auto_spin
|
|
49
|
+
spinner = PWN::Plugins::TTYSpinner.start
|
|
52
50
|
|
|
53
51
|
max_request_attempts = 3
|
|
54
52
|
tot_request_attempts ||= 1
|
|
@@ -127,7 +125,7 @@ module PWN
|
|
|
127
125
|
rescue StandardError => e
|
|
128
126
|
raise e
|
|
129
127
|
ensure
|
|
130
|
-
|
|
128
|
+
PWN::Plugins::TTYSpinner.stop(spin: spinner)
|
|
131
129
|
end
|
|
132
130
|
|
|
133
131
|
# Supported Method Parameters::
|