@ellieyujin/pi-reopen 0.0.0-stage → 0.2.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ellieyujin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,122 @@
1
- # Temporary Holding Version
1
+ # @ellieyujin/pi-reopen
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ npm 包名:`@ellieyujin/pi-reopen`;CLI 命令:`pi-workspace`。采用 [MIT 许可证](LICENSE),Copyright (c) 2026 ellieyujin。
4
+
5
+ 一个 Pi package:**在 Pi 外一条命令,恢复关闭 WezTerm 时仍打开的 Pi 组合**(进入原目录、加载原对话、等待你输入)。
6
+
7
+ - [需求基线(14 条已确认规则)](docs/REQUIREMENTS.md)
8
+ - [实现状态与审查记录](docs/STATUS.md)
9
+ - [发布到 npm 与 Pi 目录](docs/PUBLISHING.md)
10
+
11
+ **目标环境:WSL + Windows 版 WezTerm,Node ≥ 22.19,已安装 Pi。当前为开发预览版,尚未完成真实多窗口恢复验收。**
12
+
13
+ ## 目录
14
+
15
+ ```text
16
+ extensions/open-all.ts Pi 扩展(薄适配层):登记存活 Pi、提供 /all
17
+ src/ 共享核心(纯 JS ESM,扩展与 CLI 共用)
18
+ paths/liveness/members/claims/plan/preflight/discover/launch/spawn/verify/restore/cli
19
+ bin/pi-workspace.js CLI 入口(node 直接运行,免编译)
20
+ test/ Node 内置测试(隔离目录 + 注入式模拟,无真实终端调用)
21
+ docs/ 需求与状态
22
+ ```
23
+
24
+ ## 使用
25
+
26
+ ```bash
27
+ pi-workspace restore # 补开上次关闭时仍打开的 Pi(已运行的跳过)
28
+ pi-workspace restore --dry-run # 只看计划,不开标签页
29
+ pi-workspace list # 查看登记成员与状态
30
+ pi-workspace doctor # 环境检查(WezTerm / WSL 互操作 / pi)
31
+ pi-workspace clear # 清空登记(不删对话文件)
32
+ ```
33
+
34
+ Pi 内等价入口:`/all`(恢复)、`/all clear`、`Ctrl+Shift+O`。
35
+
36
+ ## 安装
37
+
38
+ ### 从源码安装(npm 发布前可用)
39
+
40
+ 以下命令在 WSL 内运行,安装的是当前检出的源码:
41
+
42
+ ```bash
43
+ git clone https://github.com/bridge-01/pi-workspace.git
44
+ cd pi-workspace
45
+ pi install .
46
+ mkdir -p "$HOME/.local/bin"
47
+ ln -s "$PWD/bin/pi-workspace.js" "$HOME/.local/bin/pi-workspace"
48
+ export PATH="$HOME/.local/bin:$PATH"
49
+ pi-workspace doctor
50
+ ```
51
+
52
+ 将 PATH 配置保存到 Shell 启动文件以便后续使用。若 CLI 链接已存在,先检查其目标,不要盲目覆盖。
53
+
54
+ 只需要扩展时,也可直接安装已推送的 Git 版本:
55
+
56
+ ```bash
57
+ pi install git:github.com/bridge-01/pi-workspace
58
+ ```
59
+
60
+ ### 从 npm 安装(维护者发布后)
61
+
62
+ **npm 上无 scope 的 `pi-workspace` 已属于其他作者,不是本项目,请勿安装错包。** 本项目包名为 `@ellieyujin/pi-reopen`,预览版使用 `next` 标签:
63
+
64
+ ```bash
65
+ pi install npm:@ellieyujin/pi-reopen@next
66
+ npm install -g @ellieyujin/pi-reopen@next --omit=peer
67
+ pi-workspace doctor
68
+ ```
69
+
70
+ `pi install` 注册并加载扩展,但不保证 CLI 出现在普通 Shell 的 PATH 中;`npm install -g` 安装外部 CLI,npm 全局 bin 目录也需在 PATH 中。CLI 不依赖 Pi 的 peer 包,故使用 `--omit=peer` 避免额外安装宿主。两份安装需要分别更新,并保持版本一致。
71
+
72
+ **登记必须启用扩展**:仅装 CLI 无法记录会话。安装后在每个已打开的 Pi 中运行 `/reload` 或重启;新安装无法精准找回此前未登记的窗口。不要同时加载旧的 `open-all.ts` 副本/符号链接与 package;迁移前先检查 `pi list` 和个人扩展目录,仅停用旧入口,不删除会话或认证文件。
73
+
74
+ ## 可选配置
75
+
76
+ | 环境变量 | 用途 |
77
+ | --- | --- |
78
+ | `PI_WORKSPACE_WEZTERM` | Windows WezTerm 在 WSL 中的绝对路径,例如 `/mnt/c/Program Files/WezTerm/wezterm.exe` |
79
+ | `PI_WORKSPACE_PI_BIN` | WSL 内 Pi 可执行文件的绝对路径,不含参数;不支持别名或 Shell 函数 |
80
+ | `PI_CODING_AGENT_DIR` | 使用其他 Pi 配置/登记目录;CLI 与扩展需保持一致 |
81
+ | `PI_WORKSPACE_SRC` | 仅供开发:覆盖共享 `src/` 目录,正常 package 安装无需设置 |
82
+
83
+ 例如 Pi 通过 nvm、pnpm 或自定义 npm prefix 安装时,可显式指定:
84
+
85
+ ```bash
86
+ export PI_WORKSPACE_PI_BIN="$(type -P pi)"
87
+ export PI_WORKSPACE_WEZTERM="/mnt/c/Program Files/WezTerm/wezterm.exe"
88
+ pi-workspace doctor
89
+ ```
90
+
91
+ 默认从调用方 PATH 和 Bash 登录环境查找 Pi,不再猜测 npm 安装目录。恢复标签页使用预检得到的 Pi 路径,并显式传递调用方 PATH 与 agent 目录;不会把整个环境或认证信息拼入命令行。WezTerm 自动发现依次检查 PATH、常见安装位置和 `where.exe`(经 `wslpath` 转换)。自定义盘符挂载点等未覆盖布局请设置显式路径。修改环境变量后需重启 Pi 进程,`/reload` 不会读取父 Shell 的新环境。
92
+
93
+ 恢复目前使用目标 WSL 发行版的默认用户;请以该用户运行本工具。多用户、自定义 Shell 启动逻辑需另行验证。
94
+
95
+ ## 工作原理
96
+
97
+ - **登记**:每个 Pi 启动时把 `{会话文件绝对路径, cwd, pid, processStart, bootId}` 实时写入 `~/.pi/agent/pi-workspace/members/`(每会话独立文件、原子替换)。
98
+ - **关闭判定**:Pi 的信号退出也会发出 `quit` 清理事件,不能仅按事件名判断。扩展在 TUI 生命周期内记录 SIGHUP/SIGTERM:信号退出保留记录;无信号的主动 `/quit` 或切换会话删除记录;reload 保留记录并清理监听器。强杀无法执行清理时记录残留。断电时最多恢复已持久化的记录,不保证未落盘的最新变化。
99
+ - **去重**:恢复前检查进程存活(pid + 启动时刻 + bootId,重启后失效 PID 不误判),并用启动声明(60s TTL + 持有者存活检测)减少重复请求。当前声明不是跨进程原子锁,不能保证并发或快速连续恢复绝不重复,请勿同时发起多次恢复。
100
+ - **安全**:预检(发行版 / 互操作套接字 / WezTerm 存在且可执行 / pi 可用)未通过绝不批量启动;绝不自动 sudo;ENOEXEC 给出修复指引;"已创建标签页"与"已确认 pi 启动"分开报告。
101
+
102
+ ## 环境支持
103
+
104
+ - **WSL(Windows)**:restore 的唯一目标环境,依赖 Bash、Linux `/proc`、Windows WezTerm 与 `wsl.exe` 互操作。恢复前需启动 WezTerm,使其 CLI 能连接到运行实例;不保证自动创建新 GUI 或还原窗口布局。
105
+ - **Linux(非 WSL)/ macOS / Windows 原生**:不支持恢复,通常会被预检拒绝;其他功能未在这些平台验收。非 Linux 平台无 `/proc`,`list` 的存活状态也不可靠。
106
+ - Node ≥ 22.19;扩展 TS 由 pi 加载,测试用 Node 原生类型剥离。
107
+
108
+ ## 测试
109
+
110
+ ```bash
111
+ # 仓库根目录
112
+ npm test
113
+ npm pack --dry-run
114
+ ```
115
+
116
+ 测试覆盖核心恢复、信号处理顺序、reload 清理、`/all recent` 补选、非标准 Pi 安装路径、Shell 转义及 package 移动后的加载。测试通过临时目录和模拟启动器隔离;测试钩子 `PI_WORKSPACE_HOOKS` 不应在正常使用时设置。
117
+
118
+ 可选真实 Pi PTY 测试:`python test/pty-shutdown.py`。使用临时配置和会话,验证 SIGHUP/SIGTERM 退出保留成员、主动 `/quit` 删除成员;不调用模型、不打开可见标签页。这不是 WezTerm 全流程验收。
119
+
120
+ ## 尚待真实环境验收
121
+
122
+ 模拟测试不等于端到端验收。待你配合完成:**开多个 Pi + 普通终端 → 关闭 WezTerm → `pi-workspace restore` 验证恢复集合正确**(含提前 `/exit` 一个的场景)。通过前请视为可用但未验收。
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { run } from "../src/cli.js";
3
+
4
+ process.exitCode = await run(process.argv.slice(2));
@@ -0,0 +1,97 @@
1
+ # 发布到 npm 与 Pi 目录
2
+
3
+ ## 结论
4
+
5
+ Pi 没有要求把源码上传到单独的 Pi 注册中心。分发有两条路径:
6
+
7
+ 1. **Git 分发**:推送仓库后,别人用 `pi install git:github.com/bridge-01/pi-workspace` 安装;不需要先发布 npm。正式分享建议固定已推送的 tag 或 commit。
8
+ 2. **npm 分发 / Pi 目录发现**:发布公开 npm 包,包含 `keywords: ["pi-package"]` 和 `pi.extensions`。官方说明关键词让包具备被 [Pi package gallery](https://pi.dev/packages) 发现的资格;没有承诺即时收录或固定同步时限。
9
+
10
+ 当前 package 已包含扩展 manifest、CLI bin、host peerDependencies、公开发布配置与发布前测试。源码 TypeScript 由 Pi 加载,无需单独构建。
11
+
12
+ ## 发布前必须确认
13
+
14
+ - **包名**:已确定为 `@ellieyujin/pi-reopen`,使用 npm 账户 `ellieyujin` 发布。无 scope 的 `pi-workspace` 属于其他作者,不是本项目。CLI 命令仍为 `pi-workspace`,不能与另一个同名 CLI 同时全局安装。
15
+ - **许可证**:已选择 [MIT](../LICENSE),版权人为 `ellieyujin`;`package.json.license` 已声明为 `MIT`,发布包须包含完整 LICENSE。
16
+ - **版本**:当前 `0.2.0-dev.0` 是开发预览,`publishConfig.tag` 为 `next`,避免误设为稳定版 `latest`。已发布的同名同版本不能重复发布。
17
+ - **功能验收**:仍需真实 WezTerm 多窗口关闭/重开验收;启动声明不是跨进程原子锁,不能宣称并发恢复绝无重复。先作为预览分享,不把打包成功当成完整功能验收。
18
+ - **工作树**:核对所有未提交修改(含原有修改),不要把未审查内容一起提交。本文不自动提交、推送或发布。
19
+
20
+ ## 本地检查
21
+
22
+ 在 WSL 仓库根目录运行:
23
+
24
+ ```bash
25
+ npm test
26
+ python test/pty-shutdown.py
27
+ npm pack --dry-run
28
+ ```
29
+
30
+ PTY 测试需要已安装 Pi;它只用临时配置测试信号退出与主动退出,不调用模型、不打开 WezTerm 标签页。检查 tarball 列表应包含 `extensions/`、`src/`、`bin/`、README、LICENSE、发布文档,不包含认证、个人会话、`.env`、测试临时文件或 `node_modules`。
31
+
32
+ 打包实物也应在临时目录解压后测试 CLI 和 Pi 扩展,且不设置 `PI_WORKSPACE_SRC`,避免本机源码兜底掩盖漏打包。
33
+
34
+ ## 首次 npm 发布
35
+
36
+ 以下命令在仓库根目录由维护者确认后手动运行。已登录时无需重复登录;`whoami` 应输出 `ellieyujin`:
37
+
38
+ ```bash
39
+ npm login --registry=https://registry.npmjs.org/ --auth-type=web --browser=false
40
+ # 保持终端运行,手动打开本次生成的链接,完成授权并等待登录成功。
41
+ npm whoami --registry=https://registry.npmjs.org/
42
+ npm pkg get name license version publishConfig
43
+ npm view @ellieyujin/pi-reopen --registry=https://registry.npmjs.org/
44
+ ```
45
+
46
+ `npm view` 返回现有包时需核对所有者权限;404 只说明查询时没找到,最终是否允许该名称仍由 registry 判断。不要把 token、密码或 2FA 验证码写入仓库或聊天。
47
+
48
+ 直接发布需要 npm 账户开启 2FA,或使用有发布权限且允许 bypass 2FA 的 granular access token。手动首次发布推荐浏览器登录与 2FA;后续 CI 可研究 npm trusted publishing,不要复制旧的长期 token 教程。
49
+
50
+ ```bash
51
+ npm test
52
+ npm pack --dry-run
53
+ npm publish --dry-run --access public --tag next
54
+ # 确认名称、许可、版本、文件清单和测试结果后,才执行真正发布:
55
+ npm publish --access public --tag next
56
+ ```
57
+
58
+ `prepublishOnly` 会再次运行测试。`--dry-run` 不上传包,也不能证明账户具备真实发布权限。
59
+
60
+ ## 发布后的安装验证
61
+
62
+ ```bash
63
+ npm view @ellieyujin/pi-reopen@next name version repository dist-tags --registry=https://registry.npmjs.org/
64
+ pi install npm:@ellieyujin/pi-reopen@next
65
+ npm install -g @ellieyujin/pi-reopen@next --omit=peer
66
+ pi-workspace doctor
67
+ ```
68
+
69
+ - `pi install` 管理扩展资源;外部 CLI 用 npm 全局安装,并确认全局 bin 在 PATH。
70
+ - 使用 `--omit=peer` 避免 CLI 安装额外的 Pi 宿主副本;扩展运行时由 Pi 提供声明的 peer 包。
71
+ - 在新 Pi 会话中检查 `/all` 命令和登记;已有会话需要 `/reload` 或重启。
72
+ - 仅保留一个扩展加载入口,不要把旧手工符号链接、Git 安装和 npm 安装同时启用。
73
+ - Pi 目录未立即出现时先确认公开包、关键词、manifest 和实际安装是否正常。目录索引与 npm 安装是两件事。
74
+
75
+ ## 后续更新
76
+
77
+ ```bash
78
+ # 维护者:使用新的版本号;此命令不会自动创建 Git commit/tag。
79
+ npm version prerelease --preid=dev --no-git-tag-version
80
+ npm publish --access public --tag next
81
+
82
+ # 使用者:Pi 扩展和 CLI 分别更新。
83
+ pi update npm:@ellieyujin/pi-reopen@next
84
+ npm install -g @ellieyujin/pi-reopen@next --omit=peer
85
+ ```
86
+
87
+ 完成验收并决定发布稳定版后,设置非 prerelease 版本,并把 `publishConfig.tag` 改为 `latest`,发布时也显式指定 `--tag latest`。不要直接沿用开发版的 `next` 配置。固定版本/tag/commit 的 Pi 安装不会自动跟随其他版本,需要改用新的来源版本。
88
+
89
+ 卸载扩展使用 `pi remove <原安装来源>`,卸载 CLI 使用 `npm uninstall -g @ellieyujin/pi-reopen`;不要顺带删除 Pi 认证、会话或整个 agent 目录。
90
+
91
+ ## 官方依据
92
+
93
+ - [Pi Packages:来源、manifest、关键词与 host peers](https://pi.dev/docs/latest/packages)
94
+ - [Pi Command Line:安装和更新命令](https://pi.dev/docs/latest/cli)
95
+ - [npm:发布 scoped public packages 与 2FA](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages)
96
+ - [npm:trusted publishers](https://docs.npmjs.com/trusted-publishers)
97
+ - [名称冲突核验:pi-workspace registry metadata](https://registry.npmjs.org/pi-workspace/latest)
@@ -0,0 +1,110 @@
1
+ # 第一版需求
2
+
3
+ ## 目标
4
+
5
+ 用户在普通 WSL Shell 中执行一条命令,无需先进入交互式 Pi,即可补齐上次的 Pi 工作会话组合。交付物为一个 Pi package,包含自动登记扩展与独立 CLI;不调用模型。
6
+
7
+ - 项目目录:使用者可将仓库克隆到任意目录。
8
+ - npm 包名:`@ellieyujin/pi-reopen`;许可证为 MIT,版权人为 `ellieyujin`。无 scope 的 `pi-workspace` 属于其他作者,见 `PUBLISHING.md`。
9
+ - 外部入口:`pi-workspace restore`(已实现,端到端验收仍待完成)。
10
+ - 第一阶段环境:Windows WezTerm + Arch WSL。
11
+
12
+ ## 已确认的恢复规则
13
+
14
+ 以下三点由用户明确确认,作为第一版开发和验收依据:
15
+
16
+ 1. **只恢复 Pi**:恢复关闭 WezTerm 时仍打开的 Pi 会话,不恢复 Pi 启动的开发服务器、构建任务、工具子进程或其他普通终端。
17
+ 2. **恢复后等待用户输入**:重新启动 Pi,进入原目录并加载已保存的对话,然后等待用户操作。不自动发送“继续”、触发模型请求、重跑中断的工具命令或执行其他工作任务。
18
+ 3. **提前单独关闭的不恢复**:在关闭整个 WezTerm 之前,用户已经单独退出或关闭的 Pi 不属于待恢复集合。不能把所有曾经登记过的会话累计为恢复清单。
19
+
20
+ 例如:原本打开 A、B、C,先单独关闭 B,之后关闭 WezTerm;下次只恢复 A、C。即使 A、C 已完成回答、正在等待输入,只要关闭 WezTerm 时仍打开,也应恢复。
21
+
22
+ “接着做”指保留已经保存的文件修改并加载对话上下文,让用户接着输入;不意味着恢复已退出进程的内存现场、未保存的回答或工具执行中断点。包不得自动回滚或重放文件修改。
23
+
24
+ 这是已确认的行为要求,不代表现有原型已实现;“主动退出与被动死亡”的可靠识别仍需技术设计和实际验证。
25
+
26
+ ## 已确认的边界规则
27
+
28
+ 以下六条为用户确认的默认方案(2026-10-30):
29
+
30
+ 1. **多窗口合并**:多个 WezTerm 窗口中的 Pi 合并为一个恢复集合,一起恢复。第一版不记录窗口归属。
31
+ 2. **不还原窗口分组**:恢复时打开一个 WezTerm 窗口、多个标签页;不重建原窗口布局,标签顺序按记录顺序。
32
+ 3. **关闭判定**:用户在 Pi 内主动退出(如 `/exit`)视为提前单独关闭,不恢复;Pi 因窗口关闭、崩溃、WSL 关机而被动死亡,视为随终端关闭,恢复。边界:`kill -9` 等手动强杀按被动死亡处理、会被恢复,接受此误差。
33
+ 4. **崩溃安全**:工具持续把“当前存活 Pi 集合”实时写盘;崩溃、强制结束、断电不需要额外保存动作,磁盘上即为最新名单。
34
+ 5. **重启恢复**:WSL/电脑重启后,恢复集合全部还原;失效的 PID 不得被误判为仍在运行。
35
+ 6. **部分已开**:恢复时按会话文件识别,只补开缺失的 Pi;已在运行的会话跳过。连续或并发执行恢复命令不产生重复。
36
+
37
+ ## 已确认的交互与数据规则
38
+
39
+ 1. **触发方式**:第一版仅手动执行恢复命令;“打开终端自动恢复”留作后续可选功能,不在 P0。
40
+ 2. **空窗口**:从未发送过消息的 Pi 窗口没有对话文件,恢复时跳过,并在结果中提示跳过数量;不重建空终端。
41
+ 3. **旧记录迁移**:忽略旧原型产生的历史格式登记,不自动删除;新版本按新规则从零记录。提供 `clear` 方式手动清理,对话文件不受影响。
42
+ 4. **命令与入口**:外部命令为 `pi-workspace restore`(后续可加 `list`、`doctor`、`clear`);Pi 内 `/all` 与 `Ctrl+Shift+O` 过渡期保留,行为与 CLI 对齐,共用同一恢复逻辑。
43
+ 5. **标签页标题**:优先使用会话名(`/name` 设置过),否则使用工作目录的文件夹名。
44
+
45
+ 以上共 14 条规则构成需求基线;后续变更需更新本文档并同步测试。
46
+
47
+ ## 功能范围
48
+
49
+ ### P0:必须完成
50
+
51
+ 1. **外部恢复**:普通 Shell 一条命令启动,无需额外交互式 Pi 作为入口。
52
+ 2. **自动记录**:持续维护“当前存活 Pi 集合”,保存会话文件绝对路径、工作目录、会话名和运行身份,实时落盘。用户无需命名或手动保存工作区。
53
+ 3. **上次组合语义**:恢复关闭 WezTerm 时仍打开的 Pi,包括已完成回答、正在等待输入的 Pi;提前单独退出或关闭的 Pi 不恢复。不是恢复所有历史上打开过的对话。启动一个入口 Pi 不得覆盖之前的恢复集合。
54
+ 4. **生命周期**:按已确认判定实现——Pi 内主动退出(如 `/exit`)将成员移出集合;被动死亡(窗口关闭、崩溃、WSL 关机)保留在集合中。`/new`、`/resume`、`/fork`、`/reload` 的成员变化必须有明确设计和测试。识别机制不可靠时如实说明限制,不得宣称精确快照已实现。
55
+ 5. **去重**:同一个会话文件只恢复一次;同目录不同会话分别保留。识别正在运行、正在启动的实例;连续或并发执行恢复命令不应重复创建标签页。
56
+ 6. **安全启动**:进入原目录,以正确 WSL 发行版和用户启动 Pi;路径中的空格、引号、`$` 等字符保持原样。不在同一文件上无保护地创建多个 Pi 写入实例。
57
+ 7. **环境预检**:检查 Pi、WezTerm、WSL 互操作、工作目录与会话文件。未通过不得批量启动;不自动 sudo、不自动修改系统配置。
58
+ 8. **真实结果**:区分已运行、待恢复、缺失、启动失败;捕获同步异常和子进程失败,不把“发起请求”当成“Pi 已成功恢复”。
59
+ 9. **持久化**:支持多实例并发和异常退出,防止记录互相覆盖。进程身份需覆盖 PID 重用及 WSL 重启情形。
60
+ 10. **打包与安装**:同时提供 Pi 扩展入口和 npm `bin` 入口;明确 `pi install` 与外部 CLI PATH 的不同。安装/迁移不能造成重复加载。
61
+ 11. **被动恢复**:只重新启动 Pi 并加载对话,恢复后等待用户输入。不自动继续工作,不重启关联服务、构建或工具子进程。
62
+ 12. **空窗口与旧记录**:跳过无对话文件的空窗口并提示数量;忽略旧格式历史登记且不自动删除,按新规则从零记录。
63
+ 13. **标签页标题**:优先会话名,无会话名时用工作目录的文件夹名。
64
+
65
+ ### P1:辅助能力
66
+
67
+ - `list` / `--dry-run`:恢复前只读预览计划。
68
+ - `doctor`:检测运行环境并输出可操作的诊断。
69
+ - 手动补选近期对话:仅作旧登记丢失时的补救,不当作准确的上次工作区;须经用户确认。
70
+ - 打开终端自动恢复(Shell 钩子):作为明确开启的可选项,不是默认行为。
71
+
72
+ ## 不做
73
+
74
+ - 多套具名工作区(第一版不需要)。
75
+ - 修改 Pi 主程序,或通过替换安装文件实现 `pi -all`。
76
+ - 自动恢复其他普通 Shell、CMD、开发服务。
77
+ - 还原 WezTerm 窗口分组与布局;第一版统一为一个窗口多个标签页。
78
+ - 第一版自动执行恢复(自动恢复仅作为后续可选功能)。
79
+ - 复活已退出进程的内存、未完成模型请求、输入草稿、滚动位置。
80
+ - 第一版强依赖 Zellij/tmux;进程保活可后续增加后端。
81
+ - 由模型推测恢复清单或调用模型执行恢复。
82
+
83
+ ## 验收场景
84
+
85
+ - 六个 Pi 与其他普通终端混用,关闭整个 WezTerm 后,从普通 Shell 恢复关闭时仍打开的 Pi 组合;非 Pi 终端不恢复。
86
+ - A、B、C 三个 Pi 中提前单独关闭 B,再关闭 WezTerm;下次只恢复 A、C。
87
+ - Pi 已完成回答并停在输入框等待,关闭 WezTerm 后仍恢复该会话,不能以是否正在生成判断成员。
88
+ - 恢复后各 Pi 加载原对话并等待输入;恢复流程不发送“继续”,不触发工作任务的模型请求,不重启原来的开发服务器或中断命令。
89
+ - 两个 WezTerm 窗口共五个 Pi,关闭后恢复为一个窗口五个标签页;窗口归属不恢复。
90
+ - A、B、C 中先在 Pi 内主动 `/exit` 关闭 B,再关闭 WezTerm:B 不恢复,A、C 恢复;用 `kill -9` 强杀 B 时 B 会被恢复(已接受的误差)。
91
+ - 崩溃、强制结束、断电后恢复:实时落盘的存活集合不丢失,恢复结果与正常关闭一致。
92
+ - 混有从未发消息的空 Pi 窗口:恢复时跳过并提示跳过数量,不重建空终端。
93
+ - 存在旧格式历史登记时:新版本忽略它们,不把历史累计集合误当上次组合恢复。
94
+ - 六个中已有两个运行,只补其余四个;连续执行、并发执行均不重复。
95
+ - 同目录两个不同对话均恢复;同一个文件路径的别名正确去重。
96
+ - WSL 重启后旧 PID 不会误判为运行中。
97
+ - 缺失/未落盘文件有明确报告,不创建空对话冒充恢复成功。
98
+ - WSLInterop 缺失时输出诊断,无未捕获的 ENOEXEC,无虚假成功。
99
+ - WezTerm 请求成功但 Pi 启动失败时仍能诊断。
100
+ - 多实例登记、关闭、reload、切换对话不会丢失或错误累计恢复成员。
101
+ - 包安装、升级、卸载和旧扩展迁移可回退,不触及认证与会话内容。
102
+
103
+ ## 尚待细化(设计/实现层)
104
+
105
+ 需求基线已确认。以下为实现层待设计项,不影响需求定义:
106
+
107
+ - 关闭判定的具体技术方案:如何可靠检测“Pi 内主动退出”与“被动死亡”(如清理事件与进程存活探测的组合),需设计与真实环境验证。
108
+ - 自动发现 WezTerm 路径的机制,以及发行版/用户隔离的具体实现。
109
+ - 跨进程启动锁、超时、成功回执,以及恢复进程中断后的回收方案。
110
+ - 首次 npm 发布与安装验收(包名 `@ellieyujin/pi-reopen`、MIT 许可证已确认,实际发布尚待执行)。
package/docs/STATUS.md ADDED
@@ -0,0 +1,134 @@
1
+ # 本机实现审查
2
+
3
+ **当前结论:开发中,不能宣称第一版已完成。** 已修复关终端时误删成员的退出判定错误;当前 `npm test` 51/51 通过,隔离真实 Pi PTY 的 SIGHUP、SIGTERM、主动 /quit 三项通过。完整 WezTerm 多窗口恢复、并发启动互斥等仍需验收。下方旧轮次记录不代表当前完成状态。
4
+
5
+ ## Package 可移植性与发布准备(2026-10-30)
6
+
7
+ - 删除扩展源码定位的个人路径兜底,保留 package 相对定位与开发覆盖变量。
8
+ - 增加 `PI_WORKSPACE_PI_BIN`,统一预检与恢复标签页的 Pi 路径、PATH 和 agent 目录;`runRestore({ env })` 的默认记录路径同步使用传入环境。
9
+ - WezTerm 发现不再依赖个人 D 盘位置;支持 PATH,并将 `where.exe` 返回值经 `wslpath` 转换。
10
+ - 补充仓库元信息、`next` 预览发布配置、发布前测试、通用安装和迁移说明。后续经维护者确认,包名为 `@ellieyujin/pi-reopen`,采用 MIT 许可证,版权人为 `ellieyujin`。
11
+ - npm registry 核验:无 scope 的 `pi-workspace` 已属于其他作者;manifest 及发布文档现已同步为 `@ellieyujin/pi-reopen`,完整许可证见根目录 `LICENSE`。这不代表已完成 npm 发布。
12
+ - 验证:51/51 自动化测试通过;真实 Pi PTY 3/3 通过;npm tarball 解压到临时位置后 CLI 和真实 Pi PTY 3/3 通过,未设置 `PI_WORKSPACE_SRC`。
13
+ - 没有执行 npm publish、Git 提交或推送,没有完成真实 WezTerm 多窗口恢复验收。并发锁等既有问题不在本轮修复范围。
14
+
15
+ ## 深度测试轮(2026-10-30)
16
+
17
+ 新增 24 项自动化测试(`test/deep.test.mjs` 18 项 + `test/recent.test.mjs` 6 项),共 45/45 通过;`python test/pty-shutdown.py` 3/3 通过。
18
+
19
+ **新覆盖**:processStart 解析鲁棒性(comm 含括号/空格、僵尸、缺失);claim 同 PID 重取/死持有者接管;sweepClaims 保留/删除/损坏声明;plan 的 claimed 状态;resolveSessionFile 优先级与回退;preflight 全项通过(仅 WSL 上可测)与 pi 缺失提示;openTab 无 distro 分支;默认 spawner 成功/失败/ENOENT;shellQuote 回环;listMembers 损坏/缺 ts;成员文件 0600;runRestore 全部被占用时不启动且如实报告;summarize 空结果;CLI 用法/退出码/隔离目录 list+clear。**/all recent 补选路径首次纳入自动化测试**(stub 化 preflight/spawn 后验证候选发现、输入校验、确认、成功登记、失败回收、own/live 排除)。
20
+
21
+ **本轮修复两处问题**:
22
+
23
+ 1. `src/cli.js`:`list`/`clear` 忽略传入的 `env`(与 `restore`/`doctor` 不一致),改用 `workspacePaths(env)`。bin 入口传 `process.env`,行为不变;仅影响测试与库调用方。
24
+ 2. `extensions/open-all.ts` `openRecent`:原先 `readFileSync` 整个会话文件只为取首行(会话文件可达数 MB),改为只读首 64KB 切首行。
25
+
26
+ **已知限制(记录,未改)**:
27
+
28
+ - claims 存在 TOCTOU 竞态:两个恢复进程同一瞬间读写声明可能都判空,极端情况多开一个标签页;TTL 只防顺序重复。影响有限,概率极低。
29
+ - 全部待恢复项被其他恢复进程占用时,本次 `restore` 返回 exit 1(冲突视为未完成),语义待确认。
30
+ - 损坏的 claim 文件 `sweepClaims` 不清理,仅在该会话再次被声明或 `clear` 时移除。
31
+ - 非 Linux 平台(macOS/Windows 原生)无 /proc,存活判定退化为“全部判死”;restore 被预检拒绝,无实际危害,但 `list` 状态会误导。环境支持结论:restore 仅 WSL;其他平台仅登记/list/doctor 可用(预检安全拒绝)。
32
+ - 扩展个人路径兜底已在 package 可移植性轮删除。
33
+
34
+ ## 最新修复:信号退出被误判为主动退出
35
+
36
+ 源码证据:本机 Pi 的 `interactive-mode.js` 在 SIGHUP/SIGTERM 时调用 `runtimeHost.dispose()`,后者在 `agent-session-runtime.js` 中同样发出 `session_shutdown(reason: "quit")`。原扩展把 quit 一律当作用户退出,导致关闭前 list 有成员、关闭后成员被删除。
37
+
38
+ 修复:TUI 生命周期内监听 SIGHUP/SIGTERM,只记录退出来源,不接管宿主退出流程;quit 清理延迟到本轮信号分发结束再判断。信号退出保留成员,无信号的主动 quit 删除成员;reload/切换时清理监听器。
39
+
40
+ 验证:`npm test` 为 21/21;`python test/pty-shutdown.py` 在隔离配置下启动真实 Pi,SIGHUP 与 SIGTERM 退出后记录仍在且 CLI list 显示待恢复,主动 `/quit` 后记录删除。未调用模型、未打开可见标签页、未操作用户正在运行的 Pi。
41
+
42
+ 此前“没有登记就是没重载”“新会话没有文件路径”的解释未充分验证,不能作为该问题的根因结论。已被旧逻辑删除的成员无法从当前清单精准重建,需要用户从历史对话选择。
43
+
44
+ ## 需求确认更新
45
+
46
+ 用户已确认:只恢复 Pi;恢复后等待用户输入,不自动继续执行;提前单独关闭的 Pi 不恢复。完整规则和验收场景见 `REQUIREMENTS.md` 的“已确认的恢复规则”。
47
+
48
+ 后续用户对 6 条边界默认方案与 5 条交互/数据默认方案逐条确认(“没问题”),未提出修改。至此 **14 条规则的需求基线闭环**(核心 3 + 边界 6 + 交互/数据 5),需求环节完成,已全部写入 `REQUIREMENTS.md`。
49
+
50
+ 当前原型不满足该规则:普通退出一律保留记录,不能区分主动退出与被动死亡。需求确认后仍未实现行为变化,未重新执行测试;下面的未完成结论不变。
51
+
52
+ ## 迁移前实际文件
53
+
54
+ - 只有 `~/.pi/agent/extensions/open-all.ts`。
55
+ - 无独立 CLI、无 `bin` 安装入口、无已有项目/package 清单。
56
+ - 之前位于 `/tmp` 的临时回归测试已不存在,不能把此前口头测试结论当成当前可复现验收。
57
+ - 有旧共享注册表和逐会话记录目录;保留原处,不复制个人会话数据到项目。
58
+
59
+ ## 已有原型能力(不等于已验收)
60
+
61
+ | 项目 | 状态 |
62
+ | --- | --- |
63
+ | Pi 内 `/all`、`/all recent`、`/all clear` | 有实现 |
64
+ | 会话文件绝对路径与存在性检查 | 有实现 |
65
+ | 启动命令补 PATH、Shell 单引号转义 | 有实现 |
66
+ | 同一 WSL 启动期间的 PID + 启动时间识别 | 有实现 |
67
+ | 分会话文件原子替换 | 有实现 |
68
+ | 退出时保留登记 | 有实现 |
69
+ | 近期历史补选并确认 | 有实现 |
70
+
71
+ ## 实现记录(2026-10-30 第二轮)
72
+
73
+ 按已确认规则重写为共享核心 + 双入口:
74
+
75
+ - `src/*.js`(纯 JS ESM):paths / liveness(pid+启动时刻+bootId)/ members(每会话独立原子写)/ claims(启动占用,60s TTL+持有者存活)/ plan(六种状态+realpath 去重)/ preflight(含 ENOEXEC 诊断,不自动修复)/ spawn(转义+PATH 注入)/ verify(登记回执轮询)/ restore(编排+中文报告)/ cli(restore/list/doctor/clear)。
76
+ - `extensions/open-all.ts` 薄适配:realpath 解析项目源码(jiti 下符号链接路径≠真实路径,已实测)、退出语义(quit/new/resume/fork 删记录,reload 保留)、`/all` 与 `/all recent`。
77
+ - 关键设计:**主动退出/切换 → 删除记录;被动死亡 → 记录残留**,文件系统状态天然编码已确认的关闭判定规则;崩溃/断电无需额外保存。
78
+ - 测试 16/16 通过(核心 11 + 扩展接线 5),覆盖生命周期、重启失效、占用互斥/接管、缺失/重复/别名、冷 shell 转义(含引号/$/空格真实执行)、预检失败关断、诚实报告、迁移不清旧数据。
79
+ - 真机验证:`pi -p` 加载扩展无错误;`pi-workspace doctor` 全项通过(interop 已恢复);`list`/`restore --dry-run` 正常;CLI 已链接到 `~/.local/bin/pi-workspace`。
80
+ - 旧格式记录(pi-terminals.json / pi-terminal-records/)按需求忽略不删。
81
+
82
+ ### 仍待验收
83
+
84
+ - 真实六窗口关闭/重开端到端(需用户配合,含提前 `/exit` 场景)。
85
+ - ENOEXEC 真实场景未复现(已修复注册),诊断分支仅注入测试;若再现按 doctor 提示处理。
86
+ - `/all recent` 补选路径已纳入自动化测试(2026-10-30 深度测试轮,stub 化 preflight/spawn);真实 WezTerm 开标签页的补选流程仍未端到端验收。
87
+
88
+ ### 追加:新会话永不登记的缺陷(2026-10-30 晚)
89
+
90
+ 用户实测发现:重启后的全新 Pi 窗口发过消息、关闭 WezTerm 后仍无法恢复。根因:全新会话在 session_start 时文件尚未落盘,登记被跳过后不再有触发点,导致新窗口永远不会进入恢复集合。修复:监听 message_end,首条用户消息落盘后补登记,后续用户消息刷新时间戳;“从未发消息的窗口不登记”规则保持不变。测试 18/18 通过(新增延迟登记用例)。此前已关闭的窗口因当时未登记,无法自动补录;可用 `/all recent` 手动补选(打开即登记)。
91
+
92
+ ### 追加:interop 反复丢失与迁移期空登记(2026-10-30)
93
+
94
+ 用户实测发现两个问题:
95
+
96
+ 1. **WSLInterop 当天两次丢失**。根因:本机 systemd 启用但无任何 binfmt.d 配置,systemd-binfmt 跳过注册,WSL 自带注册被清后无人补。处理:`doctor` 新增持久化检查(`src/binfmt.js`,17/17 测试通过),给出一次性持久化命令(`/usr/lib/binfmt.d/WSLInterop.conf` + 重启 systemd-binfmt),不自动 sudo。用户执行前每次 WSL 重启后需手动重新注册才能恢复。
97
+ 2. **旧进程未向新格式登记**。用户当时开着的 Pi 是在旧扩展代码下启动的,新登记目录为空,故 `/all` 与 CLI 均报“没有待恢复的会话”。这是一次性迁移成本:每个 Pi 窗口重启一次(或 `/reload`)后,登记开始;此前关闭的窗口无法补登记(对话文件仍在,可用后续 P1 的补选能力)。
98
+
99
+ ## 原型阶段的阻塞问题(历史记录,不代表均已解决)
100
+
101
+ 1. **无外部入口**:不能在普通 Shell 执行 `pi-workspace restore`。
102
+ 2. **集合语义不符**:普通退出一律保留记录,主动 `/exit` 关闭的 Pi 仍会进入恢复集合,违反已确认的关闭判定规则(主动退出→排除,被动死亡→保留);当前记录可能累计为历史集合。
103
+ 3. **启动中无保护**:无原子启动占用/锁,连续或并发 `/all` 可在登记完成前重复创建。
104
+ 4. **错误处理不足**:`spawn()` 同步异常可直接冒出;异步错误被忽略;无子进程退出检查和启动回执。ENOEXEC 仍是已知阻塞。
105
+ 5. **环境硬编码**:WezTerm 固定 `/mnt/d/Apps/WezTerm/wezterm.exe`;Pi 搜索路径为个人配置;缺少系统/工具预检与发现配置。
106
+ 6. **身份不完整**:只使用 PID + 启动 tick,没有 boot ID,跨重启不能做强保证;没有会话路径规范化去重。
107
+ 7. **旧表迁移不完整**:旧共享表一直叠加读取,切换会话时仅删除新记录可能令旧项重新出现。
108
+ 8. **测试缺口**:无真实关闭/重开端到端验收,无并发启动与失败注入验收。
109
+ 9. **用户隔离**:恢复指定发行版但未明确用户;自定义 agent 目录未支持。
110
+
111
+ ## 本次整理范围
112
+
113
+ - 原型源文件原样迁移,SHA-256:`df0f87f1f75446121cb078a1ed977a2068fa124beed3adba9ed74e0c03bd0d32`。
114
+ - 新增 Pi package 清单,版本 `0.1.0-prototype.0`,`private: true` 防误发布。
115
+ - 原安装位置改为项目源码符号链接;不再维护两份代码,也不额外注册一次 package。
116
+ - 新增 Node 内置测试:在隔离目录与模拟启动器内检查原型现有逻辑。
117
+ - 新增关键未完成项的 TODO 测试;TODO 不算通过验收。
118
+ - 不修改会话内容、认证文件、Pi settings、系统 WSL 配置。
119
+ - 不增加 CLI 假入口,不声称解决 ENOEXEC,不自动打开标签页。
120
+
121
+ ## 验证边界
122
+
123
+ `npm test` 的测试使用 Node TypeScript 类型剥离和受控 VM,注入 Pi API / 子进程模拟器。能够验证源码逻辑,但**不是 Pi 的真实 jiti 加载、Windows 终端创建或任务恢复测试**。
124
+
125
+ 本次 `npm test` 实测:16 项测试,8 项通过、0 项失败、8 项 TODO。TODO 不代表通过。真实环境功能仍未验收。当前未完成项清单见测试 TODO 与 `REQUIREMENTS.md`。
126
+
127
+ ## 建议后续顺序
128
+
129
+ 1. 需求已闭环。按已确认的关闭判定规则(主动退出→排除,被动死亡→保留)设计实现方案并验证其可靠性;补齐多窗口合并、崩溃、重启等生命周期测试。
130
+ 2. 将记录、恢复计划、终端适配从扩展抽成共享模块。
131
+ 3. 实现外部 CLI 和只读诊断;先修复并测试 ENOEXEC 的可读错误处理。
132
+ 4. 增加跨进程锁、启动占用和成功回执。
133
+ 5. 完成 package/CLI 安装与迁移流程。
134
+ 6. 用户同意后做实际六窗口关闭/重开验收;通过前保持原型标记。