The confirmation gate previously REQUIRED a hand-built interactive card
template (per app, manual button param binding in the card console) — and
without one the gate silently turned off. That manual step is too clunky to
be the default. Add a text-approval mode so the digital twin works with zero
card-platform setup: when --owner-user-id is set but no --approval-card-
template, the bot posts a plain confirmation and the owner replies 「同意」/
「拒绝」 in chat to decide.
- newTextApprovalOrchestrator: gate enabled on owner alone (no card sender)
- text mode is async, NOT blocking: handleReply posts the prompt and returns;
the owner's decision arrives as an ordinary inbound message captured by
handleOwnerDecision (blocking on Await would deadlock the per-conversation
worker that must also process that reply)
- gate.pendingForConv maps an owner reply (carries only the conversation) back
to its request; parseDecisionWord matches a WHOLE-message keyword so an
embedded 「同意」 never accidentally approves
- only the configured owner can decide; non-owner / non-keyword pass through
- devapp_connect: no card template now falls back to text mode instead of
disabling the gate
- existing card flow refactored into handleReplyCard, unchanged behavior
- tests: parseDecisionWord, pendingForConv, text-mode approve/reject,
non-owner rejected, non-decision pass-through