@junghanacs/entwurf 0.21.0 → 0.22.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 (45) hide show
  1. package/AGENTS.md +2 -2
  2. package/BASELINE.md +3 -1
  3. package/CHANGELOG.md +278 -0
  4. package/DELIVERY.md +156 -26
  5. package/README.md +64 -13
  6. package/VERIFY.md +67 -11
  7. package/docs/external-mcp-host.md +16 -6
  8. package/docs/setup-clean-host.md +63 -22
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +40 -16
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +194 -1
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +160 -25
  15. package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
  16. package/mcp/entwurf-bridge/src/index.ts +50 -16
  17. package/mcp/entwurf-bridge/tsconfig.build.json +9 -0
  18. package/package.json +2 -2
  19. package/pi-extensions/entwurf-control.ts +4 -4
  20. package/pi-extensions/lib/codex-caller-seat.ts +204 -0
  21. package/pi-extensions/lib/codex-fresh-preflight.ts +218 -1
  22. package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
  23. package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
  24. package/pi-extensions/lib/mux-fresh-call.ts +196 -37
  25. package/run.sh +127 -3
  26. package/scripts/check-codex-app-server-launch.ts +445 -0
  27. package/scripts/check-entwurf-v2-production.ts +42 -1
  28. package/scripts/check-entwurf-v2-send.ts +26 -7
  29. package/scripts/check-gate-qualification.ts +4 -2
  30. package/scripts/check-mux-launch-tmux.ts +331 -35
  31. package/scripts/codex-app-server-launch.sh +275 -0
  32. package/scripts/codex-socket-path.ts +33 -0
  33. package/scripts/codex-terminal-title-config.py +500 -0
  34. package/scripts/codex_toml_io.py +121 -0
  35. package/scripts/lib/codex-fresh-live-protocol.ts +11 -3
  36. package/scripts/lib/codex-fresh-source-receipts.ts +29 -2
  37. package/scripts/mutants/codex-app-server-launch.json +157 -0
  38. package/scripts/mutants/codex-caller-seat.json +336 -0
  39. package/scripts/mutants/codex-native.json +3 -3
  40. package/scripts/mutants/mux-fresh-call.json +86 -14
  41. package/scripts/mutants/v2-surface.json +22 -0
  42. package/scripts/smoke-codex-config-state.sh +192 -3
  43. package/scripts/smoke-codex-fresh-live.ts +277 -37
  44. package/scripts/smoke-entwurf-chain-live.ts +50 -0
  45. package/scripts/smoke-setup-verdict.sh +13 -11
@@ -27,7 +27,16 @@
27
27
  * - the #105 seat, through the real `freshCall`: an absent seat refuses and creates nothing,
28
28
  * an existing one puts the window in THAT session with a receipt naming the resolved
29
29
  * target, and the resulting handle closes through `closeWindow` from outside that session
30
- * - #95's omitted Codex seat resolves exact existing `codex`, while omitted Pi stays caller-local
30
+ * - an omitted seat stays caller-local for EVERY backend (#95 D1 retired the Codex home)
31
+ * - #95 lane B's caller-seat leaf against REAL pane titles: 0 / 1 / 2 matching panes, and the
32
+ * activity-adjacent title a ` | `-segment rule would miss
33
+ * - #95 lane B's COMPOSITION: a Codex caller's omitted-placement fresh call lands in the
34
+ * session its own titled pane is in, an explicit seat still overrides it, and an
35
+ * unresolvable anchor refuses with the server byte-identical
36
+ * - #95 lane C: WHERE a sibling starts — a caller's record directory places the pane, an
37
+ * explicit request still wins, an omitted one inherits THIS process's directory rather
38
+ * than the target session's `session_path`, and codex carries that same directory to its
39
+ * thread as `-C`, read off the runtime's own recorded argv
31
40
  */
32
41
 
33
42
  import assert from "node:assert/strict";
@@ -35,7 +44,7 @@ import { spawnSync } from "node:child_process";
35
44
  import fs from "node:fs";
36
45
  import path from "node:path";
37
46
  import { buildLaunchArgs, LaunchPreconditionError, launchPi } from "../pi-extensions/lib/mux-launch.ts";
38
- import { inspectPlacement } from "../pi-extensions/lib/mux-placement.ts";
47
+ import { inspectPlacement, runTmux } from "../pi-extensions/lib/mux-placement.ts";
39
48
  import { skipLive } from "./lib/live-skip.ts";
40
49
 
41
50
  const LABEL = "check-mux-launch-tmux";
@@ -119,9 +128,14 @@ async function main(): Promise<void> {
119
128
  const runtimeDir = fs.mkdtempSync(path.join(process.env.XDG_RUNTIME_DIR ?? "/tmp", "entwurf-mux-rt-"));
120
129
  // A long-lived stand-in for the official runtime. `exec` so no shell survives: the pane's
121
130
  // process must be the runtime itself for the pane_pid claim to mean anything.
131
+ // Each stand-in also DUMPS its own argv beside itself before it sleeps. That file is the
132
+ // only oracle in this repo for "what actually reached the runtime", independent of the
133
+ // builder that composed it — #95 lane C needs it because codex's thread directory rides a
134
+ // VENDOR flag whose absence is silent (the thread simply opens in the app-server's repo).
122
135
  const runtime = path.join(runtimeDir, "pi");
123
- fs.writeFileSync(runtime, "#!/bin/sh\nexec sleep 900\n", { mode: 0o755 });
124
- fs.writeFileSync(path.join(runtimeDir, "codex"), "#!/bin/sh\nexec sleep 900\n", { mode: 0o755 });
136
+ const DUMP_ARGV = '#!/bin/sh\nprintf \'%s\\n\' "$@" > "$0.argv"\nexec sleep 900\n';
137
+ fs.writeFileSync(runtime, DUMP_ARGV, { mode: 0o755 });
138
+ fs.writeFileSync(path.join(runtimeDir, "codex"), DUMP_ARGV, { mode: 0o755 });
125
139
 
126
140
  try {
127
141
  assert.equal(fx("-f", "/dev/null", "new-session", "-d", "-s", SESSION).status, 0, "fixture new-session");
@@ -373,11 +387,16 @@ async function main(): Promise<void> {
373
387
  !fxLines("list-windows", "-a", "-F", "#{window_id}").includes(receipt.windowId),
374
388
  );
375
389
 
376
- // (iv) Codex's omitted seat is a product default, not fixture prose. Missing home
377
- // refuses before mutation; then a private session named exactly `codex` makes the real
378
- // composition resolve that name while the Pi omitted-seat control stays caller-local.
379
- const beforeMissingHome = inventory();
380
- const missingHome = freshCall(
390
+ // (iv) #95 D1 (GLG, 2026-09-16): an omitted seat is caller-local for EVERY backend,
391
+ // Codex included. Until then an omitted-placement Codex selected a fixed existing
392
+ // session named `codex` a room the operator had to keep because nothing could find
393
+ // the pane a Codex caller was sitting in. The caller-pane anchor below IS that
394
+ // mapping, so this cell now asserts the ABSENCE of the old default: with a session
395
+ // literally named `codex` present on this server, an omitted-placement Codex call
396
+ // must still land in the caller's own session and name no seat at all.
397
+ assert.equal(fx("new-session", "-d", "-s", "codex").status, 0, "fixture session named codex");
398
+ const decoyHomeId = fxLines("list-windows", "-t", "=codex", "-F", "#{session_id}")[0];
399
+ const omittedCodex = freshCall(
381
400
  {
382
401
  backend: "codex",
383
402
  model: "fixture-model",
@@ -386,37 +405,314 @@ async function main(): Promise<void> {
386
405
  },
387
406
  inherited,
388
407
  );
389
- ok(
390
- "codex home: omitted placement refuses a missing exact home without mutation",
391
- !missingHome.ok && missingHome.reason === "tmux-session-missing" && inventory() === beforeMissingHome,
392
- );
393
- assert.equal(fx("new-session", "-d", "-s", "codex").status, 0, "fixture Codex home session");
394
- const codexHomeId = fxLines("list-windows", "-t", "=codex", "-F", "#{session_id}")[0];
395
- const home = freshCall(
396
- {
397
- backend: "codex",
398
- model: "fixture-model",
399
- task: "fixture task",
400
- callerGardenId: "20260101T000000-fixture",
401
- },
402
- inherited,
403
- );
404
- assert.ok(home.ok, `the omitted-placement Codex call must succeed: ${home.ok ? "" : home.reason}`);
405
- ok(
406
- "codex home: omitted placement lands in exact existing `codex`, not the caller session",
407
- home.receipt.sessionId === codexHomeId &&
408
- home.receipt.sessionId !== placement.sessionId &&
409
- home.receipt.tmuxSession === "codex" &&
410
- home.receipt.tmuxSessionSource === "codex-home",
408
+ assert.ok(
409
+ omittedCodex.ok,
410
+ `the omitted-placement Codex call must succeed: ${omittedCodex.ok ? "" : omittedCodex.reason}`,
411
411
  );
412
412
  ok(
413
- "codex home: the home receipt closes by stable handle",
414
- closeWindow(home.receipt, inherited) === "closed" &&
415
- !fxLines("list-windows", "-a", "-F", "#{window_id}").includes(home.receipt.windowId),
413
+ "no fixed home: an omitted Codex seat is caller-local and names NO seat, even with a session called `codex` right there",
414
+ omittedCodex.receipt.sessionId === placement.sessionId &&
415
+ omittedCodex.receipt.sessionId !== decoyHomeId &&
416
+ omittedCodex.receipt.tmuxSession === undefined &&
417
+ omittedCodex.receipt.tmuxSessionSource === undefined &&
418
+ fxLines("list-windows", "-t", decoyHomeId, "-F", "#{window_id}").length === 1,
416
419
  );
420
+ process.kill(Number(omittedCodex.receipt.panePid), "SIGKILL");
417
421
  fx("kill-session", "-t", seatId);
418
- fx("kill-session", "-t", codexHomeId);
422
+ fx("kill-session", "-t", decoyHomeId);
419
423
  ok("seat: the fixture is back to one session", sessionCount() === 1);
424
+
425
+ // (v) #95 lane B: the caller-seat leaf, against REAL pane titles on this server.
426
+ // The deterministic gate can pin the anchor and the counting, but "what tmux
427
+ // actually reports as `#{pane_title}`, and which `$session` that pane is in" had
428
+ // no oracle independent of the leaf's own parse. This cell is that oracle. The
429
+ // titles are set with `select-pane -T` rather than by a Codex process: the claim
430
+ // under test is the LOOKUP, and a real TUI would add a model turn, a record and a
431
+ // vendor version to a gate that must stay hermetic.
432
+ {
433
+ const { resolveCodexCallerSeat } = await import("../pi-extensions/lib/codex-caller-seat.ts");
434
+ const thread = "01a0a7f9-ed9c-7aa2-a4dd-b1a39c0d4e11";
435
+ const anchor = `${thread.slice(0, 29)}...`;
436
+ const seatRun = (args: string[]) => runTmux(args, inherited);
437
+ const titleOf = (pane: string): string =>
438
+ fxLines("list-panes", "-a", "-F", "#{pane_id}|#{pane_title}").find((row) => row.startsWith(`${pane}|`)) ?? "";
439
+
440
+ // 0 matches: every pane on this server carries the default title, which is the
441
+ // same shape a server with `allow-set-title off` reports for a live Codex.
442
+ ok(
443
+ "caller seat: no pane naming the thread refuses as unresolved — no fallback session",
444
+ (() => {
445
+ const r = resolveCodexCallerSeat(thread, seatRun);
446
+ return !r.ok && r.reason === "codex-caller-seat-unresolved";
447
+ })(),
448
+ );
449
+
450
+ // 1 match, in a session that is NOT the caller's: the leaf must report THAT
451
+ // session, which is the whole point of the anchor.
452
+ assert.equal(fx("new-session", "-d", "-s", `${SESSION}-tui`).status, 0, "fixture TUI session");
453
+ const tuiSessionId = fxLines("list-windows", "-t", `=${SESSION}-tui`, "-F", "#{session_id}")[0];
454
+ const tuiPane = fxLines("list-panes", "-t", `=${SESSION}-tui`, "-F", "#{pane_id}")[0];
455
+ assert.equal(fx("select-pane", "-t", tuiPane, "-T", `entwurf | ${anchor}`).status, 0, "fixture TUI title");
456
+ ok(
457
+ "caller seat: tmux really reports the OSC-shaped title we are matching against",
458
+ titleOf(tuiPane) === `${tuiPane}|entwurf | ${anchor}`,
459
+ );
460
+ ok(
461
+ "caller seat: the one matching pane resolves to ITS session, not the caller's",
462
+ (() => {
463
+ const r = resolveCodexCallerSeat(thread, seatRun);
464
+ return (
465
+ r.ok &&
466
+ r.seat.paneId === tuiPane &&
467
+ r.seat.sessionId === tuiSessionId &&
468
+ r.seat.sessionId !== placement.sessionId &&
469
+ r.seat.source === "codex-title-anchor"
470
+ );
471
+ })(),
472
+ );
473
+
474
+ // The ACTIVITY-adjacent shape. `title_setup.rs:183-193` joins the activity item
475
+ // to its neighbour with a plain space, so an operator list ending in `activity`
476
+ // renders the thread id INSIDE one ` | ` segment. A segment-equality rule reads
477
+ // this pane as no match at all; the token rule resolves it.
478
+ assert.equal(fx("select-pane", "-t", tuiPane, "-T", `entwurf | Working ${anchor}`).status, 0, "activity title");
479
+ ok(
480
+ "caller seat: an activity-adjacent title still resolves — the id is a TOKEN, not a whole segment",
481
+ (() => {
482
+ const r = resolveCodexCallerSeat(thread, seatRun);
483
+ return r.ok && r.seat.paneId === tuiPane && r.seat.sessionId === tuiSessionId;
484
+ })(),
485
+ );
486
+
487
+ // 2 matches: a stale duplicate in another session. Picking either would seat a
488
+ // sibling by guess, so nothing is chosen.
489
+ assert.equal(fx("split-window", "-d", "-t", tuiPane).status, 0, "fixture duplicate pane");
490
+ const duplicate = fxLines("list-panes", "-t", `=${SESSION}-tui`, "-F", "#{pane_id}").find(
491
+ (id) => id !== tuiPane,
492
+ );
493
+ assert.ok(duplicate, "the fixture duplicate pane must exist");
494
+ assert.equal(fx("select-pane", "-t", duplicate, "-T", `tmp | ${anchor}`).status, 0, "duplicate title");
495
+ ok(
496
+ "caller seat: two panes naming one thread refuse as ambiguous — never the first match",
497
+ (() => {
498
+ const r = resolveCodexCallerSeat(thread, seatRun);
499
+ return !r.ok && r.reason === "codex-caller-seat-ambiguous";
500
+ })(),
501
+ );
502
+
503
+ // ── the COMPOSITION, through the real `freshCall` (#95 lane B C3) ─────────
504
+ // The leaf's decision is proven above; what has no oracle outside production is
505
+ // whether the omitted-placement path for a CODEX CALLER actually reaches that
506
+ // decision and turns it into the `-t` target. These cells run the real
507
+ // composition with the same hermetic runtime the rest of this gate uses, and
508
+ // read the answer from the server's own inventory rather than the receipt alone.
509
+ // Back to EXACTLY one matching pane: the duplicate above must stop naming the
510
+ // thread, or the composition below would (correctly) refuse as ambiguous.
511
+ assert.equal(fx("select-pane", "-t", duplicate, "-T", "plain-shell").status, 0, "clear duplicate title");
512
+ assert.equal(fx("select-pane", "-t", tuiPane, "-T", `entwurf | ${anchor}`).status, 0, "restore TUI title");
513
+ const anchoredCall = (over?: { tmuxSession: string }) =>
514
+ freshCall(
515
+ {
516
+ backend: "pi",
517
+ model: "fixture/model",
518
+ task: "fixture task",
519
+ placement: over,
520
+ callerGardenId: "20260101T000000-fixture",
521
+ callerNativeSessionId: thread,
522
+ },
523
+ inherited,
524
+ );
525
+
526
+ const anchored = anchoredCall();
527
+ assert.ok(anchored.ok, `the anchored fresh call must succeed: ${anchored.ok ? "" : anchored.reason}`);
528
+ ok(
529
+ "caller seat composition: the window lands in the session the CALLER's pane is in, not the caller session",
530
+ anchored.receipt.sessionId === tuiSessionId &&
531
+ anchored.receipt.sessionId !== placement.sessionId &&
532
+ fxLines("list-windows", "-t", tuiSessionId, "-F", "#{window_id}").includes(anchored.receipt.windowId),
533
+ );
534
+ ok(
535
+ "caller seat composition: the receipt names the SOURCE and no session name — the pane was observed, not requested",
536
+ anchored.receipt.tmuxSessionSource === "codex-title-anchor" && anchored.receipt.tmuxSession === undefined,
537
+ );
538
+ ok(
539
+ "caller seat composition: that window closes by its own handle",
540
+ closeWindow(anchored.receipt, inherited) === "closed" &&
541
+ !fxLines("list-windows", "-a", "-F", "#{window_id}").includes(anchored.receipt.windowId),
542
+ );
543
+
544
+ // Rule 1 over rule 2, against a real server: an explicit seat is never rewritten
545
+ // by the anchor, even when the anchor would have resolved.
546
+ assert.equal(fx("new-session", "-d", "-s", `${SESSION}-over`).status, 0, "fixture override session");
547
+ const overrideId = fxLines("list-windows", "-t", `=${SESSION}-over`, "-F", "#{session_id}")[0];
548
+ const overridden = anchoredCall({ tmuxSession: `${SESSION}-over` });
549
+ assert.ok(overridden.ok, `the overridden fresh call must succeed: ${overridden.ok ? "" : overridden.reason}`);
550
+ ok(
551
+ "caller seat composition: an explicit seat still wins over a resolvable anchor",
552
+ overridden.receipt.sessionId === overrideId &&
553
+ overridden.receipt.sessionId !== tuiSessionId &&
554
+ overridden.receipt.tmuxSession === `${SESSION}-over` &&
555
+ overridden.receipt.tmuxSessionSource === "requested",
556
+ );
557
+ closeWindow(overridden.receipt, inherited);
558
+ fx("kill-session", "-t", overrideId);
559
+
560
+ // An unresolvable anchor is a refusal with NOTHING created anywhere — the D2
561
+ // contract, read from the server rather than from the return value alone.
562
+ const beforeUnresolved = inventory();
563
+ const unresolved = freshCall(
564
+ {
565
+ backend: "pi",
566
+ model: "fixture/model",
567
+ task: "fixture task",
568
+ callerGardenId: "20260101T000000-fixture",
569
+ callerNativeSessionId: "01a0ffff-ffff-7fff-bfff-ffffffffffff",
570
+ },
571
+ inherited,
572
+ );
573
+ ok(
574
+ "caller seat composition: an unresolvable anchor refuses and creates NOTHING — no window, no session, no fallback",
575
+ !unresolved.ok && unresolved.reason === "codex-caller-seat-unresolved" && inventory() === beforeUnresolved,
576
+ );
577
+
578
+ fx("kill-session", "-t", tuiSessionId);
579
+ ok("caller seat: the fixture is back to one session", sessionCount() === 1);
580
+ }
581
+
582
+ // (vi) #95 lane C: WHERE the sibling starts, read off the pane and off the runtime's
583
+ // own argv. The deterministic gate can pin which value the composition selects; what
584
+ // only tmux and the runtime can answer is whether that value actually placed the
585
+ // pane, and whether the vendor token carrying it survived to the process. A codex
586
+ // thread's directory is the sharper half: with no `-C` the TUI attaches to the
587
+ // operator's app-server and the THREAD opens in the app-server's repo while the pane
588
+ // sits somewhere else entirely — a divergence no receipt in this repo would show.
589
+ {
590
+ const callerRepo = path.join(runtimeDir, "caller-repo");
591
+ const requestedRepo = path.join(runtimeDir, "requested-repo");
592
+ const sessionRepo = path.join(runtimeDir, "session-repo");
593
+ for (const dir of [callerRepo, requestedRepo, sessionRepo]) fs.mkdirSync(dir);
594
+ const paneCwd = (paneId: string): string =>
595
+ fxLines("display-message", "-p", "-t", paneId, "#{pane_current_path}")[0];
596
+ const argvAt = (backend: "pi" | "codex"): string => `${path.join(runtimeDir, backend)}.argv`;
597
+ /** `[측정]` a pane's `#{pane_current_path}` is read from the child's `/proc` at
598
+ * QUERY time, and tmux chdirs in that child AFTER forking it — so a read taken
599
+ * straight after the launch receipt can still answer the SERVER's directory and
600
+ * make a correct `-c` look ignored. The runtime's own argv dump is the settle
601
+ * signal: once it exists the fixture is running and has been chdir'ed. Bounded,
602
+ * never unbounded — a runtime that never starts is a failed assertion, not a hang.
603
+ * Each launch clears the previous file first, so a stale dump can never be read
604
+ * as this launch's. */
605
+ const clearArgv = (backend: "pi" | "codex"): void => fs.rmSync(argvAt(backend), { force: true });
606
+ const awaitRuntime = (backend: "pi" | "codex"): void => {
607
+ const end = Date.now() + 5000;
608
+ while (!fs.existsSync(argvAt(backend)) && Date.now() < end) spawnSync("sleep", ["0.05"]);
609
+ assert.ok(fs.existsSync(argvAt(backend)), `the ${backend} fixture started and recorded its argv`);
610
+ };
611
+ const runtimeArgv = (backend: "pi" | "codex"): string[] => {
612
+ awaitRuntime(backend);
613
+ const argv = fs.readFileSync(argvAt(backend), "utf8").split("\n").slice(0, -1);
614
+ clearArgv(backend);
615
+ return argv;
616
+ };
617
+ const freshWith = (over: { backend?: "pi" | "codex"; cwd?: string; callerCwd?: string }) =>
618
+ freshCall(
619
+ {
620
+ backend: over.backend ?? "pi",
621
+ model: "fixture/model",
622
+ task: "fixture task",
623
+ cwd: over.cwd,
624
+ callerCwd: over.callerCwd,
625
+ callerGardenId: "20260101T000000-fixture",
626
+ },
627
+ inherited,
628
+ );
629
+
630
+ clearArgv("pi");
631
+ const fromRecord = freshWith({ callerCwd: callerRepo });
632
+ assert.ok(fromRecord.ok, `the caller-cwd fresh call must succeed: ${fromRecord.ok ? "" : fromRecord.reason}`);
633
+ awaitRuntime("pi");
634
+ ok(
635
+ "caller cwd: a caller that HAS a record directory opens the pane THERE, not in this process's directory",
636
+ paneCwd(fromRecord.receipt.paneId) === callerRepo &&
637
+ callerRepo !== process.cwd() &&
638
+ fromRecord.receipt.cwd === callerRepo &&
639
+ fromRecord.receipt.cwdSource === "codex-caller-record",
640
+ );
641
+ ok(
642
+ "caller cwd: the runtime argv carries no tmux carrier — the directory reached the PANE through `-c`",
643
+ !runtimeArgv("pi").includes("-c"),
644
+ );
645
+ closeWindow(fromRecord.receipt, inherited);
646
+
647
+ clearArgv("pi");
648
+ const requested = freshWith({ cwd: requestedRepo, callerCwd: callerRepo });
649
+ assert.ok(requested.ok, `the requested-cwd fresh call must succeed: ${requested.ok ? "" : requested.reason}`);
650
+ awaitRuntime("pi");
651
+ ok(
652
+ "caller cwd: an explicit request still wins over the caller's record directory, and the receipt says which rule chose it",
653
+ paneCwd(requested.receipt.paneId) === requestedRepo &&
654
+ requested.receipt.cwd === requestedRepo &&
655
+ requested.receipt.cwdSource === "requested",
656
+ );
657
+ closeWindow(requested.receipt, inherited);
658
+ runtimeArgv("pi");
659
+
660
+ // The inherited case, and the measurement `-C`'s default rests on: with no `-c`
661
+ // the pane takes the directory of the process that ran `new-window` — NOT the
662
+ // target session's `session_path`, which is why a seat in another directory is
663
+ // what makes this readable at all.
664
+ assert.equal(
665
+ fx("new-session", "-d", "-s", `${SESSION}-cwd`, "-c", sessionRepo).status,
666
+ 0,
667
+ "fixture session rooted elsewhere",
668
+ );
669
+ const seatCwdId = fxLines("list-windows", "-t", `=${SESSION}-cwd`, "-F", "#{session_id}")[0];
670
+ clearArgv("pi");
671
+ const inherit = freshCall(
672
+ {
673
+ backend: "pi",
674
+ model: "fixture/model",
675
+ task: "fixture task",
676
+ placement: { tmuxSession: `${SESSION}-cwd` },
677
+ callerGardenId: "20260101T000000-fixture",
678
+ },
679
+ inherited,
680
+ );
681
+ assert.ok(inherit.ok, `the inherited-cwd fresh call must succeed: ${inherit.ok ? "" : inherit.reason}`);
682
+ awaitRuntime("pi");
683
+ ok(
684
+ "caller cwd: with NO directory named, the pane inherits this process's — not the target session's session_path — and the receipt invents nothing",
685
+ paneCwd(inherit.receipt.paneId) === process.cwd() &&
686
+ process.cwd() !== sessionRepo &&
687
+ inherit.receipt.sessionId === seatCwdId &&
688
+ inherit.receipt.cwd === undefined &&
689
+ inherit.receipt.cwdSource === undefined,
690
+ );
691
+ closeWindow(inherit.receipt, inherited);
692
+ runtimeArgv("pi");
693
+ fx("kill-session", "-t", seatCwdId);
694
+
695
+ // The codex half: the SAME value the pane got, carried to the vendor as `-C`,
696
+ // read off the process's own argv rather than off the builder that wrote it.
697
+ clearArgv("codex");
698
+ const codexThread = freshWith({ backend: "codex", callerCwd: callerRepo });
699
+ assert.ok(codexThread.ok, `the codex fresh call must succeed: ${codexThread.ok ? "" : codexThread.reason}`);
700
+ const codexArgv = runtimeArgv("codex");
701
+ const dashC = codexArgv.indexOf("-C");
702
+ ok(
703
+ "codex thread cwd: the vendor argv carries `-C <dir>` with the SAME directory the pane landed in — one value, two carriers",
704
+ dashC >= 0 &&
705
+ codexArgv[dashC + 1] === callerRepo &&
706
+ codexArgv.filter((a) => a === "-C").length === 1 &&
707
+ paneCwd(codexThread.receipt.paneId) === callerRepo,
708
+ );
709
+ ok(
710
+ "codex thread cwd: the flag rides the SAME argv as the remote attachment — an attached thread with no override takes the app-server's directory",
711
+ codexArgv.includes("--remote") && dashC > codexArgv.indexOf("--remote"),
712
+ );
713
+ closeWindow(codexThread.receipt, inherited);
714
+ ok("caller cwd: the fixture is back to one session", sessionCount() === 1);
715
+ }
420
716
  }
421
717
  } finally {
422
718
  fx("kill-server");