Files
workbuddy_skills/session-mechanism/assets/board-launch.py.tpl
T
admin 19101acd65 init: workbuddy_skills 重建,仅收录 session-mechanism
- 按用户指示清空原有 25 技能内容,只提交 session-mechanism(57 文件)
- 附 .gitignore(产物 + 本机凭据)
- 令牌明文已脱敏(历史 .neodata_token 与 pitfalls 引用均不入库)
- 本提交为孤儿提交(父提交为空),历史自此重新开始
2026-10-05 14:13:24 +08:00

84 lines
4.3 KiB
Smarty
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""看板启动器(**无控制台**)· 模板。
> 🔴 用户 2026-10-04 21:0x 定案:「**看板 包括常驻 按现在的方式启动,全局只允许一个,把规则记录下来**」
> 本文件=本文档所述「现在的方式」的可复制载体。装法见 `references/supervise-persistence.md`。
## 为什么必须有它(⛔ 别再退回 `.cmd`)
计划任务的动作若写 `.cmd`/`.bat`/`powershell.exe`,那些都是**控制台程序**
⇒ 每次触发都分配一个 `conhost.exe` ⇒ **屏幕上弹黑窗**(用户已投诉两次)。
更糟:`.cmd` 里若**前台**跑 `pythonw`(没有 `start`)⇒ `cmd.exe` **不返回**
⇒ 黑窗**常驻**挂着(实测 `cmd.exe 62932` + `conhost.exe 54928` 挂在看板进程树上)。
## 解法
计划任务动作 **直接指向 `pythonw.exe`**(GUI 子系统 ⇒ 操作系统层面就不分配控制台 ⇒ 零黑窗);
本来靠 `.cmd` 的 `set` 传的环境变量,改在**本进程内**设好,再 `runpy` 起 `board.py`。
## 装法(⚠️ 三处必改:{{WS}} / {{BOARD}} / {{PORT}})
```powershell
$pyw = '<managed-python>\pythonw.exe'
$a = New-ScheduledTaskAction -Execute $pyw -Argument '"<本文件>"' `
-WorkingDirectory '<工作区根>'
$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) `
-MultipleInstances IgnoreNew -RestartCount 999 `
-RestartInterval (New-TimeSpan -Minutes 1)
$p = New-ScheduledTaskPrincipal -UserId '<当前用户>' -LogonType Interactive -RunLevel Highest
Register-ScheduledTask -TaskName 'dsh-board-<端口>' -Action $a -Settings $s -Principal $p -Force
```
🔴 **`RunLevel=Highest` 不是可选项**:`Limited` 实测 `LastTaskResult=1`(写日志/读库被拒)。
## ⛔ 两条封死的写法(都亲手栽过)
1. **`exec(compile(...))`** —— 从 `C:\Windows\System32`(任务默认 cwd)起时 **rc=1 且零输出**。
✅ 用 `runpy.run_path(BOARD, run_name="__main__")`:它会正确设好
`__file__`/`__name__`/`sys.path[0]`,`board.py` 顶部的 `_sm_load_roots()`
(靠 `__file__` 往上找 `roots.env`)才找得到。
2. **`pythonw.exe` 下 `sys.stdout/stderr` 可能是 `None`** ⇒ 任何 `print` 抛 `AttributeError` 秒退。
✅ 起 `board.py` 前先给两个流兜底(本文件已做)。
## 验收(三条全绿才算成,⛔ "打印了启动消息"不算)
- `netstat` 见 `127.0.0.1:<port> LISTENING`
- `curl /` = 200
- 🔴 **父进程是 `svchost.exe -s Schedule`**(⇒ 已脱离会话)**且进程树下零 `cmd.exe`/零 `conhost.exe`**
"""
import os
import sys
# ── 三处按实际改 ────────────────────────────────────────────────
WS = r"<工作区根,例:E:\ProgramData\AIProject\ai1net-dsh-server>"
BOARD = r"<技能脚本,例:E:\ProgramData\.workbuddy\skills\session-mechanism\scripts\board.py>"
PORT = "8788"
# ───────────────────────────────────────────────────────────────
LOG = os.path.join(WS, "tmp", "board-serve.err.log")
# 🔴 必须**先设好 env 再起 board** —— `board.py` 在**模块顶层**读这些名字。
# ⚠️ `roots.env` 只兜底 `COLLABD_PROD_CONFIG`,而 `board.py` 读的是 `COLLABD_CONFIG`
# ⇒ **名字对不上,不设就落到 `${脚本目录}/collabd.config.json`(不存在)⇒ 起不来**。
os.environ["COLLABD_CONFIG"] = os.path.join(WS, ".workbuddy", "collab", "collabd.config.json")
os.environ.setdefault("CODEBUDDY_CONFIG_DIR", os.path.expanduser("~/.workbuddy"))
os.environ["PYTHONIOENCODING"] = "utf-8"
os.environ["PYTHONUNBUFFERED"] = "1"
# 🔴 `pythonw.exe` 下两个流可能是 None ⇒ 兜底,否则 print 抛异常秒退
try:
_f = open(LOG, "a", encoding="utf-8", errors="replace")
except Exception:
_f = None
if _f is not None:
if sys.stdout is None:
sys.stdout = _f
if sys.stderr is None:
sys.stderr = _f
# 🔴 ⛔ 别用 `exec(compile(...))`(见文件头「两条封死的写法」)⇒ `runpy` 才会正确设好 `__file__` 等
import runpy
sys.argv = [BOARD, "--serve", PORT]
runpy.run_path(BOARD, run_name="__main__")