ace-herdr 0.6.1 → 0.8.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -95
- data/README.md +35 -17
- data/docs/usage.md +113 -345
- data/lib/ace/herdr/cli/commands/status.rb +119 -0
- data/lib/ace/herdr/cli.rb +4 -0
- data/lib/ace/herdr/molecules/status_composer.rb +131 -0
- data/lib/ace/herdr/organisms/control_surface.rb +2 -1
- data/lib/ace/herdr/organisms/deliverer.rb +2 -1
- data/lib/ace/herdr/organisms/dispatcher.rb +2 -1
- data/lib/ace/herdr/version.rb +1 -1
- data/lib/ace/herdr.rb +1 -0
- metadata +10 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3941b27b9b021af12c7398dd2007cce7816a3353bd2a6835a9958d9d4e996d0b
|
|
4
|
+
data.tar.gz: fa7ff918b46680fd0f5f7745917a9a7f9c36d2b18707e3e8fc13102fd40f23b7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3fc92a81e4165d8f015932c1da9787f344198d1e99a17519a0aaf640e71028ba26917985b7fa615a70ee4ca1cbd11df87afb0b05479200659299e690af05c2b1
|
|
7
|
+
data.tar.gz: f732a3b7fce224517f4f3d3f9bdcd1a0e5ac1bba6923f6d6783699a5ca4b6f02c2f6c6e8f484b1f117a0e20b7dcd996d4f3fa6953d7c1c5a2c486c0fdf9922a2
|
data/CHANGELOG.md
CHANGED
|
@@ -7,108 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [0.
|
|
10
|
+
## [0.8.1] - 2026-10-20
|
|
11
11
|
|
|
12
12
|
### Fixed
|
|
13
|
+
- Honor `ACE_TOOLCHAIN=installed` for published-gem execution and context propagation without source-runtime fallback; retain default auto development behavior.
|
|
13
14
|
|
|
14
|
-
- Share native immutable session normalization between Inbox and process admission so real Pi path selectors bind their session UUID without weakening ancestry, foreground owner or live-session checks.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- Follow the ace-hitl-contract 0.3 request-envelope line (dependency floor).
|
|
19
|
-
|
|
20
|
-
## [0.6.0] - 2026-10-16
|
|
16
|
+
## [0.8.0] - 2026-10-20
|
|
21
17
|
|
|
22
18
|
### Added
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- Require the published producer and consumer patch versions containing the protected context and managed-attempt APIs; exclude incompatible prior dependency graphs.
|
|
31
|
-
|
|
32
|
-
## [0.5.0] - 2026-10-08
|
|
33
|
-
|
|
34
|
-
### Changed
|
|
35
|
-
|
|
36
|
-
- Submit Codex answers through the existing Herdr prompt command to the original live terminal. A successful send means submitted, not read; retain unknown sends without automatic resend.
|
|
37
|
-
- Remove dedicated Codex app-server selection, message-read observation, signing keys and consumed/superseded reconciliation APIs.
|
|
38
|
-
- Keep authenticated original-context admission and actual scoped effects without a second current-state projection.
|
|
39
|
-
|
|
40
|
-
### Fixed
|
|
41
|
-
|
|
42
|
-
- Load bounded process/error primitives directly and preserve bounded cleanup of the original process after execution uncertainty.
|
|
43
|
-
|
|
44
|
-
## [0.4.0] - 2026-10-05
|
|
45
|
-
|
|
46
|
-
### Fixed
|
|
47
|
-
- Use the shared typed reverse-reference pair at CLI, Inbox and delivery boundaries, refusing malformed persisted targets and noncanonical serialized files before claim or native observation.
|
|
48
|
-
- Pin managed delivery to the accepted original native target under the event lock and reject secret-bearing payloads even without a nested message.
|
|
19
|
+
- Documented the L7.3 report-delivery surface to a pi overseer (task
|
|
20
|
+
8xg.t.sbz.6.2) in `docs/usage.md`: pane push via `ace-herdr deliver`
|
|
21
|
+
(idempotent per event id, concurrent deliveries prompt once) and the
|
|
22
|
+
provider-native pi queue client (`ACE_HERDR_PI_QUEUE_CLIENT`), with the
|
|
23
|
+
single-execution guarantee — a delivery during an active overseer turn
|
|
24
|
+
joins the one session run and never starts a second overseer.
|
|
49
25
|
|
|
50
26
|
### Changed
|
|
51
27
|
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
28
|
+
- `ace-herdr status` refined to the reviewed `l7-statuses` implementation
|
|
29
|
+
(task 8xg.t.sbz.6.0; extracted from the superseded PR #382): composition
|
|
30
|
+
moved into a `StatusComposer` molecule, new `--workspace <id>` scoping,
|
|
31
|
+
counts recomputed from the nesting with panes referencing vanished
|
|
32
|
+
tabs/workspaces kept visible in a synthetic `(unlinked)` group, and a
|
|
33
|
+
live-but-empty layout printing `No live herdr workspaces.` (exit 0) while
|
|
34
|
+
an unreachable runtime stays an actionable non-zero error.
|
|
57
35
|
|
|
58
|
-
- Add protected Herdr control pinned to the installed server/socket and fixed workspace, creating one fresh bootstrap pane with exact child observation and no default shell.
|
|
59
|
-
|
|
60
|
-
- Expose verified native process/session ownership and allow identical signed inbox proof re-verification after settlement without duplicate transitions.
|
|
61
|
-
|
|
62
|
-
## [0.3.2] - 2026-10-04
|
|
63
|
-
|
|
64
|
-
### Changed
|
|
65
|
-
|
|
66
|
-
- Depend on the leaf `ace-hitl-contract` provider vocabulary instead of full HITL orchestration, removing the assignment adapter dependency cycle.
|
|
67
|
-
|
|
68
|
-
## [0.3.1] - 2026-10-04
|
|
69
|
-
|
|
70
|
-
### Fixed
|
|
71
|
-
- Replacing a dead prepared pane updates only the recorded pane pointer: a foreign tab's pointer-only identity record stays pointer-only instead of gaining invented `root`/`preset` ownership keys, so the live replacement pane is reused across adapter and process restarts and `ensure_window` at the verified native root still adopts the tab. Records with genuine provenance keep their exact root and preset, and wrong ids, conflicting provenance, corrupt records, and stale pointers still cannot bypass the ownership guards.
|
|
72
|
-
- `ace-herdr tab` reports a failed preset materialization (native tab created, later step failed) through the standard CLI error boundary — non-zero exit, actionable message, no success payload, and no stack trace in ordinary mode. The runtime adapter keeps its documented runtime classification and exact-id rollback; this entrypoint never closes tabs.
|
|
73
|
-
|
|
74
|
-
## [0.3.0] - 2026-10-02
|
|
75
|
-
|
|
76
|
-
### Added
|
|
77
|
-
- Durable `inbox enqueue`, `status`, and `deliver` commands for attempt-linked messages. Enqueue binds the live pane, terminal, agent, and native session; per-event locked claims and saved submission intent prevent duplicate dispatch after a crash. Codex and Pi use exact-session native queues, and idle agents receive a bounded, payload-free Herdr wake after accepted submission.
|
|
78
|
-
- Inbox status reports claim generation, target binding, accepted submission receipt, and uncertainty. `reconcile` accepts an operator or supervisor native-outcome receipt file only when its trusted detached signature and event, attempt, generation, digest, and full bound identity match; missing or mismatched proof returns a machine-readable refusal and keeps the event uncertain.
|
|
79
|
-
- Installed acceptance covers Codex delivery, signed uncertainty reconciliation, and idle Pi delivery; bounded wake tests cover subprocess stalls and structured errors.
|
|
80
|
-
|
|
81
|
-
### Fixed
|
|
82
|
-
- Post-launch subprocess I/O failures terminate the child process group before surfacing, so a failed run can never leave an orphaned native client behind past the deadline.
|
|
83
|
-
- Post-launch subprocess I/O failures are classified separately from spawn failures, so a native client that already ran keeps its event uncertain instead of being re-queued for a duplicate submission; and a pane that changed agent is classified as target drift before agent-specific validation can mask it.
|
|
84
|
-
- Unreadable or malformed reconciliation receipt files surface as the documented machine-readable refusal while persistence failures still exit as command errors.
|
|
85
|
-
- Orphaned claims recover by proof: a claim saved before any submission intent requeues as retryable, while only intent-carrying claims stay uncertain; every spawn-time failure is classified at the process boundary as proven pre-launch (retryable) across both executors; and the inbox CLI reports persistence failures as command failures instead of receipt refusals.
|
|
86
|
-
- Target identity drift is classified before agent-specific event id rules, so a bound event whose pane changed agent lands in reconcilable uncertainty instead of looping on a pre-send validation error.
|
|
87
|
-
- Native queue bindings require an immutable session id for both Codex and Pi, so an agent restarting under the same name in a reused pane can never inherit an old event; signed replacement targets are validated against the replacement agent's own event-id and payload rules before a supersession re-binds the event.
|
|
88
|
-
- Pane probe responses are shape-checked and exit-checked before use, so malformed or failed `herdr pane get` output becomes a classified retryable error instead of stranding a claimed event or delivering against a failed observation; the Pi identity probe rejects non-object JSON the same way.
|
|
89
|
-
- The pane identity probe runs under a bounded child deadline (process-group kill), so a stalled herdr pane get can no longer hold the per-event inbox lock; a probe timeout classifies as a retryable pre-submission failure. The idle-wake decision rides the first delivered transition (a busy target can never be prompted by a crash-recovery retry), and enqueue rejects payloads that are not valid UTF-8 text with an actionable validation error.
|
|
90
|
-
- Pane observation launch failures are normalized to executor unavailability so a delivery probe can never strand an event in `claimed`; idle-wake retry outcomes persist across restarts; reverse-address files that are not JSON objects fail enqueue with an actionable validation error instead of crashing.
|
|
91
|
-
- Enqueue applies the target agent's native payload limit (Codex argv bound, Pi body bound) before persisting an event, validates recognized agent statuses before submission (undetermined statuses stay retryable pre-send), and shares one Pi event id validator between enqueue and delivery, so events that could never be delivered are rejected up front instead of queuing forever.
|
|
92
|
-
- Idle-agent wakes are persisted as pending before they are attempted and retried by later deliver calls (re-verifying the live target; identity drift demotes the event to uncertain) without ever resubmitting the native payload, so a crash or transient wake failure can no longer leave an accepted message undelivered to an idle agent. Pi targets now enforce the `inb-`/`wnk-` event id constraint at enqueue instead of queuing an event that can never be delivered.
|
|
93
|
-
- Native queue submission and the Pi identity probe enforce their deadline at the child boundary through a shared bounded-process molecule (process-group kill, bounded pipe draining, nonblocking partial stdin writes). A stalled or non-reading native client can no longer hold the per-event inbox lock past the configured timeout the way a cleanup-blocking `Timeout.timeout` around `Open3.capture3` allowed. Codex payloads are size-bounded before launch and a spawn-time `E2BIG` is classified as a proven pre-submission rejection, keeping the event retryable instead of stranding it uncertain.
|
|
94
|
-
|
|
95
|
-
## [0.2.0] - 2026-10-02
|
|
96
|
-
|
|
97
|
-
### Added
|
|
98
|
-
- `Ace::Herdr::Organisms::RuntimeAdapter` and the lazy `ace/runtime/adapters/herdr` entrypoint implement the shared ace-runtime intent contract. It preserves native agent prompt and wait semantics, polls all four lifecycle observations, and maps Herdr failures to contract errors. Native tab and pane probes support context, focus, and retained pane preparation. The packaged shared adapter contract suite runs against a Herdr-shaped fake executor.
|
|
99
|
-
|
|
100
|
-
## [0.1.0] - 2026-09-27
|
|
101
|
-
|
|
102
|
-
### Added
|
|
103
|
-
- `ace-herdr tidy` (spec 8wq.t.1w0): dry-run-by-default cleanup of finished agent panes and delivery records. Pane closure requires positive completion evidence only (observed `done` agent state, or a pane whose native `pane process-info` shows no live foreground process — herdr omits `foreground_processes` when empty) re-confirmed by a fresh probe immediately before the rename-to-`done`-then-close mutation; revived or uncertain candidates are excluded and never mutated. Delivered records strictly older than the new `tidy.delivered_retention_days` config key (default 7) are atomically archived to `deliveries_dir/archive/` after a lock-guarded reload re-proves eligibility; `pending`/`retryable`/`failed` and unreadable records are never touched, an unreadable record encountered during apply is preserved and reported instead of aborting. Archival never breaks delivery idempotency (`deliver`/`--resume` consult the archive copy, so identical re-delivery still short-circuits and conflicts still fail closed). Deterministic one-line JSON report with explicit empty states; an unreachable herdr runtime fails with an explicit error and no partial report.
|
|
104
|
-
- Terminal-control surface matching the ace-tmux intent vocabulary (spec 8wq.t.k84), keeping one-line JSON output: `ace-herdr list` (panes/tabs/workspaces, `--workspace` scoping, explicit empty arrays), `ace-herdr send` (declaration-ordered `--cmd`/`--msg`/`--key`; agent-aware routing sends one self-submitting `agent prompt` on agent panes with `agent_blocked`/`agent_prompt_stalled` gating and drop-and-report of a single trailing `--key Enter`; invalid shapes fail closed before any transport call), `ace-herdr capture` (raw `pane read` text, `--source visible|recent`), `ace-herdr wait --for output --pattern` (literal match, immediate check then poll, seconds→ms conversion, timeout CLI error) alongside the unchanged agent wait, and `--list-presets`.
|
|
105
|
-
- Preset-driven creation: `ace-herdr workspace <preset>` / `ace-herdr tab <preset>` compose YAML layouts through the ADR-022 cascade (`.ace/herdr/` > `~/.ace/herdr/` > gem `.ace-defaults/herdr/`, ships `workspaces/development.yml` + `tabs/agent.yml`), with recursive `preset:` inheritance (deep merge, array concat, cycle detection, fail-closed missing references), deterministic creation order (workspace → tabs → root pane + splits → renames → pane commands → readiness-gated agent starts; herdr's seeded initial tab is closed once the declared tabs exist), CLI `--cwd` overrides (CLI > tab > root > pane), and pre-creation layout validation (unplaced panes, duplicate placements, unknown split targets/directions fail closed and create nothing). Includes a tmux-intent ↔ herdr-command parity table in `docs/usage.md`.
|
|
106
|
-
- `Ace::Herdr::Organisms::ControlSurface`: owner of the terminal-control behavior — native JSON normalization into public payloads, send validation/routing, and preset instantiation.
|
|
107
|
-
- `Ace::Herdr::Molecules::PresetLoader` (ADR-022 cascade discovery) and `Ace::Herdr::Molecules::PresetResolver` (`preset:` reference resolution).
|
|
108
|
-
- `Ace::Herdr::Molecules::HerdrExecutor`: native operations for workspace/tab/pane listing, pane read/send-text/send-keys, pane wait-output, pane split, workspace create, agent send-keys, and focus flags on tab/workspace creation; typed `TabNotFoundError`/`WorkspaceNotFoundError` classification; herdr machine codes are now carried in executor error messages (for example `pane_not_found: ...`); output-wait timeouts raise `WaitTimeoutError`.
|
|
109
|
-
- Gem bootstrap (spec 8wm.t.vs0): push delivery + agent bootstrap for the Herdr runtime, implementing the ace-hitl provider delivery contract (`deliver(ref, answer)` -> `DeliverResult` with `:delivered`/`:retryable`/`:failed`).
|
|
110
|
-
- `Ace::Herdr::Organisms::Deliverer`: idempotent per event id with write-ahead delivery records under `.ace-local/herdr/deliveries/` (0600, atomic writes; the full answer is persisted so a crash never loses it, recoverable via `ace-herdr deliver --resume`), per-event lock serializing concurrent deliveries, duplicate-identical short-circuit, fail-closed content/destination conflicts, bootstrap of missing agents (`herdr agent start` with shell-escaped `HERDR_SESSION`/`HERDR_PANE` exported into the pane shell), a readiness gate before prompting, retry limits with fixed deterministic backoff (covering prompt and probe failures), and terminal-failure reporting — including the ambiguous crash window after a submitted prompt, reported instead of silently resent.
|
|
111
|
-
- `Ace::Herdr::Organisms::Dispatcher`: one-command subagent dispatch — tab + `herdr agent start` + prompt with deterministic defaults (caller's workspace, label = agent/pane name, prompt from file/stdin/`--no-prompt`); workspace and pane identifiers are token-validated and shell-escaped before they reach a pane shell.
|
|
112
|
-
- `ace-herdr` CLI: `deliver`, `dispatch`, `wait`, `close` (JSON output, exception-based exit codes per ADR-023).
|
|
113
|
-
- `Ace::Herdr::Molecules::HerdrExecutor`: the single seam to the herdr CLI (argv arrays per ADR-031) with typed error classification from herdr's machine-readable codes (`agent_blocked`, `agent_prompt_stalled`, `pane_not_found`, `timeout`).
|
|
114
|
-
- Config cascade (ADR-022) defaults: `default_agent_kind`, delivery retry limits/backoff, timeouts, deliveries dir (ADR-029).
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1> ACE - HERDR </h1>
|
|
3
3
|
|
|
4
|
-
Zero-token
|
|
4
|
+
Zero-token terminal transport and agent bootstrap for the Herdr runtime.
|
|
5
5
|
|
|
6
6
|
<img src="../docs/brand/AgenticCodingEnvironment.Logo.XS.jpg" alt="ACE Logo" width="480">
|
|
7
7
|
<br><br>
|
|
@@ -14,44 +14,62 @@
|
|
|
14
14
|
|
|
15
15
|
> Works with: Claude Code, Codex CLI, OpenCode, Gemini CLI, pi-agent, and more.
|
|
16
16
|
|
|
17
|
+
> **State owner:** none for task, assignment, or HITL state. `ace-herdr` owns
|
|
18
|
+
> only terminal transport and its delivery retry records under
|
|
19
|
+
> `.ace-local/herdr/deliveries/`. tmux/Herdr are transports, not owners of the
|
|
20
|
+
> file-based flow ([docs/lab-flow.md](../docs/lab-flow.md)).
|
|
21
|
+
|
|
17
22
|
[Usage Guide](docs/usage.md)
|
|
18
23
|
|
|
19
|
-
`ace-herdr` is to herdr what `ace-tmux` is to tmux: a deterministic, zero-token
|
|
24
|
+
`ace-herdr` is to herdr what `ace-tmux` is to tmux: a deterministic, zero-token
|
|
25
|
+
wrapper over the `herdr` CLI. It sends text and keys to panes, starts agents,
|
|
26
|
+
captures output, waits for agent state or output, and closes finished panes.
|
|
27
|
+
It also implements the HITL push-delivery transport used by `ace-hitl` adapters.
|
|
20
28
|
|
|
21
29
|
## How It Works
|
|
22
30
|
|
|
23
|
-
1.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
1. Terminal operations address panes through opaque handles; `ace-runtime`
|
|
32
|
+
selects tmux or Herdr and hides the difference.
|
|
33
|
+
2. `ace-herdr deliver` pushes an answer to the pane that asked, bootstrapping an
|
|
34
|
+
agent (`herdr agent start`) when the pane has none.
|
|
35
|
+
3. Delivery is idempotent per event id: write-ahead delivery records under
|
|
36
|
+
`.ace-local/herdr/deliveries/` make retries safe.
|
|
37
|
+
4. `ace-herdr dispatch`, `wait`, and `close` give agents one-command subagent
|
|
38
|
+
lifecycle with deterministic defaults.
|
|
30
39
|
|
|
31
40
|
## Use Cases
|
|
32
41
|
|
|
33
|
-
- Push an
|
|
34
|
-
- Dispatch a subagent in one command with deterministic defaults
|
|
42
|
+
- Push an answer to the pane that asked, even if its agent restarted.
|
|
43
|
+
- Dispatch a subagent in one command with deterministic defaults.
|
|
35
44
|
- Wait for agent readiness or completion without scraping pane noise.
|
|
36
|
-
- Close
|
|
37
|
-
-
|
|
45
|
+
- Close finished agent panes and return their result.
|
|
46
|
+
- See the real layout of spaces, tabs, and panes in one command.
|
|
47
|
+
- Use Herdr through the shared [ace-runtime](../ace-runtime) API.
|
|
38
48
|
|
|
39
49
|
## Installation
|
|
40
50
|
|
|
41
|
-
Install as part of the ACE toolkit (see the repo README), or add to your Gemfile:
|
|
42
|
-
|
|
43
51
|
```ruby
|
|
44
52
|
gem "ace-herdr"
|
|
45
53
|
```
|
|
46
54
|
|
|
55
|
+
## Planned waves
|
|
56
|
+
|
|
57
|
+
> **Planned (L2):** the unified report/question/reply interface and configurable
|
|
58
|
+
> adapter set are not available yet. `ace-herdr` remains a transport/adapter
|
|
59
|
+
> building block; it does not own HITL state.
|
|
60
|
+
>
|
|
61
|
+
> The old durable agent inbox (`inbox`) and guarded native-source requirements
|
|
62
|
+
> belong to the replaced protected model and are not part of the file-based
|
|
63
|
+
> flow.
|
|
64
|
+
|
|
47
65
|
## Testing
|
|
48
66
|
|
|
49
67
|
Fast tests only (no live herdr needed):
|
|
50
68
|
|
|
51
69
|
```bash
|
|
52
|
-
ace-test ace-herdr
|
|
70
|
+
bin/ace-test ace-herdr
|
|
53
71
|
```
|
|
54
72
|
|
|
55
73
|
## License
|
|
56
74
|
|
|
57
|
-
MIT
|
|
75
|
+
MIT -- see [LICENSE](LICENSE).
|
data/docs/usage.md
CHANGED
|
@@ -1,399 +1,167 @@
|
|
|
1
1
|
---
|
|
2
2
|
doc-type: user
|
|
3
3
|
title: ace-herdr Usage
|
|
4
|
-
purpose:
|
|
4
|
+
purpose: CLI and configuration reference for ace-herdr terminal transport, delivery, and agent bootstrap.
|
|
5
5
|
ace-docs:
|
|
6
|
-
last-updated: 2026-10-
|
|
7
|
-
last-checked: 2026-10-
|
|
6
|
+
last-updated: 2026-10-17
|
|
7
|
+
last-checked: 2026-10-17
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
# Usage
|
|
10
|
+
# ace-herdr Usage
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
> **State owner:** none for task, assignment, or HITL state. `ace-herdr` owns
|
|
13
|
+
> terminal transport and its delivery retry records only. See
|
|
14
|
+
> [docs/lab-flow.md](../../docs/lab-flow.md).
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
`ace-herdr` is a zero-token wrapper over the `herdr` CLI. It is a transport:
|
|
17
|
+
it does not own task, assignment, queue, or HITL event state.
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
- `ace-herdr inbox enqueue|status|deliver [OPTIONS]`
|
|
18
|
-
- `ace-herdr dispatch [OPTIONS]`
|
|
19
|
-
- `ace-herdr list [--panes|--tabs|--workspaces] [--workspace ID] [--quiet]`
|
|
20
|
-
- `ace-herdr send [--cmd TEXT] [--msg TEXT...] [--key NAME...] --pane ID [--quiet]`
|
|
21
|
-
- `ace-herdr capture --pane ID [--lines N] [--source visible|recent]`
|
|
22
|
-
- `ace-herdr wait --pane ID (--for output --pattern PATTERN | --for agent [--until ...]) [--timeout S] [--quiet]`
|
|
23
|
-
- `ace-herdr close [OPTIONS]`
|
|
24
|
-
- `ace-herdr tidy [--apply] [--quiet]`
|
|
25
|
-
- `ace-herdr workspace <preset> [--cwd PATH] [--quiet]`
|
|
26
|
-
- `ace-herdr tab <preset> [--workspace ID] [--cwd PATH] [--quiet]`
|
|
27
|
-
- `ace-herdr --list-presets [workspaces|tabs]`
|
|
19
|
+
## Command surface
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
| Command | Purpose |
|
|
22
|
+
|---------|---------|
|
|
23
|
+
| `deliver` | Push an answer to an agent pane (HITL delivery transport) |
|
|
24
|
+
| `dispatch` | Start an agent in one command: tab + agent + prompt |
|
|
25
|
+
| `send` | Send a command, raw text, or named keys to a pane |
|
|
26
|
+
| `capture` | Print recent pane output as raw text |
|
|
27
|
+
| `wait` | Wait for an agent state or matching pane output |
|
|
28
|
+
| `close` | Rename and/or close a finished agent pane |
|
|
29
|
+
| `list` | List live panes, tabs, or workspaces as one JSON line |
|
|
30
|
+
| `status` | Show the live layout: workspaces, tabs, and panes in one view |
|
|
31
|
+
| `tidy` | Report cleanable panes/delivery records; close/archive with `--apply` |
|
|
32
|
+
| `tab` / `workspace` | Create a tab/workspace from a preset |
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
## Delivery
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Install `ace-herdr` alongside `ace-runtime`, then resolve the adapter with `Ace::Runtime.resolve("herdr")`. The contract's `session` is a Herdr **workspace**, `window` is a **tab**, and `pane` is an opaque Herdr pane ID. The adapter does not construct pane IDs. It locates the caller through `HERDR_SESSION` and `HERDR_PANE` and reads the explicit pane with `pane get`; the pane's native workspace is authoritative and a conflicting `HERDR_WORKSPACE_ID` hint is rejected instead of honored. Outside Herdr, `context` reports `in_runtime: false`; operations requiring a caller workspace raise `Ace::Runtime::RuntimeUnavailableError`.
|
|
38
|
-
|
|
39
|
-
The adapter implements `context`, `ensure_window`, `prepare_pane`, `focus`, ordered `send` and its convenience methods, `capture`, `wait_output`, `wait_agent`, `wait_lifecycle`, `close_window`, `list_windows`, and `list_panes`. `ensure_window` scopes the sanitized tab label to the caller workspace. It records the tab's root, preset, and prepared pane under `~/.ace/local/herdr/runtime-tabs/` — keyed by workspace and label, so adapter instances from any working directory or requested root share one record, create through the same lock, and serialize prepared-pane creation through it. Later instances verify an idempotent request and reject a conflicting one; a tab whose ownership cannot be proven is never closed or replaced (a failed create rolls back only tabs the same attempt created). `prepare_pane` splits a retained shell target and returns the native pane ID after verifying it exists and has a shell process.
|
|
40
|
-
|
|
41
|
-
Replacing a dead prepared pane updates only the recorded pane pointer: a foreign tab's pointer-only record stays pointer-only (no root/preset keys are invented, so it never becomes ownership evidence), and an owned record keeps its exact root and preset. A prepared-pane pointer to a dead pane is replaced by the next split; a pointer-only record never satisfies `ensure_window` by itself -- adoption still requires the tab's verified native root.
|
|
42
|
-
|
|
43
|
-
Sends probe for a live agent. Plain panes receive raw text and keys in order; agent panes receive one self-submitting `agent prompt`, with one trailing Enter dropped and reported. Agent waits use native `agent wait` states (`idle`, `working`, `blocked`, `done`). Contract timeout values are seconds and are converted to Herdr milliseconds. Lifecycle waits observe `tab get`, focused tab state, `pane get`, and `pane process-info`; a missing pane keeps `pane-exists` waiting but immediately satisfies `pane-exited`. A pane with only its retained shell also satisfies `pane-exited` because no submitted foreground command remains. This observation does not prove assignment success or authorize cleanup.
|
|
44
|
-
|
|
45
|
-
Native `agent_blocked` becomes `SendRejectedError`; `agent_prompt_stalled` becomes `SendStalledError` and must not be automatically resent. Native wait timeouts become `WaitTimeoutError`, missing targets become `TargetNotFoundError`, and an unavailable binary or socket becomes `RuntimeUnavailableError`.
|
|
46
|
-
|
|
47
|
-
## Durable agent inbox
|
|
48
|
-
|
|
49
|
-
Use a stable event ID for one message and the assignment attempt that owns it. The reverse-address JSON contains `session` and `pane`, as with `deliver`.
|
|
50
|
-
|
|
51
|
-
Reverse references use the shared `ace.hitl.ref/v1` pair contract. Herdr token names
|
|
52
|
-
remain valid; the codec also preserves paired native tmux IDs (`$0`/`%0`) when
|
|
53
|
-
carried by managed metadata. Mixed native/token or cross-field IDs refuse.
|
|
54
|
-
Wire and persisted references must be canonical. Syntax acceptance does not add
|
|
55
|
-
tmux execution to this Herdr controller or replace its exact native ownership,
|
|
56
|
-
target and submission checks.
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
ace-herdr inbox enqueue --event inb-12345678 --attempt ATTEMPT --ref ref.json --file prompt.txt
|
|
60
|
-
ace-herdr inbox status --event inb-12345678 --format json
|
|
61
|
-
ace-herdr inbox deliver --event inb-12345678
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
`enqueue` checks the live pane, terminal and original agent-session identity.
|
|
65
|
-
Managed callers supply `expected_target` from the accepted runtime owner. Reusing
|
|
66
|
-
an event with identical attempt, target and payload returns its original record;
|
|
67
|
-
changed input refuses. `deliver` records one submission intent before sending.
|
|
68
|
-
|
|
69
|
-
Codex delivery uses the existing Herdr terminal prompt; Pi uses its maintained
|
|
70
|
-
queue boundary. `delivered` means the submission was accepted, not that the
|
|
71
|
-
agent read it or completed the task. A known pre-submission refusal stays queued.
|
|
72
|
-
A lost response, process interruption after intent, or changed target is uncertain;
|
|
73
|
-
a later `deliver` does not automatically resend or select a replacement terminal.
|
|
74
|
-
|
|
75
|
-
Inspect current progress directly through `ace-herdr capture` or the native pane.
|
|
76
|
-
Inbox status is transport history, not a cached projection of current agent state.
|
|
77
|
-
No agent-read proof, signing key, reconciliation command or dedicated Codex
|
|
78
|
-
app-server is required. Actual task results and scoped service effects retain
|
|
79
|
-
their independent authorization and verification.
|
|
80
|
-
|
|
81
|
-
## tmux-intent ↔ herdr-command parity
|
|
82
|
-
|
|
83
|
-
Every common terminal-control intent available through `ace-tmux` is available through `ace-herdr` (herdr 0.9.1). Parity is of INTENT and FLAG VOCABULARY, not byte-format: ace-herdr keeps one-line JSON where ace-tmux renders human tables.
|
|
84
|
-
|
|
85
|
-
| ace-tmux intent | ace-herdr command | Native herdr call |
|
|
86
|
-
|---|---|---|
|
|
87
|
-
| `list` (panes) | `list` / `list --panes` | `pane list` |
|
|
88
|
-
| `list --windows` | `list --tabs` | `tab list` |
|
|
89
|
-
| `list --sessions` | `list --workspaces` | `workspace list` |
|
|
90
|
-
| `send --cmd` | `send --cmd` | `pane run` (plain) / `agent prompt` (agent pane) |
|
|
91
|
-
| `send --msg` / `--key` | `send --msg` / `--key` | `pane send-text` / `pane send-keys`; `agent prompt` / `agent send-keys` |
|
|
92
|
-
| `capture` | `capture` | `pane read` |
|
|
93
|
-
| `wait --for output` | `wait --for output --pattern` | `pane wait-output` |
|
|
94
|
-
| `wait --for agent` | `wait --until` (agent state) | `agent wait` |
|
|
95
|
-
| `start <preset>` | `workspace <preset>` | `workspace create` + tabs/panes |
|
|
96
|
-
| `window <preset>` | `tab <preset>` | `tab create` + splits |
|
|
97
|
-
| `--list-presets` | `--list-presets` | -- (config cascade) |
|
|
98
|
-
| `attach` / `detach` | ❌ out of scope | human-facing; no automation equivalent |
|
|
99
|
-
| layout strings / `select-layout` | ❌ out of scope | herdr has split/resize only |
|
|
100
|
-
|
|
101
|
-
## `ace-herdr list`
|
|
102
|
-
|
|
103
|
-
Inspect live state. Panes are the default scope; `--workspace <id>` scopes panes and tabs.
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
ace-herdr list # panes across all workspaces
|
|
107
|
-
ace-herdr list --workspace w1 # panes in one workspace
|
|
108
|
-
ace-herdr list --tabs --workspace w1 # tabs in one workspace
|
|
109
|
-
ace-herdr list --workspaces # workspaces
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Output: `{"panes":[{"id":"w1:p1","tab":"w1:t1","workspace":"w1","title":"~","cwd":"/tmp","focused":true,"agent_status":"idle"},...]}` -- `{"tabs":[{id, workspace, title, number, pane_count, focused}...]}` for `--tabs`, `{"workspaces":[{id, title, number, tab_count, pane_count, focused}...]}` for `--workspaces`. Empty results are a success with an empty array, never an error.
|
|
113
|
-
|
|
114
|
-
## `ace-herdr send`
|
|
115
|
-
|
|
116
|
-
Send a command, raw text, or named keys. Input reaches the pane in **declaration order** (an intentional divergence from ace-tmux's msgs-then-keys; order is expressive here, so it is preserved, not normalized). At least one input token is required -- none is a usage error before any transport call.
|
|
36
|
+
`deliver` pushes an answer to the pane that asked, bootstrapping an agent when
|
|
37
|
+
the pane has none. Deliveries are idempotent per event id and never lose the
|
|
38
|
+
answer: a write-ahead record survives crashes, transient failures retry with
|
|
39
|
+
fixed backoff, and `--resume` re-delivers from the stored record.
|
|
117
40
|
|
|
118
41
|
```bash
|
|
119
|
-
ace-herdr
|
|
120
|
-
ace-herdr
|
|
121
|
-
ace-herdr send --pane p5 --key Esc --cmd 'reset' # rejected -- see rules
|
|
122
|
-
ace-herdr send --pane p5 --key enter
|
|
42
|
+
ace-herdr deliver --session ws-1 --pane p5 --event-id evt-1 --answer-file answer.md
|
|
43
|
+
echo 'the answer' | ace-herdr deliver --pane p5
|
|
123
44
|
```
|
|
124
45
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- `--cmd TEXT` is exactly one `pane run` submission (text + Enter). It must be declared before every `--key`; trailing keys are sent after the submission (post-submission keystrokes such as `y`/`n` confirmations). Leading keys (`--key Esc --cmd run`) are rejected before any transport call.
|
|
128
|
-
- `--msg a --msg b` types raw text without submitting, concatenated in order.
|
|
129
|
-
- `--key K...` sends named keys in order; each `enter` submits pending text.
|
|
130
|
-
- `--cmd` combined with `--msg` is a usage error before any transport call (fail closed, nothing sent).
|
|
131
|
-
- Multi-Enter sequences submit per Enter.
|
|
132
|
-
- Output: `{"pane":"p5","sent":"cmd"}` for command-led sends, `"text"` for message-led sends, `"keys"` for key-only sends.
|
|
46
|
+
These are transport records, not a journal or a state owner.
|
|
133
47
|
|
|
134
|
-
|
|
48
|
+
## Delivering reports to a pi overseer (L7.3)
|
|
135
49
|
|
|
136
|
-
|
|
50
|
+
An ace-hitl report reaches a pi overseer session through the transports Herdr
|
|
51
|
+
already owns — there is no second queue on the ACE side:
|
|
137
52
|
|
|
138
|
-
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
53
|
+
- **Pane push (`ace-herdr deliver`):** the report text is prompted into the
|
|
54
|
+
overseer pane. Delivery is idempotent per event id: a repeated notification
|
|
55
|
+
of the same event short-circuits against the delivery record instead of
|
|
56
|
+
prompting again, and concurrent deliveries of one event prompt once. A pane
|
|
57
|
+
without an agent is bootstrapped and gated on readiness first.
|
|
58
|
+
- **Provider-native queue (`ACE_HERDR_PI_QUEUE_CLIENT`):** the configured pi
|
|
59
|
+
queue client receives the payload (`--delivery <event-id> --session-id ...`
|
|
60
|
+
with a digest-bound receipt) and hands it to the session, which accepts it
|
|
61
|
+
while waiting (immediate turn) and while working (queued follow-up for the
|
|
62
|
+
active run).
|
|
143
63
|
|
|
144
|
-
|
|
64
|
+
Either way a delivery during an ACTIVE overseer turn never starts a second
|
|
65
|
+
overseer execution: the message joins the one session run, and the woken
|
|
66
|
+
session resumes the commissioned process with the `/continue-work` prompt
|
|
67
|
+
(see ace-handbook-integration-pi). Repetition of a delivery is transport
|
|
68
|
+
noise — work repetition is decided by event state on the receiving side, not
|
|
69
|
+
by the number of deliveries.
|
|
145
70
|
|
|
146
|
-
|
|
71
|
+
## Dispatch, send, capture, wait, close
|
|
147
72
|
|
|
148
73
|
```bash
|
|
149
|
-
ace-herdr
|
|
150
|
-
ace-herdr
|
|
151
|
-
ace-herdr capture --pane p5 --source visible # the agent screen
|
|
152
|
-
```
|
|
74
|
+
ace-herdr dispatch --label t.123 --kind pi --prompt-file prompt.md
|
|
75
|
+
ace-herdr dispatch --label review --pane p7 --cwd /Users/me/project
|
|
153
76
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
ace-herdr
|
|
160
|
-
ace-herdr wait --pane p5 --until done --timeout 120 # agent: one state
|
|
161
|
-
ace-herdr wait --pane p5 --for output --pattern 'tests? OK' --timeout 30
|
|
77
|
+
ace-herdr send --pane p5 --cmd 'bundle exec rake test'
|
|
78
|
+
ace-herdr send --pane p5 --msg 'continue with option 2' --key Enter
|
|
79
|
+
ace-herdr capture --pane p5 --lines 40
|
|
80
|
+
ace-herdr wait --pane p5 --until done --timeout 120
|
|
81
|
+
ace-herdr wait --pane p5 --for output --pattern 'build succeeded'
|
|
82
|
+
ace-herdr close --pane p5 --rename done
|
|
162
83
|
```
|
|
163
84
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
85
|
+
`send` delivers input in declaration order. Panes hosting a live agent get
|
|
86
|
+
prompt semantics: text becomes one self-submitting agent prompt, and a single
|
|
87
|
+
trailing Enter is dropped and reported. A command must be declared before any
|
|
88
|
+
key.
|
|
167
89
|
|
|
168
|
-
##
|
|
90
|
+
## Workspaces, tabs, and presets
|
|
169
91
|
|
|
170
|
-
|
|
92
|
+
Presets resolve through the ADR-022 cascade (`.ace/herdr/` > `~/.ace/herdr/` >
|
|
93
|
+
gem defaults) and may compose via `preset:` refs.
|
|
171
94
|
|
|
172
95
|
```bash
|
|
173
|
-
ace-herdr workspace development
|
|
174
96
|
ace-herdr workspace development --cwd /path/to/project
|
|
175
97
|
ace-herdr tab agent --workspace w1
|
|
176
|
-
ace-herdr --list-presets
|
|
177
|
-
ace-herdr --list-presets workspaces
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### Preset cascade (ADR-022)
|
|
181
|
-
|
|
182
|
-
Presets load nearest-wins and the `--list-presets` inventory reflects the merged set:
|
|
183
|
-
|
|
184
|
-
1. project `.ace/herdr/{workspaces,tabs}/*.yml` (highest)
|
|
185
|
-
2. user `~/.ace/herdr/{workspaces,tabs}/*.yml`
|
|
186
|
-
3. gem `.ace-defaults/herdr/{workspaces,tabs}/*.yml` -- ships `workspaces/development.yml` and `tabs/agent.yml`
|
|
187
|
-
|
|
188
|
-
A project file with the same name overrides a gem/user file; names unique to a level still appear in the merged listing.
|
|
189
|
-
|
|
190
|
-
### Schema
|
|
191
|
-
|
|
192
|
-
```yaml
|
|
193
|
-
# .ace/herdr/workspaces/development.yml
|
|
194
|
-
label: development # workspace label (defaults to the preset name)
|
|
195
|
-
cwd: /workspace/project # root cwd (tab/panes inherit; ~ expands)
|
|
196
|
-
focus: true # pass --focus to workspace create
|
|
197
|
-
tabs:
|
|
198
|
-
- preset: agent # a tab may inherit a tab preset (recursive; overlay wins)
|
|
199
|
-
label: work # ...and override any field
|
|
200
|
-
- label: editor
|
|
201
|
-
cwd: /workspace/project # tab cwd overrides the root cwd
|
|
202
|
-
focus: false
|
|
203
|
-
panes:
|
|
204
|
-
- label: shell # the FIRST pane is the tab's root pane
|
|
205
|
-
- label: agent
|
|
206
|
-
cwd: /workspace/project
|
|
207
|
-
agent: # declares an agent pane
|
|
208
|
-
kind: pi # default: config default_agent_kind
|
|
209
|
-
name: development-agent
|
|
210
|
-
prompt: Review the current task.
|
|
211
|
-
splits: # every pane after the first must be placed by a split
|
|
212
|
-
- direction: right # herdr-native right|down; horizontal|vertical accepted
|
|
213
|
-
target: shell # pane label to split from (default: the root pane)
|
|
214
|
-
pane: agent # the declared pane placed by this split
|
|
215
|
-
ratio: 0.5
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
```yaml
|
|
219
|
-
# .ace/herdr/tabs/agent.yml -- a tab preset uses the tab-level fields directly
|
|
220
|
-
label: agent
|
|
221
|
-
panes:
|
|
222
|
-
- label: agent
|
|
223
|
-
agent:
|
|
224
|
-
kind: pi
|
|
225
|
-
name: task-agent
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
Creation is deterministic and ordered: the workspace is created first, then tabs/panes in declared order (root pane from `tab create`, further panes from `pane split`, panes renamed to their labels), then pane `command`s run, then declared agents start readiness-gated (`agent start` blocks until the pane is interactive; vs0 bootstrap order: reverse address exported into the pane shell, agent start, optional prompt). herdr seeds every new workspace with an initial tab; once the preset's declared tabs exist that seeded tab is closed, so only declared tabs remain (a preset with no `tabs:` keeps the seeded one). All layouts are validated before any herdr call -- an unplaced pane, a duplicate split placement, an unknown split target, or an unknown direction fails closed and creates nothing.
|
|
229
|
-
|
|
230
|
-
Output: `{"workspace":"w2","tabs":[{"tab":"w2:t1","panes":["w2:p1","w2:p2"],"commands":1,"agents":["development-agent"]}]}`; the tab command reports the single tab object. `--cwd` overrides the resolved root/tab cwd (CLI > tab > root > pane inheritance).
|
|
231
|
-
|
|
232
|
-
Unknown preset: CLI error listing the available names (ace-tmux `--list-presets` parity), for example `Error: Unknown workspace preset 'nope' (available: development)`.
|
|
233
|
-
|
|
234
|
-
Failed tab materialization: when a preset's native tab is created but a later materialization step fails (split, command, or agent start), both `ace-herdr tab` and `ace-herdr workspace` report a standard CLI error -- the process exits non-zero with the underlying failure on stderr, prints no success payload on stdout, and shows no stack trace in ordinary mode. These entrypoints never close tabs (rollback of the failed tab is the runtime adapter's exact-id policy).
|
|
235
|
-
|
|
236
|
-
## `ace-herdr deliver`
|
|
237
|
-
|
|
238
|
-
Push an answer to an agent pane.
|
|
239
|
-
|
|
240
|
-
```bash
|
|
241
|
-
# Answer from a file, explicit ref
|
|
242
|
-
ace-herdr deliver --session ws-1 --pane p5 --event-id evt-1 --answer-file answer.md
|
|
243
|
-
|
|
244
|
-
# Answer from stdin, ref from the environment (inside the asking pane)
|
|
245
|
-
echo 'the answer' | ace-herdr deliver
|
|
246
|
-
|
|
247
|
-
# Bootstrap a specific agent kind if the pane has none
|
|
248
|
-
ace-herdr deliver --session ws-1 --pane p5 --kind codex --label 8wm.t.vs0 --answer-file a.md
|
|
98
|
+
ace-herdr --list-presets
|
|
249
99
|
```
|
|
250
100
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
Output: one JSON line `{"ref":{...},"state":"delivered|retryable|failed"}`. Exit code is non-zero unless the state is `delivered`.
|
|
254
|
-
|
|
255
|
-
### The delivery contract
|
|
256
|
-
|
|
257
|
-
`ace-hitl ask` captures the asker's reverse address fail-closed from the environment (`HERDR_SESSION` / `HERDR_PANE`, schema `ace.hitl.ref/v1`). `ace-herdr deliver` pushes an answer back to that address:
|
|
258
|
-
|
|
259
|
-
1. A write-ahead delivery record carrying the full answer is persisted under `.ace-local/herdr/deliveries/<event-id>.json` (mode 0600, atomic rename) before any herdr contact, so a crash can never lose the answer. A crashed run is recovered with `--resume <event-id>`.
|
|
260
|
-
2. Delivery is idempotent per event id and serialized by a per-event lock: concurrent deliveries prompt once. Re-delivering identical content after a `delivered` record short-circuits without contacting herdr; different content or a different destination for the same event id fails closed.
|
|
261
|
-
3. If the target pane has no agent, one is bootstrapped (`herdr agent start`), the reverse address is exported into the pane shell (`export HERDR_SESSION=... HERDR_PANE=...`; values are token-validated and shell-escaped), and delivery waits for the agent to become idle before prompting.
|
|
262
|
-
4. Transient failures (readiness timeout, `agent_prompt_stalled`, socket/binary unavailability -- at the probe as well as the prompt) persist their history and report `retryable` within `delivery.max_attempts`.
|
|
263
|
-
5. Terminal failures (`agent_blocked` pre-send rejection, missing pane, agent start failure) report `failed` immediately with the error persisted in the record history.
|
|
264
|
-
6. An interrupted run whose last recorded event is a prompt submission without an outcome is ambiguous: the answer may already have been delivered. Re-running reports `failed` ("previous run crashed after submitting") instead of silently resending.
|
|
265
|
-
|
|
266
|
-
Result states follow the ace-hitl contract (spec 8wm.t.vrz §1.2): `delivered`, `retryable` (safe to re-push identical content), `failed` (terminal).
|
|
267
|
-
|
|
268
|
-
## `ace-herdr dispatch`
|
|
269
|
-
|
|
270
|
-
Start an agent in one command: tab + `herdr agent start` + prompt, all with deterministic defaults.
|
|
101
|
+
## List and tidy
|
|
271
102
|
|
|
272
103
|
```bash
|
|
273
|
-
ace-herdr
|
|
274
|
-
ace-herdr
|
|
275
|
-
ace-herdr
|
|
104
|
+
ace-herdr list
|
|
105
|
+
ace-herdr list --tabs --workspace w1
|
|
106
|
+
ace-herdr list --workspaces
|
|
107
|
+
ace-herdr tidy # dry run: report candidates only
|
|
108
|
+
ace-herdr tidy --apply # close eligible panes, archive old delivered records
|
|
276
109
|
```
|
|
277
110
|
|
|
278
|
-
|
|
111
|
+
`tidy` closes panes only on positive completion evidence re-confirmed right
|
|
112
|
+
before closing; unknown, pending, retryable, and failed resources are never
|
|
113
|
+
touched.
|
|
279
114
|
|
|
280
|
-
|
|
115
|
+
## Status
|
|
281
116
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
```bash
|
|
287
|
-
ace-herdr close --pane p5 --rename done # rename, then close
|
|
288
|
-
ace-herdr close --pane p5 --keep --rename wip # rename only
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
## `ace-herdr tidy`
|
|
292
|
-
|
|
293
|
-
Report cleanable agent panes and delivery records as one deterministic JSON line; **dry run by default** -- nothing is closed, moved, or removed without `--apply`.
|
|
117
|
+
`status` composes the three live lists (`workspace_list`, `tab_list`,
|
|
118
|
+
`pane_list`) into one readable tree: workspaces with their tabs and each tab
|
|
119
|
+
with its panes, focused items marked with `*`, agent panes suffixed with
|
|
120
|
+
`agent: <state>`, and home directories shortened to `~`.
|
|
294
121
|
|
|
295
122
|
```bash
|
|
296
|
-
ace-herdr
|
|
297
|
-
ace-herdr
|
|
298
|
-
ace-herdr
|
|
123
|
+
ace-herdr status # whole layout as a table
|
|
124
|
+
ace-herdr status --workspace w1 # one workspace
|
|
125
|
+
ace-herdr status --format json # the composed tree as JSON
|
|
299
126
|
```
|
|
300
127
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
-
|
|
309
|
-
|
|
310
|
-
`
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
128
|
+
Semantics:
|
|
129
|
+
|
|
130
|
+
- A live but empty result prints `No live herdr workspaces.` and exits 0 —
|
|
131
|
+
an empty valid set, not an error.
|
|
132
|
+
- An unreachable runtime (`ExecutorUnavailableError` and other executor
|
|
133
|
+
failures) is reported as an actionable non-zero error, never as an empty
|
|
134
|
+
layout.
|
|
135
|
+
- Counts are recomputed from the composed nesting, and panes whose tab or
|
|
136
|
+
workspace vanished between the list calls are kept in a synthetic
|
|
137
|
+
`(unlinked)` group instead of being dropped.
|
|
138
|
+
- JSON shape: `{"workspaces":[{"id","title","number","focused","tab_count",
|
|
139
|
+
"pane_count","tabs":[{"id","title","number","focused","pane_count","panes":
|
|
140
|
+
[{"id","tab","workspace","title","cwd","focused","agent_status"}]}]}]}`
|
|
141
|
+
with synthetic groups carrying `synthetic: true`.
|
|
142
|
+
|
|
143
|
+
## Planned waves
|
|
144
|
+
|
|
145
|
+
> **Planned (L2):** the unified HITL report/question/reply interface and
|
|
146
|
+
> configurable adapter set are not available yet.
|
|
147
|
+
>
|
|
148
|
+
> The old durable agent inbox (`inbox`) and guarded native-source requirements
|
|
149
|
+
> belong to the replaced protected model and are not part of the file-based
|
|
150
|
+
> flow.
|
|
321
151
|
|
|
322
152
|
## Configuration
|
|
323
153
|
|
|
324
|
-
Defaults (`.ace-defaults/herdr/config.yml`), overridable in `~/.ace/herdr/config.yml` or `.ace/herdr/config.yml`:
|
|
325
|
-
|
|
326
154
|
```yaml
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
max_attempts: 3 # prompt attempts per delivery sequence
|
|
330
|
-
backoff_seconds: [1, 2, 4] # fixed deterministic backoff, no jitter
|
|
155
|
+
# .ace/herdr/config.yml
|
|
156
|
+
default_agent_kind: pi
|
|
331
157
|
timeouts:
|
|
332
|
-
|
|
333
|
-
prompt: 30
|
|
334
|
-
wait: 30 # readiness gate / ace-herdr wait (both wait modes)
|
|
335
|
-
deliveries_dir: .ace-local/herdr/deliveries
|
|
158
|
+
wait: 300
|
|
336
159
|
tidy:
|
|
337
|
-
delivered_retention_days: 7
|
|
160
|
+
delivered_retention_days: 7
|
|
338
161
|
```
|
|
339
162
|
|
|
340
|
-
##
|
|
341
|
-
|
|
342
|
-
Records are JSON, one file per event id under `deliveries_dir` (relative to the working directory, mode 0600): event id, reverse address, SHA-256 answer digest, the full answer (so a crash never loses content), state (`pending` / `delivered` / `retryable` / `failed`), attempt count, and an append-only history of bootstrap, readiness, and prompt events with errors. Records are written atomically and guarded by a per-event lock. Re-running `deliver` with the same event id and content resumes or short-circuits; with different content or a different destination it fails closed.
|
|
343
|
-
|
|
344
|
-
## Error semantics
|
|
345
|
-
|
|
346
|
-
Commands raise a CLI error (non-zero exit) carrying herdr's machine code where one exists:
|
|
347
|
-
|
|
348
|
-
- Unknown pane/tab/workspace: `pane_not_found: ...`, `tab_not_found: ...`, `workspace_not_found: ...`
|
|
349
|
-
- herdr binary or socket unavailable: explicit CLI error, no partial output
|
|
350
|
-
- Blocked agent: `agent_blocked: ...` (terminal -- never silently dropped); stalled prompt: `agent_prompt_stalled: ...` (transient)
|
|
351
|
-
- Output wait timeout: `timeout: ...`
|
|
352
|
-
- Failed tab materialization: CLI error carrying the underlying failure (`ace-herdr tab`, `ace-herdr workspace`); never a false success or a stack trace
|
|
353
|
-
- Unknown preset: usage error listing the available preset names
|
|
354
|
-
- Invalid send shapes: usage error **before any transport call** (nothing is sent)
|
|
355
|
-
|
|
356
|
-
Exit `0` means success (including an explicit empty `list` result or a satisfied wait).
|
|
357
|
-
|
|
358
|
-
Runtime recovery exposes the read-only `process_binding(pane:, caller_pid:)`
|
|
359
|
-
adapter operation, which checks OS ancestry, foreground ownership and the
|
|
360
|
-
original agent-session identity. Message submission is never task completion.
|
|
361
|
-
|
|
362
|
-
## Packaged guarded native source
|
|
363
|
-
|
|
364
|
-
`ace-herdr native-source selection` describes the source selection and patch shipped by this gem. These assets live under `lib/ace/herdr/native_source`; installed consumers do not need task directories. This source retains native version 0.9.3 and protocol 22; its exact source commit and patch digest identify the guarded producer. No upstream release or publication is implied.
|
|
163
|
+
## Testing
|
|
365
164
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
```sh
|
|
369
|
-
ace-herdr native-source build --source /absolute/clean/local/herdr-checkout --output /absolute/new/artifact-directory --target x86_64-unknown-linux-musl
|
|
370
|
-
ace-herdr native-source verify --artifact /absolute/artifact-directory
|
|
165
|
+
```bash
|
|
166
|
+
bin/ace-test ace-herdr
|
|
371
167
|
```
|
|
372
|
-
|
|
373
|
-
The build requires a matching native Linux host (x86_64 or aarch64), the selected Rust and Zig versions, and a native `musl-gcc` toolchain already provisioned by the build owner. Cross builds are unsupported. The output must be new. The builder reproduces the exact baseline plus shipped patch in an isolated checkout, checks the commit/tree/lock/toolchain bytes, pins resolved Rust/Zig/linker paths, invokes the locked release build, and records observed executable/tool digests. It never runs the resulting Herdr binary, changes global tool defaults, contacts a running Herdr server, or publishes an artifact. Locked dependency fetching by Cargo may require network access. Failed builds retain their output claim; a partial directory does not verify.
|
|
374
|
-
|
|
375
|
-
Verification checks bounded strict provenance against this gem's source selection and the observed ELF bytes/architecture. It provides byte provenance, not deployment authorization. The trusted Lab deployment owner separately selects the gem, source selection, artifact and receipt digests before installing the fixed Lab binary. Protected runtime replacement must also use the canonical maintenance admission/release owner. That operational installer join remains required source work; these commands alone do not activate the producer or complete installed acceptance.
|
|
376
|
-
|
|
377
|
-
The selected source also provides `terminal.inhibit_input` for an exact original terminal/origin. Its successful `terminal_input_drained` reply proves permanent input inhibition and zero queued input, including terminal replies; it does not prove child exit or canonical scope cleanup. Lost acknowledgement may be retried only against the same original actor. Source fixtures validate this contract without running a native binary; actual Lab installation and N2 consumption remain required.
|
|
378
|
-
|
|
379
|
-
The inbox context source owner supplies durable operation admission and exclusive key rotation through a fixed authenticated exchange. A lost begin reply recovers the same original process's grant; unknown effects, owner restart and incomplete installation retain blocking state. Replacement and rollback each require an attestation from the actual configured signer plus protected public/config readback. This core does not yet activate a context CLI/server, migrate Assign/private-store consumers, or authorize orphan reclamation. Existing protected mutations must all be migrated before this becomes an accepted end-to-end rotation path; installation and private-key pair handling remain the trusted provisioner's responsibility.
|
|
380
|
-
|
|
381
|
-
### Trusted bounded descriptor handoff
|
|
382
|
-
|
|
383
|
-
Internal source installers using `BoundedProcess.call` may supply
|
|
384
|
-
`descriptor_mapping: {4 => authenticated_readonly_file}` together with bounded
|
|
385
|
-
`stdin_data` carrying exact selected source bytes. The mapping has at most16
|
|
386
|
-
entries; child descriptor keys are strict Integers3..63 and handles must be live
|
|
387
|
-
read-only regular files. Stdio overwrite, writable/closed/nonregular handles and
|
|
388
|
-
ambient descriptor/path fallback refuse before spawn. Standard Open3 spawn maps
|
|
389
|
-
only explicit descriptors; `close_others: true` excludes all other descriptors.
|
|
390
|
-
The supplying verification owner keeps the handle scoped and authenticates the
|
|
391
|
-
selected inode; the runner does not confer authority or authenticate arbitrary
|
|
392
|
-
files. Actual installed privilege-transition/PAM preservation is a separate
|
|
393
|
-
installed acceptance check, not established by controlled file/child tests.
|
|
394
|
-
|
|
395
|
-
### Bounded subprocess cleanup
|
|
396
|
-
|
|
397
|
-
`BoundedProcess.call` retains capped stdout/stderr and uses an execution deadline plus at most one additional second of cleanup. `stderr_limit:` defaults to `output_limit:`. Unconfirmed cleanup raises `PostLaunchError` and transfers only the still-owned original child to an eventual reaper; no success or absence proof follows. Generic successful calls do not newly signal process groups.
|
|
398
|
-
|
|
399
|
-
The fixed protected handler selects `cleanup_group: true`: stream EOF alone never triggers termination. Nonreaping observation must establish the original leader exited before its group is signalled and the leader reaped. Loss of original child ownership refuses further signalling. Controlled source checks cover ownership/IO boundaries; installed native/descendant effectiveness belongs to gad.2.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "ace/support/cli"
|
|
5
|
+
require "ace/core"
|
|
6
|
+
require_relative "support"
|
|
7
|
+
|
|
8
|
+
module Ace
|
|
9
|
+
module Herdr
|
|
10
|
+
module CLI
|
|
11
|
+
module Commands
|
|
12
|
+
class Status < Ace::Support::Cli::Command
|
|
13
|
+
include Ace::Support::Cli::Base
|
|
14
|
+
include Runtime
|
|
15
|
+
|
|
16
|
+
desc <<~DESC.strip
|
|
17
|
+
Show the live herdr layout: workspaces, tabs, and panes in one view
|
|
18
|
+
|
|
19
|
+
Composes workspace/tab/pane lists into a readable tree. A live but
|
|
20
|
+
empty result is a valid state; an unreachable runtime is reported
|
|
21
|
+
as an error, never as an empty layout.
|
|
22
|
+
DESC
|
|
23
|
+
|
|
24
|
+
example [
|
|
25
|
+
"--workspace w1 # One workspace only",
|
|
26
|
+
"--format json # Composed tree as JSON"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
option :workspace, type: :string, desc: "Scope tabs and panes to a workspace id"
|
|
30
|
+
option :format, type: :string, default: "table", desc: "Output format: table or json"
|
|
31
|
+
option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress output"
|
|
32
|
+
|
|
33
|
+
def initialize(executor: nil)
|
|
34
|
+
@executor = executor
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def call(**options)
|
|
38
|
+
translate_errors do
|
|
39
|
+
format = options[:format] || "table"
|
|
40
|
+
cli_error("Invalid format: #{format} (use table or json)") unless %w[table json].include?(format)
|
|
41
|
+
|
|
42
|
+
control = Organisms::ControlSurface.new(executor: executor)
|
|
43
|
+
scope = options[:workspace]
|
|
44
|
+
tree = Molecules::StatusComposer.call(
|
|
45
|
+
workspaces: control.list_workspaces,
|
|
46
|
+
tabs: control.list_tabs(workspace_id: scope),
|
|
47
|
+
panes: control.list_panes(workspace_id: scope)
|
|
48
|
+
)
|
|
49
|
+
tree = scope_tree(tree, scope) if scope
|
|
50
|
+
|
|
51
|
+
return if options[:quiet]
|
|
52
|
+
|
|
53
|
+
if format == "json"
|
|
54
|
+
puts JSON.generate(tree)
|
|
55
|
+
else
|
|
56
|
+
puts render_table(tree)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Scoping must never conflate "scoped away" with "empty": the
|
|
64
|
+
# rendered tree contains only the requested workspace (header
|
|
65
|
+
# counts included), and an id no live workspace carries is a
|
|
66
|
+
# concrete error, not a silently full or empty layout.
|
|
67
|
+
def scope_tree(tree, scope)
|
|
68
|
+
live_ids = tree[:workspaces].reject { |workspace| workspace[:synthetic] }.map { |workspace| workspace[:id] }
|
|
69
|
+
cli_error("Unknown workspace: #{scope} (no live workspace with that id)") unless live_ids.include?(scope)
|
|
70
|
+
|
|
71
|
+
tree[:workspaces] = tree[:workspaces].select { |workspace| workspace[:id] == scope }
|
|
72
|
+
tree
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def render_table(tree)
|
|
76
|
+
workspaces = tree[:workspaces]
|
|
77
|
+
return "No live herdr workspaces." if workspaces.empty?
|
|
78
|
+
|
|
79
|
+
tab_total = workspaces.sum { |workspace| workspace[:tab_count] }
|
|
80
|
+
pane_total = workspaces.sum { |workspace| workspace[:pane_count] }
|
|
81
|
+
lines = ["Herdr: #{workspaces.length} workspace(s), #{tab_total} tab(s), #{pane_total} pane(s)"]
|
|
82
|
+
workspaces.each do |workspace|
|
|
83
|
+
lines << format(
|
|
84
|
+
"%s %-14s %-16s %d tab(s), %d pane(s)",
|
|
85
|
+
workspace[:focused] ? "*" : " ", workspace[:id], workspace[:title].to_s,
|
|
86
|
+
workspace[:tab_count], workspace[:pane_count]
|
|
87
|
+
)
|
|
88
|
+
workspace[:tabs].each do |tab|
|
|
89
|
+
lines << format(
|
|
90
|
+
" %s %-18s %-14s %d pane(s)",
|
|
91
|
+
tab[:focused] ? "*" : " ", tab[:id], tab[:title].to_s, tab[:pane_count]
|
|
92
|
+
)
|
|
93
|
+
tab[:panes].each do |pane|
|
|
94
|
+
lines << pane_line(pane)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
lines.join("\n")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def pane_line(pane)
|
|
102
|
+
title = pane[:title].to_s.empty? ? "—" : pane[:title]
|
|
103
|
+
line = format(
|
|
104
|
+
" %s %-8s %-14s %s",
|
|
105
|
+
pane[:focused] ? "*" : " ", pane[:id], title, shorten_home(pane[:cwd].to_s)
|
|
106
|
+
)
|
|
107
|
+
line += " agent: #{pane[:agent_status]}" if pane[:agent_status]
|
|
108
|
+
line
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def shorten_home(path)
|
|
112
|
+
home = File.join(Dir.home, "")
|
|
113
|
+
path.start_with?(home) ? path.sub(home, "~/") : path
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
data/lib/ace/herdr/cli.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "cli/commands/wait"
|
|
|
10
10
|
require_relative "cli/commands/close"
|
|
11
11
|
require_relative "cli/commands/tidy"
|
|
12
12
|
require_relative "cli/commands/list"
|
|
13
|
+
require_relative "cli/commands/status"
|
|
13
14
|
require_relative "cli/commands/send"
|
|
14
15
|
require_relative "cli/commands/capture"
|
|
15
16
|
require_relative "cli/commands/workspace"
|
|
@@ -32,6 +33,7 @@ module Ace
|
|
|
32
33
|
["dispatch", "Start an agent in one command: tab + agent + prompt"],
|
|
33
34
|
["native-source", "Describe, build, or verify the packaged guarded native source"],
|
|
34
35
|
["list", "List live panes, tabs, or workspaces as one JSON line"],
|
|
36
|
+
["status", "Show live herdr layout: workspaces, tabs, and panes in one view"],
|
|
35
37
|
["send", "Send a command, raw text, or named keys to a pane"],
|
|
36
38
|
["capture", "Print recent pane output as raw text"],
|
|
37
39
|
["wait", "Wait for an agent state or matching pane output"],
|
|
@@ -48,6 +50,7 @@ module Ace
|
|
|
48
50
|
"echo 'the answer' | ace-herdr deliver --pane p5",
|
|
49
51
|
"ace-herdr dispatch --label 8wm.t.vs0 --kind pi --prompt-file prompt.md",
|
|
50
52
|
"ace-herdr list --workspace w1",
|
|
53
|
+
"ace-herdr status --workspace w1",
|
|
51
54
|
"ace-herdr send --pane p5 --cmd 'bundle exec rake test'",
|
|
52
55
|
"ace-herdr send --pane p5 --msg 'continue' --key Enter",
|
|
53
56
|
"ace-herdr capture --pane p5 --lines 40",
|
|
@@ -73,6 +76,7 @@ module Ace
|
|
|
73
76
|
register "dispatch", CLI::Commands::Dispatch.new
|
|
74
77
|
register "native-source", CLI::Commands::NativeSource.new
|
|
75
78
|
register "list", CLI::Commands::List.new
|
|
79
|
+
register "status", CLI::Commands::Status.new
|
|
76
80
|
register "send", CLI::Commands::Send.new
|
|
77
81
|
register "capture", CLI::Commands::Capture.new
|
|
78
82
|
register "wait", CLI::Commands::Wait.new
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Herdr
|
|
5
|
+
module Molecules
|
|
6
|
+
# Pure join of the three normalized live lists (workspaces, tabs,
|
|
7
|
+
# panes) into one nested status tree. Counts are recomputed from the
|
|
8
|
+
# nesting so the tree is internally consistent even if the runtime's
|
|
9
|
+
# own counters raced; panes referencing a tab or workspace missing
|
|
10
|
+
# from the lists are kept in a deterministic synthetic "(unlinked)"
|
|
11
|
+
# group instead of being dropped (task 8xg.t.sbz.6.0: a source gap
|
|
12
|
+
# must stay visible, never become a "clean" empty layout).
|
|
13
|
+
class StatusComposer
|
|
14
|
+
UNLINKED = "(unlinked)"
|
|
15
|
+
|
|
16
|
+
# @param workspaces [Array<Hash>] normalized workspace hashes
|
|
17
|
+
# @param tabs [Array<Hash>] normalized tab hashes
|
|
18
|
+
# @param panes [Array<Hash>] normalized pane hashes
|
|
19
|
+
# @return [Hash] {workspaces: [...]} with nested tabs/panes and
|
|
20
|
+
# recomputed counts
|
|
21
|
+
def self.call(workspaces:, tabs:, panes:)
|
|
22
|
+
new.compose(workspaces: workspaces, tabs: tabs, panes: panes)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def compose(workspaces:, tabs:, panes:)
|
|
26
|
+
panes_by_tab = group_panes(panes)
|
|
27
|
+
tabs_by_workspace = group_tabs(tabs, panes_by_tab)
|
|
28
|
+
|
|
29
|
+
roots = build_workspaces(workspaces, tabs_by_workspace, panes_by_tab)
|
|
30
|
+
|
|
31
|
+
synthetic_root = build_synthetic_root(tabs_by_workspace, panes_by_tab)
|
|
32
|
+
roots << synthetic_root if synthetic_root
|
|
33
|
+
|
|
34
|
+
{workspaces: roots}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def group_panes(panes)
|
|
40
|
+
panes.group_by { |pane| pane[:tab] }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def group_tabs(tabs, panes_by_tab)
|
|
44
|
+
# Tabs carry the panes nested under them; orphan panes whose tab
|
|
45
|
+
# is absent from the tab list get a synthetic tab inside their
|
|
46
|
+
# own workspace so the pane stays visible.
|
|
47
|
+
tabs_by_workspace = Hash.new { |hash, key| hash[key] = [] }
|
|
48
|
+
tabs.each do |tab|
|
|
49
|
+
tabs_by_workspace[tab[:workspace]] << compose_tab(tab, panes_by_tab.delete(tab[:id]) || [])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
panes_by_tab.each do |tab_id, orphan_panes|
|
|
53
|
+
# Panes sharing a vanished tab id may still disagree on their
|
|
54
|
+
# workspace (snapshot race): each workspace keeps its own
|
|
55
|
+
# synthetic tab so neither side silently undercounts.
|
|
56
|
+
orphan_panes.group_by { |pane| pane[:workspace] }.each do |workspace_id, workspace_panes|
|
|
57
|
+
tabs_by_workspace[workspace_id] << synthetic_tab(tab_id, workspace_id, workspace_panes)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
tabs_by_workspace
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def build_workspaces(workspaces, tabs_by_workspace, panes_by_tab)
|
|
64
|
+
ordered(workspaces).map do |workspace|
|
|
65
|
+
nested_tabs = sort_tabs(tabs_by_workspace.delete(workspace[:id]) || [])
|
|
66
|
+
compose_workspace(workspace, nested_tabs)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def build_synthetic_root(tabs_by_workspace, panes_by_tab)
|
|
71
|
+
return nil if tabs_by_workspace.empty?
|
|
72
|
+
|
|
73
|
+
nested_tabs = sort_tabs(tabs_by_workspace.values.flatten)
|
|
74
|
+
compose_workspace(
|
|
75
|
+
{id: UNLINKED, title: UNLINKED, number: nil, focused: nil, synthetic: true},
|
|
76
|
+
nested_tabs
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def compose_workspace(workspace, nested_tabs)
|
|
81
|
+
pane_count = nested_tabs.sum { |tab| tab[:pane_count] }
|
|
82
|
+
{
|
|
83
|
+
id: workspace[:id],
|
|
84
|
+
title: workspace[:title],
|
|
85
|
+
number: workspace[:number],
|
|
86
|
+
focused: workspace[:focused],
|
|
87
|
+
synthetic: workspace[:synthetic] || false,
|
|
88
|
+
tab_count: nested_tabs.length,
|
|
89
|
+
pane_count: pane_count,
|
|
90
|
+
tabs: nested_tabs
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def compose_tab(tab, nested_panes)
|
|
95
|
+
nested_panes = ordered(nested_panes)
|
|
96
|
+
{
|
|
97
|
+
id: tab[:id],
|
|
98
|
+
title: tab[:title],
|
|
99
|
+
number: tab[:number],
|
|
100
|
+
focused: tab[:focused],
|
|
101
|
+
synthetic: tab[:synthetic] || false,
|
|
102
|
+
pane_count: nested_panes.length,
|
|
103
|
+
panes: nested_panes
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def synthetic_tab(tab_id, workspace_id, orphan_panes)
|
|
108
|
+
{
|
|
109
|
+
id: tab_id,
|
|
110
|
+
workspace: workspace_id,
|
|
111
|
+
title: UNLINKED,
|
|
112
|
+
number: nil,
|
|
113
|
+
focused: nil,
|
|
114
|
+
synthetic: true
|
|
115
|
+
}.then { |tab| compose_tab(tab, orphan_panes) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def sort_tabs(tabs)
|
|
119
|
+
# Synthetic tabs sort last within their workspace
|
|
120
|
+
synthetic, natural = tabs.partition { |tab| tab[:synthetic] }
|
|
121
|
+
ordered(natural) + ordered(synthetic)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Deterministic ordering: number, then id; nil numbers last
|
|
125
|
+
def ordered(items)
|
|
126
|
+
items.sort_by { |item| [item[:number] ? 0 : 1, item[:number] || 0, item[:id].to_s] }
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "shellwords"
|
|
4
|
+
require "ace/support/cli/toolchain"
|
|
4
5
|
|
|
5
6
|
module Ace
|
|
6
7
|
module Herdr
|
|
@@ -364,7 +365,7 @@ module Ace
|
|
|
364
365
|
name = agent["name"] || pane[:spec]["label"] || "agent"
|
|
365
366
|
@executor.pane_run(
|
|
366
367
|
pane_id,
|
|
367
|
-
"export HERDR_SESSION=#{Shellwords.escape(workspace_id.to_s)} " \
|
|
368
|
+
Ace::Support::Cli::Toolchain.shell_setup + "export HERDR_SESSION=#{Shellwords.escape(workspace_id.to_s)} " \
|
|
368
369
|
"HERDR_PANE=#{Shellwords.escape(pane_id)}"
|
|
369
370
|
)
|
|
370
371
|
@executor.agent_start(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "shellwords"
|
|
4
|
+
require "ace/support/cli/toolchain"
|
|
4
5
|
require "time"
|
|
5
6
|
require "ace/hitl/contract"
|
|
6
7
|
|
|
@@ -179,7 +180,7 @@ module Ace
|
|
|
179
180
|
# delivery; otherwise the record to prompt. Bootstrap failures are
|
|
180
181
|
# terminal: an immediate retry cannot heal a broken pane.
|
|
181
182
|
def bootstrap_and_wait(ref, record, kind, label)
|
|
182
|
-
export = "export HERDR_SESSION=#{Shellwords.escape(ref.session)} " \
|
|
183
|
+
export = Ace::Support::Cli::Toolchain.shell_setup + "export HERDR_SESSION=#{Shellwords.escape(ref.session)} " \
|
|
183
184
|
"HERDR_PANE=#{Shellwords.escape(ref.pane)}"
|
|
184
185
|
begin
|
|
185
186
|
@executor.pane_run(ref.pane, export)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "shellwords"
|
|
4
|
+
require "ace/support/cli/toolchain"
|
|
4
5
|
require "ace/hitl/contract"
|
|
5
6
|
|
|
6
7
|
module Ace
|
|
@@ -54,7 +55,7 @@ module Ace
|
|
|
54
55
|
|
|
55
56
|
@executor.pane_run(
|
|
56
57
|
pane,
|
|
57
|
-
"export HERDR_SESSION=#{Shellwords.escape(workspace_id)} " \
|
|
58
|
+
Ace::Support::Cli::Toolchain.shell_setup + "export HERDR_SESSION=#{Shellwords.escape(workspace_id)} " \
|
|
58
59
|
"HERDR_PANE=#{Shellwords.escape(pane)}"
|
|
59
60
|
)
|
|
60
61
|
@executor.agent_start(
|
data/lib/ace/herdr/version.rb
CHANGED
data/lib/ace/herdr.rb
CHANGED
|
@@ -25,6 +25,7 @@ require_relative "herdr/organisms/inbox_context_owner"
|
|
|
25
25
|
require_relative "herdr/organisms/inbox_context_server"
|
|
26
26
|
require_relative "herdr/molecules/inbox_context_client"
|
|
27
27
|
require_relative "herdr/organisms/dispatcher"
|
|
28
|
+
require_relative "herdr/molecules/status_composer"
|
|
28
29
|
require_relative "herdr/organisms/control_surface"
|
|
29
30
|
require_relative "herdr/organisms/runtime_adapter"
|
|
30
31
|
require_relative "herdr/organisms/tidy"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-herdr
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-20 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: websocket-driver
|
|
@@ -78,6 +78,9 @@ dependencies:
|
|
|
78
78
|
- - "~>"
|
|
79
79
|
- !ruby/object:Gem::Version
|
|
80
80
|
version: '0.6'
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: 0.6.10
|
|
81
84
|
type: :runtime
|
|
82
85
|
prerelease: false
|
|
83
86
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -85,6 +88,9 @@ dependencies:
|
|
|
85
88
|
- - "~>"
|
|
86
89
|
- !ruby/object:Gem::Version
|
|
87
90
|
version: '0.6'
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: 0.6.10
|
|
88
94
|
- !ruby/object:Gem::Dependency
|
|
89
95
|
name: ace-support-core
|
|
90
96
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -230,6 +236,7 @@ files:
|
|
|
230
236
|
- lib/ace/herdr/cli/commands/list_presets.rb
|
|
231
237
|
- lib/ace/herdr/cli/commands/native_source.rb
|
|
232
238
|
- lib/ace/herdr/cli/commands/send.rb
|
|
239
|
+
- lib/ace/herdr/cli/commands/status.rb
|
|
233
240
|
- lib/ace/herdr/cli/commands/support.rb
|
|
234
241
|
- lib/ace/herdr/cli/commands/tab.rb
|
|
235
242
|
- lib/ace/herdr/cli/commands/tidy.rb
|
|
@@ -261,6 +268,7 @@ files:
|
|
|
261
268
|
- lib/ace/herdr/molecules/preset_resolver.rb
|
|
262
269
|
- lib/ace/herdr/molecules/protected_inbox_selection.rb
|
|
263
270
|
- lib/ace/herdr/molecules/protected_native_control.rb
|
|
271
|
+
- lib/ace/herdr/molecules/status_composer.rb
|
|
264
272
|
- lib/ace/herdr/native_source/HERDR-LICENSE
|
|
265
273
|
- lib/ace/herdr/native_source/guarded-prompt.patch
|
|
266
274
|
- lib/ace/herdr/native_source/selection.json
|