foliko 1.0.74 → 1.0.75
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/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +288 -0
- package/.agent/agents/ambient-agent.md +57 -0
- package/.agent/agents/debugger.md +55 -0
- package/.agent/agents/email-assistant.md +49 -0
- package/.agent/agents/file-manager.md +42 -0
- package/.agent/agents/python-developer.md +60 -0
- package/.agent/agents/scheduler.md +59 -0
- package/.agent/agents/web-developer.md +45 -0
- package/.agent/data/default.json +29 -0
- package/.agent/data/plugins-state.json +255 -0
- package/.agent/mcp_config.json +4 -0
- package/.agent/mcp_config_updated.json +12 -0
- package/.agent/plugins.json +5 -0
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/rules/allow-rule.md +77 -0
- package/.agent/rules/log-rule.md +83 -0
- package/.agent/rules/security-rule.md +93 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +81 -0
- package/.agent/workflows/simple-test.md +42 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/structured-orchestrate.md +180 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/.claude/settings.local.json +157 -149
- package/.editorconfig +56 -0
- package/.husky/pre-commit +4 -0
- package/.lintstagedrc +7 -0
- package/.prettierignore +29 -0
- package/.prettierrc +11 -0
- package/CLAUDE.md +2 -0
- package/README.md +64 -55
- package/SPEC.md +102 -61
- package/cli/bin/foliko.js +4 -4
- package/cli/src/commands/chat.js +53 -51
- package/cli/src/commands/list.js +40 -37
- package/cli/src/index.js +18 -18
- package/cli/src/ui/chat-ui.js +78 -76
- package/cli/src/utils/ansi.js +15 -15
- package/cli/src/utils/markdown.js +112 -116
- package/docker-compose.yml +1 -1
- package/docs/ai-sdk-optimization.md +655 -643
- package/docs/features.md +80 -80
- package/docs/quick-reference.md +49 -46
- package/docs/user-manual.md +411 -380
- package/examples/ambient-example.js +95 -97
- package/examples/basic.js +115 -110
- package/examples/bootstrap.js +52 -43
- package/examples/mcp-example.js +56 -53
- package/examples/skill-example.js +49 -49
- package/examples/test-chat.js +60 -58
- package/examples/test-mcp.js +49 -43
- package/examples/test-reload.js +38 -40
- package/examples/test-telegram.js +3 -3
- package/examples/test-tg-bot.js +7 -4
- package/examples/test-tg-simple.js +4 -3
- package/examples/test-tg.js +3 -3
- package/examples/test-think.js +13 -7
- package/examples/test-web-plugin.js +61 -56
- package/examples/test-weixin-feishu.js +40 -37
- package/examples/workflow.js +49 -49
- package/foliko-1.0.75.tgz +0 -0
- package/package.json +37 -3
- package/plugins/ai-plugin.js +7 -5
- package/plugins/ambient-agent/EventWatcher.js +113 -0
- package/plugins/ambient-agent/ExplorerLoop.js +640 -0
- package/plugins/ambient-agent/GoalManager.js +197 -0
- package/plugins/ambient-agent/Reflector.js +95 -0
- package/plugins/ambient-agent/StateStore.js +90 -0
- package/plugins/ambient-agent/constants.js +101 -0
- package/plugins/ambient-agent/index.js +579 -0
- package/plugins/default-plugins.js +62 -49
- package/plugins/email/constants.js +64 -0
- package/plugins/email/handlers.js +461 -0
- package/plugins/email/index.js +278 -0
- package/plugins/email/monitor.js +269 -0
- package/plugins/email/parser.js +138 -0
- package/plugins/email/reply.js +151 -0
- package/plugins/email/utils.js +124 -0
- package/plugins/feishu-plugin.js +23 -19
- package/plugins/file-system-plugin.js +456 -106
- package/plugins/install-plugin.js +6 -4
- package/plugins/python-executor-plugin.js +3 -1
- package/plugins/python-plugin-loader.js +10 -8
- package/plugins/rules-plugin.js +5 -3
- package/plugins/scheduler-plugin.js +18 -16
- package/plugins/session-plugin.js +3 -1
- package/plugins/storage-plugin.js +5 -3
- package/plugins/subagent-plugin.js +152 -92
- package/plugins/telegram-plugin.js +26 -19
- package/plugins/think-plugin.js +4 -2
- package/plugins/tools-plugin.js +3 -1
- package/plugins/web-plugin.js +15 -13
- package/plugins/weixin-plugin.js +43 -36
- package/reports/system-health-report-20260401.md +79 -0
- package/skills/ambient-agent/SKILL.md +49 -39
- package/skills/foliko-dev/AGENTS.md +64 -61
- package/skills/foliko-dev/SKILL.md +125 -119
- package/skills/mcp-usage/SKILL.md +19 -17
- package/skills/python-plugin-dev/SKILL.md +16 -15
- package/skills/skill-guide/SKILL.md +12 -12
- package/skills/subagent-guide/SKILL.md +237 -0
- package/skills/workflow-guide/SKILL.md +90 -45
- package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
- package/skills/workflow-troubleshooting/SKILL.md +156 -79
- package/src/capabilities/index.js +4 -4
- package/src/capabilities/skill-manager.js +211 -197
- package/src/capabilities/workflow-engine.js +461 -547
- package/src/core/agent-chat.js +426 -279
- package/src/core/agent.js +453 -249
- package/src/core/framework.js +183 -149
- package/src/core/index.js +8 -8
- package/src/core/plugin-base.js +52 -52
- package/src/core/plugin-manager.js +377 -281
- package/src/core/provider.js +35 -32
- package/src/core/sub-agent-config.js +264 -0
- package/src/core/system-prompt-builder.js +120 -0
- package/src/core/tool-registry.js +416 -33
- package/src/core/tool-router.js +149 -68
- package/src/executors/executor-base.js +58 -58
- package/src/executors/mcp-executor.js +269 -257
- package/src/index.js +5 -17
- package/src/utils/circuit-breaker.js +301 -0
- package/src/utils/error-boundary.js +363 -0
- package/src/utils/error.js +374 -0
- package/src/utils/event-emitter.js +20 -20
- package/src/utils/id.js +133 -0
- package/src/utils/index.js +217 -3
- package/src/utils/logger.js +181 -0
- package/src/utils/plugin-helpers.js +90 -0
- package/src/utils/retry.js +122 -0
- package/src/utils/sandbox.js +292 -0
- package/test/tool-registry-validation.test.js +218 -0
- package/test_report.md +70 -0
- package/website/docs/api.html +169 -107
- package/website/docs/configuration.html +296 -144
- package/website/docs/plugin-development.html +154 -85
- package/website/docs/project-structure.html +110 -109
- package/website/docs/skill-development.html +117 -61
- package/website/index.html +209 -205
- package/website/script.js +20 -17
- package/website/styles.css +1 -1
- package/plugins/ambient-agent-plugin.js +0 -1565
- package/plugins/email.js +0 -1142
|
@@ -21,6 +21,24 @@ class FileSystemPlugin extends Plugin {
|
|
|
21
21
|
const path = require('path')
|
|
22
22
|
const { exec } = require('child_process')
|
|
23
23
|
|
|
24
|
+
// 路径安全验证:防止路径穿越攻击
|
|
25
|
+
const validatePath = (filePath, allowOutsideCwd = false) => {
|
|
26
|
+
const resolved = path.resolve(filePath)
|
|
27
|
+
const cwd = process.cwd()
|
|
28
|
+
|
|
29
|
+
// 允许绝对路径且在允许列表中的路径(如果有的话)
|
|
30
|
+
if (allowOutsideCwd) {
|
|
31
|
+
return { valid: true, resolved }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 检查是否在 cwd 目录下
|
|
35
|
+
if (!resolved.startsWith(cwd)) {
|
|
36
|
+
return { valid: false, error: `路径不允许访问: ${filePath}` }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { valid: true, resolved }
|
|
40
|
+
}
|
|
41
|
+
|
|
24
42
|
// 读取目录
|
|
25
43
|
framework.registerTool({
|
|
26
44
|
name: 'read_directory',
|
|
@@ -95,25 +113,32 @@ class FileSystemPlugin extends Plugin {
|
|
|
95
113
|
if (!filePath) {
|
|
96
114
|
return { success: false, error: 'path 是必填参数' }
|
|
97
115
|
}
|
|
116
|
+
|
|
117
|
+
// 路径安全验证
|
|
118
|
+
const pathCheck = validatePath(filePath)
|
|
119
|
+
if (!pathCheck.valid) {
|
|
120
|
+
return { success: false, error: pathCheck.error }
|
|
121
|
+
}
|
|
122
|
+
|
|
98
123
|
try {
|
|
99
|
-
if (!fs.existsSync(
|
|
124
|
+
if (!fs.existsSync(pathCheck.resolved)) {
|
|
100
125
|
return { success: false, error: '文件不存在' }
|
|
101
126
|
}
|
|
102
|
-
const stat = fs.statSync(
|
|
127
|
+
const stat = fs.statSync(pathCheck.resolved)
|
|
103
128
|
if (stat.size > 1024 * 1024) {
|
|
104
129
|
return { success: false, error: '文件太大,超过 1MB' }
|
|
105
130
|
}
|
|
106
131
|
let content
|
|
107
132
|
if (lines) {
|
|
108
|
-
const fileContent = fs.readFileSync(
|
|
133
|
+
const fileContent = fs.readFileSync(pathCheck.resolved, 'utf8')
|
|
109
134
|
const allLines = fileContent.split('\n')
|
|
110
135
|
content = allLines.slice(0, lines).join('\n')
|
|
111
136
|
} else {
|
|
112
|
-
content = fs.readFileSync(
|
|
137
|
+
content = fs.readFileSync(pathCheck.resolved, 'utf8')
|
|
113
138
|
}
|
|
114
139
|
return {
|
|
115
140
|
success: true,
|
|
116
|
-
filePath,
|
|
141
|
+
filePath: pathCheck.resolved,
|
|
117
142
|
content,
|
|
118
143
|
size: stat.size,
|
|
119
144
|
lines: lines ? null : content.split('\n').length
|
|
@@ -137,13 +162,20 @@ class FileSystemPlugin extends Plugin {
|
|
|
137
162
|
if (!filePath || !content) {
|
|
138
163
|
return { success: false, error: 'path 和 content 都是必填参数' }
|
|
139
164
|
}
|
|
165
|
+
|
|
166
|
+
// 路径安全验证
|
|
167
|
+
const pathCheck = validatePath(filePath)
|
|
168
|
+
if (!pathCheck.valid) {
|
|
169
|
+
return { success: false, error: pathCheck.error }
|
|
170
|
+
}
|
|
171
|
+
|
|
140
172
|
try {
|
|
141
|
-
const dir = path.dirname(
|
|
173
|
+
const dir = path.dirname(pathCheck.resolved)
|
|
142
174
|
if (!fs.existsSync(dir)) {
|
|
143
175
|
fs.mkdirSync(dir, { recursive: true })
|
|
144
176
|
}
|
|
145
|
-
fs.writeFileSync(
|
|
146
|
-
return { success: true, message: `文件已写入: ${
|
|
177
|
+
fs.writeFileSync(pathCheck.resolved, content, 'utf8')
|
|
178
|
+
return { success: true, message: `文件已写入: ${pathCheck.resolved}`, filePath: pathCheck.resolved, size: content.length }
|
|
147
179
|
} catch (error) {
|
|
148
180
|
return { success: false, error: error.message }
|
|
149
181
|
}
|
|
@@ -162,21 +194,28 @@ class FileSystemPlugin extends Plugin {
|
|
|
162
194
|
execute: async (args, framework) => {
|
|
163
195
|
const targetPath = args.path || args.targetPath
|
|
164
196
|
const recursive = args.recursive || false
|
|
197
|
+
|
|
198
|
+
// 路径安全验证
|
|
199
|
+
const pathCheck = validatePath(targetPath)
|
|
200
|
+
if (!pathCheck.valid) {
|
|
201
|
+
return { success: false, error: pathCheck.error }
|
|
202
|
+
}
|
|
203
|
+
|
|
165
204
|
try {
|
|
166
|
-
if (!fs.existsSync(
|
|
205
|
+
if (!fs.existsSync(pathCheck.resolved)) {
|
|
167
206
|
return { success: false, error: '目标不存在' }
|
|
168
207
|
}
|
|
169
|
-
const stat = fs.statSync(
|
|
208
|
+
const stat = fs.statSync(pathCheck.resolved)
|
|
170
209
|
if (stat.isDirectory()) {
|
|
171
210
|
if (recursive) {
|
|
172
|
-
fs.rmSync(
|
|
211
|
+
fs.rmSync(pathCheck.resolved, { recursive: true, force: true })
|
|
173
212
|
} else {
|
|
174
|
-
fs.rmdirSync(
|
|
213
|
+
fs.rmdirSync(pathCheck.resolved)
|
|
175
214
|
}
|
|
176
215
|
} else {
|
|
177
|
-
fs.unlinkSync(
|
|
216
|
+
fs.unlinkSync(pathCheck.resolved)
|
|
178
217
|
}
|
|
179
|
-
return { success: true, message: `已删除: ${
|
|
218
|
+
return { success: true, message: `已删除: ${pathCheck.resolved}` }
|
|
180
219
|
} catch (error) {
|
|
181
220
|
return { success: false, error: error.message }
|
|
182
221
|
}
|
|
@@ -184,64 +223,236 @@ class FileSystemPlugin extends Plugin {
|
|
|
184
223
|
})
|
|
185
224
|
|
|
186
225
|
// 修改文件(替换文本)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
226
|
+
framework.registerTool({
|
|
227
|
+
name: 'modify_file',
|
|
228
|
+
description: '修改文件内容(替换文本),支持精确匹配和正则',
|
|
229
|
+
inputSchema: z.object({
|
|
230
|
+
filePath: z.string().describe('文件路径(必须)'),
|
|
231
|
+
find: z.string().min(1).describe('要查找的文本(必填,不能为空)'),
|
|
232
|
+
replace: z.string().describe('替换后的文本'),
|
|
233
|
+
replaceAll: z.boolean().optional().describe('是否替换所有匹配,默认 true'),
|
|
234
|
+
useRegex: z.boolean().optional().describe('是否使用正则表达式匹配,默认 false'),
|
|
235
|
+
backup: z.boolean().default(false).describe('修改前是否创建备份,默认 false')
|
|
236
|
+
}),
|
|
237
|
+
execute: async (args, framework) => {
|
|
238
|
+
const filePath = args.filePath || args.path
|
|
239
|
+
const find = args.find
|
|
240
|
+
const replace = args.replace
|
|
241
|
+
const replaceAll = args.replaceAll !== false // 默认 true
|
|
242
|
+
const useRegex = args.useRegex === true
|
|
243
|
+
const backup = args.backup !== false // 默认 true
|
|
244
|
+
|
|
245
|
+
// 校验 filePath
|
|
246
|
+
if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
|
|
247
|
+
return { success: false, error: 'filePath 是必填参数,不能为空' }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 校验 find
|
|
251
|
+
if (!find || typeof find !== 'string' || find.trim() === '') {
|
|
252
|
+
return { success: false, error: 'find 是必填参数,不能为空字符串' }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 路径安全验证
|
|
256
|
+
const pathCheck = validatePath(filePath)
|
|
257
|
+
if (!pathCheck.valid) {
|
|
258
|
+
return { success: false, error: pathCheck.error }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
if (!fs.existsSync(pathCheck.resolved)) {
|
|
263
|
+
return { success: false, error: '文件不存在' }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 检查是否是二进制文件
|
|
267
|
+
const stats = fs.statSync(pathCheck.resolved)
|
|
268
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
269
|
+
return { success: false, error: '文件超过 10MB,不支持修改' }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let content = fs.readFileSync(pathCheck.resolved, 'utf8')
|
|
273
|
+
let count = 0
|
|
274
|
+
const matches = []
|
|
275
|
+
|
|
276
|
+
if (useRegex) {
|
|
277
|
+
// 正则表达式模式
|
|
278
|
+
const flags = replaceAll ? 'g' : ''
|
|
279
|
+
const regex = new RegExp(find, flags)
|
|
280
|
+
|
|
281
|
+
let match
|
|
282
|
+
while ((match = regex.exec(content)) !== null) {
|
|
283
|
+
matches.push({
|
|
284
|
+
index: match.index,
|
|
285
|
+
length: match[0].length,
|
|
286
|
+
text: match[0]
|
|
287
|
+
})
|
|
288
|
+
if (!replaceAll) break
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (matches.length > 0) {
|
|
292
|
+
count = replaceAll ? matches.length : 1
|
|
293
|
+
content = content.replace(regex, replace)
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
// 精确字符串匹配
|
|
297
|
+
const escapedFind = find.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
298
|
+
const flags = replaceAll ? 'g' : ''
|
|
299
|
+
const regex = new RegExp(escapedFind, flags)
|
|
300
|
+
|
|
301
|
+
let match
|
|
302
|
+
while ((match = regex.exec(content)) !== null) {
|
|
303
|
+
matches.push({
|
|
304
|
+
index: match.index,
|
|
305
|
+
length: match[0].length,
|
|
306
|
+
text: match[0]
|
|
307
|
+
})
|
|
308
|
+
if (!replaceAll) break
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (matches.length > 0) {
|
|
312
|
+
count = replaceAll ? matches.length : 1
|
|
313
|
+
content = content.replace(regex, replace)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (count === 0) {
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: `未找到匹配文本: "${find.substring(0, 50)}${find.length > 50 ? '...' : ''}"`,
|
|
321
|
+
filePath
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 创建备份
|
|
326
|
+
if (backup) {
|
|
327
|
+
const backupPath = pathCheck.resolved + '.bak'
|
|
328
|
+
fs.writeFileSync(backupPath, fs.readFileSync(pathCheck.resolved), 'utf8')
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 原子写入:先写临时文件,再 rename
|
|
332
|
+
const tempPath = pathCheck.resolved + '.tmp.' + Date.now()
|
|
333
|
+
fs.writeFileSync(tempPath, content, 'utf8')
|
|
334
|
+
|
|
335
|
+
// 验证写入内容
|
|
336
|
+
const verifyContent = fs.readFileSync(tempPath, 'utf8')
|
|
337
|
+
if (verifyContent !== content) {
|
|
338
|
+
fs.unlinkSync(tempPath)
|
|
339
|
+
return { success: false, error: '写入验证失败,内容不匹配' }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fs.renameSync(tempPath, pathCheck.resolved)
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
success: true,
|
|
346
|
+
message: `文件已修改: ${pathCheck.resolved}`,
|
|
347
|
+
filePath: pathCheck.resolved,
|
|
348
|
+
replacements: count,
|
|
349
|
+
matches: matches.slice(0, 5), // 最多返回5个匹配位置
|
|
350
|
+
backupCreated: backup
|
|
351
|
+
}
|
|
352
|
+
} catch (error) {
|
|
353
|
+
return { success: false, error: error.message }
|
|
354
|
+
}
|
|
211
355
|
}
|
|
212
|
-
|
|
213
|
-
return { success: true, message: `文件已修改: ${filePath}`, filePath }
|
|
214
|
-
} catch (error) {
|
|
215
|
-
return { success: false, error: error.message }
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
})
|
|
356
|
+
})
|
|
219
357
|
|
|
220
358
|
// 搜索文件
|
|
221
359
|
framework.registerTool({
|
|
222
360
|
name: 'search_file',
|
|
223
|
-
description: '
|
|
361
|
+
description: '在文件或目录中搜索文本,支持精确匹配和正则表达式',
|
|
224
362
|
inputSchema: z.object({
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
363
|
+
pattern: z.string().describe('搜索模式(关键词或正则表达式)'),
|
|
364
|
+
path: z.string().optional().describe('搜索目录路径'),
|
|
365
|
+
file: z.string().optional().describe('搜索指定文件(与 path 二选一)'),
|
|
366
|
+
fileType: z.string().optional().describe('文件类型过滤,如 .js、.py'),
|
|
367
|
+
maxResults: z.number().optional().describe('最大结果数,默认 100'),
|
|
368
|
+
maxResultsPerFile: z.number().optional().describe('每个文件最大结果数,默认 50'),
|
|
369
|
+
contextLines: z.number().optional().describe('匹配行的上下文行数,默认 0'),
|
|
370
|
+
caseSensitive: z.boolean().optional().describe('是否大小写敏感,默认 false'),
|
|
371
|
+
useRegex: z.boolean().optional().describe('是否使用正则表达式,默认 false'),
|
|
372
|
+
excludeDirs: z.array(z.string()).optional().describe('排除的目录名,默认 ["node_modules", ".git", "dist", "build"]')
|
|
234
373
|
}),
|
|
235
374
|
execute: async (args, framework) => {
|
|
236
|
-
const pattern = args.
|
|
237
|
-
const dirPath = args.path ||
|
|
375
|
+
const pattern = args.pattern
|
|
376
|
+
const dirPath = args.path || process.cwd()
|
|
238
377
|
const targetFile = args.file
|
|
239
378
|
const fileType = args.fileType
|
|
240
|
-
const maxResults = args.maxResults ||
|
|
379
|
+
const maxResults = args.maxResults || 100
|
|
380
|
+
const maxResultsPerFile = args.maxResultsPerFile || 50
|
|
241
381
|
const contextLines = args.contextLines || 0
|
|
382
|
+
const caseSensitive = args.caseSensitive === true
|
|
383
|
+
const useRegex = args.useRegex === true
|
|
384
|
+
const excludeDirs = args.excludeDirs || ['node_modules', '.git', 'dist', 'build', '.claude']
|
|
385
|
+
|
|
386
|
+
if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') {
|
|
387
|
+
return { success: false, error: 'pattern 是必填参数,不能为空' }
|
|
388
|
+
}
|
|
389
|
+
|
|
242
390
|
try {
|
|
243
391
|
const results = []
|
|
244
|
-
|
|
392
|
+
let regex
|
|
393
|
+
|
|
394
|
+
if (useRegex) {
|
|
395
|
+
// 直接使用正则表达式
|
|
396
|
+
try {
|
|
397
|
+
regex = new RegExp(pattern, caseSensitive ? 'g' : 'gi')
|
|
398
|
+
} catch (e) {
|
|
399
|
+
return { success: false, error: `无效的正则表达式: ${e.message}` }
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
// 转义特殊字符作为精确匹配
|
|
403
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
404
|
+
regex = new RegExp(escaped, caseSensitive ? 'g' : 'gi')
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const binaryExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.exe', '.dll', '.so', '.zip', '.tar', '.gz', '.rar', '.7z', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.mp3', '.mp4', '.avi', '.mkv', '.wav', '.flac']
|
|
408
|
+
|
|
409
|
+
const searchInContent = (content, filePath) => {
|
|
410
|
+
const matches = []
|
|
411
|
+
const lines = content.split('\n')
|
|
412
|
+
let fileResultsCount = 0
|
|
413
|
+
|
|
414
|
+
for (let i = 0; i < lines.length; i++) {
|
|
415
|
+
if (fileResultsCount >= maxResultsPerFile) break
|
|
416
|
+
const line = lines[i]
|
|
417
|
+
regex.lastIndex = 0 // 重置 regex 状态
|
|
418
|
+
|
|
419
|
+
if (regex.test(line)) {
|
|
420
|
+
// 找到所有匹配的位置
|
|
421
|
+
const lineMatches = []
|
|
422
|
+
regex.lastIndex = 0
|
|
423
|
+
let match
|
|
424
|
+
while ((match = regex.exec(line)) !== null) {
|
|
425
|
+
lineMatches.push({
|
|
426
|
+
column: match.index,
|
|
427
|
+
length: match[0].length,
|
|
428
|
+
text: match[0]
|
|
429
|
+
})
|
|
430
|
+
if (!regex.global) break
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const matchInfo = {
|
|
434
|
+
file: filePath,
|
|
435
|
+
line: i + 1,
|
|
436
|
+
content: line.substring(0, 300),
|
|
437
|
+
matches: lineMatches
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (contextLines > 0) {
|
|
441
|
+
const start = Math.max(0, i - contextLines)
|
|
442
|
+
const end = Math.min(lines.length, i + contextLines + 1)
|
|
443
|
+
matchInfo.context = lines.slice(start, end).map((l, idx) => ({
|
|
444
|
+
line: start + idx + 1,
|
|
445
|
+
content: l
|
|
446
|
+
}))
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
matches.push(matchInfo)
|
|
450
|
+
fileResultsCount++
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return matches
|
|
455
|
+
}
|
|
245
456
|
|
|
246
457
|
// 搜索单个文件
|
|
247
458
|
if (targetFile) {
|
|
@@ -249,74 +460,85 @@ class FileSystemPlugin extends Plugin {
|
|
|
249
460
|
if (!fs.existsSync(fullPath)) {
|
|
250
461
|
return { success: false, error: `文件不存在: ${targetFile}` }
|
|
251
462
|
}
|
|
463
|
+
|
|
464
|
+
const ext = path.extname(fullPath).toLowerCase()
|
|
465
|
+
if (binaryExtensions.includes(ext)) {
|
|
466
|
+
return { success: false, error: `不支持搜索二进制文件: ${ext}` }
|
|
467
|
+
}
|
|
468
|
+
|
|
252
469
|
try {
|
|
253
470
|
const content = fs.readFileSync(fullPath, 'utf8')
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
if (regex.test(lines[i])) {
|
|
257
|
-
let matchInfo = { file: targetFile, line: i + 1, content: lines[i].substring(0, 200) }
|
|
258
|
-
// 添加上下文行
|
|
259
|
-
if (contextLines > 0) {
|
|
260
|
-
const start = Math.max(0, i - contextLines)
|
|
261
|
-
const end = Math.min(lines.length, i + contextLines + 1)
|
|
262
|
-
matchInfo.context = lines.slice(start, end).map((l, idx) => ({
|
|
263
|
-
line: start + idx + 1,
|
|
264
|
-
content: l
|
|
265
|
-
}))
|
|
266
|
-
}
|
|
267
|
-
results.push(matchInfo)
|
|
268
|
-
if (results.length >= maxResults) break
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
regex.lastIndex = 0
|
|
471
|
+
const fileMatches = searchInContent(content, targetFile)
|
|
472
|
+
results.push(...fileMatches)
|
|
272
473
|
} catch (e) {
|
|
273
474
|
return { success: false, error: `读取文件失败: ${e.message}` }
|
|
274
475
|
}
|
|
275
476
|
} else {
|
|
276
477
|
// 搜索目录
|
|
277
478
|
const searchDir = (currentPath, depth = 0) => {
|
|
278
|
-
if (depth >
|
|
279
|
-
|
|
479
|
+
if (depth > 10 || results.length >= maxResults) return
|
|
480
|
+
|
|
481
|
+
let entries
|
|
482
|
+
try {
|
|
483
|
+
entries = fs.readdirSync(currentPath, { withFileTypes: true })
|
|
484
|
+
} catch (e) {
|
|
485
|
+
return // 跳过无法读取的目录
|
|
486
|
+
}
|
|
487
|
+
|
|
280
488
|
for (const entry of entries) {
|
|
281
489
|
if (results.length >= maxResults) break
|
|
490
|
+
|
|
282
491
|
const fullPath = path.join(currentPath, entry.name)
|
|
283
492
|
const relativePath = path.relative(process.cwd(), fullPath)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
493
|
+
|
|
494
|
+
// 检查是否在排除目录中
|
|
495
|
+
const shouldExclude = excludeDirs.some(exclude =>
|
|
496
|
+
relativePath.includes(exclude)
|
|
497
|
+
)
|
|
498
|
+
if (shouldExclude) continue
|
|
499
|
+
|
|
288
500
|
if (entry.isDirectory()) {
|
|
289
501
|
searchDir(fullPath, depth + 1)
|
|
290
502
|
} else if (entry.isFile()) {
|
|
503
|
+
// 文件类型过滤
|
|
291
504
|
if (fileType && !entry.name.endsWith(fileType)) continue
|
|
292
|
-
|
|
293
|
-
|
|
505
|
+
|
|
506
|
+
const ext = path.extname(entry.name).toLowerCase()
|
|
507
|
+
if (binaryExtensions.includes(ext)) continue
|
|
508
|
+
|
|
294
509
|
try {
|
|
295
510
|
const content = fs.readFileSync(fullPath, 'utf8')
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const start = Math.max(0, i - contextLines)
|
|
302
|
-
const end = Math.min(lines.length, i + contextLines + 1)
|
|
303
|
-
matchInfo.context = lines.slice(start, end).map((l, idx) => ({
|
|
304
|
-
line: start + idx + 1,
|
|
305
|
-
content: l
|
|
306
|
-
}))
|
|
307
|
-
}
|
|
308
|
-
results.push(matchInfo)
|
|
309
|
-
if (results.length >= maxResults) break
|
|
310
|
-
}
|
|
511
|
+
const fileMatches = searchInContent(content, relativePath)
|
|
512
|
+
|
|
513
|
+
for (const match of fileMatches) {
|
|
514
|
+
if (results.length >= maxResults) break
|
|
515
|
+
results.push(match)
|
|
311
516
|
}
|
|
312
|
-
|
|
313
|
-
|
|
517
|
+
} catch (e) {
|
|
518
|
+
// 跳过无法读取的文件
|
|
519
|
+
}
|
|
314
520
|
}
|
|
315
521
|
}
|
|
316
522
|
}
|
|
523
|
+
|
|
317
524
|
searchDir(dirPath)
|
|
318
525
|
}
|
|
319
|
-
|
|
526
|
+
|
|
527
|
+
// 计算统计信息
|
|
528
|
+
const filesWithMatches = new Set(results.map(r => r.file)).size
|
|
529
|
+
const totalMatches = results.reduce((sum, r) => sum + (r.matches?.length || 1), 0)
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
success: true,
|
|
533
|
+
pattern,
|
|
534
|
+
results: results.slice(0, maxResults),
|
|
535
|
+
total: results.length,
|
|
536
|
+
stats: {
|
|
537
|
+
filesWithMatches,
|
|
538
|
+
totalMatches,
|
|
539
|
+
searchPath: targetFile || dirPath
|
|
540
|
+
}
|
|
541
|
+
}
|
|
320
542
|
} catch (error) {
|
|
321
543
|
return { success: false, error: error.message }
|
|
322
544
|
}
|
|
@@ -337,13 +559,45 @@ class FileSystemPlugin extends Plugin {
|
|
|
337
559
|
execute: async (args, framework) => {
|
|
338
560
|
const command = args.cmd || args.command || args.run
|
|
339
561
|
const cwd = args.cwd || process.cwd()
|
|
340
|
-
const timeout = args.timeout || 30000
|
|
562
|
+
const timeout = Math.min(args.timeout || 30000, 120000) // 最多 2 分钟
|
|
563
|
+
|
|
564
|
+
// 验证命令:检查危险的 shell 模式
|
|
565
|
+
const dangerousPatterns = [
|
|
566
|
+
/;\s*rm\s+/i, // ; rm -rf
|
|
567
|
+
/\|\s*rm\s+/i, // | rm -rf
|
|
568
|
+
/&&\s*rm\s+/i, // && rm -rf
|
|
569
|
+
/;\s*del\s+/i, // ; del
|
|
570
|
+
/\|\s*del\s+/i, // | del
|
|
571
|
+
/&\s*rm\s+/i, // & rm -rf
|
|
572
|
+
/;\s*format\s+/i, // ; format
|
|
573
|
+
/&\s*format\s+/i, // & format
|
|
574
|
+
/\$\(/i, // $(command substitution)
|
|
575
|
+
/`[^`]+`/i, // `command substitution`
|
|
576
|
+
/>\s*\/dev\//i, // redirect to /dev/
|
|
577
|
+
/<\s*\/dev\//i, // read from /dev/
|
|
578
|
+
]
|
|
579
|
+
|
|
580
|
+
for (const pattern of dangerousPatterns) {
|
|
581
|
+
if (pattern.test(command)) {
|
|
582
|
+
return {
|
|
583
|
+
success: false,
|
|
584
|
+
error: `命令包含可疑模式,已拒绝执行: ${pattern.toString()}`
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 验证 cwd 路径
|
|
590
|
+
const resolvedCwd = path.resolve(cwd)
|
|
591
|
+
if (!fs.existsSync(resolvedCwd)) {
|
|
592
|
+
return { success: false, error: `工作目录不存在: ${resolvedCwd}` }
|
|
593
|
+
}
|
|
594
|
+
|
|
341
595
|
return new Promise((resolve) => {
|
|
342
596
|
const startTime = Date.now()
|
|
343
|
-
exec(command, { cwd, timeout }, (error, stdout, stderr) => {
|
|
597
|
+
exec(command, { cwd: resolvedCwd, timeout, shell: true }, (error, stdout, stderr) => {
|
|
344
598
|
const duration = Date.now() - startTime
|
|
345
599
|
if (error) {
|
|
346
|
-
resolve({ success: false, command, error: error.message, stderr, duration })
|
|
600
|
+
resolve({ success: false, command, error: error.message, stderr: stderr.substring(0, 1000), duration })
|
|
347
601
|
} else {
|
|
348
602
|
resolve({
|
|
349
603
|
success: true,
|
|
@@ -358,6 +612,102 @@ class FileSystemPlugin extends Plugin {
|
|
|
358
612
|
}
|
|
359
613
|
})
|
|
360
614
|
|
|
615
|
+
// 执行 Bash 命令
|
|
616
|
+
framework.registerTool({
|
|
617
|
+
name: 'bash',
|
|
618
|
+
description: '执行 Bash 命令(支持 Linux/macOS/Windows Git Bash)',
|
|
619
|
+
inputSchema: z.object({
|
|
620
|
+
cmd: z.string().optional().describe('要执行的 bash 命令'),
|
|
621
|
+
command: z.string().optional().describe('要执行的 bash 命令(同cmd)'),
|
|
622
|
+
cwd: z.string().optional().describe('工作目录'),
|
|
623
|
+
timeout: z.number().optional().describe('超时时间(ms)')
|
|
624
|
+
}),
|
|
625
|
+
execute: async (args, framework) => {
|
|
626
|
+
const command = args.cmd || args.command
|
|
627
|
+
const cwd = args.cwd || process.cwd()
|
|
628
|
+
const timeout = Math.min(args.timeout || 30000, 120000)
|
|
629
|
+
|
|
630
|
+
if (!command) {
|
|
631
|
+
return { success: false, error: 'cmd/command 是必填参数' }
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// 验证命令:检查危险的 shell 模式
|
|
635
|
+
const dangerousPatterns = [
|
|
636
|
+
/;\s*rm\s+-rf/i,
|
|
637
|
+
/\|\s*rm\s+/i,
|
|
638
|
+
/&&\s*rm\s+/i,
|
|
639
|
+
/;\s*del\s+/i,
|
|
640
|
+
/\$\(/i,
|
|
641
|
+
/`[^`]+`/i,
|
|
642
|
+
/>\s*\/dev\//i,
|
|
643
|
+
/<\s*\/dev\//i,
|
|
644
|
+
]
|
|
645
|
+
|
|
646
|
+
for (const pattern of dangerousPatterns) {
|
|
647
|
+
if (pattern.test(command)) {
|
|
648
|
+
return {
|
|
649
|
+
success: false,
|
|
650
|
+
error: `命令包含可疑模式,已拒绝执行: ${pattern.toString()}`
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// 验证 cwd 路径
|
|
656
|
+
const resolvedCwd = path.resolve(cwd)
|
|
657
|
+
if (!fs.existsSync(resolvedCwd)) {
|
|
658
|
+
return { success: false, error: `工作目录不存在: ${resolvedCwd}` }
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// 确定 bash 路径
|
|
662
|
+
let bashPath = 'bash'
|
|
663
|
+
const isWindows = process.platform === 'win32'
|
|
664
|
+
|
|
665
|
+
if (isWindows) {
|
|
666
|
+
// Windows 下尝试查找 Git Bash
|
|
667
|
+
const possiblePaths = [
|
|
668
|
+
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
669
|
+
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
670
|
+
process.env.GIT_BASH_PATH || 'bash'
|
|
671
|
+
]
|
|
672
|
+
for (const p of possiblePaths) {
|
|
673
|
+
if (fs.existsSync(p)) {
|
|
674
|
+
bashPath = p
|
|
675
|
+
break
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return new Promise((resolve) => {
|
|
681
|
+
const startTime = Date.now()
|
|
682
|
+
exec(
|
|
683
|
+
`"${bashPath}" -c ${JSON.stringify(command)}`,
|
|
684
|
+
{ cwd: resolvedCwd, timeout, shell: false },
|
|
685
|
+
(error, stdout, stderr) => {
|
|
686
|
+
const duration = Date.now() - startTime
|
|
687
|
+
if (error) {
|
|
688
|
+
resolve({
|
|
689
|
+
success: false,
|
|
690
|
+
command,
|
|
691
|
+
error: error.message,
|
|
692
|
+
stderr: stderr.substring(0, 2000),
|
|
693
|
+
stdout: stdout.substring(0, 2000),
|
|
694
|
+
duration
|
|
695
|
+
})
|
|
696
|
+
} else {
|
|
697
|
+
resolve({
|
|
698
|
+
success: true,
|
|
699
|
+
command,
|
|
700
|
+
stdout: stdout.substring(0, 10000),
|
|
701
|
+
stderr: stderr.substring(0, 1000),
|
|
702
|
+
duration
|
|
703
|
+
})
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
)
|
|
707
|
+
})
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
|
|
361
711
|
// 获取北京时间
|
|
362
712
|
framework.registerTool({
|
|
363
713
|
name: 'get_time',
|