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
core/sqlite_util.py ADDED
@@ -0,0 +1,59 @@
1
+ """Small shared SQLite helper: WAL connection + jittered write-retry (roadmap P6).
2
+
3
+ Centralizes the WAL-with-DELETE-fallback + 'database is locked' retry pattern so
4
+ the cross-process session registry (and, later, the cron store) don't each
5
+ re-implement it. Mirrors Reference ``SessionDB``: WAL for concurrent readers + one
6
+ writer, DELETE fallback on NFS/SMB/FUSE, and 20–150ms jitter to avoid SQLite's
7
+ deterministic backoff convoy under contention.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import random
12
+ import sqlite3
13
+ import time
14
+ from typing import Optional
15
+
16
+ _MAX_RETRIES = 15
17
+ _BUSY_TIMEOUT_S = 1.0
18
+
19
+
20
+ def wal_connect(db_path: str, timeout: float = _BUSY_TIMEOUT_S) -> sqlite3.Connection:
21
+ conn = sqlite3.connect(db_path, timeout=timeout)
22
+ conn.row_factory = sqlite3.Row
23
+ try:
24
+ conn.execute("PRAGMA journal_mode=WAL")
25
+ except sqlite3.OperationalError:
26
+ conn.execute("PRAGMA journal_mode=DELETE")
27
+ return conn
28
+
29
+
30
+ def execute_retry(db_path: str, sql: str, params: tuple = (), *, fetch: Optional[str] = None):
31
+ """Execute one statement with jittered retry on write contention.
32
+
33
+ fetch: None -> rowcount; 'one' -> a Row or None; 'all' -> list[Row].
34
+ """
35
+ last_err: Optional[Exception] = None
36
+ for _ in range(_MAX_RETRIES):
37
+ try:
38
+ conn = wal_connect(db_path)
39
+ try:
40
+ cur = conn.execute(sql, params)
41
+ if fetch == "one":
42
+ row = cur.fetchone()
43
+ conn.commit()
44
+ return row
45
+ if fetch == "all":
46
+ rows = cur.fetchall()
47
+ conn.commit()
48
+ return rows
49
+ conn.commit()
50
+ return cur.rowcount
51
+ finally:
52
+ conn.close()
53
+ except sqlite3.OperationalError as e:
54
+ if "locked" in str(e).lower() or "busy" in str(e).lower():
55
+ last_err = e
56
+ time.sleep(random.uniform(0.02, 0.15))
57
+ continue
58
+ raise
59
+ raise last_err # type: ignore[misc]
@@ -0,0 +1,16 @@
1
+ """Singular Chat Interface — transport-free surface contract (core)."""
2
+ from core.surfaces.envelopes import (
3
+ MessageKind, SessionSource, Identity, InboundMessage,
4
+ OutboundMessage, SurfaceCapabilities, SendResult,
5
+ )
6
+ from core.surfaces.surface import Surface
7
+ from core.surfaces.session_chat_registry import SessionChatRegistry, build_session_key
8
+ from core.surfaces.message_router import MessageRouter
9
+ from core.surfaces.registry import SurfaceRegistry, register_surface, is_surface_enabled
10
+
11
+ __all__ = [
12
+ "MessageKind", "SessionSource", "Identity", "InboundMessage",
13
+ "OutboundMessage", "SurfaceCapabilities", "SendResult", "Surface",
14
+ "SessionChatRegistry", "build_session_key", "MessageRouter",
15
+ "SurfaceRegistry", "register_surface", "is_surface_enabled",
16
+ ]
@@ -0,0 +1,118 @@
1
+ """WS-A access-tier resolver — classify one inbound's sender into a trust tier.
2
+
3
+ Three tiers, resolved ONCE at the routing boundary and carried into the dispatcher:
4
+
5
+ - **OWNER** — the bound owner principal, the local single-user operator, or a paired
6
+ user. May command the agent (steering turn).
7
+ - **CORRESPONDENT** — a third party the agent INITIATED contact with, i.e. an ACTIVE
8
+ binding in the correspondent registry. Their inbound is DATA delivered only to the
9
+ originating session; it can never command the agent.
10
+ - **DENIED** — anonymous, unknown, group/multi-party, or anything that isn't clearly
11
+ one of the above.
12
+
13
+ Invariants:
14
+ - **Tier = authenticated sender**, never thread membership (the registry keys on the
15
+ sender address; ``thread_id`` only disambiguates among that sender's own sessions).
16
+ - **Fail-closed on the CORRESPONDENT→OWNER boundary**: any fault degrades toward
17
+ DENIED and never UPGRADES a sender to OWNER.
18
+ - **Groups/channels are DENIED in v1** — the envelope is single-principal, so there is
19
+ no safe per-author tiering inside a multi-party chat yet.
20
+
21
+ Pure decision function; no surface/transport imports. Reads ``POLYROB_LOCAL`` directly
22
+ (like ``core/pairing``) to stay on the core side of the core→agents boundary.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ import os
28
+ from enum import Enum
29
+ from typing import Any, Mapping, Optional
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ _BOOL_TRUE = {"1", "true", "yes", "on"}
34
+
35
+ # Single-user local mode (`is_owner(local=True)`) grants OWNER to any non-empty uid.
36
+ # That is correct ONLY for surfaces whose principal is the local operator — NEVER for a
37
+ # network surface where the sender id is a forgeable remote address (Fusion CRITICAL:
38
+ # else any email/telegram sender becomes an owner command-turn under POLYROB_LOCAL).
39
+ _LOCAL_OWNER_SURFACES = {"cli", "local", "repl"}
40
+
41
+
42
+ class AccessTier(str, Enum):
43
+ OWNER = "owner"
44
+ CORRESPONDENT = "correspondent"
45
+ DENIED = "denied"
46
+
47
+
48
+ def _is_owner_or_paired(container: Any, uid: str, env: Mapping[str, str],
49
+ *, allow_local: bool) -> bool:
50
+ """True if ``uid`` is the owner/local operator or a paired user. Fail-closed.
51
+
52
+ ``allow_local`` gates the single-user local-owner bypass: it is only honoured for a
53
+ trusted local surface (never a network surface — see ``_LOCAL_OWNER_SURFACES``).
54
+ """
55
+ try:
56
+ from core.instance import is_owner, resolve_owner_principal
57
+ local = allow_local and (env.get("POLYROB_LOCAL", "") or "").strip().lower() in _BOOL_TRUE
58
+ if is_owner(uid, owner_principal=resolve_owner_principal(env), local=local):
59
+ return True
60
+ except Exception as e: # never let an owner-check fault grant or crash
61
+ logger.debug("access-tier owner check failed (fail-closed): %s", e)
62
+ try:
63
+ from core.pairing import PairingStore
64
+ cfg = getattr(container, "config", None) if container else None
65
+ data_dir = getattr(cfg, "data_dir", "data") or "data"
66
+ store = PairingStore(os.path.join(data_dir, "pairing.db"))
67
+ if store.is_paired(uid):
68
+ return True
69
+ except Exception as e:
70
+ logger.debug("access-tier pairing check failed (fail-closed): %s", e)
71
+ return False
72
+
73
+
74
+ def resolve_access_tier(
75
+ container: Any,
76
+ identity: Any,
77
+ *,
78
+ thread_id: Optional[str] = None,
79
+ env: Optional[Mapping[str, str]] = None,
80
+ ) -> AccessTier:
81
+ """Classify ``identity`` into an :class:`AccessTier`. Never raises."""
82
+ src = os.environ if env is None else env
83
+ try:
84
+ uid = (str(getattr(identity, "user_id", "")) or "").strip()
85
+ if not uid:
86
+ return AccessTier.DENIED
87
+
88
+ source = getattr(identity, "source", None)
89
+ surface = getattr(source, "surface_id", "") or ""
90
+ chat_type = getattr(source, "chat_type", "dm") or "dm"
91
+
92
+ # v1: no per-author tiering inside a multi-party chat -> DENIED.
93
+ if chat_type != "dm":
94
+ return AccessTier.DENIED
95
+
96
+ if _is_owner_or_paired(container, uid, src,
97
+ allow_local=surface in _LOCAL_OWNER_SURFACES):
98
+ return AccessTier.OWNER
99
+
100
+ # Non-owner: routable ONLY as a known correspondent (active binding).
101
+ registry = container.get_service("correspondent_registry") if container else None
102
+ if registry is not None:
103
+ address = getattr(identity, "raw_user_id", None) or uid
104
+ try:
105
+ row = registry.resolve(surface=surface, address=address, thread_id=thread_id)
106
+ except Exception as e: # fail-closed: a registry fault is NOT an upgrade
107
+ logger.debug("access-tier registry resolve failed (fail-closed): %s", e)
108
+ row = None
109
+ if row is not None:
110
+ return AccessTier.CORRESPONDENT
111
+
112
+ return AccessTier.DENIED
113
+ except Exception as e: # absolute fail-closed backstop
114
+ logger.debug("resolve_access_tier fault (fail-closed to DENIED): %s", e)
115
+ return AccessTier.DENIED
116
+
117
+
118
+ __all__ = ["AccessTier", "resolve_access_tier"]
@@ -0,0 +1,88 @@
1
+ """P1b-2: bind a session's orchestrator to the Singular Chat outbound bus.
2
+
3
+ Called from TaskAgent.create_session immediately after the orchestrator is built
4
+ and BEFORE orchestrator.initialize() — because _register_stream_callback captures
5
+ orchestrator._message_router / ._chat_session_key BY VALUE when it wires the stream
6
+ mirror, so binding after initialize() would capture None (a permanent no-op).
7
+
8
+ Flag-gated (SINGULAR_CHAT_ENABLED, default OFF) and fail-open: when OFF, or the bus
9
+ isn't installed, or no chat_session_key was supplied (legacy callers: cron/goal/
10
+ `polyrob run`/raw API), it touches NOTHING and returns False -> the orchestrator keeps
11
+ its legacy callback-only path, byte-identical to today.
12
+ """
13
+ import logging
14
+ from typing import Any, Optional
15
+
16
+ from core.surfaces.envelopes import SessionSource
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def bind_chat_surface(
22
+ orchestrator: Any,
23
+ container: Any,
24
+ *,
25
+ session_source: Optional[SessionSource],
26
+ chat_session_key: Optional[str],
27
+ session_id: str,
28
+ user_id: str,
29
+ ) -> bool:
30
+ """Set the orchestrator's router+key and write the chat<->session registry row.
31
+
32
+ Returns True when the orchestrator was bound (router+key set), False when the
33
+ feature is off / unavailable / no key was supplied.
34
+ """
35
+ from agents.task.surface_config import SurfaceConfig
36
+
37
+ if not SurfaceConfig.singular_chat_enabled():
38
+ return False
39
+ if container is None or not chat_session_key:
40
+ return False
41
+ router = container.get_service("message_router")
42
+ if router is None:
43
+ return False
44
+
45
+ orchestrator._message_router = router
46
+ orchestrator._chat_session_key = chat_session_key
47
+
48
+ # Write the durable chat<->session row so publish can resolve key -> surface.
49
+ # Needs the SessionSource (surface_id/chat_id); without it we still bind the
50
+ # mirror but publish will fail-open-drop until a row exists.
51
+ if session_source is not None:
52
+ registry = container.get_service("session_chat_registry")
53
+ if registry is not None:
54
+ try:
55
+ registry.bind(
56
+ chat_session_key, session_id, user_id,
57
+ session_source.surface_id, session_source.chat_id,
58
+ )
59
+ except Exception as e: # fail-open: a bind error must not break session create
60
+ logger.debug("bind_chat_surface registry.bind failed: %s", e)
61
+ return True
62
+
63
+
64
+ def surface_ask_capability(orchestrator: Any) -> Optional[bool]:
65
+ """Can the surface bound to this orchestrator collect a reply (ask)?
66
+
67
+ Returns True/False when a surface is bound and its capability is known, or None
68
+ when nothing is bound / the bus is absent (the legacy, surface-agnostic case).
69
+ Used by the send_message action to avoid the wait_for_response deadlock: pausing
70
+ a session to wait for a reply on a surface that can't deliver one would hang it.
71
+ """
72
+ key = getattr(orchestrator, "_chat_session_key", None)
73
+ container = getattr(orchestrator, "container", None)
74
+ if not key or container is None:
75
+ return None
76
+ try:
77
+ registry = container.get_service("session_chat_registry")
78
+ row = registry.resolve(key) if registry is not None else None
79
+ if not row:
80
+ return None
81
+ surface_registry = container.get_service("surface_registry")
82
+ surface = surface_registry.get(row.get("surface_id")) if surface_registry else None
83
+ if surface is None:
84
+ return None
85
+ return bool(surface.capabilities.supports_interactive_ask)
86
+ except Exception as e: # fail-open: unknown -> legacy behavior
87
+ logger.debug("surface_ask_capability failed: %s", e)
88
+ return None
@@ -0,0 +1,78 @@
1
+ """P1b-0: construct + register the outbound bus on a DI container.
2
+
3
+ This is the one prerequisite the rest of the Singular Chat Interface depends on:
4
+ nothing else in the codebase instantiates MessageRouter / SessionChatRegistry, so
5
+ without this call container.get_service("message_router") is always None and every
6
+ P1a outbound mirror (plus cron/delivery.py's telegram sink) is silently inert.
7
+
8
+ Gated on SINGULAR_CHAT_ENABLED so flag-OFF means the services never exist -> the
9
+ mirrors stay no-ops -> behavior is byte-identical to today. Idempotent: a second
10
+ call reuses the already-installed router so live surface subscriptions are never
11
+ dropped. Fail-open: any construction error logs and returns False rather than
12
+ breaking startup.
13
+ """
14
+ import logging
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def install_surface_bus(container, db_path: str = None) -> bool:
20
+ """Build SessionChatRegistry + MessageRouter and register them on ``container``.
21
+
22
+ Returns True if the bus is present on the container after the call (installed
23
+ now or already present), False if the flag is OFF or construction failed.
24
+
25
+ ``db_path`` defaults to ``<container.config.data_dir>/surfaces.db`` so the bus,
26
+ outbox, and circuit store follow POLYROB_DATA_DIR isolation instead of a hardcoded
27
+ ``./data`` inside the code tree. Pass an explicit path to override.
28
+ """
29
+ from agents.task.surface_config import SurfaceConfig
30
+
31
+ if not SurfaceConfig.singular_chat_enabled():
32
+ return False
33
+
34
+ # Idempotent: never clobber a live router (it holds surface subscriptions).
35
+ existing = container.get_service("message_router")
36
+ if existing is not None:
37
+ return True
38
+
39
+ if db_path is None:
40
+ import os as _os
41
+ data_dir = getattr(getattr(container, "config", None), "data_dir", "data") or "data"
42
+ db_path = _os.path.join(data_dir, "surfaces.db")
43
+
44
+ try:
45
+ from core.surfaces.session_chat_registry import SessionChatRegistry
46
+ from core.surfaces.message_router import MessageRouter
47
+
48
+ registry = SessionChatRegistry(db_path)
49
+ router = MessageRouter(registry)
50
+ container.register_service("session_chat_registry", registry)
51
+ container.register_service("message_router", router)
52
+
53
+ from core.surfaces.outbound_allowlist import OutboundAllowlist
54
+ container.register_service("outbound_allowlist", OutboundAllowlist(db_path))
55
+
56
+ if SurfaceConfig.outbound_queue_enabled():
57
+ import os
58
+ from core.surfaces.outbound_queue import OutboundDeliveryQueue
59
+ from core.surfaces.outbound_dispatcher import OutboundDispatcher
60
+ from core.surfaces.circuit import CircuitStore, SurfaceCircuitBreaker
61
+ q = OutboundDeliveryQueue(os.path.join(os.path.dirname(db_path) or ".", "outbox.db"))
62
+ q.reclaim_inflight(older_than=__import__("time").time() - 120) # restart-recovery
63
+ router.attach_queue(q)
64
+ circuit_store = CircuitStore(
65
+ os.path.join(os.path.dirname(db_path) or ".", "surface_state.db")
66
+ )
67
+ circuit = SurfaceCircuitBreaker(store=circuit_store)
68
+ dispatcher = OutboundDispatcher(q, lambda sid: router._surfaces.get(sid),
69
+ circuit=circuit)
70
+ container.register_service("outbound_queue", q)
71
+ container.register_service("outbound_dispatcher", dispatcher)
72
+ container.register_service("surface_circuit_breaker", circuit)
73
+ logger.info("surface bus: durable outbound queue + dispatcher + circuit breaker constructed")
74
+ logger.info("surface bus installed (session_chat_registry + message_router)")
75
+ return True
76
+ except Exception as e: # fail-open: a bus build error must not break startup
77
+ logger.error("install_surface_bus failed: %s", e, exc_info=True)
78
+ return False
@@ -0,0 +1,168 @@
1
+ """Per-surface circuit breaker for the outbound dispatcher.
2
+
3
+ Opens automatically after K consecutive failures (stops hammering a dead platform);
4
+ also supports manual pause/resume so an operator can hold a surface while it's
5
+ in maintenance. An optional CircuitStore persists the paused flag across processes
6
+ so the CLI's `polyrob surface pause` is visible to the worker.
7
+
8
+ No `from __future__ import annotations` — callers may introspect param annotations.
9
+ """
10
+ import logging
11
+ from typing import Optional
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class CircuitStore:
17
+ """Tiny SQLite-backed paused-flag store.
18
+
19
+ One table: surface_state(surface_id TEXT PK, paused INTEGER).
20
+ Reads/writes go through core.sqlite_util for WAL + jittered retry.
21
+ """
22
+
23
+ _CREATE = (
24
+ "CREATE TABLE IF NOT EXISTS surface_state "
25
+ "(surface_id TEXT PRIMARY KEY, paused INTEGER NOT NULL DEFAULT 0)"
26
+ )
27
+
28
+ def __init__(self, db_path: str) -> None:
29
+ self._db = db_path
30
+ from core.sqlite_util import wal_connect
31
+ conn = wal_connect(db_path)
32
+ try:
33
+ conn.execute(self._CREATE)
34
+ conn.commit()
35
+ finally:
36
+ conn.close()
37
+
38
+ def pause(self, surface_id: str) -> None:
39
+ from core.sqlite_util import execute_retry
40
+ execute_retry(
41
+ self._db,
42
+ "INSERT INTO surface_state(surface_id, paused) VALUES(?,1) "
43
+ "ON CONFLICT(surface_id) DO UPDATE SET paused=1",
44
+ (surface_id,),
45
+ )
46
+
47
+ def resume(self, surface_id: str) -> None:
48
+ from core.sqlite_util import execute_retry
49
+ execute_retry(
50
+ self._db,
51
+ "INSERT INTO surface_state(surface_id, paused) VALUES(?,0) "
52
+ "ON CONFLICT(surface_id) DO UPDATE SET paused=0",
53
+ (surface_id,),
54
+ )
55
+
56
+ def is_paused(self, surface_id: str) -> bool:
57
+ from core.sqlite_util import execute_retry
58
+ row = execute_retry(
59
+ self._db,
60
+ "SELECT paused FROM surface_state WHERE surface_id=?",
61
+ (surface_id,),
62
+ fetch="one",
63
+ )
64
+ return bool(row and row["paused"])
65
+
66
+ def list_all(self) -> list:
67
+ from core.sqlite_util import execute_retry
68
+ rows = execute_retry(
69
+ self._db,
70
+ "SELECT surface_id, paused FROM surface_state ORDER BY surface_id",
71
+ fetch="all",
72
+ )
73
+ return [{"surface_id": r["surface_id"], "paused": bool(r["paused"])} for r in (rows or [])]
74
+
75
+
76
+ class SurfaceCircuitBreaker:
77
+ """Auto-pauses a surface after `threshold` consecutive failures.
78
+
79
+ In-memory state:
80
+ - ``_counts[surface_id]``: consecutive failure counter (resets on ok).
81
+ - ``_paused``: set of manually paused surface IDs.
82
+
83
+ Optional ``store`` (a ``CircuitStore``): pause/resume also writes the flag to
84
+ SQLite, and ``is_open`` reads it so a worker process sees CLI changes.
85
+ """
86
+
87
+ def __init__(self, threshold: int = 5, store: Optional[CircuitStore] = None) -> None:
88
+ self._threshold = threshold
89
+ self._counts: dict = {} # surface_id -> consecutive fail count
90
+ self._paused: set = set()
91
+ self._store = store
92
+
93
+ # ------------------------------------------------------------------
94
+ # Auto-tripping path
95
+ # ------------------------------------------------------------------
96
+
97
+ def record_ok(self, surface_id: str) -> None:
98
+ """Reset the consecutive-fail counter; closes the auto-open state."""
99
+ self._counts[surface_id] = 0
100
+
101
+ def record_fail(self, surface_id: str) -> None:
102
+ """Increment the consecutive-fail counter; opens when >= threshold."""
103
+ count = self._counts.get(surface_id, 0) + 1
104
+ self._counts[surface_id] = count
105
+ if count >= self._threshold:
106
+ logger.warning(
107
+ "circuit_breaker: surface=%s opened after %d consecutive failures",
108
+ surface_id, count,
109
+ )
110
+
111
+ # ------------------------------------------------------------------
112
+ # Manual pause/resume path
113
+ # ------------------------------------------------------------------
114
+
115
+ def pause(self, surface_id: str) -> None:
116
+ """Manually pause a surface (operator-driven; persisted if store attached)."""
117
+ self._paused.add(surface_id)
118
+ if self._store is not None:
119
+ self._store.pause(surface_id)
120
+
121
+ def resume(self, surface_id: str) -> None:
122
+ """Resume a paused surface and reset its fail counter."""
123
+ self._paused.discard(surface_id)
124
+ self._counts[surface_id] = 0
125
+ if self._store is not None:
126
+ self._store.resume(surface_id)
127
+
128
+ # ------------------------------------------------------------------
129
+ # State query
130
+ # ------------------------------------------------------------------
131
+
132
+ def _store_paused(self, surface_id: str) -> bool:
133
+ """Read the persisted pause flag, fail-open. A store/DB fault (missing or corrupt
134
+ surface_state.db) must NEVER crash a send — treat it as not-paused."""
135
+ if self._store is None:
136
+ return False
137
+ try:
138
+ return bool(self._store.is_paused(surface_id))
139
+ except Exception as e:
140
+ logger.warning("circuit_store: read failed for %s (treating as not paused): %s",
141
+ surface_id, e)
142
+ return False
143
+
144
+ def is_open(self, surface_id: str) -> bool:
145
+ """Return True if the surface should be skipped (open = bad = skip)."""
146
+ if surface_id in self._paused:
147
+ return True
148
+ if (self._counts.get(surface_id, 0)) >= self._threshold:
149
+ return True
150
+ if self._store_paused(surface_id):
151
+ return True
152
+ return False
153
+
154
+ def state(self, surface_id: str) -> dict:
155
+ """Return a snapshot of the surface's circuit state."""
156
+ count = self._counts.get(surface_id, 0)
157
+ manually_paused = surface_id in self._paused
158
+ store_paused = self._store_paused(surface_id)
159
+ auto_open = count >= self._threshold
160
+ return {
161
+ "surface_id": surface_id,
162
+ "consecutive_failures": count,
163
+ "threshold": self._threshold,
164
+ "auto_open": auto_open,
165
+ "manually_paused": manually_paused,
166
+ "store_paused": store_paused,
167
+ "is_open": manually_paused or auto_open or store_paused,
168
+ }
@@ -0,0 +1,77 @@
1
+ """Idle-reset continuity bridge (Task 6, 2026-07-03).
2
+
3
+ Seeds a short "last time we were discussing X" foundation message into a FRESH chat
4
+ session, sourced from the prior chat episode's summary for the same ``thread_key`` —
5
+ so continuity survives an idle/daily reset. The bridge is literally "recall my last
6
+ episode for this chat key": a consumer of the episodic store (Tasks 1-2), not a
7
+ second summary system.
8
+
9
+ Deviation from the original brief (documented, not an oversight)
10
+ ------------------------------------------------------------------
11
+ The brief additionally sketched a ``write_closing_episode`` helper called from
12
+ ``core/surfaces/dispatcher.py``'s ``if fresh:`` branch, writing a closing episode for
13
+ the OUTGOING session at the reset boundary (with no summary available there — routing
14
+ code isn't a summarizer). That write is deliberately NOT implemented:
15
+
16
+ ``SqliteMemoryProvider.record_episode`` upserts
17
+ ``ON CONFLICT(user_id, session_id) DO UPDATE SET summary=excluded.summary`` —
18
+ keyed by ``(user_id, session_id)``, NOT ``thread_key``. Task 6 Part A (see
19
+ ``agents/task/session/cleanup.py``) already writes a REAL H-MEM-derived summary for
20
+ that same ``session_id`` when the session is actually torn down. In the common case
21
+ the old session has already been LRU/idle-evicted (cleanup already ran, already wrote
22
+ a good summary) by the time a new inbound message triggers ``fresh`` for its
23
+ thread_key — so a dispatcher-side null-summary write would land AFTER and CLOBBER the
24
+ good one via that same upsert. Rather than add cross-site write-ordering/locking to
25
+ make a second write site safe, this bridge is written from exactly ONE place
26
+ (cleanup()), which is also the only place a real summary is available.
27
+
28
+ Known limitation (v1, acceptable): a session that is still resident — not yet
29
+ LRU/idle-evicted, so its cleanup() (and therefore its summary-bearing episode) hasn't
30
+ run yet — will not bridge on the very next reset boundary for its thread_key; the
31
+ bridge only starts working once that session's cleanup episode has landed. Task 5's
32
+ broader "recent activity" digest still covers that window ("what did you do"), just
33
+ not the more specific "what were we just discussing".
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import logging
38
+ from typing import Optional
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ async def build_bridge_message(*, user_id: Optional[str], thread_key: Optional[str]):
44
+ """Recall the most recent ``kind="chat"`` episode with a non-empty summary for
45
+ ``thread_key`` and wrap it as a ``SESSION_BRIDGE`` control message.
46
+
47
+ Returns None when: ``AutonomyConfig.continuity_bridge_enabled()`` is False,
48
+ ``thread_key`` is falsy, no prior chat episode exists for it, or every recalled
49
+ episode has an empty/whitespace-only summary. Fail-open — any error returns None.
50
+ """
51
+ try:
52
+ from agents.task.constants import AutonomyConfig
53
+ if not AutonomyConfig.continuity_bridge_enabled() or not thread_key:
54
+ return None
55
+
56
+ from modules.memory.registry import memory_recall_episodes
57
+ rows = await memory_recall_episodes(
58
+ user_id=user_id, thread_key=thread_key, kind="chat", limit=3, order="newest")
59
+
60
+ summary = None
61
+ for row in rows or []:
62
+ candidate = (getattr(row, "summary", None) or "").strip()
63
+ if candidate:
64
+ summary = candidate
65
+ break
66
+ if not summary:
67
+ return None
68
+
69
+ from modules.llm.messages import make_control_message, MessageOrigin
70
+ return make_control_message(
71
+ "Continuing an earlier conversation with this contact. "
72
+ f"Last time: {summary[:600]}",
73
+ MessageOrigin.SESSION_BRIDGE,
74
+ )
75
+ except Exception:
76
+ logger.warning("build_bridge_message failed", exc_info=True)
77
+ return None