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,612 @@
1
+ """Session cleanup mixin (roadmap P9; code-motion from orchestrator.py).
2
+
3
+ The session teardown method (browser release, agent/tool cleanup, status update),
4
+ split out of SessionOrchestrator as a whole cohesive method so orchestrator.py
5
+ shrinks. SessionOrchestrator composes SessionCleanupMixin; callers
6
+ (task_agent_lite, tests) use orchestrator.cleanup() unchanged via MRO.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import json
12
+ import logging
13
+ import time
14
+ from datetime import datetime
15
+ from agents.task.path import pm
16
+ from typing import Any, Dict, List, Optional
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # cleanup(status=...) is called with free-form strings from a handful of production
21
+ # sites (task_agent_lite.py's TTL/LRU eviction passes "suspended"; app-shutdown
22
+ # reap passes the default "completed"). Map the ones we've actually seen to the
23
+ # small terminal vocabulary finalize_episode's outcome accepts
24
+ # ({"done","failed","partial","cancelled"}); anything unrecognized is dropped
25
+ # (no episode written) rather than guessed at.
26
+ _CLEANUP_STATUS_TO_OUTCOME = {
27
+ "completed": "done",
28
+ "suspended": "partial", # paged-out/evicted, not a genuine completion
29
+ "error": "failed",
30
+ "failed": "failed",
31
+ "cancelled": "cancelled",
32
+ }
33
+
34
+ _SUMMARY_MAX_CHARS = 500
35
+
36
+
37
+ def _derive_closing_chat_summary(orchestrator) -> Optional[str]:
38
+ """Best-effort short H-MEM summary of the CLOSING chat session (Task 6 Part A).
39
+
40
+ Sourced from the (non-sub-agent) session's ``ContextRetriever._format_session_summary()``
41
+ — the same Layer-1 "[HIERARCHICAL MEMORY - SESSION CONTEXT]" text the retriever
42
+ already builds for in-session context injection, reached via
43
+ ``agent.task_context_manager.get_session(session_id).context_retriever``.
44
+
45
+ Gated by ``AutonomyConfig.continuity_bridge_enabled()`` at the call site (this
46
+ function is only invoked when the flag is on) so behaviour when the bridge is
47
+ disabled is byte-identical to before Task 6 (summary stays None).
48
+
49
+ Fail-open: ANY error (missing attribute, no H-MEM session, provider hiccup) is
50
+ swallowed and returns None — a summary failure must never break cleanup.
51
+ """
52
+ try:
53
+ agent = None
54
+ for candidate in getattr(orchestrator, "agents", {}).values():
55
+ if not getattr(candidate, "_is_sub_agent", False):
56
+ agent = candidate
57
+ break
58
+ if agent is None:
59
+ return None
60
+
61
+ task_context_manager = getattr(agent, "task_context_manager", None)
62
+ if task_context_manager is None:
63
+ return None
64
+
65
+ session_id = getattr(orchestrator, "session_id", None)
66
+ session_data = task_context_manager.get_session(session_id)
67
+ retriever = getattr(session_data, "context_retriever", None) if session_data else None
68
+ if retriever is None:
69
+ return None
70
+
71
+ text = retriever._format_session_summary()
72
+ text = (text or "").strip()
73
+ return text[:_SUMMARY_MAX_CHARS] or None
74
+ except Exception:
75
+ logger.warning("continuity bridge summary derivation failed", exc_info=True)
76
+ return None
77
+
78
+
79
+ class SessionCleanupMixin:
80
+ """Session teardown for SessionOrchestrator."""
81
+
82
+ async def cleanup(
83
+ self,
84
+ preserve_workspace: bool = False,
85
+ preserve_agents: bool = True,
86
+ status: str = "completed",
87
+ full_cleanup: bool = False
88
+ ) -> None:
89
+ """Clean up orchestrator resources.
90
+
91
+ This method releases resources owned by THIS session only.
92
+ It does NOT cleanup the shared BrowserManager (that's for app shutdown only).
93
+
94
+ Args:
95
+ preserve_workspace: Whether to preserve workspace files
96
+ preserve_agents: Whether to keep agents in registry for continuous chat (default True)
97
+ status: Status to mark in metadata (completed, error, cancelled)
98
+ full_cleanup: If True, release ALL resources (agents, LLMs, tools).
99
+ If False, only release browser contexts (for continuous chat).
100
+ """
101
+ # C-N1: fire on_session_end once, on a full cleanup (fail-open; no-op unless
102
+ # hooks registered). Runs before teardown so hooks can still read session state.
103
+ if full_cleanup and not getattr(self, "_session_end_fired", False):
104
+ self._session_end_fired = True
105
+ run_hooks = getattr(self, "_run_session_end_hooks", None)
106
+ if run_hooks is not None:
107
+ await run_hooks(
108
+ session_id=getattr(self, "session_id", None),
109
+ user_id=getattr(self, "user_id", None),
110
+ status=status,
111
+ )
112
+ # Episodic-memory write: one row per genuine CHAT session completion.
113
+ # Goal/cron runs already record their OWN episode at the dispatcher/
114
+ # runner site — they're marked via autonomy_marker.mark_autonomous, so
115
+ # skip them here to avoid a double-recorded episode for the same run.
116
+ # Fail-open: a memory error must NEVER block session teardown.
117
+ try:
118
+ from agents.task.goals.autonomy_marker import is_autonomous
119
+ _episode_session_id = getattr(self, "session_id", None)
120
+ if not is_autonomous(_episode_session_id):
121
+ outcome = _CLEANUP_STATUS_TO_OUTCOME.get((status or "").lower())
122
+ if outcome is not None:
123
+ from modules.memory.episodic import finalize_episode
124
+ # Task 6 Part A: best-effort H-MEM summary of the closing
125
+ # session, ONLY when the continuity bridge is enabled (keeps
126
+ # the flag-off path byte-identical: summary stays None).
127
+ _summary = None
128
+ try:
129
+ from agents.task.constants import AutonomyConfig
130
+ if AutonomyConfig.continuity_bridge_enabled():
131
+ _summary = _derive_closing_chat_summary(self)
132
+ except Exception:
133
+ _summary = None
134
+ # P2-5: chat episodes were written with no task, no spend, and
135
+ # no steps, so the session-start digest rendered every chat row
136
+ # as `- chat:done $0.00 ""` (and under-reported total spend).
137
+ # Pass the main agent's task and collect_provenance (the goal
138
+ # path already does this). Fail-open — a provenance hiccup must
139
+ # never block the episode write.
140
+ _task = None
141
+ try:
142
+ for _a in (getattr(self, "agents", None) or {}).values():
143
+ _t = getattr(_a, "task", None)
144
+ if _t:
145
+ _task = str(_t)[:500]
146
+ break
147
+ except Exception:
148
+ _task = None
149
+ _prov = {"spend_usd": 0.0, "steps": 0, "artifacts": []}
150
+ try:
151
+ from modules.memory.episodic import collect_provenance
152
+ _prov = await collect_provenance(self)
153
+ except Exception:
154
+ pass
155
+ await finalize_episode(
156
+ session_id=_episode_session_id,
157
+ user_id=getattr(self, "user_id", None),
158
+ kind="chat",
159
+ task=_task,
160
+ outcome=outcome,
161
+ summary=_summary,
162
+ spend_usd=_prov.get("spend_usd", 0.0),
163
+ steps=_prov.get("steps", 0),
164
+ artifacts=_prov.get("artifacts", []),
165
+ thread_key=getattr(self, "_chat_session_key", None),
166
+ meta={"source": "session_end", "status": status},
167
+ )
168
+ except Exception:
169
+ self.logger.warning("chat episodic write failed", exc_info=True)
170
+
171
+ # Computer-use posture: release this session's persistent shell sandbox
172
+ # container + revoke its published loopback ports (best-effort, no-op
173
+ # unless the shell tool was used at posture>=1). Without this the container
174
+ # only gets swept by reap_orphans (>1h) and its loopback ports linger in
175
+ # the SSRF allowlist. Import-guarded + fail-open — never block teardown.
176
+ try:
177
+ from tools.shell.backend_pool import teardown_session as _shell_teardown
178
+ await _shell_teardown(getattr(self, "session_id", None))
179
+ except Exception:
180
+ pass
181
+
182
+ try:
183
+ # Release browser contexts for THIS session
184
+ # Use tracked contexts instead of agents dict to prevent leaks
185
+ if self.browser_manager:
186
+ try:
187
+ released_count = 0
188
+ failed_count = 0
189
+
190
+ # Get contexts to release from dedicated tracking set
191
+ contexts_to_release = set()
192
+ if hasattr(self, '_browser_contexts'):
193
+ contexts_to_release = self._browser_contexts.copy()
194
+ else:
195
+ # Fallback: use agents dict if tracking set not available
196
+ contexts_to_release = set(self.agents.keys())
197
+ self.logger.warning("Using fallback agent keys for browser cleanup (tracking set not found)")
198
+
199
+ # Release each tracked browser context
200
+ for agent_id in contexts_to_release:
201
+ try:
202
+ await self.browser_manager.release_context(agent_id, close=True)
203
+ released_count += 1
204
+ self.logger.debug(f"Released browser context for {agent_id}")
205
+
206
+ # Remove from tracking set
207
+ if hasattr(self, '_browser_contexts'):
208
+ self._browser_contexts.discard(agent_id)
209
+
210
+ # Track context release with telemetry
211
+ if hasattr(self, 'telemetry_manager') and self.telemetry_manager:
212
+ try:
213
+ self.telemetry_manager.capture_event(
214
+ event_type="browser_context_released",
215
+ data={
216
+ "agent_id": agent_id,
217
+ "session_id": self.session_id,
218
+ "status": status
219
+ }
220
+ )
221
+ except Exception:
222
+ pass
223
+ except Exception as e:
224
+ failed_count += 1
225
+ self.logger.error(f"Error releasing context for {agent_id}: {e}")
226
+
227
+ if released_count > 0:
228
+ self.logger.info(
229
+ f"Released {released_count} browser context(s) for session {self.session_id}"
230
+ + (f" ({failed_count} failed)" if failed_count > 0 else "")
231
+ )
232
+
233
+ except Exception as e:
234
+ self.logger.error(f"Error releasing browser contexts: {e}", exc_info=True)
235
+
236
+ # NOTE: Do NOT call browser_manager.cleanup() here!
237
+ # That method closes the ENTIRE shared browser instance and is only
238
+ # meant for application shutdown, not per-session cleanup.
239
+
240
+ # On full (permanent) teardown, forget any cached per-agent browser
241
+ # configs for this session. Best-effort over every agent_id this
242
+ # session ever created (self.agents), not just currently-tracked
243
+ # contexts (_browser_contexts only has ones still allocated -- an
244
+ # agent_id whose context was already released in an earlier turn
245
+ # would otherwise never get its config forgotten).
246
+ if full_cleanup and self.browser_manager:
247
+ for agent_id in list(getattr(self, "agents", {}).keys()):
248
+ try:
249
+ self.browser_manager.forget_session_config(agent_id)
250
+ except Exception:
251
+ pass
252
+
253
+ # REMOVED: TodoManager cleanup - now handled by TaskTool
254
+ # TaskTool manages its own cleanup via _cleanup() method
255
+
256
+ # If full cleanup, release all resources (for session eviction)
257
+ if full_cleanup:
258
+ self.logger.info(f"Performing full cleanup for session {self.session_id}")
259
+
260
+ # PERSISTENCE: Save message history and agent state before cleanup
261
+ # Skip sub-agents - they don't need persistence (their results are captured)
262
+ for agent_id, agent in list(self.agents.items()):
263
+ # Skip sub-agents to prevent context pollution
264
+ if hasattr(agent, '_is_sub_agent') and agent._is_sub_agent:
265
+ self.logger.debug(f"Skipping persistence for sub-agent {agent_id}")
266
+ continue
267
+
268
+ try:
269
+ # Save message history
270
+ if hasattr(agent, 'message_manager') and agent.message_manager:
271
+ agent.message_manager.save_to_disk(
272
+ session_id=self.session_id,
273
+ user_id=self.user_id
274
+ )
275
+ self.logger.info(f"💾 Saved message history for agent {agent_id}")
276
+
277
+ # Save agent state
278
+ if hasattr(agent, 'state') and hasattr(agent.state, 'save_to_file'):
279
+ from agents.task.path import pm
280
+ state_file = pm().create_file_path(
281
+ session_id=self.session_id,
282
+ subdir_name="data",
283
+ filename="agent_state.json",
284
+ user_id=self.user_id
285
+ )
286
+ if agent.state.save_to_file(state_file):
287
+ self.logger.info(f"💾 Saved agent state for agent {agent_id}")
288
+
289
+ # Save + release hierarchical memory (H-MEM). M1: close_session
290
+ # persists to disk AND removes the in-memory HierarchicalMemory
291
+ # entry — save_session alone left TaskContextManager._sessions (a
292
+ # process-wide singleton) growing unbounded on a long-running
293
+ # server / the autonomous goal-cron loop (close_session had no callers).
294
+ if hasattr(agent, 'task_context_manager') and agent.task_context_manager:
295
+ try:
296
+ # P0-7: run close (which may fire a session-close reflection
297
+ # aux-LLM call) OFF the event loop — synchronously here it
298
+ # froze the loop up to ~30s (and deadlocked its own metering).
299
+ # Mirrors the per-step reflection offload (memory_writer.py).
300
+ import asyncio as _asyncio
301
+ closed, _drained = await _asyncio.to_thread(
302
+ agent.task_context_manager.close_session_and_drain,
303
+ self.session_id,
304
+ self.user_id,
305
+ )
306
+ if closed:
307
+ self.logger.info(f"💾 Saved + released hierarchical memory for agent {agent_id}")
308
+ # P0-7: push the consolidated summary + any unsynced
309
+ # findings to the cross-session store (same path the
310
+ # per-step loop uses). Previously discarded at close.
311
+ if _drained:
312
+ try:
313
+ from modules.memory.registry import memory_sync_turn
314
+ await memory_sync_turn(
315
+ getattr(agent, 'task', '') or '',
316
+ "\n".join(_drained),
317
+ session_id=self.session_id,
318
+ user_id=self.user_id,
319
+ )
320
+ except Exception as sync_err:
321
+ self.logger.debug(
322
+ f"close-time memory sync skipped: {sync_err}"
323
+ )
324
+ else:
325
+ self.logger.warning(f"⚠️ No H-MEM session to close for agent {agent_id}")
326
+ except Exception as hmem_error:
327
+ self.logger.error(f"Failed to close H-MEM for agent {agent_id}: {hmem_error}")
328
+ except Exception as e:
329
+ self.logger.error(f"Failed to save state for agent {agent_id}: {e}")
330
+
331
+ # Close all agents
332
+ for agent_id, agent in list(self.agents.items()):
333
+ try:
334
+ if hasattr(agent, 'cleanup'):
335
+ await agent.cleanup()
336
+ self.logger.debug(f"Cleaned up agent {agent_id}")
337
+ except Exception as e:
338
+ self.logger.error(f"Error cleaning up agent {agent_id}: {e}")
339
+
340
+ # M2 FIX: close per-session ISOLATED aux/reflection LLM clients. These are
341
+ # built fresh (not cached, not in self.llm_clients) by
342
+ # _provision_aux_llm(isolated=True) for compaction/reflection, so nothing
343
+ # else closes them — their httpx connection pools would leak per session
344
+ # whenever COMPACTION_MODEL / AUX_* / REFLECTION_LLM_ENABLED is set.
345
+ for agent_id, agent in list(self.agents.items()):
346
+ for owner, attr in (
347
+ (getattr(agent, 'message_manager', None), 'aux_llm'),
348
+ (getattr(agent, 'task_context_manager', None), 'reflection_llm'),
349
+ # P2-8: the cheap 'judge' aux client is cached on the agent by
350
+ # _validate_output / background review / goal judge but was never
351
+ # closed here — its httpx pool leaked per session when
352
+ # AUX_MODEL_JUDGE / VALIDATE_OUTPUT / background review was active.
353
+ (agent, '_judge_llm'),
354
+ ):
355
+ if owner is None:
356
+ continue
357
+ aux = getattr(owner, attr, None)
358
+ if aux is None:
359
+ continue
360
+ # aux is a BaseChatModel adapter wrapping the real client in ._client
361
+ underlying = getattr(aux, '_client', aux)
362
+ try:
363
+ cleanup_fn = getattr(underlying, 'cleanup', None) or getattr(underlying, 'aclose', None)
364
+ if cleanup_fn is not None:
365
+ import asyncio
366
+ if asyncio.iscoroutinefunction(cleanup_fn):
367
+ await cleanup_fn()
368
+ else:
369
+ cleanup_fn()
370
+ self.logger.debug(f"✓ Closed isolated {attr} for agent {agent_id}")
371
+ except Exception as e:
372
+ self.logger.warning(f"Error closing isolated {attr} for {agent_id}: {e}")
373
+ finally:
374
+ try:
375
+ setattr(owner, attr, None)
376
+ except Exception:
377
+ pass
378
+
379
+ # Clear message queues and callbacks from HITL managers
380
+ for agent_id, agent in list(self.agents.items()):
381
+ try:
382
+ if hasattr(agent, 'hitl_manager') and agent.hitl_manager:
383
+ agent.hitl_manager.clear_all_queues()
384
+ agent.hitl_manager.clear_callbacks()
385
+ self.logger.debug(f"Cleared HITL queues/callbacks for agent {agent_id}")
386
+ except Exception as e:
387
+ self.logger.error(f"Error clearing HITL resources for {agent_id}: {e}")
388
+
389
+ # Cancel background tasks
390
+ if hasattr(self, '_execution_tasks') and self._execution_tasks:
391
+ try:
392
+ for task in self._execution_tasks:
393
+ if not task.done():
394
+ task.cancel()
395
+ self._execution_tasks.clear()
396
+ self.logger.debug("Cancelled and cleared background tasks")
397
+ except Exception as e:
398
+ self.logger.error(f"Error cancelling background tasks: {e}")
399
+
400
+ # FIX #14: Clear workspace context cache for this session
401
+ try:
402
+ from agents.task.workspace_context import get_workspace_context
403
+ ws_ctx = get_workspace_context()
404
+ if ws_ctx:
405
+ ws_ctx.clear_session(self.session_id)
406
+ self.logger.debug(f"Cleared workspace context cache for session {self.session_id}")
407
+ except Exception as e:
408
+ self.logger.warning(f"Failed to clear workspace context cache: {e}")
409
+
410
+ # FIX: Cleanup user MCP servers loaded for this session
411
+ try:
412
+ if self.controller:
413
+ mcp_tool = self.controller.get_tool('mcp')
414
+ if mcp_tool and hasattr(mcp_tool, 'unload_user_servers'):
415
+ # C6: unload only THIS session's tenant servers, explicitly.
416
+ # Do NOT set_user_context(None) here — on the shared singleton
417
+ # that would null the context out from under a concurrent
418
+ # session; the per-action path re-sets it before every action.
419
+ unloaded_count = await mcp_tool.unload_user_servers(user_id=self.user_id)
420
+ if unloaded_count > 0:
421
+ self.logger.info(f"🔌 Unloaded {unloaded_count} user MCP servers")
422
+ except Exception as e:
423
+ self.logger.warning(f"Failed to cleanup user MCP servers: {e}")
424
+
425
+ # Clear agent references (only if not explicitly preserving them)
426
+ # Note: We're inside the full_cleanup block, so this gives a way to preserve
427
+ # agents even during full cleanup (useful for graceful session handoff)
428
+ if not preserve_agents:
429
+ agent_count = len(self.agents)
430
+ self.agents.clear()
431
+ self.agent_types.clear()
432
+ self.agent_names.clear()
433
+ self.agent_models.clear()
434
+ self.logger.info(f"Cleared {agent_count} agents from orchestrator during full_cleanup")
435
+ else:
436
+ self.logger.info(
437
+ f"Preserved {len(self.agents)} agents even during full_cleanup "
438
+ f"(agent IDs: {list(self.agents.keys())})"
439
+ )
440
+
441
+ # Close LLM clients if available
442
+ if hasattr(self, 'llm_clients') and self.llm_clients:
443
+ for client_name, client in list(self.llm_clients.items()):
444
+ try:
445
+ # Try multiple cleanup methods - different clients use different patterns
446
+ closed = False
447
+
448
+ # Method 1: async close()
449
+ if hasattr(client, 'close') and callable(client.close):
450
+ import asyncio
451
+ if asyncio.iscoroutinefunction(client.close):
452
+ await client.close()
453
+ else:
454
+ client.close()
455
+ closed = True
456
+
457
+ # Method 2: aclose() (aiohttp pattern)
458
+ elif hasattr(client, 'aclose') and callable(client.aclose):
459
+ await client.aclose()
460
+ closed = True
461
+
462
+ # Method 3: shutdown() (some HTTP clients)
463
+ elif hasattr(client, 'shutdown') and callable(client.shutdown):
464
+ await client.shutdown()
465
+ closed = True
466
+
467
+ # Method 4: Force close underlying HTTP client if accessible
468
+ elif hasattr(client, 'client') and hasattr(client.client, 'aclose'):
469
+ await client.client.aclose()
470
+ closed = True
471
+
472
+ # Method 5: Force close session if accessible
473
+ elif hasattr(client, 'session') and hasattr(client.session, 'close'):
474
+ await client.session.close()
475
+ closed = True
476
+
477
+ if closed:
478
+ self.logger.debug(f"✓ Closed LLM client {client_name}")
479
+ else:
480
+ self.logger.warning(
481
+ f"⚠️ LLM client {client_name} has no close method - "
482
+ f"connection may leak (type: {type(client).__name__})"
483
+ )
484
+ except Exception as e:
485
+ self.logger.error(f"Error closing LLM client {client_name}: {e}", exc_info=True)
486
+ self.llm_clients.clear()
487
+
488
+ # Flush telemetry
489
+ if hasattr(self, 'telemetry_manager') and self.telemetry_manager:
490
+ try:
491
+ await self.telemetry_manager.flush_buffers()
492
+ self.logger.debug("Flushed telemetry buffers")
493
+ except Exception as e:
494
+ self.logger.debug(f"Error flushing telemetry: {e}")
495
+
496
+ # Release controller tools with comprehensive cleanup
497
+ if self.controller:
498
+ try:
499
+ cleaned_count = 0
500
+ failed_count = 0
501
+
502
+ for tool_name in self.controller.list_tools():
503
+ tool = self.controller.get_tool(tool_name)
504
+ if not tool:
505
+ continue
506
+
507
+ try:
508
+ # Try multiple cleanup patterns
509
+ cleaned = False
510
+
511
+ # Method 1: _cleanup() (preferred async)
512
+ if hasattr(tool, '_cleanup') and callable(tool._cleanup):
513
+ import asyncio
514
+ if asyncio.iscoroutinefunction(tool._cleanup):
515
+ await tool._cleanup()
516
+ else:
517
+ tool._cleanup()
518
+ cleaned = True
519
+
520
+ # Method 2: cleanup() (alternative)
521
+ elif hasattr(tool, 'cleanup') and callable(tool.cleanup):
522
+ import asyncio
523
+ if asyncio.iscoroutinefunction(tool.cleanup):
524
+ await tool.cleanup()
525
+ else:
526
+ tool.cleanup()
527
+ cleaned = True
528
+
529
+ # Method 3: close() (HTTP clients, etc.)
530
+ elif hasattr(tool, 'close') and callable(tool.close):
531
+ import asyncio
532
+ if asyncio.iscoroutinefunction(tool.close):
533
+ await tool.close()
534
+ else:
535
+ tool.close()
536
+ cleaned = True
537
+
538
+ if cleaned:
539
+ cleaned_count += 1
540
+ self.logger.debug(f"✓ Cleaned up tool: {tool_name}")
541
+ else:
542
+ self.logger.debug(
543
+ f"⚠️ Tool {tool_name} has no cleanup method "
544
+ f"(type: {type(tool).__name__})"
545
+ )
546
+ except Exception as e:
547
+ failed_count += 1
548
+ self.logger.error(f"Error cleaning up tool {tool_name}: {e}")
549
+
550
+ if cleaned_count > 0 or failed_count > 0:
551
+ self.logger.info(
552
+ f"Cleaned up {cleaned_count} tool(s)"
553
+ + (f" ({failed_count} failed)" if failed_count > 0 else "")
554
+ )
555
+ except Exception as e:
556
+ self.logger.error(f"Error during tool cleanup: {e}")
557
+
558
+ # Create a cleanup marker in the workspace if preserving workspace files
559
+ if preserve_workspace:
560
+ try:
561
+ # Create metadata file to indicate session status
562
+ import json
563
+ import time
564
+ from datetime import datetime
565
+ import os
566
+
567
+ from agents.task.path import pm
568
+ metadata_path = pm().create_file_path(self.session_id, ".", ".session_metadata.json")
569
+ metadata = {
570
+ "session_id": self.session_id,
571
+ "user_id": self.user_id,
572
+ "status": status,
573
+ "cleanup_time": datetime.now().isoformat(),
574
+ "timestamp": time.time(),
575
+ "workspace_preserved": True
576
+ }
577
+
578
+ with open(metadata_path, "w") as f:
579
+ json.dump(metadata, f, indent=2)
580
+
581
+ self.logger.info(f"Created workspace metadata file to mark preserved workspace")
582
+ except Exception as e:
583
+ self.logger.error(f"Error creating workspace metadata: {e}")
584
+
585
+ # Update session status if provided and not overwriting terminal states
586
+ # DEFENSIVE: Don't overwrite terminal statuses (error, failed, cancelled) with non-terminal
587
+ # Terminal states should only be changed by explicit user action or retry logic
588
+ TERMINAL_STATUSES = {'error', 'failed', 'cancelled'}
589
+
590
+ if self.session_manager and status:
591
+ current_status = self.session_manager.get_session_info(self.session_id)
592
+ if current_status:
593
+ current_status_value = current_status.get('status', '').lower()
594
+
595
+ # Protect terminal statuses from being overwritten with non-terminal status
596
+ if current_status_value in TERMINAL_STATUSES and status.lower() not in TERMINAL_STATUSES:
597
+ self.logger.debug(
598
+ f"Preserved terminal status '{current_status_value}', "
599
+ f"not overwriting with: {status}"
600
+ )
601
+ else:
602
+ self.session_manager.update_session_status(self.session_id, status)
603
+ self.logger.debug(f"Updated session status to: {status}")
604
+ else:
605
+ self.session_manager.update_session_status(self.session_id, status)
606
+
607
+ cleanup_type = "full" if full_cleanup else "partial"
608
+ self.logger.info(f"Session {self.session_id} {cleanup_type} cleanup complete, status: {status}")
609
+
610
+ except Exception as e:
611
+ self.logger.error(f"Error during cleanup: {e}")
612
+