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
webview/owner_auth.py ADDED
@@ -0,0 +1,108 @@
1
+ """Owner username/password login — Posture 1's net-new auth path (design doc §1).
2
+
3
+ Does NOT require standing up the full SIWE SaaS layer. Verifies against
4
+ POLYROB_OWNER_USERNAME + POLYROB_OWNER_PASSWORD_HASH (argon2, never plaintext)
5
+ and mints a session cookie on the SAME canonical request.state contract
6
+ Workstream C4 defines: user_id/tier/role/payment_method/authenticated. Owner
7
+ login sets role="owner", tier="admin", payment_method=None (no billing outside
8
+ Posture 2).
9
+
10
+ Reuses the existing wallet-JWT cookie infra byte-for-byte (same "auth_token"
11
+ cookie name/flags api/auth_endpoints.py:226-235 uses, same JWT_SECRET_KEY, same
12
+ HS256 algorithm) so webview/server.py's existing auth_middleware JWT-decode
13
+ branch (webview/server.py:598-641) needs ZERO changes to accept an owner
14
+ session — it already decodes "role"/"tier"/"user_id" generically. That decode
15
+ branch now writes request.state via api/auth_state.py::set_auth_state (the C4
16
+ SSOT) so an owner token converges on the exact same canonical contract this
17
+ module mints.
18
+ """
19
+ import hmac
20
+ import os
21
+ from datetime import datetime, timedelta
22
+
23
+ import jwt as pyjwt
24
+ from argon2 import PasswordHasher
25
+ from fastapi import Response
26
+
27
+ from webview import webgate
28
+
29
+ _hasher = PasswordHasher()
30
+
31
+ # Precomputed ONCE at module load: a valid argon2 hash of an arbitrary
32
+ # constant. verify_owner_password() runs an argon2 verify against this on
33
+ # EVERY call where the username doesn't match, so a bad-username attempt
34
+ # costs the same wall-clock time as a bad-password attempt against a real
35
+ # user. Without this, a wrong username short-circuits before argon2 ever
36
+ # runs, and that near-instant-vs-tens-of-ms gap is a network-observable
37
+ # oracle that confirms the owner username (user enumeration).
38
+ _DUMMY_HASH = _hasher.hash("polyrob-owner-auth-constant-time-dummy")
39
+
40
+
41
+ def owner_credentials_configured() -> bool:
42
+ return bool(
43
+ os.environ.get("POLYROB_OWNER_USERNAME")
44
+ and os.environ.get("POLYROB_OWNER_PASSWORD_HASH")
45
+ )
46
+
47
+
48
+ def verify_owner_password(username: str, password: str) -> bool:
49
+ """Fail-closed: any misconfiguration or verify failure returns False, never raises.
50
+
51
+ Timing-safe by construction: every call — bad username, bad password,
52
+ unconfigured credentials, or a correct pair — performs exactly ONE
53
+ argon2 verify (against the real hash when the username matches, else a
54
+ precomputed dummy hash) before returning. There is no early fast-return,
55
+ so wall-clock time never leaks which field (username vs password) was
56
+ wrong, or whether owner auth is configured at all. The username
57
+ comparison itself uses `hmac.compare_digest` so it isn't a fast
58
+ string-compare oracle either.
59
+ """
60
+ configured = owner_credentials_configured()
61
+ expected_username = os.environ.get("POLYROB_OWNER_USERNAME", "") if configured else ""
62
+ expected_hash = os.environ.get("POLYROB_OWNER_PASSWORD_HASH", "") if configured else ""
63
+
64
+ username_matches = configured and hmac.compare_digest(
65
+ username.encode("utf-8"), expected_username.encode("utf-8")
66
+ )
67
+ target_hash = expected_hash if username_matches else _DUMMY_HASH
68
+
69
+ try:
70
+ verified = _hasher.verify(target_hash, password)
71
+ except Exception:
72
+ verified = False
73
+
74
+ return bool(verified and username_matches)
75
+
76
+
77
+ def issue_owner_session_cookie(response: Response) -> str:
78
+ """Mint + set the owner session JWT. Returns the raw token."""
79
+ jwt_secret = os.environ.get("JWT_SECRET_KEY")
80
+ if not jwt_secret:
81
+ raise RuntimeError("JWT_SECRET_KEY not configured - cannot issue owner session")
82
+
83
+ expires_at = datetime.utcnow() + timedelta(days=7)
84
+ payload = {
85
+ "sub": webgate.local_owner_id(),
86
+ "user_id": webgate.local_owner_id(),
87
+ "tier": "admin",
88
+ "role": "owner",
89
+ "payment_method": None,
90
+ "iat": datetime.utcnow(),
91
+ "exp": expires_at,
92
+ }
93
+ token = pyjwt.encode(payload, jwt_secret, algorithm="HS256")
94
+
95
+ is_production = os.environ.get("ENVIRONMENT", "production") == "production"
96
+ response.set_cookie(
97
+ key="auth_token",
98
+ value=token,
99
+ max_age=7 * 24 * 60 * 60,
100
+ path="/",
101
+ secure=is_production,
102
+ httponly=True,
103
+ samesite="lax",
104
+ )
105
+ return token
106
+
107
+
108
+ __all__ = ["owner_credentials_configured", "verify_owner_password", "issue_owner_session_cookie"]
webview/pages.py ADDED
@@ -0,0 +1,448 @@
1
+ """Webgate v1 read-only pages — Memory · Autonomy · Identity · System.
2
+
3
+ These are the four single-user pages POLYROB lacked (doc 03 §4 P2). Each page is
4
+ one Jinja template + one JSON read endpoint here. Every endpoint **reuses the
5
+ existing service** — it never reimplements a second source of truth:
6
+
7
+ - Memory → the active ``MemoryProvider.search()`` (``modules/memory/*``).
8
+ - Autonomy → ``GoalBoard.list()`` (``agents/task/goals/board.py``) +
9
+ ``CronService.list_jobs()`` (``cron/service.py``).
10
+ - Identity → ``core/instance.py`` SOUL/SELF (``load_self_context`` /
11
+ ``load_self_doc``) — **read-only**, no web write path (editing stays
12
+ the owner-gated ``self_context_manage`` action).
13
+ - System → ``cli/commands/doctor.py::doctor_report`` (imported, not shelled out).
14
+
15
+ Web endpoints have no agent ``execution_context``, so they call the services
16
+ directly rather than the agent tool-action path — still the same SSOT, not a
17
+ rebuild. Everything is fail-open: a missing provider / disabled flag / read error
18
+ degrades to an empty result, never a 500.
19
+
20
+ Flags are read via the dedicated helpers / ``os.environ`` (never ``BotConfig.get``,
21
+ which is a ``getattr`` trap — AGENTS.md landmine). NO ``from __future__ import
22
+ annotations`` here (kept consistent with the registry-closure landmine elsewhere
23
+ and unnecessary in this module).
24
+ """
25
+ import os
26
+
27
+ from pathlib import Path
28
+
29
+ from fastapi import APIRouter, HTTPException, Request
30
+ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, Response
31
+ from fastapi.templating import Jinja2Templates
32
+
33
+ from webview import webgate
34
+
35
+ # Reused services / config — imported at module level so tests can monkeypatch
36
+ # them at the ``webview.pages`` seam (the proof of reuse).
37
+ from agents.task.constants import AutonomyConfig
38
+ from agents.task.goals.board import GoalBoard
39
+ from cli.commands.doctor import doctor_report, local_flag_on, resolve_memory_backend
40
+ from core.instance import (
41
+ load_pfp_meta,
42
+ load_self_context,
43
+ load_self_doc,
44
+ pfp_path,
45
+ resolve_instance_id,
46
+ )
47
+ from cron.jobs import CronJobStore
48
+ from cron.service import CronService
49
+ from core.version import get_version
50
+ from modules.credits.unified_ledger import build_ledger
51
+
52
+ router = APIRouter()
53
+
54
+
55
+ # --- template resolution (same asset base as server.py) --------------------- #
56
+
57
+ def _templates() -> Jinja2Templates:
58
+ try:
59
+ from core.assets import webgate_asset_dir
60
+ templates_dir = webgate_asset_dir() / "templates"
61
+ except Exception: # fail-open to the repo checkout
62
+ from pathlib import Path
63
+ templates_dir = Path(__file__).resolve().parent / "templates"
64
+ return Jinja2Templates(directory=str(templates_dir))
65
+
66
+
67
+ _TEMPLATES = _templates()
68
+ _TEMPLATES.env.globals["console_display_name"] = webgate.console_display_name
69
+ _TEMPLATES.env.globals["branding"] = webgate.branding_config
70
+ _TEMPLATES.env.globals["get_version"] = get_version
71
+ # Posture default for the layout's tenant-nav block (P0-3) — same global as
72
+ # server.py's env; the explicit `is_multitenant` context var still wins.
73
+ _TEMPLATES.env.globals["is_multitenant_posture"] = webgate.is_multitenant
74
+
75
+
76
+ # --- shared helpers --------------------------------------------------------- #
77
+
78
+ def _data_dir() -> str:
79
+ """Data home the running services use (``goals.db``/``cron.db``/``memory.db``).
80
+
81
+ Honors ``POLYROB_DATA_DIR`` (the same env the agents/autonomy runtime read);
82
+ defaults to ``data`` (the tool/runtime default).
83
+ """
84
+ return os.environ.get("POLYROB_DATA_DIR", "data")
85
+
86
+
87
+ def _cron_enabled() -> bool:
88
+ """Whether the cron subsystem is on (lazy import to keep this module light)."""
89
+ try:
90
+ from tools.cronjob_tools import cron_enabled
91
+ return cron_enabled()
92
+ except Exception:
93
+ return False
94
+
95
+
96
+ def _effective_user_id(request: Request) -> str:
97
+ """Tenant identity for the webgate-v1 pages (memory/goals/cron/identity).
98
+
99
+ Single-tenant postures (local/own_ops): ``webgate.local_owner_id()`` — there is
100
+ exactly one owner and no separate caller-identity concept.
101
+ Multitenant: the AUTHENTICATED caller's ``request.state.user_id`` (fixes
102
+ assessment gap 5 — this used to be unconditionally ``local_owner_id()``,
103
+ leaking the instance owner's memory/goals/cron/identity data to every
104
+ authenticated tenant).
105
+
106
+ Fails CLOSED, never open: if multitenant and ``request.state.user_id`` is
107
+ missing/None/empty, this raises ``HTTPException(403)`` rather than falling
108
+ back to ``local_owner_id()`` (the instance owner's data). ``auth_middleware``
109
+ already 401s unauthenticated requests in multitenant mode before this is
110
+ ever called, so the 403 here is defense-in-depth, not the expected path —
111
+ but it must never resolve to the owner's identity.
112
+ """
113
+ if webgate.is_multitenant():
114
+ uid = getattr(request.state, "user_id", None)
115
+ if uid:
116
+ return uid
117
+ raise HTTPException(status_code=403, detail="Authenticated tenant identity required")
118
+ return webgate.local_owner_id()
119
+
120
+
121
+ def _memory_provider():
122
+ """The active external MemoryProvider, or None — reuse the registry/factory.
123
+
124
+ Prefers a provider already registered by an agent in this process; otherwise
125
+ constructs the configured backend read-only via the same factory the agent
126
+ uses. Fail-open to None (Memory page then shows an empty state).
127
+ """
128
+ try:
129
+ from modules.memory.registry import get_memory_registry
130
+ active = get_memory_registry().active()
131
+ if active is not None and getattr(active, "is_external", False):
132
+ return active
133
+ except Exception:
134
+ pass
135
+ try:
136
+ from modules.memory.backend_factory import maybe_register_memory_backend
137
+ return maybe_register_memory_backend(data_dir=_data_dir())
138
+ except Exception:
139
+ return None
140
+
141
+
142
+ def _split_snippets(raw: str) -> list:
143
+ """Turn the provider's newline-joined ``- {content}`` string into a JSON list."""
144
+ if not raw:
145
+ return []
146
+ out = []
147
+ for line in raw.splitlines():
148
+ line = line.strip()
149
+ if not line:
150
+ continue
151
+ if line.startswith("- "):
152
+ line = line[2:]
153
+ out.append(line)
154
+ return out
155
+
156
+
157
+ def _goal_dict(g) -> dict:
158
+ return {
159
+ "id": g.id,
160
+ "title": g.title,
161
+ "body": g.body,
162
+ "status": g.status,
163
+ "priority": g.priority,
164
+ "created_at": g.created_at,
165
+ "completed_at": g.completed_at,
166
+ "result": g.result,
167
+ }
168
+
169
+
170
+ def _cron_dict(j) -> dict:
171
+ return {
172
+ "id": j.id,
173
+ "task": j.task,
174
+ "schedule_spec": j.schedule_spec,
175
+ "next_run_at": j.next_run_at.isoformat() if j.next_run_at else None,
176
+ "one_shot": j.one_shot,
177
+ "enabled": j.enabled,
178
+ "status": j.status,
179
+ "last_run_at": j.last_run_at.isoformat() if getattr(j, "last_run_at", None) else None,
180
+ }
181
+
182
+
183
+ def _version() -> str:
184
+ # get_version() is the SSOT (source pyproject wins over stale installed
185
+ # metadata). Reading importlib.metadata directly here re-introduced the stale-
186
+ # install bug (a venv pinned at an old 1.0.0 would show 1.0.0). An explicit
187
+ # WEBVIEW_VERSION override still wins for deployments that want to pin it.
188
+ return os.environ.get("WEBVIEW_VERSION") or get_version()
189
+
190
+
191
+ def _provider_model():
192
+ try:
193
+ from cli.config_store import resolve_provider_model
194
+ from modules.llm.profiles import PROFILES, providers_with_keys
195
+ present = set(providers_with_keys(dict(os.environ)))
196
+ keys = {p.env_key for p in PROFILES.values() if p.name in present}
197
+ return resolve_provider_model(None, None, available_keys=keys)
198
+ except Exception:
199
+ return (None, None)
200
+
201
+
202
+ def _page_context(request: Request) -> dict:
203
+ return {
204
+ "request": request,
205
+ "is_multitenant": webgate.is_multitenant(),
206
+ "version": _version(),
207
+ "ws_url": os.environ.get("WEBVIEW_WS_URL", ""),
208
+ }
209
+
210
+
211
+ # --- JSON read endpoints ---------------------------------------------------- #
212
+
213
+ @router.get("/api/webgate/memory")
214
+ async def api_memory(request: Request, q: str = "", limit: int = 10):
215
+ """Browse + search recall over the active MemoryProvider (tenant-scoped).
216
+
217
+ ``q`` set → discover; ``q`` empty → browse-recent. Fail-open to no items.
218
+ Returns ``{items, count, mode, limit}`` so the page can caption results
219
+ honestly ("showing the N most recent" vs "N matches"). Per-hit
220
+ timestamp/score provenance is NOT available from the provider's search
221
+ surface (the ``memories`` FTS5 store has no timestamp column — the
222
+ episodic-memory plan tracks that); the envelope carries what exists.
223
+ """
224
+ mode = "search" if (q or "").strip() else "browse"
225
+ provider = _memory_provider()
226
+ if provider is None:
227
+ return JSONResponse({"items": [], "count": 0, "mode": mode, "limit": limit})
228
+ user_id = _effective_user_id(request) # raises 403 outside this try — must not be fail-open-swallowed
229
+ try:
230
+ raw = await provider.search(q or "", user_id=user_id, limit=limit)
231
+ except Exception:
232
+ return JSONResponse({"items": [], "count": 0, "mode": mode, "limit": limit})
233
+ items = _split_snippets(raw)
234
+ return JSONResponse({"items": items, "count": len(items), "mode": mode, "limit": limit})
235
+
236
+
237
+ @router.get("/api/webgate/goals")
238
+ async def api_goals(request: Request):
239
+ """Durable goal board for the effective tenant — reuse ``GoalBoard.list()``."""
240
+ if not AutonomyConfig.goals_enabled():
241
+ return JSONResponse({"enabled": False, "goals": []})
242
+ user_id = _effective_user_id(request) # raises 403 outside this try — must not be fail-open-swallowed
243
+ try:
244
+ board = GoalBoard(os.path.join(_data_dir(), "goals.db"))
245
+ goals = board.list(user_id=user_id)
246
+ return JSONResponse({"enabled": True, "goals": [_goal_dict(g) for g in goals]})
247
+ except Exception:
248
+ return JSONResponse({"enabled": True, "goals": []})
249
+
250
+
251
+ @router.get("/api/webgate/cron")
252
+ async def api_cron(request: Request):
253
+ """Cron job board for the effective tenant — reuse ``CronService.list_jobs()``."""
254
+ if not _cron_enabled():
255
+ return JSONResponse({"enabled": False, "jobs": []})
256
+ user_id = _effective_user_id(request) # raises 403 outside this try — must not be fail-open-swallowed
257
+ try:
258
+ service = CronService(CronJobStore(os.path.join(_data_dir(), "cron.db")))
259
+ jobs = service.list_jobs(user_id=user_id)
260
+ return JSONResponse({"enabled": True, "jobs": [_cron_dict(j) for j in jobs]})
261
+ except Exception:
262
+ return JSONResponse({"enabled": True, "jobs": []})
263
+
264
+
265
+ @router.get("/api/webgate/identity")
266
+ async def api_identity(request: Request):
267
+ """SOUL/SELF identity — reuse ``core.instance`` (READ-ONLY, no write path).
268
+
269
+ SOUL is deliberately NOT re-scoped (instance-wide by design,
270
+ ``core/self_context_writer.py:25``); only the SELF-tier read
271
+ (``load_self_doc``) is per-tenant.
272
+ """
273
+ home = _data_dir()
274
+ owner = _effective_user_id(request)
275
+ instance_id = resolve_instance_id()
276
+ try:
277
+ soul = load_self_context(home) or None
278
+ except Exception:
279
+ soul = None
280
+ try:
281
+ self_doc = load_self_doc(home, owner, instance_id) or None
282
+ except Exception:
283
+ self_doc = None
284
+ return JSONResponse({
285
+ "soul": soul,
286
+ "self": self_doc,
287
+ "instance_id": instance_id,
288
+ "owner": owner,
289
+ })
290
+
291
+
292
+ # --- avatar (pfp) — the instance's face rendered LIVE in the console ---------- #
293
+ # The engine is served same-origin so the console runs the EXACT avatar/mindprint.js
294
+ # on a canvas (animated), driven by the instance's frozen /pfp.json. All fail-open:
295
+ # no avatar yet -> 404 -> the identity page just shows a neutral fallback.
296
+ _AVATAR_DIR = Path(__file__).resolve().parents[1] / "avatar"
297
+
298
+
299
+ def _pfp_data_dir() -> str:
300
+ """Data home to read the avatar from.
301
+
302
+ The avatar is WRITTEN by the CLI (`pfp generate`), whose data home defaults to
303
+ ``cwd/.polyrob`` when ``POLYROB_DATA_DIR`` is unset (``core.bootstrap.
304
+ _resolve_cli_data_home``). Mirror that so generate↔serve agree in local dev; in
305
+ prod (``POLYROB_DATA_DIR`` set) this is identical to :func:`_data_dir`.
306
+ """
307
+ env = os.environ.get("POLYROB_DATA_DIR")
308
+ if env:
309
+ return env
310
+ local = Path.cwd() / ".polyrob"
311
+ return str(local) if local.exists() else _data_dir()
312
+
313
+
314
+ @router.get("/pfp.json")
315
+ async def api_pfp_json():
316
+ """The instance avatar identity blob (traits + engine-agnostic voice), or 404."""
317
+ meta = load_pfp_meta(_pfp_data_dir(), resolve_instance_id())
318
+ if not meta:
319
+ return JSONResponse({"detail": "no avatar"}, status_code=404)
320
+ return JSONResponse(meta)
321
+
322
+
323
+ @router.get("/pfp.png")
324
+ async def pfp_png():
325
+ """The instance avatar still PNG (progressive fallback / OG image), or 404."""
326
+ p = pfp_path(_pfp_data_dir(), resolve_instance_id())
327
+ if not p.is_file():
328
+ return Response(status_code=404)
329
+ return FileResponse(str(p), media_type="image/png")
330
+
331
+
332
+ @router.get("/avatar/mindprint.js")
333
+ async def avatar_engine():
334
+ """Serve the EXACT engine same-origin (classic script; sets window.Mindprint)."""
335
+ p = _AVATAR_DIR / "mindprint.js"
336
+ if not p.is_file():
337
+ return Response(status_code=404)
338
+ return FileResponse(str(p), media_type="application/javascript")
339
+
340
+
341
+ @router.get("/avatar/avatar-live.js")
342
+ async def avatar_live():
343
+ """Serve the read-only live embed (fetch /pfp.json -> animate the canvas)."""
344
+ p = _AVATAR_DIR / "webview" / "avatar-live.js"
345
+ if not p.is_file():
346
+ return Response(status_code=404)
347
+ return FileResponse(str(p), media_type="application/javascript")
348
+
349
+
350
+ def _empty_ledger(user_id: str, days: int) -> dict:
351
+ """The all-zero ledger shape — returned when the ledger read fails so the
352
+ Finance page shows zeros rather than a 500 (every ledger leg fail-opens too)."""
353
+ return {"user_id": user_id, "window_days": days, "llm_api_cost_usd": 0.0,
354
+ "credits_spent": 0.0, "llm_calls": 0, "wallet_spend_usd": 0.0,
355
+ "wallet_payments": 0, "earned_usd": 0.0, "settled_payments": 0,
356
+ "pending_invoices_usd": 0.0, "pending_invoices": 0,
357
+ "total_spend_usd": 0.0, "net_usd": 0.0}
358
+
359
+
360
+ def _ledger_caps() -> dict:
361
+ """Display-only policy caps (NOT part of the ledger read model) — the operator
362
+ context for the earned/spent/pending numbers. Resolved from env, fail-open."""
363
+ def _f(name, default):
364
+ try:
365
+ v = os.environ.get(name)
366
+ return float(v) if v not in (None, "") else default
367
+ except (TypeError, ValueError):
368
+ return default
369
+ return {
370
+ "wallet_daily_cap_usd": _f("WALLET_DAILY_CAP_USD", None),
371
+ "invoice_max_usd": _f("X402_INVOICE_MAX_USD", 50.0),
372
+ "invoice_daily_max": _f("X402_INVOICE_DAILY_MAX", 10.0),
373
+ "autonomy_budget_usd": _f("AUTONOMY_BUDGET_USD", 10.0),
374
+ }
375
+
376
+
377
+ @router.get("/api/webgate/ledger")
378
+ async def api_ledger(request: Request, days: int = 7):
379
+ """Unified financial ledger for the effective tenant — reuse ``build_ledger``.
380
+
381
+ earned / spent / pending / net over a trailing window. Read-only, tenant-
382
+ scoped (``_effective_user_id`` raises 403 outside the try — must not be
383
+ fail-open-swallowed). All-zero-tolerant: a ledger error degrades to zeros."""
384
+ days = max(1, min(int(days), 365))
385
+ user_id = _effective_user_id(request) # 403 in multitenant if no tenant identity
386
+ try:
387
+ ledger = await build_ledger(user_id, days=days)
388
+ except Exception:
389
+ ledger = _empty_ledger(user_id, days)
390
+ ledger["caps"] = _ledger_caps()
391
+ return JSONResponse(ledger)
392
+
393
+
394
+ @router.get("/finance", response_class=HTMLResponse)
395
+ async def finance_page(request: Request):
396
+ return _TEMPLATES.TemplateResponse("finance.html", _page_context(request))
397
+
398
+
399
+ @router.get("/api/webgate/doctor")
400
+ async def api_doctor():
401
+ """System health — reuse ``doctor_report`` (same checks as ``polyrob doctor``).
402
+
403
+ The webview is a SERVER process — nothing does the CLI's POLYROB_LOCAL
404
+ setdefault here — so both the checks and the header's ``memory_backend``
405
+ resolve with ``absent_means_on=False`` (matching what
406
+ ``modules.memory.backend_factory`` actually does at runtime). One shared
407
+ resolution, so the page can never contradict itself again (P0-4).
408
+ """
409
+ env = dict(os.environ)
410
+ try:
411
+ checks = doctor_report(env, local_absent_means_on=False)
412
+ except Exception:
413
+ checks = []
414
+ provider, model = _provider_model()
415
+ rob_local = local_flag_on(env, absent_means_on=False)
416
+ return JSONResponse({
417
+ "checks": checks,
418
+ "instance_id": resolve_instance_id(),
419
+ "version": _version(),
420
+ "provider": provider,
421
+ "model": model,
422
+ "memory_backend": resolve_memory_backend(env, rob_local),
423
+ })
424
+
425
+
426
+ # --- page routes (render the template; data fetched client-side via the API) - #
427
+
428
+ @router.get("/memory", response_class=HTMLResponse)
429
+ async def memory_page(request: Request):
430
+ return _TEMPLATES.TemplateResponse("memory.html", _page_context(request))
431
+
432
+
433
+ @router.get("/autonomy", response_class=HTMLResponse)
434
+ async def autonomy_page(request: Request):
435
+ return _TEMPLATES.TemplateResponse("autonomy.html", _page_context(request))
436
+
437
+
438
+ @router.get("/identity", response_class=HTMLResponse)
439
+ async def identity_page(request: Request):
440
+ return _TEMPLATES.TemplateResponse("identity.html", _page_context(request))
441
+
442
+
443
+ @router.get("/system", response_class=HTMLResponse)
444
+ async def system_page(request: Request):
445
+ return _TEMPLATES.TemplateResponse("system.html", _page_context(request))
446
+
447
+
448
+ __all__ = ["router"]