@junghanacs/entwurf 0.45.0 → 0.50.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 (105) hide show
  1. package/AGENTS.md +3 -2
  2. package/BASELINE.md +110 -1
  3. package/CHANGELOG.md +169 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/ENTWURF-CLI.json +1229 -0
  6. package/ENTWURF.json +63 -15
  7. package/FAQ.md +4 -4
  8. package/README.md +124 -188
  9. package/VERIFY.md +2 -2
  10. package/docs/external-mcp-host.md +3 -3
  11. package/docs/setup-clean-host.md +37 -12
  12. package/mcp/entwurf-bridge/dist/ENTWURF.json +63 -15
  13. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +19 -8
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +483 -18
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -10
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +613 -33
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-resume-call.js +497 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +3 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +50 -5
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +7 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-location.js +347 -0
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-owner-fence.js +1132 -0
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +1 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +18 -11
  25. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +30 -15
  26. package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +40 -7
  27. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +1 -27
  28. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +1 -1
  29. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
  30. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +1 -1
  31. package/mcp/entwurf-bridge/src/index.ts +28 -8
  32. package/mcp/entwurf-bridge/tsconfig.build.json +7 -1
  33. package/native/win32-x64/manifest.json +1 -1
  34. package/package.json +6 -3
  35. package/pi/pi-durable/bootstrap.mjs +179 -6
  36. package/pi/pi-durable/carrier/runtime.js +108 -9
  37. package/pi/pi-durable/carrier/session-name.js +11 -0
  38. package/pi/pi-durable/carrier/sessions.js +96 -20
  39. package/pi/pi-durable/overlay/upstream-pin.json +11 -5
  40. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +710 -38
  41. package/pi-extensions/lib/fresh-call-dispatch.ts +64 -12
  42. package/pi-extensions/lib/herdr-fresh-call.ts +919 -40
  43. package/pi-extensions/lib/herdr-resume-call.ts +810 -0
  44. package/pi-extensions/lib/meta-session.ts +13 -8
  45. package/pi-extensions/lib/mux-resume-call.ts +77 -4
  46. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +9 -2
  47. package/pi-extensions/lib/pi-durable-location.ts +446 -0
  48. package/pi-extensions/lib/pi-durable-owner-fence.ts +1461 -0
  49. package/pi-extensions/lib/pi-durable-runtime.ts +4 -0
  50. package/pi-extensions/lib/resume-launch-identity.ts +31 -12
  51. package/pi-extensions/meta-bridge-pi-durable.ts +29 -15
  52. package/run.sh +411 -657
  53. package/scripts/check-claude-launch.ts +341 -0
  54. package/scripts/check-entwurf-v2-surface.ts +2 -2
  55. package/scripts/check-fresh-call-dispatch.ts +70 -9
  56. package/scripts/check-fresh-cut-gate.sh +23 -9
  57. package/scripts/check-gate-qualification.ts +18 -13
  58. package/scripts/check-herdr-activation.ts +301 -23
  59. package/scripts/check-herdr-fresh-call.ts +161 -25
  60. package/scripts/check-herdr-plugin-build.ts +173 -1
  61. package/scripts/check-herdr-plugin.ts +28 -2
  62. package/scripts/check-herdr-runtime-bootstrap.ts +136 -0
  63. package/scripts/check-install-surface.ts +1236 -6
  64. package/scripts/check-meta-doctor-oracle.sh +1 -1
  65. package/scripts/check-meta-facts.ts +20 -1
  66. package/scripts/check-mux-resume-call.ts +28 -0
  67. package/scripts/check-peer-facts.ts +39 -2
  68. package/scripts/cli-help.mjs +222 -0
  69. package/scripts/dev-bin.sh +39 -2
  70. package/scripts/herdr-activation.mjs +28 -6
  71. package/scripts/herdr-operator-link.mjs +389 -0
  72. package/scripts/herdr-plugin-activate.mjs +5 -0
  73. package/scripts/herdr-plugin-deactivate.mjs +18 -5
  74. package/scripts/herdr-rails.mjs +43 -7
  75. package/scripts/herdr-runtime.mjs +27 -0
  76. package/scripts/meta-bridge-fresh-cut.ts +1 -27
  77. package/scripts/meta-bridge-prune.ts +1 -1
  78. package/scripts/meta-facts.ts +1 -1
  79. package/scripts/mutants/agent-guidance.json +4 -4
  80. package/scripts/mutants/claude-launch.json +142 -0
  81. package/scripts/mutants/cli-help-source.json +420 -0
  82. package/scripts/mutants/dev-gate-dispatch.json +109 -0
  83. package/scripts/mutants/fresh-call-dispatch.json +38 -2
  84. package/scripts/mutants/fresh-cut.json +15 -0
  85. package/scripts/mutants/herdr-activation.json +128 -0
  86. package/scripts/mutants/herdr-fresh-call.json +278 -5
  87. package/scripts/mutants/herdr-operator-link.json +318 -0
  88. package/scripts/mutants/herdr-plugin-build.json +55 -0
  89. package/scripts/mutants/herdr-plugin.json +17 -0
  90. package/scripts/mutants/herdr-resume-call.json +288 -0
  91. package/scripts/mutants/herdr-runtime-bootstrap.json +48 -0
  92. package/scripts/mutants/meta-facts.json +15 -0
  93. package/scripts/mutants/mux-resume-call.json +63 -2
  94. package/scripts/mutants/peer-facts.json +15 -0
  95. package/scripts/mutants/pi-durable.json +979 -1
  96. package/scripts/mutants/source-install.json +35 -0
  97. package/scripts/mutants/v2-surface.json +2 -2
  98. package/scripts/mutants/v2-visible-resume.json +255 -6
  99. package/scripts/peer-facts.ts +1 -1
  100. package/scripts/smoke-agy-install-state.sh +66 -0
  101. package/scripts/smoke-meta-prune.sh +23 -0
  102. package/scripts/smoke-omp-fresh-live.ts +12 -3
  103. package/scripts/smoke-omp-receive-live.ts +7 -1
  104. package/scripts/smoke-pi-durable-fresh-live.ts +12 -2
  105. package/team.json.example +39 -0
@@ -42,44 +42,72 @@
42
42
  */
43
43
 
44
44
  import { createHash } from "node:crypto";
45
- import { statSync } from "node:fs";
45
+ import { accessSync, constants as fsConstants, statSync } from "node:fs";
46
+ import { createConnection } from "node:net";
46
47
  import path from "node:path";
47
- import { HERDR_FRESH_CALL_BACKENDS } from "../../scripts/herdr-rails.mjs";
48
+ import {
49
+ HERDR_AGENT_START_BACKENDS,
50
+ HERDR_FRESH_CALL_BACKENDS,
51
+ HERDR_LAYOUT_LAUNCH_BACKENDS,
52
+ } from "../../scripts/herdr-rails.mjs";
48
53
  import { callbackEnvAssignments } from "./callback-env.ts";
49
54
  import {
50
55
  buildOmpBootstrapPayload,
51
56
  composeBackendArgs,
52
57
  composeFreshCallFraming,
58
+ composeFreshCallPrompt,
53
59
  FRESH_CALL_PROVIDER_HINT,
54
60
  type FreshCallComposition,
55
61
  type FreshCallInputRejectReason,
56
62
  type FreshCallProviderRejectReason,
57
63
  normalizeFreshCallInputs,
64
+ OMP_BOOTSTRAP_VERSION,
58
65
  TASK_MAX_CHARS,
59
66
  } from "./fresh-call-composition.ts";
67
+ import {
68
+ PI_DURABLE_PREFLIGHT_HINT,
69
+ type PiDurablePreflightRejectReason,
70
+ piDurableFreshPreflight,
71
+ } from "./pi-durable-fresh-preflight.ts";
72
+ import {
73
+ checkPiDurableRuntime,
74
+ PI_DURABLE_RUNTIME_HINT,
75
+ type PiDurablePackageLayout,
76
+ type PiDurableRuntimeRejectReason,
77
+ piDurablePackageLayout,
78
+ } from "./pi-durable-runtime.ts";
60
79
 
61
80
  /**
62
- * The pilot set, closed. `[#116 decision]` herdr's own `--kind` enum is much larger, and that is
63
- * NOT evidence of support: every other backend is a pre-mutation named reject here, and there is
64
- * no fallback to the tmux rail — a caller inside herdr who asks for codex gets a refusal, not a
65
- * window somewhere else.
81
+ * Three closed sets, one leaf. `[#116 decision]` herdr's own `--kind` enum is much larger, and that
82
+ * is NOT evidence of support. Since #157 A-L1 the rail has two launch domains — the agent-start set
83
+ * (pi, claude-code: `tab create` + `agent start --kind`) and the layout set (pi-durable: one
84
+ * `layout.apply`) — and admits exactly their union: every backend outside it is a pre-mutation
85
+ * named reject, with no fallback to the tmux rail — a caller inside herdr who asks for codex gets a
86
+ * refusal, not a window somewhere else.
66
87
  *
67
- * The VALUE now lives in `scripts/herdr-rails.mjs`, the one leaf the status pane and the install
68
- * narration also read (#116 A7), so a fourth backend cannot appear in prose on one surface while
69
- * this rail still refuses it. The re-export keeps this module's public name unchanged. The TYPE
70
- * still has to be a tuple: a `.mjs` leaf infers `readonly string[]` without the JSDoc cast there,
71
- * and `HerdrFreshCallBackend` would silently widen to `string` while every runtime check still
72
- * passed. A gate pins that.
88
+ * The VALUES live in `scripts/herdr-rails.mjs` (#116 A7). The status pane's RAILS block renders
89
+ * the agent-start and layout sets as two groups (#157 A-L2); the install narration covers only the
90
+ * integrations it activated, which pi-durable never is. The re-export keeps this module's
91
+ * public names. The TYPES still have to be tuples: a `.mjs` leaf infers `readonly string[]` without
92
+ * the JSDoc cast there, and these types would silently widen to `string` while every runtime check
93
+ * still passed. A gate pins that.
73
94
  */
74
- export { HERDR_FRESH_CALL_BACKENDS };
95
+ export { HERDR_AGENT_START_BACKENDS, HERDR_FRESH_CALL_BACKENDS, HERDR_LAYOUT_LAUNCH_BACKENDS };
96
+ /** Everything this rail admits (#157 A-L1: the union of the two launch domains below). */
75
97
  export type HerdrFreshCallBackend = (typeof HERDR_FRESH_CALL_BACKENDS)[number];
98
+ /** The `tab create` + `agent start --kind` domain. The kind maps are keyed by THIS type only. */
99
+ export type HerdrAgentStartBackend = (typeof HERDR_AGENT_START_BACKENDS)[number];
100
+ /** The one-`layout.apply` domain: no herdr kind exists for these, so none is ever requested. */
101
+ export type HerdrLayoutLaunchBackend = (typeof HERDR_LAYOUT_LAUNCH_BACKENDS)[number];
76
102
 
77
103
  /** Our backend name → the `--kind` token herdr accepts. `[측정 2026-09-14]` both round-tripped with
78
104
  * the vendor executable echoed back in `argv`. This is a REQUESTED kind: `[file:line @ c77af189]`
79
105
  * `src/app/agents.rs:197-199` turns it into a bare executable name and writes it into the pane's
80
106
  * shell, so nothing here has proven the binary exists, is on PATH, or will start. Runtime failure
81
- * is a post-mutation outcome on this rail, named as such, never a precondition we can claim. */
82
- export const HERDR_AGENT_KIND: Record<HerdrFreshCallBackend, string> = {
107
+ * is a post-mutation outcome on this rail, named as such, never a precondition we can claim.
108
+ * Keyed by the agent-start domain ONLY: a layout-launched backend has no kind, and inventing one
109
+ * would start herdr's program for that kind under another backend's name. */
110
+ export const HERDR_AGENT_KIND: Record<HerdrAgentStartBackend, string> = {
83
111
  pi: "pi",
84
112
  "claude-code": "claude",
85
113
  };
@@ -141,7 +169,17 @@ export type HerdrFreshCallRejectReason =
141
169
  | "herdr-argv-control-character"
142
170
  | "cwd-not-absolute"
143
171
  | "cwd-missing"
144
- | "cwd-not-directory";
172
+ | "cwd-not-directory"
173
+ // #157 A-L1 — the layout branch's own pre-mutation refusals.
174
+ /** `HERDR_SOCKET_PATH` is absent, empty or relative. Read ONLY on the layout branch: the
175
+ * agent-start backends never need it and are never refused for its absence. */
176
+ | "herdr-socket-path-missing"
177
+ /** The verified package's own `run.sh` is not an executable regular file. */
178
+ | "pi-durable-launcher-missing"
179
+ /** The caller is a record-backed Codex citizen, asked for no cwd, and its record carries none.
180
+ * Its process directory is the app-server's, so it is NOT a fallback (#95 lane C). */
181
+ | "codex-caller-cwd-unavailable"
182
+ | PiDurablePreflightRejectReason;
145
183
 
146
184
  /** Why a launch that had ALREADY created a tab failed. These are separate from the refusals above
147
185
  * because the operator's next question is different: something exists and may need reclaiming. */
@@ -164,6 +202,25 @@ export type HerdrLaunchFailureReason =
164
202
  /** The echoed argv is not the canonical executable plus exactly what we passed after `--`. */
165
203
  | "herdr-agent-start-argv-drift";
166
204
 
205
+ /**
206
+ * #157 A-L1 — why the ONE layout mutation write did not yield a launch receipt. A separate type
207
+ * from the agent-start failures above because these do NOT all mean a tab exists: each carries a
208
+ * `none` or `unknown` recovery, and only the connect-before-write case or a well-formed pre-create
209
+ * herdr error is `none`. The same-id resume reuses the agent-start set and never sees these.
210
+ */
211
+ export type HerdrLayoutFailureReason =
212
+ /** The socket exchange itself failed: before the write (`none`) or after it (`unknown`). */
213
+ | "herdr-layout-transport-failed"
214
+ /** A well-formed herdr error whose code the pinned handler returns before any creation. */
215
+ | "herdr-layout-apply-refused"
216
+ /** A well-formed herdr error with any other code, `layout_apply_failed` included — it is also
217
+ * returned after a tab was created or a rollback attempted, so it proves nothing about creation. */
218
+ | "herdr-layout-apply-failed"
219
+ /** The reply line is not one well-formed, id-correlated envelope with exactly one arm. */
220
+ | "herdr-layout-reply-unparsable"
221
+ /** A success envelope that does not prove THIS launch: wrong arm, workspace, shape or echo. */
222
+ | "herdr-layout-reply-drift";
223
+
167
224
  /** Why an orphan pane could NOT be reclaimed. `[측정 2026-09-14, isolated sandbox server]` a herdr
168
225
  * server restart brought the SAME pane ids back bound to entirely different terminals
169
226
  * (`w1:p1/p2/p3`, `term_65b6d90e…` → `term_65b6d916…`). So a bare pane id carries no authority to
@@ -222,11 +279,13 @@ export type HerdrRecovery =
222
279
  * whether herdr had been told a session id by the time we stopped asking, and carries no id.
223
280
  */
224
281
  export interface HerdrFreshCallReceipt {
225
- readonly backend: HerdrFreshCallBackend;
282
+ readonly backend: HerdrAgentStartBackend;
226
283
  /** The kind herdr was ASKED for. Not proof that the runtime exists — see `HERDR_AGENT_KIND`. */
227
284
  readonly requestedKind: string;
228
285
  readonly model: string;
229
- readonly cwd?: string;
286
+ /** The explicit start directory sent as `--cwd` — always present since #157 A-L1x. */
287
+ readonly cwd: string;
288
+ readonly cwdSource: HerdrLayoutCwdSource;
230
289
  readonly herdrAgentName: string;
231
290
  readonly herdrWorkspaceId: string;
232
291
  readonly herdrTabId: string;
@@ -268,15 +327,77 @@ export interface HerdrWitness {
268
327
  readonly settleMs: number;
269
328
  }
270
329
 
330
+ /**
331
+ * The pi-durable LAYOUT launch receipt (#157 A-L1): what was requested and the coordinates herdr's
332
+ * own `layout_apply` reply named. Read it as "herdr accepted one layout request and named this tab".
333
+ *
334
+ * ABSENT BY CONTRACT, typed `never` so no reader can mistake one for present: no herdr kind (none
335
+ * exists for pi-durable and none was requested), no agent name, no terminal id (the layout reply
336
+ * carries none, so this branch has no create-receipt terminal and therefore no close authority),
337
+ * and no witness (no detection was requested). Like the agent-start receipt it carries no garden id
338
+ * or native session id: the address arrives only in the sibling's callback envelope.
339
+ *
340
+ * `focus:false` is what we REQUESTED. herdr's `focused_pane_id` is focus INSIDE the new tab, so this
341
+ * receipt does not certify that the tab is unfocused; the actual view is a separate observation.
342
+ */
343
+ export interface HerdrLayoutFreshCallReceipt {
344
+ readonly backend: HerdrLayoutLaunchBackend;
345
+ readonly launch: "layout-apply";
346
+ readonly model: string;
347
+ /** The explicit start directory sent as `root.cwd` — always present on this branch. */
348
+ readonly cwd: string;
349
+ readonly cwdSource: HerdrLayoutCwdSource;
350
+ /** The verified package's own launcher, the first element of the requested argv vector. */
351
+ readonly runtime: string;
352
+ readonly herdrWorkspaceId: string;
353
+ readonly herdrTabId: string;
354
+ readonly herdrPaneId: string;
355
+ readonly nonce: string;
356
+ readonly requestedKind?: never;
357
+ readonly herdrAgentName?: never;
358
+ readonly herdrTerminalId?: never;
359
+ readonly witness?: never;
360
+ }
361
+
362
+ /** Where the layout branch's explicit cwd came from. `caller-process` is THIS process's directory —
363
+ * the same inherited fact the tmux rail's omitted cwd rests on — and is never used for a Codex
364
+ * caller, whose process directory is the app-server's. */
365
+ export type HerdrLayoutCwdSource = "requested" | "codex-caller-record" | "caller-process";
366
+
367
+ /**
368
+ * Display-only diagnostics a layout failure carries: what herdr or the transport REPORTED. Every
369
+ * value is cut to `HERDR_DIAGNOSTIC_MAX_CHARS` and is rendered quoted. All decisions were made on the
370
+ * complete original values before this object was built, and nothing here is a receipt, a
371
+ * placement or authority to close anything.
372
+ */
373
+ export interface HerdrReportedDiagnostics {
374
+ readonly transport?: string;
375
+ readonly errorCode?: string;
376
+ readonly errorMessage?: string;
377
+ readonly workspaceId?: string;
378
+ readonly tabId?: string;
379
+ readonly paneId?: string;
380
+ }
381
+
271
382
  export type HerdrFreshCallResult =
272
- | { readonly ok: true; readonly receipt: HerdrFreshCallReceipt }
273
- | { readonly ok: false; readonly reason: HerdrFreshCallRejectReason }
383
+ | { readonly ok: true; readonly receipt: HerdrFreshCallReceipt | HerdrLayoutFreshCallReceipt }
274
384
  | {
275
385
  readonly ok: false;
276
- readonly reason: HerdrLaunchFailureReason;
386
+ readonly reason: HerdrFreshCallRejectReason;
387
+ /** Layout branch caller-pane read only: herdr's bounded, display-only cause (A-L1 D4). */
388
+ readonly reported?: HerdrReportedDiagnostics;
389
+ }
390
+ | {
391
+ readonly ok: false;
392
+ readonly reason: HerdrLaunchFailureReason | HerdrLayoutFailureReason;
277
393
  /** herdr's own error code when it gave one (`{"error":{"code":…}}`) — quoted, never retyped. */
278
394
  readonly herdrErrorCode?: string;
279
395
  readonly recovery: HerdrRecovery;
396
+ /** Layout branch only: bounded, display-only reported values (see the type). */
397
+ readonly reported?: HerdrReportedDiagnostics;
398
+ /** Layout branch, POST-WRITE `unknown` only: the launch nonce a sibling that did start
399
+ * would still carry in its callback, so the caller can correlate one if it arrives. */
400
+ readonly nonce?: string;
280
401
  };
281
402
 
282
403
  /** One invocation of the herdr CLI, injected. Production supplies `execFileSync`; tests supply a
@@ -652,7 +773,7 @@ export function parseHerdrAgentStartResponse(stdout: string): HerdrStartFacts |
652
773
  /** The canonical executable herdr writes for a requested kind, measured twice today (`["claude"]`
653
774
  * and `["pi","--entwurf-control"]`) and pinned upstream at `src/detect/mod.rs:155-156 @ c77af189`.
654
775
  * We only assert it for the two pilot kinds we have actually seen. */
655
- export const HERDR_CANONICAL_EXECUTABLE: Record<HerdrFreshCallBackend, string> = {
776
+ export const HERDR_CANONICAL_EXECUTABLE: Record<HerdrAgentStartBackend, string> = {
656
777
  pi: "pi",
657
778
  "claude-code": "claude",
658
779
  };
@@ -662,7 +783,7 @@ export const HERDR_CANONICAL_EXECUTABLE: Record<HerdrFreshCallBackend, string> =
662
783
  * reveal. */
663
784
  export function argvMatchesRequest(
664
785
  echoed: readonly string[] | null,
665
- backend: HerdrFreshCallBackend,
786
+ backend: HerdrAgentStartBackend,
666
787
  backendArgs: readonly string[],
667
788
  ): boolean {
668
789
  if (echoed === null) return false;
@@ -786,11 +907,18 @@ export async function herdrFreshCall(
786
907
  readonly cwd?: string;
787
908
  readonly placement?: { readonly tmuxSession?: string };
788
909
  readonly callerGardenId: string | null;
910
+ /** Both herdr branches (layout #157 A-L1, agent start A-L1x): the record-backed Codex caller's
911
+ * own record cwd, consulted only when no cwd was requested. */
912
+ readonly callerCwd?: string;
913
+ /** Both herdr branches: whether the caller is a record-backed Codex citizen. A boolean on
914
+ * purpose — no thread id, pane title or seat reaches this rail. */
915
+ readonly callerIsCodex?: boolean;
789
916
  },
790
917
  run: HerdrRun,
791
918
  env: NodeJS.ProcessEnv,
792
919
  nonce: string,
793
920
  clock: HerdrClock = HERDR_REAL_CLOCK,
921
+ layoutDeps: Partial<HerdrLayoutDeps> = {},
794
922
  ): Promise<HerdrFreshCallResult> {
795
923
  const context = resolveHerdrContext(env);
796
924
  if (!context.ok) return { ok: false, reason: context.reason };
@@ -803,19 +931,34 @@ export async function herdrFreshCall(
803
931
  // worse than a refusal they can read.
804
932
  return { ok: false, reason: "herdr-backend-unsupported" };
805
933
  }
806
- const backend = params.backend as HerdrFreshCallBackend;
934
+ if ((HERDR_LAYOUT_LAUNCH_BACKENDS as readonly string[]).includes(params.backend)) {
935
+ // #157 A-L1: a backend herdr has no kind for. It never reaches `tab create`/`agent start`
936
+ // below and never runs the herdr CLI; its own branch owns its whole order.
937
+ return herdrLayoutFreshCall(params, context.context, env, nonce, {
938
+ ...HERDR_LAYOUT_PRODUCTION_DEPS,
939
+ ...layoutDeps,
940
+ });
941
+ }
942
+ const backend = params.backend as HerdrAgentStartBackend;
807
943
 
808
944
  // The caller's inputs are judged by the SHARED contract — same order, same trimming, same
809
945
  // five words — so a caller cannot learn a different vocabulary by being inside herdr. The
810
- // cwd omission rule comes with it: `undefined` and `""` both mean "no cwd", which this rail
811
- // previously mistook for an invalid path.
946
+ // cwd omission rule comes with it: `undefined` and `""` both mean "no cwd requested", which this
947
+ // rail once mistook for an invalid path. An omitted cwd means the CALLER's directory, chosen by
948
+ // the same helper the layout branch uses (#157 A-L1x), and is always sent explicitly: herdr's own
949
+ // default for an omitted `--cwd` is herdr's, not a fact about the caller.
812
950
  const normalized = normalizeFreshCallInputs(params);
813
951
  if (!normalized.ok) return { ok: false, reason: normalized.reason };
814
- const { callerGardenId, model, task, cwd } = normalized.inputs;
815
- if (cwd !== undefined) {
816
- const badCwd = classifyHerdrCwd(cwd);
817
- if (badCwd !== null) return { ok: false, reason: badCwd };
818
- }
952
+ const { callerGardenId, model, task, cwd: requestedCwd } = normalized.inputs;
953
+ const chosenCwd = chooseHerdrLayoutCwd({
954
+ ...(requestedCwd === undefined ? {} : { requestedCwd }),
955
+ callerIsCodex: params.callerIsCodex === true,
956
+ ...(params.callerCwd === undefined ? {} : { callerCwd: params.callerCwd }),
957
+ callerProcessCwd: layoutDeps.callerProcessCwd ?? HERDR_LAYOUT_PRODUCTION_DEPS.callerProcessCwd,
958
+ });
959
+ if (!chosenCwd.ok) return { ok: false, reason: chosenCwd.reason };
960
+ const badCwd = classifyHerdrCwd(chosenCwd.cwd);
961
+ if (badCwd !== null) return { ok: false, reason: badCwd };
819
962
 
820
963
  const framing = composeFreshCallFraming({
821
964
  backend,
@@ -868,7 +1011,7 @@ export async function herdrFreshCall(
868
1011
  const tabRun = await run(
869
1012
  buildHerdrTabCreateArgs({
870
1013
  workspaceId: callerPane.workspaceId,
871
- ...(cwd === undefined ? {} : { cwd }),
1014
+ cwd: chosenCwd.cwd,
872
1015
  callback: { target: callerGardenId, nonce },
873
1016
  }),
874
1017
  );
@@ -964,7 +1107,8 @@ export async function herdrFreshCall(
964
1107
  backend,
965
1108
  requestedKind: HERDR_AGENT_KIND[backend],
966
1109
  model,
967
- ...(cwd === undefined ? {} : { cwd }),
1110
+ cwd: chosenCwd.cwd,
1111
+ cwdSource: chosenCwd.source,
968
1112
  herdrAgentName: agentName,
969
1113
  // The tab coordinates come from the reply that CREATED them, not from the start's
970
1114
  // echo: the create receipt is what the reclaim above is bound to, so the receipt an
@@ -1084,8 +1228,13 @@ export async function settleAgentWitness(params: {
1084
1228
  }
1085
1229
  }
1086
1230
 
1087
- /** Reclaim the pane we opened — conditionally, or not at all. */
1088
- async function reclaim(pane: HerdrPaneFacts, run: HerdrRun): Promise<HerdrRecovery> {
1231
+ /** Reclaim the pane we opened — conditionally, or not at all.
1232
+ *
1233
+ * Exported for the ONE other herdr launch that creates a tab and can fail after it: the same-id pi
1234
+ * resume (`herdr-resume-call.ts`, #157 G1a). It is the existing within-generation witness
1235
+ * (`decideConditionalClose`) plus the one pane close, reused rather than copied, so the two herdr
1236
+ * launches cannot drift into different ideas of what may be closed. */
1237
+ export async function reclaim(pane: HerdrPaneFacts, run: HerdrRun): Promise<HerdrRecovery> {
1089
1238
  const getRun = await run(buildHerdrPaneGetArgs(pane.paneId));
1090
1239
  const decision = decideConditionalClose(
1091
1240
  pane,
@@ -1098,12 +1247,628 @@ async function reclaim(pane: HerdrPaneFacts, run: HerdrRun): Promise<HerdrRecove
1098
1247
  return { outcome: "closed", paneId: pane.paneId, terminalId: pane.terminalId };
1099
1248
  }
1100
1249
 
1250
+ // ── #157 A-L1: pi-durable through ONE layout.apply on the caller's own herdr socket ──────────────
1251
+ //
1252
+ // WHY A SOCKET AND NOT THE CLI. The documented herdr CLI exposes no layout mutation, and
1253
+ // `layout.apply` is the only herdr surface that execs an argv VECTOR in a new tab — no `--kind`, no
1254
+ // shell, no PTY keystrokes (`[blob 2ecf19a9]` app-api-layouts.rs:117-140, pane.rs:2456-2500). So this
1255
+ // branch sends one fixed JSON line per connection and reads one reply line: the framing herdr's own
1256
+ // pi integration uses, and the one measured on the pinned 0.9.5 server (G2/G3).
1257
+ //
1258
+ // ONE ENDPOINT FOR THE READ AND THE WRITE. The caller-pane read that names the workspace and the
1259
+ // mutation that uses it go to the SAME explicit endpoint, `HERDR_SOCKET_PATH` — never the CLI for one
1260
+ // and the socket for the other, which could each reach a different server where `w1` names a
1261
+ // different workspace. Two connections do NOT establish server-generation invariance across a
1262
+ // restart or rebind: a valid inherited env pair (`HERDR_PANE_ID`, `HERDR_SOCKET_PATH`) and endpoint
1263
+ // continuity during this call are ASSUMPTIONS, named here rather than proven, and no generation or
1264
+ // liveness manager is added for them. The read is also a physical socket write; "exactly one" below
1265
+ // always means exactly one LAYOUT MUTATION write.
1266
+ //
1267
+ // WHAT THE PARSERS ARE. An authority-bearing REDUCTION of herdr's replies, not a vendor-schema
1268
+ // validator: they check the envelope this launch relies on and the id/echo fields it needs, against
1269
+ // the binary-bundled schema (protocol 22) and recorded replies, and nothing else. `agent_status` is
1270
+ // checked for presence only and is never interpreted. `result` XOR `error` is Entwurf's own strict
1271
+ // acceptance policy — the schema does not forbid extra properties.
1272
+
1273
+ /** Each reported diagnostic string is cut to this many characters for DISPLAY, and rendered quoted.
1274
+ * Every decision is taken on the complete original value before it is cut. */
1275
+ export const HERDR_DIAGNOSTIC_MAX_CHARS = 256;
1276
+
1277
+ /**
1278
+ * The error codes that prove NOTHING was created, for exactly this request shape (a `workspace_id`,
1279
+ * no `tab_id`, one pane). `[blob 2ecf19a9]` the pinned `handle_layout_apply` returns these only
1280
+ * before its first creation call: `workspace_not_found` at app-api-layouts.rs:56/66/114,
1281
+ * `invalid_layout` at :69/:97/:109, and `invalid_env` at :105 (app-api-env.rs:3-32 yields no other
1282
+ * code). `tab_not_found`/`invalid_target` need a `tab_id`, which this branch never sends, so they are
1283
+ * NOT here — and `layout_apply_failed` is returned at :143/:158/:196 and after the creation events,
1284
+ * so it never proves anything about creation.
1285
+ */
1286
+ export const HERDR_LAYOUT_NONE_CODES: readonly string[] = Object.freeze([
1287
+ "workspace_not_found",
1288
+ "invalid_layout",
1289
+ "invalid_env",
1290
+ ]);
1291
+
1292
+ /** What one request on the socket came back as. `connect-failed` is the ONLY outcome in which the
1293
+ * request line was certainly never written. */
1294
+ export type HerdrSocketOutcome =
1295
+ | { readonly kind: "connect-failed"; readonly code: string }
1296
+ | { readonly kind: "reply"; readonly line: string }
1297
+ | { readonly kind: "io-failed"; readonly code: string }
1298
+ | { readonly kind: "timeout" }
1299
+ | { readonly kind: "oversize" }
1300
+ | { readonly kind: "eof-before-newline" };
1301
+
1302
+ /** One request, one connection, one reply line. No retry, no reconnect, no resend. */
1303
+ export type HerdrSocketCall = (requestLine: string) => Promise<HerdrSocketOutcome>;
1304
+ /** Bound to ONE endpoint: both of this branch's requests go through the call it returns. */
1305
+ export type HerdrSocketCallFactory = (socketPath: string) => HerdrSocketCall;
1306
+
1307
+ export interface HerdrSocketBounds {
1308
+ readonly timeoutMs: number;
1309
+ /** The whole reply LINE (bytes before its newline). Larger is `oversize`, never truncated. */
1310
+ readonly maxReplyBytes: number;
1311
+ }
1312
+
1313
+ /**
1314
+ * The production socket client. The reply is collected as BYTES and decoded once, after its newline,
1315
+ * so a multi-byte character split across chunks is never decoded in halves. A bound that expires
1316
+ * before the connection exists is a connect failure (nothing written); after it, a timeout (the
1317
+ * request may have been written). Exactly one settle: connect, data, error, close and the timer race.
1318
+ */
1319
+ export function createHerdrSocketCall(
1320
+ socketPath: string,
1321
+ bounds: HerdrSocketBounds = { timeoutMs: HERDR_CLI_TIMEOUT_MS, maxReplyBytes: HERDR_MAX_OUTPUT_BYTES },
1322
+ ): HerdrSocketCall {
1323
+ return (requestLine) =>
1324
+ new Promise((resolve) => {
1325
+ let settled = false;
1326
+ let connected = false;
1327
+ let lineBytes = 0;
1328
+ const chunks: Buffer[] = [];
1329
+ const socket = createConnection({ path: socketPath });
1330
+ const settle = (outcome: HerdrSocketOutcome) => {
1331
+ if (settled) return;
1332
+ settled = true;
1333
+ clearTimeout(timer);
1334
+ socket.destroy();
1335
+ resolve(outcome);
1336
+ };
1337
+ const timer = setTimeout(
1338
+ () => settle(connected ? { kind: "timeout" } : { kind: "connect-failed", code: "timeout-before-connect" }),
1339
+ bounds.timeoutMs,
1340
+ );
1341
+ timer.unref?.();
1342
+ socket.on("connect", () => {
1343
+ connected = true;
1344
+ socket.write(requestLine);
1345
+ });
1346
+ socket.on("data", (chunk: Buffer) => {
1347
+ const newline = chunk.indexOf(0x0a);
1348
+ const part = newline === -1 ? chunk : chunk.subarray(0, newline);
1349
+ lineBytes += part.length;
1350
+ if (lineBytes > bounds.maxReplyBytes) {
1351
+ settle({ kind: "oversize" });
1352
+ return;
1353
+ }
1354
+ chunks.push(part);
1355
+ if (newline !== -1) settle({ kind: "reply", line: decodeHerdrReplyBytes(chunks) });
1356
+ });
1357
+ socket.on("error", (error: NodeJS.ErrnoException) => {
1358
+ const code = error.code ?? error.message;
1359
+ settle(connected ? { kind: "io-failed", code } : { kind: "connect-failed", code });
1360
+ });
1361
+ socket.on("close", () => settle({ kind: "eof-before-newline" }));
1362
+ });
1363
+ }
1364
+
1365
+ /** The reply line's ONE decode: every collected chunk concatenated, then UTF-8 — never chunk by
1366
+ * chunk, so a multi-byte character split between reads is decoded whole. */
1367
+ export function decodeHerdrReplyBytes(chunks: readonly Buffer[]): string {
1368
+ return Buffer.concat(chunks).toString("utf8");
1369
+ }
1370
+
1371
+ /** The fixed caller-pane read. Product function, not an RPC surface: method and shape are literal. */
1372
+ export function buildHerdrPaneGetPacket(id: string, paneId: string): string {
1373
+ return `${JSON.stringify({ id, method: "pane.get", params: { pane_id: paneId } })}\n`;
1374
+ }
1375
+
1376
+ /** The fixed layout mutation: one unfocused pane tab in a named workspace, an explicit cwd, an argv
1377
+ * vector and an env map. No `tab_id`, `tab_label`, split, label or ratio can be expressed through it. */
1378
+ export function buildHerdrLayoutApplyPacket(
1379
+ id: string,
1380
+ params: {
1381
+ readonly workspaceId: string;
1382
+ readonly cwd: string;
1383
+ readonly command: readonly string[];
1384
+ readonly env: Readonly<Record<string, string>>;
1385
+ },
1386
+ ): string {
1387
+ return `${JSON.stringify({
1388
+ id,
1389
+ method: "layout.apply",
1390
+ params: {
1391
+ workspace_id: params.workspaceId,
1392
+ focus: false,
1393
+ root: { type: "pane", cwd: params.cwd, command: [...params.command], env: { ...params.env } },
1394
+ },
1395
+ })}\n`;
1396
+ }
1397
+
1398
+ /** The launch env: the identity scrub the other launches use, plus the callback pair from its one
1399
+ * validating formatter (which throws rather than inject a pair the callback verb would refuse). */
1400
+ export function buildHerdrLayoutLaunchEnv(callback: { target: string; nonce: string }): Record<string, string> {
1401
+ const env: Record<string, string> = { PI_SESSION_ID: "", PI_AGENT_ID: "" };
1402
+ for (const assignment of callbackEnvAssignments(callback)) {
1403
+ const at = assignment.indexOf("=");
1404
+ env[assignment.slice(0, at)] = assignment.slice(at + 1);
1405
+ }
1406
+ return env;
1407
+ }
1408
+
1409
+ /**
1410
+ * The argv vector: the verified package's own launcher, then the SHARED pi-durable composition, with
1411
+ * the bootstrap payload made Cc-free at ITS OWN layer. `JSON.stringify` escapes C0, `"` and `\` but
1412
+ * leaves DEL and C1 literal; escaping only the outer request would be undone by herdr's own decode,
1413
+ * so the reversible `\uXXXX` escape is applied to the payload element itself. The bootstrap decoder
1414
+ * is `JSON.parse` (meta-bridge-omp.ts `decodeOmpBootstrapPayload`), so the task comes back byte-exact
1415
+ * — asserted here, and a failure is OUR bug, so it throws rather than launch a task we cannot
1416
+ * reproduce. No blanket control-character refusal: this exec path has no Cc rule of its own.
1417
+ */
1418
+ export function composeHerdrPiDurableCommand(params: {
1419
+ readonly runtime: string;
1420
+ readonly prompt: string;
1421
+ readonly model: string;
1422
+ readonly callerGardenId: string;
1423
+ readonly nonce: string;
1424
+ readonly task: string;
1425
+ }): string[] {
1426
+ const args = composeBackendArgs(
1427
+ "pi-durable",
1428
+ {
1429
+ prompt: params.prompt,
1430
+ bootstrapPayload: buildOmpBootstrapPayload({
1431
+ callerGardenId: params.callerGardenId,
1432
+ nonce: params.nonce,
1433
+ task: params.task,
1434
+ }),
1435
+ },
1436
+ params.model,
1437
+ () => {
1438
+ throw new Error("herdr-fresh-call: codex is not a herdr backend");
1439
+ },
1440
+ () => {
1441
+ throw new Error("herdr-fresh-call: codex is not a herdr backend");
1442
+ },
1443
+ );
1444
+ const raw = args.at(-1);
1445
+ if (args.at(-2) !== "--entwurf-bootstrap" || raw === undefined) {
1446
+ throw new Error("herdr-fresh-call: the pi-durable composition no longer ends with its bootstrap payload");
1447
+ }
1448
+ const payload = escapeRemainingControlChars(raw);
1449
+ const decoded: unknown = JSON.parse(payload);
1450
+ const intact =
1451
+ typeof decoded === "object" &&
1452
+ decoded !== null &&
1453
+ (decoded as Record<string, unknown>).v === OMP_BOOTSTRAP_VERSION &&
1454
+ (decoded as Record<string, unknown>).task === params.prompt &&
1455
+ !containsControlChar(payload);
1456
+ if (!intact) {
1457
+ throw new Error(
1458
+ "herdr-fresh-call: the pi-durable bootstrap payload did not decode back to the framed prompt — refusing to launch a sibling with a task we cannot reproduce",
1459
+ );
1460
+ }
1461
+ return [params.runtime, ...args.slice(0, -1), payload];
1462
+ }
1463
+
1464
+ /** `HERDR_SOCKET_PATH`, read only here and only as given: non-empty and absolute, or `null`. */
1465
+ export function resolveHerdrSocketPath(env: NodeJS.ProcessEnv): string | null {
1466
+ const value = env.HERDR_SOCKET_PATH;
1467
+ return typeof value === "string" && value.length > 0 && path.isAbsolute(value) ? value : null;
1468
+ }
1469
+
1470
+ /**
1471
+ * The explicit start directory. herdr's own default for an omitted `root.cwd` is the FOCUSED pane's
1472
+ * cwd in the target workspace (`[blob 2ecf19a9]` app-api-layouts.rs:343-365) — unrelated to the
1473
+ * caller — so this branch always sends one. Requested first; then a known Codex caller's RECORD
1474
+ * cwd, with NO fallback when it has none (its process directory is the app-server's, #95 lane C);
1475
+ * then, for every other caller, this process's own directory — the inherited fact the tmux rail's
1476
+ * omitted cwd rests on. Never herdr's own `cwd`/`foreground_cwd` for the caller's pane: that is a view.
1477
+ * The agent-start branch reuses this same choice for its `tab create --cwd` (#157 A-L1x), so both
1478
+ * herdr branches start an omitted cwd where the caller is; the name stays layout-first only by history.
1479
+ */
1480
+ export function chooseHerdrLayoutCwd(input: {
1481
+ readonly requestedCwd?: string;
1482
+ readonly callerIsCodex: boolean;
1483
+ readonly callerCwd?: string;
1484
+ readonly callerProcessCwd: () => string;
1485
+ }):
1486
+ | { readonly ok: true; readonly cwd: string; readonly source: HerdrLayoutCwdSource }
1487
+ | { readonly ok: false; readonly reason: "codex-caller-cwd-unavailable" } {
1488
+ if (input.requestedCwd !== undefined) return { ok: true, cwd: input.requestedCwd, source: "requested" };
1489
+ if (input.callerIsCodex) {
1490
+ const record = input.callerCwd === undefined || input.callerCwd === "" ? undefined : input.callerCwd;
1491
+ return record === undefined
1492
+ ? { ok: false, reason: "codex-caller-cwd-unavailable" }
1493
+ : { ok: true, cwd: record, source: "codex-caller-record" };
1494
+ }
1495
+ return { ok: true, cwd: input.callerProcessCwd(), source: "caller-process" };
1496
+ }
1497
+
1498
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
1499
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1500
+ }
1501
+
1502
+ function isNonEmptyString(value: unknown): value is string {
1503
+ return typeof value === "string" && value.length > 0;
1504
+ }
1505
+
1506
+ /** Cut for display, never for comparison. A lone high surrogate is not left at the cut. */
1507
+ export function boundHerdrDiagnostic(value: string): string {
1508
+ if (value.length <= HERDR_DIAGNOSTIC_MAX_CHARS) return value;
1509
+ let cut = HERDR_DIAGNOSTIC_MAX_CHARS - 1;
1510
+ const last = value.charCodeAt(cut - 1);
1511
+ if (last >= 0xd800 && last <= 0xdbff) cut -= 1;
1512
+ return `${value.slice(0, cut)}…`;
1513
+ }
1514
+
1515
+ /**
1516
+ * The envelope, before any certainty is decided: a JSON object (not an array, not null), the exact
1517
+ * id we sent, and EXACTLY ONE of `result`/`error`, whose value is itself a non-null, non-array
1518
+ * object. An error arm also needs a non-empty string `code` and a string `message` (both required
1519
+ * by the bundled schema). Anything else is `null`: unreadable, never a partially believed answer.
1520
+ */
1521
+ export function readHerdrEnvelope(
1522
+ line: string,
1523
+ id: string,
1524
+ ):
1525
+ | { readonly arm: "result"; readonly result: Record<string, unknown> }
1526
+ | { readonly arm: "error"; readonly code: string; readonly message: string }
1527
+ | null {
1528
+ let parsed: unknown;
1529
+ try {
1530
+ parsed = JSON.parse(line);
1531
+ } catch {
1532
+ return null;
1533
+ }
1534
+ if (!isPlainObject(parsed)) return null;
1535
+ if (typeof parsed.id !== "string" || parsed.id !== id) return null;
1536
+ const hasResult = Object.hasOwn(parsed, "result");
1537
+ if (hasResult === Object.hasOwn(parsed, "error")) return null;
1538
+ if (hasResult) return isPlainObject(parsed.result) ? { arm: "result", result: parsed.result } : null;
1539
+ const error = parsed.error;
1540
+ if (!isPlainObject(error)) return null;
1541
+ if (!isNonEmptyString(error.code) || typeof error.message !== "string") return null;
1542
+ return { arm: "error", code: error.code, message: error.message };
1543
+ }
1544
+
1545
+ /** The transport cause of a read that got no reply line, cut for display. */
1546
+ function readTransportCause(outcome: Exclude<HerdrSocketOutcome, { readonly kind: "reply" }>): string {
1547
+ return boundHerdrDiagnostic("code" in outcome ? `${outcome.kind}: ${outcome.code}` : outcome.kind);
1548
+ }
1549
+
1550
+ /** The caller-pane read, reduced. Every failure is a REFUSAL: nothing has been mutated yet. A
1551
+ * transport cause or a well-formed herdr error travels with it as bounded, display-only report. */
1552
+ export function classifyHerdrLayoutPaneRead(
1553
+ outcome: HerdrSocketOutcome,
1554
+ id: string,
1555
+ callerPaneId: string,
1556
+ ):
1557
+ | { readonly ok: true; readonly workspaceId: string }
1558
+ | {
1559
+ readonly ok: false;
1560
+ readonly reason:
1561
+ | "herdr-caller-pane-get-failed"
1562
+ | "herdr-caller-pane-unparsable"
1563
+ | "herdr-caller-pane-drift"
1564
+ | "herdr-caller-workspace-missing";
1565
+ readonly reported: HerdrReportedDiagnostics;
1566
+ } {
1567
+ if (outcome.kind !== "reply") {
1568
+ return { ok: false, reason: "herdr-caller-pane-get-failed", reported: { transport: readTransportCause(outcome) } };
1569
+ }
1570
+ const envelope = readHerdrEnvelope(outcome.line, id);
1571
+ if (envelope === null) return { ok: false, reason: "herdr-caller-pane-unparsable", reported: {} };
1572
+ if (envelope.arm === "error") {
1573
+ return {
1574
+ ok: false,
1575
+ reason: "herdr-caller-pane-get-failed",
1576
+ reported: {
1577
+ errorCode: boundHerdrDiagnostic(envelope.code),
1578
+ errorMessage: boundHerdrDiagnostic(envelope.message),
1579
+ },
1580
+ };
1581
+ }
1582
+ if (envelope.result.type !== "pane_info") return { ok: false, reason: "herdr-caller-pane-unparsable", reported: {} };
1583
+ const pane = envelope.result.pane;
1584
+ if (
1585
+ !isPlainObject(pane) ||
1586
+ !isNonEmptyString(pane.pane_id) ||
1587
+ !isNonEmptyString(pane.terminal_id) ||
1588
+ !isNonEmptyString(pane.tab_id) ||
1589
+ typeof pane.focused !== "boolean" ||
1590
+ typeof pane.revision !== "number" ||
1591
+ !Number.isSafeInteger(pane.revision) ||
1592
+ pane.revision < 0 ||
1593
+ !Object.hasOwn(pane, "agent_status")
1594
+ ) {
1595
+ return { ok: false, reason: "herdr-caller-pane-unparsable", reported: {} };
1596
+ }
1597
+ if (pane.pane_id !== callerPaneId) return { ok: false, reason: "herdr-caller-pane-drift", reported: {} };
1598
+ if (!isNonEmptyString(pane.workspace_id)) {
1599
+ return { ok: false, reason: "herdr-caller-workspace-missing", reported: {} };
1600
+ }
1601
+ return { ok: true, workspaceId: pane.workspace_id };
1602
+ }
1603
+
1604
+ /** What a layout-branch failure carries: reason, certainty, and bounded display-only reports. */
1605
+ export interface HerdrLayoutFailure {
1606
+ readonly ok: false;
1607
+ readonly reason: HerdrLayoutFailureReason;
1608
+ readonly herdrErrorCode?: string;
1609
+ readonly recovery: { readonly outcome: "none" } | { readonly outcome: "unknown" };
1610
+ readonly reported: HerdrReportedDiagnostics;
1611
+ }
1612
+
1613
+ function layoutFailure(
1614
+ reason: HerdrLayoutFailureReason,
1615
+ outcome: "none" | "unknown",
1616
+ reported: HerdrReportedDiagnostics,
1617
+ herdrErrorCode?: string,
1618
+ ): HerdrLayoutFailure {
1619
+ return {
1620
+ ok: false,
1621
+ reason,
1622
+ ...(herdrErrorCode === undefined ? {} : { herdrErrorCode }),
1623
+ recovery: { outcome },
1624
+ reported,
1625
+ };
1626
+ }
1627
+
1628
+ /** The coordinates a drifted success reply named — strings only, cut for display. Never a receipt. */
1629
+ function reportedCoordinates(
1630
+ layout: Record<string, unknown> | undefined,
1631
+ root: Record<string, unknown> | undefined,
1632
+ ): HerdrReportedDiagnostics {
1633
+ return {
1634
+ ...(typeof layout?.workspace_id === "string" ? { workspaceId: boundHerdrDiagnostic(layout.workspace_id) } : {}),
1635
+ ...(typeof layout?.tab_id === "string" ? { tabId: boundHerdrDiagnostic(layout.tab_id) } : {}),
1636
+ ...(typeof root?.pane_id === "string" ? { paneId: boundHerdrDiagnostic(root.pane_id) } : {}),
1637
+ };
1638
+ }
1639
+
1640
+ function sameArgv(echoed: unknown, sent: readonly string[]): boolean {
1641
+ return (
1642
+ Array.isArray(echoed) &&
1643
+ echoed.length === sent.length &&
1644
+ echoed.every((token, index) => typeof token === "string" && token === sent[index])
1645
+ );
1646
+ }
1647
+
1648
+ /**
1649
+ * The ONE layout mutation write's outcome. Envelope first (`readHerdrEnvelope`), certainty second:
1650
+ * `none` only when the request was never written or herdr answered a well-formed matching error
1651
+ * with a pre-create code; EVERY other non-success is `unknown` — a tab may exist, nothing is closed
1652
+ * and nothing is resent. A success must prove THIS launch: the arm, the workspace we asked for, a
1653
+ * tab, a single-pane root whose pane is the tab's focus, and the cwd and argv we sent echoed back
1654
+ * (an echo, not an execution witness; schema-nullable `pane_id`/`cwd`/`command` that come back null
1655
+ * cannot prove it, so they are `unknown` too).
1656
+ */
1657
+ export function classifyHerdrLayoutApplyReply(
1658
+ outcome: HerdrSocketOutcome,
1659
+ expect: {
1660
+ readonly id: string;
1661
+ readonly workspaceId: string;
1662
+ readonly cwd: string;
1663
+ readonly command: readonly string[];
1664
+ },
1665
+ ):
1666
+ | { readonly ok: true; readonly workspaceId: string; readonly tabId: string; readonly paneId: string }
1667
+ | HerdrLayoutFailure {
1668
+ switch (outcome.kind) {
1669
+ case "connect-failed":
1670
+ return layoutFailure("herdr-layout-transport-failed", "none", {
1671
+ transport: boundHerdrDiagnostic(`connect-failed: ${outcome.code}`),
1672
+ });
1673
+ case "io-failed":
1674
+ return layoutFailure("herdr-layout-transport-failed", "unknown", {
1675
+ transport: boundHerdrDiagnostic(`io-failed: ${outcome.code}`),
1676
+ });
1677
+ case "timeout":
1678
+ case "oversize":
1679
+ case "eof-before-newline":
1680
+ return layoutFailure("herdr-layout-transport-failed", "unknown", { transport: outcome.kind });
1681
+ default:
1682
+ break;
1683
+ }
1684
+ const envelope = readHerdrEnvelope(outcome.line, expect.id);
1685
+ if (envelope === null) return layoutFailure("herdr-layout-reply-unparsable", "unknown", {});
1686
+ if (envelope.arm === "error") {
1687
+ const reported = {
1688
+ errorCode: boundHerdrDiagnostic(envelope.code),
1689
+ errorMessage: boundHerdrDiagnostic(envelope.message),
1690
+ };
1691
+ return HERDR_LAYOUT_NONE_CODES.includes(envelope.code)
1692
+ ? layoutFailure("herdr-layout-apply-refused", "none", reported, envelope.code)
1693
+ : layoutFailure("herdr-layout-apply-failed", "unknown", reported, envelope.code);
1694
+ }
1695
+ const layout = isPlainObject(envelope.result.layout) ? envelope.result.layout : undefined;
1696
+ const root = isPlainObject(layout?.root) ? layout.root : undefined;
1697
+ if (
1698
+ envelope.result.type !== "layout_apply" ||
1699
+ layout === undefined ||
1700
+ root === undefined ||
1701
+ layout.workspace_id !== expect.workspaceId ||
1702
+ !isNonEmptyString(layout.tab_id) ||
1703
+ typeof layout.zoomed !== "boolean" ||
1704
+ !isNonEmptyString(layout.focused_pane_id) ||
1705
+ root.type !== "pane" ||
1706
+ !isNonEmptyString(root.pane_id) ||
1707
+ layout.focused_pane_id !== root.pane_id ||
1708
+ root.cwd !== expect.cwd ||
1709
+ !sameArgv(root.command, expect.command)
1710
+ ) {
1711
+ return layoutFailure("herdr-layout-reply-drift", "unknown", reportedCoordinates(layout, root));
1712
+ }
1713
+ return { ok: true, workspaceId: expect.workspaceId, tabId: layout.tab_id, paneId: root.pane_id };
1714
+ }
1715
+
1716
+ /** What the layout branch needs from its surroundings, injectable so a gate can drive the whole
1717
+ * branch without a herdr server, a package closure or this process's directory. */
1718
+ export interface HerdrLayoutDeps {
1719
+ readonly socket: HerdrSocketCallFactory;
1720
+ readonly callerProcessCwd: () => string;
1721
+ readonly packageLayout: () => PiDurablePackageLayout | null;
1722
+ readonly runtimeVerdict: (layout: PiDurablePackageLayout) => PiDurableRuntimeRejectReason | null;
1723
+ }
1724
+
1725
+ export const HERDR_LAYOUT_PRODUCTION_DEPS: HerdrLayoutDeps = {
1726
+ socket: (socketPath) => createHerdrSocketCall(socketPath),
1727
+ callerProcessCwd: () => process.cwd(),
1728
+ packageLayout: () => piDurablePackageLayout(),
1729
+ runtimeVerdict: (layout) => {
1730
+ const verdict = checkPiDurableRuntime(layout);
1731
+ return verdict.ok ? null : verdict.reason;
1732
+ },
1733
+ };
1734
+
1735
+ /** A bounded environment probe: is the verified package's own launcher an executable regular file? */
1736
+ function isExecutableRegularFile(file: string): boolean {
1737
+ try {
1738
+ if (!statSync(file).isFile()) return false;
1739
+ accessSync(file, fsConstants.X_OK);
1740
+ return true;
1741
+ } catch {
1742
+ return false;
1743
+ }
1744
+ }
1745
+
1746
+ /**
1747
+ * #157 B3: the verified package's own launcher for a layout command, or `null` when it is missing or
1748
+ * not executable — the same path and probe the fresh layout branch puts first in its argv vector. The
1749
+ * pi-durable same-id resume (`herdr-resume-call.ts`) asks it through this export.
1750
+ */
1751
+ export function resolvePiDurableLauncher(layout: PiDurablePackageLayout): string | null {
1752
+ const runtime = path.join(layout.root, "run.sh");
1753
+ return isExecutableRegularFile(runtime) ? runtime : null;
1754
+ }
1755
+
1756
+ /**
1757
+ * Open a pi-durable sibling in a new herdr tab. ORDER IS THE ARGUMENT: everything decidable without
1758
+ * herdr — inputs, the explicit cwd, the endpoint, the pi-durable preflight (provider split, first
1759
+ * input cap, package, carrier and SDK set, units), the launcher and the argv — is decided before
1760
+ * the first socket request. The caller-pane read comes next and mutates nothing, so any failure of
1761
+ * it refuses with zero layout writes. Then the ONE layout mutation write, never resent, and no
1762
+ * reclaim: herdr's layout reply names no terminal id, so this branch holds no create-receipt
1763
+ * terminal and therefore no authority to close what it opened.
1764
+ */
1765
+ async function herdrLayoutFreshCall(
1766
+ params: {
1767
+ readonly backend: string;
1768
+ readonly model: string;
1769
+ readonly task: string;
1770
+ readonly cwd?: string;
1771
+ readonly callerGardenId: string | null;
1772
+ readonly callerCwd?: string;
1773
+ readonly callerIsCodex?: boolean;
1774
+ },
1775
+ context: HerdrContext,
1776
+ env: NodeJS.ProcessEnv,
1777
+ nonce: string,
1778
+ deps: HerdrLayoutDeps,
1779
+ ): Promise<HerdrFreshCallResult> {
1780
+ const normalized = normalizeFreshCallInputs(params);
1781
+ if (!normalized.ok) return { ok: false, reason: normalized.reason };
1782
+ const { callerGardenId, model, task, cwd: requestedCwd } = normalized.inputs;
1783
+
1784
+ const chosen = chooseHerdrLayoutCwd({
1785
+ ...(requestedCwd === undefined ? {} : { requestedCwd }),
1786
+ callerIsCodex: params.callerIsCodex === true,
1787
+ ...(params.callerCwd === undefined ? {} : { callerCwd: params.callerCwd }),
1788
+ callerProcessCwd: deps.callerProcessCwd,
1789
+ });
1790
+ if (!chosen.ok) return { ok: false, reason: chosen.reason };
1791
+ const badCwd = classifyHerdrCwd(chosen.cwd);
1792
+ if (badCwd !== null) return { ok: false, reason: badCwd };
1793
+
1794
+ const socketPath = resolveHerdrSocketPath(env);
1795
+ if (socketPath === null) return { ok: false, reason: "herdr-socket-path-missing" };
1796
+
1797
+ const prompt = composeFreshCallPrompt({
1798
+ backend: "pi-durable",
1799
+ task,
1800
+ callerGardenId,
1801
+ nonce,
1802
+ openingLine: HERDR_FRESH_CALL_OPENING_LINE,
1803
+ });
1804
+ const packageLayout = deps.packageLayout();
1805
+ const missing = piDurableFreshPreflight({
1806
+ model,
1807
+ firstInput: prompt,
1808
+ layout: packageLayout,
1809
+ runtime: packageLayout === null ? null : deps.runtimeVerdict(packageLayout),
1810
+ });
1811
+ if (missing !== null) return { ok: false, reason: missing };
1812
+ if (packageLayout === null) throw new Error("herdr-fresh-call: the pi-durable preflight passed without a package");
1813
+
1814
+ const runtime = path.join(packageLayout.root, "run.sh");
1815
+ if (!isExecutableRegularFile(runtime)) return { ok: false, reason: "pi-durable-launcher-missing" };
1816
+ const command = composeHerdrPiDurableCommand({ runtime, prompt, model, callerGardenId, nonce, task });
1817
+ const launchEnv = buildHerdrLayoutLaunchEnv({ target: callerGardenId, nonce });
1818
+
1819
+ const call = deps.socket(socketPath);
1820
+ const base = herdrAgentNameFromNonce(nonce);
1821
+ const readId = `${base}:pane.get`;
1822
+ const read = classifyHerdrLayoutPaneRead(
1823
+ await call(buildHerdrPaneGetPacket(readId, context.callerPaneId)),
1824
+ readId,
1825
+ context.callerPaneId,
1826
+ );
1827
+ if (!read.ok) return { ok: false, reason: read.reason, reported: read.reported };
1828
+
1829
+ // ── everything above this line leaves nothing behind; the ONE layout mutation write follows ──
1830
+ const writeId = `${base}:layout.apply`;
1831
+ const applied = classifyHerdrLayoutApplyReply(
1832
+ await call(
1833
+ buildHerdrLayoutApplyPacket(writeId, {
1834
+ workspaceId: read.workspaceId,
1835
+ cwd: chosen.cwd,
1836
+ command,
1837
+ env: launchEnv,
1838
+ }),
1839
+ ),
1840
+ { id: writeId, workspaceId: read.workspaceId, cwd: chosen.cwd, command },
1841
+ );
1842
+ if (!applied.ok) return { ...applied, ...(applied.recovery.outcome === "unknown" ? { nonce } : {}) };
1843
+ return {
1844
+ ok: true,
1845
+ receipt: {
1846
+ backend: "pi-durable",
1847
+ launch: "layout-apply",
1848
+ model,
1849
+ cwd: chosen.cwd,
1850
+ cwdSource: chosen.source,
1851
+ runtime,
1852
+ herdrWorkspaceId: applied.workspaceId,
1853
+ herdrTabId: applied.tabId,
1854
+ herdrPaneId: applied.paneId,
1855
+ nonce,
1856
+ },
1857
+ };
1858
+ }
1859
+
1101
1860
  /** Why each refusal happened, in the caller's terms. A reason a caller cannot act on is a reason
1102
1861
  * they will guess about. The five shared input reasons keep the wording the public verb has always
1103
1862
  * used; the rest are this rail's own. */
1104
- const HERDR_REJECT_HINT: Record<HerdrFreshCallRejectReason | HerdrLaunchFailureReason, string> = {
1863
+ export const HERDR_REJECT_HINT: Record<
1864
+ HerdrFreshCallRejectReason | HerdrLaunchFailureReason | HerdrLayoutFailureReason,
1865
+ string
1866
+ > = {
1105
1867
  // #76: decided at the dispatch root, worded once in the shared composition leaf.
1106
1868
  ...FRESH_CALL_PROVIDER_HINT,
1869
+ // #157 A-L1: the pi-durable preflight's repair text stays beside its predicates, as on tmux.
1870
+ ...PI_DURABLE_PREFLIGHT_HINT,
1871
+ ...PI_DURABLE_RUNTIME_HINT,
1107
1872
  "caller-identity-unavailable":
1108
1873
  "this surface has no record-backed identity, so a sibling would have nowhere to call home",
1109
1874
  "model-empty": "pass the model you want the sibling opened with — it is never inherited from this process",
@@ -1119,13 +1884,29 @@ const HERDR_REJECT_HINT: Record<HerdrFreshCallRejectReason | HerdrLaunchFailureR
1119
1884
  "herdr-caller-pane-missing":
1120
1885
  "herdr did not give this process a HERDR_PANE_ID, so there is no way to ask herdr which workspace this caller is in",
1121
1886
  "herdr-caller-pane-get-failed":
1122
- "herdr would not report this process's own pane — run `herdr pane get $HERDR_PANE_ID` to see what it says",
1887
+ "herdr would not report this process's own pane — for pi and claude-code run `herdr pane get $HERDR_PANE_ID` to see what it says; pi-durable asked through one `pane.get` on HERDR_SOCKET_PATH instead, and any cause herdr reported is quoted below",
1123
1888
  "herdr-caller-pane-unparsable": "herdr's reply about this process's own pane could not be read",
1124
1889
  "herdr-caller-pane-drift":
1125
1890
  "herdr answered about a different pane than HERDR_PANE_ID names, so its workspace says nothing about where this caller is",
1126
1891
  "herdr-caller-workspace-missing":
1127
1892
  "herdr reported this process's pane with no workspace, and guessing one would open the sibling wherever the operator is looking",
1128
- "herdr-backend-unsupported": "this rail opens pi and claude-code only; nothing is opened elsewhere instead",
1893
+ "herdr-backend-unsupported":
1894
+ "this rail opens pi and claude-code through herdr agent start, and pi-durable through one layout.apply tab; nothing is opened elsewhere instead",
1895
+ "herdr-socket-path-missing":
1896
+ "HERDR_SOCKET_PATH is absent, empty or not absolute, so there is no herdr endpoint for pi-durable's one layout request — this rail does not guess or scan for one",
1897
+ "pi-durable-launcher-missing":
1898
+ "this Entwurf package's own run.sh is missing or not executable, so the verified package has no launcher to put in the layout command — reinstall Entwurf",
1899
+ "codex-caller-cwd-unavailable":
1900
+ "this Codex caller's record carries no directory, and its process directory is the app-server's, not its own — pass cwd explicitly",
1901
+ "herdr-layout-transport-failed":
1902
+ "the one exchange with herdr's socket did not complete — read the recovery line for whether the request can have reached herdr",
1903
+ "herdr-layout-apply-refused":
1904
+ "herdr declined the layout request with a code its handler returns before creating anything",
1905
+ "herdr-layout-apply-failed":
1906
+ "herdr answered the layout request with an error that does not prove nothing was created",
1907
+ "herdr-layout-reply-unparsable": "herdr's reply was not one well-formed answer to this exact request",
1908
+ "herdr-layout-reply-drift":
1909
+ "herdr's success reply does not describe the tab this request asked for, so its coordinates were not accepted",
1129
1910
  "herdr-placement-tmux-rejected":
1130
1911
  "`placement` names a tmux session, which does not exist inside herdr — drop it rather than have the sibling silently placed somewhere else",
1131
1912
  "herdr-argv-control-character":
@@ -1194,6 +1975,96 @@ function renderFailureHeader(recovery: HerdrRecovery): string {
1194
1975
  }
1195
1976
  }
1196
1977
 
1978
+ const HERDR_LAYOUT_FAILURE_REASONS: ReadonlySet<string> = new Set<HerdrLayoutFailureReason>([
1979
+ "herdr-layout-transport-failed",
1980
+ "herdr-layout-apply-refused",
1981
+ "herdr-layout-apply-failed",
1982
+ "herdr-layout-reply-unparsable",
1983
+ "herdr-layout-reply-drift",
1984
+ ]);
1985
+
1986
+ /** The reported diagnostics, each already cut, rendered QUOTED so a value cannot pose as prose. */
1987
+ function renderReported(reported: HerdrReportedDiagnostics | undefined): string {
1988
+ if (reported === undefined) return "";
1989
+ const all: ReadonlyArray<readonly [string, string | undefined]> = [
1990
+ ["transport", reported.transport],
1991
+ ["code", reported.errorCode],
1992
+ ["message", reported.errorMessage],
1993
+ ["workspace", reported.workspaceId],
1994
+ ["tab", reported.tabId],
1995
+ ["pane", reported.paneId],
1996
+ ];
1997
+ const fields = all.filter((field): field is readonly [string, string] => field[1] !== undefined);
1998
+ if (fields.length === 0) return "";
1999
+ return ` reported (display only, unverified, each value cut at ${HERDR_DIAGNOSTIC_MAX_CHARS} chars — not a receipt, a placement or anything to close): ${fields.map(([name, value]) => `${name} ${JSON.stringify(value)}`).join(", ")}\n`;
2000
+ }
2001
+
2002
+ /** A layout-branch failure. The certainty line says only what the outcome licenses: `none` never
2003
+ * says a sibling may exist, and only a POST-WRITE `unknown` mentions a possible callback. */
2004
+ function renderLayoutFailure(
2005
+ result: {
2006
+ readonly reason: HerdrLaunchFailureReason | HerdrLayoutFailureReason;
2007
+ readonly herdrErrorCode?: string;
2008
+ readonly recovery: HerdrRecovery;
2009
+ readonly reported?: HerdrReportedDiagnostics;
2010
+ readonly nonce?: string;
2011
+ },
2012
+ hint: string,
2013
+ ): string {
2014
+ const code =
2015
+ result.herdrErrorCode === undefined
2016
+ ? ""
2017
+ : ` [herdr: ${JSON.stringify(boundHerdrDiagnostic(result.herdrErrorCode))}]`;
2018
+ if (result.recovery.outcome === "none") {
2019
+ const why =
2020
+ result.reason === "herdr-layout-transport-failed"
2021
+ ? "the request was never written to herdr"
2022
+ : "herdr declined by name before creating anything";
2023
+ return (
2024
+ `entwurf_fresh_call failed at the herdr layout.apply step: ${result.reason}${code} — ${hint}.\n` +
2025
+ ` recovery: none — ${why}, so nothing was created\n` +
2026
+ renderReported(result.reported)
2027
+ );
2028
+ }
2029
+ const nonce = result.nonce === undefined ? "this call's launch nonce" : `nonce ${result.nonce}`;
2030
+ return (
2031
+ `entwurf_fresh_call failed at the herdr layout.apply step with an UNKNOWN outcome: ${result.reason}${code} — ${hint}.\n` +
2032
+ ` recovery: UNKNOWN — the layout write was attempted and may have reached herdr, so a pi-durable tab MAY exist and its sibling may still call back under ${nonce}; that callback's sender envelope is the only address. Nothing was closed and nothing will be retried — check herdr\n` +
2033
+ renderReported(result.reported)
2034
+ );
2035
+ }
2036
+
2037
+ const HERDR_LAYOUT_CWD_SOURCE_TEXT: Record<HerdrLayoutCwdSource, string> = {
2038
+ requested: "the requested start directory",
2039
+ "codex-caller-record": "this Codex caller's own record directory",
2040
+ "caller-process": "this agent's own process directory",
2041
+ };
2042
+
2043
+ function renderLayoutReceipt(r: HerdrLayoutFreshCallReceipt): string {
2044
+ return (
2045
+ `[entwurf fresh call → herdr]\n` +
2046
+ ` backend: ${r.backend} (ONE layout.apply request — herdr has no agent kind for it, and none was requested)\n` +
2047
+ ` model: ${r.model} (requested on the runtime CLI)\n` +
2048
+ ` cwd: ${r.cwd} (${HERDR_LAYOUT_CWD_SOURCE_TEXT[r.cwdSource]}, sent explicitly — not an observation)\n` +
2049
+ ` runtime: ${r.runtime} (this package's verified launcher, the first element of the requested argv)\n` +
2050
+ ` tab: ${r.herdrTabId} in workspace ${r.herdrWorkspaceId} — a NEW tab beside the caller's, REQUESTED unfocused; whether it is actually unfocused is not observed here\n` +
2051
+ ` pane: ${r.herdrPaneId} — the tab's root pane; herdr VIEW coordinates, not an address (herdr's layout reply names no terminal)\n` +
2052
+ ` nonce: ${r.nonce}\n` +
2053
+ ` detection: none requested — pi-durable has no herdr agent integration, so herdr is not told its session and its peers placement column reads unobserved\n` +
2054
+ `\n` +
2055
+ `This is a LAUNCH receipt: herdr accepted one layout request and named the tab above. It does NOT ` +
2056
+ `mean the sibling is running, that its first turn ran, or that the task was delivered. The tab and pane ` +
2057
+ `coordinates are a view and can change under the sibling — they are not an address and nothing may be ` +
2058
+ `dispatched to them.\n` +
2059
+ `The sibling's garden id arrives separately — before its task it calls the zero-argument callback verb ` +
2060
+ `(a read-only look at the caller may come first), and ` +
2061
+ `the sender envelope of THAT message is the address. The nonce above is the correlation tag it carries; ` +
2062
+ `the sibling reads it from its own launch env, never from this receipt. Nothing is polling for it; if it ` +
2063
+ `never comes — not started, stuck, or held back after its check; this receipt cannot tell which — the ` +
2064
+ `pane is visible and can be read directly.`
2065
+ );
2066
+ }
2067
+
1197
2068
  /**
1198
2069
  * Three outcomes, and an operator must be able to tell them apart at a glance: a refusal that
1199
2070
  * created nothing, a failure that may have left a tab behind, and a launch.
@@ -1203,10 +2074,17 @@ export function renderHerdrFreshCall(result: HerdrFreshCallResult): { text: stri
1203
2074
  const hint = HERDR_REJECT_HINT[result.reason];
1204
2075
  if (!("recovery" in result)) {
1205
2076
  return {
1206
- text: `entwurf_fresh_call rejected: ${result.reason} — ${hint}. No tab and no pane were created.`,
2077
+ text:
2078
+ `entwurf_fresh_call rejected: ${result.reason} — ${hint}. No tab and no pane were created.` +
2079
+ ("reported" in result && renderReported(result.reported) !== ""
2080
+ ? `\n${renderReported(result.reported).trimEnd()}`
2081
+ : ""),
1207
2082
  isError: true,
1208
2083
  };
1209
2084
  }
2085
+ if (HERDR_LAYOUT_FAILURE_REASONS.has(result.reason)) {
2086
+ return { text: renderLayoutFailure(result, hint), isError: true };
2087
+ }
1210
2088
  const code = result.herdrErrorCode === undefined ? "" : ` [herdr: ${result.herdrErrorCode}]`;
1211
2089
  return {
1212
2090
  text:
@@ -1215,13 +2093,14 @@ export function renderHerdrFreshCall(result: HerdrFreshCallResult): { text: stri
1215
2093
  isError: true,
1216
2094
  };
1217
2095
  }
2096
+ if (result.receipt.backend === "pi-durable") return { text: renderLayoutReceipt(result.receipt), isError: false };
1218
2097
  const r = result.receipt;
1219
2098
  return {
1220
2099
  text:
1221
2100
  `[entwurf fresh call → herdr]\n` +
1222
2101
  ` backend: ${r.backend} (requested kind ${r.requestedKind} — herdr resolves the executable, we did not)\n` +
1223
2102
  ` model: ${r.model} (requested on the runtime CLI)\n` +
1224
- (r.cwd === undefined ? "" : ` cwd: ${r.cwd} (requested start directory — not an observation)\n`) +
2103
+ ` cwd: ${r.cwd} (${HERDR_LAYOUT_CWD_SOURCE_TEXT[r.cwdSource]}, sent explicitly — not an observation)\n` +
1225
2104
  ` agent: ${r.herdrAgentName} (herdr's name for it, derived from the nonce)\n` +
1226
2105
  ` tab: ${r.herdrTabId} in workspace ${r.herdrWorkspaceId} — a NEW tab beside the caller's, opened without taking focus\n` +
1227
2106
  ` pane: ${r.herdrPaneId} terminal ${r.herdrTerminalId} — the tab's initial pane; herdr VIEW coordinates, not an address\n` +