@shgroup/opencode-serenity-plugin 0.2.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/README.md +199 -0
- package/bin/opencode-serenity-plugin.js +316 -0
- package/dist/activation.d.ts +40 -0
- package/dist/activation.d.ts.map +1 -0
- package/dist/activation.js +133 -0
- package/dist/activation.js.map +1 -0
- package/dist/bash-override.d.ts +10 -0
- package/dist/bash-override.d.ts.map +1 -0
- package/dist/bash-override.js +24 -0
- package/dist/bash-override.js.map +1 -0
- package/dist/bash-toggle.d.ts +17 -0
- package/dist/bash-toggle.d.ts.map +1 -0
- package/dist/bash-toggle.js +42 -0
- package/dist/bash-toggle.js.map +1 -0
- package/dist/config-schema.d.ts +300 -0
- package/dist/config-schema.d.ts.map +1 -0
- package/dist/config-schema.js +185 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/errors.d.ts +90 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +151 -0
- package/dist/errors.js.map +1 -0
- package/dist/fs/file-system-tool.d.ts +25 -0
- package/dist/fs/file-system-tool.d.ts.map +1 -0
- package/dist/fs/file-system-tool.js +318 -0
- package/dist/fs/file-system-tool.js.map +1 -0
- package/dist/fs/resolve-path.d.ts +53 -0
- package/dist/fs/resolve-path.d.ts.map +1 -0
- package/dist/fs/resolve-path.js +100 -0
- package/dist/fs/resolve-path.js.map +1 -0
- package/dist/hooks/compacting.d.ts +23 -0
- package/dist/hooks/compacting.d.ts.map +1 -0
- package/dist/hooks/compacting.js +90 -0
- package/dist/hooks/compacting.js.map +1 -0
- package/dist/hooks/permission-auto-reply.d.ts +91 -0
- package/dist/hooks/permission-auto-reply.d.ts.map +1 -0
- package/dist/hooks/permission-auto-reply.js +158 -0
- package/dist/hooks/permission-auto-reply.js.map +1 -0
- package/dist/hooks/permission-guards.d.ts +41 -0
- package/dist/hooks/permission-guards.d.ts.map +1 -0
- package/dist/hooks/permission-guards.js +153 -0
- package/dist/hooks/permission-guards.js.map +1 -0
- package/dist/hooks/shell-env.d.ts +20 -0
- package/dist/hooks/shell-env.d.ts.map +1 -0
- package/dist/hooks/shell-env.js +38 -0
- package/dist/hooks/shell-env.js.map +1 -0
- package/dist/hooks/util.d.ts +81 -0
- package/dist/hooks/util.d.ts.map +1 -0
- package/dist/hooks/util.js +172 -0
- package/dist/hooks/util.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/init/init-wizard.d.ts +39 -0
- package/dist/init/init-wizard.d.ts.map +1 -0
- package/dist/init/init-wizard.js +297 -0
- package/dist/init/init-wizard.js.map +1 -0
- package/dist/install.d.ts +117 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +255 -0
- package/dist/install.js.map +1 -0
- package/dist/msm-schema.d.ts +76 -0
- package/dist/msm-schema.d.ts.map +1 -0
- package/dist/msm-schema.js +207 -0
- package/dist/msm-schema.js.map +1 -0
- package/dist/msm.d.ts +25 -0
- package/dist/msm.d.ts.map +1 -0
- package/dist/msm.js +317 -0
- package/dist/msm.js.map +1 -0
- package/dist/session/lib.d.ts +33 -0
- package/dist/session/lib.d.ts.map +1 -0
- package/dist/session/lib.js +475 -0
- package/dist/session/lib.js.map +1 -0
- package/dist/session/session-tool.d.ts +17 -0
- package/dist/session/session-tool.d.ts.map +1 -0
- package/dist/session/session-tool.js +109 -0
- package/dist/session/session-tool.js.map +1 -0
- package/dist/skills/install-skill.d.ts +36 -0
- package/dist/skills/install-skill.d.ts.map +1 -0
- package/dist/skills/install-skill.js +91 -0
- package/dist/skills/install-skill.js.map +1 -0
- package/dist/skills/template-loader.d.ts +79 -0
- package/dist/skills/template-loader.d.ts.map +1 -0
- package/dist/skills/template-loader.js +170 -0
- package/dist/skills/template-loader.js.map +1 -0
- package/dist/state.d.ts +35 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +62 -0
- package/dist/state.js.map +1 -0
- package/dist/tui.d.ts +61 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +279 -0
- package/dist/tui.js.map +1 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +17 -0
- package/dist/types/index.js.map +1 -0
- package/dist/util/config-patch.d.ts +58 -0
- package/dist/util/config-patch.d.ts.map +1 -0
- package/dist/util/config-patch.js +117 -0
- package/dist/util/config-patch.js.map +1 -0
- package/dist/util/git.d.ts +29 -0
- package/dist/util/git.d.ts.map +1 -0
- package/dist/util/git.js +74 -0
- package/dist/util/git.js.map +1 -0
- package/dist/util/init-check.d.ts +22 -0
- package/dist/util/init-check.d.ts.map +1 -0
- package/dist/util/init-check.js +76 -0
- package/dist/util/init-check.js.map +1 -0
- package/dist/util/init.d.ts +54 -0
- package/dist/util/init.d.ts.map +1 -0
- package/dist/util/init.js +87 -0
- package/dist/util/init.js.map +1 -0
- package/dist/util/log.d.ts +25 -0
- package/dist/util/log.d.ts.map +1 -0
- package/dist/util/log.js +28 -0
- package/dist/util/log.js.map +1 -0
- package/dist/util/msm-call.d.ts +48 -0
- package/dist/util/msm-call.d.ts.map +1 -0
- package/dist/util/msm-call.js +86 -0
- package/dist/util/msm-call.js.map +1 -0
- package/dist/util/msm-exec-runtime.d.ts +123 -0
- package/dist/util/msm-exec-runtime.d.ts.map +1 -0
- package/dist/util/msm-exec-runtime.js +532 -0
- package/dist/util/msm-exec-runtime.js.map +1 -0
- package/dist/util/path.d.ts +10 -0
- package/dist/util/path.d.ts.map +1 -0
- package/dist/util/path.js +21 -0
- package/dist/util/path.js.map +1 -0
- package/dist/util/ready-state.d.ts +43 -0
- package/dist/util/ready-state.d.ts.map +1 -0
- package/dist/util/ready-state.js +104 -0
- package/dist/util/ready-state.js.map +1 -0
- package/dist/util/serenity-file.d.ts +30 -0
- package/dist/util/serenity-file.d.ts.map +1 -0
- package/dist/util/serenity-file.js +69 -0
- package/dist/util/serenity-file.js.map +1 -0
- package/dist/util/tui-install.d.ts +61 -0
- package/dist/util/tui-install.d.ts.map +1 -0
- package/dist/util/tui-install.js +94 -0
- package/dist/util/tui-install.js.map +1 -0
- package/docs/architecture-v0.md +294 -0
- package/docs/contract-v0.md +417 -0
- package/docs/plugin-self-contained-msm-v1.md +182 -0
- package/docs/refactor-direction-v1.11.md +78 -0
- package/docs/requirements-v0-scope.md +104 -0
- package/docs/requirements-v0-summary.md +108 -0
- package/docs/rr7-init-design.md +304 -0
- package/docs/v0.1-candidates.md +132 -0
- package/package.json +54 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# Plugin API Contract v0 — 接口/契约层
|
|
2
|
+
|
|
3
|
+
> **会话**:2026-06-04--opencode-serenity-plugin
|
|
4
|
+
> **承接**:`docs/architecture-v0.md`(方案层)
|
|
5
|
+
> **范围**:v0 plugin 暴露给 opencode runtime + LLM 的**全部接口契约**。
|
|
6
|
+
> **不含**:实现代码(`src/`)。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. 契约清单
|
|
11
|
+
|
|
12
|
+
| # | 契约 | 暴露给 | 类型 |
|
|
13
|
+
|---|------|--------|------|
|
|
14
|
+
| C1 | plugin 入口默认 export | opencode loader | `async (api) => PluginReturn` |
|
|
15
|
+
| C2 | `msm_list` tool | LLM | tool registration |
|
|
16
|
+
| C3 | `msm_exec` tool | LLM | tool registration |
|
|
17
|
+
| C4 | 同名 `bash` tool 覆盖 | LLM | tool registration (throws) |
|
|
18
|
+
| C5 | `/serenity-init` slash command | LLM | command registration |
|
|
19
|
+
| C6 | `permission.asked` event hook | opencode runtime | event subscription |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## C1 — Plugin 入口
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// src/index.ts
|
|
27
|
+
import type { Plugin, Hooks } from '@opencode-ai/plugin';
|
|
28
|
+
|
|
29
|
+
const plugin: Plugin = async (input) => {
|
|
30
|
+
// ... 2 阶段启动协议
|
|
31
|
+
// Phase 1: tryActivateSync(RR6 git 验证,同步)
|
|
32
|
+
// Phase 2: activateAsync(RR1+RR2,fire-and-forget 后台)
|
|
33
|
+
return hooks; // Hooks
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
id: 'opencode-serenity-plugin-server',
|
|
38
|
+
server: plugin,
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**契约**:
|
|
43
|
+
- 必须**默认 export 一个对象** `{ id, server }`(opencode 1.16+ SDK 加载协议)
|
|
44
|
+
- `server` 是 async `(input) => Promise<Hooks>` 形式
|
|
45
|
+
- 不抛错(抛错会中断 opencode 启动;不激活时返回 `{}` hooks)
|
|
46
|
+
- 同仓另存 TUI entry `src/tui.ts`,默认 export `{ id, tui }`(详见 C5 注释)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## C2 — `msm_list` Tool
|
|
51
|
+
|
|
52
|
+
### 注册
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { tool, type ToolDefinition } from '@opencode-ai/plugin';
|
|
56
|
+
|
|
57
|
+
export const msmListTool: ToolDefinition = tool({
|
|
58
|
+
description: '...', // LLM 可见的 description
|
|
59
|
+
args: {}, // 无参数(zod schema)
|
|
60
|
+
execute: async () => { /* 返回 msm 列表 */ },
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 描述文本(LLM 可见)
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
[PRIMARY] List all available MSM (Mech & Semi-Mech) tools in the current serenity
|
|
68
|
+
instance. **This is the FIRST tool to call for any shell/exec operation** —
|
|
69
|
+
bash, read (path arguments), and most plugin tools are intentionally limited.
|
|
70
|
+
Each MSM is a deterministic, audited operation registered in mech-registry.json.
|
|
71
|
+
Returns one MSM per line: `name | skill | category | description`.
|
|
72
|
+
If you need an operation that has no MSM, ask the user to register a new one
|
|
73
|
+
before running arbitrary commands.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 入参
|
|
77
|
+
|
|
78
|
+
无。
|
|
79
|
+
|
|
80
|
+
### 出参
|
|
81
|
+
|
|
82
|
+
**字符串**(不是 JSON),每行一个 MSM:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
ssh-connect | home-serenity | mech | 唯一授权 SSH 工具。凭证统一管理,禁止裸 ssh
|
|
86
|
+
resolve-path | home-serenity | mech | 宁静号根路径解析工具
|
|
87
|
+
mech-manifest | home-serenity | mech | 全域 MSM 清单查询
|
|
88
|
+
...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
格式严格遵循:`name | skill | category | description`(4 字段,`|` 两边空格分隔)。
|
|
92
|
+
|
|
93
|
+
空注册表时返回 `"(no MSM registered)"`;plugin 未激活时返回 `"serenity plugin is not active: <reason>"`。
|
|
94
|
+
|
|
95
|
+
### 错误
|
|
96
|
+
|
|
97
|
+
- `MsmNotRegisteredError` — 用户调 `msm_exec` 时 name 不在注册表(C3 错误)
|
|
98
|
+
- 注册表文件读取失败时降级:返回空列表 + `log.warn`(不抛错)
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## C3 — `msm_exec` Tool
|
|
103
|
+
|
|
104
|
+
### 注册
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { tool, type ToolDefinition } from '@opencode-ai/plugin';
|
|
108
|
+
import { z } from 'zod';
|
|
109
|
+
|
|
110
|
+
export const msmExecTool: ToolDefinition = tool({
|
|
111
|
+
description: '...', // LLM 可见的 description
|
|
112
|
+
args: {
|
|
113
|
+
msm_name: z.string().describe('MSM 名称(来自 msm_list 输出)'),
|
|
114
|
+
args: z.string().default('').describe('CLI args 字符串'),
|
|
115
|
+
},
|
|
116
|
+
execute: async (input) => { /* ... */ },
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 描述文本(LLM 可见)
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
[PRIMARY] Execute a registered MSM tool or invoke a protocol meta-command.
|
|
124
|
+
ALWAYS call msm_list first to discover the MSM name.
|
|
125
|
+
**args is a CLI args string** — protocol flags (S022 RFC §2.2) are
|
|
126
|
+
intercepted at the prefix: --format=<text|json>, --log <path>, --help,
|
|
127
|
+
--version, --list, --schema.
|
|
128
|
+
Examples: args="--format=json /tmp/x" for real exec; args="--list" for
|
|
129
|
+
MSM listing; args="--schema ssh-connect" for a MSM schema.
|
|
130
|
+
**args in real-exec mode**: rest of the string after protocol flags =
|
|
131
|
+
business args, passed verbatim to the MSM. 30s timeout.
|
|
132
|
+
**Direct bash is disabled by serenity policy (RR3)** — msm_exec is the
|
|
133
|
+
only path for shell work.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 入参
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
interface MsmExecInput {
|
|
140
|
+
msm_name: string; // 必填,从 msm_list 获取
|
|
141
|
+
args: string; // CLI args 字符串(不解析 JSON),默认空串
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**args 解析规则**(v1.16+):
|
|
146
|
+
- `args` 是**不透明 CLI 字符串**,plugin 不解析 JSON;msm-exec.ts 协议层按 POSIX 风格 tokenize
|
|
147
|
+
- 协议 flag(前缀段,6 必含 flag)由 plugin 拦截:
|
|
148
|
+
- `--format=text|json` — 输出格式
|
|
149
|
+
- `--log <path>` — JSON Lines 日志
|
|
150
|
+
- `--help [name]` — 显示帮助
|
|
151
|
+
- `--version` — 显示版本
|
|
152
|
+
- `--list` — 列出 MSMs(meta)
|
|
153
|
+
- `--schema [name]` — 显示 schema(meta)
|
|
154
|
+
- 协议 flag 之后为**业务段**(rest),原样透传给业务 MSM
|
|
155
|
+
|
|
156
|
+
**示例**:
|
|
157
|
+
- `args="--format=json --log /tmp/x.log ssh-connect --host ubuntu --exec 'uptime'"` → `--format=json --log /tmp/x.log` 协议;`ssh-connect --host ubuntu --exec 'uptime'` 业务
|
|
158
|
+
- `args="--list"` → msm-exec 协议层输出 MSM 清单(meta)
|
|
159
|
+
- `args=""` → 调 `msm_name` 指定的 MSM,args 为空
|
|
160
|
+
|
|
161
|
+
### 出参
|
|
162
|
+
|
|
163
|
+
**字符串**(不是 JSON 对象),等于子进程 stdout:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
<msm 子进程 stdout 原文>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
空 stdout 时返回 `"(no output)"`。
|
|
170
|
+
|
|
171
|
+
**失败路径**(exit code != 0):抛 `MsmExecutionError`,错误对象持有 `stdout` / `stderr` / `exitCode` 三个字段。LLM 看到的 message 包含 stdout 摘要(前 1000 字符)+ stderr 摘要(前 500 字符)。
|
|
172
|
+
|
|
173
|
+
### 错误
|
|
174
|
+
|
|
175
|
+
| 错误 | 触发条件 | LLM 看到的 message |
|
|
176
|
+
|------|---------|------------------|
|
|
177
|
+
| `MsmNotRegisteredError` | msm_name 不在注册表 | `MSM "<name>" is not in mech-registry.json; serenity plugin requires registration before use` |
|
|
178
|
+
| `MsmTimeoutError` | 执行超过 30s | `MSM "<name>" timed out after 30000ms` |
|
|
179
|
+
| `MsmExecutionError` | 子进程返回非 0 exit | `MSM "<name>" failed with exit code <n>\nstdout: <first 1000 chars>\nstderr: <first 500 chars>` |
|
|
180
|
+
| `MsmPathEscapeError` | path-arg 解析为 cwdRoot 之外(v0.1-2 守卫)| `MSM "<name>" path-arg "<argName>"="<value>" resolves to "<resolved>" which is outside cwdRoot; serenity plugin blocks path traversal (v0.1-2 pre-indexed guard)` |
|
|
181
|
+
| `MsmSymlinkError` | path-arg 指向 symlink(v1-1 守卫)| `MSM "<name>" path-arg "<argName>"="<value>" → "<resolved>": <reason>; serenity plugin blocks symlink attacks (v1-1 symlink guard)` |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## C4 — 同名 `bash` Tool 覆盖
|
|
186
|
+
|
|
187
|
+
### 注册
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
api.registerTool({
|
|
191
|
+
name: 'bash',
|
|
192
|
+
description: '...',
|
|
193
|
+
parameters: z.object({
|
|
194
|
+
command: z.string(),
|
|
195
|
+
description: z.string().optional(),
|
|
196
|
+
}),
|
|
197
|
+
execute: async () => {
|
|
198
|
+
throw new BashDisabledError();
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 描述文本(LLM 可见)
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
[DISABLED BY SERENITY POLICY] Direct shell execution is disabled in
|
|
207
|
+
serenity instances. Use `msm_list` + `msm_exec` to run MSMs, or create
|
|
208
|
+
a new MSM if none exists for your task.
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 行为
|
|
212
|
+
|
|
213
|
+
**每次** LLM 调 bash,**总是抛** `BashDisabledError`:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
class BashDisabledError extends Error {
|
|
217
|
+
constructor() {
|
|
218
|
+
super(
|
|
219
|
+
'Direct bash execution is disabled by serenity policy (RR3). ' +
|
|
220
|
+
'Use msm_exec to run an MSM, or create a new MSM in ' +
|
|
221
|
+
'.opencode/skills/<instance>/scripts/ and register it.'
|
|
222
|
+
);
|
|
223
|
+
this.name = 'BashDisabledError';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**关键**:bash 抛错 **不 throw 顶层**(即不能让 plugin 整体崩溃)—— `execute` 函数内 throw 是允许的(tool 层抛错),opencode 会把错误消息返回给 LLM。
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## C5 — `/serenity-init` Slash Command
|
|
233
|
+
|
|
234
|
+
### 注册
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
api.registerCommand({
|
|
238
|
+
name: 'serenity-init',
|
|
239
|
+
description: 'Initialize the current directory as a serenity instance',
|
|
240
|
+
execute: async (args) => { /* ... */ },
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 描述文本(LLM 可见)
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
Initialize the current directory as a serenity instance. Creates
|
|
248
|
+
`/.serenity` (instance marker) and commits it.
|
|
249
|
+
|
|
250
|
+
Prerequisites (caller's responsibility):
|
|
251
|
+
- The current directory must be a git repo (run `git init` first if not)
|
|
252
|
+
- The current directory must NOT already have `/.serenity`
|
|
253
|
+
|
|
254
|
+
Arguments (all optional):
|
|
255
|
+
--name <name> Instance name (default: directory name, kebab-case)
|
|
256
|
+
--no-commit Do not auto-commit /.serenity (default: commit)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 入参
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
interface SerenityInitArgs {
|
|
263
|
+
name?: string; // --name <name>
|
|
264
|
+
no_commit?: boolean; // --no-commit
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 出参(用户可见消息)
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
✓ Initialized serenity instance "home-serenity"
|
|
272
|
+
- Created /.serenity (1 file)
|
|
273
|
+
- Committed: <commit-hash>
|
|
274
|
+
|
|
275
|
+
Next steps:
|
|
276
|
+
- Add MSMs in .opencode/skills/<instance>/scripts/
|
|
277
|
+
- Register MSMs in .opencode/skills/<instance>/references/mech-registry.json
|
|
278
|
+
- Customize the SKILL.md if needed
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 错误
|
|
282
|
+
|
|
283
|
+
| 错误 | 触发条件 | 用户/CLI 看到 |
|
|
284
|
+
|------|---------|--------------|
|
|
285
|
+
| `SerenityInitAlreadyError` | `/.serenity` 已存在 | `/.serenity already exists. Remove it first to re-initialize.` |
|
|
286
|
+
| `SerenityInitNotGitRepoError` | cwd 不在 git repo | `Not a git repo. Run 'git init' first.` |
|
|
287
|
+
| `SerenityInitInvalidNameError` | name 包含非法字符 | `Instance name "X" is invalid. Use kebab-case (e.g. "home-serenity").` |
|
|
288
|
+
| `SerenityInitGitCommitError` | commit 失败 | 透传 git 错误 |
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## C6 — `permission.asked` Event Hook(v1.3-v4 实现)
|
|
293
|
+
|
|
294
|
+
### 注册
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { createPermissionAutoReplyHandler } from './hooks/permission-auto-reply.js';
|
|
298
|
+
|
|
299
|
+
const event: Partial<Hooks['event']> = createPermissionAutoReplyHandler({
|
|
300
|
+
getServerUrl: () => input.serverUrl,
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 行为
|
|
305
|
+
|
|
306
|
+
**无条件 reply "always"**(v1.3-v4 决策)—— 不做 pattern check,不看 event.path。
|
|
307
|
+
|
|
308
|
+
- `event.permission.asked` 触发 → 调用 `client.postSessionPermissionReply()`,reply `"always"`
|
|
309
|
+
- "always" 写入 opencode own `always` list(**单一真相源**)
|
|
310
|
+
- 与 RR5 协同:
|
|
311
|
+
- **cwd 内** read/edit/webfetch → opencode own allow 列表 + user "always" → 不弹窗
|
|
312
|
+
- **cwd 外** → RR5 hard block(`src/hooks/permission-guards.ts#tool.execute.before` 抛 `MsmPathEscapeError`/deny)→ 永不弹窗(直接拒绝)
|
|
313
|
+
|
|
314
|
+
### 演进史
|
|
315
|
+
|
|
316
|
+
- v1.3-v1:实现 cwd 内 pattern check → 与 RR5 重复
|
|
317
|
+
- v1.3-v2:事件名错(`event.permission.asked` 实际是 `permission.asked`)→ 修复
|
|
318
|
+
- v1.3-v3:根因(user "always" 是单一真相源,plugin 重复做 pattern check 浪费)
|
|
319
|
+
- v1.3-v4:简化 → 无条件 reply "always",trust opencode own allow list
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 附录 A — 完整工具 description 文案
|
|
324
|
+
|
|
325
|
+
### msm_list
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
[PRIMARY] List all available MSM (Mech & Semi-Mech) tools in the current
|
|
329
|
+
serenity instance. **This is the FIRST tool to call for any shell/exec
|
|
330
|
+
operation** — bash, read (path arguments), and most plugin tools are
|
|
331
|
+
intentionally limited. Each MSM is a deterministic, audited operation
|
|
332
|
+
registered in `mech-registry.json`. Returns one MSM per line:
|
|
333
|
+
`name | skill | category | description`. If you need an operation that
|
|
334
|
+
has no MSM, ask the user to register a new one before running arbitrary
|
|
335
|
+
commands.
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### msm_exec
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
[PRIMARY] Execute a registered MSM tool or invoke a protocol meta-command.
|
|
342
|
+
ALWAYS call `msm_list` first to discover the MSM name.
|
|
343
|
+
**args is a CLI args string** — protocol flags (S022 RFC §2.2) are
|
|
344
|
+
intercepted at the prefix: `--format=<text|json>`, `--log <path>`,
|
|
345
|
+
`--help [name]`, `--version`, `--list`, `--schema [name]`. Examples:
|
|
346
|
+
`args="--format=json /tmp/x"` for real exec; `args="--list"` for MSM
|
|
347
|
+
listing; `args="--schema ssh-connect"` for a MSM schema. **args in
|
|
348
|
+
real-exec mode**: rest of the string after protocol flags = business
|
|
349
|
+
args, passed verbatim to the MSM. 30s timeout. **Direct `bash` is
|
|
350
|
+
disabled by serenity policy (RR3)** — msm_exec is the only path for
|
|
351
|
+
shell work.
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### msm_admin (v1.17 合并 register + deregister)
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
Register or deregister an MSM (Mech/Semi-Mech) in mech-registry.json.
|
|
358
|
+
**v1.17**: replaces the old msm_register + msm_deregister tools with a
|
|
359
|
+
single tool + action enum. Auto-commits the registry change as
|
|
360
|
+
"chore(msm): register <name>" or "chore(msm): deregister <name>".
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### bash (disabled)
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
[DISABLED BY SERENITY POLICY] Direct shell execution is disabled in
|
|
367
|
+
serenity instances. Use `msm_list` + `msm_exec` to run MSMs, or create
|
|
368
|
+
a new MSM if none exists for your task.
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## 附录 B — 错误类清单(v0.0.2 — 与 src/errors.ts 1:1 对齐)
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// src/errors.ts (2026-06-07 状态)
|
|
377
|
+
export class SerenityError extends Error {
|
|
378
|
+
constructor(message: string) { super(message); this.name = 'SerenityError'; }
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// RR1 违反
|
|
382
|
+
export class NotInGitRepoError extends SerenityError { /* RR6 git 验证失败 */ }
|
|
383
|
+
export class SerenityFileNotFoundError extends SerenityError { /* /ᐧ.serenity 缺失 */ }
|
|
384
|
+
export class SerenityFileEmptyError extends SerenityError { /* /ᐧ.serenity 空 */ }
|
|
385
|
+
export class SkillNotFoundError extends SerenityError { /* SKILL.md 缺失 (RR2) */ }
|
|
386
|
+
|
|
387
|
+
// RR3 违反
|
|
388
|
+
export class BashDisabledError extends SerenityError { /* bash 同名覆盖抛错 */ }
|
|
389
|
+
|
|
390
|
+
// msm 相关
|
|
391
|
+
export class MsmNotRegisteredError extends SerenityError { /* name 不在 registry */ }
|
|
392
|
+
export class MsmTimeoutError extends SerenityError { /* 30s 超时 */ }
|
|
393
|
+
export class MsmExecutionError extends SerenityError { /* exit != 0;持有 stdout/stderr/exitCode */ }
|
|
394
|
+
export class MsmAlreadyRegisteredError extends SerenityError { /* msm_admin register 冲突 */ }
|
|
395
|
+
export class MsmScriptNotFoundError extends SerenityError { /* register 时脚本文件不存在 */ }
|
|
396
|
+
export class MsmNotInRegistryError extends SerenityError { /* msm_admin deregister name 不存在 */ }
|
|
397
|
+
export class MsmPathEscapeError extends SerenityError { /* v0.1-2 path-arg 守卫 */ }
|
|
398
|
+
export class MsmSymlinkError extends SerenityError { /* v1-1 symlink 守卫 */ }
|
|
399
|
+
|
|
400
|
+
// RR7 触发
|
|
401
|
+
export class InitGitCommitError extends SerenityError { /* git add+commit 失败 */ }
|
|
402
|
+
export class InvalidInstanceNameError extends SerenityError { /* prefix 非 kebab-case (RR7 v1.10) */ }
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**13 个 SerenityError 子类 + 1 基类** = 14 个错误类。**所有错误 extends SerenityError**(grep 友好,统一 catch 模式)。
|
|
406
|
+
|
|
407
|
+
> **变更记录**(v0.0.1 → v0.0.2):
|
|
408
|
+
> - 删 `MsmArgsInvalidError`(args 改 CLI 字符串后不再 JSON parse)— 见 T1
|
|
409
|
+
> - 删 `MsmArgsParseError`(同上历史原因,已 dead code)
|
|
410
|
+
> - `MsmExecTimeoutError` → 重命名 `MsmTimeoutError`(语义清晰)
|
|
411
|
+
> - `MsmExecFailedError` → 重命名 `MsmExecutionError`(持有 stdout 字段 §9 修复)
|
|
412
|
+
> - 新增 `MsmPathEscapeError`(v0.1-2)、`MsmSymlinkError`(v1-1)、`InvalidInstanceNameError`(v1.10)、`MsmAlreadyRegisteredError`(v1.1)、`MsmScriptNotFoundError`(v1.1)、`MsmNotInRegistryError`(v1.1)
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
> **本文件完成时间**:2026-06-04
|
|
417
|
+
> **下一文件**:`src/types/`(TS 类型定义)+ `src/`(实现)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# plugin-self-contained-msm v1 — 设计文档
|
|
2
|
+
|
|
3
|
+
> 关联会话:[S028](../AGENT_SESSIONS/2026-06-08--S028--plugin-self-contained-msm/SESSION.md)
|
|
4
|
+
> 关联决策:反转 S024/D26(plugin 仓 3 个 msm tool 不再 spawn 子进程)
|
|
5
|
+
> 目标 release:plugin v0.0.3
|
|
6
|
+
|
|
7
|
+
## 1. 背景
|
|
8
|
+
|
|
9
|
+
### 1.1 S024 现状(v0.0.2 release 2026-06-07)
|
|
10
|
+
|
|
11
|
+
S024 v1.14 选择了"**serenity 侧新 MSM + plugin 薄包装**"(**而非 plugin-only 或 plugin-replaces-MSM**)双仓架构:
|
|
12
|
+
|
|
13
|
+
| 仓 | 文件 | 行数 | 职责 |
|
|
14
|
+
|----|------|------|------|
|
|
15
|
+
| serenity | `msm-exec.ts` | 579 | 协议 runtime:6 必含 flag + stderr 6 字段 schema + spawn 业务 msm |
|
|
16
|
+
| plugin | `src/util/msm-call.ts` | 122 | 薄包装:spawn `npx tsx <serenity>/msm-exec.ts <args>` |
|
|
17
|
+
|
|
18
|
+
plugin 仓 3 个 msm tool 全部经过 `msm-call.ts`:
|
|
19
|
+
- `msm_exec` ← 唯一调用 msm-call 的 tool
|
|
20
|
+
- `msm_list` ← 直读 registry
|
|
21
|
+
- `msm_admin` ← 直写 registry
|
|
22
|
+
|
|
23
|
+
### 1.2 反转触发(S028 2026-06-08)
|
|
24
|
+
|
|
25
|
+
用户的核心约束(plugin 作为 serenity 的"创建者/管理者",durable 基础设施;serenity 是可再生 artifact):
|
|
26
|
+
|
|
27
|
+
> plugin 不假设 serenity 仓任何文件存在。新 serenity 项目冷启动时 plugin 必须能工作。
|
|
28
|
+
|
|
29
|
+
当前架构下,plugin 仓 4 个 msm tool 全部依赖 serenity 仓 `msm-exec.ts` 的绝对路径在 plugin 仓 cwd 下存在。新 serenity 项目(plugin 用户 clone 完 plugin 装到全局,但 serenity 仓还没初始化或在不同位置)就 spawn 失败。
|
|
30
|
+
|
|
31
|
+
### 1.3 关键发现(探索后)
|
|
32
|
+
|
|
33
|
+
- `msm-exec.ts` 579 行**零三方依赖**(仅 `node:fs` / `node:child_process` / `node:path` / `node:url`)— 可直接 in-process 化
|
|
34
|
+
- plugin 端 `parseMechRegistryFile`(v1.13 zod-first D26)已支持 v0 数组 + v1 wrapped 双 schema
|
|
35
|
+
- 9 个 `msm-call.test.ts` 用例是 E2E(真 spawn + tmp dir stub)— 反转后可保留高保真回归
|
|
36
|
+
- `msmExecTool` 当前 description 写"30s timeout"但实际 600_000ms(10 分钟)— 反转时修复
|
|
37
|
+
- plugin 端 SESSION.md v0.0.2 留 "msm_exec tool-level protocol flag prefix parsing (v1.15 deferred)" — S028 自动消解
|
|
38
|
+
|
|
39
|
+
## 2. 关键决策(D1-D8,S028)
|
|
40
|
+
|
|
41
|
+
| # | 决策 | 影响 |
|
|
42
|
+
|---|------|------|
|
|
43
|
+
| D1 | 反转 S024/D26 双仓设计。plugin 端 3 个 msm tool 完全自包含,serenity 仓 msm-exec.ts 保留供 eap-tool 等内部使用 | plugin 仓新增 ~600 行;serenity 仓 0 改动 |
|
|
44
|
+
| D2 | 冷启动边界 = 新 serenity 项目 | plugin 不假设任何 serenity 文件存在 |
|
|
45
|
+
| D3 | msmExec tool description 在 opencode 工具面板(LLM 视角)显示参数 schema | 描述重写为极简 A 版 |
|
|
46
|
+
| D4 | plugin 仓内独立文件 `src/util/msm-exec-runtime.ts`(~579 行),`msm-call.ts` 改为直接 import | 不新增 repo / 不 npm 化 / 不 git subtree |
|
|
47
|
+
| D5 | msmExec tool 描述只讲 msmName / args + 1 示例 | 极简 |
|
|
48
|
+
| D6 | Bootstrap on missing:plugin 初始化时若 `mech-registry.json` 不存在 → 创建空注册表 `{version:1, entries:[]}` | 不是 error,是合法初始状态 |
|
|
49
|
+
| D7 | drift 不防:plugin = 真相源(durable),serenity = 可再生 artifact | serenity 旧了直接删了重建 |
|
|
50
|
+
| D8 | msmRegistry 来源 = C:plugin 仓独立注册表,与 serenity 仓分离 | plugin 不读 serenity 的 registry |
|
|
51
|
+
|
|
52
|
+
## 3. 文件变更
|
|
53
|
+
|
|
54
|
+
| # | 文件 | 旧 | 新 | 备注 |
|
|
55
|
+
|---|------|----|----|------|
|
|
56
|
+
| 1 | `src/util/msm-exec-runtime.ts` | 不存在 | 从 serenity `msm-exec.ts` 移植 579 行 | 调整 `resolveRegistryPath` 路径解析(D9 待定) |
|
|
57
|
+
| 2 | `src/util/msm-call.ts` | spawn 子进程 122 行 | in-process import 调用 ~50 行 | 删 spawn / timeout 600s 保留 / 错误处理保留 |
|
|
58
|
+
| 3 | `src/msm.ts` | msmExec description 6 行("30s timeout" + "ALWAYS msm_list first" + "RR3" 等)| 极简 A 版(2-3 字段 + 1 示例)| D5 |
|
|
59
|
+
| 4 | `src/util/mech-registry.ts` (新) | 不存在 | 路径解析 + bootstrap 逻辑(D6) | 独立文件,避免污染 msm-exec-runtime |
|
|
60
|
+
| 5 | `tests/msm-call.test.ts` | 9 用例(E2E spawn 风格)| 9 用例改 in-process 断言 | 保留 §9 错误回归 |
|
|
61
|
+
| 6 | `tests/msm-exec-runtime.test.ts` (新) | 不存在 | 6 必含 flag 单测(v0.0.2 deferred from v1.14)| 覆盖 --format/--log/--help/--version/--list/--schema |
|
|
62
|
+
| 7 | `mech-registry.json` (新) | 不存在 | 初始空 `{version:1, description:"...", entries:[]}` | D6 + D8 联合产物 |
|
|
63
|
+
| 8 | `SESSION.md` | 0 行 msm 改动 | 增 v0.0.3 块 + D27 + open follow-ups 更新 | 沿用 v0.0.2 release 块模板 |
|
|
64
|
+
|
|
65
|
+
**serenity 仓(home-serenity/):0 改动**。msm-exec.ts 保留供 eap-tool 等内部使用(D1)。
|
|
66
|
+
|
|
67
|
+
## 4. 实施步骤
|
|
68
|
+
|
|
69
|
+
### Step 1 — 移植 msm-exec-runtime.ts
|
|
70
|
+
- 从 `home-serenity/.opencode/skills/home-serenity/scripts/msm-exec.ts` 复制 579 行到 `opencode-serenity-plugin/src/util/msm-exec-runtime.ts`
|
|
71
|
+
- `resolveRegistryPath` 改:D9 决定(见 §5)
|
|
72
|
+
- 保持所有 6 必含 flag / stderr schema / JSON Lines 日志行为不变
|
|
73
|
+
|
|
74
|
+
### Step 2 — 创建 mech-registry bootstrap 工具
|
|
75
|
+
- 新文件 `src/util/mech-registry.ts`(~50 行):
|
|
76
|
+
- `resolveMechRegistryPath(): string` — 返回 plugin 仓 registry 路径
|
|
77
|
+
- `loadMechRegistry(): {version, description, entries}` — 不存在则创建空并返回空 schema
|
|
78
|
+
- 单一职责:路径解析 + bootstrap,不做业务逻辑
|
|
79
|
+
|
|
80
|
+
### Step 3 — 重写 msm-call.ts 为 in-process
|
|
81
|
+
- 删除 spawn 相关代码(~80 行)
|
|
82
|
+
- 改为 `import { runMsmExec } from "./msm-exec-runtime"` + 直接调用
|
|
83
|
+
- 保持对外 API `callMsmExec(opts): Promise<MsmCallResult>` 不变(msm.ts 引用点不动)
|
|
84
|
+
- timeout 处理:msm-exec-runtime 内 business msm 30s → 统一为 600s(D5x 新增决策,见 §5)
|
|
85
|
+
|
|
86
|
+
### Step 4 — 改 msmExecTool description(D5)
|
|
87
|
+
- 旧描述剥到只剩:
|
|
88
|
+
```
|
|
89
|
+
[PRIMARY] Execute a registered MSM (Mech/Semi-Mech) tool.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
msm_name: string (required) — name of the MSM
|
|
93
|
+
args: string[] (default []) — passed positionally; protocol flags supported (--format, --log, --schema, --list, --help, --version)
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
args: ["list"] // → calls msm named "list" with text output
|
|
97
|
+
```
|
|
98
|
+
- 删除:
|
|
99
|
+
- "30s timeout"(错文案,反转时统一 600s 后也不显眼写出)
|
|
100
|
+
- "ALWAYS call msm_list first"(D5:极简,无引导)
|
|
101
|
+
- "Direct bash is disabled by serenity policy (RR3)"(D5:极简;RR3 保护是隐式的)
|
|
102
|
+
|
|
103
|
+
### Step 5 — 创建初始 mech-registry.json
|
|
104
|
+
- plugin 仓根目录 `mech-registry.json`:
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"version": 1,
|
|
108
|
+
"description": "opencode-serenity-plugin 内部 msm 注册表(plugin 仓独立 — 不依赖 serenity 仓)",
|
|
109
|
+
"entries": []
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
- D6 bootstrap:plugin 启动时若不存在则创建这个空文件
|
|
113
|
+
- 不主动注册任何 MSM(让用户用 msm_admin 工具加)
|
|
114
|
+
|
|
115
|
+
### Step 6 — 更新测试
|
|
116
|
+
- `tests/msm-call.test.ts` 9 用例:
|
|
117
|
+
- #1-#5 (resolveMsmExecScriptPath / callMsmExec 基本透传) → 改 in-process 直接断言 msm-exec-runtime 收到正确 args
|
|
118
|
+
- #6/#7 (tool 注册) → 不变
|
|
119
|
+
- #8/#9 (§9 fix 错误处理) → 保留 E2E 行为(spawn business msm,断言错误捕获路径)
|
|
120
|
+
- #10 (空 stdout) → 保留 E2E
|
|
121
|
+
- `tests/msm-exec-runtime.test.ts` (新) ~15 用例:
|
|
122
|
+
- --format text/json
|
|
123
|
+
- --log JSON Lines append
|
|
124
|
+
- --help / --version / --list / --schema 各自输出格式
|
|
125
|
+
- 业务 msm 调用的 argv 透传
|
|
126
|
+
- 业务 msm 失败的 6 字段 stderr 解析
|
|
127
|
+
- 未知 flag → PARAMETER_INVALID_VALUE
|
|
128
|
+
|
|
129
|
+
### Step 7 — plugin SESSION.md 更新
|
|
130
|
+
- 加 v0.0.3 release 块(含 commits + D27 + open follow-ups 更新)
|
|
131
|
+
- v0.0.2 块的 follow-ups 划掉"msm_exec tool-level protocol flag prefix parsing"和"msm-exec.ts unit tests"(S028 解决)
|
|
132
|
+
|
|
133
|
+
### Step 8 — commit + push
|
|
134
|
+
- 用 `serenity-plugin-develop-kit` MSM:typecheck → test → build → install → commit + push origin main
|
|
135
|
+
- 预期 commit 链:
|
|
136
|
+
- `feat: msm_exec in-process runtime (D27, S028)`
|
|
137
|
+
- `feat: msmExec tool description 极简化 (D5)`
|
|
138
|
+
- `test: msm-exec-runtime unit tests (v1.14 deferred)`
|
|
139
|
+
- `chore: v0.0.3 release`
|
|
140
|
+
|
|
141
|
+
## 5. 开放设计点(实施前决策)
|
|
142
|
+
|
|
143
|
+
| # | 决策点 | 选择 | 理由 |
|
|
144
|
+
|---|--------|------|------|
|
|
145
|
+
| D9 | plugin 仓 msm-exec-runtime 的 `resolveRegistryPath` | `__dirname/../../mech-registry.json`(plugin 根)| 镜像 serenity 的 `__dirname/../references/...` 模式,路径是 plugin 根。**不**放 `references/`(plugin 仓还没这个目录) |
|
|
146
|
+
| D10 | 初始 mech-registry.json 状态 | 空 entries `{version:1, description:"...", entries:[]}` | D6 bootstrap 自然结果;用户用 msm_admin 加 |
|
|
147
|
+
| D11 | timeout 统一值 | 600_000ms(10 分钟)| 保留 plugin 端原 600s 上限(业务 msm 长任务友好)。msm-exec.ts 原 30s(业务 msm)一并提到 600s |
|
|
148
|
+
| D12 | msmExec tool description 极简度 | msmName + args(2 字段)+ 1 示例 | D5 锁定 |
|
|
149
|
+
|
|
150
|
+
D9-D12 我自行决策(user: "剩下的你自己可以找到")。如有偏差,实施后用户可调整。
|
|
151
|
+
|
|
152
|
+
## 6. 风险与回滚
|
|
153
|
+
|
|
154
|
+
| 风险 | 概率 | 影响 | 缓解 |
|
|
155
|
+
|------|------|------|------|
|
|
156
|
+
| msm-exec.ts 移植时漏改路径解析 | 中 | plugin 找不到 registry → 启动失败 | 单测覆盖 resolveMechRegistryPath + e2e spawn |
|
|
157
|
+
| in-process 调用的 error 传递丢失 | 低 | §9 fix 退化 | 保留 #8/#9 E2E 回归 |
|
|
158
|
+
| 30s → 600s 改动影响用户预期 | 低 | 长 timeout 占用资源 | 业务 msm 仍是独立 spawn,可单独 timeout |
|
|
159
|
+
| plugin 仓 mech-registry 与 serenity 仓同名 | 中 | git pull / IDE 混淆 | plugin 用根 `mech-registry.json`;serenity 用 `references/mech-registry.json`。两仓同名但路径不冲突 |
|
|
160
|
+
|
|
161
|
+
**回滚**:S024 状态可重现 — `git revert` v0.0.3 commits 即可恢复 spawn 架构。
|
|
162
|
+
|
|
163
|
+
## 7. 不在本次范围
|
|
164
|
+
|
|
165
|
+
- msmExec v1 签名(cwd/timeout/env 顶层字段)— plugin SESSION.md 留 v0.0.4+ follow-up
|
|
166
|
+
- msmHelp / msmVersion / msmSchema tool 复活 — 已被 v1.16 Option C 删,本会话不复活
|
|
167
|
+
- msmAdmin 拆 register/deregister — 已被 v1.17 合并,本会话不拆
|
|
168
|
+
- eap-tool 迁移到 plugin 自包含 msm-exec — D1 决定 serenity 仓 msm-exec.ts 保留,eap-tool 暂不动
|
|
169
|
+
|
|
170
|
+
## 8. 验收标准
|
|
171
|
+
|
|
172
|
+
- [ ] plugin 仓 `pnpm typecheck` 通过
|
|
173
|
+
- [ ] plugin 仓 `pnpm test` 通过(既有 320 + 新增 ~15 msm-exec-runtime + 9 msm-call 改 in-process = ~344)
|
|
174
|
+
- [ ] plugin 仓 `pnpm build` 通过
|
|
175
|
+
- [ ] plugin 加载到 opencode 后 toast 显示新版本号
|
|
176
|
+
- [ ] 模拟冷启动:删除 `mech-registry.json` 后 `msm_list` 调用 → 触发 bootstrap → 返回空列表(不报错)
|
|
177
|
+
- [ ] `msm_admin register` 注册一个测试 msm → `msm_list` 可见 → `msm_exec` 调通
|
|
178
|
+
- [ ] msmExec description 在 opencode tool list 显示极简版(2 字段 + 1 示例,无 "30s" / "ALWAYS" / "RR3" 字样)
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
**下一步**:写完设计文档后立刻进 Step 1 实施(移植 msm-exec-runtime.ts)。任何一步失败停下来回滚。
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
## Plugin Refactor Direction — v1.11 to v1.13 (✅ Done 2026-06-07)
|
|
2
|
+
|
|
3
|
+
> 起源:用户对 v1.10 / v1.10.1 后 plugin 体验满意,提了两个问题:
|
|
4
|
+
> 1. 安装需要装两个 plugin (server + TUI),能否简化?
|
|
5
|
+
> 2. 现有功能是否有更好的实现方法?
|
|
6
|
+
>
|
|
7
|
+
> 调研参考:omo (github.com/code-yeongyu/oh-my-openagent @ 2e22f4f) 源码分析,报告 `AGENT_SESSIONS/2026-06-07--S021--omo-plugin-investigation/REPORT-v2.md`
|
|
8
|
+
>
|
|
9
|
+
> 关键拐点:用户 m0107 强调"先结合宁静号自身的定位"——不能照搬 omo,必须从宁静号价值反推。
|
|
10
|
+
|
|
11
|
+
### 宁静号 plugin 的双重价值 (第一性原理)
|
|
12
|
+
|
|
13
|
+
| 价值 | 含义 | 实现保障 |
|
|
14
|
+
|------|------|---------|
|
|
15
|
+
| **V1: 自由初始化** | 用户能在任何目录把项目升级为 serenity | `/serenity-init` slash command 必须在非 serenity 目录可见 (v1.10.1 已实现) |
|
|
16
|
+
| **V2: 非侵入** | 非 serenity 目录,opencode 行为和没装 plugin 一样 | 0 MCP、0 hook、0 permission patch、0 输出 |
|
|
17
|
+
|
|
18
|
+
V1 + V2 同时满足 = 两 entry 架构:
|
|
19
|
+
- **Server entry 仅 project-level 加载** (`opencode.json` 的 `plugin` 数组) —— 只在 serenity 目录被 opencode 加载 → V2 ✓
|
|
20
|
+
- **TUI entry 全局加载** (`tui.json` 的 `plugin` 数组) —— 只注册 `/serenity-init` 一个 slash command,不修改任何 host 行为 → V2 ✓ + V1 ✓
|
|
21
|
+
- v1.10.1 修复让 TUI plugin 自安装到 global `~/.config/opencode/tui.json` → V1 加强
|
|
22
|
+
|
|
23
|
+
### omo 模式反思
|
|
24
|
+
|
|
25
|
+
omo 假设"装了我就在每个目录活跃"——13 MCP / 56 hook / 5 agent 默认全开,**没有"非激活态"概念**。
|
|
26
|
+
|
|
27
|
+
omo 的两个层面:
|
|
28
|
+
| 层面 | omo 实现 | 我们的选择 |
|
|
29
|
+
|------|---------|----------|
|
|
30
|
+
| **架构层** (单 entry) | `string[]` 形式,1 个 server entry,config mutation 注入所有 UI/MCP/agent | ❌ 不抄。合并 entry 会破坏 V2:plugin 必须全局加载 → 必须每个 hook 加 `isSerenity` 守卫 → "激活判断"从宿主层下沉到 plugin 内部 → 失去硬隔离 |
|
|
31
|
+
| **代码层** (hook 保护、zod-first、bin install) | `isHookEnabled` + `safeCreateHook`、zod schema、`bin install` CLI | ✅ 该抄。这些是代码质量改进,跟 V1/V2 无关 |
|
|
32
|
+
|
|
33
|
+
### 锁定的实施方向 ✅ 全部完成
|
|
34
|
+
|
|
35
|
+
**保留两 entry 架构**。改进 3 件事:
|
|
36
|
+
|
|
37
|
+
| 版本 | 任务 | 价值 | 工作量 | 风险 | 实施 commit |
|
|
38
|
+
|------|------|------|--------|------|--------|
|
|
39
|
+
| **v1.11** | `bin install` CLI:一次写两 entry (project opencode.json + global tui.json) | V1 安装体验 | 6-8h | 低 | `93348e3` |
|
|
40
|
+
| **v1.12** | `isHookEnabled` + `safeCreateHook` 工具 (仿 omo safeHook 模式) | V2 健壮性 | 2-3h | 低 | `7ad23ee` |
|
|
41
|
+
| **v1.13** | plugin config 改 zod-first schema | 代码质量 | 3h | 低 | `bca0e63` |
|
|
42
|
+
|
|
43
|
+
**实际工作量**:分 3 个 commit 落地;总测试数 184 → 320 pass(v0.0.1 → v0.0.2)。
|
|
44
|
+
**延展**:v1.14-v1.17 在此基础上叠加 msm_exec 协议层、tool 合并、stdout 保留等 4 个 commit(见 SESSION.md v0.0.2 块的 Commits 段)。
|
|
45
|
+
|
|
46
|
+
### v1.11 `bin install` 设计草案
|
|
47
|
+
|
|
48
|
+
**入口**:`npx opencode-serenity-plugin install [flags]`
|
|
49
|
+
|
|
50
|
+
**行为**:
|
|
51
|
+
1. 解析 `dist/index.js` + `dist/tui.js` 绝对路径 (用 `realpathSync` 解析 symlink)
|
|
52
|
+
2. 写两处 (不合并 entry):
|
|
53
|
+
- `<cwd>/opencode.json#plugin` append `dist/index.js` (idempotent)
|
|
54
|
+
- `~/.config/opencode/tui.json#plugin` append `dist/tui.js` (idempotent)
|
|
55
|
+
3. 尊重 `$XDG_CONFIG_HOME` (跨平台)
|
|
56
|
+
4. 不破坏用户现有配置 (保留其他 plugin / 其他字段)
|
|
57
|
+
5. 静默幂等
|
|
58
|
+
|
|
59
|
+
**flags**:
|
|
60
|
+
- `--global`:跳过项目级,只装 TUI
|
|
61
|
+
- `--uninstall`:反向删除 entry (保留其他 plugin)
|
|
62
|
+
- `--dry-run`:打印将要做的修改,不实际写
|
|
63
|
+
- `--cwd <path>`:override 默认 cwd
|
|
64
|
+
|
|
65
|
+
**参考**:omo `src/cli/install.ts` + `tui-installer.ts` + `add-plugin-to-opencode-config.ts`
|
|
66
|
+
|
|
67
|
+
**不抄 omo**:
|
|
68
|
+
- ❌ postinstall 钩子 (omo 自己也没用,只做自检)
|
|
69
|
+
- ❌ `@clack/prompts` 交互式 TUI (v1 阶段不需要,CLI flag 已够用)
|
|
70
|
+
- ❌ telemetry / GitHub star 提示
|
|
71
|
+
- ❌ dual-publish / 多个 bin alias
|
|
72
|
+
|
|
73
|
+
### 决策记录
|
|
74
|
+
|
|
75
|
+
- **D23** (2026-06-07):保留两 entry 架构。理由:V2 非侵入要求 plugin 在非 serenity 目录完全无感;单 entry 必须每个 hook 加守卫,违反硬隔离原则。
|
|
76
|
+
- **D24** (2026-06-07):v1.11 实施 `bin install` CLI。理由:用户最痛点是安装体验,6-8h 解决。
|
|
77
|
+
- **D25** (2026-06-07):v1.12 实施 hook 保护。理由:1 个 hook 抛错杀整个 plugin 的脆弱性必须修。
|
|
78
|
+
- **D26** (2026-06-07):v1.13 实施 zod-first。理由:plugin config 还在手写 interface,zod 是 single source of truth。
|