@next-open-ai/openbot 0.2.8 → 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.
- package/README.md +13 -3
- package/apps/desktop/renderer/dist/assets/index-BNuvb6Ay.css +10 -0
- package/apps/desktop/renderer/dist/assets/index-DvQjslfT.js +89 -0
- package/apps/desktop/renderer/dist/index.html +2 -2
- package/dist/core/agent/agent-manager.d.ts +17 -6
- package/dist/core/agent/agent-manager.js +62 -25
- package/dist/core/agent/run.js +2 -2
- package/dist/core/config/desktop-config.d.ts +17 -0
- package/dist/core/config/desktop-config.js +23 -1
- package/dist/core/installer/index.d.ts +1 -1
- package/dist/core/installer/index.js +1 -1
- package/dist/core/installer/skill-installer.d.ts +9 -0
- package/dist/core/installer/skill-installer.js +94 -0
- package/dist/core/mcp/adapter.d.ts +17 -0
- package/dist/core/mcp/adapter.js +49 -0
- package/dist/core/mcp/client.d.ts +24 -0
- package/dist/core/mcp/client.js +70 -0
- package/dist/core/mcp/config.d.ts +22 -0
- package/dist/core/mcp/config.js +69 -0
- package/dist/core/mcp/index.d.ts +18 -0
- package/dist/core/mcp/index.js +20 -0
- package/dist/core/mcp/operator.d.ts +15 -0
- package/dist/core/mcp/operator.js +72 -0
- package/dist/core/mcp/transport/index.d.ts +11 -0
- package/dist/core/mcp/transport/index.js +16 -0
- package/dist/core/mcp/transport/sse.d.ts +20 -0
- package/dist/core/mcp/transport/sse.js +82 -0
- package/dist/core/mcp/transport/stdio.d.ts +32 -0
- package/dist/core/mcp/transport/stdio.js +132 -0
- package/dist/core/mcp/types.d.ts +72 -0
- package/dist/core/mcp/types.js +5 -0
- package/dist/core/session-current-agent.d.ts +34 -0
- package/dist/core/session-current-agent.js +32 -0
- package/dist/core/tools/bookmark-tool.d.ts +9 -0
- package/dist/core/tools/bookmark-tool.js +118 -0
- package/dist/core/tools/create-agent-tool.d.ts +6 -0
- package/dist/core/tools/create-agent-tool.js +97 -0
- package/dist/core/tools/index.d.ts +4 -0
- package/dist/core/tools/index.js +4 -0
- package/dist/core/tools/list-agents-tool.d.ts +5 -0
- package/dist/core/tools/list-agents-tool.js +45 -0
- package/dist/core/tools/switch-agent-tool.d.ts +6 -0
- package/dist/core/tools/switch-agent-tool.js +54 -0
- package/dist/gateway/channel/adapters/feishu.d.ts +11 -0
- package/dist/gateway/channel/adapters/feishu.js +218 -0
- package/dist/gateway/channel/channel-core.d.ts +9 -0
- package/dist/gateway/channel/channel-core.js +127 -0
- package/dist/gateway/channel/registry.d.ts +16 -0
- package/dist/gateway/channel/registry.js +54 -0
- package/dist/gateway/channel/run-agent.d.ts +26 -0
- package/dist/gateway/channel/run-agent.js +137 -0
- package/dist/gateway/channel/session-persistence.d.ts +36 -0
- package/dist/gateway/channel/session-persistence.js +46 -0
- package/dist/gateway/channel/types.d.ts +70 -0
- package/dist/gateway/channel/types.js +4 -0
- package/dist/gateway/channel-handler.d.ts +3 -4
- package/dist/gateway/channel-handler.js +8 -2
- package/dist/gateway/methods/agent-chat.js +31 -12
- package/dist/gateway/methods/install-skill-from-upload.d.ts +14 -0
- package/dist/gateway/methods/install-skill-from-upload.js +13 -0
- package/dist/gateway/methods/run-scheduled-task.js +5 -2
- package/dist/gateway/server.js +74 -1
- package/dist/server/agent-config/agent-config.controller.d.ts +6 -1
- package/dist/server/agent-config/agent-config.service.d.ts +15 -1
- package/dist/server/agent-config/agent-config.service.js +12 -3
- package/dist/server/agents/agents.controller.d.ts +10 -0
- package/dist/server/agents/agents.controller.js +36 -4
- package/dist/server/agents/agents.gateway.js +18 -4
- package/dist/server/agents/agents.service.d.ts +5 -1
- package/dist/server/agents/agents.service.js +20 -2
- package/dist/server/app.module.js +2 -0
- package/dist/server/config/config.controller.d.ts +2 -0
- package/dist/server/config/config.service.d.ts +3 -0
- package/dist/server/config/config.service.js +3 -1
- package/dist/server/database/database.service.d.ts +7 -0
- package/dist/server/database/database.service.js +54 -5
- package/dist/server/saved-items/saved-items.controller.d.ts +57 -0
- package/dist/server/saved-items/saved-items.controller.js +229 -0
- package/dist/server/saved-items/saved-items.module.d.ts +2 -0
- package/dist/server/saved-items/saved-items.module.js +25 -0
- package/dist/server/saved-items/saved-items.service.d.ts +31 -0
- package/dist/server/saved-items/saved-items.service.js +105 -0
- package/dist/server/saved-items/tags.controller.d.ts +30 -0
- package/dist/server/saved-items/tags.controller.js +85 -0
- package/dist/server/saved-items/tags.service.d.ts +24 -0
- package/dist/server/saved-items/tags.service.js +84 -0
- package/dist/server/skills/skills.service.d.ts +2 -0
- package/dist/server/skills/skills.service.js +80 -16
- package/dist/server/workspace/workspace.service.d.ts +11 -0
- package/dist/server/workspace/workspace.service.js +40 -1
- package/package.json +6 -1
- package/skills/url-bookmark/SKILL.md +36 -0
- package/apps/desktop/renderer/dist/assets/index-BOS-F8a4.js +0 -89
- package/apps/desktop/renderer/dist/assets/index-DxqxayUL.css +0 -10
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 在 Gateway 层处理 POST /server-api/skills/install-from-upload,
|
|
3
|
+
* 接收 multipart zip 文件,委托核心 installer 解压并安装到全局或工作区。
|
|
4
|
+
*/
|
|
5
|
+
import { installSkillFromUpload } from "../../core/installer/index.js";
|
|
6
|
+
export async function handleInstallSkillFromUpload(body) {
|
|
7
|
+
const { buffer, scope = "global", workspace = "default" } = body;
|
|
8
|
+
if (!buffer || !Buffer.isBuffer(buffer) || buffer.length === 0) {
|
|
9
|
+
throw new Error("请上传 zip 文件");
|
|
10
|
+
}
|
|
11
|
+
const result = await installSkillFromUpload(buffer, { scope, workspace });
|
|
12
|
+
return { success: true, data: { installDir: result.installDir, name: result.name } };
|
|
13
|
+
}
|
|
@@ -49,12 +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,
|
|
60
|
+
mcpServers: agentConfig?.mcpServers,
|
|
61
|
+
systemPrompt: agentConfig?.systemPrompt,
|
|
58
62
|
});
|
|
59
63
|
let assistantContent = "";
|
|
60
64
|
let turnPromptTokens = 0;
|
|
@@ -120,7 +124,6 @@ export async function handleRunScheduledTask(req, res) {
|
|
|
120
124
|
res.end(JSON.stringify({ success: false, error: friendlyError }));
|
|
121
125
|
}
|
|
122
126
|
finally {
|
|
123
|
-
|
|
124
|
-
agentManager.deleteSession(sessionId);
|
|
127
|
+
agentManager.deleteSession(sessionId + COMPOSITE_KEY_SEP + sessionAgentId);
|
|
125
128
|
}
|
|
126
129
|
}
|
package/dist/gateway/server.js
CHANGED
|
@@ -31,10 +31,18 @@ import { handleSse } from "./sse-handler.js";
|
|
|
31
31
|
import { handleVoiceUpgrade } from "./voice-handler.js";
|
|
32
32
|
import { handleConnection } from "./connection-handler.js";
|
|
33
33
|
import { handleRunScheduledTask } from "./methods/run-scheduled-task.js";
|
|
34
|
+
import multer from "multer";
|
|
34
35
|
import { handleInstallSkillFromPath } from "./methods/install-skill-from-path.js";
|
|
36
|
+
import { handleInstallSkillFromUpload } from "./methods/install-skill-from-upload.js";
|
|
35
37
|
import { setBackendBaseUrl } from "./backend-url.js";
|
|
36
|
-
import { ensureDesktopConfigInitialized } from "../core/config/desktop-config.js";
|
|
38
|
+
import { ensureDesktopConfigInitialized, getChannelsConfigSync } from "../core/config/desktop-config.js";
|
|
37
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";
|
|
38
46
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
39
47
|
const PACKAGE_ROOT = join(__dirname, "..", "..");
|
|
40
48
|
/** 内嵌到 Electron 时由主进程设置 OPENBOT_STATIC_DIR,指向打包后的 renderer/dist */
|
|
@@ -59,6 +67,27 @@ export async function startGatewayServer(port = 38080) {
|
|
|
59
67
|
console.log(`Starting gateway server on port ${port}...`);
|
|
60
68
|
setBackendBaseUrl(`http://localhost:${port}`);
|
|
61
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
|
+
}
|
|
62
91
|
const gatewayExpress = express();
|
|
63
92
|
gatewayExpress.get(PATHS.HEALTH, (_req, res) => {
|
|
64
93
|
res.status(200).json({ status: "ok", timestamp: Date.now() });
|
|
@@ -66,6 +95,10 @@ export async function startGatewayServer(port = 38080) {
|
|
|
66
95
|
gatewayExpress.post(PATHS.RUN_SCHEDULED_TASK, async (req, res) => {
|
|
67
96
|
await handleRunScheduledTask(req, res);
|
|
68
97
|
});
|
|
98
|
+
const uploadZip = multer({
|
|
99
|
+
storage: multer.memoryStorage(),
|
|
100
|
+
limits: { fileSize: 10 * 1024 * 1024 },
|
|
101
|
+
});
|
|
69
102
|
gatewayExpress.post(`${PATHS.SERVER_API}/skills/install-from-path`, async (req, res) => {
|
|
70
103
|
const body = await new Promise((resolve, reject) => {
|
|
71
104
|
const chunks = [];
|
|
@@ -88,6 +121,24 @@ export async function startGatewayServer(port = 38080) {
|
|
|
88
121
|
res.status(code).json({ success: false, message });
|
|
89
122
|
}
|
|
90
123
|
});
|
|
124
|
+
gatewayExpress.post(`${PATHS.SERVER_API}/skills/install-from-upload`, authHookServerApi, uploadZip.single("file"), async (req, res) => {
|
|
125
|
+
try {
|
|
126
|
+
const file = req.file;
|
|
127
|
+
const buffer = file?.buffer;
|
|
128
|
+
if (!buffer || !Buffer.isBuffer(buffer)) {
|
|
129
|
+
return res.status(400).json({ success: false, message: "请上传 zip 文件" });
|
|
130
|
+
}
|
|
131
|
+
const scope = req.body?.scope === "workspace" ? "workspace" : "global";
|
|
132
|
+
const workspace = req.body?.workspace ?? "default";
|
|
133
|
+
const result = await handleInstallSkillFromUpload({ buffer, scope, workspace });
|
|
134
|
+
res.status(200).json(result);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
138
|
+
const code = message.includes("请上传") || message.includes("SKILL.md") || message.includes("目录") || message.includes("10MB") ? 400 : 500;
|
|
139
|
+
res.status(code).json({ success: false, message });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
91
142
|
gatewayExpress.use(PATHS.SERVER_API, authHookServerApi, nestExpress);
|
|
92
143
|
gatewayExpress.use(PATHS.CHANNEL, authHookChannel, (req, res) => handleChannel(req, res));
|
|
93
144
|
gatewayExpress.use(PATHS.SSE, authHookSse, (req, res) => handleSse(req, res));
|
|
@@ -154,8 +205,30 @@ export async function startGatewayServer(port = 38080) {
|
|
|
154
205
|
resolve(p);
|
|
155
206
|
});
|
|
156
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
|
+
}
|
|
157
229
|
const close = async () => {
|
|
158
230
|
console.log("Closing gateway server...");
|
|
231
|
+
await stopAllChannels();
|
|
159
232
|
await nestApp.close();
|
|
160
233
|
wss.clients.forEach((c) => c.close());
|
|
161
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'>>): 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
|
}>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { McpServerConfig } from '../../core/mcp/index.js';
|
|
1
2
|
/** 缺省智能体 ID / 工作空间名,不可删除;对应目录 ~/.openbot/workspace/default */
|
|
2
3
|
export declare const DEFAULT_AGENT_ID = "default";
|
|
3
4
|
/**
|
|
@@ -14,6 +15,12 @@ export interface AgentConfigItem {
|
|
|
14
15
|
modelItemCode?: string;
|
|
15
16
|
/** 是否为系统缺省智能体(主智能体),不可删除 */
|
|
16
17
|
isDefault?: boolean;
|
|
18
|
+
/** MCP 服务器配置列表,创建 Session 时传入(与 Skill 类似) */
|
|
19
|
+
mcpServers?: McpServerConfig[];
|
|
20
|
+
/** 自定义系统提示词,会与技能等一起组成最终 systemPrompt */
|
|
21
|
+
systemPrompt?: string;
|
|
22
|
+
/** 智能体图标标识(前端预设图标 id,如 default、star、code 等) */
|
|
23
|
+
icon?: string;
|
|
17
24
|
}
|
|
18
25
|
export declare class AgentConfigService {
|
|
19
26
|
private configDir;
|
|
@@ -30,8 +37,15 @@ export declare class AgentConfigService {
|
|
|
30
37
|
createAgent(params: {
|
|
31
38
|
name: string;
|
|
32
39
|
workspace: string;
|
|
40
|
+
provider?: string;
|
|
41
|
+
model?: string;
|
|
42
|
+
modelItemCode?: string;
|
|
43
|
+
/** 自定义系统提示词;可由用户描述生成,创建后可在详情页修改 */
|
|
44
|
+
systemPrompt?: string;
|
|
45
|
+
/** 智能体图标标识 */
|
|
46
|
+
icon?: string;
|
|
33
47
|
}): Promise<AgentConfigItem>;
|
|
34
|
-
updateAgent(id: string, updates: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode'>>): Promise<AgentConfigItem>;
|
|
48
|
+
updateAgent(id: string, updates: Partial<Pick<AgentConfigItem, 'name' | 'provider' | 'model' | 'modelItemCode' | 'mcpServers' | 'systemPrompt' | 'icon'>>): Promise<AgentConfigItem>;
|
|
35
49
|
deleteAgent(id: string): Promise<void>;
|
|
36
50
|
/**
|
|
37
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);
|
|
@@ -150,6 +153,12 @@ let AgentConfigService = class AgentConfigService {
|
|
|
150
153
|
agent.model = updates.model;
|
|
151
154
|
if (updates.modelItemCode !== undefined)
|
|
152
155
|
agent.modelItemCode = updates.modelItemCode;
|
|
156
|
+
if (updates.mcpServers !== undefined)
|
|
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;
|
|
153
162
|
await this.writeAgentsFile(file);
|
|
154
163
|
return { ...agent, isDefault: agent.id === DEFAULT_AGENT_ID };
|
|
155
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,
|
|
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,11 +46,19 @@ let AgentsController = class AgentsController {
|
|
|
46
46
|
data: session,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
49
|
+
updateSession(id, body) {
|
|
50
|
+
const session = this.agentsService.getSession(id);
|
|
51
|
+
if (!session) {
|
|
52
52
|
throw new HttpException('Session not found', HttpStatus.NOT_FOUND);
|
|
53
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
|
+
}
|
|
60
|
+
async deleteSession(id) {
|
|
61
|
+
await this.agentsService.deleteSession(id);
|
|
54
62
|
return {
|
|
55
63
|
success: true,
|
|
56
64
|
message: 'Session deleted',
|
|
@@ -63,6 +71,14 @@ let AgentsController = class AgentsController {
|
|
|
63
71
|
data: history,
|
|
64
72
|
};
|
|
65
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
|
+
}
|
|
66
82
|
appendMessage(id, body) {
|
|
67
83
|
this.agentsService.appendMessage(id, body.role, body.content, {
|
|
68
84
|
toolCalls: body.toolCalls,
|
|
@@ -80,6 +96,7 @@ __decorate([
|
|
|
80
96
|
], AgentsController.prototype, "createSession", null);
|
|
81
97
|
__decorate([
|
|
82
98
|
Get('sessions'),
|
|
99
|
+
Header('Cache-Control', 'no-store'),
|
|
83
100
|
__metadata("design:type", Function),
|
|
84
101
|
__metadata("design:paramtypes", []),
|
|
85
102
|
__metadata("design:returntype", void 0)
|
|
@@ -91,6 +108,14 @@ __decorate([
|
|
|
91
108
|
__metadata("design:paramtypes", [String]),
|
|
92
109
|
__metadata("design:returntype", void 0)
|
|
93
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);
|
|
94
119
|
__decorate([
|
|
95
120
|
Delete('sessions/:id'),
|
|
96
121
|
__param(0, Param('id')),
|
|
@@ -105,6 +130,13 @@ __decorate([
|
|
|
105
130
|
__metadata("design:paramtypes", [String]),
|
|
106
131
|
__metadata("design:returntype", void 0)
|
|
107
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);
|
|
108
140
|
__decorate([
|
|
109
141
|
Post('sessions/:id/messages'),
|
|
110
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
|
-
//
|
|
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
|
-
|
|
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,11 +53,15 @@ export declare class AgentsService {
|
|
|
53
53
|
}): Promise<AgentSession>;
|
|
54
54
|
getSessions(): AgentSession[];
|
|
55
55
|
getSession(sessionId: string): AgentSession | undefined;
|
|
56
|
-
|
|
56
|
+
/** 更新会话的当前 agent(同一 session 内切换 agent 时调用) */
|
|
57
|
+
updateSessionAgentId(sessionId: string, agentId: string): void;
|
|
58
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
57
59
|
getMessageHistory(sessionId: string): ChatMessage[];
|
|
58
60
|
addAssistantMessage(sessionId: string, content: string): void;
|
|
59
61
|
appendMessage(sessionId: string, role: 'user' | 'assistant', content: string, options?: {
|
|
60
62
|
toolCalls?: any[];
|
|
61
63
|
contentParts?: any[];
|
|
62
64
|
}): void;
|
|
65
|
+
/** 清除会话的所有对话消息(保留会话本身,message_count 置 0) */
|
|
66
|
+
clearSessionMessages(sessionId: string): void;
|
|
63
67
|
}
|
|
@@ -134,10 +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
|
-
agentManager.deleteSession(sessionId);
|
|
139
145
|
const result = this.db.run('DELETE FROM sessions WHERE id = ?', [sessionId]);
|
|
140
|
-
|
|
146
|
+
if (result.changes > 0) {
|
|
147
|
+
this.db.persist();
|
|
148
|
+
}
|
|
149
|
+
agentManager.deleteSessionsByBusinessId(sessionId);
|
|
141
150
|
}
|
|
142
151
|
getMessageHistory(sessionId) {
|
|
143
152
|
const rows = this.db.all('SELECT * FROM chat_messages WHERE session_id = ? ORDER BY timestamp ASC', [sessionId]);
|
|
@@ -159,6 +168,15 @@ let AgentsService = class AgentsService {
|
|
|
159
168
|
this.db.run(`INSERT INTO chat_messages (id, session_id, role, content, timestamp, tool_calls_json) VALUES (?, ?, ?, ?, ?, ?)`, [id, sessionId, role, content, now, toolCallsJson]);
|
|
160
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]);
|
|
161
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
|
+
}
|
|
162
180
|
};
|
|
163
181
|
AgentsService = __decorate([
|
|
164
182
|
Injectable(),
|
|
@@ -15,6 +15,7 @@ import { UsersModule } from './users/users.module.js';
|
|
|
15
15
|
import { WorkspaceModule } from './workspace/workspace.module.js';
|
|
16
16
|
import { TasksModule } from './tasks/tasks.module.js';
|
|
17
17
|
import { UsageModule } from './usage/usage.module.js';
|
|
18
|
+
import { SavedItemsModule } from './saved-items/saved-items.module.js';
|
|
18
19
|
let AppModule = class AppModule {
|
|
19
20
|
};
|
|
20
21
|
AppModule = __decorate([
|
|
@@ -30,6 +31,7 @@ AppModule = __decorate([
|
|
|
30
31
|
WorkspaceModule,
|
|
31
32
|
TasksModule,
|
|
32
33
|
UsageModule,
|
|
34
|
+
SavedItemsModule,
|
|
33
35
|
],
|
|
34
36
|
})
|
|
35
37
|
], AppModule);
|
|
@@ -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 */
|
|
@@ -13,6 +13,13 @@ export declare class DatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
13
13
|
private getDb;
|
|
14
14
|
private runMigrations;
|
|
15
15
|
run(sql: string, params?: unknown[]): RunResult;
|
|
16
|
+
/**
|
|
17
|
+
* 使用文件 DB 时立即落盘。每次 run() 写入后都会调用,保证「每次保存马上写到磁盘」。
|
|
18
|
+
* 落盘失败时重新抛出,便于上层返回错误、前端不乐观更新,避免「删了但重启又出现」。
|
|
19
|
+
*/
|
|
20
|
+
private persistIfFile;
|
|
21
|
+
/** 供删除等关键操作后显式落盘,确保删除结果持久化 */
|
|
22
|
+
persist(): void;
|
|
16
23
|
get<T>(sql: string, params?: unknown[]): T | undefined;
|
|
17
24
|
all<T>(sql: string, params?: unknown[]): T[];
|
|
18
25
|
onModuleDestroy(): void;
|
|
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { Injectable } from '@nestjs/common';
|
|
8
8
|
import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'fs';
|
|
9
|
-
import { join } from 'path';
|
|
9
|
+
import { join, resolve } from 'path';
|
|
10
10
|
import { homedir } from 'os';
|
|
11
11
|
let DatabaseService = class DatabaseService {
|
|
12
12
|
sqlDb = null;
|
|
@@ -25,7 +25,8 @@ let DatabaseService = class DatabaseService {
|
|
|
25
25
|
? ':memory:'
|
|
26
26
|
: pathEnv ?? join(process.env.OPENBOT_DB_DIR ?? defaultDir, 'openbot.db');
|
|
27
27
|
if (path !== ':memory:') {
|
|
28
|
-
const
|
|
28
|
+
const resolvedPath = resolve(path);
|
|
29
|
+
const dir = resolvedPath.endsWith('.db') ? join(resolvedPath, '..') : resolvedPath;
|
|
29
30
|
if (!existsSync(dir)) {
|
|
30
31
|
mkdirSync(dir, { recursive: true });
|
|
31
32
|
}
|
|
@@ -38,18 +39,23 @@ let DatabaseService = class DatabaseService {
|
|
|
38
39
|
this.dbPath = ':memory:';
|
|
39
40
|
}
|
|
40
41
|
else {
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const absolutePath = resolve(path);
|
|
43
|
+
if (existsSync(absolutePath)) {
|
|
44
|
+
const buf = readFileSync(absolutePath);
|
|
43
45
|
db = new SQL.Database(new Uint8Array(buf));
|
|
44
46
|
}
|
|
45
47
|
else {
|
|
46
48
|
db = new SQL.Database();
|
|
47
49
|
}
|
|
48
|
-
this.dbPath =
|
|
50
|
+
this.dbPath = absolutePath;
|
|
49
51
|
}
|
|
50
52
|
this.sqlDb = db;
|
|
51
53
|
db.run('PRAGMA foreign_keys = ON;');
|
|
52
54
|
this.runMigrations();
|
|
55
|
+
if (path !== ':memory:') {
|
|
56
|
+
this.persistIfFile();
|
|
57
|
+
console.log('[DatabaseService] Database file:', this.dbPath);
|
|
58
|
+
}
|
|
53
59
|
}
|
|
54
60
|
getDb() {
|
|
55
61
|
if (!this.sqlDb) {
|
|
@@ -127,6 +133,28 @@ let DatabaseService = class DatabaseService {
|
|
|
127
133
|
);
|
|
128
134
|
CREATE INDEX IF NOT EXISTS idx_token_usage_session_id ON token_usage(session_id);
|
|
129
135
|
CREATE INDEX IF NOT EXISTS idx_token_usage_created_at ON token_usage(created_at);
|
|
136
|
+
|
|
137
|
+
CREATE TABLE IF NOT EXISTS tags (
|
|
138
|
+
id TEXT PRIMARY KEY,
|
|
139
|
+
name TEXT UNIQUE NOT NULL,
|
|
140
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
141
|
+
created_at INTEGER NOT NULL
|
|
142
|
+
);
|
|
143
|
+
CREATE TABLE IF NOT EXISTS saved_items (
|
|
144
|
+
id TEXT PRIMARY KEY,
|
|
145
|
+
url TEXT NOT NULL,
|
|
146
|
+
title TEXT,
|
|
147
|
+
workspace TEXT NOT NULL DEFAULT 'default',
|
|
148
|
+
created_at INTEGER NOT NULL
|
|
149
|
+
);
|
|
150
|
+
CREATE TABLE IF NOT EXISTS saved_item_tags (
|
|
151
|
+
saved_item_id TEXT NOT NULL,
|
|
152
|
+
tag_id TEXT NOT NULL,
|
|
153
|
+
PRIMARY KEY (saved_item_id, tag_id),
|
|
154
|
+
FOREIGN KEY (saved_item_id) REFERENCES saved_items(id) ON DELETE CASCADE,
|
|
155
|
+
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
|
156
|
+
);
|
|
157
|
+
CREATE INDEX IF NOT EXISTS idx_saved_item_tags_tag_id ON saved_item_tags(tag_id);
|
|
130
158
|
`;
|
|
131
159
|
const statements = ddl.split(';').map((s) => s.trim()).filter(Boolean);
|
|
132
160
|
for (const stmt of statements) {
|
|
@@ -148,11 +176,32 @@ let DatabaseService = class DatabaseService {
|
|
|
148
176
|
run(sql, params = []) {
|
|
149
177
|
const db = this.getDb();
|
|
150
178
|
db.run(sql, params);
|
|
179
|
+
this.persistIfFile();
|
|
151
180
|
const rows = db.exec('SELECT changes() AS c, last_insert_rowid() AS id');
|
|
152
181
|
const c = rows[0]?.values?.[0]?.[0] ?? 0;
|
|
153
182
|
const id = rows[0]?.values?.[0]?.[1] ?? 0;
|
|
154
183
|
return { changes: Number(c), lastInsertRowid: Number(id) };
|
|
155
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* 使用文件 DB 时立即落盘。每次 run() 写入后都会调用,保证「每次保存马上写到磁盘」。
|
|
187
|
+
* 落盘失败时重新抛出,便于上层返回错误、前端不乐观更新,避免「删了但重启又出现」。
|
|
188
|
+
*/
|
|
189
|
+
persistIfFile() {
|
|
190
|
+
if (!this.sqlDb || !this.dbPath || this.dbPath === ':memory:')
|
|
191
|
+
return;
|
|
192
|
+
try {
|
|
193
|
+
const data = this.sqlDb.export();
|
|
194
|
+
writeFileSync(this.dbPath, Buffer.from(data));
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
console.error('[DatabaseService] Failed to persist database:', e);
|
|
198
|
+
throw e;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** 供删除等关键操作后显式落盘,确保删除结果持久化 */
|
|
202
|
+
persist() {
|
|
203
|
+
this.persistIfFile();
|
|
204
|
+
}
|
|
156
205
|
get(sql, params = []) {
|
|
157
206
|
const db = this.getDb();
|
|
158
207
|
const stmt = db.prepare(sql);
|