@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.browser.d.mts +430 -0
- package/dist/index.browser.mjs +1058 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +326 -274
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +314 -255
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -4
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
var
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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.
|
|
87
|
+
this.connectionInfo = info;
|
|
88
|
+
return info;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
113
91
|
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.
|
|
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
|
-
},
|
|
108
|
+
}, 2e3);
|
|
109
|
+
return;
|
|
132
110
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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,
|