@junghanacs/entwurf 0.34.2 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +244 -0
- package/README.md +7 -0
- package/VERIFY.md +16 -5
- package/demo/README.md +109 -175
- package/demo/demo-baseline.sh +5 -1
- package/demo/demo.sh +6 -2
- package/demo/herdr-pi-cc-20261011.provenance.json +64 -0
- package/docs/acp-backend-rail.md +28 -2
- package/docs/assets/entwurf-demo.gif +0 -0
- package/docs/setup-clean-host.md +1 -1
- 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/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/resume-launch-identity.js +13 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +119 -11
- package/package.json +4 -2
- 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/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/resume-launch-identity.ts +12 -4
- package/pi-extensions/meta-bridge-pi-durable.ts +153 -13
- package/run.sh +118 -20
- 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-fresh-call-dispatch.ts +47 -4
- package/scripts/check-gate-qualification.ts +8 -5
- package/scripts/check-probe-ordering.ts +13 -6
- package/scripts/check-release-gate-outcomes.ts +159 -1
- package/scripts/check-resume-launch-identity.ts +38 -0
- package/scripts/ci-qualify-decide.sh +114 -17
- package/scripts/mutants/acp-cortex.json +17 -8
- package/scripts/mutants/acp-set-model-readback.json +28 -0
- package/scripts/mutants/demo-reproducibility.json +155 -0
- package/scripts/mutants/fresh-call-dispatch.json +11 -0
- package/scripts/mutants/herdr-dev-checkout.json +70 -0
- package/scripts/mutants/pi-durable.json +117 -0
- package/scripts/mutants/release-gate.json +28 -2
- package/scripts/mutants/resume-launch-identity.json +15 -0
- package/scripts/mutants/v2-visible-resume.json +11 -0
- 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/scripts/tsconfig.json +1 -0
package/run.sh
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
#
|
|
3
3
|
# Model id convention (see AGENTS.md Hard Rule #1):
|
|
4
4
|
# - User-facing examples use the qualified form `entwurf/<backend-model>`
|
|
5
|
-
# (e.g. `entwurf/claude-sonnet-5`); the prefix routes to this provider
|
|
5
|
+
# (e.g. `entwurf/claude-sonnet-5-5`); the prefix routes to this provider
|
|
6
6
|
# so `--provider` is redundant and is dropped in docs.
|
|
7
7
|
# - Smoke helpers that feed `ensureBridgeSession({modelId})` directly (cancel,
|
|
8
|
-
# model-switch) pass BARE backend ids (`claude-sonnet-5`, `gpt-5.4`)
|
|
8
|
+
# model-switch) pass BARE backend ids (`claude-sonnet-5-5`, `gpt-5.4`)
|
|
9
9
|
# because the bridge library contract is bare. Smoke helpers that invoke pi
|
|
10
10
|
# via the CLI still pin `--provider entwurf` and can accept either
|
|
11
11
|
# bare or qualified model, but we keep bare here to match the bridge-level
|
|
@@ -32,7 +32,7 @@ TARGET_PROJECT_DIR=${2:-$PROJECT_DIR_DEFAULT}
|
|
|
32
32
|
# and install path against the same scope for traceability.
|
|
33
33
|
PACKAGE_NAME="@junghanacs/entwurf"
|
|
34
34
|
# Runtime provider id — DO NOT change. Embedded in model strings
|
|
35
|
-
# (`entwurf/claude-sonnet-5`), settings keys (`entwurfProvider`),
|
|
35
|
+
# (`entwurf/claude-sonnet-5-5`), settings keys (`entwurfProvider`),
|
|
36
36
|
# log prefixes (`[entwurf:bootstrap]`), and the `--provider entwurf`
|
|
37
37
|
# CLI surface. Renaming this would break every consumer transcript and every
|
|
38
38
|
# saved session anchor.
|
|
@@ -116,9 +116,55 @@ run_vitest() {
|
|
|
116
116
|
(cd "$REPO_DIR" && "$bin" run "$@")
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
# A source-only UI over the existing owned developer-return procedure. No second
|
|
120
|
+
# installer, npm acquisition, automatic mode switch or ownership writer lives here.
|
|
121
|
+
developer_menu() {
|
|
122
|
+
[ "$#" -eq 0 ] || { echo 'usage: ./run.sh menu' >&2; return 2; }
|
|
123
|
+
if [ ! -d "$REPO_DIR/.git" ] || [ ! -f "$REPO_DIR/plugins/herdr/use-dev-checkout.sh" ]; then
|
|
124
|
+
echo '[menu] developer-checkout-source-only — run ./run.sh menu from the prepared source checkout, not the npm installation.' >&2
|
|
125
|
+
return 1
|
|
126
|
+
fi
|
|
127
|
+
if [ ! -t 0 ]; then
|
|
128
|
+
echo '[menu] menu-tty-required — interactive stdin is required; nothing was changed.' >&2
|
|
129
|
+
return 2
|
|
130
|
+
fi
|
|
131
|
+
local choice confirm
|
|
132
|
+
printf '\nHerdr — npm installation check → developer board (local checkout)\n'
|
|
133
|
+
printf 'Checkout: %s\n' "$REPO_DIR"
|
|
134
|
+
printf ' 1) Readiness check — read-only (not a full certificate of installation prerequisites)\n'
|
|
135
|
+
printf ' 2) Switch to the developer board — Pi / Claude Code / Herdr local link\n'
|
|
136
|
+
printf ' 0) Quit — nothing is changed\n'
|
|
137
|
+
if ! IFS= read -r -p 'Choose [0/1/2]: ' choice; then
|
|
138
|
+
echo '[menu] menu-input-closed — nothing was changed.' >&2
|
|
139
|
+
return 2
|
|
140
|
+
fi
|
|
141
|
+
case "$choice" in
|
|
142
|
+
0) return 0 ;;
|
|
143
|
+
1) bash "$REPO_DIR/plugins/herdr/use-dev-checkout.sh" --check ;;
|
|
144
|
+
2)
|
|
145
|
+
# Show the real owner's readiness first. Busy sessions or bad ownership
|
|
146
|
+
# stop here; confirmation grants no authority to bypass those guards.
|
|
147
|
+
bash "$REPO_DIR/plugins/herdr/use-dev-checkout.sh" --check || return $?
|
|
148
|
+
printf '\nClose the affected sessions yourself, and start no new session while switching.\n'
|
|
149
|
+
printf 'The currently owned installation is deactivated, then this checkout is linked. There is no automatic recovery or retry.\n'
|
|
150
|
+
if ! IFS= read -r -p 'Type DEV to switch (anything else cancels): ' confirm; then
|
|
151
|
+
echo '[menu] menu-input-closed — apply was not called.' >&2
|
|
152
|
+
return 2
|
|
153
|
+
fi
|
|
154
|
+
if [ "$confirm" != DEV ]; then
|
|
155
|
+
echo '[menu] menu-cancelled — installation wiring is unchanged.'
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
bash "$REPO_DIR/plugins/herdr/use-dev-checkout.sh" --apply
|
|
159
|
+
;;
|
|
160
|
+
*) echo '[menu] invalid-menu-choice — choose 0, 1 or 2; nothing was changed.' >&2; return 2 ;;
|
|
161
|
+
esac
|
|
162
|
+
}
|
|
163
|
+
|
|
119
164
|
usage() {
|
|
120
165
|
cat <<'EOF'
|
|
121
166
|
Usage:
|
|
167
|
+
./run.sh menu # source-only Herdr developer board: read-only readiness check or explicitly confirmed return to checkout wiring; never closes sessions or acquires npm artifacts
|
|
122
168
|
./run.sh setup [project-dir] # ONE presence-driven composition (#86): per-component PASS/SKIP/FAIL for pi/claude/agy/copilot/omp/codex + stable dev bins + v2 install smoke. Codex birth/MCP/status-line atoms are composed at user scope; setup stays non-green until the operator trusts the birth declaration once in visible Codex. Absent harness = zero-state SKIP, detected-incomplete = named FAIL + nonzero exit. Never installs a harness, starts a daemon, touches credentials, or writes the vendor trust receipt
|
|
123
169
|
./run.sh release-gate [project-dir] [--cut] [--allow-skip-gemini] # SINGLE release gate: full static (pnpm run check:full) + the v2-native live gates (v2 matrix-live, check-bridge, doctor-pi-provider, RGG) + the ACP plugin acceptance floor (12 LIVE smokes: socket-citizen/raw-turn/overlay/provider/session-reuse/carrier-augment/memory-containment/rgg/mcp/skill/bundled-mcp/v2-send) + the one surviving axis the aggregate used to omit silently (claude-native-resume; Cortex stays a documented on-demand direct call) + the cross-harness delivery chain (smoke-entwurf-chain-live) + the Codex Pi -> visible Codex -> visible Pi receipt chain (smoke-codex-fresh-live; initial Pi and the Codex it opens in one session S, explicit operator-owned app-server PID in a DIFFERENT session — that separation is the lane B claim). TWO-TIER summary: MUST (release-blocking, owns the exit code — "green" applies here) + BEHAVIOR (advisory, non-blocking: RGG positives model-in-loop turn). STEP OUTCOME protocol: every step is INVOKED and reports its own PASS / SKIP (exit 97, a prerequisite it does not have) / FAIL — a skip is never counted as a pass. Without --cut this is the unattended diagnostic (SKIPs reported, exit 0). WITH --cut it is read as release acceptance and ANY MUST SKIP is red, which is what makes "a CUT needs LIVE=1, SKIP=0" executable instead of prose. --allow-skip-gemini accepted-but-ignored (back-compat). final cut authorization is GLG's.
|
|
124
170
|
./run.sh check-bridge # entwurf-bridge direct MCP smoke + protocol/negative-path test.sh (live substrate = v2 live smokes)
|
|
@@ -134,6 +180,7 @@ Usage:
|
|
|
134
180
|
./run.sh check-omp-birth-hook # #87 gate: drives the real OMP assembler into a temp dir, imports the ASSEMBLED index.ts into a MOCK omp host and fires session_start/session_switch. tui mints one backend:"omp" record + a sender marker keyed to the host's OWN pid (the one-process join) + the garden id on the status line; print/rpc/json mint NOTHING (hasUI is true on the rpc rows, as in the vendor); switch attaches on the same native id and mints the replacement on a new one; the CERTIFIED receiver reader still finds no marker. Also the four-root joint binding: extension and omp-labeled bridge child resolve the same sessions/mailbox/senders/receivers bundle against test-built literals (never the production resolver as its own oracle), under a poisoned PI_CODING_AGENT_DIR and under four distinct ENTWURF_META_* overrides; the override grammar is absolute-or-~ and both halves plus the doctor refuse anything else by name; and a drifted provenance label selects no root policy at all. Hermetic; no omp, no model turn
|
|
135
181
|
./run.sh check-pi-durable-contact # #129 L-identity gate, CHECKOUT-ONLY (an installed package refuses it in run_ts). Opens the REAL durable app from THIS checkout's verified carrier (pi/pi-durable/carrier/ under its resolver, against the checkout's pinned pi SDK set — refused by name first) with the pi-durable contact, births the citizen and runs its tools adapter-direct through a privately emitted branch bridge (ENTWURF_PI_DURABLE_BRIDGE_ENTRY; its registry must match this checkout). Matrix roots {default, explicit} x {fresh, --continue}: one record keyed by the durable session dir, sender marker on the spawned host pid, exactly one bridge child whose /proc PPID is that host, poisoned identity carriers kept from it, its four root carriers bound to the roots written, the receiver ARMED (raw marker owned by the host pid + start key as pi-durable-host, joined to the sender marker; removed on close), self = the record and replyable, peers from an outside bridge, same garden id on reopen; the bundle's compiled owner join must admit pi-durable-host. Reaps only the host/bridges it owns on any failure. Harness-bypassed: no model turn, no ToolTask. Missing inputs → SKIP (97), never a pass
|
|
136
182
|
./run.sh check-pi-durable-send # #129 L-send FIRST CELL, CHECKOUT-ONLY. Same input as check-pi-durable-contact. One native durable turn: the app's own controller input → GenerationTask → ToolTask → the contact's entwurf_v2 → the private bridge → a seeded self-fetch target (C2 recipe, owned by a separate idle child). S = a 127.0.0.1 scripted OpenAI-compatible endpoint configured through the app's documented models.json (dummy key, PI_OFFLINE); H = the native ToolTask path; V (a vendor turn) is NOT run. Oracles: the wire (tools offered, v2 schema = the bridge's tools/list, exactly two requests, the tool result + receipt in the second), the raw mailbox (.msg 1, signal, sender = the host record), the native view (one call, its settled result, root current). Not exactly-once, not recovery, not a recipient turn. Missing inputs → SKIP (97)
|
|
183
|
+
./run.sh check-demo-reproducibility # source-checkout-only: discover offline demo Node tests; no LIVE, model, render or publication
|
|
137
184
|
./run.sh check-pi-durable-beside # #129 mutant execution coordinate (VERIFY rule 3): run_vitest over the six pi-durable beside tests (adapter, carrier/SDK-set verifier, preflight, owner join, carrier relocation/resolver/emitter/task footer, native-module file and directory ingress) — the pi-durable qualification lane's gate argv. Same files check-tests-beside-behavior already runs.
|
|
138
185
|
./run.sh check-pi-durable-receive # #129 L-receive FIRST CELL, CHECKOUT-ONLY. Same input (the bundle must carry the pi-durable-host owner join). The host driver gives the app NO input; a seeded claude-code sender (marker owned by a separate idle child, handed to a gate-owned sending bridge through ENTWURF_META_SENDER_MARKER) enqueues one message, the host's doorbell admits its notice to the ROOT conversation, and the scripted model drains it with entwurf_inbox_read. S withholds response #1 until the ENQUEUE receipt, the .delivered bytes, the signal, the host-owned receiver marker and the notice are captured. Oracles: receiver() admission, native view, raw archive (.delivered.read + lastReadAt), wire (exactly two requests, drain result = view), and after a green reap a forensic read-only SQLite copy (submission row by request id = reported id, root, input, done, placed entry = the notice). V NOT run. Missing inputs → SKIP (97)
|
|
139
186
|
./run.sh check-copilot-receive-arm # #82 RAIL 5 gate: the REAL receiver installer + the REAL extension.mjs forked with a stubbed SDK. Arms only after birth, marker owned by the WATCHER pid, self-fetch dispatch answer, doorbell carries the garden id and NOT the body, id-drift/foreign-parent refusals. Hermetic; no Copilot, no model turn
|
|
@@ -222,7 +269,7 @@ Usage:
|
|
|
222
269
|
./run.sh check-meta-manifest-schema # 0.12.2 meta-bridge: CLI-version-INDEPENDENT static guard — plugin manifests pinned to the minimal keyset that validates on the lowest supported Claude (closed-schema regression that broke 0.12.1 install on floor) + desired_mcp installed-vs-clone dual-mode. Offline (deps: python3)
|
|
223
270
|
./run.sh smoke-claude-native-resume-live # LIVE-only: Claude Code native fresh→--resume continuity + meta-record uniqueness; proves meta-bridge records identity without touching the backend resume path
|
|
224
271
|
|
|
225
|
-
./run.sh install-meta-bridge # INTERNAL
|
|
272
|
+
./run.sh install-meta-bridge # INTERNAL wiring leaf: setup, the source-only developer menu and doctor recovery compose it; stateful GLOBAL install (plugin + USER MCP + settings keyset, honest uninstall state)
|
|
226
273
|
./run.sh uninstall-meta-bridge # 1.0.0 meta-bridge Phase 2: stateful GLOBAL uninstall (restore only keys/items captured in install-state)
|
|
227
274
|
./run.sh doctor-meta-bridge # THE RELEASE ORACLE (#51, Linux-certified repair axis). exit 0 = every required layer was MEASURED on this Linux host: toolchain + state + plugin/MCP + resolved-artifact launch-form classification (all 3 owner hooks + doorbell static contract) + synthetic owner join + store scan + hook errors + SessionStart evidence + REQUIRED live MCP↔marker join + writer-version parity. Missing live evidence is NOT CERTIFIED (open a Claude session and re-run), never a pass; Darwin is NOT CERTIFIED and stays nonzero, on ONE narrowed reason — start-key and ppid are portable there (`ps` fallback, same string the TS core mints), so what holds the lane closed is per-process environment DISCOVERY alone, pending physical-host evidence. Detection power is held by check-meta-doctor-oracle
|
|
228
275
|
./run.sh copilot [args...] # #82 RAIL 7: the MANAGED Copilot launch. exec()s the vendor CLI in THIS terminal (cwd/pid/exit preserved) with COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS set for that one invocation — entwurf owns no part of your shell and writes nothing to it, but it owns the process it is about to become. Without that flag Copilot skips the extension scan SILENTLY. Refuses to launch unless the receiver unit is actually installed. Injects `--model auto` when no --model was given and `--yolo` when no explicit permission/surface policy flag was given, both BEFORE any `--`; every argument you pass is forwarded byte-identical. RUNNING THIS IS YOUR CONSENT to that profile — use plain `copilot` for stock vendor behaviour. Not tmux, not fresh-call, mints no citizen (birth is still the first prompt)
|
|
@@ -290,7 +337,7 @@ Usage:
|
|
|
290
337
|
./run.sh check-fresh-cut-gate # SOURCE cell of the generation-boundary proof (IN pnpm run check:full): drives real install/setup/fresh-cut in a sandbox; certification refusal is pre-write, quiescence is fail-closed, archives preserve bytes, and the #54 exit matrix distinguishes complete / no-move / usage / incomplete transition / complete-with-cleanup-residue. No model/network/cost
|
|
291
338
|
./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke + the #116 M3-b1 herdr-plugin RUNTIME VERIFIER run against that actual installed tarball (the focused check-herdr-runtime-bootstrap drives a fixture; THIS is where the real package is proven) with the pinned pi SDK (pins derived from the package.json exact production dependency; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES) + the INSTALLED-PACKAGE branch of the Copilot and OMP birth installers actually RUN (compiled entry selected, no raw .ts, and a real birth edge mints a citizen — the half a required-artifact list can never stand in for) + the INSTALLED #129 durable bootstrap (pi/pi-durable/bootstrap.mjs) imported from node_modules: main/openDurableCitizen exported and PACKAGED_BRIDGE_ENTRY = the installed package's own dist entry — import only, no runtime/TUI/birth/bridge started + the #130 pi-durable CARRIER on actual npm consumers (Entwurf-only / aligned host / isolated global): the installed verifier, a consumer-contained resolution oracle with a same-sandbox neighbour control, every physical SDK edge at the pin, a mixed tree refused as pi-durable-sdk-mismatch though it still imports, and the public `entwurf pi-durable` reaching the app's own explicit-model refusal before any birth, also with one explicit `--native-module` (the synthetic fixture, initialized first; a reserved extension name refused before anything opens) (offline, sandbox HOME)
|
|
292
339
|
./run.sh check-install-container # 0.12.8 (#51 C): Linux artifact-CONSUMER gate — one candidate .tgz handed read-only to a checkout-invisible node:<engines-major>-bookworm cell. Default packs once to temp; ENTWURF_CANDIDATE_TGZ=/absolute/preserved.tgz consumes those exact bytes with no re-pack and prints canonical path+sha256 for release. Non-root global PATH install, frozen package, MCP tools/list, fake-Claude install-meta-bridge, path+sha256 fence, strict doctor, and the GENERATION host-state matrix (clean / v3-only store bytes unchanged / previous-generation REFUSE→fresh-cut→retry PASS) seeded inline. Docker missing = honest SKIP; ENTWURF_REQUIRE_DOCKER=1 makes that RED (required CI)
|
|
293
|
-
./run.sh install [project-dir] # INTERNAL
|
|
340
|
+
./run.sh install [project-dir] # INTERNAL wiring leaf: setup/npm-consumer entry and the source-only developer menu compose it; prefer those owning procedures
|
|
294
341
|
./run.sh remove [project-dir] # remove entwurf entries from project .pi/settings.json (project scope only; global user-scope citizen left intact)
|
|
295
342
|
./run.sh install-user-scope [--plugin-runtime <root>] # #116 M3-b2 PLUGIN-ONLY forward seam: compose ONLY the global pi citizen (user-scope packages[] + provider), never a project `.pi/settings.json` — a Herdr plugin activation has no project and must not invent one. With --plugin-runtime the provider's managed command becomes the absolute bridge under that certified stable runtime (a clean host has nothing entwurf on PATH); without it every byte is the historical bare-bin install. Inverse: remove-user-scope
|
|
296
343
|
./run.sh herdr-plugin-activate <backend...> # #116 M3-b2 public forward activation. Takes a subset of {pi, claude-code} and NOTHING else — the stable runtime root is DERIVED from XDG here and handed down, and both harness writers accept a named root only when it IS that derived one. Certifies the runtime-ready journal AND the installed tree, certifies the existing ledger and its recorded harness roots, plans ADD-ONLY (a backend absent from this request is retained, never removed), preflights every selected component before the first byte, then writes each component `pending` BEFORE mutating and checkpoints `active` the moment its writer returns — so a run that dies halfway leaves a record of how far it got. Which backends is M3-b3's question: this verb is handed the answer, it does not read Herdr's integration status
|
|
@@ -1029,6 +1076,37 @@ check_capability_bundle_reach() {
|
|
|
1029
1076
|
run_ts scripts/check-capability-bundle-reach.ts
|
|
1030
1077
|
}
|
|
1031
1078
|
|
|
1079
|
+
check_demo_reproducibility() {
|
|
1080
|
+
# Source-only Node strip-types/CLI and subprocess fixtures, not adapter Vitest tests.
|
|
1081
|
+
# Discovery lives beside demo behavior; no filename list or npm TS crossing.
|
|
1082
|
+
section "demo reproducibility (offline source fixtures, no model or recording)"
|
|
1083
|
+
case "$REPO_DIR" in */node_modules/*) fail "check-demo-reproducibility is source-checkout-only"; return 1 ;; esac
|
|
1084
|
+
local -a tests=()
|
|
1085
|
+
local f
|
|
1086
|
+
while IFS= read -r f; do [ -n "$f" ] && tests+=("$f"); done < <(cd "$REPO_DIR" && find demo -type f -name '*.test.ts' -print | LC_ALL=C sort)
|
|
1087
|
+
if [ "${#tests[@]}" -eq 0 ]; then
|
|
1088
|
+
fail "[QK:DEMO-TEST-DISCOVERY] demo test discovery is empty — no fixtures is not a passing floor"
|
|
1089
|
+
return 1
|
|
1090
|
+
fi
|
|
1091
|
+
(
|
|
1092
|
+
local sandbox name
|
|
1093
|
+
sandbox=$(mktemp -d /tmp/entwurf-demo-unit.XXXXXX)
|
|
1094
|
+
trap 'rm -rf -- "$sandbox"' EXIT
|
|
1095
|
+
while IFS= read -r name; do
|
|
1096
|
+
case "$name" in ENTWURF_*|HERDR_*|PI_SESSION_ID|PI_AGENT_ID|CLAUDECODE|CLAUDE_CODE_SESSION_ID|TMUX|TMUX_PANE|LIVE) unset "$name" ;; esac
|
|
1097
|
+
done < <(compgen -e)
|
|
1098
|
+
export HOME="$sandbox/home" PI_CODING_AGENT_DIR="$sandbox/pi"
|
|
1099
|
+
export XDG_CONFIG_HOME="$sandbox/config" XDG_DATA_HOME="$sandbox/data" XDG_STATE_HOME="$sandbox/state" XDG_CACHE_HOME="$sandbox/cache" XDG_RUNTIME_DIR="$sandbox/runtime"
|
|
1100
|
+
mkdir -m 700 "$HOME" "$PI_CODING_AGENT_DIR" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" "$XDG_CACHE_HOME" "$XDG_RUNTIME_DIR" "$sandbox/bin"
|
|
1101
|
+
# Take.finish records PATH versions; fixtures must not depend on an installed Pi or run it.
|
|
1102
|
+
printf '#!/bin/sh\n[ "$1" = --version ] && { printf "offline-fixture\\n"; exit 0; }\nexit 81\n' > "$sandbox/bin/pi"
|
|
1103
|
+
chmod +x "$sandbox/bin/pi"
|
|
1104
|
+
export PATH="$sandbox/bin:$PATH"
|
|
1105
|
+
cd "$REPO_DIR"
|
|
1106
|
+
node --test "${tests[@]}"
|
|
1107
|
+
)
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1032
1110
|
check_bridge_delivery() {
|
|
1033
1111
|
# demo/demo.sh scene 3, recovered as a deterministic gate. demo drove two real pi
|
|
1034
1112
|
# panes so it proved DELIVERY through the installed binary (at model cost); what
|
|
@@ -1363,6 +1441,14 @@ check_acp_foreground_only() {
|
|
|
1363
1441
|
run_vitest pi-extensions/lib/acp/backend-adapter.test.ts
|
|
1364
1442
|
}
|
|
1365
1443
|
|
|
1444
|
+
check_acp_set_model_readback() {
|
|
1445
|
+
# NOT a discovery path — check-tests-beside-behavior finds the test by glob. This case is the
|
|
1446
|
+
# MUTANT EXECUTION COORDINATE for scripts/mutants/acp-set-model-readback.json (#128): the claude
|
|
1447
|
+
# adapter refuses a set-model answer that does not name the requested model, before the prompt.
|
|
1448
|
+
section "claude ACP set-model readback (mutant execution coordinate)"
|
|
1449
|
+
run_vitest pi-extensions/lib/acp/backend-adapter.test.ts
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1366
1452
|
check_acp_vendor_rejection() {
|
|
1367
1453
|
# Behaviour-adjacent tests are discovered by the glob lane. This narrow case is the
|
|
1368
1454
|
# mutant execution coordinate: include the adapter admission and real turn pipeline,
|
|
@@ -1571,12 +1657,12 @@ check_mux_launch_tmux() {
|
|
|
1571
1657
|
smoke_acp_socket_citizen_live() {
|
|
1572
1658
|
# S1 acceptance smoke (ACP plugin on v2) — OUT of pnpm check, needs LIVE=1.
|
|
1573
1659
|
# Spawns a REAL `pi --entwurf-control` resident on an ACP model
|
|
1574
|
-
# (entwurf/claude-opus-5) and proves it is a first-class socket-citizen:
|
|
1660
|
+
# (entwurf/claude-opus-5-5) and proves it is a first-class socket-citizen:
|
|
1575
1661
|
# the control socket stands up, get_info answers with the ACP model (model-lock
|
|
1576
1662
|
# did NOT revert — QM1), idle/cwd are reported, and the fail-loud streamSimple
|
|
1577
1663
|
# stub never fires (turn-free launch — QM2). No prompt is sent: S1 proves
|
|
1578
1664
|
# citizenship, never a backend turn (that is S2). Honest skip when LIVE!=1.
|
|
1579
|
-
# Model override: ENTWURF_S1_MODEL (default claude-opus-5).
|
|
1665
|
+
# Model override: ENTWURF_S1_MODEL (default claude-opus-5-5).
|
|
1580
1666
|
# LIVE=1 ./run.sh smoke-acp-socket-citizen-live
|
|
1581
1667
|
run_ts scripts/smoke-acp-socket-citizen-live.ts
|
|
1582
1668
|
}
|
|
@@ -1590,7 +1676,7 @@ smoke_acp_raw_turn_live() {
|
|
|
1590
1676
|
# plus captured raw NDJSON bytes. NO provider/overlay/streamSimple/_meta — the
|
|
1591
1677
|
# raw backend pipe only. Launch source must be the package bin (PATH fallback
|
|
1592
1678
|
# fails acceptance unless ENTWURF_ACP_RAW_TURN_ALLOW_PATH_FALLBACK=1, debug).
|
|
1593
|
-
# Model override: ENTWURF_ACP_RAW_TURN_MODEL (default claude-sonnet-5).
|
|
1679
|
+
# Model override: ENTWURF_ACP_RAW_TURN_MODEL (default claude-sonnet-5-5).
|
|
1594
1680
|
# LIVE=1 ./run.sh smoke-acp-raw-turn-live
|
|
1595
1681
|
run_ts scripts/smoke-acp-raw-turn-live.ts
|
|
1596
1682
|
}
|
|
@@ -1608,7 +1694,7 @@ smoke_acp_overlay_live() {
|
|
|
1608
1694
|
# meta-store for mailbox absence (flaky — concurrent sessions); the honest
|
|
1609
1695
|
# claim is overlay-supplies-hooks:{}. Launch must be the package bin (PATH
|
|
1610
1696
|
# fallback fails acceptance unless ENTWURF_ACP_OVERLAY_ALLOW_PATH_FALLBACK=1).
|
|
1611
|
-
# Model override: ENTWURF_ACP_OVERLAY_MODEL (default claude-sonnet-5).
|
|
1697
|
+
# Model override: ENTWURF_ACP_OVERLAY_MODEL (default claude-sonnet-5-5).
|
|
1612
1698
|
# LIVE=1 ./run.sh smoke-acp-overlay-live
|
|
1613
1699
|
run_ts scripts/smoke-acp-overlay-live.ts
|
|
1614
1700
|
}
|
|
@@ -1623,7 +1709,7 @@ smoke_acp_memory_containment_live() {
|
|
|
1623
1709
|
# can stop a memory write is the lever — not us. Fails loud if engraving.md is
|
|
1624
1710
|
# empty (carrier OFF = no containment). Launch must be the package bin (PATH
|
|
1625
1711
|
# fallback fails acceptance unless ENTWURF_ACP_MEMORY_ALLOW_PATH_FALLBACK=1).
|
|
1626
|
-
# Model override: ENTWURF_ACP_MEMORY_MODEL (default claude-sonnet-5).
|
|
1712
|
+
# Model override: ENTWURF_ACP_MEMORY_MODEL (default claude-sonnet-5-5).
|
|
1627
1713
|
# LIVE=1 ./run.sh smoke-acp-memory-containment-live
|
|
1628
1714
|
run_ts scripts/smoke-acp-memory-containment-live.ts
|
|
1629
1715
|
}
|
|
@@ -1650,7 +1736,7 @@ smoke_acp_provider_live() {
|
|
|
1650
1736
|
# assistant reply (live model proof) + the removed S0 stub error never appears
|
|
1651
1737
|
# (provider path actually opened) + pi exits 0. Tool-free prompt; the
|
|
1652
1738
|
# event-mapper gate owns the tool→notice contract.
|
|
1653
|
-
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
|
|
1739
|
+
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5-5).
|
|
1654
1740
|
# LIVE=1 ./run.sh smoke-acp-provider-live
|
|
1655
1741
|
run_ts scripts/smoke-acp-provider-live.ts
|
|
1656
1742
|
}
|
|
@@ -1664,7 +1750,7 @@ smoke_acp_session_reuse_live() {
|
|
|
1664
1750
|
# is the verdict; codeword recall is supporting only (a rebuilt full transcript
|
|
1665
1751
|
# could recall it too). The one-shot exit0 half is owned by
|
|
1666
1752
|
# smoke-acp-provider-live.
|
|
1667
|
-
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
|
|
1753
|
+
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5-5).
|
|
1668
1754
|
# LIVE=1 ./run.sh smoke-acp-session-reuse-live
|
|
1669
1755
|
run_ts scripts/smoke-acp-session-reuse-live.ts
|
|
1670
1756
|
}
|
|
@@ -1676,7 +1762,7 @@ smoke_acp_carrier_augment_live() {
|
|
|
1676
1762
|
# secret (the augment rode the wire to the model) and the EMPTY default carrier
|
|
1677
1763
|
# must bill clean (exit 0, no HTTP-400 canary — 핀1 live). Optional tiny carrier
|
|
1678
1764
|
# check via SMOKE_ACP_CARRIER_PRESENT=1 (non-blocking).
|
|
1679
|
-
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
|
|
1765
|
+
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5-5).
|
|
1680
1766
|
# LIVE=1 ./run.sh smoke-acp-carrier-augment-live
|
|
1681
1767
|
run_ts scripts/smoke-acp-carrier-augment-live.ts
|
|
1682
1768
|
}
|
|
@@ -1762,9 +1848,9 @@ smoke_acp_rgg_live() {
|
|
|
1762
1848
|
# birth, record-keyed socket, attach-on-reopen, no pi-session-id socket) holds
|
|
1763
1849
|
# under the ACP provider too — the logic is provider-agnostic. The POSITIVE cell
|
|
1764
1850
|
# (one real turn completing transcriptPath/model) is the only model-in-loop part.
|
|
1765
|
-
# Target override: ENTWURF_RGG_TARGET (default entwurf/claude-sonnet-5).
|
|
1851
|
+
# Target override: ENTWURF_RGG_TARGET (default entwurf/claude-sonnet-5-5).
|
|
1766
1852
|
# ./run.sh smoke-acp-rgg-live
|
|
1767
|
-
local target="${ENTWURF_RGG_TARGET:-entwurf/claude-sonnet-5}"
|
|
1853
|
+
local target="${ENTWURF_RGG_TARGET:-entwurf/claude-sonnet-5-5}"
|
|
1768
1854
|
(cd "$REPO_DIR" && ENTWURF_LIVE_TARGET="$target" SMOKE_RGG_POSITIVE=0 bash scripts/smoke-resident-garden-guard.sh)
|
|
1769
1855
|
}
|
|
1770
1856
|
|
|
@@ -3614,7 +3700,7 @@ check_pack() {
|
|
|
3614
3700
|
"package.json" "README.md" "LICENSE" "CHANGELOG.md"
|
|
3615
3701
|
"protocol.js" "run.sh"
|
|
3616
3702
|
"pi-extensions/acp-provider.ts"
|
|
3617
|
-
"pi-extensions/lib/acp/models.ts" "pi-extensions/lib/acp/backend.ts"
|
|
3703
|
+
"pi-extensions/lib/acp/models.ts" "pi-extensions/lib/acp/model-ids.js" "pi-extensions/lib/acp/backend.ts"
|
|
3618
3704
|
"pi-extensions/lib/acp/overlay.ts" "pi-extensions/lib/acp/tool-surface.ts"
|
|
3619
3705
|
"pi-extensions/lib/acp/event-mapper.ts" "pi-extensions/lib/acp/context.ts"
|
|
3620
3706
|
"pi-extensions/entwurf-control.ts"
|
|
@@ -3756,6 +3842,8 @@ check_pack() {
|
|
|
3756
3842
|
# pi-extensions/ so it also covers any future allowlist entry; `plugins/` needs no
|
|
3757
3843
|
# companion negation because `^plugins/` above already refuses that whole tree.
|
|
3758
3844
|
'\.test\.[cm]?[jt]s$'
|
|
3845
|
+
# Maintainer take/renderer/helper TS is source-only, including non-test implementations.
|
|
3846
|
+
'^demo/.*\.ts$'
|
|
3759
3847
|
)
|
|
3760
3848
|
|
|
3761
3849
|
local pass=1 f pat hit
|
|
@@ -4260,7 +4348,7 @@ _check_pack_install_impl() {
|
|
|
4260
4348
|
"package.json" "README.md" "LICENSE" "CHANGELOG.md"
|
|
4261
4349
|
"protocol.js" "run.sh"
|
|
4262
4350
|
"pi-extensions/acp-provider.ts"
|
|
4263
|
-
"pi-extensions/lib/acp/models.ts" "pi-extensions/lib/acp/backend.ts"
|
|
4351
|
+
"pi-extensions/lib/acp/models.ts" "pi-extensions/lib/acp/model-ids.js" "pi-extensions/lib/acp/backend.ts"
|
|
4264
4352
|
"pi-extensions/lib/acp/overlay.ts" "pi-extensions/lib/acp/tool-surface.ts"
|
|
4265
4353
|
"pi-extensions/lib/acp/event-mapper.ts" "pi-extensions/lib/acp/context.ts"
|
|
4266
4354
|
"pi-extensions/entwurf-control.ts"
|
|
@@ -4686,8 +4774,8 @@ _check_pack_install_impl() {
|
|
|
4686
4774
|
loader_ids=$(awk '$1 == "entwurf" { print $2 }' <<<"$loader_out" | LC_ALL=C sort)
|
|
4687
4775
|
loader_expected=$(printf '%s\n' \
|
|
4688
4776
|
claude-fable-5-1 \
|
|
4689
|
-
claude-opus-5 \
|
|
4690
|
-
claude-sonnet-5 \
|
|
4777
|
+
claude-opus-5-5 \
|
|
4778
|
+
claude-sonnet-5-5 \
|
|
4691
4779
|
cortex-auto \
|
|
4692
4780
|
cortex-claude-opus-5 \
|
|
4693
4781
|
cortex-claude-sonnet-5 \
|
|
@@ -4796,7 +4884,7 @@ sys.exit(0 if any(isinstance(s,str) and s.endswith('/node_modules/@junghanacs/en
|
|
|
4796
4884
|
echo "$foreign_out" | tail -10 | sed 's/^/ /' >&2
|
|
4797
4885
|
return 1
|
|
4798
4886
|
}
|
|
4799
|
-
if grep -qi "Unknown option" <<<"$foreign_out" || ! grep -q "claude-opus-5" <<<"$foreign_out"; then
|
|
4887
|
+
if grep -qi "Unknown option" <<<"$foreign_out" || ! grep -q "claude-opus-5-5" <<<"$foreign_out"; then
|
|
4800
4888
|
fail "[check-pack-install] foreign-cwd --entwurf-control did not load the entwurf extension from user scope:"
|
|
4801
4889
|
echo "$foreign_out" | tail -10 | sed 's/^/ /' >&2
|
|
4802
4890
|
return 1
|
|
@@ -7035,6 +7123,10 @@ release_gate() {
|
|
|
7035
7123
|
|
|
7036
7124
|
cmd=${1:-}
|
|
7037
7125
|
case "$cmd" in
|
|
7126
|
+
menu)
|
|
7127
|
+
shift
|
|
7128
|
+
developer_menu "$@"
|
|
7129
|
+
;;
|
|
7038
7130
|
setup)
|
|
7039
7131
|
setup_all "$TARGET_PROJECT_DIR"
|
|
7040
7132
|
;;
|
|
@@ -7209,6 +7301,9 @@ case "$cmd" in
|
|
|
7209
7301
|
check-acp-foreground-only)
|
|
7210
7302
|
check_acp_foreground_only
|
|
7211
7303
|
;;
|
|
7304
|
+
check-acp-set-model-readback)
|
|
7305
|
+
check_acp_set_model_readback
|
|
7306
|
+
;;
|
|
7212
7307
|
check-resume-launch-identity)
|
|
7213
7308
|
check_resume_launch_identity
|
|
7214
7309
|
;;
|
|
@@ -8175,6 +8270,9 @@ case "$cmd" in
|
|
|
8175
8270
|
check-pi-queue-oracle)
|
|
8176
8271
|
check_pi_queue_oracle
|
|
8177
8272
|
;;
|
|
8273
|
+
check-demo-reproducibility)
|
|
8274
|
+
check_demo_reproducibility
|
|
8275
|
+
;;
|
|
8178
8276
|
check-tests-beside-behavior)
|
|
8179
8277
|
check_tests_beside_behavior
|
|
8180
8278
|
;;
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
import { strict as assert } from "node:assert";
|
|
18
18
|
import { execFileSync } from "node:child_process";
|
|
19
|
-
import { readdirSync, readFileSync, rmSync } from "node:fs";
|
|
19
|
+
import { readdirSync, readFileSync, rmdirSync, rmSync } from "node:fs";
|
|
20
20
|
import { join, resolve } from "node:path";
|
|
21
21
|
import { pathToFileURL } from "node:url";
|
|
22
22
|
import type { Api, AssistantMessageEvent, Model, TranscriptContext } from "@earendil-works/pi-ai";
|
|
23
23
|
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
24
24
|
|
|
25
|
-
const model = { id: "claude-sonnet-5" } as unknown as Model<Api>;
|
|
25
|
+
const model = { id: "claude-sonnet-5-5" } as unknown as Model<Api>;
|
|
26
26
|
|
|
27
27
|
// Declared tools exclude `read`, but the Claude child always exposes Read →
|
|
28
28
|
// declared != actual → the preflight must reject before spawning.
|
|
@@ -157,6 +157,14 @@ try {
|
|
|
157
157
|
);
|
|
158
158
|
} finally {
|
|
159
159
|
rmSync(TMP_EMIT, { recursive: true, force: true });
|
|
160
|
+
try {
|
|
161
|
+
// Same as check-acp-cortex: the emit created `.tmp-verify/` as TMP_EMIT's parent, and
|
|
162
|
+
// check-gate-qualification's snapshot manifest walks ignored paths, so a leftover empty
|
|
163
|
+
// parent reads as a dirty tree. Remove it when empty; a sibling's emit keeps it alive.
|
|
164
|
+
rmdirSync(".tmp-verify");
|
|
165
|
+
} catch {
|
|
166
|
+
// non-empty or already gone — fine either way
|
|
167
|
+
}
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
// ---------------------------------------------------------------------------
|
|
@@ -53,11 +53,11 @@ import {
|
|
|
53
53
|
import { tmpdir } from "node:os";
|
|
54
54
|
import { join, resolve } from "node:path";
|
|
55
55
|
import { pathToFileURL } from "node:url";
|
|
56
|
+
import { getModels } from "@earendil-works/pi-ai/compat";
|
|
56
57
|
import { buildPiContextAugment } from "../pi-extensions/lib/acp/augment.ts";
|
|
57
58
|
import type { AcpMcpServer } from "../pi-extensions/lib/acp/config.ts";
|
|
58
59
|
import {
|
|
59
60
|
CORTEX_MODEL_PREFIX,
|
|
60
|
-
curatedClaudeModels,
|
|
61
61
|
curatedCortexModels,
|
|
62
62
|
SUPPORTED_CORTEX_MODEL_IDS,
|
|
63
63
|
} from "../pi-extensions/lib/acp/models.ts";
|
|
@@ -87,12 +87,13 @@ const cortexRowIds = cortexRows.map((m) => m.id);
|
|
|
87
87
|
assert.deepEqual(cortexRowIds, [...SUPPORTED_CORTEX_MODEL_IDS], "curated cortex rows must match the supported id set");
|
|
88
88
|
// The two Claude rows ride their OWN registry bases (a fabricated row would
|
|
89
89
|
// drift from the pi-ai catalog); auto/gpt ride the sonnet base as a floor.
|
|
90
|
-
|
|
90
|
+
// Cortex retains its own native 5 ids even when Claude's curated set moves to 5.5.
|
|
91
|
+
const claudeRows = getModels("anthropic");
|
|
91
92
|
const sonnetBase = claudeRows.find((m) => m.id === "claude-sonnet-5");
|
|
92
93
|
const opusBase = claudeRows.find((m) => m.id === "claude-opus-5");
|
|
93
94
|
// Plain throw (not assert.ok): an asserts-function premise makes tsc report the
|
|
94
95
|
// downstream `base` const as circularly-typed (TS7022).
|
|
95
|
-
if (!sonnetBase || !opusBase) throw new Error("test premise: sonnet-5/opus-5 present in
|
|
96
|
+
if (!sonnetBase || !opusBase) throw new Error("test premise: sonnet-5/opus-5 present in pi-ai registry");
|
|
96
97
|
for (const row of cortexRows) {
|
|
97
98
|
// Explicit annotation: node:assert's asserts-typed helpers otherwise make tsc
|
|
98
99
|
// flag this flow-dependent const as circularly typed (TS7022).
|
|
@@ -480,7 +481,7 @@ try {
|
|
|
480
481
|
// throwing IS the anti-collision proof; we also assert the rows are present.
|
|
481
482
|
const registered = mod.allCuratedModels().map((m) => m.id);
|
|
482
483
|
for (const want of [
|
|
483
|
-
"claude-sonnet-5",
|
|
484
|
+
"claude-sonnet-5-5",
|
|
484
485
|
"cortex-auto",
|
|
485
486
|
"cortex-claude-opus-5",
|
|
486
487
|
"cortex-claude-sonnet-5",
|
|
@@ -494,12 +495,12 @@ try {
|
|
|
494
495
|
assert.equal(routedSonnet.adapter.backend, "cortex", "cortex-claude-sonnet-5 must route to the cortex adapter");
|
|
495
496
|
assert.equal(routedSonnet.nativeModelId, "claude-sonnet-5", "prefix strip must recover the native model id");
|
|
496
497
|
assert.equal(
|
|
497
|
-
mod.resolveAcpBackendAdapter("claude-sonnet-5").adapter.backend,
|
|
498
|
+
mod.resolveAcpBackendAdapter("claude-sonnet-5-5").adapter.backend,
|
|
498
499
|
"claude",
|
|
499
500
|
"the unprefixed claude id must still route to claude (prefix keeps the namespaces apart)",
|
|
500
501
|
);
|
|
501
502
|
assert.equal(
|
|
502
|
-
mod.cortexAdapter.routeModel("claude-sonnet-5"),
|
|
503
|
+
mod.cortexAdapter.routeModel("claude-sonnet-5-5"),
|
|
503
504
|
undefined,
|
|
504
505
|
"cortex must NOT claim the unprefixed claude id",
|
|
505
506
|
);
|
|
@@ -40,7 +40,7 @@ import type { Api, AssistantMessageEvent, Message, Model, TranscriptContext } fr
|
|
|
40
40
|
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
41
41
|
import { isRetryableAssistantError } from "@earendil-works/pi-ai/compat";
|
|
42
42
|
|
|
43
|
-
const sonnet = { id: "claude-sonnet-5" } as unknown as Model<Api>;
|
|
43
|
+
const sonnet = { id: "claude-sonnet-5-5" } as unknown as Model<Api>;
|
|
44
44
|
|
|
45
45
|
type Stream = AsyncIterable<AssistantMessageEvent>;
|
|
46
46
|
|
|
@@ -151,7 +151,7 @@ function makeHarness(recordDir: string) {
|
|
|
151
151
|
const makeConnection = (handlers: any) => ({
|
|
152
152
|
initialize: async () => ({ agentCapabilities: {} }),
|
|
153
153
|
newSession: async () => ({ sessionId: "ACP-1" }),
|
|
154
|
-
setSessionConfigOption: async () => ({}),
|
|
154
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: "model", currentValue: p.value }] }),
|
|
155
155
|
prompt: ({ sessionId }: any) => {
|
|
156
156
|
promptCount++;
|
|
157
157
|
notifier = (update) => handlers.sessionUpdate({ update, sessionId });
|
|
@@ -123,7 +123,7 @@ try {
|
|
|
123
123
|
assert.equal(cap.cfg.apiKey, ENTWURF_ACP_NO_AUTH_SENTINEL, "entry apiKey is not the no-auth sentinel");
|
|
124
124
|
assert.equal(cap.cfg.api, "entwurf", "entry api field drifted");
|
|
125
125
|
const capIds = (cap.cfg.models ?? []).map((m) => m.id);
|
|
126
|
-
for (const want of ["claude-sonnet-5", CURATED_ANCHOR_MODEL_ID]) {
|
|
126
|
+
for (const want of ["claude-sonnet-5-5", CURATED_ANCHOR_MODEL_ID]) {
|
|
127
127
|
assert.ok(capIds.includes(want), `entry model surface missing ${want} (got: ${capIds.join(", ") || "none"})`);
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -51,8 +51,8 @@ function tctx(messages: Context["messages"]): TranscriptContext {
|
|
|
51
51
|
return normalizeContext({ tools: PI_DECLARED_TOOLS, messages });
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
const sonnet = { id: "claude-sonnet-5" } as unknown as Model<Api>;
|
|
55
|
-
const opus = { id: "claude-opus-5" } as unknown as Model<Api>;
|
|
54
|
+
const sonnet = { id: "claude-sonnet-5-5" } as unknown as Model<Api>;
|
|
55
|
+
const opus = { id: "claude-opus-5-5" } as unknown as Model<Api>;
|
|
56
56
|
|
|
57
57
|
type Stream = AsyncIterable<AssistantMessageEvent> & {
|
|
58
58
|
result: () => Promise<{ stopReason: string; errorMessage?: string }>;
|
|
@@ -154,7 +154,7 @@ function makeHarness(recordDir: string, config: any = DEFAULT_RESOLVED_CONFIG, e
|
|
|
154
154
|
newSessionSeq++;
|
|
155
155
|
return { sessionId: `ACP-${newSessionSeq}` };
|
|
156
156
|
},
|
|
157
|
-
setSessionConfigOption: async () => ({}),
|
|
157
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: "model", currentValue: p.value }] }),
|
|
158
158
|
prompt: async ({ sessionId, prompt }: any) => {
|
|
159
159
|
promptCalls.push({ sessionId, text: prompt.map((b: { text: string }) => b.text).join("\n") });
|
|
160
160
|
if (block) await block;
|
|
@@ -505,7 +505,7 @@ try {
|
|
|
505
505
|
);
|
|
506
506
|
const d1 = deltaText(t1);
|
|
507
507
|
const iPrep = d1.indexOf("[acp: preparing claude session]");
|
|
508
|
-
const iReady = d1.indexOf("[acp: session ready model=claude-sonnet-5]");
|
|
508
|
+
const iReady = d1.indexOf("[acp: session ready model=claude-sonnet-5-5]");
|
|
509
509
|
const iSent = d1.indexOf("[acp: sending prompt]");
|
|
510
510
|
assert.ok(
|
|
511
511
|
iPrep >= 0 && iReady > iPrep && iSent > iReady,
|
|
@@ -533,7 +533,7 @@ try {
|
|
|
533
533
|
const ctxWithNotice = tctx([
|
|
534
534
|
{ role: "user", content: "real user line", timestamp: 0 },
|
|
535
535
|
mkAssistant([
|
|
536
|
-
{ type: "text", text: "\n[acp: session ready model=claude-sonnet-5]\n", textSignature: MARKER },
|
|
536
|
+
{ type: "text", text: "\n[acp: session ready model=claude-sonnet-5-5]\n", textSignature: MARKER },
|
|
537
537
|
{ type: "text", text: "real assistant line" },
|
|
538
538
|
]),
|
|
539
539
|
]);
|
|
@@ -55,7 +55,7 @@ import { pathToFileURL } from "node:url";
|
|
|
55
55
|
import type { Api, AssistantMessageEvent, Model } from "@earendil-works/pi-ai";
|
|
56
56
|
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
57
57
|
|
|
58
|
-
const sonnet = { id: "claude-sonnet-5" } as unknown as Model<Api>;
|
|
58
|
+
const sonnet = { id: "claude-sonnet-5-5" } as unknown as Model<Api>;
|
|
59
59
|
|
|
60
60
|
type Stream = AsyncIterable<AssistantMessageEvent> & {
|
|
61
61
|
result: () => Promise<{ stopReason: string; errorMessage?: string }>;
|
|
@@ -124,7 +124,7 @@ function makeHarness(recordDir: string, wireReason: string | null) {
|
|
|
124
124
|
createConnection: (_child: any, handlers: any) => ({
|
|
125
125
|
initialize: async () => ({ agentCapabilities: {} }),
|
|
126
126
|
newSession: async () => ({ sessionId: "ACP-1" }),
|
|
127
|
-
setSessionConfigOption: async () => ({}),
|
|
127
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: "model", currentValue: p.value }] }),
|
|
128
128
|
prompt: async ({ sessionId }: any) => {
|
|
129
129
|
await handlers.sessionUpdate({
|
|
130
130
|
update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: "PARTIAL" } },
|
|
@@ -35,7 +35,7 @@ import { pathToFileURL } from "node:url";
|
|
|
35
35
|
import type { Api, AssistantMessageEvent, Message, Model, TranscriptContext } from "@earendil-works/pi-ai";
|
|
36
36
|
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
37
37
|
|
|
38
|
-
const sonnet = { id: "claude-sonnet-5" } as unknown as Model<Api>;
|
|
38
|
+
const sonnet = { id: "claude-sonnet-5-5" } as unknown as Model<Api>;
|
|
39
39
|
|
|
40
40
|
type Stream = AsyncIterable<AssistantMessageEvent>;
|
|
41
41
|
|
|
@@ -93,7 +93,7 @@ function makeHarness(recordDir: string) {
|
|
|
93
93
|
const makeConnection = (_handlers: any) => ({
|
|
94
94
|
initialize: async () => ({ agentCapabilities: {} }),
|
|
95
95
|
newSession: async () => ({ sessionId: "ACP-1" }),
|
|
96
|
-
setSessionConfigOption: async () => ({}),
|
|
96
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: "model", currentValue: p.value }] }),
|
|
97
97
|
prompt: async (params: any) => {
|
|
98
98
|
// Deep-cloned: the capture must be the bytes that crossed the seam, not a
|
|
99
99
|
// live reference a later mutation could rewrite.
|
|
@@ -61,7 +61,7 @@ import type {
|
|
|
61
61
|
import { isContextOverflow, normalizeContext } from "@earendil-works/pi-ai";
|
|
62
62
|
import { calculateContextTokens } from "@earendil-works/pi-coding-agent";
|
|
63
63
|
|
|
64
|
-
const claude = { id: "claude-sonnet-5" } as unknown as Model<Api>;
|
|
64
|
+
const claude = { id: "claude-sonnet-5-5" } as unknown as Model<Api>;
|
|
65
65
|
const cortex = { id: "cortex-auto" } as unknown as Model<Api>;
|
|
66
66
|
|
|
67
67
|
type Stream = AsyncIterable<AssistantMessageEvent>;
|
|
@@ -160,7 +160,7 @@ function makeHarness(recordDir: string, scripts: TurnScript[]) {
|
|
|
160
160
|
newSessionCalls++;
|
|
161
161
|
return { sessionId: "ACP-1" };
|
|
162
162
|
},
|
|
163
|
-
setSessionConfigOption: async () => ({}),
|
|
163
|
+
setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: "model", currentValue: p.value }] }),
|
|
164
164
|
prompt: async ({ sessionId }: any) => {
|
|
165
165
|
const script = scripts[turnIndex++] ?? {};
|
|
166
166
|
const update: Record<string, unknown> = { sessionUpdate: "usage_update" };
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
* FCD-RENDER-PER-RAIL each rail renders its own receipt; no universal tmux prose
|
|
27
27
|
* FCD-HERDR-VIEW-NOT-ADDRESS the herdr receipt says its coordinates are a view
|
|
28
28
|
* FCD-RECOVERY-VISIBLE a post-split failure shows closed vs orphan-unreclaimed:reason
|
|
29
|
+
* FCD-ACP-MODEL-EXACT a pi `entwurf/<id>` that is not an exact curated ACP id refuses on
|
|
30
|
+
* either rail before anything runs (#128)
|
|
29
31
|
*/
|
|
30
32
|
|
|
31
33
|
import assert from "node:assert/strict";
|
|
@@ -112,10 +114,18 @@ async function main(): Promise<void> {
|
|
|
112
114
|
// Up to the FIRST statement of the tmux path, which is the target capability question
|
|
113
115
|
// (#95 lane B added a caller-side one beside it, so `const missing =` is no longer the
|
|
114
116
|
// boundary — it is the line that FOLDS the two).
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
// Fail closed on the anchors: a renamed or reordered line would otherwise slice an EMPTY branch,
|
|
118
|
+
// and every negative `!herdrBranch.includes(…)` below would pass on nothing (#128 review D2).
|
|
119
|
+
// The slice ends at the herdr branch's OWN return, not at a tmux-path statement, so a mutant
|
|
120
|
+
// that edits the tmux path cannot move this boundary and fail this cell for its reason.
|
|
121
|
+
const herdrStart = dispatchCode.indexOf('if (selectFreshCallRail(env) === "herdr")');
|
|
122
|
+
const herdrReturn = 'return { rail: "herdr", result: await herdrFreshCall(';
|
|
123
|
+
const herdrReturnAt = dispatchCode.indexOf(herdrReturn);
|
|
124
|
+
const herdrBranch =
|
|
125
|
+
herdrStart >= 0 && herdrReturnAt > herdrStart
|
|
126
|
+
? dispatchCode.slice(herdrStart, herdrReturnAt + herdrReturn.length)
|
|
127
|
+
: "";
|
|
128
|
+
const herdrBranchLocated = herdrBranch.includes("herdrFreshCall(");
|
|
119
129
|
|
|
120
130
|
// ── no fallback, either direction ────────────────────────────────────────────────────
|
|
121
131
|
const incomplete = await dispatchFreshCall(REQUEST, { HERDR_ENV: "1" }, recordingSpawn().spawn);
|
|
@@ -124,6 +134,37 @@ async function main(): Promise<void> {
|
|
|
124
134
|
incomplete.rail === "herdr" && !incomplete.result.ok && incomplete.result.reason === "herdr-context-missing",
|
|
125
135
|
);
|
|
126
136
|
|
|
137
|
+
// ── #128: an entwurf/<id> pi launch names an EXACT curated ACP id ───────────────────────
|
|
138
|
+
{
|
|
139
|
+
const herdrSpawn = recordingSpawn();
|
|
140
|
+
const pi = (model: string) => ({ ...REQUEST, model });
|
|
141
|
+
const herdrRetired = await dispatchFreshCall(pi("entwurf/claude-sonnet-5"), HERDR_ENV, herdrSpawn.spawn);
|
|
142
|
+
const tmuxRetired = await dispatchFreshCall(pi(" entwurf/claude-opus-5 "), {}, recordingSpawn().spawn);
|
|
143
|
+
const current = recordingSpawn();
|
|
144
|
+
const herdrCurrent = await dispatchFreshCall(pi("entwurf/claude-opus-5-5"), HERDR_ENV, current.spawn);
|
|
145
|
+
const cortex = await dispatchFreshCall(pi("entwurf/cortex-claude-sonnet-5"), {}, recordingSpawn().spawn);
|
|
146
|
+
const otherBackend = await dispatchFreshCall(
|
|
147
|
+
{ ...REQUEST, backend: "claude-code", model: "entwurf/claude-sonnet-5" },
|
|
148
|
+
{},
|
|
149
|
+
recordingSpawn().spawn,
|
|
150
|
+
);
|
|
151
|
+
const reasonOf = (d: Awaited<ReturnType<typeof dispatchFreshCall>>) => (d.result.ok ? null : d.result.reason);
|
|
152
|
+
ok(
|
|
153
|
+
"[QK:FCD-ACP-MODEL-EXACT] a pi sibling under the entwurf provider must name an EXACT curated ACP id: a retired Claude 5 id refuses as acp-model-not-curated on BOTH rails (trimmed like the rails read it) before herdr is asked anything, while the 5.5 id and a Cortex id pass through and another backend is not judged — Pi would otherwise open the retired id on a Cortex row by substring",
|
|
154
|
+
herdrRetired.rail === "herdr" &&
|
|
155
|
+
reasonOf(herdrRetired) === "acp-model-not-curated" &&
|
|
156
|
+
herdrSpawn.calls.length === 0 &&
|
|
157
|
+
tmuxRetired.rail === "tmux" &&
|
|
158
|
+
reasonOf(tmuxRetired) === "acp-model-not-curated" &&
|
|
159
|
+
reasonOf(herdrCurrent) !== "acp-model-not-curated" &&
|
|
160
|
+
current.calls.length > 0 &&
|
|
161
|
+
reasonOf(cortex) !== "acp-model-not-curated" &&
|
|
162
|
+
reasonOf(otherBackend) !== "acp-model-not-curated" &&
|
|
163
|
+
renderDispatchedFreshCall(herdrRetired).text.includes("exact curated ACP id") &&
|
|
164
|
+
renderDispatchedFreshCall(tmuxRetired).text.includes("exact curated ACP id"),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
127
168
|
const nonPilot = recordingSpawn();
|
|
128
169
|
const refused = await dispatchFreshCall({ ...REQUEST, backend: "codex" }, HERDR_ENV, nonPilot.spawn);
|
|
129
170
|
ok(
|
|
@@ -132,6 +173,7 @@ async function main(): Promise<void> {
|
|
|
132
173
|
!refused.result.ok &&
|
|
133
174
|
refused.result.reason === "herdr-backend-unsupported" &&
|
|
134
175
|
nonPilot.calls.length === 0 &&
|
|
176
|
+
herdrBranchLocated &&
|
|
135
177
|
// And structurally: the herdr branch names NEITHER capability preflight — not the
|
|
136
178
|
// target one, and not the caller-seat one #95 lane B put beside it.
|
|
137
179
|
!herdrBranch.includes("codexFreshPreflight") &&
|
|
@@ -153,6 +195,7 @@ async function main(): Promise<void> {
|
|
|
153
195
|
!durableRefused.result.ok &&
|
|
154
196
|
durableRefused.result.reason === "herdr-backend-unsupported" &&
|
|
155
197
|
durable.calls.length === 0 &&
|
|
198
|
+
herdrBranchLocated &&
|
|
156
199
|
!herdrBranch.includes("piDurable"),
|
|
157
200
|
);
|
|
158
201
|
|