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,684 @@
1
+ """polyrob session commands (P3 cli/commands split)."""
2
+ import asyncio
3
+ import json
4
+ import re
5
+ import sys
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import List, Optional
9
+
10
+ import click
11
+
12
+ from cli.ui.events import normalize as _normalize_event
13
+ from cli.ui.plain_renderer import PlainRenderer
14
+ from cli.ui.state import SessionState
15
+
16
+
17
+ @click.group("session")
18
+ def session():
19
+ """Manage task sessions."""
20
+ pass
21
+
22
+
23
+ def find_compaction_checkpoints(data_root: Path, session_id: str) -> List[Path]:
24
+ """Locate `compaction_{n}.json` pre-compaction snapshots for a session (A4).
25
+
26
+ Mirrors the multi-pattern session-dir lookup used by `session tail`, then lists
27
+ the checkpoints under `.../data/history`, sorted by their numeric suffix so the
28
+ recovery order is chronological. Returns [] when the session or history is absent.
29
+ """
30
+ data_root = Path(data_root)
31
+ history_dir = None
32
+ patterns = [
33
+ f"*/{session_id}*/data/history",
34
+ f"*/sessions/{session_id}*/data/history",
35
+ f"*/*{session_id}*/data/history",
36
+ ]
37
+ for pattern in patterns:
38
+ for path in data_root.glob(pattern):
39
+ if path.is_dir():
40
+ history_dir = path
41
+ break
42
+ if history_dir:
43
+ break
44
+
45
+ if not history_dir:
46
+ return []
47
+
48
+ def _n(p: Path) -> int:
49
+ m = re.search(r"compaction_(\d+)\.json$", p.name)
50
+ return int(m.group(1)) if m else 0
51
+
52
+ return sorted(history_dir.glob("compaction_*.json"), key=_n)
53
+
54
+
55
+ @session.command("history")
56
+ @click.argument("session_id")
57
+ @click.option("--dump", type=int, default=None, metavar="N",
58
+ help="Print the full JSON of checkpoint N instead of just listing.")
59
+ def session_history(session_id: str, dump):
60
+ """List (or --dump) compaction checkpoints for a session."""
61
+ import json
62
+ from core.bootstrap import setup_project_path, setup_sqlite_compat
63
+ setup_project_path()
64
+ setup_sqlite_compat()
65
+
66
+ from agents.task.path import pm
67
+
68
+ checkpoints = find_compaction_checkpoints(pm().data_root, session_id)
69
+ if not checkpoints:
70
+ click.echo(f"No compaction checkpoints found for session matching '{session_id}'")
71
+ return
72
+
73
+ if dump is not None:
74
+ match = next((p for p in checkpoints
75
+ if re.search(rf"compaction_{dump}\.json$", p.name)), None)
76
+ if not match:
77
+ click.echo(click.style("[polyrob] ", fg="red") + f"No checkpoint #{dump} (have: "
78
+ + ", ".join(re.search(r'compaction_(\d+)', p.name).group(1) for p in checkpoints) + ")")
79
+ sys.exit(1)
80
+ try:
81
+ click.echo(json.dumps(json.loads(match.read_text()), indent=2))
82
+ except (json.JSONDecodeError, OSError) as e:
83
+ click.echo(click.style("[polyrob] ", fg="red") + f"Could not read {match.name}: {e}")
84
+ sys.exit(1)
85
+ return
86
+
87
+ click.echo(click.style("[polyrob] ", fg="cyan") + f"{len(checkpoints)} checkpoint(s):")
88
+ for p in checkpoints:
89
+ n = re.search(r"compaction_(\d+)", p.name).group(1)
90
+ size = p.stat().st_size
91
+ click.echo(f" #{n:<4} {p.name:<24} {size:>8} bytes {p}")
92
+ click.echo("\nInspect one with: " + click.style(f"polyrob session history {session_id} --dump <N>", fg="cyan"))
93
+
94
+
95
+ @session.command("list")
96
+ @click.option("--all", "show_all", is_flag=True, help="Show all sessions including completed")
97
+ @click.option("--json", "as_json", is_flag=True, help="Print machine-readable JSON.")
98
+ def session_list(show_all: bool, as_json: bool):
99
+ """List recent sessions."""
100
+ asyncio.run(_session_list(show_all, as_json))
101
+
102
+
103
+ async def _session_list(show_all: bool, as_json: bool):
104
+ import logging as _logging
105
+ from core.bootstrap import build_cli_container, setup_project_path, setup_sqlite_compat
106
+ setup_project_path()
107
+ setup_sqlite_compat()
108
+
109
+ _logging.disable(_logging.CRITICAL)
110
+ from cli.keys import preflight_or_onboard
111
+ if not preflight_or_onboard(interactive=False):
112
+ sys.exit(1)
113
+ container = await build_cli_container(log_level="ERROR")
114
+ _logging.disable(_logging.ERROR)
115
+
116
+ task_agent = container.get_agent("task_agent")
117
+ if not task_agent:
118
+ click.echo("TaskAgent not available")
119
+ sys.exit(1)
120
+
121
+ sessions = task_agent.session_manager.get_all_sessions()
122
+
123
+ if not show_all:
124
+ # F9: match the "active" semantics used by the session-limit check and
125
+ # SessionManager.get_active_sessions (session.py:503) — 'suspended' is
126
+ # NOT active there, so excluding it here keeps the CLI's default list in
127
+ # lockstep with what counts against the per-user cap. Use --all to see
128
+ # suspended/completed/failed/cancelled sessions.
129
+ active_statuses = {"created", "running", "resumed"}
130
+ sessions = [s for s in sessions if s.get("status") in active_statuses]
131
+
132
+ if not sessions:
133
+ click.echo("No sessions found.")
134
+ return
135
+
136
+ if as_json:
137
+ click.echo(json.dumps(sessions, indent=2, default=str))
138
+ return
139
+
140
+ click.echo(f"{'ID':<12} {'Status':<12} {'Task':<40} {'Created'}")
141
+ click.echo("-" * 80)
142
+
143
+ for s in sessions:
144
+ # NB: `.get(k, default)` returns None when the key is PRESENT with a null
145
+ # value (default only applies to absent keys), so `(x or default)` is
146
+ # required before slicing — a null task/created_at/id crashed session list.
147
+ sid = (s.get("id") or s.get("session_id") or "?")[:10]
148
+ status = s.get("status") or "?"
149
+ task_str = (s.get("task") or "")[:38]
150
+ created = (s.get("created_at") or "")[:19]
151
+
152
+ color = {"running": "green", "created": "cyan", "suspended": "yellow",
153
+ "completed": "white", "failed": "red", "cancelled": "red"}.get(status, "white")
154
+ click.echo(f"{sid:<12} {click.style(status, fg=color):<21} {task_str:<40} {created}")
155
+
156
+
157
+ @session.command("tail")
158
+ @click.argument("session_id")
159
+ def session_tail(session_id: str):
160
+ """Stream a session's feed (reads from feed directory)."""
161
+ asyncio.run(_session_tail(session_id))
162
+
163
+
164
+ async def _session_tail(session_id: str):
165
+ import json
166
+ from core.bootstrap import setup_project_path, setup_sqlite_compat
167
+ setup_project_path()
168
+ setup_sqlite_compat()
169
+
170
+ from agents.task.path import pm
171
+
172
+ # Find feed directory — try multiple path patterns
173
+ feed_dir = None
174
+ patterns = [
175
+ f"*/{session_id}*/feed", # data/task/{user}/{session_id}/feed
176
+ f"*/sessions/{session_id}*/feed", # data/auto/{user}/sessions/{session_id}/feed
177
+ f"*/*{session_id}*/feed", # partial match
178
+ ]
179
+ for pattern in patterns:
180
+ for path in pm().data_root.glob(pattern):
181
+ if path.is_dir():
182
+ feed_dir = path
183
+ break
184
+ if feed_dir:
185
+ break
186
+
187
+ if not feed_dir or not feed_dir.exists():
188
+ click.echo(f"No feed found for session matching '{session_id}'")
189
+ sys.exit(1)
190
+
191
+ click.echo(click.style("[polyrob] ", fg="cyan") + f"Tailing feed: {feed_dir}")
192
+
193
+ feed_files = sorted(feed_dir.glob("*.json"))
194
+ _state = SessionState()
195
+ _renderer = PlainRenderer(state=_state, stream=sys.stdout)
196
+ for f in feed_files:
197
+ try:
198
+ data = json.loads(f.read_text())
199
+ event = _normalize_event(data)
200
+ _state.update(event)
201
+ _renderer.on_event(event)
202
+ except (json.JSONDecodeError, OSError):
203
+ pass
204
+
205
+ click.echo(click.style("[polyrob] ", fg="cyan") + f"End of feed ({len(feed_files)} entries)")
206
+
207
+
208
+ @session.command("cancel")
209
+ @click.argument("session_id")
210
+ def session_cancel(session_id: str):
211
+ """Cancel a running session."""
212
+ asyncio.run(_session_cancel(session_id))
213
+
214
+
215
+ async def _session_cancel(session_id: str):
216
+ import logging as _logging
217
+ from core.bootstrap import build_cli_container, setup_project_path, setup_sqlite_compat
218
+ setup_project_path()
219
+ setup_sqlite_compat()
220
+
221
+ _logging.disable(_logging.CRITICAL)
222
+ from cli.keys import preflight_or_onboard
223
+ if not preflight_or_onboard(interactive=False):
224
+ sys.exit(1)
225
+ container = await build_cli_container(log_level="ERROR")
226
+ _logging.disable(_logging.ERROR)
227
+
228
+ task_agent = container.get_agent("task_agent")
229
+ if not task_agent:
230
+ click.echo("TaskAgent not available")
231
+ sys.exit(1)
232
+
233
+ user_id = container.get_service("identity").resolve()
234
+ success = await task_agent.cancel_session(user_id=user_id, session_id=session_id)
235
+ if success:
236
+ click.echo(click.style("[polyrob] ", fg="green") + f"Session {session_id} cancelled")
237
+ else:
238
+ click.echo(click.style("[polyrob] ", fg="red") + f"Failed to cancel session {session_id}")
239
+ sys.exit(1)
240
+
241
+
242
+ @session.command("show")
243
+ @click.argument("session_id")
244
+ @click.option("--json", "as_json", is_flag=True, help="Print machine-readable JSON.")
245
+ def session_show(session_id: str, as_json: bool):
246
+ """Show detailed information about a session."""
247
+ asyncio.run(_session_show(session_id, as_json))
248
+
249
+
250
+ async def _session_show(session_id: str, as_json: bool):
251
+ import logging as _logging
252
+ from core.bootstrap import build_cli_container, setup_project_path, setup_sqlite_compat
253
+ setup_project_path()
254
+ setup_sqlite_compat()
255
+
256
+ _logging.disable(_logging.CRITICAL)
257
+ from cli.keys import preflight_or_onboard
258
+ if not preflight_or_onboard(interactive=False):
259
+ sys.exit(1)
260
+ container = await build_cli_container(log_level="ERROR")
261
+ _logging.disable(_logging.ERROR)
262
+
263
+ task_agent = container.get_agent("task_agent")
264
+ if not task_agent:
265
+ click.echo("TaskAgent not available")
266
+ sys.exit(1)
267
+
268
+ session_info = task_agent.session_manager.get_session_info(session_id)
269
+ if not session_info:
270
+ click.echo(click.style("[polyrob] ", fg="red") + f"Session {session_id} not found")
271
+ sys.exit(1)
272
+
273
+ if as_json:
274
+ click.echo(json.dumps(session_info, indent=2, default=str))
275
+ return
276
+
277
+ sid16 = (session_info.get('id') or session_id)[:16]
278
+ click.echo(f"{click.style(sid16, fg='cyan', bold=True)}: {session_info.get('task') or 'No task'}")
279
+ click.echo(f" status: {session_info.get('status', '?')}")
280
+ click.echo(f" created: {session_info.get('created_at', '?')}")
281
+ if session_info.get('updated_at'):
282
+ click.echo(f" updated: {session_info.get('updated_at')}")
283
+ if session_info.get('model'):
284
+ click.echo(f" model: {session_info.get('model')}")
285
+ if session_info.get('provider'):
286
+ click.echo(f" provider: {session_info.get('provider')}")
287
+
288
+
289
+ @session.command("attach")
290
+ @click.argument("session_id")
291
+ def session_attach(session_id: str):
292
+ """Attach to a running session (NOT YET IMPLEMENTED — use `polyrob run --resume`)."""
293
+ click.echo(click.style("[polyrob] ", fg="yellow")
294
+ + "attach is not yet implemented - continue a session with "
295
+ "'polyrob run --resume <id>', or 'polyrob session tail <id>' to watch it")
296
+ click.echo("To monitor an existing session, use: polyrob session tail <id>")
297
+
298
+
299
+ @session.command("pause")
300
+ @click.argument("session_id")
301
+ def session_pause(session_id: str):
302
+ """Pause a running session."""
303
+ asyncio.run(_session_pause(session_id))
304
+
305
+
306
+ async def _session_pause(session_id: str):
307
+ import logging as _logging
308
+ from core.bootstrap import build_cli_container, setup_project_path, setup_sqlite_compat
309
+ setup_project_path()
310
+ setup_sqlite_compat()
311
+
312
+ _logging.disable(_logging.CRITICAL)
313
+ from cli.keys import preflight_or_onboard
314
+ if not preflight_or_onboard(interactive=False):
315
+ sys.exit(1)
316
+ container = await build_cli_container(log_level="ERROR")
317
+ _logging.disable(_logging.ERROR)
318
+
319
+ task_agent = container.get_agent("task_agent")
320
+ if not task_agent:
321
+ click.echo("TaskAgent not available")
322
+ sys.exit(1)
323
+
324
+ user_id = container.get_service("identity").resolve()
325
+ success = await task_agent.pause_session(user_id=user_id, session_id=session_id)
326
+ if success:
327
+ click.echo(click.style("[polyrob] ", fg="green") + f"Session {session_id} paused")
328
+ else:
329
+ click.echo(click.style("[polyrob] ", fg="red") + f"Failed to pause session {session_id}")
330
+ sys.exit(1)
331
+
332
+
333
+ @session.command("resume")
334
+ @click.argument("session_id")
335
+ def session_resume(session_id: str):
336
+ """Resume a paused session."""
337
+ asyncio.run(_session_resume(session_id))
338
+
339
+
340
+ async def _session_resume(session_id: str):
341
+ import logging as _logging
342
+ from core.bootstrap import build_cli_container, setup_project_path, setup_sqlite_compat
343
+ setup_project_path()
344
+ setup_sqlite_compat()
345
+
346
+ _logging.disable(_logging.CRITICAL)
347
+ from cli.keys import preflight_or_onboard
348
+ if not preflight_or_onboard(interactive=False):
349
+ sys.exit(1)
350
+ container = await build_cli_container(log_level="ERROR")
351
+ _logging.disable(_logging.ERROR)
352
+
353
+ task_agent = container.get_agent("task_agent")
354
+ if not task_agent:
355
+ click.echo("TaskAgent not available")
356
+ sys.exit(1)
357
+
358
+ user_id = container.get_service("identity").resolve()
359
+ success = await task_agent.resume_session(user_id=user_id, session_id=session_id)
360
+ if success:
361
+ click.echo(click.style("[polyrob] ", fg="green") + f"Session {session_id} marked resumable")
362
+ click.echo(" Continue execution with: polyrob run --resume " + session_id)
363
+ else:
364
+ click.echo(click.style("[polyrob] ", fg="red") + f"Failed to resume session {session_id}")
365
+ sys.exit(1)
366
+
367
+
368
+ @session.command("export")
369
+ @click.argument("session_id")
370
+ @click.option("--output", "-o", type=click.Path(), help="Output file path.")
371
+ @click.option("--format", type=click.Choice(["json", "txt"]), default="json", help="Export format.")
372
+ def session_export(session_id: str, output: Optional[str], format: str):
373
+ """Export a session's data (transcript, messages, artifacts)."""
374
+ asyncio.run(_session_export(session_id, output, format))
375
+
376
+
377
+ async def _session_export(session_id: str, output: Optional[str], format: str):
378
+ import logging as _logging
379
+ from core.bootstrap import build_cli_container, setup_project_path, setup_sqlite_compat
380
+ setup_project_path()
381
+ setup_sqlite_compat()
382
+
383
+ _logging.disable(_logging.CRITICAL)
384
+ from cli.keys import preflight_or_onboard
385
+ if not preflight_or_onboard(interactive=False):
386
+ sys.exit(1)
387
+ container = await build_cli_container(log_level="ERROR")
388
+ _logging.disable(_logging.ERROR)
389
+
390
+ from agents.task.path import pm
391
+
392
+ # Fetch session metadata (status/model/task/created) to FOLD INTO the export —
393
+ # previously this was fetched then discarded, so the export omitted it.
394
+ ta = task_agent_or_none(container)
395
+ session_info = ta.session_manager.get_session_info(session_id) if ta else None
396
+
397
+ if output is None:
398
+ output = f"{session_id}_export.{format}"
399
+
400
+ # Find session directory
401
+ data_root = pm().data_root
402
+ session_dir = None
403
+ patterns = [
404
+ f"*/{session_id}*/",
405
+ f"*/sessions/{session_id}*/",
406
+ f"*/*{session_id}*/",
407
+ ]
408
+ for pattern in patterns:
409
+ for path in data_root.glob(pattern):
410
+ if path.is_dir():
411
+ session_dir = path
412
+ break
413
+ if session_dir:
414
+ break
415
+
416
+ if not session_dir:
417
+ click.echo(click.style("[polyrob] ", fg="red") + f"Session directory not found for {session_id}")
418
+ sys.exit(1)
419
+
420
+ export_data = _assemble_export_data(
421
+ session_id, session_info, session_dir, datetime.now().isoformat()
422
+ )
423
+
424
+ # Write output
425
+ if format == "json":
426
+ with open(output, "w") as f:
427
+ json.dump(export_data, f, indent=2, default=str)
428
+ else:
429
+ sess = export_data.get("session") or {}
430
+ with open(output, "w") as f:
431
+ f.write(f"Session Export: {session_id}\n")
432
+ f.write(f"Exported: {export_data['exported_at']}\n")
433
+ f.write(f"Directory: {session_dir}\n")
434
+ if sess.get("status"):
435
+ f.write(f"Status: {sess['status']}\n")
436
+ if sess.get("model"):
437
+ f.write(f"Model: {sess['model']}\n")
438
+ if "messages" in export_data:
439
+ f.write(f"Messages: {len(export_data['messages'])}\n")
440
+
441
+ click.echo(click.style("[polyrob] ", fg="green") + f"Exported to {output}")
442
+
443
+
444
+ def _summarize_llm_usage(session_dir) -> Optional[dict]:
445
+ """Aggregate the per-call ``llm_usage_*.json`` records under a session dir.
446
+
447
+ Returns ``{records, total_tokens, total_cost_estimate}`` or None if there are
448
+ none. Reads the REAL per-call files (the old fallback read a never-written
449
+ ``usage.json``) plus a legacy ``usage.json`` if present. Robust to schema drift:
450
+ the record count is always meaningful; token/cost are best-effort sums.
451
+ """
452
+ d = Path(session_dir) / "data" / "llm_usage"
453
+ if not d.exists():
454
+ return None
455
+ records = []
456
+ for f in sorted(d.glob("llm_usage_*.json")):
457
+ try:
458
+ records.append(json.loads(f.read_text()))
459
+ except (json.JSONDecodeError, OSError):
460
+ continue
461
+ legacy = d / "usage.json"
462
+ if legacy.exists():
463
+ try:
464
+ data = json.loads(legacy.read_text())
465
+ records.extend(data if isinstance(data, list) else [data])
466
+ except (json.JSONDecodeError, OSError):
467
+ pass
468
+ if not records:
469
+ return None
470
+ tokens = sum((r.get("token_count") or 0) for r in records if isinstance(r, dict))
471
+ cost = sum((r.get("cost_estimate") or 0) for r in records if isinstance(r, dict))
472
+ return {"records": len(records), "total_tokens": tokens, "total_cost_estimate": round(cost, 6)}
473
+
474
+
475
+ def _assemble_export_data(session_id, session_info, session_dir, exported_at) -> dict:
476
+ """Build the export payload from disk + the fetched session metadata (pure)."""
477
+ session_dir = Path(session_dir)
478
+ export_data = {
479
+ "session_id": session_id,
480
+ "exported_at": exported_at,
481
+ "session_dir": str(session_dir),
482
+ }
483
+ if session_info:
484
+ meta = {
485
+ k: session_info.get(k)
486
+ for k in ("status", "model", "provider", "task", "created_at", "updated_at")
487
+ if session_info.get(k) is not None
488
+ }
489
+ if meta:
490
+ export_data["session"] = meta
491
+ history_file = session_dir / "message_history.json"
492
+ if history_file.exists():
493
+ try:
494
+ export_data["messages"] = json.loads(history_file.read_text())
495
+ except (json.JSONDecodeError, OSError):
496
+ export_data["messages"] = []
497
+ feed_dir = session_dir / "feed"
498
+ if feed_dir.exists():
499
+ export_data["feed_events"] = len(sorted(feed_dir.glob("*.json")))
500
+ return export_data
501
+
502
+
503
+ def task_agent_or_none(container):
504
+ """Get task_agent from container without noise."""
505
+ import logging as _logging
506
+ _logging.disable(_logging.CRITICAL)
507
+ task_agent = container.get_agent("task_agent")
508
+ _logging.disable(_logging.ERROR)
509
+ return task_agent
510
+
511
+
512
+ @session.command("artifacts")
513
+ @click.argument("session_id")
514
+ def session_artifacts(session_id: str):
515
+ """List artifacts (screenshots, downloads, outputs) for a session."""
516
+ from agents.task.path import pm
517
+
518
+ data_root = pm().data_root
519
+ session_dir = None
520
+ patterns = [
521
+ f"*/{session_id}*/",
522
+ f"*/sessions/{session_id}*/",
523
+ f"*/*{session_id}*/",
524
+ ]
525
+ for pattern in patterns:
526
+ for path in data_root.glob(pattern):
527
+ if path.is_dir():
528
+ session_dir = path
529
+ break
530
+ if session_dir:
531
+ break
532
+
533
+ if not session_dir:
534
+ click.echo(click.style("[polyrob] ", fg="red") + f"Session directory not found for {session_id}")
535
+ sys.exit(1)
536
+
537
+ click.echo(f"Artifacts for session {session_id[:16]}:")
538
+
539
+ # Screenshots
540
+ screenshots_dir = session_dir / "screenshots"
541
+ if screenshots_dir.exists():
542
+ screenshots = list(screenshots_dir.glob("*"))
543
+ click.echo(f" Screenshots: {len(screenshots)}")
544
+ for s in screenshots[:10]: # Show first 10
545
+ click.echo(f" {s.name}")
546
+
547
+ # Workspace files
548
+ workspace_dir = session_dir / "workspace"
549
+ if workspace_dir.exists():
550
+ files = list(workspace_dir.rglob("*"))
551
+ files = [f for f in files if f.is_file()]
552
+ click.echo(f" Workspace files: {len(files)}")
553
+ for f in files[:10]:
554
+ click.echo(f" {f.relative_to(workspace_dir)}")
555
+
556
+
557
+ @session.command("costs")
558
+ @click.argument("session_id")
559
+ @click.option("--json", "as_json", is_flag=True, help="Print machine-readable JSON.")
560
+ def session_costs(session_id: str, as_json: bool):
561
+ """Show cost breakdown for a session."""
562
+ asyncio.run(_session_costs(session_id, as_json))
563
+
564
+
565
+ async def _session_costs(session_id: str, as_json: bool):
566
+ import logging as _logging
567
+ from core.bootstrap import build_cli_container, setup_project_path, setup_sqlite_compat
568
+ setup_project_path()
569
+ setup_sqlite_compat()
570
+
571
+ _logging.disable(_logging.CRITICAL)
572
+ from cli.keys import preflight_or_onboard
573
+ if not preflight_or_onboard(interactive=False):
574
+ sys.exit(1)
575
+ container = await build_cli_container(log_level="ERROR")
576
+ _logging.disable(_logging.ERROR)
577
+
578
+ task_agent = container.get_agent("task_agent")
579
+ if not task_agent:
580
+ click.echo("TaskAgent not available")
581
+ sys.exit(1)
582
+
583
+ # Try to get breakdown from usage tracker
584
+ orchestrator = None
585
+ try:
586
+ orchestrator = task_agent.get_orchestrator(session_id)
587
+ except Exception:
588
+ pass
589
+
590
+ costs = {"session_id": session_id, "breakdown": "unavailable"}
591
+
592
+ if orchestrator and hasattr(orchestrator, "usage_tracker"):
593
+ try:
594
+ breakdown = await orchestrator.usage_tracker.get_session_breakdown(session_id)
595
+ costs["breakdown"] = breakdown
596
+ except Exception:
597
+ pass
598
+
599
+ # Fallback: read llm_usage files
600
+ if costs["breakdown"] == "unavailable":
601
+ from agents.task.path import pm
602
+ data_root = pm().data_root
603
+ session_dir = None
604
+ patterns = [
605
+ f"*/{session_id}*/",
606
+ f"*/sessions/{session_id}*/",
607
+ f"*/*{session_id}*/",
608
+ ]
609
+ for pattern in patterns:
610
+ for path in data_root.glob(pattern):
611
+ if path.is_dir():
612
+ session_dir = path
613
+ break
614
+ if session_dir:
615
+ break
616
+
617
+ if session_dir:
618
+ usage = _summarize_llm_usage(session_dir)
619
+ if usage:
620
+ costs["llm_usage"] = usage
621
+
622
+ if as_json:
623
+ click.echo(json.dumps(costs, indent=2, default=str))
624
+ else:
625
+ click.echo(f"Costs for session {session_id[:16]}:")
626
+ if isinstance(costs["breakdown"], dict):
627
+ total = costs["breakdown"].get("total_credits", 0)
628
+ click.echo(f" Total credits: {total}")
629
+ elif costs.get("llm_usage"):
630
+ u = costs["llm_usage"]
631
+ click.echo(f" On-disk usage: {u['records']} call(s), "
632
+ f"{u['total_tokens']} tokens, ~${u['total_cost_estimate']} est.")
633
+ else:
634
+ click.echo(f" {costs['breakdown']}")
635
+
636
+
637
+ @session.command("tools")
638
+ @click.argument("session_id")
639
+ @click.option("--json", "as_json", is_flag=True, help="Print machine-readable JSON.")
640
+ def session_tools(session_id: str, as_json: bool):
641
+ """Show tools used in a session."""
642
+ from agents.task.path import pm
643
+
644
+ data_root = pm().data_root
645
+ session_dir = None
646
+ patterns = [
647
+ f"*/{session_id}*/",
648
+ f"*/sessions/{session_id}*/",
649
+ f"*/*{session_id}*/",
650
+ ]
651
+ for pattern in patterns:
652
+ for path in data_root.glob(pattern):
653
+ if path.is_dir():
654
+ session_dir = path
655
+ break # inside the is_dir guard (was mis-indented; cf. session_artifacts)
656
+ if session_dir:
657
+ break
658
+
659
+ if not session_dir:
660
+ click.echo(click.style("[polyrob] ", fg="red") + f"Session directory not found for {session_id}")
661
+ sys.exit(1)
662
+
663
+ # Scan feed for tool calls
664
+ feed_dir = session_dir / "feed"
665
+ tool_calls = {}
666
+ if feed_dir.exists():
667
+ for feed_file in sorted(feed_dir.glob("*.json")):
668
+ try:
669
+ data = json.loads(feed_file.read_text())
670
+ if data.get("type") == "tool_call":
671
+ name = data.get("name", "unknown")
672
+ tool_calls[name] = tool_calls.get(name, 0) + 1
673
+ except (json.JSONDecodeError, OSError):
674
+ pass
675
+
676
+ if as_json:
677
+ click.echo(json.dumps(tool_calls, indent=2))
678
+ else:
679
+ click.echo(f"Tools used in session {session_id[:16]}:")
680
+ if tool_calls:
681
+ for name, count in sorted(tool_calls.items(), key=lambda x: -x[1]):
682
+ click.echo(f" {name}: {count}")
683
+ else:
684
+ click.echo(" (no tool calls found)")