@@ -838,15 +838,16 @@ else
838838 echo ; echo " -- B3 first bytes (od) --" ; echo " $IM_ARR " | od -c | head -6; exit 1
839839fi
840840
841- # B4: STDIN — piped JSON resolves; this path must NOT emit a deprecation warning.
842- IM_STDIN=$( echo " {\" project\" :\" $PROJECT \" }" | " $BINARY " cli get_graph_schema 2> " $CLI_STDERR " )
843- if ! echo " $IM_STDIN " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); sys.exit(0 if 'node_labels' in d else 1)" 2> /dev/null; then
844- echo " FAIL B4: stdin get_graph_schema did not resolve" ; echo " $IM_STDIN " | head -c 300; cat " $CLI_STDERR " ; exit 1
841+ # B4: STDIN + --json is the generated-client transport. It must return the
842+ # complete MCP result envelope and must NOT emit a deprecation warning.
843+ IM_STDIN=$( printf ' %s' " {\" project\" :\" $PROJECT \" }" | " $BINARY " cli --json get_graph_schema 2> " $CLI_STDERR " )
844+ if ! printf ' %s' " $IM_STDIN " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); c=d.get('content'); p=json.loads(c[0].get('text','')) if isinstance(c,list) and c and c[0].get('type') == 'text' else None; sys.exit(0 if d.get('isError') is not True and isinstance(p,dict) and isinstance(p.get('node_labels'),list) else 1)" 2> /dev/null; then
845+ echo " FAIL B4: compact stdin + --json did not return a successful get_graph_schema MCP payload" ; echo " $IM_STDIN " | head -c 300; cat " $CLI_STDERR " ; exit 1
845846fi
846847if grep -qi ' deprecated' " $CLI_STDERR " ; then
847848 echo " FAIL B4: stdin path wrongly emitted a deprecation warning" ; cat " $CLI_STDERR " ; exit 1
848849fi
849- echo " OK B4: STDIN input resolves , no deprecation warning"
850+ echo " OK B4: compact STDIN + --json returns a successful schema MCP envelope , no deprecation warning"
850851
851852# B5: --args-file — JSON read from a file resolves; must NOT warn deprecated.
852853IM_ARGS_FILE=$( smoke_mktemp_file)
890891 echo " FAIL B6c: 'notatool --help' did not report 'unknown tool'" ; cat " $CLI_STDERR " ; exit 1
891892fi
892893
893- # B7: DEPRECATION guard — one raw-JSON call MUST warn on stderr; flag form must NOT.
894+ # B7: DEPRECATION control — positional raw JSON MUST warn on stderr; this is
895+ # retained only to prove B4 is exercising the non-deprecated stdin transport.
894896cli search_graph " {\" project\" :\" $PROJECT \" ,\" name_pattern\" :\" compute\" }" > /dev/null || true
895897if grep -qi ' deprecated' " $CLI_STDERR " ; then
896898 echo " OK B7a: raw-JSON cli emits deprecation warning on stderr"
@@ -2303,16 +2305,96 @@ if ! path_match "$CMD" "$SELF_PATH" ||
23032305fi
23042306echo " OK 8ak: custom KIMI_CODE_HOME MCP + durable context + UserPromptSubmit hook"
23052307
2306- # 8al: Pi has documented instructions and skill, but no invented MCP config.
2308+ # 8al: Pi has documented instructions and skill, no invented MCP config, and
2309+ # an installed generated extension that uses the non-deprecated stdin bridge.
23072310PI_INSTRUCTIONS=" $FAKE_HOME /.pi/agent/AGENTS.md"
23082311PI_SKILL=" $FAKE_HOME /.pi/agent/skills/codebase-memory/SKILL.md"
2312+ PI_EXTENSION=" $FAKE_HOME /.pi/agent/extensions/cbmem.ts"
23092313if ! grep -q ' search_graph' " $PI_INSTRUCTIONS " 2> /dev/null ||
23102314 ! grep -q ' Sessions and Subagents' " $PI_SKILL " 2> /dev/null ||
2315+ ! grep -Fq " spawn(BIN, ['cli', '--json', tool], {" " $PI_EXTENSION " 2> /dev/null ||
2316+ ! grep -Fq " stdio: ['pipe', 'pipe', 'pipe']" " $PI_EXTENSION " 2> /dev/null ||
2317+ ! grep -Fq " child.stdin.on('error'" " $PI_EXTENSION " 2> /dev/null ||
2318+ ! grep -Fq ' child.stdin.end(JSON.stringify(args ?? {}));' " $PI_EXTENSION " 2> /dev/null ||
2319+ grep -Fq ' tool, JSON.stringify(args ?? {})]' " $PI_EXTENSION " 2> /dev/null ||
2320+ grep -Fq " stdio: ['ignore', 'pipe', 'pipe']" " $PI_EXTENSION " 2> /dev/null ||
23112321 [ -e " $FAKE_HOME /.pi/agent/mcp.json" ]; then
2312- echo " FAIL 8al: Pi durable context missing or unsupported MCP config created"
2322+ echo " FAIL 8al: Pi durable context or stdin client bridge missing, or unsupported MCP config created"
23132323 exit 1
23142324fi
2315- echo " OK 8al: Pi durable context only (no MCP config)"
2325+ echo " OK 8al: Pi durable context + stdin client bridge (no MCP config)"
2326+
2327+ # 8al-node: execute the generated extension against a child that exits without
2328+ # consuming a deliberately over-pipe-capacity payload. This is the lifecycle
2329+ # Node implements: without an stdin error listener, the late EPIPE is an
2330+ # unhandled EventEmitter error and crashes the Pi host. A second call emits a
2331+ # valid MCP envelope before the same early exit, proving parsed JSON remains
2332+ # authoritative over a retained stdin transport error.
2333+ #
2334+ # SKIP_WHITELIST: the minimal C-only Linux image intentionally has no Node.
2335+ # What was tried: making Node a universal core-suite prerequisite would widen
2336+ # the product's C build dependencies. The exact generated-source assertions
2337+ # above still run there; this live probe gates every Node-equipped smoke venue.
2338+ if command -v node > /dev/null 2>&1 ; then
2339+ PI_NODE=$( command -v node)
2340+ PI_PROBE_DIR=" $TMPDIR /pi-node-probe"
2341+ mkdir -p " $PI_PROBE_DIR "
2342+ python3 - " $PI_EXTENSION " " $PI_PROBE_DIR /cbmem.mjs" << 'PYPIADAPTER '
2343+ import pathlib
2344+ import sys
2345+
2346+ source, destination = map(pathlib.Path, sys.argv[1:])
2347+ text = source.read_text(encoding="utf-8")
2348+ lines = text.splitlines()
2349+ matches = [i for i, line in enumerate(lines) if line.startswith("const BIN = ")]
2350+ if len(matches) != 1:
2351+ raise SystemExit("generated Pi extension has no unique BIN declaration")
2352+ lines[matches[0]] = "const BIN = process.execPath;"
2353+ destination.write_text("\n".join(lines) + "\n", encoding="utf-8")
2354+ PYPIADAPTER
2355+ cat > " $PI_PROBE_DIR /cli" << 'PICHILD '
2356+ const fs = require('node:fs');
2357+ if (process.argv[3] === 'get_graph_schema') {
2358+ fs.writeSync(1, JSON.stringify({ content: [{ type: 'text', text: 'schema-ok' }] }) + '\n');
2359+ }
2360+ process.exit(0);
2361+ PICHILD
2362+ if ! grep -Fxq ' const BIN = process.execPath;' " $PI_PROBE_DIR /cbmem.mjs" ||
2363+ ! grep -Fxq " const fs = require('node:fs');" " $PI_PROBE_DIR /cli" ; then
2364+ echo " FAIL 8al-node: generated lifecycle probe is not portable across Node launch environments"
2365+ exit 1
2366+ fi
2367+ cat > " $PI_PROBE_DIR /probe.mjs" << 'PIPROBE '
2368+ import extension from './cbmem.mjs';
2369+
2370+ const tools = [];
2371+ extension({ registerTool: (definition) => tools.push(definition) });
2372+ const byName = (name) => tools.find((tool) => tool.name === name);
2373+ const args = { padding: 'x'.repeat(16 * 1024 * 1024) };
2374+
2375+ const successful = await byName('get_graph_schema').execute('probe-ok', args);
2376+ if (successful?.content?.[0]?.text !== 'schema-ok') {
2377+ throw new Error('valid child JSON was not authoritative over stdin EPIPE');
2378+ }
2379+
2380+ let transportError = '';
2381+ try {
2382+ await byName('search_graph').execute('probe-error', args);
2383+ } catch (error) {
2384+ transportError = String(error?.message ?? error);
2385+ }
2386+ if (!/(EPIPE|broken pipe|write)/i.test(transportError)) {
2387+ throw new Error(`missing surfaced stdin transport error: ${transportError || '<none>'}`);
2388+ }
2389+ PIPROBE
2390+ if ! (cd " $PI_PROBE_DIR " && " $PI_NODE " probe.mjs); then
2391+ echo " FAIL 8al-node: generated Pi extension did not contain early-exit stdin errors"
2392+ exit 1
2393+ fi
2394+ echo " OK 8al-node: generated Pi extension contains EPIPE and keeps valid JSON authoritative"
2395+ else
2396+ echo " SKIP 8al-node: Node unavailable in this C-only smoke venue (whitelisted above)"
2397+ fi
23162398
23172399# 8am: Warp receives the documented shared skill; MCP remains user/UI-managed.
23182400WARP_SKILL=" $FAKE_HOME /.agents/skills/codebase-memory/SKILL.md"
0 commit comments