@junghanacs/entwurf 0.20.1 → 0.21.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 (96) hide show
  1. package/AGENTS.md +85 -190
  2. package/BASELINE.md +4 -3
  3. package/CHANGELOG.md +195 -14
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +202 -60
  6. package/README.md +49 -22
  7. package/VERIFY.md +37 -7
  8. package/docs/acp-backend-rail.md +0 -1
  9. package/docs/external-mcp-host.md +54 -33
  10. package/docs/setup-clean-host.md +110 -17
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +101 -109
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +326 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +7 -4
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +20 -11
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +227 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +50 -15
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +10 -7
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +403 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +4 -4
  25. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-codex.js +325 -0
  26. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +6 -1
  27. package/mcp/entwurf-bridge/src/index.ts +115 -111
  28. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  29. package/package.json +13 -4
  30. package/pi-extensions/entwurf-control.ts +71 -19
  31. package/pi-extensions/lib/codex-fresh-preflight.ts +363 -0
  32. package/pi-extensions/lib/compaction-send-guard.ts +80 -0
  33. package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
  34. package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
  35. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
  36. package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
  37. package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
  38. package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
  39. package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
  40. package/pi-extensions/lib/meta-sender-identity.ts +305 -0
  41. package/pi-extensions/lib/mux-fresh-call.ts +64 -19
  42. package/pi-extensions/lib/native-push/adapter.ts +21 -24
  43. package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
  44. package/pi-extensions/lib/native-push/register.ts +7 -9
  45. package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
  46. package/run.sh +218 -28
  47. package/scripts/check-agy-sender-identity.ts +1 -1
  48. package/scripts/check-codex-birth-hook.ts +264 -0
  49. package/scripts/check-codex-bridge-identity.ts +179 -0
  50. package/scripts/check-codex-native-push.ts +386 -0
  51. package/scripts/check-codex-sender-identity.ts +495 -0
  52. package/scripts/check-compaction-send-guard.ts +130 -0
  53. package/scripts/check-copilot-receive-arm.ts +4 -1
  54. package/scripts/check-entwurf-fact-provider.ts +38 -0
  55. package/scripts/check-entwurf-peers-surface.ts +13 -1
  56. package/scripts/check-entwurf-self-address.ts +15 -16
  57. package/scripts/check-entwurf-v2-contract.ts +4 -3
  58. package/scripts/check-entwurf-v2-decider.ts +7 -5
  59. package/scripts/check-entwurf-v2-native-push.ts +35 -7
  60. package/scripts/check-entwurf-v2-production.ts +203 -11
  61. package/scripts/check-entwurf-v2-runner.ts +1 -1
  62. package/scripts/check-entwurf-v2-surface.ts +1 -1
  63. package/scripts/check-gate-qualification.ts +6 -3
  64. package/scripts/check-harness-admission-parity.ts +0 -1
  65. package/scripts/check-mux-launch-tmux.ts +47 -2
  66. package/scripts/check-native-push-adapter.ts +20 -16
  67. package/scripts/check-native-push-register.ts +5 -1
  68. package/scripts/check-release-gate-outcomes.ts +47 -1
  69. package/scripts/check-setup-qualification.sh +3 -1
  70. package/scripts/codex-birth-doctor.sh +276 -0
  71. package/scripts/codex-birth-install.sh +414 -0
  72. package/scripts/codex-birth-uninstall.sh +170 -0
  73. package/scripts/codex-mcp-config.py +435 -0
  74. package/scripts/codex-statusline-config.py +434 -0
  75. package/scripts/codex_toml_io.py +532 -0
  76. package/scripts/lib/codex-fresh-live-protocol.ts +113 -0
  77. package/scripts/lib/codex-fresh-source-receipts.ts +399 -0
  78. package/scripts/lib/launch-receipt-windows.ts +46 -0
  79. package/scripts/lib/tmux-coordinate-row.ts +58 -0
  80. package/scripts/meta-bridge-fresh-cut.ts +6 -1
  81. package/scripts/mutants/codex-native.json +838 -0
  82. package/scripts/mutants/compaction-send-guard.json +103 -0
  83. package/scripts/mutants/entwurf-peers.json +19 -0
  84. package/scripts/mutants/mux-fresh-call.json +19 -8
  85. package/scripts/mutants/omp-fresh.json +6 -4
  86. package/scripts/mutants/release-gate.json +13 -0
  87. package/scripts/mutants/v2-surface.json +53 -1
  88. package/scripts/raw-async-delivery/README.md +2 -1
  89. package/scripts/raw-codex-measure/README.md +114 -46
  90. package/scripts/smoke-agy-native-push-live.ts +3 -1
  91. package/scripts/smoke-codex-birth.sh +347 -0
  92. package/scripts/smoke-codex-config-state.sh +511 -0
  93. package/scripts/smoke-codex-fresh-live.ts +1186 -0
  94. package/scripts/smoke-codex-native-push-live.ts +75 -0
  95. package/scripts/smoke-setup-verdict.sh +123 -10
  96. package/scripts/tsconfig.json +1 -0
@@ -65,11 +65,11 @@ import * as process from "node:process";
65
65
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
66
66
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
67
67
  import { z } from "zod";
68
-
68
+ import { codexFreshPreflight } from "../../../pi-extensions/lib/codex-fresh-preflight.ts";
69
69
  import { controlSocketPathIn, defaultControlSocketDir } from "../../../pi-extensions/lib/control-socket-path.js";
70
70
  import { resolveMailboxReceiverFacts } from "../../../pi-extensions/lib/entwurf-deliverability.ts";
71
71
  import { listEntwurfFacts } from "../../../pi-extensions/lib/entwurf-fact-provider.ts";
72
- import { renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-render.ts";
72
+ import { ENTWURF_PEERS_RENDER_LIMIT, renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-render.ts";
73
73
  import { computeSelfAddressability, type MetaDeliveryDomain } from "../../../pi-extensions/lib/entwurf-self-address.ts";
74
74
  import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.ts";
75
75
  import { resolveMailboxWakeModeCapability } from "../../../pi-extensions/lib/entwurf-v2-decider.ts";
@@ -81,8 +81,12 @@ import {
81
81
  visibleResume,
82
82
  } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.ts";
83
83
  import {
84
+ type CodexRequestSender,
84
85
  probeNativeSenderAlive,
86
+ reconcileSenderIdentityClaims,
87
+ resolveCodexRequestSenderIdentity,
85
88
  resolveTrustedMetaSenderIdentity,
89
+ type TrustedMetaSender,
86
90
  } from "../../../pi-extensions/lib/meta-sender-identity.ts";
87
91
  import {
88
92
  applyOmpBridgeChildRootPolicy,
@@ -157,11 +161,11 @@ const server = new McpServer({ name: "entwurf-bridge", version: "0.1.0" });
157
161
 
158
162
  // Transparency envelope.
159
163
  //
160
- // Record-backed pi and trusted native-marker senders carry a structured envelope
161
- // so the receiver renders WHO (agentId, sessionId), FROM WHERE (cwd), and WHEN
162
- // (timestamp UTC, displayed in KST). `entwurf_self` is identity-required: pi's env
163
- // is a child carrier for the garden id established by record birth; a native sender
164
- // marker is accepted only through its backing record. Plain anonymous external hosts
164
+ // Record-backed pi, pid-marker native senders, and request-scoped Codex senders carry a
165
+ // structured envelope so the receiver renders WHO (agentId, sessionId), FROM WHERE (cwd), and
166
+ // WHEN (timestamp UTC, displayed in KST). `entwurf_self` is identity-required: pi's env is a
167
+ // child carrier for the garden id established by record birth; a native sender marker or Codex
168
+ // request selector is accepted only through its backing record. Plain anonymous external hosts
165
169
  // fail. #50 C4: v2 delivery is identity-REQUIRED by default — "if we don't know
166
170
  // who sent it, we don't send it" holds on every install surface, not only where
167
171
  // an installer remembered to set a flag. The ONE documented escape hatch is
@@ -174,11 +178,11 @@ class EntwurfEnvelopeWiringError extends Error {
174
178
  constructor(missing: string[]) {
175
179
  super(
176
180
  `entwurf sender envelope wiring incomplete — missing env: ${missing.join(", ")}, ` +
177
- "and no trusted meta-sender marker was found. This MCP child should either inherit " +
178
- "PI_SESSION_ID + PI_AGENT_ID (from an entwurf-control pi session), " +
179
- "or run inside a garden-native meta-session whose own native hook wrote a live " +
180
- "sender marker (Claude Code writes it from SessionStart, Antigravity from PreInvocation). " +
181
- "entwurf_self is only callable when one of those authoritative identity paths is present.",
181
+ "and no record-backed native sender claim was found. This MCP child should inherit " +
182
+ "PI_SESSION_ID + PI_AGENT_ID from an entwurf-control pi session, carry a live sender marker " +
183
+ "written by its native hook, or be the managed Codex MCP child whose current tool request " +
184
+ "names a thread backed by a Codex citizen record. entwurf_self is callable only while one of " +
185
+ "those authoritative identity paths is present.",
182
186
  );
183
187
  }
184
188
  }
@@ -192,21 +196,19 @@ interface SenderEnvelope {
192
196
  replyable?: boolean;
193
197
  }
194
198
 
195
- // #50 C4: anonymous sends are refused BY DEFAULT — a send with no pi-session
196
- // identity AND no trusted meta-sender marker does not go out as anonymous
197
- // external-mcp unless the operator explicitly wired the escape hatch.
199
+ // #50 C4: anonymous sends are refused BY DEFAULT — a send with no pi-session identity,
200
+ // trusted pid marker, or request-scoped Codex record does not go out as anonymous external-mcp
201
+ // unless the operator explicitly wired the escape hatch.
198
202
  // "If we don't know who sent it, we don't send it."
199
203
  class EntwurfSenderIdentityError extends Error {
200
204
  constructor() {
201
205
  super(
202
206
  "entwurf-bridge refused: no authoritative sender identity. Anonymous external sends are " +
203
- "refused by default, and no pi-session env (PI_SESSION_ID + PI_AGENT_ID) or live meta-sender " +
204
- "marker was found for this process. Each native backend writes that marker from its OWN hook, keyed " +
205
- "by the native host's parent pid + start-time (Claude Code from SessionStart, Antigravity from " +
206
- "PreInvocation) open this session through the installed meta-bridge so your garden id is " +
207
- "registered, then retry. A deliberately-anonymous external MCP host may set " +
208
- "ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER=1 (explicit operator wiring; the send is then marked " +
209
- "external/non-replyable).",
207
+ "refused by default, and this request has no complete pi-session env, live record-backed " +
208
+ "sender marker, or managed Codex thread selector backed by a Codex citizen record. Open the " +
209
+ "session through its installed birth/MCP integration, then retry. A deliberately-anonymous " +
210
+ "external MCP host may set ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER=1 (explicit operator wiring; " +
211
+ "the send is then marked external/non-replyable).",
210
212
  );
211
213
  }
212
214
  }
@@ -254,41 +256,15 @@ interface AuthoritativeSelf {
254
256
  metaDeliveryDomain?: MetaDeliveryDomain;
255
257
  }
256
258
 
257
- async function buildTrustedMetaSenderEnvelope(cwd: string = process.cwd()): Promise<AuthoritativeSelf | null> {
258
- // No pi-session identity. Try the meta-sender marker: a native backend that minted a
259
- // garden-id from its own hook (Claude SessionStart / agy PreInvocation). The marker is
260
- // keyed by the shared parent pid — this MCP child's process.ppid IS the native host the
261
- // hook ran under (NOT cwd inference). A trusted marker promotes this process from
262
- // anonymous external-mcp to a meta-session sender addressed by its garden-id.
263
- const trusted = resolveTrustedMetaSenderIdentity({
264
- markerPath: process.env.ENTWURF_META_SENDER_MARKER?.trim() || undefined,
265
- });
266
- if (!trusted) return null;
267
- const { marker, identity } = trusted;
268
-
269
- // Identity is trusted — but `replyable` is a SEPARATE fact, and WHICH fact depends on the
270
- // rail a reply would ride (보정①). THREE values, not a native-push-or-self-fetch binary:
271
- // native-push ← nativePushSupported(backend). NOT wakeMode: `direct-inject` also covers
272
- // codex/pi, which have no native-push adapter.
273
- // self-fetch ← resolveMailboxWakeModeCapability (the decider's mailbox seam — one owner
274
- // with dispatch). A new hardcoded backend list would drift the moment the registry
275
- // admits another self-fetch citizen.
276
- // none ← neither. omp today: no mailbox drain, no native-push adapter. Rendering
277
- // this as self-fetch printed a mailboxPath nothing drains.
278
- // self-fetch (claude-code/copilot): can this citizen's own inbox wake? → the SHARED
279
- // receiver composition `resolveMailboxReceiverFacts`, the same one the v2 dispatch seam
280
- // uses, so a citizen's self-reported replyability can never disagree with what dispatch
281
- // decides about it. It reads the presence marker (a dead/reused owner already folds to
282
- // null) AND, where the watch owner is the sender-marker process, the #101 join that says
283
- // the owner is still serving THIS garden rather than one it switched away from.
284
- // native-push (antigravity): there is no inbox and no watch. A reply is injected into a
285
- // live app-server conversation, so only an adapter probe can answer. Composing the
286
- // receiver atom here would demand `watchArmed` from a backend that never arms one, and
287
- // every agy citizen would report replyable:false forever.
288
- // Either way an inactive/unreachable citizen STILL returns its identity (who-sent must
289
- // survive; degrading to null would erase the sender) — only with replyable:false.
290
- // The rail, named ONCE and reused for both the predicate and the caller's rendering —
291
- // so entwurf_self can never re-derive it differently from what decided `replyable`.
259
+ interface SenderRequestContext {
260
+ requestMeta?: unknown;
261
+ }
262
+
263
+ async function buildMetaSenderEnvelope(
264
+ identity: TrustedMetaSender["identity"],
265
+ cwd: string,
266
+ trustedMarker?: TrustedMetaSender,
267
+ ): Promise<AuthoritativeSelf> {
292
268
  const metaDeliveryDomain: MetaDeliveryDomain = nativePushSupported(identity.backend)
293
269
  ? "native-push"
294
270
  : resolveMailboxWakeModeCapability(identity)
@@ -304,6 +280,11 @@ async function buildTrustedMetaSenderEnvelope(cwd: string = process.cwd()): Prom
304
280
  }
305
281
  : metaDeliveryDomain === "self-fetch"
306
282
  ? (() => {
283
+ if (!trustedMarker) {
284
+ throw new Error(
285
+ `entwurf-bridge: ${identity.backend} resolved as self-fetch without the sender marker its receiver join requires`,
286
+ );
287
+ }
307
288
  const receiver = resolveMailboxReceiverFacts(identity, {
308
289
  readReceiverMarker: (gardenId: string) => readMetaReceiverMarker({ gardenId }),
309
290
  readSenderMarker: (backend: string, ownerPid: number) =>
@@ -323,12 +304,11 @@ async function buildTrustedMetaSenderEnvelope(cwd: string = process.cwd()): Prom
323
304
  recordBacked: true,
324
305
  };
325
306
  const self = computeSelfAddressability(facts);
326
-
327
307
  return {
328
308
  envelope: {
329
309
  sessionId: identity.gardenId,
330
310
  agentId: `meta-session/${identity.backend}`,
331
- cwd: marker.cwd || cwd,
311
+ cwd,
332
312
  timestamp: new Date().toISOString(),
333
313
  origin: "meta-session",
334
314
  replyable: self.replyable,
@@ -337,17 +317,44 @@ async function buildTrustedMetaSenderEnvelope(cwd: string = process.cwd()): Prom
337
317
  };
338
318
  }
339
319
 
340
- // async only for the native-push branch's adapter probe: a pi sender and a claude-code
341
- // sender still resolve from files alone, so their cost is unchanged.
342
- async function buildAuthoritativeSelfEnvelope(): Promise<AuthoritativeSelf> {
320
+ async function resolveAuthoritativeSender(
321
+ context: SenderRequestContext,
322
+ cwd: string = process.cwd(),
323
+ ): Promise<AuthoritativeSelf | null> {
343
324
  const sessionId = process.env.PI_SESSION_ID?.trim();
344
325
  const agentId = process.env.PI_AGENT_ID?.trim();
345
- const cwd = process.cwd();
346
- if (sessionId && agentId && cwd) return { envelope: buildStrictPiSenderEnvelope() };
347
-
348
- const meta = await buildTrustedMetaSenderEnvelope(cwd);
349
- if (meta) return meta;
326
+ const pi = sessionId && agentId && cwd ? buildStrictPiSenderEnvelope() : null;
327
+ const marker = resolveTrustedMetaSenderIdentity({
328
+ markerPath: process.env.ENTWURF_META_SENDER_MARKER?.trim() || undefined,
329
+ });
330
+ const codex: CodexRequestSender | null = resolveCodexRequestSenderIdentity({
331
+ provenance: process.env.ENTWURF_BRIDGE_NATIVE_HOST,
332
+ clientInfo: server.server.getClientVersion(),
333
+ requestMeta: context.requestMeta,
334
+ });
335
+ const selected = reconcileSenderIdentityClaims([
336
+ ...(pi ? [{ rail: "pi-session" as const, id: pi.sessionId }] : []),
337
+ ...(marker ? [{ rail: "meta-sender-marker" as const, id: marker.identity.gardenId }] : []),
338
+ ...(codex ? [{ rail: "codex-request" as const, id: codex.identity.gardenId }] : []),
339
+ ]);
340
+ if (!selected) return null;
341
+ if (codex && codex.identity.gardenId === selected.id) {
342
+ return buildMetaSenderEnvelope(codex.identity, codex.identity.cwd);
343
+ }
344
+ if (marker && marker.identity.gardenId === selected.id) {
345
+ return buildMetaSenderEnvelope(marker.identity, marker.marker.cwd || cwd, marker);
346
+ }
347
+ if (pi && pi.sessionId === selected.id) return { envelope: pi };
348
+ throw new Error(`entwurf-bridge: reconciled sender ${selected.id} has no matching identity source`);
349
+ }
350
350
 
351
+ // Async only when the selected sender rides native-push: replyability is the adapter's live fact.
352
+ async function buildAuthoritativeSelfEnvelope(context: SenderRequestContext = {}): Promise<AuthoritativeSelf> {
353
+ const resolved = await resolveAuthoritativeSender(context);
354
+ if (resolved) return resolved;
355
+ const sessionId = process.env.PI_SESSION_ID?.trim();
356
+ const agentId = process.env.PI_AGENT_ID?.trim();
357
+ const cwd = process.cwd();
351
358
  const missing: string[] = [];
352
359
  if (!sessionId) missing.push("PI_SESSION_ID");
353
360
  if (!agentId) missing.push("PI_AGENT_ID");
@@ -355,18 +362,10 @@ async function buildAuthoritativeSelfEnvelope(): Promise<AuthoritativeSelf> {
355
362
  throw new EntwurfEnvelopeWiringError(missing);
356
363
  }
357
364
 
358
- async function buildSendSenderEnvelope(): Promise<SenderEnvelope> {
359
- const sessionId = process.env.PI_SESSION_ID?.trim();
360
- const agentId = process.env.PI_AGENT_ID?.trim();
365
+ async function buildSendSenderEnvelope(context: SenderRequestContext = {}): Promise<SenderEnvelope> {
366
+ const resolved = await resolveAuthoritativeSender(context);
367
+ if (resolved) return resolved.envelope;
361
368
  const cwd = process.cwd();
362
- if (sessionId && agentId && cwd) return buildStrictPiSenderEnvelope();
363
-
364
- const meta = await buildTrustedMetaSenderEnvelope(cwd);
365
- // Delivery takes the WIRE envelope only — the rail axis is rendering-local.
366
- if (meta) return meta.envelope;
367
-
368
- // No marker. #50 C4: anonymous external is refused UNLESS the operator wired the
369
- // explicit escape hatch — identity-required is the default, not an install flag.
370
369
  if (process.env.ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER !== "1") {
371
370
  throw new EntwurfSenderIdentityError();
372
371
  }
@@ -421,12 +420,11 @@ server.tool(
421
420
  "outcome (delivered / rejected / delivered-but-lock-dirty). EXISTING targets only; discover with " +
422
421
  "entwurf_peers. A peer entwurf_peers shows as liveness=alive → fire-and-forget. A " +
423
422
  "citizen with NO socket liveness (liveness=unsupported) is ALSO fire-and-forget — unsupported means only " +
424
- '"no control-socket probe" and the decider picks its own rail: a self-fetch backend (e.g. Claude Code) ' +
425
- "gets the mailbox, a native-push backend (e.g. Antigravity) gets direct injection and has NO mailbox at " +
426
- "all. THERE IS A THIRD RESULT: the mailbox delivers only to a DELIVERABLE citizen, so a terminated " +
427
- "session, or a backend with no adapter here (e.g. codex), is mailbox-undeliverable, not queued for an " +
428
- "inbox nobody drains. The native-push probe is 3-valued: alive → injected; dead → " +
429
- "native-push-target-dead; indeterminate → native-push-probe-indeterminate (unestablished ≠ gone). " +
423
+ '"no control-socket probe." The decider resolves the actual rail at dispatch time: a self-fetch ' +
424
+ "mailbox only while deliverable, native-push direct injection only while its adapter probe is alive, " +
425
+ "or a THIRD RESULT: rejection when neither holds. An inactive self-fetch citizen is mailbox-undeliverable; " +
426
+ "native-push has NO mailbox: dead native-push-target-dead; indeterminate " +
427
+ "native-push-probe-indeterminate. " +
430
428
  "DORMANT IS UNREACHABLE: a socket-domain citizen that is not running gets dormant-fire-forget-unsupported " +
431
429
  "— same receiver rule as the mailbox, no active drainer means no delivery. " +
432
430
  "The intent that used to answer there, owned-outcome, resumed it by launching a hidden background child " +
@@ -466,11 +464,11 @@ server.tool(
466
464
  ),
467
465
  wants_reply: z.boolean().optional().describe("Human-conversation reply hint (default false)"),
468
466
  },
469
- async ({ target, intent, message, mode, wants_reply }) => {
467
+ async ({ target, intent, message, mode, wants_reply }, extra) => {
470
468
  try {
471
469
  // Resolved ONCE so the dispatch-moment timestamp is fixed and the control RPC sender
472
470
  // + the mailbox body sender share one envelope. No replyability gate (see above).
473
- const sender = await buildSendSenderEnvelope();
471
+ const sender = await buildSendSenderEnvelope({ requestMeta: extra._meta });
474
472
  const rendered = await runAndRenderEntwurfV2FromSurface(
475
473
  { target, intent, message, mode, wants_reply },
476
474
  // No trust-preflight inputs are passed, and none exist to pass: the preflight on this
@@ -493,17 +491,16 @@ server.tool(
493
491
  "degrading it to nothing would erase who-sent. Use to confirm WHO you " +
494
492
  "are (agentId, sessionId), FROM WHERE (cwd), and WHEN this snapshot was taken. " +
495
493
  "Works for pi sessions (PI_SESSION_ID / PI_AGENT_ID) and for garden-native meta-sessions, whose " +
496
- "garden id comes from a trusted sender marker their OWN native hook wrote (Claude Code from " +
497
- "SessionStart, Antigravity from PreInvocation). For a meta-session it also reports WHICH rail a " +
498
- "reply rides, because that differs by backend: a self-fetch citizen (Claude Code) has a drainable " +
499
- "mailbox and its path is shown, while a native-push citizen (Antigravity) has NO mailbox at all — " +
500
- "a reply is injected straight into its live conversation. Do not expect a mailbox just because " +
501
- "origin is meta-session. Throws for plain anonymous external MCP hosts because they have no " +
502
- "authoritative reply address.",
494
+ "garden id comes from the matching record plus either a trusted native process marker or Codex's " +
495
+ "request-scoped thread selector. For a meta-session it also reports WHICH rail a reply rides, " +
496
+ "because that differs by backend: a self-fetch citizen has a drainable mailbox, while a " +
497
+ "native-push citizen has NO mailbox at all — a reply is injected straight into its live native " +
498
+ "conversation/thread. Do not expect a mailbox just because origin is meta-session. Throws for " +
499
+ "plain anonymous external MCP hosts because they have no authoritative reply address.",
503
500
  {},
504
- async () => {
501
+ async (_args, request) => {
505
502
  try {
506
- const self = await buildAuthoritativeSelfEnvelope();
503
+ const self = await buildAuthoritativeSelfEnvelope({ requestMeta: request._meta });
507
504
  const sender = self.envelope;
508
505
  const kst = formatKstTimestamp(sender.timestamp);
509
506
  const extra: Record<string, string> = {};
@@ -598,6 +595,9 @@ server.tool(
598
595
  readRecord: makeStoreRecordReader(sessionsDir),
599
596
  // Socket axis: the same dir dispatch uses (grammar SSOT), scan-internal only.
600
597
  socket: { dir: ENTWURF_DIR },
598
+ // #112: observe only rows this human surface can render. Full store facts,
599
+ // diagnostics, liveness, and machine payload remain intact.
600
+ observationLimit: ENTWURF_PEERS_RENDER_LIMIT,
601
601
  });
602
602
  const { text } = renderEntwurfPeers(result);
603
603
  return textOk(text);
@@ -707,30 +707,30 @@ server.tool(
707
707
  // against that answer would call home to a garden id nobody holds.
708
708
  server.tool(
709
709
  "entwurf_fresh_call",
710
- "Open ONE fresh visible sibling in the operator's tmux and hand it a first task. Four fixed " +
711
- "backends only: pi, claude-code, copilot, omp. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
710
+ "Open ONE fresh visible sibling in the operator's tmux and hand it a first task. Five fixed " +
711
+ "backends only: pi, claude-code, copilot, omp, codex. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
712
712
  "sender envelope of that callback is its garden id — that is how you learn the address of something that " +
713
713
  "did not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing " +
714
714
  "else: it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls " +
715
715
  "for the callback; if it never arrives the window is visible and can be read directly. For EXISTING " +
716
716
  "citizens use entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and " +
717
717
  "is passed to the chosen runtime CLI (`provider/model` for pi; model id/alias for Claude Code; a model name " +
718
- "or `auto` for copilot; a fuzzy model pattern for omp). A copilot launch goes through entwurf's own managed " +
719
- "invocation and is refused BEFORE any window opens if this host lacks the Copilot birth, MCP, receiver or " +
720
- "visible-footer units; an omp launch is refused the same way if this host lacks the OMP birth, MCP, receiver or " +
721
- "visible-status units, or if omp's tools.xdev is not false (the vendor default hides MCP tool schemas from the " +
722
- "prompt, so the sibling could not call you back at all). An optional " +
718
+ "or `auto` for copilot; a fuzzy model pattern for omp or codex). Copilot, omp, and codex are refused BEFORE " +
719
+ "any window opens when their required birth, MCP, receive/delivery, or visible-identity units are absent. " +
720
+ "Codex additionally requires the operator-owned default app-server socket; entwurf never starts or supervises it. " +
721
+ "An optional " +
723
722
  "cwd starts the sibling in ONE literal absolute existing directory (cross-repo fresh) — never pick resume " +
724
723
  "for a dormant record's cwd; resume is continuity-only. Omitted/empty cwd means the caller's own directory. " +
725
- "An optional placement.tmuxSession opens it in ONE EXISTING session of this agent's own tmux server; an " +
726
- "absent SESSION is tmux-session-missing and NOTHING is created. Omit placement for the caller's own session. " +
724
+ "An optional placement.tmuxSession is an expert override naming ONE EXISTING session on this agent's own tmux server. " +
725
+ "When omitted, Codex targets the exact existing `codex` home session; other backends target the caller's session. " +
726
+ "A missing named/home session is tmux-session-missing and NOTHING is created. " +
727
727
  "There are no arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to " +
728
728
  "same-user processes on this host. Requires that this agent itself runs " +
729
729
  "inside tmux: without a pane anchor there is no session to open a sibling beside.",
730
730
  {
731
731
  backend: z
732
- .enum(["pi", "claude-code", "copilot", "omp"])
733
- .describe("Which fixed runtime to open. Only these four; there is no arbitrary command."),
732
+ .enum(["pi", "claude-code", "copilot", "omp", "codex"])
733
+ .describe("Which fixed runtime to open. Only these five; there is no arbitrary command."),
734
734
  model: z
735
735
  .string()
736
736
  .min(1)
@@ -745,7 +745,7 @@ server.tool(
745
745
  // biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above
746
746
  .regex(/^[A-Za-z0-9][A-Za-z0-9._/:\[\]-]*$/)
747
747
  .describe(
748
- "Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot model name (or auto).",
748
+ "Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot/OMP/Codex model name.",
749
749
  ),
750
750
  task: z
751
751
  .string()
@@ -770,13 +770,13 @@ server.tool(
770
770
  })
771
771
  .optional()
772
772
  .describe(
773
- "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.",
773
+ "Optional expert seat override: open the sibling in ONE EXISTING tmux session of this agent's own server. When omitted, Codex selects the exact existing `codex` home session; other backends use the caller's session. Nothing is ever created. Independent of cwd; neither is inferred from the other. The receipt reports the selected name, its source, and resolved target session id.",
774
774
  ),
775
775
  },
776
- async ({ backend, model, task, cwd, placement }) => {
776
+ async ({ backend, model, task, cwd, placement }, extra) => {
777
777
  let callerGardenId: string | null = null;
778
778
  try {
779
- const self = await buildAuthoritativeSelfEnvelope();
779
+ const self = await buildAuthoritativeSelfEnvelope({ requestMeta: extra._meta });
780
780
  callerGardenId = self.envelope.sessionId;
781
781
  } catch (err) {
782
782
  // ONE error is a legitimate answer here: this host has no authoritative identity at all
@@ -790,7 +790,11 @@ server.tool(
790
790
  callerGardenId = null;
791
791
  }
792
792
  try {
793
- const rendered = renderFreshCall(freshCall({ backend, model, task, cwd, placement, callerGardenId }));
793
+ const missing = backend === "codex" ? await codexFreshPreflight(process.env) : null;
794
+ const result = missing
795
+ ? ({ ok: false, reason: missing } as const)
796
+ : freshCall({ backend, model, task, cwd, placement, callerGardenId });
797
+ const rendered = renderFreshCall(result);
794
798
  return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
795
799
  } catch (err) {
796
800
  return textErr(`entwurf_fresh_call error: ${err instanceof Error ? err.message : String(err)}`);
@@ -137,6 +137,7 @@
137
137
  "../../scripts/omp-receive-facts.ts",
138
138
  "../../pi-extensions/meta-bridge-hook.ts",
139
139
  "../../pi-extensions/meta-bridge-hook-copilot.ts",
140
+ "../../pi-extensions/meta-bridge-hook-codex.ts",
140
141
  "../../pi-extensions/meta-bridge-omp.ts",
141
142
  "../../pi-extensions/meta-bridge-receive-omp.ts"
142
143
  ]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@junghanacs/entwurf",
3
- "version": "0.20.1",
4
- "description": "Garden-citizen dispatch substrate and meta-bridge for pi, Claude Code, Copilot CLI, OMP, and Antigravity; Claude and Snowflake Cortex Code ACP backends. Codex has verified delivery-probe evidence, not a managed native-citizen lane.",
3
+ "version": "0.21.0",
4
+ "description": "Garden-citizen dispatch substrate and meta-bridge for pi, Claude Code, Copilot CLI, OMP, Antigravity, and Codex CLI; Claude and Snowflake Cortex Code ACP backends.",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Junghan Kim",
@@ -86,6 +86,7 @@
86
86
  "@agentclientprotocol/sdk": "1.4.0",
87
87
  "@anthropic-ai/sdk": "0.100.1",
88
88
  "@modelcontextprotocol/sdk": "^1.12.1",
89
+ "smol-toml": "^1.8.0",
89
90
  "zod": "^3.25.0 || ^4.0.0"
90
91
  },
91
92
  "peerDependencies": {
@@ -142,6 +143,14 @@
142
143
  "smoke-omp-fresh-live": "./run.sh smoke-omp-fresh-live",
143
144
  "check-omp-receive-arm": "./run.sh check-omp-receive-arm",
144
145
  "check-omp-fresh-preflight": "./run.sh check-omp-fresh-preflight",
146
+ "check-codex-sender-identity": "./run.sh check-codex-sender-identity",
147
+ "check-codex-native-push": "./run.sh check-codex-native-push",
148
+ "check-codex-bridge-identity": "./run.sh check-codex-bridge-identity",
149
+ "check-codex-birth-hook": "./run.sh check-codex-birth-hook",
150
+ "check-codex-fresh-preflight": "./run.sh check-codex-fresh-preflight",
151
+ "smoke-codex-birth": "./run.sh smoke-codex-birth",
152
+ "smoke-codex-config-state": "./run.sh smoke-codex-config-state",
153
+ "smoke-codex-fresh-live": "./run.sh smoke-codex-fresh-live",
145
154
  "check-hook-launch-topology": "./run.sh check-hook-launch-topology",
146
155
  "check-meta-hook-session-switch": "./run.sh check-meta-hook-session-switch",
147
156
  "check-acp-carrier-augment": "./run.sh check-acp-carrier-augment",
@@ -165,8 +174,8 @@
165
174
  "check:core": "pnpm run check:toolchain && pnpm run check:vitest && pnpm run check:contracts",
166
175
  "check:toolchain": "pnpm lint && pnpm typecheck",
167
176
  "check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface",
168
- "check:contracts": "./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-lock && ./run.sh check-entwurf-v2-decider && ./run.sh check-entwurf-v2-matrix && ./run.sh check-entwurf-v2-release && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-send-fallback && ./run.sh check-entwurf-v2-mailbox && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-runner && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-entwurf-resume-args && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-entwurf-facts && ./run.sh check-control-socket-path && ./run.sh check-socket-discovery && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment",
169
- "check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-meta-hook-session-switch && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-birth-hook && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-usage-accounting && ./run.sh check-acp-launch-namespace && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-gate-manifests && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex",
177
+ "check:contracts": "./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-lock && ./run.sh check-entwurf-v2-decider && ./run.sh check-entwurf-v2-matrix && ./run.sh check-entwurf-v2-release && ./run.sh check-entwurf-v2-send && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-v2-send-fallback && ./run.sh check-entwurf-v2-mailbox && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-runner && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-entwurf-resume-args && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-entwurf-facts && ./run.sh check-control-socket-path && ./run.sh check-socket-discovery && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push",
178
+ "check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-meta-hook-session-switch && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-birth-hook && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-usage-accounting && ./run.sh check-acp-launch-namespace && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-gate-manifests && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex && ./run.sh check-codex-bridge-identity && ./run.sh check-codex-birth-hook && ./run.sh smoke-codex-config-state && ./run.sh smoke-codex-birth",
170
179
  "check:package": "./run.sh check-install-surface && ./run.sh smoke-meta-install-state && ./run.sh smoke-agy-install-state && ./run.sh smoke-setup-verdict && ./run.sh check-pack-pin-matcher && ./run.sh check-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
171
180
  },
172
181
  "pi": {