polyrob 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (745) hide show
  1. agents/README.md +453 -0
  2. agents/__init__.py +164 -0
  3. agents/base_agent.py +743 -0
  4. agents/personality/character.py +119 -0
  5. agents/personality/character_manager.py +309 -0
  6. agents/personality/characters/rob.character.json +56 -0
  7. agents/personality/characters/trump.character.json +63 -0
  8. agents/personality/persona_render.py +78 -0
  9. agents/personality/persona_resolver.py +115 -0
  10. agents/prompt/__init__.py +58 -0
  11. agents/prompt/base_prompt.py +92 -0
  12. agents/prompt/system.py +417 -0
  13. agents/task/README_PATH_MANAGEMENT.md +141 -0
  14. agents/task/__init__.py +162 -0
  15. agents/task/agent/__init__.py +70 -0
  16. agents/task/agent/agent_state.py +408 -0
  17. agents/task/agent/async_delegation.py +274 -0
  18. agents/task/agent/autonomy_state.py +275 -0
  19. agents/task/agent/conversation.py +75 -0
  20. agents/task/agent/core/__init__.py +6 -0
  21. agents/task/agent/core/aux_metering.py +61 -0
  22. agents/task/agent/core/background_review.py +157 -0
  23. agents/task/agent/core/construction.py +1179 -0
  24. agents/task/agent/core/conversational_exit.py +49 -0
  25. agents/task/agent/core/correspondent_gate.py +227 -0
  26. agents/task/agent/core/curator.py +246 -0
  27. agents/task/agent/core/episodic_digest.py +103 -0
  28. agents/task/agent/core/error_recovery.py +480 -0
  29. agents/task/agent/core/history_io.py +257 -0
  30. agents/task/agent/core/llm_provisioning.py +287 -0
  31. agents/task/agent/core/llm_runner.py +547 -0
  32. agents/task/agent/core/logging_io.py +276 -0
  33. agents/task/agent/core/loop_detection.py +262 -0
  34. agents/task/agent/core/memory_prefetch.py +325 -0
  35. agents/task/agent/core/memory_writer.py +667 -0
  36. agents/task/agent/core/model_introspection.py +82 -0
  37. agents/task/agent/core/model_swap.py +177 -0
  38. agents/task/agent/core/next_action_internal.py +1331 -0
  39. agents/task/agent/core/output_validation.py +303 -0
  40. agents/task/agent/core/project_context.py +272 -0
  41. agents/task/agent/core/resources.py +93 -0
  42. agents/task/agent/core/result_processing.py +389 -0
  43. agents/task/agent/core/run_loop.py +661 -0
  44. agents/task/agent/core/safety_lifecycle.py +228 -0
  45. agents/task/agent/core/secret_guard.py +293 -0
  46. agents/task/agent/core/self_wake.py +280 -0
  47. agents/task/agent/core/session_metadata.py +104 -0
  48. agents/task/agent/core/step.py +781 -0
  49. agents/task/agent/core/step_execution.py +398 -0
  50. agents/task/agent/core/step_telemetry.py +222 -0
  51. agents/task/agent/core/test_aux_metering.py +50 -0
  52. agents/task/agent/core/test_billing_failover.py +68 -0
  53. agents/task/agent/core/test_fatal_error_classifier.py +38 -0
  54. agents/task/agent/core/test_judge_validation.py +275 -0
  55. agents/task/agent/core/test_model_swap.py +190 -0
  56. agents/task/agent/core/test_reflection_offload.py +54 -0
  57. agents/task/agent/core/turn_input.py +8 -0
  58. agents/task/agent/core/untrusted_wrap.py +103 -0
  59. agents/task/agent/core/user_ingress.py +228 -0
  60. agents/task/agent/hitl_manager.py +396 -0
  61. agents/task/agent/log_sanitize.py +36 -0
  62. agents/task/agent/message_manager/config.py +97 -0
  63. agents/task/agent/message_manager/service.py +758 -0
  64. agents/task/agent/message_manager/tests.py +305 -0
  65. agents/task/agent/message_manager/tool_call_builder.py +539 -0
  66. agents/task/agent/message_manager/tool_message_repair.py +364 -0
  67. agents/task/agent/message_manager/views.py +143 -0
  68. agents/task/agent/messages/__init__.py +8 -0
  69. agents/task/agent/messages/builders.py +309 -0
  70. agents/task/agent/messages/compactor.py +611 -0
  71. agents/task/agent/messages/context_references.py +271 -0
  72. agents/task/agent/messages/filters.py +333 -0
  73. agents/task/agent/messages/guidance.py +328 -0
  74. agents/task/agent/messages/persistence.py +448 -0
  75. agents/task/agent/messages/retrieval.py +506 -0
  76. agents/task/agent/messages/sqlite_persistence.py +85 -0
  77. agents/task/agent/messages/test_compaction_upgrade.py +349 -0
  78. agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
  79. agents/task/agent/messages/test_sqlite_persistence.py +54 -0
  80. agents/task/agent/messages/token_counter.py +532 -0
  81. agents/task/agent/orchestrator.py +776 -0
  82. agents/task/agent/profile_manager.py +105 -0
  83. agents/task/agent/profile_registry.py +173 -0
  84. agents/task/agent/prompts.py +1090 -0
  85. agents/task/agent/scenario_registry.py +226 -0
  86. agents/task/agent/service.py +509 -0
  87. agents/task/agent/session.py +984 -0
  88. agents/task/agent/skill_discovery.py +110 -0
  89. agents/task/agent/skill_frontmatter.py +94 -0
  90. agents/task/agent/skill_manager.py +1201 -0
  91. agents/task/agent/skill_store.py +627 -0
  92. agents/task/agent/skill_validation.py +56 -0
  93. agents/task/agent/skill_writer.py +492 -0
  94. agents/task/agent/sub_agent_manager.py +1137 -0
  95. agents/task/agent/test_session_disk_recovery.py +34 -0
  96. agents/task/agent/tests.py +513 -0
  97. agents/task/agent/tool_call_tracker.py +511 -0
  98. agents/task/agent/views.py +469 -0
  99. agents/task/config.py +432 -0
  100. agents/task/constants.py +1384 -0
  101. agents/task/flag_defaults.py +63 -0
  102. agents/task/goals/__init__.py +1 -0
  103. agents/task/goals/autonomy_marker.py +28 -0
  104. agents/task/goals/board.py +690 -0
  105. agents/task/goals/completion_judge.py +247 -0
  106. agents/task/goals/context.py +62 -0
  107. agents/task/goals/dispatcher.py +848 -0
  108. agents/task/goals/escalation.py +102 -0
  109. agents/task/goals/planner.py +114 -0
  110. agents/task/logging_config.py +155 -0
  111. agents/task/path.py +991 -0
  112. agents/task/robust_parse_config.py +400 -0
  113. agents/task/runtime/__init__.py +0 -0
  114. agents/task/runtime/run_as_session.py +130 -0
  115. agents/task/runtime_safety.py +308 -0
  116. agents/task/session/__init__.py +5 -0
  117. agents/task/session/browser_pool.py +130 -0
  118. agents/task/session/cleanup.py +612 -0
  119. agents/task/session/execution.py +397 -0
  120. agents/task/session/feed.py +200 -0
  121. agents/task/session/hitl_ingress.py +201 -0
  122. agents/task/session/hooks.py +83 -0
  123. agents/task/session/multi_agent.py +243 -0
  124. agents/task/session/workspace.py +91 -0
  125. agents/task/session_registry.py +91 -0
  126. agents/task/session_route.py +35 -0
  127. agents/task/sqlite_session_registry.py +261 -0
  128. agents/task/surface_config.py +185 -0
  129. agents/task/telemetry/__init__.py +58 -0
  130. agents/task/telemetry/event_log.py +180 -0
  131. agents/task/telemetry/formatters.py +642 -0
  132. agents/task/telemetry/manager.py +571 -0
  133. agents/task/telemetry/memory_events.py +66 -0
  134. agents/task/telemetry/self_events.py +53 -0
  135. agents/task/telemetry/sequence.py +160 -0
  136. agents/task/telemetry/service.py +1265 -0
  137. agents/task/telemetry/views.py +882 -0
  138. agents/task/templates.py +98 -0
  139. agents/task/test_aux_chain.py +151 -0
  140. agents/task/test_aux_router.py +41 -0
  141. agents/task/tests/test_parse_robustness.py +170 -0
  142. agents/task/tool_defaults.py +110 -0
  143. agents/task/utils.py +762 -0
  144. agents/task/utils_json.py +768 -0
  145. agents/task/utils_webview.py +86 -0
  146. agents/task/workspace_context.py +229 -0
  147. agents/task_agent_lite.py +2398 -0
  148. api/README.md +683 -0
  149. api/TASK_API_DOCS.md +151 -0
  150. api/__init__.py +0 -0
  151. api/a2a/__init__.py +37 -0
  152. api/a2a/agent_card.py +337 -0
  153. api/a2a/client.py +657 -0
  154. api/a2a/endpoints.py +375 -0
  155. api/a2a/models.py +268 -0
  156. api/a2a/streaming.py +419 -0
  157. api/a2a/task_handler.py +929 -0
  158. api/admin_endpoints.py +1584 -0
  159. api/app.py +923 -0
  160. api/auth_constants.py +28 -0
  161. api/auth_endpoints.py +402 -0
  162. api/auth_state.py +36 -0
  163. api/chat_via_task.py +42 -0
  164. api/conversation_manager.py +231 -0
  165. api/dependencies.py +186 -0
  166. api/eip8004_endpoints.py +452 -0
  167. api/hyperliquid_models.py +174 -0
  168. api/hyperliquid_routes.py +450 -0
  169. api/interfaces.py +207 -0
  170. api/jwt_middleware.py +83 -0
  171. api/kb/__init__.py +1 -0
  172. api/kb/endpoints.py +314 -0
  173. api/mcp_models.py +279 -0
  174. api/mcp_routes.py +438 -0
  175. api/middleware.py +526 -0
  176. api/models.py +176 -0
  177. api/openai_compat/__init__.py +0 -0
  178. api/openai_compat/model_map.py +25 -0
  179. api/openai_compat/models.py +59 -0
  180. api/openai_compat/router.py +140 -0
  181. api/payment_endpoints.py +444 -0
  182. api/payment_verification.py +138 -0
  183. api/polymarket_models.py +155 -0
  184. api/polymarket_routes.py +501 -0
  185. api/pricing_endpoints.py +89 -0
  186. api/session_routing.py +54 -0
  187. api/skill_endpoints.py +453 -0
  188. api/task_http_api.py +1856 -0
  189. api/webhooks.py +52 -0
  190. api/x402_endpoints.py +280 -0
  191. avatar/README.md +44 -0
  192. avatar/__init__.py +1 -0
  193. avatar/config/rob.json +7 -0
  194. avatar/mindprint.js +395 -0
  195. avatar/renders/rob.meta.json +20 -0
  196. avatar/renders/rob.png +0 -0
  197. avatar/studio.html +373 -0
  198. avatar/webview/avatar-live.js +44 -0
  199. cli/README.md +89 -0
  200. cli/__init__.py +0 -0
  201. cli/cli_surface.py +67 -0
  202. cli/commands/__init__.py +0 -0
  203. cli/commands/_bootstrap.py +49 -0
  204. cli/commands/_errors.py +53 -0
  205. cli/commands/chat.py +973 -0
  206. cli/commands/config.py +100 -0
  207. cli/commands/dashboard.py +103 -0
  208. cli/commands/doctor.py +222 -0
  209. cli/commands/email.py +174 -0
  210. cli/commands/gateway.py +346 -0
  211. cli/commands/goals.py +509 -0
  212. cli/commands/init.py +276 -0
  213. cli/commands/journey.py +30 -0
  214. cli/commands/kb.py +280 -0
  215. cli/commands/model.py +107 -0
  216. cli/commands/owner.py +431 -0
  217. cli/commands/pfp.py +232 -0
  218. cli/commands/run.py +414 -0
  219. cli/commands/serve.py +41 -0
  220. cli/commands/session.py +684 -0
  221. cli/commands/skill_install.py +786 -0
  222. cli/commands/skills.py +128 -0
  223. cli/commands/subagents.py +100 -0
  224. cli/commands/surface.py +81 -0
  225. cli/commands/telegram.py +185 -0
  226. cli/commands/todos.py +209 -0
  227. cli/commands/tools.py +102 -0
  228. cli/commands/update.py +316 -0
  229. cli/commands/whatsapp.py +175 -0
  230. cli/config_store.py +276 -0
  231. cli/gitignore.py +43 -0
  232. cli/inventory.py +46 -0
  233. cli/keys.py +101 -0
  234. cli/persona.py +17 -0
  235. cli/polyrob.py +137 -0
  236. cli/ui/__init__.py +64 -0
  237. cli/ui/activity.py +141 -0
  238. cli/ui/app.py +538 -0
  239. cli/ui/banner.py +233 -0
  240. cli/ui/blocks.py +534 -0
  241. cli/ui/bootstrap_notice.py +47 -0
  242. cli/ui/commands/__init__.py +41 -0
  243. cli/ui/commands/h_cron.py +64 -0
  244. cli/ui/commands/h_journey.py +164 -0
  245. cli/ui/commands/h_kb.py +121 -0
  246. cli/ui/commands/h_learn.py +100 -0
  247. cli/ui/commands/h_mcp.py +215 -0
  248. cli/ui/commands/h_self.py +113 -0
  249. cli/ui/commands/h_skills.py +314 -0
  250. cli/ui/commands/handlers.py +1672 -0
  251. cli/ui/commands/registry.py +426 -0
  252. cli/ui/dialog.py +425 -0
  253. cli/ui/event_registry.py +110 -0
  254. cli/ui/event_specs.py +45 -0
  255. cli/ui/events.py +433 -0
  256. cli/ui/identity.py +15 -0
  257. cli/ui/lifecycle.py +159 -0
  258. cli/ui/live_hooks.py +43 -0
  259. cli/ui/model_selector.py +532 -0
  260. cli/ui/persistent_loop.py +181 -0
  261. cli/ui/pick.py +113 -0
  262. cli/ui/plain_renderer.py +351 -0
  263. cli/ui/renderer.py +384 -0
  264. cli/ui/rich_renderer.py +533 -0
  265. cli/ui/secrets.py +115 -0
  266. cli/ui/state.py +376 -0
  267. cli/ui/statusbar.py +231 -0
  268. cli/ui/streaming.py +254 -0
  269. cli/ui/terminal_render.py +105 -0
  270. cli/ui/theme.py +132 -0
  271. cli/update/__init__.py +6 -0
  272. cli/update/context.py +121 -0
  273. cli/update/detect.py +176 -0
  274. cli/update/engine.py +95 -0
  275. cli/update/migrate_guarded.py +55 -0
  276. cli/update/process_guard.py +215 -0
  277. cli/update/runners.py +78 -0
  278. cli/update/snapshot.py +270 -0
  279. cli/update/versions.py +205 -0
  280. core/README.md +377 -0
  281. core/__init__.py +174 -0
  282. core/assets.py +87 -0
  283. core/async_bridge.py +76 -0
  284. core/autonomy_runtime.py +259 -0
  285. core/base_component.py +302 -0
  286. core/bootstrap.py +681 -0
  287. core/bot.py +310 -0
  288. core/config.py +833 -0
  289. core/constants.py +126 -0
  290. core/container.py +462 -0
  291. core/db_manifest.py +101 -0
  292. core/embedding.py +57 -0
  293. core/env.py +39 -0
  294. core/exceptions.py +287 -0
  295. core/flags.py +144 -0
  296. core/flags_catalog.py +345 -0
  297. core/home_migration.py +62 -0
  298. core/identity.py +118 -0
  299. core/initialization.py +1004 -0
  300. core/instance.py +537 -0
  301. core/interactive_gate.py +96 -0
  302. core/logging.py +436 -0
  303. core/owner_doc_writer.py +176 -0
  304. core/pairing.py +193 -0
  305. core/path_safety.py +20 -0
  306. core/paths.py +27 -0
  307. core/payment_config.py +21 -0
  308. core/permissions.py +322 -0
  309. core/runtime_config.py +96 -0
  310. core/runtime_paths.py +135 -0
  311. core/seams.py +35 -0
  312. core/secret_scan.py +25 -0
  313. core/secret_scrub.py +71 -0
  314. core/secrets.py +35 -0
  315. core/security_logging_filter.py +148 -0
  316. core/self_context_writer.py +333 -0
  317. core/self_evolution.py +294 -0
  318. core/session_context.py +47 -0
  319. core/sqlite_util.py +59 -0
  320. core/surfaces/__init__.py +16 -0
  321. core/surfaces/access.py +118 -0
  322. core/surfaces/binding.py +88 -0
  323. core/surfaces/bootstrap.py +78 -0
  324. core/surfaces/circuit.py +168 -0
  325. core/surfaces/continuity.py +77 -0
  326. core/surfaces/correspondents.py +228 -0
  327. core/surfaces/dispatcher.py +216 -0
  328. core/surfaces/envelopes.py +75 -0
  329. core/surfaces/gc.py +30 -0
  330. core/surfaces/idempotency.py +57 -0
  331. core/surfaces/inbound_webhook.py +124 -0
  332. core/surfaces/media.py +32 -0
  333. core/surfaces/message_router.py +91 -0
  334. core/surfaces/outbound_allowlist.py +80 -0
  335. core/surfaces/outbound_dispatcher.py +110 -0
  336. core/surfaces/outbound_mirror.py +37 -0
  337. core/surfaces/outbound_queue.py +107 -0
  338. core/surfaces/outbound_target.py +12 -0
  339. core/surfaces/owner_admin.py +29 -0
  340. core/surfaces/proactive.py +78 -0
  341. core/surfaces/progress.py +108 -0
  342. core/surfaces/rate_bucket.py +21 -0
  343. core/surfaces/registry.py +51 -0
  344. core/surfaces/rendering.py +53 -0
  345. core/surfaces/send_policy.py +14 -0
  346. core/surfaces/serialize.py +24 -0
  347. core/surfaces/session_chat_registry.py +112 -0
  348. core/surfaces/session_policy.py +60 -0
  349. core/surfaces/surface.py +288 -0
  350. core/surfaces/transcription.py +88 -0
  351. core/surfaces/voice_echo.py +26 -0
  352. core/surfaces/voice_guard.py +23 -0
  353. core/tickers.py +174 -0
  354. core/tool_catalog.py +192 -0
  355. core/version.py +75 -0
  356. core/wallet/__init__.py +5 -0
  357. core/wallet/agent_wallet.py +59 -0
  358. core/wallet/audit_sink.py +61 -0
  359. core/wallet/config.py +70 -0
  360. core/wallet/factory.py +87 -0
  361. core/wallet/policy.py +104 -0
  362. core/wallet/signer.py +51 -0
  363. cron/README.md +65 -0
  364. cron/__init__.py +0 -0
  365. cron/delivery.py +277 -0
  366. cron/digest.py +124 -0
  367. cron/jobs.py +207 -0
  368. cron/runner.py +297 -0
  369. cron/schedule.py +194 -0
  370. cron/scheduler.py +201 -0
  371. cron/service.py +57 -0
  372. cron/wake_gate.py +223 -0
  373. data/__init__.py +1 -0
  374. data/prompts/autov2_prompts.json +87 -0
  375. data/prompts/skills/browser-automation/SKILL.md +43 -0
  376. data/prompts/skills/coding-workflow/SKILL.md +36 -0
  377. data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
  378. data/prompts/skills/document-writing/SKILL.md +33 -0
  379. data/prompts/skills/email-comms/SKILL.md +33 -0
  380. data/prompts/skills/file-data-ops/SKILL.md +35 -0
  381. data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
  382. data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
  383. data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
  384. data/prompts/skills/lead-research/SKILL.md +86 -0
  385. data/prompts/skills/market-research-brief/SKILL.md +104 -0
  386. data/prompts/skills/person-analyzer/SKILL.md +124 -0
  387. data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
  388. data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
  389. data/prompts/skills/polymarket-trading/SKILL.md +55 -0
  390. data/prompts/skills/presentation-creator/SKILL.md +36 -0
  391. data/prompts/skills/project-analyzer/SKILL.md +169 -0
  392. data/prompts/skills/rules.json +268 -0
  393. data/prompts/skills/secret-handling/SKILL.md +20 -0
  394. data/prompts/skills/skill-authoring/SKILL.md +31 -0
  395. data/prompts/skills/skill-security-review/SKILL.md +27 -0
  396. data/prompts/skills/social-discovery/SKILL.md +57 -0
  397. data/prompts/skills/task-planning/SKILL.md +33 -0
  398. data/prompts/skills/web-research/SKILL.md +39 -0
  399. data/prompts/skills/web-scraping/SKILL.md +73 -0
  400. data/prompts/skills/x-engagement/SKILL.md +69 -0
  401. data/prompts/system_prompts.json +4 -0
  402. data/subscription_channels.json +5 -0
  403. modules/README.md +503 -0
  404. modules/__init__.py +124 -0
  405. modules/auth/__init__.py +13 -0
  406. modules/auth/api_key_manager.py +157 -0
  407. modules/auth/identity_mapper.py +380 -0
  408. modules/auth/siwe_auth.py +273 -0
  409. modules/auth/tier_manager.py +173 -0
  410. modules/base_module.py +190 -0
  411. modules/credits/__init__.py +29 -0
  412. modules/credits/balance_manager.py +237 -0
  413. modules/credits/cost_utils.py +129 -0
  414. modules/credits/pricing.py +139 -0
  415. modules/credits/unified_ledger.py +148 -0
  416. modules/credits/usage_meter.py +310 -0
  417. modules/credits/usage_tracker.py +674 -0
  418. modules/database/__init__.py +33 -0
  419. modules/database/audit_log.py +322 -0
  420. modules/database/auth_tables.py +276 -0
  421. modules/database/connection.py +644 -0
  422. modules/database/connection_pool.py +305 -0
  423. modules/database/conversation_contexts.py +446 -0
  424. modules/database/database_manager.py +237 -0
  425. modules/database/hyperliquid.py +375 -0
  426. modules/database/polymarket.py +574 -0
  427. modules/database/user_mcp_servers.py +728 -0
  428. modules/database/user_profiles.py +562 -0
  429. modules/database/utils.py +107 -0
  430. modules/database/x402_tables.py +92 -0
  431. modules/eip8004/README.md +1069 -0
  432. modules/eip8004/__init__.py +65 -0
  433. modules/eip8004/contracts.py +498 -0
  434. modules/eip8004/models.py +271 -0
  435. modules/eip8004/registration.py +148 -0
  436. modules/eip8004/reputation.py +367 -0
  437. modules/eip8004/validation.py +277 -0
  438. modules/llm/__init__.py +97 -0
  439. modules/llm/adapters.py +963 -0
  440. modules/llm/anthropic_client.py +831 -0
  441. modules/llm/available_models.py +106 -0
  442. modules/llm/brain_scrubber.py +137 -0
  443. modules/llm/cache_hints.py +162 -0
  444. modules/llm/deepseek_client.py +562 -0
  445. modules/llm/gemini_client.py +1367 -0
  446. modules/llm/llm_client.py +1139 -0
  447. modules/llm/llm_client_registry.py +188 -0
  448. modules/llm/llm_factory.py +237 -0
  449. modules/llm/llm_manager.py +1036 -0
  450. modules/llm/messages.py +374 -0
  451. modules/llm/model_registry.py +1935 -0
  452. modules/llm/nvidia_client.py +125 -0
  453. modules/llm/openai_client.py +800 -0
  454. modules/llm/openrouter_client.py +969 -0
  455. modules/llm/profiles.py +178 -0
  456. modules/llm/test_anthropic_cache_capture.py +35 -0
  457. modules/llm/test_cached_pricing_derivation.py +24 -0
  458. modules/llm/think_scrubber.py +444 -0
  459. modules/llm/token_counter.py +268 -0
  460. modules/memory/__init__.py +35 -0
  461. modules/memory/backend_factory.py +83 -0
  462. modules/memory/cache_manager.py +186 -0
  463. modules/memory/episodic.py +108 -0
  464. modules/memory/local_vector_memory_provider.py +552 -0
  465. modules/memory/memory_manager.py +225 -0
  466. modules/memory/models.py +312 -0
  467. modules/memory/provider.py +210 -0
  468. modules/memory/registry.py +205 -0
  469. modules/memory/sqlite_memory_provider.py +767 -0
  470. modules/memory/task/__init__.py +52 -0
  471. modules/memory/task/compaction_manager.py +157 -0
  472. modules/memory/task/context_retriever.py +809 -0
  473. modules/memory/task/hierarchical_memory.py +1002 -0
  474. modules/memory/task/lexical_retriever.py +71 -0
  475. modules/memory/task/null_context_manager.py +213 -0
  476. modules/memory/task/phase_manager.py +625 -0
  477. modules/memory/task/reflection_service.py +157 -0
  478. modules/memory/task/semantic_retriever.py +218 -0
  479. modules/memory/task/task_context_manager.py +1165 -0
  480. modules/memory/task/test_forgetting_engages.py +24 -0
  481. modules/memory/task/test_reflection_llm.py +39 -0
  482. modules/memory/task/test_threat_scan.py +53 -0
  483. modules/memory/task/threat_scan.py +90 -0
  484. modules/memory/test_sqlite_memory_provider.py +106 -0
  485. modules/memory/user_profile_manager.py +527 -0
  486. modules/payments/__init__.py +11 -0
  487. modules/payments/deposit_monitor.py +478 -0
  488. modules/payments/price_oracle.py +70 -0
  489. modules/payments/treasury_sweeper.py +401 -0
  490. modules/payments/wallet_generator.py +145 -0
  491. modules/pfp/__init__.py +1 -0
  492. modules/pfp/config.py +92 -0
  493. modules/pfp/mesh.py +565 -0
  494. modules/pfp/push.py +69 -0
  495. modules/pfp/renderer.py +85 -0
  496. modules/pfp/store.py +82 -0
  497. modules/skills/__init__.py +1 -0
  498. modules/skills/skill_usage.py +248 -0
  499. modules/transcription/__init__.py +24 -0
  500. modules/transcription/base.py +20 -0
  501. modules/transcription/faster_whisper_transcriber.py +55 -0
  502. modules/x402/README.md +249 -0
  503. modules/x402/__init__.py +22 -0
  504. modules/x402/invoicing.py +416 -0
  505. modules/x402/middleware.py +364 -0
  506. modules/x402/settlement_watcher.py +145 -0
  507. modules/x402/x402_integration.py +344 -0
  508. polyrob-0.5.0.dist-info/METADATA +482 -0
  509. polyrob-0.5.0.dist-info/RECORD +745 -0
  510. polyrob-0.5.0.dist-info/WHEEL +5 -0
  511. polyrob-0.5.0.dist-info/entry_points.txt +2 -0
  512. polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
  513. polyrob-0.5.0.dist-info/top_level.txt +12 -0
  514. surfaces/README.md +70 -0
  515. surfaces/__init__.py +0 -0
  516. surfaces/email/__init__.py +0 -0
  517. surfaces/email/dedup.py +44 -0
  518. surfaces/email/harness.py +187 -0
  519. surfaces/email/inbound.py +159 -0
  520. surfaces/email/seed.py +60 -0
  521. surfaces/email/surface.py +105 -0
  522. surfaces/telegram/__init__.py +0 -0
  523. surfaces/telegram/dedup.py +83 -0
  524. surfaces/telegram/harness.py +886 -0
  525. surfaces/telegram/inbound.py +145 -0
  526. surfaces/telegram/interactive_tools.py +49 -0
  527. surfaces/telegram/markdown.py +347 -0
  528. surfaces/telegram/rate_limit.py +167 -0
  529. surfaces/telegram/surface.py +187 -0
  530. surfaces/telegram/voice.py +52 -0
  531. surfaces/whatsapp/__init__.py +0 -0
  532. surfaces/whatsapp/client.py +51 -0
  533. surfaces/whatsapp/harness.py +62 -0
  534. surfaces/whatsapp/inbound.py +132 -0
  535. surfaces/whatsapp/surface.py +64 -0
  536. surfaces/whatsapp/window.py +34 -0
  537. tools/README.md +578 -0
  538. tools/__init__.py +424 -0
  539. tools/alchemy/__init__.py +5 -0
  540. tools/alchemy/alchemy_tool.py +389 -0
  541. tools/anysite/__init__.py +17 -0
  542. tools/anysite/client.py +88 -0
  543. tools/anysite/tool.py +89 -0
  544. tools/base_tool.py +468 -0
  545. tools/browser/__init__.py +22 -0
  546. tools/browser/actions.py +118 -0
  547. tools/browser/browser.py +1457 -0
  548. tools/browser/browser_manager.py +690 -0
  549. tools/browser/context.py +1748 -0
  550. tools/browser/playwright_utils.py +137 -0
  551. tools/browser/views.py +56 -0
  552. tools/code_exec/SANDBOX_SECURITY.md +144 -0
  553. tools/code_exec/__init__.py +178 -0
  554. tools/code_exec/backend.py +73 -0
  555. tools/code_exec/backends/__init__.py +1 -0
  556. tools/code_exec/backends/docker.py +779 -0
  557. tools/code_exec/backends/local_subprocess.py +154 -0
  558. tools/code_exec/env_policy.py +45 -0
  559. tools/code_exec/result.py +42 -0
  560. tools/code_exec/sandbox_guard.py +59 -0
  561. tools/code_exec/tool.py +227 -0
  562. tools/coding/__init__.py +50 -0
  563. tools/coding/edit.py +107 -0
  564. tools/coding/search.py +113 -0
  565. tools/coding/tool.py +353 -0
  566. tools/collabland/__init__.py +5 -0
  567. tools/collabland/collabland_tool.py +471 -0
  568. tools/controller/__init__.py +0 -0
  569. tools/controller/_helpers.py +165 -0
  570. tools/controller/action_registration.py +1765 -0
  571. tools/controller/approval.py +244 -0
  572. tools/controller/approval_interactive.py +90 -0
  573. tools/controller/delegation.py +195 -0
  574. tools/controller/execution.py +797 -0
  575. tools/controller/execution_context.py +141 -0
  576. tools/controller/hooks.py +135 -0
  577. tools/controller/introspection.py +344 -0
  578. tools/controller/mcp_registrar.py +190 -0
  579. tools/controller/message_send.py +31 -0
  580. tools/controller/registry/__init__.py +4 -0
  581. tools/controller/registry/schema_generators.py +395 -0
  582. tools/controller/registry/schema_sanitizer.py +406 -0
  583. tools/controller/registry/service.py +1266 -0
  584. tools/controller/registry/views.py +151 -0
  585. tools/controller/service.py +409 -0
  586. tools/controller/tool_management.py +334 -0
  587. tools/controller/types.py +28 -0
  588. tools/controller/views.py +450 -0
  589. tools/cronjob_tools.py +166 -0
  590. tools/crypto_trade_gate.py +59 -0
  591. tools/descriptors.py +452 -0
  592. tools/dom/__init__.py +0 -0
  593. tools/dom/history_tree_processor/service.py +244 -0
  594. tools/dom/history_tree_processor/view.py +99 -0
  595. tools/dom/service.py +471 -0
  596. tools/dom/views.py +346 -0
  597. tools/email_tool.py +403 -0
  598. tools/exceptions.py +169 -0
  599. tools/filesystem.py +1038 -0
  600. tools/filesystem_docproc.py +331 -0
  601. tools/filesystem_pdf.py +755 -0
  602. tools/git/__init__.py +42 -0
  603. tools/git/tool.py +258 -0
  604. tools/github/__init__.py +33 -0
  605. tools/github/client.py +94 -0
  606. tools/github/tool.py +237 -0
  607. tools/goal_tools.py +309 -0
  608. tools/hyperliquid/__init__.py +56 -0
  609. tools/hyperliquid/models.py +233 -0
  610. tools/hyperliquid/service.py +1680 -0
  611. tools/knowledge_ingest.py +950 -0
  612. tools/mcp/README.md +535 -0
  613. tools/mcp/__init__.py +52 -0
  614. tools/mcp/catalog.py +115 -0
  615. tools/mcp/config.py +337 -0
  616. tools/mcp/mcp_tool.py +1415 -0
  617. tools/mcp/param_coercion.py +289 -0
  618. tools/mcp/protocol.py +1520 -0
  619. tools/mcp/rate_limit.py +50 -0
  620. tools/mcp/security.py +424 -0
  621. tools/mcp/self_install.py +111 -0
  622. tools/mcp/server_manager.py +829 -0
  623. tools/mcp/subscriptions.py +72 -0
  624. tools/mcp/user_mcp_service.py +899 -0
  625. tools/mcp/validation_tracker.py +89 -0
  626. tools/mcp/views.py +346 -0
  627. tools/oauth/__init__.py +17 -0
  628. tools/oauth/manager.py +75 -0
  629. tools/oauth/provider.py +69 -0
  630. tools/oauth/providers/__init__.py +1 -0
  631. tools/oauth/providers/generic_oauth2.py +96 -0
  632. tools/perplexity_tool.py +280 -0
  633. tools/polymarket/__init__.py +41 -0
  634. tools/polymarket/clob_adapter.py +76 -0
  635. tools/polymarket/models.py +182 -0
  636. tools/polymarket/service.py +2082 -0
  637. tools/self_env/__init__.py +61 -0
  638. tools/self_env/tool.py +274 -0
  639. tools/shell/__init__.py +80 -0
  640. tools/shell/backend_pool.py +118 -0
  641. tools/shell/discipline.py +62 -0
  642. tools/shell/executor.py +157 -0
  643. tools/shell/loopback_allow.py +87 -0
  644. tools/shell/process_registry.py +113 -0
  645. tools/shell/process_tool.py +131 -0
  646. tools/shell/state.py +129 -0
  647. tools/shell/tool.py +151 -0
  648. tools/task_tool.py +637 -0
  649. tools/twitter_tool.py +2086 -0
  650. tools/user_directory.py +179 -0
  651. tools/web_fetch/__init__.py +3 -0
  652. tools/web_fetch/fetcher.py +144 -0
  653. tools/web_fetch/render.py +44 -0
  654. tools/web_fetch/tool.py +60 -0
  655. tools/x402/__init__.py +60 -0
  656. tools/x402/client.py +47 -0
  657. tools/x402/invoice_tool.py +143 -0
  658. tools/x402/real_client.py +250 -0
  659. tools/x402/service.py +131 -0
  660. utils/README.md +498 -0
  661. utils/__init__.py +46 -0
  662. utils/auth_utils.py +114 -0
  663. utils/bounded_collections.py +162 -0
  664. utils/circuit_breaker.py +389 -0
  665. utils/gif_utils.py +435 -0
  666. utils/markdown_utils.py +291 -0
  667. utils/message_utils.py +147 -0
  668. utils/metrics.py +36 -0
  669. utils/path_validator.py +227 -0
  670. utils/rate_limit_manager.py +399 -0
  671. utils/result_size.py +296 -0
  672. utils/time_utils.py +206 -0
  673. utils/user_utils.py +188 -0
  674. webview/README.md +111 -0
  675. webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
  676. webview/__init__.py +8 -0
  677. webview/activity.py +636 -0
  678. webview/owner_auth.py +108 -0
  679. webview/pages.py +448 -0
  680. webview/repair_sessions.py +399 -0
  681. webview/server.py +4323 -0
  682. webview/server_launcher.py +112 -0
  683. webview/static/css/activity.css +89 -0
  684. webview/static/css/chat.css +1772 -0
  685. webview/static/css/components.css +1190 -0
  686. webview/static/css/config-panel.css +651 -0
  687. webview/static/css/pages/admin.css +766 -0
  688. webview/static/css/pages/profile.css +231 -0
  689. webview/static/css/pages/settings.css +297 -0
  690. webview/static/css/pages/signin.css +316 -0
  691. webview/static/css/style.css +4250 -0
  692. webview/static/css/variables.css +147 -0
  693. webview/static/css/workspace-fullwidth-fix.css +109 -0
  694. webview/static/img/favicon.ico +0 -0
  695. webview/static/js/activity.js +394 -0
  696. webview/static/js/admin/activity.js +186 -0
  697. webview/static/js/admin/dashboard.js +163 -0
  698. webview/static/js/admin/user_detail.js +400 -0
  699. webview/static/js/admin/users.js +174 -0
  700. webview/static/js/admin/utils.js +214 -0
  701. webview/static/js/chat.js +3178 -0
  702. webview/static/js/config-panel.js +522 -0
  703. webview/static/js/constants.js +20 -0
  704. webview/static/js/error-handler.js +226 -0
  705. webview/static/js/ethers.min.js +1 -0
  706. webview/static/js/event-filter.js +137 -0
  707. webview/static/js/event-store.js +351 -0
  708. webview/static/js/file-attachments.js +374 -0
  709. webview/static/js/file-loader.js +163 -0
  710. webview/static/js/index.js +103 -0
  711. webview/static/js/performance-utils.js +196 -0
  712. webview/static/js/profile.js +396 -0
  713. webview/static/js/screenshot.js +386 -0
  714. webview/static/js/session.js +1527 -0
  715. webview/static/js/settings.js +1288 -0
  716. webview/static/js/sidebar-data.js +766 -0
  717. webview/static/js/sidebar-toggle.js +296 -0
  718. webview/static/js/socket.io.min.js +7 -0
  719. webview/static/js/stats.js +355 -0
  720. webview/static/js/ui-utils.js +420 -0
  721. webview/static/js/workspace.js +966 -0
  722. webview/stats_service.py +613 -0
  723. webview/templates/__init__.py +1 -0
  724. webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  725. webview/templates/activity.html +36 -0
  726. webview/templates/admin/activity.html +122 -0
  727. webview/templates/admin/dashboard.html +112 -0
  728. webview/templates/admin/user_detail.html +228 -0
  729. webview/templates/admin/users.html +105 -0
  730. webview/templates/autonomy.html +60 -0
  731. webview/templates/error.html +133 -0
  732. webview/templates/finance.html +90 -0
  733. webview/templates/identity.html +73 -0
  734. webview/templates/index.html +42 -0
  735. webview/templates/layout.html +125 -0
  736. webview/templates/memory.html +59 -0
  737. webview/templates/owner_login.html +15 -0
  738. webview/templates/profile.html +121 -0
  739. webview/templates/session.html +312 -0
  740. webview/templates/settings.html +534 -0
  741. webview/templates/sidebar.html +112 -0
  742. webview/templates/signin.html +213 -0
  743. webview/templates/status.html +14 -0
  744. webview/templates/system.html +45 -0
  745. webview/webgate.py +196 -0
@@ -0,0 +1,89 @@
1
+ """Pricing transparency API endpoints."""
2
+
3
+ from fastapi import APIRouter
4
+ from typing import Dict, Any
5
+ import logging
6
+
7
+ # Import pricing from credits module (SINGLE SOURCE OF TRUTH)
8
+ from modules.credits.pricing import pricing as _pricing_config
9
+
10
+ logger = logging.getLogger(__name__)
11
+ router = APIRouter(prefix="/pricing", tags=["pricing"])
12
+
13
+
14
+ @router.get("/models")
15
+ async def get_model_pricing():
16
+ """
17
+ Get current model pricing (public endpoint).
18
+
19
+ Shows token-based pricing for all models with examples.
20
+ """
21
+ from modules.llm.model_registry import get_all_models, calculate_cost
22
+
23
+ models_data = []
24
+ all_models = get_all_models(include_deprecated=False)
25
+
26
+ for model in all_models:
27
+ if not model.pricing:
28
+ continue
29
+
30
+ # Calculate example costs
31
+ examples = {}
32
+ for size, (inp, out) in [("small", (1000, 500)), ("medium", (10000, 5000)), ("large", (50000, 20000))]:
33
+ api_cost = calculate_cost(model.name, inp, out)
34
+ credits, user_cost = _pricing_config.calculate_credits_from_api_cost(api_cost)
35
+ examples[size] = {
36
+ "input_tokens": inp,
37
+ "output_tokens": out,
38
+ "api_cost_usd": round(api_cost, 6),
39
+ "credits": credits,
40
+ "user_cost_usd": round(user_cost, 6)
41
+ }
42
+
43
+ models_data.append({
44
+ "name": model.name,
45
+ "provider": model.provider.value,
46
+ "input_price_per_1M": model.pricing.input_price,
47
+ "output_price_per_1M": model.pricing.output_price,
48
+ "cached_price_per_1M": model.pricing.cached_input_price,
49
+ "examples": examples
50
+ })
51
+
52
+ markup_info = _pricing_config.get_markup_info()
53
+ return {
54
+ "pricing_model": "token-based",
55
+ "credit_value_usd": markup_info["credit_value_usd"],
56
+ "markup": markup_info["markup"],
57
+ "markup_percent": markup_info["markup_percentage"],
58
+ "minimum_charge": markup_info["min_charge"],
59
+ "description": markup_info["description"],
60
+ "models": models_data
61
+ }
62
+
63
+
64
+ @router.get("/calculator")
65
+ async def cost_calculator(
66
+ model: str,
67
+ input_tokens: int,
68
+ output_tokens: int,
69
+ cached_tokens: int = 0
70
+ ):
71
+ """Calculate cost for specific usage."""
72
+ from modules.llm.model_registry import calculate_cost
73
+
74
+ try:
75
+ api_cost = calculate_cost(model, input_tokens, output_tokens, cached_tokens)
76
+ credits, user_cost = _pricing_config.calculate_credits_from_api_cost(api_cost)
77
+
78
+ return {
79
+ "model": model,
80
+ "input_tokens": input_tokens,
81
+ "output_tokens": output_tokens,
82
+ "cached_tokens": cached_tokens,
83
+ "api_cost_usd": round(api_cost, 6),
84
+ "markup": _pricing_config.MARKUP,
85
+ "credits": credits,
86
+ "user_cost_usd": round(user_cost, 6)
87
+ }
88
+ except Exception as e:
89
+ return {"error": str(e)}
api/session_routing.py ADDED
@@ -0,0 +1,54 @@
1
+ """Cross-worker session routing → HTTP (Item 6).
2
+
3
+ Translate a ``SessionRoute`` (``agents/task/session_route.py``) into an honest HTTP
4
+ outcome. The point is to stop **false-404ing** a session that simply lives in another
5
+ uvicorn worker: ``REMOTE`` becomes a ``409`` carrying ``owner_pid`` + ``Retry-After``,
6
+ so a sticky load balancer (or a human) can route the request to its owner.
7
+
8
+ With the default in-process registry every route is ``LOCAL`` or ``MISSING``, so
9
+ behaviour is unchanged until ``SESSION_REGISTRY_BACKEND=sqlite`` + ``workers>1``.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, Optional
14
+
15
+ from fastapi import HTTPException
16
+
17
+
18
+ def _remote_409(session_id: str, owner_pid: Optional[int]) -> HTTPException:
19
+ return HTTPException(
20
+ status_code=409,
21
+ detail={
22
+ "session_id": session_id,
23
+ "owner_pid": owner_pid,
24
+ "detail": "session owned by another worker; enable sticky routing",
25
+ },
26
+ headers={"Retry-After": "1"},
27
+ )
28
+
29
+
30
+ def route_to_http(route: Any, session_id: str) -> Any:
31
+ """Full mapping: LOCAL → orchestrator; REMOTE → 409; MISSING → 404.
32
+
33
+ Use at endpoints that REQUIRE a live in-process orchestrator.
34
+ """
35
+ if route.is_local:
36
+ return route.orchestrator
37
+ if route.is_remote:
38
+ raise _remote_409(session_id, route.owner_pid)
39
+ raise HTTPException(status_code=404, detail=f"Session {session_id} not found")
40
+
41
+
42
+ def guard_remote(agent: Any, session_id: str) -> Optional[Any]:
43
+ """Raise 409 iff the session is owned by another worker; else return the route.
44
+
45
+ LOCAL/MISSING pass through unchanged so an endpoint's own (DB-backed / resumable)
46
+ fallback logic still runs. Tolerates a legacy agent without ``route_session``.
47
+ """
48
+ route_fn = getattr(agent, "route_session", None)
49
+ if route_fn is None:
50
+ return None
51
+ route = route_fn(session_id)
52
+ if route is not None and getattr(route, "is_remote", False):
53
+ raise _remote_409(session_id, route.owner_pid)
54
+ return route
api/skill_endpoints.py ADDED
@@ -0,0 +1,453 @@
1
+ """
2
+ User Skills API - CRUD operations for user-created skills.
3
+
4
+ Users can create custom skills that get loaded alongside system skills.
5
+ User skills are stored in: data/prompts/skills/user_{user_id}/
6
+ """
7
+
8
+ import json
9
+ import shutil
10
+ import logging
11
+ from pathlib import Path
12
+ from typing import List, Optional, Dict, Any
13
+
14
+ from fastapi import APIRouter, HTTPException, Depends, Request
15
+ from pydantic import BaseModel, Field
16
+
17
+ logger = logging.getLogger(__name__)
18
+ router = APIRouter(prefix="/api/skills", tags=["skills"])
19
+
20
+
21
+ # =============================================================================
22
+ # Models
23
+ # =============================================================================
24
+
25
+ class SkillTriggers(BaseModel):
26
+ """Triggers that activate a skill."""
27
+ tool_ids: List[str] = Field(default_factory=list, description="Tool IDs that trigger this skill")
28
+ keywords: List[str] = Field(default_factory=list, description="Keywords in task that trigger this skill")
29
+ task_patterns: List[str] = Field(default_factory=list, description="Regex patterns to match task")
30
+
31
+
32
+ class SkillCreate(BaseModel):
33
+ """Create a new skill."""
34
+ id: str = Field(..., description="Skill ID (lowercase, hyphenated: my-custom-skill)")
35
+ name: str = Field(..., description="Human-readable name")
36
+ description: str = Field(..., description="Short description")
37
+ content: str = Field(..., description="Markdown content for the skill")
38
+ triggers: SkillTriggers = Field(default_factory=SkillTriggers)
39
+ priority: int = Field(default=5, ge=1, le=10, description="Priority (1=highest)")
40
+
41
+
42
+ class SkillUpdate(BaseModel):
43
+ """Update an existing skill."""
44
+ name: Optional[str] = None
45
+ description: Optional[str] = None
46
+ content: Optional[str] = None
47
+ triggers: Optional[SkillTriggers] = None
48
+ priority: Optional[int] = Field(default=None, ge=1, le=10)
49
+
50
+
51
+ class SkillResponse(BaseModel):
52
+ """Skill details response."""
53
+ id: str
54
+ name: str
55
+ description: str
56
+ type: str # "system" or "user"
57
+ priority: int
58
+ triggers: Dict[str, Any] = {}
59
+ content: Optional[str] = None # Only included when fetching single skill
60
+
61
+
62
+ class SkillListResponse(BaseModel):
63
+ """List of skills."""
64
+ system: List[SkillResponse]
65
+ user: List[SkillResponse]
66
+
67
+
68
+ # =============================================================================
69
+ # Helpers
70
+ # =============================================================================
71
+
72
+ def get_skills_base_dir() -> Path:
73
+ """Get base (SYSTEM/builtin) skills directory (anchored to the
74
+ install/repo root, not CWD). Unchanged by Task 9 - the shipped
75
+ system-skill library stays read-only on the package tree; only per-tenant
76
+ USER storage moves to data-home (see get_user_skills_dir).
77
+ """
78
+ return Path(__file__).resolve().parents[1] / "data" / "prompts" / "skills"
79
+
80
+
81
+ def get_user_skills_dir(user_id: str) -> Path:
82
+ """Get user's skills directory, creating if needed.
83
+
84
+ Task 9 (REST-layer data-home fix, folded in from the Task 8 review): this
85
+ used to hardcode ``get_skills_base_dir()`` (the installed package tree),
86
+ which split-brained from ``SkillManager``/``SkillWriterMixin`` the moment
87
+ Task 8 moved THEIR user-scope reads/writes to data-home
88
+ (``skill_store.skills_data_home()``) - post-``polyrob update``,
89
+ ``GET /api/skills`` would show a phantom-empty user list, ``PUT``/
90
+ ``DELETE`` would false-404, and the ``POST`` 409 conflict-precheck would
91
+ go blind.
92
+
93
+ Delegates to the LIVE ``SkillManager`` singleton's ``_user_dirs_root()``
94
+ (the same "where do user writes actually land" resolver
95
+ ``SkillWriterMixin._user_root`` uses) rather than re-deriving the
96
+ data-home path independently - this guarantees the REST root can never
97
+ drift from wherever SkillManager actually writes, INCLUDING the
98
+ pre-existing test-only single-root override contract
99
+ (``SkillManager(skills_dir=...)`` / ``sm.skills_dir = ...``) that several
100
+ tests in this module rely on. In production (no override) that resolver
101
+ returns exactly ``skill_store.skills_data_home()``.
102
+ """
103
+ from agents.task.agent.skill_manager import get_skill_manager
104
+
105
+ base = get_skill_manager()._user_dirs_root()
106
+ user_dir = base / f"user_{user_id}"
107
+ user_dir.mkdir(parents=True, exist_ok=True)
108
+ return user_dir
109
+
110
+
111
+ def get_user_rules(user_id: str) -> Dict[str, Any]:
112
+ """Load user's skill rules."""
113
+ user_dir = get_user_skills_dir(user_id)
114
+ rules_file = user_dir / "rules.json"
115
+
116
+ if rules_file.exists():
117
+ try:
118
+ with open(rules_file) as f:
119
+ return json.load(f)
120
+ except Exception as e:
121
+ logger.warning(f"Failed to load user rules for {user_id}: {e}")
122
+
123
+ return {}
124
+
125
+
126
+ def save_user_rules(user_id: str, rules: Dict[str, Any]) -> None:
127
+ """Save user's skill rules."""
128
+ user_dir = get_user_skills_dir(user_id)
129
+ rules_file = user_dir / "rules.json"
130
+
131
+ with open(rules_file, "w") as f:
132
+ json.dump(rules, f, indent=2)
133
+
134
+
135
+ def get_current_user(request: Request) -> str:
136
+ """Extract user ID from request (via JWT middleware)."""
137
+ from api.dependencies import get_user_id
138
+ return get_user_id(request)
139
+
140
+
141
+ def get_current_user_optional(request: Request) -> Optional[str]:
142
+ """Extract user ID if authenticated, else None (no 401).
143
+
144
+ E10 (A6 gap 9): normalizes the previously-bespoke
145
+ `getattr(request.state, 'user_id', None)` duplicated at list_skills/get_skill
146
+ onto the SAME canonical identity resolution get_current_user /
147
+ api.dependencies.get_user_id uses, minus the raise. Anonymous reads still
148
+ degrade to the base catalog only — no behavior change (A6 gap 9 found this
149
+ to be an inconsistency, not an exploitable leak).
150
+ """
151
+ from api.dependencies import get_user_id
152
+ try:
153
+ return get_user_id(request)
154
+ except HTTPException:
155
+ return None
156
+
157
+
158
+ def validate_skill_id(skill_id: str) -> None:
159
+ """Validate skill ID format."""
160
+ import re
161
+ if not re.match(r'^[a-z][a-z0-9-]*$', skill_id):
162
+ raise HTTPException(
163
+ 400,
164
+ "Skill ID must be lowercase, start with a letter, and contain only letters, numbers, and hyphens"
165
+ )
166
+ if len(skill_id) > 50:
167
+ raise HTTPException(400, "Skill ID must be 50 characters or less")
168
+
169
+
170
+ # =============================================================================
171
+ # Endpoints
172
+ # =============================================================================
173
+
174
+ @router.get("", response_model=SkillListResponse)
175
+ async def list_skills(request: Request, user_id: Optional[str] = Depends(get_current_user_optional)):
176
+ """List all available skills (system + user's custom skills)."""
177
+ # user_id resolved via get_current_user_optional (Depends) — unauthenticated
178
+ # callers see only system skills, same as before.
179
+
180
+ # Load system skills
181
+ from agents.task.agent.skill_manager import get_skill_manager
182
+ sm = get_skill_manager()
183
+ sm._ensure_rules_loaded()
184
+
185
+ system_skills = []
186
+ for skill_id, rules in sm.skill_rules.items():
187
+ # Route through the same catalog description resolver the agent uses
188
+ # (frontmatter-preferred, rules.json fallback) so REST and the agent
189
+ # catalog can never drift (P0 minor #5). _load_skill_content populates
190
+ # the frontmatter meta cache that _resolve_skill_description reads.
191
+ sm._load_skill_content(skill_id)
192
+ system_skills.append(SkillResponse(
193
+ id=skill_id,
194
+ name=rules.get("name", skill_id.replace("-", " ").title()),
195
+ description=sm._resolve_skill_description(skill_id, rules),
196
+ type="system",
197
+ priority=rules.get("priority", 5),
198
+ triggers=rules.get("triggers", {}),
199
+ ))
200
+
201
+ # Load user skills
202
+ user_skills = []
203
+ if user_id:
204
+ user_rules = get_user_rules(user_id)
205
+ for skill_id, rules in user_rules.items():
206
+ sm._load_skill_content(skill_id, user_id=user_id)
207
+ user_skills.append(SkillResponse(
208
+ id=skill_id,
209
+ name=rules.get("name", skill_id),
210
+ description=sm._resolve_skill_description(skill_id, rules, user_id=user_id),
211
+ type="user",
212
+ priority=rules.get("priority", 5),
213
+ triggers=rules.get("triggers", {}),
214
+ ))
215
+
216
+ return SkillListResponse(system=system_skills, user=user_skills)
217
+
218
+
219
+ @router.post("", status_code=201)
220
+ async def create_skill(skill: SkillCreate, user_id: str = Depends(get_current_user)):
221
+ """Create a new user skill."""
222
+ validate_skill_id(skill.id)
223
+
224
+ # Check if skill already exists
225
+ user_dir = get_user_skills_dir(user_id)
226
+ skill_dir = user_dir / skill.id
227
+
228
+ if skill_dir.exists():
229
+ raise HTTPException(409, f"Skill '{skill.id}' already exists")
230
+
231
+ # Check system skills
232
+ from agents.task.agent.skill_manager import get_skill_manager
233
+ sm = get_skill_manager()
234
+ sm._ensure_rules_loaded()
235
+ if skill.id in sm.skill_rules:
236
+ raise HTTPException(409, f"Skill '{skill.id}' conflicts with a system skill")
237
+
238
+ # Route the write through the scanned, atomic SkillWriter choke-point (T5).
239
+ from agents.task.agent.skill_writer import PROVENANCE_USER
240
+ res = sm.create_skill(
241
+ skill.id, skill.content, user_id=user_id,
242
+ description=skill.description, created_by=PROVENANCE_USER, pending=False,
243
+ )
244
+ if not res.ok:
245
+ raise HTTPException(400, f"skill rejected: {'; '.join(res.errors)}")
246
+
247
+ # Preserve the REST contract fields the writer does not model (name/triggers/priority).
248
+ rules = get_user_rules(user_id)
249
+ entry = rules.get(skill.id, {})
250
+ updates: dict = {
251
+ "name": skill.name,
252
+ "description": skill.description,
253
+ "priority": skill.priority,
254
+ "auto_activate": True,
255
+ }
256
+ # BUG 6 fix: only overwrite writer-derived keyword triggers when the caller
257
+ # supplied at least one non-empty trigger list. An all-empty SkillTriggers
258
+ # (the default) would otherwise discard the keywords _upsert_rule derived
259
+ # from the skill id/description, leaving the skill auto_activate=True but
260
+ # unmatchable by get_skills_for_session.
261
+ caller_triggers = skill.triggers.model_dump()
262
+ if any(v for v in caller_triggers.values()):
263
+ updates["triggers"] = caller_triggers
264
+ entry.update(updates)
265
+ rules[skill.id] = entry
266
+ save_user_rules(user_id, rules)
267
+
268
+ logger.info(f"User {user_id} created skill: {skill.id}")
269
+
270
+ return {"id": skill.id, "message": "Skill created successfully"}
271
+
272
+
273
+ @router.get("/{skill_id}", response_model=SkillResponse)
274
+ async def get_skill(skill_id: str, request: Request, user_id: Optional[str] = Depends(get_current_user_optional)):
275
+ """Get skill details including content."""
276
+ validate_skill_id(skill_id)
277
+ # user_id resolved via get_current_user_optional (Depends) — unauthenticated
278
+ # callers fall through to the system-skill branch, same as before.
279
+
280
+ # Check user skills first
281
+ if user_id:
282
+ user_dir = get_user_skills_dir(user_id)
283
+ user_skill_file = user_dir / skill_id / "SKILL.md"
284
+
285
+ if user_skill_file.exists():
286
+ content = user_skill_file.read_text()
287
+ # Warm the frontmatter meta cache (same lookup path, see
288
+ # _load_skill_content) so _resolve_skill_description can prefer it;
289
+ # `content` itself stays the raw file text (unchanged contract).
290
+ from agents.task.agent.skill_manager import get_skill_manager
291
+ sm = get_skill_manager()
292
+ sm._load_skill_content(skill_id, user_id=user_id)
293
+ rules = get_user_rules(user_id).get(skill_id, {})
294
+ return SkillResponse(
295
+ id=skill_id,
296
+ name=rules.get("name", skill_id),
297
+ description=sm._resolve_skill_description(skill_id, rules, user_id=user_id),
298
+ type="user",
299
+ priority=rules.get("priority", 5),
300
+ triggers=rules.get("triggers", {}),
301
+ content=content,
302
+ )
303
+
304
+ # Check system skills
305
+ from agents.task.agent.skill_manager import get_skill_manager
306
+ sm = get_skill_manager()
307
+ sm._ensure_rules_loaded()
308
+
309
+ if skill_id in sm.skill_rules:
310
+ content = sm._load_skill_content(skill_id)
311
+ rules = sm.skill_rules[skill_id]
312
+ return SkillResponse(
313
+ id=skill_id,
314
+ name=rules.get("name", skill_id.replace("-", " ").title()),
315
+ description=sm._resolve_skill_description(skill_id, rules),
316
+ type="system",
317
+ priority=rules.get("priority", 5),
318
+ triggers=rules.get("triggers", {}),
319
+ content=content,
320
+ )
321
+
322
+ raise HTTPException(404, f"Skill '{skill_id}' not found")
323
+
324
+
325
+ @router.put("/{skill_id}")
326
+ async def update_skill(
327
+ skill_id: str,
328
+ update: SkillUpdate,
329
+ user_id: str = Depends(get_current_user)
330
+ ):
331
+ """Update a user skill. System skills cannot be modified."""
332
+ validate_skill_id(skill_id)
333
+ user_dir = get_user_skills_dir(user_id)
334
+ skill_dir = user_dir / skill_id
335
+
336
+ if not skill_dir.exists():
337
+ # Check if it's a system skill
338
+ from agents.task.agent.skill_manager import get_skill_manager
339
+ sm = get_skill_manager()
340
+ sm._ensure_rules_loaded()
341
+ if skill_id in sm.skill_rules:
342
+ raise HTTPException(403, "Cannot modify system skills. Fork it first.")
343
+ raise HTTPException(404, f"Skill '{skill_id}' not found")
344
+
345
+ # Update content if provided — route through the scanned writer (T5).
346
+ if update.content is not None:
347
+ from agents.task.agent.skill_manager import get_skill_manager
348
+ from agents.task.agent.skill_writer import PROVENANCE_USER
349
+ res = get_skill_manager().create_skill(
350
+ skill_id, update.content, user_id=user_id,
351
+ description=(update.description or get_user_rules(user_id).get(skill_id, {}).get("description", "")),
352
+ created_by=PROVENANCE_USER, pending=False,
353
+ )
354
+ if not res.ok:
355
+ raise HTTPException(400, f"skill rejected: {'; '.join(res.errors)}")
356
+
357
+ # Update rules
358
+ rules = get_user_rules(user_id)
359
+ if skill_id in rules:
360
+ if update.name is not None:
361
+ rules[skill_id]["name"] = update.name
362
+ if update.description is not None:
363
+ rules[skill_id]["description"] = update.description
364
+ if update.triggers is not None:
365
+ rules[skill_id]["triggers"] = update.triggers.model_dump()
366
+ if update.priority is not None:
367
+ rules[skill_id]["priority"] = update.priority
368
+
369
+ save_user_rules(user_id, rules)
370
+
371
+ logger.info(f"User {user_id} updated skill: {skill_id}")
372
+
373
+ return {"message": "Skill updated successfully"}
374
+
375
+
376
+ @router.delete("/{skill_id}")
377
+ async def delete_skill(skill_id: str, user_id: str = Depends(get_current_user)):
378
+ """Delete a user skill. System skills cannot be deleted."""
379
+ validate_skill_id(skill_id)
380
+ user_dir = get_user_skills_dir(user_id)
381
+ skill_dir = user_dir / skill_id
382
+
383
+ # Check if it's a system skill before touching the filesystem.
384
+ from agents.task.agent.skill_manager import get_skill_manager
385
+ sm = get_skill_manager()
386
+ sm._ensure_rules_loaded()
387
+ if skill_id in sm.skill_rules:
388
+ raise HTTPException(403, "Cannot delete system skills")
389
+
390
+ if not skill_dir.exists():
391
+ raise HTTPException(404, f"Skill '{skill_id}' not found")
392
+
393
+ # Route deletion through the archiving writer (recoverable; no raw rmtree).
394
+ from agents.task.agent.skill_writer import PROVENANCE_USER
395
+ ok = sm.delete_skill(skill_id, user_id=user_id, created_by=PROVENANCE_USER)
396
+ if not ok:
397
+ raise HTTPException(404, f"Skill '{skill_id}' not found or could not be deleted")
398
+
399
+ logger.info(f"User {user_id} deleted skill: {skill_id}")
400
+
401
+ return {"message": "Skill deleted successfully"}
402
+
403
+
404
+ @router.post("/{skill_id}/fork")
405
+ async def fork_skill(skill_id: str, user_id: str = Depends(get_current_user)):
406
+ """Fork a system skill to create a user copy that can be customized."""
407
+ # Load system skill
408
+ from agents.task.agent.skill_manager import get_skill_manager
409
+ sm = get_skill_manager()
410
+ sm._ensure_rules_loaded()
411
+
412
+ if skill_id not in sm.skill_rules:
413
+ raise HTTPException(404, f"System skill '{skill_id}' not found")
414
+
415
+ # Create new ID
416
+ new_id = f"{skill_id}-custom"
417
+ user_dir = get_user_skills_dir(user_id)
418
+
419
+ # Check if already exists
420
+ if (user_dir / new_id).exists():
421
+ raise HTTPException(409, f"Forked skill '{new_id}' already exists")
422
+
423
+ # Copy content
424
+ content = sm._load_skill_content(skill_id)
425
+ rules = sm.skill_rules[skill_id]
426
+ resolved_description = sm._resolve_skill_description(skill_id, rules)
427
+
428
+ # Route through the scanned writer (T5).
429
+ from agents.task.agent.skill_writer import PROVENANCE_USER
430
+ res = sm.create_skill(
431
+ new_id, content, user_id=user_id,
432
+ description=resolved_description,
433
+ created_by=PROVENANCE_USER, pending=False,
434
+ )
435
+ if not res.ok:
436
+ raise HTTPException(400, f"fork rejected: {'; '.join(res.errors)}")
437
+
438
+ # Save rules
439
+ user_rules = get_user_rules(user_id)
440
+ user_rules[new_id] = {
441
+ "name": f"{rules.get('name', skill_id)} (Custom)",
442
+ "description": resolved_description,
443
+ "triggers": rules.get("triggers", {}),
444
+ "priority": rules.get("priority", 5),
445
+ "auto_activate": True,
446
+ "forked_from": skill_id,
447
+ }
448
+ save_user_rules(user_id, user_rules)
449
+
450
+ logger.info(f"User {user_id} forked skill {skill_id} -> {new_id}")
451
+
452
+ return {"id": new_id, "message": f"Forked '{skill_id}' to '{new_id}'"}
453
+