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
54 changes: 38 additions & 16 deletions bin/stop-emrg.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,32 @@ REM fallback), excludes the daemon (pythonw.exe -m emrg.server)
REM 3. daemon: `emrg server stop` protocol shutdown via the OLD install's CLI
REM (present since #364 -- version-safe), emrgd.pid poll (<=10s), then
REM taskkill /F /PID fallback
REM 4. bundled git (rant 2026-08-11T17:56:25): kill orphaned git/ssh/bash
REM processes whose executable lives under %INSTALL%\git\ (the portable Git
REM the daemon spawns for evolution-cycle git ops). When the daemon is killed
REM mid-operation these become orphans holding install\git\usr\bin\msys-2.0.dll
REM -> Inno "DeleteFile failed; code 5" on upgrade. Filter is by executable
REM PATH so system Git (C:\Program Files\Git) is never touched.
REM 4. bundled git: kill ONLY EMRG-owned git/ssh/bash subprocess trees under
REM %INSTALL%\git\ (portable Git the daemon spawns for evolution-cycle ops).
REM Rant 2026-08-11T17:56:25: orphans hold msys-2.0.dll -> Inno code 5.
REM Rant 2026-08-11T18:56:58: blanket path kill also hit the HOST's Git Bash
REM sh/vim -> only EMRG trees (ancestor = daemon/TUI) are killed; host tools
REM are never touched (same class as R125).
REM
REM Returns 0 when nothing EMRG-related survives; 1 if a process could not be
REM stopped (installer aborts with a clear message instead of hanging).
REM Safe on clean install: no old install dir -> everything is skipped -> 0.
setlocal enabledelayedexpansion
set "EMRG_DIR=%USERPROFILE%\.emrg"
set "INSTALL=%EMRG_DIR%\install"
REM PID snapshot file: EMRG-owned process tree captured BEFORE any kill, so dead
REM parents are irrelevant (review 2026-08-11T19:03: ancestor walk cannot resolve
REM dead daemon parents -- the #683 primary orphan case regresses).
set "PIDFILE=%TEMP%\emrg-stop-pids.txt"
del /q "%PIDFILE%" >nul 2>&1

REM --- 0. snapshot EMRG-owned tree (daemon + TUI + GUI + descendants) ---
REM Walk DOWN from the roots (emrgd.pid, EMRG.exe, python.exe -m emrg) while they
REM are still alive, collecting the full descendant PID set. Step 4 / :verify use
REM ONLY this recorded set -> host Git Bash sh/vim (not descendants of EMRG roots)
REM are never touched (rant 2026-08-11T18:56:58), and orphans of an already-dead
REM daemon are still captured (their PID was recorded while the daemon lived).
powershell -NoProfile -Command "$f=Get-CimInstance Win32_Process; $roots=New-Object System.Collections.Generic.List[int]; if(Test-Path \"%EMRG_DIR%\emrgd.pid\"){ $d=Get-Content \"%EMRG_DIR%\emrgd.pid\" -ErrorAction SilentlyContinue; if($d -match '^\d+$'){ $roots.Add([int]$d) } }; $f | Where-Object { $_.Name -eq 'EMRG.exe' -or ($_.Name -eq 'python.exe' -and $_.CommandLine -match '-m emrg' -and $_.CommandLine -notmatch 'emrg\.server') } | ForEach-Object { $roots.Add([int]$_.ProcessId) }; $set=New-Object 'System.Collections.Generic.HashSet[int]'; foreach($r in $roots){ [void]$set.Add($r) }; $changed=$true; while($changed){ $changed=$false; foreach($p in $f){ if($set.Contains([int]$p.ParentProcessId) -and -not $set.Contains([int]$p.ProcessId)){ [void]$set.Add([int]$p.ProcessId); $changed=$true } } }; $set | ForEach-Object { $_ } | Set-Content -Path \"%PIDFILE%\" -Encoding ascii" >nul 2>&1

REM --- 1. GUI: graceful WM_CLOSE, then unconditional /F after the grace window ---
REM (host report 2026-08-10T01:27:07Z: a long-lived ~15h GUI session deferred
Expand Down Expand Up @@ -67,12 +80,17 @@ set "DPID="
for /f "usebackq delims=" %%p in ("%EMRG_DIR%\emrgd.pid") do set "DPID=%%p"
if defined DPID taskkill /F /PID %DPID% >nul 2>&1

REM --- 4. bundled git: orphaned git/ssh/bash under %INSTALL%\git (msys-2.0.dll lock) ---
REM (host 2026-08-11T17:56:25: daemon killed mid-git-op leaves orphans holding
REM install\git\usr\bin\msys-2.0.dll -> Inno DeleteFile code 5. Filter by
REM ExecutablePath prefix so system Git is never touched. `\"` = PowerShell
REM quote escape precedent: TUI branch line 44.)
powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }" >nul 2>&1
REM --- 4. bundled git: kill only RECORDED EMRG-owned git/ssh/bash (snapshot step 0) ---
REM (rant 2026-08-11T17:56:25: daemon killed mid-git-op leaves orphans holding
REM install\git\usr\bin\msys-2.0.dll -> Inno DeleteFile code 5.)
REM (host 2026-08-11T18:56:58: blanket path kill also caught the HOST's Git Bash
REM sh/vim -- vim edits lost + installer aborted. Same class as R125.) Only PIDs
REM recorded in step 0 (EMRG tree captured while daemon/TUI were alive) are killed
REM when their executable is under install\git\. Host Git Bash sh/vim are not in
REM the recorded set -> never touched.
if exist "%PIDFILE%" (
powershell -NoProfile -Command "$f=Get-CimInstance Win32_Process; $ids=@(Get-Content \"%PIDFILE%\" | ForEach-Object { [int]$_ }); $f | Where-Object { $ids -contains [int]$_.ProcessId -and $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }" >nul 2>&1
)

:verify
set "EXIT_CODE=0"
Expand All @@ -86,8 +104,12 @@ if exist "%EMRG_DIR%\emrgd.pid" (
tasklist /FI "PID eq !DPID!" 2>nul | findstr /i "!DPID!" >nul && set "EXIT_CODE=1"
)
)
REM bundled-git survival check: any process still under install\git\ -> exit 1
REM (installer aborts with a clear message instead of a repeat "Try again" loop)
powershell -NoProfile -Command "if (Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" }) { exit 1 }" >nul 2>&1
if errorlevel 1 set "EXIT_CODE=1"
REM EMRG-owned bundled-git survival check: only RECORDED EMRG git PIDs still alive
REM -> exit 1 (installer aborts with a restart hint). Host Git Bash sh/vim alive is
REM NOT a failure (rant 2026-08-11T18:56:58).
if exist "%PIDFILE%" (
powershell -NoProfile -Command "$f=Get-CimInstance Win32_Process; $ids=@(Get-Content \"%PIDFILE%\" | ForEach-Object { [int]$_ }); $surv = $f | Where-Object { $ids -contains [int]$_.ProcessId -and $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" }; if ($surv) { exit 1 }" >nul 2>&1
if errorlevel 1 set "EXIT_CODE=1"
)
del /q "%PIDFILE%" >nul 2>&1
endlocal & exit /b %EXIT_CODE%
2 changes: 1 addition & 1 deletion emrg/server/evolution_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ When reading rants, follow these rules:
> - README fact fixes + trust copy (#677 宿主 rant 2026-08-11T17:28:02:竞品表 Codex 开源列 ❌→✅ *Apache-2.0*(此前误标 ❌——Codex 是开源但无自进化,差异化注:开源本身不是差异点,闭环进化才是);FAQ 新增安全边界条目 EN/CN(自进化只改 `~/.emrg/evolution/emrg`,绝不碰宿主项目文件;宿主项目上的工具调用仅宿主指令触发;每处改动都过 pytest + PR 评审);Quick Start 新增 BYOK 句 EN/CN(自带 API Key、额度/账单归宿主、软件 MIT 免费);README.md/README.cn.md 同步,MANIFESTO.md 零改动;合并 a499732) ✅
> - README contribution philosophy (#678 宿主 rant 2026-08-11T17:30:31:Rant-Driven Evolution 段(Real example 之后)EN/CN 对称新增"如何贡献?使用它。"——连接 GitHub + 吐槽 → 吐槽变成真实 PR(演化周期负责编码/测试/上线),无需 fork/clone/写代码,"使用 EMRG 就是在为 EMRG 做贡献";完成三段式叙事(产品经理 / 硅基生命宿主 / 使用即贡献);合并 35ca1c3) ✅
> - GUI packaged icon fix (#680 宿主 rant 2026-08-11T17:37:03:打包版曾用 Electron 默认蓝色原子球图标——package.json build.mac/win/linux.icon 从目录形 `../packaging/assets/` 改显式单文件(mac .icns / win .ico / linux .png,全部存在于 packaging/assets);运行时 `BrowserWindow icon` 经 `windowIconPath()`(打包版 extraResources 落 `resources/icon.png`,源码回退 `packaging/assets/icon.png`);fs/path 已导入零新增依赖;node --check 通过;合并 b400267) ✅
> - Windows installer kill bundled-git orphans (#683 宿主 rant 2026-08-11T17:56:25:Windows 升级安装卡 Inno "DeleteFile failed; code 5"——daemon 演化周期 git 操作中途被杀 → 孤儿 git/ssh/bash 进程锁 `install\git\usr\bin\msys-2.0.dll`,CloseApplications 看不见(非 EMRG 名);修复=stop-emrg.cmd step 4 按**可执行文件路径前缀**杀 bundled git 孤儿(`Get-CimInstance Win32_Process` + `ExecutablePath -like "$env:USERPROFILE\.emrg\install\git\*"`——只命中打包便携 Git,绝不误杀系统 Git C:\Program Files\Git),置于 daemon 停止后、:verify 前;:verify 存活判定exit 1 → 安装器中止给可操作消息(提示重启电脑);`\"` 引号转义沿用 TUI 分支先例;Get-CimInstance 兼容 Win11 24H2+(无 wmic 依赖);测试锚定判别信号;合并 c9131c6) ✅
> - Windows installer kill bundled-git orphans (#683 宿主 rant 2026-08-11T17:56:25,**18:56:58 修正方向**:Windows 升级安装卡 Inno "DeleteFile failed; code 5"——daemon 演化周期 git 操作中途被杀 → 孤儿 git/ssh/bash 进程锁 `install\git\usr\bin\msys-2.0.dll`;初版修复=stop-emrg.cmd step 4 按**可执行文件路径前缀**杀 bundled git 孤儿(`ExecutablePath -like "$env:USERPROFILE\.emrg\install\git\*"`),:verify 无差别存活判定 → exit 1 中止;**宿主实测 18:56:58 发现方向错误**:路径前缀会误杀宿主自己的 Git Bash sh/vim(也从 install\git 启动),vim 未存盘内容丢失 + verify 抓存活安装中止(R125 同族:Inno 不该干涉宿主工具);**修正**=step 4 只杀 **EMRG 自己的 git 子进程树**(`Get-CimInstance` + `ParentProcessId` 祖先回溯 ≤5 层,祖先含 daemon `pythonw -m emrg.server` / TUI `python -m emrg` 才杀;宿主 sh/vim 祖先链是 explorer/终端 → 不杀),:verify 存活判定同步改为"仅 EMRG 子树残留 → exit 1",宿主工具存活不再是失败;测试锚定判别信号(ParentProcessId + -m emrg + verify 无旧无差别检查);合并 c9131c6(#683)→ 修正 PR 待合并) ✅
> - README emoji restraint (#684 宿主 rant 2026-08-11T17:58:11:README 标题/特性表/流程图 emoji 泛滥——装饰性 emoji 从标题(# 🧱→#、## ✨→##、## 🔄→##、## 🚀→##、### 📦→###)与特性表行移除,EN/CN 对称;演化流程图改纯文本(Inputs / 演化循环 / 编号步骤);doc-count 新增标题无 emoji 守卫(`^#{1,3} ` 标题行禁 emoji,对比表 ✅/❌、底部 ❤️、语言切换 🇬🇧/🇨🇳 保留在正文非标题);MANIFESTO.md 零改动;合并 80a338a) ✅
> - Release v0.2.26 (#686 宿主 rant 2026-08-11T18:16:52:master 自 v0.2.25 (c8eb77a) 以来 11 commits / 11 PRs(#676-#686),宿主确认发布下一版本;版本号 0.2.25→0.2.26 同步 7 files / 8 refs(emrg/__init__.py、pyproject.toml、uv.lock、gui/package.json、gui/package-lock.json、build-runtime.sh、make-installer.sh),grep 0.2.25 仅历史;pytest 694 + GUI 212 绿;Build Release v0.2.26 成功,7 assets(含 Windows zip #649),latest=v0.2.26;合并 f2ba4be) ✅

Expand Down
37 changes: 27 additions & 10 deletions tests/test_installer_stop.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,26 @@ def test_stop_emrg_cmd_covers_gui_tui_daemon_in_order():
# 顺序:GUI 在 daemon 之前(GUI 不能复活 daemon)
daemon_line = content.index('call "%INSTALL%\\bin\\emrg.cmd" server stop')
assert content.index("taskkill /IM EMRG.exe") < daemon_line
# step 4(rant 2026-08-11T17:56:25):按可执行文件路径杀 bundled git 孤儿
# (daemon 演化周期 git 操作中途被杀 → git/ssh/bash 孤儿锁 msys-2.0.dll → Inno
# DeleteFile code 5)。判别:ExecutablePath 前缀过滤只命中 %INSTALL%\git\,
# 不误杀系统 Git;daemon 停止之后、:verify 之前。
# step 4(rant 2026-08-11T17:56:25 → 18:56:58 修正):只杀 EMRG 自己 git 子进程树,
# 不碰宿主 Git Bash sh/vim(R125 同族——宿主工具不干涉)。
# 判别:ExecutablePath 前缀过滤只命中 %INSTALL%\git\(不误杀系统 Git)。
# ⚡ review 2026-08-11T19:03:祖先回溯(ancestor walk)无法解析"已死父进程"——
# #683 主场景正是 daemon 已死后的孤儿 → 改为 step 0 **向下**快照
# (daemon/TUI/GUI 根 + BFS 子孙集,写入 %TEMP%\emrg-stop-pids.txt),
# step 4 / :verify 只作用于快照记录集。
assert 'Get-CimInstance Win32_Process' in content
assert r'$env:USERPROFILE\.emrg\install\git\*' in content
# 锚定实际 kill 命令($_.ExecutablePath 只在 step 4 出现——TUI 分支是
# -Filter Name='python.exe',不共享此模式),勿用 index() 撞到 TUI/REM 注释
git_kill = content.index("$_.ExecutablePath -like")
# step 0 快照:向下 BFS(ParentProcessId → 子进程加入集合)在杀任何进程之前
assert "emrg-stop-pids.txt" in content
assert "ParentProcessId" in content
assert "-match '-m emrg'" in content
assert "Set-Content" in content # 快照写盘
# step 4 的 kill 必须基于快照记录集($ids -contains)+ ExecutablePath 过滤
step4_idx = content.index("REM --- 4. bundled git: kill only RECORDED")
daemon_line2 = content.index('call "%INSTALL%\\bin\\emrg.cmd" server stop')
verify_idx = content.index(':verify\nset "EXIT_CODE=0"') # 标签定义处(goto :verify 在前,勿用裸 index(":verify"))
assert daemon_line2 < git_kill < verify_idx
assert daemon_line2 < step4_idx < verify_idx
assert "$ids -contains [int]$_.ProcessId" in content
# 干净安装安全:无旧 install 目录时跳过
assert 'set "INSTALL=%EMRG_DIR%\\install"' in content
# 括号块内 pid 判定必须用延迟展开(!DPID!)——%DPID% 在块解析时展开,
Expand All @@ -62,10 +70,19 @@ def test_stop_emrg_cmd_covers_gui_tui_daemon_in_order():
assert "PID eq !DPID!" in verify_block
# 非延迟展开 %DPID% 不得出现在括号块内(块解析时展开=恒旧值)
assert "%DPID%" not in verify_block
# :verify 的 bundled-git 存活判定:仍有进程在 install\git\ 下 → exit 1
# :verify 的 EMRG-owned bundled-git 存活判定:只有快照记录集内的 EMRG git PID 残留
# → exit 1;宿主 sh/vim 存活不是失败(rant 2026-08-11T18:56:58)——判别:verify 块
# 含 $ids -contains(快照集过滤),但不再有无差别 ExecutablePath 存活检查
assert "exit 1" in verify_block
assert r'ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\"' in verify_block
assert "if errorlevel 1 set \"EXIT_CODE=1\"" in verify_block
assert "$ids -contains" in verify_block
# 无差别 bundled-git 存活检查必须已删除:verify 块里不得再有"任何 install\git\ 进程
# 即 exit 1"的旧逻辑(旧式:if (Get-CimInstance ...) { exit 1 })
assert "}) { exit 1 }" not in verify_block
# 而 step 4 的 kill 命令必须保留(快照集过滤 + ExecutablePath)
assert "Stop-Process" in content
# 快照文件清理
assert 'del /q "%PIDFILE%"' in content


def test_emrgd_cmd_has_stop_branch():
Expand Down
Loading