@junghanacs/entwurf 0.15.1 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +354 -0
- package/DELIVERY.md +3 -2
- package/README.md +68 -88
- package/VERIFY.md +4 -1
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +1 -1
- package/docs/external-mcp-host.md +147 -9
- package/docs/setup-clean-host.md +125 -6
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +65 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +19 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +17 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +172 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +152 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +271 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +988 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js +574 -0
- package/mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js +84 -0
- package/mcp/entwurf-bridge/src/index.ts +65 -19
- package/mcp/entwurf-bridge/tsconfig.build.json +19 -3
- package/mcp/tsconfig.json +10 -0
- package/package.json +19 -9
- package/pi/entwurf-capabilities.json +1 -0
- package/pi/meta-bridge-omp/entwurf-meta-omp/package.json +7 -0
- package/pi/omp-receive/entwurf-receive-omp/package.json +7 -0
- package/pi-extensions/entwurf-control.ts +12 -9
- package/pi-extensions/lib/acp/backend-adapter.ts +19 -9
- package/pi-extensions/lib/acp/backend.ts +125 -7
- package/pi-extensions/lib/acp/claude-acp-launch.js +100 -0
- package/pi-extensions/lib/entwurf-self-address.ts +18 -7
- package/pi-extensions/lib/meta-sender-identity.ts +1 -1
- package/pi-extensions/lib/meta-session.ts +219 -5
- package/pi-extensions/lib/mux-fresh-call.ts +171 -17
- package/pi-extensions/lib/omp-fresh-preflight.ts +275 -0
- package/pi-extensions/meta-bridge-omp.ts +1244 -0
- package/pi-extensions/meta-bridge-receive-omp.ts +666 -0
- package/run.sh +399 -31
- package/scripts/check-acp-launch-namespace.ts +127 -0
- package/scripts/check-acp-prompt-lifecycle.ts +145 -2
- package/scripts/check-copilot-birth-hook.ts +28 -1
- package/scripts/check-entwurf-self-address.ts +31 -0
- package/scripts/check-gate-qualification.ts +7 -3
- package/scripts/check-harness-admission-parity.ts +143 -0
- package/scripts/check-meta-doctor-oracle.sh +20 -0
- package/scripts/check-omp-birth-hook.ts +1049 -0
- package/scripts/check-omp-fresh-preflight.ts +208 -0
- package/scripts/check-omp-receive-arm.ts +516 -0
- package/scripts/check-setup-qualification.sh +40 -2
- package/scripts/copilot-bridge-oracle.sh +14 -6
- package/scripts/fake-copilot-vendor.sh +4 -2
- package/scripts/inventory-verification-surface.ts +1 -1
- package/scripts/meta-bridge-hook-log.sh +9 -1
- package/scripts/mutants/acp-launch-namespace.json +34 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +67 -2
- package/scripts/mutants/copilot-birth.json +10 -10
- package/scripts/mutants/mux-fresh-call.json +22 -22
- package/scripts/mutants/omp-birth.json +173 -0
- package/scripts/mutants/omp-fresh.json +300 -0
- package/scripts/mutants/omp-receive.json +135 -0
- package/scripts/mutants/pack-install.json +2 -2
- package/scripts/mutants/self-address.json +34 -0
- package/scripts/mutants/setup-verdict.json +35 -0
- package/scripts/omp-bridge-doctor.sh +315 -0
- package/scripts/omp-bridge-install.sh +221 -0
- package/scripts/omp-bridge-oracle.sh +154 -0
- package/scripts/omp-bridge-uninstall.sh +57 -0
- package/scripts/omp-config-xdev.py +310 -0
- package/scripts/omp-config-xdev.sh +76 -0
- package/scripts/omp-mcp-bridge.sh +320 -0
- package/scripts/omp-mcp-config.py +392 -0
- package/scripts/omp-receive-doctor.sh +246 -0
- package/scripts/omp-receive-facts.ts +106 -0
- package/scripts/omp-receive-install.sh +228 -0
- package/scripts/omp-receive-uninstall.sh +60 -0
- package/scripts/omp-tool-surface.py +400 -0
- package/scripts/raw-acp-child-exit-measure/README.md +285 -0
- package/scripts/raw-acp-child-exit-measure/acp-turn-population.py +89 -0
- package/scripts/raw-acp-child-exit-measure/reaper-correlation.py +47 -0
- package/scripts/raw-omp-measure/README.md +420 -0
- package/scripts/raw-omp-measure/probe-extension.ts +76 -0
- package/scripts/raw-omp-measure/probe-receive-surface.ts +250 -0
- package/scripts/raw-omp-measure/source-audit.md +414 -0
- package/scripts/smoke-omp-bridge-state.sh +221 -0
- package/scripts/smoke-omp-fresh-live.ts +497 -0
- package/scripts/smoke-omp-mcp-state.sh +327 -0
- package/scripts/smoke-omp-receive-live.ts +340 -0
- package/scripts/smoke-omp-receive-state.sh +196 -0
- package/scripts/smoke-setup-verdict.sh +48 -3
- package/scripts/tsconfig.json +2 -0
package/docs/setup-clean-host.md
CHANGED
|
@@ -12,9 +12,10 @@ only on Linux because its strict live-owner join uses `/proc`.
|
|
|
12
12
|
| npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
|
|
13
13
|
| Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot wiring writers |
|
|
14
14
|
| entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
|
|
15
|
-
| pi | optional-by-presence, `>=0.84.
|
|
15
|
+
| pi | optional-by-presence, `>=0.84.4 <0.85` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
|
|
16
16
|
| Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
|
|
17
17
|
| GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
|
|
18
|
+
| OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
|
|
18
19
|
| Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
|
|
19
20
|
| Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
|
|
20
21
|
|
|
@@ -71,11 +72,12 @@ cd ~/repos/gh/entwurf
|
|
|
71
72
|
|
|
72
73
|
This owns `~/.local/bin/entwurf` as a symlink to that checkout's `run.sh` and fails if the
|
|
73
74
|
link is foreign, outside PATH, or shadowed by another command. It detects and wires
|
|
74
|
-
pi/Claude/agy/Copilot by presence and prints a computed per-component PASS/SKIP/FAIL summary — a
|
|
75
|
+
pi/Claude/agy/Copilot/OMP by presence and prints a computed per-component PASS/SKIP/FAIL summary — a
|
|
75
76
|
detected harness that cannot be completed makes setup exit nonzero. A detected `copilot`
|
|
76
77
|
composes all four native units (birth → MCP → receiver → visible footer) with independent
|
|
77
|
-
per-unit verdicts (#86 C3b)
|
|
78
|
-
|
|
78
|
+
per-unit verdicts (#86 C3b), and a detected `omp` composes its own four (birth → MCP →
|
|
79
|
+
`tools.xdev` setting → receiver) the same way; §4 and §4b keep the explicit per-unit
|
|
80
|
+
install/doctor/inverse surfaces for repair.
|
|
79
81
|
|
|
80
82
|
### 1.1 User-scope ownership (one shared registration, one recorded owner)
|
|
81
83
|
|
|
@@ -108,7 +110,7 @@ packageRoot↔installerRoot coupling mismatch and a package/provider managed-pat
|
|
|
108
110
|
Install the exact release floor, then wire the project:
|
|
109
111
|
|
|
110
112
|
```bash
|
|
111
|
-
npm install -g @earendil-works/pi-coding-agent@0.84.
|
|
113
|
+
npm install -g @earendil-works/pi-coding-agent@0.84.4
|
|
112
114
|
pi --version
|
|
113
115
|
|
|
114
116
|
cd ~/entwurf-smoke
|
|
@@ -116,7 +118,7 @@ entwurf install .
|
|
|
116
118
|
pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
|
|
117
119
|
```
|
|
118
120
|
|
|
119
|
-
The supported range is `>=0.84.
|
|
121
|
+
The supported range is `>=0.84.4 <0.85`. It is a hard minimum: installing this
|
|
120
122
|
release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
|
|
121
123
|
minor. A host using only the external MCP bridge can skip pi until it needs a
|
|
122
124
|
control socket; no delivery rail launches a pi process.
|
|
@@ -185,6 +187,123 @@ scanning for that process, checks the receiver, removes inherited pi identity ca
|
|
|
185
187
|
the model/permission defaults. Birth occurs on the first prompt. `entwurf_fresh_call` uses this
|
|
186
188
|
same managed invocation and requires the birth, MCP, receiver, and visible-identity preflight.
|
|
187
189
|
|
|
190
|
+
## 4b. Optional OMP (`omp`) native citizen — accepted on one host
|
|
191
|
+
|
|
192
|
+
Three independently owned surfaces, and a boundary that is part of the instructions rather
|
|
193
|
+
than a footnote. Birth, visible identity, who-sent, the MCP hand and RECEIVE are landed, and
|
|
194
|
+
`entwurf_fresh_call` opens an omp sibling on all three public surfaces. The clause 7 LIVE
|
|
195
|
+
receipt has been taken: `smoke-omp-fresh-live` went green on 2026-08-30 (omp 18.0.0, one
|
|
196
|
+
model, one accepted run), which is what `docs/adding-a-harness.md` step 9 asks for and why
|
|
197
|
+
that smoke is wired as a release-gate MUST — the label was never allowed to move ahead of the
|
|
198
|
+
evidence, and it moved only once the evidence existed. Read the receipt itself in DELIVERY.md's
|
|
199
|
+
OMP row; what it does NOT establish is multi-host, multi-model, or repeated fresh calls in one
|
|
200
|
+
process. Open omp yourself and it is a two-way citizen either way — it sends under its own
|
|
201
|
+
garden id and a reply lands on it.
|
|
202
|
+
|
|
203
|
+
The first turn of a fresh omp sibling is a TWO-STAGE BOOTSTRAP, not a positional prompt: the
|
|
204
|
+
launcher carries `{v,target,nonce,task}` on the fixed registered flag `--entwurf-bootstrap`,
|
|
205
|
+
and the installed birth extension waits until the callback tool is actually callable, sends a
|
|
206
|
+
callback-only prompt, and delivers the operator's task only after that exact call succeeds.
|
|
207
|
+
That is a measured correction — the interactive host defers MCP discovery, so a positional
|
|
208
|
+
first turn began before the tool it named existed.
|
|
209
|
+
|
|
210
|
+
**One operator setting is load-bearing for fresh, not just for receive.** `tools: xdev: false`
|
|
211
|
+
in the omp agent config is checked by the fresh preflight BEFORE any window opens, because the
|
|
212
|
+
vendor default mounts MCP tools as `xd://` devices whose schemas never reach the prompt: a
|
|
213
|
+
sibling launched onto a default-config host would start, look healthy, and be unable to call
|
|
214
|
+
the callback tool at all. A refusal there names `omp-callback-tool-uncallable` and opens
|
|
215
|
+
nothing.
|
|
216
|
+
|
|
217
|
+
`setup` composes all four omp units when `omp` is on PATH, and the setting is one of them —
|
|
218
|
+
`entwurf setup` writes `tools: xdev: false` itself. The verbs below are the REPAIR path, not
|
|
219
|
+
the install:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
entwurf install-omp-bridge # the birth extension, into <omp agent dir>/extensions/
|
|
223
|
+
entwurf install-omp-mcp # the omp-native entwurf-bridge server
|
|
224
|
+
entwurf install-omp-config # the operator setting: tools.xdev: false
|
|
225
|
+
entwurf install-omp-receive # the receiver extension: mailbox watch + doorbell
|
|
226
|
+
|
|
227
|
+
entwurf doctor-omp-bridge
|
|
228
|
+
entwurf doctor-omp-mcp # also owns the tools.xdev runtime axis
|
|
229
|
+
entwurf doctor-omp-receive
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The setting writer owns exactly the lines it adds and records them, so `uninstall-omp-config`
|
|
233
|
+
takes back its own bytes and nothing else. It refuses a symlinked config, a config it cannot
|
|
234
|
+
parse, and — deliberately — an EXPLICIT `tools: xdev: true`: that is your decision, not drift,
|
|
235
|
+
so setup names it as a component FAIL for you to resolve instead of overwriting it.
|
|
236
|
+
|
|
237
|
+
Order matters only in one direction: the receiver JOINS the citizen birth mints, and
|
|
238
|
+
announces a tool the MCP hand provides. Install it without them and it will log
|
|
239
|
+
`arm-deferred`, give up after ~20s, and `doctor-omp-receive` will name the missing sibling
|
|
240
|
+
as a note rather than a fault.
|
|
241
|
+
|
|
242
|
+
**The receiver arms per session, and only for the visible TUI host.** Opening omp arms it;
|
|
243
|
+
`/new` re-arms it for the replacement citizen and retires the previous one; closing omp
|
|
244
|
+
retires it. A task subagent arms nothing. While nothing is armed, dispatch to that garden
|
|
245
|
+
id is the honest `mailbox-undeliverable` refusal — an unarmed receiver is a legible state,
|
|
246
|
+
not a broken one.
|
|
247
|
+
|
|
248
|
+
`[측정]` This section used to end by saying `setup` did not compose these and the verbs had to
|
|
249
|
+
be run by hand. That is what v0.16.0 actually shipped, and on an operator host it printed a
|
|
250
|
+
green `setup` summary with OMP entirely absent — no extension, no MCP entry, no visible garden
|
|
251
|
+
id. The composition landed afterwards; `docs/adding-a-harness.md` step 10 is the rule that
|
|
252
|
+
keeps the next harness from repeating it.
|
|
253
|
+
|
|
254
|
+
Both installers resolve the omp agent directory the way omp itself does, and REFUSE rather
|
|
255
|
+
than guess when an inherited `PI_CODING_AGENT_DIR`, `PI_CONFIG_DIR` or `PI_PROFILE` makes it
|
|
256
|
+
ambiguous: omp is a pi fork and reads pi's env vocabulary, so those names no longer say which
|
|
257
|
+
harness they address. Pass `ENTWURF_OMP_AGENT_DIR` if you genuinely mean a non-default one.
|
|
258
|
+
|
|
259
|
+
Two things the installers deliberately will NOT do. They never adopt an artifact already
|
|
260
|
+
sitting at their path without entwurf's own ownership state — a directory that merely looks
|
|
261
|
+
like our unit could be yours, and adopting it would overwrite it with no way back — so a
|
|
262
|
+
no-state path is a named refusal you resolve by hand. And the MCP writer's target is exactly
|
|
263
|
+
`<resolved omp agent dir>/mcp.json`; there is no path override, so it can never be aimed at
|
|
264
|
+
another tool's config.
|
|
265
|
+
|
|
266
|
+
**Where an omp citizen's garden artifacts live.** Under `$HOME/.pi/agent/meta-*`, the same
|
|
267
|
+
garden every other citizen uses — and that stays true under `omp --profile work`. omp is a pi
|
|
268
|
+
fork, so the vendor exports `PI_CODING_AGENT_DIR` for every named profile; for entwurf that
|
|
269
|
+
name means pi's persistence root, so honouring it here would put an omp session's record in a
|
|
270
|
+
different store (or in a pi sandbox). For backend omp it is read as the VENDOR's agent dir
|
|
271
|
+
only, and never as a garden root. Its presence on a live omp is normal and is not a fault.
|
|
272
|
+
The four `ENTWURF_META_*` variables remain the way to relocate the garden roots, and for
|
|
273
|
+
backend omp each one must be **absolute or `~`-rooted** (`~` or `~/…`). A relative value is
|
|
274
|
+
refused by name rather than resolved: it would resolve against each process's own working
|
|
275
|
+
directory, and the omp extension and `doctor-omp-bridge` do not share one — the doctor would
|
|
276
|
+
then report on a directory the extension never writes to. A refused value mints nothing and
|
|
277
|
+
turns the doctor's runtime axis red.
|
|
278
|
+
|
|
279
|
+
**One vendor setting is required, and the default is wrong for a citizen.** omp's
|
|
280
|
+
`tools.xdev` (default ON) mounts MCP tools as `xd://<tool>` devices and removes them from the
|
|
281
|
+
model's top-level toolset, so `entwurf_v2` is reached by WRITING JSON to a virtual file rather
|
|
282
|
+
than by calling a tool — and with `tools.xdevDocs` at its `builtins` default its schema is not
|
|
283
|
+
in the prompt at all. On that default a plain "send this to garden id X" was measured to list
|
|
284
|
+
peers and then CLAIM the send without ever dispatching. Put this in `~/.omp/agent/config.yml`
|
|
285
|
+
and restart the session:
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
tools:
|
|
289
|
+
xdev: false
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
It disables nothing — it exposes every enabled tool top-level, omp's own `lsp`, `debug`,
|
|
293
|
+
`browser` and `ast_edit` included, which the default was hiding as well. Plan mode and staged
|
|
294
|
+
`xd://resolve` / `xd://propose` finalization keep working. The rationale, the numbers and the
|
|
295
|
+
narrower `tools.xdevInlineDevices` alternative are in
|
|
296
|
+
[`external-mcp-host.md`](./external-mcp-host.md).
|
|
297
|
+
|
|
298
|
+
Birth happens when the TUI OPENS (not on the first prompt, unlike Copilot), and the garden id
|
|
299
|
+
appears on omp's status line as `🪛 <garden-id> omp`. `/new`, fork and in-TUI resume mint the
|
|
300
|
+
replacement session's own record. Task subagents of that session are refused by design — they
|
|
301
|
+
borrow the host's tools under the host's garden id and never get a second address.
|
|
302
|
+
|
|
303
|
+
The MCP entry deliberately uses the same server key as any Claude Code import so that it
|
|
304
|
+
SHADOWS it; see [`external-mcp-host.md`](./external-mcp-host.md) for why that key is pinned and
|
|
305
|
+
why `disabledServers` is never the way to hide an import.
|
|
306
|
+
|
|
188
307
|
## 5. Optional Antigravity native citizen
|
|
189
308
|
|
|
190
309
|
Install the three independently owned surfaces:
|
|
@@ -69,14 +69,37 @@ import { listEntwurfFacts } from "../../../pi-extensions/lib/entwurf-fact-provid
|
|
|
69
69
|
import { renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-render.js";
|
|
70
70
|
import { computeSelfAddressability } from "../../../pi-extensions/lib/entwurf-self-address.js";
|
|
71
71
|
import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
|
|
72
|
+
import { resolveMailboxWakeModeCapability } from "../../../pi-extensions/lib/entwurf-v2-decider.js";
|
|
72
73
|
import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/entwurf-v2-surface.js";
|
|
73
74
|
import { makeVisibleResumeDeps, renderVisibleResume, visibleResume, } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.js";
|
|
74
75
|
import { probeNativeSenderAlive, resolveTrustedMetaSenderIdentity, } from "../../../pi-extensions/lib/meta-sender-identity.js";
|
|
75
|
-
import { defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaInbox, readMetaReceiverMarker, } from "../../../pi-extensions/lib/meta-session.js";
|
|
76
|
+
import { applyOmpBridgeChildRootPolicy, defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaInbox, readMetaReceiverMarker, } from "../../../pi-extensions/lib/meta-session.js";
|
|
76
77
|
import { freshCall, renderFreshCall } from "../../../pi-extensions/lib/mux-fresh-call.js";
|
|
77
78
|
import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
|
|
78
79
|
import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
|
|
79
80
|
const HOME = os.homedir();
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// OMP root policy — FIRST, before any lazy default-root consumer (#87 B1).
|
|
83
|
+
//
|
|
84
|
+
// This runs at module load for one reason: every meta-root consumer below resolves its
|
|
85
|
+
// directory lazily inside a tool handler, and the whole point of the policy is that none
|
|
86
|
+
// of them may ever see the foreign value. It is a no-op for every other child — it fires
|
|
87
|
+
// only when this process carries the exact `external-mcp/omp` provenance label its managed
|
|
88
|
+
// entry writes, and then it removes `PI_CODING_AGENT_DIR` from THIS process alone (the omp
|
|
89
|
+
// HOST keeps it: there the variable is the vendor's own agent dir) and pins the four
|
|
90
|
+
// entwurf-owned meta roots to the shared leaf's answer. See `applyOmpBridgeChildRootPolicy`.
|
|
91
|
+
// ============================================================================
|
|
92
|
+
try {
|
|
93
|
+
applyOmpBridgeChildRootPolicy(process.env, HOME);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
// FAIL CLOSED, LOUDLY. The policy refuses an ambiguous garden root (a relative
|
|
97
|
+
// `ENTWURF_META_*` override, #87 A2), and a bridge child that cannot say which store it
|
|
98
|
+
// is addressing must not boot with a guess — the extension half received the identical
|
|
99
|
+
// refusal, so proceeding here is exactly the split the policy exists to prevent.
|
|
100
|
+
console.error(`[entwurf-bridge] fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
80
103
|
// Directory SOURCE is this adapter's own policy — the bridge honours an explicit
|
|
81
104
|
// ENTWURF_DIR override the pi side does not. The path GRAMMAR is the shared leaf.
|
|
82
105
|
const ENTWURF_DIR = process.env.ENTWURF_DIR ?? defaultControlSocketDir(HOME);
|
|
@@ -192,11 +215,17 @@ async function buildTrustedMetaSenderEnvelope(cwd = process.cwd()) {
|
|
|
192
215
|
return null;
|
|
193
216
|
const { marker, identity } = trusted;
|
|
194
217
|
// Identity is trusted — but `replyable` is a SEPARATE fact, and WHICH fact depends on the
|
|
195
|
-
// rail a reply would ride (보정①).
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
218
|
+
// rail a reply would ride (보정①). THREE values, not a native-push-or-self-fetch binary:
|
|
219
|
+
// native-push ← nativePushSupported(backend). NOT wakeMode: `direct-inject` also covers
|
|
220
|
+
// codex/pi, which have no native-push adapter.
|
|
221
|
+
// self-fetch ← resolveMailboxWakeModeCapability (the decider's mailbox seam — one owner
|
|
222
|
+
// with dispatch). A new hardcoded backend list would drift the moment the registry
|
|
223
|
+
// admits another self-fetch citizen.
|
|
224
|
+
// none ← neither. omp today: no mailbox drain, no native-push adapter. Rendering
|
|
225
|
+
// this as self-fetch printed a mailboxPath nothing drains.
|
|
226
|
+
// self-fetch (claude-code/copilot): can this citizen's own inbox wake? → the receiver
|
|
227
|
+
// presence marker (readMetaReceiverMarker folds a dead/reused owner to null, so a match
|
|
228
|
+
// means a live, ARMED receiver — the sender marker proves identity, never an armed watch).
|
|
200
229
|
// native-push (antigravity): there is no inbox and no watch. A reply is injected into a
|
|
201
230
|
// live app-server conversation, so only an adapter probe can answer. Composing the
|
|
202
231
|
// receiver atom here would demand `watchArmed` from a backend that never arms one, and
|
|
@@ -205,7 +234,11 @@ async function buildTrustedMetaSenderEnvelope(cwd = process.cwd()) {
|
|
|
205
234
|
// survive; degrading to null would erase the sender) — only with replyable:false.
|
|
206
235
|
// The rail, named ONCE and reused for both the predicate and the caller's rendering —
|
|
207
236
|
// so entwurf_self can never re-derive it differently from what decided `replyable`.
|
|
208
|
-
const metaDeliveryDomain = nativePushSupported(identity.backend)
|
|
237
|
+
const metaDeliveryDomain = nativePushSupported(identity.backend)
|
|
238
|
+
? "native-push"
|
|
239
|
+
: resolveMailboxWakeModeCapability(identity)
|
|
240
|
+
? "self-fetch"
|
|
241
|
+
: "none";
|
|
209
242
|
const facts = metaDeliveryDomain === "native-push"
|
|
210
243
|
? {
|
|
211
244
|
origin: "meta-session",
|
|
@@ -213,17 +246,23 @@ async function buildTrustedMetaSenderEnvelope(cwd = process.cwd()) {
|
|
|
213
246
|
recordBacked: true,
|
|
214
247
|
probeAlive: await probeNativeSenderAlive(identity),
|
|
215
248
|
}
|
|
216
|
-
:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
249
|
+
: metaDeliveryDomain === "self-fetch"
|
|
250
|
+
? (() => {
|
|
251
|
+
const receiver = readMetaReceiverMarker({ gardenId: identity.gardenId });
|
|
252
|
+
const active = receiverMarkerMatchesIdentity(receiver, identity);
|
|
253
|
+
return {
|
|
254
|
+
origin: "meta-session",
|
|
255
|
+
metaDeliveryDomain,
|
|
256
|
+
recordBacked: true,
|
|
257
|
+
ownerAlive: active,
|
|
258
|
+
watchArmed: active,
|
|
259
|
+
};
|
|
260
|
+
})()
|
|
261
|
+
: {
|
|
220
262
|
origin: "meta-session",
|
|
221
263
|
metaDeliveryDomain,
|
|
222
264
|
recordBacked: true,
|
|
223
|
-
ownerAlive: active,
|
|
224
|
-
watchArmed: active,
|
|
225
265
|
};
|
|
226
|
-
})();
|
|
227
266
|
const self = computeSelfAddressability(facts);
|
|
228
267
|
return {
|
|
229
268
|
envelope: {
|
|
@@ -428,6 +467,9 @@ server.tool("entwurf_self", "Return this caller's authoritative identity envelop
|
|
|
428
467
|
else if (rail === "native-push") {
|
|
429
468
|
lines.push("mailbox: none — native-push has no inbox; a reply direct-injects only while the adapter probe is alive");
|
|
430
469
|
}
|
|
470
|
+
else if (rail === "none") {
|
|
471
|
+
lines.push("mailbox: none — no inbound rail (no mailbox, no native-push adapter)");
|
|
472
|
+
}
|
|
431
473
|
else {
|
|
432
474
|
// Fail-closed, matching computeSelfAddressability's own unsupplied-domain row:
|
|
433
475
|
// with no rail we cannot say how a reply would travel, so we claim no transport.
|
|
@@ -562,24 +604,27 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
|
|
|
562
604
|
// its own garden id, a fresh cell answered with its uuidv7 `PI_SESSION_ID` value read out of the
|
|
563
605
|
// environment by an MCP server it had spawned itself — confidently, and wrong. A sibling launched
|
|
564
606
|
// 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, copilot. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
|
|
607
|
+
server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operator's own tmux session and hand it a first task. Four fixed " +
|
|
608
|
+
"backends only: pi, claude-code, copilot, omp. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
|
|
567
609
|
"sender envelope of that callback is its garden id — that is how you learn the address of something that " +
|
|
568
610
|
"did not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing " +
|
|
569
611
|
"else: it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls " +
|
|
570
612
|
"for the callback; if it never arrives the window is visible and can be read directly. For EXISTING " +
|
|
571
613
|
"citizens use entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and " +
|
|
572
614
|
"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
|
|
574
|
-
"BEFORE any window opens if this host lacks the Copilot birth, MCP, receiver or
|
|
615
|
+
"or `auto` for copilot; a fuzzy model pattern for omp). A copilot launch goes through entwurf's own managed " +
|
|
616
|
+
"invocation and is refused BEFORE any window opens if this host lacks the Copilot birth, MCP, receiver or " +
|
|
617
|
+
"visible-footer units; an omp launch is refused the same way if this host lacks the OMP birth, MCP, receiver or " +
|
|
618
|
+
"visible-status units, or if omp's tools.xdev is not false (the vendor default hides MCP tool schemas from the " +
|
|
619
|
+
"prompt, so the sibling could not call you back at all). An optional " +
|
|
575
620
|
"cwd starts the sibling in ONE literal absolute existing directory (cross-repo fresh) — never pick resume " +
|
|
576
621
|
"for a dormant record's cwd; resume is continuity-only. Omitted/empty cwd means the caller's own directory. " +
|
|
577
622
|
"There are no arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to " +
|
|
578
623
|
"same-user processes on this host. Requires that this agent itself runs " +
|
|
579
624
|
"inside tmux: without a pane anchor there is no session to open a sibling beside.", {
|
|
580
625
|
backend: z
|
|
581
|
-
.enum(["pi", "claude-code", "copilot"])
|
|
582
|
-
.describe("Which fixed runtime to open. Only these
|
|
626
|
+
.enum(["pi", "claude-code", "copilot", "omp"])
|
|
627
|
+
.describe("Which fixed runtime to open. Only these four; there is no arbitrary command."),
|
|
583
628
|
model: z
|
|
584
629
|
.string()
|
|
585
630
|
.min(1)
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"antigravity": { "wakeMode": "direct-inject", "deliveryLevel": "D6", "nativeIdLabel": "conversationId" },
|
|
6
6
|
"codex": { "wakeMode": "direct-inject", "deliveryLevel": "D6", "nativeIdLabel": "threadId" },
|
|
7
7
|
"copilot": { "wakeMode": "self-fetch", "deliveryLevel": "D6", "nativeIdLabel": "sessionId" },
|
|
8
|
+
"omp": { "wakeMode": "self-fetch", "deliveryLevel": "D6", "nativeIdLabel": "sessionId" },
|
|
8
9
|
"pi": { "wakeMode": "direct-inject", "deliveryLevel": "D6", "nativeIdLabel": "sessionId" }
|
|
9
10
|
}
|
|
10
11
|
}
|
|
@@ -17,10 +17,9 @@
|
|
|
17
17
|
//
|
|
18
18
|
// Fence: imported by the root program with `.js` suffixes, same as the sibling
|
|
19
19
|
// lib/acp modules — no new strip-types fence.
|
|
20
|
-
import { readFileSync } from "node:fs";
|
|
21
20
|
import { createRequire } from "node:module";
|
|
22
21
|
import { homedir } from "node:os";
|
|
23
|
-
import {
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
24
23
|
import { enrichMcpServersWithEnvelope } from "./config.js";
|
|
25
24
|
import { loadEngraving } from "./engraving.js";
|
|
26
25
|
import { CORTEX_MODEL_PREFIX, curatedClaudeModels, curatedCortexModels, SUPPORTED_ANTHROPIC_MODEL_IDS, SUPPORTED_CORTEX_MODEL_IDS, } from "./models.js";
|
|
@@ -39,19 +38,29 @@ function shellQuote(value) {
|
|
|
39
38
|
// claude adapter — the first implementation (the rail's reference backend)
|
|
40
39
|
// ---------------------------------------------------------------------------
|
|
41
40
|
const SUPPORTED_CLAUDE_IDS = new Set(SUPPORTED_ANTHROPIC_MODEL_IDS);
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the claude launch — an ENTWURF-OWNED launcher, or the env override for debug.
|
|
43
|
+
* This is the single source for the claude launch spec; backend.ts holds no private copy.
|
|
44
|
+
*
|
|
45
|
+
* The default no longer names the vendor bin directly. `claude-acp-launch.js`
|
|
46
|
+
* imports it in-process; that file's header carries the reason (#72: a janitor
|
|
47
|
+
* for another harness selects `claude-agent-acp` by argv substring and SIGTERMs
|
|
48
|
+
* it by age, and the vendor's own handler erases the signal into exit 0).
|
|
49
|
+
*
|
|
50
|
+
* `CLAUDE_AGENT_ACP_COMMAND` is an EXPLICIT operator override and is deliberately
|
|
51
|
+
* NOT routed through the launcher: an operator who names their own command owns
|
|
52
|
+
* the result, including the loss of the name split and the signal observation.
|
|
53
|
+
*/
|
|
44
54
|
function resolveClaudeLaunch() {
|
|
45
55
|
const override = process.env.CLAUDE_AGENT_ACP_COMMAND?.trim();
|
|
46
56
|
if (override)
|
|
47
57
|
return { command: "bash", args: ["-lc", override] };
|
|
58
|
+
// Resolved here (not inside the launcher's own directory lookup) so a missing
|
|
59
|
+
// vendor package still fails at launch resolution, where it always failed.
|
|
48
60
|
const require = createRequire(import.meta.url);
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
if (!binPath)
|
|
53
|
-
throw new Error("@agentclientprotocol/claude-agent-acp resolved but exposes no bin entry");
|
|
54
|
-
return { command: process.execPath, args: [join(dirname(pkgJsonPath), binPath)] };
|
|
61
|
+
require.resolve("@agentclientprotocol/claude-agent-acp/package.json");
|
|
62
|
+
const launcher = fileURLToPath(new URL("./claude-acp-launch.js", import.meta.url));
|
|
63
|
+
return { command: process.execPath, args: [launcher] };
|
|
55
64
|
}
|
|
56
65
|
export const claudeAdapter = {
|
|
57
66
|
backend: "claude",
|
|
@@ -33,10 +33,13 @@
|
|
|
33
33
|
* - external-mcp: never replyable — no authoritative reply address.
|
|
34
34
|
*
|
|
35
35
|
* `origin` stays sender-carrier PROVENANCE, never the citizen identity authority and never a rail.
|
|
36
|
-
* Which rail a meta citizen's reply rides is a SECOND axis — `metaDeliveryDomain
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
36
|
+
* Which rail a meta citizen's reply rides is a SECOND axis — `metaDeliveryDomain`.
|
|
37
|
+
* The caller derives it as three values, never as a native-push-or-self-fetch binary:
|
|
38
|
+
* native-push ← `nativePushSupported(backend)` (the adapter list; NOT wakeMode —
|
|
39
|
+
* `direct-inject` also covers codex/pi, which have no native-push adapter)
|
|
40
|
+
* self-fetch ← `resolveMailboxWakeModeCapability` (the decider's own mailbox seam)
|
|
41
|
+
* none ← neither (omp today: no mailbox drain, no native-push adapter)
|
|
42
|
+
* Fail-closed: an unsupplied domain is not replyable.
|
|
40
43
|
*/
|
|
41
44
|
import { computeMetaReceiverActive, nativePushDeliverable } from "./entwurf-deliverability.js";
|
|
42
45
|
/**
|
|
@@ -65,9 +68,11 @@ export function computeSelfAddressability(facts) {
|
|
|
65
68
|
};
|
|
66
69
|
}
|
|
67
70
|
case "meta-session": {
|
|
68
|
-
//
|
|
71
|
+
// THREE rails, pinned apart (보정①). Each branch composes the predicate that OWNS its
|
|
69
72
|
// axis — the mailbox receiver atom and the native-push predicate share nothing, so a
|
|
70
|
-
// mailbox liveness fact can never leak into a backend that has no mailbox.
|
|
73
|
+
// mailbox liveness fact can never leak into a backend that has no mailbox. `none` is
|
|
74
|
+
// the remainder: no inbound rail at all (not an unsupplied domain — that stays the
|
|
75
|
+
// default fail-closed row).
|
|
71
76
|
switch (facts.metaDeliveryDomain) {
|
|
72
77
|
case "native-push": {
|
|
73
78
|
const push = nativePushDeliverable({ recordBacked: facts.recordBacked, probeAlive: facts.probeAlive });
|
|
@@ -93,6 +98,12 @@ export function computeSelfAddressability(facts) {
|
|
|
93
98
|
reason: recv.active ? `meta receiver active (${recv.reason})` : `meta receiver inactive — ${recv.reason}`,
|
|
94
99
|
};
|
|
95
100
|
}
|
|
101
|
+
case "none":
|
|
102
|
+
return {
|
|
103
|
+
replyable: false,
|
|
104
|
+
socketState: "none",
|
|
105
|
+
reason: "no inbound rail — this backend has no mailbox and no native-push adapter",
|
|
106
|
+
};
|
|
96
107
|
default:
|
|
97
108
|
return {
|
|
98
109
|
replyable: false,
|
|
@@ -46,7 +46,7 @@ import { resolveNativePushAdapter } from "./native-push/adapter.js";
|
|
|
46
46
|
* exactly the #46 defect on agy, and copilot joined the list only once its own hook wrote one
|
|
47
47
|
* (#82 RAIL 5b). Membership says a marker may EXIST, never that a reply can land: the reply
|
|
48
48
|
* rail is chosen from `nativePushSupported` at the bridge, not from this list. */
|
|
49
|
-
export const META_SENDER_BACKENDS = ["claude-code", "antigravity", "copilot"];
|
|
49
|
+
export const META_SENDER_BACKENDS = ["claude-code", "antigravity", "copilot", "omp"];
|
|
50
50
|
/**
|
|
51
51
|
* Two live native sessions claim this MCP process as their own. We can SEE both identities but
|
|
52
52
|
* cannot say which one is calling, so we send under neither. Choosing by lookup order, backend
|