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,768 @@
1
+ """Centralized JSON extraction utilities for AutoV2.
2
+
3
+ This module provides consistent JSON extraction from model outputs
4
+ with standardized error messages and layered parsing strategies.
5
+ """
6
+
7
+ import json
8
+ import re
9
+ import logging
10
+ from typing import Any, Dict, Optional, List, Union, Tuple
11
+ from difflib import SequenceMatcher
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Common field name variations mapping
16
+ FIELD_SYNONYMS = {
17
+ 'text': ['message', 'msg', 'content', 'value', 'string', 'data'],
18
+ 'file_path': ['file_name', 'filename', 'path', 'filepath', 'file'],
19
+ 'content': ['text', 'data', 'body', 'contents', 'value'],
20
+ 'selector': ['element', 'target', 'locator', 'xpath', 'css'],
21
+ 'url': ['link', 'href', 'address', 'uri', 'website'],
22
+ 'query': ['search', 'q', 'search_query', 'search_term', 'term'],
23
+ 'parent_pattern': ['parent', 'parent_id', 'parent_text', 'under'],
24
+ 'pattern': ['text', 'match', 'search', 'regex', 'string'],
25
+ }
26
+
27
+ _CAMEL_BOUNDARY_1 = re.compile(r'(.)([A-Z][a-z]+)')
28
+ _CAMEL_BOUNDARY_2 = re.compile(r'([a-z0-9])([A-Z])')
29
+
30
+
31
+ def camel_to_snake(name: str) -> str:
32
+ """Convert camelCase / PascalCase to snake_case; idempotent for snake_case.
33
+
34
+ ``filePath`` → ``file_path``, ``maxResults`` → ``max_results``,
35
+ ``userID`` → ``user_id``, ``file_path`` → ``file_path`` (unchanged).
36
+ """
37
+ if not name:
38
+ return name
39
+ s1 = _CAMEL_BOUNDARY_1.sub(r'\1_\2', name)
40
+ return _CAMEL_BOUNDARY_2.sub(r'\1_\2', s1).lower()
41
+
42
+
43
+ def reconcile_field_names_to_model(
44
+ params: Dict[str, Any], valid_fields: set
45
+ ) -> Dict[str, Any]:
46
+ """Systematic camelCase→snake_case reconciliation against a model's fields (WS-1.3).
47
+
48
+ Replaces the hand-maintained per-field whack-a-mole for the *casing* class of
49
+ error: a key is renamed to its snake_case form ONLY when that snake_case form
50
+ is a real field of the target Pydantic param model and the camelCase one is
51
+ not. This is provider-agnostic (any model that emits ``filePath`` for a
52
+ ``file_path`` field is fixed) yet safe for tools whose params are genuinely
53
+ camelCase (e.g. some MCP servers) — there, the snake_case form is NOT a model
54
+ field, so the key is left untouched. Semantic renames (``message``→``text``)
55
+ still go through ``apply_action_field_corrections``.
56
+ """
57
+ if not params or not valid_fields:
58
+ return params
59
+ out: Dict[str, Any] = {}
60
+ for key, value in params.items():
61
+ if key in valid_fields:
62
+ out[key] = value
63
+ continue
64
+ snake = camel_to_snake(key)
65
+ if snake != key and snake in valid_fields and snake not in params:
66
+ out[snake] = value
67
+ logger.debug(f"Reconciled camelCase field '{key}' -> '{snake}'")
68
+ else:
69
+ out[key] = value
70
+ return out
71
+
72
+
73
+ def apply_action_field_corrections(action_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
74
+ """Apply action-specific field corrections with enhanced fuzzy matching.
75
+
76
+ This function handles common LLM mistakes by mapping incorrect field names
77
+ to their expected counterparts. It uses a comprehensive mapping table and
78
+ supports fuzzy matching for common variations.
79
+ """
80
+ if not params:
81
+ return params
82
+
83
+ corrected = {}
84
+
85
+ # Enhanced corrections with more variations
86
+ corrections = {
87
+ # Core action: done (Controller action, not TaskTool)
88
+ 'done': {
89
+ 'message': 'text', 'result': 'text', 'output': 'text',
90
+ 'completion_message': 'text', 'done_text': 'text', 'summary': 'text',
91
+ 'final_message': 'text', 'completion': 'text', 'status': 'text'
92
+ },
93
+ # Namespaced task todo actions (TaskTool actions)
94
+ # Legacy names (todo_add, etc.) are handled via action_aliases below
95
+ 'task_todo_add': {
96
+ 'message': 'text', 'task': 'text', 'todo': 'text', 'item': 'text',
97
+ 'description': 'text', 'content': 'text', 'todo_text': 'text',
98
+ 'task_text': 'text', 'todo_message': 'text', 'task_message': 'text'
99
+ },
100
+ 'task_todo_complete': {
101
+ 'index': 'id', 'todo_id': 'id', 'item_id': 'id', 'task_id': 'id'
102
+ },
103
+ 'write_file': {
104
+ 'file_name': 'file_path', 'filename': 'file_path', 'path': 'file_path',
105
+ 'name': 'file_path', 'file': 'file_path', 'filepath': 'file_path',
106
+ 'data': 'content', 'text': 'content', 'file_content': 'content',
107
+ 'body': 'content', 'contents': 'content', 'file_data': 'content'
108
+ },
109
+ 'read_file': {
110
+ 'file_name': 'file_path', 'filename': 'file_path', 'path': 'file_path',
111
+ 'name': 'file_path', 'file': 'file_path', 'filepath': 'file_path'
112
+ },
113
+ 'click_element': {
114
+ 'element': 'index', 'target': 'index', 'locator': 'index',
115
+ 'element_index': 'index', 'click_target': 'index',
116
+ 'selector': 'index',
117
+ },
118
+ 'click': { # Alias for click_element
119
+ 'element': 'index', 'target': 'index', 'locator': 'index',
120
+ 'element_index': 'index', 'selector': 'index',
121
+ },
122
+ 'search_google': {
123
+ 'search': 'query', 'q': 'query', 'search_term': 'query', 'term': 'query',
124
+ 'search_query': 'query', 'google_query': 'query', 'text': 'query',
125
+ 'search_text': 'query', 'keyword': 'query', 'keywords': 'query'
126
+ },
127
+ 'navigate': {
128
+ 'address': 'url', 'link': 'url', 'website': 'url', 'site': 'url',
129
+ 'destination': 'url', 'uri': 'url', 'navigate_to': 'url', 'goto': 'url'
130
+ },
131
+ 'input_text': {
132
+ 'element': 'index', 'target': 'index', 'element_index': 'index',
133
+ 'locator': 'index', 'selector': 'index',
134
+ 'input': 'text', 'value': 'text', 'content': 'text',
135
+ 'input_value': 'text', 'type_text': 'text', 'string': 'text'
136
+ },
137
+ 'type_text': {
138
+ 'input': 'text', 'content': 'text', 'value': 'text', 'string': 'text',
139
+ 'type_value': 'text', 'input_text': 'text', 'enter_text': 'text'
140
+ }
141
+ }
142
+
143
+ # Also handle action name variations (legacy -> canonical)
144
+ action_aliases = {
145
+ 'click': 'click_element',
146
+ 'goto': 'navigate',
147
+ 'go_to': 'navigate',
148
+ 'open': 'navigate',
149
+ 'type': 'type_text',
150
+ 'input': 'type_text',
151
+ 'search': 'search_google',
152
+ 'google': 'search_google',
153
+ # Todo aliases - map legacy names to namespaced names
154
+ 'todo_add': 'task_todo_add',
155
+ 'add_todo': 'task_todo_add',
156
+ 'create_todo': 'task_todo_add',
157
+ 'todo_list': 'task_todo_list',
158
+ 'todo_complete': 'task_todo_complete',
159
+ 'todo_progress': 'task_todo_progress',
160
+ 'todo_next': 'task_todo_next',
161
+ 'finish': 'done',
162
+ 'complete': 'done',
163
+ 'save_file': 'write_file',
164
+ 'create_file': 'write_file',
165
+ 'open_file': 'read_file',
166
+ 'load_file': 'read_file'
167
+ }
168
+
169
+ # Extract base name from namespaced action (e.g., "browser_click_element" -> "click_element")
170
+ base_action_name = action_name
171
+ if '_' in action_name:
172
+ parts = action_name.split('_', 1)
173
+ if len(parts) == 2 and (parts[1] in corrections or parts[1] in action_aliases):
174
+ base_action_name = parts[1]
175
+ logger.debug(f"Extracted base action: '{action_name}' -> '{base_action_name}'")
176
+
177
+ # Normalize action name if it's an alias
178
+ normalized_action = action_aliases.get(base_action_name.lower(), base_action_name)
179
+
180
+ # Try multiple lookup strategies
181
+ action_corrections = (
182
+ corrections.get(action_name, {}) or
183
+ corrections.get(normalized_action, {}) or
184
+ corrections.get(base_action_name, {})
185
+ )
186
+
187
+ # Process parameters with case-insensitive matching, in TWO passes so a
188
+ # genuine field always wins over a synonym regardless of dict ordering
189
+ # (e.g. done({'text': 'real', 'summary': 'short'}) must never let the
190
+ # 'summary'->'text' rename clobber the real 'text').
191
+ renames: Dict[str, str] = {} # original field -> target field
192
+ for field, value in params.items():
193
+ field_lower = field.lower()
194
+
195
+ # Check if this field needs correction
196
+ correct_field = action_corrections.get(field_lower, field)
197
+
198
+ # If no direct match, try to find a close match — but on a '_'-delimited
199
+ # TOKEN boundary, not a raw substring. A substring test against short keys
200
+ # ('q'->'query', 'text'->'content') mis-maps unrelated fields ('quality',
201
+ # 'context') and clobbers the real one. Require the shorter name to be a
202
+ # whole token of the longer.
203
+ if correct_field == field and action_corrections:
204
+ field_tokens = field_lower.split('_')
205
+ for wrong_field, right_field in action_corrections.items():
206
+ wf = wrong_field.lower()
207
+ if wf in field_tokens or field_lower in wf.split('_'):
208
+ correct_field = right_field
209
+ break
210
+
211
+ if correct_field == field:
212
+ # Pass 1: genuine/uncorrected fields land first, so they can never
213
+ # be clobbered by a later (or earlier) synonym rename.
214
+ corrected[field] = value
215
+ else:
216
+ renames[field] = correct_field
217
+
218
+ # Pass 2: apply renames only into slots absent from BOTH the original
219
+ # params and what we've built so far. A redundant synonym is dropped
220
+ # (first rename wins if two synonyms target the same field).
221
+ for field, correct_field in renames.items():
222
+ if correct_field in corrected or correct_field in params:
223
+ logger.debug(
224
+ f"Dropped redundant field: '{field}' (target '{correct_field}' "
225
+ f"already supplied) for '{action_name}'"
226
+ )
227
+ continue
228
+ corrected[correct_field] = params[field]
229
+ logger.debug(f"Corrected field: '{field}' -> '{correct_field}' for '{action_name}'")
230
+
231
+ return corrected
232
+
233
+ def preprocess_action_data(action: Dict[str, Any]) -> Tuple[str, Any]:
234
+ """Extract action name and params from action dict.
235
+
236
+ Args:
237
+ action: Action dictionary with single key-value pair
238
+
239
+ Returns:
240
+ Tuple of (action_name, action_params)
241
+ """
242
+ if not action or not isinstance(action, dict):
243
+ return "unknown", {}
244
+
245
+ action_name = list(action.keys())[0] if action else "unknown"
246
+ action_params = action.get(action_name, {})
247
+ return action_name, action_params
248
+
249
+
250
+ def _extract_balanced_json(text: str) -> Optional[Dict[str, Any]]:
251
+ """Extract a balanced JSON object from text using brace counting.
252
+
253
+ Args:
254
+ text: Text containing a JSON object
255
+
256
+ Returns:
257
+ Parsed JSON dict or None if extraction fails
258
+ """
259
+ start = text.find('{')
260
+ if start < 0:
261
+ return None
262
+
263
+ brace_count = 0
264
+ end = start
265
+ for i, char in enumerate(text[start:], start):
266
+ if char == '{':
267
+ brace_count += 1
268
+ elif char == '}':
269
+ brace_count -= 1
270
+ if brace_count == 0:
271
+ end = i + 1
272
+ break
273
+
274
+ if brace_count != 0:
275
+ return None
276
+
277
+ try:
278
+ json_str = text[start:end]
279
+ return json.loads(json_str)
280
+ except (json.JSONDecodeError, ValueError):
281
+ return None
282
+
283
+
284
+ def _parse_function_args(func_args_str: str) -> Dict[str, Any]:
285
+ """Parse function arguments from a string like 'arg1="val1", arg2=123'.
286
+
287
+ Handles both quoted strings and unquoted values (int, float, bool, null).
288
+
289
+ Args:
290
+ func_args_str: Arguments string from function call
291
+
292
+ Returns:
293
+ Dictionary of parsed arguments
294
+ """
295
+ func_args = {}
296
+
297
+ if not func_args_str or not func_args_str.strip():
298
+ return func_args
299
+
300
+ # First match quoted values: key="value" or key='value'
301
+ quoted_pattern = r'(\w+)\s*=\s*["\']([^"\']*)["\']'
302
+ for arg_match in re.finditer(quoted_pattern, func_args_str):
303
+ func_args[arg_match.group(1)] = arg_match.group(2)
304
+
305
+ # Then match unquoted values: key=123, key=true, key=false, key=null
306
+ unquoted_pattern = r'(\w+)\s*=\s*([0-9]+(?:\.[0-9]+)?|true|false|null)'
307
+ for arg_match in re.finditer(unquoted_pattern, func_args_str, re.IGNORECASE):
308
+ arg_name = arg_match.group(1)
309
+ if arg_name not in func_args: # Don't overwrite quoted values
310
+ val_str = arg_match.group(2).lower()
311
+ if val_str == 'true':
312
+ func_args[arg_name] = True
313
+ elif val_str == 'false':
314
+ func_args[arg_name] = False
315
+ elif val_str == 'null':
316
+ func_args[arg_name] = None
317
+ elif '.' in arg_match.group(2):
318
+ func_args[arg_name] = float(arg_match.group(2))
319
+ else:
320
+ func_args[arg_name] = int(arg_match.group(2))
321
+
322
+ return func_args
323
+
324
+
325
+ def _parse_function_calls(func_section: str) -> List[Dict[str, Any]]:
326
+ """Parse all function calls from a Functions: section.
327
+
328
+ Args:
329
+ func_section: Text after "Functions:" containing function calls
330
+
331
+ Returns:
332
+ List of action dicts like [{func_name: {args}}]
333
+ """
334
+ func_call_pattern = r'(\w+)\(([^)]*)\)'
335
+ all_actions = []
336
+
337
+ for func_match in re.finditer(func_call_pattern, func_section):
338
+ func_name = func_match.group(1)
339
+ func_args_str = func_match.group(2)
340
+ func_args = _parse_function_args(func_args_str)
341
+ all_actions.append({func_name: func_args})
342
+ logger.debug(f"Parsed function '{func_name}' with args: {func_args}")
343
+
344
+ return all_actions
345
+
346
+
347
+ def _build_converted_response(
348
+ all_actions: List[Dict[str, Any]],
349
+ current_state_data: Optional[Dict[str, Any]] = None
350
+ ) -> Dict[str, Any]:
351
+ """Build a converted response dict from actions and optional current_state.
352
+
353
+ Args:
354
+ all_actions: List of action dicts
355
+ current_state_data: Optional parsed current_state JSON
356
+
357
+ Returns:
358
+ Response dict with 'action' and 'current_state' fields
359
+ """
360
+ converted = {"action": all_actions}
361
+
362
+ if current_state_data:
363
+ if "current_state" in current_state_data:
364
+ converted["current_state"] = current_state_data["current_state"]
365
+ else:
366
+ converted["current_state"] = current_state_data
367
+ else:
368
+ # Create minimal current_state for schema validation
369
+ first_action_name = list(all_actions[0].keys())[0] if all_actions else "unknown"
370
+ converted["current_state"] = {
371
+ "memory": "Action from function call format",
372
+ "evaluation_previous_goal": "Unknown",
373
+ "next_goal": first_action_name
374
+ }
375
+
376
+ return converted
377
+
378
+ def extract_json_from_model_output(text: str) -> Dict[str, Any]:
379
+ """Extract JSON from model output with consistent error handling.
380
+
381
+ Tries multiple extraction strategies:
382
+ 1. Strip code fences and think tags
383
+ 2. Try direct JSON parsing
384
+ 3. Extract from various block formats
385
+ 4. Find JSON-like structures
386
+ 5. Validate required keys
387
+
388
+ Args:
389
+ text: Model output text potentially containing JSON
390
+
391
+ Returns:
392
+ Parsed JSON as dictionary
393
+
394
+ Raises:
395
+ ValueError with standardized messages:
396
+ - "Empty response from model" for None/empty input
397
+ - "Could not parse response" for extraction failures
398
+ """
399
+ if not text:
400
+ raise ValueError("Empty response from model")
401
+
402
+ # Import config for validation - single source of truth
403
+ from agents.task.robust_parse_config import RobustParseConfig as Config
404
+
405
+ # Debug logging - only at DEBUG level to prevent log spam
406
+ logger.debug(f"extract_json_from_model_output input (first 500 chars): {text[:500]}")
407
+
408
+ original_text = text
409
+
410
+ # FIX 3a: Strip code fences and think tags first
411
+ if Config.STRIP_CODE_FENCES:
412
+ # Remove code fences - properly handle content between them
413
+ text = re.sub(r'```[a-z]*\n?', '', text, flags=re.IGNORECASE)
414
+ text = re.sub(r'```\s*$', '', text)
415
+ # Remove triple quotes
416
+ text = re.sub(r'"""[a-z]*\n?', '', text, flags=re.IGNORECASE)
417
+ text = re.sub(r'"""', '', text)
418
+
419
+ if Config.STRIP_THINK_TAGS:
420
+ # Remove think tags and their content
421
+ text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL | re.IGNORECASE)
422
+ text = re.sub(r'<thinking>.*?</thinking>', '', text, flags=re.DOTALL | re.IGNORECASE)
423
+
424
+ # Try direct parsing first (after stripping)
425
+ try:
426
+ parsed = json.loads(text.strip())
427
+ # FIX 3d: Validate required keys
428
+ if Config.REQUIRE_SCHEMA_KEYS:
429
+ if Config.validate_json_candidate(json.dumps(parsed)):
430
+ logger.debug(f"Direct JSON parse succeeded with validation")
431
+ return parsed
432
+ # Continue to other strategies if validation fails
433
+ logger.debug(f"Direct JSON parse succeeded but validation failed")
434
+ else:
435
+ logger.debug(f"Direct JSON parse succeeded (no validation)")
436
+ return parsed
437
+ except (json.JSONDecodeError, TypeError) as e:
438
+ logger.debug(f"Direct JSON parse failed: {e}")
439
+ pass
440
+
441
+ # T-04: Enhanced pattern detection for function-calling format
442
+ # Try to detect OpenAI function call format first
443
+ function_call_pattern = r'\{\s*"function"\s*:\s*"[^"]+"\s*,\s*"args"\s*:\s*\{[^}]*\}\s*\}'
444
+ func_match = re.search(function_call_pattern, original_text)
445
+ if func_match:
446
+ try:
447
+ func_call = json.loads(func_match.group())
448
+ # Convert to standard action format
449
+ if "function" in func_call and "args" in func_call:
450
+ converted = {
451
+ "action": [{func_call["function"]: func_call.get("args", {})}]
452
+ }
453
+ # Add a minimal current_state if required
454
+ if Config.REQUIRE_SCHEMA_KEYS:
455
+ converted["current_state"] = {
456
+ "page_summary": "Action from function call",
457
+ "evaluation_previous_goal": "Unknown",
458
+ "memory": "",
459
+ "next_goal": func_call["function"]
460
+ }
461
+ logger.debug(f"Converted OpenAI function-call format to standard action")
462
+ return converted
463
+ except (json.JSONDecodeError, TypeError):
464
+ pass
465
+
466
+ # T-05: Handle "Text: {...} Functions: func_name(...)" format from OpenRouter/some models
467
+ # Format: Text: {"current_state": {...}} \n Functions: func_name(arg1="val1", arg2="val2")
468
+ # CRITICAL FIX (Dec 2025): Refactored to use helper functions
469
+ if 'Text:' in original_text and 'Functions:' in original_text:
470
+ try:
471
+ text_idx = original_text.find('Text:')
472
+ func_idx = original_text.find('Functions:')
473
+ if text_idx >= 0 and func_idx > text_idx:
474
+ # Extract JSON from between Text: and Functions:
475
+ json_section = original_text[text_idx + 5:func_idx].strip()
476
+ current_state_data = _extract_balanced_json(json_section)
477
+
478
+ # Parse all function calls
479
+ func_section = original_text[func_idx + 10:].strip()
480
+ all_actions = _parse_function_calls(func_section)
481
+
482
+ if all_actions:
483
+ converted = _build_converted_response(all_actions, current_state_data)
484
+ logger.debug(f"Converted Text/Functions format to {len(all_actions)} action(s)")
485
+ return converted
486
+ except (json.JSONDecodeError, TypeError, AttributeError, ValueError) as e:
487
+ logger.debug(f"Failed to parse Text/Functions format: {e}")
488
+
489
+ # T-05b: Handle "Functions:" only format (without "Text:" prefix)
490
+ # Some models return just "Functions: func_name(args)" without any JSON brain state
491
+ # CRITICAL FIX (Dec 2025): Refactored to use helper functions
492
+ elif 'Functions:' in original_text:
493
+ try:
494
+ func_idx = original_text.find('Functions:')
495
+ func_section = original_text[func_idx + 10:].strip()
496
+
497
+ # Try to extract JSON from before Functions: section
498
+ json_before = original_text[:func_idx].strip()
499
+ current_state_data = _extract_balanced_json(json_before) if json_before else None
500
+
501
+ # Parse all function calls
502
+ all_actions = _parse_function_calls(func_section)
503
+
504
+ if all_actions:
505
+ converted = _build_converted_response(all_actions, current_state_data)
506
+ logger.debug(f"Converted Functions-only format to {len(all_actions)} action(s)")
507
+ return converted
508
+ except Exception as e:
509
+ logger.debug(f"Failed to parse Functions-only format: {e}")
510
+
511
+ # FIX 3b: Try extracting from blocks - prefer first valid JSON
512
+ patterns = [
513
+ r'\{[^{}]*"current_state"[^{}]*"action"[^{}]*\}', # Prioritize JSON with required keys
514
+ r'\{.*?"current_state".*?"action".*?\}', # Larger JSON with required keys
515
+ r'\{[^{}]*\}', # Simple JSON objects
516
+ r'\{.*?\}', # Any JSON object (non-greedy)
517
+ ]
518
+
519
+ for pattern in patterns:
520
+ matches = re.findall(pattern, original_text, re.DOTALL)
521
+ for match in matches:
522
+ try:
523
+ cleaned = match.strip()
524
+ if cleaned:
525
+ parsed = json.loads(cleaned)
526
+ # FIX 3d: Validate and return first valid JSON
527
+ if Config.REQUIRE_SCHEMA_KEYS:
528
+ if Config.validate_json_candidate(json.dumps(parsed)):
529
+ logger.debug(f"Successfully extracted JSON with required keys from pattern: {pattern[:30]}...")
530
+ return parsed
531
+ else:
532
+ return parsed
533
+ except (json.JSONDecodeError, TypeError):
534
+ continue
535
+
536
+ # Last resort: find anything that looks like JSON
537
+ # Look for content between first { and last }
538
+ start = original_text.find('{')
539
+ end = original_text.rfind('}')
540
+
541
+ if start >= 0 and end > start:
542
+ potential_json = original_text[start:end+1]
543
+ try:
544
+ parsed = json.loads(potential_json)
545
+ # FIX 3d: Final validation
546
+ if Config.REQUIRE_SCHEMA_KEYS:
547
+ if Config.validate_json_candidate(json.dumps(parsed)):
548
+ logger.debug("Successfully extracted JSON from bracket positions")
549
+ return parsed
550
+ else:
551
+ # Log what keys are missing for debugging
552
+ missing_keys = [k for k in Config.REQUIRED_KEYS if k not in parsed]
553
+ logger.warning(f"JSON found but missing required keys: {missing_keys}")
554
+ else:
555
+ return parsed
556
+ except (json.JSONDecodeError, TypeError):
557
+ pass
558
+
559
+ # FIX 3c: Keep standardized error message
560
+ raise ValueError("Could not parse response")
561
+
562
+
563
+ def validate_json_response(response: Any, required_fields: Optional[list] = None) -> Dict[str, Any]:
564
+ """Validate and extract JSON from a model response.
565
+
566
+ Args:
567
+ response: Response object or string from model
568
+ required_fields: Optional list of required field names
569
+
570
+ Returns:
571
+ Validated JSON dictionary
572
+
573
+ Raises:
574
+ ValueError: If validation fails with standardized message
575
+ """
576
+ # Extract content from response object if needed
577
+ content = response
578
+ if hasattr(response, 'content'):
579
+ content = response.content
580
+ elif hasattr(response, 'text'):
581
+ content = response.text
582
+
583
+ # Convert to string if needed
584
+ if not isinstance(content, str):
585
+ content = str(content)
586
+
587
+ # Extract JSON
588
+ try:
589
+ result = extract_json_from_model_output(content)
590
+ except ValueError:
591
+ # Re-raise with standardized message
592
+ raise
593
+
594
+ # Validate required fields if specified
595
+ if required_fields:
596
+ missing = [f for f in required_fields if f not in result]
597
+ if missing:
598
+ raise ValueError(f"Could not parse response: missing required fields {missing}")
599
+
600
+ return result
601
+
602
+
603
+
604
+
605
+
606
+ def normalize_action_schema(data: Union[Dict[str, Any], List, Any]) -> Union[Dict[str, Any], List, Any]:
607
+ """Normalize field names in LLM response to handle provider inconsistencies.
608
+
609
+ RESPONSIBILITY CONTRACT:
610
+ - This function ONLY handles action payload field name transformations
611
+ - It does NOT handle tool_call format differences (that's ToolCallBuilder.normalize_tool_call's job)
612
+ - Keep these responsibilities separate to avoid duplication and confusion
613
+
614
+ This function centralizes field name normalization logic that was previously
615
+ duplicated in Agent._normalize_field_names. Fixes common issues where LLMs
616
+ use incorrect field names based on training patterns.
617
+
618
+ Common fixes:
619
+ - Function-calling format conversion: {"function": "name", "args": {...}} -> {"name": {...}}
620
+ - Field name mappings: done.message -> done.text, todo_add.message -> todo_add.text
621
+ - write_file.file_name -> write_file.file_path
622
+
623
+ Args:
624
+ data: Raw data from LLM response (dict, list, or primitive)
625
+
626
+ Returns:
627
+ Normalized data with corrected field names
628
+ """
629
+ if isinstance(data, dict):
630
+ normalized_data = {}
631
+ for key, value in data.items():
632
+ # Recursively normalize nested objects
633
+ normalized_value = normalize_action_schema(value)
634
+
635
+ # Handle specific field name mappings for actions
636
+ if key == "action" and isinstance(normalized_value, list):
637
+ # Process each action in the list
638
+ fixed_actions = []
639
+ for action in normalized_value:
640
+ if isinstance(action, dict):
641
+ # Handle function-calling format {"function": "name", "args": {...}}
642
+ if "function" in action and "args" in action:
643
+ # Convert from function-calling format to ActionModel format
644
+ func_name = action["function"]
645
+ func_args = action.get("args", {})
646
+
647
+ # Apply action-specific field corrections
648
+ func_args = apply_action_field_corrections(func_name, func_args)
649
+
650
+ fixed_action = {func_name: func_args}
651
+ logger.debug(f"Converted function-calling format: {func_name}")
652
+ else:
653
+ # Standard format, process normally
654
+ # Preprocess the action to handle various formats
655
+ action_name, action_params = preprocess_action_data(action)
656
+
657
+ if isinstance(action_params, dict):
658
+ # Apply field corrections
659
+ action_params = apply_action_field_corrections(action_name, action_params)
660
+
661
+ fixed_action = {action_name: action_params}
662
+ fixed_actions.append(fixed_action)
663
+ else:
664
+ # Non-dict action, keep as is
665
+ fixed_actions.append(action)
666
+ normalized_data[key] = fixed_actions
667
+ else:
668
+ # Regular field, no special processing
669
+ normalized_data[key] = normalized_value
670
+
671
+ return normalized_data
672
+ elif isinstance(data, list):
673
+ return [normalize_action_schema(item) for item in data]
674
+ else:
675
+ return data
676
+
677
+
678
+ #: Keys that mark a JSON object as agent brain-state telemetry rather than an
679
+ #: arbitrary result. A ``current_state`` wrapper or >=2 of these ⇒ brain-state.
680
+ _BRAIN_STATE_KEYS = frozenset({
681
+ 'current_state', 'next_goal', 'evaluation_previous_goal', 'page_summary',
682
+ 'memory', 'reasoning', 'macro_goal', 'subgoal', 'immediate_step',
683
+ })
684
+
685
+
686
+ def is_brain_state_content(content: Optional[str]) -> bool:
687
+ """True when *content* is (purely) an agent brain-state JSON object.
688
+
689
+ POLYROB's system prompt instructs every model to emit its brain state as a
690
+ ``{"current_state": {...}}`` JSON object in the text-content field every
691
+ turn. That content is internal telemetry, NOT the agent's user-facing voice
692
+ — it must never be streamed to output consumers (CLI / API / WebView), or it
693
+ surfaces as a raw JSON dump on a tool-free planning turn for any streaming
694
+ provider.
695
+
696
+ Conservative by design: returns True only when the trimmed content parses as
697
+ a JSON object carrying a ``current_state`` wrapper or >=2 brain keys.
698
+ Genuine prose — and partial token chunks that don't parse — return False and
699
+ stream normally.
700
+ """
701
+ if not content:
702
+ return False
703
+ flat = content.strip()
704
+ if not (flat.startswith('{') and flat.endswith('}')):
705
+ return False
706
+ try:
707
+ obj = json.loads(flat)
708
+ except (ValueError, TypeError):
709
+ return False
710
+ if not isinstance(obj, dict):
711
+ return False
712
+ if isinstance(obj.get('current_state'), dict):
713
+ return True
714
+ return sum(1 for k in obj if k in _BRAIN_STATE_KEYS) >= 2
715
+
716
+
717
+ def extract_brain_state_from_json(content: str) -> Dict[str, Any]:
718
+ """Extract brain state from LLM content in JSON format.
719
+
720
+ This function handles brain state extraction for native tools mode where
721
+ the LLM provides brain state as JSON in the text content field.
722
+
723
+ Supports both formats:
724
+ - Nested: {"current_state": {"memory": "...", ...}}
725
+ - Top-level: {"memory": "...", "next_goal": "...", ...}
726
+
727
+ Args:
728
+ content: Text content from LLM response
729
+
730
+ Returns:
731
+ Dictionary with brain state fields (memory, evaluation_previous_goal, etc.)
732
+
733
+ Raises:
734
+ ValueError: If content cannot be parsed as JSON
735
+ """
736
+ if not content or not content.strip():
737
+ raise ValueError("Empty content - cannot extract brain state")
738
+
739
+ # Use existing JSON extraction logic
740
+ brain_json = extract_json_from_model_output(content)
741
+
742
+ # Check if brain state is nested under 'current_state' (new format)
743
+ # or at top level (old format)
744
+ if 'current_state' in brain_json and isinstance(brain_json['current_state'], dict):
745
+ # New nested format - extract from current_state
746
+ source = brain_json['current_state']
747
+ logger.debug("Extracting brain state from nested 'current_state' field")
748
+ else:
749
+ # Old top-level format - use directly
750
+ source = brain_json
751
+ logger.debug("Extracting brain state from top-level fields")
752
+
753
+ # Map fields to AgentBrain schema
754
+ brain_fields = {
755
+ 'page_summary': source.get('page_summary', ''),
756
+ 'memory': source.get('memory', ''),
757
+ 'evaluation_previous_goal': source.get('evaluation_previous_goal', 'Unknown'),
758
+ 'next_goal': source.get('next_goal', ''),
759
+ 'reasoning': source.get('reasoning', ''),
760
+ 'phase': source.get('phase'),
761
+ 'macro_goal': source.get('macro_goal'),
762
+ 'subgoal': source.get('subgoal'),
763
+ 'immediate_step': source.get('immediate_step')
764
+ }
765
+
766
+ logger.debug(f"Extracted brain state: memory={brain_fields.get('memory', '')[:100]}")
767
+
768
+ return brain_fields