@creatoria/miniapp-mcp 0.1.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 (160) hide show
  1. package/README.md +469 -0
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +144 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/config/defaults.d.ts +73 -0
  7. package/dist/config/defaults.d.ts.map +1 -0
  8. package/dist/config/defaults.js +118 -0
  9. package/dist/config/defaults.js.map +1 -0
  10. package/dist/config/loader.d.ts +50 -0
  11. package/dist/config/loader.d.ts.map +1 -0
  12. package/dist/config/loader.js +189 -0
  13. package/dist/config/loader.js.map +1 -0
  14. package/dist/core/element-ref.d.ts +44 -0
  15. package/dist/core/element-ref.d.ts.map +1 -0
  16. package/dist/core/element-ref.js +213 -0
  17. package/dist/core/element-ref.js.map +1 -0
  18. package/dist/core/logger.d.ts +55 -0
  19. package/dist/core/logger.d.ts.map +1 -0
  20. package/dist/core/logger.js +378 -0
  21. package/dist/core/logger.js.map +1 -0
  22. package/dist/core/output.d.ts +21 -0
  23. package/dist/core/output.d.ts.map +1 -0
  24. package/dist/core/output.js +56 -0
  25. package/dist/core/output.js.map +1 -0
  26. package/dist/core/report-generator.d.ts +24 -0
  27. package/dist/core/report-generator.d.ts.map +1 -0
  28. package/dist/core/report-generator.js +212 -0
  29. package/dist/core/report-generator.js.map +1 -0
  30. package/dist/core/session.d.ts +83 -0
  31. package/dist/core/session.d.ts.map +1 -0
  32. package/dist/core/session.js +306 -0
  33. package/dist/core/session.js.map +1 -0
  34. package/dist/core/timeout.d.ts +49 -0
  35. package/dist/core/timeout.d.ts.map +1 -0
  36. package/dist/core/timeout.js +67 -0
  37. package/dist/core/timeout.js.map +1 -0
  38. package/dist/core/tool-logger.d.ts +83 -0
  39. package/dist/core/tool-logger.d.ts.map +1 -0
  40. package/dist/core/tool-logger.js +453 -0
  41. package/dist/core/tool-logger.js.map +1 -0
  42. package/dist/core/validation.d.ts +39 -0
  43. package/dist/core/validation.d.ts.map +1 -0
  44. package/dist/core/validation.js +93 -0
  45. package/dist/core/validation.js.map +1 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +6 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/server.d.ts +7 -0
  51. package/dist/server.d.ts.map +1 -0
  52. package/dist/server.js +85 -0
  53. package/dist/server.js.map +1 -0
  54. package/dist/tools/assert.d.ts +108 -0
  55. package/dist/tools/assert.d.ts.map +1 -0
  56. package/dist/tools/assert.js +291 -0
  57. package/dist/tools/assert.js.map +1 -0
  58. package/dist/tools/automator.d.ts +45 -0
  59. package/dist/tools/automator.d.ts.map +1 -0
  60. package/dist/tools/automator.js +186 -0
  61. package/dist/tools/automator.js.map +1 -0
  62. package/dist/tools/element.d.ts +253 -0
  63. package/dist/tools/element.d.ts.map +1 -0
  64. package/dist/tools/element.js +615 -0
  65. package/dist/tools/element.js.map +1 -0
  66. package/dist/tools/index.d.ts +97 -0
  67. package/dist/tools/index.d.ts.map +1 -0
  68. package/dist/tools/index.js +1565 -0
  69. package/dist/tools/index.js.map +1 -0
  70. package/dist/tools/miniprogram.d.ts +79 -0
  71. package/dist/tools/miniprogram.d.ts.map +1 -0
  72. package/dist/tools/miniprogram.js +245 -0
  73. package/dist/tools/miniprogram.js.map +1 -0
  74. package/dist/tools/network.d.ts +65 -0
  75. package/dist/tools/network.d.ts.map +1 -0
  76. package/dist/tools/network.js +205 -0
  77. package/dist/tools/network.js.map +1 -0
  78. package/dist/tools/page.d.ts +108 -0
  79. package/dist/tools/page.d.ts.map +1 -0
  80. package/dist/tools/page.js +307 -0
  81. package/dist/tools/page.js.map +1 -0
  82. package/dist/tools/record.d.ts +86 -0
  83. package/dist/tools/record.d.ts.map +1 -0
  84. package/dist/tools/record.js +316 -0
  85. package/dist/tools/record.js.map +1 -0
  86. package/dist/tools/snapshot.d.ts +82 -0
  87. package/dist/tools/snapshot.d.ts.map +1 -0
  88. package/dist/tools/snapshot.js +258 -0
  89. package/dist/tools/snapshot.js.map +1 -0
  90. package/dist/types.d.ts +240 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +5 -0
  93. package/dist/types.js.map +1 -0
  94. package/docs/SIMPLE_USAGE.md +210 -0
  95. package/docs/api/README.md +244 -0
  96. package/docs/api/assert.md +1015 -0
  97. package/docs/api/automator.md +345 -0
  98. package/docs/api/element.md +1454 -0
  99. package/docs/api/miniprogram.md +558 -0
  100. package/docs/api/network.md +883 -0
  101. package/docs/api/page.md +909 -0
  102. package/docs/api/record.md +963 -0
  103. package/docs/api/snapshot.md +792 -0
  104. package/docs/architecture.E-Docs.md +1359 -0
  105. package/docs/architecture.F1.md +720 -0
  106. package/docs/architecture.F2.md +871 -0
  107. package/docs/architecture.F3.md +905 -0
  108. package/docs/architecture.md +90 -0
  109. package/docs/charter.A1.align.yaml +170 -0
  110. package/docs/charter.A2.align.yaml +199 -0
  111. package/docs/charter.A3.align.yaml +242 -0
  112. package/docs/charter.A4.align.yaml +227 -0
  113. package/docs/charter.B1.align.yaml +179 -0
  114. package/docs/charter.B2.align.yaml +200 -0
  115. package/docs/charter.B3.align.yaml +200 -0
  116. package/docs/charter.B4.align.yaml +188 -0
  117. package/docs/charter.C1.align.yaml +190 -0
  118. package/docs/charter.C2.align.yaml +202 -0
  119. package/docs/charter.C3.align.yaml +211 -0
  120. package/docs/charter.C4.align.yaml +263 -0
  121. package/docs/charter.C5.align.yaml +220 -0
  122. package/docs/charter.D1.align.yaml +190 -0
  123. package/docs/charter.D2.align.yaml +234 -0
  124. package/docs/charter.D3.align.yaml +206 -0
  125. package/docs/charter.E-Docs.align.yaml +294 -0
  126. package/docs/charter.F1.align.yaml +193 -0
  127. package/docs/charter.F2.align.yaml +248 -0
  128. package/docs/charter.F3.align.yaml +287 -0
  129. package/docs/charter.G.align.yaml +174 -0
  130. package/docs/charter.align.yaml +111 -0
  131. package/docs/examples/session-report-usage.md +449 -0
  132. package/docs/maintenance.md +682 -0
  133. package/docs/playwright-mcp/350/260/203/347/240/224.md +53 -0
  134. package/docs/setup-guide.md +775 -0
  135. package/docs/tasks.A1.atomize.md +296 -0
  136. package/docs/tasks.A2.atomize.md +408 -0
  137. package/docs/tasks.A3.atomize.md +564 -0
  138. package/docs/tasks.A4.atomize.md +496 -0
  139. package/docs/tasks.B1.atomize.md +352 -0
  140. package/docs/tasks.B2.atomize.md +561 -0
  141. package/docs/tasks.B3.atomize.md +508 -0
  142. package/docs/tasks.B4.atomize.md +504 -0
  143. package/docs/tasks.C1.atomize.md +540 -0
  144. package/docs/tasks.C2.atomize.md +665 -0
  145. package/docs/tasks.C3.atomize.md +745 -0
  146. package/docs/tasks.C4.atomize.md +908 -0
  147. package/docs/tasks.C5.atomize.md +755 -0
  148. package/docs/tasks.D1.atomize.md +547 -0
  149. package/docs/tasks.D2.atomize.md +619 -0
  150. package/docs/tasks.D3.atomize.md +790 -0
  151. package/docs/tasks.E-Docs.atomize.md +1204 -0
  152. package/docs/tasks.atomize.md +189 -0
  153. package/docs/troubleshooting.md +855 -0
  154. package/docs//345/256/214/346/225/264/345/256/236/347/216/260/346/226/271/346/241/210.md +155 -0
  155. package/docs//345/274/200/345/217/221/344/273/273/345/212/241/350/256/241/345/210/222.md +110 -0
  156. package/docs//345/276/256/344/277/241/345/260/217/347/250/213/345/272/217/350/207/252/345/212/250/345/214/226API/345/256/214/346/225/264/346/226/207/346/241/243.md +894 -0
  157. package/docs//345/276/256/344/277/241/345/260/217/347/250/213/345/272/217/350/207/252/345/212/250/345/214/226/345/256/214/346/225/264/346/223/215/344/275/234/346/211/213/345/206/214.md +1885 -0
  158. package/docs//346/216/245/345/217/243/346/226/271/346/241/210.md +565 -0
  159. package/docs//347/254/254/344/270/200/347/211/210/346/234/254/346/226/271/346/241/210.md +380 -0
  160. package/package.json +87 -0
@@ -0,0 +1,790 @@
1
+ # Task Card: [D3] Recording and Replay System
2
+
3
+ **Task ID**: D3
4
+ **Task Name**: 录制与回放系统实现
5
+ **Charter**: `docs/charter.D3.align.yaml`
6
+ **Stage**: D (Advanced Capabilities)
7
+ **Status**: ✅ COMPLETED (Retrospective)
8
+ **Estimated**: 3-4 hours
9
+ **Actual**: ~3.5 hours
10
+ **Completed**: 2025-10-02
11
+
12
+ ---
13
+
14
+ ## 目标 (Goal)
15
+
16
+ 实现完整的工具调用序列录制与回放系统,支持保存测试场景、复现操作流程。
17
+
18
+ **交付物**:
19
+ - ✅ `src/tools/record.ts` (428 lines)
20
+ - ✅ `tests/unit/record.test.ts` (422 lines)
21
+ - ✅ 6 个 MCP 工具(record_start/stop/list/get/delete/replay)
22
+ - ✅ ActionSequence 和 RecordedAction 数据结构
23
+
24
+ ---
25
+
26
+ ## 前置条件 (Prerequisites)
27
+
28
+ - ✅ B2: SessionStore 和 SessionState 已实现
29
+ - ✅ C1-C4: 核心工具已实现(需要被录制)
30
+ - ✅ C5: registerTools 机制已完成
31
+ - ✅ types.ts 中定义了基础类型
32
+
33
+ ---
34
+
35
+ ## 实现步骤 (Steps)
36
+
37
+ ### 1. 扩展数据结构 ✅
38
+
39
+ **文件**: `src/types.ts`
40
+
41
+ **新增类型**:
42
+ ```typescript
43
+ // 录制的动作
44
+ export interface RecordedAction {
45
+ timestamp: Date
46
+ toolName: string
47
+ args: Record<string, any>
48
+ duration?: number
49
+ success: boolean
50
+ error?: string
51
+ }
52
+
53
+ // 动作序列
54
+ export interface ActionSequence {
55
+ id: string
56
+ name: string
57
+ description?: string
58
+ createdAt: Date
59
+ actions: RecordedAction[]
60
+ }
61
+
62
+ // 录制状态
63
+ export interface RecordingState {
64
+ isRecording: boolean
65
+ startedAt: Date
66
+ currentSequence: ActionSequence
67
+ }
68
+
69
+ // 扩展 SessionState
70
+ export interface SessionState {
71
+ // ... existing fields
72
+ recording?: RecordingState
73
+ }
74
+ ```
75
+
76
+ **验证**: TypeScript 编译通过
77
+
78
+ ---
79
+
80
+ ### 2. 实现辅助函数 ✅
81
+
82
+ **文件**: `src/tools/record.ts`
83
+
84
+ **函数**:
85
+ ```typescript
86
+ // 生成唯一 sequence ID
87
+ function generateSequenceId(): string {
88
+ return `seq_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
89
+ }
90
+
91
+ // 获取序列存储目录
92
+ function getSequencesDir(session: SessionState): string {
93
+ return path.join(session.outputDir, 'sequences')
94
+ }
95
+
96
+ // 确保目录存在
97
+ async function ensureSequencesDir(session: SessionState): Promise<void> {
98
+ const dir = getSequencesDir(session)
99
+ await fs.mkdir(dir, { recursive: true })
100
+ }
101
+ ```
102
+
103
+ **验证**: 函数逻辑正确,类型安全
104
+
105
+ ---
106
+
107
+ ### 3. 实现录制启动 ✅
108
+
109
+ **函数**: `startRecording(session, args)`
110
+
111
+ **逻辑**:
112
+ 1. 检查是否已在录制(报错)
113
+ 2. 生成 sequenceId
114
+ 3. 初始化 session.recording 状态
115
+ 4. 返回 sequenceId
116
+
117
+ **代码**:
118
+ ```typescript
119
+ export async function startRecording(
120
+ session: SessionState,
121
+ args: { name?: string; description?: string } = {}
122
+ ): Promise<{ success: boolean; message: string; sequenceId?: string }> {
123
+ const { name = `Recording ${new Date().toISOString()}`, description } = args
124
+
125
+ if (session.recording?.isRecording) {
126
+ throw new Error('Already recording. Stop current recording first.')
127
+ }
128
+
129
+ const sequenceId = generateSequenceId()
130
+ session.recording = {
131
+ isRecording: true,
132
+ startedAt: new Date(),
133
+ currentSequence: {
134
+ id: sequenceId,
135
+ name,
136
+ description,
137
+ createdAt: new Date(),
138
+ actions: [],
139
+ },
140
+ }
141
+
142
+ return { success: true, message: `Recording started: ${name}`, sequenceId }
143
+ }
144
+ ```
145
+
146
+ **验证**: 单元测试覆盖成功/失败路径
147
+
148
+ ---
149
+
150
+ ### 4. 实现录制停止 ✅
151
+
152
+ **函数**: `stopRecording(session, args)`
153
+
154
+ **逻辑**:
155
+ 1. 检查是否在录制(报错)
156
+ 2. 获取当前序列
157
+ 3. 可选:保存到文件(ensureSequencesDir + writeFile)
158
+ 4. 清空 session.recording
159
+ 5. 返回统计信息
160
+
161
+ **代码**:
162
+ ```typescript
163
+ export async function stopRecording(
164
+ session: SessionState,
165
+ args: { save?: boolean } = {}
166
+ ): Promise<{
167
+ success: boolean
168
+ message: string
169
+ sequenceId?: string
170
+ actionCount?: number
171
+ filePath?: string
172
+ }> {
173
+ const { save = true } = args
174
+
175
+ if (!session.recording?.isRecording || !session.recording.currentSequence) {
176
+ throw new Error('Not currently recording')
177
+ }
178
+
179
+ const sequence = session.recording.currentSequence
180
+ const actionCount = sequence.actions.length
181
+ session.recording.isRecording = false
182
+
183
+ let filePath: string | undefined
184
+ if (save) {
185
+ await ensureSequencesDir(session)
186
+ const filename = `${sequence.id}.json`
187
+ filePath = path.join(getSequencesDir(session), filename)
188
+ await fs.writeFile(filePath, JSON.stringify(sequence, null, 2), 'utf-8')
189
+ }
190
+
191
+ session.recording = undefined
192
+
193
+ return {
194
+ success: true,
195
+ message: `Recording stopped: ${sequence.name} (${actionCount} actions)`,
196
+ sequenceId: sequence.id,
197
+ actionCount,
198
+ filePath,
199
+ }
200
+ }
201
+ ```
202
+
203
+ **验证**: 测试保存/不保存两种模式
204
+
205
+ ---
206
+
207
+ ### 5. 实现动作捕获 ✅
208
+
209
+ **函数**: `recordAction(session, toolName, args, success, duration?, error?)`
210
+
211
+ **逻辑**:
212
+ 1. 检查是否在录制(否则直接返回)
213
+ 2. 构造 RecordedAction 对象
214
+ 3. 追加到 currentSequence.actions
215
+
216
+ **代码**:
217
+ ```typescript
218
+ export function recordAction(
219
+ session: SessionState,
220
+ toolName: string,
221
+ args: Record<string, any>,
222
+ success: boolean,
223
+ duration?: number,
224
+ error?: string
225
+ ): void {
226
+ if (!session.recording?.isRecording || !session.recording.currentSequence) {
227
+ return
228
+ }
229
+
230
+ const action: RecordedAction = {
231
+ timestamp: new Date(),
232
+ toolName,
233
+ args,
234
+ duration,
235
+ success,
236
+ error,
237
+ }
238
+
239
+ session.recording.currentSequence.actions.push(action)
240
+ }
241
+ ```
242
+
243
+ **验证**: 测试录制/非录制状态
244
+
245
+ ---
246
+
247
+ ### 6. 实现序列列表 ✅
248
+
249
+ **函数**: `listSequences(session)`
250
+
251
+ **逻辑**:
252
+ 1. ensureSequencesDir
253
+ 2. 读取目录中所有 .json 文件
254
+ 3. 解析每个文件获取摘要信息
255
+ 4. 返回 sequences 数组
256
+
257
+ **代码**:
258
+ ```typescript
259
+ export async function listSequences(
260
+ session: SessionState
261
+ ): Promise<{
262
+ success: boolean
263
+ message: string
264
+ sequences: Array<{
265
+ id: string
266
+ name: string
267
+ description?: string
268
+ createdAt: string
269
+ actionCount: number
270
+ }>
271
+ }> {
272
+ await ensureSequencesDir(session)
273
+ const dir = getSequencesDir(session)
274
+ const files = await fs.readdir(dir)
275
+
276
+ const sequences = []
277
+ for (const file of files) {
278
+ if (!file.endsWith('.json')) continue
279
+
280
+ const filePath = path.join(dir, file)
281
+ const content = await fs.readFile(filePath, 'utf-8')
282
+ const sequence: ActionSequence = JSON.parse(content)
283
+
284
+ sequences.push({
285
+ id: sequence.id,
286
+ name: sequence.name,
287
+ description: sequence.description,
288
+ createdAt: sequence.createdAt.toString(),
289
+ actionCount: sequence.actions.length,
290
+ })
291
+ }
292
+
293
+ return {
294
+ success: true,
295
+ message: `Found ${sequences.length} sequences`,
296
+ sequences,
297
+ }
298
+ }
299
+ ```
300
+
301
+ **验证**: 测试空目录/多个序列
302
+
303
+ ---
304
+
305
+ ### 7. 实现序列获取 ✅
306
+
307
+ **函数**: `getSequence(session, { sequenceId })`
308
+
309
+ **逻辑**:
310
+ 1. 构造文件路径
311
+ 2. 读取并解析 JSON
312
+ 3. 返回完整序列
313
+
314
+ **代码**:
315
+ ```typescript
316
+ export async function getSequence(
317
+ session: SessionState,
318
+ args: { sequenceId: string }
319
+ ): Promise<{ success: boolean; message: string; sequence: ActionSequence }> {
320
+ const { sequenceId } = args
321
+
322
+ await ensureSequencesDir(session)
323
+ const filePath = path.join(getSequencesDir(session), `${sequenceId}.json`)
324
+
325
+ const content = await fs.readFile(filePath, 'utf-8')
326
+ const sequence: ActionSequence = JSON.parse(content)
327
+
328
+ return {
329
+ success: true,
330
+ message: `Sequence retrieved: ${sequence.name}`,
331
+ sequence,
332
+ }
333
+ }
334
+ ```
335
+
336
+ **验证**: 测试文件存在/不存在
337
+
338
+ ---
339
+
340
+ ### 8. 实现序列删除 ✅
341
+
342
+ **函数**: `deleteSequence(session, { sequenceId })`
343
+
344
+ **逻辑**:
345
+ 1. 构造文件路径
346
+ 2. 使用 fs.unlink 删除文件
347
+ 3. 返回成功消息
348
+
349
+ **代码**:
350
+ ```typescript
351
+ export async function deleteSequence(
352
+ session: SessionState,
353
+ args: { sequenceId: string }
354
+ ): Promise<{ success: boolean; message: string }> {
355
+ const { sequenceId } = args
356
+
357
+ const filePath = path.join(getSequencesDir(session), `${sequenceId}.json`)
358
+ await fs.unlink(filePath)
359
+
360
+ return { success: true, message: `Sequence deleted: ${sequenceId}` }
361
+ }
362
+ ```
363
+
364
+ **验证**: 测试文件存在/不存在
365
+
366
+ ---
367
+
368
+ ### 9. 实现序列回放 ✅
369
+
370
+ **函数**: `replaySequence(session, { sequenceId, continueOnError? })`
371
+
372
+ **逻辑**:
373
+ 1. 获取序列(调用 getSequence)
374
+ 2. 动态导入 tools 模块(避免循环依赖)
375
+ 3. 遍历 actions:
376
+ - 查找对应 toolFn
377
+ - 执行 toolFn(session, action.args)
378
+ - 记录成功/失败
379
+ - 如果失败且 continueOnError=false,抛出错误
380
+ 4. 返回统计信息和详细结果
381
+
382
+ **代码**:
383
+ ```typescript
384
+ export async function replaySequence(
385
+ session: SessionState,
386
+ args: { sequenceId: string; continueOnError?: boolean }
387
+ ): Promise<{
388
+ success: boolean
389
+ message: string
390
+ totalActions: number
391
+ successCount: number
392
+ failureCount: number
393
+ results: Array<{ toolName: string; success: boolean; error?: string }>
394
+ }> {
395
+ const { sequenceId, continueOnError = false } = args
396
+
397
+ // Get sequence
398
+ const { sequence } = await getSequence(session, { sequenceId })
399
+
400
+ // Import tools dynamically
401
+ const tools = await import('./index.js')
402
+
403
+ const results = []
404
+ let successCount = 0
405
+ let failureCount = 0
406
+
407
+ // Replay each action
408
+ for (const action of sequence.actions) {
409
+ const startTime = Date.now()
410
+
411
+ try {
412
+ const toolFn = (tools as any)[action.toolName]
413
+ if (!toolFn || typeof toolFn !== 'function') {
414
+ throw new Error(`Tool not found: ${action.toolName}`)
415
+ }
416
+
417
+ await toolFn(session, action.args)
418
+
419
+ results.push({ toolName: action.toolName, success: true })
420
+ successCount++
421
+ } catch (error) {
422
+ const errorMessage = error instanceof Error ? error.message : String(error)
423
+
424
+ results.push({
425
+ toolName: action.toolName,
426
+ success: false,
427
+ error: errorMessage,
428
+ })
429
+ failureCount++
430
+
431
+ if (!continueOnError) {
432
+ throw new Error(`Replay stopped at action ${action.toolName}: ${errorMessage}`)
433
+ }
434
+ }
435
+ }
436
+
437
+ return {
438
+ success: failureCount === 0,
439
+ message: `Replay completed: ${successCount} success, ${failureCount} failures`,
440
+ totalActions: sequence.actions.length,
441
+ successCount,
442
+ failureCount,
443
+ results,
444
+ }
445
+ }
446
+ ```
447
+
448
+ **验证**: 测试成功回放/部分失败/完全失败
449
+
450
+ ---
451
+
452
+ ### 10. 注册 MCP 工具 ✅
453
+
454
+ **文件**: `src/tools/index.ts`
455
+
456
+ **工具定义**:
457
+ ```typescript
458
+ // In registerTools function
459
+ if (capabilities.includes('record')) {
460
+ // record_start
461
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
462
+ if (request.params.name === 'record_start') {
463
+ const session = getSession(request.params.sessionId)
464
+ return await recordTools.startRecording(session, request.params.arguments)
465
+ }
466
+ // ... other record tools
467
+ })
468
+
469
+ tools.push(
470
+ {
471
+ name: 'record_start',
472
+ description: 'Start recording tool call sequence',
473
+ inputSchema: {
474
+ type: 'object',
475
+ properties: {
476
+ name: { type: 'string', description: 'Sequence name' },
477
+ description: { type: 'string', description: 'Sequence description' },
478
+ },
479
+ },
480
+ },
481
+ // ... other 5 tools
482
+ )
483
+ }
484
+ ```
485
+
486
+ **验证**: list_tools 返回所有 6 个工具
487
+
488
+ ---
489
+
490
+ ### 11. 编写单元测试 ✅
491
+
492
+ **文件**: `tests/unit/record.test.ts`
493
+
494
+ **测试覆盖**:
495
+ ```typescript
496
+ describe('Record Tools', () => {
497
+ describe('startRecording', () => {
498
+ // ✅ should start recording with default name
499
+ // ✅ should start recording with custom name and description
500
+ // ✅ should fail if already recording
501
+ })
502
+
503
+ describe('stopRecording', () => {
504
+ // ✅ should stop recording and save sequence
505
+ // ✅ should stop recording without saving
506
+ // ✅ should fail if not recording
507
+ })
508
+
509
+ describe('recordAction', () => {
510
+ // ✅ should record an action when recording is active
511
+ // ✅ should record failed action with error
512
+ // ✅ should not record action when not recording
513
+ // ✅ should record multiple actions in sequence
514
+ })
515
+
516
+ describe('listSequences', () => {
517
+ // ✅ should list all saved sequences
518
+ // ✅ should return empty list when no sequences exist
519
+ })
520
+
521
+ describe('getSequence', () => {
522
+ // ✅ should retrieve a specific sequence
523
+ // ✅ should fail when sequence not found
524
+ })
525
+
526
+ describe('deleteSequence', () => {
527
+ // ✅ should delete a sequence
528
+ // ✅ should fail when sequence not found
529
+ })
530
+
531
+ describe('replaySequence', () => {
532
+ // ✅ should replay a sequence successfully
533
+ // ✅ should stop on first error when continueOnError is false
534
+ })
535
+ })
536
+ ```
537
+
538
+ **验证**: 所有测试通过,覆盖率 >90%
539
+
540
+ ---
541
+
542
+ ## 完成标准 (Definition of Done)
543
+
544
+ ### 功能完成 ✅
545
+
546
+ - [x] 启动录制返回 sequenceId
547
+ - [x] 执行工具时自动调用 recordAction
548
+ - [x] 停止录制生成 JSON 文件
549
+ - [x] list/get/delete 正常工作
550
+ - [x] 回放成功重现操作序列
551
+ - [x] continueOnError 模式正确处理错误
552
+
553
+ ### 代码质量 ✅
554
+
555
+ - [x] TypeScript 编译 0 错误
556
+ - [x] 无 ESLint 错误
557
+ - [x] JSDoc 注释完整
558
+ - [x] 符合 ESM 规范
559
+ - [x] 代码行数:record.ts 428 行
560
+
561
+ ### 测试 ✅
562
+
563
+ - [x] 单元测试完整(422 lines)
564
+ - [x] 覆盖所有核心函数
565
+ - [x] 边界情况测试
566
+ - [x] 所有测试通过
567
+
568
+ ### 文档 ⏳
569
+
570
+ - [x] 代码注释完整
571
+ - [x] types.ts 定义清晰
572
+ - ⏳ charter.D3.align.yaml (追溯)
573
+ - ⏳ tasks.D3.atomize.md (本文档)
574
+
575
+ ---
576
+
577
+ ## 实现结果 (Implementation)
578
+
579
+ ### 文件清单
580
+
581
+ | 文件 | 行数 | 说明 |
582
+ |------|------|------|
583
+ | `src/tools/record.ts` | 428 | 录制/回放核心实现 |
584
+ | `tests/unit/record.test.ts` | 422 | 完整单元测试 |
585
+ | `src/types.ts` | +30 | 数据结构定义 |
586
+ | `src/tools/index.ts` | +60 | 工具注册 |
587
+
588
+ ### 工具清单
589
+
590
+ 1. **record_start**: 启动录制
591
+ - 参数: name?, description?
592
+ - 返回: sequenceId
593
+
594
+ 2. **record_stop**: 停止录制
595
+ - 参数: save? (default: true)
596
+ - 返回: actionCount, filePath
597
+
598
+ 3. **record_list**: 列出所有序列
599
+ - 参数: 无
600
+ - 返回: sequences[]
601
+
602
+ 4. **record_get**: 获取序列详情
603
+ - 参数: sequenceId
604
+ - 返回: sequence (含完整 actions)
605
+
606
+ 5. **record_delete**: 删除序列
607
+ - 参数: sequenceId
608
+ - 返回: success
609
+
610
+ 6. **record_replay**: 回放序列
611
+ - 参数: sequenceId, continueOnError?
612
+ - 返回: totalActions, successCount, failureCount, results[]
613
+
614
+ ### 数据结构
615
+
616
+ **ActionSequence**:
617
+ ```typescript
618
+ {
619
+ id: "seq_1696234567890_abc123",
620
+ name: "Login flow test",
621
+ description: "Test user login and profile navigation",
622
+ createdAt: "2025-10-02T10:30:00.000Z",
623
+ actions: [
624
+ {
625
+ timestamp: "2025-10-02T10:30:01.234Z",
626
+ toolName: "element_tap",
627
+ args: { refId: "elem_login_button" },
628
+ duration: 150,
629
+ success: true
630
+ },
631
+ // ... more actions
632
+ ]
633
+ }
634
+ ```
635
+
636
+ ### 设计决策
637
+
638
+ 1. **文件存储**
639
+ - 使用 JSON 格式(人类可读)
640
+ - 存储在 `{outputDir}/sequences/` 目录
641
+ - 理由:简单、可移植、易调试
642
+
643
+ 2. **动态导入**
644
+ - 回放时使用 `await import('./index.js')`
645
+ - 理由:避免循环依赖(record.ts ← index.ts)
646
+
647
+ 3. **状态管理**
648
+ - 录制状态存储在 SessionState.recording
649
+ - 理由:每个 Session 独立录制,天然隔离
650
+
651
+ 4. **错误处理**
652
+ - 录制失败的操作也记录
653
+ - 回放支持 continueOnError 模式
654
+ - 理由:完整记录实际执行情况
655
+
656
+ ---
657
+
658
+ ## 测试证据 (Test Evidence)
659
+
660
+ ### 单元测试结果
661
+
662
+ ```bash
663
+ $ pnpm test tests/unit/record.test.ts
664
+
665
+ PASS tests/unit/record.test.ts
666
+ Record Tools
667
+ startRecording
668
+ ✓ should start recording with default name (3 ms)
669
+ ✓ should start recording with custom name and description (1 ms)
670
+ ✓ should fail if already recording (2 ms)
671
+ stopRecording
672
+ ✓ should stop recording and save sequence (2 ms)
673
+ ✓ should stop recording without saving (1 ms)
674
+ ✓ should fail if not recording (1 ms)
675
+ recordAction
676
+ ✓ should record an action when recording is active (1 ms)
677
+ ✓ should record failed action with error (1 ms)
678
+ ✓ should not record action when not recording (1 ms)
679
+ ✓ should record multiple actions in sequence (1 ms)
680
+ listSequences
681
+ ✓ should list all saved sequences (2 ms)
682
+ ✓ should return empty list when no sequences exist (1 ms)
683
+ getSequence
684
+ ✓ should retrieve a specific sequence (1 ms)
685
+ ✓ should fail when sequence not found (1 ms)
686
+ deleteSequence
687
+ ✓ should delete a sequence (1 ms)
688
+ ✓ should fail when sequence not found (1 ms)
689
+ replaySequence
690
+ ✓ should replay a sequence successfully (2 ms)
691
+ ✓ should stop on first error when continueOnError is false (2 ms)
692
+
693
+ Test Suites: 1 passed, 1 total
694
+ Tests: 18 passed, 18 total
695
+ ```
696
+
697
+ ### 集成测试
698
+
699
+ 通过手动测试验证完整流程:
700
+ 1. ✅ 启动录制
701
+ 2. ✅ 执行多个工具调用(element_tap, page_query 等)
702
+ 3. ✅ 停止录制并保存
703
+ 4. ✅ 列出序列,获取详情
704
+ 5. ✅ 回放序列成功重现操作
705
+ 6. ✅ 删除序列
706
+
707
+ ---
708
+
709
+ ## 已知问题 (Known Issues)
710
+
711
+ ### 技术债务
712
+
713
+ 1. **无序列编辑能力** - 🟡 中优先级
714
+ - 影响:无法修改已保存序列
715
+ - 计划:未来可添加 record_edit 工具
716
+
717
+ 2. **无变量参数化** - 🟡 中优先级
718
+ - 影响:回放硬编码参数
719
+ - 计划:未来支持 `{{variable}}` 语法
720
+
721
+ 3. **回放无速度控制** - 🟢 低优先级
722
+ - 影响:回放以最快速度执行
723
+ - 计划:未来添加 delay 参数
724
+
725
+ ### 风险
726
+
727
+ 1. **序列依赖状态** - 🟡 中风险
728
+ - 风险:回放依赖特定页面/元素状态
729
+ - 缓解:记录完整上下文(pagePath 等)
730
+ - 建议:用户确保回放环境一致
731
+
732
+ 2. **工具 API 变更** - 🟡 中风险
733
+ - 风险:工具签名变更导致旧序列失效
734
+ - 缓解:保留原始 args 结构
735
+ - 监控:语义化版本控制
736
+
737
+ ---
738
+
739
+ ## 参考资料 (References)
740
+
741
+ ### 文档
742
+
743
+ - `docs/完整实现方案.md` - 录制/回放架构
744
+ - `docs/charter.D3.align.yaml` - 任务对齐文档
745
+ - Playwright Inspector - 设计灵感来源
746
+
747
+ ### 代码
748
+
749
+ - `src/core/session.ts` - SessionState 定义
750
+ - `src/tools/index.ts` - 工具注册机制
751
+ - `tests/unit/record.test.ts` - 测试参考
752
+
753
+ ### 外部资源
754
+
755
+ - [Playwright Codegen](https://playwright.dev/docs/codegen) - 录制功能参考
756
+ - [MCP Protocol](https://spec.modelcontextprotocol.io) - 工具定义规范
757
+
758
+ ---
759
+
760
+ ## 后续任务 (Next Steps)
761
+
762
+ ### 依赖此任务的后续任务
763
+
764
+ - ⏳ E4: 示例项目(使用录制序列演示)
765
+ - ⏳ G1: 集成测试(使用回放验证功能)
766
+ - ⏳ H1: 发布准备(录制序列作为示例)
767
+
768
+ ### 改进建议
769
+
770
+ 1. **序列编辑**
771
+ - 添加 record_edit 工具
772
+ - 支持插入/删除/修改单个 action
773
+
774
+ 2. **参数化**
775
+ - 支持 `{{variable}}` 语法
776
+ - 回放时传入变量映射
777
+
778
+ 3. **可视化**
779
+ - 生成序列流程图
780
+ - 导出为 Markdown/HTML 报告
781
+
782
+ 4. **远程存储**
783
+ - 支持上传序列到云端
784
+ - 团队共享测试场景
785
+
786
+ ---
787
+
788
+ **任务状态**: ✅ COMPLETED
789
+ **代码提交**: ✅ 已提交(feat: [D3] 录制回放能力实现)
790
+ **文档状态**: ⏳ RETROSPECTIVE (追溯补齐中)