When OS revokes master stdout, bare print() raises BrokenPipeError and aborts WS handle before tab registration, causing stale web_scan data and extension reconnect loops. Mirror ga.py safe_print across TMWebDriver log sites.
Closes#672
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>
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.
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.
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.
Run the bundle's own bridge + frontend but drive an external GenericAgent
核 injected via GA_ROOT, instead of swapping to the external bridge or
copying files into 本体. New UI + external核/data, no mutation, logs
naturally unified with the核's other frontends.
- desktop_bridge.py: find_default_ga_root() honors --ga-root / GA_ROOT
(validated), else derives as before; conductor now spawned from the
BUNDLE (APP_DIR/conductor.py) with GA_ROOT in env; scheduler stays核-side
- conductor.py: ROOT prefers GA_ROOT env, else own-path derivation
- lib.rs: override no longer changes project_dir (always bundle bridge);
sanitize_bundle_env injects GA_ROOT; takeover compares effective ga_root;
set_ga_source drops file-copy sync and instead runs a contract probe
before committing; removed copy_dir_all/sync_desktop_files
- ga_contract_probe.py (new): subprocess, introspect-only (no GenericAgent
instantiation) symbol/signature check of the核's bridge/conductor/
cost_tracker contract; doubles as a dependency check
- GA桌面壳-参考方案.md: plan + feasibility review + probe checklist
On connect-to-source, push the shell's own (new) desktop files into the
本体 (backing up its originals) before restarting the bridge, so it
serves the new frontend + bridge while keeping the 本体 agent/data.
When connecting to a separate GA source (design A), the bridge and the
served frontend come from that 本体 — so an older 本体 showed an old
desktop UI. set_ga_source now pushes this shell's own (new) desktop
files into the 本体 before restarting the bridge, so it serves the new
UI + bridge while keeping the 本体 agent/data.
- copy_dir_all + sync_desktop_files: back up 本体 originals into
temp/desktop_sync_backup_<ts>/, then copy the shell version over
- synced set: frontends/desktop/static/, desktop_bridge.py,
plan_state.py, cost_tracker.py (NOT src-tauri / build artifacts)
- skipped when the source is the shell's own code location (dev tree)
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>
GenericAgent.exe linked the Universal CRT dynamically, so on stripped/older
Windows images the shell itself could fail to load before the (already
UCRT-bundled) embedded python ever ran. Enable +crt-static via a
target-scoped .cargo/config.toml — macOS/Linux builds never read the
x86_64-pc-windows-msvc section. Verified locally: built exe imports no
api-ms-win-crt-*/vcruntime140/ucrtbase (kernel32 control present).
run_offline_prepare discarded the script's stderr (Stdio::null) and only
parsed stdout for progress markers, so a failed install surfaced nothing
but 'prepare exited with status Some(N)'. Pipe stderr (drained on a helper
thread to avoid pipe-buffer deadlock), keep the last 40 lines of each
stream, and append them to the error shown in the setup window and written
to prepare_error.log — the actual pip failure is now visible.
- restore corrupted zh i18n (loading/setup blocks)
- surface first-run prepare errors in the setup window + prepare_error.log
- bundle app-local UCRT in the Windows portable package
- restore memory/import rpc case lost in merge 8bb91aa
The 8bb91aa merge (RoundSquisheen UI rewrite x main) resolved app.js by
dropping exactly one rpc dispatcher case — 'memory/import' — while keeping
the importMemory caller and the settings button. Result: clicking import
always threw 'Unknown RPC method: memory/import'. Re-add the case; the
bridge endpoint (POST /memory/import) was never lost.
When all LLM nodes timeout/fail, the conductor's except block only
printed to stdout — users saw their message disappear with no feedback.
Now broadcasts a role="error" chat item via the existing WebSocket
pipeline so the frontend renders a red error bubble inline.
- i18n.js: the zh block (loading.*/setup.*) shipped with every Chinese
character mangled into literal '?' (a non-UTF-8 save when the file was
first added in 1bcb41d). Restore all 22 keys from the intact en block.
- Surface first-run prepare failures instead of silently opening the setup
window: store the error, expose it via get_prepare_error, show it in
fallback.html, and write runtime/prepare_error.log.
- Windows CI: deploy app-local UCRT (api-ms-win-crt-*.dll + ucrtbase.dll
from the SDK redist) next to the embedded python so python.exe loads on
images lacking an in-box Universal CRT. Pure SDK source, hard-fail if absent.
* 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 据此追加「⚠️ 代码回退未完成…请检查」告警。
Fix desktop session-store scaling: store each session as its own
temp/desktop_sessions/<id>.json instead of one monolithic
desktop_sessions.json rewritten on every message. Write cost is now
O(one session) regardless of session count. Auto-migrates the legacy
file on first load; import reads both formats.
Every message called _persist(), which rewrote the ENTIRE
desktop_sessions.json (all sessions, full messages + llm_history) under
the lock. Write cost was O(all sessions), so performance degraded badly
as sessions piled up.
Now each session is its own temp/desktop_sessions/<id>.json:
- _persist_session(s): atomic per-session write, O(one session)
- _delete_session_file(sid): remove on delete
- add_message / create / plan-bind / set_model / patch now write only
the touched session; write cost no longer scales with session count
- _load_sessions reads the dir; one-time migration from the legacy
monolithic file, which is then renamed to .json.migrated
- import_sessions reads both source formats (per-file dir + legacy),
persists only newly added sessions
Add "connect to a separate GenericAgent source" (thin-shell / design A):
a Settings option that points the packaged desktop app at a standalone
本体 checkout, so its bridge, agent code, memory and sessions all come
from that source. Invalid/missing override safely falls back to the
bundled runtime.
Adds a "GA source directory" setting so the packaged desktop app can run
against a standalone GenericAgent checkout instead of its own bundled
runtime/app — memory, sessions, model_responses and the agent code all
come from that 本体, live-shared with other frontends.
- lib.rs: ga_source_override settings key honored in get_or_discover_config
BEFORE the bundle default; validated (agentmain.py + desktop_bridge.py),
invalid/missing override falls back to the bundle (本体-moved safety net)
- set_ga_source / clear_ga_source / get_ga_source commands: write setting,
stop current bridge, respawn from resolved config, reload webview
- startup uses the override-aware config for takeover + spawn
- ACL grants for the three new commands
- Settings > Features UI: pick source dir, show current, clear; zh/en i18n