ai-helper-agent 1.12.0 → 1.12.1
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/package.json +1 -1
- package/src/server.js +1 -1
- package/src/trash.js +43 -27
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -924,7 +924,7 @@ export function startServer() {
|
|
|
924
924
|
return jsonResponse(res, 500, { success: false, error: trashResult.error });
|
|
925
925
|
}
|
|
926
926
|
logFs('delete', { path: check.resolved, type: isDir ? 'directory' : 'file', size: fstat2.size, trashId: trashResult.trashId });
|
|
927
|
-
return jsonResponse(res, 200, { success: true, path: check.resolved, trashId: trashResult.trashId, message: '已移至回收站(7天后自动清理)' });
|
|
927
|
+
return jsonResponse(res, 200, { success: true, path: check.resolved, isDir: trashResult.isDir, trashId: trashResult.trashId, message: '已移至回收站(7天后自动清理)' });
|
|
928
928
|
}
|
|
929
929
|
|
|
930
930
|
// 下载文件/目录(返回 base64 内容,目录自动打包为 zip)
|
package/src/trash.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// agent/src/trash.js - 文件回收站模块
|
|
2
|
-
//
|
|
2
|
+
// 删除文件/目录时先移动到回收站,7天后自动清理
|
|
3
3
|
import { mkdir, rename, readFile, writeFile, readdir, stat, unlink, rmdir } from 'fs/promises';
|
|
4
4
|
import { join, basename } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
@@ -28,7 +28,7 @@ async function ensureTrashDir() {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* 读取回收站元数据
|
|
31
|
-
* @returns {Promise<Array>} [{ id, originalPath, name, size, deletedAt }]
|
|
31
|
+
* @returns {Promise<Array>} [{ id, originalPath, name, size, deletedAt, isDir }]
|
|
32
32
|
*/
|
|
33
33
|
async function loadMetadata() {
|
|
34
34
|
try {
|
|
@@ -47,9 +47,32 @@ async function saveMetadata(entries) {
|
|
|
47
47
|
await writeFile(TRASH_META_FILE, JSON.stringify({ entries }, null, 2), 'utf-8');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* 递归计算目录实际大小
|
|
52
|
+
* @param {string} dirPath - 目录路径
|
|
53
|
+
* @returns {Promise<number>} 总大小(字节)
|
|
54
|
+
*/
|
|
55
|
+
async function getDirSize(dirPath) {
|
|
56
|
+
let total = 0;
|
|
57
|
+
try {
|
|
58
|
+
const items = await readdir(dirPath, { withFileTypes: true });
|
|
59
|
+
for (const item of items) {
|
|
60
|
+
const fullPath = join(dirPath, item.name);
|
|
61
|
+
if (item.isDirectory()) {
|
|
62
|
+
total += await getDirSize(fullPath);
|
|
63
|
+
} else if (item.isFile() || item.isSymbolicLink()) {
|
|
64
|
+
try {
|
|
65
|
+
const s = await stat(fullPath);
|
|
66
|
+
total += s.size;
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {}
|
|
71
|
+
return total;
|
|
72
|
+
}
|
|
73
|
+
|
|
50
74
|
/**
|
|
51
75
|
* 清理过期回收站条目(超过7天)
|
|
52
|
-
* 同时在文件系统上删除对应文件/目录
|
|
53
76
|
*/
|
|
54
77
|
async function cleanExpiredTrash() {
|
|
55
78
|
await ensureTrashDir();
|
|
@@ -61,6 +84,7 @@ async function cleanExpiredTrash() {
|
|
|
61
84
|
for (const entry of expired) {
|
|
62
85
|
const trashPath = join(TRASH_DIR, entry.id);
|
|
63
86
|
try {
|
|
87
|
+
// 目录用 rename 存入,需递归删除;文件用 unlink
|
|
64
88
|
const s = await stat(trashPath);
|
|
65
89
|
if (s.isDirectory()) {
|
|
66
90
|
await rmdir(trashPath, { recursive: true });
|
|
@@ -77,31 +101,27 @@ async function cleanExpiredTrash() {
|
|
|
77
101
|
}
|
|
78
102
|
|
|
79
103
|
/**
|
|
80
|
-
*
|
|
104
|
+
* 将文件/目录移动到回收站(使用 rename,同文件系统瞬时完成)
|
|
81
105
|
* @param {string} sourcePath - 源文件/目录的绝对路径
|
|
82
|
-
* @returns {Promise<{success: boolean, trashId?: string, error?: string}>}
|
|
106
|
+
* @returns {Promise<{success: boolean, trashId?: string, isDir?: boolean, error?: string}>}
|
|
83
107
|
*/
|
|
84
108
|
export async function moveToTrash(sourcePath) {
|
|
85
109
|
await ensureTrashDir();
|
|
86
|
-
|
|
87
|
-
// 每次删除前先清理过期条目(投机式清理,无需定时器)
|
|
88
110
|
await cleanExpiredTrash();
|
|
89
111
|
|
|
90
112
|
try {
|
|
113
|
+
const s = await stat(sourcePath);
|
|
114
|
+
const isDir = s.isDirectory();
|
|
91
115
|
const ts = Date.now();
|
|
92
116
|
const randomSuffix = Math.random().toString(36).slice(2, 8);
|
|
93
117
|
const id = `${ts}_${randomSuffix}`;
|
|
94
118
|
const name = basename(sourcePath);
|
|
95
119
|
const trashPath = join(TRASH_DIR, id);
|
|
96
120
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const s = await stat(sourcePath);
|
|
101
|
-
size = s.size;
|
|
102
|
-
} catch {}
|
|
121
|
+
// 获取实际大小(目录需要递归计算)
|
|
122
|
+
const size = isDir ? await getDirSize(sourcePath) : s.size;
|
|
103
123
|
|
|
104
|
-
//
|
|
124
|
+
// 移动到回收站(rename 是文件系统元数据操作,瞬时完成)
|
|
105
125
|
await rename(sourcePath, trashPath);
|
|
106
126
|
|
|
107
127
|
// 记录元数据
|
|
@@ -111,21 +131,22 @@ export async function moveToTrash(sourcePath) {
|
|
|
111
131
|
originalPath: sourcePath,
|
|
112
132
|
name,
|
|
113
133
|
size,
|
|
114
|
-
deletedAt: ts
|
|
134
|
+
deletedAt: ts,
|
|
135
|
+
isDir
|
|
115
136
|
});
|
|
116
137
|
await saveMetadata(entries);
|
|
117
138
|
|
|
118
|
-
console.log(`[Trash] 已移至回收站: ${sourcePath} -> ${trashPath}`);
|
|
119
|
-
return { success: true, trashId: id };
|
|
139
|
+
console.log(`[Trash] 已移至回收站: ${sourcePath} -> ${trashPath} (${size} 字节, ${isDir ? '目录' : '文件'})`);
|
|
140
|
+
return { success: true, trashId: id, isDir };
|
|
120
141
|
} catch (err) {
|
|
121
142
|
return { success: false, error: `移至回收站失败: ${err.message}` };
|
|
122
143
|
}
|
|
123
144
|
}
|
|
124
145
|
|
|
125
146
|
/**
|
|
126
|
-
*
|
|
147
|
+
* 从回收站恢复文件/目录
|
|
127
148
|
* @param {string} trashId - 回收站条目 ID
|
|
128
|
-
* @returns {Promise<{success: boolean, error?: string}>}
|
|
149
|
+
* @returns {Promise<{success: boolean, restoredPath?: string, error?: string}>}
|
|
129
150
|
*/
|
|
130
151
|
export async function restoreFromTrash(trashId) {
|
|
131
152
|
const entries = await loadMetadata();
|
|
@@ -138,14 +159,12 @@ export async function restoreFromTrash(trashId) {
|
|
|
138
159
|
const trashPath = join(TRASH_DIR, entry.id);
|
|
139
160
|
|
|
140
161
|
if (!existsSync(trashPath)) {
|
|
141
|
-
// 文件已不在回收站,清理元数据
|
|
142
162
|
entries.splice(idx, 1);
|
|
143
163
|
await saveMetadata(entries);
|
|
144
164
|
return { success: false, error: '回收站文件已不存在' };
|
|
145
165
|
}
|
|
146
166
|
|
|
147
167
|
try {
|
|
148
|
-
// 检查原路径是否已被占用
|
|
149
168
|
if (existsSync(entry.originalPath)) {
|
|
150
169
|
return { success: false, error: `原位置已有文件: ${entry.originalPath}` };
|
|
151
170
|
}
|
|
@@ -162,19 +181,18 @@ export async function restoreFromTrash(trashId) {
|
|
|
162
181
|
}
|
|
163
182
|
|
|
164
183
|
/**
|
|
165
|
-
*
|
|
184
|
+
* 获取回收站条目列表
|
|
166
185
|
* @returns {Promise<{success: boolean, entries: Array}>}
|
|
167
186
|
*/
|
|
168
187
|
export async function listTrash() {
|
|
169
188
|
await ensureTrashDir();
|
|
170
|
-
// 前先清理一次过期条目,保持视图清爽
|
|
171
189
|
await cleanExpiredTrash();
|
|
172
190
|
const entries = await loadMetadata();
|
|
173
191
|
return { success: true, entries };
|
|
174
192
|
}
|
|
175
193
|
|
|
176
194
|
/**
|
|
177
|
-
*
|
|
195
|
+
* 获取回收站目录路径
|
|
178
196
|
*/
|
|
179
197
|
export function getTrashDir() {
|
|
180
198
|
return TRASH_DIR;
|
|
@@ -182,10 +200,9 @@ export function getTrashDir() {
|
|
|
182
200
|
|
|
183
201
|
/**
|
|
184
202
|
* 启动定期清理定时器(每6小时执行一次过期清理)
|
|
185
|
-
* 配合 moveToTrash/listTrash 中的投机式清理,确保即使长期不操作也能清理过期文件
|
|
186
203
|
*/
|
|
187
204
|
export function startPeriodicCleanup() {
|
|
188
|
-
if (periodicCleanTimer) return;
|
|
205
|
+
if (periodicCleanTimer) return;
|
|
189
206
|
periodicCleanTimer = setInterval(async () => {
|
|
190
207
|
try {
|
|
191
208
|
await cleanExpiredTrash();
|
|
@@ -193,7 +210,6 @@ export function startPeriodicCleanup() {
|
|
|
193
210
|
console.error('[Trash] 定期清理出错:', err.message);
|
|
194
211
|
}
|
|
195
212
|
}, PERIODIC_CLEAN_INTERVAL_MS);
|
|
196
|
-
// 允许进程退出(不阻止事件循环),Node 会在退出时自动清理
|
|
197
213
|
if (periodicCleanTimer.unref) periodicCleanTimer.unref();
|
|
198
214
|
console.log('[Trash] 定期清理已启动(间隔6小时)');
|
|
199
215
|
}
|