mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(docker): keep gateway ws client self-contained (#107561)
* fix(docker): keep gateway ws client self-contained * test(raft): narrow optional runtime schema * test(tlon): narrow signed URL mock input * fix(tui): type searchable selector metadata
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
// Gateway Ws Client script supports OpenClaw repository automation.
|
||||
import { Buffer } from "node:buffer";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import WebSocket from "ws";
|
||||
import { rawDataToString } from "../../src/infra/ws.js";
|
||||
|
||||
// Release Docker images ship this script without src/, so keep transport
|
||||
// normalization self-contained instead of importing a core-only helper.
|
||||
export function rawDataToString(data: WebSocket.RawData): string {
|
||||
if (Array.isArray(data)) {
|
||||
return Buffer.concat(data).toString("utf8");
|
||||
}
|
||||
return data instanceof ArrayBuffer ? Buffer.from(data).toString("utf8") : data.toString("utf8");
|
||||
}
|
||||
|
||||
type GatewayReqFrame = { type: "req"; id: string; method: string; params?: unknown };
|
||||
type GatewayResFrame = {
|
||||
|
||||
@@ -22,12 +22,16 @@ export interface SearchableSelectListTheme extends SelectListTheme {
|
||||
matchHighlight: (text: string) => string;
|
||||
}
|
||||
|
||||
export interface SearchableSelectItem extends SelectItem {
|
||||
searchText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A select list with a search input at the top for fuzzy filtering.
|
||||
*/
|
||||
export class SearchableSelectList implements Component {
|
||||
private items: SelectItem[];
|
||||
private filteredItems: SelectItem[];
|
||||
private items: SearchableSelectItem[];
|
||||
private filteredItems: SearchableSelectItem[];
|
||||
private selectedIndex = 0;
|
||||
private maxVisible: number;
|
||||
private theme: SearchableSelectListTheme;
|
||||
@@ -44,7 +48,7 @@ export class SearchableSelectList implements Component {
|
||||
// Keep a small right margin so we don't risk wrapping due to styling/terminal quirks.
|
||||
private static readonly RIGHT_MARGIN_WIDTH = 2;
|
||||
|
||||
constructor(items: SelectItem[], maxVisible: number, theme: SearchableSelectListTheme) {
|
||||
constructor(items: SearchableSelectItem[], maxVisible: number, theme: SearchableSelectListTheme) {
|
||||
this.items = items;
|
||||
this.filteredItems = items;
|
||||
this.maxVisible = maxVisible;
|
||||
@@ -81,10 +85,10 @@ export class SearchableSelectList implements Component {
|
||||
* 2. Exact substring in description
|
||||
* 3. Fuzzy match (lowest priority)
|
||||
*/
|
||||
private smartFilter(query: string): SelectItem[] {
|
||||
private smartFilter(query: string): SearchableSelectItem[] {
|
||||
const q = normalizeLowercaseStringOrEmpty(query);
|
||||
type ScoredItem = { item: SelectItem; tier: number; score: number };
|
||||
type FuzzyCandidate = { item: SelectItem; searchText: string };
|
||||
type ScoredItem = { item: SearchableSelectItem; tier: number; score: number };
|
||||
type FuzzyCandidate = { item: SearchableSelectItem; searchText: string };
|
||||
const scoredItems: ScoredItem[] = [];
|
||||
const fuzzyCandidates: FuzzyCandidate[] = [];
|
||||
|
||||
@@ -107,7 +111,7 @@ export class SearchableSelectList implements Component {
|
||||
continue;
|
||||
}
|
||||
// Tier 3: Fuzzy match
|
||||
const searchText = (item as { searchText?: string }).searchText ?? "";
|
||||
const searchText = item.searchText ?? "";
|
||||
fuzzyCandidates.push({
|
||||
item,
|
||||
searchText: normalizeLowercaseStringOrEmpty(
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Selector components adapt Pi TUI list controls for OpenClaw settings.
|
||||
import { type SelectItem, type SettingItem, SettingsList } from "@earendil-works/pi-tui";
|
||||
import { type SettingItem, SettingsList } from "@earendil-works/pi-tui";
|
||||
import {
|
||||
filterableSelectListTheme,
|
||||
searchableSelectListTheme,
|
||||
settingsListTheme,
|
||||
} from "../theme/theme.js";
|
||||
import { FilterableSelectList, type FilterableSelectItem } from "./filterable-select-list.js";
|
||||
import { SearchableSelectList } from "./searchable-select-list.js";
|
||||
import { SearchableSelectList, type SearchableSelectItem } from "./searchable-select-list.js";
|
||||
|
||||
/** Creates a themed searchable select list for TUI overlays. */
|
||||
export function createSearchableSelectList(items: SelectItem[], maxVisible = 7) {
|
||||
export function createSearchableSelectList(items: SearchableSelectItem[], maxVisible = 7) {
|
||||
return new SearchableSelectList(items, maxVisible, searchableSelectListTheme);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createServer, type Server } from "node:http";
|
||||
import type { Duplex } from "node:stream";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import { createGatewayWsClient } from "../../scripts/dev/gateway-ws-client.js";
|
||||
import { createGatewayWsClient, rawDataToString } from "../../scripts/dev/gateway-ws-client.js";
|
||||
|
||||
let server: Server | undefined;
|
||||
let wss: WebSocketServer | undefined;
|
||||
@@ -88,6 +88,14 @@ async function listenStalledUpgrade(): Promise<{ close: () => Promise<void>; url
|
||||
}
|
||||
|
||||
describe("createGatewayWsClient", () => {
|
||||
it("decodes every ws raw-data shape without core source files", () => {
|
||||
expect(rawDataToString(Buffer.from("buffer"))).toBe("buffer");
|
||||
expect(rawDataToString(Uint8Array.from(Buffer.from("array-buffer")).buffer)).toBe(
|
||||
"array-buffer",
|
||||
);
|
||||
expect(rawDataToString([Buffer.from("frag"), Buffer.from("ments")])).toBe("fragments");
|
||||
});
|
||||
|
||||
it("rejects pending RPC requests when the client closes", async () => {
|
||||
const url = await listen(() => {});
|
||||
const client = createGatewayWsClient({ url });
|
||||
|
||||
Reference in New Issue
Block a user