batter-pixso-mcp 0.4.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 +452 -287
- package/dist/plugin/code.js +15 -12
- package/package.json +1 -1
- package/standalone/index.js +153 -30
- package/standalone/plugin/code.js +15 -12
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
|
|
@@ -318,7 +341,7 @@ var DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
|
|
|
318
341
|
## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011
|
|
319
342
|
|
|
320
343
|
1. **\u8D44\u6E90\u662F\u672C\u673A\u6587\u4EF6\uFF0C\u6309\u9700\u53D6\u7528**\uFF1ADSL \u4E0E\u622A\u56FE\u7ED3\u679C\u4E2D\u7684 \`img\` \u5B57\u6BB5\u662F\u4EA7\u7269**\u6587\u4EF6\u540D**
|
|
321
|
-
\uFF08\u5982 \`"ab12cd34.svg"\`\uFF09\uFF0C\u5B8C\u6574\u8DEF\u5F84 = \u4EA7\u7269\u76EE\u5F55 + \u6587\u4EF6\u540D\uFF08\
|
|
344
|
+
\uFF08\u5982 \`"ab12cd34.svg"\`\uFF09\uFF0C\u5B8C\u6574\u8DEF\u5F84 = \u4EA7\u7269\u76EE\u5F55 + \u6587\u4EF6\u540D\uFF08\u672C\u673A\u4EA7\u7269\u76EE\u5F55\uFF1A\`${ASSETS_DIR}\`\uFF09\u3002\u4EA7\u7269\u4E0E\u670D\u52A1\u540C\u673A\uFF0C\u76F4\u63A5\u8BFB\u6587\u4EF6\u7CFB\u7EDF\uFF1A
|
|
322
345
|
- **\u622A\u56FE\u7B49\u89C6\u89C9\u53C2\u7167**\uFF1A\u7528 Read \u5DE5\u5177\u76F4\u63A5\u8BFB\u8BE5\u8DEF\u5F84\u67E5\u770B\uFF0C\u65E0\u9700\u590D\u5236\u3002
|
|
323
346
|
- **\u8981\u5199\u8FDB\u4EA4\u4ED8\u4EE3\u7801\u7684\u56FE\u6807/\u56FE\u7247**\uFF1A**\u5FC5\u987B**\u5148\u590D\u5236\u5230\u9879\u76EE\u8D44\u6E90\u76EE\u5F55
|
|
324
347
|
\uFF08PowerShell \`Copy-Item\` / \u7C7B Unix \`cp\`\uFF09\uFF0C\u518D\u4EE5\u672C\u5730\u76F8\u5BF9\u8DEF\u5F84\u5F15\u7528
|
|
@@ -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
|
|
@@ -388,6 +411,7 @@ DSL \u662F\u5355\u884C\u7D27\u51D1 JSON\uFF1A\u77ED key\u3001\u4F4D\u7F6E\u6570\
|
|
|
388
411
|
"palette": ["#1a73e8", "#f5222d"], // \u8C03\u8272\u677F\uFF08\u4EC5\u6839\u8282\u70B9\uFF09\uFF0C"p0"/"p1" \u4E3A\u4E0B\u6807\u5F15\u7528
|
|
389
412
|
"depth": 6, // \u6574\u68F5\u6811\u5B9E\u9645\u6700\u5927\u6DF1\u5EA6\uFF08\u4EC5\u6839\u8282\u70B9\uFF0C\u603B\u662F\u8F93\u51FA\uFF0C\u4E0E\u622A\u65AD\u65E0\u5173\uFF09
|
|
390
413
|
"more": 3, // \u4EC5\u56E0 maxDepth \u622A\u65AD\u65F6\u51FA\u73B0\uFF1A\u8BE5\u8282\u70B9\u4E0B\u8FD8\u6709 3 \u7EA7\u672A\u5C55\u5F00\uFF0C\u62FF id \u56DE\u4F20 guid \u7EE7\u7EED\u4E0B\u94BB
|
|
414
|
+
"kidsSummary": [{"id": "...", "name": "...", "type": "FRAME"}], // \u622A\u65AD\u5904\u6216\u584C\u7F29\u53F6\u5B50\uFF08img \u8282\u70B9\uFF09\u7684\u76F4\u63A5\u5B50\u8282\u70B9\u6D45\u9884\u89C8
|
|
391
415
|
"kids": [] // \u5B50\u8282\u70B9\u9012\u5F52
|
|
392
416
|
}
|
|
393
417
|
\`\`\`
|
|
@@ -406,7 +430,7 @@ DSL \u662F\u5355\u884C\u7D27\u51D1 JSON\uFF1A\u77ED key\u3001\u4F4D\u7F6E\u6570\
|
|
|
406
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
|
|
407
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
|
|
408
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
|
|
409
|
-
- **\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
|
|
410
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
|
|
411
435
|
|
|
412
436
|
## \u7EC4\u4EF6\u5173\u8054\uFF08comp \u5B57\u6BB5\uFF09
|
|
@@ -424,6 +448,8 @@ INSTANCE / COMPONENT / COMPONENT_SET \u8282\u70B9\u5E26 \`comp\` \u5B57\u6BB5\uF
|
|
|
424
448
|
- \`type: "IMAGE"\`\uFF1A\u56FE\u7247\u586B\u5145\u8282\u70B9\uFF0C\`img\` \u4E3A **PNG** \u6587\u4EF6\u540D\u3002
|
|
425
449
|
- \u6587\u4EF6\u540D\u62FC\u8DEF\u5F84\u4E0E\u53D6\u7528\u8981\u6C42\u89C1\u5F00\u5934\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\u3002
|
|
426
450
|
- \u5BB9\u5668\u81EA\u8EAB\u6709\u5BFC\u51FA\u8BBE\u7F6E\u6807\u8BB0\u65F6\u4E5F\u4F1A\u584C\u7F29\u4E3A\u53F6\u5B50\uFF08\u5C0A\u91CD\u8BBE\u8BA1\u5E08\u7684\u5BFC\u51FA\u610F\u56FE\uFF09\u3002
|
|
451
|
+
- **\u584C\u7F29\u53F6\u5B50\u5E26 \`kidsSummary\`**\uFF08\u76F4\u63A5\u5B50\u8282\u70B9\u6D45\u9884\u89C8\uFF09\uFF1A\u5185\u90E8\u7ED3\u6784\u9ED8\u8BA4\u584C\u7F29\u9690\u85CF\uFF0C\u9700\u8981\u4E0B\u63A2\u65F6
|
|
452
|
+
\uFF08\u5982\u5B9E\u4F8B\u5185\u8FD8\u6709\u6587\u672C/\u5FBD\u6807/\u5355\u72EC\u6807\u8BB0\u5BFC\u51FA\u7684\u5B50\u8282\u70B9\uFF09\u62FF\u5B50\u8282\u70B9 \`id\` \u56DE\u4F20 \`guid\` \u53D6\u5168\u91CF DSL\u3002
|
|
427
453
|
- **\u5DF2\u5E26 \`img\` \u7684\u8282\u70B9\u5728\u53D6 DSL \u65F6\u5DF2\u5B8C\u6210\u5BFC\u51FA**\uFF1A\u76F4\u63A5\u4F7F\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E25\u7981\u5BF9\u5B83\u4EEC\u518D\u8C03 \`export_image\`\uFF08\u91CD\u590D\u5BFC\u51FA\u540C\u4E00\u4EFD\u5185\u5BB9\uFF09\u3002
|
|
428
454
|
\`export_image\` \u4EC5\u7528\u4E8E DSL \u4E2D\u672A\u584C\u7F29\u7684\u8282\u70B9\uFF08\u4E3B\u52A8\u5207\u56FE\uFF09\u3002
|
|
429
455
|
|
|
@@ -432,7 +458,7 @@ INSTANCE / COMPONENT / COMPONENT_SET \u8282\u70B9\u5E26 \`comp\` \u5B57\u6BB5\uF
|
|
|
432
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
|
|
433
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
|
|
434
460
|
\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
|
|
435
|
-
- \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
|
|
436
462
|
`;
|
|
437
463
|
var GUIDES = [
|
|
438
464
|
{
|
|
@@ -477,7 +503,7 @@ var DESIGN_TO_CODE2 = {
|
|
|
477
503
|
`## \u6267\u884C\u6D41\u7A0B`,
|
|
478
504
|
``,
|
|
479
505
|
`1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
|
|
480
|
-
`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`,
|
|
481
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`,
|
|
482
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`,
|
|
483
509
|
``,
|
|
@@ -565,15 +591,131 @@ var CommandBus = class {
|
|
|
565
591
|
};
|
|
566
592
|
var commandBus = new CommandBus();
|
|
567
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
|
+
|
|
568
707
|
// src/agent/tools/shared.ts
|
|
569
708
|
var PING_TIMEOUT_MS = 800;
|
|
570
709
|
async function run(type, args, signal) {
|
|
571
|
-
const {
|
|
710
|
+
const { fileKey, sessionId, ...rest } = args;
|
|
572
711
|
try {
|
|
573
|
-
const session = await resolveAndPing(
|
|
712
|
+
const session = await resolveAndPing(
|
|
713
|
+
{ fileKey, sessionId },
|
|
714
|
+
signal
|
|
715
|
+
);
|
|
574
716
|
const data = await commandBus.execute(type, rest, { sessionId: session.id, signal });
|
|
575
717
|
sessions.touch(session.id);
|
|
576
|
-
return jsonResult({ sessionId: session.id, data });
|
|
718
|
+
return jsonResult({ sessionId: session.id, fileKey: session.meta.fileKey, data });
|
|
577
719
|
} catch (err) {
|
|
578
720
|
return errorResult(err);
|
|
579
721
|
}
|
|
@@ -602,29 +744,36 @@ async function pingCandidates(candidates, affinityId, signal) {
|
|
|
602
744
|
return null;
|
|
603
745
|
}
|
|
604
746
|
}
|
|
605
|
-
async function resolveAndPing(
|
|
606
|
-
const s =
|
|
607
|
-
|
|
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;
|
|
608
751
|
const candidates = s && !fileKey ? [s] : sessions.candidatesForFileKey(fileKey);
|
|
609
752
|
if (candidates.length === 0) {
|
|
610
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`);
|
|
611
|
-
throw
|
|
754
|
+
throw unknownRouteError(route);
|
|
612
755
|
}
|
|
613
756
|
const winner = await pingCandidates(candidates, fileKey ? sessions.affinityIdOf(fileKey) : void 0, signal);
|
|
614
757
|
if (!winner) {
|
|
615
758
|
for (const c of candidates) sessions.evict(c.id);
|
|
616
759
|
throw new Error(
|
|
617
|
-
`\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`
|
|
618
761
|
);
|
|
619
762
|
}
|
|
620
763
|
if (fileKey) sessions.pinAffinity(fileKey, winner.id);
|
|
621
764
|
return winner;
|
|
622
765
|
}
|
|
623
|
-
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";
|
|
624
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";
|
|
625
|
-
var
|
|
768
|
+
var FILE_KEY_PROP = {
|
|
626
769
|
type: "string",
|
|
627
|
-
|
|
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"
|
|
628
777
|
};
|
|
629
778
|
var CACHE_PROP = {
|
|
630
779
|
type: "string",
|
|
@@ -633,7 +782,8 @@ var CACHE_PROP = {
|
|
|
633
782
|
};
|
|
634
783
|
var GUID_PROP = {
|
|
635
784
|
type: "string",
|
|
636
|
-
|
|
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'
|
|
637
787
|
};
|
|
638
788
|
function jsonResult(data) {
|
|
639
789
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
@@ -648,94 +798,94 @@ function errorResult(err) {
|
|
|
648
798
|
// src/agent/tools/export_image.ts
|
|
649
799
|
var exportImage = {
|
|
650
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,
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
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
|
|
672
825
|
}
|
|
673
826
|
};
|
|
674
827
|
|
|
675
828
|
// src/agent/tools/get_metadata.ts
|
|
676
829
|
var getMetadata = {
|
|
677
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,
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
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
|
|
685
837
|
}
|
|
686
838
|
};
|
|
687
839
|
|
|
688
840
|
// src/agent/tools/get_node_dsl.ts
|
|
689
841
|
var URL_RULE = '1. \u94FE\u63A5\u8F93\u5165\uFF1A\u5FC5\u987B\u4ECE item-id \u67E5\u8BE2\u53C2\u6570\u63D0\u53D6\u8282\u70B9 guid \u5E76 URL \u89E3\u7801\uFF081%3A2 \u2192 "1:2"\uFF09\uFF0C\u628A\u89E3\u7801\u503C\u4F5C\u4E3A guid \u4F20\u5165\uFF1B\u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\u3002\u4EFB\u610F\u57DF\u540D\u90FD\u6709\u6548\uFF1A\u5B98\u65B9 pixso.cn \u6216\u79C1\u6709\u5316\u90E8\u7F72\uFF08\u5982 pixso.ky-tech.com.cn\uFF09\uFF0C\u683C\u5F0F\u5F62\u5982 https://<\u57DF\u540D>/app/design/<\u6587\u4EF6>?item-id=1%3A2\uFF08\u4E5F\u53EF\u80FD\u662F /app/editor/ \u8DEF\u5F84\uFF09\u3002';
|
|
690
|
-
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\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';
|
|
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';
|
|
691
843
|
var getNodeDsl = {
|
|
692
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,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
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
|
|
704
855
|
}
|
|
705
856
|
};
|
|
706
857
|
|
|
707
858
|
// src/agent/tools/get_screenshot.ts
|
|
708
859
|
var getScreenshot = {
|
|
709
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,
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
}
|
|
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
|
|
726
876
|
}
|
|
727
877
|
};
|
|
728
878
|
|
|
729
879
|
// src/agent/tools/get_selected.ts
|
|
730
880
|
var getSelected = {
|
|
731
|
-
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,
|
|
732
|
-
|
|
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 }
|
|
733
883
|
};
|
|
734
884
|
|
|
735
885
|
// src/agent/tools/list_sessions.ts
|
|
736
886
|
var listSessions = {
|
|
737
|
-
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
|
|
738
|
-
|
|
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: {},
|
|
739
889
|
handler: async () => {
|
|
740
890
|
const list = sessions.list();
|
|
741
891
|
if (!list.length) {
|
|
@@ -776,16 +926,48 @@ var TOOL_REGISTRY = {
|
|
|
776
926
|
get_screenshot: getScreenshot,
|
|
777
927
|
list_sessions: listSessions
|
|
778
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
|
+
}
|
|
779
954
|
var TOOLS = Object.fromEntries(
|
|
780
955
|
Object.entries(TOOL_REGISTRY).map(([name, def]) => {
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
|
|
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 } }];
|
|
784
966
|
})
|
|
785
967
|
);
|
|
786
968
|
|
|
787
969
|
// src/agent/mcp.ts
|
|
788
|
-
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`;
|
|
789
971
|
function createMcpServer() {
|
|
790
972
|
const server = new McpServer(
|
|
791
973
|
{ name: HUB_NAME, version: VERSION },
|
|
@@ -841,15 +1023,28 @@ function createMcpServer() {
|
|
|
841
1023
|
// src/agent/sessions.ts
|
|
842
1024
|
var transports = /* @__PURE__ */ new Map();
|
|
843
1025
|
function getMcpTransport(sessionId) {
|
|
844
|
-
|
|
1026
|
+
const entry = transports.get(sessionId);
|
|
1027
|
+
if (entry) entry.lastSeen = Date.now();
|
|
1028
|
+
return entry?.transport;
|
|
845
1029
|
}
|
|
846
1030
|
function registerMcpTransport(sessionId, transport) {
|
|
847
|
-
transports.set(sessionId, transport);
|
|
1031
|
+
transports.set(sessionId, { transport, lastSeen: Date.now() });
|
|
848
1032
|
log(`MCP HTTP session initialized: ${sessionId.slice(0, 8)} (total ${transports.size})`);
|
|
849
1033
|
transport.onclose = () => {
|
|
850
1034
|
if (transport.sessionId) transports.delete(transport.sessionId);
|
|
851
1035
|
};
|
|
852
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();
|
|
853
1048
|
|
|
854
1049
|
// src/agent/http.ts
|
|
855
1050
|
function handleMcp(req, res, url) {
|
|
@@ -905,18 +1100,18 @@ var MCP_ROUTES = [
|
|
|
905
1100
|
// src/install/dist-resolver.ts
|
|
906
1101
|
import { existsSync } from "node:fs";
|
|
907
1102
|
import { readFile } from "node:fs/promises";
|
|
908
|
-
import { join as
|
|
909
|
-
import { fileURLToPath } from "node:url";
|
|
1103
|
+
import { join as join4 } from "node:path";
|
|
1104
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
910
1105
|
function resolvePluginDir() {
|
|
911
1106
|
const candidates = [
|
|
912
|
-
|
|
1107
|
+
fileURLToPath2(new URL("./plugin/", import.meta.url)),
|
|
913
1108
|
// bundle: dist/index.js → dist/plugin/
|
|
914
|
-
|
|
1109
|
+
fileURLToPath2(new URL("../../plugin/dist/", import.meta.url)),
|
|
915
1110
|
// bundle 开发: dist/index.js → packages/plugin/dist/
|
|
916
|
-
|
|
1111
|
+
fileURLToPath2(new URL("../../../plugin/dist/", import.meta.url))
|
|
917
1112
|
// tsx 开发: src/install/ → packages/plugin/dist/
|
|
918
1113
|
];
|
|
919
|
-
return candidates.find((p) => existsSync(
|
|
1114
|
+
return candidates.find((p) => existsSync(join4(p, "manifest.json"))) ?? null;
|
|
920
1115
|
}
|
|
921
1116
|
var resolved = null;
|
|
922
1117
|
function pluginDir() {
|
|
@@ -929,7 +1124,7 @@ function hasPluginDist() {
|
|
|
929
1124
|
async function pluginManifest() {
|
|
930
1125
|
const dir = pluginDir();
|
|
931
1126
|
if (!dir) return null;
|
|
932
|
-
const raw = JSON.parse(await readFile(
|
|
1127
|
+
const raw = JSON.parse(await readFile(join4(dir, "manifest.json"), "utf8"));
|
|
933
1128
|
raw.networkAccess = {
|
|
934
1129
|
allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
|
|
935
1130
|
};
|
|
@@ -939,7 +1134,7 @@ async function readPluginFile(name) {
|
|
|
939
1134
|
const dir = pluginDir();
|
|
940
1135
|
if (!dir) return null;
|
|
941
1136
|
try {
|
|
942
|
-
return await readFile(
|
|
1137
|
+
return await readFile(join4(dir, name));
|
|
943
1138
|
} catch {
|
|
944
1139
|
return null;
|
|
945
1140
|
}
|
|
@@ -1587,14 +1782,22 @@ var PLUGIN_ROUTES = [
|
|
|
1587
1782
|
|
|
1588
1783
|
`);
|
|
1589
1784
|
});
|
|
1785
|
+
const teardown = () => {
|
|
1786
|
+
clearInterval(heartbeat);
|
|
1787
|
+
unsubscribe();
|
|
1788
|
+
sessions.setConnected(session, false);
|
|
1789
|
+
};
|
|
1590
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
|
+
}
|
|
1591
1797
|
sessions.touchSeen(session);
|
|
1592
|
-
res.write(": ping\n\n");
|
|
1593
1798
|
}, 15e3);
|
|
1594
1799
|
req.on("close", () => {
|
|
1595
|
-
|
|
1596
|
-
unsubscribe();
|
|
1597
|
-
sessions.setConnected(session, false);
|
|
1800
|
+
teardown();
|
|
1598
1801
|
log(`SSE channel closed: session ${session.slice(0, 8)}`);
|
|
1599
1802
|
});
|
|
1600
1803
|
}
|
|
@@ -1624,14 +1827,9 @@ var PLUGIN_ROUTES = [
|
|
|
1624
1827
|
];
|
|
1625
1828
|
|
|
1626
1829
|
// src/http.ts
|
|
1627
|
-
var lastActivity = Date.now();
|
|
1628
|
-
function getLastActivity() {
|
|
1629
|
-
return lastActivity;
|
|
1630
|
-
}
|
|
1631
1830
|
var ROUTES = [handleInstall, handleMcp, handlePluginApi];
|
|
1632
1831
|
function startHttpServer() {
|
|
1633
1832
|
const server = createServer((req, res) => {
|
|
1634
|
-
lastActivity = Date.now();
|
|
1635
1833
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1636
1834
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
1637
1835
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
|
|
@@ -1682,19 +1880,11 @@ async function runHub() {
|
|
|
1682
1880
|
log(`\u7AEF\u53E3 ${config.httpPort} \u5DF2\u6709 hub \u5728\u8FD0\u884C\uFF0C\u672C\u8FDB\u7A0B\u9000\u51FA`);
|
|
1683
1881
|
process.exit(0);
|
|
1684
1882
|
}
|
|
1685
|
-
|
|
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");
|
|
1686
1885
|
void cleanOldAssets(config.assetTtlMs);
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
const idle = Date.now() - getLastActivity();
|
|
1690
|
-
const anyPluginOnline = sessions.list().some((s) => s.online);
|
|
1691
|
-
if (idle > config.hubIdleMs && !anyPluginOnline) {
|
|
1692
|
-
log(`\u7A7A\u95F2 ${Math.round(idle / 6e4)} \u5206\u949F\u4E14\u65E0\u5728\u7EBF\u63D2\u4EF6\uFF0Chub \u81EA\u52A8\u9000\u51FA`);
|
|
1693
|
-
process.exit(0);
|
|
1694
|
-
}
|
|
1695
|
-
if (++tick % 60 === 0) void cleanOldAssets(config.assetTtlMs);
|
|
1696
|
-
}, 6e4);
|
|
1697
|
-
watchdog.unref();
|
|
1886
|
+
const sweeper2 = setInterval(() => void cleanOldAssets(config.assetTtlMs), 60 * 6e4);
|
|
1887
|
+
sweeper2.unref();
|
|
1698
1888
|
}
|
|
1699
1889
|
|
|
1700
1890
|
// src/process/stdio-proxy.ts
|
|
@@ -1703,64 +1893,6 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
1703
1893
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1704
1894
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1705
1895
|
import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
1706
|
-
|
|
1707
|
-
// src/process/hub-process.ts
|
|
1708
|
-
import { spawn } from "node:child_process";
|
|
1709
|
-
import { closeSync, openSync } from "node:fs";
|
|
1710
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
1711
|
-
import { join as join4 } from "node:path";
|
|
1712
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1713
|
-
async function probeHub() {
|
|
1714
|
-
try {
|
|
1715
|
-
const res = await fetch(`${hubBaseUrl()}/health`, { signal: AbortSignal.timeout(1500) });
|
|
1716
|
-
if (!res.ok) return "foreign";
|
|
1717
|
-
const body = await res.json();
|
|
1718
|
-
if (body.ok === true && body.name === HUB_NAME) return "alive";
|
|
1719
|
-
if (body.ok === true) return "legacy";
|
|
1720
|
-
return "foreign";
|
|
1721
|
-
} catch {
|
|
1722
|
-
return "down";
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
var PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
|
|
1726
|
-
function hubStateError(state) {
|
|
1727
|
-
if (state === "legacy") {
|
|
1728
|
-
return new Error(
|
|
1729
|
-
`\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}`
|
|
1730
|
-
);
|
|
1731
|
-
}
|
|
1732
|
-
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`);
|
|
1733
|
-
}
|
|
1734
|
-
var HUB_LOG = join4(tmpdir2(), "batter-pixso-mcp-hub.log");
|
|
1735
|
-
function spawnHub() {
|
|
1736
|
-
const out = openSync(HUB_LOG, "a");
|
|
1737
|
-
const entry = process.argv[1] ?? fileURLToPath2(import.meta.url);
|
|
1738
|
-
const SKIP_FLAGS = /* @__PURE__ */ new Set(["--eval", "-e", "--print", "-p", "--watch", "--watch-preserve-output"]);
|
|
1739
|
-
const execArgv = process.execArgv.filter(
|
|
1740
|
-
(a, i, arr) => !SKIP_FLAGS.has(a) && !(i > 0 && SKIP_FLAGS.has(arr[i - 1]))
|
|
1741
|
-
);
|
|
1742
|
-
const child = spawn(process.execPath, [...execArgv, entry, "--hub"], {
|
|
1743
|
-
detached: true,
|
|
1744
|
-
stdio: ["ignore", out, out],
|
|
1745
|
-
windowsHide: true,
|
|
1746
|
-
env: { ...process.env }
|
|
1747
|
-
});
|
|
1748
|
-
child.unref();
|
|
1749
|
-
closeSync(out);
|
|
1750
|
-
log(`hub spawned (pid ${child.pid}), log: ${HUB_LOG}`);
|
|
1751
|
-
}
|
|
1752
|
-
async function waitForHub(timeoutMs = 15e3) {
|
|
1753
|
-
const deadline = Date.now() + timeoutMs;
|
|
1754
|
-
for (; ; ) {
|
|
1755
|
-
const state = await probeHub();
|
|
1756
|
-
if (state === "alive") return;
|
|
1757
|
-
if (state !== "down") throw hubStateError(state);
|
|
1758
|
-
if (Date.now() > deadline) throw new Error(`\u7B49\u5F85 hub \u5C31\u7EEA\u8D85\u65F6\uFF08${timeoutMs / 1e3}s\uFF09`);
|
|
1759
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
// src/process/stdio-proxy.ts
|
|
1764
1896
|
async function startStdioProxy() {
|
|
1765
1897
|
const mcpUrl = `${hubBaseUrl()}/mcp`;
|
|
1766
1898
|
let upstream = null;
|
|
@@ -1774,14 +1906,40 @@ async function startStdioProxy() {
|
|
|
1774
1906
|
}
|
|
1775
1907
|
async function ensureUpstream() {
|
|
1776
1908
|
if (upstream) return upstream;
|
|
1777
|
-
const state = await probeHub();
|
|
1778
|
-
if (state === "
|
|
1779
|
-
|
|
1780
|
-
|
|
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);
|
|
1781
1926
|
upstream = await connectUpstream();
|
|
1782
1927
|
log(`proxy connected to ${mcpUrl}`);
|
|
1783
1928
|
return upstream;
|
|
1784
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
|
+
}
|
|
1785
1943
|
async function callWithRecovery(fn) {
|
|
1786
1944
|
const client = await ensureUpstream();
|
|
1787
1945
|
try {
|
|
@@ -1793,8 +1951,7 @@ async function startStdioProxy() {
|
|
|
1793
1951
|
);
|
|
1794
1952
|
if (alive) throw err;
|
|
1795
1953
|
log("hub \u4F1A\u8BDD\u5931\u8054\uFF0C\u91CD\u5EFA\u540E\u91CD\u8BD5\u2026");
|
|
1796
|
-
|
|
1797
|
-
const rebuilt = await ensureUpstream();
|
|
1954
|
+
const rebuilt = await rebuildUpstream();
|
|
1798
1955
|
return await fn(rebuilt);
|
|
1799
1956
|
}
|
|
1800
1957
|
}
|
|
@@ -1820,6 +1977,14 @@ async function main() {
|
|
|
1820
1977
|
await runHub();
|
|
1821
1978
|
return;
|
|
1822
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
|
+
}
|
|
1823
1988
|
await startStdioProxy();
|
|
1824
1989
|
}
|
|
1825
1990
|
|