@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.
@@ -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
- if (
559
- !operation ||
560
- !this.isCurrent(operation) ||
561
- operation.engine !== "codex" ||
562
- operation.stage !== "awaiting_callback" ||
563
- operation.callbackUsed ||
564
- operation.finishing ||
565
- !operation.process ||
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",
@@ -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 for Docker (Desktop/OrbStack forward host.docker.internal to it).
77
- * Apple vmnet guests cannot reach host loopback at all, so the apple backend
78
- * defaults to all interfaces the unguessable per-task path token remains
79
- * the auth (same posture the operator override has always allowed). Resolved
80
- * per call: the runtime pin lands before the production listener starts, and
81
- * a pin never changes within a process. */
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
- return pinnedContainerRuntimeProvider() === "apple-container"
89
- ? "0.0.0.0"
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.59",
3
+ "version": "0.9.60",
4
4
  "description": "Uai host — runs ephemeral AI tasks in containers on a machine you control.",
5
5
  "license": "MIT",
6
6
  "author": "Uai Tech <team@runuai.com>",