@junghanacs/entwurf 0.25.0 → 0.30.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.
Files changed (88) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +1 -1
  3. package/CHANGELOG.md +182 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +1 -1
  6. package/README.md +178 -674
  7. package/VERIFY.md +17 -16
  8. package/demo/README.md +1 -1
  9. package/docs/acp-backend-rail.md +6 -2
  10. package/docs/external-mcp-host.md +1 -1
  11. package/docs/setup-clean-host.md +346 -10
  12. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +27 -4
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +12 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +1 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +2 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-mailbox.js +2 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +2 -2
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -5
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +3 -3
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +13 -6
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +3 -3
  23. package/mcp/entwurf-bridge/src/index.ts +30 -3
  24. package/package.json +11 -11
  25. package/pi-extensions/entwurf-control.ts +50 -637
  26. package/pi-extensions/lib/acp/backend-adapter.ts +13 -1
  27. package/pi-extensions/lib/entwurf-fact-provider.ts +2 -2
  28. package/pi-extensions/lib/entwurf-mcp-server.ts +74 -0
  29. package/pi-extensions/lib/entwurf-peers-render.ts +1 -1
  30. package/pi-extensions/lib/entwurf-v2-decider.ts +2 -2
  31. package/pi-extensions/lib/entwurf-v2-mailbox.ts +2 -2
  32. package/pi-extensions/lib/entwurf-v2-production.ts +3 -3
  33. package/pi-extensions/lib/entwurf-v2-surface.ts +3 -5
  34. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +3 -3
  35. package/pi-extensions/lib/fresh-call-composition.ts +13 -6
  36. package/pi-extensions/lib/meta-mailbox-body.ts +1 -1
  37. package/pi-extensions/lib/mux-fresh-call.ts +3 -3
  38. package/run.sh +168 -54
  39. package/scripts/check-entwurf-peers-surface.ts +8 -15
  40. package/scripts/check-entwurf-self-address.ts +7 -11
  41. package/scripts/check-entwurf-v2-surface.ts +20 -112
  42. package/scripts/check-fresh-call-dispatch.ts +12 -13
  43. package/scripts/check-gate-qualification.ts +2741 -157
  44. package/scripts/check-install-surface.ts +3 -2
  45. package/scripts/check-mux-resume-call.ts +24 -35
  46. package/scripts/check-pi-bridge-sender.ts +130 -0
  47. package/scripts/check-pi-mcp-bridge.ts +251 -0
  48. package/scripts/lib/codex-fresh-live-protocol.ts +17 -5
  49. package/scripts/lib/codex-fresh-source-receipts.ts +21 -2
  50. package/scripts/lib/mutation-qualify.ts +269 -44
  51. package/scripts/lib/pi-mcp-bridge-probe.mjs +170 -0
  52. package/scripts/lib/qualification-acceptance.ts +146 -0
  53. package/scripts/lib/qualification-artifact.ts +359 -0
  54. package/scripts/lib/qualification-collection.ts +384 -0
  55. package/scripts/lib/qualification-gh.ts +231 -0
  56. package/scripts/lib/qualification-ledger.ts +669 -0
  57. package/scripts/lib/qualification-oracle-core.ts +279 -0
  58. package/scripts/lib/qualification-receipt.ts +561 -0
  59. package/scripts/lib/qualification-release.ts +271 -0
  60. package/scripts/meta-bridge-doctor.sh +16 -5
  61. package/scripts/mutants/acp-foreground-only.json +17 -0
  62. package/scripts/mutants/codex-native.json +11 -11
  63. package/scripts/mutants/control-send-receipt.json +6 -4
  64. package/scripts/mutants/copilot-birth.json +2 -1
  65. package/scripts/mutants/fresh-call-dispatch.json +3 -14
  66. package/scripts/mutants/gate-qualification.json +59 -0
  67. package/scripts/mutants/mux-fresh-call.json +43 -45
  68. package/scripts/mutants/mux-resume-call.json +11 -35
  69. package/scripts/mutants/pack-install.json +15 -4
  70. package/scripts/mutants/pi-bridge-sender.json +54 -0
  71. package/scripts/mutants/pi-floor.json +6 -2
  72. package/scripts/mutants/pi-mcp-bridge.json +36 -0
  73. package/scripts/mutants/pi-mcp-register.json +51 -0
  74. package/scripts/mutants/qualification-ledger.json +489 -0
  75. package/scripts/mutants/v2-surface.json +6 -6
  76. package/scripts/qualification-oracle.ts +124 -0
  77. package/scripts/resident-rpc-drive.ts +5 -1
  78. package/scripts/smoke-acp-bundled-mcp-live.ts +3 -1
  79. package/scripts/smoke-acp-cortex-live.ts +3 -1
  80. package/scripts/smoke-acp-session-reuse-live.ts +29 -12
  81. package/scripts/smoke-acp-socket-citizen-live.ts +3 -1
  82. package/scripts/smoke-acp-v2-send-live.ts +3 -1
  83. package/scripts/smoke-codex-fresh-live.ts +20 -1
  84. package/scripts/smoke-entwurf-chain-live.ts +6 -2
  85. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -1
  86. package/scripts/smoke-herdr-raw-install-live.sh +8 -3
  87. package/scripts/smoke-mux-lifecycle-live.ts +1 -1
  88. package/scripts/smoke-resident-garden-guard.sh +3 -1
@@ -267,6 +267,18 @@ function resolveClaudeLaunch(): AcpLaunchSpec {
267
267
  return { command: process.execPath, args: [launcher] };
268
268
  }
269
269
 
270
+ /**
271
+ * The claude child runs with Claude Code's own background-task switch on (#125): the vendor drops
272
+ * `run_in_background` from the Bash/Agent schemas it declares to the model and turns off timeout and
273
+ * turn-abort backgrounding. Without it a vendor-backgrounded command runs in its own session (setsid),
274
+ * outlives an ACP cancel, and is orphaned to init when the child exits — the process-group teardown
275
+ * cannot reach it (`[측정 2026-10-02]` claude-agent-sdk 0.3.274 / claude-agent-acp 0.79.0). A process a
276
+ * command detaches on its own is outside the switch. It rides
277
+ * the claude adapter's launch env, which backend.ts spreads over `process.env` at spawn, so an
278
+ * ambient value cannot turn it off. Cortex declares nothing of the kind.
279
+ */
280
+ export const CLAUDE_FOREGROUND_ONLY_ENV = { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" } as const;
281
+
270
282
  export const claudeAdapter: AcpBackendAdapter = {
271
283
  backend: "claude",
272
284
 
@@ -292,7 +304,7 @@ export const claudeAdapter: AcpBackendAdapter = {
292
304
  },
293
305
 
294
306
  launchEnvDefaults() {
295
- return claudeLaunchEnvDefaults();
307
+ return { ...claudeLaunchEnvDefaults(), ...CLAUDE_FOREGROUND_ONLY_ENV };
296
308
  },
297
309
 
298
310
  ensureOverlay() {
@@ -102,8 +102,8 @@ export interface EntwurfFactsDeps {
102
102
  /** Socket axis: injected into scanSocketProbes (controlDir/readdir/probe). */
103
103
  socket?: Partial<SocketScanDeps>;
104
104
  /** Observation axis (#101): per-citizen receiver + transcript facts. Defaults to the
105
- * REAL measurement — the same seam shape `makeProductionEntwurfV2Deps` uses, so the two
106
- * wiring sites (MCP + pi-native) cannot drift by each passing their own observer, and a
105
+ * REAL measurement — the same seam shape `makeProductionEntwurfV2Deps` uses, so a wiring
106
+ * site cannot drift by passing its own observer (one site, the bridge, since #125), and a
107
107
  * gate still drives the assembly with a fake and no filesystem. */
108
108
  observe?: PeerObserver;
109
109
  /** Placement axis (#116 S1): the ONE placement-owner read this listing is allowed.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * entwurf-mcp-server — how a pi citizen hands its Entwurf verbs to Pi's built-in MCP (#125).
3
+ *
4
+ * WHY THE VERBS ARE NOT NATIVE TOOLS ANY MORE. Pi 0.99 ships MCP as a built-in extension
5
+ * (`builtin:mcp`): it spawns stdio servers, owns their lifecycle, and closes them on
6
+ * `session_shutdown`. The same compiled `entwurf-bridge` every other harness already reaches is
7
+ * therefore the one surface a pi session needs too — the five hand-written `registerTool` copies
8
+ * that duplicated it are gone, and so is the drift they invited. Pi, not Entwurf, supervises the
9
+ * child: nothing here starts, restarts or stops a process.
10
+ *
11
+ * WHAT THIS MODULE DECIDES, AND NOTHING ELSE:
12
+ * - the server NAME. `entwurf-bridge` is the key every other host already uses. Pi sanitizes it
13
+ * into the call name, so a pi model calls `mcp__entwurf_bridge__<verb>` (`[측정 2026-10-01,
14
+ * pi 0.99.2]` `extensions/mcp/tools.ts:82-90`: everything outside `[A-Za-z0-9_]` becomes `_`).
15
+ * The key itself is unchanged. It is a call-name prefix, never an address: the record is the
16
+ * only address axis.
17
+ * - the ENTRY. The COMPILED bridge (`scripts/build-bridge.sh` output) that a packaged consumer
18
+ * receives next to `pi-extensions/`. There is no TypeScript fallback: a missing build is a
19
+ * named failure, not a silent switch to a different artifact.
20
+ * - the IDENTITY carrier. `PI_SESSION_ID` / `PI_AGENT_ID` are passed EXPLICITLY in the server
21
+ * config from the record this session was just born with. Pi merges a stdio child's env as
22
+ * `{...process.env, ...config.env}`, so the explicit value wins over whatever the process
23
+ * happens to carry — the carrier is the record-established identity by construction, not by
24
+ * the timing of an ambient export.
25
+ * - the EXPOSURE. `direct`: the verbs are declared to the model like built-in tools. Two bridge
26
+ * tools have no use on a pi host and are hidden: `entwurf_inbox_read` drains a caller-supplied
27
+ * mailbox (a pi citizen has no mailbox), and `entwurf_register_native` binds a NATIVE harness
28
+ * session (a pi citizen is born by this extension).
29
+ *
30
+ * A same-namespace entry in the operator's or a trusted project's `mcp.json` — `entwurf-bridge` or
31
+ * `entwurf_bridge`, which Pi folds to one namespace — replaces this registration by Pi's own
32
+ * precedence, and `/mcp` says so. That is the operator's choice and
33
+ * Pi's contract; this module does not scan config, fight it, or add a second owner.
34
+ */
35
+
36
+ import * as path from "node:path";
37
+ import type { McpServerConfig } from "@earendil-works/pi-coding-agent";
38
+
39
+ /** The built-in MCP server key. Pi sanitizes it into the call name (`mcp__entwurf_bridge__<verb>`); never an address. */
40
+ export const ENTWURF_MCP_SERVER_NAME = "entwurf-bridge";
41
+
42
+ /** Bridge tools with no use on a pi host (see the module header). */
43
+ export const ENTWURF_MCP_HIDDEN_TOOLS = ["entwurf_inbox_read", "entwurf_register_native"] as const;
44
+
45
+ /**
46
+ * The compiled bridge entry, resolved from the directory of the extension file that loads it.
47
+ * Checkout and packaged install share this layout: `pi-extensions/` beside `mcp/entwurf-bridge/dist/`.
48
+ */
49
+ export function entwurfBridgeCompiledEntry(extensionDir: string): string {
50
+ return path.resolve(extensionDir, "..", "mcp", "entwurf-bridge", "dist", "mcp", "entwurf-bridge", "src", "index.js");
51
+ }
52
+
53
+ export interface EntwurfMcpServerInput {
54
+ /** The record gardenId this session was just born with. */
55
+ gardenId: string;
56
+ /** `<provider>/<model>` of the live session, when known. */
57
+ agentId: string | undefined;
58
+ /** Absolute path of the compiled bridge entry. */
59
+ compiledEntry: string;
60
+ /** The node binary running pi. */
61
+ nodePath: string;
62
+ }
63
+
64
+ /** The one server config a born pi citizen registers. Pure: no IO, no env reads. */
65
+ export function buildEntwurfMcpServerConfig(input: EntwurfMcpServerInput): McpServerConfig {
66
+ if (!input.gardenId) throw new Error("entwurf-mcp-server: a server config needs the born record's gardenId");
67
+ return {
68
+ command: input.nodePath,
69
+ args: [input.compiledEntry],
70
+ env: { PI_SESSION_ID: input.gardenId, ...(input.agentId ? { PI_AGENT_ID: input.agentId } : {}) },
71
+ exposure: "direct",
72
+ toolExposure: Object.fromEntries(ENTWURF_MCP_HIDDEN_TOOLS.map((tool) => [tool, "hidden"])),
73
+ };
74
+ }
@@ -3,7 +3,7 @@
3
3
  * surface (0.11 Stage 0 step 4, slice 4c). The MCP handler stays thin: it does IO
4
4
  * (readdir the meta-store + probe sockets via `listEntwurfFacts`) and then calls
5
5
  * THIS to shape the text + JSON. No IO here, so the gate drives it without a
6
- * filesystem and the SAME facts can feed pi-native / doctor / v2 dispatch later
6
+ * filesystem and the SAME facts can feed the bridge / doctor / v2 dispatch
7
7
  * (a handler that did its own brain work would deny them that reuse).
8
8
  *
9
9
  * Two hard rules carried from the frozen contract (동결결정 10 + #50 C4):
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * The frozen 7-step order (NEXT.md "통합 decider 순서"):
24
24
  * 1. requireGardenId — runtime guard BEFORE any path is built (F2-P1; closes the
25
- * MCP-schema bypass for pi-native/internal callers).
25
+ * MCP-schema bypass for internal callers).
26
26
  * 2. resolveTarget — no citizen → bad-target; a record-less control socket →
27
27
  * record-less-socket (#50 C4: the record is the sole address authority);
28
28
  * quarantined (out-of-socket-domain record sharing a socket/symlink) → target-address-conflict.
@@ -227,7 +227,7 @@ export interface DispatchDeciderDeps {
227
227
 
228
228
  /**
229
229
  * F2-P1 defense in depth: never build a lock/socket path from an unvalidated gid.
230
- * The MCP TypeBox pattern guards that one surface; a pi-native / internal caller
230
+ * The MCP schema pattern guards that one surface; an internal caller
231
231
  * bypasses the schema, so the decider re-validates as its very first step.
232
232
  */
233
233
  function requireGardenId(target: string): string {
@@ -77,8 +77,8 @@ export function executeMetaMailboxSend(
77
77
  * Build the production `sendViaMailbox(plan, lock)` adapter the 5c-2a send hand consumes.
78
78
  * It IGNORES `lock` entirely (release is the hand's job; a mailbox plan is lock-free) and
79
79
  * wraps the sync body in the async dep signature. `senderProvider` is supplied by the
80
- * wiring site — it calls the private `buildLocalSenderEnvelope(ctx)` and decorates
81
- * origin/replyable — so this module never imports ExtensionContext. `enqueue` defaults to
80
+ * wiring site — the bridge's authoritative sender resolution (the one caller-side
81
+ * surface since #125) — so this module never imports ExtensionContext. `enqueue` defaults to
82
82
  * the real `enqueueMetaMessage`; the gate injects a fake.
83
83
  */
84
84
  export function makeProductionSendViaMailbox(opts: {
@@ -2,8 +2,8 @@
2
2
  * entwurf-v2-production — 5d-2b: the ctx-free PRODUCTION assembly of `runEntwurfV2`'s deps.
3
3
  * `makeProductionEntwurfV2Deps(opts)` wires the real 5b decider IO seams + the three 5c
4
4
  * transport hands into one `EntwurfV2RunDeps` (`{decide, executor}`). It touches NO
5
- * `ExtensionContext`/`ExtensionAPI` — the wiring SITE (5d-3, entwurf-control.ts) builds the
6
- * `senderProvider` from `buildLocalSenderEnvelope(ctx)` and passes it in, so this module
5
+ * `ExtensionContext`/`ExtensionAPI` — the wiring SITE (the entwurf-bridge `entwurf_v2`
6
+ * handler; the pi-native one left with #125) builds the `senderProvider` and passes it in, so this module
7
7
  * stays in `lib/` with the rest of the gate-/smoke-testable v2 core.
8
8
  *
9
9
  * Three invariants this factory exists to guarantee (each gate-proven):
@@ -136,7 +136,7 @@ export interface ProductionEntwurfV2Seams {
136
136
  }
137
137
 
138
138
  export interface ProductionEntwurfV2Opts {
139
- /** Built at the wiring site from `buildLocalSenderEnvelope(ctx)`, decorated with its
139
+ /** Built at the wiring site (the bridge's authoritative sender resolution), carrying its
140
140
  * HONEST pi-session replyability (SE-1 2e-a: `replyable` reflects whether the canonical
141
141
  * control socket actually exists, not a hardcoded true). ONE provider feeds ALL THREE
142
142
  * rails — the control-socket RPC sender, the meta-mailbox body sender, and the
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * entwurf-v2-surface — 5d-3a: the ctx-free SURFACE adapter that joins a surface's raw params
3
3
  * to `runEntwurfV2` and renders the outcome-rich result back to a human/tool string. It is the
4
- * ONE place a surface (pi-native `entwurf_v2` tool now; the MCP `entwurf_v2` verb in 5d-3b)
5
- * crosses into the v2 fence — so `entwurf-control.ts` (a root-tsc, emit-capable surface that
6
- * CANNOT statically import the `.ts`-extension fence without TS5097) reaches this via a
7
- * NON-LITERAL dynamic import, and the MCP bridge (already a `.ts`-import consumer) imports it
8
- * directly. Either way the ctx binding stays OUT of the fence: the caller builds
4
+ * ONE place a surface (the MCP `entwurf_v2` verb — the only one since #125 retired the
5
+ * pi-native copy) crosses into the v2 fence; the MCP bridge (a `.ts`-import consumer) imports
6
+ * it directly. Either way the ctx binding stays OUT of the fence: the caller builds
9
7
  * `senderProvider` from its own envelope source and passes it in `opts`.
10
8
  *
11
9
  * Three exports:
@@ -329,9 +329,9 @@ const REJECT_HINT: Record<VisibleResumeRejectReason, string> = {
329
329
  };
330
330
 
331
331
  /**
332
- * ONE renderer for both surfaces — the same reason `mux-fresh-call` has one: the two
333
- * registrations are separate literals, and a shared renderer is what keeps the operator-visible
334
- * answer from drifting apart between native pi and the MCP bridge.
332
+ * ONE renderer — the same reason `mux-fresh-call` has one: it keeps the operator-visible answer
333
+ * out of the registration literal. (Since #125 the MCP bridge is the only registration; the
334
+ * native pi copy it once had to agree with is retired.)
335
335
  *
336
336
  * The success text keeps the two receipts visibly apart, including in the good case. A reader
337
337
  * who sees "window opened" and "socket alive" as one sentence has been told the resume worked;
@@ -33,9 +33,16 @@ export const FRESH_CALL_BACKENDS = ["pi", "claude-code", "copilot", "omp", "code
33
33
  export type FreshCallBackend = (typeof FRESH_CALL_BACKENDS)[number];
34
34
 
35
35
  /**
36
- * The callback tool NAME differs per backend and that is not cosmetic: native pi exposes the
37
- * capability directly (`entwurf_v2`), while an MCP-hosted session reaches it under whatever
38
- * name that harness composes. Naming the wrong one costs the whole first turn.
36
+ * The callback tool NAME differs per backend and that is not cosmetic: every session reaches the
37
+ * capability under whatever name its harness composes for the `entwurf-bridge` MCP server, and
38
+ * naming the wrong one costs the whole first turn.
39
+ *
40
+ * `[측정 2026-10-01, pi 0.99.2]` pi reaches the verbs through its BUILT-IN MCP (#125 — the native
41
+ * `registerTool` copies are gone), and Pi composes `mcp__<server>__<tool>` replacing everything
42
+ * outside `[A-Za-z0-9_]` with `_` (`extensions/mcp/tools.ts:82-90`, tag v0.99.2): the server key
43
+ * stays `entwurf-bridge`, but the model-facing names are `mcp__entwurf_bridge__entwurf_*` — the
44
+ * same spelling Codex composes, and NOT Claude Code's. 0.99.1 kept the hyphen; the floor moved to
45
+ * 0.99.2 as a hard cut — one literal pi dialect, chosen over an alias or a version router.
39
46
  *
40
47
  * `[측정]` Copilot CLI 1.0.80 composes `<mcpServerName>-<mcpToolName>` — NOT Claude Code's
41
48
  * `mcp__<server>__<tool>`. Read from two independent sessions' own event logs
@@ -54,7 +61,7 @@ export type FreshCallBackend = (typeof FRESH_CALL_BACKENDS)[number];
54
61
  * permission dialect: omp's approval layer consults the same minted string (`source-audit.md`).
55
62
  */
56
63
  export const FRESH_CALL_CALLBACK_TOOL: Record<FreshCallBackend, string> = {
57
- pi: "entwurf_callback",
64
+ pi: "mcp__entwurf_bridge__entwurf_callback",
58
65
  "claude-code": "mcp__entwurf-bridge__entwurf_callback",
59
66
  copilot: "entwurf-bridge-entwurf_callback",
60
67
  omp: "mcp__entwurf_bridge_entwurf_callback",
@@ -65,7 +72,7 @@ export const FRESH_CALL_CALLBACK_TOOL: Record<FreshCallBackend, string> = {
65
72
 
66
73
  /** Where a sibling SENDS the task result. Always the delivery verb, never the birth callback. */
67
74
  export const FRESH_CALL_DELIVERY_TOOL: Record<FreshCallBackend, string> = {
68
- pi: "entwurf_v2",
75
+ pi: "mcp__entwurf_bridge__entwurf_v2",
69
76
  "claude-code": "mcp__entwurf-bridge__entwurf_v2",
70
77
  copilot: "entwurf-bridge-entwurf_v2",
71
78
  omp: "mcp__entwurf_bridge_entwurf_v",
@@ -87,7 +94,7 @@ export const FRESH_CALL_DELIVERY_TOOL: Record<FreshCallBackend, string> = {
87
94
  * SAME rule produces the whole word here.
88
95
  */
89
96
  export const FRESH_CALL_PEERS_TOOL: Record<FreshCallBackend, string> = {
90
- pi: "entwurf_peers",
97
+ pi: "mcp__entwurf_bridge__entwurf_peers",
91
98
  "claude-code": "mcp__entwurf-bridge__entwurf_peers",
92
99
  copilot: "entwurf-bridge-entwurf_peers",
93
100
  omp: "mcp__entwurf_bridge_entwurf_peers",
@@ -3,7 +3,7 @@
3
3
  * meta-bridge mailbox body. Every transport that can deliver to a garden citizen
4
4
  * with no live control socket uses this:
5
5
  * - the MCP bridge entwurf_v2 (mcp/entwurf-bridge) — external/Claude-host sends
6
- * - the pi-native entwurf_v2 (pi-extensions/entwurf-control.ts) — pi-session sends
6
+ * (pi-session sends included since #125 — a pi session reaches that same verb)
7
7
  * - the native-push rail (pi-extensions/lib/entwurf-v2-native-push.ts) — direct
8
8
  * injection into an Antigravity conversation or a loaded Codex thread. Its
9
9
  * THIRD consumer, added after the #95 A LIVE run measured what an envelope-less
@@ -659,9 +659,9 @@ const REJECT_HINT: Record<FreshCallRejectReason, string> = {
659
659
  };
660
660
 
661
661
  /**
662
- * ONE renderer for both surfaces. Not a convenience: the two registrations are separate literals
663
- * (that is this repo's shape), so a shared renderer is what keeps the operator-visible answer
664
- * from drifting apart between native pi and the MCP bridge.
662
+ * ONE renderer, outside the registration. Not a convenience: it keeps the operator-visible answer
663
+ * out of a registration literal. (Since #125 the MCP bridge is the only registration; the native
664
+ * pi copy it once had to agree with is retired.)
665
665
  *
666
666
  * The success text states the boundary out loud. A caller that reads "launched" and assumes
667
667
  * "delivered" is the exact confusion the two-receipt split exists to prevent, so the text refuses