From 45b58e2b19a3a12f557e94e02d9d850d332dac18 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Thu, 20 Aug 2026 16:32:34 +0800 Subject: [PATCH] emrg: remove GUI 'working directory' (project_dir) concept (rant 2026-08-20T16:03:31) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Host design-finalized: the GUI's global project_dir is useless — a session's real cwd is its project path (projectPath, P5 slice 2). Remove the concept; keep cwd semantics (real project dir per session). - config.toml [gui] project_dir: no longer read/written/validated - GUI fallback cwd fixed to os.homedir() (DEFAULT_CWD in main.js) - Settings panel: workdir tab removed (6 → 5 tabs); settings-body-workdir block deleted - Welcome page: step 2 'choose a working directory' removed - daemon_client: TOKEN_FILE/EMRGD_LOG fixed to canonical ~/.emrg/emrgd.token and ~/.emrg/emrgd.log — projectDir parameter and G129 fallback logic removed (emrgd.token is the sole canonical location since #884) - conn-manager: projectDir passthrough removed - renderer app.js: state.projectDir gone; project_dir_valid startup check gone; sessionProjectName fallback → 'home'; projectPathFor fallback → '' - dialogs/i18n/index.html: workdir inputs, tabs, step2 and keys removed - Tests: 45 daemon_client + 16 i18n + 246 unit green (7 integration fail locally by design); pytest 980 passed + 1 skipped; import + CLI OK --- emrg/gui/conn-manager.js | 5 +- emrg/gui/daemon_client.js | 75 +++++-------- emrg/gui/main.js | 53 ++++----- emrg/gui/renderer/index.html | 18 ---- emrg/gui/renderer/js/app.js | 27 +---- emrg/gui/renderer/js/dialogs.js | 6 -- emrg/gui/renderer/js/i18n.js | 14 --- emrg/gui/test/app-commands.test.js | 12 +-- emrg/gui/test/conn-manager.test.js | 38 +++---- emrg/gui/test/daemon_client.test.js | 156 ++++++++++++--------------- emrg/gui/test/integration.test.js | 2 +- emrg/gui/test/renderer.smoke.test.js | 32 ++---- 12 files changed, 161 insertions(+), 277 deletions(-) diff --git a/emrg/gui/conn-manager.js b/emrg/gui/conn-manager.js index 42cad02a..4bac0280 100644 --- a/emrg/gui/conn-manager.js +++ b/emrg/gui/conn-manager.js @@ -26,8 +26,7 @@ const { DaemonClient } = require("./daemon_client.js"); class ConnManager { - constructor({ projectDir, logger = console, isPackaged = false, restartWindowMs = 1000, singleRetryDelayMs = 1000 } = {}) { - this.projectDir = projectDir; + constructor({ logger = console, isPackaged = false, restartWindowMs = 1000, singleRetryDelayMs = 1000 } = {}) { this.logger = logger; this.isPackaged = isPackaged; this._conns = new Map(); // sid -> { conn, projectPath } @@ -49,7 +48,6 @@ class ConnManager { async ensureDaemon() { if (this._daemonConn && this._daemonConn.connected) return this._daemonConn; const boot = new DaemonClient({ - projectDir: this.projectDir, logger: this.logger, isPackaged: this.isPackaged, }); @@ -79,7 +77,6 @@ class ConnManager { } await this.ensureDaemon(); const conn = new DaemonClient({ - projectDir: this.projectDir, logger: this.logger, isPackaged: this.isPackaged, deltaBatchMs: 16, // P2:delta 批量(G122 16ms)每连接一份(#626) diff --git a/emrg/gui/daemon_client.js b/emrg/gui/daemon_client.js index eb738bd5..2afe7782 100644 --- a/emrg/gui/daemon_client.js +++ b/emrg/gui/daemon_client.js @@ -18,22 +18,13 @@ const crypto = require("crypto"); const { spawn } = require("child_process"); const WebSocket = require("ws"); -// G129 (rant 2026-08-09T08:03:46): TOKEN_FILE 必须接受 projectDir——硬编码 -// os.homedir() 时,Windows 测试的 setupTempHome() 只设 HOME 不设 USERPROFILE, -// os.homedir() 仍读 USERPROFILE(真实用户目录)→ 测试把假 token 写进 -// 真实的 ~/.emrg/emrgd.token → 演化周期 10 小时连不上 daemon(WinError 1225)。 -// 所有调用点必须传 this.projectDir(默认 os.homedir() 保持生产行为不变)。 -const TOKEN_FILE = (projectDir = os.homedir()) => path.join(projectDir, ".emrg", "emrgd.token"); -const EMRGD_LOG = (projectDir = os.homedir()) => path.join(projectDir, ".emrg", "emrgd.log"); -// Rant 2026-08-09T18:47:37(GUI 连不上 daemon 回归):daemon 的规范运行时目录永远是 -// ~/.emrg(daemon.py config_dir() = Path.home()/".emrg";connect.py 无条件读 -// ~/.emrg/emrgd.token)。GUI 的 projectDir 若被 config gui.project_dir 指向别处 -// (非 home),按 projectDir 读 token/pid/log 全部落空 → 误判 daemon 不存在 → -// 反复 spawn 撞 PID 锁 → "failed to start after 3 attempts" 假错误,而真 daemon 一直活着。 -// 规范位置常量:作为 projectDir 读取失败时的权威回退。 -const HOME_TOKEN_FILE = () => path.join(os.homedir(), ".emrg", "emrgd.token"); +// Rant 2026-08-20T16:03:31:GUI"工作目录"概念已删除——daemon 运行时文件固定读取 +// 规范位置 ~/.emrg(daemon.py config_dir() = Path.home()/".emrg";connect.py 无条件 +// 读 ~/.emrg/emrgd.token)。projectDir 参数与 G129 回退逻辑随概念一起清理(#884 后 +// emrgd.token 已是唯一规范位置,回退冗余)。 +const TOKEN_FILE = () => path.join(os.homedir(), ".emrg", "emrgd.token"); +const EMRGD_LOG = () => path.join(os.homedir(), ".emrg", "emrgd.log"); const HOME_PID_FILE = () => path.join(os.homedir(), ".emrg", "emrgd.pid"); -const HOME_EMRGD_LOG = () => path.join(os.homedir(), ".emrg", "emrgd.log"); // Fixed daemon port (rant 2026-08-19T08:05:21 + 2026-08-20T14:32:52): the // daemon always listens on this constant — keep in sync with emrg/connect.py // EMRGD_PORT and emrg/_stop_all.py _EMRGD_PORT. The token file no longer @@ -86,8 +77,7 @@ const RESPONSE_TYPES = { }; class DaemonClient { - constructor({ projectDir = os.homedir(), logger = console, authTimeoutMs = AUTH_TIMEOUT_MS, isPackaged = false, deltaBatchMs = 0 } = {}) { - this.projectDir = projectDir; + constructor({ logger = console, authTimeoutMs = AUTH_TIMEOUT_MS, isPackaged = false, deltaBatchMs = 0 } = {}) { this.logger = logger; this._authTimeoutMs = authTimeoutMs; // G142 测试可注入短超时(默认 10s) this._isPackaged = isPackaged; // Phase 4:打包模式(rant #12 §4)由 main.js 注入 app.isPackaged @@ -125,9 +115,9 @@ class DaemonClient { // ── 生命周期 ──────────────────────────────────────────── - // Rant 2026-08-09T18:47:37:读 token 的权威入口。先试 projectDir(G129 语义), - // 缺失/畸形时回退 daemon 规范位置 ~/.emrg。文件仅含单行 token(rant - // 2026-08-20T14:32:52);端口一律用 EMRGD_PORT 常量。返回 {token, source, port} 或 null。 + // Rant 2026-08-20T16:03:31:读 token 的权威入口——固定读 daemon 规范位置 + // ~/.emrg/emrgd.token(#884 后唯一位置)。文件仅含单行 token;端口一律用 + // EMRGD_PORT 常量。返回 {token, source, port} 或 null。 _readPortToken() { const tryRead = (file) => { try { @@ -137,16 +127,8 @@ class DaemonClient { } catch { /* missing/unreadable → try next */ } return null; }; - const project = tryRead(TOKEN_FILE(this.projectDir)); - if (project) return { ...project, source: "projectDir", port: EMRGD_PORT }; - const home = tryRead(HOME_TOKEN_FILE()); - if (home) { - this.logger.warn( - `[gui] token file not found at projectDir (${TOKEN_FILE(this.projectDir)}) — ` + - `reusing canonical ~/.emrg/emrgd.token` - ); - return { ...home, source: "home", port: EMRGD_PORT }; - } + const token = tryRead(TOKEN_FILE()); + if (token) return { ...token, source: "canonical", port: EMRGD_PORT }; return null; } @@ -168,8 +150,8 @@ class DaemonClient { // R124 对应(daemon_manager.py):spawn 超时后读 emrgd.log 尾部, // 让宿主看到真实失败原因(缺 DLL / PATH / 端口冲突),而不是干巴巴的 // "failed to start within timeout"(rant 2026-08-09T13:16:36 验收项 ②)。 - // 18:47:37:log 也在规范 ~/.emrg 下——projectDir 读不到就回退 home。 - for (const file of [EMRGD_LOG(this.projectDir), HOME_EMRGD_LOG()]) { + // 18:47:37:log 在规范 ~/.emrg 下;16:03:31 后固定读该位置。 + for (const file of [EMRGD_LOG()]) { try { const data = fs.readFileSync(file, "utf8"); const tail = data.trim().split("\n").slice(-lines).join("\n"); @@ -193,7 +175,7 @@ class DaemonClient { if (this._isPackaged) { const emrgdPath = this._findDaemonExecutable(); const opts = { - cwd: this.projectDir, + cwd: os.homedir(), stdio: "ignore", detached: true, }; @@ -203,7 +185,7 @@ class DaemonClient { opts.shell = true; opts.windowsHide = true; } - this.logger.info(`[gui] spawning packaged daemon: ${emrgdPath} cwd=${this.projectDir}`); + this.logger.info(`[gui] spawning packaged daemon: ${emrgdPath} cwd=${os.homedir()}`); const child = spawn(emrgdPath, [], opts); child.unref(); this._daemonChild = child; @@ -218,9 +200,9 @@ class DaemonClient { // G125:spawn 设 cwd=project_dir(daemon load_skills 用 Path.cwd() 加载项目级 skills) const python = this._findPython(); const args = ["-m", "emrg.server"]; - this.logger.info(`[gui] spawning daemon: ${python} ${args.join(" ")} cwd=${this.projectDir}`); + this.logger.info(`[gui] spawning daemon: ${python} ${args.join(" ")} cwd=${os.homedir()}`); const child = spawn(python, args, { - cwd: this.projectDir, + cwd: os.homedir(), stdio: "ignore", // G68:对照 DEVNULL detached: true, // 对照 start_new_session=True // windowsHide: python.exe 是 console 子系统——GUI spawn 时不隐藏会 @@ -242,12 +224,9 @@ class DaemonClient { // Rant 2026-08-09T13:16:36 G43 加固:daemon 进程是否存活(emrgd.pid 探测)。 // 存活 → ws 连接失败视为瞬时(daemon 重启/启动中),保留 port 文件交给退避重试; // 死亡 → 允许 G43 删文件重拉。 - // 18:47:37:pid 文件也在规范 ~/.emrg —— projectDir 读不到回退 home。 + // 18:47:37:pid 文件在规范 ~/.emrg;16:03:31 后固定读该位置。 _daemonProcessAlive() { - const pidFiles = [ - path.join(this.projectDir, ".emrg", "emrgd.pid"), - HOME_PID_FILE(), - ]; + const pidFiles = [HOME_PID_FILE()]; for (const pidFile of pidFiles) { try { const pid = Number(String(fs.readFileSync(pidFile, "utf8")).trim()); @@ -290,8 +269,8 @@ class DaemonClient { // Rant 2026-08-09T18:47:37(A1 + B1):探测"已存在的 daemon"——4 状态诊断日志 // (token_file_exists / token_file_content / daemon_alive(ping) / spawn_result)。 - // spawn 失败 ≠ daemon 不存在:GUI 可能因 projectDir≠home 读错 token 文件, - // 或 daemon 早已被 scheduler/TUI 拉起。返回 {token, source, port} 或 null。 + // spawn 失败 ≠ daemon 不存在:daemon 可能早已被 scheduler/TUI 拉起。 + // 返回 {token, source, port} 或 null。 async _probeExistingDaemon(spawnResult = "n/a") { const pt = this._readPortToken(); const tokenFileExists = !!(pt || this._readPortTokenRaw()); @@ -306,7 +285,7 @@ class DaemonClient { // 读 token 文件原始存在性(不含解析),供 probe 日志用。 _readPortTokenRaw() { - for (const file of [TOKEN_FILE(this.projectDir), HOME_TOKEN_FILE()]) { + for (const file of [TOKEN_FILE()]) { try { if (fs.readFileSync(file, "utf8").trim()) return true; } catch { /* next */ } } return false; @@ -329,7 +308,7 @@ class DaemonClient { throw spawnErr; } // spawn 成功:daemon 永远写规范 ~/.emrg/emrgd.token(daemon.py config_dir()), - // 用权威读取(projectDir 回退 home),不假设 projectDir==home。 + // 权威读取固定该位置(16:03:31)。 const pt = this._readPortToken(); if (!pt) throw new Error("token file not written after spawn"); this.logger.info(`[gui] daemon spawned ok: port=${EMRGD_PORT}`); @@ -337,7 +316,7 @@ class DaemonClient { } async ensureConnected({ skipStart = false } = {}) { - // Rant 2026-08-09T18:47:37:1. 读 token 文件(projectDir → 规范 ~/.emrg 回退)→ + // Rant 2026-08-09T18:47:37:1. 读 token 文件(固定规范 ~/.emrg)→ // 无则拉 daemon;spawn 失败先探测已有 daemon,活着直接复用,不再盲报 // "failed to start after 3 attempts"。每步打结构化诊断日志(B1-B5)。 // P2 connManager(rant 2026-08-10T15:07:19):skipStart=true 时 daemon 生命周期 @@ -353,7 +332,7 @@ class DaemonClient { } else { if (skipStart) { throw new Error( - `daemon not running (skipStart): no token file at ${TOKEN_FILE(this.projectDir)}` + `daemon not running (skipStart): no token file at ${TOKEN_FILE()}` ); } this.logger.info(`[gui] ensureConnected: token_file_exists=false — spawning daemon`); @@ -392,7 +371,7 @@ class DaemonClient { } this.logger.warn(`[gui] ws connect failed: ${e.message} — stale token, respawning daemon`); try { this.ws.close(); } catch { /* ignore */ } - try { fs.unlinkSync(TOKEN_FILE(this.projectDir)); } catch { /* ignore */ } + try { fs.unlinkSync(TOKEN_FILE()); } catch { /* ignore */ } const r = await this._spawnOrProbe(); port = r.port; token = r.token; diff --git a/emrg/gui/main.js b/emrg/gui/main.js index d881770a..bcc56d37 100644 --- a/emrg/gui/main.js +++ b/emrg/gui/main.js @@ -28,7 +28,8 @@ function main() { const logger = createLogger(); let win = null; let connManager = null; // P2(rant 15:07:19):连接管理器 = daemon 生命周期唯一 owner - let projectDir = os.homedir(); + // Rant 2026-08-20T16:03:31:GUI"工作目录"概念已删除——无项目上下文时的兜底 cwd 固定为 home。 + const DEFAULT_CWD = os.homedir(); let configExists = false; let currentSessionId = null; let reconnectTimer = null; @@ -191,7 +192,7 @@ vision = false } const python = connManager?.daemonConn()?._findPython() || "python3"; const child = spawn(python, ["-c", "from emrg.config import ensure_config; ensure_config()"], { - cwd: projectDir, + cwd: DEFAULT_CWD, stdio: "ignore", ...(process.platform === "win32" ? { windowsHide: true } : {}), }); @@ -232,7 +233,7 @@ vision = false function validateConfig(c) { // 设计 §7.1:直接接收所需字段 + 基本类型检查(防写坏 config.toml 的健壮性,非安全设计) const out = {}; - for (const k of ["apiKey", "baseUrl", "model", "projectDir", "theme"]) { + for (const k of ["apiKey", "baseUrl", "model", "theme"]) { if (c[k] !== undefined) out[k] = typeof c[k] === "string" ? c[k] : String(c[k]); } if (Array.isArray(c.models)) { @@ -257,20 +258,14 @@ vision = false // G34/G71/G112:config 存在性 → ensureConnected → ping → list_sessions configExists = fs.existsSync(configPath()); const cfg = readConfig(); - projectDir = cfg.gui?.project_dir || os.homedir(); - // G121:校验 project_dir 存在可写 - let projectDirValid = true; - try { - fs.accessSync(projectDir, fs.constants.W_OK); - } catch { projectDirValid = false; } if (!configExists) { // config 缺失 → 不拉起 daemon(daemon 启动即崩),直接返回缺配置 - return { config_exists: false, api_key_configured: false, project_dir: projectDir, project_dir_valid: projectDirValid, server_id: "", model: "", version: APP_VERSION }; + return { config_exists: false, api_key_configured: false, server_id: "", model: "", version: APP_VERSION }; } const keyConfigured = isKeyConfigured(cfg.llm?.api_key); if (!keyConfigured) { - return { config_exists: true, api_key_configured: false, project_dir: projectDir, project_dir_valid: projectDirValid, server_id: "", model: "", version: APP_VERSION }; + return { config_exists: true, api_key_configured: false, server_id: "", model: "", version: APP_VERSION }; } await ensureConnected(); @@ -281,8 +276,6 @@ vision = false return { config_exists: true, api_key_configured: true, - project_dir: projectDir, - project_dir_valid: projectDirValid, server_id: pong?.identity?.instance_id || "", model: pong?.model || "", evolution_count: pong?.evolution_count ?? 0, // G19:init 透传演化计数(waitForPong 已消耗 pong) @@ -301,7 +294,7 @@ vision = false } // P2:每会话独立连接——首条消息前自动打开(新会话不 resume,daemon 隐式订阅) // P5 slice 2:cwd 取该会话所属项目(跨项目会话用其项目路径,非全局 projectDir) - const sessionCwd = openSessions.get(sessionId)?.projectPath || projectDir; + const sessionCwd = openSessions.get(sessionId)?.projectPath || DEFAULT_CWD; let conn = connManager?.get(sessionId); if (!conn || !conn.connected) { conn = await openSession(sessionId, sessionCwd, { resume: false }); @@ -338,7 +331,7 @@ vision = false // G110:切会话清空旧连接分组缓存(含 timer),防广播"幽灵"残留 connManager?.get(prevSid)?.clearGroups(); // P5 slice 2:跨项目打开——用该项目路径 resume(非全局 projectDir) - const targetPath = projectPath || openSessions.get(sessionId)?.projectPath || projectDir; + const targetPath = projectPath || openSessions.get(sessionId)?.projectPath || DEFAULT_CWD; try { await openSession(sessionId, targetPath); // 打开(新)会话连接 + resume_session 自动订阅 } catch (e) { @@ -362,7 +355,7 @@ vision = false ipcMain.handle("emrg:deleteSession", async (_e, { sessionId }) => { if (!validateSessionId(sessionId)) throw new Error("invalid session_id"); - await requireConn().sendCommandAndWait("delete_session", { session_id: sessionId, cwd: projectDir }, 5000); + await requireConn().sendCommandAndWait("delete_session", { session_id: sessionId, cwd: DEFAULT_CWD }, 5000); connManager?.close(sessionId); // P2:删除会话 → 关闭该会话连接(若打开) openSessions.delete(sessionId); // P4:删除(删数据)→ 一并移出打开会话簿记 schedulePersistGuiState(); @@ -374,7 +367,7 @@ vision = false if (!validateSessionId(sessionId)) throw new Error("invalid session_id"); const clean = String(title || "").trim().slice(0, 80); // 截断超长标题 if (!clean) throw new Error("empty title"); - const frame = await requireConn().sendCommandAndWait("rename_session", { session_id: sessionId, cwd: projectDir, title: clean }, 5000); + const frame = await requireConn().sendCommandAndWait("rename_session", { session_id: sessionId, cwd: DEFAULT_CWD, title: clean }, 5000); // 跨项目会话重命名成功后立即同步侧边栏标题(rant 12:01:44) const v = openSessions.get(sessionId); if (v) { @@ -419,21 +412,21 @@ vision = false ipcMain.handle("emrg:clearSession", async (_e, { sessionId }) => { // GUI / 指令 P1:/clear — 清空当前会话(daemon 协议 clear_session 已存在) if (!validateSessionId(sessionId)) throw new Error("invalid session_id"); - await requireConn().sendCommandAndWait("clear_session", { session_id: sessionId, cwd: projectDir }, 5000); + await requireConn().sendCommandAndWait("clear_session", { session_id: sessionId, cwd: DEFAULT_CWD }, 5000); return { ok: true }; }); ipcMain.handle("emrg:compactSession", async (_e, { sessionId }) => { // GUI / 指令 P1:/compact — 压缩当前会话历史(daemon 协议 compact 已存在) if (!validateSessionId(sessionId)) throw new Error("invalid session_id"); - await requireConn().sendCommandAndWait("compact", { session_id: sessionId, cwd: projectDir }, 5000); + await requireConn().sendCommandAndWait("compact", { session_id: sessionId, cwd: DEFAULT_CWD }, 5000); return { ok: true }; }); ipcMain.handle("emrg:listHistory", async (_e, { sessionId, limit, offset } = {}) => { // GUI / 指令 P2:/rewind + rant 14:15:12 历史按需加载(limit/offset 可选) if (!validateSessionId(sessionId)) throw new Error("invalid session_id"); - const payload = { session_id: sessionId, cwd: projectDir }; + const payload = { session_id: sessionId, cwd: DEFAULT_CWD }; if (limit != null) payload.limit = limit; if (offset != null) payload.offset = offset; const frame = await requireConn().sendCommandAndWait("list_history", payload, 5000); @@ -448,7 +441,7 @@ vision = false } const frame = await requireConn().sendCommandAndWait( "rewind_session", - { session_id: sessionId, cwd: projectDir, record_index: recordIndex }, + { session_id: sessionId, cwd: DEFAULT_CWD, record_index: recordIndex }, 5000 ); return { ok: true, removedCount: frame.removed_count ?? 0 }; @@ -456,7 +449,7 @@ vision = false ipcMain.handle("emrg:listMemories", async (_e, { scope = "project", sessionId } = {}) => { // GUI / 指令 P3:/memory — 列出记忆(daemon list_memories → memories_list) - const params = { scope, cwd: projectDir }; + const params = { scope, cwd: DEFAULT_CWD }; if (scope === "session") { if (!validateSessionId(sessionId)) throw new Error("invalid session_id"); params.session_id = sessionId; @@ -468,7 +461,7 @@ vision = false ipcMain.handle("emrg:readMemory", async (_e, { memoryId, scope = "project", sessionId } = {}) => { // GUI / 指令 P3:/memory — 读取单条记忆(daemon read_memory → memory_content) if (typeof memoryId !== "string" || !memoryId.trim()) throw new Error("invalid memory_id"); - const params = { scope, memory_id: memoryId.trim(), cwd: projectDir }; + const params = { scope, memory_id: memoryId.trim(), cwd: DEFAULT_CWD }; if (scope === "session") { if (!validateSessionId(sessionId)) throw new Error("invalid session_id"); params.session_id = sessionId; @@ -534,7 +527,7 @@ vision = false const skills = []; const dirs = [ { dir: path.join(os.homedir(), ".emrg", "skills"), source: "user" }, - { dir: path.join(projectDir, ".emrg", "skills"), source: "project" }, + { dir: path.join(DEFAULT_CWD, ".emrg", "skills"), source: "project" }, ]; for (const { dir, source } of dirs) { let files = []; @@ -769,7 +762,6 @@ vision = false apiKey: isKeyConfigured(key) ? key : "", baseUrl: cfg.llm?.base_url || "", model: cfg.llm?.model || "", - projectDir: cfg.gui?.project_dir || os.homedir(), models, modelDetails, theme: cfg.gui?.theme || "system", // §7.1:外观主题持久化(浅色/深色/跟随系统) @@ -792,11 +784,6 @@ vision = false if (cfg.apiKey !== undefined) toml.llm.api_key = cfg.apiKey; if (cfg.baseUrl !== undefined) toml.llm.base_url = cfg.baseUrl; if (cfg.model !== undefined) toml.llm.model = cfg.model; - if (cfg.projectDir !== undefined) { - toml.gui = toml.gui || {}; - toml.gui.project_dir = cfg.projectDir; // G115:snake_case 落盘 - projectDir = cfg.projectDir; - } if (cfg.theme !== undefined) { toml.gui = toml.gui || {}; toml.gui.theme = cfg.theme; // §7.1:主题持久化(浅色/深色/跟随系统) @@ -845,7 +832,7 @@ vision = false // 惰性初始化 connManager(挂事件桥 + 恢复钩子)。 function ensureConnManager() { if (connManager) return connManager; - connManager = new ConnManager({ projectDir, logger, isPackaged: app.isPackaged }); + connManager = new ConnManager({ logger, isPackaged: app.isPackaged }); // 每个新会话连接建立时挂 renderer 事件桥(附带 sid;含 recoverAll 重开路径) connManager.onOpen((sid, conn, projectPath) => { conn.onEvent((type, data) => { @@ -1084,7 +1071,7 @@ vision = false if (connManager.daemonConn()?.connected) { // G41(P2 改写):恢复当前会话连接(若 daemon 重启后未由 recoverAll 重开) if (currentSessionId && !connManager.get(currentSessionId)) { - try { await openSession(currentSessionId, projectDir); } catch { /* 会话可能已删 */ } + try { await openSession(currentSessionId, DEFAULT_CWD); } catch { /* 会话可能已删 */ } } const sessions = await listSessions(); sendToRenderer("sessions", { sessions }); @@ -1114,7 +1101,7 @@ vision = false }); } - async function listSessions(cwd = projectDir) { + async function listSessions(cwd = DEFAULT_CWD) { try { const frame = await requireConn().sendCommandAndWait("list_sessions", { cwd }, 5000); return frame.sessions || []; diff --git a/emrg/gui/renderer/index.html b/emrg/gui/renderer/index.html index adfb5613..d15ff8f8 100644 --- a/emrg/gui/renderer/index.html +++ b/emrg/gui/renderer/index.html @@ -172,7 +172,6 @@

Rant 管理

设置

- @@ -206,16 +205,6 @@

设置

点左侧圆点可设为默认模型,切换后下一条消息即生效
-