pi-daddy 0.15.0 → 0.16.0
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.
- package/CHANGELOG.md +91 -0
- package/README.md +27 -12
- package/dist/cli.js +0 -0
- package/dist/executor.d.ts +38 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +93 -0
- package/dist/executor.js.map +1 -0
- package/dist/herdr-cli.d.ts +78 -0
- package/dist/herdr-cli.d.ts.map +1 -0
- package/dist/herdr-cli.js +113 -0
- package/dist/herdr-cli.js.map +1 -0
- package/dist/herdr-name.d.ts +37 -0
- package/dist/herdr-name.d.ts.map +1 -0
- package/dist/herdr-name.js +59 -0
- package/dist/herdr-name.js.map +1 -0
- package/dist/herdr-poll.d.ts +104 -0
- package/dist/herdr-poll.d.ts.map +1 -0
- package/dist/herdr-poll.js +150 -0
- package/dist/herdr-poll.js.map +1 -0
- package/dist/herdr-stage.d.ts +40 -0
- package/dist/herdr-stage.d.ts.map +1 -0
- package/dist/herdr-stage.js +54 -0
- package/dist/herdr-stage.js.map +1 -0
- package/dist/ledger-report.d.ts +18 -0
- package/dist/ledger-report.d.ts.map +1 -1
- package/dist/ledger-report.js +10 -0
- package/dist/ledger-report.js.map +1 -1
- package/dist/ledger.d.ts +17 -0
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +1 -0
- package/dist/ledger.js.map +1 -1
- package/dist/pane-reaper.d.ts +66 -4
- package/dist/pane-reaper.d.ts.map +1 -1
- package/dist/pane-reaper.js +131 -9
- package/dist/pane-reaper.js.map +1 -1
- package/dist/progress.d.ts +96 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +167 -0
- package/dist/progress.js.map +1 -0
- package/dist/run-child.d.ts +27 -0
- package/dist/run-child.d.ts.map +1 -1
- package/dist/run-child.js +84 -7
- package/dist/run-child.js.map +1 -1
- package/dist/run-herdr.d.ts +41 -28
- package/dist/run-herdr.d.ts.map +1 -1
- package/dist/run-herdr.js +150 -167
- package/dist/run-herdr.js.map +1 -1
- package/extensions/delegation.ts +94 -2
- package/extensions/grants-command.ts +26 -1
- package/extensions/grants.ts +85 -163
- package/extensions/run-delegation.ts +70 -8
- package/extensions/session-report.ts +231 -0
- package/extensions/session.ts +63 -11
- package/extensions/tripwire.ts +44 -0
- package/package.json +17 -1
- package/src/executor.ts +122 -0
- package/src/herdr-cli.ts +125 -0
- package/src/herdr-name.ts +61 -0
- package/src/herdr-poll.ts +185 -0
- package/src/herdr-stage.ts +55 -0
- package/src/ledger-report.ts +21 -0
- package/src/ledger.ts +18 -0
- package/src/pane-reaper.ts +147 -9
- package/src/progress.ts +206 -0
- package/src/run-child.ts +96 -7
- package/src/run-herdr.ts +170 -174
package/CHANGELOG.md
CHANGED
|
@@ -93,6 +93,97 @@ resolution, enforcement, approvals or the ledger changed — this is the part be
|
|
|
93
93
|
defect as the `exports` map that worked in the tree and threw for every consumer, and the second time that
|
|
94
94
|
script has caught it.
|
|
95
95
|
|
|
96
|
+
## 0.16.0 — children you can watch, in panes chosen for you
|
|
97
|
+
|
|
98
|
+
**If herdr is running, your sub-agents now run in herdr panes without you configuring anything — and the
|
|
99
|
+
parent shows what each one is doing while it works.** Two ADRs, shipped together.
|
|
100
|
+
|
|
101
|
+
### `PI_GRANTS_HERDR` is three-state, and unset now means *probe* (ADR-0031)
|
|
102
|
+
|
|
103
|
+
| Value | Behaviour |
|
|
104
|
+
| :--- | :--- |
|
|
105
|
+
| unset | Probe once at session start (`herdr tab list`, 2s bound). A server that **answers** ⇒ herdr panes; anything else ⇒ captured subprocess. |
|
|
106
|
+
| `1` | Demand herdr. **Every delegation refuses** if it is unreachable — no fallback. |
|
|
107
|
+
| `0` | Demand the captured subprocess. No probe. |
|
|
108
|
+
|
|
109
|
+
**Not a `PATH` check.** A binary with no server behind it would make every delegation fail at `tab create`, on
|
|
110
|
+
a path nobody chose — so only a *reachable* server counts. This reverses part of ADR-0016 point 6, which
|
|
111
|
+
refused auto-detection on the grounds that a run must not "silently relocate"; the answer to *silently* is that
|
|
112
|
+
the executor is now named at session start, in `/grants`, and per child in the ledger.
|
|
113
|
+
|
|
114
|
+
**What to do about it:** nothing, unless you relied on an unset variable meaning subprocesses. If you did, set
|
|
115
|
+
`PI_GRANTS_HERDR=0`.
|
|
116
|
+
|
|
117
|
+
A stale `PI_GRANTS_HERDR=1` in a shell profile still breaks delegation on a machine without herdr — **as it did in
|
|
118
|
+
0.15.0**, where every child failed at `tab create`. There was never a fallback to lose. What changed is that the
|
|
119
|
+
failure is reported at session start, names the variable, and says what to set instead, rather than surfacing as a
|
|
120
|
+
per-delegation spawn error.
|
|
121
|
+
|
|
122
|
+
### A running delegation is visible (ADR-0032)
|
|
123
|
+
|
|
124
|
+
Both tools discarded pi's `onUpdate`, so a delegation showed the bare word `delegate` from the call until the
|
|
125
|
+
result — up to ten minutes, and the same one word for all eight children of a `delegate_all`. Now there is one
|
|
126
|
+
status block per call, redrawn in place, with a three-line tail per child and its herdr agent and pane id:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
2 children · herdr panes
|
|
130
|
+
|
|
131
|
+
review agent review-d0.1 pane w7:t12 running 0:42
|
|
132
|
+
3 findings so far: unchecked nil at
|
|
133
|
+
session.ts:88, missing expiry compare…
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Both executors stream. The block is a **display, never the result** — the answer is still what the child
|
|
137
|
+
returned.
|
|
138
|
+
|
|
139
|
+
### Panes live until you get your prompt back
|
|
140
|
+
|
|
141
|
+
A pane used to be destroyed the instant its child settled, so a twenty-second child's pane was gone before
|
|
142
|
+
anyone could switch to it. Panes now belong to the **agent run** and are swept at `agent_settled`, capped at 8
|
|
143
|
+
at once, with process `exit` as the backstop. `PI_GRANTS_HERDR_KEEP_PANE=1` still means *not even then*.
|
|
144
|
+
|
|
145
|
+
A child's pane also defaults to the **parent's own herdr workspace** now, so switching to one is a tab away
|
|
146
|
+
rather than a workspace away.
|
|
147
|
+
|
|
148
|
+
### Fixed before release — eighteen defects from six independent reviewers
|
|
149
|
+
|
|
150
|
+
Everything above was implemented, then attacked by six reviewers with one written hypothesis each. Two were
|
|
151
|
+
shipping blockers, and they change what the herdr path guarantees:
|
|
152
|
+
|
|
153
|
+
- **`herdr agent stop` does not exist.** Measured against herdr 0.7.5. Three call sites issued it for nothing, and
|
|
154
|
+
`docs/probes/g16-herdr` asserted it worked — from a rerun block that was never run. Closing the tab is the only
|
|
155
|
+
kill herdr offers, so an unsettled child now loses its tab at once; leaving it would have left a governed child
|
|
156
|
+
working with its grant after its result was reported.
|
|
157
|
+
- **herdr binds an agent name to its tab.** With panes outliving their calls, the **second `delegate` of every
|
|
158
|
+
turn** failed with `agent_name_taken`. Names are now unique per spawn.
|
|
159
|
+
- **The 8-pane cap killed live siblings**, because pi runs tool calls in parallel by default. Only *settled* panes
|
|
160
|
+
are reclaimable now; if they are all live the cap yields rather than enforcing.
|
|
161
|
+
- **The pane reader amplified output 89,000×** once a pane scrolled or passed the output cap: `agent read` returns
|
|
162
|
+
a snapshot of a bounded terminal and was being diffed as an append-only stream.
|
|
163
|
+
- **The output cap counted bytes but truncated by UTF-16 code units** — 2048 bytes through the 1024 default on
|
|
164
|
+
non-ASCII — and a multi-byte character split across a pipe boundary became U+FFFD **in the child's answer**.
|
|
165
|
+
Both pre-existing, both fixed.
|
|
166
|
+
- **A failed pane read came back as the child's successful answer**, and a truncated pane never said so.
|
|
167
|
+
- **The session-start executor line never reached pi's TUI**, because consecutive `info` notifies overwrite each
|
|
168
|
+
other — which also means `holding [...]` has been silently overwritten since the spawnable summary was added.
|
|
169
|
+
All info lines are now one message.
|
|
170
|
+
- **The approval dialog ran before the refusal**, so a demanded-but-unreachable herdr could bank a 30-day approval
|
|
171
|
+
for a delegation it then refused.
|
|
172
|
+
|
|
173
|
+
Six tests that could not fail were rewritten, each re-verified by re-applying the mutation that had defeated it,
|
|
174
|
+
and coverage was added where it was simply absent — including that nothing verified a real spawn records
|
|
175
|
+
`executor: "herdr"`. 461 unit tests, up from 442.
|
|
176
|
+
|
|
177
|
+
### Also
|
|
178
|
+
|
|
179
|
+
- **`/grants ledger` tallies executors**, so "which children ran in panes?" no longer needs `jq`.
|
|
180
|
+
- **The tripwire names `delegate_all`.** It said only *"Use `delegate` instead"*, and a request for parallel
|
|
181
|
+
work was answered with a single sequential call as a result.
|
|
182
|
+
- **The ledger records `executor` per child** — required, not optional. The executor is decided by a probe now,
|
|
183
|
+
so nothing outside the record preserves which one ran, and the two paths do not produce the same argv.
|
|
184
|
+
- **R-62 re-rated L×L → M×L.** Its "low severity" rested on the herdr executor being opt-in, which is no
|
|
185
|
+
longer true. The failure is unchanged; how often anyone meets it is not.
|
|
186
|
+
|
|
96
187
|
## 0.15.0 — `/grants init`, a grant that survives without an env var, and a setup that was wrong
|
|
97
188
|
|
|
98
189
|
**Setup is two steps instead of five, and one of the five was wrong.**
|
package/README.md
CHANGED
|
@@ -183,11 +183,19 @@ delegate_all({ children: [ {…}, {…}, {…} ] }) // several
|
|
|
183
183
|
|
|
184
184
|
### Two executors, one plan
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
Either a captured child process, or a visible, attachable **herdr** pane — the same governed argv, the same
|
|
187
|
+
`--tools` enforcement, somewhere you can watch it.
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
**Which one runs is decided by a probe (ADR-0031).** `PI_GRANTS_HERDR` is three-state: **unset** probes once at
|
|
190
|
+
session start (`herdr tab list`, 2s bound) and uses panes if a server *answers*; **`1`** demands herdr and
|
|
191
|
+
**refuses every delegation** if it is unreachable, rather than quietly relocating; **`0`** demands subprocesses and
|
|
192
|
+
skips the probe. Which one was chosen is printed at session start, shown by `/grants`, and recorded per child in
|
|
193
|
+
the ledger.
|
|
194
|
+
|
|
195
|
+
Still **never auto-detected from `herdr` being on `PATH`** — a binary with no server behind it would make every
|
|
196
|
+
delegation fail at `tab create`, on a path nobody chose. Only a reachable server counts.
|
|
197
|
+
|
|
198
|
+
A child's pane goes in **your own herdr workspace** by default, so switching to one is a tab away. Constraints found by building it are in `docs/probes/g16-herdr` — herdr has
|
|
191
199
|
no `--env` (the grant rides on the pane, which the agent's shell inherits), `agent start` types argv into a
|
|
192
200
|
shell so a multi-line argument must be staged to a file, and `agent wait --until idle` matches the state the
|
|
193
201
|
agent was *already* in, so settling requires a state counter to advance.
|
|
@@ -461,9 +469,9 @@ everything below it.
|
|
|
461
469
|
| `PI_GRANTS_CHILD_TIMEOUT` | `600` (seconds) | Wall-clock limit for a child. Inherited by descendants — an operator preference, deliberately *not* attenuating state. |
|
|
462
470
|
| `PI_GRANTS_FANOUT` | `8` | **Subtree budget**: total descendants this session may create. Attenuates downward like depth. Malformed or `0` falls back to the default — a bound a typo can switch off is not a bound. |
|
|
463
471
|
| `PI_GRANTS_PARENT_ID` | `d0` | This session's ledger id; set by the parent. Makes sibling records joinable into a tree. |
|
|
464
|
-
| `PI_GRANTS_HERDR` | unset |
|
|
465
|
-
| `PI_GRANTS_HERDR_WORKSPACE` |
|
|
466
|
-
| `PI_GRANTS_HERDR_KEEP_PANE` | unset | `1` keeps each child's pane for inspection. Off by default: a fan-out would flood the workspace. |
|
|
472
|
+
| `PI_GRANTS_HERDR` | unset ⇒ **probe** | Three-state. Unset probes for a reachable herdr and uses panes if one answers; `1` demands panes and refuses every delegation if herdr is unreachable; `0` demands captured subprocesses. Never detected from `herdr` merely being on `PATH`. |
|
|
473
|
+
| `PI_GRANTS_HERDR_WORKSPACE` | the parent's `HERDR_WORKSPACE_ID` | herdr workspace for spawned panes. Defaults to the workspace this session is in, so a child is a tab away rather than a workspace away. |
|
|
474
|
+
| `PI_GRANTS_HERDR_KEEP_PANE` | unset | `1` keeps each child's pane for inspection, and no sweep closes it. Off by default: a fan-out would flood the workspace. |
|
|
467
475
|
| `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi's own variable, not ours — but it decides where persisted approvals live, so it is listed here. |
|
|
468
476
|
|
|
469
477
|
**A malformed value disables spawning; it never falls back to a default.** An unreadable
|
|
@@ -669,11 +677,18 @@ Known gaps, stated because a gap nobody wrote down is the one that surprises som
|
|
|
669
677
|
- **`bash` escapes governance.** Out of scope by decision (ADR-0012).
|
|
670
678
|
- **`subagents:rpc:spawn` bypasses the tripwire.** Unfixable from here.
|
|
671
679
|
- **The ledger is verified at session start** when one is configured: a damaged trail announces itself, an intact one stays quiet.
|
|
672
|
-
- **
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
680
|
+
- **A pane outlives its tool call only if its child settled.** A child that answered keeps its pane so you can
|
|
681
|
+
read it, and it is swept when you get your prompt back (`agent_settled`), with process `exit` as a backstop. A
|
|
682
|
+
child that did **not** settle — timeout, abort, failed start — loses its tab at once, because closing the tab is
|
|
683
|
+
the only way to stop a herdr agent (`herdr agent stop` does not exist). At most 8 panes are open at once, and
|
|
684
|
+
only *settled* ones are ever reclaimed: if they are all live the cap yields rather than killing a child.
|
|
685
|
+
- **Pane cleanup does not cover being killed outright.** SIGKILL, and a SIGTERM nothing else is listening for,
|
|
686
|
+
run no `exit` handlers — by Node's design — so a pane can be orphaned; `herdr tab close <id>` is the remedy. No
|
|
687
|
+
signal handler is installed, deliberately: one here would suppress Node's default termination and turn pi's
|
|
688
|
+
*"interrupt this turn"* into *"exit pi"* (R-62, re-rated M×L now that panes are the default path).
|
|
689
|
+
- **A running delegation is visible.** One status block per call — per child: its definition, its herdr agent, its
|
|
690
|
+
pane id, its state, elapsed time, and the last three lines it printed. Bounded in height and width, so a fan-out
|
|
691
|
+
cannot flood your screen. It is a **display, never the result**.
|
|
677
692
|
- **A definition's *instructions* are governed only by identity.** `agent:<name>` says which file may be
|
|
678
693
|
spawned and the digest says which version ran, but nothing reads a body and judges what it says — the
|
|
679
694
|
operator authorises a file, and its contents are their responsibility.
|
package/dist/cli.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which executor runs a governed child — ADR-0031.
|
|
3
|
+
*
|
|
4
|
+
* `PI_GRANTS_HERDR` is three-state, and **absent means probe**. That reverses ADR-0016 point 6's opt-in, and
|
|
5
|
+
* the reversal is narrower than it sounds: nothing is detected from `herdr` being on `PATH` (option C, rejected
|
|
6
|
+
* by name), only from a server that *answered*. The grant, the depth bound, the gate and `--tools` enforcement
|
|
7
|
+
* are identical either way — `planSpawn` produces one plan and both executors enforce it.
|
|
8
|
+
*
|
|
9
|
+
* **Pure on purpose.** The probe is I/O and lives in `herdr-cli.ts`; the DECISION is a table, and a table that
|
|
10
|
+
* fits on one screen is the only reason a reversal like this is reviewable at all.
|
|
11
|
+
*
|
|
12
|
+
* The disclosure string is part of the return value rather than composed at the call site, because ADR-0031's
|
|
13
|
+
* defence against "this relocates silently" is that every outcome says what it chose AND what to set instead.
|
|
14
|
+
* Two call sites composing that separately is how one of them comes to omit it (R-28).
|
|
15
|
+
*/
|
|
16
|
+
import type { HerdrProbe } from "./herdr-cli.ts";
|
|
17
|
+
export declare const ENV_HERDR = "PI_GRANTS_HERDR";
|
|
18
|
+
export type ExecutorKind = "herdr" | "process";
|
|
19
|
+
export interface ExecutorChoice {
|
|
20
|
+
kind: ExecutorKind;
|
|
21
|
+
/** The operator named it: `PI_GRANTS_HERDR` was exactly `0` or `1`. */
|
|
22
|
+
forced: boolean;
|
|
23
|
+
/** Whether a probe was needed at all — false only for `0`. */
|
|
24
|
+
probed: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Set ONLY when herdr was demanded and is unreachable. Every delegation must refuse with this.
|
|
27
|
+
*
|
|
28
|
+
* Note that `kind` stays `"herdr"` in that case, deliberately: nothing downstream may mistake a refusing
|
|
29
|
+
* session for a working process-executor one.
|
|
30
|
+
*/
|
|
31
|
+
refusal?: string;
|
|
32
|
+
/** One line for the session banner and `/grants`. Always present. */
|
|
33
|
+
disclosure: string;
|
|
34
|
+
}
|
|
35
|
+
/** `0` is the one value that needs no probe: the operator ruled herdr out, so asking is pure cost. */
|
|
36
|
+
export declare function needsProbe(raw: string | undefined): boolean;
|
|
37
|
+
export declare function chooseExecutor(raw: string | undefined, probe: HerdrProbe | null): ExecutorChoice;
|
|
38
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,eAAO,MAAM,SAAS,oBAAoB,CAAC;AAE3C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,uEAAuE;IACvE,MAAM,EAAE,OAAO,CAAC;IAChB,8DAA8D;IAC9D,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,sGAAsG;AACtG,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE3D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,cAAc,CA6EhG"}
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which executor runs a governed child — ADR-0031.
|
|
3
|
+
*
|
|
4
|
+
* `PI_GRANTS_HERDR` is three-state, and **absent means probe**. That reverses ADR-0016 point 6's opt-in, and
|
|
5
|
+
* the reversal is narrower than it sounds: nothing is detected from `herdr` being on `PATH` (option C, rejected
|
|
6
|
+
* by name), only from a server that *answered*. The grant, the depth bound, the gate and `--tools` enforcement
|
|
7
|
+
* are identical either way — `planSpawn` produces one plan and both executors enforce it.
|
|
8
|
+
*
|
|
9
|
+
* **Pure on purpose.** The probe is I/O and lives in `herdr-cli.ts`; the DECISION is a table, and a table that
|
|
10
|
+
* fits on one screen is the only reason a reversal like this is reviewable at all.
|
|
11
|
+
*
|
|
12
|
+
* The disclosure string is part of the return value rather than composed at the call site, because ADR-0031's
|
|
13
|
+
* defence against "this relocates silently" is that every outcome says what it chose AND what to set instead.
|
|
14
|
+
* Two call sites composing that separately is how one of them comes to omit it (R-28).
|
|
15
|
+
*/
|
|
16
|
+
export const ENV_HERDR = "PI_GRANTS_HERDR";
|
|
17
|
+
/** `0` is the one value that needs no probe: the operator ruled herdr out, so asking is pure cost. */
|
|
18
|
+
export function needsProbe(raw) {
|
|
19
|
+
return raw === undefined || raw === "1";
|
|
20
|
+
}
|
|
21
|
+
export function chooseExecutor(raw, probe) {
|
|
22
|
+
if (raw === "0") {
|
|
23
|
+
return {
|
|
24
|
+
kind: "process",
|
|
25
|
+
forced: true,
|
|
26
|
+
probed: false,
|
|
27
|
+
disclosure: "captured subprocess (PI_GRANTS_HERDR=0) — children have no terminal",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (raw === "1") {
|
|
31
|
+
if (probe?.ok) {
|
|
32
|
+
return { kind: "herdr", forced: true, probed: true, disclosure: "herdr panes (PI_GRANTS_HERDR=1)" };
|
|
33
|
+
}
|
|
34
|
+
// Refusal, not fallback — the operator's decision of 2026-08-17, against the alternative of falling back
|
|
35
|
+
// loudly. A fallback nobody reads is R-25's shape; refusing keeps the ledger unable to name a child that
|
|
36
|
+
// ran somewhere nobody chose. A `null` probe lands here too: if the probe failed so badly it produced no
|
|
37
|
+
// result, herdr was still demanded, and failing closed means refusing.
|
|
38
|
+
const why = probe?.error ?? "the herdr probe did not succeed";
|
|
39
|
+
return {
|
|
40
|
+
kind: "herdr",
|
|
41
|
+
forced: true,
|
|
42
|
+
probed: true,
|
|
43
|
+
refusal: `PI_GRANTS_HERDR=1 demands the herdr executor and herdr is not answering (${why}). ` +
|
|
44
|
+
`Delegation is refused rather than quietly relocated to a captured subprocess, so this session's ` +
|
|
45
|
+
`ledger can never contain a child that ran somewhere nobody chose. Start herdr, or unset ` +
|
|
46
|
+
`PI_GRANTS_HERDR to let this session probe, or set PI_GRANTS_HERDR=0 to choose subprocesses.`,
|
|
47
|
+
disclosure: `herdr panes DEMANDED but unreachable (${why}) — every delegation will refuse`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (raw !== undefined) {
|
|
51
|
+
// Rule 8: fail closed and be loud. An unrecognised value must not relocate a run, and must not break
|
|
52
|
+
// delegation either — the operator meant *something*, and the dependency-free executor is the safe read.
|
|
53
|
+
// The empty string lands here rather than with `undefined`, which keeps absent-versus-empty
|
|
54
|
+
// distinguishable exactly as `PI_GRANTS_GATED` does.
|
|
55
|
+
return {
|
|
56
|
+
kind: "process",
|
|
57
|
+
forced: false,
|
|
58
|
+
probed: probe !== null,
|
|
59
|
+
disclosure: `captured subprocess — PI_GRANTS_HERDR is set to an unrecognised value and was ignored. ` +
|
|
60
|
+
`Use 1 (demand herdr panes), 0 (demand subprocesses), or unset it to probe.`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (probe?.ok) {
|
|
64
|
+
return { kind: "herdr", forced: false, probed: true, disclosure: "herdr panes (probed — herdr is answering)" };
|
|
65
|
+
}
|
|
66
|
+
if (probe === null) {
|
|
67
|
+
// **The pre-probe seed, and it must not claim a probe happened.** `createGrantsSession` builds a choice
|
|
68
|
+
// synchronously with `probe: null` because S-5 forces the factory to run before any hook; `resolveExecutor`
|
|
69
|
+
// replaces it during `session_start`. This branch previously fell through to the one below and reported
|
|
70
|
+
// `probed: true` with the word "(probed)" in its disclosure — a fabricated observation, indistinguishable
|
|
71
|
+
// from a real negative probe.
|
|
72
|
+
//
|
|
73
|
+
// Unreachable in practice today (pi awaits the `session_start` emit before the first prompt, verified by a
|
|
74
|
+
// reviewer against real pi), which is exactly why it is worth making honest rather than leaving to be
|
|
75
|
+
// discovered: if a throw ever strands a session on this reading, the disclosure should say so.
|
|
76
|
+
return {
|
|
77
|
+
kind: "process",
|
|
78
|
+
forced: false,
|
|
79
|
+
probed: false,
|
|
80
|
+
disclosure: "not yet probed — settling at session start",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
kind: "process",
|
|
85
|
+
forced: false,
|
|
86
|
+
probed: true,
|
|
87
|
+
// Names the remedy on the same line as the state. The gap that produced ADR-0031 was not that the
|
|
88
|
+
// operator could not see which executor ran — it was that seeing it would not have told them what to do.
|
|
89
|
+
disclosure: `captured subprocess (probed — no herdr answering${probe?.error ? `: ${probe.error}` : ""}). ` +
|
|
90
|
+
`Set PI_GRANTS_HERDR=1 to demand panes.`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,CAAC,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAqB3C,sGAAsG;AACtG,MAAM,UAAU,UAAU,CAAC,GAAuB;IAChD,OAAO,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,GAAG,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAuB,EAAE,KAAwB;IAC9E,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;QAChB,OAAO;YACL,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,KAAK;YACb,UAAU,EAAE,qEAAqE;SAClF,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,KAAK,EAAE,EAAE,EAAE,CAAC;YACd,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,iCAAiC,EAAE,CAAC;QACtG,CAAC;QACD,yGAAyG;QACzG,yGAAyG;QACzG,yGAAyG;QACzG,uEAAuE;QACvE,MAAM,GAAG,GAAG,KAAK,EAAE,KAAK,IAAI,iCAAiC,CAAC;QAC9D,OAAO;YACL,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,IAAI;YACZ,OAAO,EACL,4EAA4E,GAAG,KAAK;gBACpF,kGAAkG;gBAClG,0FAA0F;gBAC1F,6FAA6F;YAC/F,UAAU,EAAE,yCAAyC,GAAG,kCAAkC;SAC3F,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,qGAAqG;QACrG,yGAAyG;QACzG,4FAA4F;QAC5F,qDAAqD;QACrD,OAAO;YACL,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,KAAK,KAAK,IAAI;YACtB,UAAU,EACR,yFAAyF;gBACzF,4EAA4E;SAC/E,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,EAAE,EAAE,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,2CAA2C,EAAE,CAAC;IACjH,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,wGAAwG;QACxG,4GAA4G;QAC5G,wGAAwG;QACxG,0GAA0G;QAC1G,8BAA8B;QAC9B,EAAE;QACF,2GAA2G;QAC3G,sGAAsG;QACtG,+FAA+F;QAC/F,OAAO;YACL,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,KAAK;YACb,UAAU,EAAE,4CAA4C;SACzD,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,IAAI;QACZ,kGAAkG;QAClG,yGAAyG;QACzG,UAAU,EACR,mDAAmD,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK;YAC9F,wCAAwC;KAC3C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Talking to herdr: one command, one JSON envelope, plus the two questions ADR-0031 needs answered.
|
|
3
|
+
*
|
|
4
|
+
* Lifted out of `src/run-herdr.ts`, which was at 357 of the 400-line ceiling and gains output polling under
|
|
5
|
+
* ADR-0032. But the split is not only about lines: **the probe is not an executor concern**. It runs at session
|
|
6
|
+
* start, before any delegation exists, to decide *which* executor a session will use — so leaving it inside the
|
|
7
|
+
* herdr executor would mean the session imported the thing it was deciding whether to use.
|
|
8
|
+
*
|
|
9
|
+
* Every rule here is tested against an injected `exec`, so the suite stays fast, pi-free and herdr-free. The
|
|
10
|
+
* facts the fakes reproduce were measured against real herdr 0.7.5 (`docs/probes/g16-herdr`).
|
|
11
|
+
*/
|
|
12
|
+
/** One herdr CLI invocation. Injectable so every rule below is testable without herdr installed. */
|
|
13
|
+
export type HerdrExec = (args: string[]) => Promise<{
|
|
14
|
+
code: number | null;
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare const defaultExec: HerdrExec;
|
|
19
|
+
/**
|
|
20
|
+
* Parse herdr's JSON envelope. Every command replies `{id, result}` or `{id, error:{code,message}}`.
|
|
21
|
+
*
|
|
22
|
+
* `stderr` is folded into the message because the first end-to-end run failed with an EMPTY stdout and the
|
|
23
|
+
* real reason on stderr, producing the useless diagnostic "unparseable herdr reply: ". A wrapper that
|
|
24
|
+
* hides the substrate's own error message costs more time than it saves.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseReply(reply: {
|
|
27
|
+
stdout: string;
|
|
28
|
+
stderr: string;
|
|
29
|
+
}): {
|
|
30
|
+
result?: Record<string, unknown>;
|
|
31
|
+
error?: string;
|
|
32
|
+
};
|
|
33
|
+
/** Bound on the session-start probe. Short: it sits in front of the operator's first prompt. */
|
|
34
|
+
export declare const PROBE_TIMEOUT_MS = 2000;
|
|
35
|
+
export interface HerdrProbe {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
/** herdr's own words when it is not reachable. Carried so the disclosure line can name the reason. */
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Is there a herdr server that will answer right now? — ADR-0031's selection input.
|
|
42
|
+
*
|
|
43
|
+
* **`tab list`, not `which herdr`.** ADR-0031 rejects `PATH` detection as option C by name: a binary on `PATH`
|
|
44
|
+
* with no server behind it would make every delegation fail at `tab create`, on a path the operator never
|
|
45
|
+
* chose, and the diagnostic would arrive at the first delegation rather than at startup. Only a parsed
|
|
46
|
+
* `result` envelope counts as reachable; an `error` envelope, a non-JSON reply, a timeout and a throwing
|
|
47
|
+
* `exec` are all "not reachable" with the reason preserved.
|
|
48
|
+
*
|
|
49
|
+
* **Zero tabs is a successful answer**, deliberately: a fresh herdr with nothing open is reachable.
|
|
50
|
+
*
|
|
51
|
+
* Never throws. A probe that threw out of `session_start` would cancel every control after it, which is
|
|
52
|
+
* R-60's shape exactly — and this one runs *before* the line that discloses what it decided.
|
|
53
|
+
*/
|
|
54
|
+
export declare function probeHerdr(options?: {
|
|
55
|
+
exec?: HerdrExec;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
}): Promise<HerdrProbe>;
|
|
58
|
+
/** herdr's own variable, set in every pane it creates. Measured 2026-08-17; documented nowhere. */
|
|
59
|
+
export declare const ENV_PARENT_WORKSPACE = "HERDR_WORKSPACE_ID";
|
|
60
|
+
/** The operator's explicit override. Defined here because this is the only module that reads it. */
|
|
61
|
+
export declare const ENV_HERDR_WORKSPACE = "PI_GRANTS_HERDR_WORKSPACE";
|
|
62
|
+
/**
|
|
63
|
+
* Which herdr workspace a governed child's pane belongs in.
|
|
64
|
+
*
|
|
65
|
+
* **Defaults to the parent's own workspace.** herdr tells a pane which workspace it is in
|
|
66
|
+
* (`HERDR_WORKSPACE_ID`, alongside `HERDR_TAB_ID` and `HERDR_PANE_ID`), and a child placed in a *different*
|
|
67
|
+
* workspace from the pi session that spawned it turns "switch between them" into a workspace hop — which is
|
|
68
|
+
* the entire feature ADR-0032 exists to deliver. The previous behaviour was "omitted lets herdr choose",
|
|
69
|
+
* which is that failure by default on any machine with more than one workspace.
|
|
70
|
+
*
|
|
71
|
+
* `PI_GRANTS_HERDR_WORKSPACE` still wins: it is the operator saying so explicitly, and an explicit answer
|
|
72
|
+
* beating an inference is this package's standing rule (ADR-0030 says it about the grant itself).
|
|
73
|
+
*
|
|
74
|
+
* Blank is treated as absent rather than passed through — `--workspace ""` is not a workspace, and it would
|
|
75
|
+
* fail `tab create` on a path nobody chose.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveWorkspace(env: NodeJS.ProcessEnv): string | undefined;
|
|
78
|
+
//# sourceMappingURL=herdr-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"herdr-cli.d.ts","sourceRoot":"","sources":["../src/herdr-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,oGAAoG;AACpG,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE7G,eAAO,MAAM,WAAW,EAAE,SAatB,CAAC;AAEL;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAW1H;AAED,gGAAgG;AAChG,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,sGAAsG;IACtG,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAAC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAsB5G;AAED,mGAAmG;AACnG,eAAO,MAAM,oBAAoB,uBAAuB,CAAC;AAEzD,oGAAoG;AACpG,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAE/D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,SAAS,CAI3E"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Talking to herdr: one command, one JSON envelope, plus the two questions ADR-0031 needs answered.
|
|
3
|
+
*
|
|
4
|
+
* Lifted out of `src/run-herdr.ts`, which was at 357 of the 400-line ceiling and gains output polling under
|
|
5
|
+
* ADR-0032. But the split is not only about lines: **the probe is not an executor concern**. It runs at session
|
|
6
|
+
* start, before any delegation exists, to decide *which* executor a session will use — so leaving it inside the
|
|
7
|
+
* herdr executor would mean the session imported the thing it was deciding whether to use.
|
|
8
|
+
*
|
|
9
|
+
* Every rule here is tested against an injected `exec`, so the suite stays fast, pi-free and herdr-free. The
|
|
10
|
+
* facts the fakes reproduce were measured against real herdr 0.7.5 (`docs/probes/g16-herdr`).
|
|
11
|
+
*/
|
|
12
|
+
import { execFile } from "node:child_process";
|
|
13
|
+
export const defaultExec = (args) => new Promise((settle) => {
|
|
14
|
+
execFile("herdr", args, { maxBuffer: 32 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
15
|
+
const raw = error?.code;
|
|
16
|
+
const code = typeof raw === "number" ? raw : error ? 1 : 0;
|
|
17
|
+
// **A string `code` is a spawn failure, and it used to be thrown away.** `ENOENT` — herdr not installed —
|
|
18
|
+
// arrives as `code: "ENOENT"`, so the numeric test failed, the message was dropped, and an operator with
|
|
19
|
+
// `PI_GRANTS_HERDR=1` on a machine without herdr was told *"herdr is not answering (unparseable herdr
|
|
20
|
+
// reply: (no output))"* rather than that the binary is missing. Rule 8 wants the loud version, and this is
|
|
21
|
+
// the first diagnostic such an operator meets.
|
|
22
|
+
const spawnFailure = typeof raw === "string" ? `herdr could not be run (${raw}): ${error?.message ?? ""}` : "";
|
|
23
|
+
settle({ code, stdout: String(stdout), stderr: spawnFailure || String(stderr) });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Parse herdr's JSON envelope. Every command replies `{id, result}` or `{id, error:{code,message}}`.
|
|
28
|
+
*
|
|
29
|
+
* `stderr` is folded into the message because the first end-to-end run failed with an EMPTY stdout and the
|
|
30
|
+
* real reason on stderr, producing the useless diagnostic "unparseable herdr reply: ". A wrapper that
|
|
31
|
+
* hides the substrate's own error message costs more time than it saves.
|
|
32
|
+
*/
|
|
33
|
+
export function parseReply(reply) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(reply.stdout);
|
|
36
|
+
if (parsed.error)
|
|
37
|
+
return { error: parsed.error.message ?? parsed.error.code ?? "herdr reported an error" };
|
|
38
|
+
return { result: parsed.result };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// A non-JSON reply is a herdr-version or PATH problem, not a governance decision. Surfaced as a spawn
|
|
42
|
+
// error so the caller reports "could not start" rather than "the child produced nothing".
|
|
43
|
+
const detail = [reply.stdout.trim(), reply.stderr.trim()].filter((t) => t.length > 0).join(" | ");
|
|
44
|
+
return { error: `unparseable herdr reply: ${detail.slice(0, 300) || "(no output)"}` };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Bound on the session-start probe. Short: it sits in front of the operator's first prompt. */
|
|
48
|
+
export const PROBE_TIMEOUT_MS = 2000;
|
|
49
|
+
/**
|
|
50
|
+
* Is there a herdr server that will answer right now? — ADR-0031's selection input.
|
|
51
|
+
*
|
|
52
|
+
* **`tab list`, not `which herdr`.** ADR-0031 rejects `PATH` detection as option C by name: a binary on `PATH`
|
|
53
|
+
* with no server behind it would make every delegation fail at `tab create`, on a path the operator never
|
|
54
|
+
* chose, and the diagnostic would arrive at the first delegation rather than at startup. Only a parsed
|
|
55
|
+
* `result` envelope counts as reachable; an `error` envelope, a non-JSON reply, a timeout and a throwing
|
|
56
|
+
* `exec` are all "not reachable" with the reason preserved.
|
|
57
|
+
*
|
|
58
|
+
* **Zero tabs is a successful answer**, deliberately: a fresh herdr with nothing open is reachable.
|
|
59
|
+
*
|
|
60
|
+
* Never throws. A probe that threw out of `session_start` would cancel every control after it, which is
|
|
61
|
+
* R-60's shape exactly — and this one runs *before* the line that discloses what it decided.
|
|
62
|
+
*/
|
|
63
|
+
export async function probeHerdr(options = {}) {
|
|
64
|
+
const exec = options.exec ?? defaultExec;
|
|
65
|
+
const timeoutMs = options.timeoutMs ?? PROBE_TIMEOUT_MS;
|
|
66
|
+
let timer;
|
|
67
|
+
try {
|
|
68
|
+
return await Promise.race([
|
|
69
|
+
exec(["tab", "list"]).then((reply) => {
|
|
70
|
+
const parsed = parseReply(reply);
|
|
71
|
+
return parsed.error ? { ok: false, error: parsed.error } : { ok: true };
|
|
72
|
+
}),
|
|
73
|
+
new Promise((settle) => {
|
|
74
|
+
timer = setTimeout(() => settle({ ok: false, error: `probe timed out after ${timeoutMs}ms` }), timeoutMs);
|
|
75
|
+
}),
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return { ok: false, error: String(error) };
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
// Cleared whichever branch won, so a fast probe does not hold the event loop open for the timeout's
|
|
83
|
+
// remainder — which would add up to two seconds to every `node --test` run of this file.
|
|
84
|
+
if (timer)
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** herdr's own variable, set in every pane it creates. Measured 2026-08-17; documented nowhere. */
|
|
89
|
+
export const ENV_PARENT_WORKSPACE = "HERDR_WORKSPACE_ID";
|
|
90
|
+
/** The operator's explicit override. Defined here because this is the only module that reads it. */
|
|
91
|
+
export const ENV_HERDR_WORKSPACE = "PI_GRANTS_HERDR_WORKSPACE";
|
|
92
|
+
/**
|
|
93
|
+
* Which herdr workspace a governed child's pane belongs in.
|
|
94
|
+
*
|
|
95
|
+
* **Defaults to the parent's own workspace.** herdr tells a pane which workspace it is in
|
|
96
|
+
* (`HERDR_WORKSPACE_ID`, alongside `HERDR_TAB_ID` and `HERDR_PANE_ID`), and a child placed in a *different*
|
|
97
|
+
* workspace from the pi session that spawned it turns "switch between them" into a workspace hop — which is
|
|
98
|
+
* the entire feature ADR-0032 exists to deliver. The previous behaviour was "omitted lets herdr choose",
|
|
99
|
+
* which is that failure by default on any machine with more than one workspace.
|
|
100
|
+
*
|
|
101
|
+
* `PI_GRANTS_HERDR_WORKSPACE` still wins: it is the operator saying so explicitly, and an explicit answer
|
|
102
|
+
* beating an inference is this package's standing rule (ADR-0030 says it about the grant itself).
|
|
103
|
+
*
|
|
104
|
+
* Blank is treated as absent rather than passed through — `--workspace ""` is not a workspace, and it would
|
|
105
|
+
* fail `tab create` on a path nobody chose.
|
|
106
|
+
*/
|
|
107
|
+
export function resolveWorkspace(env) {
|
|
108
|
+
const explicit = env[ENV_HERDR_WORKSPACE]?.trim();
|
|
109
|
+
if (explicit)
|
|
110
|
+
return explicit;
|
|
111
|
+
return env[ENV_PARENT_WORKSPACE]?.trim() || undefined;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=herdr-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"herdr-cli.js","sourceRoot":"","sources":["../src/herdr-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Naming a herdr agent: the grammar herdr enforces, and uniqueness it does not.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `src/run-herdr.ts` at the 400-line ceiling, and a real seam: both rules below come from **herdr's
|
|
5
|
+
* own validation and lifecycle**, not from anything this package decides. Two shipping defects lived here, and
|
|
6
|
+
* both were invisible to every test because the unit fake accepts whatever name it is handed and the integration
|
|
7
|
+
* suite never reaches a real herdr spawn. They surfaced from two real spawns against the live daemon.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Make a herdr agent name that is **valid** and cannot collide with a live one.
|
|
11
|
+
*
|
|
12
|
+
* **Validity is a separate, PRE-EXISTING defect, and it is the more serious half.** Callers build a name as
|
|
13
|
+
* `${definition}-${childId}`, and a ledger child id is hierarchical — `d0.1`, `d0.1.2` (ADR-0008/F8). Those dots
|
|
14
|
+
* are **not in herdr's grammar**, so `agent start review-d0.1 …` is rejected with `invalid_agent_name`. Every
|
|
15
|
+
* `delegate({agent})` on the herdr path has therefore failed at `agent start` since the executor was written.
|
|
16
|
+
*
|
|
17
|
+
* Nothing could see it. The unit fake accepts any name it is handed, and the integration suite never reaches a
|
|
18
|
+
* real herdr spawn — so both were green while the feature could not work. It surfaced only by running two real
|
|
19
|
+
* spawns against the live daemon, which is the argument for doing that at all.
|
|
20
|
+
*
|
|
21
|
+
* **Measured, and a shipping defect without it.** herdr binds an agent name to its **tab**, and only closing
|
|
22
|
+
* the tab frees the name: a second `agent start` with a name still held returns
|
|
23
|
+
* `agent_name_taken: agent <name> is already used; … tab_id=…`. `herdr agent stop` does not exist (see
|
|
24
|
+
* `cleanup`), so nothing else releases it.
|
|
25
|
+
*
|
|
26
|
+
* Callers build a name from the definition and the ledger child id — and for a plain blocking `delegate` that
|
|
27
|
+
* id is **constant** (`d0.1`, index 0 of the session), so every delegation in a session asked for the same
|
|
28
|
+
* name. That was harmless while the pane closed at the end of each call. Once ADR-0032 kept panes alive to
|
|
29
|
+
* `agent_settled`, the **first** delegation of a turn worked and every later one failed with
|
|
30
|
+
* `agent_name_taken`, on the executor ADR-0031 had just made the default.
|
|
31
|
+
*
|
|
32
|
+
* Uniquified HERE rather than at the call site, so no caller can forget: the constraint belongs to herdr, and
|
|
33
|
+
* this module is the only thing that talks to herdr. The suffix is a counter rather than a random token so a
|
|
34
|
+
* pane label stays readable and reproducible within a run.
|
|
35
|
+
*/
|
|
36
|
+
export declare function uniqueAgentName(base: string): string;
|
|
37
|
+
//# sourceMappingURL=herdr-name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"herdr-name.d.ts","sourceRoot":"","sources":["../src/herdr-name.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAapD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Naming a herdr agent: the grammar herdr enforces, and uniqueness it does not.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `src/run-herdr.ts` at the 400-line ceiling, and a real seam: both rules below come from **herdr's
|
|
5
|
+
* own validation and lifecycle**, not from anything this package decides. Two shipping defects lived here, and
|
|
6
|
+
* both were invisible to every test because the unit fake accepts whatever name it is handed and the integration
|
|
7
|
+
* suite never reaches a real herdr spawn. They surfaced from two real spawns against the live daemon.
|
|
8
|
+
*/
|
|
9
|
+
/** Monotonic within this process. See `uniqueAgentName`. */
|
|
10
|
+
let spawnSeq = 0;
|
|
11
|
+
/**
|
|
12
|
+
* herdr's agent-name grammar, measured from its own rejection message.
|
|
13
|
+
*
|
|
14
|
+
* `agent name must start with a lowercase letter and contain only lowercase letters, digits, '-' or '_'
|
|
15
|
+
* (1-32 characters)`.
|
|
16
|
+
*/
|
|
17
|
+
const AGENT_NAME_MAX = 32;
|
|
18
|
+
/**
|
|
19
|
+
* Make a herdr agent name that is **valid** and cannot collide with a live one.
|
|
20
|
+
*
|
|
21
|
+
* **Validity is a separate, PRE-EXISTING defect, and it is the more serious half.** Callers build a name as
|
|
22
|
+
* `${definition}-${childId}`, and a ledger child id is hierarchical — `d0.1`, `d0.1.2` (ADR-0008/F8). Those dots
|
|
23
|
+
* are **not in herdr's grammar**, so `agent start review-d0.1 …` is rejected with `invalid_agent_name`. Every
|
|
24
|
+
* `delegate({agent})` on the herdr path has therefore failed at `agent start` since the executor was written.
|
|
25
|
+
*
|
|
26
|
+
* Nothing could see it. The unit fake accepts any name it is handed, and the integration suite never reaches a
|
|
27
|
+
* real herdr spawn — so both were green while the feature could not work. It surfaced only by running two real
|
|
28
|
+
* spawns against the live daemon, which is the argument for doing that at all.
|
|
29
|
+
*
|
|
30
|
+
* **Measured, and a shipping defect without it.** herdr binds an agent name to its **tab**, and only closing
|
|
31
|
+
* the tab frees the name: a second `agent start` with a name still held returns
|
|
32
|
+
* `agent_name_taken: agent <name> is already used; … tab_id=…`. `herdr agent stop` does not exist (see
|
|
33
|
+
* `cleanup`), so nothing else releases it.
|
|
34
|
+
*
|
|
35
|
+
* Callers build a name from the definition and the ledger child id — and for a plain blocking `delegate` that
|
|
36
|
+
* id is **constant** (`d0.1`, index 0 of the session), so every delegation in a session asked for the same
|
|
37
|
+
* name. That was harmless while the pane closed at the end of each call. Once ADR-0032 kept panes alive to
|
|
38
|
+
* `agent_settled`, the **first** delegation of a turn worked and every later one failed with
|
|
39
|
+
* `agent_name_taken`, on the executor ADR-0031 had just made the default.
|
|
40
|
+
*
|
|
41
|
+
* Uniquified HERE rather than at the call site, so no caller can forget: the constraint belongs to herdr, and
|
|
42
|
+
* this module is the only thing that talks to herdr. The suffix is a counter rather than a random token so a
|
|
43
|
+
* pane label stays readable and reproducible within a run.
|
|
44
|
+
*/
|
|
45
|
+
export function uniqueAgentName(base) {
|
|
46
|
+
spawnSeq += 1;
|
|
47
|
+
const suffix = `-${spawnSeq}`;
|
|
48
|
+
const cleaned = base
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/[^a-z0-9_-]+/g, "-") // dots from a child id, and anything else outside the grammar
|
|
51
|
+
.replace(/-{2,}/g, "-")
|
|
52
|
+
.replace(/^[^a-z]+/, ""); // must START with a lowercase letter, so a leading digit or dash goes
|
|
53
|
+
// Truncated so the whole name fits, and trimmed of a trailing separator so the join stays readable. The
|
|
54
|
+
// fallback covers a base that sanitises to nothing at all (a definition named entirely in non-Latin script).
|
|
55
|
+
const room = AGENT_NAME_MAX - suffix.length;
|
|
56
|
+
const head = cleaned.slice(0, room).replace(/[-_]+$/, "") || "agent";
|
|
57
|
+
return `${head}${suffix}`;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=herdr-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"herdr-name.js","sourceRoot":"","sources":["../src/herdr-name.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,4DAA4D;AAC5D,IAAI,QAAQ,GAAG,CAAC,CAAC;AAEjB;;;;;GAKG;AACH,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,QAAQ,IAAI,CAAC,CAAC;IACd,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,IAAI;SACjB,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC,8DAA8D;SAC5F,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,sEAAsE;IAClG,wGAAwG;IACxG,6GAA6G;IAC7G,MAAM,IAAI,GAAG,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC;IAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC;IACrE,OAAO,GAAG,IAAI,GAAG,MAAM,EAAE,CAAC;AAC5B,CAAC"}
|