cli-agent-runner 0.1.6__tar.gz → 0.1.7__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.
- cli_agent_runner-0.1.7/.githooks/commit-msg +33 -0
- cli_agent_runner-0.1.7/.github/workflows/ci.yml +105 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/CHANGELOG.md +58 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/CONTRIBUTING.md +8 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/PKG-INFO +6 -5
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/README.md +5 -4
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/agent_runtime.py +8 -19
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/api.py +8 -2
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/api_types.py +1 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/common.py +1 -1
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/init_cmd.py +8 -2
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/config.py +8 -4
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/defenses.py +4 -4
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/monitor.py +2 -2
- cli_agent_runner-0.1.7/agent_runner/presets/aider.toml +30 -0
- cli_agent_runner-0.1.7/agent_runner/presets/claude.toml +29 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/runner.py +1 -1
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/scaffold.py +26 -38
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/docs/README.md +9 -6
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/docs/architecture.md +6 -6
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/docs/configuration.md +33 -6
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/docs/quickstart.md +11 -6
- cli_agent_runner-0.1.7/docs/recipes/aider.md +117 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/docs/runbook.md +14 -3
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/conftest.py +27 -0
- cli_agent_runner-0.1.7/tests/contract/test_public_api_surface.py +130 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/integration/test_run_one_round_with_fake_agent.py +39 -0
- cli_agent_runner-0.1.7/tests/integration/test_scaffold_presets.py +80 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_module_sizes.py +7 -4
- cli_agent_runner-0.1.7/tests/invariants/test_no_ai_signatures.py +83 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_peek_schema_version.py +2 -2
- cli_agent_runner-0.1.7/tests/literate/__init__.py +0 -0
- cli_agent_runner-0.1.7/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_agent_runtime.py +23 -12
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_cli_init_install.py +27 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_config.py +96 -14
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_defenses.py +26 -2
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_monitor_detectors.py +5 -5
- cli_agent_runner-0.1.7/tests/unit/test_presets.py +87 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_scaffold.py +38 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_service_unit.py +1 -1
- cli_agent_runner-0.1.6/.github/workflows/ci.yml +0 -57
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/.codecov.yml +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/.gitignore +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/.vulture-whitelist.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/LICENSE +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/README.zh.md +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/SECURITY.md +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/detector_helpers.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/events.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.1.6/tests → cli_agent_runner-0.1.7/agent_runner/presets}/__init__.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/build.sh +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/deploy/example-agent-runner.toml +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/deploy/launchd.plist.tmpl +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/deploy/run-loop.sh +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/deploy/systemd.service.tmpl +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/docs/commands.md +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/docs/plugins.md +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/pyproject.toml +0 -0
- {cli_agent_runner-0.1.6/tests/e2e → cli_agent_runner-0.1.7/tests}/__init__.py +0 -0
- {cli_agent_runner-0.1.6/tests/integration → cli_agent_runner-0.1.7/tests/contract}/__init__.py +0 -0
- {cli_agent_runner-0.1.6/tests/invariants → cli_agent_runner-0.1.7/tests/e2e}/__init__.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.1.6/tests/literate → cli_agent_runner-0.1.7/tests/integration}/__init__.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/integration/test_run_loop_backoff.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.1.6/tests/unit → cli_agent_runner-0.1.7/tests/invariants}/__init__.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_architecture.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_detector_helpers.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.1.6 → cli_agent_runner-0.1.7}/tests/unit/test_vcs_state.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Reject commit messages that contain AI-tool attribution.
|
|
3
|
+
#
|
|
4
|
+
# Rationale: this repository's history is curated to credit only the human
|
|
5
|
+
# author. AI-tool trailers (Co-Authored-By: Claude, 🤖 Generated with ..., etc.)
|
|
6
|
+
# create false "claude" / "anthropic" entries in GitHub's Contributors widget
|
|
7
|
+
# and bloat commit log noise.
|
|
8
|
+
#
|
|
9
|
+
# Activate per clone: git config core.hooksPath .githooks
|
|
10
|
+
# Bypass (rarely needed): git commit --no-verify
|
|
11
|
+
|
|
12
|
+
MSG_FILE="$1"
|
|
13
|
+
|
|
14
|
+
# Strip git-comment lines before checking
|
|
15
|
+
CONTENT=$(grep -v '^#' "$MSG_FILE" || true)
|
|
16
|
+
|
|
17
|
+
# Case-insensitive regex; tab/space tolerant
|
|
18
|
+
FORBIDDEN_REGEX='([Cc]o-[Aa]uthored-[Bb]y:|🤖|[Gg]enerated with [Cc]laude|[Gg]enerated with \[Cursor\]|noreply@anthropic\.com)'
|
|
19
|
+
|
|
20
|
+
if printf '%s\n' "$CONTENT" | grep -E -q "$FORBIDDEN_REGEX"; then
|
|
21
|
+
echo "" >&2
|
|
22
|
+
echo "commit-msg hook: AI-tool attribution detected in commit message." >&2
|
|
23
|
+
echo "" >&2
|
|
24
|
+
echo "Offending lines:" >&2
|
|
25
|
+
printf '%s\n' "$CONTENT" | grep -E -n "$FORBIDDEN_REGEX" | sed 's/^/ /' >&2
|
|
26
|
+
echo "" >&2
|
|
27
|
+
echo "Strip Co-Authored-By trailers, 🤖, 'Generated with ...', and" >&2
|
|
28
|
+
echo "noreply@anthropic.com refs, then retry." >&2
|
|
29
|
+
echo "" >&2
|
|
30
|
+
echo "(Bypass: git commit --no-verify — use sparingly, requires explicit" >&2
|
|
31
|
+
echo " review since CI lint-commits job will also reject the push.)" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
# Cancel in-flight runs on the same PR ref when a new push lands.
|
|
10
|
+
# Push-to-main runs always complete (cancel-in-progress only on PRs).
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ci-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
lint-commits:
|
|
17
|
+
name: lint commit messages (no AI-tool attribution)
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 2
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
# PR: scan only new commits the PR introduces (base..head).
|
|
24
|
+
# Push to main: scan the just-pushed commits (before..after).
|
|
25
|
+
# fetch-depth=0 ensures both refs are available.
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
- name: scan new commit messages
|
|
28
|
+
env:
|
|
29
|
+
GH_EVENT: ${{ github.event_name }}
|
|
30
|
+
BEFORE_SHA: ${{ github.event.before }}
|
|
31
|
+
BASE_REF: ${{ github.event.pull_request.base.sha }}
|
|
32
|
+
HEAD_REF: ${{ github.event.pull_request.head.sha }}
|
|
33
|
+
run: |
|
|
34
|
+
set -euo pipefail
|
|
35
|
+
if [ "$GH_EVENT" = "pull_request" ]; then
|
|
36
|
+
RANGE="${BASE_REF}..${HEAD_REF}"
|
|
37
|
+
else
|
|
38
|
+
# push event; before is 0..0 on first push or branch creation
|
|
39
|
+
if [ -z "${BEFORE_SHA:-}" ] || [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then
|
|
40
|
+
RANGE="HEAD~1..HEAD"
|
|
41
|
+
else
|
|
42
|
+
RANGE="${BEFORE_SHA}..HEAD"
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
echo "Scanning commit-message bodies in $RANGE"
|
|
46
|
+
# Collect messages joined by NUL bytes; grep -P for fixed alternation.
|
|
47
|
+
BODIES=$(git log --format='%B%x00' "$RANGE" || true)
|
|
48
|
+
if [ -z "$BODIES" ]; then
|
|
49
|
+
echo " (no commits in range)"
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
PATTERN='(Co-Authored-By:|🤖|Generated with Claude|Generated with \[Cursor\]|noreply@anthropic\.com)'
|
|
53
|
+
if printf '%s' "$BODIES" | grep -i -E -q "$PATTERN"; then
|
|
54
|
+
echo "::error::AI-tool attribution detected in new commit messages."
|
|
55
|
+
echo "Offending lines:"
|
|
56
|
+
printf '%s' "$BODIES" | grep -i -n -E "$PATTERN" || true
|
|
57
|
+
echo ""
|
|
58
|
+
echo "Strip Co-Authored-By trailers, 🤖, 'Generated with ...',"
|
|
59
|
+
echo "and noreply@anthropic.com refs, then force-push the fixed range."
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
echo " clean"
|
|
63
|
+
|
|
64
|
+
test:
|
|
65
|
+
name: test py${{ matrix.python }} / ${{ matrix.os }}
|
|
66
|
+
runs-on: ${{ matrix.os }}
|
|
67
|
+
timeout-minutes: 15
|
|
68
|
+
strategy:
|
|
69
|
+
fail-fast: false
|
|
70
|
+
matrix:
|
|
71
|
+
python: ["3.11", "3.12", "3.13"]
|
|
72
|
+
os: [ubuntu-latest, macos-latest]
|
|
73
|
+
# Steps below mirror ./build.sh check, inlined for per-step Actions UI
|
|
74
|
+
# granularity AND because coverage requires extra pytest flags not in
|
|
75
|
+
# the local `test` task. CONTRIBUTING.md states the parity intentionally.
|
|
76
|
+
steps:
|
|
77
|
+
- uses: actions/checkout@v4
|
|
78
|
+
with:
|
|
79
|
+
fetch-depth: 0
|
|
80
|
+
- uses: actions/setup-python@v5
|
|
81
|
+
with:
|
|
82
|
+
python-version: ${{ matrix.python }}
|
|
83
|
+
cache: pip
|
|
84
|
+
- name: install
|
|
85
|
+
run: pip install -e ".[dev]"
|
|
86
|
+
- name: ruff
|
|
87
|
+
run: |
|
|
88
|
+
ruff check .
|
|
89
|
+
ruff format --check .
|
|
90
|
+
- name: tests with coverage
|
|
91
|
+
run: pytest -q --ignore=tests/e2e --ignore=tests/literate --cov --cov-report=xml --cov-report=term
|
|
92
|
+
- name: docs CI gate
|
|
93
|
+
run: |
|
|
94
|
+
python -m agent_runner._docgen
|
|
95
|
+
git diff --exit-code docs/
|
|
96
|
+
- name: literate quickstart
|
|
97
|
+
run: pytest tests/literate/ -q
|
|
98
|
+
- name: upload coverage
|
|
99
|
+
if: matrix.python == '3.12' && matrix.os == 'ubuntu-latest'
|
|
100
|
+
uses: codecov/codecov-action@v4
|
|
101
|
+
with:
|
|
102
|
+
files: ./coverage.xml
|
|
103
|
+
fail_ci_if_error: false
|
|
104
|
+
env:
|
|
105
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -7,6 +7,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.7] - 2026-05-13
|
|
11
|
+
|
|
12
|
+
### Migration for existing 0.1.6 users (DOWNSTREAM CONSUMERS READ THIS)
|
|
13
|
+
|
|
14
|
+
If you maintain an `agent-runner.toml` by hand (rather than via `agent-runner init`),
|
|
15
|
+
you must add an `[agent.env]` block to preserve the Claude self-update suppression
|
|
16
|
+
that 0.1.6 injected implicitly. **Without this, mid-loop self-updates can race with
|
|
17
|
+
the supervisor.**
|
|
18
|
+
|
|
19
|
+
Add to your `agent-runner.toml`:
|
|
20
|
+
|
|
21
|
+
```toml
|
|
22
|
+
[agent.env]
|
|
23
|
+
DISABLE_AUTOUPDATER = "1"
|
|
24
|
+
CLAUDE_CODE_EFFORT_LEVEL = "xhigh"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or regenerate cleanly:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
agent-runner init --preset claude --force
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Plugin authors (Argus Gateway, etc.): no public API was renamed or removed
|
|
34
|
+
from your import surface. The deleted symbols (`agent_runner.agent_runtime.CRITICAL_ENV_DEFAULTS`,
|
|
35
|
+
`agent_runner.agent_runtime.merge_critical_envs`) were internal — not part of
|
|
36
|
+
the documented plugin API. A new public-API contract test
|
|
37
|
+
(`tests/contract/test_public_api_surface.py`) locks in `api_types`, `events`,
|
|
38
|
+
`hooks`, `monitor`, `detector_helpers` import surfaces so future refactors
|
|
39
|
+
can't silently drop names you rely on.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- `agent-runner init --preset {claude,aider}` selects between bundled CLI presets
|
|
43
|
+
(default: `claude`). New preset directory: `agent_runner/presets/`.
|
|
44
|
+
- `[agent.env]` TOML block — per-CLI env injections, replacing the hardcoded
|
|
45
|
+
`CRITICAL_ENV_DEFAULTS` constant. Empty dict by default.
|
|
46
|
+
- `docs/recipes/aider.md` — aider integration recipe.
|
|
47
|
+
- `tests/contract/test_public_api_surface.py` — public-API surface snapshot for
|
|
48
|
+
plugin authors.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
- Core code (`agent_runtime.py`, `config.py`, `runner.py`, `scaffold.py`,
|
|
52
|
+
`defenses.py`) is now truly provider-agnostic: zero hardcoded Claude defaults.
|
|
53
|
+
Claude remains the reference example throughout the docs, but its specifics
|
|
54
|
+
live in `agent_runner/presets/claude.toml` (shipped as package data).
|
|
55
|
+
- `MonitorConfig.auth_fail_hint` default is now empty string; per-CLI hint
|
|
56
|
+
comes from the preset.
|
|
57
|
+
- `PEEK_SCHEMA_VERSION` bumped 1.3 → 1.4. `InitResult` gains `preset: str` field.
|
|
58
|
+
- `agent_runner/defenses.py` `critical_envs_injection` row now reads from
|
|
59
|
+
`cfg.agent.env.keys()` (not the deleted constant); state is "active" iff
|
|
60
|
+
the config defines any env injections, else "off".
|
|
61
|
+
|
|
62
|
+
### Removed
|
|
63
|
+
- `agent_runner.agent_runtime.CRITICAL_ENV_DEFAULTS` constant.
|
|
64
|
+
- `agent_runner.agent_runtime.merge_critical_envs()` function.
|
|
65
|
+
- `agent_runner.config._DEFAULT_AUTH_HINT` constant's Claude-specific default
|
|
66
|
+
string (replaced with `""`; per-CLI hint now in preset files).
|
|
67
|
+
|
|
10
68
|
## [0.1.6] - 2026-05-12
|
|
11
69
|
|
|
12
70
|
Zero-feature maintenance release — internal cleanup pass after the 0.1.x plugin
|
|
@@ -9,6 +9,7 @@ git clone https://github.com/wan9yu/cli-agent-runner.git
|
|
|
9
9
|
cd cli-agent-runner
|
|
10
10
|
python3 -m venv .venv && source .venv/bin/activate
|
|
11
11
|
pip install -e ".[dev]"
|
|
12
|
+
git config core.hooksPath .githooks # enables the commit-msg lint hook
|
|
12
13
|
./build.sh check
|
|
13
14
|
```
|
|
14
15
|
|
|
@@ -16,6 +17,13 @@ pip install -e ".[dev]"
|
|
|
16
17
|
+ integration tests, the literate quickstart, and the docs CI gate. It's
|
|
17
18
|
what GitHub Actions runs on every push and PR.
|
|
18
19
|
|
|
20
|
+
`git config core.hooksPath .githooks` activates the in-repo
|
|
21
|
+
[`.githooks/commit-msg`](.githooks/commit-msg) hook which rejects commit
|
|
22
|
+
messages containing `Co-Authored-By:` trailers, robot emojis, or other
|
|
23
|
+
AI-tool attribution patterns. The same check runs in CI (`lint-commits`
|
|
24
|
+
job) and as a pytest invariant (`tests/invariants/test_no_ai_signatures.py`)
|
|
25
|
+
— defense in depth.
|
|
26
|
+
|
|
19
27
|
## Workflow
|
|
20
28
|
|
|
21
29
|
1. Open an issue first for non-trivial changes — saves wasted work on both sides.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: Restart-on-exit supervisor for autonomous CLI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/wan9yu/cli-agent-runner
|
|
6
6
|
Project-URL: Documentation, https://github.com/wan9yu/cli-agent-runner#readme
|
|
@@ -41,10 +41,11 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
|
|
42
42
|
# agent-runner
|
|
43
43
|
|
|
44
|
-
A restart-on-exit supervisor for autonomous
|
|
45
|
-
Code
|
|
46
|
-
the
|
|
47
|
-
|
|
44
|
+
A restart-on-exit supervisor for autonomous coding CLIs. Tested with Claude
|
|
45
|
+
Code and aider out of the box; any prompt-arg CLI via custom config. Spawn
|
|
46
|
+
the agent round-after-round under defenses that prevent the failure modes
|
|
47
|
+
that bite in production: stuck rounds, orphan commits, OAuth burn loops,
|
|
48
|
+
full disks, runaway memory.
|
|
48
49
|
|
|
49
50
|
```
|
|
50
51
|
┌──────────────────────────────────────────┐
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
# agent-runner
|
|
6
6
|
|
|
7
|
-
A restart-on-exit supervisor for autonomous
|
|
8
|
-
Code
|
|
9
|
-
the
|
|
10
|
-
|
|
7
|
+
A restart-on-exit supervisor for autonomous coding CLIs. Tested with Claude
|
|
8
|
+
Code and aider out of the box; any prompt-arg CLI via custom config. Spawn
|
|
9
|
+
the agent round-after-round under defenses that prevent the failure modes
|
|
10
|
+
that bite in production: stuck rounds, orphan commits, OAuth burn loops,
|
|
11
|
+
full disks, runaway memory.
|
|
11
12
|
|
|
12
13
|
```
|
|
13
14
|
┌──────────────────────────────────────────┐
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.7'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 7)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""Agent subprocess management —
|
|
1
|
+
"""Agent subprocess management — spawns the configured agent CLI process.
|
|
2
2
|
|
|
3
3
|
Defenses encoded here:
|
|
4
4
|
- R725: SIGTERM handler reaps process group before runner exits
|
|
5
5
|
- R1128: ROUND_TIMEOUT is wall-clock hard wall (no activity-based extension)
|
|
6
6
|
- #307: start_new_session=True isolates subprocess in its own pgrp
|
|
7
|
-
- env injection:
|
|
7
|
+
- env injection: per-CLI envs come from AgentConfig.env (preset-supplied);
|
|
8
|
+
no implicit injection in this module.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from __future__ import annotations
|
|
@@ -97,26 +98,14 @@ def run(
|
|
|
97
98
|
log_file.close()
|
|
98
99
|
|
|
99
100
|
|
|
100
|
-
CRITICAL_ENV_DEFAULTS: dict[str, str] = {
|
|
101
|
-
"DISABLE_AUTOUPDATER": "1", # do not let claude self-update mid-loop
|
|
102
|
-
"CLAUDE_CODE_EFFORT_LEVEL": "xhigh", # full effort, not default
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def merge_critical_envs(user_env: dict[str, str]) -> dict[str, str]:
|
|
107
|
-
"""Merge user env with CRITICAL_ENV_DEFAULTS — critical always wins."""
|
|
108
|
-
merged = dict(user_env)
|
|
109
|
-
merged.update(CRITICAL_ENV_DEFAULTS)
|
|
110
|
-
return merged
|
|
111
|
-
|
|
112
|
-
|
|
113
101
|
def install_sigterm_reaper(reaper: Callable[[], None]) -> object:
|
|
114
102
|
"""Install a SIGTERM handler that calls ``reaper()`` first.
|
|
115
103
|
|
|
116
|
-
R725 defense: when supervisor receives SIGTERM (e.g. systemctl stop,
|
|
117
|
-
kill), bash wrapper would otherwise respawn fresh runner
|
|
118
|
-
keeps running → two
|
|
119
|
-
swallow first commit's chat-room
|
|
104
|
+
R725 defense: when the supervisor receives SIGTERM (e.g. systemctl stop,
|
|
105
|
+
manual kill), the bash wrapper would otherwise respawn a fresh runner
|
|
106
|
+
while the old agent keeps running → two agent processes race on the same
|
|
107
|
+
git tree, the second commit can swallow the first commit's chat-room
|
|
108
|
+
entry. Reaper terminates pgroup first.
|
|
120
109
|
|
|
121
110
|
Returns the previous SIGTERM handler so caller can restore it.
|
|
122
111
|
"""
|
|
@@ -71,10 +71,16 @@ def _systemctl_user(*args: str) -> None:
|
|
|
71
71
|
# init / install / uninstall
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
def init(
|
|
74
|
+
def init(
|
|
75
|
+
work_dir: Path | None = None,
|
|
76
|
+
*,
|
|
77
|
+
preset: str = "claude",
|
|
78
|
+
force: bool = False,
|
|
79
|
+
commit: bool = True,
|
|
80
|
+
) -> InitResult:
|
|
75
81
|
if work_dir is None:
|
|
76
82
|
work_dir = Path.cwd()
|
|
77
|
-
return scaffold_project(work_dir, force=force, commit=commit)
|
|
83
|
+
return scaffold_project(work_dir, preset=preset, force=force, commit=commit)
|
|
78
84
|
|
|
79
85
|
|
|
80
86
|
def install(
|
|
@@ -15,7 +15,7 @@ from agent_runner.events import plugin_event_kinds
|
|
|
15
15
|
from agent_runner.hooks import plugin_context_enrichers
|
|
16
16
|
from agent_runner.monitor import plugin_detectors
|
|
17
17
|
|
|
18
|
-
PEEK_SCHEMA_VERSION = "1.
|
|
18
|
+
PEEK_SCHEMA_VERSION = "1.4"
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def cfg_from_args(args) -> Config:
|
|
@@ -8,6 +8,12 @@ from agent_runner.cli.common import emit, fail, work_dir_from_args
|
|
|
8
8
|
|
|
9
9
|
def add_parser(sub, parent) -> None:
|
|
10
10
|
p = sub.add_parser("init", parents=[parent], help="Scaffold agent-runner project files")
|
|
11
|
+
p.add_argument(
|
|
12
|
+
"--preset",
|
|
13
|
+
choices=["claude", "aider"],
|
|
14
|
+
default="claude",
|
|
15
|
+
help="Which agent CLI preset to scaffold (default: claude)",
|
|
16
|
+
)
|
|
11
17
|
p.add_argument("--force", action="store_true", help="Overwrite existing toml")
|
|
12
18
|
g = p.add_mutually_exclusive_group()
|
|
13
19
|
g.add_argument(
|
|
@@ -24,8 +30,8 @@ def add_parser(sub, parent) -> None:
|
|
|
24
30
|
def cmd(args) -> int:
|
|
25
31
|
work_dir = work_dir_from_args(args)
|
|
26
32
|
try:
|
|
27
|
-
result = api.init(work_dir, force=args.force, commit=args.commit)
|
|
28
|
-
except (FileExistsError, RuntimeError) as e:
|
|
33
|
+
result = api.init(work_dir, preset=args.preset, force=args.force, commit=args.commit)
|
|
34
|
+
except (FileExistsError, RuntimeError, FileNotFoundError) as e:
|
|
29
35
|
return fail(str(e))
|
|
30
36
|
emit(result, json_mode=getattr(args, "json", False))
|
|
31
37
|
return 0
|
|
@@ -15,6 +15,7 @@ class AgentConfig:
|
|
|
15
15
|
command: list[str]
|
|
16
16
|
prompt_arg_template: list[str]
|
|
17
17
|
name: str | None = None
|
|
18
|
+
env: dict[str, str] = field(default_factory=dict)
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@dataclass(frozen=True)
|
|
@@ -38,14 +39,16 @@ class VcsConfig:
|
|
|
38
39
|
stash_idempotency_s: int = 5
|
|
39
40
|
|
|
40
41
|
|
|
41
|
-
# Default auth-failure detection regex —
|
|
42
|
-
#
|
|
43
|
-
# providers override via [monitor].auth_fail_patterns.
|
|
42
|
+
# Default auth-failure detection regex — matches common OAuth/401/expired-session
|
|
43
|
+
# vocabularies. Presets override [monitor].auth_fail_hint per CLI.
|
|
44
44
|
_DEFAULT_AUTH_PATTERNS: list[str] = [
|
|
45
45
|
r"\b(oauth|unauthorized|401|api[_ ]key|"
|
|
46
46
|
r"auth(entication)?[_ -]?(failed|error|expired)|session.*expired)\b",
|
|
47
47
|
]
|
|
48
|
-
|
|
48
|
+
# Default auth-failure hint is empty — per-CLI hints come from preset files
|
|
49
|
+
# (agent_runner/presets/*.toml) which write `[monitor].auth_fail_hint` into the
|
|
50
|
+
# user's agent-runner.toml at scaffold time.
|
|
51
|
+
_DEFAULT_AUTH_HINT: str = ""
|
|
49
52
|
|
|
50
53
|
# Default allow-list of detector names whose ``stop_service`` action is honored.
|
|
51
54
|
# Plugin detectors must be added explicitly by the operator to opt them in.
|
|
@@ -94,6 +97,7 @@ def load_config(toml_path: Path) -> Config:
|
|
|
94
97
|
command=list(_require(agent_d, "command")),
|
|
95
98
|
prompt_arg_template=list(_require(agent_d, "prompt_arg_template")),
|
|
96
99
|
name=agent_d.get("name"),
|
|
100
|
+
env={str(k): str(v) for k, v in agent_d.get("env", {}).items()},
|
|
97
101
|
)
|
|
98
102
|
raw_work_dir = str(_require(raw, "runtime", "work_dir"))
|
|
99
103
|
work_dir = _expand_path(raw_work_dir, "").resolve()
|
|
@@ -13,7 +13,6 @@ from dataclasses import dataclass
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
|
-
from agent_runner.agent_runtime import CRITICAL_ENV_DEFAULTS
|
|
17
16
|
from agent_runner.config import Config
|
|
18
17
|
|
|
19
18
|
|
|
@@ -73,12 +72,13 @@ def catalog(cfg: Config) -> list[Defense]:
|
|
|
73
72
|
),
|
|
74
73
|
Defense(
|
|
75
74
|
name="critical_envs_injection",
|
|
76
|
-
value=
|
|
75
|
+
value=sorted(cfg.agent.env.keys()),
|
|
77
76
|
codifies=(
|
|
78
|
-
"
|
|
77
|
+
"Env injection via [agent.env] block — preset-supplied per CLI "
|
|
78
|
+
"(e.g. DISABLE_AUTOUPDATER for claude prevents mid-loop self-updates)"
|
|
79
79
|
),
|
|
80
80
|
guarded_by=None,
|
|
81
|
-
current_state="active",
|
|
81
|
+
current_state="active" if cfg.agent.env else "off",
|
|
82
82
|
),
|
|
83
83
|
Defense(
|
|
84
84
|
name="startup_smoke_check",
|
|
@@ -28,7 +28,7 @@ from agent_runner.api_types import (
|
|
|
28
28
|
ServiceStatus,
|
|
29
29
|
SystemMetrics,
|
|
30
30
|
)
|
|
31
|
-
from agent_runner.config import
|
|
31
|
+
from agent_runner.config import _DEFAULT_AUTH_PATTERNS
|
|
32
32
|
from agent_runner.context_store import read_json
|
|
33
33
|
from agent_runner.events import emit as emit_event
|
|
34
34
|
from agent_runner.events import now_iso_ms, parse_iso_ms
|
|
@@ -278,7 +278,7 @@ def detect_oauth_fail(
|
|
|
278
278
|
"matches": matches,
|
|
279
279
|
"window": total,
|
|
280
280
|
"threshold": threshold,
|
|
281
|
-
"hint": hint if hint is not None else
|
|
281
|
+
"hint": hint if hint is not None else "",
|
|
282
282
|
},
|
|
283
283
|
auto_action="stop_service",
|
|
284
284
|
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# agent-runner.toml — generated by `agent-runner init --preset aider`.
|
|
2
|
+
#
|
|
3
|
+
# Prereqs:
|
|
4
|
+
# - aider installed (`pipx install aider-chat`)
|
|
5
|
+
# - your model provider env var set (OPENAI_API_KEY / ANTHROPIC_API_KEY /
|
|
6
|
+
# DEEPSEEK_API_KEY / OPENROUTER_API_KEY / etc.) — `aider --models` lists
|
|
7
|
+
# detected providers.
|
|
8
|
+
|
|
9
|
+
[agent]
|
|
10
|
+
command = ["aider", "--yes-always", "--no-stream", "--analytics-disable"]
|
|
11
|
+
prompt_arg_template = ["--message", "{prompt}"]
|
|
12
|
+
name = "aider"
|
|
13
|
+
# [agent.env] omitted — aider needs no env injection.
|
|
14
|
+
|
|
15
|
+
[runtime]
|
|
16
|
+
work_dir = "."
|
|
17
|
+
log_dir = "~/.agent-runner/{project}/logs"
|
|
18
|
+
round_timeout_s = 1800
|
|
19
|
+
restart_delay_s = 3
|
|
20
|
+
|
|
21
|
+
[prompt]
|
|
22
|
+
file = "./prompts/main.md"
|
|
23
|
+
inject_context = true
|
|
24
|
+
|
|
25
|
+
[vcs]
|
|
26
|
+
orphan_action = "stash"
|
|
27
|
+
stash_idempotency_s = 5
|
|
28
|
+
|
|
29
|
+
[monitor]
|
|
30
|
+
auth_fail_hint = "Verify your model provider env var (OPENAI_API_KEY / ANTHROPIC_API_KEY / DEEPSEEK_API_KEY / etc.); run `aider --models` to list detected providers."
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# agent-runner.toml — generated by `agent-runner init --preset claude`.
|
|
2
|
+
|
|
3
|
+
[agent]
|
|
4
|
+
command = ["claude", "--model", "claude-opus-4-7",
|
|
5
|
+
"--dangerously-skip-permissions",
|
|
6
|
+
"--verbose", "--output-format", "stream-json"]
|
|
7
|
+
prompt_arg_template = ["-p", "{prompt}"]
|
|
8
|
+
name = "claude"
|
|
9
|
+
|
|
10
|
+
[agent.env]
|
|
11
|
+
DISABLE_AUTOUPDATER = "1"
|
|
12
|
+
CLAUDE_CODE_EFFORT_LEVEL = "xhigh"
|
|
13
|
+
|
|
14
|
+
[runtime]
|
|
15
|
+
work_dir = "."
|
|
16
|
+
log_dir = "~/.agent-runner/{project}/logs"
|
|
17
|
+
round_timeout_s = 1800
|
|
18
|
+
restart_delay_s = 3
|
|
19
|
+
|
|
20
|
+
[prompt]
|
|
21
|
+
file = "./prompts/main.md"
|
|
22
|
+
inject_context = true
|
|
23
|
+
|
|
24
|
+
[vcs]
|
|
25
|
+
orphan_action = "stash"
|
|
26
|
+
stash_idempotency_s = 5
|
|
27
|
+
|
|
28
|
+
[monitor]
|
|
29
|
+
auth_fail_hint = "Run `claude /login` on the supervisor host or refresh ANTHROPIC_API_KEY."
|
|
@@ -230,7 +230,7 @@ def _run_one_round_inner(cfg: Config) -> RoundResult:
|
|
|
230
230
|
prompt=prompt,
|
|
231
231
|
timeout_s=cfg.runtime.round_timeout_s,
|
|
232
232
|
log_path=log_path,
|
|
233
|
-
env_extra=
|
|
233
|
+
env_extra=dict(cfg.agent.env),
|
|
234
234
|
)
|
|
235
235
|
events.emit(
|
|
236
236
|
log_dir,
|
|
@@ -1,55 +1,31 @@
|
|
|
1
1
|
"""Project scaffold for `agent-runner init`.
|
|
2
2
|
|
|
3
3
|
Writes three files into a git repo:
|
|
4
|
-
agent-runner.toml — copy of
|
|
4
|
+
agent-runner.toml — copy of selected preset, project name substituted
|
|
5
5
|
prompts/main.md — neutral 8-line placeholder
|
|
6
6
|
.gitignore — append "logs/" if missing
|
|
7
7
|
|
|
8
|
+
Available presets ship as package data in `agent_runner/presets/*.toml`.
|
|
9
|
+
Currently: `claude`, `aider`.
|
|
10
|
+
|
|
8
11
|
Optionally commits in one step (default true via the CLI).
|
|
9
12
|
"""
|
|
10
13
|
|
|
11
14
|
from __future__ import annotations
|
|
12
15
|
|
|
16
|
+
import importlib.resources
|
|
13
17
|
import subprocess # noqa: TID251 — scaffold needs git for the commit step
|
|
14
18
|
from pathlib import Path
|
|
15
19
|
|
|
16
20
|
from agent_runner.api_types import InitResult
|
|
17
21
|
from agent_runner.vcs_state import is_git_repo
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
command = ["claude", "--model", "claude-opus-4-7",
|
|
26
|
-
"--dangerously-skip-permissions",
|
|
27
|
-
"--verbose", "--output-format", "stream-json"]
|
|
28
|
-
prompt_arg_template = ["-p", "{prompt}"]
|
|
29
|
-
# name = "claude" # optional — provider identifier; defaults to command[0]
|
|
30
|
-
|
|
31
|
-
[runtime]
|
|
32
|
-
work_dir = "."
|
|
33
|
-
log_dir = "~/.agent-runner/{project}/logs"
|
|
34
|
-
round_timeout_s = 1800
|
|
35
|
-
restart_delay_s = 3
|
|
36
|
-
|
|
37
|
-
[prompt]
|
|
38
|
-
file = "./prompts/main.md"
|
|
39
|
-
inject_context = true
|
|
40
|
-
|
|
41
|
-
# [phases] # optional — uncomment for phase rotation
|
|
42
|
-
# list = ["diverge", "converge", "refine"]
|
|
43
|
-
|
|
44
|
-
[vcs]
|
|
45
|
-
orphan_action = "stash"
|
|
46
|
-
stash_idempotency_s = 5
|
|
47
|
-
|
|
48
|
-
# [monitor] # optional — auto-stop policy overrides
|
|
49
|
-
# auto_stop_on = ["oauth_fail", "disk_critical"]
|
|
50
|
-
# disk_warning_pct = 90.0
|
|
51
|
-
# disk_critical_pct = 95.0
|
|
52
|
-
"""
|
|
23
|
+
|
|
24
|
+
def _load_preset(name: str) -> str:
|
|
25
|
+
"""Read preset TOML text; raises FileNotFoundError if name unknown."""
|
|
26
|
+
presets = importlib.resources.files("agent_runner.presets")
|
|
27
|
+
return (presets / f"{name}.toml").read_text(encoding="utf-8")
|
|
28
|
+
|
|
53
29
|
|
|
54
30
|
_PROMPT_TEMPLATE = """\
|
|
55
31
|
# Agent Prompt
|
|
@@ -68,7 +44,13 @@ auto-stash if you forget, but explicit commits with meaningful messages are bett
|
|
|
68
44
|
_GITIGNORE_LINE = "logs/"
|
|
69
45
|
|
|
70
46
|
|
|
71
|
-
def scaffold_project(
|
|
47
|
+
def scaffold_project(
|
|
48
|
+
work_dir: Path,
|
|
49
|
+
*,
|
|
50
|
+
preset: str = "claude",
|
|
51
|
+
force: bool,
|
|
52
|
+
commit: bool,
|
|
53
|
+
) -> InitResult:
|
|
72
54
|
if not is_git_repo(work_dir):
|
|
73
55
|
raise RuntimeError(f"{work_dir} is not a git working tree — run `git init` first")
|
|
74
56
|
|
|
@@ -83,7 +65,8 @@ def scaffold_project(work_dir: Path, *, force: bool, commit: bool) -> InitResult
|
|
|
83
65
|
files_created: list[Path] = []
|
|
84
66
|
|
|
85
67
|
project = work_dir.resolve().name or "default"
|
|
86
|
-
|
|
68
|
+
toml_text = _load_preset(preset).replace("{project}", project)
|
|
69
|
+
toml_path.write_text(toml_text)
|
|
87
70
|
files_created.append(toml_path)
|
|
88
71
|
|
|
89
72
|
prompt_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -119,4 +102,9 @@ def scaffold_project(work_dir: Path, *, force: bool, commit: bool) -> InitResult
|
|
|
119
102
|
)
|
|
120
103
|
committed = r.returncode == 0 # may fail if nothing changed
|
|
121
104
|
|
|
122
|
-
return InitResult(
|
|
105
|
+
return InitResult(
|
|
106
|
+
work_dir=work_dir,
|
|
107
|
+
files_created=files_created,
|
|
108
|
+
committed=committed,
|
|
109
|
+
preset=preset,
|
|
110
|
+
)
|