diff --git a/Agent.md b/Agent.md index d0508da1..2530a48a 100644 --- a/Agent.md +++ b/Agent.md @@ -85,7 +85,7 @@ Usage: say "tool loop" for the whole process, "round N" for a single LLM request - Streaming chat with delta rendering (16ms batching), markdown on done (marked + DOMPurify + local highlight.js subset), tool call status cards (2000-char truncation + expand) - Session list/switch/new/delete + right-click rename (context menu, #423) synced with daemon; own-stream busy lock (G65); broadcast streams from other clients tagged "来自其他客户端" - Disconnect/reconnect: red status dot, auto daemon respawn (stale-port detection), session resume, input bar restored on disconnect (no 30s fake-timeout) - - Unit tests `npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py` + - Unit tests `npm test` (256: 45 daemon_client + 19 conn-manager + 22 app-commands + 127 renderer smoke + 15 i18n + 8 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py` - **Scheduled tasks** — Task generalization + CRUD (rant 2026-08-12T18:23:15, #709/#710/#711) - Task handler generalized: `TaskHandler` (renamed from `EvolutionHandler`), repo-configured self-heal for any project, template lookup builtin → `~/.emrg/task-templates/.md` → fallback - Daemon commands: `task_create/update/delete` + `task_template_create/list/update/delete` (tasks stored in `~/.emrg/tasks.yml`, custom type templates in `~/.emrg/task-templates/`) @@ -119,7 +119,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg ``` Python: `uv run pytest tests/ -v` (988) — import check: `uv run python -c "from emrg.client.app import run_client"` -GUI: `cd emrg/gui && npm test` (254: 45 daemon_client + 19 conn-manager + 22 app-commands + 126 renderer smoke + 15 i18n + 7 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` +GUI: `cd emrg/gui && npm test` (256: 45 daemon_client + 19 conn-manager + 22 app-commands + 127 renderer smoke + 15 i18n + 8 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响) diff --git a/emrg/gui/renderer/js/dialogs.js b/emrg/gui/renderer/js/dialogs.js index a02c1f08..72ecd905 100644 --- a/emrg/gui/renderer/js/dialogs.js +++ b/emrg/gui/renderer/js/dialogs.js @@ -1229,8 +1229,13 @@ const Dialogs = (() => { } sessions.forEach((s) => { const row = el("button", { class: "help-row", type: "button", style: "width:100%;text-align:left;cursor:pointer;background:none;border:none;" }); - const name = el("span", { class: "help-cmd" }, s.title || _t("app.unnamed")); - const hint = el("span", { class: "help-hint" }, s.session_id === App.state.sessionId ? _t("app.current") : ""); + // rant 11:41:07:有 title 显 title,无 title 显完整 session id(name-or-id 规则,不截短) + const name = el("span", { class: "help-cmd" }, s.title || s.session_id); + const marks = []; + if (s.session_id === App.state.sessionId) marks.push(_t("app.current")); + const act = relTime(s.updated_at); // 最后活跃时间(Session.list_sessions 已返回 updated_at) + if (act) marks.push(act); + const hint = el("span", { class: "help-hint" }, marks.join(" · ")); row.appendChild(name); row.appendChild(hint); row.addEventListener("click", async () => { diff --git a/emrg/gui/test/renderer.smoke.test.js b/emrg/gui/test/renderer.smoke.test.js index 6757eecc..98169ae2 100644 --- a/emrg/gui/test/renderer.smoke.test.js +++ b/emrg/gui/test/renderer.smoke.test.js @@ -2157,6 +2157,40 @@ test("P5: /open 指令 → 打开会话对话框;无项目 → 提示新建", ); }); +test("rant 11:41:07: 打开会话列表 — 有 title 显 title、无 title 显完整 id + 最后活跃时间", async () => { + const now = Date.now(); + const { ctx, els } = makeSandbox({ + listProjects: async () => [{ name: "emrg", path: "/p/emrg" }], + listProjectSessions: async () => ({ + sessions: [ + { session_id: "s-title", title: "My Session", updated_at: new Date(now - 5 * 60000).toISOString() }, + { session_id: "s-untitled-full-id", title: "", updated_at: new Date(now - 2 * 3600000).toISOString() }, + { session_id: "s-no-time" }, + ], + }), + }); + await tick(); + await vm.runInContext("EMRG_Dialogs.showOpenSessionDialog()", ctx); + await tick(); + els["open-session-list"].children[0].children[0].click(); // 点项目 → 会话列表 + await tick(); + const rows = els["open-session-list"].children; + assert.strictEqual(rows.length, 3, "three sessions listed"); + const r0name = rows[0].children[0].textContent || ""; + const r0hint = rows[0].children[1].textContent || ""; + assert.ok(r0name.includes("My Session"), "titled session shows title"); + assert.ok(r0hint.includes("分钟前"), "titled session shows relative last-active time"); + const r1name = rows[1].children[0].textContent || ""; + const r1hint = rows[1].children[1].textContent || ""; + assert.ok(r1name.includes("s-untitled-full-id"), "untitled session shows full session id"); + assert.ok(r1hint.includes("小时前"), "untitled session shows relative last-active time"); + assert.ok(!r1name.includes("未命名"), "no unnamed fallback for untitled session"); + const r2name = rows[2].children[0].textContent || ""; + const r2hint = rows[2].children[1].textContent || ""; + assert.ok(r2name.includes("s-no-time"), "session without updated_at still shows id"); + assert.ok(r2hint === "", "no relative time when updated_at missing"); +}); + // ── P5 slice 2(rant 15:07:19):新建会话对话框 + 删除项目(受保护守卫) ── test("P5 slice 2: showNewSessionDialog 列项目 → 点选项目 → newSession(projectPath)", async () => {