@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.
- package/AGENTS.md +85 -190
- package/BASELINE.md +4 -3
- package/CHANGELOG.md +195 -14
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +202 -60
- package/README.md +49 -22
- package/VERIFY.md +37 -7
- package/docs/acp-backend-rail.md +0 -1
- package/docs/external-mcp-host.md +54 -33
- package/docs/setup-clean-host.md +110 -17
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +101 -109
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +326 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +7 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +20 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +227 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +50 -15
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +10 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +403 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +4 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-codex.js +325 -0
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +6 -1
- package/mcp/entwurf-bridge/src/index.ts +115 -111
- package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
- package/package.json +13 -4
- package/pi-extensions/entwurf-control.ts +71 -19
- package/pi-extensions/lib/codex-fresh-preflight.ts +363 -0
- package/pi-extensions/lib/compaction-send-guard.ts +80 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
- package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
- package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
- package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
- package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
- package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
- package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
- package/pi-extensions/lib/meta-sender-identity.ts +305 -0
- package/pi-extensions/lib/mux-fresh-call.ts +64 -19
- package/pi-extensions/lib/native-push/adapter.ts +21 -24
- package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
- package/pi-extensions/lib/native-push/register.ts +7 -9
- package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
- package/run.sh +218 -28
- package/scripts/check-agy-sender-identity.ts +1 -1
- package/scripts/check-codex-birth-hook.ts +264 -0
- package/scripts/check-codex-bridge-identity.ts +179 -0
- package/scripts/check-codex-native-push.ts +386 -0
- package/scripts/check-codex-sender-identity.ts +495 -0
- package/scripts/check-compaction-send-guard.ts +130 -0
- package/scripts/check-copilot-receive-arm.ts +4 -1
- package/scripts/check-entwurf-fact-provider.ts +38 -0
- package/scripts/check-entwurf-peers-surface.ts +13 -1
- package/scripts/check-entwurf-self-address.ts +15 -16
- package/scripts/check-entwurf-v2-contract.ts +4 -3
- package/scripts/check-entwurf-v2-decider.ts +7 -5
- package/scripts/check-entwurf-v2-native-push.ts +35 -7
- package/scripts/check-entwurf-v2-production.ts +203 -11
- package/scripts/check-entwurf-v2-runner.ts +1 -1
- package/scripts/check-entwurf-v2-surface.ts +1 -1
- package/scripts/check-gate-qualification.ts +6 -3
- package/scripts/check-harness-admission-parity.ts +0 -1
- package/scripts/check-mux-launch-tmux.ts +47 -2
- package/scripts/check-native-push-adapter.ts +20 -16
- package/scripts/check-native-push-register.ts +5 -1
- package/scripts/check-release-gate-outcomes.ts +47 -1
- package/scripts/check-setup-qualification.sh +3 -1
- package/scripts/codex-birth-doctor.sh +276 -0
- package/scripts/codex-birth-install.sh +414 -0
- package/scripts/codex-birth-uninstall.sh +170 -0
- package/scripts/codex-mcp-config.py +435 -0
- package/scripts/codex-statusline-config.py +434 -0
- package/scripts/codex_toml_io.py +532 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +113 -0
- package/scripts/lib/codex-fresh-source-receipts.ts +399 -0
- package/scripts/lib/launch-receipt-windows.ts +46 -0
- package/scripts/lib/tmux-coordinate-row.ts +58 -0
- package/scripts/meta-bridge-fresh-cut.ts +6 -1
- package/scripts/mutants/codex-native.json +838 -0
- package/scripts/mutants/compaction-send-guard.json +103 -0
- package/scripts/mutants/entwurf-peers.json +19 -0
- package/scripts/mutants/mux-fresh-call.json +19 -8
- package/scripts/mutants/omp-fresh.json +6 -4
- package/scripts/mutants/release-gate.json +13 -0
- package/scripts/mutants/v2-surface.json +53 -1
- package/scripts/raw-async-delivery/README.md +2 -1
- package/scripts/raw-codex-measure/README.md +114 -46
- package/scripts/smoke-agy-native-push-live.ts +3 -1
- package/scripts/smoke-codex-birth.sh +347 -0
- package/scripts/smoke-codex-config-state.sh +511 -0
- package/scripts/smoke-codex-fresh-live.ts +1186 -0
- package/scripts/smoke-codex-native-push-live.ts +75 -0
- package/scripts/smoke-setup-verdict.sh +123 -10
- package/scripts/tsconfig.json +1 -0
|
@@ -1,7 +1,8 @@
|
|
|
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.
|
|
4
|
+
* the launch argv, and let it name itself back to the caller. Codex is the one measured topology
|
|
5
|
+
* exception: an omitted seat selects the existing `codex` home session (#95), never a TUI guess.
|
|
5
6
|
*
|
|
6
7
|
* ── Why this is a third module and not a parameter on the leaf ──
|
|
7
8
|
*
|
|
@@ -78,6 +79,7 @@
|
|
|
78
79
|
|
|
79
80
|
import { randomBytes } from "node:crypto";
|
|
80
81
|
import { classifyTmuxCwd, type TmuxCwdRejectReason } from "./classify-tmux-cwd.ts";
|
|
82
|
+
import { CODEX_PREFLIGHT_HINT, type CodexPreflightRejectReason } from "./codex-fresh-preflight.ts";
|
|
81
83
|
import {
|
|
82
84
|
COPILOT_PREFLIGHT_HINT,
|
|
83
85
|
type CopilotPreflightRejectReason,
|
|
@@ -100,15 +102,16 @@ import {
|
|
|
100
102
|
runTmux,
|
|
101
103
|
type WindowHandle,
|
|
102
104
|
} from "./mux-placement.ts";
|
|
105
|
+
import { resolveCodexDefaultSocketPath } from "./native-push/codex-ws-client.ts";
|
|
103
106
|
import { OMP_PREFLIGHT_HINT, type OmpPreflightRejectReason, ompFreshPreflight } from "./omp-fresh-preflight.ts";
|
|
104
107
|
import { classifyTmuxSessionName, resolveTmuxSessionId, type TmuxSessionRejectReason } from "./resolve-tmux-session.ts";
|
|
105
108
|
|
|
106
109
|
/** The backends this rail can open. Fixed set, not a profile — a further one is a decision,
|
|
107
|
-
* not a config entry. `copilot` was added by #82 RAIL 9
|
|
108
|
-
*
|
|
109
|
-
* `check-harness-admission-parity`: a harness that mints records but is
|
|
110
|
-
* unwired convenience, it is a release blocker. */
|
|
111
|
-
export const FRESH_CALL_BACKENDS = ["pi", "claude-code", "copilot", "omp"] as const;
|
|
110
|
+
* not a config entry. `copilot` was added by #82 RAIL 9, `omp` by #87 Bundle C, and `codex`
|
|
111
|
+
* by #95 after its system birth and app-server rails were measured. The set is joined to the
|
|
112
|
+
* citizen backends by `check-harness-admission-parity`: a harness that mints records but is
|
|
113
|
+
* missing HERE is not an unwired convenience, it is a release blocker. */
|
|
114
|
+
export const FRESH_CALL_BACKENDS = ["pi", "claude-code", "copilot", "omp", "codex"] as const;
|
|
112
115
|
export type FreshCallBackend = (typeof FRESH_CALL_BACKENDS)[number];
|
|
113
116
|
|
|
114
117
|
/**
|
|
@@ -138,6 +141,7 @@ export const FRESH_CALL_RUNTIME: Record<FreshCallBackend, string> = {
|
|
|
138
141
|
"claude-code": "claude",
|
|
139
142
|
copilot: "entwurf",
|
|
140
143
|
omp: "omp",
|
|
144
|
+
codex: "codex",
|
|
141
145
|
};
|
|
142
146
|
|
|
143
147
|
/**
|
|
@@ -166,6 +170,7 @@ export const FRESH_CALL_CALLBACK_TOOL: Record<FreshCallBackend, string> = {
|
|
|
166
170
|
"claude-code": "mcp__entwurf-bridge__entwurf_v2",
|
|
167
171
|
copilot: "entwurf-bridge-entwurf_v2",
|
|
168
172
|
omp: "mcp__entwurf_bridge_entwurf_v",
|
|
173
|
+
codex: "mcp__entwurf_bridge__entwurf_v2",
|
|
169
174
|
};
|
|
170
175
|
|
|
171
176
|
/** Mirrors the `entwurf_v2` message bound. This is an INTERFACE cap for symmetry with the
|
|
@@ -276,6 +281,7 @@ export function buildBackendArgs(
|
|
|
276
281
|
backend: FreshCallBackend,
|
|
277
282
|
composition: FreshCallComposition,
|
|
278
283
|
model: string,
|
|
284
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
279
285
|
): string[] {
|
|
280
286
|
switch (backend) {
|
|
281
287
|
case "pi":
|
|
@@ -286,11 +292,20 @@ export function buildBackendArgs(
|
|
|
286
292
|
return ["copilot", "--interactive", composition.prompt, "--model", model, "--yolo"];
|
|
287
293
|
case "omp":
|
|
288
294
|
return [`--${OMP_BOOTSTRAP_FLAG}`, composition.bootstrapPayload, "--model", model, "--approval-mode", "yolo"];
|
|
295
|
+
case "codex":
|
|
296
|
+
return [
|
|
297
|
+
"--remote",
|
|
298
|
+
`unix://${resolveCodexDefaultSocketPath(env)}`,
|
|
299
|
+
"--model",
|
|
300
|
+
model,
|
|
301
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
302
|
+
composition.prompt,
|
|
303
|
+
];
|
|
289
304
|
}
|
|
290
305
|
}
|
|
291
306
|
|
|
292
307
|
/**
|
|
293
|
-
* What a launch has to say, in the two shapes the
|
|
308
|
+
* What a launch has to say, in the two shapes the five backends need. Four of them are
|
|
294
309
|
* handed a first-turn PROMPT; omp is handed a bootstrap PAYLOAD its own installed extension
|
|
295
310
|
* unpacks. Both are always built, because building one is cheap and a backend switch must
|
|
296
311
|
* never be able to reach a field that was not composed.
|
|
@@ -375,6 +390,7 @@ export type FreshCallRejectReason =
|
|
|
375
390
|
| TmuxSessionRejectReason
|
|
376
391
|
| CopilotPreflightRejectReason
|
|
377
392
|
| OmpPreflightRejectReason
|
|
393
|
+
| CodexPreflightRejectReason
|
|
378
394
|
| "caller-identity-unavailable"
|
|
379
395
|
| "model-empty"
|
|
380
396
|
| "model-invalid"
|
|
@@ -389,6 +405,28 @@ export interface FreshCallPlacement {
|
|
|
389
405
|
tmuxSession: string;
|
|
390
406
|
}
|
|
391
407
|
|
|
408
|
+
/**
|
|
409
|
+
* Codex's explicit home topology (#95): the operator provides this one existing tmux session
|
|
410
|
+
* and seats the operator-owned app-server plus supported Codex TUIs there. This is a placement
|
|
411
|
+
* convention, not an address axis — `threadId` remains the native delivery address.
|
|
412
|
+
*/
|
|
413
|
+
export const CODEX_HOME_TMUX_SESSION = "codex" as const;
|
|
414
|
+
export type FreshCallSeatSource = "requested" | "codex-home";
|
|
415
|
+
export interface FreshCallSeat {
|
|
416
|
+
tmuxSession: string;
|
|
417
|
+
source: FreshCallSeatSource;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** An explicit seat always wins. Only omitted Codex placement gets the fixed home. */
|
|
421
|
+
export function selectFreshCallSeat(
|
|
422
|
+
backend: FreshCallBackend,
|
|
423
|
+
placement: FreshCallPlacement | undefined,
|
|
424
|
+
): FreshCallSeat | null {
|
|
425
|
+
if (placement !== undefined) return { tmuxSession: placement.tmuxSession, source: "requested" };
|
|
426
|
+
if (backend === "codex") return { tmuxSession: CODEX_HOME_TMUX_SESSION, source: "codex-home" };
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
392
430
|
/** Coordinates plus what was handed to tmux. Read `runtimePath` as "what we asked to start".
|
|
393
431
|
* There is deliberately NO field here for the callback, the nonce's arrival, or the sibling's
|
|
394
432
|
* garden id — see the module header. */
|
|
@@ -399,10 +437,11 @@ export interface FreshCallReceipt extends WindowHandle {
|
|
|
399
437
|
* of fact as `runtimePath`: what tmux was asked for, never an observation of where the pane
|
|
400
438
|
* landed. */
|
|
401
439
|
cwd?: string;
|
|
402
|
-
/** The
|
|
403
|
-
* target is the inherited `sessionId`, which is the session the window is actually in
|
|
404
|
-
* field is the request that produced it, exactly as `cwd` is. */
|
|
440
|
+
/** The selected session name — either caller-requested or Codex's fixed home. The RESOLVED
|
|
441
|
+
* target is the inherited `sessionId`, which is the session the window is actually in. */
|
|
405
442
|
tmuxSession?: string;
|
|
443
|
+
/** Why `tmuxSession` was selected. Absent exactly when no named seat was used. */
|
|
444
|
+
tmuxSessionSource?: FreshCallSeatSource;
|
|
406
445
|
runtimePath: string;
|
|
407
446
|
nonce: string;
|
|
408
447
|
}
|
|
@@ -435,9 +474,9 @@ function defaultRandomHex(): string {
|
|
|
435
474
|
* for. That is sufficient and not a compromise: every reader of the carrier trims and tests
|
|
436
475
|
* truthiness (`index.ts:212-217`), so empty and absent are the same answer by construction.
|
|
437
476
|
*
|
|
438
|
-
* It is applied to all
|
|
477
|
+
* It is applied to all five backends because the leak is a property of tmux, not of a vendor. A
|
|
439
478
|
* scrub only on the backend whose measurement surfaced it would encode the claim that the other
|
|
440
|
-
*
|
|
479
|
+
* four are immune, which is false. It costs the legitimate case nothing: a carrier is only ever
|
|
441
480
|
* authoritative when the process that owns it exported it ITSELF, and a fresh `pi` sibling does
|
|
442
481
|
* exactly that after this argv has run. This is a fixed two-variable seam and deliberately NOT a
|
|
443
482
|
* general env carrier — an arbitrary `-e` passthrough would hand callers the environment-shaping
|
|
@@ -526,8 +565,10 @@ export function freshCall(
|
|
|
526
565
|
// without tmux, so an unresolvable name is answered before anything else runs. Whether that
|
|
527
566
|
// session EXISTS is a tmux question and is asked below, after the caller's own context is
|
|
528
567
|
// proven — a name check that needed a live server would refuse for the wrong reason on a
|
|
529
|
-
// host with no tmux at all.
|
|
530
|
-
|
|
568
|
+
// host with no tmux at all. Codex alone selects its fixed home when the caller omitted a seat;
|
|
569
|
+
// an explicit seat remains an expert override and is reported as such.
|
|
570
|
+
const selectedSeat = selectFreshCallSeat(params.backend, params.placement);
|
|
571
|
+
const seat = selectedSeat?.tmuxSession;
|
|
531
572
|
if (seat !== undefined) {
|
|
532
573
|
const badSeat = classifyTmuxSessionName(seat);
|
|
533
574
|
if (badSeat) return { ok: false, reason: badSeat };
|
|
@@ -555,7 +596,6 @@ export function freshCall(
|
|
|
555
596
|
const missing = ompFreshPreflight(env);
|
|
556
597
|
if (missing) return { ok: false, reason: missing };
|
|
557
598
|
}
|
|
558
|
-
|
|
559
599
|
const inspected = inspectPlacement(env);
|
|
560
600
|
if (!inspected.ok) return { ok: false, reason: inspected.reason };
|
|
561
601
|
const placement = inspected.placement;
|
|
@@ -582,7 +622,7 @@ export function freshCall(
|
|
|
582
622
|
bootstrapPayload: buildOmpBootstrapPayload({ callerGardenId: params.callerGardenId, nonce, task }),
|
|
583
623
|
};
|
|
584
624
|
const run = runTmux(
|
|
585
|
-
buildFreshCallArgs(targetSessionId, runtimePath, buildBackendArgs(params.backend, composition, model), cwd),
|
|
625
|
+
buildFreshCallArgs(targetSessionId, runtimePath, buildBackendArgs(params.backend, composition, model, env), cwd),
|
|
586
626
|
env,
|
|
587
627
|
);
|
|
588
628
|
assertTmuxOk("new-window", run);
|
|
@@ -610,7 +650,9 @@ export function freshCall(
|
|
|
610
650
|
backend: params.backend,
|
|
611
651
|
model,
|
|
612
652
|
...(cwd === undefined ? {} : { cwd }),
|
|
613
|
-
...(
|
|
653
|
+
...(selectedSeat === null
|
|
654
|
+
? {}
|
|
655
|
+
: { tmuxSession: selectedSeat.tmuxSession, tmuxSessionSource: selectedSeat.source }),
|
|
614
656
|
runtimePath,
|
|
615
657
|
nonce,
|
|
616
658
|
},
|
|
@@ -624,6 +666,7 @@ const REJECT_HINT: Record<FreshCallRejectReason, string> = {
|
|
|
624
666
|
// the sentence an operator reads cannot drift away from the predicate that produced it.
|
|
625
667
|
...COPILOT_PREFLIGHT_HINT,
|
|
626
668
|
...OMP_PREFLIGHT_HINT,
|
|
669
|
+
...CODEX_PREFLIGHT_HINT,
|
|
627
670
|
"no-tmux-context": "this agent is not running inside tmux, so there is no session to open a sibling beside",
|
|
628
671
|
"anchor-malformed": "TMUX_PANE is not a native pane id",
|
|
629
672
|
"anchor-unresolved": "tmux resolved no pane for this agent's anchor",
|
|
@@ -640,7 +683,7 @@ const REJECT_HINT: Record<FreshCallRejectReason, string> = {
|
|
|
640
683
|
"tmux-session-name-invalid":
|
|
641
684
|
"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",
|
|
642
685
|
"tmux-session-missing":
|
|
643
|
-
"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",
|
|
686
|
+
"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; Codex's omitted-placement home is the exact session name 'codex'",
|
|
644
687
|
"model-empty": "model is empty after trimming; fresh calls require an explicit model",
|
|
645
688
|
"model-invalid": `model must be one ${MODEL_MAX_CHARS}-character argv-safe id/alias without whitespace or tmux syntax`,
|
|
646
689
|
"task-empty": "task is empty after trimming",
|
|
@@ -678,7 +721,9 @@ export function renderFreshCall(result: FreshCallResult): { text: string; isErro
|
|
|
678
721
|
(r.cwd === undefined ? "" : ` cwd: ${r.cwd} (requested start directory — not an observation)\n`) +
|
|
679
722
|
(r.tmuxSession === undefined
|
|
680
723
|
? ""
|
|
681
|
-
:
|
|
724
|
+
: r.tmuxSessionSource === "codex-home"
|
|
725
|
+
? ` seat: ${r.tmuxSession} (Codex home tmux session, resolved to ${r.sessionId})\n`
|
|
726
|
+
: ` seat: ${r.tmuxSession} (requested tmux session, resolved to ${r.sessionId})\n`) +
|
|
682
727
|
` window: ${r.windowId} (index ${r.windowIndex}) in session ${r.sessionId}\n` +
|
|
683
728
|
` pane: ${r.paneId} pid ${r.panePid}\n` +
|
|
684
729
|
` nonce: ${r.nonce}\n` +
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* native-push adapter rail — the transport LEAF by which a native-push backend
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* is (1) probed for a LIVE native target and (2) direct-injected with a message.
|
|
4
|
+
* Mirrors the ACP backend-adapter rail (acp/backend-adapter.ts §ADAPTERS
|
|
5
5
|
* /resolveAcpBackendAdapter): one interface, one registry, a fail-fast resolver.
|
|
6
6
|
*
|
|
7
7
|
* Purity contract (봉인 3):
|
|
@@ -26,6 +26,7 @@ import * as os from "node:os";
|
|
|
26
26
|
import * as path from "node:path";
|
|
27
27
|
|
|
28
28
|
import type { NativePushBackend } from "../entwurf-v2-contract.ts";
|
|
29
|
+
import { codexNativePushAdapter } from "./codex-ws-client.ts";
|
|
29
30
|
|
|
30
31
|
// ── runner seam (injectable process exec) ───────────────────────────────────
|
|
31
32
|
|
|
@@ -85,14 +86,13 @@ export const realNativePushRunner: NativePushRunner = {
|
|
|
85
86
|
// ── route + probe result ────────────────────────────────────────────────────
|
|
86
87
|
|
|
87
88
|
/**
|
|
88
|
-
* A VOLATILE native-push route
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* `send` call, handed straight from a fresh `probe`.
|
|
89
|
+
* A VOLATILE native-push route. Antigravity carries its live LS address; Codex carries
|
|
90
|
+
* the one app-server UDS that proved the target thread loaded. Routes are never stored:
|
|
91
|
+
* every dispatch derives one from a fresh probe and hands it straight to `send`.
|
|
92
92
|
*/
|
|
93
|
-
export
|
|
94
|
-
readonly lsAddress: string
|
|
95
|
-
}
|
|
93
|
+
export type NativePushRoute =
|
|
94
|
+
| { readonly backend: "antigravity"; readonly lsAddress: string }
|
|
95
|
+
| { readonly backend: "codex"; readonly socketPath: string };
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* A probe outcome. `alive` carries the volatile route; `dead`/`indeterminate` carry a
|
|
@@ -108,17 +108,12 @@ export type NativePushProbeResult =
|
|
|
108
108
|
export interface NativePushAdapter {
|
|
109
109
|
/** Backend discriminator (a member of NATIVE_PUSH_BACKENDS). */
|
|
110
110
|
readonly id: NativePushBackend;
|
|
111
|
-
/**
|
|
112
|
-
*
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*/
|
|
111
|
+
/** Whether the executor may re-probe and retry once after an ambiguous send failure.
|
|
112
|
+
* Codex is false: `codex queue` may accept before its receipt fails, so retry can duplicate. */
|
|
113
|
+
readonly retriable: boolean;
|
|
114
|
+
/** Probe the backend's current native route for `nativeSessionId`. */
|
|
116
115
|
probe(nativeSessionId: string): Promise<NativePushProbeResult>;
|
|
117
|
-
/**
|
|
118
|
-
* Direct-inject `content` into the conversation over `route`. Single attempt: throws
|
|
119
|
-
* on failure (fail-loud). Does NOT probe and does NOT retry — the executor hand owns
|
|
120
|
-
* re-probe/retry (봉인 3), so the adapter can never silently paper over a dead route.
|
|
121
|
-
*/
|
|
116
|
+
/** Direct-inject once through the just-probed route. Throws on failure. */
|
|
122
117
|
send(route: NativePushRoute, nativeSessionId: string, content: string): Promise<void>;
|
|
123
118
|
}
|
|
124
119
|
|
|
@@ -211,6 +206,7 @@ export function createAntigravityAdapter(deps: AntigravityAdapterDeps): NativePu
|
|
|
211
206
|
|
|
212
207
|
return {
|
|
213
208
|
id: "antigravity",
|
|
209
|
+
retriable: true,
|
|
214
210
|
|
|
215
211
|
async probe(nativeSessionId) {
|
|
216
212
|
let pids: number[];
|
|
@@ -237,7 +233,7 @@ export function createAntigravityAdapter(deps: AntigravityAdapterDeps): NativePu
|
|
|
237
233
|
for (const pid of pids) {
|
|
238
234
|
for (const lsAddress of portsByPid.get(pid) ?? []) {
|
|
239
235
|
if (await servesConversation(lsAddress, nativeSessionId)) {
|
|
240
|
-
return { status: "alive", route: { lsAddress } };
|
|
236
|
+
return { status: "alive", route: { backend: "antigravity", lsAddress } };
|
|
241
237
|
}
|
|
242
238
|
}
|
|
243
239
|
}
|
|
@@ -251,12 +247,13 @@ export function createAntigravityAdapter(deps: AntigravityAdapterDeps): NativePu
|
|
|
251
247
|
},
|
|
252
248
|
|
|
253
249
|
async send(route, nativeSessionId, content) {
|
|
250
|
+
if (route.backend !== "antigravity") {
|
|
251
|
+
throw new Error(`antigravity adapter received ${route.backend} route`);
|
|
252
|
+
}
|
|
254
253
|
const r = await runner.exec([binary, "agentapi", "send-message", nativeSessionId, content], {
|
|
255
254
|
env: { ANTIGRAVITY_LS_ADDRESS: route.lsAddress },
|
|
256
255
|
timeoutMs: AGY_SEND_TIMEOUT_MS,
|
|
257
256
|
});
|
|
258
|
-
// A non-zero code — including a timeout kill (124) on a stalled route — THROWS
|
|
259
|
-
// (fail-loud); the executor hand owns the 1-shot re-probe→re-send on that throw.
|
|
260
257
|
if (r.code !== 0) {
|
|
261
258
|
throw new Error(
|
|
262
259
|
`native-push send failed (agentapi send-message exit ${r.code}) via ${route.lsAddress}: ${
|
|
@@ -268,12 +265,12 @@ export function createAntigravityAdapter(deps: AntigravityAdapterDeps): NativePu
|
|
|
268
265
|
};
|
|
269
266
|
}
|
|
270
267
|
|
|
271
|
-
/**
|
|
268
|
+
/** Production adapters. */
|
|
272
269
|
export const antigravityAdapter: NativePushAdapter = createAntigravityAdapter({ runner: realNativePushRunner });
|
|
273
270
|
|
|
274
271
|
// ── registry + fail-fast resolver (mirror resolveAcpBackendAdapter) ──────────
|
|
275
272
|
|
|
276
|
-
const ADAPTERS: readonly NativePushAdapter[] = [antigravityAdapter];
|
|
273
|
+
const ADAPTERS: readonly NativePushAdapter[] = [antigravityAdapter, codexNativePushAdapter];
|
|
277
274
|
|
|
278
275
|
/**
|
|
279
276
|
* Resolve the native-push adapter that owns backend `id`. Fail-fast, like
|