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,398 @@
1
+ """Step validate + execute phases (roadmap P9; code-motion from step.py).
2
+
3
+ The _validate_and_intervene gate, execution-context construction, and the
4
+ _execute_actions phase — split out of StepMixin so step.py drops under 700L while
5
+ keeping the step pipeline readable. Agent composes StepExecutionMixin; _step_impl
6
+ calls these via MRO.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import time
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from agents.task.agent.views import ActionResult
15
+ from modules.llm.messages import HumanMessage
16
+ from agents.task.constants import MAX_MCP_PER_STEP
17
+
18
+ #: OR-4: how many consecutive empty-action steps before the strong "thinking loop"
19
+ #: intervention fires. Default 2 (one step sooner than the legacy 3) so a model
20
+ #: that answers in prose without a tool call — Grok/GPT-5 on content-gen — is
21
+ #: pushed to act faster, wasting fewer steps. Set EMPTY_ACTION_FAST_ESCALATE=off
22
+ #: to restore the legacy threshold of 3.
23
+ _FAST_ESCALATE = os.getenv("EMPTY_ACTION_FAST_ESCALATE", "true").strip().lower() not in (
24
+ "0",
25
+ "false",
26
+ "off",
27
+ "no",
28
+ )
29
+ _EMPTY_ACTION_ESCALATE_AT = 2 if _FAST_ESCALATE else 3
30
+
31
+
32
+ class StepExecutionMixin:
33
+ """Validate-and-intervene + execute-actions step phases for Agent."""
34
+
35
+ def _validate_and_intervene(self, model_output) -> bool:
36
+ """Phase 2b: validate model output; inject corrective guidance and return False if the step should end early, else True."""
37
+ # Validate model output
38
+ if not self._validate_model_output(model_output):
39
+ # Inject corrective guidance
40
+ # Initialize thinking loop counter if needed
41
+ if not hasattr(self, '_empty_action_counter'):
42
+ self._empty_action_counter = 0
43
+
44
+ self._empty_action_counter += 1
45
+ self.logger.warning(f"Empty action response {self._empty_action_counter}/{_EMPTY_ACTION_ESCALATE_AT}")
46
+
47
+ # D1-a (seed resolution): tolerate a bounded number of tool-free "planning"
48
+ # turns. Instead of treating the very first empty response as an error,
49
+ # acknowledge it as a planning turn and nudge (not scold) the model to act
50
+ # next. The >=3 thinking-loop escalation below remains the hard backstop, so
51
+ # the loop bound is unchanged. Disable with ALLOWED_REASONING_TURNS=0.
52
+ from agents.task.constants import ALLOWED_REASONING_TURNS
53
+ if self._empty_action_counter <= ALLOWED_REASONING_TURNS:
54
+ self.logger.info(
55
+ f"📝 Tool-free planning turn {self._empty_action_counter}/{ALLOWED_REASONING_TURNS} "
56
+ f"allowed (no action this step; must act next)"
57
+ )
58
+ self.message_manager.inject_user_guidance([{
59
+ 'text': (
60
+ "📝 Planning turn noted. You may reason this turn, but you MUST call at "
61
+ "least one function on your NEXT step to make progress "
62
+ "(e.g. done(text=...) if you are finished)."
63
+ ),
64
+ 'kind': 'guidance',
65
+ 'metadata': {'source': 'reasoning_turn_allowance', 'turn': self._empty_action_counter}
66
+ }])
67
+ # Not an error: a planning turn is a legitimate (bounded) outcome.
68
+ # Tagged for telemetry/clarity; the R1 conversational-exit treats it as
69
+ # a non-reply step (it lacks conversational_reply), so it resets the
70
+ # reply-run rather than counting toward an exit.
71
+ self._last_result = [ActionResult(
72
+ extracted_content="Planning turn (no action taken). Act on the next step.",
73
+ include_in_memory=False,
74
+ metadata={'planning_turn': True}
75
+ )]
76
+ if self.tool_call_tracker:
77
+ self.tool_call_tracker.complete_step()
78
+ return False
79
+
80
+ # Get available action count for context
81
+ available_actions = len(self.controller.get_action_names()) if self.controller else 0
82
+
83
+ # Escalating intervention after N consecutive failures (OR-4: N defaults
84
+ # to 2 so a prose-only model is pushed to act one step sooner).
85
+ if self._empty_action_counter >= _EMPTY_ACTION_ESCALATE_AT:
86
+ _n = self._empty_action_counter
87
+ self.logger.error(
88
+ f"🚨 CRITICAL: Thinking loop detected - {_n} consecutive steps without function calls"
89
+ )
90
+
91
+ # Strong intervention with explicit example
92
+ self.message_manager.inject_user_guidance([{
93
+ 'text': (
94
+ "🚨 INTERVENTION: Thinking loop detected.\n\n"
95
+ f"You have failed to call functions for {_n} consecutive steps.\n"
96
+ "This violates the core agent contract.\n\n"
97
+ "**STOP thinking. START acting.**\n\n"
98
+ "Example for current task:\n"
99
+ f"Task: {self.task}\n\n"
100
+ "If this is a file operation → call filesystem_write_file(...)\n"
101
+ "If this is a search → call mcp_execute_tool(...)\n"
102
+ "If you're stuck → call done(text='explanation')\n\n"
103
+ "**CALL A FUNCTION IN YOUR NEXT RESPONSE.**\n"
104
+ "No more explanations, no more planning."
105
+ ),
106
+ 'kind': 'intervention',
107
+ 'metadata': {
108
+ 'source': 'thinking_loop_detector',
109
+ 'counter': _n,
110
+ 'task': self.task[:100] if self.task else 'unknown'
111
+ }
112
+ }])
113
+
114
+ # History clearing disabled - preserves context
115
+ self.logger.info("Thinking loop detected - guidance injected")
116
+
117
+ # Reset counter for next attempt
118
+ self._empty_action_counter = 0
119
+
120
+ # Set error result
121
+ self._last_result = [ActionResult(
122
+ error="Thinking loop detected - guidance injected. YOU MUST CALL A FUNCTION.",
123
+ include_in_memory=True
124
+ )]
125
+ else:
126
+ # Normal error handling for attempts 1-2
127
+ self.message_manager.inject_user_guidance([
128
+ {
129
+ 'text': (
130
+ "❌ CRITICAL ERROR: No function calls detected.\n\n"
131
+ "You MUST call at least one function every step.\n"
132
+ "There is no 'thinking mode' or 'planning phase'.\n\n"
133
+ "**What to do RIGHT NOW:**\n"
134
+ "1. Look at the available functions below\n"
135
+ "2. Pick one that makes progress on the task\n"
136
+ "3. CALL IT with proper parameters\n\n"
137
+ f"You have {available_actions} functions available - use them!"
138
+ ),
139
+ 'kind': 'error',
140
+ 'metadata': {'source': 'empty_actions_validation', 'attempt': self._empty_action_counter}
141
+ }
142
+ ])
143
+
144
+ # Set error result
145
+ self._last_result = [ActionResult(
146
+ error=f"No function calls (attempt {self._empty_action_counter}/{_EMPTY_ACTION_ESCALATE_AT})",
147
+ include_in_memory=True
148
+ )]
149
+
150
+ # Clean up tracker (no messages added since execution never happened)
151
+ if self.tool_call_tracker:
152
+ self.tool_call_tracker.complete_step()
153
+ self.logger.debug("Cleared tool call tracker after validation error")
154
+ return False
155
+
156
+ # Check if model output has no actions - treat as failure
157
+ if not model_output.action or len(model_output.action) == 0:
158
+ self.logger.error("❌ Model failed to generate actions - this indicates an LLM error")
159
+ self.logger.error("Empty actions violate the agent contract. LLM must provide at least one action per step.")
160
+
161
+ # Treat as a normal failure
162
+ self.state.increment_failures()
163
+ self._last_result = [ActionResult(
164
+ error="LLM failed to generate actions. This may be a prompt issue, API problem, or model limitation.",
165
+ include_in_memory=True
166
+ )]
167
+ if self.tool_call_tracker:
168
+ self.tool_call_tracker.complete_step()
169
+ return False
170
+
171
+ return True
172
+
173
+ def _build_execution_context(self, browser_context):
174
+ """Build the ActionExecutionContext for this step's action execution."""
175
+ from tools.controller.execution_context import ActionExecutionContext
176
+ # SK-F10: stamp the forged-turn marker (set by _drain_user_messages when the
177
+ # turn was opened by a self-wake / async-delegation-result re-entry, cleared
178
+ # on a genuine drained turn) so action closures (skill_manage,
179
+ # self_context_manage) can tell a forged main-agent turn apart from a
180
+ # genuine one — both otherwise look identical (role='orchestrator',
181
+ # is_sub_agent=False). getattr default is None: an orchestrator that never
182
+ # drained a forged message is genuine.
183
+ turn_kind = getattr(self.orchestrator, '_forged_turn_kind', None)
184
+ execution_context = ActionExecutionContext(
185
+ browser_context=browser_context, # Use local variable from step
186
+ # Agent identification for sub-agent isolation
187
+ agent_id=self.agent_id,
188
+ is_sub_agent=self._is_sub_agent,
189
+ role=getattr(self, '_role', 'orchestrator'),
190
+ parent_session_id=self._parent_session_id if self._is_sub_agent else None,
191
+ # Use effective_session_id (virtual for sub-agents, real for main)
192
+ session_id=self.effective_session_id,
193
+ user_id=self.user_id,
194
+ workspace_dir=self.orchestrator.workspace_dir,
195
+ available_file_paths=self.available_file_paths or [],
196
+ sensitive_data=self.sensitive_data or {},
197
+ metadata={"turn_kind": turn_kind},
198
+ )
199
+ return execution_context
200
+
201
+ async def _execute_actions(self, model_output, tool_calls_to_pass, state, step_info, browser_context):
202
+ """Phase 3: run the model's actions via the controller and handle results. Returns the result list, or None to signal the step should abort (corrupted state)."""
203
+ # Allow agents to use as many actions as needed per step
204
+ if model_output and hasattr(model_output, 'action') and model_output.action:
205
+ # CRITICAL FIX: Limit MCP actions to prevent timeout cascades
206
+ # MCP tools (scraping, searches, APIs) are expensive and execute SEQUENTIALLY
207
+ actions_list = model_output.action if isinstance(model_output.action, list) else [model_output.action]
208
+
209
+ # Re-queue deferred MCP actions from previous step (if any)
210
+ if self._deferred_mcp_actions:
211
+ self.logger.info(f"🔄 Re-queuing {len(self._deferred_mcp_actions)} deferred MCP actions from previous step")
212
+ # Prepend deferred actions so they execute first
213
+ actions_list = self._deferred_mcp_actions + list(actions_list)
214
+ self._deferred_mcp_actions = []
215
+ # CO-F2: make the merged list authoritative — execution reads
216
+ # model_output.action, which is otherwise only rebuilt when the
217
+ # MCP throttle re-fires, silently dropping the re-queued actions.
218
+ model_output.action = actions_list
219
+
220
+ # Analyze action composition
221
+ mcp_actions = []
222
+ non_mcp_actions = []
223
+
224
+ for action in actions_list:
225
+ action_dump = action.model_dump(exclude_unset=True) if hasattr(action, 'model_dump') else {}
226
+ action_keys = list(action_dump.keys())
227
+ action_name = action_keys[0] if action_keys else None
228
+
229
+ if action_name and action_name.startswith('mcp_execute_tool'):
230
+ mcp_actions.append(action)
231
+ else:
232
+ non_mcp_actions.append(action)
233
+
234
+ # Apply MCP throttling to prevent timeout cascades
235
+ # Uses centralized constant from agents.task.constants
236
+
237
+ if len(mcp_actions) > MAX_MCP_PER_STEP:
238
+ self.logger.warning(
239
+ f"⚠️ Step has {len(mcp_actions)} MCP actions - limiting to {MAX_MCP_PER_STEP} "
240
+ f"to prevent timeout cascades (MCP actions execute sequentially, ~30-180s each)"
241
+ )
242
+
243
+ # Keep first MAX_MCP_PER_STEP MCP actions
244
+ kept_mcp = mcp_actions[:MAX_MCP_PER_STEP]
245
+ deferred_mcp = mcp_actions[MAX_MCP_PER_STEP:]
246
+
247
+ # Save deferred actions for next step (up to max deferrals)
248
+ # Track deferral count via _deferral_count attribute on action
249
+ for action in deferred_mcp:
250
+ if not hasattr(action, '_deferral_count'):
251
+ action._deferral_count = 0
252
+ action._deferral_count += 1
253
+
254
+ if action._deferral_count <= self._max_mcp_deferrals:
255
+ self._deferred_mcp_actions.append(action)
256
+ else:
257
+ action_dump = action.model_dump(exclude_unset=True) if hasattr(action, 'model_dump') else {}
258
+ action_name = list(action_dump.keys())[0] if action_dump else "unknown"
259
+ self.logger.warning(
260
+ f"⚠️ Dropping MCP action '{action_name}' after {self._max_mcp_deferrals} deferrals"
261
+ )
262
+
263
+ # Log what's being executed vs deferred
264
+ self.logger.info(
265
+ f"Executing {len(kept_mcp)} MCP + {len(non_mcp_actions)} other actions this step. "
266
+ f"Deferred {len(self._deferred_mcp_actions)} MCP actions to next step."
267
+ )
268
+
269
+ # Reconstruct action list: kept MCP + all non-MCP
270
+ model_output.action = kept_mcp + non_mcp_actions
271
+ actions_list = model_output.action
272
+
273
+ action_count = len(actions_list)
274
+ self.logger.info(f"Executing {action_count} actions in this step")
275
+
276
+ # VALIDATION: Check that all requested actions exist before execution
277
+ invalid_actions = []
278
+ for action in actions_list:
279
+ action_dump = action.model_dump(exclude_unset=True) if hasattr(action, 'model_dump') else {}
280
+ action_keys = list(action_dump.keys())
281
+ action_name = action_keys[0] if action_keys else None
282
+
283
+ if action_name and not self.controller.has_action(action_name):
284
+ invalid_actions.append(action_name)
285
+
286
+ if invalid_actions:
287
+ error_msg = (
288
+ f"Agent requested invalid actions: {invalid_actions}. "
289
+ f"These actions are not registered in the controller. "
290
+ f"Available actions: {len(self.controller.registry.list_action_names())}"
291
+ )
292
+ self.logger.error(f"❌ {error_msg}")
293
+ raise ValueError(error_msg)
294
+
295
+ # Issue #8 Fix: Pre-execution MCP validation loop check
296
+ # Block MCP actions that have repeatedly failed validation (saves resources)
297
+ if mcp_actions and self.tool_call_tracker:
298
+ blocked_mcp = []
299
+ allowed_mcp = []
300
+
301
+ for action in mcp_actions:
302
+ action_dump = action.model_dump(exclude_unset=True) if hasattr(action, 'model_dump') else {}
303
+ mcp_params = action_dump.get('mcp_execute_tool', {})
304
+ server_name = mcp_params.get('server_name', '')
305
+ tool_name = mcp_params.get('tool_name', '')
306
+
307
+ if server_name and tool_name:
308
+ if self.tool_call_tracker.should_inject_mcp_schema(server_name, tool_name):
309
+ blocked_mcp.append((server_name, tool_name))
310
+ self.logger.warning(
311
+ f"🚫 Blocking repeated MCP validation failure: {server_name}/{tool_name}. "
312
+ f"Check the error messages above for correct parameter format."
313
+ )
314
+ else:
315
+ allowed_mcp.append(action)
316
+ else:
317
+ allowed_mcp.append(action)
318
+
319
+ if blocked_mcp:
320
+ # Update the MCP actions list to only include allowed ones
321
+ mcp_actions = allowed_mcp
322
+ model_output.action = mcp_actions + non_mcp_actions
323
+ actions_list = model_output.action
324
+
325
+ # Inject guidance
326
+ blocked_names = [f"{s}/{t}" for s, t in blocked_mcp]
327
+ self.message_manager.inject_user_guidance([{
328
+ 'text': (
329
+ f"⚠️ Blocked {len(blocked_mcp)} MCP actions due to repeated validation failures: {blocked_names}\n\n"
330
+ f"The schema has been injected in previous error messages. "
331
+ f"Please review the FULL SCHEMA and correct your arguments before retrying."
332
+ ),
333
+ 'kind': 'warning',
334
+ 'metadata': {'source': 'mcp_validation_loop_prevention'}
335
+ }])
336
+
337
+ # PHASE 2 FIX (Nov 4, 2025): Loop Detection
338
+ # Detect if agent is repeating same actions without progress
339
+ if model_output and hasattr(model_output, 'current_state') and model_output.current_state:
340
+ loop_detected, loop_warning = self.detect_action_loop(
341
+ model_output.current_state,
342
+ actions_list
343
+ )
344
+
345
+ if loop_detected and loop_warning:
346
+ # Log warning
347
+ self.logger.warning(f"⚠️ Loop detected at step {self.state.n_steps}")
348
+
349
+ # Inject warning into agent's next context as ephemeral message
350
+ # HumanMessage imported at module level from modules.llm.messages
351
+ self.message_manager.push_ephemeral_message(
352
+ HumanMessage(content=loop_warning)
353
+ )
354
+
355
+ # Optional: Could halt after N consecutive loops
356
+ # For now: Non-blocking, agent gets feedback and can adjust
357
+
358
+ # CRITICAL FIX: Actually execute the actions through controller
359
+ execution_context = self._build_execution_context(browser_context)
360
+
361
+ # FIX (Jan 2026): Tool call rate limiting to prevent context overflow
362
+ # Limit parallel tool calls to prevent 60K+ token additions in a single step
363
+ MAX_TOOL_CALLS_PER_STEP = 3 # Allow up to 3 parallel calls
364
+ actions_to_execute = model_output.action
365
+ deferred_actions = []
366
+
367
+ if len(actions_to_execute) > MAX_TOOL_CALLS_PER_STEP:
368
+ self.logger.warning(
369
+ f"⚠️ Tool limit: {len(actions_to_execute)} actions requested, "
370
+ f"executing first {MAX_TOOL_CALLS_PER_STEP}, deferring {len(actions_to_execute) - MAX_TOOL_CALLS_PER_STEP}"
371
+ )
372
+ deferred_actions = actions_to_execute[MAX_TOOL_CALLS_PER_STEP:]
373
+ actions_to_execute = actions_to_execute[:MAX_TOOL_CALLS_PER_STEP]
374
+
375
+ # Inject deferral notice as ephemeral message for next step
376
+ deferred_names = [getattr(a, 'name', str(a)[:30]) for a in deferred_actions]
377
+ self.message_manager.push_ephemeral_message(
378
+ HumanMessage(content=(
379
+ f"⚠️ **Tool Call Limit Applied**\n\n"
380
+ f"{len(deferred_actions)} tool calls were deferred to prevent context overflow:\n"
381
+ f"- {', '.join(deferred_names[:5])}"
382
+ f"{'...' if len(deferred_names) > 5 else ''}\n\n"
383
+ f"Process these in your next step, 1-3 at a time."
384
+ ))
385
+ )
386
+
387
+ result = await self.controller.multi_act(
388
+ actions=actions_to_execute,
389
+ execution_context=execution_context
390
+ )
391
+ self._last_result = result
392
+ # L3: count the actions actually executed, not the pre-truncation request count
393
+ # (a step requesting >MAX_TOOL_CALLS_PER_STEP defers the rest — action_count
394
+ # would over-count what ran, feeding stall detection / telemetry a wrong number).
395
+ self.state.total_actions_count += len(actions_to_execute)
396
+
397
+ return result
398
+
@@ -0,0 +1,222 @@
1
+ """Step-telemetry mixin (roadmap P9; code-motion from step.py).
2
+
3
+ The best-effort per-step + iteration-complete telemetry emission, split out of the
4
+ StepMixin so step.py stays focused on the step phases. Agent composes
5
+ StepTelemetryMixin; the only caller is _finalize_step (via MRO).
6
+ """
7
+ from __future__ import annotations
8
+
9
+
10
+ def _detect_service_for_action(action_name: str) -> str:
11
+ """Lazy delegate to the module-level helper in service.py.
12
+
13
+ Defined here so the moved telemetry body (which references
14
+ ``_detect_service_for_action`` as a global) resolves at runtime without a
15
+ circular import at module load time. Mirrors the shim in ``step.py``.
16
+ """
17
+ from agents.task.agent.service import _detect_service_for_action as _impl
18
+ return _impl(action_name)
19
+
20
+
21
+ class StepTelemetryMixin:
22
+ """Per-step telemetry emission for Agent."""
23
+
24
+ async def _emit_step_telemetry(self, model_output, state, result, step_info=None):
25
+ """Best-effort step + iteration-complete telemetry; returns extracted actions for memory persistence."""
26
+ actions = []
27
+ try: # Capture telemetry using TelemetryManager
28
+ import time as time_module
29
+ step_end_time = time_module.time()
30
+
31
+ # Extract actions from model output
32
+ if model_output and model_output.action is not None:
33
+ for a in model_output.action:
34
+ if hasattr(a, 'model_dump'):
35
+ actions.append(a.model_dump(exclude_unset=True))
36
+ elif isinstance(a, dict):
37
+ actions.append(a)
38
+ else:
39
+ actions.append({'unknown': str(a)})
40
+
41
+ # Extract errors from results
42
+ errors = [r.error for r in result if r.error] if result else []
43
+
44
+ # Create enhanced input data
45
+ inputs = {
46
+ "task": self.task,
47
+ "state": {
48
+ "url": state.url if state else None,
49
+ "title": state.title if state else None,
50
+ },
51
+ "last_result": [r.error or r.extracted_content for r in self._last_result] if self._last_result else []
52
+ }
53
+
54
+ # Create enhanced output data
55
+ outputs = {}
56
+ if model_output:
57
+ outputs = {
58
+ "page_summary": model_output.current_state.page_summary if hasattr(model_output.current_state, "page_summary") else "",
59
+ "evaluation_previous_goal": model_output.current_state.evaluation_previous_goal if hasattr(model_output.current_state, "evaluation_previous_goal") else "",
60
+ "memory": model_output.current_state.memory if hasattr(model_output.current_state, "memory") else "",
61
+ "next_goal": model_output.current_state.next_goal if hasattr(model_output.current_state, "next_goal") else "",
62
+ "reasoning": model_output.current_state.reasoning if hasattr(model_output.current_state, "reasoning") else ""
63
+ }
64
+
65
+ # Capture metrics
66
+ metrics = {
67
+ "consecutive_failures": self.state.consecutive_failures,
68
+ "token_count": self.message_manager.get_token_count() if hasattr(self.message_manager, "get_token_count") else None
69
+ }
70
+
71
+ # Extract file operations from actions and results
72
+ files_created = []
73
+ files_modified = []
74
+ files_read = []
75
+ files_deleted = []
76
+
77
+ for action in actions:
78
+ action_name = None
79
+ action_params = {}
80
+
81
+ # Handle different action formats
82
+ if isinstance(action, dict):
83
+ # Format: {action_name: {params}} or {name: x, params: y}
84
+ if 'name' in action:
85
+ action_name = action.get('name')
86
+ action_params = action.get('params', action)
87
+ else:
88
+ # First key is action name
89
+ keys = [k for k in action.keys() if k not in ('status', 'error')]
90
+ if keys:
91
+ action_name = keys[0]
92
+ action_params = action.get(action_name, {}) if isinstance(action.get(action_name), dict) else {}
93
+
94
+ if action_name:
95
+ action_name_lower = action_name.lower()
96
+ file_path = action_params.get('file_path') or action_params.get('path') or action_params.get('filename')
97
+
98
+ if file_path:
99
+ if action_name_lower in ('write_file', 'create_file', 'save_file'):
100
+ files_created.append(file_path)
101
+ elif action_name_lower in ('append_file', 'edit_file', 'modify_file', 'update_file'):
102
+ files_modified.append(file_path)
103
+ elif action_name_lower in ('read_file', 'get_file_content', 'load_file'):
104
+ files_read.append(file_path)
105
+ elif action_name_lower in ('delete_file', 'remove_file'):
106
+ files_deleted.append(file_path)
107
+
108
+ # Determine iteration type based on actions
109
+ iteration_type = "mixed"
110
+ if not actions:
111
+ iteration_type = "thinking"
112
+ else:
113
+ services = set()
114
+ has_done = False
115
+ for action in actions:
116
+ if isinstance(action, dict):
117
+ # Check for done action
118
+ if 'done' in action or action.get('name') == 'done':
119
+ has_done = True
120
+ # Detect service
121
+ action_name = action.get('name') or list(action.keys())[0] if action else None
122
+ if action_name:
123
+ service = _detect_service_for_action(action_name)
124
+ services.add(service)
125
+
126
+ if has_done:
127
+ iteration_type = "done"
128
+ elif services == {'browser'}:
129
+ iteration_type = "browser"
130
+ elif services == {'filesystem'}:
131
+ iteration_type = "filesystem"
132
+ elif services == {'mcp'}:
133
+ iteration_type = "mcp"
134
+
135
+ # Determine iteration status
136
+ iteration_status = "completed"
137
+ error_message = None
138
+ if errors:
139
+ if len(errors) == len(actions) and actions:
140
+ iteration_status = "failed"
141
+ else:
142
+ iteration_status = "partial"
143
+ error_message = errors[0] if errors else None
144
+
145
+ # Check for done action
146
+ is_done = any(
147
+ isinstance(a, dict) and ('done' in a or a.get('name') == 'done')
148
+ for a in actions
149
+ ) if actions else False
150
+
151
+ if is_done:
152
+ iteration_status = "done"
153
+
154
+ # Use TelemetryManager - single method call with new iteration fields
155
+ self.telemetry_manager.capture_step(
156
+ step=self.state.n_steps,
157
+ actions=actions,
158
+ errors=errors,
159
+ brain_state=model_output.current_state if model_output else None,
160
+ consecutive_failures=self.state.consecutive_failures,
161
+ agent_name=getattr(self, 'agent_name', 'Unknown'),
162
+ agent_type=self.__class__.__name__,
163
+ current_task=self.task,
164
+ inputs=inputs,
165
+ outputs=outputs,
166
+ metrics=metrics,
167
+ # New iteration fields
168
+ iteration=self.state.n_steps,
169
+ iteration_type=iteration_type,
170
+ iteration_status=iteration_status,
171
+ files_created=files_created,
172
+ files_modified=files_modified,
173
+ files_read=files_read,
174
+ files_deleted=files_deleted,
175
+ error_message=error_message,
176
+ is_done=is_done
177
+ )
178
+
179
+ # Also emit iteration_complete event for clean UI boundary
180
+ reasoning_summary = ""
181
+ if model_output and hasattr(model_output.current_state, 'reasoning'):
182
+ reasoning_summary = (model_output.current_state.reasoning or "")[:200]
183
+
184
+ # Build action results summary
185
+ action_results = []
186
+ if result:
187
+ for i, r in enumerate(result):
188
+ action_results.append({
189
+ 'index': i,
190
+ 'success': not r.error,
191
+ 'error': r.error,
192
+ 'has_content': bool(r.extracted_content)
193
+ })
194
+
195
+ # Calculate duration using state's last_step_start_time
196
+ step_duration = 0.0
197
+ if hasattr(self.state, 'last_step_start_time') and self.state.last_step_start_time:
198
+ step_duration = step_end_time - self.state.last_step_start_time
199
+
200
+ self.telemetry_manager.capture_iteration_complete(
201
+ iteration=self.state.n_steps,
202
+ step=self.state.n_steps,
203
+ iteration_type=iteration_type,
204
+ iteration_status=iteration_status,
205
+ reasoning_summary=reasoning_summary,
206
+ actions_executed=[], # Don't duplicate actions - step event already has them
207
+ action_results=action_results,
208
+ files_created=files_created,
209
+ files_modified=files_modified,
210
+ files_read=files_read,
211
+ files_deleted=files_deleted,
212
+ success=(iteration_status in ('completed', 'done')),
213
+ error=error_message,
214
+ is_done=is_done,
215
+ duration_seconds=step_duration
216
+ )
217
+
218
+ except Exception as telemetry_error:
219
+ self.logger.debug(f"Failed to capture step telemetry: {telemetry_error}", exc_info=True)
220
+
221
+ return actions
222
+
@@ -0,0 +1,50 @@
1
+ """Tests for the aux-LLM metering helper (A3).
2
+
3
+ Covers: normal metering call records with the right component/tokens; no-op when
4
+ no user_id/tracker; fail-open (never raises) on a tracker error including anything
5
+ that looks like InsufficientCreditsError.
6
+ """
7
+ import asyncio
8
+ from types import SimpleNamespace
9
+
10
+ from agents.task.agent.core.aux_metering import meter_aux_llm
11
+
12
+
13
+ class _Tracker:
14
+ def __init__(self):
15
+ self.calls = []
16
+
17
+ async def record_llm_usage(self, **kw):
18
+ self.calls.append(kw)
19
+ return SimpleNamespace()
20
+
21
+
22
+ def test_meter_aux_records_with_component_and_tokens():
23
+ tr = _Tracker()
24
+ llm = SimpleNamespace(model_name="claude-haiku-4-5")
25
+ resp = SimpleNamespace(usage_metadata={"input_tokens": 500, "output_tokens": 40})
26
+ asyncio.run(meter_aux_llm(usage_tracker=tr, user_id="u1", session_id="s1",
27
+ agent_id="a1", llm=llm, response=resp, duration_seconds=0.1,
28
+ component="compaction", purpose="compaction"))
29
+ assert len(tr.calls) == 1
30
+ c = tr.calls[0]
31
+ assert c["component"] == "compaction" and c["input_tokens"] == 500 and c["output_tokens"] == 40
32
+
33
+
34
+ def test_meter_aux_noop_without_user():
35
+ tr = _Tracker()
36
+ asyncio.run(meter_aux_llm(usage_tracker=tr, user_id=None, session_id="s", agent_id="a",
37
+ llm=SimpleNamespace(), response=SimpleNamespace(), duration_seconds=0,
38
+ component="judge", purpose="x"))
39
+ assert tr.calls == []
40
+
41
+
42
+ def test_meter_aux_fails_open_on_tracker_error():
43
+ class Boom:
44
+ async def record_llm_usage(self, **kw):
45
+ raise RuntimeError("insufficient")
46
+
47
+ # must NOT raise
48
+ asyncio.run(meter_aux_llm(usage_tracker=Boom(), user_id="u", session_id="s", agent_id="a",
49
+ llm=SimpleNamespace(model_name="m"), response=SimpleNamespace(usage_metadata={}),
50
+ duration_seconds=0, component="reflection", purpose="x"))