@next-open-ai/openclawx 0.8.32 → 0.8.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.
- package/README.md +7 -7
- package/dist/core/agent/agent-manager.js +4 -1
- package/dist/core/mcp/client.d.ts +4 -0
- package/dist/core/mcp/client.js +2 -0
- package/dist/core/mcp/index.d.ts +3 -1
- package/dist/core/mcp/index.js +4 -1
- package/dist/core/mcp/operator.d.ts +17 -2
- package/dist/core/mcp/operator.js +97 -30
- package/dist/core/mcp/transport/index.d.ts +4 -0
- package/dist/core/mcp/transport/index.js +6 -1
- package/dist/core/mcp/transport/stdio.d.ts +6 -0
- package/dist/core/mcp/transport/stdio.js +22 -1
- package/dist/core/session-outlet/index.d.ts +19 -0
- package/dist/core/session-outlet/index.js +33 -0
- package/dist/core/session-outlet/outlet.d.ts +15 -0
- package/dist/core/session-outlet/outlet.js +49 -0
- package/dist/core/session-outlet/types.d.ts +35 -0
- package/dist/core/session-outlet/types.js +5 -0
- package/dist/gateway/channel/channel-core.d.ts +1 -0
- package/dist/gateway/channel/channel-core.js +91 -70
- package/dist/gateway/methods/agent-cancel.js +3 -0
- package/dist/gateway/methods/agent-chat.d.ts +4 -0
- package/dist/gateway/methods/agent-chat.js +292 -240
- package/dist/gateway/server.js +2 -0
- package/package.json +1 -1
|
@@ -7,9 +7,25 @@ import { connectedClients } from "../clients.js";
|
|
|
7
7
|
import { getDesktopConfig, loadDesktopAgentConfig } from "../../core/config/desktop-config.js";
|
|
8
8
|
import { consumePendingAgentReload } from "../../core/config/agent-reload-pending.js";
|
|
9
9
|
import { registerProxyRunAbort } from "../proxy-run-abort.js";
|
|
10
|
+
import { getSessionOutlet, sendSessionMessage } from "../../core/session-outlet/index.js";
|
|
10
11
|
const COMPOSITE_KEY_SEP = "::";
|
|
12
|
+
/** 当前每个 session 的流式订阅(用于在 cancel 或新 run 前移除旧订阅,避免重复广播) */
|
|
13
|
+
const sessionSubscriptionBySessionId = new Map();
|
|
11
14
|
/**
|
|
12
|
-
*
|
|
15
|
+
* 移除某 session 的流式订阅(cancel 或新消息开始时调用,避免同一事件被多个回调处理导致界面重复)
|
|
16
|
+
*/
|
|
17
|
+
export function clearSessionStreamSubscription(sessionId) {
|
|
18
|
+
const unsub = sessionSubscriptionBySessionId.get(sessionId);
|
|
19
|
+
if (unsub) {
|
|
20
|
+
try {
|
|
21
|
+
unsub();
|
|
22
|
+
}
|
|
23
|
+
catch (_) { }
|
|
24
|
+
sessionSubscriptionBySessionId.delete(sessionId);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Broadcast message to all clients subscribed to a session (used by Web consumer).
|
|
13
29
|
*/
|
|
14
30
|
function broadcastToSession(sessionId, message) {
|
|
15
31
|
for (const client of connectedClients) {
|
|
@@ -18,6 +34,58 @@ function broadcastToSession(sessionId, message) {
|
|
|
18
34
|
}
|
|
19
35
|
}
|
|
20
36
|
}
|
|
37
|
+
/** 系统消息前缀,与正常回复同路下发,仅用此前缀区分,前端无需额外逻辑 */
|
|
38
|
+
const SYSTEM_MSG_PREFIX = "[System Message] ";
|
|
39
|
+
/** 系统消息结尾换行,与当前轮次助手内容分行显示 */
|
|
40
|
+
const SYSTEM_MSG_SUFFIX = "\n";
|
|
41
|
+
/**
|
|
42
|
+
* 创建 Web 端会话消息消费者:将统一出口的 SessionMessage 转为 Gateway 事件并 broadcast。
|
|
43
|
+
* 系统消息以 agent.chunk 形式发送,正文带 [System Message] 前缀且结尾换行,与当轮回复分行。
|
|
44
|
+
*/
|
|
45
|
+
function createWebSessionConsumer(_sessionId) {
|
|
46
|
+
return {
|
|
47
|
+
send(msg) {
|
|
48
|
+
const sid = msg.sessionId;
|
|
49
|
+
if (msg.type === "system" && msg.code === "command.result") {
|
|
50
|
+
const raw = msg.payload?.text ?? "";
|
|
51
|
+
const text = raw ? SYSTEM_MSG_PREFIX + raw + SYSTEM_MSG_SUFFIX : "";
|
|
52
|
+
if (text)
|
|
53
|
+
broadcastToSession(sid, createEvent("agent.chunk", { text, sessionId: sid }));
|
|
54
|
+
broadcastToSession(sid, createEvent("turn_end", { sessionId: sid, content: "" }));
|
|
55
|
+
broadcastToSession(sid, createEvent("message_complete", { sessionId: sid, content: "" }));
|
|
56
|
+
broadcastToSession(sid, createEvent("agent_end", { sessionId: sid }));
|
|
57
|
+
broadcastToSession(sid, createEvent("conversation_end", { sessionId: sid }));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (msg.type === "system" && msg.code === "mcp.progress") {
|
|
61
|
+
const raw = msg.payload?.message ?? msg.payload?.phase ?? "";
|
|
62
|
+
if (raw) {
|
|
63
|
+
const text = SYSTEM_MSG_PREFIX + raw + SYSTEM_MSG_SUFFIX;
|
|
64
|
+
broadcastToSession(sid, createEvent("agent.chunk", { text, sessionId: sid }));
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (msg.type === "chat") {
|
|
69
|
+
if (msg.code === "agent.chunk") {
|
|
70
|
+
broadcastToSession(sid, createEvent("agent.chunk", { ...msg.payload, sessionId: sid }));
|
|
71
|
+
}
|
|
72
|
+
else if (msg.code === "agent.tool") {
|
|
73
|
+
broadcastToSession(sid, createEvent("agent.tool", { ...msg.payload }));
|
|
74
|
+
}
|
|
75
|
+
else if (msg.code === "turn_end") {
|
|
76
|
+
broadcastToSession(sid, createEvent("turn_end", { sessionId: sid, ...msg.payload }));
|
|
77
|
+
broadcastToSession(sid, createEvent("message_complete", { sessionId: sid, ...msg.payload }));
|
|
78
|
+
}
|
|
79
|
+
else if (msg.code === "message_complete") {
|
|
80
|
+
broadcastToSession(sid, createEvent("message_complete", { sessionId: sid, ...msg.payload }));
|
|
81
|
+
}
|
|
82
|
+
else if (msg.code === "agent_end" || msg.code === "conversation_end") {
|
|
83
|
+
broadcastToSession(sid, createEvent(msg.code === "agent_end" ? "agent_end" : "conversation_end", { sessionId: sid, ...msg.payload }));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
21
89
|
/**
|
|
22
90
|
* Handle agent chat request with streaming support
|
|
23
91
|
*/
|
|
@@ -35,271 +103,255 @@ export async function handleAgentChat(client, params) {
|
|
|
35
103
|
return handleAgentChatInner(client, targetSessionId, message, params);
|
|
36
104
|
}
|
|
37
105
|
async function handleAgentChatInner(client, targetSessionId, message, params) {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.log(`[agent.chat] session=${targetSessionId} resolved agentId=${currentAgentId} (params=${params.agentId ?? "—"}, stored=${storedAgentId ?? "—"}, client=${client.agentId ?? "—"})`);
|
|
61
|
-
let workspace = "default";
|
|
62
|
-
let provider;
|
|
63
|
-
let modelId;
|
|
64
|
-
let apiKey;
|
|
65
|
-
const agentConfig = await loadDesktopAgentConfig(currentAgentId);
|
|
66
|
-
if (agentConfig) {
|
|
67
|
-
if (agentConfig.workspace)
|
|
68
|
-
workspace = agentConfig.workspace;
|
|
69
|
-
provider = agentConfig.provider;
|
|
70
|
-
modelId = agentConfig.model;
|
|
71
|
-
if (agentConfig.apiKey)
|
|
72
|
-
apiKey = agentConfig.apiKey;
|
|
73
|
-
}
|
|
74
|
-
const runnerType = agentConfig?.runnerType ?? "local";
|
|
75
|
-
const isProxyAgent = runnerType === "coze" || runnerType === "openclawx" || runnerType === "opencode";
|
|
76
|
-
if (isProxyAgent) {
|
|
77
|
-
console.log(`[agent.chat] Using proxy agent (${runnerType}) for session=${targetSessionId}, agentId=${currentAgentId}`);
|
|
78
|
-
}
|
|
79
|
-
// 代理智能体(Coze / OpenClawX / OpenCode):走 AgentProxy 统一入口,流式结果通过 WebSocket 推给客户端
|
|
80
|
-
if (isProxyAgent) {
|
|
81
|
-
const { signal, unregister } = registerProxyRunAbort(targetSessionId, currentAgentId);
|
|
82
|
-
const finishAndUnregister = () => {
|
|
83
|
-
unregister();
|
|
84
|
-
broadcastToSession(targetSessionId, createEvent("turn_end", { sessionId: targetSessionId, content: "" }));
|
|
85
|
-
broadcastToSession(targetSessionId, createEvent("message_complete", { sessionId: targetSessionId, content: "" }));
|
|
86
|
-
broadcastToSession(targetSessionId, createEvent("agent_end", { sessionId: targetSessionId }));
|
|
87
|
-
broadcastToSession(targetSessionId, createEvent("conversation_end", { sessionId: targetSessionId }));
|
|
88
|
-
};
|
|
89
|
-
try {
|
|
90
|
-
await runForChannelStream({
|
|
91
|
-
sessionId: targetSessionId,
|
|
92
|
-
message,
|
|
93
|
-
agentId: currentAgentId,
|
|
94
|
-
signal,
|
|
95
|
-
}, {
|
|
96
|
-
onChunk(delta) {
|
|
97
|
-
broadcastToSession(targetSessionId, createEvent("agent.chunk", { text: delta, sessionId: targetSessionId }));
|
|
98
|
-
},
|
|
99
|
-
onTurnEnd() {
|
|
100
|
-
broadcastToSession(targetSessionId, createEvent("turn_end", { sessionId: targetSessionId, content: "" }));
|
|
101
|
-
broadcastToSession(targetSessionId, createEvent("message_complete", { sessionId: targetSessionId, content: "" }));
|
|
102
|
-
},
|
|
103
|
-
onDone() {
|
|
104
|
-
finishAndUnregister();
|
|
105
|
-
},
|
|
106
|
+
const outlet = getSessionOutlet();
|
|
107
|
+
const unregisterConsumer = outlet
|
|
108
|
+
? outlet.registerConsumer(targetSessionId, createWebSessionConsumer(targetSessionId))
|
|
109
|
+
: () => { };
|
|
110
|
+
try {
|
|
111
|
+
const { targetAgentId } = params;
|
|
112
|
+
const sessionType = params.sessionType ?? client.sessionType ?? "chat";
|
|
113
|
+
const resolveCurrentAgent = getSessionCurrentAgentResolver();
|
|
114
|
+
const storedAgentId = resolveCurrentAgent?.(targetSessionId);
|
|
115
|
+
const clientAgentId = params.agentId ?? client.agentId ?? "default";
|
|
116
|
+
const initialAgentId = params.agentId ?? storedAgentId ?? clientAgentId ?? "default";
|
|
117
|
+
const { message: preprocessedMessage, agentId: preprocessedAgentId, directResponse } = await preprocessInboundMessage({
|
|
118
|
+
sessionId: targetSessionId,
|
|
119
|
+
message: message.trim(),
|
|
120
|
+
currentAgentId: initialAgentId,
|
|
121
|
+
});
|
|
122
|
+
getSessionCurrentAgentUpdater()?.(targetSessionId, preprocessedAgentId);
|
|
123
|
+
if (directResponse != null && directResponse !== "") {
|
|
124
|
+
sendSessionMessage(targetSessionId, {
|
|
125
|
+
type: "system",
|
|
126
|
+
code: "command.result",
|
|
127
|
+
payload: { text: directResponse },
|
|
106
128
|
});
|
|
107
129
|
return { status: "completed", sessionId: targetSessionId };
|
|
108
130
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
131
|
+
message = preprocessedMessage;
|
|
132
|
+
let currentAgentId = preprocessedAgentId;
|
|
133
|
+
console.log(`[agent.chat] session=${targetSessionId} resolved agentId=${currentAgentId} (params=${params.agentId ?? "—"}, stored=${storedAgentId ?? "—"}, client=${client.agentId ?? "—"})`);
|
|
134
|
+
let workspace = "default";
|
|
135
|
+
let provider;
|
|
136
|
+
let modelId;
|
|
137
|
+
let apiKey;
|
|
138
|
+
const agentConfig = await loadDesktopAgentConfig(currentAgentId);
|
|
139
|
+
if (agentConfig) {
|
|
140
|
+
if (agentConfig.workspace)
|
|
141
|
+
workspace = agentConfig.workspace;
|
|
142
|
+
provider = agentConfig.provider;
|
|
143
|
+
modelId = agentConfig.model;
|
|
144
|
+
if (agentConfig.apiKey)
|
|
145
|
+
apiKey = agentConfig.apiKey;
|
|
119
146
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
const effectiveTargetAgentId = sessionType === "system" ? targetAgentId : currentAgentId;
|
|
126
|
-
const { maxAgentSessions } = getDesktopConfig();
|
|
127
|
-
if (await consumePendingAgentReload(currentAgentId)) {
|
|
128
|
-
await agentManager.deleteSessionsByAgentId(currentAgentId);
|
|
129
|
-
}
|
|
130
|
-
let session;
|
|
131
|
-
try {
|
|
132
|
-
session = await agentManager.getOrCreateSession(targetSessionId, {
|
|
133
|
-
agentId: currentAgentId,
|
|
134
|
-
workspace,
|
|
135
|
-
provider,
|
|
136
|
-
modelId,
|
|
137
|
-
apiKey,
|
|
138
|
-
maxSessions: maxAgentSessions,
|
|
139
|
-
targetAgentId: effectiveTargetAgentId,
|
|
140
|
-
mcpServers: agentConfig?.mcpServers,
|
|
141
|
-
systemPrompt: agentConfig?.systemPrompt,
|
|
142
|
-
useLongMemory: agentConfig?.useLongMemory,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
const msg = err?.message ?? String(err);
|
|
147
|
-
if (msg.includes("No API key") || msg.includes("API key")) {
|
|
148
|
-
const prov = provider ?? "deepseek";
|
|
149
|
-
throw new Error(`未配置 ${prov} 的 API Key。请在桌面端「设置」-「模型/API」中配置,或运行:openbot login ${prov} <你的API Key>`);
|
|
147
|
+
const runnerType = agentConfig?.runnerType ?? "local";
|
|
148
|
+
const isProxyAgent = runnerType === "coze" || runnerType === "openclawx" || runnerType === "opencode";
|
|
149
|
+
if (isProxyAgent) {
|
|
150
|
+
console.log(`[agent.chat] Using proxy agent (${runnerType}) for session=${targetSessionId}, agentId=${currentAgentId}`);
|
|
150
151
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
wsMessage = createEvent("agent.chunk", { text: update.assistantMessageEvent.delta, isThinking: true });
|
|
152
|
+
// 代理智能体(Coze / OpenClawX / OpenCode):走 AgentProxy 统一入口,流式结果经统一出口推给客户端
|
|
153
|
+
if (isProxyAgent) {
|
|
154
|
+
const { signal, unregister } = registerProxyRunAbort(targetSessionId, currentAgentId);
|
|
155
|
+
const finishAndUnregister = () => {
|
|
156
|
+
unregister();
|
|
157
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "turn_end", payload: {} });
|
|
158
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "message_complete", payload: {} });
|
|
159
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent_end", payload: {} });
|
|
160
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "conversation_end", payload: {} });
|
|
161
|
+
};
|
|
162
|
+
try {
|
|
163
|
+
await runForChannelStream({
|
|
164
|
+
sessionId: targetSessionId,
|
|
165
|
+
message,
|
|
166
|
+
agentId: currentAgentId,
|
|
167
|
+
signal,
|
|
168
|
+
}, {
|
|
169
|
+
onChunk(delta) {
|
|
170
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text: delta } });
|
|
171
|
+
},
|
|
172
|
+
onTurnEnd() {
|
|
173
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "turn_end", payload: {} });
|
|
174
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "message_complete", payload: {} });
|
|
175
|
+
},
|
|
176
|
+
onDone() {
|
|
177
|
+
finishAndUnregister();
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
return { status: "completed", sessionId: targetSessionId };
|
|
181
181
|
}
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
catch (error) {
|
|
183
|
+
const isAbort = error?.name === "AbortError" || (typeof error?.message === "string" && error.message.includes("abort"));
|
|
184
|
+
if (!isAbort)
|
|
185
|
+
console.error(`Error in agent chat (proxy ${runnerType}):`, error);
|
|
186
|
+
finishAndUnregister();
|
|
187
|
+
if (!isAbort) {
|
|
188
|
+
const errMsg = error?.message || String(error);
|
|
189
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text: `请求失败:${errMsg}` } });
|
|
190
|
+
}
|
|
191
|
+
return { status: "completed", sessionId: targetSessionId };
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
const isEphemeralSession = sessionType === "system" || sessionType === "scheduled";
|
|
195
|
+
if (isEphemeralSession) {
|
|
196
|
+
await agentManager.deleteSession(targetSessionId + COMPOSITE_KEY_SEP + currentAgentId);
|
|
197
|
+
}
|
|
198
|
+
const effectiveTargetAgentId = sessionType === "system" ? targetAgentId : currentAgentId;
|
|
199
|
+
const { maxAgentSessions } = getDesktopConfig();
|
|
200
|
+
if (await consumePendingAgentReload(currentAgentId)) {
|
|
201
|
+
await agentManager.deleteSessionsByAgentId(currentAgentId);
|
|
193
202
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
let session;
|
|
204
|
+
try {
|
|
205
|
+
session = await agentManager.getOrCreateSession(targetSessionId, {
|
|
206
|
+
agentId: currentAgentId,
|
|
207
|
+
workspace,
|
|
208
|
+
provider,
|
|
209
|
+
modelId,
|
|
210
|
+
apiKey,
|
|
211
|
+
maxSessions: maxAgentSessions,
|
|
212
|
+
targetAgentId: effectiveTargetAgentId,
|
|
213
|
+
mcpServers: agentConfig?.mcpServers,
|
|
214
|
+
systemPrompt: agentConfig?.systemPrompt,
|
|
215
|
+
useLongMemory: agentConfig?.useLongMemory,
|
|
201
216
|
});
|
|
202
217
|
}
|
|
203
|
-
|
|
204
|
-
const msg =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
218
|
+
catch (err) {
|
|
219
|
+
const msg = err?.message ?? String(err);
|
|
220
|
+
if (msg.includes("No API key") || msg.includes("API key")) {
|
|
221
|
+
const prov = provider ?? "deepseek";
|
|
222
|
+
throw new Error(`未配置 ${prov} 的 API Key。请在桌面端「设置」-「模型/API」中配置,或运行:openbot login ${prov} <你的API Key>`);
|
|
223
|
+
}
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
// 向各通道广播:turn_end(本小轮结束)、agent_end(整轮对话结束),经统一出口推送给已注册消费者
|
|
227
|
+
let resolveAgentDone;
|
|
228
|
+
const agentDonePromise = new Promise((resolve) => {
|
|
229
|
+
resolveAgentDone = resolve;
|
|
230
|
+
});
|
|
231
|
+
let didUnsubscribe = false;
|
|
232
|
+
let unsubscribe;
|
|
233
|
+
const doUnsubscribe = () => {
|
|
234
|
+
if (didUnsubscribe)
|
|
235
|
+
return;
|
|
236
|
+
didUnsubscribe = true;
|
|
237
|
+
sessionSubscriptionBySessionId.delete(targetSessionId);
|
|
238
|
+
unsubscribe();
|
|
239
|
+
};
|
|
240
|
+
clearSessionStreamSubscription(targetSessionId);
|
|
241
|
+
let hasReceivedAnyChunk = false;
|
|
242
|
+
unsubscribe = session.subscribe((event) => {
|
|
243
|
+
if (event.type !== "message_update") {
|
|
244
|
+
console.log(`[agent.chat] event: ${event.type}`);
|
|
245
|
+
}
|
|
246
|
+
let wsPayload = null;
|
|
247
|
+
if (event.type === "message_update") {
|
|
248
|
+
const update = event;
|
|
249
|
+
if (update.assistantMessageEvent && update.assistantMessageEvent.type === "text_delta") {
|
|
215
250
|
hasReceivedAnyChunk = true;
|
|
251
|
+
wsPayload = { type: "chat", code: "agent.chunk", payload: { text: update.assistantMessageEvent.delta } };
|
|
252
|
+
}
|
|
253
|
+
else if (update.assistantMessageEvent && update.assistantMessageEvent.type === "thinking_delta") {
|
|
254
|
+
wsPayload = { type: "chat", code: "agent.chunk", payload: { text: update.assistantMessageEvent.delta, isThinking: true } };
|
|
255
|
+
}
|
|
256
|
+
else if (update.assistantMessageEvent?.type === "error" && update.assistantMessageEvent?.error?.errorMessage) {
|
|
257
|
+
console.warn("[agent.chat] model error:", update.assistantMessageEvent.error.errorMessage);
|
|
216
258
|
}
|
|
217
259
|
}
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
const errText = msg.errorMessage.includes("402") || msg.errorMessage.includes("Insufficient Balance")
|
|
221
|
-
? "API 余额不足,请到「设置」检查并充值后重试。"
|
|
222
|
-
: `请求失败:${msg.errorMessage}`;
|
|
223
|
-
broadcastToSession(targetSessionId, createEvent("agent.chunk", { text: errText }));
|
|
260
|
+
else if (event.type === "tool_execution_start") {
|
|
261
|
+
wsPayload = { type: "chat", code: "agent.tool", payload: { type: "start", toolCallId: event.toolCallId, toolName: event.toolName, args: event.args } };
|
|
224
262
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
263
|
+
else if (event.type === "tool_execution_end") {
|
|
264
|
+
wsPayload = { type: "chat", code: "agent.tool", payload: { type: "end", toolCallId: event.toolCallId, toolName: event.toolName, result: event.result, isError: event.isError } };
|
|
265
|
+
}
|
|
266
|
+
else if (event.type === "message_end") {
|
|
267
|
+
const msg = event.message;
|
|
268
|
+
if (msg?.role === "assistant" && msg?.content && Array.isArray(msg.content)) {
|
|
269
|
+
const text = msg.content
|
|
270
|
+
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
271
|
+
.map((c) => c.text)
|
|
272
|
+
.join("");
|
|
273
|
+
if (text && !hasReceivedAnyChunk) {
|
|
274
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text } });
|
|
275
|
+
}
|
|
236
276
|
hasReceivedAnyChunk = true;
|
|
237
277
|
}
|
|
278
|
+
if (msg?.errorMessage) {
|
|
279
|
+
const errText = msg.errorMessage.includes("402") || msg.errorMessage.includes("Insufficient Balance")
|
|
280
|
+
? "API 余额不足,请到「设置」检查并充值后重试。"
|
|
281
|
+
: `请求失败:${msg.errorMessage}`;
|
|
282
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text: errText } });
|
|
283
|
+
}
|
|
284
|
+
wsPayload = null;
|
|
238
285
|
}
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
: `请求失败:${msg.errorMessage}`;
|
|
244
|
-
broadcastToSession(targetSessionId, createEvent("agent.chunk", { text: errText }));
|
|
245
|
-
hasReceivedAnyChunk = true;
|
|
246
|
-
}
|
|
247
|
-
const usage = msg?.usage;
|
|
248
|
-
const promptTokens = Number(usage?.input ?? usage?.input_tokens ?? 0) || 0;
|
|
249
|
-
const completionTokens = Number(usage?.output ?? usage?.output_tokens ?? 0) || 0;
|
|
250
|
-
const usagePayload = promptTokens > 0 || completionTokens > 0
|
|
251
|
-
? { promptTokens, completionTokens }
|
|
252
|
-
: undefined;
|
|
253
|
-
const turnPayload = {
|
|
254
|
-
sessionId: targetSessionId,
|
|
255
|
-
content: "",
|
|
256
|
-
...(usagePayload && { usage: usagePayload }),
|
|
257
|
-
};
|
|
258
|
-
broadcastToSession(targetSessionId, createEvent("turn_end", turnPayload));
|
|
259
|
-
broadcastToSession(targetSessionId, createEvent("message_complete", turnPayload));
|
|
260
|
-
wsMessage = null;
|
|
261
|
-
}
|
|
262
|
-
else if (event.type === "agent_end") {
|
|
263
|
-
if (!hasReceivedAnyChunk && Array.isArray(event.messages)) {
|
|
264
|
-
const messages = event.messages;
|
|
265
|
-
const lastAssistant = [...messages].reverse().find((m) => m?.role === "assistant" && m?.content);
|
|
266
|
-
if (lastAssistant?.content) {
|
|
267
|
-
const text = lastAssistant.content
|
|
286
|
+
else if (event.type === "turn_end") {
|
|
287
|
+
const msg = event.message;
|
|
288
|
+
if (!hasReceivedAnyChunk && msg?.content && Array.isArray(msg.content)) {
|
|
289
|
+
const fullText = msg.content
|
|
268
290
|
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
269
291
|
.map((c) => c.text)
|
|
270
292
|
.join("");
|
|
271
|
-
if (
|
|
272
|
-
|
|
293
|
+
if (fullText) {
|
|
294
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text: fullText } });
|
|
295
|
+
hasReceivedAnyChunk = true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (msg?.errorMessage) {
|
|
299
|
+
const errText = msg.errorMessage.includes("402") || msg.errorMessage.includes("Insufficient Balance")
|
|
300
|
+
? "API 余额不足,请到「设置」检查并充值后重试。"
|
|
301
|
+
: `请求失败:${msg.errorMessage}`;
|
|
302
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text: errText } });
|
|
303
|
+
hasReceivedAnyChunk = true;
|
|
273
304
|
}
|
|
305
|
+
const usage = msg?.usage;
|
|
306
|
+
const promptTokens = Number(usage?.input ?? usage?.input_tokens ?? 0) || 0;
|
|
307
|
+
const completionTokens = Number(usage?.output ?? usage?.output_tokens ?? 0) || 0;
|
|
308
|
+
const usagePayload = promptTokens > 0 || completionTokens > 0 ? { promptTokens, completionTokens } : undefined;
|
|
309
|
+
const turnPayload = { content: "", ...(usagePayload && { usage: usagePayload }) };
|
|
310
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "turn_end", payload: turnPayload });
|
|
311
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "message_complete", payload: turnPayload });
|
|
312
|
+
wsPayload = null;
|
|
274
313
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
314
|
+
else if (event.type === "agent_end") {
|
|
315
|
+
if (!hasReceivedAnyChunk && Array.isArray(event.messages)) {
|
|
316
|
+
const messages = event.messages;
|
|
317
|
+
const lastAssistant = [...messages].reverse().find((m) => m?.role === "assistant" && m?.content);
|
|
318
|
+
if (lastAssistant?.content) {
|
|
319
|
+
const text = lastAssistant.content
|
|
320
|
+
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
321
|
+
.map((c) => c.text)
|
|
322
|
+
.join("");
|
|
323
|
+
if (text)
|
|
324
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text } });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent_end", payload: {} });
|
|
328
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "conversation_end", payload: {} });
|
|
329
|
+
wsPayload = null;
|
|
330
|
+
resolveAgentDone();
|
|
331
|
+
doUnsubscribe();
|
|
332
|
+
if (isEphemeralSession) {
|
|
333
|
+
void agentManager.deleteSession(targetSessionId + COMPOSITE_KEY_SEP + currentAgentId).catch(() => { });
|
|
334
|
+
}
|
|
283
335
|
}
|
|
336
|
+
if (wsPayload) {
|
|
337
|
+
sendSessionMessage(targetSessionId, wsPayload);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
sessionSubscriptionBySessionId.set(targetSessionId, unsubscribe);
|
|
341
|
+
try {
|
|
342
|
+
await session.sendUserMessage(message, { deliverAs: "followUp" });
|
|
343
|
+
await agentDonePromise;
|
|
344
|
+
console.log(`Agent chat completed for session ${targetSessionId}`);
|
|
345
|
+
return { status: "completed", sessionId: targetSessionId };
|
|
284
346
|
}
|
|
285
|
-
|
|
286
|
-
|
|
347
|
+
catch (error) {
|
|
348
|
+
console.error(`Error in agent chat:`, error);
|
|
349
|
+
resolveAgentDone();
|
|
350
|
+
doUnsubscribe();
|
|
351
|
+
throw error;
|
|
287
352
|
}
|
|
288
|
-
});
|
|
289
|
-
try {
|
|
290
|
-
// 若 agent 正在流式输出,deliverAs: 'followUp' 将本条消息排队,避免抛出 "Agent is already processing"
|
|
291
|
-
await session.sendUserMessage(message, { deliverAs: "followUp" });
|
|
292
|
-
await agentDonePromise;
|
|
293
|
-
console.log(`Agent chat completed for session ${targetSessionId}`);
|
|
294
|
-
return {
|
|
295
|
-
status: "completed",
|
|
296
|
-
sessionId: targetSessionId,
|
|
297
|
-
};
|
|
298
353
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
resolveAgentDone();
|
|
302
|
-
doUnsubscribe();
|
|
303
|
-
throw error;
|
|
354
|
+
finally {
|
|
355
|
+
unregisterConsumer();
|
|
304
356
|
}
|
|
305
357
|
}
|
package/dist/gateway/server.js
CHANGED
|
@@ -53,6 +53,7 @@ import { createTelegramChannel } from "./channel/adapters/telegram.js";
|
|
|
53
53
|
import { createWechatChannel, getWechatQrCode, getWechatStatus, refreshWechatQrCode } from "./channel/adapters/wechat.js";
|
|
54
54
|
import { setChannelSessionPersistence } from "./channel/session-persistence.js";
|
|
55
55
|
import { setSessionCurrentAgentResolver, setSessionCurrentAgentUpdater, setAgentListProvider, setCreateAgentProvider, } from "../core/session-current-agent.js";
|
|
56
|
+
import { SessionOutlet, setSessionOutlet } from "../core/session-outlet/index.js";
|
|
56
57
|
import { AgentsService } from "../server/agents/agents.service.js";
|
|
57
58
|
import { AgentConfigService } from "../server/agent-config/agent-config.service.js";
|
|
58
59
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -101,6 +102,7 @@ export async function startGatewayServer(port = 38080) {
|
|
|
101
102
|
catch (e) {
|
|
102
103
|
console.warn("[Gateway] Channel session persistence / session-agent bridge unavailable:", e);
|
|
103
104
|
}
|
|
105
|
+
setSessionOutlet(new SessionOutlet());
|
|
104
106
|
const gatewayExpress = express();
|
|
105
107
|
gatewayExpress.get(PATHS.HEALTH, (_req, res) => {
|
|
106
108
|
res.status(200).json({ status: "ok", timestamp: Date.now() });
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.8.
|
|
6
|
+
"version": "0.8.36",
|
|
7
7
|
"description": "OpenClawX - A professional desktop application for managing and executing AI agents with real-time chat, session management, and skills browsing.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "dist/index.js",
|