web3skill 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/archives/web3-audit-orchestrator.skill +0 -0
- package/dist/archives/web3-audit-reporting.skill +0 -0
- package/dist/archives/web3-fuzzing-and-invariants.skill +0 -0
- package/dist/archives/web3-native-operator.skill +0 -0
- package/dist/archives/web3-repo-heuristics.skill +0 -0
- package/dist/archives/web3-research-and-market-intel.skill +0 -0
- package/dist/archives/web3-risk-gate.skill +0 -0
- package/dist/archives/web3-service-orchestrator.skill +0 -0
- package/dist/archives/web3-static-analysis-runner.skill +0 -0
- package/dist/archives/web3-trace-and-state-analysis.skill +0 -0
- package/dist/archives/web3-transaction-simulator.skill +0 -0
- package/dist/archives/web3-wallet-operator.skill +0 -0
- package/dist/manifest.json +170 -0
- package/dist/skills/web3-audit-orchestrator/SKILL.md +79 -0
- package/dist/skills/web3-audit-orchestrator/references/ADAPTER_CONSUMPTION_MAP.md +15 -0
- package/dist/skills/web3-audit-orchestrator/references/OUTPUT_TEMPLATE.md +52 -0
- package/dist/skills/web3-audit-orchestrator/references/REVIEW_STATE_MACHINE.md +25 -0
- package/dist/skills/web3-audit-orchestrator/scripts/render_audit_review.py +95 -0
- package/dist/skills/web3-audit-reporting/SKILL.md +77 -0
- package/dist/skills/web3-audit-reporting/references/FINDING_TEMPLATE.md +54 -0
- package/dist/skills/web3-audit-reporting/references/REPORT_TEMPLATE.md +58 -0
- package/dist/skills/web3-audit-reporting/references/RETEST_TEMPLATE.md +35 -0
- package/dist/skills/web3-audit-reporting/references/SEVERITY_RUBRIC.md +75 -0
- package/dist/skills/web3-fuzzing-and-invariants/SKILL.md +68 -0
- package/dist/skills/web3-fuzzing-and-invariants/references/ADAPTER_CONSUMPTION_MAP.md +14 -0
- package/dist/skills/web3-fuzzing-and-invariants/references/OUTPUT_TEMPLATE.md +40 -0
- package/dist/skills/web3-fuzzing-and-invariants/references/READINESS_AND_FAILURES.md +25 -0
- package/dist/skills/web3-fuzzing-and-invariants/scripts/render_fuzz_summary.py +64 -0
- package/dist/skills/web3-native-operator/SKILL.md +218 -0
- package/dist/skills/web3-native-operator/references/EXECUTION_BUNDLE_TEMPLATE.md +47 -0
- package/dist/skills/web3-native-operator/references/OPERATOR_BUNDLE_TEMPLATE.md +39 -0
- package/dist/skills/web3-native-operator/references/POSTTRADE_FOLLOWUP_BUNDLE_TEMPLATE.md +35 -0
- package/dist/skills/web3-native-operator/references/POSTTRADE_WATCH_TEMPLATE.md +34 -0
- package/dist/skills/web3-native-operator/references/PRETRADE_PACKET_TEMPLATE.md +34 -0
- package/dist/skills/web3-native-operator/references/ROUTE_RECIPES.md +140 -0
- package/dist/skills/web3-native-operator/references/ROUTING_STATE_MACHINE.md +73 -0
- package/dist/skills/web3-native-operator/references/WATCH_CRON_REQUEST_TEMPLATE.md +26 -0
- package/dist/skills/web3-native-operator/references/WATCH_FOLLOWUP_BUNDLE_TEMPLATE.md +35 -0
- package/dist/skills/web3-native-operator/references/WATCH_HEARTBEAT_TEMPLATE.md +31 -0
- package/dist/skills/web3-native-operator/scripts/apply_followup_bundle_to_heartbeat.py +118 -0
- package/dist/skills/web3-native-operator/scripts/render_execution_bundle.py +259 -0
- package/dist/skills/web3-native-operator/scripts/render_operator_bundle.py +800 -0
- package/dist/skills/web3-native-operator/scripts/render_posttrade_followup_bundle.py +118 -0
- package/dist/skills/web3-native-operator/scripts/render_posttrade_watch_status.py +125 -0
- package/dist/skills/web3-native-operator/scripts/render_pretrade_packet.py +205 -0
- package/dist/skills/web3-native-operator/scripts/render_watch_cron_request.py +88 -0
- package/dist/skills/web3-native-operator/scripts/render_watch_followup_bundle.py +118 -0
- package/dist/skills/web3-native-operator/scripts/render_watch_heartbeat.py +52 -0
- package/dist/skills/web3-repo-heuristics/SKILL.md +37 -0
- package/dist/skills/web3-repo-heuristics/references/FOUNDRY.md +49 -0
- package/dist/skills/web3-repo-heuristics/references/HARDHAT.md +47 -0
- package/dist/skills/web3-repo-heuristics/references/VYPER.md +26 -0
- package/dist/skills/web3-research-and-market-intel/SKILL.md +138 -0
- package/dist/skills/web3-research-and-market-intel/references/ADAPTER_CONSUMPTION_MAP.md +66 -0
- package/dist/skills/web3-research-and-market-intel/references/EVIDENCE_QUALITY.md +27 -0
- package/dist/skills/web3-research-and-market-intel/references/OUTPUT_TEMPLATE.md +37 -0
- package/dist/skills/web3-research-and-market-intel/references/PORTFOLIO_STATUS_TEMPLATE.md +51 -0
- package/dist/skills/web3-research-and-market-intel/references/WATCH_STATUS_TEMPLATE.md +39 -0
- package/dist/skills/web3-research-and-market-intel/scripts/render_portfolio_status.py +85 -0
- package/dist/skills/web3-research-and-market-intel/scripts/render_research_brief.py +58 -0
- package/dist/skills/web3-research-and-market-intel/scripts/render_watch_status.py +70 -0
- package/dist/skills/web3-risk-gate/SKILL.md +100 -0
- package/dist/skills/web3-risk-gate/references/OUTPUT_TEMPLATE.md +72 -0
- package/dist/skills/web3-risk-gate/references/SIGNAL_TAXONOMY.md +34 -0
- package/dist/skills/web3-risk-gate/scripts/merge_risk_gate_blocks.py +189 -0
- package/dist/skills/web3-service-orchestrator/SKILL.md +34 -0
- package/dist/skills/web3-static-analysis-runner/SKILL.md +76 -0
- package/dist/skills/web3-static-analysis-runner/references/ADAPTER_CONSUMPTION_MAP.md +13 -0
- package/dist/skills/web3-static-analysis-runner/references/OUTPUT_TEMPLATE.md +45 -0
- package/dist/skills/web3-static-analysis-runner/references/TRIAGE_BUCKETS.md +16 -0
- package/dist/skills/web3-static-analysis-runner/scripts/render_static_analysis_summary.py +64 -0
- package/dist/skills/web3-trace-and-state-analysis/SKILL.md +74 -0
- package/dist/skills/web3-trace-and-state-analysis/references/ADAPTER_CONSUMPTION_MAP.md +27 -0
- package/dist/skills/web3-trace-and-state-analysis/references/OUTPUT_TEMPLATE.md +63 -0
- package/dist/skills/web3-trace-and-state-analysis/references/TRACE_BACKEND_PREFLIGHT.md +47 -0
- package/dist/skills/web3-trace-and-state-analysis/scripts/render_trace_summary.py +99 -0
- package/dist/skills/web3-transaction-simulator/SKILL.md +83 -0
- package/dist/skills/web3-transaction-simulator/references/OUTPUT_TEMPLATE.md +86 -0
- package/dist/skills/web3-transaction-simulator/references/STATUS_AND_FAILURES.md +49 -0
- package/dist/skills/web3-transaction-simulator/scripts/merge_simulation_blocks.py +198 -0
- package/dist/skills/web3-wallet-operator/SKILL.md +52 -0
- package/dist/skills/web3-wallet-operator/references/ACTION_RECIPES.md +56 -0
- package/dist/skills/web3-wallet-operator/references/OUTPUT_TEMPLATE.md +43 -0
- package/dist/skills/web3-wallet-operator/scripts/render_wallet_operation_plan.py +101 -0
- package/index.js +50 -0
- package/package.json +36 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Watch Heartbeat Template
|
|
2
|
+
|
|
3
|
+
Use this template when a `watch_status` should be persisted into `HEARTBEAT.md`
|
|
4
|
+
for later heartbeat execution.
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
- [ ] Watch token:PEPE
|
|
8
|
+
Task: Re-run whale-watcher for token:PEPE on heartbeat and summarize any new evidence. Escalate immediately if severity becomes high or critical.
|
|
9
|
+
Watch Kind: research
|
|
10
|
+
Status: ACTIVE
|
|
11
|
+
Severity: medium
|
|
12
|
+
Adapter: whale-watcher
|
|
13
|
+
Schedule Hint: */15 * * * *
|
|
14
|
+
Next Check Hint: refresh whale flow after 15 minutes
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Required Fields
|
|
18
|
+
|
|
19
|
+
- title line with checklist marker
|
|
20
|
+
- `Task`
|
|
21
|
+
- `Watch Kind`
|
|
22
|
+
- `Status`
|
|
23
|
+
- `Severity`
|
|
24
|
+
- `Adapter`
|
|
25
|
+
- `Schedule Hint`
|
|
26
|
+
- `Next Check Hint`
|
|
27
|
+
|
|
28
|
+
## Notes
|
|
29
|
+
|
|
30
|
+
- Keep it human-readable because `HEARTBEAT.md` is consumed by the LLM, not a strict parser.
|
|
31
|
+
- `Schedule Hint` stays advisory until a runtime or user turns it into a real cron job.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Apply a watch/posttrade follow-up bundle into HEARTBEAT.md."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
DEFAULT_HEARTBEAT_TEMPLATE = """# Heartbeat Tasks
|
|
13
|
+
|
|
14
|
+
This file is checked every 30 minutes by your nanobot agent.
|
|
15
|
+
Add tasks below that you want the agent to work on periodically.
|
|
16
|
+
|
|
17
|
+
If this file has no tasks (only headers and comments), the agent will skip the heartbeat.
|
|
18
|
+
|
|
19
|
+
## Active Tasks
|
|
20
|
+
|
|
21
|
+
<!-- Add your periodic tasks below this line -->
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Completed
|
|
25
|
+
|
|
26
|
+
<!-- Move completed tasks here or delete them -->
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
31
|
+
parser = argparse.ArgumentParser(description="Apply a follow-up bundle to HEARTBEAT.md.")
|
|
32
|
+
parser.add_argument("--heartbeat-file", required=True)
|
|
33
|
+
parser.add_argument("--bundle-file", required=True, help="JSON file path or '-' for stdin")
|
|
34
|
+
parser.add_argument("--allow-duplicate", action="store_true")
|
|
35
|
+
return parser
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_payload(bundle_file: str) -> dict[str, object]:
|
|
39
|
+
if bundle_file == "-":
|
|
40
|
+
return json.load(sys.stdin)
|
|
41
|
+
return json.loads(Path(bundle_file).read_text(encoding="utf-8"))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def extract_heartbeat_task(payload: dict[str, object]) -> tuple[str, str]:
|
|
45
|
+
for key in ("watch_followup_bundle", "posttrade_followup_bundle"):
|
|
46
|
+
bundle = payload.get(key)
|
|
47
|
+
if isinstance(bundle, dict):
|
|
48
|
+
task = bundle.get("heartbeat_task")
|
|
49
|
+
if isinstance(task, str) and task.strip():
|
|
50
|
+
return key, task.strip("\n")
|
|
51
|
+
raise ValueError("bundle does not contain a heartbeat_task")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def ensure_template(path: Path) -> str:
|
|
55
|
+
if path.exists():
|
|
56
|
+
return path.read_text(encoding="utf-8")
|
|
57
|
+
return DEFAULT_HEARTBEAT_TEMPLATE
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def find_section_indexes(lines: list[str]) -> tuple[int, int]:
|
|
61
|
+
try:
|
|
62
|
+
active_idx = lines.index("## Active Tasks")
|
|
63
|
+
completed_idx = lines.index("## Completed")
|
|
64
|
+
except ValueError as exc:
|
|
65
|
+
raise ValueError("HEARTBEAT.md missing required section headers") from exc
|
|
66
|
+
if completed_idx <= active_idx:
|
|
67
|
+
raise ValueError("HEARTBEAT.md sections are out of order")
|
|
68
|
+
return active_idx, completed_idx
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def insert_task(content: str, task: str, allow_duplicate: bool) -> tuple[str, str, str]:
|
|
72
|
+
lines = content.splitlines()
|
|
73
|
+
active_idx, completed_idx = find_section_indexes(lines)
|
|
74
|
+
title = task.splitlines()[0].strip()
|
|
75
|
+
active_lines = lines[active_idx + 1 : completed_idx]
|
|
76
|
+
if not allow_duplicate and any(line.strip() == title for line in active_lines):
|
|
77
|
+
return content, "unchanged", title
|
|
78
|
+
|
|
79
|
+
prefix = lines[:completed_idx]
|
|
80
|
+
suffix = lines[completed_idx:]
|
|
81
|
+
while prefix and prefix[-1] == "":
|
|
82
|
+
prefix.pop()
|
|
83
|
+
while suffix and suffix[0] == "":
|
|
84
|
+
suffix.pop(0)
|
|
85
|
+
|
|
86
|
+
new_lines = prefix + [""] + task.splitlines() + [""] + suffix
|
|
87
|
+
return "\n".join(new_lines) + "\n", "updated", title
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def apply_payload_to_heartbeat_file(
|
|
91
|
+
payload: dict[str, object],
|
|
92
|
+
heartbeat_file: str | Path,
|
|
93
|
+
allow_duplicate: bool = False,
|
|
94
|
+
) -> dict[str, object]:
|
|
95
|
+
heartbeat_path = Path(heartbeat_file)
|
|
96
|
+
bundle_kind, task = extract_heartbeat_task(payload)
|
|
97
|
+
content = ensure_template(heartbeat_path)
|
|
98
|
+
updated_content, status, title = insert_task(content, task, allow_duplicate)
|
|
99
|
+
heartbeat_path.write_text(updated_content, encoding="utf-8")
|
|
100
|
+
return {
|
|
101
|
+
"status": status,
|
|
102
|
+
"bundle_kind": bundle_kind,
|
|
103
|
+
"heartbeat_file": str(heartbeat_path),
|
|
104
|
+
"task_title": title,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def main() -> int:
|
|
109
|
+
args = build_parser().parse_args()
|
|
110
|
+
payload = load_payload(args.bundle_file)
|
|
111
|
+
result = apply_payload_to_heartbeat_file(payload, args.heartbeat_file, args.allow_duplicate)
|
|
112
|
+
json.dump(result, sys.stdout, ensure_ascii=False, indent=2)
|
|
113
|
+
sys.stdout.write("\n")
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render a stable Web3 operator bundle from normalized risk/simulation states."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
RISK_GATE_SCRIPTS_DIR = (
|
|
13
|
+
Path(__file__).resolve().parents[2] / "web3-risk-gate" / "scripts"
|
|
14
|
+
)
|
|
15
|
+
if str(RISK_GATE_SCRIPTS_DIR) not in sys.path:
|
|
16
|
+
sys.path.insert(0, str(RISK_GATE_SCRIPTS_DIR))
|
|
17
|
+
|
|
18
|
+
from merge_risk_gate_blocks import load_payload, merge_payloads
|
|
19
|
+
|
|
20
|
+
SIMULATION_SCRIPTS_DIR = (
|
|
21
|
+
Path(__file__).resolve().parents[2] / "web3-transaction-simulator" / "scripts"
|
|
22
|
+
)
|
|
23
|
+
if str(SIMULATION_SCRIPTS_DIR) not in sys.path:
|
|
24
|
+
sys.path.insert(0, str(SIMULATION_SCRIPTS_DIR))
|
|
25
|
+
|
|
26
|
+
from merge_simulation_blocks import load_payload as load_simulation_payload
|
|
27
|
+
from merge_simulation_blocks import merge_payloads as merge_simulation_payloads
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
31
|
+
parser = argparse.ArgumentParser(description="Render a Web3 operator execution bundle.")
|
|
32
|
+
parser.add_argument("--intent", required=True)
|
|
33
|
+
parser.add_argument("--chain", required=True)
|
|
34
|
+
parser.add_argument("--risk-decision", choices=("ALLOW", "WARN", "BLOCK"))
|
|
35
|
+
parser.add_argument("--risk-coverage", choices=("complete", "partial", "missing"))
|
|
36
|
+
parser.add_argument("--risk-input-file", action="append", default=[])
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--simulation-status",
|
|
39
|
+
choices=("PASS", "WARN", "FAIL", "NO_SIMULATION"),
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--simulation-readiness",
|
|
43
|
+
choices=("ready", "needs-confirmation", "blocked"),
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument("--simulation-venue")
|
|
46
|
+
parser.add_argument("--simulation-input-file", action="append", default=[])
|
|
47
|
+
parser.add_argument("--reason", action="append", default=[])
|
|
48
|
+
parser.add_argument("--follow-up-skill", action="append", default=[])
|
|
49
|
+
return parser
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def resolve_risk_gate(
|
|
53
|
+
risk_decision: str | None,
|
|
54
|
+
risk_coverage: str | None,
|
|
55
|
+
risk_input_files: list[str],
|
|
56
|
+
) -> tuple[str, str, dict[str, object] | None]:
|
|
57
|
+
if risk_input_files:
|
|
58
|
+
merged_gate = merge_payloads([load_payload(path) for path in risk_input_files])["risk_gate"]
|
|
59
|
+
merged_decision = str(merged_gate["decision"])
|
|
60
|
+
merged_coverage = str(merged_gate["coverage"])
|
|
61
|
+
if risk_decision and risk_decision != merged_decision:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"explicit risk decision {risk_decision} conflicts with merged risk_gate {merged_decision}"
|
|
64
|
+
)
|
|
65
|
+
if risk_coverage and risk_coverage != merged_coverage:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"explicit risk coverage {risk_coverage} conflicts with merged risk_gate {merged_coverage}"
|
|
68
|
+
)
|
|
69
|
+
return merged_decision, merged_coverage, merged_gate
|
|
70
|
+
|
|
71
|
+
if not risk_decision:
|
|
72
|
+
raise ValueError("risk decision is required unless --risk-input-file is provided")
|
|
73
|
+
return risk_decision, risk_coverage or "complete", None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def resolve_simulation(
|
|
77
|
+
simulation_status: str | None,
|
|
78
|
+
simulation_readiness: str | None,
|
|
79
|
+
simulation_venue: str | None,
|
|
80
|
+
simulation_input_files: list[str],
|
|
81
|
+
) -> tuple[str, str, str, dict[str, object] | None]:
|
|
82
|
+
if simulation_input_files:
|
|
83
|
+
merged_simulation = merge_simulation_payloads(
|
|
84
|
+
[load_simulation_payload(path) for path in simulation_input_files]
|
|
85
|
+
)["simulation"]
|
|
86
|
+
merged_status = str(merged_simulation["status"])
|
|
87
|
+
merged_readiness = str(merged_simulation["readiness"])
|
|
88
|
+
merged_venue = str(merged_simulation.get("venue", ""))
|
|
89
|
+
if simulation_status and simulation_status != merged_status:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"explicit simulation status {simulation_status} conflicts with merged simulation {merged_status}"
|
|
92
|
+
)
|
|
93
|
+
if simulation_readiness and simulation_readiness != merged_readiness:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
"explicit simulation readiness "
|
|
96
|
+
f"{simulation_readiness} conflicts with merged simulation {merged_readiness}"
|
|
97
|
+
)
|
|
98
|
+
if simulation_venue and merged_venue and simulation_venue != merged_venue:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"explicit simulation venue {simulation_venue} conflicts with merged simulation {merged_venue}"
|
|
101
|
+
)
|
|
102
|
+
return merged_status, merged_readiness, simulation_venue or merged_venue, merged_simulation
|
|
103
|
+
|
|
104
|
+
if not simulation_status:
|
|
105
|
+
raise ValueError("simulation status is required unless --simulation-input-file is provided")
|
|
106
|
+
return (
|
|
107
|
+
simulation_status,
|
|
108
|
+
simulation_readiness or "needs-confirmation",
|
|
109
|
+
simulation_venue or "",
|
|
110
|
+
None,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def validate_merged_context(
|
|
115
|
+
intent: str,
|
|
116
|
+
chain: str,
|
|
117
|
+
merged_gate: dict[str, object] | None,
|
|
118
|
+
merged_simulation: dict[str, object] | None,
|
|
119
|
+
) -> None:
|
|
120
|
+
if merged_gate is not None:
|
|
121
|
+
gate_action = str(merged_gate.get("action", ""))
|
|
122
|
+
gate_chain = str(merged_gate.get("chain", ""))
|
|
123
|
+
if gate_action and gate_action != intent:
|
|
124
|
+
raise ValueError(f"risk_gate action {gate_action} conflicts with intent {intent}")
|
|
125
|
+
if gate_chain and gate_chain != chain:
|
|
126
|
+
raise ValueError(f"risk_gate chain {gate_chain} conflicts with chain {chain}")
|
|
127
|
+
|
|
128
|
+
if merged_simulation is not None:
|
|
129
|
+
simulation_action = str(merged_simulation.get("action", ""))
|
|
130
|
+
simulation_chain = str(merged_simulation.get("chain", ""))
|
|
131
|
+
if simulation_action and simulation_action != intent:
|
|
132
|
+
raise ValueError(f"simulation action {simulation_action} conflicts with intent {intent}")
|
|
133
|
+
if simulation_chain and simulation_chain != chain:
|
|
134
|
+
raise ValueError(f"simulation chain {simulation_chain} conflicts with chain {chain}")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def choose_state(
|
|
138
|
+
risk_decision: str,
|
|
139
|
+
risk_coverage: str,
|
|
140
|
+
simulation_status: str,
|
|
141
|
+
simulation_readiness: str,
|
|
142
|
+
) -> tuple[str, str, bool]:
|
|
143
|
+
if risk_decision == "BLOCK" or simulation_status == "FAIL":
|
|
144
|
+
return "blocked", "stop", False
|
|
145
|
+
if simulation_status == "NO_SIMULATION" or risk_coverage != "complete":
|
|
146
|
+
return "investigate-more", "investigate", False
|
|
147
|
+
if (
|
|
148
|
+
risk_decision == "WARN"
|
|
149
|
+
or simulation_status == "WARN"
|
|
150
|
+
or simulation_readiness == "needs-confirmation"
|
|
151
|
+
):
|
|
152
|
+
return "needs-confirmation", "confirm_then_route", True
|
|
153
|
+
return "ready-to-route", "route", False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def choose_route(
|
|
157
|
+
state: str,
|
|
158
|
+
intent: str,
|
|
159
|
+
venue: str,
|
|
160
|
+
follow_up_skills: list[str],
|
|
161
|
+
) -> str:
|
|
162
|
+
if state == "blocked":
|
|
163
|
+
return "none"
|
|
164
|
+
if state == "investigate-more":
|
|
165
|
+
return follow_up_skills[0] if follow_up_skills else "web3-trace-and-state-analysis"
|
|
166
|
+
if venue == "hyperliquid":
|
|
167
|
+
return "hyperliquid"
|
|
168
|
+
if intent == "liquidity":
|
|
169
|
+
return "liquidity-planner"
|
|
170
|
+
if venue == "token-swap" or intent == "swap":
|
|
171
|
+
return "token-swap"
|
|
172
|
+
return "web3-native-operator"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def build_execution_bundle(
|
|
176
|
+
intent: str,
|
|
177
|
+
chain: str,
|
|
178
|
+
risk_decision: str,
|
|
179
|
+
risk_coverage: str,
|
|
180
|
+
simulation_status: str,
|
|
181
|
+
simulation_readiness: str,
|
|
182
|
+
simulation_venue: str,
|
|
183
|
+
reasons: list[str],
|
|
184
|
+
follow_up_skills: list[str],
|
|
185
|
+
) -> dict[str, object]:
|
|
186
|
+
state, execution_mode, requires_confirmation = choose_state(
|
|
187
|
+
risk_decision,
|
|
188
|
+
risk_coverage,
|
|
189
|
+
simulation_status,
|
|
190
|
+
simulation_readiness,
|
|
191
|
+
)
|
|
192
|
+
return {
|
|
193
|
+
"version": 1,
|
|
194
|
+
"workflow_kind": "execution",
|
|
195
|
+
"intent": intent,
|
|
196
|
+
"chain": chain,
|
|
197
|
+
"state": state,
|
|
198
|
+
"next_adapter": choose_route(
|
|
199
|
+
state,
|
|
200
|
+
intent,
|
|
201
|
+
simulation_venue,
|
|
202
|
+
follow_up_skills,
|
|
203
|
+
),
|
|
204
|
+
"execution_mode": execution_mode,
|
|
205
|
+
"requires_user_confirmation": requires_confirmation,
|
|
206
|
+
"inputs": {
|
|
207
|
+
"risk_decision": risk_decision,
|
|
208
|
+
"risk_coverage": risk_coverage,
|
|
209
|
+
"simulation_status": simulation_status,
|
|
210
|
+
"simulation_readiness": simulation_readiness,
|
|
211
|
+
"simulation_venue": simulation_venue,
|
|
212
|
+
},
|
|
213
|
+
"follow_up_skills": follow_up_skills,
|
|
214
|
+
"reasons": reasons,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def main() -> int:
|
|
219
|
+
parser = build_parser()
|
|
220
|
+
args = parser.parse_args()
|
|
221
|
+
try:
|
|
222
|
+
risk_decision, risk_coverage, merged_gate = resolve_risk_gate(
|
|
223
|
+
args.risk_decision,
|
|
224
|
+
args.risk_coverage,
|
|
225
|
+
args.risk_input_file,
|
|
226
|
+
)
|
|
227
|
+
simulation_status, simulation_readiness, simulation_venue, merged_simulation = resolve_simulation(
|
|
228
|
+
args.simulation_status,
|
|
229
|
+
args.simulation_readiness,
|
|
230
|
+
args.simulation_venue,
|
|
231
|
+
args.simulation_input_file,
|
|
232
|
+
)
|
|
233
|
+
validate_merged_context(args.intent, args.chain, merged_gate, merged_simulation)
|
|
234
|
+
except ValueError as exc:
|
|
235
|
+
parser.error(str(exc))
|
|
236
|
+
payload: dict[str, object] = {
|
|
237
|
+
"operator_bundle": build_execution_bundle(
|
|
238
|
+
intent=args.intent,
|
|
239
|
+
chain=args.chain,
|
|
240
|
+
risk_decision=risk_decision,
|
|
241
|
+
risk_coverage=risk_coverage,
|
|
242
|
+
simulation_status=simulation_status,
|
|
243
|
+
simulation_readiness=simulation_readiness,
|
|
244
|
+
simulation_venue=simulation_venue,
|
|
245
|
+
reasons=args.reason,
|
|
246
|
+
follow_up_skills=args.follow_up_skill,
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
if merged_gate is not None:
|
|
250
|
+
payload["risk_gate"] = merged_gate
|
|
251
|
+
if merged_simulation is not None:
|
|
252
|
+
payload["simulation"] = merged_simulation
|
|
253
|
+
json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
|
|
254
|
+
sys.stdout.write("\n")
|
|
255
|
+
return 0
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
if __name__ == "__main__":
|
|
259
|
+
raise SystemExit(main())
|