@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.
- package/README.md +212 -0
- package/dist/agent/agent-dir.d.ts +14 -0
- package/dist/agent/agent-dir.js +75 -0
- package/dist/agent/agent-manager.d.ts +61 -0
- package/dist/agent/agent-manager.js +257 -0
- package/dist/agent/config-manager.d.ts +25 -0
- package/dist/agent/config-manager.js +84 -0
- package/dist/agent/desktop-config.d.ts +15 -0
- package/dist/agent/desktop-config.js +91 -0
- package/dist/agent/run.d.ts +26 -0
- package/dist/agent/run.js +65 -0
- package/dist/agent/skills.d.ts +20 -0
- package/dist/agent/skills.js +86 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +168 -0
- package/dist/gateway/backend-url.d.ts +2 -0
- package/dist/gateway/backend-url.js +11 -0
- package/dist/gateway/clients.d.ts +5 -0
- package/dist/gateway/clients.js +4 -0
- package/dist/gateway/connection-handler.d.ts +6 -0
- package/dist/gateway/connection-handler.js +48 -0
- package/dist/gateway/desktop-config.d.ts +7 -0
- package/dist/gateway/desktop-config.js +25 -0
- package/dist/gateway/index.d.ts +3 -0
- package/dist/gateway/index.js +2 -0
- package/dist/gateway/message-handler.d.ts +5 -0
- package/dist/gateway/message-handler.js +65 -0
- package/dist/gateway/methods/agent-cancel.d.ts +10 -0
- package/dist/gateway/methods/agent-cancel.js +17 -0
- package/dist/gateway/methods/agent-chat.d.ts +8 -0
- package/dist/gateway/methods/agent-chat.js +194 -0
- package/dist/gateway/methods/connect.d.ts +8 -0
- package/dist/gateway/methods/connect.js +15 -0
- package/dist/gateway/methods/install-skill-from-path.d.ts +13 -0
- package/dist/gateway/methods/install-skill-from-path.js +48 -0
- package/dist/gateway/methods/run-scheduled-task.d.ts +13 -0
- package/dist/gateway/methods/run-scheduled-task.js +164 -0
- package/dist/gateway/server.d.ts +10 -0
- package/dist/gateway/server.js +268 -0
- package/dist/gateway/types.d.ts +76 -0
- package/dist/gateway/types.js +1 -0
- package/dist/gateway/utils.d.ts +22 -0
- package/dist/gateway/utils.js +67 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/memory/build-summary.d.ts +6 -0
- package/dist/memory/build-summary.js +27 -0
- package/dist/memory/compaction-extension.d.ts +6 -0
- package/dist/memory/compaction-extension.js +23 -0
- package/dist/memory/embedding.d.ts +10 -0
- package/dist/memory/embedding.js +22 -0
- package/dist/memory/index.d.ts +29 -0
- package/dist/memory/index.js +66 -0
- package/dist/memory/types.d.ts +16 -0
- package/dist/memory/types.js +1 -0
- package/dist/memory/vector-store.d.ts +15 -0
- package/dist/memory/vector-store.js +65 -0
- package/dist/server/agent-config/agent-config.controller.d.ts +30 -0
- package/dist/server/agent-config/agent-config.controller.js +83 -0
- package/dist/server/agent-config/agent-config.module.d.ts +2 -0
- package/dist/server/agent-config/agent-config.module.js +19 -0
- package/dist/server/agent-config/agent-config.service.d.ts +34 -0
- package/dist/server/agent-config/agent-config.service.js +171 -0
- package/dist/server/agents/agents.controller.d.ts +41 -0
- package/dist/server/agents/agents.controller.js +120 -0
- package/dist/server/agents/agents.gateway.d.ts +21 -0
- package/dist/server/agents/agents.gateway.js +103 -0
- package/dist/server/agents/agents.module.d.ts +2 -0
- package/dist/server/agents/agents.module.js +20 -0
- package/dist/server/agents/agents.service.d.ts +63 -0
- package/dist/server/agents/agents.service.js +167 -0
- package/dist/server/app.module.d.ts +2 -0
- package/dist/server/app.module.js +36 -0
- package/dist/server/auth/auth.controller.d.ts +20 -0
- package/dist/server/auth/auth.controller.js +64 -0
- package/dist/server/auth/auth.module.d.ts +2 -0
- package/dist/server/auth/auth.module.js +19 -0
- package/dist/server/config/config.controller.d.ts +51 -0
- package/dist/server/config/config.controller.js +81 -0
- package/dist/server/config/config.module.d.ts +2 -0
- package/dist/server/config/config.module.js +19 -0
- package/dist/server/config/config.service.d.ts +34 -0
- package/dist/server/config/config.service.js +91 -0
- package/dist/server/database/database.module.d.ts +2 -0
- package/dist/server/database/database.module.js +18 -0
- package/dist/server/database/database.service.d.ts +11 -0
- package/dist/server/database/database.service.js +137 -0
- package/dist/server/main.d.ts +1 -0
- package/dist/server/main.js +18 -0
- package/dist/server/skills/skills.controller.d.ts +63 -0
- package/dist/server/skills/skills.controller.js +194 -0
- package/dist/server/skills/skills.module.d.ts +2 -0
- package/dist/server/skills/skills.module.js +22 -0
- package/dist/server/skills/skills.service.d.ts +63 -0
- package/dist/server/skills/skills.service.js +324 -0
- package/dist/server/tasks/tasks.controller.d.ts +52 -0
- package/dist/server/tasks/tasks.controller.js +163 -0
- package/dist/server/tasks/tasks.module.d.ts +2 -0
- package/dist/server/tasks/tasks.module.js +22 -0
- package/dist/server/tasks/tasks.service.d.ts +84 -0
- package/dist/server/tasks/tasks.service.js +313 -0
- package/dist/server/usage/usage.controller.d.ts +12 -0
- package/dist/server/usage/usage.controller.js +46 -0
- package/dist/server/usage/usage.module.d.ts +2 -0
- package/dist/server/usage/usage.module.js +19 -0
- package/dist/server/usage/usage.service.d.ts +21 -0
- package/dist/server/usage/usage.service.js +55 -0
- package/dist/server/users/users.controller.d.ts +35 -0
- package/dist/server/users/users.controller.js +69 -0
- package/dist/server/users/users.module.d.ts +2 -0
- package/dist/server/users/users.module.js +19 -0
- package/dist/server/users/users.service.d.ts +39 -0
- package/dist/server/users/users.service.js +140 -0
- package/dist/server/workspace/workspace.controller.d.ts +24 -0
- package/dist/server/workspace/workspace.controller.js +132 -0
- package/dist/server/workspace/workspace.module.d.ts +2 -0
- package/dist/server/workspace/workspace.module.js +21 -0
- package/dist/server/workspace/workspace.service.d.ts +25 -0
- package/dist/server/workspace/workspace.service.js +103 -0
- package/dist/tools/browser-tool.d.ts +10 -0
- package/dist/tools/browser-tool.js +362 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/install-skill-tool.d.ts +9 -0
- package/dist/tools/install-skill-tool.js +77 -0
- package/dist/tools/save-experience-tool.d.ts +5 -0
- package/dist/tools/save-experience-tool.js +54 -0
- package/package.json +80 -0
- package/skills/agent-browser/SKILL.md +207 -0
- package/skills/agent-browser/references/authentication.md +202 -0
- package/skills/agent-browser/references/commands.md +259 -0
- package/skills/agent-browser/references/proxy-support.md +188 -0
- package/skills/agent-browser/references/session-management.md +193 -0
- package/skills/agent-browser/references/snapshot-refs.md +194 -0
- package/skills/agent-browser/references/video-recording.md +173 -0
- package/skills/agent-browser/templates/authenticated-session.sh +97 -0
- package/skills/agent-browser/templates/capture-workflow.sh +69 -0
- package/skills/agent-browser/templates/form-automation.sh +62 -0
- package/skills/find-skills/SKILL.md +140 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avoid MaxListenersExceededWarning when Browser Tool is used repeatedly.
|
|
3
|
+
* Playwright/agent-browser attach abort listeners to the same AbortSignal per action;
|
|
4
|
+
* Node's default EventTarget maxListeners is 10.
|
|
5
|
+
*/
|
|
6
|
+
const Et = globalThis.EventTarget;
|
|
7
|
+
if (Et?.prototype?.addEventListener && Et.prototype.setMaxListeners) {
|
|
8
|
+
const add = Et.prototype.addEventListener;
|
|
9
|
+
Et.prototype.addEventListener = function (type, listener, options) {
|
|
10
|
+
if (type === "abort" && typeof this.setMaxListeners === "function") {
|
|
11
|
+
this.setMaxListeners(32);
|
|
12
|
+
}
|
|
13
|
+
return add.call(this, type, listener, options);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
import { WebSocketServer } from "ws";
|
|
17
|
+
import { createServer, request as httpRequest } from "http";
|
|
18
|
+
import { handleConnection } from "./connection-handler.js";
|
|
19
|
+
import { readFile, stat } from "fs/promises";
|
|
20
|
+
import { join, extname } from "path";
|
|
21
|
+
import { existsSync } from "fs";
|
|
22
|
+
import { spawn } from "child_process";
|
|
23
|
+
import { createServer as createNetServer } from "net";
|
|
24
|
+
import { handleRunScheduledTask } from "./methods/run-scheduled-task.js";
|
|
25
|
+
import { handleInstallSkillFromPath } from "./methods/install-skill-from-path.js";
|
|
26
|
+
import { setBackendBaseUrl } from "./backend-url.js";
|
|
27
|
+
/**
|
|
28
|
+
* Find an available port starting from startPort
|
|
29
|
+
*/
|
|
30
|
+
async function findAvailablePort(startPort) {
|
|
31
|
+
let port = startPort;
|
|
32
|
+
while (true) {
|
|
33
|
+
try {
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
const server = createNetServer();
|
|
36
|
+
server.once("error", (err) => {
|
|
37
|
+
if (err.code === "EADDRINUSE") {
|
|
38
|
+
resolve(); // Port taken, try next
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
reject(err);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
server.once("listening", () => {
|
|
45
|
+
server.close(() => resolve()); // Port available
|
|
46
|
+
});
|
|
47
|
+
server.listen(port);
|
|
48
|
+
});
|
|
49
|
+
// If we get here and the server listened successfully (then closed), checking if it was actually available logic needs care.
|
|
50
|
+
// Actually the above logic is slightly flawed for "resolve on error".
|
|
51
|
+
// Let's refine:
|
|
52
|
+
// If listen succeeds -> port is free. return it.
|
|
53
|
+
// If EADDRINUSE -> port busy. loop continue.
|
|
54
|
+
const isAvailable = await new Promise((resolve) => {
|
|
55
|
+
const server = createNetServer();
|
|
56
|
+
server.once("error", () => resolve(false));
|
|
57
|
+
server.once("listening", () => {
|
|
58
|
+
server.close(() => resolve(true));
|
|
59
|
+
});
|
|
60
|
+
server.listen(port);
|
|
61
|
+
});
|
|
62
|
+
if (isAvailable)
|
|
63
|
+
return port;
|
|
64
|
+
port++;
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
port++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* MIME types for static files
|
|
73
|
+
*/
|
|
74
|
+
const MIME_TYPES = {
|
|
75
|
+
".html": "text/html",
|
|
76
|
+
".js": "text/javascript",
|
|
77
|
+
".css": "text/css",
|
|
78
|
+
".json": "application/json",
|
|
79
|
+
".png": "image/png",
|
|
80
|
+
".jpg": "image/jpeg",
|
|
81
|
+
".gif": "image/gif",
|
|
82
|
+
".svg": "image/svg+xml",
|
|
83
|
+
".ico": "image/x-icon",
|
|
84
|
+
".woff": "font/woff",
|
|
85
|
+
".woff2": "font/woff2",
|
|
86
|
+
".ttf": "font/ttf",
|
|
87
|
+
".eot": "application/vnd.ms-fontobject",
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Start WebSocket gateway server
|
|
91
|
+
*/
|
|
92
|
+
export async function startGatewayServer(port = 3000) {
|
|
93
|
+
console.log(`Starting gateway server on port ${port}...`);
|
|
94
|
+
// 1. Find available port for Desktop Server
|
|
95
|
+
const backendPort = await findAvailablePort(3001);
|
|
96
|
+
console.log(`Found available port for Desktop Server: ${backendPort}`);
|
|
97
|
+
setBackendBaseUrl(`http://localhost:${backendPort}`);
|
|
98
|
+
// 2. Start Desktop Server
|
|
99
|
+
let backendProcess = null;
|
|
100
|
+
const serverPath = join(process.cwd(), "dist/server/main.js");
|
|
101
|
+
if (existsSync(serverPath)) {
|
|
102
|
+
console.log(`Spawning Desktop Server at ${serverPath}...`);
|
|
103
|
+
backendProcess = spawn("node", [serverPath], {
|
|
104
|
+
cwd: process.cwd(),
|
|
105
|
+
env: { ...process.env, PORT: backendPort.toString() },
|
|
106
|
+
stdio: ["ignore", "pipe", "pipe"], // Pipe stdout/stderr to capture logs
|
|
107
|
+
});
|
|
108
|
+
backendProcess.stdout?.on("data", (data) => {
|
|
109
|
+
const str = data.toString().trim();
|
|
110
|
+
if (str)
|
|
111
|
+
console.log(`[Desktop Server] ${str}`);
|
|
112
|
+
});
|
|
113
|
+
backendProcess.stderr?.on("data", (data) => {
|
|
114
|
+
const str = data.toString().trim();
|
|
115
|
+
if (str)
|
|
116
|
+
console.error(`[Desktop Server Error] ${str}`);
|
|
117
|
+
});
|
|
118
|
+
backendProcess.on("exit", (code) => {
|
|
119
|
+
if (code !== 0 && code !== null) {
|
|
120
|
+
console.error(`Desktop Server exited with code ${code}`);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.warn("⚠️ Desktop Server build not found. Skipping auto-start.");
|
|
126
|
+
console.warn(" Run 'npm run desktop:build' to build the server.");
|
|
127
|
+
}
|
|
128
|
+
// Create HTTP server
|
|
129
|
+
const httpServer = createServer(async (req, res) => {
|
|
130
|
+
// Simple health check endpoint
|
|
131
|
+
if (req.url === "/health") {
|
|
132
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
133
|
+
res.end(JSON.stringify({ status: "ok", timestamp: Date.now() }));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Scheduled task: run agent and POST assistant message back to Nest
|
|
137
|
+
const pathname = req.url?.split("?")[0] || "";
|
|
138
|
+
if (req.method === "POST" && pathname === "/run-scheduled-task") {
|
|
139
|
+
await handleRunScheduledTask(req, res);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// 本地技能目录安装:在 Gateway 层直接处理,不依赖 Nest,保证桌面端稳定可用
|
|
143
|
+
if (req.method === "POST" && pathname === "/server-api/skills/install-from-path") {
|
|
144
|
+
const body = await new Promise((resolve, reject) => {
|
|
145
|
+
const chunks = [];
|
|
146
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
147
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
148
|
+
req.on("error", reject);
|
|
149
|
+
});
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(body || "{}");
|
|
152
|
+
const result = await handleInstallSkillFromPath({
|
|
153
|
+
path: parsed.path ?? "",
|
|
154
|
+
scope: parsed.scope === "workspace" ? "workspace" : "global",
|
|
155
|
+
workspace: parsed.workspace,
|
|
156
|
+
});
|
|
157
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
158
|
+
res.end(JSON.stringify(result));
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
162
|
+
const code = message.includes("required") || message.includes("不存在") || message.includes("SKILL.md") || message.includes("目录名") ? 400 : 500;
|
|
163
|
+
res.writeHead(code, { "Content-Type": "application/json" });
|
|
164
|
+
res.end(JSON.stringify({ success: false, message }));
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Proxy API requests to Backend (prefixed with /server-api)
|
|
169
|
+
if (req.url && req.url.startsWith("/server-api")) {
|
|
170
|
+
const options = {
|
|
171
|
+
hostname: "localhost",
|
|
172
|
+
port: backendPort, // Use discovered port
|
|
173
|
+
path: req.url,
|
|
174
|
+
method: req.method,
|
|
175
|
+
headers: req.headers,
|
|
176
|
+
};
|
|
177
|
+
const proxyReq = httpRequest(options, (proxyRes) => {
|
|
178
|
+
res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
|
|
179
|
+
proxyRes.pipe(res, { end: true });
|
|
180
|
+
});
|
|
181
|
+
proxyReq.on("error", (err) => {
|
|
182
|
+
console.error(`Proxy error to :${backendPort}`, err);
|
|
183
|
+
if (!res.headersSent) {
|
|
184
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
185
|
+
res.end(JSON.stringify({ error: "Bad Gateway", message: "Failed to connect to backend server" }));
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
req.pipe(proxyReq, { end: true });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Serve static files
|
|
192
|
+
try {
|
|
193
|
+
const staticDir = join(process.cwd(), "desktop/renderer/dist");
|
|
194
|
+
// Normalize URL to remove query parameters and ensuring it starts with /
|
|
195
|
+
const urlPath = req.url?.split("?")[0] || "/";
|
|
196
|
+
// Determine file path
|
|
197
|
+
let filePath = join(staticDir, urlPath === "/" ? "index.html" : urlPath);
|
|
198
|
+
// Check if file exists
|
|
199
|
+
try {
|
|
200
|
+
const stats = await stat(filePath);
|
|
201
|
+
if (stats.isDirectory()) {
|
|
202
|
+
filePath = join(filePath, "index.html");
|
|
203
|
+
await stat(filePath); // Check if index.html exists
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// File not found
|
|
208
|
+
// SPA Fallback: serve index.html for non-API requests accepting HTML
|
|
209
|
+
if (req.headers.accept?.includes("text/html") && req.method === "GET") {
|
|
210
|
+
filePath = join(staticDir, "index.html");
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
214
|
+
res.end("Not Found");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Read and serve file
|
|
219
|
+
const content = await readFile(filePath);
|
|
220
|
+
const ext = extname(filePath).toLowerCase();
|
|
221
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
222
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
223
|
+
res.end(content);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
console.error("Static file error:", error);
|
|
227
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
228
|
+
res.end("Internal Server Error");
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
// Create WebSocket server
|
|
232
|
+
const wss = new WebSocketServer({ server: httpServer });
|
|
233
|
+
// Handle new connections
|
|
234
|
+
wss.on("connection", (ws, req) => {
|
|
235
|
+
handleConnection(ws, req);
|
|
236
|
+
});
|
|
237
|
+
// Start listening
|
|
238
|
+
await new Promise((resolve) => {
|
|
239
|
+
httpServer.listen(port, () => {
|
|
240
|
+
console.log(`✅ Gateway server listening on ws://localhost:${port}`);
|
|
241
|
+
console.log(` Health check: http://localhost:${port}/health`);
|
|
242
|
+
resolve();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
// Cleanup function
|
|
246
|
+
const close = async () => {
|
|
247
|
+
console.log("Closing gateway server...");
|
|
248
|
+
// Stop Desktop Server
|
|
249
|
+
if (backendProcess) {
|
|
250
|
+
console.log("Stopping Desktop Server...");
|
|
251
|
+
backendProcess.kill();
|
|
252
|
+
}
|
|
253
|
+
// Close all WebSocket connections
|
|
254
|
+
wss.clients.forEach((client) => {
|
|
255
|
+
client.close();
|
|
256
|
+
});
|
|
257
|
+
// Close WebSocket server
|
|
258
|
+
await new Promise((resolve) => {
|
|
259
|
+
wss.close(() => resolve());
|
|
260
|
+
});
|
|
261
|
+
// Close HTTP server
|
|
262
|
+
await new Promise((resolve) => {
|
|
263
|
+
httpServer.close(() => resolve());
|
|
264
|
+
});
|
|
265
|
+
console.log("Gateway server closed");
|
|
266
|
+
};
|
|
267
|
+
return { httpServer, wss, close };
|
|
268
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { WebSocket } from "ws";
|
|
2
|
+
/**
|
|
3
|
+
* Gateway client connection
|
|
4
|
+
*/
|
|
5
|
+
export interface GatewayClient {
|
|
6
|
+
id: string;
|
|
7
|
+
ws: WebSocket;
|
|
8
|
+
authenticated: boolean;
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
connectedAt: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Gateway message types
|
|
14
|
+
*/
|
|
15
|
+
export type GatewayMessageType = "request" | "response" | "event";
|
|
16
|
+
/**
|
|
17
|
+
* Base gateway message
|
|
18
|
+
*/
|
|
19
|
+
export interface GatewayMessage {
|
|
20
|
+
type: GatewayMessageType;
|
|
21
|
+
id?: string;
|
|
22
|
+
method?: string;
|
|
23
|
+
params?: any;
|
|
24
|
+
result?: any;
|
|
25
|
+
error?: {
|
|
26
|
+
message: string;
|
|
27
|
+
code?: string;
|
|
28
|
+
};
|
|
29
|
+
event?: string;
|
|
30
|
+
payload?: any;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Request message
|
|
34
|
+
*/
|
|
35
|
+
export interface GatewayRequest extends GatewayMessage {
|
|
36
|
+
type: "request";
|
|
37
|
+
id: string;
|
|
38
|
+
method: string;
|
|
39
|
+
params?: any;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Response message
|
|
43
|
+
*/
|
|
44
|
+
export interface GatewayResponse extends GatewayMessage {
|
|
45
|
+
type: "response";
|
|
46
|
+
id: string;
|
|
47
|
+
result?: any;
|
|
48
|
+
error?: {
|
|
49
|
+
message: string;
|
|
50
|
+
code?: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Event message
|
|
55
|
+
*/
|
|
56
|
+
export interface GatewayEvent extends GatewayMessage {
|
|
57
|
+
type: "event";
|
|
58
|
+
event: string;
|
|
59
|
+
payload?: any;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Connect params
|
|
63
|
+
*/
|
|
64
|
+
export interface ConnectParams {
|
|
65
|
+
sessionId?: string;
|
|
66
|
+
nonce?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Agent chat params
|
|
70
|
+
*/
|
|
71
|
+
export interface AgentChatParams {
|
|
72
|
+
message: string;
|
|
73
|
+
sessionId?: string;
|
|
74
|
+
/** 对话/安装目标:具体 agentId,或 "global"|"all" 表示全局;用于 install_skill 等隔离 */
|
|
75
|
+
targetAgentId?: string;
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { WebSocket } from "ws";
|
|
2
|
+
import type { GatewayMessage } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Send JSON message to WebSocket client
|
|
5
|
+
*/
|
|
6
|
+
export declare function send(ws: WebSocket, message: GatewayMessage): void;
|
|
7
|
+
/**
|
|
8
|
+
* Parse incoming WebSocket message
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseMessage(data: Buffer | string): GatewayMessage | null;
|
|
11
|
+
/**
|
|
12
|
+
* Create error response
|
|
13
|
+
*/
|
|
14
|
+
export declare function createErrorResponse(id: string, message: string, code?: string): GatewayMessage;
|
|
15
|
+
/**
|
|
16
|
+
* Create success response
|
|
17
|
+
*/
|
|
18
|
+
export declare function createSuccessResponse(id: string, result: any): GatewayMessage;
|
|
19
|
+
/**
|
|
20
|
+
* Create event message
|
|
21
|
+
*/
|
|
22
|
+
export declare function createEvent(event: string, payload: any): GatewayMessage;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send JSON message to WebSocket client
|
|
3
|
+
*/
|
|
4
|
+
export function send(ws, message) {
|
|
5
|
+
try {
|
|
6
|
+
ws.send(JSON.stringify(message));
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
console.error("Failed to send message:", error);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Parse incoming WebSocket message
|
|
14
|
+
*/
|
|
15
|
+
export function parseMessage(data) {
|
|
16
|
+
try {
|
|
17
|
+
const text = typeof data === "string" ? data : data.toString();
|
|
18
|
+
const obj = JSON.parse(text);
|
|
19
|
+
// If type is missing, try to infer it
|
|
20
|
+
if (!obj.type) {
|
|
21
|
+
if (obj.method) {
|
|
22
|
+
obj.type = "request";
|
|
23
|
+
}
|
|
24
|
+
else if (obj.event) {
|
|
25
|
+
obj.type = "event";
|
|
26
|
+
}
|
|
27
|
+
else if (obj.result !== undefined || obj.error !== undefined) {
|
|
28
|
+
obj.type = "response";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return obj;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error("Failed to parse message:", error);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create error response
|
|
40
|
+
*/
|
|
41
|
+
export function createErrorResponse(id, message, code) {
|
|
42
|
+
return {
|
|
43
|
+
type: "response",
|
|
44
|
+
id,
|
|
45
|
+
error: { message, code },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create success response
|
|
50
|
+
*/
|
|
51
|
+
export function createSuccessResponse(id, result) {
|
|
52
|
+
return {
|
|
53
|
+
type: "response",
|
|
54
|
+
id,
|
|
55
|
+
result,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create event message
|
|
60
|
+
*/
|
|
61
|
+
export function createEvent(event, payload) {
|
|
62
|
+
return {
|
|
63
|
+
type: "event",
|
|
64
|
+
event,
|
|
65
|
+
payload,
|
|
66
|
+
};
|
|
67
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { loadSkillsFromDir, loadSkillsFromPaths, formatSkillsForPrompt, type Skill, type LoadSkillsFromDirOptions, } from "./agent/skills.js";
|
|
2
|
+
export { run, type RunOptions, type RunResult } from "./agent/run.js";
|
|
3
|
+
export { getOpenbotAgentDir, ensureDefaultAgentDir } from "./agent/agent-dir.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getLatestCompactionEntry, } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { convertToLlm } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
/**
|
|
4
|
+
* 从 session entries 中提取可用于存入向量库的摘要文本:
|
|
5
|
+
* 优先使用最新 compaction 的 summary,否则用最近若干条消息拼成短文。
|
|
6
|
+
*/
|
|
7
|
+
export function buildSessionSummaryFromEntries(entries) {
|
|
8
|
+
const compaction = getLatestCompactionEntry(entries);
|
|
9
|
+
if (compaction?.summary) {
|
|
10
|
+
return compaction.summary;
|
|
11
|
+
}
|
|
12
|
+
const messageEntries = entries.filter((e) => e.type === "message");
|
|
13
|
+
if (messageEntries.length === 0)
|
|
14
|
+
return null;
|
|
15
|
+
const recent = messageEntries.slice(-20);
|
|
16
|
+
const messages = recent.map((e) => e.message);
|
|
17
|
+
const parts = [];
|
|
18
|
+
for (const msg of convertToLlm(messages)) {
|
|
19
|
+
const role = msg.role === "user" ? "User" : "Assistant";
|
|
20
|
+
const content = typeof msg.content === "string"
|
|
21
|
+
? msg.content
|
|
22
|
+
: msg.content?.map((c) => c.text ?? "").join(" ") ?? "";
|
|
23
|
+
if (content.trim())
|
|
24
|
+
parts.push(`${role}: ${content.trim()}`);
|
|
25
|
+
}
|
|
26
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
27
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
/**
|
|
3
|
+
* 创建用于在 session_compact 事件时把 compaction summary 写入向量库的 extension factory。
|
|
4
|
+
* 在 AgentSession 发生 compaction(自动或手动)后触发,无需等到关闭会话。
|
|
5
|
+
*/
|
|
6
|
+
export declare function createCompactionMemoryExtensionFactory(sessionId: string): ExtensionFactory;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { addMemory } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* 创建用于在 session_compact 事件时把 compaction summary 写入向量库的 extension factory。
|
|
4
|
+
* 在 AgentSession 发生 compaction(自动或手动)后触发,无需等到关闭会话。
|
|
5
|
+
*/
|
|
6
|
+
export function createCompactionMemoryExtensionFactory(sessionId) {
|
|
7
|
+
return (pi) => {
|
|
8
|
+
pi.on("session_compact", async (event) => {
|
|
9
|
+
const summary = event.compactionEntry?.summary?.trim();
|
|
10
|
+
if (!summary)
|
|
11
|
+
return;
|
|
12
|
+
try {
|
|
13
|
+
await addMemory(summary, {
|
|
14
|
+
infotype: "compaction",
|
|
15
|
+
sessionId,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch (_) {
|
|
19
|
+
// 写入失败不打断会话
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function getEmbedder(): Promise<(input: string, opts?: {
|
|
2
|
+
pooling?: string;
|
|
3
|
+
normalize?: boolean;
|
|
4
|
+
}) => Promise<{
|
|
5
|
+
data: Float32Array;
|
|
6
|
+
}>>;
|
|
7
|
+
/**
|
|
8
|
+
* 对单条文本做 embedding,mean pooling + L2 归一化
|
|
9
|
+
*/
|
|
10
|
+
export declare function embed(text: string): Promise<number[]>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/// <reference path="./vendor.d.ts" />
|
|
2
|
+
import { pipeline } from "@xenova/transformers";
|
|
3
|
+
/** 多语言小模型,中英文表现较好,适合语义检索 */
|
|
4
|
+
const MODEL = "Xenova/paraphrase-multilingual-MiniLM-L12-v2";
|
|
5
|
+
let embedder = null;
|
|
6
|
+
export async function getEmbedder() {
|
|
7
|
+
if (!embedder) {
|
|
8
|
+
embedder = await pipeline("feature-extraction", MODEL, { quantized: true });
|
|
9
|
+
}
|
|
10
|
+
return embedder;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 对单条文本做 embedding,mean pooling + L2 归一化
|
|
14
|
+
*/
|
|
15
|
+
export async function embed(text) {
|
|
16
|
+
const ext = await getEmbedder();
|
|
17
|
+
const out = await ext(text, {
|
|
18
|
+
pooling: "mean",
|
|
19
|
+
normalize: true,
|
|
20
|
+
});
|
|
21
|
+
return Array.from(out.data);
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { InfoType, MemorySearchResult } from "./types.js";
|
|
2
|
+
export { buildSessionSummaryFromEntries } from "./build-summary.js";
|
|
3
|
+
export type { InfoType, MemoryMetadata, MemorySearchResult } from "./types.js";
|
|
4
|
+
export declare function initMemory(): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* 将一条文本写入向量库
|
|
7
|
+
* @param text 内容
|
|
8
|
+
* @param metadata.infotype 经验总结(experience) 或 compaction 摘要(compaction)
|
|
9
|
+
* @param metadata.sessionId 会话 id
|
|
10
|
+
*/
|
|
11
|
+
export declare function addMemory(text: string, metadata: {
|
|
12
|
+
infotype: InfoType;
|
|
13
|
+
sessionId: string;
|
|
14
|
+
}): Promise<string>;
|
|
15
|
+
export interface SearchMemoryOptions {
|
|
16
|
+
topK?: number;
|
|
17
|
+
infotype?: import("./types.js").InfoType;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 按语义搜索记忆,可按 infotype、sessionId 过滤
|
|
22
|
+
*/
|
|
23
|
+
export declare function searchMemory(query: string, topK?: number, options?: SearchMemoryOptions): Promise<MemorySearchResult[]>;
|
|
24
|
+
/** 拉取 3 条经验,格式化为「经验内容」文本(用于拼入用户消息) */
|
|
25
|
+
export declare function getExperienceContextForUserMessage(): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* 拉取 1 条 compaction 摘要,用于拼入 system prompt(新建 AgentSession 时调用,可选 sessionId)
|
|
28
|
+
*/
|
|
29
|
+
export declare function getCompactionContextForSystemPrompt(sessionId?: string): Promise<string>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { embed } from "./embedding.js";
|
|
3
|
+
import { addToStore, queryStore } from "./vector-store.js";
|
|
4
|
+
export { buildSessionSummaryFromEntries } from "./build-summary.js";
|
|
5
|
+
let initialized = false;
|
|
6
|
+
export async function initMemory() {
|
|
7
|
+
if (initialized)
|
|
8
|
+
return;
|
|
9
|
+
const { getChroma } = await import("./vector-store.js");
|
|
10
|
+
await getChroma();
|
|
11
|
+
initialized = true;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 将一条文本写入向量库
|
|
15
|
+
* @param text 内容
|
|
16
|
+
* @param metadata.infotype 经验总结(experience) 或 compaction 摘要(compaction)
|
|
17
|
+
* @param metadata.sessionId 会话 id
|
|
18
|
+
*/
|
|
19
|
+
export async function addMemory(text, metadata) {
|
|
20
|
+
const id = randomUUID();
|
|
21
|
+
const vec = await embed(text);
|
|
22
|
+
const meta = {
|
|
23
|
+
...metadata,
|
|
24
|
+
createdAt: new Date().toISOString(),
|
|
25
|
+
};
|
|
26
|
+
await addToStore(id, vec, text, meta);
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 按语义搜索记忆,可按 infotype、sessionId 过滤
|
|
31
|
+
*/
|
|
32
|
+
export async function searchMemory(query, topK = 10, options) {
|
|
33
|
+
const opts = typeof options === "object" && options != null ? options : {};
|
|
34
|
+
const k = opts.topK ?? topK;
|
|
35
|
+
const filter = opts.infotype != null || opts.sessionId != null
|
|
36
|
+
? { infotype: opts.infotype, sessionId: opts.sessionId }
|
|
37
|
+
: undefined;
|
|
38
|
+
const vec = await embed(query);
|
|
39
|
+
const rows = await queryStore(vec, k, filter);
|
|
40
|
+
return rows.map((r) => ({
|
|
41
|
+
document: r.document,
|
|
42
|
+
metadata: r.metadata,
|
|
43
|
+
distance: r.distance,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
/** 拉取 3 条经验,格式化为「经验内容」文本(用于拼入用户消息) */
|
|
47
|
+
export async function getExperienceContextForUserMessage() {
|
|
48
|
+
const results = await searchMemory("经验 总结 技巧 方法", 3, { infotype: "experience" });
|
|
49
|
+
if (results.length === 0)
|
|
50
|
+
return "";
|
|
51
|
+
const lines = results.map((r, i) => `${i + 1}. ${(r.document || "").trim()}`).filter(Boolean);
|
|
52
|
+
if (lines.length === 0)
|
|
53
|
+
return "";
|
|
54
|
+
return "以下为相关经验:\n\n" + lines.join("\n\n");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 拉取 1 条 compaction 摘要,用于拼入 system prompt(新建 AgentSession 时调用,可选 sessionId)
|
|
58
|
+
*/
|
|
59
|
+
export async function getCompactionContextForSystemPrompt(sessionId) {
|
|
60
|
+
const filter = sessionId != null ? { infotype: "compaction", sessionId } : { infotype: "compaction" };
|
|
61
|
+
const results = await searchMemory("对话 摘要 历史 上下文", 1, { ...filter, topK: 1 });
|
|
62
|
+
const doc = results[0]?.document?.trim();
|
|
63
|
+
if (!doc)
|
|
64
|
+
return "";
|
|
65
|
+
return "## 历史对话摘要\n\n" + doc;
|
|
66
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 长记忆 metadata 中的信息类型
|
|
3
|
+
* - experience: 对话结束时 Agent 调用 save_experience 工具存入的经验总结
|
|
4
|
+
* - compaction: 关闭 session 时从 compaction 取出的会话摘要
|
|
5
|
+
*/
|
|
6
|
+
export type InfoType = "experience" | "compaction";
|
|
7
|
+
export interface MemoryMetadata {
|
|
8
|
+
infotype: InfoType;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface MemorySearchResult {
|
|
13
|
+
document: string;
|
|
14
|
+
metadata: MemoryMetadata;
|
|
15
|
+
distance?: number;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|