@junghanacs/entwurf 0.21.0 → 0.23.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 (122) hide show
  1. package/AGENTS.md +3 -2
  2. package/BASELINE.md +3 -1
  3. package/CHANGELOG.md +658 -0
  4. package/DELIVERY.md +167 -29
  5. package/README.md +104 -14
  6. package/VERIFY.md +73 -11
  7. package/docs/external-mcp-host.md +16 -6
  8. package/docs/setup-clean-host.md +73 -22
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +54 -31
  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-declaration.js +609 -0
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +289 -57
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +171 -263
  26. package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
  27. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
  28. package/mcp/entwurf-bridge/src/index.ts +65 -31
  29. package/mcp/entwurf-bridge/tsconfig.build.json +10 -0
  30. package/package.json +4 -4
  31. package/pi-extensions/entwurf-control.ts +91 -52
  32. package/pi-extensions/lib/codex-caller-seat.ts +204 -0
  33. package/pi-extensions/lib/codex-declaration.js +612 -0
  34. package/pi-extensions/lib/codex-fresh-preflight.ts +325 -52
  35. package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
  36. package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
  37. package/pi-extensions/lib/entwurf-facts.ts +14 -1
  38. package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
  39. package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
  40. package/pi-extensions/lib/entwurf-self-address.ts +33 -0
  41. package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
  42. package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
  43. package/pi-extensions/lib/fresh-call-composition.ts +493 -0
  44. package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
  45. package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
  46. package/pi-extensions/lib/herdr-placement.ts +276 -0
  47. package/pi-extensions/lib/mux-fresh-call.ts +257 -304
  48. package/run.sh +522 -10
  49. package/scripts/check-codex-app-server-launch.ts +445 -0
  50. package/scripts/check-codex-birth-hook.ts +187 -0
  51. package/scripts/check-entwurf-control-rpc.ts +138 -0
  52. package/scripts/check-entwurf-fact-provider.ts +4 -2
  53. package/scripts/check-entwurf-facts.ts +12 -6
  54. package/scripts/check-entwurf-peers-surface.ts +3 -1
  55. package/scripts/check-entwurf-self-address.ts +68 -0
  56. package/scripts/check-entwurf-v2-production.ts +42 -1
  57. package/scripts/check-entwurf-v2-send.ts +26 -7
  58. package/scripts/check-fresh-call-dispatch.ts +301 -0
  59. package/scripts/check-gate-qualification.ts +19 -5
  60. package/scripts/check-herdr-activation.ts +1293 -0
  61. package/scripts/check-herdr-fresh-call.ts +1250 -0
  62. package/scripts/check-herdr-placement.ts +383 -0
  63. package/scripts/check-herdr-plugin-build.ts +842 -0
  64. package/scripts/check-herdr-plugin-profile.ts +371 -0
  65. package/scripts/check-herdr-plugin.ts +523 -0
  66. package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
  67. package/scripts/check-herdr-sandbox.ts +456 -0
  68. package/scripts/check-herdr-supply.ts +184 -0
  69. package/scripts/check-mux-launch-tmux.ts +331 -35
  70. package/scripts/check-mux-launcher-fence.ts +138 -0
  71. package/scripts/check-peer-facts.ts +331 -0
  72. package/scripts/check-release-gate-outcomes.ts +12 -0
  73. package/scripts/check-typing-call-fence.ts +296 -0
  74. package/scripts/codex-app-server-launch.sh +275 -0
  75. package/scripts/codex-birth-doctor.sh +159 -16
  76. package/scripts/codex-birth-install.sh +178 -72
  77. package/scripts/codex-birth-uninstall.sh +162 -7
  78. package/scripts/codex-socket-path.ts +33 -0
  79. package/scripts/codex-terminal-title-config.py +500 -0
  80. package/scripts/codex_toml_io.py +121 -0
  81. package/scripts/fixtures/herdr-supply.json +29 -0
  82. package/scripts/herdr-activation.mjs +536 -0
  83. package/scripts/herdr-plugin-activate.mjs +270 -0
  84. package/scripts/herdr-plugin-deactivate.mjs +193 -0
  85. package/scripts/herdr-runtime.mjs +1203 -0
  86. package/scripts/install-herdr-ci.sh +96 -0
  87. package/scripts/lib/codex-fresh-live-protocol.ts +13 -5
  88. package/scripts/lib/codex-fresh-source-receipts.ts +29 -2
  89. package/scripts/lib/tree-digest.ts +97 -0
  90. package/scripts/meta-bridge-install.sh +19 -2
  91. package/scripts/meta-bridge-state.py +167 -11
  92. package/scripts/mutants/codex-app-server-launch.json +157 -0
  93. package/scripts/mutants/codex-caller-seat.json +334 -0
  94. package/scripts/mutants/codex-native.json +195 -29
  95. package/scripts/mutants/control-socket-disconnect.json +57 -0
  96. package/scripts/mutants/fresh-call-dispatch.json +154 -0
  97. package/scripts/mutants/herdr-activation.json +305 -0
  98. package/scripts/mutants/herdr-fresh-call.json +448 -0
  99. package/scripts/mutants/herdr-placement.json +161 -0
  100. package/scripts/mutants/herdr-plugin-build.json +160 -0
  101. package/scripts/mutants/herdr-plugin-profile.json +202 -0
  102. package/scripts/mutants/herdr-plugin.json +133 -0
  103. package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
  104. package/scripts/mutants/herdr-supply.json +133 -0
  105. package/scripts/mutants/meta-hook-session-switch.json +2 -2
  106. package/scripts/mutants/mux-fresh-call.json +184 -25
  107. package/scripts/mutants/mux-launcher-fence.json +13 -0
  108. package/scripts/mutants/omp-fresh.json +4 -4
  109. package/scripts/mutants/peer-facts.json +98 -0
  110. package/scripts/mutants/self-address.json +33 -0
  111. package/scripts/mutants/typing-call-fence.json +17 -0
  112. package/scripts/mutants/v2-surface.json +22 -0
  113. package/scripts/peer-facts.ts +120 -0
  114. package/scripts/register-pi-package.py +8 -0
  115. package/scripts/register-pi-provider.py +88 -12
  116. package/scripts/smoke-codex-birth.sh +281 -12
  117. package/scripts/smoke-codex-config-state.sh +192 -3
  118. package/scripts/smoke-codex-fresh-live.ts +277 -37
  119. package/scripts/smoke-entwurf-chain-live.ts +50 -0
  120. package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
  121. package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
  122. package/scripts/smoke-setup-verdict.sh +13 -11
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env bash
2
2
  # smoke-codex-config-state — hermetic install/doctor/inverse contract for the
3
- # two Codex config atoms (codex-mcp-config.py + codex-statusline-config.py).
3
+ # three Codex config atoms (codex-mcp-config.py + codex-statusline-config.py +
4
+ # codex-terminal-title-config.py).
4
5
  # No Codex process, no model turn: the vendor's config.toml surface only.
5
6
  set -euo pipefail
6
7
 
7
8
  REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8
9
  MCP="$REPO_DIR/scripts/codex-mcp-config.py"
9
10
  SL="$REPO_DIR/scripts/codex-statusline-config.py"
11
+ TT="$REPO_DIR/scripts/codex-terminal-title-config.py"
10
12
  pass=0
11
13
  ok() { printf ' ok %s\n' "$1"; pass=$((pass + 1)); }
12
14
  die() { printf 'FAIL: %s\n' "$1" >&2; exit 1; }
@@ -39,6 +41,7 @@ mkdir -p "$CODEX_HOME" "$(dirname "$XDG_DATA_HOME")"
39
41
  CFG="$CODEX_HOME/config.toml"
40
42
  MCP_STATE="$XDG_DATA_HOME/entwurf/codex-mcp/install-state.json"
41
43
  SL_STATE="$XDG_DATA_HOME/entwurf/codex-statusline/install-state.json"
44
+ TT_STATE="$XDG_DATA_HOME/entwurf/codex-terminal-title/install-state.json"
42
45
 
43
46
  seed_config() { # the operator's hand-edited file: quoted header keys, nested
44
47
  # tables, an unrelated MCP server, and NO entwurf atom content.
@@ -464,6 +467,189 @@ sed -i 's/^status_line = .*/status_line = [\n "weather",\n "thread-title",\n]/
464
467
  refuses "restructured list refuses blind take-back" "changed since install" python3 "$SL" uninstall "$SL_STATE"
465
468
  rm -f "$SL_STATE"
466
469
 
470
+
471
+ ###############################################################################
472
+ # Atom 3 — [tui].terminal_title includes thread-id (#95 lane B caller seat)
473
+ #
474
+ # Same writer shape as atom 2 and a DIFFERENT contract in two places, both of
475
+ # which have a wrong answer that still parses: the seeded list must LEAD with
476
+ # `activity` (the herdr Codex detector keys on the spinner/action-required
477
+ # prefix), and an existing operator list is APPENDED to, never prepended — a
478
+ # prepend would put thread-id ahead of an activity the operator already
479
+ # configured. The append position is the third: a trailing comma, a trailing
480
+ # comment or a `]` inside a string item each send a naive `rfind("]")` at the
481
+ # wrong bytes, and one of those emits `,,`.
482
+ ###############################################################################
483
+
484
+ # seeded from nothing: the herdr-safe order, verbatim
485
+ rm -f "$CFG" "$TT_STATE"
486
+ python3 "$TT" install "$CFG" "$TT_STATE" >"$SB/out"
487
+ want "[QK:CODEX-TT-SEED-ACTIVITY-FIRST] seeded terminal_title leads with activity, ends with thread-id" \
488
+ "grep -q '^created-new ' '$SB/out' && grep -q '^terminal_title = \[\"activity\", \"project-name\", \"thread-id\"\]$' '$CFG'"
489
+ python3 "$TT" doctor-static "$CFG" "$TT_STATE" >"$SB/out"
490
+ want "seeded terminal-title doctor is green and owned" "grep -q '^thread-id-present owned$' '$SB/out'"
491
+ python3 "$TT" uninstall "$TT_STATE" >"$SB/out"
492
+ want "seeded file is removed whole by the inverse" "[ ! -e '$CFG' ] && [ ! -e '$TT_STATE' ]"
493
+
494
+ # an operator list is APPENDED to — the direction that is opposite to atom 2
495
+ seed_config
496
+ sed -i '/^theme = /i terminal_title = ["activity", "project-name"]' "$CFG"
497
+ cp "$CFG" "$SB/tt-seed.toml"
498
+ python3 "$TT" install "$CFG" "$TT_STATE" >"$SB/out"
499
+ want "[QK:CODEX-TT-APPENDS-AT-END] thread-id is appended LAST, operator order untouched" \
500
+ "grep -q '^merged-item ' '$SB/out' && grep -q '^terminal_title = \[\"activity\", \"project-name\", \"thread-id\"\]$' '$CFG'"
501
+ toml_ok "$CFG"; ok "merged terminal_title still parses"
502
+ MT1="$(stat -c %Y "$CFG")"; sleep 1.1
503
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
504
+ want "terminal-title reinstall writes nothing" "[ '$MT1' = '$(stat -c %Y "$CFG")' ]"
505
+ python3 "$TT" uninstall "$TT_STATE" >/dev/null
506
+ cmp -s "$SB/tt-seed.toml" "$CFG" && ok "appended item restored byte-exact" || die "terminal-title inverse mismatch"
507
+
508
+ # an activity-TERMINATED operator list: the item still lands after it
509
+ seed_config
510
+ sed -i '/^theme = /i terminal_title = ["project-name", "activity"]' "$CFG"
511
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
512
+ want "an activity-terminated list keeps activity in place and appends after it" \
513
+ "grep -q '^terminal_title = \[\"project-name\", \"activity\", \"thread-id\"\]$' '$CFG'"
514
+ python3 "$TT" uninstall "$TT_STATE" >/dev/null
515
+
516
+ # multi-line array with a TRAILING COMMA — the shape a naive append breaks
517
+ seed_config
518
+ python3 - "$CFG" <<'PY'
519
+ import sys
520
+ p = sys.argv[1]
521
+ t = open(p).read()
522
+ t = t.replace('theme = "zenburn"',
523
+ 'terminal_title = [\n "activity", # operator note\n "project-name",\n]\ntheme = "zenburn"')
524
+ open(p, 'w').write(t)
525
+ PY
526
+ cp "$CFG" "$SB/tt-multiline.toml"
527
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
528
+ toml_ok "$CFG"
529
+ want "[QK:CODEX-TT-TRAILING-COMMA] a trailing-comma array is appended to without emitting a double comma" \
530
+ "! grep -q ',[[:space:]]*,' '$CFG' && python3 -c \"import tomllib,sys; d=tomllib.load(open(sys.argv[1],'rb')); assert d['tui']['terminal_title']==['activity','project-name','thread-id'], d\" '$CFG'"
531
+ python3 "$TT" uninstall "$TT_STATE" >/dev/null
532
+ cmp -s "$SB/tt-multiline.toml" "$CFG" && ok "trailing-comma array restored byte-exact" || die "trailing-comma inverse mismatch"
533
+
534
+ # a trailing COMMENT that itself contains `]` — rfind("]") would splice into it
535
+ seed_config
536
+ sed -i '/^theme = /i terminal_title = ["activity"] # keep ] this' "$CFG"
537
+ cp "$CFG" "$SB/tt-comment.toml"
538
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
539
+ want "[QK:CODEX-TT-COMMENT-NOT-SPLICED] the item lands inside the array and the operator's comment survives" \
540
+ "grep -q '^terminal_title = \[\"activity\", \"thread-id\"\] # keep \] this$' '$CFG'"
541
+ python3 "$TT" uninstall "$TT_STATE" >/dev/null
542
+ cmp -s "$SB/tt-comment.toml" "$CFG" && ok "commented line restored byte-exact" || die "comment inverse mismatch"
543
+
544
+ # a `]` inside a string item, and the empty list
545
+ seed_config
546
+ sed -i '/^theme = /i terminal_title = ["a]b", "activity"]' "$CFG"
547
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
548
+ want "a ] inside a string item does not become the append point" \
549
+ "grep -q '^terminal_title = \[\"a\]b\", \"activity\", \"thread-id\"\]$' '$CFG'"
550
+ python3 "$TT" uninstall "$TT_STATE" >/dev/null
551
+ seed_config
552
+ sed -i '/^theme = /i terminal_title = []' "$CFG"
553
+ cp "$CFG" "$SB/tt-empty.toml"
554
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
555
+ want "empty terminal_title becomes exactly [\"thread-id\"]" "grep -q '^terminal_title = \[\"thread-id\"\]$' '$CFG'"
556
+ python3 "$TT" uninstall "$TT_STATE" >/dev/null
557
+ cmp -s "$SB/tt-empty.toml" "$CFG" && ok "empty terminal_title restored byte-exact" || die "empty terminal_title inverse mismatch"
558
+
559
+ # an operator-authored item is UNOWNED, and nothing is written over it
560
+ seed_config
561
+ sed -i '/^theme = /i terminal_title = ["activity", "thread-id"]' "$CFG"
562
+ cp "$CFG" "$SB/tt-operator.toml"
563
+ rm -f "$TT_STATE"
564
+ MT1="$(stat -c %Y "$CFG")"; sleep 1.1
565
+ python3 "$TT" install "$CFG" "$TT_STATE" >"$SB/out"
566
+ want "[QK:CODEX-TT-OPERATOR-ITEM-UNOWNED] an operator-authored thread-id writes NO receipt and no config byte" \
567
+ "grep -q '^already-present ' '$SB/out' && [ ! -e '$TT_STATE' ] && [ '$MT1' = '$(stat -c %Y "$CFG")' ]"
568
+ python3 "$TT" doctor-static "$CFG" "$TT_STATE" >"$SB/out"
569
+ want "the doctor reports it UNOWNED — we claim no edit we did not make" \
570
+ "grep -q '^thread-id-present unowned$' '$SB/out'"
571
+ python3 "$TT" uninstall "$TT_STATE" >"$SB/out" 2>&1 \
572
+ && die "uninstall claimed to take back an operator's own item"
573
+ grep -q 'nothing to undo' "$SB/out" || die "unowned uninstall refusal is unnamed: $(cat "$SB/out")"
574
+ cmp -s "$SB/tt-operator.toml" "$CFG" && ok "the refused inverse left the operator's config byte-identical" \
575
+ || die "unowned uninstall mutated the operator's config"
576
+
577
+ # ...and that is what keeps the INVERSE honest across a hand edit. With a receipt parked over an
578
+ # operator's item, a reinstall after they removed it splices ours in while the stale receipt still
579
+ # says it was already there — and the inverse then reports "nothing to take back" and leaves our
580
+ # bytes behind for good. Writing nothing above makes that second install an ordinary merged-item.
581
+ seed_config
582
+ sed -i '/^theme = /i terminal_title = ["activity"]' "$CFG"
583
+ cp "$CFG" "$SB/tt-inverse.toml"
584
+ rm -f "$TT_STATE"
585
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
586
+ sed -i 's/, "thread-id"\]/]/' "$CFG" # the operator takes our item back by hand
587
+ python3 "$TT" install "$CFG" "$TT_STATE" >"$SB/out"
588
+ want "a reinstall after a hand removal records what it ACTUALLY did" \
589
+ "grep -q '^merged-item ' '$SB/out' && grep -q '\"detectMode\": \"merged-item\"' '$TT_STATE'"
590
+ python3 "$TT" uninstall "$TT_STATE" >/dev/null
591
+ cmp -s "$SB/tt-inverse.toml" "$CFG" && ok "the inverse takes our re-added item back, leaving nothing behind" \
592
+ || { printf 'left behind:\n%s\n' "$(cat "$CFG")"; die "reinstall inverse left our item in the operator's config"; }
593
+ rm -f "$TT_STATE"
594
+
595
+ # the doctor's subject is the REQUIRED caller-seat axis, receipt or no receipt
596
+ seed_config
597
+ sed -i '/^theme = /i terminal_title = ["activity", "thread-id"]' "$CFG"
598
+ rm -f "$TT_STATE"
599
+ python3 "$TT" doctor-static "$CFG" "$TT_STATE" >"$SB/out"
600
+ want "unowned thread-id is green (state-independent)" "grep -q '^thread-id-present unowned$' '$SB/out'"
601
+ sed -i 's/, "thread-id"\]/]/' "$CFG"
602
+ python3 "$TT" doctor-static "$CFG" "$TT_STATE" >"$SB/out" 2>&1 \
603
+ && die "[QK:CODEX-TT-DOCTOR-REQUIRES-SEAT-AXIS] unowned config WITHOUT thread-id reported green: $(cat "$SB/out")"
604
+ want "[QK:CODEX-TT-DOCTOR-REQUIRES-SEAT-AXIS] a missing caller-seat axis is RED even unowned" \
605
+ "grep -q '^thread-id-absent unowned$' '$SB/out'"
606
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
607
+ sed -i 's/, "thread-id"\]/]/' "$CFG"
608
+ python3 "$TT" doctor-static "$CFG" "$TT_STATE" >"$SB/out" 2>&1 || true
609
+ want "our receipt over a vanished item reports drift, not green" "grep -q '^thread-id-absent owned drift$' '$SB/out'"
610
+ rm -f "$TT_STATE"
611
+
612
+ # the two [tui] atoms are independent: neither sees the other's key
613
+ seed_config
614
+ python3 "$SL" install "$CFG" "$SL_STATE" >/dev/null
615
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
616
+ want "[QK:CODEX-TT-ATOM-INDEPENDENCE] both [tui] atoms live side by side" \
617
+ "python3 -c \"import tomllib,sys; d=tomllib.load(open(sys.argv[1],'rb'))['tui']; assert d['status_line']==['thread-title','model-with-reasoning','current-dir'], d; assert d['terminal_title']==['activity','project-name','thread-id'], d\" '$CFG'"
618
+ python3 "$TT" uninstall "$TT_STATE" >/dev/null
619
+ want "[QK:CODEX-TT-ATOM-INDEPENDENCE] taking back terminal_title leaves status_line exactly as it was" \
620
+ "grep -q '^status_line = \[\"thread-title\", \"model-with-reasoning\", \"current-dir\"\]$' '$CFG' && ! grep -q 'terminal_title' '$CFG'"
621
+ python3 "$SL" uninstall "$SL_STATE" >/dev/null
622
+
623
+ # refusals — symlink, malformed, contrary value by name, foreign/retargeted state
624
+ seed_config
625
+ ln -sf "$SB/foreign.toml" "$CFG"
626
+ refuses "symlink config refused (terminal-title)" "symlink" python3 "$TT" install "$CFG" "$TT_STATE"
627
+ rm -f "$CFG"
628
+ printf 'garbage [[[\n' > "$CFG"
629
+ refuses "malformed TOML refused (terminal-title)" "not valid TOML" python3 "$TT" install "$CFG" "$TT_STATE"
630
+ seed_config
631
+ sed -i '/^theme = /i terminal_title = "thread-id"' "$CFG"
632
+ refuses "non-array terminal_title refused BY NAME" "terminal_title.*explicitly" python3 "$TT" install "$CFG" "$TT_STATE"
633
+ want "terminal-title refusals wrote no state" "[ ! -e '$TT_STATE' ]"
634
+ seed_config
635
+ python3 "$SL" install "$CFG" "$SL_STATE" >/dev/null
636
+ cp "$SL_STATE" "$TT_STATE" # atom 2's receipt, on our path
637
+ refuses "foreign-atom state refused (terminal-title)" "belongs to atom" python3 "$TT" install "$CFG" "$TT_STATE"
638
+ python3 "$SL" uninstall "$SL_STATE" >/dev/null
639
+ rm -f "$TT_STATE"
640
+ seed_config
641
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
642
+ sed -i "s|$(json_field "$TT_STATE" managedConfigPath)|$SB/elsewhere.toml|" "$TT_STATE"
643
+ refuses "retargeted state refused (terminal-title)" "refusing to retarget" python3 "$TT" install "$CFG" "$TT_STATE"
644
+ rm -f "$TT_STATE"
645
+
646
+ # operator restructures the list after install: the take-back refuses, by name
647
+ seed_config
648
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
649
+ sed -i 's/^terminal_title = .*/terminal_title = [\n "weather",\n "thread-id",\n]/' "$CFG"
650
+ refuses "restructured terminal_title refuses blind take-back" "changed since install" python3 "$TT" uninstall "$TT_STATE"
651
+ rm -f "$TT_STATE"
652
+
467
653
  ###############################################################################
468
654
  # Unrelated operator config survives the whole cycle, byte for byte
469
655
  ###############################################################################
@@ -471,6 +657,7 @@ seed_config
471
657
  cp "$CFG" "$SB/final-seed.toml"
472
658
  python3 "$MCP" install "$CFG" entwurf-bridge "$MCP_STATE" >/dev/null
473
659
  python3 "$SL" install "$CFG" "$SL_STATE" >/dev/null
660
+ python3 "$TT" install "$CFG" "$TT_STATE" >/dev/null
474
661
  toml_ok "$CFG"; ok "fully installed config parses as TOML"
475
662
  python3 - "$CFG" <<'PY'
476
663
  import tomllib, sys
@@ -494,17 +681,19 @@ assert d["mcp_servers"]["entwurf-bridge"] == {
494
681
  "env": {"ENTWURF_BRIDGE_NATIVE_HOST": "codex"},
495
682
  }, d
496
683
  assert d["tui"]["status_line"] == ["thread-title", "model-with-reasoning", "current-dir"], d
684
+ assert d["tui"]["terminal_title"] == ["activity", "project-name", "thread-id"], d
497
685
  assert d["tui"]["theme"] == "zenburn" and d["tui"]["model_availability_nux"] == {"gpt-5.5": 4}, d
498
686
  assert d["plugins"]["github@openai-curated"] == {"enabled": False}, d
499
687
  PY
500
- ok "unrelated tables intact and both atoms semantically live"
688
+ ok "unrelated tables intact and all three atoms semantically live"
501
689
  # the vendor writes trust state back between install and uninstall (M1)
502
690
  printf '[hooks.state."~/c.toml:stop:0:0"]\ntrusted_hash = "sha256:def"\n' >> "$CFG"
503
691
  python3 "$MCP" uninstall "$MCP_STATE" >/dev/null
504
692
  python3 "$SL" uninstall "$SL_STATE" >/dev/null
693
+ python3 "$TT" uninstall "$TT_STATE" >/dev/null
505
694
  printf '[hooks.state."~/c.toml:stop:0:0"]\ntrusted_hash = "sha256:def"\n' >> "$SB/final-seed.toml"
506
695
  cmp -s "$SB/final-seed.toml" "$CFG" && ok "full cycle leaves only vendor-added bytes (unrelated config survives)" || die "final byte mismatch"
507
- want "both states cleared" "[ ! -e '$MCP_STATE' ] && [ ! -e '$SL_STATE' ]"
696
+ want "all three states cleared" "[ ! -e '$MCP_STATE' ] && [ ! -e '$SL_STATE' ] && [ ! -e '$TT_STATE' ]"
508
697
 
509
698
  REPO_AFTER="$(cd "$REPO_DIR" && git status --porcelain)"
510
699
  [ "$REPO_BEFORE" = "$REPO_AFTER" ] || die "smoke changed the checkout"