@junghanacs/entwurf 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/BASELINE.md +3 -1
- package/CHANGELOG.md +278 -0
- package/DELIVERY.md +156 -26
- package/README.md +64 -13
- package/VERIFY.md +67 -11
- package/docs/external-mcp-host.md +16 -6
- package/docs/setup-clean-host.md +63 -22
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +40 -16
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +194 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +160 -25
- package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
- package/mcp/entwurf-bridge/src/index.ts +50 -16
- package/mcp/entwurf-bridge/tsconfig.build.json +9 -0
- package/package.json +2 -2
- package/pi-extensions/entwurf-control.ts +4 -4
- package/pi-extensions/lib/codex-caller-seat.ts +204 -0
- package/pi-extensions/lib/codex-fresh-preflight.ts +218 -1
- package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
- package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
- package/pi-extensions/lib/mux-fresh-call.ts +196 -37
- package/run.sh +127 -3
- package/scripts/check-codex-app-server-launch.ts +445 -0
- package/scripts/check-entwurf-v2-production.ts +42 -1
- package/scripts/check-entwurf-v2-send.ts +26 -7
- package/scripts/check-gate-qualification.ts +4 -2
- package/scripts/check-mux-launch-tmux.ts +331 -35
- package/scripts/codex-app-server-launch.sh +275 -0
- package/scripts/codex-socket-path.ts +33 -0
- package/scripts/codex-terminal-title-config.py +500 -0
- package/scripts/codex_toml_io.py +121 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +11 -3
- package/scripts/lib/codex-fresh-source-receipts.ts +29 -2
- package/scripts/mutants/codex-app-server-launch.json +157 -0
- package/scripts/mutants/codex-caller-seat.json +336 -0
- package/scripts/mutants/codex-native.json +3 -3
- package/scripts/mutants/mux-fresh-call.json +86 -14
- package/scripts/mutants/v2-surface.json +22 -0
- package/scripts/smoke-codex-config-state.sh +192 -3
- package/scripts/smoke-codex-fresh-live.ts +277 -37
- package/scripts/smoke-entwurf-chain-live.ts +50 -0
- package/scripts/smoke-setup-verdict.sh +13 -11
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* mux-fresh-call — open ONE visible sibling in the caller's own tmux server (its own session by
|
|
3
3
|
* default, or one named existing session on that server since #105), hand it its first task in
|
|
4
|
-
* the launch argv, and let it name itself back to the caller. Codex is the one measured
|
|
5
|
-
* exception
|
|
4
|
+
* the launch argv, and let it name itself back to the caller. A Codex CALLER is the one measured
|
|
5
|
+
* exception to "the caller's own session": it opens beside its own TUI pane, found by that
|
|
6
|
+
* pane's terminal title (#95 lane B), never by a TUI guess.
|
|
6
7
|
*
|
|
7
8
|
* ── Why this is a third module and not a parameter on the leaf ──
|
|
8
9
|
*
|
|
@@ -39,6 +40,14 @@
|
|
|
39
40
|
* byte-identical to the pre-#73 shape. Anything else is taken LITERALLY — no trim, no
|
|
40
41
|
* realpath, no project-name resolution, no store/peers/record lookup. The caller is the
|
|
41
42
|
* only cwd authority this module knows.
|
|
43
|
+
* - when a CODEX caller omits it, the surface supplies that citizen's own record cwd and it
|
|
44
|
+
* becomes an explicit `-c` (#95 lane C). This is not a second authority: it is the same
|
|
45
|
+
* caller, named more precisely, because a codex caller's PROCESS directory is the
|
|
46
|
+
* operator-owned app-server's rather than its own. Every other caller keeps the inherited
|
|
47
|
+
* directory and an unchanged argv.
|
|
48
|
+
* - the chosen value reaches codex TWICE, and that is one value with two carriers rather than
|
|
49
|
+
* two inputs: tmux `-c` places the pane, and codex `-C` places the THREAD, which a
|
|
50
|
+
* `--remote` attachment would otherwise take from the app-server (see `buildBackendArgs`).
|
|
42
51
|
* - the value is classified by the shared `classify-tmux-cwd.ts` leaf BEFORE any mutation
|
|
43
52
|
* (same four stable reasons as resume; the measured tmux 3.6a facts live on that leaf).
|
|
44
53
|
* This module's hints phrase them as the REQUESTED cwd; resume's say RECORDED.
|
|
@@ -78,7 +87,8 @@
|
|
|
78
87
|
*/
|
|
79
88
|
import { randomBytes } from "node:crypto";
|
|
80
89
|
import { classifyTmuxCwd } from "./classify-tmux-cwd.js";
|
|
81
|
-
import {
|
|
90
|
+
import { CODEX_CALLER_SEAT_HINT, resolveCodexCallerSeat, } from "./codex-caller-seat.js";
|
|
91
|
+
import { CODEX_CALLER_PREFLIGHT_HINT, CODEX_LAUNCH_CWD_PREFLIGHT_HINT, CODEX_PREFLIGHT_HINT, codexLaunchCwdFreshPreflight, } from "./codex-fresh-preflight.js";
|
|
82
92
|
import { COPILOT_PREFLIGHT_HINT, copilotFreshPreflight, } from "./copilot-fresh-preflight.js";
|
|
83
93
|
import { assertLaunchTarget, LaunchPreconditionError, resolveRuntimeOnPath, } from "./mux-launch.js";
|
|
84
94
|
import { APPEND_FORMAT, assertSelector, assertTmuxOk, inspectPlacement, parseWindowFields, requireSameContext, runTmux, } from "./mux-placement.js";
|
|
@@ -249,8 +259,33 @@ export function isSafeFreshCallModel(model) {
|
|
|
249
259
|
* have needed its own quoting, its own lifetime and its own refusal rules; argv already owns
|
|
250
260
|
* all three. This is deliberately NOT a general `--flag value` passthrough — an arbitrary
|
|
251
261
|
* carrier would hand callers the launch-shaping power this rail exists to refuse.
|
|
262
|
+
*
|
|
263
|
+
* CODEX ALONE CARRIES A DIRECTORY IN ITS ARGV, AND OMITTING IT IS A WRONG ANSWER RATHER THAN A
|
|
264
|
+
* NEUTRAL ONE. `[source rust-v0.153.4]` an explicit `--remote <endpoint>` — which this argv
|
|
265
|
+
* always passes — resolves to `AppServerTarget::Remote` (`codex-rs/tui/src/lib.rs:875-876`;
|
|
266
|
+
* `LocalDaemon` is only the IMPLICIT no-flag reuse path), and a Remote target takes its new
|
|
267
|
+
* thread's directory from `remote_cwd_override` ALONE: `thread_cwd_from_config`
|
|
268
|
+
* (`codex-rs/tui/src/app_server_session.rs:2022-2033`) answers `None` without it, and the
|
|
269
|
+
* app-server then opens the thread in ITS OWN directory. That override is exactly `-C/--cd`,
|
|
270
|
+
* retained only for a remote target (`codex-rs/tui/src/startup_orchestration.rs:191-194`), and
|
|
271
|
+
* the flag lives on the shared interactive options this argv already uses for
|
|
272
|
+
* `--dangerously-bypass-approvals-and-sandbox` (`codex-rs/utils/cli/src/shared_options.rs:53-68`)
|
|
273
|
+
* — not only on the `agents` subcommand that also spells it (`codex-rs/cli/src/main.rs:334`).
|
|
274
|
+
*
|
|
275
|
+
* `[측정 2026-09-16]` with the flag absent, three citizens of one chain (pi → Codex → Claude
|
|
276
|
+
* Code) all recorded the app-server's `~/repos/gh/entwurf` while the panes themselves sat in
|
|
277
|
+
* `~/repos/gh/agent-config`, and the birth hook wrote that vendor-supplied cwd into each record
|
|
278
|
+
* honestly (#95 lane C §1). So the token is ALWAYS present for codex: ONE directory, TWO
|
|
279
|
+
* CARRIERS — tmux `-c` places the PANE, codex `-C` places the THREAD, and `launchCwd` is the
|
|
280
|
+
* single value both receive. It is not a new input axis: the value is chosen by the cwd rules
|
|
281
|
+
* this module already has, one layer up.
|
|
252
282
|
*/
|
|
253
|
-
export function buildBackendArgs(backend, composition, model, env = process.env
|
|
283
|
+
export function buildBackendArgs(backend, composition, model, env = process.env,
|
|
284
|
+
/** Where the sibling will actually start: the directory `freshCall` chose when it chose one,
|
|
285
|
+
* and otherwise THIS process's own — which is what tmux gives a window opened with no `-c`
|
|
286
|
+
* (`[측정 ×2]`, module header). The default is that inherited fact rather than a
|
|
287
|
+
* convenience, so a caller that omits it still names the truth to codex. */
|
|
288
|
+
launchCwd = process.cwd()) {
|
|
254
289
|
switch (backend) {
|
|
255
290
|
case "pi":
|
|
256
291
|
return [composition.prompt, "--entwurf-control", "--model", model];
|
|
@@ -264,6 +299,8 @@ export function buildBackendArgs(backend, composition, model, env = process.env)
|
|
|
264
299
|
return [
|
|
265
300
|
"--remote",
|
|
266
301
|
`unix://${resolveCodexDefaultSocketPath(env)}`,
|
|
302
|
+
"-C",
|
|
303
|
+
launchCwd,
|
|
267
304
|
"--model",
|
|
268
305
|
model,
|
|
269
306
|
"--dangerously-bypass-approvals-and-sandbox",
|
|
@@ -328,17 +365,29 @@ export function buildFreshCallPrompt(params) {
|
|
|
328
365
|
].join("\n");
|
|
329
366
|
}
|
|
330
367
|
/**
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
368
|
+
* WHICH RULE PICKS THE TARGET SESSION. Three in strict order, and the order is the contract:
|
|
369
|
+
*
|
|
370
|
+
* 1. an explicit `placement` — the expert override, and it wins over everything below.
|
|
371
|
+
* 2. a CODEX CALLER with placement omitted — the sibling opens beside the caller's own TUI
|
|
372
|
+
* pane, found by the title anchor (#95 lane B). This one is NOT a named seat and is
|
|
373
|
+
* therefore not decided here: it resolves to a native `$id` with no name in between, so
|
|
374
|
+
* `freshCall` owns it directly (see the anchor step below) and this function answers
|
|
375
|
+
* `null` so the name grammar and the name→id lookup stay out of a path that has neither.
|
|
376
|
+
* 3. anything else — the caller's own session, with no named seat at all.
|
|
377
|
+
*
|
|
378
|
+
* THE SEAT FOLLOWS THE CALLER, NEVER THE BACKEND BEING OPENED. #95 first shipped a fourth rule —
|
|
379
|
+
* an omitted-placement Codex TARGET selected a fixed existing session named `codex`, where the
|
|
380
|
+
* operator seated the app-server and their TUIs — and #95 D1 retired it (GLG, 2026-09-16). It
|
|
381
|
+
* was a workaround for a mapping that did not exist yet: nothing could find the pane a Codex
|
|
382
|
+
* caller was sitting in, so the operator was asked to keep every Codex in one known room. Rule 2
|
|
383
|
+
* is that mapping, so the room is no longer load-bearing, and keeping it would have meant Codex
|
|
384
|
+
* alone answering "where does a sibling open?" differently from every other backend. The
|
|
385
|
+
* operator-owned app-server keeps its own seat wherever the operator puts it; Entwurf still
|
|
386
|
+
* never creates, moves or supervises it.
|
|
334
387
|
*/
|
|
335
|
-
export
|
|
336
|
-
/** An explicit seat always wins. Only omitted Codex placement gets the fixed home. */
|
|
337
|
-
export function selectFreshCallSeat(backend, placement) {
|
|
388
|
+
export function selectFreshCallSeat(placement) {
|
|
338
389
|
if (placement !== undefined)
|
|
339
390
|
return { tmuxSession: placement.tmuxSession, source: "requested" };
|
|
340
|
-
if (backend === "codex")
|
|
341
|
-
return { tmuxSession: CODEX_HOME_TMUX_SESSION, source: "codex-home" };
|
|
342
391
|
return null;
|
|
343
392
|
}
|
|
344
393
|
/** Correlation tag only. Random, never derived from time, cwd or a peer listing — a nonce that
|
|
@@ -416,6 +465,20 @@ export function buildFreshCallArgs(targetSessionId, runtimePath, backendArgs, cw
|
|
|
416
465
|
* `callerGardenId` is supplied by the SURFACE that registered this tool, from its own
|
|
417
466
|
* record-backed context. It is not a tool parameter and this module never derives, validates
|
|
418
467
|
* against a store, or guesses it: an empty value is a named refusal, not a lookup.
|
|
468
|
+
*
|
|
469
|
+
* `callerNativeSessionId` is supplied by the same surface under the same rule, and its PRESENCE
|
|
470
|
+
* is the whole signal: it is set exactly when the reconciled sender is a record-backed codex
|
|
471
|
+
* citizen, and it carries that citizen's `nativeSessionId` (the `_meta.threadId` the vendor put
|
|
472
|
+
* on this very request). This module never resolves it, never reads `_meta`, and never asks a
|
|
473
|
+
* store who is calling — it only turns a thread id into a pane, and only for placement.
|
|
474
|
+
*
|
|
475
|
+
* `callerCwd` rides the same surface rule and the same condition: it is that codex citizen's
|
|
476
|
+
* RECORD cwd, and it exists because a codex caller's process directory is NOT its own (the
|
|
477
|
+
* bridge runs as a child of the operator-owned app-server, so the directory that process
|
|
478
|
+
* reports is the app-server's — #95 lane C §2). It is consulted only when the caller requested
|
|
479
|
+
* no cwd, so an
|
|
480
|
+
* explicit request always wins, and this module never looks a cwd up, resolves it, or infers it
|
|
481
|
+
* from a seat, a workspace map or a project name.
|
|
419
482
|
*/
|
|
420
483
|
export function freshCall(params, env = process.env, nonce = mintNonce()) {
|
|
421
484
|
if (typeof params.callerGardenId !== "string" || params.callerGardenId.length === 0) {
|
|
@@ -433,8 +496,26 @@ export function freshCall(params, env = process.env, nonce = mintNonce()) {
|
|
|
433
496
|
return { ok: false, reason: "task-too-long" };
|
|
434
497
|
// ONLY `undefined` and the exact empty string mean "no cwd". Everything else is the literal
|
|
435
498
|
// value — deliberately untrimmed, so a whitespace-mangled path is refused loudly by the
|
|
436
|
-
// classification below instead of being silently repaired into a different directory.
|
|
437
|
-
|
|
499
|
+
// classification below instead of being silently repaired into a different directory. The
|
|
500
|
+
// caller's own record directory answers the SAME two-value emptiness rule and is consulted
|
|
501
|
+
// ONLY second: an explicit request always wins, and a caller that supplies neither leaves the
|
|
502
|
+
// pane to inherit this process's directory exactly as before — a pi caller's argv is
|
|
503
|
+
// byte-identical, because its process directory IS its own and a `-c` token would change
|
|
504
|
+
// nothing about where that window lands.
|
|
505
|
+
const requestedCwd = params.cwd === undefined || params.cwd === "" ? undefined : params.cwd;
|
|
506
|
+
const callerCwd = params.callerCwd === undefined || params.callerCwd === "" ? undefined : params.callerCwd;
|
|
507
|
+
const chosenCwd = requestedCwd !== undefined
|
|
508
|
+
? { value: requestedCwd, source: "requested" }
|
|
509
|
+
: callerCwd !== undefined
|
|
510
|
+
? { value: callerCwd, source: "codex-caller-record" }
|
|
511
|
+
: undefined;
|
|
512
|
+
const cwd = chosenCwd?.value;
|
|
513
|
+
// SEAM (#95 lane C): a caller-record directory is classified by the SAME shared leaf and
|
|
514
|
+
// answers the same four `cwd-*` reasons, whose hint text says REQUESTED. When a codex
|
|
515
|
+
// caller's recorded directory has since been deleted, the repair that hint points at is
|
|
516
|
+
// still the right one — that directory does not exist — but the noun belongs to the caller
|
|
517
|
+
// rather than to the request. Kept shared on purpose: doubling the reason set for a wording
|
|
518
|
+
// difference would double the refusal contract two surfaces and one leaf already agree on.
|
|
438
519
|
if (cwd !== undefined) {
|
|
439
520
|
const badCwd = classifyTmuxCwd(cwd);
|
|
440
521
|
if (badCwd)
|
|
@@ -444,9 +525,10 @@ export function freshCall(params, env = process.env, nonce = mintNonce()) {
|
|
|
444
525
|
// without tmux, so an unresolvable name is answered before anything else runs. Whether that
|
|
445
526
|
// session EXISTS is a tmux question and is asked below, after the caller's own context is
|
|
446
527
|
// proven — a name check that needed a live server would refuse for the wrong reason on a
|
|
447
|
-
// host with no tmux at all.
|
|
448
|
-
//
|
|
449
|
-
|
|
528
|
+
// host with no tmux at all. An explicit seat is an expert override and is reported as such; a
|
|
529
|
+
// CODEX CALLER's own pane is deliberately not a name at all, so it is absent here and
|
|
530
|
+
// resolved after the context proof below — see `selectFreshCallSeat` for the three-rule order.
|
|
531
|
+
const selectedSeat = selectFreshCallSeat(params.placement);
|
|
450
532
|
const seat = selectedSeat?.tmuxSession;
|
|
451
533
|
if (seat !== undefined) {
|
|
452
534
|
const badSeat = classifyTmuxSessionName(seat);
|
|
@@ -488,12 +570,28 @@ export function freshCall(params, env = process.env, nonce = mintNonce()) {
|
|
|
488
570
|
// native id continues; the name does not travel past this line. STILL PRE-MUTATION: an
|
|
489
571
|
// absent seat refuses with no window anywhere.
|
|
490
572
|
let targetSessionId = placement.sessionId;
|
|
573
|
+
let anchoredSeat = false;
|
|
491
574
|
if (seat !== undefined) {
|
|
492
575
|
const resolved = resolveTmuxSessionId(seat, (args) => runTmux(args, env));
|
|
493
576
|
if (!resolved.ok)
|
|
494
577
|
return { ok: false, reason: resolved.reason };
|
|
495
578
|
targetSessionId = resolved.sessionId;
|
|
496
579
|
}
|
|
580
|
+
else if (params.placement === undefined && params.callerNativeSessionId !== undefined) {
|
|
581
|
+
// Rule 2: the caller is a codex citizen and named no seat, so the sibling belongs beside
|
|
582
|
+
// the caller's own TUI. The pane is found by the title anchor and ONLY its `$session`
|
|
583
|
+
// continues — a pane title is forgeable, so it may never become an address, a liveness
|
|
584
|
+
// claim or a delivery input (Hard Rule 16). `params.placement` is re-read here rather
|
|
585
|
+
// than inferred from `seat === undefined`: "an explicit seat always wins" is the one
|
|
586
|
+
// invariant a later edit must not be able to lose by accident.
|
|
587
|
+
// STILL PRE-MUTATION: 0 or 2+ matching panes refuse with no window anywhere and no
|
|
588
|
+
// fallback to any other session.
|
|
589
|
+
const anchor = resolveCodexCallerSeat(params.callerNativeSessionId, (args) => runTmux(args, env));
|
|
590
|
+
if (!anchor.ok)
|
|
591
|
+
return { ok: false, reason: anchor.reason };
|
|
592
|
+
targetSessionId = anchor.seat.sessionId;
|
|
593
|
+
anchoredSeat = true;
|
|
594
|
+
}
|
|
497
595
|
const composition = {
|
|
498
596
|
prompt: buildFreshCallPrompt({
|
|
499
597
|
backend: params.backend,
|
|
@@ -503,7 +601,36 @@ export function freshCall(params, env = process.env, nonce = mintNonce()) {
|
|
|
503
601
|
}),
|
|
504
602
|
bootstrapPayload: buildOmpBootstrapPayload({ callerGardenId: params.callerGardenId, nonce, task }),
|
|
505
603
|
};
|
|
506
|
-
const
|
|
604
|
+
const backendArgs = buildBackendArgs(params.backend, composition, model, env, cwd);
|
|
605
|
+
// THE LAUNCH-DIRECTORY NOTE, AND IT IS A DIAGNOSTIC RATHER THAN A GATE. `[측정 2026-09-16]` a
|
|
606
|
+
// Codex sibling opened into a directory this Codex has no answer for stops on the vendor's
|
|
607
|
+
// folder-consent screen: no first turn, no rollout, no callback. It is tempting to refuse
|
|
608
|
+
// that, and refusing is the wrong product. The consent screen is SELF-REPAIRING when a human
|
|
609
|
+
// is there — one answer and the vendor records the directory, so every later launch runs —
|
|
610
|
+
// and an operator at the keyboard is exactly who a visible-first rail is built for. A refusal
|
|
611
|
+
// would replace that one answer with "no window, go run codex yourself, then call again", and
|
|
612
|
+
// it would have to be right about a decision this process cannot fully see (the vendor merges
|
|
613
|
+
// system, managed and cloud layers around the file this leaf reads). So the launch proceeds
|
|
614
|
+
// and says what it saw.
|
|
615
|
+
//
|
|
616
|
+
// The UNATTENDED case is not answered here and must not be: a gate with nobody at the keyboard
|
|
617
|
+
// needs its precondition named before it spends a model turn, which is its own oracle's job —
|
|
618
|
+
// `smoke-codex-fresh-live` asserts this same leaf up front, so a missing answer reads as a
|
|
619
|
+
// named precondition instead of a callback timeout.
|
|
620
|
+
//
|
|
621
|
+
// The directory asked about is READ BACK off codex's own `-C` token rather than recomputed:
|
|
622
|
+
// one resolution, one authority, and no way for the note to name a directory the thread will
|
|
623
|
+
// not start in (this module is deliberately not allowed to resolve the inherited default a
|
|
624
|
+
// second time — `FRESHCALL-CWD-CALLER-ONLY`).
|
|
625
|
+
if (params.backend === "codex") {
|
|
626
|
+
const at = backendArgs.indexOf("-C");
|
|
627
|
+
const launchCwd = backendArgs[at + 1] ?? "";
|
|
628
|
+
const unanswered = codexLaunchCwdFreshPreflight(env, launchCwd);
|
|
629
|
+
if (unanswered) {
|
|
630
|
+
console.error(`[fresh-call] ${unanswered}: ${launchCwd}\n` + ` ${CODEX_LAUNCH_CWD_PREFLIGHT_HINT[unanswered]}`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const run = runTmux(buildFreshCallArgs(targetSessionId, runtimePath, backendArgs, cwd), env);
|
|
507
634
|
assertTmuxOk("new-window", run);
|
|
508
635
|
let fields;
|
|
509
636
|
try {
|
|
@@ -523,9 +650,11 @@ export function freshCall(params, env = process.env, nonce = mintNonce()) {
|
|
|
523
650
|
...fields,
|
|
524
651
|
backend: params.backend,
|
|
525
652
|
model,
|
|
526
|
-
...(
|
|
653
|
+
...(chosenCwd === undefined ? {} : { cwd: chosenCwd.value, cwdSource: chosenCwd.source }),
|
|
527
654
|
...(selectedSeat === null
|
|
528
|
-
?
|
|
655
|
+
? anchoredSeat
|
|
656
|
+
? { tmuxSessionSource: "codex-title-anchor" }
|
|
657
|
+
: {}
|
|
529
658
|
: { tmuxSession: selectedSeat.tmuxSession, tmuxSessionSource: selectedSeat.source }),
|
|
530
659
|
runtimePath,
|
|
531
660
|
nonce,
|
|
@@ -540,6 +669,8 @@ const REJECT_HINT = {
|
|
|
540
669
|
...COPILOT_PREFLIGHT_HINT,
|
|
541
670
|
...OMP_PREFLIGHT_HINT,
|
|
542
671
|
...CODEX_PREFLIGHT_HINT,
|
|
672
|
+
...CODEX_CALLER_PREFLIGHT_HINT,
|
|
673
|
+
...CODEX_CALLER_SEAT_HINT,
|
|
543
674
|
"no-tmux-context": "this agent is not running inside tmux, so there is no session to open a sibling beside",
|
|
544
675
|
"anchor-malformed": "TMUX_PANE is not a native pane id",
|
|
545
676
|
"anchor-unresolved": "tmux resolved no pane for this agent's anchor",
|
|
@@ -550,7 +681,7 @@ const REJECT_HINT = {
|
|
|
550
681
|
"cwd-missing": "the requested cwd does not exist; tmux would not report this, it would open the window in $HOME and look successful",
|
|
551
682
|
"cwd-not-directory": "the requested cwd exists but is not a directory",
|
|
552
683
|
"tmux-session-name-invalid": "the requested tmux session name is outside the shape this rail addresses (start with a letter or digit, then letters, digits, '_' or '-') — some other shapes tmux cannot resolve at all ('#' is expanded when the name is stored; '.' and ':' are its own pane/window separators inside a target; a name like '$0' loses to the session id '$0'), and the rest are declined to keep one narrow grammar, so rename the session or open one whose name fits",
|
|
553
|
-
"tmux-session-missing": "no session with that exact name answers on this agent's tmux server (or that server stopped answering) — nothing was created, so open the session yourself and call again
|
|
684
|
+
"tmux-session-missing": "no session with that exact name answers on this agent's tmux server (or that server stopped answering) — nothing was created, so open the session yourself and call again",
|
|
554
685
|
"model-empty": "model is empty after trimming; fresh calls require an explicit model",
|
|
555
686
|
"model-invalid": `model must be one ${MODEL_MAX_CHARS}-character argv-safe id/alias without whitespace or tmux syntax`,
|
|
556
687
|
"task-empty": "task is empty after trimming",
|
|
@@ -583,11 +714,15 @@ export function renderFreshCall(result) {
|
|
|
583
714
|
text: `[entwurf fresh call →]\n` +
|
|
584
715
|
` backend: ${r.backend} (${r.runtimePath})\n` +
|
|
585
716
|
` model: ${r.model} (requested on the runtime CLI)\n` +
|
|
586
|
-
(r.cwd === undefined
|
|
587
|
-
|
|
717
|
+
(r.cwd === undefined
|
|
718
|
+
? ""
|
|
719
|
+
: r.cwdSource === "codex-caller-record"
|
|
720
|
+
? ` cwd: ${r.cwd} (the Codex caller's own record directory, used because no cwd was requested — not an observation)\n`
|
|
721
|
+
: ` cwd: ${r.cwd} (requested start directory — not an observation)\n`) +
|
|
722
|
+
(r.tmuxSessionSource === undefined
|
|
588
723
|
? ""
|
|
589
|
-
: r.tmuxSessionSource === "codex-
|
|
590
|
-
? ` seat: ${r.
|
|
724
|
+
: r.tmuxSessionSource === "codex-title-anchor"
|
|
725
|
+
? ` seat: ${r.sessionId} (the Codex caller's own pane, found by its thread-id terminal title — an OBSERVED session, not a requested name)\n`
|
|
591
726
|
: ` seat: ${r.tmuxSession} (requested tmux session, resolved to ${r.sessionId})\n`) +
|
|
592
727
|
` window: ${r.windowId} (index ${r.windowIndex}) in session ${r.sessionId}\n` +
|
|
593
728
|
` pane: ${r.paneId} pid ${r.panePid}\n` +
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
import { resolveCodexDefaultSocketPath } from "../pi-extensions/lib/native-push/codex-ws-client.js";
|
|
25
|
+
if (process.argv.length > 2) {
|
|
26
|
+
console.error("usage: codex-socket-path (no arguments; the environment is the input)");
|
|
27
|
+
process.exit(2);
|
|
28
|
+
}
|
|
29
|
+
// No trailing newline: the caller substitutes this straight into an address.
|
|
30
|
+
process.stdout.write(resolveCodexDefaultSocketPath(process.env));
|
|
@@ -65,7 +65,7 @@ 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
|
-
import { codexFreshPreflight } from "../../../pi-extensions/lib/codex-fresh-preflight.ts";
|
|
68
|
+
import { codexCallerFreshPreflight, 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";
|
|
@@ -254,6 +254,15 @@ interface AuthoritativeSelf {
|
|
|
254
254
|
envelope: SenderEnvelope;
|
|
255
255
|
/** meta-session only: WHICH rail carries a reply back. Never inferred from `origin`. */
|
|
256
256
|
metaDeliveryDomain?: MetaDeliveryDomain;
|
|
257
|
+
/**
|
|
258
|
+
* meta-session/codex ONLY: the `_meta.threadId` that named this caller on THIS request, and
|
|
259
|
+
* which the reconciliation above already proved equal to the selected citizen's
|
|
260
|
+
* `nativeSessionId`. It rides here for exactly one consumer — the fresh-call PLACEMENT,
|
|
261
|
+
* which turns it into the caller's own tmux pane through its title (#95 lane B). It is not
|
|
262
|
+
* an address, not a reply rail, and not part of the sender envelope: `sessionId` is the only
|
|
263
|
+
* thing anyone may send to.
|
|
264
|
+
*/
|
|
265
|
+
codexThreadId?: string;
|
|
257
266
|
}
|
|
258
267
|
|
|
259
268
|
interface SenderRequestContext {
|
|
@@ -339,7 +348,7 @@ async function resolveAuthoritativeSender(
|
|
|
339
348
|
]);
|
|
340
349
|
if (!selected) return null;
|
|
341
350
|
if (codex && codex.identity.gardenId === selected.id) {
|
|
342
|
-
return buildMetaSenderEnvelope(codex.identity, codex.identity.cwd);
|
|
351
|
+
return { ...(await buildMetaSenderEnvelope(codex.identity, codex.identity.cwd)), codexThreadId: codex.threadId };
|
|
343
352
|
}
|
|
344
353
|
if (marker && marker.identity.gardenId === selected.id) {
|
|
345
354
|
return buildMetaSenderEnvelope(marker.identity, marker.marker.cwd || cwd, marker);
|
|
@@ -712,21 +721,21 @@ server.tool(
|
|
|
712
721
|
"sender envelope of that callback is its garden id — that is how you learn the address of something that " +
|
|
713
722
|
"did not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing " +
|
|
714
723
|
"else: it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls " +
|
|
715
|
-
"for the callback; if it never arrives the window is visible
|
|
724
|
+
"for the callback; if it never arrives the window is visible. For EXISTING " +
|
|
716
725
|
"citizens use entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and " +
|
|
717
726
|
"is passed to the chosen runtime CLI (`provider/model` for pi; model id/alias for Claude Code; a model name " +
|
|
718
727
|
"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
|
|
721
|
-
"An optional " +
|
|
728
|
+
"any window opens when their required birth, MCP, receive/delivery, or visible-identity units are absent; " +
|
|
729
|
+
"Codex also requires the operator-owned default app-server socket, which entwurf never starts. An optional " +
|
|
722
730
|
"cwd starts the sibling in ONE literal absolute existing directory (cross-repo fresh) — never pick resume " +
|
|
723
|
-
"for a dormant record's cwd
|
|
724
|
-
"An optional placement.tmuxSession is an expert override naming ONE EXISTING session on this agent's own
|
|
725
|
-
"
|
|
726
|
-
"
|
|
731
|
+
"for a dormant record's cwd. Omitted/empty cwd means the caller's own directory. " +
|
|
732
|
+
"An optional placement.tmuxSession is an expert override naming ONE EXISTING session on this agent's own " +
|
|
733
|
+
"tmux server, and it ALWAYS wins. Omitted, the seat follows the CALLER: a CODEX CALLER opens beside its own " +
|
|
734
|
+
"TUI pane (matched by thread-id in that pane's title; 0 or 2+ matches REFUSE, never fall back), and every " +
|
|
735
|
+
"other caller opens in its own session. " +
|
|
736
|
+
"A missing named session is tmux-session-missing and NOTHING is created. " +
|
|
727
737
|
"There are no arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to " +
|
|
728
|
-
"same-user processes on this host. Requires that this agent itself runs "
|
|
729
|
-
"inside tmux: without a pane anchor there is no session to open a sibling beside.",
|
|
738
|
+
"same-user processes on this host. Requires that this agent itself runs inside tmux.",
|
|
730
739
|
{
|
|
731
740
|
backend: z
|
|
732
741
|
.enum(["pi", "claude-code", "copilot", "omp", "codex"])
|
|
@@ -758,7 +767,7 @@ server.tool(
|
|
|
758
767
|
.string()
|
|
759
768
|
.optional()
|
|
760
769
|
.describe(
|
|
761
|
-
"Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh).
|
|
770
|
+
"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.",
|
|
762
771
|
),
|
|
763
772
|
placement: z
|
|
764
773
|
.object({
|
|
@@ -770,14 +779,28 @@ server.tool(
|
|
|
770
779
|
})
|
|
771
780
|
.optional()
|
|
772
781
|
.describe(
|
|
773
|
-
"Optional expert seat override: open the sibling in ONE EXISTING tmux session of this agent's own server. When omitted, Codex
|
|
782
|
+
"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.",
|
|
774
783
|
),
|
|
775
784
|
},
|
|
776
785
|
async ({ backend, model, task, cwd, placement }, extra) => {
|
|
777
786
|
let callerGardenId: string | null = null;
|
|
787
|
+
// Present exactly when the reconciled caller is a record-backed codex citizen. Its ONLY
|
|
788
|
+
// consumer is the placement below: a Codex caller with no explicit seat opens its sibling
|
|
789
|
+
// beside its own TUI pane, found by that thread's terminal title (#95 lane B).
|
|
790
|
+
let callerNativeSessionId: string | undefined;
|
|
791
|
+
// The SAME citizen's record cwd, set under the SAME condition and carrying no further
|
|
792
|
+
// authority: `envelope.cwd` is only the record's directory when the selected sender is
|
|
793
|
+
// that codex citizen, so the two are derived from one fact rather than read separately.
|
|
794
|
+
// It exists because `process.cwd()` here is the operator-owned APP-SERVER's directory —
|
|
795
|
+
// this bridge is its MCP child — so a codex caller that names no cwd would otherwise open
|
|
796
|
+
// every sibling in the app-server's repo (#95 lane C §1). The composition consults it only
|
|
797
|
+
// when the tool call requested no cwd of its own.
|
|
798
|
+
let callerCwd: string | undefined;
|
|
778
799
|
try {
|
|
779
800
|
const self = await buildAuthoritativeSelfEnvelope({ requestMeta: extra._meta });
|
|
780
801
|
callerGardenId = self.envelope.sessionId;
|
|
802
|
+
callerNativeSessionId = self.codexThreadId;
|
|
803
|
+
callerCwd = self.codexThreadId === undefined ? undefined : self.envelope.cwd;
|
|
781
804
|
} catch (err) {
|
|
782
805
|
// ONE error is a legitimate answer here: this host has no authoritative identity at all
|
|
783
806
|
// (no pi carrier inherited, no trusted marker written), so the sibling would have nowhere
|
|
@@ -790,10 +813,21 @@ server.tool(
|
|
|
790
813
|
callerGardenId = null;
|
|
791
814
|
}
|
|
792
815
|
try {
|
|
793
|
-
|
|
816
|
+
// TWO capability axes, in this order, both pre-mutation and neither standing in for
|
|
817
|
+
// the other. The TARGET axis first — "entwurf cannot open a Codex sibling here at
|
|
818
|
+
// all" is the more fundamental answer than "and it would not know where to put it".
|
|
819
|
+
// The CALLER axis second, and only when the seat anchor will actually be consulted:
|
|
820
|
+
// a codex caller that named an explicit placement never reads a pane title, so
|
|
821
|
+
// refusing it for a missing `thread-id` would refuse an unused capability.
|
|
822
|
+
const targetMissing = backend === "codex" ? await codexFreshPreflight(process.env) : null;
|
|
823
|
+
const callerMissing =
|
|
824
|
+
targetMissing === null && callerNativeSessionId !== undefined && placement === undefined
|
|
825
|
+
? codexCallerFreshPreflight(process.env)
|
|
826
|
+
: null;
|
|
827
|
+
const missing = targetMissing ?? callerMissing;
|
|
794
828
|
const result = missing
|
|
795
829
|
? ({ ok: false, reason: missing } as const)
|
|
796
|
-
: freshCall({ backend, model, task, cwd, placement, callerGardenId });
|
|
830
|
+
: freshCall({ backend, model, task, cwd, placement, callerGardenId, callerNativeSessionId, callerCwd });
|
|
797
831
|
const rendered = renderFreshCall(result);
|
|
798
832
|
return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
|
|
799
833
|
} catch (err) {
|
|
@@ -114,6 +114,14 @@
|
|
|
114
114
|
// the marker-less native-push quiesce row) are lib leaves the bridge graph already
|
|
115
115
|
// tolerates — the adapter is a 봉인-3 leaf (node builtins + type-only contract), and
|
|
116
116
|
// register_native already pulls both into this emit.
|
|
117
|
+
// WHY codex-socket-path rides THIS build (#95): `entwurf codex-app-server` is a bash
|
|
118
|
+
// launcher that cannot import the socket resolver, and its first version re-derived the
|
|
119
|
+
// address instead — measurably diverging from `resolveCodexDefaultSocketPath` on a BOM-only
|
|
120
|
+
// CODEX_HOME and on path normalization. The launcher now ASKS this leaf through run_ts, so on
|
|
121
|
+
// an installed host it resolves under node_modules where strip-types is refused and a missing
|
|
122
|
+
// twin would make the one-command launch die before its first line. Its only repo import is
|
|
123
|
+
// native-push/codex-ws-client.ts, ALREADY emitted for meta-bridge-hook-codex, so this adds ONE
|
|
124
|
+
// leaf JS and ZERO new deps.
|
|
117
125
|
// WHY meta-bridge-omp rides THIS build (#87): `install-omp-bridge` explicitly SELECTS
|
|
118
126
|
// `dist/pi-extensions/meta-bridge-omp.js` when REPO_DIR is under node_modules — the same
|
|
119
127
|
// strip-types fence the Claude and Copilot birth entries cross — and
|
|
@@ -135,6 +143,7 @@
|
|
|
135
143
|
"../../scripts/meta-bridge-fresh-cut.ts",
|
|
136
144
|
"../../scripts/meta-facts.ts",
|
|
137
145
|
"../../scripts/omp-receive-facts.ts",
|
|
146
|
+
"../../scripts/codex-socket-path.ts",
|
|
138
147
|
"../../pi-extensions/meta-bridge-hook.ts",
|
|
139
148
|
"../../pi-extensions/meta-bridge-hook-copilot.ts",
|
|
140
149
|
"../../pi-extensions/meta-bridge-hook-codex.ts",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
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": {
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
"check:toolchain": "pnpm lint && pnpm typecheck",
|
|
176
176
|
"check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface",
|
|
177
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",
|
|
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-codex-app-server-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",
|
|
179
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"
|
|
180
180
|
},
|
|
181
181
|
"pi": {
|
|
@@ -1583,9 +1583,9 @@ receive/delivery, or visible-identity units are absent. Codex additionally requi
|
|
|
1583
1583
|
default app-server socket; entwurf never starts or supervises it. An optional cwd starts the
|
|
1584
1584
|
sibling in ONE literal absolute existing directory (cross-repo fresh) — never pick resume for a dormant
|
|
1585
1585
|
record's cwd; resume is continuity-only. Omitted/empty cwd means the caller's own directory. An optional
|
|
1586
|
-
placement.tmuxSession is an expert override naming ONE EXISTING session on this agent's own tmux server
|
|
1587
|
-
|
|
1588
|
-
|
|
1586
|
+
placement.tmuxSession is an expert override naming ONE EXISTING session on this agent's own tmux server and
|
|
1587
|
+
ALWAYS wins. Omitted, the seat follows the CALLER, never the backend being opened: every caller on this
|
|
1588
|
+
surface opens in its own session. A missing named session is tmux-session-missing and NOTHING is created. There are no
|
|
1589
1589
|
arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to same-user
|
|
1590
1590
|
processes on this host.`,
|
|
1591
1591
|
parameters: Type.Object({
|
|
@@ -1621,7 +1621,7 @@ processes on this host.`,
|
|
|
1621
1621
|
},
|
|
1622
1622
|
{
|
|
1623
1623
|
description:
|
|
1624
|
-
"Optional expert seat override: open the sibling in ONE EXISTING tmux session of this agent's own server. When omitted
|
|
1624
|
+
"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: every caller on THIS surface opens in its own session (a pi session is never a Codex citizen, so the Codex caller-pane rule the MCP bridge carries cannot arise here). 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.",
|
|
1625
1625
|
},
|
|
1626
1626
|
),
|
|
1627
1627
|
),
|