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,164 @@
1
+ /**
2
+ * sc — 代码审查优化的共享工具模块
3
+ *
4
+ * 从 index.js 和 tools/bridge.js 提取的重复代码
5
+ * 两处引用同一份,消除 ~600 行重复
6
+ *
7
+ * v1.1.0 — 2026-06-09 路径安全修复
8
+ */
9
+
10
+ import { validatePath } from '../security.js';
11
+
12
+ // ====== 配置常量 ======
13
+ const WORKER_MAX_FILES = 50;
14
+ const L0_MAX_FILES = 200;
15
+ const L0_MAX_SIZE_BYTES = 50 * 1024; // 50KB
16
+
17
+ // ====== L0 快车路径 ======
18
+
19
+ /**
20
+ * 🚀 L0 快车路径: 小文件(<200个/<50KB)直接 Node.js 原生 grep
21
+ * 比走 Worker 快 10x,返回与 Worker 搜索结果兼容的格式
22
+ *
23
+ * @param {string} keyword - 搜索关键词
24
+ * @param {string[]} files - 文件路径列表
25
+ * @param {function} [validateFn] - 可选的安全路径验证函数(默认使用 security.js validatePath)
26
+ * @returns {Promise<object|null>} 搜索结果对象或 null(不适合快车路径时)
27
+ */
28
+ export async function fastPathSearch(keyword, files, validateFn) {
29
+ if (!files || files.length === 0 || files.length >= L0_MAX_FILES) {
30
+ return null;
31
+ }
32
+
33
+ // 🔧 路径安全修复: 始终使用安全验证函数,不提供 fallback 到裸路径
34
+ const _validate = validateFn || validatePath;
35
+
36
+ const { stat, readFile } = await import('fs/promises');
37
+ let totalSize = 0;
38
+ const sizeChecked = [];
39
+
40
+ for (const f of files) {
41
+ try {
42
+ // 🔧 路径安全: 先 validatePath,再 stat,防止信息泄露
43
+ const safePath = await _validate(f);
44
+ const s = await stat(safePath);
45
+ sizeChecked.push({ file: f, safePath, size: s.size });
46
+ totalSize += s.size;
47
+ if (totalSize > L0_MAX_SIZE_BYTES) break;
48
+ } catch (err) {
49
+ console.warn(`[code-review-shared] stat文件失败 ${f}: ${err?.message || '未知错误'}`);
50
+ totalSize = L0_MAX_SIZE_BYTES + 1;
51
+ break;
52
+ }
53
+ }
54
+
55
+ if (totalSize > L0_MAX_SIZE_BYTES) {
56
+ return null;
57
+ }
58
+
59
+ const queryLower = keyword.toLowerCase();
60
+ let totalMatches = 0;
61
+ const results = [];
62
+
63
+ for (const { file, safePath } of sizeChecked) {
64
+ try {
65
+ const content = await readFile(safePath, 'utf-8');
66
+ const lines = content.split('\n');
67
+ const matches = [];
68
+
69
+ for (let i = 0; i < lines.length; i++) {
70
+ if (lines[i].toLowerCase().includes(queryLower)) {
71
+ matches.push({ line: i + 1, text: lines[i].trim().substring(0, 200) });
72
+ }
73
+ }
74
+
75
+ if (matches.length > 0) {
76
+ totalMatches += matches.length;
77
+ results.push({ file, matchCount: matches.length, matches });
78
+ }
79
+ } catch (e) {
80
+ results.push({ file, error: e.message });
81
+ }
82
+ }
83
+
84
+ return {
85
+ keyword,
86
+ total: totalMatches,
87
+ totalFiles: sizeChecked.length,
88
+ results,
89
+ l0FastPath: true,
90
+ };
91
+ }
92
+
93
+ // ====== 文件拆分与结果合并 ======
94
+
95
+ export function splitFiles(files, numChunks) {
96
+ if (numChunks <= 1) return [files];
97
+ const chunks = [];
98
+ const chunkSize = Math.min(Math.ceil(files.length / numChunks), WORKER_MAX_FILES);
99
+ for (let i = 0; i < files.length; i += chunkSize) {
100
+ chunks.push(files.slice(i, i + chunkSize));
101
+ }
102
+ return chunks;
103
+ }
104
+
105
+ export function mergeSearchResults(results, poolStats) {
106
+ const allResults = [];
107
+ let totalMatches = 0;
108
+ let totalFiles = 0;
109
+ let errorFiles = 0;
110
+
111
+ for (const r of results) {
112
+ if (r.results) {
113
+ for (const item of r.results) {
114
+ allResults.push(item);
115
+ totalMatches += item.matchCount || 0;
116
+ if (item.error) errorFiles++;
117
+ else totalFiles++;
118
+ }
119
+ }
120
+ }
121
+
122
+ allResults.sort((a, b) => (b.matchCount || 0) - (a.matchCount || 0));
123
+
124
+ return {
125
+ keyword: results[0]?.keyword || '',
126
+ totalMatches,
127
+ totalFiles,
128
+ errorFiles,
129
+ results: allResults,
130
+ poolStats,
131
+ };
132
+ }
133
+
134
+ export function mergeLogResults(results, poolStats) {
135
+ const allStats = [];
136
+ for (const r of results) {
137
+ if (r.stats) allStats.push(...r.stats);
138
+ }
139
+ return { stats: allStats, poolStats };
140
+ }
141
+
142
+ // ====== CLI 参数解析 ======
143
+
144
+ export function parseSearchArgs(args) {
145
+ const files = [];
146
+ let keyword = null;
147
+ let priority = 'normal';
148
+ for (let i = 0; i < args.length; i++) {
149
+ if (args[i] === '--priority') { priority = args[++i] || 'normal'; }
150
+ else if (!keyword) { keyword = args[i]; }
151
+ else { files.push(args[i]); }
152
+ }
153
+ return { keyword, files, priority };
154
+ }
155
+
156
+ export function parseLogArgs(args) {
157
+ const files = [];
158
+ let priority = 'normal';
159
+ for (let i = 0; i < args.length; i++) {
160
+ if (args[i] === '--priority') { priority = args[++i] || 'normal'; }
161
+ else { files.push(args[i]); }
162
+ }
163
+ return { files, priority };
164
+ }
package/lib/config.js ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * 🦞 sc v5.8+ — 统一模型配置读取
3
+ *
4
+ * 所有模型名统一从此文件读取,不再硬编码。
5
+ * 配置来源:openclaw.json → models.providers
6
+ *
7
+ * 用法:
8
+ * import { getDeepseekConfig, getEmbeddingConfig, getVisionConfig, getDefaultModel } from './lib/config.js';
9
+ */
10
+
11
+ import { readFile } from 'fs/promises';
12
+ import { join } from 'path';
13
+ import { homedir } from 'os';
14
+
15
+ // ====== Config cache ======
16
+ let _configCache = null;
17
+ let _configCacheTime = 0;
18
+ const CONFIG_CACHE_TTL = 30000;
19
+
20
+ /**
21
+ * 读取并缓存 openclaw.json
22
+ */
23
+ async function readOpenClawConfig() {
24
+ const now = Date.now();
25
+ if (_configCache && now - _configCacheTime < CONFIG_CACHE_TTL) {
26
+ return _configCache;
27
+ }
28
+ try {
29
+ const content = await readFile(join(homedir(), '.openclaw', 'openclaw.json'), 'utf-8');
30
+ _configCache = JSON.parse(content);
31
+ _configCacheTime = now;
32
+ } catch (err) {
33
+ console.warn(`[config] ⚠️ 读取openclaw.json失败: ${err?.message || '未知错误'}`);
34
+ _configCache = null;
35
+ _configCacheTime = now;
36
+ }
37
+ return _configCache;
38
+ }
39
+
40
+ /**
41
+ * 清除缓存(让下一次读取重新加载)
42
+ */
43
+ export function clearConfigCache() {
44
+ _configCache = null;
45
+ _configCacheTime = 0;
46
+ }
47
+
48
+ // ====== DeepSeek 聊天模型配置 ======
49
+
50
+ /**
51
+ * 获取 DeepSeek 聊天模型配置
52
+ * @returns {{ baseUrl: string, apiKey: string, defaultModel: string, models: Array }}
53
+ * 如果没有配置,defaultModel 返回 null
54
+ */
55
+ export async function getDeepseekConfig() {
56
+ const cfg = await readOpenClawConfig();
57
+ const provider = cfg?.models?.providers?.deepseek;
58
+ if (!provider || !provider.models || provider.models.length === 0) {
59
+ return {
60
+ baseUrl: 'https://api.deepseek.com/v1',
61
+ apiKey: getEnv('DEEPSEEK_API_KEY', ''),
62
+ defaultModel: null,
63
+ models: [],
64
+ };
65
+ }
66
+
67
+ // 第一个非 vision 的聊天模型作为默认
68
+ const modelList = provider.models || [];
69
+ const defaultModel = modelList.find(m => !m.id.startsWith('glm'))?.id || modelList[0]?.id || null;
70
+
71
+ return {
72
+ baseUrl: provider.baseUrl || 'https://api.deepseek.com/v1',
73
+ apiKey: provider.apiKey || getEnv('DEEPSEEK_API_KEY', ''),
74
+ defaultModel,
75
+ models: modelList,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * 获取 DeepSeek 默认聊天模型 ID
81
+ * 如果没有配置,返回 null
82
+ */
83
+ export async function getDefaultChatModel() {
84
+ const cfg = await getDeepseekConfig();
85
+ return cfg.defaultModel;
86
+ }
87
+
88
+ // ====== Embedding / Ollama 配置 ======
89
+
90
+ /**
91
+ * 获取 Embedding (Ollama) 模型配置
92
+ * @returns {{ baseUrl: string, embeddingModel: string, baseUrlChat: string }}
93
+ * 如果没有配置,embeddingModel 返回 null,baseUrl 返回默认 Ollama 地址
94
+ */
95
+ export async function getEmbeddingConfig() {
96
+ const cfg = await readOpenClawConfig();
97
+ const provider = cfg?.models?.providers?.embedding;
98
+
99
+ if (!provider) {
100
+ return {
101
+ baseUrl: 'http://127.0.0.1:11434',
102
+ embeddingModel: null,
103
+ baseUrlChat: 'http://127.0.0.1:11434',
104
+ };
105
+ }
106
+
107
+ const rawBaseUrl = provider.baseUrl || 'http://127.0.0.1:11434';
108
+ const baseUrl = rawBaseUrl.replace(/\/v1\/?$/, '').replace(/\/api\/?$/, '');
109
+
110
+ const modelList = provider.models || [];
111
+ const embeddingModel = modelList[0]?.id || null;
112
+
113
+ return {
114
+ baseUrl,
115
+ embeddingModel,
116
+ baseUrlChat: baseUrl,
117
+ };
118
+ }
119
+
120
+ // ====== 视觉模型配置 ======
121
+
122
+ /**
123
+ * 获取视觉模型配置
124
+ */
125
+ export async function getVisionConfig() {
126
+ const cfg = await readOpenClawConfig();
127
+
128
+ const visionProvider = cfg?.models?.providers?.vision;
129
+ if (visionProvider && visionProvider.models && visionProvider.models.length > 0) {
130
+ const rawBaseUrl = visionProvider.baseUrl || '';
131
+ const baseUrl = rawBaseUrl.replace(/\/v1\/?$/, '').replace(/\/api\/?$/, '');
132
+ const model = visionProvider.models[0]?.id || null;
133
+ return {
134
+ baseUrl,
135
+ model,
136
+ configured: true,
137
+ provider: 'vision',
138
+ apiKey: visionProvider.apiKey || '',
139
+ api: visionProvider.api || 'openai-completions',
140
+ };
141
+ }
142
+
143
+ const embedProvider = cfg?.models?.providers?.embedding;
144
+ if (embedProvider) {
145
+ const rawBaseUrl = embedProvider.baseUrl || 'http://127.0.0.1:11434';
146
+ const baseUrl = rawBaseUrl.replace(/\/v1\/?$/, '').replace(/\/api\/?$/, '');
147
+ return {
148
+ baseUrl,
149
+ model: null,
150
+ configured: false,
151
+ provider: 'embedding',
152
+ apiKey: '',
153
+ note: '视觉模型未配置,使用 Ollama embedding provider 地址',
154
+ };
155
+ }
156
+
157
+ return {
158
+ baseUrl: '',
159
+ model: null,
160
+ configured: false,
161
+ provider: '',
162
+ note: '视觉模型未配置,请在 openclaw.json 中添加 models.providers.vision 配置',
163
+ };
164
+ }