Extend FeishuCardActionEvent type and handleFeishuCardAction() to extract
data from select_static dropdowns (action.option), multi-select checkboxes
(action.options), and form submissions (action.form_value).
Previously only action.value was read, silently discarding interaction data
from all non-button card components. The new fields are merged with the
existing value object to provide complete context to downstream handlers.
Existing button flows (value.text and value.command) are completely
unaffected — the new extraction only activates when the component-specific
fields are present.
Add tests for select_static, checkbox, and form submission scenarios.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The revert of #43478 (commit 39b4185d0b) was silently undone by
3704293e6f which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.
The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.
Reuses the cron isolated session pattern (resolveCronSession with forceNew)
to give each heartbeat a fresh session with no prior conversation history.
Reduces per-heartbeat token cost from ~100K to ~2-5K tokens.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(feishu): fetch thread context so AI can see bot replies in topic threads
When a user replies in a Feishu topic thread, the AI previously could only
see the quoted parent message but not the bot's own prior replies in the
thread. This made multi-turn conversations in threads feel broken.
- Add `threadId` (omt_xxx) to `FeishuMessageInfo` and `getMessageFeishu`
- Add `listFeishuThreadMessages()` using `container_id_type=thread` API
to fetch all messages in a thread including bot replies
- In `handleFeishuMessage`, fetch ThreadStarterBody and ThreadHistoryBody
for topic session modes and pass them to the AI context
- Reuse quoted message result when rootId === parentId to avoid redundant
API calls; exclude root message from thread history to prevent duplication
- Fall back to inbound ctx.threadId when rootId is absent or API fails
- Fetch newest messages first (ByCreateTimeDesc + reverse) so long threads
keep the most recent turns instead of the oldest
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(feishu): skip redundant thread context injection on subsequent turns
Only inject ThreadHistoryBody on the first turn of a thread session.
On subsequent turns the session already contains prior context, so
re-injecting thread history (and starter) would waste tokens.
The heuristic checks whether the current user has already sent a
non-root message in the thread — if so, the session has prior turns
and thread context injection is skipped entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(feishu): handle thread_id-only events in prior-turn detection
When ctx.rootId is undefined (thread_id-only events), the starter
message exclusion check `msg.messageId !== ctx.rootId` was always
true, causing the first follow-up to be misclassified as a prior
turn. Fall back to the first message in the chronologically-sorted
thread history as the starter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(feishu): bootstrap topic thread context via session state
* test(memory): pin remote embedding hostnames in offline suites
* fix(feishu): use plugin-safe session runtime for thread bootstrap
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Fixes#46142
Stop forcing supportsUsageInStreaming=false on non-native openai-completions
endpoints. Most OpenAI-compatible APIs (DashScope, DeepSeek, Groq, Together,
etc.) handle stream_options: { include_usage: true } correctly. The blanket
disable broke usage/cost tracking for all non-OpenAI providers.
supportsDeveloperRole is still forced off for non-native endpoints since
the developer message role is genuinely OpenAI-specific.
Users on backends that reject stream_options can opt out with
compat.supportsUsageInStreaming: false in their model config.
Fixes#46142
Fixes#43057
* fix(auth): clear stale lockout on re-login
Clear stale `auth_permanent` and `billing` disabled state for all
profiles matching the target provider when `openclaw models auth login`
is invoked, so users locked out by expired or revoked OAuth tokens can
recover by re-authenticating instead of waiting for the cooldown timer.
Uses the agent-scoped store (`loadAuthProfileStoreForRuntime`) for
correct multi-agent profile resolution and wraps the housekeeping in
try/catch so corrupt store files never block re-authentication.
Fixes#43057
* test(auth): remove unnecessary non-null assertions
oxlint no-unnecessary-type-assertion: invocationCallOrder[0]
already returns number, not number | undefined.
Fixes#42931
When gateway.auth.mode is set to "none", authentication succeeds with
method "none" but sharedAuthOk remains false because the auth-context
only recognises token/password/trusted-proxy methods. This causes all
pairing-skip conditions to fail, so Control UI browser connections get
closed with code 1008 "pairing required" despite auth being disabled.
Short-circuit the skipPairing check: if the operator explicitly
disabled authentication, device pairing (which is itself an auth
mechanism) must also be bypassed.
Fixes#42931
Fixes#43322
* fix(feishu): clear stale streamingStartPromise on card creation failure
When FeishuStreamingSession.start() throws (HTTP 400), the catch block
sets streaming = null but leaves streamingStartPromise dangling. The
guard in startStreaming() checks streamingStartPromise first, so all
future deliver() calls silently skip streaming - the session locks
permanently.
Clear streamingStartPromise in the catch block so subsequent messages
can retry streaming instead of dropping all future replies.
Fixes#43322
* test(feishu): wrap push override in try/finally for cleanup safety