@next-open-ai/openbot 0.3.2 → 0.6.6

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 (55) hide show
  1. package/README.md +1 -2
  2. package/apps/desktop/renderer/dist/assets/index-BNuvb6Ay.css +10 -0
  3. package/apps/desktop/renderer/dist/assets/index-DvQjslfT.js +89 -0
  4. package/apps/desktop/renderer/dist/index.html +2 -2
  5. package/dist/core/agent/agent-manager.d.ts +15 -7
  6. package/dist/core/agent/agent-manager.js +52 -21
  7. package/dist/core/agent/run.js +2 -2
  8. package/dist/core/config/desktop-config.d.ts +13 -0
  9. package/dist/core/config/desktop-config.js +19 -1
  10. package/dist/core/session-current-agent.d.ts +34 -0
  11. package/dist/core/session-current-agent.js +32 -0
  12. package/dist/core/tools/create-agent-tool.d.ts +6 -0
  13. package/dist/core/tools/create-agent-tool.js +97 -0
  14. package/dist/core/tools/index.d.ts +3 -0
  15. package/dist/core/tools/index.js +3 -0
  16. package/dist/core/tools/list-agents-tool.d.ts +5 -0
  17. package/dist/core/tools/list-agents-tool.js +45 -0
  18. package/dist/core/tools/switch-agent-tool.d.ts +6 -0
  19. package/dist/core/tools/switch-agent-tool.js +54 -0
  20. package/dist/gateway/channel/adapters/feishu.d.ts +11 -0
  21. package/dist/gateway/channel/adapters/feishu.js +218 -0
  22. package/dist/gateway/channel/channel-core.d.ts +9 -0
  23. package/dist/gateway/channel/channel-core.js +127 -0
  24. package/dist/gateway/channel/registry.d.ts +16 -0
  25. package/dist/gateway/channel/registry.js +54 -0
  26. package/dist/gateway/channel/run-agent.d.ts +26 -0
  27. package/dist/gateway/channel/run-agent.js +137 -0
  28. package/dist/gateway/channel/session-persistence.d.ts +36 -0
  29. package/dist/gateway/channel/session-persistence.js +46 -0
  30. package/dist/gateway/channel/types.d.ts +70 -0
  31. package/dist/gateway/channel/types.js +4 -0
  32. package/dist/gateway/channel-handler.d.ts +3 -4
  33. package/dist/gateway/channel-handler.js +8 -2
  34. package/dist/gateway/methods/agent-chat.js +30 -12
  35. package/dist/gateway/methods/run-scheduled-task.js +4 -2
  36. package/dist/gateway/server.js +50 -1
  37. package/dist/server/agent-config/agent-config.controller.d.ts +6 -1
  38. package/dist/server/agent-config/agent-config.service.d.ts +12 -1
  39. package/dist/server/agent-config/agent-config.service.js +10 -3
  40. package/dist/server/agents/agents.controller.d.ts +10 -0
  41. package/dist/server/agents/agents.controller.js +35 -1
  42. package/dist/server/agents/agents.gateway.js +18 -4
  43. package/dist/server/agents/agents.service.d.ts +4 -0
  44. package/dist/server/agents/agents.service.js +17 -1
  45. package/dist/server/config/config.controller.d.ts +2 -0
  46. package/dist/server/config/config.service.d.ts +3 -0
  47. package/dist/server/config/config.service.js +3 -1
  48. package/dist/server/saved-items/saved-items.controller.d.ts +32 -1
  49. package/dist/server/saved-items/saved-items.controller.js +154 -3
  50. package/dist/server/saved-items/saved-items.module.js +3 -1
  51. package/dist/server/workspace/workspace.service.d.ts +11 -0
  52. package/dist/server/workspace/workspace.service.js +40 -1
  53. package/package.json +2 -1
  54. package/apps/desktop/renderer/dist/assets/index-DKtaRFW4.js +0 -89
  55. package/apps/desktop/renderer/dist/assets/index-QHuqXpWQ.css +0 -10
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 通道模块统一类型:与传输方式(webhook / 长连接)解耦。
3
+ */
4
+ /** 统一入站消息:所有入站解析后的共同结构 */
5
+ export interface UnifiedMessage {
6
+ /** 通道标识,如 feishu / telegram */
7
+ channelId: string;
8
+ /** 平台内会话/群组/单聊标识,回复时用 */
9
+ threadId: string;
10
+ /** 平台内用户标识 */
11
+ userId: string;
12
+ /** 用户显示名(可选) */
13
+ userName?: string;
14
+ /** 消息正文 */
15
+ messageText: string;
16
+ /** 附件(可选) */
17
+ attachments?: {
18
+ type: string;
19
+ url?: string;
20
+ name?: string;
21
+ }[];
22
+ /** 原始 payload(排查用) */
23
+ raw?: unknown;
24
+ /** 回复应交给哪个出站,如 "default" 或 connectionId */
25
+ replyTarget?: string;
26
+ /** 平台消息 ID(可选,用于回复引用等) */
27
+ messageId?: string;
28
+ }
29
+ /** 统一出站回复 */
30
+ export interface UnifiedReply {
31
+ text: string;
32
+ attachments?: {
33
+ type: string;
34
+ url?: string;
35
+ name?: string;
36
+ }[];
37
+ }
38
+ /** 入站传输:从外部收数据 → 产出 UnifiedMessage */
39
+ export interface IInboundTransport {
40
+ start(): Promise<void>;
41
+ stop(): Promise<void>;
42
+ /** 设置收到消息时的回调 */
43
+ setMessageHandler(handler: (msg: UnifiedMessage) => void | Promise<void>): void;
44
+ }
45
+ /** 流式出站:先发一条占位消息,再由调用方按累积内容多次更新。返回的 sink 由 channel-core 在 onChunk/onDone 时调用。 */
46
+ export interface StreamSink {
47
+ /** 更新当前已累积的全文(节流后调用) */
48
+ onChunk(accumulated: string): void | Promise<void>;
49
+ /** 流结束,做最终一次更新 */
50
+ onDone(accumulated: string): void | Promise<void>;
51
+ }
52
+ /** 出站传输:将 UnifiedReply 发到外部 */
53
+ export interface IOutboundTransport {
54
+ send(targetId: string, reply: UnifiedReply): Promise<void>;
55
+ /** 可选:该出站是否还能往 targetId 发(如连接是否有效) */
56
+ canSend?(targetId: string): boolean;
57
+ /** 可选:流式发送。先创建占位消息,返回 sink 供调用方按累积内容更新(如飞书 create + patch)。 */
58
+ sendStream?(targetId: string): Promise<StreamSink>;
59
+ }
60
+ /** 通道:身份 + 入站/出站列表 + 回复路由 */
61
+ export interface IChannel {
62
+ id: string;
63
+ name: string;
64
+ /** 默认 agentId,用于会话 */
65
+ defaultAgentId?: string;
66
+ getInbounds(): IInboundTransport[];
67
+ getOutbounds(): IOutboundTransport[];
68
+ /** 根据消息或 threadId 选择出站;默认返回第一个 outbound */
69
+ getOutboundForMessage?(msg: UnifiedMessage): IOutboundTransport | undefined;
70
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 通道模块统一类型:与传输方式(webhook / 长连接)解耦。
3
+ */
4
+ export {};
@@ -1,6 +1,5 @@
1
1
  /**
2
- * 通道模块 HTTP 处理(占位)。
3
- * 后续:接收外部 webhook、回调,校验后转内部事件;当前返回 501。
2
+ * 通道模块 HTTP 处理:GET 列出已配置通道;具体入站由各通道(如飞书 WebSocket)自行处理。
4
3
  */
5
- import type { Request, Response } from 'express';
6
- export declare function handleChannel(_req: Request, res: Response): void;
4
+ import type { Request, Response } from "express";
5
+ export declare function handleChannel(req: Request, res: Response): void;
@@ -1,3 +1,9 @@
1
- export function handleChannel(_req, res) {
2
- res.status(501).setHeader('Content-Type', 'application/json').end(JSON.stringify({ ok: false, message: 'Channel module not implemented yet' }));
1
+ import { listChannels } from "./channel/registry.js";
2
+ export function handleChannel(req, res) {
3
+ if (req.method === "GET") {
4
+ const channels = listChannels().map((c) => ({ id: c.id, name: c.name }));
5
+ res.status(200).setHeader("Content-Type", "application/json").end(JSON.stringify({ ok: true, channels }));
6
+ return;
7
+ }
8
+ res.status(404).setHeader("Content-Type", "application/json").end(JSON.stringify({ ok: false, message: "Not found" }));
3
9
  }
@@ -1,8 +1,10 @@
1
1
  import { agentManager } from "../../core/agent/agent-manager.js";
2
+ import { getSessionCurrentAgentResolver, getSessionCurrentAgentUpdater } from "../../core/session-current-agent.js";
2
3
  import { getExperienceContextForUserMessage } from "../../core/memory/index.js";
3
4
  import { send, createEvent } from "../utils.js";
4
5
  import { connectedClients } from "../clients.js";
5
6
  import { getDesktopConfig, loadDesktopAgentConfig } from "../../core/config/desktop-config.js";
7
+ const COMPOSITE_KEY_SEP = "::";
6
8
  /**
7
9
  * Broadcast message to all clients subscribed to a session
8
10
  */
@@ -31,14 +33,21 @@ export async function handleAgentChat(client, params) {
31
33
  }
32
34
  async function handleAgentChatInner(client, targetSessionId, message, params) {
33
35
  const { targetAgentId } = params;
34
- // 客户端在 connect 或 agent.chat 传入 agentId/sessionType,Gateway 不再请求 Nest
35
- const sessionAgentId = params.agentId ?? client.agentId ?? "default";
36
36
  const sessionType = params.sessionType ?? client.sessionType ?? "chat";
37
+ // 当前 agent:请求传入 > 已存(DB)> 连接/默认
38
+ const resolveCurrentAgent = getSessionCurrentAgentResolver();
39
+ const storedAgentId = resolveCurrentAgent?.(targetSessionId);
40
+ const clientAgentId = params.agentId ?? client.agentId ?? "default";
41
+ let currentAgentId = params.agentId ?? storedAgentId ?? clientAgentId ?? "default";
42
+ if (params.agentId) {
43
+ getSessionCurrentAgentUpdater()?.(targetSessionId, params.agentId);
44
+ currentAgentId = params.agentId;
45
+ }
37
46
  let workspace = "default";
38
47
  let provider;
39
48
  let modelId;
40
49
  let apiKey;
41
- const agentConfig = await loadDesktopAgentConfig(sessionAgentId);
50
+ const agentConfig = await loadDesktopAgentConfig(currentAgentId);
42
51
  if (agentConfig) {
43
52
  if (agentConfig.workspace)
44
53
  workspace = agentConfig.workspace;
@@ -47,17 +56,16 @@ async function handleAgentChatInner(client, targetSessionId, message, params) {
47
56
  if (agentConfig.apiKey)
48
57
  apiKey = agentConfig.apiKey;
49
58
  }
50
- // system / scheduled:每次对话前先删再建,对话结束马上关闭,节省资源
51
59
  const isEphemeralSession = sessionType === "system" || sessionType === "scheduled";
52
60
  if (isEphemeralSession) {
53
- agentManager.deleteSession(targetSessionId);
61
+ agentManager.deleteSession(targetSessionId + COMPOSITE_KEY_SEP + currentAgentId);
54
62
  }
55
- // system 会话用请求里的 targetAgentId;chat/scheduled session 对应的 agentId 传给 install_skill
56
- const effectiveTargetAgentId = sessionType === "system" ? targetAgentId : sessionAgentId;
63
+ const effectiveTargetAgentId = sessionType === "system" ? targetAgentId : currentAgentId;
57
64
  const { maxAgentSessions } = getDesktopConfig();
58
65
  let session;
59
66
  try {
60
67
  session = await agentManager.getOrCreateSession(targetSessionId, {
68
+ agentId: currentAgentId,
61
69
  workspace,
62
70
  provider,
63
71
  modelId,
@@ -65,6 +73,7 @@ async function handleAgentChatInner(client, targetSessionId, message, params) {
65
73
  maxSessions: maxAgentSessions,
66
74
  targetAgentId: effectiveTargetAgentId,
67
75
  mcpServers: agentConfig?.mcpServers,
76
+ systemPrompt: agentConfig?.systemPrompt,
68
77
  });
69
78
  }
70
79
  catch (err) {
@@ -75,7 +84,7 @@ async function handleAgentChatInner(client, targetSessionId, message, params) {
75
84
  }
76
85
  throw err;
77
86
  }
78
- // Set up event listener for streaming
87
+ // 向各通道广播:turn_end(本小轮结束)、agent_end(整轮对话结束),并保留 message_complete / conversation_end 兼容。各端按需处理。
79
88
  const unsubscribe = session.subscribe((event) => {
80
89
  // console.log(`Agent event received: ${event.type}`); // Reduce noise
81
90
  let wsMessage = null;
@@ -112,17 +121,26 @@ async function handleAgentChatInner(client, targetSessionId, message, params) {
112
121
  const usagePayload = promptTokens > 0 || completionTokens > 0
113
122
  ? { promptTokens, completionTokens }
114
123
  : undefined;
115
- wsMessage = createEvent("message_complete", {
124
+ const turnPayload = {
116
125
  sessionId: targetSessionId,
117
126
  content: "",
118
127
  ...(usagePayload && { usage: usagePayload }),
119
- });
128
+ };
129
+ broadcastToSession(targetSessionId, createEvent("turn_end", turnPayload));
130
+ broadcastToSession(targetSessionId, createEvent("message_complete", turnPayload));
131
+ wsMessage = null;
132
+ }
133
+ else if (event.type === "agent_end") {
134
+ const agentPayload = { sessionId: targetSessionId };
135
+ broadcastToSession(targetSessionId, createEvent("agent_end", agentPayload));
136
+ broadcastToSession(targetSessionId, createEvent("conversation_end", agentPayload));
137
+ wsMessage = null;
120
138
  }
121
139
  if (wsMessage) {
122
140
  broadcastToSession(targetSessionId, wsMessage);
123
141
  }
124
- if (event.type === "turn_end" && isEphemeralSession) {
125
- agentManager.deleteSession(targetSessionId);
142
+ if (event.type === "agent_end" && isEphemeralSession) {
143
+ agentManager.deleteSession(targetSessionId + COMPOSITE_KEY_SEP + currentAgentId);
126
144
  }
127
145
  });
128
146
  try {
@@ -49,13 +49,16 @@ export async function handleRunScheduledTask(req, res) {
49
49
  if (agentConfig.apiKey)
50
50
  apiKey = agentConfig.apiKey;
51
51
  }
52
+ const COMPOSITE_KEY_SEP = "::";
52
53
  try {
53
54
  const session = await agentManager.getOrCreateSession(sessionId, {
55
+ agentId: sessionAgentId,
54
56
  workspace: resolvedWorkspace,
55
57
  provider,
56
58
  modelId,
57
59
  apiKey,
58
60
  mcpServers: agentConfig?.mcpServers,
61
+ systemPrompt: agentConfig?.systemPrompt,
59
62
  });
60
63
  let assistantContent = "";
61
64
  let turnPromptTokens = 0;
@@ -121,7 +124,6 @@ export async function handleRunScheduledTask(req, res) {
121
124
  res.end(JSON.stringify({ success: false, error: friendlyError }));
122
125
  }
123
126
  finally {
124
- // 执行完成(成功或失败)后立即关闭并移除该 AgentSession,避免空悬占用内存/连接等资源
125
- agentManager.deleteSession(sessionId);
127
+ agentManager.deleteSession(sessionId + COMPOSITE_KEY_SEP + sessionAgentId);
126
128
  }
127
129
  }
@@ -35,8 +35,14 @@ import multer from "multer";
35
35
  import { handleInstallSkillFromPath } from "./methods/install-skill-from-path.js";
36
36
  import { handleInstallSkillFromUpload } from "./methods/install-skill-from-upload.js";
37
37
  import { setBackendBaseUrl } from "./backend-url.js";
38
- import { ensureDesktopConfigInitialized } from "../core/config/desktop-config.js";
38
+ import { ensureDesktopConfigInitialized, getChannelsConfigSync } from "../core/config/desktop-config.js";
39
39
  import { createNestAppEmbedded } from "../server/bootstrap.js";
40
+ import { registerChannel, startAllChannels, stopAllChannels } from "./channel/registry.js";
41
+ import { createFeishuChannel } from "./channel/adapters/feishu.js";
42
+ import { setChannelSessionPersistence } from "./channel/session-persistence.js";
43
+ import { setSessionCurrentAgentResolver, setSessionCurrentAgentUpdater, setAgentListProvider, setCreateAgentProvider, } from "../core/session-current-agent.js";
44
+ import { AgentsService } from "../server/agents/agents.service.js";
45
+ import { AgentConfigService } from "../server/agent-config/agent-config.service.js";
40
46
  const __dirname = dirname(fileURLToPath(import.meta.url));
41
47
  const PACKAGE_ROOT = join(__dirname, "..", "..");
42
48
  /** 内嵌到 Electron 时由主进程设置 OPENBOT_STATIC_DIR,指向打包后的 renderer/dist */
@@ -61,6 +67,27 @@ export async function startGatewayServer(port = 38080) {
61
67
  console.log(`Starting gateway server on port ${port}...`);
62
68
  setBackendBaseUrl(`http://localhost:${port}`);
63
69
  const { app: nestApp, express: nestExpress } = await createNestAppEmbedded();
70
+ try {
71
+ const agentsService = nestApp.get(AgentsService);
72
+ setChannelSessionPersistence(agentsService);
73
+ setSessionCurrentAgentResolver((sessionId) => agentsService.getSession(sessionId)?.agentId);
74
+ setSessionCurrentAgentUpdater((sessionId, agentId) => agentsService.updateSessionAgentId(sessionId, agentId));
75
+ const agentConfigService = nestApp.get(AgentConfigService);
76
+ setAgentListProvider(() => agentConfigService.listAgents().then((agents) => agents.map((a) => ({ id: a.id, name: a.name }))));
77
+ setCreateAgentProvider(async (params) => {
78
+ try {
79
+ const agent = await agentConfigService.createAgent(params);
80
+ return { id: agent.id, name: agent.name };
81
+ }
82
+ catch (e) {
83
+ const msg = e?.message ?? e?.response?.message ?? String(e);
84
+ return { error: msg };
85
+ }
86
+ });
87
+ }
88
+ catch (e) {
89
+ console.warn("[Gateway] Channel session persistence / session-agent bridge unavailable:", e);
90
+ }
64
91
  const gatewayExpress = express();
65
92
  gatewayExpress.get(PATHS.HEALTH, (_req, res) => {
66
93
  res.status(200).json({ status: "ok", timestamp: Date.now() });
@@ -178,8 +205,30 @@ export async function startGatewayServer(port = 38080) {
178
205
  resolve(p);
179
206
  });
180
207
  });
208
+ // 通道:根据配置注册并启动(如飞书 WebSocket)
209
+ const channelsConfig = getChannelsConfigSync();
210
+ const feishuCfg = channelsConfig.feishu;
211
+ if (feishuCfg?.enabled && feishuCfg.appId?.trim() && feishuCfg.appSecret?.trim()) {
212
+ try {
213
+ console.log("[Channel] Starting Feishu (WebSocket)...");
214
+ const feishuChannel = createFeishuChannel({
215
+ appId: feishuCfg.appId.trim(),
216
+ appSecret: feishuCfg.appSecret.trim(),
217
+ defaultAgentId: feishuCfg.defaultAgentId?.trim() || "default",
218
+ });
219
+ registerChannel(feishuChannel);
220
+ await startAllChannels();
221
+ }
222
+ catch (e) {
223
+ console.warn("Feishu channel start failed:", e);
224
+ }
225
+ }
226
+ else if (feishuCfg?.enabled) {
227
+ console.warn("[Channel] Feishu is enabled but appId or appSecret is missing; skip start. Check Settings → Channels.");
228
+ }
181
229
  const close = async () => {
182
230
  console.log("Closing gateway server...");
231
+ await stopAllChannels();
183
232
  await nestApp.close();
184
233
  wss.clients.forEach((c) => c.close());
185
234
  await new Promise((r) => wss.close(() => r()));
@@ -16,11 +16,16 @@ export declare class AgentConfigController {
16
16
  createAgent(body: {
17
17
  name: string;
18
18
  workspace: string;
19
+ provider?: string;
20
+ model?: string;
21
+ modelItemCode?: string;
22
+ systemPrompt?: string;
23
+ icon?: string;
19
24
  }): Promise<{
20
25
  success: boolean;
21
26
  data: AgentConfigItem;
22
27
  }>;
23
- updateAgent(id: string, body: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers'>>): Promise<{
28
+ updateAgent(id: string, body: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers' | 'systemPrompt' | 'icon'>>): Promise<{
24
29
  success: boolean;
25
30
  data: AgentConfigItem;
26
31
  }>;
@@ -17,6 +17,10 @@ export interface AgentConfigItem {
17
17
  isDefault?: boolean;
18
18
  /** MCP 服务器配置列表,创建 Session 时传入(与 Skill 类似) */
19
19
  mcpServers?: McpServerConfig[];
20
+ /** 自定义系统提示词,会与技能等一起组成最终 systemPrompt */
21
+ systemPrompt?: string;
22
+ /** 智能体图标标识(前端预设图标 id,如 default、star、code 等) */
23
+ icon?: string;
20
24
  }
21
25
  export declare class AgentConfigService {
22
26
  private configDir;
@@ -33,8 +37,15 @@ export declare class AgentConfigService {
33
37
  createAgent(params: {
34
38
  name: string;
35
39
  workspace: string;
40
+ provider?: string;
41
+ model?: string;
42
+ modelItemCode?: string;
43
+ /** 自定义系统提示词;可由用户描述生成,创建后可在详情页修改 */
44
+ systemPrompt?: string;
45
+ /** 智能体图标标识 */
46
+ icon?: string;
36
47
  }): Promise<AgentConfigItem>;
37
- updateAgent(id: string, updates: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers'>>): Promise<AgentConfigItem>;
48
+ updateAgent(id: string, updates: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers' | 'systemPrompt' | 'icon'>>): Promise<AgentConfigItem>;
38
49
  deleteAgent(id: string): Promise<void>;
39
50
  /**
40
51
  * 根据 config 的 defaultProvider / defaultModel / defaultModelItemCode 及 configuredModels 同步 agents.json 中缺省智能体的 provider、model、modelItemCode。
@@ -93,7 +93,7 @@ let AgentConfigService = class AgentConfigService {
93
93
  return null;
94
94
  }
95
95
  async createAgent(params) {
96
- const { name, workspace } = params;
96
+ const { name, workspace, provider, model, modelItemCode, systemPrompt, icon } = params;
97
97
  if (workspace === DEFAULT_AGENT_ID) {
98
98
  throw new BadRequestException('工作空间名 default 为系统保留(主智能体),请使用其他名称');
99
99
  }
@@ -118,8 +118,11 @@ let AgentConfigService = class AgentConfigService {
118
118
  id: workspace,
119
119
  name: trimmedName,
120
120
  workspace,
121
- provider: undefined,
122
- model: undefined,
121
+ provider: provider?.trim() || undefined,
122
+ model: model?.trim() || undefined,
123
+ modelItemCode: modelItemCode?.trim() || undefined,
124
+ systemPrompt: systemPrompt?.trim() || undefined,
125
+ icon: icon?.trim() || undefined,
123
126
  };
124
127
  file.agents.push(agent);
125
128
  await this.writeAgentsFile(file);
@@ -152,6 +155,10 @@ let AgentConfigService = class AgentConfigService {
152
155
  agent.modelItemCode = updates.modelItemCode;
153
156
  if (updates.mcpServers !== undefined)
154
157
  agent.mcpServers = updates.mcpServers;
158
+ if (updates.systemPrompt !== undefined)
159
+ agent.systemPrompt = updates.systemPrompt?.trim() || undefined;
160
+ if (updates.icon !== undefined)
161
+ agent.icon = updates.icon?.trim() || undefined;
155
162
  await this.writeAgentsFile(file);
156
163
  return { ...agent, isDefault: agent.id === DEFAULT_AGENT_ID };
157
164
  }
@@ -22,6 +22,12 @@ export declare class AgentsController {
22
22
  success: boolean;
23
23
  data: import("./agents.service.js").AgentSession;
24
24
  };
25
+ updateSession(id: string, body: {
26
+ agentId?: string;
27
+ }): {
28
+ success: boolean;
29
+ data: import("./agents.service.js").AgentSession | undefined;
30
+ };
25
31
  deleteSession(id: string): Promise<{
26
32
  success: boolean;
27
33
  message: string;
@@ -30,6 +36,10 @@ export declare class AgentsController {
30
36
  success: boolean;
31
37
  data: import("./agents.service.js").ChatMessage[];
32
38
  };
39
+ clearHistory(id: string): {
40
+ success: boolean;
41
+ message: string;
42
+ };
33
43
  appendMessage(id: string, body: {
34
44
  role: 'user' | 'assistant';
35
45
  content: string;
@@ -10,7 +10,7 @@ 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, Get, Post, Delete, Body, Param, Header, HttpException, HttpStatus, } from '@nestjs/common';
13
+ import { Controller, Delete, Get, Patch, Post, Body, Param, Header, HttpException, HttpStatus, } from '@nestjs/common';
14
14
  import { AgentsService } from './agents.service.js';
15
15
  let AgentsController = class AgentsController {
16
16
  agentsService;
@@ -46,6 +46,17 @@ let AgentsController = class AgentsController {
46
46
  data: session,
47
47
  };
48
48
  }
49
+ updateSession(id, body) {
50
+ const session = this.agentsService.getSession(id);
51
+ if (!session) {
52
+ throw new HttpException('Session not found', HttpStatus.NOT_FOUND);
53
+ }
54
+ if (body.agentId != null) {
55
+ this.agentsService.updateSessionAgentId(id, String(body.agentId));
56
+ }
57
+ const updated = this.agentsService.getSession(id);
58
+ return { success: true, data: updated };
59
+ }
49
60
  async deleteSession(id) {
50
61
  await this.agentsService.deleteSession(id);
51
62
  return {
@@ -60,6 +71,14 @@ let AgentsController = class AgentsController {
60
71
  data: history,
61
72
  };
62
73
  }
74
+ clearHistory(id) {
75
+ const session = this.agentsService.getSession(id);
76
+ if (!session) {
77
+ throw new HttpException('Session not found', HttpStatus.NOT_FOUND);
78
+ }
79
+ this.agentsService.clearSessionMessages(id);
80
+ return { success: true, message: 'Messages cleared' };
81
+ }
63
82
  appendMessage(id, body) {
64
83
  this.agentsService.appendMessage(id, body.role, body.content, {
65
84
  toolCalls: body.toolCalls,
@@ -89,6 +108,14 @@ __decorate([
89
108
  __metadata("design:paramtypes", [String]),
90
109
  __metadata("design:returntype", void 0)
91
110
  ], AgentsController.prototype, "getSession", null);
111
+ __decorate([
112
+ Patch('sessions/:id'),
113
+ __param(0, Param('id')),
114
+ __param(1, Body()),
115
+ __metadata("design:type", Function),
116
+ __metadata("design:paramtypes", [String, Object]),
117
+ __metadata("design:returntype", void 0)
118
+ ], AgentsController.prototype, "updateSession", null);
92
119
  __decorate([
93
120
  Delete('sessions/:id'),
94
121
  __param(0, Param('id')),
@@ -103,6 +130,13 @@ __decorate([
103
130
  __metadata("design:paramtypes", [String]),
104
131
  __metadata("design:returntype", void 0)
105
132
  ], AgentsController.prototype, "getHistory", null);
133
+ __decorate([
134
+ Delete('sessions/:id/messages'),
135
+ __param(0, Param('id')),
136
+ __metadata("design:type", Function),
137
+ __metadata("design:paramtypes", [String]),
138
+ __metadata("design:returntype", void 0)
139
+ ], AgentsController.prototype, "clearHistory", null);
106
140
  __decorate([
107
141
  Post('sessions/:id/messages'),
108
142
  __param(0, Param('id')),
@@ -51,14 +51,28 @@ let AgentsGateway = class AgentsGateway {
51
51
  client.emit('agent_tool', data);
52
52
  });
53
53
  subscriptions.set('tool', unsubTool);
54
- // Subscribe to message completion
54
+ // turn_end / agent_end:各通道均可收到,按自身逻辑处理(如 desktop 用 agent_end 开放输入)
55
+ const unsubTurnEnd = this.agentsService.addEventListener('turn_end', (data) => {
56
+ if (data.sessionId === sessionId)
57
+ client.emit('turn_end', data);
58
+ });
59
+ subscriptions.set('turn_end', unsubTurnEnd);
60
+ const unsubAgentEnd = this.agentsService.addEventListener('agent_end', (data) => {
61
+ if (data.sessionId === sessionId)
62
+ client.emit('agent_end', data);
63
+ });
64
+ subscriptions.set('agent_end', unsubAgentEnd);
65
+ // 兼容:message_complete(turn_end 语义)、conversation_end(agent_end 语义)
55
66
  const unsubComplete = this.agentsService.addEventListener('message_complete', (data) => {
56
- // Ensure we only emit for the subscribed session
57
- if (data.sessionId === sessionId) {
67
+ if (data.sessionId === sessionId)
58
68
  client.emit('message_complete', data);
59
- }
60
69
  });
61
70
  subscriptions.set('complete', unsubComplete);
71
+ const unsubConversationEnd = this.agentsService.addEventListener('conversation_end', (data) => {
72
+ if (data.sessionId === sessionId)
73
+ client.emit('conversation_end', data);
74
+ });
75
+ subscriptions.set('conversation_end', unsubConversationEnd);
62
76
  this.sessionSubscriptions.set(client.id, subscriptions);
63
77
  return { success: true };
64
78
  }
@@ -53,6 +53,8 @@ export declare class AgentsService {
53
53
  }): Promise<AgentSession>;
54
54
  getSessions(): AgentSession[];
55
55
  getSession(sessionId: string): AgentSession | undefined;
56
+ /** 更新会话的当前 agent(同一 session 内切换 agent 时调用) */
57
+ updateSessionAgentId(sessionId: string, agentId: string): void;
56
58
  deleteSession(sessionId: string): Promise<void>;
57
59
  getMessageHistory(sessionId: string): ChatMessage[];
58
60
  addAssistantMessage(sessionId: string, content: string): void;
@@ -60,4 +62,6 @@ export declare class AgentsService {
60
62
  toolCalls?: any[];
61
63
  contentParts?: any[];
62
64
  }): void;
65
+ /** 清除会话的所有对话消息(保留会话本身,message_count 置 0) */
66
+ clearSessionMessages(sessionId: string): void;
63
67
  }
@@ -134,12 +134,19 @@ let AgentsService = class AgentsService {
134
134
  const r = this.db.get('SELECT * FROM sessions WHERE id = ?', [sessionId]);
135
135
  return r ? this.rowToSession(r) : undefined;
136
136
  }
137
+ /** 更新会话的当前 agent(同一 session 内切换 agent 时调用) */
138
+ updateSessionAgentId(sessionId, agentId) {
139
+ const session = this.getSession(sessionId);
140
+ if (!session)
141
+ return;
142
+ this.db.run('UPDATE sessions SET agent_id = ? WHERE id = ?', [agentId, sessionId]);
143
+ }
137
144
  async deleteSession(sessionId) {
138
145
  const result = this.db.run('DELETE FROM sessions WHERE id = ?', [sessionId]);
139
146
  if (result.changes > 0) {
140
147
  this.db.persist();
141
148
  }
142
- agentManager.deleteSession(sessionId);
149
+ agentManager.deleteSessionsByBusinessId(sessionId);
143
150
  }
144
151
  getMessageHistory(sessionId) {
145
152
  const rows = this.db.all('SELECT * FROM chat_messages WHERE session_id = ? ORDER BY timestamp ASC', [sessionId]);
@@ -161,6 +168,15 @@ let AgentsService = class AgentsService {
161
168
  this.db.run(`INSERT INTO chat_messages (id, session_id, role, content, timestamp, tool_calls_json) VALUES (?, ?, ?, ?, ?, ?)`, [id, sessionId, role, content, now, toolCallsJson]);
162
169
  this.db.run('UPDATE sessions SET last_active_at = ?, status = ?, message_count = message_count + 1 WHERE id = ?', [now, role === 'assistant' ? 'idle' : session.status, sessionId]);
163
170
  }
171
+ /** 清除会话的所有对话消息(保留会话本身,message_count 置 0) */
172
+ clearSessionMessages(sessionId) {
173
+ const session = this.getSession(sessionId);
174
+ if (!session)
175
+ return;
176
+ this.db.run('DELETE FROM chat_messages WHERE session_id = ?', [sessionId]);
177
+ this.db.run('UPDATE sessions SET message_count = 0 WHERE id = ?', [sessionId]);
178
+ this.db.persist();
179
+ }
164
180
  };
165
181
  AgentsService = __decorate([
166
182
  Injectable(),
@@ -26,6 +26,7 @@ export declare class ConfigController {
26
26
  embeddingProvider?: string;
27
27
  embeddingModel?: string;
28
28
  };
29
+ channels?: import("../../core/config/desktop-config.js").ChannelsConfig;
29
30
  };
30
31
  }>;
31
32
  updateConfig(updates: Partial<AppConfig>): Promise<{
@@ -52,6 +53,7 @@ export declare class ConfigController {
52
53
  embeddingProvider?: string;
53
54
  embeddingModel?: string;
54
55
  };
56
+ channels?: import("../../core/config/desktop-config.js").ChannelsConfig;
55
57
  };
56
58
  }>;
57
59
  getProviders(): Promise<{
@@ -1,3 +1,4 @@
1
+ import { type ChannelsConfig } from '../../core/config/desktop-config.js';
1
2
  import { AgentConfigService } from '../agent-config/agent-config.service.js';
2
3
  /** 模型 cost 配置,写入 models.json;缺省均为 0 */
3
4
  export interface ModelCost {
@@ -55,6 +56,8 @@ export interface AppConfig {
55
56
  embeddingProvider?: string;
56
57
  embeddingModel?: string;
57
58
  };
59
+ /** 通道配置:飞书、Telegram 等 token/key */
60
+ channels?: ChannelsConfig;
58
61
  }
59
62
  export declare class ConfigService {
60
63
  private readonly agentConfigService;
@@ -11,6 +11,7 @@ import { Injectable } from '@nestjs/common';
11
11
  import { readFile, writeFile, mkdir } from 'fs/promises';
12
12
  import { join } from 'path';
13
13
  import { existsSync } from 'fs';
14
+ import { homedir } from 'os';
14
15
  import { getProviderSupport, syncDesktopConfigToModelsJson } from '../../core/config/desktop-config.js';
15
16
  import { AgentConfigService } from '../agent-config/agent-config.service.js';
16
17
  let ConfigService = class ConfigService {
@@ -19,7 +20,7 @@ let ConfigService = class ConfigService {
19
20
  config;
20
21
  constructor(agentConfigService) {
21
22
  this.agentConfigService = agentConfigService;
22
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
23
+ const homeDir = process.env.HOME || process.env.USERPROFILE || homedir();
23
24
  const configDir = join(homeDir, '.openbot', 'desktop');
24
25
  this.configPath = join(configDir, 'config.json');
25
26
  // Ensure config directory exists
@@ -40,6 +41,7 @@ let ConfigService = class ConfigService {
40
41
  providers: {},
41
42
  configuredModels: [],
42
43
  rag: undefined,
44
+ channels: {},
43
45
  };
44
46
  }
45
47
  /** 当前缺省智能体 id */