Skip to content

fix(fsapp): avoid premature task finalization#686

Open
Tomodad wants to merge 795 commits into
lsdefine:mainfrom
Tomodad:fix/fsapp-turn-hook-false-terminal
Open

fix(fsapp): avoid premature task finalization#686
Tomodad wants to merge 795 commits into
lsdefine:mainfrom
Tomodad:fix/fsapp-turn-hook-false-terminal

Conversation

@Tomodad

@Tomodad Tomodad commented Jul 14, 2026

Copy link
Copy Markdown

Summary

  • keep the Feishu turn hook summary-only
  • prevent turn-level exit_reason metadata from finalizing queued work early
  • preserve display_queue's done item as the normal task-finalization path

Fixes #685.

Root cause

_make_task_hook received _finish as on_final and invoked it whenever a hook context contained exit_reason. That metadata can describe an intermediate agent turn rather than completion of the queued Feishu task, so _finish could set result["sent"] before the display queue published its real done item.

Change

Remove the final callback from _make_task_hook and let the hook handle only summary -> card.step. The existing display-queue done -> _finish path and timeout/stop/error failure paths are unchanged.

Verification

  • isolated AST harness: 14/14 passed
  • parent revision reproduces premature finalization
  • candidate blocks exit_reason finalization while preserving summary updates
  • adversarial cases cover missing response, empty summary, combined summary + exit_reason, repeated calls, inactive task IDs, and formatting inputs
  • python -m py_compile frontends/fsapp.py
  • git diff --check
  • one file changed: 4 insertions, 8 deletions
  • no dependencies added

AspasZhang and others added 30 commits May 18, 2026 17:14
Eight independent fixes accumulated since the last batch, all landing in
tuiapp_v2.py:

1. Code-block selection offset: _align_md_renders's is_centered heuristic
   tripped on any code line indented >=4 spaces (Python's default), stripping
   the indent from the source string so the visible selection and the copied
   text disagreed. Add wide_lead >= 2*nt0_lead guard — only Rich's H1 centering
   (wide_lead ~ width/2) crosses it, code lines never do.

2. Blockquote-continuation selection offset: Rich re-emits `▌ ` on every
   wrapped visual line of a quote, but the wide-render carries the marker
   only once. Strip the marker as a decoration prefix (counts as indent, not
   content) and switch the wrap-accumulator to account for the per-break
   eaten space — both pre-existing code-block wraps and blockquotes now map
   correctly.

3. Streaming ANSI garbage: while a turn is streaming, the tail text segment
   was rendered with `Text(content, style=C_FG)` instead of `Text.from_ansi`.
   SGR codes from the model/tool output would render as literal `[31m`
   glyphs and only resolve after `m.done` flipped or a resize forced a
   remount via the Markdown path. Switch the two streaming fast paths to
   Text.from_ansi — O(n) scan, no Markdown parse, colors honored live.

4. continue picker LIMIT removed: tuiapp's own `LIMIT = 20` truncated the
   /continue picker even though list_sessions() always builds the full list.
   Show everything; header now reads "选择要恢复的会话 (N 条)".

5. Long-paste rescue + user echo elision: Windows Terminal yanks window
   focus when its >=5KiB paste-warning dialog pops, leaving focused=None
   after the user confirms; Textual then routes the Paste event to the App
   bubble instead of InputArea, and the paste appears to do nothing. Add a
   GenericAgentTUI._on_paste forwarder that refocuses #input and replays the
   event into InputArea._on_paste. Separately, on submit, elide user-side
   display of very long pastes (head 10 / tail 5 / `⋯ 省略 N 行 ⋯`); the
   agent still receives the full content via expand_placeholders.

6. mykey.py hot reload: load_llm_sessions() already had mtime-gated reload
   plumbing, but it only ran on Agent.__init__ / next_llm / list_llms — so
   editing mykey.py mid-session required restart. Two triggers added:
   on_input_area_submitted now calls load_llm_sessions() before sending
   (cheap mtime check; rebuilds on change), and a new /reload-keys command
   forces a sweep across every session (resetting llmcore._mykey_mtime
   per-iteration so the cache doesn't short-circuit later sessions).

7. Resize / sidebar toggle no longer jumps to bottom: _remount_current_session
   used to unconditionally scroll_end after rebuilding. Capture scroll_y +
   "was at bottom?" first; stick to bottom only if the user was already there,
   otherwise restore the prior scroll_y so resizes don't yank the user away
   from what they were reading.

8. Markdown table cells wrap instead of ellipsizing: Rich's Markdown
   TableElement passes no `overflow` to Table.add_column, so the default
   "ellipsis" hides long cell content with `…` on narrow widths. Patch
   TableElement.__rich_console__ on import to pass overflow="fold".
…auth-396-416

fix: 微信 /stop 中止检测 + AuthExpired 处理 + 配置向导改进
…nline-selection

[codex] feat(tg): add inline selection menus
Replace _history_lines (pre-rendered) with _blocks (source + kind) so
_repaint_screen can re-render every chip, banner, user panel at the
current width. \x1b[3J wipes scrollback on resize/Ctrl+L so the entire
conversation reflows, not just the visible viewport.

SIGWINCH delivery via self-pipe + select() — PEP 475 auto-retry on
os.read makes signal-driven InterruptedError unreliable under iTerm
split panes, so a dedicated pipe wakes the loop deterministically.

Streaming tracks a single _streaming_block whose source grows monotonically
through safe-position commits; mid-stream resize re-renders both the
committed source and the volatile live_tail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
macOS Terminal.app quantises truecolor to its nearest 256 slot (so
#5e6ad2 lavender lands on slot 62 #5f5fd7, visibly more blue) and
renders \x1b[2m as a heavy multiply instead of a gentle blend, producing
the "blue border + heavy shadow" against iTerm's identical code.

Branch on TERM_PROGRAM: iTerm keeps its truecolor accent and \x1b[2m
dim; Apple_Terminal switches to pinned 256-slot accent (105) and an
explicit mid-gray (244) for dim. Box border (146) was already 256-slot
so it carries unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(tui): scrollback-first TUI rewrite (tui_sb)
* 加入高危命令黑名单 Add  high-risk command blacklist

* GenericAgent Agent System Design Overview

* Subagent 集群监控,Subagents Monitor

* remove desing overview html

* skill_learn_from_cases — 案例驱动技能学习 CLI 工具

* skill_learn_from_cases — 案例驱动技能学习 CLI 工具 v001

* skill_learn_from_cases — 案例驱动技能学习 CLI 工具 v002

* skill_learn_from_cases — 案例驱动技能学习 CLI 工具 v003

* skill_learn_from_cases — 案例驱动技能学习 CLI 工具 v005

* skill_learn_from_cases 案例驱动技能学习 CLI 工具 v006

* skill_learn_from_cases 案例驱动技能学习 CLI 工具 v007

* skill\_learn\_from\_cases   案例驱动技能学习 CLI 工具 v008

* skill_learn_from_cases   案例驱动技能学习 CLI 工具 v009

* skill_learn_from_cases v010

* skill_learn_from_cases v 011

* earn_skill_from_cases v000

* fix:[开始空闲自主行动]按钮点击无反应

* 修复点击[开始空闲自主行动]按钮,无反应问题

* clean fork 0515

* clean051502

* 自主行动相关按钮修复

* learn_skill_from_cases v000

* cleaned again

---------

Co-authored-by: benechen <13817895035@126.com>
原链接 installation.zh.md 路径不正确,已更正为 docs/installation.md
docs: 修复安装文档中的链接路径
feat: add agent lifecycle hook trigger mechanism
- Add sys.path.append for llmcore's own directory as fallback
- Remove dead variable _imp_err
- Distinguish missing mykey vs import failure with clear error messages
lsdefine and others added 29 commits June 27, 2026 14:57
…le ChunkedEncodingError

When the LLM stream encounters a transient network error (ConnectionError,
Timeout, SSLError, ProxyError), the retry path yielded the error string
back to the caller on every attempt. This caused the UI to display a wall
of '!!!Error: ProxyError!!!Error: ConnectionError!!!Error: SSLError'
strings during a network blip, even when the retry ultimately succeeded.

Match the existing HTTP retry path (which sleeps without yielding) by
removing the intermediate yield so the caller only sees the final error
if retries are exhausted. Retry attempts remain visible via the existing
'[LLM Retry] ...' stdout log.

Also extend the exception tuple to include ChunkedEncodingError. Without
it, a truncated chunked response falls through to the bare 'except
Exception' branch and aborts the stream with no retry, even though the
transport-level error is transient and recoverable.

Closes lsdefine#571
…d + /worldline) (lsdefine#667)

* feat(worldline): integrate checkpoint-tree rewind into tui_v2

- tuiapp_v2.py: /rewind durable inline picker + /worldline three-pane
  checkpoint tree (tree UI inlined; colors follow the v2 theme) with a
  per-node diff viewport; conv-only /rewind restores via the persistent store.
- worldline.py (new): UI-agnostic backend — RewindStore (persistent
  checkpoint tree + content-addressed blobs), reconcile, restore_plan,
  node_diff, native-log projection.
- continue_cmd.py: gated worldline support — list_sessions(rewind_root=),
  continue_inplace/copy(allow_empty=); default-off, other UIs byte-identical.
- tui_v3.py: status line shows concrete model id (unrelated minor tweak).

* feat(worldline): /rewind reuses the restore-mode picker (conv/code/both)

After picking a turn, /rewind now opens the same RestoreModeScreen as
/worldline to choose conversation / code / both, then restores via the
persistent store — making /rewind a true lite view of /worldline (linear
list, no tree/diff) over the identical restore backend.

* feat(worldline): sync working memory (history_info/key_info) on rewind

树节点除 conv 外,再存 history_info 增量(与 conv 同构)+ key_info 快照。
restore_plan 重建二者,_rw_sync_working_memory 在 rewind 时就地写回
handler.history_info / working['key_info'],使注入的 WORKING MEMORY 与
回退后的对话一致,消除旧 rewind「历史回退、纪要串味」缺陷。

向后兼容:老树无 WM 记录 → path_has_wm=False → 恢复时跳过 WM 同步,
不动现场纪要。单测 21/21 + 真机(363580)端到端验证(call_013→014
v2 痕迹 ×3→×0)通过。

* fix(worldline): reconcile against the log, only on /continue; rebuild working memory on absorb

- reconcile now takes the log-parsed full history, never the compressed
  backend.history. Compressed memory is a lossy derivative; reconciling the
  full tree against it made the tree adopt a front-trimmed suffix as a new
  trunk (false divergence). Contract pinned in the docstring.
- drop in-session reconcile from /rewind and /worldline: the tree is kept
  current every turn by _rw_commit, and live history there is compressed.
  reconcile now runs only on /continue, where backend.history is the freshly
  loaded full log.
- reconcile rebuilds history_info/key_info from the absorbed log so resumed
  and external-UI sessions also carry working memory on rewind.

* fix(worldline): feed commit the full log; derive deltas from the tree's real length

_rw_commit fed the compressed backend.history, so once a long session front-trims,
commit's absolute hist_len delta drops turns. Now:
- _rw_commit feeds the log-parsed full history (fallback to backend.history).
- commit derives the delta start from len(rebuild_history(parent)) /
  len(rebuild_hist_info(parent)) instead of the stored hist_len, so a poisoned
  count can't drop turns and a previously-lost turn self-heals on the next commit
  (the log is append-only, always a superset of the tree).

* chore(worldline): mark in-memory _do_rewind as the non-persistent degraded fallback

It chops the compressed live history (can't reach front-trimmed turns, keeps
truncated remnants, restores neither files nor working memory). Only used when
there is no checkpoint tree; the durable tree-rebuild path is always preferred.

* fix(worldline): derive tree working memory from the log, not live handler state

/continue resets agent.history (handler.history_info) to empty. _rw_commit
previously fed that live list as hist_info, so after a continue the per-turn WM
delta misaligned with the tree's log-derived WM and new turns' summaries were
dropped. commit now derives hist_info/key_info from the history it stores (the
full log) when not given, matching reconcile; _rw_commit stops passing the live
list. Tree WM is uniformly log-derived and survives the continue reset.

* feat(worldline): restore working memory on /continue (TUI-only, derived from the log)

continue_cmd clears handler.history_info on restore; the TUI's continue path now
derives history_info from the loaded log (same _derive_hist_info as the tree) and
writes it back to agent.history, so a continued session keeps its working memory,
consistent with the tree and live sessions. Scoped to tuiapp_v2 only — shared
continue_cmd is untouched, so telegram/discord/streamlit frontends are unaffected.

* refactor(continue): restore working memory on /continue via opt-in restore_wm

Move the WM restore from the TUI into continue_cmd as an opt-in. _load_history_into
derives history_info from the loaded log and writes it back to agent.history when
restore_wm=True; continue_inplace/continue_copy thread the flag and the worldline
TUI passes True. Default False keeps every other frontend (telegram/discord/
streamlit/shared chat) byte-for-byte unchanged. _derive_hist_info is self-contained
(no worldline dependency), same extraction口径 as the tree.

* perf(worldline): only re-parse the log in _rw_commit when front-trim is detected

_rw_commit re-parsed the whole log every segment to feed commit a full history;
for very long sessions (multi-MB logs) that adds a multi-second lag per turn. Now
it uses the fast in-memory backend.history by default and only falls back to
parsing the log when front-trim is detected — RewindStore.first_question() vs the
live history's first question (front-trim pops the first question, so they diverge).
Common case (no trim) pays nothing; correctness under trim is preserved.

* feat(worldline): conv/code bridge nodes + align _rw_commit to native log

- worldline: commit_bridge() + restore_plan() three-way (both/conv/code) with a new rw_tag field. conv bridges fork at parent(target); code bridges mount at parent(old_head) as a sibling of the current conversation node (same conversation, older code).

- tuiapp_v2: _rw_commit now reads the native log (parse_native_log) instead of the compressible live backend.history; stat-cached _rw_log_history; title from the real log delta; skip commit when there is no new complete pair. Cursor points at the bridge for conv/code; rw_tag rendered after the title.

- continue_cmd: add parse_native_log() public wrapper (complete Prompt->Response pairs only; [] for native-but-empty, None for non-native).

* fix(worldline): realpath 去重 key + 节点行数增删 + 剥离 project-mode

- key()/cwd 改用 realpath 解析 junction/symlink,同一物理文件只产生一个 tracked
  key(修 workspace 下 @提及绝对路径与相对写入产生双 key、回退误覆写当前内容)
- 新增 node_line_delta:按 node_diff 计每节点相对父节点的 +新增/-删除 行数,
  供 /rewind、worldline 显示代码变动量
- 重建历史时剥离 project-mode 注入块与自动注入标记(worldline._msg_user_text +
  continue_cmd._derive_hist_info),标题/提问只留用户原话

* feat(worldline/rewind): 面板与 rewind UI 精简重设计

worldline 面板:
- 右栏改用原生 OptionList(整项高亮/滑窗跟踪/点击映射,取代手写 ClickableList)
- 左右栏比例 3:2;详情面板精简(删掉与状态条/diff 重复的操作提示、时间、回退说明)
- 状态条重写为完整动宾短语,Enter 随聚焦切文案;右栏聚焦高亮改用更亮的 alt_bg
  (对齐左树,修此前聚焦态反而更淡的问题)

/rewind:
- 三列:用户 prompt · 上次时间 · 代码 +新增/-删除(绿/红,无改动显示「(无改动)」);
  倒序、聚焦底部、超长 prompt 省略号截断
- 延迟折叠(defer_collapse):选中先弹模式窗,确认才回退并清卡片;Esc 取消保留列表、
  焦点回到列表,不再留误导性「✓ 已选中」

其他:
- Ctrl+D 关会话时释放日志锁(abort + release_current),之后可原地 /continue

* fix(worldline): 回填输入框的 prefill 剥离 project-mode 注入

restore_plan 的 prefill/_prompt_of 走未清洗的 user_msg_text(树里的 user 消息取自
含注入的 native 日志),把 --- [PROJECT MODE:...] --- 一起塞进输入框。统一过
_strip_project_mode(幂等)。回归:temp/test_rewind_prefill_no_projectmode.py 9/9。

* refactor(tui): 删除被取代的旧 rewind 面板与死代码

- 删旧 /rewind 整簇:RewindScreen 类 + _on_rewind_pick + _rw_* 9 个 helper
  (已被内联 choice 卡 + RestoreModeScreen / RewindTreeScreen 取代,无 live 入口)
- 删未用 import next_same_depth/CheckpointTree、只写不读的 _diff_for/assistant_len
- 净 -276 行;import tuiapp_v2 通过、worldline 回归全绿

* fix(worldline): apply_code 回退失败不再静默,回传错误由 UI 提示

apply_code 直接写用户工作区文件,中途失败(权限/磁盘)会留下半回退状态。原先
restore_plan 吞成 changed=[],UI 显示「无变更」,用户毫不知情——全 PR 里失败半径
最大却唯一被静默的点。改为 _apply_code_safe 回传 (changed, code_error),
_rw_restore_node 据此追加「⚠️ 代码回退未完成…请检查」告警。
Replace the existing GenericAgent experience group QR image with the latest version while keeping the same filename and README reference intact.

Co-authored-by: Shen Hao <shenhao-stu@users.noreply.github.com>
Co-authored-by: GenericAgent <bot@gaagent.ai>
…ompt

The /update prompt reconciles the working tree "upstream-first" file by
file. That silently drops fork-local additions to files BOTH sides edit —
most painfully .gitignore, whose local ignore rules vanish, so a fork's
private dirs resurface as untracked noise (and can get committed by
accident).

Treat files carrying a `Fork-only local overlay` marker (e.g. .gitignore)
as a MERGE: take upstream as the base, then re-append everything under the
marker verbatim, and verify the marker survived. Along the way, tighten
both the EN and ZH prompts (75 -> 58 lines) without dropping any operative
step.
…erlay-merge

fix(slash_cmds): merge additive-overlay files (e.g. .gitignore) on /update
…OP); ga.py: file_patch/file_write 保持原文件换行符, 修复LF文件被污染成CRLF; _arg 类型强转修复字符串false等
Add maxlen_multiplier derived from context_win (capped 3x); drives per-tool
truncation caps and history compression interval. WebScan uses half-increment
growth to avoid saturating its 35000 base. Docs updated for the new coupling.
Hard-delete path can preserve the first K history messages, insert a "..." gap, and strip dangling tool_use at the cut. Default 0 keeps previous behavior.
Ignore assets/glm52_instruction.txt and assets/gpt55_instruction.txt
so local customizations stay out of the repo.
…#681)

The agent's default cwd is the temp/ subdirectory, where git resolves
'-- <file>' pathspecs relative to cwd. Fed the root-relative paths that
'git diff --name-only' prints, 'git checkout upstream/main -- <file>'
fails with "pathspec did not match", breaking the working-tree
reconcile steps of /update. The prompt now tells the agent to run every
git command from the repo root (_ROOT), in both EN and ZH.

Co-authored-by: Shen Hao <shenhao-stu@users.noreply.github.com>
Keep task hooks summary-only so turn-end metadata cannot finalize queued work before display queue done.

Co-Authored-By: GenericAgent <bot@gaagent.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[fsapp] turn-end exit_reason can finalize a queued task before display queue done