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,471 @@
1
+ from __future__ import annotations
2
+
3
+ """Collab.Land integration service.
4
+
5
+ This service provides helper methods and bot actions to:
6
+ 1. Check whether a given blockchain address holds the required token(s) using the Collab.Land
7
+ `access-control/check-roles` endpoint.
8
+ 2. Persist wallet → membership status inside the configured `database_manager`.
9
+ 3. Allow administrators to trigger a background refresh to keep the local cache in-sync
10
+ with on-chain reality (users that dumped the token are revoked, new holders are granted).
11
+
12
+ The implementation conforms to the project-wide service conventions (inherits from
13
+ `BaseService`, defines `@action` decorated public endpoints, handles dependency
14
+ injection, proper life-cycle management, rate-limit handling, etc.).
15
+
16
+ NOTE: Collab.Land exposes a huge REST surface. For the initial iteration we only
17
+ consume the token-gating endpoint:
18
+ POST https://api.collab.land/access-control/check-roles
19
+ See https://dev.collab.land/docs/tutorials/token-gating-tutorial for details.
20
+ """
21
+
22
+ import asyncio
23
+ import logging
24
+ import time
25
+ from typing import Any, Dict, List, Optional
26
+
27
+ import aiohttp
28
+ from pydantic import BaseModel, Field
29
+
30
+ from core.config import BotConfig
31
+ from core.exceptions import APIError, ConfigurationError, ToolError
32
+ from tools.base_tool import BaseTool, ToolStatus
33
+
34
+ __all__ = ["CollabLandTool"]
35
+
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # Pydantic models for public actions
39
+ # ---------------------------------------------------------------------------
40
+
41
+
42
+ class CheckTokenParams(BaseModel):
43
+ """Action parameters for `collabland_check_token`."""
44
+
45
+ address: str = Field(..., description="EVM compatible wallet address to check")
46
+ rules: Optional[List[Dict[str, Any]]] = Field(
47
+ None,
48
+ description=(
49
+ "Optional explicit Token-Gating Rules overriding the defaults defined in "
50
+ "bot configuration (see `collabland_rules`)."
51
+ ),
52
+ )
53
+
54
+
55
+ class AdminRefreshParams(BaseModel):
56
+ """Parameters for the admin batch refresh action."""
57
+
58
+ confirm: bool = Field(
59
+ False,
60
+ description="Extra safety switch. Must be `true` to actually run the refresh.",
61
+ )
62
+
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # Service implementation
66
+ # ---------------------------------------------------------------------------
67
+
68
+
69
+ class CollabLandTool(BaseTool):
70
+ """Integration with Collab.Land token-gating endpoints."""
71
+
72
+ # ---- Dependencies -----------------------------------------------------
73
+
74
+ @property
75
+ def required_services(self) -> Dict[str, str]:
76
+ return {
77
+ "rate_limit_manager": "Rate limit management",
78
+ "database_manager": "Database persistence",
79
+ }
80
+
81
+ @property
82
+ def optional_services(self) -> Dict[str, str]:
83
+ return {
84
+ "cache_manager": "Optional cache layer",
85
+ }
86
+
87
+ # ---- Configuration ----------------------------------------------------
88
+
89
+ @property
90
+ def required_config(self) -> Dict[str, str]:
91
+ return {
92
+ "collabland_api_key": "Collab.Land API key",
93
+ "collabland_rules": "Default Collab.Land token-gating rules (JSON list)",
94
+ }
95
+
96
+ # ---- Lifecycle --------------------------------------------------------
97
+
98
+ def __init__(self, name: str, config: BotConfig, container: Optional[Any] = None):
99
+ super().__init__(name=name, config=config, container=container)
100
+
101
+ self._session: Optional[aiohttp.ClientSession] = None
102
+ self.base_url = getattr(config, "collabland_api_url", "https://api.collab.land")
103
+ self._rules: List[Dict[str, Any]] = getattr(config, "collabland_rules", []) or []
104
+
105
+ # Get all CollabLand configuration from BotConfig
106
+ self._api_key = getattr(config, "collabland_api_key", "")
107
+ self._client_id = getattr(config, "collabland_id", "")
108
+ self._client_secret = getattr(config, "collabland_secret", "")
109
+
110
+ # Handle compound API key format (clientId.secret)
111
+ if self._api_key and '.' in self._api_key and not self._client_id:
112
+ try:
113
+ parts = self._api_key.split('.', 1)
114
+ if len(parts) == 2:
115
+ self._client_id = parts[0]
116
+ self._client_secret = parts[1]
117
+ self.logger.info(f"Parsed compound API key: client_id={self._client_id[:5]}***, secret={self._client_secret[:5]}***")
118
+ except Exception as e:
119
+ self.logger.warning(f"Failed to parse compound API key: {e}")
120
+
121
+ # Determine if service is properly configured
122
+ self._enabled = bool(self._api_key) # Only enable if we have an API key
123
+
124
+ # Log the setup clearly
125
+ if not self._api_key:
126
+ self.logger.error("CRITICAL: Collab.Land API key missing – service disabled")
127
+ self._enabled = False
128
+ else:
129
+ self.logger.info(f"CollabLand service configured:")
130
+ self.logger.info(f" - Base URL: {self.base_url}")
131
+ self.logger.info(f" - API Key: {self._api_key[:5]}*** (length: {len(self._api_key)})")
132
+ if self._client_id:
133
+ self.logger.info(f" - Client ID: {self._client_id}")
134
+ if self._client_secret:
135
+ self.logger.info(f" - Client Secret: {self._client_secret[:5]}***")
136
+ self._enabled = True
137
+
138
+ # Check contract address format if provided
139
+ contract_address = getattr(config, "den_token_contract_address", None)
140
+ if contract_address:
141
+ if not contract_address.startswith("0x") or len(contract_address) != 42:
142
+ self.logger.warning(f"Invalid ERC-721 contract address format: {contract_address}")
143
+ else:
144
+ self.logger.info(f"Using contract address: {contract_address}")
145
+ else:
146
+ self.logger.warning("Token contract address not configured - token verification may be unreliable")
147
+
148
+ # Add default ERC721 rule if no rules configured
149
+ if not self._rules and contract_address:
150
+ self.logger.info("No CollabLand rules provided, creating default ERC721 rule")
151
+ self._rules = [{
152
+ "type": "ERC721",
153
+ "chainId": 1, # Ethereum mainnet
154
+ "contractAddress": contract_address
155
+ }]
156
+ self.logger.info(f"Created default rule: {self._rules}")
157
+
158
+ async def _initialize(self) -> None:
159
+ """Create the shared aiohttp session and test connectivity."""
160
+ # Ensure decorated actions are collected
161
+ await super()._initialize()
162
+
163
+ # Skip if not enabled
164
+ if not self._enabled:
165
+ self._status = ToolStatus.DISABLED
166
+ self.logger.warning("CollabLand service disabled due to missing API key")
167
+ return
168
+
169
+ # Set up HTTP session
170
+ headers = {
171
+ "X-API-Key": self._api_key, # Use the full API key as provided
172
+ "Content-Type": "application/json",
173
+ "Accept": "application/json",
174
+ }
175
+
176
+ # Log headers for debugging (mask sensitive data)
177
+ self.logger.debug(f"Setting up HTTP session with headers:")
178
+ self.logger.debug(f" - X-API-Key: {self._api_key[:10]}*** (full length: {len(self._api_key)})")
179
+ self.logger.debug(f" - Content-Type: application/json")
180
+ self.logger.debug(f" - Accept: application/json")
181
+
182
+ # Create session
183
+ timeout = aiohttp.ClientTimeout(total=30)
184
+ self._session = aiohttp.ClientSession(headers=headers, timeout=timeout)
185
+
186
+ # Test connectivity
187
+ try:
188
+ # Simple test to check if API is reachable
189
+ async with self._session.get(f"{self.base_url}/health") as response:
190
+ if response.status == 200:
191
+ self._status = ToolStatus.HEALTHY
192
+ self.logger.info("CollabLand API connectivity verified")
193
+ else:
194
+ self._status = ToolStatus.DEGRADED
195
+ self.logger.warning(f"CollabLand API returned status {response.status}")
196
+ except Exception as e:
197
+ self._status = ToolStatus.DEGRADED
198
+ self.logger.error(f"Failed to connect to CollabLand API: {e}")
199
+ # Don't disable the service, as it may recover later
200
+
201
+ async def _cleanup(self) -> None:
202
+ if self._session and not self._session.closed:
203
+ await self._session.close()
204
+ self._session = None
205
+ self.logger.debug("CollabLand HTTP session closed")
206
+
207
+ # ---------------------------------------------------------------------
208
+ # Internal helpers
209
+ # ---------------------------------------------------------------------
210
+
211
+ async def _check_roles(self, address: str, rules: List[Dict[str, Any]]) -> tuple[bool, Optional[str]]:
212
+ """Return (has_token, token_id) if the address fulfils all provided rules."""
213
+ # If service is disabled or not working, return False - no auto-pass
214
+ if not self._enabled:
215
+ self.logger.warning(f"CollabLand service disabled - token verification failed for wallet {address[:6]}...{address[-4:]}")
216
+ return False, None
217
+
218
+ # Ensure session exists
219
+ if not self._session:
220
+ self.logger.error("HTTP session not initialized - cannot verify token")
221
+ return False, None
222
+
223
+ try:
224
+ # Prepare request payload
225
+ payload = {
226
+ "account": address,
227
+ "rules": rules,
228
+ }
229
+
230
+ # Log the full request details for debugging
231
+ self.logger.info(f"CollabLand API request: POST {self.base_url}/access-control/check-roles")
232
+ self.logger.info(f"Request payload: {payload}")
233
+ self.logger.debug(f"Request headers: X-API-Key: {self._api_key[:10]}*** (full: {len(self._api_key)} chars)")
234
+ self.logger.debug(f"Full API key for debugging: {self._api_key}") # Temporary debug - remove in production
235
+
236
+ # Make API call to CollabLand
237
+ url = f"{self.base_url}/access-control/check-roles"
238
+ self.logger.info(f"Sending CollabLand API request to {url} for wallet {address[:6]}...{address[-4:]}")
239
+
240
+ start_time = time.time()
241
+ async with self._session.post(url, json=payload) as response:
242
+ # Calculate response time
243
+ response_time = round((time.time() - start_time) * 1000)
244
+
245
+ # Log response status and headers at INFO level
246
+ self.logger.info(f"CollabLand API response: status={response.status}, time={response_time}ms")
247
+ self.logger.debug(f"CollabLand API response headers: {dict(response.headers)}")
248
+
249
+ # Get response body for detailed logging
250
+ response_body = await response.text()
251
+ self.logger.debug(f"CollabLand API response body: {response_body}")
252
+
253
+ # Parse response
254
+ if response.status == 200:
255
+ try:
256
+ data = await response.json()
257
+ self.logger.info(f"CollabLand API returned JSON response: {data}")
258
+
259
+ # Extract relevant information
260
+ has_token = data.get("hasAccess", False)
261
+
262
+ # Try to extract token ID if available
263
+ token_id = None
264
+ if has_token:
265
+ # Access token details from response
266
+ token_details = data.get("tokenBalances", [])
267
+ if token_details:
268
+ # Take the first token ID if available
269
+ first_token = token_details[0]
270
+ token_id = first_token.get("tokenId")
271
+ self.logger.info(f"Found token ID: {token_id}")
272
+
273
+ return has_token, token_id
274
+ except Exception as parse_error:
275
+ self.logger.error(f"Error parsing JSON response: {parse_error}")
276
+ self.logger.error(f"Raw response: {response_body}")
277
+ # Don't mask parsing errors - let them bubble up
278
+ raise Exception(f"Failed to parse CollabLand API response: {parse_error}")
279
+ else:
280
+ # Handle error responses - raise exceptions for authentication errors instead of returning False
281
+ error_text = await response.text()
282
+ self.logger.error(f"CollabLand API error: {response.status} - {error_text}")
283
+
284
+ # Parse error response to get detailed error message
285
+ error_details = error_text
286
+ try:
287
+ import json
288
+ error_json = json.loads(error_text)
289
+ if isinstance(error_json, dict) and "error" in error_json:
290
+ error_info = error_json["error"]
291
+ if isinstance(error_info, dict):
292
+ error_details = error_info.get("message", error_text)
293
+ except (json.JSONDecodeError, ValueError, KeyError, TypeError):
294
+ # If we can't parse the error JSON, use the raw text
295
+ pass
296
+
297
+ # Raise specific exceptions for authentication errors to trigger Alchemy fallback
298
+ if response.status == 401 or response.status == 403:
299
+ self.logger.error("CollabLand API authentication error - check API key")
300
+ raise Exception(f"CollabLand authentication error (HTTP {response.status}): {error_details}")
301
+ elif response.status == 429:
302
+ self.logger.warning("CollabLand API rate limited - consider implementing backoff")
303
+ raise Exception(f"CollabLand rate limit error (HTTP {response.status}): {error_details}")
304
+ else:
305
+ # For other errors, also raise exceptions to trigger fallback
306
+ raise Exception(f"CollabLand API error (HTTP {response.status}): {error_details}")
307
+
308
+ except Exception as e:
309
+ # Only catch and log unexpected exceptions, but re-raise them so they can trigger fallback
310
+ self.logger.error(f"Error checking token for {address}: {e}")
311
+ # Log the full exception traceback for better debugging
312
+ import traceback
313
+ self.logger.error(f"Exception traceback: {traceback.format_exc()}")
314
+
315
+ # Check if this is an authentication error we should propagate
316
+ error_str = str(e).lower()
317
+ if any(keyword in error_str for keyword in ["authentication", "unauthorized", "invalid client", "forbidden", "api key"]):
318
+ # Re-raise authentication errors to trigger Alchemy fallback
319
+ raise e
320
+ elif "rate limit" in error_str or "429" in error_str:
321
+ # Re-raise rate limit errors to trigger Alchemy fallback
322
+ raise e
323
+ else:
324
+ # For other unexpected errors, also re-raise to trigger fallback
325
+ raise e
326
+
327
+ # ---------------------------------------------------------------------
328
+ # Database helpers
329
+ # ---------------------------------------------------------------------
330
+
331
+ async def _update_user_token_status(self, user_id: str, has_token: bool, token_id: Optional[str] = None) -> None:
332
+ """Persist the `has_token` flag inside database_manager.
333
+
334
+ The concrete schema is application-specific. We assume a "users" table with
335
+ at least columns `(id, has_token)` and upsert semantics exposed through
336
+ `database_manager.set_user_flag`.
337
+ """
338
+ db = self.database
339
+ if not db:
340
+ self.logger.warning("database_manager missing – cannot persist token status")
341
+ return
342
+
343
+ try:
344
+ if hasattr(db, "set_user_flag"):
345
+ await db.set_user_flag(user_id, "has_token", has_token)
346
+ if token_id:
347
+ await db.set_user_flag(user_id, "token_id", token_id)
348
+ else:
349
+ # Fallback: generic upsert API
350
+ await db.upsert("users", {"id": user_id, "has_token": has_token})
351
+ if token_id:
352
+ await db.upsert("users", {"id": user_id, "token_id": token_id})
353
+ except Exception as exc:
354
+ self.logger.error(f"Failed to update DB for user {user_id}: {exc}")
355
+
356
+ # ---------------------------------------------------------------------
357
+ # Public actions
358
+ # ---------------------------------------------------------------------
359
+
360
+ @BaseTool.action(
361
+ "Check if the provided wallet address owns the required Collab.Land token(s)",
362
+ param_model=CheckTokenParams,
363
+ )
364
+ async def collabland_check_token(self, params: CheckTokenParams):
365
+ """Return a human-readable message and persist the result in DB."""
366
+ await self.ensure_initialized()
367
+
368
+ if not self._enabled:
369
+ self.logger.error("CollabLand service is disabled (missing API key) - token verification failed")
370
+ return {
371
+ "status": "error",
372
+ "has_token": False,
373
+ "token_id": None,
374
+ "message": "Token verification service is currently unavailable. Please contact an administrator."
375
+ }
376
+
377
+ rules = params.rules or self._rules
378
+ if not rules:
379
+ self.logger.warning("No token-gating rules specified - verification will fail")
380
+ return {
381
+ "status": "error",
382
+ "has_token": False,
383
+ "token_id": None,
384
+ "message": "No token-gating rules available. Please contact an administrator."
385
+ }
386
+
387
+ # Normalise address
388
+ address = params.address.strip().lower()
389
+
390
+ try:
391
+ # Log the request details at INFO level for better visibility
392
+ self.logger.info(f"CollabLand token verification started for wallet: {address[:6]}...{address[-4:]}")
393
+ self.logger.info(f"Using rules: {rules}")
394
+
395
+ has_token, token_id = await self._check_roles(address, rules)
396
+
397
+ # Note: Database updates are handled by the calling service (den_onboarding_handler)
398
+ # We don't update the DB here to avoid table conflicts
399
+
400
+ # Mask address for user-facing messages
401
+ masked_address = f"{address[:6]}...{address[-4:]}"
402
+
403
+ if has_token:
404
+ message = f"✔️ Address `{masked_address}` holds the required token(s)."
405
+ if token_id:
406
+ message += f" Token ID: {token_id}"
407
+ self.logger.info(f"Token verification SUCCESS for {masked_address}: has_token=True, token_id={token_id or 'None'}")
408
+ else:
409
+ message = f"❌ Address `{masked_address}` does NOT meet the token-gating requirements."
410
+ self.logger.info(f"Token verification FAILED for {masked_address}: has_token=False")
411
+
412
+ # Create standardized response format
413
+ result = {
414
+ "status": "success",
415
+ "has_token": has_token,
416
+ "token_id": token_id,
417
+ "message": message
418
+ }
419
+
420
+ # Log full result at INFO level
421
+ self.logger.info(f"CollabLand verification complete: {result}")
422
+
423
+ return result
424
+ except Exception as e:
425
+ self.logger.error(f"Failed to check token for {address}: {e}")
426
+ # Get full exception details
427
+ import traceback
428
+ self.logger.error(f"Exception traceback: {traceback.format_exc()}")
429
+ return {
430
+ "status": "error",
431
+ "has_token": False,
432
+ "token_id": None,
433
+ "message": f"Error checking token: {str(e)}"
434
+ }
435
+
436
+ @BaseTool.action(
437
+ "Admin: batch refresh token status for all known users",
438
+ param_model=AdminRefreshParams,
439
+ )
440
+ async def collabland_admin_refresh(self, params: AdminRefreshParams):
441
+ """Iterate over all users stored in DB and refresh their token status."""
442
+ await self.ensure_initialized()
443
+
444
+ if not params.confirm:
445
+ return (
446
+ "Confirmation missing – set `confirm=true` in parameters to execute the "
447
+ "batch refresh."
448
+ )
449
+
450
+ db = self.database
451
+ if not db or not hasattr(db, "get_all_users"):
452
+ raise ToolError("database_manager does not expose get_all_users() API")
453
+
454
+ users: List[Dict[str, Any]] = await db.get_all_users()
455
+ total, updated = len(users), 0
456
+
457
+ for user in users:
458
+ address = user.get("id") or user.get("wallet") or user.get("address")
459
+ if not address:
460
+ continue
461
+
462
+ try:
463
+ has_token, token_id = await self._check_roles(address, self._rules)
464
+ current_flag = bool(user.get("has_token"))
465
+ if has_token != current_flag:
466
+ await self._update_user_token_status(address, has_token)
467
+ updated += 1
468
+ except Exception as exc:
469
+ self.logger.warning(f"Refresh failed for {address}: {exc}")
470
+
471
+ return f"Batch refresh completed – {updated}/{total} records updated."
File without changes
@@ -0,0 +1,165 @@
1
+ """Shared Controller helpers (UP-11 god-file split).
2
+
3
+ Extracted from `tools/controller/service.py` so the focused Controller mixins
4
+ (execution / tool_management / introspection / action_registration) can import
5
+ these symbols WITHOUT importing `service.py` (which would be a circular import,
6
+ since service.py imports the mixins). `service.py` re-exports all four so the
7
+ established `from tools.controller.service import ToolInfo / make_denylist_hook /
8
+ build_load_skill_result` call sites (tests + callers) keep working unchanged.
9
+
10
+ Pure code-motion — bodies are verbatim.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass
15
+ from typing import Any, Callable, Dict
16
+
17
+ from tools.controller.types import ActionResult
18
+
19
+
20
+ # Define observe function to replace the import
21
+ def observe(*args, **_kwargs):
22
+ def decorator(func):
23
+ return func
24
+ if len(args) == 1 and callable(args[0]):
25
+ return decorator(args[0])
26
+ return decorator
27
+
28
+
29
+ @dataclass
30
+ class ToolInfo:
31
+ """Information about a registered tool."""
32
+ instance: Any
33
+ actions: Dict[str, Callable]
34
+ name: str
35
+
36
+
37
+ def make_denylist_hook(denied_action_names):
38
+ """Build a pre_tool_call hook that denies the given action names.
39
+
40
+ Returns a hook ``(action_name, params, context) -> Optional[str]`` suitable for
41
+ ``Controller.register_pre_tool_call_hook``. Useful for allow/deny lists.
42
+ """
43
+ denied = set(denied_action_names or [])
44
+
45
+ def _hook(action_name, params, context):
46
+ if action_name in denied:
47
+ return f"'{action_name}' is on the tool denylist"
48
+ return None
49
+
50
+ return _hook
51
+
52
+
53
+ def list_skill_resources(skill_dir, *, max_files: int = 50):
54
+ """List a skill's on-disk resource files (Task 17), relative to ``skill_dir``.
55
+
56
+ Excludes ``SKILL.md`` itself (that's the pinned body, not a "resource") and
57
+ any ``.git`` internals. Read-only listing — never inspects file content.
58
+ Fail-open: an unresolvable/missing dir returns ``[]``.
59
+ """
60
+ from pathlib import Path
61
+ out = []
62
+ try:
63
+ base = Path(skill_dir).resolve()
64
+ except OSError:
65
+ return out
66
+ if not base.is_dir():
67
+ return out
68
+ for p in sorted(base.rglob("*")):
69
+ if len(out) >= max_files:
70
+ break
71
+ if not p.is_file() or p.name == "SKILL.md" or ".git" in p.parts:
72
+ continue
73
+ try:
74
+ out.append(str(p.resolve().relative_to(base)))
75
+ except (ValueError, OSError):
76
+ continue
77
+ return out
78
+
79
+
80
+ def read_skill_resource_confined(skill_dir, rel_path: str, *, max_bytes: int = 200_000):
81
+ """Read a single skill resource file, realpath-confined to ``skill_dir`` (Task 17).
82
+
83
+ Read-only — this NEVER executes a resource (e.g. a ``scripts/*.sh`` file),
84
+ it only returns its text content, UP-06-wrapped as untrusted data (skill
85
+ resources may originate from a third-party skill author). Rejects ``../``
86
+ escapes, absolute paths, symlink escapes (via ``.resolve()`` + parents
87
+ check), and oversize files. Returns ``(ok, content_or_error_message)``.
88
+ """
89
+ from pathlib import Path
90
+ from agents.task.agent.core.untrusted_wrap import wrap_untrusted
91
+ try:
92
+ base = Path(skill_dir).resolve()
93
+ target = (base / rel_path).resolve()
94
+ except OSError as e:
95
+ return False, f"cannot resolve resource: {e}"
96
+ if target != base and base not in target.parents:
97
+ return False, "resource path escapes skill directory"
98
+ if not target.is_file():
99
+ return False, "no such resource"
100
+ try:
101
+ if target.stat().st_size > max_bytes:
102
+ return False, f"resource too large (>{max_bytes} bytes)"
103
+ content = target.read_text(encoding="utf-8", errors="replace")
104
+ except OSError as e:
105
+ return False, f"read failed: {e}"
106
+ return True, wrap_untrusted("skill_resource", content)
107
+
108
+
109
+ def build_load_skill_result(session_skills, skill_id, activated=None, skill_dir=None) -> ActionResult:
110
+ """Resolve a load_skill(skill_id) call to an ActionResult (S-1).
111
+
112
+ Pure so it is unit-testable without a live Controller. ``session_skills`` maps
113
+ skill_id -> object with a ``.content`` attribute (the full SKILL.md body).
114
+ Returns an error result for an unknown id, else the body wrapped in a
115
+ cache-preserving <skill> block tagged with ``metadata.skill_loaded``.
116
+
117
+ ``activated`` (Task 12 — activation dedup): an optional session-scoped
118
+ ``set`` of skill_ids already delivered via this call. When ``skill_id`` is
119
+ already in ``activated``, the full body is NOT re-emitted — a short
120
+ "already active" ack is returned instead (``metadata.skill_already_active =
121
+ True``), so a model that calls ``load_skill`` twice for the same id in one
122
+ session doesn't burn tokens re-printing an unchanged body. On a fresh id the
123
+ body is returned as before and the id is recorded into ``activated``
124
+ (mutated in place, e.g. a Controller's ``self._activated_skills``) so the
125
+ NEXT call for that id short-circuits. ``activated=None`` (the default)
126
+ disables tracking entirely — existing callers that don't pass it keep the
127
+ old always-return-the-body behavior, byte-identical.
128
+
129
+ ``skill_dir`` (Task 17 — resource read-path): an optional on-disk directory
130
+ for this skill (resolved by the caller, e.g. ``SkillManager.resolve_skill_dir``).
131
+ When provided, ``metadata['skill_resources']`` is set to
132
+ ``list_skill_resources(skill_dir)`` (``[]`` if the dir doesn't resolve to any
133
+ files) so the model can see what it may subsequently read via
134
+ ``read_skill_resource``. ``skill_dir=None`` (the default) leaves ``metadata``
135
+ untouched — byte-identical to pre-Task-17 callers that don't pass it.
136
+ """
137
+ sid = (skill_id or "").strip().strip('"')
138
+ session_skills = session_skills or {}
139
+ skill = session_skills.get(sid)
140
+ if skill is None:
141
+ available = ", ".join(session_skills.keys()) or "(none)"
142
+ return ActionResult(
143
+ error=f"Unknown skill_id '{sid}'. Available in this session: {available}",
144
+ include_in_memory=True,
145
+ )
146
+ if activated is not None and sid in activated:
147
+ result = ActionResult(
148
+ extracted_content=f"Skill '{sid}' is already active this session — no need to reload.",
149
+ include_in_memory=True,
150
+ metadata={'skill_already_active': True},
151
+ )
152
+ if skill_dir is not None:
153
+ result.metadata['skill_resources'] = list_skill_resources(skill_dir)
154
+ return result
155
+ body = getattr(skill, 'content', '') or ''
156
+ if activated is not None:
157
+ activated.add(sid)
158
+ result = ActionResult(
159
+ extracted_content=f'<skill id="{sid}">\n{body}\n</skill>',
160
+ include_in_memory=True,
161
+ metadata={'skill_loaded': sid},
162
+ )
163
+ if skill_dir is not None:
164
+ result.metadata['skill_resources'] = list_skill_resources(skill_dir)
165
+ return result