@junghanacs/entwurf 0.25.1 → 0.30.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/BASELINE.md +1 -1
- package/CHANGELOG.md +190 -0
- package/DELIVERY.md +1 -1
- package/README.md +183 -674
- package/VERIFY.md +21 -17
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +115 -18
- package/docs/external-mcp-host.md +1 -1
- package/docs/setup-clean-host.md +346 -10
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +27 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +55 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-mailbox.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +3 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +13 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +3 -3
- package/mcp/entwurf-bridge/src/index.ts +30 -3
- package/package.json +13 -13
- package/pi-extensions/entwurf-control.ts +50 -637
- package/pi-extensions/lib/acp/backend-adapter.ts +81 -1
- package/pi-extensions/lib/acp/backend.ts +41 -5
- package/pi-extensions/lib/entwurf-fact-provider.ts +2 -2
- package/pi-extensions/lib/entwurf-mcp-server.ts +74 -0
- package/pi-extensions/lib/entwurf-peers-render.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-decider.ts +2 -2
- package/pi-extensions/lib/entwurf-v2-mailbox.ts +2 -2
- package/pi-extensions/lib/entwurf-v2-production.ts +3 -3
- package/pi-extensions/lib/entwurf-v2-surface.ts +3 -5
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +3 -3
- package/pi-extensions/lib/fresh-call-composition.ts +13 -6
- package/pi-extensions/lib/meta-mailbox-body.ts +1 -1
- package/pi-extensions/lib/mux-fresh-call.ts +3 -3
- package/run.sh +177 -53
- package/scripts/check-entwurf-peers-surface.ts +8 -15
- package/scripts/check-entwurf-self-address.ts +7 -11
- package/scripts/check-entwurf-v2-surface.ts +20 -112
- package/scripts/check-fresh-call-dispatch.ts +12 -13
- package/scripts/check-gate-qualification.ts +250 -5
- package/scripts/check-herdr-plugin-build.ts +159 -57
- package/scripts/check-install-surface.ts +3 -2
- package/scripts/check-mux-resume-call.ts +24 -35
- package/scripts/check-pi-bridge-sender.ts +130 -0
- package/scripts/check-pi-mcp-bridge.ts +251 -0
- package/scripts/fixtures/herdr-supply.json +8 -8
- package/scripts/lib/codex-fresh-live-protocol.ts +17 -5
- package/scripts/lib/codex-fresh-source-receipts.ts +21 -2
- package/scripts/lib/mutation-qualify.ts +269 -44
- package/scripts/lib/pi-mcp-bridge-probe.mjs +170 -0
- package/scripts/meta-bridge-doctor.sh +16 -5
- package/scripts/mutants/acp-foreground-only.json +17 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +2 -2
- package/scripts/mutants/acp-vendor-rejection.json +62 -0
- package/scripts/mutants/codex-native.json +11 -11
- package/scripts/mutants/control-send-receipt.json +6 -4
- package/scripts/mutants/copilot-birth.json +2 -1
- package/scripts/mutants/fresh-call-dispatch.json +3 -14
- package/scripts/mutants/gate-qualification.json +59 -0
- package/scripts/mutants/herdr-plugin-build.json +11 -0
- package/scripts/mutants/herdr-supply.json +3 -3
- package/scripts/mutants/mux-fresh-call.json +43 -45
- package/scripts/mutants/mux-resume-call.json +11 -35
- package/scripts/mutants/pack-install.json +15 -4
- package/scripts/mutants/pi-bridge-sender.json +54 -0
- package/scripts/mutants/pi-floor.json +6 -2
- package/scripts/mutants/pi-mcp-bridge.json +36 -0
- package/scripts/mutants/pi-mcp-register.json +51 -0
- package/scripts/mutants/v2-surface.json +6 -6
- package/scripts/resident-rpc-drive.ts +5 -1
- package/scripts/smoke-acp-bundled-mcp-live.ts +3 -1
- package/scripts/smoke-acp-cortex-live.ts +3 -1
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-session-reuse-live.ts +29 -12
- package/scripts/smoke-acp-socket-citizen-live.ts +3 -1
- package/scripts/smoke-acp-v2-send-live.ts +3 -1
- package/scripts/smoke-codex-fresh-live.ts +20 -1
- package/scripts/smoke-entwurf-chain-live.ts +6 -2
- package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -1
- package/scripts/smoke-herdr-fresh-call-live.ts +21 -7
- package/scripts/smoke-herdr-raw-install-live.sh +1 -1
- package/scripts/smoke-mux-lifecycle-live.ts +1 -1
- package/scripts/smoke-resident-garden-guard.sh +3 -1
package/docs/setup-clean-host.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Clean-host setup
|
|
2
2
|
|
|
3
|
-
Operator recipe for
|
|
3
|
+
Operator recipe for the **direct installation route** on a POSIX host. Start at
|
|
4
|
+
[the product entry](../README.md#install); for the separate Herdr plugin route,
|
|
5
|
+
use [Herdr Entwurf](https://github.com/junghan0611/entwurf/blob/main/plugins/herdr/README.md).
|
|
6
|
+
Neither route installs a harness, subscription, credential, or login. Entwurf installs
|
|
7
|
+
its own bytes; `setup` composes the operator-installed harnesses it finds.
|
|
8
|
+
|
|
9
|
+
**Linux is the certified axis** — §2–§6 are
|
|
4
10
|
written for it and their receipts are Linux receipts. What follows is the macOS
|
|
5
11
|
evidence split, and its three states must not collapse into "macOS is supported":
|
|
6
12
|
|
|
@@ -22,7 +28,7 @@ evidence split, and its three states must not collapse into "macOS is supported"
|
|
|
22
28
|
| npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
|
|
23
29
|
| Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
|
|
24
30
|
| entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
|
|
25
|
-
| pi | optional-by-presence, `>=0.
|
|
31
|
+
| pi | optional-by-presence, `>=1.0.0 <1.1` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
|
|
26
32
|
| Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
|
|
27
33
|
| 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 |
|
|
28
34
|
| 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) |
|
|
@@ -77,6 +83,7 @@ working directory:
|
|
|
77
83
|
```bash
|
|
78
84
|
npm install -g @junghanacs/entwurf
|
|
79
85
|
entwurf --help
|
|
86
|
+
entwurf setup /path/to/your-project
|
|
80
87
|
entwurf check-bridge
|
|
81
88
|
```
|
|
82
89
|
|
|
@@ -86,11 +93,16 @@ A project-local installation is also supported:
|
|
|
86
93
|
mkdir -p ~/entwurf-smoke && cd ~/entwurf-smoke
|
|
87
94
|
npm init -y
|
|
88
95
|
npm install --save-dev @junghanacs/entwurf
|
|
96
|
+
npx entwurf setup .
|
|
89
97
|
npx entwurf check-bridge
|
|
90
98
|
```
|
|
91
99
|
|
|
100
|
+
Use `entwurf <command>` for a global install, `npx entwurf <command>` from the
|
|
101
|
+
project-local install, or `./run.sh <command>` from a source checkout. Keep that
|
|
102
|
+
same scope when upgrading and checking; native process restarts are separate (§7).
|
|
103
|
+
|
|
92
104
|
`check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
|
|
93
|
-
lists the
|
|
105
|
+
lists the eight garden tools; it does not prove a backend model turn or native hook.
|
|
94
106
|
|
|
95
107
|
Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
|
|
96
108
|
Cortex, and their authentication remain operator-owned optional prerequisites for the
|
|
@@ -119,6 +131,28 @@ component outcomes, preserves exact env forwarding and idempotence, and returns
|
|
|
119
131
|
`codex-birth FAIL` until the vendor trust receipt exists.
|
|
120
132
|
§4, §4b, and §4c keep the explicit per-unit install/doctor/inverse surfaces.
|
|
121
133
|
|
|
134
|
+
### Stable bins and external MCP hosts
|
|
135
|
+
|
|
136
|
+
The package exposes six bins: `entwurf` (the operator command), `entwurf-bridge`
|
|
137
|
+
(MCP stdio), `entwurf-statusline` (Claude Code), `entwurf-agy-statusline`,
|
|
138
|
+
`entwurf-agy-imprint` (the agy birth hook), and `entwurf-copilot-statusline`.
|
|
139
|
+
Stable bin names keep native settings independent of versioned package-store paths.
|
|
140
|
+
|
|
141
|
+
A plain MCP registration exposes tools, but **does not make a garden citizen**:
|
|
142
|
+
a trusted lifecycle/identity unit must also birth the record. A plain host has no
|
|
143
|
+
authoritative sender and `entwurf_v2` is refused by default. `setup` composes those
|
|
144
|
+
units for detected harnesses; the per-harness commands below are repair surfaces.
|
|
145
|
+
For explicit manual registration, for example:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
claude mcp add --scope user entwurf-bridge entwurf-bridge
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
A project-local MCP host can point at `node_modules/.bin/entwurf-bridge`.
|
|
152
|
+
[External MCP hosts](./external-mcp-host.md) owns each host's config paths,
|
|
153
|
+
PATH/env boundary (including GUI-launched servers), anonymous-sender hatch, and
|
|
154
|
+
external-versus-garden-native semantics. There is no ambient MCP config scan.
|
|
155
|
+
|
|
122
156
|
### 1.1 User-scope ownership (one shared registration, one recorded owner)
|
|
123
157
|
|
|
124
158
|
The GLOBAL pi user-scope registration (`~/.pi/agent/settings.json` `packages[]` plus the
|
|
@@ -156,7 +190,7 @@ on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
|
|
|
156
190
|
Install the exact release floor, then wire the project:
|
|
157
191
|
|
|
158
192
|
```bash
|
|
159
|
-
npm install -g @earendil-works/pi-coding-agent@0.
|
|
193
|
+
npm install -g @earendil-works/pi-coding-agent@1.0.0
|
|
160
194
|
pi --version
|
|
161
195
|
|
|
162
196
|
cd ~/entwurf-smoke
|
|
@@ -164,7 +198,7 @@ entwurf install .
|
|
|
164
198
|
pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
|
|
165
199
|
```
|
|
166
200
|
|
|
167
|
-
The supported range is `>=0.
|
|
201
|
+
The supported range is `>=1.0.0 <1.1`. It is a hard minimum: installing this
|
|
168
202
|
release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
|
|
169
203
|
minor. A host using only the external MCP bridge can skip pi until it needs a
|
|
170
204
|
control socket; no delivery rail launches a pi process.
|
|
@@ -173,10 +207,80 @@ For daily garden-native pi sessions:
|
|
|
173
207
|
|
|
174
208
|
```bash
|
|
175
209
|
cd ~/entwurf-smoke
|
|
176
|
-
|
|
210
|
+
entwurf pi
|
|
211
|
+
# equivalent when the package is already registered:
|
|
212
|
+
pi --entwurf-control
|
|
177
213
|
```
|
|
178
214
|
|
|
215
|
+
A project-local install uses `npx entwurf pi`. `entwurf pi` execs the operator's
|
|
216
|
+
pi with the control flag; it does not install pi or inject model/Emacs preferences.
|
|
179
217
|
The V3 record births the garden id; do not inject a pi session id manually.
|
|
218
|
+
A plain `pi` can load the package without becoming a citizen.
|
|
219
|
+
|
|
220
|
+
In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
|
|
221
|
+
live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
|
|
222
|
+
message. These are harness tools, not shell subcommands; pi exposes them through
|
|
223
|
+
Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The bridge has eight tools;
|
|
224
|
+
pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving six active.
|
|
225
|
+
Tool-call ingress is MCP; delivery to a pi citizen is still its record-addressed
|
|
226
|
+
Unix control socket. A boot/listing from `check-bridge` is not a model-turn or
|
|
227
|
+
native-rail acceptance.
|
|
228
|
+
|
|
229
|
+
To inspect the ACP provider from a project-local package instead of a global one:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
pi -e ./node_modules/@junghanacs/entwurf --list-models entwurf
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The ACP provider, `entwurf-control`, and `model-lock` extensions ship as one set.
|
|
236
|
+
Disable the package as a whole rather than filtering individual entries into a
|
|
237
|
+
partially wired state. A pi-less external-only host can skip this section: no
|
|
238
|
+
delivery rail launches pi, and OMP remains its own binary with its own agent dir.
|
|
239
|
+
|
|
240
|
+
### Starting in Herdr by hand
|
|
241
|
+
|
|
242
|
+
Herdr's ordinary pi start has no control flag. Forward it through Herdr's own argv
|
|
243
|
+
passthrough when starting a citizen manually:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
herdr agent start <name> --kind pi --pane <pane-id> -- --entwurf-control
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
An Entwurf-opened pi sibling already carries the flag. Native Claude Code needs
|
|
250
|
+
no extra argv: its installed `SessionStart` hook births the record. Where the
|
|
251
|
+
citizen is drawn is placement evidence, never its address. The
|
|
252
|
+
[Herdr plugin guide](https://github.com/junghan0611/entwurf/blob/main/plugins/herdr/README.md#quick-start)
|
|
253
|
+
owns plugin installation, activation, first-use status pane and removal; direct
|
|
254
|
+
installation does not prevent context-selected fresh calls inside Herdr.
|
|
255
|
+
|
|
256
|
+
Pi's own `/new`, `/fork`, `/clone` and RPC replacement retain their native meaning:
|
|
257
|
+
`session_start` attaches the replacement to its own record and drops the old
|
|
258
|
+
control socket. Pi's native session id is not a garden address.
|
|
259
|
+
|
|
260
|
+
### Emacs frontends
|
|
261
|
+
|
|
262
|
+
Terminal and Emacs frontends that launch
|
|
263
|
+
[pi-coding-agent](https://github.com/dnouri/pi-coding-agent) use the same control
|
|
264
|
+
flag. For a dedicated agent socket:
|
|
265
|
+
|
|
266
|
+
```elisp
|
|
267
|
+
(setq pi-coding-agent-extra-args
|
|
268
|
+
'("--entwurf-control" "--emacs-agent-socket" "pi"))
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The bridge exports that name to ACP children as `PI_EMACS_AGENT_SOCKET`, so skills
|
|
272
|
+
can use the operator's socket without hardcoding it:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
emacsclient -s "${PI_EMACS_AGENT_SOCKET:-server}" --eval '(...)'
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
<details>
|
|
279
|
+
<summary>Watch entwurf in Doom Emacs (click to expand)</summary>
|
|
280
|
+
|
|
281
|
+

|
|
282
|
+
|
|
283
|
+
</details>
|
|
180
284
|
|
|
181
285
|
## 3. Optional Claude Code native citizen
|
|
182
286
|
|
|
@@ -245,6 +349,40 @@ scanning for that process, checks the receiver, removes inherited pi identity ca
|
|
|
245
349
|
the model/permission defaults. Birth occurs on the first prompt. `entwurf_fresh_call` uses this
|
|
246
350
|
same managed invocation and requires the birth, MCP, receiver, and visible-identity preflight.
|
|
247
351
|
|
|
352
|
+
### Managed launch and permissions
|
|
353
|
+
|
|
354
|
+
Copilot silently skips extension scanning without
|
|
355
|
+
`COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS`. Entwurf owns one invocation, not
|
|
356
|
+
your shell profile:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
entwurf copilot
|
|
360
|
+
entwurf copilot -p "…" --model gpt-5.4
|
|
361
|
+
copilot # the plain vendor CLI, untouched
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The managed launch execs the vendor CLI in this terminal: same cwd/pid/exit status,
|
|
365
|
+
no tmux window and no citizen before the first prompt. It checks the receiver
|
|
366
|
+
before launch, refusing with `entwurf install-copilot-receive` if absent.
|
|
367
|
+
**Running it is consent to this process's profile:**
|
|
368
|
+
|
|
369
|
+
| Injected | When |
|
|
370
|
+
|---|---|
|
|
371
|
+
| `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` | always; existing tokens preserved, in order, deduplicated |
|
|
372
|
+
| `--model auto` | only when no `--model` was supplied |
|
|
373
|
+
| `--yolo` | only when no explicit permission or surface policy flag was supplied |
|
|
374
|
+
|
|
375
|
+
Any of `--yolo`, `--allow-all`, `--allow-all-tools`, `--allow-all-paths`,
|
|
376
|
+
`--allow-all-urls`, `--allow-tool`, `--deny-tool`, `--allow-url`, `--deny-url`,
|
|
377
|
+
`--available-tools` or `--excluded-tools` suppresses injected `--yolo`. Narrowing
|
|
378
|
+
flags count so a default cannot silently widen your restriction.
|
|
379
|
+
`--allow-all-mcp-server-instructions` (prompt content) and `--autopilot` (mode)
|
|
380
|
+
are not policy flags. Arguments are forwarded byte-identically; defaults land
|
|
381
|
+
before `--`, and arguments after it are not read as policy. Nothing is written to
|
|
382
|
+
disk; plain `copilot` gets none of this profile. The unattended managed lane uses
|
|
383
|
+
`--yolo` because an idle session cannot answer the default interactive approvals
|
|
384
|
+
needed to drain `entwurf_inbox_read`.
|
|
385
|
+
|
|
248
386
|
## 4b. Optional OMP (`omp`) native citizen — accepted on one Linux host (thinkpad)
|
|
249
387
|
|
|
250
388
|
This rail is CERTIFIED on Linux (one host, named in the version rule
|
|
@@ -252,7 +390,7 @@ below); on macOS it is NOT CERTIFIED — pending physical host. The Darwin
|
|
|
252
390
|
install fences for `install-omp-bridge` and `install-omp-receive` are
|
|
253
391
|
open (`Linux | Darwin`); a completed install is not a rail receipt.
|
|
254
392
|
|
|
255
|
-
|
|
393
|
+
Four independently owned surfaces, and a boundary that is part of the instructions rather
|
|
256
394
|
than a footnote. Birth, visible identity, who-sent, the MCP hand and RECEIVE are landed, and
|
|
257
395
|
`entwurf_fresh_call` opens an omp sibling on all three public surfaces. The clause 7 LIVE
|
|
258
396
|
receipt has been taken: `smoke-omp-fresh-live` went green on 2026-08-30 (omp 18.0.0, one
|
|
@@ -307,7 +445,7 @@ as a note rather than a fault.
|
|
|
307
445
|
|
|
308
446
|
**entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
|
|
309
447
|
presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
|
|
310
|
-
gate, and no exact pin — unlike Node (`engines.node`), pi (`>=0.
|
|
448
|
+
gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.0.0 <1.1`) and Claude Code
|
|
311
449
|
(`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
|
|
312
450
|
decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
|
|
313
451
|
earned one because an older binary validates the exec manifest, drops `args` at runtime, and
|
|
@@ -402,6 +540,26 @@ why `disabledServers` is never the way to hide an import.
|
|
|
402
540
|
|
|
403
541
|
## 4c. Optional OpenAI Codex CLI native citizen
|
|
404
542
|
|
|
543
|
+
### Three operator decisions before fresh
|
|
544
|
+
|
|
545
|
+
`setup` writes every byte Entwurf owns, but three decisions remain yours:
|
|
546
|
+
|
|
547
|
+
1. Trust the birth hook once in a visible plain Codex: answer `Trust all and continue`
|
|
548
|
+
and send a first turn. `setup`/`doctor-codex-birth` remain non-green until the
|
|
549
|
+
vendor's receipt exists; Entwurf never authors your security decision.
|
|
550
|
+
2. Start `entwurf codex-app-server` in your own detached tmux session. For LIVE
|
|
551
|
+
acceptance it must differ from the Pi/Codex work session. The command execs
|
|
552
|
+
the vendor's default Unix-socket server here; Ctrl-C is yours, with no Entwurf
|
|
553
|
+
supervision or restart.
|
|
554
|
+
3. Answer folder consent once per exact sibling launch directory, for example
|
|
555
|
+
`codex -C <the directory you will launch siblings in>`, then quit. An ordinary
|
|
556
|
+
fresh call notes `codex-launch-cwd-undecided` and opens the window anyway so
|
|
557
|
+
you can answer it; the unattended smoke requires that answer beforehand.
|
|
558
|
+
|
|
559
|
+
Your own `codex --remote` attachment is optional and independent: an Entwurf-opened
|
|
560
|
+
sibling brings its own TUI. That operator attachment has no managed Entwurf spelling.
|
|
561
|
+
The detailed neighbouring consent cases and ownership boundaries follow.
|
|
562
|
+
|
|
405
563
|
Supported in 0.21.0 with Linux-focused evidence; on macOS it is NOT CERTIFIED —
|
|
406
564
|
pending physical host. It is native, not ACP: the purpose is to preserve the operator's
|
|
407
565
|
Codex tools, delegation, and work context rather than provide another GPT access path.
|
|
@@ -589,10 +747,174 @@ LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> \
|
|
|
589
747
|
The aggregate release gate is Claude-backed and does not run Cortex automatically.
|
|
590
748
|
Its silence is not a Cortex PASS.
|
|
591
749
|
|
|
750
|
+
### Backend prerequisites and resolver
|
|
751
|
+
|
|
752
|
+
Claude is the reference ACP backend. `@agentclientprotocol/claude-agent-acp` and
|
|
753
|
+
its SDK are pinned dependencies of Entwurf; authentication belongs to the local
|
|
754
|
+
operator-installed Claude runtime. Resolution order:
|
|
755
|
+
|
|
756
|
+
1. `CLAUDE_AGENT_ACP_COMMAND` — explicit alternative binary/wrapper override.
|
|
757
|
+
2. `require.resolve(...)` against the bundled `@agentclientprotocol/claude-agent-acp`.
|
|
758
|
+
3. `PATH:claude-agent-acp` fallback if package resolution fails.
|
|
759
|
+
|
|
760
|
+
The curated Claude ids are `claude-opus-5`, `claude-sonnet-5`, and
|
|
761
|
+
`claude-fable-5-1`. Cortex exposes `cortex-auto`, `cortex-claude-opus-5`,
|
|
762
|
+
`cortex-claude-sonnet-5`, and `cortex-openai-gpt-5.4`.
|
|
763
|
+
`entwurfProvider.cortexConnection` or `ENTWURF_ACP_CORTEX_CONNECTION` selects
|
|
764
|
+
an authenticated connection; the env override wins. Codex and Antigravity are
|
|
765
|
+
native-push citizens (§4c/§5), not ACP backends.
|
|
766
|
+
|
|
767
|
+
### Settings reference and backend-specific keys
|
|
768
|
+
|
|
769
|
+
The reference shape is [pi/settings.reference.json](../pi/settings.reference.json).
|
|
770
|
+
A minimum declaration:
|
|
771
|
+
|
|
772
|
+
```json
|
|
773
|
+
{
|
|
774
|
+
"compaction": { "enabled": false },
|
|
775
|
+
"entwurfProvider": {
|
|
776
|
+
"appendSystemPrompt": false,
|
|
777
|
+
"settingSources": [],
|
|
778
|
+
"strictMcpConfig": true,
|
|
779
|
+
"showToolNotifications": true,
|
|
780
|
+
"tools": ["Read", "Bash", "Edit", "Write"],
|
|
781
|
+
"skillPlugins": [],
|
|
782
|
+
"permissionAllow": ["Read(*)", "Bash(*)", "Edit(*)", "Write(*)", "mcp__*"],
|
|
783
|
+
"mcpServers": {
|
|
784
|
+
"entwurf-bridge": {
|
|
785
|
+
"command": "/path/to/entwurf/mcp/entwurf-bridge/start.sh",
|
|
786
|
+
"args": []
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
`mcpServers` is the only ACP MCP injection path; invalid entries throw
|
|
794
|
+
`McpServerConfigError`. `install` writes the bundled bridge entry and prunes the
|
|
795
|
+
retired bundled `session-bridge` entry. It is not a general MCP catalog.
|
|
796
|
+
`appendSystemPrompt: false` is intentional: pi / AGENTS context rides the first
|
|
797
|
+
user augment. Large Claude `_meta.systemPrompt` carriers can route OAuth sessions
|
|
798
|
+
to metered “extra usage” billing.
|
|
799
|
+
|
|
800
|
+
`entwurfProvider` is one block, not a promise that every key shapes both backends.
|
|
801
|
+
`tools`, `permissionAllow`, `disallowedTools`, `settingSources`, `skillPlugins`
|
|
802
|
+
and `appendSystemPrompt` are Claude's declaration surface, not Cortex's native
|
|
803
|
+
tool surface. They still enter the config signature (and `tools` the common
|
|
804
|
+
exclude-tools preflight), so editing them forces a fresh Cortex session.
|
|
805
|
+
`cortexConnection` is Cortex-only; `mcpServers` reaches both through their own
|
|
806
|
+
transports. `compaction` is a **top-level pi key**, not a backend knob. Unknown
|
|
807
|
+
and retired keys are ignored; a wrong-backend key can fail silently, so check
|
|
808
|
+
which backend consumes it before diagnosing bridge failure.
|
|
809
|
+
|
|
810
|
+
### Backend operation and containment
|
|
811
|
+
|
|
812
|
+
Claude honors operator `CLAUDE_CONFIG_DIR`. The ACP overlay whitelists required
|
|
813
|
+
auth/runtime state while hiding operator memory, hooks, agents, history, local
|
|
814
|
+
settings and project memory. It writes explicit empty `hooks: {}` for the SDK's
|
|
815
|
+
organic compaction shape and `permissions.defaultMode: "bypassPermissions"`
|
|
816
|
+
so unattended turns do not suspend on permission prompts. Explicit `tools` /
|
|
817
|
+
`disallowedTools` still constrain callable tools. The four-tool baseline is
|
|
818
|
+
Read/Bash/Edit/Write; `Skill` is added for non-empty `skillPlugins`.
|
|
819
|
+
`ENTWURF_ACP_CLAUDE_CONTEXT=<int>` overrides the operator context cap.
|
|
820
|
+
The launch env sets `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`: vendor-managed
|
|
821
|
+
background work is off, not arbitrary processes a command detaches itself.
|
|
822
|
+
|
|
823
|
+
Cortex uses its native tools, session-private HOME / `SNOWFLAKE_HOME`, and private
|
|
824
|
+
`cortex/mcp.json` projection because it ignores wire `mcpServers`. Only the bridge
|
|
825
|
+
receives the real operator HOME needed for the garden store; `CORTEX_HOME` is
|
|
826
|
+
refused, including an empty value, because it bypasses containment.
|
|
827
|
+
[ACP backend rail — Shipped adapters](./acp-backend-rail.md#shipped-adapters)
|
|
828
|
+
owns the exact overlay/carrier/backend mechanics and audit limits.
|
|
829
|
+
|
|
830
|
+
Entwurf owns no memory layer or memory DB, and no backend is another's memory
|
|
831
|
+
authority. Operator semantic-memory / Denote tools remain host-native skills,
|
|
832
|
+
not a bridge or pi privilege.
|
|
833
|
+
|
|
834
|
+
### Custom skills
|
|
835
|
+
|
|
836
|
+
Claude ACP accepts `skillPlugins`: absolute directories following the Claude
|
|
837
|
+
Agent SDK plugin layout, outside pi's internal `~/.pi/agent/` cache:
|
|
838
|
+
|
|
839
|
+
```text
|
|
840
|
+
<your-plugin-root>/
|
|
841
|
+
├── .claude-plugin/
|
|
842
|
+
│ └── plugin.json
|
|
843
|
+
└── skills/
|
|
844
|
+
└── <skill-name>/
|
|
845
|
+
└── SKILL.md
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
Example: [pi/skill-plugin-example](../pi/skill-plugin-example/).
|
|
849
|
+
|
|
850
|
+
```json
|
|
851
|
+
{
|
|
852
|
+
"entwurfProvider": {
|
|
853
|
+
"skillPlugins": ["/absolute/path/to/your-plugin-root"]
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
Non-empty `skillPlugins` auto-adds `Skill` to `tools` and `Skill(*)` to
|
|
859
|
+
`permissionAllow`. Settings parsing refuses a missing, relative, non-directory
|
|
860
|
+
path or missing `.claude-plugin/plugin.json` before starting the session; the
|
|
861
|
+
SDK, not the bridge, validates plugin.json contents and SKILL.md bodies.
|
|
862
|
+
Start a fresh Claude session and ask it to list callable skills. The operator
|
|
863
|
+
judgement is `Q-SKILL-CALLABLE` in [VERIFY](../VERIFY.md).
|
|
864
|
+
This is Claude-only; Codex uses native `~/.codex/skills/` passthrough.
|
|
865
|
+
[agent-config](https://github.com/junghan0611/agent-config) is a real multi-harness
|
|
866
|
+
consumer, not a requirement for this setup.
|
|
867
|
+
|
|
868
|
+
### Context carriers
|
|
869
|
+
|
|
870
|
+
An optional short engraving and rich pi context are separate. Runtime default:
|
|
871
|
+
`pi-extensions/lib/acp/prompts/engraving.md` (`# Engraving Here`, pinned non-empty
|
|
872
|
+
by a gate). [prompts/engraving.md](../prompts/engraving.md) is a sample:
|
|
873
|
+
copy it and set `ENTWURF_ACP_ENGRAVING_PATH=/path/to/alt.md`.
|
|
874
|
+
Template variables are `{{backend}}` and `{{mcp_servers}}`; empty/missing operator
|
|
875
|
+
text is fine. Do not put AGENTS, tool catalogs or bridge narrative in the carrier:
|
|
876
|
+
large Claude system carriers can change billing semantics.
|
|
877
|
+
|
|
878
|
+
The loader trims leading/trailing whitespace and then opens the rendered carrier
|
|
879
|
+
with one blank line. Claude's SDK prefixes its identity sentence with no separator;
|
|
880
|
+
the boundary prevents the engraving from becoming its tail. The measured
|
|
881
|
+
2026-07-31 unseparated string was
|
|
882
|
+
`You are a Claude agent, built on Anthropic's Claude Agent SDK.# Engraving Here`.
|
|
883
|
+
Template whitespace cannot supply the boundary because it is trimmed away.
|
|
884
|
+
|
|
885
|
+
Bridge identity, pi context, `~/AGENTS.md`, cwd AGENTS and date/cwd ride a one-shot
|
|
886
|
+
first-user prepend (`pi-context-augment.ts`), not the system carrier. It removes
|
|
887
|
+
duplicate cwd AGENTS already in `<project-context ...>`. Its descriptions do not
|
|
888
|
+
grant tools: the actual callable schema is truth (`read` / `Read` / `exec_command`,
|
|
889
|
+
MCP only when schema-visible). Cortex engraving rides this augment because it
|
|
890
|
+
has no system-prompt carrier; Claude uses `_meta.systemPrompt`.
|
|
891
|
+
|
|
892
|
+
### Session reuse and compaction
|
|
893
|
+
|
|
894
|
+
Current process-scoped turns reuse a live ACP session; persisted records are
|
|
895
|
+
written/validated for a future resume/load path, not consumed as today's live
|
|
896
|
+
continuation mechanism. The `pi:<sessionId>` mapping lives under
|
|
897
|
+
`~/.pi/agent/cache/entwurf/sessions/`; it never hydrates backend transcripts into
|
|
898
|
+
pi history. Backend stores such as `~/.claude/` and `~/.codex/` are interoperability
|
|
899
|
+
side effects, never Entwurf identity authority.
|
|
900
|
+
[VERIFY — bridge continuity vs semantic continuity](../VERIFY.md#bridge-continuity-vs-semantic-continuity)
|
|
901
|
+
owns the reuse policy and distinguishes live mapping from semantic recall.
|
|
902
|
+
|
|
903
|
+
**Entwurf does not implement compaction.** A backend's native compaction preserves
|
|
904
|
+
the pi session/mapping; configure its policy through that backend's own interface,
|
|
905
|
+
not a pi-side JSONL summary. The bridge exposes no backend-specific compaction
|
|
906
|
+
knobs. The ACP footer uses `usage_update.used / size` (backend prompt/tools/cache/
|
|
907
|
+
session included) with `[entwurf:usage]` diagnostics. Near limit choose a visible
|
|
908
|
+
action: clear, open a new session with a different model, or let the backend
|
|
909
|
+
compact natively.
|
|
910
|
+
|
|
592
911
|
## 7. Upgrade and repair
|
|
593
912
|
|
|
594
|
-
After
|
|
595
|
-
|
|
913
|
+
After an npm upgrade, rerun `entwurf setup <project>` in the same installation
|
|
914
|
+
scope (`npx entwurf setup .` for project-local), then `check-bridge` in that scope.
|
|
915
|
+
Use `@junghanacs/entwurf@latest` when explicitly choosing the registry stable line.
|
|
916
|
+
Setup re-composes every detected harness; use a single `install-*` only to repair
|
|
917
|
+
one unit, and restart existing native processes. Native plugin caches are not live-reload
|
|
596
918
|
safe across launch-contract changes.
|
|
597
919
|
|
|
598
920
|
On Darwin the four previously closed install fences
|
|
@@ -625,6 +947,20 @@ source-checkout-only), and composes the detected harnesses with the same per-com
|
|
|
625
947
|
PASS/SKIP/FAIL summary. The complete quiescence, archive, and exit-code contract is
|
|
626
948
|
[fresh-cut-policy.md](./fresh-cut-policy.md).
|
|
627
949
|
|
|
950
|
+
### Source checkout and pi-only repair
|
|
951
|
+
|
|
952
|
+
`entwurf install <project>` is the narrower pi wiring leaf: it writes the target
|
|
953
|
+
`.pi/settings.json` with the installed bridge's absolute launcher path and composes
|
|
954
|
+
no native harness. The retired `~/.pi/agent/` target-registry link is not read.
|
|
955
|
+
User-scope ownership/refusal/takeover remains §1.1, not an automatic repair.
|
|
956
|
+
|
|
957
|
+
The development bridge uses Node strip-types directly; the npm artifact runs
|
|
958
|
+
prebuilt `mcp/entwurf-bridge/dist/` JS because Node refuses to strip TypeScript
|
|
959
|
+
under `node_modules`. Source `./run.sh check-bridge` needs no build. After a pull,
|
|
960
|
+
run `pnpm install` when the lockfile changed and `pnpm run build-bridge` before
|
|
961
|
+
artifact-consuming checks such as `./run.sh check-bridge-delivery`; ignored dist
|
|
962
|
+
may be missing or stale. Installed mode never bootstraps pnpm in `node_modules`.
|
|
963
|
+
|
|
628
964
|
## 8. Release acceptance versus host acceptance
|
|
629
965
|
|
|
630
966
|
- `entwurf check-bridge`: installed MCP bytes boot; no backend auth.
|
|
@@ -81,7 +81,7 @@ import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/ent
|
|
|
81
81
|
import { makeVisibleResumeDeps, renderVisibleResume, visibleResume, } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.js";
|
|
82
82
|
import { dispatchFreshCall, renderDispatchedFreshCall } from "../../../pi-extensions/lib/fresh-call-dispatch.js";
|
|
83
83
|
import { probeNativeSenderAlive, reconcileSenderIdentityClaims, resolveCodexRequestSenderIdentity, resolveTrustedMetaSenderIdentity, } from "../../../pi-extensions/lib/meta-sender-identity.js";
|
|
84
|
-
import { applyOmpBridgeChildRootPolicy, defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaInbox, readMetaReceiverMarker, readMetaSenderMarker, requireBackend, } from "../../../pi-extensions/lib/meta-session.js";
|
|
84
|
+
import { applyOmpBridgeChildRootPolicy, defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaIdentityByGardenId, readMetaInbox, readMetaReceiverMarker, readMetaSenderMarker, requireBackend, } from "../../../pi-extensions/lib/meta-session.js";
|
|
85
85
|
import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
|
|
86
86
|
import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
|
|
87
87
|
const HOME = os.homedir();
|
|
@@ -281,9 +281,32 @@ async function resolveAuthoritativeSender(context, cwd = process.cwd()) {
|
|
|
281
281
|
return buildMetaSenderEnvelope(marker.identity, marker.marker.cwd || cwd, marker);
|
|
282
282
|
}
|
|
283
283
|
if (pi && pi.sessionId === selected.id)
|
|
284
|
-
return { envelope: pi };
|
|
284
|
+
return { envelope: requirePiSenderRecord(pi) };
|
|
285
285
|
throw new Error(`entwurf-bridge: reconciled sender ${selected.id} has no matching identity source`);
|
|
286
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* PI_SESSION_ID CARRIES a record-established identity; it does not establish one (Hard Rule 2).
|
|
289
|
+
* Since #125 the pi session reaches every Entwurf verb through this bridge, so the pi rail is the
|
|
290
|
+
* main road rather than a side door, and the carrier is checked where the sender is SELECTED:
|
|
291
|
+
* after the claims reconcile (so a complete conflicting Codex claim still earns its own
|
|
292
|
+
* diagnostic) and before any caller builds on the answer.
|
|
293
|
+
*
|
|
294
|
+
* The per-entry targeted read the sender/self policy already uses — no store scan, no second
|
|
295
|
+
* nativeSessionId carrier. An absent, unreadable or irregular record throws from the reader with
|
|
296
|
+
* its own cause; a record of another backend throws here. None of it falls back to another rail
|
|
297
|
+
* or to anonymous. Socket presence stays what it was: replyability, never admission.
|
|
298
|
+
*
|
|
299
|
+
* What it does NOT prove: that a trusted host which deliberately exports ANOTHER real pi
|
|
300
|
+
* citizen's id is that citizen. Carrier integrity below the record is the host's.
|
|
301
|
+
*/
|
|
302
|
+
function requirePiSenderRecord(envelope) {
|
|
303
|
+
const identity = readMetaIdentityByGardenId(envelope.sessionId);
|
|
304
|
+
if (identity.backend !== "pi") {
|
|
305
|
+
throw new Error(`entwurf-bridge: PI_SESSION_ID ${envelope.sessionId} names a ${identity.backend} record, not a pi citizen — ` +
|
|
306
|
+
"the pi-session carrier must name the record this pi session was born with");
|
|
307
|
+
}
|
|
308
|
+
return envelope;
|
|
309
|
+
}
|
|
287
310
|
// Async only when the selected sender rides native-push: replyability is the adapter's live fact.
|
|
288
311
|
async function buildAuthoritativeSelfEnvelope(context = {}) {
|
|
289
312
|
const resolved = await resolveAuthoritativeSender(context);
|
|
@@ -343,7 +366,7 @@ function abbreviateHomeMcp(cwd) {
|
|
|
343
366
|
// reports one outcome. The per-target lock is NOT taken by every rail: the decider
|
|
344
367
|
// locks only a control-socket-domain dispatch; the mailbox and native-push branches
|
|
345
368
|
// carry `lock: null` (entwurf-v2-decider.ts). It runs
|
|
346
|
-
// IN-PROCESS here (the
|
|
369
|
+
// IN-PROCESS here (the one production runner; pi reaches it through this bridge since #125) — NOT a delegating
|
|
347
370
|
// RPC — so control, mailbox and native-push all flow through
|
|
348
371
|
// `runEntwurfV2`. The sender envelope is
|
|
349
372
|
// `buildSendSenderEnvelope()` verbatim (origin/replyable as resolved) — v2 does
|
|
@@ -402,7 +425,7 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
|
|
|
402
425
|
const rendered = await runAndRenderEntwurfV2FromSurface({ target, intent, message, mode, wants_reply },
|
|
403
426
|
// No trust-preflight inputs are passed, and none exist to pass: the preflight on this
|
|
404
427
|
// path guarded the resume verdict and left with `owned-outcome`. `senderProvider` is
|
|
405
|
-
// the whole options surface now
|
|
428
|
+
// the whole options surface now.
|
|
406
429
|
{ senderProvider: () => sender });
|
|
407
430
|
return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
|
|
408
431
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { createRequire } from "node:module";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { RequestError } from "@agentclientprotocol/sdk";
|
|
23
24
|
import { enrichMcpServersWithEnvelope } from "./config.js";
|
|
24
25
|
import { loadEngraving } from "./engraving.js";
|
|
25
26
|
import { CORTEX_MODEL_PREFIX, curatedClaudeModels, curatedCortexModels, SUPPORTED_ANTHROPIC_MODEL_IDS, SUPPORTED_CORTEX_MODEL_IDS, } from "./models.js";
|
|
@@ -34,10 +35,46 @@ import { buildClaudeSessionMeta } from "./tool-surface.js";
|
|
|
34
35
|
function shellQuote(value) {
|
|
35
36
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
36
37
|
}
|
|
38
|
+
/** The pi diagnostic type under which backend.ts preserves a recognised rejection verbatim. */
|
|
39
|
+
export const ACP_PROMPT_REJECTION_DIAGNOSTIC = "entwurf_acp_prompt_rejection";
|
|
40
|
+
/** The verdict for claude-agent-acp's `incomplete_tool_call` (#127; upstream #1212, 0.85.0+). */
|
|
41
|
+
export const INCOMPLETE_TOOL_CALL_VERDICT = "ACP turn failed because foreground tool results are incomplete. Some tools may already have run; automatic replay is unsafe. Inspect the preserved vendor rejection before continuing.";
|
|
37
42
|
// ---------------------------------------------------------------------------
|
|
38
43
|
// claude adapter — the first implementation (the rail's reference backend)
|
|
39
44
|
// ---------------------------------------------------------------------------
|
|
40
45
|
const SUPPORTED_CLAUDE_IDS = new Set(SUPPORTED_ANTHROPIC_MODEL_IDS);
|
|
46
|
+
/** JSON-RPC internal error — the code claude-agent-acp rejects an unfinished-tool turn with. */
|
|
47
|
+
const JSONRPC_INTERNAL_ERROR = -32603;
|
|
48
|
+
/** claude-agent-acp's own kind for a turn that hit `end_turn` with foreground tools unanswered
|
|
49
|
+
* (read at v0.85.1 `src/acp-agent.ts:4286-4340`, `errorKindData("incomplete_tool_call")`). */
|
|
50
|
+
const CLAUDE_INCOMPLETE_TOOL_CALL = "incomplete_tool_call";
|
|
51
|
+
/**
|
|
52
|
+
* The claude reader for `readPromptRejection` (#127). Only the root wire SDK's own decode
|
|
53
|
+
* counts: its client rejects a JSON-RPC error response with `new RequestError(code, message,
|
|
54
|
+
* data)`, `data` being the parsed `error.data` (sdk 1.4.0 `dist/jsonrpc.js:842-844`). A
|
|
55
|
+
* look-alike object, another code, or another `errorKind` is not this failure and keeps the
|
|
56
|
+
* verbatim-first path.
|
|
57
|
+
*/
|
|
58
|
+
function readClaudePromptRejection(err) {
|
|
59
|
+
if (!(err instanceof RequestError) || err.code !== JSONRPC_INTERNAL_ERROR)
|
|
60
|
+
return undefined;
|
|
61
|
+
const data = err.data;
|
|
62
|
+
if (typeof data !== "object" || data === null || !("errorKind" in data))
|
|
63
|
+
return undefined;
|
|
64
|
+
if (data.errorKind !== CLAUDE_INCOMPLETE_TOOL_CALL)
|
|
65
|
+
return undefined;
|
|
66
|
+
return { errorKind: CLAUDE_INCOMPLETE_TOOL_CALL, verdict: INCOMPLETE_TOOL_CALL_VERDICT, dataJson: jsonText(data) };
|
|
67
|
+
}
|
|
68
|
+
/** Evidence text for a wire value. A value that came off the wire serializes; one that cannot
|
|
69
|
+
* (a hand-built fake) is named as such, so recording evidence never breaks the turn's seal. */
|
|
70
|
+
function jsonText(value) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.stringify(value) ?? String(value);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
return `[unserializable: ${err instanceof Error ? err.name : typeof err}]`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
41
78
|
/**
|
|
42
79
|
* Resolve the claude launch — an ENTWURF-OWNED launcher, or the env override for debug.
|
|
43
80
|
* This is the single source for the claude launch spec; backend.ts holds no private copy.
|
|
@@ -62,6 +99,17 @@ function resolveClaudeLaunch() {
|
|
|
62
99
|
const launcher = fileURLToPath(new URL("./claude-acp-launch.js", import.meta.url));
|
|
63
100
|
return { command: process.execPath, args: [launcher] };
|
|
64
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* The claude child runs with Claude Code's own background-task switch on (#125): the vendor drops
|
|
104
|
+
* `run_in_background` from the Bash/Agent schemas it declares to the model and turns off timeout and
|
|
105
|
+
* turn-abort backgrounding. Without it a vendor-backgrounded command runs in its own session (setsid),
|
|
106
|
+
* outlives an ACP cancel, and is orphaned to init when the child exits — the process-group teardown
|
|
107
|
+
* cannot reach it (`[측정 2026-10-02]` claude-agent-sdk 0.3.274 / claude-agent-acp 0.79.0). A process a
|
|
108
|
+
* command detaches on its own is outside the switch. It rides
|
|
109
|
+
* the claude adapter's launch env, which backend.ts spreads over `process.env` at spawn, so an
|
|
110
|
+
* ambient value cannot turn it off. Cortex declares nothing of the kind.
|
|
111
|
+
*/
|
|
112
|
+
export const CLAUDE_FOREGROUND_ONLY_ENV = { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" };
|
|
65
113
|
export const claudeAdapter = {
|
|
66
114
|
backend: "claude",
|
|
67
115
|
// Claude owns its UNPREFIXED curated ids only (rail “Adapter contract”). The native id
|
|
@@ -82,7 +130,7 @@ export const claudeAdapter = {
|
|
|
82
130
|
return resolveClaudeLaunch();
|
|
83
131
|
},
|
|
84
132
|
launchEnvDefaults() {
|
|
85
|
-
return claudeLaunchEnvDefaults();
|
|
133
|
+
return { ...claudeLaunchEnvDefaults(), ...CLAUDE_FOREGROUND_ONLY_ENV };
|
|
86
134
|
},
|
|
87
135
|
ensureOverlay() {
|
|
88
136
|
// Claude's overlay is constant (no settings dependence), so it ignores params.
|
|
@@ -128,6 +176,9 @@ export const claudeAdapter = {
|
|
|
128
176
|
// The token partition is NOT declared here: ACP's only token carrier is a
|
|
129
177
|
// per-turn round-trip aggregate, which is not what pi's four fields mean.
|
|
130
178
|
sealsTurnAccounting: true,
|
|
179
|
+
// #127: claude-agent-acp 0.85.0+ rejects an `end_turn` with unanswered foreground tools as
|
|
180
|
+
// -32603 `incomplete_tool_call` (upstream #1212). Its raw text reads as transient to pi.
|
|
181
|
+
readPromptRejection: readClaudePromptRejection,
|
|
131
182
|
};
|
|
132
183
|
// ---------------------------------------------------------------------------
|
|
133
184
|
// Registry — modelId → adapter
|
|
@@ -274,6 +325,9 @@ export const cortexAdapter = {
|
|
|
274
325
|
// backend.ts seals nothing for a backend without this flag, so cortex's
|
|
275
326
|
// emitted usage is byte-identical to what it was before #93. Declaring this
|
|
276
327
|
// with a guess would mint exactly the silent misaccounting #93 exists to end.
|
|
328
|
+
// NO readPromptRejection — also deliberate. Nobody has read which failures cortex's ACP
|
|
329
|
+
// server rejects a prompt with, so every cortex failure keeps the verbatim-first
|
|
330
|
+
// errorMessage. Borrowing claude's `incomplete_tool_call` reader would be a guess.
|
|
277
331
|
};
|
|
278
332
|
const ADAPTERS = [claudeAdapter, cortexAdapter];
|
|
279
333
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* surface (0.11 Stage 0 step 4, slice 4c). The MCP handler stays thin: it does IO
|
|
4
4
|
* (readdir the meta-store + probe sockets via `listEntwurfFacts`) and then calls
|
|
5
5
|
* THIS to shape the text + JSON. No IO here, so the gate drives it without a
|
|
6
|
-
* filesystem and the SAME facts can feed
|
|
6
|
+
* filesystem and the SAME facts can feed the bridge / doctor / v2 dispatch
|
|
7
7
|
* (a handler that did its own brain work would deny them that reuse).
|
|
8
8
|
*
|
|
9
9
|
* Two hard rules carried from the frozen contract (동결결정 10 + #50 C4):
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* The frozen 7-step order (NEXT.md "통합 decider 순서"):
|
|
24
24
|
* 1. requireGardenId — runtime guard BEFORE any path is built (F2-P1; closes the
|
|
25
|
-
* MCP-schema bypass for
|
|
25
|
+
* MCP-schema bypass for internal callers).
|
|
26
26
|
* 2. resolveTarget — no citizen → bad-target; a record-less control socket →
|
|
27
27
|
* record-less-socket (#50 C4: the record is the sole address authority);
|
|
28
28
|
* quarantined (out-of-socket-domain record sharing a socket/symlink) → target-address-conflict.
|
|
@@ -54,7 +54,7 @@ export { isOutOfSocketDomainGardenIdConflict };
|
|
|
54
54
|
export const ENTWURF_V2_MODE_DEFAULT = "follow_up";
|
|
55
55
|
/**
|
|
56
56
|
* F2-P1 defense in depth: never build a lock/socket path from an unvalidated gid.
|
|
57
|
-
* The MCP
|
|
57
|
+
* The MCP schema pattern guards that one surface; an internal caller
|
|
58
58
|
* bypasses the schema, so the decider re-validates as its very first step.
|
|
59
59
|
*/
|
|
60
60
|
function requireGardenId(target) {
|
|
@@ -58,8 +58,8 @@ export function executeMetaMailboxSend(plan, sender, deps) {
|
|
|
58
58
|
* Build the production `sendViaMailbox(plan, lock)` adapter the 5c-2a send hand consumes.
|
|
59
59
|
* It IGNORES `lock` entirely (release is the hand's job; a mailbox plan is lock-free) and
|
|
60
60
|
* wraps the sync body in the async dep signature. `senderProvider` is supplied by the
|
|
61
|
-
* wiring site —
|
|
62
|
-
*
|
|
61
|
+
* wiring site — the bridge's authoritative sender resolution (the one caller-side
|
|
62
|
+
* surface since #125) — so this module never imports ExtensionContext. `enqueue` defaults to
|
|
63
63
|
* the real `enqueueMetaMessage`; the gate injects a fake.
|
|
64
64
|
*/
|
|
65
65
|
export function makeProductionSendViaMailbox(opts) {
|