feishu: card rollover never fires — long tasks silently stop updating

#814 · open · 3 comments

View on GitHub ↗

cuipengcx90

## Symptom Long Feishu tasks stop updating their card around step ~50 while the agent keeps running. The user has to send "继续" to get a working card back, and any long task reproduces it (198 occurrences of the card failure in a single session log). ## Root cause (two defects) 1. `_rollover_locked()` bumps `page_no` and clears `msg_id` but **not** `self.steps`. `_build_locked()` iterates the full `self.steps`, so the "new" card is exactly as large as the overflowing one and fails the same way — rollover is a no-op. 2. `_push_sync()` returns `(False, False)` when the **create** path fails, while `_worker_loop` rolls over only on `limit=True`. Once `msg_id` is `None`, every later push takes the create path, returns `limit=False`, and rollover never runs again — the card becomes permanently unsendable. ## Reproduction Any session where accumulated steps exceed the card size limit (roughly 50 steps at `_DETAIL_LIMIT = 4000`). The log then repeats: ``` 发送失败: 230099, Failed to create card content, ext=ErrCode: 200800; ErrMsg: create universal card fail; ``` while `上一张工作卡片达到飞书限制` (the rollover note) never appears — direct proof that rollover never fired. ## Suggested fix - Clear `self.steps` in `_rollover_locked()` and carry `turn_base` so step numbering stays contiguous. - Roll over proactively when `steps` reaches a threshold, instead of waiting for the API to reject the card. - Return `limit=True` from the create path on failure so the caller can roll over and retry.

Comments

cuipengcx90

Update: fixed locally in my fork, using button pagination instead of multi-card rollover. **Approach** - The card now renders `◀ prev / N/M / next ▶` at the bottom; each button's `value` carries `{ga_card, page}`. - Registered via `register_p2_card_action_trigger`; the callback resolves the card instance from a uid registry and re-patches the **same** message, so the whole step history stays browsable in one card. - Pagination uses dual criteria: max 50 steps **and** 8000 chars per page. Measured from real logs: the original overflow fired around step 58, with single-step detail averaging 272 chars (max 9003), so a step-count-only limit is not enough. **Both underlying defects are also fixed** - `_rollover_locked()` now clears `self.steps` and advances `turn_base`, so a rollover page is actually smaller and step numbering stays contiguous. - `_push_sync()` returns `limit=True` on the create path when it fails, so the caller can roll over and retry instead of leaving `msg_id = None` forever. Result: no more silent card failures, and long tasks no longer need a manual "continue". Happy to open a PR if this fits your design — the alternatives (multi-message rollover, or a rolling window that drops old steps) both hurt reviewability.

louisss1016

I'd like to take this one, scoped to the two defects and the minimal fix suggested in the issue body: - `_rollover_locked()` clears `self.steps` and carries `turn_base` so step numbering stays contiguous and the new card is actually smaller; - `_push_sync()` returns `limit=True` when the create path fails, so `_worker_loop` rolls over and retries instead of the card becoming permanently unsendable. @cuipengcx90 you mentioned a local fork fix using button pagination — I'm deliberately **not** attempting the pagination rework here, keeping this PR to the rollover defects so the two approaches can coexist (yours is the nicer long-term UX; this unblocks the silent-stop failure mode with a small diff). If you'd rather own the whole thing, say the word and I'll drop this.

louisss1016

PR up: https://github.com/lsdefine/GenericAgent/pull/820 Important finding while implementing: the code the issue references (`_rollover_locked()` / `_push_sync()` / `_worker_loop`, `_DETAIL_LIMIT = 4000`) **does not exist on current main**. A rollover implementation was merged in #209 (`bf002e5b`) but was dropped in the later Feishu interface rework (`df525560`) — main today has no rollover at all, and the original freeze symptom reproduces structurally. So this PR re-lands the rollover behavior adapted to the current `_TaskCard`: - `_push()` returns `(ok, limit)`; the create path reports `limit=True` on failure (your defect 2 — with `msg_id is None` every push took the create path and never rolled over) - `_rollover()` clears `steps` so the continuation card is actually smaller (your defect 1) - `turn_base` + monotonic `turn_no` keep `Turn N` contiguous across cards - proactive flip at 50 steps — just under your measured ~58-step overflow point @cuipengcx90 as agreed, scoped to the rollover defects only; your button-pagination fix is complementary and untouched here. 11 new tests (ast/exec pattern); full suite 298 passed, same 3 pre-existing Windows-env failures as before my change.