@next-open-ai/openclawx 0.8.8 → 0.8.16

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.
Files changed (32) hide show
  1. package/README.md +28 -21
  2. package/apps/desktop/renderer/dist/assets/index-DmIfN-Vc.js +89 -0
  3. package/apps/desktop/renderer/dist/assets/index-DvB8yW8I.css +10 -0
  4. package/apps/desktop/renderer/dist/index.html +2 -2
  5. package/dist/core/agent/proxy/adapters/coze-adapter.d.ts +2 -0
  6. package/dist/core/agent/proxy/adapters/coze-adapter.js +399 -0
  7. package/dist/core/agent/proxy/adapters/local-adapter.d.ts +2 -0
  8. package/dist/core/agent/proxy/adapters/local-adapter.js +100 -0
  9. package/dist/core/agent/proxy/adapters/openclawx-adapter.d.ts +2 -0
  10. package/dist/core/agent/proxy/adapters/openclawx-adapter.js +108 -0
  11. package/dist/core/agent/proxy/index.d.ts +3 -0
  12. package/dist/core/agent/proxy/index.js +14 -0
  13. package/dist/core/agent/proxy/registry.d.ts +7 -0
  14. package/dist/core/agent/proxy/registry.js +13 -0
  15. package/dist/core/agent/proxy/run-for-channel.d.ts +3 -0
  16. package/dist/core/agent/proxy/run-for-channel.js +31 -0
  17. package/dist/core/agent/proxy/types.d.ts +28 -0
  18. package/dist/core/agent/proxy/types.js +1 -0
  19. package/dist/core/config/desktop-config.d.ts +38 -0
  20. package/dist/core/config/desktop-config.js +64 -1
  21. package/dist/gateway/channel/adapters/telegram.js +13 -2
  22. package/dist/gateway/channel/run-agent.d.ts +2 -4
  23. package/dist/gateway/channel/run-agent.js +13 -125
  24. package/dist/gateway/methods/agent-chat.js +34 -0
  25. package/dist/server/agent-config/agent-config.controller.d.ts +1 -1
  26. package/dist/server/agent-config/agent-config.service.d.ts +27 -1
  27. package/dist/server/agent-config/agent-config.service.js +43 -0
  28. package/dist/server/agents/agents.controller.d.ts +16 -0
  29. package/dist/server/agents/agents.controller.js +62 -1
  30. package/package.json +1 -1
  31. package/apps/desktop/renderer/dist/assets/index-D9ET31hp.css +0 -10
  32. package/apps/desktop/renderer/dist/assets/index-DI___vdo.js +0 -89
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 通道用统一执行入口:根据 agent 配置的 runnerType 选择适配器并执行。
3
+ */
4
+ import { loadDesktopAgentConfig } from "../../config/desktop-config.js";
5
+ import { getAgentProxyAdapter } from "./registry.js";
6
+ export async function runForChannelStream(options, callbacks) {
7
+ const agentId = options.agentId ?? "default";
8
+ const config = await loadDesktopAgentConfig(agentId);
9
+ if (!config) {
10
+ throw new Error(`Agent config not found for agentId: ${agentId}`);
11
+ }
12
+ const runnerType = config.runnerType ?? "local";
13
+ const adapter = getAgentProxyAdapter(runnerType);
14
+ if (!adapter) {
15
+ throw new Error(`No AgentProxy adapter registered for type: "${runnerType}"`);
16
+ }
17
+ await adapter.runStream({ ...options, agentId }, config, callbacks);
18
+ }
19
+ export async function runForChannelCollect(options) {
20
+ const agentId = options.agentId ?? "default";
21
+ const config = await loadDesktopAgentConfig(agentId);
22
+ if (!config) {
23
+ throw new Error(`Agent config not found for agentId: ${agentId}`);
24
+ }
25
+ const runnerType = config.runnerType ?? "local";
26
+ const adapter = getAgentProxyAdapter(runnerType);
27
+ if (!adapter) {
28
+ throw new Error(`No AgentProxy adapter registered for type: "${runnerType}"`);
29
+ }
30
+ return adapter.runCollect({ ...options, agentId }, config);
31
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * AgentProxy 统一类型:适配器接口与执行参数。
3
+ */
4
+ import type { DesktopAgentConfig } from "../../config/desktop-config.js";
5
+ export interface RunAgentForChannelOptions {
6
+ sessionId: string;
7
+ message: string;
8
+ agentId: string;
9
+ }
10
+ export interface RunAgentStreamCallbacks {
11
+ onChunk(delta: string): void;
12
+ onTurnEnd?(): void;
13
+ onDone(): void;
14
+ }
15
+ /**
16
+ * 代理适配器接口:按平台实现,对外提供流式/一次性回复。
17
+ */
18
+ export interface IAgentProxyAdapter {
19
+ readonly type: string;
20
+ /**
21
+ * 流式执行:通过 callbacks 推送 delta,onDone 表示整轮结束。
22
+ */
23
+ runStream(options: RunAgentForChannelOptions, config: DesktopAgentConfig, callbacks: RunAgentStreamCallbacks): Promise<void>;
24
+ /**
25
+ * 一次性执行:返回完整回复文本。
26
+ */
27
+ runCollect(options: RunAgentForChannelOptions, config: DesktopAgentConfig): Promise<string>;
28
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -29,6 +29,38 @@ export interface ChannelsConfig {
29
29
  }
30
30
  /** MCP 服务器配置(与 core/mcp 类型一致,避免 core/config 依赖 core/mcp 实现) */
31
31
  export type DesktopMcpServerConfig = import("../mcp/index.js").McpServerConfig;
32
+ /** Agent 执行器类型:local=本机 pi-coding-agent,coze/openclawx=远程代理 */
33
+ export type AgentRunnerType = "local" | "coze" | "openclawx";
34
+ /** Coze 站点:国内站 api.coze.cn / 国际站 api.coze.com,凭证不通用 */
35
+ export type CozeRegion = "cn" | "com";
36
+ /** 某站点的 Bot 凭证(国内/国际各自独立) */
37
+ export interface CozeRegionCredentials {
38
+ botId: string;
39
+ apiKey: string;
40
+ }
41
+ /** Coze 代理配置(agents.json):按站点分别存凭证,请求时用当前 region 对应的一套 */
42
+ export interface AgentCozeConfig {
43
+ /** 当前使用的站点,未填默认 com */
44
+ region?: CozeRegion;
45
+ /** 国内站 (api.coze.cn) 凭证 */
46
+ cn?: CozeRegionCredentials;
47
+ /** 国际站 (api.coze.com) 凭证 */
48
+ com?: CozeRegionCredentials;
49
+ /** 可选:自定义 API 地址,覆盖当前站点的默认地址 */
50
+ endpoint?: string;
51
+ }
52
+ /** 解析后的 Coze 配置(供适配器使用):当前站点的 botId/apiKey + region/endpoint */
53
+ export interface CozeResolvedConfig {
54
+ botId: string;
55
+ apiKey: string;
56
+ region?: CozeRegion;
57
+ endpoint?: string;
58
+ }
59
+ /** OpenClawX 代理配置(代理到另一台 OpenClawX 实例) */
60
+ export interface AgentOpenClawXConfig {
61
+ baseUrl: string;
62
+ apiKey?: string;
63
+ }
32
64
  /**
33
65
  * 同步读取桌面全局配置中的 maxAgentSessions 等。
34
66
  * Gateway 进程内使用,用于会话上限等。
@@ -50,6 +82,12 @@ export interface DesktopAgentConfig {
50
82
  mcpServers?: DesktopMcpServerConfig[];
51
83
  /** 自定义系统提示词,会与技能等一起组成最终 systemPrompt */
52
84
  systemPrompt?: string;
85
+ /** 执行器类型,缺省 local */
86
+ runnerType?: AgentRunnerType;
87
+ /** Coze 代理配置(解析后:当前站点的凭证) */
88
+ coze?: CozeResolvedConfig;
89
+ /** OpenClawX 代理配置 */
90
+ openclawx?: AgentOpenClawXConfig;
53
91
  }
54
92
  /**
55
93
  * 从 config.json 读取缺省智能体 id(defaultAgentId)。
@@ -207,7 +207,70 @@ export async function loadDesktopAgentConfig(agentId) {
207
207
  const apiKey = provConfig?.apiKey && typeof provConfig.apiKey === "string" && provConfig.apiKey.trim()
208
208
  ? provConfig.apiKey.trim()
209
209
  : undefined;
210
- return { provider, model, apiKey: apiKey ?? undefined, workspace: workspaceName, mcpServers, systemPrompt };
210
+ let runnerType = "local";
211
+ let coze;
212
+ let openclawx;
213
+ if (existsSync(agentsPath)) {
214
+ try {
215
+ const rawAgents = await readFile(agentsPath, "utf-8");
216
+ const dataAgents = JSON.parse(rawAgents);
217
+ const agentsList = Array.isArray(dataAgents.agents) ? dataAgents.agents : [];
218
+ const agentRow = agentsList.find((a) => a.id === resolvedAgentId);
219
+ if (agentRow) {
220
+ if (agentRow.runnerType === "coze" || agentRow.runnerType === "openclawx") {
221
+ runnerType = agentRow.runnerType;
222
+ }
223
+ if (agentRow.coze) {
224
+ const row = agentRow.coze;
225
+ const region = row.region === "cn" || row.region === "com" ? row.region : "com";
226
+ const credsCn = row.cn;
227
+ const credsCom = row.com;
228
+ const legacyBotId = row.botId != null ? String(row.botId).trim() : "";
229
+ const legacyKey = row.apiKey != null ? String(row.apiKey).trim() : "";
230
+ const fromRegion = region === "cn"
231
+ ? credsCn && String(credsCn.botId || "").trim() && String(credsCn.apiKey || "").trim()
232
+ ? { botId: String(credsCn.botId).trim(), apiKey: String(credsCn.apiKey).trim() }
233
+ : null
234
+ : credsCom && String(credsCom.botId || "").trim() && String(credsCom.apiKey || "").trim()
235
+ ? { botId: String(credsCom.botId).trim(), apiKey: String(credsCom.apiKey).trim() }
236
+ : null;
237
+ const fromLegacy = legacyBotId && legacyKey ? { botId: legacyBotId, apiKey: legacyKey } : null;
238
+ const creds = fromRegion ?? fromLegacy;
239
+ if (creds) {
240
+ coze = {
241
+ botId: creds.botId,
242
+ apiKey: creds.apiKey,
243
+ region,
244
+ endpoint: row.endpoint?.trim(),
245
+ };
246
+ }
247
+ else if (runnerType === "coze") {
248
+ console.warn(`[loadDesktopAgentConfig] agentId=${resolvedAgentId} runnerType=coze but no credentials for region=${region} (configure 国内/国际 in proxy settings)`);
249
+ }
250
+ }
251
+ if (agentRow.openclawx?.baseUrl) {
252
+ openclawx = {
253
+ baseUrl: String(agentRow.openclawx.baseUrl).replace(/\/$/, ""),
254
+ apiKey: agentRow.openclawx.apiKey?.trim(),
255
+ };
256
+ }
257
+ }
258
+ }
259
+ catch {
260
+ // ignore
261
+ }
262
+ }
263
+ return {
264
+ provider,
265
+ model,
266
+ apiKey: apiKey ?? undefined,
267
+ workspace: workspaceName,
268
+ mcpServers,
269
+ systemPrompt,
270
+ runnerType,
271
+ coze,
272
+ openclawx,
273
+ };
211
274
  }
212
275
  function ensureDesktopDir() {
213
276
  const desktopDir = getDesktopDir();
@@ -31,11 +31,14 @@ function truncateForTelegram(text) {
31
31
  /**
32
32
  * 入站:长轮询 getUpdates,收到 message/edited_message 后转 UnifiedMessage 并分发。
33
33
  */
34
+ /** 轮询错误日志节流:同一类错误至少间隔此毫秒数再打印,避免刷屏 */
35
+ const TELEGRAM_POLL_ERROR_LOG_INTERVAL_MS = 60_000;
34
36
  class TelegramLongPollInbound {
35
37
  config;
36
38
  messageHandler = null;
37
39
  stopped = false;
38
40
  lastOffset = 0;
41
+ lastPollErrorLogTime = 0;
39
42
  constructor(config) {
40
43
  this.config = config;
41
44
  }
@@ -91,8 +94,16 @@ class TelegramLongPollInbound {
91
94
  }
92
95
  }
93
96
  catch (e) {
94
- if (!this.stopped)
95
- console.error("[Telegram] long poll error:", e);
97
+ if (!this.stopped) {
98
+ const now = Date.now();
99
+ if (now - this.lastPollErrorLogTime >= TELEGRAM_POLL_ERROR_LOG_INTERVAL_MS) {
100
+ this.lastPollErrorLogTime = now;
101
+ const cause = e?.cause?.code === "UND_ERR_CONNECT_TIMEOUT"
102
+ ? " (api.telegram.org 连接超时,可能需代理或网络不可达,将继续重试)"
103
+ : "";
104
+ console.warn("[Telegram] long poll error:", e?.message ?? e, cause);
105
+ }
106
+ }
96
107
  await new Promise((r) => setTimeout(r, 2000));
97
108
  }
98
109
  }
@@ -15,12 +15,10 @@ export interface RunAgentStreamCallbacks {
15
15
  onDone(): void;
16
16
  }
17
17
  /**
18
- * 使用现有 agentManager 跑一轮对话,以流式回调方式推送助手回复(onChunk/onDone)。
19
- * onDone 在 agent_end 时调用,保证「对话真正结束」语义(多轮 tool+文本合并为一条回复)。
18
+ * 使用 AgentProxy 统一入口跑一轮对话,以流式回调方式推送助手回复(onChunk/onDone)。
20
19
  */
21
20
  export declare function runAgentAndStreamReply(options: RunAgentForChannelOptions, callbacks: RunAgentStreamCallbacks): Promise<void>;
22
21
  /**
23
- * 使用现有 agentManager 跑一轮对话,收集助手完整回复文本后返回。
24
- * 不依赖 WebSocket 客户端,供通道核心调用。
22
+ * 使用 AgentProxy 统一入口跑一轮对话,收集助手完整回复文本后返回。
25
23
  */
26
24
  export declare function runAgentAndCollectReply(options: RunAgentForChannelOptions): Promise<string>;
@@ -1,137 +1,25 @@
1
1
  /**
2
2
  * 通道用:跑 Agent 并收集完整回复文本(无 WebSocket 客户端)。
3
+ * 委托给 core/agent/proxy 统一入口,按 runnerType 分发到 local/coze/openclawx 等适配器。
3
4
  */
4
- import { agentManager } from "../../core/agent/agent-manager.js";
5
- import { getDesktopConfig, loadDesktopAgentConfig } from "../../core/config/desktop-config.js";
6
- import { getExperienceContextForUserMessage } from "../../core/memory/index.js";
5
+ import { runForChannelStream, runForChannelCollect } from "../../core/agent/proxy/index.js";
7
6
  /**
8
- * 使用现有 agentManager 跑一轮对话,以流式回调方式推送助手回复(onChunk/onDone)。
9
- * onDone 在 agent_end 时调用,保证「对话真正结束」语义(多轮 tool+文本合并为一条回复)。
7
+ * 使用 AgentProxy 统一入口跑一轮对话,以流式回调方式推送助手回复(onChunk/onDone)。
10
8
  */
11
9
  export async function runAgentAndStreamReply(options, callbacks) {
12
- const { sessionId, message, agentId: optionAgentId } = options;
13
- const sessionAgentId = optionAgentId ?? "default";
14
- let workspace = "default";
15
- let provider;
16
- let modelId;
17
- let apiKey;
18
- const agentConfig = await loadDesktopAgentConfig(sessionAgentId);
19
- if (agentConfig) {
20
- if (agentConfig.workspace)
21
- workspace = agentConfig.workspace;
22
- provider = agentConfig.provider;
23
- modelId = agentConfig.model;
24
- if (agentConfig.apiKey)
25
- apiKey = agentConfig.apiKey;
26
- }
27
- const { maxAgentSessions } = getDesktopConfig();
28
- const session = await agentManager.getOrCreateSession(sessionId, {
29
- agentId: sessionAgentId,
30
- workspace,
31
- provider,
32
- modelId,
33
- apiKey,
34
- maxSessions: maxAgentSessions,
35
- targetAgentId: sessionAgentId,
36
- mcpServers: agentConfig?.mcpServers,
37
- systemPrompt: agentConfig?.systemPrompt,
38
- });
39
- let resolveDone;
40
- const donePromise = new Promise((r) => {
41
- resolveDone = r;
42
- });
43
- const unsubscribe = session.subscribe((event) => {
44
- if (event.type === "message_update") {
45
- const update = event;
46
- if (update.assistantMessageEvent?.type === "text_delta" && update.assistantMessageEvent?.delta) {
47
- callbacks.onChunk(update.assistantMessageEvent.delta);
48
- }
49
- }
50
- else if (event.type === "turn_end") {
51
- callbacks.onTurnEnd?.();
52
- }
53
- else if (event.type === "agent_end") {
54
- callbacks.onDone();
55
- resolveDone();
56
- }
57
- });
58
- try {
59
- const experienceBlock = await getExperienceContextForUserMessage();
60
- const userMessageToSend = experienceBlock.trim().length > 0
61
- ? `${experienceBlock}\n\n用户问题:\n${message}`
62
- : message;
63
- await session.sendUserMessage(userMessageToSend, { deliverAs: "followUp" });
64
- await Promise.race([
65
- donePromise,
66
- new Promise((_, rej) => setTimeout(() => rej(new Error("Channel agent reply timeout")), 120_000)),
67
- ]);
68
- }
69
- finally {
70
- unsubscribe();
71
- }
10
+ await runForChannelStream({
11
+ sessionId: options.sessionId,
12
+ message: options.message,
13
+ agentId: options.agentId ?? "default",
14
+ }, callbacks);
72
15
  }
73
16
  /**
74
- * 使用现有 agentManager 跑一轮对话,收集助手完整回复文本后返回。
75
- * 不依赖 WebSocket 客户端,供通道核心调用。
17
+ * 使用 AgentProxy 统一入口跑一轮对话,收集助手完整回复文本后返回。
76
18
  */
77
19
  export async function runAgentAndCollectReply(options) {
78
- const { sessionId, message, agentId: optionAgentId } = options;
79
- const sessionAgentId = optionAgentId ?? "default";
80
- let workspace = "default";
81
- let provider;
82
- let modelId;
83
- let apiKey;
84
- const agentConfig = await loadDesktopAgentConfig(sessionAgentId);
85
- if (agentConfig) {
86
- if (agentConfig.workspace)
87
- workspace = agentConfig.workspace;
88
- provider = agentConfig.provider;
89
- modelId = agentConfig.model;
90
- if (agentConfig.apiKey)
91
- apiKey = agentConfig.apiKey;
92
- }
93
- const { maxAgentSessions } = getDesktopConfig();
94
- const session = await agentManager.getOrCreateSession(sessionId, {
95
- agentId: sessionAgentId,
96
- workspace,
97
- provider,
98
- modelId,
99
- apiKey,
100
- maxSessions: maxAgentSessions,
101
- targetAgentId: sessionAgentId,
102
- mcpServers: agentConfig?.mcpServers,
103
- systemPrompt: agentConfig?.systemPrompt,
104
- });
105
- const chunks = [];
106
- let resolveDone;
107
- const donePromise = new Promise((r) => {
108
- resolveDone = r;
109
- });
110
- const unsubscribe = session.subscribe((event) => {
111
- if (event.type === "message_update") {
112
- const update = event;
113
- if (update.assistantMessageEvent?.type === "text_delta" && update.assistantMessageEvent?.delta) {
114
- chunks.push(update.assistantMessageEvent.delta);
115
- }
116
- }
117
- else if (event.type === "agent_end") {
118
- resolveDone();
119
- }
20
+ return runForChannelCollect({
21
+ sessionId: options.sessionId,
22
+ message: options.message,
23
+ agentId: options.agentId ?? "default",
120
24
  });
121
- try {
122
- const experienceBlock = await getExperienceContextForUserMessage();
123
- const userMessageToSend = experienceBlock.trim().length > 0
124
- ? `${experienceBlock}\n\n用户问题:\n${message}`
125
- : message;
126
- await session.sendUserMessage(userMessageToSend, { deliverAs: "followUp" });
127
- // 等待 agent_end(整轮对话真正结束)或超时
128
- await Promise.race([
129
- donePromise,
130
- new Promise((_, rej) => setTimeout(() => rej(new Error("Channel agent reply timeout")), 120_000)),
131
- ]);
132
- }
133
- finally {
134
- unsubscribe();
135
- }
136
- return chunks.join("").trim() || "(无文本回复)";
137
25
  }
@@ -1,4 +1,5 @@
1
1
  import { agentManager } from "../../core/agent/agent-manager.js";
2
+ import { runForChannelStream } from "../../core/agent/proxy/index.js";
2
3
  import { getSessionCurrentAgentResolver, getSessionCurrentAgentUpdater } from "../../core/session-current-agent.js";
3
4
  import { getExperienceContextForUserMessage } from "../../core/memory/index.js";
4
5
  import { send, createEvent } from "../utils.js";
@@ -43,6 +44,7 @@ async function handleAgentChatInner(client, targetSessionId, message, params) {
43
44
  getSessionCurrentAgentUpdater()?.(targetSessionId, params.agentId);
44
45
  currentAgentId = params.agentId;
45
46
  }
47
+ console.log(`[agent.chat] session=${targetSessionId} resolved agentId=${currentAgentId} (params=${params.agentId ?? "—"}, stored=${storedAgentId ?? "—"}, client=${client.agentId ?? "—"})`);
46
48
  let workspace = "default";
47
49
  let provider;
48
50
  let modelId;
@@ -56,6 +58,38 @@ async function handleAgentChatInner(client, targetSessionId, message, params) {
56
58
  if (agentConfig.apiKey)
57
59
  apiKey = agentConfig.apiKey;
58
60
  }
61
+ const runnerType = agentConfig?.runnerType ?? "local";
62
+ const isProxyAgent = runnerType === "coze" || runnerType === "openclawx";
63
+ if (isProxyAgent) {
64
+ console.log(`[agent.chat] Using proxy agent (${runnerType}) for session=${targetSessionId}, agentId=${currentAgentId}`);
65
+ }
66
+ // 代理智能体(Coze / OpenClawX):走 AgentProxy 统一入口,流式结果通过 WebSocket 推给客户端
67
+ if (isProxyAgent) {
68
+ try {
69
+ await runForChannelStream({
70
+ sessionId: targetSessionId,
71
+ message,
72
+ agentId: currentAgentId,
73
+ }, {
74
+ onChunk(delta) {
75
+ broadcastToSession(targetSessionId, createEvent("agent.chunk", { text: delta }));
76
+ },
77
+ onTurnEnd() {
78
+ broadcastToSession(targetSessionId, createEvent("turn_end", { sessionId: targetSessionId, content: "" }));
79
+ broadcastToSession(targetSessionId, createEvent("message_complete", { sessionId: targetSessionId, content: "" }));
80
+ },
81
+ onDone() {
82
+ broadcastToSession(targetSessionId, createEvent("agent_end", { sessionId: targetSessionId }));
83
+ broadcastToSession(targetSessionId, createEvent("conversation_end", { sessionId: targetSessionId }));
84
+ },
85
+ });
86
+ return { status: "completed", sessionId: targetSessionId };
87
+ }
88
+ catch (error) {
89
+ console.error(`Error in agent chat (proxy ${runnerType}):`, error);
90
+ throw error;
91
+ }
92
+ }
59
93
  const isEphemeralSession = sessionType === "system" || sessionType === "scheduled";
60
94
  if (isEphemeralSession) {
61
95
  agentManager.deleteSession(targetSessionId + COMPOSITE_KEY_SEP + currentAgentId);
@@ -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'>>): Promise<{
28
+ updateAgent(id: string, body: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers' | 'systemPrompt' | 'icon' | 'runnerType' | 'coze' | 'openclawx'>>): Promise<{
29
29
  success: boolean;
30
30
  data: AgentConfigItem;
31
31
  }>;
@@ -1,6 +1,26 @@
1
1
  import type { McpServerConfig } from '../../core/mcp/index.js';
2
2
  /** 缺省智能体 ID / 工作空间名,不可删除;对应目录 ~/.openbot/workspace/default */
3
3
  export declare const DEFAULT_AGENT_ID = "default";
4
+ /** 执行器类型:local=本机,coze/openclawx=远程代理 */
5
+ export type AgentRunnerType = 'local' | 'coze' | 'openclawx';
6
+ /** Coze 站点:cn=国内 api.coze.cn,com=国际 api.coze.com,凭证不通用 */
7
+ export type CozeRegion = 'cn' | 'com';
8
+ /** 某站点的 Bot 凭证 */
9
+ export interface CozeRegionCredentials {
10
+ botId: string;
11
+ apiKey: string;
12
+ }
13
+ /** Coze 代理配置(存储):按站点分别存凭证,请求时用 region 对应的一套 */
14
+ export interface AgentCozeConfig {
15
+ region?: CozeRegion;
16
+ cn?: CozeRegionCredentials;
17
+ com?: CozeRegionCredentials;
18
+ endpoint?: string;
19
+ }
20
+ export interface AgentOpenClawXConfig {
21
+ baseUrl: string;
22
+ apiKey?: string;
23
+ }
4
24
  /**
5
25
  * 智能体列表与配置使用文件存储(~/.openbot/desktop/agents.json),不使用 SQLite。
6
26
  * 会话与消息历史使用 SQLite;Skills、工作空间文档为目录文件管理。
@@ -21,6 +41,12 @@ export interface AgentConfigItem {
21
41
  systemPrompt?: string;
22
42
  /** 智能体图标标识(前端预设图标 id,如 default、star、code 等) */
23
43
  icon?: string;
44
+ /** 执行器类型:local=本机,coze/openclawx=远程代理 */
45
+ runnerType?: AgentRunnerType;
46
+ /** Coze 代理配置(runnerType 为 coze 时使用) */
47
+ coze?: AgentCozeConfig;
48
+ /** OpenClawX 代理配置(runnerType 为 openclawx 时使用) */
49
+ openclawx?: AgentOpenClawXConfig;
24
50
  }
25
51
  export declare class AgentConfigService {
26
52
  private configDir;
@@ -45,7 +71,7 @@ export declare class AgentConfigService {
45
71
  /** 智能体图标标识 */
46
72
  icon?: string;
47
73
  }): Promise<AgentConfigItem>;
48
- updateAgent(id: string, updates: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers' | 'systemPrompt' | 'icon'>>): Promise<AgentConfigItem>;
74
+ updateAgent(id: string, updates: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers' | 'systemPrompt' | 'icon' | 'runnerType' | 'coze' | 'openclawx'>>): Promise<AgentConfigItem>;
49
75
  deleteAgent(id: string): Promise<void>;
50
76
  /**
51
77
  * 根据 config 的 defaultProvider / defaultModel / defaultModelItemCode 及 configuredModels 同步 agents.json 中缺省智能体的 provider、model、modelItemCode。
@@ -159,6 +159,49 @@ let AgentConfigService = class AgentConfigService {
159
159
  agent.systemPrompt = updates.systemPrompt?.trim() || undefined;
160
160
  if (updates.icon !== undefined)
161
161
  agent.icon = updates.icon?.trim() || undefined;
162
+ if (updates.runnerType !== undefined)
163
+ agent.runnerType = updates.runnerType;
164
+ if (updates.coze !== undefined) {
165
+ const incoming = updates.coze;
166
+ const endpointVal = incoming.endpoint != null ? String(incoming.endpoint).trim() : '';
167
+ const region = incoming.region === 'cn' || incoming.region === 'com' ? incoming.region : agent.coze?.region || 'com';
168
+ const mergeOne = (inCreds, existing) => {
169
+ const botId = String((inCreds?.botId ?? existing?.botId) ?? '').trim();
170
+ if (!botId)
171
+ return existing;
172
+ const apiKey = (inCreds?.apiKey != null && String(inCreds.apiKey).trim()) ||
173
+ (existing?.apiKey != null ? String(existing.apiKey).trim() : '') ||
174
+ '';
175
+ return { botId, apiKey };
176
+ };
177
+ const cn = incoming.cn !== undefined ? mergeOne(incoming.cn, agent.coze?.cn) : agent.coze?.cn;
178
+ const com = incoming.com !== undefined ? mergeOne(incoming.com, agent.coze?.com) : agent.coze?.com;
179
+ if ((incoming.botId != null || incoming.apiKey != null) &&
180
+ (agent.coze?.cn == null || agent.coze?.com == null)) {
181
+ const flatBotId = String(incoming.botId ?? agent.coze?.cn?.botId ?? agent.coze?.com?.botId ?? '').trim();
182
+ const flatKey = (incoming.apiKey != null && String(incoming.apiKey).trim()) ||
183
+ (agent.coze?.cn?.apiKey && String(agent.coze.cn.apiKey).trim()) ||
184
+ (agent.coze?.com?.apiKey && String(agent.coze.com.apiKey).trim()) ||
185
+ '';
186
+ const flat = flatBotId ? { botId: flatBotId, apiKey: flatKey } : undefined;
187
+ agent.coze = {
188
+ region,
189
+ cn: cn ?? flat ?? undefined,
190
+ com: com ?? flat ?? undefined,
191
+ endpoint: endpointVal || undefined,
192
+ };
193
+ }
194
+ else {
195
+ agent.coze = {
196
+ region,
197
+ cn: cn ?? undefined,
198
+ com: com ?? undefined,
199
+ endpoint: endpointVal || undefined,
200
+ };
201
+ }
202
+ }
203
+ if (updates.openclawx !== undefined)
204
+ agent.openclawx = updates.openclawx;
162
205
  await this.writeAgentsFile(file);
163
206
  return { ...agent, isDefault: agent.id === DEFAULT_AGENT_ID };
164
207
  }
@@ -1,3 +1,4 @@
1
+ import { Response } from 'express';
1
2
  import { AgentsService } from './agents.service.js';
2
3
  export declare class AgentsController {
3
4
  private readonly agentsService;
@@ -48,4 +49,19 @@ export declare class AgentsController {
48
49
  }): {
49
50
  success: boolean;
50
51
  };
52
+ /** 供远程 OpenClawX 代理调用:一次性返回助手回复 */
53
+ proxyChat(body: {
54
+ sessionId: string;
55
+ message: string;
56
+ agentId?: string;
57
+ }): Promise<{
58
+ success: boolean;
59
+ text: string;
60
+ }>;
61
+ /** 供远程 OpenClawX 代理调用:SSE 流式返回助手回复 */
62
+ proxyChatStream(body: {
63
+ sessionId: string;
64
+ message: string;
65
+ agentId?: string;
66
+ }, res: Response): Promise<void>;
51
67
  }
@@ -10,8 +10,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  return function (target, key) { decorator(target, key, paramIndex); }
12
12
  };
13
- import { Controller, Delete, Get, Patch, Post, Body, Param, Header, HttpException, HttpStatus, } from '@nestjs/common';
13
+ import { Controller, Delete, Get, Patch, Post, Body, Param, Res, Header, HttpException, HttpStatus, } from '@nestjs/common';
14
14
  import { AgentsService } from './agents.service.js';
15
+ import { runForChannelStream, runForChannelCollect } from '../../core/agent/proxy/index.js';
15
16
  let AgentsController = class AgentsController {
16
17
  agentsService;
17
18
  constructor(agentsService) {
@@ -86,6 +87,48 @@ let AgentsController = class AgentsController {
86
87
  });
87
88
  return { success: true };
88
89
  }
90
+ /** 供远程 OpenClawX 代理调用:一次性返回助手回复 */
91
+ async proxyChat(body) {
92
+ const sessionId = body?.sessionId ?? '';
93
+ const message = body?.message ?? '';
94
+ const agentId = body?.agentId ?? 'default';
95
+ if (!sessionId || !message) {
96
+ throw new HttpException('sessionId and message are required', HttpStatus.BAD_REQUEST);
97
+ }
98
+ try {
99
+ const text = await runForChannelCollect({ sessionId, message, agentId });
100
+ return { success: true, text };
101
+ }
102
+ catch (error) {
103
+ throw new HttpException(error?.message ?? 'Proxy chat failed', HttpStatus.INTERNAL_SERVER_ERROR);
104
+ }
105
+ }
106
+ /** 供远程 OpenClawX 代理调用:SSE 流式返回助手回复 */
107
+ async proxyChatStream(body, res) {
108
+ const sessionId = body?.sessionId ?? '';
109
+ const message = body?.message ?? '';
110
+ const agentId = body?.agentId ?? 'default';
111
+ if (!sessionId || !message) {
112
+ res.status(400).json({ error: 'sessionId and message are required' });
113
+ return;
114
+ }
115
+ res.flushHeaders();
116
+ try {
117
+ await runForChannelStream({ sessionId, message, agentId }, {
118
+ onChunk(delta) {
119
+ res.write(`data: ${JSON.stringify({ delta })}\n\n`);
120
+ },
121
+ onDone() {
122
+ res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
123
+ res.end();
124
+ },
125
+ });
126
+ }
127
+ catch (error) {
128
+ res.write(`data: ${JSON.stringify({ error: error?.message ?? 'Stream failed' })}\n\n`);
129
+ res.end();
130
+ }
131
+ }
89
132
  };
90
133
  __decorate([
91
134
  Post('sessions'),
@@ -145,6 +188,24 @@ __decorate([
145
188
  __metadata("design:paramtypes", [String, Object]),
146
189
  __metadata("design:returntype", void 0)
147
190
  ], AgentsController.prototype, "appendMessage", null);
191
+ __decorate([
192
+ Post('proxy-chat'),
193
+ __param(0, Body()),
194
+ __metadata("design:type", Function),
195
+ __metadata("design:paramtypes", [Object]),
196
+ __metadata("design:returntype", Promise)
197
+ ], AgentsController.prototype, "proxyChat", null);
198
+ __decorate([
199
+ Post('proxy-chat/stream'),
200
+ Header('Content-Type', 'text/event-stream'),
201
+ Header('Cache-Control', 'no-cache'),
202
+ Header('Connection', 'keep-alive'),
203
+ __param(0, Body()),
204
+ __param(1, Res()),
205
+ __metadata("design:type", Function),
206
+ __metadata("design:paramtypes", [Object, Object]),
207
+ __metadata("design:returntype", Promise)
208
+ ], AgentsController.prototype, "proxyChatStream", null);
148
209
  AgentsController = __decorate([
149
210
  Controller('agents'),
150
211
  __metadata("design:paramtypes", [AgentsService])