diff --git a/extensions/linq/src/channel.ts b/extensions/linq/src/channel.ts index 7b64ba942e..aaa3f062dc 100644 --- a/extensions/linq/src/channel.ts +++ b/extensions/linq/src/channel.ts @@ -16,7 +16,6 @@ import { type ResolvedLinqAccount, type LinqProbe, LinqConfigSchema, - linqOnboardingAdapter, } from "openclaw/plugin-sdk"; import { getLinqRuntime } from "./runtime.js"; @@ -28,7 +27,6 @@ export const linqPlugin: ChannelPlugin = { ...meta, aliases: ["linq-imessage"], }, - onboarding: linqOnboardingAdapter, pairing: { idLabel: "phoneNumber", notifyApproval: async ({ id }) => { diff --git a/src/channels/plugins/onboarding/linq.ts b/src/channels/plugins/onboarding/linq.ts deleted file mode 100644 index 3a95144dda..0000000000 --- a/src/channels/plugins/onboarding/linq.ts +++ /dev/null @@ -1,302 +0,0 @@ -import type { OpenClawConfig } from "../../../config/config.js"; -import type { DmPolicy } from "../../../config/types.js"; -import { - listLinqAccountIds, - resolveDefaultLinqAccountId, - resolveLinqAccount, -} from "../../../linq/accounts.js"; -import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js"; -import type { WizardPrompter } from "../../../wizard/prompts.js"; -import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js"; -import { addWildcardAllowFrom, promptAccountId } from "./helpers.js"; - -const channel = "linq" as const; - -function setLinqDmPolicy(cfg: OpenClawConfig, dmPolicy: DmPolicy) { - const allowFrom = - dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.linq?.allowFrom) : undefined; - return { - ...cfg, - channels: { - ...cfg.channels, - linq: { - ...cfg.channels?.linq, - dmPolicy, - ...(allowFrom ? { allowFrom } : {}), - }, - }, - }; -} - -async function noteLinqTokenHelp(prompter: WizardPrompter): Promise { - await prompter.note( - [ - "1) Sign up at linqapp.com", - "2) Copy your API token from the dashboard", - "3) Tip: you can also set LINQ_API_TOKEN in your env.", - ].join("\n"), - "Linq API token", - ); -} - -async function noteLinqPhoneHelp(prompter: WizardPrompter): Promise { - await prompter.note( - [ - "Your Linq phone number is shown in your linqapp.com dashboard.", - "This is the number people will text to reach your agent.", - ].join("\n"), - "Linq phone number", - ); -} - -const dmPolicy: ChannelOnboardingDmPolicy = { - label: "Linq", - channel, - policyKey: "channels.linq.dmPolicy", - allowFromKey: "channels.linq.allowFrom", - getCurrent: (cfg) => cfg.channels?.linq?.dmPolicy ?? "open", - setPolicy: (cfg, policy) => setLinqDmPolicy(cfg, policy), -}; - -export const linqOnboardingAdapter: ChannelOnboardingAdapter = { - channel, - getStatus: async ({ cfg }) => { - const configured = listLinqAccountIds(cfg).some((accountId) => - Boolean(resolveLinqAccount({ cfg, accountId }).token), - ); - return { - channel, - configured, - statusLines: [`Linq: ${configured ? "configured" : "needs token"}`], - selectionHint: configured - ? "recommended · configured" - : "recommended · iMessage blue bubbles", - quickstartScore: configured ? 1 : 10, - }; - }, - configure: async ({ - cfg, - prompter, - accountOverrides, - shouldPromptAccountIds, - forceAllowFrom, - }) => { - const linqOverride = accountOverrides.linq?.trim(); - const defaultLinqAccountId = resolveDefaultLinqAccountId(cfg); - let linqAccountId = linqOverride ? normalizeAccountId(linqOverride) : defaultLinqAccountId; - if (shouldPromptAccountIds && !linqOverride) { - linqAccountId = await promptAccountId({ - cfg, - prompter, - label: "Linq", - currentId: linqAccountId, - listAccountIds: listLinqAccountIds, - defaultAccountId: defaultLinqAccountId, - }); - } - - let next = cfg; - const resolvedAccount = resolveLinqAccount({ - cfg: next, - accountId: linqAccountId, - }); - const accountConfigured = Boolean(resolvedAccount.token); - const allowEnv = linqAccountId === DEFAULT_ACCOUNT_ID; - const canUseEnv = allowEnv && Boolean(process.env.LINQ_API_TOKEN?.trim()); - const hasConfigToken = Boolean( - resolvedAccount.config.apiToken || resolvedAccount.config.tokenFile, - ); - - // --- Token --- - let token: string | null = null; - if (!accountConfigured) { - await noteLinqTokenHelp(prompter); - } - if (canUseEnv && !resolvedAccount.config.apiToken) { - const keepEnv = await prompter.confirm({ - message: "LINQ_API_TOKEN detected. Use env var?", - initialValue: true, - }); - if (keepEnv) { - next = { - ...next, - channels: { - ...next.channels, - linq: { - ...next.channels?.linq, - enabled: true, - }, - }, - }; - } else { - token = String( - await prompter.text({ - message: "Enter Linq API token", - validate: (value) => (value?.trim() ? undefined : "Required"), - }), - ).trim(); - } - } else if (hasConfigToken) { - const keep = await prompter.confirm({ - message: "Linq token already configured. Keep it?", - initialValue: true, - }); - if (!keep) { - token = String( - await prompter.text({ - message: "Enter Linq API token", - validate: (value) => (value?.trim() ? undefined : "Required"), - }), - ).trim(); - } - } else { - token = String( - await prompter.text({ - message: "Enter Linq API token", - validate: (value) => (value?.trim() ? undefined : "Required"), - }), - ).trim(); - } - - if (token) { - if (linqAccountId === DEFAULT_ACCOUNT_ID) { - next = { - ...next, - channels: { - ...next.channels, - linq: { - ...next.channels?.linq, - enabled: true, - apiToken: token, - }, - }, - }; - } else { - next = { - ...next, - channels: { - ...next.channels, - linq: { - ...next.channels?.linq, - enabled: true, - accounts: { - ...next.channels?.linq?.accounts, - [linqAccountId]: { - ...next.channels?.linq?.accounts?.[linqAccountId], - enabled: next.channels?.linq?.accounts?.[linqAccountId]?.enabled ?? true, - apiToken: token, - }, - }, - }, - }, - }; - } - } - - // --- fromPhone --- - await noteLinqPhoneHelp(prompter); - const existingPhone = resolvedAccount.fromPhone; - const fromPhone = String( - await prompter.text({ - message: "Linq phone number (E.164 format, e.g. +15551234567)", - initialValue: existingPhone, - validate: (value) => (value?.trim() ? undefined : "Required"), - }), - ).trim(); - - if (linqAccountId === DEFAULT_ACCOUNT_ID) { - next = { - ...next, - channels: { - ...next.channels, - linq: { - ...next.channels?.linq, - fromPhone, - }, - }, - }; - } else { - next = { - ...next, - channels: { - ...next.channels, - linq: { - ...next.channels?.linq, - accounts: { - ...next.channels?.linq?.accounts, - [linqAccountId]: { - ...next.channels?.linq?.accounts?.[linqAccountId], - fromPhone, - }, - }, - }, - }, - }; - } - - // --- Webhook config --- - const linqSection = (next.channels as Record | undefined)?.linq as - | Record - | undefined; - const existingWebhookUrl = - (linqSection?.webhookUrl as string) ?? "http://localhost:3100/webhook"; - const webhookUrl = String( - await prompter.text({ - message: "Webhook URL", - initialValue: existingWebhookUrl, - validate: (value) => (value?.trim() ? undefined : "Required"), - }), - ).trim(); - - const existingWebhookPath = (linqSection?.webhookPath as string) ?? "/webhook"; - const webhookPath = String( - await prompter.text({ - message: "Webhook path", - initialValue: existingWebhookPath, - validate: (value) => (value?.trim() ? undefined : "Required"), - }), - ).trim(); - - const existingWebhookHost = (linqSection?.webhookHost as string) ?? "0.0.0.0"; - const webhookHost = String( - await prompter.text({ - message: "Webhook host", - initialValue: existingWebhookHost, - validate: (value) => (value?.trim() ? undefined : "Required"), - }), - ).trim(); - - next = { - ...next, - channels: { - ...next.channels, - linq: { - ...next.channels?.linq, - webhookUrl, - webhookPath, - webhookHost, - }, - }, - }; - - // --- DM policy default --- - if (!next.channels?.linq?.dmPolicy) { - next = setLinqDmPolicy(next, "open"); - } - - if (forceAllowFrom) { - // Linq doesn't have username resolution, so we skip the allowFrom prompting - // that Telegram does. The allowFrom list uses phone numbers directly. - } - - return { cfg: next, accountId: linqAccountId }; - }, - dmPolicy, - disable: (cfg) => ({ - ...cfg, - channels: { - ...cfg.channels, - linq: { ...cfg.channels?.linq, enabled: false }, - }, - }), -}; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index f7182fb913..3a52c78329 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -463,7 +463,6 @@ export { resolveLinqAccount, type ResolvedLinqAccount, } from "../linq/accounts.js"; -export { linqOnboardingAdapter } from "../channels/plugins/onboarding/linq.js"; export type { LinqProbe } from "../linq/types.js"; // Media utilities