polyrob 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (745) hide show
  1. agents/README.md +453 -0
  2. agents/__init__.py +164 -0
  3. agents/base_agent.py +743 -0
  4. agents/personality/character.py +119 -0
  5. agents/personality/character_manager.py +309 -0
  6. agents/personality/characters/rob.character.json +56 -0
  7. agents/personality/characters/trump.character.json +63 -0
  8. agents/personality/persona_render.py +78 -0
  9. agents/personality/persona_resolver.py +115 -0
  10. agents/prompt/__init__.py +58 -0
  11. agents/prompt/base_prompt.py +92 -0
  12. agents/prompt/system.py +417 -0
  13. agents/task/README_PATH_MANAGEMENT.md +141 -0
  14. agents/task/__init__.py +162 -0
  15. agents/task/agent/__init__.py +70 -0
  16. agents/task/agent/agent_state.py +408 -0
  17. agents/task/agent/async_delegation.py +274 -0
  18. agents/task/agent/autonomy_state.py +275 -0
  19. agents/task/agent/conversation.py +75 -0
  20. agents/task/agent/core/__init__.py +6 -0
  21. agents/task/agent/core/aux_metering.py +61 -0
  22. agents/task/agent/core/background_review.py +157 -0
  23. agents/task/agent/core/construction.py +1179 -0
  24. agents/task/agent/core/conversational_exit.py +49 -0
  25. agents/task/agent/core/correspondent_gate.py +227 -0
  26. agents/task/agent/core/curator.py +246 -0
  27. agents/task/agent/core/episodic_digest.py +103 -0
  28. agents/task/agent/core/error_recovery.py +480 -0
  29. agents/task/agent/core/history_io.py +257 -0
  30. agents/task/agent/core/llm_provisioning.py +287 -0
  31. agents/task/agent/core/llm_runner.py +547 -0
  32. agents/task/agent/core/logging_io.py +276 -0
  33. agents/task/agent/core/loop_detection.py +262 -0
  34. agents/task/agent/core/memory_prefetch.py +325 -0
  35. agents/task/agent/core/memory_writer.py +667 -0
  36. agents/task/agent/core/model_introspection.py +82 -0
  37. agents/task/agent/core/model_swap.py +177 -0
  38. agents/task/agent/core/next_action_internal.py +1331 -0
  39. agents/task/agent/core/output_validation.py +303 -0
  40. agents/task/agent/core/project_context.py +272 -0
  41. agents/task/agent/core/resources.py +93 -0
  42. agents/task/agent/core/result_processing.py +389 -0
  43. agents/task/agent/core/run_loop.py +661 -0
  44. agents/task/agent/core/safety_lifecycle.py +228 -0
  45. agents/task/agent/core/secret_guard.py +293 -0
  46. agents/task/agent/core/self_wake.py +280 -0
  47. agents/task/agent/core/session_metadata.py +104 -0
  48. agents/task/agent/core/step.py +781 -0
  49. agents/task/agent/core/step_execution.py +398 -0
  50. agents/task/agent/core/step_telemetry.py +222 -0
  51. agents/task/agent/core/test_aux_metering.py +50 -0
  52. agents/task/agent/core/test_billing_failover.py +68 -0
  53. agents/task/agent/core/test_fatal_error_classifier.py +38 -0
  54. agents/task/agent/core/test_judge_validation.py +275 -0
  55. agents/task/agent/core/test_model_swap.py +190 -0
  56. agents/task/agent/core/test_reflection_offload.py +54 -0
  57. agents/task/agent/core/turn_input.py +8 -0
  58. agents/task/agent/core/untrusted_wrap.py +103 -0
  59. agents/task/agent/core/user_ingress.py +228 -0
  60. agents/task/agent/hitl_manager.py +396 -0
  61. agents/task/agent/log_sanitize.py +36 -0
  62. agents/task/agent/message_manager/config.py +97 -0
  63. agents/task/agent/message_manager/service.py +758 -0
  64. agents/task/agent/message_manager/tests.py +305 -0
  65. agents/task/agent/message_manager/tool_call_builder.py +539 -0
  66. agents/task/agent/message_manager/tool_message_repair.py +364 -0
  67. agents/task/agent/message_manager/views.py +143 -0
  68. agents/task/agent/messages/__init__.py +8 -0
  69. agents/task/agent/messages/builders.py +309 -0
  70. agents/task/agent/messages/compactor.py +611 -0
  71. agents/task/agent/messages/context_references.py +271 -0
  72. agents/task/agent/messages/filters.py +333 -0
  73. agents/task/agent/messages/guidance.py +328 -0
  74. agents/task/agent/messages/persistence.py +448 -0
  75. agents/task/agent/messages/retrieval.py +506 -0
  76. agents/task/agent/messages/sqlite_persistence.py +85 -0
  77. agents/task/agent/messages/test_compaction_upgrade.py +349 -0
  78. agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
  79. agents/task/agent/messages/test_sqlite_persistence.py +54 -0
  80. agents/task/agent/messages/token_counter.py +532 -0
  81. agents/task/agent/orchestrator.py +776 -0
  82. agents/task/agent/profile_manager.py +105 -0
  83. agents/task/agent/profile_registry.py +173 -0
  84. agents/task/agent/prompts.py +1090 -0
  85. agents/task/agent/scenario_registry.py +226 -0
  86. agents/task/agent/service.py +509 -0
  87. agents/task/agent/session.py +984 -0
  88. agents/task/agent/skill_discovery.py +110 -0
  89. agents/task/agent/skill_frontmatter.py +94 -0
  90. agents/task/agent/skill_manager.py +1201 -0
  91. agents/task/agent/skill_store.py +627 -0
  92. agents/task/agent/skill_validation.py +56 -0
  93. agents/task/agent/skill_writer.py +492 -0
  94. agents/task/agent/sub_agent_manager.py +1137 -0
  95. agents/task/agent/test_session_disk_recovery.py +34 -0
  96. agents/task/agent/tests.py +513 -0
  97. agents/task/agent/tool_call_tracker.py +511 -0
  98. agents/task/agent/views.py +469 -0
  99. agents/task/config.py +432 -0
  100. agents/task/constants.py +1384 -0
  101. agents/task/flag_defaults.py +63 -0
  102. agents/task/goals/__init__.py +1 -0
  103. agents/task/goals/autonomy_marker.py +28 -0
  104. agents/task/goals/board.py +690 -0
  105. agents/task/goals/completion_judge.py +247 -0
  106. agents/task/goals/context.py +62 -0
  107. agents/task/goals/dispatcher.py +848 -0
  108. agents/task/goals/escalation.py +102 -0
  109. agents/task/goals/planner.py +114 -0
  110. agents/task/logging_config.py +155 -0
  111. agents/task/path.py +991 -0
  112. agents/task/robust_parse_config.py +400 -0
  113. agents/task/runtime/__init__.py +0 -0
  114. agents/task/runtime/run_as_session.py +130 -0
  115. agents/task/runtime_safety.py +308 -0
  116. agents/task/session/__init__.py +5 -0
  117. agents/task/session/browser_pool.py +130 -0
  118. agents/task/session/cleanup.py +612 -0
  119. agents/task/session/execution.py +397 -0
  120. agents/task/session/feed.py +200 -0
  121. agents/task/session/hitl_ingress.py +201 -0
  122. agents/task/session/hooks.py +83 -0
  123. agents/task/session/multi_agent.py +243 -0
  124. agents/task/session/workspace.py +91 -0
  125. agents/task/session_registry.py +91 -0
  126. agents/task/session_route.py +35 -0
  127. agents/task/sqlite_session_registry.py +261 -0
  128. agents/task/surface_config.py +185 -0
  129. agents/task/telemetry/__init__.py +58 -0
  130. agents/task/telemetry/event_log.py +180 -0
  131. agents/task/telemetry/formatters.py +642 -0
  132. agents/task/telemetry/manager.py +571 -0
  133. agents/task/telemetry/memory_events.py +66 -0
  134. agents/task/telemetry/self_events.py +53 -0
  135. agents/task/telemetry/sequence.py +160 -0
  136. agents/task/telemetry/service.py +1265 -0
  137. agents/task/telemetry/views.py +882 -0
  138. agents/task/templates.py +98 -0
  139. agents/task/test_aux_chain.py +151 -0
  140. agents/task/test_aux_router.py +41 -0
  141. agents/task/tests/test_parse_robustness.py +170 -0
  142. agents/task/tool_defaults.py +110 -0
  143. agents/task/utils.py +762 -0
  144. agents/task/utils_json.py +768 -0
  145. agents/task/utils_webview.py +86 -0
  146. agents/task/workspace_context.py +229 -0
  147. agents/task_agent_lite.py +2398 -0
  148. api/README.md +683 -0
  149. api/TASK_API_DOCS.md +151 -0
  150. api/__init__.py +0 -0
  151. api/a2a/__init__.py +37 -0
  152. api/a2a/agent_card.py +337 -0
  153. api/a2a/client.py +657 -0
  154. api/a2a/endpoints.py +375 -0
  155. api/a2a/models.py +268 -0
  156. api/a2a/streaming.py +419 -0
  157. api/a2a/task_handler.py +929 -0
  158. api/admin_endpoints.py +1584 -0
  159. api/app.py +923 -0
  160. api/auth_constants.py +28 -0
  161. api/auth_endpoints.py +402 -0
  162. api/auth_state.py +36 -0
  163. api/chat_via_task.py +42 -0
  164. api/conversation_manager.py +231 -0
  165. api/dependencies.py +186 -0
  166. api/eip8004_endpoints.py +452 -0
  167. api/hyperliquid_models.py +174 -0
  168. api/hyperliquid_routes.py +450 -0
  169. api/interfaces.py +207 -0
  170. api/jwt_middleware.py +83 -0
  171. api/kb/__init__.py +1 -0
  172. api/kb/endpoints.py +314 -0
  173. api/mcp_models.py +279 -0
  174. api/mcp_routes.py +438 -0
  175. api/middleware.py +526 -0
  176. api/models.py +176 -0
  177. api/openai_compat/__init__.py +0 -0
  178. api/openai_compat/model_map.py +25 -0
  179. api/openai_compat/models.py +59 -0
  180. api/openai_compat/router.py +140 -0
  181. api/payment_endpoints.py +444 -0
  182. api/payment_verification.py +138 -0
  183. api/polymarket_models.py +155 -0
  184. api/polymarket_routes.py +501 -0
  185. api/pricing_endpoints.py +89 -0
  186. api/session_routing.py +54 -0
  187. api/skill_endpoints.py +453 -0
  188. api/task_http_api.py +1856 -0
  189. api/webhooks.py +52 -0
  190. api/x402_endpoints.py +280 -0
  191. avatar/README.md +44 -0
  192. avatar/__init__.py +1 -0
  193. avatar/config/rob.json +7 -0
  194. avatar/mindprint.js +395 -0
  195. avatar/renders/rob.meta.json +20 -0
  196. avatar/renders/rob.png +0 -0
  197. avatar/studio.html +373 -0
  198. avatar/webview/avatar-live.js +44 -0
  199. cli/README.md +89 -0
  200. cli/__init__.py +0 -0
  201. cli/cli_surface.py +67 -0
  202. cli/commands/__init__.py +0 -0
  203. cli/commands/_bootstrap.py +49 -0
  204. cli/commands/_errors.py +53 -0
  205. cli/commands/chat.py +973 -0
  206. cli/commands/config.py +100 -0
  207. cli/commands/dashboard.py +103 -0
  208. cli/commands/doctor.py +222 -0
  209. cli/commands/email.py +174 -0
  210. cli/commands/gateway.py +346 -0
  211. cli/commands/goals.py +509 -0
  212. cli/commands/init.py +276 -0
  213. cli/commands/journey.py +30 -0
  214. cli/commands/kb.py +280 -0
  215. cli/commands/model.py +107 -0
  216. cli/commands/owner.py +431 -0
  217. cli/commands/pfp.py +232 -0
  218. cli/commands/run.py +414 -0
  219. cli/commands/serve.py +41 -0
  220. cli/commands/session.py +684 -0
  221. cli/commands/skill_install.py +786 -0
  222. cli/commands/skills.py +128 -0
  223. cli/commands/subagents.py +100 -0
  224. cli/commands/surface.py +81 -0
  225. cli/commands/telegram.py +185 -0
  226. cli/commands/todos.py +209 -0
  227. cli/commands/tools.py +102 -0
  228. cli/commands/update.py +316 -0
  229. cli/commands/whatsapp.py +175 -0
  230. cli/config_store.py +276 -0
  231. cli/gitignore.py +43 -0
  232. cli/inventory.py +46 -0
  233. cli/keys.py +101 -0
  234. cli/persona.py +17 -0
  235. cli/polyrob.py +137 -0
  236. cli/ui/__init__.py +64 -0
  237. cli/ui/activity.py +141 -0
  238. cli/ui/app.py +538 -0
  239. cli/ui/banner.py +233 -0
  240. cli/ui/blocks.py +534 -0
  241. cli/ui/bootstrap_notice.py +47 -0
  242. cli/ui/commands/__init__.py +41 -0
  243. cli/ui/commands/h_cron.py +64 -0
  244. cli/ui/commands/h_journey.py +164 -0
  245. cli/ui/commands/h_kb.py +121 -0
  246. cli/ui/commands/h_learn.py +100 -0
  247. cli/ui/commands/h_mcp.py +215 -0
  248. cli/ui/commands/h_self.py +113 -0
  249. cli/ui/commands/h_skills.py +314 -0
  250. cli/ui/commands/handlers.py +1672 -0
  251. cli/ui/commands/registry.py +426 -0
  252. cli/ui/dialog.py +425 -0
  253. cli/ui/event_registry.py +110 -0
  254. cli/ui/event_specs.py +45 -0
  255. cli/ui/events.py +433 -0
  256. cli/ui/identity.py +15 -0
  257. cli/ui/lifecycle.py +159 -0
  258. cli/ui/live_hooks.py +43 -0
  259. cli/ui/model_selector.py +532 -0
  260. cli/ui/persistent_loop.py +181 -0
  261. cli/ui/pick.py +113 -0
  262. cli/ui/plain_renderer.py +351 -0
  263. cli/ui/renderer.py +384 -0
  264. cli/ui/rich_renderer.py +533 -0
  265. cli/ui/secrets.py +115 -0
  266. cli/ui/state.py +376 -0
  267. cli/ui/statusbar.py +231 -0
  268. cli/ui/streaming.py +254 -0
  269. cli/ui/terminal_render.py +105 -0
  270. cli/ui/theme.py +132 -0
  271. cli/update/__init__.py +6 -0
  272. cli/update/context.py +121 -0
  273. cli/update/detect.py +176 -0
  274. cli/update/engine.py +95 -0
  275. cli/update/migrate_guarded.py +55 -0
  276. cli/update/process_guard.py +215 -0
  277. cli/update/runners.py +78 -0
  278. cli/update/snapshot.py +270 -0
  279. cli/update/versions.py +205 -0
  280. core/README.md +377 -0
  281. core/__init__.py +174 -0
  282. core/assets.py +87 -0
  283. core/async_bridge.py +76 -0
  284. core/autonomy_runtime.py +259 -0
  285. core/base_component.py +302 -0
  286. core/bootstrap.py +681 -0
  287. core/bot.py +310 -0
  288. core/config.py +833 -0
  289. core/constants.py +126 -0
  290. core/container.py +462 -0
  291. core/db_manifest.py +101 -0
  292. core/embedding.py +57 -0
  293. core/env.py +39 -0
  294. core/exceptions.py +287 -0
  295. core/flags.py +144 -0
  296. core/flags_catalog.py +345 -0
  297. core/home_migration.py +62 -0
  298. core/identity.py +118 -0
  299. core/initialization.py +1004 -0
  300. core/instance.py +537 -0
  301. core/interactive_gate.py +96 -0
  302. core/logging.py +436 -0
  303. core/owner_doc_writer.py +176 -0
  304. core/pairing.py +193 -0
  305. core/path_safety.py +20 -0
  306. core/paths.py +27 -0
  307. core/payment_config.py +21 -0
  308. core/permissions.py +322 -0
  309. core/runtime_config.py +96 -0
  310. core/runtime_paths.py +135 -0
  311. core/seams.py +35 -0
  312. core/secret_scan.py +25 -0
  313. core/secret_scrub.py +71 -0
  314. core/secrets.py +35 -0
  315. core/security_logging_filter.py +148 -0
  316. core/self_context_writer.py +333 -0
  317. core/self_evolution.py +294 -0
  318. core/session_context.py +47 -0
  319. core/sqlite_util.py +59 -0
  320. core/surfaces/__init__.py +16 -0
  321. core/surfaces/access.py +118 -0
  322. core/surfaces/binding.py +88 -0
  323. core/surfaces/bootstrap.py +78 -0
  324. core/surfaces/circuit.py +168 -0
  325. core/surfaces/continuity.py +77 -0
  326. core/surfaces/correspondents.py +228 -0
  327. core/surfaces/dispatcher.py +216 -0
  328. core/surfaces/envelopes.py +75 -0
  329. core/surfaces/gc.py +30 -0
  330. core/surfaces/idempotency.py +57 -0
  331. core/surfaces/inbound_webhook.py +124 -0
  332. core/surfaces/media.py +32 -0
  333. core/surfaces/message_router.py +91 -0
  334. core/surfaces/outbound_allowlist.py +80 -0
  335. core/surfaces/outbound_dispatcher.py +110 -0
  336. core/surfaces/outbound_mirror.py +37 -0
  337. core/surfaces/outbound_queue.py +107 -0
  338. core/surfaces/outbound_target.py +12 -0
  339. core/surfaces/owner_admin.py +29 -0
  340. core/surfaces/proactive.py +78 -0
  341. core/surfaces/progress.py +108 -0
  342. core/surfaces/rate_bucket.py +21 -0
  343. core/surfaces/registry.py +51 -0
  344. core/surfaces/rendering.py +53 -0
  345. core/surfaces/send_policy.py +14 -0
  346. core/surfaces/serialize.py +24 -0
  347. core/surfaces/session_chat_registry.py +112 -0
  348. core/surfaces/session_policy.py +60 -0
  349. core/surfaces/surface.py +288 -0
  350. core/surfaces/transcription.py +88 -0
  351. core/surfaces/voice_echo.py +26 -0
  352. core/surfaces/voice_guard.py +23 -0
  353. core/tickers.py +174 -0
  354. core/tool_catalog.py +192 -0
  355. core/version.py +75 -0
  356. core/wallet/__init__.py +5 -0
  357. core/wallet/agent_wallet.py +59 -0
  358. core/wallet/audit_sink.py +61 -0
  359. core/wallet/config.py +70 -0
  360. core/wallet/factory.py +87 -0
  361. core/wallet/policy.py +104 -0
  362. core/wallet/signer.py +51 -0
  363. cron/README.md +65 -0
  364. cron/__init__.py +0 -0
  365. cron/delivery.py +277 -0
  366. cron/digest.py +124 -0
  367. cron/jobs.py +207 -0
  368. cron/runner.py +297 -0
  369. cron/schedule.py +194 -0
  370. cron/scheduler.py +201 -0
  371. cron/service.py +57 -0
  372. cron/wake_gate.py +223 -0
  373. data/__init__.py +1 -0
  374. data/prompts/autov2_prompts.json +87 -0
  375. data/prompts/skills/browser-automation/SKILL.md +43 -0
  376. data/prompts/skills/coding-workflow/SKILL.md +36 -0
  377. data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
  378. data/prompts/skills/document-writing/SKILL.md +33 -0
  379. data/prompts/skills/email-comms/SKILL.md +33 -0
  380. data/prompts/skills/file-data-ops/SKILL.md +35 -0
  381. data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
  382. data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
  383. data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
  384. data/prompts/skills/lead-research/SKILL.md +86 -0
  385. data/prompts/skills/market-research-brief/SKILL.md +104 -0
  386. data/prompts/skills/person-analyzer/SKILL.md +124 -0
  387. data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
  388. data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
  389. data/prompts/skills/polymarket-trading/SKILL.md +55 -0
  390. data/prompts/skills/presentation-creator/SKILL.md +36 -0
  391. data/prompts/skills/project-analyzer/SKILL.md +169 -0
  392. data/prompts/skills/rules.json +268 -0
  393. data/prompts/skills/secret-handling/SKILL.md +20 -0
  394. data/prompts/skills/skill-authoring/SKILL.md +31 -0
  395. data/prompts/skills/skill-security-review/SKILL.md +27 -0
  396. data/prompts/skills/social-discovery/SKILL.md +57 -0
  397. data/prompts/skills/task-planning/SKILL.md +33 -0
  398. data/prompts/skills/web-research/SKILL.md +39 -0
  399. data/prompts/skills/web-scraping/SKILL.md +73 -0
  400. data/prompts/skills/x-engagement/SKILL.md +69 -0
  401. data/prompts/system_prompts.json +4 -0
  402. data/subscription_channels.json +5 -0
  403. modules/README.md +503 -0
  404. modules/__init__.py +124 -0
  405. modules/auth/__init__.py +13 -0
  406. modules/auth/api_key_manager.py +157 -0
  407. modules/auth/identity_mapper.py +380 -0
  408. modules/auth/siwe_auth.py +273 -0
  409. modules/auth/tier_manager.py +173 -0
  410. modules/base_module.py +190 -0
  411. modules/credits/__init__.py +29 -0
  412. modules/credits/balance_manager.py +237 -0
  413. modules/credits/cost_utils.py +129 -0
  414. modules/credits/pricing.py +139 -0
  415. modules/credits/unified_ledger.py +148 -0
  416. modules/credits/usage_meter.py +310 -0
  417. modules/credits/usage_tracker.py +674 -0
  418. modules/database/__init__.py +33 -0
  419. modules/database/audit_log.py +322 -0
  420. modules/database/auth_tables.py +276 -0
  421. modules/database/connection.py +644 -0
  422. modules/database/connection_pool.py +305 -0
  423. modules/database/conversation_contexts.py +446 -0
  424. modules/database/database_manager.py +237 -0
  425. modules/database/hyperliquid.py +375 -0
  426. modules/database/polymarket.py +574 -0
  427. modules/database/user_mcp_servers.py +728 -0
  428. modules/database/user_profiles.py +562 -0
  429. modules/database/utils.py +107 -0
  430. modules/database/x402_tables.py +92 -0
  431. modules/eip8004/README.md +1069 -0
  432. modules/eip8004/__init__.py +65 -0
  433. modules/eip8004/contracts.py +498 -0
  434. modules/eip8004/models.py +271 -0
  435. modules/eip8004/registration.py +148 -0
  436. modules/eip8004/reputation.py +367 -0
  437. modules/eip8004/validation.py +277 -0
  438. modules/llm/__init__.py +97 -0
  439. modules/llm/adapters.py +963 -0
  440. modules/llm/anthropic_client.py +831 -0
  441. modules/llm/available_models.py +106 -0
  442. modules/llm/brain_scrubber.py +137 -0
  443. modules/llm/cache_hints.py +162 -0
  444. modules/llm/deepseek_client.py +562 -0
  445. modules/llm/gemini_client.py +1367 -0
  446. modules/llm/llm_client.py +1139 -0
  447. modules/llm/llm_client_registry.py +188 -0
  448. modules/llm/llm_factory.py +237 -0
  449. modules/llm/llm_manager.py +1036 -0
  450. modules/llm/messages.py +374 -0
  451. modules/llm/model_registry.py +1935 -0
  452. modules/llm/nvidia_client.py +125 -0
  453. modules/llm/openai_client.py +800 -0
  454. modules/llm/openrouter_client.py +969 -0
  455. modules/llm/profiles.py +178 -0
  456. modules/llm/test_anthropic_cache_capture.py +35 -0
  457. modules/llm/test_cached_pricing_derivation.py +24 -0
  458. modules/llm/think_scrubber.py +444 -0
  459. modules/llm/token_counter.py +268 -0
  460. modules/memory/__init__.py +35 -0
  461. modules/memory/backend_factory.py +83 -0
  462. modules/memory/cache_manager.py +186 -0
  463. modules/memory/episodic.py +108 -0
  464. modules/memory/local_vector_memory_provider.py +552 -0
  465. modules/memory/memory_manager.py +225 -0
  466. modules/memory/models.py +312 -0
  467. modules/memory/provider.py +210 -0
  468. modules/memory/registry.py +205 -0
  469. modules/memory/sqlite_memory_provider.py +767 -0
  470. modules/memory/task/__init__.py +52 -0
  471. modules/memory/task/compaction_manager.py +157 -0
  472. modules/memory/task/context_retriever.py +809 -0
  473. modules/memory/task/hierarchical_memory.py +1002 -0
  474. modules/memory/task/lexical_retriever.py +71 -0
  475. modules/memory/task/null_context_manager.py +213 -0
  476. modules/memory/task/phase_manager.py +625 -0
  477. modules/memory/task/reflection_service.py +157 -0
  478. modules/memory/task/semantic_retriever.py +218 -0
  479. modules/memory/task/task_context_manager.py +1165 -0
  480. modules/memory/task/test_forgetting_engages.py +24 -0
  481. modules/memory/task/test_reflection_llm.py +39 -0
  482. modules/memory/task/test_threat_scan.py +53 -0
  483. modules/memory/task/threat_scan.py +90 -0
  484. modules/memory/test_sqlite_memory_provider.py +106 -0
  485. modules/memory/user_profile_manager.py +527 -0
  486. modules/payments/__init__.py +11 -0
  487. modules/payments/deposit_monitor.py +478 -0
  488. modules/payments/price_oracle.py +70 -0
  489. modules/payments/treasury_sweeper.py +401 -0
  490. modules/payments/wallet_generator.py +145 -0
  491. modules/pfp/__init__.py +1 -0
  492. modules/pfp/config.py +92 -0
  493. modules/pfp/mesh.py +565 -0
  494. modules/pfp/push.py +69 -0
  495. modules/pfp/renderer.py +85 -0
  496. modules/pfp/store.py +82 -0
  497. modules/skills/__init__.py +1 -0
  498. modules/skills/skill_usage.py +248 -0
  499. modules/transcription/__init__.py +24 -0
  500. modules/transcription/base.py +20 -0
  501. modules/transcription/faster_whisper_transcriber.py +55 -0
  502. modules/x402/README.md +249 -0
  503. modules/x402/__init__.py +22 -0
  504. modules/x402/invoicing.py +416 -0
  505. modules/x402/middleware.py +364 -0
  506. modules/x402/settlement_watcher.py +145 -0
  507. modules/x402/x402_integration.py +344 -0
  508. polyrob-0.5.0.dist-info/METADATA +482 -0
  509. polyrob-0.5.0.dist-info/RECORD +745 -0
  510. polyrob-0.5.0.dist-info/WHEEL +5 -0
  511. polyrob-0.5.0.dist-info/entry_points.txt +2 -0
  512. polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
  513. polyrob-0.5.0.dist-info/top_level.txt +12 -0
  514. surfaces/README.md +70 -0
  515. surfaces/__init__.py +0 -0
  516. surfaces/email/__init__.py +0 -0
  517. surfaces/email/dedup.py +44 -0
  518. surfaces/email/harness.py +187 -0
  519. surfaces/email/inbound.py +159 -0
  520. surfaces/email/seed.py +60 -0
  521. surfaces/email/surface.py +105 -0
  522. surfaces/telegram/__init__.py +0 -0
  523. surfaces/telegram/dedup.py +83 -0
  524. surfaces/telegram/harness.py +886 -0
  525. surfaces/telegram/inbound.py +145 -0
  526. surfaces/telegram/interactive_tools.py +49 -0
  527. surfaces/telegram/markdown.py +347 -0
  528. surfaces/telegram/rate_limit.py +167 -0
  529. surfaces/telegram/surface.py +187 -0
  530. surfaces/telegram/voice.py +52 -0
  531. surfaces/whatsapp/__init__.py +0 -0
  532. surfaces/whatsapp/client.py +51 -0
  533. surfaces/whatsapp/harness.py +62 -0
  534. surfaces/whatsapp/inbound.py +132 -0
  535. surfaces/whatsapp/surface.py +64 -0
  536. surfaces/whatsapp/window.py +34 -0
  537. tools/README.md +578 -0
  538. tools/__init__.py +424 -0
  539. tools/alchemy/__init__.py +5 -0
  540. tools/alchemy/alchemy_tool.py +389 -0
  541. tools/anysite/__init__.py +17 -0
  542. tools/anysite/client.py +88 -0
  543. tools/anysite/tool.py +89 -0
  544. tools/base_tool.py +468 -0
  545. tools/browser/__init__.py +22 -0
  546. tools/browser/actions.py +118 -0
  547. tools/browser/browser.py +1457 -0
  548. tools/browser/browser_manager.py +690 -0
  549. tools/browser/context.py +1748 -0
  550. tools/browser/playwright_utils.py +137 -0
  551. tools/browser/views.py +56 -0
  552. tools/code_exec/SANDBOX_SECURITY.md +144 -0
  553. tools/code_exec/__init__.py +178 -0
  554. tools/code_exec/backend.py +73 -0
  555. tools/code_exec/backends/__init__.py +1 -0
  556. tools/code_exec/backends/docker.py +779 -0
  557. tools/code_exec/backends/local_subprocess.py +154 -0
  558. tools/code_exec/env_policy.py +45 -0
  559. tools/code_exec/result.py +42 -0
  560. tools/code_exec/sandbox_guard.py +59 -0
  561. tools/code_exec/tool.py +227 -0
  562. tools/coding/__init__.py +50 -0
  563. tools/coding/edit.py +107 -0
  564. tools/coding/search.py +113 -0
  565. tools/coding/tool.py +353 -0
  566. tools/collabland/__init__.py +5 -0
  567. tools/collabland/collabland_tool.py +471 -0
  568. tools/controller/__init__.py +0 -0
  569. tools/controller/_helpers.py +165 -0
  570. tools/controller/action_registration.py +1765 -0
  571. tools/controller/approval.py +244 -0
  572. tools/controller/approval_interactive.py +90 -0
  573. tools/controller/delegation.py +195 -0
  574. tools/controller/execution.py +797 -0
  575. tools/controller/execution_context.py +141 -0
  576. tools/controller/hooks.py +135 -0
  577. tools/controller/introspection.py +344 -0
  578. tools/controller/mcp_registrar.py +190 -0
  579. tools/controller/message_send.py +31 -0
  580. tools/controller/registry/__init__.py +4 -0
  581. tools/controller/registry/schema_generators.py +395 -0
  582. tools/controller/registry/schema_sanitizer.py +406 -0
  583. tools/controller/registry/service.py +1266 -0
  584. tools/controller/registry/views.py +151 -0
  585. tools/controller/service.py +409 -0
  586. tools/controller/tool_management.py +334 -0
  587. tools/controller/types.py +28 -0
  588. tools/controller/views.py +450 -0
  589. tools/cronjob_tools.py +166 -0
  590. tools/crypto_trade_gate.py +59 -0
  591. tools/descriptors.py +452 -0
  592. tools/dom/__init__.py +0 -0
  593. tools/dom/history_tree_processor/service.py +244 -0
  594. tools/dom/history_tree_processor/view.py +99 -0
  595. tools/dom/service.py +471 -0
  596. tools/dom/views.py +346 -0
  597. tools/email_tool.py +403 -0
  598. tools/exceptions.py +169 -0
  599. tools/filesystem.py +1038 -0
  600. tools/filesystem_docproc.py +331 -0
  601. tools/filesystem_pdf.py +755 -0
  602. tools/git/__init__.py +42 -0
  603. tools/git/tool.py +258 -0
  604. tools/github/__init__.py +33 -0
  605. tools/github/client.py +94 -0
  606. tools/github/tool.py +237 -0
  607. tools/goal_tools.py +309 -0
  608. tools/hyperliquid/__init__.py +56 -0
  609. tools/hyperliquid/models.py +233 -0
  610. tools/hyperliquid/service.py +1680 -0
  611. tools/knowledge_ingest.py +950 -0
  612. tools/mcp/README.md +535 -0
  613. tools/mcp/__init__.py +52 -0
  614. tools/mcp/catalog.py +115 -0
  615. tools/mcp/config.py +337 -0
  616. tools/mcp/mcp_tool.py +1415 -0
  617. tools/mcp/param_coercion.py +289 -0
  618. tools/mcp/protocol.py +1520 -0
  619. tools/mcp/rate_limit.py +50 -0
  620. tools/mcp/security.py +424 -0
  621. tools/mcp/self_install.py +111 -0
  622. tools/mcp/server_manager.py +829 -0
  623. tools/mcp/subscriptions.py +72 -0
  624. tools/mcp/user_mcp_service.py +899 -0
  625. tools/mcp/validation_tracker.py +89 -0
  626. tools/mcp/views.py +346 -0
  627. tools/oauth/__init__.py +17 -0
  628. tools/oauth/manager.py +75 -0
  629. tools/oauth/provider.py +69 -0
  630. tools/oauth/providers/__init__.py +1 -0
  631. tools/oauth/providers/generic_oauth2.py +96 -0
  632. tools/perplexity_tool.py +280 -0
  633. tools/polymarket/__init__.py +41 -0
  634. tools/polymarket/clob_adapter.py +76 -0
  635. tools/polymarket/models.py +182 -0
  636. tools/polymarket/service.py +2082 -0
  637. tools/self_env/__init__.py +61 -0
  638. tools/self_env/tool.py +274 -0
  639. tools/shell/__init__.py +80 -0
  640. tools/shell/backend_pool.py +118 -0
  641. tools/shell/discipline.py +62 -0
  642. tools/shell/executor.py +157 -0
  643. tools/shell/loopback_allow.py +87 -0
  644. tools/shell/process_registry.py +113 -0
  645. tools/shell/process_tool.py +131 -0
  646. tools/shell/state.py +129 -0
  647. tools/shell/tool.py +151 -0
  648. tools/task_tool.py +637 -0
  649. tools/twitter_tool.py +2086 -0
  650. tools/user_directory.py +179 -0
  651. tools/web_fetch/__init__.py +3 -0
  652. tools/web_fetch/fetcher.py +144 -0
  653. tools/web_fetch/render.py +44 -0
  654. tools/web_fetch/tool.py +60 -0
  655. tools/x402/__init__.py +60 -0
  656. tools/x402/client.py +47 -0
  657. tools/x402/invoice_tool.py +143 -0
  658. tools/x402/real_client.py +250 -0
  659. tools/x402/service.py +131 -0
  660. utils/README.md +498 -0
  661. utils/__init__.py +46 -0
  662. utils/auth_utils.py +114 -0
  663. utils/bounded_collections.py +162 -0
  664. utils/circuit_breaker.py +389 -0
  665. utils/gif_utils.py +435 -0
  666. utils/markdown_utils.py +291 -0
  667. utils/message_utils.py +147 -0
  668. utils/metrics.py +36 -0
  669. utils/path_validator.py +227 -0
  670. utils/rate_limit_manager.py +399 -0
  671. utils/result_size.py +296 -0
  672. utils/time_utils.py +206 -0
  673. utils/user_utils.py +188 -0
  674. webview/README.md +111 -0
  675. webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
  676. webview/__init__.py +8 -0
  677. webview/activity.py +636 -0
  678. webview/owner_auth.py +108 -0
  679. webview/pages.py +448 -0
  680. webview/repair_sessions.py +399 -0
  681. webview/server.py +4323 -0
  682. webview/server_launcher.py +112 -0
  683. webview/static/css/activity.css +89 -0
  684. webview/static/css/chat.css +1772 -0
  685. webview/static/css/components.css +1190 -0
  686. webview/static/css/config-panel.css +651 -0
  687. webview/static/css/pages/admin.css +766 -0
  688. webview/static/css/pages/profile.css +231 -0
  689. webview/static/css/pages/settings.css +297 -0
  690. webview/static/css/pages/signin.css +316 -0
  691. webview/static/css/style.css +4250 -0
  692. webview/static/css/variables.css +147 -0
  693. webview/static/css/workspace-fullwidth-fix.css +109 -0
  694. webview/static/img/favicon.ico +0 -0
  695. webview/static/js/activity.js +394 -0
  696. webview/static/js/admin/activity.js +186 -0
  697. webview/static/js/admin/dashboard.js +163 -0
  698. webview/static/js/admin/user_detail.js +400 -0
  699. webview/static/js/admin/users.js +174 -0
  700. webview/static/js/admin/utils.js +214 -0
  701. webview/static/js/chat.js +3178 -0
  702. webview/static/js/config-panel.js +522 -0
  703. webview/static/js/constants.js +20 -0
  704. webview/static/js/error-handler.js +226 -0
  705. webview/static/js/ethers.min.js +1 -0
  706. webview/static/js/event-filter.js +137 -0
  707. webview/static/js/event-store.js +351 -0
  708. webview/static/js/file-attachments.js +374 -0
  709. webview/static/js/file-loader.js +163 -0
  710. webview/static/js/index.js +103 -0
  711. webview/static/js/performance-utils.js +196 -0
  712. webview/static/js/profile.js +396 -0
  713. webview/static/js/screenshot.js +386 -0
  714. webview/static/js/session.js +1527 -0
  715. webview/static/js/settings.js +1288 -0
  716. webview/static/js/sidebar-data.js +766 -0
  717. webview/static/js/sidebar-toggle.js +296 -0
  718. webview/static/js/socket.io.min.js +7 -0
  719. webview/static/js/stats.js +355 -0
  720. webview/static/js/ui-utils.js +420 -0
  721. webview/static/js/workspace.js +966 -0
  722. webview/stats_service.py +613 -0
  723. webview/templates/__init__.py +1 -0
  724. webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  725. webview/templates/activity.html +36 -0
  726. webview/templates/admin/activity.html +122 -0
  727. webview/templates/admin/dashboard.html +112 -0
  728. webview/templates/admin/user_detail.html +228 -0
  729. webview/templates/admin/users.html +105 -0
  730. webview/templates/autonomy.html +60 -0
  731. webview/templates/error.html +133 -0
  732. webview/templates/finance.html +90 -0
  733. webview/templates/identity.html +73 -0
  734. webview/templates/index.html +42 -0
  735. webview/templates/layout.html +125 -0
  736. webview/templates/memory.html +59 -0
  737. webview/templates/owner_login.html +15 -0
  738. webview/templates/profile.html +121 -0
  739. webview/templates/session.html +312 -0
  740. webview/templates/settings.html +534 -0
  741. webview/templates/sidebar.html +112 -0
  742. webview/templates/signin.html +213 -0
  743. webview/templates/status.html +14 -0
  744. webview/templates/system.html +45 -0
  745. webview/webgate.py +196 -0
core/runtime_paths.py ADDED
@@ -0,0 +1,135 @@
1
+ """Location: core/runtime_paths.py
2
+
3
+ One resolver for all path roots — the single seam that separates installed
4
+ **code** (read-only), **config** (secrets), and the **runtime workspace** the
5
+ agent can write to.
6
+
7
+ The whole point (doc 01 "runtime isolation"): on the SERVER/headless path,
8
+ ``realpath(workspace_root)`` must NOT live under ``realpath(code_root)`` (the
9
+ install/code tree that also holds ``config/.env.production`` secrets), so even a
10
+ confinement miss in a file tool cannot reach the agent's own source or the master
11
+ secrets. The CLI *local* mode is the single documented exception — it keeps
12
+ Claude-Code-style CWD-as-workspace (consented, single-user).
13
+
14
+ This module is intentionally dependency-light and lives in ``core/`` (NOT on the
15
+ action-registration import path), so it carries no ``from __future__ import
16
+ annotations`` landmine. ``POLYROB_DATA_DIR`` is read via ``os.getenv`` only —
17
+ never ``BotConfig.get`` (which is a getattr that silently ignores the env).
18
+ """
19
+
20
+ import os
21
+ from dataclasses import dataclass
22
+ from pathlib import Path
23
+
24
+
25
+ # code_root == the install/code root: the parent of this ``core/`` package.
26
+ # Same value as ``core/config.py``'s ``base_dir`` (dirname(dirname(__file__))).
27
+ _CODE_ROOT = Path(__file__).resolve().parent.parent
28
+
29
+ # FHS default for a systemd service's mutable state. Used on the server path when
30
+ # no explicit POLYROB_DATA_DIR is given and the location is writable/creatable.
31
+ _SERVER_DATA_HOME = Path("/var/lib/polyrob")
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class RuntimePaths:
36
+ """The four resolved path roots. Frozen — resolve once, read everywhere."""
37
+
38
+ code_root: Path
39
+ config_dir: Path
40
+ data_home: Path
41
+ workspace_root: Path
42
+
43
+
44
+ def _server_default_data_home() -> Path:
45
+ """Server data-home when POLYROB_DATA_DIR is unset.
46
+
47
+ Prefer FHS ``/var/lib/polyrob`` when its parent is writable (or it already
48
+ exists), else fall back to ``~/.polyrob`` so a non-root / locked-down service
49
+ user still gets a writable home OUTSIDE the code tree.
50
+ """
51
+ try:
52
+ if _SERVER_DATA_HOME.exists():
53
+ return _SERVER_DATA_HOME
54
+ parent = _SERVER_DATA_HOME.parent
55
+ if parent.exists() and os.access(str(parent), os.W_OK):
56
+ return _SERVER_DATA_HOME
57
+ except Exception:
58
+ pass
59
+ return Path.home() / ".polyrob"
60
+
61
+
62
+ def resolve_runtime_paths(*, local: bool) -> RuntimePaths:
63
+ """Resolve (code_root, config_dir, data_home, workspace_root).
64
+
65
+ Precedence (LOCKED — doc 01 T1):
66
+ - code_root = the install/code root (parent of ``core/``).
67
+ - data_home = ``POLYROB_DATA_DIR`` if set; elif local → ``cwd/.polyrob``;
68
+ else server → ``/var/lib/polyrob`` (if writable) else
69
+ ``~/.polyrob``.
70
+ - config_dir = ``code_root/config`` (server) or ``data_home`` (local).
71
+ - workspace_root= ``cwd`` (local, project-root mode) or ``data_home/task``.
72
+ """
73
+ code_root = _CODE_ROOT
74
+
75
+ env_data_dir = os.getenv("POLYROB_DATA_DIR")
76
+ if env_data_dir:
77
+ data_home = Path(env_data_dir).resolve()
78
+ elif local:
79
+ # Local home is the project-scoped ``.polyrob`` dir (doc 02 rename).
80
+ data_home = (Path.cwd() / ".polyrob").resolve()
81
+ else:
82
+ data_home = _server_default_data_home().resolve()
83
+
84
+ if local:
85
+ config_dir = data_home
86
+ workspace_root = Path.cwd().resolve()
87
+ else:
88
+ config_dir = code_root / "config"
89
+ workspace_root = data_home / "task"
90
+
91
+ return RuntimePaths(
92
+ code_root=code_root,
93
+ config_dir=config_dir,
94
+ data_home=data_home,
95
+ workspace_root=workspace_root,
96
+ )
97
+
98
+
99
+ # The PathManager's own legacy default (agents/task/path.py reads DATA_ROOT,
100
+ # NOT POLYROB_DATA_DIR — the landmine at the heart of RC-1, 2026-07-07).
101
+ _LEGACY_SESSIONS_DEFAULT = "./data/task"
102
+
103
+
104
+ def resolve_session_data_root() -> Path:
105
+ """Resolve the session ARTIFACT tree root (PathManager ``data_root``).
106
+
107
+ RC-1 (2026-07-07 webview full-control handoff): the agent process resolves
108
+ its session tree via ``build_cli_container`` (``POLYROB_DATA_DIR`` →
109
+ ``{data_home}/sessions``) and installs it as the global ``pm()``; the
110
+ webview process never ran that bootstrap, so its ``pm()`` fell back to the
111
+ PathManager default (env ``DATA_ROOT`` → ``./data/task``) — a DIFFERENT
112
+ tree. Every non-agent process that reads session artifacts must resolve
113
+ the root through THIS function so the trees cannot diverge again.
114
+
115
+ Resolution order:
116
+ 1. Explicit ``DATA_ROOT`` — the PathManager's own env — always wins (an
117
+ operator who set it meant it; existing DATA_ROOT test rigs unchanged).
118
+ 2. ``POLYROB_DATA_DIR`` set (headless/server data home) →
119
+ ``{POLYROB_DATA_DIR}/sessions`` — exactly what ``build_cli_container``
120
+ produces for the agent process (parity pinned by test).
121
+ 3. Neither set → the legacy ``./data/task``, byte-identical to a bare
122
+ ``PathManager()``.
123
+
124
+ The local-dev CLI branches (``POLYROB_DATA_DIR`` unset → ``cwd/.polyrob/
125
+ sessions``) are deliberately NOT mirrored: this function runs in OTHER
126
+ processes whose cwd is not the CLI's, so guessing would be wrong more
127
+ often than the byte-identical legacy default.
128
+ """
129
+ explicit = os.getenv("DATA_ROOT")
130
+ if explicit and explicit.strip():
131
+ return Path(explicit).resolve()
132
+ data_home = os.getenv("POLYROB_DATA_DIR")
133
+ if data_home and data_home.strip():
134
+ return Path(data_home).resolve() / "sessions"
135
+ return Path(_LEGACY_SESSIONS_DEFAULT).resolve()
core/seams.py ADDED
@@ -0,0 +1,35 @@
1
+ """Core ⇄ platform seam contracts.
2
+
3
+ This module is the single home for the dependency-inversion seams between polyrob-core
4
+ (the agent framework) and polyrob-platform (auth/billing/metering). Core declares the
5
+ Protocols + permissive defaults; platform injects real implementations.
6
+
7
+ This commit seeds ONLY the usage carrier type. The five Protocols (UsageRecorder,
8
+ SessionAdmissionPolicy, PaymentVerifier, plus the auth/admission defaults) land in the
9
+ seam-inversion plan. Keep this module free of any platform import (modules.credits/
10
+ payments/x402, api.* platform modules).
11
+ """
12
+
13
+ from dataclasses import dataclass
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class LLMUsage:
18
+ """One LLM call's billable usage, carried across the UsageRecorder seam.
19
+
20
+ Field names mirror the kwargs the agent loop already passes to the platform usage
21
+ tracker (agents/task/agent/core/next_action_internal.py). The platform recorder maps
22
+ this onto modules.credits.usage_tracker.UsageRecord; core never imports that type.
23
+ """
24
+
25
+ user_id: str
26
+ session_id: str
27
+ agent_id: str
28
+ model: str
29
+ provider: str
30
+ input_tokens: int
31
+ output_tokens: int
32
+ cached_tokens: int
33
+ duration_seconds: float
34
+ component: str
35
+ purpose: str
core/secret_scan.py ADDED
@@ -0,0 +1,25 @@
1
+ """SEC-1 backstop: does a directory look like a secrets/code tree?
2
+
3
+ Used to WARN (or optionally refuse) when the agent's persistent workspace is the
4
+ cwd default AND that cwd contains secrets/VCS — the launch-in-a-repo footgun
5
+ (memory rob-glm-livetest-paramodel-bug-2026-06-20). This is a STEER, not a
6
+ read-denial guard; real per-read denial belongs at the filesystem/coding
7
+ chokepoint in a separate hardening PR (analysis §8.3).
8
+ """
9
+ import os
10
+ from typing import List
11
+
12
+ _EXACT = {".env", ".env.production", ".env.development", ".git",
13
+ "id_rsa", "credentials.json"}
14
+ _SUFFIX = (".pem",)
15
+
16
+
17
+ def looks_like_secrets_tree(path: str) -> List[str]:
18
+ found: List[str] = []
19
+ try:
20
+ for name in os.listdir(path):
21
+ if name in _EXACT or name.endswith(_SUFFIX):
22
+ found.append(name)
23
+ except OSError:
24
+ return []
25
+ return sorted(found)
core/secret_scrub.py ADDED
@@ -0,0 +1,71 @@
1
+ """core.secret_scrub — conservative secret-shape redaction for PERSISTED content.
2
+
3
+ POLYROB's message-history sensitive-data filter (``messages/filters.py``) was
4
+ allowlist-only: it redacted just the credentials explicitly registered in
5
+ ``sensitive_data``. Any UNregistered secret that lands in a tool result (a
6
+ ``cat .env``, an MCP response echoing an auth header, a printed private key) was
7
+ written verbatim into ``message_history.json`` and the compaction checkpoints on
8
+ disk, in the clear.
9
+
10
+ This module adds a pattern backstop. Unlike the CLI's display-only scrubber
11
+ (``cli/ui/secrets.py``), this one runs over content that is FED BACK TO THE MODEL
12
+ and kept as working state, so it deliberately OMITS the aggressive
13
+ hex/base64 catch-alls: redacting every 32-char hex or 40-char base64 run would
14
+ corrupt legitimate working data (a git SHA the agent is comparing, a base64 blob
15
+ it is processing). Only HIGH-CONFIDENCE credential shapes are redacted here.
16
+
17
+ Pure (no I/O, no state). Defense-in-depth, NOT a security boundary — the real fix
18
+ for secret EXPOSURE is workspace path-confinement at the tool layer.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import re
23
+ from typing import Optional
24
+
25
+ #: Replacement marker. Matches the ``<secret>…</secret>`` convention the history
26
+ #: filter already uses for registered sensitive values, so the model sees one shape.
27
+ REDACTED = "<secret>redacted</secret>"
28
+
29
+ #: PEM private-key blocks (multi-line) — redact the whole block.
30
+ _PEM_RE = re.compile(
31
+ r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----",
32
+ re.DOTALL,
33
+ )
34
+
35
+ #: ``Bearer <token>`` — run before the kv rule (kv value would stop at the space
36
+ #: and leave the token). 8+ token chars.
37
+ _BEARER_RE = re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._\-]{8,}")
38
+
39
+ #: ``key = value`` / ``key: value`` where the key NAME signals a credential.
40
+ #: Key is preserved (line stays readable); only the value (>=6 chars) is redacted.
41
+ _KV_RE = re.compile(
42
+ r"(?i)\b(api[_-]?key|apikey|secret|client_secret|password|passwd|"
43
+ r"access[_-]?token|auth[_-]?token|token|authorization|bearer)"
44
+ r"(\s*[=:]\s*)"
45
+ r"(['\"]?)([^\s'\"]{6,})\3"
46
+ )
47
+
48
+ #: Provider-style opaque keys: ``sk-``/``pk-``/``rk-`` (OpenAI/Anthropic/Stripe),
49
+ #: ``rob_`` (POLYROB API keys), and AWS ``AKIA…`` access-key ids.
50
+ _PROVIDER_KEY_RE = re.compile(r"\b(?:sk|pk|rk)-[A-Za-z0-9_-]{16,}")
51
+ _ROB_KEY_RE = re.compile(r"\brob_[A-Za-z0-9]{16,}")
52
+ _AWS_RE = re.compile(r"\bAKIA[0-9A-Z]{16}\b")
53
+
54
+
55
+ def scrub_secret_shapes(text: Optional[str]) -> str:
56
+ """Redact high-confidence credential shapes from *text*.
57
+
58
+ Returns "" for None. No-op for content with no secret-shaped substrings, so it
59
+ is safe to apply to every persisted message's string content and tool args.
60
+ Conservative by design — see the module docstring on why hex/base64 blobs are
61
+ intentionally left intact.
62
+ """
63
+ if not text:
64
+ return ""
65
+ out = _PEM_RE.sub(REDACTED, text)
66
+ out = _BEARER_RE.sub(REDACTED, out)
67
+ out = _KV_RE.sub(lambda m: f"{m.group(1)}{m.group(2)}{REDACTED}", out)
68
+ out = _PROVIDER_KEY_RE.sub(REDACTED, out)
69
+ out = _ROB_KEY_RE.sub(REDACTED, out)
70
+ out = _AWS_RE.sub(REDACTED, out)
71
+ return out
core/secrets.py ADDED
@@ -0,0 +1,35 @@
1
+ """The single ENV-VAR-NAME predicate for DISPLAY REDACTION.
2
+
3
+ Reused by cli config show, core.config_service, and the webview config router.
4
+
5
+ NOTE: this is distinct from core/bootstrap._is_secret_key, which is a *backfill
6
+ selector* (a narrow allowlist of names we import from config/.env.production) —
7
+ a different question; do not conflate them.
8
+
9
+ Value-shape scrubbing of transcript text is yet another concern and lives in
10
+ cli/ui/secrets.py.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ _SECRET_HINTS = (
15
+ "KEY", "TOKEN", "SECRET", "PASSWORD", "PASSPHRASE", "PASS", "JWT",
16
+ "CREDENTIAL", "PRIVATE", "SIGNATURE", "SEED", "MNEMONIC",
17
+ )
18
+
19
+
20
+ def is_secret_key(name: str) -> bool:
21
+ """Return True if name appears to be a secret (for display redaction).
22
+
23
+ Checks if any of the secret substring hints appear case-insensitively in the name.
24
+ Used to decide whether to redact a config value in display output.
25
+
26
+ Args:
27
+ name: The environment variable name to check.
28
+
29
+ Returns:
30
+ True if the name matches a secret hint, False otherwise.
31
+ """
32
+ if not name:
33
+ return False
34
+ upper = name.upper()
35
+ return any(hint in upper for hint in _SECRET_HINTS)
@@ -0,0 +1,148 @@
1
+ """
2
+ Security logging filter to prevent secrets from being logged.
3
+ """
4
+
5
+ import logging
6
+ import re
7
+ from typing import Set, Pattern
8
+
9
+ class SecretScrubbingFilter(logging.Filter):
10
+ """Filter that scrubs sensitive information from log records."""
11
+
12
+ # Common secret patterns to match
13
+ SECRET_PATTERNS = [
14
+ # API keys - various formats
15
+ re.compile(r'(?i)api[_-]?key["\']?\s*[:=]\s*["\']?([a-zA-Z0-9+/=]{20,})["\']?'),
16
+ re.compile(r'(?i)key["\']?\s*[:=]\s*["\']?([a-zA-Z0-9+/=]{20,})["\']?'),
17
+ re.compile(r'(?i)token["\']?\s*[:=]\s*["\']?([a-zA-Z0-9+/=_-]{20,})["\']?'),
18
+ re.compile(r'(?i)secret["\']?\s*[:=]\s*["\']?([a-zA-Z0-9+/=_-]{20,})["\']?'),
19
+
20
+ # Passwords
21
+ re.compile(r'(?i)password["\']?\s*[:=]\s*["\']?([^\s"\']{6,})["\']?'),
22
+ re.compile(r'(?i)passwd["\']?\s*[:=]\s*["\']?([^\s"\']{6,})["\']?'),
23
+
24
+ # Bearer tokens
25
+ re.compile(r'(?i)bearer\s+([a-zA-Z0-9+/=_-]{20,})'),
26
+
27
+ # Specific service patterns
28
+ re.compile(r'sk-[a-zA-Z0-9]{48}'), # OpenAI API keys
29
+ re.compile(r'claude-[a-zA-Z0-9_-]{20,}'), # Anthropic keys
30
+ re.compile(r'pc-[a-zA-Z0-9]{32}'), # Pinecone keys
31
+
32
+ # Generic base64-looking strings that might be keys
33
+ re.compile(r'([a-zA-Z0-9+/]{32,}={0,2})'),
34
+ ]
35
+
36
+ # Fields that should always be scrubbed if they contain potential secrets
37
+ SENSITIVE_FIELDS = {
38
+ 'api_key', 'apikey', 'api-key',
39
+ 'secret_key', 'secretkey', 'secret-key', 'secret',
40
+ 'password', 'passwd', 'pass',
41
+ 'token', 'access_token', 'refresh_token',
42
+ 'authorization', 'auth',
43
+ 'key', 'private_key', 'public_key',
44
+ 'client_secret', 'client_id'
45
+ }
46
+
47
+ def __init__(self, mask_length: int = 4, mask_char: str = '*'):
48
+ """
49
+ Initialize the filter.
50
+
51
+ Args:
52
+ mask_length: Number of characters to show at start/end
53
+ mask_char: Character to use for masking
54
+ """
55
+ super().__init__()
56
+ self.mask_length = mask_length
57
+ self.mask_char = mask_char
58
+
59
+ def mask_secret(self, secret: str) -> str:
60
+ """Mask a secret string, showing only first/last few characters."""
61
+ if len(secret) <= self.mask_length * 2:
62
+ return self.mask_char * len(secret)
63
+
64
+ start = secret[:self.mask_length]
65
+ end = secret[-self.mask_length:]
66
+ middle_length = len(secret) - (self.mask_length * 2)
67
+ return f"{start}{self.mask_char * min(middle_length, 10)}{end}"
68
+
69
+ def scrub_message(self, message: str) -> str:
70
+ """Scrub secrets from a message string."""
71
+ scrubbed = message
72
+
73
+ # Apply all secret patterns
74
+ for pattern in self.SECRET_PATTERNS:
75
+ def replace_secret(match):
76
+ full_match = match.group(0)
77
+ if len(match.groups()) > 0:
78
+ secret = match.group(1)
79
+ masked_secret = self.mask_secret(secret)
80
+ return full_match.replace(secret, masked_secret)
81
+ return self.mask_secret(full_match)
82
+
83
+ scrubbed = pattern.sub(replace_secret, scrubbed)
84
+
85
+ return scrubbed
86
+
87
+ def filter(self, record: logging.LogRecord) -> bool:
88
+ """
89
+ Filter method called on each log record.
90
+
91
+ Returns:
92
+ True to allow the record through, False to drop it
93
+ """
94
+ # Scrub the main message
95
+ if hasattr(record, 'msg') and record.msg:
96
+ if isinstance(record.msg, str):
97
+ record.msg = self.scrub_message(record.msg)
98
+
99
+ # Scrub formatted message
100
+ if hasattr(record, 'getMessage'):
101
+ try:
102
+ original_get_message = record.getMessage
103
+
104
+ def scrubbed_get_message():
105
+ msg = original_get_message()
106
+ return self.scrub_message(msg) if isinstance(msg, str) else msg
107
+
108
+ record.getMessage = scrubbed_get_message
109
+ except Exception:
110
+ pass
111
+
112
+ # Scrub any dictionary-like extra data
113
+ if hasattr(record, '__dict__'):
114
+ for key, value in record.__dict__.items():
115
+ if isinstance(value, str) and (
116
+ key.lower() in self.SENSITIVE_FIELDS or
117
+ any(field in key.lower() for field in self.SENSITIVE_FIELDS)
118
+ ):
119
+ record.__dict__[key] = self.mask_secret(value)
120
+
121
+ return True
122
+
123
+
124
+ def install_secret_scrubbing_filter(logger_name: str = None):
125
+ """
126
+ Install the secret scrubbing filter on a logger.
127
+
128
+ Args:
129
+ logger_name: Name of logger to install on, None for root logger
130
+ """
131
+ logger = logging.getLogger(logger_name)
132
+
133
+ # Check if filter is already installed
134
+ for filter_obj in logger.filters:
135
+ if isinstance(filter_obj, SecretScrubbingFilter):
136
+ return
137
+
138
+ filter_obj = SecretScrubbingFilter()
139
+ logger.addFilter(filter_obj)
140
+
141
+
142
+ def install_global_secret_scrubbing():
143
+ """Install secret scrubbing on the root logger to catch all logs."""
144
+ install_secret_scrubbing_filter()
145
+
146
+
147
+ # Auto-install on import
148
+ install_global_secret_scrubbing()