foliko 1.1.64 → 1.1.65

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.64",
3
+ "version": "1.1.65",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -62,10 +62,12 @@ class AuditPlugin extends Plugin {
62
62
 
63
63
  return {
64
64
  success: true,
65
- logs: logs.slice(-limit - offset, logs.length - offset),
66
- total: logs.length,
67
- offset,
68
- limit
65
+ data: logs.slice(-limit - offset, logs.length - offset),
66
+ metadata: {
67
+ total: logs.length,
68
+ offset,
69
+ limit
70
+ }
69
71
  }
70
72
  }
71
73
  })
@@ -78,7 +80,7 @@ class AuditPlugin extends Plugin {
78
80
  const stats = this.getStats()
79
81
  return {
80
82
  success: true,
81
- stats
83
+ data: stats
82
84
  }
83
85
  }
84
86
  })
@@ -96,8 +98,10 @@ class AuditPlugin extends Plugin {
96
98
  const result = this.exportLogs(args)
97
99
  return {
98
100
  success: true,
99
- count: result.logs.length,
100
- data: result.logs
101
+ data: result.logs,
102
+ metadata: {
103
+ count: result.logs.length
104
+ }
101
105
  }
102
106
  }
103
107
  })
@@ -156,12 +156,14 @@ class CoordinatorPlugin extends Plugin {
156
156
 
157
157
  return {
158
158
  success: true,
159
- workerId: result.workerId,
160
- status: result.status,
161
- result: result.result,
162
- message: result.status === 'completed'
163
- ? `Worker '${args.worker_name}' completed with result`
164
- : `Worker '${args.worker_name}' ${result.status}`,
159
+ data: result.result,
160
+ metadata: {
161
+ workerId: result.workerId,
162
+ status: result.status,
163
+ message: result.status === 'completed'
164
+ ? `Worker '${args.worker_name}' completed with result`
165
+ : `Worker '${args.worker_name}' ${result.status}`,
166
+ },
165
167
  };
166
168
  } catch (err) {
167
169
  return { success: false, error: err.message };
@@ -187,8 +189,8 @@ class CoordinatorPlugin extends Plugin {
187
189
  const result = await coordinatorManager.sendToWorker(args.worker_name, args.message);
188
190
  return {
189
191
  success: true,
190
- workerName: args.worker_name,
191
- result: result.message || result,
192
+ data: result.message || result,
193
+ metadata: { workerName: args.worker_name },
192
194
  };
193
195
  } catch (err) {
194
196
  return { success: false, error: err.message };
@@ -214,7 +216,7 @@ class CoordinatorPlugin extends Plugin {
214
216
  const result = coordinatorManager.stopWorker(args.worker_name, args.reason);
215
217
  return {
216
218
  success: true,
217
- ...result,
219
+ data: result,
218
220
  };
219
221
  } catch (err) {
220
222
  return { success: false, error: err.message };
@@ -236,8 +238,8 @@ class CoordinatorPlugin extends Plugin {
236
238
  const workers = coordinatorManager.listWorkers();
237
239
  return {
238
240
  success: true,
239
- count: workers.length,
240
- workers,
241
+ data: workers,
242
+ metadata: { count: workers.length },
241
243
  };
242
244
  },
243
245
  });
@@ -262,7 +264,7 @@ class CoordinatorPlugin extends Plugin {
262
264
 
263
265
  return {
264
266
  success: true,
265
- worker,
267
+ data: worker,
266
268
  };
267
269
  },
268
270
  });
@@ -0,0 +1,323 @@
1
+ /**
2
+ * DataSplitterPlugin — 大数据自动分拆插件
3
+ *
4
+ * 功能:
5
+ * 1. 注册 `split_and_process` 工具:AI 可主动调用,将大文本分拆给子 Agent 处理
6
+ * 2. 注册 `get_content_preview` 工具:获取大内容的前 N 行,判断是否需要分拆
7
+ * 3. 自动检测工具返回的大数据,触发透明分拆
8
+ * 4. 系统提示词中告知 AI 大数据处理能力
9
+ */
10
+
11
+ const { Plugin } = require('../src/core/plugin-base');
12
+ const { z } = require('zod');
13
+ const { logger } = require('../src/utils/logger');
14
+ const { DataSplitter } = require('../src/utils/data-splitter');
15
+
16
+ // 超过此大小的工具结果自动触发分拆(字符数,默认 100K tokens ≈ 200K chars)
17
+ const AUTO_SPLIT_THRESHOLD = 50000;
18
+
19
+ class DataSplitterPlugin extends Plugin {
20
+ constructor(config = {}) {
21
+ super();
22
+ this.name = 'data-splitter';
23
+ this.version = '1.0.0';
24
+ this.description = '大数据自动分拆处理:读取大文件或抓取网页时,自动分拆为多个子Agent并行处理';
25
+ this.priority = 5;
26
+
27
+ this._framework = null;
28
+ this._splitter = null;
29
+
30
+ // 配置
31
+ this.config = {
32
+ autoSplitThreshold: config.autoSplitThreshold || AUTO_SPLIT_THRESHOLD,
33
+ chunkSize: config.chunkSize || 60000,
34
+ maxConcurrent: config.maxConcurrent || 3,
35
+ ...config,
36
+ };
37
+ }
38
+
39
+ install(framework) {
40
+ this._framework = framework;
41
+ this._splitter = new DataSplitter(framework, {
42
+ chunkSize: this.config.chunkSize,
43
+ safeThreshold: this.config.autoSplitThreshold,
44
+ maxConcurrent: this.config.maxConcurrent,
45
+ });
46
+
47
+ return this;
48
+ }
49
+
50
+ start(framework) {
51
+ // 注册大数据处理工具
52
+ this._registerSplitTools();
53
+
54
+ // 注册系统提示词
55
+ this.registerPromptPart('data-splitter-rules', 85, () => this._getPromptRules());
56
+
57
+ // 注册工具结果监听器(自动检测大结果)
58
+ this._registerAutoSplitHook();
59
+
60
+ logger.info('[DataSplitterPlugin] 已启动,阈值:', this.config.autoSplitThreshold);
61
+ }
62
+
63
+ /**
64
+ * 注册分拆工具
65
+ * @private
66
+ */
67
+ _registerSplitTools() {
68
+ const framework = this._framework;
69
+
70
+ // ─── 工具1: split_and_process — AI 主动调用分拆 ───
71
+ framework.registerTool({
72
+ name: 'split_and_process',
73
+ description: `将大文本内容按大小分块,创建多个子 Agent 并行处理每块内容,最后自动汇总结果。
74
+ 当你读取文件或抓取网页返回的内容超过 10 万字符时,应该使用此工具来分拆处理。
75
+ 它会将内容拆成多块,每块由一个子 Agent 独立处理,最后给你一个汇总。`,
76
+ inputSchema: z.object({
77
+ content: z.string().describe('要分拆处理的大文本内容(如果超过 100K 字符建议使用此工具)'),
78
+ taskDescription: z.string().describe('每个子 Agent 要执行的任务描述,例如"提取所有函数定义"、"总结内容要点"等'),
79
+ chunkSize: z.number().optional().default(60000).describe('每块最大字符数,默认 60000'),
80
+ maxConcurrent: z.number().optional().default(3).describe('最大并行子 Agent 数,默认 3'),
81
+ }),
82
+ execute: async (args, ctx) => {
83
+ const { content, taskDescription, chunkSize, maxConcurrent } = args;
84
+ const splitter = this._getSplitter(ctx);
85
+
86
+ if (!content || content.length === 0) {
87
+ return { success: false, error: '内容为空,无需处理' };
88
+ }
89
+
90
+ const stats = splitter.getContentStats(content);
91
+ if (!splitter.needsSplit(content)) {
92
+ // 内容较小,直接返回
93
+ return {
94
+ success: true,
95
+ data: content,
96
+ metadata: {
97
+ needsSplit: false,
98
+ stats,
99
+ message: '内容大小在安全范围内,无需分拆处理。可直接使用原始内容。'
100
+ }
101
+ };
102
+ }
103
+
104
+ // 生成上下文中的 sessionId 用于取消信号
105
+ const sessionCtx = ctx?.getCurrentSessionContext?.();
106
+ const signal = sessionCtx?.abortSignal;
107
+
108
+ logger.info(
109
+ `[split_and_process] 开始分拆: ${stats.chars} 字符, ` +
110
+ `${stats.chunks} 块, 任务="${taskDescription?.slice(0, 40)}..."`
111
+ );
112
+
113
+ const startTime = Date.now();
114
+ const chunks = splitter.splitContent(content, chunkSize);
115
+ const result = await splitter.dispatchToSubAgents({
116
+ chunks,
117
+ taskDescription,
118
+ maxConcurrent: maxConcurrent || this.config.maxConcurrent,
119
+ signal,
120
+ });
121
+ const duration = ((Date.now() - startTime) / 1000).toFixed(1);
122
+
123
+ return {
124
+ success: true,
125
+ data: result.summary,
126
+ metadata: {
127
+ needsSplit: true,
128
+ stats: { ...stats, actualChunks: chunks.length },
129
+ totalChunks: chunks.length,
130
+ successfulChunks: result.results.filter((r) => r.success).length,
131
+ failedChunks: result.errors.length,
132
+ durationSec: parseFloat(duration),
133
+ },
134
+ };
135
+ },
136
+ });
137
+
138
+ // ─── 工具2: get_content_preview — 获取大内容预览信息 ───
139
+ framework.registerTool({
140
+ name: 'get_content_preview',
141
+ description: `获取大文本内容的预览信息(前 20 行 + 统计),判断是否需要分拆处理。
142
+ 适合在读取大文件或抓取网页后用于检查内容大小。`,
143
+ inputSchema: z.object({
144
+ content: z.string().describe('要预览的大文本内容'),
145
+ previewLines: z.number().optional().default(20).describe('预览前 N 行,默认 20'),
146
+ }),
147
+ execute: async (args) => {
148
+ const { content, previewLines = 20 } = args;
149
+ const splitter = this._getSplitter();
150
+
151
+ const stats = splitter.getContentStats(content);
152
+ const lines = content.split('\n');
153
+ const preview = lines.slice(0, previewLines).join('\n');
154
+
155
+ return {
156
+ success: true,
157
+ data: preview || '(空内容)',
158
+ metadata: {
159
+ stats,
160
+ needsSplit: splitter.needsSplit(content),
161
+ totalLines: lines.length,
162
+ suggestion: splitter.needsSplit(content)
163
+ ? `内容较大 (${stats.chars} 字符, 约 ${stats.estimatedTokens} tokens),建议使用 split_and_process 分拆处理`
164
+ : '内容大小在安全范围内',
165
+ }
166
+ };
167
+ },
168
+ });
169
+ }
170
+
171
+ /**
172
+ * 获取系统提示词
173
+ * @private
174
+ */
175
+ _getPromptRules() {
176
+ return `## 大数据处理能力
177
+
178
+ 你具备自动处理大文件和大网页的能力:
179
+
180
+ 1. **内容预览**:当工具返回的内容很大时,先用 \`get_content_preview\` 查看统计信息
181
+ 2. **分拆处理**:如果内容超过 10 万字符,用 \`split_and_process\` 将内容分块,每块交给独立的子 Agent 并行处理
182
+ 3. **自动汇总**:\`split_and_process\` 会自动汇总所有子 Agent 的处理结果,你只需基于汇总结果回答即可
183
+ 4. **性能提示**:分拆处理会并行运行多个子 Agent,处理大文件时效率很高
184
+
185
+ ### 使用建议
186
+ - 读取大文件(> 100KB)时:先读取,如果内容太大,用 \`split_and_process\` 分拆分析
187
+ - 抓取网页时:如果页面内容过多,用 \`split_and_process\` 分拆提取
188
+ - 分拆时指定清晰的任务描述,例如"提取所有关键代码函数"、"总结每段内容要点"`;
189
+ }
190
+
191
+ /**
192
+ * 注册自动分拆钩子
193
+ * 监听 tool:result 事件,如果结果太大自动触发分拆
194
+ * @private
195
+ */
196
+ _registerAutoSplitHook() {
197
+ const framework = this._framework;
198
+
199
+ // 监听工具结果,检测大数据
200
+ this._toolResultHandler = (data) => {
201
+ const { name, result } = data;
202
+
203
+ // 跳过已经标记为分拆结果的工具
204
+ if (name === 'split_and_process') return;
205
+
206
+ // 检查工具返回的内容是否过大(优先使用 data 字段)
207
+ let checkContent = '';
208
+ if (result && typeof result === 'object' && result.data) {
209
+ checkContent = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
210
+ } else {
211
+ checkContent = typeof result === 'string' ? result : JSON.stringify(result);
212
+ }
213
+ if (!checkContent || checkContent.length < this.config.autoSplitThreshold) return;
214
+
215
+ // 不阻塞执行,只在日志中记录建议
216
+ logger.info(
217
+ `[DataSplitter] 检测到大数据工具结果: ${name}, ` +
218
+ `${checkContent.length} 字符, 建议使用 split_and_process 分拆处理`
219
+ );
220
+ };
221
+
222
+ framework.on('tool:result', this._toolResultHandler);
223
+ }
224
+
225
+ /**
226
+ * 获取 DataSplitter 实例
227
+ * @private
228
+ */
229
+ _getSplitter(ctx) {
230
+ return this._splitter || new DataSplitter(this._framework, this.config);
231
+ }
232
+
233
+ reload(framework) {
234
+ // 清理旧监听器
235
+ if (this._toolResultHandler) {
236
+ framework.off('tool:result', this._toolResultHandler);
237
+ }
238
+
239
+ this._framework = framework;
240
+ this._splitter = new DataSplitter(framework, this.config);
241
+
242
+ // 重新注册
243
+ if (framework._mainAgent) {
244
+ this.start(framework);
245
+ }
246
+
247
+ // 请求刷新 mainAgent 的 system prompt
248
+ if (framework._mainAgent) {
249
+ framework._mainAgent._refreshContext();
250
+ }
251
+ }
252
+
253
+ uninstall(framework) {
254
+ if (this._toolResultHandler) {
255
+ framework.off('tool:result', this._toolResultHandler);
256
+ }
257
+ }
258
+ }
259
+
260
+ /**
261
+ * 自动检测工具结果是否过大,并透明处理
262
+ * 在 agent-chat 中调用
263
+ *
264
+ * @param {string} toolName - 工具名称
265
+ * @param {*} result - 工具返回结果
266
+ * @param {Object} framework - Framework 实例
267
+ * @returns {Promise<{ wasSplit: boolean, result: *, splitterResult?: Object }>}
268
+ */
269
+ async function autoSplitToolResult(toolName, result, framework) {
270
+ // 跳过某些工具
271
+ const skipTools = ['split_and_process', 'get_content_preview'];
272
+ if (skipTools.includes(toolName)) return { wasSplit: false, result };
273
+
274
+ // 无 framework 时无法创建子 Agent,跳过
275
+ if (!framework || typeof framework.createSubAgent !== 'function') {
276
+ return { wasSplit: false, result };
277
+ }
278
+
279
+ // 提取用于判断大小的内容
280
+ // 优先使用 result.data(统一格式),再回退到整个结果字符串
281
+ let checkContent = '';
282
+ if (typeof result === 'object' && result !== null && result.data) {
283
+ checkContent = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
284
+ } else {
285
+ checkContent = typeof result === 'string' ? result : JSON.stringify(result);
286
+ }
287
+ if (!checkContent || checkContent.length < AUTO_SPLIT_THRESHOLD) {
288
+ return { wasSplit: false, result };
289
+ }
290
+
291
+ const splitter = new DataSplitter(framework);
292
+ const taskDescription = `从以下内容中提取关键信息(代码、配置、数据等),以结构化方式输出。`;
293
+
294
+ logger.info(
295
+ `[DataSplitter] 自动分拆工具 "${toolName}": ${checkContent.length} 字符`
296
+ );
297
+
298
+ try {
299
+ const chunks = splitter.splitContent(checkContent);
300
+ const splitResult = await splitter.dispatchToSubAgents({
301
+ chunks,
302
+ taskDescription,
303
+ maxConcurrent: 3,
304
+ });
305
+
306
+ return {
307
+ wasSplit: true,
308
+ // 返回原始结果 + 分拆汇总,AI 可以同时看到两者
309
+ result: {
310
+ _autoSplit: true,
311
+ _originalSize: checkContent.length,
312
+ _summary: splitResult.summary,
313
+ _originalResult: result, // 保留原始结果供参考
314
+ },
315
+ splitterResult: splitResult,
316
+ };
317
+ } catch (err) {
318
+ logger.warn(`[DataSplitter] 自动分拆失败: ${err.message}`);
319
+ return { wasSplit: false, result };
320
+ }
321
+ }
322
+
323
+ module.exports = { DataSplitterPlugin, autoSplitToolResult };
@@ -364,7 +364,18 @@ async function bootstrapDefaults(framework, config = {}) {
364
364
  }
365
365
  }
366
366
 
367
- // 6. 自动加载 plugins/ 目录下的所有插件
367
+ // 6. 大数据分拆插件
368
+ if (shouldLoad('data-splitter')) {
369
+ const { DataSplitterPlugin } = require('./data-splitter-plugin')
370
+ await framework.loadPlugin(new DataSplitterPlugin({
371
+ autoSplitThreshold: agentConfig.dataSplitter?.autoSplitThreshold || 50000,
372
+ chunkSize: agentConfig.dataSplitter?.chunkSize || 60000,
373
+ maxConcurrent: agentConfig.dataSplitter?.maxConcurrent || 3,
374
+ }))
375
+ framework._debug&&bootstrapLog.debug(' DataSplitter Plugin loaded')
376
+ }
377
+
378
+ // 7. 自动加载 plugins/ 目录下的所有插件
368
379
  await loadCustomPlugins(framework, agentConfig)
369
380
 
370
381
  framework._debug&&bootstrapLog.debug(' All plugins loaded')
@@ -208,7 +208,7 @@ class ExtensionExecutorPlugin extends Plugin {
208
208
  source: 'extension'
209
209
  });
210
210
 
211
- return { success: true, result };
211
+ return { success: true, data: result };
212
212
  } catch (err) {
213
213
  log.error(` Tool '${tool}' failed:`, err.message);
214
214
 
@@ -239,7 +239,7 @@ class ExtensionExecutorPlugin extends Plugin {
239
239
  tools: ext.tools.map((t) => ({ name: t.name, description: t.description })),
240
240
  });
241
241
  }
242
- return { success: true, extensions };
242
+ return { success: true, data: extensions };
243
243
  },
244
244
  });
245
245
 
@@ -76,7 +76,7 @@ class FileSystemPlugin extends Plugin {
76
76
  }
77
77
  }
78
78
  readDir(dirPath)
79
- return { success: true, dirPath, items: items.slice(0, 100), total: items.length }
79
+ return { success: true, data: items.slice(0, 100), metadata: { dirPath, total: items.length } }
80
80
  } catch (error) {
81
81
  return { success: false, error: error.message }
82
82
  }
@@ -95,7 +95,7 @@ class FileSystemPlugin extends Plugin {
95
95
  const dirPath = args.path || args.dirPath
96
96
  try {
97
97
  fs.mkdirSync(dirPath, { recursive: true })
98
- return { success: true, message: `目录已创建: ${dirPath}` }
98
+ return { success: true, data: `目录已创建: ${dirPath}` }
99
99
  } catch (error) {
100
100
  return { success: false, error: error.message }
101
101
  }
@@ -140,10 +140,12 @@ class FileSystemPlugin extends Plugin {
140
140
  }
141
141
  return {
142
142
  success: true,
143
- filePath: pathCheck.resolved,
144
- content,
145
- size: stat.size,
146
- lines: lines ? null : content.split('\n').length
143
+ data: content,
144
+ metadata: {
145
+ filePath: pathCheck.resolved,
146
+ size: stat.size,
147
+ lines: lines ? null : content.split('\n').length
148
+ }
147
149
  }
148
150
  } catch (error) {
149
151
  return { success: false, error: error.message }
@@ -210,10 +212,12 @@ class FileSystemPlugin extends Plugin {
210
212
  if (err) reject(err)
211
213
  else resolve({
212
214
  success: true,
213
- message: `文件已${mode === 'append' ? '追加' : '写入'}: ${pathCheck.resolved}`,
214
- filePath: pathCheck.resolved,
215
- size: content.length,
216
- mode
215
+ data: `文件已${mode === 'append' ? '追加' : '写入'}: ${pathCheck.resolved}`,
216
+ metadata: {
217
+ filePath: pathCheck.resolved,
218
+ size: content.length,
219
+ mode
220
+ }
217
221
  })
218
222
  })
219
223
  })
@@ -254,9 +258,11 @@ class FileSystemPlugin extends Plugin {
254
258
  fs.appendFileSync(pathCheck.resolved, content, 'utf8')
255
259
  return {
256
260
  success: true,
257
- message: `已追加内容到: ${pathCheck.resolved}`,
258
- filePath: pathCheck.resolved,
259
- appendedSize: content.length
261
+ data: `已追加内容到: ${pathCheck.resolved}`,
262
+ metadata: {
263
+ filePath: pathCheck.resolved,
264
+ appendedSize: content.length
265
+ }
260
266
  }
261
267
  } catch (error) {
262
268
  return { success: false, error: error.message }
@@ -297,7 +303,7 @@ class FileSystemPlugin extends Plugin {
297
303
  } else {
298
304
  fs.unlinkSync(pathCheck.resolved)
299
305
  }
300
- return { success: true, message: `已删除: ${pathCheck.resolved}` }
306
+ return { success: true, data: `已删除: ${pathCheck.resolved}` }
301
307
  } catch (error) {
302
308
  return { success: false, error: error.message }
303
309
  }
@@ -660,13 +666,15 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
660
666
 
661
667
  return {
662
668
  success: true,
663
- pattern,
664
- results: results.slice(0, maxResults),
665
- total: results.length,
666
- stats: {
667
- filesWithMatches,
668
- totalMatches,
669
- searchPath: targetFile || dirPath
669
+ data: results.slice(0, maxResults),
670
+ metadata: {
671
+ pattern,
672
+ total: results.length,
673
+ stats: {
674
+ filesWithMatches,
675
+ totalMatches,
676
+ searchPath: targetFile || dirPath
677
+ }
670
678
  }
671
679
  }
672
680
  } catch (error) {
@@ -817,19 +825,23 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
817
825
  if (error) {
818
826
  resolve({
819
827
  success: false,
820
- command,
821
828
  error: error.message,
822
- stderr: stderr.substring(0, 2000),
823
- stdout: stdout.substring(0, 2000),
824
- duration
829
+ metadata: {
830
+ command,
831
+ stderr: stderr.substring(0, 2000),
832
+ stdout: stdout.substring(0, 2000),
833
+ duration
834
+ }
825
835
  })
826
836
  } else {
827
837
  resolve({
828
838
  success: true,
829
- command,
830
- stdout: stdout.substring(0, 10000),
831
- stderr: stderr.substring(0, 1000),
832
- duration
839
+ data: stdout.substring(0, 10000),
840
+ metadata: {
841
+ command,
842
+ stderr: stderr.substring(0, 1000),
843
+ duration
844
+ }
833
845
  })
834
846
  }
835
847
  }
@@ -848,16 +860,18 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
848
860
  const beijingTime = new Date(now.getTime() + (8 * 60 * 60 * 1000))
849
861
  return {
850
862
  success: true,
851
- beijingTime: beijingTime.toISOString().replace('T', ' ').substring(0, 19),
852
- timestamp: now.getTime(),
853
- timezone: 'Asia/Shanghai (UTC+8)',
854
- formatted: {
855
- year: beijingTime.getUTCFullYear(),
856
- month: String(beijingTime.getUTCMonth() + 1).padStart(2, '0'),
857
- day: String(beijingTime.getUTCDate()).padStart(2, '0'),
858
- hour: String(beijingTime.getUTCHours()).padStart(2, '0'),
859
- minute: String(beijingTime.getUTCMinutes()).padStart(2, '0'),
860
- second: String(beijingTime.getUTCMinutes()).padStart(2, '0')
863
+ data: beijingTime.toISOString().replace('T', ' ').substring(0, 19),
864
+ metadata: {
865
+ timestamp: now.getTime(),
866
+ timezone: 'Asia/Shanghai (UTC+8)',
867
+ formatted: {
868
+ year: beijingTime.getUTCFullYear(),
869
+ month: String(beijingTime.getUTCMonth() + 1).padStart(2, '0'),
870
+ day: String(beijingTime.getUTCDate()).padStart(2, '0'),
871
+ hour: String(beijingTime.getUTCHours()).padStart(2, '0'),
872
+ minute: String(beijingTime.getUTCMinutes()).padStart(2, '0'),
873
+ second: String(beijingTime.getUTCMinutes()).padStart(2, '0')
874
+ }
861
875
  }
862
876
  }
863
877
  }
@@ -983,21 +997,25 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
983
997
  : data
984
998
  return {
985
999
  success: true,
986
- status: response.status,
987
- statusText: response.statusText,
988
- headers: Object.fromEntries(response.headers.entries()),
989
- usedProxy: proxy,
990
- body: truncatedData,
991
- originalLength: typeof data === 'string' ? data.length : null,
992
- truncated: maxLength && typeof data === 'string' && data.length > maxLength
1000
+ data: truncatedData,
1001
+ metadata: {
1002
+ status: response.status,
1003
+ statusText: response.statusText,
1004
+ headers: Object.fromEntries(response.headers.entries()),
1005
+ usedProxy: proxy,
1006
+ originalLength: typeof data === 'string' ? data.length : null,
1007
+ truncated: maxLength && typeof data === 'string' && data.length > maxLength
1008
+ }
993
1009
  }
994
1010
  } catch (error) {
995
1011
  return {
996
1012
  success: false,
997
1013
  error: error.message,
998
- url,
999
- method,
1000
- hint: '如果访问失败,可尝试设置 proxy: true'
1014
+ metadata: {
1015
+ url,
1016
+ method,
1017
+ hint: '如果访问失败,可尝试设置 proxy: true'
1018
+ }
1001
1019
  }
1002
1020
  }
1003
1021
  }
@@ -1026,7 +1044,7 @@ edits 数组可以包含多个不重叠的替换操作,每个操作通过 oldT
1026
1044
  sessionId,
1027
1045
  timestamp: new Date().toISOString()
1028
1046
  })
1029
- return { success: true, message: '通知已发送' }
1047
+ return { success: true, data: '通知已发送' }
1030
1048
  } catch (error) {
1031
1049
  return { success: false, error: error.message }
1032
1050
  }