feat: StreamingCard debounce, SessionPoisoned recovery, thinkingDelay removal#72
Conversation
… removal - StreamingCard: 200ms debounce on conclusion render, reducing CardKit API calls from 30-100/sec to ~5/sec during streaming - cardkit: remove updateElement retry loop (debounce is root cause fix) - chat.ts: SessionPoisoned L1 recovery via v2 client deleteMessage before falling back to session invalidation - chat.ts: remove thinkingDelay, send "正在思考..." placeholder immediately when CardKit unavailable (structurally eliminates race condition) - errors.ts: reclassify tool-choice-type from SessionPoisoned to ModelUnavailable - gateway.ts: catch-all error notifications for chatType resolution and async processing failures - types.ts: remove thinkingDelay config field, nudge default to English - send-card.ts: enhance tool description with constraints - index.ts: migrate prompt path from skills/ to prompts/
- Delete skills/ directory (migrated to prompts/) - Update CLAUDE.md, BEHAVIOR.md, README.md, AGENTS.md to remove thinkingDelay references - Update prompts/feishu-card-interaction/prompt.md with latest changes
|
/gemini review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Qodana for JVMIt seems all right 👌 No new problems were found according to the checks applied View the detailed Qodana reportTo be able to view the detailed Qodana report, you can either:
To get - name: 'Qodana Scan'
uses: JetBrains/[email protected]
with:
upload-result: trueContact Qodana teamContact us at [email protected]
|
There was a problem hiding this comment.
Code Review
This pull request updates the project to version 1.9.0, introducing a dedicated BEHAVIOR.md for product logic and migrating the skills directory to prompts. Key functional changes include removing the thinkingDelay configuration, implementing a 200ms debounce for streaming card updates to reduce API load, and adding a session recovery mechanism that deletes poisoned messages. Review feedback suggests retaining the retry logic for card updates to ensure robustness against network jitters and optimizing the poisoned message search by scanning session history in reverse order.
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (22)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 44 minutes and 15 seconds.Comment |
There was a problem hiding this comment.
Code Review
This pull request updates the project to version 1.9.0, introducing a new BEHAVIOR.md file to define product behavior and responsibilities. Key changes include renaming the skills directory to prompts, refining prompt layering principles, and removing the thinkingDelay configuration. On the technical side, it implements a debouncing mechanism for card rendering to optimize API usage and introduces a session poisoning recovery flow that attempts to delete specific problematic messages. Feedback focuses on improving type safety for message parts and refactoring duplicated logic for placeholder messages.
Scan messages from newest to oldest — the poisoned message is typically the most recent one, so reverse iteration finds it faster in long sessions. Addresses Gemini review feedback.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request updates the plugin to version 1.9.0, primarily restructuring prompt management by moving from skills/ to prompts/ and introducing BEHAVIOR.md to document product behavior. Key functional changes include the removal of the thinkingDelay setting, the implementation of debounce logic for card rendering to reduce API overhead, and a new recovery mechanism for poisoned sessions that attempts to delete specific messages. Reviewers noted that removing retry logic for card updates might decrease robustness against network issues and that the lack of fallback logic when loading prompts could lead to startup crashes if files are missing.
|
Caution Review failedAn error occurred during the review process. Please try again later. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Prevents cryptic ENOENT crash if prompts/ is missing due to broken installation. Returns minimal fallback prompt instead. Addresses Gemini review feedback.
|
/gemini review |
Per user decision: if prompt.md is missing, the installation is broken and should crash at startup, not silently degrade.
There was a problem hiding this comment.
Code Review
This pull request updates the version to 1.9.0 and introduces a new product behavior specification in BEHAVIOR.md. Key changes include renaming the skills/ directory to prompts/, removing the thinkingDelay configuration, and implementing a session recovery mechanism to delete 'poisoned' messages. StreamingCard now uses a 200ms debounce for conclusion rendering. Feedback suggests restoring retry logic for card updates, refactoring duplicated recovery logic, and handling AbortError during recovery to prevent unnecessary session resets.
| const res = await this.larkClient.cardkit.v1.cardElement.content({ | ||
| data: { | ||
| content, | ||
| sequence, | ||
| }, | ||
| path: { | ||
| card_id: cardId, | ||
| element_id: elementId, | ||
| }, | ||
| }) | ||
|
|
||
| if (res?.code !== 0) { | ||
| lastErr = new Error(`CardKit updateElement 失败: ${res?.msg ?? "unknown"} (code: ${res?.code})`) | ||
| this.log?.("warn", `CardKit updateElement 尝试 ${attempt}/${MAX_ATTEMPTS} 业务失败`, { | ||
| cardId, | ||
| elementId, | ||
| code: res?.code, | ||
| msg: res?.msg, | ||
| }) | ||
| } else { | ||
| return | ||
| } | ||
| } catch (err) { | ||
| lastErr = err | ||
| this.log?.("warn", `CardKit updateElement 尝试 ${attempt}/${MAX_ATTEMPTS} transport 异常`, { | ||
| cardId, | ||
| elementId, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }) | ||
| } | ||
| if (attempt < MAX_ATTEMPTS) { | ||
| await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS)) | ||
| } | ||
| if (res?.code !== 0) { | ||
| this.log?.("error", "CardKit updateElement 失败", { | ||
| cardId, | ||
| elementId, | ||
| code: res?.code, | ||
| msg: res?.msg, | ||
| }) | ||
| throw new Error(`CardKit updateElement 失败: ${res?.msg ?? "unknown"} (code: ${res?.code})`) | ||
| } |
| await client.session.promptAsync({ | ||
| path: { id: session.id }, | ||
| query, | ||
| body: { ...baseBody, messageID: recoveryRequestMessageId }, | ||
| }) | ||
|
|
||
| const finalText = await poll(client, session.id, { | ||
| timeout, pollInterval, stablePolls, query, | ||
| signal: mergeAbortSignals([signal, getRunAbortSignal(run.runId)]), | ||
| }) | ||
|
|
||
| const actualModel = await fetchActualModel(client, session.id, requestMessageIds, log, query) | ||
| const terminalState = timedOut ? "timed_out" : "completed" | ||
| completeReplyRun(run.runId, terminalState) | ||
| if (streamingCard) { | ||
| await streamingCard.setRunState(terminalState, terminalState) | ||
| } | ||
| await finalizeReply({ | ||
| streamingCard, feishuClient, chatId, placeholderId, log, | ||
| actualModel, title: replyTitle, state: terminalState, | ||
| conclusion: finalText || latestSnapshot.text || (timedOut ? "⚠️ 响应超时" : undefined), | ||
| detailsPhases: detailPhases.values(), | ||
| }) |
| } catch (recoveryErr) { | ||
| log("error", "删除中毒消息后重发 prompt 失败,降级到新建 session", { | ||
| sessionKey, rule: e.rule, | ||
| error: recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr), | ||
| }) | ||
| } |
Summary
deleteMessageto surgically remove poisoned message before falling back to session invalidationtool-choice-typemoved from SessionPoisoned to ModelUnavailable (provider config mismatch, not history corruption)skills/toprompts/, update docs to remove thinkingDelay referencesTest plan
UnsupportedFunctionalityError)npm run typecheckpassesnpm run buildpasses🤖 Generated with Claude Code