@runuai/host 0.9.55 → 0.9.56
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 +40 -10
- package/lib/orchestrator.ts +5 -0
- package/package.json +1 -1
- package/scripts/install/linux.ts +16 -2
package/lib/engine-login.ts
CHANGED
|
@@ -125,7 +125,7 @@ export interface EngineLoginSeams {
|
|
|
125
125
|
removeTempDir(path: string): Promise<void>;
|
|
126
126
|
startContainer(
|
|
127
127
|
request: EngineLoginContainerRequest,
|
|
128
|
-
onOutput: (chunk: string) => void,
|
|
128
|
+
onOutput: (chunk: string, stream?: "stdout" | "stderr") => void,
|
|
129
129
|
): Promise<EngineLoginProcess>;
|
|
130
130
|
replayCodexCallback(
|
|
131
131
|
containerName: string,
|
|
@@ -186,6 +186,8 @@ interface LoginOperation {
|
|
|
186
186
|
settlementPromise: Promise<void> | null;
|
|
187
187
|
suppressTerminalEvent: boolean;
|
|
188
188
|
outputTail: string;
|
|
189
|
+
/** stdout-only tail — the credential boundary (stderr never joins it). */
|
|
190
|
+
stdoutTail: string;
|
|
189
191
|
inputUsed: boolean;
|
|
190
192
|
callbackUsed: boolean;
|
|
191
193
|
pendingInput: string | null;
|
|
@@ -393,6 +395,7 @@ export class EngineLoginManager {
|
|
|
393
395
|
settlementPromise: null,
|
|
394
396
|
suppressTerminalEvent: false,
|
|
395
397
|
outputTail: "",
|
|
398
|
+
stdoutTail: "",
|
|
396
399
|
inputUsed: false,
|
|
397
400
|
callbackUsed: false,
|
|
398
401
|
pendingInput: null,
|
|
@@ -462,6 +465,7 @@ export class EngineLoginManager {
|
|
|
462
465
|
);
|
|
463
466
|
operation.inputUsed = true;
|
|
464
467
|
operation.outputTail = "";
|
|
468
|
+
operation.stdoutTail = "";
|
|
465
469
|
if (!operation.process) {
|
|
466
470
|
console.log(
|
|
467
471
|
`[engine-login] input buffered for ${opId} (login process still starting)`,
|
|
@@ -650,7 +654,7 @@ export class EngineLoginManager {
|
|
|
650
654
|
const containerName = `uai-login-${operation.engine}-${this.seams.randomHex(8)}`;
|
|
651
655
|
processHandle = await this.seams.startContainer(
|
|
652
656
|
{ engine: operation.engine, containerName, tempDir },
|
|
653
|
-
(chunk) => this.onOutput(operation, chunk),
|
|
657
|
+
(chunk, stream) => this.onOutput(operation, chunk, stream ?? "stdout"),
|
|
654
658
|
);
|
|
655
659
|
if (!this.isCurrent(operation)) {
|
|
656
660
|
await this.stopDetachedProcess(processHandle);
|
|
@@ -688,13 +692,28 @@ export class EngineLoginManager {
|
|
|
688
692
|
}
|
|
689
693
|
}
|
|
690
694
|
|
|
691
|
-
private onOutput(
|
|
695
|
+
private onOutput(
|
|
696
|
+
operation: LoginOperation,
|
|
697
|
+
chunk: string,
|
|
698
|
+
stream: "stdout" | "stderr" = "stdout",
|
|
699
|
+
): void {
|
|
692
700
|
if (!this.isCurrent(operation) || operation.finishing) return;
|
|
693
701
|
operation.outputTail = appendUtf8Tail(
|
|
694
702
|
operation.outputTail,
|
|
695
703
|
chunk,
|
|
696
704
|
MAX_OUTPUT_TAIL_BYTES,
|
|
697
705
|
);
|
|
706
|
+
if (stream === "stdout") {
|
|
707
|
+
// The CREDENTIAL boundary reads stdout only: stderr chunks (the Apple
|
|
708
|
+
// runtime's XPC chatter) interleaving into a merged tail once produced
|
|
709
|
+
// a corrupted token-shaped capture (live 2026-08-21). The merged tail
|
|
710
|
+
// above stays for URL detection and human-facing evidence.
|
|
711
|
+
operation.stdoutTail = appendUtf8Tail(
|
|
712
|
+
operation.stdoutTail,
|
|
713
|
+
chunk,
|
|
714
|
+
MAX_OUTPUT_TAIL_BYTES,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
698
717
|
|
|
699
718
|
if (operation.engine === "claude") {
|
|
700
719
|
if (operation.inputUsed && !operation.postInputOutputSeen) {
|
|
@@ -724,13 +743,14 @@ export class EngineLoginManager {
|
|
|
724
743
|
// be an authorization URL/detail or hostile terminal content. This also
|
|
725
744
|
// prevents an input echo from becoming the credential boundary.
|
|
726
745
|
const token = operation.inputUsed
|
|
727
|
-
? extractClaudeOAuthToken(operation.
|
|
746
|
+
? extractClaudeOAuthToken(operation.stdoutTail)
|
|
728
747
|
: null;
|
|
729
748
|
if (token) {
|
|
730
749
|
console.log(
|
|
731
750
|
`[engine-login] token detected in CLI output for ${operation.opId}; persisting`,
|
|
732
751
|
);
|
|
733
752
|
operation.outputTail = "";
|
|
753
|
+
operation.stdoutTail = "";
|
|
734
754
|
this.beginClaudeFinish(operation, token);
|
|
735
755
|
return;
|
|
736
756
|
}
|
|
@@ -990,6 +1010,7 @@ export class EngineLoginManager {
|
|
|
990
1010
|
private detach(operation: LoginOperation): void {
|
|
991
1011
|
operation.active = false;
|
|
992
1012
|
operation.outputTail = "";
|
|
1013
|
+
operation.stdoutTail = "";
|
|
993
1014
|
operation.pendingInput = null;
|
|
994
1015
|
operation.callbackTarget = null;
|
|
995
1016
|
this.clearOperationTimer(operation);
|
|
@@ -1247,9 +1268,18 @@ export function findSafeHttpsUrl(value: string): string | null {
|
|
|
1247
1268
|
|
|
1248
1269
|
export function extractClaudeOAuthToken(value: string): string | null {
|
|
1249
1270
|
const plain = stripTerminalControl(value);
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
)
|
|
1271
|
+
// The FULL `sk-ant-oat01-` prefix and a realistic minimum length are both
|
|
1272
|
+
// load-bearing: the login CLI's stderr (the Apple runtime's XPC chatter is
|
|
1273
|
+
// full of the word "token") interleaves with stdout in the captured tail,
|
|
1274
|
+
// and a permissive scan once matched a corrupted hybrid — `sk-ant-at01-…`,
|
|
1275
|
+
// one character short, token-shaped, wrong — which persisted as a
|
|
1276
|
+
// "successful" login whose agents then 401'd forever (live 2026-08-21).
|
|
1277
|
+
// A strict prefix makes a mangled interleave a non-match, and the CLI
|
|
1278
|
+
// prints the token again on its plain summary line, which then matches.
|
|
1279
|
+
const match =
|
|
1280
|
+
/(?:^|[^A-Za-z0-9_-])(sk-ant-oat01-[A-Za-z0-9_-]{64,4096})(?![A-Za-z0-9_-])/.exec(
|
|
1281
|
+
plain,
|
|
1282
|
+
);
|
|
1253
1283
|
return match?.[1] ?? null;
|
|
1254
1284
|
}
|
|
1255
1285
|
|
|
@@ -1388,7 +1418,7 @@ function callbackReplayUrl(
|
|
|
1388
1418
|
|
|
1389
1419
|
async function startLoginContainer(
|
|
1390
1420
|
request: EngineLoginContainerRequest,
|
|
1391
|
-
onOutput: (chunk: string) => void,
|
|
1421
|
+
onOutput: (chunk: string, stream?: "stdout" | "stderr") => void,
|
|
1392
1422
|
): Promise<EngineLoginProcess> {
|
|
1393
1423
|
const args = engineLoginContainerArgs(request);
|
|
1394
1424
|
const child = spawn(engineLoginBackend().command, args, {
|
|
@@ -1397,8 +1427,8 @@ async function startLoginContainer(
|
|
|
1397
1427
|
});
|
|
1398
1428
|
child.stdout.setEncoding("utf8");
|
|
1399
1429
|
child.stderr.setEncoding("utf8");
|
|
1400
|
-
child.stdout.on("data", (chunk: string) => onOutput(chunk));
|
|
1401
|
-
child.stderr.on("data", (chunk: string) => onOutput(chunk));
|
|
1430
|
+
child.stdout.on("data", (chunk: string) => onOutput(chunk, "stdout"));
|
|
1431
|
+
child.stderr.on("data", (chunk: string) => onOutput(chunk, "stderr"));
|
|
1402
1432
|
child.stdin.on("error", () => {});
|
|
1403
1433
|
|
|
1404
1434
|
let stopped = false;
|
package/lib/orchestrator.ts
CHANGED
|
@@ -2299,6 +2299,11 @@ export class Orchestrator {
|
|
|
2299
2299
|
// Token revoked/expired: the env-injected credential only changes on
|
|
2300
2300
|
// a fresh runner. Re-spawn on the re-resolved account (picks up a
|
|
2301
2301
|
// reconnected token); bounded so a still-bad token can't loop.
|
|
2302
|
+
// Log the matched evidence — the 2026-08-21 corrupted-token loop was
|
|
2303
|
+
// diagnosed blind because the trigger text never appeared anywhere.
|
|
2304
|
+
console.warn(
|
|
2305
|
+
`[orchestrator] ${channel.taskId}/${agentId}: claude auth-revoked pattern matched: ${event.message.slice(0, 300)}`,
|
|
2306
|
+
);
|
|
2302
2307
|
await this.refreshAgentToken(channel, agent);
|
|
2303
2308
|
break;
|
|
2304
2309
|
}
|
package/package.json
CHANGED
package/scripts/install/linux.ts
CHANGED
|
@@ -545,7 +545,7 @@ export async function prepareLinuxDefinitionInstallRollback(
|
|
|
545
545
|
}
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
-
function assertSystemdDisabledForRollback(
|
|
548
|
+
export function assertSystemdDisabledForRollback(
|
|
549
549
|
execute: StepExecutor,
|
|
550
550
|
allowMissing: boolean,
|
|
551
551
|
): void {
|
|
@@ -553,11 +553,25 @@ function assertSystemdDisabledForRollback(
|
|
|
553
553
|
const disabled = execute(false, "systemctl", disableArgs);
|
|
554
554
|
if (disabled.status === 0) return;
|
|
555
555
|
if (allowMissing) {
|
|
556
|
+
// Disabling a unit whose FILE is already gone answers with stderr-only
|
|
557
|
+
// "does not exist" on AL2023 (live 2026-08-21: a poisoned rollback from
|
|
558
|
+
// an earlier failed install could never complete, wedging every later
|
|
559
|
+
// install at "could not begin"). Gone is as disabled as it gets.
|
|
560
|
+
if (/does not exist|no such file or directory/i.test(disabled.stderr)) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
556
563
|
const probeArgs = ["--user", "is-enabled", UNIT];
|
|
557
564
|
const probe = execute(false, "systemctl", probeArgs);
|
|
565
|
+
const probeState = probe.stdout.trim();
|
|
566
|
+
const neverInstalled =
|
|
567
|
+
probe.status !== 0 &&
|
|
568
|
+
probeState === "" &&
|
|
569
|
+
/no such file or directory|failed to get unit file state/i.test(
|
|
570
|
+
probe.stderr,
|
|
571
|
+
);
|
|
558
572
|
if (
|
|
559
573
|
probe.status !== 0 &&
|
|
560
|
-
DISABLED_SYSTEMD_STATES.has(
|
|
574
|
+
(DISABLED_SYSTEMD_STATES.has(probeState) || neverInstalled)
|
|
561
575
|
) {
|
|
562
576
|
return;
|
|
563
577
|
}
|