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
@@ -0,0 +1,237 @@
1
+ """Credit balance manager for managing user credit balances."""
2
+
3
+ import logging
4
+ from datetime import datetime
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class CreditBalanceManager:
10
+ """Manage user credit balances."""
11
+
12
+ def __init__(self, db, tier_manager=None):
13
+ """
14
+ Initialize credit balance manager.
15
+
16
+ Args:
17
+ db: Database manager instance
18
+ tier_manager: TierManager instance (optional)
19
+ """
20
+ self.db = db
21
+ self.tier_manager = tier_manager
22
+ self.logger = logging.getLogger('credits.balance_manager')
23
+
24
+ async def get_balance(self, user_id: str) -> dict:
25
+ """Get user's credit balance."""
26
+
27
+ result = await self.db.fetch_one("""
28
+ SELECT balance, lifetime_earned, lifetime_spent
29
+ FROM user_credits
30
+ WHERE user_id = ?
31
+ """, (user_id,))
32
+
33
+ if not result:
34
+ # User must exist (payment_endpoints ensures this now)
35
+ # Initialize balance
36
+ await self.db.execute("""
37
+ INSERT INTO user_credits (user_id, balance)
38
+ VALUES (?, 0)
39
+ """, (user_id,))
40
+ return {"balance": 0, "lifetime_earned": 0, "lifetime_spent": 0}
41
+
42
+ return dict(result)
43
+
44
+ async def has_sufficient_balance(self, user_id: str, amount: int) -> bool:
45
+ """Check if user has enough credits."""
46
+
47
+ balance = await self.get_balance(user_id)
48
+ return balance['balance'] >= amount
49
+
50
+ async def deduct_credits(self, user_id: str, amount: int,
51
+ reason: str, session_id: str = None) -> bool:
52
+ """
53
+ Deduct credits from user balance.
54
+
55
+ Uses atomic UPDATE with WHERE clause to prevent race conditions.
56
+ Wrapped in transaction for consistency between balance update and transaction log.
57
+
58
+ Args:
59
+ user_id: User ID
60
+ amount: Amount to deduct
61
+ reason: Reason for deduction
62
+ session_id: Optional session ID
63
+
64
+ Returns:
65
+ True if successful, False if insufficient balance
66
+ """
67
+ # Ensure user exists (creates with 0 balance if not)
68
+ await self.get_balance(user_id)
69
+
70
+ try:
71
+ # Start transaction for atomic operation
72
+ await self.db.connection.begin_transaction()
73
+
74
+ try:
75
+ # SECURITY FIX: Atomic UPDATE with balance check in WHERE clause
76
+ # This prevents TOCTOU race condition where two concurrent requests
77
+ # could both read the same balance before either deducts
78
+ cursor = await self.db.execute("""
79
+ UPDATE user_credits
80
+ SET balance = balance - ?,
81
+ lifetime_spent = lifetime_spent + ?,
82
+ updated_at = CURRENT_TIMESTAMP
83
+ WHERE user_id = ? AND balance >= ?
84
+ """, (amount, amount, user_id, amount))
85
+
86
+ # Check if update succeeded (rowcount > 0 means balance was sufficient)
87
+ if cursor.rowcount == 0:
88
+ # No rows updated - insufficient balance
89
+ await self.db.connection.rollback()
90
+
91
+ # Get current balance for logging
92
+ balance_info = await self.get_balance(user_id)
93
+ self.logger.warning(
94
+ f"Insufficient credits for {user_id}: need {amount}, have {balance_info['balance']}"
95
+ )
96
+ return False
97
+
98
+ # Get new balance for transaction log
99
+ balance_info = await self.get_balance(user_id)
100
+ new_balance = balance_info['balance']
101
+ # Calculate what balance was before deduction
102
+ balance_before = new_balance + amount
103
+
104
+ # Record transaction (within same transaction)
105
+ await self.db.execute("""
106
+ INSERT INTO credit_transactions (
107
+ user_id, amount, transaction_type, reason,
108
+ session_id, balance_before, balance_after, timestamp
109
+ ) VALUES (?, ?, 'usage', ?, ?, ?, ?, CURRENT_TIMESTAMP)
110
+ """, (user_id, -amount, reason, session_id, balance_before, new_balance))
111
+
112
+ # Commit transaction
113
+ await self.db.connection.commit()
114
+
115
+ self.logger.info(f"Deducted {amount} credits from {user_id}: {reason}")
116
+ return True
117
+
118
+ except Exception as e:
119
+ # Rollback on any error
120
+ await self.db.connection.rollback()
121
+ raise
122
+
123
+ except Exception as e:
124
+ self.logger.error(f"Error deducting credits for {user_id}: {e}")
125
+ raise
126
+
127
+ async def add_credits(self, user_id: str, amount: int,
128
+ reason: str, transaction_type: str = 'purchase') -> bool:
129
+ """
130
+ Add credits to user balance.
131
+
132
+ Args:
133
+ user_id: User ID
134
+ amount: Amount to add
135
+ reason: Reason for addition
136
+ transaction_type: Type ('purchase', 'allowance', 'refund', 'admin_grant')
137
+
138
+ Returns:
139
+ True if successful
140
+ """
141
+
142
+ # Ensure the row exists (creates with 0 balance if not) so the atomic
143
+ # UPDATE below actually matches a row.
144
+ await self.get_balance(user_id)
145
+
146
+ # Join an already-open transaction instead of nesting: a caller like
147
+ # DepositMonitor._process_deposit wraps credit + dedup-row in ONE outer
148
+ # transaction, and a nested BEGIN raises (while an inner COMMIT would
149
+ # end the outer transaction early, breaking its rollback guarantee).
150
+ owns_tx = not await self.db.connection.in_transaction()
151
+ try:
152
+ if owns_tx:
153
+ await self.db.connection.begin_transaction()
154
+ try:
155
+ # SECURITY FIX (C2): atomic relative UPDATE, mirroring deduct_credits.
156
+ # The previous read-modify-write (get_balance -> SET balance=<abs>)
157
+ # let two concurrent grants both read the same stale balance and the
158
+ # later absolute write clobber the earlier one — a silently lost grant.
159
+ await self.db.execute("""
160
+ UPDATE user_credits
161
+ SET balance = balance + ?,
162
+ lifetime_earned = lifetime_earned + ?,
163
+ updated_at = CURRENT_TIMESTAMP
164
+ WHERE user_id = ?
165
+ """, (amount, amount, user_id))
166
+
167
+ # Read the authoritative post-update balance for the ledger row.
168
+ balance_info = await self.get_balance(user_id)
169
+ new_balance = balance_info['balance']
170
+ balance_before = new_balance - amount
171
+
172
+ await self.db.execute("""
173
+ INSERT INTO credit_transactions (
174
+ user_id, amount, transaction_type, reason,
175
+ balance_before, balance_after, timestamp
176
+ ) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
177
+ """, (user_id, amount, transaction_type, reason, balance_before, new_balance))
178
+
179
+ if owns_tx:
180
+ await self.db.connection.commit()
181
+ except Exception:
182
+ if owns_tx:
183
+ await self.db.connection.rollback()
184
+ raise
185
+ except Exception as e:
186
+ self.logger.error(f"Error adding credits for {user_id}: {e}")
187
+ raise
188
+
189
+ self.logger.info(f"Added {amount} credits to {user_id}: {reason} ({transaction_type})")
190
+
191
+ return True
192
+
193
+ async def get_transaction_history(self, user_id: str, limit: int = 50) -> list:
194
+ """Get user's credit transaction history."""
195
+
196
+ results = await self.db.fetch_all("""
197
+ SELECT
198
+ amount,
199
+ transaction_type,
200
+ reason,
201
+ session_id,
202
+ balance_before,
203
+ balance_after,
204
+ timestamp
205
+ FROM credit_transactions
206
+ WHERE user_id = ?
207
+ ORDER BY timestamp DESC
208
+ LIMIT ?
209
+ """, (user_id, limit))
210
+
211
+ return [dict(row) for row in results]
212
+
213
+ async def get_monthly_stats(self, user_id: str) -> dict:
214
+ """Get monthly usage statistics."""
215
+
216
+ # Get this month's usage
217
+ month_usage = await self.db.fetch_one("""
218
+ SELECT COALESCE(SUM(ABS(amount)), 0) as total
219
+ FROM credit_transactions
220
+ WHERE user_id = ?
221
+ AND transaction_type = 'usage'
222
+ AND timestamp >= date('now', 'start of month')
223
+ """, (user_id,))
224
+
225
+ # Get this month's purchases
226
+ month_purchased = await self.db.fetch_one("""
227
+ SELECT COALESCE(SUM(amount), 0) as total
228
+ FROM credit_transactions
229
+ WHERE user_id = ?
230
+ AND transaction_type IN ('purchase', 'allowance')
231
+ AND timestamp >= date('now', 'start of month')
232
+ """, (user_id,))
233
+
234
+ return {
235
+ "month_spent": month_usage['total'] if month_usage else 0,
236
+ "month_earned": month_purchased['total'] if month_purchased else 0
237
+ }
@@ -0,0 +1,129 @@
1
+ """
2
+ Shared cost calculation utilities.
3
+
4
+ SINGLE SOURCE OF TRUTH for cost estimation from token counts.
5
+ All modules should import from here, not duplicate logic.
6
+
7
+ This module consolidates:
8
+ - webview/stats_service.py::_calculate_cost_from_registry (REMOVED)
9
+ - agents/task/telemetry/service.py::_calculate_cost_from_registry (REMOVED)
10
+ - modules/llm/token_counter.py::estimate_cost (delegates here)
11
+ """
12
+
13
+ from typing import Optional, Tuple, Dict, Any
14
+ import logging
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def calculate_cost_from_tokens(
20
+ model_name: str,
21
+ input_tokens: Optional[int] = None,
22
+ output_tokens: Optional[int] = None,
23
+ total_tokens: Optional[int] = None,
24
+ cached_tokens: int = 0
25
+ ) -> float:
26
+ """
27
+ Calculate API cost from token counts using model registry.
28
+
29
+ This is the SINGLE implementation - all other files should import this.
30
+
31
+ Args:
32
+ model_name: Name of the model
33
+ input_tokens: Number of input/prompt tokens
34
+ output_tokens: Number of output/completion tokens
35
+ total_tokens: Total tokens (fallback if split not available)
36
+ cached_tokens: Number of cached tokens
37
+
38
+ Returns:
39
+ Estimated API cost in USD
40
+ """
41
+ from modules.llm.model_registry import calculate_cost
42
+
43
+ if not model_name or model_name == "unknown":
44
+ logger.debug("Cannot calculate cost: invalid model name")
45
+ return 0.0
46
+
47
+ # Use split tokens if available (most accurate)
48
+ if input_tokens is not None and output_tokens is not None:
49
+ try:
50
+ return calculate_cost(
51
+ model_name=model_name,
52
+ input_tokens=input_tokens,
53
+ output_tokens=output_tokens,
54
+ cached_tokens=cached_tokens
55
+ )
56
+ except Exception as e:
57
+ logger.warning(f"Cost calculation failed for {model_name}: {e}")
58
+ # Fall through to estimation
59
+
60
+ # Fallback: estimate split from total (65% input, 35% output)
61
+ if total_tokens and total_tokens > 0:
62
+ estimated_input = int(total_tokens * 0.65)
63
+ estimated_output = int(total_tokens * 0.35)
64
+
65
+ logger.debug(
66
+ f"Estimating token split for {model_name}: "
67
+ f"{estimated_input} input + {estimated_output} output = {total_tokens} total"
68
+ )
69
+
70
+ try:
71
+ return calculate_cost(
72
+ model_name=model_name,
73
+ input_tokens=estimated_input,
74
+ output_tokens=estimated_output,
75
+ cached_tokens=0 # Can't estimate cached from total
76
+ )
77
+ except Exception as e:
78
+ logger.warning(f"Cost estimation failed for {model_name}: {e}")
79
+
80
+ logger.debug(f"Cannot calculate cost for {model_name}: no token data")
81
+ return 0.0
82
+
83
+
84
+ def calculate_user_cost(api_cost_usd: float) -> Tuple[int, float]:
85
+ """
86
+ Calculate what user pays from API cost.
87
+
88
+ SINGLE SOURCE - uses pricing.py configuration.
89
+
90
+ Args:
91
+ api_cost_usd: What we pay the API provider
92
+
93
+ Returns:
94
+ Tuple of (credits_charged, user_cost_usd)
95
+ """
96
+ from modules.credits.pricing import pricing
97
+ return pricing.calculate_credits_from_api_cost(api_cost_usd)
98
+
99
+
100
+ def get_cost_breakdown(api_cost_usd: float) -> Dict[str, Any]:
101
+ """
102
+ Get complete cost breakdown for transparency.
103
+
104
+ Args:
105
+ api_cost_usd: What we pay the API provider
106
+
107
+ Returns:
108
+ Dict with complete breakdown including markup
109
+ """
110
+ from modules.credits.pricing import pricing
111
+
112
+ credits_charged, user_cost_usd = pricing.calculate_credits_from_api_cost(api_cost_usd)
113
+
114
+ return {
115
+ "api_cost_usd": api_cost_usd,
116
+ "user_cost_usd": user_cost_usd,
117
+ "credits_charged": credits_charged,
118
+ "markup": pricing.MARKUP,
119
+ "markup_usd": user_cost_usd - api_cost_usd
120
+ }
121
+
122
+
123
+ # Re-export for convenience
124
+ __all__ = [
125
+ 'calculate_cost_from_tokens',
126
+ 'calculate_user_cost',
127
+ 'get_cost_breakdown'
128
+ ]
129
+
@@ -0,0 +1,139 @@
1
+ """
2
+ Credits Module - Pricing Configuration
3
+
4
+ SINGLE SOURCE OF TRUTH for all credit pricing and cost calculation.
5
+
6
+ This config is used by:
7
+ - modules/credits/usage_tracker.py (calculates and charges)
8
+ - modules/credits/usage_meter.py (metering)
9
+ - modules/credits/balance_manager.py (balance operations)
10
+ - modules/auth/identity_mapper.py (welcome bonus, DEN allowance)
11
+ - modules/auth/tier_manager.py (tier limits)
12
+ - api/payment_endpoints.py (public pricing API)
13
+ - webview/stats_service.py (display fallback only)
14
+
15
+ Location: modules/credits/pricing.py
16
+ Why here? Pricing is OWNED by the credits module - it's business logic, not bot config.
17
+ """
18
+
19
+ import math
20
+ import os
21
+ from typing import Dict
22
+
23
+
24
+ # ============================================================================
25
+ # CREDIT BONUSES - Single source of truth for all bonus amounts
26
+ # ============================================================================
27
+
28
+ # Welcome bonus given to ALL new users on sign up
29
+ WELCOME_BONUS: int = int(os.environ.get("WELCOME_BONUS", "100"))
30
+
31
+ # One-time DEN Sign Up Allowance per token ID (prevents transfer abuse)
32
+ DEN_SIGNUP_ALLOWANCE: int = int(os.environ.get("DEN_SIGNUP_ALLOWANCE", "2000"))
33
+
34
+
35
+ class PricingConfig:
36
+ """Centralized pricing configuration for the entire application."""
37
+
38
+ # ============================================================================
39
+ # MARKUP CONFIGURATION (Core business logic)
40
+ # ============================================================================
41
+
42
+ # MARKUP: Users pay API cost × this multiplier
43
+ # Examples:
44
+ # 1.00 = 0% markup (users pay exactly what API costs)
45
+ # 1.20 = 20% markup (users pay API cost + 20%)
46
+ # 1.50 = 50% markup
47
+ MARKUP: float = float(os.environ.get("PRICING_MARKUP", "1.00"))
48
+
49
+ # ============================================================================
50
+ # CREDIT SYSTEM (Currency conversion)
51
+ # ============================================================================
52
+
53
+ # How much USD one credit is worth
54
+ CREDIT_VALUE_USD: float = float(os.environ.get("CREDIT_VALUE_USD", "0.01")) # 1 credit = $0.01
55
+
56
+ # Minimum charge per LLM call (prevents free calls)
57
+ MIN_CREDIT_CHARGE: int = int(os.environ.get("MIN_CREDIT_CHARGE", "1")) # Minimum 1 credit
58
+
59
+ # ============================================================================
60
+ # FIXED COSTS (Non-LLM resources)
61
+ # ============================================================================
62
+
63
+ # Session creation cost (infrastructure setup)
64
+ SESSION_CREATION_COST: int = int(os.environ.get("SESSION_CREATION_COST", "1"))
65
+
66
+ # Browser action costs
67
+ BROWSER_PAGE_LOAD_COST: float = float(os.environ.get("BROWSER_PAGE_LOAD_COST", "0.5"))
68
+ BROWSER_SCREENSHOT_COST: float = float(os.environ.get("BROWSER_SCREENSHOT_COST", "0.2"))
69
+ BROWSER_INTERACTION_COST: float = float(os.environ.get("BROWSER_INTERACTION_COST", "0.1"))
70
+ BROWSER_DEFAULT_COST: float = float(os.environ.get("BROWSER_DEFAULT_COST", "0.5"))
71
+
72
+ # Special action costs
73
+ VISION_CALL_COST: int = int(os.environ.get("VISION_CALL_COST", "2"))
74
+ TOOL_CALL_COST: float = float(os.environ.get("TOOL_CALL_COST", "0.5"))
75
+
76
+ @classmethod
77
+ def get_markup_info(cls) -> Dict[str, any]:
78
+ """Get markup information for display/reporting.
79
+
80
+ Returns:
81
+ Dict with markup details and formatted description
82
+ """
83
+ markup_percentage = (cls.MARKUP - 1.0) * 100
84
+
85
+ return {
86
+ "markup": cls.MARKUP,
87
+ "markup_percentage": markup_percentage,
88
+ "description": f"{markup_percentage:.0f}% markup" if markup_percentage > 0 else "No markup (API cost only)",
89
+ "credit_value_usd": cls.CREDIT_VALUE_USD,
90
+ "min_charge": cls.MIN_CREDIT_CHARGE
91
+ }
92
+
93
+ @classmethod
94
+ def calculate_credits_from_api_cost(cls, api_cost_usd: float) -> tuple[int, float]:
95
+ """Calculate credits to charge user from API cost.
96
+
97
+ This is the CORE pricing logic used throughout the application.
98
+
99
+ Args:
100
+ api_cost_usd: Cost charged by API provider in USD
101
+
102
+ Returns:
103
+ Tuple of (credits_to_charge, user_cost_usd)
104
+ """
105
+ # Convert to credits with markup
106
+ credits_raw = (api_cost_usd / cls.CREDIT_VALUE_USD) * cls.MARKUP
107
+
108
+ # ALWAYS round UP to ensure we never charge less than API cost
109
+ # Using math.ceil() instead of int(x + 0.5) which was incorrectly rounding DOWN
110
+ # for values like 2.42 → int(2.92) = 2, losing money!
111
+ credits_charged = max(cls.MIN_CREDIT_CHARGE, math.ceil(credits_raw))
112
+
113
+ # Calculate what user actually pays
114
+ user_cost_usd = credits_charged * cls.CREDIT_VALUE_USD
115
+
116
+ return credits_charged, user_cost_usd
117
+
118
+ @classmethod
119
+ def get_browser_action_cost(cls, action_type: str) -> float:
120
+ """Get cost for browser action.
121
+
122
+ Args:
123
+ action_type: Type of browser action (page_load, screenshot, interaction, etc.)
124
+
125
+ Returns:
126
+ Cost in credits
127
+ """
128
+ costs = {
129
+ "page_load": cls.BROWSER_PAGE_LOAD_COST,
130
+ "screenshot": cls.BROWSER_SCREENSHOT_COST,
131
+ "interaction": cls.BROWSER_INTERACTION_COST,
132
+ }
133
+ return costs.get(action_type, cls.BROWSER_DEFAULT_COST)
134
+
135
+
136
+ # Create singleton instance for easy importing
137
+ # Usage: from modules.credits.pricing import pricing
138
+ # price = pricing.MARKUP * api_cost
139
+ pricing = PricingConfig()
@@ -0,0 +1,148 @@
1
+ """Unified agent ledger: one read model over the
2
+ three money organs, so "what did I earn and spend this week, on what" is
3
+ answerable with evidence.
4
+
5
+ Legs (each fail-open to zeros — the stores live in different backends and any
6
+ may be absent in a given deployment):
7
+
8
+ - **costs** — LLM/tool self-cost from ``usage_records`` (api_cost_usd is the
9
+ provider truth; credits are the platform-billing view) in the async
10
+ ``database_manager`` store;
11
+ - **outbound spend** — wallet payments from the durable telemetry event log's
12
+ ``wallet_spend`` events (``core/wallet/factory._emit_spend_to_event_log``);
13
+ - **inbound** — x402 receipts from ``x402_payment_requests`` (settled rows),
14
+ plus the open pipeline (pending agent invoices).
15
+
16
+ Deliberately read-only and deliberately NOT a new table: `modules/credits`
17
+ stays the platform-billing concern; this is the agent's own balance-sheet VIEW
18
+ joining what already exists (application-level union — the legs share no DB).
19
+ """
20
+ import logging
21
+ import time
22
+ from typing import Any, Dict, Optional
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ async def _resolve_db(db=None):
28
+ if db is not None:
29
+ return db
30
+ from core.container import DependencyContainer
31
+ return DependencyContainer.get_instance().get_service("database_manager")
32
+
33
+
34
+ async def _costs_leg(database, user_id: str, days: int) -> Dict[str, Any]:
35
+ try:
36
+ row = await database.fetch_one(
37
+ """SELECT COALESCE(SUM(api_cost_usd), 0) AS api_usd,
38
+ COALESCE(SUM(cost), 0) AS credits,
39
+ COUNT(*) AS calls
40
+ FROM usage_records
41
+ WHERE user_id = ? AND timestamp >= datetime('now', ?)""",
42
+ (user_id, f"-{int(days)} day"),
43
+ )
44
+ return {
45
+ "llm_api_cost_usd": round(float(row.get("api_usd") or 0), 6) if row else 0.0,
46
+ "credits_spent": round(float(row.get("credits") or 0), 4) if row else 0.0,
47
+ "llm_calls": int(row.get("calls") or 0) if row else 0,
48
+ }
49
+ except Exception:
50
+ logger.debug("ledger: usage_records leg unavailable", exc_info=True)
51
+ return {"llm_api_cost_usd": 0.0, "credits_spent": 0.0, "llm_calls": 0}
52
+
53
+
54
+ def _wallet_leg(user_id: str, days: int) -> Dict[str, Any]:
55
+ if not user_id:
56
+ # query(user_id=None) means "no filter" — an empty tenant must NEVER
57
+ # widen into the platform-wide spend aggregate (cross-tenant leak)
58
+ return {"wallet_spend_usd": 0.0, "wallet_payments": 0}
59
+ try:
60
+ from agents.task.telemetry.event_log import get_event_log, event_log_enabled
61
+ if not event_log_enabled():
62
+ return {"wallet_spend_usd": 0.0, "wallet_payments": 0}
63
+ events = get_event_log().query(
64
+ kind="wallet_spend", user_id=user_id,
65
+ since_ts=time.time() - days * 86400, limit=1000,
66
+ )
67
+ total = 0.0
68
+ for ev in events or []:
69
+ attrs = ev.get("attrs") or {}
70
+ try:
71
+ total += float(attrs.get("amount_usd") or 0)
72
+ except (TypeError, ValueError):
73
+ continue
74
+ return {"wallet_spend_usd": round(total, 6), "wallet_payments": len(events or [])}
75
+ except Exception:
76
+ logger.debug("ledger: wallet_spend leg unavailable", exc_info=True)
77
+ return {"wallet_spend_usd": 0.0, "wallet_payments": 0}
78
+
79
+
80
+ async def _inbound_leg(database, user_id: str, days: int) -> Dict[str, Any]:
81
+ try:
82
+ tenant_like = f'%"tenant_id": "{user_id}"%'
83
+ settled = await database.fetch_one(
84
+ """SELECT COALESCE(SUM(amount_usd), 0) AS usd, COUNT(*) AS n
85
+ FROM x402_payment_requests
86
+ WHERE (user_id = ? OR metadata LIKE ?)
87
+ AND status IN ('completed', 'settled_no_tx')
88
+ AND created_at >= datetime('now', ?)""",
89
+ (user_id, tenant_like, f"-{int(days)} day"),
90
+ )
91
+ pending = await database.fetch_one(
92
+ """SELECT COALESCE(SUM(amount_usd), 0) AS usd, COUNT(*) AS n
93
+ FROM x402_payment_requests
94
+ WHERE (user_id = ? OR metadata LIKE ?) AND status = 'pending'""",
95
+ (user_id, tenant_like),
96
+ )
97
+ return {
98
+ "earned_usd": round(float(settled.get("usd") or 0), 6) if settled else 0.0,
99
+ "settled_payments": int(settled.get("n") or 0) if settled else 0,
100
+ "pending_invoices_usd": round(float(pending.get("usd") or 0), 6) if pending else 0.0,
101
+ "pending_invoices": int(pending.get("n") or 0) if pending else 0,
102
+ }
103
+ except Exception:
104
+ logger.debug("ledger: x402 inbound leg unavailable", exc_info=True)
105
+ return {"earned_usd": 0.0, "settled_payments": 0,
106
+ "pending_invoices_usd": 0.0, "pending_invoices": 0}
107
+
108
+
109
+ async def build_ledger(user_id: str, *, days: int = 7, db=None) -> Dict[str, Any]:
110
+ """The unified balance-sheet view for one tenant over a trailing window.
111
+
112
+ Tenant-scoped by contract: an empty user_id is refused (mirrors
113
+ MEMORY_REQUIRE_USER_ID) — anonymity must never widen into an all-tenants view.
114
+ """
115
+ if not user_id:
116
+ raise ValueError("accounting requires an authenticated tenant (empty user_id refused)")
117
+ days = max(1, int(days))
118
+ database = await _resolve_db(db)
119
+ costs = (await _costs_leg(database, user_id, days)) if database is not None else {
120
+ "llm_api_cost_usd": 0.0, "credits_spent": 0.0, "llm_calls": 0}
121
+ inbound = (await _inbound_leg(database, user_id, days)) if database is not None else {
122
+ "earned_usd": 0.0, "settled_payments": 0,
123
+ "pending_invoices_usd": 0.0, "pending_invoices": 0}
124
+ wallet = _wallet_leg(user_id, days)
125
+ total_out = costs["llm_api_cost_usd"] + wallet["wallet_spend_usd"]
126
+ return {
127
+ "user_id": user_id,
128
+ "window_days": days,
129
+ **costs,
130
+ **wallet,
131
+ **inbound,
132
+ "total_spend_usd": round(total_out, 6),
133
+ "net_usd": round(inbound["earned_usd"] - total_out, 6),
134
+ }
135
+
136
+
137
+ def format_ledger(ledger: Dict[str, Any]) -> str:
138
+ """Agent/owner-readable rendering of :func:`build_ledger` (pure)."""
139
+ return (
140
+ f"Ledger — last {ledger['window_days']}d (tenant {ledger['user_id'] or '(all)'})\n"
141
+ f" earned: ${ledger['earned_usd']:.4f} across {ledger['settled_payments']} settled payment(s)\n"
142
+ f" pending: ${ledger['pending_invoices_usd']:.4f} across {ledger['pending_invoices']} open invoice(s)\n"
143
+ f" spent: ${ledger['total_spend_usd']:.4f} total — "
144
+ f"${ledger['llm_api_cost_usd']:.4f} LLM/API ({ledger['llm_calls']} calls), "
145
+ f"${ledger['wallet_spend_usd']:.4f} wallet ({ledger['wallet_payments']} payment(s))\n"
146
+ f" credits: {ledger['credits_spent']:.2f} platform credits consumed\n"
147
+ f" net: ${ledger['net_usd']:+.4f}"
148
+ )