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
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (988) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (958) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (260: 45 daemon_client + 19 conn-manager + 22 app-commands + 131 renderer smoke + 16 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`
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
31 changes: 0 additions & 31 deletions emrg/client/app.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -380,13 +380,6 @@ async def _reconnect():
import emrg
ver = getattr(emrg, "__version__", "dev")
chat.add("system", f"EMRG {ver} | {server_id}\nType /help for shortcuts, or just start chatting.")
# Auto update-check prompt (rant 2026-08-10T07:12:12):
# one-time, non-blocking — query daemon's cached latest
# release, show a status line, mark prompted (idempotent).
try:
await conn.send_command("update_check")
except Exception:
pass # never block chat on update check
status.update(left=_status_left(session_title, session_id, current_model), center=server_id)
term.set_title(f"{session_title or session_id} @ {project_name}")
term.render(); continue
Expand DownExpand Up@@ -1005,30 +998,6 @@ async def _reconnect():
term.render()
continue

# Memory content (read)
if data.get("type") == "update_check":
# Auto update-check prompt (rant 2026-08-10T07:12:12):
# one-time, non-blocking status line; idempotent per version.
if data.get("has_update") and data.get("enabled"):
latest = data.get("latest_version", "")
prompted = data.get("prompted_version", "")
if latest and latest != prompted:
import emrg
ver = getattr(emrg, "__version__", "dev")
chat.add("system",
f"New version v{latest} available (current v{ver}) — "
f"https://github.com/argszero/emrg/releases")
status.update(center=server_id or "emrg")
try:
await conn.send_command(
"update_check_prompted",
{"version": latest},
)
except Exception:
pass
term.render()
continue

if data.get("type") == "memory_content":
err = data.get("error", "")
if err:
Expand Down
41 changes: 19 additions & 22 deletions emrg/config.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,21 +39,21 @@ class LlmConfig:

@dataclass
class UpdateConfig:
"""Auto update-check settings (rants 2026-08-10T07:12:12, 2026-08-12T12:10:12).

check: master switch — when false, the daemon never queries GitHub
(checking, downloading and prompting are all disabled).
ttl_hours: how often to re-check the latest release (default 1h;
rant 2026-08-12T12:10:12: host 指示 24h → 1h).
auto_download: when a newer version exists, download the current
platform's installer into ~/.emrg/updates/ in the background
(stream + Range resume + SHA256 verify). Installation is ALWAYS
user-initiated — never auto-installed.
"""Auto-upgrade settings (rant 2026-08-20T12:33:59 — 自动升级重构).

enabled: master switch — when false, the daemon never checks for new
releases and never triggers an upgrade session.
delay_minutes: how long after a release is published before it becomes
eligible for upgrade (default 1440 = 1 day; the host can set 1 for
immediate). Granularity is minutes (host chose A). The check
interval is NOT configurable — hard-coded 5 minutes in upgrade.py.
Old fields check / ttl_hours / auto_download are removed (the
download-installer mechanism is fully replaced by the agent-driven
local equivalent install).
"""

check: bool = True
ttl_hours: int = 1
auto_download: bool = True
enabled: bool = True
delay_minutes: int = 1440


@dataclass
Expand DownExpand Up@@ -114,9 +114,8 @@ def load_config() -> EmrgConfig:

update_data = data.get("update", {})
update = UpdateConfig(
check=update_data.get("check", True),
ttl_hours=update_data.get("ttl_hours", 1),
auto_download=update_data.get("auto_download", True),
enabled=update_data.get("enabled", True),
delay_minutes=update_data.get("delay_minutes", 1440),
)

return EmrgConfig(llm=llm, update=update)
Expand All@@ -125,9 +124,8 @@ def load_config() -> EmrgConfig:
def load_update_config() -> UpdateConfig:
"""Load only the [update] section (rant 2026-08-10T07:12:12).

The daemon is constructed with just LlmConfig; this helper lets it read
the update-check switch without parsing the full config. Missing config
file or missing section → defaults (check=True, ttl=1h, auto_download=True).
The daemon constructs the UpgradeManager from this helper. Missing config
file or missing section → defaults (enabled=True, delay_minutes=1440).
"""
cfg_path = config_path()
if not cfg_path.exists():
Expand All@@ -138,9 +136,8 @@ def load_update_config() -> UpdateConfig:
return UpdateConfig()
update_data = data.get("update", {})
return UpdateConfig(
check=update_data.get("check", True),
ttl_hours=update_data.get("ttl_hours", 1),
auto_download=update_data.get("auto_download", True),
enabled=update_data.get("enabled", True),
delay_minutes=update_data.get("delay_minutes", 1440),
)


Expand Down
70 changes: 0 additions & 70 deletions emrg/gui/main.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -710,76 +710,6 @@ vision = false
return { authenticated: Boolean(frame.authenticated), user: frame.user || null };
});

ipcMain.handle("emrg:updateCheck", async (_e, { force } = {}) => {
// Auto update-check (rant 2026-08-10T07:12:12): query daemon's cached
// latest release; force=true (rant 2026-08-11T09:18:16): settings
// manual check button — daemon runs a fresh GitHub fetch.
// rant 2026-08-12T12:10:12: response also carries the auto-download
// state (downloaded_version/path/sha) for the "ready to install" UI.
try {
const frame = await requireConn().sendCommandAndWait(
"update_check",
{ force: Boolean(force) },
10000,
);
return {
current_version: frame.current_version || "",
latest_version: frame.latest_version || "",
has_update: Boolean(frame.has_update),
prompted_version: frame.prompted_version || "",
downloaded_version: frame.downloaded_version || "",
downloaded_path: frame.downloaded_path || "",
downloaded_sha: frame.downloaded_sha || "",
enabled: Boolean(frame.enabled),
};
} catch {
return { has_update: false, enabled: false, latest_version: "", current_version: "" };
}
});

ipcMain.handle("emrg:updateInstall", async (_e, { path: p, version } = {}) => {
// Auto-update one-click install (rant 2026-08-12T12:10:12): the daemon
// already downloaded + SHA256-verified the installer into
// ~/.emrg/updates/. The user clicked "install" — launch the installer,
// then quit EMRG (the installer stops any remaining EMRG processes
// itself — stop-emrg.cmd / pkg install logic). Install is ALWAYS
// user-initiated; the download itself was automatic.
try {
if (!p || typeof p !== "string") return { ok: false, error: "missing path" };
if (!fs.existsSync(p)) return { ok: false, error: "downloaded installer not found" };
const platform = process.platform;
if (platform === "win32") {
// PrivilegesRequired=lowest → no UAC prompt for the exe itself.
const child = spawn(p, [], { detached: true, stdio: "ignore", windowsHide: true });
child.unref();
} else if (platform === "darwin") {
// `open <pkg>` mounts + launches the Installer.app flow.
const child = spawn("open", [p], { detached: true, stdio: "ignore" });
child.unref();
} else if (platform === "linux") {
fs.chmodSync(p, 0o755);
const child = spawn(p, [], { detached: true, stdio: "ignore" });
child.unref();
} else {
return { ok: false, error: `unsupported platform: ${platform}` };
}
// Let the IPC reply flush, then close the GUI — the installer takes over.
setTimeout(() => { try { app.quit(); } catch { /* ignore */ } }, 500);
return { ok: true, version: String(version || "") };
} catch (err) {
return { ok: false, error: String((err && err.message) || err) };
}
});

ipcMain.handle("emrg:updateCheckPrompted", async (_e, { version }) => {
// Idempotency (rant 07:12:12 §4): record that the GUI showed the prompt
// for this version — same version never re-prompted.
try {
await requireConn().sendCommandAndWait("update_check_prompted", { version: String(version || "") }, 5000);
} catch { /* best-effort */ }
return { ok: true };
});

ipcMain.handle("emrg:githubConnect", async (_e, { token }) => {
// Windows GCM rant Stage 2:PAT 授权 + setup-git(daemon github_connect)
const frame = await requireConn().sendCommandAndWait("github_connect", { token: String(token || "").trim() }, 40000);
Expand Down
3 changes: 0 additions & 3 deletions emrg/gui/preload.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,9 +48,6 @@ const api = {
listRants: (payload) => ipcRenderer.invoke("emrg:listRants", payload),
evolutionSummary: (payload) => ipcRenderer.invoke("emrg:evolutionSummary", payload),
githubStatus: () => ipcRenderer.invoke("emrg:githubStatus"),
updateCheck: (payload) => ipcRenderer.invoke("emrg:updateCheck", payload),
updateCheckPrompted: (payload) => ipcRenderer.invoke("emrg:updateCheckPrompted", payload),
updateInstall: (payload) => ipcRenderer.invoke("emrg:updateInstall", payload),
githubConnect: (payload) => ipcRenderer.invoke("emrg:githubConnect", payload),
githubDisconnect: () => ipcRenderer.invoke("emrg:githubDisconnect"),
githubConnectWeb: () => ipcRenderer.invoke("emrg:githubConnectWeb"),
Expand Down
4 changes: 0 additions & 4 deletions emrg/gui/renderer/index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -253,10 +253,6 @@ <h2 class="workspace-view-title" data-i18n="settings.title">设置</h2>
<div class="settings-group">
<div class="about-box">
<div class="about-line">EMRG <span id="about-version">v0.2.8</span> · <span id="about-evolutions">🌱 <span data-i18n="copy.growthCountPrefix">已自我进化</span> 0 <span data-i18n="copy.times">次</span></span></div>
<div class="about-line hidden" id="about-update"></div>
<div class="about-line">
<button type="button" class="btn btn-sm" id="about-update-check-btn" data-i18n="settings.checkUpdate">检查更新</button>
</div>
<div class="hint" style="margin-top:6px;" data-i18n="settings.aboutDesc">EMRG 是一个会自我进化的 AI 智能体——每次改进都会自动汇报,你可以随时在这里看到它的成长。</div>
</div>
</div>
Expand Down
12 changes: 0 additions & 12 deletions emrg/gui/renderer/js/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,11 +124,6 @@ const App = (() => {
// 修复:boot 成功路径必须启用输入框(此前仅 done/cancelled/disconnected/error
// 回调会调用 setComposerDisabled(false),形成"需先发消息才能启用输入框"死锁)
setComposerDisabled(false);
// rant 2026-08-11T09:18:16:启动主动更新提示——不开设置也能看到新版本
// (非阻塞系统消息,一次幂等;daemon 未就绪时静默失败)
if (window.EMRG_Dialogs?.promptUpdateAtStartup) {
Dialogs.promptUpdateAtStartup();
}
} catch (e) {
Chat.addSystemMessage(_t("app.bootFailed", { msg: e.message }));
}
Expand DownExpand Up@@ -1509,13 +1504,6 @@ const App = (() => {
}
}
break;
case "update_downloaded":
// rant 2026-08-12T12:10:12:daemon 后台自动下载 + 校验完新安装包 →
// 非阻塞提示"已就绪,点击安装"(设置 → 关于显示安装按钮)
Chat.addSystemMessage(
_t("app.updateReady", { latest: data.downloaded_version || "" }),
);
break;
case "group_cleared":
Chat.groupNodes.delete(data.requestId);
break;
Expand Down
Loading
Loading