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
api/jwt_middleware.py ADDED
@@ -0,0 +1,83 @@
1
+ """JWT authentication middleware."""
2
+
3
+ import logging
4
+ import jwt
5
+ from typing import Callable
6
+
7
+ from fastapi import Request
8
+ from fastapi.responses import JSONResponse
9
+ from starlette.middleware.base import BaseHTTPMiddleware
10
+ from starlette.responses import Response
11
+
12
+ from api.auth_constants import is_admin
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class JWTAuthMiddleware(BaseHTTPMiddleware):
18
+ """Decode JWT and populate request.state."""
19
+
20
+ PUBLIC_PATHS = {
21
+ "/", "/health", "/docs", "/openapi.json", "/redoc",
22
+ "/api/auth/nonce", "/api/auth/verify",
23
+ "/api/payments/pricing", "/api/x402/pricing"
24
+ }
25
+
26
+ def __init__(self, app, jwt_secret: str):
27
+ super().__init__(app)
28
+ self.jwt_secret = jwt_secret
29
+
30
+ async def dispatch(self, request: Request, call_next: Callable) -> Response:
31
+ """Process request with JWT authentication."""
32
+
33
+ # Skip public endpoints
34
+ if (request.url.path in self.PUBLIC_PATHS or
35
+ request.url.path.startswith("/static")):
36
+ return await call_next(request)
37
+
38
+ # Get JWT
39
+ auth_header = request.headers.get("Authorization", "")
40
+ if not auth_header.startswith("Bearer "):
41
+ return await call_next(request) # x402 flow
42
+
43
+ token = auth_header[7:]
44
+
45
+ try:
46
+ # Decode JWT
47
+ payload = jwt.decode(token, self.jwt_secret, algorithms=["HS256"])
48
+
49
+ # Populate request.state via the canonical C4 contract.
50
+ from api.auth_state import set_auth_state
51
+ set_auth_state(
52
+ request.state,
53
+ user_id=payload.get("user_id"),
54
+ tier=payload.get("tier", "free"),
55
+ role=payload.get("role", "user"),
56
+ payment_method=None,
57
+ authenticated=True,
58
+ )
59
+ # NOTE: "sub" contains wallet_address (JWT standard)
60
+ request.state.wallet_address = payload.get("sub")
61
+ request.state.chain = payload.get("chain", "ethereum")
62
+
63
+ # Admin flag (simplified: only 'admin' role or admin wallet)
64
+ request.state.is_admin = is_admin(
65
+ role=request.state.role,
66
+ wallet_address=request.state.wallet_address
67
+ )
68
+
69
+ wallet_display = payload.get('sub', '')[:10] if payload.get('sub') else 'unknown'
70
+ logger.debug(f"JWT valid for {wallet_display}...")
71
+
72
+ except jwt.ExpiredSignatureError:
73
+ return JSONResponse(
74
+ status_code=401,
75
+ content={"error": "Token expired", "code": "TOKEN_EXPIRED"}
76
+ )
77
+ except jwt.InvalidTokenError:
78
+ return JSONResponse(
79
+ status_code=401,
80
+ content={"error": "Invalid token", "code": "INVALID_TOKEN"}
81
+ )
82
+
83
+ return await call_next(request)
api/kb/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # api/kb package
api/kb/endpoints.py ADDED
@@ -0,0 +1,314 @@
1
+ """KB (knowledge-base) HTTP API — gated, multi-tenant.
2
+
3
+ Mount in ``api/app.py`` behind ``KB_API_ENABLED`` (default OFF), mirroring the
4
+ OpenAI-compat block:
5
+
6
+ from api.kb.endpoints import router as kb_router, kb_api_enabled
7
+ if kb_api_enabled():
8
+ app.include_router(kb_router)
9
+
10
+ Auth
11
+ ----
12
+ ``user_id`` is ALWAYS resolved from the authenticated request state via the shared
13
+ ``get_user_id`` dependency (same seam the task HTTP API and other routers use).
14
+ A caller-supplied ``user_id`` in the body is never trusted.
15
+
16
+ Provider registration
17
+ ---------------------
18
+ The SQLite memory backend (``MEMORY_BACKEND=sqlite``, default) is registered lazily
19
+ on the first ingest/search request via ``_ensure_backend``. On the full server path
20
+ ``build_server_bot`` does NOT call ``maybe_register_memory_backend`` — that call lives
21
+ in ``construction.py`` (per-session agent build). Calling it here on first request is
22
+ idempotent: the factory's active-check short-circuits if a provider is already live
23
+ (a sibling session already registered it).
24
+
25
+ Path confinement (path-body variant)
26
+ -------------------------------------
27
+ ``POST /api/kb/ingest`` with a path in the body: the path must be **relative** and
28
+ must not escape the authenticated tenant's session workspace. Absolute paths and
29
+ ``..``-escapes are rejected with HTTP 400 *before* the engine is called (defence in
30
+ depth — the engine also confines, but we fail-fast here).
31
+
32
+ File-upload variant
33
+ -------------------
34
+ ``POST /api/kb/ingest/upload`` accepts a multipart file upload. The bytes are
35
+ written to a unique temp file UNDER the authenticated tenant's session workspace
36
+ (``<workspace>/.kb_uploads/<unique>``), so the shared ``kb_ingest`` engine's
37
+ confinement check passes naturally. The upload is ingested through the SAME engine
38
+ as the path-body variant (no duplicated chunking/hashing), with ``source_name`` set
39
+ to the stripped original filename so a re-upload of the same file dedups by its
40
+ logical name rather than the volatile temp path. The temp file is removed in a
41
+ ``finally`` block even on error.
42
+ """
43
+ from __future__ import annotations
44
+
45
+ import logging
46
+ import os
47
+ import tempfile
48
+ from pathlib import Path
49
+ from typing import Optional
50
+
51
+ from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
52
+ from fastapi.responses import JSONResponse
53
+ from pydantic import BaseModel, Field
54
+
55
+ from api.dependencies import get_user_id
56
+ from core.path_safety import is_within_root
57
+
58
+ logger = logging.getLogger(__name__)
59
+
60
+ router = APIRouter(prefix="/api/kb", tags=["knowledge-base"])
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # Gate helper
64
+ # ---------------------------------------------------------------------------
65
+
66
+
67
+ def kb_api_enabled() -> bool:
68
+ """Whether the KB HTTP API is mounted (default OFF).
69
+
70
+ Uses the project's shared ``_bool_env`` parser (falsey-set semantics:
71
+ none/off/false/0/no/'' = off), so the flag behaves consistently with the
72
+ rest of POLYROB's env flags.
73
+ """
74
+ from agents.task.constants import _bool_env
75
+ return _bool_env("KB_API_ENABLED", False)
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Lazy backend registration (idempotent)
80
+ # ---------------------------------------------------------------------------
81
+
82
+ def _ensure_backend() -> None:
83
+ """Register the memory backend if it isn't already active.
84
+
85
+ Called on the first ingest/search request; idempotent — the factory
86
+ short-circuits if a provider is already registered.
87
+ """
88
+ try:
89
+ from modules.memory.backend_factory import maybe_register_memory_backend
90
+ from core.config import BotConfig
91
+ try:
92
+ cfg = BotConfig()
93
+ data_dir = getattr(cfg, "data_dir", "data") or "data"
94
+ except Exception:
95
+ data_dir = "data"
96
+ maybe_register_memory_backend(data_dir=data_dir)
97
+ except Exception as e:
98
+ logger.debug("KB API: memory backend registration skipped: %s", e)
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Request models
103
+ # ---------------------------------------------------------------------------
104
+
105
+ class KbIngestBody(BaseModel):
106
+ """Body for ``POST /api/kb/ingest`` when providing a workspace-relative path."""
107
+ path: str = Field(..., description="Path relative to the tenant's session workspace.")
108
+ session_id: str = Field(..., description="Session ID whose workspace root to use.")
109
+ collection: str = Field("default", description="KB collection name.")
110
+ recursive: bool = Field(True, description="Recurse into subdirectories.")
111
+ globs: Optional[list[str]] = Field(None, description="Optional glob patterns.")
112
+
113
+
114
+ class KbSearchBody(BaseModel):
115
+ """Body for ``POST /api/kb/search``."""
116
+ query: str = Field(..., description="Search query.")
117
+ collection: str = Field("default", description="KB collection to search.")
118
+ limit: int = Field(8, ge=1, le=50, description="Maximum number of results.")
119
+
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # Path-safety helper (endpoint-level early rejection)
123
+ # ---------------------------------------------------------------------------
124
+
125
+ def _resolve_and_guard(
126
+ path: str,
127
+ session_id: str,
128
+ user_id: str,
129
+ ) -> Path:
130
+ """Resolve *path* against the tenant workspace and return the resolved Path.
131
+
132
+ Raises ``HTTPException(400)`` for:
133
+ - Absolute paths.
134
+ - Paths that escape the workspace root after resolution.
135
+ """
136
+ if os.path.isabs(path):
137
+ raise HTTPException(
138
+ status_code=400,
139
+ detail="Absolute paths are not allowed. Provide a path relative to your session workspace.",
140
+ )
141
+
142
+ from agents.task.path import pm
143
+ try:
144
+ workspace_root = pm().get_workspace_dir(session_id, user_id)
145
+ except Exception as exc:
146
+ raise HTTPException(
147
+ status_code=400,
148
+ detail=f"Could not resolve workspace for session '{session_id}': {exc}",
149
+ )
150
+
151
+ resolved = (workspace_root / path).resolve()
152
+
153
+ if not is_within_root(str(resolved), str(workspace_root.resolve())):
154
+ raise HTTPException(
155
+ status_code=400,
156
+ detail="Path escapes the session workspace. Only paths within the workspace are allowed.",
157
+ )
158
+
159
+ return resolved
160
+
161
+
162
+ # ---------------------------------------------------------------------------
163
+ # POST /api/kb/ingest (path body variant)
164
+ # ---------------------------------------------------------------------------
165
+
166
+ @router.post("/ingest")
167
+ async def kb_ingest_path(
168
+ body: KbIngestBody,
169
+ user_id: str = Depends(get_user_id),
170
+ ) -> JSONResponse:
171
+ """Ingest a file or directory from the tenant's session workspace into the KB.
172
+
173
+ The ``path`` in the body must be **relative** to the authenticated tenant's
174
+ session workspace. Absolute paths and ``..``-escaping paths are rejected.
175
+ ``user_id`` is derived from the auth dependency — the body never supplies it.
176
+ """
177
+ _ensure_backend()
178
+
179
+ # Early path safety — reject absolute / escaping paths before calling the engine.
180
+ _resolve_and_guard(body.path, body.session_id, user_id)
181
+
182
+ try:
183
+ from tools.knowledge_ingest import kb_ingest
184
+ result = await kb_ingest(
185
+ body.path,
186
+ collection=body.collection,
187
+ recursive=body.recursive,
188
+ globs=body.globs,
189
+ user_id=user_id,
190
+ session_id=body.session_id,
191
+ )
192
+ except Exception as exc:
193
+ logger.warning("KB ingest failed for user=%s: %s", user_id, exc)
194
+ raise HTTPException(status_code=500, detail=f"KB ingest error: {exc}")
195
+
196
+ if "error" in result:
197
+ raise HTTPException(status_code=400, detail=result["error"])
198
+
199
+ return JSONResponse(content=result)
200
+
201
+
202
+ # ---------------------------------------------------------------------------
203
+ # POST /api/kb/ingest/upload (file-upload variant)
204
+ # ---------------------------------------------------------------------------
205
+
206
+ @router.post("/ingest/upload")
207
+ async def kb_ingest_upload(
208
+ file: UploadFile = File(...),
209
+ collection: str = "default",
210
+ session_id: str = "_kb_upload_",
211
+ user_id: str = Depends(get_user_id),
212
+ ) -> JSONResponse:
213
+ """Ingest an uploaded file into the tenant's KB.
214
+
215
+ The bytes are written to a unique temp file UNDER the authenticated tenant's
216
+ session workspace, then ingested through the SAME ``kb_ingest`` engine as the
217
+ path-body variant — so size guards, MIME detection, dedup and confinement all
218
+ apply for free. ``source_name`` is the stripped original filename, so a
219
+ re-upload of the same file dedups by logical name (not the volatile temp path).
220
+ ``user_id`` comes from the auth dependency, NEVER from the request body.
221
+ """
222
+ _ensure_backend()
223
+
224
+ # Resolve the tenant's confined workspace root and write the upload UNDER it,
225
+ # so the engine's is_within_root confinement check passes naturally.
226
+ from agents.task.path import pm
227
+ try:
228
+ workspace_root = pm().get_workspace_dir(session_id, user_id)
229
+ except Exception as exc:
230
+ raise HTTPException(
231
+ status_code=400,
232
+ detail=f"Could not resolve workspace for session '{session_id}': {exc}",
233
+ )
234
+
235
+ upload_dir = Path(workspace_root) / ".kb_uploads"
236
+ upload_dir.mkdir(parents=True, exist_ok=True)
237
+
238
+ # Strip path components from the client filename (no traversal); keep it as the
239
+ # STABLE source identity so a re-upload dedups by its original name.
240
+ source_name = Path(file.filename or "upload.bin").name
241
+ suffix = Path(source_name).suffix or ".bin"
242
+
243
+ tmp_file: Optional[Path] = None
244
+ try:
245
+ fd, tmp_path_str = tempfile.mkstemp(suffix=suffix, dir=str(upload_dir))
246
+ tmp_file = Path(tmp_path_str)
247
+ try:
248
+ with os.fdopen(fd, "wb") as fh:
249
+ content = await file.read()
250
+ fh.write(content)
251
+ except Exception:
252
+ os.close(fd)
253
+ raise
254
+
255
+ # Engine path is relative to the workspace root (it resolves
256
+ # allowed_root / path); pass the temp file's path relative to the root.
257
+ rel_path = str(tmp_file.relative_to(Path(workspace_root)))
258
+
259
+ from tools.knowledge_ingest import kb_ingest
260
+ result = await kb_ingest(
261
+ rel_path,
262
+ collection=collection,
263
+ recursive=False,
264
+ user_id=user_id,
265
+ session_id=session_id,
266
+ source_name=source_name,
267
+ )
268
+ except HTTPException:
269
+ raise
270
+ except Exception as exc:
271
+ logger.warning("KB upload ingest failed for user=%s: %s", user_id, exc)
272
+ raise HTTPException(status_code=500, detail=f"KB ingest error: {exc}")
273
+ finally:
274
+ if tmp_file is not None:
275
+ try:
276
+ tmp_file.unlink(missing_ok=True)
277
+ except OSError:
278
+ pass
279
+
280
+ if "error" in result:
281
+ raise HTTPException(status_code=400, detail=result["error"])
282
+
283
+ return JSONResponse(content=result)
284
+
285
+
286
+ # ---------------------------------------------------------------------------
287
+ # POST /api/kb/search
288
+ # ---------------------------------------------------------------------------
289
+
290
+ @router.post("/search")
291
+ async def kb_search_endpoint(
292
+ body: KbSearchBody,
293
+ user_id: str = Depends(get_user_id),
294
+ ) -> JSONResponse:
295
+ """Search the tenant's KB.
296
+
297
+ Results are scoped to the authenticated ``user_id``; a caller cannot search
298
+ another tenant's data.
299
+ """
300
+ _ensure_backend()
301
+
302
+ try:
303
+ from modules.memory.registry import kb_search as _kb_search
304
+ result = await _kb_search(
305
+ body.query,
306
+ user_id=user_id,
307
+ collection=body.collection,
308
+ limit=body.limit,
309
+ )
310
+ except Exception as exc:
311
+ logger.warning("KB search failed for user=%s: %s", user_id, exc)
312
+ raise HTTPException(status_code=500, detail=f"KB search error: {exc}")
313
+
314
+ return JSONResponse(content={"results": result or ""})
api/mcp_models.py ADDED
@@ -0,0 +1,279 @@
1
+ """
2
+ Pydantic models for MCP server management API.
3
+ """
4
+
5
+ from typing import Optional, List, Dict, Any
6
+ from pydantic import BaseModel, Field, validator
7
+ import re
8
+
9
+
10
+ class AddServerRequest(BaseModel):
11
+ """Request to add a new MCP server."""
12
+
13
+ server_name: str = Field(
14
+ ...,
15
+ min_length=3,
16
+ max_length=50,
17
+ description="Unique server name (lowercase, alphanumeric, hyphens only)"
18
+ )
19
+ server_url: str = Field(
20
+ ...,
21
+ description="Server endpoint URL (must be HTTPS)"
22
+ )
23
+ server_type: str = Field(
24
+ default="sse",
25
+ description="Server type: 'sse' or 'http' (STDIO not allowed)"
26
+ )
27
+ auth_method: str = Field(
28
+ default="api_key",
29
+ description="Authentication method: 'api_key', 'bearer', or 'none'"
30
+ )
31
+ api_key: Optional[str] = Field(
32
+ default=None,
33
+ description="API key for authentication (will be encrypted)"
34
+ )
35
+ headers: Optional[Dict[str, str]] = Field(
36
+ default=None,
37
+ description="Custom headers (will be encrypted)"
38
+ )
39
+ display_name: Optional[str] = Field(
40
+ default=None,
41
+ max_length=100,
42
+ description="Friendly display name"
43
+ )
44
+ enabled: bool = Field(
45
+ default=True,
46
+ description="Whether server is enabled"
47
+ )
48
+ timeout: int = Field(
49
+ default=30,
50
+ ge=5,
51
+ le=300,
52
+ description="Connection timeout in seconds"
53
+ )
54
+ retry_attempts: int = Field(
55
+ default=3,
56
+ ge=0,
57
+ le=10,
58
+ description="Number of retry attempts"
59
+ )
60
+ retry_delay: int = Field(
61
+ default=5,
62
+ ge=1,
63
+ le=60,
64
+ description="Delay between retry attempts in seconds"
65
+ )
66
+ auto_reconnect: bool = Field(
67
+ default=True,
68
+ description="Whether to automatically reconnect on failure"
69
+ )
70
+ max_concurrent_requests: int = Field(
71
+ default=5,
72
+ ge=1,
73
+ le=50,
74
+ description="Maximum concurrent requests to this server"
75
+ )
76
+ verify_connection: bool = Field(
77
+ default=False,
78
+ description="If true, verify MCP protocol before saving (tests connection and discovers tools)"
79
+ )
80
+ message_endpoint: Optional[str] = Field(
81
+ default=None,
82
+ description="For SSE servers: explicit POST endpoint for sending messages (if different from main URL)"
83
+ )
84
+
85
+ @validator('server_name')
86
+ def validate_server_name(cls, v):
87
+ """Validate server name format."""
88
+ if not re.match(r'^[a-z0-9][a-z0-9-]*[a-z0-9]$', v) and len(v) > 2:
89
+ raise ValueError('Server name must be lowercase alphanumeric with hyphens')
90
+ if len(v) <= 2 and not re.match(r'^[a-z0-9]+$', v):
91
+ raise ValueError('Server name must be lowercase alphanumeric')
92
+ return v
93
+
94
+ @validator('server_type')
95
+ def validate_server_type(cls, v):
96
+ """Validate server type."""
97
+ if v not in ('sse', 'http', 'streamable_http'):
98
+ raise ValueError('Server type must be "sse", "http", or "streamable_http" (STDIO not allowed)')
99
+ return v
100
+
101
+ @validator('auth_method')
102
+ def validate_auth_method(cls, v):
103
+ """Validate auth method."""
104
+ if v not in ('api_key', 'bearer', 'none'):
105
+ raise ValueError('Auth method must be "api_key", "bearer", or "none"')
106
+ return v
107
+
108
+
109
+ class UpdateServerRequest(BaseModel):
110
+ """Request to update an MCP server."""
111
+
112
+ server_url: Optional[str] = Field(
113
+ default=None,
114
+ description="Server endpoint URL (must be HTTPS)"
115
+ )
116
+ auth_method: Optional[str] = Field(
117
+ default=None,
118
+ description="Authentication method"
119
+ )
120
+ api_key: Optional[str] = Field(
121
+ default=None,
122
+ description="API key for authentication"
123
+ )
124
+ headers: Optional[Dict[str, str]] = Field(
125
+ default=None,
126
+ description="Custom headers"
127
+ )
128
+ display_name: Optional[str] = Field(
129
+ default=None,
130
+ max_length=100,
131
+ description="Friendly display name"
132
+ )
133
+ enabled: Optional[bool] = Field(
134
+ default=None,
135
+ description="Whether server is enabled"
136
+ )
137
+ timeout: Optional[int] = Field(
138
+ default=None,
139
+ ge=5,
140
+ le=300,
141
+ description="Connection timeout"
142
+ )
143
+ retry_attempts: Optional[int] = Field(
144
+ default=None,
145
+ ge=0,
146
+ le=10,
147
+ description="Retry attempts"
148
+ )
149
+ retry_delay: Optional[int] = Field(
150
+ default=None,
151
+ ge=1,
152
+ le=60,
153
+ description="Delay between retry attempts in seconds"
154
+ )
155
+ auto_reconnect: Optional[bool] = Field(
156
+ default=None,
157
+ description="Whether to automatically reconnect on failure"
158
+ )
159
+ max_concurrent_requests: Optional[int] = Field(
160
+ default=None,
161
+ ge=1,
162
+ le=50,
163
+ description="Maximum concurrent requests to this server"
164
+ )
165
+ message_endpoint: Optional[str] = Field(
166
+ default=None,
167
+ description="For SSE servers: explicit POST endpoint for sending messages"
168
+ )
169
+
170
+
171
+ class ServerResponse(BaseModel):
172
+ """Response with server details."""
173
+
174
+ server_name: str
175
+ display_name: Optional[str]
176
+ server_url: str
177
+ server_type: str
178
+ auth_method: str
179
+ enabled: bool
180
+ timeout: int
181
+ retry_attempts: int
182
+ retry_delay: int
183
+ auto_reconnect: bool
184
+ max_concurrent_requests: int
185
+ message_endpoint: Optional[str]
186
+ auth_status: str
187
+ connection_count: int
188
+ tools_discovered: int
189
+ last_connected_at: Optional[str]
190
+ last_error: Optional[str]
191
+ created_at: str
192
+ updated_at: str
193
+
194
+ # Tool ID for use in session creation
195
+ tool_id: str
196
+
197
+
198
+ class ServerListResponse(BaseModel):
199
+ """Response with list of servers."""
200
+
201
+ servers: List[ServerResponse]
202
+ count: int
203
+ max_servers: int
204
+
205
+
206
+ class TestConnectionResponse(BaseModel):
207
+ """Response from connection test."""
208
+
209
+ success: bool
210
+ latency_ms: Optional[float]
211
+ error: Optional[str]
212
+ tools_discovered: Optional[int]
213
+
214
+
215
+ class UserSettingsRequest(BaseModel):
216
+ """Request to update user MCP settings."""
217
+
218
+ mcp_enabled: Optional[bool] = Field(
219
+ default=None,
220
+ description="Enable/disable MCP for user"
221
+ )
222
+ include_global_servers: Optional[bool] = Field(
223
+ default=None,
224
+ description="Include global platform servers"
225
+ )
226
+ max_servers: Optional[int] = Field(
227
+ default=None,
228
+ ge=1,
229
+ le=50,
230
+ description="Maximum number of custom servers"
231
+ )
232
+ default_timeout: Optional[int] = Field(
233
+ default=None,
234
+ ge=5,
235
+ le=300,
236
+ description="Default timeout for new servers"
237
+ )
238
+
239
+
240
+ class UserSettingsResponse(BaseModel):
241
+ """Response with user MCP settings."""
242
+
243
+ mcp_enabled: bool
244
+ include_global_servers: bool
245
+ max_servers: int
246
+ default_timeout: int
247
+
248
+
249
+ class AvailableServersResponse(BaseModel):
250
+ """Response with all available MCP servers for session creation."""
251
+
252
+ global_servers: List[Dict[str, Any]]
253
+ user_servers: List[Dict[str, Any]]
254
+ include_global: bool
255
+
256
+
257
+ class AuditLogEntry(BaseModel):
258
+ """Single audit log entry."""
259
+
260
+ id: int
261
+ action: str
262
+ server_name: Optional[str]
263
+ details: Optional[Dict[str, Any]]
264
+ ip_address: Optional[str]
265
+ timestamp: str
266
+
267
+
268
+ class AuditLogResponse(BaseModel):
269
+ """Response with audit log entries."""
270
+
271
+ entries: List[AuditLogEntry]
272
+ count: int
273
+
274
+
275
+ class ErrorResponse(BaseModel):
276
+ """Standard error response."""
277
+
278
+ error: str
279
+ detail: Optional[str] = None