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