agentmetry 0.9.7__tar.gz → 0.9.8__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.
- {agentmetry-0.9.7 → agentmetry-0.9.8}/PKG-INFO +1 -1
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/main.py +33 -8
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/routes/audit.py +24 -1
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/cli/__init__.py +22 -2
- agentmetry-0.9.8/agentmetry/core/audit/antigravity_trajectory.py +81 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/cloud_bridge.py +24 -0
- agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_container_registry_publish.jsonl +3 -0
- agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_dependency_audit_network_burst.jsonl +4 -0
- agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_terraform_apply_with_profile.jsonl +4 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/corpus.yaml +28 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/external.py +2 -2
- agentmetry-0.9.8/agentmetry/core/audit/grok_bot_transcript.py +58 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/hook_bootstrap.py +30 -0
- agentmetry-0.9.8/agentmetry/core/audit/session_tree.py +190 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_db.py +76 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/doctor.py +49 -4
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/version.py +1 -1
- agentmetry-0.9.8/tests/test_antigravity_trajectory.py +35 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cloud_bridge.py +14 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_doctor.py +78 -0
- agentmetry-0.9.8/tests/test_grok_bot_transcript.py +44 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_bootstrap.py +59 -0
- agentmetry-0.9.8/tests/test_session_tree.py +188 -0
- agentmetry-0.9.8/tests/test_transcript_watcher.py +162 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/.dockerignore +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/.env.agentmetry-demo +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/.env.example +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/.gitignore +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/Dockerfile +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/README.md +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/routes/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/routes/auth.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/trusted_host.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/websocket.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/ws_bridge.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/cli/__main__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/api_token.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/agt.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/chronicle.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/ecs.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/sentinel.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/splunk.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/alerts.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/atlas.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/canonical.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/compliance_digest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/benchmark.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/disposition.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/engine.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/live.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/live_store.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/models.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/rules.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/traits.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/yaml_config.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/loader.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/models.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/scanner.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dogfood.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/evidence_pack.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/forwarder.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/hashing.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/heartbeat.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/identity.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/ingest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/managed_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/migrate.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/mitre.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/otel_ingest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/policy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/policy_settings.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/posture_event.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/redaction.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/replay.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/run_context.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/signing.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/sinks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/spool.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/capability.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/evaluator.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/loader.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/models.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_anchor.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_chain.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_merkle.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_rotation.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/auth.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/audit_exporter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/bridges.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/bus.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/events.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/outbox.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/config.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/dashboard_session.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/autostart.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/driver_paths.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/env_file.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/hook_coverage.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/machine_view.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/mcp_inventory.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/posture.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/drivers/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/drivers/host.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/drivers/permissions.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/drivers/spec.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/extensions.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/health.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/operator_identity.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/paths.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/demo.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/hooks/__init__.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/hooks/ingest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/hooks/mcp_proxy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/policies/detection/manifest.yaml +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/policies/dlp/manifest.yaml +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/policies/opa/agent_rules.rego +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/policies/tool/manifest.yaml +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/pyproject.toml +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/conftest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/codex_hooks/README.md +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/fake_mcp_server.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/otel_claude_code_session.json +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_agentmetry_audit.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_agentmetry_ingest_client.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_agt_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_ask_gate_phase_a.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_atlas_detection_enrichment.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_atlas_mapping.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_audit_sinks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_audit_stats.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_audit_tail.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_auth.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_auth_default.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_autostart.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_boot_sequence.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_burst_windows.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_chinese_agent_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_chinese_agent_sprint_b.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_chinese_agent_sprint_c.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_chronicle_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cli_backup.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cli_commands.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cli_env_var_names.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cloudevents_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_compliance_digest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_container_and_legacy_runtime.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_copilot_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_crewai_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_demo.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_adi.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_benchmark.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_default_config.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_disposition.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_disposition_api.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_duplicate_emission.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_emit_durability.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_engine.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_evasion.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_hf_incident.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_quoted_command_words.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_rule_identity.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_rules_v2.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dlp_agent_env_override.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dlp_invisible_unicode.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dlp_markdown_exfil.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dlp_scanner.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dogfood.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dogfood_freeze.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_driver_paths.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_ecs_threat.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_event_bus.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_event_signing.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_evidence_pack.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_extensions.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_external_ingest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_forwarder.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_forwarding_shape.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_funding_manifest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hash_key.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_heartbeat.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_coverage.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_decision_format.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_enforcement_timing.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_import_cost.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_real_process.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_spool.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_identity_enrich.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_identity_provider.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_ingest_roundtrip.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_isolation.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_launch_targets.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_live_detection.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_live_detection_store.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_machine_view.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_managed_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_audit_proxy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_concealed_text.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_inventory.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_inventory_event.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_meta_fingerprint.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_proxy_packaged.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_schema.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mitre_dlp.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_opensre_adapter.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_operator_attribution.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_otel_ingest.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_paths.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_posture.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_powershell_encoding.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_readme_claims.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_redaction.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_replay.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_sentinel.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_serve_script_binds_loopback.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_shared_install.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_sigma_pack.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_static_serving.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_subagent_lifecycle.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_suite_leaves_real_home_alone.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_tool_policy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_tool_policy_agent_cli.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_tool_policy_git_hooks.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trail_anchor.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trail_chain.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trail_concurrency.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trail_merkle.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trusted_host.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_unattended_agent_policy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_version.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_yaml_detection_rules.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tools/generate_sigma_pack.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tools/mcp_audit_proxy.py +0 -0
- {agentmetry-0.9.7 → agentmetry-0.9.8}/tools/vault_fs_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentmetry
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.8
|
|
4
4
|
Summary: Local-first flight recorder for AI coding agents: hash-chained audit trail, MITRE-mapped sequence detection, DLP
|
|
5
5
|
Project-URL: Homepage, https://agentmetry.ai
|
|
6
6
|
Project-URL: Source, https://github.com/blitzcrieg1/agentmetry
|
|
@@ -2,6 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
5
8
|
from contextlib import asynccontextmanager
|
|
6
9
|
from logging.handlers import RotatingFileHandler
|
|
7
10
|
from pathlib import Path
|
|
@@ -76,6 +79,34 @@ def _start_forwarders() -> list[asyncio.Task]:
|
|
|
76
79
|
return tasks
|
|
77
80
|
|
|
78
81
|
|
|
82
|
+
def start_transcript_watcher(watcher_path: Path) -> subprocess.Popen:
|
|
83
|
+
"""Start the Antigravity transcript watcher, tied to this process's life.
|
|
84
|
+
|
|
85
|
+
Its stdin is a pipe only this process holds, and the lifeline variable
|
|
86
|
+
tells it to exit at end-of-file. When this process dies, however it dies,
|
|
87
|
+
the pipe closes and the watcher follows. Without it each restart left a
|
|
88
|
+
watcher behind, and every one of them recorded every Antigravity call.
|
|
89
|
+
"""
|
|
90
|
+
return subprocess.Popen(
|
|
91
|
+
[sys.executable, str(watcher_path)],
|
|
92
|
+
stdin=subprocess.PIPE,
|
|
93
|
+
stdout=subprocess.DEVNULL,
|
|
94
|
+
stderr=subprocess.DEVNULL,
|
|
95
|
+
env={**os.environ, "AGENTMETRY_WATCHER_LIFELINE": "1"},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def stop_transcript_watcher(process: subprocess.Popen) -> None:
|
|
100
|
+
"""Close the lifeline, then terminate. Closing alone is enough; terminate is
|
|
101
|
+
for a watcher that predates the lifeline."""
|
|
102
|
+
try:
|
|
103
|
+
if process.stdin:
|
|
104
|
+
process.stdin.close()
|
|
105
|
+
except OSError:
|
|
106
|
+
pass
|
|
107
|
+
process.terminate()
|
|
108
|
+
|
|
109
|
+
|
|
79
110
|
@asynccontextmanager
|
|
80
111
|
async def lifespan(app: FastAPI):
|
|
81
112
|
from agentmetry.core.audit.migrate import backfill_db_from_jsonl
|
|
@@ -177,17 +208,11 @@ async def lifespan(app: FastAPI):
|
|
|
177
208
|
)
|
|
178
209
|
|
|
179
210
|
# Launch transcript watcher for Antigravity in the background
|
|
180
|
-
import subprocess
|
|
181
|
-
import sys
|
|
182
211
|
watcher_process = None
|
|
183
212
|
try:
|
|
184
213
|
watcher_path = Path(__file__).resolve().parents[4] / "scripts" / "antigravity_transcript_watcher.py"
|
|
185
214
|
if watcher_path.exists():
|
|
186
|
-
watcher_process =
|
|
187
|
-
[sys.executable, str(watcher_path)],
|
|
188
|
-
stdout=subprocess.DEVNULL,
|
|
189
|
-
stderr=subprocess.DEVNULL,
|
|
190
|
-
)
|
|
215
|
+
watcher_process = start_transcript_watcher(watcher_path)
|
|
191
216
|
logger.info("Started Antigravity transcript watcher (PID: %s)", watcher_process.pid)
|
|
192
217
|
except Exception as exc:
|
|
193
218
|
logger.warning("Failed to start Antigravity transcript watcher: %s", exc)
|
|
@@ -201,7 +226,7 @@ async def lifespan(app: FastAPI):
|
|
|
201
226
|
for task in bridge_tasks:
|
|
202
227
|
task.cancel()
|
|
203
228
|
if watcher_process:
|
|
204
|
-
watcher_process
|
|
229
|
+
stop_transcript_watcher(watcher_process)
|
|
205
230
|
|
|
206
231
|
|
|
207
232
|
app = FastAPI(
|
|
@@ -101,7 +101,7 @@ class ExternalIngestBody(BaseModel):
|
|
|
101
101
|
|
|
102
102
|
source_app: str = Field(
|
|
103
103
|
...,
|
|
104
|
-
description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre",
|
|
104
|
+
description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre | zcode | grok_bot",
|
|
105
105
|
)
|
|
106
106
|
event_type: str = Field(
|
|
107
107
|
...,
|
|
@@ -309,6 +309,29 @@ async def audit_session(
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
|
|
312
|
+
@router.get("/session/{correlation_id}/tree", dependencies=[Depends(require_api_key)])
|
|
313
|
+
async def audit_session_tree(
|
|
314
|
+
correlation_id: str,
|
|
315
|
+
limit: int = Query(20000, ge=1, le=50000),
|
|
316
|
+
around_utc: str = Query("", max_length=40),
|
|
317
|
+
):
|
|
318
|
+
"""One session folded into investigation-tree nodes.
|
|
319
|
+
|
|
320
|
+
Each ask, its answer and the call it led to become one node, so a detection
|
|
321
|
+
that cites the ask and a policy verdict recorded on it land on the call
|
|
322
|
+
they belong to. A session longer than `limit` is windowed on `around_utc`
|
|
323
|
+
(a detection's first_seen_utc) when given. See `core/audit/session_tree.py`.
|
|
324
|
+
"""
|
|
325
|
+
from agentmetry.core.audit.session_tree import session_tree
|
|
326
|
+
|
|
327
|
+
if not settings.audit_export_enabled:
|
|
328
|
+
return {"correlation_id": correlation_id, "enabled": False, "nodes": [], "summary": {}, "truncated": False}
|
|
329
|
+
try:
|
|
330
|
+
return await asyncio.to_thread(session_tree, correlation_id, limit, around_utc)
|
|
331
|
+
except Exception as exc:
|
|
332
|
+
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
333
|
+
|
|
334
|
+
|
|
312
335
|
@router.get("/detections/{correlation_id}", dependencies=[Depends(require_api_key)])
|
|
313
336
|
async def audit_detections(correlation_id: str):
|
|
314
337
|
"""Run correlated behavioral rules over one session and return detections.
|
|
@@ -551,6 +551,7 @@ def cmd_cloud(args: argparse.Namespace) -> int:
|
|
|
551
551
|
map_claude_messages,
|
|
552
552
|
map_claude_session,
|
|
553
553
|
map_cursor_agent,
|
|
554
|
+
map_outbound_poll,
|
|
554
555
|
)
|
|
555
556
|
|
|
556
557
|
base = (args.url or "").strip() or None
|
|
@@ -576,7 +577,9 @@ def cmd_cloud(args: argparse.Namespace) -> int:
|
|
|
576
577
|
if not api_key:
|
|
577
578
|
print("CURSOR_API_KEY is not set.")
|
|
578
579
|
return 2
|
|
579
|
-
|
|
580
|
+
cursor_base = base or __import__("agentmetry.core.audit.cloud_bridge", fromlist=["CURSOR_DEFAULT_BASE"]).CURSOR_DEFAULT_BASE
|
|
581
|
+
post(map_outbound_poll("cursor_cloud", "GET", f"{cursor_base.rstrip('/')}/v1/agents"))
|
|
582
|
+
for agent in fetch_cursor_agents(api_key, cursor_base):
|
|
580
583
|
runs = []
|
|
581
584
|
if not args.skip_runs:
|
|
582
585
|
try:
|
|
@@ -594,7 +597,9 @@ def cmd_cloud(args: argparse.Namespace) -> int:
|
|
|
594
597
|
print("ANTHROPIC_COMPLIANCE_KEY is not set (a Compliance Access Key with read:compliance_user_data, not an admin key).")
|
|
595
598
|
return 2
|
|
596
599
|
kind = "remote" if args.remote else "local"
|
|
597
|
-
|
|
600
|
+
claude_base = base or "https://api.anthropic.com"
|
|
601
|
+
post(map_outbound_poll("claude_cloud", "GET", f"{claude_base.rstrip('/')}/v1/compliance/apps/sessions/{kind}"))
|
|
602
|
+
sessions = fetch_claude_sessions(api_key, kind=kind, base=claude_base, limit=args.limit)
|
|
598
603
|
for session in sessions[: args.limit]:
|
|
599
604
|
post(map_claude_session(session))
|
|
600
605
|
try:
|
|
@@ -663,6 +668,21 @@ def cmd_hooks(args: argparse.Namespace) -> int:
|
|
|
663
668
|
from agentmetry.core.audit import hook_bootstrap
|
|
664
669
|
from agentmetry.core.diagnostics import hook_coverage
|
|
665
670
|
|
|
671
|
+
if args.action == "install":
|
|
672
|
+
# Before --managed too: a managed install would put the temporary path
|
|
673
|
+
# into machine-wide policy files.
|
|
674
|
+
reason = hook_bootstrap.ephemeral_environment()
|
|
675
|
+
if reason:
|
|
676
|
+
print(
|
|
677
|
+
f"Not installing hooks: {reason}. That cache can be deleted at any "
|
|
678
|
+
"time, and every hook would then fail (most agents silently stop "
|
|
679
|
+
"recording; Copilot CLI blocks every tool call). Install Agentmetry "
|
|
680
|
+
"somewhere that stays, then run this again:\n"
|
|
681
|
+
" uv tool install agentmetry (or: pipx install agentmetry, or: pip install agentmetry)\n"
|
|
682
|
+
" agentmetry hooks install",
|
|
683
|
+
file=sys.stderr,
|
|
684
|
+
)
|
|
685
|
+
return 1
|
|
666
686
|
if getattr(args, "managed", False):
|
|
667
687
|
return _hooks_managed(args)
|
|
668
688
|
if args.action == "status":
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Read tool calls out of an Antigravity conversation database.
|
|
2
|
+
|
|
3
|
+
The installed hook only matches `run_command`, so file reads, writes, search
|
|
4
|
+
and the other tools never reach the trail. The conversation database stores
|
|
5
|
+
every step. The payload is a protobuf blob. The tool name is a length-prefixed
|
|
6
|
+
ASCII field and the arguments are the JSON object that follows it. That framing
|
|
7
|
+
was read off a real database on 2026-10-16. A blob that does not match it
|
|
8
|
+
yields nothing.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sqlite3
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Iterator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _varint(buf: bytes, index: int) -> tuple[int, int]:
|
|
20
|
+
value = 0
|
|
21
|
+
shift = 0
|
|
22
|
+
while index < len(buf) and shift <= 28:
|
|
23
|
+
byte = buf[index]
|
|
24
|
+
index += 1
|
|
25
|
+
value |= (byte & 0x7F) << shift
|
|
26
|
+
if not byte & 0x80:
|
|
27
|
+
return value, index
|
|
28
|
+
shift += 7
|
|
29
|
+
raise ValueError("truncated varint")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def iter_tool_calls(payload: bytes) -> Iterator[tuple[str, dict[str, Any]]]:
|
|
33
|
+
"""Yield (tool name, arguments) from one step payload."""
|
|
34
|
+
index = 0
|
|
35
|
+
while True:
|
|
36
|
+
marker = payload.find(b"\x12", index)
|
|
37
|
+
if marker < 0 or marker + 2 >= len(payload):
|
|
38
|
+
return
|
|
39
|
+
name_len = payload[marker + 1]
|
|
40
|
+
if name_len == 0 or name_len > 64 or name_len & 0x80:
|
|
41
|
+
index = marker + 1
|
|
42
|
+
continue
|
|
43
|
+
name_at = marker + 2
|
|
44
|
+
name_end = name_at + name_len
|
|
45
|
+
if name_end >= len(payload) or payload[name_end] != 0x1A:
|
|
46
|
+
index = marker + 1
|
|
47
|
+
continue
|
|
48
|
+
name = payload[name_at:name_end]
|
|
49
|
+
if not name.isascii() or not all(chr(c).isalnum() or c == ord("_") for c in name):
|
|
50
|
+
index = marker + 1
|
|
51
|
+
continue
|
|
52
|
+
try:
|
|
53
|
+
json_len, json_at = _varint(payload, name_end + 1)
|
|
54
|
+
except ValueError:
|
|
55
|
+
index = marker + 1
|
|
56
|
+
continue
|
|
57
|
+
blob = payload[json_at : json_at + json_len]
|
|
58
|
+
if not blob.startswith(b"{"):
|
|
59
|
+
index = marker + 1
|
|
60
|
+
continue
|
|
61
|
+
try:
|
|
62
|
+
args = json.loads(blob)
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
index = marker + 1
|
|
65
|
+
continue
|
|
66
|
+
if isinstance(args, dict):
|
|
67
|
+
yield name.decode("ascii"), args
|
|
68
|
+
index = json_at + max(json_len, 1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def tool_names(db_path: Path) -> dict[str, int]:
|
|
72
|
+
"""Count tool names in one conversation database. Arguments are not returned."""
|
|
73
|
+
counts: dict[str, int] = {}
|
|
74
|
+
uri = f"file:{Path(db_path).as_posix()}?mode=ro"
|
|
75
|
+
with sqlite3.connect(uri, uri=True) as con:
|
|
76
|
+
for (payload,) in con.execute("SELECT step_payload FROM steps"):
|
|
77
|
+
if not isinstance(payload, (bytes, bytearray)):
|
|
78
|
+
continue
|
|
79
|
+
for name, _args in iter_tool_calls(payload):
|
|
80
|
+
counts[name] = counts.get(name, 0) + 1
|
|
81
|
+
return counts
|
|
@@ -26,6 +26,7 @@ from __future__ import annotations
|
|
|
26
26
|
import hashlib
|
|
27
27
|
import json
|
|
28
28
|
from typing import Any
|
|
29
|
+
from urllib.parse import urlsplit
|
|
29
30
|
|
|
30
31
|
REDACT_KEYS = frozenset(
|
|
31
32
|
{"authorization", "api_key", "apikey", "password", "secret", "token", "session_key"}
|
|
@@ -110,6 +111,29 @@ def tool_summary(args: Any) -> dict[str, Any]:
|
|
|
110
111
|
return summary
|
|
111
112
|
|
|
112
113
|
|
|
114
|
+
def map_outbound_poll(source_app: str, method: str, url: str) -> dict[str, Any]:
|
|
115
|
+
"""The request this machine sends. The key is not in the payload.
|
|
116
|
+
|
|
117
|
+
Query strings are dropped: a token does not belong in the trail. The path
|
|
118
|
+
and host are the trace of the send.
|
|
119
|
+
"""
|
|
120
|
+
parts = urlsplit(url)
|
|
121
|
+
target = f"{method} {parts.netloc}{parts.path}"
|
|
122
|
+
return {
|
|
123
|
+
"source_app": source_app,
|
|
124
|
+
"adapter": "cloud_bridge",
|
|
125
|
+
"event_type": "tool_called",
|
|
126
|
+
"outcome": "success",
|
|
127
|
+
"correlation_id": f"{source_app}-outbound",
|
|
128
|
+
"reason": "outbound poll; key sent and not stored",
|
|
129
|
+
"tool": {
|
|
130
|
+
"qualified": f"{source_app}.poll",
|
|
131
|
+
"server": source_app,
|
|
132
|
+
"command": target,
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
113
137
|
# --- Cursor Cloud Agents API -------------------------------------------------
|
|
114
138
|
|
|
115
139
|
CURSOR_DEFAULT_BASE = "https://api.cursor.com"
|
agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_container_registry_publish.jsonl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:00:00+00:00","tool":{"arguments":{"command":"docker login registry.example.com --username ci-bot --password-stdin"},"command":"docker login registry.example.com --username ci-bot --password-stdin","input_hash":"1f3b6a8a28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d01","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
|
|
2
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:00:30+00:00","tool":{"arguments":{"command":"docker build -t registry.example.com/team/service:build-42 ."},"command":"docker build -t registry.example.com/team/service:build-42 .","input_hash":"2a4c6d8e28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d02","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
3
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:01:20+00:00","tool":{"arguments":{"command":"docker push registry.example.com/team/service:build-42"},"command":"docker push registry.example.com/team/service:build-42","input_hash":"3b5d7e9f28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d03","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
|
agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_dependency_audit_network_burst.jsonl
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:00+00:00","tool":{"arguments":{"command":"npm audit --json > reports/npm-audit.json"},"command":"npm audit --json > reports/npm-audit.json","input_hash":"8091234428dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d08","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
2
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:20+00:00","tool":{"arguments":{"command":"pip-audit -r requirements.txt -f json -o reports/pip-audit.json"},"command":"pip-audit -r requirements.txt -f json -o reports/pip-audit.json","input_hash":"91a2345528dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d09","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
3
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:40+00:00","tool":{"arguments":{"command":"curl -s https://registry.npmjs.org/-/npm/v1/security/advisories?limit=20 > reports/npm-advisories.json"},"command":"curl -s https://registry.npmjs.org/-/npm/v1/security/advisories?limit=20 > reports/npm-advisories.json","input_hash":"a2b3456628dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d10","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
|
|
4
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e004","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:01:20+00:00","tool":{"arguments":{"command":"python scripts/summarise_audit.py reports/npm-audit.json reports/pip-audit.json reports/npm-advisories.json"},"command":"python scripts/summarise_audit.py reports/npm-audit.json reports/pip-audit.json reports/npm-advisories.json","input_hash":"b3c4567728dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d11","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_terraform_apply_with_profile.jsonl
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:00:00+00:00","tool":{"arguments":{"command":"terraform init -backend-config=env/staging.backend"},"command":"terraform init -backend-config=env/staging.backend","input_hash":"4c6e8f1028dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d04","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
2
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:02:00+00:00","tool":{"arguments":{"command":"terraform plan -var-file env/staging.tfvars -out tfplan"},"command":"terraform plan -var-file env/staging.tfvars -out tfplan","input_hash":"5d7f901128dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d05","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
3
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:04:00+00:00","tool":{"arguments":{"command":"AWS_PROFILE=staging terraform apply -auto-approve tfplan"},"command":"AWS_PROFILE=staging terraform apply -auto-approve tfplan","input_hash":"6e80912228dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d06","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
|
|
4
|
+
{"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d004","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:06:00+00:00","tool":{"arguments":{"command":"aws sts get-caller-identity --profile staging"},"command":"aws sts get-caller-identity --profile staging","input_hash":"7f90123328dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d07","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["cloud_api"]}}
|
|
@@ -419,6 +419,34 @@ cases:
|
|
|
419
419
|
Thirty read-only calls spread over half an hour. Burst rules measure a
|
|
420
420
|
time window; a long session must not trip them by length alone.
|
|
421
421
|
|
|
422
|
+
- name: container registry publish
|
|
423
|
+
session: benign_container_registry_publish.jsonl
|
|
424
|
+
expect: []
|
|
425
|
+
benign: true
|
|
426
|
+
note: >
|
|
427
|
+
`docker login`, `docker build` and `docker push` against a registry. This
|
|
428
|
+
looks like authenticated egress, but no credential is read from disk or
|
|
429
|
+
the environment; the password arrives through stdin and never belongs in
|
|
430
|
+
the trail.
|
|
431
|
+
|
|
432
|
+
- name: terraform apply with a named profile
|
|
433
|
+
session: benign_terraform_apply_with_profile.jsonl
|
|
434
|
+
expect: []
|
|
435
|
+
benign: true
|
|
436
|
+
note: >
|
|
437
|
+
Infrastructure automation touches cloud state and later calls `aws sts`,
|
|
438
|
+
but it uses a named profile rather than reading credentials. Cloud API use
|
|
439
|
+
by itself is not lateral movement after credential harvest.
|
|
440
|
+
|
|
441
|
+
- name: dependency audit touches remote advisories
|
|
442
|
+
session: benign_dependency_audit_network_burst.jsonl
|
|
443
|
+
expect: []
|
|
444
|
+
benign: true
|
|
445
|
+
note: >
|
|
446
|
+
Package audits query registries and advisory feeds in quick succession,
|
|
447
|
+
then run a local summariser. A short network-heavy audit is not a session
|
|
448
|
+
tool burst, a download cradle or credential exfiltration.
|
|
449
|
+
|
|
422
450
|
- name: human-driven deletes currently fire
|
|
423
451
|
session: benign_human_driven_deletes.jsonl
|
|
424
452
|
expect: [destructive-delete-burst]
|
|
@@ -21,13 +21,13 @@ from agentmetry.core.config import settings
|
|
|
21
21
|
ExternalApp = Literal[
|
|
22
22
|
"cursor", "claude", "antigravity", "codex", "mcp_proxy",
|
|
23
23
|
"qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
|
|
24
|
-
"cursor_cloud", "claude_cloud", "grok_build", "agentmetry",
|
|
24
|
+
"cursor_cloud", "claude_cloud", "grok_build", "grok_bot", "agentmetry",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
KNOWN_EXTERNAL_APPS = frozenset({
|
|
28
28
|
"cursor", "claude", "antigravity", "codex", "mcp_proxy",
|
|
29
29
|
"qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
|
|
30
|
-
"cursor_cloud", "claude_cloud", "grok_build",
|
|
30
|
+
"cursor_cloud", "claude_cloud", "grok_build", "grok_bot",
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
_ACTION_MAP = {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Grok Bot desktop chat cache to ingest payloads.
|
|
2
|
+
|
|
3
|
+
The desktop app writes the conversation to sand-client-persistence. Those
|
|
4
|
+
lines are the bot's own turns. They are not Cursor tool calls, and posting
|
|
5
|
+
them without source_app grok_bot used to land them as unknown or, before
|
|
6
|
+
that, as Cursor.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def message_text(entry: dict[str, Any]) -> str:
|
|
16
|
+
if entry.get("kind") == "message":
|
|
17
|
+
text = entry.get("content")
|
|
18
|
+
return text if isinstance(text, str) else ""
|
|
19
|
+
message = entry.get("message")
|
|
20
|
+
if isinstance(message, dict):
|
|
21
|
+
text = message.get("content")
|
|
22
|
+
return text if isinstance(text, str) else ""
|
|
23
|
+
return ""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _timestamp(entry: dict[str, Any]) -> str:
|
|
27
|
+
raw = entry.get("timestampMs")
|
|
28
|
+
if isinstance(raw, (int, float)) and raw > 1_000_000_000_000:
|
|
29
|
+
return datetime.fromtimestamp(raw / 1000, timezone.utc).isoformat()
|
|
30
|
+
return ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def payloads_for_entries(entries: list[dict[str, Any]], *, agent_id: str) -> list[dict[str, Any]]:
|
|
34
|
+
"""One ingest payload per chat line. The command column carries the text."""
|
|
35
|
+
out: list[dict[str, Any]] = []
|
|
36
|
+
for entry in entries:
|
|
37
|
+
text = message_text(entry).strip()
|
|
38
|
+
entry_id = str(entry.get("id") or "")
|
|
39
|
+
if not text or not entry_id:
|
|
40
|
+
continue
|
|
41
|
+
role = "user" if entry.get("role") == "user" else "assistant"
|
|
42
|
+
payload: dict[str, Any] = {
|
|
43
|
+
"source_app": "grok_bot",
|
|
44
|
+
"adapter": "grok_bot_transcript",
|
|
45
|
+
"event_type": "tool_called",
|
|
46
|
+
"correlation_id": agent_id,
|
|
47
|
+
"outcome": "success",
|
|
48
|
+
"tool": {
|
|
49
|
+
"qualified": f"grok_bot.{role}",
|
|
50
|
+
"server": "grok_bot",
|
|
51
|
+
"command": text[:2000],
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
stamp = _timestamp(entry)
|
|
55
|
+
if stamp:
|
|
56
|
+
payload["timestamp_utc"] = stamp
|
|
57
|
+
out.append(payload)
|
|
58
|
+
return out
|
|
@@ -174,6 +174,36 @@ def _console_script() -> Path | None:
|
|
|
174
174
|
return None
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
def ephemeral_environment(executable: str | None = None) -> str:
|
|
178
|
+
"""Why this interpreter is a temporary one, or "" when it will still exist later.
|
|
179
|
+
|
|
180
|
+
`uvx agentmetry ...` and `pipx run agentmetry ...` run from a cache the tool
|
|
181
|
+
may delete at any time (`uv cache clean`, a pipx cache expiry). A hook
|
|
182
|
+
config written from there names an executable inside that cache: it works
|
|
183
|
+
today, and after the next clean every hook fails. Most agents treat a
|
|
184
|
+
failing hook as a non-blocking error, so recording stops without a word;
|
|
185
|
+
Copilot CLI fails closed on a broken pre-tool hook and blocks every tool
|
|
186
|
+
call. Coverage would still report the agent covered, because it checks the
|
|
187
|
+
hook text in the config, not the file it points at.
|
|
188
|
+
"""
|
|
189
|
+
raw = executable or sys.executable
|
|
190
|
+
try:
|
|
191
|
+
resolved = Path(raw).resolve()
|
|
192
|
+
except OSError:
|
|
193
|
+
resolved = Path(raw)
|
|
194
|
+
# Split on both separators by hand: on POSIX a Windows path is one part.
|
|
195
|
+
text = str(resolved).replace("\\", "/").lower()
|
|
196
|
+
parts = [p for p in text.split("/") if p]
|
|
197
|
+
cache = os.environ.get("UV_CACHE_DIR", "").strip()
|
|
198
|
+
if cache and text.startswith(str(Path(cache).resolve()).replace("\\", "/").lower() + "/"):
|
|
199
|
+
return f"this interpreter is inside uv's cache ({resolved})"
|
|
200
|
+
if "uv" in parts and ("archive-v0" in parts or "builds-v0" in parts):
|
|
201
|
+
return f"this interpreter is inside uv's cache ({resolved})"
|
|
202
|
+
if "pipx" in parts and ".cache" in parts:
|
|
203
|
+
return f"this interpreter is inside pipx's run cache ({resolved})"
|
|
204
|
+
return ""
|
|
205
|
+
|
|
206
|
+
|
|
177
207
|
def hook_target(repo_root: Path | None = None) -> str:
|
|
178
208
|
"""Which of the three ways to reach ingest exists here: repo, frozen, wheel.
|
|
179
209
|
|