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,228 @@
1
+ """Safety + lifecycle-control mixin (roadmap P9 decomposition; code-motion from service.py).
2
+
3
+ The loop-safety (failure/overflow/stall detection, control-flag handling) and
4
+ lifecycle (pause/resume/stop/cancel/reset) concern, moved verbatim off the
5
+ ``Agent`` god-file. ``Agent`` composes ``SafetyLifecycleMixin`` so call sites
6
+ (run loop in step.py, and external ``agent.stop()`` / ``agent.reset_for_continuation()``)
7
+ are unchanged.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import time
13
+
14
+
15
+ class SafetyLifecycleMixin:
16
+ """Failure/stall safety + pause/resume/stop/cancel/reset for Agent."""
17
+
18
+ def _too_many_failures(self) -> bool:
19
+ """Check if we should stop due to too many failures"""
20
+ if self.state.consecutive_failures >= self.max_failures:
21
+ self.logger.error(f'❌ Stopping due to {self.max_failures} consecutive failures', exc_info=True)
22
+ return True
23
+ return False
24
+
25
+ def _check_context_overflow(self) -> bool:
26
+ """Check if context exceeds threshold and trigger emergency prune.
27
+
28
+ SIMPLIFIED (Dec 2025): Uses CONTEXT_OVERFLOW_THRESHOLD from config (default 90%).
29
+ Let context grow naturally, only prune when necessary.
30
+
31
+ Returns:
32
+ True if pruning was triggered, False otherwise
33
+ """
34
+ if not hasattr(self, 'message_manager') or not self.message_manager:
35
+ return False
36
+
37
+ from agents.task.robust_parse_config import RobustParseConfig
38
+
39
+ usage_pct = self.message_manager.get_context_usage_percent()
40
+ threshold = RobustParseConfig.CONTEXT_OVERFLOW_THRESHOLD * 100 # Convert to percentage
41
+
42
+ if usage_pct >= threshold:
43
+ self.logger.info(f"📦 Context at {usage_pct:.1f}% (threshold: {threshold:.0f}%) - emergency prune")
44
+ self.message_manager.emergency_context_prune()
45
+ return True
46
+
47
+ return False
48
+
49
+ async def _check_for_stall(self) -> bool:
50
+ """Check if the agent is stalled and not making progress.
51
+
52
+ Returns:
53
+ True if agent is stalled and should be stopped
54
+ """
55
+ if not self.stall_timeout_seconds:
56
+ return False
57
+
58
+ current_time = time.time()
59
+
60
+ # IMPORTANT: Don't report stalls while waiting for LLM response
61
+ # LLM calls can legitimately take 60-200+ seconds for complex tasks
62
+ if self._llm_call_in_progress:
63
+ llm_duration = current_time - (self._llm_call_start_time or current_time)
64
+ # Only log at longer intervals (every 60s) to reduce noise
65
+ if llm_duration > 0 and int(llm_duration) % 60 == 0:
66
+ self.logger.debug(f"⏳ LLM call in progress for {llm_duration:.0f}s - stall check skipped")
67
+ return False
68
+
69
+ # Check if we've been inactive for too long (excluding LLM time)
70
+ time_since_activity = current_time - self.state.last_activity_time
71
+ if time_since_activity > self.stall_timeout_seconds:
72
+ self.logger.error(f"Agent stalled: No activity for {time_since_activity:.1f} seconds (limit: {self.stall_timeout_seconds}s)", exc_info=True)
73
+ return True
74
+
75
+ # Check if we're stuck on the same step for too long
76
+ if self.state.last_step_start_time:
77
+ time_in_step = current_time - self.state.last_step_start_time
78
+ if time_in_step > self.step_timeout_seconds:
79
+ self.logger.error(f"Agent stalled: Step taking {time_in_step:.1f} seconds (limit: {self.step_timeout_seconds}s)", exc_info=True)
80
+ return True
81
+
82
+ # Check if we're not making action progress
83
+ if self.state.total_actions_count == self._last_action_count:
84
+ # Only warn if we're NOT waiting for LLM
85
+ if not self._llm_call_in_progress:
86
+ self.logger.warning(f"No new actions in last {self._stall_check_interval} seconds")
87
+ else:
88
+ # Update last action count and activity time
89
+ self._last_action_count = self.state.total_actions_count
90
+ self.state.last_activity_time = current_time
91
+
92
+ return False
93
+
94
+ async def _stall_monitor_loop(self) -> None:
95
+ """Background task to monitor for stalls."""
96
+ try:
97
+ while not self.state.stopped:
98
+ await asyncio.sleep(self._stall_check_interval)
99
+
100
+ if self.state.stopped:
101
+ # Don't check for stalls when stopped
102
+ break
103
+
104
+ if await self._check_for_stall():
105
+ self.logger.error("Stall detected - stopping agent", exc_info=True)
106
+ self.state.consecutive_failures = self.max_failures # Force stop
107
+ self.state.stopped = True
108
+ break
109
+ except asyncio.CancelledError:
110
+ self.logger.debug("Stall monitor cancelled")
111
+ except Exception as e:
112
+ self.logger.error(f"Error in stall monitor: {e}", exc_info=True)
113
+
114
+ async def _handle_control_flags(self) -> bool:
115
+ """Handle stop and done flags. Returns True if execution should stop.
116
+
117
+ NOTE: Pause functionality removed (Dec 2025). The blocking wait loop was
118
+ incompatible with continuous chat design where new messages should be processed.
119
+
120
+ For user interruption, use stop which cleanly exits. Users can send a new
121
+ message to continue with a different task (auto-resume via COMPLETED → RESUMED).
122
+ """
123
+ # Check state.done to stop execution when agent signals completion
124
+ if self.state.done:
125
+ self.logger.info('✅ Agent execution complete (state.done=True)')
126
+ return True
127
+ if self.state.stopped:
128
+ self.logger.info('⏹️ Agent execution stopped by control flag')
129
+ raise InterruptedError("Agent stopped")
130
+
131
+ # Legacy: treat paused as stopped for backwards compatibility
132
+ # The paused flag is deprecated - use stopped instead
133
+ if self.state.paused:
134
+ self.logger.info('⏹️ Agent paused flag set - treating as stop (pause deprecated)')
135
+ self.state.stopped = True # Normalize to stopped
136
+ raise InterruptedError("Agent stopped (paused flag deprecated)")
137
+
138
+ return False
139
+
140
+ def pause(self) -> None:
141
+ """DEPRECATED: Pause functionality removed. Now calls stop() instead.
142
+
143
+ The PAUSED state was removed because the blocking wait loop was incompatible
144
+ with continuous chat design. Use stop() for user interruption, and send a
145
+ new message to continue with modifications (auto-resume via COMPLETED → RESUMED).
146
+ """
147
+ self.logger.warning('⚠️ pause() is DEPRECATED - redirecting to stop()')
148
+ self.state.stopped = True # Use stopped instead of paused
149
+
150
+ def resume(self) -> None:
151
+ """DEPRECATED: Resume functionality removed. Sessions auto-resume on new message.
152
+
153
+ The explicit resume action is no longer needed. When a new message is sent
154
+ to a completed/cancelled session, it automatically resumes via the
155
+ continuous chat flow (COMPLETED → RESUMED → RUNNING).
156
+ """
157
+ self.logger.warning('⚠️ resume() is DEPRECATED - no action taken')
158
+ # No action needed - sessions auto-resume on new message
159
+
160
+ async def stop(self) -> None:
161
+ """Stop the agent execution."""
162
+ self.logger.info('⏹️ Stopping agent execution')
163
+ self.state.stopped = True
164
+
165
+ # Browser cleanup - release context through orchestrator
166
+ try:
167
+ # Release through orchestrator (delegates to BrowserManager)
168
+ await self.orchestrator.release_browser_context(self.agent_id, close=True)
169
+ except Exception as e:
170
+ self.logger.debug(f"Browser cleanup failed: {e}")
171
+
172
+ self.logger.info("Agent stopped")
173
+
174
+ def cancel(self) -> None:
175
+ """Cancel the agent execution immediately.
176
+
177
+ Sets cancellation flag that will be checked in the main run loop,
178
+ causing execution to stop at the next step boundary.
179
+ """
180
+ self.logger.info('❌ Cancelling agent execution')
181
+ self._cancelled = True
182
+
183
+ def reset_for_continuation(self) -> None:
184
+ """Reset agent state for continuous chat continuation.
185
+
186
+ Called when reusing an existing agent for a new task phase.
187
+ Resets execution flags while preserving message history and context.
188
+
189
+ This is CRITICAL for continuous chat to work properly:
190
+ - Without this reset, _last_result contains is_done=True from previous run
191
+ - The run() loop checks _last_result and exits immediately if is_done=True
192
+ - User's new message would never be processed
193
+ """
194
+ self.logger.info('♻️ Resetting agent state for continuation')
195
+
196
+ # Cancel any running stall monitor task from previous run
197
+ # This prevents stale monitors from interfering with new execution
198
+ if hasattr(self, '_stall_check_task') and self._stall_check_task:
199
+ if not self._stall_check_task.done():
200
+ self._stall_check_task.cancel()
201
+ self.logger.debug('Cancelled stale stall monitor task from previous run')
202
+ self._stall_check_task = None
203
+
204
+ # Reset last result - prevents immediate exit due to previous is_done=True
205
+ self._last_result = None
206
+
207
+ # Reset cancellation flag
208
+ self._cancelled = False
209
+
210
+ # Reset execution control flags in AgentState
211
+ self.state.stopped = False
212
+ self.state.done = False
213
+ self.state.paused = False
214
+
215
+ # Reset failure counters for fresh execution
216
+ self.state.consecutive_failures = 0
217
+
218
+ # Reset loop detection state
219
+ self.state.reset_loop_detection()
220
+
221
+ # Update activity time
222
+ self.state.update_activity_time()
223
+
224
+ self.logger.info(
225
+ f'✅ Agent reset complete. State: steps={self.state.n_steps}, '
226
+ f'actions={self.state.total_actions_count}, '
227
+ f'history_messages={len(self.message_manager.history.messages)}'
228
+ )
@@ -0,0 +1,293 @@
1
+ """Secret and binary file guard (polyrob context-reference safety).
2
+
3
+ Pure, dependency-free helpers used by context-reference ingestion and knowledge-base
4
+ loaders to decide whether a file is safe to read or ingest. Three concerns:
5
+
6
+ 1. ``is_secret_path`` — does this path look like a credential / secret file?
7
+ 2. ``is_binary_file`` — is this file binary (unreadable as text)?
8
+ 3. ``estimate_tokens_rough`` — cheap token-count estimate for budget checks.
9
+
10
+ No I/O side-effects on ``is_secret_path``; ``is_binary_file`` reads at most 4 096 bytes.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import fnmatch
15
+ import mimetypes
16
+ from pathlib import Path
17
+ from typing import Iterable
18
+
19
+ # ---------------------------------------------------------------------------
20
+ # Secret detection
21
+ # ---------------------------------------------------------------------------
22
+
23
+ # Name globs matched case-insensitively against the file's basename.
24
+ # Entries that contain a path separator are matched against the path's
25
+ # components (see _matches_path_glob below).
26
+ SECRET_NAME_GLOBS: tuple[str, ...] = (
27
+ ".env*",
28
+ "*.pem",
29
+ "*.key",
30
+ "id_rsa",
31
+ "id_ed25519",
32
+ "id_*",
33
+ "*credential*",
34
+ "*secret*",
35
+ "*.p12",
36
+ "*.pfx",
37
+ ".netrc",
38
+ ".npmrc",
39
+ ".pypirc",
40
+ ".pgpass",
41
+ "bot.db",
42
+ # polyrob-specific relative-path patterns
43
+ "config/.env.*",
44
+ ".polyrob/.env",
45
+ ".rob/.env", # legacy home (still holds live secrets via transition fallback)
46
+ )
47
+
48
+ # Tool-facing subset: unambiguous CREDENTIAL filenames only. Used by the
49
+ # file-editing tools (filesystem/coding) to refuse reading/writing secret files
50
+ # that are legitimately IN-ROOT under POLYROB_LOCAL (workspace == project cwd, so
51
+ # confinement can't catch them). Deliberately NARROWER than SECRET_NAME_GLOBS:
52
+ # excludes the broad ``*secret*``/``*credential*`` substrings (would block a
53
+ # Python ``secrets.py``) and the ``data/`` dir rule (a legit project data dir),
54
+ # so ordinary project files stay editable. See is_credential_file().
55
+ CREDENTIAL_NAME_GLOBS: tuple[str, ...] = (
56
+ ".env*",
57
+ "*.env", # WS-7: catch `polyrob.env`, `prod.env` etc — the prod env file's
58
+ # basename does NOT start with `.env`, so `.env*` alone missed it,
59
+ # leaving the file that holds AGENT_COMPUTE_POSTURE + approval flags
60
+ # writable by an agent surface (a self-posture-escalation path).
61
+ "*.pem",
62
+ "*.key",
63
+ "*.p12",
64
+ "*.pfx",
65
+ "id_rsa",
66
+ "id_ed25519",
67
+ "id_dsa",
68
+ "id_ecdsa",
69
+ ".netrc",
70
+ ".npmrc",
71
+ ".pypirc",
72
+ ".pgpass",
73
+ ".htpasswd",
74
+ # polyrob-specific relative-path patterns
75
+ "config/.env.*",
76
+ ".polyrob/.env",
77
+ ".rob/.env",
78
+ )
79
+
80
+ # WS-7: absolute system config directories whose contents are HARD-DENIED for any
81
+ # agent-writable surface (esp. the posture-2 `self_env` patch_source, which reaches
82
+ # the install tree). These hold the frozen security flags / secrets; the agent must
83
+ # never edit them. Source files elsewhere under the install tree stay patchable.
84
+ PROTECTED_CONFIG_DIRS: tuple[str, ...] = (
85
+ "/etc/polyrob",
86
+ "/etc/rob",
87
+ )
88
+
89
+
90
+ def is_protected_config_path(path: Path) -> bool:
91
+ """True if *path* is inside a protected system-config dir (WS-7).
92
+
93
+ Realpath-free NAME/prefix check on the given path (callers that need traversal
94
+ safety realpath first). Complements :func:`is_credential_file` (name-shaped
95
+ secrets anywhere) with a location rule for absolute system config the self_env
96
+ tool could otherwise reach.
97
+ """
98
+ try:
99
+ s = str(path)
100
+ except Exception:
101
+ return False
102
+ norm = s.replace("\\", "/")
103
+ for d in PROTECTED_CONFIG_DIRS:
104
+ if norm == d or norm.startswith(d + "/"):
105
+ return True
106
+ return False
107
+
108
+
109
+ def is_credential_file(path: Path) -> bool:
110
+ """Return *True* if *path*'s NAME looks like a credential/secret file.
111
+
112
+ Basename + multi-component path-glob checks against ``CREDENTIAL_NAME_GLOBS``
113
+ only — NO parent-dir (``data/``) rule and NO ``*secret*`` substring, so it is
114
+ safe to apply to arbitrary project files in local mode. This is the guard the
115
+ filesystem/coding tools use to refuse a secret file that lives inside the
116
+ workspace (the gap the confinement floor can't cover when workspace == cwd).
117
+ """
118
+ name = path.name
119
+ for glob in CREDENTIAL_NAME_GLOBS:
120
+ if "/" not in glob:
121
+ if _matches_name_glob(name, glob):
122
+ return True
123
+ elif _matches_path_glob(path, glob):
124
+ return True
125
+ return False
126
+
127
+
128
+ # Any *directory component* of the path that matches one of these names makes
129
+ # the whole path a secret path.
130
+ SECRET_DIR_PARTS: frozenset[str] = frozenset({
131
+ ".ssh",
132
+ ".aws",
133
+ ".gnupg",
134
+ ".kube",
135
+ ".docker",
136
+ ".azure",
137
+ ".config/gh",
138
+ "data",
139
+ })
140
+
141
+
142
+ def _path_parts_lower(path: Path) -> list[str]:
143
+ """Return the parts of *path* in lower-case (POSIX separators)."""
144
+ return [p.lower() for p in path.parts]
145
+
146
+
147
+ def _matches_name_glob(name: str, glob: str) -> bool:
148
+ """Case-insensitive fnmatch of *name* against *glob*."""
149
+ return fnmatch.fnmatch(name.lower(), glob.lower())
150
+
151
+
152
+ def _matches_path_glob(path: Path, glob: str) -> bool:
153
+ """Match a multi-component glob like ``config/.env.*`` or ``.rob/.env``.
154
+
155
+ Strategy: slide a window of ``len(glob_parts)`` consecutive path parts and
156
+ check if they join (with ``/``) case-insensitively to the glob.
157
+ """
158
+ glob_parts = glob.lower().replace("\\", "/").split("/")
159
+ n = len(glob_parts)
160
+ parts = [p.lower() for p in path.parts]
161
+ for i in range(len(parts) - n + 1):
162
+ window = "/".join(parts[i : i + n])
163
+ # Rebuild glob as a single fnmatch pattern with / separators.
164
+ if fnmatch.fnmatch(window, "/".join(glob_parts)):
165
+ return True
166
+ return False
167
+
168
+
169
+ def _is_data_db(path: Path) -> bool:
170
+ """``*.db`` rule: applies only when a parent directory is named ``data``."""
171
+ if not path.name.lower().endswith(".db"):
172
+ return False
173
+ # Check parent directories (not the file itself).
174
+ return any(p.lower() == "data" for p in path.parts[:-1])
175
+
176
+
177
+ def _dir_part_match(path: Path, dir_parts: Iterable[str]) -> bool:
178
+ """True if any directory *component* of *path* matches a ``dir_parts`` entry.
179
+
180
+ Multi-segment entries (e.g. ``.config/gh``) are matched as a consecutive
181
+ window of path parts joined with ``/``.
182
+ """
183
+ parts_lower = [p.lower() for p in path.parts[:-1]] # skip the filename
184
+ for dp in dir_parts:
185
+ dp_lower = dp.lower()
186
+ dp_segs = dp_lower.split("/")
187
+ n = len(dp_segs)
188
+ window_pat = "/".join(dp_segs)
189
+ for i in range(len(parts_lower) - n + 1):
190
+ window = "/".join(parts_lower[i : i + n])
191
+ if fnmatch.fnmatch(window, window_pat):
192
+ return True
193
+ return False
194
+
195
+
196
+ def is_secret_path(path: Path, *, root: Path) -> bool: # noqa: ARG001
197
+ """Return *True* if *path* looks like a credential / secret file.
198
+
199
+ Checks (in order):
200
+ 1. Any parent directory component matches ``SECRET_DIR_PARTS``.
201
+ 2. The basename matches a simple (no ``/``) ``SECRET_NAME_GLOBS`` entry.
202
+ 3. The path contains a multi-component pattern from ``SECRET_NAME_GLOBS``
203
+ (e.g. ``config/.env.production``, ``.rob/.env``).
204
+ 4. The ``*.db`` rule: applies only when a parent dir is named ``data``.
205
+
206
+ ``root`` is accepted for callers that want to pass it (future: relative-path
207
+ normalisation), but is not required for the current checks.
208
+ """
209
+ # 1. Parent-directory check
210
+ if _dir_part_match(path, SECRET_DIR_PARTS):
211
+ return True
212
+
213
+ name = path.name
214
+
215
+ # 2. Simple (no-slash) glob matches on the basename
216
+ simple_globs = [g for g in SECRET_NAME_GLOBS if "/" not in g]
217
+ for glob in simple_globs:
218
+ if _matches_name_glob(name, glob):
219
+ return True
220
+
221
+ # 3. Multi-component path patterns
222
+ path_globs = [g for g in SECRET_NAME_GLOBS if "/" in g]
223
+ for glob in path_globs:
224
+ if _matches_path_glob(path, glob):
225
+ return True
226
+
227
+ # 4. *.db inside a `data/` directory
228
+ if _is_data_db(path):
229
+ return True
230
+
231
+ return False
232
+
233
+
234
+ # ---------------------------------------------------------------------------
235
+ # Binary detection
236
+ # ---------------------------------------------------------------------------
237
+
238
+ # Extensions we always treat as plain text regardless of mime type.
239
+ _TEXT_EXTENSIONS: frozenset[str] = frozenset({
240
+ ".txt", ".json", ".yaml", ".yml", ".toml",
241
+ ".js", ".ts", ".tsx", ".sh",
242
+ ".csv", ".html", ".xml", ".rst",
243
+ ".ini", ".cfg", ".sql", ".md", ".py",
244
+ })
245
+
246
+ # Extensions for extractable document formats — callers decide how to read
247
+ # them; we report them as NOT binary so they are not silently skipped.
248
+ _EXTRACTABLE_EXTENSIONS: frozenset[str] = frozenset({
249
+ ".pdf", ".docx", ".doc", ".odt",
250
+ ".xlsx", ".xls", ".ods",
251
+ ".pptx", ".ppt",
252
+ ".epub", ".rtf",
253
+ })
254
+
255
+
256
+ def is_binary_file(path: Path) -> bool:
257
+ """Return *True* if *path* is a binary file that cannot be decoded as text.
258
+
259
+ Detection order:
260
+ 1. Text-extension allowlist → always False (text).
261
+ 2. Extractable-document list → False (callers handle extraction).
262
+ 3. mimetypes guess: ``text/*`` → False.
263
+ 4. Null-byte sniff of the first 4 096 bytes → True if a null byte is found.
264
+ 5. Default → False (assume text when uncertain).
265
+ """
266
+ suffix = path.suffix.lower()
267
+
268
+ if suffix in _TEXT_EXTENSIONS:
269
+ return False
270
+ if suffix in _EXTRACTABLE_EXTENSIONS:
271
+ return False
272
+
273
+ mime, _ = mimetypes.guess_type(str(path))
274
+ if mime and mime.startswith("text/"):
275
+ return False
276
+
277
+ # Null-byte sniff
278
+ try:
279
+ data = path.read_bytes()[:4096]
280
+ except OSError:
281
+ return False # can't read → let the caller handle it
282
+
283
+ return b"\x00" in data
284
+
285
+
286
+ # ---------------------------------------------------------------------------
287
+ # Token estimation
288
+ # ---------------------------------------------------------------------------
289
+
290
+
291
+ def estimate_tokens_rough(text: str) -> int:
292
+ """Rough token count: ``max(1, len(text) // 4)`` (chars-per-token ≈ 4)."""
293
+ return max(1, len(text) // 4)