reasonix 0.51.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/dist/app.css +1 -1
- package/dashboard/dist/app.js +3 -3
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/{acp-XEUHGG7X.js → acp-NEUYWGUU.js} +26 -26
- package/dist/cli/chat-QA6IVFJD.js +49 -0
- package/dist/cli/{chunk-HGK57NBN.js → chunk-2W4F3RIZ.js} +2 -2
- package/dist/cli/{chunk-UO6E7FN3.js → chunk-3OXD5CBM.js} +32756 -31192
- package/dist/cli/chunk-3OXD5CBM.js.map +1 -0
- package/dist/cli/{chunk-UMZ6KHTS.js → chunk-5YLEKX2V.js} +2 -2
- package/dist/cli/{chunk-BA5R6BAE.js → chunk-6QBUXA73.js} +2 -2
- package/dist/cli/chunk-77JIQ7SL.js +40 -0
- package/dist/cli/chunk-77JIQ7SL.js.map +1 -0
- package/dist/cli/{chunk-6XWXIVQ3.js → chunk-AMSK3ZLB.js} +2 -2
- package/dist/cli/chunk-AMSK3ZLB.js.map +1 -0
- package/dist/cli/{chunk-A5PBEIJ7.js → chunk-AOYUW3HR.js} +37 -4
- package/dist/cli/chunk-AOYUW3HR.js.map +1 -0
- package/dist/cli/{chunk-3YRTIWFX.js → chunk-ARBGTNHM.js} +2 -2
- package/dist/cli/{chunk-3BTK5BHI.js → chunk-B4MOGWHW.js} +2 -2
- package/dist/cli/{chunk-5AIDYVH2.js → chunk-CFJY64UA.js} +2 -2
- package/dist/cli/{chunk-SBHF5NWD.js → chunk-CGVW5W7N.js} +14 -14
- package/dist/cli/{chunk-SBHF5NWD.js.map → chunk-CGVW5W7N.js.map} +1 -1
- package/dist/cli/{chunk-DVD67FXQ.js → chunk-CLHMV6OL.js} +568 -66
- package/dist/cli/chunk-CLHMV6OL.js.map +1 -0
- package/dist/cli/{chunk-2WUEAI2I.js → chunk-CPCUMMSR.js} +3 -3
- package/dist/cli/{chunk-JHWQDJZA.js → chunk-CTRM32BP.js} +2 -2
- package/dist/cli/{chunk-544J4PXD.js → chunk-D6WRFR6V.js} +5 -5
- package/dist/cli/{chunk-N4SEBLU4.js → chunk-DLTE4GBY.js} +3 -3
- package/dist/cli/{chunk-NRROJXXT.js → chunk-FY5UERSG.js} +9 -9
- package/dist/cli/{chunk-C2MRSJTV.js → chunk-GFJJEW3Z.js} +18 -10
- package/dist/cli/chunk-GFJJEW3Z.js.map +1 -0
- package/dist/cli/{chunk-R6KIHEF3.js → chunk-GNRKXRRE.js} +743 -660
- package/dist/cli/chunk-GNRKXRRE.js.map +1 -0
- package/dist/cli/{chunk-SXSAWOB7.js → chunk-HI6THNAZ.js} +19 -17
- package/dist/cli/chunk-HI6THNAZ.js.map +1 -0
- package/dist/cli/{chunk-K4YQFULP.js → chunk-HNZ4727T.js} +15 -15
- package/dist/cli/chunk-I3NE5S63.js +54 -0
- package/dist/cli/{chunk-EAMXOWUW.js.map → chunk-I3NE5S63.js.map} +1 -1
- package/dist/cli/{chunk-FEZK652I.js → chunk-MVLPXZAA.js} +834 -10
- package/dist/cli/chunk-MVLPXZAA.js.map +1 -0
- package/dist/cli/{chunk-36BM7INR.js → chunk-MW64SQUE.js} +2 -2
- package/dist/cli/{chunk-Z3MKG7MQ.js → chunk-OMNRXZNA.js} +2 -2
- package/dist/cli/{chunk-7YPMTE3U.js → chunk-RCC73DWQ.js} +5 -9
- package/dist/cli/chunk-RCC73DWQ.js.map +1 -0
- package/dist/cli/{chunk-2HVTBFCI.js → chunk-RHQOGG43.js} +5 -3
- package/dist/cli/chunk-RHQOGG43.js.map +1 -0
- package/dist/cli/{chunk-EWVFGYT6.js → chunk-VVPV5HU6.js} +2 -2
- package/dist/cli/{chunk-7YB26OQO.js → chunk-WPY7AFS6.js} +2 -2
- package/dist/cli/{chunk-BM6BBFAV.js → chunk-XBYHNZ5Z.js} +2 -2
- package/dist/cli/{chunk-WPOKBW5E.js → chunk-XNMXVL6C.js} +2 -2
- package/dist/cli/{chunk-SVD4UPRQ.js → chunk-XUZHBQSM.js} +2 -2
- package/dist/cli/{chunk-Q46B3Z7H.js → chunk-YMYX6QTC.js} +8 -5
- package/dist/cli/{chunk-Q46B3Z7H.js.map → chunk-YMYX6QTC.js.map} +1 -1
- package/dist/cli/{chunk-K3QJ3GKI.js → chunk-Z663GVUB.js} +3 -3
- package/dist/cli/{code-BMXLBC7D.js → code-WN6D4VZO.js} +35 -36
- package/dist/cli/{code-BMXLBC7D.js.map → code-WN6D4VZO.js.map} +1 -1
- package/dist/cli/{commands-E4RZXMF6.js → commands-DHETOY7O.js} +4 -4
- package/dist/cli/{commit-KSRQ64IL.js → commit-BBUYAKZV.js} +3 -3
- package/dist/cli/{config-QNDONOTU.js → config-KV7VV5LG.js} +4 -2
- package/dist/cli/{desktop-H3ZHIMDA.js → desktop-LJVXWXNF.js} +557 -70
- package/dist/cli/desktop-LJVXWXNF.js.map +1 -0
- package/dist/cli/diff-2JHMQAHI.js +165 -0
- package/dist/cli/{diff-I4PYI43W.js.map → diff-2JHMQAHI.js.map} +1 -1
- package/dist/cli/{doctor-Y2E4MY2F.js → doctor-GI5LOEZL.js} +11 -11
- package/dist/cli/{events-47HOT7ZA.js → events-LBKMLFM4.js} +5 -5
- package/dist/cli/index.js +40 -39
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-76DK63ZB.js → mcp-DKEJK5ND.js} +3 -3
- package/dist/cli/{mcp-browse-SDNUGO74.js → mcp-browse-V7KWDY32.js} +15 -15
- package/dist/cli/{mcp-browse-SDNUGO74.js.map → mcp-browse-V7KWDY32.js.map} +1 -1
- package/dist/cli/{mcp-inspect-BL5DEO5M.js → mcp-inspect-MTABNHVM.js} +5 -5
- package/dist/cli/{prompt-JLATI3P7.js → prompt-ATI7DKHF.js} +5 -5
- package/dist/cli/{prune-sessions-WHZDFUKD.js → prune-sessions-YQQSZTZS.js} +4 -4
- package/dist/cli/{replay-MHXS7C7Z.js → replay-ZJQ4I4CJ.js} +30 -30
- package/dist/cli/{replay-MHXS7C7Z.js.map → replay-ZJQ4I4CJ.js.map} +1 -1
- package/dist/cli/{run-SXNCPRJE.js → run-HFPRNWJY.js} +22 -22
- package/dist/cli/{server-GEHOE6CO.js → server-UHKO2VVM.js} +23 -23
- package/dist/cli/{sessions-EPBFYISL.js → sessions-IQEWWUH3.js} +16 -16
- package/dist/cli/setup-5BYKCL62.js +502 -0
- package/dist/cli/setup-5BYKCL62.js.map +1 -0
- package/dist/cli/{stats-4WB4XHBP.js → stats-OFCGOQMZ.js} +6 -6
- package/dist/cli/{version-4SP3DLLH.js → version-EODUFAAI.js} +16 -16
- package/dist/index.d.ts +12 -1
- package/dist/index.js +613 -78
- package/dist/index.js.map +1 -1
- package/package.json +21 -3
- package/dist/cli/chat-NJ2Q5KHG.js +0 -50
- package/dist/cli/chunk-2HVTBFCI.js.map +0 -1
- package/dist/cli/chunk-5BBC6YMV.js +0 -832
- package/dist/cli/chunk-5BBC6YMV.js.map +0 -1
- package/dist/cli/chunk-6XWXIVQ3.js.map +0 -1
- package/dist/cli/chunk-7YPMTE3U.js.map +0 -1
- package/dist/cli/chunk-A5PBEIJ7.js.map +0 -1
- package/dist/cli/chunk-C2MRSJTV.js.map +0 -1
- package/dist/cli/chunk-DVD67FXQ.js.map +0 -1
- package/dist/cli/chunk-EAMXOWUW.js +0 -54
- package/dist/cli/chunk-FEZK652I.js.map +0 -1
- package/dist/cli/chunk-R6KIHEF3.js.map +0 -1
- package/dist/cli/chunk-SXSAWOB7.js.map +0 -1
- package/dist/cli/chunk-UO6E7FN3.js.map +0 -1
- package/dist/cli/chunk-UPW544V3.js +0 -96
- package/dist/cli/chunk-UPW544V3.js.map +0 -1
- package/dist/cli/desktop-H3ZHIMDA.js.map +0 -1
- package/dist/cli/devtools-HW3WDT3Q.js +0 -91
- package/dist/cli/devtools-HW3WDT3Q.js.map +0 -1
- package/dist/cli/diff-I4PYI43W.js +0 -165
- package/dist/cli/setup-IW2XR5XI.js +0 -593
- package/dist/cli/setup-IW2XR5XI.js.map +0 -1
- /package/dist/cli/{acp-XEUHGG7X.js.map → acp-NEUYWGUU.js.map} +0 -0
- /package/dist/cli/{chat-NJ2Q5KHG.js.map → chat-QA6IVFJD.js.map} +0 -0
- /package/dist/cli/{chunk-HGK57NBN.js.map → chunk-2W4F3RIZ.js.map} +0 -0
- /package/dist/cli/{chunk-UMZ6KHTS.js.map → chunk-5YLEKX2V.js.map} +0 -0
- /package/dist/cli/{chunk-BA5R6BAE.js.map → chunk-6QBUXA73.js.map} +0 -0
- /package/dist/cli/{chunk-3YRTIWFX.js.map → chunk-ARBGTNHM.js.map} +0 -0
- /package/dist/cli/{chunk-3BTK5BHI.js.map → chunk-B4MOGWHW.js.map} +0 -0
- /package/dist/cli/{chunk-5AIDYVH2.js.map → chunk-CFJY64UA.js.map} +0 -0
- /package/dist/cli/{chunk-2WUEAI2I.js.map → chunk-CPCUMMSR.js.map} +0 -0
- /package/dist/cli/{chunk-JHWQDJZA.js.map → chunk-CTRM32BP.js.map} +0 -0
- /package/dist/cli/{chunk-544J4PXD.js.map → chunk-D6WRFR6V.js.map} +0 -0
- /package/dist/cli/{chunk-N4SEBLU4.js.map → chunk-DLTE4GBY.js.map} +0 -0
- /package/dist/cli/{chunk-NRROJXXT.js.map → chunk-FY5UERSG.js.map} +0 -0
- /package/dist/cli/{chunk-K4YQFULP.js.map → chunk-HNZ4727T.js.map} +0 -0
- /package/dist/cli/{chunk-36BM7INR.js.map → chunk-MW64SQUE.js.map} +0 -0
- /package/dist/cli/{chunk-Z3MKG7MQ.js.map → chunk-OMNRXZNA.js.map} +0 -0
- /package/dist/cli/{chunk-EWVFGYT6.js.map → chunk-VVPV5HU6.js.map} +0 -0
- /package/dist/cli/{chunk-7YB26OQO.js.map → chunk-WPY7AFS6.js.map} +0 -0
- /package/dist/cli/{chunk-BM6BBFAV.js.map → chunk-XBYHNZ5Z.js.map} +0 -0
- /package/dist/cli/{chunk-WPOKBW5E.js.map → chunk-XNMXVL6C.js.map} +0 -0
- /package/dist/cli/{chunk-SVD4UPRQ.js.map → chunk-XUZHBQSM.js.map} +0 -0
- /package/dist/cli/{chunk-K3QJ3GKI.js.map → chunk-Z663GVUB.js.map} +0 -0
- /package/dist/cli/{commands-E4RZXMF6.js.map → commands-DHETOY7O.js.map} +0 -0
- /package/dist/cli/{commit-KSRQ64IL.js.map → commit-BBUYAKZV.js.map} +0 -0
- /package/dist/cli/{config-QNDONOTU.js.map → config-KV7VV5LG.js.map} +0 -0
- /package/dist/cli/{doctor-Y2E4MY2F.js.map → doctor-GI5LOEZL.js.map} +0 -0
- /package/dist/cli/{events-47HOT7ZA.js.map → events-LBKMLFM4.js.map} +0 -0
- /package/dist/cli/{mcp-76DK63ZB.js.map → mcp-DKEJK5ND.js.map} +0 -0
- /package/dist/cli/{mcp-inspect-BL5DEO5M.js.map → mcp-inspect-MTABNHVM.js.map} +0 -0
- /package/dist/cli/{prompt-JLATI3P7.js.map → prompt-ATI7DKHF.js.map} +0 -0
- /package/dist/cli/{prune-sessions-WHZDFUKD.js.map → prune-sessions-YQQSZTZS.js.map} +0 -0
- /package/dist/cli/{run-SXNCPRJE.js.map → run-HFPRNWJY.js.map} +0 -0
- /package/dist/cli/{server-GEHOE6CO.js.map → server-UHKO2VVM.js.map} +0 -0
- /package/dist/cli/{sessions-EPBFYISL.js.map → sessions-IQEWWUH3.js.map} +0 -0
- /package/dist/cli/{stats-4WB4XHBP.js.map → stats-OFCGOQMZ.js.map} +0 -0
- /package/dist/cli/{version-4SP3DLLH.js.map → version-EODUFAAI.js.map} +0 -0
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
|
|
3
|
+
import {
|
|
4
|
+
formatMcpLifecycleEvent,
|
|
5
|
+
formatMcpSlowToast
|
|
6
|
+
} from "./chunk-CFJY64UA.js";
|
|
7
|
+
import {
|
|
8
|
+
buildTransportFromSpec,
|
|
9
|
+
preflightStdioSpec
|
|
10
|
+
} from "./chunk-XUZHBQSM.js";
|
|
11
|
+
import {
|
|
12
|
+
bridgeMcpTools
|
|
13
|
+
} from "./chunk-HNZ4727T.js";
|
|
14
|
+
import {
|
|
15
|
+
McpClient,
|
|
16
|
+
inspectMcpServer
|
|
17
|
+
} from "./chunk-RCC73DWQ.js";
|
|
18
|
+
import {
|
|
19
|
+
loadDotenv
|
|
20
|
+
} from "./chunk-2UQP6H6T.js";
|
|
21
|
+
import {
|
|
22
|
+
t
|
|
23
|
+
} from "./chunk-CLHMV6OL.js";
|
|
24
|
+
import {
|
|
25
|
+
decideQQAccess,
|
|
26
|
+
describeQQAccess,
|
|
27
|
+
loadQQConfig,
|
|
28
|
+
normalizeMcpConfig,
|
|
29
|
+
normalizeQQAllowlist,
|
|
30
|
+
normalizeQQOpenId,
|
|
31
|
+
overlayMatchedSpec,
|
|
32
|
+
parseMcpSpec,
|
|
33
|
+
readConfig,
|
|
34
|
+
redactQQOpenId,
|
|
35
|
+
specToRaw
|
|
36
|
+
} from "./chunk-AOYUW3HR.js";
|
|
3
37
|
import {
|
|
4
38
|
__commonJS,
|
|
5
39
|
__require,
|
|
@@ -2209,14 +2243,14 @@ var require_extension = __commonJS({
|
|
|
2209
2243
|
var require_websocket = __commonJS({
|
|
2210
2244
|
"node_modules/ws/lib/websocket.js"(exports, module) {
|
|
2211
2245
|
"use strict";
|
|
2212
|
-
var
|
|
2246
|
+
var EventEmitter2 = __require("events");
|
|
2213
2247
|
var https = __require("https");
|
|
2214
2248
|
var http = __require("http");
|
|
2215
2249
|
var net = __require("net");
|
|
2216
2250
|
var tls = __require("tls");
|
|
2217
2251
|
var { randomBytes, createHash } = __require("crypto");
|
|
2218
2252
|
var { Duplex, Readable } = __require("stream");
|
|
2219
|
-
var { URL } = __require("url");
|
|
2253
|
+
var { URL: URL2 } = __require("url");
|
|
2220
2254
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
2221
2255
|
var Receiver2 = require_receiver();
|
|
2222
2256
|
var Sender2 = require_sender();
|
|
@@ -2241,7 +2275,7 @@ var require_websocket = __commonJS({
|
|
|
2241
2275
|
var protocolVersions = [8, 13];
|
|
2242
2276
|
var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
|
|
2243
2277
|
var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
2244
|
-
var WebSocket2 = class _WebSocket extends
|
|
2278
|
+
var WebSocket2 = class _WebSocket extends EventEmitter2 {
|
|
2245
2279
|
/**
|
|
2246
2280
|
* Create a new `WebSocket`.
|
|
2247
2281
|
*
|
|
@@ -2709,11 +2743,11 @@ var require_websocket = __commonJS({
|
|
|
2709
2743
|
);
|
|
2710
2744
|
}
|
|
2711
2745
|
let parsedUrl;
|
|
2712
|
-
if (address instanceof
|
|
2746
|
+
if (address instanceof URL2) {
|
|
2713
2747
|
parsedUrl = address;
|
|
2714
2748
|
} else {
|
|
2715
2749
|
try {
|
|
2716
|
-
parsedUrl = new
|
|
2750
|
+
parsedUrl = new URL2(address);
|
|
2717
2751
|
} catch {
|
|
2718
2752
|
throw new SyntaxError(`Invalid URL: ${address}`);
|
|
2719
2753
|
}
|
|
@@ -2850,7 +2884,7 @@ var require_websocket = __commonJS({
|
|
|
2850
2884
|
req.abort();
|
|
2851
2885
|
let addr;
|
|
2852
2886
|
try {
|
|
2853
|
-
addr = new
|
|
2887
|
+
addr = new URL2(location, address);
|
|
2854
2888
|
} catch (e) {
|
|
2855
2889
|
const err = new SyntaxError(`Invalid URL: ${location}`);
|
|
2856
2890
|
emitErrorAndClose(websocket, err);
|
|
@@ -3238,7 +3272,7 @@ var require_subprotocol = __commonJS({
|
|
|
3238
3272
|
var require_websocket_server = __commonJS({
|
|
3239
3273
|
"node_modules/ws/lib/websocket-server.js"(exports, module) {
|
|
3240
3274
|
"use strict";
|
|
3241
|
-
var
|
|
3275
|
+
var EventEmitter2 = __require("events");
|
|
3242
3276
|
var http = __require("http");
|
|
3243
3277
|
var { Duplex } = __require("stream");
|
|
3244
3278
|
var { createHash } = __require("crypto");
|
|
@@ -3251,7 +3285,7 @@ var require_websocket_server = __commonJS({
|
|
|
3251
3285
|
var RUNNING = 0;
|
|
3252
3286
|
var CLOSING = 1;
|
|
3253
3287
|
var CLOSED = 2;
|
|
3254
|
-
var WebSocketServer2 = class extends
|
|
3288
|
+
var WebSocketServer2 = class extends EventEmitter2 {
|
|
3255
3289
|
/**
|
|
3256
3290
|
* Create a `WebSocketServer` instance.
|
|
3257
3291
|
*
|
|
@@ -3627,6 +3661,14 @@ var require_websocket_server = __commonJS({
|
|
|
3627
3661
|
}
|
|
3628
3662
|
});
|
|
3629
3663
|
|
|
3664
|
+
// src/qq/channel.ts
|
|
3665
|
+
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
3666
|
+
import { homedir } from "os";
|
|
3667
|
+
import { dirname, join } from "path";
|
|
3668
|
+
|
|
3669
|
+
// src/qq/bot.ts
|
|
3670
|
+
import { EventEmitter } from "events";
|
|
3671
|
+
|
|
3630
3672
|
// node_modules/ws/wrapper.mjs
|
|
3631
3673
|
var import_stream = __toESM(require_stream(), 1);
|
|
3632
3674
|
var import_extension = __toESM(require_extension(), 1);
|
|
@@ -3638,7 +3680,789 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
3638
3680
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
3639
3681
|
var wrapper_default = import_websocket.default;
|
|
3640
3682
|
|
|
3683
|
+
// src/qq/bot.ts
|
|
3684
|
+
var TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
|
|
3685
|
+
var BASE_URL = "https://api.sgroup.qq.com";
|
|
3686
|
+
var SANDBOX_URL = "https://sandbox.api.sgroup.qq.com";
|
|
3687
|
+
var INTENT_C2C_GROUP = 1 << 25;
|
|
3688
|
+
var MIN_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
3689
|
+
var MAX_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
3690
|
+
var ALLOWED_GATEWAY_HOSTS = ["api.sgroup.qq.com", "sandbox.api.sgroup.qq.com", "qq.com"];
|
|
3691
|
+
var QQBot = class extends EventEmitter {
|
|
3692
|
+
config;
|
|
3693
|
+
token = "";
|
|
3694
|
+
tokenExpiresAt = 0;
|
|
3695
|
+
ws = null;
|
|
3696
|
+
heartbeatTimer = null;
|
|
3697
|
+
seq = 0;
|
|
3698
|
+
sessionId = "";
|
|
3699
|
+
closed = false;
|
|
3700
|
+
readyReceived = false;
|
|
3701
|
+
constructor(config) {
|
|
3702
|
+
super();
|
|
3703
|
+
this.config = config;
|
|
3704
|
+
}
|
|
3705
|
+
get baseUrl() {
|
|
3706
|
+
return this.config.sandbox ? SANDBOX_URL : BASE_URL;
|
|
3707
|
+
}
|
|
3708
|
+
sanitizeHeartbeatInterval(interval) {
|
|
3709
|
+
if (typeof interval !== "number" || !Number.isFinite(interval)) {
|
|
3710
|
+
return null;
|
|
3711
|
+
}
|
|
3712
|
+
if (interval < MIN_HEARTBEAT_INTERVAL_MS) {
|
|
3713
|
+
return MIN_HEARTBEAT_INTERVAL_MS;
|
|
3714
|
+
}
|
|
3715
|
+
if (interval > MAX_HEARTBEAT_INTERVAL_MS) {
|
|
3716
|
+
return MAX_HEARTBEAT_INTERVAL_MS;
|
|
3717
|
+
}
|
|
3718
|
+
return Math.trunc(interval);
|
|
3719
|
+
}
|
|
3720
|
+
validateGatewayUrl(rawUrl) {
|
|
3721
|
+
const url = new URL(rawUrl);
|
|
3722
|
+
const trustedHost = ALLOWED_GATEWAY_HOSTS.some(
|
|
3723
|
+
(host) => url.hostname === host || url.hostname.endsWith(`.${host}`)
|
|
3724
|
+
);
|
|
3725
|
+
if (url.protocol !== "wss:" || !trustedHost || url.username || url.password || url.search || url.hash) {
|
|
3726
|
+
throw new Error(`Unexpected QQ gateway URL: ${rawUrl}`);
|
|
3727
|
+
}
|
|
3728
|
+
return url.toString();
|
|
3729
|
+
}
|
|
3730
|
+
async ensureToken() {
|
|
3731
|
+
if (this.token && Date.now() < this.tokenExpiresAt - 6e4) {
|
|
3732
|
+
return this.token;
|
|
3733
|
+
}
|
|
3734
|
+
const res = await fetch(TOKEN_URL, {
|
|
3735
|
+
method: "POST",
|
|
3736
|
+
headers: { "Content-Type": "application/json" },
|
|
3737
|
+
body: JSON.stringify({
|
|
3738
|
+
appId: this.config.appid,
|
|
3739
|
+
clientSecret: this.config.secret
|
|
3740
|
+
})
|
|
3741
|
+
});
|
|
3742
|
+
if (!res.ok) {
|
|
3743
|
+
const text = await res.text();
|
|
3744
|
+
throw new Error(`Failed to get access token (${res.status}): ${text}`);
|
|
3745
|
+
}
|
|
3746
|
+
const data = await res.json();
|
|
3747
|
+
this.token = data.access_token;
|
|
3748
|
+
this.tokenExpiresAt = Date.now() + data.expires_in * 1e3;
|
|
3749
|
+
return this.token;
|
|
3750
|
+
}
|
|
3751
|
+
async getGateway() {
|
|
3752
|
+
const token = await this.ensureToken();
|
|
3753
|
+
const res = await fetch(`${this.baseUrl}/gateway`, {
|
|
3754
|
+
headers: { Authorization: `QQBot ${token}` }
|
|
3755
|
+
});
|
|
3756
|
+
if (!res.ok) {
|
|
3757
|
+
const text = await res.text();
|
|
3758
|
+
throw new Error(`Failed to get gateway (${res.status}): ${text}`);
|
|
3759
|
+
}
|
|
3760
|
+
const data = await res.json();
|
|
3761
|
+
return this.validateGatewayUrl(data.url);
|
|
3762
|
+
}
|
|
3763
|
+
sendOp(op, data) {
|
|
3764
|
+
if (!this.ws) return;
|
|
3765
|
+
this.ws.send(JSON.stringify({ op, d: data ?? {} }));
|
|
3766
|
+
}
|
|
3767
|
+
async handlePayload(payload) {
|
|
3768
|
+
switch (payload.op) {
|
|
3769
|
+
case 10: {
|
|
3770
|
+
const d = payload.d;
|
|
3771
|
+
this.sendOp(2, {
|
|
3772
|
+
token: `QQBot ${await this.ensureToken()}`,
|
|
3773
|
+
intents: INTENT_C2C_GROUP,
|
|
3774
|
+
shard: [0, 1]
|
|
3775
|
+
});
|
|
3776
|
+
const heartbeatInterval = this.sanitizeHeartbeatInterval(d?.heartbeat_interval);
|
|
3777
|
+
if (heartbeatInterval) {
|
|
3778
|
+
this.heartbeatTimer = setInterval(() => {
|
|
3779
|
+
this.sendOp(1, this.seq || null);
|
|
3780
|
+
}, heartbeatInterval);
|
|
3781
|
+
}
|
|
3782
|
+
break;
|
|
3783
|
+
}
|
|
3784
|
+
case 0: {
|
|
3785
|
+
if (payload.s) this.seq = payload.s;
|
|
3786
|
+
if (payload.t === "READY") {
|
|
3787
|
+
const d = payload.d;
|
|
3788
|
+
this.sessionId = d.session_id;
|
|
3789
|
+
this.readyReceived = true;
|
|
3790
|
+
this.emit("online");
|
|
3791
|
+
} else if (payload.t === "C2C_MESSAGE_CREATE") {
|
|
3792
|
+
this.emit("message.private", payload.d);
|
|
3793
|
+
} else if (payload.t === "GROUP_AT_MESSAGE_CREATE") {
|
|
3794
|
+
this.emit("message.group", payload.d);
|
|
3795
|
+
}
|
|
3796
|
+
break;
|
|
3797
|
+
}
|
|
3798
|
+
case 7: {
|
|
3799
|
+
this.reconnect();
|
|
3800
|
+
break;
|
|
3801
|
+
}
|
|
3802
|
+
case 9: {
|
|
3803
|
+
this.sessionId = "";
|
|
3804
|
+
this.sendOp(2, {
|
|
3805
|
+
token: `QQBot ${await this.ensureToken()}`,
|
|
3806
|
+
intents: INTENT_C2C_GROUP,
|
|
3807
|
+
shard: [0, 1]
|
|
3808
|
+
});
|
|
3809
|
+
break;
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
async reconnect() {
|
|
3814
|
+
this.cleanup();
|
|
3815
|
+
await this.connect();
|
|
3816
|
+
}
|
|
3817
|
+
cleanup() {
|
|
3818
|
+
if (this.heartbeatTimer) {
|
|
3819
|
+
clearInterval(this.heartbeatTimer);
|
|
3820
|
+
this.heartbeatTimer = null;
|
|
3821
|
+
}
|
|
3822
|
+
if (this.ws) {
|
|
3823
|
+
this.ws.removeAllListeners();
|
|
3824
|
+
this.ws.close();
|
|
3825
|
+
this.ws = null;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
async connect() {
|
|
3829
|
+
const gatewayUrl = await this.getGateway();
|
|
3830
|
+
const token = await this.ensureToken();
|
|
3831
|
+
this.ws = new wrapper_default(gatewayUrl, {
|
|
3832
|
+
headers: {
|
|
3833
|
+
Authorization: `QQBot ${token}`,
|
|
3834
|
+
"X-Union-Appid": this.config.appid
|
|
3835
|
+
}
|
|
3836
|
+
});
|
|
3837
|
+
this.ws.on("open", () => {
|
|
3838
|
+
if (this.sessionId) {
|
|
3839
|
+
this.sendOp(6, {
|
|
3840
|
+
token: `QQBot ${this.token}`,
|
|
3841
|
+
session_id: this.sessionId,
|
|
3842
|
+
seq: this.seq
|
|
3843
|
+
});
|
|
3844
|
+
}
|
|
3845
|
+
});
|
|
3846
|
+
this.ws.on("message", (raw) => {
|
|
3847
|
+
try {
|
|
3848
|
+
const payload = JSON.parse(raw.toString());
|
|
3849
|
+
this.handlePayload(payload).catch(() => {
|
|
3850
|
+
});
|
|
3851
|
+
} catch {
|
|
3852
|
+
}
|
|
3853
|
+
});
|
|
3854
|
+
this.ws.on("close", () => {
|
|
3855
|
+
if (!this.closed) {
|
|
3856
|
+
if (this.readyReceived) {
|
|
3857
|
+
console.error("QQ WebSocket reconnecting...");
|
|
3858
|
+
this.cleanup();
|
|
3859
|
+
setTimeout(() => this.reconnect(), 3e3);
|
|
3860
|
+
} else {
|
|
3861
|
+
const msg = "QQ WebSocket closed before authentication completed \u2014 check your appId and appSecret";
|
|
3862
|
+
this.emit("bot_error", msg);
|
|
3863
|
+
this.closed = true;
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
});
|
|
3867
|
+
this.ws.on("error", (err) => {
|
|
3868
|
+
const msg = `QQ WebSocket error: ${err.message}`;
|
|
3869
|
+
console.error(msg);
|
|
3870
|
+
this.emit("bot_error", msg);
|
|
3871
|
+
});
|
|
3872
|
+
}
|
|
3873
|
+
async start() {
|
|
3874
|
+
this.closed = false;
|
|
3875
|
+
this.readyReceived = false;
|
|
3876
|
+
await this.connect();
|
|
3877
|
+
}
|
|
3878
|
+
async stop() {
|
|
3879
|
+
this.closed = true;
|
|
3880
|
+
this.cleanup();
|
|
3881
|
+
}
|
|
3882
|
+
async sendPrivateMessage(openid, content, msgId, msgSeq) {
|
|
3883
|
+
const token = await this.ensureToken();
|
|
3884
|
+
const body = {
|
|
3885
|
+
content,
|
|
3886
|
+
msg_type: 0
|
|
3887
|
+
};
|
|
3888
|
+
if (msgId) body.msg_id = msgId;
|
|
3889
|
+
if (typeof msgSeq === "number" && Number.isFinite(msgSeq)) body.msg_seq = Math.trunc(msgSeq);
|
|
3890
|
+
const res = await fetch(`${this.baseUrl}/v2/users/${encodeURIComponent(openid)}/messages`, {
|
|
3891
|
+
method: "POST",
|
|
3892
|
+
headers: {
|
|
3893
|
+
Authorization: `QQBot ${token}`,
|
|
3894
|
+
"Content-Type": "application/json",
|
|
3895
|
+
"X-Union-Appid": this.config.appid
|
|
3896
|
+
},
|
|
3897
|
+
body: JSON.stringify(body)
|
|
3898
|
+
});
|
|
3899
|
+
if (!res.ok) {
|
|
3900
|
+
const text = await res.text();
|
|
3901
|
+
const msg = `QQ sendPrivateMessage failed (${res.status}): ${text}`;
|
|
3902
|
+
throw new Error(msg);
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
};
|
|
3906
|
+
|
|
3907
|
+
// src/qq/strings.ts
|
|
3908
|
+
function formatQQModeLabel(codeMode) {
|
|
3909
|
+
return t(codeMode ? "handlers.qq.modeCode" : "handlers.qq.modeChat");
|
|
3910
|
+
}
|
|
3911
|
+
function formatQQAccessSummary(config) {
|
|
3912
|
+
const ownerOpenId = normalizeQQOpenId(config.ownerOpenId);
|
|
3913
|
+
const allowlist = normalizeQQAllowlist(config.allowlist) ?? [];
|
|
3914
|
+
const runtimeBoundOpenId = normalizeQQOpenId(config.runtimeBoundOpenId);
|
|
3915
|
+
if (ownerOpenId) {
|
|
3916
|
+
if (allowlist.length > 0) {
|
|
3917
|
+
return t("handlers.qq.accessOwnerWithAllowlist", {
|
|
3918
|
+
owner: redactQQOpenId(ownerOpenId),
|
|
3919
|
+
count: allowlist.length
|
|
3920
|
+
});
|
|
3921
|
+
}
|
|
3922
|
+
return t("handlers.qq.accessOwner", {
|
|
3923
|
+
owner: redactQQOpenId(ownerOpenId)
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
if (allowlist.length > 0) {
|
|
3927
|
+
return t("handlers.qq.accessAllowlist", { count: allowlist.length });
|
|
3928
|
+
}
|
|
3929
|
+
if (runtimeBoundOpenId) {
|
|
3930
|
+
return t("handlers.qq.accessRuntime", {
|
|
3931
|
+
owner: redactQQOpenId(runtimeBoundOpenId)
|
|
3932
|
+
});
|
|
3933
|
+
}
|
|
3934
|
+
return t("handlers.qq.accessOpen");
|
|
3935
|
+
}
|
|
3936
|
+
function formatQQSetupPrompt(step) {
|
|
3937
|
+
return t(step === "appId" ? "handlers.qq.promptAppId" : "handlers.qq.promptAppSecret");
|
|
3938
|
+
}
|
|
3939
|
+
function formatQQSetupWaiting(step) {
|
|
3940
|
+
return t(
|
|
3941
|
+
step === "appId" ? "handlers.qq.setupWaitingAppId" : "handlers.qq.setupWaitingAppSecret"
|
|
3942
|
+
);
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
// src/qq/channel.ts
|
|
3946
|
+
var QQ_LOCK_FILE = join(homedir(), ".reasonix", "qq-channel.pid");
|
|
3947
|
+
var QQ_MAX_CHUNK_BYTES = 1500;
|
|
3948
|
+
var NATURAL_SPLIT_MIN_FRACTION = 0.6;
|
|
3949
|
+
function fitUtf8Slice(text, maxBytes) {
|
|
3950
|
+
let end = 0;
|
|
3951
|
+
let bytes = 0;
|
|
3952
|
+
for (const char of text) {
|
|
3953
|
+
const nextBytes = Buffer.byteLength(char, "utf8");
|
|
3954
|
+
if (bytes > 0 && bytes + nextBytes > maxBytes) break;
|
|
3955
|
+
end += char.length;
|
|
3956
|
+
bytes += nextBytes;
|
|
3957
|
+
}
|
|
3958
|
+
return end > 0 ? text.slice(0, end) : text.slice(0, 1);
|
|
3959
|
+
}
|
|
3960
|
+
function pickNaturalSplit(candidate) {
|
|
3961
|
+
const minSplit = Math.floor(candidate.length * NATURAL_SPLIT_MIN_FRACTION);
|
|
3962
|
+
const splitters = ["\n\n", "\n", " "];
|
|
3963
|
+
for (const splitter of splitters) {
|
|
3964
|
+
const at = candidate.lastIndexOf(splitter);
|
|
3965
|
+
if (at >= minSplit) return at + splitter.length;
|
|
3966
|
+
}
|
|
3967
|
+
return candidate.length;
|
|
3968
|
+
}
|
|
3969
|
+
function splitQQMessage(text, maxBytes = QQ_MAX_CHUNK_BYTES) {
|
|
3970
|
+
const chunks = [];
|
|
3971
|
+
let remaining = text;
|
|
3972
|
+
while (remaining.length > 0) {
|
|
3973
|
+
if (Buffer.byteLength(remaining, "utf8") <= maxBytes) {
|
|
3974
|
+
chunks.push(remaining);
|
|
3975
|
+
break;
|
|
3976
|
+
}
|
|
3977
|
+
const candidate = fitUtf8Slice(remaining, maxBytes);
|
|
3978
|
+
const splitAt = pickNaturalSplit(candidate);
|
|
3979
|
+
chunks.push(candidate.slice(0, splitAt));
|
|
3980
|
+
remaining = remaining.slice(splitAt).trimStart();
|
|
3981
|
+
}
|
|
3982
|
+
return chunks;
|
|
3983
|
+
}
|
|
3984
|
+
var QQChannel = class {
|
|
3985
|
+
constructor(callbacks) {
|
|
3986
|
+
this.callbacks = callbacks;
|
|
3987
|
+
}
|
|
3988
|
+
callbacks;
|
|
3989
|
+
bot = null;
|
|
3990
|
+
qqUserId = null;
|
|
3991
|
+
qqMessageId = null;
|
|
3992
|
+
ownerOpenId;
|
|
3993
|
+
allowlist;
|
|
3994
|
+
runtimeBoundOpenId = null;
|
|
3995
|
+
processedMsgIds = /* @__PURE__ */ new Set();
|
|
3996
|
+
processedMsgIdQueue = [];
|
|
3997
|
+
lockAcquired = false;
|
|
3998
|
+
nextOutboundMsgSeq = 1;
|
|
3999
|
+
rememberMessage(id) {
|
|
4000
|
+
if (this.processedMsgIds.has(id)) return false;
|
|
4001
|
+
this.processedMsgIds.add(id);
|
|
4002
|
+
this.processedMsgIdQueue.push(id);
|
|
4003
|
+
if (this.processedMsgIdQueue.length > 200) {
|
|
4004
|
+
const oldest = this.processedMsgIdQueue.shift();
|
|
4005
|
+
if (oldest) this.processedMsgIds.delete(oldest);
|
|
4006
|
+
}
|
|
4007
|
+
return true;
|
|
4008
|
+
}
|
|
4009
|
+
acquireLock() {
|
|
4010
|
+
try {
|
|
4011
|
+
const existing = Number(readFileSync(QQ_LOCK_FILE, "utf8").trim());
|
|
4012
|
+
if (Number.isInteger(existing) && existing > 0 && existing !== process.pid) {
|
|
4013
|
+
try {
|
|
4014
|
+
process.kill(existing, 0);
|
|
4015
|
+
throw new Error(t("handlers.qq.lockAlreadyRunning", { pid: existing }));
|
|
4016
|
+
} catch (err) {
|
|
4017
|
+
const e = err;
|
|
4018
|
+
if (e.code !== "ESRCH") throw err;
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
} catch (err) {
|
|
4022
|
+
const e = err;
|
|
4023
|
+
if (e.code !== "ENOENT") throw err;
|
|
4024
|
+
}
|
|
4025
|
+
mkdirSync(dirname(QQ_LOCK_FILE), { recursive: true });
|
|
4026
|
+
writeFileSync(QQ_LOCK_FILE, String(process.pid), "utf8");
|
|
4027
|
+
this.lockAcquired = true;
|
|
4028
|
+
}
|
|
4029
|
+
releaseLock() {
|
|
4030
|
+
if (!this.lockAcquired) return;
|
|
4031
|
+
try {
|
|
4032
|
+
const existing = Number(readFileSync(QQ_LOCK_FILE, "utf8").trim());
|
|
4033
|
+
if (existing === process.pid) unlinkSync(QQ_LOCK_FILE);
|
|
4034
|
+
} catch {
|
|
4035
|
+
}
|
|
4036
|
+
this.lockAcquired = false;
|
|
4037
|
+
}
|
|
4038
|
+
applyAccessConfig(config) {
|
|
4039
|
+
this.ownerOpenId = config.ownerOpenId;
|
|
4040
|
+
this.allowlist = config.allowlist;
|
|
4041
|
+
if (this.ownerOpenId || (this.allowlist?.length ?? 0) > 0) {
|
|
4042
|
+
this.runtimeBoundOpenId = null;
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
handlePrivateMessage(msg) {
|
|
4046
|
+
const text = msg.content?.trim();
|
|
4047
|
+
if (!text) return;
|
|
4048
|
+
if (!this.rememberMessage(msg.id)) return;
|
|
4049
|
+
const openid = msg.author.user_openid;
|
|
4050
|
+
const verdict = decideQQAccess(
|
|
4051
|
+
{
|
|
4052
|
+
ownerOpenId: this.ownerOpenId,
|
|
4053
|
+
allowlist: this.allowlist,
|
|
4054
|
+
runtimeBoundOpenId: this.runtimeBoundOpenId
|
|
4055
|
+
},
|
|
4056
|
+
openid
|
|
4057
|
+
);
|
|
4058
|
+
if (!verdict.accept) {
|
|
4059
|
+
this.callbacks.onError?.(
|
|
4060
|
+
t("handlers.qq.unauthorizedMessage", {
|
|
4061
|
+
openid: redactQQOpenId(openid),
|
|
4062
|
+
access: formatQQAccessSummary({
|
|
4063
|
+
ownerOpenId: this.ownerOpenId,
|
|
4064
|
+
allowlist: this.allowlist,
|
|
4065
|
+
runtimeBoundOpenId: this.runtimeBoundOpenId
|
|
4066
|
+
})
|
|
4067
|
+
})
|
|
4068
|
+
);
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
if (verdict.bindRuntime) {
|
|
4072
|
+
this.runtimeBoundOpenId = openid;
|
|
4073
|
+
this.callbacks.onError?.(
|
|
4074
|
+
t("handlers.qq.runtimeBound", {
|
|
4075
|
+
openid: redactQQOpenId(openid)
|
|
4076
|
+
})
|
|
4077
|
+
);
|
|
4078
|
+
}
|
|
4079
|
+
this.qqUserId = openid;
|
|
4080
|
+
this.qqMessageId = msg.id;
|
|
4081
|
+
this.callbacks.onSubmitMessage(`[QQ] ${text}`);
|
|
4082
|
+
}
|
|
4083
|
+
refreshAccessConfig() {
|
|
4084
|
+
this.applyAccessConfig(loadQQConfig());
|
|
4085
|
+
}
|
|
4086
|
+
describeAccess() {
|
|
4087
|
+
return describeQQAccess({
|
|
4088
|
+
ownerOpenId: this.ownerOpenId,
|
|
4089
|
+
allowlist: this.allowlist,
|
|
4090
|
+
runtimeBoundOpenId: this.runtimeBoundOpenId
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
4093
|
+
getRuntimeBoundOpenId() {
|
|
4094
|
+
return this.runtimeBoundOpenId;
|
|
4095
|
+
}
|
|
4096
|
+
async start() {
|
|
4097
|
+
loadDotenv();
|
|
4098
|
+
this.acquireLock();
|
|
4099
|
+
const config = loadQQConfig();
|
|
4100
|
+
if (!config.appId) {
|
|
4101
|
+
this.releaseLock();
|
|
4102
|
+
throw new Error(t("handlers.qq.missingAppId"));
|
|
4103
|
+
}
|
|
4104
|
+
if (!config.appSecret) {
|
|
4105
|
+
this.releaseLock();
|
|
4106
|
+
throw new Error(t("handlers.qq.missingAppSecret"));
|
|
4107
|
+
}
|
|
4108
|
+
this.applyAccessConfig(config);
|
|
4109
|
+
const bot = new QQBot({
|
|
4110
|
+
appid: config.appId,
|
|
4111
|
+
secret: config.appSecret,
|
|
4112
|
+
sandbox: config.sandbox ?? false
|
|
4113
|
+
});
|
|
4114
|
+
bot.on("online", () => {
|
|
4115
|
+
process.stderr.write("QQ bot is online!\n");
|
|
4116
|
+
});
|
|
4117
|
+
bot.on("bot_error", (msg) => {
|
|
4118
|
+
this.callbacks.onError?.(msg);
|
|
4119
|
+
});
|
|
4120
|
+
bot.on("message.private", (msg) => {
|
|
4121
|
+
this.handlePrivateMessage(msg);
|
|
4122
|
+
});
|
|
4123
|
+
this.bot = bot;
|
|
4124
|
+
try {
|
|
4125
|
+
await bot.start();
|
|
4126
|
+
const readyOrError = await Promise.race([
|
|
4127
|
+
new Promise((resolve) => bot.once("online", () => resolve("ready"))),
|
|
4128
|
+
new Promise((resolve) => bot.once("bot_error", () => resolve("error"))),
|
|
4129
|
+
new Promise((resolve) => setTimeout(() => resolve("timeout"), 15e3))
|
|
4130
|
+
]);
|
|
4131
|
+
if (readyOrError === "error") {
|
|
4132
|
+
throw new Error(t("handlers.qq.authFailed"));
|
|
4133
|
+
}
|
|
4134
|
+
if (readyOrError === "timeout") {
|
|
4135
|
+
throw new Error(t("handlers.qq.readyTimeout"));
|
|
4136
|
+
}
|
|
4137
|
+
} catch (err) {
|
|
4138
|
+
this.releaseLock();
|
|
4139
|
+
throw err;
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
async sendResponse(text) {
|
|
4143
|
+
if (!this.bot || !this.qqUserId) return;
|
|
4144
|
+
const chunks = splitQQMessage(text);
|
|
4145
|
+
for (let index = 0; index < chunks.length; index++) {
|
|
4146
|
+
const chunk = chunks[index];
|
|
4147
|
+
if (!chunk) continue;
|
|
4148
|
+
try {
|
|
4149
|
+
await this.bot.sendPrivateMessage(
|
|
4150
|
+
this.qqUserId,
|
|
4151
|
+
chunk,
|
|
4152
|
+
this.qqMessageId ?? void 0,
|
|
4153
|
+
this.nextOutboundMsgSeq++
|
|
4154
|
+
);
|
|
4155
|
+
} catch (err) {
|
|
4156
|
+
const msg = `QQ sendResponse chunk ${index + 1}/${chunks.length} failed: ${err.message}`;
|
|
4157
|
+
this.callbacks.onError?.(msg);
|
|
4158
|
+
break;
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
async stop() {
|
|
4163
|
+
await this.bot?.stop();
|
|
4164
|
+
this.releaseLock();
|
|
4165
|
+
}
|
|
4166
|
+
};
|
|
4167
|
+
|
|
4168
|
+
// src/mcp/summary.ts
|
|
4169
|
+
function buildMcpServerSummary(opts) {
|
|
4170
|
+
return {
|
|
4171
|
+
label: opts.label,
|
|
4172
|
+
spec: opts.spec,
|
|
4173
|
+
toolCount: opts.toolCount,
|
|
4174
|
+
report: opts.report,
|
|
4175
|
+
host: opts.host,
|
|
4176
|
+
bridgeEnv: opts.bridgeEnv,
|
|
4177
|
+
readResource(uri) {
|
|
4178
|
+
return opts.host.client.readResource(uri);
|
|
4179
|
+
},
|
|
4180
|
+
getPrompt(name, args) {
|
|
4181
|
+
return args !== void 0 ? opts.host.client.getPrompt(name, args) : opts.host.client.getPrompt(name);
|
|
4182
|
+
}
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
// src/cli/commands/mcp-runtime.ts
|
|
4187
|
+
var stderrLifecycleSink = (n) => {
|
|
4188
|
+
if (n.kind === "slow") {
|
|
4189
|
+
process.stderr.write(
|
|
4190
|
+
`${formatMcpSlowToast({ name: n.serverName, p95Ms: n.p95Ms, sampleSize: n.sampleSize })}
|
|
4191
|
+
`
|
|
4192
|
+
);
|
|
4193
|
+
return;
|
|
4194
|
+
}
|
|
4195
|
+
if (n.kind === "failed") {
|
|
4196
|
+
process.stderr.write(
|
|
4197
|
+
`${formatMcpLifecycleEvent({ state: "failed", name: n.name, reason: n.reason })}
|
|
4198
|
+
\u2192 ${t("mcpLifecycle.failedSetupHint")}
|
|
4199
|
+
`
|
|
4200
|
+
);
|
|
4201
|
+
return;
|
|
4202
|
+
}
|
|
4203
|
+
if (n.kind === "connected") {
|
|
4204
|
+
process.stderr.write(
|
|
4205
|
+
`${formatMcpLifecycleEvent({
|
|
4206
|
+
state: "connected",
|
|
4207
|
+
name: n.name,
|
|
4208
|
+
tools: n.tools,
|
|
4209
|
+
resources: n.resources,
|
|
4210
|
+
prompts: n.prompts,
|
|
4211
|
+
ms: n.ms
|
|
4212
|
+
})}
|
|
4213
|
+
`
|
|
4214
|
+
);
|
|
4215
|
+
return;
|
|
4216
|
+
}
|
|
4217
|
+
if (n.kind === "tools-ready") {
|
|
4218
|
+
process.stderr.write(
|
|
4219
|
+
`${formatMcpLifecycleEvent({ state: "tools-ready", name: n.name, tools: n.tools, ms: n.ms })}
|
|
4220
|
+
`
|
|
4221
|
+
);
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
4224
|
+
if (n.kind === "warn") {
|
|
4225
|
+
process.stderr.write(
|
|
4226
|
+
`${formatMcpLifecycleEvent({ state: "warn", name: n.name, reason: n.reason })}
|
|
4227
|
+
`
|
|
4228
|
+
);
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4231
|
+
process.stderr.write(
|
|
4232
|
+
`${formatMcpLifecycleEvent({ state: n.kind, name: n.name })}
|
|
4233
|
+
`
|
|
4234
|
+
);
|
|
4235
|
+
};
|
|
4236
|
+
function createMcpRuntime(ctx) {
|
|
4237
|
+
const records = /* @__PURE__ */ new Map();
|
|
4238
|
+
const insertionOrder = [];
|
|
4239
|
+
const failureMap = /* @__PURE__ */ new Map();
|
|
4240
|
+
let sink = stderrLifecycleSink;
|
|
4241
|
+
async function addSpec(raw, loop, signal) {
|
|
4242
|
+
if (records.has(raw)) {
|
|
4243
|
+
return { ok: true, summary: records.get(raw).summary };
|
|
4244
|
+
}
|
|
4245
|
+
failureMap.delete(raw);
|
|
4246
|
+
const tools = ctx.getTools();
|
|
4247
|
+
if (!tools) return { ok: false, reason: "no tool registry available" };
|
|
4248
|
+
const cfg = readConfig();
|
|
4249
|
+
const normalized = normalizeMcpConfig(cfg);
|
|
4250
|
+
let label = "anon";
|
|
4251
|
+
let mcp;
|
|
4252
|
+
let resolveReady;
|
|
4253
|
+
let rejectReady;
|
|
4254
|
+
const ready = new Promise((resolve, reject) => {
|
|
4255
|
+
resolveReady = resolve;
|
|
4256
|
+
rejectReady = reject;
|
|
4257
|
+
});
|
|
4258
|
+
ready.catch(() => void 0);
|
|
4259
|
+
try {
|
|
4260
|
+
const parsed = parseMcpSpec(raw);
|
|
4261
|
+
label = parsed.name ?? "anon";
|
|
4262
|
+
const matched = parsed.name ? normalized.find((s) => s.name === parsed.name) : void 0;
|
|
4263
|
+
const spec = overlayMatchedSpec(parsed, matched);
|
|
4264
|
+
if (spec.disabled) {
|
|
4265
|
+
sink({ kind: "disabled", name: label });
|
|
4266
|
+
rejectReady(new Error(`MCP server "${label}" is disabled`));
|
|
4267
|
+
failureMap.set(raw, { spec: raw, name: label, reason: "disabled by user", at: Date.now() });
|
|
4268
|
+
return { ok: false, reason: "disabled by user" };
|
|
4269
|
+
}
|
|
4270
|
+
sink({ kind: "handshake", name: label });
|
|
4271
|
+
const t0 = Date.now();
|
|
4272
|
+
const namePrefix = spec.name ? `${spec.name}_` : ctx.getRequestedCount() === 1 && ctx.getMcpPrefix() ? ctx.getMcpPrefix() : "";
|
|
4273
|
+
if (spec.transport === "stdio") preflightStdioSpec(spec);
|
|
4274
|
+
const workspaceDir = ctx.getWorkspaceDir?.();
|
|
4275
|
+
const transport = buildTransportFromSpec(spec, { cwd: workspaceDir });
|
|
4276
|
+
mcp = new McpClient({ transport, workspaceDir });
|
|
4277
|
+
await mcp.initialize({ signal });
|
|
4278
|
+
const host = { client: mcp };
|
|
4279
|
+
const bridge = await bridgeMcpTools(mcp, {
|
|
4280
|
+
registry: tools,
|
|
4281
|
+
namePrefix,
|
|
4282
|
+
serverName: label,
|
|
4283
|
+
host,
|
|
4284
|
+
ready,
|
|
4285
|
+
onProgress: (info) => ctx.progressSink.current?.(info),
|
|
4286
|
+
onSlow: (info) => sink({
|
|
4287
|
+
kind: "slow",
|
|
4288
|
+
serverName: info.serverName,
|
|
4289
|
+
p95Ms: info.p95Ms,
|
|
4290
|
+
sampleSize: info.sampleSize
|
|
4291
|
+
})
|
|
4292
|
+
});
|
|
4293
|
+
const ms = Date.now() - t0;
|
|
4294
|
+
const allSpecs = tools.specs();
|
|
4295
|
+
const registeredSpecs = allSpecs.filter(
|
|
4296
|
+
(s) => bridge.registeredNames.includes(s.function.name)
|
|
4297
|
+
);
|
|
4298
|
+
records.set(raw, {
|
|
4299
|
+
spec: raw,
|
|
4300
|
+
client: mcp,
|
|
4301
|
+
summary: buildMcpServerSummary({
|
|
4302
|
+
label,
|
|
4303
|
+
spec: raw,
|
|
4304
|
+
toolCount: bridge.registeredNames.length,
|
|
4305
|
+
report: {
|
|
4306
|
+
protocolVersion: mcp.protocolVersion,
|
|
4307
|
+
serverInfo: mcp.serverInfo,
|
|
4308
|
+
capabilities: mcp.serverCapabilities ?? {},
|
|
4309
|
+
tools: { supported: true, items: [] },
|
|
4310
|
+
resources: { supported: false, reason: "still inspecting" },
|
|
4311
|
+
prompts: { supported: false, reason: "still inspecting" },
|
|
4312
|
+
elapsedMs: ms
|
|
4313
|
+
},
|
|
4314
|
+
host,
|
|
4315
|
+
bridgeEnv: bridge.env
|
|
4316
|
+
}),
|
|
4317
|
+
registeredNames: bridge.registeredNames,
|
|
4318
|
+
registeredSpecs
|
|
4319
|
+
});
|
|
4320
|
+
insertionOrder.push(raw);
|
|
4321
|
+
resolveReady();
|
|
4322
|
+
sink({
|
|
4323
|
+
kind: "tools-ready",
|
|
4324
|
+
name: label,
|
|
4325
|
+
tools: bridge.registeredNames.length,
|
|
4326
|
+
ms
|
|
4327
|
+
});
|
|
4328
|
+
let report;
|
|
4329
|
+
try {
|
|
4330
|
+
report = await inspectMcpServer(mcp);
|
|
4331
|
+
} catch {
|
|
4332
|
+
report = {
|
|
4333
|
+
protocolVersion: mcp.protocolVersion,
|
|
4334
|
+
serverInfo: mcp.serverInfo,
|
|
4335
|
+
capabilities: mcp.serverCapabilities ?? {},
|
|
4336
|
+
tools: { supported: true, items: [] },
|
|
4337
|
+
resources: { supported: false, reason: "inspect failed" },
|
|
4338
|
+
prompts: { supported: false, reason: "inspect failed" },
|
|
4339
|
+
elapsedMs: 0
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
const resourceCount = report.resources.supported ? report.resources.items.length : 0;
|
|
4343
|
+
const promptCount = report.prompts.supported ? report.prompts.items.length : 0;
|
|
4344
|
+
sink({
|
|
4345
|
+
kind: "connected",
|
|
4346
|
+
name: label,
|
|
4347
|
+
tools: bridge.registeredNames.length,
|
|
4348
|
+
resources: resourceCount,
|
|
4349
|
+
prompts: promptCount,
|
|
4350
|
+
ms
|
|
4351
|
+
});
|
|
4352
|
+
const summary = buildMcpServerSummary({
|
|
4353
|
+
label,
|
|
4354
|
+
spec: raw,
|
|
4355
|
+
toolCount: bridge.registeredNames.length,
|
|
4356
|
+
report,
|
|
4357
|
+
host,
|
|
4358
|
+
bridgeEnv: bridge.env
|
|
4359
|
+
});
|
|
4360
|
+
records.set(raw, {
|
|
4361
|
+
spec: raw,
|
|
4362
|
+
client: mcp,
|
|
4363
|
+
summary,
|
|
4364
|
+
registeredNames: bridge.registeredNames,
|
|
4365
|
+
registeredSpecs
|
|
4366
|
+
});
|
|
4367
|
+
if (loop)
|
|
4368
|
+
for (const s of registeredSpecs)
|
|
4369
|
+
try {
|
|
4370
|
+
loop.prefix.addTool(s);
|
|
4371
|
+
} catch (err) {
|
|
4372
|
+
sink({
|
|
4373
|
+
kind: "warn",
|
|
4374
|
+
name: label,
|
|
4375
|
+
reason: `addTool failed for ${s.function.name}: ${err.message}`
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
return { ok: true, summary };
|
|
4379
|
+
} catch (err) {
|
|
4380
|
+
const reason = err.message;
|
|
4381
|
+
if (!records.has(raw)) {
|
|
4382
|
+
await mcp?.close().catch(() => void 0);
|
|
4383
|
+
rejectReady(new Error(`MCP server "${label}" failed to start: ${reason}`));
|
|
4384
|
+
sink({ kind: "failed", name: label, reason });
|
|
4385
|
+
failureMap.set(raw, { spec: raw, name: label, reason, at: Date.now() });
|
|
4386
|
+
return { ok: false, reason };
|
|
4387
|
+
}
|
|
4388
|
+
sink({ kind: "warn", name: label, reason });
|
|
4389
|
+
return { ok: true, summary: records.get(raw).summary };
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
async function removeSpec(raw, loop) {
|
|
4393
|
+
failureMap.delete(raw);
|
|
4394
|
+
const record = records.get(raw);
|
|
4395
|
+
if (!record) return false;
|
|
4396
|
+
await record.client.close().catch(() => void 0);
|
|
4397
|
+
const tools = ctx.getTools();
|
|
4398
|
+
for (const name of record.registeredNames) {
|
|
4399
|
+
tools?.unregister(name);
|
|
4400
|
+
loop?.prefix.removeTool(name);
|
|
4401
|
+
}
|
|
4402
|
+
records.delete(raw);
|
|
4403
|
+
const idx = insertionOrder.indexOf(raw);
|
|
4404
|
+
if (idx >= 0) insertionOrder.splice(idx, 1);
|
|
4405
|
+
return true;
|
|
4406
|
+
}
|
|
4407
|
+
async function reloadFromConfig(loop) {
|
|
4408
|
+
const normalized = normalizeMcpConfig(readConfig());
|
|
4409
|
+
const desired = normalized.map(specToRaw);
|
|
4410
|
+
const desiredSet = new Set(desired);
|
|
4411
|
+
const currentSet = new Set(records.keys());
|
|
4412
|
+
const added = [];
|
|
4413
|
+
const removed = [];
|
|
4414
|
+
const failed = [];
|
|
4415
|
+
for (const spec of [...currentSet]) {
|
|
4416
|
+
if (!desiredSet.has(spec)) {
|
|
4417
|
+
await removeSpec(spec, loop);
|
|
4418
|
+
removed.push(spec);
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
for (const spec of desired) {
|
|
4422
|
+
if (currentSet.has(spec)) continue;
|
|
4423
|
+
const result = await addSpec(spec, loop);
|
|
4424
|
+
if (result.ok) added.push(spec);
|
|
4425
|
+
else failed.push({ spec, reason: result.reason });
|
|
4426
|
+
}
|
|
4427
|
+
return { added, removed, failed, summaries: summaries() };
|
|
4428
|
+
}
|
|
4429
|
+
function specs() {
|
|
4430
|
+
return [...insertionOrder];
|
|
4431
|
+
}
|
|
4432
|
+
function summaries() {
|
|
4433
|
+
return insertionOrder.map((s) => records.get(s)?.summary).filter((s) => Boolean(s));
|
|
4434
|
+
}
|
|
4435
|
+
async function closeAll() {
|
|
4436
|
+
for (const r of records.values()) await r.client.close().catch(() => void 0);
|
|
4437
|
+
records.clear();
|
|
4438
|
+
insertionOrder.length = 0;
|
|
4439
|
+
failureMap.clear();
|
|
4440
|
+
}
|
|
4441
|
+
function failures() {
|
|
4442
|
+
return [...failureMap.values()];
|
|
4443
|
+
}
|
|
4444
|
+
function setLifecycleSink(s) {
|
|
4445
|
+
sink = s;
|
|
4446
|
+
}
|
|
4447
|
+
return {
|
|
4448
|
+
size: () => records.size,
|
|
4449
|
+
specs,
|
|
4450
|
+
summaries,
|
|
4451
|
+
failures,
|
|
4452
|
+
addSpec,
|
|
4453
|
+
removeSpec,
|
|
4454
|
+
reloadFromConfig,
|
|
4455
|
+
closeAll,
|
|
4456
|
+
setLifecycleSink
|
|
4457
|
+
};
|
|
4458
|
+
}
|
|
4459
|
+
|
|
3641
4460
|
export {
|
|
3642
|
-
|
|
4461
|
+
formatQQModeLabel,
|
|
4462
|
+
formatQQAccessSummary,
|
|
4463
|
+
formatQQSetupPrompt,
|
|
4464
|
+
formatQQSetupWaiting,
|
|
4465
|
+
QQChannel,
|
|
4466
|
+
createMcpRuntime
|
|
3643
4467
|
};
|
|
3644
|
-
//# sourceMappingURL=chunk-
|
|
4468
|
+
//# sourceMappingURL=chunk-MVLPXZAA.js.map
|