ctrlrelay 0.27.0__tar.gz → 0.28.1__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.
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/CHANGELOG.md +217 -1
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/PKG-INFO +10 -5
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/README.md +9 -4
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/config/orchestrator.yaml.example +31 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/architecture.md +27 -13
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/cli.md +5 -2
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/configuration.md +478 -24
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/feedback-loop.md +35 -9
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/getting-started.md +22 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/index.md +2 -1
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/llms.txt +1 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/operations.md +1 -1
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/registered-mode.md +32 -1
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/pyproject.toml +1 -1
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/cli.py +174 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/__init__.py +14 -0
- ctrlrelay-0.28.1/src/ctrlrelay/core/agent_check.py +1025 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/agent_env.py +108 -1
- ctrlrelay-0.28.1/src/ctrlrelay/core/agent_plugins.py +100 -0
- ctrlrelay-0.28.1/src/ctrlrelay/core/agent_presets.py +179 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/budget.py +30 -1
- ctrlrelay-0.28.1/src/ctrlrelay/core/checkpoint.py +303 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/config.py +263 -3
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/dispatcher.py +797 -74
- ctrlrelay-0.28.1/src/ctrlrelay/core/generic_cli.py +582 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/github.py +12 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/issue_questions.py +59 -10
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/obs.py +32 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/proof.py +4 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/question_expiry.py +76 -13
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/state.py +79 -4
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/worktree.py +240 -20
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/pipelines/dev.py +121 -21
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/pipelines/secops.py +113 -30
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/pipelines/task.py +92 -19
- ctrlrelay-0.28.1/tests/test_agent_check.py +1362 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_agent_no_keys.py +11 -3
- ctrlrelay-0.28.1/tests/test_agent_plugins.py +514 -0
- ctrlrelay-0.28.1/tests/test_agent_presets.py +493 -0
- ctrlrelay-0.28.1/tests/test_agent_process_group.py +392 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_agent_routing.py +2 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_archived.py +1 -0
- ctrlrelay-0.28.1/tests/test_checkpoint_snippet.py +176 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_config.py +2 -2
- ctrlrelay-0.28.1/tests/test_generic_cli_adapter.py +669 -0
- ctrlrelay-0.28.1/tests/test_generic_cli_usage.py +916 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_issue_questions.py +59 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_opencode_adapter.py +33 -1
- ctrlrelay-0.28.1/tests/test_provider_credentials.py +252 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_question_expiry.py +171 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_question_survives_restart.py +73 -1
- ctrlrelay-0.28.1/tests/test_relay_git_credential.py +218 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_resume_prompt_contract.py +6 -4
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_run_resume.py +29 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_secops_pipeline.py +1 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_state.py +84 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_worktree.py +343 -0
- ctrlrelay-0.27.0/src/ctrlrelay/core/checkpoint.py +0 -155
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.github/dependabot.yml +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.github/workflows/build.yml +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.github/workflows/cla.yml +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.github/workflows/pages.yml +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.github/workflows/publish.yml +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.github/workflows/test.yml +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/.gitignore +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/CODE_OF_CONDUCT.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/CONTRIBUTING.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/LICENSE +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/SECURITY.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/Gemfile +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/_config.yml +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/adding-a-machine.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/bridge.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/control-plane-brain.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/development.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/personalization.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/superpowers/plans/2026-10-30-status-page.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/docs/superpowers/specs/2026-10-30-status-page-design.md +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/bridge/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/bridge/__main__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/bridge/factory.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/bridge/handler.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/bridge/mattermost_handler.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/bridge/node_tag.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/bridge/protocol.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/bridge/server.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/bridge/telegram_handler.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/archived.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/audit.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/binaries.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/blockers.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/claims.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/code_review.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/dispatch_health.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/events.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/github_budget.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/heartbeat.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/intake_hold.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/job_claims.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/network.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/node_check.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/orphans.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/poller.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/pr_verifier.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/pr_watcher.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/publish.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/redact.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/reopens.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/scheduler.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/self_update.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/slots.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/stuck.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/usage.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/core/weekly_summary.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/dashboard/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/dashboard/client.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/gh_protocol.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/install.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/personalization/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/personalization/manager.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/personalization/merge.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/personalization/paths.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/pipelines/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/pipelines/base.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/pipelines/post_merge.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/setup.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/templates/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/templates/global-CLAUDE.md.snippet +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/templates/launchd/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/templates/launchd/bridge.plist.template +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/templates/launchd/poller.plist.template +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/templates/systemd/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/templates/systemd/bridge.service.template +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/templates/systemd/poller.service.template +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/templates/systemd/web.service.template +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/transports/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/transports/base.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/transports/file_mock.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/transports/socket_client.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/drift.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/fleet.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/health.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/links.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/numbers.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/server.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/static/index.html +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/status.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/transcript.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/src/ctrlrelay/web/waiting.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/__init__.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/_poller_fakes.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/_publish_fakes.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/conftest.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/fixtures/gh_search_issues.json +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/fixtures/stream_json_rate_limit.jsonl +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/fixtures/transcript_sample.jsonl +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_agent_backend_wiring.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_agent_sandbox.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_allowed_accounts.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_audit.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_binary_resolution.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_bridge_protocol.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_bridge_server.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_bridge_start_command.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_budget.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_chat_machines.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_checkpoint.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_claim_reconciliation.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_cli_ci_wait.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_cli_dev.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_cli_repos.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_cli_secops.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_cli_start.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_cli_version.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_code_review.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dashboard_client.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dev_integration.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dev_pipeline.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dev_plan_step.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dev_step_switches.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dev_stuck.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dev_work_steps.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dispatch_health.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dispatch_outcome_hook.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_dispatcher.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_docs_site.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_event_emission.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_events.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_fleet.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_gh_protocol.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_github.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_github_budget.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_install.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_intake_hold.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_intake_hold_poller.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_issue_blockers.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_issue_outcome_announcement.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_job_claims.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_mattermost_handler.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_max_parallel.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_network.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_no_change_run.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_node_check.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_node_claims.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_notify_transport.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_obs.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_orphans.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_personalization.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_personalization_merge.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_pipeline_base.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_poller.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_poller_priority_order.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_poller_recheck.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_poller_search.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_post_merge.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_pr_verifier.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_pr_watcher.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_pre_dispatch_retry.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_proof.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_publish_failure_retry.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_question_backoff.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_redact.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_relay_alert_off_lock.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_relay_git_263.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_reopens.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_review_copy.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_review_gate.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_sandbox_visibility.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_scheduler.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_secops_integration.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_secops_operator_prs.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_self_update.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_setup.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_stuck.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_task_pipeline.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_telegram_handler.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_timeout_save.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_transport.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_transport_resolution.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_unreviewed_pr_announcement.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_usage.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_web_drift.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_web_health.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_web_links.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_web_numbers.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_web_peer_links.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_web_server.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_web_status.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_web_transcript.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_web_waiting.py +0 -0
- {ctrlrelay-0.27.0 → ctrlrelay-0.28.1}/tests/test_weekly_summary.py +0 -0
|
@@ -7,6 +7,220 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.28.1] - 2026-11-11
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- The relay's own clone, fetch and `ls-remote` now use gh's login on a node
|
|
15
|
+
where git has no credential for github.com (#398). The first dev run on
|
|
16
|
+
such a node failed before the agent started with `could not read
|
|
17
|
+
Username for 'https://github.com'`. `gh auth git-credential` is appended
|
|
18
|
+
as a helper for `https://github.com` through `GIT_CONFIG_COUNT`, after
|
|
19
|
+
any entries already in the environment, so a unit's `insteadOf` rewrite
|
|
20
|
+
keeps working and an operator's own helper is still asked first. These
|
|
21
|
+
calls also set `GIT_TERMINAL_PROMPT=0`, so a missing credential fails at
|
|
22
|
+
once. Pushes already used gh's login; the agent's environment is
|
|
23
|
+
unchanged.
|
|
24
|
+
- The checkpoint snippets in the dev, task and secops prompts, and in
|
|
25
|
+
`ctrlrelay agent check --resume`, no longer give an agent anything to
|
|
26
|
+
re-quote (#397). The BLOCKED and FAILED snippets (and secops' DONE) were a
|
|
27
|
+
`printf` built from single-quoted pieces joined by backslash
|
|
28
|
+
continuations. Agents sometimes rewrote the quoting or dropped a
|
|
29
|
+
continuation, and the round ended with a malformed or missing checkpoint
|
|
30
|
+
after the work was done. They now write the file with `python3` from a
|
|
31
|
+
quoted heredoc, the shape the DONE snippets already used. The text goes on
|
|
32
|
+
its own lines in a raw string and `json.dump` escapes it, so a question or
|
|
33
|
+
error may hold double quotes, newlines and backslashes; the old `%s`
|
|
34
|
+
pasted those into the JSON unescaped. One function renders the snippet for
|
|
35
|
+
every pipeline and for `agent check`, so the check runs exactly the shell
|
|
36
|
+
a real run uses.
|
|
37
|
+
- A run paused on a question is no longer abandoned half an hour later
|
|
38
|
+
while its issue is still open (#400). The hourly sweep that retires a
|
|
39
|
+
question once everything it cites is closed read `oaf-kernel#118` as
|
|
40
|
+
this repository's `#118`, found an unrelated issue closed days before,
|
|
41
|
+
and retired a live question 33 minutes after it was asked. A reference
|
|
42
|
+
written `owner/repo#N`, or linked, is now looked up in that repository;
|
|
43
|
+
one whose repository cannot be told (`name#N`, `PR#N`) keeps the
|
|
44
|
+
question; and the issue the question was asked on must be closed too.
|
|
45
|
+
Closing that issue now retires its question even when the question
|
|
46
|
+
cites nothing else. The 48-hour
|
|
47
|
+
`question_ttl_seconds` was never the cause.
|
|
48
|
+
- A question asked on an issue that expires unanswered now says on the
|
|
49
|
+
issue that the run has stopped and how to go on, and labels the issue
|
|
50
|
+
`ctrlrelay:unanswered` so it can be found. Adding it creates the label
|
|
51
|
+
in the repository if it is missing; nothing removes it. If the label
|
|
52
|
+
cannot be added, the comment still posts and does not mention it. A question retired
|
|
53
|
+
because its issue was closed is not labelled.
|
|
54
|
+
|
|
55
|
+
## [0.28.0] - 2026-11-11
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- `ctrlrelay agent check <agent_type>` proves one backend writes its
|
|
60
|
+
checkpoint, without starting a dev run (#381). It spawns a single session
|
|
61
|
+
in a throwaway git repository in a temporary directory, with GitHub
|
|
62
|
+
credentials stripped and a prompt whose whole content is the `DONE`
|
|
63
|
+
checkpoint snippet, then reports the exit code, the checkpoint and its
|
|
64
|
+
status, the native session id the adapter captured, the selected identity
|
|
65
|
+
and the elapsed time — one line each, because they fail separately. Exits
|
|
66
|
+
0 only on a `DONE` checkpoint from a session that exited on its own: a
|
|
67
|
+
CLI that exits zero having written nothing is the failure the command
|
|
68
|
+
exists to find, and one that writes `DONE` and then has to be stopped
|
|
69
|
+
would hang every dev run. The throwaway repository is a working
|
|
70
|
+
directory, not a sandbox. `--timeout` defaults to 300 seconds and must
|
|
71
|
+
be at least 1, `--json` prints the same report as one object including on
|
|
72
|
+
the failure paths — with the structured log moved to stderr so stdout is
|
|
73
|
+
exactly that object — and an unknown type exits non-zero naming every type
|
|
74
|
+
enrolled on the node. As an operator tool it may select any enrolled
|
|
75
|
+
backend and does not consult `agent.assignable_types`, which governs what
|
|
76
|
+
an assignment arriving from outside the machine may ask for.
|
|
77
|
+
- `ctrlrelay agent check <agent_type> --resume` proves the other half of the
|
|
78
|
+
contract: that the backend comes back knowing what it was doing (#382). A
|
|
79
|
+
backend that cannot resume fails every run that has to ask a question, and
|
|
80
|
+
the only way to exercise it before this was a real dev run that blocked and
|
|
81
|
+
waited for a chat answer. Round 1 is told a freshly generated 8-character
|
|
82
|
+
token and must write `BLOCKED_NEEDS_INPUT`; round 2 spawns with
|
|
83
|
+
`resume_session_id` set to the native id round 1 reported — the same argv a
|
|
84
|
+
pipeline resume builds — and must write `DONE` whose summary is that token.
|
|
85
|
+
The token is not in the second prompt, so a round that answered from its
|
|
86
|
+
own prompt could not pass, and a decoy fresh session with a different token
|
|
87
|
+
runs between the rounds, so a manifest that resumes the most recent session
|
|
88
|
+
instead of the id it is given fails rather than passing by luck. Round 1 must report both the blocked checkpoint
|
|
89
|
+
and a native session id or the command stops and says which was missing,
|
|
90
|
+
rather than spending a second fresh session that could only report a pass
|
|
91
|
+
for a backend that never resumed. A backend reporting no `resume`
|
|
92
|
+
capability fails with `resume not configured for <type>` without spawning.
|
|
93
|
+
A native session id that changes between rounds is a warning, not a
|
|
94
|
+
failure. Each round gets its own report line, each failure its own, and
|
|
95
|
+
`--json` carries both rounds plus `resume: true`.
|
|
96
|
+
- A plugin identity and capability contract for coding-agent CLIs (#379).
|
|
97
|
+
Claude and OpenCode now expose the same identity metadata, and local
|
|
98
|
+
`agent.plugins` manifests can enroll simple non-interactive CLIs with fixed
|
|
99
|
+
argv, prompt-over-argument or stdin, checkpoint handling, and optional
|
|
100
|
+
native-session mapping. `agent.assignable_types` defines the smaller set a
|
|
101
|
+
future control-plane assignment may select; assignments cannot supply a
|
|
102
|
+
binary or arbitrary arguments. The selected plugin, model, and canonical
|
|
103
|
+
invocation fingerprint are persisted before dispatch and pinned across CI
|
|
104
|
+
fixes, retries, and pending resumes. Generic executables must resolve to an
|
|
105
|
+
absolute path, native session IDs are validated before argv substitution,
|
|
106
|
+
and a pre-#379 native session with unknown identity is refused rather than
|
|
107
|
+
resumed through the current default backend.
|
|
108
|
+
- Repo context is linked under each backend's own instruction filename, so
|
|
109
|
+
Codex and Copilot see `AGENTS.md` or `.github/copilot-instructions.md`
|
|
110
|
+
instead of only Claude's `CLAUDE.md` (#379). A name the repository already
|
|
111
|
+
has is left alone: the repo's own file wins under that name, and the relay
|
|
112
|
+
context goes only under names that are free.
|
|
113
|
+
- Built-in identity pins use the stable launcher path, so Claude's normal
|
|
114
|
+
self-update does not invalidate a pending resume. Generic manifests still
|
|
115
|
+
pin their full invocation mapping (#379).
|
|
116
|
+
- A checked GitHub Copilot CLI manifest in `docs/configuration.md` and the
|
|
117
|
+
example config, with resume through its `sessionId`. It passes
|
|
118
|
+
`--disable-builtin-mcps`, because Copilot's built-in GitHub MCP server
|
|
119
|
+
otherwise gives the agent GitHub access under Copilot's own login,
|
|
120
|
+
bypassing credential stripping (#379).
|
|
121
|
+
|
|
122
|
+
- Copilot and Codex as built-in agent presets (#384, #385). Set
|
|
123
|
+
`repos[].agent_type: copilot` or `codex` (or `agent.type`) with no
|
|
124
|
+
`agent.plugins` entry. Their arguments are fixed in code, because some of them
|
|
125
|
+
are what make the backend safe unattended: Copilot's `--disable-builtin-mcps`
|
|
126
|
+
keeps its own GitHub login away from the agent. `agent.presets.<name>` may
|
|
127
|
+
change only `binary`, `model` and `default_timeout_seconds`. Both presets
|
|
128
|
+
run unconfined and refuse to start on a `sandbox: "bwrap"` node.
|
|
129
|
+
- Generic plugins can have their token usage measured, so the token budget
|
|
130
|
+
applies to them (#387). A manifest's `usage` mapping says which JSON-lines
|
|
131
|
+
event carries usage and where each count is. The reading is taken when the
|
|
132
|
+
CLI exits, a resumed round is charged only the difference from the
|
|
133
|
+
session's last total, and anything unreadable is recorded as unknown, never
|
|
134
|
+
zero. Both presets carry mappings measured against real output.
|
|
135
|
+
- Each agent backend receives only its own model-provider keys (#390). A
|
|
136
|
+
well-known provider variable such as `OPENAI_API_KEY` or
|
|
137
|
+
`ANTHROPIC_API_KEY` reaches an agent only if its backend allows it:
|
|
138
|
+
Claude gets `ANTHROPIC_*` and `CLAUDE_*`, the presets their own, and
|
|
139
|
+
`opencode` and plugins what `env_allow` lists (default none). The security
|
|
140
|
+
sweep keeps GitHub access but loses the relay's secrets and other backends'
|
|
141
|
+
keys.
|
|
142
|
+
- One agent backends table in the docs, with resume, usage, stuck detection
|
|
143
|
+
and sandbox for each backend, and how to log the Copilot and Codex CLIs in
|
|
144
|
+
for relay use (#386).
|
|
145
|
+
|
|
146
|
+
### Fixed
|
|
147
|
+
|
|
148
|
+
- An agent can no longer outlive its session or stall the relay through
|
|
149
|
+
what it leaves behind (#394). Agents are spawned in their own session, so
|
|
150
|
+
each leads its own process group, and every exit path signals that group
|
|
151
|
+
rather than the direct child alone: after a normal exit and on a timeout
|
|
152
|
+
with SIGTERM, a 3-second grace, then SIGKILL; on cancellation, a stuck or
|
|
153
|
+
over-budget stop and a pipe error with an immediate SIGKILL, as the child
|
|
154
|
+
alone was before. A job the agent put in the background used
|
|
155
|
+
to keep running as the relay user after the run was reported done; and
|
|
156
|
+
because agents shared the relay's own process group, an agent that
|
|
157
|
+
signalled its group stopped the relay with it. The relay never signals a
|
|
158
|
+
group that is not provably the agent's own. The checkpoint read now opens
|
|
159
|
+
only a small regular file (`O_NOFOLLOW | O_NONBLOCK`, `fstat` on the same
|
|
160
|
+
descriptor, a 256 KiB cap, and the `.ctrlrelay` directory itself opened
|
|
161
|
+
without following a symlink, so an agent cannot point it at another run's
|
|
162
|
+
and have the relay read — and delete — that run's checkpoint): a FIFO at the state path blocked the read
|
|
163
|
+
forever, past the session timeout and with every other run on the event
|
|
164
|
+
loop stalled behind it, and a huge file or a symlink to `/dev/zero` was
|
|
165
|
+
read whole. A checkpoint that breaks the JSON parser (an integer past the
|
|
166
|
+
int-string limit, deep nesting, bad UTF-8) is now an unreadable checkpoint
|
|
167
|
+
rather than an exception. Known limit: a process group does not contain a
|
|
168
|
+
process that leaves it on purpose — one that calls `setsid` or
|
|
169
|
+
`setpgid` survives without a sandbox. Under `sandbox: "bwrap"` the PID
|
|
170
|
+
namespace ends it with the sandbox; full containment for the other
|
|
171
|
+
backends is #352. A narrow race also remains: if the agent's group id is
|
|
172
|
+
recycled for another group during a session and that group's leader has
|
|
173
|
+
exited too, the relay cannot tell the two apart and may signal it.
|
|
174
|
+
- An agent no longer inherits the daemon's stdin; it gets `/dev/null`
|
|
175
|
+
unless its prompt is sent on stdin. `codex exec` appends piped stdin to
|
|
176
|
+
its prompt and waits for end-of-file, so with the daemon's stdin an open
|
|
177
|
+
pipe every Codex run sat until the timeout without starting (#379).
|
|
178
|
+
- A repository's own instruction link that points outside the checkout,
|
|
179
|
+
such as `AGENTS.md -> /opt/shared/AGENTS.md`, was taken for the relay's
|
|
180
|
+
context link: it was replaced while the agent ran and deleted at cleanup.
|
|
181
|
+
Only links to the repo's context file are now replaced or removed (#379).
|
|
182
|
+
- When the daemon's `PATH` held a relative entry (`.`, an empty entry,
|
|
183
|
+
`bin`), a file committed to the repository could run in place of a bare
|
|
184
|
+
agent `binary`, of an `#!/usr/bin/env` interpreter, or of a command the
|
|
185
|
+
agent ran, because the agent starts inside the worktree. Agents now start
|
|
186
|
+
with relative `PATH` entries removed, sandboxed or not. This predates the
|
|
187
|
+
plugin runtime.
|
|
188
|
+
- Context-link cleanup walks the worktree without following symlinks, so a
|
|
189
|
+
directory swapped for a link during a run cannot redirect it outside the
|
|
190
|
+
checkout (#379).
|
|
191
|
+
- The relay no longer replaces a repository's own `CLAUDE.md` with its repo
|
|
192
|
+
context link. The tracked file became a type change while the agent ran
|
|
193
|
+
and a deletion after cleanup, so an agent that staged everything could put
|
|
194
|
+
either into its pull request. Cleanup now removes only links the relay
|
|
195
|
+
made (#379).
|
|
196
|
+
- A plugin listed under `agent.plugins` but not routed to by any repo or by
|
|
197
|
+
`agent.assignable_types` is built only when a pinned run selects it, so
|
|
198
|
+
one that cannot run under `agent.sandbox: bwrap` no longer stops the
|
|
199
|
+
daemon at startup. A routed one still does, naming where it was routed
|
|
200
|
+
from. A backend that fails to build on demand is refused for that run
|
|
201
|
+
instead of raising out of the pipeline (#379).
|
|
202
|
+
|
|
203
|
+
### Upgrade notes
|
|
204
|
+
|
|
205
|
+
- `agent.plugins.copilot` and `agent.plugins.codex` are now refused at load,
|
|
206
|
+
because those names are built-in presets (#384, #385). Remove the entry and
|
|
207
|
+
set `agent.presets.<name>` if you changed its binary, model or timeout.
|
|
208
|
+
- An `opencode` setup, or a plugin, that reads a model-provider key from the
|
|
209
|
+
environment now needs that key listed in its `env_allow` (#390).
|
|
210
|
+
- On Linux, log the Copilot CLI in with the keyring out of reach
|
|
211
|
+
(`DBUS_SESSION_BUS_ADDRESS=disabled: copilot`, then `/login`) or set
|
|
212
|
+
`COPILOT_GITHUB_TOKEN`: the relay blocks the desktop keyring, so a
|
|
213
|
+
keyring-stored login cannot be read.
|
|
214
|
+
- A run waiting on a question from before this release has no recorded
|
|
215
|
+
agent identity. If its agent session had already started, it is refused
|
|
216
|
+
with `agent_identity_unknown` instead of being resumed on whatever backend
|
|
217
|
+
is configured now. Answer or close those questions before upgrading, or
|
|
218
|
+
re-run the issue afterwards (#379).
|
|
219
|
+
- A repo that commits its own `CLAUDE.md` now runs with that file instead of
|
|
220
|
+
the relay's context under that name. Move anything the agent must see into
|
|
221
|
+
the repo's file, or into the contexts entry for a name the repo does not
|
|
222
|
+
use (#379).
|
|
223
|
+
|
|
10
224
|
## [0.27.0] - 2026-11-06
|
|
11
225
|
|
|
12
226
|
### Added
|
|
@@ -2650,7 +2864,9 @@ pipeline).
|
|
|
2650
2864
|
per-phase implementation plans (Phase 0 through Phase 4).
|
|
2651
2865
|
- `docs/Claude_Code_Project_Guide.md` — project development guide.
|
|
2652
2866
|
|
|
2653
|
-
[Unreleased]: https://github.com/AInvirion/ctrlrelay/compare/v0.
|
|
2867
|
+
[Unreleased]: https://github.com/AInvirion/ctrlrelay/compare/v0.28.1...HEAD
|
|
2868
|
+
[0.28.1]: https://github.com/AInvirion/ctrlrelay/compare/v0.28.0...v0.28.1
|
|
2869
|
+
[0.28.0]: https://github.com/AInvirion/ctrlrelay/compare/v0.27.0...v0.28.0
|
|
2654
2870
|
[0.27.0]: https://github.com/AInvirion/ctrlrelay/compare/v0.26.0...v0.27.0
|
|
2655
2871
|
[0.26.0]: https://github.com/AInvirion/ctrlrelay/compare/v0.25.0...v0.26.0
|
|
2656
2872
|
[0.25.0]: https://github.com/AInvirion/ctrlrelay/compare/v0.24.0...v0.25.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ctrlrelay
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.28.1
|
|
4
4
|
Summary: Local-first orchestrator for headless coding agents across multiple GitHub repos
|
|
5
5
|
Project-URL: Homepage, https://github.com/AInvirion/ctrlrelay
|
|
6
6
|
Project-URL: Documentation, https://ainvirion.github.io/ctrlrelay/
|
|
@@ -66,7 +66,8 @@ Coding agents are great when you sit and watch them. ctrlrelay is for the
|
|
|
66
66
|
work you don't want to sit and watch.
|
|
67
67
|
|
|
68
68
|
- **Your backlog is the to-do list.** No new tool to learn. Assign an issue
|
|
69
|
-
(or add a label) and the work starts.
|
|
69
|
+
(or add a label) and the work starts. Teammates you allow can hand it work
|
|
70
|
+
the same way. The result is a normal pull request
|
|
70
71
|
your team already knows how to review.
|
|
71
72
|
- **Runs where your code already lives.** ctrlrelay is a small program on your
|
|
72
73
|
laptop or server — not a cloud service. Nothing leaves your machine except
|
|
@@ -81,8 +82,11 @@ work you don't want to sit and watch.
|
|
|
81
82
|
- **Done means done.** It waits for CI, fixes failing checks, and posts an
|
|
82
83
|
independent review on the pull request — so what reaches you is ready to
|
|
83
84
|
read, not a first draft.
|
|
84
|
-
- **Pick your agent per repo.** Claude Code
|
|
85
|
-
|
|
85
|
+
- **Pick your agent per repo.** Claude Code, OpenCode, GitHub Copilot CLI or
|
|
86
|
+
Codex, plus simple non-interactive CLIs enrolled through a local plugin
|
|
87
|
+
manifest ([what each one supports](https://ainvirion.github.io/ctrlrelay/configuration/#agent-backends)).
|
|
88
|
+
Choose the default per repo; a future control plane can select from the
|
|
89
|
+
agents you allow.
|
|
86
90
|
- **Grows with you.** Start on one laptop. Add more machines later; they share
|
|
87
91
|
the work without stepping on each other.
|
|
88
92
|
- **Open source.** Apache 2.0. Read every line of what runs on your machine.
|
|
@@ -116,7 +120,8 @@ flowchart LR
|
|
|
116
120
|
PR -->|CI + review| YOU(["You merge"])
|
|
117
121
|
```
|
|
118
122
|
|
|
119
|
-
1. **You assign an issue** to yourself, or add a label you pick.
|
|
123
|
+
1. **You assign an issue** to yourself, or add a label you pick. Teammates
|
|
124
|
+
on your `allowed_accounts` list can do the same.
|
|
120
125
|
2. **ctrlrelay starts the agent** in a fresh, isolated copy of the repo.
|
|
121
126
|
3. **If the agent has a question**, it lands in your chat. Your reply goes
|
|
122
127
|
straight back to the same session.
|
|
@@ -25,7 +25,8 @@ Coding agents are great when you sit and watch them. ctrlrelay is for the
|
|
|
25
25
|
work you don't want to sit and watch.
|
|
26
26
|
|
|
27
27
|
- **Your backlog is the to-do list.** No new tool to learn. Assign an issue
|
|
28
|
-
(or add a label) and the work starts.
|
|
28
|
+
(or add a label) and the work starts. Teammates you allow can hand it work
|
|
29
|
+
the same way. The result is a normal pull request
|
|
29
30
|
your team already knows how to review.
|
|
30
31
|
- **Runs where your code already lives.** ctrlrelay is a small program on your
|
|
31
32
|
laptop or server — not a cloud service. Nothing leaves your machine except
|
|
@@ -40,8 +41,11 @@ work you don't want to sit and watch.
|
|
|
40
41
|
- **Done means done.** It waits for CI, fixes failing checks, and posts an
|
|
41
42
|
independent review on the pull request — so what reaches you is ready to
|
|
42
43
|
read, not a first draft.
|
|
43
|
-
- **Pick your agent per repo.** Claude Code
|
|
44
|
-
|
|
44
|
+
- **Pick your agent per repo.** Claude Code, OpenCode, GitHub Copilot CLI or
|
|
45
|
+
Codex, plus simple non-interactive CLIs enrolled through a local plugin
|
|
46
|
+
manifest ([what each one supports](https://ainvirion.github.io/ctrlrelay/configuration/#agent-backends)).
|
|
47
|
+
Choose the default per repo; a future control plane can select from the
|
|
48
|
+
agents you allow.
|
|
45
49
|
- **Grows with you.** Start on one laptop. Add more machines later; they share
|
|
46
50
|
the work without stepping on each other.
|
|
47
51
|
- **Open source.** Apache 2.0. Read every line of what runs on your machine.
|
|
@@ -75,7 +79,8 @@ flowchart LR
|
|
|
75
79
|
PR -->|CI + review| YOU(["You merge"])
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
1. **You assign an issue** to yourself, or add a label you pick.
|
|
82
|
+
1. **You assign an issue** to yourself, or add a label you pick. Teammates
|
|
83
|
+
on your `allowed_accounts` list can do the same.
|
|
79
84
|
2. **ctrlrelay starts the agent** in a fresh, isolated copy of the repo.
|
|
80
85
|
3. **If the agent has a question**, it lands in your chat. Your reply goes
|
|
81
86
|
straight back to the same session.
|
|
@@ -39,6 +39,9 @@ agent:
|
|
|
39
39
|
binary: "claude"
|
|
40
40
|
default_timeout_seconds: 1800
|
|
41
41
|
output_format: "json"
|
|
42
|
+
# Enrolled types a future control-plane assignment may explicitly select.
|
|
43
|
+
# Empty keeps selection entirely local. Standalone repo defaults are unaffected.
|
|
44
|
+
assignable_types: []
|
|
42
45
|
|
|
43
46
|
# Read only when a backend of type "opencode" is built, so this block
|
|
44
47
|
# is inert on a claude-only deployment and harmless to leave in place.
|
|
@@ -67,6 +70,34 @@ agent:
|
|
|
67
70
|
# # model is usually slower per token than a hosted one.
|
|
68
71
|
# default_timeout_seconds: 3600
|
|
69
72
|
|
|
73
|
+
# Optional generic CLI manifests (#379). Each key becomes an agent_type you
|
|
74
|
+
# can use as agent.type, repos[].agent_type, or an allowlisted assignment.
|
|
75
|
+
# Arguments run directly (no shell). Supported placeholders: {prompt},
|
|
76
|
+
# {session_id}, {state_file}, {resume_session_id}, {model}, {provider}.
|
|
77
|
+
# Generic plugins have no live transcript, usage reading, or bwrap support.
|
|
78
|
+
# plugins:
|
|
79
|
+
# example:
|
|
80
|
+
# binary: "/opt/example/bin/agent"
|
|
81
|
+
# args: ["run", "{prompt}"]
|
|
82
|
+
# prompt_mode: "arg" # or "stdin"
|
|
83
|
+
# resume_args: ["resume", "{resume_session_id}", "{prompt}"]
|
|
84
|
+
# session_id_path: "info.session.id" # required with resume_args
|
|
85
|
+
# model: "model-x"
|
|
86
|
+
# provider: "provider-y"
|
|
87
|
+
# plugin_version: "1"
|
|
88
|
+
# instructions_files: ["AGENTS.md"] # names the CLI reads for context
|
|
89
|
+
# default_timeout_seconds: 1800
|
|
90
|
+
# Copilot and Codex are built-in presets: set repos[].agent_type to
|
|
91
|
+
# "copilot" or "codex" with no entry here. Only these three keys can be
|
|
92
|
+
# changed; the arguments are fixed. See docs/configuration.md.
|
|
93
|
+
# presets:
|
|
94
|
+
# copilot:
|
|
95
|
+
# binary: "copilot"
|
|
96
|
+
# model: "claude-sonnet-4.5"
|
|
97
|
+
# default_timeout_seconds: 3600
|
|
98
|
+
# codex:
|
|
99
|
+
# model: "gpt-5.1-codex"
|
|
100
|
+
|
|
70
101
|
# EXACTLY ONE transport is active, and it is the one named by `type`.
|
|
71
102
|
# The other blocks below are inert: they may stay in the file, but nothing
|
|
72
103
|
# reads them. Running Telegram and Mattermost at the same time is NOT
|
|
@@ -40,11 +40,15 @@ pipeline work.
|
|
|
40
40
|
## Dispatcher ↔ agent interaction
|
|
41
41
|
|
|
42
42
|
[`src/ctrlrelay/core/dispatcher.py`](https://github.com/AInvirion/ctrlrelay/blob/main/src/ctrlrelay/core/dispatcher.py)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`
|
|
46
|
-
`
|
|
47
|
-
`
|
|
43
|
+
owns the shared subprocess lifecycle. Thin adapters provide CLI-specific argv
|
|
44
|
+
and native-session parsing: `ClaudeDispatcher`, `OpenCodeDispatcher`, and
|
|
45
|
+
`GenericCliDispatcher` for local manifests. All expose the same async
|
|
46
|
+
`spawn_session` shape, so pipelines stay agent-agnostic.
|
|
47
|
+
`make_repo_agent_dispatcher` enrolls the configured plugins and selects the
|
|
48
|
+
standalone default per repo: `agent.type` is the global default and
|
|
49
|
+
`repos[].agent_type` overrides it. `select_agent` additionally resolves an
|
|
50
|
+
allowlisted assignment override or a previously pinned identity to one concrete
|
|
51
|
+
adapter at the run boundary.
|
|
48
52
|
|
|
49
53
|
- **Spawn shape:** Claude Code runs
|
|
50
54
|
`claude -p <prompt> --output-format stream-json --verbose --dangerously-skip-permissions [--resume <id>]`;
|
|
@@ -70,11 +74,20 @@ with the same async `spawn_session` shape, so pipelines stay agent-agnostic:
|
|
|
70
74
|
exposes `.success`, `.blocked`, `.failed` properties for pattern-matching
|
|
71
75
|
pipelines.
|
|
72
76
|
- **Resume:** `resume_session_id` translates to `--resume <id>` (Claude
|
|
73
|
-
Code)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
Code), `--session <id>` (OpenCode), or a generic manifest's `resume_args`.
|
|
78
|
+
Pipelines look up the agent's own id (persisted as
|
|
79
|
+
`sessions.agent_session_id` by the prior spawn) and pass it through. A
|
|
80
|
+
generic plugin without a resume mapping refuses the request instead of
|
|
81
|
+
silently starting fresh. Claude retains its existing fresh-session fallback
|
|
82
|
+
when no native id was captured.
|
|
83
|
+
- **Identity and capabilities (#379):** every concrete adapter reports an
|
|
84
|
+
`AgentIdentity` (`agent_type`, plugin id/version, model, provider and an
|
|
85
|
+
invocation fingerprint) and conservative `AgentCapabilities` (resume,
|
|
86
|
+
structured events, usage and sandbox). The selected identity is written to
|
|
87
|
+
`sessions` before dispatch and
|
|
88
|
+
checked again before storing a native session id. A conflicting identity is
|
|
89
|
+
an error, never a silent backend switch. Capabilities default to false;
|
|
90
|
+
missing usage or structured-event support is not guessed.
|
|
78
91
|
|
|
79
92
|
The dispatcher does NOT proxy or wrap the agent session — it shells out the
|
|
80
93
|
same way you'd shell out to `gh` or `git`. This is deliberate.
|
|
@@ -87,7 +100,7 @@ SQLite, single file at `paths.state_db`.
|
|
|
87
100
|
|
|
88
101
|
| Table | Columns | Notes |
|
|
89
102
|
|---|---|---|
|
|
90
|
-
| `sessions` | `id`, `pipeline`, `repo`, `issue_number`, `worktree_path`, `status`, `blocked_question`, `started_at`, `ended_at`, `claude_exit_code`, `summary`, `agent_session_id`, `output_tail` | Indexed on `repo` and `status`. One row per pipeline run. `agent_session_id` stores the agent's own session id for resume; `claude_exit_code` holds the worker's exit code whichever backend ran; `output_tail` is a scrubbed, capped tail of its output (#204). |
|
|
103
|
+
| `sessions` | `id`, `pipeline`, `repo`, `issue_number`, `worktree_path`, `status`, `blocked_question`, `started_at`, `ended_at`, `claude_exit_code`, `summary`, `agent_session_id`, `agent_type`, `agent_plugin_id`, `agent_plugin_version`, `agent_model`, `agent_provider`, `agent_fingerprint`, `output_tail` | Indexed on `repo` and `status`. One row per pipeline run. `agent_session_id` stores the agent's own session id for resume; the `agent_*` columns pin the selected plugin identity and invocation fingerprint (#379); `claude_exit_code` holds the worker's exit code whichever backend ran; `output_tail` is a scrubbed, capped tail of its output (#204). |
|
|
91
104
|
| `repo_locks` | `repo` (PK), `session_id`, `acquired_at` | Acquired with `acquire_lock`, released in the pipeline's `finally`. |
|
|
92
105
|
| `github_cursor` | `repo`, `last_checked_at`, `last_seen_issue_update` | Bounds GitHub API calls in the poller. |
|
|
93
106
|
| `pending_resumes` | `session_id` (PK), `pipeline`, `repo`, `question`, `created_at`, `answer`, `answered_at`, `resumed_at`, `expired_at`, `expired_reason`, `held_until`, `posted`, `last_answered_at` | One row per BLOCKED session awaiting an answer, whichever chat it was asked on. An answer arriving after the in-session wait lands here and the poller's sweeper drives the resume. |
|
|
@@ -99,8 +112,9 @@ SQLite, single file at `paths.state_db`.
|
|
|
99
112
|
| `telegram_pending` | `request_id`, `session_id`, `question`, `asked_at`, `answered_at`, `answer` | Legacy: still created by the schema, but no code reads or writes it. Outstanding questions live in `pending_resumes`. |
|
|
100
113
|
|
|
101
114
|
Methods on `StateDB`: `acquire_lock`, `release_lock`, `get_lock_holder`,
|
|
102
|
-
`list_locks`, `set_agent_session_id`, `get_agent_session_id`,
|
|
103
|
-
`
|
|
115
|
+
`list_locks`, `set_agent_session_id`, `get_agent_session_id`,
|
|
116
|
+
`pin_agent_identity`, `get_agent_identity`, plus raw `execute` / `commit`.
|
|
117
|
+
The pipeline code uses raw SQL for the `sessions`
|
|
104
118
|
table on purpose — kept thin and grep-able.
|
|
105
119
|
|
|
106
120
|
## Worktree lifecycle
|
|
@@ -203,7 +203,8 @@ ctrlrelay run dev -i ISSUE [-r REPO] [-c PATH] [--from-step STEP]
|
|
|
203
203
|
| `--from-step` | no | — | Start again from `plan`, `code` or `publish`, taking the earlier steps from the issue's newest run that saved them and moving the branch back. See [A run whose process died](operations.md#a-run-whose-process-died). |
|
|
204
204
|
|
|
205
205
|
Acquires the per-repo lock, creates the worktree, spawns the repo's agent
|
|
206
|
-
(Claude Code or
|
|
206
|
+
(Claude Code, OpenCode, Copilot, Codex or a plugin, per `agent.type` /
|
|
207
|
+
`repos[].agent_type`) with the
|
|
207
208
|
issue's title/body in the prompt, and reports the result. If the agent blocks
|
|
208
209
|
and a chat transport (Telegram or Mattermost) is configured, posts the question
|
|
209
210
|
to your chat and waits for a reply (up to `DEFAULT_MAX_BLOCKED_ROUNDS` rounds).
|
|
@@ -263,7 +264,9 @@ ctrlrelay poller start [-c PATH] [-F|--foreground] [-i SECONDS]
|
|
|
263
264
|
| `--config` / `-c` | `config/orchestrator.yaml` | Config path. |
|
|
264
265
|
|
|
265
266
|
Polls each configured repo for issues assigned to your GitHub user (resolved
|
|
266
|
-
via `gh api user --jq .login`)
|
|
267
|
+
via `gh api user --jq .login`), by you or by an account in
|
|
268
|
+
`allowed_accounts`, plus issues carrying a trigger label one of those
|
|
269
|
+
accounts added. On the first run, seeds the seen-issue set
|
|
267
270
|
with current assignments so it does not replay your existing backlog.
|
|
268
271
|
|
|
269
272
|
For each newly assigned issue, runs `run_dev_issue(...)` (i.e. the dev pipeline)
|