This commit is contained in:
Cyrus Goh 2026-01-25 14:09:30 -08:00
parent a8560afedc
commit 07650765d2
No known key found for this signature in database
GPG Key ID: D547D2AF3FD0FD5A
23 changed files with 51 additions and 21 deletions

View File

@ -8,6 +8,7 @@ import type { LineReplyMessage, SendLineReplyChunksParams } from "./reply-chunks
export type LineAutoReplyDeps = {
buildTemplateMessageFromPayload: (
payload: LineTemplateMessagePayload,
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- SDK types
) => messagingApi.TemplateMessage | null;
processLineMessage: (text: string) => ProcessedLineMessage;
chunkMarkdownText: (text: string, limit: number) => string[];
@ -168,6 +169,7 @@ export async function deliverLineAutoReply(params: {
const quickReply = deps.createQuickReplyItems(lineData.quickReplies!);
const targetIndex =
replyToken && !replyTokenUsed ? Math.min(4, combined.length - 1) : combined.length - 1;
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- SDK types
const target = combined[targetIndex] as messagingApi.Message & {
quickReply?: messagingApi.QuickReply;
};

View File

@ -123,6 +123,7 @@ async function sendLinePairingReply(params: {
}
async function shouldProcessLineEvent(
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- SDK types
event: MessageEvent | PostbackEvent,
context: LineHandlerContext,
): Promise<boolean> {

View File

@ -86,12 +86,14 @@ const STICKER_PACKAGES: Record<string, string> = {
function describeStickerKeywords(sticker: StickerEventMessage): string {
// Use sticker keywords if available (LINE provides these for some stickers)
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- SDK types
const keywords = (sticker as StickerEventMessage & { keywords?: string[] }).keywords;
if (keywords && keywords.length > 0) {
return keywords.slice(0, 3).join(", ");
}
// Use sticker text if available
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- SDK types
const stickerText = (sticker as StickerEventMessage & { text?: string }).text;
if (stickerText) {
return stickerText;

View File

@ -291,6 +291,7 @@ export async function getRichMenuList(opts: RichMenuOpts = {}): Promise<RichMenu
export async function getRichMenu(
richMenuId: string,
opts: RichMenuOpts = {},
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- SDK types
): Promise<RichMenuResponse | null> {
const client = getClient(opts);

View File

@ -531,6 +531,7 @@ export function createQuickReplyItems(labels: string[]): QuickReply {
export function createTextMessageWithQuickReplies(
text: string,
quickReplyLabels: string[],
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- SDK types
): TextMessage & { quickReply: QuickReply } {
return {
type: "text",

View File

@ -324,6 +324,7 @@ import type { LineTemplateMessagePayload } from "./types.js";
*/
export function buildTemplateMessageFromPayload(
payload: LineTemplateMessagePayload,
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- SDK types
): TemplateMessage | null {
switch (payload.type) {
case "confirm": {

View File

@ -61,6 +61,7 @@ export interface ResolvedLineAccount {
config: LineConfig & LineAccountConfig;
}
/* eslint-disable @typescript-eslint/no-redundant-type-constituents -- SDK types */
export type LineMessageType =
| TextMessage
| ImageMessage
@ -68,6 +69,7 @@ export type LineMessageType =
| AudioMessage
| StickerMessage
| LocationMessage;
/* eslint-enable @typescript-eslint/no-redundant-type-constituents */
export interface LineWebhookContext {
event: WebhookEvent;

View File

@ -23,6 +23,7 @@ function readRawBody(req: Request): string | null {
return Buffer.isBuffer(rawBody) ? rawBody.toString("utf-8") : rawBody;
}
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- SDK types
function parseWebhookBody(req: Request, rawBody: string): WebhookRequestBody | null {
if (req.body && typeof req.body === "object" && !Buffer.isBuffer(req.body)) {
return req.body as WebhookRequestBody;

View File

@ -67,7 +67,11 @@ function formatToolOutput(value: unknown): string | null {
try {
text = JSON.stringify(value, null, 2);
} catch {
text = String(value);
if (typeof value === "object" && value !== null) {
text = "[object]";
} else {
text = String(value as string | number | boolean | symbol | bigint);
}
}
}
const truncated = truncateText(text, TOOL_OUTPUT_CHAR_LIMIT);

View File

@ -79,7 +79,7 @@ export type AppViewState = {
configApplying: boolean;
updateRunning: boolean;
configSnapshot: ConfigSnapshot | null;
configSchema: unknown | null;
configSchema: unknown;
configSchemaLoading: boolean;
configUiHints: Record<string, unknown>;
configForm: Record<string, unknown> | null;
@ -129,7 +129,7 @@ export type AppViewState = {
debugStatus: StatusSummary | null;
debugHealth: HealthSnapshot | null;
debugModels: unknown[];
debugHeartbeat: unknown | null;
debugHeartbeat: unknown;
debugCallMethod: string;
debugCallParams: string;
debugCallResult: string | null;

View File

@ -160,7 +160,7 @@ export class ClawdbotApp extends LitElement {
@state() updateRunning = false;
@state() applySessionKey = this.settings.lastActiveSessionKey;
@state() configSnapshot: ConfigSnapshot | null = null;
@state() configSchema: unknown | null = null;
@state() configSchema: unknown = null;
@state() configSchemaVersion: string | null = null;
@state() configSchemaLoading = false;
@state() configUiHints: ConfigUiHints = {};
@ -221,7 +221,7 @@ export class ClawdbotApp extends LitElement {
@state() debugStatus: StatusSummary | null = null;
@state() debugHealth: HealthSnapshot | null = null;
@state() debugModels: unknown[] = [];
@state() debugHeartbeat: unknown | null = null;
@state() debugHeartbeat: unknown = null;
@state() debugCallMethod = "";
@state() debugCallParams = "{}";
@state() debugCallResult: string | null = null;

View File

@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { ClawdbotApp } from "./app";
// eslint-disable-next-line @typescript-eslint/unbound-method
const originalConnect = ClawdbotApp.prototype.connect;
function mountApp(pathname: string) {

View File

@ -21,7 +21,8 @@ export function normalizeMessage(message: unknown): NormalizedMessage {
Array.isArray(contentItems) &&
contentItems.some((item) => {
const x = item as Record<string, unknown>;
const t = String(x.type ?? "").toLowerCase();
const rawType = x.type;
const t = (typeof rawType === "string" ? rawType : "").toLowerCase();
return t === "toolresult" || t === "tool_result";
});

View File

@ -13,7 +13,8 @@ export function extractToolCards(message: unknown): ToolCard[] {
const cards: ToolCard[] = [];
for (const item of content) {
const kind = String(item.type ?? "").toLowerCase();
const rawType = item.type;
const kind = (typeof rawType === "string" ? rawType : "").toLowerCase();
const isToolCall =
["toolcall", "tool_call", "tooluse", "tool_use"].includes(kind) ||
(typeof item.name === "string" && item.arguments != null);
@ -27,7 +28,8 @@ export function extractToolCards(message: unknown): ToolCard[] {
}
for (const item of content) {
const kind = String(item.type ?? "").toLowerCase();
const rawKind = item.type;
const kind = (typeof rawKind === "string" ? rawKind : "").toLowerCase();
if (kind !== "toolresult" && kind !== "tool_result") continue;
const text = extractToolText(item);
const name = typeof item.name === "string" ? item.name : "tool";

View File

@ -20,7 +20,7 @@ export type ConfigState = {
configApplying: boolean;
updateRunning: boolean;
configSnapshot: ConfigSnapshot | null;
configSchema: unknown | null;
configSchema: unknown;
configSchemaVersion: string | null;
configSchemaLoading: boolean;
configUiHints: ConfigUiHints;

View File

@ -8,7 +8,7 @@ export type DebugState = {
debugStatus: StatusSummary | null;
debugHealth: HealthSnapshot | null;
debugModels: unknown[];
debugHeartbeat: unknown | null;
debugHeartbeat: unknown;
debugCallMethod: string;
debugCallParams: string;
debugCallResult: string | null;

View File

@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { ClawdbotApp } from "./app";
// eslint-disable-next-line @typescript-eslint/unbound-method
const originalConnect = ClawdbotApp.prototype.connect;
function mountApp(pathname: string) {

View File

@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { ClawdbotApp } from "./app";
import "../styles.css";
// eslint-disable-next-line @typescript-eslint/unbound-method
const originalConnect = ClawdbotApp.prototype.connect;
function mountApp(pathname: string) {

View File

@ -31,7 +31,8 @@ export function formatEventPayload(payload: unknown): string {
try {
return JSON.stringify(payload, null, 2);
} catch {
return String(payload);
if (typeof payload === "object" && payload !== null) return "[object]";
return String(payload as string | number | boolean | symbol | bigint);
}
}

View File

@ -7,7 +7,7 @@ import { analyzeConfigSchema, renderNode, schemaType, type JsonSchema } from "./
type ChannelConfigFormProps = {
channelId: string;
configValue: Record<string, unknown> | null;
schema: unknown | null;
schema: unknown;
uiHints: ConfigUiHints;
disabled: boolean;
onPatch: (path: Array<string | number>, value: unknown) => void;

View File

@ -26,7 +26,7 @@ export type ChannelsProps = {
whatsappQrDataUrl: string | null;
whatsappConnected: boolean | null;
whatsappBusy: boolean;
configSchema: unknown | null;
configSchema: unknown;
configSchemaLoading: boolean;
configForm: Record<string, unknown> | null;
configUiHints: ConfigUiHints;

View File

@ -130,7 +130,7 @@ export function renderNode(params: {
}
// Check if it's a set of literal values (enum-like)
const extractLiteral = (v: JsonSchema): unknown | undefined => {
const extractLiteral = (v: JsonSchema): unknown => {
if (v.const !== undefined) return v.const;
if (v.enum && v.enum.length === 1) return v.enum[0];
return undefined;
@ -141,6 +141,8 @@ export function renderNode(params: {
if (allLiterals && literals.length > 0 && literals.length <= 5) {
// Use segmented control for small sets
const resolvedValue = value ?? schema.default;
const stringify = (v: unknown) =>
typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? String(v) : "";
return html`
<div class="cfg-field">
${showLabel ? html`<label class="cfg-field__label">${label}</label>` : nothing}
@ -150,11 +152,11 @@ export function renderNode(params: {
(lit) => html`
<button
type="button"
class="cfg-segmented__btn ${lit === resolvedValue || String(lit) === String(resolvedValue) ? "active" : ""}"
class="cfg-segmented__btn ${lit === resolvedValue || stringify(lit) === stringify(resolvedValue) ? "active" : ""}"
?disabled=${disabled}
@click=${() => onPatch(path, lit)}
>
${String(lit)}
${stringify(lit)}
</button>
`,
)}
@ -296,9 +298,15 @@ function renderTextInput(params: {
const label = hint?.label ?? schema.title ?? humanize(String(path.at(-1)));
const help = hint?.help ?? schema.description;
const isSensitive = hint?.sensitive ?? isSensitivePath(path);
const defaultStr =
schema.default !== undefined &&
(typeof schema.default === "string" ||
typeof schema.default === "number" ||
typeof schema.default === "boolean")
? String(schema.default)
: undefined;
const placeholder =
hint?.placeholder ??
(isSensitive ? "••••" : schema.default !== undefined ? `Default: ${schema.default}` : "");
hint?.placeholder ?? (isSensitive ? "••••" : defaultStr ? `Default: ${defaultStr}` : "");
const displayValue = value ?? "";
return html`
@ -310,7 +318,7 @@ function renderTextInput(params: {
type=${isSensitive ? "password" : inputType}
class="cfg-input"
placeholder=${placeholder}
.value=${displayValue == null ? "" : String(displayValue)}
.value=${displayValue == null ? "" : typeof displayValue === "string" || typeof displayValue === "number" ? String(displayValue) : ""}
?disabled=${disabled}
@input=${(e: Event) => {
const raw = (e.target as HTMLInputElement).value;
@ -375,7 +383,7 @@ function renderNumberInput(params: {
<input
type="number"
class="cfg-number__input"
.value=${displayValue == null ? "" : String(displayValue)}
.value=${displayValue == null ? "" : typeof displayValue === "string" || typeof displayValue === "number" ? String(displayValue) : ""}
?disabled=${disabled}
@input=${(e: Event) => {
const raw = (e.target as HTMLInputElement).value;

View File

@ -13,7 +13,7 @@ export type ConfigProps = {
applying: boolean;
updating: boolean;
connected: boolean;
schema: unknown | null;
schema: unknown;
schemaLoading: boolean;
uiHints: ConfigUiHints;
formMode: "form" | "raw";