@yushaw/sanqian-sdk 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,290 +30,300 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- Conversation: () => Conversation,
34
- DiscoveryManager: () => DiscoveryManager,
35
- ErrorMessages: () => ErrorMessages,
36
- SANQIAN_WEBSITE: () => SANQIAN_WEBSITE,
37
- SDKErrorCode: () => SDKErrorCode,
38
- SanqianSDK: () => SanqianSDK,
39
- SanqianSDKError: () => SanqianSDKError,
40
- createSDKError: () => createSDKError
41
- });
42
- module.exports = __toCommonJS(index_exports);
43
-
44
- // src/client.ts
45
- var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
46
-
47
33
  // src/discovery.ts
48
- var import_fs = require("fs");
49
- var import_os = require("os");
50
- var import_path = require("path");
51
- var import_child_process = require("child_process");
52
- var DiscoveryManager = class {
53
- connectionInfo = null;
54
- watcher = null;
55
- onChange = null;
56
- pollInterval = null;
57
- /**
58
- * Cached executable path from last successful connection.json read.
59
- * Persists even when Sanqian is not running, for use in auto-launch.
60
- */
61
- cachedExecutable = null;
62
- /**
63
- * Get the path to connection.json
64
- */
65
- getConnectionFilePath() {
66
- return (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime", "connection.json");
67
- }
68
- /**
69
- * Read and validate connection info
70
- *
71
- * Returns null if:
72
- * - File doesn't exist
73
- * - File is invalid JSON
74
- * - Process is not running
75
- */
76
- read() {
77
- const filePath = this.getConnectionFilePath();
78
- if (!(0, import_fs.existsSync)(filePath)) {
79
- return null;
80
- }
81
- try {
82
- const content = (0, import_fs.readFileSync)(filePath, "utf-8");
83
- const info = JSON.parse(content);
84
- if (!info.port || !info.token || !info.pid) {
85
- return null;
86
- }
87
- if (info.executable) {
88
- this.cachedExecutable = info.executable;
89
- }
90
- if (!this.isProcessRunning(info.pid)) {
91
- return null;
34
+ var discovery_exports = {};
35
+ __export(discovery_exports, {
36
+ DiscoveryManager: () => DiscoveryManager
37
+ });
38
+ var import_fs, import_os, import_path, import_child_process, DiscoveryManager;
39
+ var init_discovery = __esm({
40
+ "src/discovery.ts"() {
41
+ "use strict";
42
+ import_fs = require("fs");
43
+ import_os = require("os");
44
+ import_path = require("path");
45
+ import_child_process = require("child_process");
46
+ DiscoveryManager = class {
47
+ connectionInfo = null;
48
+ watcher = null;
49
+ onChange = null;
50
+ pollInterval = null;
51
+ /**
52
+ * Cached executable path from last successful connection.json read.
53
+ * Persists even when Sanqian is not running, for use in auto-launch.
54
+ */
55
+ cachedExecutable = null;
56
+ /**
57
+ * Get the path to connection.json
58
+ */
59
+ getConnectionFilePath() {
60
+ return (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime", "connection.json");
92
61
  }
93
- this.connectionInfo = info;
94
- return info;
95
- } catch {
96
- return null;
97
- }
98
- }
99
- /**
100
- * Start watching for connection file changes
101
- */
102
- startWatching(onChange) {
103
- this.onChange = onChange;
104
- const dir = (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime");
105
- if (!(0, import_fs.existsSync)(dir)) {
106
- this.pollInterval = setInterval(() => {
107
- if ((0, import_fs.existsSync)(dir)) {
108
- if (this.pollInterval) {
109
- clearInterval(this.pollInterval);
110
- this.pollInterval = null;
62
+ /**
63
+ * Read and validate connection info
64
+ *
65
+ * Returns null if:
66
+ * - File doesn't exist
67
+ * - File is invalid JSON
68
+ * - Process is not running
69
+ */
70
+ read() {
71
+ const filePath = this.getConnectionFilePath();
72
+ if (!(0, import_fs.existsSync)(filePath)) {
73
+ return null;
74
+ }
75
+ try {
76
+ const content = (0, import_fs.readFileSync)(filePath, "utf-8");
77
+ const info = JSON.parse(content);
78
+ if (!info.port || !info.token || !info.pid) {
79
+ return null;
80
+ }
81
+ if (info.executable) {
82
+ this.cachedExecutable = info.executable;
83
+ }
84
+ if (!this.isProcessRunning(info.pid)) {
85
+ return null;
111
86
  }
112
- this.setupWatcher(dir);
87
+ this.connectionInfo = info;
88
+ return info;
89
+ } catch {
90
+ return null;
113
91
  }
114
- }, 2e3);
115
- return;
116
- }
117
- this.setupWatcher(dir);
118
- }
119
- setupWatcher(dir) {
120
- try {
121
- this.watcher = (0, import_fs.watch)(dir, (event, filename) => {
122
- if (filename === "connection.json") {
123
- setTimeout(() => {
124
- const newInfo = this.read();
125
- const oldInfoStr = JSON.stringify(this.connectionInfo);
126
- const newInfoStr = JSON.stringify(newInfo);
127
- if (oldInfoStr !== newInfoStr) {
128
- this.connectionInfo = newInfo;
129
- this.onChange?.(newInfo);
92
+ }
93
+ /**
94
+ * Start watching for connection file changes
95
+ */
96
+ startWatching(onChange) {
97
+ this.onChange = onChange;
98
+ const dir = (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime");
99
+ if (!(0, import_fs.existsSync)(dir)) {
100
+ this.pollInterval = setInterval(() => {
101
+ if ((0, import_fs.existsSync)(dir)) {
102
+ if (this.pollInterval) {
103
+ clearInterval(this.pollInterval);
104
+ this.pollInterval = null;
105
+ }
106
+ this.setupWatcher(dir);
130
107
  }
131
- }, 100);
108
+ }, 2e3);
109
+ return;
132
110
  }
133
- });
134
- } catch (e) {
135
- console.error("Failed to watch connection file:", e);
136
- }
137
- }
138
- /**
139
- * Stop watching
140
- */
141
- stopWatching() {
142
- if (this.pollInterval) {
143
- clearInterval(this.pollInterval);
144
- this.pollInterval = null;
145
- }
146
- this.watcher?.close();
147
- this.watcher = null;
148
- this.onChange = null;
149
- }
150
- /**
151
- * Check if a process is running by PID
152
- *
153
- * Cross-platform implementation:
154
- * - macOS/Linux: process.kill(pid, 0) works correctly
155
- * - Windows: process.kill(pid, 0) can give false positives due to PID reuse,
156
- * so we use tasklist command for reliable checking
157
- */
158
- isProcessRunning(pid) {
159
- try {
160
- if ((0, import_os.platform)() === "win32") {
111
+ this.setupWatcher(dir);
112
+ }
113
+ setupWatcher(dir) {
161
114
  try {
162
- const result = (0, import_child_process.execSync)(`tasklist /FI "PID eq ${pid}" /NH`, {
163
- encoding: "utf-8",
164
- stdio: ["pipe", "pipe", "pipe"]
115
+ this.watcher = (0, import_fs.watch)(dir, (event, filename) => {
116
+ if (filename === "connection.json") {
117
+ setTimeout(() => {
118
+ const newInfo = this.read();
119
+ const oldInfoStr = JSON.stringify(this.connectionInfo);
120
+ const newInfoStr = JSON.stringify(newInfo);
121
+ if (oldInfoStr !== newInfoStr) {
122
+ this.connectionInfo = newInfo;
123
+ this.onChange?.(newInfo);
124
+ }
125
+ }, 100);
126
+ }
165
127
  });
166
- const trimmed = result.trim();
167
- if (!trimmed || trimmed.toUpperCase().startsWith("INFO")) {
168
- return false;
128
+ } catch (e) {
129
+ console.error("Failed to watch connection file:", e);
130
+ }
131
+ }
132
+ /**
133
+ * Stop watching
134
+ */
135
+ stopWatching() {
136
+ if (this.pollInterval) {
137
+ clearInterval(this.pollInterval);
138
+ this.pollInterval = null;
139
+ }
140
+ this.watcher?.close();
141
+ this.watcher = null;
142
+ this.onChange = null;
143
+ }
144
+ /**
145
+ * Check if a process is running by PID
146
+ *
147
+ * Cross-platform implementation:
148
+ * - macOS/Linux: process.kill(pid, 0) works correctly
149
+ * - Windows: process.kill(pid, 0) can give false positives due to PID reuse,
150
+ * so we use tasklist command for reliable checking
151
+ */
152
+ isProcessRunning(pid) {
153
+ try {
154
+ if ((0, import_os.platform)() === "win32") {
155
+ try {
156
+ const result = (0, import_child_process.execSync)(`tasklist /FI "PID eq ${pid}" /NH`, {
157
+ encoding: "utf-8",
158
+ stdio: ["pipe", "pipe", "pipe"]
159
+ });
160
+ const trimmed = result.trim();
161
+ if (!trimmed || trimmed.toUpperCase().startsWith("INFO")) {
162
+ return false;
163
+ }
164
+ return new RegExp(`\\b${pid}\\b`).test(trimmed);
165
+ } catch {
166
+ return false;
167
+ }
168
+ } else {
169
+ process.kill(pid, 0);
170
+ return true;
169
171
  }
170
- return new RegExp(`\\b${pid}\\b`).test(trimmed);
171
172
  } catch {
172
173
  return false;
173
174
  }
174
- } else {
175
- process.kill(pid, 0);
176
- return true;
177
175
  }
178
- } catch {
179
- return false;
180
- }
181
- }
182
- /**
183
- * Get cached connection info (may be stale)
184
- */
185
- getCached() {
186
- return this.connectionInfo;
187
- }
188
- /**
189
- * Build WebSocket URL from connection info
190
- */
191
- buildWebSocketUrl(info) {
192
- const wsPath = info.ws_path || "/ws/apps";
193
- return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
194
- }
195
- /**
196
- * Build HTTP base URL from connection info
197
- */
198
- buildHttpUrl(info) {
199
- return `http://127.0.0.1:${info.port}`;
200
- }
201
- /**
202
- * Find Sanqian executable path
203
- * Searches in standard installation locations for each platform
204
- */
205
- findSanqianPath(customPath) {
206
- if (customPath) {
207
- if ((0, import_fs.existsSync)(customPath)) {
208
- return customPath;
176
+ /**
177
+ * Get cached connection info (may be stale)
178
+ */
179
+ getCached() {
180
+ return this.connectionInfo;
209
181
  }
210
- console.warn(`[SDK] Custom Sanqian path not found: ${customPath}`);
211
- return null;
212
- }
213
- const os = (0, import_os.platform)();
214
- const searchPaths = [];
215
- if (os === "darwin") {
216
- searchPaths.push(
217
- // Production: installed app
218
- "/Applications/Sanqian.app/Contents/MacOS/Sanqian",
219
- (0, import_path.join)((0, import_os.homedir)(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
220
- // Development: electron-builder output
221
- (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
222
- (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
223
- );
224
- } else if (os === "win32") {
225
- const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
226
- const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
227
- const localAppData = process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local");
228
- searchPaths.push(
229
- // Production: NSIS installer uses lowercase directory name from package.json "name"
230
- (0, import_path.join)(localAppData, "Programs", "sanqian", "Sanqian.exe"),
231
- // Legacy/alternative paths with uppercase
232
- (0, import_path.join)(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
233
- (0, import_path.join)(programFiles, "Sanqian", "Sanqian.exe"),
234
- (0, import_path.join)(programFilesX86, "Sanqian", "Sanqian.exe"),
235
- // Development: electron-builder output
236
- (0, import_path.join)((0, import_os.homedir)(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
237
- );
238
- } else {
239
- searchPaths.push(
240
- "/usr/bin/sanqian",
241
- "/usr/local/bin/sanqian",
242
- (0, import_path.join)((0, import_os.homedir)(), ".local/bin/sanqian"),
243
- "/opt/Sanqian/sanqian",
244
- // Development
245
- (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/linux-unpacked/sanqian")
246
- );
247
- }
248
- for (const path of searchPaths) {
249
- if ((0, import_fs.existsSync)(path)) {
250
- return path;
182
+ /**
183
+ * Build WebSocket URL from connection info
184
+ */
185
+ buildWebSocketUrl(info) {
186
+ const wsPath = info.ws_path || "/ws/apps";
187
+ return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
251
188
  }
252
- }
253
- return null;
254
- }
255
- /**
256
- * Launch Sanqian in hidden/tray mode
257
- * Returns true if launch was initiated successfully
258
- *
259
- * Priority for finding Sanqian executable:
260
- * 1. customPath parameter (if provided)
261
- * 2. Cached executable from connection.json (most reliable)
262
- * 3. Search in standard installation locations (fallback)
263
- */
264
- launchSanqian(customPath) {
265
- let sanqianPath = null;
266
- if (customPath) {
267
- sanqianPath = this.findSanqianPath(customPath);
268
- } else if (this.cachedExecutable && (0, import_fs.existsSync)(this.cachedExecutable)) {
269
- sanqianPath = this.cachedExecutable;
270
- console.log(`[SDK] Using cached executable: ${sanqianPath}`);
271
- } else {
272
- sanqianPath = this.findSanqianPath();
273
- }
274
- if (!sanqianPath) {
275
- console.error("[SDK] Sanqian executable not found");
276
- return false;
277
- }
278
- console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
279
- try {
280
- const os = (0, import_os.platform)();
281
- if (os === "darwin") {
282
- const appPath = sanqianPath.replace(
283
- "/Contents/MacOS/Sanqian",
284
- ""
285
- );
286
- (0, import_child_process.spawn)("open", ["-g", "-a", appPath, "--args", "--hidden"], {
287
- detached: true,
288
- stdio: "ignore"
289
- }).unref();
290
- } else {
291
- (0, import_child_process.spawn)(sanqianPath, ["--hidden"], {
292
- detached: true,
293
- stdio: "ignore",
294
- // On Windows, hide the console window
295
- ...os === "win32" && {
296
- windowsHide: true,
297
- shell: false
189
+ /**
190
+ * Build HTTP base URL from connection info
191
+ */
192
+ buildHttpUrl(info) {
193
+ return `http://127.0.0.1:${info.port}`;
194
+ }
195
+ /**
196
+ * Find Sanqian executable path
197
+ * Searches in standard installation locations for each platform
198
+ */
199
+ findSanqianPath(customPath) {
200
+ if (customPath) {
201
+ if ((0, import_fs.existsSync)(customPath)) {
202
+ return customPath;
298
203
  }
299
- }).unref();
204
+ console.warn(`[SDK] Custom Sanqian path not found: ${customPath}`);
205
+ return null;
206
+ }
207
+ const os = (0, import_os.platform)();
208
+ const searchPaths = [];
209
+ if (os === "darwin") {
210
+ searchPaths.push(
211
+ // Production: installed app
212
+ "/Applications/Sanqian.app/Contents/MacOS/Sanqian",
213
+ (0, import_path.join)((0, import_os.homedir)(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
214
+ // Development: electron-builder output
215
+ (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
216
+ (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
217
+ );
218
+ } else if (os === "win32") {
219
+ const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
220
+ const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
221
+ const localAppData = process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local");
222
+ searchPaths.push(
223
+ // Production: NSIS installer uses lowercase directory name from package.json "name"
224
+ (0, import_path.join)(localAppData, "Programs", "sanqian", "Sanqian.exe"),
225
+ // Legacy/alternative paths with uppercase
226
+ (0, import_path.join)(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
227
+ (0, import_path.join)(programFiles, "Sanqian", "Sanqian.exe"),
228
+ (0, import_path.join)(programFilesX86, "Sanqian", "Sanqian.exe"),
229
+ // Development: electron-builder output
230
+ (0, import_path.join)((0, import_os.homedir)(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
231
+ );
232
+ } else {
233
+ searchPaths.push(
234
+ "/usr/bin/sanqian",
235
+ "/usr/local/bin/sanqian",
236
+ (0, import_path.join)((0, import_os.homedir)(), ".local/bin/sanqian"),
237
+ "/opt/Sanqian/sanqian",
238
+ // Development
239
+ (0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/linux-unpacked/sanqian")
240
+ );
241
+ }
242
+ for (const path of searchPaths) {
243
+ if ((0, import_fs.existsSync)(path)) {
244
+ return path;
245
+ }
246
+ }
247
+ return null;
300
248
  }
301
- return true;
302
- } catch (e) {
303
- console.error("[SDK] Failed to launch Sanqian:", e);
304
- return false;
305
- }
306
- }
307
- /**
308
- * Check if Sanqian is running
309
- */
310
- isSanqianRunning() {
311
- return this.read() !== null;
249
+ /**
250
+ * Launch Sanqian in hidden/tray mode
251
+ * Returns true if launch was initiated successfully
252
+ *
253
+ * Priority for finding Sanqian executable:
254
+ * 1. customPath parameter (if provided)
255
+ * 2. Cached executable from connection.json (most reliable)
256
+ * 3. Search in standard installation locations (fallback)
257
+ */
258
+ launchSanqian(customPath) {
259
+ let sanqianPath = null;
260
+ if (customPath) {
261
+ sanqianPath = this.findSanqianPath(customPath);
262
+ } else if (this.cachedExecutable && (0, import_fs.existsSync)(this.cachedExecutable)) {
263
+ sanqianPath = this.cachedExecutable;
264
+ console.log(`[SDK] Using cached executable: ${sanqianPath}`);
265
+ } else {
266
+ sanqianPath = this.findSanqianPath();
267
+ }
268
+ if (!sanqianPath) {
269
+ console.error("[SDK] Sanqian executable not found");
270
+ return false;
271
+ }
272
+ console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
273
+ try {
274
+ const os = (0, import_os.platform)();
275
+ if (os === "darwin") {
276
+ const appPath = sanqianPath.replace(
277
+ "/Contents/MacOS/Sanqian",
278
+ ""
279
+ );
280
+ (0, import_child_process.spawn)("open", ["-g", "-a", appPath, "--args", "--hidden"], {
281
+ detached: true,
282
+ stdio: "ignore"
283
+ }).unref();
284
+ } else {
285
+ (0, import_child_process.spawn)(sanqianPath, ["--hidden"], {
286
+ detached: true,
287
+ stdio: "ignore",
288
+ // On Windows, hide the console window
289
+ ...os === "win32" && {
290
+ windowsHide: true,
291
+ shell: false
292
+ }
293
+ }).unref();
294
+ }
295
+ return true;
296
+ } catch (e) {
297
+ console.error("[SDK] Failed to launch Sanqian:", e);
298
+ return false;
299
+ }
300
+ }
301
+ /**
302
+ * Check if Sanqian is running
303
+ */
304
+ isSanqianRunning() {
305
+ return this.read() !== null;
306
+ }
307
+ };
312
308
  }
313
- };
309
+ });
310
+
311
+ // src/index.ts
312
+ var src_exports = {};
313
+ __export(src_exports, {
314
+ Conversation: () => Conversation,
315
+ DiscoveryManager: () => DiscoveryManager,
316
+ ErrorMessages: () => ErrorMessages,
317
+ SANQIAN_WEBSITE: () => SANQIAN_WEBSITE,
318
+ SDKErrorCode: () => SDKErrorCode,
319
+ SanqianSDK: () => SanqianSDK,
320
+ SanqianSDKError: () => SanqianSDKError,
321
+ createSDKError: () => createSDKError
322
+ });
323
+ module.exports = __toCommonJS(src_exports);
324
+
325
+ // src/client.ts
326
+ var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
314
327
 
315
328
  // src/errors.ts
316
329
  var SANQIAN_WEBSITE = "https://sanqian.io";
@@ -481,7 +494,8 @@ function createSDKError(code, details) {
481
494
  // src/client.ts
482
495
  var SanqianSDK = class _SanqianSDK {
483
496
  config;
484
- discovery;
497
+ // DiscoveryManager is only initialized in Node.js environments (when connectionInfo is not provided)
498
+ discovery = null;
485
499
  ws = null;
486
500
  connectionInfo = null;
487
501
  state = {
@@ -537,11 +551,10 @@ var SanqianSDK = class _SanqianSDK {
537
551
  autoLaunchSanqian: true,
538
552
  ...config
539
553
  };
540
- this.discovery = new DiscoveryManager();
541
554
  for (const tool of config.tools) {
542
555
  this.toolHandlers.set(tool.name, tool.handler);
543
556
  }
544
- this.launchedBySanqian = typeof process !== "undefined" && process.env?.SANQIAN_NO_RECONNECT === "1";
557
+ this.launchedBySanqian = typeof process !== "undefined" && (process.env?.SANQIAN_LAUNCHED === "1" || process.env?.SANQIAN_NO_RECONNECT === "1");
545
558
  if (this.launchedBySanqian) {
546
559
  this.log("Detected launch by Sanqian, will connect immediately without auto-reconnect");
547
560
  setTimeout(() => {
@@ -551,6 +564,26 @@ var SanqianSDK = class _SanqianSDK {
551
564
  }, 0);
552
565
  }
553
566
  }
567
+ /**
568
+ * Build WebSocket URL from connection info
569
+ * This is inlined to avoid importing DiscoveryManager in browser environments
570
+ */
571
+ buildWebSocketUrl(info) {
572
+ const wsPath = info.ws_path || "/ws/apps";
573
+ return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
574
+ }
575
+ /**
576
+ * Lazily initialize DiscoveryManager (only in Node.js environments)
577
+ * This uses dynamic import to avoid bundling Node.js APIs in browser builds
578
+ */
579
+ async getDiscovery() {
580
+ if (this.discovery) {
581
+ return this.discovery;
582
+ }
583
+ const { DiscoveryManager: DiscoveryManager2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
584
+ this.discovery = new DiscoveryManager2();
585
+ return this.discovery;
586
+ }
554
587
  // ============================================
555
588
  // Lifecycle
556
589
  // ============================================
@@ -574,7 +607,7 @@ var SanqianSDK = class _SanqianSDK {
574
607
  */
575
608
  async connectWithInfo(info) {
576
609
  this.connectionInfo = info;
577
- const url = this.discovery.buildWebSocketUrl(info);
610
+ const url = this.buildWebSocketUrl(info);
578
611
  return new Promise((resolve, reject) => {
579
612
  this.log(`Connecting to ${url}`);
580
613
  this.ws = new import_isomorphic_ws.default(url);
@@ -624,7 +657,7 @@ var SanqianSDK = class _SanqianSDK {
624
657
  async disconnect() {
625
658
  this.stopHeartbeat();
626
659
  this.stopReconnect();
627
- this.discovery.stopWatching();
660
+ this.discovery?.stopWatching();
628
661
  if (this.ws) {
629
662
  this.ws.close(1e3, "Client disconnect");
630
663
  this.ws = null;
@@ -1005,11 +1038,12 @@ var SanqianSDK = class _SanqianSDK {
1005
1038
  this.log("Using pre-configured connection info (browser mode)");
1006
1039
  info = this.config.connectionInfo;
1007
1040
  } else {
1008
- info = this.discovery.read();
1041
+ const discovery = await this.getDiscovery();
1042
+ info = discovery.read();
1009
1043
  if (!info) {
1010
1044
  if (this.config.autoLaunchSanqian) {
1011
1045
  this.log("Sanqian not running, attempting to launch...");
1012
- const launched = this.discovery.launchSanqian(this.config.sanqianPath);
1046
+ const launched = discovery.launchSanqian(this.config.sanqianPath);
1013
1047
  if (!launched) {
1014
1048
  throw createSDKError("NOT_INSTALLED" /* NOT_INSTALLED */);
1015
1049
  }
@@ -1028,8 +1062,9 @@ var SanqianSDK = class _SanqianSDK {
1028
1062
  async waitForSanqianStartup(timeout = 12e4) {
1029
1063
  const startTime = Date.now();
1030
1064
  const pollInterval = 500;
1065
+ const discovery = await this.getDiscovery();
1031
1066
  while (Date.now() - startTime < timeout) {
1032
- const info = this.discovery.read();
1067
+ const info = discovery.read();
1033
1068
  if (info) {
1034
1069
  this.log("Sanqian started, connection info available");
1035
1070
  return info;
@@ -1378,6 +1413,20 @@ var SanqianSDK = class _SanqianSDK {
1378
1413
  });
1379
1414
  return this.on(event, onceWrapper);
1380
1415
  }
1416
+ /**
1417
+ * Remove all event listeners
1418
+ *
1419
+ * Call this to prevent memory leaks when disposing the SDK instance.
1420
+ * If event is specified, only removes listeners for that event.
1421
+ */
1422
+ removeAllListeners(event) {
1423
+ if (event) {
1424
+ this.eventListeners.delete(event);
1425
+ } else {
1426
+ this.eventListeners.clear();
1427
+ }
1428
+ return this;
1429
+ }
1381
1430
  emit(event, ...args) {
1382
1431
  const listeners = this.eventListeners.get(event);
1383
1432
  if (listeners) {
@@ -1478,6 +1527,9 @@ var Conversation = class {
1478
1527
  });
1479
1528
  }
1480
1529
  };
1530
+
1531
+ // src/index.ts
1532
+ init_discovery();
1481
1533
  // Annotate the CommonJS export names for ESM import in node:
1482
1534
  0 && (module.exports = {
1483
1535
  Conversation,