@junghanacs/entwurf 0.13.0 → 0.14.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 (193) hide show
  1. package/AGENTS.md +52 -17
  2. package/BASELINE.md +60 -206
  3. package/CHANGELOG.md +64 -0
  4. package/CONTRIBUTING.md +14 -10
  5. package/DELIVERY.md +118 -261
  6. package/README.md +91 -431
  7. package/VERIFY.md +74 -95
  8. package/demo/README.md +1 -1
  9. package/demo/demo-baseline.sh +1 -3
  10. package/demo/demo.sh +2 -5
  11. package/docs/acp-backend-rail.md +236 -0
  12. package/docs/external-mcp-host.md +131 -0
  13. package/docs/fresh-cut-policy.md +99 -0
  14. package/docs/setup-clean-host.md +124 -328
  15. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +160 -67
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +11 -10
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +1 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +4 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +11 -9
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
  32. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
  33. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
  34. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
  35. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
  36. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
  37. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
  38. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
  39. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
  40. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
  41. package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
  42. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
  43. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
  44. package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
  45. package/mcp/entwurf-bridge/src/index.ts +176 -67
  46. package/mcp/entwurf-bridge/start.sh +2 -2
  47. package/mcp/entwurf-bridge/test.sh +23 -9
  48. package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
  49. package/package.json +25 -11
  50. package/pi-extensions/entwurf-control.ts +220 -71
  51. package/pi-extensions/lib/acp/acp-client.ts +22 -0
  52. package/pi-extensions/lib/acp/augment.ts +42 -3
  53. package/pi-extensions/lib/acp/backend-adapter.ts +12 -11
  54. package/pi-extensions/lib/acp/backend.ts +366 -38
  55. package/pi-extensions/lib/acp/config.ts +1 -1
  56. package/pi-extensions/lib/acp/engraving.ts +43 -1
  57. package/pi-extensions/lib/acp/event-mapper.ts +16 -13
  58. package/pi-extensions/lib/acp/models.ts +4 -4
  59. package/pi-extensions/lib/acp/overlay.ts +11 -9
  60. package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
  61. package/pi-extensions/lib/entwurf-core.ts +15 -15
  62. package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
  63. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  64. package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
  65. package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
  66. package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
  67. package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
  68. package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
  69. package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
  70. package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
  71. package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
  72. package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
  73. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
  74. package/pi-extensions/lib/meta-session.ts +93 -5
  75. package/pi-extensions/lib/mux-fresh-call.ts +328 -0
  76. package/pi-extensions/lib/mux-launch.ts +267 -0
  77. package/pi-extensions/lib/mux-placement.ts +387 -0
  78. package/pi-extensions/lib/mux-resume-call.ts +221 -0
  79. package/pi-extensions/lib/resume-launch-identity.ts +162 -0
  80. package/pi-extensions/lib/session-id.js +8 -5
  81. package/pi-extensions/lib/socket-discovery.ts +3 -3
  82. package/prompts/engraving.md +13 -5
  83. package/run.sh +688 -282
  84. package/scripts/agy-bridge-config.py +5 -1
  85. package/scripts/check-acp-backend-preflight.ts +1 -1
  86. package/scripts/check-acp-carrier-augment.ts +246 -10
  87. package/scripts/check-acp-cortex.ts +5 -5
  88. package/scripts/check-acp-overlay.ts +13 -3
  89. package/scripts/check-acp-prompt-lifecycle.ts +565 -0
  90. package/scripts/check-acp-stop-reason.ts +342 -0
  91. package/scripts/check-acp-stream-hooks.ts +504 -0
  92. package/scripts/check-elapsed.sh +25 -0
  93. package/scripts/check-entwurf-bridge-boot.ts +51 -4
  94. package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
  95. package/scripts/check-entwurf-control-rpc.ts +4 -3
  96. package/scripts/check-entwurf-resume-args.ts +72 -70
  97. package/scripts/check-entwurf-session-identity.ts +14 -10
  98. package/scripts/check-entwurf-v2-contract.ts +34 -59
  99. package/scripts/check-entwurf-v2-decider.ts +17 -177
  100. package/scripts/check-entwurf-v2-lock.ts +5 -2
  101. package/scripts/check-entwurf-v2-matrix.ts +3 -53
  102. package/scripts/check-entwurf-v2-production.ts +2 -91
  103. package/scripts/check-entwurf-v2-release.ts +10 -105
  104. package/scripts/check-entwurf-v2-runner.ts +4 -85
  105. package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
  106. package/scripts/check-entwurf-v2-send.ts +0 -28
  107. package/scripts/check-entwurf-v2-surface.ts +198 -128
  108. package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
  109. package/scripts/check-fresh-cut-gate.sh +1 -1
  110. package/scripts/check-gate-qualification.ts +101 -7
  111. package/scripts/check-install-container.sh +10 -2
  112. package/scripts/check-install-surface.ts +1 -1
  113. package/scripts/check-keyset-overlap.py +1 -1
  114. package/scripts/check-meta-facts.ts +249 -0
  115. package/scripts/check-meta-identity-consumers.ts +1 -1
  116. package/scripts/check-meta-session.ts +169 -0
  117. package/scripts/check-mux-launch-tmux.ts +316 -0
  118. package/scripts/check-mux-launch.ts +288 -0
  119. package/scripts/check-mux-launcher-fence.ts +264 -0
  120. package/scripts/check-mux-parent-artifact.ts +195 -0
  121. package/scripts/check-mux-placement-tmux.ts +322 -0
  122. package/scripts/check-mux-placement.ts +323 -0
  123. package/scripts/check-mux-resume-call.ts +282 -0
  124. package/scripts/check-probe-cli-shim.ts +25 -22
  125. package/scripts/check-probe-ordering.ts +121 -81
  126. package/scripts/check-release-gate-outcomes.ts +452 -0
  127. package/scripts/check-resume-launch-identity.ts +244 -0
  128. package/scripts/check-socket-discovery.ts +1 -1
  129. package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
  130. package/scripts/inventory-verification-surface.ts +349 -0
  131. package/scripts/lib/claude-launcher-fence.ts +322 -0
  132. package/scripts/lib/live-skip.ts +33 -0
  133. package/scripts/lib/mutation-qualify.ts +109 -3
  134. package/scripts/lib/probe-acp-turn.ts +33 -12
  135. package/scripts/lib/step-outcome.sh +88 -0
  136. package/scripts/meta-bridge-doctor.sh +6 -8
  137. package/scripts/meta-facts.ts +60 -0
  138. package/scripts/mutants/acp-augment.json +106 -0
  139. package/scripts/mutants/acp-cortex.json +2 -2
  140. package/scripts/mutants/acp-overlay.json +17 -0
  141. package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
  142. package/scripts/mutants/acp-stop-reason.json +80 -0
  143. package/scripts/mutants/acp-stream-hooks.json +158 -0
  144. package/scripts/mutants/bridge-boot-resume.json +45 -0
  145. package/scripts/mutants/meta-facts.json +50 -0
  146. package/scripts/mutants/meta-identity.json +36 -0
  147. package/scripts/mutants/mux-boundary.json +196 -0
  148. package/scripts/mutants/mux-fresh-call.json +185 -0
  149. package/scripts/mutants/mux-launcher-fence.json +123 -0
  150. package/scripts/mutants/mux-parent-artifact.json +39 -0
  151. package/scripts/mutants/mux-resume-call.json +148 -0
  152. package/scripts/mutants/probe-ordering.json +6 -1021
  153. package/scripts/mutants/release-gate.json +140 -0
  154. package/scripts/mutants/resume-args.json +76 -0
  155. package/scripts/mutants/resume-launch-identity.json +96 -0
  156. package/scripts/mutants/v2-surface.json +74 -23
  157. package/scripts/mutants/v2-visible-resume.json +215 -0
  158. package/scripts/new-session-id.ts +9 -4
  159. package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
  160. package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
  161. package/scripts/smoke-acp-cortex-live.ts +8 -14
  162. package/scripts/smoke-acp-long-turn-live.ts +185 -0
  163. package/scripts/smoke-acp-mcp-live.ts +2 -2
  164. package/scripts/smoke-acp-memory-containment-live.ts +2 -2
  165. package/scripts/smoke-acp-ordering-probe-live.ts +2 -2
  166. package/scripts/smoke-acp-overlay-live.ts +2 -2
  167. package/scripts/smoke-acp-provider-live.ts +2 -2
  168. package/scripts/smoke-acp-raw-turn-live.ts +3 -3
  169. package/scripts/smoke-acp-session-reuse-live.ts +2 -2
  170. package/scripts/smoke-acp-skill-live.ts +2 -2
  171. package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
  172. package/scripts/smoke-acp-v2-send-live.ts +4 -3
  173. package/scripts/smoke-agy-native-push-live.ts +10 -20
  174. package/scripts/smoke-claude-native-resume-live.sh +13 -3
  175. package/scripts/smoke-entwurf-chain-live.ts +352 -0
  176. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -3
  177. package/scripts/smoke-meta-honesty.sh +1 -1
  178. package/scripts/smoke-meta-install-state.sh +4 -0
  179. package/scripts/smoke-mux-fresh-call-live.ts +365 -0
  180. package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
  181. package/scripts/smoke-pi-attach.ts +1 -1
  182. package/scripts/smoke-user-scope-citizen.sh +1 -1
  183. package/scripts/tsconfig.json +1 -0
  184. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
  185. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
  186. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
  187. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
  188. package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
  189. package/scripts/check-acp-sdk-surface.ts +0 -236
  190. package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
  191. package/scripts/check-entwurf-v2-spawn.ts +0 -399
  192. package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
  193. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -469
@@ -39,7 +39,6 @@ export interface AcpStreamIdentity {
39
39
  type ObservedToolState = {
40
40
  title: string;
41
41
  status?: string;
42
- notifiedRunning?: boolean;
43
42
  };
44
43
 
45
44
  export type AcpPiStreamState = {
@@ -81,7 +80,12 @@ export function createAcpStreamState(
81
80
  provider: identity.provider,
82
81
  model: identity.model,
83
82
  usage: zeroUsage(),
84
- stopReason: "stop",
83
+ // Seed, not a verdict. pi 0.83 added "pending" for exactly this: a partial
84
+ // streaming message has not observed a terminal reason yet, and every pi
85
+ // provider seeds it here and treats a stream that ENDS still-pending as an
86
+ // error rather than a successful stop. Seeding "stop" instead would
87
+ // pre-claim success for the whole time the turn is in flight.
88
+ stopReason: "pending",
85
89
  timestamp: opts?.timestamp ?? Date.now(),
86
90
  };
87
91
  return {
@@ -221,22 +225,21 @@ function renderToolUpdate(state: AcpPiStreamState, update: Record<string, unknow
221
225
  const title = titleForTool(update, previous?.title);
222
226
  const status = typeof update?.status === "string" ? (update.status as string) : previous?.status;
223
227
  const updateContent = Array.isArray(update?.content) ? (update.content as unknown[]) : undefined;
224
- const meta = update?._meta as { terminal_output?: unknown } | undefined;
225
-
226
- let notifiedRunning = previous?.notifiedRunning;
227
228
 
228
229
  if (update.sessionUpdate === "tool_call") {
229
- observedTools.set(toolCallId, { title, status, notifiedRunning });
230
+ observedTools.set(toolCallId, { title, status });
230
231
  pushNotice(state, `\n[tool:start] ${sanitizeNoticeFragment(title, NOTICE_TITLE_MAX)}\n`);
231
232
  return;
232
233
  }
233
234
 
234
- // tool_call_update
235
- if (meta?.terminal_output && !notifiedRunning) {
236
- notifiedRunning = true;
237
- pushNotice(state, `\n[tool:running] ${sanitizeNoticeFragment(title, NOTICE_TITLE_MAX)}\n`);
238
- }
239
-
235
+ // tool_call_update. There is deliberately no mid-flight `[tool:running]`
236
+ // notice: it would have to be driven by the adapter's `_meta.terminal_output`,
237
+ // and that meta is gated upstream on `clientCapabilities._meta.terminal_output
238
+ // === true` while backend.ts initializes with `clientCapabilities: {}`. So the
239
+ // branch could never fire for any backend we ship — it claimed a transcript
240
+ // line the operator was never going to see. Declaring the terminal capability
241
+ // is a separate axis, not a one-line re-enable: the adapter would then send
242
+ // terminal widgets/metas this mapper cannot render honestly into a transcript.
240
243
  if (status && status !== previous?.status) {
241
244
  const summary = firstTextContent(update?.rawOutput) ?? firstTextContent(updateContent);
242
245
  const suffix = summary ? ` — ${sanitizeNoticeFragment(summary, NOTICE_SUMMARY_MAX)}` : "";
@@ -250,7 +253,7 @@ function renderToolUpdate(state: AcpPiStreamState, update: Record<string, unknow
250
253
  }
251
254
  }
252
255
 
253
- observedTools.set(toolCallId, { title, status, notifiedRunning });
256
+ observedTools.set(toolCallId, { title, status });
254
257
  }
255
258
 
256
259
  /** Push a permission-decision notice (informational text, not a tool call). */
@@ -9,8 +9,8 @@
9
9
  // Two curated sets live here, one per adapter on the rail
10
10
  // (backend-adapter.ts): the unprefixed Claude ids (the reference backend) and
11
11
  // the `cortex-` prefixed Snowflake Cortex Code ids (the second backend, landed
12
- // 0.13.0 — docs/acp-backend-rail.md §6/§11-8). Codex is already a native garden
13
- // citizen and Gemini/major tools use native, so neither is an ACP backend here.
12
+ // 0.13.0 — docs/acp-backend-rail.md “Cortex Code audit”). Codex has native
13
+ // delivery-probe evidence but no managed citizen lane; neither it nor Gemini is an ACP backend here.
14
14
  // A third governed backend EXTENDS this file the same way — it does not change
15
15
  // the pattern.
16
16
 
@@ -87,9 +87,9 @@ function claudeContextWindow(model: { id: string; contextWindow: number }): numb
87
87
  // ── Cortex (Snowflake Cortex Code) curated surface ──────────────────────────
88
88
  //
89
89
  // entwurf's ACP rail is backend-extensible: Cortex is the first NON-claude
90
- // backend to land through it (docs/acp-backend-rail.md §6). pi-ai carries no
90
+ // backend to land through it (docs/acp-backend-rail.md, “Shipped adapters”). pi-ai carries no
91
91
  // snowflake/cortex model source, so this surface is HAND-CURATED. Every id
92
- // carries the reserved `cortex-` prefix (§9-1): the prefix is the SINGLE routing
92
+ // carries the reserved `cortex-` prefix: the prefix is the SINGLE routing
93
93
  // authority (backend-adapter.ts `routeModel`) and keeps the ids off the Claude
94
94
  // native ids Cortex routes to (`cortex-claude-sonnet-5` vs the unprefixed
95
95
  // `claude-sonnet-5` the claude adapter owns). `routeModel` strips the prefix to
@@ -2,8 +2,8 @@
2
2
  //
3
3
  // claude-agent-acp's SettingsManager loads the operator's `~/.claude/settings.json`
4
4
  // DIRECTLY (CLAUDE_CONFIG_DIR is the only knob that redirects that read). So the
5
- // operator's native `permissions.defaultMode` ("auto"), hooks, plugins, and
6
- // per-cwd memory/projects state would otherwise leak into entwurf ACP
5
+ // operator's native `permissions.defaultMode` (whatever its current value), hooks,
6
+ // plugins, and per-cwd memory/projects state would otherwise leak into entwurf ACP
7
7
  // sessions. The overlay redirects SettingsManager at a pi-owned directory whose
8
8
  // `settings.json` WE author (minimal, `hooks:{}`), while keeping exactly the
9
9
  // operator entries a backend needs (credentials, caches, built-in skills)
@@ -15,7 +15,7 @@
15
15
  // materializer, and cortex did: `ensureCortexDualHomeOverlay` (+ projectCortexMcpJson /
16
16
  // sweepDeadCortexOverlays / cortexOverlayScopeId) lives further down this file with its
17
17
  // own D-number rationale — an isolated HOME rather than a redirect knob, because cortex
18
- // has no CLAUDE_CONFIG_DIR equivalent (§11-8 D2). The two share the module, never the
18
+ // has no CLAUDE_CONFIG_DIR equivalent (ACP rail Cortex audit D2). The two share the module, never the
19
19
  // strategy. Codex/Gemini overlays (CODEX_HOME / admin.toml) remain behavior-oracle
20
20
  // territory and are out of scope here.
21
21
  //
@@ -105,9 +105,11 @@ export const OVERLAY_BINARY_OWNED: ReadonlySet<string> = new Set([".claude.json"
105
105
 
106
106
  /**
107
107
  * Minimal overlay settings.json. Only fields with a reason to pin:
108
- * - `permissions.defaultMode: "default"` neutralizes the operator's native
109
- * "auto"; combined with the explicit `tools`/`permissionAllow` surface,
110
- * "default" auto-passes every tool we expose without prompts.
108
+ * - `permissions.defaultMode: "bypassPermissions"` is deliberate unattended
109
+ * ACP operation: a tool call must never suspend a model turn on an interactive
110
+ * permission prompt. This does not widen the callable surface — explicit
111
+ * `tools`/`disallowedTools` still shape it, and `permissionAllow` still rides
112
+ * the inline Claude settings — or bypass backend authentication.
111
113
  * - `autoMemoryEnabled: false` — SDK opt-out for auto-memory (defense in
112
114
  * depth; the tiny non-empty engraving/preset replacement is the primary
113
115
  * write-containment lever for Claude ACP).
@@ -117,7 +119,7 @@ export const OVERLAY_BINARY_OWNED: ReadonlySet<string> = new Set([".claude.json"
117
119
  export function overlaySettingsJson(): string {
118
120
  return `${JSON.stringify(
119
121
  {
120
- permissions: { defaultMode: "default" },
122
+ permissions: { defaultMode: "bypassPermissions" },
121
123
  autoMemoryEnabled: false,
122
124
  hooks: {},
123
125
  },
@@ -230,11 +232,11 @@ export function ensureClaudeConfigOverlay(
230
232
 
231
233
  // ============================================================================
232
234
  // Cortex dual-HOME overlay — the as-measured containment for `cortex acp serve`
233
- // (CP0 audit 2026-07-29, Cortex Code v1.1.52; docs/acp-backend-rail.md §4/§11-8).
235
+ // (CP0 audit 2026-07-29, Cortex Code v1.1.52; docs/acp-backend-rail.md “Cortex Code audit”).
234
236
  // ============================================================================
235
237
  //
236
238
  // Why this is NOT the claude-shaped `SNOWFLAKE_HOME`-only overlay PR #40 shipped
237
- // (each point is a measured defect, not a preference — §11-8 D-numbers):
239
+ // (each point is a measured defect, not a preference — Cortex audit D-numbers):
238
240
  //
239
241
  // - D2: cortex reads `CONFIG_DIRS = [".claude", ".cortex"]` at `homedir()` and
240
242
  // `~/.claude/skills` — a SNOWFLAKE_HOME redirect cannot move that axis. An
@@ -37,12 +37,14 @@ export interface SenderEnvelope {
37
37
 
38
38
  /**
39
39
  * THE `<sender_info>` synthesis — the one place the sender envelope becomes
40
- * message text. Two consumers, one shape (#50 C3):
40
+ * message text. ONE consumer since the visible-first cut (#50 C3):
41
41
  * - the live socket rail's RECEIVER (entwurf-control handleCommand("send")
42
- * appends it to the delivered customMessage);
43
- * - the dormant spawn-resume rail's SENDER (entwurf-v2-production appends it
44
- * to the resume prompt, so a resumed citizen wakes knowing who called in
45
- * exactly the shape a live delivery would have rendered).
42
+ * appends it to the delivered customMessage).
43
+ *
44
+ * The second consumer was the dormant spawn-resume rail's SENDER, which appended
45
+ * the same shape to a resume prompt so a resumed citizen woke knowing who called.
46
+ * That rail is gone. The shape stays single-sourced here so a future VISIBLE
47
+ * resume renders an identical envelope rather than inventing a second one.
46
48
  * `wants_reply` is emitted only when explicitly true — an unset/false marker
47
49
  * renders nothing (etiquette marker, not transport contract).
48
50
  */
@@ -1,19 +1,18 @@
1
1
  /**
2
2
  * entwurf-core — sync entwurf execution, host-agnostic.
3
3
  *
4
- * DIRECT importers, MEASURED from the import graph (2026-07-27) — not from memory:
4
+ * DIRECT importers, RE-MEASURED from the import graph (2026-08-06) — not from memory:
5
5
  * - pi-extensions/entwurf-control.ts (the pi adapter)
6
- * - pi-extensions/lib/entwurf-v2-spawn-production.ts (v2 spawn-bg launch identity)
7
- * - pi-extensions/lib/project-trust-handler.ts · lib/acp/augment.ts ·
8
- * lib/native-push/adapter.ts
9
- * - plus gates/smokes (check-shell-quote, check-package-source-routing, )
6
+ * - pi-extensions/lib/resume-launch-identity.ts (record-authoritative launch identity,
7
+ * itself consumer-zero since the visible-first cut see its header)
8
+ * - plus gates/smokes (check-shell-quote, check-package-source-routing,
9
+ * check-entwurf-session-identity, new-session-id, resolve-acp-bridge)
10
10
  *
11
- * The MCP bridge is a TRANSITIVE consumer, not a direct one, and the distinction is the
12
- * whole point: `mcp/entwurf-bridge/src/index.ts → entwurf-v2-surface.ts →
13
- * entwurf-v2-production.ts → entwurf-v2-spawn-production.ts → entwurf-core.ts`. So the old
14
- * header's claim — that the bridge imports this shared implementation the way a v1 tool
15
- * surface did — was false, but so is "the bridge does not reach it": it reaches it at
16
- * runtime, only for spawn-bg production.
11
+ * The MCP bridge does NOT reach this module on any path, and that changed with the cut:
12
+ * its one edge ran `index.ts → entwurf-v2-surface.ts → entwurf-v2-production.ts →
13
+ * entwurf-v2-spawn-production.ts → entwurf-core.ts`, and the spawn-production link was
14
+ * deleted with the `spawn-bg` transport. Do not restate the old "transitive consumer,
15
+ * for spawn-bg production" line; re-measure before claiming either way.
17
16
  *
18
17
  * NOT consumers on any path: `pi-extensions/entwurf.ts` (the v1 pi native tool surface) and
19
18
  * `pi-extensions/lib/entwurf-async.ts` were REMOVED in the 0.12 cutover. Do not reintroduce
@@ -131,9 +130,9 @@ export function normalizeCodexEntwurfModelForAcp(model?: string): string | undef
131
130
  // The Entwurf Target Registry is GONE (#50 C3). `pi/entwurf-targets.json` and its
132
131
  // reader chain (loadEntwurfTargets / resolveEntwurfTarget / EntwurfRegistryError +
133
132
  // the ~/.pi/agent symlink machinery) were the v1 "narrow door" for spawn-model
134
- // policy — but v2 never spawns from a model tuple: entwurf_v2 resumes an
135
- // already-identified record-backed citizen, and the model axis is the citizen's
136
- // own (Identity Preservation Rule). The last readers were the RT-dead
133
+ // policy — but v2 never spawns at all, from a model tuple or otherwise: entwurf_v2
134
+ // addresses an already-identified record-backed citizen, and the model axis is the
135
+ // citizen's own (Identity Preservation Rule). The last readers were the RT-dead
137
136
  // buildSessionName mirror and the v1 spawn guard, both swept with this cut.
138
137
  // Bridge-extension routing for provider=entwurf survives below (getRegistryRouting
139
138
  // ← scripts/resolve-acp-bridge.ts) and takes a caller-supplied target — no file.
@@ -211,7 +210,8 @@ const SESSION_READ_CHUNK_BYTES = 64 * 1024;
211
210
  * old name-mirror integrity check and the `requireEntwurf` name-tag
212
211
  * authorization are gone (#50 C3): resume authorization is record existence,
213
212
  * and transcript integrity is the caller's header-id ↔ record.nativeSessionId
214
- * check (entwurf-v2-spawn-production.resolveResumeLaunchIdentity).
213
+ * check (resume-launch-identity.resolveResumeLaunchIdentity — the leaf that
214
+ * outlived the spawn-bg factory it used to sit in).
215
215
  */
216
216
  export interface RecordedSessionIdentity {
217
217
  /** JSONL header `id` (pi's own session id — the record's `nativeSessionId`). */
@@ -1,41 +1,43 @@
1
1
  /**
2
- * entwurf-resume-args — the SINGLE source of truth for the `pi` argv a resume spawn is
3
- * launched with (0.11 Stage 0 step 5c-3b). One place owns the
4
- * `--no-extensions` / `--entwurf-control` decision so the launch shape cannot drift.
2
+ * entwurf-resume-args — the SINGLE source of truth for the `pi` argv that reopens a dormant
3
+ * citizen, MEASURED against the runtime rather than inherited from the removed transport.
5
4
  *
6
- * The shipped posture (A1): a RESIDENT, addressable garden citizen. The resume turn runs via
7
- * `-p <prompt>` (the prompt-as-turn authority), and the child is launched WITH
8
- * `--entwurf-control` and WITHOUT `--no-extensions` — the keep-alive is the GOAL here: the
9
- * resumed session stands its control socket up and stays addressable. The 5c-3a watcher's
10
- * `socket-alive` is exactly that "resumed citizen is up" observation (→ release the per-gid
11
- * lock, child lives on); `child-exited` is the early-exit/failure observation.
12
- * `plan.launchArgs` (`--approve` or empty, from the decider's preflight) rides along as pi
13
- * flags before the prompt.
5
+ * ── What changed, and why the old shape was not reusable ──
14
6
  *
15
- * A second `legacy` one-shot posture (`--no-extensions`, no control socket, so `pi -p` could
16
- * exit) lived here until 2026-07-27 and was removed with its launcher and the v1 verbs.
7
+ * Until the visible-first cut this builder emitted `--mode json -p … <prompt>`: a headless JSON
8
+ * child running one prompt-as-turn, which is what `spawn-bg`'s detached `defaultSpawnChild`
9
+ * needed. The design notes carried that argv forward as "visibility-neutral, reuse verbatim".
10
+ * It is not. Measured 2026-08-06 (private tmux + fixture, `pi --help`: `--print, -p` =
11
+ * *non-interactive mode*): dropped into a tmux window, the headless prefix produces a JSON
12
+ * stream and a turn, not a window an operator can type into. The visible dialect is the one
13
+ * `mux-fresh-call` already measured for a fresh sibling — no `--mode`, no `-p`.
17
14
  *
18
- * Provider/model identity is the caller's existing authority (readSessionIdentity /
19
- * getEntwurfExplicitExtensions) — this builder only LAYS OUT argv, it never resolves
20
- * identity. `explicitExtensionArgs` is preserved verbatim: a recorded `provider=entwurf`
21
- * resume needs the bridge re-injected to resolve the provider, and dropping it would
22
- * re-introduce the "Unknown provider" footgun (#29). (A future slice may dedup against
23
- * settings-loaded extensions; not here.)
15
+ * So the shipped posture is now one shape, and it is the only one:
24
16
  *
25
- * This module is import-free on purpose: the v2 adapter and the gate import the same source,
26
- * and a self-contained string builder keeps both tsconfigs happy.
17
+ * pi --entwurf-control [-e <bridge> …] --session <file> [--provider <p>] --model <m>
18
+ *
19
+ * There is deliberately NO prompt and no `launchArgs`. A resume opens the window; talking to the
20
+ * citizen afterwards is `entwurf_v2`'s job on the socket this launch stands up. That split is
21
+ * what makes the resumed turn free: measured, a promptless resume left the transcript
22
+ * byte-identical (1666 → 1666) and started no model turn at all.
23
+ *
24
+ * The one-shot `legacy` variant (removed 2026-07-27) and the headless prefix (removed here) are
25
+ * not kept behind a variant flag for compatibility. An exported branch no product path takes is
26
+ * exactly what let this module's prose claim a live consumer for months.
27
+ *
28
+ * Provider/model identity is the caller's authority (`resolveResumeLaunchIdentity`) — this
29
+ * builder only LAYS OUT argv, it never resolves identity. `explicitExtensionArgs` is preserved
30
+ * verbatim: a recorded `provider=entwurf` resume needs the bridge re-injected or pi cannot
31
+ * resolve the provider (#29). Measured on that axis: the resolver emits
32
+ * `["-e", "<bridge>"]`, and the argv carries it exactly once, between `--entwurf-control` and
33
+ * `--session`.
34
+ *
35
+ * The runtime path itself is NOT here. Resolving `pi` on PATH and proving it launchable belongs
36
+ * to the mux lane (`mux-launch`), so this module emits the flags after the runtime and nothing
37
+ * more — which is also why it stays import-free and can be read by both tsconfigs.
27
38
  */
28
39
 
29
- /** The one shipped launch posture: a resident citizen (`--entwurf-control`, extensions
30
- * loaded). The `legacy` one-shot variant was removed with its launcher (2026-07-27) — an
31
- * exported branch no product path took was what let this module's prose claim a second
32
- * live consumer for months. Reviving a one-shot posture means adding it back deliberately,
33
- * with a consumer, not un-deleting a dead enum member. */
34
- export type ResumeArgsVariant = "v2-control";
35
-
36
40
  export interface ResumePiArgsInput {
37
- /** v2-control = resident citizen (`--entwurf-control`, extensions loaded). */
38
- variant: ResumeArgsVariant;
39
41
  /** ABSOLUTE path of the session JSONL to resume — pi's `--session <path>` (#50 C2).
40
42
  * It replaced `--session-id <gardenId>`, which did two jobs that are no longer the
41
43
  * same string: it named the session to reopen AND fixed the control-socket key. The
@@ -52,37 +54,24 @@ export interface ResumePiArgsInput {
52
54
  provider: string | null | undefined;
53
55
  /** The resolved launch model (caller applies `modelOverride ?? resumeModel`). */
54
56
  model: string;
55
- /** The resume prompt — the final positional, run as the model turn under `-p`. */
56
- prompt: string;
57
- /** The decider's `plan.launchArgs` (`["--approve"]` or `[]`). */
58
- launchArgs?: readonly string[];
59
57
  }
60
58
 
61
59
  /**
62
- * Build the `pi` argv for a resume spawn. The prefix is `--mode json -p` (headless JSON
63
- * child, prompt-as-turn); then the resident posture; then
64
- * `[…ext args] --session <file> [--provider <p>] --model <m> <prompt>`.
60
+ * Build the `pi` flags for a VISIBLE resume, in the measured order.
65
61
  *
66
62
  * Invariants the gate pins:
67
- * - v2-control carries `--entwurf-control` and NO `--no-extensions`, plus `-p` + prompt.
68
- * - `explicitExtensionArgs` appears exactly once.
69
- * - `launchArgs` is included before the suffix.
70
- * - provider/model/prompt identity layout is fixed.
63
+ * - `--entwurf-control` is FIRST: the resumed session must stand its control socket up, or
64
+ * the transcript comes back with no address and the citizen is still unreachable.
65
+ * - no `--mode`, no `-p`, no positional prompt — the window is interactive and no turn runs.
66
+ * - `explicitExtensionArgs` appears exactly once, after the control flag and before
67
+ * `--session`.
68
+ * - provider is emitted only when recorded; `--model <m>` is the tail.
71
69
  */
72
70
  export function buildResumePiArgs(input: ResumePiArgsInput): string[] {
73
- const args: string[] = ["--mode", "json", "-p"];
74
-
75
- // Resident citizen: stand the control socket up (A1) and keep extensions loaded. The
76
- // keep-alive the removed one-shot launcher had to avoid is precisely the goal here —
77
- // the resumed session must stay addressable. `--approve`/launchArgs ride along.
78
- args.push("--entwurf-control");
79
- args.push(...(input.launchArgs ?? []));
80
-
81
- // Suffix — the fixed identity layout.
71
+ const args: string[] = ["--entwurf-control"];
82
72
  args.push(...input.explicitExtensionArgs);
83
73
  args.push("--session", input.sessionFile);
84
74
  if (input.provider) args.push("--provider", input.provider);
85
- args.push("--model", input.model, input.prompt);
86
-
75
+ args.push("--model", input.model);
87
76
  return args;
88
77
  }
@@ -46,7 +46,7 @@ export const EntwurfV2InputSchema = Type.Object(
46
46
  }),
47
47
  intent: StringEnum(ENTWURF_INTENTS, {
48
48
  description:
49
- "caller's declared outcome contract (F1): fire-and-forget = ack only, owned-outcome = caller owns completion.",
49
+ "caller's declared outcome contract (F1). ONE member since the visible-first cut: fire-and-forget = ack only. The second, owned-outcome (caller owns completion), resumed a dormant citizen by launching a hidden background child and was withdrawn — a dormant citizen is currently unreachable by this verb.",
50
50
  }),
51
51
  mode: Type.Optional(
52
52
  StringEnum(ENTWURF_V2_MODES, {