@myassis/gateway 1.0.34 → 1.0.36
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,
|
|
@@ -125,7 +125,10 @@ async function getSystemPromptAsync(agent, customPrompt) {
|
|
|
125
125
|
1.逐步思考,分步骤调用工具,工具调用前尽量返回content,明确当前工具调用的目的。
|
|
126
126
|
2.工具调用的参数全部采用JSON语法,调用前先验证JSON语法正确后,再回复,切记未验证JSON语法就直接回复。
|
|
127
127
|
【命令执行】执行命令时一定要根据当前系统来执行命令
|
|
128
|
-
|
|
128
|
+
【文件操作】
|
|
129
|
+
1.文件编辑后一定要验证编辑是否成功,文件内容与预期是否一致。
|
|
130
|
+
2.文件工具目前未提供批量替换操作,如果需要批量修改,请自行创建脚本来完成。
|
|
131
|
+
3.大文件写入如果无法识别,请先写入主体内容,再使用edit工具进行修改来操作
|
|
129
132
|
【特殊语法】mermaid折线图需要使用xychart-beta,完整示例如下:
|
|
130
133
|
xychart-beta
|
|
131
134
|
title "2025年上半年产品销量对比"
|
|
@@ -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: '在本地计算机上执行命令行命令。适用于运行系统命令、脚本、查看文件、执行程序等场景。',
|
|
@@ -86,22 +146,22 @@ exports.execTool = {
|
|
|
86
146
|
},
|
|
87
147
|
handler: async (args, sessionId) => {
|
|
88
148
|
return new Promise(async (resolve) => {
|
|
89
|
-
const { cwd, timeout = 60000 } = args;
|
|
149
|
+
const { cwd, timeout = 60000, approved } = args;
|
|
90
150
|
let command = args.command;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
/shutdown/i,
|
|
96
|
-
/reboot/i,
|
|
97
|
-
/mkfs/i,
|
|
98
|
-
/dd\s+if=.*of=\/dev\//i,
|
|
99
|
-
];
|
|
100
|
-
for (const reg of dangerousPatterns) {
|
|
101
|
-
if (reg.test(command)) {
|
|
102
|
-
resolve({ success: false, errorMessage: '危险命令已拦截' });
|
|
151
|
+
// 检查危险命令(如果已批准则跳过)
|
|
152
|
+
if (!approved && isDangerousCommand(command)) {
|
|
153
|
+
if (!sessionId) {
|
|
154
|
+
resolve({ success: false, errorMessage: '危险命令需要用户确认,但缺少会话ID' });
|
|
103
155
|
return;
|
|
104
156
|
}
|
|
157
|
+
const token = addPendingApproval(command, cwd, timeout, sessionId);
|
|
158
|
+
resolve({
|
|
159
|
+
success: false,
|
|
160
|
+
needsApproval: true,
|
|
161
|
+
approvalToken: token,
|
|
162
|
+
errorMessage: `危险命令需要用户确认:${command}`,
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
105
165
|
}
|
|
106
166
|
const options = {
|
|
107
167
|
cwd,
|
|
@@ -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");
|