@fastgpt-plugin/cli 0.2.0-alpha.2 → 0.2.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/README.en.md +2 -0
- package/README.md +2 -0
- package/dist/index.js +238 -52
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -46,6 +46,8 @@ fastgpt-plugin dev --no-interactive \
|
|
|
46
46
|
--connect "fpg_dbg_..."
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
After `--connect` starts and connects successfully, it overwrites the persisted connection key in local `config.json`, so later `fastgpt-plugin dev` runs can reuse it. In the TUI, press `c` to enter and save a new connection key. Stop the session with `Ctrl+C`; press `Ctrl+C` again to force exit.
|
|
50
|
+
|
|
49
51
|
When passing a raw connection key, set `FASTGPT_PLUGIN_DEBUG_CONNECT_URL`, or set `FASTGPT_PLUGIN_SERVER_URL` so the CLI can request `/api/plugin/debug-sessions/connection-key:exchange`. Full connect links remain supported for compatibility. The exchange result returns the gateway WSS endpoint, `debug:tmbId:{tmbId}` source, and scoped connect token. The CLI does not need `CONNECTION_GATEWAY_AUTH_TOKEN` or `JWT_SECRET`.
|
|
50
52
|
|
|
51
53
|
When no plugin directories are passed, `dev` auto-discovers plugins from the current directory. If the current directory has `index.ts`, it is used as the plugin entry; otherwise, the CLI scans one level of child directories for `index.ts`. You can still pass multiple plugin directories explicitly.
|
package/README.md
CHANGED
|
@@ -46,6 +46,8 @@ fastgpt-plugin dev --no-interactive \
|
|
|
46
46
|
--connect "fpg_dbg_..."
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
`--connect` 启动并成功连接后会覆盖本地持久配置 `config.json` 里的 connection key,后续 `fastgpt-plugin dev` 可直接复用该配置。TUI 运行中按 `c` 可以重新输入并保存 connection key;停止会话使用 `Ctrl+C`,再次按 `Ctrl+C` 强制退出。
|
|
50
|
+
|
|
49
51
|
使用裸 connection key 时,需要设置 `FASTGPT_PLUGIN_DEBUG_CONNECT_URL`,或设置 `FASTGPT_PLUGIN_SERVER_URL` 让 CLI 默认请求 `/api/plugin/debug-sessions/connection-key:exchange`。兼容场景仍可传入完整 connect link。exchange 结果会返回 gateway WSS 地址、`debug:tmbId:{tmbId}` source 和 scoped connect token。CLI 不需要 `CONNECTION_GATEWAY_AUTH_TOKEN` 或 `JWT_SECRET`。
|
|
50
52
|
|
|
51
53
|
`dev` 未传插件目录时会自动探测当前目录:如果当前目录有 `index.ts`,则把当前目录作为插件;否则扫描当前目录下一层子目录中的 `index.ts`。也可以手动传入多个插件目录。
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { kebabCase } from "es-toolkit";
|
|
|
11
11
|
import fs$1, { createWriteStream } from "node:fs";
|
|
12
12
|
import os from "node:os";
|
|
13
13
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
14
|
-
import { Box, Text, render, useInput } from "ink";
|
|
14
|
+
import { Box, Text, render, useApp, useInput } from "ink";
|
|
15
15
|
import React from "react";
|
|
16
16
|
import { createHash, randomUUID } from "node:crypto";
|
|
17
17
|
import { Readable } from "stream";
|
|
@@ -526,7 +526,7 @@ var CheckCommand = class extends BaseCommand {
|
|
|
526
526
|
//#endregion
|
|
527
527
|
//#region package.json
|
|
528
528
|
var name = "@fastgpt-plugin/cli";
|
|
529
|
-
var version = "0.2.0
|
|
529
|
+
var version = "0.2.0";
|
|
530
530
|
|
|
531
531
|
//#endregion
|
|
532
532
|
//#region src/constants.ts
|
|
@@ -811,6 +811,12 @@ const ConnectionGatewayWsHeartbeatMessageSchema = z.object({
|
|
|
811
811
|
type: z.literal("heartbeat"),
|
|
812
812
|
ts: z.number().int().positive()
|
|
813
813
|
});
|
|
814
|
+
const ConnectionGatewayWsMetadataMessageSchema = z.object({
|
|
815
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
816
|
+
type: z.literal("metadata"),
|
|
817
|
+
requestId: z.string().min(1).optional(),
|
|
818
|
+
metadata: z.record(z.string(), z.unknown())
|
|
819
|
+
});
|
|
814
820
|
const ConnectionGatewayWsErrorMessageSchema = z.object({
|
|
815
821
|
protocol: ConnectionGatewayWsProtocolSchema,
|
|
816
822
|
type: z.literal("error"),
|
|
@@ -827,7 +833,8 @@ const ConnectionGatewayWsBoundMessageSchema = z.object({
|
|
|
827
833
|
const ConnectionGatewayWsClientMessageSchema = z.discriminatedUnion("type", [
|
|
828
834
|
ConnectionGatewayWsBindMessageSchema,
|
|
829
835
|
ConnectionGatewayWsEnvelopeMessageSchema,
|
|
830
|
-
ConnectionGatewayWsHeartbeatMessageSchema
|
|
836
|
+
ConnectionGatewayWsHeartbeatMessageSchema,
|
|
837
|
+
ConnectionGatewayWsMetadataMessageSchema
|
|
831
838
|
]);
|
|
832
839
|
const ConnectionGatewayWsServerMessageSchema = z.discriminatedUnion("type", [
|
|
833
840
|
ConnectionGatewayWsBoundMessageSchema,
|
|
@@ -2238,12 +2245,14 @@ async function assertPathExists(targetPath, message) {
|
|
|
2238
2245
|
//#endregion
|
|
2239
2246
|
//#region src/debug/gateway.ts
|
|
2240
2247
|
const GATEWAY_DUPLICATE_CONNECTION_CODE = "connection_gateway.session_already_bound";
|
|
2241
|
-
|
|
2248
|
+
const GATEWAY_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
2249
|
+
async function connectDebugGateway({ targets, options, onLog, onReconnect }) {
|
|
2242
2250
|
if (targets.length === 0) throw new Error("Gateway debug targets cannot be empty");
|
|
2243
2251
|
if (options.reconnect) return connectReconnectingDebugGateway({
|
|
2244
2252
|
targets,
|
|
2245
2253
|
options,
|
|
2246
|
-
onLog
|
|
2254
|
+
onLog,
|
|
2255
|
+
onReconnect
|
|
2247
2256
|
});
|
|
2248
2257
|
return connectSingleDebugGateway({
|
|
2249
2258
|
targets,
|
|
@@ -2251,7 +2260,7 @@ async function connectDebugGateway({ targets, options, onLog }) {
|
|
|
2251
2260
|
onLog
|
|
2252
2261
|
});
|
|
2253
2262
|
}
|
|
2254
|
-
async function connectReconnectingDebugGateway({ targets, options, onLog }) {
|
|
2263
|
+
async function connectReconnectingDebugGateway({ targets, options, onLog, onReconnect }) {
|
|
2255
2264
|
let closed = false;
|
|
2256
2265
|
let current = null;
|
|
2257
2266
|
let currentSession = null;
|
|
@@ -2274,6 +2283,7 @@ async function connectReconnectingDebugGateway({ targets, options, onLog }) {
|
|
|
2274
2283
|
options: currentOptions,
|
|
2275
2284
|
onLog
|
|
2276
2285
|
});
|
|
2286
|
+
if (currentSession) onReconnect?.(current.session);
|
|
2277
2287
|
currentSession = current.session;
|
|
2278
2288
|
await current.closed;
|
|
2279
2289
|
} catch (error) {
|
|
@@ -2300,6 +2310,9 @@ async function connectReconnectingDebugGateway({ targets, options, onLog }) {
|
|
|
2300
2310
|
latestTargets = targets;
|
|
2301
2311
|
current?.updateTargets(targets);
|
|
2302
2312
|
},
|
|
2313
|
+
reconnect() {
|
|
2314
|
+
current?.close();
|
|
2315
|
+
},
|
|
2303
2316
|
close() {
|
|
2304
2317
|
closed = true;
|
|
2305
2318
|
current?.close();
|
|
@@ -2312,6 +2325,7 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
|
|
|
2312
2325
|
let targetsByPluginId = createTargetsByPluginId(targets);
|
|
2313
2326
|
let closed = false;
|
|
2314
2327
|
let session = null;
|
|
2328
|
+
let stopHeartbeat = () => void 0;
|
|
2315
2329
|
let boundResolve;
|
|
2316
2330
|
let boundReject;
|
|
2317
2331
|
const bound = new Promise((resolve, reject) => {
|
|
@@ -2321,6 +2335,7 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
|
|
|
2321
2335
|
const closedPromise = new Promise((resolve) => {
|
|
2322
2336
|
socket.addEventListener("close", () => {
|
|
2323
2337
|
closed = true;
|
|
2338
|
+
stopHeartbeat();
|
|
2324
2339
|
resolve();
|
|
2325
2340
|
});
|
|
2326
2341
|
socket.addEventListener("error", () => {
|
|
@@ -2369,19 +2384,56 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
|
|
|
2369
2384
|
throw error;
|
|
2370
2385
|
});
|
|
2371
2386
|
const boundSession = await bound;
|
|
2387
|
+
stopHeartbeat = startGatewayHeartbeat(socket);
|
|
2372
2388
|
onLog?.(`已连接 Connection Gateway session: ${boundSession.id}`);
|
|
2373
2389
|
return {
|
|
2374
2390
|
session: boundSession,
|
|
2375
2391
|
updateTargets(targets) {
|
|
2376
2392
|
targetsByPluginId = createTargetsByPluginId(targets);
|
|
2393
|
+
updateGatewayMetadata({
|
|
2394
|
+
socket,
|
|
2395
|
+
targets,
|
|
2396
|
+
options,
|
|
2397
|
+
onLog
|
|
2398
|
+
});
|
|
2377
2399
|
onLog?.(`已热更新本地调试插件: ${targets.map((target) => target.snapshot.pluginId).join(", ")}`);
|
|
2378
2400
|
},
|
|
2379
2401
|
close() {
|
|
2380
|
-
if (!closed)
|
|
2402
|
+
if (!closed) {
|
|
2403
|
+
stopHeartbeat();
|
|
2404
|
+
socket.close();
|
|
2405
|
+
}
|
|
2381
2406
|
},
|
|
2382
2407
|
closed: closedPromise
|
|
2383
2408
|
};
|
|
2384
2409
|
}
|
|
2410
|
+
async function updateGatewayMetadata({ socket, targets, options, onLog }) {
|
|
2411
|
+
try {
|
|
2412
|
+
await sendMessage(socket, {
|
|
2413
|
+
protocol: "connection-gateway.ws.v1",
|
|
2414
|
+
type: "metadata",
|
|
2415
|
+
requestId: randomUUID(),
|
|
2416
|
+
metadata: makePluginDebugMetadata(targets, options.source)
|
|
2417
|
+
});
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
onLog?.(`Connection Gateway metadata 更新失败: ${formatErrorMessage(error)}`);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
function startGatewayHeartbeat(socket) {
|
|
2423
|
+
const timer = setInterval(() => {
|
|
2424
|
+
sendMessage(socket, {
|
|
2425
|
+
protocol: "connection-gateway.ws.v1",
|
|
2426
|
+
type: "heartbeat",
|
|
2427
|
+
ts: Date.now()
|
|
2428
|
+
}).catch(() => {
|
|
2429
|
+
socket.close();
|
|
2430
|
+
});
|
|
2431
|
+
}, GATEWAY_HEARTBEAT_INTERVAL_MS);
|
|
2432
|
+
if (typeof timer === "object" && timer && "unref" in timer) timer.unref();
|
|
2433
|
+
return () => {
|
|
2434
|
+
clearInterval(timer);
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2385
2437
|
function createTargetsByPluginId(targets) {
|
|
2386
2438
|
return new Map(targets.map((target) => [target.snapshot.pluginId, target]));
|
|
2387
2439
|
}
|
|
@@ -2713,8 +2765,9 @@ var InvokeManager = class {
|
|
|
2713
2765
|
//#region src/debug/remote-invoke.ts
|
|
2714
2766
|
var RemoteDebugInvokeBridge = class {
|
|
2715
2767
|
sessions = /* @__PURE__ */ new Map();
|
|
2768
|
+
getFastgptBaseUrl;
|
|
2716
2769
|
constructor(fastgptBaseUrl) {
|
|
2717
|
-
this.
|
|
2770
|
+
this.getFastgptBaseUrl = typeof fastgptBaseUrl === "function" ? fastgptBaseUrl : () => fastgptBaseUrl;
|
|
2718
2771
|
}
|
|
2719
2772
|
attach(runtime) {
|
|
2720
2773
|
runtime.setHostRequestHandler((request) => this.handleHostRequest(request));
|
|
@@ -2731,7 +2784,7 @@ var RemoteDebugInvokeBridge = class {
|
|
|
2731
2784
|
if (!session) throw new Error("远程调试反向调用会话不存在或已结束。");
|
|
2732
2785
|
const invoke = new InvokeManager({
|
|
2733
2786
|
token: session.invokeToken,
|
|
2734
|
-
fastgptBaseUrl: this.
|
|
2787
|
+
fastgptBaseUrl: this.getFastgptBaseUrl()
|
|
2735
2788
|
});
|
|
2736
2789
|
switch (method) {
|
|
2737
2790
|
case InvokeMethodEnum.uploadFile: return invoke.uploadFile({
|
|
@@ -2793,7 +2846,11 @@ async function runRemoteDebugSessionOnce({ entriesInput, options, commandName })
|
|
|
2793
2846
|
commandName,
|
|
2794
2847
|
targets
|
|
2795
2848
|
});
|
|
2796
|
-
|
|
2849
|
+
let currentGatewayOptions = gatewayOptions;
|
|
2850
|
+
gatewayOptions.onResolvedReconnectOptions = (nextOptions) => {
|
|
2851
|
+
currentGatewayOptions = nextOptions;
|
|
2852
|
+
};
|
|
2853
|
+
attachRemoteInvokeBridges(targets, () => currentGatewayOptions.fastgptBaseUrl);
|
|
2797
2854
|
const reporter = createRemoteDebugReporter({
|
|
2798
2855
|
commandName,
|
|
2799
2856
|
options,
|
|
@@ -2805,12 +2862,29 @@ async function runRemoteDebugSessionOnce({ entriesInput, options, commandName })
|
|
|
2805
2862
|
const gateway = await connectDebugGateway({
|
|
2806
2863
|
targets,
|
|
2807
2864
|
options: gatewayOptions,
|
|
2808
|
-
onLog: (message) => reporter.log(`[gateway] ${message}`)
|
|
2865
|
+
onLog: (message) => reporter.log(`[gateway] ${message}`),
|
|
2866
|
+
onReconnect: (session) => {
|
|
2867
|
+
reporter.ready(session.sessionScope.source ?? currentGatewayOptions.source ?? "-");
|
|
2868
|
+
}
|
|
2809
2869
|
});
|
|
2810
2870
|
reporter.attachClose(({ force }) => {
|
|
2811
2871
|
gateway.close();
|
|
2812
2872
|
if (force) process.exit(130);
|
|
2813
2873
|
});
|
|
2874
|
+
reporter.attachConfigureConnectionKey(async () => {
|
|
2875
|
+
await configureConnectionKey({
|
|
2876
|
+
options,
|
|
2877
|
+
commandName,
|
|
2878
|
+
targets,
|
|
2879
|
+
reconnect: () => {
|
|
2880
|
+
if (gateway.reconnect) {
|
|
2881
|
+
gateway.reconnect();
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
gateway.close();
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
});
|
|
2814
2888
|
const source = gateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
|
|
2815
2889
|
reporter.ready(source);
|
|
2816
2890
|
await gateway.closed;
|
|
@@ -2831,7 +2905,11 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
|
|
|
2831
2905
|
commandName,
|
|
2832
2906
|
targets: initialTargets
|
|
2833
2907
|
});
|
|
2834
|
-
|
|
2908
|
+
let currentGatewayOptions = gatewayOptions;
|
|
2909
|
+
gatewayOptions.onResolvedReconnectOptions = (nextOptions) => {
|
|
2910
|
+
currentGatewayOptions = nextOptions;
|
|
2911
|
+
};
|
|
2912
|
+
attachRemoteInvokeBridges(initialTargets, () => currentGatewayOptions.fastgptBaseUrl);
|
|
2835
2913
|
const reporter = createRemoteDebugReporter({
|
|
2836
2914
|
commandName,
|
|
2837
2915
|
options,
|
|
@@ -2842,6 +2920,7 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
|
|
|
2842
2920
|
let reloadPending = false;
|
|
2843
2921
|
let closed = false;
|
|
2844
2922
|
let gateway;
|
|
2923
|
+
let activeTargets = initialTargets;
|
|
2845
2924
|
const reloadTargets = async () => {
|
|
2846
2925
|
if (reloadRunning) {
|
|
2847
2926
|
reloadPending = true;
|
|
@@ -2853,7 +2932,8 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
|
|
|
2853
2932
|
reloadPending = false;
|
|
2854
2933
|
try {
|
|
2855
2934
|
const nextTargets = await loadTargets(entries, options);
|
|
2856
|
-
attachRemoteInvokeBridges(nextTargets,
|
|
2935
|
+
attachRemoteInvokeBridges(nextTargets, () => currentGatewayOptions.fastgptBaseUrl);
|
|
2936
|
+
activeTargets = nextTargets;
|
|
2857
2937
|
gateway?.updateTargets(nextTargets);
|
|
2858
2938
|
reporter.log(`检测到插件文件变化,已热更新 ${nextTargets.length} 个本地插件。`);
|
|
2859
2939
|
} catch (error) {
|
|
@@ -2872,7 +2952,10 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
|
|
|
2872
2952
|
const connectedGateway = await connectDebugGateway({
|
|
2873
2953
|
targets: initialTargets,
|
|
2874
2954
|
options: gatewayOptions,
|
|
2875
|
-
onLog: (message) => reporter.log(`[gateway] ${message}`)
|
|
2955
|
+
onLog: (message) => reporter.log(`[gateway] ${message}`),
|
|
2956
|
+
onReconnect: (session) => {
|
|
2957
|
+
reporter.ready(session.sessionScope.source ?? currentGatewayOptions.source ?? "-");
|
|
2958
|
+
}
|
|
2876
2959
|
});
|
|
2877
2960
|
gateway = connectedGateway;
|
|
2878
2961
|
reporter.attachClose(({ force }) => {
|
|
@@ -2881,6 +2964,20 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
|
|
|
2881
2964
|
connectedGateway.close();
|
|
2882
2965
|
if (force) process.exit(130);
|
|
2883
2966
|
});
|
|
2967
|
+
reporter.attachConfigureConnectionKey(async () => {
|
|
2968
|
+
await configureConnectionKey({
|
|
2969
|
+
options,
|
|
2970
|
+
commandName,
|
|
2971
|
+
targets: activeTargets,
|
|
2972
|
+
reconnect: () => {
|
|
2973
|
+
if (connectedGateway.reconnect) {
|
|
2974
|
+
connectedGateway.reconnect();
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
connectedGateway.close();
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
});
|
|
2884
2981
|
const source = connectedGateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
|
|
2885
2982
|
reporter.ready(source);
|
|
2886
2983
|
await connectedGateway.closed;
|
|
@@ -2918,9 +3015,9 @@ async function loadTargets(entries, options) {
|
|
|
2918
3015
|
if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
|
|
2919
3016
|
return targets;
|
|
2920
3017
|
}
|
|
2921
|
-
function attachRemoteInvokeBridges(targets,
|
|
3018
|
+
function attachRemoteInvokeBridges(targets, getFastgptBaseUrl) {
|
|
2922
3019
|
targets.forEach((target) => {
|
|
2923
|
-
const invokeBridge = new RemoteDebugInvokeBridge(
|
|
3020
|
+
const invokeBridge = new RemoteDebugInvokeBridge(getFastgptBaseUrl);
|
|
2924
3021
|
invokeBridge.attach(target.runtime);
|
|
2925
3022
|
target.invokeBridge = invokeBridge;
|
|
2926
3023
|
});
|
|
@@ -2956,7 +3053,10 @@ function createRemoteDebugReporter({ commandName, options, gatewayOptions, targe
|
|
|
2956
3053
|
return options.interactive !== false && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY) ? new TuiRemoteDebugReporter(summary) : new PlainRemoteDebugReporter(summary);
|
|
2957
3054
|
}
|
|
2958
3055
|
async function ensureConnectLink({ options, commandName, targets }) {
|
|
2959
|
-
if (options.connect)
|
|
3056
|
+
if (options.connect) {
|
|
3057
|
+
options.connectPersistOnSuccess = true;
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
2960
3060
|
const savedConnect = await readSavedConnectionKey();
|
|
2961
3061
|
if (savedConnect) {
|
|
2962
3062
|
options.connect = savedConnect;
|
|
@@ -3019,6 +3119,16 @@ async function saveConnectionKey(connectionKey) {
|
|
|
3019
3119
|
mode: 384
|
|
3020
3120
|
});
|
|
3021
3121
|
}
|
|
3122
|
+
async function configureConnectionKey({ options, commandName, targets, reconnect }) {
|
|
3123
|
+
options.connect = await promptConnectLink({
|
|
3124
|
+
commandName,
|
|
3125
|
+
targets,
|
|
3126
|
+
defaultValue: options.connect
|
|
3127
|
+
});
|
|
3128
|
+
options.connectPersistOnSuccess = true;
|
|
3129
|
+
logger.info("已更新 FastGPT connection key,重连成功后会保存到本地配置。");
|
|
3130
|
+
reconnect();
|
|
3131
|
+
}
|
|
3022
3132
|
async function readCliConfig() {
|
|
3023
3133
|
const configPath = getCliConfigPath();
|
|
3024
3134
|
let raw;
|
|
@@ -3050,10 +3160,20 @@ const CLI_CONFIG_FILE_NAME = "config.json";
|
|
|
3050
3160
|
const CLI_CONFIG_DIR_NAME = "fastgpt-plugin";
|
|
3051
3161
|
const CliConfigSchema = z.object({ debug: z.object({ connectionKey: z.string().min(1).optional() }).optional() }).passthrough();
|
|
3052
3162
|
var PlainRemoteDebugReporter = class {
|
|
3163
|
+
closeSession;
|
|
3164
|
+
interruptCount = 0;
|
|
3165
|
+
onSigint = () => {
|
|
3166
|
+
this.requestClose("SIGINT");
|
|
3167
|
+
};
|
|
3168
|
+
onSigterm = () => {
|
|
3169
|
+
this.requestClose("SIGTERM");
|
|
3170
|
+
};
|
|
3053
3171
|
constructor(summary) {
|
|
3054
3172
|
this.summary = summary;
|
|
3055
3173
|
}
|
|
3056
3174
|
start() {
|
|
3175
|
+
process.on("SIGINT", this.onSigint);
|
|
3176
|
+
process.on("SIGTERM", this.onSigterm);
|
|
3057
3177
|
logger.info([
|
|
3058
3178
|
"FastGPT 插件开发会话",
|
|
3059
3179
|
` command: fastgpt-plugin ${this.summary.commandName}`,
|
|
@@ -3065,6 +3185,13 @@ var PlainRemoteDebugReporter = class {
|
|
|
3065
3185
|
...this.summary.targets.map(formatTargetLine)
|
|
3066
3186
|
].join("\n"));
|
|
3067
3187
|
}
|
|
3188
|
+
requestClose(signal) {
|
|
3189
|
+
this.interruptCount += 1;
|
|
3190
|
+
const force = this.interruptCount >= 2;
|
|
3191
|
+
this.log(force ? `再次收到 ${signal},正在强制退出。` : `收到 ${signal},正在关闭远程调试会话。再次发送 ${signal} 强制退出。`);
|
|
3192
|
+
this.closeSession?.({ force });
|
|
3193
|
+
if (force && !this.closeSession) process.exit(130);
|
|
3194
|
+
}
|
|
3068
3195
|
log(message) {
|
|
3069
3196
|
logger.info(message);
|
|
3070
3197
|
}
|
|
@@ -3074,8 +3201,13 @@ var PlainRemoteDebugReporter = class {
|
|
|
3074
3201
|
});
|
|
3075
3202
|
logger.info(`已建立 1 个远程调试通道,挂载 ${this.summary.targets.length} 个插件,按 Ctrl+C 停止。`);
|
|
3076
3203
|
}
|
|
3077
|
-
attachClose() {
|
|
3204
|
+
attachClose(close) {
|
|
3205
|
+
this.closeSession = close;
|
|
3206
|
+
}
|
|
3207
|
+
attachConfigureConnectionKey() {}
|
|
3078
3208
|
end() {
|
|
3209
|
+
process.off("SIGINT", this.onSigint);
|
|
3210
|
+
process.off("SIGTERM", this.onSigterm);
|
|
3079
3211
|
logger.info("远程调试会话已结束。");
|
|
3080
3212
|
}
|
|
3081
3213
|
};
|
|
@@ -3085,36 +3217,34 @@ var TuiRemoteDebugReporter = class {
|
|
|
3085
3217
|
logs = [];
|
|
3086
3218
|
instance;
|
|
3087
3219
|
closeSession;
|
|
3220
|
+
configureConnectionKey;
|
|
3088
3221
|
interruptCount = 0;
|
|
3222
|
+
configuring = false;
|
|
3089
3223
|
onSigint = () => {
|
|
3090
|
-
this.requestClose("
|
|
3224
|
+
this.requestClose("SIGINT");
|
|
3091
3225
|
};
|
|
3092
|
-
requestClose = (
|
|
3093
|
-
if (reason === "q") {
|
|
3094
|
-
this.log("收到退出指令,正在关闭远程调试会话。");
|
|
3095
|
-
this.closeSession?.({ force: false });
|
|
3096
|
-
return;
|
|
3097
|
-
}
|
|
3226
|
+
requestClose = (signal) => {
|
|
3098
3227
|
this.interruptCount += 1;
|
|
3099
3228
|
const force = this.interruptCount >= 2;
|
|
3100
3229
|
this.status = force ? "closed" : "closing";
|
|
3101
|
-
this.log(force ?
|
|
3230
|
+
this.log(force ? `再次收到 ${signal},正在强制退出。` : `收到 ${signal},正在关闭远程调试会话。再次按 Ctrl+C 强制退出。`);
|
|
3102
3231
|
this.closeSession?.({ force });
|
|
3103
3232
|
if (force && !this.closeSession) process.exit(130);
|
|
3104
3233
|
};
|
|
3234
|
+
requestConfigureConnectionKey = (suspendTerminal) => {
|
|
3235
|
+
if (this.configuring) return;
|
|
3236
|
+
if (!this.configureConnectionKey) {
|
|
3237
|
+
this.log("connection key 配置入口尚未就绪。");
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
this.runConfigureConnectionKey(suspendTerminal);
|
|
3241
|
+
};
|
|
3105
3242
|
constructor(summary) {
|
|
3106
3243
|
this.summary = summary;
|
|
3107
3244
|
}
|
|
3108
3245
|
start() {
|
|
3109
3246
|
process.on("SIGINT", this.onSigint);
|
|
3110
|
-
this.
|
|
3111
|
-
stdin: process.stdin,
|
|
3112
|
-
stdout: process.stdout,
|
|
3113
|
-
stderr: process.stderr,
|
|
3114
|
-
exitOnCtrlC: false,
|
|
3115
|
-
interactive: true,
|
|
3116
|
-
patchConsole: false
|
|
3117
|
-
});
|
|
3247
|
+
this.mount();
|
|
3118
3248
|
}
|
|
3119
3249
|
log(message) {
|
|
3120
3250
|
this.logs.push({
|
|
@@ -3132,39 +3262,80 @@ var TuiRemoteDebugReporter = class {
|
|
|
3132
3262
|
attachClose(close) {
|
|
3133
3263
|
this.closeSession = close;
|
|
3134
3264
|
}
|
|
3265
|
+
attachConfigureConnectionKey(handler) {
|
|
3266
|
+
this.configureConnectionKey = handler;
|
|
3267
|
+
}
|
|
3135
3268
|
async end() {
|
|
3136
3269
|
process.off("SIGINT", this.onSigint);
|
|
3137
3270
|
this.status = "closed";
|
|
3138
3271
|
this.rerender();
|
|
3139
|
-
|
|
3140
|
-
this.instance = void 0;
|
|
3141
|
-
if (instance) {
|
|
3142
|
-
await Promise.race([instance.waitUntilRenderFlush(), setTimeout$1(50)]).catch(() => void 0);
|
|
3143
|
-
instance.unmount();
|
|
3144
|
-
await Promise.race([instance.waitUntilExit(), setTimeout$1(50)]).catch(() => void 0);
|
|
3145
|
-
}
|
|
3272
|
+
await this.unmount();
|
|
3146
3273
|
logger.info("远程调试会话已结束。");
|
|
3147
3274
|
}
|
|
3148
3275
|
rerender() {
|
|
3149
3276
|
this.instance?.rerender(this.createApp());
|
|
3150
3277
|
}
|
|
3278
|
+
mount() {
|
|
3279
|
+
if (this.instance) return;
|
|
3280
|
+
this.instance = render(this.createApp(), {
|
|
3281
|
+
stdin: process.stdin,
|
|
3282
|
+
stdout: process.stdout,
|
|
3283
|
+
stderr: process.stderr,
|
|
3284
|
+
exitOnCtrlC: false,
|
|
3285
|
+
interactive: true,
|
|
3286
|
+
patchConsole: false
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
async unmount() {
|
|
3290
|
+
const instance = this.instance;
|
|
3291
|
+
this.instance = void 0;
|
|
3292
|
+
if (!instance) return;
|
|
3293
|
+
await Promise.race([instance.waitUntilRenderFlush(), setTimeout$1(50)]).catch(() => void 0);
|
|
3294
|
+
instance.unmount();
|
|
3295
|
+
await Promise.race([instance.waitUntilExit(), setTimeout$1(50)]).catch(() => void 0);
|
|
3296
|
+
}
|
|
3297
|
+
async runConfigureConnectionKey(suspendTerminal) {
|
|
3298
|
+
if (!this.configureConnectionKey) return;
|
|
3299
|
+
const previousStatus = this.status;
|
|
3300
|
+
this.configuring = true;
|
|
3301
|
+
this.status = "configuring";
|
|
3302
|
+
this.rerender();
|
|
3303
|
+
try {
|
|
3304
|
+
await suspendTerminal(async () => {
|
|
3305
|
+
await this.configureConnectionKey?.();
|
|
3306
|
+
});
|
|
3307
|
+
this.status = "connecting";
|
|
3308
|
+
this.log("已更新 connection key,正在重新连接 Connection Gateway。");
|
|
3309
|
+
} catch (error) {
|
|
3310
|
+
this.status = previousStatus;
|
|
3311
|
+
this.log(`connection key 配置失败: ${formatConnectionKeyExchangeError(error)}`);
|
|
3312
|
+
} finally {
|
|
3313
|
+
this.configuring = false;
|
|
3314
|
+
if (this.status !== "closed") {
|
|
3315
|
+
this.mount();
|
|
3316
|
+
this.rerender();
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3151
3320
|
createApp() {
|
|
3152
3321
|
return React.createElement(RemoteDebugInkApp, {
|
|
3153
3322
|
summary: this.summary,
|
|
3154
3323
|
status: this.status,
|
|
3155
3324
|
source: this.source,
|
|
3156
3325
|
logs: this.logs,
|
|
3157
|
-
|
|
3326
|
+
onClose: () => this.requestClose("SIGINT"),
|
|
3327
|
+
onConfigureConnectionKey: this.requestConfigureConnectionKey
|
|
3158
3328
|
});
|
|
3159
3329
|
}
|
|
3160
3330
|
};
|
|
3161
|
-
function RemoteDebugInkApp({ summary, status, source, logs,
|
|
3331
|
+
function RemoteDebugInkApp({ summary, status, source, logs, onClose, onConfigureConnectionKey }) {
|
|
3332
|
+
const { suspendTerminal } = useApp();
|
|
3162
3333
|
useInput((inputValue, key) => {
|
|
3163
3334
|
if (key.ctrl && inputValue === "c") {
|
|
3164
|
-
|
|
3335
|
+
onClose();
|
|
3165
3336
|
return;
|
|
3166
3337
|
}
|
|
3167
|
-
if (inputValue === "
|
|
3338
|
+
if (inputValue === "c") onConfigureConnectionKey(suspendTerminal);
|
|
3168
3339
|
});
|
|
3169
3340
|
const statusMeta = getTuiStatusMeta(status);
|
|
3170
3341
|
const sourceLabel = source === "-" ? "waiting for bind" : source;
|
|
@@ -3256,7 +3427,7 @@ function RemoteDebugInkApp({ summary, status, source, logs, onQuit }) {
|
|
|
3256
3427
|
}, ...activityItems)), React.createElement(Box, {
|
|
3257
3428
|
marginTop: 1,
|
|
3258
3429
|
justifyContent: "space-between"
|
|
3259
|
-
}, React.createElement(Text, { dimColor: true }, "
|
|
3430
|
+
}, React.createElement(Text, { dimColor: true }, "c configure connection key"), React.createElement(Text, { dimColor: true }, "Ctrl+C stop · second Ctrl+C force exit")));
|
|
3260
3431
|
}
|
|
3261
3432
|
function getTuiStatusMeta(status) {
|
|
3262
3433
|
if (status === "connected") return {
|
|
@@ -3273,6 +3444,13 @@ function getTuiStatusMeta(status) {
|
|
|
3273
3444
|
color: "gray",
|
|
3274
3445
|
borderColor: "gray"
|
|
3275
3446
|
};
|
|
3447
|
+
if (status === "configuring") return {
|
|
3448
|
+
label: "CONFIGURE",
|
|
3449
|
+
marker: "◆",
|
|
3450
|
+
description: "Waiting for a new FastGPT connection key.",
|
|
3451
|
+
color: "cyanBright",
|
|
3452
|
+
borderColor: "cyan"
|
|
3453
|
+
};
|
|
3276
3454
|
if (status === "closing") return {
|
|
3277
3455
|
label: "CLOSING",
|
|
3278
3456
|
marker: "◆",
|
|
@@ -3329,7 +3507,7 @@ function formatConnectionKeyExchangeError(error) {
|
|
|
3329
3507
|
}
|
|
3330
3508
|
async function resolveConnectGatewayOptions(options) {
|
|
3331
3509
|
const info = await exchangeConnectLink(options.connect);
|
|
3332
|
-
|
|
3510
|
+
const resolvedOptions = {
|
|
3333
3511
|
gatewayUrl: normalizeGatewayWsUrl(info.gatewayUrl),
|
|
3334
3512
|
connectToken: info.connectToken,
|
|
3335
3513
|
userId: info.tmbId,
|
|
@@ -3337,9 +3515,20 @@ async function resolveConnectGatewayOptions(options) {
|
|
|
3337
3515
|
fastgptBaseUrl: info.fastgptBaseUrl,
|
|
3338
3516
|
tokenTtlMs: Math.max(1, info.expiresAt - Date.now()),
|
|
3339
3517
|
reconnect: options.noReconnect ? false : options.reconnect ?? true,
|
|
3340
|
-
reconnectIntervalMs: toPositiveInt(options.reconnectIntervalMs ?? process.env.CONNECTION_GATEWAY_RECONNECT_INTERVAL_MS ?? "2000", "reconnect-interval-ms")
|
|
3341
|
-
|
|
3518
|
+
reconnectIntervalMs: toPositiveInt(options.reconnectIntervalMs ?? process.env.CONNECTION_GATEWAY_RECONNECT_INTERVAL_MS ?? "2000", "reconnect-interval-ms")
|
|
3519
|
+
};
|
|
3520
|
+
resolvedOptions.resolveReconnectOptions = async () => {
|
|
3521
|
+
const nextOptions = await resolveConnectGatewayOptions(options);
|
|
3522
|
+
if (options.connectPersistOnSuccess === true) {
|
|
3523
|
+
await saveConnectionKey(options.connect);
|
|
3524
|
+
logger.info(`已保存 FastGPT connection key 到本地配置: ${getCliConfigPath()}`);
|
|
3525
|
+
options.connectPersistOnSuccess = false;
|
|
3526
|
+
}
|
|
3527
|
+
resolvedOptions.onResolvedReconnectOptions?.(nextOptions);
|
|
3528
|
+
nextOptions.onResolvedReconnectOptions = resolvedOptions.onResolvedReconnectOptions;
|
|
3529
|
+
return nextOptions;
|
|
3342
3530
|
};
|
|
3531
|
+
return resolvedOptions;
|
|
3343
3532
|
}
|
|
3344
3533
|
function resolveUploadDir({ entryDir, index, total, uploadDir }) {
|
|
3345
3534
|
if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
|
|
@@ -3719,9 +3908,6 @@ async function run(argv) {
|
|
|
3719
3908
|
|
|
3720
3909
|
//#endregion
|
|
3721
3910
|
//#region src/index.ts
|
|
3722
|
-
process.on("SIGINT", () => {
|
|
3723
|
-
process.exit(130);
|
|
3724
|
-
});
|
|
3725
3911
|
const main = async () => {
|
|
3726
3912
|
try {
|
|
3727
3913
|
await run();
|