Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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/<name>.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/`)
Expand DownExpand Up@@ -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 路径不受影响)

Expand Down
9 changes: 7 additions & 2 deletions emrg/gui/renderer/js/dialogs.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 () => {
Expand Down
34 changes: 34 additions & 0 deletions emrg/gui/test/renderer.smoke.test.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 () => {
Expand Down
Loading