@runuai/host 0.9.59 → 0.9.60
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/lib/engine-login.ts +37 -10
- package/lib/mcp-gateway.ts +13 -9
- package/package.json +1 -1
package/lib/engine-login.ts
CHANGED
|
@@ -555,20 +555,34 @@ export class EngineLoginManager {
|
|
|
555
555
|
/** Validate and replay a Codex callback only to this operation's listener. */
|
|
556
556
|
async callback(opId: string, value: string): Promise<boolean> {
|
|
557
557
|
const operation = this.operations.get(opId);
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
!operation.callbackTarget
|
|
567
|
-
) {
|
|
558
|
+
// Same named-rejection contract as input() (0.9.51): every silent exit
|
|
559
|
+
// here left an operator staring at "Waiting for the host…" with zero
|
|
560
|
+
// evidence on either side (live 2026-08-21 — the callback path was the
|
|
561
|
+
// last breadcrumb-free room in the login pipeline).
|
|
562
|
+
const refuse = (reason: string): false => {
|
|
563
|
+
console.log(
|
|
564
|
+
`[engine-login] callback refused for ${opId}: ${reason}`,
|
|
565
|
+
);
|
|
568
566
|
return false;
|
|
567
|
+
};
|
|
568
|
+
if (!operation) return refuse("unknown opId");
|
|
569
|
+
if (!this.isCurrent(operation)) return refuse("superseded operation");
|
|
570
|
+
if (operation.engine !== "codex") return refuse("wrong engine");
|
|
571
|
+
if (operation.stage !== "awaiting_callback") {
|
|
572
|
+
return refuse(`wrong stage (${operation.stage})`);
|
|
569
573
|
}
|
|
574
|
+
if (operation.callbackUsed) return refuse("callback already used");
|
|
575
|
+
if (operation.finishing) return refuse("operation finishing");
|
|
576
|
+
if (!operation.process) return refuse("no login process");
|
|
577
|
+
if (!operation.callbackTarget) return refuse("no callback target");
|
|
578
|
+
console.log(
|
|
579
|
+
`[engine-login] callback accepted for ${opId} (${value.length} chars); validating`,
|
|
580
|
+
);
|
|
570
581
|
const query = validateCodexCallback(value, operation.callbackTarget);
|
|
571
582
|
if (!query) {
|
|
583
|
+
console.warn(
|
|
584
|
+
`[engine-login] callback failed validation for ${opId} (state/shape mismatch with this attempt's authorize URL)`,
|
|
585
|
+
);
|
|
572
586
|
await this.fail(
|
|
573
587
|
operation,
|
|
574
588
|
"invalid_callback",
|
|
@@ -576,6 +590,9 @@ export class EngineLoginManager {
|
|
|
576
590
|
);
|
|
577
591
|
return false;
|
|
578
592
|
}
|
|
593
|
+
console.log(
|
|
594
|
+
`[engine-login] callback validated for ${opId}; replaying into the login container`,
|
|
595
|
+
);
|
|
579
596
|
operation.callbackUsed = true;
|
|
580
597
|
const callbackUrl = callbackReplayUrl(operation.callbackTarget, query);
|
|
581
598
|
const replay = (async () => {
|
|
@@ -584,6 +601,9 @@ export class EngineLoginManager {
|
|
|
584
601
|
operation.process!.containerName,
|
|
585
602
|
callbackUrl,
|
|
586
603
|
);
|
|
604
|
+
console.log(
|
|
605
|
+
`[engine-login] callback replay delivered for ${operation.opId}; waiting for the CLI to finish`,
|
|
606
|
+
);
|
|
587
607
|
} catch {
|
|
588
608
|
if (this.isCurrent(operation)) {
|
|
589
609
|
await this.fail(
|
|
@@ -2036,6 +2056,13 @@ export function codexCallbackReplayDockerRequest(
|
|
|
2036
2056
|
"--fail",
|
|
2037
2057
|
"--silent",
|
|
2038
2058
|
"--show-error",
|
|
2059
|
+
// Follow the callback's redirect to the CLI's /success page: codex
|
|
2060
|
+
// writes auth.json on the callback but EXITS only after serving that
|
|
2061
|
+
// page — a real browser follows; a replay that stops at the redirect
|
|
2062
|
+
// leaves a completed login waiting forever (live 2026-08-24: the
|
|
2063
|
+
// first-ever completed pane codex login needed a manual /success
|
|
2064
|
+
// fetch to let the CLI exit).
|
|
2065
|
+
"--location",
|
|
2039
2066
|
"--max-time",
|
|
2040
2067
|
"10",
|
|
2041
2068
|
"--output",
|
package/lib/mcp-gateway.ts
CHANGED
|
@@ -73,21 +73,25 @@ function gatewayPort(value: string | undefined): number {
|
|
|
73
73
|
export const MCP_GATEWAY_PORT = gatewayPort(
|
|
74
74
|
process.env.UAI_MCP_GATEWAY_PORT,
|
|
75
75
|
);
|
|
76
|
-
/** Loopback
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* the
|
|
80
|
-
*
|
|
81
|
-
*
|
|
76
|
+
/** Loopback works only where the container runtime forwards
|
|
77
|
+
* host.docker.internal INTO the host loopback — macOS Docker
|
|
78
|
+
* (Desktop/OrbStack). Native Linux docker resolves host.docker.internal to
|
|
79
|
+
* the bridge gateway IP, where a loopback bind is unreachable: every task's
|
|
80
|
+
* MCP config carried a dead URL and the gateway showed connection-refused
|
|
81
|
+
* from inside containers (live 2026-08-24, first Linux host with an MCP
|
|
82
|
+
* connection). Apple vmnet guests cannot reach host loopback either. Both
|
|
83
|
+
* non-mac-docker cases therefore bind all interfaces — the unguessable
|
|
84
|
+
* per-task path token remains the auth (same posture the operator override
|
|
85
|
+
* has always allowed). Resolved per call: the runtime pin lands before the
|
|
86
|
+
* production listener starts, and a pin never changes within a process. */
|
|
82
87
|
function gatewayBindAddress(): string {
|
|
83
88
|
const configured = process.env.UAI_MCP_GATEWAY_BIND;
|
|
84
89
|
if (configured) return configured;
|
|
85
90
|
// The pin, not the ready-gated identity: the production listener starts
|
|
86
91
|
// during boot while activation still reports `checking`, and a loopback
|
|
87
92
|
// bind chosen then would strand every vmnet guest.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
: "127.0.0.1";
|
|
93
|
+
if (pinnedContainerRuntimeProvider() === "apple-container") return "0.0.0.0";
|
|
94
|
+
return process.platform === "darwin" ? "127.0.0.1" : "0.0.0.0";
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/** Host address a task container dials to reach this gateway. Docker guests
|