delegate-agent-cli 0.20.0__tar.gz → 0.22.0__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.
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/CHANGELOG.md +130 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/PKG-INFO +1 -1
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/cli-reference.md +28 -2
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/worktrees.md +22 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/pyproject.toml +1 -1
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/__init__.py +1 -1
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/capability_commands.py +19 -2
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/cli.py +5 -15
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/cli_parser.py +12 -24
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/command_errors.py +2 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/command_help.py +19 -2
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/config.py +3 -5
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/config_commands.py +7 -3
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/constants.py +24 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/describe_payload.py +5 -14
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/harness_discovery.py +272 -33
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/isolation.py +128 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/private_io.py +23 -5
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/profiles.py +1 -6
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/prompt_transport.py +18 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/reasoning.py +2 -31
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/redaction.py +38 -6
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/rendering.py +39 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/request_build.py +276 -125
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/run_registry.py +0 -2
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/run_status.py +6 -10
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/runner.py +42 -111
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/safe_workspace.py +0 -4
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/setup_commands.py +29 -16
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/snapshot_view.py +2 -2
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/structured_output.py +8 -7
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/workflows/commands.py +12 -12
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/workflows/registry.py +3 -4
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/workflows/runtime.py +53 -45
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/workflows/schema.py +5 -4
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/workflows/script.py +8 -4
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/worktree_commands.py +22 -4
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/worktree_execution.py +7 -15
- delegate_agent_cli-0.22.0/src/delegate_agent/worktree_gc.py +1129 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/worktree_mgmt.py +8 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/worktree_records.py +1 -3
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent_cli.egg-info/PKG-INFO +1 -1
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_capability_commands.py +70 -2
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_engine_argv.py +461 -1
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_execution_worktree_failure_cleanup.py +0 -3
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_execution_worktree_run.py +21 -2
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_harness_discovery.py +474 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_retention.py +0 -3
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_setup_commands.py +27 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_snapshot_redaction.py +14 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_worktree_prune_gc.py +1032 -1
- delegate_agent_cli-0.20.0/src/delegate_agent/worktree_gc.py +0 -577
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/CONTRIBUTING.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/LICENSE +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/MANIFEST.in +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/README.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/SECURITY.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/bin/delegate-profile-shim +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/bin/delegate.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/config.example.json +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/agent-setup.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/assets/delegate-agent-header.png +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/configuration.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/delegate-workflows.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/development.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/live-runtime.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/publishing-checklist.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/security-model.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/docs/troubleshooting.md +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/examples/task.claude.json +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/examples/task.codex.json +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/examples/task.cursor.json +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/examples/task.droid.json +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/examples/task.grok.json +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/examples/task.judge.json +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/examples/task.kimi.json +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/setup.cfg +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/archived_logs.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/argv_builders.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/argv_utils.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/bundled_models.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/child_failures.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/errors.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/git_utils.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/harness_events.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/inspection_commands.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/json_types.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/log_output.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/model_discovery.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/profile_commands.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/profile_guard.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/prompt_instructions.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/request_models.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/retention.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/run_metadata.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/run_output_commands.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/seatbelt.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/wait_cancel_commands.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/workflows/__init__.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/worktree_remove.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/worktree_summary.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/wsl.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent_cli.egg-info/SOURCES.txt +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent_cli.egg-info/dependency_links.txt +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent_cli.egg-info/entry_points.txt +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent_cli.egg-info/requires.txt +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent_cli.egg-info/top_level.txt +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_child_failures.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_codex_pure_sandbox.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_codex_thread_resilience.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_command_help.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_config_commands.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_delegate_help_cli.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_delegate_isolation.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_delegate_parser.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_delegate_validation.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_end_to_end_tracking.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_execution_argv_and_prompt.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_execution_dry_run.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_execution_worktree_preflight.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_harness_events.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_inspection_commands.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_kimi_commands.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_model_discovery.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_model_selection_wave1a.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_model_selection_wave1b.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_omp_read_only_behavior.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_packaging.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_profiles.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_pure_call.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_reasoning_capabilities.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_run_registry.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_runner_capture.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_safe_workspace_isolation.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_slash_passthrough.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_snapshot_rendering.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_snapshot_run_output.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_snapshot_view.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_structured_output.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_tracked_timeout.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_utility_modules.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_wait_cancel_commands.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_wave4_launch_features.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_workflow_commands.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_worktree_list_show.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_worktree_remove.py +0 -0
- {delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/tests/test_wsl_guardrails.py +0 -0
|
@@ -5,6 +5,134 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.22.0] - 2026-07-26
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Consolidated pure-call validation in `constants.validate_pure_call`, collapsed repeated `attempt`/`call` invocations in `runner.py` into local closures, eliminated `typing.Any` from `src`, and removed defensive `getattr`/`isinstance` hedges where the contract guarantees the type.
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
|
|
16
|
+
- Removed the write-only `ReasoningCapability.supported_efforts` field and unused `STATUS_FILTER_ACTIVE`/`STATUS_FILTER_RECENT` constants.
|
|
17
|
+
- Removed dead back-compat re-exports from `delegate_agent.cli`: `build_kimi_argv`, `build_opencode_argv`, `build_pi_argv`, `prefix_droid_safe_prompt`, `redacted_prompt_argv`, `KIMI_PROMPT_REDACTION`, `_grok_harness_bypass_enabled`, `_resolve_default_model`, and `block_external_symlinks`. These had no known consumers; the supported import surface is the CLI and `delegate --json` contracts.
|
|
18
|
+
|
|
19
|
+
## [0.21.0] - 2026-07-26
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Added `delegate worktree gc --all`, which scans the machine-wide worktree
|
|
24
|
+
pool for worktrees whose source repository no longer exists. Such worktrees
|
|
25
|
+
were previously unreachable by any `gc` invocation, because reaping is driven
|
|
26
|
+
by the run registry inside each source repository and that registry dies with
|
|
27
|
+
the repository. The scan adds no catalog or persistent state: it reads each
|
|
28
|
+
worktree's Git backlink directly, and treats a backlink whose target is gone
|
|
29
|
+
as the sole orphan signal. The scan removes nothing at all, at any depth: a
|
|
30
|
+
missing source repository makes a worktree's uncommitted state impossible to
|
|
31
|
+
inspect, so orphans and empty pool directories alike are reported for
|
|
32
|
+
deliberate manual cleanup. Because that output invites manual deletion, every
|
|
33
|
+
uncertainty resolves away from an orphan verdict: a pointer file that cannot
|
|
34
|
+
be read, a path comparison the filesystem cannot answer, an entry too recently
|
|
35
|
+
modified to have settled, and a directory whose name does not match the pool's
|
|
36
|
+
own fingerprint contract are all reported as warnings rather than orphans.
|
|
37
|
+
That last rule is what keeps a scan of an arbitrary root from labelling
|
|
38
|
+
somebody else's directories. `--pool PATH` scans a different pool root, which
|
|
39
|
+
reaches pools stranded by an earlier `worktrees.dataHome`.
|
|
40
|
+
- `capabilities refresh` and `setup` now name each harness on stderr as it is
|
|
41
|
+
probed, so a harness that hangs is identifiable rather than anonymous
|
|
42
|
+
silence. Progress output is suppressed under `--json`.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- A launch now fails instead of proceeding when the configured binary for a
|
|
47
|
+
harness identifies itself as a *different* known harness. Delegate re-probes
|
|
48
|
+
`--version` when a cached capability record has refused a run, and during
|
|
49
|
+
`capabilities refresh` and `setup` (see Fixed); when that banner positively
|
|
50
|
+
names another harness — a Codex path replaced in place by Grok, say — the run
|
|
51
|
+
stops with `harness_identity_mismatch` and exit code 2, naming the configured
|
|
52
|
+
harness, the harness that answered, and the selector path.
|
|
53
|
+
Previously the launch continued against that same binary with the same Codex
|
|
54
|
+
argv, sandbox flags, and approval policy, and only the cached capability
|
|
55
|
+
metadata was discarded. This fires solely on a banner that NAMES another known
|
|
56
|
+
harness, never on uncertainty: a probe that errors, times out, exceeds its
|
|
57
|
+
output bound, or prints an unbranded banner still fails open and launches on
|
|
58
|
+
the cached record. An unbranded shape — a bare date-and-hash build ID is the
|
|
59
|
+
clearest case — is deliberately not identification, because wrapper prefixes
|
|
60
|
+
are a supported configuration and refusing a legitimate launch costs more than
|
|
61
|
+
carrying a stale record until the next refresh. The selector is scrubbed of
|
|
62
|
+
credentials before it reaches the message or the diagnostics, since a wrapper
|
|
63
|
+
prefix can carry a token inline or after a flag. Dry runs do not probe and are
|
|
64
|
+
unaffected. The refusal also names the discovery cache file for the active
|
|
65
|
+
auth profile, in its `cachePath` diagnostic and in a next action, because
|
|
66
|
+
`capabilities refresh` cannot clear this state: a refresh keeps the
|
|
67
|
+
last-known-good record whenever a probe fails, which is correct for a flaky
|
|
68
|
+
probe and wrong here, where the record itself is the problem. Without the
|
|
69
|
+
path, a wrong identification would refuse every launch with no documented way
|
|
70
|
+
out short of editing configuration.
|
|
71
|
+
- A run whose cached capability record is discarded for version drift now says
|
|
72
|
+
so, naming `delegate capabilities refresh <engine>`. The record is dropped for
|
|
73
|
+
that run only and the superseded copy stays on disk, so every later launch
|
|
74
|
+
repeats the drop and loses the same discovery-sourced models and reasoning
|
|
75
|
+
levels — previously with no indication that a stale cache was the cause.
|
|
76
|
+
Selector drift stays silent, since `capabilities` already reports it under
|
|
77
|
+
`driftedHarnesses`.
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- Discovery now detects a harness that was upgraded in place. Cached capability
|
|
82
|
+
records were invalidated only by comparing the configured selector, so an
|
|
83
|
+
upgrade behind an unchanged selector left stale capabilities cached
|
|
84
|
+
indefinitely and a newly supported reasoning level was rejected until a manual
|
|
85
|
+
`capabilities refresh`. Delegate now re-probes `--version` for the single
|
|
86
|
+
harness it is about to use and compares it against the version already stored
|
|
87
|
+
in the record, then rebuilds the run against the refreshed picture. The probe
|
|
88
|
+
runs only once the cached record has already cost the run something, never on
|
|
89
|
+
the ordinary path, which therefore spawns no extra process at all — and that
|
|
90
|
+
matters because the probe costs 26-668ms, worst on the Node and Bun harnesses
|
|
91
|
+
that are already slow to start. Two things trigger it: an explicit
|
|
92
|
+
`--reasoning-effort` the record refuses, and a `defaultReasoningEffort` the
|
|
93
|
+
record silently drops. The second is the one most likely to go unnoticed,
|
|
94
|
+
since a configured default degrades to a warning rather than failing the
|
|
95
|
+
launch, and it is set once and then never typed again. A probe that cannot
|
|
96
|
+
repair the default leaves the run exactly as it was: a launch that succeeded
|
|
97
|
+
is never turned into a failure. The check fails open — a probe that errors,
|
|
98
|
+
times out, or prints a banner nothing recognizes keeps the cached record, so a
|
|
99
|
+
failing probe can never prevent a launch. A banner that positively identifies
|
|
100
|
+
a *different* known harness is the exception, and it aborts the launch rather
|
|
101
|
+
than merely invalidating the record — see Changed. Each banner is matched
|
|
102
|
+
against the expected harness's own pattern before anyone else's, so a banner
|
|
103
|
+
mentioning more than one tool is read as the harness that was asked for
|
|
104
|
+
instead of whichever pattern happens to be declared earlier. Banners are read
|
|
105
|
+
with escape sequences stripped, so a colorized version line no longer freezes
|
|
106
|
+
a record permanently. A cached selector whose binary no longer exists is also
|
|
107
|
+
treated as drift.
|
|
108
|
+
- Not covered, deliberately: a harness that *removes* a capability leaves its
|
|
109
|
+
cached record listing more than the harness now supports, which produces no
|
|
110
|
+
refusal and so no probe. The child rejects the effort it was handed, which is
|
|
111
|
+
loud; the one quiet case is Cursor's route table aiming a run at a superseded
|
|
112
|
+
model id. Catching either would cost a probe on every launch, which is the
|
|
113
|
+
trade this path exists to refuse. `capabilities refresh` remains the cure.
|
|
114
|
+
- A dry run never probes the harness for its version. Dry run documents that it
|
|
115
|
+
neither launches a child runtime nor requires the real child binary, and the
|
|
116
|
+
drift probe is a child runtime; it now runs only on a real launch, leaving dry
|
|
117
|
+
run with the two filesystem-local checks.
|
|
118
|
+
- A discovery cache carrying a newer schema than the running build is no longer
|
|
119
|
+
discarded and overwritten. It is left intact while the run degrades to
|
|
120
|
+
probing without persisting, so an older Delegate can no longer destroy a newer
|
|
121
|
+
cache. Losing that race mid-probe degrades the same way rather than failing the
|
|
122
|
+
command: `capabilities refresh` and `setup` both report `cacheWriteSkipped`,
|
|
123
|
+
`setup` additionally leaving `cacheState` at `unchanged`, and each exits
|
|
124
|
+
successfully, since only persistence was lost. The
|
|
125
|
+
schema is re-read immediately before the atomic replacement rather than before
|
|
126
|
+
the snapshot is serialized, which narrows the window in which a newer cache
|
|
127
|
+
could be published and then overwritten down to the `os.replace` call itself.
|
|
128
|
+
POSIX offers no compare-and-swap for a rename, so that last window remains.
|
|
129
|
+
- The test suite no longer writes into the developer's real
|
|
130
|
+
`~/.delegate/worktrees`. Tests exercising persistent-worktree runs created
|
|
131
|
+
real pooled worktrees over temporary source repositories and orphaned them
|
|
132
|
+
permanently, because `worktrees_data_home` falls through to the user's home
|
|
133
|
+
directory with no seam a test could redirect. `HOME` is now redirected at test
|
|
134
|
+
collection time, which reaches every such resolver at once.
|
|
135
|
+
|
|
8
136
|
## [0.20.0] - 2026-07-25
|
|
9
137
|
|
|
10
138
|
### Added
|
|
@@ -631,6 +759,8 @@ Usage-audit fix wave: 82 sessions and 1,241 delegate invocations from one week o
|
|
|
631
759
|
|
|
632
760
|
- Releases before 0.1.3 predate this changelog.
|
|
633
761
|
|
|
762
|
+
[0.22.0]: https://github.com/treygoff24/delegate-agent/compare/v0.21.0...v0.22.0
|
|
763
|
+
[0.21.0]: https://github.com/treygoff24/delegate-agent/compare/v0.20.0...v0.21.0
|
|
634
764
|
[0.20.0]: https://github.com/treygoff24/delegate-agent/compare/v0.19.0...v0.20.0
|
|
635
765
|
[0.19.0]: https://github.com/treygoff24/delegate-agent/compare/v0.18.0...v0.19.0
|
|
636
766
|
[0.18.0]: https://github.com/treygoff24/delegate-agent/compare/v0.17.0...v0.18.0
|
|
@@ -803,7 +803,31 @@ records independently. If one current probe fails, its last-known-good record
|
|
|
803
803
|
is retained and the refresh response lists that harness in `staleHarnesses`.
|
|
804
804
|
If the configured executable selector changes, cached reads mark that harness
|
|
805
805
|
stale and ordinary launches ignore only that harness's record until refresh.
|
|
806
|
-
Cached reads never run `--version` merely to test staleness
|
|
806
|
+
Cached reads never run `--version` merely to test staleness, and neither does an
|
|
807
|
+
ordinary launch: a cached record can only be short of what the harness now
|
|
808
|
+
supports, never claim more, so one that satisfies the run is used as-is.
|
|
809
|
+
|
|
810
|
+
A launch re-probes `--version` for its own harness only when the cached record
|
|
811
|
+
has already cost the run something: it refused an explicit `--reasoning-effort`,
|
|
812
|
+
or it silently dropped a configured `defaultReasoningEffort` (a configured
|
|
813
|
+
default degrades to a warning instead of failing the launch, so nothing else
|
|
814
|
+
would signal it). An in-place upgrade behind an unchanged selector is the reason
|
|
815
|
+
either can be wrong, so the probe compares the live banner against the version
|
|
816
|
+
stored in the record, and a mismatch drops the record and rebuilds the run
|
|
817
|
+
against config and bundled capabilities. If the refreshed picture cannot satisfy
|
|
818
|
+
a configured default either, the run proceeds as it already had — a probe never
|
|
819
|
+
turns a launch that succeeded into one that fails.
|
|
820
|
+
|
|
821
|
+
The reverse case is not covered: a harness that *removes* a capability leaves
|
|
822
|
+
its record listing more than the harness now supports, which produces no refusal
|
|
823
|
+
and therefore no probe. Run `capabilities refresh` after an upgrade that narrows
|
|
824
|
+
what a harness accepts. The result carries a warning naming
|
|
825
|
+
`capabilities refresh <engine>`, because the superseded record stays on disk
|
|
826
|
+
until a refresh rewrites it. The probe fails open — an error, a timeout, or an
|
|
827
|
+
unrecognized banner leaves the original refusal standing — except when the
|
|
828
|
+
banner positively names a *different* known harness, which fails the run with
|
|
829
|
+
`harness_identity_mismatch` rather than handing one harness's argv and sandbox
|
|
830
|
+
policy to another. Dry runs never probe at all.
|
|
807
831
|
|
|
808
832
|
All automatic probes run with `shell=False`, stdin closed, a neutral temporary
|
|
809
833
|
working directory, bounded output, and a timeout. They pass the active profile
|
|
@@ -1079,11 +1103,13 @@ delegate worktree show <handle>
|
|
|
1079
1103
|
delegate worktree show --latest HARNESS
|
|
1080
1104
|
delegate worktree remove <handle|--group NAME> [--discard-uncommitted] [--force-branch] [--force] [--keep-branch]
|
|
1081
1105
|
delegate worktree prune [--merged] [--older-than DAYS] [--harness HARNESS] [--group NAME] [--include-detached] [--dry-run] [--discard-uncommitted] [--force-branch] [--force]
|
|
1082
|
-
delegate worktree gc [--dry-run]
|
|
1106
|
+
delegate worktree gc [--dry-run] [--all] [--pool PATH]
|
|
1083
1107
|
```
|
|
1084
1108
|
|
|
1085
1109
|
`worktree show --latest HARNESS` selects the latest persistent worktree for the harness, not merely the latest run overall. `worktree list` JSON includes a `summary` with status counts, registry drift counts, warning counts, `autoPruneMode`, and whether the returned operation was read-only; `summary.totalPersistentWorktrees` is always registry-wide, while `allStatusCounts` is scoped to the `--harness` / `--group` filters (pre-status-filter) and `statusCounts` to the visible entries. `worktree remove --group NAME` removes all matching persistent worktrees with the same safety checks as single-handle removal. `worktree prune --group NAME` limits prune candidates to the group. `worktree gc --dry-run` reports `wouldPruneSourceRoots` and structured orphan reasons; `worktree gc` JSON also includes `mode`, `effects`, per-entry `action`, and orphan `safeAction` fields. `gc` never deletes worktree directories.
|
|
1086
1110
|
|
|
1111
|
+
`worktree gc --all` additionally scans the machine-wide worktree pool (`worktrees.dataHome`, default `~/.delegate/worktrees`) and adds a `pool` object with `dataHome`, `scannedWorktrees`, `orphans`, `emptyFingerprintDirs`, and `warnings`. A pool orphan is a pooled worktree whose Git admin directory no longer serves it, classified `source_root_missing` or `worktree_metadata_missing` with the same `safeAction` vocabulary as registry orphans, plus `worktreePath`, `fingerprint`, `sourceGitRoot`, and `gitdir` (null when the backlink is absent). This is the only way to see worktrees whose source repository was deleted along with its `.delegate/` registry, so `--all` is accepted outside a Delegate registry. The pool scan removes nothing at all: orphans are reported because uncommitted work in them is undetectable once the source repository is gone, and empty fingerprint directories are reported for the same hand-clearing rather than reclaimed. Only a settled absence produces an orphan — metadata that could not be read (a symlinked, non-regular, oversized, or unreadable `.git` entry, or an admin directory that could not be inspected) is treated as live and reported in `pool.warnings`, and so is any pool entry modified within the last 15 minutes, since worktree creation writes the directory before its metadata and a concurrent scan would otherwise call a worktree being born an orphan (`worktree_unsettled`, `fingerprint_dir_unsettled`). `pool.warnings` also covers first-level directories whose names are not repository fingerprints, fingerprint directories that hold loose files rather than being empty, and unlistable fingerprint directories. `--pool PATH` scans a specific pool root instead of the configured one, which finds pools left behind by an earlier `worktrees.dataHome`; an explicit `--pool` path that is missing, not a directory, or unreadable is an `invalid_pool_root` error.
|
|
1112
|
+
|
|
1087
1113
|
List/show entry fields include `branchMergedIntoSource` (branch graph only), `mergedIntoSource` (backward-compatible branch graph state), `fullyIntegrated` (branch merged and worktree clean), `hasUncommittedChanges`, `integrationStatus`, and `uncommittedChangesIntegrated`. `workSummary` is included on `worktree show` and run completion payloads when Delegate can inspect the worktree; `worktree list` omits the deep summary for responsiveness. Consumers that need safe retirement should require `fullyIntegrated: true` or inspect `integrationStatus`. When `integrationStatus` is `branch-merged-worktree-dirty`, merge/cherry-pick suggestions are suppressed because commit integration is already complete and only uncommitted edits remain.
|
|
1088
1114
|
|
|
1089
1115
|
Unknown persistent-worktree handles return suggestions scoped to persistent
|
|
@@ -216,6 +216,28 @@ delegate worktree gc
|
|
|
216
216
|
|
|
217
217
|
In dry-run mode, `gc` reports `wouldPruneSourceRoots` and classifies un-prunable worktrees with reasons such as `source_root_missing`, `worktree_metadata_missing`, `branch_missing`, and `detached_backlink`. Without `--dry-run`, it may update Delegate registry status (for example, marking missing paths as `missing` or inconsistent metadata as `unknown`) and may run `git worktree prune` to clean Git administrative metadata for already-missing paths, but it does not delete worktree directories. JSON output includes an `effects` object that makes those mutation boundaries explicit.
|
|
218
218
|
|
|
219
|
+
## Find pooled worktrees whose repository is gone
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
delegate worktree gc --all
|
|
223
|
+
delegate worktree gc --all --dry-run
|
|
224
|
+
delegate worktree gc --pool ~/.delegate/worktrees
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Persistent worktrees live in a machine-global pool under `worktrees.dataHome` (default `~/.delegate/worktrees`), but they are tracked in a run registry inside the source repository. Delete the source repository and its registry goes with it, leaving a pooled worktree that no per-repository `gc` can reach. `--all` finds these by walking the pool directly and reading each worktree's `.git` backlink file as text — the ordinary Git-based checks cannot classify them, because every `git` command fails inside a worktree whose repository is gone. Because a live worktree always has a live backlink, the walk is safe to run across every repository on the machine at once. `--all` works outside a Delegate registry for the same reason.
|
|
228
|
+
|
|
229
|
+
The scan adds a `pool` object to the JSON with `dataHome`, `scannedWorktrees`, `orphans`, `emptyFingerprintDirs`, and `warnings`. Each orphan carries `worktreePath`, `fingerprint`, the recovered `sourceGitRoot` (null when the layout does not reveal it), `gitdir` (null when the worktree has no `.git` backlink), a `reason`, and a `safeAction`.
|
|
230
|
+
|
|
231
|
+
A worktree counts as an orphan only when two things are settled. Its `.git` backlink must be definitely gone or definitely unusable — absent, or readable and holding no usable `gitdir:` line. And its Git admin directory must no longer serve it: the admin directory must exist and hold a `gitdir` backfile naming this worktree. Anything that merely *could not be checked* — a `.git` entry that is a symlink, a directory, a FIFO, oversized, or unreadable; an admin directory that could not be inspected — is reported as live and surfaced in `warnings` with reason `worktree_metadata_unverifiable`. A false orphan invites someone to delete real work by hand, while a false live only under-reports, so every unresolved case resolves toward live.
|
|
232
|
+
|
|
233
|
+
Absence also has to have lasted. Creating a persistent worktree is several steps — Delegate makes the fingerprint directory, then `git worktree add` creates the worktree directory, its `.git` pointer, and the admin directory's backfile — and a scan that lands inside that window sees exactly what a deleted repository leaves behind. Since the scan runs in a different process from the one creating the worktree, and often for a different repository, it has nothing to wait on and judges by age instead: a pool entry modified within the last 15 minutes is reported in `warnings` (`worktree_unsettled`, or `fingerprint_dir_unsettled` for an empty directory) and classified on a later run. An entry whose age cannot be read at all is treated the same way. This delays reporting a genuine orphan by one run at most, since a real orphan's metadata never comes back.
|
|
234
|
+
|
|
235
|
+
Only first-level directories named like a repository fingerprint (12 lowercase hex characters, as written by `plan_worktree_path`) are treated as Delegate's. The root is whatever you passed, so anything else is skipped without being descended, and is summarized in `warnings` with reason `not_a_pool_fingerprint_dir` — pointing `--pool` at an ordinary directory yields warnings, never orphans or cleanup candidates.
|
|
236
|
+
|
|
237
|
+
The pool scan removes nothing. With the source repository gone there is no way to tell whether an orphan holds uncommitted work, so removal is a deliberate manual decision — inspect the reported path, rescue anything you need, then delete it yourself. Empty fingerprint directories, left behind when a fingerprint's last worktree was removed, are reported in `emptyFingerprintDirs` for the same hand-clearing; `gc` does not remove those either. "Empty" means the directory holds nothing at all: a fingerprint directory with loose files in it is reported in `warnings` (`fingerprint_dir_not_empty`) instead. Fingerprint directories that could not be listed also appear in `warnings` rather than being reported as empty.
|
|
238
|
+
|
|
239
|
+
Use `--pool PATH` to scan a pool root that is no longer the configured `dataHome`. An explicitly named `--pool` path must exist and be a readable directory; anything else is an `invalid_pool_root` error rather than a successful scan of nothing, including a root that becomes unreadable between validation and the walk. A configured `dataHome` that does not exist yet is not an error — it just means no pool has been created — and is reported in `pool.warnings`.
|
|
240
|
+
|
|
219
241
|
## Security boundary
|
|
220
242
|
|
|
221
243
|
Worktree isolation is source-checkout isolation, not a full sandbox. The child process may still use credentials, network access, external tools, and absolute paths according to its runtime and host permissions. See [Security model](security-model.md).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "delegate-agent-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.22.0"
|
|
8
8
|
description = "A tiny CLI for delegating bounded agent tasks across coding-agent runtimes."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
{delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/capability_commands.py
RENAMED
|
@@ -138,9 +138,12 @@ def _refresh_payload(
|
|
|
138
138
|
*,
|
|
139
139
|
profile: profiles.ProfileResolution,
|
|
140
140
|
engines: tuple[str, ...] | None = None,
|
|
141
|
+
progress: harness_discovery.ProgressCallback | None = None,
|
|
141
142
|
) -> JsonObject:
|
|
142
143
|
try:
|
|
143
|
-
result = harness_discovery.refresh_discovery(
|
|
144
|
+
result = harness_discovery.refresh_discovery(
|
|
145
|
+
config, profile=profile, engines=engines, progress=progress
|
|
146
|
+
)
|
|
144
147
|
except OSError as exc:
|
|
145
148
|
raise CapabilitiesError(
|
|
146
149
|
"capability_refresh_failed",
|
|
@@ -215,6 +218,8 @@ def _refresh_payload(
|
|
|
215
218
|
}
|
|
216
219
|
if drifted_harnesses:
|
|
217
220
|
payload["driftedHarnesses"] = drifted_harnesses
|
|
221
|
+
if result.get("futureSchemaCache") is True:
|
|
222
|
+
payload["cacheWriteSkipped"] = harness_discovery.FUTURE_SCHEMA_CACHE_WARNING
|
|
218
223
|
_add_legacy_cache_fields(payload, legacy_path, reasoning_payload)
|
|
219
224
|
return redaction.scrub_public_projection(payload)
|
|
220
225
|
|
|
@@ -229,6 +234,7 @@ def emit(
|
|
|
229
234
|
profile: profiles.ProfileResolution | None = None,
|
|
230
235
|
discovery: JsonObject | None = None,
|
|
231
236
|
stdout: TextIO,
|
|
237
|
+
stderr: TextIO | None = None,
|
|
232
238
|
) -> int:
|
|
233
239
|
active_profile = profile or profiles.resolve_active_profile(
|
|
234
240
|
config,
|
|
@@ -236,8 +242,16 @@ def emit(
|
|
|
236
242
|
cli_override=auth_profile_override,
|
|
237
243
|
)
|
|
238
244
|
if command.refresh:
|
|
245
|
+
# JSON callers get a clean stdout document and a quiet stderr; the
|
|
246
|
+
# per-harness line exists for a human watching a cold refresh.
|
|
239
247
|
payload = _refresh_payload(
|
|
240
|
-
config,
|
|
248
|
+
config,
|
|
249
|
+
workspace,
|
|
250
|
+
profile=active_profile,
|
|
251
|
+
engines=command.engines,
|
|
252
|
+
progress=(
|
|
253
|
+
None if command.json_mode else harness_discovery.stderr_probe_progress(stderr)
|
|
254
|
+
),
|
|
241
255
|
)
|
|
242
256
|
else:
|
|
243
257
|
payload = capabilities_payload(
|
|
@@ -252,6 +266,9 @@ def emit(
|
|
|
252
266
|
delegate_rendering.print_json(payload, stdout)
|
|
253
267
|
else:
|
|
254
268
|
print(f"reasoning capabilities: {payload['cachePath']}", file=stdout)
|
|
269
|
+
skipped = payload.get("cacheWriteSkipped")
|
|
270
|
+
if isinstance(skipped, str):
|
|
271
|
+
print(skipped, file=stdout)
|
|
255
272
|
drifted = payload.get("driftedHarnesses")
|
|
256
273
|
if isinstance(drifted, list) and drifted:
|
|
257
274
|
print(
|
|
@@ -40,20 +40,14 @@ from delegate_agent import runner as delegate_runner
|
|
|
40
40
|
from delegate_agent.argv_builders import ( # noqa: F401 # re-exported for tests / back-compat
|
|
41
41
|
SAFE_REVIEW_PREFIX_BY_ENGINE,
|
|
42
42
|
_claude_harness_bypass_enabled,
|
|
43
|
-
_grok_harness_bypass_enabled,
|
|
44
43
|
build_claude_argv,
|
|
45
44
|
build_codex_argv,
|
|
46
45
|
build_cursor_argv,
|
|
47
46
|
build_devin_argv,
|
|
48
47
|
build_droid_argv,
|
|
49
48
|
build_grok_argv,
|
|
50
|
-
build_kimi_argv,
|
|
51
49
|
build_omp_argv,
|
|
52
|
-
build_opencode_argv,
|
|
53
|
-
build_pi_argv,
|
|
54
50
|
prefix_cursor_safe_prompt,
|
|
55
|
-
prefix_droid_safe_prompt,
|
|
56
|
-
redacted_prompt_argv,
|
|
57
51
|
)
|
|
58
52
|
from delegate_agent.argv_utils import public_argv
|
|
59
53
|
from delegate_agent.cli_parser import ( # noqa: F401 # re-exported for tests / back-compat
|
|
@@ -98,7 +92,6 @@ from delegate_agent.prompt_transport import ( # noqa: F401 # CURSOR_PROMPT_RED
|
|
|
98
92
|
DEVIN_AGENT_CONFIG_DISPLAY,
|
|
99
93
|
DROID_PROMPT_FILE_ARG_PLACEHOLDER,
|
|
100
94
|
DROID_PROMPT_FILE_DISPLAY,
|
|
101
|
-
KIMI_PROMPT_REDACTION,
|
|
102
95
|
PROMPT_FILE_ARG_PLACEHOLDER,
|
|
103
96
|
PROMPT_FILE_DISPLAY,
|
|
104
97
|
PROMPT_TRANSPORT_ARGV,
|
|
@@ -109,7 +102,6 @@ from delegate_agent.request_build import ( # noqa: F401 # re-exported for test
|
|
|
109
102
|
CALL_TEMP_CWD_PLACEHOLDER,
|
|
110
103
|
RUN_INPUT_KEYS,
|
|
111
104
|
_load_input_json_object,
|
|
112
|
-
_resolve_default_model,
|
|
113
105
|
build_request,
|
|
114
106
|
effective_prompt,
|
|
115
107
|
load_config,
|
|
@@ -136,7 +128,6 @@ from delegate_agent.safe_workspace import ( # noqa: F401 # re-exported for tes
|
|
|
136
128
|
SAFE_BLOCKED_SYMLINK_PLACEHOLDER,
|
|
137
129
|
SAFE_EXTERNAL_SYMLINK_WARNING_PREFIX,
|
|
138
130
|
SAFE_UNBORN_GIT_WARNING,
|
|
139
|
-
block_external_symlinks,
|
|
140
131
|
cleanup_safe_isolated_workspace,
|
|
141
132
|
create_directory_safe_workspace,
|
|
142
133
|
create_git_safe_workspace,
|
|
@@ -332,7 +323,6 @@ def dry_run_payload(request: Request) -> JsonObject:
|
|
|
332
323
|
if request.profile_resolution.name is not None:
|
|
333
324
|
payload["profileEnv"] = redaction.redact_env_map(request.profile_resolution.env)
|
|
334
325
|
|
|
335
|
-
# Structured isolation fields from the isolation context.
|
|
336
326
|
if request.isolation_context is not None:
|
|
337
327
|
ctx = request.isolation_context
|
|
338
328
|
|
|
@@ -515,7 +505,6 @@ def make_run_context(
|
|
|
515
505
|
if request.isolation_context is not None
|
|
516
506
|
else False
|
|
517
507
|
)
|
|
518
|
-
# Extract isolation metadata from the isolation context.
|
|
519
508
|
iso_ctx = request.isolation_context
|
|
520
509
|
if iso_ctx is not None:
|
|
521
510
|
isolation_mode = iso_ctx.isolation_mode
|
|
@@ -860,7 +849,7 @@ def execute_request(
|
|
|
860
849
|
profiles.preflight_codex_request(request, config.get("codex", {}))
|
|
861
850
|
ctx = request.isolation_context
|
|
862
851
|
|
|
863
|
-
#
|
|
852
|
+
# Persistent worktree runs (work + worktree) hand off to worktree_execution.
|
|
864
853
|
if ctx is not None and ctx.isolation_lifecycle == "persistent":
|
|
865
854
|
try:
|
|
866
855
|
return worktree_execution.execute_persistent_worktree(
|
|
@@ -1022,7 +1011,6 @@ def pre_read_run_json_for_config(
|
|
|
1022
1011
|
if json_cwd is not None and not isinstance(json_cwd, str):
|
|
1023
1012
|
raise DelegateError("invalid_cwd", "cwd must be a string.")
|
|
1024
1013
|
|
|
1025
|
-
# Reject explicit null isolation in the JSON pre-read.
|
|
1026
1014
|
if "isolation" in raw and raw["isolation"] is None:
|
|
1027
1015
|
raise DelegateError(
|
|
1028
1016
|
"invalid_isolation",
|
|
@@ -1087,6 +1075,7 @@ def main(
|
|
|
1087
1075
|
json_mode=global_options.json_mode,
|
|
1088
1076
|
auth_profile_override=global_options.auth_profile,
|
|
1089
1077
|
stdout=stdout,
|
|
1078
|
+
stderr=stderr,
|
|
1090
1079
|
)
|
|
1091
1080
|
|
|
1092
1081
|
# For run --input-json, pre-read the JSON to discover config from the
|
|
@@ -1177,6 +1166,7 @@ def main(
|
|
|
1177
1166
|
profile=discovery_profile,
|
|
1178
1167
|
discovery=discovery_snapshot,
|
|
1179
1168
|
stdout=stdout,
|
|
1169
|
+
stderr=stderr,
|
|
1180
1170
|
)
|
|
1181
1171
|
|
|
1182
1172
|
if parsed.subcommand in {
|
|
@@ -1278,8 +1268,8 @@ def main(
|
|
|
1278
1268
|
DelegateError(
|
|
1279
1269
|
exc.error,
|
|
1280
1270
|
exc.message,
|
|
1281
|
-
diagnostics=
|
|
1282
|
-
next_actions=
|
|
1271
|
+
diagnostics=exc.diagnostics,
|
|
1272
|
+
next_actions=exc.next_actions,
|
|
1283
1273
|
),
|
|
1284
1274
|
json_mode,
|
|
1285
1275
|
stdout,
|
|
@@ -32,10 +32,9 @@ from delegate_agent.constants import (
|
|
|
32
32
|
ENGINES_PROSE,
|
|
33
33
|
KNOWN_ENGINES,
|
|
34
34
|
MODELESS_ENGINES,
|
|
35
|
-
PURE_CALL_ENGINES,
|
|
36
35
|
VALID_MODES,
|
|
37
|
-
pure_call_supported,
|
|
38
36
|
validate_mode,
|
|
37
|
+
validate_pure_call,
|
|
39
38
|
)
|
|
40
39
|
from delegate_agent.errors import DelegateError
|
|
41
40
|
from delegate_agent.request_models import (
|
|
@@ -647,10 +646,6 @@ def corrected_global_argv(argv: list[str]) -> list[str]:
|
|
|
647
646
|
return [*globals_out, *rest]
|
|
648
647
|
|
|
649
648
|
|
|
650
|
-
def corrected_global_command(argv: list[str]) -> str:
|
|
651
|
-
return _shell_command(corrected_global_argv(argv))
|
|
652
|
-
|
|
653
|
-
|
|
654
649
|
def raise_misplaced_global_option(message: str, argv: list[str] | None = None) -> NoReturn:
|
|
655
650
|
guidance = (
|
|
656
651
|
"Move global options before the subcommand "
|
|
@@ -1320,22 +1315,7 @@ def _validate_pure_options(
|
|
|
1320
1315
|
return
|
|
1321
1316
|
if mode != "call":
|
|
1322
1317
|
raise DelegateError("unsupported_pure_call", "--pure only applies to call mode.")
|
|
1323
|
-
|
|
1324
|
-
raise DelegateError(
|
|
1325
|
-
"pure_conflicts_group",
|
|
1326
|
-
"--pure cannot be combined with --group; pure call is a stateless one-hop completion.",
|
|
1327
|
-
)
|
|
1328
|
-
if read_only:
|
|
1329
|
-
raise DelegateError(
|
|
1330
|
-
"pure_conflicts_read_only", "--pure cannot be combined with --read-only."
|
|
1331
|
-
)
|
|
1332
|
-
if not pure_call_supported(engine):
|
|
1333
|
-
supported = ", ".join(PURE_CALL_ENGINES)
|
|
1334
|
-
raise DelegateError(
|
|
1335
|
-
"unsupported_pure_call",
|
|
1336
|
-
f"{engine} does not support pure call mode.",
|
|
1337
|
-
next_actions=[f"Use --pure with one of: {supported}."],
|
|
1338
|
-
)
|
|
1318
|
+
validate_pure_call(engine, pure=pure, read_only=read_only, group=group)
|
|
1339
1319
|
|
|
1340
1320
|
|
|
1341
1321
|
def parse_snapshot(rest: list[str], json_mode: bool, cwd: str | None) -> ParsedCommand:
|
|
@@ -1982,6 +1962,10 @@ def _require_option_value(rest: list[str], index: int, option: str) -> str:
|
|
|
1982
1962
|
|
|
1983
1963
|
WorktreeOptionSpec = tuple[str, str]
|
|
1984
1964
|
|
|
1965
|
+
# Parsed worktree option values: flags store True, "str"/"group"/"status" kinds
|
|
1966
|
+
# store the validated string, and the int kinds store a parsed int.
|
|
1967
|
+
WorktreeOptionValue = str | int | bool
|
|
1968
|
+
|
|
1985
1969
|
WORKTREE_OPTION_SPECS: dict[str, dict[str, WorktreeOptionSpec]] = {
|
|
1986
1970
|
"list": {
|
|
1987
1971
|
"--harness": ("str", "harness"),
|
|
@@ -2013,12 +1997,14 @@ WORKTREE_OPTION_SPECS: dict[str, dict[str, WorktreeOptionSpec]] = {
|
|
|
2013
1997
|
},
|
|
2014
1998
|
"gc": {
|
|
2015
1999
|
"--dry-run": ("flag", "dry_run"),
|
|
2000
|
+
"--all": ("flag", "all_pools"),
|
|
2001
|
+
"--pool": ("str", "pool"),
|
|
2016
2002
|
},
|
|
2017
2003
|
}
|
|
2018
2004
|
|
|
2019
2005
|
|
|
2020
2006
|
def _apply_worktree_option(
|
|
2021
|
-
options: dict[str,
|
|
2007
|
+
options: dict[str, WorktreeOptionValue],
|
|
2022
2008
|
args: list[str],
|
|
2023
2009
|
index: int,
|
|
2024
2010
|
option: str,
|
|
@@ -2067,7 +2053,7 @@ def parse_worktree(rest: list[str], json_mode: bool, cwd: str | None) -> ParsedC
|
|
|
2067
2053
|
return help_command(json_mode, topic)
|
|
2068
2054
|
if action not in WORKTREE_OPTION_SPECS:
|
|
2069
2055
|
raise DelegateError("unknown_worktree_action", f"Unknown worktree action: {action}")
|
|
2070
|
-
options: dict[str,
|
|
2056
|
+
options: dict[str, WorktreeOptionValue] = {}
|
|
2071
2057
|
positional: list[str] = []
|
|
2072
2058
|
i = 0
|
|
2073
2059
|
action_specs = WORKTREE_OPTION_SPECS[action]
|
|
@@ -2090,6 +2076,8 @@ def parse_worktree(rest: list[str], json_mode: bool, cwd: str | None) -> ParsedC
|
|
|
2090
2076
|
raise DelegateError(
|
|
2091
2077
|
"unexpected_argument", f"worktree {action} does not accept positional arguments."
|
|
2092
2078
|
)
|
|
2079
|
+
if action == "gc" and options.get("pool") == "":
|
|
2080
|
+
raise DelegateError("invalid_option_value", "worktree gc --pool requires a path.")
|
|
2093
2081
|
if action == "show":
|
|
2094
2082
|
if options.get("latest_harness") is not None:
|
|
2095
2083
|
if positional:
|
|
@@ -1408,19 +1408,36 @@ COMMAND_SPECS: dict[str, CommandSpec] = {
|
|
|
1408
1408
|
"worktree gc": CommandSpec(
|
|
1409
1409
|
name="worktree gc",
|
|
1410
1410
|
summary="Garbage-collect orphaned worktree metadata and stale registry entries.",
|
|
1411
|
-
usage=("delegate [--cwd PATH] [--json] worktree gc [--dry-run]",),
|
|
1411
|
+
usage=("delegate [--cwd PATH] [--json] worktree gc [--dry-run] [--all] [--pool PATH]",),
|
|
1412
1412
|
options=(
|
|
1413
1413
|
OptionSpec(
|
|
1414
1414
|
"--dry-run",
|
|
1415
1415
|
None,
|
|
1416
1416
|
"Report source roots that would be pruned and un-prunable worktree reasons without changing anything.",
|
|
1417
1417
|
),
|
|
1418
|
+
OptionSpec(
|
|
1419
|
+
"--all",
|
|
1420
|
+
None,
|
|
1421
|
+
"Also scan the machine-wide worktree pool for worktrees whose source repository is gone. Reports them; never removes them.",
|
|
1422
|
+
),
|
|
1423
|
+
OptionSpec(
|
|
1424
|
+
"--pool",
|
|
1425
|
+
"PATH",
|
|
1426
|
+
"Scan this worktree pool root instead of the configured worktrees.dataHome (finds pools left behind by an earlier dataHome). The path must exist and be a readable directory.",
|
|
1427
|
+
),
|
|
1418
1428
|
),
|
|
1419
1429
|
examples=(
|
|
1420
1430
|
"delegate worktree gc",
|
|
1421
1431
|
"delegate worktree gc --dry-run",
|
|
1432
|
+
"delegate worktree gc --all",
|
|
1433
|
+
"delegate worktree gc --all --dry-run",
|
|
1434
|
+
),
|
|
1435
|
+
notes=(
|
|
1436
|
+
"GC reconciles registry and Git metadata; it never removes worktree paths.",
|
|
1437
|
+
"--all works outside a Delegate registry, since pooled worktrees outlive the repositories that created them.",
|
|
1438
|
+
"Pool orphans are reported, never removed: their uncommitted state cannot be inspected once the source repository is gone.",
|
|
1439
|
+
"The pool scan removes nothing at all, including empty pool directories — it reports them so you can clear them yourself.",
|
|
1422
1440
|
),
|
|
1423
|
-
notes=("GC reconciles registry and Git metadata; it never removes worktree paths.",),
|
|
1424
1441
|
see_also=("worktree list", "worktree prune", "worktree remove"),
|
|
1425
1442
|
unsupported_global_options=("--isolation", "--auth-profile"),
|
|
1426
1443
|
),
|
|
@@ -821,11 +821,9 @@ def _reject_opencode_flag_like_value(
|
|
|
821
821
|
def _reject_pi_family_flag_like_value(value: JsonValue, *, path: str, error: str) -> None:
|
|
822
822
|
if value is None:
|
|
823
823
|
return
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
f"{path} must be a non-empty string that does not start with '-'.",
|
|
828
|
-
)
|
|
824
|
+
_reject_opencode_flag_like_value(value, path=path, error=error)
|
|
825
|
+
if not isinstance(value, str):
|
|
826
|
+
return
|
|
829
827
|
if ":" in value:
|
|
830
828
|
raise ConfigError(
|
|
831
829
|
error,
|
{delegate_agent_cli-0.20.0 → delegate_agent_cli-0.22.0}/src/delegate_agent/config_commands.py
RENAMED
|
@@ -23,10 +23,14 @@ class ConfigCommand:
|
|
|
23
23
|
PROFILE_CONFIG_NAMES = delegate_config.PROFILE_CONFIG_NAMES
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def target_config_path() -> Path:
|
|
27
|
+
"""Resolve the config path, rejecting Windows-style paths under WSL."""
|
|
27
28
|
raw = delegate_config.config_path()
|
|
28
29
|
if wsl.should_reject_windows_path(str(raw)):
|
|
29
|
-
raise DelegateError(
|
|
30
|
+
raise DelegateError(
|
|
31
|
+
"windows_path",
|
|
32
|
+
wsl.windows_path_message(delegate_config.CONFIG_ENV, str(raw)),
|
|
33
|
+
)
|
|
30
34
|
return raw
|
|
31
35
|
|
|
32
36
|
|
|
@@ -94,7 +98,7 @@ def _validated_effective_config(config: JsonObject) -> JsonObject:
|
|
|
94
98
|
def emit(command: ConfigCommand, stdout: TextIO) -> int:
|
|
95
99
|
if command.action not in {"init", "sync-profiles"}:
|
|
96
100
|
raise DelegateError("invalid_config_command", f"Unknown config action: {command.action}")
|
|
97
|
-
path =
|
|
101
|
+
path = target_config_path()
|
|
98
102
|
if command.action == "sync-profiles":
|
|
99
103
|
if not path.exists():
|
|
100
104
|
raise DelegateError(
|
|
@@ -53,6 +53,30 @@ def pure_call_supported(engine: str) -> bool:
|
|
|
53
53
|
return engine == "claude"
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
def validate_pure_call(
|
|
57
|
+
engine: str, *, pure: bool, read_only: bool, group: str | None = None
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Reject --pure combinations that conflict with its stateless one-hop contract."""
|
|
60
|
+
if not pure:
|
|
61
|
+
return
|
|
62
|
+
if group is not None:
|
|
63
|
+
raise DelegateError(
|
|
64
|
+
"pure_conflicts_group",
|
|
65
|
+
"--pure cannot be combined with --group; pure call is a stateless one-hop completion.",
|
|
66
|
+
)
|
|
67
|
+
if read_only:
|
|
68
|
+
raise DelegateError(
|
|
69
|
+
"pure_conflicts_read_only", "--pure cannot be combined with --read-only."
|
|
70
|
+
)
|
|
71
|
+
if not pure_call_supported(engine):
|
|
72
|
+
supported = ", ".join(PURE_CALL_ENGINES)
|
|
73
|
+
raise DelegateError(
|
|
74
|
+
"unsupported_pure_call",
|
|
75
|
+
f"{engine} does not support pure call mode.",
|
|
76
|
+
next_actions=[f"Use --pure with one of: {supported}."],
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
56
80
|
# Public harness capability contract. Request validation and describe output both
|
|
57
81
|
# derive from this map so enabling a capability cannot drift between the two.
|
|
58
82
|
ENGINE_CAPABILITIES = {
|