dg-lab-mcp-sse-server 1.0.3

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.
Files changed (44) hide show
  1. package/README.md +179 -0
  2. package/dist/app.d.ts +43 -0
  3. package/dist/app.d.ts.map +1 -0
  4. package/dist/cli.d.ts +7 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +3165 -0
  7. package/dist/cli.js.map +7 -0
  8. package/dist/config.d.ts +79 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/errors.d.ts +132 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +3163 -0
  15. package/dist/index.js.map +7 -0
  16. package/dist/jsonrpc-handler.d.ts +79 -0
  17. package/dist/jsonrpc-handler.d.ts.map +1 -0
  18. package/dist/mcp-protocol.d.ts +50 -0
  19. package/dist/mcp-protocol.d.ts.map +1 -0
  20. package/dist/server.d.ts +41 -0
  21. package/dist/server.d.ts.map +1 -0
  22. package/dist/session-manager.d.ts +209 -0
  23. package/dist/session-manager.d.ts.map +1 -0
  24. package/dist/sse-transport.d.ts +88 -0
  25. package/dist/sse-transport.d.ts.map +1 -0
  26. package/dist/tool-manager.d.ts +159 -0
  27. package/dist/tool-manager.d.ts.map +1 -0
  28. package/dist/tools/control-tools.d.ts +126 -0
  29. package/dist/tools/control-tools.d.ts.map +1 -0
  30. package/dist/tools/device-tools.d.ts +33 -0
  31. package/dist/tools/device-tools.d.ts.map +1 -0
  32. package/dist/tools/waveform-tools.d.ts +53 -0
  33. package/dist/tools/waveform-tools.d.ts.map +1 -0
  34. package/dist/types/jsonrpc.d.ts +157 -0
  35. package/dist/types/jsonrpc.d.ts.map +1 -0
  36. package/dist/waveform-parser.d.ts +259 -0
  37. package/dist/waveform-parser.d.ts.map +1 -0
  38. package/dist/waveform-storage.d.ts +101 -0
  39. package/dist/waveform-storage.d.ts.map +1 -0
  40. package/dist/ws-bridge.d.ts +107 -0
  41. package/dist/ws-bridge.d.ts.map +1 -0
  42. package/dist/ws-server.d.ts +174 -0
  43. package/dist/ws-server.d.ts.map +1 -0
  44. package/package.json +63 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/config.ts", "../src/errors.ts", "../src/server.ts", "../src/sse-transport.ts", "../src/types/jsonrpc.ts", "../src/jsonrpc-handler.ts", "../src/mcp-protocol.ts", "../src/tool-manager.ts", "../src/session-manager.ts", "../src/ws-server.ts", "../src/ws-bridge.ts", "../src/tools/device-tools.ts", "../src/waveform-storage.ts", "../src/waveform-parser.ts", "../src/tools/waveform-tools.ts", "../src/tools/control-tools.ts", "../src/app.ts", "../src/cli.ts"],
4
+ "sourcesContent": ["/**\r\n * @fileoverview \u914D\u7F6E\u7BA1\u7406\u6A21\u5757\r\n * \r\n * \u8FD9\u4E2A\u6A21\u5757\u8D1F\u8D23\u52A0\u8F7D\u548C\u9A8C\u8BC1\u670D\u52A1\u5668\u914D\u7F6E\u3002\u76F4\u63A5\u4ECE\u73AF\u5883\u53D8\u91CF\u8BFB\u53D6\u914D\u7F6E\uFF0C\r\n * \u5BF9\u4E8E\u672A\u8BBE\u7F6E\u7684\u9009\u9879\u4F1A\u4F7F\u7528\u5408\u7406\u7684\u9ED8\u8BA4\u503C\u3002\r\n * \r\n * \u4E3B\u8981\u529F\u80FD\uFF1A\r\n * - \u4ECE\u73AF\u5883\u53D8\u91CF\u52A0\u8F7D\u914D\u7F6E\r\n * - \u9A8C\u8BC1\u914D\u7F6E\u503C\u7684\u6709\u6548\u6027\r\n * - \u63D0\u4F9B IP \u5730\u5740\u68C0\u6D4B\uFF08\u672C\u5730\u548C\u516C\u7F51\uFF09\r\n * - \u5355\u4F8B\u6A21\u5F0F\u786E\u4FDD\u914D\u7F6E\u4E00\u81F4\u6027\r\n */\r\n\r\nimport * as os from \"os\";\r\nimport { ConfigError, ErrorCode } from \"./errors\";\r\n/**\r\n * \u670D\u52A1\u5668\u914D\u7F6E\r\n * \r\n * \u5305\u542B\u670D\u52A1\u5668\u8FD0\u884C\u6240\u9700\u7684\u6240\u6709\u914D\u7F6E\u9879\u3002\u5927\u90E8\u5206\u914D\u7F6E\u90FD\u6709\u5408\u7406\u7684\u9ED8\u8BA4\u503C\uFF0C\r\n * \u53EA\u6709\u5728\u9700\u8981\u81EA\u5B9A\u4E49\u65F6\u624D\u9700\u8981\u901A\u8FC7\u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E\u3002\r\n */\r\nexport interface ServerConfig {\r\n /** \u670D\u52A1\u7AEF\u53E3\uFF0CHTTP \u548C WebSocket \u5171\u7528\u8FD9\u4E2A\u7AEF\u53E3 */\r\n port: number;\r\n /** \u516C\u7F51 IP \u5730\u5740\uFF0C\u7528\u4E8E\u751F\u6210\u4E8C\u7EF4\u7801\u3002\u7559\u7A7A\u5219\u81EA\u52A8\u68C0\u6D4B\u672C\u5730 IP */\r\n publicIp: string;\r\n /** SSE \u7AEF\u70B9\u8DEF\u5F84\uFF0CMCP \u5BA2\u6237\u7AEF\u901A\u8FC7\u8FD9\u4E2A\u8DEF\u5F84\u5EFA\u7ACB SSE \u8FDE\u63A5 */\r\n ssePath: string;\r\n /** POST \u7AEF\u70B9\u8DEF\u5F84\uFF0CMCP \u5BA2\u6237\u7AEF\u901A\u8FC7\u8FD9\u4E2A\u8DEF\u5F84\u53D1\u9001 JSON-RPC \u6D88\u606F */\r\n postPath: string;\r\n /** \u4F1A\u8BDD\u5B58\u50A8\u8DEF\u5F84\uFF08\u76EE\u524D\u672A\u4F7F\u7528\uFF0C\u4F1A\u8BDD\u4EC5\u5B58\u5185\u5B58\uFF09 */\r\n sessionStorePath: string;\r\n /** \u6CE2\u5F62\u5B58\u50A8\u8DEF\u5F84\uFF0C\u4FDD\u5B58\u7528\u6237\u5BFC\u5165\u7684\u6CE2\u5F62\u6570\u636E */\r\n waveformStorePath: string;\r\n /** \u5FC3\u8DF3\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09\uFF0C\u7528\u4E8E\u4FDD\u6301 WebSocket \u8FDE\u63A5\u6D3B\u8DC3 */\r\n heartbeatInterval: number;\r\n /** \u8BBE\u5907\u8FC7\u671F\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09\uFF0C\u8D85\u8FC7\u8FD9\u4E2A\u65F6\u95F4\u4E0D\u6D3B\u8DC3\u7684\u8BBE\u5907\u4F1A\u88AB\u6E05\u7406 */\r\n staleDeviceTimeout: number;\r\n /** \u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4\uFF08\u5206\u949F\uFF09\uFF0C\u4F1A\u8BDD\u521B\u5EFA\u540E\u5728\u6B64\u65F6\u95F4\u5185\u672A\u7ED1\u5B9A APP \u5219\u81EA\u52A8\u9500\u6BC1 */\r\n connectionTimeoutMinutes: number;\r\n}\r\n\r\n/**\r\n * \u4ECE\u73AF\u5883\u53D8\u91CF\u8BFB\u53D6\u5B57\u7B26\u4E32\u503C\r\n * \r\n * \u5982\u679C\u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E\uFF0C\u8FD4\u56DE\u9ED8\u8BA4\u503C\u3002\u8FD9\u662F\u4E2A\u7B80\u5355\u7684\u8F85\u52A9\u51FD\u6570\uFF0C\r\n * \u8BA9\u914D\u7F6E\u52A0\u8F7D\u4EE3\u7801\u66F4\u6E05\u6670\u3002\r\n */\r\nfunction getEnvString(key: string, defaultValue: string): string {\r\n return process.env[key] ?? defaultValue;\r\n}\r\n\r\n/**\r\n * \u4ECE\u73AF\u5883\u53D8\u91CF\u8BFB\u53D6\u6570\u5B57\u503C\r\n * \r\n * \u5982\u679C\u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E\uFF0C\u8FD4\u56DE\u9ED8\u8BA4\u503C\u3002\u5982\u679C\u8BBE\u7F6E\u4E86\u4F46\u4E0D\u662F\u6709\u6548\u6570\u5B57\uFF0C\r\n * \u4F1A\u629B\u51FA\u9519\u8BEF\u63D0\u9192\u7528\u6237\u68C0\u67E5\u914D\u7F6E\u3002\r\n * \r\n * @throws \u5F53\u73AF\u5883\u53D8\u91CF\u503C\u4E0D\u662F\u6709\u6548\u6570\u5B57\u65F6\r\n */\r\nfunction getEnvNumber(key: string, defaultValue: number): number {\r\n const value = process.env[key];\r\n if (value === undefined) return defaultValue;\r\n const parsed = parseInt(value, 10);\r\n if (isNaN(parsed)) {\r\n throw new ConfigError(`\u73AF\u5883\u53D8\u91CF ${key} \u7684\u503C\u65E0\u6548: ${value}`, {\r\n code: ErrorCode.CONFIG_LOAD_FAILED,\r\n context: { key, value },\r\n });\r\n }\r\n return parsed;\r\n}\r\n\r\n/**\r\n * \u52A0\u8F7D\u670D\u52A1\u5668\u914D\u7F6E\r\n * \r\n * \u4ECE\u73AF\u5883\u53D8\u91CF\u8BFB\u53D6\u6240\u6709\u914D\u7F6E\u9879\uFF0C.env \u6587\u4EF6\u5728\u6A21\u5757\u52A0\u8F7D\u65F6\u5DF2\u7ECF\u5904\u7406\u8FC7\u4E86\u3002\r\n * \u52A0\u8F7D\u5B8C\u6210\u540E\u4F1A\u9A8C\u8BC1\u914D\u7F6E\u7684\u6709\u6548\u6027\uFF0C\u786E\u4FDD\u670D\u52A1\u5668\u80FD\u6B63\u5E38\u542F\u52A8\u3002\r\n * \r\n * @returns \u5B8C\u6574\u7684\u670D\u52A1\u5668\u914D\u7F6E\u5BF9\u8C61\r\n */\r\nexport function loadConfig(): ServerConfig {\r\n const config: ServerConfig = {\r\n port: getEnvNumber(\"PORT\", 3323),\r\n publicIp: getEnvString(\"PUBLIC_IP\", \"\"),\r\n ssePath: getEnvString(\"SSE_PATH\", \"/sse\"),\r\n postPath: getEnvString(\"POST_PATH\", \"/message\"),\r\n sessionStorePath: getEnvString(\"SESSION_STORE_PATH\", \"./data/sessions.json\"),\r\n waveformStorePath: getEnvString(\"WAVEFORM_STORE_PATH\", \"./data/waveforms.json\"),\r\n heartbeatInterval: getEnvNumber(\"HEARTBEAT_INTERVAL\", 30000),\r\n staleDeviceTimeout: getEnvNumber(\"STALE_DEVICE_TIMEOUT\", 3600000),\r\n connectionTimeoutMinutes: getEnvNumber(\"CONNECTION_TIMEOUT_MINUTES\", 5),\r\n };\r\n\r\n validateConfig(config);\r\n return config;\r\n}\r\n\r\n/**\r\n * \u9A8C\u8BC1\u914D\u7F6E\u6709\u6548\u6027\r\n * \r\n * \u68C0\u67E5\u6240\u6709\u914D\u7F6E\u9879\u662F\u5426\u5728\u5408\u7406\u8303\u56F4\u5185\u3002\u5BF9\u4E8E\u65E0\u6548\u7684\u516C\u7F51 IP\uFF0C\r\n * \u4F1A\u6253\u5370\u8B66\u544A\u5E76\u56DE\u9000\u5230\u672C\u5730 IP\uFF0C\u800C\u4E0D\u662F\u76F4\u63A5\u62A5\u9519\u3002\r\n * \r\n * @throws \u5F53\u914D\u7F6E\u503C\u660E\u663E\u65E0\u6548\u65F6\uFF08\u5982\u7AEF\u53E3\u8D85\u51FA\u8303\u56F4\uFF09\r\n */\r\nfunction validateConfig(config: ServerConfig): void {\r\n if (config.port < 1 || config.port > 65535) {\r\n throw new ConfigError(`\u7AEF\u53E3\u65E0\u6548: ${config.port}\uFF0C\u5FC5\u987B\u5728 1-65535 \u8303\u56F4\u5185`, {\r\n code: ErrorCode.CONFIG_INVALID_PORT,\r\n context: { port: config.port },\r\n });\r\n }\r\n\r\n // \u9A8C\u8BC1\u516C\u7F51IP\u683C\u5F0F\uFF08\u5982\u679C\u63D0\u4F9B\uFF09\r\n if (config.publicIp) {\r\n const ipv4Regex = /^(\\d{1,3}\\.){3}\\d{1,3}$/;\r\n if (!ipv4Regex.test(config.publicIp)) {\r\n console.warn(`[\u914D\u7F6E] \u26A0\uFE0F \u516C\u7F51IP\u683C\u5F0F\u65E0\u6548: ${config.publicIp}\uFF0C\u5C06\u4F7F\u7528\u672C\u5730IP`);\r\n config.publicIp = \"\"; // \u56DE\u9000\u5230\u672C\u5730IP\r\n } else {\r\n // \u9A8C\u8BC1\u6BCF\u4E2A\u6570\u5B57\u6BB5\u57280-255\u8303\u56F4\u5185\r\n const parts = config.publicIp.split(\".\");\r\n if (parts.some(part => parseInt(part, 10) > 255)) {\r\n console.warn(`[\u914D\u7F6E] \u26A0\uFE0F \u516C\u7F51IP\u683C\u5F0F\u65E0\u6548: ${config.publicIp}\uFF0C\u6BCF\u6BB5\u5FC5\u987B\u57280-255\u8303\u56F4\u5185\uFF0C\u5C06\u4F7F\u7528\u672C\u5730IP`);\r\n config.publicIp = \"\"; // \u56DE\u9000\u5230\u672C\u5730IP\r\n }\r\n }\r\n }\r\n\r\n if (!config.ssePath.startsWith(\"/\")) {\r\n throw new ConfigError(`SSE \u8DEF\u5F84\u65E0\u6548: ${config.ssePath}\uFF0C\u5FC5\u987B\u4EE5 / \u5F00\u5934`, {\r\n code: ErrorCode.CONFIG_INVALID_PATH,\r\n context: { path: config.ssePath, type: 'ssePath' },\r\n });\r\n }\r\n\r\n if (!config.postPath.startsWith(\"/\")) {\r\n throw new ConfigError(`POST \u8DEF\u5F84\u65E0\u6548: ${config.postPath}\uFF0C\u5FC5\u987B\u4EE5 / \u5F00\u5934`, {\r\n code: ErrorCode.CONFIG_INVALID_PATH,\r\n context: { path: config.postPath, type: 'postPath' },\r\n });\r\n }\r\n\r\n if (config.heartbeatInterval < 1000) {\r\n throw new ConfigError(`\u5FC3\u8DF3\u95F4\u9694\u65E0\u6548: ${config.heartbeatInterval}\uFF0C\u5FC5\u987B\u81F3\u5C11 1000ms`, {\r\n code: ErrorCode.CONFIG_LOAD_FAILED,\r\n context: { heartbeatInterval: config.heartbeatInterval },\r\n });\r\n }\r\n\r\n if (config.staleDeviceTimeout < 60000) {\r\n throw new ConfigError(`\u8BBE\u5907\u8FC7\u671F\u8D85\u65F6\u65E0\u6548: ${config.staleDeviceTimeout}\uFF0C\u5FC5\u987B\u81F3\u5C11 60000ms`, {\r\n code: ErrorCode.CONFIG_LOAD_FAILED,\r\n context: { staleDeviceTimeout: config.staleDeviceTimeout },\r\n });\r\n }\r\n\r\n if (config.connectionTimeoutMinutes < 1 || config.connectionTimeoutMinutes > 60) {\r\n throw new ConfigError(`\u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4\u65E0\u6548: ${config.connectionTimeoutMinutes}\uFF0C\u5FC5\u987B\u5728 1-60 \u5206\u949F\u8303\u56F4\u5185`, {\r\n code: ErrorCode.CONFIG_LOAD_FAILED,\r\n context: { connectionTimeoutMinutes: config.connectionTimeoutMinutes },\r\n });\r\n }\r\n}\r\n\r\n/** \u914D\u7F6E\u5355\u4F8B\uFF0C\u786E\u4FDD\u6574\u4E2A\u5E94\u7528\u4F7F\u7528\u540C\u4E00\u4EFD\u914D\u7F6E */\r\nlet configInstance: ServerConfig | null = null;\r\n\r\n/**\r\n * \u83B7\u53D6\u914D\u7F6E\u5B9E\u4F8B\r\n * \r\n * \u4F7F\u7528\u5355\u4F8B\u6A21\u5F0F\uFF0C\u7B2C\u4E00\u6B21\u8C03\u7528\u65F6\u52A0\u8F7D\u914D\u7F6E\uFF0C\u540E\u7EED\u8C03\u7528\u8FD4\u56DE\u540C\u4E00\u5B9E\u4F8B\u3002\r\n * \u8FD9\u6837\u53EF\u4EE5\u786E\u4FDD\u6574\u4E2A\u5E94\u7528\u4F7F\u7528\u4E00\u81F4\u7684\u914D\u7F6E\uFF0C\u907F\u514D\u91CD\u590D\u52A0\u8F7D\u3002\r\n */\r\nexport function getConfig(): ServerConfig {\r\n if (!configInstance) {\r\n configInstance = loadConfig();\r\n }\r\n return configInstance;\r\n}\r\n\r\n/**\r\n * \u91CD\u7F6E\u914D\u7F6E\u5B9E\u4F8B\r\n * \r\n * \u4E3B\u8981\u7528\u4E8E\u6D4B\u8BD5\u573A\u666F\uFF0C\u8BA9\u6BCF\u4E2A\u6D4B\u8BD5\u7528\u4F8B\u53EF\u4EE5\u4ECE\u5E72\u51C0\u7684\u72B6\u6001\u5F00\u59CB\u3002\r\n * \u751F\u4EA7\u73AF\u5883\u4E00\u822C\u4E0D\u9700\u8981\u8C03\u7528\u8FD9\u4E2A\u51FD\u6570\u3002\r\n */\r\nexport function resetConfig(): void {\r\n configInstance = null;\r\n}\r\n\r\n/**\r\n * \u83B7\u53D6\u672C\u5730 IP \u5730\u5740\r\n * \r\n * \u904D\u5386\u6240\u6709\u7F51\u7EDC\u63A5\u53E3\uFF0C\u627E\u5230\u7B2C\u4E00\u4E2A\u975E\u5185\u90E8\u7684 IPv4 \u5730\u5740\u3002\r\n * \u8FD9\u901A\u5E38\u662F\u5C40\u57DF\u7F51 IP\uFF0C\u9002\u5408\u5728\u672C\u5730\u7F51\u7EDC\u4E2D\u4F7F\u7528\u3002\r\n * \u5982\u679C\u627E\u4E0D\u5230\u5408\u9002\u7684\u5730\u5740\uFF0C\u8FD4\u56DE localhost\u3002\r\n */\r\nexport function getLocalIP(): string {\r\n const interfaces = os.networkInterfaces();\r\n for (const name of Object.keys(interfaces)) {\r\n for (const iface of interfaces[name] || []) {\r\n // \u8DF3\u8FC7\u5185\u90E8\u5730\u5740\uFF08\u5982 127.0.0.1\uFF09\u548C\u975E IPv4 \u5730\u5740\r\n if (iface.family === \"IPv4\" && !iface.internal) {\r\n return iface.address;\r\n }\r\n }\r\n }\r\n return \"localhost\";\r\n}\r\n\r\n/**\r\n * \u83B7\u53D6\u7528\u4E8E\u751F\u6210\u4E8C\u7EF4\u7801\u7684 IP \u5730\u5740\r\n * \r\n * \u4F18\u5148\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684\u516C\u7F51 IP\uFF0C\u5982\u679C\u6CA1\u6709\u8BBE\u7F6E\u5219\u81EA\u52A8\u68C0\u6D4B\u672C\u5730 IP\u3002\r\n * \u8FD9\u4E2A\u51FD\u6570\u96C6\u4E2D\u4E86 IP \u5730\u5740\u7684\u83B7\u53D6\u903B\u8F91\uFF0C\u907F\u514D\u5728\u591A\u5904\u91CD\u590D\u5B9E\u73B0\u3002\r\n * \r\n * @param config - \u53EF\u9009\uFF0C\u4E0D\u4F20\u5219\u4F7F\u7528\u5355\u4F8B\u914D\u7F6E\r\n */\r\nexport function getEffectiveIP(config?: ServerConfig): string {\r\n const cfg = config || getConfig();\r\n return cfg.publicIp || getLocalIP();\r\n}\r\n", "/**\r\n * @fileoverview \u7EDF\u4E00\u9519\u8BEF\u5904\u7406\u6A21\u5757\r\n * \r\n * \u8FD9\u4E2A\u6A21\u5757\u5B9A\u4E49\u4E86\u5E94\u7528\u4E2D\u4F7F\u7528\u7684\u6240\u6709\u9519\u8BEF\u7C7B\u578B\u3002\u901A\u8FC7\u7EDF\u4E00\u7684\u9519\u8BEF\u7ED3\u6784\uFF0C\r\n * \u53EF\u4EE5\u66F4\u65B9\u4FBF\u5730\u8FDB\u884C\u9519\u8BEF\u5904\u7406\u3001\u65E5\u5FD7\u8BB0\u5F55\u548C\u7528\u6237\u53CD\u9988\u3002\r\n * \r\n * \u9519\u8BEF\u5206\u7C7B\uFF1A\r\n * - ConfigError: \u914D\u7F6E\u95EE\u9898\uFF0C\u901A\u5E38\u9700\u8981\u4FEE\u590D\u914D\u7F6E\u540E\u91CD\u542F\r\n * - ConnectionError: \u8FDE\u63A5\u95EE\u9898\uFF0C\u5355\u4E2A\u8FDE\u63A5\u5931\u8D25\u4E0D\u5F71\u54CD\u5176\u4ED6\r\n * - ToolError: \u5DE5\u5177\u8C03\u7528\u5931\u8D25\uFF0C\u53EF\u4EE5\u91CD\u8BD5\u6216\u6362\u4E2A\u65B9\u5F0F\r\n * - WebSocketError: WebSocket \u901A\u4FE1\u95EE\u9898\r\n * - WaveformError: \u6CE2\u5F62\u6570\u636E\u5904\u7406\u95EE\u9898\r\n */\r\n\r\n/**\r\n * \u9519\u8BEF\u7801\u679A\u4E3E\r\n * \r\n * \u6309\u6A21\u5757\u5206\u7EC4\uFF0C\u65B9\u4FBF\u5FEB\u901F\u5B9A\u4F4D\u95EE\u9898\u6765\u6E90\u3002\u9519\u8BEF\u7801\u662F\u5B57\u7B26\u4E32\u800C\u4E0D\u662F\u6570\u5B57\uFF0C\r\n * \u8FD9\u6837\u5728\u65E5\u5FD7\u4E2D\u66F4\u5BB9\u6613\u7406\u89E3\u3002\r\n */\r\nexport enum ErrorCode {\r\n // \u914D\u7F6E\u9519\u8BEF (1xx)\r\n CONFIG_LOAD_FAILED = \"CONFIG_LOAD_FAILED\",\r\n CONFIG_INVALID_PORT = \"CONFIG_INVALID_PORT\",\r\n CONFIG_INVALID_IP = \"CONFIG_INVALID_IP\",\r\n CONFIG_INVALID_PATH = \"CONFIG_INVALID_PATH\",\r\n \r\n // \u8FDE\u63A5\u9519\u8BEF (2xx)\r\n CONN_DEVICE_NOT_FOUND = \"CONN_DEVICE_NOT_FOUND\",\r\n CONN_NOT_BOUND = \"CONN_NOT_BOUND\",\r\n CONN_ALREADY_EXISTS = \"CONN_ALREADY_EXISTS\",\r\n CONN_TIMEOUT = \"CONN_TIMEOUT\",\r\n \r\n // \u5DE5\u5177\u6267\u884C\u9519\u8BEF (3xx)\r\n TOOL_INVALID_PARAMS = \"TOOL_INVALID_PARAMS\",\r\n TOOL_EXECUTION_FAILED = \"TOOL_EXECUTION_FAILED\",\r\n TOOL_NOT_FOUND = \"TOOL_NOT_FOUND\",\r\n \r\n // WebSocket \u9519\u8BEF (4xx)\r\n WS_CONNECTION_FAILED = \"WS_CONNECTION_FAILED\",\r\n WS_MESSAGE_INVALID = \"WS_MESSAGE_INVALID\",\r\n WS_PEER_DISCONNECTED = \"WS_PEER_DISCONNECTED\",\r\n \r\n // \u6CE2\u5F62\u9519\u8BEF (5xx)\r\n WAVEFORM_PARSE_FAILED = \"WAVEFORM_PARSE_FAILED\",\r\n WAVEFORM_NOT_FOUND = \"WAVEFORM_NOT_FOUND\",\r\n WAVEFORM_INVALID_FORMAT = \"WAVEFORM_INVALID_FORMAT\",\r\n}\r\n\r\n/**\r\n * \u5E94\u7528\u9519\u8BEF\u57FA\u7C7B\r\n * \r\n * \u6240\u6709\u81EA\u5B9A\u4E49\u9519\u8BEF\u90FD\u7EE7\u627F\u81EA\u8FD9\u4E2A\u7C7B\u3002\u9664\u4E86\u6807\u51C6\u7684 Error \u5C5E\u6027\u5916\uFF0C\r\n * \u8FD8\u5305\u542B\u9519\u8BEF\u7801\u3001\u662F\u5426\u53EF\u6062\u590D\u7684\u6807\u5FD7\uFF0C\u4EE5\u53CA\u53EF\u9009\u7684\u4E0A\u4E0B\u6587\u4FE1\u606F\u3002\r\n * \r\n * \u53EF\u6062\u590D\u7684\u9519\u8BEF\u610F\u5473\u7740\u7A0B\u5E8F\u53EF\u4EE5\u7EE7\u7EED\u8FD0\u884C\uFF0C\u6BD4\u5982\u5355\u4E2A\u8BBE\u5907\u8FDE\u63A5\u5931\u8D25\u3002\r\n * \u4E0D\u53EF\u6062\u590D\u7684\u9519\u8BEF\u901A\u5E38\u9700\u8981\u4FEE\u590D\u540E\u91CD\u542F\uFF0C\u6BD4\u5982\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\u3002\r\n */\r\nexport class AppError extends Error {\r\n /** \u9519\u8BEF\u7801\uFF0C\u7528\u4E8E\u7A0B\u5E8F\u5316\u5904\u7406 */\r\n readonly code: ErrorCode;\r\n /** \u662F\u5426\u53EF\u6062\u590D\uFF08true \u8868\u793A\u53EF\u4EE5\u7EE7\u7EED\u8FD0\u884C\uFF0Cfalse \u8868\u793A\u9700\u8981\u7EC8\u6B62\uFF09 */\r\n readonly recoverable: boolean;\r\n /** \u9519\u8BEF\u4E0A\u4E0B\u6587\u4FE1\u606F\uFF0C\u5305\u542B\u76F8\u5173\u7684\u8C03\u8BD5\u6570\u636E */\r\n readonly context?: Record<string, unknown>;\r\n\r\n constructor(\r\n code: ErrorCode,\r\n message: string,\r\n options?: {\r\n recoverable?: boolean;\r\n context?: Record<string, unknown>;\r\n cause?: Error;\r\n }\r\n ) {\r\n super(message, { cause: options?.cause });\r\n this.name = \"AppError\";\r\n this.code = code;\r\n this.recoverable = options?.recoverable ?? true;\r\n this.context = options?.context;\r\n }\r\n\r\n /**\r\n * \u683C\u5F0F\u5316\u9519\u8BEF\u4FE1\u606F\uFF0C\u65B9\u4FBF\u65E5\u5FD7\u8F93\u51FA\r\n * \r\n * \u8F93\u51FA\u683C\u5F0F\u5305\u542B\u9519\u8BEF\u7801\u3001\u6D88\u606F\u3001\u4E0A\u4E0B\u6587\u548C\u539F\u56E0\uFF0C\u4E00\u76EE\u4E86\u7136\u3002\r\n */\r\n toLogString(): string {\r\n const parts = [`[${this.code}] ${this.message}`];\r\n if (this.context) {\r\n parts.push(`Context: ${JSON.stringify(this.context)}`);\r\n }\r\n if (this.cause) {\r\n parts.push(`Cause: ${this.cause}`);\r\n }\r\n return parts.join(\"\\n \");\r\n }\r\n}\r\n\r\n/**\r\n * \u914D\u7F6E\u9519\u8BEF\r\n * \r\n * \u5F53\u914D\u7F6E\u52A0\u8F7D\u6216\u9A8C\u8BC1\u5931\u8D25\u65F6\u4F7F\u7528\u3002\u8FD9\u7C7B\u9519\u8BEF\u901A\u5E38\u662F\u81F4\u547D\u7684\uFF0C\r\n * \u9700\u8981\u7528\u6237\u4FEE\u590D\u914D\u7F6E\u6587\u4EF6\u540E\u91CD\u65B0\u542F\u52A8\u670D\u52A1\u5668\u3002\r\n */\r\nexport class ConfigError extends AppError {\r\n constructor(\r\n message: string,\r\n options?: {\r\n code?: ErrorCode;\r\n context?: Record<string, unknown>;\r\n cause?: Error;\r\n }\r\n ) {\r\n super(options?.code ?? ErrorCode.CONFIG_LOAD_FAILED, message, {\r\n recoverable: false,\r\n context: options?.context,\r\n cause: options?.cause,\r\n });\r\n this.name = \"ConfigError\";\r\n }\r\n}\r\n\r\n/**\r\n * \u8FDE\u63A5\u9519\u8BEF\r\n * \r\n * \u5F53\u8BBE\u5907\u8FDE\u63A5\u3001\u4F1A\u8BDD\u7BA1\u7406\u51FA\u73B0\u95EE\u9898\u65F6\u4F7F\u7528\u3002\u8FD9\u7C7B\u9519\u8BEF\u901A\u5E38\u662F\u53EF\u6062\u590D\u7684\uFF0C\r\n * \u5355\u4E2A\u8FDE\u63A5\u5931\u8D25\u4E0D\u4F1A\u5F71\u54CD\u5176\u4ED6\u8BBE\u5907\u7684\u6B63\u5E38\u4F7F\u7528\u3002\r\n */\r\nexport class ConnectionError extends AppError {\r\n constructor(\r\n message: string,\r\n options?: {\r\n code?: ErrorCode;\r\n context?: Record<string, unknown>;\r\n cause?: Error;\r\n }\r\n ) {\r\n super(options?.code ?? ErrorCode.CONN_DEVICE_NOT_FOUND, message, {\r\n recoverable: true,\r\n context: options?.context,\r\n cause: options?.cause,\r\n });\r\n this.name = \"ConnectionError\";\r\n }\r\n}\r\n\r\n/**\r\n * \u5DE5\u5177\u6267\u884C\u9519\u8BEF\r\n * \r\n * \u5F53 MCP \u5DE5\u5177\u8C03\u7528\u5931\u8D25\u65F6\u4F7F\u7528\u3002\u8FD9\u7C7B\u9519\u8BEF\u662F\u53EF\u6062\u590D\u7684\uFF0C\r\n * \u5355\u4E2A\u5DE5\u5177\u8C03\u7528\u5931\u8D25\u4E0D\u4F1A\u5F71\u54CD\u670D\u52A1\u5668\u7684\u6B63\u5E38\u8FD0\u884C\u3002\r\n */\r\nexport class ToolError extends AppError {\r\n constructor(\r\n message: string,\r\n options?: {\r\n code?: ErrorCode;\r\n context?: Record<string, unknown>;\r\n cause?: Error;\r\n }\r\n ) {\r\n super(options?.code ?? ErrorCode.TOOL_EXECUTION_FAILED, message, {\r\n recoverable: true,\r\n context: options?.context,\r\n cause: options?.cause,\r\n });\r\n this.name = \"ToolError\";\r\n }\r\n}\r\n\r\n/**\r\n * WebSocket \u9519\u8BEF\r\n * \r\n * \u5F53 WebSocket \u8FDE\u63A5\u6216\u6D88\u606F\u5904\u7406\u51FA\u73B0\u95EE\u9898\u65F6\u4F7F\u7528\u3002\u8FD9\u7C7B\u9519\u8BEF\u662F\u53EF\u6062\u590D\u7684\uFF0C\r\n * \u5355\u4E2A WebSocket \u8FDE\u63A5\u7684\u95EE\u9898\u4E0D\u4F1A\u5F71\u54CD\u5176\u4ED6\u8FDE\u63A5\u3002\r\n */\r\nexport class WebSocketError extends AppError {\r\n constructor(\r\n message: string,\r\n options?: {\r\n code?: ErrorCode;\r\n context?: Record<string, unknown>;\r\n cause?: Error;\r\n }\r\n ) {\r\n super(options?.code ?? ErrorCode.WS_CONNECTION_FAILED, message, {\r\n recoverable: true,\r\n context: options?.context,\r\n cause: options?.cause,\r\n });\r\n this.name = \"WebSocketError\";\r\n }\r\n}\r\n\r\n/**\r\n * \u6CE2\u5F62\u9519\u8BEF\r\n * \r\n * \u5F53\u6CE2\u5F62\u6570\u636E\u89E3\u6790\u6216\u5B58\u50A8\u51FA\u73B0\u95EE\u9898\u65F6\u4F7F\u7528\u3002\u8FD9\u7C7B\u9519\u8BEF\u662F\u53EF\u6062\u590D\u7684\uFF0C\r\n * \u5355\u4E2A\u6CE2\u5F62\u5904\u7406\u5931\u8D25\u4E0D\u4F1A\u5F71\u54CD\u5176\u4ED6\u64CD\u4F5C\u3002\r\n */\r\nexport class WaveformError extends AppError {\r\n constructor(\r\n message: string,\r\n options?: {\r\n code?: ErrorCode;\r\n context?: Record<string, unknown>;\r\n cause?: Error;\r\n }\r\n ) {\r\n super(options?.code ?? ErrorCode.WAVEFORM_PARSE_FAILED, message, {\r\n recoverable: true,\r\n context: options?.context,\r\n cause: options?.cause,\r\n });\r\n this.name = \"WaveformError\";\r\n }\r\n}\r\n", "/**\r\n * @fileoverview HTTP \u670D\u52A1\u5668\r\n * @description \u63D0\u4F9B SSE\u3001POST \u7AEF\u70B9\u548C WebSocket \u7684 HTTP \u670D\u52A1\u5668\r\n * SSE/POST \u7528\u4E8E MCP \u534F\u8BAE\uFF0CWebSocket \u7528\u4E8E DG-LAB APP \u8FDE\u63A5\r\n */\r\n\r\nimport express from \"express\";\r\nimport type { Request, Response, Application } from \"express\";\r\nimport type { Server as HttpServer } from \"http\";\r\nimport type { ServerConfig } from \"./config\";\r\nimport { SSETransport } from \"./sse-transport\";\r\nimport { JsonRpcHandler } from \"./jsonrpc-handler\";\r\nimport { serialize } from \"./types/jsonrpc\";\r\nimport type { JsonRpcResponse } from \"./types/jsonrpc\";\r\n\r\n/**\r\n * MCP \u670D\u52A1\u5668\u63A5\u53E3\r\n */\r\nexport interface MCPServer {\r\n /** Express \u5E94\u7528\u5B9E\u4F8B */\r\n app: Application;\r\n /** HTTP \u670D\u52A1\u5668\u5B9E\u4F8B */\r\n httpServer: HttpServer | null;\r\n /** SSE \u4F20\u8F93\u5C42 */\r\n sseTransport: SSETransport;\r\n /** JSON-RPC \u5904\u7406\u5668 */\r\n jsonRpcHandler: JsonRpcHandler;\r\n /** \u542F\u52A8\u670D\u52A1\u5668 */\r\n start(): Promise<void>;\r\n /** \u505C\u6B62\u670D\u52A1\u5668 */\r\n stop(): Promise<void>;\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA MCP \u670D\u52A1\u5668\r\n * @param config - \u670D\u52A1\u5668\u914D\u7F6E\r\n * @returns MCP \u670D\u52A1\u5668\u5B9E\u4F8B\r\n */\r\nexport function createServer(config: ServerConfig): MCPServer {\r\n const app = express();\r\n \r\n // CORS \u4E2D\u95F4\u4EF6\r\n app.use((req, res, next) => {\r\n res.header(\"Access-Control-Allow-Origin\", \"*\");\r\n res.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\r\n res.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\r\n if (req.method === \"OPTIONS\") {\r\n res.sendStatus(200);\r\n return;\r\n }\r\n next();\r\n });\r\n\r\n app.use(express.json());\r\n app.use(express.text({ type: \"application/json\" }));\r\n\r\n const sseTransport = new SSETransport(config.postPath);\r\n const jsonRpcHandler = new JsonRpcHandler({\r\n onError: (error) => {\r\n console.error(\"[JSON-RPC \u9519\u8BEF]\", error);\r\n },\r\n });\r\n\r\n // SSE \u7AEF\u70B9 (GET /sse)\r\n app.get(config.ssePath, (req: Request, res: Response) => {\r\n console.log(\"[SSE] \u65B0\u8FDE\u63A5\");\r\n const connection = sseTransport.connect(req, res);\r\n console.log(`[SSE] \u8FDE\u63A5\u5DF2\u5EFA\u7ACB: ${connection.id}`);\r\n });\r\n\r\n // POST \u7AEF\u70B9\uFF0C\u7528\u4E8E JSON-RPC \u6D88\u606F\r\n app.post(config.postPath, async (req: Request, res: Response) => {\r\n const sessionId = req.query.sessionId as string;\r\n \r\n if (!sessionId || !sseTransport.hasConnection(sessionId)) {\r\n res.status(400).json({ error: \"\u65E0\u6548\u6216\u7F3A\u5C11 sessionId\" });\r\n return;\r\n }\r\n\r\n // \u83B7\u53D6\u539F\u59CB\u8BF7\u6C42\u4F53\r\n let body: string;\r\n if (typeof req.body === \"string\") {\r\n body = req.body;\r\n } else {\r\n body = JSON.stringify(req.body);\r\n }\r\n\r\n console.log(`[POST] \u6536\u5230\u4F1A\u8BDD ${sessionId} \u7684\u6D88\u606F:`, body);\r\n\r\n // \u5904\u7406 JSON-RPC \u6D88\u606F\r\n const response = await jsonRpcHandler.handleMessage(body);\r\n\r\n // \u5982\u679C\u6709\u54CD\u5E94\uFF0C\u901A\u8FC7 SSE \u53D1\u9001\uFF08\u8BF7\u6C42\u6709\u54CD\u5E94\uFF0C\u901A\u77E5\u6CA1\u6709\uFF09\r\n if (response) {\r\n sseTransport.send(sessionId, response);\r\n }\r\n\r\n // POST \u8BF7\u6C42\u59CB\u7EC8\u8FD4\u56DE 202 Accepted\uFF08\u54CD\u5E94\u901A\u8FC7 SSE \u53D1\u9001\uFF09\r\n res.status(202).json({ status: \"accepted\" });\r\n });\r\n\r\n // \u5065\u5EB7\u68C0\u67E5\u7AEF\u70B9\r\n app.get(\"/health\", (_req: Request, res: Response) => {\r\n res.json({\r\n status: \"ok\",\r\n connections: sseTransport.connectionCount,\r\n });\r\n });\r\n\r\n let httpServer: HttpServer | null = null;\r\n\r\n return {\r\n app,\r\n httpServer: null,\r\n sseTransport,\r\n jsonRpcHandler,\r\n\r\n async start(): Promise<void> {\r\n return new Promise((resolve) => {\r\n httpServer = app.listen(config.port, () => {\r\n console.log(`[\u670D\u52A1\u5668] MCP SSE \u670D\u52A1\u5668\u76D1\u542C\u7AEF\u53E3 ${config.port}`);\r\n console.log(`[\u670D\u52A1\u5668] SSE \u7AEF\u70B9: ${config.ssePath}`);\r\n console.log(`[\u670D\u52A1\u5668] POST \u7AEF\u70B9: ${config.postPath}`);\r\n resolve();\r\n });\r\n // \u66F4\u65B0\u5B9E\u4F8B\u5F15\u7528\r\n (this as MCPServer).httpServer = httpServer;\r\n });\r\n },\r\n\r\n async stop(): Promise<void> {\r\n return new Promise((resolve) => {\r\n // \u68C0\u67E5\u670D\u52A1\u5668\u662F\u5426\u6B63\u5728\u76D1\u542C\r\n if (httpServer && httpServer.listening) {\r\n httpServer.close((err) => {\r\n if (err) {\r\n // \u8BB0\u5F55\u9519\u8BEF\u4F46\u4E0D\u4E2D\u65AD\u5173\u95ED\u6D41\u7A0B\r\n console.error(\"[\u670D\u52A1\u5668] \u5173\u95ED\u65F6\u51FA\u9519:\", err);\r\n } else {\r\n console.log(\"[\u670D\u52A1\u5668] \u5DF2\u505C\u6B62\");\r\n }\r\n resolve();\r\n });\r\n } else {\r\n // \u670D\u52A1\u5668\u672A\u542F\u52A8\u6216\u5DF2\u5173\u95ED\uFF0C\u76F4\u63A5 resolve\r\n console.log(\"[\u670D\u52A1\u5668] \u672A\u542F\u52A8\u6216\u5DF2\u5173\u95ED\");\r\n resolve();\r\n }\r\n });\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * \u5411\u6240\u6709\u8FDE\u63A5\u7684\u5BA2\u6237\u7AEF\u5E7F\u64AD\u901A\u77E5\r\n * @param server - MCP \u670D\u52A1\u5668\u5B9E\u4F8B\r\n * @param method - \u65B9\u6CD5\u540D\r\n * @param params - \u53C2\u6570\uFF08\u53EF\u9009\uFF09\r\n */\r\nexport function broadcastNotification(\r\n server: MCPServer,\r\n method: string,\r\n params?: Record<string, unknown>\r\n): void {\r\n const notification = {\r\n jsonrpc: \"2.0\" as const,\r\n method,\r\n params,\r\n };\r\n server.sseTransport.broadcast(notification);\r\n}\r\n", "/**\r\n * @fileoverview SSE \u4F20\u8F93\u5C42\r\n * @description \u7BA1\u7406 SSE \u8FDE\u63A5\u548C\u6D88\u606F\u4F20\u8F93\r\n */\r\n\r\nimport type { Request, Response } from \"express\";\r\nimport { v4 as uuidv4 } from \"uuid\";\r\nimport { serialize } from \"./types/jsonrpc\";\r\nimport type { JsonRpcMessage } from \"./types/jsonrpc\";\r\n\r\n/**\r\n * SSE \u8FDE\u63A5\u4FE1\u606F\r\n */\r\nexport interface SSEConnection {\r\n /** \u8FDE\u63A5 ID */\r\n id: string;\r\n /** HTTP \u54CD\u5E94\u5BF9\u8C61 */\r\n response: Response;\r\n /** POST \u7AEF\u70B9 URL */\r\n postEndpoint: string;\r\n /** \u521B\u5EFA\u65F6\u95F4 */\r\n createdAt: Date;\r\n}\r\n\r\n/**\r\n * SSE \u4F20\u8F93\u5C42\u7C7B\r\n * @description \u7BA1\u7406 SSE \u8FDE\u63A5\u7684\u5EFA\u7ACB\u3001\u6D88\u606F\u53D1\u9001\u548C\u65AD\u5F00\r\n */\r\nexport class SSETransport {\r\n private connections: Map<string, SSEConnection> = new Map();\r\n private postPath: string;\r\n private baseUrl: string;\r\n\r\n /**\r\n * \u521B\u5EFA SSE \u4F20\u8F93\u5C42\r\n * @param postPath - POST \u7AEF\u70B9\u8DEF\u5F84\r\n * @param baseUrl - \u57FA\u7840 URL\uFF08\u53EF\u9009\uFF09\r\n */\r\n constructor(postPath: string, baseUrl: string = \"\") {\r\n this.postPath = postPath;\r\n this.baseUrl = baseUrl;\r\n }\r\n\r\n /**\r\n * \u5EFA\u7ACB SSE \u8FDE\u63A5\u5E76\u53D1\u9001 endpoint \u4E8B\u4EF6\r\n * @param req - HTTP \u8BF7\u6C42\r\n * @param res - HTTP \u54CD\u5E94\r\n * @returns SSE \u8FDE\u63A5\u4FE1\u606F\r\n */\r\n connect(req: Request, res: Response): SSEConnection {\r\n const connectionId = uuidv4();\r\n \r\n // \u8BBE\u7F6E SSE \u54CD\u5E94\u5934\r\n res.setHeader(\"Content-Type\", \"text/event-stream\");\r\n res.setHeader(\"Cache-Control\", \"no-cache\");\r\n res.setHeader(\"Connection\", \"keep-alive\");\r\n res.setHeader(\"X-Accel-Buffering\", \"no\"); // \u7981\u7528 nginx \u7F13\u51B2\r\n res.flushHeaders();\r\n\r\n // \u6784\u5EFA\u5E26 session ID \u7684 POST \u7AEF\u70B9 URI\r\n const postEndpoint = `${this.baseUrl}${this.postPath}?sessionId=${connectionId}`;\r\n\r\n const connection: SSEConnection = {\r\n id: connectionId,\r\n response: res,\r\n postEndpoint,\r\n createdAt: new Date(),\r\n };\r\n\r\n this.connections.set(connectionId, connection);\r\n\r\n // \u6309 MCP SSE \u89C4\u8303\u53D1\u9001 endpoint \u4E8B\u4EF6\r\n this.sendEvent(connectionId, \"endpoint\", postEndpoint);\r\n\r\n // \u5904\u7406\u5BA2\u6237\u7AEF\u65AD\u5F00\r\n req.on(\"close\", () => {\r\n this.disconnect(connectionId);\r\n });\r\n\r\n return connection;\r\n }\r\n\r\n /**\r\n * \u5411\u6307\u5B9A\u8FDE\u63A5\u53D1\u9001 SSE \u4E8B\u4EF6\r\n * @param connectionId - \u8FDE\u63A5 ID\r\n * @param event - \u4E8B\u4EF6\u540D\r\n * @param data - \u6570\u636E\r\n * @returns \u662F\u5426\u53D1\u9001\u6210\u529F\r\n */\r\n sendEvent(connectionId: string, event: string, data: string): boolean {\r\n const connection = this.connections.get(connectionId);\r\n if (!connection) return false;\r\n\r\n try {\r\n connection.response.write(`event: ${event}\\n`);\r\n connection.response.write(`data: ${data}\\n\\n`);\r\n return true;\r\n } catch {\r\n // \u8FDE\u63A5\u53EF\u80FD\u5DF2\u5173\u95ED\r\n this.disconnect(connectionId);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * \u901A\u8FC7 SSE \u53D1\u9001 JSON-RPC \u6D88\u606F\r\n * @param connectionId - \u8FDE\u63A5 ID\r\n * @param message - JSON-RPC \u6D88\u606F\r\n * @returns \u662F\u5426\u53D1\u9001\u6210\u529F\r\n */\r\n send(connectionId: string, message: JsonRpcMessage): boolean {\r\n const data = serialize(message);\r\n return this.sendEvent(connectionId, \"message\", data);\r\n }\r\n\r\n /**\r\n * \u65AD\u5F00\u5E76\u6E05\u7406 SSE \u8FDE\u63A5\r\n * @param connectionId - \u8FDE\u63A5 ID\r\n */\r\n disconnect(connectionId: string): void {\r\n const connection = this.connections.get(connectionId);\r\n if (connection) {\r\n try {\r\n connection.response.end();\r\n } catch {\r\n // \u5FFD\u7565\u7ED3\u675F\u54CD\u5E94\u65F6\u7684\u9519\u8BEF\r\n }\r\n this.connections.delete(connectionId);\r\n }\r\n }\r\n\r\n /**\r\n * \u6839\u636E ID \u83B7\u53D6\u8FDE\u63A5\r\n * @param connectionId - \u8FDE\u63A5 ID\r\n * @returns \u8FDE\u63A5\u4FE1\u606F\u6216 undefined\r\n */\r\n getConnection(connectionId: string): SSEConnection | undefined {\r\n return this.connections.get(connectionId);\r\n }\r\n\r\n /**\r\n * \u68C0\u67E5\u8FDE\u63A5\u662F\u5426\u5B58\u5728\r\n * @param connectionId - \u8FDE\u63A5 ID\r\n * @returns \u662F\u5426\u5B58\u5728\r\n */\r\n hasConnection(connectionId: string): boolean {\r\n return this.connections.has(connectionId);\r\n }\r\n\r\n /**\r\n * \u83B7\u53D6\u6240\u6709\u6D3B\u8DC3\u8FDE\u63A5 ID\r\n * @returns \u8FDE\u63A5 ID \u6570\u7EC4\r\n */\r\n getConnectionIds(): string[] {\r\n return Array.from(this.connections.keys());\r\n }\r\n\r\n /**\r\n * \u5411\u6240\u6709\u8FDE\u63A5\u5E7F\u64AD\u6D88\u606F\r\n * @param message - JSON-RPC \u6D88\u606F\r\n */\r\n broadcast(message: JsonRpcMessage): void {\r\n for (const connectionId of this.connections.keys()) {\r\n this.send(connectionId, message);\r\n }\r\n }\r\n\r\n /**\r\n * \u83B7\u53D6\u8FDE\u63A5\u6570\u91CF\r\n */\r\n get connectionCount(): number {\r\n return this.connections.size;\r\n }\r\n}\r\n", "/**\r\n * @fileoverview JSON-RPC 2.0 \u7C7B\u578B\u5B9A\u4E49\u548C\u5E8F\u5217\u5316\r\n * @description \u5B9E\u73B0 JSON-RPC 2.0 \u534F\u8BAE\u7684\u7C7B\u578B\u5B9A\u4E49\u3001\u7C7B\u578B\u5B88\u536B\u548C\u5E8F\u5217\u5316\u51FD\u6570\r\n */\r\n\r\n/** JSON-RPC 2.0 \u6807\u51C6\u9519\u8BEF\u7801 */\r\nexport const JSON_RPC_ERRORS = {\r\n /** \u89E3\u6790\u9519\u8BEF - \u65E0\u6548\u7684 JSON */\r\n PARSE_ERROR: -32700,\r\n /** \u65E0\u6548\u8BF7\u6C42 - JSON \u4E0D\u662F\u6709\u6548\u7684\u8BF7\u6C42\u5BF9\u8C61 */\r\n INVALID_REQUEST: -32600,\r\n /** \u65B9\u6CD5\u672A\u627E\u5230 */\r\n METHOD_NOT_FOUND: -32601,\r\n /** \u65E0\u6548\u53C2\u6570 */\r\n INVALID_PARAMS: -32602,\r\n /** \u5185\u90E8\u9519\u8BEF */\r\n INTERNAL_ERROR: -32603,\r\n} as const;\r\n\r\n/** JSON-RPC \u9519\u8BEF\u7801\u7C7B\u578B */\r\nexport type JsonRpcErrorCode = (typeof JSON_RPC_ERRORS)[keyof typeof JSON_RPC_ERRORS];\r\n\r\n/**\r\n * JSON-RPC 2.0 \u9519\u8BEF\u5BF9\u8C61\r\n */\r\nexport interface JsonRpcError {\r\n /** \u9519\u8BEF\u7801 */\r\n code: number;\r\n /** \u9519\u8BEF\u6D88\u606F */\r\n message: string;\r\n /** \u9644\u52A0\u6570\u636E\uFF08\u53EF\u9009\uFF09 */\r\n data?: unknown;\r\n}\r\n\r\n/**\r\n * JSON-RPC 2.0 \u8BF7\u6C42\u5BF9\u8C61\r\n */\r\nexport interface JsonRpcRequest {\r\n /** \u534F\u8BAE\u7248\u672C\uFF0C\u5FC5\u987B\u662F \"2.0\" */\r\n jsonrpc: \"2.0\";\r\n /** \u8BF7\u6C42 ID */\r\n id: string | number;\r\n /** \u65B9\u6CD5\u540D */\r\n method: string;\r\n /** \u53C2\u6570\uFF08\u53EF\u9009\uFF09 */\r\n params?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * JSON-RPC 2.0 \u901A\u77E5\u5BF9\u8C61\uFF08\u65E0 id\uFF09\r\n */\r\nexport interface JsonRpcNotification {\r\n /** \u534F\u8BAE\u7248\u672C\uFF0C\u5FC5\u987B\u662F \"2.0\" */\r\n jsonrpc: \"2.0\";\r\n /** \u65B9\u6CD5\u540D */\r\n method: string;\r\n /** \u53C2\u6570\uFF08\u53EF\u9009\uFF09 */\r\n params?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * JSON-RPC 2.0 \u6210\u529F\u54CD\u5E94\r\n */\r\nexport interface JsonRpcSuccessResponse {\r\n /** \u534F\u8BAE\u7248\u672C */\r\n jsonrpc: \"2.0\";\r\n /** \u8BF7\u6C42 ID */\r\n id: string | number | null;\r\n /** \u7ED3\u679C */\r\n result: unknown;\r\n}\r\n\r\n/**\r\n * JSON-RPC 2.0 \u9519\u8BEF\u54CD\u5E94\r\n */\r\nexport interface JsonRpcErrorResponse {\r\n /** \u534F\u8BAE\u7248\u672C */\r\n jsonrpc: \"2.0\";\r\n /** \u8BF7\u6C42 ID */\r\n id: string | number | null;\r\n /** \u9519\u8BEF\u5BF9\u8C61 */\r\n error: JsonRpcError;\r\n}\r\n\r\n/** \u54CD\u5E94\u7C7B\u578B\u8054\u5408 */\r\nexport type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;\r\n\r\n/** \u6D88\u606F\u7C7B\u578B\u8054\u5408 */\r\nexport type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcResponse;\r\n\r\n/**\r\n * \u5224\u65AD\u662F\u5426\u4E3A JSON-RPC \u8BF7\u6C42\r\n * @param msg - \u5F85\u68C0\u67E5\u7684\u6D88\u606F\r\n * @returns \u662F\u5426\u4E3A\u8BF7\u6C42\r\n */\r\nexport function isJsonRpcRequest(msg: unknown): msg is JsonRpcRequest {\r\n if (typeof msg !== \"object\" || msg === null) return false;\r\n const obj = msg as Record<string, unknown>;\r\n return (\r\n obj.jsonrpc === \"2.0\" &&\r\n \"method\" in obj &&\r\n typeof obj.method === \"string\" &&\r\n \"id\" in obj &&\r\n (typeof obj.id === \"string\" || typeof obj.id === \"number\")\r\n );\r\n}\r\n\r\n/**\r\n * \u5224\u65AD\u662F\u5426\u4E3A JSON-RPC \u901A\u77E5\r\n * @param msg - \u5F85\u68C0\u67E5\u7684\u6D88\u606F\r\n * @returns \u662F\u5426\u4E3A\u901A\u77E5\r\n */\r\nexport function isJsonRpcNotification(msg: unknown): msg is JsonRpcNotification {\r\n if (typeof msg !== \"object\" || msg === null) return false;\r\n const obj = msg as Record<string, unknown>;\r\n return (\r\n obj.jsonrpc === \"2.0\" &&\r\n \"method\" in obj &&\r\n typeof obj.method === \"string\" &&\r\n !(\"id\" in obj)\r\n );\r\n}\r\n\r\n/**\r\n * \u5224\u65AD\u662F\u5426\u4E3A JSON-RPC \u54CD\u5E94\r\n * @param msg - \u5F85\u68C0\u67E5\u7684\u6D88\u606F\r\n * @returns \u662F\u5426\u4E3A\u54CD\u5E94\r\n */\r\nexport function isJsonRpcResponse(msg: unknown): msg is JsonRpcResponse {\r\n if (typeof msg !== \"object\" || msg === null) return false;\r\n const obj = msg as Record<string, unknown>;\r\n return obj.jsonrpc === \"2.0\" && \"id\" in obj && (\"result\" in obj || \"error\" in obj);\r\n}\r\n\r\n/**\r\n * \u5224\u65AD\u662F\u5426\u4E3A\u9519\u8BEF\u54CD\u5E94\r\n * @param msg - \u5F85\u68C0\u67E5\u7684\u6D88\u606F\r\n * @returns \u662F\u5426\u4E3A\u9519\u8BEF\u54CD\u5E94\r\n */\r\nexport function isJsonRpcErrorResponse(msg: unknown): msg is JsonRpcErrorResponse {\r\n if (!isJsonRpcResponse(msg)) return false;\r\n return \"error\" in msg;\r\n}\r\n\r\n/**\r\n * \u5224\u65AD\u662F\u5426\u4E3A\u6210\u529F\u54CD\u5E94\r\n * @param msg - \u5F85\u68C0\u67E5\u7684\u6D88\u606F\r\n * @returns \u662F\u5426\u4E3A\u6210\u529F\u54CD\u5E94\r\n */\r\nexport function isJsonRpcSuccessResponse(msg: unknown): msg is JsonRpcSuccessResponse {\r\n if (!isJsonRpcResponse(msg)) return false;\r\n return \"result\" in msg;\r\n}\r\n\r\n/**\r\n * \u5E8F\u5217\u5316 JSON-RPC \u6D88\u606F\r\n * @param message - JSON-RPC \u6D88\u606F\r\n * @returns JSON \u5B57\u7B26\u4E32\r\n */\r\nexport function serialize(message: JsonRpcMessage): string {\r\n return JSON.stringify(message);\r\n}\r\n\r\n/**\r\n * \u53CD\u5E8F\u5217\u5316\u7ED3\u679C\r\n */\r\nexport interface DeserializeResult {\r\n /** \u662F\u5426\u6210\u529F */\r\n success: boolean;\r\n /** \u89E3\u6790\u540E\u7684\u6D88\u606F */\r\n message?: JsonRpcMessage;\r\n /** \u9519\u8BEF\u4FE1\u606F */\r\n error?: JsonRpcError;\r\n}\r\n\r\n/**\r\n * \u53CD\u5E8F\u5217\u5316 JSON-RPC \u6D88\u606F\r\n * @param data - JSON \u5B57\u7B26\u4E32\r\n * @returns \u53CD\u5E8F\u5217\u5316\u7ED3\u679C\r\n */\r\nexport function deserialize(data: string): DeserializeResult {\r\n try {\r\n const parsed = JSON.parse(data);\r\n \r\n if (typeof parsed !== \"object\" || parsed === null) {\r\n return {\r\n success: false,\r\n error: {\r\n code: JSON_RPC_ERRORS.INVALID_REQUEST,\r\n message: \"\u65E0\u6548\u8BF7\u6C42: \u4E0D\u662F\u5BF9\u8C61\",\r\n },\r\n };\r\n }\r\n\r\n if (parsed.jsonrpc !== \"2.0\") {\r\n return {\r\n success: false,\r\n error: {\r\n code: JSON_RPC_ERRORS.INVALID_REQUEST,\r\n message: \"\u65E0\u6548\u8BF7\u6C42: \u7F3A\u5C11\u6216\u65E0\u6548\u7684 jsonrpc \u7248\u672C\",\r\n },\r\n };\r\n }\r\n\r\n return { success: true, message: parsed as JsonRpcMessage };\r\n } catch {\r\n return {\r\n success: false,\r\n error: {\r\n code: JSON_RPC_ERRORS.PARSE_ERROR,\r\n message: \"\u89E3\u6790\u9519\u8BEF: \u65E0\u6548\u7684 JSON\",\r\n },\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA\u6210\u529F\u54CD\u5E94\r\n * @param id - \u8BF7\u6C42 ID\r\n * @param result - \u7ED3\u679C\r\n * @returns \u6210\u529F\u54CD\u5E94\u5BF9\u8C61\r\n */\r\nexport function createSuccessResponse(id: string | number | null, result: unknown): JsonRpcSuccessResponse {\r\n return { jsonrpc: \"2.0\", id, result };\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA\u9519\u8BEF\u54CD\u5E94\r\n * @param id - \u8BF7\u6C42 ID\r\n * @param code - \u9519\u8BEF\u7801\r\n * @param message - \u9519\u8BEF\u6D88\u606F\r\n * @param data - \u9644\u52A0\u6570\u636E\uFF08\u53EF\u9009\uFF09\r\n * @returns \u9519\u8BEF\u54CD\u5E94\u5BF9\u8C61\r\n */\r\nexport function createErrorResponse(\r\n id: string | number | null,\r\n code: number,\r\n message: string,\r\n data?: unknown\r\n): JsonRpcErrorResponse {\r\n const error: JsonRpcError = { code, message };\r\n if (data !== undefined) error.data = data;\r\n return { jsonrpc: \"2.0\", id, error };\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA\u901A\u77E5\r\n * @param method - \u65B9\u6CD5\u540D\r\n * @param params - \u53C2\u6570\uFF08\u53EF\u9009\uFF09\r\n * @returns \u901A\u77E5\u5BF9\u8C61\r\n */\r\nexport function createNotification(method: string, params?: Record<string, unknown>): JsonRpcNotification {\r\n const notification: JsonRpcNotification = { jsonrpc: \"2.0\", method };\r\n if (params !== undefined) notification.params = params;\r\n return notification;\r\n}\r\n", "/**\r\n * @fileoverview JSON-RPC \u5904\u7406\u5668\r\n * @description \u5904\u7406 JSON-RPC 2.0 \u8BF7\u6C42\u5E76\u8DEF\u7531\u5230\u76F8\u5E94\u7684\u5904\u7406\u51FD\u6570\r\n */\r\n\r\nimport type {\r\n JsonRpcRequest,\r\n JsonRpcNotification,\r\n JsonRpcResponse,\r\n JsonRpcError,\r\n} from \"./types/jsonrpc\";\r\nimport {\r\n JSON_RPC_ERRORS,\r\n deserialize,\r\n createSuccessResponse,\r\n createErrorResponse,\r\n isJsonRpcRequest,\r\n isJsonRpcNotification,\r\n} from \"./types/jsonrpc\";\r\n\r\n/**\r\n * \u8BF7\u6C42\u5904\u7406\u51FD\u6570\u7C7B\u578B\r\n * @param params - \u8BF7\u6C42\u53C2\u6570\r\n * @returns \u5904\u7406\u7ED3\u679C\r\n */\r\nexport type RequestHandler = (\r\n params: Record<string, unknown> | undefined\r\n) => Promise<unknown>;\r\n\r\n/**\r\n * \u901A\u77E5\u5904\u7406\u51FD\u6570\u7C7B\u578B\r\n * @param params - \u901A\u77E5\u53C2\u6570\r\n */\r\nexport type NotificationHandler = (\r\n params: Record<string, unknown> | undefined\r\n) => Promise<void>;\r\n\r\n/**\r\n * JSON-RPC \u5904\u7406\u5668\u9009\u9879\r\n */\r\nexport interface JsonRpcHandlerOptions {\r\n /** \u8BF7\u6C42\u56DE\u8C03 */\r\n onRequest?: (method: string, params?: Record<string, unknown>) => void;\r\n /** \u901A\u77E5\u56DE\u8C03 */\r\n onNotification?: (method: string, params?: Record<string, unknown>) => void;\r\n /** \u9519\u8BEF\u56DE\u8C03 */\r\n onError?: (error: JsonRpcError) => void;\r\n}\r\n\r\n/**\r\n * JSON-RPC \u5904\u7406\u5668\u7C7B\r\n * @description \u7BA1\u7406\u8BF7\u6C42\u548C\u901A\u77E5\u7684\u5904\u7406\u51FD\u6570\uFF0C\u5904\u7406 JSON-RPC \u6D88\u606F\r\n */\r\nexport class JsonRpcHandler {\r\n private requestHandlers: Map<string, RequestHandler> = new Map();\r\n private notificationHandlers: Map<string, NotificationHandler> = new Map();\r\n private options: JsonRpcHandlerOptions;\r\n\r\n /**\r\n * \u521B\u5EFA JSON-RPC \u5904\u7406\u5668\r\n * @param options - \u5904\u7406\u5668\u9009\u9879\r\n */\r\n constructor(options: JsonRpcHandlerOptions = {}) {\r\n this.options = options;\r\n }\r\n\r\n /**\r\n * \u6CE8\u518C\u8BF7\u6C42\u5904\u7406\u51FD\u6570\r\n * @param method - \u65B9\u6CD5\u540D\r\n * @param handler - \u5904\u7406\u51FD\u6570\r\n */\r\n registerRequestHandler(method: string, handler: RequestHandler): void {\r\n this.requestHandlers.set(method, handler);\r\n }\r\n\r\n /**\r\n * \u6CE8\u518C\u901A\u77E5\u5904\u7406\u51FD\u6570\r\n * @param method - \u65B9\u6CD5\u540D\r\n * @param handler - \u5904\u7406\u51FD\u6570\r\n */\r\n registerNotificationHandler(method: string, handler: NotificationHandler): void {\r\n this.notificationHandlers.set(method, handler);\r\n }\r\n\r\n /**\r\n * \u5904\u7406 JSON-RPC \u6D88\u606F\r\n * @param data - JSON \u5B57\u7B26\u4E32\r\n * @returns \u54CD\u5E94\u5BF9\u8C61\uFF08\u901A\u77E5\u8FD4\u56DE null\uFF09\r\n */\r\n async handleMessage(data: string): Promise<JsonRpcResponse | null> {\r\n const parseResult = deserialize(data);\r\n\r\n if (!parseResult.success) {\r\n const error = parseResult.error!;\r\n this.options.onError?.(error);\r\n return createErrorResponse(null, error.code, error.message, error.data);\r\n }\r\n\r\n const message = parseResult.message!;\r\n\r\n // \u5904\u7406\u8BF7\u6C42\uFF08\u6709 id\uFF09\r\n if (isJsonRpcRequest(message)) {\r\n return this.handleRequest(message);\r\n }\r\n\r\n // \u5904\u7406\u901A\u77E5\uFF08\u65E0 id\uFF09\r\n if (isJsonRpcNotification(message)) {\r\n await this.handleNotification(message);\r\n return null; // \u901A\u77E5\u4E0D\u8FD4\u56DE\u54CD\u5E94\r\n }\r\n\r\n // \u65E0\u6548\u7684\u6D88\u606F\u7C7B\u578B\r\n const error: JsonRpcError = {\r\n code: JSON_RPC_ERRORS.INVALID_REQUEST,\r\n message: \"\u65E0\u6548\u8BF7\u6C42: \u4E0D\u662F\u6709\u6548\u7684\u8BF7\u6C42\u6216\u901A\u77E5\",\r\n };\r\n this.options.onError?.(error);\r\n return createErrorResponse(null, error.code, error.message);\r\n }\r\n\r\n /**\r\n * \u5904\u7406\u8BF7\u6C42\r\n * @param request - \u8BF7\u6C42\u5BF9\u8C61\r\n * @returns \u54CD\u5E94\u5BF9\u8C61\r\n */\r\n private async handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {\r\n this.options.onRequest?.(request.method, request.params);\r\n\r\n const handler = this.requestHandlers.get(request.method);\r\n if (!handler) {\r\n const error: JsonRpcError = {\r\n code: JSON_RPC_ERRORS.METHOD_NOT_FOUND,\r\n message: `\u65B9\u6CD5\u672A\u627E\u5230: ${request.method}`,\r\n };\r\n this.options.onError?.(error);\r\n return createErrorResponse(request.id, error.code, error.message);\r\n }\r\n\r\n try {\r\n const result = await handler(request.params);\r\n return createSuccessResponse(request.id, result);\r\n } catch (err) {\r\n const error: JsonRpcError = {\r\n code: JSON_RPC_ERRORS.INTERNAL_ERROR,\r\n message: err instanceof Error ? err.message : \"\u5185\u90E8\u9519\u8BEF\",\r\n };\r\n this.options.onError?.(error);\r\n return createErrorResponse(request.id, error.code, error.message);\r\n }\r\n }\r\n\r\n /**\r\n * \u5904\u7406\u901A\u77E5\r\n * @param notification - \u901A\u77E5\u5BF9\u8C61\r\n */\r\n private async handleNotification(notification: JsonRpcNotification): Promise<void> {\r\n this.options.onNotification?.(notification.method, notification.params);\r\n\r\n const handler = this.notificationHandlers.get(notification.method);\r\n if (handler) {\r\n try {\r\n await handler(notification.params);\r\n } catch (err) {\r\n // \u901A\u77E5\u4E0D\u8FD4\u56DE\u9519\u8BEF\uFF0C\u4F46\u53EF\u4EE5\u8BB0\u5F55\r\n const error: JsonRpcError = {\r\n code: JSON_RPC_ERRORS.INTERNAL_ERROR,\r\n message: err instanceof Error ? err.message : \"\u5185\u90E8\u9519\u8BEF\",\r\n };\r\n this.options.onError?.(error);\r\n }\r\n }\r\n // \u5982\u679C\u6CA1\u6709\u5904\u7406\u51FD\u6570\uFF0C\u9759\u9ED8\u5FFD\u7565\uFF08\u7B26\u5408 JSON-RPC \u89C4\u8303\uFF09\r\n }\r\n\r\n /**\r\n * \u9A8C\u8BC1\u8BF7\u6C42\u53C2\u6570\r\n * @param id - \u8BF7\u6C42 ID\r\n * @param params - \u53C2\u6570\r\n * @param required - \u5FC5\u9700\u53C2\u6570\u5217\u8868\r\n * @returns \u9A8C\u8BC1\u5931\u8D25\u8FD4\u56DE\u9519\u8BEF\u54CD\u5E94\uFF0C\u6210\u529F\u8FD4\u56DE null\r\n */\r\n validateParams(\r\n id: string | number,\r\n params: Record<string, unknown> | undefined,\r\n required: string[]\r\n ): JsonRpcResponse | null {\r\n if (!params && required.length > 0) {\r\n return createErrorResponse(\r\n id,\r\n JSON_RPC_ERRORS.INVALID_PARAMS,\r\n `\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: ${required.join(\", \")}`\r\n );\r\n }\r\n\r\n const missing = required.filter((key) => params?.[key] === undefined);\r\n if (missing.length > 0) {\r\n return createErrorResponse(\r\n id,\r\n JSON_RPC_ERRORS.INVALID_PARAMS,\r\n `\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: ${missing.join(\", \")}`\r\n );\r\n }\r\n\r\n return null;\r\n }\r\n}\r\n", "/**\r\n * @fileoverview MCP \u534F\u8BAE\u5B9E\u73B0\r\n * @description \u5904\u7406 MCP \u521D\u59CB\u5316\u63E1\u624B\u548C\u534F\u8BAE\u65B9\u6CD5\r\n */\r\n\r\nimport type { JsonRpcHandler } from \"./jsonrpc-handler\";\r\n\r\n/** MCP \u534F\u8BAE\u7248\u672C */\r\nexport const MCP_PROTOCOL_VERSION = \"2024-11-05\";\r\n\r\n/** \u670D\u52A1\u5668\u4FE1\u606F */\r\nexport const SERVER_INFO = {\r\n name: \"dg-lab-mcp-server\",\r\n version: \"1.0.0\",\r\n};\r\n\r\n/** \u670D\u52A1\u5668\u80FD\u529B */\r\nexport const SERVER_CAPABILITIES = {\r\n tools: {\r\n listChanged: true,\r\n },\r\n};\r\n\r\n/**\r\n * \u521D\u59CB\u5316\u8BF7\u6C42\u53C2\u6570\r\n */\r\nexport interface InitializeParams {\r\n /** \u534F\u8BAE\u7248\u672C */\r\n protocolVersion: string;\r\n /** \u5BA2\u6237\u7AEF\u80FD\u529B */\r\n capabilities: Record<string, unknown>;\r\n /** \u5BA2\u6237\u7AEF\u4FE1\u606F */\r\n clientInfo: {\r\n name: string;\r\n version: string;\r\n };\r\n}\r\n\r\n/**\r\n * \u521D\u59CB\u5316\u54CD\u5E94\u7ED3\u679C\r\n */\r\nexport interface InitializeResult {\r\n /** \u534F\u8BAE\u7248\u672C */\r\n protocolVersion: string;\r\n /** \u670D\u52A1\u5668\u80FD\u529B */\r\n capabilities: typeof SERVER_CAPABILITIES;\r\n /** \u670D\u52A1\u5668\u4FE1\u606F */\r\n serverInfo: typeof SERVER_INFO;\r\n}\r\n\r\n/**\r\n * \u6CE8\u518C MCP \u534F\u8BAE\u5904\u7406\u51FD\u6570\r\n * @param handler - JSON-RPC \u5904\u7406\u5668\r\n * @param onInitialized - \u521D\u59CB\u5316\u5B8C\u6210\u56DE\u8C03\r\n */\r\nexport function registerMCPProtocol(\r\n handler: JsonRpcHandler,\r\n onInitialized?: () => void\r\n): void {\r\n // \u5904\u7406 initialize \u8BF7\u6C42\r\n handler.registerRequestHandler(\"initialize\", async (params) => {\r\n const initParams = params as unknown as InitializeParams | undefined;\r\n \r\n // \u9A8C\u8BC1\u534F\u8BAE\u7248\u672C\r\n if (initParams?.protocolVersion && initParams.protocolVersion !== MCP_PROTOCOL_VERSION) {\r\n console.log(`[MCP] \u5BA2\u6237\u7AEF\u8BF7\u6C42\u7684\u534F\u8BAE\u7248\u672C: ${initParams.protocolVersion}`);\r\n }\r\n\r\n const result: InitializeResult = {\r\n protocolVersion: MCP_PROTOCOL_VERSION,\r\n capabilities: SERVER_CAPABILITIES,\r\n serverInfo: SERVER_INFO,\r\n };\r\n\r\n console.log(\"[MCP] \u6536\u5230\u521D\u59CB\u5316\u8BF7\u6C42\uFF0C\u8FD4\u56DE\u670D\u52A1\u5668\u80FD\u529B\");\r\n return result;\r\n });\r\n\r\n // \u5904\u7406 initialized \u901A\u77E5\r\n handler.registerNotificationHandler(\"initialized\", async () => {\r\n console.log(\"[MCP] \u521D\u59CB\u5316\u5B8C\u6210\");\r\n onInitialized?.();\r\n });\r\n\r\n // \u5904\u7406 ping \u8BF7\u6C42\r\n handler.registerRequestHandler(\"ping\", async () => {\r\n return {};\r\n });\r\n}\r\n", "/**\r\n * @fileoverview \u5DE5\u5177\u7BA1\u7406\u5668\r\n * \r\n * \u7BA1\u7406 MCP \u534F\u8BAE\u4E2D\u7684\u5DE5\u5177\u5B9A\u4E49\u548C\u6267\u884C\u3002\u5DE5\u5177\u662F AI \u4E0E\u7CFB\u7EDF\u4EA4\u4E92\u7684\u4E3B\u8981\u65B9\u5F0F\uFF0C\r\n * \u6BCF\u4E2A\u5DE5\u5177\u90FD\u6709\u540D\u79F0\u3001\u63CF\u8FF0\u3001\u53C2\u6570 Schema \u548C\u5904\u7406\u51FD\u6570\u3002\r\n * \r\n * \u4E3B\u8981\u529F\u80FD\uFF1A\r\n * - \u6CE8\u518C\u548C\u6CE8\u9500\u5DE5\u5177\r\n * - \u5217\u51FA\u6240\u6709\u53EF\u7528\u5DE5\u5177\uFF08\u4F9B AI \u53D1\u73B0\uFF09\r\n * - \u6309\u540D\u79F0\u8C03\u7528\u5DE5\u5177\u5E76\u8FD4\u56DE\u7ED3\u679C\r\n * - \u4E0E JSON-RPC \u5904\u7406\u5668\u96C6\u6210\r\n */\r\n\r\nimport type { JsonRpcHandler } from \"./jsonrpc-handler\";\r\n\r\n/**\r\n * JSON Schema \u7C7B\u578B\u5B9A\u4E49\r\n * \r\n * \u7528\u4E8E\u63CF\u8FF0\u5DE5\u5177\u53C2\u6570\u7684\u7ED3\u6784\uFF0C\u9075\u5FAA JSON Schema \u89C4\u8303\u3002\r\n */\r\nexport interface JsonSchema {\r\n /** \u7C7B\u578B */\r\n type: string;\r\n /** \u5C5E\u6027\u5B9A\u4E49 */\r\n properties?: Record<string, JsonSchema & { \r\n description?: string; \r\n enum?: string[]; \r\n minimum?: number; \r\n maximum?: number; \r\n pattern?: string; \r\n maxItems?: number; \r\n items?: JsonSchema \r\n }>;\r\n /** \u5FC5\u9700\u5C5E\u6027 */\r\n required?: string[];\r\n /** \u63CF\u8FF0 */\r\n description?: string;\r\n}\r\n\r\n/**\r\n * \u5DE5\u5177\u5185\u5BB9\u7C7B\u578B\r\n * \r\n * MCP \u534F\u8BAE\u8981\u6C42\u5DE5\u5177\u8FD4\u56DE\u7ED3\u6784\u5316\u7684\u5185\u5BB9\u6570\u7EC4\u3002\r\n */\r\nexport interface ToolContent {\r\n /** \u5185\u5BB9\u7C7B\u578B */\r\n type: \"text\";\r\n /** \u6587\u672C\u5185\u5BB9 */\r\n text: string;\r\n}\r\n\r\n/**\r\n * \u5DE5\u5177\u6267\u884C\u7ED3\u679C\r\n * \r\n * \u5305\u542B\u8FD4\u56DE\u5185\u5BB9\u548C\u9519\u8BEF\u6807\u5FD7\u3002isError \u4E3A true \u65F6\u8868\u793A\u6267\u884C\u5931\u8D25\u3002\r\n */\r\nexport interface ToolResult {\r\n /** \u5185\u5BB9\u6570\u7EC4 */\r\n content: ToolContent[];\r\n /** \u662F\u5426\u4E3A\u9519\u8BEF */\r\n isError?: boolean;\r\n}\r\n\r\n/**\r\n * \u5DE5\u5177\u5B9A\u4E49\r\n * \r\n * \u63CF\u8FF0\u4E00\u4E2A\u5DE5\u5177\u7684\u5143\u4FE1\u606F\uFF0C\u7528\u4E8E AI \u53D1\u73B0\u548C\u8C03\u7528\u3002\r\n */\r\nexport interface Tool {\r\n /** \u5DE5\u5177\u540D\u79F0 */\r\n name: string;\r\n /** \u5DE5\u5177\u63CF\u8FF0 */\r\n description: string;\r\n /** \u8F93\u5165\u53C2\u6570 Schema */\r\n inputSchema: JsonSchema;\r\n}\r\n\r\n/**\r\n * \u5DE5\u5177\u5904\u7406\u51FD\u6570\u7C7B\u578B\r\n * \r\n * \u63A5\u6536\u53C2\u6570\u5BF9\u8C61\uFF0C\u8FD4\u56DE\u6267\u884C\u7ED3\u679C\u7684 Promise\u3002\r\n */\r\nexport type ToolHandler = (params: Record<string, unknown>) => Promise<ToolResult>;\r\n\r\n/**\r\n * \u5DF2\u6CE8\u518C\u7684\u5DE5\u5177\uFF08\u5185\u90E8\u4F7F\u7528\uFF09\r\n * \r\n * \u6269\u5C55 Tool \u63A5\u53E3\uFF0C\u5305\u542B\u5B9E\u9645\u7684\u5904\u7406\u51FD\u6570\u3002\r\n */\r\ninterface RegisteredTool extends Tool {\r\n handler: ToolHandler;\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA\u6210\u529F\u7684\u5DE5\u5177\u7ED3\u679C\r\n * \r\n * @param text - \u7ED3\u679C\u6587\u672C\uFF08\u901A\u5E38\u662F JSON \u5B57\u7B26\u4E32\uFF09\r\n * @returns \u683C\u5F0F\u5316\u7684\u5DE5\u5177\u7ED3\u679C\r\n */\r\nexport function createToolResult(text: string): ToolResult {\r\n return {\r\n content: [{ type: \"text\", text }],\r\n };\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA\u9519\u8BEF\u7684\u5DE5\u5177\u7ED3\u679C\r\n * \r\n * @param message - \u9519\u8BEF\u6D88\u606F\r\n * @returns \u5E26\u6709 isError \u6807\u5FD7\u7684\u5DE5\u5177\u7ED3\u679C\r\n */\r\nexport function createToolError(message: string): ToolResult {\r\n return {\r\n content: [{ type: \"text\", text: `Error: ${message}` }],\r\n isError: true,\r\n };\r\n}\r\n\r\n/**\r\n * \u5DE5\u5177\u7BA1\u7406\u5668\r\n * \r\n * \u96C6\u4E2D\u7BA1\u7406\u6240\u6709 MCP \u5DE5\u5177\u7684\u6CE8\u518C\u3001\u67E5\u8BE2\u548C\u8C03\u7528\u3002\r\n * \u652F\u6301\u5DE5\u5177\u5217\u8868\u53D8\u5316\u901A\u77E5\uFF0C\u7528\u4E8E\u52A8\u6001\u66F4\u65B0 AI \u53EF\u7528\u7684\u5DE5\u5177\u96C6\u3002\r\n */\r\nexport class ToolManager {\r\n private tools: Map<string, RegisteredTool> = new Map();\r\n private onToolsChanged?: () => void;\r\n\r\n /**\r\n * \u521B\u5EFA\u5DE5\u5177\u7BA1\u7406\u5668\u5B9E\u4F8B\r\n * \r\n * @param onToolsChanged - \u53EF\u9009\u7684\u56DE\u8C03\u51FD\u6570\uFF0C\u5F53\u5DE5\u5177\u5217\u8868\u53D8\u5316\u65F6\u8C03\u7528\r\n */\r\n constructor(onToolsChanged?: () => void) {\r\n this.onToolsChanged = onToolsChanged;\r\n }\r\n\r\n /**\r\n * \u6CE8\u518C\u65B0\u5DE5\u5177\r\n * \r\n * @param name - \u5DE5\u5177\u540D\u79F0\uFF0C\u5FC5\u987B\u552F\u4E00\r\n * @param description - \u5DE5\u5177\u63CF\u8FF0\uFF0C\u4F9B AI \u7406\u89E3\u5DE5\u5177\u7528\u9014\r\n * @param inputSchema - \u53C2\u6570 Schema\uFF0C\u5B9A\u4E49\u5DE5\u5177\u63A5\u53D7\u7684\u53C2\u6570\r\n * @param handler - \u5904\u7406\u51FD\u6570\uFF0C\u5B9E\u9645\u6267\u884C\u5DE5\u5177\u903B\u8F91\r\n */\r\n registerTool(\r\n name: string,\r\n description: string,\r\n inputSchema: JsonSchema,\r\n handler: ToolHandler\r\n ): void {\r\n this.tools.set(name, {\r\n name,\r\n description,\r\n inputSchema,\r\n handler,\r\n });\r\n this.onToolsChanged?.();\r\n }\r\n\r\n /**\r\n * \u6CE8\u9500\u5DE5\u5177\r\n * \r\n * @param name - \u8981\u6CE8\u9500\u7684\u5DE5\u5177\u540D\u79F0\r\n * @returns \u662F\u5426\u6210\u529F\u6CE8\u9500\uFF08false \u8868\u793A\u5DE5\u5177\u4E0D\u5B58\u5728\uFF09\r\n */\r\n unregisterTool(name: string): boolean {\r\n const result = this.tools.delete(name);\r\n if (result) {\r\n this.onToolsChanged?.();\r\n }\r\n return result;\r\n }\r\n\r\n /**\r\n * \u5217\u51FA\u6240\u6709\u5DF2\u6CE8\u518C\u7684\u5DE5\u5177\r\n * \r\n * \u8FD4\u56DE\u5DE5\u5177\u7684\u5143\u4FE1\u606F\uFF08\u4E0D\u5305\u542B\u5904\u7406\u51FD\u6570\uFF09\uFF0C\u4F9B AI \u53D1\u73B0\u53EF\u7528\u5DE5\u5177\u3002\r\n * \r\n * @returns \u5DE5\u5177\u5B9A\u4E49\u6570\u7EC4\r\n */\r\n listTools(): Tool[] {\r\n return Array.from(this.tools.values()).map(({ name, description, inputSchema }) => ({\r\n name,\r\n description,\r\n inputSchema,\r\n }));\r\n }\r\n\r\n /**\r\n * \u6309\u540D\u79F0\u8C03\u7528\u5DE5\u5177\r\n * \r\n * \u67E5\u627E\u5E76\u6267\u884C\u6307\u5B9A\u7684\u5DE5\u5177\uFF0C\u81EA\u52A8\u5904\u7406\u5F02\u5E38\u5E76\u8FD4\u56DE\u9519\u8BEF\u7ED3\u679C\u3002\r\n * \r\n * @param name - \u5DE5\u5177\u540D\u79F0\r\n * @param params - \u8C03\u7528\u53C2\u6570\r\n * @returns \u5DE5\u5177\u6267\u884C\u7ED3\u679C\r\n */\r\n async callTool(name: string, params: Record<string, unknown>): Promise<ToolResult> {\r\n const tool = this.tools.get(name);\r\n if (!tool) {\r\n return createToolError(`\u5DE5\u5177\u672A\u627E\u5230: ${name}`);\r\n }\r\n\r\n try {\r\n return await tool.handler(params);\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : \"\u672A\u77E5\u9519\u8BEF\";\r\n return createToolError(message);\r\n }\r\n }\r\n\r\n /**\r\n * \u68C0\u67E5\u5DE5\u5177\u662F\u5426\u5DF2\u6CE8\u518C\r\n */\r\n hasTool(name: string): boolean {\r\n return this.tools.has(name);\r\n }\r\n\r\n /**\r\n * \u83B7\u53D6\u5DF2\u6CE8\u518C\u7684\u5DE5\u5177\u6570\u91CF\r\n */\r\n get toolCount(): number {\r\n return this.tools.size;\r\n }\r\n}\r\n\r\n/**\r\n * \u6CE8\u518C\u5DE5\u5177\u76F8\u5173\u7684 MCP \u8BF7\u6C42\u5904\u7406\u5668\r\n * \r\n * \u5C06 tools/list \u548C tools/call \u8BF7\u6C42\u8DEF\u7531\u5230\u5DE5\u5177\u7BA1\u7406\u5668\u3002\r\n * \r\n * @param jsonRpcHandler - JSON-RPC \u5904\u7406\u5668\u5B9E\u4F8B\r\n * @param toolManager - \u5DE5\u5177\u7BA1\u7406\u5668\u5B9E\u4F8B\r\n */\r\nexport function registerToolHandlers(\r\n jsonRpcHandler: JsonRpcHandler,\r\n toolManager: ToolManager\r\n): void {\r\n // \u5904\u7406 tools/list \u8BF7\u6C42\r\n jsonRpcHandler.registerRequestHandler(\"tools/list\", async () => {\r\n const tools = toolManager.listTools();\r\n return { tools };\r\n });\r\n\r\n // \u5904\u7406 tools/call \u8BF7\u6C42\r\n jsonRpcHandler.registerRequestHandler(\"tools/call\", async (params) => {\r\n const name = params?.name as string;\r\n const args = (params?.arguments as Record<string, unknown>) ?? {};\r\n\r\n if (!name) {\r\n return createToolError(\"\u7F3A\u5C11\u5DE5\u5177\u540D\u79F0\");\r\n }\r\n\r\n return toolManager.callTool(name, args);\r\n });\r\n}\r\n", "/**\r\n * @fileoverview \u4F1A\u8BDD\u7BA1\u7406\u5668\r\n * \r\n * \u7BA1\u7406 DG-LAB \u8BBE\u5907\u7684\u4F1A\u8BDD\u72B6\u6001\uFF0C\u5305\u62EC\u8FDE\u63A5\u4FE1\u606F\u3001\u5F3A\u5EA6\u53C2\u6570\u548C\u751F\u547D\u5468\u671F\u3002\r\n * \u4F1A\u8BDD\u6570\u636E\u4EC5\u5B58\u50A8\u5728\u5185\u5B58\u4E2D\uFF0C\u4E0D\u505A\u78C1\u76D8\u6301\u4E45\u5316\uFF0C\u670D\u52A1\u91CD\u542F\u540E\u4F1A\u4E22\u5931\u3002\r\n * \r\n * \u4F1A\u8BDD\u751F\u547D\u5468\u671F\uFF1A\r\n * - \u521B\u5EFA\uFF1A\u8C03\u7528 dg_connect \u65F6\u521B\u5EFA\u65B0\u4F1A\u8BDD\r\n * - \u6D3B\u8DC3\uFF1A\u6BCF\u6B21\u64CD\u4F5C\u90FD\u4F1A\u66F4\u65B0 lastActive \u65F6\u95F4\u6233\r\n * - \u8FC7\u671F\uFF1A1 \u5C0F\u65F6\u4E0D\u6D3B\u52A8\u540E\u81EA\u52A8\u6E05\u7406\r\n * \r\n * \u4E3B\u8981\u529F\u80FD\uFF1A\r\n * - \u521B\u5EFA\u548C\u5220\u9664\u8BBE\u5907\u4F1A\u8BDD\r\n * - \u901A\u8FC7 deviceId \u6216 clientId \u67E5\u8BE2\u4F1A\u8BDD\r\n * - \u7BA1\u7406\u8BBE\u5907\u522B\u540D\r\n * - \u8DDF\u8E2A\u901A\u9053\u5F3A\u5EA6\u548C\u9650\u5236\r\n * - \u81EA\u52A8\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\r\n */\r\n\r\nimport { v4 as uuidv4 } from \"uuid\";\r\nimport type WebSocket from \"ws\";\r\n\r\n/** \u4F1A\u8BDD\u8FC7\u671F\u65F6\u95F4\uFF1A1 \u5C0F\u65F6\u4E0D\u6D3B\u52A8\u540E\u8FC7\u671F */\r\nconst SESSION_TTL_MS = 60 * 60 * 1000;\r\n/** \u6E05\u7406\u68C0\u67E5\u95F4\u9694\uFF1A\u6BCF 5 \u5206\u949F\u68C0\u67E5\u4E00\u6B21\u8FC7\u671F\u4F1A\u8BDD */\r\nconst CLEANUP_INTERVAL_MS = 5 * 60 * 1000;\r\n\r\n/**\r\n * \u8BBE\u5907\u4F1A\u8BDD\u6570\u636E\u7ED3\u6784\r\n * \r\n * \u5305\u542B\u8BBE\u5907\u8FDE\u63A5\u7684\u5B8C\u6574\u72B6\u6001\u4FE1\u606F\uFF0C\u4ECE\u521B\u5EFA\u5230\u65AD\u5F00\u7684\u6574\u4E2A\u751F\u547D\u5468\u671F\u3002\r\n */\r\nexport interface DeviceSession {\r\n /** \u8BBE\u5907\u552F\u4E00\u6807\u8BC6\u7B26\uFF0C\u7531\u670D\u52A1\u5668\u751F\u6210 */\r\n deviceId: string;\r\n /** \u7528\u6237\u8BBE\u7F6E\u7684\u8BBE\u5907\u522B\u540D\uFF0C\u4FBF\u4E8E\u8BC6\u522B */\r\n alias: string | null;\r\n /** WebSocket \u670D\u52A1\u5668\u5206\u914D\u7684\u5BA2\u6237\u7AEF ID\uFF0C\u7528\u4E8E\u4E0E APP \u901A\u4FE1 */\r\n clientId: string | null;\r\n /** \u7ED1\u5B9A\u7684 APP \u7684 ID */\r\n targetId: string | null;\r\n /** WebSocket \u8FDE\u63A5\u5B9E\u4F8B */\r\n ws: WebSocket | null;\r\n /** \u4F1A\u8BDD\u662F\u5426\u5DF2\u5EFA\u7ACB\u8FDE\u63A5 */\r\n connected: boolean;\r\n /** APP \u662F\u5426\u5DF2\u626B\u7801\u7ED1\u5B9A\uFF08\u5FC5\u987B\u4E3A true \u624D\u80FD\u63A7\u5236\u8BBE\u5907\uFF09 */\r\n boundToApp: boolean;\r\n /** A \u901A\u9053\u5F53\u524D\u5F3A\u5EA6\uFF080-200\uFF09 */\r\n strengthA: number;\r\n /** B \u901A\u9053\u5F53\u524D\u5F3A\u5EA6\uFF080-200\uFF09 */\r\n strengthB: number;\r\n /** A \u901A\u9053\u5F3A\u5EA6\u4E0A\u9650\uFF08\u7531 APP \u8BBE\u7F6E\uFF09 */\r\n strengthLimitA: number;\r\n /** B \u901A\u9053\u5F3A\u5EA6\u4E0A\u9650\uFF08\u7531 APP \u8BBE\u7F6E\uFF09 */\r\n strengthLimitB: number;\r\n /** \u6700\u540E\u6D3B\u8DC3\u65F6\u95F4\uFF0C\u7528\u4E8E\u8FC7\u671F\u68C0\u6D4B */\r\n lastActive: Date;\r\n /** \u4F1A\u8BDD\u521B\u5EFA\u65F6\u95F4 */\r\n createdAt: Date;\r\n /** \u8FDE\u63A5\u8D85\u65F6\u5B9A\u65F6\u5668 ID\uFF08\u672A\u7ED1\u5B9A APP \u65F6\u6709\u6548\uFF09 */\r\n connectionTimeoutId: ReturnType<typeof setTimeout> | null;\r\n}\r\n\r\n/**\r\n * \u4F1A\u8BDD\u7BA1\u7406\u5668\r\n * \r\n * \u8D1F\u8D23\u8BBE\u5907\u4F1A\u8BDD\u7684\u5B8C\u6574\u751F\u547D\u5468\u671F\u7BA1\u7406\uFF0C\u5305\u62EC\u521B\u5EFA\u3001\u67E5\u8BE2\u3001\u66F4\u65B0\u548C\u81EA\u52A8\u6E05\u7406\u3002\r\n * \u4F7F\u7528 Map \u5B58\u50A8\u4F1A\u8BDD\u6570\u636E\uFF0C\u652F\u6301\u901A\u8FC7 deviceId \u6216 clientId \u5FEB\u901F\u67E5\u627E\u3002\r\n */\r\nexport class SessionManager {\r\n private sessions: Map<string, DeviceSession> = new Map();\r\n private cleanupTimer: ReturnType<typeof setInterval> | null = null;\r\n /** \u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09 */\r\n private connectionTimeoutMs: number;\r\n\r\n constructor(connectionTimeoutMinutes: number = 5) {\r\n this.connectionTimeoutMs = connectionTimeoutMinutes * 60 * 1000;\r\n // \u542F\u52A8\u65F6\u7ACB\u5373\u5F00\u59CB\u5B9A\u671F\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\r\n this.startCleanupTimer();\r\n console.log(`[\u4F1A\u8BDD] \u8FDE\u63A5\u8D85\u65F6\u8BBE\u7F6E: ${connectionTimeoutMinutes} \u5206\u949F`);\r\n }\r\n\r\n /**\r\n * \u521B\u5EFA\u65B0\u7684\u8BBE\u5907\u4F1A\u8BDD\r\n * \r\n * \u751F\u6210\u552F\u4E00\u7684 deviceId \u5E76\u521D\u59CB\u5316\u4F1A\u8BDD\u72B6\u6001\u3002\r\n * \u65B0\u4F1A\u8BDD\u9ED8\u8BA4\u672A\u8FDE\u63A5\u3001\u672A\u7ED1\u5B9A\uFF0C\u5F3A\u5EA6\u4E3A 0\uFF0C\u4E0A\u9650\u4E3A 200\u3002\r\n * \u4F1A\u542F\u52A8\u8FDE\u63A5\u8D85\u65F6\u8BA1\u65F6\u5668\uFF0C\u5982\u679C\u5728\u8D85\u65F6\u65F6\u95F4\u5185\u672A\u7ED1\u5B9A APP \u5219\u81EA\u52A8\u9500\u6BC1\u3002\r\n * \r\n * @returns \u65B0\u521B\u5EFA\u7684\u4F1A\u8BDD\u5BF9\u8C61\r\n */\r\n createSession(): DeviceSession {\r\n const deviceId = uuidv4();\r\n const now = new Date();\r\n\r\n const session: DeviceSession = {\r\n deviceId,\r\n alias: null,\r\n clientId: null,\r\n targetId: null,\r\n ws: null,\r\n connected: false,\r\n boundToApp: false,\r\n strengthA: 0,\r\n strengthB: 0,\r\n strengthLimitA: 200,\r\n strengthLimitB: 200,\r\n lastActive: now,\r\n createdAt: now,\r\n connectionTimeoutId: null,\r\n };\r\n\r\n // \u542F\u52A8\u8FDE\u63A5\u8D85\u65F6\u8BA1\u65F6\u5668\r\n session.connectionTimeoutId = setTimeout(() => {\r\n const currentSession = this.sessions.get(deviceId);\r\n if (currentSession && !currentSession.boundToApp) {\r\n console.log(`[\u4F1A\u8BDD] \u8FDE\u63A5\u8D85\u65F6: ${deviceId} (${this.connectionTimeoutMs / 60000} \u5206\u949F\u5185\u672A\u7ED1\u5B9A APP)`);\r\n this.deleteSession(deviceId);\r\n }\r\n }, this.connectionTimeoutMs);\r\n\r\n this.sessions.set(deviceId, session);\r\n console.log(`[\u4F1A\u8BDD] \u5DF2\u521B\u5EFA: ${deviceId}`);\r\n return session;\r\n }\r\n\r\n /**\r\n * \u6839\u636E deviceId \u83B7\u53D6\u4F1A\u8BDD\r\n * \r\n * \u5982\u679C\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u4F1A\u81EA\u52A8\u5220\u9664\u5E76\u8FD4\u56DE null\u3002\r\n * \r\n * @param deviceId - \u8BBE\u5907 ID\r\n * @returns \u4F1A\u8BDD\u5BF9\u8C61\uFF0C\u5982\u679C\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F\u5219\u8FD4\u56DE null\r\n */\r\n getSession(deviceId: string): DeviceSession | null {\r\n const session = this.sessions.get(deviceId);\r\n if (session) {\r\n if (this.isExpired(session)) {\r\n this.deleteSession(deviceId);\r\n return null;\r\n }\r\n }\r\n return session ?? null;\r\n }\r\n\r\n /**\r\n * \u6839\u636E clientId \u83B7\u53D6\u4F1A\u8BDD\r\n * \r\n * clientId \u662F WebSocket \u670D\u52A1\u5668\u5206\u914D\u7684\u6807\u8BC6\u7B26\uFF0C\u7528\u4E8E\u5173\u8054 MCP \u4F1A\u8BDD\u548C WS \u8FDE\u63A5\u3002\r\n * \r\n * @param clientId - WebSocket \u5BA2\u6237\u7AEF ID\r\n * @returns \u4F1A\u8BDD\u5BF9\u8C61\uFF0C\u5982\u679C\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F\u5219\u8FD4\u56DE null\r\n */\r\n getSessionByClientId(clientId: string): DeviceSession | null {\r\n for (const session of this.sessions.values()) {\r\n if (session.clientId === clientId && !this.isExpired(session)) {\r\n return session;\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * \u5217\u51FA\u6240\u6709\u6D3B\u8DC3\u4F1A\u8BDD\r\n * \r\n * \u8FD4\u56DE\u524D\u4F1A\u5148\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\uFF0C\u786E\u4FDD\u8FD4\u56DE\u7684\u90FD\u662F\u6709\u6548\u4F1A\u8BDD\u3002\r\n * \r\n * @returns \u6240\u6709\u6D3B\u8DC3\u4F1A\u8BDD\u7684\u6570\u7EC4\r\n */\r\n listSessions(): DeviceSession[] {\r\n this.cleanupExpiredSessions();\r\n return Array.from(this.sessions.values());\r\n }\r\n\r\n /**\r\n * \u5220\u9664\u4F1A\u8BDD\r\n * \r\n * \u4F1A\u5173\u95ED\u5173\u8054\u7684 WebSocket \u8FDE\u63A5\u3001\u6E05\u7406\u8D85\u65F6\u8BA1\u65F6\u5668\u5E76\u4ECE\u5185\u5B58\u4E2D\u79FB\u9664\u4F1A\u8BDD\u6570\u636E\u3002\r\n * \r\n * @param deviceId - \u8981\u5220\u9664\u7684\u8BBE\u5907 ID\r\n * @returns \u662F\u5426\u6210\u529F\u5220\u9664\uFF08false \u8868\u793A\u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF09\r\n */\r\n deleteSession(deviceId: string): boolean {\r\n const session = this.sessions.get(deviceId);\r\n if (session) {\r\n // \u6E05\u7406\u8FDE\u63A5\u8D85\u65F6\u8BA1\u65F6\u5668\r\n if (session.connectionTimeoutId) {\r\n clearTimeout(session.connectionTimeoutId);\r\n session.connectionTimeoutId = null;\r\n }\r\n if (session.ws) {\r\n try { session.ws.close(); } catch { /* \u5FFD\u7565 */ }\r\n }\r\n this.sessions.delete(deviceId);\r\n console.log(`[\u4F1A\u8BDD] \u5DF2\u5220\u9664: ${deviceId}`);\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * \u8BBE\u7F6E\u8BBE\u5907\u522B\u540D\r\n * \r\n * \u522B\u540D\u7528\u4E8E\u65B9\u4FBF\u8BC6\u522B\u8BBE\u5907\uFF0C\u652F\u6301\u4E2D\u6587\u548C\u7279\u6B8A\u5B57\u7B26\u3002\r\n * \u8BBE\u7F6E\u522B\u540D\u4F1A\u540C\u65F6\u66F4\u65B0\u4F1A\u8BDD\u7684\u6D3B\u8DC3\u65F6\u95F4\u3002\r\n * \r\n * @param deviceId - \u8BBE\u5907 ID\r\n * @param alias - \u65B0\u7684\u522B\u540D\r\n * @returns \u662F\u5426\u6210\u529F\u8BBE\u7F6E\uFF08false \u8868\u793A\u8BBE\u5907\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F\uFF09\r\n */\r\n setAlias(deviceId: string, alias: string): boolean {\r\n const session = this.sessions.get(deviceId);\r\n if (session && !this.isExpired(session)) {\r\n session.alias = alias;\r\n session.lastActive = new Date();\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * \u6839\u636E\u522B\u540D\u67E5\u627E\u4F1A\u8BDD\r\n * \r\n * \u652F\u6301\u5927\u5C0F\u5199\u4E0D\u654F\u611F\u7684\u7CBE\u786E\u5339\u914D\u3002\r\n * \r\n * @param alias - \u8981\u67E5\u627E\u7684\u522B\u540D\r\n * @returns \u6240\u6709\u5339\u914D\u7684\u4F1A\u8BDD\u6570\u7EC4\r\n */\r\n findByAlias(alias: string): DeviceSession[] {\r\n const lowerAlias = alias.toLowerCase();\r\n return Array.from(this.sessions.values()).filter(\r\n (s) => !this.isExpired(s) && s.alias?.toLowerCase() === lowerAlias\r\n );\r\n }\r\n\r\n /**\r\n * \u66F4\u65B0\u4F1A\u8BDD\u8FDE\u63A5\u72B6\u6001\r\n * \r\n * \u7528\u4E8E\u5728 WebSocket \u8FDE\u63A5\u5EFA\u7ACB\u6216\u65AD\u5F00\u65F6\u66F4\u65B0\u4F1A\u8BDD\u72B6\u6001\u3002\r\n * \r\n * @param deviceId - \u8BBE\u5907 ID\r\n * @param updates - \u8981\u66F4\u65B0\u7684\u5B57\u6BB5\r\n * @returns \u662F\u5426\u6210\u529F\u66F4\u65B0\r\n */\r\n updateConnectionState(\r\n deviceId: string,\r\n updates: Partial<Pick<DeviceSession, \"connected\" | \"boundToApp\" | \"clientId\" | \"targetId\" | \"ws\">>\r\n ): boolean {\r\n const session = this.sessions.get(deviceId);\r\n if (session) {\r\n Object.assign(session, updates);\r\n session.lastActive = new Date();\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * \u66F4\u65B0\u4F1A\u8BDD\u5F3A\u5EA6\u503C\r\n * \r\n * \u5F53\u6536\u5230 APP \u7684\u5F3A\u5EA6\u53CD\u9988\u65F6\u8C03\u7528\uFF0C\u540C\u6B65\u66F4\u65B0\u4F1A\u8BDD\u4E2D\u7684\u5F3A\u5EA6\u6570\u636E\u3002\r\n * \r\n * @param deviceId - \u8BBE\u5907 ID\r\n * @param strengthA - A \u901A\u9053\u5F53\u524D\u5F3A\u5EA6\r\n * @param strengthB - B \u901A\u9053\u5F53\u524D\u5F3A\u5EA6\r\n * @param strengthLimitA - A \u901A\u9053\u5F3A\u5EA6\u4E0A\u9650\r\n * @param strengthLimitB - B \u901A\u9053\u5F3A\u5EA6\u4E0A\u9650\r\n * @returns \u662F\u5426\u6210\u529F\u66F4\u65B0\r\n */\r\n updateStrength(\r\n deviceId: string,\r\n strengthA: number,\r\n strengthB: number,\r\n strengthLimitA: number,\r\n strengthLimitB: number\r\n ): boolean {\r\n const session = this.sessions.get(deviceId);\r\n if (session) {\r\n session.strengthA = strengthA;\r\n session.strengthB = strengthB;\r\n session.strengthLimitA = strengthLimitA;\r\n session.strengthLimitB = strengthLimitB;\r\n session.lastActive = new Date();\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * \u89E6\u6478\u4F1A\u8BDD\u4EE5\u4FDD\u6301\u6D3B\u8DC3\r\n * \r\n * \u66F4\u65B0 lastActive \u65F6\u95F4\u6233\uFF0C\u9632\u6B62\u4F1A\u8BDD\u56E0\u4E0D\u6D3B\u52A8\u800C\u8FC7\u671F\u3002\r\n * \u6BCF\u6B21\u5BF9\u8BBE\u5907\u7684\u64CD\u4F5C\u90FD\u5E94\u8BE5\u8C03\u7528\u6B64\u65B9\u6CD5\u3002\r\n * \r\n * @param deviceId - \u8BBE\u5907 ID\r\n * @returns \u662F\u5426\u6210\u529F\r\n */\r\n touchSession(deviceId: string): boolean {\r\n const session = this.sessions.get(deviceId);\r\n if (session) {\r\n session.lastActive = new Date();\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * APP \u7ED1\u5B9A\u6210\u529F\u65F6\u8C03\u7528\r\n * \r\n * \u53D6\u6D88\u8FDE\u63A5\u8D85\u65F6\u8BA1\u65F6\u5668\uFF0C\u56E0\u4E3A\u8BBE\u5907\u5DF2\u6210\u529F\u7ED1\u5B9A APP\u3002\r\n * \u540C\u65F6\u66F4\u65B0\u4F1A\u8BDD\u7684 boundToApp \u72B6\u6001\u3002\r\n * \r\n * @param deviceId - \u8BBE\u5907 ID\r\n * @returns \u662F\u5426\u6210\u529F\r\n */\r\n onAppBound(deviceId: string): boolean {\r\n const session = this.sessions.get(deviceId);\r\n if (session) {\r\n // \u53D6\u6D88\u8FDE\u63A5\u8D85\u65F6\u8BA1\u65F6\u5668\r\n if (session.connectionTimeoutId) {\r\n clearTimeout(session.connectionTimeoutId);\r\n session.connectionTimeoutId = null;\r\n console.log(`[\u4F1A\u8BDD] \u5DF2\u53D6\u6D88\u8FDE\u63A5\u8D85\u65F6: ${deviceId} (APP \u5DF2\u7ED1\u5B9A)`);\r\n }\r\n session.boundToApp = true;\r\n session.lastActive = new Date();\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * \u83B7\u53D6\u5F53\u524D\u4F1A\u8BDD\u6570\u91CF\r\n */\r\n get sessionCount(): number {\r\n return this.sessions.size;\r\n }\r\n\r\n /**\r\n * \u68C0\u67E5\u4F1A\u8BDD\u662F\u5426\u5DF2\u8FC7\u671F\r\n */\r\n private isExpired(session: DeviceSession): boolean {\r\n return Date.now() - session.lastActive.getTime() > SESSION_TTL_MS;\r\n }\r\n\r\n /**\r\n * \u6E05\u7406\u6240\u6709\u8FC7\u671F\u4F1A\u8BDD\r\n * \r\n * \u904D\u5386\u6240\u6709\u4F1A\u8BDD\uFF0C\u5220\u9664\u8D85\u8FC7 TTL \u7684\u4F1A\u8BDD\u5E76\u5173\u95ED\u5176 WebSocket \u8FDE\u63A5\u3002\r\n * \r\n * @returns \u6E05\u7406\u7684\u4F1A\u8BDD\u6570\u91CF\r\n */\r\n cleanupExpiredSessions(): number {\r\n let cleaned = 0;\r\n const now = Date.now();\r\n\r\n for (const [deviceId, session] of this.sessions) {\r\n const age = now - session.lastActive.getTime();\r\n if (age > SESSION_TTL_MS) {\r\n // \u6E05\u7406\u8FDE\u63A5\u8D85\u65F6\u8BA1\u65F6\u5668\r\n if (session.connectionTimeoutId) {\r\n clearTimeout(session.connectionTimeoutId);\r\n }\r\n if (session.ws) {\r\n try { session.ws.close(); } catch { /* \u5FFD\u7565 */ }\r\n }\r\n this.sessions.delete(deviceId);\r\n console.log(`[\u4F1A\u8BDD] \u5DF2\u8FC7\u671F: ${deviceId} (\u4E0D\u6D3B\u8DC3 ${Math.round(age / 60000)} \u5206\u949F)`);\r\n cleaned++;\r\n }\r\n }\r\n return cleaned;\r\n }\r\n\r\n /**\r\n * \u542F\u52A8\u5B9A\u671F\u6E05\u7406\u5B9A\u65F6\u5668\r\n */\r\n private startCleanupTimer(): void {\r\n this.cleanupTimer = setInterval(() => {\r\n const cleaned = this.cleanupExpiredSessions();\r\n if (cleaned > 0) {\r\n console.log(`[\u4F1A\u8BDD] \u6E05\u7406: ${cleaned} \u4E2A\u8FC7\u671F\uFF0C\u5269\u4F59 ${this.sessions.size} \u4E2A`);\r\n }\r\n }, CLEANUP_INTERVAL_MS);\r\n }\r\n\r\n /**\r\n * \u505C\u6B62\u5B9A\u671F\u6E05\u7406\u5B9A\u65F6\u5668\r\n * \r\n * \u5728\u670D\u52A1\u5668\u5173\u95ED\u65F6\u8C03\u7528\uFF0C\u9632\u6B62\u5185\u5B58\u6CC4\u6F0F\u3002\r\n */\r\n stopCleanupTimer(): void {\r\n if (this.cleanupTimer) {\r\n clearInterval(this.cleanupTimer);\r\n this.cleanupTimer = null;\r\n }\r\n }\r\n\r\n /**\r\n * \u6E05\u9664\u6240\u6709\u4F1A\u8BDD\r\n * \r\n * \u5173\u95ED\u6240\u6709 WebSocket \u8FDE\u63A5\u3001\u6E05\u7406\u6240\u6709\u8BA1\u65F6\u5668\u5E76\u6E05\u7A7A\u4F1A\u8BDD\u5B58\u50A8\u3002\r\n * \u901A\u5E38\u5728\u670D\u52A1\u5668\u5173\u95ED\u6216\u91CD\u7F6E\u65F6\u8C03\u7528\u3002\r\n */\r\n clearAll(): void {\r\n for (const session of this.sessions.values()) {\r\n // \u6E05\u7406\u8FDE\u63A5\u8D85\u65F6\u8BA1\u65F6\u5668\r\n if (session.connectionTimeoutId) {\r\n clearTimeout(session.connectionTimeoutId);\r\n }\r\n if (session.ws) {\r\n try { session.ws.close(); } catch { /* \u5FFD\u7565 */ }\r\n }\r\n }\r\n this.sessions.clear();\r\n }\r\n}\r\n", "/**\r\n * @fileoverview DG-LAB WebSocket \u670D\u52A1\u5668\r\n * @description \u81EA\u6258\u7BA1\u7684 WebSocket \u670D\u52A1\u5668\uFF0C\u57FA\u4E8E temp_dg_plugin/app.js\r\n * \u66FF\u4EE3\u8FDE\u63A5\u5916\u90E8 WS \u670D\u52A1\u5668 - \u6211\u4EEC\u5C31\u662F WS \u670D\u52A1\u5668\r\n * \u652F\u6301\u72EC\u7ACB\u7AEF\u53E3\u6216\u9644\u52A0\u5230\u73B0\u6709 HTTP \u670D\u52A1\u5668\r\n */\r\n\r\nimport { WebSocketServer, WebSocket } from \"ws\";\r\nimport { v4 as uuidv4 } from \"uuid\";\r\nimport type { IncomingMessage } from \"http\";\r\nimport type { Server as HttpServer } from \"http\";\r\n\r\n/** DG-LAB WebSocket \u6D88\u606F\u7C7B\u578B */\r\nexport type DGLabMessageType = \"bind\" | \"msg\" | \"heartbeat\" | \"break\" | \"error\";\r\n\r\n/** DG-LAB WebSocket \u6D88\u606F */\r\nexport interface DGLabMessage {\r\n type: DGLabMessageType | string;\r\n clientId: string;\r\n targetId: string;\r\n message: string;\r\n channel?: string;\r\n time?: number;\r\n}\r\n\r\n/** \u5BA2\u6237\u7AEF\u4FE1\u606F */\r\ninterface ClientInfo {\r\n id: string;\r\n ws: WebSocket;\r\n type: \"controller\" | \"app\" | \"unknown\";\r\n boundTo: string | null;\r\n lastActive: number;\r\n}\r\n\r\n/** \u6CE2\u5F62\u53D1\u9001\u5B9A\u65F6\u5668\u4FE1\u606F */\r\ninterface WaveformTimer {\r\n timerId: ReturnType<typeof setInterval>;\r\n remaining: number;\r\n}\r\n\r\n/** \u6301\u7EED\u64AD\u653E\u72B6\u6001 */\r\ninterface ContinuousPlaybackState {\r\n /** \u63A7\u5236\u5668 ID */\r\n controllerId: string;\r\n /** \u901A\u9053 */\r\n channel: \"A\" | \"B\";\r\n /** \u6CE2\u5F62\u6570\u636E\uFF08\u5FAA\u73AF\u64AD\u653E\uFF09 */\r\n waveforms: string[];\r\n /** \u5F53\u524D\u64AD\u653E\u7D22\u5F15 */\r\n currentIndex: number;\r\n /** \u53D1\u9001\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09 */\r\n interval: number;\r\n /** \u6BCF\u6B21\u53D1\u9001\u7684\u6CE2\u5F62\u6570\u91CF */\r\n batchSize: number;\r\n /** \u5B9A\u65F6\u5668 ID */\r\n timerId: ReturnType<typeof setInterval> | null;\r\n /** \u662F\u5426\u6B63\u5728\u64AD\u653E */\r\n active: boolean;\r\n}\r\n\r\n/** WebSocket \u670D\u52A1\u5668\u9009\u9879 */\r\nexport interface WSServerOptions {\r\n /** \u72EC\u7ACB\u7AEF\u53E3\uFF08\u5982\u679C\u4E0D\u9644\u52A0\u5230 HTTP \u670D\u52A1\u5668\uFF09 */\r\n port?: number;\r\n /** \u5FC3\u8DF3\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09 */\r\n heartbeatInterval?: number;\r\n /** \u5F3A\u5EA6\u66F4\u65B0\u56DE\u8C03 */\r\n onStrengthUpdate?: (controllerId: string, a: number, b: number, limitA: number, limitB: number) => void;\r\n /** \u53CD\u9988\u56DE\u8C03 */\r\n onFeedback?: (controllerId: string, index: number) => void;\r\n /** \u7ED1\u5B9A\u53D8\u5316\u56DE\u8C03 */\r\n onBindChange?: (controllerId: string, appId: string | null) => void;\r\n /** \u63A7\u5236\u5668\u65AD\u5F00\u56DE\u8C03 */\r\n onControllerDisconnect?: (controllerId: string) => void;\r\n /** APP \u65AD\u5F00\u56DE\u8C03 */\r\n onAppDisconnect?: (appId: string) => void;\r\n}\r\n\r\n/**\r\n * DG-LAB WebSocket \u670D\u52A1\u5668\u7C7B\r\n */\r\nexport class DGLabWSServer {\r\n private wss: WebSocketServer | null = null;\r\n private clients: Map<string, ClientInfo> = new Map();\r\n private relations: Map<string, string> = new Map();\r\n private waveformTimers: Map<string, WaveformTimer> = new Map();\r\n /** \u6301\u7EED\u64AD\u653E\u72B6\u6001 Map\uFF0Ckey \u683C\u5F0F: controllerId-channel */\r\n private continuousPlaybacks: Map<string, ContinuousPlaybackState> = new Map();\r\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\r\n private options: WSServerOptions & { heartbeatInterval: number };\r\n private attachedPort: number = 0;\r\n\r\n constructor(options: WSServerOptions) {\r\n this.options = {\r\n heartbeatInterval: 60000,\r\n onStrengthUpdate: () => {},\r\n onFeedback: () => {},\r\n onBindChange: () => {},\r\n ...options,\r\n };\r\n }\r\n\r\n /** \u542F\u52A8\u72EC\u7ACB\u7684 WebSocket \u670D\u52A1\u5668\uFF08\u4F7F\u7528\u72EC\u7ACB\u7AEF\u53E3\uFF09 */\r\n start(): void {\r\n if (!this.options.port) {\r\n throw new Error(\"\u72EC\u7ACB\u542F\u52A8\u9700\u8981\u6307\u5B9A port\");\r\n }\r\n this.wss = new WebSocketServer({ port: this.options.port });\r\n this.wss.on(\"connection\", (ws: WebSocket, req: IncomingMessage) => {\r\n this.handleConnection(ws, req);\r\n });\r\n this.startHeartbeat();\r\n this.attachedPort = this.options.port;\r\n console.log(`[WS \u670D\u52A1\u5668] \u72EC\u7ACB\u76D1\u542C\u7AEF\u53E3 ${this.options.port}`);\r\n }\r\n\r\n /** \u9644\u52A0\u5230\u73B0\u6709\u7684 HTTP \u670D\u52A1\u5668\uFF08\u5171\u4EAB\u7AEF\u53E3\uFF09 */\r\n attachToServer(httpServer: HttpServer, port: number): void {\r\n this.wss = new WebSocketServer({ noServer: true });\r\n \r\n // \u5904\u7406 HTTP \u670D\u52A1\u5668\u7684 upgrade \u4E8B\u4EF6\r\n httpServer.on(\"upgrade\", (request, socket, head) => {\r\n // \u6240\u6709 WebSocket \u5347\u7EA7\u8BF7\u6C42\u90FD\u7531\u6211\u4EEC\u5904\u7406\r\n this.wss!.handleUpgrade(request, socket, head, (ws) => {\r\n this.wss!.emit(\"connection\", ws, request);\r\n });\r\n });\r\n\r\n this.wss.on(\"connection\", (ws: WebSocket, req: IncomingMessage) => {\r\n this.handleConnection(ws, req);\r\n });\r\n\r\n this.startHeartbeat();\r\n this.attachedPort = port;\r\n console.log(`[WS \u670D\u52A1\u5668] \u5DF2\u9644\u52A0\u5230 HTTP \u670D\u52A1\u5668\uFF0C\u5171\u4EAB\u7AEF\u53E3 ${port}`);\r\n }\r\n\r\n /** \u542F\u52A8\u5FC3\u8DF3\u5B9A\u65F6\u5668 */\r\n private startHeartbeat(): void {\r\n this.heartbeatTimer = setInterval(() => {\r\n this.sendHeartbeats();\r\n }, this.options.heartbeatInterval);\r\n }\r\n\r\n /** \u505C\u6B62 WebSocket \u670D\u52A1\u5668 */\r\n stop(): void {\r\n if (this.heartbeatTimer) {\r\n clearInterval(this.heartbeatTimer);\r\n this.heartbeatTimer = null;\r\n }\r\n for (const timer of this.waveformTimers.values()) {\r\n clearInterval(timer.timerId);\r\n }\r\n this.waveformTimers.clear();\r\n // \u6E05\u7406\u6240\u6709\u6301\u7EED\u64AD\u653E\r\n for (const playback of this.continuousPlaybacks.values()) {\r\n if (playback.timerId) {\r\n clearInterval(playback.timerId);\r\n }\r\n }\r\n this.continuousPlaybacks.clear();\r\n for (const client of this.clients.values()) {\r\n client.ws.close();\r\n }\r\n this.clients.clear();\r\n this.relations.clear();\r\n if (this.wss) {\r\n this.wss.close();\r\n this.wss = null;\r\n }\r\n console.log(\"[WS \u670D\u52A1\u5668] \u5DF2\u505C\u6B62\");\r\n }\r\n\r\n /** \u5904\u7406\u65B0\u7684 WebSocket \u8FDE\u63A5 */\r\n private handleConnection(ws: WebSocket, _req: IncomingMessage): void {\r\n const clientId = uuidv4();\r\n const clientInfo: ClientInfo = {\r\n id: clientId,\r\n ws,\r\n type: \"unknown\",\r\n boundTo: null,\r\n lastActive: Date.now(),\r\n };\r\n this.clients.set(clientId, clientInfo);\r\n console.log(`[WS \u670D\u52A1\u5668] \u65B0\u8FDE\u63A5: ${clientId}`);\r\n this.send(ws, { type: \"bind\", clientId, targetId: \"\", message: \"targetId\" });\r\n ws.on(\"message\", (data) => this.handleMessage(clientId, data.toString()));\r\n ws.on(\"close\", () => this.handleClose(clientId));\r\n ws.on(\"error\", (error) => {\r\n console.error(`[WS \u670D\u52A1\u5668] \u9519\u8BEF ${clientId}:`, error.message);\r\n this.handleError(clientId, error);\r\n });\r\n }\r\n\r\n /** \u5904\u7406\u6536\u5230\u7684\u6D88\u606F */\r\n private handleMessage(clientId: string, rawData: string): void {\r\n console.log(`[WS \u670D\u52A1\u5668] \u6536\u5230 ${clientId}: ${rawData}`);\r\n const client = this.clients.get(clientId);\r\n if (!client) return;\r\n client.lastActive = Date.now();\r\n\r\n let data: DGLabMessage;\r\n try {\r\n data = JSON.parse(rawData);\r\n } catch {\r\n this.send(client.ws, { type: \"msg\", clientId: \"\", targetId: \"\", message: \"403\" });\r\n return;\r\n }\r\n\r\n if (data.clientId !== clientId && data.targetId !== clientId) {\r\n if (!(data.type === \"bind\" && data.message === \"DGLAB\")) {\r\n this.send(client.ws, { type: \"msg\", clientId: \"\", targetId: \"\", message: \"404\" });\r\n return;\r\n }\r\n }\r\n\r\n switch (data.type) {\r\n case \"bind\": this.handleBind(clientId, data); break;\r\n case \"msg\": this.handleMsg(clientId, data); break;\r\n case \"heartbeat\": break;\r\n default: this.forwardMessage(clientId, data); break;\r\n }\r\n }\r\n\r\n /** \u5904\u7406\u7ED1\u5B9A\u8BF7\u6C42 */\r\n private handleBind(clientId: string, data: DGLabMessage): void {\r\n const client = this.clients.get(clientId);\r\n if (!client) return;\r\n\r\n if (data.message === \"DGLAB\" && data.clientId && data.targetId) {\r\n const controllerId = data.clientId;\r\n const appId = data.targetId;\r\n\r\n if (!this.clients.has(controllerId) || !this.clients.has(appId)) {\r\n this.send(client.ws, { type: \"bind\", clientId: controllerId, targetId: appId, message: \"401\" });\r\n return;\r\n }\r\n\r\n const alreadyBound = [controllerId, appId].some(\r\n (id) => this.relations.has(id) || [...this.relations.values()].includes(id)\r\n );\r\n if (alreadyBound) {\r\n this.send(client.ws, { type: \"bind\", clientId: controllerId, targetId: appId, message: \"400\" });\r\n return;\r\n }\r\n\r\n this.relations.set(controllerId, appId);\r\n const controllerClient = this.clients.get(controllerId);\r\n const appClient = this.clients.get(appId);\r\n if (controllerClient) { controllerClient.type = \"controller\"; controllerClient.boundTo = appId; }\r\n if (appClient) { appClient.type = \"app\"; appClient.boundTo = controllerId; }\r\n\r\n const successMsg: DGLabMessage = { type: \"bind\", clientId: controllerId, targetId: appId, message: \"200\" };\r\n if (controllerClient) this.send(controllerClient.ws, successMsg);\r\n if (appClient) this.send(appClient.ws, successMsg);\r\n if (this.options.onBindChange) {\r\n this.options.onBindChange(controllerId, appId);\r\n }\r\n console.log(`[WS \u670D\u52A1\u5668] \u5DF2\u7ED1\u5B9A: ${controllerId} <-> ${appId}`);\r\n }\r\n }\r\n\r\n /** \u5904\u7406 msg \u7C7B\u578B\u6D88\u606F */\r\n private handleMsg(clientId: string, data: DGLabMessage): void {\r\n const { message } = data;\r\n\r\n if (message.startsWith(\"strength-\")) {\r\n const parsed = this.parseStrengthMessage(message);\r\n if (parsed) {\r\n const client = this.clients.get(clientId);\r\n if (client?.boundTo && this.options.onStrengthUpdate) {\r\n this.options.onStrengthUpdate(client.boundTo, parsed.strengthA, parsed.strengthB, parsed.limitA, parsed.limitB);\r\n }\r\n this.forwardMessage(clientId, data);\r\n }\r\n return;\r\n }\r\n\r\n if (message.startsWith(\"feedback-\")) {\r\n const index = parseInt(message.substring(9));\r\n if (!isNaN(index)) {\r\n const client = this.clients.get(clientId);\r\n if (client?.boundTo && this.options.onFeedback) {\r\n this.options.onFeedback(client.boundTo, index);\r\n }\r\n }\r\n this.forwardMessage(clientId, data);\r\n return;\r\n }\r\n\r\n this.forwardMessage(clientId, data);\r\n }\r\n\r\n /** \u8F6C\u53D1\u6D88\u606F\u7ED9\u7ED1\u5B9A\u7684\u5BF9\u65B9 */\r\n private forwardMessage(fromClientId: string, data: DGLabMessage): void {\r\n const client = this.clients.get(fromClientId);\r\n if (!client?.boundTo) return;\r\n\r\n const boundId = this.relations.get(fromClientId) || \r\n [...this.relations.entries()].find(([_, v]) => v === fromClientId)?.[0];\r\n if (!boundId) {\r\n this.send(client.ws, { type: \"bind\", clientId: data.clientId, targetId: data.targetId, message: \"402\" });\r\n return;\r\n }\r\n\r\n const targetClient = this.clients.get(client.boundTo);\r\n if (targetClient) {\r\n this.send(targetClient.ws, data);\r\n } else {\r\n this.send(client.ws, { type: \"msg\", clientId: data.clientId, targetId: data.targetId, message: \"404\" });\r\n }\r\n }\r\n\r\n /** \u5904\u7406\u5BA2\u6237\u7AEF\u65AD\u5F00 */\r\n private handleClose(clientId: string): void {\r\n console.log(`[WS \u670D\u52A1\u5668] \u65AD\u5F00: ${clientId}`);\r\n const client = this.clients.get(clientId);\r\n if (!client) return;\r\n\r\n // \u6E05\u7406\u8BE5\u5BA2\u6237\u7AEF\u7684\u6CE2\u5F62\u5B9A\u65F6\u5668\r\n for (const [key, timer] of this.waveformTimers.entries()) {\r\n if (key.startsWith(clientId + \"-\")) {\r\n clearInterval(timer.timerId);\r\n this.waveformTimers.delete(key);\r\n }\r\n }\r\n\r\n // \u6E05\u7406\u8BE5\u5BA2\u6237\u7AEF\u7684\u6301\u7EED\u64AD\u653E\r\n for (const [key, playback] of this.continuousPlaybacks.entries()) {\r\n if (playback.controllerId === clientId) {\r\n if (playback.timerId) {\r\n clearInterval(playback.timerId);\r\n }\r\n this.continuousPlaybacks.delete(key);\r\n console.log(`[WS \u670D\u52A1\u5668] \u5DF2\u505C\u6B62\u6301\u7EED\u64AD\u653E: ${key}`);\r\n }\r\n }\r\n\r\n if (client.type === \"app\") {\r\n // APP \u65AD\u5F00\u5904\u7406\r\n // \u67E5\u627E\u6240\u6709\u7ED1\u5B9A\u5230\u8BE5 APP \u7684\u63A7\u5236\u5668\r\n for (const [controllerId, appId] of this.relations.entries()) {\r\n if (appId === clientId) {\r\n const controller = this.clients.get(controllerId);\r\n if (controller) {\r\n // \u901A\u77E5\u63A7\u5236\u5668 APP \u5DF2\u65AD\u5F00\r\n this.send(controller.ws, { \r\n type: \"break\", \r\n clientId: controllerId, \r\n targetId: clientId, \r\n message: \"209\" \r\n });\r\n controller.boundTo = null;\r\n }\r\n // \u6E05\u7406\u7ED1\u5B9A\u5173\u7CFB\r\n this.relations.delete(controllerId);\r\n // \u89E6\u53D1\u7ED1\u5B9A\u53D8\u5316\u56DE\u8C03\r\n if (this.options.onBindChange) {\r\n this.options.onBindChange(controllerId, null);\r\n }\r\n }\r\n }\r\n // \u89E6\u53D1 APP \u65AD\u5F00\u56DE\u8C03\r\n if (this.options.onAppDisconnect) {\r\n this.options.onAppDisconnect(clientId);\r\n }\r\n } else if (client.type === \"controller\") {\r\n // \u63A7\u5236\u5668\u65AD\u5F00\u5904\u7406\r\n if (client.boundTo) {\r\n const partner = this.clients.get(client.boundTo);\r\n if (partner) {\r\n this.send(partner.ws, { type: \"break\", clientId: client.boundTo, targetId: clientId, message: \"209\" });\r\n partner.boundTo = null;\r\n }\r\n this.relations.delete(clientId);\r\n // \u89E6\u53D1\u7ED1\u5B9A\u53D8\u5316\u56DE\u8C03\r\n if (this.options.onBindChange) {\r\n this.options.onBindChange(clientId, null);\r\n }\r\n }\r\n // \u89E6\u53D1\u63A7\u5236\u5668\u65AD\u5F00\u56DE\u8C03\r\n if (this.options.onControllerDisconnect) {\r\n this.options.onControllerDisconnect(clientId);\r\n }\r\n } else {\r\n // \u672A\u77E5\u7C7B\u578B\u5BA2\u6237\u7AEF\u65AD\u5F00\uFF0C\u6E05\u7406\u53EF\u80FD\u7684\u7ED1\u5B9A\u5173\u7CFB\r\n if (client.boundTo) {\r\n const partner = this.clients.get(client.boundTo);\r\n if (partner) {\r\n this.send(partner.ws, { type: \"break\", clientId: client.boundTo, targetId: clientId, message: \"209\" });\r\n partner.boundTo = null;\r\n }\r\n this.relations.delete(clientId);\r\n this.relations.delete(client.boundTo);\r\n }\r\n }\r\n\r\n this.clients.delete(clientId);\r\n console.log(`[WS \u670D\u52A1\u5668] \u5DF2\u6E05\u7406 ${clientId}\uFF0C\u5BA2\u6237\u7AEF\u6570: ${this.clients.size}`);\r\n }\r\n\r\n /** \u5904\u7406\u5BA2\u6237\u7AEF\u9519\u8BEF */\r\n private handleError(clientId: string, error: Error): void {\r\n const client = this.clients.get(clientId);\r\n if (!client?.boundTo) return;\r\n const partner = this.clients.get(client.boundTo);\r\n if (partner) {\r\n this.send(partner.ws, { type: \"error\", clientId: client.boundTo, targetId: clientId, message: \"500\" });\r\n }\r\n }\r\n\r\n /** \u53D1\u9001\u5FC3\u8DF3\u7ED9\u6240\u6709\u5BA2\u6237\u7AEF */\r\n private sendHeartbeats(): void {\r\n if (this.clients.size === 0) return;\r\n console.log(`[WS \u670D\u52A1\u5668] \u53D1\u9001\u5FC3\u8DF3\u7ED9 ${this.clients.size} \u4E2A\u5BA2\u6237\u7AEF`);\r\n for (const [clientId, client] of this.clients.entries()) {\r\n this.send(client.ws, { type: \"heartbeat\", clientId, targetId: client.boundTo || \"\", message: \"200\" });\r\n }\r\n }\r\n\r\n /** \u53D1\u9001\u6D88\u606F\u5230 WebSocket */\r\n private send(ws: WebSocket, msg: DGLabMessage): void {\r\n if (ws.readyState === WebSocket.OPEN) {\r\n ws.send(JSON.stringify(msg));\r\n }\r\n }\r\n\r\n /** \u89E3\u6790\u5F3A\u5EA6\u6D88\u606F */\r\n private parseStrengthMessage(message: string): { strengthA: number; strengthB: number; limitA: number; limitB: number } | null {\r\n const match = message.match(/^strength-(\\d+)\\+(\\d+)\\+(\\d+)\\+(\\d+)$/);\r\n if (!match) return null;\r\n return { strengthA: parseInt(match[1]!, 10), strengthB: parseInt(match[2]!, 10), limitA: parseInt(match[3]!, 10), limitB: parseInt(match[4]!, 10) };\r\n }\r\n\r\n // ============ MCP \u5DE5\u5177\u516C\u5171 API ============\r\n\r\n /** \u521B\u5EFA\u63A7\u5236\u5668\uFF08\u7528\u4E8E dg_connect\uFF09 */\r\n createController(): string {\r\n const clientId = uuidv4();\r\n const mockWs = this.createMockWebSocket(clientId);\r\n const clientInfo: ClientInfo = { id: clientId, ws: mockWs as unknown as WebSocket, type: \"controller\", boundTo: null, lastActive: Date.now() };\r\n this.clients.set(clientId, clientInfo);\r\n console.log(`[WS \u670D\u52A1\u5668] \u521B\u5EFA\u63A7\u5236\u5668: ${clientId}`);\r\n return clientId;\r\n }\r\n\r\n /** \u521B\u5EFA\u5185\u90E8\u63A7\u5236\u5668\u7684\u6A21\u62DF WebSocket */\r\n private createMockWebSocket(clientId: string): object {\r\n return {\r\n readyState: WebSocket.OPEN,\r\n send: (data: string) => { console.log(`[WS \u670D\u52A1\u5668] \u53D1\u9001\u7ED9\u63A7\u5236\u5668 ${clientId}: ${data}`); },\r\n close: () => {},\r\n };\r\n }\r\n\r\n /** \u68C0\u67E5\u63A7\u5236\u5668\u662F\u5426\u5DF2\u7ED1\u5B9A APP */\r\n isControllerBound(controllerId: string): boolean { return this.relations.has(controllerId); }\r\n\r\n /** \u83B7\u53D6\u7ED1\u5B9A\u5230\u63A7\u5236\u5668\u7684 APP clientId */\r\n getBoundAppId(controllerId: string): string | null { return this.relations.get(controllerId) || null; }\r\n\r\n /** \u83B7\u53D6\u63A7\u5236\u5668\u4FE1\u606F */\r\n getController(controllerId: string): ClientInfo | null {\r\n const client = this.clients.get(controllerId);\r\n return client?.type === \"controller\" ? client : null;\r\n }\r\n\r\n /** \u5217\u51FA\u6240\u6709\u63A7\u5236\u5668 */\r\n listControllers(): Array<{ id: string; boundTo: string | null; lastActive: number }> {\r\n const result: Array<{ id: string; boundTo: string | null; lastActive: number }> = [];\r\n for (const client of this.clients.values()) {\r\n if (client.type === \"controller\") {\r\n result.push({ id: client.id, boundTo: client.boundTo, lastActive: client.lastActive });\r\n }\r\n }\r\n return result;\r\n }\r\n\r\n /** \u79FB\u9664\u63A7\u5236\u5668 */\r\n removeController(controllerId: string): boolean {\r\n const client = this.clients.get(controllerId);\r\n if (!client || client.type !== \"controller\") return false;\r\n this.handleClose(controllerId);\r\n return true;\r\n }\r\n\r\n /**\r\n * \u65AD\u5F00\u6307\u5B9A\u63A7\u5236\u5668\u7684\u8FDE\u63A5\r\n * @param controllerId - \u63A7\u5236\u5668 ID\r\n * @returns \u662F\u5426\u6210\u529F\u65AD\u5F00\r\n */\r\n disconnectController(controllerId: string): boolean {\r\n const client = this.clients.get(controllerId);\r\n if (!client) {\r\n return false;\r\n }\r\n\r\n // \u6E05\u7406\u8BE5\u63A7\u5236\u5668\u7684\u6CE2\u5F62\u5B9A\u65F6\u5668\r\n for (const [key, timer] of this.waveformTimers.entries()) {\r\n if (key.startsWith(controllerId + \"-\")) {\r\n clearInterval(timer.timerId);\r\n this.waveformTimers.delete(key);\r\n }\r\n }\r\n\r\n // \u6E05\u7406\u8BE5\u63A7\u5236\u5668\u7684\u6301\u7EED\u64AD\u653E\r\n for (const channel of [\"A\", \"B\"] as const) {\r\n const key = `${controllerId}-${channel}`;\r\n const state = this.continuousPlaybacks.get(key);\r\n if (state) {\r\n if (state.timerId) {\r\n clearInterval(state.timerId);\r\n }\r\n this.continuousPlaybacks.delete(key);\r\n console.log(`[WS \u670D\u52A1\u5668] \u5DF2\u505C\u6B62\u6301\u7EED\u64AD\u653E: ${key}`);\r\n }\r\n }\r\n\r\n // \u5982\u679C\u6709\u7ED1\u5B9A\u7684 APP\uFF0C\u5148\u89E3\u7ED1\u5E76\u901A\u77E5\r\n if (client.boundTo) {\r\n const appClient = this.clients.get(client.boundTo);\r\n if (appClient && appClient.ws.readyState === WebSocket.OPEN) {\r\n // \u901A\u77E5 APP \u63A7\u5236\u5668\u5DF2\u65AD\u5F00\r\n this.send(appClient.ws, {\r\n type: \"break\",\r\n clientId: controllerId,\r\n targetId: client.boundTo,\r\n message: \"209\"\r\n });\r\n }\r\n \r\n // \u6E05\u7406\u7ED1\u5B9A\u5173\u7CFB\r\n this.relations.delete(controllerId);\r\n if (appClient) {\r\n appClient.boundTo = null;\r\n }\r\n \r\n // \u89E6\u53D1\u7ED1\u5B9A\u53D8\u5316\u56DE\u8C03\r\n if (this.options.onBindChange) {\r\n this.options.onBindChange(controllerId, null);\r\n }\r\n }\r\n\r\n // \u5173\u95ED WebSocket \u8FDE\u63A5\uFF08\u5982\u679C\u662F\u771F\u5B9E\u8FDE\u63A5\uFF09\r\n if (client.ws.readyState === WebSocket.OPEN) {\r\n client.ws.close(1000, \"Disconnected by user\");\r\n }\r\n\r\n // \u4ECE Map \u4E2D\u79FB\u9664\r\n this.clients.delete(controllerId);\r\n\r\n // \u89E6\u53D1\u63A7\u5236\u5668\u65AD\u5F00\u56DE\u8C03\r\n if (this.options.onControllerDisconnect) {\r\n this.options.onControllerDisconnect(controllerId);\r\n }\r\n\r\n console.log(`[WS \u670D\u52A1\u5668] \u63A7\u5236\u5668\u5DF2\u65AD\u5F00: ${controllerId}`);\r\n return true;\r\n }\r\n\r\n /** \u53D1\u9001\u5F3A\u5EA6\u547D\u4EE4\u5230 APP */\r\n sendStrength(controllerId: string, channel: \"A\" | \"B\", mode: \"increase\" | \"decrease\" | \"set\", value: number): boolean {\r\n const appId = this.relations.get(controllerId);\r\n if (!appId) return false;\r\n const appClient = this.clients.get(appId);\r\n if (!appClient) return false;\r\n const channelNum = channel === \"A\" ? 1 : 2;\r\n const modeNum = mode === \"decrease\" ? 0 : mode === \"increase\" ? 1 : 2;\r\n const message = `strength-${channelNum}+${modeNum}+${value}`;\r\n this.send(appClient.ws, { type: \"msg\", clientId: controllerId, targetId: appId, message });\r\n return true;\r\n }\r\n\r\n /** \u53D1\u9001\u6CE2\u5F62\u5230 APP */\r\n sendWaveform(controllerId: string, channel: \"A\" | \"B\", waveforms: string[]): boolean {\r\n const appId = this.relations.get(controllerId);\r\n if (!appId) return false;\r\n const appClient = this.clients.get(appId);\r\n if (!appClient) return false;\r\n const message = `pulse-${channel}:${JSON.stringify(waveforms)}`;\r\n this.send(appClient.ws, { type: \"msg\", clientId: controllerId, targetId: appId, message });\r\n return true;\r\n }\r\n\r\n /** \u6E05\u7A7A\u6CE2\u5F62\u961F\u5217 */\r\n clearWaveform(controllerId: string, channel: \"A\" | \"B\"): boolean {\r\n const appId = this.relations.get(controllerId);\r\n if (!appId) return false;\r\n const appClient = this.clients.get(appId);\r\n if (!appClient) return false;\r\n const channelNum = channel === \"A\" ? 1 : 2;\r\n this.send(appClient.ws, { type: \"msg\", clientId: controllerId, targetId: appId, message: `clear-${channelNum}` });\r\n return true;\r\n }\r\n\r\n // ============ \u6301\u7EED\u64AD\u653E API ============\r\n\r\n /**\r\n * \u542F\u52A8\u6301\u7EED\u64AD\u653E\r\n * \r\n * \u5FAA\u73AF\u53D1\u9001\u6CE2\u5F62\u6570\u636E\u5230\u6307\u5B9A\u901A\u9053\uFF0C\u76F4\u5230\u624B\u52A8\u505C\u6B62\u3002\r\n * \u6BCF\u6B21\u53D1\u9001\u4E00\u6279\u6CE2\u5F62\uFF0C\u6309\u95F4\u9694\u5FAA\u73AF\u53D1\u9001\u3002\r\n * \r\n * @param controllerId - \u63A7\u5236\u5668 ID\r\n * @param channel - \u76EE\u6807\u901A\u9053 A \u6216 B\r\n * @param waveforms - \u8981\u5FAA\u73AF\u64AD\u653E\u7684\u6CE2\u5F62\u6570\u636E\r\n * @param interval - \u53D1\u9001\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 100ms\r\n * @param batchSize - \u6BCF\u6B21\u53D1\u9001\u7684\u6CE2\u5F62\u6570\u91CF\uFF0C\u9ED8\u8BA4 5\r\n * @returns \u662F\u5426\u6210\u529F\u542F\u52A8\r\n */\r\n startContinuousPlayback(\r\n controllerId: string,\r\n channel: \"A\" | \"B\",\r\n waveforms: string[],\r\n interval: number = 100,\r\n batchSize: number = 5\r\n ): boolean {\r\n // \u68C0\u67E5\u63A7\u5236\u5668\u662F\u5426\u5DF2\u7ED1\u5B9A APP\r\n if (!this.isControllerBound(controllerId)) {\r\n console.log(`[WS \u670D\u52A1\u5668] \u6301\u7EED\u64AD\u653E\u5931\u8D25: \u63A7\u5236\u5668 ${controllerId} \u672A\u7ED1\u5B9A APP`);\r\n return false;\r\n }\r\n\r\n // \u68C0\u67E5\u6CE2\u5F62\u6570\u636E\u662F\u5426\u6709\u6548\r\n if (!waveforms || waveforms.length === 0) {\r\n console.log(`[WS \u670D\u52A1\u5668] \u6301\u7EED\u64AD\u653E\u5931\u8D25: \u6CE2\u5F62\u6570\u636E\u4E3A\u7A7A`);\r\n return false;\r\n }\r\n\r\n const key = `${controllerId}-${channel}`;\r\n\r\n // \u5982\u679C\u5DF2\u6709\u6301\u7EED\u64AD\u653E\uFF0C\u5148\u505C\u6B62\r\n if (this.continuousPlaybacks.has(key)) {\r\n this.stopContinuousPlayback(controllerId, channel);\r\n }\r\n\r\n // \u521B\u5EFA\u6301\u7EED\u64AD\u653E\u72B6\u6001\r\n const state: ContinuousPlaybackState = {\r\n controllerId,\r\n channel,\r\n waveforms,\r\n currentIndex: 0,\r\n interval,\r\n batchSize,\r\n timerId: null,\r\n active: true,\r\n };\r\n\r\n // \u542F\u52A8\u5B9A\u65F6\u5668\u5FAA\u73AF\u53D1\u9001\u6CE2\u5F62\r\n state.timerId = setInterval(() => {\r\n if (!state.active) {\r\n return;\r\n }\r\n\r\n // \u83B7\u53D6\u5F53\u524D\u6279\u6B21\u7684\u6CE2\u5F62\r\n const batch: string[] = [];\r\n for (let i = 0; i < state.batchSize; i++) {\r\n batch.push(state.waveforms[state.currentIndex]!);\r\n state.currentIndex = (state.currentIndex + 1) % state.waveforms.length;\r\n }\r\n\r\n // \u53D1\u9001\u6CE2\u5F62\r\n const success = this.sendWaveform(controllerId, channel, batch);\r\n if (!success) {\r\n // \u53D1\u9001\u5931\u8D25\uFF0C\u505C\u6B62\u64AD\u653E\r\n console.log(`[WS \u670D\u52A1\u5668] \u6301\u7EED\u64AD\u653E\u53D1\u9001\u5931\u8D25\uFF0C\u505C\u6B62\u64AD\u653E: ${key}`);\r\n this.stopContinuousPlayback(controllerId, channel);\r\n }\r\n }, interval);\r\n\r\n this.continuousPlaybacks.set(key, state);\r\n console.log(`[WS \u670D\u52A1\u5668] \u5DF2\u542F\u52A8\u6301\u7EED\u64AD\u653E: ${key}\uFF0C\u6CE2\u5F62\u6570: ${waveforms.length}\uFF0C\u95F4\u9694: ${interval}ms`);\r\n return true;\r\n }\r\n\r\n /**\r\n * \u505C\u6B62\u6301\u7EED\u64AD\u653E\r\n * \r\n * \u505C\u6B62\u6307\u5B9A\u901A\u9053\u7684\u6301\u7EED\u64AD\u653E\u5E76\u6E05\u7A7A\u6CE2\u5F62\u961F\u5217\u3002\r\n * \r\n * @param controllerId - \u63A7\u5236\u5668 ID\r\n * @param channel - \u76EE\u6807\u901A\u9053 A \u6216 B\r\n * @returns \u662F\u5426\u6210\u529F\u505C\u6B62\r\n */\r\n stopContinuousPlayback(controllerId: string, channel: \"A\" | \"B\"): boolean {\r\n const key = `${controllerId}-${channel}`;\r\n const state = this.continuousPlaybacks.get(key);\r\n\r\n if (!state) {\r\n console.log(`[WS \u670D\u52A1\u5668] \u505C\u6B62\u6301\u7EED\u64AD\u653E\u5931\u8D25: ${key} \u4E0D\u5B58\u5728`);\r\n return false;\r\n }\r\n\r\n // \u505C\u6B62\u5B9A\u65F6\u5668\r\n state.active = false;\r\n if (state.timerId) {\r\n clearInterval(state.timerId);\r\n state.timerId = null;\r\n }\r\n\r\n // \u6E05\u7A7A\u6CE2\u5F62\u961F\u5217\r\n this.clearWaveform(controllerId, channel);\r\n\r\n // \u79FB\u9664\u72B6\u6001\r\n this.continuousPlaybacks.delete(key);\r\n console.log(`[WS \u670D\u52A1\u5668] \u5DF2\u505C\u6B62\u6301\u7EED\u64AD\u653E: ${key}`);\r\n return true;\r\n }\r\n\r\n /**\r\n * \u68C0\u67E5\u662F\u5426\u6B63\u5728\u6301\u7EED\u64AD\u653E\r\n * \r\n * @param controllerId - \u63A7\u5236\u5668 ID\r\n * @param channel - \u76EE\u6807\u901A\u9053 A \u6216 B\r\n * @returns \u662F\u5426\u6B63\u5728\u6301\u7EED\u64AD\u653E\r\n */\r\n isContinuousPlaying(controllerId: string, channel: \"A\" | \"B\"): boolean {\r\n const key = `${controllerId}-${channel}`;\r\n const state = this.continuousPlaybacks.get(key);\r\n return state?.active ?? false;\r\n }\r\n\r\n /**\r\n * \u83B7\u53D6\u6301\u7EED\u64AD\u653E\u72B6\u6001\r\n * \r\n * @param controllerId - \u63A7\u5236\u5668 ID\r\n * @param channel - \u76EE\u6807\u901A\u9053 A \u6216 B\r\n * @returns \u6301\u7EED\u64AD\u653E\u72B6\u6001\u6216 null\r\n */\r\n getContinuousPlaybackState(controllerId: string, channel: \"A\" | \"B\"): {\r\n waveformCount: number;\r\n interval: number;\r\n batchSize: number;\r\n active: boolean;\r\n } | null {\r\n const key = `${controllerId}-${channel}`;\r\n const state = this.continuousPlaybacks.get(key);\r\n if (!state) return null;\r\n return {\r\n waveformCount: state.waveforms.length,\r\n interval: state.interval,\r\n batchSize: state.batchSize,\r\n active: state.active,\r\n };\r\n }\r\n\r\n /** \u83B7\u53D6 APP \u626B\u63CF\u7684\u4E8C\u7EF4\u7801 URL */\r\n getQRCodeUrl(controllerId: string, host: string): string {\r\n const wsUrl = `ws://${host}:${this.attachedPort}/${controllerId}`;\r\n return `https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#${wsUrl}`;\r\n }\r\n\r\n /** \u83B7\u53D6 APP \u8FDE\u63A5\u7684 WebSocket URL */\r\n getWSUrl(controllerId: string, host: string): string {\r\n return `ws://${host}:${this.attachedPort}/${controllerId}`;\r\n }\r\n\r\n /** \u83B7\u53D6\u670D\u52A1\u5668\u7AEF\u53E3 */\r\n getPort(): number { return this.attachedPort; }\r\n\r\n /** \u83B7\u53D6\u5BA2\u6237\u7AEF\u6570\u91CF */\r\n getClientCount(): number { return this.clients.size; }\r\n\r\n /** \u83B7\u53D6\u7ED1\u5B9A\u5173\u7CFB\u6570\u91CF */\r\n getRelationCount(): number { return this.relations.size; }\r\n}\r\n\r\n// mapDGLabErrorCode \u5DF2\u79FB\u81F3 ws-bridge.ts\uFF0C\u4ECE\u90A3\u91CC\u5BFC\u5165\u4F7F\u7528\r\nexport { mapDGLabErrorCode } from \"./ws-bridge\";\r\n", "/**\r\n * @fileoverview WebSocket \u6865\u63A5\u5668\r\n * @description \u8FDE\u63A5\u5230 DG-LAB WebSocket \u540E\u7AEF\uFF08\u5B98\u65B9\u6216\u81EA\u6258\u7BA1\uFF09\r\n * \u534F\u8BAE\u57FA\u4E8E temp_dg_plugin/app.js \u548C temp_dg_lab/socket/README.md\r\n * \r\n * \u65E0\u91CD\u8FDE\u673A\u5236 - \u5982\u679C\u8FDE\u63A5\u65AD\u5F00\uFF0C\u4F1A\u8BDD\u5C06\u5931\u6548\r\n */\r\n\r\nimport WebSocket from \"ws\";\r\nimport type { SessionManager, DeviceSession } from \"./session-manager\";\r\n\r\n/** DG-LAB WebSocket \u6D88\u606F\u7C7B\u578B */\r\nexport type DGLabMessageType = \"bind\" | \"msg\" | \"heartbeat\" | \"break\" | \"error\";\r\n\r\n/** DG-LAB WebSocket \u6D88\u606F */\r\nexport interface DGLabMessage {\r\n type: DGLabMessageType | string;\r\n clientId: string;\r\n targetId: string;\r\n message: string;\r\n channel?: string;\r\n}\r\n\r\n/** \u5F3A\u5EA6\u6A21\u5F0F: 0=\u51CF\u5C11, 1=\u589E\u52A0, 2=\u8BBE\u7F6E */\r\nexport type StrengthMode = \"increase\" | \"decrease\" | \"set\";\r\n\r\nconst STRENGTH_MODE_MAP: Record<StrengthMode, number> = {\r\n decrease: 0,\r\n increase: 1,\r\n set: 2,\r\n};\r\n\r\n/** WebSocket \u6865\u63A5\u5668\u9009\u9879 */\r\nexport interface WSBridgeOptions {\r\n wsBackendUrl: string;\r\n heartbeatInterval?: number;\r\n onConnectionChange?: (deviceId: string, connected: boolean) => void;\r\n onStrengthUpdate?: (deviceId: string, a: number, b: number, limitA: number, limitB: number) => void;\r\n onFeedback?: (deviceId: string, index: number) => void;\r\n onError?: (deviceId: string, error: string) => void;\r\n}\r\n\r\n/**\r\n * WebSocket \u6865\u63A5\u5668\u7C7B\r\n * @description \u7BA1\u7406\u4E0E DG-LAB WebSocket \u540E\u7AEF\u7684\u8FDE\u63A5\r\n */\r\nexport class WSBridge {\r\n private options: Required<WSBridgeOptions>;\r\n private sessionManager: SessionManager;\r\n private heartbeatTimers: Map<string, ReturnType<typeof setInterval>> = new Map();\r\n\r\n constructor(sessionManager: SessionManager, options: WSBridgeOptions) {\r\n this.sessionManager = sessionManager;\r\n this.options = {\r\n heartbeatInterval: 30000,\r\n onConnectionChange: () => {},\r\n onStrengthUpdate: () => {},\r\n onFeedback: () => {},\r\n onError: () => {},\r\n ...options,\r\n };\r\n }\r\n\r\n /**\r\n * \u5C06\u4F1A\u8BDD\u8FDE\u63A5\u5230 DG-LAB WebSocket \u540E\u7AEF\r\n * @param session - \u8BBE\u5907\u4F1A\u8BDD\r\n * @returns WS \u670D\u52A1\u5668\u5206\u914D\u7684 clientId\r\n */\r\n async connect(session: DeviceSession): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n const ws = new WebSocket(this.options.wsBackendUrl);\r\n let resolved = false;\r\n\r\n ws.on(\"open\", () => {\r\n console.log(`[WS] \u5DF2\u8FDE\u63A5\u8BBE\u5907 ${session.deviceId}`);\r\n this.sessionManager.updateConnectionState(session.deviceId, { ws, connected: true });\r\n this.options.onConnectionChange(session.deviceId, true);\r\n });\r\n\r\n ws.on(\"message\", (data) => {\r\n const msg = this.parseMessage(data.toString());\r\n if (!msg) return;\r\n\r\n // \u7B2C\u4E00\u6761\u6D88\u606F\u5E94\u8BE5\u662F\u5E26\u6709\u6211\u4EEC clientId \u7684 bind\r\n if (!resolved && msg.type === \"bind\" && msg.clientId && msg.message === \"targetId\") {\r\n this.sessionManager.updateConnectionState(session.deviceId, { clientId: msg.clientId });\r\n this.startHeartbeat(session.deviceId);\r\n resolved = true;\r\n resolve(msg.clientId);\r\n return;\r\n }\r\n\r\n this.handleMessage(session.deviceId, msg);\r\n });\r\n\r\n ws.on(\"close\", () => {\r\n console.log(`[WS] \u65AD\u5F00: ${session.deviceId}`);\r\n this.stopHeartbeat(session.deviceId);\r\n this.sessionManager.updateConnectionState(session.deviceId, { ws: null, connected: false, boundToApp: false });\r\n this.options.onConnectionChange(session.deviceId, false);\r\n });\r\n\r\n ws.on(\"error\", (err) => {\r\n console.error(`[WS] \u9519\u8BEF ${session.deviceId}:`, err.message);\r\n this.options.onError(session.deviceId, err.message);\r\n if (!resolved) { resolved = true; reject(err); }\r\n });\r\n\r\n // \u521D\u59CB\u8FDE\u63A5\u8D85\u65F6\r\n setTimeout(() => {\r\n if (!resolved) {\r\n resolved = true;\r\n ws.close();\r\n reject(new Error(\"\u8FDE\u63A5\u8D85\u65F6: \u672A\u6536\u5230 bind \u6D88\u606F\"));\r\n }\r\n }, 10000);\r\n });\r\n }\r\n\r\n /**\r\n * \u65AD\u5F00\u8BBE\u5907\u8FDE\u63A5\r\n * @param deviceId - \u8BBE\u5907 ID\r\n */\r\n disconnect(deviceId: string): void {\r\n const session = this.sessionManager.getSession(deviceId);\r\n if (session?.ws) {\r\n this.stopHeartbeat(deviceId);\r\n session.ws.close();\r\n this.sessionManager.updateConnectionState(deviceId, { ws: null, connected: false, boundToApp: false });\r\n }\r\n }\r\n\r\n /**\r\n * \u53D1\u9001\u5F3A\u5EA6\u63A7\u5236\u547D\u4EE4\r\n * \u534F\u8BAE: strength-channel+mode+value\r\n * @param deviceId - \u8BBE\u5907 ID\r\n * @param channel - \u901A\u9053 (A/B)\r\n * @param mode - \u6A21\u5F0F\r\n * @param value - \u503C\r\n * @returns \u662F\u5426\u6210\u529F\r\n */\r\n sendStrength(deviceId: string, channel: \"A\" | \"B\", mode: StrengthMode, value: number): boolean {\r\n const session = this.sessionManager.getSession(deviceId);\r\n if (!session?.ws || !session.connected || !session.targetId) return false;\r\n\r\n const channelNum = channel === \"A\" ? 1 : 2;\r\n const modeNum = STRENGTH_MODE_MAP[mode];\r\n const message = `strength-${channelNum}+${modeNum}+${value}`;\r\n\r\n return this.sendToApp(session, message);\r\n }\r\n\r\n /**\r\n * \u53D1\u9001\u6CE2\u5F62\u6570\u636E\r\n * \u534F\u8BAE: pulse-channel:[\"hex1\",\"hex2\",...]\r\n * @param deviceId - \u8BBE\u5907 ID\r\n * @param channel - \u901A\u9053 (A/B)\r\n * @param waveforms - \u6CE2\u5F62\u6570\u7EC4\r\n * @returns \u662F\u5426\u6210\u529F\r\n */\r\n sendWaveform(deviceId: string, channel: \"A\" | \"B\", waveforms: string[]): boolean {\r\n const session = this.sessionManager.getSession(deviceId);\r\n if (!session?.ws || !session.connected || !session.targetId) return false;\r\n\r\n const message = `pulse-${channel}:${JSON.stringify(waveforms)}`;\r\n return this.sendToApp(session, message);\r\n }\r\n\r\n /**\r\n * \u6E05\u7A7A\u6CE2\u5F62\u961F\u5217\r\n * \u534F\u8BAE: clear-channel\r\n * @param deviceId - \u8BBE\u5907 ID\r\n * @param channel - \u901A\u9053 (A/B)\r\n * @returns \u662F\u5426\u6210\u529F\r\n */\r\n clearWaveform(deviceId: string, channel: \"A\" | \"B\"): boolean {\r\n const session = this.sessionManager.getSession(deviceId);\r\n if (!session?.ws || !session.connected || !session.targetId) return false;\r\n\r\n const message = `clear-${channel === \"A\" ? 1 : 2}`;\r\n return this.sendToApp(session, message);\r\n }\r\n\r\n /** \u901A\u8FC7 WS \u670D\u52A1\u5668\u53D1\u9001\u6D88\u606F\u5230 APP */\r\n private sendToApp(session: DeviceSession, message: string): boolean {\r\n if (!session.ws || !session.clientId || !session.targetId) return false;\r\n\r\n const payload: DGLabMessage = {\r\n type: \"msg\",\r\n clientId: session.clientId,\r\n targetId: session.targetId,\r\n message,\r\n };\r\n\r\n try {\r\n session.ws.send(JSON.stringify(payload));\r\n this.sessionManager.touchSession(session.deviceId);\r\n return true;\r\n } catch (err) {\r\n console.error(`[WS] \u53D1\u9001\u5931\u8D25 ${session.deviceId}:`, err);\r\n return false;\r\n }\r\n }\r\n\r\n private parseMessage(data: string): DGLabMessage | null {\r\n try { return JSON.parse(data) as DGLabMessage; }\r\n catch { return null; }\r\n }\r\n\r\n private handleMessage(deviceId: string, msg: DGLabMessage): void {\r\n switch (msg.type) {\r\n case \"bind\": this.handleBind(deviceId, msg); break;\r\n case \"msg\": this.handleMsg(deviceId, msg); break;\r\n case \"heartbeat\": this.sessionManager.touchSession(deviceId); break;\r\n case \"break\": this.handleBreak(deviceId, msg); break;\r\n case \"error\": this.handleError(deviceId, msg); break;\r\n }\r\n }\r\n\r\n private handleBind(deviceId: string, msg: DGLabMessage): void {\r\n if (msg.message === \"200\") {\r\n console.log(`[WS] \u7ED1\u5B9A\u6210\u529F ${deviceId}: targetId=${msg.targetId}`);\r\n this.sessionManager.updateConnectionState(deviceId, { targetId: msg.targetId, boundToApp: true });\r\n } else {\r\n console.error(`[WS] \u7ED1\u5B9A\u5931\u8D25 ${deviceId}: ${msg.message}`);\r\n this.options.onError(deviceId, `\u7ED1\u5B9A\u5931\u8D25: ${mapDGLabErrorCode(parseInt(msg.message))}`);\r\n }\r\n }\r\n\r\n private handleMsg(deviceId: string, msg: DGLabMessage): void {\r\n const { message } = msg;\r\n\r\n if (message.startsWith(\"strength-\")) {\r\n const parsed = parseStrengthMessage(message);\r\n if (parsed) {\r\n this.sessionManager.updateStrength(deviceId, parsed.strengthA, parsed.strengthB, parsed.limitA, parsed.limitB);\r\n this.options.onStrengthUpdate(deviceId, parsed.strengthA, parsed.strengthB, parsed.limitA, parsed.limitB);\r\n }\r\n return;\r\n }\r\n\r\n if (message.startsWith(\"feedback-\")) {\r\n const index = parseInt(message.substring(9));\r\n if (!isNaN(index)) this.options.onFeedback(deviceId, index);\r\n return;\r\n }\r\n }\r\n\r\n private handleBreak(deviceId: string, msg: DGLabMessage): void {\r\n console.log(`[WS] \u65AD\u5F00 ${deviceId}: ${msg.message}`);\r\n this.sessionManager.updateConnectionState(deviceId, { boundToApp: false, targetId: null });\r\n this.options.onError(deviceId, `\u8FDE\u63A5\u65AD\u5F00: ${mapDGLabErrorCode(parseInt(msg.message))}`);\r\n }\r\n\r\n private handleError(deviceId: string, msg: DGLabMessage): void {\r\n console.error(`[WS] \u9519\u8BEF ${deviceId}: ${msg.message}`);\r\n this.options.onError(deviceId, msg.message);\r\n }\r\n\r\n private startHeartbeat(deviceId: string): void {\r\n const timer = setInterval(() => {\r\n const session = this.sessionManager.getSession(deviceId);\r\n if (session?.ws && session.connected && session.clientId) {\r\n try {\r\n const heartbeat: DGLabMessage = { type: \"heartbeat\", clientId: session.clientId, targetId: session.targetId || \"\", message: \"200\" };\r\n session.ws.send(JSON.stringify(heartbeat));\r\n } catch { /* \u8FDE\u63A5\u4E22\u5931\uFF0C\u5C06\u7531 close \u4E8B\u4EF6\u5904\u7406 */ }\r\n } else {\r\n this.stopHeartbeat(deviceId);\r\n }\r\n }, this.options.heartbeatInterval);\r\n this.heartbeatTimers.set(deviceId, timer);\r\n }\r\n\r\n private stopHeartbeat(deviceId: string): void {\r\n const timer = this.heartbeatTimers.get(deviceId);\r\n if (timer) { clearInterval(timer); this.heartbeatTimers.delete(deviceId); }\r\n }\r\n\r\n /** \u505C\u6B62\u6240\u6709\u5FC3\u8DF3\uFF08\u7528\u4E8E\u5173\u95ED\uFF09 */\r\n stopAll(): void {\r\n for (const timer of this.heartbeatTimers.values()) clearInterval(timer);\r\n this.heartbeatTimers.clear();\r\n }\r\n}\r\n\r\n/**\r\n * \u89E3\u6790\u5F3A\u5EA6\u6D88\u606F: strength-A+B+limitA+limitB\r\n * @param message - \u6D88\u606F\u5B57\u7B26\u4E32\r\n * @returns \u89E3\u6790\u7ED3\u679C\u6216 null\r\n */\r\nexport function parseStrengthMessage(message: string): { strengthA: number; strengthB: number; limitA: number; limitB: number } | null {\r\n const match = message.match(/^strength-(\\d+)\\+(\\d+)\\+(\\d+)\\+(\\d+)$/);\r\n if (!match) return null;\r\n return { strengthA: parseInt(match[1]!, 10), strengthB: parseInt(match[2]!, 10), limitA: parseInt(match[3]!, 10), limitB: parseInt(match[4]!, 10) };\r\n}\r\n\r\n/**\r\n * \u6620\u5C04 DG-LAB \u9519\u8BEF\u7801\u5230\u6D88\u606F\r\n * @param code - \u9519\u8BEF\u7801\r\n * @returns \u9519\u8BEF\u6D88\u606F\r\n */\r\nexport function mapDGLabErrorCode(code: number): string {\r\n const errors: Record<number, string> = {\r\n 200: \"\u6210\u529F\", 209: \"\u5BF9\u65B9\u5DF2\u65AD\u5F00\u8FDE\u63A5\", 210: \"\u4E8C\u7EF4\u7801\u4E2D\u6CA1\u6709\u6709\u6548\u7684clientID\", 211: \"\u670D\u52A1\u5668\u672A\u4E0B\u53D1APP ID\",\r\n 400: \"\u6B64ID\u5DF2\u88AB\u5176\u4ED6\u5BA2\u6237\u7AEF\u7ED1\u5B9A\", 401: \"\u76EE\u6807\u5BA2\u6237\u7AEF\u4E0D\u5B58\u5728\", 402: \"\u53CC\u65B9\u672A\u5EFA\u7ACB\u7ED1\u5B9A\u5173\u7CFB\",\r\n 403: \"\u6D88\u606F\u4E0D\u662F\u6709\u6548\u7684JSON\", 404: \"\u6536\u4FE1\u4EBA\u79BB\u7EBF\", 405: \"\u6D88\u606F\u957F\u5EA6\u8D85\u8FC71950\u5B57\u7B26\", 500: \"\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF\",\r\n };\r\n return errors[code] ?? `\u672A\u77E5\u9519\u8BEF: ${code}`;\r\n}\r\n", "/**\r\n * @fileoverview \u8BBE\u5907\u7BA1\u7406\u5DE5\u5177\u96C6\r\n * \r\n * \u63D0\u4F9B DG-LAB \u8BBE\u5907\u7684\u8FDE\u63A5\u3001\u67E5\u8BE2\u3001\u522B\u540D\u7BA1\u7406\u548C\u65AD\u5F00\u7B49\u6838\u5FC3\u529F\u80FD\u3002\r\n * \u8FD9\u4E9B\u5DE5\u5177\u662F AI \u4E0E DG-LAB \u8BBE\u5907\u4EA4\u4E92\u7684\u4E3B\u8981\u5165\u53E3\uFF0C\u8D1F\u8D23\uFF1A\r\n * - \u521B\u5EFA\u65B0\u7684\u8BBE\u5907\u8FDE\u63A5\u5E76\u751F\u6210\u4E8C\u7EF4\u7801\u4F9B APP \u626B\u63CF\r\n * - \u67E5\u8BE2\u548C\u7BA1\u7406\u5DF2\u8FDE\u63A5\u7684\u8BBE\u5907\u5217\u8868\r\n * - \u4E3A\u8BBE\u5907\u8BBE\u7F6E\u522B\u540D\u4EE5\u4FBF\u8BC6\u522B\u548C\u7BA1\u7406\r\n * - \u65AD\u5F00\u5E76\u6E05\u7406\u8BBE\u5907\u8FDE\u63A5\r\n * \r\n * \u5178\u578B\u4F7F\u7528\u6D41\u7A0B\uFF1A\r\n * 1. \u8C03\u7528 dg_connect \u521B\u5EFA\u8FDE\u63A5\uFF0C\u83B7\u53D6\u4E8C\u7EF4\u7801\r\n * 2. \u7528\u6237\u4F7F\u7528 DG-LAB APP \u626B\u63CF\u4E8C\u7EF4\u7801\r\n * 3. \u901A\u8FC7 dg_list_devices \u786E\u8BA4\u8BBE\u5907\u5DF2\u7ED1\u5B9A\r\n * 4. \u4F7F\u7528 control-tools \u4E2D\u7684\u5DE5\u5177\u63A7\u5236\u8BBE\u5907\r\n */\r\n\r\nimport type { ToolManager } from \"../tool-manager\";\r\nimport { createToolResult, createToolError } from \"../tool-manager\";\r\nimport type { SessionManager } from \"../session-manager\";\r\nimport type { DGLabWSServer } from \"../ws-server\";\r\nimport { getEffectiveIP, getLocalIP } from \"../config\";\r\nimport { ConnectionError, ToolError, ErrorCode } from \"../errors\";\r\n\r\n/**\r\n * \u6CE8\u518C\u6240\u6709\u8BBE\u5907\u7BA1\u7406\u76F8\u5173\u7684 MCP \u5DE5\u5177\r\n * \r\n * \u5C06\u8BBE\u5907\u7BA1\u7406\u5DE5\u5177\u6CE8\u518C\u5230\u5DE5\u5177\u7BA1\u7406\u5668\u4E2D\uFF0C\u4F7F AI \u80FD\u591F\u901A\u8FC7 MCP \u534F\u8BAE\r\n * \u8C03\u7528\u8FD9\u4E9B\u5DE5\u5177\u6765\u7BA1\u7406 DG-LAB \u8BBE\u5907\u8FDE\u63A5\u3002\r\n * \r\n * @param toolManager - \u5DE5\u5177\u7BA1\u7406\u5668\u5B9E\u4F8B\uFF0C\u7528\u4E8E\u6CE8\u518C\u5DE5\u5177\r\n * @param sessionManager - \u4F1A\u8BDD\u7BA1\u7406\u5668\uFF0C\u7EF4\u62A4\u8BBE\u5907\u4F1A\u8BDD\u72B6\u6001\r\n * @param wsServer - WebSocket \u670D\u52A1\u5668\uFF0C\u5904\u7406\u4E0E APP \u7684\u5B9E\u65F6\u901A\u4FE1\r\n * @param publicIp - \u516C\u7F51 IP \u5730\u5740\uFF0C\u7528\u4E8E\u751F\u6210\u53EF\u4ECE\u5916\u7F51\u8BBF\u95EE\u7684\u4E8C\u7EF4\u7801 URL\u3002\r\n * \u5982\u679C\u672A\u63D0\u4F9B\uFF0C\u5C06\u81EA\u52A8\u68C0\u6D4B\u672C\u5730 IP\r\n */\r\nexport function registerDeviceTools(\r\n toolManager: ToolManager,\r\n sessionManager: SessionManager,\r\n wsServer: DGLabWSServer,\r\n publicIp?: string\r\n): void {\r\n // \u786E\u5B9A\u7528\u4E8E\u4E8C\u7EF4\u7801\u7684 IP \u5730\u5740\r\n // \u4F18\u5148\u4F7F\u7528\u663E\u5F0F\u914D\u7F6E\u7684\u516C\u7F51 IP\uFF0C\u5426\u5219\u56DE\u9000\u5230\u81EA\u52A8\u68C0\u6D4B\u7684\u672C\u5730 IP\r\n const localIp = getLocalIP();\r\n const ipAddress = publicIp || localIp;\r\n \r\n // \u8BB0\u5F55 IP \u914D\u7F6E\uFF0C\u4FBF\u4E8E\u8C03\u8BD5\u8FDE\u63A5\u95EE\u9898\r\n console.log(`[\u8BBE\u5907\u5DE5\u5177] PUBLIC_IP \u914D\u7F6E: \"${publicIp || '(\u672A\u8BBE\u7F6E)'}\"`);\r\n console.log(`[\u8BBE\u5907\u5DE5\u5177] \u672C\u5730 IP: ${localIp}`);\r\n console.log(`[\u8BBE\u5907\u5DE5\u5177] \u4F7F\u7528 IP: ${ipAddress}`);\r\n\r\n // ========== dg_connect ==========\r\n // \u521B\u5EFA\u65B0\u7684\u8BBE\u5907\u8FDE\u63A5\uFF0C\u8FD9\u662F\u4F7F\u7528 DG-LAB \u7684\u7B2C\u4E00\u6B65\r\n toolManager.registerTool(\r\n \"dg_connect\",\r\n `\u3010\u7B2C\u4E00\u6B65\u3011\u521B\u5EFADG-LAB\u8BBE\u5907\u8FDE\u63A5\u3002\u8FD4\u56DEdeviceId\uFF08\u540E\u7EED\u64CD\u4F5C\u5FC5\u9700\uFF09\u548CqrCodeUrl\uFF08\u4E8C\u7EF4\u7801\u94FE\u63A5\uFF09\u3002\r\n\u4F7F\u7528\u6D41\u7A0B\uFF1A1.\u8C03\u7528\u6B64\u5DE5\u5177\u83B7\u53D6\u4E8C\u7EF4\u7801 \u2192 2.\u751F\u6210\u4E8C\u7EF4\u7801\u540E\u8BA9\u7528\u6237\u7528DG-LAB APP\u626B\u7801 \u2192 3.\u7528\u6237\u8BF4\u626B\u4E86\u7801\u540E\u7528dg_get_status\u68C0\u67E5boundToApp\u662F\u5426\u4E3Atrue \u2192 4.boundToApp\u4E3Atrue\u540E\u624D\u80FD\u63A7\u5236\u8BBE\u5907\u3002\r\n\u6CE8\u610F\uFF1A\u6BCF\u6B21\u8C03\u7528\u4F1A\u521B\u5EFA\u65B0\u8FDE\u63A5\uFF0C\u5EFA\u8BAE\u5148\u7528dg_list_devices\u68C0\u67E5\u662F\u5426\u5DF2\u6709\u53EF\u7528\u8FDE\u63A5\u662F\u5C5E\u4E8E\u7528\u6237\u7684\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {},\r\n required: [],\r\n },\r\n async () => {\r\n try {\r\n // \u521B\u5EFA\u4F1A\u8BDD\uFF1A\u5728\u4F1A\u8BDD\u7BA1\u7406\u5668\u4E2D\u5206\u914D\u4E00\u4E2A\u65B0\u7684 deviceId\r\n const session = sessionManager.createSession();\r\n\r\n // \u521B\u5EFA\u63A7\u5236\u5668\uFF1A\u5728 WebSocket \u670D\u52A1\u5668\u4E2D\u6CE8\u518C\uFF0C\u83B7\u53D6 clientId\r\n // clientId \u7528\u4E8E APP \u626B\u7801\u540E\u5EFA\u7ACB\u8FDE\u63A5\r\n const clientId = wsServer.createController();\r\n\r\n // \u5173\u8054\u4F1A\u8BDD\u548C\u63A7\u5236\u5668\r\n sessionManager.updateConnectionState(session.deviceId, {\r\n clientId,\r\n connected: true,\r\n });\r\n\r\n // \u751F\u6210\u4E8C\u7EF4\u7801 URL\uFF0CAPP \u626B\u63CF\u540E\u4F1A\u8FDE\u63A5\u5230\u8FD9\u4E2A\u5730\u5740\r\n const qrCodeUrl = wsServer.getQRCodeUrl(clientId, ipAddress);\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n deviceId: session.deviceId,\r\n qrCodeUrl,\r\n message: \"\u8BF7\u4F7F\u7528DG-LAB APP\u626B\u63CF\u4E8C\u7EF4\u7801\u8FDB\u884C\u7ED1\u5B9A\",\r\n })\r\n );\r\n } catch (err) {\r\n const error = err instanceof ConnectionError ? err : new ConnectionError(\r\n err instanceof Error ? err.message : \"\u8FDE\u63A5\u5931\u8D25\",\r\n { code: ErrorCode.CONN_DEVICE_NOT_FOUND, cause: err instanceof Error ? err : undefined }\r\n );\r\n return createToolError(`\u8FDE\u63A5\u5931\u8D25: ${error.message}`);\r\n }\r\n }\r\n );\r\n\r\n // ========== dg_list_devices ==========\r\n // \u5217\u51FA\u6240\u6709\u8BBE\u5907\u53CA\u5176\u72B6\u6001\uFF0C\u7528\u4E8E\u67E5\u770B\u5F53\u524D\u8FDE\u63A5\u60C5\u51B5\r\n toolManager.registerTool(\r\n \"dg_list_devices\",\r\n `\u5217\u51FA\u6240\u6709\u5DF2\u521B\u5EFA\u7684\u8BBE\u5907\u8FDE\u63A5\u53CA\u5176\u72B6\u6001\u3002\r\n\u8FD4\u56DE\u5B57\u6BB5\u8BF4\u660E\uFF1A\r\n- deviceId: \u8BBE\u5907\u552F\u4E00\u6807\u8BC6\uFF0C\u7528\u4E8E\u540E\u7EED\u6240\u6709\u64CD\u4F5C\r\n- alias: \u8BBE\u5907\u522B\u540D\uFF08\u53EF\u9009\uFF0C\u7528\u4E8E\u65B9\u4FBF\u8BC6\u522B\uFF09\r\n- connected: \u4F1A\u8BDD\u662F\u5426\u5DF2\u5EFA\u7ACB\r\n- boundToApp: APP\u662F\u5426\u5DF2\u626B\u7801\u7ED1\u5B9A\uFF08\u5FC5\u987B\u4E3Atrue\u624D\u80FD\u63A7\u5236\u8BBE\u5907\uFF09\r\n- strengthA/B: \u5F53\u524DA/B\u901A\u9053\u5F3A\u5EA6(0-200)\r\n- strengthLimitA/B: A/B\u901A\u9053\u5F3A\u5EA6\u4E0A\u9650\uFF08\u7531APP\u8BBE\u7F6E\uFF09\r\n\u53EF\u9009\u53C2\u6570alias\u7528\u4E8E\u6309\u522B\u540D\u8FC7\u6EE4\u8BBE\u5907\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n alias: {\r\n type: \"string\",\r\n description: \"\u53EF\u9009\uFF0C\u6309\u522B\u540D\u8FC7\u6EE4\u8BBE\u5907\uFF08\u5927\u5C0F\u5199\u4E0D\u654F\u611F\uFF09\",\r\n },\r\n },\r\n required: [],\r\n },\r\n async (params) => {\r\n let sessions = sessionManager.listSessions();\r\n\r\n // \u652F\u6301\u6309\u522B\u540D\u8FC7\u6EE4\uFF0C\u65B9\u4FBF\u5728\u591A\u8BBE\u5907\u573A\u666F\u4E0B\u5FEB\u901F\u5B9A\u4F4D\r\n const alias = params.alias as string | undefined;\r\n if (alias) {\r\n sessions = sessionManager.findByAlias(alias);\r\n }\r\n\r\n // \u6784\u5EFA\u8BBE\u5907\u72B6\u6001\u5217\u8868\uFF0C\u5408\u5E76\u4F1A\u8BDD\u4FE1\u606F\u548C WebSocket \u7ED1\u5B9A\u72B6\u6001\r\n const devices = sessions.map((s) => {\r\n // boundToApp \u8868\u793A APP \u662F\u5426\u5DF2\u626B\u7801\u5E76\u5EFA\u7ACB\u8FDE\u63A5\r\n // \u53EA\u6709 boundToApp \u4E3A true \u65F6\u624D\u80FD\u63A7\u5236\u8BBE\u5907\r\n const isBound = s.clientId ? wsServer.isControllerBound(s.clientId) : false;\r\n \r\n return {\r\n deviceId: s.deviceId,\r\n alias: s.alias,\r\n connected: s.connected,\r\n boundToApp: isBound,\r\n strengthA: s.strengthA,\r\n strengthB: s.strengthB,\r\n strengthLimitA: s.strengthLimitA,\r\n strengthLimitB: s.strengthLimitB,\r\n };\r\n });\r\n\r\n return createToolResult(JSON.stringify({ devices, count: devices.length }));\r\n }\r\n );\r\n\r\n // ========== dg_set_alias ==========\r\n // \u4E3A\u8BBE\u5907\u8BBE\u7F6E\u522B\u540D\uFF0C\u4FBF\u4E8E\u8BC6\u522B\u548C\u7BA1\u7406\u591A\u4E2A\u8BBE\u5907\r\n toolManager.registerTool(\r\n \"dg_set_alias\",\r\n `\u4E3A\u8BBE\u5907\u8BBE\u7F6E\u81EA\u5B9A\u4E49\u522B\u540D\uFF0C\u65B9\u4FBF\u540E\u7EED\u901A\u8FC7\u522B\u540D\u67E5\u627E\u548C\u7BA1\u7406\u8BBE\u5907\u3002\r\n\u522B\u540D\u53EF\u4EE5\u662F\u7528\u6237\u540D\u3001\u6635\u79F0\u6216\u4EFB\u4F55\u4FBF\u4E8E\u8BC6\u522B\u7684\u540D\u79F0\u3002\r\n\u8BBE\u7F6E\u540E\u53EF\u901A\u8FC7dg_find_device\u6309\u522B\u540D\u67E5\u627E\uFF0C\u6216\u5728dg_disconnect\u4E2D\u4F7F\u7528\u522B\u540D\u65AD\u5F00\u8FDE\u63A5\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n deviceId: {\r\n type: \"string\",\r\n description: \"\u8BBE\u5907ID\uFF08\u4ECEdg_connect\u6216dg_list_devices\u83B7\u53D6\uFF09\",\r\n },\r\n alias: {\r\n type: \"string\",\r\n description: \"\u81EA\u5B9A\u4E49\u522B\u540D\uFF08\u5982\u7528\u6237\u540D\u3001\u6635\u79F0\u7B49\uFF0C\u652F\u6301\u4E2D\u6587\uFF09\",\r\n },\r\n },\r\n required: [\"deviceId\", \"alias\"],\r\n },\r\n async (params) => {\r\n const deviceId = params.deviceId as string;\r\n const alias = params.alias as string;\r\n\r\n // \u53C2\u6570\u6821\u9A8C\r\n if (!deviceId) {\r\n return createToolError(\"\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: deviceId\");\r\n }\r\n if (!alias) {\r\n return createToolError(\"\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: alias\");\r\n }\r\n\r\n // \u5C1D\u8BD5\u8BBE\u7F6E\u522B\u540D\uFF0C\u5982\u679C\u8BBE\u5907\u4E0D\u5B58\u5728\u4F1A\u8FD4\u56DE false\r\n const success = sessionManager.setAlias(deviceId, alias);\r\n if (!success) {\r\n return createToolError(`\u8BBE\u5907\u4E0D\u5B58\u5728: ${deviceId}`);\r\n }\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n success: true,\r\n deviceId,\r\n alias,\r\n })\r\n );\r\n }\r\n );\r\n\r\n // ========== dg_find_device ==========\r\n // \u901A\u8FC7\u522B\u540D\u67E5\u627E\u8BBE\u5907\uFF0C\u652F\u6301\u6A21\u7CCA\u5339\u914D\r\n toolManager.registerTool(\r\n \"dg_find_device\",\r\n `\u901A\u8FC7\u522B\u540D\u67E5\u627E\u8BBE\u5907\uFF08\u5927\u5C0F\u5199\u4E0D\u654F\u611F\uFF0C\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09\u3002\r\n\u8FD4\u56DE\u6240\u6709\u5339\u914D\u7684\u8BBE\u5907\u5217\u8868\uFF0C\u5305\u542B\u5B8C\u6574\u72B6\u6001\u4FE1\u606F\u3002\r\n\u9002\u7528\u573A\u666F\uFF1A\u5F53\u77E5\u9053\u7528\u6237\u522B\u540D\u4F46\u4E0D\u8BB0\u5F97deviceId\u65F6\u4F7F\u7528\u3002\r\n\u8FD4\u56DE\u5B57\u6BB5\u4E0Edg_list_devices\u76F8\u540C\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n alias: {\r\n type: \"string\",\r\n description: \"\u8981\u67E5\u627E\u7684\u522B\u540D\",\r\n },\r\n },\r\n required: [\"alias\"],\r\n },\r\n async (params) => {\r\n const alias = params.alias as string;\r\n\r\n if (!alias) {\r\n return createToolError(\"\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: alias\");\r\n }\r\n\r\n // \u67E5\u627E\u6240\u6709\u5339\u914D\u7684\u8BBE\u5907\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09\r\n const sessions = sessionManager.findByAlias(alias);\r\n \r\n // \u6784\u5EFA\u8BBE\u5907\u72B6\u6001\u5217\u8868\uFF0C\u4E0E dg_list_devices \u683C\u5F0F\u4E00\u81F4\r\n const devices = sessions.map((s) => {\r\n const isBound = s.clientId ? wsServer.isControllerBound(s.clientId) : false;\r\n \r\n return {\r\n deviceId: s.deviceId,\r\n alias: s.alias,\r\n connected: s.connected,\r\n boundToApp: isBound,\r\n strengthA: s.strengthA,\r\n strengthB: s.strengthB,\r\n strengthLimitA: s.strengthLimitA,\r\n strengthLimitB: s.strengthLimitB,\r\n };\r\n });\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n devices,\r\n count: devices.length,\r\n })\r\n );\r\n }\r\n );\r\n\r\n // ========== dg_disconnect ==========\r\n // \u65AD\u5F00\u8BBE\u5907\u8FDE\u63A5\u5E76\u6E05\u7406\u8D44\u6E90\r\n toolManager.registerTool(\r\n \"dg_disconnect\",\r\n `\u65AD\u5F00\u5E76\u5220\u9664\u8BBE\u5907\u8FDE\u63A5\uFF0C\u91CA\u653E\u8D44\u6E90\u3002\r\n\u53EF\u901A\u8FC7deviceId\u7CBE\u786E\u5220\u9664\u5355\u4E2A\u8BBE\u5907\uFF0C\u6216\u901A\u8FC7alias\u5220\u9664\u6240\u6709\u5339\u914D\u7684\u8BBE\u5907\u3002\r\n\u6CE8\u610F\uFF1AdeviceId\u548Calias\u53EA\u80FD\u4E8C\u9009\u4E00\uFF0C\u4E0D\u80FD\u540C\u65F6\u63D0\u4F9B\u3002\r\n\u5220\u9664\u540E\u8BBE\u5907\u9700\u8981\u91CD\u65B0\u8C03\u7528dg_connect\u521B\u5EFA\u65B0\u8FDE\u63A5\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n deviceId: {\r\n type: \"string\",\r\n description: \"\u8BBE\u5907ID\uFF08\u4E0Ealias\u4E8C\u9009\u4E00\uFF09\",\r\n },\r\n alias: {\r\n type: \"string\",\r\n description: \"\u8BBE\u5907\u522B\u540D\uFF08\u4E0EdeviceId\u4E8C\u9009\u4E00\uFF09\",\r\n },\r\n },\r\n required: [],\r\n },\r\n async (params) => {\r\n const deviceId = params.deviceId as string | undefined;\r\n const alias = params.alias as string | undefined;\r\n\r\n // \u53C2\u6570\u6821\u9A8C\uFF1A\u5FC5\u987B\u63D0\u4F9B deviceId \u6216 alias \u4E4B\u4E00\r\n if (!deviceId && !alias) {\r\n return createToolError(\"\u5FC5\u987B\u63D0\u4F9B deviceId \u6216 alias \u53C2\u6570\u4E4B\u4E00\");\r\n }\r\n\r\n // \u4E0D\u5141\u8BB8\u540C\u65F6\u63D0\u4F9B\u4E24\u4E2A\u53C2\u6570\uFF0C\u907F\u514D\u6B67\u4E49\r\n if (deviceId && alias) {\r\n return createToolError(\"\u53EA\u80FD\u63D0\u4F9B deviceId \u6216 alias \u53C2\u6570\u4E4B\u4E00\uFF0C\u4E0D\u80FD\u540C\u65F6\u63D0\u4F9B\");\r\n }\r\n\r\n // \u6536\u96C6\u8981\u5220\u9664\u7684\u8BBE\u5907 ID \u5217\u8868\r\n let sessionsToDelete: string[] = [];\r\n\r\n if (deviceId) {\r\n // \u901A\u8FC7 deviceId \u7CBE\u786E\u67E5\u627E\r\n const session = sessionManager.getSession(deviceId);\r\n if (!session) {\r\n return createToolError(`\u8BBE\u5907\u4E0D\u5B58\u5728: ${deviceId}`);\r\n }\r\n sessionsToDelete.push(deviceId);\r\n } else if (alias) {\r\n // \u901A\u8FC7 alias \u6A21\u7CCA\u67E5\u627E\uFF0C\u53EF\u80FD\u5339\u914D\u591A\u4E2A\u8BBE\u5907\r\n const sessions = sessionManager.findByAlias(alias);\r\n if (sessions.length === 0) {\r\n return createToolError(`\u672A\u627E\u5230\u522B\u540D\u4E3A \"${alias}\" \u7684\u8BBE\u5907`);\r\n }\r\n sessionsToDelete = sessions.map(s => s.deviceId);\r\n }\r\n\r\n // \u9010\u4E2A\u5220\u9664\u5339\u914D\u7684\u8BBE\u5907\r\n const deletedDevices: Array<{ deviceId: string; alias: string | null }> = [];\r\n for (const id of sessionsToDelete) {\r\n const session = sessionManager.getSession(id);\r\n if (session) {\r\n // \u5148\u65AD\u5F00 WebSocket \u8FDE\u63A5\uFF0C\u786E\u4FDD APP \u7AEF\u6536\u5230\u65AD\u5F00\u901A\u77E5\r\n if (session.clientId) {\r\n wsServer.disconnectController(session.clientId);\r\n }\r\n \r\n deletedDevices.push({\r\n deviceId: session.deviceId,\r\n alias: session.alias,\r\n });\r\n \r\n // \u6700\u540E\u5220\u9664\u4F1A\u8BDD\u8BB0\u5F55\r\n sessionManager.deleteSession(id);\r\n }\r\n }\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n success: true,\r\n deletedCount: deletedDevices.length,\r\n deletedDevices,\r\n })\r\n );\r\n }\r\n );\r\n}\r\n", "/**\r\n * @fileoverview \u6CE2\u5F62\u5B58\u50A8\u6A21\u5757\r\n * @description \u7BA1\u7406\u6CE2\u5F62\u7684\u6301\u4E45\u5316\u5B58\u50A8\r\n * - \u4FDD\u5B58\u3001\u83B7\u53D6\u3001\u5217\u51FA\u3001\u5220\u9664\u6CE2\u5F62\r\n * - \u6301\u4E45\u5316\u5230 JSON \u6587\u4EF6\r\n * - \u542F\u52A8\u65F6\u4ECE\u6587\u4EF6\u52A0\u8F7D\r\n */\r\n\r\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"fs\";\r\nimport { dirname } from \"path\";\r\nimport type { ParsedWaveform, WaveformMetadata, WaveformSection } from \"./waveform-parser\";\r\n\r\n/**\r\n * \u5B58\u50A8\u683C\u5F0F\u7684\u6CE2\u5F62\u6570\u636E\r\n */\r\nexport interface StoredWaveform {\r\n /** \u6CE2\u5F62\u540D\u79F0 */\r\n name: string;\r\n /** \u5143\u6570\u636E */\r\n metadata: WaveformMetadata;\r\n /** \u5C0F\u8282\u6570\u636E */\r\n sections: WaveformSection[];\r\n /** \u539F\u59CB\u6570\u636E */\r\n rawData: string;\r\n /** HEX \u6CE2\u5F62\u6570\u7EC4 */\r\n hexWaveforms: string[];\r\n /** \u521B\u5EFA\u65F6\u95F4\uFF08ISO 8601 \u683C\u5F0F\uFF09 */\r\n createdAt: string;\r\n}\r\n\r\n/**\r\n * \u6CE2\u5F62\u5B58\u50A8\u6570\u636E\u683C\u5F0F\r\n */\r\nexport interface WaveformStorageData {\r\n /** \u7248\u672C\u53F7 */\r\n version: 1;\r\n /** \u6CE2\u5F62\u6570\u7EC4 */\r\n waveforms: StoredWaveform[];\r\n}\r\n\r\n/**\r\n * \u6CE2\u5F62\u5B58\u50A8\u7BA1\u7406\u5668\r\n * @description \u7BA1\u7406\u6CE2\u5F62\u7684\u5185\u5B58\u5B58\u50A8\u548C\u6301\u4E45\u5316\r\n */\r\nexport class WaveformStorage {\r\n private waveforms: Map<string, ParsedWaveform> = new Map();\r\n\r\n /**\r\n * \u4FDD\u5B58\u6CE2\u5F62\uFF08\u5982\u679C\u540D\u79F0\u5DF2\u5B58\u5728\u5219\u8986\u76D6\uFF09\r\n * @param waveform - \u6CE2\u5F62\u6570\u636E\r\n */\r\n save(waveform: ParsedWaveform): void {\r\n this.waveforms.set(waveform.name, waveform);\r\n }\r\n\r\n /**\r\n * \u6839\u636E\u540D\u79F0\u83B7\u53D6\u6CE2\u5F62\r\n * @param name - \u6CE2\u5F62\u540D\u79F0\r\n * @returns \u6CE2\u5F62\u6570\u636E\u6216 null\r\n */\r\n get(name: string): ParsedWaveform | null {\r\n return this.waveforms.get(name) || null;\r\n }\r\n\r\n /**\r\n * \u5217\u51FA\u6240\u6709\u6CE2\u5F62\r\n * @returns \u6CE2\u5F62\u6570\u7EC4\r\n */\r\n list(): ParsedWaveform[] {\r\n return Array.from(this.waveforms.values());\r\n }\r\n\r\n /**\r\n * \u6839\u636E\u540D\u79F0\u5220\u9664\u6CE2\u5F62\r\n * @param name - \u6CE2\u5F62\u540D\u79F0\r\n * @returns \u662F\u5426\u6210\u529F\u5220\u9664\r\n */\r\n delete(name: string): boolean {\r\n return this.waveforms.delete(name);\r\n }\r\n\r\n /**\r\n * \u83B7\u53D6\u6CE2\u5F62\u6570\u91CF\r\n */\r\n get count(): number {\r\n return this.waveforms.size;\r\n }\r\n\r\n /**\r\n * \u68C0\u67E5\u6CE2\u5F62\u662F\u5426\u5B58\u5728\r\n * @param name - \u6CE2\u5F62\u540D\u79F0\r\n * @returns \u662F\u5426\u5B58\u5728\r\n */\r\n has(name: string): boolean {\r\n return this.waveforms.has(name);\r\n }\r\n\r\n /**\r\n * \u6E05\u9664\u6240\u6709\u6CE2\u5F62\r\n */\r\n clear(): void {\r\n this.waveforms.clear();\r\n }\r\n\r\n /**\r\n * \u8F6C\u6362\u4E3A\u5B58\u50A8\u6570\u636E\u683C\u5F0F\r\n * @returns \u5B58\u50A8\u6570\u636E\r\n */\r\n toStorageData(): WaveformStorageData {\r\n const waveforms: StoredWaveform[] = [];\r\n\r\n for (const waveform of this.waveforms.values()) {\r\n waveforms.push({\r\n name: waveform.name,\r\n metadata: waveform.metadata,\r\n sections: waveform.sections,\r\n rawData: waveform.rawData,\r\n hexWaveforms: waveform.hexWaveforms,\r\n createdAt: waveform.createdAt.toISOString(),\r\n });\r\n }\r\n\r\n return { version: 1, waveforms };\r\n }\r\n\r\n /**\r\n * \u4ECE\u5B58\u50A8\u6570\u636E\u683C\u5F0F\u52A0\u8F7D\r\n * @param data - \u5B58\u50A8\u6570\u636E\r\n */\r\n fromStorageData(data: WaveformStorageData): void {\r\n this.waveforms.clear();\r\n\r\n for (const stored of data.waveforms) {\r\n const waveform: ParsedWaveform = {\r\n name: stored.name,\r\n metadata: stored.metadata,\r\n sections: stored.sections,\r\n rawData: stored.rawData,\r\n hexWaveforms: stored.hexWaveforms,\r\n createdAt: new Date(stored.createdAt),\r\n };\r\n this.waveforms.set(waveform.name, waveform);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * \u5C06\u6CE2\u5F62\u6301\u4E45\u5316\u5230\u78C1\u76D8\r\n * @param storage - \u6CE2\u5F62\u5B58\u50A8\u5B9E\u4F8B\r\n * @param filePath - \u6587\u4EF6\u8DEF\u5F84\r\n */\r\nexport function persistWaveforms(\r\n storage: WaveformStorage,\r\n filePath: string = \"./data/waveforms.json\"\r\n): void {\r\n const data = storage.toStorageData();\r\n const json = JSON.stringify(data, null, 2);\r\n\r\n // \u786E\u4FDD\u76EE\u5F55\u5B58\u5728\r\n const dir = dirname(filePath);\r\n if (!existsSync(dir)) {\r\n mkdirSync(dir, { recursive: true });\r\n }\r\n\r\n writeFileSync(filePath, json, \"utf8\");\r\n}\r\n\r\n/**\r\n * \u4ECE\u78C1\u76D8\u52A0\u8F7D\u6CE2\u5F62\r\n * @param storage - \u6CE2\u5F62\u5B58\u50A8\u5B9E\u4F8B\r\n * @param filePath - \u6587\u4EF6\u8DEF\u5F84\r\n * @returns \u662F\u5426\u6210\u529F\u52A0\u8F7D\r\n */\r\nexport function loadWaveforms(\r\n storage: WaveformStorage,\r\n filePath: string = \"./data/waveforms.json\"\r\n): boolean {\r\n if (!existsSync(filePath)) {\r\n return false;\r\n }\r\n\r\n try {\r\n const json = readFileSync(filePath, \"utf8\");\r\n const data = JSON.parse(json) as WaveformStorageData;\r\n\r\n if (data.version !== 1) {\r\n console.warn(`\u672A\u77E5\u7684\u6CE2\u5F62\u5B58\u50A8\u7248\u672C: ${data.version}`);\r\n return false;\r\n }\r\n\r\n storage.fromStorageData(data);\r\n return true;\r\n } catch (error) {\r\n console.error(\"\u52A0\u8F7D\u6CE2\u5F62\u5931\u8D25:\", error);\r\n return false;\r\n }\r\n}\r\n", "/**\r\n * @fileoverview \u6CE2\u5F62\u89E3\u6790\u5668\u6A21\u5757\r\n * \r\n * \u5904\u7406 DG-LAB APP \u5BFC\u51FA\u7684 Dungeonlab+pulse: \u6587\u672C\u683C\u5F0F\u6CE2\u5F62\u6570\u636E\u3002\r\n * \u8FD9\u662F APP v2.0+ \u4F7F\u7528\u7684\u6CE2\u5F62\u683C\u5F0F\uFF0C\u5305\u542B\u5B8C\u6574\u7684\u6CE2\u5F62\u5B9A\u4E49\u4FE1\u606F\u3002\r\n * \r\n * \u6CE2\u5F62\u683C\u5F0F\u6982\u8FF0\uFF1A\r\n * ```\r\n * Dungeonlab+pulse:setting=section+section+section...\r\n * ```\r\n * \r\n * \u5404\u90E8\u5206\u8BF4\u660E\uFF1A\r\n * - header: `Dungeonlab+pulse:` \u56FA\u5B9A\u524D\u7F00\r\n * - setting: `\u5C0F\u8282\u4F11\u606F\u65F6\u957F,\u64AD\u653E\u901F\u7387,\u9AD8\u4F4E\u9891\u5E73\u8861=` \u5168\u5C40\u8BBE\u7F6E\r\n * - section: `\u9891\u7387\u8303\u56F41,\u9891\u7387\u8303\u56F42,\u5C0F\u8282\u65F6\u957F,\u9891\u7387\u6A21\u5F0F,\u5C0F\u8282\u5F00\u5173/\u8109\u51B2\u5143\u5F62\u72B6\u503C-\u662F\u5426\u951A\u70B9,...`\r\n * - section-split: `+section+` \u5C0F\u8282\u5206\u9694\u7B26\r\n * \r\n * \u6838\u5FC3\u6982\u5FF5\uFF1A\r\n * - \u5F62\u72B6\u70B9\uFF1A\u5B9A\u4E49 100ms \u5185\u7684\u8F93\u51FA\u5F3A\u5EA6\uFF080-100\uFF09\r\n * - \u8109\u51B2\u5143\uFF1A\u7531\u591A\u4E2A\u5F62\u72B6\u70B9\u7EC4\u6210\u7684\u4E00\u4E2A\u5B8C\u6574\u6CE2\u5F62\u5468\u671F\r\n * - \u5C0F\u8282\uFF1A\u8109\u51B2\u5143\u5FAA\u73AF\u64AD\u653E\uFF0C\u76F4\u5230\u8FBE\u5230\u8BBE\u5B9A\u65F6\u957F\r\n * - \u9891\u7387\u6A21\u5F0F\uFF1A\u63A7\u5236\u9891\u7387\u5728\u65F6\u95F4\u8F74\u4E0A\u7684\u53D8\u5316\u65B9\u5F0F\r\n * \r\n * @example\r\n * // \u5178\u578B\u7684\u6CE2\u5F62\u6570\u636E\r\n * Dungeonlab+pulse:18,1,8=27,7,32,3,1/0-1,11.1-0,22.2-0,...+section+0,20,39,2,1/0-1,100-1\r\n */\r\n\r\nimport { WaveformError, ErrorCode } from \"./errors\";\r\n\r\n// ============================================================================\r\n// \u6570\u636E\u96C6\r\n// \u8FD9\u4E9B\u6570\u636E\u96C6\u6765\u81EA DG-LAB APP \u89C4\u8303\uFF0C\u7528\u4E8E\u5C06\u7D22\u5F15\u503C\u8F6C\u6362\u4E3A\u5B9E\u9645\u53C2\u6570\r\n// ============================================================================\r\n\r\n/**\r\n * \u9891\u7387\u6570\u636E\u96C6\uFF08\u6CE2\u5F62\u9891\u7387\uFF0C\u5355\u4F4D ms\uFF09\r\n * \r\n * \u5C06\u7D22\u5F15\u503C\uFF080-83\uFF09\u6620\u5C04\u5230\u5B9E\u9645\u6CE2\u5F62\u9891\u7387\u503C\uFF0810-1000 ms\uFF09\u3002\r\n * APP \u4E2D\u7684\u9891\u7387\u6ED1\u5757\u4F7F\u7528\u7D22\u5F15\uFF0C\u9700\u8981\u901A\u8FC7\u6B64\u8868\u8F6C\u6362\u4E3A\u6CE2\u5F62\u9891\u7387\u503C\u3002\r\n * \r\n * \u6CE2\u5F62\u9891\u7387 = \u8F93\u51FA\u5355\u5143\u65F6\u957F\uFF08ms\uFF09\uFF0C\u8109\u51B2\u9891\u7387 = 1000 / \u6CE2\u5F62\u9891\u7387\uFF08Hz\uFF09\r\n * \u4F8B\u5982\uFF1A\u6CE2\u5F62\u9891\u7387 10ms = \u8109\u51B2\u9891\u7387 100Hz\uFF0C\u6CE2\u5F62\u9891\u7387 1000ms = \u8109\u51B2\u9891\u7387 1Hz\r\n * \r\n * \u6CE8\u610F\uFF1A\u8FD9\u4E2A\u6570\u636E\u96C6\u7684\u503C\u662F\u6CE2\u5F62\u9891\u7387\uFF08ms\uFF09\uFF0C\u4E0D\u662F\u8109\u51B2\u9891\u7387\uFF08Hz\uFF09\u3002\r\n * \u53D1\u9001\u5230\u8BBE\u5907\u524D\u9700\u8981\u901A\u8FC7 getOutputValue() \u51FD\u6570\u8F6C\u6362\u4E3A\u8BBE\u5907\u534F\u8BAE\u503C\uFF0810-240\uFF09\u3002\r\n * \r\n * \u5B98\u65B9\u6570\u636E\u96C6\u89C4\u5F8B\uFF1A\r\n * - (10..50) step 1 \u2192 \u7D22\u5F15 0-40\r\n * - (52..80) step 2 \u2192 \u7D22\u5F15 41-55\r\n * - (85..100) step 5 \u2192 \u7D22\u5F15 56-59\r\n * - (110..200) step 10 \u2192 \u7D22\u5F15 60-69\r\n * - (233..400) step 33 \u2192 \u7D22\u5F15 70-75\r\n * - (450..600) step 50 \u2192 \u7D22\u5F15 76-79\r\n * - (700..1000) step 100 \u2192 \u7D22\u5F15 80-83\r\n * \r\n * \u6765\u6E90\uFF1ADG-LAB \u5B98\u65B9\u63D0\u4F9B\r\n */\r\nexport const FREQUENCY_DATASET: number[] = [\r\n // (10..50) step 1 \u2192 \u7D22\u5F15 0-40\r\n 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,\r\n 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,\r\n 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,\r\n 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\r\n // (52..80) step 2 \u2192 \u7D22\u5F15 41-55\r\n 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80,\r\n // (85..100) step 5 \u2192 \u7D22\u5F15 56-59\r\n 85, 90, 95, 100,\r\n // (110..200) step 10 \u2192 \u7D22\u5F15 60-69\r\n 110, 120, 130, 140, 150, 160, 170, 180, 190, 200,\r\n // (233..400) step 33 \u2192 \u7D22\u5F15 70-75\r\n 233, 266, 300, 333, 366, 400,\r\n // (450..600) step 50 \u2192 \u7D22\u5F15 76-79\r\n 450, 500, 550, 600,\r\n // (700..1000) step 100 \u2192 \u7D22\u5F15 80-83\r\n 700, 800, 900, 1000\r\n];\r\n\r\n/**\r\n * \u65F6\u957F\u6570\u636E\u96C6\r\n * \r\n * \u5C06\u7D22\u5F15\u503C\uFF080-99\uFF09\u6620\u5C04\u5230\u5B9E\u9645\u65F6\u957F\uFF081-100\uFF0C\u5355\u4F4D 100ms\uFF09\u3002\r\n * \u4F8B\u5982\uFF1A\u7D22\u5F15 32 \u5BF9\u5E94 3.3 \u79D2\u7684\u5C0F\u8282\u65F6\u957F\u3002\r\n */\r\nexport const DURATION_DATASET: number[] = Array.from({ length: 100 }, (_, i) => i + 1);\r\n\r\n// ============================================================================\r\n// \u63A5\u53E3\u5B9A\u4E49\r\n// ============================================================================\r\n\r\n/**\r\n * \u5168\u5C40\u6CE2\u5F62\u8BBE\u7F6E\r\n * \r\n * \u8FD9\u4E9B\u8BBE\u7F6E\u5F71\u54CD\u6574\u4E2A\u6CE2\u5F62\u7684\u64AD\u653E\u884C\u4E3A\uFF0C\u5728\u6CE2\u5F62\u6570\u636E\u7684\u5F00\u5934\u5B9A\u4E49\u3002\r\n */\r\nexport interface WaveformGlobalSettings {\r\n /** \r\n * \u5C0F\u8282\u4F11\u606F\u65F6\u957F\r\n * \u8303\u56F4 0-100\uFF0C\u5BF9\u5E94 0-10 \u79D2\u3002\u5C0F\u8282\u64AD\u653E\u5B8C\u6BD5\u540E\u7684\u9759\u9ED8\u65F6\u95F4\u3002\r\n */\r\n sectionRestTime: number;\r\n /** \r\n * \u64AD\u653E\u901F\u7387\r\n * 1=100ms, 2=50ms, 4=25ms \u91C7\u6837\u95F4\u9694\u3002\u4EC5 3.0 \u8BBE\u5907\u652F\u6301\u3002\r\n */\r\n playbackSpeed: number;\r\n /** \r\n * \u9AD8\u4F4E\u9891\u5E73\u8861\r\n * \u8303\u56F4 1-16\uFF0C\u5F71\u54CD\u8F93\u51FA\u7684\u9891\u7387\u7279\u6027\u3002\u4EC5 2.0 \u8BBE\u5907\u4F7F\u7528\u3002\r\n */\r\n frequencyBalance: number;\r\n}\r\n\r\n/**\r\n * \u6CE2\u5F62\u5143\u6570\u636E\r\n * \r\n * \u5305\u542B\u6CE2\u5F62\u7684\u5168\u5C40\u8BBE\u7F6E\u548C\u5404\u5C0F\u8282\u7684\u53C2\u6570\u7D22\u5F15\u3002\r\n * \u8FD9\u4E9B\u6570\u636E\u7528\u4E8E\u91CD\u5EFA\u6CE2\u5F62\u6216\u8FDB\u884C\u5206\u6790\u3002\r\n */\r\nexport interface WaveformMetadata {\r\n /** \u5168\u5C40\u8BBE\u7F6E */\r\n globalSettings: WaveformGlobalSettings;\r\n /** \u5404\u5C0F\u8282\u7684\u8D77\u59CB\u9891\u7387\u7D22\u5F15\uFF080-83\uFF09 */\r\n startFrequencyIndices: number[];\r\n /** \u5404\u5C0F\u8282\u7684\u7ED3\u675F\u9891\u7387\u7D22\u5F15\uFF080-83\uFF09 */\r\n endFrequencyIndices: number[];\r\n /** \u5404\u5C0F\u8282\u7684\u65F6\u957F\u7D22\u5F15\uFF080-99\uFF09 */\r\n durationIndices: number[];\r\n /** \u5404\u5C0F\u8282\u7684\u9891\u7387\u6A21\u5F0F\uFF081-4\uFF09 */\r\n frequencyModes: number[];\r\n /** \u5404\u5C0F\u8282\u7684\u542F\u7528\u72B6\u6001 */\r\n sectionEnabled: boolean[];\r\n // \u517C\u5BB9\u6027\u5B57\u6BB5\r\n startFrequencies: [number, number, number];\r\n endFrequencies: [number, number, number];\r\n durations: [number, number, number];\r\n frequencyModes_legacy: [number, number, number];\r\n section2Enabled: boolean;\r\n section3Enabled: boolean;\r\n playbackSpeed: number;\r\n}\r\n\r\n/**\r\n * \u6CE2\u5F62\u5F62\u72B6\u6570\u636E\u70B9\r\n * \r\n * \u5B9A\u4E49\u8109\u51B2\u5143\u4E2D\u5355\u4E2A\u65F6\u95F4\u70B9\u7684\u8F93\u51FA\u5F3A\u5EA6\u3002\r\n * \u6BCF\u4E2A\u5F62\u72B6\u70B9\u5BF9\u5E94 100ms \u7684\u8F93\u51FA\u65F6\u95F4\u3002\r\n */\r\nexport interface WaveformShapePoint {\r\n /** \r\n * \u5F3A\u5EA6\u503C\r\n * \u8303\u56F4 0-100\uFF0C\u8868\u793A\u8BE5\u65F6\u95F4\u70B9\u7684\u8F93\u51FA\u5F3A\u5EA6\u767E\u5206\u6BD4\u3002\r\n */\r\n strength: number;\r\n /** \r\n * \u662F\u5426\u4E3A\u951A\u70B9\r\n * \u951A\u70B9\u5728 APP \u7F16\u8F91\u5668\u4E2D\u7528\u4E8E\u56FA\u5B9A\u5173\u952E\u5E27\uFF0C\u4E0D\u5F71\u54CD\u5B9E\u9645\u8F93\u51FA\u3002\r\n */\r\n isAnchor: boolean;\r\n /** \u517C\u5BB9\u6027\u5B57\u6BB5\uFF1A\u4E0E isAnchor \u76F8\u540C\uFF0C0=\u666E\u901A\u70B9, 1=\u951A\u70B9 */\r\n shapeType: number;\r\n}\r\n\r\n/**\r\n * \u6CE2\u5F62\u5C0F\u8282\r\n * \r\n * \u5C0F\u8282\u662F\u6CE2\u5F62\u7684\u57FA\u672C\u7EC4\u6210\u5355\u4F4D\uFF0C\u5305\u542B\u9891\u7387\u8303\u56F4\u3001\u65F6\u957F\u548C\u5F62\u72B6\u6570\u636E\u3002\r\n * \u4E00\u4E2A\u6CE2\u5F62\u53EF\u4EE5\u5305\u542B\u591A\u4E2A\u5C0F\u8282\uFF0C\u6309\u987A\u5E8F\u64AD\u653E\u3002\r\n */\r\nexport interface WaveformSection {\r\n /** \u5C0F\u8282\u7D22\u5F15 */\r\n index: number;\r\n /** \u662F\u5426\u542F\u7528 */\r\n enabled: boolean;\r\n /** \u9891\u7387\u8303\u56F4 1 \u7D22\u5F15\uFF080-83\uFF09 */\r\n frequencyRange1Index: number;\r\n /** \u9891\u7387\u8303\u56F4 2 \u7D22\u5F15\uFF080-83\uFF09 */\r\n frequencyRange2Index: number;\r\n /** \u5C0F\u8282\u65F6\u957F\u7D22\u5F15\uFF080-99\uFF09 */\r\n durationIndex: number;\r\n /** \u9891\u7387\u6A21\u5F0F\uFF081-4\uFF09 */\r\n frequencyMode: number;\r\n /** \u5F62\u72B6\u6570\u636E\u70B9 */\r\n shape: WaveformShapePoint[];\r\n // \u8BA1\u7B97\u503C\uFF08\u517C\u5BB9\u6027\uFF09\r\n startFrequency: number;\r\n endFrequency: number;\r\n duration: number;\r\n}\r\n\r\n/**\r\n * \u5B8C\u6574\u7684\u89E3\u6790\u540E\u6CE2\u5F62\r\n * \r\n * \u5305\u542B\u6CE2\u5F62\u7684\u6240\u6709\u4FE1\u606F\uFF1A\u5143\u6570\u636E\u3001\u5C0F\u8282\u3001\u539F\u59CB\u6570\u636E\u548C\u8F6C\u6362\u540E\u7684 HEX \u6CE2\u5F62\u3002\r\n * \u8FD9\u662F\u6CE2\u5F62\u89E3\u6790\u7684\u6700\u7EC8\u8F93\u51FA\uFF0C\u53EF\u4EE5\u76F4\u63A5\u7528\u4E8E\u8BBE\u5907\u63A7\u5236\u3002\r\n */\r\nexport interface ParsedWaveform {\r\n /** \u6CE2\u5F62\u540D\u79F0 */\r\n name: string;\r\n /** \u5143\u6570\u636E */\r\n metadata: WaveformMetadata;\r\n /** \u5C0F\u8282\u6570\u7EC4 */\r\n sections: WaveformSection[];\r\n /** \u539F\u59CB\u6570\u636E */\r\n rawData: string;\r\n /** HEX \u6CE2\u5F62\u6570\u7EC4 */\r\n hexWaveforms: string[];\r\n /** \u521B\u5EFA\u65F6\u95F4 */\r\n createdAt: Date;\r\n}\r\n\r\n// ============================================================================\r\n// \u8F85\u52A9\u51FD\u6570\r\n// ============================================================================\r\n\r\n/**\r\n * \u6839\u636E\u7D22\u5F15\u83B7\u53D6\u9891\u7387\u503C\uFF08\u6CE2\u5F62\u9891\u7387\uFF0C\u5355\u4F4D ms\uFF09\r\n * \r\n * \u5C06 APP \u4F7F\u7528\u7684\u9891\u7387\u7D22\u5F15\uFF080-83\uFF09\u8F6C\u6362\u4E3A\u5B9E\u9645\u6CE2\u5F62\u9891\u7387\u503C\uFF0810-1000 ms\uFF09\u3002\r\n * \u8D85\u51FA\u8303\u56F4\u7684\u7D22\u5F15\u4F1A\u88AB\u9650\u5236\u5728\u6709\u6548\u8303\u56F4\u5185\u3002\r\n * \r\n * \u6CE8\u610F\uFF1A\u8FD4\u56DE\u7684\u662F\u6CE2\u5F62\u9891\u7387\uFF08ms\uFF09\uFF0C\u4E0D\u662F\u8109\u51B2\u9891\u7387\uFF08Hz\uFF09\u3002\r\n * \u8109\u51B2\u9891\u7387 = 1000 / \u6CE2\u5F62\u9891\u7387\r\n * \r\n * @param index - \u9891\u7387\u7D22\u5F15\uFF0C\u8303\u56F4 0-83\r\n * @returns \u5BF9\u5E94\u7684\u6CE2\u5F62\u9891\u7387\u503C\uFF08ms\uFF09\r\n */\r\nexport function getFrequencyFromIndex(index: number): number {\r\n const clampedIndex = Math.max(0, Math.min(83, Math.floor(index)));\r\n return FREQUENCY_DATASET[clampedIndex] ?? 10;\r\n}\r\n\r\n/**\r\n * \u6839\u636E\u7D22\u5F15\u83B7\u53D6\u65F6\u957F\u503C\r\n * \r\n * \u5C06\u65F6\u957F\u7D22\u5F15\uFF080-99\uFF09\u8F6C\u6362\u4E3A\u5B9E\u9645\u65F6\u957F\u503C\u3002\r\n * \u8FD4\u56DE\u503C\u5355\u4F4D\u4E3A 100ms\uFF0C\u4F8B\u5982\u8FD4\u56DE 32 \u8868\u793A 3.2 \u79D2\u3002\r\n * \r\n * @param index - \u65F6\u957F\u7D22\u5F15\uFF0C\u8303\u56F4 0-99\r\n * @returns \u65F6\u957F\u503C\uFF08\u5355\u4F4D 100ms\uFF09\r\n */\r\nexport function getDurationFromIndex(index: number): number {\r\n const clampedIndex = Math.max(0, Math.min(99, Math.floor(index)));\r\n return DURATION_DATASET[clampedIndex] ?? 1;\r\n}\r\n\r\n/**\r\n * \u6CE2\u5F62\u9891\u7387\u503C\u8F6C\u6362\u4E3A\u8BBE\u5907\u8F93\u51FA\u503C\r\n * \r\n * \u5C06\u6CE2\u5F62\u9891\u7387\u503C\uFF0810-1000 ms\uFF09\u8F6C\u6362\u4E3A\u8BBE\u5907\u534F\u8BAE\u4F7F\u7528\u7684\u8F93\u51FA\u503C\uFF0810-240\uFF09\u3002\r\n * \u8FD9\u4E2A\u8F6C\u6362\u57FA\u4E8E V3 \u534F\u8BAE\u89C4\u8303\uFF0C\u4F7F\u7528\u5206\u6BB5\u7EBF\u6027\u6620\u5C04\u3002\r\n * \r\n * \u8F6C\u6362\u89C4\u5219\uFF08\u53C2\u8003 temp_dg_lab/coyote/v3/README_V3.md\uFF09\uFF1A\r\n * - 10-100 ms: \u76F4\u63A5\u6620\u5C04\uFF08\u8F93\u51FA 10-100\uFF09\r\n * - 101-600 ms: \u538B\u7F29\u6620\u5C04\uFF08\u8F93\u51FA 100-200\uFF09\r\n * - 601-1000 ms: \u8FDB\u4E00\u6B65\u538B\u7F29\uFF08\u8F93\u51FA 200-240\uFF09\r\n * \r\n * @param x - \u8F93\u5165\u6CE2\u5F62\u9891\u7387\uFF08ms\uFF09\r\n * @returns \u8BBE\u5907\u8F93\u51FA\u503C\uFF0810-240\uFF09\r\n */\r\nexport function getOutputValue(x: number): number {\r\n let output: number;\r\n \r\n if (x >= 10 && x <= 100) {\r\n output = x;\r\n } else if (x > 100 && x <= 600) {\r\n output = (x - 100) / 5 + 100;\r\n } else if (x > 600 && x <= 1000) {\r\n output = (x - 600) / 10 + 200;\r\n } else if (x < 10) {\r\n output = 10;\r\n } else {\r\n output = 240;\r\n }\r\n \r\n // \u9650\u5236\u5728\u6709\u6548\u8303\u56F4\u5185\uFF0810-240\uFF09\r\n return Math.max(10, Math.min(240, Math.round(output)));\r\n}\r\n\r\n/**\r\n * \u9A8C\u8BC1 HEX \u6CE2\u5F62\u683C\u5F0F\r\n * \r\n * \u68C0\u67E5\u5B57\u7B26\u4E32\u662F\u5426\u4E3A\u6709\u6548\u7684 HEX \u6CE2\u5F62\u683C\u5F0F\uFF1A16 \u4E2A\u5341\u516D\u8FDB\u5236\u5B57\u7B26\uFF088 \u5B57\u8282\uFF09\u3002\r\n * \u6BCF\u4E2A HEX \u6CE2\u5F62\u5305\u542B 4 \u4E2A\u9891\u7387\u503C\u548C 4 \u4E2A\u5F3A\u5EA6\u503C\uFF0C\u5BF9\u5E94 100ms \u7684\u8F93\u51FA\u3002\r\n * \r\n * @param hex - \u5F85\u9A8C\u8BC1\u7684 HEX \u5B57\u7B26\u4E32\r\n * @returns \u662F\u5426\u4E3A\u6709\u6548\u7684 HEX \u6CE2\u5F62\r\n */\r\nexport function isValidHexWaveform(hex: string): boolean {\r\n return /^[0-9a-fA-F]{16}$/.test(hex);\r\n}\r\n\r\n\r\n// ============================================================================\r\n// \u89E3\u6790\u51FD\u6570\r\n// ============================================================================\r\n\r\n/**\r\n * \u89E3\u6790 Dungeonlab+pulse: \u6587\u672C\u683C\u5F0F\u7684\u6CE2\u5F62\u6570\u636E\r\n * \r\n * \u8FD9\u662F\u6CE2\u5F62\u89E3\u6790\u7684\u4E3B\u5165\u53E3\u51FD\u6570\u3002\u5B83\u5C06 APP \u5BFC\u51FA\u7684\u6587\u672C\u683C\u5F0F\u6CE2\u5F62\u6570\u636E\r\n * \u89E3\u6790\u4E3A\u7ED3\u6784\u5316\u7684 ParsedWaveform \u5BF9\u8C61\uFF0C\u5E76\u751F\u6210\u53EF\u76F4\u63A5\u53D1\u9001\u5230\u8BBE\u5907\u7684 HEX \u6CE2\u5F62\u3002\r\n * \r\n * \u89E3\u6790\u6D41\u7A0B\uFF1A\r\n * 1. \u9A8C\u8BC1\u683C\u5F0F\u524D\u7F00\r\n * 2. \u63D0\u53D6\u5168\u5C40\u8BBE\u7F6E\uFF08\u4F11\u606F\u65F6\u957F\u3001\u64AD\u653E\u901F\u7387\u3001\u9891\u7387\u5E73\u8861\uFF09\r\n * 3. \u89E3\u6790\u5404\u5C0F\u8282\uFF08\u9891\u7387\u8303\u56F4\u3001\u65F6\u957F\u3001\u9891\u7387\u6A21\u5F0F\u3001\u5F62\u72B6\u6570\u636E\uFF09\r\n * 4. \u8F6C\u6362\u4E3A HEX \u6CE2\u5F62\u6570\u7EC4\r\n * \r\n * @param data - \u6CE2\u5F62\u6570\u636E\u5B57\u7B26\u4E32\uFF0C\u5FC5\u987B\u4EE5 'Dungeonlab+pulse:' \u5F00\u5934\r\n * @param name - \u6CE2\u5F62\u540D\u79F0\uFF0C\u7528\u4E8E\u6807\u8BC6\u548C\u5B58\u50A8\r\n * @returns \u89E3\u6790\u540E\u7684\u5B8C\u6574\u6CE2\u5F62\u5BF9\u8C61\r\n * @throws Error \u5F53\u683C\u5F0F\u65E0\u6548\u6216\u6570\u636E\u4E0D\u5B8C\u6574\u65F6\r\n * \r\n * @example\r\n * const waveform = parseWaveform(\r\n * \"Dungeonlab+pulse:18,1,8=27,7,32,3,1/0-1,50-0,100-1\",\r\n * \"\u6211\u7684\u6CE2\u5F62\"\r\n * );\r\n * console.log(waveform.hexWaveforms); // \u53EF\u76F4\u63A5\u53D1\u9001\u5230\u8BBE\u5907\r\n */\r\nexport function parseWaveform(data: string, name: string): ParsedWaveform {\r\n // \u9A8C\u8BC1\u683C\u5F0F\r\n if (!data.startsWith(\"Dungeonlab+pulse:\")) {\r\n throw new WaveformError(\"\u65E0\u6548\u7684\u6CE2\u5F62\u683C\u5F0F: \u5FC5\u987B\u4EE5 'Dungeonlab+pulse:' \u5F00\u5934\", {\r\n code: ErrorCode.WAVEFORM_INVALID_FORMAT,\r\n context: { name, prefix: data.substring(0, 20) },\r\n });\r\n }\r\n\r\n // \u79FB\u9664\u524D\u7F00\r\n const cleanData = data.replace(/^Dungeonlab\\+pulse:/i, \"\");\r\n \r\n // \u6309 +section+ \u5206\u5272\u83B7\u53D6\u5404\u5C0F\u8282\r\n const sectionParts = cleanData.split(\"+section+\");\r\n \r\n if (sectionParts.length === 0 || !sectionParts[0]) {\r\n throw new WaveformError(\"\u65E0\u6548\u7684\u6CE2\u5F62\u6570\u636E: \u672A\u627E\u5230\u5C0F\u8282\", {\r\n code: ErrorCode.WAVEFORM_PARSE_FAILED,\r\n context: { name },\r\n });\r\n }\r\n\r\n // \u89E3\u6790\u5168\u5C40\u8BBE\u7F6E\u548C\u7B2C\u4E00\u4E2A\u5C0F\u8282\r\n const firstPart = sectionParts[0];\r\n const equalIdx = firstPart.indexOf(\"=\");\r\n \r\n if (equalIdx === -1) {\r\n throw new WaveformError(\"\u65E0\u6548\u7684\u6CE2\u5F62\u683C\u5F0F: \u7F3A\u5C11\u5168\u5C40\u8BBE\u7F6E\u7684 '=' \u5206\u9694\u7B26\", {\r\n code: ErrorCode.WAVEFORM_INVALID_FORMAT,\r\n context: { name },\r\n });\r\n }\r\n\r\n // \u89E3\u6790\u5168\u5C40\u8BBE\u7F6E: sectionRestTime,playbackSpeed,frequencyBalance\r\n const settingsPart = firstPart.substring(0, equalIdx);\r\n const settingsValues = settingsPart.split(\",\");\r\n \r\n const globalSettings: WaveformGlobalSettings = {\r\n sectionRestTime: Number(settingsValues[0]) || 0,\r\n playbackSpeed: Number(settingsValues[1]) || 1,\r\n frequencyBalance: Number(settingsValues[2]) || 8,\r\n };\r\n\r\n // \u89E3\u6790\u5404\u5C0F\u8282\r\n const sections: WaveformSection[] = [];\r\n const startFrequencyIndices: number[] = [];\r\n const endFrequencyIndices: number[] = [];\r\n const durationIndices: number[] = [];\r\n const frequencyModes: number[] = [];\r\n const sectionEnabled: boolean[] = [];\r\n\r\n // \u7B2C\u4E00\u4E2A\u5C0F\u8282\u6570\u636E\u5728 '=' \u4E4B\u540E\r\n const firstSectionData = firstPart.substring(equalIdx + 1);\r\n const allSectionData = [firstSectionData, ...sectionParts.slice(1)];\r\n\r\n for (let i = 0; i < allSectionData.length && i < 10; i++) {\r\n const sectionData = allSectionData[i];\r\n if (!sectionData) continue;\r\n \r\n // \u6309 '/' \u5206\u5272\uFF0C\u5206\u79BB\u5934\u90E8\u548C\u5F62\u72B6\u6570\u636E\r\n const slashIdx = sectionData.indexOf(\"/\");\r\n if (slashIdx === -1) {\r\n throw new WaveformError(`\u65E0\u6548\u7684\u5C0F\u8282 ${i + 1}: \u7F3A\u5C11 '/' \u5206\u9694\u7B26`, {\r\n code: ErrorCode.WAVEFORM_INVALID_FORMAT,\r\n context: { name, sectionIndex: i + 1 },\r\n });\r\n }\r\n\r\n const headerPart = sectionData.substring(0, slashIdx);\r\n const shapePart = sectionData.substring(slashIdx + 1);\r\n\r\n // \u89E3\u6790\u5C0F\u8282\u5934\u90E8: freqRange1,freqRange2,duration,freqMode,enabled\r\n const headerValues = headerPart.split(\",\");\r\n \r\n const freqRange1Index = Number(headerValues[0]) || 0;\r\n const freqRange2Index = Number(headerValues[1]) || 0;\r\n const durationIndex = Number(headerValues[2]) || 0;\r\n const freqMode = Number(headerValues[3]) || 1;\r\n const enabled = headerValues[4] !== \"0\";\r\n\r\n startFrequencyIndices.push(freqRange1Index);\r\n endFrequencyIndices.push(freqRange2Index);\r\n durationIndices.push(durationIndex);\r\n frequencyModes.push(freqMode);\r\n sectionEnabled.push(enabled);\r\n\r\n // \u89E3\u6790\u5F62\u72B6\u6570\u636E: strength-anchor,strength-anchor,...\r\n const shapePoints: WaveformShapePoint[] = [];\r\n const shapeItems = shapePart.split(\",\");\r\n \r\n for (const item of shapeItems) {\r\n if (!item) continue;\r\n const [strengthStr, anchorStr] = item.split(\"-\");\r\n const strength = Math.round(Number(strengthStr) || 0);\r\n const isAnchor = anchorStr === \"1\";\r\n \r\n shapePoints.push({\r\n strength: Math.max(0, Math.min(100, strength)),\r\n isAnchor,\r\n shapeType: isAnchor ? 1 : 0, // \u517C\u5BB9\u6027\r\n });\r\n }\r\n\r\n // \u9A8C\u8BC1\u5F62\u72B6\u6570\u636E\r\n if (shapePoints.length < 2) {\r\n throw new WaveformError(`\u65E0\u6548\u7684\u5C0F\u8282 ${i + 1}: \u5FC5\u987B\u81F3\u5C11\u6709 2 \u4E2A\u5F62\u72B6\u70B9`, {\r\n code: ErrorCode.WAVEFORM_INVALID_FORMAT,\r\n context: { name, sectionIndex: i + 1, shapePointCount: shapePoints.length },\r\n });\r\n }\r\n\r\n // \u83B7\u53D6\u8BA1\u7B97\u503C\r\n const startFreq = getFrequencyFromIndex(freqRange1Index);\r\n const endFreq = getFrequencyFromIndex(freqRange2Index);\r\n const duration = getDurationFromIndex(durationIndex);\r\n\r\n if (enabled) {\r\n sections.push({\r\n index: i,\r\n enabled: true,\r\n frequencyRange1Index: freqRange1Index,\r\n frequencyRange2Index: freqRange2Index,\r\n durationIndex,\r\n frequencyMode: freqMode,\r\n shape: shapePoints,\r\n startFrequency: startFreq,\r\n endFrequency: endFreq,\r\n duration,\r\n });\r\n }\r\n }\r\n\r\n if (sections.length === 0) {\r\n throw new WaveformError(\"\u65E0\u6548\u7684\u6CE2\u5F62\u6570\u636E: \u6CA1\u6709\u542F\u7528\u7684\u5C0F\u8282\", {\r\n code: ErrorCode.WAVEFORM_PARSE_FAILED,\r\n context: { name },\r\n });\r\n }\r\n\r\n // \u6784\u5EFA\u517C\u5BB9\u6027\u5143\u6570\u636E\r\n const metadata: WaveformMetadata = {\r\n globalSettings,\r\n startFrequencyIndices,\r\n endFrequencyIndices,\r\n durationIndices,\r\n frequencyModes,\r\n sectionEnabled,\r\n // \u517C\u5BB9\u6027\u5B57\u6BB5\r\n startFrequencies: [\r\n getFrequencyFromIndex(startFrequencyIndices[0] ?? 0),\r\n getFrequencyFromIndex(startFrequencyIndices[1] ?? 0),\r\n getFrequencyFromIndex(startFrequencyIndices[2] ?? 0),\r\n ],\r\n endFrequencies: [\r\n getFrequencyFromIndex(endFrequencyIndices[0] ?? 0),\r\n getFrequencyFromIndex(endFrequencyIndices[1] ?? 0),\r\n getFrequencyFromIndex(endFrequencyIndices[2] ?? 0),\r\n ],\r\n durations: [\r\n getDurationFromIndex(durationIndices[0] ?? 0),\r\n getDurationFromIndex(durationIndices[1] ?? 0),\r\n getDurationFromIndex(durationIndices[2] ?? 0),\r\n ],\r\n frequencyModes_legacy: [\r\n frequencyModes[0] ?? 1,\r\n frequencyModes[1] ?? 1,\r\n frequencyModes[2] ?? 1,\r\n ] as [number, number, number],\r\n section2Enabled: sectionEnabled[1] ?? false,\r\n section3Enabled: sectionEnabled[2] ?? false,\r\n playbackSpeed: globalSettings.playbackSpeed,\r\n };\r\n\r\n // \u4ECE\u5C0F\u8282\u751F\u6210 HEX \u6CE2\u5F62\r\n const hexWaveforms = convertToHexWaveforms(sections, globalSettings.playbackSpeed);\r\n\r\n return {\r\n name,\r\n metadata,\r\n sections,\r\n rawData: data,\r\n hexWaveforms,\r\n createdAt: new Date(),\r\n };\r\n}\r\n\r\n/**\r\n * \u5C06\u5C0F\u8282\u8F6C\u6362\u4E3A\u8BBE\u5907\u7528\u7684 HEX \u6CE2\u5F62\r\n * \r\n * \u6838\u5FC3\u6982\u5FF5\uFF1A\r\n * - \u6BCF\u4E2A\u5F62\u72B6\u70B9 = 100ms \u7684\u8F93\u51FA\u5F3A\u5EA6\uFF084 \u4E2A 25ms \u91C7\u6837\uFF09\r\n * - \u8109\u51B2\u5143 = \u6240\u6709\u5F62\u72B6\u70B9\u7EC4\u6210\u7684\u4E00\u4E2A\u5B8C\u6574\u6CE2\u5F62\u5468\u671F\r\n * - \u5C0F\u8282 = \u8109\u51B2\u5143\u5FAA\u73AF\u91CD\u590D\u64AD\u653E\uFF0C\u76F4\u5230\u5C0F\u8282\u65F6\u957F\u7ED3\u675F\r\n * - \u8109\u51B2\u5143\u4F1A\u5B8C\u6574\u64AD\u653E\uFF0C\u5373\u4F7F\u8D85\u8FC7\u8BBE\u5B9A\u7684\u5C0F\u8282\u65F6\u957F\r\n * \r\n * \u6BCF\u4E2A HEX \u6CE2\u5F62\u662F 16 \u4E2A\u5B57\u7B26\uFF088 \u5B57\u8282\uFF09:\r\n * - 4 \u5B57\u8282: \u9891\u7387\u503C\uFF084 x 25ms = 100ms\uFF09\r\n * - 4 \u5B57\u8282: \u5F3A\u5EA6\u503C\uFF084 x 25ms = 100ms\uFF09\r\n * \r\n * @param sections - \u5C0F\u8282\u6570\u7EC4\r\n * @param _playbackSpeed - \u64AD\u653E\u901F\u7387\uFF08\u4FDD\u7559\u53C2\u6570\uFF0C\u5F53\u524D\u672A\u4F7F\u7528\uFF09\r\n * @returns HEX \u6CE2\u5F62\u6570\u7EC4\r\n */\r\nfunction convertToHexWaveforms(sections: WaveformSection[], _playbackSpeed: number = 1): string[] {\r\n const hexWaveforms: string[] = [];\r\n\r\n for (const section of sections) {\r\n if (section.shape.length === 0) continue;\r\n\r\n const shapeCount = section.shape.length; // \u8109\u51B2\u5143\u7684\u5F62\u72B6\u70B9\u6570\u91CF\r\n const pulseElementDuration = shapeCount; // \u8109\u51B2\u5143\u65F6\u957F = \u5F62\u72B6\u70B9\u6570 x 100ms\r\n const sectionDuration = section.duration; // \u5C0F\u8282\u8BBE\u5B9A\u65F6\u957F\uFF08\u5355\u4F4D 100ms\uFF09\r\n const startFreq = section.startFrequency;\r\n const endFreq = section.endFrequency;\r\n const freqMode = section.frequencyMode;\r\n\r\n // \u8BA1\u7B97\u9700\u8981\u591A\u5C11\u4E2A\u5B8C\u6574\u7684\u8109\u51B2\u5143\u6765\u8986\u76D6\u5C0F\u8282\u65F6\u957F\r\n // \u8109\u51B2\u5143\u603B\u662F\u4F1A\u5B8C\u6574\u64AD\u653E\uFF0C\u5373\u4F7F\u8D85\u8FC7\u8BBE\u5B9A\u65F6\u957F\r\n const pulseElementCount = Math.max(1, Math.ceil(sectionDuration / pulseElementDuration));\r\n const actualDuration = pulseElementCount * pulseElementDuration; // \u5B9E\u9645\u64AD\u653E\u65F6\u957F\r\n\r\n const waveformFreq: number[] = [];\r\n const waveformStrength: number[] = [];\r\n\r\n // \u904D\u5386\u6BCF\u4E2A\u8109\u51B2\u5143\r\n for (let elementIdx = 0; elementIdx < pulseElementCount; elementIdx++) {\r\n // \u904D\u5386\u8109\u51B2\u5143\u5185\u7684\u6BCF\u4E2A\u5F62\u72B6\u70B9\uFF08\u6BCF\u4E2A\u5F62\u72B6\u70B9 = 100ms\uFF09\r\n for (let shapeIdx = 0; shapeIdx < shapeCount; shapeIdx++) {\r\n const currentPoint = section.shape[shapeIdx];\r\n const strength = currentPoint?.strength ?? 0;\r\n\r\n // \u5F53\u524D\u5728\u6574\u4E2A\u5C0F\u8282\u4E2D\u7684\u65F6\u95F4\u4F4D\u7F6E\uFF08\u5355\u4F4D 100ms\uFF09\r\n const currentTime = elementIdx * pulseElementDuration + shapeIdx;\r\n // \u5C0F\u8282\u5185\u7684\u8FDB\u5EA6\uFF080-1\uFF09\r\n const sectionProgress = currentTime / actualDuration;\r\n // \u8109\u51B2\u5143\u5185\u7684\u8FDB\u5EA6\uFF080-1\uFF09\r\n const elementProgress = shapeIdx / shapeCount;\r\n\r\n // \u6839\u636E\u9891\u7387\u6A21\u5F0F\u8BA1\u7B97\u9891\u7387\r\n let freq: number;\r\n switch (freqMode) {\r\n case 1: // \u56FA\u5B9A - \u4F7F\u7528\u8D77\u59CB\u9891\u7387\r\n freq = getOutputValue(startFreq);\r\n break;\r\n case 2: // \u8282\u5185\u6E10\u53D8 - \u6574\u4E2A\u5C0F\u8282\u5185\u9891\u7387\u4ECE startFreq \u6E10\u53D8\u5230 endFreq\r\n freq = getOutputValue(startFreq + (endFreq - startFreq) * sectionProgress);\r\n break;\r\n case 3: // \u5143\u5185\u6E10\u53D8 - \u6BCF\u4E2A\u8109\u51B2\u5143\u5185\u9891\u7387\u4ECE startFreq \u6E10\u53D8\u5230 endFreq\uFF0C\u7136\u540E\u91CD\u7F6E\r\n freq = getOutputValue(startFreq + (endFreq - startFreq) * elementProgress);\r\n break;\r\n case 4: // \u5143\u95F4\u6E10\u53D8 - \u8109\u51B2\u5143\u5185\u9891\u7387\u56FA\u5B9A\uFF0C\u4F46\u4ECE\u7B2C\u4E00\u4E2A\u8109\u51B2\u5143\u5230\u6700\u540E\u4E00\u4E2A\u8109\u51B2\u5143\u9891\u7387\u6E10\u53D8\r\n {\r\n const elementProgress4 = pulseElementCount > 1 \r\n ? elementIdx / (pulseElementCount - 1) \r\n : 0;\r\n freq = getOutputValue(startFreq + (endFreq - startFreq) * elementProgress4);\r\n }\r\n break;\r\n default:\r\n freq = getOutputValue(startFreq);\r\n }\r\n\r\n // \u6BCF\u4E2A\u5F62\u72B6\u70B9\u751F\u6210 4 \u4E2A 25ms \u91C7\u6837\uFF08\u5F3A\u5EA6\u76F8\u540C\uFF0C\u9891\u7387\u76F8\u540C\uFF09\r\n for (let n = 0; n < 4; n++) {\r\n waveformStrength.push(Math.max(0, Math.min(100, Math.round(strength))));\r\n waveformFreq.push(Math.round(freq));\r\n }\r\n }\r\n }\r\n\r\n // \u7EC4\u5408\u6210 8 \u5B57\u8282 HEX \u5B57\u7B26\u4E32\uFF08\u6BCF 100ms = 4 \u4E2A\u9891\u7387 + 4 \u4E2A\u5F3A\u5EA6\uFF09\r\n for (let i = 0; i < waveformFreq.length; i += 4) {\r\n const freqHex = [\r\n waveformFreq[i] ?? 10,\r\n waveformFreq[i + 1] ?? 10,\r\n waveformFreq[i + 2] ?? 10,\r\n waveformFreq[i + 3] ?? 10,\r\n ]\r\n .map((v) => Math.max(10, Math.min(240, v)).toString(16).padStart(2, \"0\"))\r\n .join(\"\");\r\n\r\n const strengthHex = [\r\n waveformStrength[i] ?? 0,\r\n waveformStrength[i + 1] ?? 0,\r\n waveformStrength[i + 2] ?? 0,\r\n waveformStrength[i + 3] ?? 0,\r\n ]\r\n .map((v) => Math.max(0, Math.min(100, v)).toString(16).padStart(2, \"0\"))\r\n .join(\"\");\r\n\r\n hexWaveforms.push(freqHex + strengthHex);\r\n }\r\n }\r\n\r\n return hexWaveforms;\r\n}\r\n\r\n/**\r\n * \u5C06\u6CE2\u5F62\u7F16\u7801\u56DE Dungeonlab+pulse: \u6587\u672C\u683C\u5F0F\r\n * \r\n * \u8FD9\u662F parseWaveform \u7684\u9006\u64CD\u4F5C\uFF0C\u7528\u4E8E\u5C06\u89E3\u6790\u540E\u7684\u6CE2\u5F62\u91CD\u65B0\u7F16\u7801\u4E3A\u6587\u672C\u683C\u5F0F\u3002\r\n * \u4E3B\u8981\u7528\u4E8E\u5F80\u8FD4\u6D4B\u8BD5\uFF08round-trip testing\uFF09\u9A8C\u8BC1\u89E3\u6790\u5668\u7684\u6B63\u786E\u6027\u3002\r\n * \r\n * \u6CE8\u610F\uFF1A\u7531\u4E8E\u6D6E\u70B9\u6570\u7CBE\u5EA6\u548C\u683C\u5F0F\u5316\u5DEE\u5F02\uFF0C\u7F16\u7801\u540E\u7684\u5B57\u7B26\u4E32\u53EF\u80FD\u4E0E\u539F\u59CB\u8F93\u5165\u7565\u6709\u4E0D\u540C\uFF0C\r\n * \u4F46\u89E3\u6790\u540E\u5E94\u8BE5\u4EA7\u751F\u7B49\u6548\u7684\u6CE2\u5F62\u6570\u636E\u3002\r\n * \r\n * @param waveform - \u89E3\u6790\u540E\u7684\u6CE2\u5F62\u5BF9\u8C61\r\n * @returns \u7F16\u7801\u540E\u7684 Dungeonlab+pulse: \u683C\u5F0F\u5B57\u7B26\u4E32\r\n */\r\nexport function encodeWaveform(waveform: ParsedWaveform): string {\r\n const { metadata, sections } = waveform;\r\n const { globalSettings } = metadata;\r\n\r\n // \u6784\u5EFA\u5168\u5C40\u8BBE\u7F6E\u90E8\u5206\r\n const settingsPart = [\r\n globalSettings.sectionRestTime,\r\n globalSettings.playbackSpeed,\r\n globalSettings.frequencyBalance,\r\n ].join(\",\");\r\n\r\n // \u6784\u5EFA\u5C0F\u8282\u5B57\u7B26\u4E32\r\n const sectionStrings: string[] = [];\r\n\r\n // \u4F7F\u7528\u539F\u59CB\u7D22\u5F15\uFF08\u5982\u679C\u53EF\u7528\uFF09\uFF0C\u5426\u5219\u4ECE\u5C0F\u8282\u91CD\u5EFA\r\n const allSectionCount = Math.max(\r\n metadata.startFrequencyIndices.length,\r\n sections.length\r\n );\r\n\r\n for (let i = 0; i < allSectionCount; i++) {\r\n const section = sections.find(s => s.index === i);\r\n \r\n let headerPart: string;\r\n let shapePart: string;\r\n\r\n if (section) {\r\n // \u4ECE\u5C0F\u8282\u6570\u636E\u6784\u5EFA\u5934\u90E8\r\n headerPart = [\r\n section.frequencyRange1Index,\r\n section.frequencyRange2Index,\r\n section.durationIndex,\r\n section.frequencyMode,\r\n section.enabled ? 1 : 0,\r\n ].join(\",\");\r\n\r\n // \u6784\u5EFA\u5F62\u72B6\u6570\u636E\r\n shapePart = section.shape\r\n .map((p) => `${p.strength}-${p.isAnchor ? 1 : 0}`)\r\n .join(\",\");\r\n } else {\r\n // \u5BF9\u7981\u7528\u7684\u5C0F\u8282\u4F7F\u7528\u5143\u6570\u636E\u7D22\u5F15\r\n headerPart = [\r\n metadata.startFrequencyIndices[i] ?? 0,\r\n metadata.endFrequencyIndices[i] ?? 0,\r\n metadata.durationIndices[i] ?? 0,\r\n metadata.frequencyModes[i] ?? 1,\r\n 0, // \u7981\u7528\r\n ].join(\",\");\r\n\r\n // \u7981\u7528\u5C0F\u8282\u7684\u6700\u5C0F\u5F62\u72B6\u6570\u636E\r\n shapePart = \"0-1,100-1\";\r\n }\r\n\r\n sectionStrings.push(`${headerPart}/${shapePart}`);\r\n }\r\n\r\n // \u7EC4\u5408: settings=firstSection+section+secondSection+section+...\r\n const firstSection = sectionStrings[0] ?? \"0,0,0,1,0/0-1,100-1\";\r\n const remainingSections = sectionStrings.slice(1);\r\n\r\n let result = `Dungeonlab+pulse:${settingsPart}=${firstSection}`;\r\n \r\n for (const section of remainingSections) {\r\n result += `+section+${section}`;\r\n }\r\n\r\n return result;\r\n}\r\n", "/**\r\n * @fileoverview \u6CE2\u5F62\u5DE5\u5177\r\n * @description MCP \u6CE2\u5F62\u7BA1\u7406\u5DE5\u5177\r\n * - dg_parse_waveform: \u89E3\u6790\u6CE2\u5F62\u6570\u636E\u5E76\u4FDD\u5B58\r\n * - dg_list_waveforms: \u5217\u51FA\u6240\u6709\u4FDD\u5B58\u7684\u6CE2\u5F62\r\n * - dg_get_waveform: \u6309\u540D\u79F0\u83B7\u53D6\u6CE2\u5F62\r\n * - dg_delete_waveform: \u6309\u540D\u79F0\u5220\u9664\u6CE2\u5F62\r\n */\r\n\r\nimport type { Tool, ToolResult, ToolHandler } from \"../tool-manager\";\r\nimport { WaveformStorage, persistWaveforms } from \"../waveform-storage\";\r\nimport { parseWaveform } from \"../waveform-parser\";\r\n\r\n/**\r\n * \u5E26\u5904\u7406\u51FD\u6570\u7684\u5DE5\u5177\u7C7B\u578B\uFF08\u5185\u90E8\u4F7F\u7528\uFF09\r\n */\r\nexport interface ToolWithHandler extends Tool {\r\n handler: ToolHandler;\r\n}\r\n\r\n/** \u5171\u4EAB\u7684\u6CE2\u5F62\u5B58\u50A8\u5B9E\u4F8B */\r\nlet waveformStorage: WaveformStorage | null = null;\r\n/** \u5B58\u50A8\u8DEF\u5F84 */\r\nlet storagePath = \"./data/waveforms.json\";\r\n\r\n/**\r\n * \u521D\u59CB\u5316\u6CE2\u5F62\u5B58\u50A8\r\n * @param storage - \u6CE2\u5F62\u5B58\u50A8\u5B9E\u4F8B\uFF08\u53EF\u9009\uFF09\r\n * @param path - \u5B58\u50A8\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09\r\n */\r\nexport function initWaveformStorage(storage?: WaveformStorage, path?: string): void {\r\n waveformStorage = storage || new WaveformStorage();\r\n if (path) storagePath = path;\r\n}\r\n\r\n/**\r\n * \u83B7\u53D6\u6CE2\u5F62\u5B58\u50A8\u5B9E\u4F8B\r\n * @returns \u6CE2\u5F62\u5B58\u50A8\u5B9E\u4F8B\r\n */\r\nexport function getWaveformStorage(): WaveformStorage {\r\n if (!waveformStorage) {\r\n waveformStorage = new WaveformStorage();\r\n }\r\n return waveformStorage;\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA\u9519\u8BEF\u7ED3\u679C\r\n * @param message - \u9519\u8BEF\u6D88\u606F\r\n * @returns \u5DE5\u5177\u7ED3\u679C\r\n */\r\nfunction createToolError(message: string): ToolResult {\r\n return {\r\n content: [{ type: \"text\", text: `Error: ${message}` }],\r\n isError: true,\r\n };\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA\u6210\u529F\u7ED3\u679C\r\n * @param data - \u6570\u636E\r\n * @returns \u5DE5\u5177\u7ED3\u679C\r\n */\r\nfunction createToolSuccess(data: unknown): ToolResult {\r\n return {\r\n content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }],\r\n };\r\n}\r\n\r\n/**\r\n * dg_parse_waveform \u5DE5\u5177\r\n * \u89E3\u6790\u6CE2\u5F62\u6570\u636E\u5E76\u4FDD\u5B58\r\n */\r\nexport const dgParseWaveformTool: ToolWithHandler = {\r\n name: \"dg_parse_waveform\",\r\n description: `\u89E3\u6790\u5E76\u4FDD\u5B58\u6CE2\u5F62\u6570\u636E\u3002\r\n\u8F93\u5165\u683C\u5F0F\uFF1ADungeonlab+pulse:\u5F00\u5934\u7684Base64\u7F16\u7801\u5B57\u7B26\u4E32\uFF08\u4ECEDG-LAB APP\u5BFC\u51FA\uFF09\u3002\r\n\u89E3\u6790\u540E\u4FDD\u5B58\u4E3AhexWaveforms\u6570\u7EC4\uFF0C\u53EF\u901A\u8FC7dg_get_waveform\u83B7\u53D6\u5E76\u7528dg_send_waveform\u53D1\u9001\u3002\r\n\u5982\u679Cname\u5DF2\u5B58\u5728\u4F1A\u8986\u76D6\u539F\u6709\u6CE2\u5F62\u3002`,\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n hexData: {\r\n type: \"string\",\r\n description: \"\u6CE2\u5F62\u6570\u636E\uFF08Dungeonlab+pulse:\u683C\u5F0F\u6587\u672C\uFF09\",\r\n },\r\n name: {\r\n type: \"string\",\r\n description: \"\u6CE2\u5F62\u540D\u79F0\uFF0C\u7528\u4E8E\u4FDD\u5B58\u548C\u540E\u7EED\u5F15\u7528\",\r\n },\r\n },\r\n required: [\"hexData\", \"name\"],\r\n },\r\n handler: async (params: Record<string, unknown>): Promise<ToolResult> => {\r\n const hexData = params.hexData as string | undefined;\r\n const name = params.name as string | undefined;\r\n\r\n if (!hexData || typeof hexData !== \"string\") {\r\n return createToolError(\"hexData \u662F\u5FC5\u9700\u7684\u4E14\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\");\r\n }\r\n\r\n if (!name || typeof name !== \"string\" || name.trim().length === 0) {\r\n return createToolError(\"name \u662F\u5FC5\u9700\u7684\u4E14\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\");\r\n }\r\n\r\n try {\r\n const waveform = parseWaveform(hexData, name.trim());\r\n\r\n // \u4FDD\u5B58\u5230\u5B58\u50A8\r\n const storage = getWaveformStorage();\r\n const existed = storage.has(name.trim());\r\n storage.save(waveform);\r\n\r\n // \u6301\u4E45\u5316\u5230\u78C1\u76D8\r\n persistWaveforms(storage, storagePath);\r\n\r\n return createToolSuccess({\r\n success: true,\r\n name: waveform.name,\r\n overwritten: existed,\r\n hexWaveformCount: waveform.hexWaveforms.length,\r\n });\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n return createToolError(error.message);\r\n }\r\n return createToolError(\"\u89E3\u6790\u6CE2\u5F62\u6570\u636E\u5931\u8D25\");\r\n }\r\n },\r\n};\r\n\r\n/**\r\n * dg_list_waveforms \u5DE5\u5177\r\n * \u5217\u51FA\u6240\u6709\u4FDD\u5B58\u7684\u6CE2\u5F62\r\n */\r\nexport const dgListWaveformsTool: ToolWithHandler = {\r\n name: \"dg_list_waveforms\",\r\n description: `\u5217\u51FA\u6240\u6709\u5DF2\u4FDD\u5B58\u7684\u6CE2\u5F62\u540D\u79F0\u548C\u6570\u636E\u91CF\u3002\r\n\u8FD4\u56DE\u6BCF\u4E2A\u6CE2\u5F62\u7684name\u548ChexWaveformCount\uFF08\u6CE2\u5F62\u6570\u636E\u6761\u6570\uFF09\u3002\r\n\u7528\u4E8E\u67E5\u770B\u53EF\u7528\u6CE2\u5F62\uFF0C\u7136\u540E\u901A\u8FC7dg_get_waveform\u83B7\u53D6\u5177\u4F53\u6570\u636E\u3002`,\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {},\r\n required: [],\r\n },\r\n handler: async (): Promise<ToolResult> => {\r\n const storage = getWaveformStorage();\r\n const waveforms = storage.list();\r\n\r\n const list = waveforms.map((w) => ({\r\n name: w.name,\r\n hexWaveformCount: w.hexWaveforms.length,\r\n }));\r\n\r\n return createToolSuccess({\r\n count: list.length,\r\n waveforms: list,\r\n });\r\n },\r\n};\r\n\r\n/**\r\n * dg_get_waveform \u5DE5\u5177\r\n * \u6309\u540D\u79F0\u83B7\u53D6\u6CE2\u5F62\r\n */\r\nexport const dgGetWaveformTool: ToolWithHandler = {\r\n name: \"dg_get_waveform\",\r\n description: `\u6309\u540D\u79F0\u83B7\u53D6\u6CE2\u5F62\u7684hexWaveforms\u6570\u7EC4\u3002\r\n\u8FD4\u56DE\u7684hexWaveforms\u53EF\u76F4\u63A5\u4F20\u7ED9dg_send_waveform\u7684waveforms\u53C2\u6570\u4F7F\u7528\u3002\r\n\u5178\u578B\u6D41\u7A0B\uFF1Adg_list_waveforms\u67E5\u770B\u53EF\u7528\u6CE2\u5F62 \u2192 dg_get_waveform\u83B7\u53D6\u6570\u636E \u2192 dg_send_waveform\u53D1\u9001\u5230\u8BBE\u5907\u3002`,\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n name: {\r\n type: \"string\",\r\n description: \"\u6CE2\u5F62\u540D\u79F0\",\r\n },\r\n },\r\n required: [\"name\"],\r\n },\r\n handler: async (params: Record<string, unknown>): Promise<ToolResult> => {\r\n const name = params.name as string | undefined;\r\n\r\n if (!name || typeof name !== \"string\") {\r\n return createToolError(\"name \u662F\u5FC5\u9700\u7684\u4E14\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\");\r\n }\r\n\r\n const storage = getWaveformStorage();\r\n const waveform = storage.get(name);\r\n\r\n if (!waveform) {\r\n return createToolError(`\u6CE2\u5F62\u672A\u627E\u5230: ${name}`);\r\n }\r\n\r\n return createToolSuccess({\r\n name: waveform.name,\r\n hexWaveforms: waveform.hexWaveforms,\r\n });\r\n },\r\n};\r\n\r\n/**\r\n * dg_delete_waveform \u5DE5\u5177\r\n * \u6309\u540D\u79F0\u5220\u9664\u6CE2\u5F62\r\n */\r\nexport const dgDeleteWaveformTool: ToolWithHandler = {\r\n name: \"dg_delete_waveform\",\r\n description: `\u6309\u540D\u79F0\u5220\u9664\u5DF2\u4FDD\u5B58\u7684\u6CE2\u5F62\u3002\r\n\u5220\u9664\u540E\u65E0\u6CD5\u6062\u590D\uFF0C\u9700\u8981\u91CD\u65B0\u7528dg_parse_waveform\u89E3\u6790\u4FDD\u5B58\u3002`,\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n name: {\r\n type: \"string\",\r\n description: \"\u8981\u5220\u9664\u7684\u6CE2\u5F62\u540D\u79F0\",\r\n },\r\n },\r\n required: [\"name\"],\r\n },\r\n handler: async (params: Record<string, unknown>): Promise<ToolResult> => {\r\n const name = params.name as string | undefined;\r\n\r\n if (!name || typeof name !== \"string\") {\r\n return createToolError(\"name \u662F\u5FC5\u9700\u7684\u4E14\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\");\r\n }\r\n\r\n const storage = getWaveformStorage();\r\n\r\n if (!storage.has(name)) {\r\n return createToolError(`\u6CE2\u5F62\u672A\u627E\u5230: ${name}`);\r\n }\r\n\r\n storage.delete(name);\r\n\r\n // \u6301\u4E45\u5316\u5230\u78C1\u76D8\r\n persistWaveforms(storage, storagePath);\r\n\r\n return createToolSuccess({\r\n success: true,\r\n deleted: name,\r\n });\r\n },\r\n};\r\n\r\n/**\r\n * \u83B7\u53D6\u6240\u6709\u6CE2\u5F62\u5DE5\u5177\r\n * @returns \u6CE2\u5F62\u5DE5\u5177\u6570\u7EC4\r\n */\r\nexport function getWaveformTools(): ToolWithHandler[] {\r\n return [\r\n dgParseWaveformTool,\r\n dgListWaveformsTool,\r\n dgGetWaveformTool,\r\n dgDeleteWaveformTool,\r\n ];\r\n}\r\n", "/**\r\n * @fileoverview \u8BBE\u5907\u63A7\u5236\u5DE5\u5177\u96C6\r\n * \r\n * \u63D0\u4F9B DG-LAB \u8BBE\u5907\u7684\u6838\u5FC3\u63A7\u5236\u529F\u80FD\uFF0C\u5305\u62EC\u5F3A\u5EA6\u8C03\u8282\u3001\u6CE2\u5F62\u53D1\u9001\u548C\u72B6\u6001\u67E5\u8BE2\u3002\r\n * \u8FD9\u4E9B\u5DE5\u5177\u9700\u8981\u5728\u8BBE\u5907\u5B8C\u6210\u7ED1\u5B9A\uFF08boundToApp \u4E3A true\uFF09\u540E\u624D\u80FD\u4F7F\u7528\u3002\r\n * \r\n * \u4E3B\u8981\u529F\u80FD\uFF1A\r\n * - dg_set_strength: \u8C03\u8282\u901A\u9053\u8F93\u51FA\u5F3A\u5EA6\r\n * - dg_send_waveform: \u53D1\u9001\u6CE2\u5F62\u6570\u636E\u63A7\u5236\u8F93\u51FA\u6A21\u5F0F\r\n * - dg_clear_waveform: \u6E05\u7A7A\u6CE2\u5F62\u961F\u5217\u505C\u6B62\u8F93\u51FA\r\n * - dg_get_status: \u67E5\u8BE2\u8BBE\u5907\u5B8C\u6574\u72B6\u6001\r\n * \r\n * \u4F7F\u7528\u524D\u63D0\uFF1A\r\n * 1. \u5DF2\u901A\u8FC7 dg_connect \u521B\u5EFA\u8FDE\u63A5\r\n * 2. \u7528\u6237\u5DF2\u7528 APP \u626B\u7801\u5B8C\u6210\u7ED1\u5B9A\r\n * 3. \u901A\u8FC7 dg_get_status \u786E\u8BA4 boundToApp \u4E3A true\r\n */\r\n\r\nimport type { ToolManager } from \"../tool-manager\";\r\nimport { createToolResult, createToolError } from \"../tool-manager\";\r\nimport type { SessionManager } from \"../session-manager\";\r\nimport type { DGLabWSServer } from \"../ws-server\";\r\nimport { getWaveformStorage } from \"./waveform-tools\";\r\nimport { ToolError, ConnectionError, ErrorCode } from \"../errors\";\r\n\r\n/** \r\n * \u5F3A\u5EA6\u8C03\u8282\u6A21\u5F0F\r\n * - increase: \u5728\u5F53\u524D\u503C\u57FA\u7840\u4E0A\u589E\u52A0\r\n * - decrease: \u5728\u5F53\u524D\u503C\u57FA\u7840\u4E0A\u51CF\u5C11\r\n * - set: \u76F4\u63A5\u8BBE\u7F6E\u4E3A\u6307\u5B9A\u503C\r\n */\r\ntype StrengthMode = \"increase\" | \"decrease\" | \"set\";\r\n\r\n// ============================================================\r\n// \u53C2\u6570\u9A8C\u8BC1\u51FD\u6570\r\n// \u8FD9\u4E9B\u51FD\u6570\u63D0\u4F9B\u7EDF\u4E00\u7684\u53C2\u6570\u6821\u9A8C\u903B\u8F91\uFF0C\u8FD4\u56DE\u7C7B\u578B\u5B89\u5168\u7684\u7ED3\u679C\r\n// ============================================================\r\n\r\n/**\r\n * \u89E3\u6790\u8BBE\u5907\u6807\u8BC6\r\n * \r\n * \u652F\u6301\u901A\u8FC7 deviceId \u6216 alias \u67E5\u627E\u8BBE\u5907\u3002\r\n * deviceId \u4F18\u5148\u7EA7\u9AD8\u4E8E alias\u3002\r\n * \r\n * @param sessionManager - \u4F1A\u8BDD\u7BA1\u7406\u5668\r\n * @param deviceId - \u8BBE\u5907 ID\uFF08\u53EF\u9009\uFF09\r\n * @param alias - \u8BBE\u5907\u522B\u540D\uFF08\u53EF\u9009\uFF09\r\n * @returns \u89E3\u6790\u7ED3\u679C\uFF0C\u5305\u542B\u4F1A\u8BDD\u6216\u9519\u8BEF\u4FE1\u606F\r\n */\r\nfunction resolveDevice(\r\n sessionManager: SessionManager,\r\n deviceId?: string,\r\n alias?: string\r\n): { error: string } | { session: NonNullable<ReturnType<SessionManager[\"getSession\"]>> } {\r\n // \u5FC5\u987B\u63D0\u4F9B deviceId \u6216 alias \u4E4B\u4E00\r\n if (!deviceId && !alias) {\r\n return { error: \"\u5FC5\u987B\u63D0\u4F9B deviceId \u6216 alias \u53C2\u6570\u4E4B\u4E00\" };\r\n }\r\n\r\n // deviceId \u4F18\u5148\u7EA7\u9AD8\u4E8E alias\r\n if (deviceId) {\r\n const session = sessionManager.getSession(deviceId);\r\n if (!session) {\r\n return { error: `\u8BBE\u5907\u4E0D\u5B58\u5728: ${deviceId}` };\r\n }\r\n return { session };\r\n }\r\n\r\n // \u901A\u8FC7 alias \u67E5\u627E\r\n const sessions = sessionManager.findByAlias(alias!);\r\n if (sessions.length === 0) {\r\n return { error: `\u672A\u627E\u5230\u522B\u540D\u4E3A \"${alias}\" \u7684\u8BBE\u5907` };\r\n }\r\n if (sessions.length > 1) {\r\n return { error: `\u522B\u540D \"${alias}\" \u5339\u914D\u5230\u591A\u4E2A\u8BBE\u5907 (${sessions.length} \u4E2A)\uFF0C\u8BF7\u4F7F\u7528 deviceId \u6307\u5B9A` };\r\n }\r\n return { session: sessions[0] };\r\n}\r\n\r\n/**\r\n * \u9A8C\u8BC1\u8BBE\u5907 ID \u5E76\u83B7\u53D6\u5BF9\u5E94\u7684\u4F1A\u8BDD\r\n * \r\n * @param sessionManager - \u4F1A\u8BDD\u7BA1\u7406\u5668\u5B9E\u4F8B\r\n * @param deviceId - \u5F85\u9A8C\u8BC1\u7684\u8BBE\u5907 ID\r\n * @returns \u5305\u542B\u9519\u8BEF\u4FE1\u606F\u7684\u5BF9\u8C61\uFF0C\u6216\u5305\u542B\u4F1A\u8BDD\u5BF9\u8C61\u7684\u5BF9\u8C61\r\n * \r\n * @example\r\n * const result = validateDeviceId(sessionManager, params.deviceId);\r\n * if (\"error\" in result) return createToolError(result.error);\r\n * const session = result.session;\r\n */\r\nfunction validateDeviceId(\r\n sessionManager: SessionManager,\r\n deviceId: string | undefined\r\n): { error: string } | { session: ReturnType<SessionManager[\"getSession\"]> } {\r\n if (!deviceId) {\r\n return { error: \"\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: deviceId\" };\r\n }\r\n\r\n const session = sessionManager.getSession(deviceId);\r\n if (!session) {\r\n return { error: `\u8BBE\u5907\u4E0D\u5B58\u5728: ${deviceId}` };\r\n }\r\n\r\n return { session };\r\n}\r\n\r\n/**\r\n * \u9A8C\u8BC1\u901A\u9053\u53C2\u6570\r\n * \r\n * DG-LAB \u8BBE\u5907\u6709\u4E24\u4E2A\u72EC\u7ACB\u7684\u8F93\u51FA\u901A\u9053 A \u548C B\uFF0C\r\n * \u6BCF\u4E2A\u901A\u9053\u53EF\u4EE5\u72EC\u7ACB\u63A7\u5236\u5F3A\u5EA6\u548C\u6CE2\u5F62\u3002\r\n * \r\n * @param channel - \u5F85\u9A8C\u8BC1\u7684\u901A\u9053\u503C\r\n * @returns \u5305\u542B\u9519\u8BEF\u4FE1\u606F\u7684\u5BF9\u8C61\uFF0C\u6216\u5305\u542B\u89C4\u8303\u5316\u901A\u9053\u503C\u7684\u5BF9\u8C61\r\n */\r\nfunction validateChannel(channel: string | undefined): { error: string } | { channel: \"A\" | \"B\" } {\r\n if (!channel) {\r\n return { error: \"\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: channel\" };\r\n }\r\n if (channel !== \"A\" && channel !== \"B\") {\r\n return { error: `\u65E0\u6548\u7684\u901A\u9053: ${channel}\uFF0C\u5FC5\u987B\u662F \"A\" \u6216 \"B\"` };\r\n }\r\n return { channel };\r\n}\r\n\r\n/**\r\n * \u9A8C\u8BC1\u5F3A\u5EA6\u503C\r\n * \r\n * \u5F3A\u5EA6\u503C\u8303\u56F4\u4E3A 0-200\uFF0C\u4F46\u5B9E\u9645\u53EF\u7528\u8303\u56F4\u53D7 APP \u8BBE\u7F6E\u7684\u4E0A\u9650\u9650\u5236\u3002\r\n * \u8D85\u8FC7\u4E0A\u9650\u7684\u503C\u4F1A\u88AB\u8BBE\u5907\u81EA\u52A8\u622A\u65AD\u3002\r\n * \r\n * @param value - \u5F85\u9A8C\u8BC1\u7684\u5F3A\u5EA6\u503C\r\n * @returns \u5305\u542B\u9519\u8BEF\u4FE1\u606F\u7684\u5BF9\u8C61\uFF0C\u6216\u5305\u542B\u6570\u503C\u7C7B\u578B\u5F3A\u5EA6\u503C\u7684\u5BF9\u8C61\r\n */\r\nfunction validateStrengthValue(value: unknown): { error: string } | { value: number } {\r\n if (value === undefined || value === null) {\r\n return { error: \"\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: value\" };\r\n }\r\n const num = Number(value);\r\n if (isNaN(num) || num < 0 || num > 200) {\r\n return { error: `\u65E0\u6548\u7684\u5F3A\u5EA6\u503C: ${value}\uFF0C\u5FC5\u987B\u5728 0-200 \u8303\u56F4\u5185` };\r\n }\r\n return { value: num };\r\n}\r\n\r\n/**\r\n * \u9A8C\u8BC1\u5F3A\u5EA6\u8C03\u8282\u6A21\u5F0F\r\n * \r\n * @param mode - \u5F85\u9A8C\u8BC1\u7684\u6A21\u5F0F\u503C\r\n * @returns \u5305\u542B\u9519\u8BEF\u4FE1\u606F\u7684\u5BF9\u8C61\uFF0C\u6216\u5305\u542B\u7C7B\u578B\u5B89\u5168\u6A21\u5F0F\u503C\u7684\u5BF9\u8C61\r\n */\r\nfunction validateStrengthMode(mode: string | undefined): { error: string } | { mode: StrengthMode } {\r\n if (!mode) {\r\n return { error: \"\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: mode\" };\r\n }\r\n if (mode !== \"increase\" && mode !== \"decrease\" && mode !== \"set\") {\r\n return { error: `\u65E0\u6548\u7684\u6A21\u5F0F: ${mode}\uFF0C\u5FC5\u987B\u662F \"increase\"\u3001\"decrease\" \u6216 \"set\"` };\r\n }\r\n return { mode };\r\n}\r\n\r\n/**\r\n * \u9A8C\u8BC1\u6CE2\u5F62\u6570\u636E\u6570\u7EC4\r\n * \r\n * \u6CE2\u5F62\u6570\u636E\u662F DG-LAB \u534F\u8BAE\u7684\u6838\u5FC3\uFF0C\u6BCF\u4E2A\u6CE2\u5F62\u7531 8 \u5B57\u8282\uFF0816 \u4E2A\u5341\u516D\u8FDB\u5236\u5B57\u7B26\uFF09\u7EC4\u6210\uFF0C\r\n * \u5305\u542B\u9891\u7387\u3001\u8109\u5BBD\u3001\u5F3A\u5EA6\u7B49\u53C2\u6570\u3002\u8BE6\u7EC6\u683C\u5F0F\u53C2\u89C1 waveform-parser.ts\u3002\r\n * \r\n * @param waveforms - \u5F85\u9A8C\u8BC1\u7684\u6CE2\u5F62\u6570\u7EC4\r\n * @returns \u5305\u542B\u9519\u8BEF\u4FE1\u606F\u7684\u5BF9\u8C61\uFF0C\u6216\u5305\u542B\u9A8C\u8BC1\u901A\u8FC7\u7684\u6CE2\u5F62\u6570\u7EC4\u7684\u5BF9\u8C61\r\n */\r\nfunction validateWaveforms(waveforms: unknown): { error: string } | { waveforms: string[] } {\r\n if (!waveforms) {\r\n return { error: \"\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: waveforms\" };\r\n }\r\n if (!Array.isArray(waveforms)) {\r\n return { error: \"waveforms \u5FC5\u987B\u662F\u6570\u7EC4\" };\r\n }\r\n if (waveforms.length === 0) {\r\n return { error: \"waveforms \u6570\u7EC4\u4E0D\u80FD\u4E3A\u7A7A\" };\r\n }\r\n // \u9650\u5236\u5355\u6B21\u53D1\u9001\u7684\u6CE2\u5F62\u6570\u91CF\uFF0C\u907F\u514D\u5185\u5B58\u95EE\u9898\r\n if (waveforms.length > 100) {\r\n return { error: `waveforms \u6570\u7EC4\u957F\u5EA6\u8D85\u8FC7\u9650\u5236: ${waveforms.length}\uFF0C\u6700\u5927 100` };\r\n }\r\n\r\n // \u9A8C\u8BC1\u6BCF\u4E2A\u6CE2\u5F62\u662F\u5426\u4E3A\u6709\u6548\u7684 16 \u5B57\u7B26 HEX \u5B57\u7B26\u4E32\r\n const hexPattern = /^[0-9a-fA-F]{16}$/;\r\n for (let i = 0; i < waveforms.length; i++) {\r\n const wf = waveforms[i];\r\n if (typeof wf !== \"string\" || !hexPattern.test(wf)) {\r\n return { error: `\u65E0\u6548\u7684\u6CE2\u5F62\u6570\u636E [${i}]: \"${wf}\"\uFF0C\u5FC5\u987B\u662F16\u5B57\u7B26\u7684HEX\u5B57\u7B26\u4E32` };\r\n }\r\n }\r\n\r\n return { waveforms: waveforms as string[] };\r\n}\r\n\r\n// ============================================================\r\n// \u5DE5\u5177\u6CE8\u518C\r\n// ============================================================\r\n\r\n/**\r\n * \u6CE8\u518C\u6240\u6709\u8BBE\u5907\u63A7\u5236\u76F8\u5173\u7684 MCP \u5DE5\u5177\r\n * \r\n * \u5C06\u63A7\u5236\u5DE5\u5177\u6CE8\u518C\u5230\u5DE5\u5177\u7BA1\u7406\u5668\u4E2D\uFF0C\u4F7F AI \u80FD\u591F\u901A\u8FC7 MCP \u534F\u8BAE\r\n * \u63A7\u5236\u5DF2\u7ED1\u5B9A\u7684 DG-LAB \u8BBE\u5907\u3002\r\n * \r\n * @param toolManager - \u5DE5\u5177\u7BA1\u7406\u5668\u5B9E\u4F8B\uFF0C\u7528\u4E8E\u6CE8\u518C\u5DE5\u5177\r\n * @param sessionManager - \u4F1A\u8BDD\u7BA1\u7406\u5668\uFF0C\u7EF4\u62A4\u8BBE\u5907\u4F1A\u8BDD\u72B6\u6001\r\n * @param wsServer - WebSocket \u670D\u52A1\u5668\uFF0C\u5904\u7406\u4E0E APP \u7684\u5B9E\u65F6\u901A\u4FE1\r\n */\r\nexport function registerControlTools(\r\n toolManager: ToolManager,\r\n sessionManager: SessionManager,\r\n wsServer: DGLabWSServer\r\n): void {\r\n // ========== dg_set_strength ==========\r\n // \u8C03\u8282\u901A\u9053\u8F93\u51FA\u5F3A\u5EA6\uFF0C\u652F\u6301\u589E\u52A0\u3001\u51CF\u5C11\u548C\u76F4\u63A5\u8BBE\u7F6E\u4E09\u79CD\u6A21\u5F0F\r\n toolManager.registerTool(\r\n \"dg_set_strength\",\r\n `\u8BBE\u7F6E\u8BBE\u5907\u901A\u9053\u5F3A\u5EA6\u3002\u5FC5\u987B\u5728boundToApp\u4E3Atrue\u540E\u624D\u80FD\u4F7F\u7528\u3002\r\n\u53C2\u6570\u8BF4\u660E\uFF1A\r\n- deviceId \u6216 alias: \u8BBE\u5907\u6807\u8BC6\uFF08\u4E8C\u9009\u4E00\uFF0CdeviceId\u4F18\u5148\uFF09\r\n- channel: A\u6216B\u901A\u9053\r\n- mode: increase(\u589E\u52A0)/decrease(\u51CF\u5C11)/set(\u76F4\u63A5\u8BBE\u7F6E)\r\n- value: \u5F3A\u5EA6\u503C0-200\uFF0C\u4F46\u5B9E\u9645\u4E0D\u80FD\u8D85\u8FC7strengthLimit\r\n\u4F7F\u7528\u524D\u8BF7\u5148\u7528dg_get_status\u786E\u8BA4\u8BBE\u5907\u5DF2\u7ED1\u5B9AAPP\u4E14\u4E86\u89E3\u5F53\u524D\u5F3A\u5EA6\u4E0A\u9650\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n deviceId: { type: \"string\", description: \"\u8BBE\u5907ID\uFF08\u4E0Ealias\u4E8C\u9009\u4E00\uFF0C\u4F18\u5148\u4F7F\u7528\uFF09\" },\r\n alias: { type: \"string\", description: \"\u8BBE\u5907\u522B\u540D\uFF08\u4E0EdeviceId\u4E8C\u9009\u4E00\uFF09\" },\r\n channel: { type: \"string\", enum: [\"A\", \"B\"], description: \"\u901A\u9053\" },\r\n mode: { type: \"string\", enum: [\"increase\", \"decrease\", \"set\"], description: \"\u6A21\u5F0F\" },\r\n value: { type: \"number\", minimum: 0, maximum: 200, description: \"\u5F3A\u5EA6\u503C\" },\r\n },\r\n required: [\"channel\", \"mode\", \"value\"],\r\n },\r\n async (params) => {\r\n // \u4F7F\u7528 resolveDevice \u652F\u6301 deviceId \u548C alias\r\n const deviceResult = resolveDevice(\r\n sessionManager,\r\n params.deviceId as string | undefined,\r\n params.alias as string | undefined\r\n );\r\n if (\"error\" in deviceResult) return createToolError(deviceResult.error);\r\n const session = deviceResult.session;\r\n\r\n const channelResult = validateChannel(params.channel as string);\r\n if (\"error\" in channelResult) return createToolError(channelResult.error);\r\n const channel = channelResult.channel;\r\n\r\n const modeResult = validateStrengthMode(params.mode as string);\r\n if (\"error\" in modeResult) return createToolError(modeResult.error);\r\n const mode = modeResult.mode;\r\n\r\n const valueResult = validateStrengthValue(params.value);\r\n if (\"error\" in valueResult) return createToolError(valueResult.error);\r\n const value = valueResult.value;\r\n\r\n // \u8FDE\u63A5\u72B6\u6001\u68C0\u67E5\uFF1A\u5FC5\u987B\u6709 clientId \u624D\u80FD\u53D1\u9001\u547D\u4EE4\r\n if (!session.clientId) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u8FDE\u63A5\");\r\n }\r\n\r\n // \u7ED1\u5B9A\u72B6\u6001\u68C0\u67E5\uFF1AAPP \u5FC5\u987B\u5DF2\u626B\u7801\u7ED1\u5B9A\r\n const isBound = wsServer.isControllerBound(session.clientId);\r\n if (!isBound) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u7ED1\u5B9AAPP\");\r\n }\r\n\r\n // \u53D1\u9001\u5F3A\u5EA6\u547D\u4EE4\u5230\u8BBE\u5907\r\n const success = wsServer.sendStrength(session.clientId, channel, mode, value);\r\n if (!success) {\r\n return createToolError(\"\u53D1\u9001\u5F3A\u5EA6\u547D\u4EE4\u5931\u8D25\");\r\n }\r\n\r\n // \u66F4\u65B0\u4F1A\u8BDD\u6D3B\u8DC3\u65F6\u95F4\uFF0C\u9632\u6B62\u88AB\u6E05\u7406\r\n sessionManager.touchSession(session.deviceId);\r\n\r\n // \u8FD4\u56DE\u66F4\u65B0\u540E\u7684\u5F3A\u5EA6\u503C\r\n const updated = sessionManager.getSession(session.deviceId);\r\n const newStrength = channel === \"A\" ? updated?.strengthA : updated?.strengthB;\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n success: true,\r\n deviceId: session.deviceId,\r\n channel,\r\n currentStrength: newStrength,\r\n })\r\n );\r\n }\r\n );\r\n\r\n // ========== dg_send_waveform ==========\r\n // \u53D1\u9001\u6CE2\u5F62\u6570\u636E\u63A7\u5236\u8F93\u51FA\u6A21\u5F0F\uFF0C\u652F\u6301\u76F4\u63A5\u63D0\u4F9B\u6570\u636E\u6216\u5F15\u7528\u5DF2\u4FDD\u5B58\u7684\u6CE2\u5F62\r\n toolManager.registerTool(\r\n \"dg_send_waveform\",\r\n `\u53D1\u9001\u6CE2\u5F62\u6570\u636E\u5230\u8BBE\u5907\uFF0C\u63A7\u5236\u8F93\u51FA\u6A21\u5F0F\u3002\u5FC5\u987B\u5728boundToApp\u4E3Atrue\u540E\u624D\u80FD\u4F7F\u7528\u3002\r\n\u652F\u6301\u4E24\u79CD\u65B9\u5F0F\uFF1A\r\n1. \u76F4\u63A5\u63D0\u4F9Bwaveforms\u6570\u7EC4\uFF08\u6BCF\u9879\u4E3A16\u5B57\u7B26HEX\u5B57\u7B26\u4E32\uFF0C\u6700\u591A100\u9879\uFF09\r\n2. \u63D0\u4F9BwaveformName\u5F15\u7528\u5DF2\u4FDD\u5B58\u7684\u6CE2\u5F62\uFF08\u901A\u8FC7dg_parse_waveform\u4FDD\u5B58\uFF09\r\n\u4E24\u79CD\u65B9\u5F0F\u4E8C\u9009\u4E00\uFF0C\u5982\u679C\u540C\u65F6\u63D0\u4F9B\u5219\u4F18\u5148\u4F7F\u7528waveforms\u3002\r\n\u6CE2\u5F62\u4F1A\u6309\u987A\u5E8F\u64AD\u653E\uFF0C\u64AD\u653E\u5B8C\u6BD5\u540E\u505C\u6B62\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n deviceId: { type: \"string\", description: \"\u8BBE\u5907ID\uFF08\u4E0Ealias\u4E8C\u9009\u4E00\uFF0C\u4F18\u5148\u4F7F\u7528\uFF09\" },\r\n alias: { type: \"string\", description: \"\u8BBE\u5907\u522B\u540D\uFF08\u4E0EdeviceId\u4E8C\u9009\u4E00\uFF09\" },\r\n channel: { type: \"string\", enum: [\"A\", \"B\"], description: \"\u901A\u9053\" },\r\n waveforms: {\r\n type: \"array\",\r\n items: { type: \"string\" },\r\n maxItems: 100,\r\n description: \"\u6CE2\u5F62\u6570\u636E\u6570\u7EC4\uFF0C\u6BCF\u9879\u4E3A8\u5B57\u8282HEX\u5B57\u7B26\u4E32\uFF0816\u4E2A\u5341\u516D\u8FDB\u5236\u5B57\u7B26\uFF09\u3002\u4E0EwaveformName\u4E8C\u9009\u4E00\",\r\n },\r\n waveformName: {\r\n type: \"string\",\r\n description: \"\u5DF2\u4FDD\u5B58\u7684\u6CE2\u5F62\u540D\u79F0\uFF08\u901A\u8FC7dg_parse_waveform\u4FDD\u5B58\uFF09\u3002\u4E0Ewaveforms\u4E8C\u9009\u4E00\",\r\n },\r\n },\r\n required: [\"channel\"],\r\n },\r\n async (params) => {\r\n // \u4F7F\u7528 resolveDevice \u652F\u6301 deviceId \u548C alias\r\n const deviceResult = resolveDevice(\r\n sessionManager,\r\n params.deviceId as string | undefined,\r\n params.alias as string | undefined\r\n );\r\n if (\"error\" in deviceResult) return createToolError(deviceResult.error);\r\n const session = deviceResult.session;\r\n\r\n const channelResult = validateChannel(params.channel as string);\r\n if (\"error\" in channelResult) return createToolError(channelResult.error);\r\n const channel = channelResult.channel;\r\n\r\n // \u83B7\u53D6\u6CE2\u5F62\u6570\u636E\u6765\u6E90\r\n const rawWaveforms = params.waveforms as string[] | undefined;\r\n const waveformName = params.waveformName as string | undefined;\r\n\r\n // \u5FC5\u987B\u63D0\u4F9B\u6CE2\u5F62\u6570\u636E\u6765\u6E90\u4E4B\u4E00\r\n if (!rawWaveforms && !waveformName) {\r\n return createToolError(\"\u5FC5\u987B\u63D0\u4F9B waveforms \u6216 waveformName \u53C2\u6570\u4E4B\u4E00\");\r\n }\r\n\r\n let waveforms: string[];\r\n\r\n if (rawWaveforms) {\r\n // \u65B9\u5F0F\u4E00\uFF1A\u76F4\u63A5\u63D0\u4F9B\u6CE2\u5F62\u6570\u636E\r\n const waveformsResult = validateWaveforms(rawWaveforms);\r\n if (\"error\" in waveformsResult) return createToolError(waveformsResult.error);\r\n waveforms = waveformsResult.waveforms;\r\n } else {\r\n // \u65B9\u5F0F\u4E8C\uFF1A\u4ECE\u5B58\u50A8\u4E2D\u83B7\u53D6\u5DF2\u4FDD\u5B58\u7684\u6CE2\u5F62\r\n const storage = getWaveformStorage();\r\n const storedWaveform = storage.get(waveformName!);\r\n \r\n if (!storedWaveform) {\r\n return createToolError(`\u6CE2\u5F62\u4E0D\u5B58\u5728: ${waveformName}`);\r\n }\r\n \r\n waveforms = storedWaveform.hexWaveforms;\r\n }\r\n\r\n // \u8FDE\u63A5\u548C\u7ED1\u5B9A\u72B6\u6001\u68C0\u67E5\r\n if (!session.clientId) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u8FDE\u63A5\");\r\n }\r\n\r\n const isBound = wsServer.isControllerBound(session.clientId);\r\n if (!isBound) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u7ED1\u5B9AAPP\");\r\n }\r\n\r\n // \u53D1\u9001\u6CE2\u5F62\u6570\u636E\u5230\u8BBE\u5907\r\n const success = wsServer.sendWaveform(session.clientId, channel, waveforms);\r\n if (!success) {\r\n return createToolError(\"\u53D1\u9001\u6CE2\u5F62\u6570\u636E\u5931\u8D25\");\r\n }\r\n\r\n sessionManager.touchSession(session.deviceId);\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n success: true,\r\n deviceId: session.deviceId,\r\n channel,\r\n waveformCount: waveforms.length,\r\n source: rawWaveforms ? \"direct\" : `waveform:${waveformName}`,\r\n })\r\n );\r\n }\r\n );\r\n\r\n // ========== dg_clear_waveform ==========\r\n // \u6E05\u7A7A\u6CE2\u5F62\u961F\u5217\uFF0C\u7ACB\u5373\u505C\u6B62\u5F53\u524D\u64AD\u653E\r\n toolManager.registerTool(\r\n \"dg_clear_waveform\",\r\n `\u6E05\u7A7A\u8BBE\u5907\u6307\u5B9A\u901A\u9053\u7684\u6CE2\u5F62\u961F\u5217\uFF0C\u7ACB\u5373\u505C\u6B62\u5F53\u524D\u6CE2\u5F62\u64AD\u653E\u3002\r\n\u7528\u4E8E\u4E2D\u65AD\u6B63\u5728\u64AD\u653E\u7684\u6CE2\u5F62\u6216\u5728\u53D1\u9001\u65B0\u6CE2\u5F62\u524D\u6E05\u7A7A\u961F\u5217\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n deviceId: { type: \"string\", description: \"\u8BBE\u5907ID\uFF08\u4E0Ealias\u4E8C\u9009\u4E00\uFF0C\u4F18\u5148\u4F7F\u7528\uFF09\" },\r\n alias: { type: \"string\", description: \"\u8BBE\u5907\u522B\u540D\uFF08\u4E0EdeviceId\u4E8C\u9009\u4E00\uFF09\" },\r\n channel: { type: \"string\", enum: [\"A\", \"B\"], description: \"\u901A\u9053\" },\r\n },\r\n required: [\"channel\"],\r\n },\r\n async (params) => {\r\n // \u4F7F\u7528 resolveDevice \u652F\u6301 deviceId \u548C alias\r\n const deviceResult = resolveDevice(\r\n sessionManager,\r\n params.deviceId as string | undefined,\r\n params.alias as string | undefined\r\n );\r\n if (\"error\" in deviceResult) return createToolError(deviceResult.error);\r\n const session = deviceResult.session;\r\n\r\n const channelResult = validateChannel(params.channel as string);\r\n if (\"error\" in channelResult) return createToolError(channelResult.error);\r\n const channel = channelResult.channel;\r\n\r\n // \u8FDE\u63A5\u548C\u7ED1\u5B9A\u72B6\u6001\u68C0\u67E5\r\n if (!session.clientId) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u8FDE\u63A5\");\r\n }\r\n\r\n const isBound = wsServer.isControllerBound(session.clientId);\r\n if (!isBound) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u7ED1\u5B9AAPP\");\r\n }\r\n\r\n // \u53D1\u9001\u6E05\u7A7A\u547D\u4EE4\r\n const success = wsServer.clearWaveform(session.clientId, channel);\r\n if (!success) {\r\n return createToolError(\"\u6E05\u7A7A\u6CE2\u5F62\u961F\u5217\u5931\u8D25\");\r\n }\r\n\r\n sessionManager.touchSession(session.deviceId);\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n success: true,\r\n deviceId: session.deviceId,\r\n channel,\r\n })\r\n );\r\n }\r\n );\r\n\r\n // ========== dg_get_status ==========\r\n // \u83B7\u53D6\u8BBE\u5907\u5B8C\u6574\u72B6\u6001\uFF0C\u7528\u4E8E\u68C0\u67E5\u7ED1\u5B9A\u72B6\u6001\u548C\u5F53\u524D\u53C2\u6570\r\n toolManager.registerTool(\r\n \"dg_get_status\",\r\n `\u83B7\u53D6\u8BBE\u5907\u5B8C\u6574\u72B6\u6001\u4FE1\u606F\u3002\r\n\u5173\u952E\u5B57\u6BB5\uFF1A\r\n- boundToApp: \u662F\u5426\u5DF2\u7ED1\u5B9AAPP\uFF08\u5FC5\u987B\u4E3Atrue\u624D\u80FD\u63A7\u5236\u8BBE\u5907\uFF09\r\n- strengthA/B: \u5F53\u524DA/B\u901A\u9053\u5F3A\u5EA6\r\n- strengthLimitA/B: A/B\u901A\u9053\u5F3A\u5EA6\u4E0A\u9650\uFF08\u7531APP\u8BBE\u7F6E\uFF0C\u4E0D\u53EF\u8D85\u8FC7\uFF09\r\n\u5EFA\u8BAE\u5728dg_connect\u540E\u5728\u7528\u6237\u8BF4\u5DF2\u5B8C\u6210\u540E\u4F7F\u7528\u6B64\u63A5\u53E3\u68C0\u67E5boundToApp\u72B6\u6001\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n deviceId: { type: \"string\", description: \"\u8BBE\u5907ID\uFF08\u4E0Ealias\u4E8C\u9009\u4E00\uFF0C\u4F18\u5148\u4F7F\u7528\uFF09\" },\r\n alias: { type: \"string\", description: \"\u8BBE\u5907\u522B\u540D\uFF08\u4E0EdeviceId\u4E8C\u9009\u4E00\uFF09\" },\r\n },\r\n required: [],\r\n },\r\n async (params) => {\r\n // \u4F7F\u7528 resolveDevice \u652F\u6301 deviceId \u548C alias\r\n const deviceResult = resolveDevice(\r\n sessionManager,\r\n params.deviceId as string | undefined,\r\n params.alias as string | undefined\r\n );\r\n if (\"error\" in deviceResult) return createToolError(deviceResult.error);\r\n const session = deviceResult.session;\r\n\r\n // \u68C0\u67E5 APP \u7ED1\u5B9A\u72B6\u6001\r\n const isBound = session.clientId ? wsServer.isControllerBound(session.clientId) : false;\r\n\r\n // \u8FD4\u56DE\u5B8C\u6574\u7684\u8BBE\u5907\u72B6\u6001\u4FE1\u606F\r\n return createToolResult(\r\n JSON.stringify({\r\n deviceId: session.deviceId,\r\n alias: session.alias,\r\n connected: session.connected,\r\n boundToApp: isBound,\r\n strengthA: session.strengthA,\r\n strengthB: session.strengthB,\r\n strengthLimitA: session.strengthLimitA,\r\n strengthLimitB: session.strengthLimitB,\r\n })\r\n );\r\n }\r\n );\r\n\r\n // ========== dg_start_continuous_playback ==========\r\n // \u542F\u52A8\u6301\u7EED\u64AD\u653E\uFF0C\u5FAA\u73AF\u53D1\u9001\u6CE2\u5F62\u76F4\u5230\u624B\u52A8\u505C\u6B62\r\n toolManager.registerTool(\r\n \"dg_start_continuous_playback\",\r\n `\u542F\u52A8\u6301\u7EED\u64AD\u653E\u6A21\u5F0F\uFF0C\u5FAA\u73AF\u53D1\u9001\u6CE2\u5F62\u6570\u636E\u76F4\u5230\u624B\u52A8\u505C\u6B62\u3002\r\n\u4E0Edg_send_waveform\u4E0D\u540C\uFF0C\u6301\u7EED\u64AD\u653E\u4F1A\u81EA\u52A8\u5FAA\u73AF\u53D1\u9001\u6CE2\u5F62\uFF0C\u9002\u5408\u9700\u8981\u6301\u7EED\u8F93\u51FA\u7684\u573A\u666F\u3002\r\n\u652F\u6301\u4E24\u79CD\u65B9\u5F0F\u63D0\u4F9B\u6CE2\u5F62\uFF1A\r\n1. \u76F4\u63A5\u63D0\u4F9Bwaveforms\u6570\u7EC4\r\n2. \u63D0\u4F9BwaveformName\u5F15\u7528\u5DF2\u4FDD\u5B58\u7684\u6CE2\u5F62\r\n\u53EF\u9009\u53C2\u6570\uFF1A\r\n- interval: \u53D1\u9001\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4100ms\r\n- batchSize: \u6BCF\u6B21\u53D1\u9001\u7684\u6CE2\u5F62\u6570\u91CF\uFF0C\u9ED8\u8BA45`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n deviceId: { type: \"string\", description: \"\u8BBE\u5907ID\uFF08\u4E0Ealias\u4E8C\u9009\u4E00\uFF0C\u4F18\u5148\u4F7F\u7528\uFF09\" },\r\n alias: { type: \"string\", description: \"\u8BBE\u5907\u522B\u540D\uFF08\u4E0EdeviceId\u4E8C\u9009\u4E00\uFF09\" },\r\n channel: { type: \"string\", enum: [\"A\", \"B\"], description: \"\u901A\u9053\" },\r\n waveforms: {\r\n type: \"array\",\r\n items: { type: \"string\" },\r\n maxItems: 100,\r\n description: \"\u6CE2\u5F62\u6570\u636E\u6570\u7EC4\uFF0C\u6BCF\u9879\u4E3A8\u5B57\u8282HEX\u5B57\u7B26\u4E32\u3002\u4E0EwaveformName\u4E8C\u9009\u4E00\",\r\n },\r\n waveformName: {\r\n type: \"string\",\r\n description: \"\u5DF2\u4FDD\u5B58\u7684\u6CE2\u5F62\u540D\u79F0\u3002\u4E0Ewaveforms\u4E8C\u9009\u4E00\",\r\n },\r\n interval: {\r\n type: \"number\",\r\n minimum: 50,\r\n maximum: 5000,\r\n description: \"\u53D1\u9001\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4100\",\r\n },\r\n batchSize: {\r\n type: \"number\",\r\n minimum: 1,\r\n maximum: 20,\r\n description: \"\u6BCF\u6B21\u53D1\u9001\u7684\u6CE2\u5F62\u6570\u91CF\uFF0C\u9ED8\u8BA45\",\r\n },\r\n },\r\n required: [\"channel\"],\r\n },\r\n async (params) => {\r\n // \u4F7F\u7528 resolveDevice \u652F\u6301 deviceId \u548C alias\r\n const deviceResult = resolveDevice(\r\n sessionManager,\r\n params.deviceId as string | undefined,\r\n params.alias as string | undefined\r\n );\r\n if (\"error\" in deviceResult) return createToolError(deviceResult.error);\r\n const session = deviceResult.session;\r\n\r\n const channelResult = validateChannel(params.channel as string);\r\n if (\"error\" in channelResult) return createToolError(channelResult.error);\r\n const channel = channelResult.channel;\r\n\r\n // \u83B7\u53D6\u6CE2\u5F62\u6570\u636E\u6765\u6E90\r\n const rawWaveforms = params.waveforms as string[] | undefined;\r\n const waveformName = params.waveformName as string | undefined;\r\n\r\n if (!rawWaveforms && !waveformName) {\r\n return createToolError(\"\u5FC5\u987B\u63D0\u4F9B waveforms \u6216 waveformName \u53C2\u6570\u4E4B\u4E00\");\r\n }\r\n\r\n let waveforms: string[];\r\n\r\n if (rawWaveforms) {\r\n const waveformsResult = validateWaveforms(rawWaveforms);\r\n if (\"error\" in waveformsResult) return createToolError(waveformsResult.error);\r\n waveforms = waveformsResult.waveforms;\r\n } else {\r\n const storage = getWaveformStorage();\r\n const storedWaveform = storage.get(waveformName!);\r\n if (!storedWaveform) {\r\n return createToolError(`\u6CE2\u5F62\u4E0D\u5B58\u5728: ${waveformName}`);\r\n }\r\n waveforms = storedWaveform.hexWaveforms;\r\n }\r\n\r\n // \u8FDE\u63A5\u548C\u7ED1\u5B9A\u72B6\u6001\u68C0\u67E5\r\n if (!session.clientId) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u8FDE\u63A5\");\r\n }\r\n\r\n const isBound = wsServer.isControllerBound(session.clientId);\r\n if (!isBound) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u7ED1\u5B9AAPP\");\r\n }\r\n\r\n // \u83B7\u53D6\u53EF\u9009\u53C2\u6570\r\n const interval = typeof params.interval === \"number\" ? params.interval : 100;\r\n const batchSize = typeof params.batchSize === \"number\" ? params.batchSize : 5;\r\n\r\n // \u542F\u52A8\u6301\u7EED\u64AD\u653E\r\n const success = wsServer.startContinuousPlayback(\r\n session.clientId,\r\n channel,\r\n waveforms,\r\n interval,\r\n batchSize\r\n );\r\n\r\n if (!success) {\r\n return createToolError(\"\u542F\u52A8\u6301\u7EED\u64AD\u653E\u5931\u8D25\");\r\n }\r\n\r\n sessionManager.touchSession(session.deviceId);\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n success: true,\r\n deviceId: session.deviceId,\r\n channel,\r\n waveformCount: waveforms.length,\r\n interval,\r\n batchSize,\r\n source: rawWaveforms ? \"direct\" : `waveform:${waveformName}`,\r\n })\r\n );\r\n }\r\n );\r\n\r\n // ========== dg_stop_continuous_playback ==========\r\n // \u505C\u6B62\u6301\u7EED\u64AD\u653E\r\n toolManager.registerTool(\r\n \"dg_stop_continuous_playback\",\r\n `\u505C\u6B62\u6307\u5B9A\u901A\u9053\u7684\u6301\u7EED\u64AD\u653E\u3002\r\n\u4F1A\u7ACB\u5373\u505C\u6B62\u5FAA\u73AF\u53D1\u9001\u5E76\u6E05\u7A7A\u6CE2\u5F62\u961F\u5217\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n deviceId: { type: \"string\", description: \"\u8BBE\u5907ID\uFF08\u4E0Ealias\u4E8C\u9009\u4E00\uFF0C\u4F18\u5148\u4F7F\u7528\uFF09\" },\r\n alias: { type: \"string\", description: \"\u8BBE\u5907\u522B\u540D\uFF08\u4E0EdeviceId\u4E8C\u9009\u4E00\uFF09\" },\r\n channel: { type: \"string\", enum: [\"A\", \"B\"], description: \"\u901A\u9053\" },\r\n },\r\n required: [\"channel\"],\r\n },\r\n async (params) => {\r\n // \u4F7F\u7528 resolveDevice \u652F\u6301 deviceId \u548C alias\r\n const deviceResult = resolveDevice(\r\n sessionManager,\r\n params.deviceId as string | undefined,\r\n params.alias as string | undefined\r\n );\r\n if (\"error\" in deviceResult) return createToolError(deviceResult.error);\r\n const session = deviceResult.session;\r\n\r\n const channelResult = validateChannel(params.channel as string);\r\n if (\"error\" in channelResult) return createToolError(channelResult.error);\r\n const channel = channelResult.channel;\r\n\r\n // \u8FDE\u63A5\u72B6\u6001\u68C0\u67E5\r\n if (!session.clientId) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u8FDE\u63A5\");\r\n }\r\n\r\n // \u505C\u6B62\u6301\u7EED\u64AD\u653E\r\n const success = wsServer.stopContinuousPlayback(session.clientId, channel);\r\n\r\n if (!success) {\r\n return createToolError(\"\u505C\u6B62\u6301\u7EED\u64AD\u653E\u5931\u8D25\uFF1A\u8BE5\u901A\u9053\u6CA1\u6709\u6B63\u5728\u8FDB\u884C\u7684\u6301\u7EED\u64AD\u653E\");\r\n }\r\n\r\n sessionManager.touchSession(session.deviceId);\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n success: true,\r\n deviceId: session.deviceId,\r\n channel,\r\n })\r\n );\r\n }\r\n );\r\n\r\n // ========== dg_get_playback_status ==========\r\n // \u83B7\u53D6\u6301\u7EED\u64AD\u653E\u72B6\u6001\r\n toolManager.registerTool(\r\n \"dg_get_playback_status\",\r\n `\u83B7\u53D6\u8BBE\u5907\u7684\u6301\u7EED\u64AD\u653E\u72B6\u6001\u3002\r\n\u8FD4\u56DEA\u548CB\u901A\u9053\u7684\u64AD\u653E\u72B6\u6001\uFF0C\u5305\u62EC\u662F\u5426\u6B63\u5728\u64AD\u653E\u3001\u6CE2\u5F62\u6570\u91CF\u3001\u53D1\u9001\u95F4\u9694\u7B49\u4FE1\u606F\u3002`,\r\n {\r\n type: \"object\",\r\n properties: {\r\n deviceId: { type: \"string\", description: \"\u8BBE\u5907ID\uFF08\u4E0Ealias\u4E8C\u9009\u4E00\uFF0C\u4F18\u5148\u4F7F\u7528\uFF09\" },\r\n alias: { type: \"string\", description: \"\u8BBE\u5907\u522B\u540D\uFF08\u4E0EdeviceId\u4E8C\u9009\u4E00\uFF09\" },\r\n },\r\n required: [],\r\n },\r\n async (params) => {\r\n // \u4F7F\u7528 resolveDevice \u652F\u6301 deviceId \u548C alias\r\n const deviceResult = resolveDevice(\r\n sessionManager,\r\n params.deviceId as string | undefined,\r\n params.alias as string | undefined\r\n );\r\n if (\"error\" in deviceResult) return createToolError(deviceResult.error);\r\n const session = deviceResult.session;\r\n\r\n // \u8FDE\u63A5\u72B6\u6001\u68C0\u67E5\r\n if (!session.clientId) {\r\n return createToolError(\"\u8BBE\u5907\u672A\u8FDE\u63A5\");\r\n }\r\n\r\n // \u83B7\u53D6\u4E24\u4E2A\u901A\u9053\u7684\u64AD\u653E\u72B6\u6001\r\n const statusA = wsServer.getContinuousPlaybackState(session.clientId, \"A\");\r\n const statusB = wsServer.getContinuousPlaybackState(session.clientId, \"B\");\r\n\r\n return createToolResult(\r\n JSON.stringify({\r\n deviceId: session.deviceId,\r\n channelA: statusA ? {\r\n playing: statusA.active,\r\n waveformCount: statusA.waveformCount,\r\n interval: statusA.interval,\r\n batchSize: statusA.batchSize,\r\n } : { playing: false },\r\n channelB: statusB ? {\r\n playing: statusB.active,\r\n waveformCount: statusB.waveformCount,\r\n interval: statusB.interval,\r\n batchSize: statusB.batchSize,\r\n } : { playing: false },\r\n })\r\n );\r\n }\r\n );\r\n}\r\n\r\n// ============================================================\r\n// \u5BFC\u51FA\u9A8C\u8BC1\u51FD\u6570\u4F9B\u6D4B\u8BD5\u4F7F\u7528\r\n// ============================================================\r\n\r\nexport {\r\n validateDeviceId,\r\n validateChannel,\r\n validateStrengthValue,\r\n validateStrengthMode,\r\n validateWaveforms,\r\n resolveDevice,\r\n};\r\n", "/**\r\n * @fileoverview \u5E94\u7528\u521D\u59CB\u5316\u6A21\u5757\r\n * @description \u5C01\u88C5\u5E94\u7528\u7684\u521D\u59CB\u5316\u903B\u8F91\uFF0C\u5305\u62EC\u4F9D\u8D56\u6CE8\u5165\u548C\u6A21\u5757\u7EC4\u88C5\r\n */\r\n\r\nimport type { ServerConfig } from \"./config\";\r\nimport { loadConfig, getEffectiveIP, getLocalIP } from \"./config\";\r\nimport { createServer, broadcastNotification, type MCPServer } from \"./server\";\r\nimport { registerMCPProtocol } from \"./mcp-protocol\";\r\nimport { ToolManager, registerToolHandlers } from \"./tool-manager\";\r\nimport { SessionManager } from \"./session-manager\";\r\nimport { DGLabWSServer } from \"./ws-server\";\r\nimport { registerDeviceTools } from \"./tools/device-tools\";\r\nimport { registerControlTools } from \"./tools/control-tools\";\r\nimport { getWaveformTools, initWaveformStorage } from \"./tools/waveform-tools\";\r\nimport { WaveformStorage, loadWaveforms } from \"./waveform-storage\";\r\nimport { ConfigError, ErrorCode } from \"./errors\";\r\n\r\n/**\r\n * \u5E94\u7528\u5B9E\u4F8B\uFF0C\u5305\u542B\u6240\u6709\u6838\u5FC3\u7EC4\u4EF6\u7684\u5F15\u7528\r\n */\r\nexport interface App {\r\n config: ServerConfig;\r\n server: MCPServer;\r\n toolManager: ToolManager;\r\n sessionManager: SessionManager;\r\n wsServer: DGLabWSServer;\r\n waveformStorage: WaveformStorage;\r\n shutdown: () => Promise<void>;\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA\u5E76\u521D\u59CB\u5316\u5E94\u7528\r\n * \r\n * \u8FD9\u4E2A\u51FD\u6570\u8D1F\u8D23\uFF1A\r\n * 1. \u52A0\u8F7D\u914D\u7F6E\r\n * 2. \u521B\u5EFA\u5404\u4E2A\u6838\u5FC3\u7EC4\u4EF6\r\n * 3. \u6CE8\u518C\u5DE5\u5177\u548C\u534F\u8BAE\u5904\u7406\u5668\r\n * 4. \u8BBE\u7F6E\u7EC4\u4EF6\u95F4\u7684\u56DE\u8C03\u5173\u7CFB\r\n * \r\n * @returns \u521D\u59CB\u5316\u5B8C\u6210\u7684\u5E94\u7528\u5B9E\u4F8B\r\n */\r\nexport function createApp(): App {\r\n // \u52A0\u8F7D\u914D\u7F6E\r\n const config = loadConfig();\r\n \r\n // \u6253\u5370\u914D\u7F6E\u4FE1\u606F\r\n printConfigInfo(config);\r\n\r\n // \u521B\u5EFA MCP SSE \u7684 HTTP \u670D\u52A1\u5668\r\n const server = createServer(config);\r\n\r\n // \u521B\u5EFA\u5DE5\u5177\u7BA1\u7406\u5668\r\n const toolManager = new ToolManager(() => {\r\n broadcastNotification(server, \"notifications/tools/list_changed\");\r\n });\r\n\r\n // \u521B\u5EFA\u4F1A\u8BDD\u7BA1\u7406\u5668\uFF08\u4EC5\u5185\u5B58\uFF0C\u914D\u7F6E\u5316\u8D85\u65F6\uFF09\r\n const sessionManager = new SessionManager(config.connectionTimeoutMinutes);\r\n console.log(`[\u4F1A\u8BDD] \u4EC5\u5185\u5B58\u6A21\u5F0F\uFF08\u8FDE\u63A5\u8D85\u65F6: ${config.connectionTimeoutMinutes} \u5206\u949F\uFF0C\u6D3B\u8DC3\u8D85\u65F6: 1 \u5C0F\u65F6\uFF09`);\r\n\r\n // \u521B\u5EFA WebSocket \u670D\u52A1\u5668\r\n const wsServer = createWSServer(config, sessionManager);\r\n\r\n // \u521D\u59CB\u5316\u6CE2\u5F62\u5B58\u50A8\r\n const waveformStorage = initWaveforms(config);\r\n\r\n // \u6CE8\u518C\u534F\u8BAE\u548C\u5DE5\u5177\r\n registerProtocolAndTools(server, toolManager, sessionManager, wsServer, config);\r\n\r\n // \u521B\u5EFA\u5173\u95ED\u51FD\u6570\r\n const shutdown = async () => {\r\n console.log(\"\\n[\u670D\u52A1\u5668] \u6B63\u5728\u5173\u95ED...\");\r\n wsServer.stop();\r\n sessionManager.stopCleanupTimer();\r\n sessionManager.clearAll();\r\n await server.stop();\r\n console.log(\"[\u670D\u52A1\u5668] \u5DF2\u505C\u6B62\");\r\n };\r\n\r\n return {\r\n config,\r\n server,\r\n toolManager,\r\n sessionManager,\r\n wsServer,\r\n waveformStorage,\r\n shutdown,\r\n };\r\n}\r\n\r\n/**\r\n * \u6253\u5370\u914D\u7F6E\u4FE1\u606F\r\n */\r\nfunction printConfigInfo(config: ServerConfig): void {\r\n console.log(\"=\".repeat(50));\r\n console.log(\"DG-LAB MCP SSE \u670D\u52A1\u5668\");\r\n console.log(\"=\".repeat(50));\r\n console.log(`[\u914D\u7F6E] \u7AEF\u53E3: ${config.port}`);\r\n console.log(`[\u914D\u7F6E] SSE \u8DEF\u5F84: ${config.ssePath}`);\r\n console.log(`[\u914D\u7F6E] POST \u8DEF\u5F84: ${config.postPath}`);\r\n \r\n const effectiveIP = getEffectiveIP(config);\r\n const localIP = getLocalIP();\r\n console.log(`[\u914D\u7F6E] \u672C\u5730 IP: ${localIP}`);\r\n console.log(`[\u914D\u7F6E] \u516C\u7F51 IP: ${config.publicIp || \"(\u672A\u8BBE\u7F6E)\"}`);\r\n console.log(`[\u914D\u7F6E] \u5B9E\u9645\u4F7F\u7528 IP: ${effectiveIP}`);\r\n}\r\n\r\n/**\r\n * \u521B\u5EFA WebSocket \u670D\u52A1\u5668\u5E76\u8BBE\u7F6E\u56DE\u8C03\r\n */\r\nfunction createWSServer(config: ServerConfig, sessionManager: SessionManager): DGLabWSServer {\r\n return new DGLabWSServer({\r\n heartbeatInterval: config.heartbeatInterval,\r\n onStrengthUpdate: (controllerId, a, b, limitA, limitB) => {\r\n console.log(`[WS] ${controllerId} \u5F3A\u5EA6: A=${a}/${limitA}, B=${b}/${limitB}`);\r\n const session = sessionManager.getSessionByClientId(controllerId);\r\n if (session) {\r\n sessionManager.updateStrength(session.deviceId, a, b, limitA, limitB);\r\n }\r\n },\r\n onFeedback: (controllerId, index) => {\r\n console.log(`[WS] ${controllerId} \u53CD\u9988: ${index}`);\r\n },\r\n onBindChange: (controllerId, appId) => {\r\n console.log(`[WS] ${controllerId} \u7ED1\u5B9A: ${appId || \"\u5DF2\u89E3\u7ED1\"}`);\r\n const session = sessionManager.getSessionByClientId(controllerId);\r\n if (session) {\r\n sessionManager.updateConnectionState(session.deviceId, {\r\n boundToApp: !!appId,\r\n targetId: appId,\r\n });\r\n // \u7ED1\u5B9A APP \u65F6\u53D6\u6D88\u8FDE\u63A5\u8D85\u65F6\u8BA1\u65F6\u5668\r\n if (appId) {\r\n sessionManager.onAppBound(session.deviceId);\r\n }\r\n }\r\n },\r\n onControllerDisconnect: (controllerId) => {\r\n console.log(`[WS] \u63A7\u5236\u5668\u65AD\u5F00: ${controllerId}`);\r\n const session = sessionManager.getSessionByClientId(controllerId);\r\n if (session) {\r\n sessionManager.updateConnectionState(session.deviceId, {\r\n connected: false,\r\n boundToApp: false,\r\n clientId: null,\r\n targetId: null,\r\n });\r\n }\r\n },\r\n onAppDisconnect: (appId) => {\r\n console.log(`[WS] APP \u65AD\u5F00: ${appId}`);\r\n const sessions = sessionManager.listSessions();\r\n for (const session of sessions) {\r\n if (session.targetId === appId) {\r\n sessionManager.updateConnectionState(session.deviceId, {\r\n boundToApp: false,\r\n targetId: null,\r\n });\r\n }\r\n }\r\n },\r\n });\r\n}\r\n\r\n/**\r\n * \u521D\u59CB\u5316\u6CE2\u5F62\u5B58\u50A8\r\n */\r\nfunction initWaveforms(config: ServerConfig): WaveformStorage {\r\n const waveformStorage = new WaveformStorage();\r\n if (loadWaveforms(waveformStorage, config.waveformStorePath)) {\r\n console.log(`[\u6CE2\u5F62] \u4ECE\u78C1\u76D8\u52A0\u8F7D\u4E86 ${waveformStorage.list().length} \u4E2A\u6CE2\u5F62`);\r\n }\r\n initWaveformStorage(waveformStorage, config.waveformStorePath);\r\n return waveformStorage;\r\n}\r\n\r\n/**\r\n * \u6CE8\u518C MCP \u534F\u8BAE\u548C\u6240\u6709\u5DE5\u5177\r\n */\r\nfunction registerProtocolAndTools(\r\n server: MCPServer,\r\n toolManager: ToolManager,\r\n sessionManager: SessionManager,\r\n wsServer: DGLabWSServer,\r\n config: ServerConfig\r\n): void {\r\n // \u6CE8\u518C MCP \u534F\u8BAE\u5904\u7406\u51FD\u6570\r\n registerMCPProtocol(server.jsonRpcHandler, () => {\r\n console.log(\"[MCP] \u5BA2\u6237\u7AEF\u5DF2\u521D\u59CB\u5316\");\r\n });\r\n\r\n // \u6CE8\u518C\u5DE5\u5177\u5904\u7406\u51FD\u6570\r\n registerToolHandlers(server.jsonRpcHandler, toolManager);\r\n\r\n // \u6CE8\u518C\u8BBE\u5907\u5DE5\u5177\r\n registerDeviceTools(toolManager, sessionManager, wsServer, config.publicIp || undefined);\r\n console.log(\"[\u5DE5\u5177] \u8BBE\u5907\u5DE5\u5177\u5DF2\u6CE8\u518C\");\r\n\r\n // \u6CE8\u518C\u63A7\u5236\u5DE5\u5177\r\n registerControlTools(toolManager, sessionManager, wsServer);\r\n console.log(\"[\u5DE5\u5177] \u63A7\u5236\u5DE5\u5177\u5DF2\u6CE8\u518C\");\r\n\r\n // \u6CE8\u518C\u6CE2\u5F62\u5DE5\u5177\r\n const waveformTools = getWaveformTools();\r\n for (const tool of waveformTools) {\r\n toolManager.registerTool(tool.name, tool.description, tool.inputSchema, tool.handler);\r\n }\r\n console.log(\"[\u5DE5\u5177] \u6CE2\u5F62\u5DE5\u5177\u5DF2\u6CE8\u518C\");\r\n console.log(`[\u5DE5\u5177] \u603B\u8BA1: ${toolManager.toolCount}`);\r\n}\r\n\r\n/**\r\n * \u542F\u52A8\u5E94\u7528\r\n * \r\n * \u542F\u52A8 HTTP \u670D\u52A1\u5668\u5E76\u9644\u52A0 WebSocket \u670D\u52A1\u5668\u3002\r\n * \r\n * @param app - \u5E94\u7528\u5B9E\u4F8B\r\n */\r\nexport async function startApp(app: App): Promise<void> {\r\n // \u542F\u52A8 HTTP \u670D\u52A1\u5668\r\n await app.server.start();\r\n\r\n // \u5C06 WebSocket \u670D\u52A1\u5668\u9644\u52A0\u5230 HTTP \u670D\u52A1\u5668\r\n if (app.server.httpServer) {\r\n app.wsServer.attachToServer(app.server.httpServer, app.config.port);\r\n } else {\r\n throw new ConfigError(\"HTTP \u670D\u52A1\u5668\u672A\u542F\u52A8\uFF0C\u65E0\u6CD5\u9644\u52A0 WebSocket\", {\r\n code: ErrorCode.CONFIG_LOAD_FAILED,\r\n context: { port: app.config.port },\r\n });\r\n }\r\n\r\n // \u6253\u5370\u5C31\u7EEA\u4FE1\u606F\r\n console.log(\"=\".repeat(50));\r\n console.log(\"\u670D\u52A1\u5668\u5C31\u7EEA\");\r\n console.log(`SSE: http://localhost:${app.config.port}${app.config.ssePath}`);\r\n console.log(`POST: http://localhost:${app.config.port}${app.config.postPath}`);\r\n console.log(`WebSocket: ws://localhost:${app.config.port}`);\r\n console.log(\"=\".repeat(50));\r\n}\r\n", "#!/usr/bin/env node\r\n/**\r\n * @fileoverview DG-LAB MCP SSE \u670D\u52A1\u5668 CLI \u5165\u53E3\r\n * @description \u7528\u4E8E npx \u542F\u52A8\u7684\u547D\u4EE4\u884C\u5165\u53E3\r\n */\r\n\r\nimport { createApp, startApp } from \"./app.js\";\r\n\r\n/**\r\n * CLI \u4E3B\u51FD\u6570\r\n */\r\nasync function main() {\r\n // \u521B\u5EFA\u5E76\u521D\u59CB\u5316\u5E94\u7528\r\n const app = createApp();\r\n\r\n // \u8BBE\u7F6E\u4F18\u96C5\u5173\u95ED\r\n const shutdown = async () => {\r\n await app.shutdown();\r\n process.exit(0);\r\n };\r\n\r\n process.on(\"SIGINT\", shutdown);\r\n process.on(\"SIGTERM\", shutdown);\r\n\r\n // \u542F\u52A8\u5E94\u7528\r\n await startApp(app);\r\n}\r\n\r\nmain().catch((error) => {\r\n console.error(\"[\u81F4\u547D\u9519\u8BEF]\", error);\r\n process.exit(1);\r\n});\r\n"],
5
+ "mappings": "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",
6
+ "names": ["error", "uuidv4", "WebSocket", "uuidv4", "uuidv4", "WebSocket", "createToolError", "waveformStorage"]
7
+ }