@atlisp/mcp 1.8.23 → 1.8.25

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.
Binary file
@@ -4,15 +4,55 @@ import process from 'process';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
6
  import crypto from 'crypto';
7
+ import { fileURLToPath } from 'url';
7
8
  import { validateLispCodeWorker, checkParens } from './lisp-security.js';
8
9
 
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const _dllPath = path.join(__dirname, '..', 'dist', 'cad-rothelper.dll');
13
+
14
+ function _loadDll(methodName) {
15
+ try {
16
+ return edge.func({
17
+ assemblyFile: _dllPath,
18
+ typeName: 'CadRotHelper.ConnectionManager',
19
+ methodName,
20
+ });
21
+ } catch (e) {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ function _loadEnum() {
27
+ try {
28
+ return edge.func({
29
+ assemblyFile: _dllPath,
30
+ typeName: 'CadRotHelper.RotEnumerator',
31
+ methodName: 'EnumerateAll',
32
+ });
33
+ } catch (e) {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ const _rotConnect = _loadDll('RotConnect');
39
+ const _rotDisconnect = _loadDll('RotDisconnect');
40
+ const _rotSendCmd = _loadDll('RotSendCommand');
41
+ const _rotSendResult = _loadDll('RotSendWithResult');
42
+ const _rotIsBusy = _loadDll('RotIsBusy');
43
+ const _rotHasDoc = _loadDll('RotHasDoc');
44
+ const _rotDocInfo = _loadDll('RotGetActiveDocInfo');
45
+ const _rotBringToFront = _loadDll('RotBringToFront');
46
+ const _rotNewDoc = _loadDll('RotNewDoc');
47
+ const _rotEnum = _loadEnum();
48
+
49
+ let _monikerSession = null;
50
+
9
51
  const BUSY_RETRIES = parseInt(process.env.BUSY_RETRIES || '10', 10);
10
52
  const BUSY_DELAY = parseInt(process.env.BUSY_DELAY || '500', 10);
11
53
 
12
- // Lisp expression cache (hash -> compiled Lisp code)
13
54
  const LISP_CACHE = new Map();
14
55
  const LISP_CACHE_MAX = parseInt(process.env.LISP_CACHE_MAX || '500', 10);
15
-
16
56
  const CACHE_TTL = parseInt(process.env.LISP_CACHE_TTL || '300000', 10);
17
57
 
18
58
  function getCachedLisp(code) {
@@ -123,7 +163,7 @@ const cadLaunch = edge.func(function() {/*
123
163
  }
124
164
  } catch (Exception ex) {
125
165
  System.Diagnostics.Debug.WriteLine("cadLaunch create " + progIds[i] + " error: " + ex.Message);
126
- }
166
+ }
127
167
  }
128
168
 
129
169
  return new { success = false, error = "No CAD found" };
@@ -135,14 +175,14 @@ const cadSend = edge.func(function() {/*
135
175
  try {
136
176
  dynamic d = input;
137
177
  string code = d.code.ToString();
138
- string platform = d.platform.ToString();
139
- string progId = platform == "BricsCAD" ? "BricscadApp.AcadApplication" : platform + ".Application";
178
+ string platform = d.platform.ToString();
179
+ string progId = platform == "BricsCAD" ? "BricscadApp.AcadApplication" : platform + ".Application";
140
180
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
141
181
  if (cad != null) {
142
182
  dynamic doc = cad.ActiveDocument;
143
- if (doc != null) {
144
- doc.SendCommand(code);
145
- return new { success = true };
183
+ if (doc != null) {
184
+ doc.SendCommand(code);
185
+ return new { success = true };
146
186
  }
147
187
  }
148
188
  } catch (Exception ex) {
@@ -165,18 +205,16 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
165
205
  string codeHash = d.codeHash != null ? d.codeHash.ToString() : "";
166
206
  bool cacheEnabled = d.cacheEnabled != null && d.cacheEnabled.ToString() == "True";
167
207
  bool hasCacheKey = cacheEnabled && !string.IsNullOrEmpty(codeHash);
168
- string progId = platform == "BricsCAD" ? "BricscadApp.AcadApplication" : platform + ".Application";
169
- string tempDir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "@lisp", "tmp");
208
+ string progId = platform == "BricsCAD" ? "BricscadApp.AcadApplication" : platform + ".Application";
209
+ string tempDir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "@lisp", "tmp");
170
210
  System.IO.Directory.CreateDirectory(tempDir);
171
-
172
- // 结果文件始终使用 GUID 命名(每次调用唯一)
211
+
173
212
  tempFile = System.IO.Path.Combine(tempDir, "atlisp_result_" + System.Guid.NewGuid().ToString("N") + ".txt");
174
-
175
- // 代码文件:启用缓存时使用 hash 命名,否则使用 GUID 命名
213
+
176
214
  string fileKey = hasCacheKey ? codeHash : System.Guid.NewGuid().ToString("N");
177
215
  userCodeFile = System.IO.Path.Combine(tempDir, "atlisp_user_" + fileKey + ".lsp");
178
216
  lispFile = System.IO.Path.Combine(tempDir, "atlisp_code_" + fileKey + ".lsp");
179
-
217
+
180
218
  bool useCachedUserCode = false;
181
219
  if (hasCacheKey) {
182
220
  if (System.IO.File.Exists(userCodeFile)) {
@@ -186,11 +224,11 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
186
224
  }
187
225
  }
188
226
  }
189
-
227
+
190
228
  if (!useCachedUserCode) {
191
229
  System.IO.File.WriteAllText(userCodeFile, code, System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage));
192
230
  }
193
-
231
+
194
232
  string lispCode = "(progn"
195
233
  + "(setq f(open \"" + tempFile.Replace("\\", "\\\\") + "\" \"w\"))"
196
234
  + "(setq r(vl-catch-all-apply '(lambda ()(load \"" + userCodeFile.Replace("\\", "\\\\") + "\"))))"
@@ -199,15 +237,15 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
199
237
  + "(princ r f))"
200
238
  + "(close f)"
201
239
  + "(princ))";
202
-
203
- System.IO.File.WriteAllText(lispFile, lispCode, System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage));
204
-
240
+
241
+ System.IO.File.WriteAllText(lispFile, lispCode, System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage));
242
+
205
243
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
206
244
  if (cad != null) {
207
245
  dynamic doc = cad.ActiveDocument;
208
- if (doc != null) {
209
- doc.SendCommand("(load \"" + lispFile.Replace("\\", "\\\\") + "\")\n");
210
-
246
+ if (doc != null) {
247
+ doc.SendCommand("(load \"" + lispFile.Replace("\\", "\\\\") + "\")\n");
248
+
211
249
  int retries = 10;
212
250
  string result = "";
213
251
  while (retries > 0 && !System.IO.File.Exists(tempFile)) {
@@ -220,7 +258,7 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
220
258
  try {
221
259
  byte[] bytes = System.IO.File.ReadAllBytes(tempFile);
222
260
  System.Text.Encoding enc;
223
-
261
+
224
262
  if (!string.IsNullOrEmpty(encoding)) {
225
263
  enc = System.Text.Encoding.GetEncoding(encoding);
226
264
  } else {
@@ -239,13 +277,13 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
239
277
  }
240
278
  }
241
279
  }
242
-
280
+
243
281
  result = enc.GetString(bytes).Trim();
244
-
282
+
245
283
  if (result.StartsWith("ERROR:")) {
246
284
  return new { success = false, error = result.Substring(6).Trim() };
247
285
  }
248
-
286
+
249
287
  try { System.IO.File.Delete(tempFile); } catch {}
250
288
  try { System.IO.File.Delete(lispFile); } catch {}
251
289
  if (!hasCacheKey) { try { if (System.IO.File.Exists(userCodeFile)) System.IO.File.Delete(userCodeFile); } catch {} }
@@ -512,184 +550,208 @@ function computeCodeHash(code, cacheEnabled) {
512
550
  return '';
513
551
  }
514
552
 
515
- async function handleMessage(msg) {
516
- if (msg.type === 'ping') {
517
- return { pong: true };
553
+ function dllCall(fn, args) {
554
+ return new Promise((resolve, reject) => {
555
+ if (!fn) return resolve({ success: false, error: 'DLL method not available' });
556
+ fn(args, (e, r) => {
557
+ if (e) reject(e);
558
+ else resolve(r);
559
+ });
560
+ });
561
+ }
562
+
563
+ async function monikerOrEdge(rotFn, edgeFn, args, platform) {
564
+ if (_monikerSession && rotFn) return await dllCall(rotFn, { key: _monikerSession, ...args });
565
+ return new Promise((resolve, reject) => {
566
+ edgeFn(platform || 'AutoCAD', (e, r) => {
567
+ if (e) reject(e);
568
+ else resolve(r);
569
+ });
570
+ });
571
+ }
572
+
573
+ function validateCode(code) {
574
+ const parenCheck = checkParens(code);
575
+ if (!parenCheck.valid) {
576
+ return { valid: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
518
577
  }
519
- if (msg.type === 'connect') {
520
- if (msg.platform) {
521
- return await connectToPlatform(msg.platform);
522
- }
523
- try {
524
- const result = await new Promise((resolve, reject) => {
525
- cadConnect({}, (e, r) => {
526
- if (e) reject(e);
527
- else resolve(r);
528
- });
529
- });
530
- if (result && result.success) return result;
531
- } catch (e) {
532
- log('cadConnect failed: ' + e.message);
578
+ const securityLevel = process.env.SECURITY_LEVEL || 'strict';
579
+ if (securityLevel !== 'relaxed') {
580
+ const injectionCheck = validateLispCodeWorker(code);
581
+ if (!injectionCheck.valid) {
582
+ return { valid: false, error: `安全限制: ${injectionCheck.error}` };
533
583
  }
534
- return new Promise((resolve, reject) => {
535
- cadLaunch({}, (e, r) => {
536
- if (e) reject(e);
537
- else resolve(r);
538
- });
539
- });
540
584
  }
541
- if (msg.type === 'hasdoc') {
542
- return new Promise((resolve, reject) => {
543
- cadHasDoc(msg.platform || 'AutoCAD', (e, r) => {
544
- if (e) reject(e);
545
- else resolve(r);
546
- });
585
+ return { valid: true };
586
+ }
587
+
588
+ async function ensureDocument(platform) {
589
+ if (_monikerSession) return true;
590
+ const docCheck = await new Promise((resolve, reject) => {
591
+ cadHasDoc(platform || 'AutoCAD', (e, r) => {
592
+ if (e) reject(e);
593
+ else resolve(r);
547
594
  });
595
+ });
596
+ return docCheck.hasDoc;
597
+ }
598
+
599
+ async function executeWithRetry(fn, label) {
600
+ const maxRetries = 3;
601
+ let lastError = null;
602
+ for (let i = 0; i < maxRetries; i++) {
603
+ const isBusy = await waitForIdle();
604
+ if (isBusy) {
605
+ try {
606
+ return await fn();
607
+ } catch (e) {
608
+ log(`${label} error: ${e.message}`);
609
+ lastError = e;
610
+ }
611
+ }
612
+ await new Promise(r => setTimeout(r, 500));
548
613
  }
549
- if (msg.type === 'newdoc') {
550
- return new Promise((resolve, reject) => {
551
- cadNewDoc(msg.platform || 'AutoCAD', (e, r) => {
552
- if (e) reject(e);
553
- else resolve(r);
554
- });
555
- });
614
+ return { success: false, error: lastError?.message || '发送失败' };
615
+ }
616
+
617
+ async function executeLisp(msg, withResult) {
618
+ if (!msg.code.endsWith('\n')) {
619
+ msg.code += '\n';
556
620
  }
557
- if (msg.type === 'bringToFront') {
558
- return new Promise((resolve, reject) => {
559
- cadBringToFront(msg.platform || 'AutoCAD', (e, r) => {
560
- if (e) reject(e);
561
- else resolve(r);
562
- });
563
- });
621
+
622
+ const validation = validateCode(msg.code);
623
+ if (!validation.valid) return validation;
624
+
625
+ if (!await ensureDocument(msg.platform)) {
626
+ return { success: false, error: 'No open document' };
564
627
  }
565
- if (msg.type === 'send') {
566
- if (!msg.code.endsWith('\n')) {
567
- msg.code += '\n';
568
- }
569
- const docCheck = await new Promise((resolve, reject) => {
570
- cadHasDoc(msg.platform || 'AutoCAD', (e, r) => {
571
- if (e) reject(e);
572
- else resolve(r);
573
- });
574
- });
575
- if (!docCheck.hasDoc) {
576
- return { success: false, error: 'No open document' };
577
- }
578
- const parenCheck = checkParens(msg.code);
579
- if (!parenCheck.valid) {
580
- return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
581
- }
582
- const securityLevel = process.env.SECURITY_LEVEL || 'strict';
583
- if (securityLevel !== 'relaxed') {
584
- const injectionCheck = validateLispCodeWorker(msg.code);
585
- if (!injectionCheck.valid) {
586
- return { success: false, error: `安全限制: ${injectionCheck.error}` };
587
- }
628
+
629
+ if (_monikerSession) {
630
+ if (withResult || msg.code.length > 240) {
631
+ return await dllCall(_rotSendResult, { key: _monikerSession, code: msg.code, encoding: msg.encoding || '' });
588
632
  }
589
- if (msg.code.length > 255) {
590
- const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
591
- const codeHash = computeCodeHash(msg.code, cacheEnabled);
592
- log(`send (long code ${msg.code.length} chars, fallback to sendResult, cache=${cacheEnabled}): ${msg.code.substring(0, 80)}...`);
633
+ return await dllCall(_rotSendCmd, { key: _monikerSession, code: msg.code });
634
+ }
635
+
636
+ if (withResult || msg.code.length > 255) {
637
+ const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
638
+ const codeHash = computeCodeHash(msg.code, cacheEnabled);
639
+ log(`sendResult (${msg.code.length} chars, cache=${cacheEnabled}): ${msg.code.substring(0, 80)}...`);
640
+ return await executeWithRetry(async () => {
593
641
  const r = await new Promise((resolve, reject) => {
594
- cadSendWithResult({ code: msg.code, platform: msg.platform || 'AutoCAD', encoding: '', codeHash, cacheEnabled }, (e, r) => {
642
+ cadSendWithResult({ code: msg.code, platform: msg.platform, encoding: msg.encoding || '', codeHash, cacheEnabled }, (e, r) => {
595
643
  if (e) reject(e);
596
644
  else resolve(r);
597
645
  });
598
646
  });
599
- log(`send result (long code): ${JSON.stringify(r)}`);
647
+ log(`sendResult result: ${JSON.stringify(r)}`);
600
648
  return r;
601
- }
602
- const maxRetries = 3;
603
- let lastError = null;
604
- for (let i = 0; i < maxRetries; i++) {
605
- const isBusy = await waitForIdle(msg.platform || 'AutoCAD');
606
- if (isBusy) {
607
- log(`send: ${msg.code}`);
608
- try {
609
- const r = await new Promise((resolve, reject) => {
610
- cadSend({ code: msg.code, platform: msg.platform }, (e, r) => {
611
- if (e) reject(e);
612
- else resolve(r);
613
- });
614
- });
615
- log(`send result: ${JSON.stringify(r)}`);
616
- return r;
617
- } catch (e) {
618
- log(`send error: ${e.message}`);
619
- lastError = e;
620
- }
621
- }
622
- await new Promise(r => setTimeout(r, 500));
623
- }
624
- return { success: false, error: lastError?.message || '发送失败' };
649
+ }, 'sendResult');
625
650
  }
626
- if (msg.type === 'sendResult') {
627
- const docCheck = await new Promise((resolve, reject) => {
628
- cadHasDoc(msg.platform || 'AutoCAD', (e, r) => {
651
+
652
+ log(`send: ${msg.code}`);
653
+ return await executeWithRetry(async () => {
654
+ const r = await new Promise((resolve, reject) => {
655
+ cadSend({ code: msg.code, platform: msg.platform }, (e, r) => {
629
656
  if (e) reject(e);
630
657
  else resolve(r);
631
658
  });
632
659
  });
633
- if (!docCheck.hasDoc) {
634
- return { success: false, error: 'No open document' };
635
- }
636
- const parenCheck = checkParens(msg.code);
637
- if (!parenCheck.valid) {
638
- return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
639
- }
640
- const injectionCheck = validateLispCodeWorker(msg.code);
641
- if (!injectionCheck.valid) {
642
- return { success: false, error: `安全限制: ${injectionCheck.error}` };
643
- }
644
- const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
645
- const codeHash = computeCodeHash(msg.code, cacheEnabled);
646
- const maxRetriesResult = 3;
647
- let lastErrorResult = null;
648
- for (let i = 0; i < maxRetriesResult; i++) {
649
- const isBusy = await waitForIdle(msg.platform);
650
- if (isBusy) {
651
- log(`sendResult: ${msg.code}`);
652
- try {
653
- const r = await new Promise((resolve, reject) => {
654
- cadSendWithResult({ code: msg.code, platform: msg.platform, encoding: msg.encoding || '', codeHash, cacheEnabled }, (e, r) => {
655
- if (e) reject(e);
656
- else resolve(r);
657
- });
658
- });
659
- log(`sendResult result: ${JSON.stringify(r)}`);
660
- return r;
661
- } catch (e) {
662
- log(`sendResult error: ${e.message}`);
663
- lastErrorResult = e;
664
- }
660
+ log(`send result: ${JSON.stringify(r)}`);
661
+ return r;
662
+ }, 'send');
663
+ }
664
+
665
+ const MESSAGE_HANDLERS = {
666
+ ping() {
667
+ return { pong: true };
668
+ },
669
+
670
+ async connect(msg) {
671
+ if (msg.moniker && _rotConnect) {
672
+ const result = await dllCall(_rotConnect, { key: msg.monikerKey, moniker: msg.moniker });
673
+ if (result && result.success) {
674
+ _monikerSession = msg.monikerKey;
675
+ return result;
665
676
  }
666
- await new Promise(r => setTimeout(r, 500));
677
+ return result;
667
678
  }
668
- return { success: false, error: lastErrorResult?.message || '发送失败' };
669
- }
670
- if (msg.type === 'isBusy') {
671
- return new Promise((resolve, reject) => {
672
- cadIsBusy(msg.platform || 'AutoCAD', (e, r) => {
673
- if (e) reject(e);
674
- else resolve(r);
679
+ if (msg.platform) {
680
+ return await connectToPlatform(msg.platform);
681
+ }
682
+ try {
683
+ const result = await new Promise((resolve, reject) => {
684
+ cadConnect({}, (e, r) => {
685
+ if (e) reject(e);
686
+ else resolve(r);
687
+ });
675
688
  });
676
- });
677
- }
678
- if (msg.type === 'getActiveDocInfo') {
689
+ if (result && result.success) return result;
690
+ } catch (e) {
691
+ log('cadConnect failed: ' + e.message);
692
+ }
679
693
  return new Promise((resolve, reject) => {
680
- cadGetActiveDocInfo(msg.platform || 'AutoCAD', (e, r) => {
694
+ cadLaunch({}, (e, r) => {
681
695
  if (e) reject(e);
682
696
  else resolve(r);
683
697
  });
684
698
  });
685
- }
686
- if (msg.type === 'startMonitor') {
699
+ },
700
+
701
+ async disconnect() {
702
+ if (_monikerSession && _rotDisconnect) {
703
+ await dllCall(_rotDisconnect, { key: _monikerSession });
704
+ }
705
+ _monikerSession = null;
706
+ return { success: true };
707
+ },
708
+
709
+ async hasdoc(msg) {
710
+ return await monikerOrEdge(_rotHasDoc, cadHasDoc, {}, msg.platform);
711
+ },
712
+
713
+ async newdoc(msg) {
714
+ return await monikerOrEdge(_rotNewDoc, cadNewDoc, {}, msg.platform);
715
+ },
716
+
717
+ async bringToFront(msg) {
718
+ return await monikerOrEdge(_rotBringToFront, cadBringToFront, {}, msg.platform);
719
+ },
720
+
721
+ async send(msg) {
722
+ return await executeLisp(msg, false);
723
+ },
724
+
725
+ async sendResult(msg) {
726
+ return await executeLisp(msg, true);
727
+ },
728
+
729
+ async isBusy(msg) {
730
+ return await monikerOrEdge(_rotIsBusy, cadIsBusy, {}, msg.platform);
731
+ },
732
+
733
+ async getActiveDocInfo(msg) {
734
+ return await monikerOrEdge(_rotDocInfo, cadGetActiveDocInfo, {}, msg.platform);
735
+ },
736
+
737
+ async startMonitor(msg) {
738
+ if (_monikerSession) {
739
+ return { success: false, error: 'startMonitor not supported for moniker connections' };
740
+ }
687
741
  return new Promise((resolve, reject) => {
688
742
  cadStartMonitor(msg.platform || 'AutoCAD', (e, r) => {
689
743
  if (e) reject(e);
690
744
  else resolve(r);
691
745
  });
692
746
  });
693
- }
694
- return { error: 'unknown message type' };
747
+ },
748
+ };
749
+
750
+ export async function handleMessage(msg) {
751
+ const handler = MESSAGE_HANDLERS[msg.type];
752
+ if (!handler) return { error: 'unknown message type' };
753
+ return await handler(msg);
695
754
  }
755
+
756
+ // Exported for testing
757
+ export { validateCode, computeCodeHash, getCachedLisp, setCachedLisp, MESSAGE_HANDLERS, executeLisp };