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,85 @@
1
+ """Headless still renderer — runs the EXACT ``avatar/mindprint.js`` engine in
2
+ Chromium (Playwright) and writes a deterministic PNG. Python only orchestrates;
3
+ every pixel comes from the committed JS engine.
4
+
5
+ Playwright is an OPTIONAL extra (``pip install '.[browser]' && playwright install
6
+ chromium``). When it (or Chromium) is absent, :func:`render_still` raises
7
+ :class:`PfpRenderUnavailable` so callers (``store.generate_pfp``) can fall back to the
8
+ committed reference PNG. The renderer is never on a runtime/interactive path.
9
+
10
+ Engine loading: the engine is a CLASSIC script (window globals, no ES-module export),
11
+ injected via ``set_content`` on ``about:blank`` — no ``file://`` / ESM / CSP concerns
12
+ and no local HTTP server.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import base64
17
+ from dataclasses import dataclass
18
+ from pathlib import Path
19
+ from typing import Any, Dict, Optional
20
+
21
+ from .config import render_seed, normalized_override
22
+
23
+ _AVATAR = Path(__file__).resolve().parents[2] / "avatar"
24
+ _ENGINE = _AVATAR / "mindprint.js"
25
+
26
+ # Renders a deterministic STILL and returns the PNG data-URL + derived meta.
27
+ _JS_RENDER = """(a) => {
28
+ const mp = new Mindprint(a.seed);
29
+ Object.assign(mp.override, a.override || {});
30
+ const cv = document.getElementById('c');
31
+ cv.width = cv.height = a.size;
32
+ mp.N = 0;
33
+ mp.render(cv.getContext('2d'), a.size, 1.0, 0, {still: true});
34
+ mp.N = 0;
35
+ return {dataURL: cv.toDataURL('image/png'), traits: mp.traitList(),
36
+ hex: mp.hex, voice: mp.voiceCfg()};
37
+ }"""
38
+
39
+
40
+ class PfpRenderUnavailable(RuntimeError):
41
+ """Raised when Playwright/Chromium is not available to render a still."""
42
+
43
+
44
+ @dataclass
45
+ class RenderResult:
46
+ path: Path
47
+ traits: Dict[str, Any]
48
+ seed_hex: str
49
+ voice: Dict[str, float]
50
+
51
+
52
+ def render_still(config: Dict[str, Any], out_png, *, size: Optional[int] = None) -> RenderResult:
53
+ """Render ``config`` to ``out_png`` via headless Chromium. Raises
54
+ :class:`PfpRenderUnavailable` if Playwright/Chromium is missing."""
55
+ try:
56
+ from playwright.sync_api import sync_playwright, Error as PlaywrightError
57
+ except ImportError as e: # extra not installed
58
+ raise PfpRenderUnavailable("playwright not installed ('.[browser]')") from e
59
+
60
+ engine = _ENGINE.read_text(encoding="utf-8")
61
+ seed = render_seed(config)
62
+ override = normalized_override(config.get("override", {}) or {})
63
+ sz = int(size or config.get("size") or 768)
64
+ html = (
65
+ "<!doctype html><meta charset='utf-8'>"
66
+ "<canvas id='c'></canvas><script>" + engine + "</script>"
67
+ )
68
+
69
+ try:
70
+ with sync_playwright() as pw:
71
+ browser = pw.chromium.launch(args=["--disable-gpu"])
72
+ try:
73
+ page = browser.new_page()
74
+ page.set_content(html, wait_until="load")
75
+ res = page.evaluate(_JS_RENDER, {"seed": seed, "override": override, "size": sz})
76
+ finally:
77
+ browser.close()
78
+ except PlaywrightError as e: # chromium binary missing / launch failure
79
+ raise PfpRenderUnavailable(f"chromium unavailable: {e}") from e
80
+
81
+ data = base64.b64decode(res["dataURL"].split(",", 1)[1])
82
+ out_png = Path(out_png)
83
+ out_png.parent.mkdir(parents=True, exist_ok=True)
84
+ out_png.write_bytes(data)
85
+ return RenderResult(path=out_png, traits=res["traits"], seed_hex=res["hex"], voice=res["voice"])
modules/pfp/store.py ADDED
@@ -0,0 +1,82 @@
1
+ """Persist the frozen avatar into the instance identity home.
2
+
3
+ ``generate_pfp`` writes ``<home>/identity/{instance_id}/pfp/{pfp.png,pfp.json}``. It
4
+ is idempotent ("created once" — a no-op if the PNG exists unless ``force``), and it
5
+ falls back to a committed reference PNG when Chromium/Playwright is unavailable, so the
6
+ avatar always materializes even headless / in CI. Trait/voice/seed_hex meta is computed
7
+ by the pure-Python mesh (no browser), so the identity record is complete on either path.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import shutil
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Any, Dict, Optional
16
+
17
+ from core.instance import pfp_path, pfp_dir, load_pfp_meta, DEFAULT_INSTANCE_ID
18
+ from .config import load_frozen_config
19
+ from .mesh import Mesh
20
+ from .renderer import render_still, PfpRenderUnavailable
21
+
22
+ _AVATAR = Path(__file__).resolve().parents[2] / "avatar"
23
+ _DEFAULT_CONFIG = _AVATAR / "config" / "rob.json"
24
+ _DEFAULT_REFERENCE = _AVATAR / "renders" / "rob.png"
25
+
26
+
27
+ def _now_iso() -> str:
28
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
29
+
30
+
31
+ def generate_pfp(
32
+ home_dir,
33
+ instance_id: str = DEFAULT_INSTANCE_ID,
34
+ *,
35
+ config: Optional[Dict[str, Any]] = None,
36
+ config_path=None,
37
+ force: bool = False,
38
+ reference_png=None,
39
+ size: Optional[int] = None,
40
+ ) -> Dict[str, Any]:
41
+ """Render + persist the instance avatar; return the ``pfp.json`` meta.
42
+
43
+ Idempotent unless ``force``. Raises :class:`PfpRenderUnavailable` only if BOTH the
44
+ headless render fails AND no reference PNG is available.
45
+ """
46
+ cfg = config if config is not None else load_frozen_config(config_path or _DEFAULT_CONFIG)
47
+
48
+ target = pfp_path(home_dir, instance_id)
49
+ meta_path = pfp_dir(home_dir, instance_id) / "pfp.json"
50
+
51
+ if target.is_file() and not force:
52
+ existing = load_pfp_meta(home_dir, instance_id)
53
+ if existing is not None:
54
+ return existing # created once — no rewrite
55
+
56
+ mesh = Mesh(cfg) # traits/voice/seed_hex — pure Python, no browser
57
+
58
+ try:
59
+ render_still(cfg, target, size=size)
60
+ rendered_by = "playwright-chromium"
61
+ except PfpRenderUnavailable:
62
+ ref = Path(reference_png) if reference_png else _DEFAULT_REFERENCE
63
+ if ref.is_file():
64
+ target.parent.mkdir(parents=True, exist_ok=True)
65
+ shutil.copyfile(ref, target)
66
+ rendered_by = "committed-reference"
67
+ else:
68
+ raise
69
+
70
+ meta: Dict[str, Any] = {
71
+ **cfg,
72
+ "instance_id": instance_id,
73
+ "seed_hex": mesh.hex,
74
+ "traits": mesh.traits(),
75
+ "voice": mesh.voice(),
76
+ "config_ref": "avatar/config/rob.json",
77
+ "rendered_by": rendered_by,
78
+ "created_at": _now_iso(),
79
+ }
80
+ meta_path.parent.mkdir(parents=True, exist_ok=True)
81
+ meta_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
82
+ return meta
@@ -0,0 +1 @@
1
+ """Skill provenance + usage metrics (W2-D)."""
@@ -0,0 +1,248 @@
1
+ """Skill provenance + usage metrics (W2-D).
2
+
3
+ Answers the question the safety brief requires of writable skills: *do authored
4
+ skills actually get reused?* Two tiny tenant-scoped tables in
5
+ ``<data_dir>/skill_usage.db`` (shared WAL+jitter helpers):
6
+
7
+ - ``skill_provenance`` — who authored a skill and when (``user``/``agent``/
8
+ ``background_review``);
9
+ - ``skill_usage`` — per-(skill,user) load_count + last_used_at, bumped each time the
10
+ agent pulls a skill body via ``load_skill``.
11
+
12
+ Feeds W5 (curator: archive unused authored skills) and W7 (`/insights`: authored-skill
13
+ reuse %). Pure storage; fail-open at the call sites (a metrics write must never break
14
+ a skill load).
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ import time
20
+ import threading
21
+ from typing import Callable, Dict, List, Optional
22
+
23
+ from core.identity import is_anonymous
24
+ from core.sqlite_util import execute_retry, wal_connect
25
+
26
+
27
+ class SkillUsageStore:
28
+ def __init__(self, db_path: str, *, clock: Callable[[], float] = time.time):
29
+ self.db_path = db_path
30
+ self._now = clock
31
+ parent = os.path.dirname(db_path)
32
+ if parent:
33
+ os.makedirs(parent, exist_ok=True)
34
+ self._init_schema()
35
+
36
+ def _init_schema(self) -> None:
37
+ conn = wal_connect(self.db_path)
38
+ try:
39
+ conn.executescript(
40
+ """
41
+ CREATE TABLE IF NOT EXISTS skill_provenance (
42
+ skill_id TEXT NOT NULL,
43
+ user_id TEXT NOT NULL,
44
+ created_by TEXT NOT NULL,
45
+ created_at REAL NOT NULL,
46
+ PRIMARY KEY (skill_id, user_id)
47
+ );
48
+ CREATE TABLE IF NOT EXISTS skill_usage (
49
+ skill_id TEXT NOT NULL,
50
+ user_id TEXT NOT NULL,
51
+ load_count INTEGER NOT NULL DEFAULT 0,
52
+ last_used_at REAL,
53
+ PRIMARY KEY (skill_id, user_id)
54
+ );
55
+ CREATE TABLE IF NOT EXISTS curator_state (
56
+ key TEXT PRIMARY KEY,
57
+ value TEXT
58
+ );
59
+ CREATE TABLE IF NOT EXISTS skill_install_audit (
60
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
61
+ name TEXT NOT NULL,
62
+ user_id TEXT NOT NULL,
63
+ source TEXT NOT NULL,
64
+ resolved_sha TEXT,
65
+ approver TEXT NOT NULL,
66
+ ts REAL NOT NULL
67
+ );
68
+ """
69
+ )
70
+ conn.commit()
71
+ finally:
72
+ conn.close()
73
+
74
+ # --- provenance ----------------------------------------------------------
75
+
76
+ def record_provenance(self, skill_id: str, user_id: str, created_by: str) -> None:
77
+ if is_anonymous(user_id):
78
+ return
79
+ execute_retry(
80
+ self.db_path,
81
+ """INSERT INTO skill_provenance (skill_id,user_id,created_by,created_at)
82
+ VALUES (?,?,?,?)
83
+ ON CONFLICT(skill_id,user_id) DO UPDATE SET created_by=excluded.created_by""",
84
+ (skill_id, user_id, created_by, self._now()),
85
+ )
86
+
87
+ def get_provenance(self, skill_id: str, user_id: str) -> Optional[Dict]:
88
+ row = execute_retry(
89
+ self.db_path,
90
+ "SELECT * FROM skill_provenance WHERE skill_id=? AND user_id=?",
91
+ (skill_id, user_id), fetch="one",
92
+ )
93
+ return dict(row) if row else None
94
+
95
+ # --- usage ---------------------------------------------------------------
96
+
97
+ def bump_load(self, skill_id: str, user_id: str) -> None:
98
+ if is_anonymous(user_id):
99
+ return
100
+ execute_retry(
101
+ self.db_path,
102
+ """INSERT INTO skill_usage (skill_id,user_id,load_count,last_used_at)
103
+ VALUES (?,?,1,?)
104
+ ON CONFLICT(skill_id,user_id)
105
+ DO UPDATE SET load_count=load_count+1, last_used_at=excluded.last_used_at""",
106
+ (skill_id, user_id, self._now()),
107
+ )
108
+
109
+ def get_usage(self, skill_id: str, user_id: str) -> Dict:
110
+ row = execute_retry(
111
+ self.db_path,
112
+ "SELECT * FROM skill_usage WHERE skill_id=? AND user_id=?",
113
+ (skill_id, user_id), fetch="one",
114
+ )
115
+ return dict(row) if row else {"skill_id": skill_id, "user_id": user_id,
116
+ "load_count": 0, "last_used_at": None}
117
+
118
+ def list_authored(self, user_id: Optional[str] = None,
119
+ created_by: Optional[List[str]] = None) -> List[Dict]:
120
+ """Join provenance+usage for authored skills (curator/insights feed)."""
121
+ sql = ("SELECT p.skill_id, p.user_id, p.created_by, p.created_at, "
122
+ "COALESCE(u.load_count,0) AS load_count, u.last_used_at "
123
+ "FROM skill_provenance p LEFT JOIN skill_usage u "
124
+ "ON p.skill_id=u.skill_id AND p.user_id=u.user_id")
125
+ clauses, params = [], []
126
+ if user_id is not None:
127
+ clauses.append("p.user_id=?"); params.append(user_id)
128
+ if created_by:
129
+ clauses.append("p.created_by IN (%s)" % ",".join("?" * len(created_by)))
130
+ params.extend(created_by)
131
+ if clauses:
132
+ sql += " WHERE " + " AND ".join(clauses)
133
+ rows = execute_retry(self.db_path, sql, tuple(params), fetch="all") or []
134
+ return [dict(r) for r in rows]
135
+
136
+ def authored_reuse_summary(self, user_id: Optional[str] = None) -> Dict:
137
+ """W7 /insights: did self-authored skills get reused?
138
+
139
+ Returns counts + reuse rate over agent/background-authored skills — the
140
+ measurement the writable-skills safety brief requires. Tenant-scoped when
141
+ user_id is given.
142
+ """
143
+ rows = self.list_authored(user_id=user_id, created_by=["agent", "background_review"])
144
+ total = len(rows)
145
+ reused = sum(1 for r in rows if (r.get("load_count") or 0) > 0)
146
+ by_author: Dict[str, int] = {}
147
+ for r in rows:
148
+ by_author[r["created_by"]] = by_author.get(r["created_by"], 0) + 1
149
+ return {
150
+ "authored_total": total,
151
+ "authored_reused": reused,
152
+ "reuse_rate": (reused / total) if total else 0.0,
153
+ "by_author": by_author,
154
+ "top": sorted(
155
+ ({"skill_id": r["skill_id"], "loads": r.get("load_count") or 0} for r in rows),
156
+ key=lambda d: d["loads"], reverse=True,
157
+ )[:10],
158
+ }
159
+
160
+ # --- curator state -------------------------------------------------------
161
+
162
+ def get_state(self, key: str) -> Optional[str]:
163
+ row = execute_retry(self.db_path, "SELECT value FROM curator_state WHERE key=?",
164
+ (key,), fetch="one")
165
+ return row["value"] if row else None
166
+
167
+ def set_state(self, key: str, value: str) -> None:
168
+ execute_retry(
169
+ self.db_path,
170
+ "INSERT INTO curator_state (key,value) VALUES (?,?) "
171
+ "ON CONFLICT(key) DO UPDATE SET value=excluded.value",
172
+ (key, value),
173
+ )
174
+
175
+ # --- install audit ---------------------------------------------------------
176
+
177
+ def record_install(self, name: str, *, user_id: str, source: str,
178
+ resolved_sha: Optional[str] = None, approver: str,
179
+ ts: Optional[float] = None) -> None:
180
+ """Append-only audit trail for `polyrob skill install`/`approve`: what
181
+ skill, whose tenant, where it came from (source + resolved commit SHA
182
+ when known), who approved it, and when. Never updates/dedupes — a
183
+ re-install/re-approve of the same name is a NEW event worth keeping."""
184
+ ts = self._now() if ts is None else ts
185
+ execute_retry(
186
+ self.db_path,
187
+ """INSERT INTO skill_install_audit
188
+ (name,user_id,source,resolved_sha,approver,ts)
189
+ VALUES (?,?,?,?,?,?)""",
190
+ (name, user_id, source, resolved_sha, approver, ts),
191
+ )
192
+
193
+ def list_installs(self, user_id: Optional[str] = None) -> List[Dict]:
194
+ sql = "SELECT name,user_id,source,resolved_sha,approver,ts FROM skill_install_audit"
195
+ params: tuple = ()
196
+ if user_id is not None:
197
+ sql += " WHERE user_id=?"
198
+ params = (user_id,)
199
+ sql += " ORDER BY ts DESC"
200
+ rows = execute_retry(self.db_path, sql, params, fetch="all") or []
201
+ return [dict(r) for r in rows]
202
+
203
+
204
+ # --- process-wide singleton (shares one db with the curator) -----------------
205
+
206
+ _STORE_LOCK = threading.Lock()
207
+ _STORE: Optional[SkillUsageStore] = None
208
+
209
+
210
+ def _default_data_dir() -> str:
211
+ """Stable ABSOLUTE data dir so the singleton resolves the same file regardless of
212
+ cwd. Otherwise the relative ``"data"`` default (used by the load_skill/provenance
213
+ call sites) could resolve to a different directory than the curator's
214
+ ``config.data_dir`` (absolute), silently splitting reuse counts from the curator
215
+ and /insights. Anchored at the repo root (this file is modules/skills/skill_usage.py).
216
+ """
217
+ env = os.getenv("POLYROB_DATA_DIR")
218
+ if env:
219
+ return os.path.abspath(env)
220
+ repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
221
+ return os.path.join(repo_root, "data")
222
+
223
+
224
+ def get_skill_usage_store(data_dir: Optional[str] = None) -> SkillUsageStore:
225
+ global _STORE
226
+ with _STORE_LOCK:
227
+ if _STORE is None:
228
+ dd = os.path.abspath(data_dir) if data_dir else _default_data_dir()
229
+ _STORE = SkillUsageStore(os.path.join(dd, "skill_usage.db"))
230
+ elif data_dir:
231
+ want = os.path.join(os.path.abspath(data_dir), "skill_usage.db")
232
+ if want != _STORE.db_path:
233
+ # First caller's path wins (singleton). Warn loudly if a later caller
234
+ # (e.g. the curator) expected a different db — that would desync metrics.
235
+ import logging
236
+ logging.getLogger(__name__).warning(
237
+ "skill_usage store already bound to %s; ignoring requested %s "
238
+ "(metrics/curator may read a different file than intended)",
239
+ _STORE.db_path, want,
240
+ )
241
+ return _STORE
242
+
243
+
244
+ def reset_skill_usage_store() -> None:
245
+ """Test seam."""
246
+ global _STORE
247
+ with _STORE_LOCK:
248
+ _STORE = None
@@ -0,0 +1,24 @@
1
+ """Surface-agnostic speech-to-text (#9).
2
+
3
+ The engine lives here, NOT in any surface, so every chat surface (Telegram today,
4
+ WhatsApp/etc. tomorrow) downloads its own audio and hands raw bytes to one shared
5
+ ``Transcriber``. Keep transport-specific extraction in the surface; keep model/runtime
6
+ concerns here.
7
+
8
+ ``build_transcriber()`` is the factory: a real faster-whisper transcriber when the
9
+ extra is installed, else a ``NullTranscriber`` (returns "") so a misconfigured deploy
10
+ degrades to "no transcript" instead of crashing the inbound spine.
11
+ """
12
+ from .base import Transcriber, NullTranscriber
13
+
14
+ __all__ = ["Transcriber", "NullTranscriber", "build_transcriber"]
15
+
16
+
17
+ def build_transcriber(model_size: str = "base") -> Transcriber:
18
+ """Return a ready Transcriber. Falls back to NullTranscriber if faster-whisper isn't
19
+ importable (the model itself loads lazily on first transcribe)."""
20
+ try:
21
+ from .faster_whisper_transcriber import FasterWhisperTranscriber
22
+ return FasterWhisperTranscriber(model_size=model_size)
23
+ except Exception: # import-time failure (extra missing) -> degrade, don't crash
24
+ return NullTranscriber()
@@ -0,0 +1,20 @@
1
+ """Transcriber ABC + a no-op fallback. Surface-agnostic: bytes in, text out."""
2
+ from abc import ABC, abstractmethod
3
+ from typing import Optional
4
+
5
+
6
+ class Transcriber(ABC):
7
+ @abstractmethod
8
+ async def transcribe(
9
+ self, audio: bytes, *, mime: Optional[str] = None, language: Optional[str] = None
10
+ ) -> str:
11
+ """Transcribe raw audio bytes to text. Returns "" when nothing is recognized.
12
+ Implementations MUST be fail-open (never raise into the caller's inbound path)."""
13
+ ...
14
+
15
+
16
+ class NullTranscriber(Transcriber):
17
+ """Used when transcription is disabled or the faster-whisper extra is unavailable."""
18
+
19
+ async def transcribe(self, audio: bytes, *, mime=None, language=None) -> str:
20
+ return ""
@@ -0,0 +1,55 @@
1
+ """faster-whisper transcriber. The model loads lazily on first use and inference runs
2
+ in a worker thread (faster-whisper is sync + CPU-bound), so a long transcription never
3
+ blocks the event loop. Fail-open: any error returns "" (no transcript)."""
4
+ import asyncio
5
+ import logging
6
+ import os
7
+ import tempfile
8
+ from typing import Optional
9
+
10
+ from .base import Transcriber
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class FasterWhisperTranscriber(Transcriber):
16
+ def __init__(self, model_size: str = "base", device: str = "cpu",
17
+ compute_type: str = "int8") -> None:
18
+ self._model_size = model_size
19
+ self._device = device
20
+ self._compute_type = compute_type
21
+ self._model = None # lazily constructed on first transcribe
22
+
23
+ def _ensure_model(self):
24
+ if self._model is None:
25
+ from faster_whisper import WhisperModel # lazy: heavy import + optional extra
26
+ self._model = WhisperModel(
27
+ self._model_size, device=self._device, compute_type=self._compute_type
28
+ )
29
+ return self._model
30
+
31
+ async def transcribe(self, audio: bytes, *, mime: Optional[str] = None,
32
+ language: Optional[str] = None) -> str:
33
+ if not audio:
34
+ return ""
35
+ try:
36
+ return await asyncio.to_thread(self._transcribe_sync, audio, language)
37
+ except Exception as e: # fail-open: a transcription failure must not drop the msg
38
+ logger.warning("faster-whisper transcription failed: %s", e)
39
+ return ""
40
+
41
+ def _transcribe_sync(self, audio: bytes, language: Optional[str]) -> str:
42
+ model = self._ensure_model()
43
+ # faster-whisper reads from a path; Telegram voice is OGG/Opus, which ffmpeg
44
+ # (pulled in by faster-whisper) decodes by content, so the suffix is cosmetic.
45
+ with tempfile.NamedTemporaryFile(suffix=".ogg", delete=False) as f:
46
+ f.write(audio)
47
+ path = f.name
48
+ try:
49
+ segments, _info = model.transcribe(path, language=language)
50
+ return "".join(seg.text for seg in segments).strip()
51
+ finally:
52
+ try:
53
+ os.unlink(path)
54
+ except OSError:
55
+ pass