@junghanacs/entwurf 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.
Files changed (90) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +299 -0
  3. package/DELIVERY.md +4 -3
  4. package/README.md +99 -67
  5. package/VERIFY.md +13 -7
  6. package/docs/acp-backend-rail.md +1 -1
  7. package/docs/external-mcp-host.md +147 -9
  8. package/docs/setup-clean-host.md +221 -16
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +65 -20
  10. package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +17 -6
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +1 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +172 -5
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +152 -17
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +271 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +988 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js +574 -0
  18. package/mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js +84 -0
  19. package/mcp/entwurf-bridge/src/index.ts +65 -19
  20. package/mcp/entwurf-bridge/tsconfig.build.json +19 -3
  21. package/mcp/tsconfig.json +10 -0
  22. package/package.json +14 -4
  23. package/pi/entwurf-capabilities.json +1 -0
  24. package/pi/meta-bridge-omp/entwurf-meta-omp/package.json +7 -0
  25. package/pi/omp-receive/entwurf-receive-omp/package.json +7 -0
  26. package/pi-extensions/entwurf-control.ts +12 -9
  27. package/pi-extensions/lib/entwurf-self-address.ts +18 -7
  28. package/pi-extensions/lib/meta-sender-identity.ts +1 -1
  29. package/pi-extensions/lib/meta-session.ts +219 -5
  30. package/pi-extensions/lib/mux-fresh-call.ts +171 -17
  31. package/pi-extensions/lib/omp-fresh-preflight.ts +275 -0
  32. package/pi-extensions/meta-bridge-omp.ts +1244 -0
  33. package/pi-extensions/meta-bridge-receive-omp.ts +666 -0
  34. package/run.sh +1041 -174
  35. package/scripts/agy-bridge.sh +4 -2
  36. package/scripts/check-copilot-birth-hook.ts +700 -23
  37. package/scripts/check-entwurf-self-address.ts +31 -0
  38. package/scripts/check-fresh-cut-gate.sh +12 -4
  39. package/scripts/check-gate-qualification.ts +8 -2
  40. package/scripts/check-harness-admission-parity.ts +143 -0
  41. package/scripts/check-meta-doctor-oracle.sh +20 -0
  42. package/scripts/check-omp-birth-hook.ts +1049 -0
  43. package/scripts/check-omp-fresh-preflight.ts +181 -0
  44. package/scripts/check-omp-receive-arm.ts +516 -0
  45. package/scripts/check-setup-qualification.sh +126 -0
  46. package/scripts/copilot-bridge-doctor.sh +104 -71
  47. package/scripts/copilot-bridge-install.sh +180 -20
  48. package/scripts/copilot-bridge-oracle.sh +223 -0
  49. package/scripts/copilot-bridge-uninstall.sh +137 -0
  50. package/scripts/dev-bin.sh +50 -9
  51. package/scripts/fake-copilot-vendor.sh +67 -0
  52. package/scripts/inventory-verification-surface.ts +1 -1
  53. package/scripts/meta-bridge-hook-log.sh +9 -1
  54. package/scripts/mutants/copilot-birth.json +96 -5
  55. package/scripts/mutants/mux-fresh-call.json +22 -22
  56. package/scripts/mutants/omp-birth.json +173 -0
  57. package/scripts/mutants/omp-fresh.json +300 -0
  58. package/scripts/mutants/omp-receive.json +135 -0
  59. package/scripts/mutants/pi-package-ownership.json +92 -0
  60. package/scripts/mutants/self-address.json +34 -0
  61. package/scripts/mutants/setup-verdict.json +148 -0
  62. package/scripts/mutants/source-install.json +32 -0
  63. package/scripts/omp-bridge-doctor.sh +315 -0
  64. package/scripts/omp-bridge-install.sh +221 -0
  65. package/scripts/omp-bridge-oracle.sh +154 -0
  66. package/scripts/omp-bridge-uninstall.sh +57 -0
  67. package/scripts/omp-mcp-bridge.sh +320 -0
  68. package/scripts/omp-mcp-config.py +392 -0
  69. package/scripts/omp-receive-doctor.sh +246 -0
  70. package/scripts/omp-receive-facts.ts +106 -0
  71. package/scripts/omp-receive-install.sh +228 -0
  72. package/scripts/omp-receive-uninstall.sh +60 -0
  73. package/scripts/omp-tool-surface.py +349 -0
  74. package/scripts/pi_settings_io.py +22 -0
  75. package/scripts/raw-omp-measure/README.md +420 -0
  76. package/scripts/raw-omp-measure/probe-extension.ts +76 -0
  77. package/scripts/raw-omp-measure/probe-receive-surface.ts +250 -0
  78. package/scripts/raw-omp-measure/source-audit.md +414 -0
  79. package/scripts/register-pi-package.py +525 -7
  80. package/scripts/register-pi-provider.py +162 -12
  81. package/scripts/smoke-agy-install-state.sh +86 -30
  82. package/scripts/smoke-agy-statusline-state.sh +9 -6
  83. package/scripts/smoke-omp-bridge-state.sh +221 -0
  84. package/scripts/smoke-omp-fresh-live.ts +497 -0
  85. package/scripts/smoke-omp-mcp-state.sh +327 -0
  86. package/scripts/smoke-omp-receive-live.ts +340 -0
  87. package/scripts/smoke-omp-receive-state.sh +196 -0
  88. package/scripts/smoke-setup-verdict.sh +291 -0
  89. package/scripts/smoke-user-scope-citizen.sh +388 -1
  90. package/scripts/tsconfig.json +2 -0
@@ -1,25 +1,25 @@
1
1
  # Wiring `entwurf-bridge` into an external MCP host
2
2
 
3
- Per-harness registration for Claude Code, Codex CLI, and Antigravity, plus the
4
- PATH/env boundary and the external vs garden-native semantics. `README.md` keeps only
3
+ Per-harness registration for Claude Code, Codex CLI, Antigravity, GitHub Copilot CLI and OMP,
4
+ plus the PATH/env boundary and the external vs garden-native semantics. `README.md` keeps only
5
5
  the one-line registration; everything an operator needs to actually wire a host is here.
6
6
 
7
- `entwurf-bridge` can also be registered in a separate MCP-aware harness (Claude Code, Codex CLI, Antigravity/`agy`, …). That host does **not** become a pi session and does **not** need to be ACP-backed. There are now two honest cases:
7
+ `entwurf-bridge` can also be registered in a separate MCP-aware harness (Claude Code, Codex CLI, Antigravity/`agy`, GitHub Copilot CLI, OMP, …). That host does **not** become a pi session and does **not** need to be ACP-backed. There are now two honest cases:
8
8
 
9
9
  - **plain external MCP host**: no garden meta-record / sender marker. It can call the read surfaces (`entwurf_peers`, `entwurf_inbox_read`), but `entwurf_v2` sends are **refused by default** (#50 C4: "if we don't know who sent it, we don't send it"). The operator may wire the explicit hatch below; the send then goes out external/non-replyable.
10
- - **garden-native native session**: a trusted lifecycle hook minted a garden id and sender marker — `SessionStart` for Claude Code, `PreInvocation` for agy, `userPromptSubmitted`/`sessionStart` for GitHub Copilot CLI. It is not a pi control-socket session, but it can be replyable by garden id when its own mailbox/probe rail says so.
10
+ - **garden-native native session**: a trusted lifecycle hook minted a garden id and sender marker — `SessionStart` for Claude Code, `PreInvocation` for agy, `userPromptSubmitted`/`sessionStart` for GitHub Copilot CLI, and for OMP an in-process extension on `session_start`/`session_switch` that mints only a `mode === "tui"` host. It is not a pi control-socket session, but it can be replyable by garden id when its own mailbox/probe rail says so.
11
11
 
12
12
  **Being garden-native is not the same as being replyable, and Copilot is the case that separates them.** Its hook writes a sender marker, so an `entwurf_v2` send carries its own garden id and the receiver learns who wrote — measured 2026-08-21 on Copilot CLI 1.0.80, where a live send arrived under its own garden id with `origin: "meta-session"` and `replyable: false`. Replyability arrived later and through a different process: a first-party extension (`run.sh install-copilot-receive`) that the CLI forks, which binds to the same V3 record and writes a receiver marker owned by its own pid (#82 RAIL 5). So a Copilot citizen is `replyable: true` exactly while that extension is armed for it, and `replyable: false` — honestly, with a real garden identity — when it is not installed, not launched with `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` (which is what `entwurf copilot` sets for one invocation), not yet born, or gone. Read the two facts off different rails: the sender marker answers *who sent this*; the receiver marker answers *can a reply land*. The onboarding obligations are in [`adding-a-harness.md`](./adding-a-harness.md) step 7, and the evidence boundary — the managed LIVE acceptance that moved receive to D6, and what D7-partial / D3-pending still exclude — is in [`DELIVERY.md`](../DELIVERY.md).
13
13
 
14
- **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, or native-push Antigravity and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation (normal agy birth is automatic through the installed hook). Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?}`**, and reopen a DORMANT pi citizen under its own garden id with **`entwurf_resume_call {target}`**. Fresh call accepts one optional literal absolute `cwd`: omit it or pass `""` for the caller's cwd; otherwise it must name an existing directory and may not contain `#`. Use that input for a new cross-repository sibling — resume preserves a dormant Pi citizen's recorded continuity and is not a cwd substitute. Fresh call needs its selected runtime on the server's PATH (`pi`, `claude`, or `entwurf` — Copilot opens through the managed `entwurf copilot` invocation, never the bare vendor); resume call always needs `pi`. Both start a runtime, while delivery does not. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
14
+ **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, a mailbox-backed self-fetch citizen (Claude Code, Copilot, OMP), or native-push Antigravity and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation (normal agy birth is automatic through the installed hook). Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?}`**, and reopen a DORMANT pi citizen under its own garden id with **`entwurf_resume_call {target}`**. Fresh call accepts one optional literal absolute `cwd`: omit it or pass `""` for the caller's cwd; otherwise it must name an existing directory and may not contain `#`. Use that input for a new cross-repository sibling — resume preserves a dormant Pi citizen's recorded continuity and is not a cwd substitute. Fresh call needs its selected runtime on the server's PATH (`pi`, `claude`, `entwurf` or `omp` — Copilot opens through the managed `entwurf copilot` invocation, never the bare vendor, while OMP is opened as the bare `omp` runtime); resume call always needs `pi`. Both start a runtime, while delivery does not. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
15
15
 
16
- Observed: Claude Code, Codex CLI, Antigravity CLI and GitHub Copilot CLI all reach the read surfaces through this MCP bridge from a plain external host — `entwurf_peers` is a pure fact projection, while `entwurf_inbox_read` is a **mutating drain** (it archives the messages and stamps the read-receipt), so "read" here does not mean side-effect-free; **sending** needs an identity lane. Claude and Copilot become symmetric/replyable through a mailbox-backed meta-session — Claude's watch armed by its own hook, Copilot's by the forked extension it installs; agy becomes symmetric/replyable through its record-backed sender marker plus live native-push probe. Codex has no managed citizen lifecycle yet, so a Codex host cannot send without the explicit anonymous hatch below.
16
+ Observed: Claude Code, Codex CLI, Antigravity CLI, GitHub Copilot CLI and OMP all reach the read surfaces through this MCP bridge from a plain external host — `entwurf_peers` is a pure fact projection, while `entwurf_inbox_read` is a **mutating drain** (it archives the messages and stamps the read-receipt), so "read" here does not mean side-effect-free; **sending** needs an identity lane. Claude, Copilot and OMP become symmetric/replyable through a mailbox-backed meta-session — Claude's watch armed by its own hook, Copilot's by the forked extension it installs, OMP's by an extension running inside the operator's own TUI process; agy becomes symmetric/replyable through its record-backed sender marker plus live native-push probe. Codex has no managed citizen lifecycle yet, so a Codex host cannot send without the explicit anonymous hatch below.
17
17
 
18
18
  Prerequisites on the host running the external MCP client:
19
19
 
20
20
  - A live pi session launched with `--entwurf-control` populates `~/.pi/entwurf-control/<gardenId>.sock` — the key is the **record's** garden id, never a transcript/session id (`PI_SESSION_ID` only carries the id record birth already established). Required for `entwurf_v2` control-socket dispatch and `entwurf_peers`.
21
21
 
22
- > **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. No `entwurf_v2` rail launches a process, so this does not affect delivery — but `entwurf_fresh_call` and `entwurf_resume_call` do open a fixed runtime. If that runtime works in your terminal but an external-host call fails with `spawn pi ENOENT`, `spawn claude ENOENT`, or `spawn entwurf ENOENT`, pass a full PATH in the MCP server `env`, set `ENTWURF_BRIDGE_ENV_FILE` to a small shell file that exports PATH, or point the host at a wrapper that can find the runtime. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
22
+ > **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. No `entwurf_v2` rail launches a process, so this does not affect delivery — but `entwurf_fresh_call` and `entwurf_resume_call` do open a fixed runtime. If that runtime works in your terminal but an external-host call fails with `spawn pi ENOENT`, `spawn claude ENOENT`, `spawn entwurf ENOENT`, or `spawn omp ENOENT`, pass a full PATH in the MCP server `env`, set `ENTWURF_BRIDGE_ENV_FILE` to a small shell file that exports PATH, or point the host at a wrapper that can find the runtime. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
23
23
 
24
24
  > **Identity-carrier boundary.** A complete `PI_SESSION_ID` + `PI_AGENT_ID` pair wins before a
25
25
  > native sender marker in the bridge's authoritative-self resolution. Those variables are correct
@@ -40,6 +40,22 @@ Prerequisites on the host running the external MCP client:
40
40
  > and still speak under the parent's garden id, so the flag verdict must not absorb it. The other
41
41
  > native launchers have NOT been certified against this failure mode; do not read Copilot's cells
42
42
  > as coverage for them.
43
+ >
44
+ > **OMP has both halves, and the strip one is not an omp launcher** (#87 Bundle C). entwurf still
45
+ > owns no managed omp invocation — the bare vendor runtime IS this harness's clause 1 answer, and
46
+ > nothing here argues for a wrapper. The strip sits one level up instead, at the shared launch seam
47
+ > every `entwurf_fresh_call` backend passes through: `SCRUBBED_INHERITED_ENV` empties
48
+ > `PI_SESSION_ID` and `PI_AGENT_ID` on the `new-window` argv itself
49
+ > (`pi-extensions/lib/mux-fresh-call.ts:402`), for all four backends rather than only the one whose
50
+ > measurement surfaced it, because the leak is a property of tmux and not of a vendor. `-e VAR=`
51
+ > sets the variable empty rather than unsetting it — tmux has no per-window unset — and every
52
+ > carrier reader trims and tests truthiness, so empty and absent are the same answer by
53
+ > construction. Pinned by `[QK:FRESHCALL-IDENTITY-SCRUB]` in the `check-mux-fresh-call` vitest lane,
54
+ > inside `check:full`. The DETECT half is unchanged and still load-bearing, because a seam covers
55
+ > only the launches that pass through it: `doctor-omp-bridge` reads `/proc/<pid>/environ` for every
56
+ > live `omp` process and goes RED on its own axis when one carries either carrier. An omp the
57
+ > operator started from a pi citizen's bash never touched the seam and is still unsupported, exactly
58
+ > as this boundary says.
43
59
 
44
60
  Example env file:
45
61
 
@@ -66,7 +82,7 @@ Emergency/manual workaround when the MCP server environment is wrong but an exis
66
82
  External/meta-session semantics:
67
83
 
68
84
  - `entwurf_v2` from a plain external host is **refused by default** (no authoritative sender — #50 C4). With the explicit `ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER=1` hatch it delivers with `origin: "external-mcp"` / `replyable: false`; there is still no reply address.
69
- - `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code) is replyable only while its receiver is live and armed, and a native-push sender (Antigravity) only while its adapter probe finds the live conversation. Identity survives either way; only `replyable` drops to `false`. When it is `true`, `wants_reply: true` is allowed and the receiver can reply to the sender's garden id.
85
+ - `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code, Copilot, OMP) is replyable only while its receiver is live and armed, and a native-push sender (Antigravity) only while its adapter probe finds the live conversation. Identity survives either way; only `replyable` drops to `false`. When it is `true`, `wants_reply: true` is allowed and the receiver can reply to the sender's garden id.
70
86
  - `entwurf_v2` never launches a process, so no delivery path needs `pi` on PATH. A dormant pi target is refused as `dormant-fire-forget-unsupported`: the hidden background resume that used to answer there was withdrawn under the visible-first rule, and re-opening the session is the separate `entwurf_resume_call` verb — which DOES need `pi` on PATH, because it starts one.
71
87
  - `entwurf_self` returns the same authoritative identity for pi sessions **and** trusted meta-sessions. A plain external host with no pi env and no trusted sender marker still fails because there is no reply address to report.
72
88
 
@@ -143,9 +159,131 @@ Unrelated servers, permissions, settings, and hooks are preserved; every adapter
143
159
 
144
160
  The **global** MCP config live agy reads is `~/.gemini/config/mcp_config.json`. `~/.gemini/antigravity-cli/mcp_config.json` is not the global MCP root; the bridge installer one-way cleans only a stale entwurf-owned entry there. After the first model invocation, the imprint hook binds the native `conversationId` to a garden id, the statusline shows `🪛 <garden-id> agy`, and sends from that MCP child carry `agentId=meta-session/antigravity` with `replyable:true` only when the record exists and the live native-push probe succeeds.
145
161
 
162
+ #### OMP (`omp`, oh-my-pi)
163
+
164
+ Use the managed install surface rather than editing omp's files by hand:
165
+
166
+ ```bash
167
+ entwurf install-omp-bridge # the BIRTH extension (a garden id per visible TUI session)
168
+ entwurf install-omp-mcp # the MCP hand (this section)
169
+ entwurf install-omp-receive # the RECEIVER extension (mailbox watch + announce-only doorbell)
170
+
171
+ entwurf doctor-omp-bridge
172
+ entwurf doctor-omp-mcp
173
+ entwurf doctor-omp-receive
174
+ ```
175
+
176
+ Three units, not two. The receiver is what makes the citizen answerable at all — without it omp
177
+ sends under its own garden id and every reply is refused as `mailbox-undeliverable` — and it is
178
+ also a prerequisite of visible fresh: the fresh preflight refuses this host before touching tmux
179
+ when the receive unit is missing, rather than opening a window that can never be reached.
180
+
181
+ `install-omp-mcp` writes ONE server into omp's own user MCP file,
182
+ `<omp agent dir>/mcp.json` (`~/.omp/agent/mcp.json`, profile-aware), in omp's own writer
183
+ shape — `{command, args?, env?}` with `type` omitted, since stdio is the default:
184
+
185
+ ```json
186
+ {
187
+ "$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json",
188
+ "mcpServers": {
189
+ "entwurf-bridge": {
190
+ "command": "bash",
191
+ "args": ["/absolute/path/to/entwurf/mcp/entwurf-bridge/start.sh"],
192
+ "env": { "ENTWURF_BRIDGE_EXTERNAL_AGENT_ID": "external-mcp/omp" }
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ **The server key is a pinned literal, and that is the whole point.** omp translates Claude
199
+ Code's `~/.claude.json` as an import provider, so a host that ever used Claude Code already
200
+ has an `entwurf-bridge` — carrying `external-mcp/claude-code`. An omp session riding that
201
+ import introduces itself to the bridge under Claude Code's name. Writing the native entry
202
+ under the byte-identical key shadows it: native provider priority 100 beats claude 80,
203
+ dedupe is first-wins on the server NAME, and on a key hit the equivalence check is never
204
+ consulted, so an entry whose env deliberately differs still suppresses the import outright
205
+ — not both-loaded, not merged, no warning. A different key would load BOTH.
206
+ `[측정]` 2026-08-27, omp/18.0.0: the vendor's own `/mcp list` pane flipped from
207
+ `Claude Code (~/.claude.json): entwurf-bridge ● connected` to
208
+ `User level (~/.omp/agent/mcp.json): entwurf-bridge ● connected [stdio]`, and the spawned
209
+ bridge child's environ flipped with it.
210
+
211
+ **`disabledServers` is never the way to hide the import.** Suppression is by name and a
212
+ suppressed item still claims the dedupe key, so denylisting `entwurf-bridge` kills the
213
+ native entry and the import together. `[측정]` with that denylist the pane shows
214
+ `entwurf-bridge ○ not connected` and no Claude Code section at all, and no MCP child is
215
+ spawned. The installer refuses to write into a config that denylists its own key, and
216
+ `doctor-omp-mcp` is red while one exists.
217
+
218
+ **The tool names are omp's dialect, not Claude's.** omp mints `mcp__<server>_<tool>` after
219
+ lowercasing and replacing every `[^a-z_]+` run with `_`, collapsing runs and trimming edges,
220
+ so `entwurf_v2` surfaces as `mcp__entwurf_bridge_entwurf_v` — the trailing digit is eaten by
221
+ the charset, not by the length cap. The live tool list is the acceptance oracle; a live
222
+ session mounts all seven.
223
+
224
+ **And the NAME is not the invocation. Under omp's default settings an MCP tool is not a
225
+ function the model calls — it is a virtual file it writes to.** `tools.xdev` (boolean,
226
+ **default on**) mounts "discoverable" tools as `xd://<tool>` devices and DROPS them from the
227
+ top-level toolset; the model then reads `xd://<tool>` for the schema and *writes* the JSON
228
+ argument object to `xd://<tool>` to execute it. `tools.xdevDocs` (**default `builtins`**)
229
+ keeps built-in docs inline while MCP and extension schemas stay off-prompt until read. That
230
+ default costs a real capability. `[측정]` 2026-08-28, omp/18.0.0: with the defaults, a plain
231
+ "send this message to garden id X" produced a `write` to `xd://…entwurf_peers` (a LISTING)
232
+ and then the sentence "보냈습니다" — no `entwurf_v2` call, nothing enqueued, `lastEnqueuedAt`
233
+ unchanged. Discovery and delivery share one verb (`write`) and neither schema was in the
234
+ prompt. The vendor has hit the same shape in its own toolset: its changelog records
235
+ `web_search` becoming unreachable under `tools.xdev: true` because the mount dropped it from
236
+ top-level (`Tool web_search not found`, upstream #5973), fixed by pinning it via
237
+ `XDEV_KEEP_TOP_LEVEL` — a pin no MCP tool has.
238
+
239
+ `read xd://` reports exactly what the default hides. `[측정]` on a host with only this
240
+ bridge registered, **11 devices**: omp's own `ast_edit`, `debug`, `lsp`, `browser`, plus all
241
+ seven `entwurf_*`. So the default does not merely wrap entwurf — it wraps omp's own LSP and
242
+ debugger too.
243
+
244
+ **Set this on any omp host that is meant to work as a citizen:**
245
+
246
+ ```yaml
247
+ # ~/.omp/agent/config.yml
248
+ tools:
249
+ xdev: false # every enabled tool top-level — MCP is MCP again
250
+ ```
251
+
252
+ Nothing is disabled by that: the setting's own text is *"Disable to expose every enabled tool
253
+ top-level"*, and it moves tools rather than removing them. `[측정]` with `xdev: false` the
254
+ same plain-language request produced a first-try `mcp__entwurf_bridge_entwurf_v` function
255
+ call carrying a correct `intent`, the marker landed in the target mailbox, `read xd://`
256
+ answered `xd:// is not mounted in this session.`, and `lsp` / `debug` / `browser` /
257
+ `ast_edit` were all present top-level. The cost is prompt size: the system prompt's
258
+ non-message tokens went 18,707 → 21,834 (+3,127, +17%) on that host.
259
+
260
+ Two boundaries worth carrying:
261
+
262
+ - **`xd://` resolution devices survive the switch.** omp's plan mode and every staged-action
263
+ finalization write to `xd://propose` / `xd://resolve` / `xd://reject`, and its plan prompt
264
+ names them unconditionally — so "turn xdev off" looks like it should break planning. It
265
+ does not: the write dispatcher matches the resolution devices BEFORE the mount check.
266
+ `[측정]` with `xdev: false`, plan mode reached `write xd://propose` and the approval dialog
267
+ normally.
268
+ - **The narrow alternative keeps the wrapper.** `tools.xdevInlineDevices:
269
+ ["mcp__entwurf_bridge_*"]` inlines only our schemas (+1,013 tokens instead of +3,127) and
270
+ also fixed the send in the same measurement — but the 11 devices stay off top-level, `lsp`
271
+ included, and the listing/delivery verb stays shared. Prefer it only on a host carrying so many MCP servers
272
+ that the full top-level toolset is the larger problem.
273
+
274
+ All of the above is measured against omp 18.0.0 and is a setting, not a contract: re-measure
275
+ at a vendor upgrade.
276
+
277
+ Registration is tools, not identity: sending needs the birth extension
278
+ (`install-omp-bridge`), whose sender marker is keyed to the omp host's OWN pid — omp runs
279
+ its extensions in-process, so the marker's owner, the host, and the MCP child's parent are
280
+ one pid rather than the two-process join Claude and Copilot have. An omp session is a
281
+ citizen only in the operator-visible TUI; task subagents borrow its tools under its garden
282
+ id and never receive a second address.
283
+
146
284
  #### External-host skills and commands
147
285
 
148
- MCP registration gives the external harness the tools; the host still needs workflow guidance. Put the Mitsein-over-MCP (cross-harness collaboration) rules in that host's instruction file or, when supported, as a host-native skill. Do not assume pi slash commands are portable across external hosts — if a workflow must work across Claude Code, Codex CLI, Antigravity, and future hosts, make it a skill or MCP tool rather than a command shortcut.
286
+ MCP registration gives the external harness the tools; the host still needs workflow guidance. Put the Mitsein-over-MCP (cross-harness collaboration) rules in that host's instruction file or, when supported, as a host-native skill. Do not assume pi slash commands are portable across external hosts — if a workflow must work across Claude Code, Codex CLI, Antigravity, OMP, and future hosts, make it a skill or MCP tool rather than a command shortcut.
149
287
 
150
288
  For the maintained multi-harness setup and skill/command packaging details, see
151
289
  [agent-config](https://github.com/junghan0611/agent-config). See also the
@@ -9,10 +9,13 @@ only on Linux because its strict live-owner join uses `/proc`.
9
9
  | Component | Requirement | Needed for |
10
10
  |---|---|---|
11
11
  | Node | **`>=24.0.0`** | package and bridge runtime |
12
- | npm | bundled with Node | package installation |
13
- | entwurf | `@junghanacs/entwurf` | all lanes |
14
- | pi | optional, `@earendil-works/pi-coding-agent >=0.84.3 <0.85` | ACP provider, control sockets |
12
+ | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
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
+ | 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
16
  | Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
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) |
16
19
  | Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
17
20
  | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
18
21
 
@@ -51,6 +54,56 @@ npx entwurf check-bridge
51
54
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
52
55
  lists the seven garden tools; it does not prove a backend model turn or native hook.
53
56
 
57
+ Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, agy, Cortex and their
58
+ authentication remain operator-owned optional prerequisites for the integrations that use them;
59
+ all may be absent on an Entwurf-only host. A source checkout's pinned Pi development packages are
60
+ for building and testing this repo, not a transitive product installation promise.
61
+
62
+ Maintainers using a source checkout do not install a second global entwurf package. Full source
63
+ setup currently requires Node 24, pnpm, and Python 3 on PATH; every harness — including pi — is
64
+ optional-by-presence (absent → explicit SKIP, detected but below the supported floor → named FAIL
65
+ with a nonzero setup result):
66
+
67
+ ```bash
68
+ git clone https://github.com/junghan0611/entwurf ~/repos/gh/entwurf
69
+ cd ~/repos/gh/entwurf
70
+ ./run.sh setup /path/to/consumer-project
71
+ ```
72
+
73
+ This owns `~/.local/bin/entwurf` as a symlink to that checkout's `run.sh` and fails if the
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
76
+ detected harness that cannot be completed makes setup exit nonzero. A detected `copilot`
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.
80
+
81
+ ### 1.1 User-scope ownership (one shared registration, one recorded owner)
82
+
83
+ The GLOBAL pi user-scope registration (`~/.pi/agent/settings.json` `packages[]` plus the
84
+ `entwurfProvider.mcpServers.entwurf-bridge` key) is ONE shared entry across every install root, and
85
+ it carries a recorded owner: `packageRoot` in `$XDG_DATA_HOME/entwurf/pi-package/install-state.json`
86
+ and `installerRoot` in the provider install-state. Normal `install`/`setup` from a different root —
87
+ whether the recorded owner is live or missing — refuses with zero settings bytes written; the only
88
+ writer that moves the shared entry is the operator-explicit `entwurf takeover-user-scope`
89
+ (old→new reported). Every user-scope operation is ATOMIC across the two halves: both ownership
90
+ preflights run read-only first, so a refusal on either side leaves the other byte-identical. A
91
+ takeover over an operator's own provider override is a SPLIT verdict — the package owner moves,
92
+ the override is preserved and stays unowned (its stale ownership state is cleared), never a false
93
+ "both owned". A LEGACY provider state (no `installerRoot`) accepts no inverse: run `setup`/`install`
94
+ from the owning root first (named adoption), then remove. Both install-states also bind the exact
95
+ settings file they manage (`managedSettingsPath`): pointing an operation at a different, symlinked
96
+ or unparseable file refuses with zero writes before either half proceeds, and the owned/orphan
97
+ inverse removes only the recorded owner's exact `packages[]` entry (0 or 2+ exact entries refuse).
98
+ `entwurf doctor-pi-package` names the package-side verdict including the
99
+ packageRoot↔installerRoot coupling mismatch and a package/provider managed-path mismatch.
100
+
101
+ | Root shape | Package root written | Stable commands from | Project write | User write | Takeover trigger | Inverse | Stale/moved verdict |
102
+ |---|---|---|---|---|---|---|---|
103
+ | source checkout | the checkout dir | `dev-bin` symlinks (`setup`) | `<project>/.pi/settings.json` | shared entry + owner state | `takeover-user-scope` from the new checkout | `remove` (project) / `remove-user-scope` (global, same-owner-only) | `doctor-pi-package` → `missing-owner`; normal install still refuses |
104
+ | global npm (`npm i -g`) | the global `node_modules/@junghanacs/entwurf` | npm bin linking | same | same shared entry | same explicit action | same; a LIVE foreign owner always refuses | same |
105
+ | project-local npm | that project's `node_modules/@junghanacs/entwurf` | `node_modules/.bin` | same | same shared entry | same explicit action | same | same; a deleted root becomes the aligned `remove-user-scope` orphan cleanup (entry + package state + provider installerRoot must all name that missing root) |
106
+
54
107
  ## 2. Optional pi adapter / ACP plugin
55
108
 
56
109
  Install the exact release floor, then wire the project:
@@ -109,7 +162,135 @@ New macOS wiring is refused because the live join is not instrumented there. Dar
109
162
  uninstall remains available for cleaning an older managed install; this is an evidence
110
163
  boundary, not a permanent impossibility claim.
111
164
 
112
- ## 4. Optional Antigravity native citizen
165
+ ## 4. Optional GitHub Copilot CLI native citizen
166
+
167
+ Copilot has four independently owned surfaces. `setup` composes all four when `copilot` is on
168
+ PATH (#86 C3b); the commands below are the per-unit repair, doctor, and inverse surfaces. All
169
+ four must be green for supported visible fresh; a manual citizen may omit the footer, but fresh
170
+ refuses before opening a window when any required surface is absent.
171
+
172
+ ```bash
173
+ entwurf install-copilot-bridge
174
+ entwurf install-copilot-mcp
175
+ entwurf install-copilot-receive
176
+ entwurf install-copilot-statusline
177
+
178
+ entwurf doctor-copilot-bridge
179
+ entwurf doctor-copilot-mcp
180
+ entwurf doctor-copilot-receive
181
+ entwurf doctor-copilot-statusline
182
+ ```
183
+
184
+ Launch the supported invocation with `entwurf copilot`, not bare `copilot`. It enables extension
185
+ scanning for that process, checks the receiver, removes inherited pi identity carriers, and owns
186
+ the model/permission defaults. Birth occurs on the first prompt. `entwurf_fresh_call` uses this
187
+ same managed invocation and requires the birth, MCP, receiver, and visible-identity preflight.
188
+
189
+ ## 4b. Optional OMP (`omp`) native citizen — accepted on one host
190
+
191
+ Three independently owned surfaces, and a boundary that is part of the instructions rather
192
+ than a footnote. Birth, visible identity, who-sent, the MCP hand and RECEIVE are landed, and
193
+ `entwurf_fresh_call` opens an omp sibling on all three public surfaces. The clause 7 LIVE
194
+ receipt has been taken: `smoke-omp-fresh-live` went green on 2026-08-30 (omp 18.0.0, one
195
+ model, one accepted run), which is what `docs/adding-a-harness.md` step 9 asks for and why
196
+ that smoke is wired as a release-gate MUST — the label was never allowed to move ahead of the
197
+ evidence, and it moved only once the evidence existed. Read the receipt itself in DELIVERY.md's
198
+ OMP row; what it does NOT establish is multi-host, multi-model, or repeated fresh calls in one
199
+ process. Open omp yourself and it is a two-way citizen either way — it sends under its own
200
+ garden id and a reply lands on it.
201
+
202
+ The first turn of a fresh omp sibling is a TWO-STAGE BOOTSTRAP, not a positional prompt: the
203
+ launcher carries `{v,target,nonce,task}` on the fixed registered flag `--entwurf-bootstrap`,
204
+ and the installed birth extension waits until the callback tool is actually callable, sends a
205
+ callback-only prompt, and delivers the operator's task only after that exact call succeeds.
206
+ That is a measured correction — the interactive host defers MCP discovery, so a positional
207
+ first turn began before the tool it named existed.
208
+
209
+ **One operator setting is load-bearing for fresh, not just for receive.** `tools: xdev: false`
210
+ in the omp agent config is checked by the fresh preflight BEFORE any window opens, because the
211
+ vendor default mounts MCP tools as `xd://` devices whose schemas never reach the prompt: a
212
+ sibling launched onto a default-config host would start, look healthy, and be unable to call
213
+ the callback tool at all. A refusal there names `omp-callback-tool-uncallable` and opens
214
+ nothing.
215
+
216
+ ```bash
217
+ entwurf install-omp-bridge # the birth extension, into <omp agent dir>/extensions/
218
+ entwurf install-omp-mcp # the omp-native entwurf-bridge server
219
+ entwurf install-omp-receive # the receiver extension: mailbox watch + doorbell
220
+
221
+ entwurf doctor-omp-bridge
222
+ entwurf doctor-omp-mcp
223
+ entwurf doctor-omp-receive
224
+ ```
225
+
226
+ Order matters only in one direction: the receiver JOINS the citizen birth mints, and
227
+ announces a tool the MCP hand provides. Install it without them and it will log
228
+ `arm-deferred`, give up after ~20s, and `doctor-omp-receive` will name the missing sibling
229
+ as a note rather than a fault.
230
+
231
+ **The receiver arms per session, and only for the visible TUI host.** Opening omp arms it;
232
+ `/new` re-arms it for the replacement citizen and retires the previous one; closing omp
233
+ retires it. A task subagent arms nothing. While nothing is armed, dispatch to that garden
234
+ id is the honest `mailbox-undeliverable` refusal — an unarmed receiver is a legible state,
235
+ not a broken one.
236
+
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.
239
+
240
+ Both installers resolve the omp agent directory the way omp itself does, and REFUSE rather
241
+ than guess when an inherited `PI_CODING_AGENT_DIR`, `PI_CONFIG_DIR` or `PI_PROFILE` makes it
242
+ ambiguous: omp is a pi fork and reads pi's env vocabulary, so those names no longer say which
243
+ harness they address. Pass `ENTWURF_OMP_AGENT_DIR` if you genuinely mean a non-default one.
244
+
245
+ Two things the installers deliberately will NOT do. They never adopt an artifact already
246
+ sitting at their path without entwurf's own ownership state — a directory that merely looks
247
+ like our unit could be yours, and adopting it would overwrite it with no way back — so a
248
+ no-state path is a named refusal you resolve by hand. And the MCP writer's target is exactly
249
+ `<resolved omp agent dir>/mcp.json`; there is no path override, so it can never be aimed at
250
+ another tool's config.
251
+
252
+ **Where an omp citizen's garden artifacts live.** Under `$HOME/.pi/agent/meta-*`, the same
253
+ garden every other citizen uses — and that stays true under `omp --profile work`. omp is a pi
254
+ fork, so the vendor exports `PI_CODING_AGENT_DIR` for every named profile; for entwurf that
255
+ name means pi's persistence root, so honouring it here would put an omp session's record in a
256
+ different store (or in a pi sandbox). For backend omp it is read as the VENDOR's agent dir
257
+ only, and never as a garden root. Its presence on a live omp is normal and is not a fault.
258
+ The four `ENTWURF_META_*` variables remain the way to relocate the garden roots, and for
259
+ backend omp each one must be **absolute or `~`-rooted** (`~` or `~/…`). A relative value is
260
+ refused by name rather than resolved: it would resolve against each process's own working
261
+ directory, and the omp extension and `doctor-omp-bridge` do not share one — the doctor would
262
+ then report on a directory the extension never writes to. A refused value mints nothing and
263
+ turns the doctor's runtime axis red.
264
+
265
+ **One vendor setting is required, and the default is wrong for a citizen.** omp's
266
+ `tools.xdev` (default ON) mounts MCP tools as `xd://<tool>` devices and removes them from the
267
+ model's top-level toolset, so `entwurf_v2` is reached by WRITING JSON to a virtual file rather
268
+ than by calling a tool — and with `tools.xdevDocs` at its `builtins` default its schema is not
269
+ in the prompt at all. On that default a plain "send this to garden id X" was measured to list
270
+ peers and then CLAIM the send without ever dispatching. Put this in `~/.omp/agent/config.yml`
271
+ and restart the session:
272
+
273
+ ```yaml
274
+ tools:
275
+ xdev: false
276
+ ```
277
+
278
+ It disables nothing — it exposes every enabled tool top-level, omp's own `lsp`, `debug`,
279
+ `browser` and `ast_edit` included, which the default was hiding as well. Plan mode and staged
280
+ `xd://resolve` / `xd://propose` finalization keep working. The rationale, the numbers and the
281
+ narrower `tools.xdevInlineDevices` alternative are in
282
+ [`external-mcp-host.md`](./external-mcp-host.md).
283
+
284
+ Birth happens when the TUI OPENS (not on the first prompt, unlike Copilot), and the garden id
285
+ appears on omp's status line as `🪛 <garden-id> omp`. `/new`, fork and in-TUI resume mint the
286
+ replacement session's own record. Task subagents of that session are refused by design — they
287
+ borrow the host's tools under the host's garden id and never get a second address.
288
+
289
+ The MCP entry deliberately uses the same server key as any Claude Code import so that it
290
+ SHADOWS it; see [`external-mcp-host.md`](./external-mcp-host.md) for why that key is pinned and
291
+ why `disabledServers` is never the way to hide an import.
292
+
293
+ ## 5. Optional Antigravity native citizen
113
294
 
114
295
  Install the three independently owned surfaces:
115
296
 
@@ -134,7 +315,7 @@ Real native-push acceptance needs an already-running conversation:
134
315
  LIVE=1 AGY_CONVERSATION_ID=<id> entwurf smoke-agy-native-push-live
135
316
  ```
136
317
 
137
- ## 5. Optional ACP backend turns
318
+ ## 6. Optional ACP backend turns
138
319
 
139
320
  Claude uses the operator's existing local Claude authentication:
140
321
 
@@ -153,25 +334,34 @@ LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> \
153
334
  The aggregate release gate is Claude-backed and does not run Cortex automatically.
154
335
  Its silence is not a Cortex PASS.
155
336
 
156
- ## 6. Upgrade and repair
337
+ ## 7. Upgrade and repair
157
338
 
158
339
  After upgrading the package, rerun the managed installers for every native harness
159
340
  in use and restart their existing processes. Native plugin caches are not live-reload
160
341
  safe across launch-contract changes.
161
342
 
162
343
  If install or doctor reports an unreadable/old active citizen generation, do not edit
163
- records by hand:
344
+ records by hand. Close pi, Claude, Copilot, and agy sessions first, run
345
+ `entwurf meta-bridge-fresh-cut`, and read its exit status before any install. Then choose the
346
+ installation mode you actually own:
164
347
 
165
348
  ```bash
166
- # close pi, Claude, and agy sessions first
167
- entwurf meta-bridge-fresh-cut
168
- entwurf setup ~/entwurf-smoke
349
+ # npm package consumer
350
+ entwurf install ~/entwurf-smoke
351
+ entwurf install-meta-bridge
352
+ # `entwurf setup` re-composes the four Copilot units when `copilot` is on PATH
353
+
354
+ # source maintainer — from the checkout
355
+ ./run.sh setup ~/entwurf-smoke
169
356
  ```
170
357
 
171
- Read the cut's exit status before chaining setup. The complete quiescence, archive,
172
- and exit-code contract is [fresh-cut-policy.md](./fresh-cut-policy.md).
358
+ The package-installed `entwurf setup` is the same consumer command in installed mode: it names
359
+ that mode first, never runs npm/pnpm inside `node_modules` (the frozen pnpm bootstrap is
360
+ source-checkout-only), and composes the detected harnesses with the same per-component
361
+ PASS/SKIP/FAIL summary. The complete quiescence, archive, and exit-code contract is
362
+ [fresh-cut-policy.md](./fresh-cut-policy.md).
173
363
 
174
- ## 7. Release acceptance versus host acceptance
364
+ ## 8. Release acceptance versus host acceptance
175
365
 
176
366
  - `entwurf check-bridge`: installed MCP bytes boot; no backend auth.
177
367
  - `pnpm check` / `pnpm run check:full`: tiered source deterministic floors (everyday
@@ -190,12 +380,27 @@ Run only the surfaces this host owns:
190
380
 
191
381
  ```bash
192
382
  entwurf uninstall-meta-bridge
383
+ entwurf uninstall-copilot-statusline
384
+ entwurf uninstall-copilot-receive
385
+ entwurf uninstall-copilot-mcp
386
+ entwurf uninstall-copilot-bridge
193
387
  entwurf uninstall-agy-hooks
194
388
  entwurf uninstall-agy-statusline
195
389
  entwurf uninstall-agy-bridge
196
- entwurf uninstall ~/entwurf-smoke
390
+ entwurf remove ~/entwurf-smoke
391
+ # only when no other project uses the shared user-scope pi registration:
392
+ entwurf remove-user-scope
197
393
  npm uninstall -g @junghanacs/entwurf
198
394
  ```
199
395
 
200
- Each managed surface has an honest inverse and preserves unrelated native-harness
201
- configuration.
396
+ The package `uninstall-*`/`remove` surfaces preserve unrelated native-harness configuration.
397
+ Copilot birth now has a package-owned inverse: `uninstall-copilot-bridge` removes exactly what
398
+ its install-state (`$XDG_DATA_HOME/entwurf/copilot-bridge/install-state.json`) records — the
399
+ qualified plugin, the local marketplace registration when it is owned and still at the recorded
400
+ path, and the recorded assembly — never with `--force`, never a bare plugin name that could
401
+ match somebody else's unit, and never the stale Claude unit. The complete ownership preflight
402
+ runs read-only before the first vendor write, so a marketplace under our name at another path,
403
+ a registration the state does not own, or a failing vendor list (UNKNOWN, never absence)
404
+ refuses the whole inverse with zero writes; the state is deleted last, so a partial failure
405
+ keeps a rerun-repair authority.
406
+ A legacy no-state installation is adopted by re-running `install-copilot-bridge` first.