Skip to content

Merge desktop fork updates into upstream main#671

Open
dd3xp wants to merge 342 commits into
lsdefine:mainfrom
dd3xp:main
Open

Merge desktop fork updates into upstream main#671
dd3xp wants to merge 342 commits into
lsdefine:mainfrom
dd3xp:main

Conversation

@dd3xp

@dd3xp dd3xp commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Merge desktop fork updates into upstream main

dd3xp and others added 30 commits May 28, 2026 04:27
…w-path bubble

- Render [Image #N]/[File #N] placeholders as inline chips in-place (filename)
  in user messages, so you can see where an attachment sits in the text. Image
  thumbnails + file cards are unchanged. Applies to chat and conductor via a
  shared renderMsgTextWithChips() helper; conductor messages now carry sid+name
  so the chip can resolve a filename.
- Conductor: stripAttach() also strips inline desktop_uploads paths, and pushMsg
  folds the server echo into the local optimistic message (keep clean text +
  chips/cards, drop the raw path) so conductor matches chat instead of showing a
  second bubble with the full local upload path.

Known limitation (backend, not in scope here): conductor attachments + running
state don't survive a page refresh — conductor.py only stores/sends
{id,role,msg,ts,read} with no file refs and no run state, so the frontend can't
restore them on reload. Persisting those belongs to the conductor maintainer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(frontend): add input length limits to all text fields
…ence + TUI-style session naming

UI restructure (frontend static):
- PLAN moved from sticky toggle chip to a preset card (BUILTIN_PRESETS) that
  one-shot-sends `presetPrompt.plan`, now pointing at the real
  memory/plan_sop.md (Auto was considered too but dropped — it semantically
  overlaps with the existing "自主探索" preset). All plan/auto toggle
  plumbing removed: state, chip listeners, applyToggleClass, ga_plan/ga_auto
  localStorage, planPrefix/autoPrefix injection.
- Chat composer: model chip moved out of the joined .cb-left pill group to
  sit next to the send button (composer-bot is now right-aligned model+send).
- Conductor composer: gained its own #collab-model-chip + #collab-model-menu;
  openModelMenu/renderModelMenu/updateModelChip refactored to be ctx-aware
  (both chips share the model list; each pops its menu into its own
  composer; updateModelChip syncs both names).
- Topbar dissolved into per-page headers: the global .topbar was removed
  from <main>; the run-state moved into a .page-top inside the chat page,
  so the "topbar" now switches with the page instead of persisting.
- Removed the conductor "{n} 个任务进行中" sticky banner (#collab-sticky);
  the same info is already in the progress panel and the sidebar badge.

Conductor persistence (conductor.py + frontend wiring):
- conductor.py: add_chat() carries files/images on each chat item; WS
  receive passes data.files/data.images through; hello includes a
  running flag (any subagent with status="running"). 5 net edits.
- app.js: ws.send now posts {msg, files, images} (was msg only); onWsData
  hello/chat branches map files/images and set S.conductorTyping from
  data.running so attachments + run animation survive a page refresh.

Session naming (mirror TUI continue_cmd._preview_text):
- New helper displayTitle(sess): if user manually renamed, use sess.title;
  otherwise scan messages — prefer the last <summary>…</summary> in any
  assistant reply, fall back to the first user message text (≤40 chars,
  attachment placeholders stripped), final fallback t('conv.defaultTitle').
- newSession() now defaults sess.title to '' instead of '新对话'; the
  earlier "first message → title" auto-rename block in submitInput is
  gone — titles only get written when the user renames explicitly.
- isAutoTitle() recognises '新对话'/'New chat'/'agent-N' as placeholder so
  legacy sessions and the brief agent-N experiment get re-derived live
  in the sidebar without losing user renames.
- Sidebar render + search filter now go through displayTitle.

Note: requires a one-time restart of conductor.py for its part to take
effect; the static frontend changes are just a refresh.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
两个修复:

1. tokRenderTable 里 title 显示修复 — 新的 session naming 把 sess.title 默认设为空字符串,导致 token 统计 fallback 到原始 session ID(sess-xxx)。改为用 displayTitle(sess) 实时推导标题,和侧边栏一致。
2. tokPollBridge 里写入 history 时的 title 修复 — 同样的问题,写入时 sess?.title || sid 对空标题 fallback 到 ID。改为 sess ? displayTitle(sess) : sid。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- o4-mini: .10/.40 -> /usr/bin/bash.55/.20 (price drop)
- deepseek-v4-pro: /usr/bin/bash.55/.19 -> /usr/bin/bash.435//usr/bin/bash.87 (permanent 75% cut)
- Add claude-opus-4-8 at /5
- Add disclaimer: pricing may vary by API provider

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Always overwrite last snapshot with current values (even zero) so
restart detection only triggers once instead of every refresh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Conductor 页 UI 重构:对齐主聊天布局,复用消息/composer 样式,独立 cdb-composer 与 progress rail/drawer。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…chat-only)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolved one conflict in app.js: kept the new token trend chart code from

main and adopted PR's services-tab nav handler (replaces the old

channels/status separate handlers, which the PR consolidates anyway).

index.html and styles.css auto-merged cleanly.

Closes #7

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…style

1. Sidebar nav: move 后台服务 (services) to AFTER 指挥家 (collab).

   New order: 聊天 → 指挥家 → 后台服务 → Token 统计.

2. Token trend chart: was flat (container 160px tall, viewBox 600×140).

   Now container 280px + max-width 760px to stop it stretching on wide

   screens; viewBox bumped to 600×260 with a bit more padding so text

   does not get distorted under preserveAspectRatio="none".

3. Token stats tabs (聊天 / Conductor) reuse the .svc-tabs underline

   look from the services page — same visual language. CSS-only;

   .tok-tabs/.tok-tab classes and the JS click handler are unchanged.

4. Bump app.js?v=81→82 (app.js really changed this time, and the bump

   also flushes stale i18n cache for previously-untranslated keys).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previous commit changed .tok-tabs/.tok-tab/.tok-chart CSS but did not bump the styles.css version, so the browser kept serving the cached v=93 file and the visual changes (underline tabs, taller chart) did not appear in the page.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…scaffold

1. Removed the two floating arrow buttons (.sb-toggle / .rp-toggle) that

   used to sit at the side seams between sidebar/main/rightpanel.

2. Each page section now owns its own .page-top bar with:

   - left: .pt-sb-toggle (caretLeft, borderless icon button) — collapses

     left sidebar; svg rotates 180° when sidebar is collapsed.

   - right: .pt-rp-toggle (caretRight) — same logic for right panel.

   - chat/collab keep run-state centered between the two icons.

   - services/token use a .pt-spacer to fill the middle gap.

3. .pt-icon style is borderless / transparent / opacity-0.7 (no boxy

   bordered button look); page-top padding tightened to 4px so the

   carets hug the screen edges. Bump styles.css?v=95→97.

4. Wiring: app.js queries .pt-sb-toggle/.pt-rp-toggle by class (4 of

   each, one per page) and binds them to the existing

   .body.classList.toggle('sb-collapsed') / .toggle('rp-collapsed')

   handlers. Behavior identical to the old single buttons.

5. Dropped the DEV annotation scaffold: removed annot.css link in head,

   annot.js script at end of body, and deleted both files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per teacher request, pull the 14168 / 8900 magic numbers and the URL

construction out of ~17 inline call sites into a single block at the top

of app.js:

  const BRIDGE_PORT = 14168;

  const CONDUCTOR_PORT = 8900;

  const BRIDGE_ORIGIN = `...`;        // http(s)://host:14168

  const BRIDGE_WS_ORIGIN = `...`;     // ws(s)://host:14168

  const CONDUCTOR_ORIGIN = `...`;     // http://host:8900

  const CONDUCTOR_WS_ORIGIN = `...`;  // ws(s)://host:8900

All fetch / WebSocket call sites now use those origins. No behavioral

change — bridge still calls 14168, conductor still calls 8900. Sets up

future fixes (same-origin bridge, bridge-proxied conductor, runtime port

swap) to be a one-line change in the constants block.

Bump app.js?v=83 -> 84.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ed http)

BRIDGE_ORIGIN already used ${location.protocol}//; CONDUCTOR_ORIGIN was

hardcoded to http://, which would have broken under HTTPS deployment

with a Mixed Content block. Now both behave consistently — page over

HTTPS uses https for conductor calls and wss for the WS already (the

WS constant was already correct).

Triggered by SakuraFrp requiring HTTPS access (TCP tunnels reject

plain HTTP with 501). Tunnels now run with 自动 HTTPS enabled, so

browser hits https://...:14168 / :18900, and frontend needs to use

https in its constructed conductor URL.

Bump app.js?v=84 -> 86 (skipping 85 — that was an auth-gate-only bump).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When mobile browser is backgrounded the OS pauses/kills WebSocket; on

return the page stays on a dead connection and the next send shows

"bridge 未连接". Conductor's WS already had reconnect+backoff; bridge

did not.

Added to bridge WS:

- close handler now schedules reconnect with exponential backoff

  (1s, 2s, 4s, ... capped at 30s)

- visibilitychange listener: when page becomes visible again and ws is

  closed, reset retry counter and reconnect immediately

- wsRetries resets on successful open

- skip retry while page is hidden (battery saver)

Bump app.js?v=86 -> 87.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yiqi-017 and others added 29 commits July 5, 2026 22:55
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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…le UCRT

- 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.
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.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(conductor): push error to frontend on LLM failure
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.
- 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
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.
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).
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)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

7 participants