@junghanacs/entwurf 0.16.0 → 0.16.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.
Files changed (31) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +125 -0
  3. package/README.md +8 -11
  4. package/demo/README.md +1 -1
  5. package/docs/setup-clean-host.md +24 -10
  6. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +19 -10
  7. package/package.json +8 -8
  8. package/pi-extensions/lib/acp/backend-adapter.ts +19 -9
  9. package/pi-extensions/lib/acp/backend.ts +125 -7
  10. package/pi-extensions/lib/acp/claude-acp-launch.js +100 -0
  11. package/run.sh +108 -32
  12. package/scripts/check-acp-launch-namespace.ts +127 -0
  13. package/scripts/check-acp-prompt-lifecycle.ts +145 -2
  14. package/scripts/check-copilot-birth-hook.ts +28 -1
  15. package/scripts/check-gate-qualification.ts +3 -2
  16. package/scripts/check-omp-fresh-preflight.ts +27 -0
  17. package/scripts/check-setup-qualification.sh +40 -2
  18. package/scripts/copilot-bridge-oracle.sh +14 -6
  19. package/scripts/fake-copilot-vendor.sh +4 -2
  20. package/scripts/mutants/acp-launch-namespace.json +34 -0
  21. package/scripts/mutants/acp-prompt-lifecycle.json +67 -2
  22. package/scripts/mutants/copilot-birth.json +3 -5
  23. package/scripts/mutants/pack-install.json +2 -2
  24. package/scripts/mutants/setup-verdict.json +35 -0
  25. package/scripts/omp-config-xdev.py +310 -0
  26. package/scripts/omp-config-xdev.sh +76 -0
  27. package/scripts/omp-tool-surface.py +61 -10
  28. package/scripts/raw-acp-child-exit-measure/README.md +285 -0
  29. package/scripts/raw-acp-child-exit-measure/acp-turn-population.py +89 -0
  30. package/scripts/raw-acp-child-exit-measure/reaper-correlation.py +47 -0
  31. package/scripts/smoke-setup-verdict.sh +48 -3
package/AGENTS.md CHANGED
@@ -182,7 +182,7 @@ implement → affected focused gates → independent review → one amendment bu
182
182
  - Every `.ts` file belongs to one typecheck fence: root emit-capable config, MCP strip-types config, or scripts strip-types config. Do not hide files with `exclude`.
183
183
  - Root pi extensions import TypeBox through `@earendil-works/pi-ai`; do not mix direct `@sinclair/typebox` types.
184
184
  - MCP/scripts use explicit `.ts` imports where Node strip-types requires them. Installed operator surfaces route to compiled JS.
185
- - pi runtime range is `>=0.84.3 <0.85` with devDep exact `0.84.3`; re-evaluate loader aliases and `/compat` at every minor ceiling. The ceiling moves on measurement, never on assumption, and a previous bump's argument is never reused: at 0.83.0→0.84.0 `compat.ts` was still byte-identical but `loader.ts` was NOT, so the diff itself had to be read and judged reachable-or-not. Per-bump hashes, diff judgments, and reachability findings live in the ROADMAP **Dep bump(별도 트랙)** ledger — keep them there, not here.
185
+ - pi runtime range is `>=0.84.4 <0.85` with devDep exact `0.84.4`; re-evaluate loader aliases and `/compat` at every minor ceiling. The ceiling moves on measurement, never on assumption, and a previous bump's argument is never reused: at 0.83.0→0.84.0 `compat.ts` was still byte-identical but `loader.ts` was NOT, so the diff itself had to be read and judged reachable-or-not. Per-bump hashes, diff judgments, and reachability findings live in the ROADMAP **Dep bump(별도 트랙)** ledger — keep them there, not here.
186
186
  - ACP pins are recorded in `package.json` and checked by `check-dep-versions`/`check-acp-sdk-surface`; do not describe a dependency bump as a behavioral fix without evidence.
187
187
 
188
188
  ## Working Style
package/CHANGELOG.md CHANGED
@@ -4,6 +4,131 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.16.1 - 2026-09-01
8
+
9
+ ### Verification
10
+
11
+ Each receipt carries its own scope; none of them is transferable to another commit or host.
12
+
13
+ - **`pnpm run check:full`** — PASS, exit 0, **454s** inside the gate, on the 0.16.1
14
+ versioned tree (HEAD `2e7ceb4` + the uncommitted changelog/version edits).
15
+ - **`./run.sh check-gate-qualification`** — **335/335 KILLED, NOT KILLED 0**, as the
16
+ release-gate MUST step. The first run of this cut reported **334/335**: the single
17
+ miss was `PACK-INSTALL-PIN-MATCHER-BOUNDED` reading `MUTANT-STALE` because `5c1bda5`
18
+ moved `run.sh`'s pin matcher to the 0.84.4 floor and left that mutant's anchor on
19
+ 0.84.3, so it matched 0× and the gate had silently stopped asking its question.
20
+ Repaired in `2e7ceb4` and re-run green. A rotted anchor is recorded here rather than
21
+ quietly fixed, because "N killed" only means something when N is the whole set.
22
+ - **`LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.16.1.m0LUjr --cut`** —
23
+ **MUST: PASS=23 FAIL=0 SKIP=0**, **BEHAVIOR: PASS=1 FAIL=0 SKIP=0**. Log at
24
+ `/tmp/entwurf-release-gate-0.16.1.m0LUjr/release-gate.log`. `smoke-mux-lifecycle-live`
25
+ failed the first run on a pi-native nonce callback that never arrived inside its 300s
26
+ window (the codex rail was measured healthy at the time: 5h 23%, weekly 77%), and
27
+ passed on re-run — recorded because a LIVE step that needed a retry is not the same
28
+ evidence as one that passed first time.
29
+ - **#72 launch/observation receipts** — the launcher's two field claims are not
30
+ unit-provable, so they were measured on the host that produced the failure:
31
+ `/proc/<pid>/cmdline` carries no vendor name, the janitor's own Phase 1 selector run
32
+ against the real `ps` row with its age threshold forced to 0 selects nothing, and a
33
+ real SIGTERM still ends at exit 0 with the frame on stderr. Commands and output in
34
+ `scripts/raw-acp-child-exit-measure/README.md`.
35
+
36
+ ### Added
37
+
38
+ - **`setup` composes OMP — the fifth backend is now in the one command.** v0.16.0 admitted OMP
39
+ as a D6 citizen with installers, doctors and inverses for every unit, but `setup_all` had no
40
+ `omp` branch: on an operator host the released package printed a **green** setup summary while
41
+ OMP had nothing installed — no extension, no `mcp.json` entry, no garden id on the status line.
42
+ Presence-driven now, same shape as Copilot: `omp` on PATH composes four independent named
43
+ components (birth → MCP hand → the `tools.xdev` operator setting → receiver), absent is one
44
+ zero-state SKIP, and a detected-but-incomplete unit is a named FAIL owning a nonzero exit.
45
+ Gate: `smoke-setup-verdict` S-8 drives the real composition against a stub vendor and asserts
46
+ every install-state, both extension units in the sandbox agent dir, the effective `xdev-off`
47
+ reading, and idempotence on a second run; S-1 pins `OMP_BIN` absent and requires the SKIP row.
48
+ - **`install-omp-config` / `uninstall-omp-config` — the operator setting is a unit, not a
49
+ documentation step.** `tools: xdev: false` was a hand-edit in `docs/setup-clean-host.md`, so a
50
+ host that skipped it registered every entwurf tool behind the vendor's `xd://` wrapper and the
51
+ doorbell announced a tool the model could not call. The writer owns exactly the lines it adds
52
+ (recorded in its install-state), refuses a symlinked config, refuses a config it cannot parse,
53
+ and refuses an EXPLICIT operator `tools: xdev: true` **by name** rather than overwriting a
54
+ decision — setup surfaces that as a component FAIL for the operator to resolve. The inverse
55
+ takes back only the recorded lines and refuses when the file changed since install.
56
+ - **`docs/adding-a-harness.md` step 10 — an onboarding is not finished until `setup` composes
57
+ it.** The rule the OMP gap forced, with the five conditions a new harness owes (presence-only
58
+ trigger, one independent component row per unit, compose only what an inverse can undo,
59
+ operator settings are units with a refusing writer, pin the probe seam and add an aggregate
60
+ cell) and the shape to watch for: two closed parity loops with no gate owning the edge between
61
+ them.
62
+
63
+ ### Changed
64
+
65
+ - **pi floor moved to 0.84.4.** devDependency exact pin, the three peer ranges (`>=0.84.4 <0.85`),
66
+ the `check-pack-install` runtime pins, the pin-leak matcher's version boundary with its synthetic
67
+ fixture, and the pnpm lockfile all move together — the floor is one fact with several spellings,
68
+ and a partial bump is how they drift apart.
69
+
70
+ ### Fixed
71
+
72
+ - **#72 — the ACP Claude child is no longer selected by the host janitor that was killing it, and
73
+ a caught signal survives the vendor erasing it.** The child was not crashing: it was being **SIGTERMed from outside**. A
74
+ janitor installed on the operator's host for a *different* harness (openclaw's acpx, upstream
75
+ PR #245) selects the vendor process name `claude-agent-acp` by **argv substring** and kills
76
+ anything older than 900s. entwurf **retains** its child across turns, so that child's age is the
77
+ age of the SESSION, not of a turn — every session past 15 minutes was shot at every 5 minutes.
78
+ The vendor's own handler then turned the signal into `dispose(); process.exit(0)`, so the death
79
+ reached entwurf as exit code 0 with no signal, indistinguishable from a clean shutdown. That is
80
+ why three diagnosis passes retired four candidate causes and still missed it. Measured: **12 of
81
+ 12** anomalous terminations across two boots correlate with a reap, pid- and timestamp-locked —
82
+ including the sample that opened the issue (2026-07-30) and the original field report
83
+ (2026-08-16); receipts in `scripts/raw-acp-child-exit-measure/`. Two changes, both inside the
84
+ issue's repair fence (no timeout, no replay, no watcher/supervisor/hidden retry): (a) the default
85
+ launch is now an **entwurf-owned launcher** (`pi-extensions/lib/acp/claude-acp-launch.js`) that
86
+ imports the vendor **in-process** — same process, so there is no child to restart and it cannot
87
+ become a supervisor — carrying a name no vendor-name scanner matches, consuming no argv (the
88
+ vendor's own `argv.slice(1)` self-reinvocation still works), standing down if it would otherwise
89
+ be the only signal listener (an observer that suppressed default termination would make the
90
+ process signal-immune), and dying nonzero without retry if the import fails; (b) a caught
91
+ SIGTERM/SIGINT is recorded as a **typed lifecycle fact** on its own line
92
+ (`launch observed SIGTERM before child exit (sender not attributed)`) via an exact full-line
93
+ control frame that is consumed out of the vendor stderr tail — the tail stays vendor evidence,
94
+ the observation is entwurf's, and sender attribution is explicitly NOT claimed (that needs the
95
+ host journal). `CLAUDE_AGENT_ACP_COMMAND` stays verbatim: an operator who names their own command
96
+ owns the result. **Known trade:** after the name split a host janitor can no longer collect
97
+ entwurf's children even when they genuinely leak, so entwurf now owns that cleanup story.
98
+ **Read the order right: the cause was the host's janitor, and its owner has retired it**
99
+ (`nixos-config e283d92`, 2026-09-01 — `acp-zombie-reaper.timer` disabled). Measured there:
100
+ PR #245 is CLOSED, not merged; that deployment runs `acp.enabled=false` with no ACP agent;
101
+ acpx is not even installed in the running image (`/app/node_modules/acpx` absent, its `.bin`
102
+ entries dangling symlinks); and every reap since the 2026-06-10 ACP removal was a single-process
103
+ misfire matching our pid table. So the change here is **not what stops the symptom** — the
104
+ retirement is. It is defense-in-depth against the class, and that class is wider than this
105
+ issue: the selector reads the WHOLE `ps` line, so on a harness that puts the prompt in argv, a
106
+ session merely *discussing* `claude-agent-acp` matched. Measured 2026-09-01 with zero ACP
107
+ processes alive, the script's own counter read `alive_acp=3` — three ordinary agent sessions,
108
+ one of them the session investigating it. A launcher whose name we own survives the next such
109
+ janitor too. Gates:
110
+ new `check-acp-launch-namespace` (2 mutants) plus `check-acp-prompt-lifecycle` CELLs 12–13,
111
+ where 13 is the negative sibling holding that vendor prose mentioning `SIGTERM` can never forge
112
+ the observation (3 mutants). The primary repair still belongs to the janitor's owner — a cleaner
113
+ should scope by a positive marker it owns, not by a name anyone may share.
114
+
115
+ - **A vendor-written `config.yml` no longer reads as `unreadable`.** omp's own settings writer
116
+ emits `modelRoles:` followed by an indented `{}`, and `scripts/omp-tool-surface.py`'s
117
+ block-only reader returned None for the WHOLE file on that shape — so an untouched operator
118
+ config classified as unreadable and `doctor-omp-mcp` went RED for a reason unrelated to
119
+ `tools.xdev` (measured on a real host, omp 18.0.0). The reader now parses flow collections in
120
+ value position and as a whole child block. `check-omp-fresh-preflight` gains the vendor shapes
121
+ plus `[QK:OMP-XDEV-VENDOR-SHAPE-READABLE]` direct assertions — agreement between the two
122
+ readers could never catch this, since both collapse `unreadable` and `true` into "not false".
123
+ - **The Copilot plugin-row grammar admits the vendor's state token.** Copilot CLI 1.0.81 prints
124
+ `<qualified> (v0.1.0) (enabled)` plus an indented `from <path>` continuation line;
125
+ `copilot_exact_row_version` read the version as `0.1.0) (enabled`, called the row malformed,
126
+ and refused the birth install on a host whose plugin was installed and enabled — `entwurf
127
+ setup` reported `copilot-birth: FAIL`. The grammar now admits exactly one optional
128
+ `(enabled)`/`(disabled)` token; any other trailing token is still malformed. The fake vendors
129
+ in `scripts/fake-copilot-vendor.sh` and `check-copilot-birth-hook.ts` emit the 1.0.81 shape,
130
+ with `[QK:COPILOT-ROW-STATE-TOKEN-ADMITTED]` holding both directions.
131
+
7
132
  ## 0.16.0 - 2026-08-31
8
133
 
9
134
  This release admits **OMP (`omp`) as the fifth garden backend** and closes the admission
package/README.md CHANGED
@@ -60,8 +60,8 @@ native Antigravity / agy
60
60
  ```
61
61
 
62
62
  Claude's `install-meta-bridge`, Copilot's four `install-copilot-*` surfaces, agy's
63
- `install-agy-{bridge,statusline,hooks}` and OMP's three `install-omp-{bridge,mcp,receive}` units
64
- (each with its own `doctor-omp-*`) are distinct because their lifecycle and delivery transports are
63
+ `install-agy-{bridge,statusline,hooks}` and OMP's four `install-omp-{bridge,mcp,config,receive}`
64
+ units are distinct because their lifecycle and delivery transports are
65
65
  genuinely different. Codex remains verified probe evidence, not a shipped managed native-citizen
66
66
  lane; see [DELIVERY.md](./DELIVERY.md).
67
67
 
@@ -157,7 +157,7 @@ cd ~/repos/gh/entwurf
157
157
 
158
158
  The full source setup requires Node 24, pnpm, and Python 3 on PATH. Harnesses are
159
159
  optional-by-presence: `setup` runs the frozen dependency install, then composes what the operator
160
- already installed — a compatible `pi` (`>=0.84.3 <0.85`), Claude Code, agy, and the Copilot CLI
160
+ already installed — a compatible `pi` (`>=0.84.4 <0.85`), Claude Code, agy, and the Copilot CLI
161
161
  each get their wiring completed when detected, an absent harness is an explicit zero-state SKIP,
162
162
  and a detected harness that cannot be completed (including a below-floor `pi`) is a named FAIL
163
163
  that makes setup exit nonzero. `setup` never installs a harness binary or touches a credential
@@ -188,7 +188,7 @@ gitignored and may be absent or stale immediately after a checkout or pull.
188
188
  ### Pi adapter / ACP plugin lane
189
189
 
190
190
  To use the `entwurf` provider inside pi, install a compatible pi binary
191
- separately (`@earendil-works/pi-coding-agent >=0.84.3 <0.85`). Then point pi at
191
+ separately (`@earendil-works/pi-coding-agent >=0.84.4 <0.85`). Then point pi at
192
192
  the npm-installed package or development clone:
193
193
 
194
194
  ```bash
@@ -213,17 +213,14 @@ SKIP while the detected harnesses are composed.
213
213
 
214
214
  A plain MCP registration exposes the bridge tools; a **garden-native** session also
215
215
  needs entwurf's lifecycle hook and identity marker. `setup` already composes all of that
216
- for every harness it detects — you do not paste this list to install. OMP is the one
217
- exception and it is deliberate: its units are operator-selectable installs that `setup`
218
- does not compose, so the three `install-omp-*` commands below are how an omp host gets
219
- wired. Otherwise this is the repair surface: each unit has its own installer, its own
220
- doctor with a named refusal, and its own inverse, so a single broken unit can be redone
221
- without touching the rest.
216
+ for every harness it detects — you do not paste this list to install. This is the repair
217
+ surface: each unit has its own installer, its own doctor with a named refusal, and its own
218
+ inverse, so a single broken unit can be redone without touching the rest.
222
219
 
223
220
  - **Claude Code** (Linux-certified axis) — `install-meta-bridge`, `doctor-meta-bridge`.
224
221
  - **Antigravity / agy** — `install-agy-bridge`, `install-agy-statusline`, `install-agy-hooks`, each with a matching `doctor-agy-*`.
225
222
  - **GitHub Copilot CLI** — four independent units, four independent failure modes: `install-copilot-bridge` (birth: garden id + who-sent, on the first prompt), `install-copilot-mcp` (the entwurf tool hand, where `entwurf_inbox_read` lives), `install-copilot-receive` (the receiver extension: doorbell + receiver marker), `install-copilot-statusline` (optional for a manual citizen, required for supported fresh) — each with a matching `doctor-copilot-*` and `uninstall-copilot-*`.
226
- - **OMP (`omp`)** — three units, all in-process extensions rather than launchers: `install-omp-bridge` (birth: the `mode === "tui"` visible host, its garden id on the status line, and who-sent), `install-omp-mcp` (the omp-native `entwurf-bridge` entry), `install-omp-receive` (the receiver extension: mailbox watch + announce-only doorbell) — each with a matching `doctor-omp-*` and `uninstall-omp-*`. One OPERATOR setting sits beside them and is not ours to write: `tools: xdev: false` in the omp agent config, without which the vendor mounts MCP tools as `xd://` devices the model cannot call.
223
+ - **OMP (`omp`)** — four units, in-process extensions rather than launchers: `install-omp-bridge` (birth: the `mode === "tui"` visible host, its garden id on the status line, and who-sent), `install-omp-mcp` (the omp-native `entwurf-bridge` entry), `install-omp-config` (the one operator setting `tools: xdev: false`, without which the vendor mounts MCP tools as `xd://` devices the model cannot call), `install-omp-receive` (the receiver extension: mailbox watch + announce-only doorbell) — each with a matching `uninstall-omp-*`, and a `doctor-omp-*` for all but the setting, whose runtime axis `doctor-omp-mcp` owns. The setting writer owns exactly the lines it adds and refuses an explicit operator `tools: xdev: true` by name rather than overwriting it.
227
224
 
228
225
  Run them as `entwurf <command>`. Which unit a doctor's refusal names, and the clean-host
229
226
  walk-through for each harness, live in [docs/setup-clean-host.md](./docs/setup-clean-host.md).
package/demo/README.md CHANGED
@@ -139,7 +139,7 @@ SCENE_DELAY=30 FINAL_PAUSE=10 bash demo.sh
139
139
 
140
140
  ## Prerequisites
141
141
 
142
- - `pi` on PATH (current floor 0.84.3)
142
+ - `pi` on PATH (current floor 0.84.4)
143
143
  - `entwurf` provider configured + auth ready for the selected sender/peer models
144
144
  - `asciinema` installed
145
145
  - `agg` installed (optional — only for GIF conversion)
@@ -12,10 +12,10 @@ only on Linux because its strict live-owner join uses `/proc`.
12
12
  | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
13
13
  | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot wiring writers |
14
14
  | entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
15
- | pi | optional-by-presence, `>=0.84.3 <0.85` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
15
+ | pi | optional-by-presence, `>=0.84.4 <0.85` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
16
16
  | Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
17
17
  | GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
18
- | OMP (`omp`) | optional, operator-installed; three units (birth/MCP/receiver) plus `tools: xdev: false` in the agent config | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
18
+ | OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
19
19
  | Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
20
20
  | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
21
21
 
@@ -72,11 +72,12 @@ cd ~/repos/gh/entwurf
72
72
 
73
73
  This owns `~/.local/bin/entwurf` as a symlink to that checkout's `run.sh` and fails if the
74
74
  link is foreign, outside PATH, or shadowed by another command. It detects and wires
75
- pi/Claude/agy/Copilot by presence and prints a computed per-component PASS/SKIP/FAIL summary — a
75
+ pi/Claude/agy/Copilot/OMP by presence and prints a computed per-component PASS/SKIP/FAIL summary — a
76
76
  detected harness that cannot be completed makes setup exit nonzero. A detected `copilot`
77
77
  composes all four native units (birth → MCP → receiver → visible footer) with independent
78
- per-unit verdicts (#86 C3b); §4 keeps the explicit per-unit install/doctor/inverse surfaces for
79
- repair.
78
+ per-unit verdicts (#86 C3b), and a detected `omp` composes its own four (birth → MCP →
79
+ `tools.xdev` setting → receiver) the same way; §4 and §4b keep the explicit per-unit
80
+ install/doctor/inverse surfaces for repair.
80
81
 
81
82
  ### 1.1 User-scope ownership (one shared registration, one recorded owner)
82
83
 
@@ -109,7 +110,7 @@ packageRoot↔installerRoot coupling mismatch and a package/provider managed-pat
109
110
  Install the exact release floor, then wire the project:
110
111
 
111
112
  ```bash
112
- npm install -g @earendil-works/pi-coding-agent@0.84.3
113
+ npm install -g @earendil-works/pi-coding-agent@0.84.4
113
114
  pi --version
114
115
 
115
116
  cd ~/entwurf-smoke
@@ -117,7 +118,7 @@ entwurf install .
117
118
  pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
118
119
  ```
119
120
 
120
- The supported range is `>=0.84.3 <0.85`. It is a hard minimum: installing this
121
+ The supported range is `>=0.84.4 <0.85`. It is a hard minimum: installing this
121
122
  release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
122
123
  minor. A host using only the external MCP bridge can skip pi until it needs a
123
124
  control socket; no delivery rail launches a pi process.
@@ -213,16 +214,26 @@ sibling launched onto a default-config host would start, look healthy, and be un
213
214
  the callback tool at all. A refusal there names `omp-callback-tool-uncallable` and opens
214
215
  nothing.
215
216
 
217
+ `setup` composes all four omp units when `omp` is on PATH, and the setting is one of them —
218
+ `entwurf setup` writes `tools: xdev: false` itself. The verbs below are the REPAIR path, not
219
+ the install:
220
+
216
221
  ```bash
217
222
  entwurf install-omp-bridge # the birth extension, into <omp agent dir>/extensions/
218
223
  entwurf install-omp-mcp # the omp-native entwurf-bridge server
224
+ entwurf install-omp-config # the operator setting: tools.xdev: false
219
225
  entwurf install-omp-receive # the receiver extension: mailbox watch + doorbell
220
226
 
221
227
  entwurf doctor-omp-bridge
222
- entwurf doctor-omp-mcp
228
+ entwurf doctor-omp-mcp # also owns the tools.xdev runtime axis
223
229
  entwurf doctor-omp-receive
224
230
  ```
225
231
 
232
+ The setting writer owns exactly the lines it adds and records them, so `uninstall-omp-config`
233
+ takes back its own bytes and nothing else. It refuses a symlinked config, a config it cannot
234
+ parse, and — deliberately — an EXPLICIT `tools: xdev: true`: that is your decision, not drift,
235
+ so setup names it as a component FAIL for you to resolve instead of overwriting it.
236
+
226
237
  Order matters only in one direction: the receiver JOINS the citizen birth mints, and
227
238
  announces a tool the MCP hand provides. Install it without them and it will log
228
239
  `arm-deferred`, give up after ~20s, and `doctor-omp-receive` will name the missing sibling
@@ -234,8 +245,11 @@ retires it. A task subagent arms nothing. While nothing is armed, dispatch to th
234
245
  id is the honest `mailbox-undeliverable` refusal — an unarmed receiver is a legible state,
235
246
  not a broken one.
236
247
 
237
- The admission work has landed and `setup` still does not compose these: the three omp
238
- units stay operator-selectable installs, so run them yourself on an omp host.
248
+ `[측정]` This section used to end by saying `setup` did not compose these and the verbs had to
249
+ be run by hand. That is what v0.16.0 actually shipped, and on an operator host it printed a
250
+ green `setup` summary with OMP entirely absent — no extension, no MCP entry, no visible garden
251
+ id. The composition landed afterwards; `docs/adding-a-harness.md` step 10 is the rule that
252
+ keeps the next harness from repeating it.
239
253
 
240
254
  Both installers resolve the omp agent directory the way omp itself does, and REFUSE rather
241
255
  than guess when an inherited `PI_CODING_AGENT_DIR`, `PI_CONFIG_DIR` or `PI_PROFILE` makes it
@@ -17,10 +17,9 @@
17
17
  //
18
18
  // Fence: imported by the root program with `.js` suffixes, same as the sibling
19
19
  // lib/acp modules — no new strip-types fence.
20
- import { readFileSync } from "node:fs";
21
20
  import { createRequire } from "node:module";
22
21
  import { homedir } from "node:os";
23
- import { dirname, join } from "node:path";
22
+ import { fileURLToPath } from "node:url";
24
23
  import { enrichMcpServersWithEnvelope } from "./config.js";
25
24
  import { loadEngraving } from "./engraving.js";
26
25
  import { CORTEX_MODEL_PREFIX, curatedClaudeModels, curatedCortexModels, SUPPORTED_ANTHROPIC_MODEL_IDS, SUPPORTED_CORTEX_MODEL_IDS, } from "./models.js";
@@ -39,19 +38,29 @@ function shellQuote(value) {
39
38
  // claude adapter — the first implementation (the rail's reference backend)
40
39
  // ---------------------------------------------------------------------------
41
40
  const SUPPORTED_CLAUDE_IDS = new Set(SUPPORTED_ANTHROPIC_MODEL_IDS);
42
- /** Resolve the claude-agent-acp launch — package bin (resolve), env override for debug.
43
- * This is the single source for the claude launch spec; backend.ts holds no private copy. */
41
+ /**
42
+ * Resolve the claude launch an ENTWURF-OWNED launcher, or the env override for debug.
43
+ * This is the single source for the claude launch spec; backend.ts holds no private copy.
44
+ *
45
+ * The default no longer names the vendor bin directly. `claude-acp-launch.js`
46
+ * imports it in-process; that file's header carries the reason (#72: a janitor
47
+ * for another harness selects `claude-agent-acp` by argv substring and SIGTERMs
48
+ * it by age, and the vendor's own handler erases the signal into exit 0).
49
+ *
50
+ * `CLAUDE_AGENT_ACP_COMMAND` is an EXPLICIT operator override and is deliberately
51
+ * NOT routed through the launcher: an operator who names their own command owns
52
+ * the result, including the loss of the name split and the signal observation.
53
+ */
44
54
  function resolveClaudeLaunch() {
45
55
  const override = process.env.CLAUDE_AGENT_ACP_COMMAND?.trim();
46
56
  if (override)
47
57
  return { command: "bash", args: ["-lc", override] };
58
+ // Resolved here (not inside the launcher's own directory lookup) so a missing
59
+ // vendor package still fails at launch resolution, where it always failed.
48
60
  const require = createRequire(import.meta.url);
49
- const pkgJsonPath = require.resolve("@agentclientprotocol/claude-agent-acp/package.json");
50
- const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
51
- const binPath = typeof pkgJson.bin === "string" ? pkgJson.bin : pkgJson.bin?.["claude-agent-acp"];
52
- if (!binPath)
53
- throw new Error("@agentclientprotocol/claude-agent-acp resolved but exposes no bin entry");
54
- return { command: process.execPath, args: [join(dirname(pkgJsonPath), binPath)] };
61
+ require.resolve("@agentclientprotocol/claude-agent-acp/package.json");
62
+ const launcher = fileURLToPath(new URL("./claude-acp-launch.js", import.meta.url));
63
+ return { command: process.execPath, args: [launcher] };
55
64
  }
56
65
  export const claudeAdapter = {
57
66
  backend: "claude",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junghanacs/entwurf",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Garden-citizen dispatch substrate and meta-bridge for Claude Code, Codex, Antigravity, and pi harnesses.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -89,16 +89,16 @@
89
89
  "zod": "^3.25.0 || ^4.0.0"
90
90
  },
91
91
  "peerDependencies": {
92
- "@earendil-works/pi-ai": ">=0.84.3 <0.85",
93
- "@earendil-works/pi-coding-agent": ">=0.84.3 <0.85",
94
- "@earendil-works/pi-tui": ">=0.84.3 <0.85",
92
+ "@earendil-works/pi-ai": ">=0.84.4 <0.85",
93
+ "@earendil-works/pi-coding-agent": ">=0.84.4 <0.85",
94
+ "@earendil-works/pi-tui": ">=0.84.4 <0.85",
95
95
  "typebox": "*"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@biomejs/biome": "^2.4.13",
99
- "@earendil-works/pi-ai": "0.84.3",
100
- "@earendil-works/pi-coding-agent": "0.84.3",
101
- "@earendil-works/pi-tui": "0.84.3",
99
+ "@earendil-works/pi-ai": "0.84.4",
100
+ "@earendil-works/pi-coding-agent": "0.84.4",
101
+ "@earendil-works/pi-tui": "0.84.4",
102
102
  "@types/node": "^24.3.0",
103
103
  "husky": "^9.1.7",
104
104
  "rregex": "1.13.1",
@@ -165,7 +165,7 @@
165
165
  "check:toolchain": "pnpm lint && pnpm typecheck",
166
166
  "check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface",
167
167
  "check:contracts": "./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-lock && ./run.sh check-entwurf-v2-decider && ./run.sh check-entwurf-v2-matrix && ./run.sh check-entwurf-v2-release && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-send-fallback && ./run.sh check-entwurf-v2-mailbox && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-runner && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-entwurf-resume-args && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-entwurf-facts && ./run.sh check-control-socket-path && ./run.sh check-socket-discovery && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment",
168
- "check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex",
168
+ "check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-launch-namespace && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex",
169
169
  "check:package": "./run.sh check-install-surface && ./run.sh smoke-meta-install-state && ./run.sh smoke-agy-install-state && ./run.sh smoke-setup-verdict && ./run.sh check-pack-pin-matcher && ./run.sh check-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
170
170
  },
171
171
  "pi": {
@@ -18,10 +18,9 @@
18
18
  // Fence: imported by the root program with `.js` suffixes, same as the sibling
19
19
  // lib/acp modules — no new strip-types fence.
20
20
 
21
- import { readFileSync } from "node:fs";
22
21
  import { createRequire } from "node:module";
23
22
  import { homedir } from "node:os";
24
- import { dirname, join } from "node:path";
23
+ import { fileURLToPath } from "node:url";
25
24
 
26
25
  import type { AcpConnectionLike } from "./acp-client.js";
27
26
  import { enrichMcpServersWithEnvelope, type ResolvedAcpConfig } from "./config.js";
@@ -202,17 +201,28 @@ export interface AcpBackendAdapter {
202
201
 
203
202
  const SUPPORTED_CLAUDE_IDS: ReadonlySet<string> = new Set(SUPPORTED_ANTHROPIC_MODEL_IDS);
204
203
 
205
- /** Resolve the claude-agent-acp launch — package bin (resolve), env override for debug.
206
- * This is the single source for the claude launch spec; backend.ts holds no private copy. */
204
+ /**
205
+ * Resolve the claude launch an ENTWURF-OWNED launcher, or the env override for debug.
206
+ * This is the single source for the claude launch spec; backend.ts holds no private copy.
207
+ *
208
+ * The default no longer names the vendor bin directly. `claude-acp-launch.js`
209
+ * imports it in-process; that file's header carries the reason (#72: a janitor
210
+ * for another harness selects `claude-agent-acp` by argv substring and SIGTERMs
211
+ * it by age, and the vendor's own handler erases the signal into exit 0).
212
+ *
213
+ * `CLAUDE_AGENT_ACP_COMMAND` is an EXPLICIT operator override and is deliberately
214
+ * NOT routed through the launcher: an operator who names their own command owns
215
+ * the result, including the loss of the name split and the signal observation.
216
+ */
207
217
  function resolveClaudeLaunch(): AcpLaunchSpec {
208
218
  const override = process.env.CLAUDE_AGENT_ACP_COMMAND?.trim();
209
219
  if (override) return { command: "bash", args: ["-lc", override] };
220
+ // Resolved here (not inside the launcher's own directory lookup) so a missing
221
+ // vendor package still fails at launch resolution, where it always failed.
210
222
  const require = createRequire(import.meta.url);
211
- const pkgJsonPath = require.resolve("@agentclientprotocol/claude-agent-acp/package.json");
212
- const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as { bin?: string | Record<string, string> };
213
- const binPath = typeof pkgJson.bin === "string" ? pkgJson.bin : pkgJson.bin?.["claude-agent-acp"];
214
- if (!binPath) throw new Error("@agentclientprotocol/claude-agent-acp resolved but exposes no bin entry");
215
- return { command: process.execPath, args: [join(dirname(pkgJsonPath), binPath)] };
223
+ require.resolve("@agentclientprotocol/claude-agent-acp/package.json");
224
+ const launcher = fileURLToPath(new URL("./claude-acp-launch.js", import.meta.url));
225
+ return { command: process.execPath, args: [launcher] };
216
226
  }
217
227
 
218
228
  export const claudeAdapter: AcpBackendAdapter = {