feat: add Linq channel — real iMessage via API, no Mac required

Adds a complete Linq iMessage channel adapter that replaces the existing
iMessage channel's Mac Mini + dedicated Apple ID + SSH wrapper + Full Disk
Access setup with a single API key and phone number.

Core implementation (src/linq/):
- types.ts: Linq webhook event and message types
- accounts.ts: Multi-account resolution from config (env/file/inline token)
- send.ts: REST outbound via Linq Blue V3 API (messages, typing, reactions)
- probe.ts: Health check via GET /v3/phonenumbers
- monitor.ts: Webhook HTTP server with HMAC-SHA256 signature verification,
  replay protection, inbound debouncing, and full dispatch pipeline integration

Extension plugin (extensions/linq/):
- ChannelPlugin implementation with config, security, setup, outbound,
  gateway, and status adapters
- Supports direct and group chats, reactions, and media

Wiring:
- Channel registry, dock, config schema, plugin-sdk exports, and plugin
  runtime all updated to include the new linq channel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
George McCain
2026-02-13 15:51:57 -05:00
committed by Peter Steinberger
parent 95024d1671
commit d4a142fd8f
17 changed files with 1392 additions and 15 deletions

View File

@@ -92,6 +92,14 @@ 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";
@@ -378,6 +386,14 @@ export function createPluginRuntime(): PluginRuntime {
probeIMessage,
sendMessageIMessage,
},
linq: {
sendMessageLinq,
probeLinq,
monitorLinqProvider,
listLinqAccountIds,
resolveDefaultLinqAccountId,
resolveLinqAccount,
},
whatsapp: {
getActiveWebListener,
getWebAuthAgeMs,

View File

@@ -132,6 +132,15 @@ 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;
@@ -317,6 +326,14 @@ 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;