ai-helper-agent 1.12.0 → 1.12.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-helper-agent",
3
- "version": "1.12.0",
3
+ "version": "1.12.2",
4
4
  "description": "AI Helper 代理服务 - 为 Chrome 扩展提供本地文件读写和系统命令执行能力",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/src/search.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { spawn } from 'child_process';
3
3
  import { readdir, stat, readFile } from 'fs/promises';
4
4
  import { statSync } from 'fs';
5
- import { join, basename, extname, resolve } from 'path';
5
+ import { join, basename, extname, resolve, isAbsolute } from 'path';
6
6
  import { checkPath } from './security.js';
7
7
 
8
8
  // 缓存检测结果,避免每次启动重复检测
@@ -112,7 +112,7 @@ function searchWithFd(rootPath, filePattern, recursive, maxResults) {
112
112
  const output = Buffer.concat(stdout).toString('utf-8').trim();
113
113
  const lines = output ? output.split('\n').filter(Boolean) : [];
114
114
  const results = lines.map(p => {
115
- const absPath = join(rootPath, p);
115
+ const absPath = isAbsolute(p) ? p : join(rootPath, p);
116
116
  try {
117
117
  const s = statSync(absPath);
118
118
  return { path: absPath, name: basename(p), type: s.isDirectory() ? 'directory' : 'file', size: s.size, mtime: s.mtimeMs };
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
- // 删除文件时先移动到回收站,7天后自动清理
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
- let size = 0;
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
- * 获取回收站目录路径(用于配置 etc)
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
  }