@junghanacs/entwurf 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -3
- package/CHANGELOG.md +337 -0
- package/README.md +8 -11
- package/VERIFY.md +8 -1
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +25 -14
- package/docs/setup-clean-host.md +24 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +34 -10
- package/package.json +10 -10
- package/pi-extensions/lib/acp/acp-client.ts +57 -4
- package/pi-extensions/lib/acp/backend-adapter.ts +78 -9
- package/pi-extensions/lib/acp/backend.ts +578 -18
- package/pi-extensions/lib/acp/claude-acp-launch.js +100 -0
- package/pi-extensions/lib/acp/event-mapper.ts +43 -6
- package/run.sh +129 -32
- package/scripts/check-acp-launch-namespace.ts +127 -0
- package/scripts/check-acp-prompt-lifecycle.ts +145 -2
- package/scripts/check-acp-stop-reason.ts +8 -2
- package/scripts/check-acp-usage-accounting.ts +1074 -0
- package/scripts/check-copilot-birth-hook.ts +28 -1
- package/scripts/check-gate-qualification.ts +4 -2
- package/scripts/check-omp-fresh-preflight.ts +27 -0
- package/scripts/check-setup-qualification.sh +40 -2
- package/scripts/copilot-bridge-oracle.sh +14 -6
- package/scripts/fake-copilot-vendor.sh +4 -2
- package/scripts/lib/pi-record-discovery.ts +47 -0
- package/scripts/mutants/acp-launch-namespace.json +34 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +67 -2
- package/scripts/mutants/acp-stream-hooks.json +4 -2
- package/scripts/mutants/acp-usage-accounting.json +181 -0
- package/scripts/mutants/copilot-birth.json +3 -5
- package/scripts/mutants/pack-install.json +2 -2
- package/scripts/mutants/setup-verdict.json +35 -0
- package/scripts/omp-config-xdev.py +310 -0
- package/scripts/omp-config-xdev.sh +76 -0
- package/scripts/omp-tool-surface.py +61 -10
- package/scripts/raw-acp-child-exit-measure/README.md +285 -0
- package/scripts/raw-acp-child-exit-measure/acp-turn-population.py +89 -0
- package/scripts/raw-acp-child-exit-measure/reaper-correlation.py +47 -0
- package/scripts/smoke-acp-bundled-mcp-live.ts +2 -2
- package/scripts/smoke-acp-cortex-live.ts +2 -2
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
- package/scripts/smoke-acp-v2-send-live.ts +2 -2
- package/scripts/smoke-entwurf-v2-matrix-live.ts +60 -10
- package/scripts/smoke-mux-lifecycle-live.ts +46 -2
- package/scripts/smoke-setup-verdict.sh +48 -3
|
@@ -213,6 +213,46 @@ function pidIsAlive(pid: number): boolean {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
/**
|
|
217
|
+
* What the launched window actually shows, read at the moment a nonce callback never arrived.
|
|
218
|
+
*
|
|
219
|
+
* The launch receipt says so itself — "if it never comes, the window is visible and can be read
|
|
220
|
+
* directly" — but on a headless release-gate run nobody is there to look, and the window is torn
|
|
221
|
+
* down in teardown seconds later. So the harness looks for the operator: is the pane process even
|
|
222
|
+
* alive, and what is painted in it. That separates the three states a bare timeout collapses into
|
|
223
|
+
* one — the runtime never started (or died), it started and is sitting on an error or a prompt, or
|
|
224
|
+
* it is running fine and the model simply did not call the callback tool. A pi-native callback
|
|
225
|
+
* timeout with no window evidence is what blocked the second 0.17.0 `--cut` (and, in the same
|
|
226
|
+
* shape, a 0.16.1 run), leaving nothing to tell those three apart afterwards.
|
|
227
|
+
*
|
|
228
|
+
* Diagnostics must never become the failure: every step is best-effort, and a tmux that cannot
|
|
229
|
+
* answer is reported as its own line rather than thrown.
|
|
230
|
+
*/
|
|
231
|
+
function paneForensics(label: string, socket: string, env: NodeJS.ProcessEnv, coords: Coordinates): string {
|
|
232
|
+
const lines: string[] = [`--- ${label} window forensics (the callback never came) ---`];
|
|
233
|
+
try {
|
|
234
|
+
lines.push(`pane ${coords.paneId} pid ${coords.panePid}: ${pidIsAlive(Number(coords.panePid)) ? "ALIVE" : "GONE"}`);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
lines.push(`pane pid liveness unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
237
|
+
}
|
|
238
|
+
const panes = tmux(
|
|
239
|
+
socket,
|
|
240
|
+
["list-panes", "-a", "-F", "#{pane_id} #{pane_pid} #{pane_dead} #{pane_current_command}"],
|
|
241
|
+
env,
|
|
242
|
+
);
|
|
243
|
+
lines.push(
|
|
244
|
+
panes.status === 0 ? `list-panes:\n${panes.stdout || "(none)"}` : `list-panes failed (status ${panes.status})`,
|
|
245
|
+
);
|
|
246
|
+
const captured = tmux(socket, ["capture-pane", "-p", "-t", coords.paneId], env);
|
|
247
|
+
if (captured.status === 0) {
|
|
248
|
+
const tail = captured.stdout.split("\n").slice(-40).join("\n");
|
|
249
|
+
lines.push(`capture-pane (last 40 lines):\n${tail || "(the pane painted nothing)"}`);
|
|
250
|
+
} else {
|
|
251
|
+
lines.push(`capture-pane failed (status ${captured.status}) — the window is already gone`);
|
|
252
|
+
}
|
|
253
|
+
return lines.join("\n");
|
|
254
|
+
}
|
|
255
|
+
|
|
216
256
|
async function waitForPidsGone(pids: ReadonlySet<number>, timeoutMs = 10_000): Promise<boolean> {
|
|
217
257
|
const deadline = Date.now() + timeoutMs;
|
|
218
258
|
while (Date.now() < deadline) {
|
|
@@ -704,7 +744,9 @@ async function main(): Promise<void> {
|
|
|
704
744
|
ok(
|
|
705
745
|
`${cell}: the nonce came back and its SENDER ENVELOPE carries a garden id — correlation without asking the sibling`,
|
|
706
746
|
gid !== null && GARDEN_ID.test(gid),
|
|
707
|
-
|
|
747
|
+
gid === null
|
|
748
|
+
? `--- launch receipt ---\n${launch.text}\n${paneForensics(cell, srv.socket, srv.env, coords)}`
|
|
749
|
+
: `--- launch receipt ---\n${launch.text}`,
|
|
708
750
|
);
|
|
709
751
|
const citizen = gid as string;
|
|
710
752
|
siblingGids.add(citizen);
|
|
@@ -936,7 +978,9 @@ async function main(): Promise<void> {
|
|
|
936
978
|
ok(
|
|
937
979
|
"claude-code: the nonce came back and its SENDER ENVELOPE carries a garden id",
|
|
938
980
|
ccGid !== null && GARDEN_ID.test(ccGid),
|
|
939
|
-
|
|
981
|
+
ccGid === null
|
|
982
|
+
? `--- launch receipt ---\n${ccLaunch.text}\n${paneForensics("claude-code", cc.socket, cc.env, ccCoords)}`
|
|
983
|
+
: `--- launch receipt ---\n${ccLaunch.text}`,
|
|
940
984
|
);
|
|
941
985
|
const ccCitizen = ccGid as string;
|
|
942
986
|
siblingGids.add(ccCitizen);
|
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
# gate. This fixture drives the REAL `run.sh setup` end to end in a sandbox and
|
|
8
8
|
# pins exactly the #86 C1 acceptance cells:
|
|
9
9
|
#
|
|
10
|
-
# S-1 all-harness-absent → core PASS, pi/claude/agy SKIP, computed green,
|
|
10
|
+
# S-1 all-harness-absent → core PASS, pi/claude/agy/copilot/omp SKIP, computed green,
|
|
11
11
|
# zero harness writes, credential store untouched
|
|
12
12
|
# S-2 pi below floor → detected FAIL (never SKIP), nonzero, no Pi wiring
|
|
13
13
|
# S-3 pi at floor → presence completes project+user wiring, green,
|
|
14
14
|
# credential store still byte-identical
|
|
15
15
|
# S-4 agy detected+corrupt→ named component FAIL + NON-GREEN + nonzero exit,
|
|
16
16
|
# core and later components still attempted
|
|
17
|
+
# S-8 omp present → the four omp units compose (birth/MCP/tools.xdev
|
|
18
|
+
# setting/receiver), states exist, second run idempotent
|
|
17
19
|
# S-5 installed mode → the installed-vs-source branch is a NAMED verdict
|
|
18
20
|
# printed before anything else, and never reaches
|
|
19
21
|
# the source pnpm bootstrap
|
|
@@ -25,7 +27,8 @@
|
|
|
25
27
|
# below-floor and false-green shapes as living end-to-end evidence.
|
|
26
28
|
#
|
|
27
29
|
# Deterministic: no model, no network, no cost. Presence probes are pinned via
|
|
28
|
-
# PI_BIN / CLAUDE_BIN / AGY_BIN (the same hermetic seam
|
|
30
|
+
# PI_BIN / CLAUDE_BIN / AGY_BIN / COPILOT_BIN / OMP_BIN (the same hermetic seam
|
|
31
|
+
# smoke-agy-install-state
|
|
29
32
|
# uses), and every write root is sandboxed: HOME, XDG roots, the pi agent dir,
|
|
30
33
|
# and the dev-bin dir. The source bootstrap (`pnpm install --frozen-lockfile`)
|
|
31
34
|
# runs against the ALREADY-INSTALLED checkout, which is a verified no-op under a
|
|
@@ -64,7 +67,7 @@ mkdir -p "$HOME" "$PI_CODING_AGENT_DIR" "$SB/bin" "$SB/harness"
|
|
|
64
67
|
# Presence pins: default every harness to a definitely-absent path; each cell
|
|
65
68
|
# re-pins what it needs. Production leaves these unset.
|
|
66
69
|
ABSENT="$SB/harness/definitely-absent"
|
|
67
|
-
export PI_BIN="$ABSENT" CLAUDE_BIN="$ABSENT" AGY_BIN="$ABSENT" COPILOT_BIN="$ABSENT"
|
|
70
|
+
export PI_BIN="$ABSENT" CLAUDE_BIN="$ABSENT" AGY_BIN="$ABSENT" COPILOT_BIN="$ABSENT" OMP_BIN="$ABSENT"
|
|
68
71
|
|
|
69
72
|
PASS=0
|
|
70
73
|
ok() { PASS=$((PASS + 1)); printf ' ok %s\n' "$*"; }
|
|
@@ -98,6 +101,7 @@ want "S-1: pi absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\" | gr
|
|
|
98
101
|
want "S-1: claude absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\" | grep -q 'claude: SKIP'"
|
|
99
102
|
want "S-1: agy absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\" | grep -q 'agy: SKIP'"
|
|
100
103
|
want "S-1: copilot absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\" | grep -q 'copilot: SKIP'"
|
|
104
|
+
want "S-1: omp absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\" | grep -q 'omp: SKIP'"
|
|
101
105
|
want "S-1: core bridge boundary validated (PASS)" "printf '%s' \"\$OUT\" | grep -q 'core: PASS'"
|
|
102
106
|
want "S-1: final verdict is computed, not unconditional" \
|
|
103
107
|
"printf '%s' \"\$OUT\" | grep -q 'result: green (computed from the component outcomes above)'"
|
|
@@ -107,6 +111,7 @@ want "S-1: zero Pi wiring written for an absent pi" "[ ! -e '$PROJ1/.pi' ]"
|
|
|
107
111
|
want "S-1: no user-scope pi settings were created" "[ ! -e '$PI_CODING_AGENT_DIR/settings.json' ]"
|
|
108
112
|
want "S-1: no agy config was created" "[ ! -e '$HOME/.gemini' ]"
|
|
109
113
|
want "S-1: no Copilot config/units were created" "[ ! -e '$HOME/.copilot' ]"
|
|
114
|
+
want "S-1: no OMP config/units were created" "[ ! -e '$HOME/.omp' ]"
|
|
110
115
|
want_auth_untouched "S-1"
|
|
111
116
|
|
|
112
117
|
# ── S-2: pi resolvable but BELOW floor → detected FAIL, never SKIP, no writes ──
|
|
@@ -242,6 +247,46 @@ want_auth_untouched "S-7"
|
|
|
242
247
|
rm -rf "$HOME/.copilot" "$XDG_DATA_HOME/entwurf/copilot-mcp" "$XDG_DATA_HOME/entwurf/copilot-receive" \
|
|
243
248
|
"$XDG_DATA_HOME/entwurf/copilot-statusline"
|
|
244
249
|
|
|
250
|
+
# ── S-8: omp present → the FOUR omp units compose, including the operator setting ──
|
|
251
|
+
# OMP shipped in v0.16.0 with installers, doctors and inverses for every unit, but
|
|
252
|
+
# `setup` did not compose it — the fifth backend was a hand-run verb list and the
|
|
253
|
+
# `tools.xdev` setting was a documentation step, so a one-command host came up with
|
|
254
|
+
# an omp citizen whose MCP tools the model could not call. This cell is the standing
|
|
255
|
+
# consumer of that composition. The omp unit scripts touch NO vendor process: they
|
|
256
|
+
# only probe `omp` on PATH and write into the agent dir, so a stub binary is a
|
|
257
|
+
# faithful presence pin and every write lands in the sandbox HOME.
|
|
258
|
+
echo "[smoke-setup-verdict] S-8 omp present — four-unit composition"
|
|
259
|
+
PROJ8="$SB/proj8"; mkdir -p "$PROJ8"
|
|
260
|
+
FAKE_OMP="$SB/harness/omp-fake"; mkdir -p "$FAKE_OMP"
|
|
261
|
+
printf '#!/usr/bin/env bash\necho "omp/18.0.0"\n' > "$FAKE_OMP/omp"
|
|
262
|
+
chmod +x "$FAKE_OMP/omp"
|
|
263
|
+
seed_auth
|
|
264
|
+
# This sandbox exports PI_CODING_AGENT_DIR, which omp reads too — the oracle refuses
|
|
265
|
+
# that ambiguity by design (ledger M6), so the cell names the agent dir explicitly, the
|
|
266
|
+
# same seam a real operator on a pi-rail host would use.
|
|
267
|
+
OMP_AGENT="$HOME/.omp/agent"
|
|
268
|
+
set +e; OUT="$(OMP_BIN="$FAKE_OMP/omp" ENTWURF_OMP_AGENT_DIR="$OMP_AGENT" PATH="$FAKE_OMP:$PATH" bash "$REPO_DIR/run.sh" setup "$PROJ8" 2>&1)"; RC=$?; set -e
|
|
269
|
+
want "S-8: omp-present setup exits 0 (four units + core all green)" "[ '$RC' -eq 0 ]"
|
|
270
|
+
want "S-8: birth unit composed as PASS" "printf '%s' \"\$OUT\" | grep -q 'omp-birth: PASS'"
|
|
271
|
+
want "S-8: MCP unit composed as PASS" "printf '%s' \"\$OUT\" | grep -q 'omp-mcp: PASS'"
|
|
272
|
+
want "S-8: the tools.xdev operator setting composed as PASS" "printf '%s' \"\$OUT\" | grep -q 'omp-config: PASS'"
|
|
273
|
+
want "S-8: receiver unit composed as PASS" "printf '%s' \"\$OUT\" | grep -q 'omp-receive: PASS'"
|
|
274
|
+
want "S-8: computed summary is green" \
|
|
275
|
+
"printf '%s' \"\$OUT\" | grep -q 'result: green (computed from the component outcomes above)'"
|
|
276
|
+
want "S-8: all four package-owned install-states exist (each unit keeps its inverse authority)" \
|
|
277
|
+
"[ -f '$XDG_DATA_HOME/entwurf/omp-bridge/install-state.json' ] && [ -f '$XDG_DATA_HOME/entwurf/omp-mcp/install-state.json' ] && [ -f '$XDG_DATA_HOME/entwurf/omp-config/install-state.json' ] && [ -f '$XDG_DATA_HOME/entwurf/omp-receive/install-state.json' ]"
|
|
278
|
+
want "S-8: both extension units landed in the sandbox omp agent dir" \
|
|
279
|
+
"[ -d '$HOME/.omp/agent/extensions/entwurf-meta-omp' ] && [ -d '$HOME/.omp/agent/extensions/entwurf-receive-omp' ]"
|
|
280
|
+
want "S-8: the MCP hand wrote the pinned key and the setting reached the config the vendor reads" \
|
|
281
|
+
"grep -q 'entwurf-bridge' '$HOME/.omp/agent/mcp.json' && [ \"\$(python3 '$REPO_DIR/scripts/omp-tool-surface.py' '$HOME/.omp/agent' | awk '/^verdict /{print \$2}')\" = 'xdev-off' ]"
|
|
282
|
+
want "S-8: setup is idempotent — a second run over the composed host is still green" \
|
|
283
|
+
"OMP_BIN='$FAKE_OMP/omp' ENTWURF_OMP_AGENT_DIR='$HOME/.omp/agent' PATH='$FAKE_OMP:$PATH' bash '$REPO_DIR/run.sh' setup '$PROJ8' 2>&1 | grep -q 'result: green (computed from the component outcomes above)'"
|
|
284
|
+
want_auth_untouched "S-8"
|
|
285
|
+
# Reset the composed OMP state so the next cell decides on a clean host.
|
|
286
|
+
rm -rf "$HOME/.omp" "$XDG_DATA_HOME/entwurf/omp-bridge" "$XDG_DATA_HOME/entwurf/omp-mcp" \
|
|
287
|
+
"$XDG_DATA_HOME/entwurf/omp-config" "$XDG_DATA_HOME/entwurf/omp-receive" \
|
|
288
|
+
"$XDG_DATA_HOME/entwurf/meta-bridge-omp" "$XDG_DATA_HOME/entwurf/omp-receive"
|
|
289
|
+
|
|
245
290
|
# ── S-5: installed mode is a NAMED first verdict, never the source bootstrap ──
|
|
246
291
|
# A bare copy of run.sh under a fake node_modules root pins the mode seam
|
|
247
292
|
# cheaply: the copy is NOT a runnable installed package (no dist), so this cell
|