foliko 2.0.21 → 2.0.23
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/.editorconfig +56 -56
- package/.lintstagedrc +7 -7
- package/.prettierignore +29 -29
- package/.prettierrc +11 -11
- package/Dockerfile +63 -63
- package/install.ps1 +129 -129
- package/install.sh +121 -121
- package/package.json +3 -5
- package/plugins/core/skill-manager/index.js +34 -23
- package/plugins/core/sub-agent/index.js +103 -14
- package/plugins/core/workflow/context.js +941 -941
- package/plugins/core/workflow/engine.js +66 -66
- package/plugins/core/workflow/js-runner.js +318 -318
- package/plugins/core/workflow/json-runner.js +323 -323
- package/plugins/core/workflow/stages/choice.js +74 -74
- package/plugins/core/workflow/stages/each.js +123 -123
- package/plugins/core/workflow/stages/parallel.js +69 -69
- package/plugins/executors/extension/extension-registry.js +72 -1
- package/plugins/executors/extension/index.js +68 -9
- package/plugins/io/file-system/index.js +377 -153
- package/skills/find-skills/SKILL.md +133 -133
- package/src/agent/chat.js +207 -222
- package/src/agent/sub.js +29 -26
- package/src/agent/tool-loop.js +648 -0
- package/src/llm/provider.js +12 -28
- package/src/plugin/base.js +17 -14
- package/src/plugin/manager.js +19 -0
- package/src/tool/router.js +2 -2
- package/src/utils/message-validator.js +186 -0
- package/tests/core/chat-tool.test.js +187 -0
- package/tests/core/disable-thinking.test.js +64 -0
- package/tests/core/edit-file.test.js +194 -0
- package/tests/core/ext-call-empty-args.test.js +136 -0
- package/tests/core/reasoning-content.test.js +129 -0
- package/tests/core/sanitize-for-llm.test.js +152 -0
- package/tests/core/search.test.js +212 -0
- package/tests/core/skill-input-schema.test.js +150 -0
- package/tests/core/strip-stale-tool-calls.test.js +154 -0
- package/tests/core/sub-agent-parse.test.js +247 -0
- package/tests/core/sub-agent-skills.test.js +197 -0
- package/tests/core/tool-loop.test.js +208 -0
- package/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.jsonl +0 -76
package/install.sh
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Foliko Installer
|
|
3
|
-
|
|
4
|
-
set -e
|
|
5
|
-
|
|
6
|
-
echo -e "\033[36mFoliko Installer\033[0m"
|
|
7
|
-
echo -e "\033[36m=================\033[0m"
|
|
8
|
-
echo ""
|
|
9
|
-
|
|
10
|
-
# Detect OS
|
|
11
|
-
detect_os() {
|
|
12
|
-
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
13
|
-
echo "macOS"
|
|
14
|
-
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
15
|
-
echo "Linux"
|
|
16
|
-
else
|
|
17
|
-
echo "unknown"
|
|
18
|
-
fi
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
OS=$(detect_os)
|
|
22
|
-
|
|
23
|
-
# ============ Check/Install Node.js ============
|
|
24
|
-
echo -e "\033[36mChecking Node.js...\033[0m"
|
|
25
|
-
if command -v node &> /dev/null; then
|
|
26
|
-
echo -e "\033[32mNode.js installed: $(node --version)\033[0m"
|
|
27
|
-
else
|
|
28
|
-
echo -e "\033[33mNode.js not found, installing...\033[0m"
|
|
29
|
-
|
|
30
|
-
if [[ "$OS" == "macOS" ]]; then
|
|
31
|
-
if command -v brew &> /dev/null; then
|
|
32
|
-
brew install node
|
|
33
|
-
else
|
|
34
|
-
echo -e "\033[31mHomebrew not found. Install it first:\033[0m"
|
|
35
|
-
echo "/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
|
36
|
-
exit 1
|
|
37
|
-
fi
|
|
38
|
-
elif [[ "$OS" == "Linux" ]]; then
|
|
39
|
-
if command -v apt-get &> /dev/null; then
|
|
40
|
-
sudo apt-get update && sudo apt-get install -y nodejs npm
|
|
41
|
-
elif command -v yum &> /dev/null; then
|
|
42
|
-
sudo yum install -y nodejs npm
|
|
43
|
-
elif command -v pacman &> /dev/null; then
|
|
44
|
-
sudo pacman -S nodejs npm
|
|
45
|
-
else
|
|
46
|
-
echo -e "\033[31mNo package manager found. Install Node.js manually: https://nodejs.org/\033[0m"
|
|
47
|
-
exit 1
|
|
48
|
-
fi
|
|
49
|
-
fi
|
|
50
|
-
|
|
51
|
-
if command -v node &> /dev/null; then
|
|
52
|
-
echo -e "\033[32mNode.js installed: $(node --version)\033[0m"
|
|
53
|
-
else
|
|
54
|
-
echo -e "\033[31mNode.js installation failed\033[0m"
|
|
55
|
-
exit 1
|
|
56
|
-
fi
|
|
57
|
-
fi
|
|
58
|
-
echo ""
|
|
59
|
-
|
|
60
|
-
# ============ Check/Install Python ============
|
|
61
|
-
echo -e "\033[36mChecking Python...\033[0m"
|
|
62
|
-
if command -v python3 &> /dev/null; then
|
|
63
|
-
echo -e "\033[32mPython installed: $(python3 --version)\033[0m"
|
|
64
|
-
else
|
|
65
|
-
echo -e "\033[33mPython not found, installing...\033[0m"
|
|
66
|
-
|
|
67
|
-
if [[ "$OS" == "macOS" ]]; then
|
|
68
|
-
if command -v brew &> /dev/null; then
|
|
69
|
-
brew install python3
|
|
70
|
-
fi
|
|
71
|
-
elif [[ "$OS" == "Linux" ]]; then
|
|
72
|
-
if command -v apt-get &> /dev/null; then
|
|
73
|
-
sudo apt-get update && sudo apt-get install -y python3 python3-venv python3-pip
|
|
74
|
-
elif command -v yum &> /dev/null; then
|
|
75
|
-
sudo yum install -y python3
|
|
76
|
-
elif command -v pacman &> /dev/null; then
|
|
77
|
-
sudo pacman -S python python-pip
|
|
78
|
-
fi
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
if command -v python3 &> /dev/null; then
|
|
82
|
-
echo -e "\033[32mPython installed: $(python3 --version)\033[0m"
|
|
83
|
-
else
|
|
84
|
-
echo -e "\033[33mPython installation failed (optional)\033[0m"
|
|
85
|
-
fi
|
|
86
|
-
fi
|
|
87
|
-
echo ""
|
|
88
|
-
|
|
89
|
-
# ============ Check/Install uv ============
|
|
90
|
-
echo -e "\033[36mChecking uv...\033[0m"
|
|
91
|
-
if command -v uv &> /dev/null; then
|
|
92
|
-
echo -e "\033[32muv installed: $(uv --version)\033[0m"
|
|
93
|
-
else
|
|
94
|
-
echo -e "\033[33muv not found, installing...\033[0m"
|
|
95
|
-
|
|
96
|
-
if [[ "$OS" == "macOS" ]] || [[ "$OS" == "Linux" ]]; then
|
|
97
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
98
|
-
source $HOME/.local/bin/env 2>/dev/null || true
|
|
99
|
-
|
|
100
|
-
if command -v uv &> /dev/null; then
|
|
101
|
-
echo -e "\033[32muv installed: $(uv --version)\033[0m"
|
|
102
|
-
else
|
|
103
|
-
echo -e "\033[33muv installation failed (optional)\033[0m"
|
|
104
|
-
fi
|
|
105
|
-
fi
|
|
106
|
-
fi
|
|
107
|
-
echo ""
|
|
108
|
-
|
|
109
|
-
# ============ Install Foliko ============
|
|
110
|
-
echo -e "\033[36mInstalling Foliko...\033[0m"
|
|
111
|
-
npm install -g foliko
|
|
112
|
-
|
|
113
|
-
if command -v foliko &> /dev/null; then
|
|
114
|
-
echo ""
|
|
115
|
-
echo -e "\033[32mInstallation complete!\033[0m"
|
|
116
|
-
echo "Run: foliko chat"
|
|
117
|
-
else
|
|
118
|
-
echo ""
|
|
119
|
-
echo -e "\033[31mInstallation failed. Try manually: npm install -g foliko\033[0m"
|
|
120
|
-
exit 1
|
|
121
|
-
fi
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Foliko Installer
|
|
3
|
+
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
echo -e "\033[36mFoliko Installer\033[0m"
|
|
7
|
+
echo -e "\033[36m=================\033[0m"
|
|
8
|
+
echo ""
|
|
9
|
+
|
|
10
|
+
# Detect OS
|
|
11
|
+
detect_os() {
|
|
12
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
13
|
+
echo "macOS"
|
|
14
|
+
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
15
|
+
echo "Linux"
|
|
16
|
+
else
|
|
17
|
+
echo "unknown"
|
|
18
|
+
fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
OS=$(detect_os)
|
|
22
|
+
|
|
23
|
+
# ============ Check/Install Node.js ============
|
|
24
|
+
echo -e "\033[36mChecking Node.js...\033[0m"
|
|
25
|
+
if command -v node &> /dev/null; then
|
|
26
|
+
echo -e "\033[32mNode.js installed: $(node --version)\033[0m"
|
|
27
|
+
else
|
|
28
|
+
echo -e "\033[33mNode.js not found, installing...\033[0m"
|
|
29
|
+
|
|
30
|
+
if [[ "$OS" == "macOS" ]]; then
|
|
31
|
+
if command -v brew &> /dev/null; then
|
|
32
|
+
brew install node
|
|
33
|
+
else
|
|
34
|
+
echo -e "\033[31mHomebrew not found. Install it first:\033[0m"
|
|
35
|
+
echo "/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
elif [[ "$OS" == "Linux" ]]; then
|
|
39
|
+
if command -v apt-get &> /dev/null; then
|
|
40
|
+
sudo apt-get update && sudo apt-get install -y nodejs npm
|
|
41
|
+
elif command -v yum &> /dev/null; then
|
|
42
|
+
sudo yum install -y nodejs npm
|
|
43
|
+
elif command -v pacman &> /dev/null; then
|
|
44
|
+
sudo pacman -S nodejs npm
|
|
45
|
+
else
|
|
46
|
+
echo -e "\033[31mNo package manager found. Install Node.js manually: https://nodejs.org/\033[0m"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
if command -v node &> /dev/null; then
|
|
52
|
+
echo -e "\033[32mNode.js installed: $(node --version)\033[0m"
|
|
53
|
+
else
|
|
54
|
+
echo -e "\033[31mNode.js installation failed\033[0m"
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
echo ""
|
|
59
|
+
|
|
60
|
+
# ============ Check/Install Python ============
|
|
61
|
+
echo -e "\033[36mChecking Python...\033[0m"
|
|
62
|
+
if command -v python3 &> /dev/null; then
|
|
63
|
+
echo -e "\033[32mPython installed: $(python3 --version)\033[0m"
|
|
64
|
+
else
|
|
65
|
+
echo -e "\033[33mPython not found, installing...\033[0m"
|
|
66
|
+
|
|
67
|
+
if [[ "$OS" == "macOS" ]]; then
|
|
68
|
+
if command -v brew &> /dev/null; then
|
|
69
|
+
brew install python3
|
|
70
|
+
fi
|
|
71
|
+
elif [[ "$OS" == "Linux" ]]; then
|
|
72
|
+
if command -v apt-get &> /dev/null; then
|
|
73
|
+
sudo apt-get update && sudo apt-get install -y python3 python3-venv python3-pip
|
|
74
|
+
elif command -v yum &> /dev/null; then
|
|
75
|
+
sudo yum install -y python3
|
|
76
|
+
elif command -v pacman &> /dev/null; then
|
|
77
|
+
sudo pacman -S python python-pip
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if command -v python3 &> /dev/null; then
|
|
82
|
+
echo -e "\033[32mPython installed: $(python3 --version)\033[0m"
|
|
83
|
+
else
|
|
84
|
+
echo -e "\033[33mPython installation failed (optional)\033[0m"
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
echo ""
|
|
88
|
+
|
|
89
|
+
# ============ Check/Install uv ============
|
|
90
|
+
echo -e "\033[36mChecking uv...\033[0m"
|
|
91
|
+
if command -v uv &> /dev/null; then
|
|
92
|
+
echo -e "\033[32muv installed: $(uv --version)\033[0m"
|
|
93
|
+
else
|
|
94
|
+
echo -e "\033[33muv not found, installing...\033[0m"
|
|
95
|
+
|
|
96
|
+
if [[ "$OS" == "macOS" ]] || [[ "$OS" == "Linux" ]]; then
|
|
97
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
98
|
+
source $HOME/.local/bin/env 2>/dev/null || true
|
|
99
|
+
|
|
100
|
+
if command -v uv &> /dev/null; then
|
|
101
|
+
echo -e "\033[32muv installed: $(uv --version)\033[0m"
|
|
102
|
+
else
|
|
103
|
+
echo -e "\033[33muv installation failed (optional)\033[0m"
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
echo ""
|
|
108
|
+
|
|
109
|
+
# ============ Install Foliko ============
|
|
110
|
+
echo -e "\033[36mInstalling Foliko...\033[0m"
|
|
111
|
+
npm install -g foliko
|
|
112
|
+
|
|
113
|
+
if command -v foliko &> /dev/null; then
|
|
114
|
+
echo ""
|
|
115
|
+
echo -e "\033[32mInstallation complete!\033[0m"
|
|
116
|
+
echo "Run: foliko chat"
|
|
117
|
+
else
|
|
118
|
+
echo ""
|
|
119
|
+
echo -e "\033[31mInstallation failed. Try manually: npm install -g foliko\033[0m"
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foliko",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.23",
|
|
4
4
|
"description": "简约的插件化 Agent 框架",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -51,20 +51,18 @@
|
|
|
51
51
|
"author": "",
|
|
52
52
|
"license": "MIT",
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@ai-sdk/anthropic": "^3.0.58",
|
|
55
54
|
"@ai-sdk/mcp": "^1.0.25",
|
|
56
|
-
"@ai-sdk/openai": "^3.0.41",
|
|
57
|
-
"@ai-sdk/openai-compatible": "^2.0.35",
|
|
58
55
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
59
56
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
60
57
|
"@chnak/qq-bot": "1.0.7",
|
|
61
58
|
"@chnak/weixin-bot": "^1.2.9",
|
|
62
59
|
"@chnak/zod-to-markdown": "1.0.7",
|
|
63
60
|
"@earendil-works/pi-tui": "^0.75.3",
|
|
61
|
+
"@frsource/frs-replace": "^5.1.117",
|
|
64
62
|
"@hono/node-server": "^1.19.11",
|
|
65
63
|
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
66
64
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
67
|
-
"
|
|
65
|
+
"openai": "^6.46.0",
|
|
68
66
|
"chalk": "^5.6.2",
|
|
69
67
|
"cli-highlight": "^2.1.11",
|
|
70
68
|
"croner": "^9.1.0",
|
|
@@ -272,12 +272,11 @@ function buildSchemaFromType(type, desc, defaultValue) {
|
|
|
272
272
|
*/
|
|
273
273
|
function buildInputSchemaFromOptions(options) {
|
|
274
274
|
if (!Array.isArray(options) || options.length === 0) {
|
|
275
|
-
|
|
275
|
+
// 返回 zod schema(即使是空的也返回),确保 _doExecute 走校验路径
|
|
276
|
+
return require('zod').z.object({}).passthrough();
|
|
276
277
|
}
|
|
277
278
|
|
|
278
|
-
const
|
|
279
|
-
const required = [];
|
|
280
|
-
|
|
279
|
+
const shape = {};
|
|
281
280
|
for (const opt of options) {
|
|
282
281
|
const name = extractOptionName(opt.flags);
|
|
283
282
|
if (!name) continue;
|
|
@@ -287,33 +286,45 @@ function buildInputSchemaFromOptions(options) {
|
|
|
287
286
|
const desc = opt.description || '';
|
|
288
287
|
const defaultVal = opt.defaultValue;
|
|
289
288
|
|
|
290
|
-
|
|
289
|
+
// ★ 关键修复:返回 zod schema(不是纯 JSON Schema),
|
|
290
|
+
// 这样 _doExecute 里的 toolDef.inputSchema.safeParse 才会真的执行校验。
|
|
291
|
+
// 之前返回 JSON Schema 对象没有 .safeParse 方法,校验被完全跳过。
|
|
292
|
+
// 导致 LLM 传 args:{} 不会报错,直接到达工具 → args.id undefined。
|
|
293
|
+
const z = require('zod').z;
|
|
294
|
+
let zodField;
|
|
291
295
|
if (opt.schema && typeof opt.schema.parse === 'function') {
|
|
292
|
-
|
|
293
|
-
const s = opt.schema;
|
|
294
|
-
if (s._def.typeName === 'ZodNumber' || s._def.typeName === 'ZodInt') jsonType = 'number';
|
|
295
|
-
else if (s._def.typeName === 'ZodBoolean') jsonType = 'boolean';
|
|
296
|
-
else if (s._def.typeName === 'ZodArray') jsonType = 'array';
|
|
297
|
-
else if (s._def.typeName === 'ZodObject' || s._def.typeName === 'ZodRecord') jsonType = 'object';
|
|
298
|
-
} else if (opt.type) {
|
|
299
|
-
const L = opt.type.toLowerCase();
|
|
300
|
-
if (L === 'number' || L === 'int' || L === 'integer') jsonType = 'number';
|
|
301
|
-
else if (L === 'boolean' || L === 'bool') jsonType = 'boolean';
|
|
302
|
-
else if (L === 'array') jsonType = 'array';
|
|
303
|
-
else if (L === 'object') jsonType = 'object';
|
|
296
|
+
zodField = opt.schema;
|
|
304
297
|
} else {
|
|
305
|
-
|
|
298
|
+
const type = (opt.type || (hasValue ? 'string' : 'boolean')).toLowerCase();
|
|
299
|
+
if (type === 'number' || type === 'int' || type === 'integer') {
|
|
300
|
+
zodField = z.number();
|
|
301
|
+
} else if (type === 'boolean' || type === 'bool') {
|
|
302
|
+
zodField = z.boolean();
|
|
303
|
+
} else if (type === 'array') {
|
|
304
|
+
zodField = z.array(z.any());
|
|
305
|
+
} else if (type === 'object') {
|
|
306
|
+
zodField = z.object({}).passthrough();
|
|
307
|
+
} else {
|
|
308
|
+
zodField = z.string();
|
|
309
|
+
}
|
|
306
310
|
}
|
|
307
311
|
|
|
308
|
-
|
|
312
|
+
if (!isRequired) {
|
|
313
|
+
zodField = zodField.optional();
|
|
314
|
+
}
|
|
309
315
|
if (defaultVal !== undefined && defaultVal !== null) {
|
|
310
|
-
|
|
316
|
+
zodField = zodField.default(defaultVal);
|
|
311
317
|
}
|
|
312
|
-
|
|
313
|
-
|
|
318
|
+
// 描述里加 (必填) 标记,方便 LLM 看到
|
|
319
|
+
zodField = zodField.describe(desc + (isRequired ? '(必填)' : ''));
|
|
320
|
+
|
|
321
|
+
shape[name] = zodField;
|
|
314
322
|
}
|
|
315
323
|
|
|
316
|
-
|
|
324
|
+
if (Object.keys(shape).length === 0) {
|
|
325
|
+
return require('zod').z.object({}).passthrough();
|
|
326
|
+
}
|
|
327
|
+
return require('zod').z.object(shape).passthrough();
|
|
317
328
|
}
|
|
318
329
|
|
|
319
330
|
/**
|
|
@@ -26,6 +26,16 @@ class SubAgentPlugin extends Plugin {
|
|
|
26
26
|
this.llmConfig = config.llmConfig || null
|
|
27
27
|
this.hidden = config.hidden || false // 隐藏子Agent,不在指令系统中显示
|
|
28
28
|
|
|
29
|
+
// ★ 新增:frontmatter 之外的配置
|
|
30
|
+
// systemPrompt: 来自 markdown body(自动提取)
|
|
31
|
+
// skills: 来自 frontmatter 的 `skills: a, b, c` 字段,技能名数组
|
|
32
|
+
this.systemPrompt = config.systemPrompt || ''
|
|
33
|
+
this.skills = Array.isArray(config.skills)
|
|
34
|
+
? config.skills
|
|
35
|
+
: (typeof config.skills === 'string'
|
|
36
|
+
? config.skills.split(',').map(s => s.trim()).filter(Boolean)
|
|
37
|
+
: [])
|
|
38
|
+
|
|
29
39
|
this.config = {}
|
|
30
40
|
|
|
31
41
|
this._framework = null
|
|
@@ -266,6 +276,24 @@ class SubAgentPlugin extends Plugin {
|
|
|
266
276
|
}
|
|
267
277
|
|
|
268
278
|
_getFullSystemPrompt() {
|
|
279
|
+
// ★ 优先用 markdown body 提取的 systemPrompt(来自 _parseMarkdownConfig)
|
|
280
|
+
// 然后叠加 skills 列表(自动从 skill-manager 查询命令并注入)
|
|
281
|
+
const base = this._buildBaseSystemPrompt();
|
|
282
|
+
const skillsSection = this._buildSkillsSection();
|
|
283
|
+
if (skillsSection) {
|
|
284
|
+
return base + '\n\n' + skillsSection;
|
|
285
|
+
}
|
|
286
|
+
return base;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
_buildBaseSystemPrompt() {
|
|
290
|
+
// 1) 显式配置的 systemPrompt 优先
|
|
291
|
+
if (this.systemPrompt && this.systemPrompt.trim()) {
|
|
292
|
+
// 已经是 markdown body(含 "你是..." 这类角色定义)
|
|
293
|
+
const header = `# ${this.name}\n\n${this.description || ''}`.trim();
|
|
294
|
+
return `${header}\n\n${this.systemPrompt.trim()}`;
|
|
295
|
+
}
|
|
296
|
+
// 2) 回退到 subAgentConfigManager
|
|
269
297
|
const configManager = this._framework._subAgentConfigManager;
|
|
270
298
|
if (configManager) {
|
|
271
299
|
const config = configManager.get(this.name);
|
|
@@ -273,10 +301,43 @@ class SubAgentPlugin extends Plugin {
|
|
|
273
301
|
return config.getSystemPrompt();
|
|
274
302
|
}
|
|
275
303
|
}
|
|
276
|
-
|
|
304
|
+
// 3) 最后回退到 _buildSystemPrompt(旧逻辑)
|
|
277
305
|
return this._buildSystemPrompt();
|
|
278
306
|
}
|
|
279
307
|
|
|
308
|
+
/**
|
|
309
|
+
* 根据 this.skills 列表,从 skill-manager 查询每个 skill 的命令,
|
|
310
|
+
* 生成 "## 可用技能" section 注入 systemPrompt
|
|
311
|
+
* @private
|
|
312
|
+
*/
|
|
313
|
+
_buildSkillsSection() {
|
|
314
|
+
if (!this.skills || this.skills.length === 0) return '';
|
|
315
|
+
const extExecutor = this._framework?.pluginManager?.get('extension-executor');
|
|
316
|
+
if (!extExecutor) return '';
|
|
317
|
+
|
|
318
|
+
const lines = ['## 🛠️ 可用技能', ''];
|
|
319
|
+
let hasContent = false;
|
|
320
|
+
for (const skillName of this.skills) {
|
|
321
|
+
// 通过 ext_call 拿 skill 下的所有 tool
|
|
322
|
+
const tools = extExecutor.getExtensionTools(`skill:${skillName}`) || [];
|
|
323
|
+
if (tools.length === 0) {
|
|
324
|
+
lines.push(`### ${skillName}`);
|
|
325
|
+
lines.push('(该技能尚未注册或无可用命令)');
|
|
326
|
+
lines.push('');
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
hasContent = true;
|
|
330
|
+
lines.push(`### ${skillName}`);
|
|
331
|
+
lines.push('通过 ext_call 调用,格式:`ext_call({plugin: "skill:' + skillName + '", tool: "<命令>", args: {...}})`');
|
|
332
|
+
lines.push('');
|
|
333
|
+
for (const tool of tools) {
|
|
334
|
+
lines.push(`- **${tool.name}**: ${tool.description || '(无描述)'}`);
|
|
335
|
+
}
|
|
336
|
+
lines.push('');
|
|
337
|
+
}
|
|
338
|
+
return hasContent ? lines.join('\n') : '';
|
|
339
|
+
}
|
|
340
|
+
|
|
280
341
|
_updateAgentSystemPrompt() {
|
|
281
342
|
if (!this._agent) return;
|
|
282
343
|
const systemPrompt = this._getFullSystemPrompt();
|
|
@@ -545,27 +606,55 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
545
606
|
_parseMarkdownConfig(filePath, defaultName) {
|
|
546
607
|
const content = fs.readFileSync(filePath, 'utf-8')
|
|
547
608
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
609
|
+
// ★ 修复:优先解析 frontmatter(如果文件以 --- 开头)
|
|
610
|
+
// 之前的 bug:先匹配第一个 ```json 代码块,导致 body 里的示例 JSON 被当作 config。
|
|
611
|
+
// 例:news-pipeline-agent.md 里有示例 ```json { "news_list": [...] } ```,
|
|
612
|
+
// 这个示例被当成 config 解析,结果没有 name 字段,注册失败。
|
|
613
|
+
let config = null
|
|
556
614
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
|
|
557
615
|
if (frontmatterMatch) {
|
|
558
616
|
const frontmatterContent = frontmatterMatch[1]
|
|
559
617
|
const parsed = this._parseYamlLike(frontmatterContent)
|
|
560
618
|
if (parsed && parsed.name) {
|
|
561
|
-
|
|
619
|
+
config = { name: parsed.name || defaultName, ...parsed }
|
|
562
620
|
}
|
|
621
|
+
// frontmatter 存在但没有 name,回退到 body JSON 块
|
|
563
622
|
}
|
|
564
623
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
624
|
+
// 回退:找第一个 JSON 代码块作为 config(仅当无 frontmatter 时)
|
|
625
|
+
if (!config) {
|
|
626
|
+
const jsonMatch = content.match(/```(?:json)?\s*\n([\s\S]*?)\n\s*```/)
|
|
627
|
+
if (jsonMatch) {
|
|
628
|
+
try {
|
|
629
|
+
const parsed = JSON.parse(jsonMatch[1].trim())
|
|
630
|
+
if (parsed && parsed.name) {
|
|
631
|
+
config = parsed
|
|
632
|
+
}
|
|
633
|
+
// JSON 块没有 name 字段 → 视为示例代码,跳过
|
|
634
|
+
} catch (err) {
|
|
635
|
+
// 解析失败也跳过
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// 最后回退:尝试匹配文件首行 key: value(不带 m flag,避免匹配 body 中间的内容)
|
|
641
|
+
if (!config) {
|
|
642
|
+
config = { name: defaultName }
|
|
643
|
+
const yamlMatch = content.match(/^(name|role|description|parentTools|tools):\s*(.+)$/)
|
|
644
|
+
if (yamlMatch) {
|
|
645
|
+
config[yamlMatch[1]] = yamlMatch[2].trim()
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ★ 新增:提取 markdown body 作为 systemPrompt
|
|
650
|
+
// 移除 frontmatter 后剩下的内容(去掉首个 # 标题和首尾空白)
|
|
651
|
+
let body = content
|
|
652
|
+
if (frontmatterMatch) {
|
|
653
|
+
body = content.slice(frontmatterMatch[0].length)
|
|
654
|
+
}
|
|
655
|
+
body = body.replace(/^\s*#\s+[^\n]*\n+/, '').trim()
|
|
656
|
+
if (body && body.length > 20) {
|
|
657
|
+
config.systemPrompt = body
|
|
569
658
|
}
|
|
570
659
|
|
|
571
660
|
return config
|