@myassis/gateway 1.0.35 → 1.0.37
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.
|
@@ -8,6 +8,7 @@ exports.webSocketService = void 0;
|
|
|
8
8
|
const ws_1 = require("ws");
|
|
9
9
|
const index_js_1 = require("../stores/index.js");
|
|
10
10
|
const shared_1 = require("@myassis/shared");
|
|
11
|
+
const Session_js_1 = require("./session/Session.js");
|
|
11
12
|
const logger = (0, shared_1.getLogger)('WebSocketService');
|
|
12
13
|
class WebSocketService {
|
|
13
14
|
wss = null;
|
|
@@ -127,6 +128,13 @@ class WebSocketService {
|
|
|
127
128
|
case 'subscribe':
|
|
128
129
|
logger.debug(`用户 ${userId} 订阅: ${message.payload?.channel || 'all'}`);
|
|
129
130
|
break;
|
|
131
|
+
case 'approval_response':
|
|
132
|
+
// 处理批准响应
|
|
133
|
+
const { token, approved } = message.payload || {};
|
|
134
|
+
if (token) {
|
|
135
|
+
(0, Session_js_1.handleApprovalResponse)(token, approved === true);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
130
138
|
default:
|
|
131
139
|
logger.debug(`收到用户 ${userId} 的消息: ${message.type}`);
|
|
132
140
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Session = void 0;
|
|
3
|
+
exports.Session = exports.handleApprovalResponse = exports.registerApprovalWaiter = void 0;
|
|
4
4
|
const uuid_1 = require("uuid");
|
|
5
5
|
const shared_1 = require("@myassis/shared");
|
|
6
6
|
const SessionStore_js_1 = require("./SessionStore.js");
|
|
@@ -16,6 +16,40 @@ const index_js_2 = require("../../config/index.js");
|
|
|
16
16
|
const SessionManager_js_1 = require("./SessionManager.js");
|
|
17
17
|
const logger = (0, shared_1.getLogger)('Session');
|
|
18
18
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
19
|
+
// 批准等待缓存:token -> { resolve, reject, expiresAt }
|
|
20
|
+
const approvalWaiters = new Map();
|
|
21
|
+
/**
|
|
22
|
+
* 注册批准等待者
|
|
23
|
+
*/
|
|
24
|
+
function registerApprovalWaiter(token, timeout = 5 * 60 * 1000) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
approvalWaiters.set(token, {
|
|
27
|
+
resolve,
|
|
28
|
+
reject,
|
|
29
|
+
expiresAt: Date.now() + timeout,
|
|
30
|
+
});
|
|
31
|
+
// 超时自动拒绝
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
const waiter = approvalWaiters.get(token);
|
|
34
|
+
if (waiter) {
|
|
35
|
+
approvalWaiters.delete(token);
|
|
36
|
+
reject(new Error('批准请求超时'));
|
|
37
|
+
}
|
|
38
|
+
}, timeout);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
exports.registerApprovalWaiter = registerApprovalWaiter;
|
|
42
|
+
/**
|
|
43
|
+
* 处理批准响应(由 WebSocket 调用)
|
|
44
|
+
*/
|
|
45
|
+
function handleApprovalResponse(token, approved) {
|
|
46
|
+
const waiter = approvalWaiters.get(token);
|
|
47
|
+
if (waiter) {
|
|
48
|
+
approvalWaiters.delete(token);
|
|
49
|
+
waiter.resolve(approved);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.handleApprovalResponse = handleApprovalResponse;
|
|
19
53
|
// AgentStore 单例 - 需要通过 SessionStore 获取数据库实例
|
|
20
54
|
let agentStoreInstance = null;
|
|
21
55
|
function getAgentStore() {
|
|
@@ -639,6 +673,141 @@ class Session {
|
|
|
639
673
|
sendSSE(res, toolStartEvent);
|
|
640
674
|
try {
|
|
641
675
|
const result = await (0, index_js_1.executeTool)(pendingToolCall.toolName, (0, LLMClient_js_1.parseAruments)(pendingToolCall.input), this.id, this.currentMessageId, this.userId);
|
|
676
|
+
// 检查是否需要用户批准
|
|
677
|
+
if (result.needsApproval) {
|
|
678
|
+
// 发送需要批准的事件
|
|
679
|
+
const approvalEvent = {
|
|
680
|
+
type: 'approval_pending',
|
|
681
|
+
id: pendingToolCall.id,
|
|
682
|
+
toolName: pendingToolCall.toolName,
|
|
683
|
+
actionName: pendingToolCall.actionName,
|
|
684
|
+
input: pendingToolCall.input,
|
|
685
|
+
approvalToken: result.approvalToken,
|
|
686
|
+
errorMessage: result.errorMessage,
|
|
687
|
+
toolCallId: toolCall.id,
|
|
688
|
+
modelName,
|
|
689
|
+
sessionId: this.id
|
|
690
|
+
};
|
|
691
|
+
sendSSE(res, approvalEvent);
|
|
692
|
+
// 等待 Desktop 批准
|
|
693
|
+
try {
|
|
694
|
+
const approved = await registerApprovalWaiter(result.approvalToken);
|
|
695
|
+
if (approved) {
|
|
696
|
+
// 用户批准,重新调用 executeTool 并传入 approved=true
|
|
697
|
+
const pending = (0, index_js_1.getAndRemovePendingApproval)(result.approvalToken);
|
|
698
|
+
if (pending) {
|
|
699
|
+
// 重新执行工具调用,传入 approved 参数
|
|
700
|
+
const approvedResult = await (0, index_js_1.executeTool)(pendingToolCall.toolName, { ...(0, LLMClient_js_1.parseAruments)(pendingToolCall.input), approved: true }, this.id, this.currentMessageId, this.userId);
|
|
701
|
+
toolResults.push({
|
|
702
|
+
id: pendingToolCall.id,
|
|
703
|
+
name: pendingToolCall.toolName,
|
|
704
|
+
output: approvedResult.output || approvedResult.errorMessage || '',
|
|
705
|
+
success: approvedResult.success,
|
|
706
|
+
});
|
|
707
|
+
toolCall.toolCalls.push({
|
|
708
|
+
id: pendingToolCall.id,
|
|
709
|
+
toolName: pendingToolCall.toolName,
|
|
710
|
+
input: pendingToolCall.input,
|
|
711
|
+
output: approvedResult.output || approvedResult.errorMessage || '',
|
|
712
|
+
status: approvedResult.success ? 'success' : 'error',
|
|
713
|
+
actionName: pendingToolCall.actionName
|
|
714
|
+
});
|
|
715
|
+
sendSSE(res, {
|
|
716
|
+
type: 'tool_call_result',
|
|
717
|
+
id: pendingToolCall.id,
|
|
718
|
+
toolName: pendingToolCall.toolName,
|
|
719
|
+
actionName: pendingToolCall.actionName,
|
|
720
|
+
output: JSON.stringify(approvedResult),
|
|
721
|
+
status: approvedResult.success ? 'success' : 'error',
|
|
722
|
+
toolCallId: toolCall.id,
|
|
723
|
+
modelName
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
// 批准 token 已过期或不存在
|
|
728
|
+
toolResults.push({
|
|
729
|
+
id: pendingToolCall.id,
|
|
730
|
+
name: pendingToolCall.toolName,
|
|
731
|
+
output: '批准请求已过期',
|
|
732
|
+
success: false,
|
|
733
|
+
});
|
|
734
|
+
toolCall.toolCalls.push({
|
|
735
|
+
id: pendingToolCall.id,
|
|
736
|
+
toolName: pendingToolCall.toolName,
|
|
737
|
+
input: pendingToolCall.input,
|
|
738
|
+
output: '批准请求已过期',
|
|
739
|
+
status: 'error',
|
|
740
|
+
actionName: pendingToolCall.actionName
|
|
741
|
+
});
|
|
742
|
+
sendSSE(res, {
|
|
743
|
+
type: 'tool_call_result',
|
|
744
|
+
id: pendingToolCall.id,
|
|
745
|
+
toolName: pendingToolCall.toolName,
|
|
746
|
+
output: '批准请求已过期',
|
|
747
|
+
status: 'error',
|
|
748
|
+
toolCallId: toolCall.id,
|
|
749
|
+
actionName: pendingToolCall.actionName,
|
|
750
|
+
modelName
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
// 用户拒绝
|
|
756
|
+
toolResults.push({
|
|
757
|
+
id: pendingToolCall.id,
|
|
758
|
+
name: pendingToolCall.toolName,
|
|
759
|
+
output: '用户拒绝执行',
|
|
760
|
+
success: false,
|
|
761
|
+
});
|
|
762
|
+
toolCall.toolCalls.push({
|
|
763
|
+
id: pendingToolCall.id,
|
|
764
|
+
toolName: pendingToolCall.toolName,
|
|
765
|
+
input: pendingToolCall.input,
|
|
766
|
+
output: '用户拒绝执行',
|
|
767
|
+
status: 'error',
|
|
768
|
+
actionName: pendingToolCall.actionName
|
|
769
|
+
});
|
|
770
|
+
sendSSE(res, {
|
|
771
|
+
type: 'tool_call_result',
|
|
772
|
+
id: pendingToolCall.id,
|
|
773
|
+
toolName: pendingToolCall.toolName,
|
|
774
|
+
output: '用户拒绝执行',
|
|
775
|
+
status: 'error',
|
|
776
|
+
toolCallId: toolCall.id,
|
|
777
|
+
actionName: pendingToolCall.actionName,
|
|
778
|
+
modelName
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
catch (waitError) {
|
|
783
|
+
// 等待超时或其他错误
|
|
784
|
+
toolResults.push({
|
|
785
|
+
id: pendingToolCall.id,
|
|
786
|
+
name: pendingToolCall.toolName,
|
|
787
|
+
output: waitError.message,
|
|
788
|
+
success: false,
|
|
789
|
+
});
|
|
790
|
+
toolCall.toolCalls.push({
|
|
791
|
+
id: pendingToolCall.id,
|
|
792
|
+
toolName: pendingToolCall.toolName,
|
|
793
|
+
input: pendingToolCall.input,
|
|
794
|
+
output: waitError.message,
|
|
795
|
+
status: 'error',
|
|
796
|
+
actionName: pendingToolCall.actionName
|
|
797
|
+
});
|
|
798
|
+
sendSSE(res, {
|
|
799
|
+
type: 'tool_call_result',
|
|
800
|
+
id: pendingToolCall.id,
|
|
801
|
+
toolName: pendingToolCall.toolName,
|
|
802
|
+
output: waitError.message,
|
|
803
|
+
status: 'error',
|
|
804
|
+
toolCallId: toolCall.id,
|
|
805
|
+
actionName: pendingToolCall.actionName,
|
|
806
|
+
modelName
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
642
811
|
toolResults.push({
|
|
643
812
|
id: pendingToolCall.id,
|
|
644
813
|
name: pendingToolCall.toolName,
|
|
@@ -3,14 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.execTool = exports.clearSessionCwd = exports.setSessionCwd = exports.getSessionCwd = void 0;
|
|
6
|
+
exports.execTool = exports.cleanupExpiredApprovals = exports.getAndRemovePendingApproval = exports.addPendingApproval = exports.clearSessionCwd = exports.setSessionCwd = exports.getSessionCwd = void 0;
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const util_1 = require("util");
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const os_1 = __importDefault(require("os"));
|
|
11
11
|
const shared_1 = require("@myassis/shared");
|
|
12
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
13
|
const logger = (0, shared_1.getLogger)('exec');
|
|
13
14
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
15
|
+
// 待批准的命令缓存:token -> { command, cwd, timeout, sessionId, expiresAt }
|
|
16
|
+
const pendingApprovals = new Map();
|
|
14
17
|
// 会话级工作目录状态:sessionId -> cwd
|
|
15
18
|
// 让 cd 命令能跨多次 exec 调用持久化工作目录
|
|
16
19
|
const sessionCwdMap = new Map();
|
|
@@ -62,6 +65,63 @@ function clearSessionCwd(sessionId) {
|
|
|
62
65
|
sessionCwdMap.delete(sessionId);
|
|
63
66
|
}
|
|
64
67
|
exports.clearSessionCwd = clearSessionCwd;
|
|
68
|
+
/** 生成批准 token */
|
|
69
|
+
function generateApprovalToken() {
|
|
70
|
+
return crypto_1.default.randomBytes(16).toString('hex');
|
|
71
|
+
}
|
|
72
|
+
/** 检查命令是否危险 */
|
|
73
|
+
function isDangerousCommand(command) {
|
|
74
|
+
const dangerousPatterns = [
|
|
75
|
+
/rm\s+-rf\s+\//,
|
|
76
|
+
/format\s+[a-z]:/i,
|
|
77
|
+
/del\s+\/[sfq]\s+\*/i,
|
|
78
|
+
/shutdown/i,
|
|
79
|
+
/reboot/i,
|
|
80
|
+
/mkfs/i,
|
|
81
|
+
/dd\s+if=.*of=\/dev\//i,
|
|
82
|
+
];
|
|
83
|
+
for (const reg of dangerousPatterns) {
|
|
84
|
+
if (reg.test(command))
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
/** 添加待批准命令,返回 token */
|
|
90
|
+
function addPendingApproval(command, cwd, timeout, sessionId) {
|
|
91
|
+
const token = generateApprovalToken();
|
|
92
|
+
pendingApprovals.set(token, {
|
|
93
|
+
command,
|
|
94
|
+
cwd,
|
|
95
|
+
timeout,
|
|
96
|
+
sessionId,
|
|
97
|
+
expiresAt: Date.now() + 5 * 60 * 1000, // 5分钟过期
|
|
98
|
+
});
|
|
99
|
+
return token;
|
|
100
|
+
}
|
|
101
|
+
exports.addPendingApproval = addPendingApproval;
|
|
102
|
+
/** 验证并获取待批准命令 */
|
|
103
|
+
function getAndRemovePendingApproval(token) {
|
|
104
|
+
const pending = pendingApprovals.get(token);
|
|
105
|
+
if (!pending)
|
|
106
|
+
return null;
|
|
107
|
+
if (Date.now() > pending.expiresAt) {
|
|
108
|
+
pendingApprovals.delete(token);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
pendingApprovals.delete(token);
|
|
112
|
+
return pending;
|
|
113
|
+
}
|
|
114
|
+
exports.getAndRemovePendingApproval = getAndRemovePendingApproval;
|
|
115
|
+
/** 清理过期的待批准命令 */
|
|
116
|
+
function cleanupExpiredApprovals() {
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
for (const [token, pending] of pendingApprovals.entries()) {
|
|
119
|
+
if (now > pending.expiresAt) {
|
|
120
|
+
pendingApprovals.delete(token);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.cleanupExpiredApprovals = cleanupExpiredApprovals;
|
|
65
125
|
exports.execTool = {
|
|
66
126
|
name: 'exec',
|
|
67
127
|
description: '在本地计算机上执行命令行命令。适用于运行系统命令、脚本、查看文件、执行程序等场景。',
|
|
@@ -80,26 +140,37 @@ exports.execTool = {
|
|
|
80
140
|
type: 'number',
|
|
81
141
|
description: '命令超时时间(毫秒),默认 60000',
|
|
82
142
|
default: 60000,
|
|
143
|
+
},
|
|
144
|
+
isUserAsk: {
|
|
145
|
+
type: 'boolean',
|
|
146
|
+
description: '是否为用户主动要求的命令(用于绕过危险命令拦截)。如果用户明确要求执行此命令(如"帮我执行shutdown"),设置为true。',
|
|
147
|
+
default: false,
|
|
83
148
|
}
|
|
84
149
|
},
|
|
85
150
|
required: ['command'],
|
|
86
151
|
},
|
|
87
152
|
handler: async (args, sessionId) => {
|
|
88
153
|
return new Promise(async (resolve) => {
|
|
89
|
-
const { cwd, timeout = 60000 } = args;
|
|
154
|
+
const { cwd, timeout = 60000, approved, isUserAsk = false } = args;
|
|
90
155
|
let command = args.command;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
156
|
+
// 检查危险命令(如果已批准或用户主动要求则跳过拦截)
|
|
157
|
+
if (!approved && isDangerousCommand(command)) {
|
|
158
|
+
if (isUserAsk) {
|
|
159
|
+
// 用户主动要求,跳过拦截
|
|
160
|
+
logger.info(`用户主动要求执行危险命令,跳过拦截: ${command}`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
if (!sessionId) {
|
|
164
|
+
resolve({ success: false, errorMessage: '危险命令需要用户确认,但缺少会话ID' });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const token = addPendingApproval(command, cwd, timeout, sessionId);
|
|
168
|
+
resolve({
|
|
169
|
+
success: false,
|
|
170
|
+
needsApproval: true,
|
|
171
|
+
approvalToken: token,
|
|
172
|
+
errorMessage: `危险命令需要用户确认:${command}`,
|
|
173
|
+
});
|
|
103
174
|
return;
|
|
104
175
|
}
|
|
105
176
|
}
|
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.getToolDefinitions = exports.executeTool = exports.getToolByName = exports.tools = void 0;
|
|
17
|
+
exports.getToolDefinitions = exports.executeTool = exports.cleanupExpiredApprovals = exports.getAndRemovePendingApproval = exports.addPendingApproval = exports.getToolByName = exports.tools = void 0;
|
|
18
18
|
/**
|
|
19
19
|
* 工具定义集合
|
|
20
20
|
*/
|
|
@@ -27,6 +27,9 @@ const mouse_js_1 = require("./mouse.js");
|
|
|
27
27
|
const fetch_js_1 = require("./fetch.js");
|
|
28
28
|
const skill_js_1 = require("./skill.js");
|
|
29
29
|
const exec_js_1 = require("./exec.js");
|
|
30
|
+
Object.defineProperty(exports, "addPendingApproval", { enumerable: true, get: function () { return exec_js_1.addPendingApproval; } });
|
|
31
|
+
Object.defineProperty(exports, "getAndRemovePendingApproval", { enumerable: true, get: function () { return exec_js_1.getAndRemovePendingApproval; } });
|
|
32
|
+
Object.defineProperty(exports, "cleanupExpiredApprovals", { enumerable: true, get: function () { return exec_js_1.cleanupExpiredApprovals; } });
|
|
30
33
|
const file_js_1 = require("./file.js");
|
|
31
34
|
const task_js_1 = require("./task.js");
|
|
32
35
|
const model_js_1 = require("./model.js");
|