batter-pixso-mcp 0.5.0 → 0.6.0
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/dist/index.js +447 -285
- package/dist/plugin/code.js +4 -8
- package/package.json +1 -1
- package/standalone/index.js +38 -38
- package/standalone/plugin/code.js +4 -8
package/dist/index.js
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/plugin/assets.ts
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
|
-
import { join as join2 } from "node:path";
|
|
7
|
-
|
|
8
3
|
// src/config.ts
|
|
9
4
|
import { tmpdir } from "node:os";
|
|
10
5
|
import { join } from "node:path";
|
|
11
|
-
var VERSION = true ? "0.
|
|
6
|
+
var VERSION = true ? "0.6.0" : "0.0.0-dev";
|
|
12
7
|
var HUB_NAME = "batter-pixso-mcp";
|
|
13
8
|
var ASSETS_DIR = join(tmpdir(), "batter-pixso-assets");
|
|
14
9
|
var config = {
|
|
15
10
|
/** 插件通信 HTTP 端口,仅监听 127.0.0.1。与插件 UI 的 DEFAULT_PORT 互为默认对端,改默认需同步 */
|
|
16
11
|
httpPort: Number(process.env.HTTP_PORT ?? 3210),
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
// hub 常驻(不自毁):升级由 proxy 版本比对自动换血,手动停止用 --stop
|
|
13
|
+
/** MCP HTTP 会话空闲 TTL:超时清扫防泄漏(proxy 对失效会话有自动重建兜底),默认 30 分钟 */
|
|
14
|
+
mcpSessionTtlMs: Number(process.env.MCP_SESSION_TTL_MS ?? 30 * 60 * 1e3),
|
|
19
15
|
/** 产物图片保留时长,默认 7 天 */
|
|
20
16
|
assetTtlMs: Number(process.env.ASSET_TTL_DAYS ?? 7) * 24 * 60 * 60 * 1e3
|
|
21
17
|
};
|
|
@@ -36,6 +32,9 @@ function withResolvers() {
|
|
|
36
32
|
}
|
|
37
33
|
|
|
38
34
|
// src/plugin/assets.ts
|
|
35
|
+
import { createHash } from "node:crypto";
|
|
36
|
+
import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
37
|
+
import { join as join2 } from "node:path";
|
|
39
38
|
var EXT_BY_FORMAT = {
|
|
40
39
|
PNG: ".png",
|
|
41
40
|
SVG: ".svg",
|
|
@@ -89,114 +88,138 @@ async function cleanOldAssets(ttlMs) {
|
|
|
89
88
|
return removed;
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
// src/
|
|
93
|
-
import {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return [...this.sessions.values()].filter((s) => s.meta.fileKey === fileKey && this.isOnline(s));
|
|
109
|
-
}
|
|
110
|
-
/** 亲和查询:上一次为该 fileKey 服务的 sessionId */
|
|
111
|
-
affinityIdOf(fileKey) {
|
|
112
|
-
return this.lastUsedByFileKey.get(fileKey);
|
|
113
|
-
}
|
|
114
|
-
/** 亲和钉住:fileKey 寻址探活成功后记录(下次优先) */
|
|
115
|
-
pinAffinity(fileKey, sessionId) {
|
|
116
|
-
this.lastUsedByFileKey.set(fileKey, sessionId);
|
|
91
|
+
// src/process/hub-process.ts
|
|
92
|
+
import { spawn } from "node:child_process";
|
|
93
|
+
import { closeSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
94
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
95
|
+
import { join as join3 } from "node:path";
|
|
96
|
+
import { fileURLToPath } from "node:url";
|
|
97
|
+
async function probeHub() {
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(`${hubBaseUrl()}/health`, { signal: AbortSignal.timeout(1500) });
|
|
100
|
+
if (!res.ok) return { state: "foreign" };
|
|
101
|
+
const body = await res.json();
|
|
102
|
+
if (body.ok === true && body.name === HUB_NAME) return { state: "alive", health: body };
|
|
103
|
+
return { state: "foreign" };
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const hung = err instanceof Error && err.name === "TimeoutError";
|
|
106
|
+
return { state: hung ? "hung" : "down" };
|
|
117
107
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
log(`session evicted: ${id.slice(0, 8)}`);
|
|
125
|
-
}
|
|
108
|
+
}
|
|
109
|
+
var PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
|
|
110
|
+
function hubStateError(state) {
|
|
111
|
+
if (state === "hung") {
|
|
112
|
+
return new Error(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u7684\u670D\u52A1\u65E0\u54CD\u5E94\uFF08\u53EF\u80FD\u6302\u6B7B\uFF09\u4E14\u65E0\u6CD5\u5B9A\u4F4D pid\u3002${PORT_FIX_HINT}`);
|
|
126
113
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
114
|
+
return new Error(`\u7AEF\u53E3 ${config.httpPort} \u88AB\u5176\u4ED6\u7A0B\u5E8F\u5360\u7528\u3002${PORT_FIX_HINT}\uFF0C\u6216\u4FEE\u6539 HTTP_PORT \u6362\u7AEF\u53E3`);
|
|
115
|
+
}
|
|
116
|
+
var HUB_LOG = join3(tmpdir2(), "batter-pixso-mcp-hub.log");
|
|
117
|
+
var PID_FILE = join3(tmpdir2(), "batter-pixso-mcp-hub.pid");
|
|
118
|
+
function writePidFile(pid) {
|
|
119
|
+
try {
|
|
120
|
+
writeFileSync(PID_FILE, String(pid), "utf8");
|
|
121
|
+
process.on("exit", () => {
|
|
122
|
+
try {
|
|
123
|
+
rmSync(PID_FILE);
|
|
124
|
+
} catch {
|
|
133
125
|
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
this.sessions.set(session.id, session);
|
|
137
|
-
this.lastActiveId = session.id;
|
|
138
|
-
log(`session registered: ${session.id} (${meta.fileName ?? "unknown file"})`);
|
|
139
|
-
return this.toInfo(session);
|
|
126
|
+
});
|
|
127
|
+
} catch {
|
|
140
128
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
129
|
+
}
|
|
130
|
+
function readPidFile() {
|
|
131
|
+
try {
|
|
132
|
+
const pid = Number(readFileSync(PID_FILE, "utf8").trim());
|
|
133
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
147
136
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return s;
|
|
137
|
+
}
|
|
138
|
+
function removePidFile() {
|
|
139
|
+
try {
|
|
140
|
+
rmSync(PID_FILE);
|
|
141
|
+
} catch {
|
|
154
142
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
143
|
+
}
|
|
144
|
+
function killHubByPid(pid) {
|
|
145
|
+
try {
|
|
146
|
+
process.kill(pid);
|
|
159
147
|
return true;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
160
150
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
151
|
+
}
|
|
152
|
+
function killHubByPidFile() {
|
|
153
|
+
const pid = readPidFile();
|
|
154
|
+
if (!pid) return false;
|
|
155
|
+
if (!killHubByPid(pid)) return false;
|
|
156
|
+
removePidFile();
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
function spawnHub() {
|
|
160
|
+
const out = openSync(HUB_LOG, "a");
|
|
161
|
+
const entry = process.argv[1] ?? fileURLToPath(import.meta.url);
|
|
162
|
+
const SKIP_FLAGS = /* @__PURE__ */ new Set(["--eval", "-e", "--print", "-p", "--watch", "--watch-preserve-output"]);
|
|
163
|
+
const SKIP_PREFIXES = [...SKIP_FLAGS].map((f) => f + "=");
|
|
164
|
+
const execArgv = process.execArgv.filter(
|
|
165
|
+
(a, i, arr) => !SKIP_FLAGS.has(a) && !SKIP_PREFIXES.some((p) => a.startsWith(p)) && !(i > 0 && SKIP_FLAGS.has(arr[i - 1]))
|
|
166
|
+
);
|
|
167
|
+
const child = spawn(process.execPath, [...execArgv, entry, "--hub"], {
|
|
168
|
+
detached: true,
|
|
169
|
+
stdio: ["ignore", out, out],
|
|
170
|
+
windowsHide: true,
|
|
171
|
+
env: { ...process.env }
|
|
172
|
+
});
|
|
173
|
+
child.on("error", (err) => log("hub spawn \u5931\u8D25:", err.message));
|
|
174
|
+
child.unref();
|
|
175
|
+
closeSync(out);
|
|
176
|
+
log(`hub spawned (pid ${child.pid}), log: ${HUB_LOG}`);
|
|
177
|
+
}
|
|
178
|
+
async function waitForHub(timeoutMs = 15e3) {
|
|
179
|
+
const deadline = Date.now() + timeoutMs;
|
|
180
|
+
for (; ; ) {
|
|
181
|
+
const { state } = await probeHub();
|
|
182
|
+
if (state === "alive") return;
|
|
183
|
+
if (state !== "down") throw hubStateError(state);
|
|
184
|
+
if (Date.now() > deadline) throw new Error(`\u7B49\u5F85 hub \u5C31\u7EEA\u8D85\u65F6\uFF08${timeoutMs / 1e3}s\uFF09`);
|
|
185
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
169
186
|
}
|
|
170
|
-
|
|
171
|
-
|
|
187
|
+
}
|
|
188
|
+
async function waitForHubDown(timeoutMs = 5e3) {
|
|
189
|
+
const deadline = Date.now() + timeoutMs;
|
|
190
|
+
for (; ; ) {
|
|
191
|
+
const { state } = await probeHub();
|
|
192
|
+
if (state === "down") return;
|
|
193
|
+
if (Date.now() > deadline) throw new Error(`\u7B49\u5F85 hub \u9000\u51FA\u8D85\u65F6\uFF08${timeoutMs / 1e3}s\uFF09`);
|
|
194
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
172
195
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
196
|
+
}
|
|
197
|
+
async function stopHub() {
|
|
198
|
+
const { state, health } = await probeHub();
|
|
199
|
+
if (state === "down") {
|
|
200
|
+
log(`hub \u672A\u8FD0\u884C\uFF08\u7AEF\u53E3 ${config.httpPort} \u65E0\u670D\u52A1\uFF09`);
|
|
201
|
+
return;
|
|
176
202
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
203
|
+
if (state === "foreign") {
|
|
204
|
+
log(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u4E0D\u662F batter-pixso-mcp\uFF0C\u672A\u5904\u7406`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (state === "hung") {
|
|
208
|
+
if (killHubByPidFile()) {
|
|
209
|
+
log("hub \u65E0\u54CD\u5E94\uFF08\u50F5\u5C38\u8FDB\u7A0B\uFF09\uFF0C\u5DF2\u6309 pid \u6587\u4EF6\u5F3A\u6740\u3002\u4E0B\u6B21 agent \u8BF7\u6C42\u4F1A\u81EA\u52A8\u62C9\u8D77\u65B0\u7248\u672C");
|
|
210
|
+
return;
|
|
182
211
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
throw new Error("Pixso \u63D2\u4EF6\u672A\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6");
|
|
212
|
+
log(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u7684\u670D\u52A1\u65E0\u54CD\u5E94\u4E14\u65E0\u6CD5\u5B9A\u4F4D pid\u3002${PORT_FIX_HINT}`);
|
|
213
|
+
return;
|
|
186
214
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
selectionCount: s.meta.selection?.length,
|
|
194
|
-
online: this.isOnline(s),
|
|
195
|
-
lastSeen: s.lastSeen
|
|
196
|
-
};
|
|
215
|
+
const pid = health.pid;
|
|
216
|
+
if (killHubByPid(pid)) {
|
|
217
|
+
removePidFile();
|
|
218
|
+
log(`hub \u5DF2\u505C\u6B62\uFF08pid ${pid}\uFF09\u3002\u4E0B\u6B21 agent \u8BF7\u6C42\u4F1A\u81EA\u52A8\u62C9\u8D77\u65B0\u7248\u672C`);
|
|
219
|
+
} else {
|
|
220
|
+
log(`\u505C\u6B62 pid ${pid} \u5931\u8D25\u3002${PORT_FIX_HINT}`);
|
|
197
221
|
}
|
|
198
|
-
}
|
|
199
|
-
var sessions = new SessionRegistry();
|
|
222
|
+
}
|
|
200
223
|
|
|
201
224
|
// src/http.ts
|
|
202
225
|
import { createServer } from "node:http";
|
|
@@ -302,13 +325,13 @@ var INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
|
|
|
302
325
|
|
|
303
326
|
## \u591A\u6587\u4EF6\u573A\u666F
|
|
304
327
|
|
|
305
|
-
\u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u662F\u4E00\u4E2A\u4F1A\u8BDD\u3002\
|
|
306
|
-
\u5148\u7528 \`list_sessions\` \u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF0C\
|
|
307
|
-
\u4F5C\u4E3A\
|
|
328
|
+
\u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u662F\u4E00\u4E2A\u4F1A\u8BDD\u3002\u8282\u70B9\u7C7B\u5DE5\u5177\uFF08\`get_node_dsl\`/\`get_metadata\`/\`get_screenshot\`/\`export_image\`\uFF09
|
|
329
|
+
\u7684 \`guid\` \u5FC5\u4F20\uFF0C\u4E14 \`fileKey\`/\`sessionId\` \u4E8C\u9009\u4E00\u5FC5\u4F20\uFF1A\u5148\u7528 \`list_sessions\` \u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF0C\u628A\u8FD4\u56DE\u7684 \`fileKey\`
|
|
330
|
+
\u4F5C\u4E3A\u8DEF\u7531\u53C2\u6570\u4F20\u5165\uFF08\u8DE8\u91CD\u5F00\u7A33\u5B9A\uFF0C\u4F18\u5148\u4E8E \`sessionId\`\uFF09\uFF1B\u4EC5 \`get_selected\`/\`list_sessions\` \u53EF\u7F3A\u7701\u8DEF\u7531\u53C2\u6570\u505A\u63A2\u67E5\u3002
|
|
308
331
|
|
|
309
|
-
**\u4F1A\u8BDD\u89E3\u6790\u4E0E\u4FE1\u5C01**\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u7684\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, data}\`\u2014\u2014\`sessionId\` \u662F\u672C\u6B21\u5B9E\u9645\u670D\u52A1\u7684\u4F1A\u8BDD
|
|
310
|
-
\uFF08\u6BCF\u6B21\u8C03\u7528\u7ECF ping/pong \u63A2\u6D3B\u91CD\u65B0\u786E\u8BA4\uFF0C\u4E0D\u662F\u53EF\u957F\u671F\u9489\u4F4F\u7684\u56FA\u5B9A\u6807\u8BC6\uFF09\
|
|
311
|
-
\u6BCF\u6B21\u8C03\u7528\u6309 fileKey \u91CD\u65B0\u5BFB\u5740\u2014\u2014\u540C fileKey \u591A\u4F1A\u8BDD\u65F6\u4F18\u5148\u4E0A\u4E00\u6B21\u4F7F\u7528\u7684\uFF08\u4EB2\u548C\uFF09\uFF0C\u6587\u4EF6\u5173\u95ED\u91CD\u5F00\u3001\u63D2\u4EF6\u91CD\u8DD1\u540E
|
|
332
|
+
**\u4F1A\u8BDD\u89E3\u6790\u4E0E\u4FE1\u5C01**\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u7684\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, fileKey, data}\`\u2014\u2014\`sessionId\` \u662F\u672C\u6B21\u5B9E\u9645\u670D\u52A1\u7684\u4F1A\u8BDD
|
|
333
|
+
\uFF08\u6BCF\u6B21\u8C03\u7528\u7ECF ping/pong \u63A2\u6D3B\u91CD\u65B0\u786E\u8BA4\uFF0C\u4E0D\u662F\u53EF\u957F\u671F\u9489\u4F4F\u7684\u56FA\u5B9A\u6807\u8BC6\uFF09\uFF1B\`fileKey\` \u662F\u8DE8\u91CD\u5F00\u7A33\u5B9A\u7684\u53E5\u67C4\uFF0C\u540E\u7EED\u8C03\u7528
|
|
334
|
+
\u7528\u5B83\u505A\u8DEF\u7531\u53C2\u6570\uFF1A\u6BCF\u6B21\u8C03\u7528\u6309 fileKey \u91CD\u65B0\u5BFB\u5740\u2014\u2014\u540C fileKey \u591A\u4F1A\u8BDD\u65F6\u4F18\u5148\u4E0A\u4E00\u6B21\u4F7F\u7528\u7684\uFF08\u4EB2\u548C\uFF09\uFF0C\u6587\u4EF6\u5173\u95ED\u91CD\u5F00\u3001\u63D2\u4EF6\u91CD\u8DD1\u540E
|
|
312
335
|
\u81EA\u52A8\u89E3\u6790\u5230\u5F53\u524D\u5B58\u6D3B\u5B9E\u4F8B\uFF0Cagent \u65E0\u9700\u5173\u5FC3 sessionId \u7684\u5931\u6548\u3002
|
|
313
336
|
|
|
314
337
|
\u8BE6\u7EC6\u5DE5\u4F5C\u6D41\u89C1\u8D44\u6E90 \`batter://guides/design-to-code\`\u3002
|
|
@@ -336,12 +359,12 @@ var DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
|
|
|
336
359
|
|
|
337
360
|
1. **\u786E\u8BA4\u76EE\u6807\u8282\u70B9**
|
|
338
361
|
- \u7528\u6237\u7ED9\u4E86 Pixso \u94FE\u63A5\uFF1A\u6309\u4E0A\u65B9\u786C\u6027\u89C4\u5219\u7B2C 2 \u6761\u63D0\u53D6 guid\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
|
|
339
|
-
- \u7528\u6237\u660E\u786E\u8BF4\u4E86"\u8FD9\u4E2A/\u6211\u9009\u4E2D\u7684"\uFF1A\u8C03 \`get_selected\` \u62FF guid\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
|
|
362
|
+
- \u7528\u6237\u660E\u786E\u8BF4\u4E86"\u8FD9\u4E2A/\u6211\u9009\u4E2D\u7684"\uFF1A\u8C03 \`get_selected\` \u62FF guid \u4E0E fileKey\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
|
|
340
363
|
- \u5176\u4F59\u4F9D\u8D56\u5F53\u524D\u9009\u533A\u7684\u573A\u666F\uFF1A**\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u9009\u533A guid**\u2014\u2014\u8C03 \`get_selected\` \u540E
|
|
341
364
|
\u5FC5\u987B\u5148\u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08"\u5F53\u524D\u9009\u4E2D\u7684\u662F\u300CXX\u300D\uFF0C\u786E\u8BA4\u5904\u7406\u5B83\u5417\uFF1F"\uFF09\uFF0C\u786E\u8BA4\u540E\u518D\u7EE7\u7EED\u3002
|
|
342
|
-
|
|
343
|
-
2. **\u53D6\u7ED3\u6784**\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, data}\` \u4FE1\u5C01\u2014\u2014DSL \u5728 \`data\` \u5B57\u6BB5\u91CC\u3002
|
|
344
|
-
\`get_node_dsl(guid)\` \u62FF\u7CBE\u7B80 DSL\u3002
|
|
365
|
+
\u786E\u8BA4\u540E\u7684\u8C03\u7528\u91CC guid \u7528\u8FD4\u56DE\u7684\u9009\u4E2D\u56FE\u5C42 id\u3001\u8DEF\u7531\u53C2\u6570\u7528\u8FD4\u56DE\u7684 fileKey\uFF08guid \u5FC5\u4F20\u3001fileKey/sessionId \u5FC5\u4F20\u5176\u4E00\uFF09\u3002
|
|
366
|
+
2. **\u53D6\u7ED3\u6784**\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, fileKey, data}\` \u4FE1\u5C01\u2014\u2014DSL \u5728 \`data\` \u5B57\u6BB5\u91CC\u3002
|
|
367
|
+
\`get_node_dsl(guid, fileKey)\` \u62FF\u7CBE\u7B80 DSL\uFF08guid \u5FC5\u4F20\uFF0CfileKey/sessionId \u5FC5\u4F20\u5176\u4E00\uFF09\u3002
|
|
345
368
|
**\u9996\u6B21\u63A5\u89E6\u4E00\u4E2A\u8BBE\u8BA1\u7A3F/\u9875\u9762**\uFF08\u8FD8\u6CA1\u6709\u76EE\u6807\u8282\u70B9\u7684\u4E0A\u4E0B\u6587\u65F6\uFF09\uFF1A\u5148 \`get_metadata(guid)\`
|
|
346
369
|
\u62FF\u5168\u6811\u9AA8\u67B6\uFF08\u4EC5 id/name/type/kids\uFF0C\u65E0\u6837\u5F0F/\u5750\u6807/\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09+ \`get_screenshot(guid)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF0C
|
|
347
370
|
\u636E\u6B64\u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF0C\u518D\u5BF9\u76EE\u6807\u6B63\u5E38\u53D6 DSL\u3002\u9AA8\u67B6\u4E2D ICON/IMAGE \u53F6\u5B50\u65E0 \`img\`\u2014\u2014\u9700\u8D44\u6E90\u65F6\u6309\u5176 \`id\` \u4E0B\u94BB\u5168\u91CF DSL\u3002
|
|
@@ -407,7 +430,7 @@ DSL \u662F\u5355\u884C\u7D27\u51D1 JSON\uFF1A\u77ED key\u3001\u4F4D\u7F6E\u6570\
|
|
|
407
430
|
- **\u7F3A guid**\uFF1A\u5411\u7528\u6237\u8981\u5B8C\u6574\u7684 Pixso \u94FE\u63A5\uFF08\u542B item-id \u67E5\u8BE2\u53C2\u6570\uFF09\uFF0C\u4E0D\u8981\u731C\u6D4B\u8282\u70B9 id\u3002
|
|
408
431
|
- **\u547D\u4EE4\u8D85\u65F6/\u7ED3\u679C\u8FC7\u5927**\uFF1A\u5BF9\u76EE\u6807\u8282\u70B9\u7684\u5B50\u8282\u70B9\u7528 \`get_node_dsl\` + \u8F83\u4F4E \`maxDepth\` \u5206\u6BB5\u53D6\uFF0C\u4E0D\u8981\u6574\u6811\u4E00\u6B21\u62C9\u3002
|
|
409
432
|
- **\u7ED3\u679C\u7591\u4F3C\u8FC7\u671F**\uFF08\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\uFF09\uFF1A\u7ED9\u5BFC\u51FA\u7C7B\u5DE5\u5177\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
|
|
410
|
-
- **\u63D2\u4EF6\u65E0\u54CD\u5E94**\uFF08ping \u63A2\u6D4B\u8D85\u65F6\uFF09\uFF1A\u63D2\u4EF6\u672A\u8FD0\u884C\u6216\u6B63\u5728\u6267\u884C\u957F\u547D\u4EE4\u2014\u2014\u7A0D\u540E\u91CD\u8BD5\uFF1B\u6587\u4EF6\u91CD\u5F00\u573A\u666F
|
|
433
|
+
- **\u63D2\u4EF6\u65E0\u54CD\u5E94**\uFF08ping \u63A2\u6D4B\u8D85\u65F6\uFF09\uFF1A\u63D2\u4EF6\u672A\u8FD0\u884C\u6216\u6B63\u5728\u6267\u884C\u957F\u547D\u4EE4\u2014\u2014\u7A0D\u540E\u91CD\u8BD5\uFF1B\u6587\u4EF6\u91CD\u5F00\u573A\u666F\u7528 fileKey \u5BFB\u5740\u4F1A\u81EA\u52A8\u89E3\u6790\u5230\u65B0\u5B9E\u4F8B\u3002
|
|
411
434
|
- **\u7981\u6B62\u4EC5\u51ED\u622A\u56FE\u786C\u7F16\u7801**\uFF1A\u622A\u56FE\u662F\u6821\u9A8C\u4E0E\u610F\u56FE\u53C2\u7167\uFF0C\u4E0D\u662F\u7ED3\u6784\u6765\u6E90\u2014\u2014\u7ED3\u6784\u4E00\u5F8B\u8D70 DSL\u3002
|
|
412
435
|
|
|
413
436
|
## \u7EC4\u4EF6\u5173\u8054\uFF08comp \u5B57\u6BB5\uFF09
|
|
@@ -435,7 +458,7 @@ INSTANCE / COMPONENT / COMPONENT_SET \u8282\u70B9\u5E26 \`comp\` \u5B57\u6BB5\uF
|
|
|
435
458
|
- DSL \u662F**\u6709\u635F\u7CBE\u7B80**\uFF1A\u7701\u7565\u4E86\u5E03\u5C14\u8FD0\u7B97\u8DEF\u5F84\u3001\u8499\u7248\u7B49\u7EAF\u8BBE\u8BA1\u7EC6\u8282\uFF0C\u89C6\u89C9\u590D\u6742\u8282\u70B9\u4EE5\u622A\u56FE\u4E3A\u51C6\u3002
|
|
436
459
|
- \u5BFC\u51FA\u7C7B\u5DE5\u5177\uFF08\`get_node_dsl\`/\`get_screenshot\`/\`export_image\`\uFF09\u5E26 15 \u5206\u949F\u7ED3\u679C\u7F13\u5B58\uFF08\u63D2\u4EF6\u4FA7 TTL\uFF09\uFF1B
|
|
437
460
|
\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
|
|
438
|
-
- \u591A\u6587\u4EF6\u573A\u666F\
|
|
461
|
+
- \u591A\u6587\u4EF6\u573A\u666F\u8282\u70B9\u7C7B\u5DE5\u5177\u7528 \`fileKey\`/\`sessionId\` \u53C2\u6570\u8DEF\u7531\uFF08\u4E8C\u9009\u4E00\u5FC5\u4F20\uFF09\uFF0C\u89C1 \`batter://guides/intro\`\u3002
|
|
439
462
|
`;
|
|
440
463
|
var GUIDES = [
|
|
441
464
|
{
|
|
@@ -480,7 +503,7 @@ var DESIGN_TO_CODE2 = {
|
|
|
480
503
|
`## \u6267\u884C\u6D41\u7A0B`,
|
|
481
504
|
``,
|
|
482
505
|
`1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
|
|
483
|
-
`2. \u53D6\u6570\u636E\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, data}\` \u4FE1\u5C01\uFF0C\u53D6 \`data\` \u5B57\u6BB5\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 \`get_metadata(guid)\` \u62FF\u5168\u6811\u9AA8\u67B6\u5B9A\u4F4D\u76EE\u6807 + \`get_screenshot(guid)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF1B\u968F\u540E \`get_node_dsl(guid)\` \u62FF\u5168\u91CF DSL\uFF08\u7ED3\u6784\u5F88\u5927\u65F6\u7528 \`maxDepth\` \u5206\u6BB5\u4E0B\u63A2\u2014\u2014\u622A\u65AD\u5904\u8282\u70B9\u5E26 \`more\` \u6807\u8BB0\u4E0E \`kidsSummary\` \u6D45\u9884\u89C8\uFF0C\u62FF\u5176 \`id\` \u56DE\u4F20 \`guid\` \u4E0B\u94BB\uFF09\u3002DSL \u4E2D\u5DF2\u5E26 \`img\` \u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u518D\u8C03 \`export_image\`\u3002`,
|
|
506
|
+
`2. \u53D6\u6570\u636E\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, fileKey, data}\` \u4FE1\u5C01\uFF0C\u53D6 \`data\` \u5B57\u6BB5\u3002\u8282\u70B9\u7C7B\u5DE5\u5177\u7684 guid \u5FC5\u4F20\uFF0CfileKey/sessionId \u5FC5\u4F20\u5176\u4E00\uFF08\u4F18\u5148 fileKey\uFF1B\u9009\u533A\u573A\u666F\u7528 \`get_selected\` \u8FD4\u56DE\u7684 fileKey\uFF09\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 \`get_metadata(guid, fileKey)\` \u62FF\u5168\u6811\u9AA8\u67B6\u5B9A\u4F4D\u76EE\u6807 + \`get_screenshot(guid, fileKey)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF1B\u968F\u540E \`get_node_dsl(guid, fileKey)\` \u62FF\u5168\u91CF DSL\uFF08\u7ED3\u6784\u5F88\u5927\u65F6\u7528 \`maxDepth\` \u5206\u6BB5\u4E0B\u63A2\u2014\u2014\u622A\u65AD\u5904\u8282\u70B9\u5E26 \`more\` \u6807\u8BB0\u4E0E \`kidsSummary\` \u6D45\u9884\u89C8\uFF0C\u62FF\u5176 \`id\` \u56DE\u4F20 \`guid\` \u4E0B\u94BB\uFF09\u3002DSL \u4E2D\u5DF2\u5E26 \`img\` \u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u518D\u8C03 \`export_image\`\u3002`,
|
|
484
507
|
`3. \u751F\u6210\u4EE3\u7801\uFF1A\u6309\u6307\u5357\u4E2D\u7684 DSL \u5B57\u6BB5\u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002`,
|
|
485
508
|
`4. \u81EA\u68C0\uFF1A\u518D\u6B21\u8C03 \`get_screenshot(guid)\`\uFF0C\u4E0E\u4EE3\u7801\u6E32\u67D3\u7ED3\u679C\u5BF9\u6BD4\uFF0C\u6709\u504F\u5DEE\u5C31\u4FEE\u6B63\u540E\u518D\u6BD4\u4E00\u8F6E\uFF08\u5BFC\u51FA\u7C7B\u5DE5\u5177\u5E26 15 \u5206\u949F\u7F13\u5B58\uFF1B\u7ED3\u679C\u7591\u4F3C\u8FC7\u671F\u65F6\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\uFF09\u3002`,
|
|
486
509
|
``,
|
|
@@ -568,15 +591,131 @@ var CommandBus = class {
|
|
|
568
591
|
};
|
|
569
592
|
var commandBus = new CommandBus();
|
|
570
593
|
|
|
594
|
+
// src/plugin/sessions.ts
|
|
595
|
+
import { randomUUID } from "node:crypto";
|
|
596
|
+
var ONLINE_WINDOW_MS = 45e3;
|
|
597
|
+
function unknownRouteError(route) {
|
|
598
|
+
if (route.fileKey) {
|
|
599
|
+
return new Error(`\u627E\u4E0D\u5230 fileKey \u4E3A ${route.fileKey} \u7684\u5B58\u6D3B\u63D2\u4EF6\u4F1A\u8BDD\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u8FDE\u63A5\u7684\u4F1A\u8BDD`);
|
|
600
|
+
}
|
|
601
|
+
return new Error(`\u627E\u4E0D\u5230 sessionId \u4E3A ${route.sessionId} \u7684\u5B58\u6D3B\u63D2\u4EF6\u4F1A\u8BDD\uFF08\u53EF\u80FD\u5DF2\u88AB\u5254\u9664\uFF09\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u8FDE\u63A5\u7684\u4F1A\u8BDD`);
|
|
602
|
+
}
|
|
603
|
+
var SessionRegistry = class {
|
|
604
|
+
sessions = /* @__PURE__ */ new Map();
|
|
605
|
+
lastActiveId = null;
|
|
606
|
+
/** 会话亲和:fileKey → 上一次路由使用的 sessionId(仍在线时优先,避免同文件多实例间漂移) */
|
|
607
|
+
lastUsedByFileKey = /* @__PURE__ */ new Map();
|
|
608
|
+
isOnline(s) {
|
|
609
|
+
return s.connected || Date.now() - s.lastSeen < ONLINE_WINDOW_MS;
|
|
610
|
+
}
|
|
611
|
+
/** fileKey 寻址候选:该文件的在线会话(谁先 pong 由 ping 竞速决定,此处不排序) */
|
|
612
|
+
candidatesForFileKey(fileKey) {
|
|
613
|
+
return [...this.sessions.values()].filter((s) => s.meta.fileKey === fileKey && this.isOnline(s));
|
|
614
|
+
}
|
|
615
|
+
/** 亲和查询:上一次为该 fileKey 服务的 sessionId */
|
|
616
|
+
affinityIdOf(fileKey) {
|
|
617
|
+
return this.lastUsedByFileKey.get(fileKey);
|
|
618
|
+
}
|
|
619
|
+
/** 亲和钉住:fileKey 寻址探活成功后记录(下次优先) */
|
|
620
|
+
pinAffinity(fileKey, sessionId) {
|
|
621
|
+
this.lastUsedByFileKey.set(fileKey, sessionId);
|
|
622
|
+
}
|
|
623
|
+
/** 剔除会话(幽灵剔除/ping 判死共用):清理亲和引用;存活但繁忙被误剔的插件会在 SSE 断开或上报 404 后自重注册;
|
|
624
|
+
* 失效 sessionId 的 failover 不靠墓碑——工具结果信封带 fileKey,agent 手里的稳定句柄一直是 fileKey */
|
|
625
|
+
evict(id) {
|
|
626
|
+
if (this.sessions.delete(id)) {
|
|
627
|
+
for (const [k, v] of this.lastUsedByFileKey) {
|
|
628
|
+
if (v === id) this.lastUsedByFileKey.delete(k);
|
|
629
|
+
}
|
|
630
|
+
log(`session evicted: ${id.slice(0, 8)}`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
register(meta = {}) {
|
|
634
|
+
if (meta.fileKey) {
|
|
635
|
+
for (const s of [...this.sessions.values()]) {
|
|
636
|
+
if (s.meta.fileKey === meta.fileKey && !this.isOnline(s)) {
|
|
637
|
+
this.evict(s.id);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const session = { id: randomUUID(), meta, lastSeen: Date.now(), connected: false };
|
|
642
|
+
this.sessions.set(session.id, session);
|
|
643
|
+
this.lastActiveId = session.id;
|
|
644
|
+
log(`session registered: ${session.id} (${meta.fileName ?? "unknown file"})`);
|
|
645
|
+
return this.toInfo(session);
|
|
646
|
+
}
|
|
647
|
+
/** 心跳/连接状态用:只刷 lastSeen,不影响 lastActiveId("最近活跃"= 用户行为,心跳不算) */
|
|
648
|
+
touchSeen(id) {
|
|
649
|
+
const s = this.sessions.get(id);
|
|
650
|
+
if (!s) return null;
|
|
651
|
+
s.lastSeen = Date.now();
|
|
652
|
+
return s;
|
|
653
|
+
}
|
|
654
|
+
/** 用户行为(选区上报/命令服务/注册):刷 lastSeen 并标记最近活跃 */
|
|
655
|
+
touch(id) {
|
|
656
|
+
const s = this.touchSeen(id);
|
|
657
|
+
if (!s) return null;
|
|
658
|
+
this.lastActiveId = id;
|
|
659
|
+
return s;
|
|
660
|
+
}
|
|
661
|
+
setConnected(id, connected) {
|
|
662
|
+
const s = this.touchSeen(id);
|
|
663
|
+
if (!s) return false;
|
|
664
|
+
s.connected = connected;
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
updateContext(id, meta) {
|
|
668
|
+
const s = this.touch(id);
|
|
669
|
+
if (!s) return false;
|
|
670
|
+
s.meta = { ...s.meta, ...meta };
|
|
671
|
+
log(
|
|
672
|
+
`context updated [${id.slice(0, 8)}]: ${s.meta.fileName ?? "?"} / ${s.meta.pageName ?? "?"} / ${s.meta.selection?.length ?? 0} selected`
|
|
673
|
+
);
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
list() {
|
|
677
|
+
return [...this.sessions.values()].map((s) => this.toInfo(s));
|
|
678
|
+
}
|
|
679
|
+
/** 纯查找(不判活):工具层寻址用——离线会话也可取到 fileKey 用于候选扩展与故障转移 */
|
|
680
|
+
get(id) {
|
|
681
|
+
return this.sessions.get(id);
|
|
682
|
+
}
|
|
683
|
+
/** 缺省路由:最近活跃在线会话 > 任意在线会话(显式 fileKey/sessionId 由工具层 get/candidatesForFileKey + ping 完成) */
|
|
684
|
+
resolve() {
|
|
685
|
+
if (this.lastActiveId) {
|
|
686
|
+
const s = this.sessions.get(this.lastActiveId);
|
|
687
|
+
if (s && this.isOnline(s)) return s;
|
|
688
|
+
}
|
|
689
|
+
const fallback = [...this.sessions.values()].find((s) => this.isOnline(s));
|
|
690
|
+
if (fallback) return fallback;
|
|
691
|
+
throw new Error("Pixso \u63D2\u4EF6\u672A\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6");
|
|
692
|
+
}
|
|
693
|
+
toInfo(s) {
|
|
694
|
+
return {
|
|
695
|
+
sessionId: s.id,
|
|
696
|
+
fileKey: s.meta.fileKey,
|
|
697
|
+
fileName: s.meta.fileName,
|
|
698
|
+
pageName: s.meta.pageName,
|
|
699
|
+
selectionCount: s.meta.selection?.length,
|
|
700
|
+
online: this.isOnline(s),
|
|
701
|
+
lastSeen: s.lastSeen
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
var sessions = new SessionRegistry();
|
|
706
|
+
|
|
571
707
|
// src/agent/tools/shared.ts
|
|
572
708
|
var PING_TIMEOUT_MS = 800;
|
|
573
709
|
async function run(type, args, signal) {
|
|
574
|
-
const {
|
|
710
|
+
const { fileKey, sessionId, ...rest } = args;
|
|
575
711
|
try {
|
|
576
|
-
const session = await resolveAndPing(
|
|
712
|
+
const session = await resolveAndPing(
|
|
713
|
+
{ fileKey, sessionId },
|
|
714
|
+
signal
|
|
715
|
+
);
|
|
577
716
|
const data = await commandBus.execute(type, rest, { sessionId: session.id, signal });
|
|
578
717
|
sessions.touch(session.id);
|
|
579
|
-
return jsonResult({ sessionId: session.id, data });
|
|
718
|
+
return jsonResult({ sessionId: session.id, fileKey: session.meta.fileKey, data });
|
|
580
719
|
} catch (err) {
|
|
581
720
|
return errorResult(err);
|
|
582
721
|
}
|
|
@@ -605,29 +744,36 @@ async function pingCandidates(candidates, affinityId, signal) {
|
|
|
605
744
|
return null;
|
|
606
745
|
}
|
|
607
746
|
}
|
|
608
|
-
async function resolveAndPing(
|
|
609
|
-
const s =
|
|
610
|
-
|
|
747
|
+
async function resolveAndPing(route, signal) {
|
|
748
|
+
const s = route.sessionId ? sessions.get(route.sessionId) : route.fileKey ? void 0 : sessions.resolve();
|
|
749
|
+
if (route.sessionId && !s) throw unknownRouteError(route);
|
|
750
|
+
const fileKey = s ? s.meta.fileKey : route.fileKey;
|
|
611
751
|
const candidates = s && !fileKey ? [s] : sessions.candidatesForFileKey(fileKey);
|
|
612
752
|
if (candidates.length === 0) {
|
|
613
753
|
if (s) throw new Error(`\u76EE\u6807\u63D2\u4EF6\u5DF2\u79BB\u7EBF\uFF1A${s.meta.fileName ?? s.id}\uFF08\u91CD\u65B0\u6253\u5F00\u6587\u4EF6\u5E76\u8FD0\u884C\u63D2\u4EF6\u540E\u91CD\u8BD5\uFF09`);
|
|
614
|
-
throw
|
|
754
|
+
throw unknownRouteError(route);
|
|
615
755
|
}
|
|
616
756
|
const winner = await pingCandidates(candidates, fileKey ? sessions.affinityIdOf(fileKey) : void 0, signal);
|
|
617
757
|
if (!winner) {
|
|
618
758
|
for (const c of candidates) sessions.evict(c.id);
|
|
619
759
|
throw new Error(
|
|
620
|
-
`\u63D2\u4EF6\u65E0\u54CD\u5E94\uFF08${PING_TIMEOUT_MS}ms \u672A\u56DE pong\uFF09\uFF1A${fileKey ??
|
|
760
|
+
`\u63D2\u4EF6\u65E0\u54CD\u5E94\uFF08${PING_TIMEOUT_MS}ms \u672A\u56DE pong\uFF09\uFF1A${fileKey ?? route.sessionId}\u2014\u2014\u63D2\u4EF6\u672A\u8FD0\u884C\uFF0C\u6216\u6B63\u5728\u6267\u884C\u957F\u547D\u4EE4\uFF08\u7A0D\u540E\u91CD\u8BD5\uFF09`
|
|
621
761
|
);
|
|
622
762
|
}
|
|
623
763
|
if (fileKey) sessions.pinAffinity(fileKey, winner.id);
|
|
624
764
|
return winner;
|
|
625
765
|
}
|
|
626
|
-
var ENVELOPE_NOTE = "\u8FD4\u56DE\u5C01\u88C5\u4E3A {sessionId, data}\uFF08\u9519\u8BEF\u4E3A isError \u6587\u672C\uFF0C\u4E0D\u542B\u4FE1\u5C01\uFF09\uFF1AsessionId \u662F ping/pong \u786E\u8BA4\u7684\u5B9E\u9645\u670D\u52A1\u4F1A\u8BDD\
|
|
766
|
+
var ENVELOPE_NOTE = "\u8FD4\u56DE\u5C01\u88C5\u4E3A {sessionId, fileKey, data}\uFF08\u9519\u8BEF\u4E3A isError \u6587\u672C\uFF0C\u4E0D\u542B\u4FE1\u5C01\uFF09\uFF1AsessionId \u662F ping/pong \u786E\u8BA4\u7684\u5B9E\u9645\u670D\u52A1\u4F1A\u8BDD\uFF0CfileKey \u662F\u8DE8\u91CD\u5F00\u7A33\u5B9A\u7684\u53E5\u67C4\u2014\u2014\u540E\u7EED\u8C03\u7528\u7528 fileKey \u5BFB\u5740\u3002";
|
|
627
767
|
var GUIDES_PTR = "\u5B8C\u6574\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D/\u7981\u6B62\u622A\u56FE\u786C\u7F16\u7801\uFF09\u89C1\u8D44\u6E90 batter://guides/design-to-code\u3002";
|
|
628
|
-
var
|
|
768
|
+
var FILE_KEY_PROP = {
|
|
629
769
|
type: "string",
|
|
630
|
-
|
|
770
|
+
minLength: 1,
|
|
771
|
+
description: "\u76EE\u6807\u8BBE\u8BA1\u6587\u4EF6\u7684 fileKey\uFF08\u8DE8\u91CD\u5F00\u7A33\u5B9A\uFF0C\u4F18\u5148\uFF09\u3002\u4E0E sessionId \u4E8C\u9009\u4E00\uFF1B\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u3002"
|
|
772
|
+
};
|
|
773
|
+
var SESSION_ID_PROP = {
|
|
774
|
+
type: "string",
|
|
775
|
+
minLength: 1,
|
|
776
|
+
description: "\u76EE\u6807\u63D2\u4EF6\u4F1A\u8BDD\u7684 sessionId\uFF08ping/pong \u786E\u8BA4\u7684\u5B58\u6D3B\u5B9E\u4F8B\uFF09\u3002\u4E0E fileKey \u4E8C\u9009\u4E00\u3002"
|
|
631
777
|
};
|
|
632
778
|
var CACHE_PROP = {
|
|
633
779
|
type: "string",
|
|
@@ -636,7 +782,8 @@ var CACHE_PROP = {
|
|
|
636
782
|
};
|
|
637
783
|
var GUID_PROP = {
|
|
638
784
|
type: "string",
|
|
639
|
-
|
|
785
|
+
minLength: 1,
|
|
786
|
+
description: '\u8282\u70B9 GUID\uFF08\u5982 "297:133032"\uFF09\uFF0C\u5FC5\u4F20\u3002\u94FE\u63A5\u8F93\u5165\uFF1A\u4ECE item-id \u63D0\u53D6\u5E76 URL \u89E3\u7801\u540E\u4F20\u5165\uFF1B\u8981\u64CD\u4F5C\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\u65F6\uFF0C\u5148\u7528 get_selected \u53D6\u5176 guid \u518D\u4F20\u5165\u3002'
|
|
640
787
|
};
|
|
641
788
|
function jsonResult(data) {
|
|
642
789
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
@@ -651,40 +798,42 @@ function errorResult(err) {
|
|
|
651
798
|
// src/agent/tools/export_image.ts
|
|
652
799
|
var exportImage = {
|
|
653
800
|
description: '\u3010\u7528\u9014\u3011\u5BFC\u51FA\u8282\u70B9\u7684\u56FE\u7247\u7D20\u6750\uFF08\u5207\u56FE\uFF09\uFF0C\u4F9B\u4EA4\u4ED8\u4EE3\u7801\u4F7F\u7528\u3002\u5355\u8282\u70B9\u4F20 guid\uFF1B\u591A\u8282\u70B9\u4F20 guids \u6570\u7EC4\u6279\u91CF\u3002DSL \u4E2D\u5DF2\u5E26 img \u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5728\u53D6 DSL \u65F6\u5DF2\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E25\u7981\u5BF9\u672C\u7C7B\u8282\u70B9\u91CD\u590D\u8C03\u7528\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807/\u5BFC\u51FA\u56FE\u7247\u7D20\u6750/\u628A\u67D0\u4E2A\u8282\u70B9\u5B58\u6210\u56FE\u7247"\u3002\u8981\u89C6\u89C9\u53C2\u7167\u7528 get_screenshot\u3002\u3010\u8FD4\u56DE\u3011\u5355\u8282\u70B9 {width, height, exports: [{img, format, scale?, suffix?}]}\uFF1B\u6279\u91CF {items: [{guid, width, height, exports}]}\u3002\u8282\u70B9\u5E26\u8BBE\u8BA1\u5E08\u5BFC\u51FA\u8BBE\u7F6E\u65F6\u6309\u5176\u8BBE\u7F6E\u9010\u4EFD\u5BFC\u51FA\uFF08format/scale \u53C2\u6570\u5931\u6548\uFF09\uFF1B\u65E0\u6807\u8BB0\u6309\u53C2\u6570\u5BFC\u51FA\u5355\u4EFD\uFF08\u9ED8\u8BA4 PNG\u30012x\uFF09\u3002suffix\uFF08\u5982 "@2x"\uFF09\u590D\u5236\u8FDB\u9879\u76EE\u65F6\u4FDD\u7559\u547D\u540D\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
801
|
+
// 两组互斥各取其一:guid|guids(单节点|批量)× fileKey|sessionId(路由)
|
|
802
|
+
requires: [
|
|
803
|
+
["guid", "guids"],
|
|
804
|
+
["fileKey", "sessionId"]
|
|
805
|
+
],
|
|
806
|
+
properties: {
|
|
807
|
+
guid: GUID_PROP,
|
|
808
|
+
guids: {
|
|
809
|
+
type: "array",
|
|
810
|
+
items: { type: "string" },
|
|
811
|
+
description: "\u6279\u91CF\u5BFC\u51FA\uFF1A\u8282\u70B9 GUID \u6570\u7EC4\uFF08\u4E0E guid \u4E8C\u9009\u4E00\uFF09\u3002\u591A\u8282\u70B9\u573A\u666F\u4F18\u5148\u7528\u6279\u91CF\uFF0C\u907F\u514D\u9010\u4E2A\u8C03\u7528\u3002"
|
|
812
|
+
},
|
|
813
|
+
format: {
|
|
814
|
+
type: "string",
|
|
815
|
+
enum: ["PNG", "SVG", "JPG"],
|
|
816
|
+
description: "\u65E0\u5BFC\u51FA\u6807\u8BB0\u65F6\u7684\u515C\u5E95\u683C\u5F0F\uFF0C\u9ED8\u8BA4 PNG\u3002\u8282\u70B9\u5E26\u5BFC\u51FA\u6807\u8BB0\u65F6\u6B64\u53C2\u6570\u65E0\u6548\u3002"
|
|
817
|
+
},
|
|
818
|
+
scale: {
|
|
819
|
+
type: "number",
|
|
820
|
+
description: "\u65E0\u5BFC\u51FA\u6807\u8BB0\u65F6\u7684\u515C\u5E95\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002\u8282\u70B9\u5E26\u5BFC\u51FA\u6807\u8BB0\u65F6\u6B64\u53C2\u6570\u65E0\u6548\u3002"
|
|
821
|
+
},
|
|
822
|
+
cache: CACHE_PROP,
|
|
823
|
+
fileKey: FILE_KEY_PROP,
|
|
824
|
+
sessionId: SESSION_ID_PROP
|
|
675
825
|
}
|
|
676
826
|
};
|
|
677
827
|
|
|
678
828
|
// src/agent/tools/get_metadata.ts
|
|
679
829
|
var getMetadata = {
|
|
680
830
|
description: '\u3010\u7528\u9014\u3011\u5168\u6811\u7ED3\u6784\u9AA8\u67B6\uFF08\u4EC5 id/name/type/kids\uFF0C\u65E0\u6837\u5F0F/\u5750\u6807/\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\u65F6\uFF1A\u672C\u5DE5\u5177 + get_screenshot \u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF0C\u518D\u5BF9\u76EE\u6807\u8C03 get_node_dsl \u53D6\u5168\u91CF\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1B"\u9875\u9762\u91CC\u6709\u6CA1\u6709 XX"\uFF1B\u6309\u540D\u5B57\u627E\u8282\u70B9\uFF1B\u53EA\u8981\u7ED3\u6784\u6982\u89C8\u4E0D\u8981\u6837\u5F0F\u3002\u3010\u8FD4\u56DE\u3011\u7D27\u51D1 JSON\uFF1A{id, name?, type, kids: \u9012\u5F52}\uFF1BICON/IMAGE \u53F6\u5B50\u65E0 img\uFF08\u9700\u8D44\u6E90\u65F6\u6309 id \u8C03 get_node_dsl\uFF09\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
831
|
+
requires: ["guid", ["fileKey", "sessionId"]],
|
|
832
|
+
properties: {
|
|
833
|
+
guid: GUID_PROP,
|
|
834
|
+
cache: CACHE_PROP,
|
|
835
|
+
fileKey: FILE_KEY_PROP,
|
|
836
|
+
sessionId: SESSION_ID_PROP
|
|
688
837
|
}
|
|
689
838
|
};
|
|
690
839
|
|
|
@@ -693,52 +842,50 @@ var URL_RULE = '1. \u94FE\u63A5\u8F93\u5165\uFF1A\u5FC5\u987B\u4ECE item-id \u67
|
|
|
693
842
|
var DSL_LEGEND = '\u5B57\u6BB5\u56FE\u4F8B\uFF1Aid=\u8282\u70B9 guid\uFF08\u53EF\u56DE\u4F20 guid \u4E0B\u94BB\uFF09\uFF1Bname=\u56FE\u5C42\u540D\uFF08\u81EA\u52A8\u547D\u540D\u5DF2\u7701\u7565\uFF09\uFF1Btype=\u8282\u70B9\u7C7B\u578B\uFF08ICON/IMAGE \u4E3A\u584C\u7F29\u53F6\u5B50\uFF09\uFF1Brect=[x,y,w,h] \u7EDD\u5BF9\u5B9A\u4F4D\uFF1Blayout={dir:row|column, gap, pad:[\u4E0A,\u53F3,\u4E0B,\u5DE6], align:[\u4E3B\u8F74,\u4EA4\u53C9\u8F74], sizing:[hug|fixed|fill]\xD72}\uFF1Bconstraints={h,v}\uFF08\u9ED8\u8BA4 left/top \u7701\u7565\uFF09\uFF1Btext={content, size, font, weight, lh, align, color, deco, ls, link, segs=\u6DF7\u6392\u5206\u6BB5\uFF08\u4EC5\u5DEE\u5F02\u5B57\u6BB5\uFF0C\u987A\u5E8F\u5373\u5B57\u7B26\u504F\u79FB\uFF09}\uFF1Bimg=\u8D44\u6E90\u6587\u4EF6\u540D\uFF1Bkids=\u5B50\u8282\u70B9\u9012\u5F52\uFF1Bpalette=\u8C03\u8272\u677F\uFF08\u4EC5\u6839\u8282\u70B9\uFF09\uFF1A\u22652 \u6B21\u7684\u5355\u8272\u63D0\u53D6\u4E3A\u6570\u7EC4\uFF0Cfills/text.color/stroke \u4E2D "p0""p1" \u4E3A\u4E0B\u6807\u5F15\u7528\uFF08\u590D\u5408 CSS \u4E32\u5185\u989C\u8272\u4E0D\u67E5\u8868\uFF09\uFF1Bnotes=\u8BBE\u8BA1\u6807\u6CE8\u6587\u672C\uFF08\u4EC5\u5B58\u5728\u65F6\uFF09\uFF1Bcomp=\u7EC4\u4EF6\u5173\u8054\uFF08INSTANCE/COMPONENT/COMPONENT_SET\uFF09\uFF1Akey=\u8DE8\u6587\u4EF6\u7A33\u5B9A\u6807\u8BC6\uFF0Cname=\u7EC4\u4EF6\u540D\uFF08\u6620\u5C04\u6743\u91CD\u6700\u9AD8\u7684\u4F9D\u636E\u2014\u2014\u5728\u9879\u76EE\u7EC4\u4EF6\u5E93\u627E\u5BF9\u5E94\uFF0C\u4E0D\u9010\u5B57\u76F4\u7528\uFF09\uFF0Cprops=variant \u5C5E\u6027\uFF0Cset=variant \u7EC4\uFF1Bdepth=\u6574\u6811\u5B9E\u9645\u6DF1\u5EA6\uFF08\u4EC5\u6839\u8282\u70B9\uFF0C\u603B\u662F\u8F93\u51FA\uFF09\uFF1Bmore=\u622A\u65AD\u5904\u5269\u4F59\u5C42\u7EA7\u6570\uFF08\u4EC5 maxDepth \u622A\u65AD\u65F6\uFF0C\u62FF id \u56DE\u4F20 guid \u4E0B\u94BB\uFF09\uFF1BkidsSummary=\u622A\u65AD\u5904\u6216\u584C\u7F29\u53F6\u5B50\uFF08img \u8282\u70B9\uFF09\u7684\u76F4\u63A5\u5B50\u8282\u70B9\u6D45\u9884\u89C8 [{id, name?, type}]\uFF08\u8F85\u52A9\u9009\u62E9\u4E0B\u94BB\u5206\u652F\uFF09\u3002\u7701\u7565\u89C4\u5219\uFF1A\u9ED8\u8BA4\u503C\u4E0D\u8F93\u51FA\uFF08text.align=left\u3001lh=normal\u3001weight=Regular\u3001gap=0\u3001pad \u5168 0\uFF09\u3002';
|
|
694
843
|
var getNodeDsl = {
|
|
695
844
|
description: '\u3010\u7528\u9014\u3011\u751F\u6210\u8282\u70B9\u7684\u5F00\u53D1\u5411\u7D27\u51D1 DSL\uFF08\u7ED3\u6784+\u6837\u5F0F+\u8D44\u6E90\u6587\u4EF6\u540D\uFF09\uFF0C\u8BBE\u8BA1\u8F6C\u4EE3\u7801\u7684\u6838\u5FC3\u5165\u53E3\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801/\u6839\u636E\u8BBE\u8BA1\u7A3F\u5199\u9875\u9762\u3001\u7EC4\u4EF6"\uFF0C\u6216\u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF09\u65F6\uFF0C\u9ED8\u8BA4\u4ECE\u672C\u5DE5\u5177\u5F00\u59CB\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 get_metadata \u9AA8\u67B6 + get_screenshot \u89C6\u89C9\uFF0C\u5B9A\u4F4D\u76EE\u6807\u540E\u518D\u7528\u672C\u5DE5\u5177\u3002\u7ED3\u6784\u590D\u6742\uFF1AmaxDepth \u4F20 0 \u9884\u5224\u89C4\u6A21\uFF08\u6839\u8282\u70B9 depth=\u6574\u6811\u6DF1\u5EA6\uFF09\uFF0C\u518D\u5BF9\u5E26 more \u6807\u8BB0\u7684\u5B50\u6811\u6309 id \u4E0B\u94BB\uFF08kidsSummary \u8F85\u52A9\u9009\u5206\u652F\uFF09\u3002\u3010\u8FD4\u56DE\u3011\u5355\u884C\u7D27\u51D1 JSON\u3002' + DSL_LEGEND + "\u584C\u7F29\u53F6\u5B50\uFF1A\u7EAF\u77E2\u91CF\u7EC4\u4EF6\u5B9E\u4F8B\u2192SVG\u3001\u56FE\u7247\u586B\u5145\u2192PNG\u3001\u8BBE\u8BA1\u5E08\u6807\u8BB0\u6309\u5176\u8BBE\u7F6E\uFF1B\u5DF2\u5E26 img \u7684\u8282\u70B9\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u52FF\u518D\u8C03 export_image\u3002DSL \u6709\u635F\u7CBE\u7B80\uFF08\u7701\u7565\u8499\u7248/\u5E03\u5C14\u8DEF\u5F84\u7B49\uFF09\uFF0C\u89C6\u89C9\u590D\u6742\u8282\u70B9\u914D\u5408 get_screenshot \u5BF9\u7167\u3002\u3010\u786C\u6027\u89C4\u5219\u3011" + URL_RULE + " " + GUIDES_PTR + " " + ENVELOPE_NOTE,
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
}
|
|
845
|
+
requires: ["guid", ["fileKey", "sessionId"]],
|
|
846
|
+
properties: {
|
|
847
|
+
guid: GUID_PROP,
|
|
848
|
+
maxDepth: {
|
|
849
|
+
type: "number",
|
|
850
|
+
description: "\u4E0B\u63A2\u5B50\u8282\u70B9\u7684\u6700\u5927\u6DF1\u5EA6\uFF08\u6839\u8282\u70B9\u4E3A\u7B2C 0 \u5C42\uFF09\uFF0C\u7F3A\u7701\u4E0D\u9650\u3002\u622A\u65AD\u8282\u70B9\u5E26 more\uFF08\u5269\u4F59\u5C42\u7EA7\u6570\uFF09+ kidsSummary\uFF08\u5B50\u8282\u70B9\u6D45\u9884\u89C8\uFF09\uFF0C\u62FF\u5176 id \u56DE\u4F20 guid \u7EE7\u7EED\u4E0B\u94BB\uFF1B\u4F20 0 \u53EA\u8FD4\u56DE\u6839\u8282\u70B9\uFF08\u914D\u5408 depth \u5B57\u6BB5\u9884\u5224\u89C4\u6A21\uFF09\u3002"
|
|
851
|
+
},
|
|
852
|
+
cache: CACHE_PROP,
|
|
853
|
+
fileKey: FILE_KEY_PROP,
|
|
854
|
+
sessionId: SESSION_ID_PROP
|
|
707
855
|
}
|
|
708
856
|
};
|
|
709
857
|
|
|
710
858
|
// src/agent/tools/get_screenshot.ts
|
|
711
859
|
var getScreenshot = {
|
|
712
860
|
description: '\u3010\u7528\u9014\u3011\u5BFC\u51FA\u5355\u4E2A\u8282\u70B9\u7684\u622A\u56FE\uFF0C\u4F5C\u89C6\u89C9\u53C2\u7167\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u770B\u770B\u8FD9\u4E2A\u8BBE\u8BA1\u7A3F\u957F\u4EC0\u4E48\u6837"/"\u7ED9\u6211\u622A\u56FE"\uFF1B\u8FD8\u539F\u4EFB\u52A1\u9700\u8981\u89C6\u89C9\u53C2\u7167\u6216\u8FD8\u539F\u5EA6\u6821\u9A8C\u3002\u8981\u5207\u56FE\u7D20\u6750\u7528 export_image\u3002\u751F\u6210\u4EE3\u7801\u540E\u53EF\u518D\u6B21\u8C03\u7528\u672C\u5DE5\u5177\u5BF9\u6BD4\u8FD8\u539F\u5EA6\uFF1B\u5EFA\u8BAE\u4E0E get_node_dsl \u914D\u5408\uFF1ADSL \u5B9A\u7ED3\u6784\uFF0C\u622A\u56FE\u6821\u9A8C\u89C6\u89C9\u3002\u3010\u8FD4\u56DE\u3011{img: \u4EA7\u7269\u6587\u4EF6\u540D, width, height, format}\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
861
|
+
requires: ["guid", ["fileKey", "sessionId"]],
|
|
862
|
+
properties: {
|
|
863
|
+
guid: GUID_PROP,
|
|
864
|
+
format: {
|
|
865
|
+
type: "string",
|
|
866
|
+
enum: ["PNG", "SVG"],
|
|
867
|
+
description: "\u5BFC\u51FA\u683C\u5F0F\uFF0C\u9ED8\u8BA4 PNG\u3002\u622A\u56FE\u7528\u9014\u7528 PNG\uFF1B\u7EAF\u56FE\u5F62\u60F3\u62FF\u77E2\u91CF\u518D\u9009 SVG\u3002"
|
|
868
|
+
},
|
|
869
|
+
scale: {
|
|
870
|
+
type: "number",
|
|
871
|
+
description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
|
|
872
|
+
},
|
|
873
|
+
cache: CACHE_PROP,
|
|
874
|
+
fileKey: FILE_KEY_PROP,
|
|
875
|
+
sessionId: SESSION_ID_PROP
|
|
729
876
|
}
|
|
730
877
|
};
|
|
731
878
|
|
|
732
879
|
// src/agent/tools/get_selected.ts
|
|
733
880
|
var getSelected = {
|
|
734
|
-
description: '\u3010\u7528\u9014\u3011\u5B9E\u65F6\u8BFB\u53D6 Pixso \u753B\u5E03\u5F53\u524D\u9009\u4E2D\u7684\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\uFF09\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u7528\u6237\u8BF4"\u8FD9\u4E2A/\u5F53\u524D\u9009\u4E2D\u7684"\u56FE\u5C42\u3001\u7EC4\u4EF6\u3002\u5DF2\u7ED9 Pixso \u94FE\u63A5\u65F6\u4E0D\u8981\u7528\u672C\u5DE5\u5177\u2014\u2014\u76F4\u63A5\u4ECE\u94FE\u63A5\u63D0\u53D6 guid\uFF08\u89C1 get_node_dsl\uFF09\u3002\u3010\u8FD4\u56DE\u3011{fileKey, fileName, pageName, selection: [{id, name, type}]}\u3002\u3010\u786C\u6027\u89C4\u5219\u3011\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u8FD4\u56DE\u7684 guid\uFF1A\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\u5FC5\u987B\u5148\u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08"\u5F53\u524D\u9009\u4E2D\u7684\u662F\u300CXX\u300D\uFF0C\u786E\u8BA4\u5904\u7406\u5B83\u5417\uFF1F"\uFF09\uFF0C\u786E\u8BA4\u540E\u624D\u80FD\u4F20\u7ED9\u5176\u4ED6\u5DE5\u5177\u3002 ' + ENVELOPE_NOTE,
|
|
735
|
-
|
|
881
|
+
description: '\u3010\u7528\u9014\u3011\u5B9E\u65F6\u8BFB\u53D6 Pixso \u753B\u5E03\u5F53\u524D\u9009\u4E2D\u7684\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\uFF09\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u7528\u6237\u8BF4"\u8FD9\u4E2A/\u5F53\u524D\u9009\u4E2D\u7684"\u56FE\u5C42\u3001\u7EC4\u4EF6\u3002\u5DF2\u7ED9 Pixso \u94FE\u63A5\u65F6\u4E0D\u8981\u7528\u672C\u5DE5\u5177\u2014\u2014\u76F4\u63A5\u4ECE\u94FE\u63A5\u63D0\u53D6 guid\uFF08\u89C1 get_node_dsl\uFF09\u3002\u3010\u8FD4\u56DE\u3011{fileKey, fileName, pageName, selection: [{id, name, type}]}\u3002\u3010\u786C\u6027\u89C4\u5219\u3011\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u8FD4\u56DE\u7684 guid\uFF1A\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\u5FC5\u987B\u5148\u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08"\u5F53\u524D\u9009\u4E2D\u7684\u662F\u300CXX\u300D\uFF0C\u786E\u8BA4\u5904\u7406\u5B83\u5417\uFF1F"\uFF09\uFF0C\u786E\u8BA4\u540E\u624D\u80FD\u4F20\u7ED9\u5176\u4ED6\u5DE5\u5177\u3002\u672C\u5DE5\u5177\u7684 fileKey/sessionId \u53EF\u7F3A\u7701\uFF08\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u6587\u4EF6\uFF09\uFF1B\u5176\u4ED6\u5DE5\u5177\u4E24\u8005\u5FC5\u4F20\u5176\u4E00\u3002 ' + ENVELOPE_NOTE,
|
|
882
|
+
properties: { fileKey: FILE_KEY_PROP, sessionId: SESSION_ID_PROP }
|
|
736
883
|
};
|
|
737
884
|
|
|
738
885
|
// src/agent/tools/list_sessions.ts
|
|
739
886
|
var listSessions = {
|
|
740
|
-
description: "\u3010\u7528\u9014\u3011\u5217\u51FA\u5B58\u6D3B\u7684\u8BBE\u8BA1\u7A3F\u63D2\u4EF6\u4F1A\u8BDD\uFF08sessionId\u3001\u6587\u4EF6\u3001\u9875\u9762\u3001\u9009\u4E2D\u6570\uFF09\u2014\u2014\u6BCF\u6B21\u8C03\u7528\u7ECF ping/pong \u5B9E\u6D4B\uFF0C\u65E0\u54CD\u5E94\u7684\u4E0D\u51FA\u73B0\u3002\u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u5BF9\u5E94\u4E00\u4E2A\u4F1A\u8BDD\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u5F00\u63D2\u4EF6\u65F6\uFF0C\u5148\u7528\u672C\u5DE5\u5177\u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF1B\u53EA\u6709\u4E00\u4E2A\u4F1A\u8BDD\u65F6\u65E0\u9700\u5173\u6CE8\u672C\u5DE5\u5177\u3002\u3010\u8FD4\u56DE\u3011\u4F1A\u8BDD\u5217\u8868 JSON\u3002\u628A
|
|
741
|
-
|
|
887
|
+
description: "\u3010\u7528\u9014\u3011\u5217\u51FA\u5B58\u6D3B\u7684\u8BBE\u8BA1\u7A3F\u63D2\u4EF6\u4F1A\u8BDD\uFF08sessionId\u3001\u6587\u4EF6\u3001\u9875\u9762\u3001\u9009\u4E2D\u6570\uFF09\u2014\u2014\u6BCF\u6B21\u8C03\u7528\u7ECF ping/pong \u5B9E\u6D4B\uFF0C\u65E0\u54CD\u5E94\u7684\u4E0D\u51FA\u73B0\u3002\u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u5BF9\u5E94\u4E00\u4E2A\u4F1A\u8BDD\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u5F00\u63D2\u4EF6\u65F6\uFF0C\u5148\u7528\u672C\u5DE5\u5177\u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF1B\u53EA\u6709\u4E00\u4E2A\u4F1A\u8BDD\u65F6\u65E0\u9700\u5173\u6CE8\u672C\u5DE5\u5177\u3002\u3010\u8FD4\u56DE\u3011\u4F1A\u8BDD\u5217\u8868 JSON\u3002\u628A fileKey\uFF08\u8DE8\u91CD\u5F00\u7A33\u5B9A\uFF0C\u4F18\u5148\uFF09\u6216 sessionId \u4F5C\u4E3A\u5176\u4ED6\u5DE5\u5177\u7684\u540C\u540D\u53C2\u6570\u4F20\u5165\u5373\u8DEF\u7531\u5230\u5BF9\u5E94\u6587\u4EF6\uFF08\u8282\u70B9\u7C7B\u5DE5\u5177\u4E8C\u8005\u5FC5\u4F20\u5176\u4E00\uFF1B\u4EC5 get_selected \u4E0E\u672C\u5DE5\u5177\u53EF\u7F3A\u7701\uFF09\u3002\u8BE6\u89C1\u8D44\u6E90 batter://guides/intro\u3002",
|
|
888
|
+
properties: {},
|
|
742
889
|
handler: async () => {
|
|
743
890
|
const list = sessions.list();
|
|
744
891
|
if (!list.length) {
|
|
@@ -779,16 +926,48 @@ var TOOL_REGISTRY = {
|
|
|
779
926
|
get_screenshot: getScreenshot,
|
|
780
927
|
list_sessions: listSessions
|
|
781
928
|
};
|
|
929
|
+
function buildInputSchema(requires, properties) {
|
|
930
|
+
const singles = requires.filter((r) => typeof r === "string");
|
|
931
|
+
const xorGroups = requires.filter((r) => Array.isArray(r));
|
|
932
|
+
const groupSchemas = xorGroups.map((g) => ({ oneOf: g.map((p) => ({ required: [p] })) }));
|
|
933
|
+
return {
|
|
934
|
+
type: "object",
|
|
935
|
+
...singles.length ? { required: singles } : {},
|
|
936
|
+
...groupSchemas.length === 1 ? groupSchemas[0] : {},
|
|
937
|
+
...groupSchemas.length > 1 ? { allOf: groupSchemas } : {},
|
|
938
|
+
properties
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
function validateArgs(requires, args) {
|
|
942
|
+
const present = (k) => args[k] !== void 0 && args[k] !== null && args[k] !== "";
|
|
943
|
+
for (const req of requires) {
|
|
944
|
+
if (typeof req === "string") {
|
|
945
|
+
if (!present(req)) return `\u7F3A\u5C11\u5FC5\u4F20\u53C2\u6570\uFF1A${req}`;
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const given = req.filter(present);
|
|
949
|
+
if (given.length === 0) return `\u7F3A\u5C11\u53C2\u6570\uFF1A${req.join(" / ")} \u9700\u4F20\u4E00\u4E2A`;
|
|
950
|
+
if (given.length > 1) return `\u53C2\u6570\u4E92\u65A5\uFF1A${req.join(" / ")} \u53EA\u80FD\u4F20\u4E00\u4E2A`;
|
|
951
|
+
}
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
782
954
|
var TOOLS = Object.fromEntries(
|
|
783
955
|
Object.entries(TOOL_REGISTRY).map(([name, def]) => {
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
|
|
956
|
+
const requires = def.requires ?? [];
|
|
957
|
+
const inputSchema = buildInputSchema(requires, def.properties);
|
|
958
|
+
const base = def.handler ?? ((args, ctx) => run(name, args, ctx?.signal));
|
|
959
|
+
const handler = (args, ctx) => {
|
|
960
|
+
const err = requires.length ? validateArgs(requires, args) : null;
|
|
961
|
+
if (err) return Promise.resolve(errorResult(err));
|
|
962
|
+
return base(args, ctx);
|
|
963
|
+
};
|
|
964
|
+
const resolvedDef = { description: def.description, inputSchema, handler };
|
|
965
|
+
return [name, { def: resolvedDef, tool: { name, description: def.description, inputSchema } }];
|
|
787
966
|
})
|
|
788
967
|
);
|
|
789
968
|
|
|
790
969
|
// src/agent/mcp.ts
|
|
791
|
-
var INSTRUCTIONS = `Pixso \u8BBE\u8BA1\u7A3F\u8F6C\u4EE3\u7801\u670D\u52A1\u3002\u6309\u7528\u6237\u8F93\u5165\u8DEF\u7531\uFF1A1. \u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF09\u2192 \u4ECE item-id \u63D0\u53D6 guid\uFF08URL \u89E3\u7801\uFF09\uFF0C\u76F4\u63A5\u8C03 get_node_dsl / get_screenshot / export_image\uFF1B2. \u8BF4"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\u2192 get_selected \u62FF guid\uFF1B\u5176\u4ED6\u4F9D\u8D56\u9009\u533A\u7684\u573A\u666F\u5148 get_selected \u518D\u5411\u7528\u6237\u62A5\u540D\u79F0\u786E\u8BA4\uFF1B3. "\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801"\u2192 get_node_dsl \u53D6\u7ED3\u6784 + get_screenshot \u53D6\u89C6\u89C9\u53C2\u7167\uFF1B4. "\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807\u6216\u56FE\u7247\u7D20\u6750"\u2192 export_image\uFF08DSL \u91CC\u5DF2\u5E26 img \u7684\u8282\u70B9\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u91CD\u590D\u5BFC\u51FA\uFF09\uFF1B5. \u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F/\u53EA\u8981\u7ED3\u6784\u6982\u89C8 \u2192 get_metadata \u62FF\u5168\u6811\u9AA8\u67B6\uFF08\u65E0\u6837\u5F0F\u65E0\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09\u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF1B\u7ED3\u6784\u590D\u6742 \u2192 get_node_dsl \u7528 maxDepth \u5206\u6BB5\u4E0B\u63A2\uFF08\u622A\u65AD\u5904 more \u6807\u8BB0 + kidsSummary \u6D45\u9884\u89C8\uFF0C\u6309 id \u4E0B\u94BB\uFF09\uFF1B6. \u751F\u6210\u540E\u5BF9\u6BD4\u8FD8\u539F\u5EA6 \u2192 get_screenshot \u6821\u9A8C\u5FAA\u73AF\uFF1B7. \u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u6253\u5F00 \u2192 list_sessions +
|
|
970
|
+
var INSTRUCTIONS = `Pixso \u8BBE\u8BA1\u7A3F\u8F6C\u4EE3\u7801\u670D\u52A1\u3002\u6309\u7528\u6237\u8F93\u5165\u8DEF\u7531\uFF1A1. \u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF09\u2192 \u4ECE item-id \u63D0\u53D6 guid\uFF08URL \u89E3\u7801\uFF09\uFF0C\u76F4\u63A5\u8C03 get_node_dsl / get_screenshot / export_image\uFF1B2. \u8BF4"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\u2192 get_selected \u62FF guid\uFF1B\u5176\u4ED6\u4F9D\u8D56\u9009\u533A\u7684\u573A\u666F\u5148 get_selected \u518D\u5411\u7528\u6237\u62A5\u540D\u79F0\u786E\u8BA4\uFF1B3. "\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801"\u2192 get_node_dsl \u53D6\u7ED3\u6784 + get_screenshot \u53D6\u89C6\u89C9\u53C2\u7167\uFF1B4. "\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807\u6216\u56FE\u7247\u7D20\u6750"\u2192 export_image\uFF08DSL \u91CC\u5DF2\u5E26 img \u7684\u8282\u70B9\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u91CD\u590D\u5BFC\u51FA\uFF09\uFF1B5. \u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F/\u53EA\u8981\u7ED3\u6784\u6982\u89C8 \u2192 get_metadata \u62FF\u5168\u6811\u9AA8\u67B6\uFF08\u65E0\u6837\u5F0F\u65E0\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09\u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF1B\u7ED3\u6784\u590D\u6742 \u2192 get_node_dsl \u7528 maxDepth \u5206\u6BB5\u4E0B\u63A2\uFF08\u622A\u65AD\u5904 more \u6807\u8BB0 + kidsSummary \u6D45\u9884\u89C8\uFF0C\u6309 id \u4E0B\u94BB\uFF09\uFF1B6. \u751F\u6210\u540E\u5BF9\u6BD4\u8FD8\u539F\u5EA6 \u2192 get_screenshot \u6821\u9A8C\u5FAA\u73AF\uFF1B7. \u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u6253\u5F00 \u2192 list_sessions + fileKey/sessionId \u53C2\u6570\u8DEF\u7531\uFF08\u8282\u70B9\u7C7B\u5DE5\u5177\u4E8C\u8005\u5FC5\u4F20\u5176\u4E00\uFF09\u3002\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D/\u7981\u6B62\u622A\u56FE\u786C\u7F16\u7801\uFF09\u89C1\u8D44\u6E90 batter://guides/design-to-code\u2014\u2014\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\u3002`;
|
|
792
971
|
function createMcpServer() {
|
|
793
972
|
const server = new McpServer(
|
|
794
973
|
{ name: HUB_NAME, version: VERSION },
|
|
@@ -844,15 +1023,28 @@ function createMcpServer() {
|
|
|
844
1023
|
// src/agent/sessions.ts
|
|
845
1024
|
var transports = /* @__PURE__ */ new Map();
|
|
846
1025
|
function getMcpTransport(sessionId) {
|
|
847
|
-
|
|
1026
|
+
const entry = transports.get(sessionId);
|
|
1027
|
+
if (entry) entry.lastSeen = Date.now();
|
|
1028
|
+
return entry?.transport;
|
|
848
1029
|
}
|
|
849
1030
|
function registerMcpTransport(sessionId, transport) {
|
|
850
|
-
transports.set(sessionId, transport);
|
|
1031
|
+
transports.set(sessionId, { transport, lastSeen: Date.now() });
|
|
851
1032
|
log(`MCP HTTP session initialized: ${sessionId.slice(0, 8)} (total ${transports.size})`);
|
|
852
1033
|
transport.onclose = () => {
|
|
853
1034
|
if (transport.sessionId) transports.delete(transport.sessionId);
|
|
854
1035
|
};
|
|
855
1036
|
}
|
|
1037
|
+
var sweeper = setInterval(() => {
|
|
1038
|
+
const now = Date.now();
|
|
1039
|
+
for (const [id, entry] of transports) {
|
|
1040
|
+
if (now - entry.lastSeen <= config.mcpSessionTtlMs) continue;
|
|
1041
|
+
transports.delete(id);
|
|
1042
|
+
void entry.transport.close().catch(() => {
|
|
1043
|
+
});
|
|
1044
|
+
log(`MCP HTTP session expired: ${id.slice(0, 8)} (total ${transports.size})`);
|
|
1045
|
+
}
|
|
1046
|
+
}, 6e4);
|
|
1047
|
+
sweeper.unref();
|
|
856
1048
|
|
|
857
1049
|
// src/agent/http.ts
|
|
858
1050
|
function handleMcp(req, res, url) {
|
|
@@ -908,18 +1100,18 @@ var MCP_ROUTES = [
|
|
|
908
1100
|
// src/install/dist-resolver.ts
|
|
909
1101
|
import { existsSync } from "node:fs";
|
|
910
1102
|
import { readFile } from "node:fs/promises";
|
|
911
|
-
import { join as
|
|
912
|
-
import { fileURLToPath } from "node:url";
|
|
1103
|
+
import { join as join4 } from "node:path";
|
|
1104
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
913
1105
|
function resolvePluginDir() {
|
|
914
1106
|
const candidates = [
|
|
915
|
-
|
|
1107
|
+
fileURLToPath2(new URL("./plugin/", import.meta.url)),
|
|
916
1108
|
// bundle: dist/index.js → dist/plugin/
|
|
917
|
-
|
|
1109
|
+
fileURLToPath2(new URL("../../plugin/dist/", import.meta.url)),
|
|
918
1110
|
// bundle 开发: dist/index.js → packages/plugin/dist/
|
|
919
|
-
|
|
1111
|
+
fileURLToPath2(new URL("../../../plugin/dist/", import.meta.url))
|
|
920
1112
|
// tsx 开发: src/install/ → packages/plugin/dist/
|
|
921
1113
|
];
|
|
922
|
-
return candidates.find((p) => existsSync(
|
|
1114
|
+
return candidates.find((p) => existsSync(join4(p, "manifest.json"))) ?? null;
|
|
923
1115
|
}
|
|
924
1116
|
var resolved = null;
|
|
925
1117
|
function pluginDir() {
|
|
@@ -932,7 +1124,7 @@ function hasPluginDist() {
|
|
|
932
1124
|
async function pluginManifest() {
|
|
933
1125
|
const dir = pluginDir();
|
|
934
1126
|
if (!dir) return null;
|
|
935
|
-
const raw = JSON.parse(await readFile(
|
|
1127
|
+
const raw = JSON.parse(await readFile(join4(dir, "manifest.json"), "utf8"));
|
|
936
1128
|
raw.networkAccess = {
|
|
937
1129
|
allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
|
|
938
1130
|
};
|
|
@@ -942,7 +1134,7 @@ async function readPluginFile(name) {
|
|
|
942
1134
|
const dir = pluginDir();
|
|
943
1135
|
if (!dir) return null;
|
|
944
1136
|
try {
|
|
945
|
-
return await readFile(
|
|
1137
|
+
return await readFile(join4(dir, name));
|
|
946
1138
|
} catch {
|
|
947
1139
|
return null;
|
|
948
1140
|
}
|
|
@@ -1590,14 +1782,22 @@ var PLUGIN_ROUTES = [
|
|
|
1590
1782
|
|
|
1591
1783
|
`);
|
|
1592
1784
|
});
|
|
1785
|
+
const teardown = () => {
|
|
1786
|
+
clearInterval(heartbeat);
|
|
1787
|
+
unsubscribe();
|
|
1788
|
+
sessions.setConnected(session, false);
|
|
1789
|
+
};
|
|
1593
1790
|
const heartbeat = setInterval(() => {
|
|
1791
|
+
if (!res.write(": ping\n\n")) {
|
|
1792
|
+
teardown();
|
|
1793
|
+
res.end();
|
|
1794
|
+
log(`SSE ghost reaped: session ${session.slice(0, 8)}`);
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1594
1797
|
sessions.touchSeen(session);
|
|
1595
|
-
res.write(": ping\n\n");
|
|
1596
1798
|
}, 15e3);
|
|
1597
1799
|
req.on("close", () => {
|
|
1598
|
-
|
|
1599
|
-
unsubscribe();
|
|
1600
|
-
sessions.setConnected(session, false);
|
|
1800
|
+
teardown();
|
|
1601
1801
|
log(`SSE channel closed: session ${session.slice(0, 8)}`);
|
|
1602
1802
|
});
|
|
1603
1803
|
}
|
|
@@ -1627,14 +1827,9 @@ var PLUGIN_ROUTES = [
|
|
|
1627
1827
|
];
|
|
1628
1828
|
|
|
1629
1829
|
// src/http.ts
|
|
1630
|
-
var lastActivity = Date.now();
|
|
1631
|
-
function getLastActivity() {
|
|
1632
|
-
return lastActivity;
|
|
1633
|
-
}
|
|
1634
1830
|
var ROUTES = [handleInstall, handleMcp, handlePluginApi];
|
|
1635
1831
|
function startHttpServer() {
|
|
1636
1832
|
const server = createServer((req, res) => {
|
|
1637
|
-
lastActivity = Date.now();
|
|
1638
1833
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1639
1834
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
1640
1835
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
|
|
@@ -1685,19 +1880,11 @@ async function runHub() {
|
|
|
1685
1880
|
log(`\u7AEF\u53E3 ${config.httpPort} \u5DF2\u6709 hub \u5728\u8FD0\u884C\uFF0C\u672C\u8FDB\u7A0B\u9000\u51FA`);
|
|
1686
1881
|
process.exit(0);
|
|
1687
1882
|
}
|
|
1688
|
-
|
|
1883
|
+
writePidFile(process.pid);
|
|
1884
|
+
log("hub ready\uFF08\u5E38\u9A7B\uFF0C\u4E0D\u81EA\u6BC1\uFF1B\u7248\u672C\u4E0D\u7B26\u65F6\u7531 proxy \u81EA\u52A8\u6362\u8840\uFF0C\u6216 --stop \u624B\u52A8\u505C\u6B62\uFF09");
|
|
1689
1885
|
void cleanOldAssets(config.assetTtlMs);
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
const idle = Date.now() - getLastActivity();
|
|
1693
|
-
const anyPluginOnline = sessions.list().some((s) => s.online);
|
|
1694
|
-
if (idle > config.hubIdleMs && !anyPluginOnline) {
|
|
1695
|
-
log(`\u7A7A\u95F2 ${Math.round(idle / 6e4)} \u5206\u949F\u4E14\u65E0\u5728\u7EBF\u63D2\u4EF6\uFF0Chub \u81EA\u52A8\u9000\u51FA`);
|
|
1696
|
-
process.exit(0);
|
|
1697
|
-
}
|
|
1698
|
-
if (++tick % 60 === 0) void cleanOldAssets(config.assetTtlMs);
|
|
1699
|
-
}, 6e4);
|
|
1700
|
-
watchdog.unref();
|
|
1886
|
+
const sweeper2 = setInterval(() => void cleanOldAssets(config.assetTtlMs), 60 * 6e4);
|
|
1887
|
+
sweeper2.unref();
|
|
1701
1888
|
}
|
|
1702
1889
|
|
|
1703
1890
|
// src/process/stdio-proxy.ts
|
|
@@ -1706,64 +1893,6 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
1706
1893
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1707
1894
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1708
1895
|
import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
1709
|
-
|
|
1710
|
-
// src/process/hub-process.ts
|
|
1711
|
-
import { spawn } from "node:child_process";
|
|
1712
|
-
import { closeSync, openSync } from "node:fs";
|
|
1713
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
1714
|
-
import { join as join4 } from "node:path";
|
|
1715
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1716
|
-
async function probeHub() {
|
|
1717
|
-
try {
|
|
1718
|
-
const res = await fetch(`${hubBaseUrl()}/health`, { signal: AbortSignal.timeout(1500) });
|
|
1719
|
-
if (!res.ok) return "foreign";
|
|
1720
|
-
const body = await res.json();
|
|
1721
|
-
if (body.ok === true && body.name === HUB_NAME) return "alive";
|
|
1722
|
-
if (body.ok === true) return "legacy";
|
|
1723
|
-
return "foreign";
|
|
1724
|
-
} catch {
|
|
1725
|
-
return "down";
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
var PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
|
|
1729
|
-
function hubStateError(state) {
|
|
1730
|
-
if (state === "legacy") {
|
|
1731
|
-
return new Error(
|
|
1732
|
-
`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u662F\u65E7\u7248\u672C batter-pixso-mcp \u5B9E\u4F8B\uFF08\u4E0D\u652F\u6301\u591A agent \u590D\u7528\uFF09\u3002${PORT_FIX_HINT}`
|
|
1733
|
-
);
|
|
1734
|
-
}
|
|
1735
|
-
return new Error(`\u7AEF\u53E3 ${config.httpPort} \u88AB\u5176\u4ED6\u7A0B\u5E8F\u5360\u7528\u3002${PORT_FIX_HINT}\uFF0C\u6216\u4FEE\u6539 HTTP_PORT \u6362\u7AEF\u53E3`);
|
|
1736
|
-
}
|
|
1737
|
-
var HUB_LOG = join4(tmpdir2(), "batter-pixso-mcp-hub.log");
|
|
1738
|
-
function spawnHub() {
|
|
1739
|
-
const out = openSync(HUB_LOG, "a");
|
|
1740
|
-
const entry = process.argv[1] ?? fileURLToPath2(import.meta.url);
|
|
1741
|
-
const SKIP_FLAGS = /* @__PURE__ */ new Set(["--eval", "-e", "--print", "-p", "--watch", "--watch-preserve-output"]);
|
|
1742
|
-
const execArgv = process.execArgv.filter(
|
|
1743
|
-
(a, i, arr) => !SKIP_FLAGS.has(a) && !(i > 0 && SKIP_FLAGS.has(arr[i - 1]))
|
|
1744
|
-
);
|
|
1745
|
-
const child = spawn(process.execPath, [...execArgv, entry, "--hub"], {
|
|
1746
|
-
detached: true,
|
|
1747
|
-
stdio: ["ignore", out, out],
|
|
1748
|
-
windowsHide: true,
|
|
1749
|
-
env: { ...process.env }
|
|
1750
|
-
});
|
|
1751
|
-
child.unref();
|
|
1752
|
-
closeSync(out);
|
|
1753
|
-
log(`hub spawned (pid ${child.pid}), log: ${HUB_LOG}`);
|
|
1754
|
-
}
|
|
1755
|
-
async function waitForHub(timeoutMs = 15e3) {
|
|
1756
|
-
const deadline = Date.now() + timeoutMs;
|
|
1757
|
-
for (; ; ) {
|
|
1758
|
-
const state = await probeHub();
|
|
1759
|
-
if (state === "alive") return;
|
|
1760
|
-
if (state !== "down") throw hubStateError(state);
|
|
1761
|
-
if (Date.now() > deadline) throw new Error(`\u7B49\u5F85 hub \u5C31\u7EEA\u8D85\u65F6\uFF08${timeoutMs / 1e3}s\uFF09`);
|
|
1762
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
// src/process/stdio-proxy.ts
|
|
1767
1896
|
async function startStdioProxy() {
|
|
1768
1897
|
const mcpUrl = `${hubBaseUrl()}/mcp`;
|
|
1769
1898
|
let upstream = null;
|
|
@@ -1777,14 +1906,40 @@ async function startStdioProxy() {
|
|
|
1777
1906
|
}
|
|
1778
1907
|
async function ensureUpstream() {
|
|
1779
1908
|
if (upstream) return upstream;
|
|
1780
|
-
const state = await probeHub();
|
|
1781
|
-
if (state === "
|
|
1782
|
-
|
|
1783
|
-
|
|
1909
|
+
const { state, health } = await probeHub();
|
|
1910
|
+
if (state === "alive" && health?.version !== VERSION) {
|
|
1911
|
+
log(`hub \u7248\u672C\u4E0D\u7B26\uFF08\u8FD0\u884C ${health?.version} / \u5F53\u524D ${VERSION}\uFF09\uFF0C\u81EA\u52A8\u91CD\u542F\u2026`);
|
|
1912
|
+
if (!killHubByPid(health.pid)) await stopHub();
|
|
1913
|
+
await waitForHubDown();
|
|
1914
|
+
spawnHub();
|
|
1915
|
+
} else if (state === "hung") {
|
|
1916
|
+
log("hub \u65E0\u54CD\u5E94\uFF08\u50F5\u5C38\u8FDB\u7A0B\uFF09\uFF0C\u5F3A\u6740\u540E\u91CD\u5EFA\u2026");
|
|
1917
|
+
if (!killHubByPidFile()) throw hubStateError("hung");
|
|
1918
|
+
await waitForHubDown(1e4);
|
|
1919
|
+
spawnHub();
|
|
1920
|
+
} else if (state === "down") {
|
|
1921
|
+
spawnHub();
|
|
1922
|
+
} else if (state === "foreign") {
|
|
1923
|
+
throw hubStateError(state);
|
|
1924
|
+
}
|
|
1925
|
+
await waitForHub(3e4);
|
|
1784
1926
|
upstream = await connectUpstream();
|
|
1785
1927
|
log(`proxy connected to ${mcpUrl}`);
|
|
1786
1928
|
return upstream;
|
|
1787
1929
|
}
|
|
1930
|
+
let rebuilding = null;
|
|
1931
|
+
function rebuildUpstream() {
|
|
1932
|
+
rebuilding ??= (async () => {
|
|
1933
|
+
const stale = upstream;
|
|
1934
|
+
upstream = null;
|
|
1935
|
+
if (stale) await stale.close().catch(() => {
|
|
1936
|
+
});
|
|
1937
|
+
return ensureUpstream();
|
|
1938
|
+
})().finally(() => {
|
|
1939
|
+
rebuilding = null;
|
|
1940
|
+
});
|
|
1941
|
+
return rebuilding;
|
|
1942
|
+
}
|
|
1788
1943
|
async function callWithRecovery(fn) {
|
|
1789
1944
|
const client = await ensureUpstream();
|
|
1790
1945
|
try {
|
|
@@ -1796,8 +1951,7 @@ async function startStdioProxy() {
|
|
|
1796
1951
|
);
|
|
1797
1952
|
if (alive) throw err;
|
|
1798
1953
|
log("hub \u4F1A\u8BDD\u5931\u8054\uFF0C\u91CD\u5EFA\u540E\u91CD\u8BD5\u2026");
|
|
1799
|
-
|
|
1800
|
-
const rebuilt = await ensureUpstream();
|
|
1954
|
+
const rebuilt = await rebuildUpstream();
|
|
1801
1955
|
return await fn(rebuilt);
|
|
1802
1956
|
}
|
|
1803
1957
|
}
|
|
@@ -1823,6 +1977,14 @@ async function main() {
|
|
|
1823
1977
|
await runHub();
|
|
1824
1978
|
return;
|
|
1825
1979
|
}
|
|
1980
|
+
if (process.argv.includes("--stop")) {
|
|
1981
|
+
await stopHub();
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
1985
|
+
log(VERSION);
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1826
1988
|
await startStdioProxy();
|
|
1827
1989
|
}
|
|
1828
1990
|
|