polyrob 0.5.0__py3-none-any.whl

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 (745) hide show
  1. agents/README.md +453 -0
  2. agents/__init__.py +164 -0
  3. agents/base_agent.py +743 -0
  4. agents/personality/character.py +119 -0
  5. agents/personality/character_manager.py +309 -0
  6. agents/personality/characters/rob.character.json +56 -0
  7. agents/personality/characters/trump.character.json +63 -0
  8. agents/personality/persona_render.py +78 -0
  9. agents/personality/persona_resolver.py +115 -0
  10. agents/prompt/__init__.py +58 -0
  11. agents/prompt/base_prompt.py +92 -0
  12. agents/prompt/system.py +417 -0
  13. agents/task/README_PATH_MANAGEMENT.md +141 -0
  14. agents/task/__init__.py +162 -0
  15. agents/task/agent/__init__.py +70 -0
  16. agents/task/agent/agent_state.py +408 -0
  17. agents/task/agent/async_delegation.py +274 -0
  18. agents/task/agent/autonomy_state.py +275 -0
  19. agents/task/agent/conversation.py +75 -0
  20. agents/task/agent/core/__init__.py +6 -0
  21. agents/task/agent/core/aux_metering.py +61 -0
  22. agents/task/agent/core/background_review.py +157 -0
  23. agents/task/agent/core/construction.py +1179 -0
  24. agents/task/agent/core/conversational_exit.py +49 -0
  25. agents/task/agent/core/correspondent_gate.py +227 -0
  26. agents/task/agent/core/curator.py +246 -0
  27. agents/task/agent/core/episodic_digest.py +103 -0
  28. agents/task/agent/core/error_recovery.py +480 -0
  29. agents/task/agent/core/history_io.py +257 -0
  30. agents/task/agent/core/llm_provisioning.py +287 -0
  31. agents/task/agent/core/llm_runner.py +547 -0
  32. agents/task/agent/core/logging_io.py +276 -0
  33. agents/task/agent/core/loop_detection.py +262 -0
  34. agents/task/agent/core/memory_prefetch.py +325 -0
  35. agents/task/agent/core/memory_writer.py +667 -0
  36. agents/task/agent/core/model_introspection.py +82 -0
  37. agents/task/agent/core/model_swap.py +177 -0
  38. agents/task/agent/core/next_action_internal.py +1331 -0
  39. agents/task/agent/core/output_validation.py +303 -0
  40. agents/task/agent/core/project_context.py +272 -0
  41. agents/task/agent/core/resources.py +93 -0
  42. agents/task/agent/core/result_processing.py +389 -0
  43. agents/task/agent/core/run_loop.py +661 -0
  44. agents/task/agent/core/safety_lifecycle.py +228 -0
  45. agents/task/agent/core/secret_guard.py +293 -0
  46. agents/task/agent/core/self_wake.py +280 -0
  47. agents/task/agent/core/session_metadata.py +104 -0
  48. agents/task/agent/core/step.py +781 -0
  49. agents/task/agent/core/step_execution.py +398 -0
  50. agents/task/agent/core/step_telemetry.py +222 -0
  51. agents/task/agent/core/test_aux_metering.py +50 -0
  52. agents/task/agent/core/test_billing_failover.py +68 -0
  53. agents/task/agent/core/test_fatal_error_classifier.py +38 -0
  54. agents/task/agent/core/test_judge_validation.py +275 -0
  55. agents/task/agent/core/test_model_swap.py +190 -0
  56. agents/task/agent/core/test_reflection_offload.py +54 -0
  57. agents/task/agent/core/turn_input.py +8 -0
  58. agents/task/agent/core/untrusted_wrap.py +103 -0
  59. agents/task/agent/core/user_ingress.py +228 -0
  60. agents/task/agent/hitl_manager.py +396 -0
  61. agents/task/agent/log_sanitize.py +36 -0
  62. agents/task/agent/message_manager/config.py +97 -0
  63. agents/task/agent/message_manager/service.py +758 -0
  64. agents/task/agent/message_manager/tests.py +305 -0
  65. agents/task/agent/message_manager/tool_call_builder.py +539 -0
  66. agents/task/agent/message_manager/tool_message_repair.py +364 -0
  67. agents/task/agent/message_manager/views.py +143 -0
  68. agents/task/agent/messages/__init__.py +8 -0
  69. agents/task/agent/messages/builders.py +309 -0
  70. agents/task/agent/messages/compactor.py +611 -0
  71. agents/task/agent/messages/context_references.py +271 -0
  72. agents/task/agent/messages/filters.py +333 -0
  73. agents/task/agent/messages/guidance.py +328 -0
  74. agents/task/agent/messages/persistence.py +448 -0
  75. agents/task/agent/messages/retrieval.py +506 -0
  76. agents/task/agent/messages/sqlite_persistence.py +85 -0
  77. agents/task/agent/messages/test_compaction_upgrade.py +349 -0
  78. agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
  79. agents/task/agent/messages/test_sqlite_persistence.py +54 -0
  80. agents/task/agent/messages/token_counter.py +532 -0
  81. agents/task/agent/orchestrator.py +776 -0
  82. agents/task/agent/profile_manager.py +105 -0
  83. agents/task/agent/profile_registry.py +173 -0
  84. agents/task/agent/prompts.py +1090 -0
  85. agents/task/agent/scenario_registry.py +226 -0
  86. agents/task/agent/service.py +509 -0
  87. agents/task/agent/session.py +984 -0
  88. agents/task/agent/skill_discovery.py +110 -0
  89. agents/task/agent/skill_frontmatter.py +94 -0
  90. agents/task/agent/skill_manager.py +1201 -0
  91. agents/task/agent/skill_store.py +627 -0
  92. agents/task/agent/skill_validation.py +56 -0
  93. agents/task/agent/skill_writer.py +492 -0
  94. agents/task/agent/sub_agent_manager.py +1137 -0
  95. agents/task/agent/test_session_disk_recovery.py +34 -0
  96. agents/task/agent/tests.py +513 -0
  97. agents/task/agent/tool_call_tracker.py +511 -0
  98. agents/task/agent/views.py +469 -0
  99. agents/task/config.py +432 -0
  100. agents/task/constants.py +1384 -0
  101. agents/task/flag_defaults.py +63 -0
  102. agents/task/goals/__init__.py +1 -0
  103. agents/task/goals/autonomy_marker.py +28 -0
  104. agents/task/goals/board.py +690 -0
  105. agents/task/goals/completion_judge.py +247 -0
  106. agents/task/goals/context.py +62 -0
  107. agents/task/goals/dispatcher.py +848 -0
  108. agents/task/goals/escalation.py +102 -0
  109. agents/task/goals/planner.py +114 -0
  110. agents/task/logging_config.py +155 -0
  111. agents/task/path.py +991 -0
  112. agents/task/robust_parse_config.py +400 -0
  113. agents/task/runtime/__init__.py +0 -0
  114. agents/task/runtime/run_as_session.py +130 -0
  115. agents/task/runtime_safety.py +308 -0
  116. agents/task/session/__init__.py +5 -0
  117. agents/task/session/browser_pool.py +130 -0
  118. agents/task/session/cleanup.py +612 -0
  119. agents/task/session/execution.py +397 -0
  120. agents/task/session/feed.py +200 -0
  121. agents/task/session/hitl_ingress.py +201 -0
  122. agents/task/session/hooks.py +83 -0
  123. agents/task/session/multi_agent.py +243 -0
  124. agents/task/session/workspace.py +91 -0
  125. agents/task/session_registry.py +91 -0
  126. agents/task/session_route.py +35 -0
  127. agents/task/sqlite_session_registry.py +261 -0
  128. agents/task/surface_config.py +185 -0
  129. agents/task/telemetry/__init__.py +58 -0
  130. agents/task/telemetry/event_log.py +180 -0
  131. agents/task/telemetry/formatters.py +642 -0
  132. agents/task/telemetry/manager.py +571 -0
  133. agents/task/telemetry/memory_events.py +66 -0
  134. agents/task/telemetry/self_events.py +53 -0
  135. agents/task/telemetry/sequence.py +160 -0
  136. agents/task/telemetry/service.py +1265 -0
  137. agents/task/telemetry/views.py +882 -0
  138. agents/task/templates.py +98 -0
  139. agents/task/test_aux_chain.py +151 -0
  140. agents/task/test_aux_router.py +41 -0
  141. agents/task/tests/test_parse_robustness.py +170 -0
  142. agents/task/tool_defaults.py +110 -0
  143. agents/task/utils.py +762 -0
  144. agents/task/utils_json.py +768 -0
  145. agents/task/utils_webview.py +86 -0
  146. agents/task/workspace_context.py +229 -0
  147. agents/task_agent_lite.py +2398 -0
  148. api/README.md +683 -0
  149. api/TASK_API_DOCS.md +151 -0
  150. api/__init__.py +0 -0
  151. api/a2a/__init__.py +37 -0
  152. api/a2a/agent_card.py +337 -0
  153. api/a2a/client.py +657 -0
  154. api/a2a/endpoints.py +375 -0
  155. api/a2a/models.py +268 -0
  156. api/a2a/streaming.py +419 -0
  157. api/a2a/task_handler.py +929 -0
  158. api/admin_endpoints.py +1584 -0
  159. api/app.py +923 -0
  160. api/auth_constants.py +28 -0
  161. api/auth_endpoints.py +402 -0
  162. api/auth_state.py +36 -0
  163. api/chat_via_task.py +42 -0
  164. api/conversation_manager.py +231 -0
  165. api/dependencies.py +186 -0
  166. api/eip8004_endpoints.py +452 -0
  167. api/hyperliquid_models.py +174 -0
  168. api/hyperliquid_routes.py +450 -0
  169. api/interfaces.py +207 -0
  170. api/jwt_middleware.py +83 -0
  171. api/kb/__init__.py +1 -0
  172. api/kb/endpoints.py +314 -0
  173. api/mcp_models.py +279 -0
  174. api/mcp_routes.py +438 -0
  175. api/middleware.py +526 -0
  176. api/models.py +176 -0
  177. api/openai_compat/__init__.py +0 -0
  178. api/openai_compat/model_map.py +25 -0
  179. api/openai_compat/models.py +59 -0
  180. api/openai_compat/router.py +140 -0
  181. api/payment_endpoints.py +444 -0
  182. api/payment_verification.py +138 -0
  183. api/polymarket_models.py +155 -0
  184. api/polymarket_routes.py +501 -0
  185. api/pricing_endpoints.py +89 -0
  186. api/session_routing.py +54 -0
  187. api/skill_endpoints.py +453 -0
  188. api/task_http_api.py +1856 -0
  189. api/webhooks.py +52 -0
  190. api/x402_endpoints.py +280 -0
  191. avatar/README.md +44 -0
  192. avatar/__init__.py +1 -0
  193. avatar/config/rob.json +7 -0
  194. avatar/mindprint.js +395 -0
  195. avatar/renders/rob.meta.json +20 -0
  196. avatar/renders/rob.png +0 -0
  197. avatar/studio.html +373 -0
  198. avatar/webview/avatar-live.js +44 -0
  199. cli/README.md +89 -0
  200. cli/__init__.py +0 -0
  201. cli/cli_surface.py +67 -0
  202. cli/commands/__init__.py +0 -0
  203. cli/commands/_bootstrap.py +49 -0
  204. cli/commands/_errors.py +53 -0
  205. cli/commands/chat.py +973 -0
  206. cli/commands/config.py +100 -0
  207. cli/commands/dashboard.py +103 -0
  208. cli/commands/doctor.py +222 -0
  209. cli/commands/email.py +174 -0
  210. cli/commands/gateway.py +346 -0
  211. cli/commands/goals.py +509 -0
  212. cli/commands/init.py +276 -0
  213. cli/commands/journey.py +30 -0
  214. cli/commands/kb.py +280 -0
  215. cli/commands/model.py +107 -0
  216. cli/commands/owner.py +431 -0
  217. cli/commands/pfp.py +232 -0
  218. cli/commands/run.py +414 -0
  219. cli/commands/serve.py +41 -0
  220. cli/commands/session.py +684 -0
  221. cli/commands/skill_install.py +786 -0
  222. cli/commands/skills.py +128 -0
  223. cli/commands/subagents.py +100 -0
  224. cli/commands/surface.py +81 -0
  225. cli/commands/telegram.py +185 -0
  226. cli/commands/todos.py +209 -0
  227. cli/commands/tools.py +102 -0
  228. cli/commands/update.py +316 -0
  229. cli/commands/whatsapp.py +175 -0
  230. cli/config_store.py +276 -0
  231. cli/gitignore.py +43 -0
  232. cli/inventory.py +46 -0
  233. cli/keys.py +101 -0
  234. cli/persona.py +17 -0
  235. cli/polyrob.py +137 -0
  236. cli/ui/__init__.py +64 -0
  237. cli/ui/activity.py +141 -0
  238. cli/ui/app.py +538 -0
  239. cli/ui/banner.py +233 -0
  240. cli/ui/blocks.py +534 -0
  241. cli/ui/bootstrap_notice.py +47 -0
  242. cli/ui/commands/__init__.py +41 -0
  243. cli/ui/commands/h_cron.py +64 -0
  244. cli/ui/commands/h_journey.py +164 -0
  245. cli/ui/commands/h_kb.py +121 -0
  246. cli/ui/commands/h_learn.py +100 -0
  247. cli/ui/commands/h_mcp.py +215 -0
  248. cli/ui/commands/h_self.py +113 -0
  249. cli/ui/commands/h_skills.py +314 -0
  250. cli/ui/commands/handlers.py +1672 -0
  251. cli/ui/commands/registry.py +426 -0
  252. cli/ui/dialog.py +425 -0
  253. cli/ui/event_registry.py +110 -0
  254. cli/ui/event_specs.py +45 -0
  255. cli/ui/events.py +433 -0
  256. cli/ui/identity.py +15 -0
  257. cli/ui/lifecycle.py +159 -0
  258. cli/ui/live_hooks.py +43 -0
  259. cli/ui/model_selector.py +532 -0
  260. cli/ui/persistent_loop.py +181 -0
  261. cli/ui/pick.py +113 -0
  262. cli/ui/plain_renderer.py +351 -0
  263. cli/ui/renderer.py +384 -0
  264. cli/ui/rich_renderer.py +533 -0
  265. cli/ui/secrets.py +115 -0
  266. cli/ui/state.py +376 -0
  267. cli/ui/statusbar.py +231 -0
  268. cli/ui/streaming.py +254 -0
  269. cli/ui/terminal_render.py +105 -0
  270. cli/ui/theme.py +132 -0
  271. cli/update/__init__.py +6 -0
  272. cli/update/context.py +121 -0
  273. cli/update/detect.py +176 -0
  274. cli/update/engine.py +95 -0
  275. cli/update/migrate_guarded.py +55 -0
  276. cli/update/process_guard.py +215 -0
  277. cli/update/runners.py +78 -0
  278. cli/update/snapshot.py +270 -0
  279. cli/update/versions.py +205 -0
  280. core/README.md +377 -0
  281. core/__init__.py +174 -0
  282. core/assets.py +87 -0
  283. core/async_bridge.py +76 -0
  284. core/autonomy_runtime.py +259 -0
  285. core/base_component.py +302 -0
  286. core/bootstrap.py +681 -0
  287. core/bot.py +310 -0
  288. core/config.py +833 -0
  289. core/constants.py +126 -0
  290. core/container.py +462 -0
  291. core/db_manifest.py +101 -0
  292. core/embedding.py +57 -0
  293. core/env.py +39 -0
  294. core/exceptions.py +287 -0
  295. core/flags.py +144 -0
  296. core/flags_catalog.py +345 -0
  297. core/home_migration.py +62 -0
  298. core/identity.py +118 -0
  299. core/initialization.py +1004 -0
  300. core/instance.py +537 -0
  301. core/interactive_gate.py +96 -0
  302. core/logging.py +436 -0
  303. core/owner_doc_writer.py +176 -0
  304. core/pairing.py +193 -0
  305. core/path_safety.py +20 -0
  306. core/paths.py +27 -0
  307. core/payment_config.py +21 -0
  308. core/permissions.py +322 -0
  309. core/runtime_config.py +96 -0
  310. core/runtime_paths.py +135 -0
  311. core/seams.py +35 -0
  312. core/secret_scan.py +25 -0
  313. core/secret_scrub.py +71 -0
  314. core/secrets.py +35 -0
  315. core/security_logging_filter.py +148 -0
  316. core/self_context_writer.py +333 -0
  317. core/self_evolution.py +294 -0
  318. core/session_context.py +47 -0
  319. core/sqlite_util.py +59 -0
  320. core/surfaces/__init__.py +16 -0
  321. core/surfaces/access.py +118 -0
  322. core/surfaces/binding.py +88 -0
  323. core/surfaces/bootstrap.py +78 -0
  324. core/surfaces/circuit.py +168 -0
  325. core/surfaces/continuity.py +77 -0
  326. core/surfaces/correspondents.py +228 -0
  327. core/surfaces/dispatcher.py +216 -0
  328. core/surfaces/envelopes.py +75 -0
  329. core/surfaces/gc.py +30 -0
  330. core/surfaces/idempotency.py +57 -0
  331. core/surfaces/inbound_webhook.py +124 -0
  332. core/surfaces/media.py +32 -0
  333. core/surfaces/message_router.py +91 -0
  334. core/surfaces/outbound_allowlist.py +80 -0
  335. core/surfaces/outbound_dispatcher.py +110 -0
  336. core/surfaces/outbound_mirror.py +37 -0
  337. core/surfaces/outbound_queue.py +107 -0
  338. core/surfaces/outbound_target.py +12 -0
  339. core/surfaces/owner_admin.py +29 -0
  340. core/surfaces/proactive.py +78 -0
  341. core/surfaces/progress.py +108 -0
  342. core/surfaces/rate_bucket.py +21 -0
  343. core/surfaces/registry.py +51 -0
  344. core/surfaces/rendering.py +53 -0
  345. core/surfaces/send_policy.py +14 -0
  346. core/surfaces/serialize.py +24 -0
  347. core/surfaces/session_chat_registry.py +112 -0
  348. core/surfaces/session_policy.py +60 -0
  349. core/surfaces/surface.py +288 -0
  350. core/surfaces/transcription.py +88 -0
  351. core/surfaces/voice_echo.py +26 -0
  352. core/surfaces/voice_guard.py +23 -0
  353. core/tickers.py +174 -0
  354. core/tool_catalog.py +192 -0
  355. core/version.py +75 -0
  356. core/wallet/__init__.py +5 -0
  357. core/wallet/agent_wallet.py +59 -0
  358. core/wallet/audit_sink.py +61 -0
  359. core/wallet/config.py +70 -0
  360. core/wallet/factory.py +87 -0
  361. core/wallet/policy.py +104 -0
  362. core/wallet/signer.py +51 -0
  363. cron/README.md +65 -0
  364. cron/__init__.py +0 -0
  365. cron/delivery.py +277 -0
  366. cron/digest.py +124 -0
  367. cron/jobs.py +207 -0
  368. cron/runner.py +297 -0
  369. cron/schedule.py +194 -0
  370. cron/scheduler.py +201 -0
  371. cron/service.py +57 -0
  372. cron/wake_gate.py +223 -0
  373. data/__init__.py +1 -0
  374. data/prompts/autov2_prompts.json +87 -0
  375. data/prompts/skills/browser-automation/SKILL.md +43 -0
  376. data/prompts/skills/coding-workflow/SKILL.md +36 -0
  377. data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
  378. data/prompts/skills/document-writing/SKILL.md +33 -0
  379. data/prompts/skills/email-comms/SKILL.md +33 -0
  380. data/prompts/skills/file-data-ops/SKILL.md +35 -0
  381. data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
  382. data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
  383. data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
  384. data/prompts/skills/lead-research/SKILL.md +86 -0
  385. data/prompts/skills/market-research-brief/SKILL.md +104 -0
  386. data/prompts/skills/person-analyzer/SKILL.md +124 -0
  387. data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
  388. data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
  389. data/prompts/skills/polymarket-trading/SKILL.md +55 -0
  390. data/prompts/skills/presentation-creator/SKILL.md +36 -0
  391. data/prompts/skills/project-analyzer/SKILL.md +169 -0
  392. data/prompts/skills/rules.json +268 -0
  393. data/prompts/skills/secret-handling/SKILL.md +20 -0
  394. data/prompts/skills/skill-authoring/SKILL.md +31 -0
  395. data/prompts/skills/skill-security-review/SKILL.md +27 -0
  396. data/prompts/skills/social-discovery/SKILL.md +57 -0
  397. data/prompts/skills/task-planning/SKILL.md +33 -0
  398. data/prompts/skills/web-research/SKILL.md +39 -0
  399. data/prompts/skills/web-scraping/SKILL.md +73 -0
  400. data/prompts/skills/x-engagement/SKILL.md +69 -0
  401. data/prompts/system_prompts.json +4 -0
  402. data/subscription_channels.json +5 -0
  403. modules/README.md +503 -0
  404. modules/__init__.py +124 -0
  405. modules/auth/__init__.py +13 -0
  406. modules/auth/api_key_manager.py +157 -0
  407. modules/auth/identity_mapper.py +380 -0
  408. modules/auth/siwe_auth.py +273 -0
  409. modules/auth/tier_manager.py +173 -0
  410. modules/base_module.py +190 -0
  411. modules/credits/__init__.py +29 -0
  412. modules/credits/balance_manager.py +237 -0
  413. modules/credits/cost_utils.py +129 -0
  414. modules/credits/pricing.py +139 -0
  415. modules/credits/unified_ledger.py +148 -0
  416. modules/credits/usage_meter.py +310 -0
  417. modules/credits/usage_tracker.py +674 -0
  418. modules/database/__init__.py +33 -0
  419. modules/database/audit_log.py +322 -0
  420. modules/database/auth_tables.py +276 -0
  421. modules/database/connection.py +644 -0
  422. modules/database/connection_pool.py +305 -0
  423. modules/database/conversation_contexts.py +446 -0
  424. modules/database/database_manager.py +237 -0
  425. modules/database/hyperliquid.py +375 -0
  426. modules/database/polymarket.py +574 -0
  427. modules/database/user_mcp_servers.py +728 -0
  428. modules/database/user_profiles.py +562 -0
  429. modules/database/utils.py +107 -0
  430. modules/database/x402_tables.py +92 -0
  431. modules/eip8004/README.md +1069 -0
  432. modules/eip8004/__init__.py +65 -0
  433. modules/eip8004/contracts.py +498 -0
  434. modules/eip8004/models.py +271 -0
  435. modules/eip8004/registration.py +148 -0
  436. modules/eip8004/reputation.py +367 -0
  437. modules/eip8004/validation.py +277 -0
  438. modules/llm/__init__.py +97 -0
  439. modules/llm/adapters.py +963 -0
  440. modules/llm/anthropic_client.py +831 -0
  441. modules/llm/available_models.py +106 -0
  442. modules/llm/brain_scrubber.py +137 -0
  443. modules/llm/cache_hints.py +162 -0
  444. modules/llm/deepseek_client.py +562 -0
  445. modules/llm/gemini_client.py +1367 -0
  446. modules/llm/llm_client.py +1139 -0
  447. modules/llm/llm_client_registry.py +188 -0
  448. modules/llm/llm_factory.py +237 -0
  449. modules/llm/llm_manager.py +1036 -0
  450. modules/llm/messages.py +374 -0
  451. modules/llm/model_registry.py +1935 -0
  452. modules/llm/nvidia_client.py +125 -0
  453. modules/llm/openai_client.py +800 -0
  454. modules/llm/openrouter_client.py +969 -0
  455. modules/llm/profiles.py +178 -0
  456. modules/llm/test_anthropic_cache_capture.py +35 -0
  457. modules/llm/test_cached_pricing_derivation.py +24 -0
  458. modules/llm/think_scrubber.py +444 -0
  459. modules/llm/token_counter.py +268 -0
  460. modules/memory/__init__.py +35 -0
  461. modules/memory/backend_factory.py +83 -0
  462. modules/memory/cache_manager.py +186 -0
  463. modules/memory/episodic.py +108 -0
  464. modules/memory/local_vector_memory_provider.py +552 -0
  465. modules/memory/memory_manager.py +225 -0
  466. modules/memory/models.py +312 -0
  467. modules/memory/provider.py +210 -0
  468. modules/memory/registry.py +205 -0
  469. modules/memory/sqlite_memory_provider.py +767 -0
  470. modules/memory/task/__init__.py +52 -0
  471. modules/memory/task/compaction_manager.py +157 -0
  472. modules/memory/task/context_retriever.py +809 -0
  473. modules/memory/task/hierarchical_memory.py +1002 -0
  474. modules/memory/task/lexical_retriever.py +71 -0
  475. modules/memory/task/null_context_manager.py +213 -0
  476. modules/memory/task/phase_manager.py +625 -0
  477. modules/memory/task/reflection_service.py +157 -0
  478. modules/memory/task/semantic_retriever.py +218 -0
  479. modules/memory/task/task_context_manager.py +1165 -0
  480. modules/memory/task/test_forgetting_engages.py +24 -0
  481. modules/memory/task/test_reflection_llm.py +39 -0
  482. modules/memory/task/test_threat_scan.py +53 -0
  483. modules/memory/task/threat_scan.py +90 -0
  484. modules/memory/test_sqlite_memory_provider.py +106 -0
  485. modules/memory/user_profile_manager.py +527 -0
  486. modules/payments/__init__.py +11 -0
  487. modules/payments/deposit_monitor.py +478 -0
  488. modules/payments/price_oracle.py +70 -0
  489. modules/payments/treasury_sweeper.py +401 -0
  490. modules/payments/wallet_generator.py +145 -0
  491. modules/pfp/__init__.py +1 -0
  492. modules/pfp/config.py +92 -0
  493. modules/pfp/mesh.py +565 -0
  494. modules/pfp/push.py +69 -0
  495. modules/pfp/renderer.py +85 -0
  496. modules/pfp/store.py +82 -0
  497. modules/skills/__init__.py +1 -0
  498. modules/skills/skill_usage.py +248 -0
  499. modules/transcription/__init__.py +24 -0
  500. modules/transcription/base.py +20 -0
  501. modules/transcription/faster_whisper_transcriber.py +55 -0
  502. modules/x402/README.md +249 -0
  503. modules/x402/__init__.py +22 -0
  504. modules/x402/invoicing.py +416 -0
  505. modules/x402/middleware.py +364 -0
  506. modules/x402/settlement_watcher.py +145 -0
  507. modules/x402/x402_integration.py +344 -0
  508. polyrob-0.5.0.dist-info/METADATA +482 -0
  509. polyrob-0.5.0.dist-info/RECORD +745 -0
  510. polyrob-0.5.0.dist-info/WHEEL +5 -0
  511. polyrob-0.5.0.dist-info/entry_points.txt +2 -0
  512. polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
  513. polyrob-0.5.0.dist-info/top_level.txt +12 -0
  514. surfaces/README.md +70 -0
  515. surfaces/__init__.py +0 -0
  516. surfaces/email/__init__.py +0 -0
  517. surfaces/email/dedup.py +44 -0
  518. surfaces/email/harness.py +187 -0
  519. surfaces/email/inbound.py +159 -0
  520. surfaces/email/seed.py +60 -0
  521. surfaces/email/surface.py +105 -0
  522. surfaces/telegram/__init__.py +0 -0
  523. surfaces/telegram/dedup.py +83 -0
  524. surfaces/telegram/harness.py +886 -0
  525. surfaces/telegram/inbound.py +145 -0
  526. surfaces/telegram/interactive_tools.py +49 -0
  527. surfaces/telegram/markdown.py +347 -0
  528. surfaces/telegram/rate_limit.py +167 -0
  529. surfaces/telegram/surface.py +187 -0
  530. surfaces/telegram/voice.py +52 -0
  531. surfaces/whatsapp/__init__.py +0 -0
  532. surfaces/whatsapp/client.py +51 -0
  533. surfaces/whatsapp/harness.py +62 -0
  534. surfaces/whatsapp/inbound.py +132 -0
  535. surfaces/whatsapp/surface.py +64 -0
  536. surfaces/whatsapp/window.py +34 -0
  537. tools/README.md +578 -0
  538. tools/__init__.py +424 -0
  539. tools/alchemy/__init__.py +5 -0
  540. tools/alchemy/alchemy_tool.py +389 -0
  541. tools/anysite/__init__.py +17 -0
  542. tools/anysite/client.py +88 -0
  543. tools/anysite/tool.py +89 -0
  544. tools/base_tool.py +468 -0
  545. tools/browser/__init__.py +22 -0
  546. tools/browser/actions.py +118 -0
  547. tools/browser/browser.py +1457 -0
  548. tools/browser/browser_manager.py +690 -0
  549. tools/browser/context.py +1748 -0
  550. tools/browser/playwright_utils.py +137 -0
  551. tools/browser/views.py +56 -0
  552. tools/code_exec/SANDBOX_SECURITY.md +144 -0
  553. tools/code_exec/__init__.py +178 -0
  554. tools/code_exec/backend.py +73 -0
  555. tools/code_exec/backends/__init__.py +1 -0
  556. tools/code_exec/backends/docker.py +779 -0
  557. tools/code_exec/backends/local_subprocess.py +154 -0
  558. tools/code_exec/env_policy.py +45 -0
  559. tools/code_exec/result.py +42 -0
  560. tools/code_exec/sandbox_guard.py +59 -0
  561. tools/code_exec/tool.py +227 -0
  562. tools/coding/__init__.py +50 -0
  563. tools/coding/edit.py +107 -0
  564. tools/coding/search.py +113 -0
  565. tools/coding/tool.py +353 -0
  566. tools/collabland/__init__.py +5 -0
  567. tools/collabland/collabland_tool.py +471 -0
  568. tools/controller/__init__.py +0 -0
  569. tools/controller/_helpers.py +165 -0
  570. tools/controller/action_registration.py +1765 -0
  571. tools/controller/approval.py +244 -0
  572. tools/controller/approval_interactive.py +90 -0
  573. tools/controller/delegation.py +195 -0
  574. tools/controller/execution.py +797 -0
  575. tools/controller/execution_context.py +141 -0
  576. tools/controller/hooks.py +135 -0
  577. tools/controller/introspection.py +344 -0
  578. tools/controller/mcp_registrar.py +190 -0
  579. tools/controller/message_send.py +31 -0
  580. tools/controller/registry/__init__.py +4 -0
  581. tools/controller/registry/schema_generators.py +395 -0
  582. tools/controller/registry/schema_sanitizer.py +406 -0
  583. tools/controller/registry/service.py +1266 -0
  584. tools/controller/registry/views.py +151 -0
  585. tools/controller/service.py +409 -0
  586. tools/controller/tool_management.py +334 -0
  587. tools/controller/types.py +28 -0
  588. tools/controller/views.py +450 -0
  589. tools/cronjob_tools.py +166 -0
  590. tools/crypto_trade_gate.py +59 -0
  591. tools/descriptors.py +452 -0
  592. tools/dom/__init__.py +0 -0
  593. tools/dom/history_tree_processor/service.py +244 -0
  594. tools/dom/history_tree_processor/view.py +99 -0
  595. tools/dom/service.py +471 -0
  596. tools/dom/views.py +346 -0
  597. tools/email_tool.py +403 -0
  598. tools/exceptions.py +169 -0
  599. tools/filesystem.py +1038 -0
  600. tools/filesystem_docproc.py +331 -0
  601. tools/filesystem_pdf.py +755 -0
  602. tools/git/__init__.py +42 -0
  603. tools/git/tool.py +258 -0
  604. tools/github/__init__.py +33 -0
  605. tools/github/client.py +94 -0
  606. tools/github/tool.py +237 -0
  607. tools/goal_tools.py +309 -0
  608. tools/hyperliquid/__init__.py +56 -0
  609. tools/hyperliquid/models.py +233 -0
  610. tools/hyperliquid/service.py +1680 -0
  611. tools/knowledge_ingest.py +950 -0
  612. tools/mcp/README.md +535 -0
  613. tools/mcp/__init__.py +52 -0
  614. tools/mcp/catalog.py +115 -0
  615. tools/mcp/config.py +337 -0
  616. tools/mcp/mcp_tool.py +1415 -0
  617. tools/mcp/param_coercion.py +289 -0
  618. tools/mcp/protocol.py +1520 -0
  619. tools/mcp/rate_limit.py +50 -0
  620. tools/mcp/security.py +424 -0
  621. tools/mcp/self_install.py +111 -0
  622. tools/mcp/server_manager.py +829 -0
  623. tools/mcp/subscriptions.py +72 -0
  624. tools/mcp/user_mcp_service.py +899 -0
  625. tools/mcp/validation_tracker.py +89 -0
  626. tools/mcp/views.py +346 -0
  627. tools/oauth/__init__.py +17 -0
  628. tools/oauth/manager.py +75 -0
  629. tools/oauth/provider.py +69 -0
  630. tools/oauth/providers/__init__.py +1 -0
  631. tools/oauth/providers/generic_oauth2.py +96 -0
  632. tools/perplexity_tool.py +280 -0
  633. tools/polymarket/__init__.py +41 -0
  634. tools/polymarket/clob_adapter.py +76 -0
  635. tools/polymarket/models.py +182 -0
  636. tools/polymarket/service.py +2082 -0
  637. tools/self_env/__init__.py +61 -0
  638. tools/self_env/tool.py +274 -0
  639. tools/shell/__init__.py +80 -0
  640. tools/shell/backend_pool.py +118 -0
  641. tools/shell/discipline.py +62 -0
  642. tools/shell/executor.py +157 -0
  643. tools/shell/loopback_allow.py +87 -0
  644. tools/shell/process_registry.py +113 -0
  645. tools/shell/process_tool.py +131 -0
  646. tools/shell/state.py +129 -0
  647. tools/shell/tool.py +151 -0
  648. tools/task_tool.py +637 -0
  649. tools/twitter_tool.py +2086 -0
  650. tools/user_directory.py +179 -0
  651. tools/web_fetch/__init__.py +3 -0
  652. tools/web_fetch/fetcher.py +144 -0
  653. tools/web_fetch/render.py +44 -0
  654. tools/web_fetch/tool.py +60 -0
  655. tools/x402/__init__.py +60 -0
  656. tools/x402/client.py +47 -0
  657. tools/x402/invoice_tool.py +143 -0
  658. tools/x402/real_client.py +250 -0
  659. tools/x402/service.py +131 -0
  660. utils/README.md +498 -0
  661. utils/__init__.py +46 -0
  662. utils/auth_utils.py +114 -0
  663. utils/bounded_collections.py +162 -0
  664. utils/circuit_breaker.py +389 -0
  665. utils/gif_utils.py +435 -0
  666. utils/markdown_utils.py +291 -0
  667. utils/message_utils.py +147 -0
  668. utils/metrics.py +36 -0
  669. utils/path_validator.py +227 -0
  670. utils/rate_limit_manager.py +399 -0
  671. utils/result_size.py +296 -0
  672. utils/time_utils.py +206 -0
  673. utils/user_utils.py +188 -0
  674. webview/README.md +111 -0
  675. webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
  676. webview/__init__.py +8 -0
  677. webview/activity.py +636 -0
  678. webview/owner_auth.py +108 -0
  679. webview/pages.py +448 -0
  680. webview/repair_sessions.py +399 -0
  681. webview/server.py +4323 -0
  682. webview/server_launcher.py +112 -0
  683. webview/static/css/activity.css +89 -0
  684. webview/static/css/chat.css +1772 -0
  685. webview/static/css/components.css +1190 -0
  686. webview/static/css/config-panel.css +651 -0
  687. webview/static/css/pages/admin.css +766 -0
  688. webview/static/css/pages/profile.css +231 -0
  689. webview/static/css/pages/settings.css +297 -0
  690. webview/static/css/pages/signin.css +316 -0
  691. webview/static/css/style.css +4250 -0
  692. webview/static/css/variables.css +147 -0
  693. webview/static/css/workspace-fullwidth-fix.css +109 -0
  694. webview/static/img/favicon.ico +0 -0
  695. webview/static/js/activity.js +394 -0
  696. webview/static/js/admin/activity.js +186 -0
  697. webview/static/js/admin/dashboard.js +163 -0
  698. webview/static/js/admin/user_detail.js +400 -0
  699. webview/static/js/admin/users.js +174 -0
  700. webview/static/js/admin/utils.js +214 -0
  701. webview/static/js/chat.js +3178 -0
  702. webview/static/js/config-panel.js +522 -0
  703. webview/static/js/constants.js +20 -0
  704. webview/static/js/error-handler.js +226 -0
  705. webview/static/js/ethers.min.js +1 -0
  706. webview/static/js/event-filter.js +137 -0
  707. webview/static/js/event-store.js +351 -0
  708. webview/static/js/file-attachments.js +374 -0
  709. webview/static/js/file-loader.js +163 -0
  710. webview/static/js/index.js +103 -0
  711. webview/static/js/performance-utils.js +196 -0
  712. webview/static/js/profile.js +396 -0
  713. webview/static/js/screenshot.js +386 -0
  714. webview/static/js/session.js +1527 -0
  715. webview/static/js/settings.js +1288 -0
  716. webview/static/js/sidebar-data.js +766 -0
  717. webview/static/js/sidebar-toggle.js +296 -0
  718. webview/static/js/socket.io.min.js +7 -0
  719. webview/static/js/stats.js +355 -0
  720. webview/static/js/ui-utils.js +420 -0
  721. webview/static/js/workspace.js +966 -0
  722. webview/stats_service.py +613 -0
  723. webview/templates/__init__.py +1 -0
  724. webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  725. webview/templates/activity.html +36 -0
  726. webview/templates/admin/activity.html +122 -0
  727. webview/templates/admin/dashboard.html +112 -0
  728. webview/templates/admin/user_detail.html +228 -0
  729. webview/templates/admin/users.html +105 -0
  730. webview/templates/autonomy.html +60 -0
  731. webview/templates/error.html +133 -0
  732. webview/templates/finance.html +90 -0
  733. webview/templates/identity.html +73 -0
  734. webview/templates/index.html +42 -0
  735. webview/templates/layout.html +125 -0
  736. webview/templates/memory.html +59 -0
  737. webview/templates/owner_login.html +15 -0
  738. webview/templates/profile.html +121 -0
  739. webview/templates/session.html +312 -0
  740. webview/templates/settings.html +534 -0
  741. webview/templates/sidebar.html +112 -0
  742. webview/templates/signin.html +213 -0
  743. webview/templates/status.html +14 -0
  744. webview/templates/system.html +45 -0
  745. webview/webgate.py +196 -0
core/tool_catalog.py ADDED
@@ -0,0 +1,192 @@
1
+ """Product-facing tool catalog built from the descriptor source of truth."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import os
7
+ from dataclasses import asdict, dataclass, field
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ def _load_descriptors_module():
13
+ """Load tools/descriptors.py without importing tools/__init__.py."""
14
+ path = Path(__file__).resolve().parents[1] / "tools" / "descriptors.py"
15
+ spec = importlib.util.spec_from_file_location("_polyrob_tool_descriptors", path)
16
+ if spec is None or spec.loader is None:
17
+ raise RuntimeError(f"could not load tool descriptors from {path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ spec.loader.exec_module(module)
20
+ return module
21
+
22
+
23
+ _DESCRIPTORS = _load_descriptors_module()
24
+ TOOL_DESCRIPTORS = _DESCRIPTORS.TOOL_DESCRIPTORS
25
+ ToolCategory = _DESCRIPTORS.ToolCategory
26
+ ToolDescriptor = _DESCRIPTORS.ToolDescriptor
27
+
28
+
29
+ _CATEGORY_LABELS = {
30
+ ToolCategory.CORE: "core",
31
+ ToolCategory.BROWSER: "automation",
32
+ ToolCategory.COMMUNICATION: "integration",
33
+ ToolCategory.SEARCH: "automation",
34
+ ToolCategory.VERIFICATION: "integration",
35
+ ToolCategory.INTEGRATION: "integration",
36
+ }
37
+
38
+ _PERMISSIONS = {
39
+ "filesystem": ["fs.read", "fs.write"],
40
+ "task": ["memory.read", "memory.write"],
41
+ "browser_manager": ["browser.control", "network.read"],
42
+ "perplexity": ["network.read"],
43
+ "twitter": ["network.read", "network.write", "social.post"],
44
+ "email": ["network.write", "email.send"],
45
+ "collabland": ["network.read"],
46
+ "alchemy": ["network.read"],
47
+ "mcp": ["mcp.call", "network.read"],
48
+ "anysite": ["network.read", "process.spawn"],
49
+ "polymarket": ["network.read", "trade.execute"],
50
+ "hyperliquid": ["network.read", "trade.execute"],
51
+ # Read-only split tools: market data only, no wallet/signing.
52
+ "polymarket_data": ["network.read"],
53
+ "hyperliquid_data": ["network.read"],
54
+ }
55
+
56
+ _HIGH_RISK_TOOLS = {"twitter", "email", "polymarket", "hyperliquid"}
57
+ _MEDIUM_RISK_TOOLS = {"mcp", "anysite", "browser_manager", "perplexity"}
58
+ _ENABLE_FLAGS = {
59
+ "twitter": "TWITTER_ENABLED",
60
+ }
61
+ _DB_CREDENTIAL_TOOLS = {
62
+ "polymarket": "requires database-stored trading credentials and explicit operator enablement",
63
+ "hyperliquid": "requires database-stored trading credentials and explicit operator enablement",
64
+ }
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class ToolCatalogEntry:
69
+ """Stable product metadata for one tool."""
70
+
71
+ id: str
72
+ display_name: str
73
+ category: str
74
+ model_description: str
75
+ human_description: str
76
+ permissions: List[str] = field(default_factory=list)
77
+ scope: str = "session"
78
+ enabled: bool = True
79
+ disabled_reason: Optional[str] = None
80
+ required_config: List[str] = field(default_factory=list)
81
+ required_services: List[str] = field(default_factory=list)
82
+ optional_services: List[str] = field(default_factory=list)
83
+ rate_limited: bool = False
84
+ rate_limits: Dict[str, Any] = field(default_factory=dict)
85
+ cost_risk: str = "low"
86
+ security_risk: str = "low"
87
+ audit_events: List[str] = field(default_factory=list)
88
+
89
+ def to_dict(self) -> Dict[str, Any]:
90
+ return asdict(self)
91
+
92
+
93
+ def build_tool_catalog(env: Optional[Dict[str, str]] = None) -> List[ToolCatalogEntry]:
94
+ """Return catalog entries sorted by category and id."""
95
+ env = os.environ if env is None else env
96
+ entries = [_entry_from_descriptor(name, desc, env) for name, desc in TOOL_DESCRIPTORS.items()]
97
+ return sorted(entries, key=lambda e: (e.category, e.id))
98
+
99
+
100
+ def find_tool(tool_id: str, env: Optional[Dict[str, str]] = None) -> Optional[ToolCatalogEntry]:
101
+ needle = tool_id.lower().strip()
102
+ for entry in build_tool_catalog(env=env):
103
+ if entry.id == needle:
104
+ return entry
105
+ return None
106
+
107
+
108
+ def permission_catalog() -> Dict[str, List[str]]:
109
+ """Return permission class -> tool ids."""
110
+ result: Dict[str, List[str]] = {}
111
+ for entry in build_tool_catalog():
112
+ for permission in entry.permissions:
113
+ result.setdefault(permission, []).append(entry.id)
114
+ return {key: sorted(value) for key, value in sorted(result.items())}
115
+
116
+
117
+ def _entry_from_descriptor(
118
+ name: str,
119
+ desc: ToolDescriptor,
120
+ env: Dict[str, str],
121
+ ) -> ToolCatalogEntry:
122
+ required_config = list(desc.required_config)
123
+ missing = [key for key in required_config if not _config_present(key, env)]
124
+ gated_reason = _gated_reason(name, env)
125
+ enabled = not missing
126
+ disabled_reason = None
127
+ if missing:
128
+ disabled_reason = "missing config: " + ", ".join(missing)
129
+ if gated_reason:
130
+ enabled = False
131
+ disabled_reason = gated_reason
132
+
133
+ return ToolCatalogEntry(
134
+ id=name,
135
+ display_name=name.replace("_", " ").title(),
136
+ category=_CATEGORY_LABELS.get(desc.category, desc.category.value),
137
+ model_description=_model_description(desc.description),
138
+ human_description=desc.description,
139
+ permissions=list(_PERMISSIONS.get(name, [])),
140
+ enabled=enabled,
141
+ disabled_reason=disabled_reason,
142
+ required_config=required_config,
143
+ required_services=list(desc.required_services),
144
+ optional_services=list(desc.optional_services),
145
+ rate_limited=desc.rate_limited,
146
+ rate_limits=dict(desc.rate_limit_settings),
147
+ cost_risk=_risk_for(name),
148
+ security_risk=_risk_for(name),
149
+ audit_events=_audit_events(name),
150
+ )
151
+
152
+
153
+ def _config_present(key: str, env: Dict[str, str]) -> bool:
154
+ variants = {
155
+ key,
156
+ key.upper(),
157
+ key.lower(),
158
+ key.replace("_api_key", "_API_KEY").upper(),
159
+ }
160
+ return any(bool(env.get(variant)) for variant in variants)
161
+
162
+
163
+ def _model_description(description: str) -> str:
164
+ first_sentence = description.split(".", 1)[0].strip()
165
+ return first_sentence or description.strip()
166
+
167
+
168
+ def _risk_for(name: str) -> str:
169
+ if name in _HIGH_RISK_TOOLS:
170
+ return "high"
171
+ if name in _MEDIUM_RISK_TOOLS:
172
+ return "medium"
173
+ return "low"
174
+
175
+
176
+ def _gated_reason(name: str, env: Dict[str, str]) -> Optional[str]:
177
+ flag = _ENABLE_FLAGS.get(name)
178
+ if flag and not _truthy(env.get(flag)):
179
+ return f"gated by {flag}=false"
180
+ return _DB_CREDENTIAL_TOOLS.get(name)
181
+
182
+
183
+ def _truthy(value: Optional[str]) -> bool:
184
+ return str(value or "").strip().lower() in {"1", "true", "yes", "on"}
185
+
186
+
187
+ def _audit_events(name: str) -> List[str]:
188
+ permissions = _PERMISSIONS.get(name, [])
189
+ events = ["tool.call"]
190
+ if any(p.endswith(".write") or p in {"social.post", "email.send", "trade.execute"} for p in permissions):
191
+ events.append("external.write")
192
+ return events
core/version.py ADDED
@@ -0,0 +1,75 @@
1
+ """Single source of truth for the project version at runtime.
2
+
3
+ Build-time SSOT is pyproject.toml. Resolution order at runtime:
4
+
5
+ 1. The **source** ``pyproject.toml`` next to this checkout — authoritative when you
6
+ run ``polyrob`` from a source tree. This is FIRST on purpose: a stale editable
7
+ or wheel install (e.g. a venv still carrying ``polyrob 1.0.0`` while the source
8
+ is ``0.4.2``) must NOT shadow the version of the code you're actually running.
9
+ 2. Installed package metadata — for a real ``pip install`` where there's no source
10
+ ``pyproject.toml`` adjacent to this module (site-packages).
11
+ 3. The literal fallback below (tests pin it to pyproject.toml so it can't drift).
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from importlib.metadata import PackageNotFoundError, version as _pkg_version
16
+ from pathlib import Path
17
+ from typing import Optional
18
+
19
+ # Dev-checkout fallback. MUST equal pyproject.toml [project].version.
20
+ _FALLBACK_VERSION = "0.5.0"
21
+
22
+ # Project names this module is willing to claim from an adjacent pyproject.toml, so
23
+ # a parent/monorepo pyproject can never mislabel the version.
24
+ _OWN_PROJECT_NAMES = {"polyrob", "polyrob-core", "rob"}
25
+
26
+
27
+ def _load_toml(text: str) -> Optional[dict]:
28
+ try:
29
+ import tomllib # py3.11+
30
+ except ModuleNotFoundError: # pragma: no cover - older interpreters
31
+ try:
32
+ import tomli as tomllib # type: ignore
33
+ except ModuleNotFoundError:
34
+ return None
35
+ try:
36
+ return tomllib.loads(text)
37
+ except Exception:
38
+ return None
39
+
40
+
41
+ def _source_pyproject_version() -> Optional[str]:
42
+ """Version from the checkout's own ``pyproject.toml``, or None.
43
+
44
+ ``core/version.py`` → repo root is ``parents[1]``. Only trusted when the file's
45
+ ``[project].name`` is one of ours, so a wrong/parent pyproject is ignored. Fully
46
+ fail-open: any read/parse problem returns None and we fall through to metadata.
47
+ """
48
+ try:
49
+ pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml"
50
+ if not pyproject.is_file():
51
+ return None
52
+ data = _load_toml(pyproject.read_text(encoding="utf-8"))
53
+ if not data:
54
+ return None
55
+ project = data.get("project", {}) or {}
56
+ name = str(project.get("name", "")).strip().lower()
57
+ ver = project.get("version")
58
+ if ver and name in _OWN_PROJECT_NAMES:
59
+ return str(ver)
60
+ except Exception:
61
+ return None
62
+ return None
63
+
64
+
65
+ def get_version() -> str:
66
+ src = _source_pyproject_version()
67
+ if src:
68
+ return src
69
+ try:
70
+ return _pkg_version("polyrob")
71
+ except PackageNotFoundError:
72
+ return _FALLBACK_VERSION
73
+
74
+
75
+ __version__ = get_version()
@@ -0,0 +1,5 @@
1
+ """Agent personal wallet (core-tier). The agent's own wallet, used as a tool.
2
+
3
+ Deliberately separate from the server-tier tariffing gateway (modules/x402,
4
+ modules/payments). MUST NOT import any server-tier billing module.
5
+ """
@@ -0,0 +1,59 @@
1
+ """AgentWallet: the agent's single, operator-funded personal wallet (core-tier).
2
+
3
+ Hub-and-spoke topology: one master seed → a treasury key + domain-separated
4
+ per-venue keys. Hyperliquid trades via its own delegated key (the venue's
5
+ built-in withdrawal firewall); x402 pays from its own small float.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ from typing import Callable, List, Optional
11
+
12
+ from core.wallet.config import WalletConfig
13
+ from core.wallet.policy import PolicyGate
14
+ from core.wallet.signer import LocalEoaSigner
15
+
16
+ VENUES = frozenset({"treasury", "x402", "polymarket", "hyperliquid"})
17
+
18
+ _PBKDF2_ITERS = 100_000
19
+
20
+
21
+ class AgentWallet:
22
+ def __init__(self, config: WalletConfig, audit_sink: Optional[List[dict]] = None,
23
+ on_record: Optional[Callable[[dict], None]] = None):
24
+ self._config = config
25
+ if config.enabled:
26
+ if not config.master_seed or len(config.master_seed) < 32:
27
+ raise ValueError("AGENT_WALLET_MASTER_SEED must be set and >=32 chars when enabled")
28
+ self._seed = config.master_seed or ""
29
+ self._signers: dict[str, LocalEoaSigner] = {}
30
+ self._policy = PolicyGate(
31
+ max_per_tx_usd=config.max_per_tx_usd,
32
+ audit_sink=audit_sink,
33
+ daily_cap_usd=getattr(config, "daily_cap_usd", None),
34
+ per_venue_daily_cap_usd=getattr(config, "per_venue_daily_cap_usd", None),
35
+ on_record=on_record,
36
+ )
37
+
38
+ def _derive_key(self, venue: str) -> bytes:
39
+ # Domain-separated label keeps venue keys independent and recoverable.
40
+ label = f"agent-wallet:{venue}".encode("utf-8")
41
+ return hashlib.pbkdf2_hmac("sha256", self._seed.encode("utf-8"), label, _PBKDF2_ITERS, dklen=32)
42
+
43
+ def signer_for(self, venue: str) -> LocalEoaSigner:
44
+ if venue not in VENUES:
45
+ raise ValueError(f"unknown venue '{venue}' (expected one of {sorted(VENUES)})")
46
+ if venue not in self._signers:
47
+ self._signers[venue] = LocalEoaSigner(self._derive_key(venue))
48
+ return self._signers[venue]
49
+
50
+ def account_for(self, venue: str):
51
+ return self.signer_for(venue).account
52
+
53
+ @property
54
+ def address(self) -> str:
55
+ return self.signer_for("treasury").address
56
+
57
+ @property
58
+ def policy(self) -> PolicyGate:
59
+ return self._policy
@@ -0,0 +1,61 @@
1
+ """Durable, append-only audit sink for the wallet PolicyGate (G3).
2
+
3
+ An in-memory ``list`` of audit entries is lost on restart, so the harness can't sum
4
+ lifetime/rolling spend across a service restart — a mainnet prerequisite. This sink
5
+ is a ``list`` subclass that mirrors every appended entry to an append-only JSONL file
6
+ and reloads prior entries on construction, so a fresh ``PolicyGate(audit_sink=...)``
7
+ sees the full history. Default behavior is unchanged: PolicyGate uses a plain list
8
+ unless a sink is injected (only the factory does, when the wallet is enabled).
9
+
10
+ Fail-open: a file I/O error never blocks an action (the in-memory copy is always
11
+ authoritative for the live process); persistence is best-effort.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import logging
17
+ import os
18
+ from typing import List
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class JsonlAuditSink(list):
24
+ """A list of audit dicts mirrored to an append-only JSONL file."""
25
+
26
+ def __init__(self, path: str):
27
+ super().__init__()
28
+ self._path = path
29
+ parent = os.path.dirname(path)
30
+ if parent:
31
+ os.makedirs(parent, exist_ok=True)
32
+ self._load()
33
+
34
+ def _load(self) -> None:
35
+ if not os.path.exists(self._path):
36
+ return
37
+ try:
38
+ with open(self._path, "r", encoding="utf-8") as fh:
39
+ for line in fh:
40
+ line = line.strip()
41
+ if not line:
42
+ continue
43
+ try:
44
+ list.append(self, json.loads(line)) # base append: no re-write
45
+ except json.JSONDecodeError:
46
+ continue # skip a corrupt line, keep the rest
47
+ except OSError as e:
48
+ logger.warning("wallet audit sink load failed (%s): %s", self._path, e)
49
+
50
+ def append(self, entry: dict) -> None: # type: ignore[override]
51
+ list.append(self, entry)
52
+ try:
53
+ with open(self._path, "a", encoding="utf-8") as fh:
54
+ fh.write(json.dumps(entry) + "\n")
55
+ except OSError as e:
56
+ logger.warning("wallet audit sink write failed (%s): %s", self._path, e)
57
+
58
+
59
+ def default_audit_sink(data_dir: str = "data") -> List[dict]:
60
+ """The factory's default persistent sink at ``<data_dir>/wallet/audit.jsonl``."""
61
+ return JsonlAuditSink(os.path.join(data_dir, "wallet", "audit.jsonl"))
core/wallet/config.py ADDED
@@ -0,0 +1,70 @@
1
+ """Agent-wallet configuration (env-driven, default-safe)."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from dataclasses import dataclass, field
6
+ from typing import Dict, Mapping, Optional
7
+
8
+ TESTNET_FACILITATOR_URL = "https://x402.org/facilitator"
9
+
10
+ _TRUE = {"1", "true", "yes", "on"}
11
+
12
+
13
+ def _b(env: Mapping[str, str], key: str, default: bool) -> bool:
14
+ raw = env.get(key)
15
+ return default if raw is None else raw.strip().lower() in _TRUE
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class WalletConfig:
20
+ enabled: bool
21
+ backend: str
22
+ master_seed: Optional[str]
23
+ network: str
24
+ max_per_tx_usd: float
25
+ x402_client_enabled: bool
26
+ x402_facilitator_url: str
27
+ daily_cap_usd: Optional[float] = None
28
+ per_venue_daily_cap_usd: Dict[str, float] = field(default_factory=dict)
29
+
30
+
31
+ def _opt_float(env: Mapping[str, str], key: str) -> Optional[float]:
32
+ raw = env.get(key)
33
+ if raw is None or not str(raw).strip():
34
+ return None
35
+ try:
36
+ return float(raw)
37
+ except ValueError:
38
+ return None
39
+
40
+
41
+ def _load_per_venue_caps(env: Mapping[str, str]) -> Dict[str, float]:
42
+ """Parse WALLET_VENUE_DAILY_CAP_<VENUE>_USD env vars into {venue: cap}."""
43
+ prefix, suffix = "WALLET_VENUE_DAILY_CAP_", "_USD"
44
+ caps: Dict[str, float] = {}
45
+ for key in env:
46
+ if key.startswith(prefix) and key.endswith(suffix):
47
+ venue = key[len(prefix):-len(suffix)].lower()
48
+ val = _opt_float(env, key)
49
+ if venue and val is not None:
50
+ caps[venue] = val
51
+ return caps
52
+
53
+
54
+ def load_wallet_config(env: Optional[Mapping[str, str]] = None) -> WalletConfig:
55
+ env = os.environ if env is None else env
56
+ network = env.get("AGENT_WALLET_NETWORK", "testnet").strip().lower()
57
+ return WalletConfig(
58
+ enabled=_b(env, "AGENT_WALLET_ENABLED", False),
59
+ backend=env.get("AGENT_WALLET_BACKEND", "local_eoa").strip().lower(),
60
+ master_seed=env.get("AGENT_WALLET_MASTER_SEED"),
61
+ network=network if network in ("testnet", "mainnet") else "testnet",
62
+ # Safety default: a catastrophic per-tx ceiling, NOT a budget. Was
63
+ # $1,000,000 (a typo could drain funds); raise it explicitly if needed.
64
+ max_per_tx_usd=float(env.get("AGENT_WALLET_MAX_PER_TX_USD", "1000")),
65
+ x402_client_enabled=_b(env, "X402_CLIENT_ENABLED", False),
66
+ x402_facilitator_url=env.get("X402_CLIENT_FACILITATOR_URL", TESTNET_FACILITATOR_URL),
67
+ # Rolling 24h spend cap; unset = disabled = legacy behavior (per-tx ceiling only).
68
+ daily_cap_usd=_opt_float(env, "WALLET_DAILY_CAP_USD"),
69
+ per_venue_daily_cap_usd=_load_per_venue_caps(env),
70
+ )
core/wallet/factory.py ADDED
@@ -0,0 +1,87 @@
1
+ """Process-level AgentWallet singleton (the agent's wallet is single/operator-owned)."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Optional
5
+
6
+ from core.wallet.agent_wallet import AgentWallet
7
+ from core.wallet.config import load_wallet_config
8
+ from core.wallet.policy import PolicyGate
9
+
10
+ _cached: Optional[AgentWallet] = None
11
+ _resolved = False
12
+ _standalone_policy: Optional[PolicyGate] = None
13
+
14
+
15
+ def _emit_spend_to_event_log(entry: dict) -> None:
16
+ """PolicyGate.on_record hook: mirror each recorded spend into the durable
17
+ telemetry event log so money movement survives a restart and is queryable
18
+ cross-session (telemetry audit 2026-07-04). LAZY import keeps the core/wallet
19
+ tier free of a top-level agents-tier dependency; fail-open throughout."""
20
+ try:
21
+ from agents.task.telemetry.event_log import get_event_log, event_log_enabled
22
+ if not event_log_enabled():
23
+ return
24
+ get_event_log().record(
25
+ "wallet_spend",
26
+ source="wallet",
27
+ venue=entry.get("venue"),
28
+ action=entry.get("action"),
29
+ amount_usd=entry.get("amount_usd"),
30
+ counterparty=entry.get("counterparty"),
31
+ result_ref=entry.get("result_ref"),
32
+ ts=entry.get("ts"),
33
+ )
34
+ except Exception:
35
+ pass
36
+
37
+
38
+ def get_agent_wallet() -> Optional[AgentWallet]:
39
+ global _cached, _resolved
40
+ if _resolved:
41
+ return _cached
42
+ cfg = load_wallet_config()
43
+ if cfg.enabled:
44
+ # Persist the audit trail so rolling-spend/lifetime tracking survives a
45
+ # restart (mainnet prerequisite). Fail-open: fall back to in-memory if the
46
+ # durable sink can't be created.
47
+ sink = None
48
+ try:
49
+ from core.wallet.audit_sink import default_audit_sink
50
+ sink = default_audit_sink()
51
+ except Exception:
52
+ sink = None
53
+ _cached = AgentWallet(cfg, audit_sink=sink, on_record=_emit_spend_to_event_log)
54
+ else:
55
+ _cached = None
56
+ _resolved = True
57
+ return _cached
58
+
59
+
60
+ def get_policy_gate() -> PolicyGate:
61
+ """The PolicyGate that guards value-moving actions (trades + payments).
62
+
63
+ Returns the agent wallet's gate when the wallet is enabled; otherwise a
64
+ standalone gate built from wallet config so the catastrophic per-tx ceiling
65
+ and daily/venue caps STILL apply to DB-credential trading (which has no
66
+ agent wallet). Cached so rolling-spend/replay state persists within a process.
67
+ """
68
+ wallet = get_agent_wallet()
69
+ if wallet is not None:
70
+ return wallet.policy
71
+ global _standalone_policy
72
+ if _standalone_policy is None:
73
+ cfg = load_wallet_config()
74
+ _standalone_policy = PolicyGate(
75
+ max_per_tx_usd=cfg.max_per_tx_usd,
76
+ daily_cap_usd=cfg.daily_cap_usd,
77
+ per_venue_daily_cap_usd=cfg.per_venue_daily_cap_usd,
78
+ on_record=_emit_spend_to_event_log,
79
+ )
80
+ return _standalone_policy
81
+
82
+
83
+ def reset_agent_wallet_cache() -> None:
84
+ global _cached, _resolved, _standalone_policy
85
+ _cached = None
86
+ _resolved = False
87
+ _standalone_policy = None
core/wallet/policy.py ADDED
@@ -0,0 +1,104 @@
1
+ """PolicyGate: cheap, backend-independent hygiene for value-moving actions.
2
+
3
+ NOT a budget engine (the operator asked for 'no limits now'); it enforces ONE
4
+ catastrophic per-tx ceiling, idempotency de-dup, and an append-only audit trail.
5
+ A future budget engine can extend `check`."""
6
+ from __future__ import annotations
7
+
8
+ import time
9
+ from dataclasses import dataclass
10
+ from typing import Callable, List, Optional
11
+
12
+ _DAY_SECONDS = 86_400
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class PolicyDecision:
17
+ allowed: bool
18
+ reason: Optional[str]
19
+
20
+
21
+ class PolicyGate:
22
+ def __init__(self, max_per_tx_usd: float, audit_sink: Optional[List[dict]] = None,
23
+ daily_cap_usd: Optional[float] = None,
24
+ per_venue_daily_cap_usd: Optional[dict] = None,
25
+ clock: Callable[[], float] = time.time,
26
+ on_record: Optional[Callable[[dict], None]] = None):
27
+ self._ceiling = float(max_per_tx_usd)
28
+ # Telemetry hook (audit 2026-07-04): fired with each recorded spend entry so
29
+ # a durable sink can capture money movement. Fail-open — never break record().
30
+ self._on_record = on_record
31
+ self._audit: List[dict] = audit_sink if audit_sink is not None else []
32
+ self._daily_cap: Optional[float] = (
33
+ None if daily_cap_usd is None else float(daily_cap_usd)
34
+ )
35
+ # Per-venue rolling-24h caps so one venue can't drain the global budget.
36
+ self._per_venue_cap: dict = {
37
+ str(k).lower(): float(v)
38
+ for k, v in (per_venue_daily_cap_usd or {}).items()
39
+ }
40
+ self._now = clock
41
+ # Rebuild the replay-guard set from a pre-loaded (persistent) sink so
42
+ # idempotency survives a restart. No-op for the default empty list.
43
+ self._seen_idempotency: set[str] = {
44
+ e["idempotency_key"] for e in self._audit
45
+ if e.get("idempotency_key")
46
+ }
47
+
48
+ def _rolling_24h_spend(self, venue: Optional[str] = None) -> float:
49
+ window_start = self._now() - _DAY_SECONDS
50
+ return sum(
51
+ float(e.get("amount_usd") or 0.0)
52
+ for e in self._audit
53
+ if float(e.get("ts") or 0.0) >= window_start
54
+ and (venue is None or e.get("venue") == venue)
55
+ )
56
+
57
+ def check(self, *, venue: str, amount_usd: float, idempotency_key: Optional[str]) -> PolicyDecision:
58
+ if amount_usd > self._ceiling:
59
+ return PolicyDecision(False, f"amount ${amount_usd:.2f} exceeds catastrophic ceiling ${self._ceiling:.2f}")
60
+ if idempotency_key and idempotency_key in self._seen_idempotency:
61
+ return PolicyDecision(False, f"idempotency key '{idempotency_key}' already used (replay blocked)")
62
+ if self._daily_cap is not None:
63
+ spent = self._rolling_24h_spend()
64
+ if spent + amount_usd > self._daily_cap:
65
+ return PolicyDecision(
66
+ False,
67
+ f"daily spend cap ${self._daily_cap:.2f} would be exceeded "
68
+ f"(trailing-24h ${spent:.2f} + ${amount_usd:.2f})",
69
+ )
70
+ venue_cap = self._per_venue_cap.get(str(venue).lower())
71
+ if venue_cap is not None:
72
+ venue_spent = self._rolling_24h_spend(venue=venue)
73
+ if venue_spent + amount_usd > venue_cap:
74
+ return PolicyDecision(
75
+ False,
76
+ f"venue '{venue}' daily cap ${venue_cap:.2f} would be exceeded "
77
+ f"(trailing-24h ${venue_spent:.2f} + ${amount_usd:.2f})",
78
+ )
79
+ return PolicyDecision(True, None)
80
+
81
+ def record(self, *, venue: str, action: str, amount_usd: float,
82
+ counterparty: Optional[str], idempotency_key: Optional[str],
83
+ result_ref: Optional[str]) -> None:
84
+ if idempotency_key:
85
+ self._seen_idempotency.add(idempotency_key)
86
+ entry = {
87
+ "ts": self._now(),
88
+ "venue": venue,
89
+ "action": action,
90
+ "amount_usd": amount_usd,
91
+ "counterparty": counterparty,
92
+ "idempotency_key": idempotency_key,
93
+ "result_ref": result_ref,
94
+ }
95
+ self._audit.append(entry)
96
+ if self._on_record is not None:
97
+ try:
98
+ self._on_record(entry)
99
+ except Exception:
100
+ pass # fail-open: telemetry must never break a recorded spend
101
+
102
+ @property
103
+ def audit_log(self) -> List[dict]:
104
+ return list(self._audit)