概述
让生成产物 的两条本地启动路径——wizard“一键启动”与产物自带的 <显示名>.{bat,sh}——在所有平台上都把产物 server 作为真正脱离启动器的静默后台进程 运行,并提供统一、干净的停止手段 (PID 文件 + <slug> stop)。
直接动因:当前 wizard 一键启动用 subprocess.Popen(..., start_new_session=True) 派生产物,而 start_new_session 是 POSIX-only ;在 Windows 上它被忽略,产物继承 wizard 的控制台与进程组 → 关掉 wizard 的 bat 窗口(CTRL_CLOSE_EVENT)或对 wizard 按 Ctrl-C 会连带杀掉本应独立存活的产物 。
纯生成器侧 + 产物启动脚本的改动,不改 HarnessSpec、不给产物加运行期依赖 ;产物“生成即脱离”定位不变。
动机
Windows 脱离失效 :wizard/app.py::_launch_product 仅用 start_new_session=True,Windows 无效 → 一键产物与 wizard 同生共死,违背“产物 outlive 临时配置工具”的预期。
两条路径表现不一致 :
一键(wizard):语义上应是后台,但 Windows 下其实没脱离;
产物 bat:前台占用双击出来的控制台窗口,关窗口 / Ctrl-C 即停。
静默后台缺管理手段 :真脱离后没有窗口,只能任务管理器 / taskkill,不够干净。
目标:两条路径统一为“静默后台 + 一个明确的停止入口 ”,跨平台一致。
方案概要
跨平台 detach :把脱离所需的 Popen 参数按平台拆开成 _detach_kwargs(platform)
POSIX:start_new_session=True(setsid,脱离终端会话)。
Windows:creationflags = DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP(无控制台 → CTRL_CLOSE_EVENT 到不了;独立进程组 → Ctrl-C 不波及)。常量用 getattr(subprocess, ..., 0x8/0x200) 兜底,便于在 Linux 上单测。
产物输出已重定向到 .serve.log,无控制台不影响日志。
产物记录 PID :产物 serve 在自身进程内写 .serve.pid、退出删,供停止 / 查活。
统一停止 :新增 <slug> stop(读 PID → 终止 → 清理 stale,跨平台);产物 <显示名>.{bat,sh} 改为静默后台拉起 + 打印 URL 与停止提示(可选再渲染一个 Stop <显示名> 脚本)。
结果 :wizard 一键 与 产物脚本 两条路径都 = 静默后台 server,均可用 <slug> stop 收尸。
flowchart LR
Wizard["wizard 一键<br/>_launch_product"] -->|"detach kwargs"| Serve
Bat["产物启动脚本<br/>launch_name.bat / .sh"] -->|"静默后台"| Serve
Serve["产物 serve<br/>后台 / 无窗口"] --> Pid[".serve.pid"]
Serve --> Log[".serve.log"]
Stop["slug stop / Stop 脚本"] -->|"读 PID 终止"| Serve
Loading
交付物
生成器侧(harnessmith/)
wizard/app.py:新增 _detach_kwargs(platform) helper;_launch_product 用 **_detach_kwargs() 替换 start_new_session=True;更新相关 docstring / _LAUNCHED 注释为跨平台准确措辞。
templates/src/__project_slug__/interfaces/cli.py.j2:serve 启动写 / 退出删 .serve.pid;新增 stop 子命令。
templates/__launch_name__.bat.j2 / __launch_name__.sh.j2:改为静默后台启动 + 停止提示;(可选)新增 Stop 脚本模板。
生成产物侧(渲染后)
<pkg> serve 写 .serve.pid;<pkg> stop 停止后台 server。
<显示名>.{bat,sh} 双击 = 后台起 server + 提示停止方式。
README.md / AGENTS.md:说明后台运行与停止方式。
任务拆解
验收标准(退出门禁)
需人审决策(命中 CLAUDE.md §6)
本方案刻意不命中 §6.1 / §6.2(不改 schema、不给产物加运行期依赖)。
§6.10 :取舍影响 wizard 与产物启动脚本两处,设计已在讨论中定档为“统一静默后台 + PID/stop”。
人审手验 :Windows 上 detach 真效(CI 无法 headless 验证 Windows 控制台事件)。
非目标 / 后续
Windows 系统托盘(tray) :讨论过用托盘图标作为后台 server 的可见 / 可控入口。本片不做 ——要覆盖产物 bat 路径,托盘代码必须进产物仓库 → 产物吃 Windows 桌面 GUI 依赖(pystray / Pillow,或 300+ 行 ctypes Win32),命中 §6.1(spec 开关)/ §6.2(产物运行期依赖)/ §6.8(体积),且 GUI 核心 CI 无法 headless 测,与“薄 + 产物独立 + 跨平台 + 黄金可测”定位冲突。若要,另立带 spec 开关、默认关、Windows-only 的独立 slice。
不引入任何 agent 编排框架。
不改默认薄产物形态(除新增 .serve.pid + stop 命令的轻量管理)。
风险
Windows 不可 headless 自测 :DETACHED_PROCESS / 控制台事件行为只能靠 Windows 手验;CI 仅覆盖参数映射与透传。
uv run 派生层级 :产物经 uv run <slug> serve 派生,真正 server 是其孙进程;PID 文件应记录真正 server 进程 的 PID(在产物进程内写),stop 才能准确终止,而非误杀 uv 包装层。
静默后台的可发现性 :无窗口 → 必须靠 .serve.log + stop / Stop 脚本提供可见与可控;中英文案要清楚。
参考
相关代码:harnessmith/wizard/app.py(_launch_product)、harnessmith/templates/src/__project_slug__/interfaces/cli.py.j2(serve)、harnessmith/templates/__launch_name__.{bat,sh}.j2、根 HarnessSmith.{bat,sh}。
slice 7 向导:docs/02-development/08-slice-7-wizard.md(§跨平台启动健壮性)。
开发总览与切片门禁:docs/02-development/00-overview.md。
概述
让生成产物的两条本地启动路径——wizard“一键启动”与产物自带的
<显示名>.{bat,sh}——在所有平台上都把产物 server 作为真正脱离启动器的静默后台进程运行,并提供统一、干净的停止手段(PID 文件 +<slug> stop)。直接动因:当前 wizard 一键启动用
subprocess.Popen(..., start_new_session=True)派生产物,而start_new_session是 POSIX-only;在 Windows 上它被忽略,产物继承 wizard 的控制台与进程组 → 关掉 wizard 的 bat 窗口(CTRL_CLOSE_EVENT)或对 wizard 按Ctrl-C会连带杀掉本应独立存活的产物。动机
wizard/app.py::_launch_product仅用start_new_session=True,Windows 无效 → 一键产物与 wizard 同生共死,违背“产物 outlive 临时配置工具”的预期。Ctrl-C即停。taskkill,不够干净。目标:两条路径统一为“静默后台 + 一个明确的停止入口”,跨平台一致。
方案概要
Popen参数按平台拆开成_detach_kwargs(platform)start_new_session=True(setsid,脱离终端会话)。creationflags = DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP(无控制台 →CTRL_CLOSE_EVENT到不了;独立进程组 →Ctrl-C不波及)。常量用getattr(subprocess, ..., 0x8/0x200)兜底,便于在 Linux 上单测。.serve.log,无控制台不影响日志。serve在自身进程内写.serve.pid、退出删,供停止 / 查活。<slug> stop(读 PID → 终止 → 清理 stale,跨平台);产物<显示名>.{bat,sh}改为静默后台拉起 + 打印 URL 与停止提示(可选再渲染一个Stop <显示名>脚本)。<slug> stop收尸。交付物
生成器侧(
harnessmith/)wizard/app.py:新增_detach_kwargs(platform)helper;_launch_product用**_detach_kwargs()替换start_new_session=True;更新相关 docstring /_LAUNCHED注释为跨平台准确措辞。templates/src/__project_slug__/interfaces/cli.py.j2:serve启动写 / 退出删.serve.pid;新增stop子命令。templates/__launch_name__.bat.j2/__launch_name__.sh.j2:改为静默后台启动 + 停止提示;(可选)新增 Stop 脚本模板。生成产物侧(渲染后)
<pkg> serve写.serve.pid;<pkg> stop停止后台 server。<显示名>.{bat,sh}双击 = 后台起 server + 提示停止方式。README.md/AGENTS.md:说明后台运行与停止方式。任务拆解
_detach_kwargs+_launch_product接入(Windows detach 修复)。serve的.serve.pid写入 / 清理(记录真正 server 进程的 PID)。stop命令(跨平台、处理 stale PID)。__launch_name__.{bat,sh}改静默后台 + 停止提示(可选 Stop 脚本)。README/AGENTS+ slice 7 文档同步。验收标准(退出门禁)
_detach_kwargs("win32")含DETACHED_PROCESS(0x8)+CREATE_NEW_PROCESS_GROUP(0x200)、不含start_new_session;_detach_kwargs("linux")=={"start_new_session": True};_launch_product把 detach 参数透传给Popen(monkeypatch 捕获)。uv sync && pytest(含 PID /stop单测)全绿;mock 跑通一轮 function-calling;pyproject.toml不含langchain/langgraph/adk。serve起后存在.serve.pid、退出清理;stop能终止、对 stale / 缺失 PID 不报错。ReadLintsclean。HarnessSmith.bat→ 一键启动 → 产物 web 起来 → 关 wizard 黑窗 → 浏览器仍可用;双击产物 bat → 后台起 server →<slug> stop/ Stop 脚本能停。需人审决策(命中
CLAUDE.md §6)非目标 / 后续
pystray/Pillow,或 300+ 行ctypesWin32),命中 §6.1(spec 开关)/ §6.2(产物运行期依赖)/ §6.8(体积),且 GUI 核心 CI 无法 headless 测,与“薄 + 产物独立 + 跨平台 + 黄金可测”定位冲突。若要,另立带spec开关、默认关、Windows-only 的独立 slice。.serve.pid+stop命令的轻量管理)。风险
DETACHED_PROCESS/ 控制台事件行为只能靠 Windows 手验;CI 仅覆盖参数映射与透传。uv run派生层级:产物经uv run <slug> serve派生,真正 server 是其孙进程;PID 文件应记录真正 server 进程的 PID(在产物进程内写),stop才能准确终止,而非误杀uv包装层。.serve.log+stop/ Stop 脚本提供可见与可控;中英文案要清楚。参考
harnessmith/wizard/app.py(_launch_product)、harnessmith/templates/src/__project_slug__/interfaces/cli.py.j2(serve)、harnessmith/templates/__launch_name__.{bat,sh}.j2、根HarnessSmith.{bat,sh}。docs/02-development/08-slice-7-wizard.md(§跨平台启动健壮性)。docs/02-development/00-overview.md。