@next-open-ai/openclawx 0.8.32 → 0.8.40
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 +55 -47
- package/apps/desktop/renderer/dist/assets/index-BSfTiTKo.css +10 -0
- package/apps/desktop/renderer/dist/assets/index-DgLpQsA-.js +89 -0
- package/apps/desktop/renderer/dist/index.html +2 -2
- package/dist/cli/cli.js +29 -0
- package/dist/cli/extension-cmd.d.ts +15 -0
- package/dist/cli/extension-cmd.js +107 -0
- package/dist/core/agent/agent-dir.d.ts +6 -0
- package/dist/core/agent/agent-dir.js +8 -0
- package/dist/core/agent/agent-manager.d.ts +13 -0
- package/dist/core/agent/agent-manager.js +72 -6
- package/dist/core/agent/proxy/adapters/claude-code-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/claude-code-adapter.js +186 -0
- package/dist/core/agent/proxy/adapters/local-adapter.js +2 -0
- package/dist/core/agent/proxy/adapters/opencode-adapter.js +65 -29
- package/dist/core/agent/proxy/adapters/opencode-local-runner.js +9 -0
- package/dist/core/agent/proxy/index.js +2 -0
- package/dist/core/agent/token-usage-log-extension.d.ts +14 -0
- package/dist/core/agent/token-usage-log-extension.js +61 -0
- package/dist/core/config/desktop-config.d.ts +22 -2
- package/dist/core/config/desktop-config.js +57 -1
- package/dist/core/extensions/index.d.ts +1 -0
- package/dist/core/extensions/index.js +1 -0
- package/dist/core/extensions/load.d.ts +11 -0
- package/dist/core/extensions/load.js +101 -0
- package/dist/core/mcp/adapter.d.ts +4 -2
- package/dist/core/mcp/adapter.js +10 -4
- package/dist/core/mcp/client.d.ts +4 -0
- package/dist/core/mcp/client.js +2 -0
- package/dist/core/mcp/index.d.ts +5 -1
- package/dist/core/mcp/index.js +5 -1
- package/dist/core/mcp/operator.d.ts +19 -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/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/truncate-result.d.ts +14 -0
- package/dist/core/tools/truncate-result.js +27 -0
- package/dist/core/tools/web-search/create-web-search-tool.d.ts +17 -0
- package/dist/core/tools/web-search/create-web-search-tool.js +87 -0
- package/dist/core/tools/web-search/index.d.ts +4 -0
- package/dist/core/tools/web-search/index.js +2 -0
- package/dist/core/tools/web-search/providers/brave.d.ts +2 -0
- package/dist/core/tools/web-search/providers/brave.js +87 -0
- package/dist/core/tools/web-search/providers/duck-duck-scrape.d.ts +2 -0
- package/dist/core/tools/web-search/providers/duck-duck-scrape.js +47 -0
- package/dist/core/tools/web-search/providers/index.d.ts +5 -0
- package/dist/core/tools/web-search/providers/index.js +13 -0
- package/dist/core/tools/web-search/types.d.ts +35 -0
- package/dist/core/tools/web-search/types.js +4 -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 +294 -240
- package/dist/gateway/methods/run-scheduled-task.js +2 -0
- package/dist/gateway/server.js +2 -0
- package/dist/server/agent-config/agent-config.controller.d.ts +1 -1
- package/dist/server/agent-config/agent-config.service.d.ts +15 -3
- package/dist/server/agent-config/agent-config.service.js +18 -0
- package/dist/server/config/config.controller.d.ts +26 -0
- package/dist/server/config/config.service.d.ts +14 -0
- package/package.json +3 -1
- package/presets/preset-agents.json +121 -91
- package/presets/workspaces/finance-expert/skills/akshare-helper/SKILL.md +9 -0
- package/presets/workspaces/office-automation/skills/rpa-helper/SKILL.md +9 -0
- package/presets/workspaces/self-media-bot/skills/self-media-tools/SKILL.md +9 -0
- package/apps/desktop/renderer/dist/assets/index-BGHtXhm3.js +0 -89
- package/apps/desktop/renderer/dist/assets/index-CB2-m4ae.css +0 -10
|
@@ -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,257 @@ 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" || runnerType === "claude_code";
|
|
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
|
+
mcpMaxResultTokens: agentConfig?.mcpMaxResultTokens,
|
|
215
|
+
systemPrompt: agentConfig?.systemPrompt,
|
|
216
|
+
useLongMemory: agentConfig?.useLongMemory,
|
|
217
|
+
webSearch: agentConfig?.webSearch,
|
|
201
218
|
});
|
|
202
219
|
}
|
|
203
|
-
|
|
204
|
-
const msg =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
220
|
+
catch (err) {
|
|
221
|
+
const msg = err?.message ?? String(err);
|
|
222
|
+
if (msg.includes("No API key") || msg.includes("API key")) {
|
|
223
|
+
const prov = provider ?? "deepseek";
|
|
224
|
+
throw new Error(`未配置 ${prov} 的 API Key。请在桌面端「设置」-「模型/API」中配置,或运行:openbot login ${prov} <你的API Key>`);
|
|
225
|
+
}
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
// 向各通道广播:turn_end(本小轮结束)、agent_end(整轮对话结束),经统一出口推送给已注册消费者
|
|
229
|
+
let resolveAgentDone;
|
|
230
|
+
const agentDonePromise = new Promise((resolve) => {
|
|
231
|
+
resolveAgentDone = resolve;
|
|
232
|
+
});
|
|
233
|
+
let didUnsubscribe = false;
|
|
234
|
+
let unsubscribe;
|
|
235
|
+
const doUnsubscribe = () => {
|
|
236
|
+
if (didUnsubscribe)
|
|
237
|
+
return;
|
|
238
|
+
didUnsubscribe = true;
|
|
239
|
+
sessionSubscriptionBySessionId.delete(targetSessionId);
|
|
240
|
+
unsubscribe();
|
|
241
|
+
};
|
|
242
|
+
clearSessionStreamSubscription(targetSessionId);
|
|
243
|
+
let hasReceivedAnyChunk = false;
|
|
244
|
+
unsubscribe = session.subscribe((event) => {
|
|
245
|
+
if (event.type !== "message_update") {
|
|
246
|
+
console.log(`[agent.chat] event: ${event.type}`);
|
|
247
|
+
}
|
|
248
|
+
let wsPayload = null;
|
|
249
|
+
if (event.type === "message_update") {
|
|
250
|
+
const update = event;
|
|
251
|
+
if (update.assistantMessageEvent && update.assistantMessageEvent.type === "text_delta") {
|
|
215
252
|
hasReceivedAnyChunk = true;
|
|
253
|
+
wsPayload = { type: "chat", code: "agent.chunk", payload: { text: update.assistantMessageEvent.delta } };
|
|
254
|
+
}
|
|
255
|
+
else if (update.assistantMessageEvent && update.assistantMessageEvent.type === "thinking_delta") {
|
|
256
|
+
wsPayload = { type: "chat", code: "agent.chunk", payload: { text: update.assistantMessageEvent.delta, isThinking: true } };
|
|
257
|
+
}
|
|
258
|
+
else if (update.assistantMessageEvent?.type === "error" && update.assistantMessageEvent?.error?.errorMessage) {
|
|
259
|
+
console.warn("[agent.chat] model error:", update.assistantMessageEvent.error.errorMessage);
|
|
216
260
|
}
|
|
217
261
|
}
|
|
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 }));
|
|
262
|
+
else if (event.type === "tool_execution_start") {
|
|
263
|
+
wsPayload = { type: "chat", code: "agent.tool", payload: { type: "start", toolCallId: event.toolCallId, toolName: event.toolName, args: event.args } };
|
|
224
264
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
265
|
+
else if (event.type === "tool_execution_end") {
|
|
266
|
+
wsPayload = { type: "chat", code: "agent.tool", payload: { type: "end", toolCallId: event.toolCallId, toolName: event.toolName, result: event.result, isError: event.isError } };
|
|
267
|
+
}
|
|
268
|
+
else if (event.type === "message_end") {
|
|
269
|
+
const msg = event.message;
|
|
270
|
+
if (msg?.role === "assistant" && msg?.content && Array.isArray(msg.content)) {
|
|
271
|
+
const text = msg.content
|
|
272
|
+
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
273
|
+
.map((c) => c.text)
|
|
274
|
+
.join("");
|
|
275
|
+
if (text && !hasReceivedAnyChunk) {
|
|
276
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text } });
|
|
277
|
+
}
|
|
236
278
|
hasReceivedAnyChunk = true;
|
|
237
279
|
}
|
|
280
|
+
if (msg?.errorMessage) {
|
|
281
|
+
const errText = msg.errorMessage.includes("402") || msg.errorMessage.includes("Insufficient Balance")
|
|
282
|
+
? "API 余额不足,请到「设置」检查并充值后重试。"
|
|
283
|
+
: `请求失败:${msg.errorMessage}`;
|
|
284
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text: errText } });
|
|
285
|
+
}
|
|
286
|
+
wsPayload = null;
|
|
238
287
|
}
|
|
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
|
|
288
|
+
else if (event.type === "turn_end") {
|
|
289
|
+
const msg = event.message;
|
|
290
|
+
if (!hasReceivedAnyChunk && msg?.content && Array.isArray(msg.content)) {
|
|
291
|
+
const fullText = msg.content
|
|
268
292
|
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
269
293
|
.map((c) => c.text)
|
|
270
294
|
.join("");
|
|
271
|
-
if (
|
|
272
|
-
|
|
295
|
+
if (fullText) {
|
|
296
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text: fullText } });
|
|
297
|
+
hasReceivedAnyChunk = true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (msg?.errorMessage) {
|
|
301
|
+
const errText = msg.errorMessage.includes("402") || msg.errorMessage.includes("Insufficient Balance")
|
|
302
|
+
? "API 余额不足,请到「设置」检查并充值后重试。"
|
|
303
|
+
: `请求失败:${msg.errorMessage}`;
|
|
304
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text: errText } });
|
|
305
|
+
hasReceivedAnyChunk = true;
|
|
273
306
|
}
|
|
307
|
+
const usage = msg?.usage;
|
|
308
|
+
const promptTokens = Number(usage?.input ?? usage?.input_tokens ?? 0) || 0;
|
|
309
|
+
const completionTokens = Number(usage?.output ?? usage?.output_tokens ?? 0) || 0;
|
|
310
|
+
const usagePayload = promptTokens > 0 || completionTokens > 0 ? { promptTokens, completionTokens } : undefined;
|
|
311
|
+
const turnPayload = { content: "", ...(usagePayload && { usage: usagePayload }) };
|
|
312
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "turn_end", payload: turnPayload });
|
|
313
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "message_complete", payload: turnPayload });
|
|
314
|
+
wsPayload = null;
|
|
274
315
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
316
|
+
else if (event.type === "agent_end") {
|
|
317
|
+
if (!hasReceivedAnyChunk && Array.isArray(event.messages)) {
|
|
318
|
+
const messages = event.messages;
|
|
319
|
+
const lastAssistant = [...messages].reverse().find((m) => m?.role === "assistant" && m?.content);
|
|
320
|
+
if (lastAssistant?.content) {
|
|
321
|
+
const text = lastAssistant.content
|
|
322
|
+
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
323
|
+
.map((c) => c.text)
|
|
324
|
+
.join("");
|
|
325
|
+
if (text)
|
|
326
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent.chunk", payload: { text } });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "agent_end", payload: {} });
|
|
330
|
+
sendSessionMessage(targetSessionId, { type: "chat", code: "conversation_end", payload: {} });
|
|
331
|
+
wsPayload = null;
|
|
332
|
+
resolveAgentDone();
|
|
333
|
+
doUnsubscribe();
|
|
334
|
+
if (isEphemeralSession) {
|
|
335
|
+
void agentManager.deleteSession(targetSessionId + COMPOSITE_KEY_SEP + currentAgentId).catch(() => { });
|
|
336
|
+
}
|
|
283
337
|
}
|
|
338
|
+
if (wsPayload) {
|
|
339
|
+
sendSessionMessage(targetSessionId, wsPayload);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
sessionSubscriptionBySessionId.set(targetSessionId, unsubscribe);
|
|
343
|
+
try {
|
|
344
|
+
await session.sendUserMessage(message, { deliverAs: "followUp" });
|
|
345
|
+
await agentDonePromise;
|
|
346
|
+
console.log(`Agent chat completed for session ${targetSessionId}`);
|
|
347
|
+
return { status: "completed", sessionId: targetSessionId };
|
|
284
348
|
}
|
|
285
|
-
|
|
286
|
-
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error(`Error in agent chat:`, error);
|
|
351
|
+
resolveAgentDone();
|
|
352
|
+
doUnsubscribe();
|
|
353
|
+
throw error;
|
|
287
354
|
}
|
|
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
355
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
resolveAgentDone();
|
|
302
|
-
doUnsubscribe();
|
|
303
|
-
throw error;
|
|
356
|
+
finally {
|
|
357
|
+
unregisterConsumer();
|
|
304
358
|
}
|
|
305
359
|
}
|
|
@@ -61,8 +61,10 @@ export async function handleRunScheduledTask(req, res) {
|
|
|
61
61
|
modelId,
|
|
62
62
|
apiKey,
|
|
63
63
|
mcpServers: agentConfig?.mcpServers,
|
|
64
|
+
mcpMaxResultTokens: agentConfig?.mcpMaxResultTokens,
|
|
64
65
|
systemPrompt: agentConfig?.systemPrompt,
|
|
65
66
|
useLongMemory: agentConfig?.useLongMemory,
|
|
67
|
+
webSearch: agentConfig?.webSearch,
|
|
66
68
|
});
|
|
67
69
|
let assistantContent = "";
|
|
68
70
|
let turnPromptTokens = 0;
|
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() });
|
|
@@ -25,7 +25,7 @@ export declare class AgentConfigController {
|
|
|
25
25
|
success: boolean;
|
|
26
26
|
data: AgentConfigItem;
|
|
27
27
|
}>;
|
|
28
|
-
updateAgent(id: string, body: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers' | 'systemPrompt' | 'icon' | 'runnerType' | 'coze' | 'openclawx' | 'opencode'>>): Promise<{
|
|
28
|
+
updateAgent(id: string, body: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers' | 'mcpMaxResultTokens' | 'systemPrompt' | 'icon' | 'runnerType' | 'coze' | 'openclawx' | 'opencode' | 'claudeCode' | 'useLongMemory' | 'webSearch'>>): Promise<{
|
|
29
29
|
success: boolean;
|
|
30
30
|
data: AgentConfigItem;
|
|
31
31
|
}>;
|