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/wallet/signer.py ADDED
@@ -0,0 +1,51 @@
1
+ """Signer seam: produces ECDSA / EIP-712 signatures for ONE agent address.
2
+
3
+ The raw private key NEVER crosses this boundary outward. LocalEoaSigner is the
4
+ self-custody backend; CdpSigner/TurnkeySigner (deferred) implement the same
5
+ Protocol so the custody backend is swappable without touching venue tools.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Protocol, runtime_checkable
10
+
11
+ from eth_account import Account
12
+ from eth_account.messages import encode_defunct, encode_typed_data
13
+
14
+
15
+ @runtime_checkable
16
+ class Signer(Protocol):
17
+ @property
18
+ def address(self) -> str: ...
19
+ def sign_message(self, data: bytes) -> str: ...
20
+ def sign_typed_data(self, domain: dict, types: dict, message: dict) -> str: ...
21
+
22
+
23
+ class LocalEoaSigner:
24
+ """Self-custody EOA signer wrapping an eth_account LocalAccount."""
25
+
26
+ def __init__(self, private_key: bytes):
27
+ self._account = Account.from_key(private_key)
28
+
29
+ @property
30
+ def address(self) -> str:
31
+ return self._account.address
32
+
33
+ @property
34
+ def account(self):
35
+ """The native LocalAccount, for SDKs that require it (e.g. Hyperliquid).
36
+
37
+ In-process use only — never returned from a tool action / logged."""
38
+ return self._account
39
+
40
+ def sign_message(self, data: bytes) -> str:
41
+ signed = self._account.sign_message(encode_defunct(data))
42
+ return signed.signature.hex() if signed.signature.hex().startswith("0x") else "0x" + signed.signature.hex()
43
+
44
+ def sign_typed_data(self, domain: dict, types: dict, message: dict) -> str:
45
+ signable = encode_typed_data(domain_data=domain, message_types=types, message_data=message)
46
+ signed = self._account.sign_message(signable)
47
+ sig = signed.signature.hex()
48
+ return sig if sig.startswith("0x") else "0x" + sig
49
+
50
+ def __repr__(self) -> str: # never leak the key
51
+ return f"<LocalEoaSigner address={self._account.address}>"
cron/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Cron Package — durable scheduled runs
2
+
3
+ _Last reviewed: 2026-06-30. For env flags see ../docs/CONFIGURATION.md; for the autonomy loops see ../docs/guide/architecture.md and ../AGENTS.md._
4
+
5
+ ## Overview
6
+
7
+ The `cron` package provides **durable, scheduled agent runs** — the home for work
8
+ that must survive a process restart, which in-memory `delegate_task` cannot. Jobs
9
+ are persisted in SQLite (`data/cron.db`, WAL + jittered retry) and executed by a
10
+ ticker that is safe under multiple workers via a file-based tick lock.
11
+
12
+ Off by default: nothing ticks unless `CRON_ENABLED=true`. It is wired into both the
13
+ API lifespan and the local CLI through the shared autonomy runtime
14
+ (`core/autonomy_runtime.py::start_autonomy`), alongside the goal board and curator.
15
+
16
+ ## Package structure
17
+
18
+ ```
19
+ cron/
20
+ ├── schedule.py # PURE schedule parser + next-run computation (tz-naive, local
21
+ │ # wall-clock, reference time passed in → deterministic):
22
+ │ # durations ("30m"), `every monday 09:00`, 5-field cron,
23
+ │ # ISO one-shot
24
+ ├── jobs.py # SQLite cron_jobs store — durable schedule state; WAL mode with
25
+ │ # jittered retry on write contention
26
+ ├── service.py # CronService — schedule / list / cancel API over schedule+jobs
27
+ │ # (thin, fully testable; shared by the tool and the ticker)
28
+ ├── scheduler.py # tick(): find due jobs → run each through an injected async
29
+ │ # runner under a hard per-run duration cap → reschedule
30
+ │ # (recurring) or complete (one-shot); file-based TickLock
31
+ ├── runner.py # CronTicker (drives tick() on an interval) + make_agent_runner
32
+ │ # (W3 fix: actually runs run_session, not just create_session)
33
+ └── delivery.py # out-of-band result delivery after a run: allowlist
34
+ # {telegram, email, twitter}; [SILENT] suppresses; tenant-scoped
35
+ # recipient; fail-open; inside the scheduler wait_for budget
36
+ ```
37
+
38
+ ## Execution model
39
+
40
+ 1. `CronTicker` calls `scheduler.tick()` on an interval.
41
+ 2. `tick()` acquires the file `TickLock` (safe under `workers>1`), finds due jobs,
42
+ and runs each through the injected runner with a hard duration cap.
43
+ 3. `make_agent_runner` runs the real agent loop (`create_session` + `run_session`,
44
+ `skip_memory=True`); a one-shot job is marked done, a recurring job is
45
+ rescheduled to its next run.
46
+ 4. If the job requested delivery, `delivery.py` sends the final result to the
47
+ chosen sink (within the tick's `wait_for` budget).
48
+
49
+ ## Key invariants
50
+
51
+ - **W3 run-loop fix (default on, `CRON_RUN_LOOP`):** cron historically called only
52
+ `create_session` and never ran the loop, so jobs created an idle session and did
53
+ nothing. It now runs `run_session`.
54
+ - **Idle-gate:** `scheduler.tick()` skips while the interactive REPL is busy
55
+ (shared CWD safety — see `core/interactive_gate.py`); inert on the server.
56
+ - **Delivery is gated** separately (`CRON_DELIVERY_ENABLED`, default off) and
57
+ fail-open.
58
+
59
+ ## Related
60
+
61
+ - Agent-facing tool surface: `tools/cronjob_tools.py`
62
+ (`cronjob_schedule` / `list` / `cancel`), registered only when `CRON_ENABLED=true`.
63
+ - Durable cross-session **goal board** (reuses this package's `TickLock` pattern):
64
+ `agents/task/goals/` + `agents/task/goals/dispatcher.py`.
65
+ - Shared lifecycle: `core/autonomy_runtime.py`.
cron/__init__.py ADDED
File without changes
cron/delivery.py ADDED
@@ -0,0 +1,277 @@
1
+ """Cron result delivery (W3, Reference-parity §_deliver_result).
2
+
3
+ After a cron job runs the agent loop (W3 run-loop fix), its final result can be
4
+ delivered out-of-band to an external sink — email, twitter, or telegram — so a
5
+ scheduled task can actually *report back* instead of dying silently in a session.
6
+
7
+ Design constraints (Fusion-validated):
8
+
9
+ - **Allowlisted targets** ``{telegram, email, twitter}`` — never an arbitrary sink.
10
+ - **`[SILENT]`** anywhere in the result (case-insensitive) suppresses delivery,
11
+ matching the Reference convention (the agent opts a run out of notifying).
12
+ - **Tenant-scoped** — the recipient is resolved from the *job's owner*, not from a
13
+ free-form payload address, except where the agent is delivering to its own
14
+ configured channel. A cron job must not be able to exfiltrate to a stranger.
15
+ - **Fail-open** — a sink that errors (or is unconfigured) logs and returns False;
16
+ it NEVER fails the cron job. Delivery is best-effort reporting, not core work.
17
+ - **Inside the budget** — callers invoke this from within the scheduler's
18
+ ``asyncio.wait_for(runner(job), timeout=max_duration_seconds)``, so delivery I/O
19
+ shares the per-run hard cap rather than running unbounded after a hard-cancel.
20
+
21
+ Gated by ``CRON_DELIVERY_ENABLED`` (default OFF) at the call site (``cron/runner.py``).
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import logging
26
+ from typing import Any, Optional
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ ALLOWED_TARGETS = ("telegram", "email", "twitter")
31
+ SILENT_MARKER = "[SILENT]"
32
+
33
+
34
+ def _allow_explicit_target() -> bool:
35
+ """Whether an agent-supplied ``deliver_target`` (arbitrary address/chat) is honored.
36
+
37
+ Default FALSE → delivery always goes to the JOB OWNER's own channel, closing the
38
+ exfiltration vector where a (possibly prompt-injected) cron job sets
39
+ ``deliver_target="attacker@evil.com"``. Operators of trusted single-user installs
40
+ can opt in with ``CRON_DELIVERY_ALLOW_EXPLICIT_TARGET=true``.
41
+ """
42
+ import os
43
+ return os.getenv("CRON_DELIVERY_ALLOW_EXPLICIT_TARGET", "").strip().lower() in (
44
+ "1", "true", "yes", "on")
45
+
46
+
47
+ def is_silent(final: Optional[str]) -> bool:
48
+ """True when the agent opted this run out of delivery via ``[SILENT]``."""
49
+ return bool(final) and SILENT_MARKER in final.upper()
50
+
51
+
52
+ def delivery_outcome(final: Optional[str], ok: bool) -> str:
53
+ """Classify a cron delivery for the observability log: ``suppressed`` (agent chose
54
+ ``[SILENT]``), ``sent`` (delivered), or ``failed`` (real send failure).
55
+
56
+ The runner previously logged both a ``[SILENT]`` opt-out and a genuine send error
57
+ as ``ok=False``, so harmless status-digest opt-outs were indistinguishable from
58
+ broken deliveries in the journal. ``is_silent`` takes precedence because a silent
59
+ run never attempts a send (``deliver_result`` returns False for it).
60
+ """
61
+ if is_silent(final):
62
+ return "suppressed"
63
+ return "sent" if ok else "failed"
64
+
65
+
66
+ async def deliver_result(
67
+ task_agent: Any,
68
+ job: Any,
69
+ final: Optional[str],
70
+ *,
71
+ target: Optional[str],
72
+ deliver_target: Optional[str] = None,
73
+ session_id: Optional[str] = None,
74
+ ) -> bool:
75
+ """Deliver a cron job's final result to an allowlisted external sink.
76
+
77
+ Returns True on a successful send, False otherwise (unknown/blank target,
78
+ empty/`[SILENT]` result, sink unconfigured, or any sink exception). Never
79
+ raises — delivery must not fail the job.
80
+
81
+ ``session_id`` (Task 7, optional): when provided, a SUCCESSFUL send marks the
82
+ episodic row for this session as ``surfaced`` (the digest builder then omits
83
+ it via ``exclude_surfaced=True``, avoiding double-reporting what was already
84
+ delivered out-of-band). Callers must pass ``session_id`` AFTER the episode
85
+ row has been written (``finalize_episode``) — the mark is a plain ``UPDATE``
86
+ that no-ops if the row doesn't exist yet (see ``cron/runner.py`` ordering).
87
+ """
88
+ if not target:
89
+ return False
90
+ target = target.strip().lower()
91
+ if target not in ALLOWED_TARGETS:
92
+ logger.warning("cron delivery: target %r not in allowlist %s", target, ALLOWED_TARGETS)
93
+ return False
94
+ if not final or not final.strip():
95
+ return False
96
+ if is_silent(final):
97
+ logger.info("cron delivery: job %s opted out via [SILENT]", getattr(job, "id", "?"))
98
+ return False
99
+
100
+ # Security: ignore an agent-supplied explicit recipient unless an operator opted in.
101
+ # By default, deliver only to the job owner's own channel (no exfiltration).
102
+ if deliver_target and not _allow_explicit_target():
103
+ logger.info("cron delivery: ignoring explicit deliver_target (owner-only by default)")
104
+ deliver_target = None
105
+
106
+ ok = False
107
+ try:
108
+ if target == "email":
109
+ ok = await _deliver_email(task_agent, job, final, deliver_target)
110
+ elif target == "twitter":
111
+ ok = await _deliver_twitter(task_agent, job, final)
112
+ elif target == "telegram":
113
+ ok = await _deliver_telegram(task_agent, job, final, deliver_target)
114
+ except Exception as e: # fail-open: a delivery error never fails the job
115
+ logger.error("cron delivery to %s failed for job %s: %s",
116
+ target, getattr(job, "id", "?"), e, exc_info=True)
117
+ return False
118
+ if ok and session_id:
119
+ _mark_surfaced(session_id, getattr(job, "user_id", None))
120
+ return ok
121
+
122
+
123
+ def _mark_surfaced(session_id: str, user_id: Optional[str] = None) -> None:
124
+ """Fail-open: mark the delivered session's episode as surfaced (Task 7).
125
+
126
+ Scoped to the job's own ``user_id`` (episodes are keyed on the composite
127
+ ``(user_id, session_id)``) so a session_id collision across tenants can't
128
+ flip another tenant's row.
129
+ """
130
+ try:
131
+ from modules.memory.registry import get_memory_registry
132
+ prov = get_memory_registry().active()
133
+ if prov is not None and hasattr(prov, "mark_episode_surfaced"):
134
+ prov.mark_episode_surfaced(session_id=session_id, user_id=user_id)
135
+ except Exception:
136
+ logger.debug("cron delivery: mark_episode_surfaced skipped", exc_info=True)
137
+
138
+
139
+ # --- sinks -------------------------------------------------------------------
140
+ # Each sink resolves its tool lazily from the task_agent's container/config and is
141
+ # wrapped by deliver_result's try/except. Kept as module functions so tests can
142
+ # monkeypatch them without standing up SMTP/Twitter/Telegram.
143
+
144
+ def _config_and_container(task_agent: Any):
145
+ config = getattr(task_agent, "config", None)
146
+ container = getattr(task_agent, "container", None)
147
+ return config, container
148
+
149
+
150
+ async def _deliver_email(task_agent: Any, job: Any, final: str, deliver_target: Optional[str]) -> bool:
151
+ # Tenant scope: only the job owner's own address. ``deliver_target`` is honored
152
+ # only as an explicit user-supplied override (the agent scheduling for itself);
153
+ # callers that don't trust it should leave it None and rely on owner lookup.
154
+ to_email = deliver_target or _owner_email(task_agent, job)
155
+ if not to_email:
156
+ logger.info("cron delivery: no email recipient for job %s", getattr(job, "id", "?"))
157
+ return False
158
+ config, container = _config_and_container(task_agent)
159
+ from tools.email_tool import EmailTool
160
+ tool = EmailTool("email", config, container)
161
+ await tool.ensure_initialized()
162
+ subject = f"[POLYROB cron] {getattr(job, 'task', 'scheduled task')[:60]}"
163
+ return bool(await tool.send_email(to_email=to_email, subject=subject, body=final))
164
+
165
+
166
+ async def _deliver_twitter(task_agent: Any, job: Any, final: str) -> bool:
167
+ config, container = _config_and_container(task_agent)
168
+ from tools.twitter_tool import TwitterTool
169
+ tool = TwitterTool("twitter", config, container)
170
+ text = final.strip()
171
+ if len(text) > 280:
172
+ text = text[:277] + "..."
173
+ return bool(await tool.post(text=text))
174
+
175
+
176
+ async def _deliver_telegram(task_agent: Any, job: Any, final: str, deliver_target: Optional[str]) -> bool:
177
+ # Telegram routing goes through the bot's message sink if one is registered on
178
+ # the container. Best-effort: absent a router, log and skip (fail-open).
179
+ _, container = _config_and_container(task_agent)
180
+ sink = None
181
+ if container is not None and hasattr(container, "get_service"):
182
+ sink = container.get_service("telegram_sink") or container.get_service("message_router")
183
+ chat_id = deliver_target or _owner_telegram(task_agent, job)
184
+ if sink is None or not chat_id:
185
+ logger.info("cron delivery: telegram sink/chat unavailable for job %s", getattr(job, "id", "?"))
186
+ return False
187
+
188
+ # Gate proactive send by surface send-policy (WhatsApp 24h window etc.).
189
+ # For Telegram (no window), resolve_proactive_send returns ("send", None) — no-op.
190
+ from core.surfaces.proactive import resolve_proactive_send
191
+ _action, _extra = await resolve_proactive_send(container, "telegram", chat_id, final)
192
+ if _action == "suppress":
193
+ logger.info("cron delivery: suppressed (send policy) for job %s", getattr(job, "id", "?"))
194
+ return False
195
+ if _action == "template":
196
+ # TODO(4.x): send approved template instead of suppress
197
+ logger.info(
198
+ "cron delivery: send window closed; template required (job %s) — suppressing free-text",
199
+ getattr(job, "id", "?"),
200
+ )
201
+ return False
202
+
203
+ send = getattr(sink, "send_message", None)
204
+ if send is None:
205
+ return False
206
+ res = send(chat_id, final)
207
+ if hasattr(res, "__await__"):
208
+ res = await res
209
+ return bool(res)
210
+
211
+
212
+ def _owner_email(task_agent: Any, job: Any) -> Optional[str]:
213
+ """Resolve the job owner's email (tenant scope).
214
+
215
+ 1. A registered ``user_directory`` service (real multi-user store) — the hook
216
+ point for a proper user store.
217
+ 2. E3 fallback: the single configured ``POLYROB_OWNER_EMAIL`` for single-owner
218
+ headless deploys where nothing registers ``user_directory`` (mirrors the
219
+ telegram ``_configured_owner_telegram_id`` fallback). Without this, the
220
+ user_directory service is never registered, so ``deliver="email"`` was
221
+ always unreachable.
222
+ """
223
+ container = getattr(task_agent, "container", None)
224
+ if container is not None and hasattr(container, "get_service"):
225
+ users = container.get_service("user_directory")
226
+ if users is not None and hasattr(users, "get_email"):
227
+ try:
228
+ addr = users.get_email(getattr(job, "user_id", None))
229
+ if addr:
230
+ return addr
231
+ except Exception:
232
+ pass
233
+ # 2. single-owner env fallback (SSOT in core.instance)
234
+ try:
235
+ from core.instance import resolve_owner_email
236
+ return resolve_owner_email()
237
+ except Exception:
238
+ return None
239
+
240
+
241
+ def _configured_owner_telegram_id() -> Optional[str]:
242
+ """The single configured owner telegram id, for headless single-owner deploys.
243
+
244
+ A goal/cron run started by the autonomy loops carries a non-numeric tenant id
245
+ (e.g. ``"rob"`` — now also the aliased tenant of the owner's OWN chat), so it
246
+ cannot self-resolve a telegram chat. When the deploy is owner-locked to exactly
247
+ ONE telegram id via ``ALLOWED_TELEGRAM_USER_IDS`` (or an explicit
248
+ ``POLYROB_OWNER_TELEGRAM_ID``), that id IS the owner's chat.
249
+
250
+ SSOT: delegates to ``core.instance.resolve_owner_telegram_id`` — the SAME resolver
251
+ the inbound owner alias uses, so the id an owner-tg sender is aliased FROM and the
252
+ chat an out-of-band delivery is routed TO stay in lockstep.
253
+ """
254
+ from core.instance import resolve_owner_telegram_id
255
+ return resolve_owner_telegram_id()
256
+
257
+
258
+ def _owner_telegram(task_agent: Any, job: Any) -> Optional[str]:
259
+ """Resolve the job owner's telegram chat id (tenant scope)."""
260
+ uid = getattr(job, "user_id", None)
261
+ # 1. explicit user-directory mapping (real multi-user store)
262
+ container = getattr(task_agent, "container", None)
263
+ if container is not None and hasattr(container, "get_service"):
264
+ users = container.get_service("user_directory")
265
+ if users is not None and hasattr(users, "get_telegram_chat_id"):
266
+ try:
267
+ cid = users.get_telegram_chat_id(uid)
268
+ if cid:
269
+ return str(cid)
270
+ except Exception:
271
+ pass
272
+ # 2. the user_id IS the telegram chat id (telegram-origin sessions).
273
+ if uid and str(uid).isdigit():
274
+ return str(uid)
275
+ # 3. headless autonomy run (non-numeric tenant): fall back to the single
276
+ # configured owner id so proactive outreach actually reaches the owner.
277
+ return _configured_owner_telegram_id()
cron/digest.py ADDED
@@ -0,0 +1,124 @@
1
+ """Owner daily digest — one pushed message written from evidence, not self-report.
2
+
3
+ A deterministic ($0, no-LLM) composer over the same sources the owner can already
4
+ audit — the unified ledger (money in/out), the durable event log (goals moved,
5
+ skills changed, sessions run), the goal board's open asks (pending approvals), and
6
+ episodes (what ran). Composed in code and pushed via the existing cron delivery
7
+ rail (``cron/delivery.deliver_result``), so it costs nothing and cannot
8
+ hallucinate. Runs as a cron job carrying ``payload.digest=true`` — the runner
9
+ routes such a job here instead of paying for a model turn.
10
+
11
+ Every read is a module-level seam so tests can monkeypatch it; every read fails
12
+ open (a missing source contributes nothing, never an exception).
13
+ """
14
+ import time
15
+ from typing import Any, Dict, List, Optional
16
+
17
+
18
+ def _ledger(user_id: str, days: int) -> Dict[str, Any]:
19
+ try:
20
+ from core.async_bridge import run_coroutine_sync
21
+ from modules.credits.unified_ledger import build_ledger
22
+ # Loop-safe: compose_digest is awaited inside the cron runner's running
23
+ # loop, where a bare asyncio.run would raise and empty the money section.
24
+ return run_coroutine_sync(build_ledger(user_id, days=max(1, int(days)))) or {}
25
+ except Exception:
26
+ return {}
27
+
28
+
29
+ def _event_aggregate(user_id: str, since_ts: Optional[float]) -> Dict[str, Any]:
30
+ try:
31
+ from agents.task.telemetry.event_log import get_event_log, event_log_enabled
32
+ if not event_log_enabled():
33
+ return {}
34
+ return get_event_log().aggregate(since_ts=since_ts, user_id=user_id) or {}
35
+ except Exception:
36
+ return {}
37
+
38
+
39
+ def _open_asks(user_id: str, data_dir: Optional[str]) -> List[Dict[str, Any]]:
40
+ try:
41
+ import os
42
+
43
+ from agents.task.goals.board import GoalBoard
44
+ board = GoalBoard(os.path.join(data_dir or "data", "goals.db"))
45
+ rows = board.asks(user_id=user_id, status="open") or []
46
+ out = []
47
+ for a in rows:
48
+ get = (a.get if isinstance(a, dict) else lambda k, d=None: getattr(a, k, d))
49
+ out.append({"title": get("title") or get("what") or ""})
50
+ return out
51
+ except Exception:
52
+ return []
53
+
54
+
55
+ def _episodes(user_id: str, since_ts: Optional[float]) -> List[Dict[str, Any]]:
56
+ try:
57
+ from core.async_bridge import run_coroutine_sync
58
+ from modules.memory.registry import memory_recall_episodes
59
+ rows = run_coroutine_sync(memory_recall_episodes(
60
+ user_id=user_id, since_ts=int(since_ts) if since_ts else None,
61
+ limit=20, order="newest"))
62
+ out = []
63
+ for r in rows or []:
64
+ get = (r.get if isinstance(r, dict) else lambda k, d=None: getattr(r, k, d))
65
+ out.append({"kind": get("kind"), "outcome": get("outcome")})
66
+ return out
67
+ except Exception:
68
+ return []
69
+
70
+
71
+ async def compose_digest(user_id: str, *, days: int = 1,
72
+ data_dir: Optional[str] = None, db=None) -> str:
73
+ """Build the digest text from evidence. Pure-ish (all reads fail-open)."""
74
+ since_ts = time.time() - max(1, int(days)) * 86400
75
+ ledger = _ledger(user_id, days)
76
+ agg = _event_aggregate(user_id, since_ts)
77
+ asks = _open_asks(user_id, data_dir)
78
+ episodes = _episodes(user_id, since_ts)
79
+
80
+ counts = agg.get("counts_by_kind", {}) or {}
81
+ n_sessions = len(episodes)
82
+ n_goals = int(counts.get("goal_run", 0)) + int(counts.get("goal_completion", 0))
83
+ n_self_mods = int(counts.get("self_modification", 0))
84
+ earned = float(ledger.get("earned_usd") or 0.0)
85
+ spent = float(ledger.get("total_spend_usd") or 0.0)
86
+ net = float(ledger.get("net_usd") or (earned - spent))
87
+ pending_n = int(ledger.get("pending_invoices") or 0)
88
+ pending_usd = float(ledger.get("pending_invoices_usd") or 0.0)
89
+
90
+ period = "today" if days == 1 else f"last {days}d"
91
+ lines = [f"Daily digest — {period}:"]
92
+ lines.append(f"• Activity: {n_sessions} session(s), {n_goals} goal event(s), "
93
+ f"{n_self_mods} self-change(s)")
94
+ lines.append(f"• Money: earned ${earned:.2f}, spent ${spent:.2f}, net ${net:.2f}"
95
+ + (f"; {pending_n} pending invoice(s) ${pending_usd:.2f}" if pending_n else ""))
96
+ if asks:
97
+ lines.append(f"• Pending approvals ({len(asks)}):")
98
+ for a in asks[:5]:
99
+ title = (a.get("title") or "").strip().replace("\n", " ")[:80]
100
+ lines.append(f" - {title}")
101
+ else:
102
+ lines.append("• Pending approvals: none")
103
+ return "\n".join(lines)
104
+
105
+
106
+ async def run_digest(task_agent: Any, job: Any) -> bool:
107
+ """Compose the digest for the job's tenant and deliver it out-of-band.
108
+
109
+ A $0 tick: no model call. Returns True when a send is attempted-and-succeeds
110
+ (or is intentionally suppressed); False on delivery failure. Never raises."""
111
+ try:
112
+ payload = dict(getattr(job, "payload", {}) or {})
113
+ days = int(payload.get("days", 1) or 1)
114
+ data_dir = payload.get("data_dir")
115
+ text = await compose_digest(getattr(job, "user_id", ""), days=days, data_dir=data_dir)
116
+ except Exception:
117
+ return False
118
+ target = payload.get("deliver") or "telegram"
119
+ try:
120
+ from cron.delivery import deliver_result
121
+ return await deliver_result(task_agent, job, text, target=target,
122
+ deliver_target=payload.get("deliver_target"))
123
+ except Exception:
124
+ return False