batter-pixso-mcp 0.6.0 → 0.8.0-beta.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 CHANGED
@@ -1,233 +1,322 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
2
11
 
3
12
  // src/config.ts
4
13
  import { tmpdir } from "node:os";
5
14
  import { join } from "node:path";
6
- var VERSION = true ? "0.6.0" : "0.0.0-dev";
7
- var HUB_NAME = "batter-pixso-mcp";
8
- var ASSETS_DIR = join(tmpdir(), "batter-pixso-assets");
9
- var config = {
10
- /** 插件通信 HTTP 端口,仅监听 127.0.0.1。与插件 UI 的 DEFAULT_PORT 互为默认对端,改默认需同步 */
11
- httpPort: Number(process.env.HTTP_PORT ?? 3210),
12
- // hub 常驻(不自毁):升级由 proxy 版本比对自动换血,手动停止用 --stop
13
- /** MCP HTTP 会话空闲 TTL:超时清扫防泄漏(proxy 对失效会话有自动重建兜底),默认 30 分钟 */
14
- mcpSessionTtlMs: Number(process.env.MCP_SESSION_TTL_MS ?? 30 * 60 * 1e3),
15
- /** 产物图片保留时长,默认 7 天 */
16
- assetTtlMs: Number(process.env.ASSET_TTL_DAYS ?? 7) * 24 * 60 * 60 * 1e3
17
- };
18
- function hubBaseUrl() {
15
+ function sleep(ms) {
16
+ const { promise, resolve } = Promise.withResolvers();
17
+ setTimeout(resolve, ms);
18
+ return promise;
19
+ }
20
+ function serviceBaseUrl() {
19
21
  return `http://127.0.0.1:${config.httpPort}`;
20
22
  }
23
+ function mcpUrl() {
24
+ return `${serviceBaseUrl()}/mcp`;
25
+ }
26
+ function claudeAddCommand() {
27
+ return `claude mcp add --transport http batter-pixso ${mcpUrl()}`;
28
+ }
21
29
  function log(...args) {
22
30
  console.error("[batter-pixso-mcp]", ...args);
23
31
  }
24
- function withResolvers() {
25
- let resolve;
26
- let reject;
27
- const promise = new Promise((res, rej) => {
28
- resolve = res;
29
- reject = rej;
30
- });
31
- return { promise, resolve, reject };
32
- }
32
+ var VERSION, DEFAULT_PORT, SERVICE_NAME, ASSETS_DIR, config, PING_TIMEOUT_MS, SESSION_ONLINE_WINDOW_MS, COMMAND_TIMEOUT_MS, SSE_HEARTBEAT_MS, MCP_SESSION_SWEEP_MS, PROBE_TIMEOUT_MS, ASSET_SWEEP_INTERVAL_MS;
33
+ var init_config = __esm({
34
+ "src/config.ts"() {
35
+ "use strict";
36
+ VERSION = true ? "0.8.0-beta.0" : "0.0.0-dev";
37
+ DEFAULT_PORT = true ? 3210 : 3210;
38
+ SERVICE_NAME = "batter-pixso-mcp";
39
+ ASSETS_DIR = join(tmpdir(), "batter-pixso-assets");
40
+ config = {
41
+ /** 插件通信 HTTP 端口,仅监听 127.0.0.1。默认值见上方 DEFAULT_PORT(HTTP_PORT 环境变量优先) */
42
+ httpPort: Number(process.env.HTTP_PORT ?? DEFAULT_PORT),
43
+ /** MCP HTTP 会话空闲 TTL:超时清扫防泄漏(客户端重连重新 initialize 即可恢复),默认 30 分钟 */
44
+ mcpSessionTtlMs: Number(process.env.MCP_SESSION_TTL_MS ?? 30 * 60 * 1e3),
45
+ /** 产物图片保留时长,默认 7 天 */
46
+ assetTtlMs: Number(process.env.ASSET_TTL_DAYS ?? 7) * 24 * 60 * 60 * 1e3
47
+ };
48
+ PING_TIMEOUT_MS = 800;
49
+ SESSION_ONLINE_WINDOW_MS = 45e3;
50
+ COMMAND_TIMEOUT_MS = 6e4;
51
+ SSE_HEARTBEAT_MS = 15e3;
52
+ MCP_SESSION_SWEEP_MS = 6e4;
53
+ PROBE_TIMEOUT_MS = 1500;
54
+ ASSET_SWEEP_INTERVAL_MS = 60 * 6e4;
55
+ }
56
+ });
33
57
 
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";
38
- var EXT_BY_FORMAT = {
39
- PNG: ".png",
40
- SVG: ".svg",
41
- JPG: ".jpg",
42
- JPEG: ".jpg",
43
- WEBP: ".webp",
44
- PDF: ".pdf"
45
- // export_image 尊重设计师导出标记,标记可能指定 PDF
46
- };
47
- var dirReady = null;
48
- async function saveAsset(bytes, format) {
49
- dirReady ??= mkdir(ASSETS_DIR, { recursive: true });
50
- await dirReady;
51
- const ext = EXT_BY_FORMAT[format.toUpperCase()] ?? ".bin";
52
- const name = `${createHash("sha1").update(bytes).digest("hex").slice(0, 16)}${ext}`;
53
- await writeFile(join2(ASSETS_DIR, name), bytes);
54
- log(`asset saved: ${name} (${bytes.length} bytes)`);
55
- return name;
56
- }
57
- function substituteAssets(result, assets) {
58
- if (!assets.length) return result;
59
- const nameByKey = new Map(assets.map((a) => [a.key, a.name]));
60
- return JSON.parse(
61
- JSON.stringify(
62
- result,
63
- (_k, v) => typeof v === "string" ? nameByKey.get(v) ?? v : v
64
- )
65
- );
66
- }
67
- async function cleanOldAssets(ttlMs) {
68
- let removed = 0;
69
- const cutoff = Date.now() - ttlMs;
70
- let names;
58
+ // src/cli/service.ts
59
+ var service_exports = {};
60
+ __export(service_exports, {
61
+ PORT_FIX_HINT: () => PORT_FIX_HINT,
62
+ probeService: () => probeService,
63
+ serviceStateError: () => serviceStateError,
64
+ stopRunningService: () => stopRunningService,
65
+ stopService: () => stopService
66
+ });
67
+ async function probeService() {
71
68
  try {
72
- names = await readdir(ASSETS_DIR);
73
- } catch {
74
- return 0;
75
- }
76
- for (const name of names) {
69
+ const res = await fetch(`${serviceBaseUrl()}/health`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
70
+ if (!res.ok) return { state: "foreign" };
71
+ let body;
77
72
  try {
78
- const p = join2(ASSETS_DIR, name);
79
- const s = await stat(p);
80
- if (s.isFile() && s.mtimeMs < cutoff) {
81
- await unlink(p);
82
- removed++;
83
- }
73
+ body = await res.json();
84
74
  } catch {
75
+ return { state: "foreign" };
85
76
  }
86
- }
87
- if (removed > 0) log(`assets cleanup: removed ${removed} expired file(s)`);
88
- return removed;
89
- }
90
-
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 };
77
+ if (body.ok === true && body.name === SERVICE_NAME) return { state: "alive", health: body };
103
78
  return { state: "foreign" };
104
79
  } catch (err) {
105
80
  const hung = err instanceof Error && err.name === "TimeoutError";
106
81
  return { state: hung ? "hung" : "down" };
107
82
  }
108
83
  }
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) {
84
+ function serviceStateError(state) {
111
85
  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}`);
86
+ return new Error(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u7684\u670D\u52A1\u65E0\u54CD\u5E94\uFF08\u53EF\u80FD\u6302\u6B7B\uFF09\u3002${PORT_FIX_HINT}`);
113
87
  }
114
88
  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
89
  }
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) {
90
+ function killServiceByPid(pid) {
119
91
  try {
120
- writeFileSync(PID_FILE, String(pid), "utf8");
121
- process.on("exit", () => {
122
- try {
123
- rmSync(PID_FILE);
124
- } catch {
125
- }
126
- });
92
+ process.kill(pid);
93
+ return true;
127
94
  } catch {
95
+ return false;
96
+ }
97
+ }
98
+ async function stopRunningService() {
99
+ const { state, health } = await probeService();
100
+ if (state !== "alive") return state;
101
+ return killServiceByPid(health.pid) ? "stopped" : "failed";
102
+ }
103
+ async function stopService() {
104
+ const result = await stopRunningService();
105
+ if (result === "down") {
106
+ log(`\u670D\u52A1\u672A\u5728\u8FD0\u884C\uFF08\u7AEF\u53E3 ${config.httpPort} \u65E0\u670D\u52A1\uFF09`);
107
+ } else if (result === "foreign") {
108
+ log(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u4E0D\u662F batter-pixso-mcp\uFF0C\u672A\u5904\u7406`);
109
+ } else if (result === "stopped") {
110
+ log("\u670D\u52A1\u5DF2\u505C\u6B62");
111
+ } else {
112
+ log(`\u505C\u6B62\u5931\u8D25${result === "hung" ? "\uFF08\u670D\u52A1\u65E0\u54CD\u5E94\uFF0C\u53EF\u80FD\u6302\u6B7B\uFF09" : ""}\u3002${PORT_FIX_HINT}`);
113
+ }
114
+ }
115
+ var PORT_FIX_HINT;
116
+ var init_service = __esm({
117
+ "src/cli/service.ts"() {
118
+ "use strict";
119
+ init_config();
120
+ PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
128
121
  }
122
+ });
123
+
124
+ // src/install/dist-resolver.ts
125
+ import { existsSync } from "node:fs";
126
+ import { readFile } from "node:fs/promises";
127
+ import { join as join2 } from "node:path";
128
+ import { fileURLToPath } from "node:url";
129
+ function resolvePluginDir() {
130
+ const candidates = [
131
+ fileURLToPath(new URL("./plugin/", import.meta.url)),
132
+ // bundle: dist/index.js → dist/plugin/
133
+ fileURLToPath(new URL("../../plugin/dist/", import.meta.url)),
134
+ // bundle 开发: dist/index.js → packages/plugin/dist/
135
+ fileURLToPath(new URL("../../../plugin/dist/", import.meta.url))
136
+ // tsx 开发: src/install/ → packages/plugin/dist/
137
+ ];
138
+ return candidates.find((p) => existsSync(join2(p, "manifest.json"))) ?? null;
139
+ }
140
+ function pluginDistDir() {
141
+ resolved ??= resolvePluginDir();
142
+ return resolved;
129
143
  }
130
- function readPidFile() {
144
+ function hasPluginDist() {
145
+ return pluginDistDir() !== null;
146
+ }
147
+ async function pluginManifest() {
148
+ const dir = pluginDistDir();
149
+ if (!dir) return null;
150
+ const raw = JSON.parse(await readFile(join2(dir, "manifest.json"), "utf8"));
151
+ raw.networkAccess = {
152
+ allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
153
+ };
154
+ return Buffer.from(JSON.stringify(raw, null, 2));
155
+ }
156
+ async function readPluginFile(name) {
157
+ const dir = pluginDistDir();
158
+ if (!dir) return null;
131
159
  try {
132
- const pid = Number(readFileSync(PID_FILE, "utf8").trim());
133
- return Number.isInteger(pid) && pid > 0 ? pid : null;
160
+ return await readFile(join2(dir, name));
134
161
  } catch {
135
162
  return null;
136
163
  }
137
164
  }
138
- function removePidFile() {
139
- try {
140
- rmSync(PID_FILE);
141
- } catch {
165
+ var resolved;
166
+ var init_dist_resolver = __esm({
167
+ "src/install/dist-resolver.ts"() {
168
+ "use strict";
169
+ init_config();
170
+ resolved = null;
171
+ }
172
+ });
173
+
174
+ // src/cli/setup.ts
175
+ var setup_exports = {};
176
+ __export(setup_exports, {
177
+ setupAutostart: () => setupAutostart,
178
+ uninstallAutostart: () => uninstallAutostart
179
+ });
180
+ import { spawn, spawnSync } from "node:child_process";
181
+ import { existsSync as existsSync2, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
182
+ import { copyFile, cp } from "node:fs/promises";
183
+ import { dirname, join as join3 } from "node:path";
184
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
185
+ function serviceCmd() {
186
+ return [
187
+ "@echo off",
188
+ `set HTTP_PORT=${config.httpPort}`,
189
+ `"%~dp0node.exe" "%~dp0index.js" >> "%TEMP%\\${SERVICE_LOG_NAME}" 2>&1`
190
+ ].join("\r\n");
191
+ }
192
+ async function withTransientLockRetry(what, action) {
193
+ for (let i = 0; ; i++) {
194
+ try {
195
+ await action();
196
+ return;
197
+ } catch (err) {
198
+ const code = err.code ?? "";
199
+ if (!TRANSIENT_LOCK_ERRORS.has(code) || i >= 4) {
200
+ throw new Error(
201
+ `\u62F7\u8D1D\u5931\u8D25 ${what}\uFF08${code || err}\uFF09\uFF1A\u6587\u4EF6\u88AB\u5360\u7528\u2014\u2014\u82E5\u81EA\u542F\u5B9E\u4F8B\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148 --stop\uFF1B\u5426\u5219\u591A\u4E3A\u6740\u6BD2\u8F6F\u4EF6\u626B\u63CF\u9501\u5B9A\uFF0C\u7A0D\u5019\u91CD\u8BD5`,
202
+ { cause: err }
203
+ );
204
+ }
205
+ await sleep(500);
206
+ }
142
207
  }
143
208
  }
144
- function killHubByPid(pid) {
209
+ async function copyWithRetry(src, dest) {
210
+ await withTransientLockRetry(dest, () => copyFile(src, dest));
211
+ }
212
+ async function materializeAutostart() {
213
+ if (!DIR) throw new Error("\u65E0\u6CD5\u5B9A\u4F4D\u7269\u5316\u76EE\u5F55\uFF1A\u73AF\u5883\u53D8\u91CF LOCALAPPDATA \u672A\u8BBE\u7F6E");
214
+ const bundle = fileURLToPath2(import.meta.url);
215
+ if (!bundle.endsWith(".js")) {
216
+ throw new Error("--setup \u4EC5\u652F\u6301\u53D1\u5E03\u5305\u5F62\u6001\uFF08dist bundle\uFF09\uFF1Btsx \u5F00\u53D1\u6001\u8BF7\u5148 npm run build \u540E\u7528 dist \u5165\u53E3\u6267\u884C");
217
+ }
218
+ const standalone = join3(dirname(bundle), "..", "standalone", "index.js");
219
+ if (!existsSync2(standalone)) {
220
+ throw new Error("\u672A\u627E\u5230 standalone bundle\uFF08standalone/index.js\uFF09\uFF1B\u4ED3\u5E93\u5185\u8BF7\u5148\u6267\u884C npm run pack:standalone -w batter-pixso-mcp");
221
+ }
222
+ const fresh = autostartVersion() === VERSION && existsSync2(join3(DIR, "node.exe")) && existsSync2(join3(DIR, "index.js")) && existsSync2(join3(DIR, "plugin", "manifest.json"));
223
+ mkdirSync(DIR, { recursive: true });
224
+ if (!fresh) {
225
+ const plugin = pluginDistDir();
226
+ await Promise.all([
227
+ copyWithRetry(process.execPath, join3(DIR, "node.exe")),
228
+ copyWithRetry(standalone, join3(DIR, "index.js")),
229
+ ...plugin ? [withTransientLockRetry(join3(DIR, "plugin"), () => cp(plugin, join3(DIR, "plugin"), { recursive: true }))] : []
230
+ ]);
231
+ }
232
+ writeFileSync(join3(DIR, "service.cmd"), serviceCmd(), "utf8");
233
+ writeFileSync(join3(DIR, "run.vbs"), RUN_VBS, "utf8");
234
+ writeFileSync(join3(DIR, "version.txt"), VERSION, "utf8");
235
+ }
236
+ function autostartVersion() {
237
+ if (!DIR) return null;
145
238
  try {
146
- process.kill(pid);
147
- return true;
239
+ return readFileSync(join3(DIR, "version.txt"), "utf8").trim() || null;
148
240
  } catch {
149
- return false;
241
+ return null;
150
242
  }
151
243
  }
152
- function killHubByPidFile() {
153
- const pid = readPidFile();
154
- if (!pid) return false;
155
- if (!killHubByPid(pid)) return false;
156
- removePidFile();
157
- return true;
244
+ async function setupAutostart() {
245
+ if (process.platform !== "win32") {
246
+ throw new Error("--setup \u5F53\u524D\u4EC5\u652F\u6301 Windows\uFF1Bmac/Linux \u8BF7\u7528\u7CFB\u7EDF\u5F00\u673A\u542F\u52A8\u673A\u5236\u914D\u7F6E npx batter-pixso-mcp");
247
+ }
248
+ await materializeAutostart();
249
+ const target = `wscript.exe "${join3(DIR, "run.vbs")}"`;
250
+ const r = spawnSync("reg", ["add", RUN_KEY, "/v", RUN_VALUE, "/t", "REG_SZ", "/d", target, "/f"]);
251
+ if (r.status !== 0) {
252
+ throw new Error(`\u6CE8\u518C\u5F00\u673A\u81EA\u542F\u5931\u8D25\uFF1A${r.stderr?.toString().trim() || `reg exit ${r.status}`}`);
253
+ }
254
+ log(`\u5F00\u673A\u81EA\u542F\u5DF2\u6CE8\u518C\uFF1A\u767B\u5F55\u540E\u9759\u9ED8\u542F\u52A8\u670D\u52A1\uFF08\u526F\u672C\u5728 ${DIR}\uFF0C\u4E0E nvm/npm \u72B6\u6001\u65E0\u5173\uFF09`);
255
+ await startHiddenNow();
158
256
  }
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));
257
+ async function startHiddenNow() {
258
+ const { state, health } = await probeService();
259
+ if (state === "alive") {
260
+ if (health?.version === VERSION) {
261
+ log("\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E14\u7248\u672C\u4E00\u81F4\uFF0C\u65E0\u9700\u91CD\u590D\u62C9\u8D77");
262
+ return;
263
+ }
264
+ throw new Error(`\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E14\u7248\u672C\u4E0D\u7B26\uFF08\u8FD0\u884C ${health?.version} / \u5F53\u524D ${VERSION}\uFF09\u2014\u2014\u5148\u6267\u884C --stop \u518D\u91CD\u8DD1 --setup`);
186
265
  }
187
- }
188
- async function waitForHubDown(timeoutMs = 5e3) {
189
- const deadline = Date.now() + timeoutMs;
266
+ if (state === "hung" || state === "foreign") {
267
+ throw serviceStateError(state);
268
+ }
269
+ spawn("wscript.exe", [join3(DIR, "run.vbs")], { detached: true, stdio: "ignore" }).unref();
270
+ const deadline = Date.now() + 1e4;
190
271
  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));
272
+ await sleep(300);
273
+ if ((await probeService()).state === "alive") {
274
+ log(`\u670D\u52A1\u5DF2\u5728\u540E\u53F0\u542F\u52A8\uFF08\u65E5\u5FD7\uFF1A%TEMP%\\${SERVICE_LOG_NAME}\uFF09`);
275
+ return;
276
+ }
277
+ if (Date.now() > deadline) {
278
+ throw new Error(`\u540E\u53F0\u670D\u52A1 10s \u5185\u672A\u5C31\u7EEA\u2014\u2014\u67E5\u770B\u65E5\u5FD7 %TEMP%\\${SERVICE_LOG_NAME}`);
279
+ }
195
280
  }
196
281
  }
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;
282
+ async function uninstallAutostart() {
283
+ if (process.platform === "win32") {
284
+ spawnSync("reg", ["delete", RUN_KEY, "/v", RUN_VALUE, "/f"]);
285
+ log("\u5DF2\u79FB\u9664\u5F00\u673A\u81EA\u542F\uFF08\u82E5\u66FE\u6CE8\u518C\uFF09");
202
286
  }
203
- if (state === "foreign") {
204
- log(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u4E0D\u662F batter-pixso-mcp\uFF0C\u672A\u5904\u7406`);
205
- return;
287
+ const result = await stopRunningService();
288
+ if (result === "failed" || result === "hung") {
289
+ log(`\u670D\u52A1\u505C\u6B62\u5931\u8D25\uFF0C\u7269\u5316\u76EE\u5F55\u53EF\u80FD\u5220\u4E0D\u51C0\u3002${PORT_FIX_HINT}`);
206
290
  }
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;
291
+ if (DIR && existsSync2(DIR)) {
292
+ try {
293
+ rmSync(DIR, { recursive: true, force: true, maxRetries: 4, retryDelay: 500 });
294
+ } catch {
211
295
  }
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;
214
- }
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}`);
296
+ log(existsSync2(DIR) ? `\u76EE\u5F55\u5220\u9664\u5931\u8D25\uFF0C\u53EF\u624B\u52A8\u5220\u9664\uFF1A${DIR}` : `\u5DF2\u5220\u9664\u7269\u5316\u76EE\u5F55 ${DIR}`);
221
297
  }
298
+ log("\u5378\u8F7D\u5B8C\u6210\uFF08npm \u5168\u5C40/npx \u7F13\u5B58\u4E2D\u7684\u5305\u5982\u9700\u6E05\u7406\u8BF7\u81EA\u884C npm uninstall\uFF09");
222
299
  }
223
-
224
- // src/http.ts
225
- import { createServer } from "node:http";
226
-
227
- // src/agent/http.ts
228
- import { randomUUID as randomUUID2 } from "node:crypto";
229
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
230
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
300
+ var DIR, RUN_KEY, RUN_VALUE, SERVICE_LOG_NAME, RUN_VBS, TRANSIENT_LOCK_ERRORS;
301
+ var init_setup = __esm({
302
+ "src/cli/setup.ts"() {
303
+ "use strict";
304
+ init_config();
305
+ init_dist_resolver();
306
+ init_service();
307
+ DIR = process.env.LOCALAPPDATA ? join3(process.env.LOCALAPPDATA, "batter-pixso-mcp") : null;
308
+ RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
309
+ RUN_VALUE = "BatterPixsoMcp";
310
+ SERVICE_LOG_NAME = "batter-pixso-mcp-service.log";
311
+ RUN_VBS = [
312
+ "Set fso = CreateObject(" + JSON.stringify("Scripting.FileSystemObject") + ")",
313
+ "dir = fso.GetParentFolderName(WScript.ScriptFullName)",
314
+ // 路径由脚本自身位置推导,不写死绝对路径(用户名含中文/空格时 VBS 字面量会翻车)
315
+ 'CreateObject("Wscript.Shell").Run """" & dir & "\\service.cmd""", 0, False'
316
+ ].join("\r\n");
317
+ TRANSIENT_LOCK_ERRORS = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
318
+ }
319
+ });
231
320
 
232
321
  // src/http-body.ts
233
322
  function sendJson(res, status, body) {
@@ -239,8 +328,6 @@ async function readBody(req) {
239
328
  for await (const chunk of req) chunks.push(chunk);
240
329
  return Buffer.concat(chunks);
241
330
  }
242
- var JsonBodyError = class extends Error {
243
- };
244
331
  async function readJsonBody(req) {
245
332
  const buf = await readBody(req);
246
333
  try {
@@ -249,7 +336,6 @@ async function readJsonBody(req) {
249
336
  throw new JsonBodyError("invalid json");
250
337
  }
251
338
  }
252
- var segsCache = /* @__PURE__ */ new Map();
253
339
  function segsOf(template) {
254
340
  let segs = segsCache.get(template);
255
341
  if (!segs) {
@@ -284,7 +370,7 @@ async function dispatchRoute(routes, req, res, url) {
284
370
  if (err instanceof JsonBodyError) {
285
371
  if (!res.headersSent) sendJson(res, 400, { error: "invalid json" });
286
372
  } else {
287
- console.error("[batter-pixso-mcp] route handler error:", err);
373
+ log("route handler error:", err);
288
374
  if (!res.headersSent) sendJson(res, 500, { error: "internal error" });
289
375
  }
290
376
  }
@@ -292,26 +378,32 @@ async function dispatchRoute(routes, req, res, url) {
292
378
  }
293
379
  return false;
294
380
  }
295
-
296
- // src/agent/mcp.ts
297
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
298
- import {
299
- CallToolRequestSchema,
300
- GetPromptRequestSchema,
301
- ListPromptsRequestSchema,
302
- ListResourceTemplatesRequestSchema,
303
- ListToolsRequestSchema
304
- } from "@modelcontextprotocol/sdk/types.js";
381
+ var JsonBodyError, segsCache;
382
+ var init_http_body = __esm({
383
+ "src/http-body.ts"() {
384
+ "use strict";
385
+ init_config();
386
+ JsonBodyError = class extends Error {
387
+ };
388
+ segsCache = /* @__PURE__ */ new Map();
389
+ }
390
+ });
305
391
 
306
392
  // src/agent/guides.ts
307
- var INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
393
+ var INTRO, DESIGN_TO_CODE, GUIDES;
394
+ var init_guides = __esm({
395
+ "src/agent/guides.ts"() {
396
+ "use strict";
397
+ init_config();
398
+ INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
308
399
 
309
400
  ## \u5DE5\u5177\u603B\u89C8
310
401
 
311
402
  | \u5DE5\u5177 | \u7528\u9014 |
312
403
  | --- | --- |
313
- | \`get_selected\` | \u8BFB\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\u3001\u5C3A\u5BF8\uFF09 |
404
+ | \`get_selected\` | \u8BFB\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\uFF09 |
314
405
  | \`get_metadata\` | \u5168\u6811\u7ED3\u6784\u9AA8\u67B6\uFF08\u4EC5 id/name/type/kids\uFF0C\u65E0\u6837\u5F0F/\u5750\u6807/\u8D44\u6E90\uFF09\uFF0C\u9996\u6B21\u5B9A\u4F4D\u7528 |
406
+ | \`get_page_metadata\` | \u5F53\u524D\u9875\u9876\u5C42\u9AA8\u67B6\uFF08\u9875\u9762 id/name/depth + \u9876\u5C42\u56FE\u5C42\u5217\u8868\uFF09\uFF0C\u65E0 guid \u65F6\u7684\u51B7\u542F\u52A8\u5165\u53E3 |
315
407
  | \`get_node_dsl\` | \u751F\u6210\u8282\u70B9\u7684\u5F00\u53D1\u5411\u7CBE\u7B80 DSL\uFF08\u7ED3\u6784 + \u6837\u5F0F + \u8D44\u6E90\u6587\u4EF6\u540D\uFF09\uFF0C\u8BBE\u8BA1\u8F6C\u4EE3\u7801\u7684\u6838\u5FC3\u5165\u53E3 |
316
408
  | \`get_screenshot\` | \u5BFC\u51FA\u8282\u70B9\u622A\u56FE\uFF0C\u8FD4\u56DE\u56FE\u7247\u6587\u4EF6\u540D\uFF08\u672C\u673A\u8DEF\u5F84\u76F4\u8BFB\uFF09\uFF0C\u7528\u4E8E\u89C6\u89C9\u53C2\u7167/\u6821\u9A8C |
317
409
  | \`export_image\` | \u6309 guid \u5BFC\u51FA\u56FE\u7247\u7D20\u6750\uFF08\u5207\u56FE\uFF09\uFF1A\u8282\u70B9\u5E26\u5BFC\u51FA\u6807\u8BB0\u65F6\u6309\u8BBE\u8BA1\u5E08\u8BBE\u7F6E\u9010\u4EFD\u5BFC\u51FA\uFF0C\u65E0\u6807\u8BB0\u6309\u53C2\u6570\u515C\u5E95 |
@@ -327,7 +419,7 @@ var INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
327
419
 
328
420
  \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
421
  \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
422
+ \u4F5C\u4E3A\u8DEF\u7531\u53C2\u6570\u4F20\u5165\uFF08\u8DE8\u91CD\u5F00\u7A33\u5B9A\uFF0C\u4F18\u5148\u4E8E \`sessionId\`\uFF09\uFF1B\u4EC5 \`get_selected\`/\`get_page_metadata\`/\`list_sessions\` \u53EF\u7F3A\u7701\u8DEF\u7531\u53C2\u6570\u505A\u63A2\u67E5\u3002
331
423
 
332
424
  **\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
425
  \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
@@ -336,7 +428,7 @@ var INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
336
428
 
337
429
  \u8BE6\u7EC6\u5DE5\u4F5C\u6D41\u89C1\u8D44\u6E90 \`batter://guides/design-to-code\`\u3002
338
430
  `;
339
- var DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
431
+ DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
340
432
 
341
433
  ## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011
342
434
 
@@ -354,6 +446,11 @@ var DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
354
446
  \`https://pixso.ky-tech.com.cn/app/design/<\u6587\u4EF6>?item-id=1%3A2\`\uFF08\u4E5F\u53EF\u80FD\u662F \`/app/editor/\` \u8DEF\u5F84\uFF09\u3002
355
447
  \u4ECE \`item-id\` \u67E5\u8BE2\u53C2\u6570\u63D0\u53D6 guid \u5E76 URL \u89E3\u7801\uFF08\`1%3A2\` \u2192 \`"1:2"\`\uFF09\u540E\u4F5C\u4E3A \`guid\` \u4F20\u5165\u3002
356
448
  \u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\uFF1B\u65E0\u8BBA\u57DF\u540D\u662F\u5426\u8BA4\u8BC6\uFF0C\u90FD\u6309\u6B64\u89C4\u5219\u5904\u7406\u3002
449
+ 3. **DSL \u662F\u7ED3\u6784\u7684\u552F\u4E00\u4E8B\u5B9E\u6E90**\uFF1A\u4F4D\u7F6E/\u5C3A\u5BF8/\u5C42\u7EA7/\u95F4\u8DDD/\u989C\u8272\u4E00\u5F8B\u4EE5 DSL \u5B57\u6BB5\u4E3A\u51C6\u3002\u622A\u56FE\u53EA\u7528\u4E8E\u89C6\u89C9\u6821\u9A8C\u4E0E\u610F\u56FE\u53C2\u7167\u2014\u2014
450
+ \u7981\u6B62\u526A\u88C1/\u653E\u5927\u622A\u56FE\u91CF\u53D6\u50CF\u7D20\u6765\u63A8\u6D4B\u5E03\u5C40\u6216\u8BA1\u7B97\u4F4D\u7F6E\uFF1B\u7981\u6B62\u8131\u79BB DSL \u51ED\u622A\u56FE\u786C\u7F16\u7801\u7ED3\u6784\u3002
451
+ 4. **\u8D44\u6E90\u5B8C\u6574\u6027**\uFF1A\u8282\u70B9\u5E26 \`img\` \u65F6\u5FC5\u987B\u5F15\u7528\u8BE5\u5BFC\u51FA\u6587\u4EF6\uFF0C\u7981\u6B62\u6309 DSL \u7684\u5C3A\u5BF8/\u989C\u8272\u624B\u5199 SVG/Canvas/CSS \u5F62\u72B6\u53BB"\u8FD1\u4F3C"\u5B83\uFF1B
452
+ \u5BFC\u51FA\u4EA7\u7269\u5373\u4EA4\u4ED8\u7D20\u6750\u672C\u4F53\u2014\u2014\u7981\u6B62\u7F29\u653E/\u538B\u7F29/\u8F6C\u683C\u5F0F/\u88C1\u5207/\u5408\u6210\u7B49\u4EFB\u4F55\u4E8C\u6B21\u5904\u7406\uFF0C
453
+ \u9700\u8981\u4E0D\u540C\u5C3A\u5BF8\u3001\u500D\u6570\u6216\u683C\u5F0F\u65F6\u6309\u8282\u70B9 id \u91CD\u8C03 \`export_image\` \u6307\u5B9A \`scale\`/\`format\` \u56DE\u6E90\u83B7\u53D6\u3002
357
454
 
358
455
  ## \u63A8\u8350\u6B65\u9AA4
359
456
 
@@ -368,6 +465,8 @@ var DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
368
465
  **\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)\`
369
466
  \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
370
467
  \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
468
+ **\u8FD8\u6CA1\u6709\u4EFB\u4F55 guid**\uFF08\u7528\u6237\u6CA1\u7ED9\u94FE\u63A5\u4E5F\u6CA1\u9009\u4E2D\u56FE\u5C42\uFF09\uFF1A\u5148 \`get_page_metadata()\` \u62FF\u5F53\u524D\u9875\u9876\u5C42\u9AA8\u67B6
469
+ \uFF08\u9875\u9762 id/name/depth + \u9876\u5C42\u56FE\u5C42 \`kids\`\uFF09\uFF0C\u628A\u76EE\u6807\u56FE\u5C42\u7684 \`id\` \u4F5C\u4E3A guid \u7EE7\u7EED\u4E0A\u8FF0\u6D41\u7A0B\uFF1B\u8FD4\u56DE\u7684\u9875\u9762 id \u4E0D\u53EF\u56DE\u4F20\u8282\u70B9\u7C7B\u5DE5\u5177\u3002
371
470
  3. **\u53D6\u89C6\u89C9\u53C2\u7167**\uFF1A\`get_screenshot(guid)\` \u62FF\u622A\u56FE\u6587\u4EF6\u540D\uFF0C\u6309\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\u62FC\u8DEF\u5F84\u540E\u7528 Read \u76F4\u8BFB\uFF0C\u4E0E DSL \u5BF9\u7167\u7406\u89E3\u89C6\u89C9\u5C42\u7EA7\u3002
372
471
  4. **\u751F\u6210\u4EE3\u7801**\uFF1A\u6309\u4E0B\u6587 DSL \u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002
373
472
  5. **\u6821\u9A8C**\uFF1A\u751F\u6210\u540E\u518D\u53D6\u4E00\u6B21\u622A\u56FE\u5BF9\u6BD4\uFF0C\u6709\u504F\u5DEE\u5C31\u4FEE\u6B63\u540E\u518D\u6BD4\u4E00\u8F6E\u3002
@@ -416,7 +515,7 @@ DSL \u662F\u5355\u884C\u7D27\u51D1 JSON\uFF1A\u77ED key\u3001\u4F4D\u7F6E\u6570\
416
515
  }
417
516
  \`\`\`
418
517
 
419
- **\u6DF1\u5EA6\u63A7\u5236**\uFF1A\`get_node_dsl\` \u7684 \`maxDepth\` \u53C2\u6570\u9650\u5236\u4E0B\u63A2\u5C42\u7EA7\uFF08\u6839\u4E3A\u7B2C 0 \u5C42\uFF0C\u7F3A\u7701\u4E0D\u9650\uFF09\u3002
518
+ **\u6DF1\u5EA6\u63A7\u5236**\uFF1A\`get_node_dsl\` \u7684 \`maxDepth\` \u53C2\u6570\u9650\u5236\u4E0B\u63A2\u5C42\u7EA7\uFF08\u6839\u4E3A\u7B2C 0 \u5C42\uFF0C\u7F3A\u7701 4 \u5C42\uFF0C\u4F20 -1 \u4E0D\u9650\uFF09\u3002
420
519
  \u5927\u6811\u5206\u6BB5\u7B56\u7565\uFF1A\u5148 \`maxDepth: 0\` \u62FF\u6839\u8282\u70B9\uFF08\u542B \`depth\` \u603B\u6DF1\u5EA6\uFF09\u9884\u5224\u89C4\u6A21\uFF0C\u518D\u6309\u9700\u5BF9\u5E26 \`more\` \u6807\u8BB0\u7684\u5B50\u6811\u7528\u5176 \`id\` \u5355\u72EC\u4E0B\u94BB\u3002
421
520
  \u622A\u65AD\u8282\u70B9\u5E26 \`kidsSummary\`\uFF08\u76F4\u63A5\u5B50\u8282\u70B9\u7684 \`{id, name?, type}\` \u6D45\u9884\u89C8\uFF09\u2014\u2014\u636E\u6B64\u5224\u65AD\u54EA\u4E9B\u5206\u652F\u503C\u5F97\u5C55\u5F00\uFF0C\u4E0D\u5FC5\u76F2\u4E0B\u94BB\u6BCF\u4E2A \`more\` \u8282\u70B9\u3002
422
521
 
@@ -427,11 +526,10 @@ DSL \u662F\u5355\u884C\u7D27\u51D1 JSON\uFF1A\u77ED key\u3001\u4F4D\u7F6E\u6570\
427
526
 
428
527
  ## \u9519\u8BEF\u6062\u590D
429
528
 
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
529
+ - **\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\uFF1B\u53EA\u8981\u63A2\u7D22\u5F53\u524D\u9875\u5185\u5BB9\u65F6\u7528 \`get_page_metadata\` \u81EA\u4E3E\uFF0C\u65E0\u9700 guid\u3002
431
530
  - **\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
432
531
  - **\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
433
532
  - **\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
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
435
533
 
436
534
  ## \u7EC4\u4EF6\u5173\u8054\uFF08comp \u5B57\u6BB5\uFF09
437
535
 
@@ -460,252 +558,272 @@ INSTANCE / COMPONENT / COMPONENT_SET \u8282\u70B9\u5E26 \`comp\` \u5B57\u6BB5\uF
460
558
  \u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
461
559
  - \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
462
560
  `;
463
- var GUIDES = [
464
- {
465
- name: "intro",
466
- uri: "batter://guides/intro",
467
- description: "batter-pixso-mcp \u6982\u89C8\uFF1A\u5DE5\u5177\u6E05\u5355\u3001\u94FE\u63A5\u63D0\u53D6 item-id\u3001\u591A\u6587\u4EF6\u4F1A\u8BDD\u8DEF\u7531\u3002",
468
- text: INTRO
469
- },
470
- {
471
- name: "design-to-code",
472
- uri: "batter://guides/design-to-code",
473
- description: "\u8BBE\u8BA1\u8F6C\u4EE3\u7801\uFF08\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F\uFF09\u5DE5\u4F5C\u6D41\uFF1A\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u6309\u672C\u673A\u8DEF\u5F84\u53D6\u7528\u3001\u94FE\u63A5\u63D0\u53D6 item-id\uFF09\u3001DSL \u5B57\u6BB5\u8BED\u4E49\u3001\u89C6\u89C9\u6821\u9A8C\u3002",
474
- text: DESIGN_TO_CODE
561
+ GUIDES = [
562
+ {
563
+ name: "intro",
564
+ uri: "batter://guides/intro",
565
+ description: "batter-pixso-mcp \u6982\u89C8\uFF1A\u5DE5\u5177\u6E05\u5355\u3001\u94FE\u63A5\u63D0\u53D6 item-id\u3001\u591A\u6587\u4EF6\u4F1A\u8BDD\u8DEF\u7531\u3002",
566
+ text: INTRO
567
+ },
568
+ {
569
+ name: "design-to-code",
570
+ uri: "batter://guides/design-to-code",
571
+ description: "\u8BBE\u8BA1\u8F6C\u4EE3\u7801\uFF08\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F\uFF09\u5DE5\u4F5C\u6D41\uFF1A\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u6309\u672C\u673A\u8DEF\u5F84\u53D6\u7528\u3001\u94FE\u63A5\u63D0\u53D6 item-id\uFF09\u3001DSL \u5B57\u6BB5\u8BED\u4E49\u3001\u89C6\u89C9\u6821\u9A8C\u3002",
572
+ text: DESIGN_TO_CODE
573
+ }
574
+ ];
475
575
  }
476
- ];
576
+ });
477
577
 
478
578
  // src/agent/prompts.ts
479
- var DESIGN_TO_CODE2 = {
480
- name: "design-to-code",
481
- description: "\u8BBE\u8BA1\u7A3F\u8F6C\u524D\u7AEF\u4EE3\u7801\uFF08\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F\uFF09\uFF1A\u4ECE Pixso \u94FE\u63A5\u6216\u5F53\u524D\u9009\u533A\u53D6 DSL + \u622A\u56FE \u2192 \u751F\u6210 \u2192 \u622A\u56FE\u6821\u9A8C\u3002",
482
- arguments: [
483
- {
484
- name: "url",
485
- description: "Pixso \u94FE\u63A5\uFF08\u542B item-id \u67E5\u8BE2\u53C2\u6570\uFF0C\u4EFB\u610F\u57DF\u540D\uFF1A\u5B98\u65B9 pixso.cn \u6216\u79C1\u6709\u5316\u90E8\u7F72\uFF09\u3002\u7559\u7A7A\u5219\u4F7F\u7528\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\u3002",
486
- required: false
487
- }
488
- ],
489
- render: (args) => {
490
- const targetLine = args.url?.trim() ? `\u76EE\u6807\u8282\u70B9\u6765\u81EA\u94FE\u63A5\uFF1A${args.url.trim()}\u3002\u5FC5\u987B\u4ECE\u67E5\u8BE2\u53C2\u6570 \`item-id\` \u63D0\u53D6\u8282\u70B9 guid \u5E76 URL \u89E3\u7801\uFF08\`1%3A2\` \u2192 \`"1:2"\`\uFF09\u540E\u4F5C\u4E3A guid \u4F20\u5165\uFF1B\u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\u3002\u65E0\u8BBA\u57DF\u540D\u662F\u5426\u8BA4\u8BC6\uFF0C\u90FD\u6309\u6B64\u89C4\u5219\u5904\u7406\u3002` : `\u76EE\u6807\u8282\u70B9\u4E3A\u5F53\u524D\u9009\u533A\uFF1A\u8C03 \`get_selected\` \u62FF\u9009\u4E2D\u56FE\u5C42\u3002\u7981\u6B62\u9690\u5F0F\u4F7F\u7528\u5176 guid\u2014\u2014\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\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`;
491
- return [
492
- {
493
- role: "user",
494
- content: {
495
- type: "text",
496
- text: [
497
- `\u628A Pixso \u8BBE\u8BA1\u7A3F\u8FD8\u539F\u4E3A\u524D\u7AEF\u4EE3\u7801\u3002`,
498
- ``,
499
- `## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011`,
500
- ``,
501
- `**\u5199\u4EE3\u7801\u524D\u5FC5\u987B\u5148\u8BFB**\u8D44\u6E90 \`batter://guides/design-to-code\` \u5E76\u9075\u5B88\u5176\u5168\u90E8\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\uFF1B\u603B\u89C8\u89C1 \`batter://guides/intro\`\u3002`,
502
- ``,
503
- `## \u6267\u884C\u6D41\u7A0B`,
504
- ``,
505
- `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
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`,
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`,
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`,
509
- ``,
510
- `**\u6700\u540E\u91CD\u7533**\uFF1A\u4EA4\u4ED8\u4EE3\u7801\u4E2D\u4E0D\u5141\u8BB8\u51FA\u73B0\u4EA7\u7269\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u5168\u90E8\u56FE\u7247\u5FC5\u987B\u662F\u5DF2\u590D\u5236\u5230\u9879\u76EE\u7684\u672C\u5730\u76F8\u5BF9\u8DEF\u5F84\uFF08\u7EC6\u5219\u89C1\u6307\u5357\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\uFF09\u3002`
511
- ].join("\n")
579
+ var DESIGN_TO_CODE2, PROMPTS;
580
+ var init_prompts = __esm({
581
+ "src/agent/prompts.ts"() {
582
+ "use strict";
583
+ DESIGN_TO_CODE2 = {
584
+ name: "design-to-code",
585
+ description: "\u8BBE\u8BA1\u7A3F\u8F6C\u524D\u7AEF\u4EE3\u7801\uFF08\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F\uFF09\uFF1A\u4ECE Pixso \u94FE\u63A5\u6216\u5F53\u524D\u9009\u533A\u53D6 DSL + \u622A\u56FE \u2192 \u751F\u6210 \u2192 \u622A\u56FE\u6821\u9A8C\u3002",
586
+ arguments: [
587
+ {
588
+ name: "url",
589
+ description: "Pixso \u94FE\u63A5\uFF08\u542B item-id \u67E5\u8BE2\u53C2\u6570\uFF0C\u4EFB\u610F\u57DF\u540D\uFF1A\u5B98\u65B9 pixso.cn \u6216\u79C1\u6709\u5316\u90E8\u7F72\uFF09\u3002\u7559\u7A7A\u5219\u4F7F\u7528\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\u3002",
590
+ required: false
512
591
  }
592
+ ],
593
+ render: (args) => {
594
+ const targetLine = args.url?.trim() ? `\u76EE\u6807\u8282\u70B9\u6765\u81EA\u94FE\u63A5\uFF1A${args.url.trim()}\u3002\u5FC5\u987B\u4ECE\u67E5\u8BE2\u53C2\u6570 \`item-id\` \u63D0\u53D6\u8282\u70B9 guid \u5E76 URL \u89E3\u7801\uFF08\`1%3A2\` \u2192 \`"1:2"\`\uFF09\u540E\u4F5C\u4E3A guid \u4F20\u5165\uFF1B\u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\u3002\u65E0\u8BBA\u57DF\u540D\u662F\u5426\u8BA4\u8BC6\uFF0C\u90FD\u6309\u6B64\u89C4\u5219\u5904\u7406\u3002` : `\u76EE\u6807\u8282\u70B9\u4E3A\u5F53\u524D\u9009\u533A\uFF1A\u8C03 \`get_selected\` \u62FF\u9009\u4E2D\u56FE\u5C42\u3002\u7981\u6B62\u9690\u5F0F\u4F7F\u7528\u5176 guid\u2014\u2014\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\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`;
595
+ return [
596
+ {
597
+ role: "user",
598
+ content: {
599
+ type: "text",
600
+ text: [
601
+ `\u628A Pixso \u8BBE\u8BA1\u7A3F\u8FD8\u539F\u4E3A\u524D\u7AEF\u4EE3\u7801\u3002`,
602
+ ``,
603
+ `## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011`,
604
+ ``,
605
+ `**\u5199\u4EE3\u7801\u524D\u5FC5\u987B\u5148\u8BFB**\u8D44\u6E90 \`batter://guides/design-to-code\` \u5E76\u9075\u5B88\u5176\u5168\u90E8\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528\u4E0E\u5B8C\u6574\u6027/DSL \u4E3A\u7ED3\u6784\u4E8B\u5B9E\u6E90/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D\uFF09\uFF1B\u603B\u89C8\u89C1 \`batter://guides/intro\`\u3002`,
606
+ ``,
607
+ `## \u6267\u884C\u6D41\u7A0B`,
608
+ ``,
609
+ `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
610
+ `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\u6709 guid \u5148 \`get_metadata(guid, fileKey)\` \u62FF\u5168\u6811\u9AA8\u67B6\u5B9A\u4F4D\u76EE\u6807 + \`get_screenshot(guid, fileKey)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF1B\u6CA1\u6709\u4EFB\u4F55 guid\uFF08\u6CA1\u7ED9\u94FE\u63A5\u4E5F\u6CA1\u9009\u4E2D\uFF09\u5148 \`get_page_metadata()\` \u62FF\u5F53\u524D\u9875\u9876\u5C42\u9AA8\u67B6\uFF0C\u7528\u5176 kids \u7684 id \u4F5C guid \u7EE7\u7EED\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`,
611
+ `3. \u751F\u6210\u4EE3\u7801\uFF1A\u6309\u6307\u5357\u4E2D\u7684 DSL \u5B57\u6BB5\u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002`,
612
+ `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`,
613
+ ``,
614
+ `**\u6700\u540E\u91CD\u7533**\uFF1A\u4EA4\u4ED8\u4EE3\u7801\u4E2D\u4E0D\u5141\u8BB8\u51FA\u73B0\u4EA7\u7269\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u5168\u90E8\u56FE\u7247\u5FC5\u987B\u662F\u5DF2\u590D\u5236\u5230\u9879\u76EE\u7684\u672C\u5730\u76F8\u5BF9\u8DEF\u5F84\uFF08\u7EC6\u5219\u89C1\u6307\u5357\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\uFF09\uFF1B\u4F4D\u7F6E/\u5C3A\u5BF8/\u5C42\u7EA7\u53EA\u4FE1 DSL\uFF0C\u7981\u6B62\u526A\u88C1\u6216\u653E\u5927\u622A\u56FE\u91CF\u53D6\u50CF\u7D20\u63A8\u5E03\u5C40\uFF08\u7B2C 3 \u6761\uFF09\uFF1B\u5E26 \`img\` \u7684\u8282\u70B9\u5FC5\u987B\u5F15\u7528\u5BFC\u51FA\u6587\u4EF6\uFF0C\u7981\u6B62\u624B\u5199 SVG \u8FD1\u4F3C\uFF0C\u5BFC\u51FA\u56FE\u7247\u7981\u6B62\u7F29\u653E/\u8F6C\u683C\u5F0F/\u88C1\u5207\u7B49\u4E8C\u6B21\u5904\u7406\u2014\u2014\u8981\u522B\u7684\u5C3A\u5BF8\u6216\u683C\u5F0F\u5C31\u91CD\u8C03 \`export_image\`\uFF08\u7B2C 4 \u6761\uFF09\u3002`
615
+ ].join("\n")
616
+ }
617
+ }
618
+ ];
513
619
  }
514
- ];
620
+ };
621
+ PROMPTS = [DESIGN_TO_CODE2];
515
622
  }
516
- };
517
- var PROMPTS = [DESIGN_TO_CODE2];
623
+ });
518
624
 
519
625
  // src/plugin/command-bus.ts
520
- var CommandBus = class {
521
- sseListeners = /* @__PURE__ */ new Map();
522
- pendingById = /* @__PURE__ */ new Map();
523
- seq = 0;
524
- /** AI 侧:执行命令 */
525
- execute(type, args, opts) {
526
- const timeoutMs = opts.timeoutMs ?? 6e4;
527
- const { promise, resolve, reject } = withResolvers();
528
- const command = { id: `cmd-${++this.seq}`, type, args };
529
- const entry = {
530
- command,
531
- sessionId: opts.sessionId,
532
- resolve,
533
- reject,
534
- dispatched: false,
535
- timer: setTimeout(() => {
626
+ var CommandBus, commandBus;
627
+ var init_command_bus = __esm({
628
+ "src/plugin/command-bus.ts"() {
629
+ "use strict";
630
+ init_config();
631
+ CommandBus = class {
632
+ sseListeners = /* @__PURE__ */ new Map();
633
+ pendingById = /* @__PURE__ */ new Map();
634
+ seq = 0;
635
+ /** AI 侧:执行命令 */
636
+ execute(type, args, opts) {
637
+ const timeoutMs = opts.timeoutMs ?? COMMAND_TIMEOUT_MS;
638
+ const { promise, resolve, reject } = Promise.withResolvers();
639
+ const command = { id: `cmd-${++this.seq}`, type, args };
640
+ const entry = {
641
+ command,
642
+ sessionId: opts.sessionId,
643
+ resolve,
644
+ reject,
645
+ dispatched: false,
646
+ timer: setTimeout(() => {
647
+ this.drop(entry);
648
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
649
+ }, timeoutMs)
650
+ };
651
+ opts.signal?.addEventListener("abort", () => {
652
+ if (!this.pendingById.has(command.id)) return;
653
+ this.drop(entry);
654
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
655
+ });
656
+ this.pendingById.set(command.id, entry);
657
+ this.dispatch(opts.sessionId);
658
+ return promise;
659
+ }
660
+ /** 插件侧:订阅即时推送(SSE 通道),返回退订函数;补发订阅前已入队的命令 */
661
+ subscribe(sessionId, listener) {
662
+ let subs = this.sseListeners.get(sessionId);
663
+ if (!subs) {
664
+ subs = /* @__PURE__ */ new Set();
665
+ this.sseListeners.set(sessionId, subs);
666
+ }
667
+ subs.add(listener);
668
+ this.dispatch(sessionId);
669
+ return () => {
670
+ subs.delete(listener);
671
+ if (subs.size === 0) this.sseListeners.delete(sessionId);
672
+ };
673
+ }
674
+ /** 插件侧:回传结果 */
675
+ complete(id, ok, resultOrError) {
676
+ const entry = this.pendingById.get(id);
677
+ if (!entry) return false;
536
678
  this.drop(entry);
537
- reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
538
- }, timeoutMs)
539
- };
540
- opts.signal?.addEventListener("abort", () => {
541
- if (!this.pendingById.has(command.id)) return;
542
- this.drop(entry);
543
- reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
544
- });
545
- this.pendingById.set(command.id, entry);
546
- this.dispatch(opts.sessionId);
547
- return promise;
548
- }
549
- /** 插件侧:订阅即时推送(SSE 通道),返回退订函数;补发订阅前已入队的命令 */
550
- subscribe(sessionId, listener) {
551
- let subs = this.sseListeners.get(sessionId);
552
- if (!subs) {
553
- subs = /* @__PURE__ */ new Set();
554
- this.sseListeners.set(sessionId, subs);
555
- }
556
- subs.add(listener);
557
- this.dispatch(sessionId);
558
- return () => {
559
- subs.delete(listener);
560
- if (subs.size === 0) this.sseListeners.delete(sessionId);
679
+ if (ok) {
680
+ entry.resolve(resultOrError);
681
+ } else {
682
+ entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
683
+ }
684
+ return true;
685
+ }
686
+ /** 排空该会话全部未派发命令(无订阅者则留待 subscribe 补发) */
687
+ dispatch(sessionId) {
688
+ const subs = this.sseListeners.get(sessionId);
689
+ if (!subs || subs.size === 0) return;
690
+ for (const entry of this.pendingById.values()) {
691
+ if (entry.sessionId !== sessionId || entry.dispatched) continue;
692
+ entry.dispatched = true;
693
+ log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)}`);
694
+ for (const fn of subs) fn(entry.command);
695
+ }
696
+ }
697
+ /** 从 pending 表移除(完成/超时/取消共用) */
698
+ drop(entry) {
699
+ clearTimeout(entry.timer);
700
+ this.pendingById.delete(entry.command.id);
701
+ }
561
702
  };
703
+ commandBus = new CommandBus();
562
704
  }
563
- /** 插件侧:回传结果 */
564
- complete(id, ok, resultOrError) {
565
- const entry = this.pendingById.get(id);
566
- if (!entry) return false;
567
- this.drop(entry);
568
- if (ok) {
569
- entry.resolve(resultOrError);
570
- } else {
571
- entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
572
- }
573
- return true;
574
- }
575
- /** 排空该会话全部未派发命令(无订阅者则留待 subscribe 补发) */
576
- dispatch(sessionId) {
577
- const subs = this.sseListeners.get(sessionId);
578
- if (!subs || subs.size === 0) return;
579
- for (const entry of this.pendingById.values()) {
580
- if (entry.sessionId !== sessionId || entry.dispatched) continue;
581
- entry.dispatched = true;
582
- log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)}`);
583
- for (const fn of subs) fn(entry.command);
584
- }
585
- }
586
- /** 从 pending 表移除(完成/超时/取消共用) */
587
- drop(entry) {
588
- clearTimeout(entry.timer);
589
- this.pendingById.delete(entry.command.id);
590
- }
591
- };
592
- var commandBus = new CommandBus();
705
+ });
593
706
 
594
707
  // src/plugin/sessions.ts
595
708
  import { randomUUID } from "node:crypto";
596
- var ONLINE_WINDOW_MS = 45e3;
597
709
  function unknownRouteError(route) {
598
710
  if (route.fileKey) {
599
711
  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
712
  }
601
713
  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
714
  }
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);
715
+ var SessionRegistry, sessions;
716
+ var init_sessions = __esm({
717
+ "src/plugin/sessions.ts"() {
718
+ "use strict";
719
+ init_config();
720
+ SessionRegistry = class {
721
+ sessions = /* @__PURE__ */ new Map();
722
+ lastActiveId = null;
723
+ /** 会话亲和:fileKey → 上一次路由使用的 sessionId(仍在线时优先,避免同文件多实例间漂移) */
724
+ lastUsedByFileKey = /* @__PURE__ */ new Map();
725
+ isOnline(s) {
726
+ return s.connected || Date.now() - s.lastSeen < SESSION_ONLINE_WINDOW_MS;
629
727
  }
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);
728
+ /** fileKey 寻址候选:该文件的在线会话(谁先 pong 由 ping 竞速决定,此处不排序) */
729
+ candidatesForFileKey(fileKey) {
730
+ return [...this.sessions.values()].filter((s) => s.meta.fileKey === fileKey && this.isOnline(s));
731
+ }
732
+ /** 亲和查询:上一次为该 fileKey 服务的 sessionId */
733
+ affinityIdOf(fileKey) {
734
+ return this.lastUsedByFileKey.get(fileKey);
735
+ }
736
+ /** 亲和钉住:fileKey 寻址探活成功后记录(下次优先) */
737
+ pinAffinity(fileKey, sessionId) {
738
+ this.lastUsedByFileKey.set(fileKey, sessionId);
739
+ }
740
+ /** 剔除会话(幽灵剔除/ping 判死共用):清理亲和引用;存活但繁忙被误剔的插件会在 SSE 断开或上报 404 后自重注册;
741
+ * 失效 sessionId 的 failover 不靠墓碑——工具结果信封带 fileKey,agent 手里的稳定句柄一直是 fileKey */
742
+ evict(id) {
743
+ if (this.sessions.delete(id)) {
744
+ for (const [k, v] of this.lastUsedByFileKey) {
745
+ if (v === id) this.lastUsedByFileKey.delete(k);
746
+ }
747
+ log(`session evicted: ${id.slice(0, 8)}`);
638
748
  }
639
749
  }
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
750
+ register(meta = {}) {
751
+ if (meta.fileKey) {
752
+ for (const s of [...this.sessions.values()]) {
753
+ if (s.meta.fileKey === meta.fileKey && !this.isOnline(s)) {
754
+ this.evict(s.id);
755
+ }
756
+ }
757
+ }
758
+ const session = { id: randomUUID(), meta, lastSeen: Date.now(), connected: false };
759
+ this.sessions.set(session.id, session);
760
+ this.lastActiveId = session.id;
761
+ log(`session registered: ${session.id} (${meta.fileName ?? "unknown file"})`);
762
+ return this.toInfo(session);
763
+ }
764
+ /** 心跳/连接状态用:只刷 lastSeen,不影响 lastActiveId("最近活跃"= 用户行为,心跳不算) */
765
+ touchSeen(id) {
766
+ const s = this.sessions.get(id);
767
+ if (!s) return null;
768
+ s.lastSeen = Date.now();
769
+ return s;
770
+ }
771
+ /** 用户行为(选区上报/命令服务/注册):刷 lastSeen 并标记最近活跃 */
772
+ touch(id) {
773
+ const s = this.touchSeen(id);
774
+ if (!s) return null;
775
+ this.lastActiveId = id;
776
+ return s;
777
+ }
778
+ setConnected(id, connected) {
779
+ const s = this.touchSeen(id);
780
+ if (!s) return false;
781
+ s.connected = connected;
782
+ return true;
783
+ }
784
+ updateContext(id, meta) {
785
+ const s = this.touch(id);
786
+ if (!s) return false;
787
+ s.meta = { ...s.meta, ...meta };
788
+ log(
789
+ `context updated [${id.slice(0, 8)}]: ${s.meta.fileName ?? "?"} / ${s.meta.pageName ?? "?"} / ${s.meta.selection?.length ?? 0} selected`
790
+ );
791
+ return true;
792
+ }
793
+ list() {
794
+ return [...this.sessions.values()].map((s) => this.toInfo(s));
795
+ }
796
+ /** 纯查找(不判活):工具层寻址用——离线会话也可取到 fileKey 用于候选扩展与故障转移 */
797
+ get(id) {
798
+ return this.sessions.get(id);
799
+ }
800
+ /** 缺省路由:最近活跃在线会话 > 任意在线会话(显式 fileKey/sessionId 由工具层 get/candidatesForFileKey + ping 完成) */
801
+ resolve() {
802
+ if (this.lastActiveId) {
803
+ const s = this.sessions.get(this.lastActiveId);
804
+ if (s && this.isOnline(s)) return s;
805
+ }
806
+ const fallback = [...this.sessions.values()].find((s) => this.isOnline(s));
807
+ if (fallback) return fallback;
808
+ throw new Error("Pixso \u63D2\u4EF6\u672A\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6");
809
+ }
810
+ toInfo(s) {
811
+ return {
812
+ sessionId: s.id,
813
+ fileKey: s.meta.fileKey,
814
+ fileName: s.meta.fileName,
815
+ pageName: s.meta.pageName,
816
+ selectionCount: s.meta.selection?.length,
817
+ online: this.isOnline(s),
818
+ lastSeen: s.lastSeen
819
+ };
820
+ }
702
821
  };
822
+ sessions = new SessionRegistry();
703
823
  }
704
- };
705
- var sessions = new SessionRegistry();
824
+ });
706
825
 
707
826
  // src/agent/tools/shared.ts
708
- var PING_TIMEOUT_MS = 800;
709
827
  async function run(type, args, signal) {
710
828
  const { fileKey, sessionId, ...rest } = args;
711
829
  try {
@@ -763,28 +881,6 @@ async function resolveAndPing(route, signal) {
763
881
  if (fileKey) sessions.pinAffinity(fileKey, winner.id);
764
882
  return winner;
765
883
  }
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";
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";
768
- var FILE_KEY_PROP = {
769
- type: "string",
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"
777
- };
778
- var CACHE_PROP = {
779
- type: "string",
780
- enum: ["first", "refresh"],
781
- description: "\u7F13\u5B58\u7B56\u7565\uFF1Afirst=\u7F13\u5B58\u4F18\u5148\uFF08\u9ED8\u8BA4\uFF0C15 \u5206\u949F\u5185\u76F8\u540C\u8C03\u7528\u76F4\u63A5\u8FD4\u56DE\uFF09\uFF1Brefresh=\u5F3A\u5236\u91CD\u65B0\u6784\u5EFA/\u5BFC\u51FA\uFF08\u4ECD\u5199\u5165\u7F13\u5B58\uFF09\u3002\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4E14\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\u7528 refresh\u3002"
782
- };
783
- var GUID_PROP = {
784
- type: "string",
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'
787
- };
788
884
  function jsonResult(data) {
789
885
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
790
886
  }
@@ -794,138 +890,217 @@ function errorResult(err) {
794
890
  isError: true
795
891
  };
796
892
  }
893
+ var ENVELOPE_NOTE, GUIDES_PTR, FILE_KEY_PROP, SESSION_ID_PROP, CACHE_PROP, GUID_PROP;
894
+ var init_shared = __esm({
895
+ "src/agent/tools/shared.ts"() {
896
+ "use strict";
897
+ init_command_bus();
898
+ init_sessions();
899
+ init_config();
900
+ 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";
901
+ GUIDES_PTR = "\u5B8C\u6574\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528\u4E0E\u5B8C\u6574\u6027/DSL \u4E3A\u7ED3\u6784\u4E8B\u5B9E\u6E90/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D\uFF09\u89C1\u8D44\u6E90 batter://guides/design-to-code\u3002";
902
+ FILE_KEY_PROP = {
903
+ type: "string",
904
+ minLength: 1,
905
+ 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"
906
+ };
907
+ SESSION_ID_PROP = {
908
+ type: "string",
909
+ minLength: 1,
910
+ 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"
911
+ };
912
+ CACHE_PROP = {
913
+ type: "string",
914
+ enum: ["first", "refresh"],
915
+ description: "\u7F13\u5B58\u7B56\u7565\uFF1Afirst=\u7F13\u5B58\u4F18\u5148\uFF08\u9ED8\u8BA4\uFF0C15 \u5206\u949F\u5185\u76F8\u540C\u8C03\u7528\u76F4\u63A5\u8FD4\u56DE\uFF09\uFF1Brefresh=\u5F3A\u5236\u91CD\u65B0\u6784\u5EFA/\u5BFC\u51FA\uFF08\u4ECD\u5199\u5165\u7F13\u5B58\uFF09\u3002\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4E14\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\u7528 refresh\u3002"
916
+ };
917
+ GUID_PROP = {
918
+ type: "string",
919
+ minLength: 1,
920
+ 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'
921
+ };
922
+ }
923
+ });
797
924
 
798
925
  // src/agent/tools/export_image.ts
799
- var exportImage = {
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,
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
926
+ var exportImage;
927
+ var init_export_image = __esm({
928
+ "src/agent/tools/export_image.ts"() {
929
+ "use strict";
930
+ init_shared();
931
+ exportImage = {
932
+ 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,
933
+ // 两组互斥各取其一:guid|guids(单节点|批量)× fileKey|sessionId(路由)
934
+ requires: [
935
+ ["guid", "guids"],
936
+ ["fileKey", "sessionId"]
937
+ ],
938
+ properties: {
939
+ guid: GUID_PROP,
940
+ guids: {
941
+ type: "array",
942
+ items: { type: "string" },
943
+ 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"
944
+ },
945
+ format: {
946
+ type: "string",
947
+ enum: ["PNG", "SVG", "JPG"],
948
+ 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"
949
+ },
950
+ scale: {
951
+ type: "number",
952
+ 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"
953
+ },
954
+ cache: CACHE_PROP,
955
+ fileKey: FILE_KEY_PROP,
956
+ sessionId: SESSION_ID_PROP
957
+ }
958
+ };
825
959
  }
826
- };
960
+ });
827
961
 
828
962
  // src/agent/tools/get_metadata.ts
829
- var getMetadata = {
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,
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
963
+ var getMetadata;
964
+ var init_get_metadata = __esm({
965
+ "src/agent/tools/get_metadata.ts"() {
966
+ "use strict";
967
+ init_shared();
968
+ getMetadata = {
969
+ 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,
970
+ requires: ["guid", ["fileKey", "sessionId"]],
971
+ properties: {
972
+ guid: GUID_PROP,
973
+ cache: CACHE_PROP,
974
+ fileKey: FILE_KEY_PROP,
975
+ sessionId: SESSION_ID_PROP
976
+ }
977
+ };
837
978
  }
838
- };
979
+ });
839
980
 
840
981
  // src/agent/tools/get_node_dsl.ts
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';
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';
843
- var getNodeDsl = {
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,
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
982
+ var URL_RULE, DSL_LEGEND, getNodeDsl;
983
+ var init_get_node_dsl = __esm({
984
+ "src/agent/tools/get_node_dsl.ts"() {
985
+ "use strict";
986
+ init_shared();
987
+ 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';
988
+ 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';
989
+ getNodeDsl = {
990
+ 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,
991
+ requires: ["guid", ["fileKey", "sessionId"]],
992
+ properties: {
993
+ guid: GUID_PROP,
994
+ maxDepth: {
995
+ type: "number",
996
+ description: "\u4E0B\u63A2\u5B50\u8282\u70B9\u7684\u6700\u5927\u6DF1\u5EA6\uFF08\u6839\u8282\u70B9\u4E3A\u7B2C 0 \u5C42\uFF09\uFF0C\u7F3A\u7701 4 \u5C42\uFF0C\u4F20 -1 \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"
997
+ },
998
+ cache: CACHE_PROP,
999
+ fileKey: FILE_KEY_PROP,
1000
+ sessionId: SESSION_ID_PROP
1001
+ }
1002
+ };
855
1003
  }
856
- };
1004
+ });
1005
+
1006
+ // src/agent/tools/get_page_metadata.ts
1007
+ var getPageMetadata;
1008
+ var init_get_page_metadata = __esm({
1009
+ "src/agent/tools/get_page_metadata.ts"() {
1010
+ "use strict";
1011
+ init_shared();
1012
+ getPageMetadata = {
1013
+ description: '\u3010\u7528\u9014\u3011\u5F53\u524D\u9875\u9762\u9876\u5C42\u9AA8\u67B6\uFF08\u9875\u9762 id/name\u3001\u6574\u9875\u6DF1\u5EA6 depth\u3001\u9876\u5C42\u56FE\u5C42\u5217\u8868\uFF09\uFF0C\u51B7\u542F\u52A8\u63A2\u7D22\u5165\u53E3\uFF0C\u65E0\u9700 guid\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\u4F46\u6CA1\u7ED9\u94FE\u63A5\u4E5F\u6CA1\u9009\u4E2D\u56FE\u5C42\uFF1B"\u9875\u9762\u91CC\u6709\u4EC0\u4E48/\u6709\u54EA\u4E9B\u56FE\u5C42"\uFF1B\u9700\u8981\u5148\u5B9A\u4F4D\u76EE\u6807\u518D\u4E0B\u94BB\u3002\u3010\u8FD4\u56DE\u3011{id, name, depth, kids: [{id, name?, type}]}\uFF08kids \u4EC5\u9876\u5C42\u4E00\u5C42\uFF09\u3002\u4E0B\u94BB\uFF1A\u628A kids \u91CC\u76EE\u6807\u56FE\u5C42\u7684 id \u4F5C\u4E3A guid \u4F20\u7ED9 get_metadata/get_node_dsl/get_screenshot\uFF1B\u8FD4\u56DE\u7684\u9875\u9762 id \u4E0D\u53EF\u56DE\u4F20\u8282\u70B9\u7C7B\u5DE5\u5177\uFF08\u9875\u9762\u4E0D\u652F\u6301\u5BFC\u51FA/DSL\uFF09\u3002\u672C\u5DE5\u5177\u7684 fileKey/sessionId \u53EF\u7F3A\u7701\uFF08\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u6587\u4EF6\uFF09\uFF1B\u4E0B\u94BB\u8C03\u7528\u4E24\u8005\u5FC5\u4F20\u5176\u4E00\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
1014
+ properties: { fileKey: FILE_KEY_PROP, sessionId: SESSION_ID_PROP }
1015
+ };
1016
+ }
1017
+ });
857
1018
 
858
1019
  // src/agent/tools/get_screenshot.ts
859
- var getScreenshot = {
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,
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
1020
+ var getScreenshot;
1021
+ var init_get_screenshot = __esm({
1022
+ "src/agent/tools/get_screenshot.ts"() {
1023
+ "use strict";
1024
+ init_shared();
1025
+ getScreenshot = {
1026
+ 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,
1027
+ requires: ["guid", ["fileKey", "sessionId"]],
1028
+ properties: {
1029
+ guid: GUID_PROP,
1030
+ format: {
1031
+ type: "string",
1032
+ enum: ["PNG", "SVG"],
1033
+ 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"
1034
+ },
1035
+ scale: {
1036
+ type: "number",
1037
+ description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
1038
+ },
1039
+ cache: CACHE_PROP,
1040
+ fileKey: FILE_KEY_PROP,
1041
+ sessionId: SESSION_ID_PROP
1042
+ }
1043
+ };
876
1044
  }
877
- };
1045
+ });
878
1046
 
879
1047
  // src/agent/tools/get_selected.ts
880
- var getSelected = {
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 }
883
- };
884
-
885
- // src/agent/tools/list_sessions.ts
886
- var listSessions = {
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: {},
889
- handler: async () => {
890
- const list = sessions.list();
891
- if (!list.length) {
892
- return {
893
- content: [{ type: "text", text: "\u5F53\u524D\u6CA1\u6709\u63D2\u4EF6\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6\u3002" }]
894
- };
895
- }
896
- const checks = await Promise.all(
897
- list.map(async (info) => info.online && await pingSession(info.sessionId) ? info : null)
898
- );
899
- const alive = checks.filter((c) => c !== null);
900
- for (const info of list) {
901
- if (!alive.includes(info)) sessions.evict(info.sessionId);
902
- }
903
- if (!alive.length) {
904
- return {
905
- content: [{ type: "text", text: "\u5F53\u524D\u6CA1\u6709\u5B58\u6D3B\u7684\u63D2\u4EF6\u8FDE\u63A5\uFF08\u5DF2\u6E05\u7406\u65E0\u54CD\u5E94\u4F1A\u8BDD\uFF09\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6\u3002" }]
906
- };
907
- }
908
- return jsonResult(
909
- alive.map(({ sessionId, fileKey, fileName, pageName, selectionCount }) => ({
910
- sessionId,
911
- fileKey,
912
- fileName,
913
- pageName,
914
- selectionCount
915
- }))
916
- );
1048
+ var getSelected;
1049
+ var init_get_selected = __esm({
1050
+ "src/agent/tools/get_selected.ts"() {
1051
+ "use strict";
1052
+ init_shared();
1053
+ getSelected = {
1054
+ 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,
1055
+ properties: { fileKey: FILE_KEY_PROP, sessionId: SESSION_ID_PROP }
1056
+ };
1057
+ }
1058
+ });
1059
+
1060
+ // src/agent/tools/list_sessions.ts
1061
+ var listSessions;
1062
+ var init_list_sessions = __esm({
1063
+ "src/agent/tools/list_sessions.ts"() {
1064
+ "use strict";
1065
+ init_sessions();
1066
+ init_shared();
1067
+ listSessions = {
1068
+ 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",
1069
+ properties: {},
1070
+ handler: async () => {
1071
+ const list = sessions.list();
1072
+ if (!list.length) {
1073
+ return {
1074
+ content: [{ type: "text", text: "\u5F53\u524D\u6CA1\u6709\u63D2\u4EF6\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6\u3002" }]
1075
+ };
1076
+ }
1077
+ const checks = await Promise.all(
1078
+ list.map(async (info) => info.online && await pingSession(info.sessionId) ? info : null)
1079
+ );
1080
+ const alive = checks.filter((c) => c !== null);
1081
+ for (const info of list) {
1082
+ if (!alive.includes(info)) sessions.evict(info.sessionId);
1083
+ }
1084
+ if (!alive.length) {
1085
+ return {
1086
+ content: [{ type: "text", text: "\u5F53\u524D\u6CA1\u6709\u5B58\u6D3B\u7684\u63D2\u4EF6\u8FDE\u63A5\uFF08\u5DF2\u6E05\u7406\u65E0\u54CD\u5E94\u4F1A\u8BDD\uFF09\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6\u3002" }]
1087
+ };
1088
+ }
1089
+ return jsonResult(
1090
+ alive.map(({ sessionId, fileKey, fileName, pageName, selectionCount }) => ({
1091
+ sessionId,
1092
+ fileKey,
1093
+ fileName,
1094
+ pageName,
1095
+ selectionCount
1096
+ }))
1097
+ );
1098
+ }
1099
+ };
917
1100
  }
918
- };
1101
+ });
919
1102
 
920
1103
  // src/agent/tools/index.ts
921
- var TOOL_REGISTRY = {
922
- get_selected: getSelected,
923
- get_node_dsl: getNodeDsl,
924
- get_metadata: getMetadata,
925
- export_image: exportImage,
926
- get_screenshot: getScreenshot,
927
- list_sessions: listSessions
928
- };
929
1104
  function buildInputSchema(requires, properties) {
930
1105
  const singles = requires.filter((r) => typeof r === "string");
931
1106
  const xorGroups = requires.filter((r) => Array.isArray(r));
@@ -951,26 +1126,56 @@ function validateArgs(requires, args) {
951
1126
  }
952
1127
  return null;
953
1128
  }
954
- var TOOLS = Object.fromEntries(
955
- Object.entries(TOOL_REGISTRY).map(([name, def]) => {
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);
1129
+ var TOOL_REGISTRY, TOOLS;
1130
+ var init_tools = __esm({
1131
+ "src/agent/tools/index.ts"() {
1132
+ "use strict";
1133
+ init_export_image();
1134
+ init_get_metadata();
1135
+ init_get_node_dsl();
1136
+ init_get_page_metadata();
1137
+ init_get_screenshot();
1138
+ init_get_selected();
1139
+ init_list_sessions();
1140
+ init_shared();
1141
+ TOOL_REGISTRY = {
1142
+ get_selected: getSelected,
1143
+ get_node_dsl: getNodeDsl,
1144
+ get_metadata: getMetadata,
1145
+ get_page_metadata: getPageMetadata,
1146
+ export_image: exportImage,
1147
+ get_screenshot: getScreenshot,
1148
+ list_sessions: listSessions
963
1149
  };
964
- const resolvedDef = { description: def.description, inputSchema, handler };
965
- return [name, { def: resolvedDef, tool: { name, description: def.description, inputSchema } }];
966
- })
967
- );
1150
+ TOOLS = Object.fromEntries(
1151
+ Object.entries(TOOL_REGISTRY).map(([name, def]) => {
1152
+ const requires = def.requires ?? [];
1153
+ const inputSchema = buildInputSchema(requires, def.properties);
1154
+ const base = def.handler ?? ((args, ctx) => run(name, args, ctx?.signal));
1155
+ const handler = (args, ctx) => {
1156
+ const err = requires.length ? validateArgs(requires, args) : null;
1157
+ if (err) return Promise.resolve(errorResult(err));
1158
+ return base(args, ctx);
1159
+ };
1160
+ const resolvedDef = { description: def.description, inputSchema, handler };
1161
+ return [name, { def: resolvedDef, tool: { name, description: def.description, inputSchema } }];
1162
+ })
1163
+ );
1164
+ }
1165
+ });
968
1166
 
969
1167
  // src/agent/mcp.ts
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`;
1168
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1169
+ import {
1170
+ CallToolRequestSchema,
1171
+ GetPromptRequestSchema,
1172
+ ListPromptsRequestSchema,
1173
+ ListResourceTemplatesRequestSchema,
1174
+ ListToolsRequestSchema
1175
+ } from "@modelcontextprotocol/sdk/types.js";
971
1176
  function createMcpServer() {
972
1177
  const server = new McpServer(
973
- { name: HUB_NAME, version: VERSION },
1178
+ { name: SERVICE_NAME, version: VERSION },
974
1179
  { capabilities: { tools: {}, resources: {}, prompts: {} }, instructions: INSTRUCTIONS }
975
1180
  );
976
1181
  for (const guide of GUIDES) {
@@ -1019,9 +1224,19 @@ function createMcpServer() {
1019
1224
  });
1020
1225
  return server;
1021
1226
  }
1227
+ var INSTRUCTIONS;
1228
+ var init_mcp = __esm({
1229
+ "src/agent/mcp.ts"() {
1230
+ "use strict";
1231
+ init_config();
1232
+ init_guides();
1233
+ init_prompts();
1234
+ init_tools();
1235
+ 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 \u6709 guid \u7528 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\u6CA1\u7ED9\u94FE\u63A5\u4E5F\u6CA1\u9009\u4E2D \u2192 get_page_metadata \u62FF\u5F53\u524D\u9875\u9876\u5C42\u9AA8\u67B6\uFF08\u65E0\u9700 guid\uFF09\uFF0C\u4ECE\u5176 kids \u7684 id \u4E0B\u94BB\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\u4E0E\u5B8C\u6574\u6027/DSL \u4E3A\u7ED3\u6784\u4E8B\u5B9E\u6E90/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D\uFF09\u89C1\u8D44\u6E90 batter://guides/design-to-code\u2014\u2014\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\u3002`;
1236
+ }
1237
+ });
1022
1238
 
1023
1239
  // src/agent/sessions.ts
1024
- var transports = /* @__PURE__ */ new Map();
1025
1240
  function getMcpTransport(sessionId) {
1026
1241
  const entry = transports.get(sessionId);
1027
1242
  if (entry) entry.lastSeen = Date.now();
@@ -1034,19 +1249,30 @@ function registerMcpTransport(sessionId, transport) {
1034
1249
  if (transport.sessionId) transports.delete(transport.sessionId);
1035
1250
  };
1036
1251
  }
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})`);
1252
+ var transports, sweeper;
1253
+ var init_sessions2 = __esm({
1254
+ "src/agent/sessions.ts"() {
1255
+ "use strict";
1256
+ init_config();
1257
+ transports = /* @__PURE__ */ new Map();
1258
+ sweeper = setInterval(() => {
1259
+ const now = Date.now();
1260
+ for (const [id, entry] of transports) {
1261
+ if (now - entry.lastSeen <= config.mcpSessionTtlMs) continue;
1262
+ transports.delete(id);
1263
+ void entry.transport.close().catch(() => {
1264
+ });
1265
+ log(`MCP HTTP session expired: ${id.slice(0, 8)} (total ${transports.size})`);
1266
+ }
1267
+ }, MCP_SESSION_SWEEP_MS);
1268
+ sweeper.unref();
1045
1269
  }
1046
- }, 6e4);
1047
- sweeper.unref();
1270
+ });
1048
1271
 
1049
1272
  // src/agent/http.ts
1273
+ import { randomUUID as randomUUID2 } from "node:crypto";
1274
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1275
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
1050
1276
  function handleMcp(req, res, url) {
1051
1277
  return dispatchRoute(MCP_ROUTES, req, res, url);
1052
1278
  }
@@ -1059,95 +1285,54 @@ async function sessionRequestHandler({ req, res }) {
1059
1285
  }
1060
1286
  await transport.handleRequest(req, res);
1061
1287
  }
1062
- var MCP_ROUTES = [
1063
- {
1064
- method: "POST",
1065
- path: "/mcp",
1066
- handler: async ({ req, res }) => {
1067
- let body;
1068
- try {
1069
- body = await readJsonBody(req);
1070
- } catch {
1071
- sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
1072
- return;
1073
- }
1074
- const sessionId = req.headers["mcp-session-id"];
1075
- let transport = sessionId ? getMcpTransport(sessionId) : void 0;
1076
- if (!transport) {
1077
- if (!sessionId && isInitializeRequest(body)) {
1078
- const t = new StreamableHTTPServerTransport({
1079
- sessionIdGenerator: () => randomUUID2(),
1080
- onsessioninitialized: (id) => registerMcpTransport(id, t)
1081
- });
1082
- await createMcpServer().connect(t);
1083
- transport = t;
1084
- } else {
1085
- sendJson(res, 400, {
1086
- jsonrpc: "2.0",
1087
- error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
1088
- id: null
1089
- });
1090
- return;
1288
+ var MCP_ROUTES;
1289
+ var init_http = __esm({
1290
+ "src/agent/http.ts"() {
1291
+ "use strict";
1292
+ init_http_body();
1293
+ init_mcp();
1294
+ init_sessions2();
1295
+ MCP_ROUTES = [
1296
+ {
1297
+ method: "POST",
1298
+ path: "/mcp",
1299
+ handler: async ({ req, res }) => {
1300
+ let body;
1301
+ try {
1302
+ body = await readJsonBody(req);
1303
+ } catch {
1304
+ sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
1305
+ return;
1306
+ }
1307
+ const sessionId = req.headers["mcp-session-id"];
1308
+ let transport = sessionId ? getMcpTransport(sessionId) : void 0;
1309
+ if (!transport) {
1310
+ if (!sessionId && isInitializeRequest(body)) {
1311
+ const t = new StreamableHTTPServerTransport({
1312
+ sessionIdGenerator: () => randomUUID2(),
1313
+ onsessioninitialized: (id) => registerMcpTransport(id, t)
1314
+ });
1315
+ await createMcpServer().connect(t);
1316
+ transport = t;
1317
+ } else {
1318
+ sendJson(res, 400, {
1319
+ jsonrpc: "2.0",
1320
+ error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
1321
+ id: null
1322
+ });
1323
+ return;
1324
+ }
1325
+ }
1326
+ await transport.handleRequest(req, res, body);
1091
1327
  }
1092
- }
1093
- await transport.handleRequest(req, res, body);
1094
- }
1095
- },
1096
- { method: "GET", path: "/mcp", handler: sessionRequestHandler },
1097
- { method: "DELETE", path: "/mcp", handler: sessionRequestHandler }
1098
- ];
1099
-
1100
- // src/install/dist-resolver.ts
1101
- import { existsSync } from "node:fs";
1102
- import { readFile } from "node:fs/promises";
1103
- import { join as join4 } from "node:path";
1104
- import { fileURLToPath as fileURLToPath2 } from "node:url";
1105
- function resolvePluginDir() {
1106
- const candidates = [
1107
- fileURLToPath2(new URL("./plugin/", import.meta.url)),
1108
- // bundle: dist/index.js → dist/plugin/
1109
- fileURLToPath2(new URL("../../plugin/dist/", import.meta.url)),
1110
- // bundle 开发: dist/index.js → packages/plugin/dist/
1111
- fileURLToPath2(new URL("../../../plugin/dist/", import.meta.url))
1112
- // tsx 开发: src/install/ → packages/plugin/dist/
1113
- ];
1114
- return candidates.find((p) => existsSync(join4(p, "manifest.json"))) ?? null;
1115
- }
1116
- var resolved = null;
1117
- function pluginDir() {
1118
- resolved ??= resolvePluginDir();
1119
- return resolved;
1120
- }
1121
- function hasPluginDist() {
1122
- return pluginDir() !== null;
1123
- }
1124
- async function pluginManifest() {
1125
- const dir = pluginDir();
1126
- if (!dir) return null;
1127
- const raw = JSON.parse(await readFile(join4(dir, "manifest.json"), "utf8"));
1128
- raw.networkAccess = {
1129
- allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
1130
- };
1131
- return Buffer.from(JSON.stringify(raw, null, 2));
1132
- }
1133
- async function readPluginFile(name) {
1134
- const dir = pluginDir();
1135
- if (!dir) return null;
1136
- try {
1137
- return await readFile(join4(dir, name));
1138
- } catch {
1139
- return null;
1328
+ },
1329
+ { method: "GET", path: "/mcp", handler: sessionRequestHandler },
1330
+ { method: "DELETE", path: "/mcp", handler: sessionRequestHandler }
1331
+ ];
1140
1332
  }
1141
- }
1333
+ });
1142
1334
 
1143
1335
  // src/install/template.ts
1144
- var HTML_ESCAPES = {
1145
- "&": "&amp;",
1146
- "<": "&lt;",
1147
- ">": "&gt;",
1148
- '"': "&quot;",
1149
- "'": "&#39;"
1150
- };
1151
1336
  function escapeHtml(s) {
1152
1337
  return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
1153
1338
  }
@@ -1162,12 +1347,33 @@ function renderTemplate(tpl, vars, escape = escapeHtml) {
1162
1347
  };
1163
1348
  return tpl.replace(/\{\{\{\s*(\w+)\s*\}\}\}/g, (_, key) => lookup(key)).replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => escape(lookup(key)));
1164
1349
  }
1350
+ var HTML_ESCAPES;
1351
+ var init_template = __esm({
1352
+ "src/install/template.ts"() {
1353
+ "use strict";
1354
+ HTML_ESCAPES = {
1355
+ "&": "&amp;",
1356
+ "<": "&lt;",
1357
+ ">": "&gt;",
1358
+ '"': "&quot;",
1359
+ "'": "&#39;"
1360
+ };
1361
+ }
1362
+ });
1165
1363
 
1166
1364
  // src/install/templates/bookmarklet.txt
1167
- var bookmarklet_default = '// Batter Bridge \u4E66\u7B7E\u6CE8\u5165\u811A\u672C\n// \u5728 Pixso / MasterGo \u7F51\u9875\u7248\u8BBE\u8BA1\u6587\u4EF6\u9875\u9762\u70B9\u51FB\u540E\u6267\u884C\uFF1A\n// \u4ECE\u672C\u670D\u52A1\u62C9\u53D6 manifest + code\uFF0C\u901A\u8FC7\u5BBF\u4E3B\u6CE8\u5165 API \u8FD0\u884C\u63D2\u4EF6\u3002\n// \u670D\u52A1\u4E0D\u53EF\u8FBE\u65F6\u56DE\u9000 localStorage \u7F13\u5B58\uFF08\u4E0A\u6B21\u6210\u529F\u62C9\u53D6\u7684\u63D2\u4EF6\u5305\uFF09\u3002\n// \u6E32\u67D3\u65F6 {{ base }} \u66FF\u6362\u4E3A\u670D\u52A1\u5730\u5740\uFF08JS \u5B57\u7B26\u4E32\u8F6C\u4E49\uFF09\uFF0C\u6574\u4F53 encodeURIComponent \u540E\u4F5C\u4E3A\u4E66\u7B7E href\u3002\n(function () {\n var SERVER_BASE = "{{ base }}";\n var CACHE_KEY = "batter-bridge-cache";\n\n /** \u7528\u5BBF\u4E3B\u9875\u9762\u7684\u6CE8\u5165 API \u542F\u52A8\u63D2\u4EF6 */\n function startPlugin(codeText, manifestText) {\n try {\n var manifest = JSON.parse(manifestText);\n if (window._FicAppObj && window._FicAppObj.runTestPlugin) {\n // MasterGo \u5BBF\u4E3B\n window._FicAppObj.runTestPlugin(manifest, codeText, "", null, { startup: "DEBUG" });\n return;\n }\n if (window.runNewPlugin) {\n // Pixso \u5BBF\u4E3B\n window.runNewPlugin(codeText, null, null, { manifest: manifest, plugin: manifest });\n return;\n }\n alert("\u8BF7\u5728 Pixso / MasterGo \u8BBE\u8BA1\u6587\u4EF6\u9875\u9762\u70B9\u51FB\u6B64\u4E66\u7B7E");\n } catch (err) {\n alert("\u63D2\u4EF6\u542F\u52A8\u5931\u8D25\uFF1A" + ((err && err.message) || err));\n }\n }\n\n /** \u5C1D\u8BD5\u7528\u7F13\u5B58\u7684\u63D2\u4EF6\u5305\u542F\u52A8\uFF1B\u65E0\u7F13\u5B58\u8FD4\u56DE false */\n function startFromCache() {\n try {\n var cached = JSON.parse(localStorage.getItem(CACHE_KEY) || "null");\n if (cached && cached.manifest && cached.code) {\n startPlugin(cached.code, cached.manifest);\n return true;\n }\n } catch (err) {\n // \u7F13\u5B58\u635F\u574F\uFF1A\u89C6\u4E3A\u65E0\u7F13\u5B58\n }\n return false;\n }\n\n function saveCache(manifestText, codeText) {\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify({ manifest: manifestText, code: codeText }));\n } catch (err) {\n // \u5B58\u50A8\u8D85\u9650\u7B49\uFF1A\u7F13\u5B58\u53EA\u662F\u79BB\u7EBF\u515C\u5E95\uFF0C\u5931\u8D25\u53EF\u5FFD\u7565\n }\n }\n\n function fetchText(path) {\n return fetch(SERVER_BASE + path + "?t=" + Date.now(), { cache: "no-store" }).then(\n function (res) {\n if (!res.ok) throw new Error("HTTP " + res.status);\n return res.text();\n },\n );\n }\n\n Promise.all([fetchText("manifest.json"), fetchText("code.js")])\n .then(function (texts) {\n var manifestText = texts[0];\n var codeText = texts[1];\n saveCache(manifestText, codeText);\n startPlugin(codeText, manifestText);\n })\n .catch(function () {\n if (!startFromCache()) {\n alert("\u65E0\u6CD5\u8FDE\u63A5 batter-pixso-mcp \u670D\u52A1\uFF08" + SERVER_BASE + "\uFF09\uFF0C\u8BF7\u786E\u8BA4\u670D\u52A1\u5DF2\u542F\u52A8");\n }\n });\n})();\n';
1365
+ var bookmarklet_default;
1366
+ var init_bookmarklet = __esm({
1367
+ "src/install/templates/bookmarklet.txt"() {
1368
+ bookmarklet_default = '// Batter Bridge \u4E66\u7B7E\u6CE8\u5165\u811A\u672C\n// \u5728 Pixso / MasterGo \u7F51\u9875\u7248\u8BBE\u8BA1\u6587\u4EF6\u9875\u9762\u70B9\u51FB\u540E\u6267\u884C\uFF1A\n// \u4ECE\u672C\u670D\u52A1\u62C9\u53D6 manifest + code\uFF0C\u901A\u8FC7\u5BBF\u4E3B\u6CE8\u5165 API \u8FD0\u884C\u63D2\u4EF6\u3002\n// \u670D\u52A1\u4E0D\u53EF\u8FBE\u65F6\u56DE\u9000 localStorage \u7F13\u5B58\uFF08\u4E0A\u6B21\u6210\u529F\u62C9\u53D6\u7684\u63D2\u4EF6\u5305\uFF09\u3002\n// \u6E32\u67D3\u65F6 {{ base }} \u66FF\u6362\u4E3A\u670D\u52A1\u5730\u5740\uFF08JS \u5B57\u7B26\u4E32\u8F6C\u4E49\uFF09\uFF0C\u6574\u4F53 encodeURIComponent \u540E\u4F5C\u4E3A\u4E66\u7B7E href\u3002\n(function () {\n var SERVER_BASE = "{{ base }}";\n var CACHE_KEY = "batter-bridge-cache";\n\n /** \u7528\u5BBF\u4E3B\u9875\u9762\u7684\u6CE8\u5165 API \u542F\u52A8\u63D2\u4EF6 */\n function startPlugin(codeText, manifestText) {\n try {\n var manifest = JSON.parse(manifestText);\n if (window._FicAppObj && window._FicAppObj.runTestPlugin) {\n // MasterGo \u5BBF\u4E3B\n window._FicAppObj.runTestPlugin(manifest, codeText, "", null, { startup: "DEBUG" });\n return;\n }\n if (window.runNewPlugin) {\n // Pixso \u5BBF\u4E3B\n window.runNewPlugin(codeText, null, null, { manifest: manifest, plugin: manifest });\n return;\n }\n alert("\u8BF7\u5728 Pixso / MasterGo \u8BBE\u8BA1\u6587\u4EF6\u9875\u9762\u70B9\u51FB\u6B64\u4E66\u7B7E");\n } catch (err) {\n alert("\u63D2\u4EF6\u542F\u52A8\u5931\u8D25\uFF1A" + ((err && err.message) || err));\n }\n }\n\n /** \u5C1D\u8BD5\u7528\u7F13\u5B58\u7684\u63D2\u4EF6\u5305\u542F\u52A8\uFF1B\u65E0\u7F13\u5B58\u8FD4\u56DE false */\n function startFromCache() {\n try {\n var cached = JSON.parse(localStorage.getItem(CACHE_KEY) || "null");\n if (cached && cached.manifest && cached.code) {\n startPlugin(cached.code, cached.manifest);\n return true;\n }\n } catch (err) {\n // \u7F13\u5B58\u635F\u574F\uFF1A\u89C6\u4E3A\u65E0\u7F13\u5B58\n }\n return false;\n }\n\n function saveCache(manifestText, codeText) {\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify({ manifest: manifestText, code: codeText }));\n } catch (err) {\n // \u5B58\u50A8\u8D85\u9650\u7B49\uFF1A\u7F13\u5B58\u53EA\u662F\u79BB\u7EBF\u515C\u5E95\uFF0C\u5931\u8D25\u53EF\u5FFD\u7565\n }\n }\n\n function fetchText(path) {\n return fetch(SERVER_BASE + path + "?t=" + Date.now(), { cache: "no-store" }).then(\n function (res) {\n if (!res.ok) throw new Error("HTTP " + res.status);\n return res.text();\n },\n );\n }\n\n Promise.all([fetchText("manifest.json"), fetchText("code.js")])\n .then(function (texts) {\n var manifestText = texts[0];\n var codeText = texts[1];\n saveCache(manifestText, codeText);\n startPlugin(codeText, manifestText);\n })\n .catch(function () {\n if (!startFromCache()) {\n alert("\u65E0\u6CD5\u8FDE\u63A5 batter-pixso-mcp \u670D\u52A1\uFF08" + SERVER_BASE + "\uFF09\uFF0C\u8BF7\u786E\u8BA4\u670D\u52A1\u5DF2\u542F\u52A8");\n }\n });\n})();\n';
1369
+ }
1370
+ });
1168
1371
 
1169
1372
  // src/install/templates/install-page.html
1170
- var install_page_default = `<!DOCTYPE html>
1373
+ var install_page_default;
1374
+ var init_install_page = __esm({
1375
+ "src/install/templates/install-page.html"() {
1376
+ install_page_default = `<!DOCTYPE html>
1171
1377
  <html lang="zh-CN">
1172
1378
  <head>
1173
1379
  <meta charset="utf-8">
@@ -1500,15 +1706,17 @@ var install_page_default = `<!DOCTYPE html>
1500
1706
  <pre id="cfg">{
1501
1707
  "mcpServers": {
1502
1708
  "batter-pixso": {
1503
- "command": "npx",
1504
- "args": ["-y", "batter-pixso-mcp"],
1505
- "env": { "HTTP_PORT": "{{ httpPort }}" }
1709
+ "url": "{{ mcpUrl }}"
1506
1710
  }
1507
1711
  }
1508
1712
  }</pre>
1509
1713
  <button class="copy" id="copy" type="button">\u590D\u5236</button>
1510
1714
  </div>
1511
- <p class="note">\u7C98\u8D34\u5230 Cursor / Claude Code \u7684 MCP \u914D\u7F6E\u6587\u4EF6\u3002\u7CFB\u7EDF Node \u4F4E\u4E8E 18 \u65F6\u6539\u7528 volta \u5305\u88F9\u542F\u52A8\uFF0C\u8BE6\u89C1 README\u3002</p>
1715
+ <p class="note">
1716
+ \u7C98\u8D34\u5230 Cursor / Cherry Studio \u7B49\u7684 MCP \u914D\u7F6E\u6587\u4EF6\uFF1BClaude Code \u6267\u884C
1717
+ <code>{{ claudeAddCommand }}</code>\u3002
1718
+ \u670D\u52A1\u672A\u542F\u52A8\u65F6\u5148\u5728\u7EC8\u7AEF\u6267\u884C <code>npx batter-pixso-mcp</code>\uFF08Windows \u53EF\u7528 <code>--setup</code> \u6CE8\u518C\u5F00\u673A\u81EA\u542F\uFF09\u3002
1719
+ </p>
1512
1720
  </div>
1513
1721
  </div>
1514
1722
 
@@ -1560,6 +1768,7 @@ copyBtn.addEventListener("click", function () {
1560
1768
  if (navigator.clipboard && navigator.clipboard.writeText) {
1561
1769
  navigator.clipboard.writeText(text).then(done, done);
1562
1770
  } else {
1771
+ // \u515C\u5E95\uFF1A\u65E0 clipboard API \u7684\u73AF\u5883\uFF08\u53CC\u6838\u6D4F\u89C8\u5668\u517C\u5BB9\u6A21\u5F0F\u7B49\uFF09
1563
1772
  var ta = document.createElement("textarea");
1564
1773
  ta.value = text;
1565
1774
  document.body.appendChild(ta);
@@ -1573,6 +1782,8 @@ copyBtn.addEventListener("click", function () {
1573
1782
  </body>
1574
1783
  </html>
1575
1784
  `;
1785
+ }
1786
+ });
1576
1787
 
1577
1788
  // src/install/page.ts
1578
1789
  function bookmarkletHref(base) {
@@ -1580,26 +1791,27 @@ function bookmarkletHref(base) {
1580
1791
  return `javascript:${encodeURIComponent(source)}`;
1581
1792
  }
1582
1793
  function renderInstallPage() {
1583
- const base = `${hubBaseUrl()}/`;
1794
+ const base = `${serviceBaseUrl()}/`;
1584
1795
  return renderTemplate(install_page_default, {
1585
1796
  version: VERSION,
1586
1797
  href: bookmarkletHref(base),
1587
1798
  base,
1588
- httpPort: config.httpPort
1799
+ mcpUrl: mcpUrl(),
1800
+ claudeAddCommand: claudeAddCommand()
1589
1801
  });
1590
1802
  }
1803
+ var init_page = __esm({
1804
+ "src/install/page.ts"() {
1805
+ "use strict";
1806
+ init_config();
1807
+ init_template();
1808
+ init_bookmarklet();
1809
+ init_install_page();
1810
+ }
1811
+ });
1591
1812
 
1592
1813
  // src/install/zip.ts
1593
1814
  import { Buffer as Buffer2 } from "node:buffer";
1594
- var CRC_TABLE = (() => {
1595
- const table = new Uint32Array(256);
1596
- for (let n = 0; n < 256; n++) {
1597
- let c = n;
1598
- for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
1599
- table[n] = c >>> 0;
1600
- }
1601
- return table;
1602
- })();
1603
1815
  function crc32(buf) {
1604
1816
  let crc = 4294967295;
1605
1817
  for (const byte of buf) crc = CRC_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
@@ -1655,9 +1867,23 @@ function buildZip(entries) {
1655
1867
  eocd.writeUInt32LE(offset, 16);
1656
1868
  return Buffer2.concat([...chunks, centralBuf, eocd]);
1657
1869
  }
1870
+ var CRC_TABLE;
1871
+ var init_zip = __esm({
1872
+ "src/install/zip.ts"() {
1873
+ "use strict";
1874
+ CRC_TABLE = (() => {
1875
+ const table = new Uint32Array(256);
1876
+ for (let n = 0; n < 256; n++) {
1877
+ let c = n;
1878
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
1879
+ table[n] = c >>> 0;
1880
+ }
1881
+ return table;
1882
+ })();
1883
+ }
1884
+ });
1658
1885
 
1659
1886
  // src/install/api.ts
1660
- var NO_DIST = "plugin dist not found\uFF08\u8FD0\u884C npm run build \u751F\u6210\uFF09";
1661
1887
  function handleInstall(req, res, url) {
1662
1888
  return dispatchRoute(INSTALL_ROUTES, req, res, url);
1663
1889
  }
@@ -1665,169 +1891,269 @@ function sendBinary(res, buf, contentType) {
1665
1891
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "no-store" });
1666
1892
  res.end(buf);
1667
1893
  }
1668
- var PLUGIN_FILE_ROUTES = [
1669
- { path: "/code.js", file: "code.js", type: "text/javascript; charset=utf-8" },
1670
- { path: "/ui.html", file: "ui.html", type: "text/html; charset=utf-8" }
1671
- ].map(({ path, file, type }) => ({
1672
- method: "GET",
1673
- path,
1674
- handler: async ({ res }) => {
1675
- const buf = await readPluginFile(file);
1676
- if (!buf) {
1677
- sendJson(res, 404, { error: hasPluginDist() ? "not found" : NO_DIST });
1678
- return;
1679
- }
1680
- sendBinary(res, buf, type);
1681
- }
1682
- }));
1683
- var INSTALL_ROUTES = [
1684
- {
1685
- method: "GET",
1686
- path: "/",
1687
- handler: ({ res }) => {
1688
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1689
- res.end(renderInstallPage());
1690
- }
1691
- },
1692
- {
1693
- method: "GET",
1694
- path: "/manifest.json",
1695
- handler: async ({ res }) => {
1696
- const buf = await pluginManifest();
1697
- if (!buf) {
1698
- sendJson(res, 404, { error: NO_DIST });
1699
- return;
1894
+ var NO_DIST, PLUGIN_FILE_ROUTES, INSTALL_ROUTES;
1895
+ var init_api = __esm({
1896
+ "src/install/api.ts"() {
1897
+ "use strict";
1898
+ init_http_body();
1899
+ init_dist_resolver();
1900
+ init_page();
1901
+ init_zip();
1902
+ NO_DIST = "plugin dist not found\uFF08\u8FD0\u884C npm run build \u751F\u6210\uFF09";
1903
+ PLUGIN_FILE_ROUTES = [
1904
+ { path: "/code.js", file: "code.js", type: "text/javascript; charset=utf-8" },
1905
+ { path: "/ui.html", file: "ui.html", type: "text/html; charset=utf-8" }
1906
+ ].map(({ path, file, type }) => ({
1907
+ method: "GET",
1908
+ path,
1909
+ handler: async ({ res }) => {
1910
+ const buf = await readPluginFile(file);
1911
+ if (!buf) {
1912
+ sendJson(res, 404, { error: hasPluginDist() ? "not found" : NO_DIST });
1913
+ return;
1914
+ }
1915
+ sendBinary(res, buf, type);
1700
1916
  }
1701
- sendBinary(res, buf, "application/json; charset=utf-8");
1702
- }
1703
- },
1704
- ...PLUGIN_FILE_ROUTES,
1705
- {
1706
- method: "GET",
1707
- path: "/plugin.zip",
1708
- handler: async ({ res }) => {
1709
- if (!hasPluginDist()) {
1710
- sendJson(res, 404, { error: NO_DIST });
1711
- return;
1917
+ }));
1918
+ INSTALL_ROUTES = [
1919
+ {
1920
+ method: "GET",
1921
+ path: "/",
1922
+ handler: ({ res }) => {
1923
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1924
+ res.end(renderInstallPage());
1925
+ }
1926
+ },
1927
+ {
1928
+ method: "GET",
1929
+ path: "/manifest.json",
1930
+ handler: async ({ res }) => {
1931
+ const buf = await pluginManifest();
1932
+ if (!buf) {
1933
+ sendJson(res, 404, { error: NO_DIST });
1934
+ return;
1935
+ }
1936
+ sendBinary(res, buf, "application/json; charset=utf-8");
1937
+ }
1938
+ },
1939
+ ...PLUGIN_FILE_ROUTES,
1940
+ {
1941
+ method: "GET",
1942
+ path: "/plugin.zip",
1943
+ handler: async ({ res }) => {
1944
+ if (!hasPluginDist()) {
1945
+ sendJson(res, 404, { error: NO_DIST });
1946
+ return;
1947
+ }
1948
+ try {
1949
+ const [manifest, code, ui] = await Promise.all([
1950
+ pluginManifest(),
1951
+ readPluginFile("code.js"),
1952
+ readPluginFile("ui.html")
1953
+ ]);
1954
+ const zip = buildZip([
1955
+ { name: "manifest.json", data: manifest },
1956
+ { name: "code.js", data: code },
1957
+ { name: "ui.html", data: ui }
1958
+ ]);
1959
+ res.writeHead(200, {
1960
+ "Content-Type": "application/zip",
1961
+ "Content-Disposition": 'attachment; filename="batter-pixso-bridge.zip"'
1962
+ });
1963
+ res.end(zip);
1964
+ } catch {
1965
+ sendJson(res, 500, { error: "zip build failed" });
1966
+ }
1967
+ }
1968
+ }
1969
+ ];
1970
+ }
1971
+ });
1972
+
1973
+ // src/plugin/assets.ts
1974
+ import { createHash } from "node:crypto";
1975
+ import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
1976
+ import { join as join4 } from "node:path";
1977
+ async function saveAsset(bytes, format) {
1978
+ dirReady ??= mkdir(ASSETS_DIR, { recursive: true });
1979
+ await dirReady;
1980
+ const ext = EXT_BY_FORMAT[format.toUpperCase()] ?? ".bin";
1981
+ const name = `${createHash("sha1").update(bytes).digest("hex").slice(0, 16)}${ext}`;
1982
+ await writeFile(join4(ASSETS_DIR, name), bytes);
1983
+ log(`asset saved: ${name} (${bytes.length} bytes)`);
1984
+ return name;
1985
+ }
1986
+ function substituteAssets(result, assets) {
1987
+ if (!assets.length) return result;
1988
+ const nameByKey = new Map(assets.map((a) => [a.key, a.name]));
1989
+ return JSON.parse(
1990
+ JSON.stringify(
1991
+ result,
1992
+ (_k, v) => typeof v === "string" ? nameByKey.get(v) ?? v : v
1993
+ )
1994
+ );
1995
+ }
1996
+ async function cleanOldAssets(ttlMs) {
1997
+ let removed = 0;
1998
+ const cutoff = Date.now() - ttlMs;
1999
+ let names;
2000
+ try {
2001
+ names = await readdir(ASSETS_DIR);
2002
+ } catch {
2003
+ return 0;
2004
+ }
2005
+ const expired = await Promise.all(
2006
+ names.map(async (name) => {
2007
+ try {
2008
+ const p = join4(ASSETS_DIR, name);
2009
+ const s = await stat(p);
2010
+ return s.isFile() && s.mtimeMs < cutoff ? p : null;
2011
+ } catch {
2012
+ return null;
1712
2013
  }
2014
+ })
2015
+ );
2016
+ await Promise.all(
2017
+ expired.filter((p) => p !== null).map(async (p) => {
1713
2018
  try {
1714
- const [manifest, code, ui] = await Promise.all([
1715
- pluginManifest(),
1716
- readPluginFile("code.js"),
1717
- readPluginFile("ui.html")
1718
- ]);
1719
- const zip = buildZip([
1720
- { name: "manifest.json", data: manifest },
1721
- { name: "code.js", data: code },
1722
- { name: "ui.html", data: ui }
1723
- ]);
1724
- res.writeHead(200, {
1725
- "Content-Type": "application/zip",
1726
- "Content-Disposition": 'attachment; filename="batter-pixso-bridge.zip"'
1727
- });
1728
- res.end(zip);
2019
+ await unlink(p);
2020
+ removed++;
1729
2021
  } catch {
1730
- sendJson(res, 500, { error: "zip build failed" });
1731
2022
  }
1732
- }
2023
+ })
2024
+ );
2025
+ if (removed > 0) log(`assets cleanup: removed ${removed} expired file(s)`);
2026
+ return removed;
2027
+ }
2028
+ function scheduleAssetsSweep(ttlMs) {
2029
+ void cleanOldAssets(ttlMs);
2030
+ const sweeper2 = setInterval(() => void cleanOldAssets(ttlMs), ASSET_SWEEP_INTERVAL_MS);
2031
+ sweeper2.unref();
2032
+ }
2033
+ var EXT_BY_FORMAT, dirReady;
2034
+ var init_assets = __esm({
2035
+ "src/plugin/assets.ts"() {
2036
+ "use strict";
2037
+ init_config();
2038
+ EXT_BY_FORMAT = {
2039
+ PNG: ".png",
2040
+ SVG: ".svg",
2041
+ JPG: ".jpg",
2042
+ JPEG: ".jpg",
2043
+ WEBP: ".webp",
2044
+ PDF: ".pdf"
2045
+ // export_image 尊重设计师导出标记,标记可能指定 PDF
2046
+ };
2047
+ dirReady = null;
1733
2048
  }
1734
- ];
2049
+ });
1735
2050
 
1736
2051
  // src/plugin/api.ts
1737
2052
  function handlePluginApi(req, res, url) {
1738
2053
  return dispatchRoute(PLUGIN_ROUTES, req, res, url);
1739
2054
  }
1740
- var PLUGIN_ROUTES = [
1741
- {
1742
- method: "POST",
1743
- path: "/sessions/register",
1744
- handler: async ({ req, res }) => {
1745
- sendJson(res, 201, sessions.register(await readJsonBody(req)));
1746
- }
1747
- },
1748
- {
1749
- method: "POST",
1750
- path: "/context",
1751
- handler: async ({ req, res, url }) => {
1752
- const session = url.searchParams.get("session");
1753
- if (!session) {
1754
- sendJson(res, 400, { error: "missing session" });
1755
- return;
1756
- }
1757
- const meta = await readJsonBody(req);
1758
- if (!sessions.updateContext(session, meta)) {
1759
- sendJson(res, 404, { error: "unknown_session" });
1760
- return;
1761
- }
1762
- sendJson(res, 200, { ok: true });
1763
- }
1764
- },
1765
- {
1766
- method: "GET",
1767
- path: "/commands/stream",
1768
- handler: ({ req, res, url }) => {
1769
- const session = url.searchParams.get("session");
1770
- if (!session || !sessions.setConnected(session, true)) {
1771
- sendJson(res, 404, { error: "unknown_session" });
1772
- return;
1773
- }
1774
- res.writeHead(200, {
1775
- "Content-Type": "text/event-stream; charset=utf-8",
1776
- "Cache-Control": "no-store",
1777
- Connection: "keep-alive"
1778
- });
1779
- res.write(": connected\n\n");
1780
- const unsubscribe = commandBus.subscribe(session, (command) => {
1781
- res.write(`data: ${JSON.stringify({ command })}
2055
+ var PLUGIN_ROUTES;
2056
+ var init_api2 = __esm({
2057
+ "src/plugin/api.ts"() {
2058
+ "use strict";
2059
+ init_config();
2060
+ init_http_body();
2061
+ init_assets();
2062
+ init_command_bus();
2063
+ init_sessions();
2064
+ PLUGIN_ROUTES = [
2065
+ {
2066
+ method: "POST",
2067
+ path: "/sessions/register",
2068
+ handler: async ({ req, res }) => {
2069
+ sendJson(res, 201, sessions.register(await readJsonBody(req)));
2070
+ }
2071
+ },
2072
+ {
2073
+ method: "POST",
2074
+ path: "/context",
2075
+ handler: async ({ req, res, url }) => {
2076
+ const session = url.searchParams.get("session");
2077
+ if (!session) {
2078
+ sendJson(res, 400, { error: "missing session" });
2079
+ return;
2080
+ }
2081
+ const meta = await readJsonBody(req);
2082
+ if (!sessions.updateContext(session, meta)) {
2083
+ sendJson(res, 404, { error: "unknown_session" });
2084
+ return;
2085
+ }
2086
+ sendJson(res, 200, { ok: true });
2087
+ }
2088
+ },
2089
+ {
2090
+ method: "GET",
2091
+ path: "/commands/stream",
2092
+ handler: ({ req, res, url }) => {
2093
+ const session = url.searchParams.get("session");
2094
+ if (!session || !sessions.setConnected(session, true)) {
2095
+ sendJson(res, 404, { error: "unknown_session" });
2096
+ return;
2097
+ }
2098
+ res.writeHead(200, {
2099
+ "Content-Type": "text/event-stream; charset=utf-8",
2100
+ "Cache-Control": "no-store",
2101
+ Connection: "keep-alive"
2102
+ });
2103
+ res.write(": connected\n\n");
2104
+ const unsubscribe = commandBus.subscribe(session, (command) => {
2105
+ res.write(`data: ${JSON.stringify({ command })}
1782
2106
 
1783
2107
  `);
1784
- });
1785
- const teardown = () => {
1786
- clearInterval(heartbeat);
1787
- unsubscribe();
1788
- sessions.setConnected(session, false);
1789
- };
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;
2108
+ });
2109
+ const teardown = () => {
2110
+ clearInterval(heartbeat);
2111
+ unsubscribe();
2112
+ sessions.setConnected(session, false);
2113
+ };
2114
+ const heartbeat = setInterval(() => {
2115
+ if (!res.write(": ping\n\n")) {
2116
+ teardown();
2117
+ res.end();
2118
+ log(`SSE ghost reaped: session ${session.slice(0, 8)}`);
2119
+ return;
2120
+ }
2121
+ sessions.touchSeen(session);
2122
+ }, SSE_HEARTBEAT_MS);
2123
+ req.on("close", () => {
2124
+ teardown();
2125
+ log(`SSE channel closed: session ${session.slice(0, 8)}`);
2126
+ });
2127
+ }
2128
+ },
2129
+ {
2130
+ method: "POST",
2131
+ path: "/commands/:id/result",
2132
+ handler: async ({ req, res, params }) => {
2133
+ const payload = await readJsonBody(req);
2134
+ const result = payload.ok && payload.assets?.length ? substituteAssets(payload.result, payload.assets) : payload.result;
2135
+ const found = commandBus.complete(params["id"], payload.ok, payload.ok ? result : payload.error);
2136
+ sendJson(res, found ? 200 : 404, found ? { ok: true } : { error: "unknown command" });
2137
+ }
2138
+ },
2139
+ {
2140
+ method: "POST",
2141
+ path: "/assets",
2142
+ handler: async ({ req, res, url }) => {
2143
+ try {
2144
+ const name = await saveAsset(await readBody(req), url.searchParams.get("format") ?? "PNG");
2145
+ sendJson(res, 201, { name });
2146
+ } catch {
2147
+ sendJson(res, 500, { error: "asset save failed" });
2148
+ }
1796
2149
  }
1797
- sessions.touchSeen(session);
1798
- }, 15e3);
1799
- req.on("close", () => {
1800
- teardown();
1801
- log(`SSE channel closed: session ${session.slice(0, 8)}`);
1802
- });
1803
- }
1804
- },
1805
- {
1806
- method: "POST",
1807
- path: "/commands/:id/result",
1808
- handler: async ({ req, res, params }) => {
1809
- const payload = await readJsonBody(req);
1810
- const result = payload.ok && payload.assets?.length ? substituteAssets(payload.result, payload.assets) : payload.result;
1811
- const found = commandBus.complete(params["id"], payload.ok, payload.ok ? result : payload.error);
1812
- sendJson(res, found ? 200 : 404, found ? { ok: true } : { error: "unknown command" });
1813
- }
1814
- },
1815
- {
1816
- method: "POST",
1817
- path: "/assets",
1818
- handler: async ({ req, res, url }) => {
1819
- try {
1820
- const name = await saveAsset(await readBody(req), url.searchParams.get("format") ?? "PNG");
1821
- sendJson(res, 201, { name });
1822
- } catch {
1823
- sendJson(res, 500, { error: "asset save failed" });
1824
2150
  }
1825
- }
2151
+ ];
1826
2152
  }
1827
- ];
2153
+ });
1828
2154
 
1829
2155
  // src/http.ts
1830
- var ROUTES = [handleInstall, handleMcp, handlePluginApi];
2156
+ import { createServer } from "node:http";
1831
2157
  function startHttpServer() {
1832
2158
  const server = createServer((req, res) => {
1833
2159
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -1844,7 +2170,7 @@ function startHttpServer() {
1844
2170
  const all = sessions.list();
1845
2171
  sendJson(res, 200, {
1846
2172
  ok: true,
1847
- name: HUB_NAME,
2173
+ name: SERVICE_NAME,
1848
2174
  pid: process.pid,
1849
2175
  version: VERSION,
1850
2176
  pluginsOnline: all.filter((s) => s.online).length
@@ -1868,128 +2194,140 @@ function startHttpServer() {
1868
2194
  });
1869
2195
  server.listen(config.httpPort, "127.0.0.1", () => {
1870
2196
  log(`plugin HTTP + MCP server on http://127.0.0.1:${config.httpPort} (MCP endpoint: /mcp, pid ${process.pid})`);
2197
+ scheduleAssetsSweep(config.assetTtlMs);
1871
2198
  resolve(server);
1872
2199
  });
1873
2200
  });
1874
2201
  }
2202
+ var ROUTES;
2203
+ var init_http2 = __esm({
2204
+ "src/http.ts"() {
2205
+ "use strict";
2206
+ init_http();
2207
+ init_config();
2208
+ init_http_body();
2209
+ init_api();
2210
+ init_api2();
2211
+ init_assets();
2212
+ init_sessions();
2213
+ ROUTES = [handleInstall, handleMcp, handlePluginApi];
2214
+ }
2215
+ });
1875
2216
 
1876
- // src/process/hub.ts
1877
- async function runHub() {
2217
+ // src/cli/serve.ts
2218
+ var serve_exports = {};
2219
+ __export(serve_exports, {
2220
+ printUsage: () => printUsage,
2221
+ serve: () => serve
2222
+ });
2223
+ async function serve() {
2224
+ const { state, health } = await probeService();
2225
+ if (state === "alive" && health?.version === VERSION) {
2226
+ printUsage();
2227
+ return;
2228
+ }
2229
+ if (state === "alive") {
2230
+ throw new Error(
2231
+ `\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E14\u7248\u672C\u4E0D\u7B26\uFF08\u8FD0\u884C ${health?.version} / \u5F53\u524D ${VERSION}\uFF09\u2014\u2014\u5148\u6267\u884C --stop \u505C\u6B62\u65E7\u7248\u672C\u518D\u542F\u52A8`
2232
+ );
2233
+ }
2234
+ if (state === "hung" || state === "foreign") {
2235
+ throw serviceStateError(state);
2236
+ }
1878
2237
  const server = await startHttpServer();
1879
2238
  if (!server) {
1880
- log(`\u7AEF\u53E3 ${config.httpPort} \u5DF2\u6709 hub \u5728\u8FD0\u884C\uFF0C\u672C\u8FDB\u7A0B\u9000\u51FA`);
1881
- process.exit(0);
2239
+ throw new Error(
2240
+ `\u7AEF\u53E3 ${config.httpPort} \u5728\u542F\u52A8\u77AC\u95F4\u88AB\u5360\u7528\uFF08\u53EF\u80FD\u662F\u767B\u5F55\u81EA\u542F\u5B9E\u4F8B\u62A2\u5148\u542F\u52A8\u2014\u2014\u91CD\u8DD1\u672C\u547D\u4EE4\u5373\u53EF\u81EA\u52A8\u8BC6\u522B\uFF1B\u82E5\u6301\u7EED\u88AB\u5360\uFF0C${PORT_FIX_HINT}\uFF09`
2241
+ );
1882
2242
  }
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");
1885
- void cleanOldAssets(config.assetTtlMs);
1886
- const sweeper2 = setInterval(() => void cleanOldAssets(config.assetTtlMs), 60 * 6e4);
1887
- sweeper2.unref();
2243
+ printUsage();
1888
2244
  }
1889
-
1890
- // src/process/stdio-proxy.ts
1891
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1892
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1893
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1894
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1895
- import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
1896
- async function startStdioProxy() {
1897
- const mcpUrl = `${hubBaseUrl()}/mcp`;
1898
- let upstream = null;
1899
- async function connectUpstream() {
1900
- const client = new Client(
1901
- { name: "batter-pixso-mcp-proxy", version: VERSION },
1902
- { capabilities: {} }
1903
- );
1904
- await client.connect(new StreamableHTTPClientTransport(new URL(mcpUrl)));
1905
- return client;
1906
- }
1907
- async function ensureUpstream() {
1908
- if (upstream) return upstream;
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);
1926
- upstream = await connectUpstream();
1927
- log(`proxy connected to ${mcpUrl}`);
1928
- return upstream;
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;
2245
+ function printUsage() {
2246
+ const url = mcpUrl();
2247
+ console.log(
2248
+ [
2249
+ ``,
2250
+ `MCP \u5BA2\u6237\u7AEF\u914D\u7F6E\uFF08URL \u76F4\u8FDE\uFF0C\u4E00\u4E2A\u5730\u5740\u5168\u5BA2\u6237\u7AEF\u901A\u7528\uFF09\uFF1A`,
2251
+ ``,
2252
+ ` Claude Code\uFF08\u4E00\u884C\u547D\u4EE4\uFF09\uFF1A`,
2253
+ ` ${claudeAddCommand()}`,
2254
+ ``,
2255
+ ` \u901A\u7528 JSON\uFF08Cursor / Cherry Studio \u7B49\uFF09\uFF1A`,
2256
+ ` { "mcpServers": { "batter-pixso": { "url": "${url}" } } }`,
2257
+ ``,
2258
+ `\u63D2\u4EF6\u5B89\u88C5\uFF1A\u6D4F\u89C8\u5668\u6253\u5F00 ${serviceBaseUrl()}/ \u6309\u5F15\u5BFC\u5B89\u88C5`,
2259
+ `\u5F00\u673A\u81EA\u542F\uFF1Anpx batter-pixso-mcp --setup\uFF08Windows\uFF09`,
2260
+ `\u505C\u6B62\u670D\u52A1\uFF1Anpx batter-pixso-mcp --stop`
2261
+ ].join("\n")
2262
+ );
2263
+ }
2264
+ var init_serve = __esm({
2265
+ "src/cli/serve.ts"() {
2266
+ "use strict";
2267
+ init_config();
2268
+ init_http2();
2269
+ init_service();
1942
2270
  }
1943
- async function callWithRecovery(fn) {
1944
- const client = await ensureUpstream();
1945
- try {
1946
- return await fn(client);
1947
- } catch (err) {
1948
- const alive = await client.ping().then(
1949
- () => true,
1950
- () => false
1951
- );
1952
- if (alive) throw err;
1953
- log("hub \u4F1A\u8BDD\u5931\u8054\uFF0C\u91CD\u5EFA\u540E\u91CD\u8BD5\u2026");
1954
- const rebuilt = await rebuildUpstream();
1955
- return await fn(rebuilt);
2271
+ });
2272
+
2273
+ // src/cli/index.ts
2274
+ init_config();
2275
+
2276
+ // src/cli/commands.ts
2277
+ init_config();
2278
+ var COMMANDS = [
2279
+ {
2280
+ flags: ["--stop"],
2281
+ summary: "\u505C\u6B62\u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1",
2282
+ run: async () => (await Promise.resolve().then(() => (init_service(), service_exports))).stopService()
2283
+ },
2284
+ {
2285
+ flags: ["--version", "-v"],
2286
+ summary: "\u6253\u5370\u7248\u672C\u53F7",
2287
+ run: async () => log(VERSION)
2288
+ },
2289
+ {
2290
+ flags: ["--uninstall"],
2291
+ summary: "\u79FB\u9664\u767B\u5F55\u81EA\u542F + \u505C\u6B62\u670D\u52A1 + \u5220\u9664\u7269\u5316\u76EE\u5F55",
2292
+ run: async () => (await Promise.resolve().then(() => (init_setup(), setup_exports))).uninstallAutostart()
2293
+ },
2294
+ {
2295
+ flags: ["--setup"],
2296
+ summary: "\uFF08Windows\uFF09\u7269\u5316\u81EA\u5305\u542B\u526F\u672C\u5230 %LOCALAPPDATA% \u5E76\u6CE8\u518C\u767B\u5F55\u81EA\u542F\uFF0C\u968F\u540E\u7ACB\u5373\u540E\u53F0\u542F\u52A8\u670D\u52A1\uFF08\u4E0D\u5360\u524D\u53F0\uFF09",
2297
+ run: async () => {
2298
+ await (await Promise.resolve().then(() => (init_setup(), setup_exports))).setupAutostart();
2299
+ (await Promise.resolve().then(() => (init_serve(), serve_exports))).printUsage();
1956
2300
  }
2301
+ },
2302
+ {
2303
+ flags: ["--help", "-h"],
2304
+ summary: "\u6253\u5370\u672C\u5E2E\u52A9",
2305
+ run: async () => printHelp()
1957
2306
  }
1958
- const upstreamClient = await ensureUpstream();
1959
- const server = new Server(
1960
- { name: HUB_NAME, version: VERSION },
1961
- {
1962
- capabilities: upstreamClient.getServerCapabilities() ?? {}
1963
- }
2307
+ ];
2308
+ function printHelp() {
2309
+ console.log(
2310
+ [
2311
+ `batter-pixso-mcp v${VERSION}`,
2312
+ ``,
2313
+ `\u7528\u6CD5\uFF1Abatter-pixso-mcp [\u547D\u4EE4]\uFF08\u4E0D\u5E26\u547D\u4EE4 = \u524D\u53F0\u542F\u52A8\u670D\u52A1\uFF09`,
2314
+ ``,
2315
+ ...COMMANDS.map((c) => ` ${c.flags.join(", ").padEnd(18)}${c.summary}`)
2316
+ ].join("\n")
1964
2317
  );
1965
- server.fallbackRequestHandler = (req, extra) => callWithRecovery((c) => c.request(req, ResultSchema, { signal: extra.signal }));
1966
- server.onclose = () => {
1967
- log("agent disconnected, proxy exiting");
1968
- process.exit(0);
1969
- };
1970
- await server.connect(new StdioServerTransport());
1971
- log("proxy ready on stdio");
1972
2318
  }
1973
-
1974
- // src/process/main.ts
1975
- async function main() {
1976
- if (process.argv.includes("--hub")) {
1977
- await runHub();
1978
- return;
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);
2319
+ async function dispatch(argv) {
2320
+ const hit = COMMANDS.find((c) => c.flags.some((f) => argv.includes(f)));
2321
+ if (hit) {
2322
+ await hit.run();
1986
2323
  return;
1987
2324
  }
1988
- await startStdioProxy();
2325
+ const { serve: serve2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
2326
+ await serve2();
1989
2327
  }
1990
2328
 
1991
- // src/process/index.ts
1992
- main().catch((err) => {
1993
- console.error("[batter-pixso-mcp] fatal:", err);
2329
+ // src/cli/index.ts
2330
+ dispatch(process.argv).catch((err) => {
2331
+ log("fatal:", err);
1994
2332
  process.exit(1);
1995
2333
  });