3 Commits

Author SHA1 Message Date
程序员阿江(Relakkes)
bfafd26fb6 fix: batch resolve 5 issues from Feishu bug tracker
- fix(desktop): Settings Agents/Skills page layout overflow at narrow widths
  Shift header grid breakpoint md→xl, summary cards sm→3col, add min-w-0/truncate

- fix(desktop): TabBar tab style — remove border-r separators, use bg-only active state
  Drop ::after indicator and inter-tab borders; active tab distinguished by surface bg

- feat(desktop): IM Adapters page — horizontal tabs with Feishu first, Telegram second
  Replace vertical stack with tablist; add ImTabButton with aria-selected + focus-visible

- fix(server): bundled IM session cwd resolves to / instead of selected project
  Root cause: preload.ts chdir(CALLER_DIR) inherits '/' from Tauri-spawned sidecar.
  Fix: pin CALLER_DIR and PWD to session workDir in conversationService spawn env.
  Add diagnostic logs at sessionService, ws/handler, and conversationService.

- fix(feishu): StreamingCard shows duplicate "thinking" indicators
  Merge static loading element into streaming_content; renderedText() controls state.

- test(adapters): add 5 ImageBlockWatcher edge-case unit tests (reset, relative path,
  multi-image chunk, malformed data URI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:36:14 +08:00
程序员阿江(Relakkes)
e338b2f7a3 fix(feishu): stream reasoning + tool steps live, drop them on finalize
Feishu cards used to sit on "正在思考中..." for the entire turn and
then dump everything at message_complete, while Telegram showed the
reasoning and tool calls progressively. Two underlying causes:

1. handleServerMessage ignored `thinking`, `content_start{tool_use}`
   and `tool_use_complete` entirely, so nothing was fed into the card
   between turns of text output.
2. StreamingCard.performFlush silently set `cardKitStreamActive=false`
   on any non-rate-limit error, after which all middle frames were
   skipped and only finalize touched the card — exactly the
   "long wait → all at once" symptom.

StreamingCard now tracks accumulatedReasoningText and a toolSteps
list, with appendReasoning / startTool / completeTool methods that
trigger throttled flushes like appendText. renderedText composes
tools → reasoning → answer in plain markdown (no blockquotes / lists
that historically tripped Feishu's parser). consecutiveStreamFailures
threshold (3) keeps a single transient error from killing the stream.

terminalText() is used by finalize so the final card holds only the
answer text, matching the Desktop UI's clean post-completion view.

handleServerMessage wires the new events to the existing per-chat
StreamingCard via the streamingCards map (read-only — never
auto-creates cards from thinking/tool events to avoid empty cards
for /clear-style commands).

Test coverage: realistic event-stream regression (thinking → tool_use
→ text → finalize), first-frame-failure recovery, finalize drops
intermediate state, plus per-method tests for the new APIs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:37:39 +08:00
程序员阿江(Relakkes)
ae586b0d9b feat(feishu): rewrite streaming to CardKit Schema 2.0 with proper markdown
Replace legacy im.message.patch streaming path with a Feishu CardKit
Schema 2.0 pipeline mirroring openclaw-lark's implementation. Desktop
now renders real formatted markdown (headings, tables, code blocks)
instead of literal text, and streaming output updates per chat without
cross-talk.

New modules:
- card-errors.ts: structured Lark SDK error parsing for code 230020
  (rate limit -> skip frame) and 230099/11310 (table limit -> disable
  CardKit streaming and fall back to settings+update at finalize)
- flush-controller.ts: mutex + needsReflush + long-gap batching
  throttler for per-chat flush coordination
- cardkit.ts: thin wrapper over client.cardkit.v1.* (card.create +
  im.message.{create,reply} + cardElement.content + card.settings +
  card.update), with monotonic sequence and CardKitApiError
- streaming-card.ts: per-chat StreamingCard state machine covering
  idle -> creating -> streaming -> finalizing -> completed/aborted,
  including patch fallback when CardKit create/send fails

index.ts rewrite:
- replaces MessageBuffer/chatStates/buffers maps with a single
  streamingCards Map<chatId, StreamingCard>
- content_start{text}/content_delta create or reuse the card; finalize
  runs on message_complete; abort renders a red error card
- /clear and other silent commands stay out of the streaming-card path
  so they don't leave empty cards
- all command handlers + normal chat are wrapped in the per-chat
  enqueue() serial queue, preventing reply reordering when commands
  are fired rapidly
- createSessionForChat() now first resets any stale WS session before
  calling connectSession, fixing /projects pick_project leaving the
  old session bound to the previous workDir
- normal messages pre-create the card before sendUserMessage so users
  see a "☁️ 正在思考中..." loading indicator immediately
- content_start{tool_use} no longer finalizes the current card, letting
  one user turn's full text stay in a single card

markdown-style.ts:
- default cardVersion changed from 1 to 2 (Schema 2.0)
- headings H2~H6 demoted to H5, H1 to H4, only when source has H1~H3
- Schema 2.0 <br> padding around headings, tables, code blocks
- stripInvalidImageKeys() drops non-img_* image references
- FEISHU_CARD_TABLE_LIMIT=3 + sanitizeTextForCard() wraps 4th+ table
  in a fenced code block to avoid 230099/11310

Tests: 182 pass, 0 fail, 383 expect() calls across 6 files. tsc clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:04:22 +08:00