@junghanacs/entwurf 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +128 -0
- package/README.md +62 -117
- package/VERIFY.md +40 -21
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +1 -1
- package/docs/setup-clean-host.md +112 -95
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +8 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +3 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +153 -205
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +6 -4
- package/mcp/entwurf-bridge/src/index.ts +1 -1
- package/package.json +14 -17
- package/pi/pi-durable/bootstrap.mjs +142 -19
- package/pi/pi-durable/carrier/LICENSE +21 -0
- package/pi/pi-durable/carrier/harness-setup.js +123 -0
- package/pi/pi-durable/carrier/prompt.js +74 -0
- package/pi/pi-durable/carrier/runtime.js +402 -0
- package/pi/pi-durable/carrier/sessions.js +56 -0
- package/pi/pi-durable/carrier/subagent.js +55 -0
- package/pi/pi-durable/carrier/tui.js +655 -0
- package/pi/pi-durable/carrier-relocation.mjs +160 -0
- package/pi/pi-durable/carrier-resolver.mjs +38 -0
- package/pi/pi-durable/overlay/upstream-pin.json +103 -11
- package/pi-extensions/lib/fresh-call-composition.ts +1 -1
- package/pi-extensions/lib/mux-fresh-call.ts +8 -10
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +3 -3
- package/pi-extensions/lib/pi-durable-runtime.ts +197 -263
- package/pi-extensions/meta-bridge-pi-durable.ts +13 -7
- package/run.sh +519 -248
- package/scripts/check-entwurf-bridge-pi-free.ts +7 -5
- package/scripts/check-fresh-call-dispatch.ts +1 -1
- package/scripts/check-gate-qualification.ts +86 -34
- package/scripts/check-herdr-activation.ts +5 -4
- package/scripts/check-pi-durable-contact.ts +26 -93
- package/scripts/check-pi-durable-receive.ts +22 -59
- package/scripts/check-pi-durable-send.ts +235 -75
- package/scripts/check-release-gate-outcomes.ts +21 -17
- package/scripts/emit-pi-durable-carrier.ts +355 -0
- package/scripts/lib/mutation-qualify.ts +21 -45
- package/scripts/lib/pi-durable-operator-guard.ts +89 -0
- package/scripts/lib/qualification-receipt.ts +7 -11
- package/scripts/mutants/control-send-receipt.json +1 -1
- package/scripts/mutants/gate-qualification.json +13 -0
- package/scripts/mutants/mux-fresh-call.json +13 -13
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/mutants/pi-durable.json +442 -109
- package/scripts/mutants/pi-floor.json +24 -2
- package/scripts/mutants/release-gate.json +5 -3
- package/scripts/pi-durable-contact-host.mjs +3 -3
- package/scripts/pi-durable-receive-host.mjs +3 -3
- package/scripts/pi-durable-send-host.mjs +76 -5
- package/scripts/smoke-herdr-raw-install-live.sh +1 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +3 -3
- package/scripts/smoke-setup-verdict.sh +15 -15
- package/scripts/smoke-user-scope-citizen.sh +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// G-receive host half (#129 L-receive). Runs AS the durable host, under
|
|
2
|
-
//
|
|
1
|
+
// G-receive host half (#129 L-receive). Runs AS the durable host, under this checkout's pi-durable
|
|
2
|
+
// carrier resolver (#130), and is spawned only by scripts/check-pi-durable-receive.ts:
|
|
3
3
|
//
|
|
4
|
-
// node --import <checkout>/
|
|
4
|
+
// node --import <checkout>/pi/pi-durable/carrier-resolver.mjs \
|
|
5
5
|
// scripts/pi-durable-receive-host.mjs <bridge entry>
|
|
6
6
|
//
|
|
7
7
|
// It opens the durable app through the production bootstrap composition — which arms the contact's
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// G-send host half (#129 L-send first cell). Runs AS the durable host, under
|
|
2
|
-
//
|
|
1
|
+
// G-send host half (#129 L-send first cell). Runs AS the durable host, under this checkout's
|
|
2
|
+
// pi-durable carrier resolver (#130), and is spawned only by scripts/check-pi-durable-send.ts:
|
|
3
3
|
//
|
|
4
|
-
// node --import <checkout>/
|
|
4
|
+
// node --import <checkout>/pi/pi-durable/carrier-resolver.mjs \
|
|
5
5
|
// scripts/pi-durable-send-host.mjs <bridge entry> <prompt>
|
|
6
6
|
//
|
|
7
7
|
// It opens the durable app through the production bootstrap composition, then gives the app ONE
|
|
@@ -16,11 +16,16 @@
|
|
|
16
16
|
// holds. It prints one JSON report line, holds the host alive until the gate writes `close`,
|
|
17
17
|
// closes, prints `{"closed":true}` and exits. With ENTWURF_PI_DURABLE_SEND_FAULT=malformed-report
|
|
18
18
|
// (the gate's control) it gives the app no input and sends a non-JSON report line instead.
|
|
19
|
+
//
|
|
20
|
+
// --native-module <file> (#130 P2): the whole packaged `main` runs instead — its argument parse, the
|
|
21
|
+
// operator module's initialization, the TUI load and the open, in main's own order — with ONE seam:
|
|
22
|
+
// the TUI runner is this driver, which gives the app the one input through the controller it is
|
|
23
|
+
// handed and reports the view as above. Nothing else of main is replaced.
|
|
19
24
|
import { createInterface } from "node:readline";
|
|
20
|
-
import { openDurableCitizen } from "../plugins/pi-durable/bootstrap.mjs";
|
|
25
|
+
import { main, openDurableCitizen } from "../plugins/pi-durable/bootstrap.mjs";
|
|
21
26
|
|
|
22
27
|
const USAGE =
|
|
23
|
-
"usage: pi-durable-send-host.mjs <bridge entry> <prompt> [--await-go] [--report-intent <callId> | --recover <callId>]";
|
|
28
|
+
"usage: pi-durable-send-host.mjs <bridge entry> <prompt> [--await-go] [--report-intent <callId> | --recover <callId> | --native-module <file>]";
|
|
24
29
|
const [bridgeEntry, prompt, ...flags] = process.argv.slice(2);
|
|
25
30
|
if (bridgeEntry === undefined || prompt === undefined) throw new Error(USAGE);
|
|
26
31
|
// --await-go: report `born` once the citizen exists, and give the app its one input only after the
|
|
@@ -36,13 +41,18 @@ if (bridgeEntry === undefined || prompt === undefined) throw new Error(USAGE);
|
|
|
36
41
|
let awaitGo = false;
|
|
37
42
|
let reportIntent;
|
|
38
43
|
let recoverCall;
|
|
44
|
+
let nativeModule;
|
|
39
45
|
for (let i = 0; i < flags.length; i++) {
|
|
40
46
|
if (flags[i] === "--await-go") awaitGo = true;
|
|
41
47
|
else if (flags[i] === "--report-intent" && flags[i + 1] !== undefined) reportIntent = flags[++i];
|
|
42
48
|
else if (flags[i] === "--recover" && flags[i + 1] !== undefined) recoverCall = flags[++i];
|
|
49
|
+
else if (flags[i] === "--native-module" && flags[i + 1] !== undefined) nativeModule = flags[++i];
|
|
43
50
|
else throw new Error(USAGE);
|
|
44
51
|
}
|
|
45
52
|
if (reportIntent !== undefined && recoverCall !== undefined) throw new Error(USAGE);
|
|
53
|
+
if (nativeModule !== undefined && (awaitGo || reportIntent !== undefined || recoverCall !== undefined)) {
|
|
54
|
+
throw new Error(USAGE);
|
|
55
|
+
}
|
|
46
56
|
|
|
47
57
|
const stdinLines = createInterface({ input: process.stdin })[Symbol.asyncIterator]();
|
|
48
58
|
/** Wait for one exact line from the gate; the gate closing stdin first is a failure. */
|
|
@@ -80,6 +90,67 @@ function summarize(entry) {
|
|
|
80
90
|
};
|
|
81
91
|
}
|
|
82
92
|
|
|
93
|
+
/** Give the app one input through `controller` and resolve once `source` shows the turn settled. */
|
|
94
|
+
function submitAndSettle(source, controller) {
|
|
95
|
+
const before = source.current();
|
|
96
|
+
const noticesBefore = before.notices.length;
|
|
97
|
+
let submitted = false;
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
const check = () => {
|
|
100
|
+
if (!submitted) return;
|
|
101
|
+
const view = source.current();
|
|
102
|
+
const busy = view.conversation.docs["pi.live"]?.run !== undefined;
|
|
103
|
+
const answered = view.conversation.entries.some((e) => e.kind === "pi.assistant");
|
|
104
|
+
const failed = view.notices.slice(noticesBefore).some((n) => n.level === "error");
|
|
105
|
+
if ((!busy && answered) || failed) {
|
|
106
|
+
unsubscribe();
|
|
107
|
+
resolve(before);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const unsubscribe = source.subscribe(check);
|
|
111
|
+
controller.submit(prompt, "followUp").then(() => {
|
|
112
|
+
submitted = true;
|
|
113
|
+
check();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (nativeModule !== undefined) {
|
|
119
|
+
let code = 0;
|
|
120
|
+
try {
|
|
121
|
+
await main(
|
|
122
|
+
["--provider", "loopback", "--model", "scripted", "--width", "task-wide", "--native-module", nativeModule],
|
|
123
|
+
{
|
|
124
|
+
bridgeEntry,
|
|
125
|
+
loadTui: async () => ({
|
|
126
|
+
runDurableTui: async (source, controller) => {
|
|
127
|
+
const before = await submitAndSettle(source, controller);
|
|
128
|
+
const view = source.current();
|
|
129
|
+
process.stdout.write(
|
|
130
|
+
`${JSON.stringify({
|
|
131
|
+
native: true,
|
|
132
|
+
hostPid: process.pid,
|
|
133
|
+
session: view.session,
|
|
134
|
+
conversationIdBefore: before.conversation.conversation.id,
|
|
135
|
+
conversationIdAfter: view.conversation.conversation.id,
|
|
136
|
+
entries: view.conversation.entries.filter((e) => e.kind.startsWith("pi.")).map(summarize),
|
|
137
|
+
busy: view.conversation.docs["pi.live"]?.run !== undefined,
|
|
138
|
+
notices: view.notices.map((n) => `${n.level}: ${n.message}`),
|
|
139
|
+
})}\n`,
|
|
140
|
+
);
|
|
141
|
+
await waitFor("close");
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
code = 1;
|
|
148
|
+
process.stdout.write(`${JSON.stringify({ error: error instanceof Error ? error.message : String(error) })}\n`);
|
|
149
|
+
}
|
|
150
|
+
process.stdout.write(`${JSON.stringify({ closed: true })}\n`);
|
|
151
|
+
process.exit(code);
|
|
152
|
+
}
|
|
153
|
+
|
|
83
154
|
const citizen = await openDurableCitizen({
|
|
84
155
|
bridgeEntry,
|
|
85
156
|
cwd: process.cwd(),
|
|
@@ -108,7 +108,7 @@ if ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1; then
|
|
|
108
108
|
exit 0
|
|
109
109
|
fi
|
|
110
110
|
|
|
111
|
-
PI_SPEC="$(node -p "require('$REPO/package.json').
|
|
111
|
+
PI_SPEC="$(node -p "require('$REPO/package.json').dependencies['@earendil-works/pi-coding-agent']")"
|
|
112
112
|
# Pinned, not floating: a receipt that cannot say WHICH Claude Code it ran is not
|
|
113
113
|
# reproducible. This is the version measured in the container on 2026-09-19. Claude Code
|
|
114
114
|
# is scaffolding here (Rule 17) — the product face starts at `herdr integration install`.
|
|
@@ -214,11 +214,11 @@ for (const bin of ["pi", "entwurf", "tmux"]) {
|
|
|
214
214
|
}
|
|
215
215
|
const layout = piDurablePackageLayout();
|
|
216
216
|
ok("this package closure carries the pi-durable units", layout !== null);
|
|
217
|
-
const runtime = checkPiDurableRuntime(
|
|
217
|
+
const runtime = checkPiDurableRuntime(layout);
|
|
218
218
|
ok(
|
|
219
|
-
"
|
|
219
|
+
"this package's pi-durable carrier and its declared SDK set verify (the 0.32.0 supply contract)",
|
|
220
220
|
runtime.ok,
|
|
221
|
-
runtime.ok ? "" : `${runtime.reason}`,
|
|
221
|
+
runtime.ok ? "" : `${runtime.reason}: ${runtime.detail}`,
|
|
222
222
|
);
|
|
223
223
|
receipts["0-runtime"] = runtime.ok ? JSON.stringify(runtime) : "";
|
|
224
224
|
|
|
@@ -140,8 +140,8 @@ want "S-1: claude absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\"
|
|
|
140
140
|
want "S-1: agy absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\" | grep -q 'agy: SKIP'"
|
|
141
141
|
want "S-1: copilot absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\" | grep -q 'copilot: SKIP'"
|
|
142
142
|
want "S-1: omp absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\" | grep -q 'omp: SKIP'"
|
|
143
|
-
want "S-1:
|
|
144
|
-
want "S-1: setup
|
|
143
|
+
want "S-1: the pi-durable row is the package's own carrier verdict (PASS: carrier + pinned SDK set verify; never a SKIP)" "printf '%s' \"\$OUT\" | grep -q 'pi-durable: PASS'"
|
|
144
|
+
want "S-1: setup writes nothing for pi-durable (the carrier ships in the package)" "[ ! -e '$XDG_DATA_HOME/entwurf/pi-durable' ]"
|
|
145
145
|
want "S-1: core bridge boundary validated (PASS)" "printf '%s' \"\$OUT\" | grep -q 'core: PASS'"
|
|
146
146
|
want "S-1: final verdict is computed, not unconditional" \
|
|
147
147
|
"printf '%s' \"\$OUT\" | grep -q 'result: green (computed from the component outcomes above)'"
|
|
@@ -151,20 +151,20 @@ want "S-1: zero Pi wiring written for an absent pi" "[ ! -e '$PROJ1/.pi' ]"
|
|
|
151
151
|
want "S-1: no user-scope pi settings were created" "[ ! -e '$PI_CODING_AGENT_DIR/settings.json' ]"
|
|
152
152
|
want "S-1: no agy config was created" "[ ! -e '$HOME/.gemini' ]"
|
|
153
153
|
|
|
154
|
-
# ── S-PD:
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
|
|
154
|
+
# ── S-PD: the retired source-runtime location decides nothing (#130 hard cut, no legacy reader) ──
|
|
155
|
+
# 0.31.0 read an operator runtime at $XDG_DATA_HOME/entwurf/pi-durable/runtime. Since #130 the
|
|
156
|
+
# carrier ships in the package and that location is not a fallback: debris left there (here, an
|
|
157
|
+
# empty non-checkout directory, which 0.31.0 refused as unverifiable) must neither turn the row
|
|
158
|
+
# into a FAIL nor be touched. The row stays the package's own carrier verdict.
|
|
159
|
+
echo "[smoke-setup-verdict] S-PD debris at the retired durable runtime location"
|
|
159
160
|
PROJPD="$SB/projpd"; mkdir -p "$PROJPD"
|
|
160
161
|
PD_RUNTIME="$XDG_DATA_HOME/entwurf/pi-durable/runtime"; mkdir -p "$PD_RUNTIME"
|
|
161
162
|
seed_auth
|
|
162
163
|
set +e; OUT="$(bash "$REPO_DIR/run.sh" setup "$PROJPD" 2>&1)"; RC=$?; set -e
|
|
163
|
-
want "S-PD:
|
|
164
|
-
"[ '$RC' -
|
|
165
|
-
want "S-PD:
|
|
166
|
-
want "S-PD:
|
|
167
|
-
want "S-PD: setup changed nothing in the operator's runtime directory" "[ -z \"\$(ls -A '$PD_RUNTIME')\" ]"
|
|
164
|
+
want "S-PD: the retired location does not decide the row — the carrier verdict is still PASS, setup green" \
|
|
165
|
+
"[ '$RC' -eq 0 ] && printf '%s' \"\$OUT\" | grep -q 'pi-durable: PASS'"
|
|
166
|
+
want "S-PD: no 0.31.0 runtime refusal is named" "! printf '%s' \"\$OUT\" | grep -q 'pi-durable-runtime-'"
|
|
167
|
+
want "S-PD: setup changed nothing at the retired location" "[ -z \"\$(ls -A '$PD_RUNTIME')\" ]"
|
|
168
168
|
want_auth_untouched "S-PD"
|
|
169
169
|
rm -rf "$XDG_DATA_HOME/entwurf/pi-durable"
|
|
170
170
|
want "S-1: no Copilot config/units were created" "[ ! -e '$HOME/.copilot' ]"
|
|
@@ -212,7 +212,7 @@ want_auth_untouched "S-2b"
|
|
|
212
212
|
# ── S-3: pi at the pinned floor → presence completes project + user wiring ──
|
|
213
213
|
echo "[smoke-setup-verdict] S-3 pi present at floor"
|
|
214
214
|
PROJ3="$SB/proj3"; mkdir -p "$PROJ3"
|
|
215
|
-
PI_PIN="$(node -e 'console.log(require(process.argv[1]).
|
|
215
|
+
PI_PIN="$(node -e 'console.log(require(process.argv[1]).dependencies["@earendil-works/pi-coding-agent"])' "$REPO_DIR/package.json")"
|
|
216
216
|
printf '#!/usr/bin/env bash\necho %s\n' "$PI_PIN" > "$SB/harness/pi-ok"; chmod +x "$SB/harness/pi-ok"
|
|
217
217
|
seed_auth
|
|
218
218
|
set +e; OUT="$(PI_BIN="$SB/harness/pi-ok" bash "$REPO_DIR/run.sh" setup "$PROJ3" 2>&1)"; RC=$?; set -e
|
|
@@ -529,8 +529,8 @@ PROJ9C="$SB/proj9c"; mkdir -p "$PROJ9C"
|
|
|
529
529
|
seed_auth
|
|
530
530
|
set +e; OUT="$(PATH="$FAKE_UNAME:$PATH" bash "$REPO_DIR/run.sh" setup "$PROJ9C" 2>&1)"; RC=$?; set -e
|
|
531
531
|
want "S-9c: a harness-free uncertified host still exits 0" "[ '$RC' -eq 0 ]"
|
|
532
|
-
want "S-9c: all
|
|
533
|
-
"printf '%s' \"\$OUT\" | grep -q 'pi: SKIP' && printf '%s' \"\$OUT\" | grep -q 'claude: SKIP' && printf '%s' \"\$OUT\" | grep -q 'agy: SKIP' && printf '%s' \"\$OUT\" | grep -q 'copilot: SKIP' && printf '%s' \"\$OUT\" | grep -q 'omp: SKIP' && printf '%s' \"\$OUT\" | grep -q 'codex: SKIP' && printf '%s' \"\$OUT\" | grep -q 'pi-durable:
|
|
532
|
+
want "S-9c: all six harness probes are still zero-state SKIPs, and the packaged pi-durable carrier still verifies" \
|
|
533
|
+
"printf '%s' \"\$OUT\" | grep -q 'pi: SKIP' && printf '%s' \"\$OUT\" | grep -q 'claude: SKIP' && printf '%s' \"\$OUT\" | grep -q 'agy: SKIP' && printf '%s' \"\$OUT\" | grep -q 'copilot: SKIP' && printf '%s' \"\$OUT\" | grep -q 'omp: SKIP' && printf '%s' \"\$OUT\" | grep -q 'codex: SKIP' && printf '%s' \"\$OUT\" | grep -q 'pi-durable: PASS'"
|
|
534
534
|
want "S-9c: core still PASSes and the computed summary is still green" \
|
|
535
535
|
"printf '%s' \"\$OUT\" | grep -q 'core: PASS' && printf '%s' \"\$OUT\" | grep -q 'result: green (computed from the component outcomes above)'"
|
|
536
536
|
want "S-9c: an absent harness is never given a rail verdict it did not earn" \
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
#
|
|
19
19
|
# Cell 15 closes the third (#110), and it is the one cell here that SPAWNS pi: the
|
|
20
20
|
# defect is not a byte, it is which of two checkouts wins `--entwurf-control`, and
|
|
21
|
-
# only the real loader can say. It uses the pinned
|
|
21
|
+
# only the real loader can say. It uses the pinned dependency pi, never PATH.
|
|
22
22
|
set -euo pipefail
|
|
23
23
|
export PYTHONDONTWRITEBYTECODE=1 # snapshot purity: no ignored scripts/__pycache__ writes under qualification
|
|
24
24
|
|