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,303 @@
1
+ """Output-validation mixin (roadmap P9; code-motion from llm_runner.py).
2
+
3
+ Post-LLM output validation + the thin next_action / _get_llm_parameters helpers,
4
+ split out of LLMRunnerMixin. Agent composes OutputValidationMixin; call sites
5
+ (service.py _validate_output, step loop) unchanged via MRO.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, List, Optional
10
+
11
+ from pydantic import BaseModel, ConfigDict
12
+
13
+ from agents.task.agent.views import AgentOutput, ActionResult
14
+ from agents.task.agent.prompts import AgentMessagePrompt
15
+ from agents.task.utils import time_execution_async
16
+ from modules.llm.messages import BaseMessage, HumanMessage, SystemMessage
17
+
18
+ # P0-6: hard bound on the judge call so a slow/hung judge can never stall the run
19
+ # loop past this (fail-open on timeout). Overridable for tests via monkeypatch.
20
+ VALIDATION_JUDGE_TIMEOUT_SEC: float = 60.0
21
+
22
+
23
+ class _Verdict:
24
+ """Minimal is_valid/reason carrier for the tolerant judge parser (P0-6).
25
+
26
+ The judge's structured path returns a Pydantic ``ValidationResult`` (defined
27
+ locally in ``_validate_output``); the manual path routes through this instead
28
+ so the tolerant parser can live at module scope and be unit-tested directly.
29
+ """
30
+
31
+ __slots__ = ("is_valid", "reason")
32
+
33
+ def __init__(self, is_valid: bool, reason: str):
34
+ self.is_valid = is_valid
35
+ self.reason = reason
36
+
37
+
38
+ def _parse_validation_verdict(text: str) -> "_Verdict":
39
+ """Tolerantly extract an is_valid/reason verdict from a judge reply.
40
+
41
+ Mirrors the ladder in ``agents/task/goals/completion_judge.py``: try the
42
+ shared JSON extractor first; on a miss (a judge that narrates prose instead
43
+ of JSON — the documented prod failure mode) fall back to a keyword scan for
44
+ an explicit ``is_valid: false``. Defaults to VALID (pass) when the reply
45
+ carries no clear negative verdict, keeping the judge fail-open: it only
46
+ fails a turn on a verdict it can actually read as ``false``.
47
+ """
48
+ import re
49
+ from agents.task.utils_json import extract_json_from_model_output
50
+
51
+ try:
52
+ data = extract_json_from_model_output(text) or {}
53
+ if isinstance(data, dict) and 'is_valid' in data:
54
+ return _Verdict(
55
+ is_valid=bool(data.get('is_valid', True)),
56
+ reason=str(data.get('reason') or 'No reason provided'),
57
+ )
58
+ except Exception:
59
+ pass
60
+
61
+ # Keyword fallback: an explicit false verdict anywhere in the prose fails;
62
+ # anything else passes (fail-open). Match `"is_valid": false`, `is_valid=false`,
63
+ # `is_valid false`, tolerant of quotes/whitespace/casing.
64
+ if re.search(r'is_valid["\']?\s*[:=]?\s*false', text, re.IGNORECASE):
65
+ return _Verdict(is_valid=False, reason=text.strip()[:300] or 'Judge returned an invalid verdict')
66
+ return _Verdict(is_valid=True, reason='No clear negative verdict; passing (fail-open)')
67
+
68
+
69
+ class OutputValidationMixin:
70
+ """LLM output validation + next_action wrapper for Agent."""
71
+
72
+ def _get_llm_parameters(self) -> dict:
73
+ """Extract LLM parameters for logging purposes - delegates to MessageManager"""
74
+ return self.message_manager.get_llm_parameters()
75
+
76
+ async def _validate_output(self) -> bool:
77
+ """Validate the output of the last action is what the user wanted"""
78
+ browser_context = await self.get_browser_context()
79
+ has_browser = bool(browser_context and browser_context.session)
80
+
81
+ # A2: task-type-neutral base prompt — this judge now runs for chat/coding/MCP
82
+ # tasks too (the browser-only early-return was removed in T16), so the framing
83
+ # must not assume a browser. Browser-specific guidance is appended ONLY when a
84
+ # live browser session is present.
85
+ system_msg = (
86
+ f'You are a validator of an AI agent. '
87
+ f'Validate if the output of the last action is what the user wanted and if the task is completed. '
88
+ f'If the task is unclearly defined, you can let it pass. But if something is missing or the output does not satisfy the request, dont let it pass. '
89
+ )
90
+ if has_browser:
91
+ system_msg += (
92
+ f'The agent interacts with a browser; try to understand the page and help with '
93
+ f'suggestions like scroll, do x, ... to get the solution right. '
94
+ )
95
+ system_msg += (
96
+ f'Task to validate: {self.task}. Return a JSON object with 2 keys: is_valid and reason. '
97
+ f'is_valid is a boolean that indicates if the output is correct. '
98
+ f'reason is a string that explains why it is valid or not.'
99
+ f' example: {{"is_valid": false, "reason": "The user wanted to search for "cat photos", but the agent searched for "dog photos" instead."}}'
100
+ )
101
+
102
+ if has_browser:
103
+ state = await browser_context.get_state()
104
+ content = AgentMessagePrompt(
105
+ state=state,
106
+ result=self._last_result,
107
+ include_attributes=self.include_attributes,
108
+ max_error_length=self.max_error_length,
109
+ )
110
+ msg = [SystemMessage(content=system_msg), content.get_user_message(self.use_vision)]
111
+ else:
112
+ # CO-F1: no live browser session (chat/coding/MCP/non-browser tasks) — judge
113
+ # the final answer as text instead of skipping validation. Previously this
114
+ # branch unconditionally returned True ("can't validate"), which made
115
+ # validate_output a no-op for every non-browser task.
116
+ result_text = "\n".join(
117
+ (r.extracted_content or r.error or "")
118
+ for r in (self._last_result or [])
119
+ if (r.extracted_content or r.error)
120
+ ) or "(no output produced)"
121
+ msg = [
122
+ SystemMessage(content=system_msg),
123
+ HumanMessage(content=f"Agent's final output:\n{result_text}"),
124
+ ]
125
+
126
+ class ValidationResult(BaseModel):
127
+ """
128
+ Validation results.
129
+ """
130
+ is_valid: bool
131
+ reason: str
132
+
133
+ # FIXED: Add model_config to prevent additionalProperties schema error
134
+ model_config = ConfigDict(extra='forbid')
135
+
136
+ # CO-F1: lazily provision the cheap 'judge' aux model on first use, only when
137
+ # validate_output is actually on (mirrors BackgroundReviewMixin's
138
+ # provision-at-point-of-use pattern) — construction.py no longer provisions it
139
+ # unconditionally. Fail-open to the main model; cached on self._judge_llm so
140
+ # repeated validations in the same session don't re-resolve it.
141
+ if getattr(self, "validate_output", False) and getattr(self, "_judge_llm", None) is None:
142
+ try:
143
+ # P2-9: await the async provisioning so building the judge client yields
144
+ # the loop instead of blocking it (run_coroutine_sync) for up to 60s.
145
+ self._judge_llm = await self._provision_aux_llm_async("judge")
146
+ except Exception as e:
147
+ self.logger.debug(f"judge aux model provisioning skipped: {e}")
148
+ self._judge_llm = None
149
+
150
+ # UP-10 2.1: route validation through the optional cheap 'judge' aux model when
151
+ # provisioned (AUX_MODEL_JUDGE / AUX_AUTO); fail-open to the main model. Default
152
+ # (no judge configured) is byte-identical to the legacy self.llm path.
153
+ judge_llm = getattr(self, "_judge_llm", None) or self.llm
154
+
155
+ # Structured output if the adapter supports it; native adapters generally
156
+ # don't (browser-use legacy), so degrade to a plain call + JSON parse rather
157
+ # than raising AttributeError. (native structured-output fallback, 2026-06.)
158
+ validator = None
159
+ if hasattr(judge_llm, 'with_structured_output'):
160
+ try:
161
+ validator = judge_llm.with_structured_output(ValidationResult, include_raw=True)
162
+ except Exception as schema_error:
163
+ self.logger.debug(f"Structured output unavailable for ValidationResult, using manual parse: {schema_error}")
164
+ validator = None
165
+
166
+ # P0-6: the judge is fail-OPEN end-to-end. An unparseable / erroring /
167
+ # hanging judge reply must NOT raise out of the run loop and kill the turn
168
+ # after the agent already called done() (the previous behaviour: a prose
169
+ # judge reply let extract_json_from_model_output raise ValueError straight
170
+ # through the bare `await self._validate_output()` at run_loop.py:459).
171
+ # Fail-open covers judge FAILURES only — a well-formed {"is_valid": false}
172
+ # verdict still fails validation (that's the feature). Mirrors the tolerant
173
+ # ladder the goal judge already uses (agents/task/goals/completion_judge.py).
174
+ import asyncio
175
+ import time as _time
176
+ _t0 = _time.time()
177
+ _resp_for_meter = None
178
+ try:
179
+ if validator is not None:
180
+ response: dict[str, Any] = await asyncio.wait_for(
181
+ validator.ainvoke(msg), timeout=VALIDATION_JUDGE_TIMEOUT_SEC,
182
+ ) # type: ignore
183
+ _resp_for_meter = response # extract_token_usage unwraps {'raw': ...}
184
+ raw_parsed = response.get('parsed') if isinstance(response, dict) else None
185
+ if raw_parsed is None:
186
+ # LOW-1: structured output can yield {'parsed': None, 'raw': ...}
187
+ # on a schema-mismatch — treat as a parse failure → fail-open.
188
+ self.logger.warning(
189
+ '⚠️ Judge structured output returned no parsed verdict; '
190
+ 'passing (fail-open).'
191
+ )
192
+ return True
193
+ parsed = raw_parsed
194
+ else:
195
+ raw = await asyncio.wait_for(
196
+ judge_llm.ainvoke(msg), timeout=VALIDATION_JUDGE_TIMEOUT_SEC,
197
+ )
198
+ _resp_for_meter = raw
199
+ content = getattr(raw, 'content', raw)
200
+ parsed = _parse_validation_verdict(
201
+ content if isinstance(content, str) else str(content)
202
+ )
203
+ except asyncio.TimeoutError:
204
+ self.logger.warning(
205
+ f'⚠️ Judge validation timed out after {VALIDATION_JUDGE_TIMEOUT_SEC}s; '
206
+ 'passing (fail-open).'
207
+ )
208
+ return True
209
+ except Exception as e: # noqa: BLE001 — any judge failure is fail-open
210
+ self.logger.warning(f'⚠️ Judge validation errored ({e!r}); passing (fail-open).')
211
+ return True
212
+
213
+ # A3: meter this aux LLM call through the single deduction path (fail-open).
214
+ # Only reached on a successful invoke — a failed/timed-out call is never metered.
215
+ from agents.task.agent.core.aux_metering import meter_aux_llm
216
+ await meter_aux_llm(
217
+ usage_tracker=getattr(self, "usage_tracker", None),
218
+ user_id=getattr(self, "user_id", None),
219
+ session_id=getattr(self, "session_id", ""),
220
+ agent_id=getattr(self, "agent_id", "") or "",
221
+ llm=judge_llm, response=_resp_for_meter, duration_seconds=_time.time() - _t0,
222
+ component="judge", purpose="output_validation",
223
+ )
224
+ is_valid = parsed.is_valid
225
+ if not is_valid:
226
+ self.logger.info(f'❌ Validator decision: {parsed.reason}')
227
+ msg = f'The output is not yet correct. {parsed.reason}.'
228
+ self._last_result = [ActionResult(extracted_content=msg, include_in_memory=True)]
229
+ else:
230
+ self.logger.info(f'✅ Validator decision: {parsed.reason}')
231
+ return is_valid
232
+
233
+ async def next_action(self, state: BrowserState, result: Optional[List[ActionResult]] = None) -> AgentOutput:
234
+ """
235
+ Get the next action from the agent based on current state.
236
+
237
+ This is a simplified method that delegates to get_next_action for the actual LLM call
238
+ while providing a cleaner interface for external usage.
239
+
240
+ Args:
241
+ state: Current browser state
242
+ result: Optional previous action results
243
+
244
+ Returns:
245
+ AgentOutput with the next action to take
246
+ """
247
+ import time
248
+
249
+ # CRITICAL FIX: Apply base64 stripping to state before any token operations
250
+ if hasattr(state, 'page_content') and state.page_content:
251
+ from agents.task.robust_parse_config import RobustParseConfig
252
+ state.page_content = RobustParseConfig.strip_base64_images(state.page_content)
253
+
254
+ start_time = time.time()
255
+
256
+ try:
257
+ # Store the result for context
258
+ if result:
259
+ self._last_result = result
260
+
261
+ # Add state and result to message context
262
+ # FIX (Jan 2026): Conditionally include browser state
263
+ include_browser = self._has_active_browser_usage()
264
+ self.message_manager.add_state_message(
265
+ state, result, None, self.use_vision,
266
+ include_browser_state=include_browser
267
+ )
268
+
269
+ # Get messages for LLM call
270
+ input_messages = self.message_manager.get_messages()
271
+
272
+ # Call the main next action method - this will handle its own telemetry
273
+ # so we don't send duplicate telemetry from this wrapper method
274
+ agent_output = await self.get_next_action(input_messages)
275
+
276
+ # P2-14: the LLM responded (even a parse-failed safe-default means the
277
+ # request was delivered) — drop the one-shot ephemerals now.
278
+ try:
279
+ self.message_manager.commit_ephemeral_consumption()
280
+ except Exception:
281
+ pass
282
+
283
+ duration = time.time() - start_time
284
+
285
+ # Only log timing since get_next_action already sent telemetry
286
+ self.logger.debug(f"next_action wrapper completed in {duration:.2f}s")
287
+
288
+ return agent_output
289
+
290
+ except Exception as e:
291
+ duration = time.time() - start_time
292
+ # P2-14: the LLM call ultimately failed — re-queue the ephemerals
293
+ # (correspondent reply / RECALL) so the next step re-includes them
294
+ # instead of losing them forever.
295
+ try:
296
+ self.message_manager.restore_ephemeral_on_failure()
297
+ except Exception:
298
+ pass
299
+ # Log error but don't send duplicate telemetry
300
+ self.logger.error(f"next_action failed after {duration:.2f}s: {e}", exc_info=True)
301
+ raise
302
+
303
+
@@ -0,0 +1,272 @@
1
+ """C9: Auto-load project context files (CLAUDE.md / AGENTS.md / .cursorrules).
2
+
3
+ CLI-only, default-OFF on server (gated by ``AutonomyConfig.project_context_autoload()``
4
+ AND ``local_mode_enabled()`` in construction.py).
5
+
6
+ ``load_project_context(root, *, cap_tokens)`` walks from *root* up to the git root,
7
+ finds the first occurrence of each recognised filename, runs the injection-threat scan
8
+ (fail-OPEN on import error, fail-CLOSED on scan error), caps total content to
9
+ *cap_tokens* via ``estimate_tokens_rough``, and returns the concatenated result.
10
+ Returns ``None`` if nothing is found or any unrecoverable error occurs (fully
11
+ fail-open at the outer level).
12
+
13
+ Safety properties:
14
+ - Skips any file whose path is flagged by ``is_secret_path``.
15
+ - Rejects any document whose content is flagged by the ``is_suspicious`` threat
16
+ scanner (fail-OPEN if the scanner is unavailable; fail-CLOSED if it raises).
17
+ - Truncates concatenated content to *cap_tokens* with an appended notice.
18
+ - All I/O errors are swallowed; the whole function returns ``None`` on exception.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import logging
23
+ from pathlib import Path
24
+ from typing import Optional
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # Recognised context filenames, in descending precedence.
29
+ # Only ONE name is loaded: the highest-precedence name that exists anywhere on the
30
+ # walk wins, and its most-local occurrence is used. Recognised names are NOT
31
+ # concatenated (a repo with both AGENTS.md and CLAUDE.md loads only AGENTS.md).
32
+ # polyrob.md — native control name (target POLYROB without touching other agents' files)
33
+ # POLYROB.md — uppercase variant
34
+ # AGENTS.md — vendor-neutral standard (Codex/OpenClaw interop)
35
+ # CLAUDE.md — Claude Code interop
36
+ # .cursorrules — legacy
37
+ _CONTEXT_FILENAMES: tuple[str, ...] = (
38
+ "polyrob.md",
39
+ "POLYROB.md",
40
+ "AGENTS.md",
41
+ "CLAUDE.md",
42
+ ".cursorrules",
43
+ )
44
+
45
+ # Header template injected before each file's content so the model knows the source.
46
+ _FILE_HEADER_TPL = "<!-- project-context: {filename} -->"
47
+
48
+
49
+ def should_load_project_context(
50
+ *, autoload: bool, local: bool, server_mode: bool
51
+ ) -> bool:
52
+ """Decide whether to load project context, given the resolved gates.
53
+
54
+ - Local CLI single-owner: load when ``autoload`` is on (trusted framing).
55
+ - Server: load ONLY when ``server_mode`` is explicitly opted in (untrusted
56
+ framing). ``autoload`` defaults OFF on the server, so the default is no-load
57
+ → byte-identical to the pre-Phase-2 behaviour.
58
+ """
59
+ if local:
60
+ return autoload
61
+ return server_mode
62
+
63
+
64
+ def resolve_project_context_root(
65
+ *, local: bool, cwd: str, workspace_dir: Optional[str]
66
+ ) -> Optional[str]:
67
+ """Pick the directory to search for project-context files, by tier.
68
+
69
+ - Local CLI: the process CWD (the user's project root).
70
+ - Server: the tenant's session ``workspace_dir`` ONLY — **never** the process
71
+ CWD, which on a multi-tenant deployment is the install dir (e.g.
72
+ ``/opt/polyrob``). Reading CWD there would leak the deployment's own files
73
+ (or another tenant's) into every session. Returns ``None`` when no server
74
+ workspace is resolvable, so the loader simply loads nothing.
75
+ """
76
+ if local:
77
+ return cwd
78
+ return workspace_dir
79
+
80
+
81
+ def build_project_context_message(
82
+ *,
83
+ local: bool,
84
+ autoload: bool,
85
+ server_mode: bool,
86
+ cwd: str,
87
+ workspace_dir: Optional[str],
88
+ cap_tokens: int = 20000,
89
+ ) -> Optional[str]:
90
+ """End-to-end: decide → resolve the tier root → load → frame.
91
+
92
+ Returns the foundation-message body (trusted/steering on local, untrusted-DATA
93
+ wrapped on the server opt-in), or ``None`` when nothing should/could be loaded.
94
+ Pure except for the filesystem read in :func:`load_project_context`; the caller
95
+ resolves ``workspace_dir`` (via the path manager) and passes it in, so this stays
96
+ unit-testable without a live session.
97
+ """
98
+ if not should_load_project_context(
99
+ autoload=autoload, local=local, server_mode=server_mode
100
+ ):
101
+ return None
102
+ root = resolve_project_context_root(local=local, cwd=cwd, workspace_dir=workspace_dir)
103
+ if root is None:
104
+ return None
105
+ ctx = load_project_context(root, cap_tokens=cap_tokens)
106
+ if ctx is None:
107
+ return None
108
+ return frame_project_context(ctx, trusted=local)
109
+
110
+
111
+ def frame_project_context(content: str, *, trusted: bool) -> str:
112
+ """Frame loaded project context for injection as a foundation message.
113
+
114
+ A project file is owner-authored config on the local CLI (trusted → returned
115
+ unchanged, read as steering). On the server the same file may come from a repo
116
+ the operator merely opened, so it is untrusted input: wrap it in
117
+ ``<untrusted_tool_result>`` DATA delimiters so the model treats it as a
118
+ DESCRIPTION of the project, not as instructions it must obey. The secret-skip
119
+ and ``is_suspicious`` scan in :func:`load_project_context` still run first.
120
+ """
121
+ if trusted:
122
+ return content
123
+ from agents.task.agent.core.untrusted_wrap import wrap_untrusted
124
+
125
+ return wrap_untrusted("project-context", content)
126
+
127
+
128
+ def _find_git_root(start: Path) -> Optional[Path]:
129
+ """Walk from *start* upward to the first directory containing ``.git``.
130
+
131
+ Returns the directory itself, or ``None`` if no ``.git`` marker is found
132
+ before reaching the filesystem root.
133
+ """
134
+ current = start.resolve()
135
+ for _ in range(50): # hard cap: prevents infinite loops on pathological FSes
136
+ if (current / ".git").exists():
137
+ return current
138
+ parent = current.parent
139
+ if parent == current:
140
+ # Reached the filesystem root without finding .git.
141
+ return None
142
+ current = parent
143
+ return None
144
+
145
+
146
+ def load_project_context(
147
+ root: str | Path,
148
+ *,
149
+ cap_tokens: int = 20000,
150
+ ) -> Optional[str]:
151
+ """Load and return project context from recognised context files.
152
+
153
+ Walks from *root* upward to the git root, collects the first occurrence of
154
+ each name in ``_CONTEXT_FILENAMES``, filters secret/suspicious files, and
155
+ returns their concatenated content capped to *cap_tokens*.
156
+
157
+ Returns ``None`` when nothing is found or the whole function fails.
158
+ """
159
+ try:
160
+ return _load_project_context_impl(Path(root), cap_tokens=cap_tokens)
161
+ except Exception as e:
162
+ logger.debug("load_project_context failed (non-fatal): %s", e)
163
+ return None
164
+
165
+
166
+ def _load_project_context_impl(root: Path, *, cap_tokens: int) -> Optional[str]:
167
+ """Implementation (raises on error; caller wraps in try/except)."""
168
+ from agents.task.agent.core.secret_guard import is_secret_path, estimate_tokens_rough
169
+
170
+ # Resolve the threat-scanner once; None means scanner unavailable (fail-OPEN).
171
+ try:
172
+ from modules.memory.task.threat_scan import is_suspicious
173
+ except Exception:
174
+ is_suspicious = None # type: ignore[assignment]
175
+
176
+ root_resolved = root.resolve()
177
+ git_root = _find_git_root(root_resolved)
178
+ # If there is no .git root, fall back to the provided root so at least the
179
+ # immediate directory is searched.
180
+ search_root = git_root if git_root is not None else root_resolved
181
+
182
+ # Collect candidate directories: from root upward to (and including) git_root.
183
+ dirs: list[Path] = []
184
+ current = root_resolved
185
+ while True:
186
+ dirs.append(current)
187
+ if current == search_root:
188
+ break
189
+ parent = current.parent
190
+ if parent == current:
191
+ break
192
+ current = parent
193
+
194
+ # Name-first precedence: walk filenames in precedence order; for each, find its
195
+ # most-local occurrence across the dirs. The FIRST name that yields a usable
196
+ # file wins, and we stop — recognised names are not concatenated (L1 fix).
197
+ found: list[tuple[str, str]] = [] # at most one (filename, content)
198
+
199
+ for filename in _CONTEXT_FILENAMES:
200
+ if found:
201
+ break
202
+ for directory in dirs: # dirs is most-local → git-root order
203
+ candidate = directory / filename
204
+ if not candidate.is_file():
205
+ continue
206
+
207
+ # Secret-path guard.
208
+ if is_secret_path(candidate, root=search_root):
209
+ logger.debug("project_context: skipping secret path %s", candidate)
210
+ continue
211
+
212
+ # Read the file.
213
+ try:
214
+ raw = candidate.read_text(encoding="utf-8", errors="replace")
215
+ except OSError as e:
216
+ logger.debug("project_context: could not read %s: %s", candidate, e)
217
+ continue
218
+
219
+ if not raw.strip():
220
+ logger.debug("project_context: %s is empty, skipping", candidate)
221
+ continue
222
+
223
+ # Threat-scan (fail-OPEN on import absence; fail-CLOSED on scan error).
224
+ if is_suspicious is not None:
225
+ try:
226
+ flagged = is_suspicious(raw)
227
+ except Exception as scan_err:
228
+ logger.warning(
229
+ "project_context: %s rejected (scan error, fail-closed): %s",
230
+ candidate, scan_err,
231
+ )
232
+ continue
233
+ if flagged:
234
+ logger.warning(
235
+ "project_context: %s rejected (suspicious content)", candidate
236
+ )
237
+ continue
238
+
239
+ found.append((filename, raw))
240
+ break # most-local occurrence of the winning name — stop walking dirs
241
+
242
+ if not found:
243
+ return None
244
+
245
+ # Concatenate with per-file headers.
246
+ parts: list[str] = []
247
+ for filename, content in found:
248
+ header = _FILE_HEADER_TPL.format(filename=filename)
249
+ parts.append(f"{header}\n{content}")
250
+
251
+ combined = "\n\n".join(parts)
252
+
253
+ # Cap to cap_tokens.
254
+ total_tokens = estimate_tokens_rough(combined)
255
+ if total_tokens > cap_tokens:
256
+ # Truncate the combined text to approximately cap_tokens.
257
+ # chars = tokens * 4 (estimate_tokens_rough uses len // 4).
258
+ max_chars = cap_tokens * 4
259
+ truncated = combined[:max_chars]
260
+ notice = f"\n\n<!-- project-context: truncated to {cap_tokens} tokens -->"
261
+ combined = truncated + notice
262
+ logger.debug(
263
+ "project_context: truncated from ~%d to ~%d tokens", total_tokens, cap_tokens
264
+ )
265
+
266
+ logger.debug(
267
+ "project_context: loaded %d file(s) [%s], ~%d tokens",
268
+ len(found),
269
+ ", ".join(name for name, _ in found),
270
+ estimate_tokens_rough(combined),
271
+ )
272
+ return combined
@@ -0,0 +1,93 @@
1
+ """Agent resource-management mixin (roadmap P9 decomposition; code-motion from service.py).
2
+
3
+ Bounded-collection memory management (init + cleanup) and the browser-context
4
+ accessor. Moved verbatim off the ``Agent`` god-file; ``Agent`` composes
5
+ ``ResourceMixin`` (call sites unchanged via MRO).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from typing import Dict
11
+
12
+
13
+ class ResourceMixin:
14
+ """Memory-management (bounded collections) + browser-context accessor for Agent."""
15
+
16
+ def _initialize_memory_management(self) -> None:
17
+ """Initialize memory management using bounded collections."""
18
+ from utils.bounded_collections import BoundedDict
19
+
20
+ # Use BoundedDict for automatic LRU eviction
21
+ self._telemetry_requests = BoundedDict(max_size=100)
22
+ self._file_references = BoundedDict(max_size=500)
23
+
24
+ # NOTE: User message queue removed - managed by HITLManager
25
+ self._max_user_guidance_tokens = int(os.getenv("MAX_USER_GUIDANCE_TOKENS", "1000"))
26
+
27
+ self.logger.debug("Initialized memory management with bounded collections")
28
+
29
+ def cleanup_memory(self, force: bool = False) -> Dict[str, int]:
30
+ """Clean up memory using bounded collections.
31
+
32
+ Args:
33
+ force: If True, perform aggressive cleanup regardless of thresholds
34
+
35
+ Returns:
36
+ Dictionary with cleanup statistics
37
+ """
38
+ from datetime import timedelta
39
+
40
+ cleanup_stats = {}
41
+
42
+ try:
43
+ # 1. Telemetry cleanup - BoundedDict handles eviction automatically
44
+ if hasattr(self, '_telemetry_requests') and force:
45
+ removed = self._telemetry_requests.cleanup_old(max_age=timedelta(minutes=5))
46
+ if removed > 0:
47
+ cleanup_stats['telemetry_entries_removed'] = removed
48
+
49
+ # 2. File references cleanup - BoundedDict handles eviction automatically
50
+ if hasattr(self, '_file_references') and force:
51
+ removed = self._file_references.cleanup_old(max_age=timedelta(hours=1))
52
+ if removed > 0:
53
+ cleanup_stats['files_cleaned'] = removed
54
+
55
+ # 3. Browser state history (keep existing logic)
56
+ if hasattr(self, '_last_browser_states') and len(self._last_browser_states) > 3:
57
+ while len(self._last_browser_states) > 3:
58
+ self._last_browser_states.popleft()
59
+ cleanup_stats['states_removed'] = cleanup_stats.get('states_removed', 0) + 1
60
+
61
+ # 4. Force garbage collection if requested
62
+ if force:
63
+ import gc
64
+ collected = gc.collect()
65
+ cleanup_stats['gc_collected'] = collected
66
+
67
+ # Log results
68
+ total_cleaned = sum(v for k, v in cleanup_stats.items() if k != 'gc_collected')
69
+ if total_cleaned > 10 or force:
70
+ self.logger.info(f"Memory cleanup removed {total_cleaned} items: {cleanup_stats}")
71
+ elif total_cleaned > 0:
72
+ self.logger.debug(f"Memory cleanup: {cleanup_stats}")
73
+
74
+ return cleanup_stats
75
+
76
+ except Exception as e:
77
+ self.logger.error(f"Error during memory cleanup: {e}", exc_info=True)
78
+ return cleanup_stats
79
+
80
+ async def get_browser_context(self):
81
+ """Get browser context from orchestrator.
82
+
83
+ BrowserManager maintains context cache internally (contexts_in_use),
84
+ so calling this multiple times returns the same context for this agent_id.
85
+
86
+ Returns:
87
+ Current browser context or None if not available
88
+ """
89
+ try:
90
+ return await self.orchestrator.get_browser_context(self.agent_id)
91
+ except Exception as e:
92
+ self.logger.debug(f"Failed to get browser context: {e}")
93
+ return None