@runuai/host 0.9.51 → 0.9.52
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 +55 -1
- package/package.json +1 -1
package/lib/engine-login.ts
CHANGED
|
@@ -190,6 +190,10 @@ interface LoginOperation {
|
|
|
190
190
|
/** ADR-116: when set, the captured credential lands in a labeled extra
|
|
191
191
|
* account instead of the default slot. */
|
|
192
192
|
accountLabel: string | null;
|
|
193
|
+
/** Diagnosis breadcrumb: has ANY output arrived after the one-time code
|
|
194
|
+
* was written? Distinguishes "stdin never delivered" from "CLI answered
|
|
195
|
+
* and we ignored it" without reading the PTY transcript. */
|
|
196
|
+
postInputOutputSeen: boolean;
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
const defaultTimers: EngineLoginTimers = {
|
|
@@ -391,6 +395,7 @@ export class EngineLoginManager {
|
|
|
391
395
|
pendingInput: null,
|
|
392
396
|
callbackTarget: null,
|
|
393
397
|
accountLabel: label ?? null,
|
|
398
|
+
postInputOutputSeen: false,
|
|
394
399
|
};
|
|
395
400
|
this.operations.set(opId, operation);
|
|
396
401
|
this.engineOperations.set(engine, operation);
|
|
@@ -664,6 +669,29 @@ export class EngineLoginManager {
|
|
|
664
669
|
);
|
|
665
670
|
|
|
666
671
|
if (operation.engine === "claude") {
|
|
672
|
+
if (operation.inputUsed && !operation.postInputOutputSeen) {
|
|
673
|
+
operation.postInputOutputSeen = true;
|
|
674
|
+
console.log(
|
|
675
|
+
`[engine-login] output resumed after input for ${operation.opId} — the code reached the login TUI`,
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
// The TUI answers a bad/expired/mismatched code with an error AND an
|
|
679
|
+
// open prompt — no exit, no token. Detect it and fail visibly instead
|
|
680
|
+
// of the silent wait-for-timeout that hid the 2026-08-20 hangs.
|
|
681
|
+
if (operation.inputUsed && !operation.finishing) {
|
|
682
|
+
const cliError = detectClaudeCliError(operation.outputTail);
|
|
683
|
+
if (cliError) {
|
|
684
|
+
console.warn(
|
|
685
|
+
`[engine-login] login CLI rejected the code for ${operation.opId}: ${cliError}`,
|
|
686
|
+
);
|
|
687
|
+
void this.fail(
|
|
688
|
+
operation,
|
|
689
|
+
"invalid_input",
|
|
690
|
+
`The sign-in CLI rejected the code: ${cliError} Make sure the code comes from THIS attempt's authorization page, then retry.`,
|
|
691
|
+
);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
667
695
|
// Before the one-time input is consumed, token-shaped output could only
|
|
668
696
|
// be an authorization URL/detail or hostile terminal content. This also
|
|
669
697
|
// prevents an input echo from becoming the credential boundary.
|
|
@@ -1047,6 +1075,27 @@ export function createEngineLoginManager(
|
|
|
1047
1075
|
return new EngineLoginManager(options);
|
|
1048
1076
|
}
|
|
1049
1077
|
|
|
1078
|
+
/** Detect the TUI's post-input rejection ("OAuth error: Invalid code…",
|
|
1079
|
+
* expired codes, …) in the sanitized output. ANSI stripping can eat spaces
|
|
1080
|
+
* ("Invalidcode"), so the patterns tolerate missing whitespace. Exported for
|
|
1081
|
+
* tests. */
|
|
1082
|
+
export function detectClaudeCliError(tail: string): string | null {
|
|
1083
|
+
const lines = tail
|
|
1084
|
+
.replace(/\u001b\[[0-9;?]*[ -\/]*[@-~]/g, "")
|
|
1085
|
+
.replace(/[\u0000-\u0009\u000b-\u001f\u007f]/g, "")
|
|
1086
|
+
.split("\n")
|
|
1087
|
+
.map((line) => line.trim())
|
|
1088
|
+
.filter(Boolean);
|
|
1089
|
+
const match = lines.find((line) =>
|
|
1090
|
+
/oauth\s*error|invalid\s*cod|code\s*.{0,12}expired|authentication\s*failed/i.test(
|
|
1091
|
+
line,
|
|
1092
|
+
),
|
|
1093
|
+
);
|
|
1094
|
+
if (!match) return null;
|
|
1095
|
+
if (/sk-ant-|Bearer |[A-Za-z0-9_-]{40,}/.test(match)) return null;
|
|
1096
|
+
return match.slice(0, 200);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1050
1099
|
/** The last visible, sanitized line of the login CLI's output — bounded and
|
|
1051
1100
|
* secret-checked so it can ride a terminal event message (ADR-116). Anything
|
|
1052
1101
|
* token-shaped disqualifies the whole line rather than risking a partial. */
|
|
@@ -1440,7 +1489,12 @@ export function engineLoginContainerArgs(
|
|
|
1440
1489
|
// a client id (live 2026-08-18). stty targets the PTY `script`
|
|
1441
1490
|
// just allocated.
|
|
1442
1491
|
"stty cols 500 rows 50 2>/dev/null; exec claude setup-token",
|
|
1443
|
-
|
|
1492
|
+
// The PTY transcript lands in the op's private bind leaf (same
|
|
1493
|
+
// trust class as the codex auth.json captured there; the leaf is
|
|
1494
|
+
// 0700 and deleted at cleanup). A stuck flow can then be read
|
|
1495
|
+
// VERBATIM from the host — the 2026-08-20 hang was undiagnosable
|
|
1496
|
+
// because the typescript went to /dev/null.
|
|
1497
|
+
`${LOGIN_MOUNT}/typescript`,
|
|
1444
1498
|
]
|
|
1445
1499
|
: ["codex", "login"];
|
|
1446
1500
|
if (engineLoginBackend().apple) {
|