gdmcode 0.1.4__tar.gz → 0.1.6__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.
- {gdmcode-0.1.4 → gdmcode-0.1.6}/PKG-INFO +1 -1
- gdmcode-0.1.6/gdmcode/__init__.py +1 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/tool_orchestrator.py +3 -11
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/cli.py +118 -6
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/mcp_server.py +1 -1
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/main.py +14 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/definitions.py +2 -3
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/repl.py +79 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/__init__.py +15 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/pyproject.toml +1 -1
- gdmcode-0.1.6/tests/test_cli_smoke.py +115 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_health.py +9 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_repl_smoke.py +36 -0
- gdmcode-0.1.4/gdmcode/__init__.py +0 -1
- gdmcode-0.1.4/tests/test_cli_smoke.py +0 -59
- {gdmcode-0.1.4 → gdmcode-0.1.6}/.gitignore +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/CONTRIBUTING.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/README.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/config.toml +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/agentic-runtime-audit.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/architecture.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/cli-reference.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/configuration.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/deployment.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/plugin-guide.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/quick-start.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/security-hardening.md +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/_internal/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/_internal/constants.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/_internal/domain_skills.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/commit_classifier.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/context_budget.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/daemon.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/dag_validator.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/debug_loop.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/impact_analyzer.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/impact_graph.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/loop.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/orchestrator.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/regression_guard.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/review_gate.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/risk_scorer.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/self_healing.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/smart_test_selector.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/system_prompt.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/task_tracker.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/test_validator.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/transcript.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/verification_loop.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/work_director.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/worktree_manager.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/artifacts/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/artifacts/artifact_store.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/artifacts/verification_graph.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/auth.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/commands.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/config.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/cost_tracker.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/db/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/db/migrations.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/audit_log.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/identity.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/rbac.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/team_config.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/usage_analytics.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/exceptions.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/git_workflow.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/github_actions.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/sentry_integration.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/sentry_server.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/webhook_security.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/code_index.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/compressor.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/context_memory.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/continuous_memory.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/conventions.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/db.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/document_index.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/file_cache.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/project_scanner.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/session_store.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/client.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/router.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/schemas.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/permissions.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/command_filter.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/models.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/permission_handler.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/phone_ui.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/protocol.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/qr.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/server.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/token_manager.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/tunnel.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/runtime/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/runtime/branch_farm.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/runtime/replay.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sandbox/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sandbox/hermetic.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sandbox/policy.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sdk/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sdk/plugin_base.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sdk/plugin_host.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sdk/plugin_loader.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/security.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/bridge.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/bridge_cli.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/bridge_client.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/protocol_version.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/session/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/session/event_fanout.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/session/input_broker.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/session/permission_bridge.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/_atomic.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/agent_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/ask_user_tool.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/bash_tool.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/browser_tool.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/browser_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/dep_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/document_reader.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/document_tool.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/document_writer.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/impact_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/playwright_tool.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/quality_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/read_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/result_cache.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/search_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/shell_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/write_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/audio_capture.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/audio_playback.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/errors.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/models.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/providers.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/vad.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/voice_loop.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/proxy/Dockerfile +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/proxy/main.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/proxy/requirements.txt +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/remote/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/remote/test_remote_server.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_agent_loop.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_agent_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_api_fallback.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_artifact_store.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_audit_log.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_auth.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_auto_quality.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_autonomy_levels.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_bash_tool.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_batch_api.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_branch_farm.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_bridge.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_bridge_smoke.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_browser_tool_smoke.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_browser_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_btw_queue.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_budget_tracker.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_chrome_extension.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_ci_runner.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_code_index.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_commands.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_compression.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_confidence.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_config.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_continuous_memory.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_convention_drift.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_cost_tracker.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_daemon.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_daemon_stability.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_daemon_watchdog.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_db.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_debate.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_debug_loop.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_debug_loop_smoke.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_dep_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_doctor.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_document_index.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_document_reader.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_document_tool.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_document_writer.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_domain_skills.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_eval_harness.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_event_log.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_failure_taxonomy.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_file_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_git_workflow.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_github_actions.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_hermetic_sandbox.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_identity_rbac.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_impact_analysis.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_impact_analyzer.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_impact_graph.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_impact_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_injection_gate.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_leaderboard.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_local_models.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_loop.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_loop_p3.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_mcp_server.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_memory.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_migrations.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_mock_provider.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_model_config.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_orchestrator.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_package.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_permissions.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_phase2_modules.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_phone_ui.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_playwright_tool.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_plugin_sdk.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_protocol_version.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_provenance.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_proxy_server.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_quality_integration.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_reasoning_toggle.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_redaction.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_regression_collector.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_regression_guard_integration.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_regression_runner.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_replay.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_resilience.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_result_cache.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_review_gate_expanded.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_risk_scorer.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_rollback.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_router.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_router_compressor_conventions.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_router_escalation.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_sandbox.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_scoring.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_search_tools.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_self_healing.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_semantic_edit.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_sentry_integration.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_session_checkpoint.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_session_controller.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_session_restore.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_signal_handling.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_swebench_adapter.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_swebench_runner.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_system_prompt.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_team_config.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tool_cache.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tool_orchestrator.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tool_timeout.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tools_registry.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tunnel_qr.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_usage_analytics.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_verification_graph.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_verification_loop.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_voice_loop.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_voice_providers.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_whole_codebase.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_work_director.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/voice/__init__.py +0 -0
- {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/voice/test_audio_foundation.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.6"
|
|
@@ -25,7 +25,7 @@ from typing import Any
|
|
|
25
25
|
from gdmcode._internal.constants import _ALWAYS_DENY_TOOLS, _WRITE_TOOLS
|
|
26
26
|
from gdmcode.exceptions import ToolPermissionError
|
|
27
27
|
from gdmcode.security import AuditLogger
|
|
28
|
-
from gdmcode.tools import REGISTRY, ToolResult
|
|
28
|
+
from gdmcode.tools import REGISTRY, ToolResult, ensure_default_tools_registered
|
|
29
29
|
|
|
30
30
|
__all__ = ["ToolOrchestrator"]
|
|
31
31
|
|
|
@@ -144,15 +144,7 @@ _TOOL_CACHE: _ToolResultCache = _ToolResultCache()
|
|
|
144
144
|
|
|
145
145
|
def _ensure_tools_registered() -> None:
|
|
146
146
|
"""Import all tool modules so they register themselves with REGISTRY."""
|
|
147
|
-
|
|
148
|
-
import gdmcode.tools.read_tools # noqa: F401
|
|
149
|
-
import gdmcode.tools.search_tools # noqa: F401
|
|
150
|
-
import gdmcode.tools.shell_tools # noqa: F401
|
|
151
|
-
import gdmcode.tools.ask_user_tool # noqa: F401
|
|
152
|
-
import gdmcode.tools.write_tools # noqa: F401
|
|
153
|
-
import gdmcode.tools.dep_tools # noqa: F401
|
|
154
|
-
import gdmcode.tools.impact_tools # noqa: F401
|
|
155
|
-
import gdmcode.tools.browser_tool # noqa: F401
|
|
147
|
+
ensure_default_tools_registered()
|
|
156
148
|
|
|
157
149
|
|
|
158
150
|
_ensure_tools_registered()
|
|
@@ -399,4 +391,4 @@ class ToolOrchestrator:
|
|
|
399
391
|
"""Dispatch a registered plugin tool by its namespaced tool_name."""
|
|
400
392
|
if tool_name not in self._plugin_channels:
|
|
401
393
|
raise KeyError(f"Unknown plugin tool: {tool_name}")
|
|
402
|
-
return self._dispatch_plugin(tool_name, args)
|
|
394
|
+
return self._dispatch_plugin(tool_name, args)
|
|
@@ -287,7 +287,8 @@ def cmd_health(
|
|
|
287
287
|
GdmDatabase = _GdmDatabase
|
|
288
288
|
from gdmcode.models.definitions import PROVIDER_BASE_URLS, ModelTier, get_model
|
|
289
289
|
if REGISTRY is None:
|
|
290
|
-
from gdmcode.tools import REGISTRY as _REGISTRY
|
|
290
|
+
from gdmcode.tools import REGISTRY as _REGISTRY, ensure_default_tools_registered
|
|
291
|
+
ensure_default_tools_registered()
|
|
291
292
|
REGISTRY = _REGISTRY
|
|
292
293
|
|
|
293
294
|
@dataclass
|
|
@@ -347,13 +348,61 @@ def cmd_health(
|
|
|
347
348
|
except Exception as exc: # noqa: BLE001
|
|
348
349
|
return CheckResult(f"api ({cfg.provider})", "fail", detail=str(exc)[:60])
|
|
349
350
|
|
|
351
|
+
def _check_completion() -> CheckResult:
|
|
352
|
+
t0 = time.monotonic()
|
|
353
|
+
try:
|
|
354
|
+
from gdmcode.models.client import GdmClient
|
|
355
|
+
|
|
356
|
+
model_def = get_model(ModelTier.CODER, cfg.provider)
|
|
357
|
+
proxy_active = bool(
|
|
358
|
+
getattr(cfg, "proxy_enabled", False)
|
|
359
|
+
and getattr(cfg, "proxy_url", "")
|
|
360
|
+
and getattr(cfg, "proxy_token", "")
|
|
361
|
+
)
|
|
362
|
+
client = (
|
|
363
|
+
GdmClient.for_proxy(getattr(cfg, "proxy_url"), getattr(cfg, "proxy_token"))
|
|
364
|
+
if proxy_active
|
|
365
|
+
else GdmClient(cfg)
|
|
366
|
+
)
|
|
367
|
+
text = client.complete_text(
|
|
368
|
+
"Reply with exactly: OK",
|
|
369
|
+
model=model_def.id,
|
|
370
|
+
max_tokens=8,
|
|
371
|
+
system="You are a health check. Reply only with OK.",
|
|
372
|
+
)
|
|
373
|
+
status = "ok" if text.strip() else "fail"
|
|
374
|
+
detail = f"{model_def.id}: {text.strip()[:30] or 'empty response'}"
|
|
375
|
+
return CheckResult("llm completion", status, latency_ms=int((time.monotonic() - t0) * 1000), detail=detail, priority="P0")
|
|
376
|
+
except Exception as exc: # noqa: BLE001
|
|
377
|
+
return CheckResult("llm completion", "fail", latency_ms=int((time.monotonic() - t0) * 1000), detail=str(exc)[:120], priority="P0")
|
|
378
|
+
|
|
350
379
|
def _check_tools() -> CheckResult:
|
|
351
380
|
t0 = time.monotonic()
|
|
352
381
|
try:
|
|
353
382
|
tools = REGISTRY.all_tools()
|
|
354
383
|
count = len(tools)
|
|
355
|
-
|
|
356
|
-
|
|
384
|
+
if count == 0:
|
|
385
|
+
return CheckResult("tools", "fail", latency_ms=int((time.monotonic() - t0) * 1000), detail="0 tools registered")
|
|
386
|
+
probe_name = "powershell" if sys.platform == "win32" else "bash"
|
|
387
|
+
probe_args = (
|
|
388
|
+
{"command": "$PWD.Path", "timeout": 5}
|
|
389
|
+
if probe_name == "powershell"
|
|
390
|
+
else {"command": "pwd", "timeout": 5, "read_only": True}
|
|
391
|
+
)
|
|
392
|
+
probe = REGISTRY.call(probe_name, probe_args)
|
|
393
|
+
if not probe.ok:
|
|
394
|
+
return CheckResult(
|
|
395
|
+
"tools",
|
|
396
|
+
"fail",
|
|
397
|
+
latency_ms=int((time.monotonic() - t0) * 1000),
|
|
398
|
+
detail=f"{count} registered; {probe_name} probe failed: {probe.error}",
|
|
399
|
+
)
|
|
400
|
+
return CheckResult(
|
|
401
|
+
"tools",
|
|
402
|
+
"ok",
|
|
403
|
+
latency_ms=int((time.monotonic() - t0) * 1000),
|
|
404
|
+
detail=f"{count} registered; {probe_name} probe ok",
|
|
405
|
+
)
|
|
357
406
|
except Exception as exc: # noqa: BLE001
|
|
358
407
|
return CheckResult("tools", "fail", detail=str(exc))
|
|
359
408
|
|
|
@@ -380,10 +429,11 @@ def cmd_health(
|
|
|
380
429
|
return results
|
|
381
430
|
|
|
382
431
|
# Run scalar checks in parallel; sysdeps is fast so fine in its own slot
|
|
383
|
-
with ThreadPoolExecutor(max_workers=
|
|
432
|
+
with ThreadPoolExecutor(max_workers=7) as executor:
|
|
384
433
|
f_db = executor.submit(_check_db)
|
|
385
434
|
f_daemon = executor.submit(_check_daemon)
|
|
386
435
|
f_api = executor.submit(_check_api)
|
|
436
|
+
f_completion = executor.submit(_check_completion)
|
|
387
437
|
f_tools = executor.submit(_check_tools)
|
|
388
438
|
f_budget = executor.submit(_check_budget)
|
|
389
439
|
f_sys = executor.submit(_check_sysdeps)
|
|
@@ -392,13 +442,15 @@ def cmd_health(
|
|
|
392
442
|
f_db.result(),
|
|
393
443
|
f_daemon.result(),
|
|
394
444
|
f_api.result(),
|
|
445
|
+
f_completion.result(),
|
|
395
446
|
f_tools.result(),
|
|
396
447
|
f_budget.result(),
|
|
397
448
|
*f_sys.result(),
|
|
398
449
|
]
|
|
399
450
|
|
|
400
451
|
overall_fail = any(
|
|
401
|
-
r.status
|
|
452
|
+
r.status == "fail" and r.priority in ("P0", "P1")
|
|
453
|
+
or r.status == "missing" and r.priority == "P0"
|
|
402
454
|
for r in all_checks
|
|
403
455
|
)
|
|
404
456
|
|
|
@@ -1179,12 +1231,17 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
|
|
|
1179
1231
|
"""
|
|
1180
1232
|
from rich.status import Status
|
|
1181
1233
|
from gdmcode.agent.loop import EventType
|
|
1234
|
+
from gdmcode.repl import _format_llm_error, _is_connection_probe
|
|
1182
1235
|
|
|
1183
1236
|
if not _has_model_connection(cfg):
|
|
1184
1237
|
console.print("[red]No model connection configured.[/red]")
|
|
1185
1238
|
console.print("Run [bold]gdm login grok[/bold], [bold]gdm login gemini[/bold], or start [bold]gdm[/bold] and use [bold]/proxy token[/bold] then [bold]/proxy on[/bold].")
|
|
1186
1239
|
raise typer.Exit(1)
|
|
1187
1240
|
|
|
1241
|
+
if _is_connection_probe(prompt):
|
|
1242
|
+
_run_connection_probe_once(cfg, prompt, model_override=model_override)
|
|
1243
|
+
return
|
|
1244
|
+
|
|
1188
1245
|
try:
|
|
1189
1246
|
loop, cost_tracker, _db = _setup_agent(cfg, yes=yes, model_override=model_override)
|
|
1190
1247
|
except Exception as exc: # noqa: BLE001
|
|
@@ -1248,10 +1305,26 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
|
|
|
1248
1305
|
console.print(event.content)
|
|
1249
1306
|
elif event.type == EventType.ERROR:
|
|
1250
1307
|
status.stop()
|
|
1251
|
-
|
|
1308
|
+
content = str(event.content or "")
|
|
1309
|
+
if content.lower().startswith("max turns"):
|
|
1310
|
+
console.print(f"[yellow]Agent stopped before completion:[/yellow] {content}")
|
|
1311
|
+
console.print("[dim]Increase --max-turns or simplify the prompt.[/dim]")
|
|
1312
|
+
else:
|
|
1313
|
+
console.print(_format_llm_error(event.content))
|
|
1252
1314
|
had_error = True
|
|
1253
1315
|
elif event.type == EventType.TOOL_CALL:
|
|
1316
|
+
status.stop()
|
|
1254
1317
|
console.print(f"[yellow] [tool] {event.tool_name}[/yellow]")
|
|
1318
|
+
elif event.type == EventType.TOOL_RESULT:
|
|
1319
|
+
status.stop()
|
|
1320
|
+
result = event.result
|
|
1321
|
+
if result is not None and getattr(result, "error", None):
|
|
1322
|
+
console.print(f"[red] [tool error] {event.tool_name}:[/red] {getattr(result, 'error')}")
|
|
1323
|
+
had_error = True
|
|
1324
|
+
elif result is not None:
|
|
1325
|
+
output = (getattr(result, "output", "") or "").strip()
|
|
1326
|
+
if output:
|
|
1327
|
+
console.print(f"[dim]{output[:2000]}[/dim]")
|
|
1255
1328
|
elif event.type == EventType.DONE:
|
|
1256
1329
|
status.stop()
|
|
1257
1330
|
except Exception as exc: # noqa: BLE001
|
|
@@ -1267,6 +1340,45 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
|
|
|
1267
1340
|
raise typer.Exit(1)
|
|
1268
1341
|
|
|
1269
1342
|
|
|
1343
|
+
def _run_connection_probe_once(cfg: object, prompt: str, *, model_override: str | None) -> None:
|
|
1344
|
+
"""Run a one-shot no-tools LLM connection probe."""
|
|
1345
|
+
from rich.status import Status
|
|
1346
|
+
from gdmcode.models.client import GdmClient
|
|
1347
|
+
from gdmcode.models.definitions import ModelTier, get_model
|
|
1348
|
+
from gdmcode.repl import _format_llm_error
|
|
1349
|
+
|
|
1350
|
+
status = Status("[cyan]Checking LLM connection...[/cyan]", console=console, spinner="dots")
|
|
1351
|
+
status.start()
|
|
1352
|
+
try:
|
|
1353
|
+
tier = model_override or ModelTier.CODER
|
|
1354
|
+
model_def = get_model(tier, cfg.provider) # type: ignore[union-attr]
|
|
1355
|
+
proxy_active = bool(
|
|
1356
|
+
getattr(cfg, "proxy_enabled", False)
|
|
1357
|
+
and getattr(cfg, "proxy_url", "")
|
|
1358
|
+
and getattr(cfg, "proxy_token", "")
|
|
1359
|
+
)
|
|
1360
|
+
client = (
|
|
1361
|
+
GdmClient.for_proxy(getattr(cfg, "proxy_url"), getattr(cfg, "proxy_token"))
|
|
1362
|
+
if proxy_active
|
|
1363
|
+
else GdmClient(cfg) # type: ignore[arg-type]
|
|
1364
|
+
)
|
|
1365
|
+
response = client.complete_text(
|
|
1366
|
+
prompt,
|
|
1367
|
+
model=model_def.id,
|
|
1368
|
+
max_tokens=80,
|
|
1369
|
+
system=(
|
|
1370
|
+
"You are gdm code's CLI connection check. Reply in one short, friendly "
|
|
1371
|
+
"sentence. Confirm the LLM is connected. Do not inspect files or call tools."
|
|
1372
|
+
),
|
|
1373
|
+
).strip()
|
|
1374
|
+
status.stop()
|
|
1375
|
+
console.print(response or "[green]LLM connected.[/green]")
|
|
1376
|
+
except Exception as exc: # noqa: BLE001
|
|
1377
|
+
status.stop()
|
|
1378
|
+
console.print(_format_llm_error(exc))
|
|
1379
|
+
raise typer.Exit(1) from exc
|
|
1380
|
+
|
|
1381
|
+
|
|
1270
1382
|
# ---------------------------------------------------------------------------
|
|
1271
1383
|
# Helpers
|
|
1272
1384
|
# ---------------------------------------------------------------------------
|
|
@@ -51,7 +51,7 @@ _INTERNAL_ERROR = -32603
|
|
|
51
51
|
class MCPServer:
|
|
52
52
|
"""Minimal MCP server with stdio transport."""
|
|
53
53
|
|
|
54
|
-
def __init__(self, name: str = "gdmcode", version: str = "0.1.
|
|
54
|
+
def __init__(self, name: str = "gdmcode", version: str = "0.1.6") -> None:
|
|
55
55
|
self._name = name
|
|
56
56
|
self._version = version
|
|
57
57
|
self._tools: dict[str, MCPTool] = {}
|
|
@@ -6,6 +6,7 @@ pyproject.toml's `gdm = "gdmcode.main:main"` script works.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
9
|
+
import sys
|
|
9
10
|
|
|
10
11
|
from gdmcode.cli import app
|
|
11
12
|
|
|
@@ -20,8 +21,21 @@ def _get_version() -> str:
|
|
|
20
21
|
|
|
21
22
|
def main() -> None:
|
|
22
23
|
"""Entry point for the `gdm` CLI command."""
|
|
24
|
+
_configure_stdio()
|
|
23
25
|
app()
|
|
24
26
|
|
|
25
27
|
|
|
28
|
+
def _configure_stdio() -> None:
|
|
29
|
+
"""Avoid Windows cp1252 crashes when Rich output is redirected."""
|
|
30
|
+
for stream_name in ("stdout", "stderr"):
|
|
31
|
+
stream = getattr(sys, stream_name, None)
|
|
32
|
+
if stream is None or not hasattr(stream, "reconfigure"):
|
|
33
|
+
continue
|
|
34
|
+
try:
|
|
35
|
+
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
36
|
+
except (OSError, ValueError):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
26
40
|
if __name__ == "__main__":
|
|
27
41
|
main()
|
|
@@ -201,14 +201,14 @@ GEMINI_SCOUT = ModelDef(
|
|
|
201
201
|
)
|
|
202
202
|
|
|
203
203
|
GEMINI_CODER = ModelDef(
|
|
204
|
-
id="gemini-2.5-flash
|
|
204
|
+
id="gemini-2.5-flash",
|
|
205
205
|
tier=ModelTier.CODER,
|
|
206
206
|
provider=Provider.GEMINI,
|
|
207
207
|
input_per_m=0.075,
|
|
208
208
|
output_per_m=0.30,
|
|
209
209
|
cached_per_m=0.0,
|
|
210
210
|
context_window=1_000_000,
|
|
211
|
-
notes="
|
|
211
|
+
notes="Cheap bulk: file indexing, convention extraction, boilerplate edits.",
|
|
212
212
|
)
|
|
213
213
|
|
|
214
214
|
GEMINI_THINKER = ModelDef(
|
|
@@ -456,4 +456,3 @@ def get_capability(tier: str, provider: str, capability: str) -> bool:
|
|
|
456
456
|
return bool(getattr(m, capability, False))
|
|
457
457
|
except KeyError:
|
|
458
458
|
return False
|
|
459
|
-
|
|
@@ -162,6 +162,35 @@ def _format_llm_error(error: object) -> str:
|
|
|
162
162
|
return f"[red]{title}{code}.[/red]\n[dim]{escape(raw[:800])}[/dim]\n[yellow]{hint}[/yellow]"
|
|
163
163
|
|
|
164
164
|
|
|
165
|
+
def _is_connection_probe(text: str) -> bool:
|
|
166
|
+
"""Return True for tiny greetings/checks that should not enter tool mode."""
|
|
167
|
+
import re
|
|
168
|
+
|
|
169
|
+
normalized = re.sub(r"[^a-z0-9\s']", " ", text.lower()).strip()
|
|
170
|
+
normalized = re.sub(r"\s+", " ", normalized)
|
|
171
|
+
if not normalized:
|
|
172
|
+
return False
|
|
173
|
+
direct = {
|
|
174
|
+
"hey",
|
|
175
|
+
"hi",
|
|
176
|
+
"hello",
|
|
177
|
+
"yo",
|
|
178
|
+
"ping",
|
|
179
|
+
"test",
|
|
180
|
+
"hello there",
|
|
181
|
+
"hey there",
|
|
182
|
+
"hi there",
|
|
183
|
+
"are you there",
|
|
184
|
+
"you there",
|
|
185
|
+
}
|
|
186
|
+
if normalized in direct:
|
|
187
|
+
return True
|
|
188
|
+
words = normalized.split()
|
|
189
|
+
return len(words) <= 4 and words[0] in {"hey", "hi", "hello"} and all(
|
|
190
|
+
word in {"hey", "hi", "hello", "there", "gdm", "agent", "please"} for word in words
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
165
194
|
def _render_event(event: object, status: Status, console: Console) -> bool:
|
|
166
195
|
"""Render one AgentEvent to the terminal while the spinner is live."""
|
|
167
196
|
from gdmcode.agent.loop import EventType # lazy
|
|
@@ -173,6 +202,7 @@ def _render_event(event: object, status: Status, console: Console) -> bool:
|
|
|
173
202
|
label = f"[dim cyan]Thinking... {snippet}[/dim cyan]"
|
|
174
203
|
status.update(label)
|
|
175
204
|
case EventType.TOOL_CALL:
|
|
205
|
+
status.stop()
|
|
176
206
|
console.print(f"[yellow][tool] {ev.tool_name}({_fmt_args(ev.args)})[/yellow]") # type: ignore[union-attr]
|
|
177
207
|
case EventType.TOOL_RESULT:
|
|
178
208
|
result_str = str(ev.result or "")[:80] # type: ignore[union-attr]
|
|
@@ -384,6 +414,51 @@ def start_repl(cfg: "GdmConfig", db: "GdmDatabase", *, yes: bool = False, model_
|
|
|
384
414
|
console.print("[yellow]Agent loop not available in this installation.[/yellow]")
|
|
385
415
|
return False
|
|
386
416
|
|
|
417
|
+
def _run_connection_probe(text: str) -> None:
|
|
418
|
+
"""Send a tiny no-tools request to confirm the active LLM route."""
|
|
419
|
+
nonlocal cfg
|
|
420
|
+
if not _has_model_route():
|
|
421
|
+
try:
|
|
422
|
+
cfg = _reload_cfg(require_credentials=True) # type: ignore[assignment]
|
|
423
|
+
dispatcher._cfg = cfg # type: ignore[attr-defined]
|
|
424
|
+
dispatcher._provider = getattr(cfg, "provider", dispatcher._provider) # type: ignore[attr-defined]
|
|
425
|
+
except Exception as exc: # noqa: BLE001
|
|
426
|
+
_print_onboarding(console, error=str(exc))
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
status = Status("[cyan]Checking LLM connection...[/cyan]", console=console, spinner="dots")
|
|
430
|
+
status.start()
|
|
431
|
+
try:
|
|
432
|
+
from gdmcode.models.client import GdmClient
|
|
433
|
+
from gdmcode.models.definitions import ModelTier, get_model
|
|
434
|
+
|
|
435
|
+
tier = model_override or ModelTier.CODER
|
|
436
|
+
model_def = get_model(tier, cfg.provider)
|
|
437
|
+
proxy_active = bool(
|
|
438
|
+
getattr(cfg, "proxy_enabled", False)
|
|
439
|
+
and getattr(cfg, "proxy_url", "")
|
|
440
|
+
and getattr(cfg, "proxy_token", "")
|
|
441
|
+
)
|
|
442
|
+
client = (
|
|
443
|
+
GdmClient.for_proxy(getattr(cfg, "proxy_url"), getattr(cfg, "proxy_token"))
|
|
444
|
+
if proxy_active
|
|
445
|
+
else GdmClient(cfg)
|
|
446
|
+
)
|
|
447
|
+
response = client.complete_text(
|
|
448
|
+
text,
|
|
449
|
+
model=model_def.id,
|
|
450
|
+
max_tokens=80,
|
|
451
|
+
system=(
|
|
452
|
+
"You are gdm code's CLI connection check. Reply in one short, friendly "
|
|
453
|
+
"sentence. Confirm the LLM is connected. Do not inspect files or call tools."
|
|
454
|
+
),
|
|
455
|
+
).strip()
|
|
456
|
+
status.stop()
|
|
457
|
+
console.print(response or "[green]LLM connected.[/green]")
|
|
458
|
+
except Exception as exc: # noqa: BLE001
|
|
459
|
+
status.stop()
|
|
460
|
+
console.print(_format_llm_error(exc))
|
|
461
|
+
|
|
387
462
|
if not getattr(cfg, "gdm_quiet", False):
|
|
388
463
|
if _has_model_route():
|
|
389
464
|
console.print("[dim]Agent will initialize on first prompt.[/dim]")
|
|
@@ -587,6 +662,10 @@ def start_repl(cfg: "GdmConfig", db: "GdmDatabase", *, yes: bool = False, model_
|
|
|
587
662
|
console.print("[yellow]Restore not supported by current agent.[/yellow]")
|
|
588
663
|
continue
|
|
589
664
|
|
|
665
|
+
if _is_connection_probe(text):
|
|
666
|
+
_run_connection_probe(text)
|
|
667
|
+
continue
|
|
668
|
+
|
|
590
669
|
pending = _show_pending_btw(db, session_id, console)
|
|
591
670
|
full_message = text
|
|
592
671
|
if pending:
|
|
@@ -20,6 +20,7 @@ __all__ = [
|
|
|
20
20
|
"ToolResult",
|
|
21
21
|
"ToolRegistry",
|
|
22
22
|
"REGISTRY",
|
|
23
|
+
"ensure_default_tools_registered",
|
|
23
24
|
"BrowserTools",
|
|
24
25
|
"BrowserToolResult",
|
|
25
26
|
]
|
|
@@ -155,6 +156,20 @@ class ToolRegistry:
|
|
|
155
156
|
# Module-level singleton — all concrete tool modules import and extend this.
|
|
156
157
|
REGISTRY: ToolRegistry = ToolRegistry()
|
|
157
158
|
|
|
159
|
+
|
|
160
|
+
def ensure_default_tools_registered() -> None:
|
|
161
|
+
"""Import built-in tool modules so they register with REGISTRY."""
|
|
162
|
+
import gdmcode.tools.bash_tool # noqa: F401
|
|
163
|
+
import gdmcode.tools.read_tools # noqa: F401
|
|
164
|
+
import gdmcode.tools.search_tools # noqa: F401
|
|
165
|
+
import gdmcode.tools.shell_tools # noqa: F401
|
|
166
|
+
import gdmcode.tools.ask_user_tool # noqa: F401
|
|
167
|
+
import gdmcode.tools.write_tools # noqa: F401
|
|
168
|
+
import gdmcode.tools.dep_tools # noqa: F401
|
|
169
|
+
import gdmcode.tools.impact_tools # noqa: F401
|
|
170
|
+
import gdmcode.tools.browser_tool # noqa: F401
|
|
171
|
+
|
|
172
|
+
|
|
158
173
|
# Re-export browser tools so callers can do `from gdmcode.tools import BrowserTools`.
|
|
159
174
|
# Import after REGISTRY is defined to avoid circular imports.
|
|
160
175
|
from gdmcode.tools.browser_tools import BrowserToolResult, BrowserTools, BrowserToolsSync # noqa: E402, F401
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Smoke tests for gdm CLI entrypoints."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from typer.testing import CliRunner
|
|
9
|
+
|
|
10
|
+
from gdmcode.cli import app
|
|
11
|
+
|
|
12
|
+
runner = CliRunner()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestCliSmoke:
|
|
16
|
+
def test_help_exits_zero(self) -> None:
|
|
17
|
+
result = runner.invoke(app, ["--help"])
|
|
18
|
+
assert result.exit_code == 0
|
|
19
|
+
|
|
20
|
+
def test_version_exits_zero(self) -> None:
|
|
21
|
+
result = runner.invoke(app, ["version"])
|
|
22
|
+
assert result.exit_code == 0
|
|
23
|
+
|
|
24
|
+
def test_doctor_exits_cleanly(self) -> None:
|
|
25
|
+
"""doctor must exit 0 even if optional deps are missing."""
|
|
26
|
+
result = runner.invoke(app, ["doctor"])
|
|
27
|
+
assert result.exit_code == 0
|
|
28
|
+
assert "Core" in result.output
|
|
29
|
+
|
|
30
|
+
def test_doctor_shows_python_version(self) -> None:
|
|
31
|
+
result = runner.invoke(app, ["doctor"])
|
|
32
|
+
assert "Python" in result.output
|
|
33
|
+
|
|
34
|
+
def test_code_help_exits_zero(self) -> None:
|
|
35
|
+
result = runner.invoke(app, ["code", "--help"])
|
|
36
|
+
assert result.exit_code == 0
|
|
37
|
+
|
|
38
|
+
def test_login_help_exits_zero(self) -> None:
|
|
39
|
+
result = runner.invoke(app, ["login", "--help"])
|
|
40
|
+
assert result.exit_code == 0
|
|
41
|
+
|
|
42
|
+
def test_logout_help_exits_zero(self) -> None:
|
|
43
|
+
result = runner.invoke(app, ["logout", "--help"])
|
|
44
|
+
assert result.exit_code == 0
|
|
45
|
+
|
|
46
|
+
def test_daemon_help_exits_zero(self) -> None:
|
|
47
|
+
result = runner.invoke(app, ["daemon", "--help"])
|
|
48
|
+
assert result.exit_code == 0
|
|
49
|
+
|
|
50
|
+
def test_doctor_no_traceback_on_missing_deps(self) -> None:
|
|
51
|
+
"""Graceful output even when optional packages absent."""
|
|
52
|
+
result = runner.invoke(app, ["doctor"])
|
|
53
|
+
assert "Traceback" not in result.output
|
|
54
|
+
assert "Traceback" not in (
|
|
55
|
+
result.exception.__class__.__name__ if result.exception else ""
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def test_version_shows_version_string(self) -> None:
|
|
59
|
+
result = runner.invoke(app, ["version"])
|
|
60
|
+
assert "gdm" in result.output.lower() or "0." in result.output
|
|
61
|
+
|
|
62
|
+
def test_health_bootstraps_and_executes_tools(self, tmp_path, monkeypatch) -> None:
|
|
63
|
+
import gdmcode.cli as cli
|
|
64
|
+
|
|
65
|
+
class FakeDb:
|
|
66
|
+
def __init__(self, project_root):
|
|
67
|
+
self.project_root = project_root
|
|
68
|
+
|
|
69
|
+
def execute(self, *_args, **_kwargs):
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
class FakeDaemon:
|
|
73
|
+
is_running = True
|
|
74
|
+
|
|
75
|
+
def __init__(self, db):
|
|
76
|
+
self.db = db
|
|
77
|
+
|
|
78
|
+
def pending_count(self):
|
|
79
|
+
return 0
|
|
80
|
+
|
|
81
|
+
monkeypatch.setattr(cli, "GdmDatabase", FakeDb)
|
|
82
|
+
monkeypatch.setattr(cli, "BackgroundDaemon", FakeDaemon)
|
|
83
|
+
monkeypatch.setattr(cli, "REGISTRY", None)
|
|
84
|
+
monkeypatch.setattr(
|
|
85
|
+
cli,
|
|
86
|
+
"load_config",
|
|
87
|
+
lambda: SimpleNamespace(provider="gemini", api_key="key", project_root=tmp_path),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
response = SimpleNamespace(status_code=200)
|
|
91
|
+
with patch("gdmcode.cli.httpx.get", return_value=response):
|
|
92
|
+
with patch("gdmcode.models.client.GdmClient.complete_text", return_value="OK"):
|
|
93
|
+
result = runner.invoke(app, ["health"])
|
|
94
|
+
|
|
95
|
+
assert "tools" in result.output
|
|
96
|
+
assert "registered" in result.output
|
|
97
|
+
assert "probe ok" in result.output
|
|
98
|
+
assert "llm completion" in result.output
|
|
99
|
+
|
|
100
|
+
def test_one_shot_greeting_uses_connection_probe(self, tmp_path, monkeypatch) -> None:
|
|
101
|
+
import gdmcode.cli as cli
|
|
102
|
+
|
|
103
|
+
cfg = SimpleNamespace(provider="gemini", api_key="key", project_root=tmp_path)
|
|
104
|
+
monkeypatch.setattr(cli, "load_config", lambda require_credentials=False: cfg)
|
|
105
|
+
monkeypatch.setattr(cli, "_print_header", lambda _cfg: None)
|
|
106
|
+
monkeypatch.setattr(cli, "_has_model_connection", lambda _cfg: True)
|
|
107
|
+
probe = MagicMock()
|
|
108
|
+
monkeypatch.setattr(cli, "_run_connection_probe_once", probe)
|
|
109
|
+
|
|
110
|
+
result = runner.invoke(app, ["code", "--prompt", "hey"])
|
|
111
|
+
|
|
112
|
+
assert result.exit_code == 0
|
|
113
|
+
probe.assert_called_once()
|
|
114
|
+
assert probe.call_args.args[1] == "hey"
|
|
115
|
+
assert probe.call_args.kwargs == {"model_override": None}
|
|
@@ -22,6 +22,9 @@ def _make_cfg(**kwargs):
|
|
|
22
22
|
cfg.provider = kwargs.get("provider", "grok")
|
|
23
23
|
cfg.api_key = kwargs.get("api_key", "sk-test")
|
|
24
24
|
cfg.project_root = Path("/tmp/gdm-test")
|
|
25
|
+
cfg.proxy_enabled = kwargs.get("proxy_enabled", False)
|
|
26
|
+
cfg.proxy_url = kwargs.get("proxy_url", "")
|
|
27
|
+
cfg.proxy_token = kwargs.get("proxy_token", None)
|
|
25
28
|
return cfg
|
|
26
29
|
|
|
27
30
|
|
|
@@ -63,6 +66,7 @@ def _run_health(
|
|
|
63
66
|
daemon = _make_daemon(running=daemon_running)
|
|
64
67
|
mock_registry = MagicMock()
|
|
65
68
|
mock_registry.all_tools.return_value = [MagicMock()] * tools_count
|
|
69
|
+
mock_registry.call.return_value = MagicMock(ok=True, output="ok", error=None)
|
|
66
70
|
|
|
67
71
|
mock_response = MagicMock()
|
|
68
72
|
mock_response.status_code = api_status
|
|
@@ -75,6 +79,7 @@ def _run_health(
|
|
|
75
79
|
patch("gdmcode.cli.shutil.which", side_effect=lambda cmd: which_map.get(cmd)),
|
|
76
80
|
patch("gdmcode.cli.httpx.get",
|
|
77
81
|
side_effect=api_exc if api_exc else lambda *a, **kw: mock_response),
|
|
82
|
+
patch("gdmcode.models.client.GdmClient.complete_text", return_value="OK"),
|
|
78
83
|
):
|
|
79
84
|
runner = CliRunner()
|
|
80
85
|
result = runner.invoke(app, ["health", *args])
|
|
@@ -138,6 +143,7 @@ def test_api_probe_sends_auth_header():
|
|
|
138
143
|
daemon = _make_daemon()
|
|
139
144
|
mock_registry = MagicMock()
|
|
140
145
|
mock_registry.all_tools.return_value = [MagicMock()]
|
|
146
|
+
mock_registry.call.return_value = MagicMock(ok=True, output="ok", error=None)
|
|
141
147
|
|
|
142
148
|
with (
|
|
143
149
|
patch("gdmcode.cli.load_config", return_value=cfg),
|
|
@@ -146,6 +152,7 @@ def test_api_probe_sends_auth_header():
|
|
|
146
152
|
patch("gdmcode.cli.REGISTRY", mock_registry),
|
|
147
153
|
patch("gdmcode.cli.shutil.which", return_value="/usr/bin/git"),
|
|
148
154
|
patch("gdmcode.cli.httpx.get", side_effect=fake_get),
|
|
155
|
+
patch("gdmcode.models.client.GdmClient.complete_text", return_value="OK"),
|
|
149
156
|
):
|
|
150
157
|
runner = CliRunner()
|
|
151
158
|
runner.invoke(app, ["health"])
|
|
@@ -280,6 +287,7 @@ def test_parallel_execution_completes():
|
|
|
280
287
|
daemon = _make_daemon()
|
|
281
288
|
mock_registry = MagicMock()
|
|
282
289
|
mock_registry.all_tools.return_value = [MagicMock()]
|
|
290
|
+
mock_registry.call.return_value = MagicMock(ok=True, output="ok", error=None)
|
|
283
291
|
|
|
284
292
|
def slow_api(*a, **kw):
|
|
285
293
|
import time as _t
|
|
@@ -295,6 +303,7 @@ def test_parallel_execution_completes():
|
|
|
295
303
|
patch("gdmcode.cli.REGISTRY", mock_registry),
|
|
296
304
|
patch("gdmcode.cli.shutil.which", return_value="/bin/git"),
|
|
297
305
|
patch("gdmcode.cli.httpx.get", side_effect=slow_api),
|
|
306
|
+
patch("gdmcode.models.client.GdmClient.complete_text", return_value="OK"),
|
|
298
307
|
):
|
|
299
308
|
runner = CliRunner()
|
|
300
309
|
t0 = time.monotonic()
|
|
@@ -39,6 +39,13 @@ class TestReplHelpers:
|
|
|
39
39
|
from gdmcode.repl import _fmt_args
|
|
40
40
|
assert _fmt_args({}) == ""
|
|
41
41
|
|
|
42
|
+
def test_connection_probe_detection(self) -> None:
|
|
43
|
+
from gdmcode.repl import _is_connection_probe
|
|
44
|
+
|
|
45
|
+
assert _is_connection_probe("hey")
|
|
46
|
+
assert _is_connection_probe("hello there")
|
|
47
|
+
assert not _is_connection_probe("fix the failing tests")
|
|
48
|
+
|
|
42
49
|
|
|
43
50
|
class TestReplStartRepl:
|
|
44
51
|
"""Test start_repl runs and exits cleanly when given /exit input."""
|
|
@@ -157,6 +164,24 @@ class TestReplStartRepl:
|
|
|
157
164
|
assert "/login grok" in output
|
|
158
165
|
assert "/proxy token" in output
|
|
159
166
|
|
|
167
|
+
def test_plain_hey_with_credentials_uses_no_tool_probe(self, tmp_path, capsys) -> None:
|
|
168
|
+
from gdmcode.repl import start_repl
|
|
169
|
+
|
|
170
|
+
cfg, db = self._make_mocks(tmp_path)
|
|
171
|
+
cfg.api_key = "test-key"
|
|
172
|
+
|
|
173
|
+
with patch("gdmcode.repl._build_input_fn") as mock_build:
|
|
174
|
+
mock_build.return_value = MagicMock(side_effect=["hey", EOFError])
|
|
175
|
+
with patch("gdmcode.models.client.GdmClient.complete_text", return_value="Hey, LLM connected."):
|
|
176
|
+
with patch("gdmcode.repl._run_agent_turn") as run_agent:
|
|
177
|
+
with patch("gdmcode.cost_tracker.CostTracker"):
|
|
178
|
+
with patch("gdmcode.repl._ensure_session", return_value="session-123"):
|
|
179
|
+
start_repl(cfg, db)
|
|
180
|
+
|
|
181
|
+
output = capsys.readouterr().out
|
|
182
|
+
assert "LLM connected" in output
|
|
183
|
+
run_agent.assert_not_called()
|
|
184
|
+
|
|
160
185
|
|
|
161
186
|
class TestReplErrorFormatting:
|
|
162
187
|
def test_format_llm_error_rate_limit(self) -> None:
|
|
@@ -193,6 +218,17 @@ class TestReplErrorFormatting:
|
|
|
193
218
|
output = capsys.readouterr().out
|
|
194
219
|
assert "credit" in output.lower() or "quota" in output.lower()
|
|
195
220
|
|
|
221
|
+
def test_render_tool_call_stops_spinner_before_permission_work(self, capsys) -> None:
|
|
222
|
+
from gdmcode.agent.loop import EventType
|
|
223
|
+
from gdmcode.repl import _render_event
|
|
224
|
+
|
|
225
|
+
status = MagicMock()
|
|
226
|
+
console = MagicMock()
|
|
227
|
+
event = SimpleNamespace(type=EventType.TOOL_CALL, tool_name="bash", args={"command": "ls -F"})
|
|
228
|
+
|
|
229
|
+
assert _render_event(event, status, console) is False
|
|
230
|
+
status.stop.assert_called_once()
|
|
231
|
+
|
|
196
232
|
|
|
197
233
|
class TestCommandDispatcher:
|
|
198
234
|
"""Smoke tests for CommandDispatcher (called by start_repl)."""
|