graph-agents-cli 0.3.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.
- graph_agents_cli/__init__.py +26 -0
- graph_agents_cli/_api_policy.py +2145 -0
- graph_agents_cli/_approvals.py +400 -0
- graph_agents_cli/_build.py +186 -0
- graph_agents_cli/_build_info.json +7 -0
- graph_agents_cli/_chat_client.py +462 -0
- graph_agents_cli/_click.py +157 -0
- graph_agents_cli/_defaults.py +139 -0
- graph_agents_cli/_experiments.py +64 -0
- graph_agents_cli/_http.py +192 -0
- graph_agents_cli/_output.py +83 -0
- graph_agents_cli/_project.py +462 -0
- graph_agents_cli/_remote.py +220 -0
- graph_agents_cli/_response_schema.py +264 -0
- graph_agents_cli/_runner.py +319 -0
- graph_agents_cli/_skills_check.py +274 -0
- graph_agents_cli/_tools.py +189 -0
- graph_agents_cli/_trust.py +66 -0
- graph_agents_cli/api/__init__.py +15 -0
- graph_agents_cli/api/_changes.py +506 -0
- graph_agents_cli/api/_files.py +658 -0
- graph_agents_cli/api/cmd_api.py +2480 -0
- graph_agents_cli/deploy/__init__.py +15 -0
- graph_agents_cli/deploy/_config.py +171 -0
- graph_agents_cli/deploy/_image.py +128 -0
- graph_agents_cli/deploy/_kube.py +286 -0
- graph_agents_cli/deploy/_modes.py +234 -0
- graph_agents_cli/deploy/_preflight.py +370 -0
- graph_agents_cli/deploy/_values.py +168 -0
- graph_agents_cli/deploy/cmd_deploy.py +1866 -0
- graph_agents_cli/deploy/gitops.py +562 -0
- graph_agents_cli/deploy/local_load.py +273 -0
- graph_agents_cli/dev/__init__.py +13 -0
- graph_agents_cli/dev/cmd_build.py +131 -0
- graph_agents_cli/dev/cmd_install.py +78 -0
- graph_agents_cli/dev/cmd_lint.py +119 -0
- graph_agents_cli/dev/cmd_playground.py +297 -0
- graph_agents_cli/dev/policy_check.py +1287 -0
- graph_agents_cli/eval/__init__.py +22 -0
- graph_agents_cli/eval/_client.py +670 -0
- graph_agents_cli/eval/_common.py +177 -0
- graph_agents_cli/eval/_judge.py +168 -0
- graph_agents_cli/eval/_judge_runner.py +238 -0
- graph_agents_cli/eval/_paths.py +212 -0
- graph_agents_cli/eval/checks.py +581 -0
- graph_agents_cli/eval/cmd_analyze.py +278 -0
- graph_agents_cli/eval/cmd_compare.py +284 -0
- graph_agents_cli/eval/cmd_eval_group.py +80 -0
- graph_agents_cli/eval/cmd_generate.py +558 -0
- graph_agents_cli/eval/cmd_grade.py +466 -0
- graph_agents_cli/eval/cmd_metric.py +156 -0
- graph_agents_cli/eval/cmd_run.py +370 -0
- graph_agents_cli/eval/cmd_submit.py +400 -0
- graph_agents_cli/eval/config.py +435 -0
- graph_agents_cli/eval/dataset.py +350 -0
- graph_agents_cli/eval/gate.py +420 -0
- graph_agents_cli/eval/transcript.py +192 -0
- graph_agents_cli/extension/__init__.py +13 -0
- graph_agents_cli/extension/_compat.py +86 -0
- graph_agents_cli/extension/_loader.py +293 -0
- graph_agents_cli/extension/_manifest.py +135 -0
- graph_agents_cli/extension/_overrides.py +195 -0
- graph_agents_cli/extension/_paths.py +91 -0
- graph_agents_cli/extension/_refs.py +193 -0
- graph_agents_cli/extension/_resolver.py +453 -0
- graph_agents_cli/extension/_schema.py +106 -0
- graph_agents_cli/extension/_spec.py +253 -0
- graph_agents_cli/extension/_sync.py +102 -0
- graph_agents_cli/extension/_trust.py +58 -0
- graph_agents_cli/extension/cmd_extension_add.py +259 -0
- graph_agents_cli/extension/cmd_extension_group.py +57 -0
- graph_agents_cli/extension/cmd_extension_list.py +56 -0
- graph_agents_cli/extension/cmd_extension_remove.py +61 -0
- graph_agents_cli/extension/cmd_extension_update.py +195 -0
- graph_agents_cli/info/__init__.py +13 -0
- graph_agents_cli/info/cmd_info.py +222 -0
- graph_agents_cli/infra/__init__.py +15 -0
- graph_agents_cli/infra/checks.py +1169 -0
- graph_agents_cli/infra/cmd_infra.py +103 -0
- graph_agents_cli/main.py +591 -0
- graph_agents_cli/peer/__init__.py +15 -0
- graph_agents_cli/peer/_generate.py +254 -0
- graph_agents_cli/peer/cmd_peer.py +1151 -0
- graph_agents_cli/run/__init__.py +13 -0
- graph_agents_cli/run/_local_server.py +1157 -0
- graph_agents_cli/run/_signals.py +141 -0
- graph_agents_cli/run/cmd_approvals.py +530 -0
- graph_agents_cli/run/cmd_run.py +1421 -0
- graph_agents_cli/scaffold/__init__.py +19 -0
- graph_agents_cli/scaffold/agents/README.md +24 -0
- graph_agents_cli/scaffold/agents/empty_py/.template/templateconfig.yaml +22 -0
- graph_agents_cli/scaffold/agents/langgraph/.env.example +292 -0
- graph_agents_cli/scaffold/agents/langgraph/.template/templateconfig.yaml +28 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile +59 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile.langgraph-server +59 -0
- graph_agents_cli/scaffold/agents/langgraph/README.md +571 -0
- graph_agents_cli/scaffold/agents/langgraph/api-policy.yaml +60 -0
- graph_agents_cli/scaffold/agents/langgraph/app/__init__.py +20 -0
- graph_agents_cli/scaffold/agents/langgraph/app/agent.py +174 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/__init__.py +15 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a.py +2162 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a_client.py +1167 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/api_client.py +4220 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/approvals.py +1349 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/auth.py +1986 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/chat.py +2962 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/checkpointer.py +432 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/content.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/db.py +580 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/limits.py +203 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/metrics.py +231 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/middleware.py +361 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/model.py +611 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/playground.py +230 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/run_locks.py +459 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/structured.py +755 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/telemetry.py +681 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/threads.py +493 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/token_exchange.py +959 -0
- graph_agents_cli/scaffold/agents/langgraph/app/fast_api_app.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/__init__.py +55 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/custom.py +97 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/__init__.py +46 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/example_api.py +92 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/weather.py +33 -0
- graph_agents_cli/scaffold/agents/langgraph/langgraph.json +14 -0
- graph_agents_cli/scaffold/agents/langgraph/pyproject.toml +78 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/conftest.py +376 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/datasets/basic-dataset.json +53 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/eval_config.yaml +32 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/approval_graph.py +137 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_issuer.py +216 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_openai.py +357 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_outcomes.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_relay.py +479 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_api_surface.py +812 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals.py +1367 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals_server.py +794 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor.py +497 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor_server.py +247 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_history_repair.py +278 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_model_apis.py +242 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_postgres.py +637 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_resilience_postgres.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_runtime_guardrails.py +854 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_e2e.py +340 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_runtime.py +989 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_answers.py +584 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_server.py +222 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_token_exchange_issuer.py +650 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/.results/.placeholder +0 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/README.md +22 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/conftest.py +21 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/load_test.py +81 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_client.py +824 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_scoping.py +724 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client.py +1214 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client_hardening.py +716 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_policy_rpc.py +767 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_approval_ledger.py +1536 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_fake_model.py +115 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_jwt_policy.py +991 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_limits.py +310 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging.py +148 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging_hardening.py +271 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_policy.py +378 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_resilience.py +610 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_server_auth.py +702 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_structured.py +673 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_telemetry.py +404 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_thread_listing.py +255 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_threads.py +268 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_token_exchange.py +1320 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_untrusted_content.py +393 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-fastapi.lock +2084 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-langgraph-server.lock +2106 -0
- graph_agents_cli/scaffold/agents/langgraph/{{cookiecutter.agent_guidance_filename}} +129 -0
- graph_agents_cli/scaffold/base_templates/_shared/graph-agents-cli-manifest.yaml +36 -0
- graph_agents_cli/scaffold/base_templates/python/.dockerignore +32 -0
- graph_agents_cli/scaffold/base_templates/python/.github/CODEOWNERS +30 -0
- graph_agents_cli/scaffold/base_templates/python/.github/agent.env +7 -0
- graph_agents_cli/scaffold/base_templates/python/.github/workflows/pr_checks.yaml +214 -0
- graph_agents_cli/scaffold/base_templates/python/.gitignore +209 -0
- graph_agents_cli/scaffold/base_templates/python/tests/unit/test_dummy.py +23 -0
- graph_agents_cli/scaffold/base_templates/python/{{cookiecutter.agent_guidance_filename}} +35 -0
- graph_agents_cli/scaffold/cmd_scaffold_group.py +49 -0
- graph_agents_cli/scaffold/commands/__init__.py +13 -0
- graph_agents_cli/scaffold/commands/create.py +1424 -0
- graph_agents_cli/scaffold/commands/enhance.py +1652 -0
- graph_agents_cli/scaffold/commands/upgrade.py +570 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/agent.env +12 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/promote-to-prod.yaml +371 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/staging.yaml +450 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-dev.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-prod.yaml +41 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-staging.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/.helmignore +14 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/Chart.yaml +21 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/examples/networkpolicy.yaml +103 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/NOTES.txt +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/_helpers.tpl +189 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/certificate.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/configmap.yaml +10 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/deployment.yaml +199 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/hpa.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/httproute.yaml +30 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/ingress.yaml +39 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/networkpolicy.yaml +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/pdb.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/postgresql-secret.yaml +37 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/service.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/serviceaccount.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/servicemonitor.yaml +42 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-dev.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-prod.yaml +45 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-staging.yaml +29 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values.yaml +396 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/tests/integration/test_chart.py +269 -0
- graph_agents_cli/scaffold/deployment_targets/none/README.md +5 -0
- graph_agents_cli/scaffold/deployment_targets/none/python/README.md +6 -0
- graph_agents_cli/scaffold/utils/__init__.py +13 -0
- graph_agents_cli/scaffold/utils/backup.py +212 -0
- graph_agents_cli/scaffold/utils/build_record.py +257 -0
- graph_agents_cli/scaffold/utils/cli_options.py +184 -0
- graph_agents_cli/scaffold/utils/fs.py +83 -0
- graph_agents_cli/scaffold/utils/generate_locks.py +214 -0
- graph_agents_cli/scaffold/utils/generation_metadata.py +88 -0
- graph_agents_cli/scaffold/utils/keyedit.py +768 -0
- graph_agents_cli/scaffold/utils/keymerge.py +537 -0
- graph_agents_cli/scaffold/utils/language.py +138 -0
- graph_agents_cli/scaffold/utils/lock_utils.py +94 -0
- graph_agents_cli/scaffold/utils/logging.py +77 -0
- graph_agents_cli/scaffold/utils/manifest.py +292 -0
- graph_agents_cli/scaffold/utils/merge.py +970 -0
- graph_agents_cli/scaffold/utils/merge3.py +216 -0
- graph_agents_cli/scaffold/utils/openapi_seed.py +199 -0
- graph_agents_cli/scaffold/utils/remote_template.py +376 -0
- graph_agents_cli/scaffold/utils/template.py +1352 -0
- graph_agents_cli/scaffold/utils/upgrade.py +894 -0
- graph_agents_cli/scaffold/utils/version.py +438 -0
- graph_agents_cli/secrets/__init__.py +15 -0
- graph_agents_cli/secrets/_apply.py +954 -0
- graph_agents_cli/secrets/_required.py +188 -0
- graph_agents_cli/secrets/cmd_secrets.py +211 -0
- graph_agents_cli/setup/__init__.py +13 -0
- graph_agents_cli/setup/_antigravity.py +221 -0
- graph_agents_cli/setup/cmd_auth.py +1030 -0
- graph_agents_cli/setup/cmd_dev_token.py +513 -0
- graph_agents_cli/setup/cmd_setup.py +428 -0
- graph_agents_cli/setup/cmd_update.py +140 -0
- graph_agents_cli/skills/__init__.py +13 -0
- graph_agents_cli/skills/_bundle.py +65 -0
- graph_agents_cli/skills/data/README.md +19 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/SKILL.md +357 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/github-settings.md +113 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/gitops.md +137 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/kubernetes.md +315 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/secrets.md +160 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/SKILL.md +303 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/dataset_schema.md +282 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/metrics-guide.md +143 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/SKILL.md +659 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langchain-models.md +124 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langgraph.md +235 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/template-contract.md +477 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/SKILL.md +231 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/langsmith.md +46 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/otel.md +59 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/SKILL.md +414 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/references/flags.md +134 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/SKILL.md +478 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/brainstorming.md +118 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/commands.md +419 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/extension.md +156 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/internals.md +67 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/spec-template.md +56 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/terminology.md +119 -0
- graph_agents_cli/system/__init__.py +15 -0
- graph_agents_cli/system/_apply.py +519 -0
- graph_agents_cli/system/_checks.py +1023 -0
- graph_agents_cli/system/_deploy.py +215 -0
- graph_agents_cli/system/_model.py +363 -0
- graph_agents_cli/system/_system.py +664 -0
- graph_agents_cli/system/_views.py +208 -0
- graph_agents_cli/system/cmd_system.py +423 -0
- graph_agents_cli-0.3.1.dist-info/METADATA +162 -0
- graph_agents_cli-0.3.1.dist-info/RECORD +291 -0
- graph_agents_cli-0.3.1.dist-info/WHEEL +4 -0
- graph_agents_cli-0.3.1.dist-info/entry_points.txt +2 -0
- graph_agents_cli-0.3.1.dist-info/licenses/LICENSE +201 -0
- graph_agents_cli-0.3.1.dist-info/licenses/NOTICE +19 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Copyright 2026 graph-agents-cli contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Thread ownership (atomic claim, owner, read-across role, stranger), run locks, redaction."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import ast
|
|
20
|
+
import asyncio
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
from fastapi import HTTPException
|
|
25
|
+
from langchain_core.messages import AIMessage
|
|
26
|
+
|
|
27
|
+
from {{cookiecutter.agent_directory}}.app_utils.auth import Actor, Principal
|
|
28
|
+
from {{cookiecutter.agent_directory}}.app_utils.chat import serialize_message
|
|
29
|
+
from {{cookiecutter.agent_directory}}.app_utils.db import Database
|
|
30
|
+
from {{cookiecutter.agent_directory}}.app_utils.threads import (
|
|
31
|
+
ThreadBusy,
|
|
32
|
+
ThreadLocks,
|
|
33
|
+
ThreadStore,
|
|
34
|
+
assert_access,
|
|
35
|
+
assert_owner,
|
|
36
|
+
can_access,
|
|
37
|
+
is_owner,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
OWNER = Principal(id="a", roles=["viewer"])
|
|
41
|
+
STRANGER = Principal(id="b", roles=["viewer"])
|
|
42
|
+
AUDITOR = Principal(id="c", roles=["auditor"])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture
|
|
46
|
+
def store(monkeypatch: pytest.MonkeyPatch) -> ThreadStore:
|
|
47
|
+
monkeypatch.setenv("AUTH_READ_ACROSS_ROLES", "auditor, ops")
|
|
48
|
+
return ThreadStore(Database("memory"))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def test_owner_reads_and_continues_its_thread(store: ThreadStore) -> None:
|
|
52
|
+
record = await store.create("t1", OWNER)
|
|
53
|
+
assert is_owner(OWNER, record) and can_access(OWNER, record)
|
|
54
|
+
assert (await store.ensure("t1", OWNER)).principal_id == "a"
|
|
55
|
+
assert [r.thread_id for r in await store.list_for(OWNER)] == ["t1"]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def test_stranger_is_refused_everywhere(store: ThreadStore) -> None:
|
|
59
|
+
record = await store.create("t1", OWNER)
|
|
60
|
+
assert not can_access(STRANGER, record)
|
|
61
|
+
for check in (assert_access, assert_owner):
|
|
62
|
+
with pytest.raises(HTTPException) as exc:
|
|
63
|
+
check(STRANGER, record)
|
|
64
|
+
assert exc.value.status_code == 403
|
|
65
|
+
with pytest.raises(HTTPException) as exc:
|
|
66
|
+
await store.ensure("t1", STRANGER)
|
|
67
|
+
assert exc.value.status_code == 403
|
|
68
|
+
assert await store.list_for(STRANGER) == []
|
|
69
|
+
# Creating a thread of its own is unchanged.
|
|
70
|
+
assert (await store.ensure("t2", STRANGER)).principal_id == "b"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def test_read_across_role_reads_but_does_not_write(store: ThreadStore) -> None:
|
|
74
|
+
record = await store.create("t1", OWNER)
|
|
75
|
+
assert can_access(AUDITOR, record) and not is_owner(AUDITOR, record)
|
|
76
|
+
assert_access(AUDITOR, record) # reads are allowed
|
|
77
|
+
assert [r.thread_id for r in await store.list_for(AUDITOR)] == ["t1"]
|
|
78
|
+
with pytest.raises(HTTPException) as exc:
|
|
79
|
+
assert_owner(AUDITOR, record)
|
|
80
|
+
assert exc.value.status_code == 403
|
|
81
|
+
# chat.send (ensure) is a write: a read-across role may not continue another's thread.
|
|
82
|
+
with pytest.raises(HTTPException) as exc:
|
|
83
|
+
await store.ensure("t1", AUDITOR)
|
|
84
|
+
assert exc.value.status_code == 403
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def test_read_across_roles_default_to_none(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
88
|
+
monkeypatch.delenv("AUTH_READ_ACROSS_ROLES", raising=False)
|
|
89
|
+
store = ThreadStore(Database("memory"))
|
|
90
|
+
record = await store.create("t1", OWNER)
|
|
91
|
+
assert not can_access(AUDITOR, record)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_serialize_message_omits_tool_args_when_asked() -> None:
|
|
95
|
+
message = AIMessage(
|
|
96
|
+
content="",
|
|
97
|
+
tool_calls=[{"id": "c1", "name": "probe", "args": {"query": "SF"}}],
|
|
98
|
+
)
|
|
99
|
+
with_args = serialize_message(message)
|
|
100
|
+
assert with_args["tool_calls"] == [{"id": "c1", "name": "probe", "args": {"query": "SF"}}]
|
|
101
|
+
without = serialize_message(message, include_tool_args=False)
|
|
102
|
+
assert without["tool_calls"] == [{"id": "c1", "name": "probe"}]
|
|
103
|
+
assert "args" not in without["tool_calls"][0]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def test_claim_is_atomic_under_concurrency(store: ThreadStore) -> None:
|
|
107
|
+
"""Two principals racing for one new id: exactly one owns it, the other is refused."""
|
|
108
|
+
results = await asyncio.gather(store.claim("race", OWNER), store.claim("race", STRANGER))
|
|
109
|
+
assert sorted(created for _, created in results) == [False, True]
|
|
110
|
+
owner = next(record for record, created in results if created).principal_id
|
|
111
|
+
loser = STRANGER if owner == OWNER.id else OWNER
|
|
112
|
+
with pytest.raises(HTTPException) as exc:
|
|
113
|
+
await store.ensure("race", loser)
|
|
114
|
+
assert exc.value.status_code == 403
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def test_continuing_a_thread_moves_its_idle_clock(store: ThreadStore) -> None:
|
|
118
|
+
record = await store.create("t1", OWNER)
|
|
119
|
+
store._memory["t1"].updated_at = "2000-01-01T00:00:00+00:00"
|
|
120
|
+
assert await store.idle_before("2001-01-01T00:00:00+00:00") == ["t1"]
|
|
121
|
+
await store.ensure("t1", OWNER)
|
|
122
|
+
assert await store.idle_before("2001-01-01T00:00:00+00:00") == []
|
|
123
|
+
# The owner is listed hashed, never as the raw principal id.
|
|
124
|
+
assert record.public().keys() == {"thread_id", "owner", "created_at", "updated_at"}
|
|
125
|
+
assert record.public()["owner"] == OWNER.hashed_id() != OWNER.id
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def test_list_is_most_recent_first_and_paged(store: ThreadStore) -> None:
|
|
129
|
+
for i in range(3):
|
|
130
|
+
await store.create(f"t{i}", OWNER)
|
|
131
|
+
store._memory[f"t{i}"].updated_at = f"2026-01-0{i + 1}T00:00:00+00:00"
|
|
132
|
+
assert [r.thread_id for r in await store.list_for(OWNER)] == ["t2", "t1", "t0"]
|
|
133
|
+
assert [r.thread_id for r in await store.list_for(OWNER, limit=1, offset=1)] == ["t1"]
|
|
134
|
+
assert await store.list_for(STRANGER) == []
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def test_one_run_per_thread() -> None:
|
|
138
|
+
locks = ThreadLocks()
|
|
139
|
+
lease = await locks.acquire("t1")
|
|
140
|
+
with pytest.raises(ThreadBusy):
|
|
141
|
+
await locks.acquire("t1")
|
|
142
|
+
other = await locks.acquire("t2") # other threads are independent
|
|
143
|
+
await lease.release()
|
|
144
|
+
await lease.release() # idempotent
|
|
145
|
+
again = await locks.acquire("t1")
|
|
146
|
+
assert locks.held == {"t1", "t2"}
|
|
147
|
+
await again.release()
|
|
148
|
+
await other.release()
|
|
149
|
+
assert locks.held == frozenset()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async def test_the_fence_admits_only_held_threads() -> None:
|
|
153
|
+
"""In-process leases never expire; writes to a thread nobody here holds are refused."""
|
|
154
|
+
from {{cookiecutter.agent_directory}}.app_utils.threads import LeaseLost
|
|
155
|
+
|
|
156
|
+
locks = ThreadLocks()
|
|
157
|
+
lease = await locks.acquire("t1")
|
|
158
|
+
locks.fence("t1")
|
|
159
|
+
with pytest.raises(LeaseLost):
|
|
160
|
+
locks.fence("t2")
|
|
161
|
+
await lease.release()
|
|
162
|
+
with pytest.raises(LeaseLost):
|
|
163
|
+
locks.fence("t1")
|
|
164
|
+
with pytest.raises(LeaseLost):
|
|
165
|
+
lease.check()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# --- threads an agent started for its user (0.3) ------------------------------------------
|
|
169
|
+
|
|
170
|
+
ALICE = Principal(id="a", roles=["viewer"])
|
|
171
|
+
CONCIERGE = Principal(id="a", actor=Actor(id="concierge", chain=("concierge",)))
|
|
172
|
+
BILLING = Principal(id="a", actor=Actor(id="billing", chain=("billing",)))
|
|
173
|
+
BOBS_CONCIERGE = Principal(id="b", actor=Actor(id="concierge", chain=("concierge",)))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def test_the_subject_owns_the_threads_its_agents_start(store: ThreadStore) -> None:
|
|
177
|
+
record = await store.create("t-agent", CONCIERGE)
|
|
178
|
+
assert record.actor == "concierge" and record.principal_id == "a"
|
|
179
|
+
# The person reads, continues and deletes it.
|
|
180
|
+
assert is_owner(ALICE, record) and can_access(ALICE, record)
|
|
181
|
+
assert (await store.ensure("t-agent", ALICE)).actor == "concierge"
|
|
182
|
+
assert [r.thread_id for r in await store.list_for(ALICE)] == ["t-agent"]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
async def test_an_agent_reaches_only_its_own_threads_for_the_user(store: ThreadStore) -> None:
|
|
186
|
+
concierge_thread = await store.create("t-concierge", CONCIERGE)
|
|
187
|
+
direct_thread = await store.create("t-direct", ALICE)
|
|
188
|
+
assert is_owner(CONCIERGE, concierge_thread)
|
|
189
|
+
for stranger in (BILLING, BOBS_CONCIERGE):
|
|
190
|
+
assert not can_access(stranger, concierge_thread)
|
|
191
|
+
for check in (assert_access, assert_owner):
|
|
192
|
+
with pytest.raises(HTTPException) as exc:
|
|
193
|
+
check(stranger, concierge_thread)
|
|
194
|
+
assert exc.value.status_code == 403
|
|
195
|
+
assert exc.value.detail == "This thread belongs to another principal."
|
|
196
|
+
with pytest.raises(HTTPException):
|
|
197
|
+
await store.ensure("t-concierge", stranger)
|
|
198
|
+
# A direct thread (one a person started, or one from before 0.3) is no agent's.
|
|
199
|
+
assert not is_owner(CONCIERGE, direct_thread)
|
|
200
|
+
with pytest.raises(HTTPException):
|
|
201
|
+
await store.ensure("t-direct", CONCIERGE)
|
|
202
|
+
assert [r.thread_id for r in await store.list_for(CONCIERGE)] == ["t-concierge"]
|
|
203
|
+
assert await store.list_for(BILLING) == []
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
async def test_a_delegated_principals_roles_never_read_across(store: ThreadStore) -> None:
|
|
207
|
+
record = await store.create("t1", OWNER)
|
|
208
|
+
agent = Principal(id="c", roles=["auditor"], actor=Actor(id="concierge"))
|
|
209
|
+
assert not can_access(agent, record)
|
|
210
|
+
assert await store.list_for(agent) == []
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_a_record_without_an_actor_reads_as_direct() -> None:
|
|
214
|
+
from {{cookiecutter.agent_directory}}.app_utils.threads import _record_from_row
|
|
215
|
+
|
|
216
|
+
legacy = _record_from_row({"thread_id": "t", "principal_id": "a", "tenant": None})
|
|
217
|
+
assert legacy.actor == "" and is_owner(ALICE, legacy) and not is_owner(CONCIERGE, legacy)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# Every place the template builds a `Principal`: (file, function, whether it passes `actor`).
|
|
221
|
+
# A site that rebuilds a principal without its actor turns an agent into the person it acts
|
|
222
|
+
# for, so a new site fails this test until it is reviewed and added here.
|
|
223
|
+
REVIEWED_PRINCIPAL_SITES = sorted(
|
|
224
|
+
[
|
|
225
|
+
# A2A: a task owner key's subject hashed to compare it with an approval's requester
|
|
226
|
+
# (the actor compared apart).
|
|
227
|
+
("app_utils/a2a.py", "owner_is_requester", False),
|
|
228
|
+
("app_utils/approvals.py", "resume_principal", True), # the requester's @actor
|
|
229
|
+
("app_utils/auth.py", "authenticate", False), # shared-bearer: one direct principal
|
|
230
|
+
("app_utils/auth.py", "finalize_principal", False), # direct: only @actor removed
|
|
231
|
+
("app_utils/auth.py", "finalize_principal", True),
|
|
232
|
+
("app_utils/auth.py", "on_threads_create_run", False), # hashes the subject only
|
|
233
|
+
("app_utils/auth.py", "principal_from_claims", True), # jwt: act / azp
|
|
234
|
+
# The forwarded words added to (or taken from) a principal, its actor kept.
|
|
235
|
+
("app_utils/auth.py", "with_origin", True),
|
|
236
|
+
("app_utils/threads.py", "owner_hash", False), # hashes the subject only
|
|
237
|
+
("app_utils/threads.py", "own_view", True),
|
|
238
|
+
]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _principal_sites(root: Path) -> list[tuple[str, str, bool]]:
|
|
243
|
+
sites: set[tuple[str, str, bool]] = set()
|
|
244
|
+
for path in sorted(root.rglob("*.py")):
|
|
245
|
+
tree = ast.parse(path.read_text(encoding="utf-8"))
|
|
246
|
+
# Each call's innermost function: `ast.walk` reaches an outer function first.
|
|
247
|
+
innermost: dict[int, tuple[str, ast.Call]] = {}
|
|
248
|
+
for function in ast.walk(tree):
|
|
249
|
+
if not isinstance(function, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
250
|
+
continue
|
|
251
|
+
for node in ast.walk(function):
|
|
252
|
+
if (
|
|
253
|
+
isinstance(node, ast.Call)
|
|
254
|
+
and isinstance(node.func, ast.Name)
|
|
255
|
+
and node.func.id == "Principal"
|
|
256
|
+
):
|
|
257
|
+
innermost[id(node)] = (function.name, node)
|
|
258
|
+
for name, node in innermost.values():
|
|
259
|
+
has_actor = any(k.arg == "actor" for k in node.keywords)
|
|
260
|
+
sites.add((path.relative_to(root).as_posix(), name, has_actor))
|
|
261
|
+
return sorted(sites)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_no_principal_reconstruction_drops_actor() -> None:
|
|
265
|
+
from {{cookiecutter.agent_directory}} import agent
|
|
266
|
+
|
|
267
|
+
root = Path(agent.__file__).parent
|
|
268
|
+
assert _principal_sites(root) == sorted(set(REVIEWED_PRINCIPAL_SITES))
|