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
tools/coding/edit.py ADDED
@@ -0,0 +1,107 @@
1
+ """Pure str_replace logic for the coding tool (C1).
2
+
3
+ Exact-match, unique-or-fail: a 0-match or ambiguous (>1) replace FAILS LOUDLY
4
+ rather than silently editing the wrong place. ``replace_all`` opts into replacing
5
+ every occurrence. No file I/O here — the tool layer owns read/write + path
6
+ confinement; this stays a pure, trivially-testable string transform.
7
+ """
8
+
9
+ # NOTE: deliberately NO ``from __future__ import annotations`` — keep this module
10
+ # consistent with the coding-tool package landmine rule (registry param-model
11
+ # introspection). It costs nothing here.
12
+
13
+ import re as _re
14
+
15
+
16
+ class EditError(Exception):
17
+ """Raised when a str_replace cannot be applied unambiguously."""
18
+
19
+
20
+ def apply_str_replace(
21
+ content: str,
22
+ old_string: str,
23
+ new_string: str,
24
+ replace_all: bool = False,
25
+ ) -> str:
26
+ """Return ``content`` with ``old_string`` replaced by ``new_string``.
27
+
28
+ Raises :class:`EditError` when ``old_string`` is absent, identical to
29
+ ``new_string``, or non-unique without ``replace_all``.
30
+ """
31
+ if old_string == new_string:
32
+ raise EditError("old_string and new_string are identical; nothing to change")
33
+ count = content.count(old_string)
34
+ if count == 0:
35
+ raise EditError("old_string not found in file")
36
+ if count > 1 and not replace_all:
37
+ raise EditError(
38
+ f"old_string is not unique ({count} matches) — add surrounding context "
39
+ f"to make it unique, or pass replace_all=true"
40
+ )
41
+ if replace_all:
42
+ return content.replace(old_string, new_string)
43
+ return content.replace(old_string, new_string, 1)
44
+
45
+
46
+ _HUNK_RE = _re.compile(r"^@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@")
47
+
48
+
49
+ def apply_patch(content: str, patch: str) -> str:
50
+ """Apply a unified-diff ``patch`` to ``content`` and return the new text.
51
+
52
+ Reject-on-context-mismatch: every context (' ') and removed ('-') line must
53
+ match the source at the hunk location, or :class:`EditError` is raised. The
54
+ ``@@ -start,len +start,len @@`` header locates the apply point (1-based).
55
+ Lines starting with '+' are inserted; '\\ No newline...' and blank trailing
56
+ lines are ignored. Single-file only (the tool layer owns one file per call).
57
+ """
58
+ original = content.split("\n")
59
+ patch_lines = patch.split("\n")
60
+ result: list[str] = []
61
+ src_idx = 0
62
+ i = 0
63
+ saw_hunk = False
64
+ while i < len(patch_lines):
65
+ header = _HUNK_RE.match(patch_lines[i])
66
+ if not header:
67
+ i += 1
68
+ continue
69
+ saw_hunk = True
70
+ start = int(header.group(1)) - 1 # 0-based
71
+ if start < src_idx:
72
+ raise EditError("overlapping or out-of-order hunks")
73
+ if start > len(original):
74
+ raise EditError(f"hunk start {start + 1} is beyond end of file")
75
+ result.extend(original[src_idx:start])
76
+ src_idx = start
77
+ i += 1
78
+ while i < len(patch_lines) and not _HUNK_RE.match(patch_lines[i]):
79
+ pl = patch_lines[i]
80
+ if pl.startswith("\\"): # ""
81
+ i += 1
82
+ continue
83
+ if pl.startswith(" "):
84
+ if src_idx >= len(original) or original[src_idx] != pl[1:]:
85
+ found = original[src_idx] if src_idx < len(original) else "<EOF>"
86
+ raise EditError(
87
+ f"context mismatch at line {src_idx + 1}: "
88
+ f"expected {pl[1:]!r}, found {found!r}"
89
+ )
90
+ result.append(original[src_idx])
91
+ src_idx += 1
92
+ elif pl.startswith("-"):
93
+ if src_idx >= len(original) or original[src_idx] != pl[1:]:
94
+ found = original[src_idx] if src_idx < len(original) else "<EOF>"
95
+ raise EditError(
96
+ f"removed-line mismatch at line {src_idx + 1}: "
97
+ f"expected {pl[1:]!r}, found {found!r}"
98
+ )
99
+ src_idx += 1
100
+ elif pl.startswith("+"):
101
+ result.append(pl[1:])
102
+ # bare "" (patch tail) and any other line: ignore
103
+ i += 1
104
+ if not saw_hunk:
105
+ raise EditError("no @@ hunk header found in patch")
106
+ result.extend(original[src_idx:])
107
+ return "\n".join(result)
tools/coding/search.py ADDED
@@ -0,0 +1,113 @@
1
+ """Pure grep/search logic for the coding tool (C2).
2
+
3
+ Python ``re`` walk over a directory tree, gitignore-aware, glob-scoped, bounded.
4
+ No subprocess here so the behaviour is deterministic and unit-testable; the tool
5
+ layer MAY prefer a ``ripgrep`` subprocess as a speed optimisation but this Python
6
+ walk is the correctness SSOT and fallback.
7
+
8
+ Gitignore support is a pragmatic subset: it always skips VCS/build noise
9
+ (``.git``/``__pycache__``/``node_modules``/``venv``/``.venv``/``.mypy_cache``)
10
+ and honours the *literal* entries + simple ``dir/`` and ``*.ext`` globs found in a
11
+ root-level ``.gitignore``. It is NOT a full gitignore implementation (no nested
12
+ ignore files, no negation) — documented intentionally.
13
+ """
14
+ import fnmatch
15
+ import os
16
+ import re
17
+ from dataclasses import dataclass
18
+
19
+ _ALWAYS_SKIP_DIRS = {
20
+ ".git", "__pycache__", "node_modules", "venv", ".venv",
21
+ ".mypy_cache", ".pytest_cache", ".ruff_cache", ".tox",
22
+ }
23
+
24
+
25
+ @dataclass
26
+ class Match:
27
+ path: str
28
+ line_no: int
29
+ line: str
30
+
31
+
32
+ def _load_gitignore(root):
33
+ """Return (dir_names, glob_patterns) parsed from a root-level .gitignore."""
34
+ dir_names, globs = set(), []
35
+ gi = os.path.join(root, ".gitignore")
36
+ if not os.path.isfile(gi):
37
+ return dir_names, globs
38
+ try:
39
+ for raw in open(gi, "r", encoding="utf-8", errors="ignore"):
40
+ line = raw.strip()
41
+ if not line or line.startswith("#") or line.startswith("!"):
42
+ continue
43
+ line = line.rstrip("/").lstrip("/") if line.endswith("/") else line.lstrip("/")
44
+ if "/" in line:
45
+ # only support a simple leading "dir/..." -> ignore the top dir segment
46
+ line = line.split("/", 1)[0]
47
+ if any(ch in line for ch in "*?["):
48
+ globs.append(line)
49
+ else:
50
+ dir_names.add(line)
51
+ except OSError:
52
+ pass
53
+ return dir_names, globs
54
+
55
+
56
+ def _is_ignored(name, ignored_dirs, ignored_globs):
57
+ if name in ignored_dirs:
58
+ return True
59
+ return any(fnmatch.fnmatch(name, g) for g in ignored_globs)
60
+
61
+
62
+ def search_files(
63
+ root,
64
+ pattern,
65
+ *,
66
+ glob=None,
67
+ output_mode="content",
68
+ max_results=200,
69
+ respect_gitignore=True,
70
+ ):
71
+ """Search ``root`` recursively for ``pattern`` (regex).
72
+
73
+ output_mode="content" -> list[Match]; "files" -> list[str] of unique paths.
74
+ Bounded by ``max_results``. Binary/undecodable files are skipped.
75
+ """
76
+ rx = re.compile(pattern)
77
+ ignored_dirs, ignored_globs = (
78
+ _load_gitignore(root) if respect_gitignore else (set(), [])
79
+ )
80
+ matches = []
81
+ seen_files = []
82
+ seen_set = set()
83
+
84
+ for dirpath, dirnames, filenames in os.walk(root):
85
+ # prune ignored / noise directories in-place
86
+ dirnames[:] = [
87
+ d for d in dirnames
88
+ if d not in _ALWAYS_SKIP_DIRS
89
+ and not (respect_gitignore and _is_ignored(d, ignored_dirs, ignored_globs))
90
+ ]
91
+ for fn in filenames:
92
+ if respect_gitignore and _is_ignored(fn, ignored_dirs, ignored_globs):
93
+ continue
94
+ if glob and not fnmatch.fnmatch(fn, glob):
95
+ continue
96
+ full = os.path.join(dirpath, fn)
97
+ try:
98
+ with open(full, "r", encoding="utf-8") as f:
99
+ for i, line in enumerate(f, start=1):
100
+ if rx.search(line):
101
+ if output_mode == "files":
102
+ if full not in seen_set:
103
+ seen_set.add(full)
104
+ seen_files.append(full)
105
+ if len(seen_files) >= max_results:
106
+ return seen_files
107
+ break
108
+ matches.append(Match(path=full, line_no=i, line=line.rstrip("\n")))
109
+ if len(matches) >= max_results:
110
+ return matches
111
+ except (OSError, UnicodeDecodeError):
112
+ continue
113
+ return seen_files if output_mode == "files" else matches
tools/coding/tool.py ADDED
@@ -0,0 +1,353 @@
1
+ """``coding`` tool (H10-B / H9 SPEC §4): str_replace / grep / run_tests.
2
+
3
+ A minimal single-user coding surface. Edits are confined to the session workspace
4
+ (or CWD) root; ``str_replace`` is exact + unique-or-fail; ``grep`` is the pure
5
+ gitignore-aware search; ``run_tests`` routes through the existing code_exec backend
6
+ (inherits its pgroup-kill timeout + output cap + secret-free env).
7
+
8
+ LANDMINE: NO ``from __future__ import annotations`` in this module — it holds the
9
+ action closures whose param models the registry resolves.
10
+ """
11
+ import asyncio
12
+ import logging
13
+ import os
14
+ from pathlib import Path
15
+ from typing import Optional
16
+
17
+ from pydantic import BaseModel, Field
18
+
19
+ from tools.base_tool import BaseTool
20
+ from tools.coding.edit import apply_str_replace, apply_patch, EditError
21
+ from tools.coding.search import search_files
22
+
23
+
24
+ class CodingError(Exception):
25
+ """Raised for confinement / IO errors at the tool boundary."""
26
+
27
+
28
+ class StrReplaceParams(BaseModel):
29
+ file_path: str = Field(..., description="Path to the file to edit (relative to the workspace, or absolute within it)")
30
+ old_string: str = Field(..., description="Exact text to replace; must be unique unless replace_all=true")
31
+ new_string: str = Field(..., description="Replacement text")
32
+ replace_all: bool = Field(False, description="Replace every occurrence instead of failing on ambiguity")
33
+
34
+
35
+ class ApplyPatchParams(BaseModel):
36
+ file_path: str = Field(..., description="Path to the file to patch (relative to the workspace, or absolute within it)")
37
+ patch: str = Field(..., description="Unified diff for this one file (@@ hunks). Context/removed lines must match exactly.")
38
+
39
+
40
+ class GrepParams(BaseModel):
41
+ pattern: str = Field(..., description="Regex to search for")
42
+ path: Optional[str] = Field(None, description="Subdir (relative to workspace) to search; defaults to the whole workspace")
43
+ glob: Optional[str] = Field(None, description="Filename glob filter, e.g. '*.py'")
44
+ output_mode: str = Field("content", description="'content' (path:line:text) or 'files' (matching paths only)")
45
+
46
+
47
+ class RunTestsParams(BaseModel):
48
+ command: Optional[str] = Field(None, description="Test command to run; defaults to 'pytest -q'")
49
+ timeout: Optional[float] = Field(None, description="Wall-clock seconds (clamped by the code_exec backend)")
50
+
51
+
52
+ class CreateFileParams(BaseModel):
53
+ file_path: str = Field(..., description="Path of the new file (relative to the workspace)")
54
+ content: str = Field("", description="Initial file content")
55
+ overwrite: bool = Field(False, description="Allow overwriting an existing file")
56
+
57
+
58
+ class MoveFileParams(BaseModel):
59
+ src_path: str = Field(..., description="Existing path to move (relative to the workspace)")
60
+ dest_path: str = Field(..., description="Destination path (relative to the workspace)")
61
+ overwrite: bool = Field(False, description="Allow overwriting an existing destination")
62
+
63
+
64
+ class DeleteFileParams(BaseModel):
65
+ file_path: str = Field(..., description="Path to delete (relative to the workspace)")
66
+
67
+
68
+ class CodingTool(BaseTool):
69
+ """Edit/search/test the workspace. Gated by CODING_TOOLS_ENABLED."""
70
+
71
+ def __init__(self, name: str = "coding", config=None, container=None):
72
+ super().__init__(name=name, config=config, container=container)
73
+ self._backend = None
74
+ # P1-B F7b: mirrors CodeExecutionTool._get_backend's session-scoped cache
75
+ # (see _get_code_exec_backend below) — a PERSISTENT backend is bound to
76
+ # ONE session's container and must never be shared across sessions the
77
+ # way `self._backend` shares the ephemeral one.
78
+ self._persistent_backends: dict = {}
79
+ self._persistent_lock = asyncio.Lock()
80
+ self._root_override = None
81
+
82
+ # --- root + confinement --------------------------------------------------
83
+
84
+ def _resolve_root(self, execution_context=None) -> str:
85
+ if self._root_override:
86
+ return os.path.abspath(self._root_override)
87
+ try:
88
+ sid = getattr(execution_context, "session_id", None) or getattr(self, "session_id", None)
89
+ if sid:
90
+ from agents.task.path import pm
91
+ return str(pm().get_workspace_dir(sid))
92
+ except Exception:
93
+ pass
94
+ return os.getcwd()
95
+
96
+ def _confine(self, file_path: str, root: str) -> str:
97
+ target = os.path.abspath(os.path.join(root, file_path))
98
+ # Confine on realpath (both sides) so an in-root symlink can't redirect a
99
+ # write outside; operate on the lexical target so a legitimate in-root
100
+ # symlink keeps its semantics. Reuses the shared helper (single source).
101
+ from core.path_safety import is_within_root
102
+ if not is_within_root(target, root):
103
+ raise CodingError(f"path escapes the workspace root: {file_path}")
104
+ # Secret-content guard: refuse credential-shaped targets (.env*, *.pem,
105
+ # config/.env.*, …) even when in-root. Under POLYROB_LOCAL the workspace is
106
+ # the project cwd, so confinement alone can't stop a coding action from
107
+ # reading/rewriting a config/.env.production that lives in the project.
108
+ from agents.task.agent.core.secret_guard import is_credential_file
109
+ if is_credential_file(Path(target)):
110
+ raise CodingError(f"refusing to touch a credential/secret file: {file_path}")
111
+ return target
112
+
113
+ # --- code_exec backend resolution (P1-B F7b) ------------------------------
114
+
115
+ async def _get_code_exec_backend(self, execution_context=None, dev_mode: bool = False):
116
+ """Resolve the code_exec backend for ``run_tests``.
117
+
118
+ PERSISTENT (opt-in, P1-B F7b): when ``CODE_EXEC_DOCKER_PERSISTENT`` is on
119
+ AND ``execution_context`` carries a truthy ``session_id``, resolve via
120
+ ``resolve_backend(session_id=sid)`` and cache it PER SESSION — the
121
+ container is created once (one ``setup()`` call) and reused for every
122
+ later ``run_tests`` call in that session. Mirrors
123
+ ``tools.code_exec.tool.CodeExecutionTool._get_backend``.
124
+
125
+ EPHEMERAL (default, byte-for-byte unchanged): flag off, or no
126
+ session_id — one process-wide, session-less backend cached on
127
+ ``self._backend``, exactly as before this change.
128
+
129
+ ``dev_mode`` (WS-1): a posture-entitled call caches a DEV persistent
130
+ backend under ``(sid, True)`` (writable ``/install`` mounted at setup) so
131
+ a pytest installed by ``run_code(packages=[...])`` is importable here.
132
+ Non-dev keeps the legacy ``resolve_backend(session_id=sid)`` call shape.
133
+ """
134
+ from tools.code_exec import resolve_backend, code_exec_docker_persistent_enabled
135
+
136
+ sid = None
137
+ if code_exec_docker_persistent_enabled():
138
+ sid = getattr(execution_context, "session_id", None) or None
139
+
140
+ if sid:
141
+ key = (sid, bool(dev_mode))
142
+ cached = self._persistent_backends.get(key)
143
+ if cached is not None:
144
+ return cached
145
+ async with self._persistent_lock:
146
+ cached = self._persistent_backends.get(key) # re-check: lost the race?
147
+ if cached is None:
148
+ if dev_mode:
149
+ cached = resolve_backend(session_id=sid, dev_mode=True)
150
+ else:
151
+ cached = resolve_backend(session_id=sid)
152
+ await cached.setup()
153
+ self._persistent_backends[key] = cached
154
+ return cached
155
+
156
+ if self._backend is None:
157
+ backend = resolve_backend()
158
+ await backend.setup()
159
+ self._backend = backend
160
+ return self._backend
161
+
162
+ @staticmethod
163
+ def _ok(content):
164
+ from tools.controller.types import ActionResult
165
+ return ActionResult(extracted_content=content)
166
+
167
+ @staticmethod
168
+ def _err(msg):
169
+ from tools.controller.types import ActionResult
170
+ return ActionResult(error=msg)
171
+
172
+ # --- actions -------------------------------------------------------------
173
+
174
+ @BaseTool.action(
175
+ "Replace an exact, unique string in a file (fails loudly on 0 or >1 matches unless replace_all)",
176
+ param_model=StrReplaceParams,
177
+ )
178
+ async def str_replace(self, params: StrReplaceParams, execution_context=None):
179
+ try:
180
+ root = self._resolve_root(execution_context)
181
+ target = self._confine(params.file_path, root)
182
+ if not os.path.isfile(target):
183
+ return self._err(f"file not found: {params.file_path}")
184
+ with open(target, "r", encoding="utf-8") as f:
185
+ content = f.read()
186
+ try:
187
+ updated = apply_str_replace(
188
+ content, params.old_string, params.new_string, params.replace_all
189
+ )
190
+ except EditError as e:
191
+ return self._err(str(e))
192
+ with open(target, "w", encoding="utf-8") as f:
193
+ f.write(updated)
194
+ n = content.count(params.old_string) if params.replace_all else 1
195
+ return self._ok(f"Edited {params.file_path} ({n} replacement{'s' if n != 1 else ''}).")
196
+ except CodingError as e:
197
+ return self._err(str(e))
198
+ except Exception as e:
199
+ getattr(self, "logger", logging.getLogger(__name__)).error(f"str_replace failed: {e}")
200
+ return self._err(f"str_replace failed: {e}")
201
+
202
+ @BaseTool.action(
203
+ "Apply a unified-diff patch to one file (reject-on-context-mismatch; fails loudly if context doesn't match)",
204
+ param_model=ApplyPatchParams,
205
+ )
206
+ async def apply_patch(self, params: ApplyPatchParams, execution_context=None):
207
+ try:
208
+ root = self._resolve_root(execution_context)
209
+ target = self._confine(params.file_path, root)
210
+ if not os.path.isfile(target):
211
+ return self._err(f"file not found: {params.file_path}")
212
+ with open(target, "r", encoding="utf-8") as f:
213
+ content = f.read()
214
+ try:
215
+ updated = apply_patch(content, params.patch)
216
+ except EditError as e:
217
+ return self._err(str(e))
218
+ with open(target, "w", encoding="utf-8") as f:
219
+ f.write(updated)
220
+ return self._ok(f"Patched {params.file_path}.")
221
+ except CodingError as e:
222
+ return self._err(str(e))
223
+ except Exception as e:
224
+ getattr(self, "logger", logging.getLogger(__name__)).error(f"apply_patch failed: {e}")
225
+ return self._err(f"apply_patch failed: {e}")
226
+
227
+ @BaseTool.action(
228
+ "Search files for a regex (gitignore-aware); output_mode 'content' or 'files'",
229
+ param_model=GrepParams,
230
+ )
231
+ async def grep(self, params: GrepParams, execution_context=None):
232
+ try:
233
+ root = self._resolve_root(execution_context)
234
+ search_root = self._confine(params.path, root) if params.path else root
235
+ hits = search_files(
236
+ search_root, params.pattern, glob=params.glob, output_mode=params.output_mode,
237
+ )
238
+ if not hits:
239
+ return self._ok("(no matches)")
240
+ if params.output_mode == "files":
241
+ rels = [os.path.relpath(p, root) for p in hits]
242
+ return self._ok("\n".join(rels))
243
+ lines = [f"{os.path.relpath(h.path, root)}:{h.line_no}:{h.line}" for h in hits]
244
+ return self._ok("\n".join(lines))
245
+ except CodingError as e:
246
+ return self._err(str(e))
247
+ except Exception as e:
248
+ getattr(self, "logger", logging.getLogger(__name__)).error(f"grep failed: {e}")
249
+ return self._err(f"grep failed: {e}")
250
+
251
+ @BaseTool.action(
252
+ "Run the test suite (default 'pytest -q') in the workspace via the code_exec backend "
253
+ "(sandbox-gated on servers; local convenience under POLYROB_LOCAL)",
254
+ param_model=RunTestsParams,
255
+ )
256
+ async def run_tests(self, params: RunTestsParams, execution_context=None):
257
+ from tools.code_exec.sandbox_guard import code_exec_execution_blocked_reason
258
+ blocked = code_exec_execution_blocked_reason()
259
+ if blocked:
260
+ return self._err(blocked)
261
+ try:
262
+ from tools.code_exec.result import ExecutionRequest
263
+
264
+ root = self._resolve_root(execution_context)
265
+ command = params.command or "pytest -q"
266
+ # WS-1: an entitled session runs importable-mode (PYTHONPATH=/install)
267
+ # so packages installed via run_code(packages=[...]) are visible here.
268
+ try:
269
+ from agents.task.constants import compute_posture_allows
270
+ dev_mode = bool(compute_posture_allows(execution_context, 1))
271
+ except Exception:
272
+ dev_mode = False
273
+ backend = await self._get_code_exec_backend(execution_context, dev_mode=dev_mode)
274
+ req = ExecutionRequest(
275
+ language="bash", code=command, timeout=params.timeout, workdir=root,
276
+ dev_mode=dev_mode,
277
+ )
278
+ result = await backend.run(req)
279
+ parts = []
280
+ if result.stdout:
281
+ parts.append(result.stdout)
282
+ if result.stderr:
283
+ parts.append(f"[stderr]\n{result.stderr}")
284
+ if result.truncated:
285
+ parts.append("[output truncated]")
286
+ content = "\n".join(parts) if parts else "(no output)"
287
+ if result.timed_out:
288
+ return self._err(f"tests timed out after {result.duration_sec:.1f}s\n{content}")
289
+ if result.exit_code not in (0, None):
290
+ return self._err(f"tests failed (exit {result.exit_code})\n{content}")
291
+ return self._ok(content)
292
+ except Exception as e:
293
+ getattr(self, "logger", logging.getLogger(__name__)).error(f"run_tests failed: {e}")
294
+ return self._err(f"run_tests failed: {e}")
295
+
296
+ @BaseTool.action(
297
+ "Create a new file in the workspace (fails if it exists unless overwrite=true)",
298
+ param_model=CreateFileParams,
299
+ )
300
+ async def create_file(self, params: CreateFileParams, execution_context=None):
301
+ try:
302
+ root = self._resolve_root(execution_context)
303
+ target = self._confine(params.file_path, root)
304
+ if os.path.isdir(target):
305
+ return self._err(f"path is a directory: {params.file_path}")
306
+ if os.path.exists(target) and not params.overwrite:
307
+ return self._err(f"file already exists: {params.file_path} (set overwrite=true)")
308
+ os.makedirs(os.path.dirname(target) or root, exist_ok=True)
309
+ with open(target, "w", encoding="utf-8") as f:
310
+ f.write(params.content or "")
311
+ return self._ok(f"Created {params.file_path} ({len(params.content or '')} bytes).")
312
+ except CodingError as e:
313
+ return self._err(str(e))
314
+ except Exception as e:
315
+ getattr(self, "logger", logging.getLogger(__name__)).error(f"create_file failed: {e}")
316
+ return self._err(f"create_file failed: {e}")
317
+
318
+ @BaseTool.action("Move/rename a file within the workspace", param_model=MoveFileParams)
319
+ async def move_file(self, params: MoveFileParams, execution_context=None):
320
+ try:
321
+ root = self._resolve_root(execution_context)
322
+ src = self._confine(params.src_path, root)
323
+ dest = self._confine(params.dest_path, root)
324
+ if not os.path.exists(src):
325
+ return self._err(f"source not found: {params.src_path}")
326
+ if os.path.exists(dest) and not params.overwrite:
327
+ return self._err(f"destination exists: {params.dest_path} (set overwrite=true)")
328
+ os.makedirs(os.path.dirname(dest) or root, exist_ok=True)
329
+ import shutil as _shutil
330
+ _shutil.move(src, dest)
331
+ return self._ok(f"Moved {params.src_path} -> {params.dest_path}.")
332
+ except CodingError as e:
333
+ return self._err(str(e))
334
+ except Exception as e:
335
+ getattr(self, "logger", logging.getLogger(__name__)).error(f"move_file failed: {e}")
336
+ return self._err(f"move_file failed: {e}")
337
+
338
+ @BaseTool.action("Delete a file within the workspace", param_model=DeleteFileParams)
339
+ async def delete_file(self, params: DeleteFileParams, execution_context=None):
340
+ try:
341
+ root = self._resolve_root(execution_context)
342
+ target = self._confine(params.file_path, root)
343
+ if not os.path.exists(target):
344
+ return self._err(f"file not found: {params.file_path}")
345
+ if os.path.isdir(target):
346
+ return self._err(f"refusing to delete a directory: {params.file_path}")
347
+ os.remove(target)
348
+ return self._ok(f"Deleted {params.file_path}.")
349
+ except CodingError as e:
350
+ return self._err(str(e))
351
+ except Exception as e:
352
+ getattr(self, "logger", logging.getLogger(__name__)).error(f"delete_file failed: {e}")
353
+ return self._err(f"delete_file failed: {e}")
@@ -0,0 +1,5 @@
1
+ """Collab.Land tool package."""
2
+
3
+ from .collabland_tool import CollabLandTool
4
+
5
+ __all__ = ["CollabLandTool"]