@fastgpt-plugin/cli 0.1.0-beta.9 → 0.2.0-alpha.1
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 +71 -0
- package/README.md +41 -4
- package/dist/index.js +1296 -91
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -8,8 +8,12 @@ import { build } from "tsdown";
|
|
|
8
8
|
import z from "zod";
|
|
9
9
|
import { input, select } from "@inquirer/prompts";
|
|
10
10
|
import { kebabCase } from "es-toolkit";
|
|
11
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
12
11
|
import fs$1, { createWriteStream } from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
14
|
+
import { Box, Text, render, useInput } from "ink";
|
|
15
|
+
import React from "react";
|
|
16
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
13
17
|
import { Readable } from "stream";
|
|
14
18
|
import { Readable as Readable$1 } from "node:stream";
|
|
15
19
|
import { ZipFile } from "yazl";
|
|
@@ -48,27 +52,6 @@ const PluginPermissionEnumSchema = z.enum([
|
|
|
48
52
|
]);
|
|
49
53
|
const PluginPermissionEnum = PluginPermissionEnumSchema.enum;
|
|
50
54
|
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region ../../packages/domain/src/value-objects/plugin.vo.ts
|
|
53
|
-
const PluginUniqueIdSchema = z.object({
|
|
54
|
-
pluginId: z.string(),
|
|
55
|
-
version: z.string(),
|
|
56
|
-
etag: z.string()
|
|
57
|
-
});
|
|
58
|
-
const PluginSourceSchema = z.literal("system").or(z.string());
|
|
59
|
-
const PluginTagListItemSchema = z.object({
|
|
60
|
-
id: z.string(),
|
|
61
|
-
name: I18nStringStrictSchema
|
|
62
|
-
});
|
|
63
|
-
const PluginTagListSchema = z.array(PluginTagListItemSchema);
|
|
64
|
-
const PluginRuntimeModeSchema = z.enum(["localPool", "serverless"]);
|
|
65
|
-
const PluginRuntimeModeEnum = PluginRuntimeModeSchema.enum;
|
|
66
|
-
const UserPluginIdSchema = z.object({
|
|
67
|
-
pluginId: z.string(),
|
|
68
|
-
version: z.string().optional(),
|
|
69
|
-
source: PluginSourceSchema.optional()
|
|
70
|
-
});
|
|
71
|
-
|
|
72
55
|
//#endregion
|
|
73
56
|
//#region ../../packages/domain/src/entities/plugin-base.entity.ts
|
|
74
57
|
const PluginTagSchema = z.enum([
|
|
@@ -543,7 +526,7 @@ var CheckCommand = class extends BaseCommand {
|
|
|
543
526
|
//#endregion
|
|
544
527
|
//#region package.json
|
|
545
528
|
var name = "@fastgpt-plugin/cli";
|
|
546
|
-
var version = "0.
|
|
529
|
+
var version = "0.2.0-alpha.1";
|
|
547
530
|
|
|
548
531
|
//#endregion
|
|
549
532
|
//#region src/constants.ts
|
|
@@ -703,6 +686,301 @@ var CreateCommand = class extends BaseCommand {
|
|
|
703
686
|
}
|
|
704
687
|
};
|
|
705
688
|
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/debug/discover.ts
|
|
691
|
+
const IGNORED_DIRS = new Set([
|
|
692
|
+
".build",
|
|
693
|
+
".fastgpt-plugin-debug",
|
|
694
|
+
".git",
|
|
695
|
+
".turbo",
|
|
696
|
+
".vite",
|
|
697
|
+
"coverage",
|
|
698
|
+
"dist",
|
|
699
|
+
"node_modules",
|
|
700
|
+
"__pack_output__"
|
|
701
|
+
]);
|
|
702
|
+
async function discoverDebugEntries(cwd = process.cwd()) {
|
|
703
|
+
const root = path.resolve(cwd);
|
|
704
|
+
if (await hasIndexFile(root)) return [root];
|
|
705
|
+
const children = await fs.readdir(root, { withFileTypes: true });
|
|
706
|
+
const entries = [];
|
|
707
|
+
for (const child of children) {
|
|
708
|
+
if (!child.isDirectory() || IGNORED_DIRS.has(child.name)) continue;
|
|
709
|
+
const candidate = path.join(root, child.name);
|
|
710
|
+
if (await hasIndexFile(candidate)) entries.push(candidate);
|
|
711
|
+
}
|
|
712
|
+
entries.sort();
|
|
713
|
+
if (entries.length === 0) throw new Error(`当前目录未发现可调试插件,请传入插件目录或在当前目录下放置 index.ts。`);
|
|
714
|
+
return entries;
|
|
715
|
+
}
|
|
716
|
+
async function hasIndexFile(dir) {
|
|
717
|
+
try {
|
|
718
|
+
return (await fs.stat(path.join(dir, "index.ts"))).isFile();
|
|
719
|
+
} catch {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
//#endregion
|
|
725
|
+
//#region ../../packages/domain/src/value-objects/connection-gateway.vo.ts
|
|
726
|
+
const ConnectionGatewayConsumerTypeSchema = z.string().min(1).max(64);
|
|
727
|
+
const ConnectionGatewayTransportSchema = z.literal("websocket");
|
|
728
|
+
const ConnectionGatewayCapabilitySchema = z.string().min(1).max(64);
|
|
729
|
+
const ConnectionGatewaySessionScopeSchema = z.object({
|
|
730
|
+
userId: z.string().min(1),
|
|
731
|
+
teamId: z.string().min(1).optional(),
|
|
732
|
+
source: z.string().min(1).optional(),
|
|
733
|
+
sources: z.array(z.string().min(1)).optional()
|
|
734
|
+
});
|
|
735
|
+
const ConnectionGatewayTokenClaimsSchema = z.object({
|
|
736
|
+
consumerType: ConnectionGatewayConsumerTypeSchema,
|
|
737
|
+
subject: z.string().min(1),
|
|
738
|
+
sessionScope: ConnectionGatewaySessionScopeSchema,
|
|
739
|
+
transport: ConnectionGatewayTransportSchema,
|
|
740
|
+
capabilities: z.array(ConnectionGatewayCapabilitySchema).default([]),
|
|
741
|
+
expiresAt: z.number().int().positive(),
|
|
742
|
+
issuedAt: z.number().int().positive().optional(),
|
|
743
|
+
nonce: z.string().min(1).optional()
|
|
744
|
+
});
|
|
745
|
+
const ConnectionGatewayEnvelopeSchema = z.object({
|
|
746
|
+
protocol: z.literal("connection-gateway.v1"),
|
|
747
|
+
sessionId: z.string().min(1),
|
|
748
|
+
generation: z.number().int().nonnegative(),
|
|
749
|
+
requestId: z.string().min(1).optional(),
|
|
750
|
+
type: z.enum([
|
|
751
|
+
"request",
|
|
752
|
+
"response",
|
|
753
|
+
"event",
|
|
754
|
+
"stream"
|
|
755
|
+
]),
|
|
756
|
+
consumerType: ConnectionGatewayConsumerTypeSchema,
|
|
757
|
+
capability: ConnectionGatewayCapabilitySchema.optional(),
|
|
758
|
+
traceId: z.string().optional(),
|
|
759
|
+
payload: z.unknown().optional(),
|
|
760
|
+
createdAt: z.number().int().positive()
|
|
761
|
+
});
|
|
762
|
+
const ConnectionGatewayWsProtocolSchema = z.literal("connection-gateway.ws.v1");
|
|
763
|
+
const ConnectionGatewayWsBindMessageSchema = z.object({
|
|
764
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
765
|
+
type: z.literal("bind"),
|
|
766
|
+
requestId: z.string().min(1),
|
|
767
|
+
token: z.string().min(1),
|
|
768
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
769
|
+
});
|
|
770
|
+
const ConnectionGatewayWsEnvelopeMessageSchema = z.object({
|
|
771
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
772
|
+
type: z.literal("envelope"),
|
|
773
|
+
envelope: ConnectionGatewayEnvelopeSchema
|
|
774
|
+
});
|
|
775
|
+
const ConnectionGatewayWsHeartbeatMessageSchema = z.object({
|
|
776
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
777
|
+
type: z.literal("heartbeat"),
|
|
778
|
+
ts: z.number().int().positive()
|
|
779
|
+
});
|
|
780
|
+
const ConnectionGatewayWsErrorMessageSchema = z.object({
|
|
781
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
782
|
+
type: z.literal("error"),
|
|
783
|
+
requestId: z.string().min(1).optional(),
|
|
784
|
+
code: z.string().min(1),
|
|
785
|
+
message: z.string().min(1)
|
|
786
|
+
});
|
|
787
|
+
const ConnectionGatewayWsBoundMessageSchema = z.object({
|
|
788
|
+
protocol: ConnectionGatewayWsProtocolSchema,
|
|
789
|
+
type: z.literal("bound"),
|
|
790
|
+
requestId: z.string().min(1),
|
|
791
|
+
session: z.lazy(() => ConnectionGatewaySessionSchema)
|
|
792
|
+
});
|
|
793
|
+
const ConnectionGatewayWsClientMessageSchema = z.discriminatedUnion("type", [
|
|
794
|
+
ConnectionGatewayWsBindMessageSchema,
|
|
795
|
+
ConnectionGatewayWsEnvelopeMessageSchema,
|
|
796
|
+
ConnectionGatewayWsHeartbeatMessageSchema
|
|
797
|
+
]);
|
|
798
|
+
const ConnectionGatewayWsServerMessageSchema = z.discriminatedUnion("type", [
|
|
799
|
+
ConnectionGatewayWsBoundMessageSchema,
|
|
800
|
+
ConnectionGatewayWsEnvelopeMessageSchema,
|
|
801
|
+
ConnectionGatewayWsHeartbeatMessageSchema,
|
|
802
|
+
ConnectionGatewayWsErrorMessageSchema
|
|
803
|
+
]);
|
|
804
|
+
const ConnectionGatewaySessionStatusSchema = z.enum([
|
|
805
|
+
"connecting",
|
|
806
|
+
"connected",
|
|
807
|
+
"draining",
|
|
808
|
+
"closed"
|
|
809
|
+
]);
|
|
810
|
+
const ConnectionGatewaySessionSchema = z.object({
|
|
811
|
+
id: z.string().min(1),
|
|
812
|
+
consumerType: ConnectionGatewayConsumerTypeSchema,
|
|
813
|
+
subject: z.string().min(1),
|
|
814
|
+
sessionScope: ConnectionGatewaySessionScopeSchema,
|
|
815
|
+
transport: ConnectionGatewayTransportSchema,
|
|
816
|
+
capabilities: z.array(ConnectionGatewayCapabilitySchema),
|
|
817
|
+
generation: z.number().int().nonnegative(),
|
|
818
|
+
ownerNodeId: z.string().min(1),
|
|
819
|
+
status: ConnectionGatewaySessionStatusSchema,
|
|
820
|
+
connectedAt: z.number().int().positive(),
|
|
821
|
+
lastSeenAt: z.number().int().positive(),
|
|
822
|
+
expiresAt: z.number().int().positive(),
|
|
823
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
824
|
+
});
|
|
825
|
+
const ConnectionGatewaySessionStatusViewSchema = z.object({
|
|
826
|
+
session: ConnectionGatewaySessionSchema.nullable(),
|
|
827
|
+
ownerAlive: z.boolean(),
|
|
828
|
+
mailboxLag: z.number().int().nonnegative(),
|
|
829
|
+
logs: z.array(z.object({
|
|
830
|
+
ts: z.number().int().positive(),
|
|
831
|
+
level: z.enum([
|
|
832
|
+
"debug",
|
|
833
|
+
"info",
|
|
834
|
+
"warn",
|
|
835
|
+
"error"
|
|
836
|
+
]),
|
|
837
|
+
message: z.string(),
|
|
838
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
839
|
+
}))
|
|
840
|
+
});
|
|
841
|
+
const ConnectionGatewayMetricsSchema = z.object({
|
|
842
|
+
nodeId: z.string(),
|
|
843
|
+
activeConnections: z.number().int().nonnegative(),
|
|
844
|
+
activeSessions: z.number().int().nonnegative(),
|
|
845
|
+
inFlightRequests: z.number().int().nonnegative(),
|
|
846
|
+
streamBufferBytes: z.number().int().nonnegative(),
|
|
847
|
+
slowConsumers: z.number().int().nonnegative(),
|
|
848
|
+
ownerLeaseExpiries: z.number().int().nonnegative(),
|
|
849
|
+
mailbox: z.object({
|
|
850
|
+
lag: z.number().int().nonnegative(),
|
|
851
|
+
redisRoundTripMs: z.number().nonnegative()
|
|
852
|
+
}),
|
|
853
|
+
limits: z.object({
|
|
854
|
+
maxConnections: z.number().int().positive(),
|
|
855
|
+
maxSessionsPerSubject: z.number().int().positive(),
|
|
856
|
+
maxInFlightPerSession: z.number().int().positive(),
|
|
857
|
+
maxEnvelopeBytes: z.number().int().positive()
|
|
858
|
+
})
|
|
859
|
+
});
|
|
860
|
+
const ConnectionGatewayResourceLimitsSchema = z.object({
|
|
861
|
+
maxConnections: z.number().int().positive(),
|
|
862
|
+
maxSessionsPerSubject: z.number().int().positive(),
|
|
863
|
+
maxInFlightPerSession: z.number().int().positive(),
|
|
864
|
+
maxEnvelopeBytes: z.number().int().positive(),
|
|
865
|
+
slowConsumerBufferBytes: z.number().int().positive()
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
//#endregion
|
|
869
|
+
//#region ../../packages/domain/src/value-objects/system-var.vo.ts
|
|
870
|
+
const SystemVarSchema = z.object({
|
|
871
|
+
app: z.object({
|
|
872
|
+
id: z.string(),
|
|
873
|
+
name: z.string()
|
|
874
|
+
}),
|
|
875
|
+
chat: z.object({
|
|
876
|
+
chatId: z.string(),
|
|
877
|
+
uid: z.string().optional()
|
|
878
|
+
}),
|
|
879
|
+
invokeToken: z.string(),
|
|
880
|
+
time: z.string()
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
//#endregion
|
|
884
|
+
//#region ../../packages/domain/src/value-objects/plugin.vo.ts
|
|
885
|
+
const PluginUniqueIdSchema = z.object({
|
|
886
|
+
pluginId: z.string(),
|
|
887
|
+
version: z.string(),
|
|
888
|
+
etag: z.string()
|
|
889
|
+
});
|
|
890
|
+
const PluginSourceSchema = z.literal("system").or(z.string());
|
|
891
|
+
const PluginTagListItemSchema = z.object({
|
|
892
|
+
id: z.string(),
|
|
893
|
+
name: I18nStringStrictSchema
|
|
894
|
+
});
|
|
895
|
+
const PluginTagListSchema = z.array(PluginTagListItemSchema);
|
|
896
|
+
const PluginRuntimeModeSchema = z.enum(["localPool", "serverless"]);
|
|
897
|
+
const PluginRuntimeModeEnum = PluginRuntimeModeSchema.enum;
|
|
898
|
+
const UserPluginIdSchema = z.object({
|
|
899
|
+
pluginId: z.string(),
|
|
900
|
+
version: z.string().optional(),
|
|
901
|
+
source: PluginSourceSchema.optional()
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
//#endregion
|
|
905
|
+
//#region ../../packages/domain/src/value-objects/tool.vo.ts
|
|
906
|
+
const ToolHandlerReturnSchema = z.record(z.string(), z.unknown());
|
|
907
|
+
const StreamMessageTypeSchema = z.enum([
|
|
908
|
+
"response",
|
|
909
|
+
"error",
|
|
910
|
+
"stream"
|
|
911
|
+
]);
|
|
912
|
+
const StreamMessageTypeEnum = StreamMessageTypeSchema.enum;
|
|
913
|
+
const StreamDataAnswerTypeSchema = z.enum(["answer", "fastAnswer"]);
|
|
914
|
+
const StreamDataAnswerTypeEnum = StreamDataAnswerTypeSchema.enum;
|
|
915
|
+
const ToolAnswerSchema = z.object({
|
|
916
|
+
type: StreamDataAnswerTypeSchema,
|
|
917
|
+
content: z.string()
|
|
918
|
+
});
|
|
919
|
+
const ToolStreamMessageSchema = z.discriminatedUnion("type", [
|
|
920
|
+
z.object({
|
|
921
|
+
type: z.literal(StreamMessageTypeEnum.response),
|
|
922
|
+
data: ToolHandlerReturnSchema
|
|
923
|
+
}),
|
|
924
|
+
z.object({
|
|
925
|
+
type: z.literal(StreamMessageTypeEnum.stream),
|
|
926
|
+
data: ToolAnswerSchema
|
|
927
|
+
}),
|
|
928
|
+
z.object({
|
|
929
|
+
type: z.literal(StreamMessageTypeEnum.error),
|
|
930
|
+
data: z.string()
|
|
931
|
+
})
|
|
932
|
+
]);
|
|
933
|
+
const ToolRunInputSchema = z.object({
|
|
934
|
+
pluginId: z.string(),
|
|
935
|
+
version: z.preprocess((value) => {
|
|
936
|
+
if (value === "") return void 0;
|
|
937
|
+
return value;
|
|
938
|
+
}, z.string().optional()),
|
|
939
|
+
source: PluginSourceSchema.optional(),
|
|
940
|
+
childId: z.string().optional(),
|
|
941
|
+
input: z.record(z.string(), z.unknown()),
|
|
942
|
+
secrets: z.record(z.string(), z.unknown()).optional(),
|
|
943
|
+
systemVar: SystemVarSchema
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
//#endregion
|
|
947
|
+
//#region ../../packages/domain/src/value-objects/connection-gateway-debug.vo.ts
|
|
948
|
+
const CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY = "invoke";
|
|
949
|
+
const ConnectionGatewayPluginDebugRequestPayloadSchema = z.object({
|
|
950
|
+
kind: z.literal("plugin-debug.run"),
|
|
951
|
+
eventName: z.literal("run"),
|
|
952
|
+
payload: z.object({
|
|
953
|
+
pluginId: z.string().min(1).optional(),
|
|
954
|
+
input: z.record(z.string(), z.unknown()),
|
|
955
|
+
secrets: z.record(z.string(), z.unknown()).optional(),
|
|
956
|
+
systemVar: SystemVarSchema,
|
|
957
|
+
source: z.string().optional(),
|
|
958
|
+
childId: z.string().optional()
|
|
959
|
+
})
|
|
960
|
+
});
|
|
961
|
+
const ConnectionGatewayPluginDebugResponsePayloadSchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("plugin-debug.accepted") }), z.object({
|
|
962
|
+
kind: z.literal("plugin-debug.error"),
|
|
963
|
+
message: z.string(),
|
|
964
|
+
code: z.string().optional(),
|
|
965
|
+
data: z.unknown().optional()
|
|
966
|
+
})]);
|
|
967
|
+
const ConnectionGatewayPluginDebugStreamPayloadSchema = z.discriminatedUnion("event", [
|
|
968
|
+
z.object({
|
|
969
|
+
kind: z.literal("plugin-debug.stream"),
|
|
970
|
+
event: z.literal("chunk"),
|
|
971
|
+
data: ToolStreamMessageSchema
|
|
972
|
+
}),
|
|
973
|
+
z.object({
|
|
974
|
+
kind: z.literal("plugin-debug.stream"),
|
|
975
|
+
event: z.literal("end")
|
|
976
|
+
}),
|
|
977
|
+
z.object({
|
|
978
|
+
kind: z.literal("plugin-debug.stream"),
|
|
979
|
+
event: z.literal("error"),
|
|
980
|
+
message: z.string()
|
|
981
|
+
})
|
|
982
|
+
]);
|
|
983
|
+
|
|
706
984
|
//#endregion
|
|
707
985
|
//#region src/debug/import-hooks.ts
|
|
708
986
|
const SOURCE_EXTENSIONS = [
|
|
@@ -977,63 +1255,6 @@ var StreamData = class StreamData {
|
|
|
977
1255
|
}
|
|
978
1256
|
};
|
|
979
1257
|
|
|
980
|
-
//#endregion
|
|
981
|
-
//#region ../../packages/domain/src/value-objects/system-var.vo.ts
|
|
982
|
-
const SystemVarSchema = z.object({
|
|
983
|
-
app: z.object({
|
|
984
|
-
id: z.string(),
|
|
985
|
-
name: z.string()
|
|
986
|
-
}),
|
|
987
|
-
chat: z.object({
|
|
988
|
-
chatId: z.string(),
|
|
989
|
-
uid: z.string().optional()
|
|
990
|
-
}),
|
|
991
|
-
invokeToken: z.string(),
|
|
992
|
-
time: z.string()
|
|
993
|
-
});
|
|
994
|
-
|
|
995
|
-
//#endregion
|
|
996
|
-
//#region ../../packages/domain/src/value-objects/tool.vo.ts
|
|
997
|
-
const ToolHandlerReturnSchema = z.record(z.string(), z.unknown());
|
|
998
|
-
const StreamMessageTypeSchema = z.enum([
|
|
999
|
-
"response",
|
|
1000
|
-
"error",
|
|
1001
|
-
"stream"
|
|
1002
|
-
]);
|
|
1003
|
-
const StreamMessageTypeEnum = StreamMessageTypeSchema.enum;
|
|
1004
|
-
const StreamDataAnswerTypeSchema = z.enum(["answer", "fastAnswer"]);
|
|
1005
|
-
const StreamDataAnswerTypeEnum = StreamDataAnswerTypeSchema.enum;
|
|
1006
|
-
const ToolAnswerSchema = z.object({
|
|
1007
|
-
type: StreamDataAnswerTypeSchema,
|
|
1008
|
-
content: z.string()
|
|
1009
|
-
});
|
|
1010
|
-
const ToolStreamMessageSchema = z.discriminatedUnion("type", [
|
|
1011
|
-
z.object({
|
|
1012
|
-
type: z.literal(StreamMessageTypeEnum.response),
|
|
1013
|
-
data: ToolHandlerReturnSchema
|
|
1014
|
-
}),
|
|
1015
|
-
z.object({
|
|
1016
|
-
type: z.literal(StreamMessageTypeEnum.stream),
|
|
1017
|
-
data: ToolAnswerSchema
|
|
1018
|
-
}),
|
|
1019
|
-
z.object({
|
|
1020
|
-
type: z.literal(StreamMessageTypeEnum.error),
|
|
1021
|
-
data: z.string()
|
|
1022
|
-
})
|
|
1023
|
-
]);
|
|
1024
|
-
const ToolRunInputSchema = z.object({
|
|
1025
|
-
pluginId: z.string(),
|
|
1026
|
-
version: z.preprocess((value) => {
|
|
1027
|
-
if (value === "") return void 0;
|
|
1028
|
-
return value;
|
|
1029
|
-
}, z.string().optional()),
|
|
1030
|
-
source: PluginSourceSchema.optional(),
|
|
1031
|
-
childId: z.string().optional(),
|
|
1032
|
-
input: z.record(z.string(), z.unknown()),
|
|
1033
|
-
secrets: z.record(z.string(), z.unknown()).optional(),
|
|
1034
|
-
systemVar: SystemVarSchema
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
1258
|
//#endregion
|
|
1038
1259
|
//#region ../../packages/infrastructure/src/plugin/plugin-runtime/ports/channel/event/client.ts
|
|
1039
1260
|
/**
|
|
@@ -1171,7 +1392,9 @@ const PluginChannelMessageIdSchema = z.union([z.string(), z.number()]);
|
|
|
1171
1392
|
const PluginChannelErrorSchema = z.object({
|
|
1172
1393
|
code: z.string(),
|
|
1173
1394
|
message: z.string(),
|
|
1174
|
-
|
|
1395
|
+
reason: I18nStringSchema.optional(),
|
|
1396
|
+
data: z.unknown().optional(),
|
|
1397
|
+
cause: z.lazy(() => PluginChannelErrorSchema).optional()
|
|
1175
1398
|
});
|
|
1176
1399
|
/**
|
|
1177
1400
|
* request message:需要对端返回 success/error。
|
|
@@ -1467,7 +1690,7 @@ function createIncomingStream({ source, streamName, streamId, traceId, meta }) {
|
|
|
1467
1690
|
};
|
|
1468
1691
|
}
|
|
1469
1692
|
function toStreamData(source) {
|
|
1470
|
-
if (source
|
|
1693
|
+
if (isStreamDataLike$1(source)) return source;
|
|
1471
1694
|
const output = StreamData.create();
|
|
1472
1695
|
(async () => {
|
|
1473
1696
|
try {
|
|
@@ -1479,6 +1702,9 @@ function toStreamData(source) {
|
|
|
1479
1702
|
})();
|
|
1480
1703
|
return output;
|
|
1481
1704
|
}
|
|
1705
|
+
function isStreamDataLike$1(source) {
|
|
1706
|
+
return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
|
|
1707
|
+
}
|
|
1482
1708
|
|
|
1483
1709
|
//#endregion
|
|
1484
1710
|
//#region src/debug/session.ts
|
|
@@ -1643,10 +1869,13 @@ function createVirtualHostHandler(uploadDir) {
|
|
|
1643
1869
|
async function readSourceToBuffer(source) {
|
|
1644
1870
|
if (!source) return Buffer.alloc(0);
|
|
1645
1871
|
const chunks = [];
|
|
1646
|
-
const iterable = source
|
|
1872
|
+
const iterable = isStreamDataLike(source) ? source.values() : source;
|
|
1647
1873
|
for await (const chunk of iterable) chunks.push(toBuffer(chunk));
|
|
1648
1874
|
return Buffer.concat(chunks);
|
|
1649
1875
|
}
|
|
1876
|
+
function isStreamDataLike(source) {
|
|
1877
|
+
return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
|
|
1878
|
+
}
|
|
1650
1879
|
function toBuffer(value) {
|
|
1651
1880
|
if (Buffer.isBuffer(value)) return value;
|
|
1652
1881
|
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
@@ -1714,22 +1943,960 @@ async function assertPathExists(targetPath, message) {
|
|
|
1714
1943
|
}
|
|
1715
1944
|
}
|
|
1716
1945
|
|
|
1946
|
+
//#endregion
|
|
1947
|
+
//#region src/debug/gateway.ts
|
|
1948
|
+
async function connectDebugGateway({ targets, options, onLog }) {
|
|
1949
|
+
if (targets.length === 0) throw new Error("Gateway debug targets cannot be empty");
|
|
1950
|
+
if (options.reconnect) return connectReconnectingDebugGateway({
|
|
1951
|
+
targets,
|
|
1952
|
+
options,
|
|
1953
|
+
onLog
|
|
1954
|
+
});
|
|
1955
|
+
return connectSingleDebugGateway({
|
|
1956
|
+
targets,
|
|
1957
|
+
options,
|
|
1958
|
+
onLog
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
async function connectReconnectingDebugGateway({ targets, options, onLog }) {
|
|
1962
|
+
let closed = false;
|
|
1963
|
+
let current = null;
|
|
1964
|
+
let currentSession = null;
|
|
1965
|
+
let latestTargets = targets;
|
|
1966
|
+
const intervalMs = Math.max(100, options.reconnectIntervalMs ?? 2e3);
|
|
1967
|
+
const closedPromise = (async () => {
|
|
1968
|
+
while (!closed) {
|
|
1969
|
+
try {
|
|
1970
|
+
current = await connectSingleDebugGateway({
|
|
1971
|
+
targets: latestTargets,
|
|
1972
|
+
options,
|
|
1973
|
+
onLog
|
|
1974
|
+
});
|
|
1975
|
+
currentSession = current.session;
|
|
1976
|
+
await current.closed;
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
if (closed) return;
|
|
1979
|
+
onLog?.(`Connection Gateway 调试通道断开: ${formatErrorMessage(error)}`);
|
|
1980
|
+
} finally {
|
|
1981
|
+
current = null;
|
|
1982
|
+
}
|
|
1983
|
+
if (!closed) {
|
|
1984
|
+
onLog?.(`将在 ${intervalMs}ms 后重连 Connection Gateway。`);
|
|
1985
|
+
await delay(intervalMs);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
})();
|
|
1989
|
+
while (!currentSession && !closed) await delay(10);
|
|
1990
|
+
if (!currentSession) throw new Error("Connection Gateway debug session was closed before connecting");
|
|
1991
|
+
return {
|
|
1992
|
+
session: currentSession,
|
|
1993
|
+
updateTargets(targets) {
|
|
1994
|
+
latestTargets = targets;
|
|
1995
|
+
current?.updateTargets(targets);
|
|
1996
|
+
},
|
|
1997
|
+
close() {
|
|
1998
|
+
closed = true;
|
|
1999
|
+
current?.close();
|
|
2000
|
+
},
|
|
2001
|
+
closed: closedPromise
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
async function connectSingleDebugGateway({ targets, options, onLog }) {
|
|
2005
|
+
const socket = await openWebSocket(options.gatewayUrl);
|
|
2006
|
+
let targetsByPluginId = createTargetsByPluginId(targets);
|
|
2007
|
+
let closed = false;
|
|
2008
|
+
let session = null;
|
|
2009
|
+
let boundResolve;
|
|
2010
|
+
let boundReject;
|
|
2011
|
+
const bound = new Promise((resolve, reject) => {
|
|
2012
|
+
boundResolve = resolve;
|
|
2013
|
+
boundReject = reject;
|
|
2014
|
+
});
|
|
2015
|
+
const closedPromise = new Promise((resolve) => {
|
|
2016
|
+
socket.addEventListener("close", () => {
|
|
2017
|
+
closed = true;
|
|
2018
|
+
resolve();
|
|
2019
|
+
});
|
|
2020
|
+
socket.addEventListener("error", () => {
|
|
2021
|
+
onLog?.("Connection Gateway WebSocket 错误");
|
|
2022
|
+
});
|
|
2023
|
+
});
|
|
2024
|
+
socket.addEventListener("message", (event) => {
|
|
2025
|
+
const message = parseWsServerMessage(event.data);
|
|
2026
|
+
if (message.type === "bound") {
|
|
2027
|
+
session = message.session;
|
|
2028
|
+
boundResolve(message.session);
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
if (message.type === "heartbeat") return;
|
|
2032
|
+
if (message.type === "error") {
|
|
2033
|
+
const error = new Error(message.message);
|
|
2034
|
+
if (!session) boundReject(error);
|
|
2035
|
+
onLog?.(`Connection Gateway 错误: ${message.code} ${message.message}`);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
if (message.type === "envelope") {
|
|
2039
|
+
const envelope = message.envelope;
|
|
2040
|
+
if (!session) {
|
|
2041
|
+
boundReject(/* @__PURE__ */ new Error("Gateway envelope received before session bound"));
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
handleGatewayEnvelope({
|
|
2045
|
+
socket,
|
|
2046
|
+
session,
|
|
2047
|
+
targetsByPluginId,
|
|
2048
|
+
envelope,
|
|
2049
|
+
onLog
|
|
2050
|
+
}).catch(async (error) => {
|
|
2051
|
+
if (session) await sendEnvelope(socket, makeErrorEnvelope(session, envelope, error));
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
await sendMessage(socket, {
|
|
2056
|
+
protocol: "connection-gateway.ws.v1",
|
|
2057
|
+
type: "bind",
|
|
2058
|
+
requestId: randomUUID(),
|
|
2059
|
+
token: options.connectToken,
|
|
2060
|
+
metadata: makePluginDebugMetadata(targets, options.source)
|
|
2061
|
+
}).catch(async (error) => {
|
|
2062
|
+
socket.close();
|
|
2063
|
+
throw error;
|
|
2064
|
+
});
|
|
2065
|
+
const boundSession = await bound;
|
|
2066
|
+
onLog?.(`已连接 Connection Gateway session: ${boundSession.id}`);
|
|
2067
|
+
return {
|
|
2068
|
+
session: boundSession,
|
|
2069
|
+
updateTargets(targets) {
|
|
2070
|
+
targetsByPluginId = createTargetsByPluginId(targets);
|
|
2071
|
+
onLog?.(`已热更新本地调试插件: ${targets.map((target) => target.snapshot.pluginId).join(", ")}`);
|
|
2072
|
+
},
|
|
2073
|
+
close() {
|
|
2074
|
+
if (!closed) socket.close();
|
|
2075
|
+
},
|
|
2076
|
+
closed: closedPromise
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
function createTargetsByPluginId(targets) {
|
|
2080
|
+
return new Map(targets.map((target) => [target.snapshot.pluginId, target]));
|
|
2081
|
+
}
|
|
2082
|
+
async function handleGatewayEnvelope({ socket, session, targetsByPluginId, envelope, onLog }) {
|
|
2083
|
+
if (envelope.type !== "request") return;
|
|
2084
|
+
const request = ConnectionGatewayPluginDebugRequestPayloadSchema.parse(envelope.payload);
|
|
2085
|
+
const source = request.payload.source ?? session.sessionScope.source;
|
|
2086
|
+
if (!source) throw new Error("Gateway debug request missing source");
|
|
2087
|
+
const pluginId = request.payload.pluginId;
|
|
2088
|
+
if (!pluginId) throw new Error("Gateway debug request missing pluginId");
|
|
2089
|
+
const target = targetsByPluginId.get(pluginId);
|
|
2090
|
+
if (!target) throw new Error(`Gateway debug target not found: ${source} ${pluginId}`);
|
|
2091
|
+
onLog?.(`收到远程调试请求: ${source} ${pluginId} ${envelope.requestId ?? "-"}`);
|
|
2092
|
+
await sendEnvelope(socket, {
|
|
2093
|
+
protocol: "connection-gateway.v1",
|
|
2094
|
+
sessionId: session.id,
|
|
2095
|
+
generation: session.generation,
|
|
2096
|
+
requestId: requiredRequestId(envelope),
|
|
2097
|
+
type: "response",
|
|
2098
|
+
consumerType: session.consumerType,
|
|
2099
|
+
capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
|
|
2100
|
+
traceId: envelope.traceId,
|
|
2101
|
+
createdAt: Date.now(),
|
|
2102
|
+
payload: { kind: "plugin-debug.accepted" }
|
|
2103
|
+
});
|
|
2104
|
+
const result = await runDebugTool({
|
|
2105
|
+
runtime: target.runtime,
|
|
2106
|
+
snapshot: target.snapshot,
|
|
2107
|
+
toolId: request.payload.childId,
|
|
2108
|
+
input: request.payload.input,
|
|
2109
|
+
secrets: request.payload.secrets,
|
|
2110
|
+
systemVar: request.payload.systemVar
|
|
2111
|
+
});
|
|
2112
|
+
for (const message of result.streamMessages) await sendStreamChunk(socket, session, envelope, message);
|
|
2113
|
+
await sendEnvelope(socket, {
|
|
2114
|
+
protocol: "connection-gateway.v1",
|
|
2115
|
+
sessionId: session.id,
|
|
2116
|
+
generation: session.generation,
|
|
2117
|
+
requestId: requiredRequestId(envelope),
|
|
2118
|
+
type: "stream",
|
|
2119
|
+
consumerType: session.consumerType,
|
|
2120
|
+
capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
|
|
2121
|
+
traceId: envelope.traceId,
|
|
2122
|
+
createdAt: Date.now(),
|
|
2123
|
+
payload: {
|
|
2124
|
+
kind: "plugin-debug.stream",
|
|
2125
|
+
event: "end"
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
async function sendStreamChunk(socket, session, request, message) {
|
|
2130
|
+
await sendEnvelope(socket, {
|
|
2131
|
+
protocol: "connection-gateway.v1",
|
|
2132
|
+
sessionId: session.id,
|
|
2133
|
+
generation: session.generation,
|
|
2134
|
+
requestId: requiredRequestId(request),
|
|
2135
|
+
type: "stream",
|
|
2136
|
+
consumerType: session.consumerType,
|
|
2137
|
+
capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
|
|
2138
|
+
traceId: request.traceId,
|
|
2139
|
+
createdAt: Date.now(),
|
|
2140
|
+
payload: {
|
|
2141
|
+
kind: "plugin-debug.stream",
|
|
2142
|
+
event: "chunk",
|
|
2143
|
+
data: ToolStreamMessageSchema.parse(message)
|
|
2144
|
+
}
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
function makeErrorEnvelope(session, request, error) {
|
|
2148
|
+
return {
|
|
2149
|
+
protocol: "connection-gateway.v1",
|
|
2150
|
+
sessionId: session.id,
|
|
2151
|
+
generation: session.generation,
|
|
2152
|
+
requestId: request.requestId,
|
|
2153
|
+
type: "stream",
|
|
2154
|
+
consumerType: session.consumerType,
|
|
2155
|
+
capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
|
|
2156
|
+
traceId: request.traceId,
|
|
2157
|
+
createdAt: Date.now(),
|
|
2158
|
+
payload: {
|
|
2159
|
+
kind: "plugin-debug.stream",
|
|
2160
|
+
event: "error",
|
|
2161
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2162
|
+
}
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2165
|
+
function makePluginDebugMetadata(targets, source) {
|
|
2166
|
+
return { pluginDebug: { targets: targets.map((target) => ({
|
|
2167
|
+
source,
|
|
2168
|
+
pluginId: target.snapshot.pluginId,
|
|
2169
|
+
version: target.snapshot.version,
|
|
2170
|
+
name: target.snapshot.name,
|
|
2171
|
+
description: target.snapshot.description,
|
|
2172
|
+
toolDescription: target.snapshot.toolDescription,
|
|
2173
|
+
author: target.snapshot.author,
|
|
2174
|
+
tags: target.snapshot.tags,
|
|
2175
|
+
permissions: target.snapshot.permissions,
|
|
2176
|
+
secretSchema: target.snapshot.secretSchema,
|
|
2177
|
+
isToolSet: target.snapshot.isToolSet,
|
|
2178
|
+
tools: target.snapshot.tools
|
|
2179
|
+
})) } };
|
|
2180
|
+
}
|
|
2181
|
+
async function openWebSocket(url) {
|
|
2182
|
+
return new Promise((resolve, reject) => {
|
|
2183
|
+
const socket = new WebSocket(url);
|
|
2184
|
+
socket.addEventListener("open", () => {
|
|
2185
|
+
resolve(socket);
|
|
2186
|
+
}, { once: true });
|
|
2187
|
+
socket.addEventListener("error", () => {
|
|
2188
|
+
reject(/* @__PURE__ */ new Error(`Connection Gateway WebSocket connect failed: ${url}`));
|
|
2189
|
+
}, { once: true });
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
async function sendEnvelope(socket, envelope) {
|
|
2193
|
+
await sendMessage(socket, {
|
|
2194
|
+
protocol: "connection-gateway.ws.v1",
|
|
2195
|
+
type: "envelope",
|
|
2196
|
+
envelope
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
async function sendMessage(socket, message) {
|
|
2200
|
+
if (socket.readyState !== WebSocket.OPEN) throw new Error("Connection Gateway WebSocket is not open");
|
|
2201
|
+
socket.send(JSON.stringify(message));
|
|
2202
|
+
}
|
|
2203
|
+
function requiredRequestId(envelope) {
|
|
2204
|
+
if (!envelope.requestId) throw new Error("Gateway request envelope missing requestId");
|
|
2205
|
+
return envelope.requestId;
|
|
2206
|
+
}
|
|
2207
|
+
function delay(ms) {
|
|
2208
|
+
return new Promise((resolve) => {
|
|
2209
|
+
setTimeout(resolve, ms);
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
function formatErrorMessage(error) {
|
|
2213
|
+
return error instanceof Error ? error.message : String(error);
|
|
2214
|
+
}
|
|
2215
|
+
function parseWsServerMessage(data) {
|
|
2216
|
+
const text = typeof data === "string" ? data : data instanceof ArrayBuffer ? Buffer.from(data).toString("utf8") : ArrayBuffer.isView(data) ? Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8") : String(data);
|
|
2217
|
+
return ConnectionGatewayWsServerMessageSchema.parse(JSON.parse(text));
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
//#endregion
|
|
2221
|
+
//#region src/debug/remote-session.ts
|
|
2222
|
+
async function runRemoteDebugSession({ entriesInput, options, commandName, migrationHint }) {
|
|
2223
|
+
if (migrationHint) logger.warn(migrationHint);
|
|
2224
|
+
if (options.watch) {
|
|
2225
|
+
await runWatchRemoteDebugSession({
|
|
2226
|
+
entriesInput,
|
|
2227
|
+
options,
|
|
2228
|
+
commandName
|
|
2229
|
+
});
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
await runRemoteDebugSessionOnce({
|
|
2233
|
+
entriesInput,
|
|
2234
|
+
options,
|
|
2235
|
+
commandName
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
async function runRemoteDebugSessionOnce({ entriesInput, options, commandName }) {
|
|
2239
|
+
const targets = await loadTargets(await resolveDebugEntries(entriesInput), options);
|
|
2240
|
+
await ensureConnectLink({
|
|
2241
|
+
options,
|
|
2242
|
+
commandName,
|
|
2243
|
+
targets
|
|
2244
|
+
});
|
|
2245
|
+
const gatewayOptions = await resolveGatewayOptions({
|
|
2246
|
+
options,
|
|
2247
|
+
commandName,
|
|
2248
|
+
targets
|
|
2249
|
+
});
|
|
2250
|
+
const reporter = createRemoteDebugReporter({
|
|
2251
|
+
commandName,
|
|
2252
|
+
options,
|
|
2253
|
+
gatewayOptions,
|
|
2254
|
+
targets
|
|
2255
|
+
});
|
|
2256
|
+
reporter.start();
|
|
2257
|
+
try {
|
|
2258
|
+
const gateway = await connectDebugGateway({
|
|
2259
|
+
targets,
|
|
2260
|
+
options: gatewayOptions,
|
|
2261
|
+
onLog: (message) => reporter.log(`[gateway] ${message}`)
|
|
2262
|
+
});
|
|
2263
|
+
reporter.attachClose(({ force }) => {
|
|
2264
|
+
gateway.close();
|
|
2265
|
+
if (force) process.exit(130);
|
|
2266
|
+
});
|
|
2267
|
+
const source = gateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
|
|
2268
|
+
reporter.ready(source);
|
|
2269
|
+
await gateway.closed;
|
|
2270
|
+
} finally {
|
|
2271
|
+
await reporter.end();
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
async function runWatchRemoteDebugSession({ entriesInput, options, commandName }) {
|
|
2275
|
+
const entries = await resolveDebugEntries(entriesInput);
|
|
2276
|
+
const initialTargets = await loadTargets(entries, options);
|
|
2277
|
+
await ensureConnectLink({
|
|
2278
|
+
options,
|
|
2279
|
+
commandName,
|
|
2280
|
+
targets: initialTargets
|
|
2281
|
+
});
|
|
2282
|
+
const gatewayOptions = await resolveGatewayOptions({
|
|
2283
|
+
options,
|
|
2284
|
+
commandName,
|
|
2285
|
+
targets: initialTargets
|
|
2286
|
+
});
|
|
2287
|
+
const reporter = createRemoteDebugReporter({
|
|
2288
|
+
commandName,
|
|
2289
|
+
options,
|
|
2290
|
+
gatewayOptions,
|
|
2291
|
+
targets: initialTargets
|
|
2292
|
+
});
|
|
2293
|
+
let reloadRunning = false;
|
|
2294
|
+
let reloadPending = false;
|
|
2295
|
+
let closed = false;
|
|
2296
|
+
let gateway;
|
|
2297
|
+
const reloadTargets = async () => {
|
|
2298
|
+
if (reloadRunning) {
|
|
2299
|
+
reloadPending = true;
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
reloadRunning = true;
|
|
2303
|
+
try {
|
|
2304
|
+
do {
|
|
2305
|
+
reloadPending = false;
|
|
2306
|
+
try {
|
|
2307
|
+
const nextTargets = await loadTargets(entries, options);
|
|
2308
|
+
gateway?.updateTargets(nextTargets);
|
|
2309
|
+
reporter.log(`检测到插件文件变化,已热更新 ${nextTargets.length} 个本地插件。`);
|
|
2310
|
+
} catch (error) {
|
|
2311
|
+
reporter.log(`插件热更新失败: ${formatConnectionKeyExchangeError(error)}`);
|
|
2312
|
+
}
|
|
2313
|
+
} while (reloadPending && !closed);
|
|
2314
|
+
} finally {
|
|
2315
|
+
reloadRunning = false;
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
const watcher = watchDebugEntries(entries, () => {
|
|
2319
|
+
reloadTargets();
|
|
2320
|
+
});
|
|
2321
|
+
reporter.start();
|
|
2322
|
+
try {
|
|
2323
|
+
const connectedGateway = await connectDebugGateway({
|
|
2324
|
+
targets: initialTargets,
|
|
2325
|
+
options: gatewayOptions,
|
|
2326
|
+
onLog: (message) => reporter.log(`[gateway] ${message}`)
|
|
2327
|
+
});
|
|
2328
|
+
gateway = connectedGateway;
|
|
2329
|
+
reporter.attachClose(({ force }) => {
|
|
2330
|
+
closed = true;
|
|
2331
|
+
watcher.close();
|
|
2332
|
+
connectedGateway.close();
|
|
2333
|
+
if (force) process.exit(130);
|
|
2334
|
+
});
|
|
2335
|
+
const source = connectedGateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
|
|
2336
|
+
reporter.ready(source);
|
|
2337
|
+
await connectedGateway.closed;
|
|
2338
|
+
} finally {
|
|
2339
|
+
closed = true;
|
|
2340
|
+
watcher.close();
|
|
2341
|
+
await reporter.end();
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
async function resolveDebugEntries(entriesInput) {
|
|
2345
|
+
const entries = (Array.isArray(entriesInput) ? entriesInput : [entriesInput]).filter((entry) => entry.trim().length > 0);
|
|
2346
|
+
if (entries.length > 0) return entries.map((entry) => path.resolve(entry));
|
|
2347
|
+
const discovered = await discoverDebugEntries();
|
|
2348
|
+
logger.info([`自动发现 ${discovered.length} 个可调试插件:`, ...discovered.map((entry) => ` - ${entry}`)].join("\n"));
|
|
2349
|
+
return discovered;
|
|
2350
|
+
}
|
|
2351
|
+
async function loadTargets(entries, options) {
|
|
2352
|
+
const targets = [];
|
|
2353
|
+
for (const [index, entryDir] of entries.entries()) {
|
|
2354
|
+
const session = await loadDebugSession({
|
|
2355
|
+
entryDir,
|
|
2356
|
+
uploadDir: resolveUploadDir({
|
|
2357
|
+
entryDir,
|
|
2358
|
+
index,
|
|
2359
|
+
total: entries.length,
|
|
2360
|
+
uploadDir: options.uploadDir
|
|
2361
|
+
})
|
|
2362
|
+
});
|
|
2363
|
+
targets.push({
|
|
2364
|
+
runtime: session.runtime,
|
|
2365
|
+
snapshot: session.snapshot
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
const duplicatePluginIds = findDuplicateValues$1(targets.map((target) => target.snapshot.pluginId));
|
|
2369
|
+
if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
|
|
2370
|
+
return targets;
|
|
2371
|
+
}
|
|
2372
|
+
function watchDebugEntries(entries, onChange) {
|
|
2373
|
+
const watchers = entries.map((entry) => fsWatch(entry, {
|
|
2374
|
+
recursive: true,
|
|
2375
|
+
onChange
|
|
2376
|
+
}));
|
|
2377
|
+
return { close() {
|
|
2378
|
+
watchers.forEach((watcher) => watcher.close());
|
|
2379
|
+
} };
|
|
2380
|
+
}
|
|
2381
|
+
function fsWatch(dir, { recursive, onChange }) {
|
|
2382
|
+
let timer;
|
|
2383
|
+
const watcher = fs$1.watch(dir, { recursive }, () => {
|
|
2384
|
+
clearTimeout(timer);
|
|
2385
|
+
timer = setTimeout(onChange, 150);
|
|
2386
|
+
});
|
|
2387
|
+
return { close() {
|
|
2388
|
+
clearTimeout(timer);
|
|
2389
|
+
watcher.close();
|
|
2390
|
+
} };
|
|
2391
|
+
}
|
|
2392
|
+
function createRemoteDebugReporter({ commandName, options, gatewayOptions, targets }) {
|
|
2393
|
+
const summary = {
|
|
2394
|
+
commandName,
|
|
2395
|
+
mode: "FastGPT connection key",
|
|
2396
|
+
gateway: gatewayOptions.gatewayUrl,
|
|
2397
|
+
reconnect: gatewayOptions.reconnect === false ? "off" : "on",
|
|
2398
|
+
targets: targets.map((target) => target.snapshot)
|
|
2399
|
+
};
|
|
2400
|
+
return options.interactive !== false && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY) ? new TuiRemoteDebugReporter(summary) : new PlainRemoteDebugReporter(summary);
|
|
2401
|
+
}
|
|
2402
|
+
async function ensureConnectLink({ options, commandName, targets }) {
|
|
2403
|
+
if (options.connect) return;
|
|
2404
|
+
const savedConnect = await readSavedConnectionKey();
|
|
2405
|
+
if (savedConnect) {
|
|
2406
|
+
options.connect = savedConnect;
|
|
2407
|
+
options.connectPersistOnSuccess = true;
|
|
2408
|
+
logger.info(`已读取本地 FastGPT connection key 配置: ${getCliConfigPath()}`);
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
if (options.interactive === false || !process.stdin.isTTY || !process.stdout.isTTY) throw new Error("dev 需要 --connect,或在交互式终端中输入 FastGPT connection key。");
|
|
2412
|
+
options.connect = await promptConnectLink({
|
|
2413
|
+
commandName,
|
|
2414
|
+
targets
|
|
2415
|
+
});
|
|
2416
|
+
options.connectPersistOnSuccess = true;
|
|
2417
|
+
}
|
|
2418
|
+
async function promptConnectLink({ commandName, targets, defaultValue, errorMessage }) {
|
|
2419
|
+
process.stdout.write([
|
|
2420
|
+
"\x1B[?25h\x1B[2J\x1B[HFastGPT Plugin Dev",
|
|
2421
|
+
"",
|
|
2422
|
+
`Command fastgpt-plugin ${commandName}`,
|
|
2423
|
+
"Mode waiting for FastGPT connection key",
|
|
2424
|
+
"Status pending",
|
|
2425
|
+
"",
|
|
2426
|
+
"Plugins",
|
|
2427
|
+
...targets.map((target) => ` ${target.snapshot.pluginId}@${target.snapshot.version}`),
|
|
2428
|
+
"",
|
|
2429
|
+
...errorMessage ? [`Last error ${errorMessage}`, ""] : [],
|
|
2430
|
+
"Paste the FastGPT connection key or connect link to start the WSS debug session.",
|
|
2431
|
+
""
|
|
2432
|
+
].join("\n"));
|
|
2433
|
+
return (await input({
|
|
2434
|
+
message: "FastGPT connection key",
|
|
2435
|
+
default: defaultValue,
|
|
2436
|
+
prefill: defaultValue ? "editable" : void 0,
|
|
2437
|
+
validate(value) {
|
|
2438
|
+
return value.trim().length > 0 || "请输入 FastGPT connection key";
|
|
2439
|
+
}
|
|
2440
|
+
})).trim();
|
|
2441
|
+
}
|
|
2442
|
+
async function readSavedConnectionKey() {
|
|
2443
|
+
return (await readCliConfig()).debug?.connectionKey?.trim() || void 0;
|
|
2444
|
+
}
|
|
2445
|
+
async function saveConnectionKey(connectionKey) {
|
|
2446
|
+
const trimmed = connectionKey.trim();
|
|
2447
|
+
if (!trimmed) return;
|
|
2448
|
+
const configPath = getCliConfigPath();
|
|
2449
|
+
const config = await readCliConfig();
|
|
2450
|
+
const nextConfig = {
|
|
2451
|
+
...config,
|
|
2452
|
+
debug: {
|
|
2453
|
+
...config.debug,
|
|
2454
|
+
connectionKey: trimmed
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
await fs.mkdir(path.dirname(configPath), {
|
|
2458
|
+
recursive: true,
|
|
2459
|
+
mode: 448
|
|
2460
|
+
});
|
|
2461
|
+
await fs.writeFile(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, {
|
|
2462
|
+
encoding: "utf-8",
|
|
2463
|
+
mode: 384
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2466
|
+
async function readCliConfig() {
|
|
2467
|
+
const configPath = getCliConfigPath();
|
|
2468
|
+
let raw;
|
|
2469
|
+
try {
|
|
2470
|
+
raw = await fs.readFile(configPath, "utf-8");
|
|
2471
|
+
} catch (error) {
|
|
2472
|
+
if (isNodeError(error) && error.code === "ENOENT") return {};
|
|
2473
|
+
throw error;
|
|
2474
|
+
}
|
|
2475
|
+
if (!raw.trim()) return {};
|
|
2476
|
+
return CliConfigSchema.parse(JSON.parse(raw));
|
|
2477
|
+
}
|
|
2478
|
+
function getCliConfigPath() {
|
|
2479
|
+
return path.join(getCliConfigDir(), CLI_CONFIG_FILE_NAME);
|
|
2480
|
+
}
|
|
2481
|
+
function getCliConfigDir() {
|
|
2482
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
|
|
2483
|
+
if (xdgConfigHome) return path.join(xdgConfigHome, CLI_CONFIG_DIR_NAME);
|
|
2484
|
+
return path.join(os.homedir(), ".config", CLI_CONFIG_DIR_NAME);
|
|
2485
|
+
}
|
|
2486
|
+
function isNodeError(error) {
|
|
2487
|
+
return error instanceof Error && "code" in error;
|
|
2488
|
+
}
|
|
2489
|
+
function formatTargetLine(snapshot) {
|
|
2490
|
+
const tools = snapshot.tools.map((tool) => tool.id).join(", ") || "-";
|
|
2491
|
+
return ` - ${snapshot.pluginId}@${snapshot.version} tools=[${tools}] entry=${snapshot.entryDir}`;
|
|
2492
|
+
}
|
|
2493
|
+
const CLI_CONFIG_FILE_NAME = "config.json";
|
|
2494
|
+
const CLI_CONFIG_DIR_NAME = "fastgpt-plugin";
|
|
2495
|
+
const CliConfigSchema = z.object({ debug: z.object({ connectionKey: z.string().min(1).optional() }).optional() }).passthrough();
|
|
2496
|
+
var PlainRemoteDebugReporter = class {
|
|
2497
|
+
constructor(summary) {
|
|
2498
|
+
this.summary = summary;
|
|
2499
|
+
}
|
|
2500
|
+
start() {
|
|
2501
|
+
logger.info([
|
|
2502
|
+
"FastGPT 插件开发会话",
|
|
2503
|
+
` command: fastgpt-plugin ${this.summary.commandName}`,
|
|
2504
|
+
` mode: ${this.summary.mode}`,
|
|
2505
|
+
" ui: plain",
|
|
2506
|
+
` gateway: ${this.summary.gateway}`,
|
|
2507
|
+
` reconnect: ${this.summary.reconnect}`,
|
|
2508
|
+
" plugins:",
|
|
2509
|
+
...this.summary.targets.map(formatTargetLine)
|
|
2510
|
+
].join("\n"));
|
|
2511
|
+
}
|
|
2512
|
+
log(message) {
|
|
2513
|
+
logger.info(message);
|
|
2514
|
+
}
|
|
2515
|
+
ready(source) {
|
|
2516
|
+
this.summary.targets.forEach((target) => {
|
|
2517
|
+
logger.success(`远程调试已就绪: ${source} ${target.pluginId}`);
|
|
2518
|
+
});
|
|
2519
|
+
logger.info(`已建立 1 个远程调试通道,挂载 ${this.summary.targets.length} 个插件,按 Ctrl+C 停止。`);
|
|
2520
|
+
}
|
|
2521
|
+
attachClose() {}
|
|
2522
|
+
end() {
|
|
2523
|
+
logger.info("远程调试会话已结束。");
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
var TuiRemoteDebugReporter = class {
|
|
2527
|
+
status = "connecting";
|
|
2528
|
+
source = "-";
|
|
2529
|
+
logs = [];
|
|
2530
|
+
instance;
|
|
2531
|
+
closeSession;
|
|
2532
|
+
interruptCount = 0;
|
|
2533
|
+
onSigint = () => {
|
|
2534
|
+
this.requestClose("ctrl-c");
|
|
2535
|
+
};
|
|
2536
|
+
requestClose = (reason) => {
|
|
2537
|
+
if (reason === "q") {
|
|
2538
|
+
this.log("收到退出指令,正在关闭远程调试会话。");
|
|
2539
|
+
this.closeSession?.({ force: false });
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
this.interruptCount += 1;
|
|
2543
|
+
const force = this.interruptCount >= 2;
|
|
2544
|
+
this.status = force ? "closed" : "closing";
|
|
2545
|
+
this.log(force ? "再次收到 Ctrl+C,正在强制退出。" : "收到 Ctrl+C,正在关闭远程调试会话。再次按 Ctrl+C 强制退出。");
|
|
2546
|
+
this.closeSession?.({ force });
|
|
2547
|
+
if (force && !this.closeSession) process.exit(130);
|
|
2548
|
+
};
|
|
2549
|
+
constructor(summary) {
|
|
2550
|
+
this.summary = summary;
|
|
2551
|
+
}
|
|
2552
|
+
start() {
|
|
2553
|
+
process.on("SIGINT", this.onSigint);
|
|
2554
|
+
this.instance = render(this.createApp(), {
|
|
2555
|
+
stdin: process.stdin,
|
|
2556
|
+
stdout: process.stdout,
|
|
2557
|
+
stderr: process.stderr,
|
|
2558
|
+
exitOnCtrlC: false,
|
|
2559
|
+
interactive: true,
|
|
2560
|
+
patchConsole: false
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
log(message) {
|
|
2564
|
+
this.logs.push({
|
|
2565
|
+
at: /* @__PURE__ */ new Date(),
|
|
2566
|
+
message
|
|
2567
|
+
});
|
|
2568
|
+
this.logs = this.logs.slice(-6);
|
|
2569
|
+
this.rerender();
|
|
2570
|
+
}
|
|
2571
|
+
ready(source) {
|
|
2572
|
+
this.status = "connected";
|
|
2573
|
+
this.source = source;
|
|
2574
|
+
this.log(`远程调试已就绪,挂载 ${this.summary.targets.length} 个插件。`);
|
|
2575
|
+
}
|
|
2576
|
+
attachClose(close) {
|
|
2577
|
+
this.closeSession = close;
|
|
2578
|
+
}
|
|
2579
|
+
async end() {
|
|
2580
|
+
process.off("SIGINT", this.onSigint);
|
|
2581
|
+
this.status = "closed";
|
|
2582
|
+
this.rerender();
|
|
2583
|
+
const instance = this.instance;
|
|
2584
|
+
this.instance = void 0;
|
|
2585
|
+
if (instance) {
|
|
2586
|
+
await Promise.race([instance.waitUntilRenderFlush(), setTimeout$1(50)]).catch(() => void 0);
|
|
2587
|
+
instance.unmount();
|
|
2588
|
+
await Promise.race([instance.waitUntilExit(), setTimeout$1(50)]).catch(() => void 0);
|
|
2589
|
+
}
|
|
2590
|
+
logger.info("远程调试会话已结束。");
|
|
2591
|
+
}
|
|
2592
|
+
rerender() {
|
|
2593
|
+
this.instance?.rerender(this.createApp());
|
|
2594
|
+
}
|
|
2595
|
+
createApp() {
|
|
2596
|
+
return React.createElement(RemoteDebugInkApp, {
|
|
2597
|
+
summary: this.summary,
|
|
2598
|
+
status: this.status,
|
|
2599
|
+
source: this.source,
|
|
2600
|
+
logs: this.logs,
|
|
2601
|
+
onQuit: this.requestClose
|
|
2602
|
+
});
|
|
2603
|
+
}
|
|
2604
|
+
};
|
|
2605
|
+
function RemoteDebugInkApp({ summary, status, source, logs, onQuit }) {
|
|
2606
|
+
useInput((inputValue, key) => {
|
|
2607
|
+
if (key.ctrl && inputValue === "c") {
|
|
2608
|
+
onQuit("ctrl-c");
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
if (inputValue === "q") onQuit("q");
|
|
2612
|
+
});
|
|
2613
|
+
const statusMeta = getTuiStatusMeta(status);
|
|
2614
|
+
const sourceLabel = source === "-" ? "waiting for bind" : source;
|
|
2615
|
+
const rows = [
|
|
2616
|
+
["command", `fastgpt-plugin ${summary.commandName}`],
|
|
2617
|
+
["gateway", summary.gateway],
|
|
2618
|
+
["source", sourceLabel],
|
|
2619
|
+
["reconnect", summary.reconnect === "on" ? "enabled" : "disabled"]
|
|
2620
|
+
];
|
|
2621
|
+
const plugins = summary.targets.map((target) => {
|
|
2622
|
+
const toolCount = target.tools.length;
|
|
2623
|
+
return {
|
|
2624
|
+
id: `${target.pluginId}:${target.version}`,
|
|
2625
|
+
name: `${target.pluginId}@${target.version}`,
|
|
2626
|
+
tools: toolCount === 1 ? "1 tool" : `${toolCount} tools`,
|
|
2627
|
+
entry: target.entryDir
|
|
2628
|
+
};
|
|
2629
|
+
});
|
|
2630
|
+
const activityItems = logs.length > 0 ? logs.map((entry, index) => React.createElement(Box, {
|
|
2631
|
+
key: `${entry.at.getTime()}:${index}`,
|
|
2632
|
+
flexDirection: "row"
|
|
2633
|
+
}, React.createElement(Text, { color: "gray" }, formatTuiTime(entry.at)), React.createElement(Text, { dimColor: true }, " │ "), React.createElement(Text, { wrap: "truncate-end" }, entry.message))) : [React.createElement(Text, {
|
|
2634
|
+
key: "empty-log",
|
|
2635
|
+
dimColor: true
|
|
2636
|
+
}, "waiting for gateway events")];
|
|
2637
|
+
return React.createElement(Box, {
|
|
2638
|
+
flexDirection: "column",
|
|
2639
|
+
paddingX: 1,
|
|
2640
|
+
paddingY: 1
|
|
2641
|
+
}, React.createElement(Box, {
|
|
2642
|
+
borderStyle: "round",
|
|
2643
|
+
borderColor: statusMeta.borderColor,
|
|
2644
|
+
paddingX: 2,
|
|
2645
|
+
paddingY: 1,
|
|
2646
|
+
flexDirection: "column"
|
|
2647
|
+
}, React.createElement(Box, { justifyContent: "space-between" }, React.createElement(Box, null, React.createElement(Text, {
|
|
2648
|
+
bold: true,
|
|
2649
|
+
color: "cyanBright"
|
|
2650
|
+
}, "FastGPT Plugin Dev"), React.createElement(Text, { dimColor: true }, " remote debug")), React.createElement(Box, null, React.createElement(Text, {
|
|
2651
|
+
color: statusMeta.color,
|
|
2652
|
+
bold: true
|
|
2653
|
+
}, statusMeta.label), React.createElement(Text, { dimColor: true }, ` ${plugins.length} plugins`))), React.createElement(Box, { marginTop: 1 }, React.createElement(Text, { color: statusMeta.color }, statusMeta.marker), React.createElement(Text, { dimColor: true }, " "), React.createElement(Text, { wrap: "truncate-end" }, statusMeta.description))), React.createElement(Box, {
|
|
2654
|
+
marginTop: 1,
|
|
2655
|
+
columnGap: 1
|
|
2656
|
+
}, React.createElement(Box, {
|
|
2657
|
+
borderStyle: "round",
|
|
2658
|
+
borderColor: "gray",
|
|
2659
|
+
paddingX: 1,
|
|
2660
|
+
paddingY: 1,
|
|
2661
|
+
flexDirection: "column",
|
|
2662
|
+
width: "50%"
|
|
2663
|
+
}, React.createElement(Text, { bold: true }, "Session"), React.createElement(Box, {
|
|
2664
|
+
flexDirection: "column",
|
|
2665
|
+
marginTop: 1
|
|
2666
|
+
}, ...rows.map(([label, value]) => React.createElement(SessionRow, {
|
|
2667
|
+
key: label,
|
|
2668
|
+
label,
|
|
2669
|
+
value
|
|
2670
|
+
})))), React.createElement(Box, {
|
|
2671
|
+
borderStyle: "round",
|
|
2672
|
+
borderColor: "gray",
|
|
2673
|
+
paddingX: 1,
|
|
2674
|
+
paddingY: 1,
|
|
2675
|
+
flexDirection: "column",
|
|
2676
|
+
width: "50%"
|
|
2677
|
+
}, React.createElement(Box, { justifyContent: "space-between" }, React.createElement(Text, { bold: true }, "Plugins"), React.createElement(Text, { dimColor: true }, `${plugins.length} mounted`)), React.createElement(Box, {
|
|
2678
|
+
flexDirection: "column",
|
|
2679
|
+
marginTop: 1
|
|
2680
|
+
}, ...plugins.map((plugin, index) => React.createElement(Box, {
|
|
2681
|
+
key: plugin.id,
|
|
2682
|
+
flexDirection: "column",
|
|
2683
|
+
marginTop: index === 0 ? 0 : 1
|
|
2684
|
+
}, React.createElement(Box, null, React.createElement(Text, { color: "greenBright" }, "● "), React.createElement(Text, {
|
|
2685
|
+
bold: true,
|
|
2686
|
+
wrap: "truncate-end"
|
|
2687
|
+
}, plugin.name), React.createElement(Text, { dimColor: true }, ` ${plugin.tools}`)), React.createElement(Text, {
|
|
2688
|
+
dimColor: true,
|
|
2689
|
+
wrap: "truncate-end"
|
|
2690
|
+
}, ` ${plugin.entry}`)))))), React.createElement(Box, {
|
|
2691
|
+
borderStyle: "round",
|
|
2692
|
+
borderColor: "gray",
|
|
2693
|
+
paddingX: 1,
|
|
2694
|
+
paddingY: 1,
|
|
2695
|
+
flexDirection: "column",
|
|
2696
|
+
marginTop: 1
|
|
2697
|
+
}, React.createElement(Box, { justifyContent: "space-between" }, React.createElement(Text, { bold: true }, "Activity"), React.createElement(Text, { dimColor: true }, "latest 6 events")), React.createElement(Box, {
|
|
2698
|
+
flexDirection: "column",
|
|
2699
|
+
marginTop: 1
|
|
2700
|
+
}, ...activityItems)), React.createElement(Box, {
|
|
2701
|
+
marginTop: 1,
|
|
2702
|
+
justifyContent: "space-between"
|
|
2703
|
+
}, React.createElement(Text, { dimColor: true }, "q stop"), React.createElement(Text, { dimColor: true }, "Ctrl+C stop · second Ctrl+C force exit")));
|
|
2704
|
+
}
|
|
2705
|
+
function getTuiStatusMeta(status) {
|
|
2706
|
+
if (status === "connected") return {
|
|
2707
|
+
label: "CONNECTED",
|
|
2708
|
+
marker: "●",
|
|
2709
|
+
description: "Gateway channel is live and ready to receive FastGPT invocations.",
|
|
2710
|
+
color: "greenBright",
|
|
2711
|
+
borderColor: "green"
|
|
2712
|
+
};
|
|
2713
|
+
if (status === "closed") return {
|
|
2714
|
+
label: "CLOSED",
|
|
2715
|
+
marker: "■",
|
|
2716
|
+
description: "Remote debug session has been closed.",
|
|
2717
|
+
color: "gray",
|
|
2718
|
+
borderColor: "gray"
|
|
2719
|
+
};
|
|
2720
|
+
if (status === "closing") return {
|
|
2721
|
+
label: "CLOSING",
|
|
2722
|
+
marker: "◆",
|
|
2723
|
+
description: "Closing gateway channel. Press Ctrl+C again to force exit.",
|
|
2724
|
+
color: "yellowBright",
|
|
2725
|
+
borderColor: "yellow"
|
|
2726
|
+
};
|
|
2727
|
+
return {
|
|
2728
|
+
label: "CONNECTING",
|
|
2729
|
+
marker: "◆",
|
|
2730
|
+
description: "Binding local plugins to the FastGPT connection gateway.",
|
|
2731
|
+
color: "yellowBright",
|
|
2732
|
+
borderColor: "yellow"
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
function SessionRow({ label, value }) {
|
|
2736
|
+
return React.createElement(Box, null, React.createElement(Text, { color: "gray" }, label.padEnd(10)), React.createElement(Text, { wrap: "truncate-end" }, value));
|
|
2737
|
+
}
|
|
2738
|
+
function formatTuiTime(value) {
|
|
2739
|
+
return [
|
|
2740
|
+
value.getHours().toString().padStart(2, "0"),
|
|
2741
|
+
value.getMinutes().toString().padStart(2, "0"),
|
|
2742
|
+
value.getSeconds().toString().padStart(2, "0")
|
|
2743
|
+
].join(":");
|
|
2744
|
+
}
|
|
2745
|
+
async function resolveGatewayOptions({ options, commandName, targets }) {
|
|
2746
|
+
if (!options.connect) throw new Error("dev 需要 --connect 来建立远程调试通道。");
|
|
2747
|
+
while (true) try {
|
|
2748
|
+
const gatewayOptions = await resolveConnectGatewayOptions(options);
|
|
2749
|
+
if (options.connectPersistOnSuccess === true) {
|
|
2750
|
+
await saveConnectionKey(options.connect);
|
|
2751
|
+
logger.info(`已保存 FastGPT connection key 到本地配置: ${getCliConfigPath()}`);
|
|
2752
|
+
options.connectPersistOnSuccess = false;
|
|
2753
|
+
}
|
|
2754
|
+
return gatewayOptions;
|
|
2755
|
+
} catch (error) {
|
|
2756
|
+
const errorMessage = formatConnectionKeyExchangeError(error);
|
|
2757
|
+
if (!canPromptForConnectInput(options)) throw error;
|
|
2758
|
+
logger.error(errorMessage);
|
|
2759
|
+
options.connect = await promptConnectLink({
|
|
2760
|
+
commandName,
|
|
2761
|
+
targets,
|
|
2762
|
+
defaultValue: options.connect,
|
|
2763
|
+
errorMessage
|
|
2764
|
+
});
|
|
2765
|
+
options.connectPersistOnSuccess = true;
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
function canPromptForConnectInput(options) {
|
|
2769
|
+
return options.interactive !== false && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
2770
|
+
}
|
|
2771
|
+
function formatConnectionKeyExchangeError(error) {
|
|
2772
|
+
return error instanceof Error ? error.message : String(error);
|
|
2773
|
+
}
|
|
2774
|
+
async function resolveConnectGatewayOptions(options) {
|
|
2775
|
+
const info = await exchangeConnectLink(options.connect);
|
|
2776
|
+
return {
|
|
2777
|
+
gatewayUrl: normalizeGatewayWsUrl(info.gatewayUrl),
|
|
2778
|
+
connectToken: info.connectToken,
|
|
2779
|
+
userId: info.tmbId,
|
|
2780
|
+
source: info.source,
|
|
2781
|
+
tokenTtlMs: Math.max(1, info.expiresAt - Date.now()),
|
|
2782
|
+
reconnect: options.noReconnect ? false : options.reconnect ?? true,
|
|
2783
|
+
reconnectIntervalMs: toPositiveInt(options.reconnectIntervalMs ?? process.env.CONNECTION_GATEWAY_RECONNECT_INTERVAL_MS ?? "2000", "reconnect-interval-ms")
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
function resolveUploadDir({ entryDir, index, total, uploadDir }) {
|
|
2787
|
+
if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
|
|
2788
|
+
if (total === 1) return path.resolve(uploadDir);
|
|
2789
|
+
return path.resolve(uploadDir, `${index + 1}-${sanitizePathSegment$1(path.basename(entryDir))}`);
|
|
2790
|
+
}
|
|
2791
|
+
const ConnectInfoSchema = z.object({
|
|
2792
|
+
gatewayUrl: z.string().min(1),
|
|
2793
|
+
transport: z.literal("websocket"),
|
|
2794
|
+
source: z.string().min(1),
|
|
2795
|
+
connectToken: z.string().min(1),
|
|
2796
|
+
expiresAt: z.number().int().positive()
|
|
2797
|
+
});
|
|
2798
|
+
async function exchangeConnectLink(connectInput) {
|
|
2799
|
+
const response = isHttpUrl(connectInput) ? await fetch(connectInput) : await fetch(resolveConnectionKeyExchangeUrl(), {
|
|
2800
|
+
method: "POST",
|
|
2801
|
+
headers: { "Content-Type": "application/json" },
|
|
2802
|
+
body: JSON.stringify({ connectionKey: connectInput })
|
|
2803
|
+
});
|
|
2804
|
+
const text = await response.text();
|
|
2805
|
+
if (!response.ok) throw new Error(`connection key 请求失败: ${response.status} ${text}`);
|
|
2806
|
+
const payload = text ? JSON.parse(text) : {};
|
|
2807
|
+
const info = ConnectInfoSchema.parse(payload.data ?? payload);
|
|
2808
|
+
return {
|
|
2809
|
+
...info,
|
|
2810
|
+
tmbId: parseTmbIdFromDebugSource(info.source)
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
function isHttpUrl(value) {
|
|
2814
|
+
try {
|
|
2815
|
+
const parsed = new URL(value);
|
|
2816
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
2817
|
+
} catch {
|
|
2818
|
+
return false;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
function resolveConnectionKeyExchangeUrl() {
|
|
2822
|
+
const explicit = process.env.FASTGPT_PLUGIN_DEBUG_CONNECT_URL;
|
|
2823
|
+
if (explicit) return explicit;
|
|
2824
|
+
const baseUrl = process.env.FASTGPT_PLUGIN_SERVER_URL ?? process.env.PLUGIN_SERVER_URL;
|
|
2825
|
+
if (!baseUrl) throw new Error("使用裸 connection key 时需要设置 FASTGPT_PLUGIN_DEBUG_CONNECT_URL 或 FASTGPT_PLUGIN_SERVER_URL。");
|
|
2826
|
+
return new URL("/api/plugin/debug-sessions/connection-key:exchange", baseUrl).toString();
|
|
2827
|
+
}
|
|
2828
|
+
function parseTmbIdFromDebugSource(source) {
|
|
2829
|
+
const parts = source.split(":");
|
|
2830
|
+
const index = parts.indexOf("tmbId");
|
|
2831
|
+
const tmbId = index >= 0 ? parts[index + 1] : void 0;
|
|
2832
|
+
if (!tmbId) throw new Error(`debug source 缺少 tmbId: ${source}`);
|
|
2833
|
+
return tmbId;
|
|
2834
|
+
}
|
|
2835
|
+
function normalizeGatewayWsUrl(gatewayUrl) {
|
|
2836
|
+
const parsed = new URL(gatewayUrl);
|
|
2837
|
+
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new Error("gatewayUrl 必须使用 ws:// 或 wss:// 协议。");
|
|
2838
|
+
if (!parsed.hostname) throw new Error("gatewayUrl 必须包含 host。");
|
|
2839
|
+
return parsed.toString();
|
|
2840
|
+
}
|
|
2841
|
+
function toPositiveInt(value, label) {
|
|
2842
|
+
const parsed = Number(value);
|
|
2843
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new Error(`${label} 必须是正整数。`);
|
|
2844
|
+
return parsed;
|
|
2845
|
+
}
|
|
2846
|
+
function findDuplicateValues$1(values) {
|
|
2847
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2848
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
2849
|
+
values.forEach((value) => {
|
|
2850
|
+
if (seen.has(value)) {
|
|
2851
|
+
duplicates.add(value);
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2854
|
+
seen.add(value);
|
|
2855
|
+
});
|
|
2856
|
+
return [...duplicates];
|
|
2857
|
+
}
|
|
2858
|
+
function sanitizePathSegment$1(value) {
|
|
2859
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_") || "plugin";
|
|
2860
|
+
}
|
|
2861
|
+
|
|
1717
2862
|
//#endregion
|
|
1718
2863
|
//#region src/commands/debug.ts
|
|
1719
2864
|
var DebugCommand = class extends BaseCommand {
|
|
1720
2865
|
register(parent) {
|
|
1721
|
-
parent.command("debug <
|
|
1722
|
-
await this.run(
|
|
2866
|
+
parent.command("debug <entries...>").description("直接加载本地插件目录并调试工具").option("-t, --tool <childId>", "工具集子工具 ID").option("--run", "立即执行一次工具调试", false).option("-i, --input <json>", "工具输入 JSON 字符串").option("--input-file <path>", "工具输入 JSON 文件路径").option("--secrets <json>", "secrets JSON 字符串").option("--secrets-file <path>", "secrets JSON 文件路径").option("--system-var <json>", "systemVar JSON 字符串").option("--system-var-file <path>", "systemVar JSON 文件路径").option("--upload-dir <path>", "虚拟 uploadFile 的输出目录").option("--connect <keyOrUrl>", "兼容入口:FastGPT debug connection key 或 connect link,建议改用 dev 命令").option("--reconnect", "兼容入口:断线后自动重连", true).option("--no-reconnect", "兼容入口:关闭自动重连").option("--reconnect-interval-ms <ms>", "兼容入口:重连间隔").action(async (entries, opts) => {
|
|
2867
|
+
await this.run(entries, opts);
|
|
1723
2868
|
});
|
|
1724
2869
|
}
|
|
1725
|
-
async run(
|
|
1726
|
-
const
|
|
1727
|
-
const
|
|
1728
|
-
|
|
2870
|
+
async run(entriesInput, options) {
|
|
2871
|
+
const entries = (Array.isArray(entriesInput) ? entriesInput : [entriesInput]).map((entry) => path.resolve(entry));
|
|
2872
|
+
const isMultiEntry = entries.length > 1;
|
|
2873
|
+
if (options.connect) {
|
|
2874
|
+
await runRemoteDebugSession({
|
|
2875
|
+
entriesInput,
|
|
2876
|
+
options,
|
|
2877
|
+
commandName: "dev",
|
|
2878
|
+
migrationHint: "debug --connect 是兼容入口,远程集成调试建议改用 fastgpt-plugin dev。"
|
|
2879
|
+
});
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
if (isMultiEntry && !options.connect) throw new Error("debug 是轻量本地调试入口,一次只支持一个插件;多插件远程调试请使用 dev。");
|
|
2883
|
+
if (isMultiEntry && options.run) throw new Error("--run 只支持单个插件入口。");
|
|
2884
|
+
const sessions = [];
|
|
2885
|
+
for (const [index, entryDir] of entries.entries()) sessions.push(await loadDebugSession({
|
|
1729
2886
|
entryDir,
|
|
1730
|
-
uploadDir
|
|
2887
|
+
uploadDir: this.resolveUploadDir({
|
|
2888
|
+
entryDir,
|
|
2889
|
+
index,
|
|
2890
|
+
total: entries.length,
|
|
2891
|
+
uploadDir: options.uploadDir
|
|
2892
|
+
})
|
|
2893
|
+
}));
|
|
2894
|
+
sessions.forEach((session) => {
|
|
2895
|
+
this.printSnapshot(session.snapshot, session.uploadDir);
|
|
1731
2896
|
});
|
|
1732
|
-
|
|
2897
|
+
const duplicatePluginIds = findDuplicateValues(sessions.map((session) => session.snapshot.pluginId));
|
|
2898
|
+
if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
|
|
2899
|
+
const [session] = sessions;
|
|
1733
2900
|
if (!options.run) return;
|
|
1734
2901
|
const input = await this.readJsonOption(options.input, options.inputFile, "input");
|
|
1735
2902
|
const secrets = await this.readJsonOption(options.secrets, options.secretsFile, "secrets");
|
|
@@ -1810,6 +2977,11 @@ var DebugCommand = class extends BaseCommand {
|
|
|
1810
2977
|
JSON.stringify(this.createInputExample(tool.inputSchema))
|
|
1811
2978
|
].map(quoteShellArg).join(" ");
|
|
1812
2979
|
}
|
|
2980
|
+
resolveUploadDir({ entryDir, index, total, uploadDir }) {
|
|
2981
|
+
if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
|
|
2982
|
+
if (total === 1) return path.resolve(uploadDir);
|
|
2983
|
+
return path.resolve(uploadDir, `${index + 1}-${sanitizePathSegment(path.basename(entryDir))}`);
|
|
2984
|
+
}
|
|
1813
2985
|
createInputExample(schema) {
|
|
1814
2986
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
1815
2987
|
if ("const" in schema) return schema.const;
|
|
@@ -1853,6 +3025,38 @@ function quoteShellArg(value) {
|
|
|
1853
3025
|
if (/^[a-zA-Z0-9_./:@-]+$/.test(value)) return value;
|
|
1854
3026
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
1855
3027
|
}
|
|
3028
|
+
function findDuplicateValues(values) {
|
|
3029
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3030
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
3031
|
+
values.forEach((value) => {
|
|
3032
|
+
if (seen.has(value)) {
|
|
3033
|
+
duplicates.add(value);
|
|
3034
|
+
return;
|
|
3035
|
+
}
|
|
3036
|
+
seen.add(value);
|
|
3037
|
+
});
|
|
3038
|
+
return [...duplicates];
|
|
3039
|
+
}
|
|
3040
|
+
function sanitizePathSegment(value) {
|
|
3041
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_") || "plugin";
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
//#endregion
|
|
3045
|
+
//#region src/commands/dev.ts
|
|
3046
|
+
var DevCommand = class extends BaseCommand {
|
|
3047
|
+
register(parent) {
|
|
3048
|
+
parent.command("dev [entries...]").description("启动 FastGPT 插件集成开发会话").option("--connect <keyOrUrl>", "可选:FastGPT debug connection key 或 connect link,适合脚本和 Agent").option("--reconnect", "断线后自动重连", true).option("--no-reconnect", "关闭自动重连").option("--reconnect-interval-ms <ms>", "重连间隔").option("--upload-dir <path>", "虚拟 uploadFile 的输出目录").option("--watch", "监听插件文件变化并自动重载远程调试会话", false).option("--no-interactive", "使用纯文本状态输出,适合脚本和 Agent").action(async (entries, opts) => {
|
|
3049
|
+
await this.run(entries, opts);
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
async run(entriesInput, options) {
|
|
3053
|
+
await runRemoteDebugSession({
|
|
3054
|
+
entriesInput,
|
|
3055
|
+
options,
|
|
3056
|
+
commandName: "dev"
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
3059
|
+
};
|
|
1856
3060
|
|
|
1857
3061
|
//#endregion
|
|
1858
3062
|
//#region src/commands/pack.ts
|
|
@@ -1942,6 +3146,7 @@ function createProgram() {
|
|
|
1942
3146
|
new CheckCommand().register(program);
|
|
1943
3147
|
new CreateCommand().register(program);
|
|
1944
3148
|
new DebugCommand().register(program);
|
|
3149
|
+
new DevCommand().register(program);
|
|
1945
3150
|
new PackCommand().register(program);
|
|
1946
3151
|
return program;
|
|
1947
3152
|
}
|