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,781 @@
1
+ """Step execution loop mixin (code-motion from service.py)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import base64
7
+ import http
8
+ import importlib
9
+ import json
10
+ import logging
11
+ import os
12
+ import re
13
+ import subprocess
14
+ import traceback
15
+ import time
16
+ from datetime import datetime
17
+ from io import BytesIO
18
+ from pathlib import Path
19
+ from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union, Tuple
20
+ from collections import deque # ADDED: Import deque for bounded collections
21
+ from agents.task.agent.tool_call_tracker import ToolCallTracker # Robust tool call ID tracking
22
+
23
+ from dotenv import load_dotenv
24
+
25
+ # Import centralized constants
26
+ from agents.task.constants import (
27
+ IMG_TOKENS,
28
+ LoopDetectionConfig,
29
+ DEFAULT_USER_ID,
30
+ MemoryConfig,
31
+ MAX_MCP_PER_STEP
32
+ )
33
+
34
+ # Import POLYROB exceptions
35
+ from core.exceptions import (
36
+ AgentError,
37
+ ValidationError as ROBValidationError,
38
+ LLMResponseError,
39
+ ToolError
40
+ )
41
+
42
+ # Import billing exception for fail-fast handling
43
+ from core.exceptions import InsufficientCreditsError
44
+
45
+ # Import centralized utilities to avoid repeated inline imports
46
+ from agents.task.utils_json import normalize_action_schema
47
+ # Model limits now come from modules.llm.model_registry
48
+
49
+ # Native message types
50
+ from modules.llm.messages import (
51
+ AIMessage,
52
+ BaseMessage,
53
+ HumanMessage,
54
+ SystemMessage,
55
+ ToolMessage,
56
+ )
57
+ from modules.llm.adapters import BaseChatModel
58
+ from core.exceptions import (
59
+ RateLimitError,
60
+ LLMError,
61
+ LLMRateLimitError,
62
+ LLMAuthenticationError,
63
+ LLMConnectionError,
64
+ LLMContextLengthError,
65
+ LLMResponseError,
66
+ LLMPermanentError,
67
+ LLMProviderExhaustedError
68
+ )
69
+ # PIL Image imported locally in save_screenshot() method where needed
70
+ from pydantic import BaseModel, ConfigDict, ValidationError
71
+
72
+ from tools.browser.views import BrowserStateHistory, BrowserState
73
+ from agents.task.agent.message_manager.service import MessageManager
74
+ from agents.task.agent.prompts import SystemPrompt, AgentMessagePrompt
75
+ from agents.task.agent.views import (
76
+ ActionModel,
77
+ AgentBrain,
78
+ AgentError,
79
+ AgentHistory,
80
+ AgentHistoryList,
81
+ AgentOutput,
82
+ AgentStepInfo,
83
+ ActionResult,
84
+ )
85
+ from tools.browser.context import BrowserContext
86
+ from tools.dom.views import DOMElementNode, SelectorMap
87
+ from agents.task.telemetry.views import (
88
+ HumanApprovalRequestedEvent,
89
+ HumanApprovalDecisionEvent,
90
+ TodoStatusEvent,
91
+ AgentRunTelemetryEvent,
92
+ AgentEndTelemetryEvent,
93
+ ProviderFailureEvent,
94
+ ProviderFallbackSuccessEvent,
95
+ )
96
+ # ProductTelemetry no longer directly used - accessed via TelemetryManager
97
+ from agents.task.utils import time_execution_async, detect_llm_provider, extract_token_usage
98
+ # Safely import Google API exceptions
99
+ try:
100
+ from google.api_core.exceptions import ResourceExhausted
101
+ except ImportError:
102
+ # Create a dummy exception class if google-api-core is not installed
103
+ class ResourceExhausted(Exception):
104
+ pass
105
+
106
+ # Import from our own logging config
107
+ from agents.task.logging_config import get_task_logger
108
+
109
+ # Import centralized path management
110
+ from agents.task.path import pm
111
+
112
+ load_dotenv()
113
+
114
+ # Generic logger for the module itself (not instances)
115
+ logger = get_task_logger('agent')
116
+
117
+ T = TypeVar('T', bound=BaseModel)
118
+
119
+
120
+ def _detect_service_for_action(action_name: str) -> str:
121
+ """Lazy delegate to the module-level helper in service.py.
122
+
123
+ Defined here so the moved ``_step_impl`` body (which references
124
+ ``_detect_service_for_action`` as a global) resolves at runtime without a
125
+ circular import at module load time.
126
+ """
127
+ from agents.task.agent.service import _detect_service_for_action as _impl
128
+ return _impl(action_name)
129
+
130
+
131
+ def _is_fatal_step_error(error_str: str, billing_failover_enabled: bool) -> bool:
132
+ """Classify a step-loop exception as fatal (halt immediately) vs recoverable.
133
+
134
+ `error_str` must be lowercased. When `billing_failover_enabled` is True, billing/
135
+ quota errors are NOT fatal here — they flow to `_handle_step_error` which attempts a
136
+ provider swap before halting (HIGH-1). With the flag off, classification is identical
137
+ to the historical inline check (all billing/quota terms fatal).
138
+ """
139
+ is_billing = 'insufficient_quota' in error_str or 'billing' in error_str
140
+ # CO-F5: bare '429'/'rate limit' are NOT fatal — they're retryable. Routing them
141
+ # here halted the session before `_handle_step_error` could handle them. In practice
142
+ # a plain-Exception '429'/'rate limit' string is caught by the generic `is_llm_error`
143
+ # block (error_recovery.py ~:203-296: matches 'rate limit'/'429', then circuit
144
+ # breaker → provider fallback → backoff), which now owns all retryable rate-limit
145
+ # handling.
146
+ return (
147
+ ('quota' in error_str and 'exceeded' in error_str) or
148
+ 'authentication' in error_str or
149
+ 'api key' in error_str or
150
+ (is_billing and not billing_failover_enabled)
151
+ )
152
+
153
+
154
+ class StepMixin:
155
+ """Mixin extracted verbatim from agents/task/agent/service.py (pure code-motion)."""
156
+
157
+ @time_execution_async('--step')
158
+ async def step(self, step_info: Optional[AgentStepInfo] = None) -> None:
159
+ """Execute one step of the task with runtime safety guards"""
160
+ # ALWAYS use timeout protection - default to 600 seconds if not configured
161
+ timeout_seconds = self.step_timeout_seconds or 600
162
+ try:
163
+ # Track step start time for stall detection
164
+ self.state.last_step_start_time = time.time()
165
+
166
+ # Check AgentState loop detection BEFORE step
167
+ if self.state.is_stuck_in_loop():
168
+ self.logger.warning("🚨 AgentState detected stuck loop")
169
+ self._trigger_loop_intervention("AgentState loop detection triggered (multi-signal)")
170
+ elif self.state.is_showing_loop_symptoms():
171
+ # Early warning - log but don't intervene
172
+ self.logger.info(
173
+ f"âš ī¸ Loop symptoms detected: similar_actions={self.state.consecutive_similar_actions}, "
174
+ f"steps_since_finding={self.state.n_steps - self.state.last_finding_step}"
175
+ )
176
+
177
+ # H-MEM loop signal - log only, no intervention
178
+ if hasattr(self, 'task_context_manager') and self.task_context_manager:
179
+ hmem_signal = self.task_context_manager.check_loop_signal(self.session_id)
180
+ if hmem_signal:
181
+ self.logger.info(f"📊 H-MEM: {hmem_signal}")
182
+ self.task_context_manager.reset_loop_signal(self.session_id)
183
+
184
+ async with asyncio.timeout(timeout_seconds):
185
+ await self._step_impl(step_info)
186
+
187
+ # Reset stall detection on successful step
188
+ self.state.last_activity_time = time.time()
189
+
190
+ except asyncio.TimeoutError:
191
+ self.logger.error(f"Step timed out after {timeout_seconds} seconds", exc_info=True)
192
+ self.state.consecutive_failures = self.max_failures
193
+
194
+ # Try to save progress before failing
195
+ try:
196
+ await self._save_progress_on_timeout()
197
+ except Exception as e:
198
+ self.logger.warning(f"Failed to save progress on timeout: {e}")
199
+
200
+ raise
201
+
202
+ async def _prepare_step(self, step_info: Optional[AgentStepInfo] = None):
203
+ """Phase 1: pre-LLM setup — refresh context, build the state message, return (state, input_messages)."""
204
+ # Check cancellation before starting step execution
205
+ if self._cancelled:
206
+ self.logger.warning(f"❌ Step execution cancelled before start (step {self.state.n_steps})")
207
+ raise asyncio.CancelledError("Agent cancelled")
208
+
209
+ # Update step counter if step_info is provided
210
+ if step_info is not None and hasattr(step_info, 'step_number'):
211
+ # Use the provided step number + 1 (since step_number is 0-indexed)
212
+ self.state.n_steps = step_info.step_number + 1
213
+
214
+ # Use step number only in log to reduce verbosity
215
+ self.logger.info(f'📍 Step {self.state.n_steps}')
216
+
217
+ # FIX: Invalidate H-MEM cache at start of each step to ensure fresh context
218
+ # This prevents duplicate hierarchical searches within a step
219
+ self.message_manager.invalidate_hmem_cache()
220
+
221
+ # FIX (Context Optimization Phase 2): Log context usage breakdown
222
+ # Log every 10 steps or first 3 steps for early visibility
223
+ if self.state.n_steps % 10 == 0 or self.state.n_steps <= 3:
224
+ self._log_context_breakdown()
225
+
226
+ # Periodic memory monitoring (less aggressive)
227
+ if self.state.n_steps % 10 == 0: # Check less frequently
228
+ try:
229
+ import psutil
230
+ import gc
231
+ # NOTE: `os` is module-level (top of file); do NOT re-import it locally —
232
+ # a function-local `import os` would shadow it for this whole method and
233
+ # turn any earlier os.* use into an UnboundLocalError.
234
+
235
+ process = psutil.Process(os.getpid())
236
+ memory_mb = process.memory_info().rss / 1024 / 1024
237
+
238
+ # NOTE: Removed truncation of _last_result - it was destroying important context
239
+ # Previous results are needed for task continuity
240
+
241
+ # Check memory usage
242
+ memory_bytes = self.message_manager.get_memory_usage_estimate()
243
+ memory_mb = memory_bytes / (1024 * 1024)
244
+ if memory_mb > 50: # If message history is using > 50MB
245
+ self.logger.warning(f"Message history using {memory_mb:.1f}MB - consider checkpointing")
246
+
247
+ # Periodic memory cleanup
248
+ self.cleanup_memory(force=False)
249
+
250
+ # Log if high memory usage
251
+ if memory_mb > 1000: # 1GB
252
+ self.logger.info(f"Memory usage: {memory_mb:.0f}MB at step {self.state.n_steps}")
253
+
254
+
255
+ except Exception as e:
256
+ self.logger.debug(f"Memory cleanup error: {e}")
257
+
258
+ # Context compaction check - EVERY STEP with tiered thresholds
259
+ # FIX (Jan 2026): Changed from every 10 steps to every step
260
+ # MCP responses can add 60K+ tokens in a single step, causing overflow before check
261
+ try:
262
+ usage_pct = self.message_manager.get_context_usage_percent()
263
+
264
+ if usage_pct >= 95:
265
+ # CRITICAL: Emergency prune immediately - about to overflow.
266
+ # Non-LLM, always runs (the overflow safety net) - no cooldown.
267
+ self.logger.warning(f"🚨 Critical context: {usage_pct:.1f}% - emergency prune")
268
+ self.message_manager.emergency_context_prune()
269
+ elif usage_pct >= 85:
270
+ # HIGH: LLM compaction to intelligently summarize.
271
+ # Flow-efficiency D3-a: llm_compact_history is an EXTRA LLM call. Apply a
272
+ # step cooldown so it does not re-fire every step while usage lingers in the
273
+ # 85-95% band (a single large MCP result can re-cross 85% each step). If it
274
+ # climbs to >=95%, the emergency prune above handles overflow without an LLM call.
275
+ from agents.task.constants import COMPACTION_COOLDOWN_STEPS
276
+ last_compaction = getattr(self, '_last_llm_compaction_step', None)
277
+ steps_since = (
278
+ self.state.n_steps - last_compaction
279
+ if last_compaction is not None else COMPACTION_COOLDOWN_STEPS
280
+ )
281
+ if steps_since >= COMPACTION_COOLDOWN_STEPS:
282
+ self.logger.info(f"đŸ“Ļ High context: {usage_pct:.1f}% - LLM compaction")
283
+ # P2-15: only start the cooldown when compaction ACTUALLY ran. A
284
+ # transient abort (rate-limit/connection blip), a <15-message history,
285
+ # or an anti-thrash no-op returns False and is designed to retry next
286
+ # step — stamping the cooldown regardless delayed that retry by
287
+ # COMPACTION_COOLDOWN_STEPS while usage sat in the 85-95% band.
288
+ if await self.message_manager.llm_compact_history():
289
+ self._last_llm_compaction_step = self.state.n_steps
290
+ else:
291
+ self.logger.info(
292
+ f"đŸ“Ļ High context: {usage_pct:.1f}% - compacted {steps_since} step(s) ago, "
293
+ f"skipping LLM compaction (cooldown {COMPACTION_COOLDOWN_STEPS})"
294
+ )
295
+ elif usage_pct >= 70:
296
+ # WARNING: Log for visibility every 5 steps at this level
297
+ if self.state.n_steps % 5 == 0:
298
+ self.logger.info(f"âš ī¸ Context at {usage_pct:.1f}% - monitoring")
299
+ except Exception as e:
300
+ self.logger.debug(f"Context compaction error: {e}")
301
+
302
+ state = None
303
+ browser_context = None # bound before the try so the return is always safe
304
+
305
+ try:
306
+ # Get fresh browser context from orchestrator
307
+ browser_context = await self.get_browser_context()
308
+
309
+ # Only get browser state if we have a browser context
310
+ if browser_context:
311
+ try:
312
+ # FIX (Dec 2025): Only capture screenshots when vision is enabled
313
+ # This saves ~100-200ms per step for non-vision tasks
314
+ # Pass use_vision flag to control screenshot capture
315
+ state = await browser_context.get_state(capture_screenshot=self.use_vision)
316
+
317
+ # Apply page content truncation early for token safety
318
+ if hasattr(state, 'page_content') and state.page_content:
319
+ from agents.task.robust_parse_config import RobustParseConfig
320
+ state.page_content = RobustParseConfig.truncate_page_content(state.page_content)
321
+ except Exception as e:
322
+ self.logger.warning(f"Error getting browser state: {str(e)}")
323
+ # Don't fail - just continue without browser state
324
+ state = None
325
+ else:
326
+ # No browser context - use a minimal state
327
+ self.logger.debug("No browser context available - running without browser state")
328
+ # Create a minimal state object for non-browser tasks
329
+ from agents.task.agent.views import BrowserState
330
+ from tools.dom.views import DOMElementNode, SelectorMap
331
+ # BrowserState requires element_tree and selector_map from DOMState
332
+ # Create a minimal DOMElementNode instance
333
+ minimal_element = DOMElementNode(
334
+ tag_name='html',
335
+ xpath='/',
336
+ attributes={},
337
+ children=[],
338
+ is_visible=True,
339
+ parent=None
340
+ )
341
+ state = BrowserState(
342
+ url="",
343
+ title="No Browser",
344
+ tabs=[],
345
+ element_tree=minimal_element,
346
+ selector_map={}
347
+ )
348
+ except Exception as e:
349
+ self.logger.error(f"Unexpected error in state setup: {str(e)}", exc_info=True)
350
+ # Create minimal state as fallback
351
+ from agents.task.agent.views import BrowserState
352
+ from tools.dom.views import DOMElementNode, SelectorMap
353
+ # Create a minimal DOMElementNode for no-browser state
354
+ empty_element = DOMElementNode(
355
+ tag_name="html",
356
+ xpath="/html",
357
+ attributes={},
358
+ children=[]
359
+ )
360
+ state = BrowserState(
361
+ url="",
362
+ title="No Browser",
363
+ tabs=[],
364
+ element_tree=empty_element,
365
+ selector_map=SelectorMap()
366
+ )
367
+
368
+ self._check_if_stopped()
369
+
370
+ # FIXED: Force recalibration after large actions
371
+ from agents.task.robust_parse_config import RobustParseConfig
372
+ should_recalibrate = False
373
+
374
+ # Check if we need to recalibrate after large actions
375
+ if (RobustParseConfig.FORCE_RECALIBRATE_AFTER_LARGE_ACTIONS and
376
+ self._last_result and any(
377
+ result.extracted_content and len(result.extracted_content) > RobustParseConfig.LARGE_ACTION_THRESHOLD
378
+ for result in self._last_result
379
+ )):
380
+ should_recalibrate = True
381
+ self.logger.debug("Forcing recalibration after large action result")
382
+
383
+ # Recalibrate token counts on first step, every 5 steps, or after large actions
384
+ if self.state.n_steps == 1 or self.state.n_steps % 5 == 0 or should_recalibrate:
385
+ self.message_manager.recalibrate_token_counts()
386
+
387
+ # Pass previous brain state for memory continuity
388
+ previous_brain = None
389
+ if hasattr(self, '_last_model_output') and self._last_model_output and hasattr(self._last_model_output, 'current_state'):
390
+ previous_brain = self._last_model_output.current_state
391
+
392
+ # FIX (Jan 2026): Conditionally include browser state to prevent context bleeding
393
+ # The AgentMessagePrompt will also check _has_meaningful_browser_state()
394
+ # so even if we pass True here, empty states won't bleed through
395
+ include_browser = self._has_active_browser_usage()
396
+
397
+ # B-T2: prefetch & inject recalled memory (first step, then every
398
+ # MEMORY_PREFETCH_CADENCE steps when set). Fail-open; inert unless an
399
+ # external memory provider is registered.
400
+ await self._maybe_prefetch_memory()
401
+
402
+ # Task 5: session-start "recent activity" digest — first step only, chat/
403
+ # owner sessions only (never goal/cron/sub-agent). Fail-open; inert unless
404
+ # AutonomyConfig.episodic_digest_inject() is on.
405
+ await self._maybe_inject_episodic_digest()
406
+
407
+ # Task 6: idle-reset continuity bridge — first step only, chat/owner sessions
408
+ # only (never goal/cron/sub-agent). Fail-open; inert unless
409
+ # AutonomyConfig.continuity_bridge_enabled() is on and a prior chat episode
410
+ # with a summary exists for this session's thread_key.
411
+ await self._maybe_inject_continuity_bridge()
412
+
413
+ # §7.5: autonomous continuity bridge — first step only, AUTONOMOUS sessions
414
+ # only (goal/cron; never chat/sub-agent). Carries recent activity into the
415
+ # tick so it stops re-deriving "nothing new". Fail-open; inert unless
416
+ # AutonomyConfig.autonomous_continuity_bridge() is on.
417
+ await self._maybe_inject_autonomous_continuity()
418
+
419
+ # CO-F7: mark the once-per-SESSION bootstrap as done immediately after
420
+ # both injectors above have had their one shot on the session's first
421
+ # executed step. This is deliberately set here (not at the end of
422
+ # run()) so that a turn-1 run() that breaks BEFORE step() ever runs
423
+ # (cancellation, resumed-from-done/stopped, too-many-failures) leaves
424
+ # the flag False — a later real turn still gets to inject once. A
425
+ # _continue_session=True turn resets n_steps too, so its own first step
426
+ # also lands here; the injectors' own `_session_bootstrap_done` guard
427
+ # (see memory_prefetch.py) makes this idempotent — they've already
428
+ # fired-or-declined by the time this line runs, so re-setting an
429
+ # already-True flag is a no-op.
430
+ if self.state.n_steps == 1:
431
+ self._session_bootstrap_done = True
432
+
433
+ self.message_manager.add_state_message(
434
+ state,
435
+ self._last_result,
436
+ step_info,
437
+ self.use_vision,
438
+ previous_brain,
439
+ include_browser_state=include_browser
440
+ )
441
+
442
+ # Planning artifacts (plan.json, todo.md) are maintained directly by the agent
443
+
444
+ # User messages are drained in the run loop (after each step and after completion)
445
+ # This prevents unnecessary LLM calls just to check for new messages
446
+ # See run() method for message draining logic
447
+
448
+ input_messages = self.message_manager.get_messages()
449
+
450
+ # DIAGNOSTIC: Log control flags before check to help debug unexpected stops
451
+ if self.state.stopped:
452
+ self.logger.info(f'âš ī¸ Control flags before get_next_action: stopped={self.state.stopped}')
453
+
454
+ self._check_if_stopped()
455
+
456
+ return state, input_messages, browser_context
457
+
458
+ async def _record_step(self, state, model_output, input_messages) -> None:
459
+ """Recording tail of a step: persist last model output, run loop/repetition
460
+ detection, fire the new-step callback, save the conversation, and refresh
461
+ the state message. Extracted verbatim from _step_impl (behavior-preserving).
462
+ """
463
+ # Store model output for next step
464
+ self._last_model_output = model_output
465
+
466
+ # FIX #4: Track action in AgentState for loop detection
467
+ if model_output and hasattr(model_output, 'current_state') and model_output.current_state:
468
+ action_summary_for_tracking = model_output.current_state.memory[:200] if model_output.current_state.memory else "No memory"
469
+ self.state.track_action(action_summary_for_tracking)
470
+
471
+ # Action repetition detection
472
+ if model_output and hasattr(model_output, 'action') and model_output.action:
473
+ # Create a simplified representation of the action for comparison
474
+ import hashlib
475
+ # --- UPGRADE: use order-independent semantic hash ----------------
476
+ action_dump = []
477
+ for a in model_output.action:
478
+ # Dump each action with sorted keys so param order does not matter
479
+ action_dump.append(a.model_dump(mode="json", exclude_unset=True, by_alias=True))
480
+ action_hash = hashlib.md5(json.dumps(action_dump, sort_keys=True).encode()).hexdigest()
481
+ # ---------------------------------------------------------------
482
+
483
+ # Check for repeated actions with improved detection
484
+ if self._previous_actions and action_hash == self._previous_actions[-1]:
485
+ self._action_repetition_counter += 1
486
+ # Log warnings after 3+ repetitions (more aggressive)
487
+ if self._action_repetition_counter >= 3:
488
+ self.logger.warning(f"Same action repeated {self._action_repetition_counter} times")
489
+
490
+ # FIX #1: Respect the configured threshold instead of hardcoding 8
491
+ # The config value is typically 2-3 for faster loop detection
492
+ max_reps = self._max_allowed_repetitions # Removed the max(..., 8) override
493
+ if self._action_repetition_counter >= max_reps:
494
+ self._trigger_loop_intervention(f"Action repeated {self._action_repetition_counter} times")
495
+ else:
496
+ # Different action, reset counter
497
+ self._action_repetition_counter = 0
498
+
499
+ # Update action history (deque automatically maintains max size)
500
+ self._previous_actions.append(action_hash)
501
+
502
+ # FIX #2: Enhanced pattern detection with IMMEDIATE intervention
503
+ if len(self._previous_actions) >= 4:
504
+ # Convert deque to list for slice operations
505
+ prev_actions_list = list(self._previous_actions)
506
+
507
+ # Check for A-B-A-B pattern - IMMEDIATE intervention
508
+ if (prev_actions_list[-1] == prev_actions_list[-3] and
509
+ prev_actions_list[-2] == prev_actions_list[-4]):
510
+ self.logger.warning("🔄 Detected alternating action pattern (A-B-A-B) - INTERVENING NOW")
511
+ self._trigger_loop_intervention("Alternating A-B-A-B pattern detected")
512
+
513
+ # Check for A-B-C-A-B-C pattern - IMMEDIATE intervention
514
+ elif (len(self._previous_actions) >= 6 and
515
+ prev_actions_list[-3:] == prev_actions_list[-6:-3]):
516
+ self.logger.warning("🔄 Detected repeating 3-action pattern (A-B-C-A-B-C) - INTERVENING NOW")
517
+ self._trigger_loop_intervention("Repeating 3-action pattern detected")
518
+
519
+ if self.register_new_step_callback:
520
+ if callable(self.register_new_step_callback):
521
+ self.register_new_step_callback(state, model_output, self.state.n_steps)
522
+ else:
523
+ self.logger.warning("register_new_step_callback is set but not callable")
524
+
525
+ # Offload blocking file I/O off the event loop so concurrent sessions
526
+ # aren't stalled by this session's conversation write.
527
+ await asyncio.to_thread(self._save_conversation, input_messages, model_output)
528
+ self.message_manager.remove_last_state_message() # we dont want the whole state in the chat history
529
+
530
+ self._check_if_stopped()
531
+
532
+ # T-16: Model output was already added earlier, right after get_next_action
533
+ # This ensures consistent flow for both native and non-native tool calls
534
+ # tool_call_id is already set from the earlier add_model_output call
535
+
536
+ async def _call_llm(self, input_messages):
537
+ """Phase 2: call the LLM and normalize its tool calls -> (model_output, tool_calls_to_pass)."""
538
+ # P2-14: get_next_action consumes one-shot ephemerals (correspondent reply /
539
+ # RECALL) into a pending buffer. Drop them once the LLM responds; on a transient
540
+ # failure, re-queue them so the next step re-includes them instead of losing them.
541
+ try:
542
+ model_output = await self.get_next_action(input_messages)
543
+ except Exception:
544
+ try:
545
+ self.message_manager.restore_ephemeral_on_failure()
546
+ except Exception:
547
+ pass
548
+ raise
549
+ try:
550
+ self.message_manager.commit_ephemeral_consumption()
551
+ except Exception:
552
+ pass
553
+
554
+ # Handle tool_call_id based on native vs non-native mode
555
+ tool_calls_to_pass = None
556
+ if self.message_manager.use_native_tools:
557
+ # Get tool calls stored by get_next_action
558
+ if hasattr(self, '_current_normalized_tool_calls') and self._current_normalized_tool_calls:
559
+ tool_calls_to_pass = self._current_normalized_tool_calls
560
+ self.logger.debug(f"Using {len(tool_calls_to_pass)} native tool calls from LLM response")
561
+ else:
562
+ self.logger.debug("Native tools enabled but no tool calls in LLM response")
563
+ else:
564
+ # For non-native models, create synthetic tool calls from actions
565
+ if model_output and hasattr(model_output, 'action') and model_output.action:
566
+ # Create synthetic tool calls for non-native models
567
+ synthetic_calls = []
568
+ for idx, action in enumerate(model_output.action):
569
+ # Extract action name and params
570
+ if hasattr(action, 'model_dump'):
571
+ action_dict = action.model_dump(exclude_unset=True, by_alias=True)
572
+ if action_dict:
573
+ action_name = list(action_dict.keys())[0]
574
+ action_params = action_dict[action_name] if isinstance(action_dict[action_name], dict) else {}
575
+ else:
576
+ continue
577
+ elif isinstance(action, dict) and action:
578
+ action_name = list(action.keys())[0]
579
+ action_params = action[action_name] if isinstance(action[action_name], dict) else {}
580
+ else:
581
+ continue
582
+
583
+ # Create synthetic tool call
584
+ synthetic_call = {
585
+ 'id': f"synthetic_{self.state.n_steps}_{idx}",
586
+ 'name': action_name,
587
+ 'args': action_params
588
+ }
589
+ synthetic_calls.append(synthetic_call)
590
+
591
+ if synthetic_calls:
592
+ # Normalize them using ToolCallBuilder
593
+ from agents.task.agent.message_manager.tool_call_builder import ToolCallBuilder
594
+ tool_calls_to_pass = [
595
+ ToolCallBuilder.normalize_and_correct(tc, provider="synthetic")
596
+ for tc in synthetic_calls
597
+ ]
598
+
599
+ if tool_calls_to_pass:
600
+ self.logger.debug(f"Created {len(tool_calls_to_pass)} synthetic tool calls for non-native model")
601
+ # Store them for tracking
602
+ if self.tool_call_tracker:
603
+ for call in tool_calls_to_pass:
604
+ self.tool_call_tracker.register_call(
605
+ call_id=call.get('id') if isinstance(call, dict) else call.id,
606
+ tool_name=call.get('name') if isinstance(call, dict) else call.name,
607
+ args=call.get('args', {}) if isinstance(call, dict) else getattr(call, 'args', {})
608
+ )
609
+
610
+ # Tool call IDs are now tracked by tool_call_tracker - no temporary storage needed
611
+
612
+ # ✅ PHASE 2 (Nov 5, 2025): Atomic message addition AFTER execution
613
+ # DON'T add model output yet - wait until after successful execution
614
+ # AIMessage + ToolMessages will be added atomically via add_tool_call_pair_atomic()
615
+ self.logger.debug(f"Prepared {len(tool_calls_to_pass) if tool_calls_to_pass else 0} tool calls for execution")
616
+ # tool_call_id = self.message_manager.add_model_output(model_output, tool_calls=tool_calls_to_pass) # OLD: Optimistic commit
617
+
618
+ # Clean up temporary storage
619
+ if hasattr(self, '_current_normalized_tool_calls'):
620
+ delattr(self, '_current_normalized_tool_calls')
621
+
622
+ return model_output, tool_calls_to_pass
623
+
624
+ async def _finalize_step(self, model_output, state, result, step_info=None):
625
+ """Finally-phase bookkeeping: complete the tracker step, emit step telemetry, persist memory, auto-save state. No early return."""
626
+ # Ensure tool call tracker step is completed (idempotent - safe to call multiple times)
627
+ # Do this BEFORE telemetry to ensure cleanup happens even if telemetry fails
628
+ # NOTE: complete_step() is already idempotent - it only acts if _current_step_calls is not empty
629
+ # The normal flow (lines 1744, 1749) calls this after adding tool responses,
630
+ # but we call it here too to handle error cases where normal flow didn't complete
631
+ if self.tool_call_tracker:
632
+ try:
633
+ self.tool_call_tracker.complete_step()
634
+ self.logger.debug("Tool call tracker step completed in finally block")
635
+ except Exception as tracker_error:
636
+ self.logger.warning(f"Error completing tool call tracker step: {tracker_error}")
637
+
638
+ actions = await self._emit_step_telemetry(model_output, state, result, step_info)
639
+
640
+ # Add to hierarchical memory (Phase 2)
641
+ if model_output:
642
+ await self._save_step_to_memory(
643
+ step_number=self.state.n_steps,
644
+ brain_state=model_output.current_state.model_dump(),
645
+ actions=actions if actions else [],
646
+ results=result if result else [],
647
+ step_info=step_info
648
+ )
649
+
650
+ # PERSISTENCE: Auto-save agent state and message history periodically
651
+ if self.state.n_steps % 5 == 0:
652
+ try:
653
+ from agents.task.path import pm
654
+
655
+ # Save agent state
656
+ state_file = pm().create_file_path(
657
+ session_id=self.session_id,
658
+ subdir_name="data",
659
+ filename="agent_state.json",
660
+ user_id=self.user_id
661
+ )
662
+ if self.state.save_to_file(state_file):
663
+ self.logger.debug(f"💾 Auto-saved agent state at step {self.state.n_steps}")
664
+
665
+ # Save message history (skip for sub-agents - they don't need persistence)
666
+ if hasattr(self, 'message_manager') and self.message_manager and not self._is_sub_agent:
667
+ self.message_manager.save_to_disk(
668
+ session_id=self.session_id,
669
+ user_id=self.user_id
670
+ )
671
+ self.logger.debug(f"💾 Auto-saved message history at step {self.state.n_steps}")
672
+ except Exception as save_error:
673
+ self.logger.error(f"Failed to auto-save state at step {self.state.n_steps}: {save_error}")
674
+
675
+ async def _step_impl(self, step_info: Optional[AgentStepInfo] = None) -> None:
676
+ """Actual step implementation without timeout wrapper"""
677
+ # Phase 1: prepare. Returns the browser_context it acquired so we don't
678
+ # re-acquire it here (that would double-emit telemetry / context tracking).
679
+ state, input_messages, browser_context = await self._prepare_step(step_info)
680
+
681
+ model_output = None
682
+ tool_call_id = None # Initialize here for wider scope
683
+ result: list[ActionResult] = []
684
+
685
+ try:
686
+ model_output, tool_calls_to_pass = await self._call_llm(input_messages)
687
+
688
+ # Validate model output; bail out early if invalid (guidance already injected)
689
+ if not self._validate_and_intervene(model_output):
690
+ # CO-F3: clean up the state message added for this step, same as the
691
+ # exception handlers below do, so a planning-turn / empty-action step
692
+ # doesn't leak its state message into permanent history.
693
+ self.message_manager.remove_last_state_message()
694
+ return
695
+
696
+ result = await self._execute_actions(model_output, tool_calls_to_pass, state, step_info, browser_context)
697
+ result = await self._process_action_results(result, model_output, tool_calls_to_pass)
698
+ if result is None:
699
+ # CO-F3: corrupted tool-message pairing aborted the step; clean up the
700
+ # state message so it doesn't leak into permanent history either.
701
+ self.message_manager.remove_last_state_message()
702
+ return
703
+ await self._record_step(state, model_output, input_messages)
704
+
705
+ except InterruptedError as e:
706
+ # CRITICAL FIX (Dec 2025): Handle stop BEFORE generic Exception handler
707
+ # InterruptedError is a subclass of Exception, so this MUST come first!
708
+ interruption_reason = str(e) if str(e) else "Agent stopped"
709
+ self.logger.info(f'âšī¸ {interruption_reason} at step {self.state.n_steps}')
710
+
711
+ # Also log state flags for debugging
712
+ self.logger.info(f'State flags: stopped={self.state.stopped}')
713
+
714
+ # Clean up state message
715
+ self.message_manager.remove_last_state_message()
716
+
717
+ # Clean up tracker
718
+ if self.tool_call_tracker:
719
+ self.tool_call_tracker.complete_step()
720
+ self.logger.debug("Cleared tool call tracker after agent stopped")
721
+
722
+ # Set graceful stop result - not an error, just user interruption
723
+ self._last_result = [
724
+ ActionResult(
725
+ extracted_content='Agent stopped by user. Send a message to continue.',
726
+ include_in_memory=False # Don't pollute memory with stop messages
727
+ )
728
+ ]
729
+ return
730
+
731
+ except Exception as e:
732
+ # Clean up state message for any exception
733
+ self.message_manager.remove_last_state_message()
734
+
735
+ # CRITICAL: Detect fatal errors that should stop execution immediately
736
+ error_str = str(e).lower()
737
+ # HIGH-1: when billing failover is enabled, let billing/quota errors reach
738
+ # _handle_step_error (which tries a provider swap) instead of halting here.
739
+ from agents.task.agent.core.error_recovery import _billing_failover_enabled
740
+ _billing_failover = _billing_failover_enabled()
741
+ is_fatal_error = _is_fatal_step_error(error_str, _billing_failover)
742
+
743
+ if is_fatal_error:
744
+ # Log fatal error and mark session as failed
745
+ self.logger.error(f"❌ FATAL ERROR - Core component failure, halting session: {e}")
746
+ self.state.consecutive_failures = self.max_failures # Force max failures
747
+ self.state.stopped = True # Mark session as stopped
748
+
749
+ # Create error result for user visibility
750
+ self._last_result = [ActionResult(
751
+ error=f"FATAL ERROR: {str(e)[:400]}. The session has been halted due to a critical error. Please check your API configuration and billing status.",
752
+ include_in_memory=True
753
+ )]
754
+
755
+ # Clean up tracker
756
+ if self.tool_call_tracker:
757
+ self.tool_call_tracker.complete_step()
758
+
759
+ # DO NOT re-raise - just return to let run() loop detect stopped state
760
+ # This prevents infinite retry loops
761
+ return
762
+
763
+ # Non-fatal error - normal error handling
764
+ result = await self._handle_step_error(e)
765
+ self._last_result = result
766
+ # Clean up tracker (no messages added since step failed)
767
+ if self.tool_call_tracker:
768
+ self.tool_call_tracker.complete_step()
769
+ self.logger.debug("Cleared tool call tracker after step error")
770
+
771
+ finally:
772
+ await self._finalize_step(model_output, state, result, step_info)
773
+
774
+ # NEVER `return` inside this finally. A bare `return` here swallows any
775
+ # in-flight exception propagating through it — an asyncio.CancelledError
776
+ # from task.cancel()/the step timeout, or an exception re-raised by
777
+ # _handle_step_error — silently defeating cancellation, the step timeout,
778
+ # and error propagation. Use a positive guard instead of an early return.
779
+ # (Chat-mode step summary removed — handled by telemetry instead.)
780
+ if result and state:
781
+ await self._make_history_item(model_output, state, result)