@helmisatria/mcp-chrome-bridge 1.0.30
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/README.md +183 -0
- package/dist/README.md +25 -0
- package/dist/agent/attachment-service.d.ts +83 -0
- package/dist/agent/attachment-service.js +370 -0
- package/dist/agent/attachment-service.js.map +1 -0
- package/dist/agent/ccr-detector.d.ts +59 -0
- package/dist/agent/ccr-detector.js +311 -0
- package/dist/agent/ccr-detector.js.map +1 -0
- package/dist/agent/chat-service.d.ts +50 -0
- package/dist/agent/chat-service.js +439 -0
- package/dist/agent/chat-service.js.map +1 -0
- package/dist/agent/db/client.d.ts +26 -0
- package/dist/agent/db/client.js +244 -0
- package/dist/agent/db/client.js.map +1 -0
- package/dist/agent/db/index.d.ts +5 -0
- package/dist/agent/db/index.js +22 -0
- package/dist/agent/db/index.js.map +1 -0
- package/dist/agent/db/schema.d.ts +711 -0
- package/dist/agent/db/schema.js +121 -0
- package/dist/agent/db/schema.js.map +1 -0
- package/dist/agent/directory-picker.d.ts +11 -0
- package/dist/agent/directory-picker.js +149 -0
- package/dist/agent/directory-picker.js.map +1 -0
- package/dist/agent/engines/claude.d.ts +79 -0
- package/dist/agent/engines/claude.js +1338 -0
- package/dist/agent/engines/claude.js.map +1 -0
- package/dist/agent/engines/codex.d.ts +48 -0
- package/dist/agent/engines/codex.js +822 -0
- package/dist/agent/engines/codex.js.map +1 -0
- package/dist/agent/engines/types.d.ts +133 -0
- package/dist/agent/engines/types.js +3 -0
- package/dist/agent/engines/types.js.map +1 -0
- package/dist/agent/message-service.d.ts +56 -0
- package/dist/agent/message-service.js +198 -0
- package/dist/agent/message-service.js.map +1 -0
- package/dist/agent/open-project.d.ts +25 -0
- package/dist/agent/open-project.js +469 -0
- package/dist/agent/open-project.js.map +1 -0
- package/dist/agent/project-service.d.ts +49 -0
- package/dist/agent/project-service.js +254 -0
- package/dist/agent/project-service.js.map +1 -0
- package/dist/agent/project-types.d.ts +27 -0
- package/dist/agent/project-types.js +3 -0
- package/dist/agent/project-types.js.map +1 -0
- package/dist/agent/session-service.d.ts +198 -0
- package/dist/agent/session-service.js +292 -0
- package/dist/agent/session-service.js.map +1 -0
- package/dist/agent/storage.d.ts +27 -0
- package/dist/agent/storage.js +73 -0
- package/dist/agent/storage.js.map +1 -0
- package/dist/agent/stream-manager.d.ts +42 -0
- package/dist/agent/stream-manager.js +243 -0
- package/dist/agent/stream-manager.js.map +1 -0
- package/dist/agent/tool-bridge.d.ts +44 -0
- package/dist/agent/tool-bridge.js +50 -0
- package/dist/agent/tool-bridge.js.map +1 -0
- package/dist/agent/types.d.ts +6 -0
- package/dist/agent/types.js +3 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +224 -0
- package/dist/cli.js.map +1 -0
- package/dist/constant/index.d.ts +60 -0
- package/dist/constant/index.js +80 -0
- package/dist/constant/index.js.map +1 -0
- package/dist/file-handler.d.ts +41 -0
- package/dist/file-handler.js +295 -0
- package/dist/file-handler.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/mcp-server-stdio.d.ts +72 -0
- package/dist/mcp/mcp-server-stdio.js +143 -0
- package/dist/mcp/mcp-server-stdio.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +36 -0
- package/dist/mcp/mcp-server.js +26 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/register-tools.d.ts +2 -0
- package/dist/mcp/register-tools.js +148 -0
- package/dist/mcp/register-tools.js.map +1 -0
- package/dist/mcp/stdio-config.json +3 -0
- package/dist/native-messaging-host.d.ts +42 -0
- package/dist/native-messaging-host.js +312 -0
- package/dist/native-messaging-host.js.map +1 -0
- package/dist/run_host.bat +194 -0
- package/dist/run_host.sh +264 -0
- package/dist/scripts/browser-config.d.ts +28 -0
- package/dist/scripts/browser-config.js +229 -0
- package/dist/scripts/browser-config.js.map +1 -0
- package/dist/scripts/build.d.ts +1 -0
- package/dist/scripts/build.js +126 -0
- package/dist/scripts/build.js.map +1 -0
- package/dist/scripts/constant.d.ts +4 -0
- package/dist/scripts/constant.js +8 -0
- package/dist/scripts/constant.js.map +1 -0
- package/dist/scripts/doctor.d.ts +70 -0
- package/dist/scripts/doctor.js +930 -0
- package/dist/scripts/doctor.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +2 -0
- package/dist/scripts/postinstall.js +246 -0
- package/dist/scripts/postinstall.js.map +1 -0
- package/dist/scripts/register-dev.d.ts +1 -0
- package/dist/scripts/register-dev.js +5 -0
- package/dist/scripts/register-dev.js.map +1 -0
- package/dist/scripts/register.d.ts +2 -0
- package/dist/scripts/register.js +28 -0
- package/dist/scripts/register.js.map +1 -0
- package/dist/scripts/report.d.ts +96 -0
- package/dist/scripts/report.js +686 -0
- package/dist/scripts/report.js.map +1 -0
- package/dist/scripts/utils.d.ts +64 -0
- package/dist/scripts/utils.js +443 -0
- package/dist/scripts/utils.js.map +1 -0
- package/dist/server/index.d.ts +35 -0
- package/dist/server/index.js +312 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/agent.d.ts +21 -0
- package/dist/server/routes/agent.js +971 -0
- package/dist/server/routes/agent.js.map +1 -0
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +9 -0
- package/dist/server/routes/index.js.map +1 -0
- package/dist/trace-analyzer.d.ts +14 -0
- package/dist/trace-analyzer.js +113 -0
- package/dist/trace-analyzer.js.map +1 -0
- package/dist/util/logger.d.ts +1 -0
- package/dist/util/logger.js +43 -0
- package/dist/util/logger.js.map +1 -0
- package/package.json +91 -0
package/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Fastify Chrome Native Messaging服务
|
|
2
|
+
|
|
3
|
+
这是一个基于Fastify的TypeScript项目,用于与Chrome扩展进行原生通信。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 通过Chrome Native Messaging协议与Chrome扩展进行双向通信
|
|
8
|
+
- **支持多浏览器**: Chrome 和 Chromium (包括 Linux、macOS 和 Windows)
|
|
9
|
+
- 提供RESTful API服务
|
|
10
|
+
- 完全使用TypeScript开发
|
|
11
|
+
- 包含完整的测试套件
|
|
12
|
+
- 遵循代码质量最佳实践
|
|
13
|
+
|
|
14
|
+
## 开发环境设置
|
|
15
|
+
|
|
16
|
+
### 前置条件
|
|
17
|
+
|
|
18
|
+
- Node.js 20+
|
|
19
|
+
- npm 8+ 或 pnpm 8+
|
|
20
|
+
|
|
21
|
+
### 安装
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git clone https://github.com/your-username/fastify-chrome-native.git
|
|
25
|
+
cd fastify-chrome-native
|
|
26
|
+
npm install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 开发
|
|
30
|
+
|
|
31
|
+
1. 本地构建注册native server
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd app/native-server
|
|
35
|
+
npm run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. 启动chrome extension
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd app/chrome-extension
|
|
42
|
+
npm run dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 构建
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm run build
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 注册Native Messaging主机
|
|
52
|
+
|
|
53
|
+
#### 自动检测并注册所有已安装的浏览器
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
mcp-chrome-bridge register --detect
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### 注册特定浏览器
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 仅注册 Chrome
|
|
63
|
+
mcp-chrome-bridge register --browser chrome
|
|
64
|
+
|
|
65
|
+
# 仅注册 Chromium
|
|
66
|
+
mcp-chrome-bridge register --browser chromium
|
|
67
|
+
|
|
68
|
+
# 注册所有支持的浏览器
|
|
69
|
+
mcp-chrome-bridge register --browser all
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### 全局安装(会自动注册检测到的浏览器)
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm i -g mcp-chrome-bridge
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### 浏览器支持
|
|
79
|
+
|
|
80
|
+
| 浏览器 | Linux | macOS | Windows |
|
|
81
|
+
| ------------- | ----- | ----- | ------- |
|
|
82
|
+
| Google Chrome | ✓ | ✓ | ✓ |
|
|
83
|
+
| Chromium | ✓ | ✓ | ✓ |
|
|
84
|
+
|
|
85
|
+
注册位置:
|
|
86
|
+
|
|
87
|
+
- **Linux**: `~/.config/[browser-name]/NativeMessagingHosts/`
|
|
88
|
+
- **macOS**: `~/Library/Application Support/[Browser]/NativeMessagingHosts/`
|
|
89
|
+
- **Windows**: `%APPDATA%\[Browser]\NativeMessagingHosts\`
|
|
90
|
+
|
|
91
|
+
### 与Chrome扩展集成
|
|
92
|
+
|
|
93
|
+
以下是Chrome扩展中如何使用此服务的简单示例:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
// background.js
|
|
97
|
+
let nativePort = null;
|
|
98
|
+
let serverRunning = false;
|
|
99
|
+
|
|
100
|
+
// 启动Native Messaging服务
|
|
101
|
+
function startServer() {
|
|
102
|
+
if (nativePort) {
|
|
103
|
+
console.log('已连接到Native Messaging主机');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
nativePort = chrome.runtime.connectNative('com.yourcompany.fastify_native_host');
|
|
109
|
+
|
|
110
|
+
nativePort.onMessage.addListener((message) => {
|
|
111
|
+
console.log('收到Native消息:', message);
|
|
112
|
+
|
|
113
|
+
if (message.type === 'started') {
|
|
114
|
+
serverRunning = true;
|
|
115
|
+
console.log(`服务已启动,端口: ${message.payload.port}`);
|
|
116
|
+
} else if (message.type === 'stopped') {
|
|
117
|
+
serverRunning = false;
|
|
118
|
+
console.log('服务已停止');
|
|
119
|
+
} else if (message.type === 'error') {
|
|
120
|
+
console.error('Native错误:', message.payload.message);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
nativePort.onDisconnect.addListener(() => {
|
|
125
|
+
console.log('Native连接断开:', chrome.runtime.lastError);
|
|
126
|
+
nativePort = null;
|
|
127
|
+
serverRunning = false;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// 启动服务器
|
|
131
|
+
nativePort.postMessage({ type: 'start', payload: { port: 3000 } });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('启动Native Messaging时出错:', error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 停止服务器
|
|
138
|
+
function stopServer() {
|
|
139
|
+
if (nativePort && serverRunning) {
|
|
140
|
+
nativePort.postMessage({ type: 'stop' });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 测试与服务器的通信
|
|
145
|
+
async function testPing() {
|
|
146
|
+
try {
|
|
147
|
+
const response = await fetch('http://localhost:3000/ping');
|
|
148
|
+
const data = await response.json();
|
|
149
|
+
console.log('Ping响应:', data);
|
|
150
|
+
return data;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('Ping失败:', error);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 在扩展启动时连接Native主机
|
|
158
|
+
chrome.runtime.onStartup.addListener(startServer);
|
|
159
|
+
|
|
160
|
+
// 导出供popup或内容脚本使用的API
|
|
161
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
162
|
+
if (message.action === 'startServer') {
|
|
163
|
+
startServer();
|
|
164
|
+
sendResponse({ success: true });
|
|
165
|
+
} else if (message.action === 'stopServer') {
|
|
166
|
+
stopServer();
|
|
167
|
+
sendResponse({ success: true });
|
|
168
|
+
} else if (message.action === 'testPing') {
|
|
169
|
+
testPing().then(sendResponse);
|
|
170
|
+
return true; // 指示我们将异步发送响应
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 测试
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm run test
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 许可证
|
|
182
|
+
|
|
183
|
+
MIT
|
package/dist/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @helmisatria/mcp-chrome
|
|
2
|
+
|
|
3
|
+
本程序为Chrome扩展的Native Messaging主机端。
|
|
4
|
+
|
|
5
|
+
## 安装说明
|
|
6
|
+
|
|
7
|
+
1. 确保已安装Node.js
|
|
8
|
+
2. 全局安装本程序:
|
|
9
|
+
```
|
|
10
|
+
npm install -g @helmisatria/mcp-chrome
|
|
11
|
+
```
|
|
12
|
+
3. 注册Native Messaging主机:
|
|
13
|
+
```
|
|
14
|
+
# 用户级别安装(推荐)
|
|
15
|
+
@helmisatria/mcp-chrome register
|
|
16
|
+
|
|
17
|
+
# 如果用户级别安装失败,可以尝试系统级别安装
|
|
18
|
+
@helmisatria/mcp-chrome register --system
|
|
19
|
+
# 或者使用管理员权限
|
|
20
|
+
sudo @helmisatria/mcp-chrome register
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 使用方法
|
|
24
|
+
|
|
25
|
+
此应用程序由Chrome扩展自动启动,无需手动运行。
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { AgentAttachment, AttachmentMetadata, AttachmentProjectStats } from 'chrome-mcp-shared';
|
|
2
|
+
export interface SaveAttachmentInput {
|
|
3
|
+
projectId: string;
|
|
4
|
+
messageId: string;
|
|
5
|
+
attachment: AgentAttachment;
|
|
6
|
+
index: number;
|
|
7
|
+
}
|
|
8
|
+
export interface SavedAttachment {
|
|
9
|
+
/** Absolute path on disk (for engines) */
|
|
10
|
+
absolutePath: string;
|
|
11
|
+
/** Persisted filename under project dir */
|
|
12
|
+
filename: string;
|
|
13
|
+
/** Metadata to store in message.metadata.attachments */
|
|
14
|
+
metadata: AttachmentMetadata;
|
|
15
|
+
}
|
|
16
|
+
export interface AttachmentStats {
|
|
17
|
+
rootDir: string;
|
|
18
|
+
totalFiles: number;
|
|
19
|
+
totalBytes: number;
|
|
20
|
+
projects: AttachmentProjectStats[];
|
|
21
|
+
}
|
|
22
|
+
export interface CleanupAttachmentsInput {
|
|
23
|
+
/** If omitted, cleanup all project dirs under root */
|
|
24
|
+
projectIds?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface CleanupProjectResult {
|
|
27
|
+
projectId: string;
|
|
28
|
+
dirPath: string;
|
|
29
|
+
existed: boolean;
|
|
30
|
+
removedFiles: number;
|
|
31
|
+
removedBytes: number;
|
|
32
|
+
}
|
|
33
|
+
export interface CleanupResult {
|
|
34
|
+
rootDir: string;
|
|
35
|
+
removedFiles: number;
|
|
36
|
+
removedBytes: number;
|
|
37
|
+
results: CleanupProjectResult[];
|
|
38
|
+
}
|
|
39
|
+
export declare class AttachmentService {
|
|
40
|
+
/**
|
|
41
|
+
* Get the root directory for all attachments.
|
|
42
|
+
*/
|
|
43
|
+
getAttachmentsRootDir(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Get the directory for a specific project's attachments.
|
|
46
|
+
*/
|
|
47
|
+
getProjectAttachmentsDir(projectId: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Get the absolute path for a specific attachment file.
|
|
50
|
+
* Validates to prevent path traversal attacks.
|
|
51
|
+
*/
|
|
52
|
+
getAttachmentPath(projectId: string, filename: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Save an attachment to persistent storage.
|
|
55
|
+
* Creates directories if needed.
|
|
56
|
+
*/
|
|
57
|
+
saveAttachment(input: SaveAttachmentInput): Promise<SavedAttachment>;
|
|
58
|
+
/**
|
|
59
|
+
* Get statistics for all attachments.
|
|
60
|
+
*/
|
|
61
|
+
getAttachmentStats(): Promise<AttachmentStats>;
|
|
62
|
+
/**
|
|
63
|
+
* Get statistics for a single project.
|
|
64
|
+
*/
|
|
65
|
+
private getProjectStats;
|
|
66
|
+
/**
|
|
67
|
+
* Cleanup attachments for specified projects or all projects.
|
|
68
|
+
*/
|
|
69
|
+
cleanupAttachments(input?: CleanupAttachmentsInput): Promise<CleanupResult>;
|
|
70
|
+
/**
|
|
71
|
+
* Cleanup attachments for a single project.
|
|
72
|
+
*/
|
|
73
|
+
private cleanupProject;
|
|
74
|
+
/**
|
|
75
|
+
* Check if an attachment file exists.
|
|
76
|
+
*/
|
|
77
|
+
attachmentExists(projectId: string, filename: string): Promise<boolean>;
|
|
78
|
+
/**
|
|
79
|
+
* Read an attachment file.
|
|
80
|
+
*/
|
|
81
|
+
readAttachment(projectId: string, filename: string): Promise<Buffer>;
|
|
82
|
+
}
|
|
83
|
+
export declare const attachmentService: AttachmentService;
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.attachmentService = exports.AttachmentService = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Attachment Service for persisting and managing image attachments.
|
|
9
|
+
*
|
|
10
|
+
* Handles:
|
|
11
|
+
* - Saving attachments to persistent storage (not temp files)
|
|
12
|
+
* - Getting attachment statistics per project
|
|
13
|
+
* - Cleaning up attachments by project or all
|
|
14
|
+
*
|
|
15
|
+
* Storage structure:
|
|
16
|
+
* ~/.chrome-mcp-agent/attachments/{projectId}/{messageId}-{index}-{uuid}.{ext}
|
|
17
|
+
*/
|
|
18
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
19
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
const node_crypto_1 = require("node:crypto");
|
|
21
|
+
const storage_1 = require("./storage");
|
|
22
|
+
// ============================================================
|
|
23
|
+
// Constants
|
|
24
|
+
// ============================================================
|
|
25
|
+
const ATTACHMENTS_DIR_NAME = 'attachments';
|
|
26
|
+
/** Allowed MIME types for image attachments */
|
|
27
|
+
const ALLOWED_MIME_TYPES = new Set(['image/png', 'image/jpeg', 'image/gif', 'image/webp']);
|
|
28
|
+
// ============================================================
|
|
29
|
+
// Helper Functions
|
|
30
|
+
// ============================================================
|
|
31
|
+
/**
|
|
32
|
+
* Convert MIME type to file extension.
|
|
33
|
+
*/
|
|
34
|
+
function mimeTypeToExt(mimeType) {
|
|
35
|
+
switch (mimeType) {
|
|
36
|
+
case 'image/png':
|
|
37
|
+
return 'png';
|
|
38
|
+
case 'image/jpeg':
|
|
39
|
+
return 'jpg';
|
|
40
|
+
case 'image/gif':
|
|
41
|
+
return 'gif';
|
|
42
|
+
case 'image/webp':
|
|
43
|
+
return 'webp';
|
|
44
|
+
default:
|
|
45
|
+
return 'bin';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build a unique filename for an attachment.
|
|
50
|
+
* Format: {messageId}-{index}-{uuid}.{ext}
|
|
51
|
+
*/
|
|
52
|
+
function buildAttachmentFilename(params) {
|
|
53
|
+
const ext = mimeTypeToExt(params.mimeType);
|
|
54
|
+
const uuid = (0, node_crypto_1.randomUUID)().slice(0, 8);
|
|
55
|
+
return `${params.messageId}-${params.index}-${uuid}.${ext}`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate filename to prevent path traversal attacks.
|
|
59
|
+
*/
|
|
60
|
+
function isValidFilename(filename) {
|
|
61
|
+
// Reject empty, path separators, parent directory references
|
|
62
|
+
if (!filename || filename.includes('/') || filename.includes('\\')) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (filename === '.' || filename === '..' || filename.startsWith('.')) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
// Only allow alphanumeric, dash, underscore, dot
|
|
69
|
+
return /^[a-zA-Z0-9_-]+\.[a-zA-Z0-9]+$/.test(filename);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate projectId to prevent path traversal attacks.
|
|
73
|
+
*/
|
|
74
|
+
function isValidProjectId(projectId) {
|
|
75
|
+
if (!projectId)
|
|
76
|
+
return false;
|
|
77
|
+
// UUID format or alphanumeric with dashes
|
|
78
|
+
return /^[a-zA-Z0-9_-]+$/.test(projectId);
|
|
79
|
+
}
|
|
80
|
+
// ============================================================
|
|
81
|
+
// AttachmentService Class
|
|
82
|
+
// ============================================================
|
|
83
|
+
class AttachmentService {
|
|
84
|
+
/**
|
|
85
|
+
* Get the root directory for all attachments.
|
|
86
|
+
*/
|
|
87
|
+
getAttachmentsRootDir() {
|
|
88
|
+
return node_path_1.default.join((0, storage_1.getAgentDataDir)(), ATTACHMENTS_DIR_NAME);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the directory for a specific project's attachments.
|
|
92
|
+
*/
|
|
93
|
+
getProjectAttachmentsDir(projectId) {
|
|
94
|
+
if (!isValidProjectId(projectId)) {
|
|
95
|
+
throw new Error(`Invalid projectId: ${projectId}`);
|
|
96
|
+
}
|
|
97
|
+
return node_path_1.default.join(this.getAttachmentsRootDir(), projectId);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the absolute path for a specific attachment file.
|
|
101
|
+
* Validates to prevent path traversal attacks.
|
|
102
|
+
*/
|
|
103
|
+
getAttachmentPath(projectId, filename) {
|
|
104
|
+
if (!isValidProjectId(projectId)) {
|
|
105
|
+
throw new Error(`Invalid projectId: ${projectId}`);
|
|
106
|
+
}
|
|
107
|
+
if (!isValidFilename(filename)) {
|
|
108
|
+
throw new Error(`Invalid filename: ${filename}`);
|
|
109
|
+
}
|
|
110
|
+
const projectDir = this.getProjectAttachmentsDir(projectId);
|
|
111
|
+
const filePath = node_path_1.default.join(projectDir, filename);
|
|
112
|
+
// Double-check resolved path is within project directory (defense in depth)
|
|
113
|
+
const resolved = node_path_1.default.resolve(filePath);
|
|
114
|
+
const resolvedProjectDir = node_path_1.default.resolve(projectDir);
|
|
115
|
+
if (!resolved.startsWith(resolvedProjectDir + node_path_1.default.sep)) {
|
|
116
|
+
throw new Error('Path traversal attempt detected');
|
|
117
|
+
}
|
|
118
|
+
return filePath;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Save an attachment to persistent storage.
|
|
122
|
+
* Creates directories if needed.
|
|
123
|
+
*/
|
|
124
|
+
async saveAttachment(input) {
|
|
125
|
+
const { projectId, messageId, attachment, index } = input;
|
|
126
|
+
// Validate input
|
|
127
|
+
if (!isValidProjectId(projectId)) {
|
|
128
|
+
throw new Error(`Invalid projectId: ${projectId}`);
|
|
129
|
+
}
|
|
130
|
+
if (attachment.type !== 'image') {
|
|
131
|
+
throw new Error(`Unsupported attachment type: ${attachment.type}`);
|
|
132
|
+
}
|
|
133
|
+
if (!ALLOWED_MIME_TYPES.has(attachment.mimeType)) {
|
|
134
|
+
throw new Error(`Unsupported MIME type: ${attachment.mimeType}`);
|
|
135
|
+
}
|
|
136
|
+
// Build filename and paths
|
|
137
|
+
const filename = buildAttachmentFilename({
|
|
138
|
+
messageId,
|
|
139
|
+
index,
|
|
140
|
+
mimeType: attachment.mimeType,
|
|
141
|
+
});
|
|
142
|
+
const projectDir = this.getProjectAttachmentsDir(projectId);
|
|
143
|
+
const absolutePath = node_path_1.default.join(projectDir, filename);
|
|
144
|
+
// Decode base64 and get size
|
|
145
|
+
const buffer = Buffer.from(attachment.dataBase64, 'base64');
|
|
146
|
+
const sizeBytes = buffer.length;
|
|
147
|
+
// Create directory and write file
|
|
148
|
+
await promises_1.default.mkdir(projectDir, { recursive: true });
|
|
149
|
+
await promises_1.default.writeFile(absolutePath, buffer);
|
|
150
|
+
// Build metadata
|
|
151
|
+
const metadata = {
|
|
152
|
+
version: 1,
|
|
153
|
+
kind: 'image',
|
|
154
|
+
projectId,
|
|
155
|
+
messageId,
|
|
156
|
+
index,
|
|
157
|
+
filename,
|
|
158
|
+
urlPath: `/agent/attachments/${projectId}/${filename}`,
|
|
159
|
+
mimeType: attachment.mimeType,
|
|
160
|
+
sizeBytes,
|
|
161
|
+
originalName: attachment.name,
|
|
162
|
+
createdAt: new Date().toISOString(),
|
|
163
|
+
};
|
|
164
|
+
console.error(`[AttachmentService] Saved attachment: ${absolutePath} (${sizeBytes} bytes)`);
|
|
165
|
+
return {
|
|
166
|
+
absolutePath,
|
|
167
|
+
filename,
|
|
168
|
+
metadata,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get statistics for all attachments.
|
|
173
|
+
*/
|
|
174
|
+
async getAttachmentStats() {
|
|
175
|
+
const rootDir = this.getAttachmentsRootDir();
|
|
176
|
+
const projects = [];
|
|
177
|
+
let totalFiles = 0;
|
|
178
|
+
let totalBytes = 0;
|
|
179
|
+
try {
|
|
180
|
+
// Check if root directory exists
|
|
181
|
+
await promises_1.default.access(rootDir);
|
|
182
|
+
// Read all project directories
|
|
183
|
+
const entries = await promises_1.default.readdir(rootDir, { withFileTypes: true });
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
if (!entry.isDirectory())
|
|
186
|
+
continue;
|
|
187
|
+
const projectId = entry.name;
|
|
188
|
+
const dirPath = node_path_1.default.join(rootDir, projectId);
|
|
189
|
+
try {
|
|
190
|
+
const stats = await this.getProjectStats(projectId, dirPath);
|
|
191
|
+
projects.push(stats);
|
|
192
|
+
totalFiles += stats.fileCount;
|
|
193
|
+
totalBytes += stats.totalBytes;
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
// Skip directories we can't read
|
|
197
|
+
console.error(`[AttachmentService] Failed to stat project ${projectId}:`, error);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (_a) {
|
|
202
|
+
// Root directory doesn't exist - return empty stats
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
rootDir,
|
|
206
|
+
totalFiles,
|
|
207
|
+
totalBytes,
|
|
208
|
+
projects,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get statistics for a single project.
|
|
213
|
+
*/
|
|
214
|
+
async getProjectStats(projectId, dirPath) {
|
|
215
|
+
let fileCount = 0;
|
|
216
|
+
let totalBytes = 0;
|
|
217
|
+
let lastModifiedAt;
|
|
218
|
+
let latestMtime = 0;
|
|
219
|
+
try {
|
|
220
|
+
const files = await promises_1.default.readdir(dirPath);
|
|
221
|
+
for (const file of files) {
|
|
222
|
+
const filePath = node_path_1.default.join(dirPath, file);
|
|
223
|
+
try {
|
|
224
|
+
const stat = await promises_1.default.stat(filePath);
|
|
225
|
+
if (stat.isFile()) {
|
|
226
|
+
fileCount++;
|
|
227
|
+
totalBytes += stat.size;
|
|
228
|
+
if (stat.mtimeMs > latestMtime) {
|
|
229
|
+
latestMtime = stat.mtimeMs;
|
|
230
|
+
lastModifiedAt = stat.mtime.toISOString();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (_a) {
|
|
235
|
+
// Skip files we can't stat
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
projectId,
|
|
240
|
+
dirPath,
|
|
241
|
+
exists: true,
|
|
242
|
+
fileCount,
|
|
243
|
+
totalBytes,
|
|
244
|
+
lastModifiedAt,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch (_b) {
|
|
248
|
+
return {
|
|
249
|
+
projectId,
|
|
250
|
+
dirPath,
|
|
251
|
+
exists: false,
|
|
252
|
+
fileCount: 0,
|
|
253
|
+
totalBytes: 0,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Cleanup attachments for specified projects or all projects.
|
|
259
|
+
*/
|
|
260
|
+
async cleanupAttachments(input) {
|
|
261
|
+
const rootDir = this.getAttachmentsRootDir();
|
|
262
|
+
const results = [];
|
|
263
|
+
let totalRemovedFiles = 0;
|
|
264
|
+
let totalRemovedBytes = 0;
|
|
265
|
+
// Determine which projects to clean
|
|
266
|
+
let projectIds;
|
|
267
|
+
if ((input === null || input === void 0 ? void 0 : input.projectIds) && input.projectIds.length > 0) {
|
|
268
|
+
// Clean specific projects
|
|
269
|
+
projectIds = input.projectIds;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// Clean all projects - enumerate from filesystem
|
|
273
|
+
try {
|
|
274
|
+
const entries = await promises_1.default.readdir(rootDir, { withFileTypes: true });
|
|
275
|
+
projectIds = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
276
|
+
}
|
|
277
|
+
catch (_a) {
|
|
278
|
+
// Root doesn't exist - nothing to clean
|
|
279
|
+
return {
|
|
280
|
+
rootDir,
|
|
281
|
+
removedFiles: 0,
|
|
282
|
+
removedBytes: 0,
|
|
283
|
+
results: [],
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Clean each project
|
|
288
|
+
for (const projectId of projectIds) {
|
|
289
|
+
if (!isValidProjectId(projectId)) {
|
|
290
|
+
console.error(`[AttachmentService] Skipping invalid projectId: ${projectId}`);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const result = await this.cleanupProject(projectId);
|
|
294
|
+
results.push(result);
|
|
295
|
+
totalRemovedFiles += result.removedFiles;
|
|
296
|
+
totalRemovedBytes += result.removedBytes;
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
rootDir,
|
|
300
|
+
removedFiles: totalRemovedFiles,
|
|
301
|
+
removedBytes: totalRemovedBytes,
|
|
302
|
+
results,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Cleanup attachments for a single project.
|
|
307
|
+
*/
|
|
308
|
+
async cleanupProject(projectId) {
|
|
309
|
+
const dirPath = this.getProjectAttachmentsDir(projectId);
|
|
310
|
+
try {
|
|
311
|
+
// Get stats before deletion
|
|
312
|
+
const stats = await this.getProjectStats(projectId, dirPath);
|
|
313
|
+
if (!stats.exists) {
|
|
314
|
+
return {
|
|
315
|
+
projectId,
|
|
316
|
+
dirPath,
|
|
317
|
+
existed: false,
|
|
318
|
+
removedFiles: 0,
|
|
319
|
+
removedBytes: 0,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
// Remove directory and all contents
|
|
323
|
+
await promises_1.default.rm(dirPath, { recursive: true, force: true });
|
|
324
|
+
console.error(`[AttachmentService] Cleaned up ${stats.fileCount} files (${stats.totalBytes} bytes) for project ${projectId}`);
|
|
325
|
+
return {
|
|
326
|
+
projectId,
|
|
327
|
+
dirPath,
|
|
328
|
+
existed: true,
|
|
329
|
+
removedFiles: stats.fileCount,
|
|
330
|
+
removedBytes: stats.totalBytes,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
console.error(`[AttachmentService] Failed to cleanup project ${projectId}:`, error);
|
|
335
|
+
return {
|
|
336
|
+
projectId,
|
|
337
|
+
dirPath,
|
|
338
|
+
existed: false,
|
|
339
|
+
removedFiles: 0,
|
|
340
|
+
removedBytes: 0,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Check if an attachment file exists.
|
|
346
|
+
*/
|
|
347
|
+
async attachmentExists(projectId, filename) {
|
|
348
|
+
try {
|
|
349
|
+
const filePath = this.getAttachmentPath(projectId, filename);
|
|
350
|
+
await promises_1.default.access(filePath);
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
catch (_a) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Read an attachment file.
|
|
359
|
+
*/
|
|
360
|
+
async readAttachment(projectId, filename) {
|
|
361
|
+
const filePath = this.getAttachmentPath(projectId, filename);
|
|
362
|
+
return promises_1.default.readFile(filePath);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
exports.AttachmentService = AttachmentService;
|
|
366
|
+
// ============================================================
|
|
367
|
+
// Singleton Export
|
|
368
|
+
// ============================================================
|
|
369
|
+
exports.attachmentService = new AttachmentService();
|
|
370
|
+
//# sourceMappingURL=attachment-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-service.js","sourceRoot":"","sources":["../../src/agent/attachment-service.ts"],"names":[],"mappings":"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"}
|