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,89 @@
1
+ """MCP validation-failure tracking — schema-injection policy.
2
+
3
+ Extracted from ToolCallTracker (PR11). Repeated MCP-tool validation failures
4
+ should escalate to injecting the tool's full schema to help the LLM. That is
5
+ MCP error *policy*, not tool-call ID lifecycle, so it lives here in tools/mcp/
6
+ rather than on the generic tracker. ToolCallTracker composes one of these and
7
+ delegates to it, preserving its public method names for existing callers.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from datetime import datetime, timedelta
14
+ from threading import RLock
15
+ from typing import Dict, Optional
16
+
17
+
18
+ class MCPValidationTracker:
19
+ """Per-(server, tool) validation-failure counter with TTL expiry.
20
+
21
+ After ``failure_threshold`` failures within ``failure_ttl_minutes``, callers
22
+ should inject the full tool schema. A successful call clears the counter.
23
+ Self-contained and thread-safe (its own lock).
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ logger: Optional[logging.Logger] = None,
29
+ failure_threshold: int = 2,
30
+ failure_ttl_minutes: int = 30,
31
+ ) -> None:
32
+ self.logger = logger or logging.getLogger(__name__)
33
+ self._failures: Dict[str, int] = {}
34
+ self._timestamps: Dict[str, datetime] = {}
35
+ self._threshold = failure_threshold
36
+ self._ttl_minutes = failure_ttl_minutes
37
+ self._lock = RLock()
38
+
39
+ @staticmethod
40
+ def _key(server_name: str, tool_name: str) -> str:
41
+ return f"{server_name}:{tool_name}"
42
+
43
+ def track_failure(self, server_name: str, tool_name: str) -> int:
44
+ """Record a validation failure; return the current count for this tool."""
45
+ with self._lock:
46
+ key = self._key(server_name, tool_name)
47
+ self._cleanup_expired()
48
+ self._failures[key] = self._failures.get(key, 0) + 1
49
+ self._timestamps[key] = datetime.now()
50
+ count = self._failures[key]
51
+ self.logger.warning(f"MCP validation failure #{count} for {key}")
52
+ return count
53
+
54
+ def should_inject_schema(self, server_name: str, tool_name: str) -> bool:
55
+ """True once failures for this tool reach the threshold.
56
+
57
+ P2-12: TTL-clean on the READ path too. Previously only track_failure cleaned,
58
+ so once a (server, tool) reached the threshold it stayed there for the whole
59
+ session — the consumer blocks the action, so clear_failures (success-only) never
60
+ runs and, absent another MCP failure to trigger track_failure, the 30-min TTL
61
+ never fired. Cleaning here lets an expired failure record lapse.
62
+ """
63
+ with self._lock:
64
+ self._cleanup_expired()
65
+ return self._failures.get(self._key(server_name, tool_name), 0) >= self._threshold
66
+
67
+ def clear_failures(self, server_name: str, tool_name: str) -> None:
68
+ """Clear the failure count after a successful execution."""
69
+ with self._lock:
70
+ key = self._key(server_name, tool_name)
71
+ if key in self._failures:
72
+ del self._failures[key]
73
+ self._timestamps.pop(key, None)
74
+ self.logger.debug(f"Cleared MCP validation failures for {key}")
75
+
76
+ def reset(self) -> None:
77
+ """Drop all tracked failures."""
78
+ with self._lock:
79
+ self._failures.clear()
80
+ self._timestamps.clear()
81
+
82
+ def _cleanup_expired(self) -> None:
83
+ """Remove failures older than the TTL. Caller holds the lock."""
84
+ cutoff = datetime.now() - timedelta(minutes=self._ttl_minutes)
85
+ expired = [k for k, ts in self._timestamps.items() if ts < cutoff]
86
+ for key in expired:
87
+ self._failures.pop(key, None)
88
+ self._timestamps.pop(key, None)
89
+ self.logger.debug(f"Expired MCP validation failures for {key}")
tools/mcp/views.py ADDED
@@ -0,0 +1,346 @@
1
+ """Pydantic models for MCP service actions."""
2
+
3
+ from typing import Dict, Any, Optional, List
4
+ from pydantic import BaseModel, Field, field_validator, ConfigDict
5
+
6
+
7
+ class MCPExecuteToolAction(BaseModel):
8
+ """Parameters for executing an MCP tool."""
9
+ model_config = ConfigDict(extra='forbid')
10
+
11
+ server_name: str = Field(..., description="Name of the MCP server (e.g., 'anysite', 'ghost')")
12
+ tool_name: str = Field(..., description="Name of the tool to execute on the server")
13
+ # FIX (Jan 2026): Make arguments REQUIRED so LLMs cannot omit it
14
+ # Previously optional with default={}, but LLMs were completely omitting the field,
15
+ # causing all MCP tools to receive empty parameters.
16
+ # Making it required forces LLMs to include arguments={...} even if empty.
17
+ arguments: Dict[str, Any] = Field(
18
+ ..., # Required - LLM MUST include this field
19
+ description=(
20
+ "REQUIRED: The parameters to pass to the target tool. "
21
+ "This dict is passed directly to the tool - PUT YOUR SEARCH PARAMETERS HERE! "
22
+ "Example: {'query': 'AI startups', 'count': 10} for search tools. "
23
+ "Example: {'keywords': 'CEO founder', 'count': 5} for LinkedIn search. "
24
+ "Use mcp_list_tools to see each tool's required parameters. "
25
+ "For tools that need no parameters, pass empty dict {}."
26
+ )
27
+ )
28
+
29
+ @field_validator('server_name')
30
+ @classmethod
31
+ def validate_server_name(cls, v):
32
+ """Validate server name is not empty."""
33
+ if not v or not v.strip():
34
+ raise ValueError("server_name cannot be empty")
35
+ return v.strip()
36
+
37
+ @field_validator('tool_name')
38
+ @classmethod
39
+ def validate_tool_name(cls, v):
40
+ """Validate tool name is not empty."""
41
+ if not v or not v.strip():
42
+ raise ValueError("tool_name cannot be empty")
43
+ return v.strip()
44
+
45
+ @field_validator('arguments')
46
+ @classmethod
47
+ def validate_arguments(cls, v):
48
+ """Warn about empty arguments - most tools require them."""
49
+ if v is None:
50
+ v = {}
51
+ if not v:
52
+ # Log warning but allow empty for tools that don't need params
53
+ import logging
54
+ logging.getLogger(__name__).warning(
55
+ f"⚠️ Empty arguments provided for MCP tool. "
56
+ f"Most MCP tools require parameters - check mcp_list_tools for schema."
57
+ )
58
+ return v
59
+
60
+
61
+ class MCPReadResourceAction(BaseModel):
62
+ """Parameters for reading an MCP resource."""
63
+ model_config = ConfigDict(extra='forbid')
64
+
65
+ server_name: str = Field(..., description="Name of the MCP server")
66
+ resource_uri: str = Field(..., description="URI of the resource to read")
67
+
68
+ @field_validator('server_name')
69
+ @classmethod
70
+ def validate_server_name(cls, v):
71
+ """Validate server name is not empty."""
72
+ if not v or not v.strip():
73
+ raise ValueError("server_name cannot be empty")
74
+ return v.strip()
75
+
76
+ @field_validator('resource_uri')
77
+ @classmethod
78
+ def validate_resource_uri(cls, v):
79
+ """Validate resource URI is not empty."""
80
+ if not v or not v.strip():
81
+ raise ValueError("resource_uri cannot be empty")
82
+ return v.strip()
83
+
84
+
85
+ class MCPListToolsAction(BaseModel):
86
+ """Parameters for listing MCP tools."""
87
+ model_config = ConfigDict(extra='ignore') # Allow extra fields but ignore them
88
+
89
+ server_name: Optional[str] = Field(None, description="Specific server name (optional, lists all if not provided)")
90
+
91
+ @field_validator('server_name')
92
+ @classmethod
93
+ def validate_server_name(cls, v):
94
+ """Validate server name is not empty if provided."""
95
+ if v is not None and (not v or not v.strip()):
96
+ raise ValueError("server_name cannot be empty if provided")
97
+ return v.strip() if v else None
98
+
99
+
100
+ class MCPListResourcesAction(BaseModel):
101
+ """Parameters for listing MCP resources."""
102
+ model_config = ConfigDict(extra='ignore') # Allow extra fields but ignore them
103
+
104
+ server_name: Optional[str] = Field(None, description="Specific server name (optional, lists all if not provided)")
105
+
106
+ @field_validator('server_name')
107
+ @classmethod
108
+ def validate_server_name(cls, v):
109
+ """Validate server name is not empty if provided."""
110
+ if v is not None and (not v or not v.strip()):
111
+ raise ValueError("server_name cannot be empty if provided")
112
+ return v.strip() if v else None
113
+
114
+
115
+ class MCPListServersAction(BaseModel):
116
+ """Parameters for listing MCP servers."""
117
+ model_config = ConfigDict(extra='ignore') # Allow extra fields but ignore them
118
+
119
+ include_disabled: bool = Field(False, description="Whether to include disabled servers")
120
+ include_details: bool = Field(True, description="Whether to include detailed server information")
121
+
122
+
123
+ class MCPServerStatusAction(BaseModel):
124
+ """Parameters for getting MCP server status."""
125
+ model_config = ConfigDict(extra='forbid')
126
+
127
+ server_name: str = Field(..., description="Name of the MCP server")
128
+
129
+ @field_validator('server_name')
130
+ @classmethod
131
+ def validate_server_name(cls, v):
132
+ """Validate server name is not empty."""
133
+ if not v or not v.strip():
134
+ raise ValueError("server_name cannot be empty")
135
+ return v.strip()
136
+
137
+
138
+ class MCPConnectServerAction(BaseModel):
139
+ """Parameters for connecting to an MCP server."""
140
+ model_config = ConfigDict(extra='forbid')
141
+
142
+ server_name: str = Field(..., description="Name of the MCP server to connect")
143
+
144
+ @field_validator('server_name')
145
+ @classmethod
146
+ def validate_server_name(cls, v):
147
+ """Validate server name is not empty."""
148
+ if not v or not v.strip():
149
+ raise ValueError("server_name cannot be empty")
150
+ return v.strip()
151
+
152
+
153
+ class MCPDisconnectServerAction(BaseModel):
154
+ """Parameters for disconnecting from an MCP server."""
155
+ model_config = ConfigDict(extra='forbid')
156
+
157
+ server_name: str = Field(..., description="Name of the MCP server to disconnect")
158
+
159
+ @field_validator('server_name')
160
+ @classmethod
161
+ def validate_server_name(cls, v):
162
+ """Validate server name is not empty."""
163
+ if not v or not v.strip():
164
+ raise ValueError("server_name cannot be empty")
165
+ return v.strip()
166
+
167
+
168
+ class MCPReloadServerAction(BaseModel):
169
+ """Parameters for reloading an MCP server connection."""
170
+ model_config = ConfigDict(extra='forbid')
171
+
172
+ server_name: str = Field(..., description="Name of the MCP server to reload")
173
+
174
+ @field_validator('server_name')
175
+ @classmethod
176
+ def validate_server_name(cls, v):
177
+ """Validate server name is not empty."""
178
+ if not v or not v.strip():
179
+ raise ValueError("server_name cannot be empty")
180
+ return v.strip()
181
+
182
+
183
+ class MCPGetCapabilitiesAction(BaseModel):
184
+ """Parameters for getting MCP server capabilities."""
185
+ model_config = ConfigDict(extra='forbid')
186
+
187
+ server_name: str = Field(..., description="Name of the MCP server")
188
+
189
+ @field_validator('server_name')
190
+ @classmethod
191
+ def validate_server_name(cls, v):
192
+ """Validate server name is not empty."""
193
+ if not v or not v.strip():
194
+ raise ValueError("server_name cannot be empty")
195
+ return v.strip()
196
+
197
+
198
+ class MCPSubscribeResourceAction(BaseModel):
199
+ """Parameters for subscribing to MCP resource updates."""
200
+ model_config = ConfigDict(extra='forbid')
201
+
202
+ server_name: str = Field(..., description="Name of the MCP server")
203
+ resource_uri: str = Field(..., description="URI of the resource to subscribe to")
204
+
205
+ @field_validator('server_name')
206
+ @classmethod
207
+ def validate_server_name(cls, v):
208
+ """Validate server name is not empty."""
209
+ if not v or not v.strip():
210
+ raise ValueError("server_name cannot be empty")
211
+ return v.strip()
212
+
213
+ @field_validator('resource_uri')
214
+ @classmethod
215
+ def validate_resource_uri(cls, v):
216
+ """Validate resource URI is not empty."""
217
+ if not v or not v.strip():
218
+ raise ValueError("resource_uri cannot be empty")
219
+ return v.strip()
220
+
221
+
222
+ class MCPUnsubscribeResourceAction(BaseModel):
223
+ """Parameters for unsubscribing from MCP resource updates."""
224
+ model_config = ConfigDict(extra='forbid')
225
+
226
+ server_name: str = Field(..., description="Name of the MCP server")
227
+ resource_uri: str = Field(..., description="URI of the resource to unsubscribe from")
228
+
229
+ @field_validator('server_name')
230
+ @classmethod
231
+ def validate_server_name(cls, v):
232
+ """Validate server name is not empty."""
233
+ if not v or not v.strip():
234
+ raise ValueError("server_name cannot be empty")
235
+ return v.strip()
236
+
237
+ @field_validator('resource_uri')
238
+ @classmethod
239
+ def validate_resource_uri(cls, v):
240
+ """Validate resource URI is not empty."""
241
+ if not v or not v.strip():
242
+ raise ValueError("resource_uri cannot be empty")
243
+ return v.strip()
244
+
245
+
246
+ class MCPHealthCheckAction(BaseModel):
247
+ """Parameters for performing MCP server health check."""
248
+ model_config = ConfigDict(extra='ignore')
249
+
250
+ server_name: Optional[str] = Field(None, description="Specific server name (optional, checks all if not provided)")
251
+
252
+ @field_validator('server_name')
253
+ @classmethod
254
+ def validate_server_name(cls, v):
255
+ """Validate server name is not empty if provided."""
256
+ if v is not None and (not v or not v.strip()):
257
+ raise ValueError("server_name cannot be empty if provided")
258
+ return v.strip() if v else None
259
+
260
+
261
+ # Response models for better type safety and documentation
262
+ class MCPToolInfo(BaseModel):
263
+ """Information about an MCP tool."""
264
+
265
+ name: str
266
+ description: str
267
+ server_name: str
268
+ input_schema: Dict[str, Any]
269
+
270
+ def __str__(self) -> str:
271
+ """Format tool info for LLM visibility with schema details."""
272
+ # Extract required parameters from schema
273
+ props = self.input_schema.get('properties', {})
274
+ required = self.input_schema.get('required', [])
275
+
276
+ # Build parameter summary
277
+ params = []
278
+ for param_name, param_schema in props.items():
279
+ param_type = param_schema.get('type', 'any')
280
+ is_required = param_name in required
281
+ req_marker = '(required)' if is_required else '(optional)'
282
+ param_desc = param_schema.get('description', '')
283
+ params.append(f" - {param_name}: {param_type} {req_marker} - {param_desc}")
284
+
285
+ params_str = '\n'.join(params) if params else ' (no parameters)'
286
+
287
+ return f"""
288
+ Tool: {self.name}
289
+ Server: {self.server_name}
290
+ Description: {self.description}
291
+ Parameters:
292
+ {params_str}
293
+ """
294
+
295
+
296
+ class MCPResourceInfo(BaseModel):
297
+ """Information about an MCP resource."""
298
+
299
+ uri: str
300
+ name: str
301
+ server_name: str
302
+ description: Optional[str] = None
303
+ mime_type: Optional[str] = None
304
+
305
+
306
+ class MCPServerInfo(BaseModel):
307
+ """Information about an MCP server."""
308
+
309
+ name: str
310
+ status: str
311
+ type: str
312
+ enabled: bool
313
+ connected_at: Optional[float] = None
314
+ last_error: Optional[str] = None
315
+ retry_count: int = 0
316
+ tools_count: int = 0
317
+ resources_count: int = 0
318
+ capabilities: Dict[str, Any] = Field(default_factory=dict)
319
+
320
+
321
+ class MCPExecutionResult(BaseModel):
322
+ """Result of MCP tool execution."""
323
+
324
+ success: bool
325
+ result: Any = None
326
+ error: Optional[str] = None
327
+ execution_time: Optional[float] = None
328
+
329
+
330
+ class MCPResourceContent(BaseModel):
331
+ """Content of an MCP resource."""
332
+
333
+ uri: str
334
+ content: Any
335
+ mime_type: Optional[str] = None
336
+ size: Optional[int] = None
337
+ last_modified: Optional[float] = None
338
+
339
+
340
+ class MCPHealthStatus(BaseModel):
341
+ """Health status of MCP servers."""
342
+
343
+ healthy_servers: List[str] = Field(default_factory=list)
344
+ unhealthy_servers: List[str] = Field(default_factory=list)
345
+ total_servers: int = 0
346
+ overall_health: str = "unknown" # "healthy", "degraded", "unhealthy"
@@ -0,0 +1,17 @@
1
+ """Unified OAuth manager (Item 4 — minimal WS-G).
2
+
3
+ Library-only: an ``OAuthProvider`` seam + ``OAuthManager`` registry on the existing
4
+ Fernet token store, plus one ``GenericOAuth2Provider``. No existing tool is migrated
5
+ (deferred). Mint/store(encrypted)/refresh a token per user through the manager.
6
+ """
7
+ from tools.oauth.provider import OAuthError, OAuthProvider, OAuthToken
8
+ from tools.oauth.manager import OAuthManager
9
+ from tools.oauth.providers.generic_oauth2 import GenericOAuth2Provider
10
+
11
+ __all__ = [
12
+ "OAuthError",
13
+ "OAuthProvider",
14
+ "OAuthToken",
15
+ "OAuthManager",
16
+ "GenericOAuth2Provider",
17
+ ]
tools/oauth/manager.py ADDED
@@ -0,0 +1,75 @@
1
+ """OAuth manager (Item 4 — WS-G1, trimmed).
2
+
3
+ Holds registered ``OAuthProvider`` instances and a per-``(user_id, provider)`` token
4
+ store encrypted with the EXISTING Fernet helper (``tools/mcp/security.py::
5
+ MCPEncryption``). ``get_token`` returns a cached valid token, auto-refreshing on
6
+ expiry. Library-only — no tool migration; mirrors ``MemoryProviderRegistry``'s shape.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, MutableMapping, Optional, Tuple
11
+
12
+ from tools.oauth.provider import OAuthError, OAuthProvider, OAuthToken
13
+
14
+ Key = Tuple[str, str]
15
+
16
+
17
+ class OAuthManager:
18
+ """Provider registry + encrypted token store keyed by ``(user_id, provider)``."""
19
+
20
+ def __init__(self, encryption: Any = None, store: Optional[MutableMapping[Key, bytes]] = None) -> None:
21
+ self._providers: Dict[str, OAuthProvider] = {}
22
+ # Encrypted-at-rest store: (user_id, provider) -> Fernet ciphertext bytes.
23
+ self._store: MutableMapping[Key, bytes] = store if store is not None else {}
24
+ self._encryption = encryption or self._default_encryption()
25
+
26
+ @staticmethod
27
+ def _default_encryption():
28
+ from tools.mcp.security import MCPEncryption
29
+ return MCPEncryption()
30
+
31
+ # -- provider registry ----------------------------------------------------
32
+
33
+ def register(self, provider: OAuthProvider) -> OAuthProvider:
34
+ self._providers[provider.name] = provider
35
+ return provider
36
+
37
+ def get_provider(self, name: str) -> OAuthProvider:
38
+ prov = self._providers.get(name)
39
+ if prov is None:
40
+ raise OAuthError(
41
+ f"unknown oauth provider '{name}' (known: {sorted(self._providers)})"
42
+ )
43
+ return prov
44
+
45
+ # -- encrypted token store ------------------------------------------------
46
+
47
+ def store_token(self, user_id: str, provider: str, token: OAuthToken) -> None:
48
+ self._store[(user_id, provider)] = self._encryption.encrypt_dict(token.to_dict())
49
+
50
+ def load_token(self, user_id: str, provider: str) -> Optional[OAuthToken]:
51
+ blob = self._store.get((user_id, provider))
52
+ if not blob:
53
+ return None
54
+ return OAuthToken.from_dict(self._encryption.decrypt_dict(blob))
55
+
56
+ def has_token(self, user_id: str, provider: str) -> bool:
57
+ return (user_id, provider) in self._store
58
+
59
+ # -- main entrypoint ------------------------------------------------------
60
+
61
+ async def get_token(self, user_id: str, provider: str) -> OAuthToken:
62
+ """Return a valid token for ``(user_id, provider)``, refreshing if expired."""
63
+ prov = self.get_provider(provider)
64
+ token = self.load_token(user_id, provider)
65
+ if token is None:
66
+ raise OAuthError(f"no token stored for user='{user_id}' provider='{provider}'")
67
+ if prov.valid_token(token):
68
+ return token
69
+ if not token.refresh_token:
70
+ raise OAuthError(
71
+ f"token expired and no refresh_token for user='{user_id}' provider='{provider}'"
72
+ )
73
+ refreshed = await prov.refresh(token)
74
+ self.store_token(user_id, provider, refreshed)
75
+ return refreshed
@@ -0,0 +1,69 @@
1
+ """OAuth provider seam (Item 4 — WS-G1, trimmed).
2
+
3
+ An ``OAuthProvider`` knows how to build an authorize URL, exchange an auth code for
4
+ tokens, and refresh them. ``OAuthToken`` is the normalised token shape stored
5
+ (encrypted) by the ``OAuthManager``.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ from abc import ABC, abstractmethod
11
+ from dataclasses import asdict, dataclass, field
12
+ from typing import Any, Dict, Optional
13
+
14
+
15
+ class OAuthError(RuntimeError):
16
+ """Raised for unknown-provider / missing-token / refresh failures."""
17
+
18
+
19
+ @dataclass
20
+ class OAuthToken:
21
+ """A normalised OAuth2 token set."""
22
+ access_token: str
23
+ refresh_token: Optional[str] = None
24
+ expires_at: Optional[float] = None # epoch seconds; None = non-expiring
25
+ token_type: str = "Bearer"
26
+ scope: Optional[str] = None
27
+ extra: Dict[str, Any] = field(default_factory=dict)
28
+
29
+ def to_dict(self) -> Dict[str, Any]:
30
+ return asdict(self)
31
+
32
+ @classmethod
33
+ def from_dict(cls, d: Dict[str, Any]) -> "OAuthToken":
34
+ d = dict(d or {})
35
+ known = {f for f in cls.__dataclass_fields__} # type: ignore[attr-defined]
36
+ extra = {k: d.pop(k) for k in list(d) if k not in known}
37
+ token = cls(**{k: v for k, v in d.items() if k in known})
38
+ if extra:
39
+ token.extra.update(extra)
40
+ return token
41
+
42
+
43
+ class OAuthProvider(ABC):
44
+ """Auth-code + refresh-token OAuth2 provider."""
45
+
46
+ name: str = "oauth"
47
+
48
+ #: refresh this many seconds before actual expiry
49
+ leeway_sec: float = 60.0
50
+
51
+ @abstractmethod
52
+ def authorize_url(self, *, state: Optional[str] = None, redirect_uri: Optional[str] = None) -> str:
53
+ """Build the user-facing authorization URL."""
54
+
55
+ @abstractmethod
56
+ async def exchange_code(self, code: str, *, redirect_uri: Optional[str] = None) -> OAuthToken:
57
+ """Exchange an authorization code for a token set."""
58
+
59
+ @abstractmethod
60
+ async def refresh(self, token: OAuthToken) -> OAuthToken:
61
+ """Exchange a refresh token for a fresh token set."""
62
+
63
+ def valid_token(self, token: Optional[OAuthToken]) -> bool:
64
+ """True if ``token`` is present and not within ``leeway_sec`` of expiry."""
65
+ if not token or not token.access_token:
66
+ return False
67
+ if token.expires_at is None:
68
+ return True
69
+ return time.time() < (token.expires_at - self.leeway_sec)
@@ -0,0 +1 @@
1
+ """OAuth providers (Item 4). Only the generic auth-code provider ships."""