imbi-api 2.45.0__tar.gz → 2.46.0__tar.gz
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.
- {imbi_api-2.45.0 → imbi_api-2.46.0}/PKG-INFO +2 -2
- {imbi_api-2.45.0 → imbi_api-2.46.0}/pyproject.toml +2 -2
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/agent_tasks/__init__.py +1 -2
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/agent_tasks/log.py +63 -30
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/agent_tasks/schemata.toml +8 -5
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/agent_tasks/store.py +172 -48
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/agent_tools.py +23 -48
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/agent_api_routes.py +4 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/seed.py +15 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/blueprint_compliance.py +88 -3
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/agent_tasks.py +277 -55
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/agents.py +10 -5
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/ai_models.py +223 -18
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/harness.py +41 -31
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_analysis.py +49 -18
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/projects.py +23 -5
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/roles.py +5 -21
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/secret_broker.py +120 -13
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/workflow_runs.py +25 -3
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/workflows.py +32 -1
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/platform.py +80 -8
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/store.py +7 -3
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/secret_broker/broker.py +161 -42
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/secret_broker/schemata.toml +5 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/secret_broker/store.py +188 -20
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_agent_platform.py +28 -2
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_agent_task_log.py +68 -6
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_agent_task_relations.py +48 -13
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_agent_tasks.py +183 -10
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_agent_usage.py +1 -1
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_agents.py +14 -13
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_ai_models.py +203 -3
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_harness_secrets.py +1 -3
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_analysis.py +57 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_projects.py +31 -5
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_roles.py +37 -19
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_secret_broker.py +112 -1
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_workflow_runs.py +41 -4
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_workflow_triggers.py +17 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_workflows.py +26 -1
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/secret_support.py +22 -9
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_agent_platform.py +149 -5
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_agent_task_events_e2e.py +1 -1
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_agent_tools.py +12 -33
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_blueprint_compliance.py +73 -7
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_secret_broker.py +350 -13
- {imbi_api-2.45.0 → imbi_api-2.46.0}/.gitignore +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/README.md +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/moon.yml +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/README.md +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/agent_tasks/sweeper.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/app.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/agent_api.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/authorization_codes.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/autonomous.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/internal_services.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/local_auth.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/login_providers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/membership.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/models.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/oauth_clients.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/organizations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/password.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/permissions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/principals.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/sessions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/tokens.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/auth/totp.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/backfill_embeddings.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/blueprint_attributes.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/commit_pushed_at_repair.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/commit_sync/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/commit_sync/queue.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/commit_sync/service.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/component_facts.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/deployment_status_repair.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/deployment_sweeper.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/deployment_sync/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/deployment_sync/queue.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/deployment_sync/service.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/documents/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/documents/read_sweeper.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/domain/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/domain/models.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/domain/scoring.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/drift.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/client.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/dependencies.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/models.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/templates/base.html +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/templates/base.txt +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/templates/password_reset.html +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/templates/password_reset.txt +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/templates/welcome.html +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/templates/welcome.txt +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/email/templates.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_credentials.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_document_events.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_document_history.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_document_reads.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_document_scope.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_helpers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_json_fields.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_pagination.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_request_urls.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/_search_index.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/admin.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/admin_plugins.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/agent_platform.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/agent_task_harness.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/agent_usage.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/ai_providers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/api_keys.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/auth.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/auth_providers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/blueprints.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/client_credentials.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/comments.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/components.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/dashboard.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/document_analytics.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/document_likes.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/document_presence.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/document_templates.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/document_versions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/documents.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/environments.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/events.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/graph_query.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/iggy_connectors.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/integrations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/link_definitions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/local_auth.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/maintenance.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/mcp_servers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/mfa.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/oauth_metadata.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/operations_log.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/organizations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/plugin_edges.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/plugin_entities.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/plugins.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_commit_sync.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_configuration.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_deployments.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_incidents.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_integrations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_lifecycle.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_logs.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_plugins.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_pr_sync.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/project_types.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/prompts.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/pull_requests.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/releases.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/sa_api_keys.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/scoring.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/scoring_policies.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/search.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/service_accounts.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/status.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/tags.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/teams.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/uploads.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/user_activity.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/users.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/endpoints/webhooks.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/entrypoint.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/graph_ids.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/graph_sql.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/auth.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/egress.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/marketplaces.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/metrics.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/schemata.toml +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/sweeper.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/harness/tokens.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/attribution.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/endpoints.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/errors.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/flows.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/host_integration.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/models.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/repository.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/resolution.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/state.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/identity/sweeper.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/iggy_metrics.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/lifespans.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/llm/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/llm/dependencies.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/llm/tokens.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/maintenance/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/maintenance/log.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/maintenance/operations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/maintenance/registry.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/maintenance/state.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/maintenance/worker.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/mcp_test.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/middleware/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/middleware/rate_limit.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/models.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/openapi.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/orphan_releases.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/assignment_writer.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/assignments.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/credentials.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/installer.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/lifecycle.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/lifecycle_dispatch.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/reload.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/resolution.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/plugins/schemas.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/pr_sync/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/pr_sync/queue.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/pr_sync/service.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/prompts/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/prompts/release_notes_system.md +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/prompts/seed.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/py.typed +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/relationships.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/release_promote/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/release_promote/queue.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/release_promote/service.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/sbom.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/scoring/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/scoring/queue.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/secret_broker/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/secret_broker/audit.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/secret_broker/identity.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/secret_broker/providers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/settings.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/storage/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/storage/client.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/storage/dependencies.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/storage/thumbnails.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/storage/validation.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/user_document_orgs.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/workflows/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/workflows/runs.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/workflows/runtime_schemata.toml +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/workflows/schemata.toml +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/workflows/store.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/src/imbi/api/workflows/sweeper.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_authentication.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_authorization.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_encryption.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_internal_services.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_login_providers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_membership.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_oauth_server.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_organizations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_permissions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_seed.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_sessions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/auth/test_totp.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/clickhouse/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/clickhouse/test_client.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/clickhouse/test_init.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/clickhouse/test_privacy.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/email/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/email/test_audit_clickhouse.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/email/test_client.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/email/test_init.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/email/test_integration.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/email/test_models.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/email/test_templates.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_admin.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_admin_plugins.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_agent_api.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_agent_task_actions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_agent_task_harness.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_ai_providers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_api_keys.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_auth.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_auth_providers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_blueprints.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_client_credentials.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_comments.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_components.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_credentials.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_cross_org_reads.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_dashboard.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_document_analytics.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_document_likes.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_document_presence.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_document_reads.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_document_reads_sql.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_document_templates.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_document_versions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_documents.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_environments.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_events.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_github_app_repository.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_graph_query.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_harness.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_harness_egress.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_iggy_connectors.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_init.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_integrations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_json_fields.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_link_definitions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_local_auth.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_maintenance.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_mcp_servers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_mfa.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_operations_log.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_organizations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_pagination.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_plugin_label_validation.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_plugins.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_commit_sync.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_configuration.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_deployments.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_deployments_autonomous.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_incidents.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_integrations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_lifecycle.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_logs.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_plugins.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_response_surfacing.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_project_types.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_projects_helpers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_prompt_decisions.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_prompts.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_pull_requests.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_releases.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_request_urls.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_sa_api_keys.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_scoring.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_scoring_policies.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_search.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_search_index.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_service_accounts.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_service_writeback.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_status.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_tags.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_teams.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_uploads.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_user_activity.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_users.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_webhooks.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/endpoints/test_workflow_run_history.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/fixtures/marketplaces/claude-code.json +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/fixtures/sbom/npm-realistic.json +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/fixtures/sbom/pypi-realistic.json +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/fixtures/sbom/tiny.json +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/identity/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/identity/test_endpoints.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/identity/test_errors.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/identity/test_flows.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/identity/test_host_integration.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/identity/test_repository.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/identity/test_resolution.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/identity/test_state.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/identity/test_sweeper.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/middleware/test_rate_limit.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/storage/__init__.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/storage/test_client.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/storage/test_init.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/storage/test_thumbnails.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/storage/test_validation.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/support.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_ai_provider_tokens.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_app.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_assignment_writer.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_auth_settings.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_backfill_embeddings.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_blueprint_attributes.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_blueprints.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_clickhouse_project_ids.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_commit_pushed_at_repair.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_commit_sync_queue.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_commit_sync_service.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_component_facts.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_deployment_status_repair.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_deployment_sweeper.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_deployment_sync_queue.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_deployment_sync_service.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_drift.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_entrypoint.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_graph_ids.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_graph_sql.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_identity_attribution.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_iggy_metrics.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_init.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_installer.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_lifecycle_dispatch.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_lifespans.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_maintenance_log.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_maintenance_operations.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_maintenance_state.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_maintenance_worker.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_models.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_openapi.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_orphan_releases.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_plugin_schemas.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_plugins.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_plugins_assignments.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_plugins_credentials.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_pr_sync_queue.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_pr_sync_service.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_projects_drift_ranges.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_prompt_seed.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_pull_request_resync_clickhouse.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_pull_request_reviews_clickhouse.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_pull_requests_pending_deploy.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_release_promote_service.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_sbom.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_scoring_queue.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_scoring_triggers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_secret_github_app.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_secret_identity.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_secret_providers.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_settings.py +0 -0
- {imbi_api-2.45.0 → imbi_api-2.46.0}/tests/test_workflow_reconcile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: imbi-api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.46.0
|
|
4
4
|
Summary: Imbi is a DevOps Service Management Platform designed to provide an efficient way to manage a large environment that contains many services and applications.
|
|
5
5
|
Author-email: Alex Campbell <alexc@aweber.com>, Dave Shawley <daves@aweber.com>, "Gavin M. Roy" <gavinr@aweber.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -20,7 +20,7 @@ Requires-Dist: fastapi
|
|
|
20
20
|
Requires-Dist: filetype
|
|
21
21
|
Requires-Dist: httpx<1,>=0.28.1
|
|
22
22
|
Requires-Dist: idna>=3.18
|
|
23
|
-
Requires-Dist: imbi-common[databases,llm,mcp,sentry,server]==2.
|
|
23
|
+
Requires-Dist: imbi-common[databases,llm,mcp,sentry,server]==2.46.0
|
|
24
24
|
Requires-Dist: jinja2
|
|
25
25
|
Requires-Dist: jsonpatch>=1.33
|
|
26
26
|
Requires-Dist: limits<6,>=5
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "imbi-api"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.46.0"
|
|
4
4
|
description = "Imbi is a DevOps Service Management Platform designed to provide an efficient way to manage a large environment that contains many services and applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -29,7 +29,7 @@ dependencies = [
|
|
|
29
29
|
"filetype",
|
|
30
30
|
"httpx>=0.28.1,<1",
|
|
31
31
|
"idna>=3.18",
|
|
32
|
-
"imbi-common[databases,llm,mcp,sentry,server]==2.
|
|
32
|
+
"imbi-common[databases,llm,mcp,sentry,server]==2.46.0",
|
|
33
33
|
"jinja2",
|
|
34
34
|
"jsonpatch>=1.33",
|
|
35
35
|
"limits>=5,<6",
|
|
@@ -30,7 +30,6 @@ from imbi.api.agent_tasks.store import (
|
|
|
30
30
|
NewRequest,
|
|
31
31
|
NewTask,
|
|
32
32
|
Pool,
|
|
33
|
-
PrimaryProject,
|
|
34
33
|
RequestKeyConflict,
|
|
35
34
|
RequestNotFound,
|
|
36
35
|
RequestNotOpen,
|
|
@@ -80,6 +79,7 @@ async def store_lifespan() -> abc.AsyncGenerator[TaskStore]:
|
|
|
80
79
|
try:
|
|
81
80
|
await pool.open()
|
|
82
81
|
store = TaskStore(pool)
|
|
82
|
+
await store.move_legacy_projects()
|
|
83
83
|
stop = asyncio.Event()
|
|
84
84
|
sweep = asyncio.create_task(sweeper.run_sweeper(store, stop=stop))
|
|
85
85
|
try:
|
|
@@ -112,7 +112,6 @@ __all__ = [
|
|
|
112
112
|
'NewRequest',
|
|
113
113
|
'NewTask',
|
|
114
114
|
'Pool',
|
|
115
|
-
'PrimaryProject',
|
|
116
115
|
'RequestKeyConflict',
|
|
117
116
|
'RequestNotFound',
|
|
118
117
|
'RequestNotOpen',
|
|
@@ -10,7 +10,7 @@ Both tables are ReplacingMergeTree tables keyed on the identity of the
|
|
|
10
10
|
row, so a row that is published two times is stored one time.
|
|
11
11
|
|
|
12
12
|
Request resolutions and terminal outcomes also go to the Operations Log
|
|
13
|
-
(I9, N9), as a projection of their events: see :func:`
|
|
13
|
+
(I9, N9), as a projection of their events: see :func:`opslog_rows`.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import decimal
|
|
@@ -45,6 +45,17 @@ ORDER BY seq
|
|
|
45
45
|
LIMIT {limit:UInt32}
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
|
+
_LAST_AGENT_TURN_QUERY = """
|
|
49
|
+
SELECT JSONExtractString(payload, 'body') AS body
|
|
50
|
+
FROM imbi.agent_task_events
|
|
51
|
+
WHERE organization_id = {organization_id:String}
|
|
52
|
+
AND task_id = {task_id:String}
|
|
53
|
+
AND type = 'turn'
|
|
54
|
+
AND actor_kind = 'agent'
|
|
55
|
+
ORDER BY seq DESC
|
|
56
|
+
LIMIT 1
|
|
57
|
+
"""
|
|
58
|
+
|
|
48
59
|
_EVENT_COUNTS_QUERY = """
|
|
49
60
|
SELECT task_id, uniqExact(seq) AS events, max(seq) AS max_seq
|
|
50
61
|
FROM imbi.agent_task_events
|
|
@@ -80,6 +91,11 @@ def _optional_str(value: typing.Any) -> str | None:
|
|
|
80
91
|
return None if value is None else str(value)
|
|
81
92
|
|
|
82
93
|
|
|
94
|
+
def _project_ids(task: Row) -> str:
|
|
95
|
+
"""The ids of the projects of a task, comma separated."""
|
|
96
|
+
return ','.join(project['project_id'] for project in task['projects'])
|
|
97
|
+
|
|
98
|
+
|
|
83
99
|
def event_row(task: Row, event: Row) -> Row:
|
|
84
100
|
"""Return the ``agent_task_events`` row of one Postgres event."""
|
|
85
101
|
return {
|
|
@@ -88,7 +104,7 @@ def event_row(task: Row, event: Row) -> Row:
|
|
|
88
104
|
'short_id': task['short_id'],
|
|
89
105
|
'agent_id': task['agent_id'],
|
|
90
106
|
'agent_version': task['agent_version'],
|
|
91
|
-
'project_id': task
|
|
107
|
+
'project_id': _project_ids(task),
|
|
92
108
|
'seq': event['seq'],
|
|
93
109
|
'event_id': str(event['event_id']),
|
|
94
110
|
'type': event['type'],
|
|
@@ -109,7 +125,7 @@ def usage_row(task: Row, ledger: Row) -> Row:
|
|
|
109
125
|
'task_id': str(task['id']),
|
|
110
126
|
'short_id': task['short_id'],
|
|
111
127
|
'agent_id': task['agent_id'],
|
|
112
|
-
'project_id': task
|
|
128
|
+
'project_id': _project_ids(task),
|
|
113
129
|
'session_id': _optional_str(ledger['session_id']),
|
|
114
130
|
'report_id': str(ledger['id']),
|
|
115
131
|
'model_id': ledger['model_id'] or '',
|
|
@@ -136,17 +152,18 @@ def usage_row(task: Row, ledger: Row) -> Row:
|
|
|
136
152
|
return row
|
|
137
153
|
|
|
138
154
|
|
|
139
|
-
def
|
|
140
|
-
"""Return the ``operations_log``
|
|
155
|
+
def opslog_rows(task: Row, event: Row) -> list[Row]:
|
|
156
|
+
"""Return the ``operations_log`` rows of an event: one row for each
|
|
157
|
+
project of the task.
|
|
141
158
|
|
|
142
|
-
Only a resolution or an outcome
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
159
|
+
Only a resolution or an outcome has rows. The Operations Log is
|
|
160
|
+
project scoped, and its list is not scoped to an organization, so a
|
|
161
|
+
task with no project never goes there. The description has no task,
|
|
162
|
+
request, or reply text. The row id comes from the event id and the
|
|
163
|
+
project id, so a row that is published again collapses.
|
|
147
164
|
"""
|
|
148
|
-
if event['type'] not in OPSLOG_EVENT_TYPES
|
|
149
|
-
return
|
|
165
|
+
if event['type'] not in OPSLOG_EVENT_TYPES:
|
|
166
|
+
return []
|
|
150
167
|
payload = event['payload']
|
|
151
168
|
if event['type'] == 'request.resolved':
|
|
152
169
|
description = f'{task["short_id"]}: {payload["kind"]} request '
|
|
@@ -154,21 +171,24 @@ def opslog_row(task: Row, event: Row) -> Row | None:
|
|
|
154
171
|
else:
|
|
155
172
|
description = f'{task["short_id"]} closed: {payload["outcome"]}'
|
|
156
173
|
actor = event['actor_id'] or 'imbi'
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
result: list[Row] = []
|
|
175
|
+
for project in task['projects']:
|
|
176
|
+
entry = models.OperationLog(
|
|
177
|
+
id=str(uuid.uuid5(event['event_id'], project['project_id'])),
|
|
178
|
+
occurred_at=event['at'],
|
|
179
|
+
recorded_by=actor,
|
|
180
|
+
performed_by=actor,
|
|
181
|
+
completed_at=event['at'],
|
|
182
|
+
project_id=project['project_id'],
|
|
183
|
+
project_slug=project['project_slug'],
|
|
184
|
+
environment_slug='',
|
|
185
|
+
entry_type='Agent Task',
|
|
186
|
+
description=description,
|
|
187
|
+
)
|
|
188
|
+
row = entry.model_dump(by_alias=True, mode='python')
|
|
189
|
+
row['is_deleted'] = 0
|
|
190
|
+
result.append(row)
|
|
191
|
+
return result
|
|
172
192
|
|
|
173
193
|
|
|
174
194
|
async def publish(
|
|
@@ -182,9 +202,7 @@ async def publish(
|
|
|
182
202
|
try:
|
|
183
203
|
# Before the events: when this publish fails, the sweep finds
|
|
184
204
|
# the events missing and publishes both again.
|
|
185
|
-
opslog = [
|
|
186
|
-
row for e in events if (row := opslog_row(task, e)) is not None
|
|
187
|
-
]
|
|
205
|
+
opslog = [row for e in events for row in opslog_rows(task, e)]
|
|
188
206
|
if opslog:
|
|
189
207
|
await iggy.publish_rows(OPSLOG_STREAM, OPSLOG_TOPIC, opslog)
|
|
190
208
|
if events:
|
|
@@ -233,6 +251,21 @@ async def archived_events(task: Row, after_seq: int, limit: int) -> list[Row]:
|
|
|
233
251
|
]
|
|
234
252
|
|
|
235
253
|
|
|
254
|
+
async def last_archived_agent_turn(task: Row) -> str | None:
|
|
255
|
+
"""Return the body of the newest agent ``turn`` of an archived task.
|
|
256
|
+
|
|
257
|
+
``None`` when the agent wrote no turn.
|
|
258
|
+
"""
|
|
259
|
+
rows = await clickhouse.query(
|
|
260
|
+
_LAST_AGENT_TURN_QUERY,
|
|
261
|
+
{
|
|
262
|
+
'organization_id': task['organization_id'],
|
|
263
|
+
'task_id': str(task['id']),
|
|
264
|
+
},
|
|
265
|
+
)
|
|
266
|
+
return str(rows[0]['body']) if rows else None
|
|
267
|
+
|
|
268
|
+
|
|
236
269
|
async def event_counts(
|
|
237
270
|
organization_id: str, task_ids: list[str]
|
|
238
271
|
) -> dict[str, tuple[int, int]]:
|
|
@@ -24,10 +24,10 @@ agent_id = "TEXT NOT NULL"
|
|
|
24
24
|
agent_version = "INTEGER NOT NULL"
|
|
25
25
|
prompt_version = "INTEGER"
|
|
26
26
|
service_account_id = "TEXT NOT NULL"
|
|
27
|
+
# Not used. A task had one primary project here before it had a list
|
|
28
|
+
# of projects (`task_projects`). Each start of imbi-api moves a value
|
|
29
|
+
# that it finds to `task_projects` and clears it.
|
|
27
30
|
project_id = "TEXT"
|
|
28
|
-
# The slug of the project at creation, for the Operations Log rows of
|
|
29
|
-
# the task. NULL for a task with no project, and for project tasks made
|
|
30
|
-
# before this column existed.
|
|
31
31
|
project_slug = "TEXT"
|
|
32
32
|
title = "TEXT NOT NULL"
|
|
33
33
|
description = "TEXT NOT NULL"
|
|
@@ -64,6 +64,10 @@ chain_id = "UUID"
|
|
|
64
64
|
# (`harness.platform_configs`) when the task was made. NULL when the
|
|
65
65
|
# organization had no configuration: the defaults apply.
|
|
66
66
|
platform_config_version = "INTEGER"
|
|
67
|
+
# When a person marked a closed task as needing no more attention, and
|
|
68
|
+
# the email of that person. The dashboard does not show it then.
|
|
69
|
+
acknowledged_at = "TIMESTAMP WITH TIME ZONE"
|
|
70
|
+
acknowledged_by = "TEXT"
|
|
67
71
|
|
|
68
72
|
[tables.primary_key]
|
|
69
73
|
columns = ["id"]
|
|
@@ -269,8 +273,7 @@ columns = ["organization_id", "dependent_task_id", "prerequisite_task_id"]
|
|
|
269
273
|
name = "task_dependencies_prerequisite_idx"
|
|
270
274
|
columns = ["organization_id", "prerequisite_task_id"]
|
|
271
275
|
|
|
272
|
-
# One row per project
|
|
273
|
-
# project (F13). The project is a graph node; `project_slug` is its slug
|
|
276
|
+
# One row per project of a task (F13). The project is a graph node; `project_slug` is its slug
|
|
274
277
|
# when the row was added, so the UI can name a project that was deleted.
|
|
275
278
|
[[tables]]
|
|
276
279
|
name = "task_projects"
|
|
@@ -110,6 +110,22 @@ _OPEN_REQUESTS = sql.SQL(
|
|
|
110
110
|
' AND (r.expires_at IS NULL OR r.expires_at > NOW())'
|
|
111
111
|
)
|
|
112
112
|
|
|
113
|
+
#: The projects of task ``t`` (F13), oldest first, as a JSON list of
|
|
114
|
+
#: ``project_id`` and ``project_slug``. Every task row has it.
|
|
115
|
+
PROJECTS: typing.LiteralString = (
|
|
116
|
+
'(SELECT coalesce(jsonb_agg(jsonb_build_object('
|
|
117
|
+
"'project_id', p.project_id, 'project_slug', p.project_slug)"
|
|
118
|
+
" ORDER BY p.added_at, p.project_id), '[]'::jsonb)"
|
|
119
|
+
' FROM agent_runtime.task_projects AS p'
|
|
120
|
+
' WHERE p.organization_id = t.organization_id AND p.task_id = t.id)'
|
|
121
|
+
' AS projects'
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
#: A task row with its projects. Continue it with a WHERE clause.
|
|
125
|
+
_SELECT_TASKS: typing.LiteralString = (
|
|
126
|
+
'SELECT t.*, ' + PROJECTS + ' FROM agent_runtime.tasks AS t' # noqa: S608
|
|
127
|
+
)
|
|
128
|
+
|
|
113
129
|
#: The tasks at the other end of the dependencies of a task, with the
|
|
114
130
|
#: link. ``this`` is the column of the task, ``other`` the other column.
|
|
115
131
|
_LINKED_TASKS = sql.SQL(
|
|
@@ -198,10 +214,6 @@ class SelfDependency(ValueError):
|
|
|
198
214
|
"""A task cannot depend on itself."""
|
|
199
215
|
|
|
200
216
|
|
|
201
|
-
class PrimaryProject(ValueError):
|
|
202
|
-
"""The project is the primary project of the task."""
|
|
203
|
-
|
|
204
|
-
|
|
205
217
|
@dataclasses.dataclass(frozen=True)
|
|
206
218
|
class Actor:
|
|
207
219
|
"""Who caused an event, and through which channel."""
|
|
@@ -220,7 +232,6 @@ class NewTask:
|
|
|
220
232
|
agent_version: int
|
|
221
233
|
prompt_version: int | None
|
|
222
234
|
service_account_id: str
|
|
223
|
-
project_id: str | None
|
|
224
235
|
title: str
|
|
225
236
|
description: str
|
|
226
237
|
origin_kind: str
|
|
@@ -229,7 +240,10 @@ class NewTask:
|
|
|
229
240
|
idempotency_key: str | None
|
|
230
241
|
owner: str
|
|
231
242
|
budget: decimal.Decimal | None
|
|
232
|
-
project_slug
|
|
243
|
+
#: The ``(project_id, project_slug)`` of each project of the task.
|
|
244
|
+
#: A child task (origin ``task``) gets the projects of its parent
|
|
245
|
+
#: instead.
|
|
246
|
+
projects: abc.Sequence[tuple[str, str]] = ()
|
|
233
247
|
#: The root task of the chain of a child task. ``None`` makes the
|
|
234
248
|
#: task the root of its own chain.
|
|
235
249
|
chain_id: uuid.UUID | None = None
|
|
@@ -437,6 +451,30 @@ class TaskStore:
|
|
|
437
451
|
if write.task['status'] == 'closed':
|
|
438
452
|
await _run_close_hooks(write.task)
|
|
439
453
|
|
|
454
|
+
async def move_legacy_projects(self) -> int:
|
|
455
|
+
"""Move the old single project of each task (``tasks.project_id``)
|
|
456
|
+
to ``task_projects``, and clear it. Return the number of tasks.
|
|
457
|
+
|
|
458
|
+
A task had one primary project before it had a project list. A
|
|
459
|
+
second run moves nothing, so each start runs it.
|
|
460
|
+
"""
|
|
461
|
+
async with self._pool.connection() as conn, conn.transaction():
|
|
462
|
+
cursor = await conn.execute(
|
|
463
|
+
'WITH moved AS (UPDATE agent_runtime.tasks AS t'
|
|
464
|
+
' SET project_id = NULL, project_slug = NULL'
|
|
465
|
+
' FROM agent_runtime.tasks AS o'
|
|
466
|
+
' WHERE o.id = t.id AND o.project_id IS NOT NULL'
|
|
467
|
+
' RETURNING t.organization_id, t.id, t.created_at,'
|
|
468
|
+
' o.project_id, o.project_slug)'
|
|
469
|
+
' INSERT INTO agent_runtime.task_projects (organization_id,'
|
|
470
|
+
' task_id, project_id, project_slug, added_by_kind,'
|
|
471
|
+
' added_by, added_at)'
|
|
472
|
+
' SELECT organization_id, id, project_id,'
|
|
473
|
+
" coalesce(project_slug, project_id), 'system', 'migration',"
|
|
474
|
+
' created_at FROM moved ON CONFLICT DO NOTHING'
|
|
475
|
+
)
|
|
476
|
+
return cursor.rowcount
|
|
477
|
+
|
|
440
478
|
async def find_by_idempotency_key(
|
|
441
479
|
self, organization_id: str, origin_kind: str, origin_id: str, key: str
|
|
442
480
|
) -> Row | None:
|
|
@@ -444,8 +482,8 @@ class TaskStore:
|
|
|
444
482
|
async with self._pool.connection() as conn:
|
|
445
483
|
return await _fetch_one(
|
|
446
484
|
conn,
|
|
447
|
-
|
|
448
|
-
' WHERE organization_id = %s AND origin_kind = %s'
|
|
485
|
+
_SELECT_TASKS
|
|
486
|
+
+ ' WHERE organization_id = %s AND origin_kind = %s'
|
|
449
487
|
' AND origin_id = %s AND idempotency_key = %s',
|
|
450
488
|
(organization_id, origin_kind, origin_id, key),
|
|
451
489
|
)
|
|
@@ -480,11 +518,11 @@ class TaskStore:
|
|
|
480
518
|
await conn.execute(
|
|
481
519
|
'INSERT INTO agent_runtime.tasks (id, organization_id,'
|
|
482
520
|
' short_id, agent_id, agent_version, prompt_version,'
|
|
483
|
-
' service_account_id,
|
|
521
|
+
' service_account_id, title,'
|
|
484
522
|
' description, origin_kind, origin_id, origin,'
|
|
485
523
|
' idempotency_key, owner, budget, chain_id,'
|
|
486
524
|
' platform_config_version) VALUES (%s,'
|
|
487
|
-
' %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
|
525
|
+
' %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,'
|
|
488
526
|
' %s, %s, %s, (SELECT max(version)'
|
|
489
527
|
' FROM harness.platform_configs'
|
|
490
528
|
' WHERE organization_id = %s))',
|
|
@@ -496,8 +534,6 @@ class TaskStore:
|
|
|
496
534
|
task.agent_version,
|
|
497
535
|
task.prompt_version,
|
|
498
536
|
task.service_account_id,
|
|
499
|
-
task.project_id,
|
|
500
|
-
task.project_slug,
|
|
501
537
|
task.title,
|
|
502
538
|
task.description,
|
|
503
539
|
task.origin_kind,
|
|
@@ -510,6 +546,45 @@ class TaskStore:
|
|
|
510
546
|
task.organization_id,
|
|
511
547
|
),
|
|
512
548
|
)
|
|
549
|
+
if task.origin_kind == 'task':
|
|
550
|
+
# The parent row is locked, so its projects cannot
|
|
551
|
+
# change until this commits.
|
|
552
|
+
await conn.execute(
|
|
553
|
+
'INSERT INTO agent_runtime.task_projects'
|
|
554
|
+
' (organization_id, task_id, project_id,'
|
|
555
|
+
' project_slug, added_by_kind, added_by)'
|
|
556
|
+
' SELECT organization_id, %s, project_id,'
|
|
557
|
+
' project_slug, %s, %s'
|
|
558
|
+
' FROM agent_runtime.task_projects'
|
|
559
|
+
' WHERE organization_id = %s AND task_id = %s::uuid',
|
|
560
|
+
(
|
|
561
|
+
task_id,
|
|
562
|
+
actor.kind,
|
|
563
|
+
actor.id,
|
|
564
|
+
task.organization_id,
|
|
565
|
+
task.origin_id,
|
|
566
|
+
),
|
|
567
|
+
)
|
|
568
|
+
elif task.projects:
|
|
569
|
+
async with conn.cursor() as cursor:
|
|
570
|
+
await cursor.executemany(
|
|
571
|
+
'INSERT INTO agent_runtime.task_projects'
|
|
572
|
+
' (organization_id, task_id, project_id,'
|
|
573
|
+
' project_slug, added_by_kind, added_by)'
|
|
574
|
+
' VALUES (%s, %s, %s, %s, %s, %s)'
|
|
575
|
+
' ON CONFLICT DO NOTHING',
|
|
576
|
+
[
|
|
577
|
+
(
|
|
578
|
+
task.organization_id,
|
|
579
|
+
task_id,
|
|
580
|
+
project_id,
|
|
581
|
+
project_slug,
|
|
582
|
+
actor.kind,
|
|
583
|
+
actor.id,
|
|
584
|
+
)
|
|
585
|
+
for project_id, project_slug in task.projects
|
|
586
|
+
],
|
|
587
|
+
)
|
|
513
588
|
event = await _append_event(
|
|
514
589
|
conn, task_id, 'task.created', event_payload, actor
|
|
515
590
|
)
|
|
@@ -537,8 +612,8 @@ class TaskStore:
|
|
|
537
612
|
async with self._pool.connection() as conn:
|
|
538
613
|
return await _fetch_one(
|
|
539
614
|
conn,
|
|
540
|
-
|
|
541
|
-
' WHERE organization_id = %s AND short_id = %s',
|
|
615
|
+
_SELECT_TASKS
|
|
616
|
+
+ ' WHERE organization_id = %s AND short_id = %s',
|
|
542
617
|
(organization_id, short_id),
|
|
543
618
|
)
|
|
544
619
|
|
|
@@ -558,8 +633,8 @@ class TaskStore:
|
|
|
558
633
|
"""Return tasks in the organization, newest first.
|
|
559
634
|
|
|
560
635
|
``text`` matches the title or the short id. A task also matches
|
|
561
|
-
when its agent is in ``text_agent_ids`` or its
|
|
562
|
-
``text_project_ids``; the caller finds those in the graph.
|
|
636
|
+
when its agent is in ``text_agent_ids`` or one of its projects is
|
|
637
|
+
in ``text_project_ids``; the caller finds those in the graph.
|
|
563
638
|
``before`` is the ``(created_at, id)`` keyset of the last row of
|
|
564
639
|
the previous page. Each row also has ``blocked_since``: for a
|
|
565
640
|
task that waits on a person (see :data:`_OPEN_REQUESTS`), the
|
|
@@ -582,7 +657,10 @@ class TaskStore:
|
|
|
582
657
|
conditions.append(
|
|
583
658
|
sql.SQL(
|
|
584
659
|
'(title ILIKE %s OR short_id ILIKE %s'
|
|
585
|
-
' OR agent_id = ANY(%s) OR
|
|
660
|
+
' OR agent_id = ANY(%s) OR EXISTS (SELECT 1'
|
|
661
|
+
' FROM agent_runtime.task_projects AS tp'
|
|
662
|
+
' WHERE tp.organization_id = t.organization_id'
|
|
663
|
+
' AND tp.task_id = t.id AND tp.project_id = ANY(%s)))'
|
|
586
664
|
)
|
|
587
665
|
)
|
|
588
666
|
params += [
|
|
@@ -595,7 +673,8 @@ class TaskStore:
|
|
|
595
673
|
conditions.append(sql.SQL('(created_at, id) < (%s, %s)'))
|
|
596
674
|
params += list(before)
|
|
597
675
|
statement = sql.SQL(
|
|
598
|
-
|
|
676
|
+
'SELECT t.*, ' + PROJECTS + ',' # noqa: S608
|
|
677
|
+
" CASE WHEN t.status <> 'closed' THEN"
|
|
599
678
|
' (SELECT min(r.opened_at) {open}) END AS blocked_since'
|
|
600
679
|
' FROM agent_runtime.tasks AS t WHERE {where}'
|
|
601
680
|
' ORDER BY created_at DESC, id DESC LIMIT %s'
|
|
@@ -639,20 +718,33 @@ class TaskStore:
|
|
|
639
718
|
)
|
|
640
719
|
return await cursor.fetchall()
|
|
641
720
|
|
|
642
|
-
async def last_agent_turn(self,
|
|
721
|
+
async def last_agent_turn(self, task: Row) -> str | None:
|
|
643
722
|
"""Return the body of the newest ``turn`` of the agent.
|
|
644
723
|
|
|
645
|
-
|
|
724
|
+
It reads ClickHouse when the log of the task is archived.
|
|
725
|
+
``None`` when the agent wrote no turn.
|
|
646
726
|
"""
|
|
727
|
+
if task['log_archived_at'] is not None:
|
|
728
|
+
return await log.last_archived_agent_turn(task)
|
|
729
|
+
# One statement reads the archive state and the turn from the same
|
|
730
|
+
# snapshot, so an archive that commits after the caller read
|
|
731
|
+
# ``task`` cannot hide the turn.
|
|
647
732
|
async with self._pool.connection() as conn:
|
|
648
733
|
cursor = await conn.execute(
|
|
649
|
-
|
|
650
|
-
"
|
|
651
|
-
" AND
|
|
652
|
-
|
|
734
|
+
'SELECT t.log_archived_at IS NOT NULL,'
|
|
735
|
+
" (SELECT e.payload->>'body' FROM agent_runtime.events AS e"
|
|
736
|
+
" WHERE e.task_id = t.id AND e.type = 'turn'"
|
|
737
|
+
" AND e.actor_kind = 'agent' ORDER BY e.seq DESC LIMIT 1)"
|
|
738
|
+
' FROM agent_runtime.tasks AS t WHERE t.id = %s',
|
|
739
|
+
(task['id'],),
|
|
653
740
|
)
|
|
654
741
|
row = await cursor.fetchone()
|
|
655
|
-
|
|
742
|
+
if row is None:
|
|
743
|
+
return None
|
|
744
|
+
archived, body = row
|
|
745
|
+
if archived:
|
|
746
|
+
return await log.last_archived_agent_turn(task)
|
|
747
|
+
return None if body is None else str(body)
|
|
656
748
|
|
|
657
749
|
async def unarchived(self) -> list[Row]:
|
|
658
750
|
"""Return the tasks whose log is not archived, by organization.
|
|
@@ -664,7 +756,8 @@ class TaskStore:
|
|
|
664
756
|
conn.cursor(row_factory=rows.dict_row) as cursor,
|
|
665
757
|
):
|
|
666
758
|
await cursor.execute(
|
|
667
|
-
'SELECT t.*,
|
|
759
|
+
'SELECT t.*, ' + PROJECTS + ',' # noqa: S608
|
|
760
|
+
' (SELECT count(*) FROM agent_runtime.budget_ledger'
|
|
668
761
|
' AS l WHERE l.task_id = t.id) AS ledger_count'
|
|
669
762
|
' FROM agent_runtime.tasks AS t'
|
|
670
763
|
' WHERE t.log_archived_at IS NULL'
|
|
@@ -745,8 +838,8 @@ class TaskStore:
|
|
|
745
838
|
async with self._pool.connection() as conn, conn.transaction():
|
|
746
839
|
task = await _fetch_one(
|
|
747
840
|
conn,
|
|
748
|
-
|
|
749
|
-
' WHERE organization_id = %s AND short_id = %s FOR UPDATE',
|
|
841
|
+
_SELECT_TASKS
|
|
842
|
+
+ ' WHERE organization_id = %s AND short_id = %s FOR UPDATE',
|
|
750
843
|
(organization_id, short_id),
|
|
751
844
|
)
|
|
752
845
|
if task is None:
|
|
@@ -875,6 +968,40 @@ class TaskStore:
|
|
|
875
968
|
await write.update({'owner': owner})
|
|
876
969
|
return write.task
|
|
877
970
|
|
|
971
|
+
async def acknowledge(
|
|
972
|
+
self, organization_id: str, short_id: str, actor_id: str | None
|
|
973
|
+
) -> Row:
|
|
974
|
+
"""Mark a closed task as seen by ``actor_id``, and return it.
|
|
975
|
+
|
|
976
|
+
The first acknowledgement stays. A closed task does not take
|
|
977
|
+
events, so this changes only the row.
|
|
978
|
+
|
|
979
|
+
Raises:
|
|
980
|
+
TaskNotFound: No such task.
|
|
981
|
+
TaskOpen: The task is not closed.
|
|
982
|
+
|
|
983
|
+
"""
|
|
984
|
+
async with self._pool.connection() as conn:
|
|
985
|
+
task = await _fetch_one(
|
|
986
|
+
conn,
|
|
987
|
+
'UPDATE agent_runtime.tasks AS t' # noqa: S608
|
|
988
|
+
' SET acknowledged_at = COALESCE(acknowledged_at, NOW()),'
|
|
989
|
+
' acknowledged_by = COALESCE(acknowledged_by, %s)'
|
|
990
|
+
' WHERE organization_id = %s AND short_id = %s'
|
|
991
|
+
" AND status = 'closed' RETURNING t.*, " + PROJECTS,
|
|
992
|
+
(actor_id, organization_id, short_id),
|
|
993
|
+
)
|
|
994
|
+
if task is not None:
|
|
995
|
+
return task
|
|
996
|
+
cursor = await conn.execute(
|
|
997
|
+
'SELECT 1 FROM agent_runtime.tasks'
|
|
998
|
+
' WHERE organization_id = %s AND short_id = %s',
|
|
999
|
+
(organization_id, short_id),
|
|
1000
|
+
)
|
|
1001
|
+
if await cursor.fetchone() is None:
|
|
1002
|
+
raise TaskNotFound(short_id)
|
|
1003
|
+
raise TaskOpen(short_id)
|
|
1004
|
+
|
|
878
1005
|
# --- Relations ----------------------------------------------------
|
|
879
1006
|
|
|
880
1007
|
@contextlib.asynccontextmanager
|
|
@@ -997,7 +1124,7 @@ class TaskStore:
|
|
|
997
1124
|
project_slug: str,
|
|
998
1125
|
actor: Actor,
|
|
999
1126
|
) -> bool:
|
|
1000
|
-
"""Associate a project
|
|
1127
|
+
"""Associate a project with a task.
|
|
1001
1128
|
|
|
1002
1129
|
The caller checks that the project is in the organization. Write
|
|
1003
1130
|
``project.associated``. Return ``False`` and write no event when
|
|
@@ -1006,12 +1133,9 @@ class TaskStore:
|
|
|
1006
1133
|
Raises:
|
|
1007
1134
|
TaskNotFound: No such task.
|
|
1008
1135
|
TaskArchived: The task is archived.
|
|
1009
|
-
PrimaryProject: The project is the primary project.
|
|
1010
1136
|
|
|
1011
1137
|
"""
|
|
1012
1138
|
async with self._relate(organization_id, [short_id]) as relate:
|
|
1013
|
-
if relate.tasks[0]['project_id'] == project_id:
|
|
1014
|
-
raise PrimaryProject(project_id)
|
|
1015
1139
|
row = await _fetch_one(
|
|
1016
1140
|
relate.conn,
|
|
1017
1141
|
'INSERT INTO agent_runtime.task_projects (organization_id,'
|
|
@@ -1097,14 +1221,14 @@ class TaskStore:
|
|
|
1097
1221
|
parent: Row | None = None
|
|
1098
1222
|
if task['origin_kind'] == 'task':
|
|
1099
1223
|
await cursor.execute(
|
|
1100
|
-
|
|
1101
|
-
' WHERE organization_id = %s AND id::text = %s',
|
|
1224
|
+
_SELECT_TASKS
|
|
1225
|
+
+ ' WHERE organization_id = %s AND id::text = %s',
|
|
1102
1226
|
(task['organization_id'], task['origin_id']),
|
|
1103
1227
|
)
|
|
1104
1228
|
parent = await cursor.fetchone()
|
|
1105
1229
|
await cursor.execute(
|
|
1106
|
-
|
|
1107
|
-
" WHERE organization_id = %s AND origin_kind = 'task'"
|
|
1230
|
+
_SELECT_TASKS
|
|
1231
|
+
+ " WHERE organization_id = %s AND origin_kind = 'task'"
|
|
1108
1232
|
' AND origin_id = %s ORDER BY created_at, id',
|
|
1109
1233
|
(task['organization_id'], str(task['id'])),
|
|
1110
1234
|
)
|
|
@@ -1715,8 +1839,8 @@ class TaskStore:
|
|
|
1715
1839
|
async with self._pool.connection() as conn:
|
|
1716
1840
|
task = await _fetch_one(
|
|
1717
1841
|
conn,
|
|
1718
|
-
|
|
1719
|
-
' WHERE organization_id = %s AND short_id = %s',
|
|
1842
|
+
_SELECT_TASKS
|
|
1843
|
+
+ ' WHERE organization_id = %s AND short_id = %s',
|
|
1720
1844
|
(organization_id, short_id),
|
|
1721
1845
|
)
|
|
1722
1846
|
if task is None:
|
|
@@ -1761,8 +1885,8 @@ async def requeue_chain(
|
|
|
1761
1885
|
"""
|
|
1762
1886
|
async with conn.cursor(row_factory=rows.dict_row) as cursor:
|
|
1763
1887
|
await cursor.execute(
|
|
1764
|
-
|
|
1765
|
-
' WHERE (chain_id = %s OR (chain_id IS NULL AND id = %s))'
|
|
1888
|
+
_SELECT_TASKS
|
|
1889
|
+
+ ' WHERE (chain_id = %s OR (chain_id IS NULL AND id = %s))'
|
|
1766
1890
|
" AND status <> 'closed' ORDER BY id FOR UPDATE",
|
|
1767
1891
|
(chain_id, chain_id),
|
|
1768
1892
|
)
|
|
@@ -1822,7 +1946,7 @@ async def append_event(
|
|
|
1822
1946
|
"""
|
|
1823
1947
|
task = await _fetch_one(
|
|
1824
1948
|
conn,
|
|
1825
|
-
|
|
1949
|
+
_SELECT_TASKS + ' WHERE id = %s'
|
|
1826
1950
|
' AND organization_id = %s AND log_archived_at IS NULL FOR UPDATE',
|
|
1827
1951
|
(task_id, organization_id),
|
|
1828
1952
|
)
|
|
@@ -2039,7 +2163,7 @@ async def _fetch_task(
|
|
|
2039
2163
|
) -> Row:
|
|
2040
2164
|
row = await _fetch_one(
|
|
2041
2165
|
conn,
|
|
2042
|
-
|
|
2166
|
+
_SELECT_TASKS + ' WHERE id = %s',
|
|
2043
2167
|
(task_id,),
|
|
2044
2168
|
)
|
|
2045
2169
|
if row is None: # pragma: no cover - read inside the write transaction
|
|
@@ -2055,8 +2179,8 @@ async def _lock_task(
|
|
|
2055
2179
|
"""Lock and return an open task, for a change in this transaction."""
|
|
2056
2180
|
task = await _fetch_one(
|
|
2057
2181
|
conn,
|
|
2058
|
-
|
|
2059
|
-
' WHERE organization_id = %s AND short_id = %s FOR UPDATE',
|
|
2182
|
+
_SELECT_TASKS
|
|
2183
|
+
+ ' WHERE organization_id = %s AND short_id = %s FOR UPDATE',
|
|
2060
2184
|
(organization_id, short_id),
|
|
2061
2185
|
)
|
|
2062
2186
|
if task is None:
|
|
@@ -2072,8 +2196,8 @@ async def _lock_tasks(
|
|
|
2072
2196
|
"""Lock and return tasks in task id order, closed or not."""
|
|
2073
2197
|
async with conn.cursor(row_factory=rows.dict_row) as cursor:
|
|
2074
2198
|
await cursor.execute(
|
|
2075
|
-
|
|
2076
|
-
' WHERE organization_id = %s AND short_id = ANY(%s)'
|
|
2199
|
+
_SELECT_TASKS
|
|
2200
|
+
+ ' WHERE organization_id = %s AND short_id = ANY(%s)'
|
|
2077
2201
|
' ORDER BY id FOR UPDATE',
|
|
2078
2202
|
(organization_id, short_ids),
|
|
2079
2203
|
)
|
|
@@ -2122,8 +2246,8 @@ async def _update_task(
|
|
|
2122
2246
|
changes: Row,
|
|
2123
2247
|
) -> Row:
|
|
2124
2248
|
statement = sql.SQL(
|
|
2125
|
-
'UPDATE agent_runtime.tasks SET {sets}, updated_at = NOW()'
|
|
2126
|
-
' WHERE id = %s RETURNING
|
|
2249
|
+
'UPDATE agent_runtime.tasks AS t SET {sets}, updated_at = NOW()' # noqa: S608
|
|
2250
|
+
' WHERE id = %s RETURNING t.*, ' + PROJECTS
|
|
2127
2251
|
).format(
|
|
2128
2252
|
sets=sql.SQL(', ').join(
|
|
2129
2253
|
sql.SQL('{col} = %s').format(col=sql.Identifier(col))
|