@junghanacs/entwurf 0.13.1 → 0.14.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.
Files changed (158) hide show
  1. package/AGENTS.md +59 -15
  2. package/BASELINE.md +3 -3
  3. package/CHANGELOG.md +74 -0
  4. package/CONTRIBUTING.md +13 -9
  5. package/DELIVERY.md +7 -6
  6. package/README.md +37 -28
  7. package/VERIFY.md +22 -14
  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 +9 -4
  12. package/docs/external-mcp-host.md +4 -5
  13. package/docs/setup-clean-host.md +8 -7
  14. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +155 -28
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +8 -6
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/classify-tmux-cwd.js +47 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +300 -0
  32. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
  33. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
  34. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +141 -0
  35. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
  36. package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
  37. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
  38. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
  39. package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
  40. package/mcp/entwurf-bridge/src/index.ts +173 -28
  41. package/mcp/entwurf-bridge/start.sh +2 -2
  42. package/mcp/entwurf-bridge/test.sh +23 -9
  43. package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
  44. package/package.json +22 -11
  45. package/pi-extensions/entwurf-control.ts +227 -40
  46. package/pi-extensions/lib/acp/backend.ts +71 -12
  47. package/pi-extensions/lib/acp/overlay.ts +8 -6
  48. package/pi-extensions/lib/classify-tmux-cwd.ts +50 -0
  49. package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
  50. package/pi-extensions/lib/entwurf-core.ts +15 -15
  51. package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
  52. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  53. package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
  54. package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
  55. package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
  56. package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
  57. package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
  58. package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
  59. package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
  60. package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
  61. package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
  62. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
  63. package/pi-extensions/lib/meta-session.ts +93 -5
  64. package/pi-extensions/lib/mux-fresh-call.ts +381 -0
  65. package/pi-extensions/lib/mux-launch.ts +267 -0
  66. package/pi-extensions/lib/mux-placement.ts +387 -0
  67. package/pi-extensions/lib/mux-resume-call.ts +189 -0
  68. package/pi-extensions/lib/resume-launch-identity.ts +162 -0
  69. package/pi-extensions/lib/session-id.js +8 -5
  70. package/pi-extensions/lib/socket-discovery.ts +3 -3
  71. package/run.sh +471 -201
  72. package/scripts/agy-bridge-config.py +5 -1
  73. package/scripts/check-acp-backend-preflight.ts +1 -1
  74. package/scripts/check-acp-overlay.ts +13 -3
  75. package/scripts/check-acp-stream-hooks.ts +504 -0
  76. package/scripts/check-elapsed.sh +25 -0
  77. package/scripts/check-entwurf-bridge-boot.ts +51 -4
  78. package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
  79. package/scripts/check-entwurf-control-rpc.ts +4 -3
  80. package/scripts/check-entwurf-resume-args.ts +72 -70
  81. package/scripts/check-entwurf-session-identity.ts +14 -10
  82. package/scripts/check-entwurf-v2-contract.ts +34 -59
  83. package/scripts/check-entwurf-v2-decider.ts +17 -177
  84. package/scripts/check-entwurf-v2-lock.ts +5 -2
  85. package/scripts/check-entwurf-v2-matrix.ts +3 -53
  86. package/scripts/check-entwurf-v2-production.ts +2 -91
  87. package/scripts/check-entwurf-v2-release.ts +10 -105
  88. package/scripts/check-entwurf-v2-runner.ts +4 -85
  89. package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
  90. package/scripts/check-entwurf-v2-send.ts +0 -28
  91. package/scripts/check-entwurf-v2-surface.ts +157 -128
  92. package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
  93. package/scripts/check-fresh-cut-gate.sh +1 -1
  94. package/scripts/check-gate-qualification.ts +99 -7
  95. package/scripts/check-install-container.sh +10 -2
  96. package/scripts/check-install-surface.ts +1 -1
  97. package/scripts/check-keyset-overlap.py +1 -1
  98. package/scripts/check-meta-facts.ts +249 -0
  99. package/scripts/check-meta-identity-consumers.ts +1 -1
  100. package/scripts/check-meta-session.ts +169 -0
  101. package/scripts/check-mux-launch-tmux.ts +316 -0
  102. package/scripts/check-mux-launch.ts +288 -0
  103. package/scripts/check-mux-launcher-fence.ts +264 -0
  104. package/scripts/check-mux-parent-artifact.ts +195 -0
  105. package/scripts/check-mux-placement-tmux.ts +322 -0
  106. package/scripts/check-mux-placement.ts +323 -0
  107. package/scripts/check-mux-resume-call.ts +283 -0
  108. package/scripts/check-probe-cli-shim.ts +25 -22
  109. package/scripts/check-probe-ordering.ts +84 -76
  110. package/scripts/check-release-gate-outcomes.ts +127 -7
  111. package/scripts/check-resume-launch-identity.ts +244 -0
  112. package/scripts/check-socket-discovery.ts +1 -1
  113. package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
  114. package/scripts/inventory-verification-surface.ts +349 -0
  115. package/scripts/lib/claude-launcher-fence.ts +322 -0
  116. package/scripts/lib/mutation-qualify.ts +109 -3
  117. package/scripts/meta-bridge-doctor.sh +6 -8
  118. package/scripts/meta-bridge-state.py +75 -1
  119. package/scripts/meta-facts.ts +60 -0
  120. package/scripts/mutants/acp-overlay.json +17 -0
  121. package/scripts/mutants/acp-stream-hooks.json +158 -0
  122. package/scripts/mutants/bridge-boot-resume.json +45 -0
  123. package/scripts/mutants/meta-facts.json +50 -0
  124. package/scripts/mutants/meta-identity.json +36 -0
  125. package/scripts/mutants/meta-retire.json +47 -0
  126. package/scripts/mutants/mux-boundary.json +196 -0
  127. package/scripts/mutants/mux-fresh-call.json +229 -0
  128. package/scripts/mutants/mux-launcher-fence.json +123 -0
  129. package/scripts/mutants/mux-parent-artifact.json +39 -0
  130. package/scripts/mutants/mux-resume-call.json +148 -0
  131. package/scripts/mutants/probe-ordering.json +0 -1037
  132. package/scripts/mutants/release-gate.json +35 -0
  133. package/scripts/mutants/resume-args.json +76 -0
  134. package/scripts/mutants/resume-launch-identity.json +96 -0
  135. package/scripts/mutants/v2-surface.json +58 -18
  136. package/scripts/mutants/v2-visible-resume.json +215 -0
  137. package/scripts/new-session-id.ts +9 -4
  138. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  139. package/scripts/smoke-agy-native-push-live.ts +6 -17
  140. package/scripts/smoke-entwurf-chain-live.ts +11 -3
  141. package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
  142. package/scripts/smoke-meta-honesty.sh +1 -1
  143. package/scripts/smoke-meta-install-state.sh +169 -3
  144. package/scripts/smoke-mux-fresh-call-live.ts +365 -0
  145. package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
  146. package/scripts/smoke-pi-attach.ts +1 -1
  147. package/scripts/smoke-user-scope-citizen.sh +1 -1
  148. package/scripts/tsconfig.json +1 -0
  149. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
  150. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
  151. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
  152. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
  153. package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
  154. package/scripts/check-acp-sdk-surface.ts +0 -275
  155. package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
  156. package/scripts/check-entwurf-v2-spawn.ts +0 -399
  157. package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
  158. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -467
@@ -11,7 +11,7 @@
11
11
  * Currently exposed tools (scope is deliberately narrow — anything that can live
12
12
  * as a local skill should live as a skill, not here):
13
13
  * - entwurf_v2 — canonical delivery surface for existing garden citizens; the decider
14
- * chooses live control-socket send / dormant spawn-bg resume / meta-mailbox.
14
+ * chooses live control-socket send / meta-mailbox enqueue / native-push.
15
15
  * - entwurf_peers — entwurf fact surface: garden citizens (meta-records) with liveness +
16
16
  * diagnostics (#50 C4: record-less sockets surface THERE, never as identity).
17
17
  * Brain = pi-extensions/lib/entwurf-fact-provider (listEntwurfFacts) +
@@ -24,6 +24,19 @@
24
24
  * garden record may call this — so the surface is "drain the inbox you were
25
25
  * pointed at", not "drain your own". A rung doorbell is a wake attempt;
26
26
  * this read is the receipt.
27
+ * - entwurf_register_native — explicit/manual fallback binding an ALREADY-RUNNING native
28
+ * conversation (antigravity) to a garden id. Never a spawn.
29
+ * - entwurf_fresh_call — open ONE fresh visible sibling in the operator's own tmux session,
30
+ * optionally at ONE literal requested cwd (cross-repo fresh, #73);
31
+ * returns a LAUNCH receipt only, and the new address arrives later as the
32
+ * sender envelope of the sibling's nonce callback.
33
+ * - entwurf_resume_call — reopen ONE DORMANT pi citizen under its OWN garden id in a visible
34
+ * window; target-only, runs no turn, LAUNCH and OBSERVATION receipts stay
35
+ * apart.
36
+ *
37
+ * That list is the WHOLE public surface — seven verbs — and `check-entwurf-bridge-boot`
38
+ * (G1f) holds it as an exact set on the runtime tools/list, so a verb added or dropped
39
+ * here without a decision is red rather than merely undocumented.
27
40
  *
28
41
  * Removed from this v2-only surface: legacy MCP `entwurf`, `entwurf_resume`, and
29
42
  * `entwurf_send`. Use `entwurf_v2` for delivery to existing garden citizens.
@@ -57,8 +70,11 @@ import { renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-ren
57
70
  import { computeSelfAddressability } from "../../../pi-extensions/lib/entwurf-self-address.js";
58
71
  import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
59
72
  import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/entwurf-v2-surface.js";
73
+ import { makeVisibleResumeDeps, renderVisibleResume, visibleResume, } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.js";
60
74
  import { probeNativeSenderAlive, resolveTrustedMetaSenderIdentity, } from "../../../pi-extensions/lib/meta-sender-identity.js";
61
75
  import { defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaInbox, readMetaReceiverMarker, } from "../../../pi-extensions/lib/meta-session.js";
76
+ import { freshCall, renderFreshCall } from "../../../pi-extensions/lib/mux-fresh-call.js";
77
+ import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
62
78
  import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
63
79
  const HOME = os.homedir();
64
80
  // Directory SOURCE is this adapter's own policy — the bridge honours an explicit
@@ -286,13 +302,12 @@ function abbreviateHomeMcp(cwd) {
286
302
  }
287
303
  // entwurf_v2 — the unified v2 dispatch verb (0.11 step 5d-3b). It hands the
288
304
  // target + intent to the 5b decider, which chooses the transport (live
289
- // control-socket send / spawn-bg resume / meta-mailbox enqueue / native-push
290
- // direct injection) and reports one outcome. The per-target lock is NOT taken by
291
- // every rail: the decider locks only a control-socket-domain dispatch, which
292
- // covers the live send AND the dormant cell's spawn-bg resume; the mailbox and
293
- // native-push branches carry `lock: null` (entwurf-v2-decider.ts). It runs
305
+ // control-socket send / meta-mailbox enqueue / native-push direct injection) and
306
+ // reports one outcome. The per-target lock is NOT taken by every rail: the decider
307
+ // locks only a control-socket-domain dispatch; the mailbox and native-push branches
308
+ // carry `lock: null` (entwurf-v2-decider.ts). It runs
294
309
  // IN-PROCESS here (the same production runner pi-native uses) — NOT a delegating
295
- // RPC — so control, mailbox, native-push, AND spawn-bg all flow through
310
+ // RPC — so control, mailbox and native-push all flow through
296
311
  // `runEntwurfV2`. The sender envelope is
297
312
  // `buildSendSenderEnvelope()` verbatim (origin/replyable as resolved) — v2 does
298
313
  // NOT gate on replyability (a `wants_reply` from an external/non-replyable caller
@@ -300,11 +315,10 @@ function abbreviateHomeMcp(cwd) {
300
315
  // sender replyability).
301
316
  server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id " +
302
317
  "alone does not say which rail that citizen answers on. Give target + intent; the decider picks transport " +
303
- "from liveness (live socket citizen → control-socket send; dormant socket citizen → spawn-bg resume; " +
304
- "deliverable self-fetch citizen → meta-bridge mailbox; probe-alive native-push citizen → direct injection " +
305
- "into its conversation) and reports ONE outcome (delivered / rejected / lock-retained / " +
306
- "delivered-but-lock-dirty). EXISTING targets only; discover with entwurf_peers. INTENT picking wrong is " +
307
- "rejected, never auto-converted. A peer entwurf_peers shows as liveness=alive → fire-and-forget. A " +
318
+ "from liveness (live socket citizen → control-socket send; deliverable self-fetch citizen → meta-bridge " +
319
+ "mailbox; probe-alive native-push citizen → direct injection into its conversation) and reports ONE " +
320
+ "outcome (delivered / rejected / delivered-but-lock-dirty). EXISTING targets only; discover with " +
321
+ "entwurf_peers. A peer entwurf_peers shows as liveness=alive fire-and-forget. A " +
308
322
  "citizen with NO socket liveness (liveness=unsupported) is ALSO fire-and-forget — unsupported means only " +
309
323
  '"no control-socket probe" — and the decider picks its own rail: a self-fetch backend (e.g. Claude Code) ' +
310
324
  "gets the mailbox, a native-push backend (e.g. Antigravity) gets direct injection and has NO mailbox at " +
@@ -312,24 +326,25 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
312
326
  "session, or a backend with no adapter here (e.g. codex), is mailbox-undeliverable, not queued for an " +
313
327
  "inbox nobody drains. The native-push probe is 3-valued: alive → injected; dead → " +
314
328
  "native-push-target-dead; indeterminate → native-push-probe-indeterminate (unestablished ≠ gone). " +
315
- "owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY live target → " +
316
- "owned-live-no-autosend, self-fetch backend-liveness-unsupported, native-push " +
317
- "native-push-no-resume-authority. LOCK: taken for a control-socket-DOMAIN dispatch the live send AND " +
318
- "the dormant cell's spawn-bg resume, a separate transport that still runs under that domain's lock. The " +
329
+ "DORMANT IS UNREACHABLE: a socket-domain citizen that is not running gets dormant-fire-forget-unsupported " +
330
+ " same receiver rule as the mailbox, no active drainer means no delivery. " +
331
+ "The intent that used to answer there, owned-outcome, resumed it by launching a hidden background child " +
332
+ "and was withdrawn under the visible-first rule re-open the session yourself, then dispatch again. " +
333
+ "LOCK: taken for a control-socket-DOMAIN dispatch. The " +
319
334
  "mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode " +
320
335
  "applies to a CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message " +
321
336
  "caps at 16000 chars; send an artifact path + digest for more.", {
322
337
  target: z.string().min(1).describe("Target garden id (use entwurf_peers to discover)"),
323
338
  intent: z
324
- .enum(["fire-and-forget", "owned-outcome"])
339
+ .enum(["fire-and-forget"])
325
340
  .describe("fire-and-forget = send/reply/hand-off to a LIVE socket target or to any citizen with no " +
326
341
  "socket liveness — the decider picks its rail, and a rail can also REJECT (self-fetch → " +
327
342
  "mailbox when deliverable, else mailbox-undeliverable; native-push → alive: direct injection, " +
328
343
  "dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate); set " +
329
- "wants_reply for an answer. owned-outcome = " +
330
- "wake a DORMANT socket-domain citizen via spawn-bg resume ONLY on a live target rejected " +
331
- "as owned-live-no-autosend, on self-fetch as backend-liveness-unsupported, on native-push as " +
332
- "native-push-no-resume-authority, and never auto-converted"),
344
+ "wants_reply for an answer. This is the ONLY intent: owned-outcome, which resumed a dormant " +
345
+ "citizen by launching a hidden background child, was withdrawn under the visible-first rule. " +
346
+ "It is not selectable, not deprecated-but-tolerated; a dormant citizen is currently " +
347
+ "unreachable by this verb and rejects as dormant-fire-forget-unsupported."),
333
348
  message: z
334
349
  .string()
335
350
  .min(1)
@@ -339,8 +354,8 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
339
354
  .enum(["steer", "follow_up"])
340
355
  .optional()
341
356
  .describe("Injection style for a CONTROL-SOCKET send only: steer (interrupt the current turn) or " +
342
- "follow_up (queue after it). The mailbox, native-push, and spawn-bg plans carry no mode, so it " +
343
- "has no effect on those rails."),
357
+ "follow_up (queue after it). The mailbox and native-push plans carry no mode, so it has no " +
358
+ "effect on those rails."),
344
359
  wants_reply: z.boolean().optional().describe("Human-conversation reply hint (default false)"),
345
360
  }, async ({ target, intent, message, mode, wants_reply }) => {
346
361
  try {
@@ -348,8 +363,9 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
348
363
  // + the mailbox body sender share one envelope. No replyability gate (see above).
349
364
  const sender = await buildSendSenderEnvelope();
350
365
  const rendered = await runAndRenderEntwurfV2FromSurface({ target, intent, message, mode, wants_reply },
351
- // agentDir / prefixRoots intentionally omitted: runAndRenderEntwurfV2FromSurface falls
352
- // back to the ENTWURF_PREFIX_ROOTS env SSOT for prefixRoots (5d-4); agentDir stays undefined.
366
+ // No trust-preflight inputs are passed, and none exist to pass: the preflight on this
367
+ // path guarded the resume verdict and left with `owned-outcome`. `senderProvider` is
368
+ // the whole options surface now (see the pi-native surface for the same note).
353
369
  { senderProvider: () => sender });
354
370
  return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
355
371
  }
@@ -432,7 +448,9 @@ server.tool("entwurf_peers", "List the entwurf fact surface: garden citizens (fr
432
448
  "`unsupported` for a backend with no control-socket probe such as claude-code); the dispatch " +
433
449
  "decision (send vs resume) is computed LATER by the entwurf_v2 contract from that liveness, " +
434
450
  "not here — this surface carries no per-row routing field, so do not read a transport off a " +
435
- "row. In particular `unsupported` does NOT mean mailbox: it means this backend has no " +
451
+ "row. A `dead` row is a REPORTED FACT and nothing more: that citizen is dormant and is " +
452
+ "currently unreachable by any verb, so listing it grants no action — appearing here is not " +
453
+ "an invitation to dispatch. In particular `unsupported` does NOT mean mailbox: it means this backend has no " +
436
454
  "control-socket probe. Which rail it answers on is a capability the decider resolves at dispatch " +
437
455
  "time, and there are THREE possible answers, not two — a self-fetch mailbox (only while that " +
438
456
  "mailbox is deliverable), native-push direct injection (only while its adapter probe is alive), " +
@@ -442,8 +460,8 @@ server.tool("entwurf_peers", "List the entwurf fact surface: garden citizens (fr
442
460
  "is not a departed host. So " +
443
461
  "`unsupported` does not promise reachability either: a record whose backend has no adapter on " +
444
462
  "this lane resolves to that reject. " +
445
- "Note: this is the *active* world. It is NOT a fresh-sibling creation surface; pass an " +
446
- "existing garden id to entwurf_v2.", {}, async () => {
463
+ "Note: this is the *active* world and it is facts-only it creates nothing. Pass an " +
464
+ "existing garden id to entwurf_v2; to open a NEW sibling use entwurf_fresh_call.", {}, async () => {
447
465
  try {
448
466
  // Meta-store axis: entries WITH their kind (ENOENT = fresh install = empty; any
449
467
  // other readdir failure is a real error, not a silent empty). The name-only
@@ -535,6 +553,115 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
535
553
  return textErr(`entwurf_register_native error: ${err instanceof Error ? err.message : String(err)}`);
536
554
  }
537
555
  });
556
+ // The caller identity for THIS surface comes from the same authoritative resolution
557
+ // `entwurf_self` uses — BOTH of its certified paths: the pi carrier this child inherited
558
+ // (PI_SESSION_ID + PI_AGENT_ID, which a record minted at birth), or a trusted meta-sender marker
559
+ // the native host's own hook wrote. Neither is a guess and neither is deprecated here.
560
+ //
561
+ // What is refused is a caller-supplied id. The measured failure mode is exactly that: asked for
562
+ // its own garden id, a fresh cell answered with its uuidv7 `PI_SESSION_ID` value read out of the
563
+ // environment by an MCP server it had spawned itself — confidently, and wrong. A sibling launched
564
+ // against that answer would call home to a garden id nobody holds.
565
+ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operator's own tmux session and hand it a first task. Two fixed " +
566
+ "backends only: pi, claude-code. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
567
+ "sender envelope of that callback is its garden id — that is how you learn the address of something that " +
568
+ "did not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing " +
569
+ "else: it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls " +
570
+ "for the callback; if it never arrives the window is visible and can be read directly. For EXISTING " +
571
+ "citizens use entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and " +
572
+ "is passed to the chosen runtime CLI (`provider/model` for pi; model id/alias for Claude Code). An optional " +
573
+ "cwd starts the sibling in ONE literal absolute existing directory (cross-repo fresh) — never pick resume " +
574
+ "for a dormant record's cwd; resume is continuity-only. Omitted/empty cwd means the caller's own directory. " +
575
+ "There are no arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to " +
576
+ "same-user processes on this host. Requires that this agent itself runs " +
577
+ "inside tmux: without a pane anchor there is no session to open a sibling beside.", {
578
+ backend: z
579
+ .enum(["pi", "claude-code"])
580
+ .describe("Which fixed runtime to open. Only these two; there is no arbitrary command."),
581
+ model: z
582
+ .string()
583
+ .min(1)
584
+ .max(200)
585
+ // The `\[` below is USELESS TO JS AND LOAD-BEARING TO THE HOST. zod emits this source
586
+ // text verbatim as the JSON Schema `pattern`, and the host's tool-schema validator is a
587
+ // Rust regex engine that rejects an unescaped `[` inside a character class as "unclosed
588
+ // character class" — a 400 on tools/list that stops EVERY Claude session from opening
589
+ // (7-M-fix). Biome's safe fix removes it; do not accept that fix. The suppression must
590
+ // stay on the line DIRECTLY above the expression, or it attaches to nothing and reads
591
+ // as an unused suppression while the escape goes back to being fixable.
592
+ // biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above
593
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._/:\[\]-]*$/)
594
+ .describe("Required runtime model: canonical provider/model for pi, or a Claude Code model id/alias."),
595
+ task: z
596
+ .string()
597
+ .min(1)
598
+ .max(16000)
599
+ .describe("What the sibling should do after it calls you back. Plain instructions; no secrets (see the tool description)."),
600
+ cwd: z
601
+ .string()
602
+ .optional()
603
+ .describe("Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Omit or pass \"\" to start in this agent's own cwd. Taken exactly as given — no trim, no realpath, no project-name resolution; '#' is refused (tmux format expansion). The receipt echoes what was REQUESTED, never an observation."),
604
+ }, async ({ backend, model, task, cwd }) => {
605
+ let callerGardenId = null;
606
+ try {
607
+ const self = await buildAuthoritativeSelfEnvelope();
608
+ callerGardenId = self.envelope.sessionId;
609
+ }
610
+ catch (err) {
611
+ // ONE error is a legitimate answer here: this host has no authoritative identity at all
612
+ // (no pi carrier inherited, no trusted marker written), so the sibling would have nowhere
613
+ // to call home. That normalises to the named refusal.
614
+ //
615
+ // Everything else — a corrupt record, an unreadable store, a broken marker — keeps its
616
+ // own diagnosis and fails loud. Folding those into "you are anonymous" would relabel a
617
+ // store defect as a wiring choice, and the operator would go looking in the wrong place.
618
+ if (!(err instanceof EntwurfEnvelopeWiringError))
619
+ throw err;
620
+ callerGardenId = null;
621
+ }
622
+ try {
623
+ const rendered = renderFreshCall(freshCall({ backend, model, task, cwd, callerGardenId }));
624
+ return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
625
+ }
626
+ catch (err) {
627
+ return textErr(`entwurf_fresh_call error: ${err instanceof Error ? err.message : String(err)}`);
628
+ }
629
+ });
630
+ // The composition root for a visible resume: this surface is the only layer that may know BOTH
631
+ // halves, so it is where the mux launcher is handed to the v2 composition. `entwurf-v2-visible-
632
+ // resume` cannot open a window on its own and `mux-resume-call` cannot read a record — that is
633
+ // the import fence in docs/mux-launch-rail.md §11, expressed as a function argument.
634
+ const visibleResumeLaunch = (input) => {
635
+ const launched = resumeCall({ cwd: input.cwd, runtimeArgs: input.runtimeArgs });
636
+ return launched.ok
637
+ ? { ok: true, handle: launched.receipt }
638
+ : { ok: false, reason: launched.reason, hint: RESUME_CALL_REJECT_HINT[launched.reason] };
639
+ };
640
+ server.tool("entwurf_resume_call", "Reopen ONE DORMANT pi citizen under its OWN garden id, in a visible window in the operator's own tmux " +
641
+ "session. The record supplies everything — which transcript, which model, which provider, which cwd — so " +
642
+ "the only input is the target id: there is no model override, no task, and no prompt. This runs NO turn: " +
643
+ "the window comes back with the conversation and waits, and talking to it is still entwurf_v2 " +
644
+ "fire-and-forget on the socket this call stands up. You get TWO receipts and they mean different things: a " +
645
+ "LAUNCH receipt (tmux made a window and was asked to start pi) and an OBSERVATION receipt (the control " +
646
+ "socket answered under the same id, or resume-unobserved). Unobserved is a real outcome, not an error to " +
647
+ "retry — the window is visible, so read it. A citizen that is already LIVE is refused: address it with " +
648
+ "entwurf_v2 instead. Only pi citizens have a same-id resume, because only they stand a control socket up. " +
649
+ "Requires that this agent itself runs inside tmux.", {
650
+ target: z
651
+ .string()
652
+ .min(1)
653
+ .regex(/^\d{8}T\d{6}-[0-9a-f]{6}$/)
654
+ .describe("Garden id of the DORMANT pi citizen to reopen (discover with entwurf_peers)."),
655
+ }, async ({ target }) => {
656
+ try {
657
+ const result = await visibleResume(target, makeVisibleResumeDeps(visibleResumeLaunch));
658
+ const rendered = renderVisibleResume(result);
659
+ return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
660
+ }
661
+ catch (err) {
662
+ return textErr(`entwurf_resume_call error: ${err instanceof Error ? err.message : String(err)}`);
663
+ }
664
+ });
538
665
  // ============================================================================
539
666
  // Main
540
667
  // ============================================================================
@@ -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)
@@ -86,9 +86,11 @@ export const OVERLAY_EMPTY_DIRS = new Set(["projects", "sessions"]);
86
86
  export const OVERLAY_BINARY_OWNED = new Set([".claude.json", "backups", "settings.json"]);
87
87
  /**
88
88
  * Minimal overlay settings.json. Only fields with a reason to pin:
89
- * - `permissions.defaultMode: "default"` neutralizes the operator's native
90
- * "auto"; combined with the explicit `tools`/`permissionAllow` surface,
91
- * "default" auto-passes every tool we expose without prompts.
89
+ * - `permissions.defaultMode: "bypassPermissions"` is deliberate unattended
90
+ * ACP operation: a tool call must never suspend a model turn on an interactive
91
+ * permission prompt. This does not widen the callable surface — explicit
92
+ * `tools`/`disallowedTools` still shape it, and `permissionAllow` still rides
93
+ * the inline Claude settings — or bypass backend authentication.
92
94
  * - `autoMemoryEnabled: false` — SDK opt-out for auto-memory (defense in
93
95
  * depth; the tiny non-empty engraving/preset replacement is the primary
94
96
  * write-containment lever for Claude ACP).
@@ -97,7 +99,7 @@ export const OVERLAY_BINARY_OWNED = new Set([".claude.json", "backups", "setting
97
99
  */
98
100
  export function overlaySettingsJson() {
99
101
  return `${JSON.stringify({
100
- permissions: { defaultMode: "default" },
102
+ permissions: { defaultMode: "bypassPermissions" },
101
103
  autoMemoryEnabled: false,
102
104
  hooks: {},
103
105
  }, null, 2)}\n`;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * classify-tmux-cwd — the ONE classification of a start directory that is about to be handed
3
+ * to tmux as a `-c` value. Shared leaf of the resume and fresh launch compositions; it owns
4
+ * the classification and NOTHING else — no hints (each consumer phrases its own: resume says
5
+ * "recorded cwd", fresh says "requested cwd"), no argv, no tmux, no fallback directory.
6
+ *
7
+ * Every rule below is a MEASURED tmux 3.6a behaviour (2026-08-06, private server), and each
8
+ * one is a way a launch would look successful while being wrong:
9
+ *
10
+ * 1. a NONEXISTENT `-c` is silent. tmux exits 0, opens the window, and the child falls back
11
+ * to `$HOME`. A launch whose directory has been deleted would therefore open a visible
12
+ * window in the wrong project and look successful. Nothing downstream can catch that:
13
+ * the launch receipt would be perfectly well-formed.
14
+ * 2. `-c` is FORMAT-EXPANDED. `#{pane_id}` inside the value silently rewrote the path
15
+ * (`<dir>/#{pane_id}` → `<dir>/%0`), and a `#(…)` value was observed running its
16
+ * command. A path is data; tmux reads it as a format. So `#` is refused outright.
17
+ * 3. whitespace is SAFE — argv is an array and nothing re-splits. A dir named `with space`
18
+ * arrived intact. So there is no quoting grammar here, and none is owed.
19
+ *
20
+ * That is the entire defence: one existence check and one character. No escaping layer, no
21
+ * sanitiser, no trim, no realpath/symlink policy — a symlinked project dir is a normal thing
22
+ * to work in, and a value is classified exactly as given.
23
+ */
24
+ import { statSync } from "node:fs";
25
+ import path from "node:path";
26
+ /**
27
+ * Classify a candidate start directory. Split into separate reasons rather than one because
28
+ * the operator's next move differs: an absolute-path bug is a caller defect, a missing
29
+ * directory is a moved/deleted project, and a `#` is a path tmux would rewrite under us.
30
+ */
31
+ export function classifyTmuxCwd(cwd) {
32
+ if (!path.isAbsolute(cwd))
33
+ return "cwd-not-absolute";
34
+ // tmux expands formats inside the `-c` VALUE. `#{…}` rewrote the path silently and `#(…)`
35
+ // was observed executing; neither is something to escape our way out of.
36
+ if (cwd.includes("#"))
37
+ return "cwd-format-token";
38
+ let st;
39
+ try {
40
+ st = statSync(cwd);
41
+ }
42
+ catch {
43
+ // tmux would NOT report this — it opens the window and lands the child in $HOME.
44
+ return "cwd-missing";
45
+ }
46
+ return st.isDirectory() ? null : "cwd-not-directory";
47
+ }
@@ -16,12 +16,14 @@
16
16
  import * as net from "node:net";
17
17
  /**
18
18
  * THE `<sender_info>` synthesis — the one place the sender envelope becomes
19
- * message text. Two consumers, one shape (#50 C3):
19
+ * message text. ONE consumer since the visible-first cut (#50 C3):
20
20
  * - the live socket rail's RECEIVER (entwurf-control handleCommand("send")
21
- * appends it to the delivered customMessage);
22
- * - the dormant spawn-resume rail's SENDER (entwurf-v2-production appends it
23
- * to the resume prompt, so a resumed citizen wakes knowing who called in
24
- * exactly the shape a live delivery would have rendered).
21
+ * appends it to the delivered customMessage).
22
+ *
23
+ * The second consumer was the dormant spawn-resume rail's SENDER, which appended
24
+ * the same shape to a resume prompt so a resumed citizen woke knowing who called.
25
+ * That rail is gone. The shape stays single-sourced here so a future VISIBLE
26
+ * resume renders an identical envelope rather than inventing a second one.
25
27
  * `wants_reply` is emitted only when explicitly true — an unset/false marker
26
28
  * renders nothing (etiquette marker, not transport contract).
27
29
  */
@@ -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
@@ -113,9 +112,9 @@ export function normalizeCodexEntwurfModelForAcp(model) {
113
112
  // The Entwurf Target Registry is GONE (#50 C3). `pi/entwurf-targets.json` and its
114
113
  // reader chain (loadEntwurfTargets / resolveEntwurfTarget / EntwurfRegistryError +
115
114
  // the ~/.pi/agent symlink machinery) were the v1 "narrow door" for spawn-model
116
- // policy — but v2 never spawns from a model tuple: entwurf_v2 resumes an
117
- // already-identified record-backed citizen, and the model axis is the citizen's
118
- // own (Identity Preservation Rule). The last readers were the RT-dead
115
+ // policy — but v2 never spawns at all, from a model tuple or otherwise: entwurf_v2
116
+ // addresses an already-identified record-backed citizen, and the model axis is the
117
+ // citizen's own (Identity Preservation Rule). The last readers were the RT-dead
119
118
  // buildSessionName mirror and the v1 spawn guard, both swept with this cut.
120
119
  // Bridge-extension routing for provider=entwurf survives below (getRegistryRouting
121
120
  // ← scripts/resolve-acp-bridge.ts) and takes a caller-supplied target — no file.
@@ -1,53 +1,58 @@
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.
5
- *
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.
14
- *
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.
17
- *
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.)
24
- *
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.
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.
4
+ *
5
+ * ── What changed, and why the old shape was not reusable ──
6
+ *
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`.
14
+ *
15
+ * So the shipped posture is now one shape, and it is the only one:
16
+ *
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
- * Build the `pi` argv for a resume spawn. The prefix is `--mode json -p` (headless JSON
30
- * child, prompt-as-turn); then the resident posture; then
31
- * `[…ext args] --session <file> [--provider <p>] --model <m> <prompt>`.
40
+ * Build the `pi` flags for a VISIBLE resume, in the measured order.
32
41
  *
33
42
  * Invariants the gate pins:
34
- * - v2-control carries `--entwurf-control` and NO `--no-extensions`, plus `-p` + prompt.
35
- * - `explicitExtensionArgs` appears exactly once.
36
- * - `launchArgs` is included before the suffix.
37
- * - provider/model/prompt identity layout is fixed.
43
+ * - `--entwurf-control` is FIRST: the resumed session must stand its control socket up, or
44
+ * the transcript comes back with no address and the citizen is still unreachable.
45
+ * - no `--mode`, no `-p`, no positional prompt — the window is interactive and no turn runs.
46
+ * - `explicitExtensionArgs` appears exactly once, after the control flag and before
47
+ * `--session`.
48
+ * - provider is emitted only when recorded; `--model <m>` is the tail.
38
49
  */
39
50
  export function buildResumePiArgs(input) {
40
- const args = ["--mode", "json", "-p"];
41
- // Resident citizen: stand the control socket up (A1) and keep extensions loaded. The
42
- // keep-alive the removed one-shot launcher had to avoid is precisely the goal here —
43
- // the resumed session must stay addressable. `--approve`/launchArgs ride along.
44
- args.push("--entwurf-control");
45
- args.push(...(input.launchArgs ?? []));
46
- // Suffix — the fixed identity layout.
51
+ const args = ["--entwurf-control"];
47
52
  args.push(...input.explicitExtensionArgs);
48
53
  args.push("--session", input.sessionFile);
49
54
  if (input.provider)
50
55
  args.push("--provider", input.provider);
51
- args.push("--model", input.model, input.prompt);
56
+ args.push("--model", input.model);
52
57
  return args;
53
58
  }