diff --git a/extensions/linq/index.ts b/extensions/linq/index.ts deleted file mode 100644 index e4e7afc54..000000000 --- a/extensions/linq/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; -import { linqPlugin } from "./src/channel.js"; -import { setLinqRuntime } from "./src/runtime.js"; - -const plugin = { - id: "linq", - name: "Linq", - description: "Linq iMessage channel plugin — real iMessage over API, no Mac required", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { - setLinqRuntime(api.runtime); - api.registerChannel({ plugin: linqPlugin as ChannelPlugin }); - }, -}; - -export default plugin; diff --git a/extensions/linq/openclaw.plugin.json b/extensions/linq/openclaw.plugin.json deleted file mode 100644 index 952ec0526..000000000 --- a/extensions/linq/openclaw.plugin.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "linq", - "channels": ["linq"], - "configSchema": { - "type": "object", - "additionalProperties": false, - "properties": {} - } -} diff --git a/extensions/linq/package.json b/extensions/linq/package.json deleted file mode 100644 index c8609be52..000000000 --- a/extensions/linq/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@openclaw/linq", - "version": "2026.2.16", - "private": true, - "description": "OpenClaw Linq iMessage channel plugin", - "type": "module", - "devDependencies": { - "openclaw": "workspace:*" - }, - "openclaw": { - "extensions": [ - "./index.ts" - ] - } -} diff --git a/extensions/linq/src/channel.ts b/extensions/linq/src/channel.ts deleted file mode 100644 index aaa3f062d..000000000 --- a/extensions/linq/src/channel.ts +++ /dev/null @@ -1,348 +0,0 @@ -import { - applyAccountNameToChannelSection, - buildChannelConfigSchema, - DEFAULT_ACCOUNT_ID, - deleteAccountFromConfigSection, - formatPairingApproveHint, - getChatChannelMeta, - listLinqAccountIds, - migrateBaseNameToDefaultAccount, - normalizeAccountId, - resolveDefaultLinqAccountId, - resolveLinqAccount, - setAccountEnabledInConfigSection, - type ChannelPlugin, - type OpenClawConfig, - type ResolvedLinqAccount, - type LinqProbe, - LinqConfigSchema, -} from "openclaw/plugin-sdk"; -import { getLinqRuntime } from "./runtime.js"; - -const meta = getChatChannelMeta("linq"); - -export const linqPlugin: ChannelPlugin = { - id: "linq", - meta: { - ...meta, - aliases: ["linq-imessage"], - }, - pairing: { - idLabel: "phoneNumber", - notifyApproval: async ({ id }) => { - // Approval notification would need a chat_id, not just a phone number. - // For now this is a no-op; pairing replies are sent in the monitor. - }, - }, - capabilities: { - chatTypes: ["direct", "group"], - reactions: true, - media: true, - }, - reload: { configPrefixes: ["channels.linq"] }, - configSchema: buildChannelConfigSchema(LinqConfigSchema), - config: { - listAccountIds: (cfg) => listLinqAccountIds(cfg), - resolveAccount: (cfg, accountId) => resolveLinqAccount({ cfg, accountId }), - defaultAccountId: (cfg) => resolveDefaultLinqAccountId(cfg), - setAccountEnabled: ({ cfg, accountId, enabled }) => - setAccountEnabledInConfigSection({ - cfg, - sectionKey: "linq", - accountId, - enabled, - allowTopLevel: true, - }), - deleteAccount: ({ cfg, accountId }) => - deleteAccountFromConfigSection({ - cfg, - sectionKey: "linq", - accountId, - clearBaseFields: ["apiToken", "tokenFile", "fromPhone", "name"], - }), - isConfigured: (account) => Boolean(account.token?.trim()), - describeAccount: (account) => ({ - accountId: account.accountId, - name: account.name, - enabled: account.enabled, - configured: Boolean(account.token?.trim()), - tokenSource: account.tokenSource, - fromPhone: account.fromPhone, - }), - resolveAllowFrom: ({ cfg, accountId }) => - (resolveLinqAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)), - formatAllowFrom: ({ allowFrom }) => - allowFrom.map((entry) => String(entry).trim()).filter(Boolean), - }, - security: { - resolveDmPolicy: ({ cfg, accountId, account }) => { - const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID; - const linqSection = (cfg.channels as Record | undefined)?.linq as - | Record - | undefined; - const useAccountPath = Boolean( - (linqSection?.accounts as Record | undefined)?.[resolvedAccountId], - ); - const basePath = useAccountPath - ? `channels.linq.accounts.${resolvedAccountId}.` - : "channels.linq."; - return { - policy: account.config.dmPolicy ?? "pairing", - allowFrom: account.config.allowFrom ?? [], - policyPath: `${basePath}dmPolicy`, - allowFromPath: basePath, - approveHint: formatPairingApproveHint("linq"), - }; - }, - collectWarnings: ({ account }) => { - const groupPolicy = account.config.groupPolicy ?? "open"; - if (groupPolicy !== "open") { - return []; - } - return [ - `- Linq groups: groupPolicy="open" allows any group member to trigger. Set channels.linq.groupPolicy="allowlist" + channels.linq.groupAllowFrom to restrict senders.`, - ]; - }, - }, - groups: { - resolveRequireMention: (params) => undefined, - resolveToolPolicy: (params) => undefined, - }, - messaging: { - normalizeTarget: (raw) => raw ?? "", - targetResolver: { - looksLikeId: (id) => /^[A-Za-z0-9_-]+$/.test(id ?? ""), - hint: "", - }, - }, - setup: { - resolveAccountId: ({ accountId }) => normalizeAccountId(accountId), - applyAccountName: ({ cfg, accountId, name }) => - applyAccountNameToChannelSection({ - cfg, - channelKey: "linq", - accountId, - name, - }), - validateInput: ({ accountId, input }) => { - if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) { - return "LINQ_API_TOKEN can only be used for the default account."; - } - if (!input.useEnv && !input.token && !input.tokenFile) { - return "Linq requires an API token or --token-file (or --use-env)."; - } - return null; - }, - applyAccountConfig: ({ cfg, accountId, input }) => { - const namedConfig = applyAccountNameToChannelSection({ - cfg, - channelKey: "linq", - accountId, - name: input.name, - }); - const next = - accountId !== DEFAULT_ACCOUNT_ID - ? migrateBaseNameToDefaultAccount({ cfg: namedConfig, channelKey: "linq" }) - : namedConfig; - if (accountId === DEFAULT_ACCOUNT_ID) { - return { - ...next, - channels: { - ...next.channels, - linq: { - ...((next.channels as Record | undefined)?.linq as - | Record - | undefined), - enabled: true, - ...(input.useEnv - ? {} - : input.tokenFile - ? { tokenFile: input.tokenFile } - : input.token - ? { apiToken: input.token } - : {}), - }, - }, - }; - } - const linqSection = (next.channels as Record | undefined)?.linq as - | Record - | undefined; - return { - ...next, - channels: { - ...next.channels, - linq: { - ...linqSection, - enabled: true, - accounts: { - ...(linqSection?.accounts as Record | undefined), - [accountId]: { - ...((linqSection?.accounts as Record | undefined)?.[accountId] as - | Record - | undefined), - enabled: true, - ...(input.tokenFile - ? { tokenFile: input.tokenFile } - : input.token - ? { apiToken: input.token } - : {}), - }, - }, - }, - }, - }; - }, - }, - outbound: { - deliveryMode: "direct", - chunker: (text, limit) => getLinqRuntime().channel.text.chunkText(text, limit), - chunkerMode: "text", - textChunkLimit: 4000, - sendText: async ({ to, text, accountId }) => { - const send = getLinqRuntime().channel.linq.sendMessageLinq; - const result = await send(to, text, { accountId: accountId ?? undefined }); - return { channel: "linq", ...result }; - }, - sendMedia: async ({ to, text, mediaUrl, accountId }) => { - const send = getLinqRuntime().channel.linq.sendMessageLinq; - const result = await send(to, text, { - mediaUrl, - accountId: accountId ?? undefined, - }); - return { channel: "linq", ...result }; - }, - }, - status: { - defaultRuntime: { - accountId: DEFAULT_ACCOUNT_ID, - running: false, - lastStartAt: null, - lastStopAt: null, - lastError: null, - }, - collectStatusIssues: (accounts) => - accounts.flatMap((account) => { - const lastError = typeof account.lastError === "string" ? account.lastError.trim() : ""; - if (!lastError) { - return []; - } - return [ - { - channel: "linq", - accountId: account.accountId, - kind: "runtime", - message: `Channel error: ${lastError}`, - }, - ]; - }), - buildChannelSummary: ({ snapshot }) => ({ - configured: snapshot.configured ?? false, - tokenSource: snapshot.tokenSource ?? "none", - running: snapshot.running ?? false, - lastStartAt: snapshot.lastStartAt ?? null, - lastStopAt: snapshot.lastStopAt ?? null, - lastError: snapshot.lastError ?? null, - probe: snapshot.probe, - lastProbeAt: snapshot.lastProbeAt ?? null, - }), - probeAccount: async ({ account, timeoutMs }) => - getLinqRuntime().channel.linq.probeLinq(account.token, timeoutMs, account.accountId), - buildAccountSnapshot: ({ account, runtime, probe }) => ({ - accountId: account.accountId, - name: account.name, - enabled: account.enabled, - configured: Boolean(account.token?.trim()), - tokenSource: account.tokenSource, - fromPhone: account.fromPhone, - running: runtime?.running ?? false, - lastStartAt: runtime?.lastStartAt ?? null, - lastStopAt: runtime?.lastStopAt ?? null, - lastError: runtime?.lastError ?? null, - probe, - lastInboundAt: runtime?.lastInboundAt ?? null, - lastOutboundAt: runtime?.lastOutboundAt ?? null, - }), - }, - gateway: { - startAccount: async (ctx) => { - const account = ctx.account; - const token = account.token.trim(); - let phoneLabel = ""; - try { - const probe = await getLinqRuntime().channel.linq.probeLinq(token, 2500); - if (probe.ok && probe.phoneNumbers?.length) { - phoneLabel = ` (${probe.phoneNumbers.join(", ")})`; - } - } catch { - // Probe failure is non-fatal for startup. - } - ctx.log?.info(`[${account.accountId}] starting Linq provider${phoneLabel}`); - return getLinqRuntime().channel.linq.monitorLinqProvider({ - accountId: account.accountId, - config: ctx.cfg, - runtime: ctx.runtime, - abortSignal: ctx.abortSignal, - }); - }, - logoutAccount: async ({ accountId, cfg }) => { - const nextCfg = { ...cfg }; - const linqSection = (cfg.channels as Record | undefined)?.linq as - | Record - | undefined; - let cleared = false; - let changed = false; - if (linqSection) { - const nextLinq = { ...linqSection }; - if (accountId === DEFAULT_ACCOUNT_ID && nextLinq.apiToken) { - delete nextLinq.apiToken; - cleared = true; - changed = true; - } - const accounts = - nextLinq.accounts && typeof nextLinq.accounts === "object" - ? { ...(nextLinq.accounts as Record) } - : undefined; - if (accounts && accountId in accounts) { - const entry = accounts[accountId]; - if (entry && typeof entry === "object") { - const nextEntry = { ...(entry as Record) }; - if ("apiToken" in nextEntry) { - cleared = true; - delete nextEntry.apiToken; - changed = true; - } - if (Object.keys(nextEntry).length === 0) { - delete accounts[accountId]; - changed = true; - } else { - accounts[accountId] = nextEntry; - } - } - } - if (accounts) { - if (Object.keys(accounts).length === 0) { - delete nextLinq.accounts; - changed = true; - } else { - nextLinq.accounts = accounts; - } - } - if (changed) { - if (Object.keys(nextLinq).length > 0) { - nextCfg.channels = { ...nextCfg.channels, linq: nextLinq } as typeof nextCfg.channels; - } else { - const nextChannels = { ...nextCfg.channels } as Record; - delete nextChannels.linq; - nextCfg.channels = nextChannels as typeof nextCfg.channels; - } - } - } - if (changed) { - await getLinqRuntime().config.writeConfigFile(nextCfg); - } - const resolved = resolveLinqAccount({ cfg: changed ? nextCfg : cfg, accountId }); - return { cleared, loggedOut: resolved.tokenSource === "none" }; - }, - }, -}; diff --git a/extensions/linq/src/runtime.ts b/extensions/linq/src/runtime.ts deleted file mode 100644 index 92a20ce5c..000000000 --- a/extensions/linq/src/runtime.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { PluginRuntime } from "openclaw/plugin-sdk"; - -let runtime: PluginRuntime | null = null; - -export function setLinqRuntime(next: PluginRuntime) { - runtime = next; -} - -export function getLinqRuntime(): PluginRuntime { - if (!runtime) { - throw new Error("Linq runtime not initialized"); - } - return runtime; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 127cc32fe..2d45788ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -357,12 +357,6 @@ importers: specifier: workspace:* version: link:../.. - extensions/linq: - devDependencies: - openclaw: - specifier: workspace:* - version: link:../.. - extensions/llm-task: devDependencies: openclaw: diff --git a/src/channels/dock.ts b/src/channels/dock.ts index 4608a1e31..2326bcfdb 100644 --- a/src/channels/dock.ts +++ b/src/channels/dock.ts @@ -1,11 +1,23 @@ import type { OpenClawConfig } from "../config/config.js"; +import type { + ChannelCapabilities, + ChannelCommandAdapter, + ChannelElevatedAdapter, + ChannelGroupAdapter, + ChannelId, + ChannelAgentPromptAdapter, + ChannelMentionAdapter, + ChannelPlugin, + ChannelThreadingContext, + ChannelThreadingAdapter, + ChannelThreadingToolContext, +} from "./plugins/types.js"; import { resolveChannelGroupRequireMention, resolveChannelGroupToolsPolicy, } from "../config/group-policy.js"; import { resolveDiscordAccount } from "../discord/accounts.js"; import { resolveIMessageAccount } from "../imessage/accounts.js"; -import { resolveLinqAccount } from "../linq/accounts.js"; import { requireActivePluginRegistry } from "../plugins/runtime.js"; import { normalizeAccountId } from "../routing/session-key.js"; import { resolveSignalAccount } from "../signal/accounts.js"; @@ -29,19 +41,6 @@ import { resolveWhatsAppGroupRequireMention, resolveWhatsAppGroupToolPolicy, } from "./plugins/group-mentions.js"; -import type { - ChannelCapabilities, - ChannelCommandAdapter, - ChannelElevatedAdapter, - ChannelGroupAdapter, - ChannelId, - ChannelAgentPromptAdapter, - ChannelMentionAdapter, - ChannelPlugin, - ChannelThreadingContext, - ChannelThreadingAdapter, - ChannelThreadingToolContext, -} from "./plugins/types.js"; import { CHAT_CHANNEL_ORDER, type ChatChannelId, getChatChannelMeta } from "./registry.js"; export type ChannelDock = { @@ -461,23 +460,6 @@ const DOCKS: Record = { buildDirectOrGroupThreadToolContext({ context, hasRepliedRef }), }, }, - linq: { - id: "linq", - capabilities: { - chatTypes: ["direct", "group"], - reactions: true, - media: true, - }, - outbound: { textChunkLimit: 4000 }, - config: { - resolveAllowFrom: ({ cfg, accountId }) => - (resolveLinqAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => - String(entry), - ), - formatAllowFrom: ({ allowFrom }) => - allowFrom.map((entry) => String(entry).trim()).filter(Boolean), - }, - }, }; function buildDockFromPlugin(plugin: ChannelPlugin): ChannelDock { diff --git a/src/channels/registry.ts b/src/channels/registry.ts index 8dfc20588..205372334 100644 --- a/src/channels/registry.ts +++ b/src/channels/registry.ts @@ -1,6 +1,6 @@ -import { requireActivePluginRegistry } from "../plugins/runtime.js"; import type { ChannelMeta } from "./plugins/types.js"; import type { ChannelId } from "./plugins/types.js"; +import { requireActivePluginRegistry } from "../plugins/runtime.js"; // Channel docking: add new core channels here (order + meta + aliases), then // register the plugin in its extension entrypoint and keep protocol IDs in sync. @@ -13,7 +13,6 @@ export const CHAT_CHANNEL_ORDER = [ "slack", "signal", "imessage", - "linq", ] as const; export type ChatChannelId = (typeof CHAT_CHANNEL_ORDER)[number]; @@ -110,16 +109,6 @@ const CHAT_CHANNEL_META: Record = { blurb: "this is still a work in progress.", systemImage: "message.fill", }, - linq: { - id: "linq", - label: "Linq", - selectionLabel: "Linq (iMessage API)", - detailLabel: "Linq iMessage", - docsPath: "/channels/linq", - docsLabel: "linq", - blurb: "real iMessage blue bubbles via API — no Mac required. Get a token at linqapp.com.", - systemImage: "bubble.left.and.text.bubble.right", - }, }; export const CHAT_CHANNEL_ALIASES: Record = { @@ -127,7 +116,6 @@ export const CHAT_CHANNEL_ALIASES: Record = { "internet-relay-chat": "irc", "google-chat": "googlechat", gchat: "googlechat", - "linq-imessage": "linq", }; const normalizeChannelKey = (raw?: string | null): string | undefined => { diff --git a/src/config/zod-schema.providers-core.ts b/src/config/zod-schema.providers-core.ts index 75d13e5cb..319c167b3 100644 --- a/src/config/zod-schema.providers-core.ts +++ b/src/config/zod-schema.providers-core.ts @@ -1010,65 +1010,3 @@ export const MSTeamsConfigSchema = z 'channels.msteams.dmPolicy="open" requires channels.msteams.allowFrom to include "*"', }); }); - -// ── Linq ───────────────────────────────────────────────────────────────────── - -const LinqAllowFromEntry = z.union([z.string(), z.number()]); - -const LinqAccountSchemaBase = z - .object({ - name: z.string().optional(), - enabled: z.boolean().optional(), - apiToken: z.string().optional().register(sensitive), - tokenFile: z.string().optional(), - fromPhone: z.string().optional(), - dmPolicy: DmPolicySchema.optional(), - allowFrom: z.array(LinqAllowFromEntry).optional(), - groupPolicy: GroupPolicySchema.optional(), - groupAllowFrom: z.array(LinqAllowFromEntry).optional(), - mediaMaxMb: z.number().positive().optional(), - textChunkLimit: z.number().positive().optional(), - webhookUrl: z.string().optional(), - webhookSecret: z.string().optional().register(sensitive), - webhookPath: z.string().optional(), - webhookHost: z.string().optional(), - historyLimit: z.number().nonnegative().optional(), - blockStreaming: z.boolean().optional(), - groups: z - .record( - z.string(), - z - .object({ - requireMention: z.boolean().optional(), - tools: ToolPolicySchema, - toolsBySender: ToolPolicyBySenderSchema, - }) - .strict() - .optional(), - ) - .optional(), - responsePrefix: z.string().optional(), - }) - .strict(); - -const LinqAccountSchema = LinqAccountSchemaBase.superRefine((value, ctx) => { - requireOpenAllowFrom({ - policy: value.dmPolicy, - allowFrom: value.allowFrom, - ctx, - path: ["allowFrom"], - message: 'channels.linq.dmPolicy="open" requires channels.linq.allowFrom to include "*"', - }); -}); - -export const LinqConfigSchema = LinqAccountSchemaBase.extend({ - accounts: z.record(z.string(), LinqAccountSchema.optional()).optional(), -}).superRefine((value, ctx) => { - requireOpenAllowFrom({ - policy: value.dmPolicy, - allowFrom: value.allowFrom, - ctx, - path: ["allowFrom"], - message: 'channels.linq.dmPolicy="open" requires channels.linq.allowFrom to include "*"', - }); -}); diff --git a/src/linq/accounts.ts b/src/linq/accounts.ts deleted file mode 100644 index 22e2d99a8..000000000 --- a/src/linq/accounts.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { readFileSync } from "node:fs"; -import type { OpenClawConfig } from "../config/config.js"; -import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; -import type { LinqAccountConfig } from "./types.js"; - -export type ResolvedLinqAccount = { - accountId: string; - enabled: boolean; - name?: string; - token: string; - tokenSource: "config" | "env" | "file" | "none"; - fromPhone?: string; - config: LinqAccountConfig; -}; - -function listConfiguredAccountIds(cfg: OpenClawConfig): string[] { - const accounts = (cfg.channels as Record | undefined)?.linq as - | LinqAccountConfig - | undefined; - if (!accounts?.accounts || typeof accounts.accounts !== "object") { - return []; - } - return Object.keys(accounts.accounts).filter(Boolean); -} - -export function listLinqAccountIds(cfg: OpenClawConfig): string[] { - const ids = listConfiguredAccountIds(cfg); - if (ids.length === 0) { - return [DEFAULT_ACCOUNT_ID]; - } - return ids.toSorted((a, b) => a.localeCompare(b)); -} - -export function resolveDefaultLinqAccountId(cfg: OpenClawConfig): string { - const ids = listLinqAccountIds(cfg); - if (ids.includes(DEFAULT_ACCOUNT_ID)) { - return DEFAULT_ACCOUNT_ID; - } - return ids[0] ?? DEFAULT_ACCOUNT_ID; -} - -function resolveAccountConfig( - cfg: OpenClawConfig, - accountId: string, -): LinqAccountConfig | undefined { - const linqSection = (cfg.channels as Record | undefined)?.linq as - | LinqAccountConfig - | undefined; - if (!linqSection?.accounts || typeof linqSection.accounts !== "object") { - return undefined; - } - return linqSection.accounts[accountId]; -} - -function mergeLinqAccountConfig(cfg: OpenClawConfig, accountId: string): LinqAccountConfig { - const linqSection = (cfg.channels as Record | undefined)?.linq as - | (LinqAccountConfig & { accounts?: unknown }) - | undefined; - const { accounts: _ignored, ...base } = linqSection ?? {}; - const account = resolveAccountConfig(cfg, accountId) ?? {}; - return { ...base, ...account }; -} - -function resolveToken( - merged: LinqAccountConfig, - accountId: string, -): { token: string; source: "config" | "env" | "file" } | { token: ""; source: "none" } { - // Environment variable takes priority for the default account. - const envToken = process.env.LINQ_API_TOKEN?.trim() ?? ""; - if (envToken && accountId === DEFAULT_ACCOUNT_ID) { - return { token: envToken, source: "env" }; - } - // Config token. - if (merged.apiToken?.trim()) { - return { token: merged.apiToken.trim(), source: "config" }; - } - // Token file (read synchronously to keep resolver sync-friendly). - if (merged.tokenFile?.trim()) { - try { - const content = readFileSync(merged.tokenFile.trim(), "utf8").trim(); - if (content) { - return { token: content, source: "file" }; - } - } catch { - // fall through - } - } - return { token: "", source: "none" }; -} - -export function resolveLinqAccount(params: { - cfg: OpenClawConfig; - accountId?: string | null; -}): ResolvedLinqAccount { - const accountId = normalizeAccountId(params.accountId); - const linqSection = (params.cfg.channels as Record | undefined)?.linq as - | LinqAccountConfig - | undefined; - const baseEnabled = linqSection?.enabled !== false; - const merged = mergeLinqAccountConfig(params.cfg, accountId); - const accountEnabled = merged.enabled !== false; - const { token, source } = resolveToken(merged, accountId); - return { - accountId, - enabled: baseEnabled && accountEnabled, - name: merged.name?.trim() || undefined, - token, - tokenSource: source, - fromPhone: merged.fromPhone?.trim() || undefined, - config: merged, - }; -} diff --git a/src/linq/monitor.ts b/src/linq/monitor.ts deleted file mode 100644 index b1ffe0ec2..000000000 --- a/src/linq/monitor.ts +++ /dev/null @@ -1,467 +0,0 @@ -import { createHmac, timingSafeEqual } from "node:crypto"; -import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"; -import { resolveHumanDelayConfig } from "../agents/identity.js"; -import { hasControlCommand } from "../auto-reply/command-detection.js"; -import { dispatchInboundMessage } from "../auto-reply/dispatch.js"; -import { - formatInboundEnvelope, - formatInboundFromLabel, - resolveEnvelopeFormatOptions, -} from "../auto-reply/envelope.js"; -import { - createInboundDebouncer, - resolveInboundDebounceMs, -} from "../auto-reply/inbound-debounce.js"; -import { finalizeInboundContext } from "../auto-reply/reply/inbound-context.js"; -import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js"; -import { createReplyPrefixOptions } from "../channels/reply-prefix.js"; -import { recordInboundSession } from "../channels/session.js"; -import { loadConfig, type OpenClawConfig } from "../config/config.js"; -import { readSessionUpdatedAt, resolveStorePath } from "../config/sessions.js"; -import { danger, logVerbose, shouldLogVerbose } from "../globals.js"; -import { buildPairingReply } from "../pairing/pairing-messages.js"; -import { - readChannelAllowFromStore, - upsertChannelPairingRequest, -} from "../pairing/pairing-store.js"; -import { resolveAgentRoute } from "../routing/resolve-route.js"; -import type { RuntimeEnv } from "../runtime.js"; -import { truncateUtf16Safe } from "../utils.js"; -import { resolveLinqAccount } from "./accounts.js"; -import { markAsReadLinq, sendMessageLinq, startTypingLinq } from "./send.js"; -import type { - LinqMediaPart, - LinqMessageReceivedData, - LinqTextPart, - LinqWebhookEvent, -} from "./types.js"; - -export type MonitorLinqOpts = { - accountId?: string; - config?: OpenClawConfig; - runtime?: RuntimeEnv; - abortSignal?: AbortSignal; -}; - -type MonitorRuntime = { - info: (msg: string) => void; - error?: (msg: string) => void; -}; - -function resolveRuntime(opts: MonitorLinqOpts): MonitorRuntime { - return { - info: (msg) => logVerbose(msg), - error: (msg) => logVerbose(msg), - ...opts.runtime, - }; -} - -function normalizeAllowList(raw?: Array): string[] { - if (!raw || !Array.isArray(raw)) { - return []; - } - return raw.map((v) => String(v).trim()).filter(Boolean); -} - -function extractTextContent(parts: Array<{ type: string; value?: string }>): string { - return parts - .filter((p): p is LinqTextPart => p.type === "text") - .map((p) => p.value) - .join("\n"); -} - -function extractMediaUrls( - parts: Array<{ type: string; url?: string; mime_type?: string }>, -): Array<{ url: string; mimeType: string }> { - return parts - .filter( - (p): p is LinqMediaPart & { url: string; mime_type: string } => - p.type === "media" && Boolean(p.url) && Boolean(p.mime_type), - ) - .map((p) => ({ url: p.url, mimeType: p.mime_type })); -} - -function verifyWebhookSignature( - secret: string, - payload: string, - timestamp: string, - signature: string, -): boolean { - const message = `${timestamp}.${payload}`; - const expected = createHmac("sha256", secret).update(message).digest("hex"); - try { - return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(signature, "hex")); - } catch { - return false; - } -} - -function isAllowedLinqSender(allowFrom: string[], sender: string): boolean { - if (allowFrom.includes("*")) { - return true; - } - const normalized = sender.replace(/[\s()-]/g, "").toLowerCase(); - return allowFrom.some((entry) => { - const norm = entry.replace(/[\s()-]/g, "").toLowerCase(); - return norm === normalized; - }); -} - -export async function monitorLinqProvider(opts: MonitorLinqOpts = {}): Promise { - const runtime = resolveRuntime(opts); - const cfg = opts.config ?? loadConfig(); - const accountInfo = resolveLinqAccount({ cfg, accountId: opts.accountId }); - const linqCfg = accountInfo.config; - const token = accountInfo.token; - - if (!token) { - throw new Error("Linq API token not configured"); - } - - const allowFrom = normalizeAllowList(linqCfg.allowFrom); - const dmPolicy = linqCfg.dmPolicy ?? "pairing"; - const webhookSecret = linqCfg.webhookSecret?.trim() ?? ""; - const webhookPath = linqCfg.webhookPath?.trim() || "/linq-webhook"; - const webhookHost = linqCfg.webhookHost?.trim() || "0.0.0.0"; - const fromPhone = accountInfo.fromPhone; - - const inboundDebounceMs = resolveInboundDebounceMs({ cfg, channel: "linq" }); - const inboundDebouncer = createInboundDebouncer<{ event: LinqMessageReceivedData }>({ - debounceMs: inboundDebounceMs, - buildKey: (entry) => { - const sender = entry.event.from?.trim(); - if (!sender) { - return null; - } - return `linq:${accountInfo.accountId}:${entry.event.chat_id}:${sender}`; - }, - shouldDebounce: (entry) => { - const text = extractTextContent( - entry.event.message.parts as Array<{ type: string; value?: string }>, - ); - if (!text.trim()) { - return false; - } - return !hasControlCommand(text, cfg); - }, - onFlush: async (entries) => { - const last = entries.at(-1); - if (!last) { - return; - } - if (entries.length === 1) { - await handleMessage(last.event); - return; - } - const combinedText = entries - .map((e) => - extractTextContent(e.event.message.parts as Array<{ type: string; value?: string }>), - ) - .filter(Boolean) - .join("\n"); - const syntheticEvent: LinqMessageReceivedData = { - ...last.event, - message: { - ...last.event.message, - parts: [{ type: "text" as const, value: combinedText }], - }, - }; - await handleMessage(syntheticEvent); - }, - onError: (err) => { - runtime.error?.(`linq debounce flush failed: ${String(err)}`); - }, - }); - - async function handleMessage(data: LinqMessageReceivedData) { - const sender = data.from?.trim(); - if (!sender) { - return; - } - if (data.is_from_me) { - return; - } - - // Filter: only process messages sent to this account's phone number. - if (fromPhone && data.recipient_phone !== fromPhone) { - logVerbose(`linq: skipping message to ${data.recipient_phone} (not ${fromPhone})`); - return; - } - - const chatId = data.chat_id; - const text = extractTextContent(data.message.parts as Array<{ type: string; value?: string }>); - const media = extractMediaUrls( - data.message.parts as Array<{ type: string; url?: string; mime_type?: string }>, - ); - - if (!text.trim() && media.length === 0) { - return; - } - - // Send read receipt and typing indicator immediately (fire-and-forget). - markAsReadLinq(chatId, token).catch(() => {}); - startTypingLinq(chatId, token).catch(() => {}); - - const storeAllowFrom = await readChannelAllowFromStore("linq").catch(() => []); - const effectiveDmAllowFrom = Array.from(new Set([...allowFrom, ...storeAllowFrom])) - .map((v) => String(v).trim()) - .filter(Boolean); - - const dmHasWildcard = effectiveDmAllowFrom.includes("*"); - const dmAuthorized = - dmPolicy === "open" - ? true - : dmHasWildcard || - (effectiveDmAllowFrom.length > 0 && isAllowedLinqSender(effectiveDmAllowFrom, sender)); - - if (dmPolicy === "disabled") { - return; - } - if (!dmAuthorized) { - if (dmPolicy === "pairing") { - const { code, created } = await upsertChannelPairingRequest({ - channel: "linq", - id: sender, - meta: { sender, chatId }, - }); - if (created) { - logVerbose(`linq pairing request sender=${sender}`); - try { - await sendMessageLinq( - chatId, - buildPairingReply({ - channel: "linq", - idLine: `Your phone number: ${sender}`, - code, - }), - { token, accountId: accountInfo.accountId }, - ); - } catch (err) { - logVerbose(`linq pairing reply failed for ${sender}: ${String(err)}`); - } - } - } else { - logVerbose(`Blocked linq sender ${sender} (dmPolicy=${dmPolicy})`); - } - return; - } - - const route = resolveAgentRoute({ - cfg, - channel: "linq", - accountId: accountInfo.accountId, - peer: { kind: "direct", id: sender }, - }); - const bodyText = text.trim() || (media.length > 0 ? "" : ""); - if (!bodyText) { - return; - } - - const replyContext = data.message.reply_to ? { id: data.message.reply_to.message_id } : null; - const createdAt = data.received_at ? Date.parse(data.received_at) : undefined; - - const fromLabel = formatInboundFromLabel({ - isGroup: false, - directLabel: sender, - directId: sender, - }); - const storePath = resolveStorePath(cfg.session?.store, { agentId: route.agentId }); - const envelopeOptions = resolveEnvelopeFormatOptions(cfg); - const previousTimestamp = readSessionUpdatedAt({ - storePath, - sessionKey: route.sessionKey, - }); - - const replySuffix = replyContext?.id ? `\n\n[Replying to message ${replyContext.id}]` : ""; - const body = formatInboundEnvelope({ - channel: "Linq iMessage", - from: fromLabel, - timestamp: createdAt, - body: `${bodyText}${replySuffix}`, - chatType: "direct", - sender: { name: sender, id: sender }, - previousTimestamp, - envelope: envelopeOptions, - }); - - const linqTo = chatId; - const ctxPayload = finalizeInboundContext({ - Body: body, - BodyForAgent: bodyText, - RawBody: bodyText, - CommandBody: bodyText, - From: `linq:${sender}`, - To: linqTo, - SessionKey: route.sessionKey, - AccountId: route.accountId, - ChatType: "direct", - ConversationLabel: fromLabel, - SenderName: sender, - SenderId: sender, - Provider: "linq", - Surface: "linq", - MessageSid: data.message.id, - ReplyToId: replyContext?.id, - Timestamp: createdAt, - MediaUrl: media[0]?.url, - MediaType: media[0]?.mimeType, - MediaUrls: media.length > 0 ? media.map((m) => m.url) : undefined, - MediaTypes: media.length > 0 ? media.map((m) => m.mimeType) : undefined, - WasMentioned: true, - CommandAuthorized: dmAuthorized, - OriginatingChannel: "linq" as const, - OriginatingTo: linqTo, - }); - - await recordInboundSession({ - storePath, - sessionKey: ctxPayload.SessionKey ?? route.sessionKey, - ctx: ctxPayload, - updateLastRoute: { - sessionKey: route.mainSessionKey, - channel: "linq", - to: linqTo, - accountId: route.accountId, - }, - onRecordError: (err) => { - logVerbose(`linq: failed updating session meta: ${String(err)}`); - }, - }); - - if (shouldLogVerbose()) { - const preview = truncateUtf16Safe(body, 200).replace(/\n/g, "\\n"); - logVerbose( - `linq inbound: chatId=${chatId} from=${sender} len=${body.length} preview="${preview}"`, - ); - } - - const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({ - cfg, - agentId: route.agentId, - channel: "linq", - accountId: route.accountId, - }); - - const dispatcher = createReplyDispatcher({ - ...prefixOptions, - humanDelay: resolveHumanDelayConfig(cfg, route.agentId), - deliver: async (payload) => { - const replyText = typeof payload === "string" ? payload : (payload.text ?? ""); - if (replyText) { - await sendMessageLinq(chatId, replyText, { - token, - accountId: accountInfo.accountId, - }); - } - }, - onError: (err, info) => { - runtime.error?.(danger(`linq ${info.kind} reply failed: ${String(err)}`)); - }, - }); - - await dispatchInboundMessage({ - ctx: ctxPayload, - cfg, - dispatcher, - replyOptions: { - disableBlockStreaming: - typeof linqCfg.blockStreaming === "boolean" ? !linqCfg.blockStreaming : undefined, - onModelSelected, - }, - }); - } - - // --- HTTP webhook server --- - const port = linqCfg.webhookUrl ? new URL(linqCfg.webhookUrl).port || "0" : "0"; - - const server: Server = createServer(async (req: IncomingMessage, res: ServerResponse) => { - const url = new URL(req.url || "/", `http://${req.headers.host}`); - if (req.method !== "POST" || !url.pathname.startsWith(webhookPath)) { - res.writeHead(404); - res.end(); - return; - } - - const chunks: Buffer[] = []; - let size = 0; - const maxPayloadBytes = 1024 * 1024; // 1MB limit - for await (const chunk of req) { - size += (chunk as Buffer).length; - if (size > maxPayloadBytes) { - res.writeHead(413); - res.end(); - return; - } - chunks.push(chunk as Buffer); - } - const rawBody = Buffer.concat(chunks).toString("utf8"); - - // Verify webhook signature if a secret is configured. - if (webhookSecret) { - const timestamp = req.headers["x-webhook-timestamp"] as string | undefined; - const signature = req.headers["x-webhook-signature"] as string | undefined; - if ( - !timestamp || - !signature || - !verifyWebhookSignature(webhookSecret, rawBody, timestamp, signature) - ) { - res.writeHead(401); - res.end("invalid signature"); - return; - } - // Reject stale webhooks (>5 minutes). - const age = Math.abs(Date.now() / 1000 - Number(timestamp)); - if (age > 300) { - res.writeHead(401); - res.end("stale timestamp"); - return; - } - } - - // Acknowledge immediately. - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ received: true })); - - // Parse and dispatch. - try { - const event = JSON.parse(rawBody) as LinqWebhookEvent; - if (event.event_type === "message.received") { - const data = event.data as LinqMessageReceivedData; - await inboundDebouncer.enqueue({ event: data }); - } - } catch (err) { - runtime.error?.(`linq webhook parse error: ${String(err)}`); - } - }); - - const listenPort = Number(port) || 0; - await new Promise((resolve, reject) => { - server.listen(listenPort, webhookHost, () => { - const addr = server.address(); - const boundPort = typeof addr === "object" ? addr?.port : listenPort; - runtime.info(`linq: webhook listener started on ${webhookHost}:${boundPort}${webhookPath}`); - resolve(); - }); - server.on("error", reject); - }); - - // Handle shutdown. - const abort = opts.abortSignal; - if (abort) { - const onAbort = () => { - server.close(); - }; - abort.addEventListener("abort", onAbort, { once: true }); - await new Promise((resolve) => { - server.on("close", resolve); - if (abort.aborted) { - server.close(); - } - }); - abort.removeEventListener("abort", onAbort); - } else { - await new Promise((resolve) => { - server.on("close", resolve); - }); - } -} diff --git a/src/linq/probe.ts b/src/linq/probe.ts deleted file mode 100644 index 6a0897484..000000000 --- a/src/linq/probe.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { loadConfig } from "../config/config.js"; -import { resolveLinqAccount } from "./accounts.js"; -import type { LinqProbe } from "./types.js"; - -const LINQ_API_BASE = "https://api.linqapp.com/api/partner/v3"; - -/** - * Probe Linq API availability by listing phone numbers. - * - * @param token - Linq API token (if not provided, resolved from config). - * @param timeoutMs - Request timeout in milliseconds. - */ -export async function probeLinq( - token?: string, - timeoutMs?: number, - accountId?: string, -): Promise { - let resolvedToken = token?.trim() ?? ""; - if (!resolvedToken) { - const cfg = loadConfig(); - const account = resolveLinqAccount({ cfg, accountId }); - resolvedToken = account.token; - } - if (!resolvedToken) { - return { ok: false, error: "Linq API token not configured" }; - } - - const url = `${LINQ_API_BASE}/phonenumbers`; - const controller = new AbortController(); - const timer = timeoutMs && timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null; - - try { - const response = await fetch(url, { - method: "GET", - headers: { Authorization: `Bearer ${resolvedToken}`, "User-Agent": "OpenClaw/1.0" }, - signal: controller.signal, - }); - if (!response.ok) { - const text = await response.text().catch(() => ""); - return { ok: false, error: `Linq API ${response.status}: ${text.slice(0, 200)}` }; - } - const data = (await response.json()) as { - phone_numbers?: Array<{ phone_number?: string }>; - }; - const phoneNumbers = (data.phone_numbers ?? []) - .map((p) => p.phone_number) - .filter(Boolean) as string[]; - return { ok: true, phoneNumbers }; - } catch (err) { - if (controller.signal.aborted) { - return { ok: false, error: `Linq probe timed out (${timeoutMs}ms)` }; - } - return { ok: false, error: String(err) }; - } finally { - if (timer) { - clearTimeout(timer); - } - } -} diff --git a/src/linq/send.ts b/src/linq/send.ts deleted file mode 100644 index 3ff6597e6..000000000 --- a/src/linq/send.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { loadConfig } from "../config/config.js"; -import { resolveLinqAccount, type ResolvedLinqAccount } from "./accounts.js"; -import type { LinqSendResult } from "./types.js"; - -const LINQ_API_BASE = "https://api.linqapp.com/api/partner/v3"; -const UA = "OpenClaw/1.0"; - -export type LinqSendOpts = { - accountId?: string; - mediaUrl?: string; - replyToMessageId?: string; - verbose?: boolean; - token?: string; - config?: ReturnType; - account?: ResolvedLinqAccount; -}; - -/** - * Send a message via Linq Blue V3 API. - * - * @param to - Chat ID (Linq chat_id) to send to. - * @param text - Message text. - * @param opts - Optional send options. - */ -export async function sendMessageLinq( - to: string, - text: string, - opts: LinqSendOpts = {}, -): Promise { - const cfg = opts.config ?? loadConfig(); - const account = opts.account ?? resolveLinqAccount({ cfg, accountId: opts.accountId }); - const token = opts.token?.trim() || account.token; - if (!token) { - throw new Error("Linq API token not configured"); - } - - const parts: Array> = []; - if (text) { - parts.push({ type: "text", value: text }); - } - if (opts.mediaUrl?.trim()) { - parts.push({ type: "media", url: opts.mediaUrl.trim() }); - } - if (parts.length === 0) { - throw new Error("Linq send requires text or media"); - } - - const message: Record = { parts }; - if (opts.replyToMessageId?.trim()) { - message.reply_to = { message_id: opts.replyToMessageId.trim() }; - } - - const url = `${LINQ_API_BASE}/chats/${encodeURIComponent(to)}/messages`; - const response = await fetch(url, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - "User-Agent": UA, - }, - body: JSON.stringify({ message }), - }); - - if (!response.ok) { - const errorText = await response.text().catch(() => ""); - throw new Error(`Linq API error: ${response.status} ${errorText.slice(0, 200)}`); - } - - const data = (await response.json()) as { - chat_id?: string; - message?: { id?: string }; - }; - return { - messageId: data.message?.id ?? "unknown", - chatId: data.chat_id ?? to, - }; -} - -/** Send a typing indicator. */ -export async function startTypingLinq(chatId: string, token: string): Promise { - const url = `${LINQ_API_BASE}/chats/${encodeURIComponent(chatId)}/typing`; - await fetch(url, { - method: "POST", - headers: { Authorization: `Bearer ${token}`, "User-Agent": UA }, - }); -} - -/** Clear a typing indicator. */ -export async function stopTypingLinq(chatId: string, token: string): Promise { - const url = `${LINQ_API_BASE}/chats/${encodeURIComponent(chatId)}/typing`; - await fetch(url, { - method: "DELETE", - headers: { Authorization: `Bearer ${token}`, "User-Agent": UA }, - }); -} - -/** Mark a chat as read. */ -export async function markAsReadLinq(chatId: string, token: string): Promise { - const url = `${LINQ_API_BASE}/chats/${encodeURIComponent(chatId)}/read`; - await fetch(url, { - method: "POST", - headers: { Authorization: `Bearer ${token}`, "User-Agent": UA }, - }); -} - -/** Send a reaction to a message. */ -export async function sendReactionLinq( - messageId: string, - type: "love" | "like" | "dislike" | "laugh" | "emphasize" | "question", - token: string, - operation: "add" | "remove" = "add", -): Promise { - const url = `${LINQ_API_BASE}/messages/${encodeURIComponent(messageId)}/reactions`; - await fetch(url, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - "User-Agent": UA, - }, - body: JSON.stringify({ operation, type }), - }); -} diff --git a/src/linq/types.ts b/src/linq/types.ts deleted file mode 100644 index 19c567a53..000000000 --- a/src/linq/types.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** Linq Blue V3 webhook event envelope. */ -export type LinqWebhookEvent = { - api_version: "v3"; - event_id: string; - created_at: string; - trace_id: string; - partner_id: string; - event_type: string; - data: unknown; -}; - -export type LinqMessageReceivedData = { - chat_id: string; - from: string; - recipient_phone: string; - received_at: string; - is_from_me: boolean; - service: "iMessage" | "SMS" | "RCS"; - message: LinqIncomingMessage; -}; - -export type LinqIncomingMessage = { - id: string; - parts: LinqMessagePart[]; - effect?: { type: "screen" | "bubble"; name: string }; - reply_to?: { message_id: string; part_index?: number }; -}; - -export type LinqTextPart = { type: "text"; value: string }; -export type LinqMediaPart = { - type: "media"; - url?: string; - attachment_id?: string; - filename?: string; - mime_type?: string; - size?: number; -}; -export type LinqMessagePart = LinqTextPart | LinqMediaPart; - -export type LinqSendResult = { - messageId: string; - chatId: string; -}; - -export type LinqProbe = { - ok: boolean; - error?: string | null; - phoneNumbers?: string[]; -}; - -/** Per-account config for the Linq channel (mirrors the Zod schema shape). */ -export type LinqAccountConfig = { - name?: string; - enabled?: boolean; - /** Linq API bearer token. */ - apiToken?: string; - /** Read token from file instead of config (mutual exclusive with apiToken). */ - tokenFile?: string; - /** Phone number this account sends from (E.164). */ - fromPhone?: string; - /** DM security policy. */ - dmPolicy?: "pairing" | "open" | "disabled"; - /** Allowed sender IDs (phone numbers or "*"). */ - allowFrom?: Array; - /** Group chat security policy. */ - groupPolicy?: "open" | "allowlist" | "disabled"; - /** Allowed group sender IDs. */ - groupAllowFrom?: Array; - /** Max media size in MB (default: 10). */ - mediaMaxMb?: number; - /** Max text chunk length (default: 4000). */ - textChunkLimit?: number; - /** Webhook URL for inbound messages from Linq. */ - webhookUrl?: string; - /** Webhook HMAC signing secret. */ - webhookSecret?: string; - /** Local HTTP path prefix for the webhook listener (default: /linq-webhook). */ - webhookPath?: string; - /** Local HTTP host to bind the webhook listener on. */ - webhookHost?: string; - /** History limit for group chats. */ - historyLimit?: number; - /** Block streaming responses. */ - blockStreaming?: boolean; - /** Group configs keyed by chat_id. */ - groups?: Record; - /** Per-account sub-accounts. */ - accounts?: Record; -}; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 3a52c7832..47ef9f247 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -123,7 +123,6 @@ export { DiscordConfigSchema, GoogleChatConfigSchema, IMessageConfigSchema, - LinqConfigSchema, MSTeamsConfigSchema, SignalConfigSchema, SlackConfigSchema, @@ -456,14 +455,5 @@ export { } from "../line/markdown-to-line.js"; export type { ProcessedLineMessage } from "../line/markdown-to-line.js"; -// Channel: Linq -export { - listLinqAccountIds, - resolveDefaultLinqAccountId, - resolveLinqAccount, - type ResolvedLinqAccount, -} from "../linq/accounts.js"; -export type { LinqProbe } from "../linq/types.js"; - // Media utilities export { loadWebMedia, type WebMediaResult } from "../web/media.js"; diff --git a/src/plugins/runtime/index.ts b/src/plugins/runtime/index.ts index 1e0376a69..d5abe6560 100644 --- a/src/plugins/runtime/index.ts +++ b/src/plugins/runtime/index.ts @@ -1,4 +1,5 @@ import { createRequire } from "node:module"; +import type { PluginRuntime } from "./types.js"; import { resolveEffectiveMessagesConfig, resolveHumanDelayConfig } from "../../agents/identity.js"; import { createMemoryGetTool, createMemorySearchTool } from "../../agents/tools/memory-tool.js"; import { handleSlackAction } from "../../agents/tools/slack-actions.js"; @@ -91,14 +92,6 @@ import { sendMessageLine, } from "../../line/send.js"; import { buildTemplateMessageFromPayload } from "../../line/template-messages.js"; -import { - listLinqAccountIds, - resolveDefaultLinqAccountId, - resolveLinqAccount, -} from "../../linq/accounts.js"; -import { monitorLinqProvider } from "../../linq/monitor.js"; -import { probeLinq } from "../../linq/probe.js"; -import { sendMessageLinq } from "../../linq/send.js"; import { getChildLogger } from "../../logging.js"; import { normalizeLogLevel } from "../../logging/levels.js"; import { convertMarkdownTables } from "../../markdown/tables.js"; @@ -146,7 +139,6 @@ import { } from "../../web/auth-store.js"; import { loadWebMedia } from "../../web/media.js"; import { formatNativeDependencyHint } from "./native-deps.js"; -import type { PluginRuntime } from "./types.js"; let cachedVersion: string | null = null; @@ -386,14 +378,6 @@ export function createPluginRuntime(): PluginRuntime { probeIMessage, sendMessageIMessage, }, - linq: { - sendMessageLinq, - probeLinq, - monitorLinqProvider, - listLinqAccountIds, - resolveDefaultLinqAccountId, - resolveLinqAccount, - }, whatsapp: { getActiveWebListener, getWebAuthAgeMs, diff --git a/src/plugins/runtime/types.ts b/src/plugins/runtime/types.ts index e84a9acb3..71b85d6f1 100644 --- a/src/plugins/runtime/types.ts +++ b/src/plugins/runtime/types.ts @@ -132,15 +132,6 @@ type SignalMessageActions = type MonitorIMessageProvider = typeof import("../../imessage/monitor.js").monitorIMessageProvider; type ProbeIMessage = typeof import("../../imessage/probe.js").probeIMessage; type SendMessageIMessage = typeof import("../../imessage/send.js").sendMessageIMessage; - -// Linq channel types -type SendMessageLinq = typeof import("../../linq/send.js").sendMessageLinq; -type ProbeLinq = typeof import("../../linq/probe.js").probeLinq; -type MonitorLinqProvider = typeof import("../../linq/monitor.js").monitorLinqProvider; -type ListLinqAccountIds = typeof import("../../linq/accounts.js").listLinqAccountIds; -type ResolveDefaultLinqAccountId = - typeof import("../../linq/accounts.js").resolveDefaultLinqAccountId; -type ResolveLinqAccount = typeof import("../../linq/accounts.js").resolveLinqAccount; type GetActiveWebListener = typeof import("../../web/active-listener.js").getActiveWebListener; type GetWebAuthAgeMs = typeof import("../../web/auth-store.js").getWebAuthAgeMs; type LogoutWeb = typeof import("../../web/auth-store.js").logoutWeb; @@ -326,14 +317,6 @@ export type PluginRuntime = { probeIMessage: ProbeIMessage; sendMessageIMessage: SendMessageIMessage; }; - linq: { - sendMessageLinq: SendMessageLinq; - probeLinq: ProbeLinq; - monitorLinqProvider: MonitorLinqProvider; - listLinqAccountIds: ListLinqAccountIds; - resolveDefaultLinqAccountId: ResolveDefaultLinqAccountId; - resolveLinqAccount: ResolveLinqAccount; - }; whatsapp: { getActiveWebListener: GetActiveWebListener; getWebAuthAgeMs: GetWebAuthAgeMs;