@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/demo/README.md
CHANGED
|
@@ -1,193 +1,127 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
# Recorded demos — source checkout recipes
|
|
2
|
+
|
|
3
|
+
## Whole Herdr: Pi ↔ native Claude Code (#138)
|
|
4
|
+
|
|
5
|
+
This is the scene shown by the [README GIF](../README.md). The recording is the **whole
|
|
6
|
+
Herdr client**, with sidebar and tabs, without captions. It observes a real Pi citizen
|
|
7
|
+
opening native Claude Code, receiving its callback, sending one task, and receiving the
|
|
8
|
+
sibling's native Bash result. Camera focus is a view, never an address or delivery receipt.
|
|
9
|
+
|
|
10
|
+
### Original publication versus a new take
|
|
11
|
+
|
|
12
|
+
[Original provenance](./herdr-pi-cc-20261011.provenance.json) records the approved GIF hash,
|
|
13
|
+
original cast hash, measured runtime/models, audited source hashes and render arguments.
|
|
14
|
+
The original raw cast, transcripts, native records and full manifests remain private.
|
|
15
|
+
**A fresh checkout cannot replay that original capture.** The command below creates a
|
|
16
|
+
**new LIVE take of the same scene**, not byte-identical GIF reproduction or renewed
|
|
17
|
+
acceptance of the original. Model text, timing, ids and terminal banners vary. The original
|
|
18
|
+
agg/ffprobe versions were not recorded; current tool versions cannot retroactively fill them.
|
|
19
|
+
|
|
20
|
+
### Prerequisites — no automatic install or repair
|
|
21
|
+
|
|
22
|
+
Run from an installed development **source checkout**, not an npm consumer. The `.ts`
|
|
23
|
+
takes/helpers/tests are maintainer tooling and are deliberately excluded from the tarball.
|
|
24
|
+
|
|
25
|
+
- Node at the `package.json#engines.node` floor; the checkout's pinned dependencies and
|
|
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.1.0**); `entwurf` on PATH must be
|
|
28
|
+
the intended source stable-bin. Source evidence is not registry-package acceptance.
|
|
29
|
+
- `herdr` on PATH at exactly `scripts/fixtures/herdr-supply.json#version`; `asciinema` v3.
|
|
30
|
+
- Herdr's existing Pi integration at
|
|
31
|
+
`${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/extensions/herdr-agent-state.ts`.
|
|
32
|
+
- The operator's existing Claude launcher/auth and vendor data at
|
|
33
|
+
`${XDG_DATA_HOME:-$HOME/.local/share}/claude`. Missing or drifted ownership is a refusal,
|
|
34
|
+
not permission to install, copy credentials or fix global hooks/settings.
|
|
35
|
+
- For offline rendering only: `agg` and `ffprobe` on PATH. Rendering is optional and
|
|
36
|
+
requires the original private `take.cast` **and its same-run manifest**.
|
|
37
|
+
|
|
38
|
+
Selectors for this scene are fixed: Pi `openai-codex/gpt-6.1-sol`, thinking `low`, native
|
|
39
|
+
Claude Code alias `sonnet`. Overrides may only restate these. An OpenRouter base-URL
|
|
40
|
+
redirect is refused before inference; no fallback model or automatic retry is used.
|
|
41
|
+
A requested alias and the native model id observed in its transcript are recorded separately.
|
|
42
|
+
|
|
43
|
+
### Offline verification first
|
|
27
44
|
|
|
28
45
|
```bash
|
|
29
|
-
|
|
46
|
+
./run.sh check-demo-reproducibility
|
|
47
|
+
pnpm exec tsc --noEmit -p scripts/tsconfig.json
|
|
30
48
|
```
|
|
31
49
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
The named source-only floor verb discovers `demo/**/*.test.ts` and runs Node's own test
|
|
51
|
+
runner, including fake-tool/child-process fixtures. It is reached by `pnpm check` and
|
|
52
|
+
`pnpm run check:full`; these are not Vitest tests and are not reached by the adapter beside
|
|
53
|
+
Vitest globs. HOME, writable XDG roots and identity carriers are sandboxed by the verb.
|
|
54
|
+
`agg` and `ffmpeg` are faked, but the renderer/caption fixtures decode real GIF bytes, so
|
|
55
|
+
this floor needs a real `ffprobe` on PATH; without it they fail (`ffprobe ENOENT`) rather
|
|
56
|
+
than skip. CI installs Ubuntu's `ffmpeg` package (which carries `ffprobe`) in the check job.
|
|
57
|
+
The receiving type fence is `scripts/tsconfig.json`, paired with the root `demo` exclusion.
|
|
39
58
|
|
|
40
|
-
|
|
59
|
+
### Explicit new LIVE take
|
|
41
60
|
|
|
42
|
-
|
|
43
|
-
public README. Two pi sessions run side-by-side in one tmux window;
|
|
44
|
-
a background driver types the scene prompts into the sender pane.
|
|
45
|
-
|
|
46
|
-
1. **Spawn** a sonnet sibling (memory write — "tempered indigo").
|
|
47
|
-
2. **Resume** that same sibling (memory recall — identity preservation,
|
|
48
|
-
the #9 regression guard).
|
|
49
|
-
3. **Cross-session greeting** via `entwurf_send`. *(Historical: `entwurf_send` was retired in the
|
|
50
|
-
0.12 cutover, and the "sent box" half of that pairing was never actually reachable — the
|
|
51
|
-
sender-side renderer had zero producers and was removed in #120 P3. What the recording shows on
|
|
52
|
-
the receive side is real; the send side is a box the operator could not have seen.)*
|
|
53
|
-
|
|
54
|
-
Run:
|
|
61
|
+
Only run after deciding to spend real subscription turns:
|
|
55
62
|
|
|
56
63
|
```bash
|
|
57
|
-
|
|
64
|
+
LIVE=1 node demo/take-herdr-pi-cc.ts
|
|
58
65
|
```
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
With approved selectors/policy and without `LIVE=1`, it exits 97 **before tool preflight
|
|
68
|
+
or resource creation**. Invalid selectors or an OpenRouter redirect can refuse earlier
|
|
69
|
+
with exit 2, still without tools or writes. Once authorized, it creates a private Herdr
|
|
70
|
+
server/fence, starts the scene, and stops only resources whose
|
|
71
|
+
ownership it can prove. Failure ends observation; it cannot undo a citizen's completed work.
|
|
72
|
+
No automatic retry, rerender or promotion follows a failure. Failed evidence is retained.
|
|
63
73
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
The fence isolates Herdr XDG state and Entwurf record/mailbox roots, **not the whole host**:
|
|
75
|
+
HOME/auth/vendor stores remain real; the existing Claude data directory is linked into the
|
|
76
|
+
private fence. Pi's control socket and the addressed v2 lock use real `~/.pi/` directories.
|
|
77
|
+
The native transcripts, records and fence are kept, not erased by teardown. Do not run this
|
|
78
|
+
against an operator server or broaden cleanup to unrelated sessions.
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
The default output is `.tmp-verify/138-demo-gifs/takes/<take-id>/` (ignored); an explicit
|
|
81
|
+
`ENTWURF_DEMO_OUT` changes its parent. `manifest.json` contains assertions, camera views,
|
|
82
|
+
raw receipts, measured models and the cast digest. The stage describes source **tooling**;
|
|
83
|
+
`entwurfOnPath` is observed, not checked against this checkout's stable-bin. Confirm that
|
|
84
|
+
binding separately before calling a take source-runtime evidence. Git HEAD and dirty state
|
|
85
|
+
are observed; a dirty HEAD is **not** an immutable source fingerprint.
|
|
86
|
+
Keep the exact source and manifest with any subsequent take you intend to audit.
|
|
70
87
|
|
|
71
|
-
|
|
72
|
-
tail -F demo/sender-debug.log demo/peer-debug.log
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Quick post-run greps:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
grep 'entwurf:debug' demo/sender-debug.log
|
|
79
|
-
grep -E '(entwurf|model-switch)' demo/sender-debug.log
|
|
80
|
-
```
|
|
88
|
+
### Offline rendering and publication boundary
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
### `demo.sh` (entwurf two-pane)
|
|
85
|
-
|
|
86
|
-
| Var | Default | Meaning |
|
|
87
|
-
|---|---|---|
|
|
88
|
-
| `PEER_MODEL` | `entwurf/gpt-5.4` (≡ `piat`) | top pane backend (receives greeting) |
|
|
89
|
-
| `SENDER_MODEL` | `entwurf/claude-sonnet-5` (≡ `pias`) | bottom pane backend (drives scenes) |
|
|
90
|
-
| `SCENE_DELAY` | `25` | seconds to wait for each scene's agent work |
|
|
91
|
-
| `WARMUP` | `3` | seconds to wait for both pi banners |
|
|
92
|
-
| `FINAL_PAUSE` | `5` | extra wait after scene 3 (peer reply lag) |
|
|
93
|
-
| `GIF_SPEED` | `2.8` | agg playback multiplier (cast time → GIF time) |
|
|
94
|
-
| `SESSION` | `entwurf-demo` | tmux session name |
|
|
95
|
-
| `OUTDIR` | `demo/` | debug log directory (cast + gif live in `docs/assets/`) |
|
|
96
|
-
|
|
97
|
-
### `demo-baseline.sh` (single-pane baseline + entwurf)
|
|
98
|
-
|
|
99
|
-
| Var | Default | Meaning |
|
|
100
|
-
|---|---|---|
|
|
101
|
-
| `DRIVER_MODEL` | `entwurf/claude-sonnet-5` (≡ `pias`) | the driven pane — answers Q-B0 and spawns the sibling |
|
|
102
|
-
| `SIBLING_MODEL` | `entwurf/gpt-5.4` (≡ `piat`) | cross-backend sibling spawned in scene 2 |
|
|
103
|
-
| `SIBLING_CWD` | repo root | cwd passed to the entwurf sibling (drives `<project-context>` injection) |
|
|
104
|
-
| `SCENE1_DELAY` | `60` | seconds for the long English baseline answer |
|
|
105
|
-
| `SCENE2_DELAY` | `30` | seconds for the sync entwurf round-trip |
|
|
106
|
-
| `WARMUP` | `3` | seconds to wait for the pi banner |
|
|
107
|
-
| `FINAL_PAUSE` | `4` | extra wait after scene 2 |
|
|
108
|
-
| `GIF_SPEED` | `2.0` | agg playback multiplier (slower than `demo.sh` — the answer rewards reading) |
|
|
109
|
-
| `GIF_COMPRESS` | `1` | run gifsicle after agg (`0` to skip) |
|
|
110
|
-
| `GIF_LOSSY` | `200` | gifsicle `--lossy` strength (drop to `80` if a future scenario shows artifacts) |
|
|
111
|
-
| `GIF_COLORS` | `64` | gifsicle `--colors` palette size |
|
|
112
|
-
| `SESSION` | `entwurf-baseline-demo` | tmux session name |
|
|
113
|
-
| `OUTDIR` | `demo/` | debug log directory (cast + gif live in `docs/assets/`) |
|
|
114
|
-
|
|
115
|
-
### Model ↔ alias map
|
|
116
|
-
|
|
117
|
-
The demo launch lines correspond to your shell aliases:
|
|
118
|
-
|
|
119
|
-
| Alias | Model id | Use as |
|
|
120
|
-
|---|---|---|
|
|
121
|
-
| `piao` | `entwurf/claude-opus-5` | `SENDER_MODEL` (precision scenes) |
|
|
122
|
-
| `pias` | `entwurf/claude-sonnet-5` | `SENDER_MODEL` (default) |
|
|
123
|
-
| `piat` | `entwurf/gpt-5.4` | `PEER_MODEL` (default) |
|
|
124
|
-
| `piat5` | `entwurf/gpt-5.5` | either |
|
|
125
|
-
| `piag` | `entwurf/gemini-3.1-pro-preview` | either |
|
|
126
|
-
|
|
127
|
-
Each pane runs with `ENTWURF_DEBUG=1` baked in — same shape as
|
|
128
|
-
the aliases — so every spawn/model-switch/entwurf trace lands in the
|
|
129
|
-
debug log files (see below).
|
|
130
|
-
|
|
131
|
-
Examples:
|
|
90
|
+
Given an existing same-run private take, no model turn is needed:
|
|
132
91
|
|
|
133
92
|
```bash
|
|
134
|
-
|
|
135
|
-
PEER_MODEL=entwurf/gemini-3.1-pro-preview bash demo.sh
|
|
136
|
-
|
|
137
|
-
# Faster pacing for short retake
|
|
138
|
-
SCENE_DELAY=30 FINAL_PAUSE=10 bash demo.sh
|
|
139
|
-
|
|
140
|
-
# All three backends in one demo would need a 3-pane variant — out of scope here.
|
|
93
|
+
node demo/render-take.ts <private-take-dir> <private-output.gif>
|
|
141
94
|
```
|
|
142
95
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
- `demo/demo.sh`
|
|
177
|
-
- `demo/demo-baseline.sh`
|
|
178
|
-
- `demo/README.md`
|
|
179
|
-
- `docs/assets/entwurf-demo.gif` — archived pre-0.12 README demo GIF (no longer `package.json#pi.image`)
|
|
180
|
-
- `docs/assets/entwurf-entwurf.gif` — archived pre-0.12 Entwurf section illustration
|
|
181
|
-
|
|
182
|
-
Ignored (regenerable recording artifacts — both demos):
|
|
183
|
-
|
|
184
|
-
- `*.cast` (global rule — applies under `docs/assets/` too)
|
|
185
|
-
- `demo/*.log`
|
|
186
|
-
|
|
187
|
-
Both demos write their recording artifacts directly into `docs/assets/`
|
|
188
|
-
using the names above. Re-running a demo overwrites the previous take so
|
|
189
|
-
the published preview always matches `main`. The `.cast` source stays
|
|
190
|
-
local (global `*.cast` ignore); only the `.gif` rides the publish surface.
|
|
191
|
-
The `gifsicle` step inside `demo-baseline.sh` makes the take reproducible
|
|
192
|
-
without a manual compression pass — see the Tunables table for tuning the
|
|
193
|
-
lossy/colors flags.
|
|
96
|
+
The renderer verifies cast digest, trims only recorder-only teardown, and promotes the
|
|
97
|
+
private GIF only after agg exits successfully and ffprobe decodes it. Each render keeps a
|
|
98
|
+
uniquely named `.render.cast` beside its output (also on failure), with name/digest in a
|
|
99
|
+
successful sidecar; the original take's `take.render.cast` is never overwritten. Non-live-pass takes
|
|
100
|
+
can render only to a private FAILED name. A mixed subject/teardown event is retained whole.
|
|
101
|
+
The renderer refuses repository `docs/assets/` destinations, including parent-directory
|
|
102
|
+
aliases: **publication is a separate reviewed copy**, never a rendering side effect.
|
|
103
|
+
|
|
104
|
+
Its email/company-path scan is deliberately narrow, **not a privacy/public-readability
|
|
105
|
+
certificate**: personal paths, hostnames, footers and native progress language can remain.
|
|
106
|
+
The operator approved the original rough pixels with their disclosed limitations. A new
|
|
107
|
+
render requires its own review; it must not overwrite the approved original implicitly.
|
|
108
|
+
The GIF and sidecar rename separately; a crash between them leaves a mismatched pair, not
|
|
109
|
+
atomic two-file publication. Optional `caption-take.ts` produces a separate derivative;
|
|
110
|
+
it is not this uncaptioned publication recipe.
|
|
111
|
+
|
|
112
|
+
## Other source scenes
|
|
113
|
+
|
|
114
|
+
`take-pi-durable.ts`, `take-pi-acp.ts` and `take-pi-cc.ts` record **different**, private tmux
|
|
115
|
+
scenes. Each requires its own explicit `LIVE=1` and, like the Herdr scene, refuses a
|
|
116
|
+
non-approved selector or an OpenRouter base-URL redirect first (exit 2, no tools or writes).
|
|
117
|
+
They are not the Herdr GIF's recipe.
|
|
118
|
+
Their helper tests remain in the same offline discovery verb. Original FAILED takes and
|
|
119
|
+
offline rejudgements are distinct evidence, never replacement LIVE acceptance.
|
|
120
|
+
|
|
121
|
+
## Archived pre-0.12 scripts
|
|
122
|
+
|
|
123
|
+
`demo-baseline.sh` and `demo.sh` retain their historical bodies for comparison, but now
|
|
124
|
+
**refuse immediately with exit 2**, even with `LIVE=1`. They cannot launch retired v1 flows
|
|
125
|
+
or overwrite today's published assets. Historical commands, selectors and old output paths
|
|
126
|
+
inside those bodies are tombstones, not runnable advice. Scene 3's current deterministic
|
|
127
|
+
delivery proof is `./run.sh check-bridge-delivery`; do not revive the v1 route.
|
package/demo/demo-baseline.sh
CHANGED
|
@@ -25,7 +25,11 @@
|
|
|
25
25
|
|
|
26
26
|
set -euo pipefail
|
|
27
27
|
|
|
28
|
-
#
|
|
28
|
+
# Hard retirement: the preserved v1 body must never launch or overwrite today's approved GIF.
|
|
29
|
+
printf '%s\n' 'entwurf demo: archived v1 baseline refused; see demo/README.md for the source-only Herdr recipe.' >&2
|
|
30
|
+
exit 2
|
|
31
|
+
|
|
32
|
+
# ---------- historical body (unreachable) ----------
|
|
29
33
|
SESSION=${SESSION:-entwurf-baseline-demo}
|
|
30
34
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
31
35
|
REPO_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
|
package/demo/demo.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# retired v1 verbs (`entwurf` / `entwurf_resume` / `entwurf_send`), which the 0.12
|
|
6
6
|
# cutover removed; a run would fail at scene 1. Kept for historical comparison only.
|
|
7
7
|
# See demo/README.md. Scene 3's delivery proof was recovered as a deterministic gate
|
|
8
|
-
# (scripts/check-bridge-delivery.ts);
|
|
8
|
+
# (scripts/check-bridge-delivery.ts); current v2-native source recipes live in demo/README.md.
|
|
9
9
|
#
|
|
10
10
|
#
|
|
11
11
|
# MODEL NOTE (2026-08-20). The model ids below are the ARCHIVED values these
|
|
@@ -28,7 +28,11 @@
|
|
|
28
28
|
|
|
29
29
|
set -euo pipefail
|
|
30
30
|
|
|
31
|
-
#
|
|
31
|
+
# Hard retirement: preserve historical source, not a second runnable v1 route.
|
|
32
|
+
printf '%s\n' 'entwurf demo: archived v1 peer scene refused; see demo/README.md for current source recipes.' >&2
|
|
33
|
+
exit 2
|
|
34
|
+
|
|
35
|
+
# ---------- historical body (unreachable) ----------
|
|
32
36
|
SESSION=${SESSION:-entwurf-demo}
|
|
33
37
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
34
38
|
REPO_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"scene": "whole-Herdr Pi to native Claude Code",
|
|
4
|
+
"take": "take-herdr-pi-cc-2026-10-11T11-57-22-297Z",
|
|
5
|
+
"startedAt": "2026-10-11T11:57:22.299Z",
|
|
6
|
+
"stage": "SOURCE checkout, not an npm consumer",
|
|
7
|
+
"gif": {
|
|
8
|
+
"path": "docs/assets/entwurf-demo.gif",
|
|
9
|
+
"sha256": "b686739b5c68fa458dd47c844711aea0f6ab5819eb3eb5d2850449922fa084c0",
|
|
10
|
+
"bytes": 1835518,
|
|
11
|
+
"seconds": 43.5,
|
|
12
|
+
"width": 1126,
|
|
13
|
+
"height": 764,
|
|
14
|
+
"frames": 340,
|
|
15
|
+
"captions": false
|
|
16
|
+
},
|
|
17
|
+
"cast": {
|
|
18
|
+
"sha256": "bbf8a409c58f8b48e88346d232a66ee41ac0c2acf8e605a3f568fd7d40857cf2",
|
|
19
|
+
"public": false
|
|
20
|
+
},
|
|
21
|
+
"runtime": {
|
|
22
|
+
"entwurf": "0.34.2",
|
|
23
|
+
"pi": "1.1.0",
|
|
24
|
+
"herdr": "0.9.3",
|
|
25
|
+
"gitHead": "9f4c9983fed1dd0ba619bc5a50f0f5abccefeef4",
|
|
26
|
+
"gitDirty": true,
|
|
27
|
+
"measuredPiModel": "openai-codex/gpt-6.1-sol",
|
|
28
|
+
"requestedClaudeAlias": "sonnet",
|
|
29
|
+
"measuredClaudeNativeModel": "claude-sonnet-5-5"
|
|
30
|
+
},
|
|
31
|
+
"auditedOriginalSourceSha256": {
|
|
32
|
+
"demo/take-herdr-pi-cc.ts": "f9cd30f1bb5b06236559a9f1283f0e030b300f6a17858d778c40d246327a6349",
|
|
33
|
+
"demo/lib/take-recorder.ts": "d47d824b90a7265c5ab6e7c4bf313b4f81b27cef836b4ddd15f971f3de386256",
|
|
34
|
+
"demo/render-take.ts": "f6e68157d7f8fb1f0e00a359efaa04aa30eeca1112ce2571661f39aea826b55d",
|
|
35
|
+
"demo/lib/herdr-take.ts": "57089d883323b2582aadd7f54f63cfce541f3a2c584bb6940313fbcdc940d83e"
|
|
36
|
+
},
|
|
37
|
+
"render": {
|
|
38
|
+
"aggArgs": [
|
|
39
|
+
"--idle-time-limit",
|
|
40
|
+
"2",
|
|
41
|
+
"--speed",
|
|
42
|
+
"1",
|
|
43
|
+
"--last-frame-duration",
|
|
44
|
+
"8",
|
|
45
|
+
"--font-size",
|
|
46
|
+
"14",
|
|
47
|
+
"--theme",
|
|
48
|
+
"monokai"
|
|
49
|
+
],
|
|
50
|
+
"trimmedTailEvents": 2,
|
|
51
|
+
"trimmedWhat": "recorder teardown: herdr client exit (alt-screen exit + server shut down line)",
|
|
52
|
+
"renderedAt": "2026-10-11T11:58:32.828Z",
|
|
53
|
+
"aggVersion": null,
|
|
54
|
+
"ffprobeVersion": null,
|
|
55
|
+
"toolVersionEvidence": "not recorded for this original; current versions cannot fill historical fields"
|
|
56
|
+
},
|
|
57
|
+
"limits": [
|
|
58
|
+
"Original cast, manifests and native records remain private; this tree cannot replay that capture.",
|
|
59
|
+
"The tracked command is a new LIVE take recipe, not byte-identical reproduction.",
|
|
60
|
+
"Current maintainer source is amended; original source hashes are historical observations, not a complete import-closure seal.",
|
|
61
|
+
"Operator approved the rough original pixels; this is not privacy or public-readability certification.",
|
|
62
|
+
"No claim of 0.35.0 release, published-package acceptance or future Herdr plugin observation follows from this take."
|
|
63
|
+
]
|
|
64
|
+
}
|
package/docs/acp-backend-rail.md
CHANGED
|
@@ -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
|
|
Binary file
|
package/docs/setup-clean-host.md
CHANGED
|
@@ -913,7 +913,7 @@ operator-installed Claude runtime. Resolution order:
|
|
|
913
913
|
2. `require.resolve(...)` against the bundled `@agentclientprotocol/claude-agent-acp`.
|
|
914
914
|
3. `PATH:claude-agent-acp` fallback if package resolution fails.
|
|
915
915
|
|
|
916
|
-
The curated Claude ids are `claude-opus-5`, `claude-sonnet-5`, and
|
|
916
|
+
The curated Claude ids are `claude-opus-5-5`, `claude-sonnet-5-5`, and
|
|
917
917
|
`claude-fable-5-1`. Cortex exposes `cortex-auto`, `cortex-claude-opus-5`,
|
|
918
918
|
`cortex-claude-sonnet-5`, and `cortex-openai-gpt-5.4`.
|
|
919
919
|
`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
|
+
}
|