@runuai/host 0.9.60 → 0.9.61

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.
@@ -169,7 +169,11 @@ export interface EngineLoginManagerOptions {
169
169
  seams?: Partial<EngineLoginSeams>;
170
170
  }
171
171
 
172
- type OperationStage = "starting" | "awaiting_input" | "awaiting_callback";
172
+ type OperationStage =
173
+ | "starting"
174
+ | "awaiting_input"
175
+ | "awaiting_callback"
176
+ | "awaiting_confirmation";
173
177
 
174
178
  interface CodexCallbackTarget {
175
179
  readonly origin: string;
@@ -819,6 +823,25 @@ export class EngineLoginManager {
819
823
  }
820
824
 
821
825
  if (operation.stage === "starting") {
826
+ const device = findCodexDeviceAuthorization(operation.outputTail);
827
+ if (device) {
828
+ console.log(
829
+ `[engine-login] codex device authorization found for ${operation.opId}; awaiting operator confirmation`,
830
+ );
831
+ operation.stage = "awaiting_confirmation";
832
+ safeEmit(
833
+ operation.emit,
834
+ authorizeEvent(operation, device.verificationUrl),
835
+ );
836
+ safeEmit(operation.emit, {
837
+ kind: "engine.login.event",
838
+ opId: operation.opId,
839
+ engine: operation.engine,
840
+ phase: "awaiting_confirmation",
841
+ message: `Enter code ${device.userCode} on the verification page. Nothing to paste here — this completes on its own.`,
842
+ });
843
+ return;
844
+ }
822
845
  const authorization = findCodexAuthorization(operation.outputTail);
823
846
  if (authorization) {
824
847
  // Symmetric with the claude branch: this breadcrumb's ABSENCE was
@@ -1489,6 +1512,24 @@ export function findSafeHttpsUrl(value: string): string | null {
1489
1512
  return null;
1490
1513
  }
1491
1514
 
1515
+ /**
1516
+ * Codex `--device-auth` banner: a verification URL plus a short one-time
1517
+ * code ("AC57-MDV9G" shape). Both the phrase gate and the code shape are
1518
+ * required so container names, hashes, or URL fragments cannot fake a
1519
+ * device prompt. Reads the merged tail — codex prints to stderr.
1520
+ */
1521
+ export function findCodexDeviceAuthorization(
1522
+ value: string,
1523
+ ): { verificationUrl: string; userCode: string } | null {
1524
+ const plain = stripTerminalControl(value);
1525
+ if (!/one-time code|device code/i.test(plain)) return null;
1526
+ const verificationUrl = findSafeHttpsUrl(plain);
1527
+ if (!verificationUrl) return null;
1528
+ const code = /(?:^|\s)([A-Z0-9]{4,8}-[A-Z0-9]{4,10})(?:\s|$)/m.exec(plain);
1529
+ if (!code) return null;
1530
+ return { verificationUrl, userCode: code[1]! };
1531
+ }
1532
+
1492
1533
  export function extractClaudeOAuthToken(value: string): string | null {
1493
1534
  const plain = stripTerminalControl(value);
1494
1535
  // The FULL `sk-ant-oat01-` prefix and a realistic minimum length are both
@@ -1921,7 +1962,12 @@ export function engineLoginContainerArgs(
1921
1962
  // because the typescript went to /dev/null.
1922
1963
  `${LOGIN_MOUNT}/typescript`,
1923
1964
  ]
1924
- : ["codex", "login"];
1965
+ : // Device-code flow: no localhost callback, no paste — the CLI prints
1966
+ // a verification URL + one-time code, the operator confirms on the
1967
+ // provider's page, and the CLI polls to completion (ADR-116 v2,
1968
+ // 2026-08-24). The callback machinery below stays as the fallback
1969
+ // for CLIs that still print the localhost authorize URL.
1970
+ ["codex", "login", "--device-auth"];
1925
1971
  if (engineLoginBackend().apple) {
1926
1972
  // Same containment contract, Apple dialect: read-only root, all caps
1927
1973
  // dropped, a plain tmpfs at /tmp (the Apple CLI takes no mount options —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.60",
3
+ "version": "0.9.61",
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>",
package/src/protocol.ts CHANGED
@@ -345,6 +345,9 @@ export type EngineLoginEventFrame =
345
345
  | "starting"
346
346
  | "awaiting_input"
347
347
  | "awaiting_callback"
348
+ // Device-code flow (codex `--device-auth`): the operator confirms a
349
+ // short one-time code on the provider's page; nothing is pasted back.
350
+ | "awaiting_confirmation"
348
351
  | "succeeded"
349
352
  | "cancelled";
350
353
  url?: never;
@@ -787,6 +790,7 @@ export function isEngineLoginEventFrame(
787
790
  (frame.phase === "starting" ||
788
791
  frame.phase === "awaiting_input" ||
789
792
  frame.phase === "awaiting_callback" ||
793
+ frame.phase === "awaiting_confirmation" ||
790
794
  frame.phase === "succeeded" ||
791
795
  frame.phase === "cancelled") &&
792
796
  !Object.hasOwn(frame, "errorCode")