@junghanacs/entwurf 0.35.0 → 0.36.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 -2
- package/CHANGELOG.md +338 -0
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +15 -0
- package/FAQ.md +3 -3
- package/README.md +30 -16
- package/VERIFY.md +14 -11
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +29 -3
- package/docs/setup-clean-host.md +31 -23
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +46 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/model-ids.js +67 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +9 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +4 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +11 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +27 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +2 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +99 -18
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +3 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +13 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +178 -25
- package/mcp/entwurf-bridge/dist/scripts/codex-socket-classify.js +44 -0
- package/mcp/entwurf-bridge/tsconfig.build.json +5 -0
- package/package.json +20 -15
- package/pi/pi-durable/bootstrap.mjs +12 -8
- package/pi/pi-durable/carrier/runtime.js +52 -2
- package/pi/pi-durable/carrier/subagent.js +6 -1
- package/pi/pi-durable/carrier/tui.js +31 -4
- package/pi/pi-durable/carrier-relocation.mjs +2 -2
- package/pi/pi-durable/overlay/upstream-pin.json +79 -36
- package/pi-extensions/lib/acp/backend-adapter.ts +52 -2
- package/pi-extensions/lib/acp/model-ids.js +77 -0
- package/pi-extensions/lib/acp/models.ts +15 -22
- package/pi-extensions/lib/codex-fresh-preflight.ts +4 -3
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +12 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +25 -1
- package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
- package/pi-extensions/lib/mux-fresh-call.ts +3 -0
- package/pi-extensions/lib/native-push/codex-ws-client.ts +129 -18
- package/pi-extensions/lib/pi-durable-runtime.ts +4 -4
- package/pi-extensions/lib/resume-launch-identity.ts +12 -4
- package/pi-extensions/meta-bridge-pi-durable.ts +230 -34
- package/run.sh +193 -58
- package/scripts/check-acp-backend-preflight.ts +10 -2
- package/scripts/check-acp-cortex.ts +7 -6
- package/scripts/check-acp-prompt-lifecycle.ts +2 -2
- package/scripts/check-acp-provider-surface.ts +1 -1
- package/scripts/check-acp-session-reuse.ts +5 -5
- package/scripts/check-acp-stop-reason.ts +2 -2
- package/scripts/check-acp-stream-hooks.ts +2 -2
- package/scripts/check-acp-usage-accounting.ts +2 -2
- package/scripts/check-codex-app-server-launch.ts +55 -12
- package/scripts/check-codex-native-push.ts +231 -12
- package/scripts/check-fresh-call-dispatch.ts +47 -4
- package/scripts/check-gate-qualification.ts +8 -6
- package/scripts/check-probe-ordering.ts +13 -6
- package/scripts/check-resume-launch-identity.ts +38 -0
- package/scripts/codex-app-server-launch.sh +54 -29
- package/scripts/codex-socket-classify.ts +46 -0
- package/scripts/emit-pi-durable-carrier.ts +159 -168
- package/scripts/mutants/acp-cortex.json +17 -8
- package/scripts/mutants/acp-set-model-readback.json +28 -0
- package/scripts/mutants/codex-app-server-launch.json +39 -12
- package/scripts/mutants/codex-native.json +15 -0
- package/scripts/mutants/control-send-receipt.json +1 -1
- package/scripts/mutants/fresh-call-dispatch.json +11 -0
- package/scripts/mutants/herdr-dev-checkout.json +70 -0
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/mutants/pi-durable.json +282 -16
- package/scripts/mutants/pi-floor.json +5 -5
- package/scripts/mutants/resume-launch-identity.json +15 -0
- package/scripts/mutants/v2-visible-resume.json +11 -0
- package/scripts/pi-durable-contact-host.mjs +1 -1
- package/scripts/resolve-acp-bridge.ts +1 -1
- package/scripts/smoke-acp-bundled-mcp-live.ts +1 -1
- package/scripts/smoke-acp-carrier-augment-live.ts +1 -1
- package/scripts/smoke-acp-long-turn-live.ts +1 -1
- package/scripts/smoke-acp-mcp-live.ts +1 -1
- package/scripts/smoke-acp-memory-containment-live.ts +1 -1
- package/scripts/smoke-acp-ordering-probe-live.ts +1 -1
- package/scripts/smoke-acp-overlay-live.ts +1 -1
- package/scripts/smoke-acp-provider-live.ts +1 -1
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-session-reuse-live.ts +1 -1
- package/scripts/smoke-acp-skill-live.ts +1 -1
- package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
- package/scripts/smoke-acp-v2-send-live.ts +1 -1
- package/scripts/smoke-entwurf-chain-live.ts +1 -1
- package/scripts/smoke-mux-lifecycle-live.ts +1 -1
- package/pi/pi-durable/overlay/runtime-contacts.patch +0 -283
package/demo/README.md
CHANGED
|
@@ -24,7 +24,7 @@ takes/helpers/tests are maintainer tooling and are deliberately excluded from th
|
|
|
24
24
|
|
|
25
25
|
- Node at the `package.json#engines.node` floor; the checkout's pinned dependencies and
|
|
26
26
|
current compiled bridge. After a source/bootstrap change, build with `pnpm run build-bridge`.
|
|
27
|
-
- Pi at the checkout's supported floor (**current floor 1.
|
|
27
|
+
- Pi at the checkout's supported floor (**current floor 1.2.0**); `entwurf` on PATH must be
|
|
28
28
|
the intended source stable-bin. Source evidence is not registry-package acceptance.
|
|
29
29
|
- `herdr` on PATH at exactly `scripts/fixtures/herdr-supply.json#version`; `asciinema` v3.
|
|
30
30
|
- Herdr's existing Pi integration at
|
package/docs/acp-backend-rail.md
CHANGED
|
@@ -134,7 +134,7 @@ ledger, 2026-10-03).
|
|
|
134
134
|
| Surface | Declaration | Class | What a green actually says |
|
|
135
135
|
|---|---|---|---|
|
|
136
136
|
| Entwurf package | `package.json` `version` | checkout declaration, not a release receipt | names the package label these rows sit beside; whether that label shipped, and with which rows, is CHANGELOG's record |
|
|
137
|
-
| pi runtime | SDK exact `1.
|
|
137
|
+
| pi runtime | SDK exact `1.2.0`, host `>=1.2.0 <1.3` | **exact** oracle + **closed range** | the SDK set this checkout builds, gates and (since #130) ships as exact production dependencies; an ordinary Pi host inside the range is accepted, and the ceiling moves only on measurement. Unshipped candidate: #141's 1.2.0 move has its own acceptance still owed. Historical: the published 0.33.0 through 0.35.1 ship SDK exact 1.1.0, 0.32.0 ships SDK exact 1.0.4 with the next-minor ceiling (ROADMAP's 2026-10-07 bump entry), and 0.31.0 declared the earlier peer floor 1.0.2 |
|
|
138
138
|
| ACP wire SDK | `@agentclientprotocol/sdk 1.6.0` | **exact** | the shared wire oracle both adapters speak |
|
|
139
139
|
| Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.85.1` | **exact**, bundled | checkout candidate; resolved before any PATH fallback; declaration is not compatibility evidence |
|
|
140
140
|
| Claude Agent SDK | `0.3.286` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
|
|
@@ -379,9 +379,9 @@ operator's repair named.
|
|
|
379
379
|
|
|
380
380
|
| Seam | Claude | Cortex Code |
|
|
381
381
|
|---|---|---|
|
|
382
|
-
| Model ids | unprefixed `claude-sonnet-5`, `claude-opus-5`, `claude-fable-5-1` | `cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, `cortex-openai-gpt-5.4`; prefix stripped before set-model |
|
|
382
|
+
| Model ids | unprefixed `claude-sonnet-5-5`, `claude-opus-5-5`, `claude-fable-5-1` | `cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, `cortex-openai-gpt-5.4`; prefix stripped before set-model |
|
|
383
383
|
| Launch | bundled `claude-agent-acp`; `CLAUDE_AGENT_ACP_COMMAND` override | `cortex acp serve`; optional connection; `CORTEX_ACP_COMMAND` override; never a launch-time `-m` |
|
|
384
|
-
| Model authority | per-turn ACP set-model | per-turn ACP set-model; an unavailable curated id fails before prompt |
|
|
384
|
+
| Model authority | per-turn ACP set-model, its answer read back before the prompt | per-turn ACP set-model; an unavailable curated id fails before prompt; the answer is not read back |
|
|
385
385
|
| Carrier | engraving in `_meta.systemPrompt` | no system-prompt carrier; engraving rides the first-user augment |
|
|
386
386
|
| Overlay | `CLAUDE_CONFIG_DIR` whitelist, configured-empty hooks, native memory hidden | session-scoped isolated HOME + `SNOWFLAKE_HOME`, private `cortex/mcp.json`, measured-minimum auth passthrough |
|
|
387
387
|
| MCP | explicit wire `mcpServers` | explicit declarations projected to private `mcp.json` because Cortex ignores the wire field |
|
|
@@ -390,6 +390,32 @@ operator's repair named.
|
|
|
390
390
|
|
|
391
391
|
### Claude
|
|
392
392
|
|
|
393
|
+
The unprefixed Claude curation uses exact Opus/Sonnet 5.5 ids; the ACP adapter does
|
|
394
|
+
not route the retired exact ids `claude-opus-5` or `claude-sonnet-5`, alias them to
|
|
395
|
+
a newer model, or rewrite existing session records. Cortex retains its separate
|
|
396
|
+
native 5 ids. Pi owns fuzzy CLI model selection before the adapter: with this
|
|
397
|
+
seven-row set, its 1.1.0 resolver matches the old bare 5 patterns to the Cortex rows
|
|
398
|
+
instead of refusing them (measured with the pure upstream resolver and synthetic rows,
|
|
399
|
+
not a vendor turn). So the launches Entwurf builds admit only an EXACT curated id
|
|
400
|
+
(`pi-extensions/lib/acp/model-ids.js`): `entwurf_fresh_call` with backend `pi` and an
|
|
401
|
+
`entwurf/<id>` model, and `entwurf_resume_call` of a session whose emitted provider is
|
|
402
|
+
`entwurf`, refuse anything else as `acp-model-not-curated` before a pane opens. A human
|
|
403
|
+
typing `pi --model …` keeps the upstream fuzzy behaviour, so use the full 5.5 ids there.
|
|
404
|
+
|
|
405
|
+
The adapter reads its set-model answer back before the prompt. The driver resolves the
|
|
406
|
+
requested id onto one of the rows it offers and answers with that row's `value` as the
|
|
407
|
+
model option's `currentValue` (0.85.1 `dist/acp-agent.js:5459-5499`). Measured on
|
|
408
|
+
2026-10-12 against 0.85.1 with no prompt sent: `claude-fable-5-1` answers its own row,
|
|
409
|
+
while `claude-sonnet-5-5` and `claude-opus-5-5` answer the alias rows `sonnet` and
|
|
410
|
+
`opus`, named `Sonnet 5.5` and `Opus 5.5`. The id an alias row resolves to is not on the
|
|
411
|
+
ACP wire, so `CLAUDE_SET_MODEL_ALIAS_ROWS` (`backend-adapter.ts`) declares, for each
|
|
412
|
+
alias-answered id, the one measured row it is accepted as, value and name together. A
|
|
413
|
+
missing model option, another row, or any other value/name pair refuses the turn before
|
|
414
|
+
the prompt, with text pi does not classify as transient. A curated id added later needs
|
|
415
|
+
its own measured answer before it ships. `check-acp-set-model-readback` holds the
|
|
416
|
+
contract offline; it does not certify the installed driver's runtime, and no 5.5 prompt
|
|
417
|
+
turn is claimed by this measurement.
|
|
418
|
+
|
|
393
419
|
The bundled adapter resolves before any PATH fallback. Its overlay retains only the
|
|
394
420
|
auth/runtime state required by the Claude Agent SDK and hides operator memory, hooks,
|
|
395
421
|
agents, history, and local settings. Rich project/operator context rides the first-user
|
package/docs/setup-clean-host.md
CHANGED
|
@@ -28,8 +28,8 @@ evidence split, and its three states must not collapse into "macOS is supported"
|
|
|
28
28
|
| npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
|
|
29
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 |
|
|
30
30
|
| entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
|
|
31
|
-
| pi | optional-by-presence, `>=1.
|
|
32
|
-
| Native durable (`pi-durable`) | supplied by the Entwurf package (#130):
|
|
31
|
+
| pi | optional-by-presence, `>=1.2.0 <1.3` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
|
|
32
|
+
| Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus the exact 1.2.0 pi SDK set as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
|
|
33
33
|
| Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
|
|
34
34
|
| 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 |
|
|
35
35
|
| 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) |
|
|
@@ -209,7 +209,7 @@ on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
|
|
|
209
209
|
Install the exact release floor, then wire the project:
|
|
210
210
|
|
|
211
211
|
```bash
|
|
212
|
-
npm install -g @earendil-works/pi-coding-agent@1.
|
|
212
|
+
npm install -g @earendil-works/pi-coding-agent@1.2.0
|
|
213
213
|
pi --version
|
|
214
214
|
|
|
215
215
|
cd ~/entwurf-smoke
|
|
@@ -217,7 +217,7 @@ entwurf install .
|
|
|
217
217
|
pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
|
|
218
218
|
```
|
|
219
219
|
|
|
220
|
-
The supported range is `>=1.
|
|
220
|
+
The supported range is `>=1.2.0 <1.3`. It is a hard minimum: installing this
|
|
221
221
|
release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
|
|
222
222
|
minor. A host using only the external MCP bridge can skip pi until it needs a
|
|
223
223
|
control socket; no delivery rail launches a pi process.
|
|
@@ -313,23 +313,22 @@ by `entwurf pi` or by registering ordinary Pi's adapter.
|
|
|
313
313
|
What the Entwurf package supplies:
|
|
314
314
|
|
|
315
315
|
1. **The pi SDK set** — `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
|
|
316
|
-
pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry}` at exactly `1.
|
|
316
|
+
pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry}` at exactly `1.2.0`, plus
|
|
317
317
|
`proper-lockfile`, as Entwurf's own production dependencies (declared once in
|
|
318
318
|
`pi/pi-durable/overlay/upstream-pin.json` `sdk`). Model data rides the published
|
|
319
319
|
`pi-ai`; there is no catalog copy.
|
|
320
320
|
2. **The native app/TUI carrier** — upstream does not publish the experimental durable
|
|
321
321
|
app (`pi-coding-agent` excludes `dist/experimental`; `pi-durable` on npm is the
|
|
322
|
-
library). Entwurf
|
|
323
|
-
|
|
324
|
-
applied to `runtime.ts`, type-stripped and relocated, in `pi/pi-durable/carrier/`
|
|
322
|
+
library). Entwurf owns the six app files as canonical source (`pi/pi-durable/src`,
|
|
323
|
+
first taken from upstream tag `v1.1.0` `abe508e1b89912adde45528136c3221eb69acdd7`) and ships them type-stripped and relocated in `pi/pi-durable/carrier/`
|
|
325
324
|
with upstream's MIT license. Each file's source blob and emitted digest are in the pin.
|
|
326
325
|
The carrier reaches pi-coding-agent's internals only through its resolver
|
|
327
326
|
(`pi/pi-durable/carrier-resolver.mjs`), which maps them to the published package's
|
|
328
327
|
own `dist/` as installed — never through a source checkout.
|
|
329
328
|
|
|
330
|
-
The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.
|
|
331
|
-
and [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
|
|
332
|
-
describe what the carrier is. Running the bare upstream app does not attach Entwurf's
|
|
329
|
+
The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.2.0/packages/durable/README.md)
|
|
330
|
+
and the [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
|
|
331
|
+
(last published at v1.1.0) describe what the carrier is. Running the bare upstream app does not attach Entwurf's
|
|
333
332
|
garden contact. Entwurf does not rebuild the controller/model/auth/storage system or
|
|
334
333
|
proxy a subscription; native execution, models, auth, queues, SQLite and recovery stay
|
|
335
334
|
upstream-owned.
|
|
@@ -411,7 +410,7 @@ tree (for example an override that binds pi-durable's `pi-ai` to an older publis
|
|
|
411
410
|
manifest field; it is a known constraint of supplying both from one package, accepted for 0.32,
|
|
412
411
|
not a claim that every load path is harmless.
|
|
413
412
|
|
|
414
|
-
The ordinary Pi CLI on PATH is a separate subject (§2, range `>=1.
|
|
413
|
+
The ordinary Pi CLI on PATH is a separate subject (§2, range `>=1.2.0 <1.3`): a host Pi is
|
|
415
414
|
not needed to run pi-durable, and a host inside the range need not be the same copy as the
|
|
416
415
|
carrier's. Native Pi owns credentials, model settings and durable SQLite sessions; keep them
|
|
417
416
|
in place rather than copying credentials or migrating ordinary Pi transcripts. Native
|
|
@@ -424,19 +423,28 @@ proof ([#130](https://github.com/junghan0611/entwurf/issues/130)).
|
|
|
424
423
|
|
|
425
424
|
### Maintainer: re-emitting the carrier
|
|
426
425
|
|
|
427
|
-
Only a maintainer
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
426
|
+
Only a maintainer changing the pi-durable app runs `scripts/emit-pi-durable-carrier.ts`. Its
|
|
427
|
+
input is Entwurf's own source in this repository (`pi/pi-durable/src`, #141, typechecked by
|
|
428
|
+
`pi/pi-durable/tsconfig.json` against the installed SDK's declarations): it type-strips with
|
|
429
|
+
Node's own strip-only TypeScript, relocates, and checks that the six files import exactly the
|
|
430
|
+
pin's declared `distTargets`:
|
|
432
431
|
|
|
433
432
|
```bash
|
|
434
|
-
node scripts/emit-pi-durable-carrier.ts --
|
|
435
|
-
node scripts/emit-pi-durable-carrier.ts --
|
|
433
|
+
node scripts/emit-pi-durable-carrier.ts --write # names every generated field it changes
|
|
434
|
+
node scripts/emit-pi-durable-carrier.ts --check # rebuild equality
|
|
435
|
+
node scripts/emit-pi-durable-carrier.ts --origin <clone holding origin.commit> # provenance only
|
|
436
436
|
```
|
|
437
437
|
|
|
438
|
-
|
|
439
|
-
|
|
438
|
+
A missing source file is a named refusal, never a skip. The byte comparison also runs as a
|
|
439
|
+
beside claim on every floor. The emitter's Node is part of the pin: under another Node,
|
|
440
|
+
`--check` compares the bytes and then reports the identity as a named SKIP, never rebuild
|
|
441
|
+
acceptance; the CI `check` job runs the pinned Node with `ENTWURF_REQUIRE_EMITTER_NODE=1`, where
|
|
442
|
+
a mismatch is red. `--origin` checks the frozen `origin` record against the upstream objects
|
|
443
|
+
it names (`<commit>:<path>`, `--no-replace-objects`) and never feeds a rebuild.
|
|
444
|
+
|
|
445
|
+
The owned source is formatted at upstream width (`biome.json` override, indentWidth 3), with no
|
|
446
|
+
per-file exception. A formatting change to an owned file changes its emitted bytes. So it is
|
|
447
|
+
re-emitted with `--write`, and its generated digest is reviewed like any other carrier change.
|
|
440
448
|
|
|
441
449
|
## 3. Optional Claude Code native citizen
|
|
442
450
|
|
|
@@ -601,7 +609,7 @@ as a note rather than a fault.
|
|
|
601
609
|
|
|
602
610
|
**entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
|
|
603
611
|
presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
|
|
604
|
-
gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.
|
|
612
|
+
gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.2.0 <1.3`) and Claude Code
|
|
605
613
|
(`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
|
|
606
614
|
decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
|
|
607
615
|
earned one because an older binary validates the exec manifest, drops `args` at runtime, and
|
|
@@ -913,7 +921,7 @@ operator-installed Claude runtime. Resolution order:
|
|
|
913
921
|
2. `require.resolve(...)` against the bundled `@agentclientprotocol/claude-agent-acp`.
|
|
914
922
|
3. `PATH:claude-agent-acp` fallback if package resolution fails.
|
|
915
923
|
|
|
916
|
-
The curated Claude ids are `claude-opus-5`, `claude-sonnet-5`, and
|
|
924
|
+
The curated Claude ids are `claude-opus-5-5`, `claude-sonnet-5-5`, and
|
|
917
925
|
`claude-fable-5-1`. Cortex exposes `cortex-auto`, `cortex-claude-opus-5`,
|
|
918
926
|
`cortex-claude-sonnet-5`, and `cortex-openai-gpt-5.4`.
|
|
919
927
|
`entwurfProvider.cortexConnection` or `ENTWURF_ACP_CORTEX_CONNECTION` selects
|
|
@@ -110,6 +110,44 @@ function resolveClaudeLaunch() {
|
|
|
110
110
|
* ambient value cannot turn it off. Cortex declares nothing of the kind.
|
|
111
111
|
*/
|
|
112
112
|
export const CLAUDE_FOREGROUND_ONLY_ENV = { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" };
|
|
113
|
+
/**
|
|
114
|
+
* The set-model answer is read back before the prompt (#128). claude-agent-acp resolves the
|
|
115
|
+
* requested id onto one of the rows it offers and answers with that row's `value` as the model
|
|
116
|
+
* option's `currentValue` (0.85.1 `dist/acp-agent.js:5459-5499`). `[측정 2026-10-12]` 0.85.1, no
|
|
117
|
+
* prompt: `claude-fable-5-1` answers its own row, while `claude-sonnet-5-5` and `claude-opus-5-5`
|
|
118
|
+
* answer the alias rows `sonnet` and `opus`, named `Sonnet 5.5` and `Opus 5.5`. The id an alias row
|
|
119
|
+
* resolves to (`ModelInfo.resolvedModel`) is not on the ACP wire, so an alias answer is accepted
|
|
120
|
+
* only as the exact measured row, value AND name, declared here for the requested id. No other
|
|
121
|
+
* spelling is matched.
|
|
122
|
+
*/
|
|
123
|
+
export const CLAUDE_SET_MODEL_ALIAS_ROWS = Object.freeze({
|
|
124
|
+
"claude-sonnet-5-5": Object.freeze({ value: "sonnet", name: "Sonnet 5.5" }),
|
|
125
|
+
"claude-opus-5-5": Object.freeze({ value: "opus", name: "Opus 5.5" }),
|
|
126
|
+
});
|
|
127
|
+
/** Refuse unless the set-model answer names the requested model. A missing model option, another
|
|
128
|
+
* row, or a row that is not the measured alias row would let a different model answer under this id. */
|
|
129
|
+
export function confirmClaudeSetModelAnswer(response, nativeModelId) {
|
|
130
|
+
const configOptions = response?.configOptions;
|
|
131
|
+
const modelOption = Array.isArray(configOptions)
|
|
132
|
+
? configOptions.find((option) => option?.id === "model")
|
|
133
|
+
: undefined;
|
|
134
|
+
const currentValue = modelOption?.currentValue;
|
|
135
|
+
if (typeof currentValue === "string" && currentValue === nativeModelId)
|
|
136
|
+
return;
|
|
137
|
+
const rows = Array.isArray(modelOption?.options)
|
|
138
|
+
? modelOption.options.flatMap((row) => Array.isArray(row?.options) ? row.options : [row])
|
|
139
|
+
: [];
|
|
140
|
+
const selected = typeof currentValue === "string" ? rows.find((row) => row?.value === currentValue) : undefined;
|
|
141
|
+
const alias = CLAUDE_SET_MODEL_ALIAS_ROWS[nativeModelId];
|
|
142
|
+
if (alias !== undefined && currentValue === alias.value && selected?.name === alias.name)
|
|
143
|
+
return;
|
|
144
|
+
const answered = modelOption === undefined
|
|
145
|
+
? "no model option"
|
|
146
|
+
: `model ${jsonText(currentValue)} named ${jsonText(selected?.name ?? null)}`;
|
|
147
|
+
throw new Error(`entwurf: claude-agent-acp answered set-model ${JSON.stringify(nativeModelId)} with ${answered}, which does not ` +
|
|
148
|
+
`name the requested model. Refusing before the prompt so a different model cannot answer under this id; ` +
|
|
149
|
+
`the installed driver and the curated id disagree, and repeating the turn cannot change that.`);
|
|
150
|
+
}
|
|
113
151
|
export const claudeAdapter = {
|
|
114
152
|
backend: "claude",
|
|
115
153
|
// Claude owns its UNPREFIXED curated ids only (rail “Adapter contract”). The native id
|
|
@@ -161,7 +199,12 @@ export const claudeAdapter = {
|
|
|
161
199
|
if (typeof setConfig !== "function") {
|
|
162
200
|
throw new Error(`setSessionConfigOption unsupported — cannot enforce model ${modelId}`);
|
|
163
201
|
}
|
|
164
|
-
await setConfig.call(connection, {
|
|
202
|
+
const answer = await setConfig.call(connection, {
|
|
203
|
+
sessionId: acpSessionId,
|
|
204
|
+
configId: "model",
|
|
205
|
+
value: nativeModelId,
|
|
206
|
+
});
|
|
207
|
+
confirmClaudeSetModelAnswer(answer, nativeModelId);
|
|
165
208
|
},
|
|
166
209
|
configSignatureFields(_adapterSettings) {
|
|
167
210
|
// Claude folds no extra backend-specific fields beyond backend + nativeModelId
|
|
@@ -302,7 +345,8 @@ export const cortexAdapter = {
|
|
|
302
345
|
// serves fails loud BEFORE the prompt (`Unsupported model: …`). PR #40's
|
|
303
346
|
// launch-time `-m` pin was retired for this: set-model is the single model
|
|
304
347
|
// authority (resolveLaunch never passes `-m`), and "auto" is set explicitly
|
|
305
|
-
// rather than treated as an unspoken default.
|
|
348
|
+
// rather than treated as an unspoken default. NO answer readback, unlike claude: nobody has
|
|
349
|
+
// measured what cortex's set_config_option response carries, so a check would be a guess.
|
|
306
350
|
async enforceModel({ connection, acpSessionId, nativeModelId, modelId }) {
|
|
307
351
|
const setConfig = connection.setSessionConfigOption;
|
|
308
352
|
if (typeof setConfig !== "function") {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// ACP plugin — the curated model IDs, and nothing else (#128).
|
|
2
|
+
//
|
|
3
|
+
// This leaf imports NOTHING. `models.ts` builds the registry rows from the pi-ai
|
|
4
|
+
// catalog; the launch seams (`fresh-call-dispatch.ts`, `resume-launch-identity.ts`)
|
|
5
|
+
// only need to know which IDs exist, and they ride the pi-free bridge closure, so
|
|
6
|
+
// they read the IDs here rather than load a vendor catalog to learn them.
|
|
7
|
+
//
|
|
8
|
+
// WHY `.js`: the same reason as `protocol.js`. Both the emit-capable root program
|
|
9
|
+
// (allowJs) and Node --experimental-strip-types resolve an explicit `.js` import
|
|
10
|
+
// literally to this file, so `models.ts` (root fence, `.js` specifiers) and the
|
|
11
|
+
// strip-types gates that load `models.ts` directly reach one SSOT. A `.ts` leaf
|
|
12
|
+
// would need a `.ts` specifier the root config cannot allow.
|
|
13
|
+
//
|
|
14
|
+
// WHY THE LAUNCH SEAMS ASK AT ALL. Pi's `--model` resolver falls back to a
|
|
15
|
+
// case-insensitive substring match among the provider's rows and prefers the
|
|
16
|
+
// lexically later id. Under one `entwurf` provider a retired `claude-sonnet-5`
|
|
17
|
+
// is a substring of `cortex-claude-sonnet-5`, so a launch Entwurf builds would
|
|
18
|
+
// silently open on the Snowflake Cortex rail instead of the Claude subscription.
|
|
19
|
+
// An exact match against this list is the only admission; nothing is aliased,
|
|
20
|
+
// substituted or rewritten.
|
|
21
|
+
/** The single ACP provider id under which every curated row is registered. */
|
|
22
|
+
export const PROVIDER_ID = "entwurf";
|
|
23
|
+
// The curated Claude ids. Adding one here is a commitment to verify it across
|
|
24
|
+
// both axes (protocol smoke + agent interview) — do not extend casually.
|
|
25
|
+
export const SUPPORTED_ANTHROPIC_MODEL_IDS = /** @type {const} */ ([
|
|
26
|
+
"claude-sonnet-5-5",
|
|
27
|
+
"claude-opus-5-5",
|
|
28
|
+
"claude-fable-5-1",
|
|
29
|
+
]);
|
|
30
|
+
/** The anchor model whose absence is a hard registry regression, not a soft skip. */
|
|
31
|
+
export const CURATED_ANCHOR_MODEL_ID = "claude-opus-5-5";
|
|
32
|
+
// Cortex ids carry the reserved `cortex-` prefix; the rest of each id is the
|
|
33
|
+
// Snowflake-native model name, which is why Cortex still says `claude-opus-5`.
|
|
34
|
+
export const CORTEX_MODEL_PREFIX = "cortex-";
|
|
35
|
+
export const SUPPORTED_CORTEX_MODEL_IDS = /** @type {const} */ ([
|
|
36
|
+
"cortex-auto",
|
|
37
|
+
"cortex-claude-opus-5",
|
|
38
|
+
"cortex-claude-sonnet-5",
|
|
39
|
+
"cortex-openai-gpt-5.4",
|
|
40
|
+
]);
|
|
41
|
+
/** @type {ReadonlySet<string>} */
|
|
42
|
+
const CURATED_ACP_MODEL_IDS = new Set([...SUPPORTED_ANTHROPIC_MODEL_IDS, ...SUPPORTED_CORTEX_MODEL_IDS]);
|
|
43
|
+
/**
|
|
44
|
+
* Exact membership only — no trim, no case folding, no prefix.
|
|
45
|
+
* @param {string} model
|
|
46
|
+
* @returns {boolean}
|
|
47
|
+
*/
|
|
48
|
+
export function isCuratedAcpModelId(model) {
|
|
49
|
+
return CURATED_ACP_MODEL_IDS.has(model);
|
|
50
|
+
}
|
|
51
|
+
/** The named refusal both launch seams answer with. */
|
|
52
|
+
export const ACP_MODEL_NOT_CURATED = /** @type {const} */ ("acp-model-not-curated");
|
|
53
|
+
export const ACP_MODEL_NOT_CURATED_HINT = `an ${PROVIDER_ID} model must be one exact curated ACP id (${[...CURATED_ACP_MODEL_IDS].join(", ")}); ` +
|
|
54
|
+
"Pi would otherwise pick another row by substring — a retired claude-sonnet-5 lands on cortex-claude-sonnet-5 — " +
|
|
55
|
+
"so nothing was opened, and nothing is aliased or rewritten";
|
|
56
|
+
/** Carries `reason` so callers match a FIELD, never the wording. */
|
|
57
|
+
export class AcpModelNotCuratedError extends Error {
|
|
58
|
+
/** @param {string} model */
|
|
59
|
+
constructor(model) {
|
|
60
|
+
super(`${ACP_MODEL_NOT_CURATED}: ${JSON.stringify(model)} is not an exact curated ${PROVIDER_ID} model id`);
|
|
61
|
+
this.name = "AcpModelNotCuratedError";
|
|
62
|
+
/** @readonly */
|
|
63
|
+
this.reason = ACP_MODEL_NOT_CURATED;
|
|
64
|
+
/** @readonly */
|
|
65
|
+
this.model = model;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -32,8 +32,11 @@
|
|
|
32
32
|
// re-export of `getBuiltinModels`. When pi removes compat we migrate to whatever
|
|
33
33
|
// the loader then exposes.
|
|
34
34
|
import { getModels } from "@earendil-works/pi-ai/compat";
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
import { CURATED_ANCHOR_MODEL_ID, SUPPORTED_ANTHROPIC_MODEL_IDS } from "./model-ids.js";
|
|
36
|
+
// The provider id and the curated ID lists live in the dependency-free
|
|
37
|
+
// `model-ids.js` leaf, shared with the launch seams (#128); this file is the only
|
|
38
|
+
// one that loads the upstream catalog to turn them into registry rows.
|
|
39
|
+
export { CORTEX_MODEL_PREFIX, CURATED_ANCHOR_MODEL_ID, PROVIDER_ID, SUPPORTED_ANTHROPIC_MODEL_IDS, SUPPORTED_CORTEX_MODEL_IDS, } from "./model-ids.js";
|
|
37
40
|
// #26 auth-boundary sentinel. `pi.registerProvider` requires an apiKey when a
|
|
38
41
|
// provider defines custom models, but the ACP plugin consumes NO key: backend
|
|
39
42
|
// auth belongs to the operator's own Claude CLI child process (AGENTS
|
|
@@ -45,15 +48,8 @@ export const PROVIDER_ID = "entwurf";
|
|
|
45
48
|
// — that silences the warning but keeps the wrong auth-boundary shape. The
|
|
46
49
|
// check-auth-boundary gate pins this.
|
|
47
50
|
export const ENTWURF_ACP_NO_AUTH_SENTINEL = "entwurf-no-auth";
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// Exported so the claude backend adapter (backend-adapter.ts) can answer
|
|
51
|
-
// `routeModel` without re-deriving the set from curatedClaudeModels().
|
|
52
|
-
export const SUPPORTED_ANTHROPIC_MODEL_IDS = ["claude-sonnet-5", "claude-opus-5", "claude-fable-5-1"];
|
|
53
|
-
/** The anchor model whose absence is a hard registry regression, not a soft skip. */
|
|
54
|
-
export const CURATED_ANCHOR_MODEL_ID = "claude-opus-5";
|
|
55
|
-
// Anthropic's registry reports 1M for Sonnet 5, Opus 5 and Fable 5.1, and the
|
|
56
|
-
// entwurf surface exposes the full 1M for all three. Sonnet 5's 1M window is the
|
|
51
|
+
// Anthropic's registry reports 1M for Sonnet 5.5, Opus 5.5 and Fable 5.1, and the
|
|
52
|
+
// entwurf surface exposes the full 1M for all three. Sonnet 5's 1M window was the
|
|
57
53
|
// whole point of the 0.12.3 bump — it is the compact-free long-context floor the
|
|
58
54
|
// earlier 200K Sonnet cap could not provide. We still clamp to a 1M ceiling so a
|
|
59
55
|
// future registry value can't silently inflate the surface past what we verify.
|
|
@@ -82,7 +78,7 @@ function claudeContextWindow(model) {
|
|
|
82
78
|
// carries the reserved `cortex-` prefix: the prefix is the SINGLE routing
|
|
83
79
|
// authority (backend-adapter.ts `routeModel`) and keeps the ids off the Claude
|
|
84
80
|
// 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
|
|
81
|
+
// `claude-sonnet-5-5` the claude adapter owns). `routeModel` strips the prefix to
|
|
86
82
|
// recover the native id, which is enforced per-turn via
|
|
87
83
|
// session/set_config_option — never a launch `-m` pin (CP0-M).
|
|
88
84
|
//
|
|
@@ -91,14 +87,7 @@ function claudeContextWindow(model) {
|
|
|
91
87
|
// the running cortex no longer serves fails LOUD at set-model, before the
|
|
92
88
|
// prompt (measured: `Unsupported model: …` names the live set). Adding an id
|
|
93
89
|
// 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
|
-
];
|
|
90
|
+
// extend casually. The prefix and the four ids are declared in model-ids.js.
|
|
102
91
|
// Hand-set conservative context window. Cortex reports the LIVE window via its
|
|
103
92
|
// own ACP session config, not this curated metadata — this is a floor for the
|
|
104
93
|
// registry surface, deliberately not inflated to the Claude 1M.
|
|
@@ -549,10 +549,11 @@ export async function codexFreshPreflight(env, deps = {}) {
|
|
|
549
549
|
return "codex-mcp-hand-missing";
|
|
550
550
|
if (visibleIdentityMissing(config))
|
|
551
551
|
return "codex-visible-identity-missing";
|
|
552
|
-
const
|
|
553
|
-
const socket = (deps.checkSocket ?? checkCodexSocketFile)(socketPath);
|
|
552
|
+
const socket = (deps.checkSocket ?? checkCodexSocketFile)(resolveCodexDefaultSocketPath(env));
|
|
554
553
|
if (!socket.ok)
|
|
555
554
|
return "codex-app-server-unavailable";
|
|
556
|
-
|
|
555
|
+
// The protocol exchange goes to the endpoint the check verified, never the advertised path
|
|
556
|
+
// again (on Codex 0.162.1 that path is an alias; see `checkCodexSocketFile`).
|
|
557
|
+
const live = await appServerIsLive(socket.connectPath, deps.openProtocol ?? realCodexProtocolOpener, deps.appServerTimeoutMs ?? DEFAULT_APP_SERVER_TIMEOUT_MS);
|
|
557
558
|
return live ? null : "codex-app-server-unavailable";
|
|
558
559
|
}
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
* a resume runs no turn (measured — the transcript stayed byte-identical), so there is no turn
|
|
47
47
|
* outcome for this module to claim.
|
|
48
48
|
*/
|
|
49
|
+
import { ACP_MODEL_NOT_CURATED_HINT, AcpModelNotCuratedError } from "./acp/model-ids.js";
|
|
49
50
|
import { buildResumePiArgs } from "./entwurf-resume-args.js";
|
|
50
51
|
import { ENTWURF_V2_LOCK_DIR, acquireLock as realAcquireLock, releaseLock as realReleaseLock, } from "./entwurf-v2-lock.js";
|
|
51
52
|
import { ResumeBackendUnsupportedError, resolveResumeLaunchIdentity, } from "./resume-launch-identity.js";
|
|
@@ -129,6 +130,15 @@ export async function visibleResume(targetGardenId, deps) {
|
|
|
129
130
|
identity = deps.resolveIdentity(targetGardenId);
|
|
130
131
|
}
|
|
131
132
|
catch (err) {
|
|
133
|
+
// #128: the recorded model is not an exact curated ACP id, so Pi would pick another row
|
|
134
|
+
// by substring. Same shape as `target-not-pi`: a named refusal matched on its FIELD.
|
|
135
|
+
if (err instanceof AcpModelNotCuratedError) {
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
reason: err.reason,
|
|
139
|
+
detail: `${targetGardenId} would launch model ${JSON.stringify(err.model)} under the entwurf provider (the resolved launch pair, which may differ from the recorded provider)`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
132
142
|
if (err instanceof ResumeBackendUnsupportedError) {
|
|
133
143
|
return {
|
|
134
144
|
ok: false,
|
|
@@ -210,6 +220,7 @@ export async function observeSameGidSocket(targetGardenId, socketPath, deps, tim
|
|
|
210
220
|
const REJECT_HINT = {
|
|
211
221
|
"target-invalid": "no citizen has that id; discover targets with entwurf_peers",
|
|
212
222
|
"target-not-pi": "that citizen's backend has no same-id resume — reach it with entwurf_v2, or open a fresh sibling",
|
|
223
|
+
"acp-model-not-curated": ACP_MODEL_NOT_CURATED_HINT,
|
|
213
224
|
"target-locked": "another resume or dispatch holds this citizen's lock right now",
|
|
214
225
|
"target-live": "that citizen is already running — this verb only reopens dormant ones",
|
|
215
226
|
"target-indeterminate": "the socket probe could not prove the citizen is dormant, so nothing was started",
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
* the one address path, on both rails.
|
|
25
25
|
*/
|
|
26
26
|
import { spawn as spawnChildProcess } from "node:child_process";
|
|
27
|
+
import { ACP_MODEL_NOT_CURATED, isCuratedAcpModelId, PROVIDER_ID } from "./acp/model-ids.js";
|
|
27
28
|
import { codexCallerFreshPreflight, codexFreshPreflight } from "./codex-fresh-preflight.js";
|
|
28
|
-
import { mintNonce } from "./fresh-call-composition.js";
|
|
29
|
+
import { isSafeFreshCallModel, mintNonce } from "./fresh-call-composition.js";
|
|
29
30
|
import { createHerdrRunner, herdrFreshCall, renderHerdrFreshCall, resolveHerdrContext, } from "./herdr-fresh-call.js";
|
|
30
31
|
import { freshCall, renderFreshCall } from "./mux-fresh-call.js";
|
|
31
32
|
/** herdr's own marker, read for its EXACT value. `[측정 2026-09-14]` herdr exports `HERDR_ENV=1`
|
|
@@ -33,6 +34,25 @@ import { freshCall, renderFreshCall } from "./mux-fresh-call.js";
|
|
|
33
34
|
export function selectFreshCallRail(env) {
|
|
34
35
|
return env.HERDR_ENV === "1" ? "herdr" : "tmux";
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* #128: a pi sibling asked for under the `entwurf` provider must name an EXACT curated ACP id. Pi
|
|
39
|
+
* resolves `--model` by substring among that provider's rows, so a retired `entwurf/claude-sonnet-5`
|
|
40
|
+
* would open on `cortex-claude-sonnet-5` — another vendor and billing rail — with a receipt that
|
|
41
|
+
* still names the requested id. Asked here, at the one root both rails share, so neither rail can
|
|
42
|
+
* forget it. It reads the model the way the rails will (trimmed), and only a model that already
|
|
43
|
+
* passes their grammar: a malformed one keeps its own `model-invalid` answer from the rail.
|
|
44
|
+
*/
|
|
45
|
+
export function isUncuratedEntwurfPiModel(backend, model) {
|
|
46
|
+
if (backend !== "pi")
|
|
47
|
+
return false;
|
|
48
|
+
const trimmed = model.trim();
|
|
49
|
+
if (!isSafeFreshCallModel(trimmed))
|
|
50
|
+
return false;
|
|
51
|
+
const slash = trimmed.indexOf("/");
|
|
52
|
+
if (slash < 0 || trimmed.slice(0, slash) !== PROVIDER_ID)
|
|
53
|
+
return false;
|
|
54
|
+
return !isCuratedAcpModelId(trimmed.slice(slash + 1));
|
|
55
|
+
}
|
|
36
56
|
/**
|
|
37
57
|
* Open a sibling on whichever rail this process is actually standing in.
|
|
38
58
|
*
|
|
@@ -49,6 +69,12 @@ export function selectFreshCallRail(env) {
|
|
|
49
69
|
* them rather than inheriting a rule from a topology it does not share.
|
|
50
70
|
*/
|
|
51
71
|
export async function dispatchFreshCall(request, env = process.env, spawn, nonce = mintNonce()) {
|
|
72
|
+
// Before either rail is asked anything: both would open a pane on the wrong row.
|
|
73
|
+
if (isUncuratedEntwurfPiModel(request.backend, request.model)) {
|
|
74
|
+
return selectFreshCallRail(env) === "herdr"
|
|
75
|
+
? { rail: "herdr", result: { ok: false, reason: ACP_MODEL_NOT_CURATED } }
|
|
76
|
+
: { rail: "tmux", result: { ok: false, reason: ACP_MODEL_NOT_CURATED } };
|
|
77
|
+
}
|
|
52
78
|
if (selectFreshCallRail(env) === "herdr") {
|
|
53
79
|
const context = resolveHerdrContext(env);
|
|
54
80
|
if (!context.ok) {
|
|
@@ -789,6 +789,9 @@ const HERDR_REJECT_HINT = {
|
|
|
789
789
|
"caller-identity-unavailable": "this surface has no record-backed identity, so a sibling would have nowhere to call home",
|
|
790
790
|
"model-empty": "pass the model you want the sibling opened with — it is never inherited from this process",
|
|
791
791
|
"model-invalid": "the model is not in the accepted grammar (no whitespace, no leading dash)",
|
|
792
|
+
// #128: decided at the dispatch root (`fresh-call-dispatch.ts`), worded here because this rail's
|
|
793
|
+
// import fence admits no ACP module. The curated list itself lives in `acp/model-ids.js`.
|
|
794
|
+
"acp-model-not-curated": "an entwurf model must be one exact curated ACP id — Pi would otherwise pick another row by substring (a retired claude-sonnet-5 lands on cortex-claude-sonnet-5), so nothing was opened and nothing was aliased",
|
|
792
795
|
"task-empty": "a sibling opened with nothing to do is a window nobody asked for",
|
|
793
796
|
"task-too-long": `the task is over the ${TASK_MAX_CHARS}-character interface bound this verb shares with entwurf_v2`,
|
|
794
797
|
"herdr-context-missing": "HERDR_ENV/HERDR_BIN_PATH are absent, so this process is not inside herdr and this rail does not exist here",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
* the session the window is actually in. It still reports no `pane_current_path`, and
|
|
86
86
|
* there is no "session created" field because nothing here creates one.
|
|
87
87
|
*/
|
|
88
|
+
import { ACP_MODEL_NOT_CURATED_HINT } from "./acp/model-ids.js";
|
|
88
89
|
import { callbackEnvAssignments } from "./callback-env.js";
|
|
89
90
|
import { classifyTmuxCwd } from "./classify-tmux-cwd.js";
|
|
90
91
|
import { CODEX_CALLER_SEAT_HINT, resolveCodexCallerSeat, } from "./codex-caller-seat.js";
|
|
@@ -490,6 +491,7 @@ const REJECT_HINT = {
|
|
|
490
491
|
"tmux-session-missing": "no session with that exact name answers on this agent's tmux server (or that server stopped answering) — nothing was created, so open the session yourself and call again",
|
|
491
492
|
"model-empty": "model is empty after trimming; fresh calls require an explicit model",
|
|
492
493
|
"model-invalid": `model must be one ${MODEL_MAX_CHARS}-character argv-safe id/alias without whitespace or tmux syntax`,
|
|
494
|
+
"acp-model-not-curated": ACP_MODEL_NOT_CURATED_HINT,
|
|
493
495
|
"task-empty": "task is empty after trimming",
|
|
494
496
|
"task-too-long": `task exceeds ${TASK_MAX_CHARS} characters`,
|
|
495
497
|
"runtime-unresolved": "the backend's runtime is not installed on PATH",
|