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 number Diff line number Diff 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` (243: 44 daemon_client + 19 conn-manager + 22 app-commands + 118 renderer smoke + 16 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state + 2 tool-group); RESPONSE_TYPES mirror daemon protocol verified against `daemon.py`
- Unit tests `npm test` (244: 44 daemon_client + 19 conn-manager + 22 app-commands + 119 renderer smoke + 16 i18n + 7 integration + 3 commands + 5 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 Down Expand Up @@ -119,7 +119,7 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (801) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (243: 44 daemon_client + 19 conn-manager + 22 app-commands + 118 renderer smoke + 16 i18n + 7 integration + 3 commands + 5 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` (244: 44 daemon_client + 19 conn-manager + 22 app-commands + 119 renderer smoke + 16 i18n + 7 integration + 3 commands + 5 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
83 changes: 83 additions & 0 deletions emrg/gui/renderer/css/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,89 @@ dialog::backdrop {
padding: 0 4px;
flex-shrink: 0;
}
/* rant 21:36:01:Rant 面板 5 列 + 状态徽标三态配色(已完成绿 / 进行中琥珀 / 待处理灰) */
.rant-head {
display: flex;
gap: 8px;
padding: 2px 8px 6px;
font-size: var(--fs-aux);
font-weight: 600;
color: var(--text-3);
}
.rant-head .rant-col-time,
.rant-row .rant-col-time {
flex: 0 0 150px;
}
.rant-head .rant-col-project,
.rant-row .rant-col-project {
flex: 0 0 90px;
}
.rant-head .rant-col-status,
.rant-row .rant-col-status {
flex: 0 0 76px;
}
.rant-head .rant-col-progress,
.rant-row .rant-col-progress {
flex: 1.2;
min-width: 0;
}
.rant-head .rant-col-content,
.rant-row .rant-col-content {
flex: 2;
min-width: 0;
}
.rant-head span,
.rant-row .rant-col-time,
.rant-row .rant-col-project,
.rant-row .rant-col-progress,
.rant-row .rant-col-content {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rant-row .rant-col-time,
.rant-row .rant-col-project,
.rant-row .rant-col-progress,
.rant-row .rant-col-content {
font-size: var(--fs-aux);
color: var(--text-2);
}
.task-badge.badge-done {
color: #16a34a;
border-color: #16a34a;
background: color-mix(in srgb, #16a34a 10%, transparent);
}
.task-badge.badge-warn {
color: #d97706;
border-color: #d97706;
background: color-mix(in srgb, #d97706 10%, transparent);
}
.task-badge.badge-muted {
color: var(--text-3);
border-color: var(--border);
background: transparent;
}
.rant-detail {
padding: 6px 8px;
border-top: 1px solid var(--border);
font-size: var(--fs-secondary);
}
.rant-meta {
font-size: var(--fs-aux);
color: var(--text-3);
margin-bottom: 4px;
}
.rant-md {
margin-top: 4px;
}
.rant-progress {
margin-top: 8px;
padding-top: 6px;
border-top: 1px dashed var(--border);
font-size: var(--fs-aux);
color: var(--text-3);
word-break: break-all;
}
.task-actions {
display: flex;
gap: 4px;
Expand Down
11 changes: 10 additions & 1 deletion emrg/gui/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<section class="workspace-view" id="panel-projects" data-view="projects">
<div class="workspace-view-body">
<!-- rant 14:10:14 P5:项目面板(复用 listProjects/listProjectSessions/registerProject/removeProject IPC) -->
<div class="hint" style="margin-bottom:6px;" data-i18n="projects.hint">查看会话 / 添加项目 / 删除(保留磁盘数据)</div>
<h2 class="workspace-view-title" data-i18n="projects.title">项目管理</h2>
<div id="project-list" class="task-list"></div>
<div style="display:flex;gap:8px;margin-top:8px;">
<button type="button" id="project-add-btn" class="btn btn-ghost" style="flex:1;" data-i18n="projects.add">+ 添加项目</button>
Expand All @@ -71,6 +71,7 @@
<section class="workspace-view" id="panel-tasks" data-view="tasks">
<div class="workspace-view-body">
<!-- rant 14:10:14 P3:任务管理从设置迁出(替代 settings 内任务区 + tasks-dialog) -->
<h2 class="workspace-view-title" data-i18n="tasks.title">任务管理</h2>
<div class="hint" style="margin-bottom:6px;" data-i18n="settings.tasksHint">管理定时任务与自定义任务类型(内置类型只读)</div>
<div id="task-list" class="task-list"></div>
<div style="display:flex;gap:8px;margin-top:8px;">
Expand Down Expand Up @@ -130,12 +131,20 @@
<section class="workspace-view" id="panel-rants" data-view="rants">
<div class="workspace-view-body">
<!-- rant 14:10:14 P4:rant 面板(列表筛选 + 详情 + 新建) -->
<h2 class="workspace-view-title" data-i18n="rants.title">Rant 管理</h2>
<div class="panel-tabs" id="rant-filter-tabs" role="tablist" style="margin-bottom:var(--sp-2);">
<button type="button" class="panel-tab active" id="rant-filter-all" data-rant-filter="" data-i18n="rants.filterAll">全部</button>
<button type="button" class="panel-tab" id="rant-filter-pending" data-rant-filter="pending" data-i18n="rants.filterPending">待处理</button>
<button type="button" class="panel-tab" id="rant-filter-inprogress" data-rant-filter="in_progress" data-i18n="rants.filterInProgress">进行中</button>
<button type="button" class="panel-tab" id="rant-filter-completed" data-rant-filter="completed" data-i18n="rants.filterCompleted">已完成</button>
</div>
<div class="rant-head">
<span class="rant-col-time" data-i18n="rants.colTime">时间</span>
<span class="rant-col-project" data-i18n="rants.colProject">项目</span>
<span class="rant-col-status" data-i18n="rants.colStatus">状态</span>
<span class="rant-col-progress" data-i18n="rants.colProgress">进度</span>
<span class="rant-col-content" data-i18n="rants.colContent">内容</span>
</div>
<div id="rant-list" class="task-list"></div>
<div style="display:flex;gap:8px;margin-top:8px;">
<button type="button" id="rant-new-btn" class="btn btn-ghost" style="flex:1;" data-i18n="rants.new">+ 新建 Rant</button>
Expand Down
41 changes: 27 additions & 14 deletions emrg/gui/renderer/js/dialogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1065,27 +1065,40 @@ const Dialogs = (() => {
return;
}
for (const r of rants) {
const row = el("div", { class: "task-row" });
// rant 21:36:01:5 列对齐(时间/项目/状态/进度/内容),与 .rant-head 列头同列宽类
const row = el("div", { class: "task-row rant-row" });
const ts = String(r.timestamp || "").slice(0, 16).replace("T", " ");
row.appendChild(el("span", { class: "task-name" }, ts));
// 状态徽标
row.appendChild(el("span", { class: "rant-col-time" }, ts || "—"));
row.appendChild(el("span", { class: "rant-col-project" }, r.project || "—"));
// 状态徽标三态配色(completed 绿 / in_progress 琥珀 / pending 灰)
const st = r.status || "pending";
const stText = st === "completed" ? _t("rants.statusCompleted") : st === "in_progress" ? _t("rants.statusInProgress") : _t("rants.statusPending");
row.appendChild(el("span", { class: `task-badge ${st === "completed" ? "badge-done" : ""}` }, stText));
// 项目 + 进度摘要
const hints = [];
if (r.project) hints.push(r.project);
if (r.progress) hints.push(String(r.progress).slice(0, 40) + (String(r.progress).length > 40 ? "…" : ""));
row.appendChild(el("span", { class: "task-hint" }, hints.join(" · ") || "—"));
// 详情展开(完整内容 + progress)
const badgeCls = st === "completed" ? "badge-done" : st === "in_progress" ? "badge-warn" : "badge-muted";
row.appendChild(el("span", { class: `task-badge ${badgeCls}` }, stText));
row.appendChild(el("span", { class: "rant-col-progress" }, r.progress ? String(r.progress) : "—"));
// 内容列:message 首行摘要(去 md 标题/列表标记)
const msg = r.message || "";
row.addEventListener("click", () => {
const firstLine = (msg.split("\n").find((l) => l.trim() !== "") || "").replace(/^#{1,6}\s+/, "").replace(/^[>*\-\s]+/, "");
row.appendChild(el("span", { class: "rant-col-content" }, firstLine || "—"));
// 详情展开(rant 21:36:01:完整 message 按 markdown 渲染 + 进度显示)
row.addEventListener("click", async () => {
const detail = list.querySelector(".rant-detail");
if (detail) detail.remove();
const detailRow = el("div", { class: "rant-detail", style: "padding:6px 8px;border-top:1px solid var(--border);font-size:var(--fs-secondary);" });
detailRow.appendChild(el("div", {}, `${_t("rants.detail")}: ${msg}`));
if (r.progress) detailRow.appendChild(el("div", {}, `${_t("rants.statusInProgress")}: ${r.progress}`));
else detailRow.appendChild(el("div", {}, _t("rants.noProgress")));
detailRow.appendChild(el("div", { class: "rant-meta" }, `${ts || "—"} · ${r.project || "—"} · ${stText}`));
const md = el("div", { class: "msg-body rant-md" });
try {
if (window.emrgMarkdown && window.emrgMarkdown.renderMarkdown) {
md.innerHTML = await window.emrgMarkdown.renderMarkdown(msg);
} else {
md.textContent = msg;
}
} catch {
md.textContent = msg;
}
detailRow.appendChild(md);
if (r.progress) detailRow.appendChild(el("div", { class: "rant-progress" }, `${_t("rants.statusInProgress")}: ${r.progress}`));
else detailRow.appendChild(el("div", { class: "rant-progress" }, _t("rants.noProgress")));
row.after(detailRow);
});
list.appendChild(row);
Expand Down
29 changes: 24 additions & 5 deletions emrg/gui/renderer/js/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const I18N = (() => {
"nav.settings": "设置",

// 项目面板(rant 14:10:14 P5)
"projects.hint": "查看会话 / 添加项目 / 删除(保留磁盘数据)",
"projects.title": "项目管理",
"projects.add": "+ 添加项目",
"projects.empty": "暂无项目(点击「添加项目」注册工作目录)",
"projects.added": "项目已添加:{path}",
Expand Down Expand Up @@ -91,6 +91,7 @@ const I18N = (() => {
"settings.aboutTitle": "关于",
"settings.aboutDesc": "EMRG 是一个会自我进化的 AI 智能体——每次改进都会自动汇报,你可以随时在这里看到它的成长。",
"settings.recentTitle": "最近改进",
"tasks.title": "任务管理",
"settings.tasksHint": "管理定时任务与自定义任务类型(内置类型只读)",
"settings.taskAdd": "+ 添加任务",
"settings.taskTemplates": "自定义类型",
Expand Down Expand Up @@ -241,6 +242,12 @@ const I18N = (() => {
// 进化 / 任务

// Rant 面板(rant 14:10:14 P4)
"rants.title": "Rant 管理",
"rants.colTime": "时间",
"rants.colProject": "项目",
"rants.colStatus": "状态",
"rants.colProgress": "进度",
"rants.colContent": "内容",
"rants.filterAll": "全部",
"rants.filterPending": "待处理",
"rants.filterInProgress": "进行中",
Expand All @@ -258,7 +265,6 @@ const I18N = (() => {
"rants.statusPending": "待处理",
"rants.statusInProgress": "进行中",
"rants.statusCompleted": "已完成",
"rants.detail": "完整内容",
"rants.noProgress": "(无进度说明)",

// / 指令 hint(commands.js 补全菜单)
Expand Down Expand Up @@ -440,7 +446,7 @@ const I18N = (() => {
"nav.settings": "Settings",

// Projects panel (rant 14:10:14 P5)
"projects.hint": "View sessions / add / remove (disk data kept)",
"projects.title": "Project Management",
"projects.add": "+ Add project",
"projects.empty": "No projects yet (click “Add project” to register a working directory)",
"projects.added": "Project added: {path}",
Expand Down Expand Up @@ -499,6 +505,7 @@ const I18N = (() => {
"settings.aboutTitle": "About",
"settings.aboutDesc": "EMRG is a self-evolving AI agent — it reports every improvement, and you can watch it grow right here.",
"settings.recentTitle": "Recent improvements",
"tasks.title": "Task Management",
"settings.tasksHint": "Manage scheduled tasks and custom task types (builtin types are read-only)",
"settings.taskAdd": "+ Add task",
"settings.taskTemplates": "Custom types",
Expand Down Expand Up @@ -649,6 +656,12 @@ const I18N = (() => {
// Evolution / tasks

// Rant panel (rant 14:10:14 P4)
"rants.title": "Rant Management",
"rants.colTime": "Time",
"rants.colProject": "Project",
"rants.colStatus": "Status",
"rants.colProgress": "Progress",
"rants.colContent": "Content",
"rants.filterAll": "All",
"rants.filterPending": "Pending",
"rants.filterInProgress": "In progress",
Expand All @@ -666,7 +679,6 @@ const I18N = (() => {
"rants.statusPending": "Pending",
"rants.statusInProgress": "In progress",
"rants.statusCompleted": "Completed",
"rants.detail": "Full content",
"rants.noProgress": "(no progress note)",

// / command hints
Expand Down Expand Up @@ -877,7 +889,14 @@ const I18N = (() => {
if (typeof document === "undefined" || !document.querySelectorAll) return;
document.querySelectorAll("[data-i18n]").forEach((node) => {
const key = node.getAttribute("data-i18n");
if (key) node.textContent = t(key);
if (!key) return;
const text = t(key);
// rant 21:46:53:只替换首个文本节点,保留 label 内的子元素控件(input/select/textarea)
// ——整体 textContent 赋值会清空含控件 label(如 <label data-i18n>任务名 <input/></label>)的子元素,
// 导致任务/模板表单控件消失、「添加任务」点击无响应。
const first = node.firstChild;
if (first && first.nodeType === 3) first.nodeValue = text;
else node.textContent = text;
});
document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => {
const key = node.getAttribute("data-i18n-placeholder");
Expand Down
Loading
Loading