@junghanacs/entwurf 0.12.10 → 0.13.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 +2 -1
- package/BASELINE.md +45 -6
- package/CHANGELOG.md +16 -0
- package/CONTRIBUTING.md +4 -2
- package/DELIVERY.md +1 -1
- package/README.md +20 -5
- package/VERIFY.md +7 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +148 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
- package/package.json +4 -2
- package/pi/settings.reference.json +1 -1
- package/pi-extensions/acp-provider.ts +20 -10
- package/pi-extensions/lib/acp/augment.ts +60 -2
- package/pi-extensions/lib/acp/backend-adapter.ts +183 -8
- package/pi-extensions/lib/acp/backend.ts +5 -1
- package/pi-extensions/lib/acp/config.ts +19 -5
- package/pi-extensions/lib/acp/engraving.ts +3 -1
- package/pi-extensions/lib/acp/event-mapper.ts +10 -3
- package/pi-extensions/lib/acp/models.ts +69 -7
- package/pi-extensions/lib/acp/overlay.ts +234 -5
- package/pi-extensions/lib/acp/tool-surface.ts +12 -4
- package/run.sh +152 -21
- package/scripts/check-acp-cortex.ts +668 -0
- package/scripts/check-acp-provider-surface.ts +50 -6
- package/scripts/check-acp-session-reuse.ts +64 -1
- package/scripts/check-gate-qualification.ts +2 -0
- package/scripts/check-probe-cli-shim.ts +879 -0
- package/scripts/check-probe-ordering.ts +2450 -0
- package/scripts/check-shell-quote.ts +4 -4
- package/scripts/fixtures/probe-cli-shim +20 -0
- package/scripts/fixtures/probe-mcp-server.ts +168 -12
- package/scripts/lib/probe-acp-turn.ts +207 -0
- package/scripts/lib/probe-cli-shim.ts +464 -0
- package/scripts/lib/probe-cli-target.ts +165 -0
- package/scripts/lib/probe-event-log.ts +383 -0
- package/scripts/lib/probe-verdict.ts +1213 -0
- package/scripts/mutants/acp-cortex.json +196 -0
- package/scripts/mutants/probe-ordering.json +1032 -0
- package/scripts/smoke-acp-cortex-live.ts +392 -0
- package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
package/AGENTS.md
CHANGED
|
@@ -64,7 +64,7 @@ Detailed incident histories belong in CHANGELOG/issues/BASELINE and source-adjac
|
|
|
64
64
|
- The common turn sequence stays backend-invariant: spawn → initialize → newSession → enforceModel → prompt → event map.
|
|
65
65
|
- Rich operator/project context rides the **first user message augment**, not a large system prompt. The actual callable schema is the tool truth; prose never grants a tool.
|
|
66
66
|
- A backend may have no carrier or use launch-time model pinning; those asymmetries stay inside its adapter.
|
|
67
|
-
- Claude is the reference adapter. Cortex
|
|
67
|
+
- Claude is the reference adapter. Cortex is the second landed adapter (0.13.0): session-scoped dual-HOME containment, overlay-private `mcp.json` projection (its ACP server ignores the wire `mcpServers` param), `CORTEX_HOME` presence refusal, per-turn set-model. Audit record and contract: `docs/acp-backend-rail.md` §11-8.
|
|
68
68
|
- entwurf never supplies, copies, proxies, decrypts, or bypasses vendor credentials/subscriptions. It uses the operator's existing local authenticated backend.
|
|
69
69
|
|
|
70
70
|
## Citizen Identity and Dispatch
|
|
@@ -114,6 +114,7 @@ LIVE=1 ./run.sh release-gate /path/to/scratch
|
|
|
114
114
|
LIVE=1 ./run.sh smoke-acp-socket-citizen-live
|
|
115
115
|
LIVE=1 ./run.sh smoke-acp-bundled-mcp-live
|
|
116
116
|
LIVE=1 ./run.sh smoke-acp-v2-send-live
|
|
117
|
+
LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live # on-demand; outside the claude release floor
|
|
117
118
|
LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live
|
|
118
119
|
```
|
|
119
120
|
|
package/BASELINE.md
CHANGED
|
@@ -6,12 +6,16 @@ silently drifted into a different identity / context surface. Questions
|
|
|
6
6
|
are deliberately open-ended — they probe what the agent actually sees,
|
|
7
7
|
not what it was told to claim.
|
|
8
8
|
|
|
9
|
-
The 0.12
|
|
10
|
-
Claude ACP baseline.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
The released 0.12 ACP backend is **Claude**, and the main question bank below is the
|
|
10
|
+
Claude ACP baseline. The prepared **0.13.0** cut adds **Snowflake Cortex Code** as a
|
|
11
|
+
second ACP backend; it has no question bank of its own yet (deferred to 0.13.1), and the
|
|
12
|
+
Claude bank must not be run against it verbatim — cortex is
|
|
13
|
+
system-prompt-carrier-less and runs its own native tool surface, so the overlay/engraving
|
|
14
|
+
questions below would grade it against a surface it does not have. Antigravity (`agy`)
|
|
15
|
+
is also shipped, but as a native-push garden citizen rather than an ACP backend, so it
|
|
16
|
+
has a separate citizen/round-trip baseline below instead of being forced into Claude's
|
|
17
|
+
overlay questions. Codex (pi-native / delivery probe) and Gemini (historical non-goal
|
|
18
|
+
ACP probe) remain reference axes, not a shipped ACP baseline.
|
|
15
19
|
|
|
16
20
|
## Release-host baseline — #51 repair cut
|
|
17
21
|
|
|
@@ -274,6 +278,41 @@ prompt, and if so quote the visible text exactly:
|
|
|
274
278
|
|
|
275
279
|
# HISTORY (pointer)
|
|
276
280
|
|
|
281
|
+
2026-07-30 Cortex 0.13.0 acceptance + landing checkpoint (Linux/thinkpad, Cortex Code
|
|
282
|
+
v1.1.52). Pre-version landing HEAD `9f1c7dc9e1fa77103e29a6f1884af7759e1595eb` passed
|
|
283
|
+
exact-SHA GitHub Actions run [30505001694](https://github.com/junghan0611/entwurf/actions/runs/30505001694):
|
|
284
|
+
`check`, `install-surface`, and `artifact-consumer` all success. The non-interference reading rests on what the
|
|
285
|
+
sources prove, not on an inspection of the runner image: the external Cortex CLI is not an npm
|
|
286
|
+
dependency, nothing in install or provider registration looks for the executable,
|
|
287
|
+
`.github/workflows/ci.yml` provisions only checkout, pnpm, and Node 24 (no Cortex install step,
|
|
288
|
+
no Snowflake auth), and the required Linux `artifact-consumer` job ran the candidate inside a
|
|
289
|
+
clean `node:24-bookworm` container and passed. Cells at that tree: `ENTWURF_REQUIRE_DOCKER=1 ./run.sh
|
|
290
|
+
check-install-container` EXIT=0 (40 ok) — candidate `junghanacs-entwurf-0.12.10.tgz`
|
|
291
|
+
**sha256=63342aa8a144011dee86ebea8f0b778c7860e54dc0f1c710438983c679e8af87**, image
|
|
292
|
+
`node:24-bookworm` `id=sha256:fcd0f74fb415c752…`,
|
|
293
|
+
`repoDigest=node@sha256:5711a0d445a1af54…`; `./run.sh check-pack-install` EXIT=0 with the
|
|
294
|
+
installed package's own model list enumerating the **exact six curated rows (claude 2 +
|
|
295
|
+
cortex 4)**; `LIVE=1 ./run.sh smoke-acp-cortex-live` **PASS 23/23**, deliberately rerun
|
|
296
|
+
after the `resolveSessionKey` overlay-scope repair so the claim does not rest on the earlier
|
|
297
|
+
CP2 code — overlay scope dir + `autoUpdate:false` + overlay-private `mcp.json` projection +
|
|
298
|
+
real-operator-HOME restore on the bridge entry, the model's own `entwurf_v2` landing exactly
|
|
299
|
+
one `.msg` on a seeded peer with the gid only in the envelope, and process-group teardown
|
|
300
|
+
leaving nothing alive in that run's overlay. `pnpm check` on the prepared tree includes
|
|
301
|
+
`check-gate-qualification` **111/111 killed** (lane `acp-cortex` twelve), and
|
|
302
|
+
`LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.13.0.drnRyR` was all green —
|
|
303
|
+
**MUST PASS=17 FAIL=0 SKIP=0**, **BEHAVIOR PASS=1 FAIL=0**, EXIT=0. Operator-session (not
|
|
304
|
+
gate) evidence from 2026-07-29 on the same implementation set: a Cortex resident used the
|
|
305
|
+
host pi citizen's garden id instead of inventing one (`agentId=entwurf/cortex-claude-sonnet-5`,
|
|
306
|
+
`backend=pi`, `origin=pi-session`, `replyable=true`) and exchanged real turns across three
|
|
307
|
+
rails — native Claude Code mailbox drain, pi native control socket, and pi ACP Claude Sonnet
|
|
308
|
+
control socket; record `20260729T204101-7aaa6f` remains on disk. **Still pending as a
|
|
309
|
+
release-blocking `make` tag gate:** the preserved exact 0.13.0 candidate installed into a
|
|
310
|
+
fresh temporary root must drive one cold `entwurf/cortex-claude-sonnet-5` turn from those
|
|
311
|
+
installed bytes (unique nonce, exit 0, candidate sha256 unchanged before/after, resolved
|
|
312
|
+
installed root recorded). Installed-artifact evidence and real-Cortex evidence have not yet
|
|
313
|
+
met in one execution; a RED there stops the cut before the tag. Second machine, macOS, and
|
|
314
|
+
WSL2 remain unclaimed for this cut.
|
|
315
|
+
|
|
277
316
|
2026-07-25 repair.1 installed-native host acceptance (stable-promotion prerequisite):
|
|
278
317
|
the published registry artifact `@junghanacs/entwurf@0.12.8-repair.1` was installed
|
|
279
318
|
fresh on two Linux hosts (maintainer + secondary) through the package's stable bins.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to this project will be documented here. Format follows [Kee
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.13.0 — 2026-07-30
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Snowflake Cortex Code joins the ACP rail as the first non-claude backend** — the `cortexAdapter` contributed by hvkiefer (PR #40), landed together with the revisions the CP0 live audit of 2026-07-29 measured against Cortex Code v1.1.52 (`docs/acp-backend-rail.md` §11-8 records the ten defects D1–D10 and the agreed contract; the deltas are drift repair, not contributor error). As landed: the curated surface is the GLG-decided 4-row set (`cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, `cortex-openai-gpt-5.4`) riding real pi-ai registry bases behind the reserved `cortex-` routing prefix; launch is `cortex acp serve` from PATH (+ `-c <connection>` via `entwurfProvider.cortexConnection` / `ENTWURF_ACP_CORTEX_CONNECTION`) with **no `-m` pin** — the model is enforced per turn through `session/set_config_option("model", <native id>)`, the same wire call as claude, so a curated id the running cortex no longer serves fails loud before the prompt. Containment is a **session-scoped dual-HOME overlay**: cortex's config/skill/hook surface is `homedir()`-anchored and `CORTEX_HOME` outranks `SNOWFLAKE_HOME`, so the adapter refuses an ambient `CORTEX_HOME` outright (empty string included), runs the child under an isolated HOME (global-scope leak measured to zero; explicit cwd project scope retained), passes through only the measured-minimum auth (`connections.toml`, optional `config.toml`, `cortex/cache/credential_cache` — symlink-through, never copied), authors `autoUpdate: false`, and — because cortex's ACP `newSession` ignores the wire `mcpServers` param — **projects** the envelope-enriched explicit `entwurfProvider.mcpServers` into the overlay-private `cortex/mcp.json`, restoring the real operator HOME on the `entwurf-bridge` entry alone so a cortex sibling sees the real garden (an isolated-HOME bridge measured an empty citizen roster); non-stdio entries fail loud before spawn. Cortex is system-prompt-carrier-less: the operator engraving rides the first-user augment. Coverage: the deterministic `check-acp-cortex` gate (in `pnpm check`) plus the `acp-cortex` mutant lane's twelve kill-qualified claims — ten on that gate, and two cross-gate: the production overlay call site passing `resolveSessionKey`'s authoritative value (`check-acp-session-reuse`) and the real compiled provider entry registering both adapters' curated rows (`check-acp-provider-surface`, so an entry that silently dropped every `cortex-` row can no longer go green) — and the on-demand CP2 live smoke `LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live` — a real cortex model turn delivering outbound `entwurf_v2` to a seeded peer as itself, plus overlay disk facts and process-group reclaim (an MCP-configured cortex child ignores stdin EOF) — deliberately outside the claude-only **aggregate** LIVE floor, so a host that runs no cortex cannot redden a claude release; that is a wiring decision, and a cut shipping cortex still owes a deliberate run of it.
|
|
12
|
+
- **A host that does not run Cortex is unaffected by this release.** The external `cortex` CLI is not an npm dependency, and neither package install nor provider registration looks for the executable: the four curated rows are enumerated deterministically from the compiled provider entry, and `cortex acp serve` is spawned only when a turn actually selects a `cortex-` model — so selecting one on a host without Cortex fails loud at that turn rather than degrading install or the rest of the provider. The deterministic `check-acp-cortex` gate runs on fake/injected seams and needs neither the CLI nor Snowflake auth; the aggregate release floor contains no Cortex LIVE step; and the dedicated smoke honest-skips without `LIVE=1`, without `cortex` on `PATH`, or without a connection on the adapter seam. Rows are deliberately **not** hidden by PATH detection — that would make the provider surface differ per host and dissolve the exact-six-row fence — so an explicit operator enable/disable control remains a separate future contract, not a silent behaviour of this cut.
|
|
13
|
+
- **The ACP ordering probe lands as a gated instrument, and the readiness question it exists to answer stays open.** The rail's open question — whether MCP tool availability precedes what `newSession` returns — had no observable signal, so the probe injects a controlled fixture startup delay and reads control + D1 + D2 paired runs off one shared NDJSON axis instead of reconstructing order from separate logs with drifting clocks. A raw client may stand in for the backend only while a gate holds it to the same calls, arguments, and order, so `check-probe-ordering` pins the sequence against `backend.ts` source, attributes every failure to its wire phase, drives the probe-mode fixture as a real child process, types the event log at its door, and replays the paired-verdict truth table through the pure classifier. The log's door is a gate too: the envelope belongs to the writer, `ts` derives from a single clock read, and per `(runId, pid)` writer the raw append order must have strictly increasing `seq` and a non-regressing clock — a JSON-valid line with an unknown marker, a broken sort axis, or a payload the classifier cannot judge takes the INVALIDATED path instead of moving a verdict. The runner now holds the child open past the turn until the marker lands, the child exits, or a deadline anchored on the fixture's own delay markers passes, and only a deadline close lets a missing marker read as evidence; every intervention reports ordering and callability as two axes so a settled comparison is not buried by an unsettled verdict, and each ordering value is named for the comparison it is (`prompt-request-ahead-of-wire` says we issued the request first, never that the server failed to wait). The delta-B name oracle cannot be the model, so that seam is a CLI shim at `CLAUDE_CODE_EXECUTABLE`: the consumer half (target preconditions by key presence, absolute/regular/executable/no-script-suffix assertions, path+sha256 roster pinning with a post-pair re-hash, snapshot channel doors, receive-axis single-prompt binding, structural-vs-reading severity split) and then the producer shim itself both land kill-qualified. **No readiness finding is claimed.** The first paired run under the door contract produced 57 events with zero malformed lines, so the parser is calibrated rather than merely strict, and rail §11-7-b carries the numbers — but the verdict is inconclusive, and an inconclusive verdict is not "nothing wrong". Artifacts predating the window protocol re-parse as INVALIDATED and remain forensic records only.
|
|
14
|
+
|
|
15
|
+
### Verification
|
|
16
|
+
|
|
17
|
+
- Pre-version landing HEAD `9f1c7dc9e1fa77103e29a6f1884af7759e1595eb` passed exact-SHA GitHub Actions run [30505001694](https://github.com/junghan0611/entwurf/actions/runs/30505001694): `check`, `install-surface`, and `artifact-consumer` all success. That run is also the third-party half of the optionality claim above, at the strength its sources actually carry: `.github/workflows/ci.yml` provisions only checkout, pnpm, and Node 24 — it installs no Cortex CLI and supplies no Snowflake auth — and the required Linux `artifact-consumer` job ran the candidate inside a clean `node:24-bookworm` container and passed. That is a workflow-configuration fact plus a passing consumer run; it is not a probe of the runner image's contents.
|
|
18
|
+
- The prepared tree passed an independent `pnpm check`, including `check-gate-qualification` at **111/111 committed mutants killed** (the `acp-cortex` lane contributing twelve). Then `LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.13.0.drnRyR` completed all green: **MUST PASS=17 FAIL=0 SKIP=0** and **BEHAVIOR PASS=1 FAIL=0**, EXIT=0. Full log: `/tmp/entwurf-release-gate-0.13.0.drnRyR/release-gate.log`; per-step artifacts are preserved inside it.
|
|
19
|
+
- Cortex acceptance on the landing tree (2026-07-30, Linux/thinkpad, Cortex Code v1.1.52, connection on the adapter seam): the on-demand `smoke-acp-cortex-live` passed **23/23** — a deliberate rerun *after* the `resolveSessionKey` overlay-scope repair, so the shipped claim no longer rests on the earlier CP2 run's code. `check-pack-install` enumerated the **exact six curated rows (claude 2 + cortex 4)** out of the installed package's own model list, and `ENTWURF_REQUIRE_DOCKER=1 ./run.sh check-install-container` consumed candidate `sha256=63342aa8a144011dee86ebea8f0b778c7860e54dc0f1c710438983c679e8af87` on `node:24-bookworm` (`id=sha256:fcd0f74fb415c752…`, `repoDigest=node@sha256:5711a0d445a1af54…`).
|
|
20
|
+
- Cross-rail live round trips are **operator-session evidence, not a gate** (2026-07-29, same implementation set): a Cortex resident used the host pi citizen's garden id rather than inventing one — `agentId=entwurf/cortex-claude-sonnet-5`, `backend=pi`, reply envelope `origin=pi-session`, `replyable=true` — and exchanged real turns with native Claude Code (mailbox drain), pi native (control socket), and pi ACP Claude Sonnet (control socket). The record `20260729T204101-7aaa6f` remains on disk as corroboration.
|
|
21
|
+
- **Deferred to `make` as a release-blocking tag gate:** the preserved exact 0.13.0 candidate must be installed into a fresh temporary root and drive one cold `entwurf/cortex-claude-sonnet-5` turn from *those installed bytes* — unique nonce, exit 0, candidate SHA-256 unchanged before and after, resolved installed package root recorded. Until it runs, installed-artifact evidence and real-Cortex evidence have never met in one execution; a RED there stops the cut before the tag. Prepared-HEAD exact-SHA CI and preserved-candidate container acceptance remain deferred to `make` as usual.
|
|
22
|
+
|
|
7
23
|
## 0.12.10 — 2026-07-27
|
|
8
24
|
|
|
9
25
|
### Changed
|
package/CONTRIBUTING.md
CHANGED
|
@@ -4,7 +4,9 @@ This is a daily-driver bridge. Correctness beats feature breadth. Read this befo
|
|
|
4
4
|
|
|
5
5
|
## What this repo is
|
|
6
6
|
|
|
7
|
-
`entwurf` is a **garden-citizen dispatch bridge** — entwurf-core (v2 dispatch) + a meta-bridge + a pi adapter + a
|
|
7
|
+
`entwurf` is a **garden-citizen dispatch bridge** — entwurf-core (v2 dispatch) + a meta-bridge + a pi adapter + an **ACP plugin on a two-backend adapter rail** — that lets already-running harnesses address one another by garden id; pi is one adapter, not the subject. The ACP plugin borrows the backend's identity (system prompt preset, model behavior, tool implementations) and shapes the *operating surface* — tools, MCP, skills, permissions — to match pi's own policy **wherever that backend exposes a knob for it**. Where it does not, the plugin does not fake one: a cortex session keeps its own native tool surface and receives MCP through an overlay projection, so "shaped to pi's policy" is a Claude-strength claim, not a universal one. Claude is the reference ACP backend and Snowflake Cortex Code is the second (landed 0.13.0, `cortex-` prefixed ids — [docs/acp-backend-rail.md](./docs/acp-backend-rail.md) §6/§11-8); Codex is a native garden citizen, and the Gemini path is deprecated. That is the entire scope.
|
|
8
|
+
|
|
9
|
+
**How a backend joins the rail** (the shape a PR must take): one adapter object in `pi-extensions/lib/acp/backend-adapter.ts` + its own curated rows/overlay modules + its own `check-acp-*` gate and mutant lane. Backend-specific *behavior* must stay behind the adapter, and backend-specific settings ride the opaque `adapterSettings` seam rather than growing the common config. That is not a ban on ever touching the common layer (`backend.ts`'s turn loop, `acp-client.ts`, `event-mapper.ts`, `session-store.ts`, `config.ts`) — cortex's landing did change `backend.ts` to pass the authoritative session key through the generic `ensureOverlay` seam. The rule is narrower and stricter: a common-layer change must be **backend-invariant** (it reads no backend name and branches on no backend) and **separately gated**. A common file that grows an `if (backend === …)` is the thing to reject.
|
|
8
10
|
|
|
9
11
|
If a change moves the bridge toward "second harness" — prompt reconstruction, transcript hydration, ambient discovery, silent fallback — it does not belong here.
|
|
10
12
|
|
|
@@ -16,7 +18,7 @@ These are enforced by code, gates, and review. Do not weaken them in a PR; if yo
|
|
|
16
18
|
2. **Session persistence**: only `pi:<sessionId>` is persisted. `cwd:<cwd>` is never persisted.
|
|
17
19
|
3. **MCP injection**: only via `entwurfProvider.mcpServers`. No ambient `~/.mcp.json` scanning, no `~/.claude/settings.json` MCP inheritance.
|
|
18
20
|
4. **Operating surface, not config inheritance**: the user's filesystem Claude Code config (`~/.claude/settings.json` hooks, env, plugins, `permissions.defaultMode`) is intentionally *not* inherited. Skills come from `skillPlugins`, permissions from `permissionAllow`, deferred-tool surface from `disallowedTools`. The `CLAUDE_CONFIG_DIR` overlay enforces this even where the SDK reads filesystem independently of `settingSources`.
|
|
19
|
-
5. **Backend-specific knobs stay explicit and namespaced**: Codex/Gemini-era ACP knobs are not
|
|
21
|
+
5. **Backend-specific knobs stay explicit and namespaced**: retired Codex/Gemini-era ACP knobs are not carried on the current path. A backend that needs its own knob uses the `ENTWURF_ACP_*` namespace, and invalid values must throw, never fall back — cortex followed this with `ENTWURF_ACP_CORTEX_CONNECTION` (the renamed `PI_SHELL_ACP*` legacy var), and its settings key rides `adapterSettings`, not the common config.
|
|
20
22
|
6. **Bridge does not implement compaction**: When a backend compacts natively, the pi session and mapping survive that. Pi-side JSONL compaction must not be presented as backend-transcript reduction, and backend-specific compaction controls belong to the backend's own native interface. Legacy `PI_SHELL_ACP_*` compaction knobs must not reappear.
|
|
21
23
|
7. **Backend coverage honesty**: changes to operating surface, session lifecycle, or persistence must state which shipped/probed backend surfaces they cover. A claim that silently drops a covered backend is a regression; if one backend is genuinely not covered, record that carve-out explicitly.
|
|
22
24
|
8. **This bridge is not a second harness**: no prompt reconstruction, no transcript hydration, no tool result ledger, no Claude Code emulation.
|
package/DELIVERY.md
CHANGED
|
@@ -115,7 +115,7 @@ the `D0–D8` capability level:
|
|
|
115
115
|
| **Codex app-server-backed TUI 0.136.0** | verified-probe | D6, D7 (status) | WebSocket-over-UDS `turn/start` into the live `threadId` | **Demonstrated, no managed standalone, no cloud.** `codex app-server --listen unix://<owned 0700 dir>` + plain `codex` auto-attach (or `--remote unix://`). Full message injection (agy-like, not a doorbell); `thread/status/changed` gives completion observation. D8 robustness (dedupe / crash recovery / ordering policy) is not tested. `turn/steer` is active-turn steering, not idle wake. |
|
|
116
116
|
| **Codex embedded TUI 0.136.0** | deferred | D0 partial | Native state DB / rollout transcript only | Standalone Embedded TUI binds no socket; no `FileChanged`/`asyncRewake` in Codex hooks; not retrofittable. Identify-only via state DB / rollout. |
|
|
117
117
|
| **Codex managed-daemon / remote-control 0.136.0** | deferred | D4–D6 conditional | `app-server proxy` newline JSON-RPC over the daemon control socket | Needs the managed standalone install; `remote-control` also enables the **cloud** bridge. Use the bare `--listen` path above for a purely-local setup. |
|
|
118
|
-
| **ACP Claude / Cortex (
|
|
118
|
+
| **ACP Claude / Cortex (runtime lane)** | shipped as runtime; deferred as delivery target | — | ACP (via entwurf's pi adapter) | Both ACP backends are **landed runtime lanes** (Claude the reference; Cortex since 0.13.0 under the dual-HOME containment — `docs/acp-backend-rail.md` §11-8), but **not native-async-*delivery* targets**: ACP sessions are bridge-spawned children, not already-running native sessions to wake, so they fall outside this doc's delivery question. `deferred` here means "no async-delivery lane," not "unsupported." |
|
|
119
119
|
|
|
120
120
|
## Backend notes
|
|
121
121
|
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm package: <https://www.npmjs.com/package/@junghanacs/entwurf>
|
|
|
10
10
|
|
|
11
11
|
Legacy package: [`@junghanacs/pi-shell-acp`](https://www.npmjs.com/package/@junghanacs/pi-shell-acp). `entwurf` is its 0.12+ successor line: the same work renamed around the garden-citizen dispatch substrate rather than the pi adapter.
|
|
12
12
|
|
|
13
|
-
> **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one supported harness adapter — important because it supplies control sockets and hosts the ACP plugin today — but it is not the project subject. Claude Code is shipped as a mailbox-backed meta-session; Antigravity (`agy`) is shipped as a native-push citizen with automatic `PreInvocation` birth, ambient garden-id status, and a managed MCP/permission install surface. Codex has a launch-mode-specific verified delivery probe documented in [DELIVERY.md](./DELIVERY.md), but no managed native-citizen install lane yet. The ACP plugin
|
|
13
|
+
> **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one supported harness adapter — important because it supplies control sockets and hosts the ACP plugin today — but it is not the project subject. Claude Code is shipped as a mailbox-backed meta-session; Antigravity (`agy`) is shipped as a native-push citizen with automatic `PreInvocation` birth, ambient garden-id status, and a managed MCP/permission install surface. Codex has a launch-mode-specific verified delivery probe documented in [DELIVERY.md](./DELIVERY.md), but no managed native-citizen install lane yet. The ACP plugin ships two backends through one adapter rail: Claude (the reference) and Snowflake Cortex Code (landed in 0.13.0 under a session-scoped dual-HOME containment — [docs/acp-backend-rail.md](./docs/acp-backend-rail.md) §11-8).
|
|
14
14
|
|
|
15
15
|
<details>
|
|
16
16
|
<summary>Watch archived pre-0.12 demo (2131×1142 GIF, click to expand)</summary>
|
|
@@ -28,7 +28,7 @@ Claude Code / Codex / agy / pi
|
|
|
28
28
|
→ control-socket | spawn-bg resume | meta-mailbox | native-push
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
[`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, spawn-bg resume, meta-mailbox enqueue, and native-push into a live Antigravity conversation. The meta-record is the sole address authority (#50 C4): a record-less control socket is refused as a `record-less-socket` diagnostic, never dispatched. The v1 entwurf verbs are gone. Fresh sibling minting
|
|
31
|
+
[`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, spawn-bg resume, meta-mailbox enqueue, and native-push into a live Antigravity conversation. The meta-record is the sole address authority (#50 C4): a record-less control socket is refused as a `record-less-socket` diagnostic, never dispatched. The v1 entwurf verbs are gone. Fresh sibling minting is still a deferred lane; the non-Claude ACP lane is not — Snowflake Cortex Code landed as the second backend in 0.13.0.
|
|
32
32
|
|
|
33
33
|
**Garden id is deliberate vocabulary.** It is not a decorative synonym for session id, worker, delegate, or subagent. The unfamiliar word is a guard: each harness keeps its own identity and transcript, while `entwurf` supplies a narrow addressable surface between siblings.
|
|
34
34
|
|
|
@@ -462,13 +462,26 @@ plain MCP registrations.
|
|
|
462
462
|
|
|
463
463
|
### Backend prerequisites
|
|
464
464
|
|
|
465
|
-
|
|
465
|
+
**Claude is the reference ACP backend.** The Claude ACP server package (`@agentclientprotocol/claude-agent-acp`, pinned with `@agentclientprotocol/sdk`) ships as a pinned `dependency` of `entwurf`; backend authentication still belongs to the operator's local `claude` CLI / runtime. Once the bridge is installed, the resolver picks the ACP server in this order:
|
|
466
466
|
|
|
467
467
|
1. **`CLAUDE_AGENT_ACP_COMMAND` env override** — explicit override for an alternative binary or a wrapper command.
|
|
468
468
|
2. **`require.resolve(...)` against the bundled package dependency** (`@agentclientprotocol/claude-agent-acp`). This is the default path; no extra global install needed.
|
|
469
469
|
3. **`PATH:claude-agent-acp` fallback** — used when the package resolution fails (e.g. a hand-edited `node_modules`).
|
|
470
470
|
|
|
471
|
-
The curated model registry exposes Claude
|
|
471
|
+
The curated model registry exposes the unprefixed Claude ids plus the `cortex-` prefixed Cortex rows (below). Codex is *not* an ACP backend here — a native Codex session is already a first-class garden citizen via direct injection, so it needs no ACP plugin (see [AGENTS.md](./AGENTS.md)).
|
|
472
|
+
|
|
473
|
+
**Snowflake Cortex Code is the second landed ACP backend** (0.13.0; audit record and contract: [docs/acp-backend-rail.md](./docs/acp-backend-rail.md) §11-8). Operator surface:
|
|
474
|
+
|
|
475
|
+
- **Curated ids (4):** `cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, `cortex-openai-gpt-5.4`. The `cortex-` prefix routes; the model is enforced per turn via ACP set-model, so an id the running cortex no longer serves fails loud before the prompt.
|
|
476
|
+
- **CLI + auth:** `cortex` must be on PATH and already authenticated through Cortex's **own web-login flow** (there is no `cortex auth` subcommand; entwurf never supplies or proxies the Snowflake credential — the overlay reaches it by **symlinking through** exactly `connections.toml`, optional `config.toml`, and `cortex/cache/credential_cache`, and nothing else. The narrowing is *which paths are reachable* (§11-8 D5), not a read-only mount: a symlink carries no write protection, so the child holds the same access to those three paths that it would have with the operator's own HOME. What the overlay removes is the rest of the operator's **home** surface — the rest of `cortex/cache`, `~/.claude`/`~/.cortex` skills, home-anchored hooks and settings, and the operator's `cortex/mcp.json`. It does **not** reach cortex's *bundled* plugin/hook surface, which fires from the CLI's own install directory and is outside any HOME the overlay controls — that stays a host fact, not a containment claim (§11-8 D1).)
|
|
477
|
+
- **Connection:** pin a Snowflake connection with `entwurfProvider.cortexConnection` in settings or per-shell via `ENTWURF_ACP_CORTEX_CONNECTION` (env wins). `entwurfProvider.backend: "cortex"` is an optional diagnostic guard, never the router.
|
|
478
|
+
- **`CORTEX_HOME` must be unset.** It outranks `SNOWFLAKE_HOME` inside cortex and would bypass the dual-HOME containment, so the adapter refuses the spawn when it is present at all (empty string included).
|
|
479
|
+
- **Containment:** each session runs under an isolated HOME (operator-global `~/.claude`/`~/.cortex` skills, hooks and settings are structurally unreachable; explicit `<cwd>/.claude` project scope is retained), with the explicit `entwurfProvider.mcpServers` projected into an overlay-private `cortex/mcp.json` — cortex's ACP server ignores the wire `mcpServers` parameter, so this projection is how tools reach a cortex session. Only the `entwurf-bridge` entry gets the real operator HOME back (the garden store axis).
|
|
480
|
+
- **Live check:** `LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> entwurf smoke-acp-cortex-live` (or `./run.sh …` from a clone) drives one real cortex turn: outbound `entwurf_v2` delivery as `entwurf/<cortex model>`, overlay disk facts, and process-group reclaim. It is deliberately **on-demand** — which is a statement about *wiring*, not about *whether cortex needs live evidence*:
|
|
481
|
+
|
|
482
|
+
- The aggregate floor (`LIVE=1 ./run.sh release-gate`) stays claude-only, so a host with no cortex install or no Snowflake auth cannot redden a release for a backend it does not run.
|
|
483
|
+
- That is **not** a claim that cortex ships on deterministic evidence alone. Accepting a cut that ships cortex means running this smoke deliberately, and reading its result — the aggregate gate will not run it for you, and its silence is not a pass.
|
|
484
|
+
- Cortex's always-on axis is the deterministic `check-acp-cortex`, which does ride `pnpm check`.
|
|
472
485
|
|
|
473
486
|
### Emacs frontends
|
|
474
487
|
|
|
@@ -523,6 +536,8 @@ Reference shape lives in [`pi/settings.reference.json`](./pi/settings.reference.
|
|
|
523
536
|
|
|
524
537
|
`appendSystemPrompt: false` is intentional. Pi / AGENTS context rides the first-user augment; putting it into the Claude `_meta.systemPrompt` carrier can route OAuth sessions to metered "extra usage" billing.
|
|
525
538
|
|
|
539
|
+
**Which keys reach which backend.** `entwurfProvider` is one block for both backends, but its keys are not universal. `tools` / `permissionAllow` / `disallowedTools` / `settingSources` / `skillPlugins` / `appendSystemPrompt` are Claude's declaration surface: they do not shape a cortex session, which runs its own native tools and reaches MCP through the overlay-private `mcp.json` projection instead. They are not inert, though — the bridge still reads `tools` for its backend-invariant exclude-tools preflight and folds all of them into the config signature, so editing one still forces a fresh cortex session. `cortexConnection` is cortex-only. `mcpServers` is the one declaration surface that reaches both, each through its own transport. (`compaction` is a *top-level* pi key, not an `entwurfProvider` one — it is pi's own transcript policy and is not a backend knob at all; see §Compaction.) Unknown and retired keys are ignored rather than rejected, so a key aimed at the wrong backend fails silently: if a cortex session does not show the surface you configured, check that the key is one cortex actually consumes before suspecting the bridge.
|
|
540
|
+
|
|
526
541
|
### Wiring `entwurf-bridge` into an external MCP host
|
|
527
542
|
|
|
528
543
|
`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:
|
|
@@ -651,7 +666,7 @@ The Claude ACP backend keeps its native model / API / tools; entwurf shapes only
|
|
|
651
666
|
|
|
652
667
|
**Claude** uses `_meta.systemPrompt` for the engraving carrier (kept short and pure — billing-safe; rich operator context rides the first user message instead, see [Context carriers](#context-carriers)) and `CLAUDE_CONFIG_DIR` for a whitelist overlay so auth/runtime entries stay available while operator memory, hooks, agents, history, local settings, and project memory remain hidden. The overlay writes an explicit empty `hooks: {}` because Claude SDK organic compaction needs the configured-empty shape; no operator hook definitions are inherited. The four-tool baseline (`Read`, `Bash`, `Edit`, `Write`) is enforced through `tools` + `permissionAllow`; `Skill` is added automatically when `skillPlugins` is non-empty. Operator context cap override: `ENTWURF_ACP_CLAUDE_CONTEXT=<int>`.
|
|
653
668
|
|
|
654
|
-
(Codex is *not* an ACP backend here — it reaches the garden natively.
|
|
669
|
+
(Codex is *not* an ACP backend here — it reaches the garden natively. The governed-CLI lane is open, not later: Snowflake Cortex Code landed on it in 0.13.0, and a third governed backend would join the same adapter rail.)
|
|
655
670
|
|
|
656
671
|
Antigravity is also not an ACP backend. It is a native-push citizen: `PreInvocation` supplies birth/sender identity, `entwurf_v2` probes and direct-injects replies into the live conversation, and no mailbox/receiver marker is involved.
|
|
657
672
|
|
package/VERIFY.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# VERIFY.md
|
|
2
2
|
|
|
3
|
-
Agent-driven verification guide for the
|
|
3
|
+
Agent-driven verification guide for the `entwurf` surface as it stands in the working tree — the last **released** line is 0.12.x, and the tree is the **prepared 0.13.0 cut** (its tag, GitHub release, and publication are separate authorizations that have not run). The cut carries more than one change (the probe/ordering lane sits in it too); the change relevant to *this guide* is the second ACP backend. Rows below marked 0.12 are the released baseline; where 0.13.0 differs, it says so.
|
|
4
4
|
|
|
5
|
-
> **Current surface.** The bundled MCP server, `entwurf-bridge`, exposes five tools: `entwurf_v2`, `entwurf_peers`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native` (an explicit/manual fallback for binding an already-running native conversation). The
|
|
5
|
+
> **Current surface.** The bundled MCP server, `entwurf-bridge`, exposes five tools: `entwurf_v2`, `entwurf_peers`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native` (an explicit/manual fallback for binding an already-running native conversation). The ACP backends are **Claude** (the reference, 0.12) and, as of **0.13.0**, **Snowflake Cortex Code** (`cortex-` prefixed ids; deterministic axis `check-acp-cortex` in `pnpm check`, LIVE axis `smoke-acp-cortex-live` on demand — see [docs/acp-backend-rail.md](./docs/acp-backend-rail.md) §11-8 for the measured contract and its limits). Antigravity (`agy`) is a separate shipped **native-push citizen** lane, not an ACP backend: automatic `PreInvocation` birth + sender identity + live probe/direct injection. Codex is pi-native by default and has native delivery probe evidence; Gemini is a non-goal/historical ACP probe on 0.12. The 0.4.x `session-bridge` adapter, the 0.11.0 fat-bridge (`acp-bridge.ts` / `ensureBridgeSession`), and the v1 `entwurf` / `entwurf_resume` / `entwurf_send` verbs are **retired** — rows mentioning them survive in CHANGELOG/git as historical baseline, never as a runnable recipe.
|
|
6
6
|
|
|
7
7
|
This is a **working document, not a metrics document**. The deterministic and live gates carry the machine-checkable invariants; this file carries only what a gate cannot judge — the human/agent reading of *whether the bridge is honestly itself*. Where a former manual procedure is now a gate, it is named as a pointer rather than re-spelled as a runnable script.
|
|
8
8
|
|
|
@@ -43,7 +43,7 @@ Verification here is not a benchmark. In production we exchange short turns and
|
|
|
43
43
|
|
|
44
44
|
> The aggregate release gate does not own a live agy conversation id, so agy's real native-push round trip is a separate acceptance axis: three fail-loud doctors plus `LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live`, followed by a fresh-conversation sender/reply check after package install. Its deterministic install/sender gates are already inside `pnpm check`; do not misreport the aggregate gate as live agy evidence.
|
|
45
45
|
>
|
|
46
|
-
> The authoritative per-cut counts live in BASELINE.md's HISTORY and CHANGELOG/git, not inline here (they drift against `run.sh`). Most recent recorded aggregate: **2026-07-24 (night) — MUST 16/1/0 + BEHAVIOR 1/0, EXIT=1** at `cbda097` — the single FAIL is the known bundled-MCP readiness race (ROADMAP 「🔴 OPEN」, observe-don't-fix; isolated re-runs pass). Most recent all-green floor: **2026-07-24 — MUST 17/0/0 + BEHAVIOR 1/0, EXIT=0** at `7cbeb29b6afcfbaf4fc28da3b7929037c339113d` (the dependency-uplift HEAD: pi 0.82.0 + claude-agent-acp 0.61.0 / ACP SDK 1.3.0). The step count moved 16→17 with `smoke-acp-v2-send-live`; earlier that day it had moved 17→16 with the v2-cutover smoke retirements. A release cut requires the aggregate red to be resolved or explicitly ruled by GLG.
|
|
46
|
+
> The authoritative per-cut counts live in BASELINE.md's HISTORY and CHANGELOG/git, not inline here (they drift against `run.sh`). Most recent recorded aggregate: **2026-07-24 (night) — MUST 16/1/0 + BEHAVIOR 1/0, EXIT=1** at `cbda097` — the single FAIL is the known bundled-MCP readiness race (ROADMAP 「🔴 OPEN」, observe-don't-fix; isolated re-runs pass). Most recent all-green floor: **2026-07-30 — MUST 17/0/0 + BEHAVIOR 1/0, EXIT=0** on the prepared 0.13.0 tree (parent landing HEAD `9f1c7dc`, scratch `/tmp/entwurf-release-gate-0.13.0.drnRyR`) — the first aggregate completion carrying the cortex adapter and the probe lane; the step count did not move, because the cortex LIVE axis is deliberately outside this aggregate and is run on demand. Before it: **2026-07-24 — MUST 17/0/0 + BEHAVIOR 1/0, EXIT=0** at `7cbeb29b6afcfbaf4fc28da3b7929037c339113d` (the dependency-uplift HEAD: pi 0.82.0 + claude-agent-acp 0.61.0 / ACP SDK 1.3.0). The step count moved 16→17 with `smoke-acp-v2-send-live`; earlier that day it had moved 17→16 with the v2-cutover smoke retirements. A release cut requires the aggregate red to be resolved or explicitly ruled by GLG.
|
|
47
47
|
|
|
48
48
|
### Artifact / host certification matrix — #51 repair line → 0.12.8 stable
|
|
49
49
|
|
|
@@ -203,6 +203,8 @@ mailbox with `origin=pi-session`, `replyable=true`).
|
|
|
203
203
|
|
|
204
204
|
Compare a fresh self-awareness report across axes: (1) same backend, different install path — answer must be path-invariant; (2) same backend, different machine — identical native tool list + MCP server/tool set; (3) different backend, same bridge — same garden capability but **different** native tool surface (a Claude session reporting another backend's native tools is a fail); (4) native pi routing vs ACP-bridged, same model — the native target reports no `entwurf-bridge` MCP (capability via pi's extension surface), while the ACP target reports it as the single MCP server. Honest "native: I cannot tell" hedging is PASS on the native side. Claude axes 1–4 are closed. agy is graded by the separate native-citizen checklist above, not by pretending it has Claude's ACP overlay. Gemini remains probe-only on 0.12.
|
|
205
205
|
|
|
206
|
+
**Cortex (0.13.0) on axis 3.** Cortex is the first real *different backend, same bridge* subject, and it is the sharpest form of this axis: a cortex session must report the garden capability (`entwurf-bridge` reachable, record-backed peers) while its native tool surface is cortex's own, never Claude's — a cortex session listing Claude's built-ins is a fail. Two contract facts to grade against rather than re-derive: the callable identifier shape is the same `mcp__<server>__<tool>` convention as Claude (**measured**, §11-8), and cortex is **system-prompt-carrier-less**, so the operator engraving arrives at the head of the first-user augment instead of in a system prompt — a cortex session claiming a system-prompt engraving is reporting something it does not have. Axis 1 (install-path invariance) is split as of 0.13.0: the **deterministic** half is closed — `check-pack-install` enumerates the exact six curated rows out of the installed package's own model list — while the **live** half is a release-blocking `make` tag gate (the preserved exact candidate installed into a fresh temporary root driving one cold `entwurf/cortex-claude-sonnet-5` turn from those installed bytes). Until that gate runs, installed-artifact evidence and real-Cortex evidence have never met in one execution. Axis 2 (second machine) is unrun.
|
|
207
|
+
|
|
206
208
|
---
|
|
207
209
|
|
|
208
210
|
## 1A. Main Agent Evaluation — Is `entwurf` Claude strong enough?
|
|
@@ -253,10 +255,11 @@ The literal callable identifier differs per backend — probe by asking the agen
|
|
|
253
255
|
| Backend | Literal identifier | Outer sep | Inner server name |
|
|
254
256
|
|---|---|---|---|
|
|
255
257
|
| Claude | `mcp__entwurf-bridge__entwurf_v2` | `__` | `entwurf-bridge` (hyphen) |
|
|
258
|
+
| Cortex *(0.13.0)* | `mcp__entwurf-bridge__entwurf_v2` | `__` | `entwurf-bridge` (hyphen) — same shape as Claude, **measured** (§11-8) |
|
|
256
259
|
| Codex | `mcp__entwurf_bridge__.entwurf_v2` | `__` | `entwurf_bridge` (underscore) + **literal dot** |
|
|
257
260
|
| Gemini *(probe)* | `mcp_entwurf-bridge_entwurf_v2` | `_` (single) | `entwurf-bridge`, no dot |
|
|
258
261
|
|
|
259
|
-
A Claude session reporting the underscore form, or any cross-shape leak, is a backend-identification leak.
|
|
262
|
+
A Claude session reporting the underscore form, or any cross-shape leak, is a backend-identification leak. Released 0.12 baseline is Claude; cortex joins it in 0.13.0 and is the one row whose shape had to be *measured* rather than assumed (identical to Claude's — so this row cannot discriminate Claude from Cortex, and the tool-surface axis in §1.4 is what does). The Codex/Gemini rows are reference for the probe lanes.
|
|
260
263
|
|
|
261
264
|
### 2.2 MCP injection visibility — equal across resume/load/new
|
|
262
265
|
|
|
@@ -18,11 +18,22 @@
|
|
|
18
18
|
// lib/acp modules — no new strip-types fence.
|
|
19
19
|
import { readFileSync } from "node:fs";
|
|
20
20
|
import { createRequire } from "node:module";
|
|
21
|
+
import { homedir } from "node:os";
|
|
21
22
|
import { dirname, join } from "node:path";
|
|
23
|
+
import { enrichMcpServersWithEnvelope } from "./config.js";
|
|
22
24
|
import { loadEngraving } from "./engraving.js";
|
|
23
|
-
import { curatedClaudeModels, SUPPORTED_ANTHROPIC_MODEL_IDS } from "./models.js";
|
|
24
|
-
import { claudeLaunchEnvDefaults, ensureClaudeConfigOverlay } from "./overlay.js";
|
|
25
|
+
import { CORTEX_MODEL_PREFIX, curatedClaudeModels, curatedCortexModels, SUPPORTED_ANTHROPIC_MODEL_IDS, SUPPORTED_CORTEX_MODEL_IDS, } from "./models.js";
|
|
26
|
+
import { claudeLaunchEnvDefaults, ensureClaudeConfigOverlay, ensureCortexDualHomeOverlay } from "./overlay.js";
|
|
25
27
|
import { buildClaudeSessionMeta } from "./tool-surface.js";
|
|
28
|
+
// POSIX-safe single-quote wrapper for shell arg interpolation. Byte-for-byte
|
|
29
|
+
// identical to the reference in entwurf-core.ts; PARITY-PINNED by
|
|
30
|
+
// scripts/check-shell-quote.ts (SOURCE_SITES). Used only by the cortex override
|
|
31
|
+
// path below, where operator-configured connection/model tokens are appended to
|
|
32
|
+
// an operator `bash -lc` string — quoting keeps a connection name with shell
|
|
33
|
+
// metacharacters from being reinterpreted by the shell.
|
|
34
|
+
function shellQuote(value) {
|
|
35
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
36
|
+
}
|
|
26
37
|
// ---------------------------------------------------------------------------
|
|
27
38
|
// claude adapter — the first implementation (the rail's reference backend)
|
|
28
39
|
// ---------------------------------------------------------------------------
|
|
@@ -105,9 +116,141 @@ export const claudeAdapter = {
|
|
|
105
116
|
// Registry — modelId → adapter
|
|
106
117
|
// ---------------------------------------------------------------------------
|
|
107
118
|
/** Registered adapters. Order carries NO routing authority — routeModel decides.
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
|
|
119
|
+
* claude (unprefixed ids) + cortex (the `cortex-` prefix). A further backend
|
|
120
|
+
* appends here with its own reserved prefix; the fail-fast below proves no two
|
|
121
|
+
* adapters claim one id. */
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// cortex adapter — Snowflake Cortex Code, the first non-claude backend on the
|
|
124
|
+
// rail (docs/acp-backend-rail.md §4/§6). It adds ZERO to the common layer:
|
|
125
|
+
// everything cortex-specific lives here + models.ts + overlay.ts + the gates.
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
const SUPPORTED_CORTEX_IDS = new Set(SUPPORTED_CORTEX_MODEL_IDS);
|
|
128
|
+
/** The env var an operator sets to pin a Snowflake connection per-shell without
|
|
129
|
+
* editing settings.json. Wins over `entwurfProvider.cortexConnection`. The
|
|
130
|
+
* ENTWURF_ACP_* convention (PR #40's legacy PI_SHELL_ACP* cortex-connection var renamed). */
|
|
131
|
+
export const CORTEX_CONNECTION_ENV = "ENTWURF_ACP_CORTEX_CONNECTION";
|
|
132
|
+
export const cortexAdapter = {
|
|
133
|
+
backend: "cortex",
|
|
134
|
+
// Cortex owns the reserved `cortex-` prefix (§9-1). routeModel strips it to the
|
|
135
|
+
// native id: `cortex-auto` → "auto", `cortex-claude-sonnet-5` → "claude-sonnet-5".
|
|
136
|
+
routeModel(modelId) {
|
|
137
|
+
if (!SUPPORTED_CORTEX_IDS.has(modelId))
|
|
138
|
+
return undefined;
|
|
139
|
+
return { nativeModelId: modelId.slice(CORTEX_MODEL_PREFIX.length) };
|
|
140
|
+
},
|
|
141
|
+
curatedModels() {
|
|
142
|
+
return curatedCortexModels();
|
|
143
|
+
},
|
|
144
|
+
// Cortex's ONLY own setting is the connection name. env override wins over
|
|
145
|
+
// settings (per-shell pin); empty/whitespace → null (Cortex falls back to its
|
|
146
|
+
// own default connection). A non-string settings value fails loud.
|
|
147
|
+
resolveAdapterSettings({ mergedBlock, projectBlock, globalPath, projectPath }) {
|
|
148
|
+
const raw = mergedBlock.cortexConnection;
|
|
149
|
+
if (raw !== undefined && typeof raw !== "string") {
|
|
150
|
+
const offending = projectBlock.cortexConnection !== undefined ? projectPath : globalPath;
|
|
151
|
+
throw new Error(`${offending}: invalid entwurfProvider settings: cortexConnection must be a string`);
|
|
152
|
+
}
|
|
153
|
+
const envConn = process.env[CORTEX_CONNECTION_ENV]?.trim();
|
|
154
|
+
const settingsConn = raw?.trim();
|
|
155
|
+
const cortexConnection = envConn || settingsConn || null;
|
|
156
|
+
return { cortexConnection };
|
|
157
|
+
},
|
|
158
|
+
// `cortex acp serve` resolved from PATH (the CLI itself IS the ACP server — no
|
|
159
|
+
// `*-acp` npm package, unlike claude). `-c <conn>` appended when a connection
|
|
160
|
+
// is pinned. NO `-m`: the model is enforced per-turn via
|
|
161
|
+
// session/set_config_option (enforceModel below, CP0-M measured GO) and a
|
|
162
|
+
// launch pin would be a SECOND model authority that drifts from it.
|
|
163
|
+
// CORTEX_ACP_COMMAND override runs via `bash -lc` with the selection flags
|
|
164
|
+
// appended so the bridge's choice wins (later yargs args override earlier ones).
|
|
165
|
+
resolveLaunch({ config }) {
|
|
166
|
+
const settings = config.adapterSettings;
|
|
167
|
+
const connection = settings?.cortexConnection?.trim() || undefined;
|
|
168
|
+
const selectionArgs = [];
|
|
169
|
+
if (connection)
|
|
170
|
+
selectionArgs.push("-c", connection);
|
|
171
|
+
const override = process.env.CORTEX_ACP_COMMAND?.trim();
|
|
172
|
+
if (override) {
|
|
173
|
+
const command = selectionArgs.length > 0 ? `${override} ${selectionArgs.map(shellQuote).join(" ")}` : override;
|
|
174
|
+
return { command: "bash", args: ["-lc", command] };
|
|
175
|
+
}
|
|
176
|
+
return { command: "cortex", args: ["acp", "serve", ...selectionArgs] };
|
|
177
|
+
},
|
|
178
|
+
// The overlay location is SESSION-SCOPED (never static), so the spawn env
|
|
179
|
+
// rides ensureOverlay(...).envOverrides; there is no static launch env. The
|
|
180
|
+
// v1.1.8-era CORTEX_DISABLE_AUTO_APPLY_PROFILES knob was retired with the
|
|
181
|
+
// dual-HOME redesign: profiles now live inside the overlay-owned isolated
|
|
182
|
+
// home (empty by construction), and the knob is unmeasured on v1.1.52.
|
|
183
|
+
launchEnvDefaults() {
|
|
184
|
+
return {};
|
|
185
|
+
},
|
|
186
|
+
// Dual-HOME containment (CP0 D2/D3/D9/D10 — see the overlay module header):
|
|
187
|
+
// refuse an ambient CORTEX_HOME outright, then materialize the session-scoped
|
|
188
|
+
// isolated HOME with auth symlinks, `autoUpdate:false`, and the mcp.json
|
|
189
|
+
// projection of the envelope-enriched explicit servers (cortex ignores the
|
|
190
|
+
// wire mcpServers param, so this file IS how tools reach a cortex session).
|
|
191
|
+
ensureOverlay({ modelId, config, sessionKey }) {
|
|
192
|
+
// D3 — presence refusal, empty string included: upstream's resolver treats
|
|
193
|
+
// a set-but-empty CORTEX_HOME differently from unset, and one ambient value
|
|
194
|
+
// would silently bypass SNOWFLAKE_HOME (the probe's CLAUDE_CODE_EXECUTABLE
|
|
195
|
+
// precondition is the same family). Refuse the ambiguity; never pick a side.
|
|
196
|
+
if ("CORTEX_HOME" in process.env) {
|
|
197
|
+
throw new Error("entwurf: CORTEX_HOME is present in the environment (empty string included) — it overrides " +
|
|
198
|
+
"SNOWFLAKE_HOME inside cortex and would bypass the dual-HOME overlay entirely (CP0 D3). " +
|
|
199
|
+
"Unset it to run a cortex ACP turn.");
|
|
200
|
+
}
|
|
201
|
+
// The scope authority is the AUTHORITATIVE params.sessionKey backend.ts
|
|
202
|
+
// computed — never an ambient re-derivation, which would drop opts.sessionId
|
|
203
|
+
// and alias two same-process/cwd sessions onto one overlay (P0-1). The
|
|
204
|
+
// envelope below still reads PI_SESSION_ID: that is the identity CARRIER for
|
|
205
|
+
// the bridge child (the same source the turn loop's wire enrichment uses),
|
|
206
|
+
// a different axis from overlay-dir scoping.
|
|
207
|
+
const piSessionId = process.env.PI_SESSION_ID?.trim() || undefined;
|
|
208
|
+
const enriched = enrichMcpServersWithEnvelope(config.mcpServers, { modelId, piSessionId });
|
|
209
|
+
const overlay = ensureCortexDualHomeOverlay({
|
|
210
|
+
scopeKey: sessionKey,
|
|
211
|
+
mcpServers: enriched,
|
|
212
|
+
realHome: homedir(),
|
|
213
|
+
});
|
|
214
|
+
return { envOverrides: { HOME: overlay.home, SNOWFLAKE_HOME: overlay.snowflakeHome } };
|
|
215
|
+
},
|
|
216
|
+
// System-prompt-carrier-less (§9-4/§11-8): Cortex ACP exposes no
|
|
217
|
+
// `_meta.systemPrompt` and has no developer_instructions / GEMINI_SYSTEM_MD
|
|
218
|
+
// equivalent. (It does READ `_meta` — a caller-session-id seam, measured but
|
|
219
|
+
// unexplored and deliberately not part of this contract.) loadCarrier returns
|
|
220
|
+
// null WITHOUT calling loadEngraving, so the cortex turn never touches the
|
|
221
|
+
// shipped-engraving / appendSystemPrompt signature; buildSessionMeta returns
|
|
222
|
+
// undefined so backend.ts omits the `_meta` key entirely. The operator
|
|
223
|
+
// engraving instead rides the first-user augment (augment.ts).
|
|
224
|
+
loadCarrier() {
|
|
225
|
+
return null;
|
|
226
|
+
},
|
|
227
|
+
buildSessionMeta() {
|
|
228
|
+
return undefined;
|
|
229
|
+
},
|
|
230
|
+
// Per-turn enforcement via session/set_config_option — the SAME wire call the
|
|
231
|
+
// claude adapter makes, measured live against cortex v1.1.52 (CP0-M): the
|
|
232
|
+
// option id is "model", accepted values are the NATIVE ids (`auto`,
|
|
233
|
+
// `claude-sonnet-5`, `openai-gpt-5.4`, …), and a value cortex no longer
|
|
234
|
+
// serves fails loud BEFORE the prompt (`Unsupported model: …`). PR #40's
|
|
235
|
+
// launch-time `-m` pin was retired for this: set-model is the single model
|
|
236
|
+
// authority (resolveLaunch never passes `-m`), and "auto" is set explicitly
|
|
237
|
+
// rather than treated as an unspoken default.
|
|
238
|
+
async enforceModel({ connection, acpSessionId, nativeModelId, modelId }) {
|
|
239
|
+
const setConfig = connection.setSessionConfigOption;
|
|
240
|
+
if (typeof setConfig !== "function") {
|
|
241
|
+
throw new Error(`setSessionConfigOption unsupported — cannot enforce model ${modelId}`);
|
|
242
|
+
}
|
|
243
|
+
await setConfig.call(connection, { sessionId: acpSessionId, configId: "model", value: nativeModelId });
|
|
244
|
+
},
|
|
245
|
+
// A connection change must invalidate a reused session (§4/§7). Flat,
|
|
246
|
+
// sorted-stable primitive map; reads ONLY the opaque adapterSettings. `backend`
|
|
247
|
+
// + `nativeModelId` are added by backend.ts.
|
|
248
|
+
configSignatureFields(adapterSettings) {
|
|
249
|
+
const settings = adapterSettings;
|
|
250
|
+
return { cortexConnection: settings?.cortexConnection ?? null };
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
const ADAPTERS = [claudeAdapter, cortexAdapter];
|
|
111
254
|
/**
|
|
112
255
|
* Resolve the backend adapter that owns `modelId`.
|
|
113
256
|
*
|
|
@@ -8,11 +8,23 @@
|
|
|
8
8
|
// config and hands it to the backend so the documented passthrough actually
|
|
9
9
|
// reaches `newSession`.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
11
|
+
// Scope. Ported from the v0.11.0 behavior oracle (index.ts
|
|
12
|
+
// `loadProviderSettings`/`readSettingsFile` + acp-bridge.ts
|
|
13
13
|
// `normalizeMcpServers`/`enrichMcpServersWithEnvelope`) — structure new, behavior
|
|
14
|
-
// preserved. Codex/Gemini fields (codexDisabledFeatures, …) are
|
|
15
|
-
//
|
|
14
|
+
// preserved. Retired Codex/Gemini-era fields (codexDisabledFeatures, …) are NOT
|
|
15
|
+
// carried and are silently ignored if present.
|
|
16
|
+
//
|
|
17
|
+
// The COMMON keys parsed here are Claude-SHAPED by origin (tools / permissionAllow /
|
|
18
|
+
// disallowedTools / settingSources / skillPlugins are claude-agent-acp's surface).
|
|
19
|
+
// What varies per backend is whether they are PROJECTED ONTO THE BACKEND SESSION —
|
|
20
|
+
// not whether they are read at all. Cortex runs native tools and reaches MCP through
|
|
21
|
+
// its own overlay mcp.json projection, so none of those keys shape a cortex session;
|
|
22
|
+
// they nonetheless stay live on the common path, where backend.ts reads `tools` for
|
|
23
|
+
// the backend-invariant exclude-tools preflight and folds every one of them into
|
|
24
|
+
// `bridgeConfigSignature` (so changing one still invalidates a reused cortex
|
|
25
|
+
// session). Backend-OWNED settings never land here: they ride the opaque
|
|
26
|
+
// `adapterSettings` seam (§10 B), which is what keeps a backend-named key like
|
|
27
|
+
// `cortexConnection` off this common shape.
|
|
16
28
|
//
|
|
17
29
|
// PURITY / SIGNATURE contract (NEXT oracle C / 핀1 / GPT `…2f9325` boost):
|
|
18
30
|
// - `normalizeMcpServers` is pure: a SORTED, validated server list + a sha256
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
// ACP plugin — curated
|
|
1
|
+
// ACP plugin — curated model surface (S0 loader/fence slice; cortex rows added
|
|
2
|
+
// in 0.13).
|
|
2
3
|
//
|
|
3
4
|
// entwurf is an ACP *plugin* on the v2 core, NOT a general-purpose
|
|
4
5
|
// Anthropic provider. It deliberately does not expose the full pi-ai model
|
|
5
|
-
// registry — the surface is curated to the
|
|
6
|
-
//
|
|
7
|
-
// the provider/model surface up).
|
|
6
|
+
// registry — the surface is curated to the ids the plugin commits to driving
|
|
7
|
+
// through a real ACP backend.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// Two curated sets live here, one per adapter on the rail
|
|
10
|
+
// (backend-adapter.ts): the unprefixed Claude ids (the reference backend) and
|
|
11
|
+
// the `cortex-` prefixed Snowflake Cortex Code ids (the second backend, landed
|
|
12
|
+
// 0.13.0 — docs/acp-backend-rail.md §6/§11-8). Codex is already a native garden
|
|
13
|
+
// citizen and Gemini/major tools use native, so neither is an ACP backend here.
|
|
14
|
+
// A third governed backend EXTENDS this file the same way — it does not change
|
|
12
15
|
// the pattern.
|
|
13
16
|
// pi 0.80 migration: the standalone root `getModels()` moved to the deprecated
|
|
14
17
|
// `@earendil-works/pi-ai/compat` entrypoint (the global-API churn). We import it
|
|
@@ -71,6 +74,62 @@ function requireRegistryModel(models, id) {
|
|
|
71
74
|
function claudeContextWindow(model) {
|
|
72
75
|
return Math.min(model.contextWindow, CLAUDE_CONTEXT_DEFAULT);
|
|
73
76
|
}
|
|
77
|
+
// ── Cortex (Snowflake Cortex Code) curated surface ──────────────────────────
|
|
78
|
+
//
|
|
79
|
+
// entwurf's ACP rail is backend-extensible: Cortex is the first NON-claude
|
|
80
|
+
// backend to land through it (docs/acp-backend-rail.md §6). pi-ai carries no
|
|
81
|
+
// snowflake/cortex model source, so this surface is HAND-CURATED. Every id
|
|
82
|
+
// carries the reserved `cortex-` prefix (§9-1): the prefix is the SINGLE routing
|
|
83
|
+
// authority (backend-adapter.ts `routeModel`) and keeps the ids off the Claude
|
|
84
|
+
// native ids Cortex routes to (`cortex-claude-sonnet-5` vs the unprefixed
|
|
85
|
+
// `claude-sonnet-5` the claude adapter owns). `routeModel` strips the prefix to
|
|
86
|
+
// recover the native id, which is enforced per-turn via
|
|
87
|
+
// session/set_config_option — never a launch `-m` pin (CP0-M).
|
|
88
|
+
//
|
|
89
|
+
// The 4-row set is the GLG-decided curation (2026-07-29) — a subset of the 11
|
|
90
|
+
// native ids cortex v1.1.52 exposed live when authenticated. A curated id that
|
|
91
|
+
// the running cortex no longer serves fails LOUD at set-model, before the
|
|
92
|
+
// prompt (measured: `Unsupported model: …` names the live set). Adding an id
|
|
93
|
+
// here is the same verify-both-axes commitment as the Claude set — do not
|
|
94
|
+
// extend casually.
|
|
95
|
+
export const CORTEX_MODEL_PREFIX = "cortex-";
|
|
96
|
+
export const SUPPORTED_CORTEX_MODEL_IDS = [
|
|
97
|
+
"cortex-auto",
|
|
98
|
+
"cortex-claude-opus-5",
|
|
99
|
+
"cortex-claude-sonnet-5",
|
|
100
|
+
"cortex-openai-gpt-5.4",
|
|
101
|
+
];
|
|
102
|
+
// Hand-set conservative context window. Cortex reports the LIVE window via its
|
|
103
|
+
// own ACP session config, not this curated metadata — this is a floor for the
|
|
104
|
+
// registry surface, deliberately not inflated to the Claude 1M.
|
|
105
|
+
const CORTEX_CONTEXT_DEFAULT = 200_000;
|
|
106
|
+
/**
|
|
107
|
+
* The curated Cortex model rows handed to the single `entwurf` provider via
|
|
108
|
+
* `allCuratedModels()`. The two Claude rows ride their OWN registry base
|
|
109
|
+
* (opus-5 / sonnet-5); `auto` (default family Claude) and the GPT row (no pi-ai
|
|
110
|
+
* source) ride the sonnet-5 base as a metadata floor. id / name / contextWindow
|
|
111
|
+
* are overridden; the `cortex-` prefix keeps them from colliding with the
|
|
112
|
+
* Claude curated ids. Same row shape as `curatedClaudeModels()` (AcpModelRow).
|
|
113
|
+
*/
|
|
114
|
+
export function curatedCortexModels() {
|
|
115
|
+
const sonnetBase = requireRegistryModel(ANTHROPIC_MODELS_ALL, "claude-sonnet-5");
|
|
116
|
+
const opusBase = requireRegistryModel(ANTHROPIC_MODELS_ALL, "claude-opus-5");
|
|
117
|
+
const row = (id, name, base) => ({
|
|
118
|
+
id,
|
|
119
|
+
name,
|
|
120
|
+
reasoning: base.reasoning,
|
|
121
|
+
input: base.input,
|
|
122
|
+
cost: base.cost,
|
|
123
|
+
contextWindow: CORTEX_CONTEXT_DEFAULT,
|
|
124
|
+
maxTokens: base.maxTokens,
|
|
125
|
+
});
|
|
126
|
+
return [
|
|
127
|
+
row("cortex-auto", "Cortex · Auto", sonnetBase),
|
|
128
|
+
row("cortex-claude-opus-5", "Cortex · Claude Opus 5", opusBase),
|
|
129
|
+
row("cortex-claude-sonnet-5", "Cortex · Claude Sonnet 5", sonnetBase),
|
|
130
|
+
row("cortex-openai-gpt-5.4", "Cortex · OpenAI GPT-5.4", sonnetBase),
|
|
131
|
+
];
|
|
132
|
+
}
|
|
74
133
|
/**
|
|
75
134
|
* The curated Claude model rows handed to `pi.registerProvider({ models })`.
|
|
76
135
|
* Fail-loud if the anchor is absent from the pi-ai registry.
|