@junghanacs/entwurf 0.14.1 → 0.15.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/AGENTS.md +8 -5
- package/CHANGELOG.md +113 -0
- package/DELIVERY.md +73 -3
- package/README.md +55 -4
- package/VERIFY.md +6 -6
- package/demo/README.md +3 -1
- package/demo/demo-baseline.sh +12 -1
- package/demo/demo.sh +9 -1
- package/docs/acp-backend-rail.md +103 -4
- package/docs/external-mcp-host.md +26 -4
- package/docs/setup-clean-host.md +3 -3
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +8 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/copilot-fresh-preflight.js +253 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +10 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +102 -28
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +77 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-copilot.js +288 -0
- package/mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js +139 -47
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +294 -0
- package/mcp/entwurf-bridge/src/index.ts +10 -6
- package/mcp/entwurf-bridge/tsconfig.build.json +17 -6
- package/package.json +23 -12
- package/pi/copilot-receive/entwurf-receive/extension.mjs +323 -0
- package/pi/entwurf-capabilities.json +1 -0
- package/pi/meta-bridge-copilot/.claude-plugin/marketplace.json +11 -0
- package/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/.claude-plugin/plugin.json +5 -0
- package/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/hooks/hooks.json +7 -0
- package/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/scripts/copilot-hook-launch.sh +85 -0
- package/pi-extensions/entwurf-control.ts +17 -8
- package/pi-extensions/lib/acp/backend.ts +229 -9
- package/pi-extensions/lib/copilot-fresh-preflight.ts +251 -0
- package/pi-extensions/lib/meta-sender-identity.ts +10 -2
- package/pi-extensions/lib/meta-session.ts +102 -29
- package/pi-extensions/lib/mux-fresh-call.ts +82 -11
- package/pi-extensions/meta-bridge-hook-copilot.ts +323 -0
- package/run.sh +389 -26
- package/scripts/agy-bridge-config.py +47 -13
- package/scripts/agy-bridge.sh +73 -23
- package/scripts/check-acp-prompt-lifecycle.ts +221 -9
- package/scripts/check-copilot-birth-hook.ts +497 -0
- package/scripts/check-copilot-launch.ts +395 -0
- package/scripts/check-copilot-receive-arm.ts +734 -0
- package/scripts/check-copilot-statusline.ts +122 -0
- package/scripts/check-entwurf-bridge-boot.ts +28 -0
- package/scripts/check-entwurf-capabilities.ts +25 -6
- package/scripts/check-fresh-cut-gate.sh +92 -23
- package/scripts/check-gate-qualification.ts +10 -3
- package/scripts/check-install-container.sh +2 -2
- package/scripts/check-install-surface.ts +1 -1
- package/scripts/check-meta-capability-source.ts +89 -5
- package/scripts/check-meta-doctor-oracle.sh +26 -0
- package/scripts/check-meta-manifest-schema.py +38 -0
- package/scripts/check-meta-receiver-marker.ts +25 -2
- package/scripts/check-meta-session.ts +32 -0
- package/scripts/check-probe-bridge-command.ts +201 -0
- package/scripts/check-release-gate-outcomes.ts +54 -1
- package/scripts/copilot-bridge-doctor.sh +209 -0
- package/scripts/copilot-bridge-install.sh +224 -0
- package/scripts/copilot-launch.sh +236 -0
- package/scripts/copilot-mcp-bridge.sh +177 -0
- package/scripts/copilot-mcp-config.py +221 -0
- package/scripts/copilot-receive-bridge.sh +483 -0
- package/scripts/copilot-statusline-bridge.sh +149 -0
- package/scripts/copilot-statusline-config.py +201 -0
- package/scripts/copilot-statusline.sh +68 -0
- package/scripts/dev-bin.sh +4 -1
- package/scripts/doctor-pi-provider.ts +155 -51
- package/scripts/meta-bridge-hook-log.sh +17 -5
- package/scripts/mutants/acp-prompt-lifecycle.json +25 -3
- package/scripts/mutants/bridge-command-boot.json +107 -0
- package/scripts/mutants/capability-cache.json +64 -0
- package/scripts/mutants/copilot-birth.json +155 -0
- package/scripts/mutants/copilot-launch.json +187 -0
- package/scripts/mutants/copilot-receive.json +263 -0
- package/scripts/mutants/fresh-cut.json +17 -0
- package/scripts/mutants/mux-fresh-call.json +216 -2
- package/scripts/mutants/pack-install.json +17 -0
- package/scripts/mutants/release-gate.json +13 -0
- package/scripts/probe-bridge-command.ts +330 -0
- package/scripts/raw-async-delivery/README.md +203 -1
- package/scripts/raw-async-delivery/copilot-enqueue-addressed.sh +35 -0
- package/scripts/raw-async-delivery/copilot-extension-receive/extension.mjs +123 -0
- package/scripts/raw-async-delivery/copilot-ui-server-probe.mjs +337 -0
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-agy-install-state.sh +76 -2
- package/scripts/smoke-copilot-mcp-state.sh +153 -0
- package/scripts/smoke-copilot-statusline-state.sh +131 -0
- package/scripts/smoke-entwurf-chain-live.ts +1 -1
- package/scripts/smoke-entwurf-v2-matrix-live.ts +2 -2
- package/scripts/smoke-mux-fresh-call-live.ts +3 -1
- package/scripts/smoke-mux-lifecycle-live.ts +4 -2
- package/scripts/smoke-pi-provider-state.sh +135 -6
- package/scripts/smoke-resident-garden-guard.sh +2 -2
- package/scripts/tsconfig.json +1 -0
package/docs/acp-backend-rail.md
CHANGED
|
@@ -70,7 +70,7 @@ none.** A turn ends only on a lifecycle event:
|
|
|
70
70
|
|---|---|
|
|
71
71
|
| the agent answers | mapped through the terminal set above |
|
|
72
72
|
| the operator aborts | ACP `session/cancel` first — the agent closes its own turn (`cancelled → aborted`); process-group teardown only after a bounded grace, so an abort always returns |
|
|
73
|
-
| the child dies / stdio ends | the turn fails naming the exit status and the session-scoped stderr tail
|
|
73
|
+
| the child dies / stdio ends | the turn fails naming the exit status and the session-scoped stderr tail — or an honest bounded absence when transport EOF wins the race — on both the new and the reuse path |
|
|
74
74
|
|
|
75
75
|
Elapsed time is not evidence of failure, and a silent turn is not a failed turn: tool
|
|
76
76
|
use, reasoning, and provider queueing all legitimately outrun any number we could pick.
|
|
@@ -85,6 +85,102 @@ turned one long turn into four in 0.13.0. Gates: `check-acp-prompt-lifecycle` (b
|
|
|
85
85
|
with pi's own classifier as the oracle), `check-probe-ordering` (no production prompt
|
|
86
86
|
cutoff in source).
|
|
87
87
|
|
|
88
|
+
## Support contract (#81)
|
|
89
|
+
|
|
90
|
+
What "supported" means here, per declaration class. The classes are kept apart on purpose: one
|
|
91
|
+
undifferentiated "supported" column is what let a Claude PASS read as if it also certified Cortex.
|
|
92
|
+
|
|
93
|
+
| Surface | Declaration | Class | What a green actually says |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| Entwurf package | `0.14.1` | shipped baseline | the package contract these rows belong to |
|
|
96
|
+
| pi runtime | devDep exact `0.84.3`, peer `>=0.84.3 <0.85` | **exact** oracle + **closed range** | built and certified against 0.84.3; hosts inside the range are accepted, and the ceiling moves only on measurement |
|
|
97
|
+
| ACP wire SDK | `@agentclientprotocol/sdk 1.3.0` | **exact** | the shared wire oracle both adapters speak |
|
|
98
|
+
| Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.70.0` | **exact**, bundled | the adapter we ship and certify; resolved before any PATH fallback |
|
|
99
|
+
| Claude Agent SDK | `0.3.232` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
|
|
100
|
+
| Anthropic SDK | `0.100.1` | **exact**, peer-resolution only | satisfies the Agent SDK peer floor (0.93.0+); never an API client here (gate L4) |
|
|
101
|
+
| Claude Code runtime | `>=2.1.217` (`entwurf.claudeCodeFloor`) | **floor** | below it, hook args are silently dropped; entwurf enforces this itself |
|
|
102
|
+
| Node | `>=24` (`engines.node`) | **floor** | single axis, derived everywhere else |
|
|
103
|
+
| Cortex Code | operator-installed CLI | **on-demand**, external | NOT a pinned dependency. Each LIVE record must name the exact CLI version it measured |
|
|
104
|
+
|
|
105
|
+
Current Cortex host reading: **`Cortex Code v1.1.52`, observed on the reference host `oracle`
|
|
106
|
+
(2026-08-19)**. This is an OBSERVATION of what happens to be installed, not a pin, not a range, and
|
|
107
|
+
not a direct LIVE certification — no Cortex evidence record is created by writing it down. It is
|
|
108
|
+
recorded here only so the on-demand row names a concrete number instead of an abstraction.
|
|
109
|
+
|
|
110
|
+
Reading rules that do not bend:
|
|
111
|
+
|
|
112
|
+
- **Claude PASS never certifies Cortex, and a Cortex SKIP never becomes a PASS.** They are separate
|
|
113
|
+
evidence axes with separate records; Cortex stays outside the Claude release floor.
|
|
114
|
+
- **Exact** means one measured build. **Range** means a closed accepted interval. **Floor** means a
|
|
115
|
+
minimum entwurf enforces itself. **On-demand** means the operator supplies the runtime and the
|
|
116
|
+
evidence names its version — presence of an adapter certifies no external release.
|
|
117
|
+
- A declared pin is not evidence. Package/deterministic gates bind the declarations
|
|
118
|
+
(`check-dep-versions`, `check-acp-sdk-surface`, the pack/install consumer gates); LIVE gates carry
|
|
119
|
+
the runtime claim.
|
|
120
|
+
|
|
121
|
+
### Minimum core value
|
|
122
|
+
|
|
123
|
+
One provider identity, `entwurf`, with model-id routing to backend adapters. The host pi session is
|
|
124
|
+
already the record-backed socket citizen; the ACP plugin mints no second citizen, socket, or peer
|
|
125
|
+
layer. Backend differences stay behind `adapterSettings` and adapter methods, and the turn sequence
|
|
126
|
+
stays backend-invariant.
|
|
127
|
+
|
|
128
|
+
Supported: one real model turn; curated routing with authoritative model enforcement; a narrow
|
|
129
|
+
callable tool surface; explicit MCP wiring including the entwurf bridge; session reuse with
|
|
130
|
+
delta-only user history; operator cancellation with bounded cleanup; honest terminal/error mapping.
|
|
131
|
+
|
|
132
|
+
Not supported, by design: workflow ownership, planner state, a memory DB, transcript hydration,
|
|
133
|
+
credential proxying, or a second harness inside pi.
|
|
134
|
+
|
|
135
|
+
### Capability posture
|
|
136
|
+
|
|
137
|
+
`clientCapabilities: {}` is the support posture, not an oversight. Optional upstream features do not
|
|
138
|
+
become reachable merely because an adapter ships them — but they stay out of reach for **two
|
|
139
|
+
different reasons, and collapsing them would hide a real risk**:
|
|
140
|
+
|
|
141
|
+
- **Capability-gated.** AIR typed session failures, the 0.69.0 AIR file-change report, terminal
|
|
142
|
+
output widgets and nested subagent transcripts each test a client capability entwurf does not
|
|
143
|
+
send, so the adapter itself keeps the legacy path.
|
|
144
|
+
- **Advertised but never called.** Some surfaces carry no capability prerequisite at all — 0.70.0's
|
|
145
|
+
`providers/list` / `providers/set` / `providers/disable` are advertised unconditionally. They are
|
|
146
|
+
unreachable only because the common loop never invokes them (nor `logout`). Nothing upstream
|
|
147
|
+
enforces that; it is our own call-site discipline, and it stops holding the moment we use one.
|
|
148
|
+
|
|
149
|
+
Adopting either class requires a separate observed need plus a complete rendering/lifecycle/evidence
|
|
150
|
+
contract. An optional upstream feature is not a core-value gap.
|
|
151
|
+
|
|
152
|
+
### Bump / defer rubric
|
|
153
|
+
|
|
154
|
+
`claude-agent-acp latest` is not a work queue. Certify when at least one holds:
|
|
155
|
+
|
|
156
|
+
- a turn/session/error path reachable under the default capability posture changes;
|
|
157
|
+
- the Claude Agent SDK, ACP SDK, peer resolution, pi floor, or backend process contract moves;
|
|
158
|
+
- a security, compatibility, or observed runtime defect requires the release;
|
|
159
|
+
- accumulated inactive minors are best folded into the next meaningful certification.
|
|
160
|
+
|
|
161
|
+
Otherwise record and defer. Every certification separates declared pins, deterministic/package
|
|
162
|
+
evidence, Claude LIVE aggregate evidence, and Cortex direct on-demand evidence (with its CLI
|
|
163
|
+
version). Per-bump measurements live in the ROADMAP **Dep bump(별도 트랙)** ledger.
|
|
164
|
+
|
|
165
|
+
### Bridge reachability is part of the contract
|
|
166
|
+
|
|
167
|
+
Explicit entwurf-bridge connectivity inside an ACP turn is core value, so it is measured rather than
|
|
168
|
+
assumed. `command -v` succeeding is not evidence: a launcher can resolve and still fail to exec (a
|
|
169
|
+
relocated package-manager shim deriving its target from `$0` exits 127), which leaves a turn with no
|
|
170
|
+
`mcp__entwurf-bridge__*` tool at all while every ownership check reads green. Both doctors therefore boot the exact configured stdio invocation — `command`, `args`, and
|
|
171
|
+
`env` — and require the full bridge verb set back over one shared leaf
|
|
172
|
+
(`./run.sh probe-bridge-command <cmd>` runs a simple command standalone; the agy doctor passes
|
|
173
|
+
its JSON invocation). The probe waits for a valid MCP `initialize` response before it sends
|
|
174
|
+
`notifications/initialized` and `tools/list`; a tools response alone is not boot evidence.
|
|
175
|
+
|
|
176
|
+
- **agy** probes every configured candidate invocation.
|
|
177
|
+
- **pi** probes every effective stdio invocation, including a legacy managed path or unowned
|
|
178
|
+
override. Runtime truth says whether the command pi will launch works; ownership separately says
|
|
179
|
+
whether entwurf may repair or normalize it.
|
|
180
|
+
|
|
181
|
+
entwurf never repairs a launcher it does not own — a foreign one is reported fail-loud with the
|
|
182
|
+
operator's repair named.
|
|
183
|
+
|
|
88
184
|
## Shipped adapters
|
|
89
185
|
|
|
90
186
|
| Seam | Claude | Cortex Code |
|
|
@@ -159,10 +255,13 @@ caller-session `_meta`, and cross-machine certification.
|
|
|
159
255
|
|
|
160
256
|
A backend can return `newSession` before its declared MCP server is callable. This was
|
|
161
257
|
observed intermittently on the Claude rail and directly on Cortex's private `mcp.json`
|
|
162
|
-
path. Neither `claude-agent-acp` 0.
|
|
258
|
+
path. Neither `claude-agent-acp` 0.70.0 nor the Cortex landing adds a client-side
|
|
163
259
|
readiness fence, and `mcpServerStatus()` is not called by the common loop.
|
|
164
|
-
(Re-measured at the 0.
|
|
165
|
-
|
|
260
|
+
(Re-measured at the 0.68.0 → 0.70.0 bump: AIR typed failures and the 0.69.0 AIR
|
|
261
|
+
file-change report are both capability-gated and unadvertised by entwurf; 0.70.0's
|
|
262
|
+
`providers/set` / `providers/disable` are advertised unconditionally by the adapter but
|
|
263
|
+
entwurf calls neither, so no session's provider state is transitioned; goal extension is
|
|
264
|
+
still not a fence. This bump changes no readiness behavior and closes no part of #72.)
|
|
166
265
|
|
|
167
266
|
### 11-7-a/b. Instrument and first measurement
|
|
168
267
|
|
|
@@ -7,17 +7,39 @@ the one-line registration; everything an operator needs to actually wire a host
|
|
|
7
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:
|
|
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. 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. 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
|
-
|
|
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.)
|
|
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.
|
|
15
17
|
|
|
16
18
|
Prerequisites on the host running the external MCP client:
|
|
17
19
|
|
|
18
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`.
|
|
19
21
|
|
|
20
|
-
> **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` or `spawn
|
|
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.
|
|
23
|
+
|
|
24
|
+
> **Identity-carrier boundary.** A complete `PI_SESSION_ID` + `PI_AGENT_ID` pair wins before a
|
|
25
|
+
> native sender marker in the bridge's authoritative-self resolution. Those variables are correct
|
|
26
|
+
> inside the pi process that planted them from record birth; they are foreign identity in another
|
|
27
|
+
> native harness. Starting that harness from a pi citizen's bash can otherwise make the new host —
|
|
28
|
+
> and any internal agents borrowing its MCP manager — speak under the parent pi garden id. Every
|
|
29
|
+
> managed native launcher therefore removes both variables before exec and lets the launched
|
|
30
|
+
> harness establish its own identity through its trusted birth marker. Clearing only one is not a
|
|
31
|
+
> repair: an incomplete pair merely changes the failure wording, while retaining either carrier
|
|
32
|
+
> invites future partial-reader drift. An unmanaged launch that inherits them is unsupported. A
|
|
33
|
+
> native-harness admission made under this contract must add a doctor cell that names this
|
|
34
|
+
> contamination rather than silently preferring the marker.
|
|
35
|
+
>
|
|
36
|
+
> **Certified so far: Copilot only** (#82 RAIL 9). `scripts/copilot-launch.sh` unsets both before
|
|
37
|
+
> `exec`, `check-copilot-launch` asserts their absence from the vendor's OWN report of its
|
|
38
|
+
> environment (and that clearing one alone does not pass), and `doctor-copilot-receive` reports any
|
|
39
|
+
> live Copilot CLI still carrying either as RED on its own axis — a session can be perfectly armed
|
|
40
|
+
> and still speak under the parent's garden id, so the flag verdict must not absorb it. The other
|
|
41
|
+
> native launchers have NOT been certified against this failure mode; do not read Copilot's cells
|
|
42
|
+
> as coverage for them.
|
|
21
43
|
|
|
22
44
|
Example env file:
|
|
23
45
|
|
package/docs/setup-clean-host.md
CHANGED
|
@@ -11,7 +11,7 @@ only on Linux because its strict live-owner join uses `/proc`.
|
|
|
11
11
|
| Node | **`>=24.0.0`** | package and bridge runtime |
|
|
12
12
|
| npm | bundled with Node | package installation |
|
|
13
13
|
| entwurf | `@junghanacs/entwurf` | all lanes |
|
|
14
|
-
| pi | optional, `@earendil-works/pi-coding-agent >=0.84.
|
|
14
|
+
| pi | optional, `@earendil-works/pi-coding-agent >=0.84.3 <0.85` | ACP provider, control sockets |
|
|
15
15
|
| Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
|
|
16
16
|
| Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
|
|
17
17
|
| Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
|
|
@@ -56,7 +56,7 @@ lists the seven garden tools; it does not prove a backend model turn or native h
|
|
|
56
56
|
Install the exact release floor, then wire the project:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
npm install -g @earendil-works/pi-coding-agent@0.84.
|
|
59
|
+
npm install -g @earendil-works/pi-coding-agent@0.84.3
|
|
60
60
|
pi --version
|
|
61
61
|
|
|
62
62
|
cd ~/entwurf-smoke
|
|
@@ -64,7 +64,7 @@ entwurf install .
|
|
|
64
64
|
pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
The supported range is `>=0.84.
|
|
67
|
+
The supported range is `>=0.84.3 <0.85`. It is a hard minimum: installing this
|
|
68
68
|
release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
|
|
69
69
|
minor. A host using only the external MCP bridge can skip pi until it needs a
|
|
70
70
|
control socket; no delivery rail launches a pi process.
|
|
@@ -562,22 +562,24 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
|
|
|
562
562
|
// its own garden id, a fresh cell answered with its uuidv7 `PI_SESSION_ID` value read out of the
|
|
563
563
|
// environment by an MCP server it had spawned itself — confidently, and wrong. A sibling launched
|
|
564
564
|
// against that answer would call home to a garden id nobody holds.
|
|
565
|
-
server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operator's own tmux session and hand it a first task.
|
|
566
|
-
"backends only: pi, claude-code. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
|
|
565
|
+
server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operator's own tmux session and hand it a first task. Three fixed " +
|
|
566
|
+
"backends only: pi, claude-code, copilot. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
|
|
567
567
|
"sender envelope of that callback is its garden id — that is how you learn the address of something that " +
|
|
568
568
|
"did not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing " +
|
|
569
569
|
"else: it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls " +
|
|
570
570
|
"for the callback; if it never arrives the window is visible and can be read directly. For EXISTING " +
|
|
571
571
|
"citizens use entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and " +
|
|
572
|
-
"is passed to the chosen runtime CLI (`provider/model` for pi; model id/alias for Claude Code
|
|
572
|
+
"is passed to the chosen runtime CLI (`provider/model` for pi; model id/alias for Claude Code; a model name " +
|
|
573
|
+
"or `auto` for copilot). A copilot launch goes through entwurf's own managed invocation and is refused " +
|
|
574
|
+
"BEFORE any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units. An optional " +
|
|
573
575
|
"cwd starts the sibling in ONE literal absolute existing directory (cross-repo fresh) — never pick resume " +
|
|
574
576
|
"for a dormant record's cwd; resume is continuity-only. Omitted/empty cwd means the caller's own directory. " +
|
|
575
577
|
"There are no arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to " +
|
|
576
578
|
"same-user processes on this host. Requires that this agent itself runs " +
|
|
577
579
|
"inside tmux: without a pane anchor there is no session to open a sibling beside.", {
|
|
578
580
|
backend: z
|
|
579
|
-
.enum(["pi", "claude-code"])
|
|
580
|
-
.describe("Which fixed runtime to open. Only these
|
|
581
|
+
.enum(["pi", "claude-code", "copilot"])
|
|
582
|
+
.describe("Which fixed runtime to open. Only these three; there is no arbitrary command."),
|
|
581
583
|
model: z
|
|
582
584
|
.string()
|
|
583
585
|
.min(1)
|
|
@@ -591,7 +593,7 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operato
|
|
|
591
593
|
// as an unused suppression while the escape goes back to being fixable.
|
|
592
594
|
// biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above
|
|
593
595
|
.regex(/^[A-Za-z0-9][A-Za-z0-9._/:\[\]-]*$/)
|
|
594
|
-
.describe("Required runtime model: canonical provider/model for pi,
|
|
596
|
+
.describe("Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot model name (or auto)."),
|
|
595
597
|
task: z
|
|
596
598
|
.string()
|
|
597
599
|
.min(1)
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"claude-code": { "wakeMode": "self-fetch", "deliveryLevel": "D6", "nativeIdLabel": "sessionId" },
|
|
5
5
|
"antigravity": { "wakeMode": "direct-inject", "deliveryLevel": "D6", "nativeIdLabel": "conversationId" },
|
|
6
6
|
"codex": { "wakeMode": "direct-inject", "deliveryLevel": "D6", "nativeIdLabel": "threadId" },
|
|
7
|
+
"copilot": { "wakeMode": "self-fetch", "deliveryLevel": "D6", "nativeIdLabel": "sessionId" },
|
|
7
8
|
"pi": { "wakeMode": "direct-inject", "deliveryLevel": "D6", "nativeIdLabel": "sessionId" }
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* copilot-fresh-preflight — the pre-mutation capability check a Copilot fresh call needs
|
|
3
|
+
* (#82 RAIL 9, `docs/adding-a-harness.md` step 9 clauses 3 and 4).
|
|
4
|
+
*
|
|
5
|
+
* ── Why this is a leaf and not a doctor ──
|
|
6
|
+
*
|
|
7
|
+
* A doctor answers "is this host correctly wired?" and is allowed to be slow, to spawn the
|
|
8
|
+
* vendor CLI, and to read live processes. This is not that. It answers ONE narrower question
|
|
9
|
+
* at ONE moment: *before* `mux-fresh-call` mutates the operator's tmux session, are the four
|
|
10
|
+
* things the fresh contract requires actually in place on this filesystem?
|
|
11
|
+
*
|
|
12
|
+
* birth — without the birth plugin the session mints no record, so the callback
|
|
13
|
+
* would carry no garden id and the sibling never becomes addressable.
|
|
14
|
+
* MCP hand — without the bridge server the callback tool does not exist in that
|
|
15
|
+
* session, and the first turn has nothing to call.
|
|
16
|
+
* receive — without the receiver unit the sibling can be launched and can call
|
|
17
|
+
* home, and then nothing can ever be delivered TO it.
|
|
18
|
+
* visible identity — without the custom footer the citizen has a garden id nobody can see,
|
|
19
|
+
* which step 4 refuses to call lifecycle parity.
|
|
20
|
+
*
|
|
21
|
+
* ── Ordering is the whole point ──
|
|
22
|
+
*
|
|
23
|
+
* `scripts/copilot-launch.sh` already fails closed on the receiver, and that check is kept as
|
|
24
|
+
* it is: it guards every manual `entwurf copilot`, whose accepted contract stays receive-only.
|
|
25
|
+
* But a launcher runs INSIDE the window tmux just made, so its refusal leaves a dead window
|
|
26
|
+
* behind and the caller reads a launch receipt for a sibling that never was. `mux-fresh-call`'s
|
|
27
|
+
* rule is that nothing above the single mutation may leave a window behind, so the fresh lane
|
|
28
|
+
* decides the same facts one layer earlier. The overlap on the receiver axis is deliberate and
|
|
29
|
+
* each side names the other; the other three axes are the FRESH lane's requirement only.
|
|
30
|
+
*
|
|
31
|
+
* ── What this deliberately does NOT claim ──
|
|
32
|
+
*
|
|
33
|
+
* Ownership/configuration truth only: entwurf's units are installed and the settings the
|
|
34
|
+
* vendor will read say what they must say. It does NOT prove the Copilot CLI loaded the
|
|
35
|
+
* plugin, connected the MCP server, scanned the extension, or rendered a garden id in its
|
|
36
|
+
* footer — that is runtime truth, and it belongs to `doctor-copilot-*` (which may spawn the
|
|
37
|
+
* vendor) and to the step 9 clause 7 LIVE receipt. A green preflight is a statement about this
|
|
38
|
+
* filesystem, not a prediction about the next process.
|
|
39
|
+
*
|
|
40
|
+
* Every predicate below MIRRORS the shipped adapter that owns that file, deliberately
|
|
41
|
+
* including its environment seams — a preflight that resolved a path its own installer never
|
|
42
|
+
* writes would refuse a correctly installed host and send the operator hunting.
|
|
43
|
+
*
|
|
44
|
+
* No vendor spawn, no network, no await, no mutation.
|
|
45
|
+
*/
|
|
46
|
+
import { accessSync, constants, existsSync, lstatSync, readFileSync, statSync } from "node:fs";
|
|
47
|
+
import * as path from "node:path";
|
|
48
|
+
const BIRTH_PLUGIN = "entwurf-meta-receive-copilot";
|
|
49
|
+
const RECEIVE_UNIT = "entwurf-receive";
|
|
50
|
+
const MCP_SERVER_KEY = "entwurf-bridge";
|
|
51
|
+
/** Same default and same env seam as `scripts/copilot-statusline-bridge.sh`. */
|
|
52
|
+
const DEFAULT_STATUSLINE_COMMAND = "entwurf-copilot-statusline";
|
|
53
|
+
function dataHome(env) {
|
|
54
|
+
const xdg = env.XDG_DATA_HOME;
|
|
55
|
+
if (typeof xdg === "string" && xdg.length > 0)
|
|
56
|
+
return xdg;
|
|
57
|
+
const home = env.HOME;
|
|
58
|
+
if (typeof home === "string" && home.length > 0)
|
|
59
|
+
return path.join(home, ".local", "share");
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/** A JSON object or nothing. Failure to read, parse, or find an object is the SAME answer
|
|
63
|
+
* here — absent — because the caller's next move is identical in all of them: run the
|
|
64
|
+
* installer. Telling a corrupt state file apart from a missing one is the doctor's job. */
|
|
65
|
+
function readJsonObject(file) {
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
68
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
69
|
+
? parsed
|
|
70
|
+
: null;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Bounded environment probing, Hard Rule 15's stated exception.
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isDir(p) {
|
|
78
|
+
try {
|
|
79
|
+
return statSync(p).isDirectory();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isSymlink(p) {
|
|
86
|
+
try {
|
|
87
|
+
return lstatSync(p).isSymbolicLink();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Same DEST the managed launcher will arm from (`scripts/copilot-launch.sh`).
|
|
94
|
+
* `COPILOT_EXTENSIONS_DIR` wins; otherwise `$HOME/.copilot/extensions/<unit>`.
|
|
95
|
+
* String equality, not realpath: the launcher compares with `[ = ]`. */
|
|
96
|
+
function receiveDest(env) {
|
|
97
|
+
const override = env.COPILOT_EXTENSIONS_DIR;
|
|
98
|
+
if (typeof override === "string" && override.length > 0) {
|
|
99
|
+
return path.join(override, RECEIVE_UNIT);
|
|
100
|
+
}
|
|
101
|
+
const home = env.HOME;
|
|
102
|
+
if (typeof home !== "string" || home.length === 0)
|
|
103
|
+
return null;
|
|
104
|
+
return path.join(home, ".copilot", "extensions", RECEIVE_UNIT);
|
|
105
|
+
}
|
|
106
|
+
function isExecutableFile(p) {
|
|
107
|
+
try {
|
|
108
|
+
// `X_OK` for THIS user, which is what `command -v` answers and what Copilot will need.
|
|
109
|
+
// A mode-bit test would call a root-owned 0700 binary executable for everyone.
|
|
110
|
+
accessSync(p, constants.X_OK);
|
|
111
|
+
return statSync(p).isFile();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The statusline command as the shipped adapter resolves it: a value containing a separator is
|
|
119
|
+
* a path and must be executable; a bare name is looked up on PATH. This is a small local
|
|
120
|
+
* search rather than `mux-launch`'s resolver because the import fence keeps `mux-launch` to
|
|
121
|
+
* exactly two importers — a third would be a decision, and this leaf does not need one.
|
|
122
|
+
*/
|
|
123
|
+
function commandResolvable(command, env) {
|
|
124
|
+
if (command.includes("/"))
|
|
125
|
+
return isExecutableFile(command);
|
|
126
|
+
const raw = env.PATH;
|
|
127
|
+
if (typeof raw !== "string" || raw.length === 0)
|
|
128
|
+
return false;
|
|
129
|
+
for (const dir of raw.split(path.delimiter)) {
|
|
130
|
+
if (dir.length === 0)
|
|
131
|
+
continue;
|
|
132
|
+
if (isExecutableFile(path.join(dir, command)))
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The visible-identity axis, step 9 clause 4. The subject is the EFFECTIVE settings the vendor
|
|
139
|
+
* will read, not entwurf's ownership record: a host can carry a correct footer configuration
|
|
140
|
+
* with no install-state (measured on the acceptance host, 2026-08-24 — `doctor-copilot-statusline`
|
|
141
|
+
* reported `settings: configured (resolvable)` / `state: absent` / rc=0), and refusing that host
|
|
142
|
+
* would be refusing a working visible identity because of a missing receipt.
|
|
143
|
+
*
|
|
144
|
+
* The install-state, when present, is checked for the ONE thing it can still contradict: that
|
|
145
|
+
* it manages the very file Copilot reads. A state pointing somewhere else means two settings
|
|
146
|
+
* files disagree about who owns the footer, and which one wins is not decidable from here.
|
|
147
|
+
*/
|
|
148
|
+
function visibleIdentityMissing(env, data) {
|
|
149
|
+
const command = env.COPILOT_STATUSLINE_COMMAND || DEFAULT_STATUSLINE_COMMAND;
|
|
150
|
+
const configured = env.COPILOT_SETTINGS_CONFIG;
|
|
151
|
+
let settingsPath;
|
|
152
|
+
if (typeof configured === "string" && configured.length > 0) {
|
|
153
|
+
settingsPath = configured;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const home = env.HOME;
|
|
157
|
+
if (typeof home !== "string" || home.length === 0)
|
|
158
|
+
return true;
|
|
159
|
+
settingsPath = path.join(home, ".copilot", "settings.json");
|
|
160
|
+
}
|
|
161
|
+
// A symlinked settings file is somebody else's SSOT; the adapter refuses to touch it and
|
|
162
|
+
// this refuses to certify it.
|
|
163
|
+
if (isSymlink(settingsPath))
|
|
164
|
+
return true;
|
|
165
|
+
const settings = readJsonObject(settingsPath);
|
|
166
|
+
if (settings === null)
|
|
167
|
+
return true;
|
|
168
|
+
const statusLine = settings.statusLine;
|
|
169
|
+
if (typeof statusLine !== "object" || statusLine === null || Array.isArray(statusLine))
|
|
170
|
+
return true;
|
|
171
|
+
if (statusLine.command !== command)
|
|
172
|
+
return true;
|
|
173
|
+
const footer = settings.footer;
|
|
174
|
+
if (typeof footer !== "object" || footer === null || Array.isArray(footer))
|
|
175
|
+
return true;
|
|
176
|
+
// `showCustom` must be exactly true. A truthy string would render nothing.
|
|
177
|
+
if (footer.showCustom !== true)
|
|
178
|
+
return true;
|
|
179
|
+
if (!commandResolvable(command, env))
|
|
180
|
+
return true;
|
|
181
|
+
const state = readJsonObject(path.join(data, "entwurf", "copilot-statusline", "install-state.json"));
|
|
182
|
+
if (state === null)
|
|
183
|
+
return false; // absent state is not drift — see the doc comment above
|
|
184
|
+
const managed = state.managedSettingsPath;
|
|
185
|
+
if (typeof managed !== "string" || !path.isAbsolute(managed))
|
|
186
|
+
return true;
|
|
187
|
+
return path.resolve(managed) !== path.resolve(settingsPath);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Answer for ONE fresh Copilot launch. `null` means every required capability is in place on
|
|
191
|
+
* this filesystem; anything else is the first missing one, in the order the fresh contract
|
|
192
|
+
* consumes them: be born, hold the hand, be reachable, be visible.
|
|
193
|
+
*/
|
|
194
|
+
export function copilotFreshPreflight(env = process.env) {
|
|
195
|
+
const data = dataHome(env);
|
|
196
|
+
// With no HOME and no XDG_DATA_HOME there is no place any of these could be installed.
|
|
197
|
+
// Report the FIRST capability rather than inventing a fifth reason for "no home".
|
|
198
|
+
if (data === null)
|
|
199
|
+
return "copilot-birth-unit-missing";
|
|
200
|
+
// 1. Birth. The assembly is what the installer bakes and what the birth doctor certifies
|
|
201
|
+
// statically; `hooks.json` is what makes it a hook unit rather than a directory. There is
|
|
202
|
+
// no install-state for this unit — the artifact IS its ownership record.
|
|
203
|
+
const birthUnit = path.join(data, "entwurf", "meta-bridge-copilot", ".assembled", BIRTH_PLUGIN);
|
|
204
|
+
if (!isDir(birthUnit) || !existsSync(path.join(birthUnit, "hooks", "hooks.json"))) {
|
|
205
|
+
return "copilot-birth-unit-missing";
|
|
206
|
+
}
|
|
207
|
+
// 2. MCP hand. The install-state names the config it owns, so the config path is READ from
|
|
208
|
+
// ownership truth rather than re-derived — a preflight that guessed would refuse a
|
|
209
|
+
// correctly installed host whose config lives somewhere else.
|
|
210
|
+
const mcpState = readJsonObject(path.join(data, "entwurf", "copilot-mcp", "install-state.json"));
|
|
211
|
+
if (mcpState === null || mcpState.serverKey !== MCP_SERVER_KEY)
|
|
212
|
+
return "copilot-mcp-hand-missing";
|
|
213
|
+
const managedConfigPath = mcpState.managedConfigPath;
|
|
214
|
+
if (typeof managedConfigPath !== "string" || !path.isAbsolute(managedConfigPath))
|
|
215
|
+
return "copilot-mcp-hand-missing";
|
|
216
|
+
const servers = readJsonObject(managedConfigPath)?.mcpServers;
|
|
217
|
+
if (typeof servers !== "object" || servers === null || Array.isArray(servers))
|
|
218
|
+
return "copilot-mcp-hand-missing";
|
|
219
|
+
// The state can be current while the config drifted (hand-edited, restored from a backup,
|
|
220
|
+
// replaced by another tool). The server key present in the file the CLI actually reads is
|
|
221
|
+
// the fact the first turn depends on.
|
|
222
|
+
if (!Object.hasOwn(servers, MCP_SERVER_KEY))
|
|
223
|
+
return "copilot-mcp-hand-missing";
|
|
224
|
+
// 3. Receive. The same facts `scripts/copilot-launch.sh` checks — unit name, recorded
|
|
225
|
+
// path, DEST equality against this env's extensions root, then the entry file.
|
|
226
|
+
const recvState = readJsonObject(path.join(data, "entwurf", "copilot-receive", "install-state.json"));
|
|
227
|
+
if (recvState === null || recvState.unit !== RECEIVE_UNIT)
|
|
228
|
+
return "copilot-receive-unit-missing";
|
|
229
|
+
const recvPath = recvState.path;
|
|
230
|
+
if (typeof recvPath !== "string" || !path.isAbsolute(recvPath))
|
|
231
|
+
return "copilot-receive-unit-missing";
|
|
232
|
+
// Mirror the launcher's path-mismatch predicate PRE-MUTATION. A state that names a
|
|
233
|
+
// real unit in a different extensions root than this env will scan still opens a
|
|
234
|
+
// window today if we only check that the files exist — then `entwurf copilot`
|
|
235
|
+
// refuses inside it and the caller holds a launch receipt for a dead sibling.
|
|
236
|
+
const dest = receiveDest(env);
|
|
237
|
+
if (dest === null || recvPath !== dest)
|
|
238
|
+
return "copilot-receive-unit-missing";
|
|
239
|
+
if (!isDir(recvPath) || !existsSync(path.join(recvPath, "extension.mjs")))
|
|
240
|
+
return "copilot-receive-unit-missing";
|
|
241
|
+
// 4. Visible identity.
|
|
242
|
+
if (visibleIdentityMissing(env, data))
|
|
243
|
+
return "copilot-visible-identity-missing";
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
/** Repair text, one line per reason. A reason a caller cannot act on is a reason they will
|
|
247
|
+
* guess about — each names the exact command that installs the missing capability. */
|
|
248
|
+
export const COPILOT_PREFLIGHT_HINT = {
|
|
249
|
+
"copilot-birth-unit-missing": "the Copilot BIRTH plugin is not installed here, so the sibling would mint no record and its callback would carry no garden id — run: entwurf install-copilot-bridge",
|
|
250
|
+
"copilot-mcp-hand-missing": "the entwurf-bridge MCP server is not registered in the Copilot config this host owns, so the callback tool would not exist in that session — run: entwurf install-copilot-mcp",
|
|
251
|
+
"copilot-receive-unit-missing": "the Copilot RECEIVER extension is not installed here, so the sibling could call home but nothing could ever be delivered to it — run: entwurf install-copilot-receive",
|
|
252
|
+
"copilot-visible-identity-missing": "Copilot's custom footer is not configured to entwurf's resolvable statusline command (or an install-state manages a different settings file), so the sibling's garden id would be visible nowhere — run: entwurf doctor-copilot-statusline, then entwurf install-copilot-statusline",
|
|
253
|
+
};
|
|
@@ -37,8 +37,16 @@
|
|
|
37
37
|
*/
|
|
38
38
|
import { isPlausibleOwnerPid, metaRecordExistsByGardenId, parentPid, readMetaIdentityByGardenId, readMetaSenderMarker, } from "./meta-session.js";
|
|
39
39
|
import { resolveNativePushAdapter } from "./native-push/adapter.js";
|
|
40
|
-
/** Every native backend that mints a garden-id from its own hook and writes a sender marker.
|
|
41
|
-
|
|
40
|
+
/** Every native backend that mints a garden-id from its own hook and writes a sender marker.
|
|
41
|
+
*
|
|
42
|
+
* WRITER AND READER OPEN TOGETHER OR NOT AT ALL. A backend listed here whose hook writes no
|
|
43
|
+
* marker costs one wasted directory read; a backend whose hook writes a marker but is absent
|
|
44
|
+
* here is INVISIBLE — the bridge holds the owner pid, never looks in that directory, and the
|
|
45
|
+
* citizen's sends are refused as anonymous for a reason nothing in the log names. That was
|
|
46
|
+
* exactly the #46 defect on agy, and copilot joined the list only once its own hook wrote one
|
|
47
|
+
* (#82 RAIL 5b). Membership says a marker may EXIST, never that a reply can land: the reply
|
|
48
|
+
* rail is chosen from `nativePushSupported` at the bridge, not from this list. */
|
|
49
|
+
export const META_SENDER_BACKENDS = ["claude-code", "antigravity", "copilot"];
|
|
42
50
|
/**
|
|
43
51
|
* Two live native sessions claim this MCP process as their own. We can SEE both identities but
|
|
44
52
|
* cannot say which one is calling, so we send under neither. Choosing by lookup order, backend
|