batter-pixso-mcp 0.5.0 → 0.7.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,210 +1,296 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/plugin/assets.ts
4
- import { createHash } from "node:crypto";
5
- import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
6
- import { join as join2 } from "node:path";
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
+ };
7
11
 
8
12
  // src/config.ts
9
13
  import { tmpdir } from "node:os";
10
14
  import { join } from "node:path";
11
- var VERSION = true ? "0.5.0" : "0.0.0-dev";
12
- var HUB_NAME = "batter-pixso-mcp";
13
- var ASSETS_DIR = join(tmpdir(), "batter-pixso-assets");
14
- var config = {
15
- /** 插件通信 HTTP 端口,仅监听 127.0.0.1。与插件 UI 的 DEFAULT_PORT 互为默认对端,改默认需同步 */
16
- httpPort: Number(process.env.HTTP_PORT ?? 3210),
17
- /** hub 空闲自毁阈值(无插件、无 agent 请求),默认 30 分钟 */
18
- hubIdleMs: Number(process.env.HUB_IDLE_MS ?? 30 * 60 * 1e3),
19
- /** 产物图片保留时长,默认 7 天 */
20
- assetTtlMs: Number(process.env.ASSET_TTL_DAYS ?? 7) * 24 * 60 * 60 * 1e3
21
- };
22
- function hubBaseUrl() {
15
+ function sleep(ms) {
16
+ const { promise, resolve } = Promise.withResolvers();
17
+ setTimeout(resolve, ms);
18
+ return promise;
19
+ }
20
+ function serviceBaseUrl() {
23
21
  return `http://127.0.0.1:${config.httpPort}`;
24
22
  }
23
+ function mcpUrl() {
24
+ return `${serviceBaseUrl()}/mcp`;
25
+ }
26
+ function claudeAddCommand() {
27
+ return `claude mcp add --transport http batter-pixso ${mcpUrl()}`;
28
+ }
25
29
  function log(...args) {
26
30
  console.error("[batter-pixso-mcp]", ...args);
27
31
  }
28
- function withResolvers() {
29
- let resolve;
30
- let reject;
31
- const promise = new Promise((res, rej) => {
32
- resolve = res;
33
- reject = rej;
34
- });
35
- return { promise, resolve, reject };
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.7.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
+ });
57
+
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() {
68
+ try {
69
+ const res = await fetch(`${serviceBaseUrl()}/health`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
70
+ if (!res.ok) return { state: "foreign" };
71
+ let body;
72
+ try {
73
+ body = await res.json();
74
+ } catch {
75
+ return { state: "foreign" };
76
+ }
77
+ if (body.ok === true && body.name === SERVICE_NAME) return { state: "alive", health: body };
78
+ return { state: "foreign" };
79
+ } catch (err) {
80
+ const hung = err instanceof Error && err.name === "TimeoutError";
81
+ return { state: hung ? "hung" : "down" };
82
+ }
83
+ }
84
+ function serviceStateError(state) {
85
+ if (state === "hung") {
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}`);
87
+ }
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`);
89
+ }
90
+ function killServiceByPid(pid) {
91
+ try {
92
+ process.kill(pid);
93
+ return true;
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";
36
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`;
121
+ }
122
+ });
37
123
 
38
- // src/plugin/assets.ts
39
- var EXT_BY_FORMAT = {
40
- PNG: ".png",
41
- SVG: ".svg",
42
- JPG: ".jpg",
43
- JPEG: ".jpg",
44
- WEBP: ".webp",
45
- PDF: ".pdf"
46
- // export_image 尊重设计师导出标记,标记可能指定 PDF
47
- };
48
- var dirReady = null;
49
- async function saveAsset(bytes, format) {
50
- dirReady ??= mkdir(ASSETS_DIR, { recursive: true });
51
- await dirReady;
52
- const ext = EXT_BY_FORMAT[format.toUpperCase()] ?? ".bin";
53
- const name = `${createHash("sha1").update(bytes).digest("hex").slice(0, 16)}${ext}`;
54
- await writeFile(join2(ASSETS_DIR, name), bytes);
55
- log(`asset saved: ${name} (${bytes.length} bytes)`);
56
- return name;
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;
57
139
  }
58
- function substituteAssets(result, assets) {
59
- if (!assets.length) return result;
60
- const nameByKey = new Map(assets.map((a) => [a.key, a.name]));
61
- return JSON.parse(
62
- JSON.stringify(
63
- result,
64
- (_k, v) => typeof v === "string" ? nameByKey.get(v) ?? v : v
65
- )
66
- );
140
+ function pluginDistDir() {
141
+ resolved ??= resolvePluginDir();
142
+ return resolved;
67
143
  }
68
- async function cleanOldAssets(ttlMs) {
69
- let removed = 0;
70
- const cutoff = Date.now() - ttlMs;
71
- let names;
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;
72
159
  try {
73
- names = await readdir(ASSETS_DIR);
160
+ return await readFile(join2(dir, name));
74
161
  } catch {
75
- return 0;
162
+ return null;
163
+ }
164
+ }
165
+ var resolved;
166
+ var init_dist_resolver = __esm({
167
+ "src/install/dist-resolver.ts"() {
168
+ "use strict";
169
+ init_config();
170
+ resolved = null;
76
171
  }
77
- for (const name of names) {
172
+ });
173
+
174
+ // src/cli/setup.ts
175
+ var setup_exports = {};
176
+ __export(setup_exports, {
177
+ setupAutostart: () => setupAutostart,
178
+ uninstallAutostart: () => uninstallAutostart
179
+ });
180
+ import { 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++) {
78
194
  try {
79
- const p = join2(ASSETS_DIR, name);
80
- const s = await stat(p);
81
- if (s.isFile() && s.mtimeMs < cutoff) {
82
- await unlink(p);
83
- removed++;
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
+ );
84
204
  }
85
- } catch {
205
+ await sleep(500);
86
206
  }
87
207
  }
88
- if (removed > 0) log(`assets cleanup: removed ${removed} expired file(s)`);
89
- return removed;
90
208
  }
91
-
92
- // src/plugin/sessions.ts
93
- import { randomUUID } from "node:crypto";
94
- var ONLINE_WINDOW_MS = 45e3;
95
- function unknownTargetError(target) {
96
- return new Error(`\u627E\u4E0D\u5230\u76EE\u6807\u63D2\u4EF6\u4F1A\u8BDD\uFF1A${target}\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u8FDE\u63A5\u7684\u4F1A\u8BDD`);
209
+ async function copyWithRetry(src, dest) {
210
+ await withTransientLockRetry(dest, () => copyFile(src, dest));
97
211
  }
98
- var SessionRegistry = class {
99
- sessions = /* @__PURE__ */ new Map();
100
- lastActiveId = null;
101
- /** 会话亲和:fileKey → 上一次路由使用的 sessionId(仍在线时优先,避免同文件多实例间漂移) */
102
- lastUsedByFileKey = /* @__PURE__ */ new Map();
103
- isOnline(s) {
104
- return s.connected || Date.now() - s.lastSeen < ONLINE_WINDOW_MS;
105
- }
106
- /** fileKey 寻址候选:该文件的在线会话(谁先 pong 由 ping 竞速决定,此处不排序) */
107
- candidatesForFileKey(fileKey) {
108
- return [...this.sessions.values()].filter((s) => s.meta.fileKey === fileKey && this.isOnline(s));
109
- }
110
- /** 亲和查询:上一次为该 fileKey 服务的 sessionId */
111
- affinityIdOf(fileKey) {
112
- return this.lastUsedByFileKey.get(fileKey);
113
- }
114
- /** 亲和钉住:fileKey 寻址探活成功后记录(下次优先) */
115
- pinAffinity(fileKey, sessionId) {
116
- this.lastUsedByFileKey.set(fileKey, sessionId);
117
- }
118
- /** 剔除会话(幽灵剔除/ping 判死共用):清理亲和引用;存活但繁忙被误剔的插件会在 SSE 断开或上报 404 后自重注册 */
119
- evict(id) {
120
- if (this.sessions.delete(id)) {
121
- for (const [k, v] of this.lastUsedByFileKey) {
122
- if (v === id) this.lastUsedByFileKey.delete(k);
123
- }
124
- log(`session evicted: ${id.slice(0, 8)}`);
125
- }
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;
238
+ try {
239
+ return readFileSync(join3(DIR, "version.txt"), "utf8").trim() || null;
240
+ } catch {
241
+ return null;
126
242
  }
127
- register(meta = {}) {
128
- if (meta.fileKey) {
129
- for (const s of [...this.sessions.values()]) {
130
- if (s.meta.fileKey === meta.fileKey && !this.isOnline(s)) {
131
- this.evict(s.id);
132
- }
133
- }
134
- }
135
- const session = { id: randomUUID(), meta, lastSeen: Date.now(), connected: false };
136
- this.sessions.set(session.id, session);
137
- this.lastActiveId = session.id;
138
- log(`session registered: ${session.id} (${meta.fileName ?? "unknown file"})`);
139
- return this.toInfo(session);
140
- }
141
- /** 心跳/连接状态用:只刷 lastSeen,不影响 lastActiveId("最近活跃"= 用户行为,心跳不算) */
142
- touchSeen(id) {
143
- const s = this.sessions.get(id);
144
- if (!s) return null;
145
- s.lastSeen = Date.now();
146
- return s;
147
- }
148
- /** 用户行为(选区上报/命令服务/注册):刷 lastSeen 并标记最近活跃 */
149
- touch(id) {
150
- const s = this.touchSeen(id);
151
- if (!s) return null;
152
- this.lastActiveId = id;
153
- return s;
154
- }
155
- setConnected(id, connected) {
156
- const s = this.touchSeen(id);
157
- if (!s) return false;
158
- s.connected = connected;
159
- return true;
243
+ }
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");
160
247
  }
161
- updateContext(id, meta) {
162
- const s = this.touch(id);
163
- if (!s) return false;
164
- s.meta = { ...s.meta, ...meta };
165
- log(
166
- `context updated [${id.slice(0, 8)}]: ${s.meta.fileName ?? "?"} / ${s.meta.pageName ?? "?"} / ${s.meta.selection?.length ?? 0} selected`
167
- );
168
- return true;
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}`}`);
169
253
  }
170
- list() {
171
- return [...this.sessions.values()].map((s) => this.toInfo(s));
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
+ }
256
+ async function uninstallAutostart() {
257
+ if (process.platform === "win32") {
258
+ spawnSync("reg", ["delete", RUN_KEY, "/v", RUN_VALUE, "/f"]);
259
+ log("\u5DF2\u79FB\u9664\u5F00\u673A\u81EA\u542F\uFF08\u82E5\u66FE\u6CE8\u518C\uFF09");
172
260
  }
173
- /** 纯查找(不判活):工具层寻址用——离线会话也可取到 fileKey 用于候选扩展与故障转移 */
174
- get(id) {
175
- return this.sessions.get(id);
261
+ const result = await stopRunningService();
262
+ if (result === "failed" || result === "hung") {
263
+ log(`\u670D\u52A1\u505C\u6B62\u5931\u8D25\uFF0C\u7269\u5316\u76EE\u5F55\u53EF\u80FD\u5220\u4E0D\u51C0\u3002${PORT_FIX_HINT}`);
176
264
  }
177
- /** 缺省路由:最近活跃在线会话 > 任意在线会话(显式 target 由工具层 get/candidatesForFileKey + ping 完成) */
178
- resolve() {
179
- if (this.lastActiveId) {
180
- const s = this.sessions.get(this.lastActiveId);
181
- if (s && this.isOnline(s)) return s;
265
+ if (DIR && existsSync2(DIR)) {
266
+ try {
267
+ rmSync(DIR, { recursive: true, force: true, maxRetries: 4, retryDelay: 500 });
268
+ } catch {
182
269
  }
183
- const fallback = [...this.sessions.values()].find((s) => this.isOnline(s));
184
- if (fallback) return fallback;
185
- throw new Error("Pixso \u63D2\u4EF6\u672A\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6");
186
- }
187
- toInfo(s) {
188
- return {
189
- sessionId: s.id,
190
- fileKey: s.meta.fileKey,
191
- fileName: s.meta.fileName,
192
- pageName: s.meta.pageName,
193
- selectionCount: s.meta.selection?.length,
194
- online: this.isOnline(s),
195
- lastSeen: s.lastSeen
196
- };
270
+ 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}`);
197
271
  }
198
- };
199
- var sessions = new SessionRegistry();
200
-
201
- // src/http.ts
202
- import { createServer } from "node:http";
203
-
204
- // src/agent/http.ts
205
- import { randomUUID as randomUUID2 } from "node:crypto";
206
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
207
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
272
+ log("\u5378\u8F7D\u5B8C\u6210\uFF08npm \u5168\u5C40/npx \u7F13\u5B58\u4E2D\u7684\u5305\u5982\u9700\u6E05\u7406\u8BF7\u81EA\u884C npm uninstall\uFF09");
273
+ }
274
+ var DIR, RUN_KEY, RUN_VALUE, SERVICE_LOG_NAME, RUN_VBS, TRANSIENT_LOCK_ERRORS;
275
+ var init_setup = __esm({
276
+ "src/cli/setup.ts"() {
277
+ "use strict";
278
+ init_config();
279
+ init_dist_resolver();
280
+ init_service();
281
+ DIR = process.env.LOCALAPPDATA ? join3(process.env.LOCALAPPDATA, "batter-pixso-mcp") : null;
282
+ RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
283
+ RUN_VALUE = "BatterPixsoMcp";
284
+ SERVICE_LOG_NAME = "batter-pixso-mcp-service.log";
285
+ RUN_VBS = [
286
+ "Set fso = CreateObject(" + JSON.stringify("Scripting.FileSystemObject") + ")",
287
+ "dir = fso.GetParentFolderName(WScript.ScriptFullName)",
288
+ // 路径由脚本自身位置推导,不写死绝对路径(用户名含中文/空格时 VBS 字面量会翻车)
289
+ 'CreateObject("Wscript.Shell").Run """" & dir & "\\service.cmd""", 0, False'
290
+ ].join("\r\n");
291
+ TRANSIENT_LOCK_ERRORS = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
292
+ }
293
+ });
208
294
 
209
295
  // src/http-body.ts
210
296
  function sendJson(res, status, body) {
@@ -216,8 +302,6 @@ async function readBody(req) {
216
302
  for await (const chunk of req) chunks.push(chunk);
217
303
  return Buffer.concat(chunks);
218
304
  }
219
- var JsonBodyError = class extends Error {
220
- };
221
305
  async function readJsonBody(req) {
222
306
  const buf = await readBody(req);
223
307
  try {
@@ -226,7 +310,6 @@ async function readJsonBody(req) {
226
310
  throw new JsonBodyError("invalid json");
227
311
  }
228
312
  }
229
- var segsCache = /* @__PURE__ */ new Map();
230
313
  function segsOf(template) {
231
314
  let segs = segsCache.get(template);
232
315
  if (!segs) {
@@ -261,7 +344,7 @@ async function dispatchRoute(routes, req, res, url) {
261
344
  if (err instanceof JsonBodyError) {
262
345
  if (!res.headersSent) sendJson(res, 400, { error: "invalid json" });
263
346
  } else {
264
- console.error("[batter-pixso-mcp] route handler error:", err);
347
+ log("route handler error:", err);
265
348
  if (!res.headersSent) sendJson(res, 500, { error: "internal error" });
266
349
  }
267
350
  }
@@ -269,25 +352,30 @@ async function dispatchRoute(routes, req, res, url) {
269
352
  }
270
353
  return false;
271
354
  }
272
-
273
- // src/agent/mcp.ts
274
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
275
- import {
276
- CallToolRequestSchema,
277
- GetPromptRequestSchema,
278
- ListPromptsRequestSchema,
279
- ListResourceTemplatesRequestSchema,
280
- ListToolsRequestSchema
281
- } from "@modelcontextprotocol/sdk/types.js";
355
+ var JsonBodyError, segsCache;
356
+ var init_http_body = __esm({
357
+ "src/http-body.ts"() {
358
+ "use strict";
359
+ init_config();
360
+ JsonBodyError = class extends Error {
361
+ };
362
+ segsCache = /* @__PURE__ */ new Map();
363
+ }
364
+ });
282
365
 
283
366
  // src/agent/guides.ts
284
- var INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
367
+ var INTRO, DESIGN_TO_CODE, GUIDES;
368
+ var init_guides = __esm({
369
+ "src/agent/guides.ts"() {
370
+ "use strict";
371
+ init_config();
372
+ INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
285
373
 
286
374
  ## \u5DE5\u5177\u603B\u89C8
287
375
 
288
376
  | \u5DE5\u5177 | \u7528\u9014 |
289
377
  | --- | --- |
290
- | \`get_selected\` | \u8BFB\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\u3001\u5C3A\u5BF8\uFF09 |
378
+ | \`get_selected\` | \u8BFB\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\uFF09 |
291
379
  | \`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 |
292
380
  | \`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 |
293
381
  | \`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 |
@@ -302,18 +390,18 @@ var INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
302
390
 
303
391
  ## \u591A\u6587\u4EF6\u573A\u666F
304
392
 
305
- \u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u662F\u4E00\u4E2A\u4F1A\u8BDD\u3002\u591A\u4E2A\u6587\u4EF6\u540C\u65F6\u6253\u5F00\u65F6\uFF1A
306
- \u5148\u7528 \`list_sessions\` \u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF0C\u518D\u628A\u8FD4\u56DE\u7684 \`sessionId\` \u6216 \`fileKey\`
307
- \u4F5C\u4E3A\u5176\u4ED6\u5DE5\u5177\u7684 \`target\` \u53C2\u6570\u4F20\u5165\uFF1B\u4E0D\u4F20 \`target\` \u65F6\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u7684\u6587\u4EF6\u3002
393
+ \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
394
+ \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\`
395
+ \u4F5C\u4E3A\u8DEF\u7531\u53C2\u6570\u4F20\u5165\uFF08\u8DE8\u91CD\u5F00\u7A33\u5B9A\uFF0C\u4F18\u5148\u4E8E \`sessionId\`\uFF09\uFF1B\u4EC5 \`get_selected\`/\`list_sessions\` \u53EF\u7F3A\u7701\u8DEF\u7531\u53C2\u6570\u505A\u63A2\u67E5\u3002
308
396
 
309
- **\u4F1A\u8BDD\u89E3\u6790\u4E0E\u4FE1\u5C01**\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u7684\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, data}\`\u2014\u2014\`sessionId\` \u662F\u672C\u6B21\u5B9E\u9645\u670D\u52A1\u7684\u4F1A\u8BDD
310
- \uFF08\u6BCF\u6B21\u8C03\u7528\u7ECF ping/pong \u63A2\u6D3B\u91CD\u65B0\u786E\u8BA4\uFF0C\u4E0D\u662F\u53EF\u957F\u671F\u9489\u4F4F\u7684\u56FA\u5B9A\u6807\u8BC6\uFF09\u3002\u63A8\u8350 \`target\` \u7528 \`fileKey\`\uFF1A
311
- \u6BCF\u6B21\u8C03\u7528\u6309 fileKey \u91CD\u65B0\u5BFB\u5740\u2014\u2014\u540C fileKey \u591A\u4F1A\u8BDD\u65F6\u4F18\u5148\u4E0A\u4E00\u6B21\u4F7F\u7528\u7684\uFF08\u4EB2\u548C\uFF09\uFF0C\u6587\u4EF6\u5173\u95ED\u91CD\u5F00\u3001\u63D2\u4EF6\u91CD\u8DD1\u540E
397
+ **\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
398
+ \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
399
+ \u7528\u5B83\u505A\u8DEF\u7531\u53C2\u6570\uFF1A\u6BCF\u6B21\u8C03\u7528\u6309 fileKey \u91CD\u65B0\u5BFB\u5740\u2014\u2014\u540C fileKey \u591A\u4F1A\u8BDD\u65F6\u4F18\u5148\u4E0A\u4E00\u6B21\u4F7F\u7528\u7684\uFF08\u4EB2\u548C\uFF09\uFF0C\u6587\u4EF6\u5173\u95ED\u91CD\u5F00\u3001\u63D2\u4EF6\u91CD\u8DD1\u540E
312
400
  \u81EA\u52A8\u89E3\u6790\u5230\u5F53\u524D\u5B58\u6D3B\u5B9E\u4F8B\uFF0Cagent \u65E0\u9700\u5173\u5FC3 sessionId \u7684\u5931\u6548\u3002
313
401
 
314
402
  \u8BE6\u7EC6\u5DE5\u4F5C\u6D41\u89C1\u8D44\u6E90 \`batter://guides/design-to-code\`\u3002
315
403
  `;
316
- var DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
404
+ DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
317
405
 
318
406
  ## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011
319
407
 
@@ -336,12 +424,12 @@ var DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
336
424
 
337
425
  1. **\u786E\u8BA4\u76EE\u6807\u8282\u70B9**
338
426
  - \u7528\u6237\u7ED9\u4E86 Pixso \u94FE\u63A5\uFF1A\u6309\u4E0A\u65B9\u786C\u6027\u89C4\u5219\u7B2C 2 \u6761\u63D0\u53D6 guid\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
339
- - \u7528\u6237\u660E\u786E\u8BF4\u4E86"\u8FD9\u4E2A/\u6211\u9009\u4E2D\u7684"\uFF1A\u8C03 \`get_selected\` \u62FF guid\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
427
+ - \u7528\u6237\u660E\u786E\u8BF4\u4E86"\u8FD9\u4E2A/\u6211\u9009\u4E2D\u7684"\uFF1A\u8C03 \`get_selected\` \u62FF guid \u4E0E fileKey\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
340
428
  - \u5176\u4F59\u4F9D\u8D56\u5F53\u524D\u9009\u533A\u7684\u573A\u666F\uFF1A**\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u9009\u533A guid**\u2014\u2014\u8C03 \`get_selected\` \u540E
341
429
  \u5FC5\u987B\u5148\u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08"\u5F53\u524D\u9009\u4E2D\u7684\u662F\u300CXX\u300D\uFF0C\u786E\u8BA4\u5904\u7406\u5B83\u5417\uFF1F"\uFF09\uFF0C\u786E\u8BA4\u540E\u518D\u7EE7\u7EED\u3002
342
- \`get_node_dsl\` / \`get_screenshot\` \u7684 guid \u7F3A\u7701\u56DE\u9000\u540C\u6837\u9002\u7528\u672C\u89C4\u5219\u3002
343
- 2. **\u53D6\u7ED3\u6784**\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, data}\` \u4FE1\u5C01\u2014\u2014DSL \u5728 \`data\` \u5B57\u6BB5\u91CC\u3002
344
- \`get_node_dsl(guid)\` \u62FF\u7CBE\u7B80 DSL\u3002
430
+ \u786E\u8BA4\u540E\u7684\u8C03\u7528\u91CC guid \u7528\u8FD4\u56DE\u7684\u9009\u4E2D\u56FE\u5C42 id\u3001\u8DEF\u7531\u53C2\u6570\u7528\u8FD4\u56DE\u7684 fileKey\uFF08guid \u5FC5\u4F20\u3001fileKey/sessionId \u5FC5\u4F20\u5176\u4E00\uFF09\u3002
431
+ 2. **\u53D6\u7ED3\u6784**\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, fileKey, data}\` \u4FE1\u5C01\u2014\u2014DSL \u5728 \`data\` \u5B57\u6BB5\u91CC\u3002
432
+ \`get_node_dsl(guid, fileKey)\` \u62FF\u7CBE\u7B80 DSL\uFF08guid \u5FC5\u4F20\uFF0CfileKey/sessionId \u5FC5\u4F20\u5176\u4E00\uFF09\u3002
345
433
  **\u9996\u6B21\u63A5\u89E6\u4E00\u4E2A\u8BBE\u8BA1\u7A3F/\u9875\u9762**\uFF08\u8FD8\u6CA1\u6709\u76EE\u6807\u8282\u70B9\u7684\u4E0A\u4E0B\u6587\u65F6\uFF09\uFF1A\u5148 \`get_metadata(guid)\`
346
434
  \u62FF\u5168\u6811\u9AA8\u67B6\uFF08\u4EC5 id/name/type/kids\uFF0C\u65E0\u6837\u5F0F/\u5750\u6807/\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09+ \`get_screenshot(guid)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF0C
347
435
  \u636E\u6B64\u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF0C\u518D\u5BF9\u76EE\u6807\u6B63\u5E38\u53D6 DSL\u3002\u9AA8\u67B6\u4E2D ICON/IMAGE \u53F6\u5B50\u65E0 \`img\`\u2014\u2014\u9700\u8D44\u6E90\u65F6\u6309\u5176 \`id\` \u4E0B\u94BB\u5168\u91CF DSL\u3002
@@ -407,7 +495,7 @@ DSL \u662F\u5355\u884C\u7D27\u51D1 JSON\uFF1A\u77ED key\u3001\u4F4D\u7F6E\u6570\
407
495
  - **\u7F3A guid**\uFF1A\u5411\u7528\u6237\u8981\u5B8C\u6574\u7684 Pixso \u94FE\u63A5\uFF08\u542B item-id \u67E5\u8BE2\u53C2\u6570\uFF09\uFF0C\u4E0D\u8981\u731C\u6D4B\u8282\u70B9 id\u3002
408
496
  - **\u547D\u4EE4\u8D85\u65F6/\u7ED3\u679C\u8FC7\u5927**\uFF1A\u5BF9\u76EE\u6807\u8282\u70B9\u7684\u5B50\u8282\u70B9\u7528 \`get_node_dsl\` + \u8F83\u4F4E \`maxDepth\` \u5206\u6BB5\u53D6\uFF0C\u4E0D\u8981\u6574\u6811\u4E00\u6B21\u62C9\u3002
409
497
  - **\u7ED3\u679C\u7591\u4F3C\u8FC7\u671F**\uFF08\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\uFF09\uFF1A\u7ED9\u5BFC\u51FA\u7C7B\u5DE5\u5177\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
410
- - **\u63D2\u4EF6\u65E0\u54CD\u5E94**\uFF08ping \u63A2\u6D4B\u8D85\u65F6\uFF09\uFF1A\u63D2\u4EF6\u672A\u8FD0\u884C\u6216\u6B63\u5728\u6267\u884C\u957F\u547D\u4EE4\u2014\u2014\u7A0D\u540E\u91CD\u8BD5\uFF1B\u6587\u4EF6\u91CD\u5F00\u573A\u666F \`target\` \u7528 fileKey \u4F1A\u81EA\u52A8\u89E3\u6790\u5230\u65B0\u5B9E\u4F8B\u3002
498
+ - **\u63D2\u4EF6\u65E0\u54CD\u5E94**\uFF08ping \u63A2\u6D4B\u8D85\u65F6\uFF09\uFF1A\u63D2\u4EF6\u672A\u8FD0\u884C\u6216\u6B63\u5728\u6267\u884C\u957F\u547D\u4EE4\u2014\u2014\u7A0D\u540E\u91CD\u8BD5\uFF1B\u6587\u4EF6\u91CD\u5F00\u573A\u666F\u7528 fileKey \u5BFB\u5740\u4F1A\u81EA\u52A8\u89E3\u6790\u5230\u65B0\u5B9E\u4F8B\u3002
411
499
  - **\u7981\u6B62\u4EC5\u51ED\u622A\u56FE\u786C\u7F16\u7801**\uFF1A\u622A\u56FE\u662F\u6821\u9A8C\u4E0E\u610F\u56FE\u53C2\u7167\uFF0C\u4E0D\u662F\u7ED3\u6784\u6765\u6E90\u2014\u2014\u7ED3\u6784\u4E00\u5F8B\u8D70 DSL\u3002
412
500
 
413
501
  ## \u7EC4\u4EF6\u5173\u8054\uFF08comp \u5B57\u6BB5\uFF09
@@ -435,148 +523,284 @@ INSTANCE / COMPONENT / COMPONENT_SET \u8282\u70B9\u5E26 \`comp\` \u5B57\u6BB5\uF
435
523
  - DSL \u662F**\u6709\u635F\u7CBE\u7B80**\uFF1A\u7701\u7565\u4E86\u5E03\u5C14\u8FD0\u7B97\u8DEF\u5F84\u3001\u8499\u7248\u7B49\u7EAF\u8BBE\u8BA1\u7EC6\u8282\uFF0C\u89C6\u89C9\u590D\u6742\u8282\u70B9\u4EE5\u622A\u56FE\u4E3A\u51C6\u3002
436
524
  - \u5BFC\u51FA\u7C7B\u5DE5\u5177\uFF08\`get_node_dsl\`/\`get_screenshot\`/\`export_image\`\uFF09\u5E26 15 \u5206\u949F\u7ED3\u679C\u7F13\u5B58\uFF08\u63D2\u4EF6\u4FA7 TTL\uFF09\uFF1B
437
525
  \u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
438
- - \u591A\u6587\u4EF6\u573A\u666F\u6240\u6709\u5DE5\u5177\u90FD\u652F\u6301 \`target\` \u53C2\u6570\uFF08sessionId/fileKey\uFF09\uFF0C\u89C1 \`batter://guides/intro\`\u3002
526
+ - \u591A\u6587\u4EF6\u573A\u666F\u8282\u70B9\u7C7B\u5DE5\u5177\u7528 \`fileKey\`/\`sessionId\` \u53C2\u6570\u8DEF\u7531\uFF08\u4E8C\u9009\u4E00\u5FC5\u4F20\uFF09\uFF0C\u89C1 \`batter://guides/intro\`\u3002
439
527
  `;
440
- var GUIDES = [
441
- {
442
- name: "intro",
443
- uri: "batter://guides/intro",
444
- 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",
445
- text: INTRO
446
- },
447
- {
448
- name: "design-to-code",
449
- uri: "batter://guides/design-to-code",
450
- 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",
451
- text: DESIGN_TO_CODE
528
+ GUIDES = [
529
+ {
530
+ name: "intro",
531
+ uri: "batter://guides/intro",
532
+ 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",
533
+ text: INTRO
534
+ },
535
+ {
536
+ name: "design-to-code",
537
+ uri: "batter://guides/design-to-code",
538
+ 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",
539
+ text: DESIGN_TO_CODE
540
+ }
541
+ ];
452
542
  }
453
- ];
543
+ });
454
544
 
455
545
  // src/agent/prompts.ts
456
- var DESIGN_TO_CODE2 = {
457
- name: "design-to-code",
458
- 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",
459
- arguments: [
460
- {
461
- name: "url",
462
- 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",
463
- required: false
464
- }
465
- ],
466
- render: (args) => {
467
- 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`;
468
- return [
469
- {
470
- role: "user",
471
- content: {
472
- type: "text",
473
- text: [
474
- `\u628A Pixso \u8BBE\u8BA1\u7A3F\u8FD8\u539F\u4E3A\u524D\u7AEF\u4EE3\u7801\u3002`,
475
- ``,
476
- `## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011`,
477
- ``,
478
- `**\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`,
479
- ``,
480
- `## \u6267\u884C\u6D41\u7A0B`,
481
- ``,
482
- `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
483
- `2. \u53D6\u6570\u636E\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, data}\` \u4FE1\u5C01\uFF0C\u53D6 \`data\` \u5B57\u6BB5\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 \`get_metadata(guid)\` \u62FF\u5168\u6811\u9AA8\u67B6\u5B9A\u4F4D\u76EE\u6807 + \`get_screenshot(guid)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF1B\u968F\u540E \`get_node_dsl(guid)\` \u62FF\u5168\u91CF DSL\uFF08\u7ED3\u6784\u5F88\u5927\u65F6\u7528 \`maxDepth\` \u5206\u6BB5\u4E0B\u63A2\u2014\u2014\u622A\u65AD\u5904\u8282\u70B9\u5E26 \`more\` \u6807\u8BB0\u4E0E \`kidsSummary\` \u6D45\u9884\u89C8\uFF0C\u62FF\u5176 \`id\` \u56DE\u4F20 \`guid\` \u4E0B\u94BB\uFF09\u3002DSL \u4E2D\u5DF2\u5E26 \`img\` \u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u518D\u8C03 \`export_image\`\u3002`,
484
- `3. \u751F\u6210\u4EE3\u7801\uFF1A\u6309\u6307\u5357\u4E2D\u7684 DSL \u5B57\u6BB5\u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002`,
485
- `4. \u81EA\u68C0\uFF1A\u518D\u6B21\u8C03 \`get_screenshot(guid)\`\uFF0C\u4E0E\u4EE3\u7801\u6E32\u67D3\u7ED3\u679C\u5BF9\u6BD4\uFF0C\u6709\u504F\u5DEE\u5C31\u4FEE\u6B63\u540E\u518D\u6BD4\u4E00\u8F6E\uFF08\u5BFC\u51FA\u7C7B\u5DE5\u5177\u5E26 15 \u5206\u949F\u7F13\u5B58\uFF1B\u7ED3\u679C\u7591\u4F3C\u8FC7\u671F\u65F6\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\uFF09\u3002`,
486
- ``,
487
- `**\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`
488
- ].join("\n")
546
+ var DESIGN_TO_CODE2, PROMPTS;
547
+ var init_prompts = __esm({
548
+ "src/agent/prompts.ts"() {
549
+ "use strict";
550
+ DESIGN_TO_CODE2 = {
551
+ name: "design-to-code",
552
+ 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",
553
+ arguments: [
554
+ {
555
+ name: "url",
556
+ 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",
557
+ required: false
489
558
  }
559
+ ],
560
+ render: (args) => {
561
+ 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`;
562
+ return [
563
+ {
564
+ role: "user",
565
+ content: {
566
+ type: "text",
567
+ text: [
568
+ `\u628A Pixso \u8BBE\u8BA1\u7A3F\u8FD8\u539F\u4E3A\u524D\u7AEF\u4EE3\u7801\u3002`,
569
+ ``,
570
+ `## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011`,
571
+ ``,
572
+ `**\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`,
573
+ ``,
574
+ `## \u6267\u884C\u6D41\u7A0B`,
575
+ ``,
576
+ `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
577
+ `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`,
578
+ `3. \u751F\u6210\u4EE3\u7801\uFF1A\u6309\u6307\u5357\u4E2D\u7684 DSL \u5B57\u6BB5\u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002`,
579
+ `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`,
580
+ ``,
581
+ `**\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`
582
+ ].join("\n")
583
+ }
584
+ }
585
+ ];
490
586
  }
491
- ];
587
+ };
588
+ PROMPTS = [DESIGN_TO_CODE2];
492
589
  }
493
- };
494
- var PROMPTS = [DESIGN_TO_CODE2];
590
+ });
495
591
 
496
592
  // src/plugin/command-bus.ts
497
- var CommandBus = class {
498
- sseListeners = /* @__PURE__ */ new Map();
499
- pendingById = /* @__PURE__ */ new Map();
500
- seq = 0;
501
- /** AI 侧:执行命令 */
502
- execute(type, args, opts) {
503
- const timeoutMs = opts.timeoutMs ?? 6e4;
504
- const { promise, resolve, reject } = withResolvers();
505
- const command = { id: `cmd-${++this.seq}`, type, args };
506
- const entry = {
507
- command,
508
- sessionId: opts.sessionId,
509
- resolve,
510
- reject,
511
- dispatched: false,
512
- timer: setTimeout(() => {
593
+ var CommandBus, commandBus;
594
+ var init_command_bus = __esm({
595
+ "src/plugin/command-bus.ts"() {
596
+ "use strict";
597
+ init_config();
598
+ CommandBus = class {
599
+ sseListeners = /* @__PURE__ */ new Map();
600
+ pendingById = /* @__PURE__ */ new Map();
601
+ seq = 0;
602
+ /** AI 侧:执行命令 */
603
+ execute(type, args, opts) {
604
+ const timeoutMs = opts.timeoutMs ?? COMMAND_TIMEOUT_MS;
605
+ const { promise, resolve, reject } = Promise.withResolvers();
606
+ const command = { id: `cmd-${++this.seq}`, type, args };
607
+ const entry = {
608
+ command,
609
+ sessionId: opts.sessionId,
610
+ resolve,
611
+ reject,
612
+ dispatched: false,
613
+ timer: setTimeout(() => {
614
+ this.drop(entry);
615
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
616
+ }, timeoutMs)
617
+ };
618
+ opts.signal?.addEventListener("abort", () => {
619
+ if (!this.pendingById.has(command.id)) return;
620
+ this.drop(entry);
621
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
622
+ });
623
+ this.pendingById.set(command.id, entry);
624
+ this.dispatch(opts.sessionId);
625
+ return promise;
626
+ }
627
+ /** 插件侧:订阅即时推送(SSE 通道),返回退订函数;补发订阅前已入队的命令 */
628
+ subscribe(sessionId, listener) {
629
+ let subs = this.sseListeners.get(sessionId);
630
+ if (!subs) {
631
+ subs = /* @__PURE__ */ new Set();
632
+ this.sseListeners.set(sessionId, subs);
633
+ }
634
+ subs.add(listener);
635
+ this.dispatch(sessionId);
636
+ return () => {
637
+ subs.delete(listener);
638
+ if (subs.size === 0) this.sseListeners.delete(sessionId);
639
+ };
640
+ }
641
+ /** 插件侧:回传结果 */
642
+ complete(id, ok, resultOrError) {
643
+ const entry = this.pendingById.get(id);
644
+ if (!entry) return false;
513
645
  this.drop(entry);
514
- reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
515
- }, timeoutMs)
516
- };
517
- opts.signal?.addEventListener("abort", () => {
518
- if (!this.pendingById.has(command.id)) return;
519
- this.drop(entry);
520
- reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
521
- });
522
- this.pendingById.set(command.id, entry);
523
- this.dispatch(opts.sessionId);
524
- return promise;
525
- }
526
- /** 插件侧:订阅即时推送(SSE 通道),返回退订函数;补发订阅前已入队的命令 */
527
- subscribe(sessionId, listener) {
528
- let subs = this.sseListeners.get(sessionId);
529
- if (!subs) {
530
- subs = /* @__PURE__ */ new Set();
531
- this.sseListeners.set(sessionId, subs);
532
- }
533
- subs.add(listener);
534
- this.dispatch(sessionId);
535
- return () => {
536
- subs.delete(listener);
537
- if (subs.size === 0) this.sseListeners.delete(sessionId);
646
+ if (ok) {
647
+ entry.resolve(resultOrError);
648
+ } else {
649
+ entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
650
+ }
651
+ return true;
652
+ }
653
+ /** 排空该会话全部未派发命令(无订阅者则留待 subscribe 补发) */
654
+ dispatch(sessionId) {
655
+ const subs = this.sseListeners.get(sessionId);
656
+ if (!subs || subs.size === 0) return;
657
+ for (const entry of this.pendingById.values()) {
658
+ if (entry.sessionId !== sessionId || entry.dispatched) continue;
659
+ entry.dispatched = true;
660
+ log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)}`);
661
+ for (const fn of subs) fn(entry.command);
662
+ }
663
+ }
664
+ /** 从 pending 表移除(完成/超时/取消共用) */
665
+ drop(entry) {
666
+ clearTimeout(entry.timer);
667
+ this.pendingById.delete(entry.command.id);
668
+ }
538
669
  };
670
+ commandBus = new CommandBus();
539
671
  }
540
- /** 插件侧:回传结果 */
541
- complete(id, ok, resultOrError) {
542
- const entry = this.pendingById.get(id);
543
- if (!entry) return false;
544
- this.drop(entry);
545
- if (ok) {
546
- entry.resolve(resultOrError);
547
- } else {
548
- entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
549
- }
550
- return true;
551
- }
552
- /** 排空该会话全部未派发命令(无订阅者则留待 subscribe 补发) */
553
- dispatch(sessionId) {
554
- const subs = this.sseListeners.get(sessionId);
555
- if (!subs || subs.size === 0) return;
556
- for (const entry of this.pendingById.values()) {
557
- if (entry.sessionId !== sessionId || entry.dispatched) continue;
558
- entry.dispatched = true;
559
- log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)}`);
560
- for (const fn of subs) fn(entry.command);
561
- }
672
+ });
673
+
674
+ // src/plugin/sessions.ts
675
+ import { randomUUID } from "node:crypto";
676
+ function unknownRouteError(route) {
677
+ if (route.fileKey) {
678
+ 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`);
562
679
  }
563
- /** 从 pending 表移除(完成/超时/取消共用) */
564
- drop(entry) {
565
- clearTimeout(entry.timer);
566
- this.pendingById.delete(entry.command.id);
680
+ 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`);
681
+ }
682
+ var SessionRegistry, sessions;
683
+ var init_sessions = __esm({
684
+ "src/plugin/sessions.ts"() {
685
+ "use strict";
686
+ init_config();
687
+ SessionRegistry = class {
688
+ sessions = /* @__PURE__ */ new Map();
689
+ lastActiveId = null;
690
+ /** 会话亲和:fileKey → 上一次路由使用的 sessionId(仍在线时优先,避免同文件多实例间漂移) */
691
+ lastUsedByFileKey = /* @__PURE__ */ new Map();
692
+ isOnline(s) {
693
+ return s.connected || Date.now() - s.lastSeen < SESSION_ONLINE_WINDOW_MS;
694
+ }
695
+ /** fileKey 寻址候选:该文件的在线会话(谁先 pong 由 ping 竞速决定,此处不排序) */
696
+ candidatesForFileKey(fileKey) {
697
+ return [...this.sessions.values()].filter((s) => s.meta.fileKey === fileKey && this.isOnline(s));
698
+ }
699
+ /** 亲和查询:上一次为该 fileKey 服务的 sessionId */
700
+ affinityIdOf(fileKey) {
701
+ return this.lastUsedByFileKey.get(fileKey);
702
+ }
703
+ /** 亲和钉住:fileKey 寻址探活成功后记录(下次优先) */
704
+ pinAffinity(fileKey, sessionId) {
705
+ this.lastUsedByFileKey.set(fileKey, sessionId);
706
+ }
707
+ /** 剔除会话(幽灵剔除/ping 判死共用):清理亲和引用;存活但繁忙被误剔的插件会在 SSE 断开或上报 404 后自重注册;
708
+ * 失效 sessionId 的 failover 不靠墓碑——工具结果信封带 fileKey,agent 手里的稳定句柄一直是 fileKey */
709
+ evict(id) {
710
+ if (this.sessions.delete(id)) {
711
+ for (const [k, v] of this.lastUsedByFileKey) {
712
+ if (v === id) this.lastUsedByFileKey.delete(k);
713
+ }
714
+ log(`session evicted: ${id.slice(0, 8)}`);
715
+ }
716
+ }
717
+ register(meta = {}) {
718
+ if (meta.fileKey) {
719
+ for (const s of [...this.sessions.values()]) {
720
+ if (s.meta.fileKey === meta.fileKey && !this.isOnline(s)) {
721
+ this.evict(s.id);
722
+ }
723
+ }
724
+ }
725
+ const session = { id: randomUUID(), meta, lastSeen: Date.now(), connected: false };
726
+ this.sessions.set(session.id, session);
727
+ this.lastActiveId = session.id;
728
+ log(`session registered: ${session.id} (${meta.fileName ?? "unknown file"})`);
729
+ return this.toInfo(session);
730
+ }
731
+ /** 心跳/连接状态用:只刷 lastSeen,不影响 lastActiveId("最近活跃"= 用户行为,心跳不算) */
732
+ touchSeen(id) {
733
+ const s = this.sessions.get(id);
734
+ if (!s) return null;
735
+ s.lastSeen = Date.now();
736
+ return s;
737
+ }
738
+ /** 用户行为(选区上报/命令服务/注册):刷 lastSeen 并标记最近活跃 */
739
+ touch(id) {
740
+ const s = this.touchSeen(id);
741
+ if (!s) return null;
742
+ this.lastActiveId = id;
743
+ return s;
744
+ }
745
+ setConnected(id, connected) {
746
+ const s = this.touchSeen(id);
747
+ if (!s) return false;
748
+ s.connected = connected;
749
+ return true;
750
+ }
751
+ updateContext(id, meta) {
752
+ const s = this.touch(id);
753
+ if (!s) return false;
754
+ s.meta = { ...s.meta, ...meta };
755
+ log(
756
+ `context updated [${id.slice(0, 8)}]: ${s.meta.fileName ?? "?"} / ${s.meta.pageName ?? "?"} / ${s.meta.selection?.length ?? 0} selected`
757
+ );
758
+ return true;
759
+ }
760
+ list() {
761
+ return [...this.sessions.values()].map((s) => this.toInfo(s));
762
+ }
763
+ /** 纯查找(不判活):工具层寻址用——离线会话也可取到 fileKey 用于候选扩展与故障转移 */
764
+ get(id) {
765
+ return this.sessions.get(id);
766
+ }
767
+ /** 缺省路由:最近活跃在线会话 > 任意在线会话(显式 fileKey/sessionId 由工具层 get/candidatesForFileKey + ping 完成) */
768
+ resolve() {
769
+ if (this.lastActiveId) {
770
+ const s = this.sessions.get(this.lastActiveId);
771
+ if (s && this.isOnline(s)) return s;
772
+ }
773
+ const fallback = [...this.sessions.values()].find((s) => this.isOnline(s));
774
+ if (fallback) return fallback;
775
+ throw new Error("Pixso \u63D2\u4EF6\u672A\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6");
776
+ }
777
+ toInfo(s) {
778
+ return {
779
+ sessionId: s.id,
780
+ fileKey: s.meta.fileKey,
781
+ fileName: s.meta.fileName,
782
+ pageName: s.meta.pageName,
783
+ selectionCount: s.meta.selection?.length,
784
+ online: this.isOnline(s),
785
+ lastSeen: s.lastSeen
786
+ };
787
+ }
788
+ };
789
+ sessions = new SessionRegistry();
567
790
  }
568
- };
569
- var commandBus = new CommandBus();
791
+ });
570
792
 
571
793
  // src/agent/tools/shared.ts
572
- var PING_TIMEOUT_MS = 800;
573
794
  async function run(type, args, signal) {
574
- const { target, ...rest } = args;
795
+ const { fileKey, sessionId, ...rest } = args;
575
796
  try {
576
- const session = await resolveAndPing(target, signal);
797
+ const session = await resolveAndPing(
798
+ { fileKey, sessionId },
799
+ signal
800
+ );
577
801
  const data = await commandBus.execute(type, rest, { sessionId: session.id, signal });
578
802
  sessions.touch(session.id);
579
- return jsonResult({ sessionId: session.id, data });
803
+ return jsonResult({ sessionId: session.id, fileKey: session.meta.fileKey, data });
580
804
  } catch (err) {
581
805
  return errorResult(err);
582
806
  }
@@ -605,39 +829,25 @@ async function pingCandidates(candidates, affinityId, signal) {
605
829
  return null;
606
830
  }
607
831
  }
608
- async function resolveAndPing(target, signal) {
609
- const s = target ? sessions.get(target) : sessions.resolve();
610
- const fileKey = s ? s.meta.fileKey : target;
832
+ async function resolveAndPing(route, signal) {
833
+ const s = route.sessionId ? sessions.get(route.sessionId) : route.fileKey ? void 0 : sessions.resolve();
834
+ if (route.sessionId && !s) throw unknownRouteError(route);
835
+ const fileKey = s ? s.meta.fileKey : route.fileKey;
611
836
  const candidates = s && !fileKey ? [s] : sessions.candidatesForFileKey(fileKey);
612
837
  if (candidates.length === 0) {
613
838
  if (s) throw new Error(`\u76EE\u6807\u63D2\u4EF6\u5DF2\u79BB\u7EBF\uFF1A${s.meta.fileName ?? s.id}\uFF08\u91CD\u65B0\u6253\u5F00\u6587\u4EF6\u5E76\u8FD0\u884C\u63D2\u4EF6\u540E\u91CD\u8BD5\uFF09`);
614
- throw unknownTargetError(target);
839
+ throw unknownRouteError(route);
615
840
  }
616
841
  const winner = await pingCandidates(candidates, fileKey ? sessions.affinityIdOf(fileKey) : void 0, signal);
617
842
  if (!winner) {
618
843
  for (const c of candidates) sessions.evict(c.id);
619
844
  throw new Error(
620
- `\u63D2\u4EF6\u65E0\u54CD\u5E94\uFF08${PING_TIMEOUT_MS}ms \u672A\u56DE pong\uFF09\uFF1A${fileKey ?? target}\u2014\u2014\u63D2\u4EF6\u672A\u8FD0\u884C\uFF0C\u6216\u6B63\u5728\u6267\u884C\u957F\u547D\u4EE4\uFF08\u7A0D\u540E\u91CD\u8BD5\uFF09`
845
+ `\u63D2\u4EF6\u65E0\u54CD\u5E94\uFF08${PING_TIMEOUT_MS}ms \u672A\u56DE pong\uFF09\uFF1A${fileKey ?? route.sessionId}\u2014\u2014\u63D2\u4EF6\u672A\u8FD0\u884C\uFF0C\u6216\u6B63\u5728\u6267\u884C\u957F\u547D\u4EE4\uFF08\u7A0D\u540E\u91CD\u8BD5\uFF09`
621
846
  );
622
847
  }
623
848
  if (fileKey) sessions.pinAffinity(fileKey, winner.id);
624
849
  return winner;
625
850
  }
626
- var ENVELOPE_NOTE = "\u8FD4\u56DE\u5C01\u88C5\u4E3A {sessionId, data}\uFF08\u9519\u8BEF\u4E3A isError \u6587\u672C\uFF0C\u4E0D\u542B\u4FE1\u5C01\uFF09\uFF1AsessionId \u662F ping/pong \u786E\u8BA4\u7684\u5B9E\u9645\u670D\u52A1\u4F1A\u8BDD\uFF1Btarget \u7528 fileKey \u6700\u7A33\u2014\u2014\u6587\u4EF6\u91CD\u5F00\u540E\u81EA\u52A8\u89E3\u6790\u5230\u5B58\u6D3B\u5B9E\u4F8B\uFF08\u591A\u5B9E\u4F8B\u4F18\u5148\u4E0A\u4E00\u6B21\u4F7F\u7528\u7684\uFF09\u3002";
627
- var GUIDES_PTR = "\u5B8C\u6574\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D/\u7981\u6B62\u622A\u56FE\u786C\u7F16\u7801\uFF09\u89C1\u8D44\u6E90 batter://guides/design-to-code\u3002";
628
- var TARGET_PROP = {
629
- type: "string",
630
- description: "\u76EE\u6807\u63D2\u4EF6\u4F1A\u8BDD\uFF08sessionId \u6216 fileKey\uFF09\u3002\u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u5F00\u63D2\u4EF6\u65F6\u624D\u9700\u8981\uFF1B\u7F3A\u7701\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u6587\u4EF6\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u3002"
631
- };
632
- var CACHE_PROP = {
633
- type: "string",
634
- enum: ["first", "refresh"],
635
- 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"
636
- };
637
- var GUID_PROP = {
638
- type: "string",
639
- description: '\u8282\u70B9 GUID\uFF08\u5982 "297:133032"\uFF09\u3002\u94FE\u63A5\u8F93\u5165\uFF1A\u4ECE item-id \u63D0\u53D6\u5E76 URL \u89E3\u7801\u540E\u4F20\u5165\uFF1B\u7F3A\u7701\u56DE\u9000\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08\u7981\u6B62\u9690\u5F0F\u4F7F\u7528\u2014\u2014\u9009\u4E2D\u786E\u8BA4\u89C4\u5219\u89C1 get_selected\uFF09\u3002'
640
- };
641
851
  function jsonResult(data) {
642
852
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
643
853
  }
@@ -647,151 +857,277 @@ function errorResult(err) {
647
857
  isError: true
648
858
  };
649
859
  }
860
+ var ENVELOPE_NOTE, GUIDES_PTR, FILE_KEY_PROP, SESSION_ID_PROP, CACHE_PROP, GUID_PROP;
861
+ var init_shared = __esm({
862
+ "src/agent/tools/shared.ts"() {
863
+ "use strict";
864
+ init_command_bus();
865
+ init_sessions();
866
+ init_config();
867
+ 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";
868
+ 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";
869
+ FILE_KEY_PROP = {
870
+ type: "string",
871
+ minLength: 1,
872
+ 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"
873
+ };
874
+ SESSION_ID_PROP = {
875
+ type: "string",
876
+ minLength: 1,
877
+ 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"
878
+ };
879
+ CACHE_PROP = {
880
+ type: "string",
881
+ enum: ["first", "refresh"],
882
+ 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"
883
+ };
884
+ GUID_PROP = {
885
+ type: "string",
886
+ minLength: 1,
887
+ 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'
888
+ };
889
+ }
890
+ });
650
891
 
651
892
  // src/agent/tools/export_image.ts
652
- var exportImage = {
653
- description: '\u3010\u7528\u9014\u3011\u5BFC\u51FA\u8282\u70B9\u7684\u56FE\u7247\u7D20\u6750\uFF08\u5207\u56FE\uFF09\uFF0C\u4F9B\u4EA4\u4ED8\u4EE3\u7801\u4F7F\u7528\u3002\u5355\u8282\u70B9\u4F20 guid\uFF1B\u591A\u8282\u70B9\u4F20 guids \u6570\u7EC4\u6279\u91CF\u3002DSL \u4E2D\u5DF2\u5E26 img \u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5728\u53D6 DSL \u65F6\u5DF2\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E25\u7981\u5BF9\u672C\u7C7B\u8282\u70B9\u91CD\u590D\u8C03\u7528\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807/\u5BFC\u51FA\u56FE\u7247\u7D20\u6750/\u628A\u67D0\u4E2A\u8282\u70B9\u5B58\u6210\u56FE\u7247"\u3002\u8981\u89C6\u89C9\u53C2\u7167\u7528 get_screenshot\u3002\u3010\u8FD4\u56DE\u3011\u5355\u8282\u70B9 {width, height, exports: [{img, format, scale?, suffix?}]}\uFF1B\u6279\u91CF {items: [{guid, width, height, exports}]}\u3002\u8282\u70B9\u5E26\u8BBE\u8BA1\u5E08\u5BFC\u51FA\u8BBE\u7F6E\u65F6\u6309\u5176\u8BBE\u7F6E\u9010\u4EFD\u5BFC\u51FA\uFF08format/scale \u53C2\u6570\u5931\u6548\uFF09\uFF1B\u65E0\u6807\u8BB0\u6309\u53C2\u6570\u5BFC\u51FA\u5355\u4EFD\uFF08\u9ED8\u8BA4 PNG\u30012x\uFF09\u3002suffix\uFF08\u5982 "@2x"\uFF09\u590D\u5236\u8FDB\u9879\u76EE\u65F6\u4FDD\u7559\u547D\u540D\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
654
- inputSchema: {
655
- type: "object",
656
- properties: {
657
- guid: GUID_PROP,
658
- guids: {
659
- type: "array",
660
- items: { type: "string" },
661
- 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"
662
- },
663
- format: {
664
- type: "string",
665
- enum: ["PNG", "SVG", "JPG"],
666
- 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"
667
- },
668
- scale: {
669
- type: "number",
670
- 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"
671
- },
672
- cache: CACHE_PROP,
673
- target: TARGET_PROP
674
- }
893
+ var exportImage;
894
+ var init_export_image = __esm({
895
+ "src/agent/tools/export_image.ts"() {
896
+ "use strict";
897
+ init_shared();
898
+ exportImage = {
899
+ 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,
900
+ // 两组互斥各取其一:guid|guids(单节点|批量)× fileKey|sessionId(路由)
901
+ requires: [
902
+ ["guid", "guids"],
903
+ ["fileKey", "sessionId"]
904
+ ],
905
+ properties: {
906
+ guid: GUID_PROP,
907
+ guids: {
908
+ type: "array",
909
+ items: { type: "string" },
910
+ 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"
911
+ },
912
+ format: {
913
+ type: "string",
914
+ enum: ["PNG", "SVG", "JPG"],
915
+ 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"
916
+ },
917
+ scale: {
918
+ type: "number",
919
+ 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"
920
+ },
921
+ cache: CACHE_PROP,
922
+ fileKey: FILE_KEY_PROP,
923
+ sessionId: SESSION_ID_PROP
924
+ }
925
+ };
675
926
  }
676
- };
927
+ });
677
928
 
678
929
  // src/agent/tools/get_metadata.ts
679
- var getMetadata = {
680
- description: '\u3010\u7528\u9014\u3011\u5168\u6811\u7ED3\u6784\u9AA8\u67B6\uFF08\u4EC5 id/name/type/kids\uFF0C\u65E0\u6837\u5F0F/\u5750\u6807/\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\u65F6\uFF1A\u672C\u5DE5\u5177 + get_screenshot \u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF0C\u518D\u5BF9\u76EE\u6807\u8C03 get_node_dsl \u53D6\u5168\u91CF\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1B"\u9875\u9762\u91CC\u6709\u6CA1\u6709 XX"\uFF1B\u6309\u540D\u5B57\u627E\u8282\u70B9\uFF1B\u53EA\u8981\u7ED3\u6784\u6982\u89C8\u4E0D\u8981\u6837\u5F0F\u3002\u3010\u8FD4\u56DE\u3011\u7D27\u51D1 JSON\uFF1A{id, name?, type, kids: \u9012\u5F52}\uFF1BICON/IMAGE \u53F6\u5B50\u65E0 img\uFF08\u9700\u8D44\u6E90\u65F6\u6309 id \u8C03 get_node_dsl\uFF09\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
681
- inputSchema: {
682
- type: "object",
683
- properties: {
684
- guid: GUID_PROP,
685
- cache: CACHE_PROP,
686
- target: TARGET_PROP
687
- }
930
+ var getMetadata;
931
+ var init_get_metadata = __esm({
932
+ "src/agent/tools/get_metadata.ts"() {
933
+ "use strict";
934
+ init_shared();
935
+ getMetadata = {
936
+ 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,
937
+ requires: ["guid", ["fileKey", "sessionId"]],
938
+ properties: {
939
+ guid: GUID_PROP,
940
+ cache: CACHE_PROP,
941
+ fileKey: FILE_KEY_PROP,
942
+ sessionId: SESSION_ID_PROP
943
+ }
944
+ };
688
945
  }
689
- };
946
+ });
690
947
 
691
948
  // src/agent/tools/get_node_dsl.ts
692
- 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';
693
- var DSL_LEGEND = '\u5B57\u6BB5\u56FE\u4F8B\uFF1Aid=\u8282\u70B9 guid\uFF08\u53EF\u56DE\u4F20 guid \u4E0B\u94BB\uFF09\uFF1Bname=\u56FE\u5C42\u540D\uFF08\u81EA\u52A8\u547D\u540D\u5DF2\u7701\u7565\uFF09\uFF1Btype=\u8282\u70B9\u7C7B\u578B\uFF08ICON/IMAGE \u4E3A\u584C\u7F29\u53F6\u5B50\uFF09\uFF1Brect=[x,y,w,h] \u7EDD\u5BF9\u5B9A\u4F4D\uFF1Blayout={dir:row|column, gap, pad:[\u4E0A,\u53F3,\u4E0B,\u5DE6], align:[\u4E3B\u8F74,\u4EA4\u53C9\u8F74], sizing:[hug|fixed|fill]\xD72}\uFF1Bconstraints={h,v}\uFF08\u9ED8\u8BA4 left/top \u7701\u7565\uFF09\uFF1Btext={content, size, font, weight, lh, align, color, deco, ls, link, segs=\u6DF7\u6392\u5206\u6BB5\uFF08\u4EC5\u5DEE\u5F02\u5B57\u6BB5\uFF0C\u987A\u5E8F\u5373\u5B57\u7B26\u504F\u79FB\uFF09}\uFF1Bimg=\u8D44\u6E90\u6587\u4EF6\u540D\uFF1Bkids=\u5B50\u8282\u70B9\u9012\u5F52\uFF1Bpalette=\u8C03\u8272\u677F\uFF08\u4EC5\u6839\u8282\u70B9\uFF09\uFF1A\u22652 \u6B21\u7684\u5355\u8272\u63D0\u53D6\u4E3A\u6570\u7EC4\uFF0Cfills/text.color/stroke \u4E2D "p0""p1" \u4E3A\u4E0B\u6807\u5F15\u7528\uFF08\u590D\u5408 CSS \u4E32\u5185\u989C\u8272\u4E0D\u67E5\u8868\uFF09\uFF1Bnotes=\u8BBE\u8BA1\u6807\u6CE8\u6587\u672C\uFF08\u4EC5\u5B58\u5728\u65F6\uFF09\uFF1Bcomp=\u7EC4\u4EF6\u5173\u8054\uFF08INSTANCE/COMPONENT/COMPONENT_SET\uFF09\uFF1Akey=\u8DE8\u6587\u4EF6\u7A33\u5B9A\u6807\u8BC6\uFF0Cname=\u7EC4\u4EF6\u540D\uFF08\u6620\u5C04\u6743\u91CD\u6700\u9AD8\u7684\u4F9D\u636E\u2014\u2014\u5728\u9879\u76EE\u7EC4\u4EF6\u5E93\u627E\u5BF9\u5E94\uFF0C\u4E0D\u9010\u5B57\u76F4\u7528\uFF09\uFF0Cprops=variant \u5C5E\u6027\uFF0Cset=variant \u7EC4\uFF1Bdepth=\u6574\u6811\u5B9E\u9645\u6DF1\u5EA6\uFF08\u4EC5\u6839\u8282\u70B9\uFF0C\u603B\u662F\u8F93\u51FA\uFF09\uFF1Bmore=\u622A\u65AD\u5904\u5269\u4F59\u5C42\u7EA7\u6570\uFF08\u4EC5 maxDepth \u622A\u65AD\u65F6\uFF0C\u62FF id \u56DE\u4F20 guid \u4E0B\u94BB\uFF09\uFF1BkidsSummary=\u622A\u65AD\u5904\u6216\u584C\u7F29\u53F6\u5B50\uFF08img \u8282\u70B9\uFF09\u7684\u76F4\u63A5\u5B50\u8282\u70B9\u6D45\u9884\u89C8 [{id, name?, type}]\uFF08\u8F85\u52A9\u9009\u62E9\u4E0B\u94BB\u5206\u652F\uFF09\u3002\u7701\u7565\u89C4\u5219\uFF1A\u9ED8\u8BA4\u503C\u4E0D\u8F93\u51FA\uFF08text.align=left\u3001lh=normal\u3001weight=Regular\u3001gap=0\u3001pad \u5168 0\uFF09\u3002';
694
- var getNodeDsl = {
695
- description: '\u3010\u7528\u9014\u3011\u751F\u6210\u8282\u70B9\u7684\u5F00\u53D1\u5411\u7D27\u51D1 DSL\uFF08\u7ED3\u6784+\u6837\u5F0F+\u8D44\u6E90\u6587\u4EF6\u540D\uFF09\uFF0C\u8BBE\u8BA1\u8F6C\u4EE3\u7801\u7684\u6838\u5FC3\u5165\u53E3\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801/\u6839\u636E\u8BBE\u8BA1\u7A3F\u5199\u9875\u9762\u3001\u7EC4\u4EF6"\uFF0C\u6216\u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF09\u65F6\uFF0C\u9ED8\u8BA4\u4ECE\u672C\u5DE5\u5177\u5F00\u59CB\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 get_metadata \u9AA8\u67B6 + get_screenshot \u89C6\u89C9\uFF0C\u5B9A\u4F4D\u76EE\u6807\u540E\u518D\u7528\u672C\u5DE5\u5177\u3002\u7ED3\u6784\u590D\u6742\uFF1AmaxDepth \u4F20 0 \u9884\u5224\u89C4\u6A21\uFF08\u6839\u8282\u70B9 depth=\u6574\u6811\u6DF1\u5EA6\uFF09\uFF0C\u518D\u5BF9\u5E26 more \u6807\u8BB0\u7684\u5B50\u6811\u6309 id \u4E0B\u94BB\uFF08kidsSummary \u8F85\u52A9\u9009\u5206\u652F\uFF09\u3002\u3010\u8FD4\u56DE\u3011\u5355\u884C\u7D27\u51D1 JSON\u3002' + DSL_LEGEND + "\u584C\u7F29\u53F6\u5B50\uFF1A\u7EAF\u77E2\u91CF\u7EC4\u4EF6\u5B9E\u4F8B\u2192SVG\u3001\u56FE\u7247\u586B\u5145\u2192PNG\u3001\u8BBE\u8BA1\u5E08\u6807\u8BB0\u6309\u5176\u8BBE\u7F6E\uFF1B\u5DF2\u5E26 img \u7684\u8282\u70B9\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u52FF\u518D\u8C03 export_image\u3002DSL \u6709\u635F\u7CBE\u7B80\uFF08\u7701\u7565\u8499\u7248/\u5E03\u5C14\u8DEF\u5F84\u7B49\uFF09\uFF0C\u89C6\u89C9\u590D\u6742\u8282\u70B9\u914D\u5408 get_screenshot \u5BF9\u7167\u3002\u3010\u786C\u6027\u89C4\u5219\u3011" + URL_RULE + " " + GUIDES_PTR + " " + ENVELOPE_NOTE,
696
- inputSchema: {
697
- type: "object",
698
- properties: {
699
- guid: GUID_PROP,
700
- maxDepth: {
701
- type: "number",
702
- 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"
703
- },
704
- cache: CACHE_PROP,
705
- target: TARGET_PROP
706
- }
949
+ var URL_RULE, DSL_LEGEND, getNodeDsl;
950
+ var init_get_node_dsl = __esm({
951
+ "src/agent/tools/get_node_dsl.ts"() {
952
+ "use strict";
953
+ init_shared();
954
+ 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';
955
+ 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';
956
+ getNodeDsl = {
957
+ 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,
958
+ requires: ["guid", ["fileKey", "sessionId"]],
959
+ properties: {
960
+ guid: GUID_PROP,
961
+ maxDepth: {
962
+ type: "number",
963
+ 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"
964
+ },
965
+ cache: CACHE_PROP,
966
+ fileKey: FILE_KEY_PROP,
967
+ sessionId: SESSION_ID_PROP
968
+ }
969
+ };
707
970
  }
708
- };
971
+ });
709
972
 
710
973
  // src/agent/tools/get_screenshot.ts
711
- var getScreenshot = {
712
- description: '\u3010\u7528\u9014\u3011\u5BFC\u51FA\u5355\u4E2A\u8282\u70B9\u7684\u622A\u56FE\uFF0C\u4F5C\u89C6\u89C9\u53C2\u7167\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u770B\u770B\u8FD9\u4E2A\u8BBE\u8BA1\u7A3F\u957F\u4EC0\u4E48\u6837"/"\u7ED9\u6211\u622A\u56FE"\uFF1B\u8FD8\u539F\u4EFB\u52A1\u9700\u8981\u89C6\u89C9\u53C2\u7167\u6216\u8FD8\u539F\u5EA6\u6821\u9A8C\u3002\u8981\u5207\u56FE\u7D20\u6750\u7528 export_image\u3002\u751F\u6210\u4EE3\u7801\u540E\u53EF\u518D\u6B21\u8C03\u7528\u672C\u5DE5\u5177\u5BF9\u6BD4\u8FD8\u539F\u5EA6\uFF1B\u5EFA\u8BAE\u4E0E get_node_dsl \u914D\u5408\uFF1ADSL \u5B9A\u7ED3\u6784\uFF0C\u622A\u56FE\u6821\u9A8C\u89C6\u89C9\u3002\u3010\u8FD4\u56DE\u3011{img: \u4EA7\u7269\u6587\u4EF6\u540D, width, height, format}\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
713
- inputSchema: {
714
- type: "object",
715
- properties: {
716
- guid: GUID_PROP,
717
- format: {
718
- type: "string",
719
- enum: ["PNG", "SVG"],
720
- 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"
721
- },
722
- scale: {
723
- type: "number",
724
- description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
725
- },
726
- cache: CACHE_PROP,
727
- target: TARGET_PROP
728
- }
974
+ var getScreenshot;
975
+ var init_get_screenshot = __esm({
976
+ "src/agent/tools/get_screenshot.ts"() {
977
+ "use strict";
978
+ init_shared();
979
+ getScreenshot = {
980
+ 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,
981
+ requires: ["guid", ["fileKey", "sessionId"]],
982
+ properties: {
983
+ guid: GUID_PROP,
984
+ format: {
985
+ type: "string",
986
+ enum: ["PNG", "SVG"],
987
+ 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"
988
+ },
989
+ scale: {
990
+ type: "number",
991
+ description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
992
+ },
993
+ cache: CACHE_PROP,
994
+ fileKey: FILE_KEY_PROP,
995
+ sessionId: SESSION_ID_PROP
996
+ }
997
+ };
729
998
  }
730
- };
999
+ });
731
1000
 
732
1001
  // src/agent/tools/get_selected.ts
733
- var getSelected = {
734
- description: '\u3010\u7528\u9014\u3011\u5B9E\u65F6\u8BFB\u53D6 Pixso \u753B\u5E03\u5F53\u524D\u9009\u4E2D\u7684\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\uFF09\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u7528\u6237\u8BF4"\u8FD9\u4E2A/\u5F53\u524D\u9009\u4E2D\u7684"\u56FE\u5C42\u3001\u7EC4\u4EF6\u3002\u5DF2\u7ED9 Pixso \u94FE\u63A5\u65F6\u4E0D\u8981\u7528\u672C\u5DE5\u5177\u2014\u2014\u76F4\u63A5\u4ECE\u94FE\u63A5\u63D0\u53D6 guid\uFF08\u89C1 get_node_dsl\uFF09\u3002\u3010\u8FD4\u56DE\u3011{fileKey, fileName, pageName, selection: [{id, name, type}]}\u3002\u3010\u786C\u6027\u89C4\u5219\u3011\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u8FD4\u56DE\u7684 guid\uFF1A\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\u5FC5\u987B\u5148\u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08"\u5F53\u524D\u9009\u4E2D\u7684\u662F\u300CXX\u300D\uFF0C\u786E\u8BA4\u5904\u7406\u5B83\u5417\uFF1F"\uFF09\uFF0C\u786E\u8BA4\u540E\u624D\u80FD\u4F20\u7ED9\u5176\u4ED6\u5DE5\u5177\u3002 ' + ENVELOPE_NOTE,
735
- inputSchema: { type: "object", properties: { target: TARGET_PROP } }
736
- };
1002
+ var getSelected;
1003
+ var init_get_selected = __esm({
1004
+ "src/agent/tools/get_selected.ts"() {
1005
+ "use strict";
1006
+ init_shared();
1007
+ getSelected = {
1008
+ 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,
1009
+ properties: { fileKey: FILE_KEY_PROP, sessionId: SESSION_ID_PROP }
1010
+ };
1011
+ }
1012
+ });
737
1013
 
738
1014
  // src/agent/tools/list_sessions.ts
739
- var listSessions = {
740
- description: "\u3010\u7528\u9014\u3011\u5217\u51FA\u5B58\u6D3B\u7684\u8BBE\u8BA1\u7A3F\u63D2\u4EF6\u4F1A\u8BDD\uFF08sessionId\u3001\u6587\u4EF6\u3001\u9875\u9762\u3001\u9009\u4E2D\u6570\uFF09\u2014\u2014\u6BCF\u6B21\u8C03\u7528\u7ECF ping/pong \u5B9E\u6D4B\uFF0C\u65E0\u54CD\u5E94\u7684\u4E0D\u51FA\u73B0\u3002\u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u5BF9\u5E94\u4E00\u4E2A\u4F1A\u8BDD\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u5F00\u63D2\u4EF6\u65F6\uFF0C\u5148\u7528\u672C\u5DE5\u5177\u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF1B\u53EA\u6709\u4E00\u4E2A\u4F1A\u8BDD\u65F6\u65E0\u9700\u5173\u6CE8\u672C\u5DE5\u5177\u3002\u3010\u8FD4\u56DE\u3011\u4F1A\u8BDD\u5217\u8868 JSON\u3002\u628A sessionId \u6216 fileKey \u4F5C\u4E3A\u5176\u4ED6\u5DE5\u5177\u7684 target \u53C2\u6570\u4F20\u5165\u5373\u8DEF\u7531\u5230\u5BF9\u5E94\u6587\u4EF6\uFF1B\u4E0D\u4F20 target \u65F6\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u7684\u6587\u4EF6\u3002\u8BE6\u89C1\u8D44\u6E90 batter://guides/intro\u3002",
741
- inputSchema: { type: "object", properties: {} },
742
- handler: async () => {
743
- const list = sessions.list();
744
- if (!list.length) {
745
- return {
746
- 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" }]
747
- };
748
- }
749
- const checks = await Promise.all(
750
- list.map(async (info) => info.online && await pingSession(info.sessionId) ? info : null)
751
- );
752
- const alive = checks.filter((c) => c !== null);
753
- for (const info of list) {
754
- if (!alive.includes(info)) sessions.evict(info.sessionId);
755
- }
756
- if (!alive.length) {
757
- return {
758
- 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" }]
759
- };
1015
+ var listSessions;
1016
+ var init_list_sessions = __esm({
1017
+ "src/agent/tools/list_sessions.ts"() {
1018
+ "use strict";
1019
+ init_sessions();
1020
+ init_shared();
1021
+ listSessions = {
1022
+ 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",
1023
+ properties: {},
1024
+ handler: async () => {
1025
+ const list = sessions.list();
1026
+ if (!list.length) {
1027
+ return {
1028
+ 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" }]
1029
+ };
1030
+ }
1031
+ const checks = await Promise.all(
1032
+ list.map(async (info) => info.online && await pingSession(info.sessionId) ? info : null)
1033
+ );
1034
+ const alive = checks.filter((c) => c !== null);
1035
+ for (const info of list) {
1036
+ if (!alive.includes(info)) sessions.evict(info.sessionId);
1037
+ }
1038
+ if (!alive.length) {
1039
+ return {
1040
+ 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" }]
1041
+ };
1042
+ }
1043
+ return jsonResult(
1044
+ alive.map(({ sessionId, fileKey, fileName, pageName, selectionCount }) => ({
1045
+ sessionId,
1046
+ fileKey,
1047
+ fileName,
1048
+ pageName,
1049
+ selectionCount
1050
+ }))
1051
+ );
1052
+ }
1053
+ };
1054
+ }
1055
+ });
1056
+
1057
+ // src/agent/tools/index.ts
1058
+ function buildInputSchema(requires, properties) {
1059
+ const singles = requires.filter((r) => typeof r === "string");
1060
+ const xorGroups = requires.filter((r) => Array.isArray(r));
1061
+ const groupSchemas = xorGroups.map((g) => ({ oneOf: g.map((p) => ({ required: [p] })) }));
1062
+ return {
1063
+ type: "object",
1064
+ ...singles.length ? { required: singles } : {},
1065
+ ...groupSchemas.length === 1 ? groupSchemas[0] : {},
1066
+ ...groupSchemas.length > 1 ? { allOf: groupSchemas } : {},
1067
+ properties
1068
+ };
1069
+ }
1070
+ function validateArgs(requires, args) {
1071
+ const present = (k) => args[k] !== void 0 && args[k] !== null && args[k] !== "";
1072
+ for (const req of requires) {
1073
+ if (typeof req === "string") {
1074
+ if (!present(req)) return `\u7F3A\u5C11\u5FC5\u4F20\u53C2\u6570\uFF1A${req}`;
1075
+ continue;
760
1076
  }
761
- return jsonResult(
762
- alive.map(({ sessionId, fileKey, fileName, pageName, selectionCount }) => ({
763
- sessionId,
764
- fileKey,
765
- fileName,
766
- pageName,
767
- selectionCount
768
- }))
1077
+ const given = req.filter(present);
1078
+ if (given.length === 0) return `\u7F3A\u5C11\u53C2\u6570\uFF1A${req.join(" / ")} \u9700\u4F20\u4E00\u4E2A`;
1079
+ if (given.length > 1) return `\u53C2\u6570\u4E92\u65A5\uFF1A${req.join(" / ")} \u53EA\u80FD\u4F20\u4E00\u4E2A`;
1080
+ }
1081
+ return null;
1082
+ }
1083
+ var TOOL_REGISTRY, TOOLS;
1084
+ var init_tools = __esm({
1085
+ "src/agent/tools/index.ts"() {
1086
+ "use strict";
1087
+ init_export_image();
1088
+ init_get_metadata();
1089
+ init_get_node_dsl();
1090
+ init_get_screenshot();
1091
+ init_get_selected();
1092
+ init_list_sessions();
1093
+ init_shared();
1094
+ TOOL_REGISTRY = {
1095
+ get_selected: getSelected,
1096
+ get_node_dsl: getNodeDsl,
1097
+ get_metadata: getMetadata,
1098
+ export_image: exportImage,
1099
+ get_screenshot: getScreenshot,
1100
+ list_sessions: listSessions
1101
+ };
1102
+ TOOLS = Object.fromEntries(
1103
+ Object.entries(TOOL_REGISTRY).map(([name, def]) => {
1104
+ const requires = def.requires ?? [];
1105
+ const inputSchema = buildInputSchema(requires, def.properties);
1106
+ const base = def.handler ?? ((args, ctx) => run(name, args, ctx?.signal));
1107
+ const handler = (args, ctx) => {
1108
+ const err = requires.length ? validateArgs(requires, args) : null;
1109
+ if (err) return Promise.resolve(errorResult(err));
1110
+ return base(args, ctx);
1111
+ };
1112
+ const resolvedDef = { description: def.description, inputSchema, handler };
1113
+ return [name, { def: resolvedDef, tool: { name, description: def.description, inputSchema } }];
1114
+ })
769
1115
  );
770
1116
  }
771
- };
772
-
773
- // src/agent/tools/index.ts
774
- var TOOL_REGISTRY = {
775
- get_selected: getSelected,
776
- get_node_dsl: getNodeDsl,
777
- get_metadata: getMetadata,
778
- export_image: exportImage,
779
- get_screenshot: getScreenshot,
780
- list_sessions: listSessions
781
- };
782
- var TOOLS = Object.fromEntries(
783
- Object.entries(TOOL_REGISTRY).map(([name, def]) => {
784
- const handler = def.handler ?? ((args, ctx) => run(name, args, ctx?.signal));
785
- const resolvedDef = { description: def.description, inputSchema: def.inputSchema, handler };
786
- return [name, { def: resolvedDef, tool: { name, description: def.description, inputSchema: def.inputSchema } }];
787
- })
788
- );
1117
+ });
789
1118
 
790
1119
  // src/agent/mcp.ts
791
- var INSTRUCTIONS = `Pixso \u8BBE\u8BA1\u7A3F\u8F6C\u4EE3\u7801\u670D\u52A1\u3002\u6309\u7528\u6237\u8F93\u5165\u8DEF\u7531\uFF1A1. \u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF09\u2192 \u4ECE item-id \u63D0\u53D6 guid\uFF08URL \u89E3\u7801\uFF09\uFF0C\u76F4\u63A5\u8C03 get_node_dsl / get_screenshot / export_image\uFF1B2. \u8BF4"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\u2192 get_selected \u62FF guid\uFF1B\u5176\u4ED6\u4F9D\u8D56\u9009\u533A\u7684\u573A\u666F\u5148 get_selected \u518D\u5411\u7528\u6237\u62A5\u540D\u79F0\u786E\u8BA4\uFF1B3. "\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801"\u2192 get_node_dsl \u53D6\u7ED3\u6784 + get_screenshot \u53D6\u89C6\u89C9\u53C2\u7167\uFF1B4. "\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807\u6216\u56FE\u7247\u7D20\u6750"\u2192 export_image\uFF08DSL \u91CC\u5DF2\u5E26 img \u7684\u8282\u70B9\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u91CD\u590D\u5BFC\u51FA\uFF09\uFF1B5. \u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F/\u53EA\u8981\u7ED3\u6784\u6982\u89C8 \u2192 get_metadata \u62FF\u5168\u6811\u9AA8\u67B6\uFF08\u65E0\u6837\u5F0F\u65E0\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09\u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF1B\u7ED3\u6784\u590D\u6742 \u2192 get_node_dsl \u7528 maxDepth \u5206\u6BB5\u4E0B\u63A2\uFF08\u622A\u65AD\u5904 more \u6807\u8BB0 + kidsSummary \u6D45\u9884\u89C8\uFF0C\u6309 id \u4E0B\u94BB\uFF09\uFF1B6. \u751F\u6210\u540E\u5BF9\u6BD4\u8FD8\u539F\u5EA6 \u2192 get_screenshot \u6821\u9A8C\u5FAA\u73AF\uFF1B7. \u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u6253\u5F00 \u2192 list_sessions + target \u53C2\u6570\u8DEF\u7531\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`;
1120
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1121
+ import {
1122
+ CallToolRequestSchema,
1123
+ GetPromptRequestSchema,
1124
+ ListPromptsRequestSchema,
1125
+ ListResourceTemplatesRequestSchema,
1126
+ ListToolsRequestSchema
1127
+ } from "@modelcontextprotocol/sdk/types.js";
792
1128
  function createMcpServer() {
793
1129
  const server = new McpServer(
794
- { name: HUB_NAME, version: VERSION },
1130
+ { name: SERVICE_NAME, version: VERSION },
795
1131
  { capabilities: { tools: {}, resources: {}, prompts: {} }, instructions: INSTRUCTIONS }
796
1132
  );
797
1133
  for (const guide of GUIDES) {
@@ -840,21 +1176,55 @@ function createMcpServer() {
840
1176
  });
841
1177
  return server;
842
1178
  }
1179
+ var INSTRUCTIONS;
1180
+ var init_mcp = __esm({
1181
+ "src/agent/mcp.ts"() {
1182
+ "use strict";
1183
+ init_config();
1184
+ init_guides();
1185
+ init_prompts();
1186
+ init_tools();
1187
+ 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`;
1188
+ }
1189
+ });
843
1190
 
844
1191
  // src/agent/sessions.ts
845
- var transports = /* @__PURE__ */ new Map();
846
1192
  function getMcpTransport(sessionId) {
847
- return transports.get(sessionId);
1193
+ const entry = transports.get(sessionId);
1194
+ if (entry) entry.lastSeen = Date.now();
1195
+ return entry?.transport;
848
1196
  }
849
1197
  function registerMcpTransport(sessionId, transport) {
850
- transports.set(sessionId, transport);
1198
+ transports.set(sessionId, { transport, lastSeen: Date.now() });
851
1199
  log(`MCP HTTP session initialized: ${sessionId.slice(0, 8)} (total ${transports.size})`);
852
1200
  transport.onclose = () => {
853
1201
  if (transport.sessionId) transports.delete(transport.sessionId);
854
1202
  };
855
1203
  }
1204
+ var transports, sweeper;
1205
+ var init_sessions2 = __esm({
1206
+ "src/agent/sessions.ts"() {
1207
+ "use strict";
1208
+ init_config();
1209
+ transports = /* @__PURE__ */ new Map();
1210
+ sweeper = setInterval(() => {
1211
+ const now = Date.now();
1212
+ for (const [id, entry] of transports) {
1213
+ if (now - entry.lastSeen <= config.mcpSessionTtlMs) continue;
1214
+ transports.delete(id);
1215
+ void entry.transport.close().catch(() => {
1216
+ });
1217
+ log(`MCP HTTP session expired: ${id.slice(0, 8)} (total ${transports.size})`);
1218
+ }
1219
+ }, MCP_SESSION_SWEEP_MS);
1220
+ sweeper.unref();
1221
+ }
1222
+ });
856
1223
 
857
1224
  // src/agent/http.ts
1225
+ import { randomUUID as randomUUID2 } from "node:crypto";
1226
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1227
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
858
1228
  function handleMcp(req, res, url) {
859
1229
  return dispatchRoute(MCP_ROUTES, req, res, url);
860
1230
  }
@@ -867,95 +1237,54 @@ async function sessionRequestHandler({ req, res }) {
867
1237
  }
868
1238
  await transport.handleRequest(req, res);
869
1239
  }
870
- var MCP_ROUTES = [
871
- {
872
- method: "POST",
873
- path: "/mcp",
874
- handler: async ({ req, res }) => {
875
- let body;
876
- try {
877
- body = await readJsonBody(req);
878
- } catch {
879
- sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
880
- return;
881
- }
882
- const sessionId = req.headers["mcp-session-id"];
883
- let transport = sessionId ? getMcpTransport(sessionId) : void 0;
884
- if (!transport) {
885
- if (!sessionId && isInitializeRequest(body)) {
886
- const t = new StreamableHTTPServerTransport({
887
- sessionIdGenerator: () => randomUUID2(),
888
- onsessioninitialized: (id) => registerMcpTransport(id, t)
889
- });
890
- await createMcpServer().connect(t);
891
- transport = t;
892
- } else {
893
- sendJson(res, 400, {
894
- jsonrpc: "2.0",
895
- error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
896
- id: null
897
- });
898
- return;
1240
+ var MCP_ROUTES;
1241
+ var init_http = __esm({
1242
+ "src/agent/http.ts"() {
1243
+ "use strict";
1244
+ init_http_body();
1245
+ init_mcp();
1246
+ init_sessions2();
1247
+ MCP_ROUTES = [
1248
+ {
1249
+ method: "POST",
1250
+ path: "/mcp",
1251
+ handler: async ({ req, res }) => {
1252
+ let body;
1253
+ try {
1254
+ body = await readJsonBody(req);
1255
+ } catch {
1256
+ sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
1257
+ return;
1258
+ }
1259
+ const sessionId = req.headers["mcp-session-id"];
1260
+ let transport = sessionId ? getMcpTransport(sessionId) : void 0;
1261
+ if (!transport) {
1262
+ if (!sessionId && isInitializeRequest(body)) {
1263
+ const t = new StreamableHTTPServerTransport({
1264
+ sessionIdGenerator: () => randomUUID2(),
1265
+ onsessioninitialized: (id) => registerMcpTransport(id, t)
1266
+ });
1267
+ await createMcpServer().connect(t);
1268
+ transport = t;
1269
+ } else {
1270
+ sendJson(res, 400, {
1271
+ jsonrpc: "2.0",
1272
+ error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
1273
+ id: null
1274
+ });
1275
+ return;
1276
+ }
1277
+ }
1278
+ await transport.handleRequest(req, res, body);
899
1279
  }
900
- }
901
- await transport.handleRequest(req, res, body);
902
- }
903
- },
904
- { method: "GET", path: "/mcp", handler: sessionRequestHandler },
905
- { method: "DELETE", path: "/mcp", handler: sessionRequestHandler }
906
- ];
907
-
908
- // src/install/dist-resolver.ts
909
- import { existsSync } from "node:fs";
910
- import { readFile } from "node:fs/promises";
911
- import { join as join3 } from "node:path";
912
- import { fileURLToPath } from "node:url";
913
- function resolvePluginDir() {
914
- const candidates = [
915
- fileURLToPath(new URL("./plugin/", import.meta.url)),
916
- // bundle: dist/index.js → dist/plugin/
917
- fileURLToPath(new URL("../../plugin/dist/", import.meta.url)),
918
- // bundle 开发: dist/index.js → packages/plugin/dist/
919
- fileURLToPath(new URL("../../../plugin/dist/", import.meta.url))
920
- // tsx 开发: src/install/ → packages/plugin/dist/
921
- ];
922
- return candidates.find((p) => existsSync(join3(p, "manifest.json"))) ?? null;
923
- }
924
- var resolved = null;
925
- function pluginDir() {
926
- resolved ??= resolvePluginDir();
927
- return resolved;
928
- }
929
- function hasPluginDist() {
930
- return pluginDir() !== null;
931
- }
932
- async function pluginManifest() {
933
- const dir = pluginDir();
934
- if (!dir) return null;
935
- const raw = JSON.parse(await readFile(join3(dir, "manifest.json"), "utf8"));
936
- raw.networkAccess = {
937
- allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
938
- };
939
- return Buffer.from(JSON.stringify(raw, null, 2));
940
- }
941
- async function readPluginFile(name) {
942
- const dir = pluginDir();
943
- if (!dir) return null;
944
- try {
945
- return await readFile(join3(dir, name));
946
- } catch {
947
- return null;
1280
+ },
1281
+ { method: "GET", path: "/mcp", handler: sessionRequestHandler },
1282
+ { method: "DELETE", path: "/mcp", handler: sessionRequestHandler }
1283
+ ];
948
1284
  }
949
- }
1285
+ });
950
1286
 
951
1287
  // src/install/template.ts
952
- var HTML_ESCAPES = {
953
- "&": "&amp;",
954
- "<": "&lt;",
955
- ">": "&gt;",
956
- '"': "&quot;",
957
- "'": "&#39;"
958
- };
959
1288
  function escapeHtml(s) {
960
1289
  return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
961
1290
  }
@@ -970,12 +1299,33 @@ function renderTemplate(tpl, vars, escape = escapeHtml) {
970
1299
  };
971
1300
  return tpl.replace(/\{\{\{\s*(\w+)\s*\}\}\}/g, (_, key) => lookup(key)).replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => escape(lookup(key)));
972
1301
  }
1302
+ var HTML_ESCAPES;
1303
+ var init_template = __esm({
1304
+ "src/install/template.ts"() {
1305
+ "use strict";
1306
+ HTML_ESCAPES = {
1307
+ "&": "&amp;",
1308
+ "<": "&lt;",
1309
+ ">": "&gt;",
1310
+ '"': "&quot;",
1311
+ "'": "&#39;"
1312
+ };
1313
+ }
1314
+ });
973
1315
 
974
1316
  // src/install/templates/bookmarklet.txt
975
- 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';
1317
+ var bookmarklet_default;
1318
+ var init_bookmarklet = __esm({
1319
+ "src/install/templates/bookmarklet.txt"() {
1320
+ 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';
1321
+ }
1322
+ });
976
1323
 
977
1324
  // src/install/templates/install-page.html
978
- var install_page_default = `<!DOCTYPE html>
1325
+ var install_page_default;
1326
+ var init_install_page = __esm({
1327
+ "src/install/templates/install-page.html"() {
1328
+ install_page_default = `<!DOCTYPE html>
979
1329
  <html lang="zh-CN">
980
1330
  <head>
981
1331
  <meta charset="utf-8">
@@ -1308,15 +1658,17 @@ var install_page_default = `<!DOCTYPE html>
1308
1658
  <pre id="cfg">{
1309
1659
  "mcpServers": {
1310
1660
  "batter-pixso": {
1311
- "command": "npx",
1312
- "args": ["-y", "batter-pixso-mcp"],
1313
- "env": { "HTTP_PORT": "{{ httpPort }}" }
1661
+ "url": "{{ mcpUrl }}"
1314
1662
  }
1315
1663
  }
1316
1664
  }</pre>
1317
1665
  <button class="copy" id="copy" type="button">\u590D\u5236</button>
1318
1666
  </div>
1319
- <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>
1667
+ <p class="note">
1668
+ \u7C98\u8D34\u5230 Cursor / Cherry Studio \u7B49\u7684 MCP \u914D\u7F6E\u6587\u4EF6\uFF1BClaude Code \u6267\u884C
1669
+ <code>{{ claudeAddCommand }}</code>\u3002
1670
+ \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
1671
+ </p>
1320
1672
  </div>
1321
1673
  </div>
1322
1674
 
@@ -1368,6 +1720,7 @@ copyBtn.addEventListener("click", function () {
1368
1720
  if (navigator.clipboard && navigator.clipboard.writeText) {
1369
1721
  navigator.clipboard.writeText(text).then(done, done);
1370
1722
  } else {
1723
+ // \u515C\u5E95\uFF1A\u65E0 clipboard API \u7684\u73AF\u5883\uFF08\u53CC\u6838\u6D4F\u89C8\u5668\u517C\u5BB9\u6A21\u5F0F\u7B49\uFF09
1371
1724
  var ta = document.createElement("textarea");
1372
1725
  ta.value = text;
1373
1726
  document.body.appendChild(ta);
@@ -1381,6 +1734,8 @@ copyBtn.addEventListener("click", function () {
1381
1734
  </body>
1382
1735
  </html>
1383
1736
  `;
1737
+ }
1738
+ });
1384
1739
 
1385
1740
  // src/install/page.ts
1386
1741
  function bookmarkletHref(base) {
@@ -1388,26 +1743,27 @@ function bookmarkletHref(base) {
1388
1743
  return `javascript:${encodeURIComponent(source)}`;
1389
1744
  }
1390
1745
  function renderInstallPage() {
1391
- const base = `${hubBaseUrl()}/`;
1746
+ const base = `${serviceBaseUrl()}/`;
1392
1747
  return renderTemplate(install_page_default, {
1393
1748
  version: VERSION,
1394
1749
  href: bookmarkletHref(base),
1395
1750
  base,
1396
- httpPort: config.httpPort
1751
+ mcpUrl: mcpUrl(),
1752
+ claudeAddCommand: claudeAddCommand()
1397
1753
  });
1398
1754
  }
1755
+ var init_page = __esm({
1756
+ "src/install/page.ts"() {
1757
+ "use strict";
1758
+ init_config();
1759
+ init_template();
1760
+ init_bookmarklet();
1761
+ init_install_page();
1762
+ }
1763
+ });
1399
1764
 
1400
1765
  // src/install/zip.ts
1401
1766
  import { Buffer as Buffer2 } from "node:buffer";
1402
- var CRC_TABLE = (() => {
1403
- const table = new Uint32Array(256);
1404
- for (let n = 0; n < 256; n++) {
1405
- let c = n;
1406
- for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
1407
- table[n] = c >>> 0;
1408
- }
1409
- return table;
1410
- })();
1411
1767
  function crc32(buf) {
1412
1768
  let crc = 4294967295;
1413
1769
  for (const byte of buf) crc = CRC_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
@@ -1463,9 +1819,23 @@ function buildZip(entries) {
1463
1819
  eocd.writeUInt32LE(offset, 16);
1464
1820
  return Buffer2.concat([...chunks, centralBuf, eocd]);
1465
1821
  }
1822
+ var CRC_TABLE;
1823
+ var init_zip = __esm({
1824
+ "src/install/zip.ts"() {
1825
+ "use strict";
1826
+ CRC_TABLE = (() => {
1827
+ const table = new Uint32Array(256);
1828
+ for (let n = 0; n < 256; n++) {
1829
+ let c = n;
1830
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
1831
+ table[n] = c >>> 0;
1832
+ }
1833
+ return table;
1834
+ })();
1835
+ }
1836
+ });
1466
1837
 
1467
1838
  // src/install/api.ts
1468
- var NO_DIST = "plugin dist not found\uFF08\u8FD0\u884C npm run build \u751F\u6210\uFF09";
1469
1839
  function handleInstall(req, res, url) {
1470
1840
  return dispatchRoute(INSTALL_ROUTES, req, res, url);
1471
1841
  }
@@ -1473,168 +1843,271 @@ function sendBinary(res, buf, contentType) {
1473
1843
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "no-store" });
1474
1844
  res.end(buf);
1475
1845
  }
1476
- var PLUGIN_FILE_ROUTES = [
1477
- { path: "/code.js", file: "code.js", type: "text/javascript; charset=utf-8" },
1478
- { path: "/ui.html", file: "ui.html", type: "text/html; charset=utf-8" }
1479
- ].map(({ path, file, type }) => ({
1480
- method: "GET",
1481
- path,
1482
- handler: async ({ res }) => {
1483
- const buf = await readPluginFile(file);
1484
- if (!buf) {
1485
- sendJson(res, 404, { error: hasPluginDist() ? "not found" : NO_DIST });
1486
- return;
1487
- }
1488
- sendBinary(res, buf, type);
1489
- }
1490
- }));
1491
- var INSTALL_ROUTES = [
1492
- {
1493
- method: "GET",
1494
- path: "/",
1495
- handler: ({ res }) => {
1496
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1497
- res.end(renderInstallPage());
1498
- }
1499
- },
1500
- {
1501
- method: "GET",
1502
- path: "/manifest.json",
1503
- handler: async ({ res }) => {
1504
- const buf = await pluginManifest();
1505
- if (!buf) {
1506
- sendJson(res, 404, { error: NO_DIST });
1507
- return;
1846
+ var NO_DIST, PLUGIN_FILE_ROUTES, INSTALL_ROUTES;
1847
+ var init_api = __esm({
1848
+ "src/install/api.ts"() {
1849
+ "use strict";
1850
+ init_http_body();
1851
+ init_dist_resolver();
1852
+ init_page();
1853
+ init_zip();
1854
+ NO_DIST = "plugin dist not found\uFF08\u8FD0\u884C npm run build \u751F\u6210\uFF09";
1855
+ PLUGIN_FILE_ROUTES = [
1856
+ { path: "/code.js", file: "code.js", type: "text/javascript; charset=utf-8" },
1857
+ { path: "/ui.html", file: "ui.html", type: "text/html; charset=utf-8" }
1858
+ ].map(({ path, file, type }) => ({
1859
+ method: "GET",
1860
+ path,
1861
+ handler: async ({ res }) => {
1862
+ const buf = await readPluginFile(file);
1863
+ if (!buf) {
1864
+ sendJson(res, 404, { error: hasPluginDist() ? "not found" : NO_DIST });
1865
+ return;
1866
+ }
1867
+ sendBinary(res, buf, type);
1508
1868
  }
1509
- sendBinary(res, buf, "application/json; charset=utf-8");
1510
- }
1511
- },
1512
- ...PLUGIN_FILE_ROUTES,
1513
- {
1514
- method: "GET",
1515
- path: "/plugin.zip",
1516
- handler: async ({ res }) => {
1517
- if (!hasPluginDist()) {
1518
- sendJson(res, 404, { error: NO_DIST });
1519
- return;
1869
+ }));
1870
+ INSTALL_ROUTES = [
1871
+ {
1872
+ method: "GET",
1873
+ path: "/",
1874
+ handler: ({ res }) => {
1875
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1876
+ res.end(renderInstallPage());
1877
+ }
1878
+ },
1879
+ {
1880
+ method: "GET",
1881
+ path: "/manifest.json",
1882
+ handler: async ({ res }) => {
1883
+ const buf = await pluginManifest();
1884
+ if (!buf) {
1885
+ sendJson(res, 404, { error: NO_DIST });
1886
+ return;
1887
+ }
1888
+ sendBinary(res, buf, "application/json; charset=utf-8");
1889
+ }
1890
+ },
1891
+ ...PLUGIN_FILE_ROUTES,
1892
+ {
1893
+ method: "GET",
1894
+ path: "/plugin.zip",
1895
+ handler: async ({ res }) => {
1896
+ if (!hasPluginDist()) {
1897
+ sendJson(res, 404, { error: NO_DIST });
1898
+ return;
1899
+ }
1900
+ try {
1901
+ const [manifest, code, ui] = await Promise.all([
1902
+ pluginManifest(),
1903
+ readPluginFile("code.js"),
1904
+ readPluginFile("ui.html")
1905
+ ]);
1906
+ const zip = buildZip([
1907
+ { name: "manifest.json", data: manifest },
1908
+ { name: "code.js", data: code },
1909
+ { name: "ui.html", data: ui }
1910
+ ]);
1911
+ res.writeHead(200, {
1912
+ "Content-Type": "application/zip",
1913
+ "Content-Disposition": 'attachment; filename="batter-pixso-bridge.zip"'
1914
+ });
1915
+ res.end(zip);
1916
+ } catch {
1917
+ sendJson(res, 500, { error: "zip build failed" });
1918
+ }
1919
+ }
1920
+ }
1921
+ ];
1922
+ }
1923
+ });
1924
+
1925
+ // src/plugin/assets.ts
1926
+ import { createHash } from "node:crypto";
1927
+ import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
1928
+ import { join as join4 } from "node:path";
1929
+ async function saveAsset(bytes, format) {
1930
+ dirReady ??= mkdir(ASSETS_DIR, { recursive: true });
1931
+ await dirReady;
1932
+ const ext = EXT_BY_FORMAT[format.toUpperCase()] ?? ".bin";
1933
+ const name = `${createHash("sha1").update(bytes).digest("hex").slice(0, 16)}${ext}`;
1934
+ await writeFile(join4(ASSETS_DIR, name), bytes);
1935
+ log(`asset saved: ${name} (${bytes.length} bytes)`);
1936
+ return name;
1937
+ }
1938
+ function substituteAssets(result, assets) {
1939
+ if (!assets.length) return result;
1940
+ const nameByKey = new Map(assets.map((a) => [a.key, a.name]));
1941
+ return JSON.parse(
1942
+ JSON.stringify(
1943
+ result,
1944
+ (_k, v) => typeof v === "string" ? nameByKey.get(v) ?? v : v
1945
+ )
1946
+ );
1947
+ }
1948
+ async function cleanOldAssets(ttlMs) {
1949
+ let removed = 0;
1950
+ const cutoff = Date.now() - ttlMs;
1951
+ let names;
1952
+ try {
1953
+ names = await readdir(ASSETS_DIR);
1954
+ } catch {
1955
+ return 0;
1956
+ }
1957
+ const expired = await Promise.all(
1958
+ names.map(async (name) => {
1959
+ try {
1960
+ const p = join4(ASSETS_DIR, name);
1961
+ const s = await stat(p);
1962
+ return s.isFile() && s.mtimeMs < cutoff ? p : null;
1963
+ } catch {
1964
+ return null;
1520
1965
  }
1966
+ })
1967
+ );
1968
+ await Promise.all(
1969
+ expired.filter((p) => p !== null).map(async (p) => {
1521
1970
  try {
1522
- const [manifest, code, ui] = await Promise.all([
1523
- pluginManifest(),
1524
- readPluginFile("code.js"),
1525
- readPluginFile("ui.html")
1526
- ]);
1527
- const zip = buildZip([
1528
- { name: "manifest.json", data: manifest },
1529
- { name: "code.js", data: code },
1530
- { name: "ui.html", data: ui }
1531
- ]);
1532
- res.writeHead(200, {
1533
- "Content-Type": "application/zip",
1534
- "Content-Disposition": 'attachment; filename="batter-pixso-bridge.zip"'
1535
- });
1536
- res.end(zip);
1971
+ await unlink(p);
1972
+ removed++;
1537
1973
  } catch {
1538
- sendJson(res, 500, { error: "zip build failed" });
1539
1974
  }
1540
- }
1975
+ })
1976
+ );
1977
+ if (removed > 0) log(`assets cleanup: removed ${removed} expired file(s)`);
1978
+ return removed;
1979
+ }
1980
+ function scheduleAssetsSweep(ttlMs) {
1981
+ void cleanOldAssets(ttlMs);
1982
+ const sweeper2 = setInterval(() => void cleanOldAssets(ttlMs), ASSET_SWEEP_INTERVAL_MS);
1983
+ sweeper2.unref();
1984
+ }
1985
+ var EXT_BY_FORMAT, dirReady;
1986
+ var init_assets = __esm({
1987
+ "src/plugin/assets.ts"() {
1988
+ "use strict";
1989
+ init_config();
1990
+ EXT_BY_FORMAT = {
1991
+ PNG: ".png",
1992
+ SVG: ".svg",
1993
+ JPG: ".jpg",
1994
+ JPEG: ".jpg",
1995
+ WEBP: ".webp",
1996
+ PDF: ".pdf"
1997
+ // export_image 尊重设计师导出标记,标记可能指定 PDF
1998
+ };
1999
+ dirReady = null;
1541
2000
  }
1542
- ];
2001
+ });
1543
2002
 
1544
2003
  // src/plugin/api.ts
1545
2004
  function handlePluginApi(req, res, url) {
1546
2005
  return dispatchRoute(PLUGIN_ROUTES, req, res, url);
1547
2006
  }
1548
- var PLUGIN_ROUTES = [
1549
- {
1550
- method: "POST",
1551
- path: "/sessions/register",
1552
- handler: async ({ req, res }) => {
1553
- sendJson(res, 201, sessions.register(await readJsonBody(req)));
1554
- }
1555
- },
1556
- {
1557
- method: "POST",
1558
- path: "/context",
1559
- handler: async ({ req, res, url }) => {
1560
- const session = url.searchParams.get("session");
1561
- if (!session) {
1562
- sendJson(res, 400, { error: "missing session" });
1563
- return;
1564
- }
1565
- const meta = await readJsonBody(req);
1566
- if (!sessions.updateContext(session, meta)) {
1567
- sendJson(res, 404, { error: "unknown_session" });
1568
- return;
1569
- }
1570
- sendJson(res, 200, { ok: true });
1571
- }
1572
- },
1573
- {
1574
- method: "GET",
1575
- path: "/commands/stream",
1576
- handler: ({ req, res, url }) => {
1577
- const session = url.searchParams.get("session");
1578
- if (!session || !sessions.setConnected(session, true)) {
1579
- sendJson(res, 404, { error: "unknown_session" });
1580
- return;
1581
- }
1582
- res.writeHead(200, {
1583
- "Content-Type": "text/event-stream; charset=utf-8",
1584
- "Cache-Control": "no-store",
1585
- Connection: "keep-alive"
1586
- });
1587
- res.write(": connected\n\n");
1588
- const unsubscribe = commandBus.subscribe(session, (command) => {
1589
- res.write(`data: ${JSON.stringify({ command })}
2007
+ var PLUGIN_ROUTES;
2008
+ var init_api2 = __esm({
2009
+ "src/plugin/api.ts"() {
2010
+ "use strict";
2011
+ init_config();
2012
+ init_http_body();
2013
+ init_assets();
2014
+ init_command_bus();
2015
+ init_sessions();
2016
+ PLUGIN_ROUTES = [
2017
+ {
2018
+ method: "POST",
2019
+ path: "/sessions/register",
2020
+ handler: async ({ req, res }) => {
2021
+ sendJson(res, 201, sessions.register(await readJsonBody(req)));
2022
+ }
2023
+ },
2024
+ {
2025
+ method: "POST",
2026
+ path: "/context",
2027
+ handler: async ({ req, res, url }) => {
2028
+ const session = url.searchParams.get("session");
2029
+ if (!session) {
2030
+ sendJson(res, 400, { error: "missing session" });
2031
+ return;
2032
+ }
2033
+ const meta = await readJsonBody(req);
2034
+ if (!sessions.updateContext(session, meta)) {
2035
+ sendJson(res, 404, { error: "unknown_session" });
2036
+ return;
2037
+ }
2038
+ sendJson(res, 200, { ok: true });
2039
+ }
2040
+ },
2041
+ {
2042
+ method: "GET",
2043
+ path: "/commands/stream",
2044
+ handler: ({ req, res, url }) => {
2045
+ const session = url.searchParams.get("session");
2046
+ if (!session || !sessions.setConnected(session, true)) {
2047
+ sendJson(res, 404, { error: "unknown_session" });
2048
+ return;
2049
+ }
2050
+ res.writeHead(200, {
2051
+ "Content-Type": "text/event-stream; charset=utf-8",
2052
+ "Cache-Control": "no-store",
2053
+ Connection: "keep-alive"
2054
+ });
2055
+ res.write(": connected\n\n");
2056
+ const unsubscribe = commandBus.subscribe(session, (command) => {
2057
+ res.write(`data: ${JSON.stringify({ command })}
1590
2058
 
1591
2059
  `);
1592
- });
1593
- const heartbeat = setInterval(() => {
1594
- sessions.touchSeen(session);
1595
- res.write(": ping\n\n");
1596
- }, 15e3);
1597
- req.on("close", () => {
1598
- clearInterval(heartbeat);
1599
- unsubscribe();
1600
- sessions.setConnected(session, false);
1601
- log(`SSE channel closed: session ${session.slice(0, 8)}`);
1602
- });
1603
- }
1604
- },
1605
- {
1606
- method: "POST",
1607
- path: "/commands/:id/result",
1608
- handler: async ({ req, res, params }) => {
1609
- const payload = await readJsonBody(req);
1610
- const result = payload.ok && payload.assets?.length ? substituteAssets(payload.result, payload.assets) : payload.result;
1611
- const found = commandBus.complete(params["id"], payload.ok, payload.ok ? result : payload.error);
1612
- sendJson(res, found ? 200 : 404, found ? { ok: true } : { error: "unknown command" });
1613
- }
1614
- },
1615
- {
1616
- method: "POST",
1617
- path: "/assets",
1618
- handler: async ({ req, res, url }) => {
1619
- try {
1620
- const name = await saveAsset(await readBody(req), url.searchParams.get("format") ?? "PNG");
1621
- sendJson(res, 201, { name });
1622
- } catch {
1623
- sendJson(res, 500, { error: "asset save failed" });
2060
+ });
2061
+ const teardown = () => {
2062
+ clearInterval(heartbeat);
2063
+ unsubscribe();
2064
+ sessions.setConnected(session, false);
2065
+ };
2066
+ const heartbeat = setInterval(() => {
2067
+ if (!res.write(": ping\n\n")) {
2068
+ teardown();
2069
+ res.end();
2070
+ log(`SSE ghost reaped: session ${session.slice(0, 8)}`);
2071
+ return;
2072
+ }
2073
+ sessions.touchSeen(session);
2074
+ }, SSE_HEARTBEAT_MS);
2075
+ req.on("close", () => {
2076
+ teardown();
2077
+ log(`SSE channel closed: session ${session.slice(0, 8)}`);
2078
+ });
2079
+ }
2080
+ },
2081
+ {
2082
+ method: "POST",
2083
+ path: "/commands/:id/result",
2084
+ handler: async ({ req, res, params }) => {
2085
+ const payload = await readJsonBody(req);
2086
+ const result = payload.ok && payload.assets?.length ? substituteAssets(payload.result, payload.assets) : payload.result;
2087
+ const found = commandBus.complete(params["id"], payload.ok, payload.ok ? result : payload.error);
2088
+ sendJson(res, found ? 200 : 404, found ? { ok: true } : { error: "unknown command" });
2089
+ }
2090
+ },
2091
+ {
2092
+ method: "POST",
2093
+ path: "/assets",
2094
+ handler: async ({ req, res, url }) => {
2095
+ try {
2096
+ const name = await saveAsset(await readBody(req), url.searchParams.get("format") ?? "PNG");
2097
+ sendJson(res, 201, { name });
2098
+ } catch {
2099
+ sendJson(res, 500, { error: "asset save failed" });
2100
+ }
2101
+ }
1624
2102
  }
1625
- }
2103
+ ];
1626
2104
  }
1627
- ];
2105
+ });
1628
2106
 
1629
2107
  // src/http.ts
1630
- var lastActivity = Date.now();
1631
- function getLastActivity() {
1632
- return lastActivity;
1633
- }
1634
- var ROUTES = [handleInstall, handleMcp, handlePluginApi];
2108
+ import { createServer } from "node:http";
1635
2109
  function startHttpServer() {
1636
2110
  const server = createServer((req, res) => {
1637
- lastActivity = Date.now();
1638
2111
  res.setHeader("Access-Control-Allow-Origin", "*");
1639
2112
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
1640
2113
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
@@ -1649,7 +2122,7 @@ function startHttpServer() {
1649
2122
  const all = sessions.list();
1650
2123
  sendJson(res, 200, {
1651
2124
  ok: true,
1652
- name: HUB_NAME,
2125
+ name: SERVICE_NAME,
1653
2126
  pid: process.pid,
1654
2127
  version: VERSION,
1655
2128
  pluginsOnline: all.filter((s) => s.online).length
@@ -1673,161 +2146,141 @@ function startHttpServer() {
1673
2146
  });
1674
2147
  server.listen(config.httpPort, "127.0.0.1", () => {
1675
2148
  log(`plugin HTTP + MCP server on http://127.0.0.1:${config.httpPort} (MCP endpoint: /mcp, pid ${process.pid})`);
2149
+ scheduleAssetsSweep(config.assetTtlMs);
1676
2150
  resolve(server);
1677
2151
  });
1678
2152
  });
1679
2153
  }
2154
+ var ROUTES;
2155
+ var init_http2 = __esm({
2156
+ "src/http.ts"() {
2157
+ "use strict";
2158
+ init_http();
2159
+ init_config();
2160
+ init_http_body();
2161
+ init_api();
2162
+ init_api2();
2163
+ init_assets();
2164
+ init_sessions();
2165
+ ROUTES = [handleInstall, handleMcp, handlePluginApi];
2166
+ }
2167
+ });
1680
2168
 
1681
- // src/process/hub.ts
1682
- async function runHub() {
2169
+ // src/cli/serve.ts
2170
+ var serve_exports = {};
2171
+ __export(serve_exports, {
2172
+ serve: () => serve
2173
+ });
2174
+ async function serve() {
2175
+ const { state, health } = await probeService();
2176
+ if (state === "alive" && health?.version === VERSION) {
2177
+ printUsage();
2178
+ return;
2179
+ }
2180
+ if (state === "alive") {
2181
+ throw new Error(
2182
+ `\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`
2183
+ );
2184
+ }
2185
+ if (state === "hung" || state === "foreign") {
2186
+ throw serviceStateError(state);
2187
+ }
1683
2188
  const server = await startHttpServer();
1684
2189
  if (!server) {
1685
- log(`\u7AEF\u53E3 ${config.httpPort} \u5DF2\u6709 hub \u5728\u8FD0\u884C\uFF0C\u672C\u8FDB\u7A0B\u9000\u51FA`);
1686
- process.exit(0);
1687
- }
1688
- log(`hub ready (idle TTL ${config.hubIdleMs / 6e4}min)`);
1689
- void cleanOldAssets(config.assetTtlMs);
1690
- let tick = 0;
1691
- const watchdog = setInterval(() => {
1692
- const idle = Date.now() - getLastActivity();
1693
- const anyPluginOnline = sessions.list().some((s) => s.online);
1694
- if (idle > config.hubIdleMs && !anyPluginOnline) {
1695
- log(`\u7A7A\u95F2 ${Math.round(idle / 6e4)} \u5206\u949F\u4E14\u65E0\u5728\u7EBF\u63D2\u4EF6\uFF0Chub \u81EA\u52A8\u9000\u51FA`);
1696
- process.exit(0);
1697
- }
1698
- if (++tick % 60 === 0) void cleanOldAssets(config.assetTtlMs);
1699
- }, 6e4);
1700
- watchdog.unref();
1701
- }
1702
-
1703
- // src/process/stdio-proxy.ts
1704
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1705
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1706
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1707
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1708
- import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
1709
-
1710
- // src/process/hub-process.ts
1711
- import { spawn } from "node:child_process";
1712
- import { closeSync, openSync } from "node:fs";
1713
- import { tmpdir as tmpdir2 } from "node:os";
1714
- import { join as join4 } from "node:path";
1715
- import { fileURLToPath as fileURLToPath2 } from "node:url";
1716
- async function probeHub() {
1717
- try {
1718
- const res = await fetch(`${hubBaseUrl()}/health`, { signal: AbortSignal.timeout(1500) });
1719
- if (!res.ok) return "foreign";
1720
- const body = await res.json();
1721
- if (body.ok === true && body.name === HUB_NAME) return "alive";
1722
- if (body.ok === true) return "legacy";
1723
- return "foreign";
1724
- } catch {
1725
- return "down";
1726
- }
1727
- }
1728
- var PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
1729
- function hubStateError(state) {
1730
- if (state === "legacy") {
1731
- return new Error(
1732
- `\u7AEF\u53E3 ${config.httpPort} \u4E0A\u662F\u65E7\u7248\u672C batter-pixso-mcp \u5B9E\u4F8B\uFF08\u4E0D\u652F\u6301\u591A agent \u590D\u7528\uFF09\u3002${PORT_FIX_HINT}`
2190
+ throw new Error(
2191
+ `\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`
1733
2192
  );
1734
2193
  }
1735
- return new Error(`\u7AEF\u53E3 ${config.httpPort} \u88AB\u5176\u4ED6\u7A0B\u5E8F\u5360\u7528\u3002${PORT_FIX_HINT}\uFF0C\u6216\u4FEE\u6539 HTTP_PORT \u6362\u7AEF\u53E3`);
2194
+ printUsage();
2195
+ log("\u524D\u53F0\u8FD0\u884C\u4E2D\uFF1A\u6B64\u7A97\u53E3/\u8FDB\u7A0B\u9700\u4FDD\u6301\uFF08Ctrl+C \u505C\u6B62\u670D\u52A1\uFF09\uFF1B\u514D\u7A97\u53E3\u7528 --setup \u6CE8\u518C\u5F00\u673A\u81EA\u542F");
1736
2196
  }
1737
- var HUB_LOG = join4(tmpdir2(), "batter-pixso-mcp-hub.log");
1738
- function spawnHub() {
1739
- const out = openSync(HUB_LOG, "a");
1740
- const entry = process.argv[1] ?? fileURLToPath2(import.meta.url);
1741
- const SKIP_FLAGS = /* @__PURE__ */ new Set(["--eval", "-e", "--print", "-p", "--watch", "--watch-preserve-output"]);
1742
- const execArgv = process.execArgv.filter(
1743
- (a, i, arr) => !SKIP_FLAGS.has(a) && !(i > 0 && SKIP_FLAGS.has(arr[i - 1]))
2197
+ function printUsage() {
2198
+ const url = mcpUrl();
2199
+ console.log(
2200
+ [
2201
+ `batter-pixso-mcp v${VERSION} \u2014 \u670D\u52A1\u8FD0\u884C\u4E2D\uFF1A${serviceBaseUrl()}`,
2202
+ ``,
2203
+ `MCP \u5BA2\u6237\u7AEF\u914D\u7F6E\uFF08URL \u76F4\u8FDE\uFF0C\u4E00\u4E2A\u5730\u5740\u5168\u5BA2\u6237\u7AEF\u901A\u7528\uFF09\uFF1A`,
2204
+ ``,
2205
+ ` Claude Code\uFF08\u4E00\u884C\u547D\u4EE4\uFF09\uFF1A`,
2206
+ ` ${claudeAddCommand()}`,
2207
+ ``,
2208
+ ` \u901A\u7528 JSON\uFF08Cursor / Cherry Studio \u7B49\uFF09\uFF1A`,
2209
+ ` { "mcpServers": { "batter-pixso": { "url": "${url}" } } }`,
2210
+ ``,
2211
+ `\u63D2\u4EF6\u5B89\u88C5\uFF1A\u6D4F\u89C8\u5668\u6253\u5F00 ${serviceBaseUrl()}/ \u6309\u5F15\u5BFC\u5B89\u88C5`,
2212
+ `\u5F00\u673A\u81EA\u542F\uFF1Anpx batter-pixso-mcp --setup\uFF08Windows\uFF09`,
2213
+ `\u505C\u6B62\u670D\u52A1\uFF1Anpx batter-pixso-mcp --stop`
2214
+ ].join("\n")
1744
2215
  );
1745
- const child = spawn(process.execPath, [...execArgv, entry, "--hub"], {
1746
- detached: true,
1747
- stdio: ["ignore", out, out],
1748
- windowsHide: true,
1749
- env: { ...process.env }
1750
- });
1751
- child.unref();
1752
- closeSync(out);
1753
- log(`hub spawned (pid ${child.pid}), log: ${HUB_LOG}`);
1754
2216
  }
1755
- async function waitForHub(timeoutMs = 15e3) {
1756
- const deadline = Date.now() + timeoutMs;
1757
- for (; ; ) {
1758
- const state = await probeHub();
1759
- if (state === "alive") return;
1760
- if (state !== "down") throw hubStateError(state);
1761
- if (Date.now() > deadline) throw new Error(`\u7B49\u5F85 hub \u5C31\u7EEA\u8D85\u65F6\uFF08${timeoutMs / 1e3}s\uFF09`);
1762
- await new Promise((r) => setTimeout(r, 200));
2217
+ var init_serve = __esm({
2218
+ "src/cli/serve.ts"() {
2219
+ "use strict";
2220
+ init_config();
2221
+ init_http2();
2222
+ init_service();
1763
2223
  }
1764
- }
2224
+ });
1765
2225
 
1766
- // src/process/stdio-proxy.ts
1767
- async function startStdioProxy() {
1768
- const mcpUrl = `${hubBaseUrl()}/mcp`;
1769
- let upstream = null;
1770
- async function connectUpstream() {
1771
- const client = new Client(
1772
- { name: "batter-pixso-mcp-proxy", version: VERSION },
1773
- { capabilities: {} }
1774
- );
1775
- await client.connect(new StreamableHTTPClientTransport(new URL(mcpUrl)));
1776
- return client;
1777
- }
1778
- async function ensureUpstream() {
1779
- if (upstream) return upstream;
1780
- const state = await probeHub();
1781
- if (state === "down") spawnHub();
1782
- if (state === "legacy" || state === "foreign") throw hubStateError(state);
1783
- await waitForHub();
1784
- upstream = await connectUpstream();
1785
- log(`proxy connected to ${mcpUrl}`);
1786
- return upstream;
1787
- }
1788
- async function callWithRecovery(fn) {
1789
- const client = await ensureUpstream();
1790
- try {
1791
- return await fn(client);
1792
- } catch (err) {
1793
- const alive = await client.ping().then(
1794
- () => true,
1795
- () => false
1796
- );
1797
- if (alive) throw err;
1798
- log("hub \u4F1A\u8BDD\u5931\u8054\uFF0C\u91CD\u5EFA\u540E\u91CD\u8BD5\u2026");
1799
- upstream = null;
1800
- const rebuilt = await ensureUpstream();
1801
- return await fn(rebuilt);
2226
+ // src/cli/index.ts
2227
+ init_config();
2228
+
2229
+ // src/cli/commands.ts
2230
+ init_config();
2231
+ var COMMANDS = [
2232
+ {
2233
+ flags: ["--stop"],
2234
+ summary: "\u505C\u6B62\u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1",
2235
+ run: async () => (await Promise.resolve().then(() => (init_service(), service_exports))).stopService()
2236
+ },
2237
+ {
2238
+ flags: ["--version", "-v"],
2239
+ summary: "\u6253\u5370\u7248\u672C\u53F7",
2240
+ run: async () => log(VERSION)
2241
+ },
2242
+ {
2243
+ flags: ["--uninstall"],
2244
+ summary: "\u79FB\u9664\u767B\u5F55\u81EA\u542F + \u505C\u6B62\u670D\u52A1 + \u5220\u9664\u7269\u5316\u76EE\u5F55",
2245
+ run: async () => (await Promise.resolve().then(() => (init_setup(), setup_exports))).uninstallAutostart()
2246
+ },
2247
+ {
2248
+ flags: ["--setup"],
2249
+ summary: "\uFF08Windows\uFF09\u7269\u5316\u81EA\u5305\u542B\u526F\u672C\u5230 %LOCALAPPDATA% \u5E76\u6CE8\u518C\u767B\u5F55\u81EA\u542F\uFF0C\u968F\u540E\u524D\u53F0\u542F\u52A8\u670D\u52A1",
2250
+ run: async () => {
2251
+ await (await Promise.resolve().then(() => (init_setup(), setup_exports))).setupAutostart();
2252
+ await (await Promise.resolve().then(() => (init_serve(), serve_exports))).serve();
1802
2253
  }
2254
+ },
2255
+ {
2256
+ flags: ["--help", "-h"],
2257
+ summary: "\u6253\u5370\u672C\u5E2E\u52A9",
2258
+ run: async () => printHelp()
1803
2259
  }
1804
- const upstreamClient = await ensureUpstream();
1805
- const server = new Server(
1806
- { name: HUB_NAME, version: VERSION },
1807
- {
1808
- capabilities: upstreamClient.getServerCapabilities() ?? {}
1809
- }
2260
+ ];
2261
+ function printHelp() {
2262
+ console.log(
2263
+ [
2264
+ `batter-pixso-mcp v${VERSION}`,
2265
+ ``,
2266
+ `\u7528\u6CD5\uFF1Abatter-pixso-mcp [\u547D\u4EE4]\uFF08\u4E0D\u5E26\u547D\u4EE4 = \u524D\u53F0\u542F\u52A8\u670D\u52A1\uFF09`,
2267
+ ``,
2268
+ ...COMMANDS.map((c) => ` ${c.flags.join(", ").padEnd(18)}${c.summary}`)
2269
+ ].join("\n")
1810
2270
  );
1811
- server.fallbackRequestHandler = (req, extra) => callWithRecovery((c) => c.request(req, ResultSchema, { signal: extra.signal }));
1812
- server.onclose = () => {
1813
- log("agent disconnected, proxy exiting");
1814
- process.exit(0);
1815
- };
1816
- await server.connect(new StdioServerTransport());
1817
- log("proxy ready on stdio");
1818
2271
  }
1819
-
1820
- // src/process/main.ts
1821
- async function main() {
1822
- if (process.argv.includes("--hub")) {
1823
- await runHub();
2272
+ async function dispatch(argv) {
2273
+ const hit = COMMANDS.find((c) => c.flags.some((f) => argv.includes(f)));
2274
+ if (hit) {
2275
+ await hit.run();
1824
2276
  return;
1825
2277
  }
1826
- await startStdioProxy();
2278
+ const { serve: serve2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
2279
+ await serve2();
1827
2280
  }
1828
2281
 
1829
- // src/process/index.ts
1830
- main().catch((err) => {
1831
- console.error("[batter-pixso-mcp] fatal:", err);
2282
+ // src/cli/index.ts
2283
+ dispatch(process.argv).catch((err) => {
2284
+ log("fatal:", err);
1832
2285
  process.exit(1);
1833
2286
  });