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
23 changes: 21 additions & 2 deletions Agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,25 @@ EMRG is a self-evolving AI agent architecture experiment. Python implementation,
- **README language**: `README.md` = English (default), `README.cn.md` = Chinese
- **Project context files**: `README.md` = English, `Agent.md` = English

## Terminology

Unified vocabulary for the agent's execution model (code terms in `emrg/server/daemon.py`):

- **Tool loop** (工具循环) — the complete process triggered by one user message: the agent calls tools and sends LLM requests repeatedly until a round produces no new tool calls. Code: "tool loop" (`_run_tool_loop`).
- **Round** (轮) — one iteration inside a tool loop: one LLM request + zero or more tool calls + tool executions. Code: `round_num` (daemon.py:1775 `for round_num in range(1, self._max_tool_rounds + 1)`), bounded by `max_tool_rounds`.
- **Evolution cycle** (演化周期) — a distinct concept: one full run of the self-evolution task ("Prepare → Review → Discover → Improve → Submit → Record"), unrelated to tool loop rounds.

Hierarchy:
```
user message
└── tool loop (the whole process)
├── round 1: LLM request → tool calls → execute
├── round 2: LLM request → tool calls → execute
└── round N: LLM request → no tool calls → loop ends
```

Usage: say "tool loop" for the whole process, "round N" for a single LLM request + tools. Do not call evolution cycles "rounds".

## Current Features

- **TUI Client** — Rich terminal UI with Markdown rendering, syntax highlighting, diff display
Expand Down Expand Up @@ -66,7 +85,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` (178: 43 daemon_client + 19 conn-manager + 22 app-commands + 59 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config + 7 gui-state); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (179: 43 daemon_client + 19 conn-manager + 22 app-commands + 59 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state); 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)
Expand Down Expand Up @@ -94,7 +113,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (681) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (178: 43 daemon_client + 19 conn-manager + 22 app-commands + 59 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
GUI: `cd emrg/gui && npm test` (179: 43 daemon_client + 19 conn-manager + 22 app-commands + 59 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state) — 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 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)

Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 681 项)· `cd emrg/gui && npm test`(178 项:43 daemon_client + 19 conn-manager + 22 app-commands + 59 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config + 7 gui-state)
快速检查:`uv run pytest tests/ -v`(当前 681 项)· `cd emrg/gui && npm test`(179 项:43 daemon_client + 19 conn-manager + 22 app-commands + 59 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state)

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 681 items) · `cd emrg/gui && npm test` (178: 43 daemon_client + 19 conn-manager + 22 app-commands + 59 renderer smoke + 15 i18n + 7 integration + 3 commands + 3 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` (currently 681 items) · `cd emrg/gui && npm test` (179: 43 daemon_client + 19 conn-manager + 22 app-commands + 59 renderer smoke + 15 i18n + 7 integration + 3 commands + 4 build-config + 7 gui-state)

---

Expand Down
2 changes: 2 additions & 0 deletions emrg/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"preload.js",
"renderer/**",
"vendor/**",
"conn-manager.js",
"gui-state.js",
"daemon_client.js",
"package.json",
"scripts/**"
Expand Down
56 changes: 50 additions & 6 deletions emrg/gui/test/build-config.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
"use strict";
/**
* build-config.test.js — 打包配置守卫(rant 2026-08-10T11:03:51)。
* build-config.test.js — 打包配置守卫(rant 2026-08-10T11:03:51 + 2026-08-10T20:37:08)。
*
* 打包版 GUI Markdown 不渲染的根因:electron-builder `files` 白名单漏掉
* `vendor/**` → marked/DOMPurify/highlight 三个第三方脚本没进安装包 →
* renderMarkdown 静默降级纯文本(源码模式正常、打包版中招)。
* 打包版 GUI 故障的根因(两类同根):electron-builder `files` 白名单漏掉
* 运行时模块 → 打包后 require 失败 / 功能缺失:
* - #612:漏 `vendor/**` → marked/DOMPurify/highlight 没进包 → Markdown 静默降级
* - rant 20:37:08:漏 `conn-manager.js` / `gui-state.js` → main.js require 直接抛
* "Cannot find module './conn-manager'"(v0.2.21 Windows 打包版启动崩溃)
*
* 本测试直接钉死三类回归
* 本测试钉死四类回归
* 1. files 白名单必须包含 "vendor/**"(有人删掉即红)
* 2. vendor/ 目录必须存在全部 3 个运行时脚本(build-vendor.js 产物)
* 3. renderMarkdown 的 `!window.marked` 降级路径必须带 console.warn
* (静默降级 → 可诊断,问题可快速定位)
* 4. **main.js / preload.js 的每个本地 require("./x") 必须被 files 白名单覆盖**
* (新增本地模块而忘记加白名单 → 即红。conn-manager.js/gui-state.js 即此漏洞的实例)
*/
const { test } = require("node:test");
const assert = require("node:assert");
Expand All @@ -23,6 +26,18 @@ const VENDOR_DIR = path.join(GUI_ROOT, "vendor");

const REQUIRED_VENDOR_FILES = ["marked.min.js", "dompurify.min.js", "highlight.custom.js"];

/** 判断文件名是否被 electron-builder files 白名单覆盖(支持 "x.js" 与 "dir/**" 形态) */
function whitelistCovers(whitelist, relPath) {
for (const w of whitelist) {
if (w.endsWith("/**")) {
if (relPath.startsWith(w.slice(0, -3))) return true; // "renderer/**" covers "renderer/js/x.js"
} else if (relPath === w || relPath.startsWith(w + "/")) {
return true;
}
}
return false;
}

test("electron-builder files whitelist bundles vendor/**", () => {
const files = (PKG.build && PKG.build.files) || [];
assert.ok(
Expand All @@ -47,3 +62,32 @@ test("renderMarkdown fallback logs when marked is missing", () => {
"silent markdown degradation must be diagnostic (console.warn when marked is missing)"
);
});

test("every main.js/preload.js local require is covered by files whitelist", () => {
const files = (PKG.build && PKG.build.files) || [];
const localRequires = [];
for (const entry of ["main.js", "preload.js"]) {
const src = fs.readFileSync(path.join(GUI_ROOT, entry), "utf-8");
// 收集 require("./xxx") 与 require("./xxx.js") 形态
const re = /require\(\s*"(\.[^"]+)"\s*\)/g;
let m;
while ((m = re.exec(src)) !== null) {
let rel = m[1].replace(/^\.\//, "");
if (!rel.endsWith(".js") && fs.existsSync(path.join(GUI_ROOT, rel + ".js"))) {
rel += ".js";
}
localRequires.push({ entry, rel });
}
}
assert.ok(localRequires.length > 0, "no local requires found — scan is broken");
const uncovered = localRequires.filter(({ rel }) => !whitelistCovers(files, rel));
assert.deepStrictEqual(
uncovered.map((u) => `${u.entry} → ${u.rel}`),
[],
`local modules required by main.js/preload.js but MISSING from build.files whitelist: ` +
`add each to package.json build.files (e.g. "conn-manager.js", "gui-state.js"). ` +
`These files exist in the repo but would NOT be packed into app.asar → packaged GUI ` +
`crashes with "Cannot find module './<name>'"`
);
});

Loading