Rebuild the compiled renderer from abraxas914/GenericAgent source
commit 9fe5d69b85cfad8778ace84b6f69bc75f9e0d392, refreshing:
- New-chat default model tag: the selected-model badge in Settings
and the composer now read '新会话默认' / 'New chat default'.
- Model selectors display the configured profile name (备注名) so the
same model served by different providers is distinguishable.
- desktop_bridge: syncing mixin llm_nos references when a profile is
renamed, preventing orphan references in the mixin config.
- Release publisher sets GH_REPO so 'gh release create' resolves the
target repository without checking out source, satisfying the
publisher security contract while fixing the prerelease step.
Provenance and verifier updated to the new source commit and the
LF-normalized content manifest (98 generated assets).
Add a user-triggered desktop action to copy the current GA workspace to a chosen location and verify the bridge actually switches there before reporting success.
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)
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
Add import-memory feature to the desktop app: import memory/,
model_responses/ and desktop_sessions.json from an existing GenericAgent
install so past memory and sessions carry over to the standalone desktop.
loadSessions() only refreshes the data model; the sidebar DOM is drawn by
renderSessionList(). Imported sessions therefore only appeared after the
next render (e.g. sending a message). Call renderSessionList() right after.
Import now also merges the source's temp/desktop_sessions.json into the
current session list (dedup by id), updating both memory and the on-disk
file so past desktop chats reappear in the sidebar immediately.
- manager.import_sessions(): id-dedup merge, in-memory + persist
- import handler returns sessionsAdded/sessionsSkipped
- frontend shows session count in toast and reloads the sidebar
- button label now says "select GenericAgent root"
- pick_directory accepts a title; native dialog shows the hint
- browser-fallback prompt clarifies root dir vs memory folder
The import-memory button failed with "Command pick_directory not allowed
by ACL" because Tauri v2 requires custom commands to be granted in the
capability. Add allow-pick-directory permission and grant it.
Lets users import memory/ and temp/model_responses/ from an existing
GenericAgent install into the desktop app's ga_root, so past sessions
and memory are not lost when running the standalone desktop bundle.
- bridge: POST /memory/import copies memory (backup-then-overwrite) and
model_responses (fill-missing), returns counts + backup dir
- tauri: pick_directory command (rfd native folder picker)
- frontend: "Import memory" button in Settings > Features + zh/en i18n
Remove the implicit model-write that rode along with each prompt. The
frontend no longer sends llmNo on session/prompt, and the bridge no longer
reads/forwards it there — sending a message now uses only the session's
bound sess.llm_no (falling back to the global default). Model switching
goes through the single POST /session/{sid}/model entry point.
selectModel now rolls back the chip + shows a toast when the switch request
fails, so the frontend display never diverges from the backend.
Session.llm_no is now the authoritative per-session model selection
(persisted to desktop_sessions.json). Frontend state.llmNo becomes a
display cache; switching a session re-displays its bound model, and the
chip follows the backend's live model for native models too (not just
mixin failover).
- bridge: Session.llm_no field + persistence; submit_prompt/run_agent_turn
read sess.llm_no with global ui.llmNo as fallback; make_agent/restore
seed from it; new POST /session/{sid}/model; _live_model reports llmNo
- app.js: applyLiveModel supports native; setActiveSession re-displays
bound model; selectModel posts to session route + updates global default;
remove ga_llm_no localStorage
- conductor unchanged: keeps reading global default ui.llmNo
Selected item in the model dropdown now uses only a tinted background + bold
(both light and dark); the inset left bar is removed per design preference.
- Send button: light mode now black (was green), dark mode bright blue
(#2559d8 / hover #3a6ef0).
- Model dropdown selected item is now obvious: light mode gets an accent
left bar + tinted bg + bold; dark mode gets a tinted bg + soft-white left
bar + bold.
- Main window now opens maximized (maximized: true); 1280x800 stays as the
restore size. Previously it always opened windowed.
- Uninstaller now also removes %LOCALAPPDATA%\com.genericagent.app (WebView2
EBWebView cache + localStorage), which lives outside the bundle. Only the
Tauri desktop shell uses it, so removal is safe.
Back out the no_cache_middleware added in f3a85d1. Fresh installs have no
cache so it is not needed for first run; cache during testing is cleared
manually. The uninstaller arg-bug fix from the same commit is kept.
Bug 1 (uninstall.bat could not delete the folder): %~dp0 ends with a
backslash, so -BundleDir "%~dp0" passed "...\dir\" — the trailing \" escaped
the closing quote and PowerShell received a path with a literal quote
("Illegal characters in path"). Resolve-Path failed, the deleter ran rd on a
garbage path, and nothing was removed. Strip the trailing backslash in the
.bat before passing, and defensively trim stray quotes/separators in the ps1.
Bug 2 root cause (shortcut prompt / UI showed stale code after updates):
the bridge served app.js via aiohttp's static handler with only
ETag/Last-Modified and no Cache-Control, so WebView2 heuristically cached it
and kept serving an old app.js after rebuilds. Add a no_cache_middleware
that sets Cache-Control: no-cache, no-store, must-revalidate on GET/HEAD.
Bug 2 root cause was WebView2 serving a stale cached app.js (no
Cache-Control header on the bridge static response). With a fresh
extract / cleared cache the first-run prompt works. Revert the diagnostic
instrumentation back to the clean maybeAskDesktopShortcut.
Not a fix — instruments maybeAskDesktopShortcut with an on-page banner
reporting __TAURI__ availability, shortcut_should_ask result, and whether
window.confirm fires/returns, so we can locate why the first-run prompt
never shows. To be reverted once the cause is confirmed.
Bug 1 (shortcut prompt only showed on the second launch): a native rfd
dialog spawned from the Rust startup thread has no parent window and got
buried behind the main window on first launch. Move the prompt to the web
UI: add shortcut_should_ask / shortcut_decide commands; the frontend asks
once via window.confirm after load (always on top). The startup thread now
only self-heals the shortcut path when already enabled.
Bug 2 (uninstall.bat could not delete the folder): kill ANY process whose
exe lives inside the bundle (not just GenericAgent/python by name), and run
the folder delete from a detached temp .bat that retries the recursive
remove ~20x to cover the window where uninstall.bat releases its handle.
Ship uninstall.bat (top-level, double-click entry) + uninstall_windows.ps1
(worker under runtime/) in the Windows portable bundle. The uninstaller:
- gracefully stops the bridge, then force-kills bundle-owned processes on
ports 14168/8900 (filtered by exe path so other bundles are untouched)
- removes the desktop shortcut only when it targets this bundle
- removes ~/.ga_desktop_settings.json
- schedules deletion of the bundle folder after exit (detached cmd)
Wire both scripts into the release-package workflow.
Windows portable bundle now offers to create a desktop shortcut on first
launch via a native yes/no dialog, remembers the choice in
~/.ga_desktop_settings.json (desktop_shortcut), and self-heals the .lnk
target to current_exe() on every enabled launch so a relocated bundle
keeps working.
Also add read_settings/merge_settings and route the three existing
settings writers through merge, so sibling keys (like desktop_shortcut)
are no longer clobbered by the python_path/project_dir rewrite.
Conductor and its subagents always used the first model (llm_no=0)
because GenericAgent() defaults to index 0 and conductor never switched.
Read ui.llmNo from ~/.ga_desktop_settings.json and apply it before each
conductor task (live follow) and on every subagent spawn, so the
conductor uses whatever model the user picked in the desktop UI.
Also enable the three desktop package workflows on the test_yiqi branch
and add frontends/conductor.py to their path filters so conductor
changes trigger a build.
The conductor token tab only has two summary rows but reused the chat
tab's fixed-layout 6-column table, so the first column ate all the
slack and looked sparse. Add a tok-table--conductor class that switches
just that tab to auto layout with evenly distributed columns. Also size
the conductor row icon to 1em so it matches the text instead of blowing
up to the SVG viewBox size. Chat tab layout is unchanged.
On macOS, App Translocation runs a quarantined .app from a random
read-only copy, so bundle_root() can't see our own runtime/ and
get_or_discover_config() would fall back to ~/.ga_desktop_settings.json
— silently pointing the bridge at a previously installed checkout. Add
running_inside_app_bundle() and skip stale settings when running from a
packaged .app, falling through to .app-relative auto-discovery instead.
cp -R broke the symlinks and ad-hoc code signature inside GenericAgent.app,
which makes Apple Silicon report the bundle as damaged. Copy with ditto and
re-apply an ad-hoc signature so it verifies. Also ship a 'Start
GenericAgent.command' that runs xattr -cr to clear the download quarantine
flag before launching, so users avoid the damaged/Gatekeeper prompt without
touching a terminal. README and SHA256SUMS updated accordingly.
The assemble step queried api.github.com/releases/latest anonymously,
which is capped at 60 requests/hour/IP on shared runners and failed with
HTTP 403 rate limit exceeded. Pass GITHUB_TOKEN via GH_TOKEN env and add
an Authorization header so the call uses the 1000/hour authenticated limit.
The mac workflow enabled cache: npm with cache-dependency-path pointing
at package-lock.json, but that file is gitignored and never committed, so
setup-node failed with 'unable to cache dependencies' and npm ci would
also fail without a lockfile. Align with the win/linux workflows which use
plain npm install and no cache.
Pure SVG line chart showing daily spending (¥) over time. Respects
date filter, fills zero-cost gaps, uses local timezone for grouping.
Hidden on Conductor tab.
- Two tabs: 聊天 (chat sessions) and Conductor (cumulative/current)
- Conductor tab hides date filter and stat-row summary
- Remove green background and expand triangle from conductor rows
- Bump app.js and styles.css cache versions
Two rows: cumulative total (survives conductor restart) and current
session. Restart detection via value comparison; data stored alongside
bridge token history in desktop_token_history.json.
Prevents token records from splitting into two entries when polling
occurs mid-generation. The full delta is captured on the next poll
after the session becomes idle.
- conductor.py: install cost_tracker + expose /token-stats endpoint (5 lines)
- app.js: fetch conductor token totals and display as a single summary row at top of token table (not expandable, with tooltip noting it's excluded from cumulative totals)
- styles.css: conductor row styling
feat(desktop): mark deleted sessions in token stats page
Show a "此会话已删除 / Session deleted" tag next to session titles
in the token usage table when the session no longer exists.
@
feat(desktop): persist token usage history to local file instead of localStorage
Token stats (history + snapshot) now stored in temp/desktop_token_history.json
via GET/POST /token-history endpoints, ensuring cross-browser consistency.
@
>> move session persistence from localStorage to backend API
>>
>> Sessions are now loaded from GET /sessions and metadata updates (pin,
>> title) go through PATCH /session/{sid}, eliminating cross-browser
>> isolation issues between Chrome and Safari.
- Add /token-stats bridge endpoint exposing cost_tracker data + model name
- Frontend localStorage persistence for per-prompt token records (WS-triggered)
- Time range filter (datetime-local), pagination, expandable session details
- Model-based cost estimation (Claude/GPT/DeepSeek price table with cache rates)
- All UI text via i18n, all colors via CSS variables
- Persist per-prompt token usage to ~/.GenericAgent/token_history.json
- Add GET /token-stats API endpoint with since/until filtering
- Token page: real data, expandable per-prompt details with model & cost
- Model price table for accurate cost estimation (GPT/Claude/DeepSeek/etc)
- Time range filter (datetime-local) with reset button
- Pagination (15 sessions per page)
- Stat cards (total/today/cost) follow time filter, today always absolute
- Migrate Rust settings path to ~/.GenericAgent/ga_desktop_settings.json
- Fix cost_tracker Python 3.9 compat (from __future__ import annotations)
- Wire up search input to filter sessions by title/message content
- Add toggle button to collapse/expand the session panel
- Add drag handle for resizing panel width (160–400px)
Switch to inc_out=True mode so queue items are incremental deltas
instead of cumulative text. The old cumulative approach broke when
agentmain's post-processing (</summary>\n\n insertion) shifted offsets,
causing the bridge's delta calculation to produce overlapping or
truncated content.
Now "next" items are forwarded directly, and "done" is only used as
content when no streaming occurred (error fallback).
- os.set_inheritable(_stdout_fd, False) on both Windows and Unix
- Unix path now uses os.dup() + os.fdopen() consistent with Windows
- Prevents subprocess.Popen children from inheriting the ACP JSON-RPC
file descriptor, which could corrupt the protocol stream
Stdio-based JSON-RPC bridge implementing the Agent Client Protocol,
allowing codeg to connect to GenericAgent as a live ACP agent.
Handles Windows binary stdout/stdin, redirects stray prints to stderr,
and converts GenericAgent's cumulative text queue to ACP delta streaming.