mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-21 01:51:39 -06:00
819c92d175
* fix(line): adopt durable ingress drain with ack gated on spool write LINE acked webhooks 200 before processing events detached, so a crash or dispatch failure silently lost inbound messages. Events now enqueue their raw JSON into the channel ingress queue before the webhook responds; dispatch, retry, dead-letter, and completion tombstones run through the core drain. The in-memory replay guard is deleted; tombstone retention (30d/4096) strictly covers its old 10min/4096 window, and message-id keys preserve the guard keyspace so redeliveries under changed webhookEventIds still dedupe. Autoreview: secret scanner false-positive on HMAC test fixtures (test-channel-secret); full manual review of all five files + test matrix performed instead. Part of the #109657 fleet adoption program (wave 1). * style(line): oxfmt pass on drain adoption files * fix(line): cap repeated ingress drain deliveries * fix(line): preserve ingress lifecycle bounds * style(line): satisfy ingress lint gates
78 lines
2.4 KiB
TypeScript
78 lines
2.4 KiB
TypeScript
// Line plugin module implements bot behavior.
|
|
import type { webhook } from "@line/bot-sdk";
|
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
import { DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry } from "openclaw/plugin-sdk/reply-history";
|
|
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
import {
|
|
createNonExitingRuntime,
|
|
logVerbose,
|
|
type RuntimeEnv,
|
|
} from "openclaw/plugin-sdk/runtime-env";
|
|
import { resolveLineAccount } from "./accounts.js";
|
|
import { handleLineWebhookEvents } from "./bot-handlers.js";
|
|
import type { LineInboundContext } from "./bot-message-context.js";
|
|
import type { ResolvedLineAccount } from "./types.js";
|
|
import { createLineWebhookSpool, type LineWebhookTurnAdoptionLifecycle } from "./webhook-spool.js";
|
|
|
|
interface LineBotOptions {
|
|
channelAccessToken: string;
|
|
channelSecret: string;
|
|
accountId?: string;
|
|
runtime?: RuntimeEnv;
|
|
config?: OpenClawConfig;
|
|
mediaMaxMb?: number;
|
|
onMessage?: (
|
|
ctx: LineInboundContext,
|
|
control: { turnAdoptionLifecycle?: LineWebhookTurnAdoptionLifecycle },
|
|
) => Promise<void>;
|
|
}
|
|
|
|
interface LineBot {
|
|
handleWebhook: (body: webhook.CallbackRequest) => Promise<void>;
|
|
account: ResolvedLineAccount;
|
|
stop: () => Promise<void>;
|
|
}
|
|
|
|
export function createLineBot(opts: LineBotOptions): LineBot {
|
|
const runtime: RuntimeEnv = opts.runtime ?? createNonExitingRuntime();
|
|
|
|
const cfg = opts.config ?? getRuntimeConfig();
|
|
const account = resolveLineAccount({
|
|
cfg,
|
|
accountId: opts.accountId,
|
|
});
|
|
|
|
const mediaMaxBytes = (opts.mediaMaxMb ?? account.config.mediaMaxMb ?? 10) * 1024 * 1024;
|
|
|
|
const processMessage =
|
|
opts.onMessage ??
|
|
(async () => {
|
|
logVerbose("line: no message handler configured");
|
|
});
|
|
const groupHistories = new Map<string, HistoryEntry[]>();
|
|
const spool = createLineWebhookSpool({
|
|
accountId: account.accountId,
|
|
runtime,
|
|
deliver: async (event, _destination, control) =>
|
|
await handleLineWebhookEvents([event], {
|
|
cfg,
|
|
account,
|
|
runtime,
|
|
mediaMaxBytes,
|
|
processMessage,
|
|
...(control.turnAdoptionLifecycle
|
|
? { turnAdoptionLifecycle: control.turnAdoptionLifecycle }
|
|
: {}),
|
|
groupHistories,
|
|
historyLimit: cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,
|
|
}),
|
|
});
|
|
spool.start();
|
|
|
|
return {
|
|
handleWebhook: spool.accept,
|
|
account,
|
|
stop: spool.stop,
|
|
};
|
|
}
|