jvagent 0.1.1__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.
- jvagent/__init__.py +35 -0
- jvagent/__main__.py +6 -0
- jvagent/_logging_compat.py +70 -0
- jvagent/_mimetypes_compat.py +53 -0
- jvagent/action/AGENTS.md +1 -0
- jvagent/action/CLAUDE.md +145 -0
- jvagent/action/README.md +34 -0
- jvagent/action/__init__.py +33 -0
- jvagent/action/access_control/README.md +230 -0
- jvagent/action/access_control/__init__.py +10 -0
- jvagent/action/access_control/access_control_action.py +452 -0
- jvagent/action/access_control/endpoints.py +602 -0
- jvagent/action/access_control/info.yaml +17 -0
- jvagent/action/actions.py +728 -0
- jvagent/action/agent_utils/README.md +3 -0
- jvagent/action/agent_utils/__init__.py +10 -0
- jvagent/action/agent_utils/agent_utils.py +112 -0
- jvagent/action/agent_utils/endpoints.py +59 -0
- jvagent/action/agent_utils/info.yaml +16 -0
- jvagent/action/avatar_action/README.md +20 -0
- jvagent/action/avatar_action/__init__.py +10 -0
- jvagent/action/avatar_action/avatar_action.py +228 -0
- jvagent/action/avatar_action/endpoints.py +275 -0
- jvagent/action/avatar_action/info.yaml +14 -0
- jvagent/action/base.py +1297 -0
- jvagent/action/channels/__init__.py +1 -0
- jvagent/action/channels/media.py +159 -0
- jvagent/action/code_execution/__init__.py +8 -0
- jvagent/action/code_execution/code_execution_action.py +203 -0
- jvagent/action/code_execution/executor.py +223 -0
- jvagent/action/code_execution/info.yaml +20 -0
- jvagent/action/email_action/README.md +35 -0
- jvagent/action/email_action/__init__.py +6 -0
- jvagent/action/email_action/canonical_send_builder.py +191 -0
- jvagent/action/email_action/email_action.py +685 -0
- jvagent/action/email_action/email_adapter.py +222 -0
- jvagent/action/email_action/email_filter.py +100 -0
- jvagent/action/email_action/email_payload.py +74 -0
- jvagent/action/email_action/email_utterance.py +68 -0
- jvagent/action/email_action/email_webhook_helpers.py +172 -0
- jvagent/action/email_action/endpoints.py +536 -0
- jvagent/action/email_action/gmail_inbox.py +153 -0
- jvagent/action/email_action/inbound/__init__.py +7 -0
- jvagent/action/email_action/inbound/gmail.py +172 -0
- jvagent/action/email_action/inbound/outlook.py +125 -0
- jvagent/action/email_action/inbound/sendgrid.py +181 -0
- jvagent/action/email_action/inbound/text_utils.py +11 -0
- jvagent/action/email_action/info.yaml +21 -0
- jvagent/action/email_action/modules/__init__.py +15 -0
- jvagent/action/email_action/modules/base.py +42 -0
- jvagent/action/email_action/modules/gmail.py +151 -0
- jvagent/action/email_action/modules/outlook.py +135 -0
- jvagent/action/email_action/modules/sendgrid.py +294 -0
- jvagent/action/email_action/outlook_inbox.py +149 -0
- jvagent/action/email_action/utils/__init__.py +1 -0
- jvagent/action/email_action/webhook_auth.py +12 -0
- jvagent/action/endpoints.py +587 -0
- jvagent/action/facebook_action/README.md +142 -0
- jvagent/action/facebook_action/__init__.py +9 -0
- jvagent/action/facebook_action/endpoints.py +803 -0
- jvagent/action/facebook_action/facebook_action.py +844 -0
- jvagent/action/facebook_action/facebook_api.py +807 -0
- jvagent/action/facebook_action/info.yaml +16 -0
- jvagent/action/facebook_action/messenger_adapter.py +121 -0
- jvagent/action/facebook_action/messenger_filter.py +27 -0
- jvagent/action/facebook_action/messenger_message_coalescer.py +161 -0
- jvagent/action/facebook_action/messenger_voice_filter.py +58 -0
- jvagent/action/facebook_action/messenger_webhook_helpers.py +551 -0
- jvagent/action/facebook_action/webhook_auth.py +12 -0
- jvagent/action/file_interface/__init__.py +5 -0
- jvagent/action/file_interface/_core.py +271 -0
- jvagent/action/file_interface/file_interface_action.py +187 -0
- jvagent/action/file_interface/info.yaml +19 -0
- jvagent/action/google/__init__.py +5 -0
- jvagent/action/google/endpoints.py +385 -0
- jvagent/action/google/google_action.py +382 -0
- jvagent/action/google/google_calendar_action/README.md +86 -0
- jvagent/action/google/google_calendar_action/__init__.py +6 -0
- jvagent/action/google/google_calendar_action/endpoints.py +237 -0
- jvagent/action/google/google_calendar_action/google_calendar_action.py +121 -0
- jvagent/action/google/google_calendar_action/info.yaml +22 -0
- jvagent/action/google/google_docs_action/__init__.py +6 -0
- jvagent/action/google/google_docs_action/endpoints.py +135 -0
- jvagent/action/google/google_docs_action/google_docs_action.py +330 -0
- jvagent/action/google/google_docs_action/info.yaml +24 -0
- jvagent/action/google/google_drive_action/README.md +240 -0
- jvagent/action/google/google_drive_action/__init__.py +6 -0
- jvagent/action/google/google_drive_action/endpoints.py +408 -0
- jvagent/action/google/google_drive_action/google_drive_action.py +421 -0
- jvagent/action/google/google_drive_action/info.yaml +24 -0
- jvagent/action/google/google_gmail_action/README.md +64 -0
- jvagent/action/google/google_gmail_action/__init__.py +6 -0
- jvagent/action/google/google_gmail_action/endpoints.py +170 -0
- jvagent/action/google/google_gmail_action/google_gmail_action.py +138 -0
- jvagent/action/google/google_gmail_action/info.yaml +23 -0
- jvagent/action/google/google_sheets_action/README.md +88 -0
- jvagent/action/google/google_sheets_action/__init__.py +6 -0
- jvagent/action/google/google_sheets_action/endpoints.py +1098 -0
- jvagent/action/google/google_sheets_action/google_sheets_action.py +1264 -0
- jvagent/action/google/google_sheets_action/info.yaml +22 -0
- jvagent/action/google/google_token.py +61 -0
- jvagent/action/handoff_interact_action/README.md +13 -0
- jvagent/action/handoff_interact_action/__init__.py +5 -0
- jvagent/action/handoff_interact_action/handoff_interact_action.py +454 -0
- jvagent/action/handoff_interact_action/info.yaml +37 -0
- jvagent/action/interact/AGENTS.md +1 -0
- jvagent/action/interact/CLAUDE.md +139 -0
- jvagent/action/interact/README.md +453 -0
- jvagent/action/interact/__init__.py +12 -0
- jvagent/action/interact/base.py +543 -0
- jvagent/action/interact/conversation_lock_manager.py +51 -0
- jvagent/action/interact/endpoints.py +929 -0
- jvagent/action/interact/interact_walker.py +1013 -0
- jvagent/action/interact/rate_limiter.py +328 -0
- jvagent/action/interact/response_builder.py +233 -0
- jvagent/action/interact/session_token.py +406 -0
- jvagent/action/interact/utils/__init__.py +1 -0
- jvagent/action/interact/utils/uploads.py +225 -0
- jvagent/action/interact/webhook_pipeline.py +394 -0
- jvagent/action/interview/AGENTS.md +1 -0
- jvagent/action/interview/CLAUDE.md +119 -0
- jvagent/action/interview/README.md +507 -0
- jvagent/action/interview/SKILL.md +100 -0
- jvagent/action/interview/__init__.py +24 -0
- jvagent/action/interview/_validate_contract.py +65 -0
- jvagent/action/interview/activation_seed.py +146 -0
- jvagent/action/interview/directive_compose.py +175 -0
- jvagent/action/interview/docs/extending.md +553 -0
- jvagent/action/interview/docs/frontmatter-schema.md +165 -0
- jvagent/action/interview/docs/multi-turn-flow.md +239 -0
- jvagent/action/interview/docs/skill_custom_instructions.md +102 -0
- jvagent/action/interview/docs/thin-harness.md +92 -0
- jvagent/action/interview/docs/troubleshooting.md +204 -0
- jvagent/action/interview/engine.py +1852 -0
- jvagent/action/interview/examples/example_account_gating/README.md +57 -0
- jvagent/action/interview/examples/example_account_gating/example_booking_interview/SKILL.md +65 -0
- jvagent/action/interview/examples/example_account_gating/example_signin_interview/SKILL.md +37 -0
- jvagent/action/interview/examples/example_account_gating/use-cases/booking-gated-no-session.yaml +34 -0
- jvagent/action/interview/examples/example_account_gating/use-cases/booking-with-session.yaml +32 -0
- jvagent/action/interview/examples/example_for_each_interview/SKILL.md +57 -0
- jvagent/action/interview/examples/example_for_each_interview/scripts/custom_tools.py +59 -0
- jvagent/action/interview/examples/example_interview/SKILL.md +95 -0
- jvagent/action/interview/examples/example_interview/scripts/custom_tools.py +331 -0
- jvagent/action/interview/flow.py +477 -0
- jvagent/action/interview/for_each.py +398 -0
- jvagent/action/interview/hooks.py +709 -0
- jvagent/action/interview/info.yaml +23 -0
- jvagent/action/interview/interview_action.py +372 -0
- jvagent/action/interview/procedure.py +33 -0
- jvagent/action/interview/session.py +168 -0
- jvagent/action/interview/spec.py +403 -0
- jvagent/action/interview/tasks.py +133 -0
- jvagent/action/interview/tools.py +220 -0
- jvagent/action/interview/validators.py +246 -0
- jvagent/action/intro/README.md +62 -0
- jvagent/action/intro/__init__.py +5 -0
- jvagent/action/intro/info.yaml +26 -0
- jvagent/action/intro/intro_interact_action.py +132 -0
- jvagent/action/leadgen/README.md +42 -0
- jvagent/action/leadgen/SKILL.md +58 -0
- jvagent/action/leadgen/__init__.py +12 -0
- jvagent/action/leadgen/_validate_contract.py +46 -0
- jvagent/action/leadgen/docs/extending.md +41 -0
- jvagent/action/leadgen/docs/frontmatter-schema.md +104 -0
- jvagent/action/leadgen/docs/thin-harness.md +25 -0
- jvagent/action/leadgen/docs/troubleshooting.md +12 -0
- jvagent/action/leadgen/engine.py +413 -0
- jvagent/action/leadgen/examples/example_leadgen/SKILL.md +80 -0
- jvagent/action/leadgen/examples/example_leadgen/scripts/custom_tools.py +21 -0
- jvagent/action/leadgen/hooks.py +97 -0
- jvagent/action/leadgen/info.yaml +15 -0
- jvagent/action/leadgen/leadgen_action.py +148 -0
- jvagent/action/leadgen/procedure.py +21 -0
- jvagent/action/leadgen/spec.py +240 -0
- jvagent/action/leadgen/store.py +255 -0
- jvagent/action/leadgen/sync.py +166 -0
- jvagent/action/leadgen/tools.py +178 -0
- jvagent/action/leadgen/validators.py +97 -0
- jvagent/action/loader/__init__.py +14 -0
- jvagent/action/loader/action_loader.py +1300 -0
- jvagent/action/loader/core_discovery.py +129 -0
- jvagent/action/loader/factory.py +44 -0
- jvagent/action/loader/importer.py +153 -0
- jvagent/action/loader/info_yaml.py +46 -0
- jvagent/action/loader/metadata.py +128 -0
- jvagent/action/loader/module_loading.py +167 -0
- jvagent/action/manifest.py +298 -0
- jvagent/action/mcp/README.md +142 -0
- jvagent/action/mcp/__init__.py +6 -0
- jvagent/action/mcp/client.py +215 -0
- jvagent/action/mcp/fs_server_factory.py +112 -0
- jvagent/action/mcp/info.yaml +20 -0
- jvagent/action/mcp/jvspatial_fs_server.py +308 -0
- jvagent/action/mcp/mcp_action.py +878 -0
- jvagent/action/mcp/prompts.py +35 -0
- jvagent/action/mcp/result.py +25 -0
- jvagent/action/microsoft/README.md +111 -0
- jvagent/action/microsoft/__init__.py +3 -0
- jvagent/action/microsoft/endpoints.py +333 -0
- jvagent/action/microsoft/microsoft_action.py +347 -0
- jvagent/action/microsoft/microsoft_excel_action/README.md +58 -0
- jvagent/action/microsoft/microsoft_excel_action/__init__.py +4 -0
- jvagent/action/microsoft/microsoft_excel_action/endpoints.py +187 -0
- jvagent/action/microsoft/microsoft_excel_action/info.yaml +18 -0
- jvagent/action/microsoft/microsoft_excel_action/microsoft_excel_action.py +583 -0
- jvagent/action/microsoft/microsoft_onedrive_action/README.md +44 -0
- jvagent/action/microsoft/microsoft_onedrive_action/__init__.py +4 -0
- jvagent/action/microsoft/microsoft_onedrive_action/endpoints.py +260 -0
- jvagent/action/microsoft/microsoft_onedrive_action/info.yaml +17 -0
- jvagent/action/microsoft/microsoft_onedrive_action/microsoft_onedrive_action.py +293 -0
- jvagent/action/microsoft/microsoft_outlook_calendar_action/README.md +38 -0
- jvagent/action/microsoft/microsoft_outlook_calendar_action/__init__.py +4 -0
- jvagent/action/microsoft/microsoft_outlook_calendar_action/endpoints.py +168 -0
- jvagent/action/microsoft/microsoft_outlook_calendar_action/info.yaml +17 -0
- jvagent/action/microsoft/microsoft_outlook_calendar_action/microsoft_outlook_calendar_action.py +163 -0
- jvagent/action/microsoft/microsoft_outlook_mail_action/README.md +43 -0
- jvagent/action/microsoft/microsoft_outlook_mail_action/__init__.py +4 -0
- jvagent/action/microsoft/microsoft_outlook_mail_action/endpoints.py +161 -0
- jvagent/action/microsoft/microsoft_outlook_mail_action/info.yaml +17 -0
- jvagent/action/microsoft/microsoft_outlook_mail_action/microsoft_outlook_mail_action.py +253 -0
- jvagent/action/microsoft/microsoft_token.py +60 -0
- jvagent/action/model/README.md +783 -0
- jvagent/action/model/__init__.py +72 -0
- jvagent/action/model/base.py +482 -0
- jvagent/action/model/context.py +259 -0
- jvagent/action/model/cost_estimator.py +108 -0
- jvagent/action/model/embedding/__init__.py +24 -0
- jvagent/action/model/embedding/base.py +152 -0
- jvagent/action/model/embedding/endpoints.py +459 -0
- jvagent/action/model/embedding/generic/__init__.py +5 -0
- jvagent/action/model/embedding/generic/generic.py +191 -0
- jvagent/action/model/embedding/generic/info.yaml +34 -0
- jvagent/action/model/embedding/huggingface/__init__.py +5 -0
- jvagent/action/model/embedding/huggingface/huggingface.py +132 -0
- jvagent/action/model/embedding/huggingface/info.yaml +34 -0
- jvagent/action/model/embedding/ollama/__init__.py +5 -0
- jvagent/action/model/embedding/ollama/info.yaml +22 -0
- jvagent/action/model/embedding/ollama/ollama.py +117 -0
- jvagent/action/model/embedding/openai/__init__.py +5 -0
- jvagent/action/model/embedding/openai/info.yaml +35 -0
- jvagent/action/model/embedding/openai/openai.py +180 -0
- jvagent/action/model/embedding/openrouter/__init__.py +5 -0
- jvagent/action/model/embedding/openrouter/info.yaml +34 -0
- jvagent/action/model/embedding/openrouter/openrouter.py +136 -0
- jvagent/action/model/language/__init__.py +39 -0
- jvagent/action/model/language/anthropic/__init__.py +5 -0
- jvagent/action/model/language/anthropic/anthropic.py +625 -0
- jvagent/action/model/language/anthropic/info.yaml +22 -0
- jvagent/action/model/language/base.py +1238 -0
- jvagent/action/model/language/endpoints.py +498 -0
- jvagent/action/model/language/groq/__init__.py +5 -0
- jvagent/action/model/language/groq/groq.py +100 -0
- jvagent/action/model/language/groq/info.yaml +37 -0
- jvagent/action/model/language/ollama/__init__.py +5 -0
- jvagent/action/model/language/ollama/info.yaml +22 -0
- jvagent/action/model/language/ollama/ollama.py +475 -0
- jvagent/action/model/language/openai/__init__.py +5 -0
- jvagent/action/model/language/openai/info.yaml +35 -0
- jvagent/action/model/language/openai/openai.py +702 -0
- jvagent/action/model/language/openrouter/__init__.py +5 -0
- jvagent/action/model/language/openrouter/info.yaml +34 -0
- jvagent/action/model/language/openrouter/openrouter.py +222 -0
- jvagent/action/model/language/templates.py +255 -0
- jvagent/action/model/language/tools.py +405 -0
- jvagent/action/model/ollama_endpoint.py +15 -0
- jvagent/action/model/utils/__init__.py +1 -0
- jvagent/action/model/utils/message_format.py +87 -0
- jvagent/action/model/utils/token_estimation.py +201 -0
- jvagent/action/oauth/__init__.py +29 -0
- jvagent/action/oauth/audit.py +92 -0
- jvagent/action/oauth/state.py +162 -0
- jvagent/action/oauth/token_crypto.py +219 -0
- jvagent/action/orchestrator/__init__.py +21 -0
- jvagent/action/orchestrator/access.py +66 -0
- jvagent/action/orchestrator/catalog.py +236 -0
- jvagent/action/orchestrator/constants.py +37 -0
- jvagent/action/orchestrator/continuation.py +326 -0
- jvagent/action/orchestrator/core_tools.py +298 -0
- jvagent/action/orchestrator/egress.py +127 -0
- jvagent/action/orchestrator/info.yaml +33 -0
- jvagent/action/orchestrator/loop.py +867 -0
- jvagent/action/orchestrator/loop_helpers.py +16 -0
- jvagent/action/orchestrator/orchestrator_interact_action.py +2790 -0
- jvagent/action/orchestrator/preconditions.py +74 -0
- jvagent/action/orchestrator/proactive_tools.py +73 -0
- jvagent/action/orchestrator/prompts.py +252 -0
- jvagent/action/orchestrator/skill_providers.py +59 -0
- jvagent/action/orchestrator/skill_tasks.py +1011 -0
- jvagent/action/orchestrator/skills.py +204 -0
- jvagent/action/orchestrator/task_runners.py +95 -0
- jvagent/action/orchestrator/tools.py +164 -0
- jvagent/action/orchestrator/uploads.py +291 -0
- jvagent/action/orchestrator/walk_path.py +48 -0
- jvagent/action/pageindex/README.md +228 -0
- jvagent/action/pageindex/__init__.py +50 -0
- jvagent/action/pageindex/adapter.py +265 -0
- jvagent/action/pageindex/config.py +439 -0
- jvagent/action/pageindex/core/README.md +16 -0
- jvagent/action/pageindex/core/__init__.py +2 -0
- jvagent/action/pageindex/core/config.yaml +9 -0
- jvagent/action/pageindex/core/page_index.py +1389 -0
- jvagent/action/pageindex/core/page_index_md.py +419 -0
- jvagent/action/pageindex/core/utils.py +722 -0
- jvagent/action/pageindex/docling_convert.py +252 -0
- jvagent/action/pageindex/document_walker.py +105 -0
- jvagent/action/pageindex/documents.py +1307 -0
- jvagent/action/pageindex/endpoints.py +2241 -0
- jvagent/action/pageindex/jvforge_assimilate.py +363 -0
- jvagent/action/pageindex/jvforge_routing.py +34 -0
- jvagent/action/pageindex/lexical_index.py +326 -0
- jvagent/action/pageindex/llm_bridge.py +148 -0
- jvagent/action/pageindex/llm_override.py +59 -0
- jvagent/action/pageindex/markdown_pages.py +108 -0
- jvagent/action/pageindex/md_tree_enriched.py +667 -0
- jvagent/action/pageindex/models.py +200 -0
- jvagent/action/pageindex/pageindex_action/__init__.py +13 -0
- jvagent/action/pageindex/pageindex_action/info.yaml +30 -0
- jvagent/action/pageindex/pageindex_action/pageindex_action.py +949 -0
- jvagent/action/pageindex/pageindex_action/runtime_config.py +133 -0
- jvagent/action/pageindex/pageindex_google_drive_sync_action/README.md +195 -0
- jvagent/action/pageindex/pageindex_google_drive_sync_action/__init__.py +21 -0
- jvagent/action/pageindex/pageindex_google_drive_sync_action/drive_ingest_filter.py +55 -0
- jvagent/action/pageindex/pageindex_google_drive_sync_action/endpoints.py +693 -0
- jvagent/action/pageindex/pageindex_google_drive_sync_action/google_drive_documents.py +52 -0
- jvagent/action/pageindex/pageindex_google_drive_sync_action/info.yaml +30 -0
- jvagent/action/pageindex/pageindex_google_drive_sync_action/pageindex_google_drive_sync_action.py +1629 -0
- jvagent/action/pageindex/pageindex_google_drive_sync_action/webhook_auth.py +12 -0
- jvagent/action/pageindex/prompts.py +39 -0
- jvagent/action/pageindex/ranking.py +70 -0
- jvagent/action/pageindex/retrieval.py +888 -0
- jvagent/action/pageindex/tokenizer.py +127 -0
- jvagent/action/pageindex/url_guard.py +133 -0
- jvagent/action/pageindex/webhook_auth.py +24 -0
- jvagent/action/parameters.py +356 -0
- jvagent/action/plugin_contracts.py +32 -0
- jvagent/action/postiz_action/README.md +72 -0
- jvagent/action/postiz_action/__init__.py +4 -0
- jvagent/action/postiz_action/endpoints.py +74 -0
- jvagent/action/postiz_action/info.yaml +17 -0
- jvagent/action/postiz_action/postiz_action.py +285 -0
- jvagent/action/reply/__init__.py +16 -0
- jvagent/action/reply/endpoints.py +202 -0
- jvagent/action/reply/info.yaml +19 -0
- jvagent/action/reply/reply_action.py +803 -0
- jvagent/action/response/README.md +664 -0
- jvagent/action/response/__init__.py +28 -0
- jvagent/action/response/channel_adapter.py +105 -0
- jvagent/action/response/channel_filter.py +121 -0
- jvagent/action/response/chunking.py +153 -0
- jvagent/action/response/message.py +127 -0
- jvagent/action/response/response_bus.py +1066 -0
- jvagent/action/response/streaming.py +120 -0
- jvagent/action/response/thought_formatting.py +44 -0
- jvagent/action/sentdm_broadcast/README.md +193 -0
- jvagent/action/sentdm_broadcast/__init__.py +11 -0
- jvagent/action/sentdm_broadcast/broadcast_tester.py +843 -0
- jvagent/action/sentdm_broadcast/docs/openapi.json +3159 -0
- jvagent/action/sentdm_broadcast/docs/openapi.yaml +2691 -0
- jvagent/action/sentdm_broadcast/docs/postman.json +2074 -0
- jvagent/action/sentdm_broadcast/endpoints.py +551 -0
- jvagent/action/sentdm_broadcast/info.yaml +17 -0
- jvagent/action/sentdm_broadcast/models.py +135 -0
- jvagent/action/sentdm_broadcast/sentdm_broadcast_action.py +1545 -0
- jvagent/action/sentdm_broadcast/webhook_auth.py +12 -0
- jvagent/action/sentdm_broadcast/webhook_debug.py +62 -0
- jvagent/action/skill_hub/__init__.py +5 -0
- jvagent/action/skill_hub/_installer.py +294 -0
- jvagent/action/skill_hub/_skills_cli.py +268 -0
- jvagent/action/skill_hub/info.yaml +22 -0
- jvagent/action/skill_hub/skill_hub_action.py +484 -0
- jvagent/action/skill_spec/__init__.py +39 -0
- jvagent/action/skill_spec/base.py +129 -0
- jvagent/action/skill_spec/contract.py +133 -0
- jvagent/action/skill_spec/docs/thin-harness.md +20 -0
- jvagent/action/skill_spec/registry.py +68 -0
- jvagent/action/skill_spec/task_lock.py +15 -0
- jvagent/action/spreadsheet/__init__.py +1 -0
- jvagent/action/spreadsheet/range_utils.py +45 -0
- jvagent/action/streaming.py +18 -0
- jvagent/action/stt_action/README.md +106 -0
- jvagent/action/stt_action/__init__.py +12 -0
- jvagent/action/stt_action/base.py +87 -0
- jvagent/action/stt_action/deepgram/__init__.py +3 -0
- jvagent/action/stt_action/deepgram/deepgram.py +229 -0
- jvagent/action/stt_action/deepgram/info.yaml +17 -0
- jvagent/action/stt_action/endpoints.py +108 -0
- jvagent/action/task_creation_interact_action/__init__.py +7 -0
- jvagent/action/task_creation_interact_action/info.yaml +17 -0
- jvagent/action/task_creation_interact_action/prompts.py +55 -0
- jvagent/action/task_creation_interact_action/task_creation_interact_action.py +326 -0
- jvagent/action/task_monitor/__init__.py +6 -0
- jvagent/action/task_monitor/endpoints.py +120 -0
- jvagent/action/task_monitor/finalize.py +117 -0
- jvagent/action/task_monitor/info.yaml +16 -0
- jvagent/action/task_monitor/task_monitor.py +464 -0
- jvagent/action/task_trigger_interact_action/__init__.py +7 -0
- jvagent/action/task_trigger_interact_action/info.yaml +17 -0
- jvagent/action/task_trigger_interact_action/task_trigger_interact_action.py +84 -0
- jvagent/action/tts_action/README.md +130 -0
- jvagent/action/tts_action/__init__.py +12 -0
- jvagent/action/tts_action/base.py +80 -0
- jvagent/action/tts_action/elevenlabs/__init__.py +3 -0
- jvagent/action/tts_action/elevenlabs/elevenlabs.py +215 -0
- jvagent/action/tts_action/elevenlabs/info.yaml +17 -0
- jvagent/action/tts_action/endpoints.py +178 -0
- jvagent/action/utils/__init__.py +17 -0
- jvagent/action/utils/endpoint_helpers.py +31 -0
- jvagent/action/utils/meta_webhook.py +23 -0
- jvagent/action/utils/webhook_reconcile.py +121 -0
- jvagent/action/utils/webhook_system_user.py +68 -0
- jvagent/action/vectorstore/__init__.py +18 -0
- jvagent/action/vectorstore/base.py +378 -0
- jvagent/action/vectorstore/endpoints.py +466 -0
- jvagent/action/vectorstore/typesense/__init__.py +5 -0
- jvagent/action/vectorstore/typesense/info.yaml +34 -0
- jvagent/action/vectorstore/typesense/typesense.py +703 -0
- jvagent/action/video_generation/README.md +37 -0
- jvagent/action/video_generation/__init__.py +1 -0
- jvagent/action/video_generation/heygen.py +558 -0
- jvagent/action/video_generation/info.yaml +17 -0
- jvagent/action/vision/__init__.py +5 -0
- jvagent/action/vision/info.yaml +23 -0
- jvagent/action/vision/multimodal.py +138 -0
- jvagent/action/vision/prompts.py +11 -0
- jvagent/action/vision/vision_action.py +144 -0
- jvagent/action/web_fetch/__init__.py +5 -0
- jvagent/action/web_fetch/info.yaml +22 -0
- jvagent/action/web_fetch/web_fetch_action.py +202 -0
- jvagent/action/web_search/README.md +133 -0
- jvagent/action/web_search/__init__.py +20 -0
- jvagent/action/web_search/base.py +62 -0
- jvagent/action/web_search/brave/__init__.py +3 -0
- jvagent/action/web_search/brave/brave.py +131 -0
- jvagent/action/web_search/brave/info.yaml +17 -0
- jvagent/action/web_search/prompts.py +8 -0
- jvagent/action/web_search/serpapi/__init__.py +3 -0
- jvagent/action/web_search/serpapi/info.yaml +18 -0
- jvagent/action/web_search/serpapi/serpapi.py +116 -0
- jvagent/action/web_search/serper/__init__.py +3 -0
- jvagent/action/web_search/serper/info.yaml +16 -0
- jvagent/action/web_search/serper/serper.py +134 -0
- jvagent/action/whatsapp/README.md +698 -0
- jvagent/action/whatsapp/__init__.py +13 -0
- jvagent/action/whatsapp/endpoints.py +1378 -0
- jvagent/action/whatsapp/info.yaml +19 -0
- jvagent/action/whatsapp/modules/base.py +643 -0
- jvagent/action/whatsapp/modules/meta_api.py +907 -0
- jvagent/action/whatsapp/modules/registry.py +41 -0
- jvagent/action/whatsapp/modules/ultramsg.py +592 -0
- jvagent/action/whatsapp/modules/wppconnect.py +421 -0
- jvagent/action/whatsapp/modules/wwebjs_api.py +965 -0
- jvagent/action/whatsapp/utils/__init__.py +1 -0
- jvagent/action/whatsapp/utils/conversation_lock_manager.py +5 -0
- jvagent/action/whatsapp/utils/endpoint_helpers.py +652 -0
- jvagent/action/whatsapp/utils/media_batch_manager.py +756 -0
- jvagent/action/whatsapp/utils/meta_audio.py +59 -0
- jvagent/action/whatsapp/utils/meta_verify_token.py +22 -0
- jvagent/action/whatsapp/utils/meta_webhook_dedup.py +65 -0
- jvagent/action/whatsapp/utils/meta_webhook_verify.py +153 -0
- jvagent/action/whatsapp/utils/typing_state_manager.py +49 -0
- jvagent/action/whatsapp/webhook_auth.py +12 -0
- jvagent/action/whatsapp/whatsapp_action.py +1122 -0
- jvagent/action/whatsapp/whatsapp_adapter.py +300 -0
- jvagent/action/whatsapp/whatsapp_filter.py +61 -0
- jvagent/action/whatsapp/whatsapp_voice_filter.py +68 -0
- jvagent/bundle/README.md +176 -0
- jvagent/bundle/__init__.py +5 -0
- jvagent/bundle/bundler.py +74 -0
- jvagent/bundle/dockerfile_generator.py +196 -0
- jvagent/cli/AGENTS.md +1 -0
- jvagent/cli/CLAUDE.md +138 -0
- jvagent/cli/__init__.py +5 -0
- jvagent/cli/__main__.py +6 -0
- jvagent/cli/agent_commands.py +363 -0
- jvagent/cli/app_commands.py +220 -0
- jvagent/cli/bootstrap.py +244 -0
- jvagent/cli/chat.py +77 -0
- jvagent/cli/commands.py +40 -0
- jvagent/cli/main.py +249 -0
- jvagent/cli/server.py +616 -0
- jvagent/cli/server_config.py +618 -0
- jvagent/cli/skill_commands.py +540 -0
- jvagent/cli/validate.py +184 -0
- jvagent/core/AGENTS.md +1 -0
- jvagent/core/CLAUDE.md +123 -0
- jvagent/core/__init__.py +40 -0
- jvagent/core/agent.py +444 -0
- jvagent/core/agent_loader.py +761 -0
- jvagent/core/agent_yaml_validator.py +163 -0
- jvagent/core/agents.py +302 -0
- jvagent/core/app.py +599 -0
- jvagent/core/app_context.py +39 -0
- jvagent/core/app_loader.py +569 -0
- jvagent/core/app_yaml_validator.py +323 -0
- jvagent/core/bootstrap_logger.py +82 -0
- jvagent/core/bootstrap_update_mode.py +77 -0
- jvagent/core/cache.py +398 -0
- jvagent/core/callback.py +380 -0
- jvagent/core/channel.py +27 -0
- jvagent/core/config.py +631 -0
- jvagent/core/dependency_installer.py +212 -0
- jvagent/core/embed_endpoints.py +42 -0
- jvagent/core/endpoints/__init__.py +54 -0
- jvagent/core/endpoints/agents.py +263 -0
- jvagent/core/endpoints/app.py +56 -0
- jvagent/core/endpoints/conversation.py +107 -0
- jvagent/core/endpoints/graph_repair.py +262 -0
- jvagent/core/endpoints/status.py +30 -0
- jvagent/core/env_resolver.py +119 -0
- jvagent/core/errors.py +190 -0
- jvagent/core/graph_repair.py +524 -0
- jvagent/core/graph_repair_handlers.py +339 -0
- jvagent/core/graph_repair_job.py +1469 -0
- jvagent/core/index_bootstrap.py +179 -0
- jvagent/core/jvspatial_compat.py +50 -0
- jvagent/core/observability.py +40 -0
- jvagent/core/profiling.py +382 -0
- jvagent/core/public_url.py +9 -0
- jvagent/core/repair_phases/__init__.py +27 -0
- jvagent/core/repair_phases/engine.py +5 -0
- jvagent/core/repair_phases/memory.py +205 -0
- jvagent/core/repair_phases/types.py +43 -0
- jvagent/core/repair_scratch.py +201 -0
- jvagent/core/repair_state.py +388 -0
- jvagent/core/sandbox.py +271 -0
- jvagent/core/scheduler_bootstrap.py +169 -0
- jvagent/core/startup.py +172 -0
- jvagent/core/yaml_validation_utils.py +59 -0
- jvagent/embed/__init__.py +66 -0
- jvagent/embed/bootstrap.py +295 -0
- jvagent/embed/interact.py +793 -0
- jvagent/env.py +49 -0
- jvagent/logging/AGENTS.md +1 -0
- jvagent/logging/CLAUDE.md +96 -0
- jvagent/logging/__init__.py +21 -0
- jvagent/logging/endpoints.py +113 -0
- jvagent/logging/service.py +20 -0
- jvagent/memory/AGENTS.md +1 -0
- jvagent/memory/CLAUDE.md +130 -0
- jvagent/memory/README.md +92 -0
- jvagent/memory/__init__.py +27 -0
- jvagent/memory/artifact.py +98 -0
- jvagent/memory/conversation.py +1231 -0
- jvagent/memory/distributed_conversation_lock.py +306 -0
- jvagent/memory/endpoints.py +547 -0
- jvagent/memory/evidence_log.py +205 -0
- jvagent/memory/interaction.py +776 -0
- jvagent/memory/lock_manager.py +143 -0
- jvagent/memory/manager.py +1182 -0
- jvagent/memory/services/__init__.py +9 -0
- jvagent/memory/task_eligibility.py +161 -0
- jvagent/memory/task_graph.py +108 -0
- jvagent/memory/task_payload.py +37 -0
- jvagent/memory/task_proactive.py +105 -0
- jvagent/memory/task_store.py +1149 -0
- jvagent/memory/user.py +329 -0
- jvagent/scaffold/__init__.py +18 -0
- jvagent/scaffold/builtin_profiles/__init__.py +1 -0
- jvagent/scaffold/builtin_profiles/conversational.yaml +6 -0
- jvagent/scaffold/builtin_profiles/minimal.yaml +34 -0
- jvagent/scaffold/builtin_profiles/orchestrator.yaml +89 -0
- jvagent/scaffold/builtin_profiles/research.yaml +6 -0
- jvagent/scaffold/builtin_profiles/whatsapp_voice.yaml +19 -0
- jvagent/scaffold/interact_action.py +218 -0
- jvagent/scaffold/operations.py +585 -0
- jvagent/scaffold/profile_resolve.py +186 -0
- jvagent/scaffold/profile_stub.py +33 -0
- jvagent/scaffold/resource_io.py +46 -0
- jvagent/scaffold/skill_resolve.py +692 -0
- jvagent/scaffold/sop_extend.py +704 -0
- jvagent/scaffold/static/__init__.py +1 -0
- jvagent/scaffold/static/env.example.txt +196 -0
- jvagent/scaffold/yaml_io.py +104 -0
- jvagent/schemas/use-case-v1.schema.json +224 -0
- jvagent/skills/README.md +297 -0
- jvagent/skills/__init__.py +1 -0
- jvagent/skills/answer/SKILL.md +80 -0
- jvagent/skills/pageindex_search_with_references/SKILL.md +97 -0
- jvagent/skills/pdf_generation/SKILL.md +63 -0
- jvagent/skills/pdf_generation/resources/requirements.txt +19 -0
- jvagent/skills/pdf_generation/scripts/render_pdf.py +118 -0
- jvagent/skills/research/SKILL.md +41 -0
- jvagent/skills/research/__init__.py +1 -0
- jvagent/skills/triage/SKILL.md +49 -0
- jvagent/skills/triage/scripts/prioritize.py +59 -0
- jvagent/stress_seed_graph.py +455 -0
- jvagent/testing/__init__.py +9 -0
- jvagent/testing/use_case_loader.py +59 -0
- jvagent/tooling/__init__.py +19 -0
- jvagent/tooling/signature_schema.py +180 -0
- jvagent/tooling/tool.py +85 -0
- jvagent/tooling/tool_decorator.py +215 -0
- jvagent/tooling/tool_executor.py +251 -0
- jvagent/tooling/tool_observability.py +50 -0
- jvagent/tooling/tool_registry.py +104 -0
- jvagent/tooling/tool_result.py +39 -0
- jvagent/tooling/tool_schema_validator.py +116 -0
- jvagent/tooling/tool_serializer.py +24 -0
- jvagent/version.py +3 -0
- jvagent/webui/__init__.py +11 -0
- jvagent/webui/dist/assets/ChatInterface-C61tS6kJ.js +267 -0
- jvagent/webui/dist/assets/ChatInterface-CpKl2eGN.css +1 -0
- jvagent/webui/dist/assets/ChatRedirect-bolUSsJG.js +1 -0
- jvagent/webui/dist/assets/DebugInteractions-BGGM2vNQ.js +47 -0
- jvagent/webui/dist/assets/Login-Bep8PiER.js +1 -0
- jvagent/webui/dist/assets/main-DSMdJ_OX.css +1 -0
- jvagent/webui/dist/assets/main-yV_cwtCQ.js +13 -0
- jvagent/webui/dist/assets/useAgents-CdIk_6vr.js +1 -0
- jvagent/webui/dist/assets/useAuth-DoodL4QV.js +1 -0
- jvagent/webui/dist/assets/vendor-cytoscape-CUqq0XTU.js +331 -0
- jvagent/webui/dist/assets/vendor-d3-DznK3MmS.js +5 -0
- jvagent/webui/dist/assets/vendor-dagre-bridge-B2IYeVxy.js +6 -0
- jvagent/webui/dist/assets/vendor-graphviz-C9sQj0WI.js +3 -0
- jvagent/webui/dist/assets/vendor-markdown-B9moJQqy.js +14 -0
- jvagent/webui/dist/assets/vendor-react-CEqf_TvG.js +60 -0
- jvagent/webui/dist/index.html +34 -0
- jvagent/webui/server.py +183 -0
- jvagent-0.1.1.dist-info/METADATA +380 -0
- jvagent-0.1.1.dist-info/RECORD +624 -0
- jvagent-0.1.1.dist-info/WHEEL +5 -0
- jvagent-0.1.1.dist-info/entry_points.txt +2 -0
- jvagent-0.1.1.dist-info/licenses/AUTHORS +15 -0
- jvagent-0.1.1.dist-info/licenses/LICENSE +21 -0
- jvagent-0.1.1.dist-info/top_level.txt +1 -0
jvagent/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""jvagent - Agentive Platform built on jvspatial.
|
|
2
|
+
|
|
3
|
+
Two supported usage modes:
|
|
4
|
+
|
|
5
|
+
* **Standalone server** (CLI) — ``jvagent run`` boots jvagent's own
|
|
6
|
+
jvspatial ``Server`` and HTTP surface. The default for new apps.
|
|
7
|
+
* **Embedded library** — host an existing jvspatial app (e.g. another
|
|
8
|
+
product also built on jvspatial) calls :mod:`jvagent.embed` to mount
|
|
9
|
+
jvagent's runtime in-process, sharing the host's database and
|
|
10
|
+
authentication. See :mod:`jvagent.embed` for the supported surface.
|
|
11
|
+
|
|
12
|
+
Public, semver-tracked exports live here. Anything reached via
|
|
13
|
+
``jvagent.<internal_module>`` is implementation detail and may break
|
|
14
|
+
between minor versions.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# Install the Logger subclass that accepts ``details=`` as a kwarg before
|
|
18
|
+
# any submodule's ``logging.getLogger(__name__)`` call runs. See
|
|
19
|
+
# ``jvagent/_logging_compat.py`` and AUDIT-INDEX §2.2.
|
|
20
|
+
from jvagent._logging_compat import install as _install_logging_compat
|
|
21
|
+
|
|
22
|
+
_install_logging_compat()
|
|
23
|
+
|
|
24
|
+
# Register markdown extensions in ``mimetypes`` so jvspatial's storage MIME
|
|
25
|
+
# allow-list accepts ``.md`` saves on hosts without libmagic or an OS mime
|
|
26
|
+
# entry for markdown. Must run before any file-save path. See
|
|
27
|
+
# ``jvagent/_mimetypes_compat.py``.
|
|
28
|
+
from jvagent._mimetypes_compat import install as _install_mimetypes_compat # noqa: E402
|
|
29
|
+
|
|
30
|
+
_install_mimetypes_compat()
|
|
31
|
+
|
|
32
|
+
from jvagent import embed # noqa: E402 -- must follow logging shim install
|
|
33
|
+
from jvagent.version import __version__ # noqa: E402
|
|
34
|
+
|
|
35
|
+
__all__ = ["__version__", "embed"]
|
jvagent/__main__.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Logger compatibility shim.
|
|
2
|
+
|
|
3
|
+
jvagent calls ``logger.error(..., details={...})`` extensively across the
|
|
4
|
+
codebase. Stdlib :class:`logging.Logger` does not accept ``details`` as a
|
|
5
|
+
kwarg; it raises ``TypeError: Logger._log() got an unexpected keyword
|
|
6
|
+
argument 'details'``. This shim installs a :class:`Logger` subclass that
|
|
7
|
+
recognizes ``details=`` and routes it to ``extra["details"]`` so that
|
|
8
|
+
:class:`jvspatial.logging.handler.DBLogHandler` picks it up under its
|
|
9
|
+
documented ``extra`` contract.
|
|
10
|
+
|
|
11
|
+
Install BEFORE any ``logging.getLogger(...)`` call in jvagent — see
|
|
12
|
+
``jvagent/__init__.py``.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from collections.abc import Mapping
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class JvAgentLogger(logging.Logger):
|
|
21
|
+
"""Stdlib-compatible :class:`Logger` that also accepts a ``details`` kwarg.
|
|
22
|
+
|
|
23
|
+
Any ``details=`` passed to ``logger.error/warning/info/debug/critical``
|
|
24
|
+
(and the underlying ``_log``) is merged into the ``extra`` dict under the
|
|
25
|
+
``"details"`` key. Caller-supplied ``extra["details"]`` wins on collision.
|
|
26
|
+
All other kwargs pass through unchanged.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def _log(
|
|
30
|
+
self,
|
|
31
|
+
level: int,
|
|
32
|
+
msg: object,
|
|
33
|
+
args: Any,
|
|
34
|
+
exc_info: Any = None,
|
|
35
|
+
extra: Any = None,
|
|
36
|
+
stack_info: bool = False,
|
|
37
|
+
stacklevel: int = 1,
|
|
38
|
+
details: Any = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
if details is not None:
|
|
41
|
+
if extra is None:
|
|
42
|
+
extra = {"details": details}
|
|
43
|
+
elif isinstance(extra, Mapping):
|
|
44
|
+
if "details" not in extra:
|
|
45
|
+
extra = {**extra, "details": details}
|
|
46
|
+
super()._log(
|
|
47
|
+
level,
|
|
48
|
+
msg,
|
|
49
|
+
args,
|
|
50
|
+
exc_info=exc_info,
|
|
51
|
+
extra=extra,
|
|
52
|
+
stack_info=stack_info,
|
|
53
|
+
stacklevel=stacklevel,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def install() -> None:
|
|
58
|
+
"""Install :class:`JvAgentLogger` as the default Logger class.
|
|
59
|
+
|
|
60
|
+
Idempotent. Must be called before any ``logging.getLogger(__name__)``
|
|
61
|
+
call inside jvagent — typically from ``jvagent/__init__.py`` at the very
|
|
62
|
+
top of the module.
|
|
63
|
+
|
|
64
|
+
Loggers created BEFORE this call keep their original class. That's fine:
|
|
65
|
+
jvagent's own modules are imported after ``jvagent/__init__.py`` runs,
|
|
66
|
+
so their module-level ``logging.getLogger(__name__)`` returns instances
|
|
67
|
+
of this subclass.
|
|
68
|
+
"""
|
|
69
|
+
if not issubclass(logging.getLoggerClass(), JvAgentLogger):
|
|
70
|
+
logging.setLoggerClass(JvAgentLogger)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Deterministic MIME-type registration for text formats jvagent emits.
|
|
2
|
+
|
|
3
|
+
jvspatial's local storage validator detects a file's MIME type via
|
|
4
|
+
``python-magic`` when available, otherwise it falls back to the stdlib
|
|
5
|
+
``mimetypes`` module (extension-based). When *neither* knows an extension the
|
|
6
|
+
type resolves to ``application/octet-stream``, which is not in jvspatial's
|
|
7
|
+
allow-list — so the save is rejected with::
|
|
8
|
+
|
|
9
|
+
File type 'application/octet-stream' is not in allowed types
|
|
10
|
+
|
|
11
|
+
Markdown is the common casualty: ``mimetypes`` only maps ``.md`` →
|
|
12
|
+
``text/markdown`` when the host OS mime database (e.g. ``/etc/mime.types``)
|
|
13
|
+
happens to include it. On hosts without that entry and without ``libmagic``
|
|
14
|
+
installed, every ``.md`` an agent writes (research reports, notes, …) fails to
|
|
15
|
+
save even though ``text/markdown`` *is* in jvspatial's allow-list.
|
|
16
|
+
|
|
17
|
+
We close the gap deterministically by registering the markdown extensions in
|
|
18
|
+
the ``mimetypes`` module at import time, before any file-save path runs. Only
|
|
19
|
+
extensions whose target type is already in jvspatial's default allow-list are
|
|
20
|
+
registered here — this changes detection, never the policy.
|
|
21
|
+
|
|
22
|
+
This mirrors the early ``_logging_compat`` install in :mod:`jvagent` and must
|
|
23
|
+
run before any submodule performs a save.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import mimetypes
|
|
29
|
+
|
|
30
|
+
# (mime_type, extension) pairs. Each ``mime_type`` MUST already be present in
|
|
31
|
+
# jvspatial's ``FileValidator.DEFAULT_ALLOWED_MIME_TYPES`` — we only fix
|
|
32
|
+
# *detection* of well-known text formats, never the allow-list itself.
|
|
33
|
+
_EXTRA_TYPES = (
|
|
34
|
+
("text/markdown", ".md"),
|
|
35
|
+
("text/markdown", ".markdown"),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
_installed = False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def install() -> None:
|
|
42
|
+
"""Register markdown extensions in ``mimetypes`` (idempotent)."""
|
|
43
|
+
global _installed
|
|
44
|
+
if _installed:
|
|
45
|
+
return
|
|
46
|
+
# ``mimetypes.add_type`` lazily inits the module's maps on first use, so an
|
|
47
|
+
# explicit init is unnecessary; add_type is safe to call repeatedly.
|
|
48
|
+
for mime_type, ext in _EXTRA_TYPES:
|
|
49
|
+
mimetypes.add_type(mime_type, ext)
|
|
50
|
+
_installed = True
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = ["install"]
|
jvagent/action/AGENTS.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
See [CLAUDE.md](CLAUDE.md) — same agent guide, alternate filename for non-Claude AI agents.
|
jvagent/action/CLAUDE.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# jvagent/action/ — Agent Guide
|
|
2
|
+
|
|
3
|
+
> Local guide for the action plugin layer. Cross-link: root [`/CLAUDE.md`](../../CLAUDE.md), [`/.planning/reference/action-authoring.md`](../../.planning/reference/action-authoring.md), [`/.planning/reference/actions-catalog.md`](../../.planning/reference/actions-catalog.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. What this directory owns
|
|
8
|
+
|
|
9
|
+
The plugin-loadable extension surface of jvagent:
|
|
10
|
+
|
|
11
|
+
- **`Action` base** ([`base.py:48`](base.py)) — Node subclass with lifecycle hooks, attribute config, endpoint registration, child-cascade delete, tool exposure to the Executive's Skills center.
|
|
12
|
+
- **`InteractAction`** ([`interact/base.py:32`](interact/base.py)) — see `interact/CLAUDE.md`.
|
|
13
|
+
- **Specialized bases**: `BaseModelAction`, `LanguageModelAction`, `BaseWebSearchAction`, `BaseSTTAction`, `BaseTTSAction`, `VectorStore`.
|
|
14
|
+
- **Concrete plugins** organized by topic: language models, response/bus, Orchestrator, memory-related, channel adapters, productivity integrations, tasks. Catalog in [`/.planning/reference/actions-catalog.md`](../../.planning/reference/actions-catalog.md).
|
|
15
|
+
- **Loader/registry** in `loader/`.
|
|
16
|
+
- **Plugin contracts** in `plugin_contracts.py`.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 2. Key files
|
|
21
|
+
|
|
22
|
+
| File | Purpose |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `base.py:48` | `Action` base class (canonical) |
|
|
25
|
+
| `base.py:272` | `get_tools()` — default discovers `@tool`-decorated methods via `collect_tools` |
|
|
26
|
+
| `jvagent/tooling/tool_decorator.py` | `@tool` decorator + `collect_tools` — preferred way to publish tools |
|
|
27
|
+
| `jvagent/tooling/signature_schema.py` | signature → JSON Schema deriver used by `@tool` |
|
|
28
|
+
| `base.py:180` | `get_capabilities()` — for ReplyAction prompt aggregation |
|
|
29
|
+
| `base.py:225` | `delete(cascade=True)` — walks outgoing edges and cascade-deletes children |
|
|
30
|
+
| `base.py:256-348` | Lifecycle hook contracts (`on_register`, `on_reload`, `post_register`, `on_startup`, `on_enable`, `on_disable`, `on_deregister`, `pulse`, `healthcheck`) |
|
|
31
|
+
| `base.py:354-460` | Endpoint discovery + unregistration (relies on `/actions/{action_id}/` path prefix) |
|
|
32
|
+
| `base.py:462-542` | Module unload safety (skips core + shared modules) |
|
|
33
|
+
| `base.py:710-852` | `get_action()` / `get_action_by_base_class()` / `get_model_action()` — cross-action lookup |
|
|
34
|
+
| `base.py:881-944` | Package metadata accessors (namespace, version, type) |
|
|
35
|
+
| `base.py:946-1100` | File storage helpers (action-scoped paths) |
|
|
36
|
+
| `interact/base.py:32` | `InteractAction` (see `interact/CLAUDE.md`) |
|
|
37
|
+
| `actions.py` | `Actions` manager node |
|
|
38
|
+
| `endpoints.py` | Top-level action HTTP routes (~9 routes) |
|
|
39
|
+
| `loader/` | Action loader, registry, plugin discovery |
|
|
40
|
+
| `plugin_contracts.py` | Plugin protocol definitions |
|
|
41
|
+
| `streaming.py` | Streaming response helpers |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. Contracts (don't break)
|
|
46
|
+
|
|
47
|
+
1. **`Action` subclasses MUST set `archetype` in `info.yaml`** to match the Python class name. The loader uses it.
|
|
48
|
+
2. **Action endpoints MUST live under `/actions/{action_id}/...`** ([`base.py:373`](base.py)). Deregister scans this prefix; non-conforming endpoints leak after `on_deregister`.
|
|
49
|
+
3. **`get_action()` is `O(1)`; `get_action_by_base_class()` is `O(n)`.** Don't use the latter in hot paths.
|
|
50
|
+
4. **Lifecycle hooks MUST not swallow exceptions** ([`base.py:569+`](base.py)). The framework's `enable()`/`disable()`/`reload()` wrappers log errors automatically with the action context — silencing them hides bugs.
|
|
51
|
+
5. **`Action.metadata` is owned by the loader.** Mutations to it are not persisted across restarts. Use `attribute(...)` fields for persistent state.
|
|
52
|
+
6. **Child Nodes attached via outgoing edges are cascade-deleted** when the action is deleted ([`base.py:225`](base.py)). Always connect via `await self.connect(child, direction="out")`.
|
|
53
|
+
7. **`is_singleton` default is `True`** ([`base.py:221`](base.py)). Override `config.singleton: false` in `info.yaml` if multiple instances per agent are allowed.
|
|
54
|
+
8. **Thin harness** — Actions expose capabilities via `get_tools()`; they must not classify user intent, inject prep steering, auto-store extracted values, or inline multi-step workflows. Put judgment in skill SOPs and domain logic in skill extensions. See [`docs/thin-harness.md`](../../docs/thin-harness.md).
|
|
55
|
+
9. **Prefer the `@tool` decorator over hand-built `Tool()`** — decorate an `async def` method with `@tool` ([`jvagent/tooling/tool_decorator.py`](../tooling/tool_decorator.py)) and the base `get_tools()` auto-publishes it. Name = `{action_name}__{method}` (override with `@tool(name=...)`); description = method docstring's first paragraph; JSON Schema = signature (use `Annotated[T, "desc"]` for per-arg docs). Hand-built `Tool()` and `get_tools()` overrides still work; only override when a tool can't be a decorated method (combine with `collect_tools(self)`).
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 4. The four-file pattern
|
|
60
|
+
|
|
61
|
+
Every action package MUST contain:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
{namespace}/{action_name}/
|
|
65
|
+
├── __init__.py # exports class + imports endpoints (for @endpoint registration)
|
|
66
|
+
├── {action_name}.py # Action subclass
|
|
67
|
+
├── endpoints.py # @endpoint-decorated routes
|
|
68
|
+
└── info.yaml # package metadata
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Skeleton and full templates: [`/.planning/reference/action-authoring.md`](../../.planning/reference/action-authoring.md).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 5. Cross-action lookup table
|
|
76
|
+
|
|
77
|
+
| Need | Method | Cost |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| One specific class | `await self.get_action(MyActionClass)` | O(1) (cached index) |
|
|
80
|
+
| Class by name string | `await self.get_action("MyActionClass")` | O(1) |
|
|
81
|
+
| Any subclass of a base | `await self.get_action_by_base_class(Base)` | O(n) isinstance scan |
|
|
82
|
+
| Any LM provider | `await self.get_model_action(required=True)` | O(1) if `model_action_type` set, else falls back to base scan |
|
|
83
|
+
| The agent | `await self.get_agent()` | Cached |
|
|
84
|
+
| The App | `await self.get_app()` | Cached singleton |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 6. Tests
|
|
89
|
+
|
|
90
|
+
- `tests/action/{name}/` per-action tests.
|
|
91
|
+
- `tests/action/test_action_loader.py` — plugin loading.
|
|
92
|
+
- `tests/action/test_action_endpoints.py` — endpoint discovery.
|
|
93
|
+
- `tests/action/test_plugin_system.py` — plugin contracts.
|
|
94
|
+
- `tests/test_tool_schema_audit.py` — tool schema sanity.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pytest tests/action/ -v
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 7. Adding a new Action
|
|
103
|
+
|
|
104
|
+
The detailed walkthrough lives at [`/.planning/reference/action-authoring.md`](../../.planning/reference/action-authoring.md) and the runbook at [`/.planning/runbooks/add-action.md`](../../.planning/runbooks/add-action.md). Short version:
|
|
105
|
+
|
|
106
|
+
1. Pick base class (Action / InteractAction / specialized).
|
|
107
|
+
2. Choose namespace (`jvagent/` for core, otherwise `contrib/` or `custom/`).
|
|
108
|
+
3. Create the 4-file directory.
|
|
109
|
+
4. Define `attribute(...)` fields; implement lifecycle hooks + `execute` (if InteractAction). Publish tools by decorating methods with `@tool` (preferred over overriding `get_tools()`).
|
|
110
|
+
5. Wire endpoints under `/actions/{action_id}/...`.
|
|
111
|
+
6. Add tests under `tests/action/{name}/`.
|
|
112
|
+
7. Update [`actions-catalog.md`](../../.planning/reference/actions-catalog.md).
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 8. Traps specific to action/
|
|
117
|
+
|
|
118
|
+
| Trap | Fix |
|
|
119
|
+
|---|---|
|
|
120
|
+
| Missing `from . import endpoints` in `__init__.py` | Add it; otherwise routes don't register |
|
|
121
|
+
| Class name doesn't match `archetype` in info.yaml | Loader silently skips the package |
|
|
122
|
+
| Heavy work in `__init__` | Use lifecycle hooks (`on_register`, `on_enable`) instead |
|
|
123
|
+
| Forgetting `@compound_index` when adding a queried field | Slow queries at scale |
|
|
124
|
+
| Writing to `self.metadata` | Not persisted; use `attribute(...)` |
|
|
125
|
+
| Endpoints not under `/actions/{action_id}/` | Deregister leaks them |
|
|
126
|
+
| Recursive `await self.get_action(MyAction)` calls | OK (cache returns same instance) but expensive isinstance walks aren't |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 9. Subdirectory pointers
|
|
131
|
+
|
|
132
|
+
| Subdir | Local guide |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `interact/` | [`interact/CLAUDE.md`](interact/CLAUDE.md) |
|
|
135
|
+
| `interview/` | [`interview/CLAUDE.md`](interview/CLAUDE.md) |
|
|
136
|
+
| `orchestrator/` | (see [`/.planning/adr/0012-skill-executive-architecture.md`](../../.planning/adr/0012-skill-executive-architecture.md)) |
|
|
137
|
+
| All other action dirs | Per-package `info.yaml` + class docstring |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 10. Out of scope here
|
|
142
|
+
|
|
143
|
+
- Walker mechanics: see `interact/CLAUDE.md`.
|
|
144
|
+
- Executive prompt/loop specifics: see [`/docs/ORCHESTRATOR.md`](../../docs/ORCHESTRATOR.md).
|
|
145
|
+
- Memory graph: see `jvagent/memory/CLAUDE.md`.
|
jvagent/action/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# jvagent.action — package index
|
|
2
|
+
|
|
3
|
+
Action plugins live under `jvagent/action/{name}/`. Each package owns its Action class,
|
|
4
|
+
`info.yaml`, endpoints, and action-local `utils/`.
|
|
5
|
+
|
|
6
|
+
## Shared modules (import these across action families)
|
|
7
|
+
|
|
8
|
+
| Module | Purpose |
|
|
9
|
+
|--------|---------|
|
|
10
|
+
| [`utils/`](utils/) | Multi-consumer helpers: typed endpoint loader, Meta HMAC verify, webhook system user, reconcile |
|
|
11
|
+
| [`interact/`](interact/) | InteractAction base, walker, channel-neutral [`webhook_pipeline`](interact/webhook_pipeline.py) |
|
|
12
|
+
| [`oauth/`](oauth/) | OAuth state node, token crypto, audit logging |
|
|
13
|
+
| [`spreadsheet/`](spreadsheet/) | Vendor-neutral A1 range helpers (Sheets + Excel) |
|
|
14
|
+
| [`skill_spec/`](skill_spec/) | Skill contracts, registries, shared task-lock types |
|
|
15
|
+
| [`model/`](model/) | Language model actions and context |
|
|
16
|
+
| [`manifest`](manifest.py), [`parameters`](parameters.py) | App/action configuration |
|
|
17
|
+
|
|
18
|
+
## Modularization contract
|
|
19
|
+
|
|
20
|
+
See [`.planning/reference/action-authoring.md` §15](../.planning/reference/action-authoring.md)
|
|
21
|
+
and [action-modularization-audit.md](../.planning/reference/action-modularization-audit.md).
|
|
22
|
+
|
|
23
|
+
**Rules (summary):**
|
|
24
|
+
|
|
25
|
+
1. No imports from sibling action packages except through the shared modules above.
|
|
26
|
+
2. Code moves to a shared path only when **2+ unrelated action families** consume it.
|
|
27
|
+
3. No persisted `Node`/`Object` types under `action/utils/`.
|
|
28
|
+
4. One canonical import path — no re-export shims.
|
|
29
|
+
5. Channel webhook finalization uses `interact.webhook_pipeline` (public API).
|
|
30
|
+
|
|
31
|
+
## Upload ownership
|
|
32
|
+
|
|
33
|
+
- **`interact/utils/uploads.py`** — upload parsing constants and helpers (canonical).
|
|
34
|
+
- **`orchestrator/uploads.py`** — orchestrator ingestion reflex only; imports from interact.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Action system for jvagent.
|
|
2
|
+
|
|
3
|
+
This module provides the action management system including:
|
|
4
|
+
- Actions: Manager node for action registration and discovery
|
|
5
|
+
- Action: Base action class for all pluggable actions with CRUD endpoints
|
|
6
|
+
- ActionLoader: Dynamic action discovery and loading
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from jvagent.action.actions import Actions
|
|
10
|
+
from jvagent.action.base import Action
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __getattr__(name: str):
|
|
14
|
+
"""Lazy-import heavy loader objects or submodules to avoid circular imports."""
|
|
15
|
+
if name in ("ActionLoader", "ActionMetadata"):
|
|
16
|
+
from jvagent.action.loader import ActionLoader, ActionMetadata
|
|
17
|
+
|
|
18
|
+
return ActionLoader if name == "ActionLoader" else ActionMetadata
|
|
19
|
+
|
|
20
|
+
if name == "interact":
|
|
21
|
+
import importlib
|
|
22
|
+
|
|
23
|
+
return importlib.import_module("jvagent.action.interact")
|
|
24
|
+
|
|
25
|
+
if name == "endpoints":
|
|
26
|
+
import importlib
|
|
27
|
+
|
|
28
|
+
return importlib.import_module("jvagent.action.endpoints")
|
|
29
|
+
|
|
30
|
+
raise AttributeError(name)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = ["Actions", "Action", "ActionLoader", "ActionMetadata"]
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Access Control Action
|
|
2
|
+
|
|
3
|
+
Role-based access control with user_id-based permissions for secure agent operations.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Access Control Action provides user-based permission management and channel/resource permissions for jvagent applications. Identity is keyed by `user_id` only (no sessions or conversations). It supports allow/deny rules for users and user groups, per-action restriction in the InteractWalker, and programmatic + REST APIs for runtime updates. User groups are scoped by action label (e.g. `default`, `PersonaAction`), allowing different group memberships per action with automatic fallback and merge resolution.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **User-only identity**: All permissions key off `user_id`
|
|
12
|
+
- **Channel/Resource permissions**: Fine-grained access by channel and action (InteractAction class name)
|
|
13
|
+
- **User groups**: Assign users to groups scoped by action label for permission inheritance; action-specific groups inherit from the `default` scope
|
|
14
|
+
- **Allow/Deny rules**: Deny rules checked first; allow rules grant access
|
|
15
|
+
- **default_deny**: When true, deny when no rule matches; when false, allow
|
|
16
|
+
- **action_aliases**: Map short names to action class names in config
|
|
17
|
+
- **enforce**: When false, all `has_action_access` checks succeed (policy off). Prefer this over disabling the graph node when you want the action to remain discoverable.
|
|
18
|
+
- **allow_anonymous**: When false (default), empty/missing `user_id` is denied whenever policy applies. Set true only if anonymous interact is intentional.
|
|
19
|
+
- **Exceptions**: Actions exempt from permission checks
|
|
20
|
+
- **Programmatic API**: Add/remove users, groups, and permission rules
|
|
21
|
+
- **Admin REST endpoints**: All agent-scoped via `agent_id` (singleton per agent)
|
|
22
|
+
|
|
23
|
+
## Agent YAML Configuration
|
|
24
|
+
|
|
25
|
+
Configure directly under `context` (not under `config`). This ensures attributes are
|
|
26
|
+
populated correctly when running on Lambda and other serverless environments.
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
actions:
|
|
30
|
+
- action: jvagent/access_control_action
|
|
31
|
+
context:
|
|
32
|
+
enabled: true
|
|
33
|
+
enforce: true
|
|
34
|
+
allow_anonymous: false
|
|
35
|
+
description: "Per-user/group access control for interact actions"
|
|
36
|
+
default_deny: false
|
|
37
|
+
action_aliases:
|
|
38
|
+
persona: PersonaAction
|
|
39
|
+
report: ReportInterviewSkill
|
|
40
|
+
permissions:
|
|
41
|
+
default:
|
|
42
|
+
any:
|
|
43
|
+
deny: []
|
|
44
|
+
allow: [{ group: all, enabled: true }]
|
|
45
|
+
PersonaAction:
|
|
46
|
+
deny: []
|
|
47
|
+
allow: [admins]
|
|
48
|
+
whatsapp:
|
|
49
|
+
any:
|
|
50
|
+
deny: []
|
|
51
|
+
allow: [{ user: "5926431530", enabled: true }]
|
|
52
|
+
user_groups:
|
|
53
|
+
default:
|
|
54
|
+
admins: [user_abc123, user_def456]
|
|
55
|
+
support_team: [user_ghi789]
|
|
56
|
+
PersonaAction:
|
|
57
|
+
reviewers: [user_xyz]
|
|
58
|
+
exceptions:
|
|
59
|
+
- ReplyAction
|
|
60
|
+
- IntroInteractAction
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Permission Structure
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
channel -> action_label -> allow/deny -> rules
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- **Channel**: "default" (web), "whatsapp", etc.
|
|
70
|
+
- **Action label**: InteractAction class name (e.g. "PersonaAction", "ReportInterviewSkill") or "any"
|
|
71
|
+
- **Allow/Deny**: Deny rules checked first; allow rules grant access
|
|
72
|
+
- **Rules**: `{ user: "user_id" }` or `{ group: "group_name" }`; `group: "all"` matches everyone. Groups resolve per action label with `default` fallback.
|
|
73
|
+
|
|
74
|
+
## Programmatic API
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# User groups (scoped by action_label, defaults to "default")
|
|
78
|
+
await action.add_user_group("support", ["u1", "u2"]) # default scope
|
|
79
|
+
await action.add_user_group("reviewers", ["u3"], action_label="PersonaAction") # action-scoped
|
|
80
|
+
await action.add_user_to_group("admins", "user_abc") # default scope
|
|
81
|
+
await action.add_users_to_group("admins", ["u1", "u2"]) # default scope
|
|
82
|
+
await action.add_user_to_group("reviewers", "u3", action_label="PersonaAction")
|
|
83
|
+
await action.remove_user_from_group("admins", "user_abc") # default scope
|
|
84
|
+
await action.remove_user_group("support") # default scope
|
|
85
|
+
await action.remove_user_group("reviewers", action_label="PersonaAction")
|
|
86
|
+
groups = action.get_user_groups() # full nested dict
|
|
87
|
+
groups = action.get_user_groups(action_label="PersonaAction") # merged (action + default)
|
|
88
|
+
|
|
89
|
+
# Direct user access (allow/deny)
|
|
90
|
+
await action.add_user_to_allow("default", "PersonaAction", "user_xyz")
|
|
91
|
+
await action.add_user_to_deny("default", "DeleteAction", "user_bad")
|
|
92
|
+
await action.remove_user_from_permission("default", "X", "user_id", from_allow=True)
|
|
93
|
+
|
|
94
|
+
# Group-based rules
|
|
95
|
+
await action.add_group_to_allow("default", "PersonaAction", "admins")
|
|
96
|
+
await action.add_group_to_deny("default", "DeleteAction", "guests")
|
|
97
|
+
await action.remove_group_from_permission("default", "X", "group", from_allow=True)
|
|
98
|
+
|
|
99
|
+
# Check access
|
|
100
|
+
has_access = await action.has_action_access(
|
|
101
|
+
user_id="user_abc",
|
|
102
|
+
action_label="PersonaAction",
|
|
103
|
+
channel="default",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Bulk config (includes default_deny, action_aliases, enforce, allow_anonymous)
|
|
107
|
+
config = action.export_config()
|
|
108
|
+
await action.import_config(config, purge=True)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## REST API (Admin Only)
|
|
112
|
+
|
|
113
|
+
All endpoints require `auth=True` and `roles=["admin"]`. AccessControlAction is a
|
|
114
|
+
singleton per agent, so all endpoints are agent-scoped via `agent_id`.
|
|
115
|
+
|
|
116
|
+
### Endpoints (`/agents/{agent_id}/access_control/`)
|
|
117
|
+
|
|
118
|
+
| Method | Path | Purpose |
|
|
119
|
+
|--------|------|---------|
|
|
120
|
+
| GET | `/config?format=json\|yaml` | Export config |
|
|
121
|
+
| PUT | `/config` | Replace permissions |
|
|
122
|
+
| PATCH | `/config` | Merge permissions |
|
|
123
|
+
| POST | `/check` | Check access (user_id, action_label, channel) |
|
|
124
|
+
| POST | `/user_groups` | Create group (body: `{name, user_ids?, action_label?}`) |
|
|
125
|
+
| GET | `/user_groups?action_label=` | List groups (scoped or full nested) |
|
|
126
|
+
| POST | `/user_groups/{group}/users` | Add users to group (body: `{user_ids, action_label?}`) |
|
|
127
|
+
| DELETE | `/user_groups/{group}/users` | Remove users from group (body: `{user_ids, action_label?}`) |
|
|
128
|
+
| DELETE | `/user_groups/{group}` | Remove group (body: `{action_label?}`) |
|
|
129
|
+
| POST | `/permissions` | Add user/group to allow or deny |
|
|
130
|
+
| DELETE | `/permissions` | Remove user/group from allow or deny |
|
|
131
|
+
|
|
132
|
+
### Config Endpoint (PUT/PATCH)
|
|
133
|
+
|
|
134
|
+
Resolves AccessControlAction from `agent_id` (no start_node). Request body:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"permissions": {
|
|
139
|
+
"default": {
|
|
140
|
+
"any": { "deny": [], "allow": [{ "group": "all", "enabled": true }] },
|
|
141
|
+
"ReportInterviewSkill": { "deny": [{ "group": "all" }], "allow": [] }
|
|
142
|
+
},
|
|
143
|
+
"whatsapp": {
|
|
144
|
+
"any": { "deny": [], "allow": [{ "group": "all", "enabled": true }] },
|
|
145
|
+
"ReportInterviewSkill": { "deny": [], "allow": [{ "group": "all", "enabled": true }] }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- **PUT**: Replaces permissions entirely. Preserves `user_groups` and `exceptions` (and other non-permissions fields on the node). Use `export_config` / `import_config` in code to replace `default_deny`, `action_aliases`, `enforce`, or `allow_anonymous` in one shot.
|
|
152
|
+
- **PATCH**: Merges permissions at channel level.
|
|
153
|
+
|
|
154
|
+
## User Groups
|
|
155
|
+
|
|
156
|
+
User groups are organized by **action label scope**, allowing different group memberships per action:
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
user_groups:
|
|
160
|
+
default: # Fallback scope — used when the action label has no entry
|
|
161
|
+
admins: [user_abc123, user_def456]
|
|
162
|
+
support_team: [user_ghi789]
|
|
163
|
+
PersonaAction: # Action-specific scope
|
|
164
|
+
reviewers: [user_xyz]
|
|
165
|
+
ReportInterviewSkill:
|
|
166
|
+
analysts: [user_abc123]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Resolution
|
|
170
|
+
|
|
171
|
+
When `_matches_rule` evaluates a `{ group: "admins" }` rule for action label `PersonaAction`:
|
|
172
|
+
|
|
173
|
+
1. Look up `user_groups["PersonaAction"]["admins"]`
|
|
174
|
+
2. Fall back to `user_groups["default"]["admins"]` if missing
|
|
175
|
+
3. **Merge**: action-specific and default groups are merged (action-specific wins on conflicts)
|
|
176
|
+
|
|
177
|
+
This means a user in `default.admins` is also considered an admin for `PersonaAction` unless `PersonaAction.admins` overrides it. The special group names `"all"` and `"any"` always match without looking up `user_groups`.
|
|
178
|
+
|
|
179
|
+
### Legacy Flat Format
|
|
180
|
+
|
|
181
|
+
The original `user_groups` format was flat: `{ admins: [user_abc] }`. This is auto-migrated to `{ default: { admins: [user_abc] } }` by `_migrate_user_groups()` during `import_config`. Both the programmatic API and REST endpoints accept the flat format for backward compatibility.
|
|
182
|
+
|
|
183
|
+
### Programmatic API (action_label parameter)
|
|
184
|
+
|
|
185
|
+
All group methods accept an optional `action_label` parameter (default `"default"`):
|
|
186
|
+
|
|
187
|
+
| Method | action_label behavior |
|
|
188
|
+
|--------|---------------------|
|
|
189
|
+
| `add_user_group(name, user_ids, action_label)` | Creates group under the given scope |
|
|
190
|
+
| `add_user_to_group(group, user_id, action_label)` | Adds user to group in the given scope |
|
|
191
|
+
| `add_users_to_group(group, user_ids, action_label)` | Adds users to group in the given scope |
|
|
192
|
+
| `remove_user_from_group(group, user_id, action_label)` | Removes user from group in the given scope |
|
|
193
|
+
| `remove_user_group(name, action_label)` | Deletes group from the given scope |
|
|
194
|
+
| `get_user_groups(action_label)` | No argument → full nested dict; with label → merged dict for that scope |
|
|
195
|
+
|
|
196
|
+
### REST API (action_label parameter)
|
|
197
|
+
|
|
198
|
+
All `user_groups` endpoints accept an `action_label` field in the request body (or query param for GET), defaulting to `"default"`:
|
|
199
|
+
|
|
200
|
+
| Endpoint | action_label source |
|
|
201
|
+
|----------|-------------------|
|
|
202
|
+
| `POST /user_groups` | Body: `{name, user_ids?, action_label?}` |
|
|
203
|
+
| `GET /user_groups?action_label=` | Query param (omit for full nested structure) |
|
|
204
|
+
| `POST /user_groups/{group}/users` | Body: `{user_ids, action_label?}` |
|
|
205
|
+
| `DELETE /user_groups/{group}/users` | Body: `{user_ids, action_label?}` |
|
|
206
|
+
| `DELETE /user_groups/{group}` | Body: `{action_label?}` |
|
|
207
|
+
|
|
208
|
+
## Integration
|
|
209
|
+
|
|
210
|
+
- **Policy applies** when the graph node is `enabled` and `enforce` is true (`policy_applies()`). If there is **no** `AccessControlAction` on the agent, interact proceeds without access checks (open by default).
|
|
211
|
+
- **Session gate**: After `memory.get_session()` resolves `user_id`, the walker checks resource `interact` before creating an interaction. Configure `default` / `whatsapp` / … channel keys for `interact` alongside per–class-name keys.
|
|
212
|
+
- **InteractWalker**: Before each foreground `InteractAction` runs, access is checked using that action’s class name. Deferred (`run_in_background`) actions are checked when queued and again before `execute` in the background runner.
|
|
213
|
+
- **HTTP `/interact`**: Entry access is enforced inside the walker (not on the raw request parameter alone), so `session_id`-only clients are evaluated after session resolution.
|
|
214
|
+
- **WhatsApp webhook**: Uses `sender` (phone) as `user_id` for channel `whatsapp`; denials emit structured logs (`access_control_denied`).
|
|
215
|
+
- **Duplicates**: If more than one `AccessControlAction` exists for an agent, an error is logged and the first match is used—fix the graph to a single node.
|
|
216
|
+
|
|
217
|
+
## Migration (breaking)
|
|
218
|
+
|
|
219
|
+
- **`user_groups` is now nested by action label**: The flat format `{ admins: [...] }` is auto-migrated to `{ default: { admins: [...] } }`. Existing YAML configs with flat `user_groups` continue to work via `_migrate_user_groups()`, but new configs should use the nested format.
|
|
220
|
+
- Empty `user_id` no longer bypasses checks when policy applies; set `allow_anonymous: true` only if you need that behavior.
|
|
221
|
+
- Turning off enforcement: set `enforce: false` (or disable the graph node); do not rely on old "empty user always allowed" behavior.
|
|
222
|
+
- Background interact actions are now subject to the same rules as foreground actions.
|
|
223
|
+
- Export/import round-trips `default_deny`, `action_aliases`, `enforce`, and `allow_anonymous`; merge import deduplicates `exceptions` entries.
|
|
224
|
+
- All group-related API methods now accept an `action_label` parameter (default `"default"`). Code calling these methods without the parameter is unaffected.
|
|
225
|
+
|
|
226
|
+
## Channel Adapters
|
|
227
|
+
|
|
228
|
+
Channels that receive requests before user resolution (e.g. WhatsApp with `sender` phone) should either:
|
|
229
|
+
- Resolve to `user_id` via `memory.get_session()` before the access check, or
|
|
230
|
+
- Use the channel-specific stable ID (e.g. phone number) as `user_id` in config.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Access Control action package.
|
|
2
|
+
|
|
3
|
+
This module provides role-based access control for agent operations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Import endpoints to register them
|
|
7
|
+
from . import endpoints # noqa: F401
|
|
8
|
+
from .access_control_action import AccessControlAction
|
|
9
|
+
|
|
10
|
+
__all__ = ["AccessControlAction"]
|