@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.
Files changed (151) hide show
  1. package/README.md +199 -0
  2. package/bin/opencode-serenity-plugin.js +316 -0
  3. package/dist/activation.d.ts +40 -0
  4. package/dist/activation.d.ts.map +1 -0
  5. package/dist/activation.js +133 -0
  6. package/dist/activation.js.map +1 -0
  7. package/dist/bash-override.d.ts +10 -0
  8. package/dist/bash-override.d.ts.map +1 -0
  9. package/dist/bash-override.js +24 -0
  10. package/dist/bash-override.js.map +1 -0
  11. package/dist/bash-toggle.d.ts +17 -0
  12. package/dist/bash-toggle.d.ts.map +1 -0
  13. package/dist/bash-toggle.js +42 -0
  14. package/dist/bash-toggle.js.map +1 -0
  15. package/dist/config-schema.d.ts +300 -0
  16. package/dist/config-schema.d.ts.map +1 -0
  17. package/dist/config-schema.js +185 -0
  18. package/dist/config-schema.js.map +1 -0
  19. package/dist/errors.d.ts +90 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +151 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/fs/file-system-tool.d.ts +25 -0
  24. package/dist/fs/file-system-tool.d.ts.map +1 -0
  25. package/dist/fs/file-system-tool.js +318 -0
  26. package/dist/fs/file-system-tool.js.map +1 -0
  27. package/dist/fs/resolve-path.d.ts +53 -0
  28. package/dist/fs/resolve-path.d.ts.map +1 -0
  29. package/dist/fs/resolve-path.js +100 -0
  30. package/dist/fs/resolve-path.js.map +1 -0
  31. package/dist/hooks/compacting.d.ts +23 -0
  32. package/dist/hooks/compacting.d.ts.map +1 -0
  33. package/dist/hooks/compacting.js +90 -0
  34. package/dist/hooks/compacting.js.map +1 -0
  35. package/dist/hooks/permission-auto-reply.d.ts +91 -0
  36. package/dist/hooks/permission-auto-reply.d.ts.map +1 -0
  37. package/dist/hooks/permission-auto-reply.js +158 -0
  38. package/dist/hooks/permission-auto-reply.js.map +1 -0
  39. package/dist/hooks/permission-guards.d.ts +41 -0
  40. package/dist/hooks/permission-guards.d.ts.map +1 -0
  41. package/dist/hooks/permission-guards.js +153 -0
  42. package/dist/hooks/permission-guards.js.map +1 -0
  43. package/dist/hooks/shell-env.d.ts +20 -0
  44. package/dist/hooks/shell-env.d.ts.map +1 -0
  45. package/dist/hooks/shell-env.js +38 -0
  46. package/dist/hooks/shell-env.js.map +1 -0
  47. package/dist/hooks/util.d.ts +81 -0
  48. package/dist/hooks/util.d.ts.map +1 -0
  49. package/dist/hooks/util.js +172 -0
  50. package/dist/hooks/util.js.map +1 -0
  51. package/dist/index.d.ts +26 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +63 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/init/init-wizard.d.ts +39 -0
  56. package/dist/init/init-wizard.d.ts.map +1 -0
  57. package/dist/init/init-wizard.js +297 -0
  58. package/dist/init/init-wizard.js.map +1 -0
  59. package/dist/install.d.ts +117 -0
  60. package/dist/install.d.ts.map +1 -0
  61. package/dist/install.js +255 -0
  62. package/dist/install.js.map +1 -0
  63. package/dist/msm-schema.d.ts +76 -0
  64. package/dist/msm-schema.d.ts.map +1 -0
  65. package/dist/msm-schema.js +207 -0
  66. package/dist/msm-schema.js.map +1 -0
  67. package/dist/msm.d.ts +25 -0
  68. package/dist/msm.d.ts.map +1 -0
  69. package/dist/msm.js +317 -0
  70. package/dist/msm.js.map +1 -0
  71. package/dist/session/lib.d.ts +33 -0
  72. package/dist/session/lib.d.ts.map +1 -0
  73. package/dist/session/lib.js +475 -0
  74. package/dist/session/lib.js.map +1 -0
  75. package/dist/session/session-tool.d.ts +17 -0
  76. package/dist/session/session-tool.d.ts.map +1 -0
  77. package/dist/session/session-tool.js +109 -0
  78. package/dist/session/session-tool.js.map +1 -0
  79. package/dist/skills/install-skill.d.ts +36 -0
  80. package/dist/skills/install-skill.d.ts.map +1 -0
  81. package/dist/skills/install-skill.js +91 -0
  82. package/dist/skills/install-skill.js.map +1 -0
  83. package/dist/skills/template-loader.d.ts +79 -0
  84. package/dist/skills/template-loader.d.ts.map +1 -0
  85. package/dist/skills/template-loader.js +170 -0
  86. package/dist/skills/template-loader.js.map +1 -0
  87. package/dist/state.d.ts +35 -0
  88. package/dist/state.d.ts.map +1 -0
  89. package/dist/state.js +62 -0
  90. package/dist/state.js.map +1 -0
  91. package/dist/tui.d.ts +61 -0
  92. package/dist/tui.d.ts.map +1 -0
  93. package/dist/tui.js +279 -0
  94. package/dist/tui.js.map +1 -0
  95. package/dist/types/index.d.ts +30 -0
  96. package/dist/types/index.d.ts.map +1 -0
  97. package/dist/types/index.js +17 -0
  98. package/dist/types/index.js.map +1 -0
  99. package/dist/util/config-patch.d.ts +58 -0
  100. package/dist/util/config-patch.d.ts.map +1 -0
  101. package/dist/util/config-patch.js +117 -0
  102. package/dist/util/config-patch.js.map +1 -0
  103. package/dist/util/git.d.ts +29 -0
  104. package/dist/util/git.d.ts.map +1 -0
  105. package/dist/util/git.js +74 -0
  106. package/dist/util/git.js.map +1 -0
  107. package/dist/util/init-check.d.ts +22 -0
  108. package/dist/util/init-check.d.ts.map +1 -0
  109. package/dist/util/init-check.js +76 -0
  110. package/dist/util/init-check.js.map +1 -0
  111. package/dist/util/init.d.ts +54 -0
  112. package/dist/util/init.d.ts.map +1 -0
  113. package/dist/util/init.js +87 -0
  114. package/dist/util/init.js.map +1 -0
  115. package/dist/util/log.d.ts +25 -0
  116. package/dist/util/log.d.ts.map +1 -0
  117. package/dist/util/log.js +28 -0
  118. package/dist/util/log.js.map +1 -0
  119. package/dist/util/msm-call.d.ts +48 -0
  120. package/dist/util/msm-call.d.ts.map +1 -0
  121. package/dist/util/msm-call.js +86 -0
  122. package/dist/util/msm-call.js.map +1 -0
  123. package/dist/util/msm-exec-runtime.d.ts +123 -0
  124. package/dist/util/msm-exec-runtime.d.ts.map +1 -0
  125. package/dist/util/msm-exec-runtime.js +532 -0
  126. package/dist/util/msm-exec-runtime.js.map +1 -0
  127. package/dist/util/path.d.ts +10 -0
  128. package/dist/util/path.d.ts.map +1 -0
  129. package/dist/util/path.js +21 -0
  130. package/dist/util/path.js.map +1 -0
  131. package/dist/util/ready-state.d.ts +43 -0
  132. package/dist/util/ready-state.d.ts.map +1 -0
  133. package/dist/util/ready-state.js +104 -0
  134. package/dist/util/ready-state.js.map +1 -0
  135. package/dist/util/serenity-file.d.ts +30 -0
  136. package/dist/util/serenity-file.d.ts.map +1 -0
  137. package/dist/util/serenity-file.js +69 -0
  138. package/dist/util/serenity-file.js.map +1 -0
  139. package/dist/util/tui-install.d.ts +61 -0
  140. package/dist/util/tui-install.d.ts.map +1 -0
  141. package/dist/util/tui-install.js +94 -0
  142. package/dist/util/tui-install.js.map +1 -0
  143. package/docs/architecture-v0.md +294 -0
  144. package/docs/contract-v0.md +417 -0
  145. package/docs/plugin-self-contained-msm-v1.md +182 -0
  146. package/docs/refactor-direction-v1.11.md +78 -0
  147. package/docs/requirements-v0-scope.md +104 -0
  148. package/docs/requirements-v0-summary.md +108 -0
  149. package/docs/rr7-init-design.md +304 -0
  150. package/docs/v0.1-candidates.md +132 -0
  151. package/package.json +54 -0
@@ -0,0 +1,294 @@
1
+ # Plugin Architecture v0 — 方案/设计层
2
+
3
+ > **会话**:opencode-serenity-plugin(项目即会话模式)
4
+ > **状态**:v0.0.2 已发布(2026-06-07)。本文件随代码演进持续修订。
5
+ > **范围**:v0 plugin 整体架构 + 启动协议 + 模块划分 + 数据流 + 失败模式。
6
+ > **不进**:接口/契约层(`docs/contract-v0.md`)、实现/代码层(`src/`)、需求层(`docs/requirements-v0-scope.md`)。
7
+
8
+ ---
9
+
10
+ ## 1. 顶层架构图
11
+
12
+ opencode 1.16+ 强制 `PluginModule` 二选一(`server | tui`),所以 plugin 走**两条独立 entry 并行加载**:
13
+
14
+ ```
15
+ ┌─────────────────────────────────────────────────────────────────┐
16
+ │ opencode 宿主运行时(任何 cwd) │
17
+ │ │
18
+ │ ┌────────────────┐ 启动时(一次) │
19
+ │ │ plugin 加载 │ ─────────────────────────┐ │
20
+ │ │ (loader) │ ▼ │
21
+ │ └────────────────┘ ┌──────────────────────────────────────┐ │
22
+ │ │ opencode-serenity-plugin (server) │ │
23
+ │ │ src/index.ts (entry) │ │
24
+ │ │ │ │
25
+ │ │ ┌───────────────────────────────┐ │ │
26
+ │ │ │ activation.ts (两阶段 init) │ │ │
27
+ │ │ │ Phase 1 sync: RR6 git check │ │ │
28
+ │ │ │ Phase 2 async: RR1 + RR2 │ │ │
29
+ │ │ │ + v1.5 init-check │ │ │
30
+ │ │ │ + v1.7 config-patch │ │ │
31
+ │ │ └───────────────────────────────┘ │ │
32
+ │ │ │ │ │
33
+ │ │ ▼ │ │
34
+ │ │ ┌───────────────────────────────┐ │ │
35
+ │ │ │ plugin state (singleton): │ │ │
36
+ │ │ │ { activated, cwdRoot, │ │ │
37
+ │ │ │ instanceName, skillPath, │ │ │
38
+ │ │ │ skillContent } │ │ │
39
+ │ │ └───────────────────────────────┘ │ │
40
+ │ │ │ │ │
41
+ │ │ ▼ │ │
42
+ │ │ 注册 hooks + tools: │ │
43
+ │ │ • tool.execute.before — RR5 hard block │ │
44
+ │ │ • experimental.chat.system.transform │ │
45
+ │ │ (注入 SKILL.md) │ │
46
+ │ │ • experimental.session.compacting │ │
47
+ │ │ • shell.env — 注入 HOME_SERENITY_ROOT │ │
48
+ │ │ • event permission.asked — auto-reply │ │
49
+ │ │ • tool: bash(override)/msm_list/ │ │
50
+ │ │ msm_exec/msm_admin │ │
51
+ │ └──────────────────────────────────────┘ │
52
+ │ │
53
+ │ ┌──────────────────────────────────────────────────────────┐ │
54
+ │ │ opencode-serenity-plugin (TUI, 平行 entry) │ │
55
+ │ │ src/tui.ts (entry) │ │
56
+ │ │ │ │
57
+ │ │ • 加载 toast: "v${VERSION} loaded" │ │
58
+ │ │ • 激活 toast: "plugin activated — read/edit = allow" │ │
59
+ │ │ • 自检自装到 global tui.json(v1.10.1 修复) │ │
60
+ │ │ • /serenity-init slash command (RR7 init) │ │
61
+ │ └──────────────────────────────────────────────────────────┘ │
62
+ └─────────────────────────────────────────────────────────────────┘
63
+ ```
64
+
65
+ 两条 entry 独立加载但共享 `src/state.ts` 模块级 singleton——server entry 写、tui entry 读(不依赖 RR1 即可展示 toast)。
66
+
67
+ ## 2. 启动协议 — 两阶段 init (v0.1)
68
+
69
+ **触发**:opencode 启动时,plugin loader 加载 `src/index.ts` 的 default export 一次。
70
+
71
+ **前提**:`@opencode-ai/plugin@1.16.2` 的 `Plugin` 类型签名 `(input: PluginInput) => Promise<Hooks>`,input 提供 `directory / worktree / client / serverUrl`。
72
+
73
+ ### Phase 1:同步(plugin 入口立即返回)
74
+
75
+ | 步 | 动作 | 失败处理 | 来源 |
76
+ |---|------|---------|------|
77
+ | 1 | `cwd = input.directory` | 异常 → return `{}`(plugin 静默不工作)| RR6 隐含 |
78
+ | 2 | `findGitRoot(cwd)` (git rev-parse --show-toplevel) | 失败 / 不在 → **mark disabled** + return `{}` | RR6 |
79
+ | 3 | 启动 Phase 2 fire-and-forget(不 await)| — | 内部 |
80
+ | 4 | 注册 hooks + tools(Phase 2 未完成时 hook 内 `ensureReady()` 阻塞)| — | 内部 |
81
+ | 5 | return `hooks` | — | 内部 |
82
+
83
+ ### Phase 2:异步(fire-and-forget 后台 IO)
84
+
85
+ | 步 | 动作 | 失败处理 | 来源 |
86
+ |---|------|---------|------|
87
+ | 1 | RR1 读 `<gitRoot>/.serenity` | 失败 → state.error(machine 推 error) | RR1 |
88
+ | 2 | RR2 验证 `<gitRoot>/.opencode/skills/<N>/SKILL.md` | 失败 → state.error | RR2 |
89
+ | 3 | 读 SKILL.md 全文(system.transform 注入用)| 失败 → 降级为 null(plugin 仍工作) | 内部 |
90
+ | 4 | setState + markReady | — | 内部 |
91
+ | 5 | v1.5 init-check:warn 不 patch | 失败 → log.warn | 内部 |
92
+ | 6 | v1.7 config-patch:自动 grant read/edit=allow + TUI toast 通知 | 失败 → log.warn(不阻断) | 内部 |
93
+
94
+ **重要约束**:
95
+ - **前 2 步**任意失败 → `return {}`(plugin 啥也不做,**就像没装一样**——Q6 用户原话)
96
+ - **不 throw**(抛错会中断 opencode 启动,且违反"不工作"语义)
97
+ - **不 console.error**(用 `log.warn`/`log.debug`——不污染用户)
98
+ - **Exception**:`tool.execute.before` (permission-guards) 故意让 throw 透传(RR5 hard block 想要的行为;其他 hook 走 `safeHook` 静默)
99
+
100
+ ## 3. 模块划分(v0.0.2 实际)
101
+
102
+ ```
103
+ opencode-serenity-plugin/
104
+ ├── README.md # 用户入口 + 安装说明
105
+ ├── SESSION.md # 项目即会话追踪
106
+ ├── package.json # version 0.0.2, deps: @opencode-ai/plugin@1.16.2 + zod@4.1.8
107
+ ├── tsconfig.json / tsconfig.test.json # TS 5.x strict + Node 20+
108
+ ├── vitest.config.ts # testTimeout 20s(msm-call 冷启动)
109
+ ├── bin/
110
+ │ └── opencode-serenity-plugin.js # CLI: install/uninstall (v1.11)
111
+ ├── src/ # ── 12 顶层 .ts ──
112
+ │ ├── index.ts # server entry (4 tool + 4 hook)
113
+ │ ├── tui.ts # TUI entry (toast + /serenity-init + self-install)
114
+ │ ├── activation.ts # 两阶段 init + config-patch wiring
115
+ │ ├── state.ts # 全局激活状态 singleton + ReadyStateMachine
116
+ │ ├── msm.ts # msmListTool + msmExecTool + msmAdminTool (4 tool)
117
+ │ ├── msm-schema.ts # flag normalize (v0/v1) + tokenizeArgs + path-arg 校验 (v0.1-2 + v1-1)
118
+ │ ├── config-schema.ts # zod-first 4 schemas (v1.13)
119
+ │ ├── install.ts # bin install lib (project + global, XDG + APPDATA)
120
+ │ ├── bash-override.ts # 同名 bash tool 覆盖 (RR3 第三层)
121
+ │ ├── errors.ts # 13 个 SerenityError 子类 + 1 基类
122
+ │ ├── types/
123
+ │ │ └── index.ts # SerenityState + INACTIVE_STATE + SerenityPluginInput
124
+ │ ├── hooks/ # ── 5 hook 工厂 ──
125
+ │ │ ├── util.ts # isHookEnabled + safeHook + safeCreateHook (oMo 模式 v1.12)
126
+ │ │ ├── permission-guards.ts # tool.execute.before — RR5 hard block + bash 防御
127
+ │ │ ├── compacting.ts # system.transform + session.compacting
128
+ │ │ ├── shell-env.ts # shell.env 注入 HOME_SERENITY_ROOT + SERENITY_PLUGIN_VERSION
129
+ │ │ └── permission-auto-reply.ts # event hook — permission.asked → "always" (v1.3-v4)
130
+ │ └── util/ # ── 10 helper ──
131
+ │ ├── git.ts # findGitRoot + tryFindGitRoot + isPathInside + git ops
132
+ │ ├── path.ts # buildSkillPath + validateSkillExists
133
+ │ ├── serenity-file.ts # readSerenityFile (RR1)
134
+ │ ├── ready-state.ts # ReadyStateMachine (idle/loading/ready/error/disabled)
135
+ │ ├── init.ts # initSerenity (RR7)
136
+ │ ├── init-check.ts # checkSerenityConfig (v1.5, warn-only)
137
+ │ ├── config-patch.ts # patchMainRepoOpencodeJson (v1.7, auto-grant)
138
+ │ ├── msm-call.ts # callMsmExec + callMsmExecMeta + parseProtocolFlags (S022 RFC)
139
+ │ ├── tui-install.ts # global tui.json 自安装 (v1.10.1, 薄包装 install.ts)
140
+ │ └── log.ts # 统一 log wrapper (no-op, 65 sites)
141
+ ├── tests/ # ── 23 测 / 320 cases ──
142
+ │ ├── activation.test.ts / ready-state.test.ts # Phase 1+2 + machine
143
+ │ ├── msm-{call,schema,registry,admin}.test.ts # 协议层 + admin
144
+ │ ├── config-{schema,patch}.test.ts # zod + patch
145
+ │ ├── errors.test.ts # 13 错误类
146
+ │ ├── hooks-{util,guard}.test.ts # 工厂 + guard
147
+ │ ├── permission-{auto-reply,guards-v16}.test.ts # event + RR5
148
+ │ ├── compacting-skill-inject.test.ts # SKILL.md 注入
149
+ │ ├── init-check.test.ts / install.test.ts # init + bin
150
+ │ ├── tui{-install,}.test.ts # tui 入口
151
+ │ ├── plugin.test.ts # full entry
152
+ │ └── util-{git,init,path,serenity-file}.test.ts # helpers
153
+ └── docs/ # ── 7 文档 ──
154
+ ├── requirements-v0-scope.md # RR1-RR7 范围层 (v0 终版)
155
+ ├── architecture-v0.md # 本文件
156
+ ├── contract-v0.md # 6 契约 + 13 错误类
157
+ ├── requirements-v0-summary.md # ⚠️ 已过时(旧 R1-R5 保留演进史)
158
+ ├── v0.1-candidates.md # ✅ ALL DONE (3 候选全部已实施)
159
+ ├── rr7-init-design.md # v1.10 + v1.10.1 实施记录
160
+ └── refactor-direction-v1.11.md # ✅ Done 2026-06-07 (v1.11-v1.17)
161
+ ```
162
+
163
+ ## 4. 数据流
164
+
165
+ ### 4.1 启动阶段(一次性)
166
+
167
+ ```
168
+ opencode 启动
169
+
170
+ plugin loader 加载 src/index.ts (server) + src/tui.ts (TUI) 并行
171
+
172
+ server: activation.tryActivateSync
173
+
174
+ [Phase 1 sync — RR6 git check]
175
+ ↓ (ok)
176
+ 注册 hooks + tools + 启动 Phase 2 fire-and-forget + 立即返回 hooks
177
+
178
+ Phase 2 async (后台): RR1 + RR2 + skillContent + init-check + config-patch
179
+
180
+ setState(activated) + machine.markAsReady()
181
+
182
+ ensureReady() 在所有 tools/hooks 内不再阻塞
183
+ ```
184
+
185
+ ### 4.2 工具调用阶段(每次)
186
+
187
+ ```
188
+ LLM 决定调工具
189
+
190
+ opencode 查 tool 注册表
191
+ ├─ msm_list ─→ src/msm.ts#msmListTool → loadMechRegistry → 字符串输出
192
+ ├─ msm_exec ─→ src/msm.ts#msmExecTool → 1. tokenizeArgs + parseProtocolFlags
193
+ │ 2. findMsm + path-arg 校验
194
+ │ 3. msm-exec.ts (协议层) — 含 6 必含 flag
195
+ │ 4. 错误类: MsmExecutionError (持 stdout)
196
+ ├─ msm_admin ─→ src/msm.ts#msmAdminTool → register/deregister + auto-commit
197
+ └─ bash ─→ src/bash-override.ts → throw BashDisabledError (RR3)
198
+
199
+ permission.asked event hook (parallel) — 无条件 reply "always"
200
+
201
+ permission-guards tool.execute.before — RR5 hard block (read/edit/write 路径守卫)
202
+
203
+ 执行结果回 LLM
204
+ ```
205
+
206
+ ### 4.3 关键判定
207
+
208
+ | 判定 | 函数 | 来源 |
209
+ |------|------|------|
210
+ | cwd 在 git repo 内 | `findGitRoot(cwd)` (git rev-parse) | RR6 |
211
+ | `/.serenity` 存在 + 非空 | `readSerenityFile(gitRoot)` | RR1 |
212
+ | 实例名 N 解析 | `parseInstanceName(content)` | RR1 |
213
+ | skill 路径存在 | `validateSkillPath(gitRoot, N)` | RR2 |
214
+ | 路径在 cwdRoot 内 | `isPathInside(cwdRoot, abs)` | RR5 |
215
+ | path-arg 解析在 cwdRoot 内 | `validatePathArgsFromTokens(rest, flags, cwdRoot)` (v0.1-2) | msm_exec 路径守卫 |
216
+ | symlink 防御 | `realpathSync` 比较 | v1-1 |
217
+ | msm_name 在注册表 | `findMsm(name, registry)` | 内部 |
218
+ | hook 启用 | `isHookEnabled(name, config?)` + env var + session disable | v1.12 |
219
+
220
+ ## 5. 与主仓关系
221
+
222
+ | 维度 | plugin 仓 | 主仓(home-serenity) | 其他工作项目 |
223
+ |------|----------|-------------------|------------|
224
+ | `/.serenity` | ❌ | ✅(内容 = `home-serenity`) | ❌ |
225
+ | 是 git repo | ✅ | ✅ | 视情况 |
226
+ | 加载 plugin 时激活? | ❌(plugin 仓不满足 RR1) | ✅ | ❌ |
227
+ | 行为 | plugin **开发** | plugin **生效** | opencode 默认行为 |
228
+
229
+ **plugin 仓** = plugin **工程实践**(git 维护、tsconfig 严格、vitest 单测、commit 历史)
230
+ **主仓** = plugin **运行时宿主**(plugin 激活后所有行为以 cwd = 主仓根为基础)
231
+ **plugin 不需要知道"主仓"在哪**——只看 cwd
232
+
233
+ **工程层独立(决策 #22)**:plugin 仓的 git/test/文档约束**独立于 RR6**——plugin 仓本身是 git repo 是为了工程需求(如 commit 历史、单测覆盖率追踪),**不是**为满足 RR6(plugin 加载时 cwd 仍是主仓根,不在 plugin 仓内)。
234
+
235
+ ## 6. 失败模式
236
+
237
+ | 失败 | 行为 | 用户可见性 |
238
+ |------|------|----------|
239
+ | cwd 不在 git repo | plugin 完全不激活 | opencode 默认行为(无变化) |
240
+ | `/.serenity` 不存在 | plugin 不激活 | 同上 |
241
+ | `/.serenity` 内容为空 | plugin 不激活 | log warn |
242
+ | `/.serenity` 找不到对应 SKILL.md | plugin 不激活 | log warn |
243
+ | msm_name 不在注册表 | `msm_exec` 抛 `MsmNotRegisteredError` | LLM 看到错误消息 |
244
+ | msm 子进程超时 (>30s) | `msm_exec` 抛 `MsmTimeoutError` | LLM 看到错误消息 |
245
+ | msm 子进程 exit ≠ 0 | `msm_exec` 抛 `MsmExecutionError`(持 stdout) | LLM 看到错误消息 + stdout |
246
+ | path-arg 越界 | `msm_exec` 抛 `MsmPathEscapeError` (v0.1-2) | LLM 看到错误消息 |
247
+ | path-arg 是 symlink 链 | `msm_exec` 抛 `MsmSymlinkError` (v1-1) | LLM 看到错误消息 |
248
+ | bash 被 LLM 调用 | 抛 `BashDisabledError` | LLM 看到错误消息(RR3 行为) |
249
+ | cwd 外 read/edit/write | tool.execute.before 抛 `[serenity] ... is outside the serenity workspace root (RR5)` | LLM 看到错误消息(v1.6 RR5 hard block) |
250
+ | hook factory 抛错 | `safeCreateHook` 自动 disable + 返回 no-op | 不影响 LLM(retry-storm 防护) |
251
+ | init-check opencode.json 缺字段 | 只 warn 不 patch | log.warn |
252
+ | config-patch 写失败 | log.warn + plugin 继续工作 | 用户需手动配置 |
253
+ | 外部 slash command(如 `/clear`) | 不拦截 | opencode 默认行为 |
254
+
255
+ ## 7. 演进路径(v0 → v1+)— ✅ 全部已实施
256
+
257
+ | 候选 | 描述 | 实施 |
258
+ |------|------|------|
259
+ | 候选 1 | `permission.asked` event hook 自动 reply "always"(v1.3-v4 决策:trust opencode own always list,无条件 reply) | ✅ v1.3 (commit `809bf94`) |
260
+ | 候选 2 | `experimental.session.compacting` hook(压缩后重注入 SKILL.md + 关键状态) | ✅ v1.4 (commit `cee8c2e`) |
261
+ | 候选 3 | `shell.env` hook 注入 `$HOME_SERENITY_ROOT` 等环境变量 | ✅ v1.5 (commit `00fcd19`) |
262
+ | 候选 4 | 多实例 registry(如果用户超过 3 个实例时) | ⏸ v0.0.2 不实施(实操未遇 ≥3 实例场景) |
263
+
264
+ 候选 1-3 实施于 v1.3-v1.5;候选 4 推迟到 v0.0.3+ 按需。完整记录见 `SESSION.md` 项目演进历史。
265
+
266
+ ## 8. 与 SDK 对接点
267
+
268
+ | 抽象层概念 | SDK 实现 (opencode 1.16.2) | 文档 |
269
+ |----------|--------------------------|------|
270
+ | 工具注册 | `import { tool } from '@opencode-ai/plugin'; tool({ description, args: z.object({...}), execute })` | `@opencode-ai/plugin/tool` |
271
+ | Hooks 集合 | `const hooks: Hooks = { tool: {...}, 'experimental.chat.system.transform': ... }` | `@opencode-ai/plugin/Hooks` |
272
+ | permission.asked event | `event: { 'permission.asked': async (input, output) => { output.reply = 'always' } }` | `@opencode-ai/plugin/Hooks.event` |
273
+ | tool.execute.before | `'tool.execute.before': async (input, output) => { ... throw ... }` | 同上 |
274
+ | system.transform | `'experimental.chat.system.transform': async (input, output) => { output.system.push(...) }` | 同上 |
275
+ | session.compacting | `'experimental.session.compacting': async (input, output) => { output.context.push(...) }` | 同上 |
276
+ | shell.env | `'shell.env': async (input, output) => { output.env.X = ... }` | 同上 |
277
+ | TUI plugin entry | `import type { TuiPlugin } from '@opencode-ai/plugin/tui'; export default { id, tui: Tui }` | `@opencode-ai/plugin/tui` |
278
+ | PluginModule 二选一 | `export default { id, server }` 或 `export default { id, tui }` | `@opencode-ai/plugin/Plugin` |
279
+ | toast 通知 | `api.ui.toast({ title, message, variant, duration })` (TuiPlugin) | 同上 |
280
+ | slash command | `api.command?.register(() => [{ title, value, slash, onSelect }])` | 同上 |
281
+
282
+ **SDK 升级注意**:opencode 1.16+ 强制 PluginModule 二选一,v1.8 引入 TUI 独立 entry 绕过此限制。**严禁**在同一 module 同时 export `server` 和 `tui`。
283
+
284
+ ## 9. 文档演进规则
285
+
286
+ - **范围层**(RR1-RR7)→ `docs/requirements-v0-scope.md`
287
+ - **方案层**(本文)→ `docs/architecture-v0.md`(本文)
288
+ - **接口层**(6 契约 + 13 错误类)→ `docs/contract-v0.md`
289
+ - **设计草案 / 演进候选** → `docs/v0.1-candidates.md` + `docs/refactor-direction-*.md` + `docs/rr7-init-design.md`
290
+ - **实现层** → `src/`
291
+ - **测试层** → `tests/`
292
+ - **SESSION 跟踪** → `SESSION.md`(项目即会话)
293
+
294
+ > **本文件最后修订**:2026-06-07(v0.0.2 文档债务清理 Phase 3 D7+D8:架构图、模块结构、SDK API 全部按 v0.0.2 实际重写;演进路径 4 候选全部标 ✅)