@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,103 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "compaction-send-guard",
4
+ "mutants": [
5
+ {
6
+ "claim": "COMPACT-SEND-NO-MODE",
7
+ "title": "the predicate grows a mode argument so steer could be admitted while follow_up is refused",
8
+ "subject": "pi-extensions/lib/compaction-send-guard.ts",
9
+ "find": [
10
+ "export function compactionSendReject(guard: CompactionGuard, facts: CompactionSendFacts): CompactionSendReject | null {"
11
+ ],
12
+ "replace": [
13
+ "export function compactionSendReject(guard: CompactionGuard, facts: CompactionSendFacts, mode?: string): CompactionSendReject | null {"
14
+ ],
15
+ "gate": ["bash", "run.sh", "check-compaction-send-guard"],
16
+ "timeoutSeconds": 60,
17
+ "signature": "[QK:COMPACT-SEND-NO-MODE]",
18
+ "signatureSource": "scripts/check-compaction-send-guard.ts"
19
+ },
20
+ {
21
+ "claim": "COMPACT-SEND-REJECT-ARMED",
22
+ "title": "armed compacting-during-stream is treated as an ordinary turn, so auto-compact still gets sendMessage",
23
+ "subject": "pi-extensions/lib/compaction-send-guard.ts",
24
+ "find": ["\tif (guard.armed) return COMPACTION_SEND_REJECT;"],
25
+ "replace": ["\t// armed compacting-during-stream no longer rejects"],
26
+ "gate": ["bash", "run.sh", "check-compaction-send-guard"],
27
+ "timeoutSeconds": 60,
28
+ "signature": "[QK:COMPACT-SEND-REJECT-ARMED]",
29
+ "signatureSource": "scripts/check-compaction-send-guard.ts"
30
+ },
31
+ {
32
+ "claim": "COMPACT-SEND-REJECT-QUIET",
33
+ "title": "quiet unknown non-idle is admitted, so sendMessage runs in the manual-compact start race",
34
+ "subject": "pi-extensions/lib/compaction-send-guard.ts",
35
+ "find": ["\tif (!facts.hasAgentSignal) return BUSY_SEND_REJECT;"],
36
+ "replace": ["\t// quiet unknown no longer rejects"],
37
+ "gate": ["bash", "run.sh", "check-compaction-send-guard"],
38
+ "timeoutSeconds": 60,
39
+ "signature": "[QK:COMPACT-SEND-REJECT-QUIET]",
40
+ "signatureSource": "scripts/check-compaction-send-guard.ts"
41
+ },
42
+ {
43
+ "claim": "COMPACT-SEND-ADMIT-IDLE",
44
+ "title": "idle no longer recovers a missed session_compact, so a later stream stays falsely refused",
45
+ "subject": "pi-extensions/lib/compaction-send-guard.ts",
46
+ "find": [
47
+ "\tif (facts.idle) {",
48
+ "\t\t// Idle means Pi is neither in an agent run nor compacting. Also recovers a",
49
+ "\t\t// missed `session_compact` so a later ordinary stream is not stuck refused.",
50
+ "\t\tguard.armed = false;",
51
+ "\t\treturn null;",
52
+ "\t}"
53
+ ],
54
+ "replace": ["\t// idle no longer admits or disarms"],
55
+ "gate": ["bash", "run.sh", "check-compaction-send-guard"],
56
+ "timeoutSeconds": 60,
57
+ "signature": "[QK:COMPACT-SEND-ADMIT-IDLE]",
58
+ "signatureSource": "scripts/check-compaction-send-guard.ts"
59
+ },
60
+ {
61
+ "claim": "COMPACT-SEND-ADMIT-STREAM",
62
+ "title": "a live agent run is refused, so idle/일반 streaming steer/followUp is no longer preserved",
63
+ "subject": "pi-extensions/lib/compaction-send-guard.ts",
64
+ "find": ["\treturn null; // live agent run — steer/followUp unchanged"],
65
+ "replace": ["\treturn COMPACTION_SEND_REJECT; // live agent run no longer admitted"],
66
+ "gate": ["bash", "run.sh", "check-compaction-send-guard"],
67
+ "timeoutSeconds": 60,
68
+ "signature": "[QK:COMPACT-SEND-ADMIT-STREAM]",
69
+ "signatureSource": "scripts/check-compaction-send-guard.ts"
70
+ },
71
+ {
72
+ "claim": "COMPACT-SEND-WIRE",
73
+ "title": "the send path skips the leaf and still calls pi.sendMessage during compaction",
74
+ "subject": "pi-extensions/entwurf-control.ts",
75
+ "find": [
76
+ "\t\tconst compactionReject = compactionSendReject(state.compaction, {",
77
+ "\t\t\tidle: ctx.isIdle(),",
78
+ "\t\t\thasAgentSignal: ctx.signal !== undefined,",
79
+ "\t\t});",
80
+ "\t\tif (compactionReject) {",
81
+ "\t\t\trespond(false, \"send\", undefined, compactionReject);",
82
+ "\t\t\treturn;",
83
+ "\t\t}"
84
+ ],
85
+ "replace": ["\t\t// compaction send guard removed"],
86
+ "gate": ["bash", "run.sh", "check-compaction-send-guard"],
87
+ "timeoutSeconds": 60,
88
+ "signature": "[QK:COMPACT-SEND-WIRE]",
89
+ "signatureSource": "scripts/check-compaction-send-guard.ts"
90
+ },
91
+ {
92
+ "claim": "COMPACT-SEND-EVENTS",
93
+ "title": "session_before_compact no longer arms the resident guard",
94
+ "subject": "pi-extensions/entwurf-control.ts",
95
+ "find": ["\tpi.on(\"session_before_compact\", () => {", "\t\tnoteCompactionBefore(state.compaction);", "\t});"],
96
+ "replace": ["\t// session_before_compact no longer arms"],
97
+ "gate": ["bash", "run.sh", "check-compaction-send-guard"],
98
+ "timeoutSeconds": 60,
99
+ "signature": "[QK:COMPACT-SEND-EVENTS]",
100
+ "signatureSource": "scripts/check-compaction-send-guard.ts"
101
+ }
102
+ ]
103
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "entwurf-peers",
4
+ "mutants": [
5
+ {
6
+ "claim": "PEERS-OBSERVATION-BUDGET",
7
+ "title": "the peers presentation limit is accepted but the full historical store is still observed",
8
+ "subject": "pi-extensions/lib/entwurf-fact-provider.ts",
9
+ "find": [
10
+ "\t\tselectedObserve = (identity) => (observedGardenIds.has(identity.gardenId) ? observe(identity) : UNOBSERVED_PEER);"
11
+ ],
12
+ "replace": ["\t\tselectedObserve = observe;"],
13
+ "gate": ["bash", "run.sh", "check-entwurf-fact-provider"],
14
+ "timeoutSeconds": 60,
15
+ "signature": "[QK:PEERS-OBSERVATION-BUDGET]",
16
+ "signatureSource": "scripts/check-entwurf-fact-provider.ts"
17
+ }
18
+ ]
19
+ }
@@ -111,7 +111,7 @@
111
111
  "claim": "FRESHCALL-CWD-RECEIPT-REQUESTED",
112
112
  "title": "production freshCall stops assembling the requested cwd into its receipt, so the operator-visible answer silently loses the one placement fact the caller asked for",
113
113
  "subject": "pi-extensions/lib/mux-fresh-call.ts",
114
- "find": ["\t\t\t...(cwd === undefined ? {} : { cwd }),\n"],
114
+ "find": ["\t\t\t...(chosenCwd === undefined ? {} : { cwd: chosenCwd.value, cwdSource: chosenCwd.source }),\n"],
115
115
  "replace": [""],
116
116
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
117
117
  "timeoutSeconds": 120,
@@ -123,7 +123,7 @@
123
123
  "title": "one surface drops the optional cwd from its schema, so cross-repo fresh works from pi and silently cannot exist from the MCP bridge",
124
124
  "subject": "mcp/entwurf-bridge/src/index.ts",
125
125
  "find": [
126
- "\t\tcwd: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t\"Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Omit or pass \\\"\\\" to start in this agent's own cwd. Taken exactly as given — no trim, no realpath, no project-name resolution; '#' is refused (tmux format expansion). The receipt echoes what was REQUESTED, never an observation.\",\n\t\t\t),\n"
126
+ "\t\tcwd: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t\"Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Taken exactly as given — no trim, no realpath, no project-name resolution; '#' is refused (tmux format expansion). Omit or pass \\\"\\\" to start where the CALLER is: its own directory, or for a Codex caller its own record directory, because this bridge is the app-server's child and that process's directory is not the caller's. The receipt echoes the directory that was REQUESTED or the caller record it came from, never an observation of where the pane landed.\",\n\t\t\t),\n"
127
127
  ],
128
128
  "replace": [""],
129
129
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
@@ -147,7 +147,7 @@
147
147
  "title": "native pi drops the required model field from fresh-call while the composition still expects one",
148
148
  "subject": "pi-extensions/entwurf-control.ts",
149
149
  "find": [
150
- "\t\t\tmodel: Type.String({\n\t\t\t\tminLength: 1,\n\t\t\t\tmaxLength: 200,\n\t\t\t\tpattern: \"^[A-Za-z0-9][A-Za-z0-9._/:\\\\[\\\\]-]*$\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot model name (or auto).\",\n\t\t\t}),\n"
150
+ "\t\t\tmodel: Type.String({\n\t\t\t\tminLength: 1,\n\t\t\t\tmaxLength: 200,\n\t\t\t\tpattern: \"^[A-Za-z0-9][A-Za-z0-9._/:\\\\[\\\\]-]*$\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot/OMP/Codex model name.\",\n\t\t\t}),\n"
151
151
  ],
152
152
  "replace": [""],
153
153
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
@@ -160,7 +160,7 @@
160
160
  "title": "the MCP bridge reads the caller garden id straight out of env instead of its authoritative self envelope",
161
161
  "subject": "mcp/entwurf-bridge/src/index.ts",
162
162
  "find": [
163
- "\t\t\tconst self = await buildAuthoritativeSelfEnvelope();\n\t\t\tcallerGardenId = self.envelope.sessionId;"
163
+ "\t\t\tconst self = await buildAuthoritativeSelfEnvelope({ requestMeta: extra._meta });\n\t\t\tcallerGardenId = self.envelope.sessionId;"
164
164
  ],
165
165
  "replace": ["\t\t\tcallerGardenId = process.env.PI_SESSION_ID ?? null;"],
166
166
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
@@ -173,7 +173,7 @@
173
173
  "title": "MCP drops the required model field from fresh-call while the composition still expects one",
174
174
  "subject": "mcp/entwurf-bridge/src/index.ts",
175
175
  "find": [
176
- "\t\tmodel: z\n\t\t\t.string()\n\t\t\t.min(1)\n\t\t\t.max(200)\n\t\t\t// The `\\[` below is USELESS TO JS AND LOAD-BEARING TO THE HOST. zod emits this source\n\t\t\t// text verbatim as the JSON Schema `pattern`, and the host's tool-schema validator is a\n\t\t\t// Rust regex engine that rejects an unescaped `[` inside a character class as \"unclosed\n\t\t\t// character class\" — a 400 on tools/list that stops EVERY Claude session from opening\n\t\t\t// (7-M-fix). Biome's safe fix removes it; do not accept that fix. The suppression must\n\t\t\t// stay on the line DIRECTLY above the expression, or it attaches to nothing and reads\n\t\t\t// as an unused suppression while the escape goes back to being fixable.\n\t\t\t// biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above\n\t\t\t.regex(/^[A-Za-z0-9][A-Za-z0-9._/:\\[\\]-]*$/)\n\t\t\t.describe(\n\t\t\t\t\"Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot model name (or auto).\",\n\t\t\t),\n"
176
+ "\t\tmodel: z\n\t\t\t.string()\n\t\t\t.min(1)\n\t\t\t.max(200)\n\t\t\t// The `\\[` below is USELESS TO JS AND LOAD-BEARING TO THE HOST. zod emits this source\n\t\t\t// text verbatim as the JSON Schema `pattern`, and the host's tool-schema validator is a\n\t\t\t// Rust regex engine that rejects an unescaped `[` inside a character class as \"unclosed\n\t\t\t// character class\" — a 400 on tools/list that stops EVERY Claude session from opening\n\t\t\t// (7-M-fix). Biome's safe fix removes it; do not accept that fix. The suppression must\n\t\t\t// stay on the line DIRECTLY above the expression, or it attaches to nothing and reads\n\t\t\t// as an unused suppression while the escape goes back to being fixable.\n\t\t\t// biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above\n\t\t\t.regex(/^[A-Za-z0-9][A-Za-z0-9._/:\\[\\]-]*$/)\n\t\t\t.describe(\n\t\t\t\t\"Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot/OMP/Codex model name.\",\n\t\t\t),\n"
177
177
  ],
178
178
  "replace": [""],
179
179
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
@@ -312,8 +312,8 @@
312
312
  "claim": "FRESHCALL-BACKEND-SET-SURFACE-PARITY",
313
313
  "title": "a backend the composition can open is missing from the MCP surface enum — the capability exists and no external host can reach it",
314
314
  "subject": "mcp/entwurf-bridge/src/index.ts",
315
- "find": ["\t\t\t.enum([\"pi\", \"claude-code\", \"copilot\", \"omp\"])"],
316
- "replace": ["\t\t\t.enum([\"pi\", \"claude-code\", \"copilot\"])"],
315
+ "find": ["\t\t\t.enum([\"pi\", \"claude-code\", \"copilot\", \"omp\", \"codex\"])"],
316
+ "replace": ["\t\t\t.enum([\"pi\", \"claude-code\", \"copilot\", \"omp\"])"],
317
317
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
318
318
  "timeoutSeconds": 180,
319
319
  "signature": "[QK:FRESHCALL-BACKEND-SET-SURFACE-PARITY]",
@@ -432,8 +432,8 @@
432
432
  "claim": "FRESHCALL-BACKEND-SET-SURFACE-PARITY-PI",
433
433
  "title": "a backend the composition can open is missing from the native pi surface enum — an in-process pi session cannot name a capability the module already opens",
434
434
  "subject": "pi-extensions/entwurf-control.ts",
435
- "find": ["\t\t\tbackend: StringEnum([\"pi\", \"claude-code\", \"copilot\", \"omp\"], {"],
436
- "replace": ["\t\t\tbackend: StringEnum([\"pi\", \"claude-code\", \"copilot\"], {"],
435
+ "find": ["\t\t\tbackend: StringEnum([\"pi\", \"claude-code\", \"copilot\", \"omp\", \"codex\"], {"],
436
+ "replace": ["\t\t\tbackend: StringEnum([\"pi\", \"claude-code\", \"copilot\", \"omp\"], {"],
437
437
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
438
438
  "timeoutSeconds": 180,
439
439
  "signature": "[QK:FRESHCALL-BACKEND-SET-SURFACE-PARITY-PI]",
@@ -512,13 +512,96 @@
512
512
  "title": "one surface drops the optional seat from its schema, so opening a sibling in the operator's project session works from pi and silently cannot exist from the MCP bridge",
513
513
  "subject": "mcp/entwurf-bridge/src/index.ts",
514
514
  "find": [
515
- "\t\tplacement: z\n\t\t\t.object({\n\t\t\t\ttmuxSession: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.describe(\n\t\t\t\t\t\t\"EXACT name of an EXISTING session on this agent's own tmux server. Nothing is created: an absent session is refused as tmux-session-missing, and a name outside [A-Za-z0-9][A-Za-z0-9_-]* as tmux-session-name-invalid.\",\n\t\t\t\t\t),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t\"Optional project seat: open the sibling in ONE EXISTING tmux session of this agent's own server instead of the caller's session. Nothing is ever created — an absent session is a refusal, not a new session. Independent of cwd; neither is inferred from the other. The receipt echoes the REQUESTED name and reports the resolved target session id.\",\n\t\t\t),\n"
515
+ "\t\tplacement: z\n\t\t\t.object({\n\t\t\t\ttmuxSession: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.describe(\n\t\t\t\t\t\t\"EXACT name of an EXISTING session on this agent's own tmux server. Nothing is created: an absent session is refused as tmux-session-missing, and a name outside [A-Za-z0-9][A-Za-z0-9_-]* as tmux-session-name-invalid.\",\n\t\t\t\t\t),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t\"Optional expert seat override: open the sibling in ONE EXISTING tmux session of this agent's own server, and it always wins. When omitted the seat follows the CALLER, never the backend being opened: a Codex CALLER opens beside its own TUI pane, matched by thread-id in that pane's terminal title (0 or 2+ matching panes refuse, never fall back); every other caller opens in its own session. A pane title is a placement input only never an address, liveness or delivery fact. Nothing is ever created. Independent of cwd; neither is inferred from the other. The receipt reports the selected name (absent for the caller-pane rule, which observed a session rather than requesting a name), its source, and resolved target session id.\",\n\t\t\t),\n"
516
516
  ],
517
517
  "replace": [""],
518
518
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
519
519
  "timeoutSeconds": 180,
520
520
  "signature": "[QK:FRESHCALL-PLACEMENT-SURFACE-PARITY]",
521
521
  "signatureSource": "test/fresh-call-surfaces.contract.test.ts"
522
+ },
523
+ {
524
+ "claim": "FRESHCALL-CODEX-THREAD-CWD",
525
+ "title": "codex loses the `-C <dir>` token, so an attached thread opens in the APP-SERVER's directory while its pane sits somewhere else — the launch, the receipt and the pane all still look right",
526
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
527
+ "find": ["\t\t\t\t\"-C\",\n\t\t\t\tlaunchCwd,\n"],
528
+ "replace": [""],
529
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
530
+ "timeoutSeconds": 120,
531
+ "signature": "[QK:FRESHCALL-CODEX-THREAD-CWD]",
532
+ "signatureSource": "test/mux-fresh-call.test.ts"
533
+ },
534
+ {
535
+ "claim": "FRESHCALL-CWD-CALLER-ONLY",
536
+ "title": "the codex dialect reads THIS process's directory instead of the directory the launch chose, so a cross-repo or caller-record cwd places the pane while the thread opens somewhere else",
537
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
538
+ "find": ["\t\t\t\tlaunchCwd,\n"],
539
+ "replace": ["\t\t\t\tprocess.cwd(),\n"],
540
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
541
+ "timeoutSeconds": 120,
542
+ "signature": "[QK:FRESHCALL-CWD-CALLER-ONLY]",
543
+ "signatureSource": "test/mux-fresh-call.test.ts"
544
+ },
545
+ {
546
+ "claim": "FRESHCALL-CALLER-CWD-CODEX",
547
+ "title": "the composition drops the caller's record directory, so a Codex caller's omitted cwd silently falls back to the app-server's directory again — the #95 lane C defect, restored with the input still on the signature",
548
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
549
+ "find": [
550
+ "\t\t\t? { value: requestedCwd, source: \"requested\" }\n\t\t\t: callerCwd !== undefined\n\t\t\t\t? { value: callerCwd, source: \"codex-caller-record\" }\n\t\t\t\t: undefined;"
551
+ ],
552
+ "replace": ["\t\t\t? { value: requestedCwd, source: \"requested\" }\n\t\t\t: undefined;"],
553
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
554
+ "timeoutSeconds": 120,
555
+ "signature": "[QK:FRESHCALL-CALLER-CWD-CODEX]",
556
+ "signatureSource": "test/mux-fresh-call.test.ts"
557
+ },
558
+ {
559
+ "claim": "FRESHCALL-CALLER-CWD-PI-ARGV-UNCHANGED",
560
+ "title": "an EMPTY caller cwd stops meaning omit, so a surface that supplies \"\" turns into a classified value — a refusal where the pre-#95 argv simply opened",
561
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
562
+ "find": [
563
+ "\tconst callerCwd = params.callerCwd === undefined || params.callerCwd === \"\" ? undefined : params.callerCwd;"
564
+ ],
565
+ "replace": ["\tconst callerCwd = params.callerCwd;"],
566
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
567
+ "timeoutSeconds": 120,
568
+ "signature": "[QK:FRESHCALL-CALLER-CWD-PI-ARGV-UNCHANGED]",
569
+ "signatureSource": "test/mux-fresh-call.test.ts"
570
+ },
571
+ {
572
+ "claim": "FRESHCALL-CWD-TMUX-CARRIER-SOURCE",
573
+ "title": "an absent chosen directory becomes this process's, so every launch carries a `-c` and the argv four backends have had since #73 is no longer the measured one",
574
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
575
+ "find": ["\tconst cwd = chosenCwd?.value;"],
576
+ "replace": ["\tconst cwd = chosenCwd?.value ?? process.cwd();"],
577
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
578
+ "timeoutSeconds": 120,
579
+ "signature": "[QK:FRESHCALL-CWD-TMUX-CARRIER-SOURCE]",
580
+ "signatureSource": "test/mux-fresh-call.test.ts"
581
+ },
582
+ {
583
+ "claim": "FRESHCALL-CWD-RECEIPT-SOURCE",
584
+ "title": "the receipt relabels a caller-record directory as REQUESTED, telling the operator someone asked for a directory nobody named",
585
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
586
+ "find": [
587
+ "\t\t\t\t: r.cwdSource === \"codex-caller-record\"\n\t\t\t\t\t? ` cwd: ${r.cwd} (the Codex caller's own record directory, used because no cwd was requested — not an observation)\\n`\n\t\t\t\t\t: ` cwd: ${r.cwd} (requested start directory — not an observation)\\n`) +"
588
+ ],
589
+ "replace": ["\t\t\t\t: ` cwd: ${r.cwd} (requested start directory — not an observation)\\n`) +"],
590
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
591
+ "timeoutSeconds": 120,
592
+ "signature": "[QK:FRESHCALL-CWD-RECEIPT-SOURCE]",
593
+ "signatureSource": "test/mux-fresh-call.test.ts"
594
+ },
595
+ {
596
+ "claim": "FRESHCALL-CALLER-CWD-SURFACE",
597
+ "title": "the MCP surface stops handing the codex caller's record directory to the composition, so the capability exists in the leaf and is unreachable from the only surface a codex caller can reach it through",
598
+ "subject": "mcp/entwurf-bridge/src/index.ts",
599
+ "find": ["\t\t\tcallerCwd = self.codexThreadId === undefined ? undefined : self.envelope.cwd;\n"],
600
+ "replace": [""],
601
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
602
+ "timeoutSeconds": 120,
603
+ "signature": "[QK:FRESHCALL-CALLER-CWD-SURFACE]",
604
+ "signatureSource": "test/fresh-call-surfaces.contract.test.ts"
522
605
  }
523
606
  ]
524
607
  }
@@ -6,8 +6,10 @@
6
6
  "claim": "ADMISSION-FRESH-PARITY",
7
7
  "title": "a post-#82 harness admitted as a full D6 citizen while entwurf_fresh_call cannot open it — the exact state the Bundle A+B candidate shipped in, where every gate was green and only prose said the backend was unsupported. This is the defect the whole gate exists for: without it, an `unsupported` note reads as a partial-release permit",
8
8
  "subject": "pi-extensions/lib/mux-fresh-call.ts",
9
- "find": ["export const FRESH_CALL_BACKENDS = [\"pi\", \"claude-code\", \"copilot\", \"omp\"] as const;"],
10
- "replace": ["export const FRESH_CALL_BACKENDS = [\"pi\", \"claude-code\", \"copilot\"] as const;"],
9
+ "find": [
10
+ "export const FRESH_CALL_BACKENDS = [\"pi\", \"claude-code\", \"copilot\", \"omp\", \"codex\"] as const;"
11
+ ],
12
+ "replace": ["export const FRESH_CALL_BACKENDS = [\"pi\", \"claude-code\", \"copilot\", \"codex\"] as const;"],
11
13
  "gate": ["bash", "run.sh", "check-harness-admission-parity"],
12
14
  "timeoutSeconds": 300,
13
15
  "signature": "[QK:ADMISSION-FRESH-PARITY]",
@@ -117,8 +119,8 @@
117
119
  "claim": "FRESHCALL-BACKEND-SET-SURFACE-PARITY-SKILL",
118
120
  "title": "a backend added to the composition and both schema surfaces but not to the operator skill — the third public surface step 9 names, and the one that had no gate at all. The skill is what an operator reads to decide what can be opened, so a stale list makes the backend unreachable in practice exactly the way a missing enum value makes it unreachable in schema",
119
121
  "subject": ".claude/skills/entwurf-dev/SKILL.md",
120
- "find": ["backend는 정확히 `pi | claude-code | copilot | omp`이고"],
121
- "replace": ["backend는 정확히 `pi | claude-code | copilot`이고"],
122
+ "find": ["backend는 정확히 `pi | claude-code | copilot | omp | codex`이고"],
123
+ "replace": ["backend는 정확히 `pi | claude-code | copilot | omp`이고"],
122
124
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
123
125
  "timeoutSeconds": 300,
124
126
  "signature": "[QK:FRESHCALL-BACKEND-SET-SURFACE-PARITY-SKILL]",
@@ -214,6 +214,19 @@
214
214
  "timeoutSeconds": 180,
215
215
  "signature": "[QK:PI-DOCTOR-IS-RELEASE-MUST]",
216
216
  "signatureSource": "scripts/check-release-gate-outcomes.ts"
217
+ },
218
+ {
219
+ "claim": "CODEX-FIRST-ADMISSION-IS-RELEASE-MUST",
220
+ "title": "the Codex first-admission smoke stays listed but bypasses run_live_step, so a missing LIVE prerequisite returns SKIP into a branch that never classifies it and the cut reads green",
221
+ "subject": "run.sh",
222
+ "find": [
223
+ " run_live_step \"smoke-codex-fresh-live (#95: visible Pi -> visible Codex/callback/addressed v2 -> visible Pi/callback)\" gate bash \"$self\" smoke-codex-fresh-live"
224
+ ],
225
+ "replace": [" bash \"$self\" smoke-codex-fresh-live"],
226
+ "gate": ["bash", "run.sh", "check-release-gate-outcomes"],
227
+ "timeoutSeconds": 180,
228
+ "signature": "[QK:CODEX-FIRST-ADMISSION-IS-RELEASE-MUST]",
229
+ "signatureSource": "scripts/check-release-gate-outcomes.ts"
217
230
  }
218
231
  ]
219
232
  }
@@ -16,6 +16,28 @@
16
16
  "signature": "[QK:V2SURF-MCP-LOCK-DOMAIN]",
17
17
  "signatureSource": "scripts/check-entwurf-v2-surface.ts"
18
18
  },
19
+ {
20
+ "claim": "V2SEND-INBAND-REJECT-REASON",
21
+ "title": "completed control or mailbox refusal drops its receiver-provided error, leaving the sender with bare rejected again",
22
+ "subject": "pi-extensions/lib/entwurf-v2-send.ts",
23
+ "find": ["\treturn { outcome: \"rejected\", rejectReason: result.error === \"\" ? undefined : result.error };"],
24
+ "replace": ["\treturn { outcome: \"rejected\" };"],
25
+ "gate": ["bash", "run.sh", "check-entwurf-v2-send"],
26
+ "timeoutSeconds": 60,
27
+ "signature": "[QK:V2SEND-INBAND-REJECT-REASON]",
28
+ "signatureSource": "scripts/check-entwurf-v2-send.ts"
29
+ },
30
+ {
31
+ "claim": "V2PROD-INBAND-ERROR-WIRED",
32
+ "title": "the production sendOverSocket closure stops copying response.error onto the RpcSendResult, so a receiver's named in-band refusal is stranded one hop before driveSend and the caller sees a reasonless rejected",
33
+ "subject": "pi-extensions/lib/entwurf-v2-production.ts",
34
+ "find": ["\t\t\treturn { success: response.success, error: response.error };"],
35
+ "replace": ["\t\t\treturn { success: response.success };"],
36
+ "gate": ["bash", "run.sh", "check-entwurf-v2-production"],
37
+ "timeoutSeconds": 60,
38
+ "signature": "[QK:V2PROD-INBAND-ERROR-WIRED]",
39
+ "signatureSource": "scripts/check-entwurf-v2-production.ts"
40
+ },
19
41
  {
20
42
  "claim": "V2SURF-PI-MODE-SCOPE",
21
43
  "title": "pi-native mode param reverts to \"Delivery mode for a live send\" (false for native-push)",
@@ -127,12 +149,64 @@
127
149
  "\t\"CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id \" +"
128
150
  ],
129
151
  "replace": [
130
- "\t\"CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id \" +\n\t\t\"PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING \" +\n\t\t\"PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING \" +"
152
+ "\t\"CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id \" +\n\t\t\"PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING \" +\n\t\t\"PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING \" +\n\t\t\"PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING \" +"
131
153
  ],
132
154
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
133
155
  "timeoutSeconds": 60,
134
156
  "signature": "[QK:V2SURF-DESC-FITS-HOST-CAP]",
135
157
  "signatureSource": "scripts/check-entwurf-v2-surface.ts"
158
+ },
159
+ {
160
+ "claim": "NATIVE-PUSH-SENDER-ENVELOPE",
161
+ "title": "native-push direct injection reverts to the raw plan message, so a directly-injected citizen receives a body with no sender envelope and cannot name who called it",
162
+ "subject": "pi-extensions/lib/entwurf-v2-native-push.ts",
163
+ "find": [
164
+ "\t\tconst content = sender ? formatMetaMailboxBody(sender, plan.message, plan.wantsReply) : plan.message;"
165
+ ],
166
+ "replace": ["\t\tconst content = plan.message;"],
167
+ "gate": ["bash", "run.sh", "check-entwurf-v2-production"],
168
+ "timeoutSeconds": 120,
169
+ "signature": "[QK:NATIVE-PUSH-SENDER-ENVELOPE]",
170
+ "signatureSource": "scripts/check-entwurf-v2-production.ts"
171
+ },
172
+ {
173
+ "claim": "NATIVE-PUSH-SENDER-PROVIDER-WIRING",
174
+ "title": "the production factory stops handing its one senderProvider to the native-push hand, so the render is reachable but never fed and every direct injection silently falls back to raw",
175
+ "subject": "pi-extensions/lib/entwurf-v2-production.ts",
176
+ "find": [
177
+ "\t\tsendNativePush: makeNativePushSend({\n\t\t\tresolveAdapter: io.resolveNativePushAdapter,\n\t\t\tsenderProvider: opts.senderProvider,\n\t\t}),"
178
+ ],
179
+ "replace": ["\t\tsendNativePush: makeNativePushSend({ resolveAdapter: io.resolveNativePushAdapter }),"],
180
+ "gate": ["bash", "run.sh", "check-entwurf-v2-production"],
181
+ "timeoutSeconds": 120,
182
+ "signature": "[QK:NATIVE-PUSH-SENDER-PROVIDER-WIRING]",
183
+ "signatureSource": "scripts/check-entwurf-v2-production.ts"
184
+ },
185
+ {
186
+ "claim": "NATIVE-PUSH-SENDER-BEFORE-ADAPTER",
187
+ "title": "the send hand resolves the native-push adapter before the sender envelope, so a failing envelope source fails with the target's adapter already in hand instead of before anything was touched",
188
+ "subject": "pi-extensions/lib/entwurf-v2-native-push.ts",
189
+ "find": [
190
+ "\t\tconst sender = senderProvider?.();\n\t\tconst content = sender ? formatMetaMailboxBody(sender, plan.message, plan.wantsReply) : plan.message;\n\t\tconst adapter = resolveAdapter(plan.backend);"
191
+ ],
192
+ "replace": [
193
+ "\t\tconst adapter = resolveAdapter(plan.backend);\n\t\tconst sender = senderProvider?.();\n\t\tconst content = sender ? formatMetaMailboxBody(sender, plan.message, plan.wantsReply) : plan.message;"
194
+ ],
195
+ "gate": ["bash", "run.sh", "check-entwurf-v2-production"],
196
+ "timeoutSeconds": 120,
197
+ "signature": "[QK:NATIVE-PUSH-SENDER-BEFORE-ADAPTER]",
198
+ "signatureSource": "scripts/check-entwurf-v2-production.ts"
199
+ },
200
+ {
201
+ "claim": "NATIVE-PUSH-RETRY-BYTE-IDENTITY",
202
+ "title": "the one permitted retry decorates the replayed body, so the second attempt delivers bytes that disagree with the first attempt the target may already have accepted",
203
+ "subject": "pi-extensions/lib/entwurf-v2-native-push.ts",
204
+ "find": ["\t\tawait adapter.send(reprobe.route, nativeSessionId, content);"],
205
+ "replace": ["\t\tawait adapter.send(reprobe.route, nativeSessionId, `${content} (retry)`);"],
206
+ "gate": ["bash", "run.sh", "check-entwurf-v2-native-push"],
207
+ "timeoutSeconds": 120,
208
+ "signature": "[QK:NATIVE-PUSH-RETRY-BYTE-IDENTITY]",
209
+ "signatureSource": "scripts/check-entwurf-v2-native-push.ts"
136
210
  }
137
211
  ]
138
212
  }
@@ -551,7 +551,8 @@ send-message-v2` spins a fresh thread, not the live one.
551
551
 
552
552
  Copilot is a GitHub **harness** (issues, PR, CI, model `auto`, mode `autopilot`,
553
553
  remote/delegate), not a second GPT provider next to pi. This makes a native lane
554
- product-distinct from the declined Codex lane: Copilot can own GitHub work while
554
+ product-distinct from Codex's separate native-push lane (#95, unreleased candidate):
555
+ Copilot can own GitHub work while
555
556
  implementation checkpoints arrive from another garden id. That split is operator
556
557
  etiquette plus dispatch, not a substrate role system.
557
558