Files
Starlight_Lancher/.agents/skills/lark-im/references/lark-im-card-action-reply.md

9.3 KiB

card.action.trigger

Prerequisite: Read ../../lark-event/SKILL.md first for event consume essentials.

Fires when a user interacts with an interactive card — button click, form submit, dropdown select, checkbox toggle, date/time pick, etc.

Setup (required)

Console configuration required: In the Feishu Developer Console, go to App → Events & Callbacks → Callback Configuration (应用--事件与回调--回调配置) and enable it. The consumer starts without errors even when not configured, but no events will be received. There is no preflight check for this setting.

After enabling, events are delivered over the existing WebSocket long connection — no additional URL configuration needed.

Scopes & auth

Scope Required for
im:message:readonly Auto-fetch card_content via message get API (covers both p2p and group messages)

Auth: bot only.

Output fields

Field Type Description
type string Always card.action.trigger
event_id string Unique event ID; safe for deduplication
timestamp string (timestamp_ms) Event delivery time (ms since epoch)
operator_id string (open_id) Open ID of the user who interacted
message_id string (message_id) Message ID of the card (om_xxx)
chat_id string (chat_id) Chat ID (oc_xxx)
host string im_message (chat card) or im_top_notice (top banner)
token string Delayed-update token; valid 30 min, max 2 uses
action_tag string Component type that was triggered (see decision table)
action_value string Developer-defined value on the component; serialized to JSON string
action_name string name attribute of the component
timezone string User timezone, e.g. Asia/Shanghai; only populated for date/time picker interactions
form_value string (JSON) All form field values as JSON string, keyed by component name; only present when a button inside a form container is clicked
input_value string Input text; only for standalone input components (not inside a form)
option string Selected value for standalone single-select: select_static, select_person, overflow, date_picker, picker_time, picker_datetime
options string Comma-separated selected values for standalone multi-select: multi_select_static, multi_select_person
checked bool Checkbox state for standalone checker elements
card_content string Original card content (userDSL text format) from when the card was sent; auto-fetched via message get API at consume time; empty if message_id absent or fetch fails — skip if empty

card_content — what it is and how to use it

card_content is the user_dsl field extracted from the card message content, auto-fetched at event consume time. It represents the card's original definition — use it as the starting point to understand the current card structure and construct the updated card JSON.

No extra API call is needed — the consumer fetches it automatically. If empty, skip — no fallback required.

action_tag decision table

Form container rule: when a component is inside a form container, its value appears in form_value[name] instead of the standalone fields (option, options, input_value, checked). There is no form_submit tag — form submission comes through as button with form_value populated.

action_tag Read field(s) Notes
button action_value (fromjson if object); form_value if inside a form Most common; form_value non-empty = form submit
overflow option Collapsible button group selection
select_static option (standalone) or form_value[name] (in form) Single-select dropdown
multi_select_static options (standalone) or form_value[name] (in form) Multi-select dropdown
select_person option — open_id of selected user Single-select person
multi_select_person options — comma-separated open_ids Multi-select person
input input_value (standalone) or form_value[name] (in form) Text input
checker checked (standalone) or form_value[name] (in form) Checkbox
date_picker option (date string) + timezone e.g. "2024-04-01 +0800"
picker_time option (time string) + timezone e.g. "08:30 +0800"
picker_datetime option (datetime string) + timezone e.g. "2024-04-29 07:07 +0800"
select_img option (single) or options (multi) Image picker

Key constraints

  1. Token valid 30 minutes, max 2 uses — if update fails after exhaustion, inform the user
  2. Delayed-update API requires complete new card JSON — partial updates are not supported
  3. SDK auto-responds {"code":200} within 3 s — your update call can be sent any time within 30 min
  4. card_content is auto-populated — no extra API call needed; if empty, skip it

After starting the listener

Once the listener is running, check whether your agent runtime supports background event monitoring (i.e. can receive and process stdout lines from a running subprocess while continuing to respond to the user). If it does, prompt the user:

"Card callback listener is now active. Do you want me to automatically handle card interactions and update the card based on user actions?"

Only enter the auto-update workflow below if the user confirms. If your runtime does not support background monitoring, inform the user that automatic card updates are not available and they will need to handle interactions manually.

Agent workflow

When a card.action.trigger event arrives (each stdout JSON line is one event — process it immediately):

1. Read action fields to understand what the user did:
   - action_tag: which component was triggered
   - action_value / option / options / checked / input_value / form_value: what value was set

2. Decide: does this interaction require a card update?
   - e.g. button click with a business action → yes
   - e.g. navigation / pagination → no (just record, no update needed)
   - Not every callback requires a card update — decide based on business semantics
   - Before updating, explicitly state what visual change the action requires. If you cannot articulate one, skip the update.

3. If update is needed:
   a. If card_content is empty: inform the user that the original card could not be fetched,
      so it is not possible to determine whether an update is needed — do not guess
   b. Determine the new card state based on the action
   c. Use card_content as the structural basis to construct the updated card JSON
   d. Detect card version: if card_content contains `"schema":"2.0"` or `"schema": "2.0"` it is Card 2.0; otherwise assume Card 1.0
   e. For Card 1.0: include `"open_ids": ["<operator_id>"]` inside the `card` object, or the API returns code 300090
   f. Call the delayed update API with the token and new card JSON

4. If no update: end (the SDK has already acknowledged the callback)

Updating the card

lark-cli api POST /open-apis/interactive/v1/card/update --as bot \
  --data '{"token":"<token>","card":<new_card_json>}'

--data parameters:

Field Required Description
token Yes Delayed-update token from the event
card Yes Complete new card JSON — construct based on card_content from the event, modified to reflect the new state
card.open_ids No Card 1.0 only. Array of open_ids defining which users see the updated card. Must contain at least one open_id (e.g. the operator's); passing [] or omitting the key both cause "openid empty" (code 300090).

Examples

# Stream all card interactions
lark-cli event consume card.action.trigger --as bot

# Grab one callback to inspect shape (debugging only — do not use in production workflows)
lark-cli event consume card.action.trigger --as bot --max-events 1 --timeout 60s

# Button clicks only (not form submit), with action value
lark-cli event consume card.action.trigger --as bot \
  --jq 'select(.action_tag == "button" and .form_value == "") | {op: .operator_id, val: (.action_value | fromjson?), token: .token}'

# Form submits (button with form_value present)
lark-cli event consume card.action.trigger --as bot \
  --jq 'select(.action_tag == "button" and .form_value != "") | {op: .operator_id, form: (.form_value | fromjson), token: .token}'

# Date picker interactions
lark-cli event consume card.action.trigger --as bot \
  --jq 'select(.action_tag == "date_picker") | {op: .operator_id, date: .option, tz: .timezone}'

# Filter to one chat
lark-cli event consume card.action.trigger --as bot \
  --jq 'select(.chat_id == "oc_xxx")'

Gotchas

  • No form_submit tag: form submission comes as action_tag = "button" with form_value populated. Check form_value != "" to distinguish from a standalone button click.
  • action_value type is developer-defined: the original may be an object or a plain string. Use fromjson? (with ? to swallow errors) or check before parsing.
  • Standalone vs form fields: input_value, option, options, checked are only populated for components not inside a form container. Inside a form, all values appear in form_value.
  • WebSocket delivery: no separate callback URL needed; uses the existing WS connection.