openclaw-sc 5.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.env.example +13 -0
  2. package/INSTALL.md +13 -0
  3. package/LICENSE +233 -0
  4. package/README.md +123 -0
  5. package/SECURITY.md +27 -0
  6. package/index.js +3479 -0
  7. package/lib/code-review-shared.js +164 -0
  8. package/lib/config.js +164 -0
  9. package/lib/constants.js +438 -0
  10. package/lib/decomposer.js +896 -0
  11. package/lib/dialog-recall.js +389 -0
  12. package/lib/env.js +59 -0
  13. package/lib/level-rules.js +72 -0
  14. package/lib/log-manager.js +258 -0
  15. package/lib/preemption.js +278 -0
  16. package/lib/priority-calc.js +134 -0
  17. package/lib/prompt-injection.js +748 -0
  18. package/lib/route-evidence.js +701 -0
  19. package/lib/shared-fs.js +244 -0
  20. package/lib/steward-rules.js +648 -0
  21. package/lib/task-center.js +602 -0
  22. package/lib/task-chain.js +713 -0
  23. package/lib/task-checker.js +317 -0
  24. package/lib/task-profiles.js +255 -0
  25. package/lib/tcell.js +411 -0
  26. package/lib/tool-auto-discover.js +522 -0
  27. package/lib/tool-enrich-subagent.js +375 -0
  28. package/lib/tool-handlers.js +178 -0
  29. package/lib/tool-selector.js +459 -0
  30. package/openclaw.plugin.json +55 -0
  31. package/package.json +63 -0
  32. package/security.js +141 -0
  33. package/tools/bridge.js +3288 -0
  34. package/tools/dashboard/tk-dashboard.py +262 -0
  35. package/tools/hippocampus-multi-search.js +1038 -0
  36. package/tools/hippocampus-sqlite.js +738 -0
  37. package/tools/hippocampus-store.js +262 -0
  38. package/tools/mcp-tools.config.json +653 -0
  39. package/tools/pipeline-engine.js +667 -0
  40. package/tools/sidecar/_check_tools.py +34 -0
  41. package/tools/sidecar/sidecar-server.cjs +1360 -0
  42. package/tools/sidecar/subagent-runner.cjs +1471 -0
  43. package/tools/system-tools.js +619 -0
  44. package/vector/README.md +100 -0
  45. package/vector/usearch-bridge.js +639 -0
  46. package/vector/usearch-http.js +74 -0
  47. package/vector/usearch-serve.js +156 -0
  48. package/workers/worker.js +1419 -0
@@ -0,0 +1,317 @@
1
+ /**
2
+ * 🔍 提示词质量检查 — Task Specificity Checker
3
+ *
4
+ * 检测子agent任务描述是否过于具体(过度约束),
5
+ * 导致子agent不会变通、机械执行。
6
+ *
7
+ * 三级检测:
8
+ * L1 — 步骤关键词("步骤1""首先""然后" 等)
9
+ * L2 — 具体命令("exec""git clone""npm install" 等)
10
+ * L3 — 结构分析(步骤段落字数 vs 目标段落字数)
11
+ *
12
+ * checkTaskOverSpecificity(taskDescription):
13
+ * 返回 { level: "safe"|"warn", reasons: [string], suggestion: string }
14
+ */
15
+
16
+ // ====== L1 检测:步骤关键词 ======
17
+
18
+ /** 步骤指示词正则 */
19
+ const L1_STEP_PATTERNS = [
20
+ // 中文步骤
21
+ /\b步骤\s*[0-9一二三四五六七八九十零]+\b/u,
22
+ /\b第[一二三四五六七八九十零]+步\b/u,
23
+ // 中文顺序词
24
+ /\b首先\b/u,
25
+ /\b然后\b/u,
26
+ /\b接着\b/u,
27
+ /\b最后\b/u,
28
+ /\b先\b[\s\S]{0,20}再\b/u,
29
+ // 中文明确编号步骤
30
+ /^\s*[0-9一二三四五六七八九十零]+[.、.、\s]/mu,
31
+ /^(第一步|第二步|第三步|第四步|第五步|第六步|第七步|第八步|第九步)/mu,
32
+ // 英文步骤
33
+ /\b(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|fourth(?:ly)?|fifth(?:ly)?)\b/i,
34
+ /\bstep\s+[0-9]+\b/i,
35
+ /\b(?:then|next|finally|afterwards|subsequently)\b/i,
36
+ // 短句步骤标记
37
+ /^\s*[0-9]+[\.\)]\s+/mu,
38
+ ];
39
+
40
+ /** 过度步骤关键词计数阈值 */
41
+ const L1_THRESHOLD = 3;
42
+
43
+ /**
44
+ * L1检测:步骤关键词
45
+ * @param {string} text
46
+ * @returns {string[]} 匹配到的原因列表
47
+ */
48
+ function detectL1(text) {
49
+ const reasons = [];
50
+ let matchCount = 0;
51
+ const seen = new Set();
52
+
53
+ for (const re of L1_STEP_PATTERNS) {
54
+ let match;
55
+ re.lastIndex = 0;
56
+ while ((match = re.exec(text)) !== null) {
57
+ const kw = match[0].trim();
58
+ if (!seen.has(kw)) {
59
+ seen.add(kw);
60
+ matchCount++;
61
+ }
62
+ // 防止无限循环
63
+ if (match.index === re.lastIndex) re.lastIndex++;
64
+ }
65
+ }
66
+
67
+ if (matchCount >= L1_THRESHOLD) {
68
+ reasons.push(`检测到 ${matchCount} 个步骤指示词(阈值: ${L1_THRESHOLD}),可能过度指定执行步骤`);
69
+ } else if (matchCount > 0) {
70
+ // 少量步骤词不报 warn,但记录观察
71
+ return reasons;
72
+ }
73
+
74
+ return reasons;
75
+ }
76
+
77
+ // ====== L2 检测:具体命令 ======
78
+
79
+ const L2_COMMAND_PATTERNS = [
80
+ // Shell 命令
81
+ /\bexec\b/,
82
+ /\bgit\s+clone\b/,
83
+ /\bgit\s+checkout\b/,
84
+ /\bgit\s+pull\b/,
85
+ /\bgit\s+push\b/,
86
+ /\bnpm\s+install\b/,
87
+ /\bnpm\s+run\b/,
88
+ /\bnpx\b/,
89
+ /\byarn\s+(add|install|run)\b/,
90
+ /\bpip\s+install\b/,
91
+ /\bcd\s+/,
92
+ /\bmkdir\s+[\-p]?\s*-p\b/,
93
+ /\btouch\s+/,
94
+ /\brm\s+[\-rf]?\s*-rf\b/,
95
+ /\bcp\s+[\-rf]?\s*-r\b/,
96
+ /\bmv\s+/,
97
+ /\bchmod\s+/,
98
+ /\bsed\s+/,
99
+ /\bgrep\s+/,
100
+ /\bawk\s+/,
101
+ /\bcat\s+/,
102
+ /\becho\s+/,
103
+ /\bcurl\s+/,
104
+ /\bwget\s+/,
105
+ // 编程操作
106
+ /\bimport\s+\{/,
107
+ /\brequire\s*\(/,
108
+ /\bfs\.readFileSync\b/,
109
+ /\bwriteFile\s*\(/,
110
+ /\bPromise\.all\b/,
111
+ // 路径指定
112
+ /[A-Za-z]:\\[\\\w\-\.]+/,
113
+ /\.\.\/|\.\//,
114
+ // 工具调用
115
+ /\bcpu_/,
116
+ /\bsessions_spawn\b/,
117
+ /\bmemory_get\b/,
118
+ /\btavily_search\b/,
119
+ /\bweb_fetch\b/,
120
+ // 系统操作
121
+ /\bStop-Process\b/,
122
+ /\bStart-Process\b/,
123
+ /\bGet-ChildItem\b/,
124
+ /\bSet-Content\b/,
125
+ /\bAdd-Content\b/,
126
+ /\bschtasks\b/,
127
+ ];
128
+
129
+ /** L2 命令检测阈值 */
130
+ const L2_THRESHOLD = 2;
131
+
132
+ /**
133
+ * L2检测:具体命令
134
+ * @param {string} text
135
+ * @returns {string[]} 匹配到的原因列表
136
+ */
137
+ function detectL2(text) {
138
+ const reasons = [];
139
+ let matchCount = 0;
140
+ const seenCmds = new Set();
141
+
142
+ for (const re of L2_COMMAND_PATTERNS) {
143
+ let match;
144
+ re.lastIndex = 0;
145
+ while ((match = re.exec(text)) !== null) {
146
+ const cmd = match[0].trim().substring(0, 30);
147
+ if (!seenCmds.has(cmd)) {
148
+ seenCmds.add(cmd);
149
+ matchCount++;
150
+ }
151
+ if (match.index === re.lastIndex) re.lastIndex++;
152
+ }
153
+ }
154
+
155
+ if (matchCount >= L2_THRESHOLD) {
156
+ reasons.push(`检测到 ${matchCount} 个具体命令(阈值: ${L2_THRESHOLD}),可能过度指定实现方式`);
157
+ }
158
+
159
+ return reasons;
160
+ }
161
+
162
+ // ====== L3 结构分析 ======
163
+
164
+ /**
165
+ * 估算段落是"步骤说明"还是"目标说明"
166
+ * 步骤说明:含具体操作动词、编号、命令
167
+ * 目标说明:含目标、目的、背景、需求描述
168
+ */
169
+ function estimateParagraphType(paragraph) {
170
+ const stepIndicators = [
171
+ /\b步骤/u, /\b第.*步/u, /\b首先\b/u, /\b然后\b/u, /\b(?:exec|install|clone|mkdir|write|create)\b/i,
172
+ /^\s*[0-9][\.\)]/m, /\bgit\b/, /\bnpm\b/, /\bcd \b/, /\brun\b/i, /\b执行\b/u, /\b调用\b/u,
173
+ ];
174
+ const goalIndicators = [
175
+ /\b目标\b/u, /\b目的\b/u, /\b背景\b/u, /\b需求\b/u, /\b实现\b/u, /\b任务[::]\b/u,
176
+ /\bgoal\b/i, /\bpurpose\b/i, /\bobjective\b/i, /\boverview\b/i, /\bsummary\b/i,
177
+ /\b的作用\b/u, /\b用于\b/u, /\b需求是\b/u, /\b需要实现\b/u,
178
+ ];
179
+
180
+ let stepScore = 0;
181
+ let goalScore = 0;
182
+ const lines = paragraph.split('\n').filter(l => l.trim());
183
+
184
+ for (const line of lines) {
185
+ for (const re of stepIndicators) {
186
+ if (re.test(line)) stepScore++;
187
+ }
188
+ for (const re of goalIndicators) {
189
+ if (re.test(line)) goalScore++;
190
+ }
191
+ }
192
+
193
+ return { stepScore, goalScore, type: stepScore > goalScore ? 'step' : 'goal' };
194
+ }
195
+
196
+ /**
197
+ * L3检测:步骤段落 vs 目标段落比例
198
+ * @param {string} text
199
+ * @returns {string[]} 原因列表
200
+ */
201
+ function detectL3(text) {
202
+ const reasons = [];
203
+ const paragraphs = text.split('\n\n').filter(p => p.trim().length > 20);
204
+
205
+ if (paragraphs.length < 2) {
206
+ return reasons; // 单段落无法做结构比较
207
+ }
208
+
209
+ let stepWordCount = 0;
210
+ let goalWordCount = 0;
211
+ let stepParaCount = 0;
212
+ let goalParaCount = 0;
213
+ let totalWords = 0;
214
+
215
+ for (const para of paragraphs) {
216
+ const { type } = estimateParagraphType(para);
217
+ const paraWords = para.replace(/[\s]/g, '').length; // 去空白算中文字符或有效字符
218
+
219
+ if (type === 'step') {
220
+ stepWordCount += paraWords;
221
+ stepParaCount++;
222
+ } else {
223
+ goalWordCount += paraWords;
224
+ goalParaCount++;
225
+ }
226
+ totalWords += paraWords;
227
+ }
228
+
229
+ if (totalWords === 0) return reasons;
230
+
231
+ const stepRatio = stepWordCount / Math.max(totalWords, 1);
232
+ const goalRatio = goalWordCount / Math.max(totalWords, 1);
233
+
234
+ // 步骤段落占比超过 60% → 警告
235
+ if (stepRatio > 0.6 && stepParaCount >= 2) {
236
+ reasons.push(
237
+ `步骤说明占比 ${(stepRatio * 100).toFixed(0)}%(目标说明仅 ${(goalRatio * 100).toFixed(0)}%),` +
238
+ `任务描述偏向"怎么做"而非"做什么"`
239
+ );
240
+ }
241
+
242
+ // 步骤段落数远超目标段落数
243
+ if (stepParaCount >= 3 && (stepParaCount / Math.max(goalParaCount, 1)) > 2) {
244
+ reasons.push(
245
+ `步骤段落 ${stepParaCount} 段是目标段落 ${goalParaCount} 段的 ${(stepParaCount / Math.max(goalParaCount, 1)).toFixed(1)} 倍` +
246
+ `,过度关注执行细节`
247
+ );
248
+ }
249
+
250
+ return reasons;
251
+ }
252
+
253
+ // ====== 建议生成 ======
254
+
255
+ /**
256
+ * 根据检测原因生成建议
257
+ * @param {string[]} reasons
258
+ * @returns {string} 建议内容
259
+ */
260
+ function generateSuggestion(reasons) {
261
+ if (reasons.length === 0) return '';
262
+
263
+ const suggestions = [];
264
+
265
+ if (reasons.some(r => r.includes('步骤指示词'))) {
266
+ suggestions.push('减少步骤关键词("首先""然后""步骤X"),改为描述目标而非过程');
267
+ }
268
+ if (reasons.some(r => r.includes('具体命令'))) {
269
+ suggestions.push('用"使用XX工具"替代具体命令,让子agent自行选择实现方式');
270
+ }
271
+ if (reasons.some(r => r.includes('步骤说明占比'))) {
272
+ suggestions.push('增加"目标/目的/背景"说明,压缩"怎么做"部分的篇幅');
273
+ }
274
+
275
+ if (suggestions.length > 0) {
276
+ return '🔧 建议:' + suggestions.join(';');
277
+ }
278
+
279
+ return '建议减少过程指定,增加目标说明,给子agent更多自主空间';
280
+ }
281
+
282
+ // ====== 主入口 ======
283
+
284
+ /**
285
+ * 检查任务描述是否过度具体化
286
+ *
287
+ * @param {string} taskDescription - 任务描述文本
288
+ * @returns {{ level: "safe"|"warn", reasons: string[], suggestion: string }}
289
+ *
290
+ * level:
291
+ * "safe" — 不过度具体,适合子agent灵活执行
292
+ * "warn" — 过度具体,建议放宽约束
293
+ *
294
+ * reasons: 检测到的具体问题列表
295
+ * suggestion: 改善建议(level="warn" 时有值)
296
+ */
297
+ export function checkTaskOverSpecificity(taskDescription) {
298
+ if (!taskDescription || typeof taskDescription !== 'string' || taskDescription.trim().length === 0) {
299
+ return { level: 'safe', reasons: [], suggestion: '' };
300
+ }
301
+
302
+ const allReasons = [
303
+ ...detectL1(taskDescription),
304
+ ...detectL2(taskDescription),
305
+ ...detectL3(taskDescription),
306
+ ];
307
+
308
+ if (allReasons.length === 0) {
309
+ return { level: 'safe', reasons: [], suggestion: '' };
310
+ }
311
+
312
+ return {
313
+ level: 'warn',
314
+ reasons: allReasons,
315
+ suggestion: generateSuggestion(allReasons),
316
+ };
317
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * 🧬 sc 任务配置文件 — 工具类型→并发/模型/thinking 映射
3
+ *
4
+ * 核心设计:
5
+ * 1. 所有模型名从 openclaw.json 动态读取(agents.defaults.model.primary),不写死任何模型ID
6
+ * 2. 按任务类别划分:搜索/编码/分析/配置/批量/视觉/系统
7
+ * 3. 每种类别定义推荐并发度、batchSize、maxWorkers、thinking预算
8
+ * 4. 导出 getTaskProfile(toolName) 和 core_pickSubagentModel(toolName)
9
+ *
10
+ * v1.1 — 2026-06-09 空catch修复 + 类型安全
11
+ */
12
+
13
+ import { readFile } from 'fs/promises';
14
+ import { join } from 'path';
15
+ import { homedir } from 'os';
16
+
17
+ // ====== Config cache ======
18
+ /** @type {object|null} */
19
+ let _configCache = null;
20
+ let _configCacheTime = 0;
21
+ const CONFIG_CACHE_TTL = 30000;
22
+
23
+ /**
24
+ * 读取并缓存 openclaw.json
25
+ * @returns {Promise<object|null>} 配置对象或 null
26
+ */
27
+ async function readOpenClawConfig() {
28
+ const now = Date.now();
29
+ if (_configCache && now - _configCacheTime < CONFIG_CACHE_TTL) return _configCache;
30
+ try {
31
+ const content = await readFile(join(homedir(), '.openclaw', 'openclaw.json'), 'utf-8');
32
+ _configCache = JSON.parse(content);
33
+ _configCacheTime = now;
34
+ } catch (err) {
35
+ // 🔧 空catch修复: 记录读取/解析失败原因
36
+ if (typeof console !== 'undefined' && console.warn) {
37
+ const reason = err?.code === 'ENOENT' ? '配置文件不存在' : (err?.message || '未知错误');
38
+ console.warn(`[task-profiles] 读取 openclaw.json 失败: ${reason}`);
39
+ }
40
+ _configCache = null;
41
+ _configCacheTime = now;
42
+ }
43
+ return _configCache;
44
+ }
45
+
46
+ /**
47
+ * 清除配置缓存(让下一次读取重新加载)
48
+ */
49
+ export function clearProfileCache() {
50
+ _configCache = null;
51
+ _configCacheTime = 0;
52
+ }
53
+
54
+ /**
55
+ * 从系统配置读取 primary 模型
56
+ * @returns {Promise<{full: string|null, provider: string|null, model: string|null}>}
57
+ */
58
+ async function getPrimaryModel() {
59
+ const cfg = await readOpenClawConfig();
60
+ if (!cfg) return { full: null, provider: null, model: null };
61
+ const modelStr = cfg?.agents?.defaults?.model?.primary || null;
62
+ if (!modelStr) return { full: null, provider: null, model: null };
63
+ const parts = modelStr.split('/');
64
+ return {
65
+ full: modelStr,
66
+ provider: parts[0] || null,
67
+ model: parts[1] || parts[0],
68
+ };
69
+ }
70
+
71
+ // ====== 任务类别枚举 ======
72
+ export const TASK_CATEGORIES = {
73
+ SEARCH: 'search',
74
+ CODING: 'coding',
75
+ ANALYSIS: 'analysis',
76
+ CONFIG: 'config',
77
+ BATCH: 'batch',
78
+ VISION: 'vision',
79
+ SYSTEM: 'system',
80
+ };
81
+
82
+ // ====== 工具名 → 类别映射 ======
83
+ const TOOL_CATEGORY_MAP = {
84
+ // 搜索查询类
85
+ cpu_search: TASK_CATEGORIES.SEARCH,
86
+ cpu_scan: TASK_CATEGORIES.SEARCH,
87
+ cpu_semanticSearch: TASK_CATEGORIES.SEARCH,
88
+ cpu_dialogRecall: TASK_CATEGORIES.SEARCH,
89
+
90
+ // 编码类
91
+ cpu_codeEdit: TASK_CATEGORIES.CODING,
92
+ cpu_codeReview: TASK_CATEGORIES.CODING,
93
+ cpu_bugFix: TASK_CATEGORIES.CODING,
94
+
95
+ // 分析类
96
+ cpu_diff: TASK_CATEGORIES.ANALYSIS,
97
+ cpu_diagnose: TASK_CATEGORIES.ANALYSIS,
98
+ cpu_research: TASK_CATEGORIES.ANALYSIS,
99
+ cpu_orchestrate: TASK_CATEGORIES.ANALYSIS,
100
+
101
+ // 配置类
102
+ cpu_resolveModel: TASK_CATEGORIES.CONFIG,
103
+
104
+ // 批量调度类
105
+ cpu_batch: TASK_CATEGORIES.BATCH,
106
+ core_dispatch: TASK_CATEGORIES.BATCH,
107
+
108
+ // 视觉分析类
109
+ core_batchVision: TASK_CATEGORIES.VISION,
110
+
111
+ // 系统管理类
112
+ core_stats: TASK_CATEGORIES.SYSTEM,
113
+ core_routeTask: TASK_CATEGORIES.SYSTEM,
114
+ cpu_routeEvidence: TASK_CATEGORIES.SYSTEM,
115
+ cpu_evolution: TASK_CATEGORIES.SYSTEM,
116
+ cpu_chainDetect: TASK_CATEGORIES.SYSTEM,
117
+ cpu_cerebellumStatus: TASK_CATEGORIES.SYSTEM,
118
+ cpu_monitorSubagents: TASK_CATEGORIES.SYSTEM,
119
+ core_backup: TASK_CATEGORIES.SYSTEM,
120
+ core_about: TASK_CATEGORIES.SYSTEM,
121
+ core_emergencyStop: TASK_CATEGORIES.SYSTEM,
122
+ cpu_compressTask: TASK_CATEGORIES.SYSTEM,
123
+ };
124
+
125
+ // ====== 各类别推荐配置 ======
126
+ const CATEGORY_PROFILES = {
127
+ [TASK_CATEGORIES.SEARCH]: {
128
+ concurrency: 8,
129
+ batchSize: 5,
130
+ maxWorkers: 28,
131
+ thinking: 'off',
132
+ label: '搜索查询类',
133
+ },
134
+ [TASK_CATEGORIES.CODING]: {
135
+ concurrency: 4,
136
+ batchSize: 3,
137
+ maxWorkers: 16,
138
+ thinking: 'high',
139
+ label: '编码类',
140
+ },
141
+ [TASK_CATEGORIES.ANALYSIS]: {
142
+ concurrency: 4,
143
+ batchSize: 4,
144
+ maxWorkers: 8,
145
+ thinking: 'medium',
146
+ label: '分析类',
147
+ },
148
+ [TASK_CATEGORIES.CONFIG]: {
149
+ concurrency: 2,
150
+ batchSize: 1,
151
+ maxWorkers: 4,
152
+ thinking: 'off',
153
+ label: '配置类',
154
+ },
155
+ [TASK_CATEGORIES.BATCH]: {
156
+ concurrency: 6,
157
+ batchSize: 4,
158
+ maxWorkers: 20,
159
+ thinking: 'off',
160
+ label: '批量调度类',
161
+ },
162
+ [TASK_CATEGORIES.VISION]: {
163
+ concurrency: 3,
164
+ batchSize: 3,
165
+ maxWorkers: 6,
166
+ thinking: 'low',
167
+ label: '视觉分析类',
168
+ },
169
+ [TASK_CATEGORIES.SYSTEM]: {
170
+ concurrency: 2,
171
+ batchSize: 1,
172
+ maxWorkers: 3,
173
+ thinking: 'off',
174
+ label: '系统管理类',
175
+ },
176
+ };
177
+
178
+ /**
179
+ * 获取工具的推荐配置
180
+ * @param {string} toolName - 工具名,如 'core_search'
181
+ * @returns {{ category: string, concurrency: number, batchSize: number, maxWorkers: number, thinking: string, label: string }}
182
+ */
183
+ export function getTaskProfile(toolName) {
184
+ const category = TOOL_CATEGORY_MAP[toolName] || TASK_CATEGORIES.SYSTEM;
185
+ const profile = CATEGORY_PROFILES[category] || CATEGORY_PROFILES[TASK_CATEGORIES.SYSTEM];
186
+ return {
187
+ category,
188
+ ...profile,
189
+ };
190
+ }
191
+
192
+ /**
193
+ * 获取工具名对应的类别
194
+ * @param {string} toolName
195
+ * @returns {string}
196
+ */
197
+ export function getTaskCategory(toolName) {
198
+ return TOOL_CATEGORY_MAP[toolName] || TASK_CATEGORIES.SYSTEM;
199
+ }
200
+
201
+ /**
202
+ * 获取所有类别定义
203
+ * @returns {Object}
204
+ */
205
+ export function getCategoryProfiles() {
206
+ return { ...CATEGORY_PROFILES };
207
+ }
208
+
209
+ /**
210
+ * 获取工具→类别映射
211
+ * @returns {Object}
212
+ */
213
+ export function getToolCategoryMap() {
214
+ return { ...TOOL_CATEGORY_MAP };
215
+ }
216
+
217
+ /**
218
+ * 🧠 为子 agent 选取推荐模型和 thinking 级别
219
+ *
220
+ * 所有模型名从系统配置(openclaw.json → agents.defaults.model.primary)动态读取,
221
+ * 不写死任何模型ID。
222
+ *
223
+ * 规则:
224
+ * - 搜索类 → thinking=off
225
+ * - 编码类 → thinking=high
226
+ * - 分析类 → thinking=low
227
+ * - 视觉类 → thinking=low
228
+ * - 其他类 → thinking=off
229
+ *
230
+ * @param {string} toolName - 工具名
231
+ * @returns {Promise<{ model: string|null, provider: string|null, modelId: string|null, thinking: string, category: string, label: string }>}
232
+ */
233
+ export async function core_pickSubagentModel(toolName) {
234
+ const primary = await getPrimaryModel();
235
+ const profile = getTaskProfile(toolName);
236
+
237
+ return {
238
+ model: primary.full || null,
239
+ provider: primary.provider,
240
+ modelId: primary.model,
241
+ thinking: profile.thinking,
242
+ category: profile.category,
243
+ label: profile.label,
244
+ };
245
+ }
246
+
247
+ export default {
248
+ getTaskProfile,
249
+ getTaskCategory,
250
+ getCategoryProfiles,
251
+ getToolCategoryMap,
252
+ core_pickSubagentModel,
253
+ clearProfileCache,
254
+ TASK_CATEGORIES,
255
+ };