pi-meantime 0.0.0-stage → 0.3.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 +21 -0
- package/README.md +113 -2
- package/docs/API-CONTRACT.md +56 -0
- package/docs/DESIGN.md +165 -0
- package/docs/SAME-NAME-GATE.md +49 -0
- package/docs/VALIDATION.md +44 -0
- package/examples/meantime.json +12 -0
- package/examples/register-fixture.mjs +10 -0
- package/index.mjs +24 -0
- package/package.json +45 -4
- package/scripts/demo.ts +36 -0
- package/scripts/pi-smoke.mjs +331 -0
- package/scripts/same-name-gate.mjs +172 -0
- package/scripts/sdk-cancel-permissions.mjs +299 -0
- package/src/config.ts +66 -0
- package/src/extension.ts +598 -0
- package/src/protocol.ts +156 -0
- package/src/runtime.ts +401 -0
- package/src/schema.ts +56 -0
- package/src/store.ts +61 -0
- package/test/adapter.test.ts +502 -0
- package/test/cancel-parity.test.ts +166 -0
- package/test/fixtures/mcp-server.mjs +42 -0
- package/test/fixtures/sdk-permissions-mcp.mjs +32 -0
- package/test/foreground.test.ts +57 -0
- package/test/helpers.ts +40 -0
- package/test/host.ts +170 -0
- package/test/lifecycle.test.ts +144 -0
- package/test/mcp-client.ts +67 -0
- package/test/mcp.test.ts +60 -0
- package/test/panel.test.ts +133 -0
- package/test/runtime.test.ts +238 -0
- package/test/schema.test.ts +47 -0
- package/test/sdk-contract.compile.ts +86 -0
- package/test/store.test.ts +82 -0
- package/test/wait-many.test.ts +45 -0
- package/tsconfig.json +9 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pi-meantime contributors
|
|
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,114 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pi-meantime
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
让长时间工具调用转到后台,Pi 继续处理其他工作;完成后通知模型,不必反复查询。
|
|
4
|
+
纯 Pi 扩展,不修改核心,也不启动第二个 Agent。当前版本 **0.3.0**,面向 **Pi 1.3.0**,需要 **Node.js 22.19+**。
|
|
5
|
+
|
|
6
|
+
`bash` 原名可用;符合条件的 MCP / 扩展工具通过 `_mt` 代理调用,业务参数不变,只增加可选的 `__meantime__` 控制字段。未转后台时保持原生结果。
|
|
7
|
+
|
|
8
|
+
## 安装
|
|
9
|
+
|
|
10
|
+
任选一种来源,安装后在 Pi 中 `/reload`:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
# Git
|
|
14
|
+
pi install git:github.com/astrosheep-zero/pi-meantime
|
|
15
|
+
|
|
16
|
+
# npm(需要该版本已发布;不可用时使用 Git)
|
|
17
|
+
pi install npm:pi-meantime@0.3.0
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
也可克隆后临时加载,不写入全局安装列表:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
git clone https://github.com/astrosheep-zero/pi-meantime.git
|
|
24
|
+
cd pi-meantime
|
|
25
|
+
pi -e ./index.mjs
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
避免与其他覆盖 `bash` 的扩展同时加载。
|
|
29
|
+
|
|
30
|
+
## 人怎么用
|
|
31
|
+
|
|
32
|
+
正常让模型执行工具即可。默认从前台开始,运行超过 **120 秒**自动转后台;运行中发送 steer 输入或按 **Ctrl+Shift+B** 也可释放前台等待,不取消底层操作。
|
|
33
|
+
|
|
34
|
+
| 命令 | 用途 |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `/meantime` | TUI 打开实时任务面板;非 TUI 显示当前会话完整任务列表 |
|
|
37
|
+
| `/meantime detach` | 手动把允许转后台的前台调用转走;终端不支持快捷键时可用 |
|
|
38
|
+
| `/meantime cancel <id>` | 请求取消指定任务,不猜目标 |
|
|
39
|
+
| `/meantime rescan` | 重新扫描可代理工具 |
|
|
40
|
+
| `/bg` | `/meantime` 的别名,接受相同参数 |
|
|
41
|
+
|
|
42
|
+
面板运行中任务优先,其余按创建时间倒序;每秒及状态变化时刷新。
|
|
43
|
+
**↑/↓** 选择,**Enter** 展开/收起命令、已有进度、结束任务的输出尾部及输出路径,**c** 请求取消,**Esc/q** 关闭。
|
|
44
|
+
手动子命令反馈显示在 Latest background control 区域,只保留最近一次操作。
|
|
45
|
+
|
|
46
|
+
## 模型怎么用
|
|
47
|
+
|
|
48
|
+
以下是工具调用示例,不是终端命令。拿到任务 ID 就代表工作已启动,**不要重复执行原操作,也不要循环轮询**。
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
bash({ command: "npm test", __meantime__: "yes" })
|
|
52
|
+
|
|
53
|
+
meantime({ action: "list" })
|
|
54
|
+
meantime({ action: "wait", id: "mt_1234abcd", timeout: 30 })
|
|
55
|
+
meantime({ action: "wait", id: "1234", timeout: 0 })
|
|
56
|
+
meantime({ action: "wait", ids: ["1234", "5678"], mode: "any", timeout: 60 })
|
|
57
|
+
meantime({ action: "cancel", id: "mt_1234abcd" })
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
ID 用实际返回值替换;支持唯一前缀,可省略 `mt_`,有歧义时报错。
|
|
61
|
+
|
|
62
|
+
| `__meantime__` | 行为 |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `"auto"`(默认) | 从前台开始,允许计时、快捷键、steer 或 detach 命令转后台 |
|
|
65
|
+
| `"yes"` | 请求立即后台执行;等待原调用进入,当前 UI 审批期间延后 |
|
|
66
|
+
| `"no"` | 始终前台,不接受上述转后台触发;仍可真正取消 |
|
|
67
|
+
|
|
68
|
+
控制字段在分派前移除。Bash 自身的 `timeout` 仍是命令硬超时,与转后台阈值无关。
|
|
69
|
+
|
|
70
|
+
`meantime` 只有三个动作:
|
|
71
|
+
|
|
72
|
+
- **list**:简短任务列表,默认 10 条;用 `offset`、`limit` 翻页,`limit` 最大 32。
|
|
73
|
+
- **wait**:等待结束或超时,返回当前状态或保留结果。`timeout` 单位为**秒**,默认 **30**,可用小数,无上限;**0** 立即返回。中止等待不取消任务。
|
|
74
|
+
- **cancel**:请求取消,不保证远端操作停止或副作用回滚;以工具最终结果为准。
|
|
75
|
+
|
|
76
|
+
`wait` 必须在 `id` 与 `ids` 中二选一。`ids` 接受 1–32 项,`mode: "all"`(默认)等全部结束,`"any"` 等任意一项结束;超时也会返回。批量回执含已结束结果及 `pending`,正文共享 32KiB 预算,被省略的结果可单独读取。
|
|
77
|
+
|
|
78
|
+
默认代理匹配 `mcp__*`,排除 IDE MCP;自定义工具可通过配置加入。代理继承原工具的 `direct` / `codemode` / `deferred` 可见性,不把间接工具提升为直连工具。MCP 默认 codemode 时,从 Codemode 调用对应 `_mt` 代理。顺序工具、不兼容的根 Schema 及保留字段冲突不会被强行包装。
|
|
79
|
+
|
|
80
|
+
## 完成通知、保存与边界
|
|
81
|
+
|
|
82
|
+
- 后台完成通知含任务 ID、状态、可选退出码、存在时的 `Output: <path>`,以及约 1KiB 的文本尾部。尾部标为不可信工具数据,不是指令,不含图片或结构化结果;需要更多信息时用单任务 `wait` 读取保留结果,或读取输出文件。
|
|
83
|
+
- Pi 忙碌或 UI 审批期间延后通知,空闲后重新筛选未读结果。期间被 `wait` 成功读取的结果会从待发通知中排除,避免重复完成唤醒;未取得结果的超时等待不算已读。通知可合并多项完成,已经入队的消息无法撤回,也不承诺崩溃场景下恰好投递一次。
|
|
84
|
+
- **Pi 1.3.0 已知缺口**:树导航取消/失败没有结束事件,期间积压的通知可能继续等待。任务仍保留 pending,可主动读取;下一次 agent settled、压缩结束等可用事件会重新尝试,不能保证无后续事件时自动补发。
|
|
85
|
+
- 任务索引及完成文本保存在 **Pi 会话自定义条目**中。同一会话的树导航共享任务;fork/clone 不接管原任务。恢复时已有完成记录可读,缺少最终记录的任务显示 `outcome_unknown`,绝不自动重放;重启不补发历史通知。
|
|
86
|
+
- **不是跨进程作业服务**:`/reload`、切换会话和退出会请求取消,停止旧通知。取消是协作式的,失去本地控制不能证明远端操作已结束。
|
|
87
|
+
- Bash 使用 Pi 原生有界文本和完整输出文件,扩展不另建或删除 Bash 文件。其他工具文本不超过 50KiB 时只存索引,更大时写私有 `.txt`,索引保留约 2000 字节预览及路径;文件默认最多保留 4MiB,超限会标记截断。图片与结构化数据仅在原进程内存保留,恢复后只有文本;读取回执不内联图片。
|
|
88
|
+
- 默认最多 4 个真实本地执行,保留最近 128 个已结束后台任务(待通知等记录暂受保护)。后台晚到 usage 可能未计入 Pi 全局费用统计。
|
|
89
|
+
- `print/json` 模式下 `auto` 保持前台,`yes` 明确报错。已转后台后才出现的 MCP 交互仍是兼容边界;这类工具建议用 `"no"` 或排除。
|
|
90
|
+
|
|
91
|
+
## 配置
|
|
92
|
+
|
|
93
|
+
读取全局 `~/.pi/agent/meantime.json`(尊重 `PI_CODING_AGENT_DIR`),或用 `PI_MEANTIME_CONFIG` 指定绝对路径;不自动读取项目配置。
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"autoDetachMs": 120000,
|
|
98
|
+
"detachOnSteer": true,
|
|
99
|
+
"include": ["mcp__*", "my_slow_tool"],
|
|
100
|
+
"maxRunning": 4
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`autoDetachMs: 0` 关闭计时转后台;`detachOnSteer: false` 只关闭 steer 触发。完整配置与约束见 [src/config.ts](src/config.ts)。
|
|
105
|
+
|
|
106
|
+
## 开发与验证
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
npm ci --ignore-scripts
|
|
110
|
+
npm run check # 类型检查 + 测试
|
|
111
|
+
npm run test:pi # 真实 Pi SDK 冒烟,脚本化 provider,无外部模型调用
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
更多边界见 [设计说明](docs/DESIGN.md),验证范围见 [验证说明](docs/VALIDATION.md)。[MIT License](LICENSE)。
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 已核对的 Pi API
|
|
2
|
+
|
|
3
|
+
固定基线:Pi v1.3.0。以下来源是官方仓库,不依赖社区插件的描述。
|
|
4
|
+
|
|
5
|
+
- [Extension 文档](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/docs/extensions.md)
|
|
6
|
+
- [类型声明](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/core/extensions/types.ts)
|
|
7
|
+
- [嵌套调用实现](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/core/nested-tool-calls.ts)
|
|
8
|
+
- [AgentSession 分派与 loadout](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/core/agent-session.ts)
|
|
9
|
+
- [原生 Bash factory](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/core/tools/bash.ts)
|
|
10
|
+
- [MCP readiness 和注册](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/extensions/mcp/index.ts)
|
|
11
|
+
- [官方 SDK 示例](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/examples/sdk/14-codemode-mcp.ts)
|
|
12
|
+
|
|
13
|
+
| API | 本扩展 的使用方式 |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `pi.registerTool` | Bash 同名包装、其他工具不同名代理、meantime |
|
|
16
|
+
| `pi.getAllTools` | Schema、元数据、exposure、来源签名 |
|
|
17
|
+
| `pi.getSettings` | 执行时读取有效 Shell 设置;加载期不访问 |
|
|
18
|
+
| `prepareLoadout(loadout)` | 观察 callable/registered 定义和 executionMode |
|
|
19
|
+
| `hiddenDeclarations` | 不对模型声明原工具,仍保留内部可调用性 |
|
|
20
|
+
| `reachesTools` | 让原生 MCP 等待相关服务器就绪 |
|
|
21
|
+
| `ctx.tools` | 执行期确认原工具可调用及顺序属性 |
|
|
22
|
+
| `ctx.executeTool(name,args,{signal,onUpdate})` | 复用原工具验证、权限和结果钩子 |
|
|
23
|
+
| `input.streamingBehavior` | 原生 steer 来时 detach;返回 continue |
|
|
24
|
+
| `registerShortcut` | Ctrl+Shift+B |
|
|
25
|
+
| `tool_execution_end` | 等最终 wrapper 结果确认后再入队完成通知 |
|
|
26
|
+
| `ui_prompt_start/end` | 审批期间暂缓 detach |
|
|
27
|
+
| `sendMessage` | followUp + triggerTurn,完成唤醒 |
|
|
28
|
+
| `appendEntry` | 非模型上下文的审计事件 |
|
|
29
|
+
| `session_shutdown/session_tree` | 撤销旧 runtime、隔离通知 |
|
|
30
|
+
| `registerMcpServer` | 仅开发示例注册真实本地 MCP 服务器 |
|
|
31
|
+
|
|
32
|
+
## 没有使用的能力
|
|
33
|
+
|
|
34
|
+
没有虚构 `decorateTool`、`detachTool`、`tools_changed`、全局 `pi.executeTool`。没有访问 `_toolRegistry`、`_extensionRunner` 或修改 prototype。也没有通过改 provider payload 偷改模型看到的 Schema。
|
|
35
|
+
|
|
36
|
+
`src/protocol.ts` 保留可用于 FakePi 的最小结构,但通过 type-only import 引用官方 SDK 1.3.0 的结果、Usage、Schema、loadout/AgentTool 元数据、事件返回值和快捷键类型。注册定义的 promptGuidelines 必须为 mutable string[];只读注册表观察仍可接受 readonly string[]。运行态 AgentTool 与扩展 ToolDefinition 分开,TSchema 先在 schema 边界验证再装饰。
|
|
37
|
+
|
|
38
|
+
`test/sdk-contract.compile.ts` 随 `npm run check` 编译:直接将真实 ExtensionAPI 传给 install,将生成工具交给正式 registerTool,将真实 ExtensionToolContext/ToolLoadout 赋给消费接口,并检查原生 Bash factory、入口 ExtensionFactory。负向断言确认非法 readonly 注册 guidelines、半截 Usage、非法事件返回/快捷键、同步 command handler 被拒绝。没有 as any、@ts-ignore 或修改 SDK。`index.mjs` 同时启用 @ts-check。
|
|
39
|
+
|
|
40
|
+
类型编译、无模型 SDK smoke、真实 TUI 行为测试分层报告;任何一层单独通过都不代表所有宿主行为通过。
|
|
41
|
+
|
|
42
|
+
实现没有依赖 `pi.getAllTools()` 取得原 execute,因为该接口没有这个字段。Bash 使用公开原生 factory,第三方工具使用 `ctx.executeTool`。
|
|
43
|
+
|
|
44
|
+
## 语义限制
|
|
45
|
+
|
|
46
|
+
`hiddenDeclarations` 只影响请求声明;原工具可能仍被其他编排器发现。路由守卫与代理配合,但不作为插件权限安全边界。
|
|
47
|
+
|
|
48
|
+
嵌套调用的 `nestedCalls` 和 usage 归父工具结果记录。父工具提前返回后,后台结果由本扩展 自己保存;不会假设原父记录会被 Pi 自动补齐。
|
|
49
|
+
|
|
50
|
+
原工具的 timeout、HTTP/stdio 连接、MCP cancellation/elicitation 语义继续由 Pi 原生实现决定。后台化不延长 MCP request timeout,也不表示服务器支持 MCP Tasks。
|
|
51
|
+
|
|
52
|
+
## 最终结果与取消
|
|
53
|
+
|
|
54
|
+
ctx.executeTool 的 Outcome.isError 是最终裁决,可能由 afterToolCall 修改而与 result.isError 不同。v0.1.4 将其单独持久化;meantime result 按最终裁决返回,保留原结果文件。cancelRequestedAt 不参与错误分类。父前台取消传原 signal.reason,手动后台取消发独立 controller.abort();底层都使用原生 executeTool/MCP 通路。
|
|
55
|
+
|
|
56
|
+
ui_prompt_end 后使用 setImmediate 延后恢复 detach/通知,给原生权限 Promise 的拒绝裁决结算机会。实际 select 菜单与无 UI fallback 分层验证,不把代理模拟选择等同真实用户授权。
|
package/docs/DESIGN.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# 设计说明与决策记录
|
|
2
|
+
|
|
3
|
+
版本:0.2.0。宿主目标:Pi 1.3.0。
|
|
4
|
+
|
|
5
|
+
## 1. 范围
|
|
6
|
+
|
|
7
|
+
本实现只扩展 Pi,不修改其核心、不使用子 Agent。支持两种用户诉求:模型预先选择后台执行,用户在运行中通过快捷键或 steer 要求继续其他工作。
|
|
8
|
+
|
|
9
|
+
实现采用两类 adapter 和一个 runtime:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
model -> bash + __meantime__ -> Pi 原生 Bash factory ------+
|
|
13
|
+
|
|
|
14
|
+
model -> original_mt + __meantime__ -> ctx.executeTool ---+--> TaskRuntime
|
|
15
|
+
| | |
|
|
16
|
+
原工具校验/权限/钩子 | +--> TaskStore
|
|
17
|
+
| | +--> completion outbox
|
|
18
|
+
MCP/扩展工具 | +--> meantime 查询
|
|
19
|
+
|
|
|
20
|
+
Ctrl+Shift+B / steer / auto timeout -> 只释放 foreground wait-+
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Bash 用原生 factory 保留 Shell 选择、命令前缀、环境、超时和进程清理语义。运行时才调用 `pi.getSettings()`;加载期只构造无 settings 的基础定义,以免在 Pi runtime 未绑定时访问只允许运行期使用的 API。
|
|
24
|
+
|
|
25
|
+
其他工具只代理其名称和参数,**不抓取/缓存第三方 execute 函数**。每次通过 `ctx.executeTool` 分派到当前注册的原工具,避免重连后仍指向陈旧连接。
|
|
26
|
+
|
|
27
|
+
## 2. 两个生命周期
|
|
28
|
+
|
|
29
|
+
任务执行和前台等待分别建模。一个任务有 execution state 和 delivery state 两个轴:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
execution: running -> completed / failed
|
|
33
|
+
-> cancel_requested -> failed / completed
|
|
34
|
+
-> controller lost without final result -> outcome_unknown
|
|
35
|
+
|
|
36
|
+
delivery: foreground -> background
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
“completed”表示工具调用成功返回,不自动表示某个工具自行提交的外部作业也已完成。“failed”表示调用返回错误,不宣称副作用被回滚。
|
|
40
|
+
|
|
41
|
+
运行时持有独立 AbortController。前台阶段,父 signal 的真正取消会转发到任务;detach 时拆除这条关系。detach 以后父回合结束、下一轮的取消,都不会误杀已交给后台的任务。显式 `meantime cancel` 或 `/meantime cancel <id>` 请求取消该任务。
|
|
42
|
+
|
|
43
|
+
所有 detach 触发最终进入同一个方法。没有 `ctx.abort()`,没有捕获再重投用户输入。`input` 返回 `continue`,Pi 自己继续处理输入文本、附件和队列。
|
|
44
|
+
|
|
45
|
+
`no` 模式是严格前台,用户快捷键也不强行破坏它。`detachOnSteer` 默认 true;false 时 input 仍返回 continue,但不触发 detach。快捷键、显式命令和自动阈值独立工作。
|
|
46
|
+
|
|
47
|
+
## 3. 调用进入门槛
|
|
48
|
+
|
|
49
|
+
仅仅调用 `ctx.executeTool()` 就立刻返回父结果有风险:嵌套调用可能仍在等 Pi 的顺序队列;Pi 真正分派原工具时会读取当前 assistant message。
|
|
50
|
+
|
|
51
|
+
代理为每个父调用创建 ticket。只有看到匹配的嵌套 `tool_call`,才允许 detach。此时原调用已由 Pi 的管线建立上下文。这个门槛不会绕过原来的权限或队列;如果原调用尚未进入,后台请求会等待。
|
|
52
|
+
|
|
53
|
+
这提供更稳的归属关系,但不把嵌套调用 API 变成官方独立任务 API。多层嵌套、SDK 自定义全局顺序模式和晚到 UI 交互仍要在实际宿主中验收。
|
|
54
|
+
|
|
55
|
+
## 4. 工具声明、可调用性和绕行
|
|
56
|
+
|
|
57
|
+
复制原工具的 exposure:`direct`→`direct`、`codemode`→`codemode`、`deferred`→`deferred`;`hidden`、`model-only` 不代理。Pi MCP 默认 `codemode`,因此相应 `_mt` 代理从 Codemode 中调用,**不会升级成模型直连工具**。注意 Codemode 的工具列表也可能同时包含原工具和 `_mt` 代理;默认 `blockOriginalCalls` 阻止绕过,但这不等于从 Codemode 描述中消除原始工具。
|
|
58
|
+
|
|
59
|
+
原工具保持原 exposure 和注册状态。`meantime.prepareLoadout` 用 `hiddenDeclarations` 去掉已有代理的原工具直接声明。原工具 `hidden` exposure 不可用于这里,因为它会同时阻断内部调用。
|
|
60
|
+
|
|
61
|
+
只有继承 direct 的代理直接向模型声明;codemode/deferred 代理保持间接调用。代理的 `reachesTools` 明确返回原工具名,供 Pi 的 MCP readiness hook 使用。
|
|
62
|
+
|
|
63
|
+
隐藏声明不等于访问控制。Codemode 的工具发现、历史消息或其他插件仍可能知道原名。默认还用 `tool_call` 路由守卫:只有对应代理 ticket 发起的原调用通过;普通直接调用原名返回使用代理名的提示。这个守卫服务于一致路由,不宣称构成针对恶意扩展的安全沙箱。
|
|
64
|
+
|
|
65
|
+
权限检查仍在原调用管线发生。某些权限扩展可能同时询问代理和原工具,扩展不会为消除重复询问而跳过原审批。
|
|
66
|
+
|
|
67
|
+
## 5. Schema 装饰与结果契约
|
|
68
|
+
|
|
69
|
+
`__meantime__` 是顶层保留属性。包装前检测声明和 required 列表中的冲突。双下划线降低碰撞概率,没有协议级保留名保证。
|
|
70
|
+
|
|
71
|
+
支持显式 object-root 输入 Schema,复制后直接添加控制属性。保留 required、additionalProperties 和所有嵌套 Schema。**不用 Type.Intersect/allOf 拼接**,因为原对象关闭额外属性时,交叉类型会拒绝新控制参数。
|
|
72
|
+
|
|
73
|
+
根级组合、条件、patternProperties、propertyNames、property-count、const/enum 等需要更复杂的等价变换;扩展拒绝包装并保留原工具。没有偷偷放松业务约束。调用原工具时再次经过 Pi 的原始参数验证。
|
|
74
|
+
|
|
75
|
+
未 detach 的调用返回原生结果,不附加 task、不二次截断、不移除图片;结束后从 runtime 删除,由 Pi 原生会话历史保存结果,不创建自家存储目录或文件。Bash 原样重抛执行异常。代理保留嵌套调用 hook 后的结果及最终 Outcome.isError,但不重复上报 Pi 已自动聚合的 usage。
|
|
76
|
+
|
|
77
|
+
只有真正 detach 才返回 `task + result_available:false`;`meantime(action="wait", id=..., timeout=0)` 继续返回有预算的任务 envelope。Bash outputSchema 是原生输出与后台句柄的 union,并继承 constrainedSampling。通用代理通过 getAllTools 发现的元数据不含 outputSchema,暂用 `{}`(unknown)描述原生输出或句柄,不再虚报统一 envelope 类型。
|
|
78
|
+
|
|
79
|
+
同名 bash 的 tool_result 在前台结果或 detach 句柄返回时触发一次;detach 后晚结果不再触发该 bash hook。其他工具代理通过 ctx.executeTool 调原名,内层真实完成仍经过原工具 hook。meantime 读取结果另经过 meantime 自己的 hook。
|
|
80
|
+
|
|
81
|
+
## 6. 动态注册与生命周期
|
|
82
|
+
|
|
83
|
+
没有假设 Pi 存在通用 `tools_changed` 事件。
|
|
84
|
+
|
|
85
|
+
- `session_start` 和 `before_agent_start` 扫描注册表。
|
|
86
|
+
- `prepareLoadout` 观察公开 loadout,排到微任务后同步,不在该回调内递归注册。
|
|
87
|
+
- `mcp_servers_change` 触发重扫,但不被当成完整的工具列表变化通知。
|
|
88
|
+
- 500ms 的有界扫描覆盖迟到注册、Schema 更新和没有事件的变化。
|
|
89
|
+
- 注册签名稳定时不重新注册,不反复污染上下文和缓存。
|
|
90
|
+
- 原工具消失/隐藏、变得不兼容时撤下自己的代理,原工具不被修改。
|
|
91
|
+
- 名称超过长度限制时使用稳定 hash 后缀;遇到他人占用名称就放弃包装。撤下路径也不会覆盖后来占据该名称的第三方工具。
|
|
92
|
+
|
|
93
|
+
工具注册和工具列表变化之间有公开 API 无法完全消除的同步窗口。执行期再次检查当前原工具和签名,失败时不启动操作。
|
|
94
|
+
|
|
95
|
+
## 7. 通知与消息归属
|
|
96
|
+
|
|
97
|
+
只有满足全部条件才入队完成通知:后台执行已结束、原 wrapper 的结果已被宿主最终确认、结果还未被主动读取、当前 runtime/会话仍有效。
|
|
98
|
+
|
|
99
|
+
最终确认同时验证 tool-call ID 和 task UUID,防止旧会话迟到事件遇到重复 call ID。快完成的前台任务不发第二次通知。
|
|
100
|
+
|
|
101
|
+
Bash 任务保存完整命令供 wait 详情读取;任务列表和完成通知把命令合为单行、截取最多 120 UTF-8 字节的预览,截断时加省略号。不新增 name/description 入参。
|
|
102
|
+
|
|
103
|
+
通知包含 task ID、工具名、状态、Bash 命令预览,以及约 1KB 的文本输出尾巴;尾巴用 `<untrusted-output-tail>` 包裹并声明为不可信数据,不是指令。通知明确声明是自动完成消息,不是用户输入或批准;没有新信息时静默处理。尾巴只含文本,不含图片和结构化数据;尾巴或落盘结果截断都会标注 truncated。省去常见场景下多一轮 `meantime(action="wait", id=..., timeout=0)`;完整保留结果仍通过 `meantime(action="wait", id=..., timeout=0)` 读取。
|
|
104
|
+
|
|
105
|
+
采用 `followUp + triggerTurn:true`;多项完成合成一条消息。只有公开 `ctx.isIdle()` 为真才交给 Pi;忙时保留 pending,后续 flush 重新筛选,排除期间被 wait 读取的任务,不消耗失败重试次数。部分已读批次只通知仍未读的任务。同步入队失败最多重试三次。`queued`只表示 sendMessage 调用已成功返回,不等于模型必然收到。宿主接口没有投递确认,进程崩溃时不宣称 exactly-once。重启后不自动投递历史通知,避免旧任务打断新工作。
|
|
106
|
+
|
|
107
|
+
Pi 1.3.0 的公开扩展事件恢复路径(核对 SDK `dist/core/extensions/types.d.ts` 的 `AgentSettledEvent`、`SessionCompactEvent`、`SessionCompactFailedEvent`、`SessionTreeEvent`):
|
|
108
|
+
- `agent_settled`、`session_compact_failed`:直接 flush。
|
|
109
|
+
- `session_compact`、`session_tree`:事件处理时仍处于 busy;排一次 `setImmediate`,让 Pi 完成清理后再 flush。回调只作用于捕获的同一个 runtime,不能替新会话补发。不使用定时轮询。
|
|
110
|
+
- tree 取消/失败:Pi 1.3.0 的 `AgentSession.navigateTree()` 在取消返回和失败抛出时,只在 `finally` 清 `_branchSummaryAbortController`,不发结束事件;成功路径才发 `session_tree`。此公开 API 缺口按 Holder 裁定保留,不能承诺取消/失败后立即补发或在完全没有后续事件时自动恢复。pending 不丢失:`TaskRuntime.flushNotifications()` 在 sink 返回 false 时直接返回,不把任务标记 queued/failed;`prune()` 保护 pending,后续 `agent_settled`、compact/tree 成功、compact 失败等 flush 仍由 `eligible()` 选中。关闭/恢复的历史通知抑制规则不变。
|
|
111
|
+
|
|
112
|
+
真实 SDK 回归 `tree-cancel` 证明缺口不会把任务变成不可恢复状态:tree 期间完成 → 取消后 idle、仍 pending、completion=0 → 发下一条 prompt → `agent_settled` → completion 恰好一次,下一模型请求看见它。`compact`、`compact-cancel`、`tree` 分别证明成功压缩、取消压缩和成功导航后未读结果无需再发 prompt 即通知一次。
|
|
113
|
+
|
|
114
|
+
复现与回归命令:`node scripts/pi-smoke.mjs`。脚本使用真实 Pi 1.3.0 AgentSession、原生 Bash 和本地脚本化 provider(无网络请求),由 Pi 驱动工具派发及 follow-up 队列。在 main `7cb217f`、源码未改时,before-wait 场景顺序为:request 1 → Bash 自动 detach → request 2 中等待 350ms(Bash 在约 120ms 完成,通知 debounce 5ms)→ wait 返回完整 RACE_A → request 3 → meantime-completion 送达 → request 4 看到该 completion。说明 observed 筛选发生在 Pi 入队之前,无法撤回已经交给 Pi 的消息。修复后该场景只有三次请求、无 completion。脚本还覆盖 wait 进行中完成、未读任务通知一次、部分批次已读,以及 wait timeout=0 尚未取到结果后仍正常通知。每个场景打印消息顺序、请求数及通知任务。
|
|
115
|
+
|
|
116
|
+
`wait` 按 Node 定时器上限分段等待,不截断总等待时长。预先取消和等待中取消都终止等待,不取消 producer、不确认结果。恢复任务没有本地 Promise 可等待时立即返回当前状态。
|
|
117
|
+
|
|
118
|
+
## 8. 取消、关闭与恢复
|
|
119
|
+
|
|
120
|
+
Bash 使用 Pi 原生进程清理;MCP/一般扩展工具仍经原名 ctx.executeTool 和 Pi 原生 AbortSignal 通路。最终 Outcome.isError(包括 afterToolCall 的最终裁决)决定 failed/completed,result payload 保留原样;最终错误位与文本单独写入 completion 索引,避免把旧 payload.isError 冒充最终裁决。cancelRequestedAt 仅记请求事实;没有最终结果且失去本地控制时显示 outcome_unknown。
|
|
121
|
+
|
|
122
|
+
`/reload`、会话切换和退出请求取消、等待有限 grace,并停止旧通知。尚未结束的前台调用也经过后台晋升路径,记录索引再释放句柄。普通 detach 若索引写入失败则保持前台;强制取消/关闭仍可释放句柄并显示 storageError。**不承诺 reload 保活。** 同一会话内树导航共享任务,不重启 runtime、不请求取消;fork/clone 使用新 owner,不接管复制历史中的旧任务。
|
|
123
|
+
|
|
124
|
+
进程全局配额只保留存活 producer 的租约,不保留旧宿主上下文。非协作任务在实际 Promise 结束前不释放配额,防止反复 reload 绕过并发上限。Pi 进程退出后,这种保护自然结束;这不等于能追踪失联的远端作业。
|
|
125
|
+
|
|
126
|
+
恢复通过 Pi getEntries() 读取全部分支中的 meantime-index,按 owner 过滤并应用 task、completion 和 removed 记录。缺少 completion 的任务投影为 outcome_unknown;不恢复 Promise、不盲目 kill PID、更不重放原请求。晚到的超大工具文本文件可由 list/wait 暴露路径,但不证明执行结果。
|
|
127
|
+
|
|
128
|
+
旧 producer 只能写原 owner 目录的超大工具文本,不能向新会话追加索引或通知。通知、已读确认和重试都只在内存;恢复后不发历史通知。不支持两个 Pi 进程同时操作同一 owner 的存储。
|
|
129
|
+
|
|
130
|
+
## 9. 存储、大小与成本
|
|
131
|
+
|
|
132
|
+
每个会话使用 hashed 目录,目录 0700、文件 0600;写临时文件再原子 rename。task ID 严格校验,拒绝结果文件 symlink。这个本地存储位于当前 OS 用户的信任域,不能代替文件系统或沙箱隔离。
|
|
133
|
+
|
|
134
|
+
除用于辨识任务的 Bash 命令外,不保存业务入参和环境变量。结果可能含敏感数据,因此采用私有权限,但不做自动内容脱敏;保留原工具结果钩子的脱敏结果。
|
|
135
|
+
|
|
136
|
+
转后台时 Pi appendEntry 保存任务索引(id、tool、kind、command、mode、createdAt、detachReason)。绑定会话内完成时追加 completion(id、endedAt、isError、cancelRequestedAt?、exitCode?、text、truncated、outputPath?)。Bash 使用原生有界文本,路径优先 structuredContent.full_output_path,其次 details.fullOutputPath;不额外截断、不写扩展文件。其他工具 cleanText 后不超过 50KiB 的文本只进索引;更大时原子写入 `<sha256(owner)[0:32]>/mt_xxx.txt`,索引保留约 2000 字节预览和路径。图片和 structuredContent 只留当前进程内存,恢复只有文本,回执说明省略。
|
|
137
|
+
|
|
138
|
+
默认保留最近完成的128个后台任务;等待通知、仍有全局存活租约以及没有 endedAt 的不确定任务暂不淘汰。活跃 runtime 在完成、通知、读取时执行清理;关闭后的晚结果延后至原会话恢复清理。显式读取时优先保留刚返回条目。清理先删除扩展自己的工具文本文件(绝不删除 Pi 的 Bash 文件),再追加 removed 索引事件,防止恢复时复活为 unknown;删除失败保留索引与内存记录并暴露 storageError,后续可重试。文件和会话索引之间没有跨文件事务保证。
|
|
139
|
+
|
|
140
|
+
超大工具文本文件默认最多4MiB;只有文件本身被截断才标记 truncated,索引预览不等于丢失完整文本。任务列表默认10条、最多32条并支持 offset。旧版完成文件格式不迁移、不读取,也不自动删除。
|
|
141
|
+
|
|
142
|
+
入站工具已经构造出来的超大结果,在截断前仍可能占内存,扩展无法用结果截断阻止上游一次性分配。用户移除整个结果目录时会失去历史数据;扩展不自动跨会话清理其他目录。
|
|
143
|
+
|
|
144
|
+
原始 `usage` 只在当前进程的任务结果中保留。前台嵌套 usage 由 Pi 自动归到父调用,包装器不重复加一次;后台晚到 usage 的 Pi 全局汇总可能不完整。扩展不修改 Pi 的历史费用记录。这是明确的已知宿主集成缺口,不是通过测试的指标。
|
|
145
|
+
|
|
146
|
+
## 10. 验收层级
|
|
147
|
+
|
|
148
|
+
1. Runtime/Schema/Store 单元测试。
|
|
149
|
+
2. 公共接口宿主替身下的适配器和事件测试。
|
|
150
|
+
3. 同一适配器配真实本地 Shell 和真实 MCP JSON-RPC/stdio 传输。
|
|
151
|
+
4. 实际 Pi SDK、provider 消息投影和 TUI/RPC 集成。
|
|
152
|
+
|
|
153
|
+
当前各层的验证范围与复跑方法见 [VALIDATION.md](VALIDATION.md)。仓库不包含原始运行日志或会话证据。
|
|
154
|
+
|
|
155
|
+
## 10. 审批与手动操作(v0.1.4)
|
|
156
|
+
|
|
157
|
+
Pi 的 ui_prompt_end 可能早于权限钩子的 Promise 结算。prompt 结束后保留一轮 setImmediate 屏障,期间所有 detach、通知排队/flush 继续暂停,让原生拒绝先成为错误结果;不能用“菜单已关闭”推断“审批通过”。这覆盖本次验证的同步 select 结果后直接返回裁决,不为任意插件另加的长期异步裁决作保证。
|
|
158
|
+
|
|
159
|
+
任务 ID 为 `mt_` + 8 位 hex(生成时查重),不兼容旧版 `bg_<uuid>` 记录。/meantime cancel 与 meantime 工具接受完整 ID 或唯一前缀(可省略 `mt_`),前缀有歧义时报错并列出候选;缺失/多参数只显示当前会话列表与用法。查找仅限会话 owner,完成后的重复取消不再发 AbortSignal。/meantime 展示 ID、工具、状态、取消请求/recovered 标志,不展示内部运行时大 JSON。后台回执保留结构化元数据,同时告诉人可输入的命令。
|
|
160
|
+
|
|
161
|
+
操作反馈使用公开 ui.setWidget 的同 key 字符串数组,标记 Latest background control,仅显示最近一次操作,不污染模型上下文、不抢焦点。原因是 Pi 1.3.0 info notify 的旧状态行会在重绘时引用最新共享文本;本扩展不修改该上游实现。状态列表运行中优先,TUI 的 /meantime 打开实时任务面板,非 TUI 显示当前会话全部任务,不分页;会话停止和开始均清面板。它不是永久操作历史。
|
|
162
|
+
|
|
163
|
+
## meantime wait
|
|
164
|
+
|
|
165
|
+
工具仅暴露 `list | wait | cancel`,不兼容旧动作或 `timeout_ms`。`meantime(action:"wait", id, timeout?)` 阻塞到任务结束、超时或调用被中止,然后返回保留结果;运行中无结果时返回状态与 `result_available:false`。`timeout` 单位为秒,支持小数,默认 30,不设上限;`timeout: 0` 立即返回当前状态或结果。也可用 `ids` 配合 `mode: "any" | "all"`(默认 all)等待多个任务,使用相同的秒制超时。工具边界只转换一次为毫秒,runtime 内部仍用毫秒并分段处理 setTimeout 上限。wait 不取消任务。
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Same-name execute wrapping: integration gate
|
|
2
|
+
|
|
3
|
+
Status: **not suitable as a replacement for the existing proxy implementation**. The proposal works mechanically but fails the existing post-hook retention contract. No production entry point imports this probe.
|
|
4
|
+
|
|
5
|
+
Run from the package root:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
node --experimental-strip-types scripts/same-name-gate.mjs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This deliberately exits 1 at the result-retention assertion on the pinned Pi 1.3.0 SDK. It uses real SDK nested dispatch, the real QuickJS codemode extension, native Bash, and production `TaskRuntime`/`TaskStore`. It makes no model or network request and removes its temporary session and task files.
|
|
12
|
+
|
|
13
|
+
## Evidence
|
|
14
|
+
|
|
15
|
+
The probe mutates the registered AgentTool in `prepareLoadout`: it decorates the parameter schema, captures the original `execute`, and replaces it with `TaskRuntime.run`. It does not register a competing tool under the target name.
|
|
16
|
+
|
|
17
|
+
Verified:
|
|
18
|
+
|
|
19
|
+
- Original name and strict input schema work; the control property is stripped before original execution.
|
|
20
|
+
- Permission rejection prevents original execution.
|
|
21
|
+
- Background execution returns a task handle while the producer keeps running.
|
|
22
|
+
- A real codemode script calls the original name through the patched object.
|
|
23
|
+
- Native Bash uses the same mechanism.
|
|
24
|
+
- Source re-registration executes v2, hidden exposure withdraws it, restoration executes v2 again.
|
|
25
|
+
|
|
26
|
+
A fixture `tool_result` handler replaces `RAW_FIXTURE` with `REDACTED_FIXTURE` and changes the authoritative outcome to `isError: true`. Actual results:
|
|
27
|
+
|
|
28
|
+
| Path | Returned to caller | Stored task result | Stored error flag |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| Foreground | `REDACTED_FIXTURE`, error true | `v1:RAW_FIXTURE` | false |
|
|
31
|
+
| Background | Running task handle | `v1:RAW_FIXTURE` | false |
|
|
32
|
+
|
|
33
|
+
On the background path, the original result hook receives the task handle. No second hook invocation occurs when the producer completes. Reading the stored result through `meantime` would invoke hooks for **meantime**, which does not repair a hook that targets the original tool name.
|
|
34
|
+
|
|
35
|
+
Session replacement/reload and MCP detachment were not exercised by this gate; it already fails the result contract. Earlier registry-lifecycle probes alone do not establish result-pipeline parity.
|
|
36
|
+
|
|
37
|
+
## Cause and consequence
|
|
38
|
+
|
|
39
|
+
Pi's `runToolCall` validates and runs pre-call hooks, awaits the selected `execute`, then invokes `afterToolCall`. Wrapping only `execute` places `TaskRuntime` persistence **before** that last step. Returning a handle completes the host call, so a later producer result cannot enter its original post-call pipeline.
|
|
40
|
+
|
|
41
|
+
This was already a documented limitation of the special Bash path in DESIGN.md. Applying that implementation to arbitrary third-party tools broadens the limitation to tools whose existing proxies currently retain post-hook results and authoritative outcome flags. This is a behavioral regression, not just an unsupported shared-reference assumption.
|
|
42
|
+
|
|
43
|
+
A renderer only changes presentation. Schema injection only changes validation. Watching `tool_execution_end` cannot supply a post-hook background result that the host never produced. Dispatching the same tool again is a second call, with duplicate permissions and possible side effects; manually redispatching extension hooks requires host internals and lifecycle handling. Neither is a transparent fix for this implementation.
|
|
44
|
+
|
|
45
|
+
## Required integration boundary
|
|
46
|
+
|
|
47
|
+
A host-supported background wrapper must retain a `next` operation encompassing **native execution and final result hooks**, and keep that operation alive after returning the task handle. Pre-call permission checks must finish before detachment; sequential execution policy, cancellation ownership, final outcome flags, and session replacement must remain explicit. A host API that wraps only `execute` repeats this defect, even if it is officially supported.
|
|
48
|
+
|
|
49
|
+
Until that boundary exists or different semantics are explicitly chosen, keep the existing production adapter. Do not silently fall back to `_bg` or publish the proposed mutation adapter as transparent.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 验证与复跑
|
|
2
|
+
|
|
3
|
+
固定开发基线:Pi/SDK 1.3.0,锁文件固定依赖;当前开发版在 Node 26.10.0 验证。仓库只保留代码和可复跑 fixture,不包含运行日志、私有环境路径、auth、生成会话或原始验收证据。
|
|
4
|
+
|
|
5
|
+
## 当前开发版已验证范围
|
|
6
|
+
|
|
7
|
+
- 正式 SDK 类型检查和单元/组件测试通过,覆盖前台不落盘、后台晋升、晚结果、owner 隔离、结果清理失败后恢复重试。
|
|
8
|
+
- 无模型调用的真实 SDK smoke:原生 Bash、本地 MCP、后台结果嵌套读取;使用磁盘 SessionManager,确认前台无扩展文件/索引,重开会话文件并重新加载扩展后读回 completion 文本。小 Bash 无文件,超大 Bash 引用 Pi 原生文件,超大扩展工具文本只写一个 `.txt`;恢复保留路径及预览,图片/结构化数据不持久化。
|
|
9
|
+
- 官方 SDK 结果钩子、取消和权限检查通过;选择器由测试适配器提供,不代表真实终端菜单验收。
|
|
10
|
+
|
|
11
|
+
## 0.1.4 历史验证
|
|
12
|
+
|
|
13
|
+
以下为旧版本范围,尚未对当前存储格式重跑;旧验收脚本可能仍依赖旧任务格式:
|
|
14
|
+
|
|
15
|
+
- 正式 SDK 类型契约及87项单元/组件测试通过
|
|
16
|
+
- 无真实模型调用的SDK smoke通过
|
|
17
|
+
- 52个真实CLI/PTY场景通过:原有后台行为17、会话/竞态14、MCP取消配对9、手动取消/steer6、真实审批菜单6
|
|
18
|
+
- 10项官方SDK结果钩子/权限检查通过;该层选择器是测试适配器,与真实菜单测试分开
|
|
19
|
+
- 实际进程SIGKILL后,官方同会话恢复34项断言通过;无业务重放,已完成结果保留
|
|
20
|
+
- 另做真实RPC传输3场景、89断言:手动命令、取消、完成消息与前台abort;这不代表第三方客户端已渲染UI
|
|
21
|
+
|
|
22
|
+
## 本地运行
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm ci --ignore-scripts
|
|
26
|
+
npm run check
|
|
27
|
+
npm run demo
|
|
28
|
+
npm run test:pi
|
|
29
|
+
node --experimental-strip-types scripts/sdk-cancel-permissions.mjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
旧版本完整宿主验收入口(当前存储格式未复验):
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
bash validation/run-all.sh /path/to/new-empty-output
|
|
36
|
+
PI_E2E_NODE="$(command -v node)" python3 e2e-restart/run-restart.py proof-new
|
|
37
|
+
python3 e2e-restart/verify-restart.py proof-new
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
真实PTY套件使用Python3、localhost TCP和官方Pi CLI。每例隔离HOME/agent目录,仅使用本地HTTP模型mock和MCP stdio fixture,不需要真实API key。脚本拒绝覆盖已有证据。可选屏幕重建使用Pillow/wcwidth,仅把原始ANSI还原为终端字符图,不是图形桌面截图。RPC增量验证不在当前52场景脚本内。
|
|
41
|
+
|
|
42
|
+
## 边界
|
|
43
|
+
|
|
44
|
+
未测试真实远端HTTP/SSE MCP及其认证/elicitation、小时/天级长跑、断电/fsync耐久、跨进程并发写事务、所有终端尺寸/主题或所有第三方RPC客户端。取消沿用原生语义,不保证远端回滚副作用。权限屏障覆盖提示结果后立即结算的钩子路径,不为任意插件追加的长期异步裁决作保证。最近操作面板是快照,后台结算后可用 /meantime 刷新。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["mcp__*"],
|
|
3
|
+
"exclude": ["mcp__ide__*", "read", "write", "edit", "codemode", "tool_search"],
|
|
4
|
+
"autoDetachMs": 1000,
|
|
5
|
+
"maxRunning": 4,
|
|
6
|
+
"maxRetained": 128,
|
|
7
|
+
"maxResultBytes": 4194304,
|
|
8
|
+
"reconcileMs": 500,
|
|
9
|
+
"shutdownGraceMs": 1000,
|
|
10
|
+
"notifyDebounceMs": 50,
|
|
11
|
+
"blockOriginalCalls": true
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Development only: start a local MCP server with sleep/echo/failure fixtures.
|
|
2
|
+
// This starts a plain Node process, not a delegated coding agent.
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
export default function fixture(pi) {
|
|
5
|
+
pi.registerMcpServer('meantime_fixture', {
|
|
6
|
+
command: process.execPath,
|
|
7
|
+
args: [fileURLToPath(new URL('../test/fixtures/mcp-server.mjs', import.meta.url))],
|
|
8
|
+
exposure: 'direct', timeout: 15,
|
|
9
|
+
});
|
|
10
|
+
}
|
package/index.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// Pi supplies this package when loading an extension. No runtime npm dependency
|
|
3
|
+
// is required by the task runtime itself. See docs/API-CONTRACT.md.
|
|
4
|
+
import { createBashToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
5
|
+
import { install } from './src/extension.ts';
|
|
6
|
+
import { loadConfig } from './src/config.ts';
|
|
7
|
+
|
|
8
|
+
/** @param {import('@earendil-works/pi-coding-agent').ExtensionAPI} pi */
|
|
9
|
+
export default function backgroundPoc(pi) {
|
|
10
|
+
install(pi, {
|
|
11
|
+
config: loadConfig(),
|
|
12
|
+
// During extension load the session runtime is not yet bound. Only the
|
|
13
|
+
// execution-time factory below may call pi.getSettings().
|
|
14
|
+
baseBash: createBashToolDefinition(process.cwd()),
|
|
15
|
+
makeBash: (cwd) => {
|
|
16
|
+
// Reuse the live, effective host settings instead of rereading project files.
|
|
17
|
+
const settings = pi.getSettings();
|
|
18
|
+
return createBashToolDefinition(cwd, {
|
|
19
|
+
shellPath: settings.shellPath,
|
|
20
|
+
commandPrefix: settings.shellCommandPrefix,
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-meantime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
|
|
5
|
+
"description": "Pure Pi 1.3 extension: managed bash and typed tool proxies with cooperative background execution",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22.19.0"
|
|
10
|
+
},
|
|
11
|
+
"pi": {
|
|
12
|
+
"extensions": [
|
|
13
|
+
"./index.mjs"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"index.mjs",
|
|
18
|
+
"src",
|
|
19
|
+
"scripts",
|
|
20
|
+
"test",
|
|
21
|
+
"tsconfig.json",
|
|
22
|
+
"README.md",
|
|
23
|
+
"docs",
|
|
24
|
+
"examples",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "node --experimental-strip-types --test test/*.test.ts",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"check": "npm run typecheck && npm test",
|
|
31
|
+
"demo": "node --experimental-strip-types scripts/demo.ts",
|
|
32
|
+
"test:pi": "node --experimental-strip-types scripts/pi-smoke.mjs"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@earendil-works/pi-coding-agent": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "25.1.0",
|
|
44
|
+
"typescript": "5.8.3",
|
|
45
|
+
"@earendil-works/pi-coding-agent": "1.3.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/scripts/demo.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Executable offline model-facing trace using the public-contract host double
|
|
2
|
+
* and a real MCP stdio server. No paid model or API key is used. */
|
|
3
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { install } from '../src/extension.ts';
|
|
7
|
+
import { validateConfig } from '../src/config.ts';
|
|
8
|
+
import { FakePi, basicTool, fixtureBash } from '../test/host.ts';
|
|
9
|
+
import { FixtureMcpClient } from '../test/mcp-client.ts';
|
|
10
|
+
import { until } from '../test/helpers.ts';
|
|
11
|
+
import type { Snapshot } from '../src/runtime.ts';
|
|
12
|
+
const root = mkdtempSync(join(tmpdir(), 'pi-bg-demo-'));
|
|
13
|
+
const client = new FixtureMcpClient();
|
|
14
|
+
const pi = new FakePi('demo');
|
|
15
|
+
for (const tool of await client.connect()) pi.registerTool(basicTool(`mcp__fixture__${tool.name}`,
|
|
16
|
+
(_id, args, signal) => client.call(tool.name, args, signal), { parameters: tool.inputSchema }));
|
|
17
|
+
const instance = install(pi, { config: validateConfig({ storageRoot: root, autoDetachMs: 0, notifyDebounceMs: 5 }), makeBash: fixtureBash });
|
|
18
|
+
try {
|
|
19
|
+
await pi.emit('session_start', { type: 'session_start' });
|
|
20
|
+
console.log('HOST: deterministic Pi contract double; TRANSPORT: real MCP JSON-RPC over stdio');
|
|
21
|
+
console.log('MODEL TOOLS:', pi.modelTools().join(', '));
|
|
22
|
+
console.log('\nMODEL CALL: mcp__fixture__slow_mt({ms:180,text:"MCP completed",__meantime__:"yes"})');
|
|
23
|
+
const out = await pi.call('mcp__fixture__slow_mt', { ms: 180, text: 'MCP completed', __meantime__: 'yes' });
|
|
24
|
+
const task = (out.result.structuredContent as { task: unknown }).task as Snapshot;
|
|
25
|
+
console.log('TOOL RESULT:', JSON.stringify(out.result.structuredContent));
|
|
26
|
+
console.log('\nMODEL CALL WHILE MCP RUNS: bash({command:"printf PARALLEL",__meantime__:"no"})');
|
|
27
|
+
const parallel = await pi.call('bash', { command: 'printf PARALLEL', __meantime__: 'no' });
|
|
28
|
+
console.log('BASH:', parallel.result.content);
|
|
29
|
+
await until(() => pi.notifications.length > 0);
|
|
30
|
+
console.log('\nNOTIFICATION:', pi.notifications[0]!.content);
|
|
31
|
+
const done = await pi.call('meantime', { action: 'result', id: task.id });
|
|
32
|
+
console.log('COLLECTED:', JSON.stringify(done.result.structuredContent));
|
|
33
|
+
console.log('MCP REQUEST COUNT:', client.sent.filter(m => m.method === 'tools/call').length);
|
|
34
|
+
} finally {
|
|
35
|
+
await instance.dispose(); await client.close(); rmSync(root, { recursive: true, force: true });
|
|
36
|
+
}
|