@runuai/host 0.9.53 → 0.9.54

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.
Files changed (2) hide show
  1. package/lib/engine-login.ts +50 -22
  2. package/package.json +1 -1
@@ -55,6 +55,9 @@ import {
55
55
  import { ASDF_DATA_VOLUME, STANDARD_IMAGE_TAG } from "./standard-image";
56
56
 
57
57
  export const ENGINE_LOGIN_TIMEOUT_MS = 10 * 60_000;
58
+ /** Delay before the separate submit keystroke — long enough for the TUI to
59
+ * finish ingesting the pasted code chunk, short enough to feel instant. */
60
+ export const SUBMIT_KEYSTROKE_DELAY_MS = 300;
58
61
  // Global serialization keeps a failed per-operation stale-resource sweep from
59
62
  // ever deleting another live login's labeled container or marked temp dir.
60
63
  export const MAX_CONCURRENT_ENGINE_LOGINS = 1;
@@ -459,27 +462,38 @@ export class EngineLoginManager {
459
462
  );
460
463
  operation.inputUsed = true;
461
464
  operation.outputTail = "";
462
- // CARRIAGE RETURN, not newline: `claude setup-token` is an Ink raw-mode
463
- // TUI whose code prompt submits only on \r. A trailing \n types the code
464
- // and then sits forever — reproduced live 2026-08-20 (fake code + \n →
465
- // masked input, no response; the same code + \r → immediate "OAuth
466
- // error: Invalid code"). Every cloud-pane Claude login ever attempted
467
- // hung on exactly this.
468
- const value = `${text}\r`;
469
465
  if (!operation.process) {
470
466
  console.log(
471
467
  `[engine-login] input buffered for ${opId} (login process still starting)`,
472
468
  );
473
- operation.pendingInput = value;
469
+ operation.pendingInput = text;
474
470
  return true;
475
471
  }
472
+ return this.deliverOneTimeCode(operation, text);
473
+ }
474
+
475
+ /**
476
+ * The submit is a SEPARATE, delayed carriage return — both halves of that
477
+ * are load-bearing, reproduced live 2026-08-20/21:
478
+ * - `\r`, not `\n`: the Ink raw-mode prompt only submits on carriage
479
+ * return (a `\n` types the code and sits forever).
480
+ * - a separate delayed write, not one chunk: a real 92-char code + `\r`
481
+ * in one write is treated as PASTE data and the embedded `\r` is
482
+ * swallowed — stars appear, nothing submits (short test codes slipped
483
+ * under the paste threshold, which is why every isolated repro passed
484
+ * while production hung).
485
+ */
486
+ private deliverOneTimeCode(operation: LoginOperation, text: string): boolean {
487
+ const process = operation.process;
488
+ if (!process) return false;
476
489
  try {
477
- operation.process.writeInput(value);
478
- console.log(`[engine-login] input written to the login process for ${opId}`);
479
- return true;
490
+ process.writeInput(text);
491
+ console.log(
492
+ `[engine-login] input written to the login process for ${operation.opId}`,
493
+ );
480
494
  } catch {
481
495
  console.warn(
482
- `[engine-login] input write FAILED for ${opId}: the login process rejected stdin`,
496
+ `[engine-login] input write FAILED for ${operation.opId}: the login process rejected stdin`,
483
497
  );
484
498
  void this.fail(
485
499
  operation,
@@ -488,6 +502,29 @@ export class EngineLoginManager {
488
502
  );
489
503
  return false;
490
504
  }
505
+ const submitTimer = setTimeout(() => {
506
+ if (
507
+ !this.isCurrent(operation) ||
508
+ operation.finishing ||
509
+ operation.process !== process
510
+ ) {
511
+ return;
512
+ }
513
+ try {
514
+ process.writeInput("\r");
515
+ console.log(
516
+ `[engine-login] submit keystroke dispatched for ${operation.opId}`,
517
+ );
518
+ } catch {
519
+ void this.fail(
520
+ operation,
521
+ "invalid_input",
522
+ "The login process could not accept the submit keystroke.",
523
+ );
524
+ }
525
+ }, SUBMIT_KEYSTROKE_DELAY_MS);
526
+ unrefTimer(submitTimer);
527
+ return true;
491
528
  }
492
529
 
493
530
  /** Validate and replay a Codex callback only to this operation's listener. */
@@ -625,16 +662,7 @@ export class EngineLoginManager {
625
662
  if (operation.pendingInput !== null) {
626
663
  const pending = operation.pendingInput;
627
664
  operation.pendingInput = null;
628
- try {
629
- processHandle.writeInput(pending);
630
- } catch {
631
- await this.fail(
632
- operation,
633
- "invalid_input",
634
- "The login process could not accept the one-time code.",
635
- );
636
- return;
637
- }
665
+ if (!this.deliverOneTimeCode(operation, pending)) return;
638
666
  }
639
667
  void processHandle.completion.then(
640
668
  (result) => this.onProcessComplete(operation, result),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.53",
3
+ "version": "0.9.54",
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>",