diff --git a/Agent.md b/Agent.md index 2e0b587..a7b1cd2 100644 --- a/Agent.md +++ b/Agent.md @@ -66,7 +66,7 @@ EMRG is a self-evolving AI agent architecture experiment. Python implementation, - 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` (86: 22 daemon_client + 22 app-commands + 17 renderer smoke + 15 i18n + 7 integration + 3 commands); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py` + - Unit tests `npm test` (88: 22 daemon_client + 22 app-commands + 19 renderer smoke + 15 i18n + 7 integration + 3 commands); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py` - **Auto project tracking** — Automatically detects and records working directories; project-scoped sessions - **Rant-driven evolution** — User feedback via `/rant` drives automatic self-improvement cycles - **Headless GitHub auth** — Non-interactive evolution auto-extracts `GH_TOKEN` from git credential store (osxkeychain / credential helper); PR comment/LGTM queries fall back to REST API (GraphQL needs `read:org` scope) @@ -94,7 +94,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg ``` Python: `uv run pytest tests/ -v` (493) — import check: `uv run python -c "from emrg.client.app import run_client"` -GUI: `cd emrg/gui && npm test` (86: 22 daemon_client + 22 app-commands + 17 renderer smoke + 15 i18n + 7 integration + 3 commands) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` +GUI: `cd emrg/gui && npm test` (88: 22 daemon_client + 22 app-commands + 19 renderer smoke + 15 i18n + 7 integration + 3 commands) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) ## Configuration diff --git a/README.md b/README.md index ebcec14..334722f 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ uv run python -m emrg # launch TUI cd emrg/gui npm ci # install deps (production: --omit=dev) npm start # launch GUI (auto-starts daemon) -npm test # run Node tests (86: 22 daemon_client + 22 app-commands + 17 renderer smoke + 15 i18n + 7 integration + 3 commands; integration runs in CI, local: npm run test:integration) +npm test # run Node tests (88: 22 daemon_client + 22 app-commands + 19 renderer smoke + 15 i18n + 7 integration + 3 commands; integration runs in CI, local: npm run test:integration) ``` CI runs tests and checks for conflict markers automatically via GitHub Actions (`.github/workflows/test.yml`). diff --git a/emrg/gui/renderer/js/chat.js b/emrg/gui/renderer/js/chat.js index af18b4e..1bafb51 100644 --- a/emrg/gui/renderer/js/chat.js +++ b/emrg/gui/renderer/js/chat.js @@ -134,6 +134,11 @@ const Chat = (() => { if (data.timeout) { addSystemMessage(EMRG_Copy._t("chat.timeoutWarn")); } + // 工具调用次数上限中断(跨项目教训:截断的工作不提示 = 用户拿半成品) + // 对齐 TUI:明确提示结果可能不完整 + 可继续(TUI 已有 "Try '继续' to resume") + if (data.content && /exceeded/i.test(data.content) && /max|limit|round/i.test(data.content)) { + addSystemMessage(EMRG_Copy._t("chat.maxRoundsHint")); + } } /** 工具友好状态行(进行中 → 完成/失败,默认折叠,点开展示原始输出) */ diff --git a/emrg/gui/renderer/js/i18n.js b/emrg/gui/renderer/js/i18n.js index be67610..9f0e7dc 100644 --- a/emrg/gui/renderer/js/i18n.js +++ b/emrg/gui/renderer/js/i18n.js @@ -187,6 +187,7 @@ const I18N = (() => { "chat.copyFailed": "复制失败", "chat.fromOtherClient": "(来自其他客户端)", "chat.timeoutWarn": "⚠️ 响应超时了,我还没说完——可以再试一次。", + "chat.maxRoundsHint": "⚠️ 这次任务因为工具调用次数上限中断了,结果可能不完整——输入“继续”我可以接着做。", "chat.elapsed": "耗时 {s}", "chat.expand": "展开全文", // 时间分组(utils.js/sidebar.js) @@ -449,6 +450,7 @@ const I18N = (() => { "chat.copyFailed": "Copy failed", "chat.fromOtherClient": "(from another client)", "chat.timeoutWarn": "⚠️ Response timed out — I wasn't done. Try again?", + "chat.maxRoundsHint": "⚠️ This task hit the tool-call limit and may be incomplete — type \"continue\" and I'll keep going.", "chat.elapsed": "took {s}", "chat.expand": "Expand full text", // Time groups (utils.js/sidebar.js) diff --git a/emrg/gui/test/renderer.smoke.test.js b/emrg/gui/test/renderer.smoke.test.js index 7eee738..e5e56cd 100644 --- a/emrg/gui/test/renderer.smoke.test.js +++ b/emrg/gui/test/renderer.smoke.test.js @@ -472,3 +472,26 @@ test("WorkBuddy P1:ResultPanel 折叠切换(⌘\ 与按钮)", async () => await vm.runInContext("ResultPanel.toggle()", ctx); assert.ok(!els["result-panel"].classList.contains("collapsed"), "再次 toggle 应展开"); }); + +test("工具调用上限中断 → 系统提示可继续(对齐 TUI,跨项目教训)", async () => { + const { ctx, els } = makeSandbox({}); + await tick(); + // 模拟 daemon 广播:max tool rounds 超限的 done 帧 + await vm.runInContext( + 'EMRG_Chat.handleDone({ request_id: "rid-max", content: "Exceeded maximum tool call rounds (30).", done: true })', + ctx + ); + const texts = (els["chat-view"].children || []).map((c) => c.textContent).join("|"); + assert.ok(/继续/.test(texts), `应提示可继续,实际: ${texts}`); +}); + +test("正常 done 帧不触发上限提示(无假阳性)", async () => { + const { ctx, els } = makeSandbox({}); + await tick(); + await vm.runInContext( + 'EMRG_Chat.handleDone({ request_id: "rid-ok", content: "完成了!", done: true })', + ctx + ); + const texts = (els["chat-view"].children || []).map((c) => c.textContent).join("|"); + assert.ok(!/继续/.test(texts), "正常完成不应出现继续提示"); +});