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,91 @@
1
+ """Session registry — maps session_id -> SessionOrchestrator.
2
+
3
+ This is the single seam through which the active in-memory orchestrators are
4
+ looked up and mutated. Today it wraps a plain in-process dict, which is what
5
+ forces ``UVICORN_WORKERS=1`` in production (an orchestrator created in one
6
+ worker is invisible to another). Keeping every access behind this interface
7
+ means the future swap to a cross-process, SQLite-backed registry (WAL mode,
8
+ jittered retry — see ``docs/ROB_CORE_SERVER_SPLIT_SPEC.md`` Phase E) is
9
+ mechanical rather than a scavenger hunt through ``task_agent_lite``.
10
+
11
+ The interface is intentionally small and dict-flavoured so callers read
12
+ naturally:
13
+
14
+ registry.register(session_id, orchestrator)
15
+ orch = registry.get(session_id)
16
+ orch = registry.remove(session_id) # pop, returns None if absent
17
+ if session_id in registry: ...
18
+ for sid, orch in registry.items(): ...
19
+ len(registry)
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Any, Dict, Iterator, List, Optional, Tuple
25
+
26
+
27
+ class SessionRegistry:
28
+ """In-process registry of active session orchestrators.
29
+
30
+ Not thread/process shared. See module docstring for the scaling caveat.
31
+ """
32
+
33
+ def __init__(self) -> None:
34
+ self._orchestrators: Dict[str, Any] = {}
35
+
36
+ def register(self, session_id: str, orchestrator: Any, *, params: Optional[Dict[str, Any]] = None) -> None:
37
+ """Store (or replace) the orchestrator for ``session_id``.
38
+
39
+ ``params`` is accepted (and ignored) for signature parity with the
40
+ SQLite-backed registry, so callers can pass it without crashing whichever
41
+ backend ``SESSION_REGISTRY_BACKEND`` selected.
42
+ """
43
+ self._orchestrators[session_id] = orchestrator
44
+
45
+ def get(self, session_id: str) -> Optional[Any]:
46
+ """Return the orchestrator for ``session_id`` or ``None``."""
47
+ return self._orchestrators.get(session_id)
48
+
49
+ def route(self, session_id: str):
50
+ """Routing decision (P6). In-process registry knows only local sessions, so
51
+ the result is either LOCAL (handle here) or MISSING."""
52
+ from agents.task.session_route import SessionRoute, LOCAL, MISSING
53
+ orch = self._orchestrators.get(session_id)
54
+ if orch is not None:
55
+ return SessionRoute(status=LOCAL, orchestrator=orch)
56
+ return SessionRoute(status=MISSING)
57
+
58
+ def remove(self, session_id: str) -> Optional[Any]:
59
+ """Remove and return the orchestrator for ``session_id`` (``None`` if absent)."""
60
+ return self._orchestrators.pop(session_id, None)
61
+
62
+ def contains(self, session_id: str) -> bool:
63
+ return session_id in self._orchestrators
64
+
65
+ def count(self) -> int:
66
+ return len(self._orchestrators)
67
+
68
+ def items(self) -> List[Tuple[str, Any]]:
69
+ """Snapshot of (session_id, orchestrator) pairs — safe to mutate while iterating."""
70
+ return list(self._orchestrators.items())
71
+
72
+ def values(self) -> List[Any]:
73
+ """Snapshot of the registered orchestrators."""
74
+ return list(self._orchestrators.values())
75
+
76
+ def session_ids(self) -> List[str]:
77
+ return list(self._orchestrators.keys())
78
+
79
+ def clear(self) -> None:
80
+ self._orchestrators.clear()
81
+
82
+ # --- dict-flavoured conveniences ---
83
+
84
+ def __contains__(self, session_id: str) -> bool:
85
+ return session_id in self._orchestrators
86
+
87
+ def __len__(self) -> int:
88
+ return len(self._orchestrators)
89
+
90
+ def __iter__(self) -> Iterator[str]:
91
+ return iter(list(self._orchestrators.keys()))
@@ -0,0 +1,35 @@
1
+ """Cross-worker session routing result (roadmap P6).
2
+
3
+ The routing *decision* a worker makes for an incoming session request: handle it
4
+ locally, report which worker owns it (so the caller can forward / return a
5
+ meaningful 409 instead of a false 404), or report it missing. Built on the
6
+ SQLite registry's cross-process visibility (``exists``/``owner_pid``); actual
7
+ request forwarding (HTTP/IPC proxy) is a thin layer on top of this decision.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from typing import Any, Optional
13
+
14
+ LOCAL = "local" # owned by this worker; orchestrator is in-process
15
+ REMOTE = "remote" # exists but owned by another worker (owner_pid set)
16
+ MISSING = "missing" # no such active session anywhere
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class SessionRoute:
21
+ status: str # LOCAL | REMOTE | MISSING
22
+ orchestrator: Optional[Any] = None # set only when LOCAL
23
+ owner_pid: Optional[int] = None # set for LOCAL and REMOTE
24
+
25
+ @property
26
+ def is_local(self) -> bool:
27
+ return self.status == LOCAL
28
+
29
+ @property
30
+ def is_remote(self) -> bool:
31
+ return self.status == REMOTE
32
+
33
+ @property
34
+ def is_missing(self) -> bool:
35
+ return self.status == MISSING
@@ -0,0 +1,261 @@
1
+ """SQLite-backed session registry (roadmap P6 / §33 — the scale unlock).
2
+
3
+ The in-process :class:`SessionRegistry` forces ``UVICORN_WORKERS=1``: an
4
+ orchestrator created in one worker is invisible to another, so a request that
5
+ lands on the wrong worker 404s. This variant keeps the **local object dict**
6
+ (the live orchestrator can't cross processes) but ALSO mirrors session metadata to
7
+ a shared SQLite ``active_sessions`` table (WAL + jittered retry) so every worker
8
+ can see which sessions exist and who owns them — the routing foundation for
9
+ ``workers>1``.
10
+
11
+ Drop-in compatible with ``SessionRegistry`` (register/get/remove/contains/count/…)
12
+ plus cross-process methods: ``exists``, ``owner_pid``, ``global_session_ids``,
13
+ ``global_count``, ``heartbeat``, ``reap_stale``.
14
+
15
+ Scope note: this gives cross-worker *visibility*, not transparent cross-worker
16
+ *method calls* on a remote orchestrator (that needs sticky routing / IPC — a
17
+ follow-up). With it, a worker can return a meaningful "owned by pid N / elsewhere"
18
+ result instead of a false 404.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import os
24
+ import uuid
25
+ from datetime import datetime, timedelta
26
+ from typing import Any, Dict, Iterator, List, Optional, Tuple
27
+
28
+ from core.sqlite_util import execute_retry
29
+
30
+
31
+ class SqliteSessionRegistry:
32
+ def __init__(self, db_path: str, *, worker_pid: Optional[int] = None) -> None:
33
+ self.db_path = db_path
34
+ self._pid = worker_pid or os.getpid()
35
+ # Per-process boot id: ownership is keyed on this, NOT the (reusable) pid.
36
+ # Two processes that happen to share a pid (PID reuse after a worker dies)
37
+ # still get distinct boot ids, so routing never mistakes one for the other.
38
+ self._boot_id = uuid.uuid4().hex
39
+ parent = os.path.dirname(db_path)
40
+ if parent:
41
+ os.makedirs(parent, exist_ok=True)
42
+ self._orchestrators: Dict[str, Any] = {}
43
+ self._init_schema()
44
+
45
+ def _init_schema(self) -> None:
46
+ # owner_boot_id is in the CREATE so a FRESH DB never needs the ALTER — this
47
+ # avoids the concurrent-boot race where two workers both PRAGMA-see the column
48
+ # missing and both ALTER (the second crashing with "duplicate column name").
49
+ execute_retry(
50
+ self.db_path,
51
+ """
52
+ CREATE TABLE IF NOT EXISTS active_sessions (
53
+ session_id TEXT PRIMARY KEY,
54
+ worker_pid INTEGER NOT NULL,
55
+ status TEXT NOT NULL DEFAULT 'active',
56
+ params TEXT NOT NULL DEFAULT '{}',
57
+ last_seen_at TEXT NOT NULL,
58
+ created_at TEXT NOT NULL,
59
+ owner_boot_id TEXT
60
+ )
61
+ """,
62
+ )
63
+ # Migration for pre-existing DBs created before owner_boot_id. (No
64
+ # ALTER ... IF NOT EXISTS in SQLite.) Tolerate the "duplicate column name"
65
+ # error so a concurrent boot that adds it first can't crash this worker.
66
+ cols = execute_retry(
67
+ self.db_path, "PRAGMA table_info(active_sessions)", fetch="all",
68
+ ) or []
69
+ names = {row["name"] for row in cols}
70
+ if "owner_boot_id" not in names:
71
+ try:
72
+ execute_retry(
73
+ self.db_path,
74
+ "ALTER TABLE active_sessions ADD COLUMN owner_boot_id TEXT",
75
+ )
76
+ except Exception as e: # concurrent add won the race — that's fine
77
+ if "duplicate column" not in str(e).lower():
78
+ raise
79
+
80
+ # --- local object dict (compatible with SessionRegistry) -----------------
81
+
82
+ def register(self, session_id: str, orchestrator: Any, *, params: Optional[Dict[str, Any]] = None) -> None:
83
+ # Write the DB row BEFORE the local dict. route() reads the DB; if the DB
84
+ # write raised after we'd already stored the object locally, route() would
85
+ # return MISSING for a session this worker actually holds — a false 404, the
86
+ # exact failure this class exists to prevent.
87
+ now = datetime.now().isoformat()
88
+ execute_retry(
89
+ self.db_path,
90
+ """
91
+ INSERT INTO active_sessions
92
+ (session_id, worker_pid, owner_boot_id, status, params, last_seen_at, created_at)
93
+ VALUES (?,?,?,?,?,?,?)
94
+ ON CONFLICT(session_id) DO UPDATE SET
95
+ worker_pid=excluded.worker_pid, owner_boot_id=excluded.owner_boot_id,
96
+ status='active', params=excluded.params, last_seen_at=excluded.last_seen_at
97
+ """,
98
+ (session_id, self._pid, self._boot_id, "active", json.dumps(params or {}), now, now),
99
+ )
100
+ self._orchestrators[session_id] = orchestrator
101
+
102
+ def get(self, session_id: str) -> Optional[Any]:
103
+ """Return the LOCAL orchestrator object (None if owned by another worker)."""
104
+ return self._orchestrators.get(session_id)
105
+
106
+ def remove(self, session_id: str) -> Optional[Any]:
107
+ execute_retry(self.db_path, "DELETE FROM active_sessions WHERE session_id=?", (session_id,))
108
+ return self._orchestrators.pop(session_id, None)
109
+
110
+ def contains(self, session_id: str) -> bool:
111
+ return session_id in self._orchestrators
112
+
113
+ def count(self) -> int:
114
+ return len(self._orchestrators)
115
+
116
+ def items(self) -> List[Tuple[str, Any]]:
117
+ return list(self._orchestrators.items())
118
+
119
+ def values(self) -> List[Any]:
120
+ return list(self._orchestrators.values())
121
+
122
+ def session_ids(self) -> List[str]:
123
+ return list(self._orchestrators.keys())
124
+
125
+ def clear(self) -> None:
126
+ for sid in list(self._orchestrators):
127
+ execute_retry(self.db_path, "DELETE FROM active_sessions WHERE session_id=?", (sid,))
128
+ self._orchestrators.clear()
129
+
130
+ # --- cross-process metadata (SQLite) -------------------------------------
131
+
132
+ def exists(self, session_id: str) -> bool:
133
+ row = execute_retry(
134
+ self.db_path, "SELECT 1 FROM active_sessions WHERE session_id=?", (session_id,), fetch="one",
135
+ )
136
+ return row is not None
137
+
138
+ def route(self, session_id: str):
139
+ """Routing decision (P6): LOCAL (object is in this worker), REMOTE (exists but
140
+ owned by another worker — caller can forward / 409 with owner_pid), or MISSING."""
141
+ from agents.task.session_route import SessionRoute, LOCAL, REMOTE, MISSING
142
+ row = execute_retry(
143
+ self.db_path,
144
+ "SELECT worker_pid, owner_boot_id FROM active_sessions WHERE session_id=?",
145
+ (session_id,), fetch="one",
146
+ )
147
+ if row is None:
148
+ return SessionRoute(status=MISSING)
149
+ owner = int(row["worker_pid"])
150
+ owner_boot = row["owner_boot_id"]
151
+ # LOCAL only when the OWNING boot id is ours (pid match alone is not enough:
152
+ # a reused pid in a fresh process must not be treated as the same owner).
153
+ local = self._orchestrators.get(session_id)
154
+ if owner_boot == self._boot_id and local is not None:
155
+ return SessionRoute(status=LOCAL, orchestrator=local, owner_pid=owner)
156
+ return SessionRoute(status=REMOTE, owner_pid=owner)
157
+
158
+ def owner_pid(self, session_id: str) -> Optional[int]:
159
+ row = execute_retry(
160
+ self.db_path, "SELECT worker_pid FROM active_sessions WHERE session_id=?", (session_id,), fetch="one",
161
+ )
162
+ return int(row["worker_pid"]) if row else None
163
+
164
+ def owner_boot_id(self, session_id: str) -> Optional[str]:
165
+ row = execute_retry(
166
+ self.db_path, "SELECT owner_boot_id FROM active_sessions WHERE session_id=?", (session_id,), fetch="one",
167
+ )
168
+ return row["owner_boot_id"] if row else None
169
+
170
+ def global_session_ids(self) -> List[str]:
171
+ rows = execute_retry(self.db_path, "SELECT session_id FROM active_sessions", fetch="all")
172
+ return [r["session_id"] for r in rows]
173
+
174
+ def global_count(self) -> int:
175
+ row = execute_retry(self.db_path, "SELECT COUNT(*) AS c FROM active_sessions", fetch="one")
176
+ return int(row["c"]) if row else 0
177
+
178
+ def heartbeat(self, session_id: str) -> None:
179
+ execute_retry(
180
+ self.db_path, "UPDATE active_sessions SET last_seen_at=? WHERE session_id=?",
181
+ (datetime.now().isoformat(), session_id),
182
+ )
183
+
184
+ @staticmethod
185
+ def _pid_alive(pid: Optional[int]) -> bool:
186
+ """True if ``pid`` is a live process on THIS host.
187
+
188
+ Same-host multi-worker (uvicorn workers) is the supported ``workers>1``
189
+ deployment, so a signal-0 liveness probe is meaningful. Be conservative:
190
+ an unknown/None pid or any unexpected error is treated as ALIVE so we never
191
+ reap a possibly-live owner. (A cross-host DB would need a heartbeat-based
192
+ liveness signal; that deployment is out of scope for the live orchestrator.)
193
+ """
194
+ if not pid or pid <= 0:
195
+ return False
196
+ try:
197
+ os.kill(pid, 0)
198
+ return True
199
+ except ProcessLookupError:
200
+ return False
201
+ except PermissionError:
202
+ return True # exists but owned by another user
203
+ except Exception:
204
+ return True
205
+
206
+ def reap_stale(self, ttl_seconds: int = 300) -> List[str]:
207
+ """Remove sessions whose last_seen_at is older than ttl. Returns reaped ids.
208
+
209
+ A row is reaped ONLY if it is truly abandoned: stale AND not held locally AND
210
+ its owning worker process is DEAD. A session sitting IDLE between user turns
211
+ does not step, so its heartbeat freezes — but it is still alive in its owning
212
+ worker and must never be reaped (deleting the row would force a false-404 and
213
+ leak that worker's orchestrator). Rows we hold locally, and rows whose
214
+ ``worker_pid`` is still alive on this host, are skipped; only genuinely
215
+ dead-worker rows are cleaned up. Complements the per-step heartbeat.
216
+ """
217
+ cutoff = (datetime.now() - timedelta(seconds=ttl_seconds)).isoformat()
218
+ # Sessions alive in THIS process, regardless of heartbeat staleness.
219
+ local_sids = set(self._orchestrators.keys())
220
+ rows = execute_retry(
221
+ self.db_path,
222
+ "SELECT session_id, worker_pid FROM active_sessions WHERE last_seen_at < ?",
223
+ (cutoff,), fetch="all",
224
+ )
225
+ # Reap only rows that are (a) not ours and (b) owned by a DEAD process. A
226
+ # stale row whose owner worker is still alive is an idle session on another
227
+ # worker — deleting it would false-404 and orphan that worker's orchestrator.
228
+ reaped = [
229
+ r["session_id"] for r in rows
230
+ if r["session_id"] not in local_sids and not self._pid_alive(r["worker_pid"])
231
+ ]
232
+ for sid in reaped:
233
+ # Re-check under the cutoff: don't delete a row that heartbeated between
234
+ # the SELECT and the DELETE (and re-skip if it somehow became local).
235
+ if sid in self._orchestrators:
236
+ continue
237
+ execute_retry(
238
+ self.db_path,
239
+ "DELETE FROM active_sessions WHERE session_id=? AND last_seen_at < ?",
240
+ (sid, cutoff),
241
+ )
242
+ self._orchestrators.pop(sid, None)
243
+ return reaped
244
+
245
+ # test helper: force a row's last_seen_at (e.g. into the past) for reap tests.
246
+ def _set_last_seen(self, session_id: str, iso: str) -> None:
247
+ execute_retry(self.db_path, "UPDATE active_sessions SET last_seen_at=? WHERE session_id=?", (iso, session_id))
248
+
249
+ # backwards-compatible alias
250
+ _set_last_seen_for_test = _set_last_seen
251
+
252
+ # --- dict-flavoured conveniences -----------------------------------------
253
+
254
+ def __contains__(self, session_id: str) -> bool:
255
+ return session_id in self._orchestrators
256
+
257
+ def __len__(self) -> int:
258
+ return len(self._orchestrators)
259
+
260
+ def __iter__(self) -> Iterator[str]:
261
+ return iter(list(self._orchestrators.keys()))
@@ -0,0 +1,185 @@
1
+ """Feature flags for the Singular Chat Interface (Surface contract).
2
+
3
+ Read through this class (NOT BotConfig.get, which is getattr-backed and returns
4
+ the default for any unknown key -> a gate read that way is permanently off).
5
+ Reuses constants._bool_env so default-ON and default-OFF flags share one parser.
6
+ """
7
+ import os
8
+ from typing import Optional
9
+
10
+ from agents.task.constants import _bool_env, local_mode_enabled
11
+
12
+
13
+ def _int_env(name: str, default: int) -> int:
14
+ try:
15
+ v = os.getenv(name)
16
+ return int(v) if v is not None and v.strip() != "" else default
17
+ except (TypeError, ValueError):
18
+ return default
19
+
20
+
21
+ class SurfaceConfig:
22
+ @staticmethod
23
+ def singular_chat_enabled() -> bool:
24
+ return _bool_env("SINGULAR_CHAT_ENABLED", False)
25
+
26
+ @staticmethod
27
+ def telegram_surface_enabled() -> bool:
28
+ return _bool_env("TELEGRAM_SURFACE_ENABLED", False)
29
+
30
+ @staticmethod
31
+ def whatsapp_surface_enabled() -> bool:
32
+ return _bool_env("WHATSAPP_SURFACE_ENABLED", False)
33
+
34
+ @staticmethod
35
+ def email_surface_enabled() -> bool:
36
+ """Run the email surface (IMAP poll inbound + SMTP outbound). Default OFF.
37
+ v1 is correspondent-only — owner-by-email stays OFF until verified-sender lands."""
38
+ return _bool_env("EMAIL_SURFACE_ENABLED", False)
39
+
40
+ @staticmethod
41
+ def email_imap_poll_sec() -> int:
42
+ """Seconds between IMAP polls for new mail (no IDLE in v1)."""
43
+ return _int_env("EMAIL_IMAP_POLL_SEC", 60)
44
+
45
+ @staticmethod
46
+ def chat_intent_classifier_enabled() -> bool:
47
+ return _bool_env("CHAT_INTENT_CLASSIFIER", False)
48
+
49
+ # --- WS-A three-tier access model (owner/correspondent/denied) -----------
50
+ @staticmethod
51
+ def correspondent_access_enabled() -> bool:
52
+ """Resolve every inbound to an access tier (OWNER/CORRESPONDENT/DENIED) at the
53
+ routing boundary. Default OFF — when off, route_inbound is byte-identical to
54
+ today (only the legacy pairing gate applies). When on, a non-owner is routable
55
+ only as a known correspondent (its reply is DATA delivered to the originating
56
+ session, never a steering turn)."""
57
+ return _bool_env("CORRESPONDENT_ACCESS_ENABLED", False)
58
+
59
+ @staticmethod
60
+ def correspondent_require_approval() -> bool:
61
+ """A newly auto-seeded correspondent is PENDING (owner must approve) before its
62
+ replies are routable. Default ON — a third party the agent contacted does not
63
+ become a trusted-data channel until the owner ratifies it. Set
64
+ CORRESPONDENT_REQUIRE_APPROVAL=false for single-user/local convenience."""
65
+ return _bool_env("CORRESPONDENT_REQUIRE_APPROVAL", True)
66
+
67
+ @staticmethod
68
+ def correspondent_max_new_per_day() -> int:
69
+ """Per-tenant cap on NEW correspondents seeded per 24h — bounds the blast radius
70
+ of an injection that tricks the agent into mass-contacting addresses."""
71
+ return _int_env("CORRESPONDENT_MAX_NEW_PER_DAY", 20)
72
+
73
+ # --- #9 voice transcription ---------------------------------------------
74
+ @staticmethod
75
+ def voice_transcription_enabled() -> bool:
76
+ """Transcribe inbound voice/audio messages to text before routing (#9).
77
+ Default ON: when the faster-whisper extra is installed, voice notes are
78
+ transcribed; when it isn't, the surface degrades gracefully (the inbound guard
79
+ tells the user voice is unavailable instead of routing an empty turn).
80
+ Set VOICE_TRANSCRIPTION_ENABLED=false to turn it off."""
81
+ return _bool_env("VOICE_TRANSCRIPTION_ENABLED", True)
82
+
83
+ @staticmethod
84
+ def voice_transcription_model() -> str:
85
+ """faster-whisper model size: tiny|base|small|medium|large-v3. `base` balances
86
+ CPU latency vs accuracy for short voice notes."""
87
+ return (os.getenv("VOICE_TRANSCRIPTION_MODEL") or "base").strip()
88
+
89
+ @staticmethod
90
+ def voice_transcription_required() -> bool:
91
+ """When ON, a missing faster-whisper engine is a startup ERROR (not just WARN).
92
+ Default OFF: most deploys degrade gracefully (voice refused with a notice)."""
93
+ return _bool_env("VOICE_TRANSCRIPTION_REQUIRED", False)
94
+
95
+ @staticmethod
96
+ def voice_transcript_echo_enabled() -> bool:
97
+ """Echo the transcript back into the chat as a persistent, voice-note-anchored
98
+ message before the agent answers (Telegram + WhatsApp). Default ON; purely additive.
99
+ Set VOICE_TRANSCRIPT_ECHO=false for byte-identical prior behavior."""
100
+ return _bool_env("VOICE_TRANSCRIPT_ECHO", True)
101
+
102
+ # --- #8 Telegram incremental streaming ----------------------------------
103
+ @staticmethod
104
+ def telegram_incremental_stream() -> bool:
105
+ """Live `editMessageText` streaming on Telegram (#8). Default OFF — the buffered
106
+ one-send-on-finalize path stays the safe default; opt in per deployment."""
107
+ return _bool_env("TELEGRAM_INCREMENTAL_STREAM", False)
108
+
109
+ @staticmethod
110
+ def telegram_stream_edit_interval_sec() -> float:
111
+ """Minimum seconds between live stream edits (flood-control). Telegram tolerates
112
+ only a few edits/sec; 1.5s is conservative. `0` edits on every delta (tests)."""
113
+ try:
114
+ v = os.getenv("TELEGRAM_STREAM_EDIT_INTERVAL_SEC")
115
+ return float(v) if v is not None and v.strip() != "" else 1.5
116
+ except (TypeError, ValueError):
117
+ return 1.5
118
+
119
+ # --- P0.1 session-boundary policy ---------------------------------------
120
+ @staticmethod
121
+ def session_reset_mode() -> str:
122
+ """idle | daily | both | none. Default `idle` everywhere (#7): a fresh thread
123
+ after a cooldown is natural and memory recall bridges the gap. The server flip
124
+ (none->idle) is gated on the recreate-race (#2) + mute-on-resume (#0) fixes,
125
+ both landed, so a reset can't drop a reply or double-build an orchestrator.
126
+ Pin `SESSION_RESET_MODE=none` to restore the legacy inert behavior; `daily` is
127
+ opt-in (server-local-tz skew). The idle WINDOW still differs by profile
128
+ (`session_idle_minutes`: 720 local / 1440 server)."""
129
+ return (os.getenv("SESSION_RESET_MODE") or "idle").strip().lower()
130
+
131
+ @staticmethod
132
+ def session_idle_minutes() -> int:
133
+ """Idle threshold; a chat older than this since last activity starts fresh.
134
+ Default 1440 (24h); 720 (12h) under POLYROB_LOCAL (a personal bot rolls sooner)."""
135
+ return _int_env("SESSION_IDLE_MINUTES", 720 if local_mode_enabled() else 1440)
136
+
137
+ @staticmethod
138
+ def session_reset_hour() -> int:
139
+ """Local hour (0-23) for the daily session roll."""
140
+ return _int_env("SESSION_RESET_HOUR", 4)
141
+
142
+ # --- outbound durability -------------------------------------------------
143
+ @staticmethod
144
+ def outbound_queue_enabled() -> bool:
145
+ """Route final outbound messages through the durable OutboundDeliveryQueue
146
+ (retry + dead-letter). Default OFF -> publish() sends directly (legacy). Streaming
147
+ deltas are always best-effort (never queued)."""
148
+ return _bool_env("OUTBOUND_QUEUE_ENABLED", False)
149
+
150
+ # --- a5 surface GC -------------------------------------------------------
151
+ @staticmethod
152
+ def surface_gc_enabled() -> bool:
153
+ """Periodic GC of stale chat<->session bindings (a5). Default ON under the local
154
+ profile when the chat bus is on; OFF on the multi-tenant server until validated."""
155
+ return _bool_env(
156
+ "SURFACE_GC_ENABLED",
157
+ local_mode_enabled() and SurfaceConfig.singular_chat_enabled(),
158
+ )
159
+
160
+ @staticmethod
161
+ def surface_gc_horizon_secs() -> int:
162
+ """Bindings with no activity for longer than this are purged. ``max(2x idle
163
+ window, 7d)`` so GC never races the idle-reset boundary — a pointer is only
164
+ dropped well after it would already start a fresh thread."""
165
+ return max(SurfaceConfig.session_idle_minutes() * 60 * 2, 7 * 86400)
166
+
167
+ # --- webhook signing secrets ------------------------------------------------
168
+ @staticmethod
169
+ def whatsapp_template_name() -> str:
170
+ """Approved utility template used to re-open the 24h window for a proactive msg."""
171
+ return (os.getenv("WHATSAPP_TEMPLATE_NAME") or "task_ready").strip()
172
+
173
+ @staticmethod
174
+ def webhook_secret(surface_id: str) -> "Optional[str]":
175
+ """Read {SURFACE}_WEBHOOK_SECRET from env. Surface id is upper-cased:
176
+ whatsapp -> WHATSAPP_WEBHOOK_SECRET. Returns stripped value or None."""
177
+ v = os.getenv(f"{surface_id.upper()}_WEBHOOK_SECRET")
178
+ return v.strip() if v and v.strip() else None
179
+
180
+ @staticmethod
181
+ def webhook_verify_token(surface_id: str) -> "Optional[str]":
182
+ """Read {SURFACE}_VERIFY_TOKEN from env. Surface id is upper-cased:
183
+ whatsapp -> WHATSAPP_VERIFY_TOKEN. Returns stripped value or None."""
184
+ v = os.getenv(f"{surface_id.upper()}_VERIFY_TOKEN")
185
+ return v.strip() if v and v.strip() else None
@@ -0,0 +1,58 @@
1
+ """
2
+ Telemetry package for task.
3
+
4
+ This package provides functionality for capturing telemetry events
5
+ and metrics from the task system.
6
+ """
7
+
8
+ import logging
9
+
10
+ # Setup logging
11
+ from agents.task.logging_config import get_task_logger
12
+ logger = get_task_logger('telemetry')
13
+ if not logger.handlers:
14
+ handler = logging.StreamHandler()
15
+ formatter = logging.Formatter('%(asctime)s %(levelname)-8s [%(name)s] %(message)s')
16
+ handler.setFormatter(formatter)
17
+ logger.addHandler(handler)
18
+ logger.setLevel(logging.INFO)
19
+
20
+ # Import primary classes - safe imports that don't create circular dependencies
21
+ from agents.task.telemetry.views import (
22
+ BaseTelemetryEvent,
23
+ AgentRunTelemetryEvent,
24
+ AgentStepTelemetryEvent,
25
+ AgentEndTelemetryEvent,
26
+ LLMRequestTelemetryEvent,
27
+ SessionRelationshipTelemetryEvent,
28
+ InterfaceTelemetryEvent,
29
+ SessionStartTelemetryEvent,
30
+ SessionCompletionTelemetryEvent,
31
+ ProviderFailureEvent,
32
+ ProviderFallbackSuccessEvent
33
+ )
34
+
35
+ # Lazy import ProductTelemetry to avoid circular references
36
+ # This way consumers can import the module without triggering additional imports
37
+ ProductTelemetry = None
38
+
39
+ def get_telemetry():
40
+ """Get the ProductTelemetry instance safely using singleton pattern"""
41
+ from agents.task.path import get_safe_singleton
42
+ from agents.task.telemetry.service import ProductTelemetry
43
+ return get_safe_singleton(ProductTelemetry)()
44
+
45
+ __all__ = [
46
+ "get_telemetry", # New function for safe access
47
+ "BaseTelemetryEvent",
48
+ "AgentRunTelemetryEvent",
49
+ "AgentStepTelemetryEvent",
50
+ "AgentEndTelemetryEvent",
51
+ "LLMRequestTelemetryEvent",
52
+ "SessionRelationshipTelemetryEvent",
53
+ "InterfaceTelemetryEvent",
54
+ "SessionStartTelemetryEvent",
55
+ "SessionCompletionTelemetryEvent",
56
+ "ProviderFailureEvent",
57
+ "ProviderFallbackSuccessEvent"
58
+ ]