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.
- package/.env.example +13 -0
- package/INSTALL.md +13 -0
- package/LICENSE +233 -0
- package/README.md +123 -0
- package/SECURITY.md +27 -0
- package/index.js +3479 -0
- package/lib/code-review-shared.js +164 -0
- package/lib/config.js +164 -0
- package/lib/constants.js +438 -0
- package/lib/decomposer.js +896 -0
- package/lib/dialog-recall.js +389 -0
- package/lib/env.js +59 -0
- package/lib/level-rules.js +72 -0
- package/lib/log-manager.js +258 -0
- package/lib/preemption.js +278 -0
- package/lib/priority-calc.js +134 -0
- package/lib/prompt-injection.js +748 -0
- package/lib/route-evidence.js +701 -0
- package/lib/shared-fs.js +244 -0
- package/lib/steward-rules.js +648 -0
- package/lib/task-center.js +602 -0
- package/lib/task-chain.js +713 -0
- package/lib/task-checker.js +317 -0
- package/lib/task-profiles.js +255 -0
- package/lib/tcell.js +411 -0
- package/lib/tool-auto-discover.js +522 -0
- package/lib/tool-enrich-subagent.js +375 -0
- package/lib/tool-handlers.js +178 -0
- package/lib/tool-selector.js +459 -0
- package/openclaw.plugin.json +55 -0
- package/package.json +63 -0
- package/security.js +141 -0
- package/tools/bridge.js +3288 -0
- package/tools/dashboard/tk-dashboard.py +262 -0
- package/tools/hippocampus-multi-search.js +1038 -0
- package/tools/hippocampus-sqlite.js +738 -0
- package/tools/hippocampus-store.js +262 -0
- package/tools/mcp-tools.config.json +653 -0
- package/tools/pipeline-engine.js +667 -0
- package/tools/sidecar/_check_tools.py +34 -0
- package/tools/sidecar/sidecar-server.cjs +1360 -0
- package/tools/sidecar/subagent-runner.cjs +1471 -0
- package/tools/system-tools.js +619 -0
- package/vector/README.md +100 -0
- package/vector/usearch-bridge.js +639 -0
- package/vector/usearch-http.js +74 -0
- package/vector/usearch-serve.js +156 -0
- package/workers/worker.js +1419 -0
package/lib/shared-fs.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sc — 共享文件系统操作
|
|
3
|
+
*
|
|
4
|
+
* 管理 shared/ 目录的读写、清理,以及 session 文件的清理。
|
|
5
|
+
* 所有写入走 tmp+rename 原子写(Copy-on-Write),确保 Worker 间 IPC 不会读到半截文件(torn read)。
|
|
6
|
+
* 读取使用 link+tmp 原子读(硬链接快照),读取时原文件被 rename/覆盖也不影响当前读取。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { readFile, mkdir, readdir, unlink, rename, stat, writeFile, link } from "fs/promises";
|
|
12
|
+
import { randomBytes } from "crypto";
|
|
13
|
+
import { SHARED_DIR, SESSION_DIR, SESSION_KEEP_RECENT, TASK_STATES_DIR } from './constants.js';
|
|
14
|
+
|
|
15
|
+
const PREEMPT_DIR = join(SHARED_DIR, 'preempt');
|
|
16
|
+
|
|
17
|
+
export async function ensureSharedDir() {
|
|
18
|
+
try { await mkdir(SHARED_DIR, { recursive: true }); } catch (err) {
|
|
19
|
+
console.warn(`[shared-fs] ⚠️ ensureSharedDir失败: ${err?.message || '未知错误'}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function sanitizeTaskName(name) {
|
|
24
|
+
if (!name || typeof name !== 'string') throw new Error('无效的 taskName');
|
|
25
|
+
const safe = name.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
26
|
+
if (safe !== name) throw new Error(`taskName 包含非法字符: ${name}`);
|
|
27
|
+
return safe;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function writeSharedResult(taskName, data) {
|
|
31
|
+
const safeName = sanitizeTaskName(taskName);
|
|
32
|
+
await ensureSharedDir();
|
|
33
|
+
const tmpSuffix = randomBytes(4).toString('hex');
|
|
34
|
+
const fp = join(SHARED_DIR, `${safeName}.json`);
|
|
35
|
+
const tmp = join(SHARED_DIR, `${safeName}.${tmpSuffix}.tmp`);
|
|
36
|
+
await writeFile(tmp, JSON.stringify({ taskName: safeName, ...data, timestamp: Date.now() }, null, 2), 'utf-8');
|
|
37
|
+
await rename(tmp, fp);
|
|
38
|
+
return fp;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function readSharedResult(taskName) {
|
|
42
|
+
const safeName = sanitizeTaskName(taskName);
|
|
43
|
+
const fp = join(SHARED_DIR, `${safeName}.json`);
|
|
44
|
+
const suffix = randomBytes(4).toString('hex');
|
|
45
|
+
const readingFp = join(SHARED_DIR, `${safeName}.${suffix}.reading.json`);
|
|
46
|
+
try {
|
|
47
|
+
// 硬链接原子读:创建硬链接后即使原文件被 rename/覆盖,链接依然指向旧 inode
|
|
48
|
+
await link(fp, readingFp);
|
|
49
|
+
const content = await readFile(readingFp, 'utf-8');
|
|
50
|
+
await unlink(readingFp).catch(() => {});
|
|
51
|
+
return JSON.parse(content);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
// 清理可能的硬链接残留
|
|
54
|
+
await unlink(readingFp).catch(() => {});
|
|
55
|
+
// 回退:直接读原始文件(可能被其他进程 rename 走了,但写入端会重建)
|
|
56
|
+
try {
|
|
57
|
+
const content = await readFile(fp, 'utf-8');
|
|
58
|
+
return JSON.parse(content);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function cleanupSharedDir(maxAgeMs = 3600000) {
|
|
66
|
+
try {
|
|
67
|
+
await ensureSharedDir();
|
|
68
|
+
const files = await readdir(SHARED_DIR);
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
let cleaned = 0;
|
|
71
|
+
for (const f of files) {
|
|
72
|
+
const fp = join(SHARED_DIR, f);
|
|
73
|
+
try {
|
|
74
|
+
const st = await stat(fp);
|
|
75
|
+
// 清理超时的临时文件(.tmp 和 .reading.json),超过1小时
|
|
76
|
+
if (f.endsWith('.tmp') || f.endsWith('.reading.json')) {
|
|
77
|
+
if (now - st.mtimeMs > 3600000) {
|
|
78
|
+
await unlink(fp);
|
|
79
|
+
cleaned++;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// 只清理过期的 .json 结果文件
|
|
84
|
+
if (!f.endsWith('.json')) continue;
|
|
85
|
+
if (now - st.mtimeMs > maxAgeMs) {
|
|
86
|
+
await unlink(fp);
|
|
87
|
+
cleaned++;
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.warn(`[shared-fs] ⚠️ 处理文件失败 ${f}: ${err?.message || '未知错误'}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// 清理 decomposed/ 子目录的过期文件
|
|
94
|
+
try {
|
|
95
|
+
const decomposedDir = join(SHARED_DIR, 'decomposed');
|
|
96
|
+
const decomposedFiles = await readdir(decomposedDir).catch(() => []);
|
|
97
|
+
for (const df of decomposedFiles) {
|
|
98
|
+
if (!df.endsWith('.json')) continue;
|
|
99
|
+
const dfp = join(decomposedDir, df);
|
|
100
|
+
try {
|
|
101
|
+
const st = await stat(dfp);
|
|
102
|
+
if (now - st.mtimeMs > maxAgeMs) {
|
|
103
|
+
await unlink(dfp);
|
|
104
|
+
cleaned++;
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.warn(`[shared-fs] ⚠️ 处理decomposed文件失败 ${df}: ${err?.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.warn(`[shared-fs] ⚠️ 清理decomposed目录失败: ${err?.message}`);
|
|
112
|
+
}
|
|
113
|
+
// 清理 preempt/ 子目录的残留 tmp 文件(进程崩溃后遗留)
|
|
114
|
+
try {
|
|
115
|
+
const preemptFiles = await readdir(PREEMPT_DIR).catch(() => []);
|
|
116
|
+
for (const pf of preemptFiles) {
|
|
117
|
+
if (!pf.endsWith('.tmp') && !pf.endsWith('.reading.json')) continue;
|
|
118
|
+
const pfp = join(PREEMPT_DIR, pf);
|
|
119
|
+
try {
|
|
120
|
+
const st = await stat(pfp);
|
|
121
|
+
if (now - st.mtimeMs > 3600000) {
|
|
122
|
+
await unlink(pfp);
|
|
123
|
+
cleaned++;
|
|
124
|
+
}
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.warn(`[shared-fs] ⚠️ 处理preempt临时文件失败 ${pf}: ${err?.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.warn(`[shared-fs] ⚠️ 清理preempt目录失败: ${err?.message}`);
|
|
131
|
+
}
|
|
132
|
+
if (cleaned > 0) console.log(`[sc] 🧹 清理了 ${cleaned} 个超时共享文件(含 decomposed/ 子步骤、preempt/ 残留tmp)`);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.warn(`[shared-fs] ⚠️ cleanupSharedDir失败: ${err?.message}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function cleanupOldSessions() {
|
|
139
|
+
try {
|
|
140
|
+
const files = await readdir(SESSION_DIR).catch(() => []);
|
|
141
|
+
|
|
142
|
+
const tempFiles = files.filter(f => f.endsWith('.lock') || f.endsWith('.tmp'));
|
|
143
|
+
let tempDeleted = 0;
|
|
144
|
+
for (const f of tempFiles) {
|
|
145
|
+
try { await unlink(join(SESSION_DIR, f)); tempDeleted++; } catch (err) { console.error("[sc] cleanupOldSessions 删除临时文件失败:", err.message); }
|
|
146
|
+
}
|
|
147
|
+
if (tempDeleted > 0) console.log(`[sc] 🧹 清理了 ${tempDeleted} 个临时文件(.lock/.tmp)`);
|
|
148
|
+
|
|
149
|
+
const sessionFiles = files.filter(f => f.endsWith('.jsonl') || f.endsWith('.trajectory.jsonl'));
|
|
150
|
+
if (sessionFiles.length <= SESSION_KEEP_RECENT) return;
|
|
151
|
+
|
|
152
|
+
const withTime = await Promise.all(
|
|
153
|
+
sessionFiles.map(async (f) => {
|
|
154
|
+
try {
|
|
155
|
+
const st = await stat(join(SESSION_DIR, f));
|
|
156
|
+
return { name: f, mtime: st.mtimeMs };
|
|
157
|
+
} catch { return null; }
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
const valid = withTime.filter(Boolean).sort((a, b) => b.mtime - a.mtime);
|
|
161
|
+
const toDelete = valid.slice(SESSION_KEEP_RECENT);
|
|
162
|
+
|
|
163
|
+
let deleted = 0;
|
|
164
|
+
for (const f of toDelete) {
|
|
165
|
+
try { await unlink(join(SESSION_DIR, f.name)); deleted++; } catch (err) { console.error("[sc] cleanupOldSessions 删除旧session文件失败:", err.message); }
|
|
166
|
+
}
|
|
167
|
+
if (deleted > 0) console.log(`[sc] 🧹 清理了 ${deleted} 个旧session文件(保留${SESSION_KEEP_RECENT}个)`);
|
|
168
|
+
} catch (err) { console.error("[sc] cleanupOldSessions 函数异常:", err.message); }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function cleanupTaskStates() {
|
|
172
|
+
try {
|
|
173
|
+
const files = await readdir(TASK_STATES_DIR).catch(() => []);
|
|
174
|
+
if (files.length === 0) return;
|
|
175
|
+
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
const DAY_MS = 86400000;
|
|
178
|
+
let deleted = 0;
|
|
179
|
+
|
|
180
|
+
for (const f of files) {
|
|
181
|
+
if (!f.endsWith('.json') && !f.endsWith('.md')) continue;
|
|
182
|
+
try {
|
|
183
|
+
const st = await stat(join(TASK_STATES_DIR, f));
|
|
184
|
+
if (now - st.mtimeMs > DAY_MS) {
|
|
185
|
+
await unlink(join(TASK_STATES_DIR, f));
|
|
186
|
+
deleted++;
|
|
187
|
+
}
|
|
188
|
+
} catch (err) { console.warn(`[shared-fs] 跳过无法访问的文件 ${f}: ${err?.message}`); }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (deleted > 0) console.log(`[sc] 🧹 清理了 ${deleted} 个孤儿任务元数据(task-states/,超过24小时)`);
|
|
192
|
+
} catch (err) { console.error("[sc] cleanupTaskStates 函数异常:", err.message); }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function ensurePreemptDir() {
|
|
196
|
+
try { await mkdir(PREEMPT_DIR, { recursive: true }); } catch (err) {
|
|
197
|
+
console.warn(`[shared-fs] 确保preempt目录失败: ${err?.message || '未知错误'}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function writePreemptState(jobId, state) {
|
|
202
|
+
await ensurePreemptDir();
|
|
203
|
+
const safeName = jobId.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
204
|
+
const fp = join(PREEMPT_DIR, `${safeName}.json`);
|
|
205
|
+
const tmp = join(PREEMPT_DIR, `${safeName}.${randomBytes(4).toString('hex')}.tmp`);
|
|
206
|
+
await writeFile(tmp, JSON.stringify({ ...state, _preemptSavedAt: Date.now() }, null, 2), 'utf-8');
|
|
207
|
+
await rename(tmp, fp);
|
|
208
|
+
return fp;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function readPreemptState(jobId) {
|
|
212
|
+
const safeName = jobId.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
213
|
+
const fp = join(PREEMPT_DIR, `${safeName}.json`);
|
|
214
|
+
try {
|
|
215
|
+
const content = await readFile(fp, 'utf-8');
|
|
216
|
+
return JSON.parse(content);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
console.warn(`[shared-fs] 读取preempt状态失败 ${jobId}: ${err?.message || '未知错误'}`);
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function clearPreemptState(jobId) {
|
|
224
|
+
const safeName = jobId.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
225
|
+
try {
|
|
226
|
+
await unlink(join(PREEMPT_DIR, `${safeName}.json`));
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.warn(`[shared-fs] 清除preempt状态失败 ${jobId}: ${err?.message || '未知错误'}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default {
|
|
233
|
+
ensureSharedDir,
|
|
234
|
+
sanitizeTaskName,
|
|
235
|
+
writeSharedResult,
|
|
236
|
+
readSharedResult,
|
|
237
|
+
cleanupSharedDir,
|
|
238
|
+
cleanupOldSessions,
|
|
239
|
+
cleanupTaskStates,
|
|
240
|
+
ensurePreemptDir,
|
|
241
|
+
writePreemptState,
|
|
242
|
+
readPreemptState,
|
|
243
|
+
clearPreemptState,
|
|
244
|
+
};
|