@junghanacs/entwurf 0.20.1 → 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 (111) hide show
  1. package/AGENTS.md +85 -190
  2. package/BASELINE.md +6 -3
  3. package/CHANGELOG.md +473 -14
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +332 -60
  6. package/README.md +100 -22
  7. package/VERIFY.md +93 -7
  8. package/docs/acp-backend-rail.md +0 -1
  9. package/docs/external-mcp-host.md +64 -33
  10. package/docs/setup-clean-host.md +151 -17
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +130 -114
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +519 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
  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/entwurf-v2-surface.js +7 -4
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +20 -11
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +227 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +189 -19
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +10 -7
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +403 -0
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +4 -4
  28. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-codex.js +325 -0
  29. package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
  30. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +6 -1
  31. package/mcp/entwurf-bridge/src/index.ts +154 -116
  32. package/mcp/entwurf-bridge/tsconfig.build.json +10 -0
  33. package/package.json +13 -4
  34. package/pi-extensions/entwurf-control.ts +71 -19
  35. package/pi-extensions/lib/codex-caller-seat.ts +204 -0
  36. package/pi-extensions/lib/codex-fresh-preflight.ts +580 -0
  37. package/pi-extensions/lib/compaction-send-guard.ts +80 -0
  38. package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
  39. package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
  40. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
  41. package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
  42. package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
  43. package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
  44. package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
  45. package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
  46. package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
  47. package/pi-extensions/lib/meta-sender-identity.ts +305 -0
  48. package/pi-extensions/lib/mux-fresh-call.ts +233 -29
  49. package/pi-extensions/lib/native-push/adapter.ts +21 -24
  50. package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
  51. package/pi-extensions/lib/native-push/register.ts +7 -9
  52. package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
  53. package/run.sh +342 -28
  54. package/scripts/check-agy-sender-identity.ts +1 -1
  55. package/scripts/check-codex-app-server-launch.ts +445 -0
  56. package/scripts/check-codex-birth-hook.ts +264 -0
  57. package/scripts/check-codex-bridge-identity.ts +179 -0
  58. package/scripts/check-codex-native-push.ts +386 -0
  59. package/scripts/check-codex-sender-identity.ts +495 -0
  60. package/scripts/check-compaction-send-guard.ts +130 -0
  61. package/scripts/check-copilot-receive-arm.ts +4 -1
  62. package/scripts/check-entwurf-fact-provider.ts +38 -0
  63. package/scripts/check-entwurf-peers-surface.ts +13 -1
  64. package/scripts/check-entwurf-self-address.ts +15 -16
  65. package/scripts/check-entwurf-v2-contract.ts +4 -3
  66. package/scripts/check-entwurf-v2-decider.ts +7 -5
  67. package/scripts/check-entwurf-v2-native-push.ts +35 -7
  68. package/scripts/check-entwurf-v2-production.ts +245 -12
  69. package/scripts/check-entwurf-v2-runner.ts +1 -1
  70. package/scripts/check-entwurf-v2-send.ts +26 -7
  71. package/scripts/check-entwurf-v2-surface.ts +1 -1
  72. package/scripts/check-gate-qualification.ts +8 -3
  73. package/scripts/check-harness-admission-parity.ts +0 -1
  74. package/scripts/check-mux-launch-tmux.ts +345 -4
  75. package/scripts/check-native-push-adapter.ts +20 -16
  76. package/scripts/check-native-push-register.ts +5 -1
  77. package/scripts/check-release-gate-outcomes.ts +47 -1
  78. package/scripts/check-setup-qualification.sh +3 -1
  79. package/scripts/codex-app-server-launch.sh +275 -0
  80. package/scripts/codex-birth-doctor.sh +276 -0
  81. package/scripts/codex-birth-install.sh +414 -0
  82. package/scripts/codex-birth-uninstall.sh +170 -0
  83. package/scripts/codex-mcp-config.py +435 -0
  84. package/scripts/codex-socket-path.ts +33 -0
  85. package/scripts/codex-statusline-config.py +434 -0
  86. package/scripts/codex-terminal-title-config.py +500 -0
  87. package/scripts/codex_toml_io.py +653 -0
  88. package/scripts/lib/codex-fresh-live-protocol.ts +121 -0
  89. package/scripts/lib/codex-fresh-source-receipts.ts +426 -0
  90. package/scripts/lib/launch-receipt-windows.ts +46 -0
  91. package/scripts/lib/tmux-coordinate-row.ts +58 -0
  92. package/scripts/meta-bridge-fresh-cut.ts +6 -1
  93. package/scripts/mutants/codex-app-server-launch.json +157 -0
  94. package/scripts/mutants/codex-caller-seat.json +336 -0
  95. package/scripts/mutants/codex-native.json +838 -0
  96. package/scripts/mutants/compaction-send-guard.json +103 -0
  97. package/scripts/mutants/entwurf-peers.json +19 -0
  98. package/scripts/mutants/mux-fresh-call.json +93 -10
  99. package/scripts/mutants/omp-fresh.json +6 -4
  100. package/scripts/mutants/release-gate.json +13 -0
  101. package/scripts/mutants/v2-surface.json +75 -1
  102. package/scripts/raw-async-delivery/README.md +2 -1
  103. package/scripts/raw-codex-measure/README.md +114 -46
  104. package/scripts/smoke-agy-native-push-live.ts +3 -1
  105. package/scripts/smoke-codex-birth.sh +347 -0
  106. package/scripts/smoke-codex-config-state.sh +700 -0
  107. package/scripts/smoke-codex-fresh-live.ts +1426 -0
  108. package/scripts/smoke-codex-native-push-live.ts +75 -0
  109. package/scripts/smoke-entwurf-chain-live.ts +50 -0
  110. package/scripts/smoke-setup-verdict.sh +125 -10
  111. package/scripts/tsconfig.json +1 -0
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env python3
2
+ """codex-mcp-config — stateful `[mcp_servers.entwurf-bridge]` adapter; stdlib only.
3
+
4
+ One reversible atom in `$CODEX_HOME/config.toml`: the entwurf-bridge MCP server
5
+ entry in the MEASURED stdio shape (`command` written by `codex mcp add`, M3 in
6
+ scripts/raw-codex-measure/README.md), the vendor-supported `env_vars` allowlist
7
+ that forwards the operator-owned app-server context without storing its values,
8
+ plus the one literal provenance env value
9
+ `ENTWURF_BRIDGE_NATIVE_HOST = "codex"` — a local ownership/drift atom, never
10
+ cryptographic authentication. `codex mcp add` is never called because it has
11
+ no exact inverse; this writer owns exactly the table-family lines it touches
12
+ (codex_toml_io), captures the operator's first preimage once, and restores it
13
+ byte-exact.
14
+
15
+ Refuses, with the offending name in the message: a symlinked config (someone
16
+ else's SSOT), malformed TOML, a foreign/no-state collision it cannot attribute,
17
+ a state retargeted at another config path, and explicit contrary operator
18
+ values by name — an `url` entry (the vendor's streamable_http transport) or an
19
+ `enabled = false` entry are the operator's decisions, not drift to overwrite.
20
+
21
+ Doctor separates the two axes the run.sh wiring composes later: the FIRST
22
+ token is effective runtime validity judged from the config alone, the SECOND
23
+ is ownership judged from the install state; `doctor-invocation` prints the
24
+ exact stdio invocation so the wiring can boot-prove the configured command is
25
+ the stable entwurf-bridge bin.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import json
31
+ import os
32
+ import sys
33
+ sys.dont_write_bytecode = True
34
+
35
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
36
+ from codex_toml_io import ( # noqa: E402
37
+ append_block,
38
+ assert_blast_radius,
39
+ atomic_write,
40
+ atomic_write_state,
41
+ deep_get,
42
+ die,
43
+ find_table_family,
44
+ now,
45
+ parse_toml,
46
+ read_text,
47
+ render_file,
48
+ scan_lines,
49
+ span_text,
50
+ splice,
51
+ toml_string,
52
+ )
53
+
54
+ STATE_SCHEMA_VERSION = 1
55
+ ATOM = "codex-mcp"
56
+ SERVER_KEY = "entwurf-bridge"
57
+ SERVER_PATH = ("mcp_servers", SERVER_KEY)
58
+ PROVENANCE_ENV = "ENTWURF_BRIDGE_NATIVE_HOST"
59
+ PROVENANCE_VALUE = "codex"
60
+ CONTEXT_ENV_VARS = (
61
+ "CODEX_HOME",
62
+ "ENTWURF_DIR",
63
+ "PI_CODING_AGENT_DIR",
64
+ "ENTWURF_META_SESSIONS_DIR",
65
+ "ENTWURF_META_MAILBOX_DIR",
66
+ "ENTWURF_META_SENDERS_DIR",
67
+ "ENTWURF_META_RECEIVERS_DIR",
68
+ "TMUX",
69
+ "TMUX_PANE",
70
+ )
71
+ STATE_REQUIRED = (
72
+ "managedConfigPath",
73
+ "atom",
74
+ "serverKey",
75
+ "command",
76
+ "detectMode",
77
+ "configExistedBefore",
78
+ "entryExistedBefore",
79
+ "preimage",
80
+ "installedAt",
81
+ )
82
+
83
+
84
+ def canonical_block(command: str) -> list[str]:
85
+ forwarded = ", ".join(toml_string(name) for name in CONTEXT_ENV_VARS)
86
+ return [
87
+ f"[mcp_servers.{SERVER_KEY}]",
88
+ f"command = {toml_string(command)}",
89
+ f"env_vars = [{forwarded}]",
90
+ f"[mcp_servers.{SERVER_KEY}.env]",
91
+ f"{PROVENANCE_ENV} = {toml_string(PROVENANCE_VALUE)}",
92
+ ]
93
+
94
+
95
+ def canonical_semantics(command: str) -> dict:
96
+ return {
97
+ "command": command,
98
+ "env_vars": list(CONTEXT_ENV_VARS),
99
+ "env": {PROVENANCE_ENV: PROVENANCE_VALUE},
100
+ }
101
+
102
+ def load_state(state_path: str) -> dict | None:
103
+ raw = read_text(state_path, "install-state")
104
+ if raw is None:
105
+ return None
106
+ if not raw.strip():
107
+ die(4, f"codex-mcp: install-state {state_path} is empty — refusing to guess")
108
+ try:
109
+ state = json.loads(raw)
110
+ except ValueError as error:
111
+ die(4, f"codex-mcp: install-state {state_path} is not valid JSON: {error}")
112
+ if not isinstance(state, dict):
113
+ die(4, f"codex-mcp: install-state {state_path} top-level must be a JSON object")
114
+ return state
115
+
116
+
117
+ def checked_state(state_path: str) -> dict:
118
+ state = load_state(state_path)
119
+ if state is None:
120
+ die(2, f"codex-mcp: no install-state at {state_path} — nothing to undo")
121
+ if state.get("schemaVersion") != STATE_SCHEMA_VERSION:
122
+ die(4, f"codex-mcp: install-state {state_path} is not schemaVersion {STATE_SCHEMA_VERSION} — refusing")
123
+ atom = state.get("atom")
124
+ if atom != ATOM:
125
+ # A foreign state: another atom's receipt sitting on our path. Never
126
+ # silently overwritten, never adopted — attribution first.
127
+ die(4, f"codex-mcp: install-state {state_path} belongs to atom {atom!r}, not {ATOM!r} — refusing to touch it")
128
+ if state.get("serverKey") != SERVER_KEY:
129
+ die(4, f"codex-mcp: install-state {state_path} serverKey is not {SERVER_KEY} — refusing")
130
+ if any(key not in state for key in STATE_REQUIRED):
131
+ die(4, f"codex-mcp: install-state {state_path} has an unsupported shape — refusing")
132
+ path = state.get("managedConfigPath")
133
+ if not isinstance(path, str) or not os.path.isabs(path):
134
+ die(4, f"codex-mcp: install-state {state_path} has no absolute managedConfigPath")
135
+ return state
136
+
137
+
138
+ def prior_state(state_path: str, config_path: str) -> dict | None:
139
+ state = load_state(state_path)
140
+ if state is None:
141
+ return None
142
+ if state.get("schemaVersion") != STATE_SCHEMA_VERSION or state.get("atom") != ATOM:
143
+ # Route through checked_state for the honest, named refusal.
144
+ checked_state(state_path)
145
+ if state.get("managedConfigPath") != os.path.abspath(config_path):
146
+ die(
147
+ 3,
148
+ f"codex-mcp: install-state {state_path} is bound to {state.get('managedConfigPath')}, "
149
+ f"not {os.path.abspath(config_path)} — refusing to retarget an install onto another config",
150
+ )
151
+ checked = checked_state(state_path)
152
+ return checked
153
+
154
+
155
+ def classify_entry(parsed: dict) -> tuple[str, dict | None]:
156
+ """The entry's effective verdict and its parsed value (None when absent).
157
+ `refuse:<name>` verdicts are the explicit contrary operator values."""
158
+ entry = deep_get(parsed, SERVER_PATH)
159
+ if entry is None:
160
+ return "absent", None
161
+ if not isinstance(entry, dict):
162
+ return "refuse:not-a-table", entry
163
+ if "url" in entry and "command" not in entry:
164
+ return "refuse:url", entry
165
+ if entry.get("enabled") is False:
166
+ return "refuse:enabled", entry
167
+ if "command" not in entry:
168
+ return "refuse:no-command", entry
169
+ return "stdio", entry
170
+
171
+
172
+ def install(config_path: str, command: str, state_path: str) -> None:
173
+ if os.path.islink(config_path):
174
+ die(3, f"codex-mcp: refusing to adopt {config_path} — symlink to {os.readlink(config_path)} (someone else's SSOT)")
175
+ if os.path.exists(config_path) and not os.path.isfile(config_path):
176
+ die(4, f"codex-mcp: refusing: {config_path} exists and is not a regular file")
177
+
178
+ text = read_text(config_path, "config")
179
+ parsed = parse_toml(text if text is not None else "", f"config {config_path}") if text is not None else {}
180
+ verdict, entry = classify_entry(parsed)
181
+ if verdict.startswith("refuse:"):
182
+ name = verdict.split(":", 1)[1]
183
+ die(
184
+ 3,
185
+ f"codex-mcp: refusing: [mcp_servers.{SERVER_KEY}] in {config_path} explicitly sets "
186
+ f"`{name}` ({'streamable_http transport, not stdio' if name == 'url' else 'the server is explicitly disabled' if name == 'enabled' else 'no stdio command'}). "
187
+ "That is the operator's decision, not drift — entwurf will not overwrite it by name.",
188
+ )
189
+
190
+ lines = (text if text is not None else "").split("\n")
191
+ infos = scan_lines(lines)
192
+ family = find_table_family(lines, infos, SERVER_PATH)
193
+ if entry is not None and family is None:
194
+ die(
195
+ 4,
196
+ f"codex-mcp: [mcp_servers.{SERVER_KEY}] in {config_path} exists but not as a literal table block "
197
+ "(inline/dotted form) — this writer owns only line-shaped tables and refuses to touch it",
198
+ )
199
+
200
+ prior = prior_state(state_path, config_path)
201
+ if prior is None:
202
+ state = {
203
+ "schemaVersion": STATE_SCHEMA_VERSION,
204
+ "managedConfigPath": os.path.abspath(config_path),
205
+ "atom": ATOM,
206
+ "serverKey": SERVER_KEY,
207
+ "command": command,
208
+ "detectMode": "adopt-regular-file" if text is not None else "created-new",
209
+ "configExistedBefore": text is not None,
210
+ "entryExistedBefore": family is not None,
211
+ # The FIRST preimage, captured once: the exact bytes of the table
212
+ # family we superseded (or null when we appended to empty space).
213
+ "preimage": span_text(lines, family[0], family[1]) if family is not None else None,
214
+ "installedAt": now(),
215
+ }
216
+ else:
217
+ state = prior
218
+ state["command"] = command # our recorded intent follows a re-run; preimages never do
219
+
220
+ block = canonical_block(command)
221
+ if family is not None:
222
+ candidate_lines = splice(lines, family[0], family[1], block)
223
+ else:
224
+ candidate_lines = append_block(lines, block) if text is not None else list(block)
225
+ candidate = render_file(candidate_lines)
226
+
227
+ _, after = assert_blast_radius(text, candidate, SERVER_PATH, f"config {config_path}")
228
+ if deep_get(after, SERVER_PATH) != canonical_semantics(command):
229
+ die(7, f"codex-mcp: internal refusal: candidate entry is not the canonical stdio shape")
230
+
231
+ # The receipt is durable BEFORE the operator's config is ever touched: it
232
+ # already carries the exact preimage of the bytes about to be replaced
233
+ # (or a prior receipt's preimage, on a re-run). A crash or write failure
234
+ # between here and the config write below leaves the operator's config
235
+ # untouched and the receipt correct — a retry resumes from it instead of
236
+ # mistaking our own canonical block for a fresh operator preimage.
237
+ atomic_write_state(state_path, state)
238
+ # Strongest idempotence is an untouched file: same bytes, same mtime.
239
+ if candidate != (text if text is not None else ""):
240
+ atomic_write(config_path, candidate, prefix=".codex-mcp-")
241
+ sys.stdout.write(f"{state['detectMode']} {os.path.abspath(config_path)}\n")
242
+
243
+
244
+ def uninstall(state_path: str) -> None:
245
+ state = checked_state(state_path)
246
+ config_path = state["managedConfigPath"]
247
+ if os.path.islink(config_path):
248
+ die(3, f"codex-mcp: refusing to uninstall — {config_path} became a symlink (someone else's SSOT)")
249
+
250
+ text = read_text(config_path, "config")
251
+ if text is None:
252
+ os.remove(state_path) # the config is already gone; the receipt outlived its target
253
+ sys.stdout.write(f"uninstalled {config_path} (config already absent)\n")
254
+ return
255
+ parsed = parse_toml(text, f"config {config_path}")
256
+ entry = deep_get(parsed, SERVER_PATH)
257
+ lines = text.split("\n")
258
+ infos = scan_lines(lines)
259
+ family = find_table_family(lines, infos, SERVER_PATH)
260
+
261
+ if family is not None:
262
+ expected = canonical_semantics(state["command"])
263
+ if entry != expected:
264
+ die(
265
+ 3,
266
+ f"codex-mcp: refusing to uninstall — [{'.'.join(SERVER_PATH)}] drifted from the "
267
+ "exact post-install table. Removing it would delete operator edits; repair or remove "
268
+ "those edits deliberately, then re-run the inverse",
269
+ )
270
+ preimage = state.get("preimage")
271
+ if preimage is None:
272
+ candidate_lines = splice(lines, family[0], family[1], [])
273
+ else:
274
+ candidate_lines = splice(lines, family[0], family[1], preimage.split("\n"))
275
+ candidate = render_file(candidate_lines)
276
+ _, after = assert_blast_radius(text, candidate, SERVER_PATH, f"config {config_path}")
277
+ if preimage is not None:
278
+ # Restoring must reproduce the superseded entry exactly (it is a
279
+ # byte splice; this guards the scanner, not the operator).
280
+ preimage_doc = parse_toml(preimage, "preimage block")
281
+ if deep_get(preimage_doc, SERVER_PATH) != deep_get(after, SERVER_PATH):
282
+ die(7, "codex-mcp: internal refusal: preimage splice changed the entry's meaning")
283
+ if state.get("detectMode") == "created-new" and not [l for l in candidate_lines if l.strip()]:
284
+ # We created this file; the inverse removes it.
285
+ os.remove(config_path)
286
+ os.remove(state_path)
287
+ sys.stdout.write(f"uninstalled {config_path} (file removed)\n")
288
+ return
289
+ atomic_write(config_path, candidate, prefix=".codex-mcp-")
290
+ elif entry is not None:
291
+ # The table is gone by NAME but the path is still populated — the
292
+ # operator (or the vendor) reshaped it into inline/dotted TOML. This
293
+ # writer cannot span-edit that shape; treating it as absent would
294
+ # silently drop the receipt over an entry we never actually took
295
+ # back. Refuse and keep the receipt so the operator can repair it.
296
+ die(
297
+ 3,
298
+ f"codex-mcp: refusing to uninstall — [{'.'.join(SERVER_PATH)}] in {config_path} exists but not as a "
299
+ "literal table block (inline/dotted form) — this writer owns only line-shaped tables and refuses to "
300
+ "guess; the receipt is retained",
301
+ )
302
+ # else: the entry is genuinely gone — never resurrect a removed table.
303
+
304
+ os.remove(state_path) # the receipt is deleted LAST, after the config is honest
305
+ sys.stdout.write(f"uninstalled {config_path}\n")
306
+
307
+ def _ownership(state_path: str, config_path: str) -> tuple[str, dict | None]:
308
+ """(ownership token, state) — `unowned` when no readable receipt of OUR
309
+ atom binds THIS config path. A foreign or retargeted receipt is not an
310
+ ownership claim, so the doctor reports unowned and install refuses
311
+ separately (checked_state names the collision)."""
312
+ state = load_state(state_path)
313
+ if state is None:
314
+ return "unowned", None
315
+ if (
316
+ state.get("schemaVersion") != STATE_SCHEMA_VERSION
317
+ or state.get("atom") != ATOM
318
+ or state.get("managedConfigPath") != os.path.abspath(config_path)
319
+ ):
320
+ return "unowned", None
321
+ return "owned", state
322
+
323
+ def _effective_entry(config_path: str) -> tuple[str, dict | None]:
324
+ """The EFFECTIVE axis, judged from the config alone — never the state."""
325
+ if os.path.islink(config_path):
326
+ return "symlink", None
327
+ text = read_text(config_path, "config")
328
+ if text is None or not text.strip():
329
+ return "file-absent", None
330
+ try:
331
+ parsed = parse_toml(text, f"config {config_path}")
332
+ except SystemExit:
333
+ return "invalid-toml", None
334
+ verdict, entry = classify_entry(parsed)
335
+ if verdict == "absent":
336
+ return "not-configured", None
337
+ if verdict == "refuse:url":
338
+ return "http-transport", entry
339
+ if verdict == "refuse:enabled":
340
+ return "disabled", entry
341
+ if verdict.startswith("refuse:"):
342
+ return "malformed-entry", None
343
+ return f"configured {entry.get('command')}", entry
344
+
345
+
346
+ def doctor_static(config_path: str, expected_command: str, state_path: str) -> None:
347
+ """Two separated tokens on one line: `<effective> <ownership> [drift reason]`.
348
+ The first token never reads the install state; the second never changes the
349
+ first. RED (exit 1) only while a receipt binds here and the entry is not
350
+ what we installed — an unowned-but-working entry is an honest note, not a
351
+ failure (this host's operator ships exactly that shape)."""
352
+ ownership, state = _ownership(state_path, config_path)
353
+ effective, entry = _effective_entry(config_path)
354
+ drift = None
355
+ if ownership == "owned":
356
+ if entry is None:
357
+ drift = "entry-gone"
358
+ elif effective == "http-transport":
359
+ drift = "transport-replaced (url)"
360
+ elif effective == "disabled":
361
+ drift = "disabled (enabled = false)"
362
+ else:
363
+ reason = drift_reason(entry, expected_command)
364
+ if reason is not None:
365
+ drift = reason
366
+ if drift is not None:
367
+ sys.stdout.write(f"{effective} drift {drift}\n")
368
+ raise SystemExit(1)
369
+ sys.stdout.write(f"{effective} {ownership}\n")
370
+
371
+
372
+ def doctor_invocation(config_path: str) -> None:
373
+ """The exact stdio invocation the vendor would spawn, as one JSON line —
374
+ the input the run.sh wiring feeds `probe-bridge-command` to boot-prove the
375
+ configured command is the stable entwurf-bridge bin."""
376
+ effective, entry = _effective_entry(config_path)
377
+ if not effective.startswith("configured "):
378
+ sys.stdout.write(f"{effective}\n")
379
+ raise SystemExit(1)
380
+ args = entry.get("args", [])
381
+ env = entry.get("env", {})
382
+ if not isinstance(args, list) or not all(isinstance(item, str) for item in args):
383
+ sys.stdout.write("malformed-entry (args is not an array of strings)\n")
384
+ raise SystemExit(1)
385
+ if not isinstance(env, dict) or not all(isinstance(k, str) and isinstance(v, str) for k, v in env.items()):
386
+ sys.stdout.write("malformed-entry (env is not a table of strings)\n")
387
+ raise SystemExit(1)
388
+ sys.stdout.write(json.dumps({"command": entry["command"], "args": args, "env": env}, separators=(",", ":")) + "\n")
389
+
390
+ def drift_reason(entry: dict, expected_command: str) -> str | None:
391
+ if entry.get("command") != expected_command:
392
+ return f"command-mismatch (configured {entry.get('command')!r}, installed {expected_command!r})"
393
+ env = entry.get("env")
394
+ if not isinstance(env, dict) or PROVENANCE_ENV not in env:
395
+ return f"env-label-missing ({PROVENANCE_ENV})"
396
+ if env.get(PROVENANCE_ENV) != PROVENANCE_VALUE:
397
+ return f"env-label-foreign ({PROVENANCE_ENV}={env.get(PROVENANCE_ENV)!r})"
398
+ env_vars = entry.get("env_vars")
399
+ if not isinstance(env_vars, list) or not all(isinstance(name, str) for name in env_vars):
400
+ return "context-env-vars-missing-or-malformed"
401
+ missing = [name for name in CONTEXT_ENV_VARS if name not in env_vars]
402
+ if missing:
403
+ return f"context-env-vars-missing ({', '.join(missing)})"
404
+ foreign = [name for name in env_vars if name not in CONTEXT_ENV_VARS]
405
+ if foreign:
406
+ return f"context-env-vars-foreign ({', '.join(foreign)})"
407
+ if env_vars != list(CONTEXT_ENV_VARS):
408
+ return "context-env-vars-reordered-or-duplicated"
409
+ foreign_keys = sorted(set(entry) - {"command", "env_vars", "env"})
410
+ if foreign_keys:
411
+ return f"foreign-keys ({', '.join(foreign_keys)})"
412
+ extra_env = sorted(set(env) - {PROVENANCE_ENV})
413
+ if extra_env:
414
+ return f"foreign-env-keys ({', '.join(extra_env)})"
415
+ return None
416
+
417
+
418
+ def main(argv: list[str]) -> None:
419
+ if len(argv) < 2:
420
+ die(5, "usage: codex-mcp-config.py <install config command state|uninstall state|doctor-static config command state|doctor-invocation config>")
421
+ verb = argv[1]
422
+ if verb == "install" and len(argv) == 5:
423
+ install(argv[2], argv[3], argv[4])
424
+ elif verb == "uninstall" and len(argv) == 3:
425
+ uninstall(argv[2])
426
+ elif verb == "doctor-static" and len(argv) == 5:
427
+ doctor_static(argv[2], argv[3], argv[4])
428
+ elif verb == "doctor-invocation" and len(argv) == 3:
429
+ doctor_invocation(argv[2])
430
+ else:
431
+ die(5, "usage: codex-mcp-config.py <install config command state|uninstall state|doctor-static config command state|doctor-invocation config>")
432
+
433
+
434
+ if __name__ == "__main__":
435
+ main(sys.argv)
@@ -0,0 +1,33 @@
1
+ /**
2
+ * codex-socket-path — print the Codex app-server default control-socket path for THIS
3
+ * environment, and nothing else.
4
+ *
5
+ * WHY A LEAF EXISTS FOR ONE LINE. `entwurf codex-app-server` is a bash launcher, and bash
6
+ * cannot import `resolveCodexDefaultSocketPath`. Its first version re-derived the path
7
+ * instead — `${CODEX_HOME:-$HOME/.codex}` plus a POSIX `[:space:]` trim — and a gate compared
8
+ * the two spellings over four ASCII-normal inputs, which they agreed on.
9
+ *
10
+ * They did not agree everywhere. `[측정 2026-09-16]` with `CODEX_HOME=$''` the TS leaf
11
+ * trims (JS `String.prototype.trim` strips U+FEFF) and falls back to `$HOME/.codex`, while the
12
+ * bash trim keeps the byte and yields `<BOM>/app-server-control/app-server-control.sock`. Same
13
+ * for `path.join`'s normalization of a trailing slash or a `..` segment. Every one of those is
14
+ * a managed launch starting a server at an address delivery and preflight never look at — a
15
+ * false success of exactly the kind this repo refuses.
16
+ *
17
+ * So the second spelling is gone rather than widened. There is ONE implementation of this
18
+ * address, this leaf prints it, and the launcher asks. Matching a transcription against its
19
+ * original can only ever test the inputs somebody thought of.
20
+ *
21
+ * Reads `process.env` directly: the resolver's contract is the ambient environment of whoever
22
+ * is about to launch, and an argv seam here would be a way to redirect the address.
23
+ */
24
+
25
+ import { resolveCodexDefaultSocketPath } from "../pi-extensions/lib/native-push/codex-ws-client.ts";
26
+
27
+ if (process.argv.length > 2) {
28
+ console.error("usage: codex-socket-path (no arguments; the environment is the input)");
29
+ process.exit(2);
30
+ }
31
+
32
+ // No trailing newline: the caller substitutes this straight into an address.
33
+ process.stdout.write(resolveCodexDefaultSocketPath(process.env));