foliko 1.0.67 → 1.0.69
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/.claude/settings.local.json +148 -145
- package/examples/basic.js +110 -110
- package/examples/mcp-example.js +53 -53
- package/examples/skill-example.js +49 -49
- package/examples/test-mcp.js +79 -79
- package/examples/test-reload.js +61 -61
- package/news-20260329-1774794949179.html +39 -0
- package/news-20260329-1774794970785.html +39 -0
- package/news-20260329-1774797491928.html +39 -0
- package/package.json +1 -1
- package/plugins/ambient-agent-plugin.js +333 -22
- package/plugins/email.js +106 -20
- package/plugins/feishu-plugin.js +12 -2
- package/plugins/file-system-plugin.js +76 -30
- package/plugins/python-executor-plugin.js +41 -8
- package/plugins/scheduler-plugin.js +14 -11
- package/plugins/telegram-plugin.js +51 -23
- package/plugins/web-plugin.js +8 -6
- package/plugins/weixin-plugin.js +12 -2
- package/skills/ambient-agent/SKILL.md +84 -14
- package/skills/workflow-guide/SKILL.md +214 -2
- package/skills/workflow-troubleshooting/DEBUGGING.md +182 -0
- package/skills/workflow-troubleshooting/SKILL.md +314 -0
- package/src/capabilities/workflow-engine.js +367 -22
- package/src/core/agent-chat.js +106 -14
- package/src/core/framework.js +81 -1
- package/src/executors/executor-base.js +58 -58
- package/test-server.js +0 -25
- package/test.txt +0 -3
package/plugins/email.js
CHANGED
|
@@ -29,6 +29,9 @@ class EmailPlugin extends Plugin {
|
|
|
29
29
|
this._lastSeenUid = null
|
|
30
30
|
this._watchConfig = null
|
|
31
31
|
this._watchEnabled = false
|
|
32
|
+
this._checkInProgress = false // 防止并发检查
|
|
33
|
+
this._recentlyEmailed = new Set() // 去重:最近已处理的 messageId(短TTL,用于快速去重)
|
|
34
|
+
this._processedEmails = new Set() // 已处理(已回复)的邮件,不会重复处理(长TTL)
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
install(framework) {
|
|
@@ -64,12 +67,36 @@ class EmailPlugin extends Plugin {
|
|
|
64
67
|
* 发送邮件事件
|
|
65
68
|
*/
|
|
66
69
|
_emitEmailReceived(email) {
|
|
67
|
-
if (this._framework)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
if (!this._framework) return
|
|
71
|
+
// 去重:如果最近已处理过这个 messageId,跳过
|
|
72
|
+
const msgId = email.messageId || email.uid
|
|
73
|
+
//console.log(`[Email] _emitEmailReceived: msgId=${msgId}, messageId=${email.messageId}, uid=${email.uid}`)
|
|
74
|
+
|
|
75
|
+
// 优先检查是否已处理过(长TTL,防止服务器标记失败导致重复处理)
|
|
76
|
+
if (msgId && this._processedEmails.has(msgId)) {
|
|
77
|
+
console.log(`[Email] 跳过已处理的邮件: ${email.subject} (${msgId})`)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 快速去重(短TTL,防止同一批次重复触发)
|
|
82
|
+
if (msgId && this._recentlyEmailed.has(msgId)) {
|
|
83
|
+
//console.log(`[Email] 跳过重复邮件: ${email.subject} (${msgId})`)
|
|
84
|
+
return
|
|
72
85
|
}
|
|
86
|
+
|
|
87
|
+
// 标记为已处理
|
|
88
|
+
if (msgId) {
|
|
89
|
+
this._recentlyEmailed.add(msgId)
|
|
90
|
+
// 5分钟后从 Set 中移除,允许重新处理(适用于邮件被删除后重新收到的极端情况)
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
this._recentlyEmailed.delete(msgId)
|
|
93
|
+
}, 5 * 60 * 1000)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this._framework.emit('email:received', {
|
|
97
|
+
email,
|
|
98
|
+
timestamp: new Date()
|
|
99
|
+
})
|
|
73
100
|
}
|
|
74
101
|
|
|
75
102
|
_registerTools() {
|
|
@@ -211,9 +238,12 @@ class EmailPlugin extends Plugin {
|
|
|
211
238
|
subject: z.string().describe('原始邮件主题'),
|
|
212
239
|
body: z.string().describe('原始邮件内容'),
|
|
213
240
|
from: z.string().optional().describe('发件人邮箱地址(可选)'),
|
|
214
|
-
prompt: z.string().optional().describe('自定义提示词,用于指导AI生成回复内容')
|
|
241
|
+
prompt: z.string().optional().describe('自定义提示词,用于指导AI生成回复内容'),
|
|
242
|
+
timeout: z.number().optional().describe('AI生成回复超时时间(毫秒),默认60000'),
|
|
243
|
+
messageId: z.string().optional().describe('原始邮件的Message-ID,用于在邮件客户端中创建回复线程')
|
|
215
244
|
}),
|
|
216
245
|
execute: async (args) => {
|
|
246
|
+
console.log('[Email] Auto-reply tool invoked with args:', args)
|
|
217
247
|
return this._handleAutoReply(args)
|
|
218
248
|
}
|
|
219
249
|
})
|
|
@@ -223,18 +253,25 @@ class EmailPlugin extends Plugin {
|
|
|
223
253
|
* 处理自动回复
|
|
224
254
|
*/
|
|
225
255
|
async _handleAutoReply(args) {
|
|
226
|
-
let { to, subject, body, from, _event, prompt } = args
|
|
256
|
+
let { to, subject, body, from, _event, prompt, messageId, inReplyTo } = args
|
|
227
257
|
|
|
228
258
|
// 如果没有直接参数,尝试从 _event 提取
|
|
229
|
-
// _event 格式: {
|
|
259
|
+
// _event 格式: { event: 'email:received', data: { uid, messageId, from, to, subject, text, ... }, timestamp }
|
|
230
260
|
if (!to && !subject && !body && _event) {
|
|
231
|
-
const email = _event.
|
|
261
|
+
const email = _event.data || _event.email || {}
|
|
232
262
|
from = email.from?.text || email.from || ''
|
|
233
263
|
to = email.to?.text || email.to || ''
|
|
234
264
|
subject = email.subject || ''
|
|
235
265
|
body = email.text || email.body || ''
|
|
266
|
+
// 从事件中提取 messageId 作为 inReplyTo
|
|
267
|
+
if (!messageId) {
|
|
268
|
+
messageId = email.messageId || email.uid
|
|
269
|
+
}
|
|
236
270
|
}
|
|
237
271
|
|
|
272
|
+
// 优先使用传入的 inReplyTo,否则用 messageId
|
|
273
|
+
const replyTo = inReplyTo || messageId
|
|
274
|
+
|
|
238
275
|
// 检查必要参数
|
|
239
276
|
if (!from && !to) {
|
|
240
277
|
return { success: false, error: '缺少收件人地址' }
|
|
@@ -244,11 +281,12 @@ class EmailPlugin extends Plugin {
|
|
|
244
281
|
}
|
|
245
282
|
|
|
246
283
|
try {
|
|
247
|
-
//
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
284
|
+
// 使用子Agent生成回复
|
|
285
|
+
const replyAgent = this._framework.createSubAgent({
|
|
286
|
+
name: 'email_replier',
|
|
287
|
+
role: '邮件自动回复助手,专注于生成专业、礼貌、简洁的邮件回复',
|
|
288
|
+
parentTools: []
|
|
289
|
+
})
|
|
252
290
|
|
|
253
291
|
// 构建提示让 LLM 生成回复
|
|
254
292
|
const finalPrompt = prompt || `你是一封邮件自动回复助手。请根据以下邮件内容,生成一封专业的回复邮件。
|
|
@@ -265,14 +303,14 @@ ${body}
|
|
|
265
303
|
3. 只输出邮件正文内容,不要额外解释
|
|
266
304
|
4. 回复语言应与原邮件一致(如果原邮件是中文,则用中文回复)`
|
|
267
305
|
|
|
268
|
-
// 等待 Agent
|
|
306
|
+
// 等待 Agent 生成回复(带超时保护,默认60秒)
|
|
307
|
+
const timeoutMs = args.timeout || 60000
|
|
269
308
|
const timeoutPromise = new Promise((_, reject) => {
|
|
270
|
-
setTimeout(() => reject(new Error(
|
|
309
|
+
setTimeout(() => reject(new Error(`AI回复生成超时(${timeoutMs/1000}秒)`)), timeoutMs)
|
|
271
310
|
})
|
|
272
311
|
|
|
273
|
-
const replyPromise =
|
|
312
|
+
const replyPromise = replyAgent.chat(finalPrompt, { maxSteps: 3 })
|
|
274
313
|
const replyResult = await Promise.race([replyPromise, timeoutPromise])
|
|
275
|
-
|
|
276
314
|
// 提取回复内容
|
|
277
315
|
let replyContent = ''
|
|
278
316
|
if (typeof replyResult === 'string') {
|
|
@@ -296,10 +334,33 @@ ${body}
|
|
|
296
334
|
const sendResult = await this._sendEmail({
|
|
297
335
|
to: from || to,
|
|
298
336
|
subject: `Re: ${subject}`,
|
|
299
|
-
body: replyContent
|
|
337
|
+
body: replyContent,
|
|
338
|
+
inReplyTo: replyTo,
|
|
339
|
+
references: replyTo ? [replyTo] : undefined
|
|
300
340
|
})
|
|
301
341
|
|
|
302
342
|
if (sendResult.success) {
|
|
343
|
+
// 发送成功后,标记为已处理(本地记录,防止服务器标记失败导致重复处理)
|
|
344
|
+
const msgId = messageId || _event?.data?.messageId || _event?.email?.messageId || uid
|
|
345
|
+
if (msgId) {
|
|
346
|
+
this._processedEmails.add(msgId)
|
|
347
|
+
console.log(`[Email] 邮件已处理: ${msgId}`)
|
|
348
|
+
// 24小时后从已处理列表中移除(防止永久记住)
|
|
349
|
+
setTimeout(() => {
|
|
350
|
+
this._processedEmails.delete(msgId)
|
|
351
|
+
}, 24 * 60 * 60 * 1000)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 自动标记原邮件为已读(服务器端)
|
|
355
|
+
const uid = _event?.data?.uid || _event?.email?.uid
|
|
356
|
+
if (uid) {
|
|
357
|
+
try {
|
|
358
|
+
await this._markAsRead({ messageId: String(uid) })
|
|
359
|
+
} catch (err) {
|
|
360
|
+
console.warn(`[Email] 自动标记已读失败: ${err.message}`)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
303
364
|
return {
|
|
304
365
|
success: true,
|
|
305
366
|
message: `自动回复已发送至 ${from || to}`,
|
|
@@ -473,6 +534,17 @@ ${body}
|
|
|
473
534
|
async _checkNewEmails() {
|
|
474
535
|
if (!this._watchConfig) return
|
|
475
536
|
|
|
537
|
+
const callId = `${Date.now()}_${Math.random().toString(36).substr(2, 5)}`
|
|
538
|
+
console.log(`[Email] [${callId}] _checkNewEmails called, _checkInProgress=${this._checkInProgress}`)
|
|
539
|
+
|
|
540
|
+
// 防止并发检查
|
|
541
|
+
if (this._checkInProgress) {
|
|
542
|
+
console.log(`[Email] [${callId}] Skipping - already in progress`)
|
|
543
|
+
return
|
|
544
|
+
}
|
|
545
|
+
this._checkInProgress = true
|
|
546
|
+
console.log(`[Email] [${callId}] Started, _checkInProgress=${this._checkInProgress}`)
|
|
547
|
+
|
|
476
548
|
const Imap = require('imap-mkl')
|
|
477
549
|
const { simpleParser } = require('mailparser')
|
|
478
550
|
|
|
@@ -495,6 +567,8 @@ ${body}
|
|
|
495
567
|
|
|
496
568
|
const cleanup = () => {
|
|
497
569
|
try { imap.end() } catch (e) {}
|
|
570
|
+
this._checkInProgress = false
|
|
571
|
+
console.log(`[Email] ${Date.now()} cleanup called, _checkInProgress=false`)
|
|
498
572
|
}
|
|
499
573
|
|
|
500
574
|
imap.on('ready', () => {
|
|
@@ -525,6 +599,9 @@ ${body}
|
|
|
525
599
|
const f = imap.fetch(latestUid, { bodies: '' })
|
|
526
600
|
|
|
527
601
|
f.on('message', (msg) => {
|
|
602
|
+
let msgCount = 0
|
|
603
|
+
msgCount++
|
|
604
|
+
console.log(`[Email] f.on('message') called count=${msgCount}, uid=${latestUid}`)
|
|
528
605
|
let email = {}
|
|
529
606
|
let bodyParsed = false
|
|
530
607
|
|
|
@@ -532,6 +609,7 @@ ${body}
|
|
|
532
609
|
simpleParser(stream).then(mail => {
|
|
533
610
|
email = {
|
|
534
611
|
uid: mail.uid || latestUid,
|
|
612
|
+
messageId: mail.messageId,
|
|
535
613
|
subject: mail.subject,
|
|
536
614
|
from: mail.from?.text || '',
|
|
537
615
|
to: mail.to?.text || '',
|
|
@@ -551,7 +629,13 @@ ${body}
|
|
|
551
629
|
})
|
|
552
630
|
|
|
553
631
|
msg.on('end', () => {
|
|
632
|
+
console.log(`[Email] msg.on('end') called for uid=${latestUid}`)
|
|
633
|
+
let checkCount = 0
|
|
554
634
|
const checkDone = () => {
|
|
635
|
+
checkCount++
|
|
636
|
+
if (checkCount > 1) {
|
|
637
|
+
console.log(`[Email] checkDone called ${checkCount} times, bodyParsed=${bodyParsed}`)
|
|
638
|
+
}
|
|
555
639
|
if (bodyParsed) {
|
|
556
640
|
// 更新最后看到的 UID
|
|
557
641
|
this._lastSeenUid = latestUid
|
|
@@ -620,7 +704,9 @@ ${body}
|
|
|
620
704
|
text: args.isHtml ? undefined : args.body,
|
|
621
705
|
html: args.isHtml ? args.body : undefined,
|
|
622
706
|
cc: args.cc,
|
|
623
|
-
bcc: args.bcc
|
|
707
|
+
bcc: args.bcc,
|
|
708
|
+
inReplyTo: args.inReplyTo,
|
|
709
|
+
references: args.references
|
|
624
710
|
}
|
|
625
711
|
|
|
626
712
|
// 处理附件
|
package/plugins/feishu-plugin.js
CHANGED
|
@@ -288,8 +288,18 @@ class FeishuPlugin extends Plugin {
|
|
|
288
288
|
|
|
289
289
|
// 确定 openId
|
|
290
290
|
let openId = null
|
|
291
|
-
|
|
292
|
-
|
|
291
|
+
let effectiveSessionId = sessionId
|
|
292
|
+
|
|
293
|
+
// 如果没有 sessionId,尝试从执行上下文获取
|
|
294
|
+
if (!effectiveSessionId) {
|
|
295
|
+
const ctx = this._framework.getExecutionContext()
|
|
296
|
+
if (ctx?.sessionId) {
|
|
297
|
+
effectiveSessionId = ctx.sessionId
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (effectiveSessionId && effectiveSessionId.startsWith('feishu_')) {
|
|
302
|
+
openId = effectiveSessionId.replace('feishu_', '')
|
|
293
303
|
} else if (this._sessionPlugin) {
|
|
294
304
|
// 获取最近的 feishu 会话
|
|
295
305
|
const sessions = this._sessionPlugin.listSessions()
|
|
@@ -221,56 +221,102 @@ class FileSystemPlugin extends Plugin {
|
|
|
221
221
|
// 搜索文件
|
|
222
222
|
framework.registerTool({
|
|
223
223
|
name: 'search_file',
|
|
224
|
-
description: '
|
|
224
|
+
description: '在文件或目录中搜索文本',
|
|
225
225
|
inputSchema: z.object({
|
|
226
226
|
query: z.string().optional().describe('搜索关键词'),
|
|
227
|
-
pattern: z.string().optional().describe('搜索模式'),
|
|
227
|
+
pattern: z.string().optional().describe('搜索模式(支持正则)'),
|
|
228
228
|
searchText: z.string().optional().describe('搜索文本'),
|
|
229
229
|
path: z.string().optional().describe('搜索目录'),
|
|
230
230
|
dirPath: z.string().optional().describe('搜索目录(同path)'),
|
|
231
|
+
file: z.string().optional().describe('搜索指定文件'),
|
|
231
232
|
fileType: z.string().optional().describe('文件类型过滤'),
|
|
232
|
-
maxResults: z.number().optional().describe('最大结果数')
|
|
233
|
+
maxResults: z.number().optional().describe('最大结果数'),
|
|
234
|
+
contextLines: z.number().optional().describe('匹配行的上下文行数')
|
|
233
235
|
}),
|
|
234
236
|
execute: async (args, framework) => {
|
|
235
237
|
const pattern = args.query || args.pattern || args.searchText || ''
|
|
236
238
|
const dirPath = args.path || args.dirPath || '.'
|
|
239
|
+
const targetFile = args.file
|
|
237
240
|
const fileType = args.fileType
|
|
238
241
|
const maxResults = args.maxResults || 50
|
|
242
|
+
const contextLines = args.contextLines || 0
|
|
239
243
|
try {
|
|
240
244
|
const results = []
|
|
241
245
|
const regex = new RegExp(pattern, 'gi')
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
246
|
+
|
|
247
|
+
// 搜索单个文件
|
|
248
|
+
if (targetFile) {
|
|
249
|
+
const fullPath = path.resolve(targetFile)
|
|
250
|
+
if (!fs.existsSync(fullPath)) {
|
|
251
|
+
return { success: false, error: `文件不存在: ${targetFile}` }
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const content = fs.readFileSync(fullPath, 'utf8')
|
|
255
|
+
const lines = content.split('\n')
|
|
256
|
+
for (let i = 0; i < lines.length; i++) {
|
|
257
|
+
if (regex.test(lines[i])) {
|
|
258
|
+
let matchInfo = { file: targetFile, line: i + 1, content: lines[i].substring(0, 200) }
|
|
259
|
+
// 添加上下文行
|
|
260
|
+
if (contextLines > 0) {
|
|
261
|
+
const start = Math.max(0, i - contextLines)
|
|
262
|
+
const end = Math.min(lines.length, i + contextLines + 1)
|
|
263
|
+
matchInfo.context = lines.slice(start, end).map((l, idx) => ({
|
|
264
|
+
line: start + idx + 1,
|
|
265
|
+
content: l
|
|
266
|
+
}))
|
|
267
|
+
}
|
|
268
|
+
results.push(matchInfo)
|
|
269
|
+
if (results.length >= maxResults) break
|
|
270
|
+
}
|
|
252
271
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
272
|
+
regex.lastIndex = 0
|
|
273
|
+
} catch (e) {
|
|
274
|
+
return { success: false, error: `读取文件失败: ${e.message}` }
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
// 搜索目录
|
|
278
|
+
const searchDir = (currentPath, depth = 0) => {
|
|
279
|
+
if (depth > 5 || results.length >= maxResults) return
|
|
280
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true })
|
|
281
|
+
for (const entry of entries) {
|
|
282
|
+
if (results.length >= maxResults) break
|
|
283
|
+
const fullPath = path.join(currentPath, entry.name)
|
|
284
|
+
const relativePath = path.relative(process.cwd(), fullPath)
|
|
285
|
+
if (relativePath.includes('node_modules') || relativePath.includes('.git') ||
|
|
286
|
+
relativePath.includes('dist') || relativePath.includes('build')) {
|
|
287
|
+
continue
|
|
288
|
+
}
|
|
289
|
+
if (entry.isDirectory()) {
|
|
290
|
+
searchDir(fullPath, depth + 1)
|
|
291
|
+
} else if (entry.isFile()) {
|
|
292
|
+
if (fileType && !entry.name.endsWith(fileType)) continue
|
|
293
|
+
const ext = path.extname(entry.name)
|
|
294
|
+
if (['.jpg', '.png', '.gif', '.exe', '.dll', '.zip'].includes(ext)) continue
|
|
295
|
+
try {
|
|
296
|
+
const content = fs.readFileSync(fullPath, 'utf8')
|
|
297
|
+
const lines = content.split('\n')
|
|
298
|
+
for (let i = 0; i < lines.length; i++) {
|
|
299
|
+
if (regex.test(lines[i])) {
|
|
300
|
+
let matchInfo = { file: relativePath, line: i + 1, content: lines[i].substring(0, 200) }
|
|
301
|
+
if (contextLines > 0) {
|
|
302
|
+
const start = Math.max(0, i - contextLines)
|
|
303
|
+
const end = Math.min(lines.length, i + contextLines + 1)
|
|
304
|
+
matchInfo.context = lines.slice(start, end).map((l, idx) => ({
|
|
305
|
+
line: start + idx + 1,
|
|
306
|
+
content: l
|
|
307
|
+
}))
|
|
308
|
+
}
|
|
309
|
+
results.push(matchInfo)
|
|
310
|
+
if (results.length >= maxResults) break
|
|
311
|
+
}
|
|
266
312
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
313
|
+
regex.lastIndex = 0
|
|
314
|
+
} catch (e) { }
|
|
315
|
+
}
|
|
270
316
|
}
|
|
271
317
|
}
|
|
318
|
+
searchDir(dirPath)
|
|
272
319
|
}
|
|
273
|
-
searchDir(dirPath)
|
|
274
320
|
return { success: true, pattern, results: results.slice(0, maxResults), total: results.length }
|
|
275
321
|
} catch (error) {
|
|
276
322
|
return { success: false, error: error.message }
|
|
@@ -50,10 +50,11 @@ class PythonExecutorPlugin extends Plugin {
|
|
|
50
50
|
description: '执行 Python 代码,禁止传入无用的emoji',
|
|
51
51
|
inputSchema: z.object({
|
|
52
52
|
code: z.string().describe('Python 代码'),
|
|
53
|
-
timeout: z.number().optional().describe('超时时间(毫秒),默认 120000')
|
|
53
|
+
timeout: z.number().optional().describe('超时时间(毫秒),默认 120000'),
|
|
54
|
+
input_data: z.record(z.any()).optional().describe('输入数据对象,可在代码中通过 input_data.get("key") 访问')
|
|
54
55
|
}),
|
|
55
56
|
execute: async (args) => {
|
|
56
|
-
return this._executePython(args.code, args.timeout || this.config.timeout)
|
|
57
|
+
return this._executePython(args.code, args.timeout || this.config.timeout, args.input_data)
|
|
57
58
|
}
|
|
58
59
|
})
|
|
59
60
|
|
|
@@ -92,7 +93,7 @@ class PythonExecutorPlugin extends Plugin {
|
|
|
92
93
|
* 执行 Python 代码
|
|
93
94
|
* @private
|
|
94
95
|
*/
|
|
95
|
-
_executePython(code, timeout) {
|
|
96
|
+
_executePython(code, timeout, inputData) {
|
|
96
97
|
return new Promise((resolve) => {
|
|
97
98
|
const startTime = Date.now()
|
|
98
99
|
let output = ''
|
|
@@ -101,10 +102,20 @@ class PythonExecutorPlugin extends Plugin {
|
|
|
101
102
|
// 创建临时脚本文件
|
|
102
103
|
const tempFile = path.join(this._tempDir, `temp_${Date.now()}.py`)
|
|
103
104
|
|
|
105
|
+
// 注入 input_data 变量
|
|
106
|
+
let inputDataSetup = ''
|
|
107
|
+
if (inputData) {
|
|
108
|
+
const inputDataJson = JSON.stringify(inputData).replace(/'/g, "\\'")
|
|
109
|
+
inputDataSetup = `import json\ninput_data = json.loads('${inputDataJson}')\n`
|
|
110
|
+
} else {
|
|
111
|
+
inputDataSetup = `input_data = {}\n`
|
|
112
|
+
}
|
|
113
|
+
|
|
104
114
|
// 包装代码,添加错误处理
|
|
105
115
|
const wrappedCode = `
|
|
106
116
|
import sys
|
|
107
117
|
import traceback
|
|
118
|
+
${inputDataSetup}
|
|
108
119
|
try:
|
|
109
120
|
${code.split('\n').map(line => ' ' + line).join('\n')}
|
|
110
121
|
except SystemExit:
|
|
@@ -136,13 +147,26 @@ except Exception:
|
|
|
136
147
|
} catch (e) { }
|
|
137
148
|
|
|
138
149
|
const elapsed = Date.now() - startTime
|
|
150
|
+
|
|
151
|
+
// 限制返回数据大小,避免上下文超限(限制为 5000 字符)
|
|
152
|
+
const MAX_OUTPUT_LENGTH = 5000
|
|
153
|
+
let truncatedOutput = output
|
|
154
|
+
let truncated = false
|
|
155
|
+
if (output.length > MAX_OUTPUT_LENGTH) {
|
|
156
|
+
truncatedOutput = output.substring(0, MAX_OUTPUT_LENGTH) + `\n... [输出已截断,总长度 ${output.length} 字符]`
|
|
157
|
+
truncated = true
|
|
158
|
+
}
|
|
159
|
+
|
|
139
160
|
resolve({
|
|
140
161
|
success: code === 0,
|
|
141
162
|
exitCode: code,
|
|
142
|
-
stdout:
|
|
143
|
-
stderr: errorOutput
|
|
163
|
+
stdout: truncatedOutput,
|
|
164
|
+
stderr: errorOutput.length > MAX_OUTPUT_LENGTH
|
|
165
|
+
? errorOutput.substring(0, MAX_OUTPUT_LENGTH) + `\n... [错误输出已截断]`
|
|
166
|
+
: errorOutput,
|
|
144
167
|
elapsed,
|
|
145
|
-
timedOut: code === null
|
|
168
|
+
timedOut: code === null,
|
|
169
|
+
truncated
|
|
146
170
|
})
|
|
147
171
|
})
|
|
148
172
|
|
|
@@ -164,11 +188,20 @@ except Exception:
|
|
|
164
188
|
try {
|
|
165
189
|
fs.unlinkSync(tempFile)
|
|
166
190
|
} catch (e) { }
|
|
191
|
+
|
|
192
|
+
// 限制返回数据大小
|
|
193
|
+
const MAX_OUTPUT_LENGTH = 5000
|
|
194
|
+
const truncatedOutput = output.length > MAX_OUTPUT_LENGTH
|
|
195
|
+
? output.substring(0, MAX_OUTPUT_LENGTH) + `\n... [输出已截断,总长度 ${output.length} 字符]`
|
|
196
|
+
: output
|
|
197
|
+
|
|
167
198
|
resolve({
|
|
168
199
|
success: false,
|
|
169
200
|
error: `Python execution timed out after ${timeout}ms`,
|
|
170
|
-
stdout:
|
|
171
|
-
stderr: errorOutput
|
|
201
|
+
stdout: truncatedOutput,
|
|
202
|
+
stderr: errorOutput.length > MAX_OUTPUT_LENGTH
|
|
203
|
+
? errorOutput.substring(0, MAX_OUTPUT_LENGTH) + `\n... [错误输出已截断]`
|
|
204
|
+
: errorOutput,
|
|
172
205
|
timedOut: true
|
|
173
206
|
})
|
|
174
207
|
}
|
|
@@ -188,8 +188,15 @@ class SchedulerPlugin extends Plugin {
|
|
|
188
188
|
return { success: false, error: 'Agent not available' }
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
// 如果没有指定 sessionId
|
|
191
|
+
// 如果没有指定 sessionId,优先从执行上下文获取(来自 WeChat 等消息源)
|
|
192
192
|
let targetSessionId = sessionId
|
|
193
|
+
if (!targetSessionId) {
|
|
194
|
+
const ctx = this._framework.getExecutionContext()
|
|
195
|
+
if (ctx?.sessionId) {
|
|
196
|
+
targetSessionId = ctx.sessionId
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// 如果执行上下文也没有,从 sessionPlugin 获取最近活跃会话
|
|
193
200
|
if (!targetSessionId) {
|
|
194
201
|
const sessionPlugin = this._framework.pluginManager.get('session')
|
|
195
202
|
if (sessionPlugin) {
|
|
@@ -547,18 +554,14 @@ class SchedulerPlugin extends Plugin {
|
|
|
547
554
|
|
|
548
555
|
try {
|
|
549
556
|
if (task.llm) {
|
|
550
|
-
// LLM
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const result = await agent.pushMessage(task.message, {
|
|
558
|
-
isScheduledTask: true,
|
|
559
|
-
sessionId: task.sessionId
|
|
557
|
+
// LLM 模式:使用子Agent处理
|
|
558
|
+
const schedulerAgent = this._framework.createSubAgent({
|
|
559
|
+
name: 'scheduler_task',
|
|
560
|
+
role: '定时任务执行助手,专注于处理定时提醒和任务执行'
|
|
560
561
|
})
|
|
561
562
|
|
|
563
|
+
const result = await schedulerAgent.chat(task.message)
|
|
564
|
+
|
|
562
565
|
this._taskStats.completed++
|
|
563
566
|
|
|
564
567
|
// 获取 LLM 返回的消息
|
|
@@ -99,8 +99,10 @@ class TelegramPlugin extends Plugin {
|
|
|
99
99
|
this._framework.on('agent:created', (agent) => {
|
|
100
100
|
console.log('[Telegram] New agent created:', agent.name)
|
|
101
101
|
})
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
|
|
103
|
+
// 监听统一通知事件
|
|
104
|
+
this._framework.on('notification', async (data) => {
|
|
105
|
+
await this._handleNotification(data)
|
|
104
106
|
})
|
|
105
107
|
|
|
106
108
|
// 监听 webhook 事件
|
|
@@ -113,36 +115,62 @@ class TelegramPlugin extends Plugin {
|
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
console.log(`[Telegram] Reminder sent to chat ${chatId}`)
|
|
125
|
-
} catch (err) {
|
|
126
|
-
console.error(`[Telegram] Failed to send reminder:`, err.message)
|
|
127
|
-
}
|
|
118
|
+
/**
|
|
119
|
+
* 处理统一通知
|
|
120
|
+
*/
|
|
121
|
+
async _handleNotification(data) {
|
|
122
|
+
const { title, message, source, level } = data
|
|
123
|
+
|
|
124
|
+
if (!this._bot) {
|
|
125
|
+
console.warn('[Telegram] Bot not ready, cannot send notification')
|
|
128
126
|
return
|
|
129
127
|
}
|
|
130
128
|
|
|
131
|
-
|
|
129
|
+
// 确定 chatId
|
|
130
|
+
let chatId = null
|
|
131
|
+
let effectiveSessionId = data.sessionId
|
|
132
|
+
|
|
133
|
+
// 如果没有 sessionId,尝试从执行上下文获取
|
|
134
|
+
if (!effectiveSessionId) {
|
|
135
|
+
const ctx = this._framework.getExecutionContext()
|
|
136
|
+
if (ctx?.sessionId) {
|
|
137
|
+
effectiveSessionId = ctx.sessionId
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (effectiveSessionId && effectiveSessionId.startsWith('telegram_')) {
|
|
142
|
+
chatId = effectiveSessionId.replace('telegram_', '')
|
|
143
|
+
} else if (this._sessionPlugin) {
|
|
144
|
+
// 获取最近的 telegram 会话
|
|
132
145
|
const sessions = this._sessionPlugin.listSessions()
|
|
133
146
|
.filter(s => s.id.startsWith('telegram_'))
|
|
134
147
|
.sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
|
|
135
|
-
|
|
136
148
|
if (sessions.length > 0) {
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
await this._bot.sendMessage(chatId, reminderText)
|
|
140
|
-
console.log(`[Telegram] Reminder sent to recent chat ${chatId}`)
|
|
141
|
-
} catch (err) {
|
|
142
|
-
console.error(`[Telegram] Failed to send reminder:`, err.message)
|
|
143
|
-
}
|
|
149
|
+
chatId = sessions[0].id.replace('telegram_', '')
|
|
144
150
|
}
|
|
145
151
|
}
|
|
152
|
+
|
|
153
|
+
if (!chatId) {
|
|
154
|
+
console.warn('[Telegram] No telegram session found for notification')
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 格式化通知消息
|
|
159
|
+
const levelEmoji = {
|
|
160
|
+
info: 'ℹ️',
|
|
161
|
+
warning: '⚠️',
|
|
162
|
+
success: '✅',
|
|
163
|
+
error: '❌'
|
|
164
|
+
}
|
|
165
|
+
const emoji = levelEmoji[level] || 'ℹ️'
|
|
166
|
+
const notificationText = `${emoji} [${source}] ${title}\n\n${message}`
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
await this._bot.sendMessage(chatId, notificationText)
|
|
170
|
+
console.log(`[Telegram] Notification sent to chat ${chatId}`)
|
|
171
|
+
} catch (err) {
|
|
172
|
+
console.error(`[Telegram] Failed to send notification:`, err.message)
|
|
173
|
+
}
|
|
146
174
|
}
|
|
147
175
|
|
|
148
176
|
/**
|
package/plugins/web-plugin.js
CHANGED
|
@@ -312,11 +312,15 @@ class WebPlugin extends Plugin {
|
|
|
312
312
|
// 触发 webhook 接收事件
|
|
313
313
|
this._framework.emit('webhook:received', { webhook, data: webhookData, sessionId: finalSessionId })
|
|
314
314
|
|
|
315
|
+
// 使用子Agent处理 webhook
|
|
316
|
+
const webhookAgent = this._framework.createSubAgent({
|
|
317
|
+
name: 'webhook_handler',
|
|
318
|
+
role: 'Webhook处理助手,专注于处理webhook数据并生成适当响应'
|
|
319
|
+
})
|
|
320
|
+
|
|
315
321
|
if (!webhook.awaitResponse) {
|
|
316
322
|
// 不等待,立即返回
|
|
317
|
-
|
|
318
|
-
sessionId: finalSessionId
|
|
319
|
-
}).then(result => {
|
|
323
|
+
webhookAgent.chat(`${prompt}\n\n数据:\n${JSON.stringify(webhookData, null, 2)}`).then(result => {
|
|
320
324
|
const responseText = result.message || result.text || ''
|
|
321
325
|
console.log(`[Web] Webhook processed (${webhook.path}), LLM response (${responseText.length} chars)`)
|
|
322
326
|
|
|
@@ -340,9 +344,7 @@ class WebPlugin extends Plugin {
|
|
|
340
344
|
|
|
341
345
|
// 等待 LLM 处理完成
|
|
342
346
|
try {
|
|
343
|
-
const result = await
|
|
344
|
-
sessionId: finalSessionId
|
|
345
|
-
})
|
|
347
|
+
const result = await webhookAgent.chat(`${prompt}\n\n数据:\n${JSON.stringify(webhookData, null, 2)}`)
|
|
346
348
|
const responseText = result.message || result.text || ''
|
|
347
349
|
console.log(`[Web] Webhook processed (${webhook.path}), LLM response (${responseText.length} chars)`)
|
|
348
350
|
|