Empty member/group Agent Preset now resolves to the bot workspace mapping (not Host global); settings label is Follow channel mapping, and /new createSession carries that mapping explicitly.
Co-authored-by: Cursor <cursoragent@cursor.com>
Classify gateway/internal, workspace/*, and agent-preset/* into actionable IM codes, enrich field diagnostics, realpath-match workspaces, and retry session.create once without a failing Agent Preset.
Co-authored-by: Cursor <cursoragent@cursor.com>
Match workspace.list entries case-insensitively on Win32 so D: vs d: no longer misses an existing workspace and falls through to SESSION_CREATE.
Co-authored-by: Cursor <cursoragent@cursor.com>
Claim in-flight IM turns before ownership flips active so approvals no longer stick only in DSH Web, auto-recover stale session models once, and let WhatsApp bots pick a shared default model for new sessions.
Co-authored-by: Cursor <cursoragent@cursor.com>
Localize WhatsApp access-grant approvals via t() and push Host language from the UI on load and locale change.
Co-authored-by: Cursor <cursoragent@cursor.com>
Send the probe to the bound private-chat target, falling back to Message Yourself via live LID so Baileys no longer reports success while the phone never receives it.
Co-authored-by: Cursor <cursoragent@cursor.com>
Require trusted loopback requests for proactive send, accept hyphenated WhatsApp group JIDs, and serialize access-grant mutations to avoid lost concurrent updates.
Co-authored-by: Cursor <cursoragent@cursor.com>
Surface chat/access-list contacts as selectable targets with clearer nicknames, and keep conversation peers usable for ops schedulers.
Co-authored-by: Cursor <cursoragent@cursor.com>
Plugins inject conversation.session.header.actions; resolve sessionId back to the bound chat and render nickname/phone or group title.
Co-authored-by: Cursor <cursoragent@cursor.com>
Access grants can pin an agentPreset on direct members and groups; new sessions resolve override over the bot-global preset.
Co-authored-by: Cursor <cursoragent@cursor.com>
Refresh missing/forced group titles via groupMetadata and participating catalog so cards show real names instead of bare JIDs.
Co-authored-by: Cursor <cursoragent@cursor.com>
Fetch groupMetadata.subject on @mentions for group cards; render contact pushName under phone rows so admins are not staring at bare numbers.
Co-authored-by: Cursor <cursoragent@cursor.com>
Client normalizeBot dropped accessGrant/groupSessionScope, so pending, contacts, and group cards never appeared. Also record contacts only on DM/@, auto-create group buckets on @, and add one-click grant buttons.
Co-authored-by: Cursor <cursoragent@cursor.com>
Mention triggers leave opaque LID digits in the body; remove those only,
without injecting sender identity (use context enhancement when needed).
Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve LID to phone aliases before allowlist and @mention checks so
group prompts are not silently dropped after a working direct chat.
Co-authored-by: Cursor <cursoragent@cursor.com>
Ops bots now isolate per-speaker Harness sessions in groups, with a
settings/RPC toggle to restore shared chat sessions when needed.
Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve the current main conflict, treat reply timeouts as inactivity windows, and confirm liveness through Harness instead of sticky interaction ownership.
Add chatId and threadId source fields across all nine IM channels, update UI/docs/tests, reconcile the latest main branch, and credit @Chan-0312 as a contributor.
Address review feedback on the interaction cards:
1. Bind card decisions to the initiating actor. The card-action operator
(operatorOpenId) is now passed into the approval and question handlers:
- submitByApprovalId receives { actor } so another allowed group member
cannot approve/reject someone else's approval;
- the answer branch requires pending.actor === operator.
2. Include the question index in the answer button action and validate it
against the current pending question, so a stale card from an earlier
question in a multi-question interaction cannot be applied to the next one.
3. When both the card send and the plain-text fallback fail, the error is no
longer swallowed: it propagates so the interaction is not marked as
presented and the existing retry/reconnect behaviour can run.
Adds regression tests for all three cases.
Render Harness approval and single-choice question interactions as
interactive Feishu cards with approve/reject and answer buttons,
matching the already-available interactionCards behaviour. Cards are now
the default; set DSH_IM_INTERACTION_CARDS=0 (or interactionCards=false)
to keep the plain-text reply flow.
- bridge.mjs: approve:/reject:/answer: card actions, interactionCards
option, gated approval render hook, interactive question presentation,
and operationArguments/printableText helpers; refactor the inline
question answer into #submitQuestionAnswer so both text replies and
card buttons share one path.
- feishu-cards.mjs: approvalCard (approve/reject) and questionCard
(option buttons) templates.
- feishu-runtime.mjs: default interactionCards from env (on).
- harness-approval.mjs: optional channel render hook + submitByApprovalId.
- i18n-en: add the new card t() keys.
- Tests: pin the plain-text reply flow in state-machine suites that
drive it, and add dedicated card tests for the default approve/reject,
answer buttons, and the text fallback. Full suite shows no new
failures versus upstream.
A long-running Harness task was falsely reported as MODEL_REPLY_TIMEOUT
after the fixed 10-minute deadline even though the backend was still
working. Replace the hard wall-clock deadline with an activity-based
stall window: the wait renews whenever the turn produces new events, the
control ownership is active, or (mid-turn with no new events) Harness
still reports the session as running. Only a genuine stall with no
progress for the whole timeoutMs window fails fast.
This is the shared harness-client layer, exercised end-to-end by the
Feishu channel (which already exposes HARNESS_REPLY_TIMEOUT_MS); other
channels inherit the same fix.
Adds two regression tests: a long-running turn with periodic activity
completes instead of timing out, and a genuinely stalled turn still
fails with harness-reply-timeout.
webSocketProxyUrl() picks up https_proxy/HTTPS_PROXY/http_proxy/
HTTP_PROXY and forces the Feishu WSS long connection through the proxy,
without consulting NO_PROXY/no_proxy. With a local proxy exported in
the shell (Clash/V2Ray setups), every long-connection attempt is
rejected by Feishu's server and the runtime force-closes and retries
in a loop, so the bot never reaches a stable connected state.
Resolve NO_PROXY/no_proxy for the long-connection endpoints
(open.feishu.cn / open.larksuite.com) before falling back to the proxy
env: exact host, parent-domain (.cn, feishu.cn) and * entries follow
the common NO_PROXY semantics.
- production.mjs: NO_PROXY gate in front of the proxy lookup
- feishu-proxy.test.mjs: NO_PROXY exclusion and fallthrough cases
- lib/index.js: regenerated host bundle
Plain command replies (menu cards, session/workspace/watch lists, /new,
/status, /compact, /archived, model and preset commands) and Harness
approval prompts were sent as fresh messages keyed only by chat_id, so
in topic groups they landed in the group's default area instead of the
topic the conversation belongs to.
Deliver them as replies to the triggering message, mirroring how stream
answers already thread:
- #sendCard accepts { replyTo } and replies interactively, falling back
to a plain card when the referenced message is gone
- command replies, status text, menu/session/workspace/watch cards,
approval prompts and resolved-question notices all carry the
triggering message id
- batch-input failure notices follow the same rule
- bridge.test.mjs: /new in a topic group must thread the confirmation
text and the menu card to the command message
- lib/index.js: regenerated host bundle
Regular answers reuse the stream card, which is created through the
reply API targeting the triggering message, so they land in the right
Feishu thread. Pending Harness questions (ask_user_question) were sent
through a fresh message.create with only the chat_id, so in topic
groups they appeared in the group's default area instead of the topic
the conversation belongs to.
Thread question delivery the same way as answers:
- #send accepts { replyTo } and uses im.v1.message.reply, falling back
to a plain message when the referenced one is gone
- the triggering message id now travels into the interaction context
and pending state, and #presentInteraction replies to it
- resolved-question notices reply to the user's answer message
- bridge.test.mjs: assert the question is delivered via the reply API
targeting the triggering message inside a topic group
- lib/index.js: regenerated host bundle
conversationKey derived the group session key from chat_id alone, so in
Feishu topic groups every topic shared one Harness session: context,
/new, pending approvals, and batch-input state all leaked across topics.
Append the event's thread_id to the session key when present. Topic
groups stamp every message with a thread_id, so each topic now maps to
its own group:<chat_id>🧵<thread_id> session. Regular group chats
carry no thread_id and keep the single shared group:<chat_id> key; p2p
keys are untouched.
- message-utils.mjs: thread-aware conversationKey
- message-utils.test.mjs: topic isolation + regular-group fallback cases
- lib/index.js: regenerated host bundle
QQ delivers emoji messages as opaque <faceType=..,faceId="..",ext="..">
markup fragments (the ext field holds a base64 JSON blob with the face
name, e.g. {"text":"吃瓜"}). Without translation, the Harness receives
only this unusable tag and loses what the sender actually meant.
Register the SDK's contentSanitizer({ parseFaceTags: true }) middleware,
mirroring tencent-connect/dsh-qqbot, so C2C and group inbound messages
surface as e.g. 【表情: 吃瓜】 instead of the raw tag.
- qq-runtime.mjs: register contentSanitizer before the typing indicator
- runtime.test.mjs: assert sanitizer registration and update middleware order
- lib/index.js: regenerated host bundle