refactor(line): remove dead exports (#107231)

This commit is contained in:
Peter Steinberger
2026-07-13 23:54:12 -07:00
committed by GitHub
parent b3239a5f0f
commit 75cd0efb04
15 changed files with 30 additions and 175 deletions

View File

@@ -2,14 +2,10 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
resolveLineAccount,
resolveDefaultLineAccountId,
normalizeAccountId,
DEFAULT_ACCOUNT_ID,
} from "./accounts.js";
import { resolveLineAccount, resolveDefaultLineAccountId, normalizeAccountId } from "./accounts.js";
describe("LINE accounts", () => {
const tempDirs: string[] = [];

View File

@@ -14,8 +14,6 @@ import type {
ResolvedLineAccount,
} from "./types.js";
export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
function readFileIfExists(filePath: string | undefined): string | undefined {
return tryReadSecretFileSync(filePath, "LINE credential file", { rejectSymlink: true });
}

View File

@@ -1,11 +1,12 @@
// Line tests cover auto reply delivery plugin behavior.
import { expectDefined } from "@openclaw/normalization-core";
import { describe, expect, it, vi } from "vitest";
import type { LineAutoReplyDeps } from "./auto-reply-delivery.js";
import { deliverLineAutoReply } from "./auto-reply-delivery.js";
import { sendLineReplyChunks } from "./reply-chunks.js";
import { createLineSendReceipt } from "./send-receipt.js";
type LineAutoReplyDeps = Parameters<typeof deliverLineAutoReply>[0]["deps"];
const createFlexMessage = (altText: string, contents: unknown) => ({
type: "flex" as const,
altText,

View File

@@ -10,7 +10,7 @@ import { buildLineQuickReplyFallbackText } from "./quick-reply-fallback.js";
import type { SendLineReplyChunksParams } from "./reply-chunks.js";
import type { LineChannelData, LineTemplateMessagePayload } from "./types.js";
export type LineAutoReplyDeps = {
type LineAutoReplyDeps = {
buildTemplateMessageFromPayload: (
payload: LineTemplateMessagePayload,
) => messagingApi.TemplateMessage | null;

View File

@@ -175,7 +175,6 @@ vi.mock("./bot-message-context.js", () => ({
let handleLineWebhookEvents: typeof import("./bot-handlers.js").handleLineWebhookEvents;
let createLineWebhookReplayCache: typeof import("./bot-handlers.js").createLineWebhookReplayCache;
let LineRetryableWebhookError: typeof import("./bot-handlers.js").LineRetryableWebhookError;
type LineWebhookContext = Parameters<typeof import("./bot-handlers.js").handleLineWebhookEvents>[1];
const createRuntime = () => ({ log: vi.fn(), error: vi.fn(), exit: vi.fn() });
@@ -315,8 +314,7 @@ async function startInflightReplayDuplicate(params: {
describe("handleLineWebhookEvents", () => {
beforeAll(async () => {
({ handleLineWebhookEvents, createLineWebhookReplayCache, LineRetryableWebhookError } =
await import("./bot-handlers.js"));
({ handleLineWebhookEvents, createLineWebhookReplayCache } = await import("./bot-handlers.js"));
});
afterAll(() => {
@@ -769,7 +767,7 @@ describe("handleLineWebhookEvents", () => {
expect(processMessage).toHaveBeenCalledTimes(1);
});
it("mirrors in-flight retryable replay failures so concurrent duplicates also fail", async () => {
it("commits in-flight failures so concurrent duplicates do not retry", async () => {
let rejectFirst: ((err: Error) => void) | undefined;
const firstDone = new Promise<void>((_, reject) => {
rejectFirst = reject;
@@ -786,10 +784,10 @@ describe("handleLineWebhookEvents", () => {
});
const { firstRun, secondRun } = await startInflightReplayDuplicate({ event, processMessage });
const firstFailure = expect(firstRun).rejects.toThrow("transient inflight failure");
const secondFailure = expect(secondRun).rejects.toThrow("transient inflight failure");
rejectFirst?.(new LineRetryableWebhookError("transient inflight failure"));
rejectFirst?.(new Error("transient inflight failure"));
await Promise.all([firstFailure, secondFailure]);
await firstFailure;
await expect(secondRun).resolves.toBeUndefined();
expect(processMessage).toHaveBeenCalledTimes(1);
});
@@ -1164,25 +1162,4 @@ describe("handleLineWebhookEvents", () => {
"line: event handler failed: Error: transient failure",
);
});
it("reopens replay after an explicit retryable event failure", async () => {
const processMessage = vi
.fn()
.mockRejectedValueOnce(new LineRetryableWebhookError("retry me"))
.mockResolvedValueOnce(undefined);
const event = createReplayMessageEvent({
messageId: "m-fail-then-retryable",
groupId: "group-retry",
userId: "user-retry",
webhookEventId: "evt-fail-then-retryable",
isRedelivery: false,
});
const context = createOpenGroupReplayContext(processMessage, createLineWebhookReplayCache());
await expect(handleLineWebhookEvents([event], context)).rejects.toThrow("retry me");
await handleLineWebhookEvents([event], context);
expect(buildLineMessageContextMock).toHaveBeenCalledTimes(2);
expect(processMessage).toHaveBeenCalledTimes(2);
});
});

View File

@@ -85,13 +85,6 @@ function normalizeLineIngressEntry(value: string): string | null {
return normalizeLineAllowEntry(value) || null;
}
export class LineRetryableWebhookError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = "LineRetryableWebhookError";
}
}
export function createLineWebhookReplayCache(): LineWebhookReplayCache {
return createClaimableDedupe({
ttlMs: LINE_WEBHOOK_REPLAY_WINDOW_MS,
@@ -614,11 +607,7 @@ export async function handleLineWebhookEvents(
}
} catch (err) {
if (replayCandidate) {
if (err instanceof LineRetryableWebhookError) {
replayCandidate.cache.release(replayCandidate.key, { error: err });
} else {
await replayCandidate.cache.commit(replayCandidate.key);
}
await replayCandidate.cache.commit(replayCandidate.key);
}
context.runtime.error?.(danger(`line: event handler failed: ${String(err)}`));
firstError ??= err;

View File

@@ -1,7 +1,7 @@
// Line tests cover message cards plugin behavior.
import { expectDefined } from "@openclaw/normalization-core";
import { describe, expect, it } from "vitest";
import { datetimePickerAction, postbackAction, uriAction } from "./actions.js";
import { datetimePickerAction, messageAction, postbackAction, uriAction } from "./actions.js";
import { registerLineCardCommand } from "./card-command.js";
import {
createActionCard,
@@ -21,7 +21,6 @@ import {
createImageCarousel,
createImageCarouselColumn,
createProductCarousel,
messageAction,
} from "./template-messages.js";
const loneHighSurrogate = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])/;

View File

@@ -6,7 +6,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { createMockIncomingRequest } from "openclaw/plugin-sdk/test-env";
import { WEBHOOK_IN_FLIGHT_DEFAULTS } from "openclaw/plugin-sdk/webhook-request-guards";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
type LineNodeWebhookHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;
type LineHandleWebhook = (...args: unknown[]) => Promise<void>;
@@ -29,8 +29,6 @@ const {
}));
let monitorLineProvider: typeof import("./monitor.js").monitorLineProvider;
let getLineRuntimeState: typeof import("./monitor.js").getLineRuntimeState;
let clearLineRuntimeStateForTests: typeof import("./monitor.js").clearLineRuntimeStateForTests;
let innerLineWebhookHandlerMock: ReturnType<typeof vi.fn<LineNodeWebhookHandler>>;
type RegisteredRoute = {
@@ -142,8 +140,7 @@ vi.mock("./template-messages.js", () => ({
describe("monitorLineProvider lifecycle", () => {
beforeAll(async () => {
({ monitorLineProvider, getLineRuntimeState, clearLineRuntimeStateForTests } =
await import("./monitor.js"));
({ monitorLineProvider } = await import("./monitor.js"));
});
afterAll(() => {
@@ -161,7 +158,6 @@ describe("monitorLineProvider lifecycle", () => {
});
beforeEach(() => {
clearLineRuntimeStateForTests();
createLineBotMock.mockReset();
createLineBotMock.mockImplementation(() => ({
account: { accountId: "default" },
@@ -200,10 +196,6 @@ describe("monitorLineProvider lifecycle", () => {
});
});
afterEach(() => {
clearLineRuntimeStateForTests();
});
const createRouteResponse = () => {
const resObj = {
statusCode: 0,
@@ -289,7 +281,7 @@ describe("monitorLineProvider lifecycle", () => {
expect(unregisterHttpMock).toHaveBeenCalledTimes(1);
});
it("records startup state under configured defaultAccount when accountId is omitted", async () => {
it("registers the configured defaultAccount when accountId is omitted", async () => {
const monitor = await monitorLineProvider({
channelAccessToken: "token",
channelSecret: "secret", // pragma: allowlist secret
@@ -309,13 +301,14 @@ describe("monitorLineProvider lifecycle", () => {
runtime: {} as RuntimeEnv,
});
expect(getLineRuntimeState("work")?.running).toBe(true);
expect(getLineRuntimeState("default")).toBeUndefined();
const registration = requireWebhookRegistration();
expect(registration.target.accountId).toBe("work");
expect(registration.route.accountId).toBe("work");
monitor.stop();
});
it("does not record running state when bot startup fails", async () => {
it("does not register a webhook when bot startup fails", async () => {
createLineBotMock.mockImplementation(() => {
throw new Error("line bot startup failed");
});
@@ -329,7 +322,6 @@ describe("monitorLineProvider lifecycle", () => {
}),
).rejects.toThrow("line bot startup failed");
expect(getLineRuntimeState("default")?.running).not.toBe(true);
expect(registerWebhookTargetWithPluginRouteMock).not.toHaveBeenCalled();
});

View File

@@ -63,17 +63,6 @@ interface LineProviderMonitor {
stop: () => void;
}
const runtimeState = new Map<
string,
{
running: boolean;
lastStartAt: number | null;
lastStopAt: number | null;
lastError: string | null;
lastInboundAt?: number | null;
lastOutboundAt?: number | null;
}
>();
const lineWebhookInFlightLimiter = createWebhookInFlightLimiter();
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES = 64 * 1024;
const LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS = 5_000;
@@ -88,36 +77,6 @@ type LineWebhookTarget = {
const lineWebhookTargets = new Map<string, LineWebhookTarget[]>();
function recordChannelRuntimeState(params: {
channel: string;
accountId: string;
state: Partial<{
running: boolean;
lastStartAt: number | null;
lastStopAt: number | null;
lastError: string | null;
lastInboundAt: number | null;
lastOutboundAt: number | null;
}>;
}): void {
const key = `${params.channel}:${params.accountId}`;
const existing = runtimeState.get(key) ?? {
running: false,
lastStartAt: null,
lastStopAt: null,
lastError: null,
};
runtimeState.set(key, { ...existing, ...params.state });
}
export function getLineRuntimeState(accountId: string) {
return runtimeState.get(`line:${accountId}`);
}
export function clearLineRuntimeStateForTests() {
runtimeState.clear();
}
function startLineLoadingKeepalive(params: {
cfg: OpenClawConfig;
userId: string;
@@ -188,14 +147,6 @@ export async function monitorLineProvider(
const { ctxPayload, replyToken, route } = ctx;
recordChannelRuntimeState({
channel: "line",
accountId: resolvedAccountId,
state: {
lastInboundAt: Date.now(),
},
});
const shouldShowLoading = Boolean(ctx.userId && !ctx.isGroup);
const displayNamePromise = ctx.userId
@@ -298,14 +249,6 @@ export async function monitorLineProvider(
// because this delivery was not a clean success.
throw deliveryResult.error;
}
recordChannelRuntimeState({
channel: "line",
accountId: resolvedAccountId,
state: {
lastOutboundAt: Date.now(),
},
});
},
onError: (err, info) => {
runtime.error?.(danger(`line ${info.kind} reply failed: ${String(err)}`));
@@ -473,15 +416,6 @@ export async function monitorLineProvider(
},
});
recordChannelRuntimeState({
channel: "line",
accountId: resolvedAccountId,
state: {
running: true,
lastStartAt: Date.now(),
},
});
logVerbose(`line: registered webhook handler at ${normalizedPath}`);
let stopped = false;
@@ -492,14 +426,6 @@ export async function monitorLineProvider(
stopped = true;
logVerbose(`line: stopping provider for account ${resolvedAccountId}`);
unregisterHttp();
recordChannelRuntimeState({
channel: "line",
accountId: resolvedAccountId,
state: {
running: false,
lastStopAt: Date.now(),
},
});
};
if (abortSignal?.aborted) {

View File

@@ -4,15 +4,12 @@ import os from "node:os";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { datetimePickerAction, messageAction, postbackAction, uriAction } from "./actions.js";
import {
createRichMenu,
createDefaultMenuConfig,
createGridLayout,
datetimePickerAction,
messageAction,
postbackAction,
uploadRichMenuImage,
uriAction,
} from "./rich-menu.js";
const {

View File

@@ -6,7 +6,7 @@ import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
import { resolveLineAccount } from "./accounts.js";
import { datetimePickerAction, messageAction, postbackAction, uriAction } from "./actions.js";
import { messageAction } from "./actions.js";
import { resolveLineChannelAccessToken } from "./channel-access-token.js";
type RichMenuRequest = messagingApi.RichMenuRequest;
@@ -253,8 +253,6 @@ export function createGridLayout(
];
}
export { datetimePickerAction, messageAction, postbackAction, uriAction };
export function createDefaultMenuConfig(): CreateRichMenuParams {
return {
size: { width: 2500, height: 843 },

View File

@@ -22,12 +22,9 @@ type LineRuntime = PluginRuntime & {
};
};
const {
setRuntime: setLineRuntime,
clearRuntime: clearLineRuntime,
getRuntime: getLineRuntime,
} = createPluginRuntimeStore<LineRuntime>({
pluginId: "line",
errorMessage: "LINE runtime not initialized - plugin not registered",
});
export { clearLineRuntime, getLineRuntime, setLineRuntime };
const { setRuntime: setLineRuntime, getRuntime: getLineRuntime } =
createPluginRuntimeStore<LineRuntime>({
pluginId: "line",
errorMessage: "LINE runtime not initialized - plugin not registered",
});
export { getLineRuntime, setLineRuntime };

View File

@@ -15,9 +15,8 @@ import type { OpenClawConfig, PluginRuntime, ResolvedLineAccount } from "../api.
import { linePlugin } from "./channel.js";
import { lineGatewayAdapter } from "./gateway.js";
import { probeLineBot } from "./probe.js";
import { clearLineRuntime, setLineRuntime } from "./runtime.js";
import { setLineRuntime } from "./runtime.js";
import { lineSetupWizard } from "./setup-surface.js";
import { lineStatusAdapter } from "./status.js";
const { getBotInfoMock, MessagingApiClientMock } = vi.hoisted(() => {
const getBotInfoMockLocal = vi.fn();
@@ -318,7 +317,6 @@ describe("probeLineBot", () => {
});
afterEach(() => {
clearLineRuntime();
vi.useRealTimers();
getBotInfoMock.mockClear();
});
@@ -352,6 +350,8 @@ describe("probeLineBot", () => {
describe("linePlugin status.probeAccount", () => {
it("falls back to the direct probe helper when runtime is not initialized", async () => {
vi.resetModules();
const { lineStatusAdapter } = await import("./status.js");
MessagingApiClientMock.mockReset();
MessagingApiClientMock.mockImplementation(function () {
return { getBotInfo: getBotInfoMock };
@@ -375,8 +375,6 @@ describe("linePlugin status.probeAccount", () => {
timeoutMs: 50,
};
clearLineRuntime();
await expect(lineStatusAdapter.probeAccount!(params)).resolves.toEqual(
await probeLineBot("token", 50),
);

View File

@@ -3,8 +3,6 @@ import type { messagingApi } from "@line/bot-sdk";
import { messageAction, postbackAction, uriAction, type Action } from "./actions.js";
import type { LineTemplateMessagePayload } from "./types.js";
export { messageAction };
type TemplateMessage = messagingApi.TemplateMessage;
type ConfirmTemplate = messagingApi.ConfirmTemplate;
type ButtonsTemplate = messagingApi.ButtonsTemplate;

View File

@@ -133,17 +133,6 @@ export const KNIP_UNUSED_EXPORT_BASELINE = [
"extensions/irc/src/control-chars.ts: isIrcControlChar",
"extensions/irc/src/monitor.ts: resolveIrcInboundTarget",
"extensions/irc/src/runtime.ts: clearIrcRuntime",
"extensions/line/src/accounts.ts: DEFAULT_ACCOUNT_ID",
"extensions/line/src/auto-reply-delivery.ts: LineAutoReplyDeps",
"extensions/line/src/bot-handlers.ts: LineRetryableWebhookError",
"extensions/line/src/monitor.ts: clearLineRuntimeStateForTests",
"extensions/line/src/monitor.ts: getLineRuntimeState",
"extensions/line/src/rich-menu.ts: datetimePickerAction",
"extensions/line/src/rich-menu.ts: messageAction",
"extensions/line/src/rich-menu.ts: postbackAction",
"extensions/line/src/rich-menu.ts: uriAction",
"extensions/line/src/runtime.ts: clearLineRuntime",
"extensions/line/src/template-messages.ts: messageAction",
"extensions/llama-cpp/src/embedding-provider.ts: createLlamaCppMemoryEmbeddingProvider",
"extensions/llama-cpp/src/embedding-provider.ts: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL",
"extensions/llama-cpp/src/embedding-provider.ts: formatLlamaCppSetupError",