mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 07:47:39 +00:00
TUI: guide pairing-required recovery in disconnect state
This commit is contained in:
@@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Gateway/Pairing: tolerate legacy paired devices missing `roles`/`scopes` metadata in websocket upgrade checks and backfill metadata on reconnect. (#21447, fixes #21236) Thanks @joshavant.
|
- Gateway/Pairing: tolerate legacy paired devices missing `roles`/`scopes` metadata in websocket upgrade checks and backfill metadata on reconnect. (#21447, fixes #21236) Thanks @joshavant.
|
||||||
- Gateway/Pairing/CLI: align read-scope compatibility in pairing/device-token checks and add local `openclaw devices` fallback recovery for loopback `pairing required` deadlocks, with explicit fallback notice to unblock approval bootstrap flows. (#21616) Thanks @shakkernerd.
|
- Gateway/Pairing/CLI: align read-scope compatibility in pairing/device-token checks and add local `openclaw devices` fallback recovery for loopback `pairing required` deadlocks, with explicit fallback notice to unblock approval bootstrap flows. (#21616) Thanks @shakkernerd.
|
||||||
- CLI/Pairing: default `pairing list` and `pairing approve` to the sole available pairing channel when omitted, so TUI-only setups can recover from `pairing required` without guessing channel arguments. (#21527) Thanks @losts1.
|
- CLI/Pairing: default `pairing list` and `pairing approve` to the sole available pairing channel when omitted, so TUI-only setups can recover from `pairing required` without guessing channel arguments. (#21527) Thanks @losts1.
|
||||||
|
- TUI/Pairing: show explicit pairing-required recovery guidance after gateway disconnects that return `pairing required`, including approval steps to unblock quickstart TUI hatching on fresh installs. (#21841) Thanks @nicolinux.
|
||||||
- Auth/Onboarding: align OAuth profile-id config mapping with stored credential IDs for OpenAI Codex and Chutes flows, preventing `provider:default` mismatches when OAuth returns email-scoped credentials. (#12692) thanks @mudrii.
|
- Auth/Onboarding: align OAuth profile-id config mapping with stored credential IDs for OpenAI Codex and Chutes flows, preventing `provider:default` mismatches when OAuth returns email-scoped credentials. (#12692) thanks @mudrii.
|
||||||
- Docker: pin base images to SHA256 digests in Docker builds to prevent mutable tag drift. (#7734) Thanks @coygeek.
|
- Docker: pin base images to SHA256 digests in Docker builds to prevent mutable tag drift. (#7734) Thanks @coygeek.
|
||||||
- Docker/Security: run E2E and install-sh test images as non-root by adding appuser directives. Thanks @thewilloftheshadow.
|
- Docker/Security: run E2E and install-sh test images as non-root by adding appuser directives. Thanks @thewilloftheshadow.
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { getSlashCommands, parseCommand } from "./commands.js";
|
import { getSlashCommands, parseCommand } from "./commands.js";
|
||||||
import { resolveFinalAssistantText, resolveTuiSessionKey } from "./tui.js";
|
import {
|
||||||
|
resolveFinalAssistantText,
|
||||||
|
resolveGatewayDisconnectState,
|
||||||
|
resolveTuiSessionKey,
|
||||||
|
} from "./tui.js";
|
||||||
|
|
||||||
describe("resolveFinalAssistantText", () => {
|
describe("resolveFinalAssistantText", () => {
|
||||||
it("falls back to streamed text when final text is empty", () => {
|
it("falls back to streamed text when final text is empty", () => {
|
||||||
@@ -67,3 +71,19 @@ describe("resolveTuiSessionKey", () => {
|
|||||||
).toBe("agent:ops:incident");
|
).toBe("agent:ops:incident");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("resolveGatewayDisconnectState", () => {
|
||||||
|
it("returns pairing recovery guidance when disconnect reason requires pairing", () => {
|
||||||
|
const state = resolveGatewayDisconnectState("gateway closed (1008): pairing required");
|
||||||
|
expect(state.connectionStatus).toContain("pairing required");
|
||||||
|
expect(state.activityStatus).toBe("pairing required: run openclaw devices list");
|
||||||
|
expect(state.pairingHint).toContain("openclaw devices list");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to idle for generic disconnect reasons", () => {
|
||||||
|
const state = resolveGatewayDisconnectState("network timeout");
|
||||||
|
expect(state.connectionStatus).toBe("gateway disconnected: network timeout");
|
||||||
|
expect(state.activityStatus).toBe("idle");
|
||||||
|
expect(state.pairingHint).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -195,6 +195,26 @@ export function resolveTuiSessionKey(params: {
|
|||||||
return `agent:${params.currentAgentId}:${trimmed}`;
|
return `agent:${params.currentAgentId}:${trimmed}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveGatewayDisconnectState(reason?: string): {
|
||||||
|
connectionStatus: string;
|
||||||
|
activityStatus: string;
|
||||||
|
pairingHint?: string;
|
||||||
|
} {
|
||||||
|
const reasonLabel = reason?.trim() ? reason.trim() : "closed";
|
||||||
|
if (/pairing required/i.test(reasonLabel)) {
|
||||||
|
return {
|
||||||
|
connectionStatus: `gateway disconnected: ${reasonLabel}`,
|
||||||
|
activityStatus: "pairing required: run openclaw devices list",
|
||||||
|
pairingHint:
|
||||||
|
"Pairing required. Run `openclaw devices list`, approve your request ID, then reconnect.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
connectionStatus: `gateway disconnected: ${reasonLabel}`,
|
||||||
|
activityStatus: "idle",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function runTui(opts: TuiOptions) {
|
export async function runTui(opts: TuiOptions) {
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const initialSessionInput = (opts.session ?? "").trim();
|
const initialSessionInput = (opts.session ?? "").trim();
|
||||||
@@ -213,6 +233,7 @@ export async function runTui(opts: TuiOptions) {
|
|||||||
let wasDisconnected = false;
|
let wasDisconnected = false;
|
||||||
let toolsExpanded = false;
|
let toolsExpanded = false;
|
||||||
let showThinking = false;
|
let showThinking = false;
|
||||||
|
let pairingHintShown = false;
|
||||||
const localRunIds = new Set<string>();
|
const localRunIds = new Set<string>();
|
||||||
|
|
||||||
const deliverDefault = opts.deliver ?? false;
|
const deliverDefault = opts.deliver ?? false;
|
||||||
@@ -772,6 +793,7 @@ export async function runTui(opts: TuiOptions) {
|
|||||||
|
|
||||||
client.onConnected = () => {
|
client.onConnected = () => {
|
||||||
isConnected = true;
|
isConnected = true;
|
||||||
|
pairingHintShown = false;
|
||||||
const reconnected = wasDisconnected;
|
const reconnected = wasDisconnected;
|
||||||
wasDisconnected = false;
|
wasDisconnected = false;
|
||||||
setConnectionStatus("connected");
|
setConnectionStatus("connected");
|
||||||
@@ -794,9 +816,13 @@ export async function runTui(opts: TuiOptions) {
|
|||||||
isConnected = false;
|
isConnected = false;
|
||||||
wasDisconnected = true;
|
wasDisconnected = true;
|
||||||
historyLoaded = false;
|
historyLoaded = false;
|
||||||
const reasonLabel = reason?.trim() ? reason.trim() : "closed";
|
const disconnectState = resolveGatewayDisconnectState(reason);
|
||||||
setConnectionStatus(`gateway disconnected: ${reasonLabel}`, 5000);
|
setConnectionStatus(disconnectState.connectionStatus, 5000);
|
||||||
setActivityStatus("idle");
|
setActivityStatus(disconnectState.activityStatus);
|
||||||
|
if (disconnectState.pairingHint && !pairingHintShown) {
|
||||||
|
pairingHintShown = true;
|
||||||
|
chatLog.addSystem(disconnectState.pairingHint);
|
||||||
|
}
|
||||||
updateFooter();
|
updateFooter();
|
||||||
tui.requestRender();
|
tui.requestRender();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user