@next-open-ai/openbot 0.1.1

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 (139) hide show
  1. package/README.md +212 -0
  2. package/dist/agent/agent-dir.d.ts +14 -0
  3. package/dist/agent/agent-dir.js +75 -0
  4. package/dist/agent/agent-manager.d.ts +61 -0
  5. package/dist/agent/agent-manager.js +257 -0
  6. package/dist/agent/config-manager.d.ts +25 -0
  7. package/dist/agent/config-manager.js +84 -0
  8. package/dist/agent/desktop-config.d.ts +15 -0
  9. package/dist/agent/desktop-config.js +91 -0
  10. package/dist/agent/run.d.ts +26 -0
  11. package/dist/agent/run.js +65 -0
  12. package/dist/agent/skills.d.ts +20 -0
  13. package/dist/agent/skills.js +86 -0
  14. package/dist/cli.d.ts +2 -0
  15. package/dist/cli.js +168 -0
  16. package/dist/gateway/backend-url.d.ts +2 -0
  17. package/dist/gateway/backend-url.js +11 -0
  18. package/dist/gateway/clients.d.ts +5 -0
  19. package/dist/gateway/clients.js +4 -0
  20. package/dist/gateway/connection-handler.d.ts +6 -0
  21. package/dist/gateway/connection-handler.js +48 -0
  22. package/dist/gateway/desktop-config.d.ts +7 -0
  23. package/dist/gateway/desktop-config.js +25 -0
  24. package/dist/gateway/index.d.ts +3 -0
  25. package/dist/gateway/index.js +2 -0
  26. package/dist/gateway/message-handler.d.ts +5 -0
  27. package/dist/gateway/message-handler.js +65 -0
  28. package/dist/gateway/methods/agent-cancel.d.ts +10 -0
  29. package/dist/gateway/methods/agent-cancel.js +17 -0
  30. package/dist/gateway/methods/agent-chat.d.ts +8 -0
  31. package/dist/gateway/methods/agent-chat.js +194 -0
  32. package/dist/gateway/methods/connect.d.ts +8 -0
  33. package/dist/gateway/methods/connect.js +15 -0
  34. package/dist/gateway/methods/install-skill-from-path.d.ts +13 -0
  35. package/dist/gateway/methods/install-skill-from-path.js +48 -0
  36. package/dist/gateway/methods/run-scheduled-task.d.ts +13 -0
  37. package/dist/gateway/methods/run-scheduled-task.js +164 -0
  38. package/dist/gateway/server.d.ts +10 -0
  39. package/dist/gateway/server.js +268 -0
  40. package/dist/gateway/types.d.ts +76 -0
  41. package/dist/gateway/types.js +1 -0
  42. package/dist/gateway/utils.d.ts +22 -0
  43. package/dist/gateway/utils.js +67 -0
  44. package/dist/index.d.ts +3 -0
  45. package/dist/index.js +3 -0
  46. package/dist/memory/build-summary.d.ts +6 -0
  47. package/dist/memory/build-summary.js +27 -0
  48. package/dist/memory/compaction-extension.d.ts +6 -0
  49. package/dist/memory/compaction-extension.js +23 -0
  50. package/dist/memory/embedding.d.ts +10 -0
  51. package/dist/memory/embedding.js +22 -0
  52. package/dist/memory/index.d.ts +29 -0
  53. package/dist/memory/index.js +66 -0
  54. package/dist/memory/types.d.ts +16 -0
  55. package/dist/memory/types.js +1 -0
  56. package/dist/memory/vector-store.d.ts +15 -0
  57. package/dist/memory/vector-store.js +65 -0
  58. package/dist/server/agent-config/agent-config.controller.d.ts +30 -0
  59. package/dist/server/agent-config/agent-config.controller.js +83 -0
  60. package/dist/server/agent-config/agent-config.module.d.ts +2 -0
  61. package/dist/server/agent-config/agent-config.module.js +19 -0
  62. package/dist/server/agent-config/agent-config.service.d.ts +34 -0
  63. package/dist/server/agent-config/agent-config.service.js +171 -0
  64. package/dist/server/agents/agents.controller.d.ts +41 -0
  65. package/dist/server/agents/agents.controller.js +120 -0
  66. package/dist/server/agents/agents.gateway.d.ts +21 -0
  67. package/dist/server/agents/agents.gateway.js +103 -0
  68. package/dist/server/agents/agents.module.d.ts +2 -0
  69. package/dist/server/agents/agents.module.js +20 -0
  70. package/dist/server/agents/agents.service.d.ts +63 -0
  71. package/dist/server/agents/agents.service.js +167 -0
  72. package/dist/server/app.module.d.ts +2 -0
  73. package/dist/server/app.module.js +36 -0
  74. package/dist/server/auth/auth.controller.d.ts +20 -0
  75. package/dist/server/auth/auth.controller.js +64 -0
  76. package/dist/server/auth/auth.module.d.ts +2 -0
  77. package/dist/server/auth/auth.module.js +19 -0
  78. package/dist/server/config/config.controller.d.ts +51 -0
  79. package/dist/server/config/config.controller.js +81 -0
  80. package/dist/server/config/config.module.d.ts +2 -0
  81. package/dist/server/config/config.module.js +19 -0
  82. package/dist/server/config/config.service.d.ts +34 -0
  83. package/dist/server/config/config.service.js +91 -0
  84. package/dist/server/database/database.module.d.ts +2 -0
  85. package/dist/server/database/database.module.js +18 -0
  86. package/dist/server/database/database.service.d.ts +11 -0
  87. package/dist/server/database/database.service.js +137 -0
  88. package/dist/server/main.d.ts +1 -0
  89. package/dist/server/main.js +18 -0
  90. package/dist/server/skills/skills.controller.d.ts +63 -0
  91. package/dist/server/skills/skills.controller.js +194 -0
  92. package/dist/server/skills/skills.module.d.ts +2 -0
  93. package/dist/server/skills/skills.module.js +22 -0
  94. package/dist/server/skills/skills.service.d.ts +63 -0
  95. package/dist/server/skills/skills.service.js +324 -0
  96. package/dist/server/tasks/tasks.controller.d.ts +52 -0
  97. package/dist/server/tasks/tasks.controller.js +163 -0
  98. package/dist/server/tasks/tasks.module.d.ts +2 -0
  99. package/dist/server/tasks/tasks.module.js +22 -0
  100. package/dist/server/tasks/tasks.service.d.ts +84 -0
  101. package/dist/server/tasks/tasks.service.js +313 -0
  102. package/dist/server/usage/usage.controller.d.ts +12 -0
  103. package/dist/server/usage/usage.controller.js +46 -0
  104. package/dist/server/usage/usage.module.d.ts +2 -0
  105. package/dist/server/usage/usage.module.js +19 -0
  106. package/dist/server/usage/usage.service.d.ts +21 -0
  107. package/dist/server/usage/usage.service.js +55 -0
  108. package/dist/server/users/users.controller.d.ts +35 -0
  109. package/dist/server/users/users.controller.js +69 -0
  110. package/dist/server/users/users.module.d.ts +2 -0
  111. package/dist/server/users/users.module.js +19 -0
  112. package/dist/server/users/users.service.d.ts +39 -0
  113. package/dist/server/users/users.service.js +140 -0
  114. package/dist/server/workspace/workspace.controller.d.ts +24 -0
  115. package/dist/server/workspace/workspace.controller.js +132 -0
  116. package/dist/server/workspace/workspace.module.d.ts +2 -0
  117. package/dist/server/workspace/workspace.module.js +21 -0
  118. package/dist/server/workspace/workspace.service.d.ts +25 -0
  119. package/dist/server/workspace/workspace.service.js +103 -0
  120. package/dist/tools/browser-tool.d.ts +10 -0
  121. package/dist/tools/browser-tool.js +362 -0
  122. package/dist/tools/index.d.ts +3 -0
  123. package/dist/tools/index.js +3 -0
  124. package/dist/tools/install-skill-tool.d.ts +9 -0
  125. package/dist/tools/install-skill-tool.js +77 -0
  126. package/dist/tools/save-experience-tool.d.ts +5 -0
  127. package/dist/tools/save-experience-tool.js +54 -0
  128. package/package.json +80 -0
  129. package/skills/agent-browser/SKILL.md +207 -0
  130. package/skills/agent-browser/references/authentication.md +202 -0
  131. package/skills/agent-browser/references/commands.md +259 -0
  132. package/skills/agent-browser/references/proxy-support.md +188 -0
  133. package/skills/agent-browser/references/session-management.md +193 -0
  134. package/skills/agent-browser/references/snapshot-refs.md +194 -0
  135. package/skills/agent-browser/references/video-recording.md +173 -0
  136. package/skills/agent-browser/templates/authenticated-session.sh +97 -0
  137. package/skills/agent-browser/templates/capture-workflow.sh +69 -0
  138. package/skills/agent-browser/templates/form-automation.sh +62 -0
  139. package/skills/find-skills/SKILL.md +140 -0
@@ -0,0 +1,25 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+ const CONFIG_DIR = join(homedir(), ".openbot", "desktop");
5
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
6
+ const DEFAULT_MAX_AGENT_SESSIONS = 5;
7
+ /**
8
+ * 读取桌面全局配置(与 Nest ConfigService 使用同一 config.json)。
9
+ * Gateway 进程内使用,用于获取 maxAgentSessions 等。
10
+ */
11
+ export function getDesktopConfig() {
12
+ try {
13
+ if (!existsSync(CONFIG_PATH)) {
14
+ return { maxAgentSessions: DEFAULT_MAX_AGENT_SESSIONS };
15
+ }
16
+ const content = readFileSync(CONFIG_PATH, "utf-8");
17
+ const data = JSON.parse(content);
18
+ const max = data.maxAgentSessions;
19
+ const maxAgentSessions = typeof max === "number" && max > 0 ? Math.floor(max) : DEFAULT_MAX_AGENT_SESSIONS;
20
+ return { maxAgentSessions };
21
+ }
22
+ catch {
23
+ return { maxAgentSessions: DEFAULT_MAX_AGENT_SESSIONS };
24
+ }
25
+ }
@@ -0,0 +1,3 @@
1
+ export { startGatewayServer } from "./server.js";
2
+ export { agentManager } from "../agent/agent-manager.js";
3
+ export type { GatewayClient, GatewayMessage, GatewayRequest, GatewayResponse, GatewayEvent, ConnectParams, AgentChatParams, } from "./types.js";
@@ -0,0 +1,2 @@
1
+ export { startGatewayServer } from "./server.js";
2
+ export { agentManager } from "../agent/agent-manager.js";
@@ -0,0 +1,5 @@
1
+ import type { GatewayClient } from "./types.js";
2
+ /**
3
+ * Handle incoming WebSocket message
4
+ */
5
+ export declare function handleMessage(client: GatewayClient, data: Buffer | string): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { parseMessage, send, createErrorResponse, createSuccessResponse } from "./utils.js";
2
+ import { handleConnect } from "./methods/connect.js";
3
+ import { handleAgentChat } from "./methods/agent-chat.js";
4
+ import { handleAgentCancel } from "./methods/agent-cancel.js";
5
+ /**
6
+ * Handle incoming WebSocket message
7
+ */
8
+ export async function handleMessage(client, data) {
9
+ const message = parseMessage(data);
10
+ if (!message) {
11
+ send(client.ws, createErrorResponse("unknown", "Invalid message format"));
12
+ return;
13
+ }
14
+ // Only handle request messages
15
+ if (message.type !== "request") {
16
+ return;
17
+ }
18
+ const request = message;
19
+ const { id, method, params } = request;
20
+ console.log(`Received request: ${method} (id: ${id})`);
21
+ try {
22
+ let result;
23
+ switch (method) {
24
+ case "connect":
25
+ result = await handleConnect(client, params || {});
26
+ break;
27
+ case "agent.chat":
28
+ // Check if client is authenticated
29
+ if (!client.authenticated) {
30
+ throw new Error("Not authenticated. Call 'connect' first.");
31
+ }
32
+ result = await handleAgentChat(client, params || {});
33
+ break;
34
+ case "agent.cancel":
35
+ if (!client.authenticated) {
36
+ throw new Error("Not authenticated. Call 'connect' first.");
37
+ }
38
+ result = await handleAgentCancel(client, params || {});
39
+ break;
40
+ case "subscribe_session":
41
+ // Handle session subscription
42
+ // Since handleAgentChat manages its own subscription per request,
43
+ // this might be for listening to async updates.
44
+ // For now, we just acknowledge it to prevent client errors.
45
+ // A more robust implementation would hook into agentManager.
46
+ console.log(`Client ${client.id} subscribed to session ${params.sessionId}`);
47
+ client.sessionId = params.sessionId; // Store session ID on client
48
+ result = { status: "subscribed", sessionId: params.sessionId };
49
+ break;
50
+ case "unsubscribe_session":
51
+ console.log(`Client ${client.id} unsubscribed from session`);
52
+ delete client.sessionId;
53
+ result = { status: "unsubscribed" };
54
+ break;
55
+ default:
56
+ throw new Error(`Unknown method: ${method}`);
57
+ }
58
+ // Send success response
59
+ send(client.ws, createSuccessResponse(id, result));
60
+ }
61
+ catch (error) {
62
+ console.error(`Error handling ${method}:`, error);
63
+ send(client.ws, createErrorResponse(id, error.message || "Internal error"));
64
+ }
65
+ }
@@ -0,0 +1,10 @@
1
+ import type { GatewayClient } from "../types.js";
2
+ /**
3
+ * Handle agent.cancel: abort the current turn for the given session.
4
+ * Uses pi-coding-agent's session.abort() to stop the running agent and wait until idle.
5
+ */
6
+ export declare function handleAgentCancel(client: GatewayClient, params: {
7
+ sessionId?: string;
8
+ }): Promise<{
9
+ status: string;
10
+ }>;
@@ -0,0 +1,17 @@
1
+ import { agentManager } from "../../agent/agent-manager.js";
2
+ /**
3
+ * Handle agent.cancel: abort the current turn for the given session.
4
+ * Uses pi-coding-agent's session.abort() to stop the running agent and wait until idle.
5
+ */
6
+ export async function handleAgentCancel(client, params) {
7
+ const sessionId = params?.sessionId ?? client.sessionId;
8
+ if (!sessionId) {
9
+ throw new Error("No session ID available");
10
+ }
11
+ const session = agentManager.getSession(sessionId);
12
+ if (!session) {
13
+ return { status: "no_session" };
14
+ }
15
+ await session.abort();
16
+ return { status: "aborted" };
17
+ }
@@ -0,0 +1,8 @@
1
+ import type { GatewayClient, AgentChatParams } from "../types.js";
2
+ /**
3
+ * Handle agent chat request with streaming support
4
+ */
5
+ export declare function handleAgentChat(client: GatewayClient, params: AgentChatParams): Promise<{
6
+ status: string;
7
+ sessionId: string;
8
+ }>;
@@ -0,0 +1,194 @@
1
+ import { agentManager } from "../../agent/agent-manager.js";
2
+ import { getExperienceContextForUserMessage } from "../../memory/index.js";
3
+ import { send, createEvent } from "../utils.js";
4
+ import { connectedClients } from "../clients.js";
5
+ import { getDesktopConfig } from "../desktop-config.js";
6
+ import { getBackendBaseUrl } from "../backend-url.js";
7
+ /**
8
+ * Report token usage to Desktop Server (persist to DB). No-op if backend URL not set.
9
+ */
10
+ async function reportTokenUsage(sessionId, source, tokens, options) {
11
+ const base = getBackendBaseUrl();
12
+ if (!base)
13
+ return;
14
+ const url = `${base.replace(/\/$/, "")}/server-api/usage`;
15
+ await fetch(url, {
16
+ method: "POST",
17
+ headers: { "Content-Type": "application/json" },
18
+ body: JSON.stringify({
19
+ sessionId,
20
+ source,
21
+ taskId: options?.taskId ?? undefined,
22
+ executionId: options?.executionId ?? undefined,
23
+ promptTokens: tokens.promptTokens,
24
+ completionTokens: tokens.completionTokens,
25
+ }),
26
+ });
27
+ }
28
+ /**
29
+ * Broadcast message to all clients subscribed to a session
30
+ */
31
+ function broadcastToSession(sessionId, message) {
32
+ for (const client of connectedClients) {
33
+ if (client.sessionId === sessionId) {
34
+ send(client.ws, message);
35
+ }
36
+ }
37
+ }
38
+ /**
39
+ * Handle agent chat request with streaming support
40
+ */
41
+ export async function handleAgentChat(client, params) {
42
+ const { message, sessionId, targetAgentId } = params;
43
+ if (!message || !message.trim()) {
44
+ throw new Error("Message is required");
45
+ }
46
+ // Use client's session ID if not provided
47
+ const targetSessionId = sessionId || client.sessionId;
48
+ if (!targetSessionId) {
49
+ throw new Error("No session ID available");
50
+ }
51
+ console.log(`Agent chat request for session ${targetSessionId}: ${message.substring(0, 50)}...`);
52
+ return handleAgentChatInner(client, targetSessionId, message, targetAgentId);
53
+ }
54
+ async function handleAgentChatInner(client, targetSessionId, message, targetAgentId) {
55
+ // 通过 sessionId 获取归属的 agentId,再通过 agentId 获取该智能体配置的 provider/model,用于创建 Agent Session
56
+ let workspace = "default";
57
+ let provider;
58
+ let modelId;
59
+ let sessionType;
60
+ let sessionAgentId = "default";
61
+ let apiKey;
62
+ const base = getBackendBaseUrl();
63
+ if (base) {
64
+ try {
65
+ const sessionRes = await fetch(`${base.replace(/\/$/, "")}/server-api/agents/sessions/${targetSessionId}`);
66
+ if (sessionRes.ok) {
67
+ const sessionData = (await sessionRes.json());
68
+ sessionType = sessionData?.data?.type;
69
+ sessionAgentId = sessionData?.data?.agentId ?? "default";
70
+ const agentRes = await fetch(`${base.replace(/\/$/, "")}/server-api/agent-config/${encodeURIComponent(sessionAgentId)}`);
71
+ if (agentRes.ok) {
72
+ const agentData = (await agentRes.json());
73
+ const agent = agentData?.data;
74
+ if (agent?.workspace)
75
+ workspace = agent.workspace;
76
+ if (agent?.provider)
77
+ provider = agent.provider;
78
+ if (agent?.model)
79
+ modelId = agent.model;
80
+ }
81
+ }
82
+ // 从桌面端全局配置读取当前 provider 的 API Key(设置里配置的会生效)
83
+ const configRes = await fetch(`${base.replace(/\/$/, "")}/server-api/config`);
84
+ if (configRes.ok) {
85
+ const configData = (await configRes.json());
86
+ const prov = provider ?? "deepseek";
87
+ const key = configData?.data?.providers?.[prov]?.apiKey;
88
+ if (key && typeof key === "string" && key.trim())
89
+ apiKey = key.trim();
90
+ }
91
+ }
92
+ catch (e) {
93
+ console.warn("[agent-chat] Failed to fetch session/agent config, using default:", e);
94
+ }
95
+ }
96
+ // system / scheduled:每次对话前先删再建,对话结束马上关闭,节省资源
97
+ const isEphemeralSession = sessionType === "system" || sessionType === "scheduled";
98
+ if (isEphemeralSession) {
99
+ agentManager.deleteSession(targetSessionId);
100
+ }
101
+ // system 会话用请求里的 targetAgentId;chat/scheduled 用 session 对应的 agentId 传给 install_skill
102
+ const effectiveTargetAgentId = sessionType === "system" ? targetAgentId : sessionAgentId;
103
+ const { maxAgentSessions } = getDesktopConfig();
104
+ let session;
105
+ try {
106
+ session = await agentManager.getOrCreateSession(targetSessionId, {
107
+ workspace,
108
+ provider,
109
+ modelId,
110
+ apiKey,
111
+ maxSessions: maxAgentSessions,
112
+ targetAgentId: effectiveTargetAgentId,
113
+ });
114
+ }
115
+ catch (err) {
116
+ const msg = err?.message ?? String(err);
117
+ if (msg.includes("No API key") || msg.includes("API key")) {
118
+ const prov = provider ?? "deepseek";
119
+ throw new Error(`未配置 ${prov} 的 API Key。请在桌面端「设置」-「模型/API」中配置,或运行:openbot login ${prov} <你的API Key>`);
120
+ }
121
+ throw err;
122
+ }
123
+ // Set up event listener for streaming
124
+ const unsubscribe = session.subscribe((event) => {
125
+ // console.log(`Agent event received: ${event.type}`); // Reduce noise
126
+ let wsMessage = null;
127
+ if (event.type === "message_update") {
128
+ const update = event;
129
+ if (update.assistantMessageEvent && update.assistantMessageEvent.type === "text_delta") {
130
+ wsMessage = createEvent("agent.chunk", { text: update.assistantMessageEvent.delta });
131
+ }
132
+ else if (update.assistantMessageEvent && update.assistantMessageEvent.type === "thinking_delta") {
133
+ wsMessage = createEvent("agent.chunk", { text: update.assistantMessageEvent.delta, isThinking: true });
134
+ }
135
+ }
136
+ else if (event.type === "tool_execution_start") {
137
+ wsMessage = createEvent("agent.tool", {
138
+ type: "start",
139
+ toolCallId: event.toolCallId,
140
+ toolName: event.toolName,
141
+ args: event.args
142
+ });
143
+ }
144
+ else if (event.type === "tool_execution_end") {
145
+ wsMessage = createEvent("agent.tool", {
146
+ type: "end",
147
+ toolCallId: event.toolCallId,
148
+ toolName: event.toolName,
149
+ result: event.result,
150
+ isError: event.isError
151
+ });
152
+ }
153
+ else if (event.type === "turn_end") {
154
+ // Explicit completion event required for frontend to stop loading state
155
+ // Only send on turn_end to avoid duplicate empty messages from message_end
156
+ wsMessage = createEvent("message_complete", { sessionId: targetSessionId, content: "" });
157
+ // Record token usage for this turn (message may have usage.input / usage.output)
158
+ const usage = event.message?.usage;
159
+ if (usage) {
160
+ const promptTokens = Number(usage.input ?? usage.input_tokens ?? 0) || 0;
161
+ const completionTokens = Number(usage.output ?? usage.output_tokens ?? 0) || 0;
162
+ if (promptTokens > 0 || completionTokens > 0) {
163
+ reportTokenUsage(targetSessionId, "chat", { promptTokens, completionTokens }).catch((err) => console.error("[agent-chat] reportTokenUsage failed:", err));
164
+ }
165
+ }
166
+ }
167
+ if (wsMessage) {
168
+ broadcastToSession(targetSessionId, wsMessage);
169
+ }
170
+ if (event.type === "turn_end" && isEphemeralSession) {
171
+ agentManager.deleteSession(targetSessionId);
172
+ }
173
+ });
174
+ try {
175
+ const experienceBlock = await getExperienceContextForUserMessage();
176
+ const userMessageToSend = experienceBlock.trim().length > 0
177
+ ? `${experienceBlock}\n\n用户问题:\n${message}`
178
+ : message;
179
+ // 若 agent 正在流式输出,deliverAs: 'followUp' 将本条消息排队,避免抛出 "Agent is already processing"
180
+ await session.sendUserMessage(userMessageToSend, { deliverAs: "followUp" });
181
+ console.log(`Agent chat completed for session ${targetSessionId}`);
182
+ return {
183
+ status: "completed",
184
+ sessionId: targetSessionId,
185
+ };
186
+ }
187
+ catch (error) {
188
+ console.error(`Error in agent chat:`, error);
189
+ throw error;
190
+ }
191
+ finally {
192
+ unsubscribe();
193
+ }
194
+ }
@@ -0,0 +1,8 @@
1
+ import type { GatewayClient, ConnectParams } from "../types.js";
2
+ /**
3
+ * Handle client connection request
4
+ */
5
+ export declare function handleConnect(client: GatewayClient, params: ConnectParams): Promise<{
6
+ sessionId: string;
7
+ status: string;
8
+ }>;
@@ -0,0 +1,15 @@
1
+ import { randomUUID } from "crypto";
2
+ /**
3
+ * Handle client connection request
4
+ */
5
+ export async function handleConnect(client, params) {
6
+ // Mark client as authenticated
7
+ client.authenticated = true;
8
+ // Use provided session ID or generate new one
9
+ client.sessionId = params.sessionId || randomUUID();
10
+ console.log(`Client ${client.id} connected with session ${client.sessionId}`);
11
+ return {
12
+ sessionId: client.sessionId || "",
13
+ status: "connected",
14
+ };
15
+ }
@@ -0,0 +1,13 @@
1
+ export interface InstallFromPathBody {
2
+ path: string;
3
+ scope?: "global" | "workspace";
4
+ workspace?: string;
5
+ }
6
+ export interface InstallFromPathResult {
7
+ success: true;
8
+ data: {
9
+ installDir: string;
10
+ name: string;
11
+ };
12
+ }
13
+ export declare function handleInstallSkillFromPath(body: InstallFromPathBody): Promise<InstallFromPathResult>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * 在 Gateway 层处理 POST /server-api/skills/install-from-path,
3
+ * 将本地技能目录复制到全局或工作区 skills,不依赖 Nest 路由,保证桌面端稳定可用。
4
+ */
5
+ import { cp, mkdir, realpath, rm, stat } from "fs/promises";
6
+ import { existsSync } from "fs";
7
+ import { basename, join, resolve } from "path";
8
+ import { getOpenbotAgentDir, getOpenbotWorkspaceDir } from "../../agent/agent-dir.js";
9
+ const SKILL_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
10
+ function getGlobalSkillsDir() {
11
+ return join(getOpenbotAgentDir(), "skills");
12
+ }
13
+ function getWorkspaceSkillsDir(workspaceName) {
14
+ return join(getOpenbotWorkspaceDir(), workspaceName, "skills");
15
+ }
16
+ export async function handleInstallSkillFromPath(body) {
17
+ const localPath = (body?.path ?? "").trim();
18
+ if (!localPath) {
19
+ throw new Error("path is required");
20
+ }
21
+ const pathToUse = resolve(localPath);
22
+ if (!existsSync(pathToUse)) {
23
+ throw new Error("本地路径不存在");
24
+ }
25
+ const pathStat = await stat(pathToUse);
26
+ if (!pathStat.isDirectory()) {
27
+ throw new Error("请选择技能目录");
28
+ }
29
+ const skillMdPath = join(pathToUse, "SKILL.md");
30
+ if (!existsSync(skillMdPath)) {
31
+ throw new Error("该目录下未找到 SKILL.md,不是有效的技能目录");
32
+ }
33
+ const scope = body?.scope ?? "global";
34
+ const workspaceName = body?.workspace ?? "default";
35
+ const targetDir = scope === "workspace" ? getWorkspaceSkillsDir(workspaceName) : getGlobalSkillsDir();
36
+ const baseName = basename(pathToUse) || "skill";
37
+ if (!baseName || !SKILL_NAME_REGEX.test(baseName)) {
38
+ throw new Error("技能目录名须为英文、数字、下划线或连字符");
39
+ }
40
+ const destPath = join(targetDir, baseName);
41
+ await mkdir(targetDir, { recursive: true });
42
+ if (existsSync(destPath)) {
43
+ await rm(destPath, { recursive: true });
44
+ }
45
+ const srcResolved = await realpath(pathToUse).catch(() => pathToUse);
46
+ await cp(srcResolved, destPath, { recursive: true });
47
+ return { success: true, data: { installDir: targetDir, name: baseName } };
48
+ }
@@ -0,0 +1,13 @@
1
+ import type { IncomingMessage, ServerResponse } from "http";
2
+ export interface RunScheduledTaskBody {
3
+ sessionId: string;
4
+ message: string;
5
+ workspace: string;
6
+ taskId?: string;
7
+ backendBaseUrl?: string;
8
+ }
9
+ /**
10
+ * Run a scheduled task: configure workspace, send message to agent, collect response, POST back to Nest.
11
+ * 执行完成后关闭并移除 AgentSession,避免空悬占用资源。
12
+ */
13
+ export declare function handleRunScheduledTask(req: IncomingMessage, res: ServerResponse): Promise<void>;
@@ -0,0 +1,164 @@
1
+ import { agentManager } from "../../agent/agent-manager.js";
2
+ import { getExperienceContextForUserMessage } from "../../memory/index.js";
3
+ async function reportTokenUsage(backendBaseUrl, sessionId, source, tokens, options) {
4
+ const url = `${backendBaseUrl.replace(/\/$/, "")}/server-api/usage`;
5
+ await fetch(url, {
6
+ method: "POST",
7
+ headers: { "Content-Type": "application/json" },
8
+ body: JSON.stringify({
9
+ sessionId,
10
+ source,
11
+ taskId: options?.taskId ?? undefined,
12
+ executionId: options?.executionId ?? undefined,
13
+ promptTokens: tokens.promptTokens,
14
+ completionTokens: tokens.completionTokens,
15
+ }),
16
+ });
17
+ }
18
+ async function readBody(req) {
19
+ return new Promise((resolve, reject) => {
20
+ const chunks = [];
21
+ req.on("data", (chunk) => chunks.push(chunk));
22
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
23
+ req.on("error", reject);
24
+ });
25
+ }
26
+ /**
27
+ * Run a scheduled task: configure workspace, send message to agent, collect response, POST back to Nest.
28
+ * 执行完成后关闭并移除 AgentSession,避免空悬占用资源。
29
+ */
30
+ export async function handleRunScheduledTask(req, res) {
31
+ if (req.method !== "POST") {
32
+ res.writeHead(405, { "Content-Type": "application/json" });
33
+ res.end(JSON.stringify({ error: "Method not allowed" }));
34
+ return;
35
+ }
36
+ let body;
37
+ try {
38
+ const raw = await readBody(req);
39
+ body = JSON.parse(raw);
40
+ }
41
+ catch {
42
+ res.writeHead(400, { "Content-Type": "application/json" });
43
+ res.end(JSON.stringify({ error: "Invalid JSON body" }));
44
+ return;
45
+ }
46
+ const { sessionId, message, workspace, backendBaseUrl, taskId } = body;
47
+ if (!sessionId || !message || !workspace) {
48
+ res.writeHead(400, { "Content-Type": "application/json" });
49
+ res.end(JSON.stringify({ error: "sessionId, message, workspace required" }));
50
+ return;
51
+ }
52
+ // 与对话一致:仅通过 sessionId 获取 session,用 session.agentId 拉取 agent 配置与 API Key;未取到时用 "default",不依赖 body.workspace
53
+ let resolvedWorkspace = "default";
54
+ let provider;
55
+ let modelId;
56
+ let apiKey;
57
+ let sessionAgentId = "default";
58
+ const base = (backendBaseUrl ?? "").replace(/\/$/, "");
59
+ if (base) {
60
+ try {
61
+ const sessionRes = await fetch(`${base}/server-api/agents/sessions/${encodeURIComponent(sessionId)}`);
62
+ if (sessionRes.ok) {
63
+ const sessionData = (await sessionRes.json());
64
+ sessionAgentId = sessionData?.data?.agentId ?? "default";
65
+ if (sessionData?.data?.workspace)
66
+ resolvedWorkspace = sessionData.data.workspace;
67
+ }
68
+ const agentRes = await fetch(`${base}/server-api/agent-config/${encodeURIComponent(sessionAgentId)}`);
69
+ if (agentRes.ok) {
70
+ const agentData = (await agentRes.json());
71
+ const agent = agentData?.data;
72
+ if (agent?.workspace)
73
+ resolvedWorkspace = agent.workspace;
74
+ if (agent?.provider)
75
+ provider = agent.provider;
76
+ if (agent?.model)
77
+ modelId = agent.model;
78
+ }
79
+ const configRes = await fetch(`${base}/server-api/config`);
80
+ if (configRes.ok) {
81
+ const configData = (await configRes.json());
82
+ const prov = provider ?? "deepseek";
83
+ const key = configData?.data?.providers?.[prov]?.apiKey;
84
+ if (key && typeof key === "string" && key.trim())
85
+ apiKey = key.trim();
86
+ }
87
+ }
88
+ catch (e) {
89
+ console.warn("[run-scheduled-task] Failed to fetch session/agent/config, using default:", e);
90
+ }
91
+ }
92
+ try {
93
+ const session = await agentManager.getOrCreateSession(sessionId, {
94
+ workspace: resolvedWorkspace,
95
+ provider,
96
+ modelId,
97
+ apiKey,
98
+ });
99
+ let assistantContent = "";
100
+ let turnPromptTokens = 0;
101
+ let turnCompletionTokens = 0;
102
+ const unsubscribe = session.subscribe((event) => {
103
+ if (event.type === "message_update" && event.assistantMessageEvent) {
104
+ const ev = event.assistantMessageEvent;
105
+ if (ev.type === "text_delta" && ev.delta) {
106
+ assistantContent += ev.delta;
107
+ }
108
+ }
109
+ else if (event.type === "turn_end") {
110
+ const usage = event.message?.usage;
111
+ if (usage) {
112
+ turnPromptTokens += Number(usage.input ?? usage.input_tokens ?? 0) || 0;
113
+ turnCompletionTokens += Number(usage.output ?? usage.output_tokens ?? 0) || 0;
114
+ }
115
+ }
116
+ });
117
+ const experienceBlock = await getExperienceContextForUserMessage();
118
+ const userMessageToSend = experienceBlock.trim().length > 0
119
+ ? `${experienceBlock}\n\n用户问题:\n${message}`
120
+ : message;
121
+ // 定时任务复用同一 session:若上次执行未结束会报 "Agent is already processing"。先等待空闲再发,避免并发。
122
+ const idleTimeoutMs = 10 * 60 * 1000;
123
+ const pollMs = 2000;
124
+ let waited = 0;
125
+ while (session.isStreaming && waited < idleTimeoutMs) {
126
+ await new Promise((r) => setTimeout(r, pollMs));
127
+ waited += pollMs;
128
+ }
129
+ if (session.isStreaming) {
130
+ throw new Error("Session still busy after waiting; try again later.");
131
+ }
132
+ await session.sendUserMessage(userMessageToSend);
133
+ unsubscribe();
134
+ if (backendBaseUrl && assistantContent !== undefined) {
135
+ const url = `${backendBaseUrl.replace(/\/$/, "")}/server-api/agents/sessions/${encodeURIComponent(sessionId)}/messages`;
136
+ await fetch(url, {
137
+ method: "POST",
138
+ headers: { "Content-Type": "application/json" },
139
+ body: JSON.stringify({ role: "assistant", content: assistantContent }),
140
+ }).catch((err) => console.error("[run-scheduled-task] POST assistant message failed:", err));
141
+ }
142
+ if (backendBaseUrl && (turnPromptTokens > 0 || turnCompletionTokens > 0)) {
143
+ reportTokenUsage(backendBaseUrl, sessionId, "scheduled_task", {
144
+ promptTokens: turnPromptTokens,
145
+ completionTokens: turnCompletionTokens,
146
+ }, { taskId }).catch((err) => console.error("[run-scheduled-task] reportTokenUsage failed:", err));
147
+ }
148
+ res.writeHead(200, { "Content-Type": "application/json" });
149
+ res.end(JSON.stringify({ success: true, sessionId, assistantContent: assistantContent ?? "" }));
150
+ }
151
+ catch (error) {
152
+ console.error("[run-scheduled-task] error:", error);
153
+ const msg = error?.message ?? String(error);
154
+ const friendlyError = msg.includes("No API key") || msg.includes("API key")
155
+ ? "未配置大模型 API Key。请在桌面端「设置」-「模型配置」中为当前智能体选择 Provider 并保存,并确保在「Provider 配置」中已填写对应 API Key。"
156
+ : msg || "Internal server error";
157
+ res.writeHead(500, { "Content-Type": "application/json" });
158
+ res.end(JSON.stringify({ success: false, error: friendlyError }));
159
+ }
160
+ finally {
161
+ // 执行完成(成功或失败)后立即关闭并移除该 AgentSession,避免空悬占用内存/连接等资源
162
+ agentManager.deleteSession(sessionId);
163
+ }
164
+ }
@@ -0,0 +1,10 @@
1
+ import { WebSocketServer } from "ws";
2
+ import { type Server } from "http";
3
+ /**
4
+ * Start WebSocket gateway server
5
+ */
6
+ export declare function startGatewayServer(port?: number): Promise<{
7
+ httpServer: Server;
8
+ wss: WebSocketServer;
9
+ close: () => Promise<void>;
10
+ }>;