polyrob 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (745) hide show
  1. agents/README.md +453 -0
  2. agents/__init__.py +164 -0
  3. agents/base_agent.py +743 -0
  4. agents/personality/character.py +119 -0
  5. agents/personality/character_manager.py +309 -0
  6. agents/personality/characters/rob.character.json +56 -0
  7. agents/personality/characters/trump.character.json +63 -0
  8. agents/personality/persona_render.py +78 -0
  9. agents/personality/persona_resolver.py +115 -0
  10. agents/prompt/__init__.py +58 -0
  11. agents/prompt/base_prompt.py +92 -0
  12. agents/prompt/system.py +417 -0
  13. agents/task/README_PATH_MANAGEMENT.md +141 -0
  14. agents/task/__init__.py +162 -0
  15. agents/task/agent/__init__.py +70 -0
  16. agents/task/agent/agent_state.py +408 -0
  17. agents/task/agent/async_delegation.py +274 -0
  18. agents/task/agent/autonomy_state.py +275 -0
  19. agents/task/agent/conversation.py +75 -0
  20. agents/task/agent/core/__init__.py +6 -0
  21. agents/task/agent/core/aux_metering.py +61 -0
  22. agents/task/agent/core/background_review.py +157 -0
  23. agents/task/agent/core/construction.py +1179 -0
  24. agents/task/agent/core/conversational_exit.py +49 -0
  25. agents/task/agent/core/correspondent_gate.py +227 -0
  26. agents/task/agent/core/curator.py +246 -0
  27. agents/task/agent/core/episodic_digest.py +103 -0
  28. agents/task/agent/core/error_recovery.py +480 -0
  29. agents/task/agent/core/history_io.py +257 -0
  30. agents/task/agent/core/llm_provisioning.py +287 -0
  31. agents/task/agent/core/llm_runner.py +547 -0
  32. agents/task/agent/core/logging_io.py +276 -0
  33. agents/task/agent/core/loop_detection.py +262 -0
  34. agents/task/agent/core/memory_prefetch.py +325 -0
  35. agents/task/agent/core/memory_writer.py +667 -0
  36. agents/task/agent/core/model_introspection.py +82 -0
  37. agents/task/agent/core/model_swap.py +177 -0
  38. agents/task/agent/core/next_action_internal.py +1331 -0
  39. agents/task/agent/core/output_validation.py +303 -0
  40. agents/task/agent/core/project_context.py +272 -0
  41. agents/task/agent/core/resources.py +93 -0
  42. agents/task/agent/core/result_processing.py +389 -0
  43. agents/task/agent/core/run_loop.py +661 -0
  44. agents/task/agent/core/safety_lifecycle.py +228 -0
  45. agents/task/agent/core/secret_guard.py +293 -0
  46. agents/task/agent/core/self_wake.py +280 -0
  47. agents/task/agent/core/session_metadata.py +104 -0
  48. agents/task/agent/core/step.py +781 -0
  49. agents/task/agent/core/step_execution.py +398 -0
  50. agents/task/agent/core/step_telemetry.py +222 -0
  51. agents/task/agent/core/test_aux_metering.py +50 -0
  52. agents/task/agent/core/test_billing_failover.py +68 -0
  53. agents/task/agent/core/test_fatal_error_classifier.py +38 -0
  54. agents/task/agent/core/test_judge_validation.py +275 -0
  55. agents/task/agent/core/test_model_swap.py +190 -0
  56. agents/task/agent/core/test_reflection_offload.py +54 -0
  57. agents/task/agent/core/turn_input.py +8 -0
  58. agents/task/agent/core/untrusted_wrap.py +103 -0
  59. agents/task/agent/core/user_ingress.py +228 -0
  60. agents/task/agent/hitl_manager.py +396 -0
  61. agents/task/agent/log_sanitize.py +36 -0
  62. agents/task/agent/message_manager/config.py +97 -0
  63. agents/task/agent/message_manager/service.py +758 -0
  64. agents/task/agent/message_manager/tests.py +305 -0
  65. agents/task/agent/message_manager/tool_call_builder.py +539 -0
  66. agents/task/agent/message_manager/tool_message_repair.py +364 -0
  67. agents/task/agent/message_manager/views.py +143 -0
  68. agents/task/agent/messages/__init__.py +8 -0
  69. agents/task/agent/messages/builders.py +309 -0
  70. agents/task/agent/messages/compactor.py +611 -0
  71. agents/task/agent/messages/context_references.py +271 -0
  72. agents/task/agent/messages/filters.py +333 -0
  73. agents/task/agent/messages/guidance.py +328 -0
  74. agents/task/agent/messages/persistence.py +448 -0
  75. agents/task/agent/messages/retrieval.py +506 -0
  76. agents/task/agent/messages/sqlite_persistence.py +85 -0
  77. agents/task/agent/messages/test_compaction_upgrade.py +349 -0
  78. agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
  79. agents/task/agent/messages/test_sqlite_persistence.py +54 -0
  80. agents/task/agent/messages/token_counter.py +532 -0
  81. agents/task/agent/orchestrator.py +776 -0
  82. agents/task/agent/profile_manager.py +105 -0
  83. agents/task/agent/profile_registry.py +173 -0
  84. agents/task/agent/prompts.py +1090 -0
  85. agents/task/agent/scenario_registry.py +226 -0
  86. agents/task/agent/service.py +509 -0
  87. agents/task/agent/session.py +984 -0
  88. agents/task/agent/skill_discovery.py +110 -0
  89. agents/task/agent/skill_frontmatter.py +94 -0
  90. agents/task/agent/skill_manager.py +1201 -0
  91. agents/task/agent/skill_store.py +627 -0
  92. agents/task/agent/skill_validation.py +56 -0
  93. agents/task/agent/skill_writer.py +492 -0
  94. agents/task/agent/sub_agent_manager.py +1137 -0
  95. agents/task/agent/test_session_disk_recovery.py +34 -0
  96. agents/task/agent/tests.py +513 -0
  97. agents/task/agent/tool_call_tracker.py +511 -0
  98. agents/task/agent/views.py +469 -0
  99. agents/task/config.py +432 -0
  100. agents/task/constants.py +1384 -0
  101. agents/task/flag_defaults.py +63 -0
  102. agents/task/goals/__init__.py +1 -0
  103. agents/task/goals/autonomy_marker.py +28 -0
  104. agents/task/goals/board.py +690 -0
  105. agents/task/goals/completion_judge.py +247 -0
  106. agents/task/goals/context.py +62 -0
  107. agents/task/goals/dispatcher.py +848 -0
  108. agents/task/goals/escalation.py +102 -0
  109. agents/task/goals/planner.py +114 -0
  110. agents/task/logging_config.py +155 -0
  111. agents/task/path.py +991 -0
  112. agents/task/robust_parse_config.py +400 -0
  113. agents/task/runtime/__init__.py +0 -0
  114. agents/task/runtime/run_as_session.py +130 -0
  115. agents/task/runtime_safety.py +308 -0
  116. agents/task/session/__init__.py +5 -0
  117. agents/task/session/browser_pool.py +130 -0
  118. agents/task/session/cleanup.py +612 -0
  119. agents/task/session/execution.py +397 -0
  120. agents/task/session/feed.py +200 -0
  121. agents/task/session/hitl_ingress.py +201 -0
  122. agents/task/session/hooks.py +83 -0
  123. agents/task/session/multi_agent.py +243 -0
  124. agents/task/session/workspace.py +91 -0
  125. agents/task/session_registry.py +91 -0
  126. agents/task/session_route.py +35 -0
  127. agents/task/sqlite_session_registry.py +261 -0
  128. agents/task/surface_config.py +185 -0
  129. agents/task/telemetry/__init__.py +58 -0
  130. agents/task/telemetry/event_log.py +180 -0
  131. agents/task/telemetry/formatters.py +642 -0
  132. agents/task/telemetry/manager.py +571 -0
  133. agents/task/telemetry/memory_events.py +66 -0
  134. agents/task/telemetry/self_events.py +53 -0
  135. agents/task/telemetry/sequence.py +160 -0
  136. agents/task/telemetry/service.py +1265 -0
  137. agents/task/telemetry/views.py +882 -0
  138. agents/task/templates.py +98 -0
  139. agents/task/test_aux_chain.py +151 -0
  140. agents/task/test_aux_router.py +41 -0
  141. agents/task/tests/test_parse_robustness.py +170 -0
  142. agents/task/tool_defaults.py +110 -0
  143. agents/task/utils.py +762 -0
  144. agents/task/utils_json.py +768 -0
  145. agents/task/utils_webview.py +86 -0
  146. agents/task/workspace_context.py +229 -0
  147. agents/task_agent_lite.py +2398 -0
  148. api/README.md +683 -0
  149. api/TASK_API_DOCS.md +151 -0
  150. api/__init__.py +0 -0
  151. api/a2a/__init__.py +37 -0
  152. api/a2a/agent_card.py +337 -0
  153. api/a2a/client.py +657 -0
  154. api/a2a/endpoints.py +375 -0
  155. api/a2a/models.py +268 -0
  156. api/a2a/streaming.py +419 -0
  157. api/a2a/task_handler.py +929 -0
  158. api/admin_endpoints.py +1584 -0
  159. api/app.py +923 -0
  160. api/auth_constants.py +28 -0
  161. api/auth_endpoints.py +402 -0
  162. api/auth_state.py +36 -0
  163. api/chat_via_task.py +42 -0
  164. api/conversation_manager.py +231 -0
  165. api/dependencies.py +186 -0
  166. api/eip8004_endpoints.py +452 -0
  167. api/hyperliquid_models.py +174 -0
  168. api/hyperliquid_routes.py +450 -0
  169. api/interfaces.py +207 -0
  170. api/jwt_middleware.py +83 -0
  171. api/kb/__init__.py +1 -0
  172. api/kb/endpoints.py +314 -0
  173. api/mcp_models.py +279 -0
  174. api/mcp_routes.py +438 -0
  175. api/middleware.py +526 -0
  176. api/models.py +176 -0
  177. api/openai_compat/__init__.py +0 -0
  178. api/openai_compat/model_map.py +25 -0
  179. api/openai_compat/models.py +59 -0
  180. api/openai_compat/router.py +140 -0
  181. api/payment_endpoints.py +444 -0
  182. api/payment_verification.py +138 -0
  183. api/polymarket_models.py +155 -0
  184. api/polymarket_routes.py +501 -0
  185. api/pricing_endpoints.py +89 -0
  186. api/session_routing.py +54 -0
  187. api/skill_endpoints.py +453 -0
  188. api/task_http_api.py +1856 -0
  189. api/webhooks.py +52 -0
  190. api/x402_endpoints.py +280 -0
  191. avatar/README.md +44 -0
  192. avatar/__init__.py +1 -0
  193. avatar/config/rob.json +7 -0
  194. avatar/mindprint.js +395 -0
  195. avatar/renders/rob.meta.json +20 -0
  196. avatar/renders/rob.png +0 -0
  197. avatar/studio.html +373 -0
  198. avatar/webview/avatar-live.js +44 -0
  199. cli/README.md +89 -0
  200. cli/__init__.py +0 -0
  201. cli/cli_surface.py +67 -0
  202. cli/commands/__init__.py +0 -0
  203. cli/commands/_bootstrap.py +49 -0
  204. cli/commands/_errors.py +53 -0
  205. cli/commands/chat.py +973 -0
  206. cli/commands/config.py +100 -0
  207. cli/commands/dashboard.py +103 -0
  208. cli/commands/doctor.py +222 -0
  209. cli/commands/email.py +174 -0
  210. cli/commands/gateway.py +346 -0
  211. cli/commands/goals.py +509 -0
  212. cli/commands/init.py +276 -0
  213. cli/commands/journey.py +30 -0
  214. cli/commands/kb.py +280 -0
  215. cli/commands/model.py +107 -0
  216. cli/commands/owner.py +431 -0
  217. cli/commands/pfp.py +232 -0
  218. cli/commands/run.py +414 -0
  219. cli/commands/serve.py +41 -0
  220. cli/commands/session.py +684 -0
  221. cli/commands/skill_install.py +786 -0
  222. cli/commands/skills.py +128 -0
  223. cli/commands/subagents.py +100 -0
  224. cli/commands/surface.py +81 -0
  225. cli/commands/telegram.py +185 -0
  226. cli/commands/todos.py +209 -0
  227. cli/commands/tools.py +102 -0
  228. cli/commands/update.py +316 -0
  229. cli/commands/whatsapp.py +175 -0
  230. cli/config_store.py +276 -0
  231. cli/gitignore.py +43 -0
  232. cli/inventory.py +46 -0
  233. cli/keys.py +101 -0
  234. cli/persona.py +17 -0
  235. cli/polyrob.py +137 -0
  236. cli/ui/__init__.py +64 -0
  237. cli/ui/activity.py +141 -0
  238. cli/ui/app.py +538 -0
  239. cli/ui/banner.py +233 -0
  240. cli/ui/blocks.py +534 -0
  241. cli/ui/bootstrap_notice.py +47 -0
  242. cli/ui/commands/__init__.py +41 -0
  243. cli/ui/commands/h_cron.py +64 -0
  244. cli/ui/commands/h_journey.py +164 -0
  245. cli/ui/commands/h_kb.py +121 -0
  246. cli/ui/commands/h_learn.py +100 -0
  247. cli/ui/commands/h_mcp.py +215 -0
  248. cli/ui/commands/h_self.py +113 -0
  249. cli/ui/commands/h_skills.py +314 -0
  250. cli/ui/commands/handlers.py +1672 -0
  251. cli/ui/commands/registry.py +426 -0
  252. cli/ui/dialog.py +425 -0
  253. cli/ui/event_registry.py +110 -0
  254. cli/ui/event_specs.py +45 -0
  255. cli/ui/events.py +433 -0
  256. cli/ui/identity.py +15 -0
  257. cli/ui/lifecycle.py +159 -0
  258. cli/ui/live_hooks.py +43 -0
  259. cli/ui/model_selector.py +532 -0
  260. cli/ui/persistent_loop.py +181 -0
  261. cli/ui/pick.py +113 -0
  262. cli/ui/plain_renderer.py +351 -0
  263. cli/ui/renderer.py +384 -0
  264. cli/ui/rich_renderer.py +533 -0
  265. cli/ui/secrets.py +115 -0
  266. cli/ui/state.py +376 -0
  267. cli/ui/statusbar.py +231 -0
  268. cli/ui/streaming.py +254 -0
  269. cli/ui/terminal_render.py +105 -0
  270. cli/ui/theme.py +132 -0
  271. cli/update/__init__.py +6 -0
  272. cli/update/context.py +121 -0
  273. cli/update/detect.py +176 -0
  274. cli/update/engine.py +95 -0
  275. cli/update/migrate_guarded.py +55 -0
  276. cli/update/process_guard.py +215 -0
  277. cli/update/runners.py +78 -0
  278. cli/update/snapshot.py +270 -0
  279. cli/update/versions.py +205 -0
  280. core/README.md +377 -0
  281. core/__init__.py +174 -0
  282. core/assets.py +87 -0
  283. core/async_bridge.py +76 -0
  284. core/autonomy_runtime.py +259 -0
  285. core/base_component.py +302 -0
  286. core/bootstrap.py +681 -0
  287. core/bot.py +310 -0
  288. core/config.py +833 -0
  289. core/constants.py +126 -0
  290. core/container.py +462 -0
  291. core/db_manifest.py +101 -0
  292. core/embedding.py +57 -0
  293. core/env.py +39 -0
  294. core/exceptions.py +287 -0
  295. core/flags.py +144 -0
  296. core/flags_catalog.py +345 -0
  297. core/home_migration.py +62 -0
  298. core/identity.py +118 -0
  299. core/initialization.py +1004 -0
  300. core/instance.py +537 -0
  301. core/interactive_gate.py +96 -0
  302. core/logging.py +436 -0
  303. core/owner_doc_writer.py +176 -0
  304. core/pairing.py +193 -0
  305. core/path_safety.py +20 -0
  306. core/paths.py +27 -0
  307. core/payment_config.py +21 -0
  308. core/permissions.py +322 -0
  309. core/runtime_config.py +96 -0
  310. core/runtime_paths.py +135 -0
  311. core/seams.py +35 -0
  312. core/secret_scan.py +25 -0
  313. core/secret_scrub.py +71 -0
  314. core/secrets.py +35 -0
  315. core/security_logging_filter.py +148 -0
  316. core/self_context_writer.py +333 -0
  317. core/self_evolution.py +294 -0
  318. core/session_context.py +47 -0
  319. core/sqlite_util.py +59 -0
  320. core/surfaces/__init__.py +16 -0
  321. core/surfaces/access.py +118 -0
  322. core/surfaces/binding.py +88 -0
  323. core/surfaces/bootstrap.py +78 -0
  324. core/surfaces/circuit.py +168 -0
  325. core/surfaces/continuity.py +77 -0
  326. core/surfaces/correspondents.py +228 -0
  327. core/surfaces/dispatcher.py +216 -0
  328. core/surfaces/envelopes.py +75 -0
  329. core/surfaces/gc.py +30 -0
  330. core/surfaces/idempotency.py +57 -0
  331. core/surfaces/inbound_webhook.py +124 -0
  332. core/surfaces/media.py +32 -0
  333. core/surfaces/message_router.py +91 -0
  334. core/surfaces/outbound_allowlist.py +80 -0
  335. core/surfaces/outbound_dispatcher.py +110 -0
  336. core/surfaces/outbound_mirror.py +37 -0
  337. core/surfaces/outbound_queue.py +107 -0
  338. core/surfaces/outbound_target.py +12 -0
  339. core/surfaces/owner_admin.py +29 -0
  340. core/surfaces/proactive.py +78 -0
  341. core/surfaces/progress.py +108 -0
  342. core/surfaces/rate_bucket.py +21 -0
  343. core/surfaces/registry.py +51 -0
  344. core/surfaces/rendering.py +53 -0
  345. core/surfaces/send_policy.py +14 -0
  346. core/surfaces/serialize.py +24 -0
  347. core/surfaces/session_chat_registry.py +112 -0
  348. core/surfaces/session_policy.py +60 -0
  349. core/surfaces/surface.py +288 -0
  350. core/surfaces/transcription.py +88 -0
  351. core/surfaces/voice_echo.py +26 -0
  352. core/surfaces/voice_guard.py +23 -0
  353. core/tickers.py +174 -0
  354. core/tool_catalog.py +192 -0
  355. core/version.py +75 -0
  356. core/wallet/__init__.py +5 -0
  357. core/wallet/agent_wallet.py +59 -0
  358. core/wallet/audit_sink.py +61 -0
  359. core/wallet/config.py +70 -0
  360. core/wallet/factory.py +87 -0
  361. core/wallet/policy.py +104 -0
  362. core/wallet/signer.py +51 -0
  363. cron/README.md +65 -0
  364. cron/__init__.py +0 -0
  365. cron/delivery.py +277 -0
  366. cron/digest.py +124 -0
  367. cron/jobs.py +207 -0
  368. cron/runner.py +297 -0
  369. cron/schedule.py +194 -0
  370. cron/scheduler.py +201 -0
  371. cron/service.py +57 -0
  372. cron/wake_gate.py +223 -0
  373. data/__init__.py +1 -0
  374. data/prompts/autov2_prompts.json +87 -0
  375. data/prompts/skills/browser-automation/SKILL.md +43 -0
  376. data/prompts/skills/coding-workflow/SKILL.md +36 -0
  377. data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
  378. data/prompts/skills/document-writing/SKILL.md +33 -0
  379. data/prompts/skills/email-comms/SKILL.md +33 -0
  380. data/prompts/skills/file-data-ops/SKILL.md +35 -0
  381. data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
  382. data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
  383. data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
  384. data/prompts/skills/lead-research/SKILL.md +86 -0
  385. data/prompts/skills/market-research-brief/SKILL.md +104 -0
  386. data/prompts/skills/person-analyzer/SKILL.md +124 -0
  387. data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
  388. data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
  389. data/prompts/skills/polymarket-trading/SKILL.md +55 -0
  390. data/prompts/skills/presentation-creator/SKILL.md +36 -0
  391. data/prompts/skills/project-analyzer/SKILL.md +169 -0
  392. data/prompts/skills/rules.json +268 -0
  393. data/prompts/skills/secret-handling/SKILL.md +20 -0
  394. data/prompts/skills/skill-authoring/SKILL.md +31 -0
  395. data/prompts/skills/skill-security-review/SKILL.md +27 -0
  396. data/prompts/skills/social-discovery/SKILL.md +57 -0
  397. data/prompts/skills/task-planning/SKILL.md +33 -0
  398. data/prompts/skills/web-research/SKILL.md +39 -0
  399. data/prompts/skills/web-scraping/SKILL.md +73 -0
  400. data/prompts/skills/x-engagement/SKILL.md +69 -0
  401. data/prompts/system_prompts.json +4 -0
  402. data/subscription_channels.json +5 -0
  403. modules/README.md +503 -0
  404. modules/__init__.py +124 -0
  405. modules/auth/__init__.py +13 -0
  406. modules/auth/api_key_manager.py +157 -0
  407. modules/auth/identity_mapper.py +380 -0
  408. modules/auth/siwe_auth.py +273 -0
  409. modules/auth/tier_manager.py +173 -0
  410. modules/base_module.py +190 -0
  411. modules/credits/__init__.py +29 -0
  412. modules/credits/balance_manager.py +237 -0
  413. modules/credits/cost_utils.py +129 -0
  414. modules/credits/pricing.py +139 -0
  415. modules/credits/unified_ledger.py +148 -0
  416. modules/credits/usage_meter.py +310 -0
  417. modules/credits/usage_tracker.py +674 -0
  418. modules/database/__init__.py +33 -0
  419. modules/database/audit_log.py +322 -0
  420. modules/database/auth_tables.py +276 -0
  421. modules/database/connection.py +644 -0
  422. modules/database/connection_pool.py +305 -0
  423. modules/database/conversation_contexts.py +446 -0
  424. modules/database/database_manager.py +237 -0
  425. modules/database/hyperliquid.py +375 -0
  426. modules/database/polymarket.py +574 -0
  427. modules/database/user_mcp_servers.py +728 -0
  428. modules/database/user_profiles.py +562 -0
  429. modules/database/utils.py +107 -0
  430. modules/database/x402_tables.py +92 -0
  431. modules/eip8004/README.md +1069 -0
  432. modules/eip8004/__init__.py +65 -0
  433. modules/eip8004/contracts.py +498 -0
  434. modules/eip8004/models.py +271 -0
  435. modules/eip8004/registration.py +148 -0
  436. modules/eip8004/reputation.py +367 -0
  437. modules/eip8004/validation.py +277 -0
  438. modules/llm/__init__.py +97 -0
  439. modules/llm/adapters.py +963 -0
  440. modules/llm/anthropic_client.py +831 -0
  441. modules/llm/available_models.py +106 -0
  442. modules/llm/brain_scrubber.py +137 -0
  443. modules/llm/cache_hints.py +162 -0
  444. modules/llm/deepseek_client.py +562 -0
  445. modules/llm/gemini_client.py +1367 -0
  446. modules/llm/llm_client.py +1139 -0
  447. modules/llm/llm_client_registry.py +188 -0
  448. modules/llm/llm_factory.py +237 -0
  449. modules/llm/llm_manager.py +1036 -0
  450. modules/llm/messages.py +374 -0
  451. modules/llm/model_registry.py +1935 -0
  452. modules/llm/nvidia_client.py +125 -0
  453. modules/llm/openai_client.py +800 -0
  454. modules/llm/openrouter_client.py +969 -0
  455. modules/llm/profiles.py +178 -0
  456. modules/llm/test_anthropic_cache_capture.py +35 -0
  457. modules/llm/test_cached_pricing_derivation.py +24 -0
  458. modules/llm/think_scrubber.py +444 -0
  459. modules/llm/token_counter.py +268 -0
  460. modules/memory/__init__.py +35 -0
  461. modules/memory/backend_factory.py +83 -0
  462. modules/memory/cache_manager.py +186 -0
  463. modules/memory/episodic.py +108 -0
  464. modules/memory/local_vector_memory_provider.py +552 -0
  465. modules/memory/memory_manager.py +225 -0
  466. modules/memory/models.py +312 -0
  467. modules/memory/provider.py +210 -0
  468. modules/memory/registry.py +205 -0
  469. modules/memory/sqlite_memory_provider.py +767 -0
  470. modules/memory/task/__init__.py +52 -0
  471. modules/memory/task/compaction_manager.py +157 -0
  472. modules/memory/task/context_retriever.py +809 -0
  473. modules/memory/task/hierarchical_memory.py +1002 -0
  474. modules/memory/task/lexical_retriever.py +71 -0
  475. modules/memory/task/null_context_manager.py +213 -0
  476. modules/memory/task/phase_manager.py +625 -0
  477. modules/memory/task/reflection_service.py +157 -0
  478. modules/memory/task/semantic_retriever.py +218 -0
  479. modules/memory/task/task_context_manager.py +1165 -0
  480. modules/memory/task/test_forgetting_engages.py +24 -0
  481. modules/memory/task/test_reflection_llm.py +39 -0
  482. modules/memory/task/test_threat_scan.py +53 -0
  483. modules/memory/task/threat_scan.py +90 -0
  484. modules/memory/test_sqlite_memory_provider.py +106 -0
  485. modules/memory/user_profile_manager.py +527 -0
  486. modules/payments/__init__.py +11 -0
  487. modules/payments/deposit_monitor.py +478 -0
  488. modules/payments/price_oracle.py +70 -0
  489. modules/payments/treasury_sweeper.py +401 -0
  490. modules/payments/wallet_generator.py +145 -0
  491. modules/pfp/__init__.py +1 -0
  492. modules/pfp/config.py +92 -0
  493. modules/pfp/mesh.py +565 -0
  494. modules/pfp/push.py +69 -0
  495. modules/pfp/renderer.py +85 -0
  496. modules/pfp/store.py +82 -0
  497. modules/skills/__init__.py +1 -0
  498. modules/skills/skill_usage.py +248 -0
  499. modules/transcription/__init__.py +24 -0
  500. modules/transcription/base.py +20 -0
  501. modules/transcription/faster_whisper_transcriber.py +55 -0
  502. modules/x402/README.md +249 -0
  503. modules/x402/__init__.py +22 -0
  504. modules/x402/invoicing.py +416 -0
  505. modules/x402/middleware.py +364 -0
  506. modules/x402/settlement_watcher.py +145 -0
  507. modules/x402/x402_integration.py +344 -0
  508. polyrob-0.5.0.dist-info/METADATA +482 -0
  509. polyrob-0.5.0.dist-info/RECORD +745 -0
  510. polyrob-0.5.0.dist-info/WHEEL +5 -0
  511. polyrob-0.5.0.dist-info/entry_points.txt +2 -0
  512. polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
  513. polyrob-0.5.0.dist-info/top_level.txt +12 -0
  514. surfaces/README.md +70 -0
  515. surfaces/__init__.py +0 -0
  516. surfaces/email/__init__.py +0 -0
  517. surfaces/email/dedup.py +44 -0
  518. surfaces/email/harness.py +187 -0
  519. surfaces/email/inbound.py +159 -0
  520. surfaces/email/seed.py +60 -0
  521. surfaces/email/surface.py +105 -0
  522. surfaces/telegram/__init__.py +0 -0
  523. surfaces/telegram/dedup.py +83 -0
  524. surfaces/telegram/harness.py +886 -0
  525. surfaces/telegram/inbound.py +145 -0
  526. surfaces/telegram/interactive_tools.py +49 -0
  527. surfaces/telegram/markdown.py +347 -0
  528. surfaces/telegram/rate_limit.py +167 -0
  529. surfaces/telegram/surface.py +187 -0
  530. surfaces/telegram/voice.py +52 -0
  531. surfaces/whatsapp/__init__.py +0 -0
  532. surfaces/whatsapp/client.py +51 -0
  533. surfaces/whatsapp/harness.py +62 -0
  534. surfaces/whatsapp/inbound.py +132 -0
  535. surfaces/whatsapp/surface.py +64 -0
  536. surfaces/whatsapp/window.py +34 -0
  537. tools/README.md +578 -0
  538. tools/__init__.py +424 -0
  539. tools/alchemy/__init__.py +5 -0
  540. tools/alchemy/alchemy_tool.py +389 -0
  541. tools/anysite/__init__.py +17 -0
  542. tools/anysite/client.py +88 -0
  543. tools/anysite/tool.py +89 -0
  544. tools/base_tool.py +468 -0
  545. tools/browser/__init__.py +22 -0
  546. tools/browser/actions.py +118 -0
  547. tools/browser/browser.py +1457 -0
  548. tools/browser/browser_manager.py +690 -0
  549. tools/browser/context.py +1748 -0
  550. tools/browser/playwright_utils.py +137 -0
  551. tools/browser/views.py +56 -0
  552. tools/code_exec/SANDBOX_SECURITY.md +144 -0
  553. tools/code_exec/__init__.py +178 -0
  554. tools/code_exec/backend.py +73 -0
  555. tools/code_exec/backends/__init__.py +1 -0
  556. tools/code_exec/backends/docker.py +779 -0
  557. tools/code_exec/backends/local_subprocess.py +154 -0
  558. tools/code_exec/env_policy.py +45 -0
  559. tools/code_exec/result.py +42 -0
  560. tools/code_exec/sandbox_guard.py +59 -0
  561. tools/code_exec/tool.py +227 -0
  562. tools/coding/__init__.py +50 -0
  563. tools/coding/edit.py +107 -0
  564. tools/coding/search.py +113 -0
  565. tools/coding/tool.py +353 -0
  566. tools/collabland/__init__.py +5 -0
  567. tools/collabland/collabland_tool.py +471 -0
  568. tools/controller/__init__.py +0 -0
  569. tools/controller/_helpers.py +165 -0
  570. tools/controller/action_registration.py +1765 -0
  571. tools/controller/approval.py +244 -0
  572. tools/controller/approval_interactive.py +90 -0
  573. tools/controller/delegation.py +195 -0
  574. tools/controller/execution.py +797 -0
  575. tools/controller/execution_context.py +141 -0
  576. tools/controller/hooks.py +135 -0
  577. tools/controller/introspection.py +344 -0
  578. tools/controller/mcp_registrar.py +190 -0
  579. tools/controller/message_send.py +31 -0
  580. tools/controller/registry/__init__.py +4 -0
  581. tools/controller/registry/schema_generators.py +395 -0
  582. tools/controller/registry/schema_sanitizer.py +406 -0
  583. tools/controller/registry/service.py +1266 -0
  584. tools/controller/registry/views.py +151 -0
  585. tools/controller/service.py +409 -0
  586. tools/controller/tool_management.py +334 -0
  587. tools/controller/types.py +28 -0
  588. tools/controller/views.py +450 -0
  589. tools/cronjob_tools.py +166 -0
  590. tools/crypto_trade_gate.py +59 -0
  591. tools/descriptors.py +452 -0
  592. tools/dom/__init__.py +0 -0
  593. tools/dom/history_tree_processor/service.py +244 -0
  594. tools/dom/history_tree_processor/view.py +99 -0
  595. tools/dom/service.py +471 -0
  596. tools/dom/views.py +346 -0
  597. tools/email_tool.py +403 -0
  598. tools/exceptions.py +169 -0
  599. tools/filesystem.py +1038 -0
  600. tools/filesystem_docproc.py +331 -0
  601. tools/filesystem_pdf.py +755 -0
  602. tools/git/__init__.py +42 -0
  603. tools/git/tool.py +258 -0
  604. tools/github/__init__.py +33 -0
  605. tools/github/client.py +94 -0
  606. tools/github/tool.py +237 -0
  607. tools/goal_tools.py +309 -0
  608. tools/hyperliquid/__init__.py +56 -0
  609. tools/hyperliquid/models.py +233 -0
  610. tools/hyperliquid/service.py +1680 -0
  611. tools/knowledge_ingest.py +950 -0
  612. tools/mcp/README.md +535 -0
  613. tools/mcp/__init__.py +52 -0
  614. tools/mcp/catalog.py +115 -0
  615. tools/mcp/config.py +337 -0
  616. tools/mcp/mcp_tool.py +1415 -0
  617. tools/mcp/param_coercion.py +289 -0
  618. tools/mcp/protocol.py +1520 -0
  619. tools/mcp/rate_limit.py +50 -0
  620. tools/mcp/security.py +424 -0
  621. tools/mcp/self_install.py +111 -0
  622. tools/mcp/server_manager.py +829 -0
  623. tools/mcp/subscriptions.py +72 -0
  624. tools/mcp/user_mcp_service.py +899 -0
  625. tools/mcp/validation_tracker.py +89 -0
  626. tools/mcp/views.py +346 -0
  627. tools/oauth/__init__.py +17 -0
  628. tools/oauth/manager.py +75 -0
  629. tools/oauth/provider.py +69 -0
  630. tools/oauth/providers/__init__.py +1 -0
  631. tools/oauth/providers/generic_oauth2.py +96 -0
  632. tools/perplexity_tool.py +280 -0
  633. tools/polymarket/__init__.py +41 -0
  634. tools/polymarket/clob_adapter.py +76 -0
  635. tools/polymarket/models.py +182 -0
  636. tools/polymarket/service.py +2082 -0
  637. tools/self_env/__init__.py +61 -0
  638. tools/self_env/tool.py +274 -0
  639. tools/shell/__init__.py +80 -0
  640. tools/shell/backend_pool.py +118 -0
  641. tools/shell/discipline.py +62 -0
  642. tools/shell/executor.py +157 -0
  643. tools/shell/loopback_allow.py +87 -0
  644. tools/shell/process_registry.py +113 -0
  645. tools/shell/process_tool.py +131 -0
  646. tools/shell/state.py +129 -0
  647. tools/shell/tool.py +151 -0
  648. tools/task_tool.py +637 -0
  649. tools/twitter_tool.py +2086 -0
  650. tools/user_directory.py +179 -0
  651. tools/web_fetch/__init__.py +3 -0
  652. tools/web_fetch/fetcher.py +144 -0
  653. tools/web_fetch/render.py +44 -0
  654. tools/web_fetch/tool.py +60 -0
  655. tools/x402/__init__.py +60 -0
  656. tools/x402/client.py +47 -0
  657. tools/x402/invoice_tool.py +143 -0
  658. tools/x402/real_client.py +250 -0
  659. tools/x402/service.py +131 -0
  660. utils/README.md +498 -0
  661. utils/__init__.py +46 -0
  662. utils/auth_utils.py +114 -0
  663. utils/bounded_collections.py +162 -0
  664. utils/circuit_breaker.py +389 -0
  665. utils/gif_utils.py +435 -0
  666. utils/markdown_utils.py +291 -0
  667. utils/message_utils.py +147 -0
  668. utils/metrics.py +36 -0
  669. utils/path_validator.py +227 -0
  670. utils/rate_limit_manager.py +399 -0
  671. utils/result_size.py +296 -0
  672. utils/time_utils.py +206 -0
  673. utils/user_utils.py +188 -0
  674. webview/README.md +111 -0
  675. webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
  676. webview/__init__.py +8 -0
  677. webview/activity.py +636 -0
  678. webview/owner_auth.py +108 -0
  679. webview/pages.py +448 -0
  680. webview/repair_sessions.py +399 -0
  681. webview/server.py +4323 -0
  682. webview/server_launcher.py +112 -0
  683. webview/static/css/activity.css +89 -0
  684. webview/static/css/chat.css +1772 -0
  685. webview/static/css/components.css +1190 -0
  686. webview/static/css/config-panel.css +651 -0
  687. webview/static/css/pages/admin.css +766 -0
  688. webview/static/css/pages/profile.css +231 -0
  689. webview/static/css/pages/settings.css +297 -0
  690. webview/static/css/pages/signin.css +316 -0
  691. webview/static/css/style.css +4250 -0
  692. webview/static/css/variables.css +147 -0
  693. webview/static/css/workspace-fullwidth-fix.css +109 -0
  694. webview/static/img/favicon.ico +0 -0
  695. webview/static/js/activity.js +394 -0
  696. webview/static/js/admin/activity.js +186 -0
  697. webview/static/js/admin/dashboard.js +163 -0
  698. webview/static/js/admin/user_detail.js +400 -0
  699. webview/static/js/admin/users.js +174 -0
  700. webview/static/js/admin/utils.js +214 -0
  701. webview/static/js/chat.js +3178 -0
  702. webview/static/js/config-panel.js +522 -0
  703. webview/static/js/constants.js +20 -0
  704. webview/static/js/error-handler.js +226 -0
  705. webview/static/js/ethers.min.js +1 -0
  706. webview/static/js/event-filter.js +137 -0
  707. webview/static/js/event-store.js +351 -0
  708. webview/static/js/file-attachments.js +374 -0
  709. webview/static/js/file-loader.js +163 -0
  710. webview/static/js/index.js +103 -0
  711. webview/static/js/performance-utils.js +196 -0
  712. webview/static/js/profile.js +396 -0
  713. webview/static/js/screenshot.js +386 -0
  714. webview/static/js/session.js +1527 -0
  715. webview/static/js/settings.js +1288 -0
  716. webview/static/js/sidebar-data.js +766 -0
  717. webview/static/js/sidebar-toggle.js +296 -0
  718. webview/static/js/socket.io.min.js +7 -0
  719. webview/static/js/stats.js +355 -0
  720. webview/static/js/ui-utils.js +420 -0
  721. webview/static/js/workspace.js +966 -0
  722. webview/stats_service.py +613 -0
  723. webview/templates/__init__.py +1 -0
  724. webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  725. webview/templates/activity.html +36 -0
  726. webview/templates/admin/activity.html +122 -0
  727. webview/templates/admin/dashboard.html +112 -0
  728. webview/templates/admin/user_detail.html +228 -0
  729. webview/templates/admin/users.html +105 -0
  730. webview/templates/autonomy.html +60 -0
  731. webview/templates/error.html +133 -0
  732. webview/templates/finance.html +90 -0
  733. webview/templates/identity.html +73 -0
  734. webview/templates/index.html +42 -0
  735. webview/templates/layout.html +125 -0
  736. webview/templates/memory.html +59 -0
  737. webview/templates/owner_login.html +15 -0
  738. webview/templates/profile.html +121 -0
  739. webview/templates/session.html +312 -0
  740. webview/templates/settings.html +534 -0
  741. webview/templates/sidebar.html +112 -0
  742. webview/templates/signin.html +213 -0
  743. webview/templates/status.html +14 -0
  744. webview/templates/system.html +45 -0
  745. webview/webgate.py +196 -0
@@ -0,0 +1,49 @@
1
+ """R1: conversational-exit policy for the agent run loop.
2
+
3
+ Pure helpers so the policy is unit-testable without a live Agent. The run loop
4
+ ends a turn when the agent keeps producing user-facing replies without calling
5
+ done() — otherwise a greeting/chat answer loops and re-greets (the 2026-06-14
6
+ Kimi anomaly). The rule is intentionally conservative:
7
+
8
+ * A turn ends only after ``CONVERSATIONAL_EXIT_AFTER_REPLIES`` *consecutive*
9
+ reply-only steps (no tool actually ran in any of them).
10
+ * Any productive step (a real tool result) OR a planning turn resets the
11
+ counter, so a task that does real work — even one that opens or closes with a
12
+ single standalone status message — is never cut short.
13
+ * Sub-agents never take this path; their turn boundary is owned by the parent.
14
+
15
+ A *blocking* send_message and done() already end the turn via ``is_done`` and are
16
+ not handled here (they are not tagged ``conversational_reply``).
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from typing import Iterable
21
+
22
+ # Number of back-to-back reply-only steps that ends a turn. 2 (not 1) leaves room
23
+ # for a task to follow a leading status message with real work on the next step.
24
+ CONVERSATIONAL_EXIT_AFTER_REPLIES = 2
25
+
26
+
27
+ def is_reply_only_step(results: Iterable) -> bool:
28
+ """True if the step produced ≥1 result and EVERY result is a non-blocking
29
+ user-facing reply (tagged ``metadata.conversational_reply``) — i.e. no tool
30
+ actually executed this step.
31
+ """
32
+ results = list(results or [])
33
+ if not results:
34
+ return False
35
+ return all(bool(getattr(r, "metadata", None)) and r.metadata.get("conversational_reply") for r in results)
36
+
37
+
38
+ def should_conversational_exit(consecutive_reply_steps: int, is_sub_agent: bool,
39
+ is_autonomous: bool = False) -> bool:
40
+ """Whether the run loop should end the turn now (R1).
41
+
42
+ T2-08: an AUTONOMOUS session (goal/cron/planner-spawned) is exempt like a
43
+ sub-agent — there is no human chatting with it, so a run of status
44
+ send_messages is progress narration, not a greeting loop; cutting the turn
45
+ there ended missions mid-flight (and mislabeled the run's completion).
46
+ """
47
+ if is_sub_agent or is_autonomous:
48
+ return False
49
+ return consecutive_reply_steps >= CONVERSATIONAL_EXIT_AFTER_REPLIES
@@ -0,0 +1,227 @@
1
+ """WS-A capability gate — block high-impact tools while a session is "tainted" by
2
+ untrusted correspondent DATA (Fusion HIGH/Q9).
3
+
4
+ Correspondent replies enter as a user-role control message with prompt-level framing
5
+ (`<correspondent-message>` + `<untrusted_tool_result>`). That framing is a SOFT defense:
6
+ a strong injection could still try to drive a tool. This pre-tool-call hook is the
7
+ STRUCTURAL backstop — while the latest input on a session is correspondent-untrusted,
8
+ the dangerous tools (money, outbound messaging, code execution, delegation, browser)
9
+ are denied, so a forged email can never directly spend/send/execute. The owner clears
10
+ the taint simply by sending a genuine message (they are driving again).
11
+
12
+ Pure policy + a hook factory; the taint flag lives on the orchestrator (set on
13
+ correspondent injection, cleared on owner intake).
14
+
15
+ ⚠️ **tool_id vs action-name (the load-bearing detail).** The pre-tool-call hook receives
16
+ the bare *action name* — ``run_code``, ``goal_create``, ``x402_fetch`` — never the
17
+ *tool_id* (``code_execution``, ``goal``, ``x402_pay``). A denylist keyed on tool_ids is
18
+ therefore dead: it only ever fires for a tool whose single action is literally named
19
+ after the tool_id. So this module blocks in two layers:
20
+
21
+ 1. ``_HIGH_IMPACT_NAMES`` — explicit high-impact *action* names (delegation, skill/
22
+ self-context, crypto trade verbs, git/github write verbs). Matched by name.
23
+ 2. ``HIGH_IMPACT_TOOL_IDS`` — whole tools whose every non-read action is high-impact,
24
+ matched by RESOLVING the action's owning tool_id at hook time (via a resolver the
25
+ wiring passes in, backed by ``Controller.get_action_details(name).tool``). This is
26
+ the only way to cover dynamically-named actions such as MCP direct actions
27
+ (``{server}_{tool}`` → tool_id ``mcp``).
28
+
29
+ Crypto tools (``hyperliquid``/``polymarket``) are deliberately EXCLUDED from
30
+ ``HIGH_IMPACT_TOOL_IDS`` so their read verbs (``get_*``) stay allowed — a tainted
31
+ session may still answer "what's the price?"; their trade verbs are enumerated by name.
32
+ """
33
+ from __future__ import annotations
34
+
35
+ import logging
36
+ from typing import Any, Callable, Optional
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Layer 1 — high-impact *action* names (matched against the bare action name the
42
+ # pre-hook receives). Includes standalone actions (registered directly, with no
43
+ # owning container tool_id) plus enumerated verbs kept for defense-in-depth even
44
+ # when tool-id resolution is unavailable. Crypto READ verbs are deliberately absent.
45
+ # ---------------------------------------------------------------------------
46
+ _HIGH_IMPACT_NAMES = frozenset({
47
+ # Delegation actions (registered directly, not container tools).
48
+ "delegate_task", "subtask", "parallel_subtasks",
49
+ # Identity / self-evolution actions (registered directly).
50
+ "skill_manage", "self_context_manage",
51
+ # Gated outbound message-to-target action (registered directly, no owning
52
+ # tool_id) — owner/allowlist-checked send to telegram/email/whatsapp.
53
+ "message",
54
+ # Aspirational coding/self-evolution action names (no tool yet; harmless tokens).
55
+ "self_modify", "mcp_install",
56
+ # WS-5: self_env self-maintenance verbs (posture 2). Owner-only via the posture
57
+ # gate already, but a tainted session must never reach them either.
58
+ "self_env_install_dep", "self_env_patch_source", "self_env_restart_service",
59
+ "self_env_git_pull", "self_env_read_source", "shell_run",
60
+ # WS-2/3: process job-manager verbs — enumerated by NAME (parity with shell_run)
61
+ # so a tool-id-resolver fault can't let a tainted session kill/inspect the owner's
62
+ # background shell jobs.
63
+ "process_kill", "process_log", "process_poll", "process_list",
64
+ # P1-4: code-exec verb enumerated by NAME (parity with shell_run) so a resolver
65
+ # fault can't open arbitrary code execution to a tainted session (the code_execution
66
+ # tool_id below only helps when resolution succeeds).
67
+ "run_code",
68
+ # P1-4: the agent money verb — x402_invoice tool, verb x402_request — mints a
69
+ # payment request. The canonical forged-email social-engineering target; must be
70
+ # unreachable while correspondent-tainted (x402_fetch/x402_pay already are).
71
+ "x402_request",
72
+ # P1-4: outbound-egress verbs whose query params are an exfil channel (parity with
73
+ # web_fetch/browser, which are already blocked). anysite/perplexity reach the
74
+ # outside world with attacker-influenced arguments.
75
+ "anysite_api", "perplexity_search",
76
+ # P1-4: curated-memory write persists into FUTURE sessions' prompts — a durable
77
+ # injection-persistence channel if written while tainted. Gate the whole action
78
+ # (read too) — fail-closed; the owner clears taint by replying.
79
+ "memory",
80
+ # Crypto trade verbs (hyperliquid + polymarket share these). Reads (get_*) are
81
+ # intentionally excluded so a tainted session can still fetch prices/history.
82
+ "place_limit_order", "place_market_order", "cancel_order", "cancel_all_orders",
83
+ "update_leverage", "approve_agent", "revoke_agent",
84
+ # git/github write verbs — also covered by tool-id resolution, kept here so a
85
+ # resolver fault can't open the money/ship-code path.
86
+ "git_push", "github_open_pr", "github_merge_pr", "github_pr_comment",
87
+ "github_issue_create",
88
+ # The auto-paying x402 action (the tool_id is x402_pay; the verb is x402_fetch).
89
+ "x402_fetch",
90
+ # Legacy tool_id tokens kept so is_high_impact(tool_id) stays truthy for callers/
91
+ # tests that probe by tool_id. Real per-verb coverage of these tools comes from
92
+ # HIGH_IMPACT_TOOL_IDS resolution below.
93
+ "code_execution", "coding", "cronjob", "goal", "x402_pay",
94
+ "hyperliquid", "polymarket", "email", "twitter", "browser", "web_fetch",
95
+ "git", "github", "process", "tool_manage", "mcp",
96
+ "x402_invoice", "anysite", "perplexity", # P1-4 legacy tool_id tokens
97
+ })
98
+
99
+ # Back-compat public name (tests / other callers import HIGH_IMPACT_TOOLS).
100
+ HIGH_IMPACT_TOOLS = _HIGH_IMPACT_NAMES
101
+
102
+ # ---------------------------------------------------------------------------
103
+ # Layer 2 — tool_ids whose EVERY action is high-impact, resolved at hook time from
104
+ # the action name. Crypto is intentionally absent (its reads must stay allowed; its
105
+ # trade verbs are in _HIGH_IMPACT_NAMES). Blocking a high-impact tool's read verbs
106
+ # too (e.g. goal_list, cronjob_list, git_log) while tainted is the safe/fail-closed
107
+ # direction — the owner clears the taint by replying.
108
+ # ---------------------------------------------------------------------------
109
+ HIGH_IMPACT_TOOL_IDS = frozenset({
110
+ "code_execution", # run_code — arbitrary code exec
111
+ "coding", # str_replace / apply_patch / run_tests / *_file — repo mutation
112
+ "cronjob", # cronjob_schedule / cancel — durable recurring runs
113
+ "goal", # goal_create / objective_* — durable autonomous work
114
+ "x402_pay", # x402_fetch — auto-pay
115
+ "email", # send_email — outbound comms
116
+ "twitter", # twitter_post / reply / quote / thread — outbound comms
117
+ "browser", # go_to_url / click_element / … — SSRF / exfil
118
+ "web_fetch", # fetch_url — outbound fetch (SSRF / exfil)
119
+ "git", # git_commit / clone / pull / push — ship code / identity
120
+ "github", # github_open_pr / merge / … — ship code / identity
121
+ "mcp", # execute_tool + dynamic {server}_{tool} — outbound / exec
122
+ "process", # background processes (aspirational)
123
+ "tool_manage", # dynamic-tool authoring (aspirational)
124
+ "shell", # WS-2: persistent sandbox shell — arbitrary command exec
125
+ "self_env", # WS-5: self-maintenance verbs — install/patch/restart/pull
126
+ # P1-4:
127
+ "x402_invoice", # x402_request / accounting — mint agent payment requests (money)
128
+ "anysite", # anysite_api — outbound structured-data egress (exfil channel)
129
+ "perplexity", # perplexity_search — outbound search egress (exfil channel)
130
+ })
131
+
132
+ # Substrings that mark a high-impact action even if the exact name isn't enumerated
133
+ # (e.g. provider-prefixed MCP/web tools that reach the outside world). NOTE: do NOT add
134
+ # "trade" here — as a substring it falsely flags the read action get_trade_history while
135
+ # matching no real trade verb (those are enumerated above).
136
+ _HIGH_IMPACT_PREFIXES = ("mcp_", "browser_", "web_", "email_", "pay", "send_email")
137
+
138
+
139
+ def is_high_impact(action_name: Optional[str]) -> bool:
140
+ """Name-only high-impact test (no tool_id resolution).
141
+
142
+ True if ``action_name`` is an enumerated high-impact action name (or legacy
143
+ tool_id token) or matches a high-impact substring. This is the backward-compatible
144
+ surface; the wired hook additionally resolves the owning tool_id via
145
+ :func:`is_high_impact_call`.
146
+ """
147
+ if not action_name:
148
+ return False
149
+ name = str(action_name).strip().lower()
150
+ if name in _HIGH_IMPACT_NAMES:
151
+ return True
152
+ return any(name.startswith(p) or p in name for p in _HIGH_IMPACT_PREFIXES)
153
+
154
+
155
+ def is_high_impact_call(action_name: Optional[str], tool_id: Optional[str] = None) -> bool:
156
+ """Full high-impact decision for one action call.
157
+
158
+ Blocks when the bare action name is high-impact (:func:`is_high_impact`) OR when the
159
+ action's owning ``tool_id`` is in :data:`HIGH_IMPACT_TOOL_IDS`. ``tool_id`` is the
160
+ resolved owning tool (``Controller.get_action_details(name).tool``); pass ``None``
161
+ when it can't be resolved, in which case the decision degrades to the name-only path.
162
+ """
163
+ if is_high_impact(action_name):
164
+ return True
165
+ if tool_id and str(tool_id).strip().lower() in HIGH_IMPACT_TOOL_IDS:
166
+ return True
167
+ return False
168
+
169
+
170
+ def build_tool_resolver(controller: Any) -> Callable[[str], Optional[str]]:
171
+ """Return ``action_name -> owning tool_id`` backed by a Controller.
172
+
173
+ Uses ``Controller.get_action_details(name).tool`` — the same registry seam the
174
+ untrusted-wrap uses to resolve an action's source tool. Never raises: a missing
175
+ controller, an unknown action, or a registry fault all degrade to ``None`` (the
176
+ caller then falls back to the name-only decision).
177
+ """
178
+ def _resolve(action_name: str) -> Optional[str]:
179
+ if controller is None:
180
+ return None
181
+ try:
182
+ details = controller.get_action_details(action_name)
183
+ return getattr(details, "tool", None) if details is not None else None
184
+ except Exception as e:
185
+ logger.debug("build_tool_resolver: get_action_details failed for %r: %s",
186
+ action_name, e)
187
+ return None
188
+ return _resolve
189
+
190
+
191
+ def make_correspondent_gate_hook(
192
+ get_tainted: Callable[[], bool],
193
+ resolve_tool: Optional[Callable[[str], Optional[str]]] = None,
194
+ ):
195
+ """Pre-tool-call hook ``(action_name, params, context) -> Optional[str]``.
196
+
197
+ Returns a denial reason (string) for a high-impact tool while the session is tainted
198
+ by untrusted correspondent data; None otherwise. Fail-CLOSED: if the taint probe
199
+ raises, a high-impact tool is denied (we can't prove the session is clean).
200
+
201
+ ``resolve_tool`` maps an action name to its owning tool_id so a tool_id-level
202
+ denylist (:data:`HIGH_IMPACT_TOOL_IDS`) actually fires — the pre-hook only ever sees
203
+ the bare action name. It is called defensively: a resolver fault degrades to the
204
+ name-only decision (never raises out of the hook, never silently opens a hole for a
205
+ name-level high-impact action).
206
+ """
207
+ def _hook(action_name: str, params: Any, context: Any) -> Optional[str]:
208
+ tool_id: Optional[str] = None
209
+ if resolve_tool is not None:
210
+ try:
211
+ tool_id = resolve_tool(action_name)
212
+ except Exception as e: # resolution must never break the gate
213
+ logger.debug("correspondent gate tool resolve failed: %s", e)
214
+ tool_id = None
215
+ if not is_high_impact_call(action_name, tool_id):
216
+ return None
217
+ try:
218
+ tainted = bool(get_tainted())
219
+ except Exception as e: # fail-closed: can't prove clean -> deny the dangerous tool
220
+ logger.debug("correspondent gate taint probe failed (deny): %s", e)
221
+ tainted = True
222
+ if tainted:
223
+ return (f"'{action_name}' is blocked: the latest input is untrusted "
224
+ f"correspondent DATA — owner confirmation is required before a "
225
+ f"high-impact action.")
226
+ return None
227
+ return _hook
@@ -0,0 +1,246 @@
1
+ """Skill curator (W5, Reference-parity curator.py).
2
+
3
+ Periodic maintenance of self-authored skills so the library doesn't rot: unused
4
+ authored skills go stale, then archive (recoverable, never hard-deleted); reuse
5
+ reactivates them. A skill the agent created but never reloads is noise — the curator
6
+ is what keeps "writable skills" from becoming "write-only skills".
7
+
8
+ Phase 1 (no LLM, always-on when CURATOR_ENABLED): ``apply_automatic_transitions``
9
+ over ``created_by ∈ {agent, background_review}`` skills using the W2-D load_count:
10
+ unused + age > ``CURATOR_STALE_DAYS`` → stale; stale + age > ``CURATOR_ARCHIVE_DAYS``
11
+ → archive (via SkillManager.delete_skill → ``.archived/``); reuse reactivates.
12
+ System/pinned skills are never touched (only agent-authored ones).
13
+
14
+ (A Phase-2 aux-LLM "merge near-duplicate skills into umbrellas" step was scaffolded
15
+ but never had a real merge policy — it was a logged no-op. It was removed (2026-06-29
16
+ dead-loop prune); re-add it under its own flag when a concrete merge policy exists.)
17
+
18
+ Wired as a lifespan ticker (interval ``CURATOR_INTERVAL_HOURS``). Fail-open; a
19
+ dry-run mode lets the first rollout observe transitions without applying them.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ import time
25
+ from pathlib import Path
26
+ from typing import Any, Dict, List, Optional
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ _AUTHORED = ["agent", "background_review"]
31
+ _DAY_S = 86400.0
32
+
33
+
34
+ class SkillCurator:
35
+ def __init__(self, skill_manager: Any, usage_store: Any, *,
36
+ clock=time.time, dry_run: bool = False):
37
+ self.sm = skill_manager
38
+ self.usage = usage_store
39
+ self._now = clock
40
+ self.dry_run = dry_run
41
+
42
+ # --- Phase 1: automatic, no LLM -----------------------------------------
43
+
44
+ def apply_automatic_transitions(self) -> Dict[str, List[str]]:
45
+ """Stale/archive/reactivate authored skills by age + reuse. Returns the plan.
46
+
47
+ Pure-ish: computes the transition for every authored skill, then (unless
48
+ dry_run) applies archives via SkillManager. ``stale``/``reactivate`` are
49
+ bookkeeping states recorded in curator_state; ``archive`` is the only
50
+ filesystem effect (and it is recoverable).
51
+ """
52
+ from agents.task.constants import AutonomyConfig
53
+ stale_days = AutonomyConfig.curator_stale_days()
54
+ archive_days = AutonomyConfig.curator_archive_days()
55
+ now = self._now()
56
+ plan: Dict[str, List[str]] = {"stale": [], "archived": [], "reactivated": [], "kept": []}
57
+
58
+ rows = self.usage.list_authored(created_by=_AUTHORED)
59
+ for r in rows:
60
+ skill_id = r["skill_id"]
61
+ user_id = r["user_id"]
62
+ load_count = r.get("load_count", 0) or 0
63
+ created_at = r.get("created_at") or now
64
+ age_days = max(0.0, (now - created_at) / _DAY_S)
65
+ key = f"{user_id}/{skill_id}"
66
+
67
+ # SK-F2: a background-review-authored draft always lands in
68
+ # `.pending/` (quarantined, uncatalogued) — its load_count can
69
+ # NEVER rise because it isn't in rules.json to be loaded from.
70
+ # Age-based archiving would otherwise sweep up drafts nobody has
71
+ # reviewed yet. Resolve the actual file (active wins over pending
72
+ # if both somehow exist) and skip the row entirely while pending.
73
+ find_file = getattr(self.sm, "_find_skill_file", None)
74
+ if callable(find_file):
75
+ try:
76
+ resolved = find_file(user_id, skill_id)
77
+ except Exception:
78
+ resolved = None
79
+ if resolved is not None and ".pending" in Path(resolved).parts:
80
+ continue # pending = awaiting owner review, load_count can never rise
81
+
82
+ if load_count > 0:
83
+ # Reused → keep active; clear any prior stale mark.
84
+ if self._get_mark(key) == "stale":
85
+ plan["reactivated"].append(key)
86
+ if not self.dry_run:
87
+ self._set_mark(key, "active")
88
+ self._self_mod_ev("reactivate", skill_id, user_id)
89
+ else:
90
+ plan["kept"].append(key)
91
+ continue
92
+
93
+ # never reused
94
+ # P2-21: skip a skill already archived on a prior tick. list_authored still
95
+ # returns its provenance row (rows persist after archive), so without this
96
+ # guard the weekly curator re-archived the same skill every tick — re-calling
97
+ # delete_skill (which returns False, file already gone) and re-emitting a
98
+ # self_modification "archive" event each time.
99
+ if self._get_mark(key) == "archived":
100
+ continue
101
+ if age_days > archive_days:
102
+ plan["archived"].append(key)
103
+ if not self.dry_run:
104
+ try:
105
+ self.sm.delete_skill(skill_id, user_id=user_id, absorbed_into=None)
106
+ self._set_mark(key, "archived")
107
+ self._self_mod_ev("archive", skill_id, user_id)
108
+ except Exception as e:
109
+ logger.debug("curator archive failed for %s: %s", key, e)
110
+ elif age_days > stale_days:
111
+ plan["stale"].append(key)
112
+ if not self.dry_run:
113
+ self._set_mark(key, "stale")
114
+ else:
115
+ plan["kept"].append(key)
116
+
117
+ logger.info("curator phase-1: %s",
118
+ {k: len(v) for k, v in plan.items()})
119
+ return plan
120
+
121
+ @staticmethod
122
+ def _self_mod_ev(action: str, skill_id: str, user_id: str) -> None:
123
+ """T4-06: curator lifecycle transitions are self-modifications too —
124
+ record them on the durable event log. Fail-open."""
125
+ try:
126
+ from agents.task.telemetry.self_events import emit_self_modification
127
+ emit_self_modification(kind="skill", action=action, item_id=skill_id,
128
+ user_id=user_id or "", created_by="curator",
129
+ source="curator")
130
+ except Exception as e:
131
+ logger.debug("curator event emit skipped: %s", e)
132
+
133
+ # --- state ---------------------------------------------------------------
134
+
135
+ def _get_mark(self, key: str) -> Optional[str]:
136
+ try:
137
+ return self.usage.get_state(f"curator:{key}")
138
+ except Exception:
139
+ return None
140
+
141
+ def _set_mark(self, key: str, value: str) -> None:
142
+ try:
143
+ self.usage.set_state(f"curator:{key}", value)
144
+ except Exception:
145
+ pass
146
+
147
+ def should_run(self) -> bool:
148
+ """Idle/interval gate: only run once per CURATOR_INTERVAL_HOURS."""
149
+ from agents.task.constants import AutonomyConfig
150
+ interval_s = AutonomyConfig.curator_interval_hours() * 3600.0
151
+ last = self.usage.get_state("curator:last_run")
152
+ if last is None:
153
+ return True
154
+ try:
155
+ return (self._now() - float(last)) >= interval_s
156
+ except (TypeError, ValueError):
157
+ return True
158
+
159
+ def mark_ran(self) -> None:
160
+ self._set_mark_raw("curator:last_run", str(self._now()))
161
+
162
+ def _set_mark_raw(self, key: str, value: str) -> None:
163
+ try:
164
+ self.usage.set_state(key, value)
165
+ except Exception:
166
+ pass
167
+
168
+ async def run_once(self) -> Dict[str, Any]:
169
+ """One curator pass: Phase 1 automatic transitions + episodic retention prune.
170
+ Fail-open.
171
+
172
+ (Stays ``async`` for the ticker contract even though Phase 1 is synchronous.)
173
+ """
174
+ result: Dict[str, Any] = {}
175
+ try:
176
+ result["transitions"] = self.apply_automatic_transitions()
177
+ self.mark_ran()
178
+ except Exception as e:
179
+ logger.error("curator run failed: %s", e, exc_info=True)
180
+ result["error"] = str(e)
181
+ self._prune_episodes()
182
+ return result
183
+
184
+ def _prune_episodes(self) -> None:
185
+ """Episodic activity-ledger retention sweep (Task 7), riding the curator's
186
+ own cadence — never the write path. Global sweep (all tenants); fail-open
187
+ so a memory-backend hiccup never breaks the curator tick."""
188
+ try:
189
+ from agents.task.constants import AutonomyConfig
190
+ from modules.memory.registry import get_memory_registry
191
+ prov = get_memory_registry().active()
192
+ if prov is not None and hasattr(prov, "prune_episodes"):
193
+ cutoff = int(self._now()) - AutonomyConfig.episodic_retention_days() * 86400
194
+ removed = prov.prune_episodes(older_than_ts=cutoff)
195
+ if removed:
196
+ logger.info("curator: pruned %d old episodes", removed)
197
+ except Exception:
198
+ logger.warning("episodic prune skipped", exc_info=True)
199
+
200
+
201
+ class CuratorTicker:
202
+ """Periodically run the curator (interval-gated). Mirrors CronTicker/GoalTicker."""
203
+
204
+ def __init__(self, curator: SkillCurator, *, check_interval_seconds: int = 3600,
205
+ lock_path: Optional[str] = None):
206
+ self.curator = curator
207
+ self.check_interval_seconds = check_interval_seconds
208
+ self.lock_path = lock_path
209
+
210
+ async def run_forever(self, stop_event=None) -> None:
211
+ import asyncio
212
+ while not (stop_event is not None and stop_event.is_set()):
213
+ try:
214
+ if self.curator.should_run():
215
+ lock = None
216
+ if self.lock_path:
217
+ from cron.scheduler import TickLock
218
+ lock = TickLock(self.lock_path)
219
+ if not lock.acquire():
220
+ lock = None # another worker holds the tick this cycle; skip
221
+ else:
222
+ try:
223
+ await self.curator.run_once()
224
+ finally:
225
+ lock.release()
226
+ else:
227
+ await self.curator.run_once()
228
+ except Exception as e:
229
+ logger.error("curator tick failed: %s", e, exc_info=True)
230
+ try:
231
+ await asyncio.wait_for(
232
+ stop_event.wait() if stop_event else asyncio.sleep(self.check_interval_seconds),
233
+ timeout=self.check_interval_seconds,
234
+ )
235
+ except Exception:
236
+ pass
237
+
238
+
239
+ def build_curator_ticker(*, data_dir: str = "data", dry_run: bool = False) -> CuratorTicker:
240
+ """Assemble the curator stack into a ticker the app lifespan can start/stop."""
241
+ import os
242
+ from agents.task.agent.skill_manager import get_skill_manager
243
+ from modules.skills.skill_usage import get_skill_usage_store
244
+ curator = SkillCurator(get_skill_manager(), get_skill_usage_store(data_dir), dry_run=dry_run)
245
+ return CuratorTicker(curator, lock_path=os.path.join(data_dir, "curator.tick.lock"))
246
+
@@ -0,0 +1,103 @@
1
+ """Session-start activity digest (2026-07-03).
2
+
3
+ Passive, read-only, pinned foundation message that puts the agent's OWN recent runs
4
+ in front of it — so it can answer 'what did I run in the last 8h?' without a tool call.
5
+ Scoped to owner/chat sessions ONLY (never goal/cron/sub-agent) to avoid a per-run token
6
+ tax + distraction. Fail-open.
7
+
8
+ NOTE on `kind not in ("chat",)`: goal/cron sessions are excluded by the CALLER (the
9
+ first-step wiring in `memory_prefetch.py::MemoryPrefetchMixin._maybe_inject_episodic_digest`
10
+ resolves `kind` via `agents.task.goals.autonomy_marker.is_autonomous(session_id)` and only
11
+ ever passes `kind="chat"` here after confirming the session is not autonomous — see that
12
+ module's docstring for why this can't be decided at Agent-construction time). This function
13
+ re-checks `kind` itself so it is also safe to call directly (as the tests below do) with an
14
+ explicit non-chat `kind`.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import time
20
+ from typing import Optional
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ async def build_activity_digest(*, user_id: Optional[str], kind: str,
26
+ is_sub_agent: bool, window_hours: int = 24,
27
+ limit: int = 10):
28
+ try:
29
+ from agents.task.constants import AutonomyConfig
30
+ if not AutonomyConfig.episodic_digest_inject():
31
+ return None
32
+ if is_sub_agent or kind not in ("chat",): # owner/chat sessions only
33
+ return None
34
+ from modules.memory.registry import memory_recall_episodes
35
+ since_ts = int(time.time()) - window_hours * 3600
36
+ # exclude_surfaced=True: an episode already delivered out-of-band (cron
37
+ # email/telegram/twitter, or a goal self-wake) doesn't need to be
38
+ # re-surfaced here too — recent_activity() (explicit query) still sees
39
+ # everything regardless of the surfaced flag.
40
+ rows = await memory_recall_episodes(user_id=user_id, since_ts=since_ts,
41
+ limit=limit, order="newest",
42
+ exclude_surfaced=True)
43
+ if not rows:
44
+ return None
45
+ lines, total = [], 0.0
46
+ for e in rows:
47
+ total += float(e.spend_usd or 0)
48
+ lines.append(f"- {e.kind}:{e.outcome or '?'} ${float(e.spend_usd or 0):.2f} "
49
+ f"\"{(e.task or '')[:60]}\"")
50
+ body = ("\n".join(lines)
51
+ + f"\n({len(rows)} runs in last {window_hours}h, ${total:.2f} total). "
52
+ "Call recent_activity(since=\"8h\") for more/older.")
53
+ try:
54
+ from agents.task.agent.core.untrusted_wrap import wrap_untrusted
55
+ body = wrap_untrusted("recent_activity", body)
56
+ except Exception:
57
+ pass
58
+ from modules.llm.messages import make_control_message, MessageOrigin
59
+ return make_control_message(
60
+ f"<recent_activity window=\"last {window_hours}h\">\n{body}\n</recent_activity>",
61
+ MessageOrigin.EPISODIC_DIGEST)
62
+ except Exception:
63
+ logger.warning("build_activity_digest failed", exc_info=True)
64
+ return None
65
+
66
+
67
+ async def build_mission_continuity(*, user_id: Optional[str], window_hours: int = 24,
68
+ limit: int = 8):
69
+ """Carry recent autonomous activity INTO the next goal/cron tick (§7.5).
70
+
71
+ Mirror-image of ``build_activity_digest``: this one is FOR autonomous sessions
72
+ (the caller confirms ``is_autonomous`` and never sub-agent), so a tick knows what
73
+ it already did/attempted and stops re-deriving 'nothing new' from scratch. Gated
74
+ ``AUTONOMOUS_CONTINUITY_BRIDGE`` (default OFF). Fail-open. Does NOT exclude
75
+ already-surfaced episodes — a tick genuinely needs to see everything it did.
76
+ """
77
+ try:
78
+ from agents.task.constants import AutonomyConfig
79
+ if not AutonomyConfig.autonomous_continuity_bridge():
80
+ return None
81
+ from modules.memory.registry import memory_recall_episodes
82
+ since_ts = int(time.time()) - window_hours * 3600
83
+ rows = await memory_recall_episodes(user_id=user_id, since_ts=since_ts,
84
+ limit=limit, order="newest")
85
+ if not rows:
86
+ return None
87
+ lines = [f"- {e.kind}:{e.outcome or '?'} \"{(e.task or '')[:70]}\"" for e in rows]
88
+ body = ("\n".join(lines)
89
+ + f"\n({len(rows)} recent runs in last {window_hours}h). "
90
+ "Build on this — do NOT repeat work already done; if it's all covered, "
91
+ "surface the blocker/next step rather than re-deriving 'nothing new'.")
92
+ try:
93
+ from agents.task.agent.core.untrusted_wrap import wrap_untrusted
94
+ body = wrap_untrusted("recent_activity", body)
95
+ except Exception:
96
+ pass
97
+ from modules.llm.messages import make_control_message, MessageOrigin
98
+ return make_control_message(
99
+ f"<mission_continuity window=\"last {window_hours}h\">\n{body}\n</mission_continuity>",
100
+ MessageOrigin.EPISODIC_DIGEST)
101
+ except Exception:
102
+ logger.warning("build_mission_continuity failed", exc_info=True)
103
+ return None