From 1d81cc4f1ff1c99dfd386c7965a52f4242cf1a60 Mon Sep 17 00:00:00 2001 From: George McCain Date: Sat, 14 Feb 2026 07:49:45 -0500 Subject: [PATCH] feat(linq): add read receipts, typing indicators, and User-Agent header Send read receipt and typing indicator immediately on inbound messages for a more natural iMessage experience. Add User-Agent header to all Linq API requests. Fix delivery payload to use .text instead of .body. Co-Authored-By: Claude Opus 4.6 --- src/linq/monitor.ts | 9 ++++++--- src/linq/probe.ts | 2 +- src/linq/send.ts | 9 ++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/linq/monitor.ts b/src/linq/monitor.ts index c9ac12ba83..bcb4f2a442 100644 --- a/src/linq/monitor.ts +++ b/src/linq/monitor.ts @@ -34,7 +34,7 @@ import { import { resolveAgentRoute } from "../routing/resolve-route.js"; import { truncateUtf16Safe } from "../utils.js"; import { resolveLinqAccount } from "./accounts.js"; -import { sendMessageLinq } from "./send.js"; +import { markAsReadLinq, sendMessageLinq, startTypingLinq } from "./send.js"; export type MonitorLinqOpts = { accountId?: string; @@ -198,6 +198,10 @@ export async function monitorLinqProvider(opts: MonitorLinqOpts = {}): Promise {}); + startTypingLinq(chatId, token).catch(() => {}); + const storeAllowFrom = await readChannelAllowFromStore("linq").catch(() => []); const effectiveDmAllowFrom = Array.from(new Set([...allowFrom, ...storeAllowFrom])) .map((v) => String(v).trim()) @@ -342,8 +346,7 @@ export async function monitorLinqProvider(opts: MonitorLinqOpts = {}): Promise { - const replyText = - typeof payload === "string" ? payload : ((payload as { body?: string }).body ?? ""); + const replyText = typeof payload === "string" ? payload : (payload.text ?? ""); if (replyText) { await sendMessageLinq(chatId, replyText, { token, diff --git a/src/linq/probe.ts b/src/linq/probe.ts index 4d5288122b..e1c07ca72e 100644 --- a/src/linq/probe.ts +++ b/src/linq/probe.ts @@ -32,7 +32,7 @@ export async function probeLinq( try { const response = await fetch(url, { method: "GET", - headers: { Authorization: `Bearer ${resolvedToken}` }, + headers: { Authorization: `Bearer ${resolvedToken}`, "User-Agent": "OpenClaw/1.0" }, signal: controller.signal, }); if (!response.ok) { diff --git a/src/linq/send.ts b/src/linq/send.ts index 701361d4c6..a68b09fea1 100644 --- a/src/linq/send.ts +++ b/src/linq/send.ts @@ -3,6 +3,7 @@ import { loadConfig } from "../config/config.js"; import { resolveLinqAccount, type ResolvedLinqAccount } from "./accounts.js"; const LINQ_API_BASE = "https://api.linqapp.com/api/partner/v3"; +const UA = "OpenClaw/1.0"; export type LinqSendOpts = { accountId?: string; @@ -55,6 +56,7 @@ export async function sendMessageLinq( headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", + "User-Agent": UA, }, body: JSON.stringify({ message }), }); @@ -79,7 +81,7 @@ export async function startTypingLinq(chatId: string, token: string): Promise