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,776 @@
1
+ from __future__ import annotations
2
+ import asyncio
3
+ import logging
4
+ import functools
5
+ from typing import Awaitable, Callable, Dict, List, Optional, Any
6
+ from pathlib import Path
7
+ import os
8
+ from datetime import datetime
9
+ import time
10
+ import json
11
+
12
+ # Streaming callback contract: invoked once per LLM output chunk.
13
+ # Kept generic so core has no transport dependency (httpx/websocket/socketio
14
+ # live in the server layer that supplies the callback).
15
+ StreamChunkCallback = Callable[[str, str, str, int], Awaitable[None]]
16
+ """async (session_id, agent_id, chunk, step) -> None"""
17
+
18
+ # Import centralized path manager
19
+ from agents.task.path import pm
20
+
21
+ # Import LLM error types for fallback handling
22
+ from core.exceptions import (
23
+ LLMError,
24
+ LLMProviderExhaustedError,
25
+ LLMPermanentError
26
+ )
27
+
28
+ # Import session manager
29
+ from agents.task.agent.session import SessionManager
30
+ from agents.task.agent.service import Agent
31
+ from tools.browser.browser import Browser
32
+ from tools.browser.context import BrowserContext
33
+ from tools.browser.browser_manager import BrowserManager
34
+ from tools.controller.service import Controller
35
+ # Import ActionResult for loop detection and intervention
36
+ from agents.task.agent.views import ActionResult
37
+ # ToolManager functionality is now integrated into Controller
38
+
39
+ # Concern-group mixins (pure code-motion from this module)
40
+ from agents.task.session.workspace import WorkspaceMixin
41
+ from agents.task.session.feed import FeedMixin
42
+ from agents.task.session.multi_agent import MultiAgentMixin
43
+ from agents.task.session.browser_pool import BrowserPoolMixin
44
+ from agents.task.session.hitl_ingress import HITLIngressMixin
45
+ from agents.task.session.cleanup import SessionCleanupMixin
46
+ from agents.task.session.execution import SessionExecutionMixin
47
+ from agents.task.session.hooks import SessionHooksMixin
48
+
49
+ # Default tools that are always loaded for every session
50
+ DEFAULT_TOOLS = ['filesystem', 'task']
51
+
52
+ # SA-01: strong refs for fire-and-forget async-delegation wake kicks. asyncio only
53
+ # weakly references a bare create_task result, so without this the kick task could be
54
+ # GC'd mid-flight before it re-runs the idle session's loop.
55
+ _ASYNC_DELEGATION_KICKS: set = set()
56
+
57
+ class SessionOrchestrator(WorkspaceMixin, FeedMixin, MultiAgentMixin, BrowserPoolMixin, HITLIngressMixin, SessionCleanupMixin, SessionExecutionMixin, SessionHooksMixin):
58
+ """Orchestrates sessions with multiple agents.
59
+
60
+ The SessionOrchestrator is responsible for coordinating agents, services and
61
+ browser contexts throughout the session lifecycle. It has the following responsibilities:
62
+
63
+ 1. Agent Management: Create, register and coordinate execution of agents
64
+ 2. Service Management: Register services and ensure they're available to agents
65
+ 3. Browser Management: Initialize and share browser instances across agents
66
+ 4. Session Tracking: Track session status and completion
67
+
68
+ The orchestrator works with the Controller component, which manages the action registry
69
+ and handles action execution. This separation of concerns allows:
70
+ - Orchestrator: Focus on high-level session management, agent coordination, and service lifecycle
71
+ - Controller: Focus on action registration and execution, serving as the "action bridge" for agents
72
+
73
+ Services registered with the orchestrator are made available to the controller,
74
+ which then registers appropriate actions based on the service capabilities.
75
+ """
76
+
77
+ # Class-level sentinels satisfy SessionContext Protocol structural check via getattr().
78
+ # Instance assignments in __init__ shadow these immediately.
79
+ session_id: str = "" # type: ignore[assignment]
80
+ user_id: Optional[str] = "" # type: ignore[assignment]
81
+
82
+ def __init__(self,
83
+ session_id: str,
84
+ user_id: Optional[str] = None,
85
+ config: Optional[Any] = None,
86
+ browser_manager: Optional[BrowserManager] = None,
87
+ container: Optional[Any] = None,
88
+ on_stream_chunk: Optional[StreamChunkCallback] = None):
89
+ """Initialize session orchestrator.
90
+
91
+ Args:
92
+ session_id: The unique identifier for this session
93
+ user_id: Optional user identifier for multi-user installations
94
+ config: Optional session configuration with orchestrator settings
95
+ browser_manager: Optional BrowserManager instance for centralized browser management
96
+ container: Optional DependencyContainer for tool loading
97
+ on_stream_chunk: Optional async callback that receives LLM output
98
+ chunks. Signature: (session_id, agent_id, chunk, step). When
99
+ None (core mode), streaming is disabled. The server layer
100
+ supplies an httpx/socketio implementation to bridge chunks to
101
+ the webview frontend.
102
+ """
103
+ # Store container for tool loading
104
+ self.container = container
105
+ # Optional streaming sink (no transport coupling in core).
106
+ self._on_stream_chunk: Optional[StreamChunkCallback] = on_stream_chunk
107
+ # Get session manager instance
108
+ from agents.task.path import get_safe_singleton
109
+ self.session_manager = get_safe_singleton(SessionManager)()
110
+
111
+ # Clean the session ID
112
+ self.session_id = pm().clean_session_id(session_id)
113
+
114
+ # Validate session_id is provided
115
+ if not self.session_id:
116
+ raise ValueError("session_id is required for SessionOrchestrator")
117
+
118
+ # Store user_id
119
+ self.user_id = user_id
120
+
121
+ # Store configuration and set up orchestrator behavior
122
+ self.config = config
123
+ self.orchestrator_config = None
124
+
125
+ # Initialize TelemetryManager for orchestrator-level telemetry
126
+ try:
127
+ from agents.task.telemetry.manager import TelemetryManager
128
+ self.telemetry_manager = TelemetryManager(
129
+ session_id=self.session_id,
130
+ agent_id=f"orchestrator_{self.session_id}"
131
+ )
132
+ except Exception as e:
133
+ # Create dummy if initialization fails - include ALL telemetry methods
134
+ import logging
135
+ logger = logging.getLogger(f"task.orchestrator[{session_id}]")
136
+ logger.error(f"Failed to initialize TelemetryManager: {e}", exc_info=True)
137
+
138
+ class DummyTelemetryManager:
139
+ """Dummy telemetry manager that no-ops all telemetry calls."""
140
+ def capture_agent_registration(self, *args, **kwargs): pass
141
+ def capture_multi_agent_relationship(self, *args, **kwargs): pass
142
+ def capture_tool_execution(self, *args, **kwargs): pass
143
+ def capture_error(self, *args, **kwargs): pass
144
+ def capture_llm_request(self, *args, **kwargs): pass
145
+ def capture_step(self, *args, **kwargs): pass
146
+ def flush_buffers(self, *args, **kwargs): pass
147
+ self.telemetry_manager = DummyTelemetryManager()
148
+
149
+ # Initialize unified usage tracker (replaces separate usage_meter + telemetry calls)
150
+ # This provides single source of truth for token tracking and cost calculation
151
+ try:
152
+ from modules.credits.usage_tracker import LLMUsageTracker
153
+
154
+ # Get required services from container
155
+ if container and hasattr(container, 'get_service'):
156
+ db = container.get_service('database_manager')
157
+ balance_manager = container.get_service('balance_manager')
158
+
159
+ if db and balance_manager:
160
+ self.usage_tracker = LLMUsageTracker(
161
+ db=db,
162
+ balance_manager=balance_manager,
163
+ telemetry_manager=self.telemetry_manager
164
+ )
165
+ import logging
166
+ logger = logging.getLogger(f"task.orchestrator[{session_id}]")
167
+ logger.info("✓ Unified usage tracker initialized")
168
+ else:
169
+ self.usage_tracker = None
170
+ import logging
171
+ logger = logging.getLogger(f"task.orchestrator[{session_id}]")
172
+ logger.debug("Usage tracker not initialized - missing database or balance_manager")
173
+ else:
174
+ self.usage_tracker = None
175
+ except Exception as e:
176
+ import logging
177
+ logger = logging.getLogger(f"task.orchestrator[{session_id}]")
178
+ logger.error(f"Failed to initialize usage tracker: {e}", exc_info=True)
179
+ self.usage_tracker = None
180
+
181
+ if config and hasattr(config, 'orchestrator'):
182
+ self.orchestrator_config = config.orchestrator
183
+ else:
184
+ # Create default orchestrator config based on current mode
185
+ from agents.task.config import OrchestratorConfigModel, TaskMode
186
+ mode = TaskMode.get_mode()
187
+ self.orchestrator_config = OrchestratorConfigModel.from_mode(mode)
188
+
189
+ # Create session
190
+ try:
191
+ self.session_id = self.session_manager.create_session(self.session_id, user_id)
192
+ # Create session logger (integrates with core logging automatically)
193
+ from agents.task.logging_config import get_task_logger
194
+ self.logger = get_task_logger("orchestrator", self.session_id)
195
+ self.logger.debug(f"Session {self.session_id} ready")
196
+ except Exception as e:
197
+ # Create logger before raising error
198
+ self.logger = logging.getLogger(f"task.orchestrator[{self.session_id}]")
199
+ self.logger.error(f"Error creating/getting session: {e}")
200
+ raise
201
+
202
+ # REMOVED: Injected browser tracking - BrowserManager owns all browsers
203
+
204
+ # Initialize or get BrowserManager
205
+ self.browser_manager = browser_manager
206
+ if not self.browser_manager:
207
+ # Try to get from dependency container
208
+ try:
209
+ from core.container import DependencyContainer
210
+ container = DependencyContainer.get_instance()
211
+ if container and container.has_service('browser_manager'):
212
+ self.browser_manager = container.get_service('browser_manager')
213
+ self.logger.debug("Got BrowserManager from container")
214
+ except Exception as e:
215
+ self.logger.debug(f"Could not get BrowserManager from container: {e}")
216
+ # Don't create a new instance - let it be created on demand if needed
217
+
218
+ # TodoManager will be initialized before Controller (not lazy loaded)
219
+
220
+ # Track initialization status
221
+ self._initialized = False
222
+
223
+ # Agent tracking dictionaries
224
+ self.agents = {} # agent_id -> agent_instance
225
+ self.agent_types = {} # agent_id -> agent type
226
+ self.agent_names = {} # agent_id -> agent name
227
+ self.agent_creation_times = {} # agent_id -> creation time
228
+ self.agent_models = {} # agent_id -> model name
229
+ self.agent_execution_sequence = [] # Track agent execution sequence
230
+
231
+ # CRITICAL FIX (Nov 26, 2025): Pending messages for pre-agent queuing
232
+ # When messages arrive before agents exist, store here for later delivery
233
+ self._pending_messages = [] # List of (text, kind, metadata) tuples
234
+ # SECURITY FIX: Lock to prevent race condition between submit_user_message and create_agent
235
+ self._pending_messages_lock = asyncio.Lock()
236
+
237
+ # NOTE: Message routing only - agents own their queues via HITLManager
238
+
239
+ # REMOVED: Legacy browser storage - use BrowserManager only
240
+ # Warn if deprecated parameters provided
241
+ # Get workspace directory from PathManager BEFORE creating controller
242
+ try:
243
+ # Always use PathManager for workspace directory
244
+ _pm = (self.container.get_service("path_manager")
245
+ if self.container and hasattr(self.container, "get_service") else None) or pm()
246
+ self._path_manager = _pm
247
+ self._workspace_dir = str(_pm.get_workspace_dir(self.session_id, self.user_id))
248
+ self.logger.debug(f"Using workspace directory: {self._workspace_dir}")
249
+ except Exception as e:
250
+ self.logger.error(f"Failed to create workspace directory: {e}")
251
+ # Raise exception - cannot proceed without workspace
252
+ raise RuntimeError(f"Critical error: Cannot properly set workspace path for session {self.session_id}. Session will be unusable.")
253
+
254
+ # REMOVED: TodoManager initialization - now handled by TaskTool
255
+ # TaskTool manages its own TodoManager instances per session
256
+
257
+ # Initialize Controller for tool management
258
+ # Try to get existing controller from container, or create new one
259
+ from tools.controller.service import Controller
260
+
261
+ # Get container (use provided one or try to get global instance)
262
+ container = self.container
263
+ if not container:
264
+ try:
265
+ from core.container import DependencyContainer
266
+ container = DependencyContainer.get_instance()
267
+ except Exception as e:
268
+ self.logger.debug(f"No container available: {e}")
269
+
270
+ # Try to get existing controller from container
271
+ self.controller = None
272
+ if container and hasattr(container, 'get_service'):
273
+ try:
274
+ self.controller = container.get_service('controller')
275
+ if self.controller:
276
+ self.logger.debug("Using existing controller from container")
277
+ except Exception as e:
278
+ self.logger.debug(f"Could not get controller from container: {e}")
279
+
280
+ # Create new controller if not found
281
+ if not self.controller:
282
+ self.controller = Controller(
283
+ container=container, # May be None
284
+ orchestrator=self # Controller gets session_id/user_id/workspace_dir from orchestrator
285
+ )
286
+ container_status = "with container" if container else "without container"
287
+ self.logger.debug(f"Created new Controller {container_status}, workspace: {self._workspace_dir}")
288
+
289
+ # Browser context management delegated to BrowserManager
290
+ # REMOVED: browser_context_in_use - BrowserManager tracks all contexts
291
+
292
+ # Initialize SubAgentManager for subtask delegation
293
+ from agents.task.agent.sub_agent_manager import SubAgentManager
294
+ self.sub_agent_manager = SubAgentManager(orchestrator=self)
295
+ self.logger.debug("SubAgentManager initialized")
296
+
297
+ # UP-12: durable async delegation registry (delegate_task background=true).
298
+ # Reuses the SubAgentManager (its semaphore/timeout) and delivers completions
299
+ # back into this session as a new turn via submit_user_message. Inert until a
300
+ # background delegation is dispatched.
301
+ from agents.task.agent.async_delegation import AsyncDelegationRegistry
302
+ # Durable write-through to autonomy_state.db (restart-durable record of
303
+ # dispatched/terminal delegations; recovery sweep in autonomy_state.py).
304
+ # Fail-open — a missing/broken store keeps the legacy volatile registry.
305
+ _deleg_store = None
306
+ try:
307
+ from agents.task.agent.autonomy_state import get_autonomy_state_store
308
+ _deleg_store = get_autonomy_state_store()
309
+ except Exception:
310
+ _deleg_store = None
311
+ self.async_delegation = AsyncDelegationRegistry(
312
+ self.sub_agent_manager, deliver=self._deliver_async_delegation,
313
+ store=_deleg_store, session_id=self.session_id, user_id=self.user_id,
314
+ )
315
+
316
+ # Track initialization status
317
+ self._initialized = False
318
+ # LLM concurrency management with validation
319
+ self.session_llm_limit = max(1, int(os.environ.get('SESSION_LLM_LIMIT', '5')))
320
+ self._session_llm_semaphore = asyncio.Semaphore(self.session_llm_limit)
321
+ self.agent_llm_limits = {} # agent_id -> limit mapping
322
+
323
+ # Register with session manager
324
+ try:
325
+ self.session_manager.register_agent(
326
+ session_id=self.session_id,
327
+ agent_name="orchestrator",
328
+ agent_id=f"orchestrator_{self.session_id}",
329
+ agent_type="SessionOrchestrator",
330
+ role="coordinator",
331
+ user_id=self.user_id
332
+ )
333
+ self.logger.debug("Registered orchestrator with session manager")
334
+ except Exception as e:
335
+ self.logger.warning(f"Failed to register orchestrator with session manager: {e}")
336
+
337
+ # Update session status
338
+ try:
339
+ self.session_manager.update_session_status(self.session_id, "initializing")
340
+ self.logger.debug("Updated session status to initializing")
341
+ except Exception as e:
342
+ self.logger.warning(f"Failed to update session status: {e}")
343
+
344
+ # Initialize async task tracking with better management
345
+ self._execution_tasks = []
346
+
347
+ # Track browser contexts separately from agents for proper cleanup
348
+ # This prevents context leaks if agent is removed before cleanup
349
+ self._browser_contexts = set() # Set of context IDs (agent_id)
350
+
351
+ async def initialize(self,
352
+ tool_ids=None,
353
+ tools_config=None,
354
+ controller=None) -> None:
355
+ """Initialize the orchestrator with tools.
356
+
357
+ Args:
358
+ tool_ids: List of tool IDs to load from container
359
+ tools_config: Tool configuration dict
360
+ controller: Pre-created controller instance
361
+ """
362
+ if self._initialized:
363
+ self.logger.debug("Orchestrator already initialized")
364
+ return
365
+
366
+ try:
367
+ self.logger.info(f"Initializing orchestrator for session {self.session_id}")
368
+
369
+ # Use provided controller if available, otherwise keep existing one
370
+ if controller:
371
+ self.controller = controller
372
+ self.shared_controller = controller
373
+ self.logger.debug("Using provided controller instance")
374
+
375
+ # REMOVED: Early TodoManager initialization - now handled by TaskTool
376
+
377
+ # Check if tools were already loaded
378
+ existing_tools = self.controller.list_tools() if self.controller else []
379
+ if existing_tools:
380
+ self.logger.info(f"Controller already has {len(existing_tools)} tools loaded: {existing_tools}")
381
+
382
+ # Determine which tools to load
383
+ if not tool_ids and not existing_tools:
384
+ # No tools specified and none loaded - use comprehensive default
385
+ # Note: twitter removed from defaults - use the anysite tool for social media
386
+ from agents.task.tool_defaults import server_default_tools
387
+ tool_ids = server_default_tools()
388
+ self.logger.info(f"No tool_ids specified, loading comprehensive defaults: {tool_ids}")
389
+ elif tool_ids:
390
+ # Tools specified - ensure defaults are included
391
+ tools_to_load = list(tool_ids) # Copy to avoid modifying caller's list
392
+ for default in DEFAULT_TOOLS:
393
+ if default not in tools_to_load and default not in existing_tools:
394
+ tools_to_load.append(default)
395
+ tool_ids = tools_to_load
396
+
397
+ # Parse MCP server specifications from tool_ids AND tools_config
398
+ # Format: "mcp:servername" or "mcp:user:servername"
399
+ # If "mcp:X" and X is a registered tool class, load that tool directly
400
+ # Otherwise treat it as a regular MCP server
401
+ from tools.descriptors import get_tool_class
402
+
403
+ mcp_servers_requested = []
404
+ parsed_tool_ids = []
405
+
406
+ # Helper to check if mcp:X should load a tool or an MCP server
407
+ def process_mcp_spec(server_id: str):
408
+ """Process mcp:X spec - load tool if registered, otherwise MCP server."""
409
+ if not server_id.startswith("mcp:"):
410
+ return
411
+ spec = server_id[4:] # Remove "mcp:" prefix
412
+
413
+ # Check if this is a registered tool (not a regular MCP server)
414
+ # NOTE: Most MCP servers (like polymarket) are NOT registered as tools.
415
+ # They're accessed purely via mcp_execute_tool.
416
+ if get_tool_class(spec) is not None:
417
+ if spec not in parsed_tool_ids:
418
+ parsed_tool_ids.append(spec)
419
+ self.logger.info(f"Tool '{spec}' requested via {server_id}")
420
+ else:
421
+ # Regular MCP server (or pseudo-MCP server like polymarket)
422
+ if spec not in mcp_servers_requested:
423
+ mcp_servers_requested.append(spec)
424
+ if "mcp" not in parsed_tool_ids:
425
+ parsed_tool_ids.append("mcp")
426
+
427
+ # Check tools_config["mcp_servers"] (UI sends mcp_servers separately)
428
+ mcp_servers_from_config = (tools_config or {}).get("mcp_servers", [])
429
+ if mcp_servers_from_config:
430
+ self.logger.info(f"MCP servers from tools_config: {mcp_servers_from_config}")
431
+ for server_id in mcp_servers_from_config:
432
+ process_mcp_spec(server_id)
433
+
434
+ for tool_id in (tool_ids or []):
435
+ if tool_id.startswith("mcp:"):
436
+ # Use generic processor for mcp: specs
437
+ process_mcp_spec(tool_id)
438
+ else:
439
+ parsed_tool_ids.append(tool_id)
440
+
441
+ tool_ids = parsed_tool_ids
442
+
443
+ if mcp_servers_requested:
444
+ self.logger.info(f"MCP servers requested: {mcp_servers_requested}")
445
+
446
+ # Filter out already loaded tools
447
+ if tool_ids and existing_tools:
448
+ tool_ids = [t for t in tool_ids if t not in existing_tools]
449
+ if not tool_ids:
450
+ self.logger.info("All requested tools are already loaded, skipping tool loading")
451
+
452
+ if tool_ids:
453
+ try:
454
+ # Load tools through the controller
455
+ loaded_tools = await self.controller.load_tools_from_container(tool_ids)
456
+ total_actions = len(self.controller.registry.list_action_names())
457
+ self.logger.info(f"✓ Loaded {len(loaded_tools)} tools: {', '.join(f'{k}({len(self.controller.registry.get_actions_by_tool(k))})' for k in loaded_tools.keys())}")
458
+
459
+ # Special handling for browser tool
460
+ if "browser" in tool_ids and "browser" not in loaded_tools:
461
+ # Try to get from browser_manager
462
+ if self.browser_manager:
463
+ try:
464
+ browser = await self._ensure_browser_available()
465
+ if browser:
466
+ self.controller.add_tool("browser", browser)
467
+ loaded_tools["browser"] = browser
468
+ self.logger.info("Loaded browser tool from browser_manager")
469
+ except Exception as e:
470
+ self.logger.error(f"Failed to get browser from browser_manager: {e}")
471
+ else:
472
+ self.logger.warning("Browser requested but browser_manager not available")
473
+
474
+ # Initialize MCP if available and configured
475
+ if "mcp" in loaded_tools:
476
+ self.logger.debug("MCP tool available")
477
+ mcp_service = loaded_tools["mcp"]
478
+
479
+ # Determine which servers to use
480
+ requested_servers = mcp_servers_requested if mcp_servers_requested else None
481
+
482
+ # Check tools_config for server list (multiple formats supported)
483
+ if not requested_servers and tools_config:
484
+ # Format 1: tools_config["mcp_servers"] = ["mcp:anysite", "mcp:ghost"]
485
+ if "mcp_servers" in tools_config:
486
+ mcp_servers_list = tools_config.get("mcp_servers", [])
487
+ # Strip "mcp:" prefix and filter out registered tools
488
+ # (those are loaded as tools, not MCP servers)
489
+ requested_servers = []
490
+ for s in mcp_servers_list:
491
+ name = s[4:] if s.startswith("mcp:") else s
492
+ # Skip if this is a registered tool (handled separately)
493
+ if get_tool_class(name) is None:
494
+ requested_servers.append(name)
495
+ if requested_servers:
496
+ self.logger.info(f"MCP servers from tools_config[mcp_servers]: {requested_servers}")
497
+ # Format 2 (legacy): tools_config["mcp"]["servers"] = ["anysite", "ghost"]
498
+ elif "mcp" in tools_config:
499
+ mcp_config = tools_config.get("mcp", {})
500
+ if "servers" in mcp_config:
501
+ requested_servers = mcp_config["servers"]
502
+
503
+ # Set up user context for user-specific MCP servers
504
+ if self.user_id and hasattr(mcp_service, 'set_user_context'):
505
+ mcp_service.set_user_context(self.user_id)
506
+
507
+ # Set up user context for ALL tools that support it (generic approach)
508
+ if self.user_id:
509
+ for tool_name, tool_instance in loaded_tools.items():
510
+ if hasattr(tool_instance, 'set_user_context'):
511
+ tool_instance.set_user_context(self.user_id)
512
+ self.logger.info(f"User context set for tool '{tool_name}'")
513
+
514
+ # Continue MCP setup if MCP was loaded
515
+ if "mcp" in loaded_tools:
516
+ mcp_service = loaded_tools["mcp"]
517
+ # Load user MCP service if available
518
+ if self.user_id and self.container and hasattr(mcp_service, 'set_user_mcp_service'):
519
+ try:
520
+ user_mcp_service = self.container.get_service('user_mcp_service')
521
+ if user_mcp_service:
522
+ mcp_service.set_user_mcp_service(user_mcp_service)
523
+ self.logger.info("User MCP service configured")
524
+ except Exception as e:
525
+ self.logger.warning(f"Could not get user MCP service: {e}")
526
+
527
+ # Load user servers if any user: servers were requested
528
+ user_servers_requested = [s for s in (requested_servers or []) if s.startswith("user:")]
529
+ if user_servers_requested and hasattr(mcp_service, 'load_user_servers'):
530
+ # C6: pass this session's tenant explicitly (don't rely on the
531
+ # shared self._current_user_id, which a concurrent session mutates).
532
+ load_result = await mcp_service.load_user_servers(user_id=self.user_id)
533
+ # Handle both old (int) and new (UserServersLoadResult) return types
534
+ if hasattr(load_result, 'loaded_count'):
535
+ user_count = load_result.loaded_count
536
+ if load_result.failed_count > 0:
537
+ self.logger.warning(
538
+ f"Failed to load {load_result.failed_count} user MCP servers: "
539
+ f"{[f['name'] for f in load_result.failed_servers]}"
540
+ )
541
+ if load_result.timed_out:
542
+ self.logger.warning("User MCP server loading timed out")
543
+ else:
544
+ user_count = load_result # Old int return type
545
+ self.logger.info(f"Loaded {user_count} user MCP servers")
546
+
547
+ if requested_servers:
548
+ self.logger.info(
549
+ f"Configuring MCP with servers: {requested_servers}"
550
+ )
551
+
552
+ # FIX #4: Convert user server request format to internal format
553
+ # API format: "user:servername" (after stripping mcp: prefix)
554
+ # Internal format: "user_{user_id}::servername"
555
+ # This ensures set_requested_servers() filter matches actual server names
556
+ normalized_servers = []
557
+ for server in requested_servers:
558
+ if server.startswith("user:") and self.user_id:
559
+ # Convert "user:servername" → "user_{user_id}::servername"
560
+ server_name = server[5:] # Remove "user:" prefix
561
+ internal_name = f"user_{self.user_id}::{server_name}"
562
+ normalized_servers.append(internal_name)
563
+ self.logger.debug(f"Normalized user server: {server} → {internal_name}")
564
+ else:
565
+ normalized_servers.append(server)
566
+
567
+ requested_servers = normalized_servers
568
+
569
+ # Verify requested servers are available (optional check)
570
+ if hasattr(mcp_service, 'server_manager') and mcp_service.server_manager:
571
+ available = mcp_service.server_manager.get_all_tools()
572
+ # Check against internal format (user servers now normalized)
573
+ # Note: polymarket uses gateway, not server_manager, so it won't appear here
574
+ missing = [s for s in requested_servers if s not in available and s != 'polymarket']
575
+ if missing:
576
+ self.logger.warning(
577
+ f"Requested MCP servers not in server_manager: {missing}"
578
+ )
579
+ else:
580
+ self.logger.info(
581
+ f"All requested MCP servers are available: {list(available.keys())}"
582
+ )
583
+
584
+ # FIX (Jan 2026): ALWAYS apply server filtering, even without server_manager
585
+ # This is critical for polymarket which uses gateway, not server_manager
586
+ # Previously this was inside the server_manager check, causing polymarket
587
+ # to never appear in the prompt when it was the only MCP server selected
588
+ if hasattr(mcp_service, 'set_requested_servers'):
589
+ mcp_service.set_requested_servers(requested_servers)
590
+ self.logger.info(
591
+ f"Applied MCP server filter: {requested_servers}"
592
+ )
593
+
594
+ # MCP tools use discovery pattern - no waiting needed
595
+ # Agent discovers tools via mcp_list_tools and executes via mcp_execute_tool
596
+ if 'mcp' in loaded_tools:
597
+ self.logger.info("✅ MCP tool loaded - tools accessible via mcp_execute_tool")
598
+
599
+ # Log final tool count
600
+ total_actions = len(self.controller.registry.list_action_names())
601
+ self.logger.info(f"📊 Total registered actions: {total_actions}")
602
+
603
+ except Exception as e:
604
+ self.logger.error(f"Error loading tools: {e}")
605
+ # Load minimum required tools
606
+ try:
607
+ from tools.filesystem import FileSystem
608
+ fs_tool = FileSystem('filesystem', self.config, self.container)
609
+ await fs_tool.initialize()
610
+ self.controller.add_tool('filesystem', fs_tool)
611
+ self.logger.info("Loaded filesystem tool as fallback")
612
+ except Exception as fallback_error:
613
+ self.logger.error(f"Failed to load even fallback tools: {fallback_error}")
614
+
615
+ # Mark as initialized
616
+ self._initialized = True
617
+ self.logger.info(f"✅ Orchestrator initialized successfully for session {self.session_id}")
618
+
619
+ except Exception as e:
620
+ self.logger.error(f"Failed to initialize orchestrator: {e}")
621
+ raise
622
+
623
+ def cancel(self) -> None:
624
+ """Cancel all agents in this session.
625
+
626
+ Sets cancellation flag on all agents, causing them to stop
627
+ execution at the next step boundary.
628
+ """
629
+ self.logger.info(f"❌ Cancelling session {self.session_id}")
630
+
631
+ # Cancel all agents
632
+ cancelled_count = 0
633
+ for agent_id, agent in self.agents.items():
634
+ try:
635
+ if hasattr(agent, 'cancel'):
636
+ agent.cancel()
637
+ cancelled_count += 1
638
+ self.logger.debug(f"Cancelled agent {agent_id}")
639
+ except Exception as e:
640
+ self.logger.error(f"Error cancelling agent {agent_id}: {e}")
641
+
642
+ if cancelled_count > 0:
643
+ self.logger.info(f"Cancelled {cancelled_count} agent(s) in session {self.session_id}")
644
+
645
+ # Update session status
646
+ if hasattr(self, 'session_manager') and self.session_manager:
647
+ try:
648
+ self.session_manager.update_session_status(self.session_id, 'cancelled')
649
+ self.logger.debug(f"Updated session status to cancelled")
650
+ except Exception as e:
651
+ self.logger.error(f"Error updating session status: {e}")
652
+
653
+ # REMOVED: get_todo_manager() - Deprecated method removed
654
+ # Agent now accesses TaskTool directly via controller.get_tool('task')
655
+
656
+ # The run_session method has been removed as it references attributes that don't exist
657
+ # in the SessionOrchestrator class (active_sessions, task_available, container, etc.)
658
+ # This functionality should be implemented in the AutoV2Agent class instead.
659
+
660
+ async def create_agent_from_profile(
661
+ self,
662
+ profile_id: str,
663
+ task: str,
664
+ llm: Any = None,
665
+ overrides: Optional[Dict[str, Any]] = None,
666
+ share_controller: bool = True,
667
+ **kwargs
668
+ ) -> Agent:
669
+ """Create an agent from a profile configuration.
670
+
671
+ Args:
672
+ profile_id: Profile identifier to load
673
+ task: Task description
674
+ llm: Optional LLM override
675
+ overrides: Optional profile overrides
676
+ share_controller: Whether to use the shared controller (default: True)
677
+ **kwargs: Additional agent parameters
678
+
679
+ Returns:
680
+ Configured Agent instance
681
+ """
682
+ self.logger.info(f"Creating agent from profile '{profile_id}' (share_controller={share_controller})")
683
+
684
+ # Pass profile_id and share_controller to create_agent
685
+ return await self.create_agent(
686
+ task=task,
687
+ llm=llm,
688
+ profile_id=profile_id,
689
+ profile_overrides=overrides,
690
+ share_controller=share_controller,
691
+ **kwargs
692
+ )
693
+
694
+ # --- SessionContext Protocol adapter methods ---
695
+
696
+ def get_agents(self) -> dict:
697
+ return self.agents
698
+
699
+ def get_tool_call_tracker(self):
700
+ if self.agents:
701
+ agent = next(iter(self.agents.values()))
702
+ return getattr(agent, 'tool_call_tracker', None)
703
+ return None
704
+
705
+ def get_sub_agent_manager(self):
706
+ return getattr(self, 'sub_agent_manager', None)
707
+
708
+ async def _deliver_async_delegation(self, rec, block: str) -> None:
709
+ """Deliver a background-delegation completion back into THIS session as a new
710
+ turn (UP-12). Routes through submit_user_message (kind='delegation_result') so
711
+ it re-enters via the HITL queue -> run-loop drain -> inject_user_guidance,
712
+ preserving message-role alternation. submit_user_message itself parks the
713
+ message if no agent is active yet and can raise MessageQueueFullError if the
714
+ queue is full — AsyncDelegationRegistry wraps this call, so a failure here is
715
+ logged, not fatal.
716
+
717
+ P1-5: the RESULT is always delivered (submit_user_message parks it if no loop
718
+ is active, or hands it to an active loop). Only the WAKE KICK — forging a fresh
719
+ run to drain an idle session — is bounded by the shared ReentryBudget, because
720
+ that is the ping-pong risk (a self-triggering delegation chain re-forging runs
721
+ indefinitely). The budget is applied UNCONDITIONALLY (the kick fires regardless
722
+ of SELF_WAKE_ENABLED — previously it was only budgeted when self-wake was on, so
723
+ the server default left it unbounded) and FAIL-CLOSED (a budget-guard error
724
+ skips the kick rather than opening the storm rail). A genuine owner turn resets
725
+ the budget."""
726
+ # A3: single-source the forged-turn kind so producer, marker-recompute, and
727
+ # the security gate all reference one constant (see self_wake.FORGED_TURN_KINDS).
728
+ from agents.task.agent.core.self_wake import DELEGATION_RESULT_KIND
729
+ await self.submit_user_message(
730
+ agent_id=getattr(rec, "parent_agent_id", None),
731
+ text=block,
732
+ kind=DELEGATION_RESULT_KIND,
733
+ metadata={
734
+ "source": "async_delegation",
735
+ "delegation_id": rec.delegation_id,
736
+ "status": rec.status,
737
+ },
738
+ )
739
+ # SA-01: if the parent loop already ended (the agent called done() /
740
+ # conversational-exit before the background child finished), submit_user_message
741
+ # only PARKED the result in the HITL queue — nothing would ever drain it, so the
742
+ # deliverable the user was promised as "a new turn" silently vanished. Kick a
743
+ # fresh run to drain it. Idempotent: run_session refuses concurrent execution
744
+ # ("session is already executing"), so this is a cheap no-op when a loop is still
745
+ # active. The kick is set by TaskAgent.register_orchestrator; absent (bare
746
+ # orchestrator / tests) => byte-identical to the parked-only UP-12 behavior.
747
+ kick = getattr(self, "_wake_kick", None)
748
+ if kick is None:
749
+ return
750
+ # P1-5: budget the kick (unconditional + fail-CLOSED). An exhausted budget parks
751
+ # the result without forging another run; a budget-guard error also skips the
752
+ # kick (never open the storm rail on an error).
753
+ try:
754
+ from agents.task.agent.core.self_wake import get_reentry_budget
755
+ if not get_reentry_budget().try_consume(self.session_id):
756
+ self.logger.info(
757
+ f"🛌 re-entry budget exhausted — parking async-delegation result "
758
+ f"without a wake kick for session {self.session_id}"
759
+ )
760
+ return
761
+ except Exception:
762
+ self.logger.debug(
763
+ "re-entry budget guard errored — skipping wake kick (fail-closed)",
764
+ exc_info=True,
765
+ )
766
+ return
767
+ try:
768
+ import asyncio as _asyncio
769
+ t = _asyncio.create_task(kick())
770
+ _ASYNC_DELEGATION_KICKS.add(t)
771
+ t.add_done_callback(_ASYNC_DELEGATION_KICKS.discard)
772
+ except Exception:
773
+ self.logger.debug("async-delegation wake kick failed (fail-open)", exc_info=True)
774
+
775
+ def get_telemetry_manager(self):
776
+ return getattr(self, 'telemetry_manager', None)