@fastgpt-plugin/cli 0.1.0-beta.9 → 0.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { registerHooks, stripTypeScriptTypes } from "node:module";
2
+ import { createRequire, registerHooks, stripTypeScriptTypes } from "node:module";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -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.1.0-beta.9";
529
+ var version = "0.2.0-alpha.2";
547
530
 
548
531
  //#endregion
549
532
  //#region src/constants.ts
@@ -703,6 +686,335 @@ var CreateCommand = class extends BaseCommand {
703
686
  }
704
687
  };
705
688
 
689
+ //#endregion
690
+ //#region ../../packages/domain/src/value-objects/plugin-debug-session.vo.ts
691
+ const PLUGIN_DEBUG_SOURCE_KIND = "debug";
692
+ const PLUGIN_DEBUG_SESSION_SOURCE_TMB_ID_KEY = "tmbId";
693
+ const PLUGIN_DEBUG_SOURCE_PREFIX = `${PLUGIN_DEBUG_SOURCE_KIND}:`;
694
+ const PLUGIN_DEBUG_SESSION_SOURCE_FORMAT = `${PLUGIN_DEBUG_SOURCE_KIND}:${PLUGIN_DEBUG_SESSION_SOURCE_TMB_ID_KEY}:{tmbId}`;
695
+ const PluginDebugSessionIdSchema = z.string().min(1);
696
+ const PluginDebugSessionTmbIdSchema = z.string().min(1);
697
+ const PluginDebugSessionSourceSchema = z.string().min(1).refine((source) => parsePluginDebugSessionSource(source) !== null, { message: "Invalid plugin debug session source" });
698
+ const PluginDebugSessionStatusSchema = z.enum([
699
+ "enabled",
700
+ "connected",
701
+ "disconnected",
702
+ "revoked"
703
+ ]);
704
+ const PluginDebugSessionSchema = z.object({
705
+ tmbId: PluginDebugSessionTmbIdSchema,
706
+ source: PluginDebugSessionSourceSchema,
707
+ status: PluginDebugSessionStatusSchema,
708
+ enabled: z.boolean(),
709
+ keyId: z.string().min(1),
710
+ connectionKeyHash: z.string().min(1),
711
+ connectionKey: z.string().min(1).optional(),
712
+ createdAt: z.number().int().positive(),
713
+ updatedAt: z.number().int().positive(),
714
+ refreshedAt: z.number().int().positive().optional(),
715
+ revokedAt: z.number().int().positive().optional()
716
+ });
717
+ function parsePluginDebugSessionSource(source) {
718
+ const parts = source.split(":");
719
+ if (parts.length !== 3 || parts[0] !== PLUGIN_DEBUG_SOURCE_KIND || parts[1] !== PLUGIN_DEBUG_SESSION_SOURCE_TMB_ID_KEY || !parts[2]) return null;
720
+ return { tmbId: parts[2] };
721
+ }
722
+
723
+ //#endregion
724
+ //#region src/debug/discover.ts
725
+ const IGNORED_DIRS = new Set([
726
+ ".build",
727
+ ".fastgpt-plugin-debug",
728
+ ".git",
729
+ ".turbo",
730
+ ".vite",
731
+ "coverage",
732
+ "dist",
733
+ "node_modules",
734
+ "__pack_output__"
735
+ ]);
736
+ async function discoverDebugEntries(cwd = process.cwd()) {
737
+ const root = path.resolve(cwd);
738
+ if (await hasIndexFile(root)) return [root];
739
+ const children = await fs.readdir(root, { withFileTypes: true });
740
+ const entries = [];
741
+ for (const child of children) {
742
+ if (!child.isDirectory() || IGNORED_DIRS.has(child.name)) continue;
743
+ const candidate = path.join(root, child.name);
744
+ if (await hasIndexFile(candidate)) entries.push(candidate);
745
+ }
746
+ entries.sort();
747
+ if (entries.length === 0) throw new Error(`当前目录未发现可调试插件,请传入插件目录或在当前目录下放置 index.ts。`);
748
+ return entries;
749
+ }
750
+ async function hasIndexFile(dir) {
751
+ try {
752
+ return (await fs.stat(path.join(dir, "index.ts"))).isFile();
753
+ } catch {
754
+ return false;
755
+ }
756
+ }
757
+
758
+ //#endregion
759
+ //#region ../../packages/domain/src/value-objects/connection-gateway.vo.ts
760
+ const ConnectionGatewayConsumerTypeSchema = z.string().min(1).max(64);
761
+ const ConnectionGatewayTransportSchema = z.literal("websocket");
762
+ const ConnectionGatewayCapabilitySchema = z.string().min(1).max(64);
763
+ const ConnectionGatewaySessionScopeSchema = z.object({
764
+ userId: z.string().min(1),
765
+ teamId: z.string().min(1).optional(),
766
+ source: z.string().min(1).optional(),
767
+ sources: z.array(z.string().min(1)).optional()
768
+ });
769
+ const ConnectionGatewayTokenClaimsSchema = z.object({
770
+ consumerType: ConnectionGatewayConsumerTypeSchema,
771
+ subject: z.string().min(1),
772
+ sessionScope: ConnectionGatewaySessionScopeSchema,
773
+ transport: ConnectionGatewayTransportSchema,
774
+ capabilities: z.array(ConnectionGatewayCapabilitySchema).default([]),
775
+ expiresAt: z.number().int().positive(),
776
+ issuedAt: z.number().int().positive().optional(),
777
+ nonce: z.string().min(1).optional()
778
+ });
779
+ const ConnectionGatewayEnvelopeSchema = z.object({
780
+ protocol: z.literal("connection-gateway.v1"),
781
+ sessionId: z.string().min(1),
782
+ generation: z.number().int().nonnegative(),
783
+ requestId: z.string().min(1).optional(),
784
+ type: z.enum([
785
+ "request",
786
+ "response",
787
+ "event",
788
+ "stream"
789
+ ]),
790
+ consumerType: ConnectionGatewayConsumerTypeSchema,
791
+ capability: ConnectionGatewayCapabilitySchema.optional(),
792
+ traceId: z.string().optional(),
793
+ payload: z.unknown().optional(),
794
+ createdAt: z.number().int().positive()
795
+ });
796
+ const ConnectionGatewayWsProtocolSchema = z.literal("connection-gateway.ws.v1");
797
+ const ConnectionGatewayWsBindMessageSchema = z.object({
798
+ protocol: ConnectionGatewayWsProtocolSchema,
799
+ type: z.literal("bind"),
800
+ requestId: z.string().min(1),
801
+ token: z.string().min(1),
802
+ metadata: z.record(z.string(), z.unknown()).optional()
803
+ });
804
+ const ConnectionGatewayWsEnvelopeMessageSchema = z.object({
805
+ protocol: ConnectionGatewayWsProtocolSchema,
806
+ type: z.literal("envelope"),
807
+ envelope: ConnectionGatewayEnvelopeSchema
808
+ });
809
+ const ConnectionGatewayWsHeartbeatMessageSchema = z.object({
810
+ protocol: ConnectionGatewayWsProtocolSchema,
811
+ type: z.literal("heartbeat"),
812
+ ts: z.number().int().positive()
813
+ });
814
+ const ConnectionGatewayWsErrorMessageSchema = z.object({
815
+ protocol: ConnectionGatewayWsProtocolSchema,
816
+ type: z.literal("error"),
817
+ requestId: z.string().min(1).optional(),
818
+ code: z.string().min(1),
819
+ message: z.string().min(1)
820
+ });
821
+ const ConnectionGatewayWsBoundMessageSchema = z.object({
822
+ protocol: ConnectionGatewayWsProtocolSchema,
823
+ type: z.literal("bound"),
824
+ requestId: z.string().min(1),
825
+ session: z.lazy(() => ConnectionGatewaySessionSchema)
826
+ });
827
+ const ConnectionGatewayWsClientMessageSchema = z.discriminatedUnion("type", [
828
+ ConnectionGatewayWsBindMessageSchema,
829
+ ConnectionGatewayWsEnvelopeMessageSchema,
830
+ ConnectionGatewayWsHeartbeatMessageSchema
831
+ ]);
832
+ const ConnectionGatewayWsServerMessageSchema = z.discriminatedUnion("type", [
833
+ ConnectionGatewayWsBoundMessageSchema,
834
+ ConnectionGatewayWsEnvelopeMessageSchema,
835
+ ConnectionGatewayWsHeartbeatMessageSchema,
836
+ ConnectionGatewayWsErrorMessageSchema
837
+ ]);
838
+ const ConnectionGatewaySessionStatusSchema = z.enum([
839
+ "connecting",
840
+ "connected",
841
+ "draining",
842
+ "closed"
843
+ ]);
844
+ const ConnectionGatewaySessionSchema = z.object({
845
+ id: z.string().min(1),
846
+ consumerType: ConnectionGatewayConsumerTypeSchema,
847
+ subject: z.string().min(1),
848
+ sessionScope: ConnectionGatewaySessionScopeSchema,
849
+ transport: ConnectionGatewayTransportSchema,
850
+ capabilities: z.array(ConnectionGatewayCapabilitySchema),
851
+ generation: z.number().int().nonnegative(),
852
+ ownerNodeId: z.string().min(1),
853
+ status: ConnectionGatewaySessionStatusSchema,
854
+ connectedAt: z.number().int().positive(),
855
+ lastSeenAt: z.number().int().positive(),
856
+ expiresAt: z.number().int().positive(),
857
+ metadata: z.record(z.string(), z.unknown()).optional()
858
+ });
859
+ const ConnectionGatewaySessionStatusViewSchema = z.object({
860
+ session: ConnectionGatewaySessionSchema.nullable(),
861
+ ownerAlive: z.boolean(),
862
+ mailboxLag: z.number().int().nonnegative(),
863
+ logs: z.array(z.object({
864
+ ts: z.number().int().positive(),
865
+ level: z.enum([
866
+ "debug",
867
+ "info",
868
+ "warn",
869
+ "error"
870
+ ]),
871
+ message: z.string(),
872
+ data: z.record(z.string(), z.unknown()).optional()
873
+ }))
874
+ });
875
+ const ConnectionGatewayMetricsSchema = z.object({
876
+ nodeId: z.string(),
877
+ activeConnections: z.number().int().nonnegative(),
878
+ activeSessions: z.number().int().nonnegative(),
879
+ inFlightRequests: z.number().int().nonnegative(),
880
+ streamBufferBytes: z.number().int().nonnegative(),
881
+ slowConsumers: z.number().int().nonnegative(),
882
+ ownerLeaseExpiries: z.number().int().nonnegative(),
883
+ mailbox: z.object({
884
+ lag: z.number().int().nonnegative(),
885
+ redisRoundTripMs: z.number().nonnegative()
886
+ }),
887
+ limits: z.object({
888
+ maxConnections: z.number().int().positive(),
889
+ maxSessionsPerSubject: z.number().int().positive(),
890
+ maxInFlightPerSession: z.number().int().positive(),
891
+ maxEnvelopeBytes: z.number().int().positive()
892
+ })
893
+ });
894
+ const ConnectionGatewayResourceLimitsSchema = z.object({
895
+ maxConnections: z.number().int().positive(),
896
+ maxSessionsPerSubject: z.number().int().positive(),
897
+ maxInFlightPerSession: z.number().int().positive(),
898
+ maxEnvelopeBytes: z.number().int().positive(),
899
+ slowConsumerBufferBytes: z.number().int().positive()
900
+ });
901
+
902
+ //#endregion
903
+ //#region ../../packages/domain/src/value-objects/system-var.vo.ts
904
+ const SystemVarSchema = z.object({
905
+ app: z.object({
906
+ id: z.string(),
907
+ name: z.string()
908
+ }),
909
+ chat: z.object({
910
+ chatId: z.string(),
911
+ uid: z.string().optional()
912
+ }),
913
+ invokeToken: z.string(),
914
+ time: z.string()
915
+ });
916
+
917
+ //#endregion
918
+ //#region ../../packages/domain/src/value-objects/plugin.vo.ts
919
+ const PluginUniqueIdSchema = z.object({
920
+ pluginId: z.string(),
921
+ version: z.string(),
922
+ etag: z.string()
923
+ });
924
+ const PluginSourceSchema = z.literal("system").or(z.string());
925
+ const PluginTagListItemSchema = z.object({
926
+ id: z.string(),
927
+ name: I18nStringStrictSchema
928
+ });
929
+ const PluginTagListSchema = z.array(PluginTagListItemSchema);
930
+ const PluginRuntimeModeSchema = z.enum(["localPool", "serverless"]);
931
+ const PluginRuntimeModeEnum = PluginRuntimeModeSchema.enum;
932
+ const UserPluginIdSchema = z.object({
933
+ pluginId: z.string(),
934
+ version: z.string().optional(),
935
+ source: PluginSourceSchema.optional()
936
+ });
937
+
938
+ //#endregion
939
+ //#region ../../packages/domain/src/value-objects/tool.vo.ts
940
+ const ToolHandlerReturnSchema = z.record(z.string(), z.unknown());
941
+ const StreamMessageTypeSchema = z.enum([
942
+ "response",
943
+ "error",
944
+ "stream"
945
+ ]);
946
+ const StreamMessageTypeEnum = StreamMessageTypeSchema.enum;
947
+ const StreamDataAnswerTypeSchema = z.enum(["answer", "fastAnswer"]);
948
+ const StreamDataAnswerTypeEnum = StreamDataAnswerTypeSchema.enum;
949
+ const ToolAnswerSchema = z.object({
950
+ type: StreamDataAnswerTypeSchema,
951
+ content: z.string()
952
+ });
953
+ const ToolStreamMessageSchema = z.discriminatedUnion("type", [
954
+ z.object({
955
+ type: z.literal(StreamMessageTypeEnum.response),
956
+ data: ToolHandlerReturnSchema
957
+ }),
958
+ z.object({
959
+ type: z.literal(StreamMessageTypeEnum.stream),
960
+ data: ToolAnswerSchema
961
+ }),
962
+ z.object({
963
+ type: z.literal(StreamMessageTypeEnum.error),
964
+ data: z.string()
965
+ })
966
+ ]);
967
+ const ToolRunInputSchema = z.object({
968
+ pluginId: z.string(),
969
+ version: z.preprocess((value) => {
970
+ if (value === "") return void 0;
971
+ return value;
972
+ }, z.string().optional()),
973
+ source: PluginSourceSchema.optional(),
974
+ childId: z.string().optional(),
975
+ input: z.record(z.string(), z.unknown()),
976
+ secrets: z.record(z.string(), z.unknown()).optional(),
977
+ systemVar: SystemVarSchema
978
+ });
979
+
980
+ //#endregion
981
+ //#region ../../packages/domain/src/value-objects/connection-gateway-debug.vo.ts
982
+ const CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY = "invoke";
983
+ const ConnectionGatewayPluginDebugRequestPayloadSchema = z.object({
984
+ kind: z.literal("plugin-debug.run"),
985
+ eventName: z.literal("run"),
986
+ payload: z.object({
987
+ pluginId: z.string().min(1).optional(),
988
+ input: z.record(z.string(), z.unknown()),
989
+ secrets: z.record(z.string(), z.unknown()).optional(),
990
+ systemVar: SystemVarSchema,
991
+ source: z.string().optional(),
992
+ childId: z.string().optional()
993
+ })
994
+ });
995
+ const ConnectionGatewayPluginDebugResponsePayloadSchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("plugin-debug.accepted") }), z.object({
996
+ kind: z.literal("plugin-debug.error"),
997
+ message: z.string(),
998
+ code: z.string().optional(),
999
+ data: z.unknown().optional()
1000
+ })]);
1001
+ const ConnectionGatewayPluginDebugStreamPayloadSchema = z.discriminatedUnion("event", [
1002
+ z.object({
1003
+ kind: z.literal("plugin-debug.stream"),
1004
+ event: z.literal("chunk"),
1005
+ data: ToolStreamMessageSchema
1006
+ }),
1007
+ z.object({
1008
+ kind: z.literal("plugin-debug.stream"),
1009
+ event: z.literal("end")
1010
+ }),
1011
+ z.object({
1012
+ kind: z.literal("plugin-debug.stream"),
1013
+ event: z.literal("error"),
1014
+ message: z.string()
1015
+ })
1016
+ ]);
1017
+
706
1018
  //#endregion
707
1019
  //#region src/debug/import-hooks.ts
708
1020
  const SOURCE_EXTENSIONS = [
@@ -719,7 +1031,11 @@ const RESOLVE_EXTENSIONS = [
719
1031
  ".cjs",
720
1032
  ".json"
721
1033
  ];
1034
+ const TRANSFORM_PROBE_SOURCE = "const __fastgptPluginTransformProbe__: number = 1;";
722
1035
  let hooksRegistered = false;
1036
+ let transformModeSupported;
1037
+ let cachedTypeScriptCompiler;
1038
+ const localRequire = createRequire(import.meta.url);
723
1039
  function ensureDebugImportHooks() {
724
1040
  if (hooksRegistered) return;
725
1041
  hooksRegistered = true;
@@ -743,15 +1059,90 @@ function ensureDebugImportHooks() {
743
1059
  if (SOURCE_EXTENSIONS.includes(ext)) return {
744
1060
  format: "module",
745
1061
  shortCircuit: true,
746
- source: stripTypeScriptTypes(fs$1.readFileSync(fileURLToPath(parsed), "utf-8"), {
747
- mode: "transform",
748
- sourceMap: false
1062
+ source: compileTypeScriptModuleSource(fs$1.readFileSync(fileURLToPath(parsed), "utf-8"), {
1063
+ filePath: fileURLToPath(parsed),
1064
+ workspaceRoot
749
1065
  })
750
1066
  };
751
1067
  return nextLoad(url, context);
752
1068
  }
753
1069
  });
754
1070
  }
1071
+ function compileTypeScriptModuleSource(sourceText, { filePath, workspaceRoot, stripTypeScriptTypesFn = stripTypeScriptTypes, typeScriptCompiler }) {
1072
+ if (supportsTransformMode(stripTypeScriptTypesFn)) try {
1073
+ return stripTypeScriptTypesFn(sourceText, {
1074
+ mode: "transform",
1075
+ sourceMap: false
1076
+ });
1077
+ } catch {}
1078
+ return transpileTypeScriptModuleSource(sourceText, {
1079
+ filePath,
1080
+ workspaceRoot,
1081
+ typeScriptCompiler
1082
+ });
1083
+ }
1084
+ function supportsTransformMode(stripTypeScriptTypesFn) {
1085
+ if (stripTypeScriptTypesFn === stripTypeScriptTypes) {
1086
+ if (transformModeSupported !== void 0) return transformModeSupported;
1087
+ transformModeSupported = canTransformTypeScript(stripTypeScriptTypesFn);
1088
+ return transformModeSupported;
1089
+ }
1090
+ return canTransformTypeScript(stripTypeScriptTypesFn);
1091
+ }
1092
+ function canTransformTypeScript(stripTypeScriptTypesFn) {
1093
+ try {
1094
+ stripTypeScriptTypesFn(TRANSFORM_PROBE_SOURCE, {
1095
+ mode: "transform",
1096
+ sourceMap: false
1097
+ });
1098
+ return true;
1099
+ } catch {
1100
+ return false;
1101
+ }
1102
+ }
1103
+ function transpileTypeScriptModuleSource(sourceText, { filePath, workspaceRoot, typeScriptCompiler }) {
1104
+ const compiler = typeScriptCompiler ?? loadTypeScriptCompiler(filePath, workspaceRoot);
1105
+ const transpiled = compiler.transpileModule(sourceText, {
1106
+ fileName: filePath,
1107
+ reportDiagnostics: true,
1108
+ compilerOptions: {
1109
+ module: compiler.ModuleKind.ESNext,
1110
+ target: compiler.ScriptTarget.ES2022,
1111
+ moduleResolution: compiler.ModuleResolutionKind.Bundler,
1112
+ jsx: compiler.JsxEmit.ReactJSX,
1113
+ sourceMap: false,
1114
+ inlineSourceMap: false,
1115
+ inlineSources: false,
1116
+ isolatedModules: true,
1117
+ esModuleInterop: true,
1118
+ allowImportingTsExtensions: true,
1119
+ verbatimModuleSyntax: true
1120
+ }
1121
+ });
1122
+ const errors = transpiled.diagnostics?.filter((diagnostic) => diagnostic.category === compiler.DiagnosticCategory.Error) ?? [];
1123
+ if (errors.length > 0) {
1124
+ const formatted = errors.map((diagnostic) => formatDiagnosticMessage(compiler, diagnostic)).join("\n");
1125
+ throw new Error(`Failed to transpile TypeScript debug module: ${filePath}\n${formatted}`);
1126
+ }
1127
+ return transpiled.outputText;
1128
+ }
1129
+ function loadTypeScriptCompiler(filePath, workspaceRoot) {
1130
+ if (cachedTypeScriptCompiler) return cachedTypeScriptCompiler;
1131
+ const resolvePaths = [
1132
+ path.dirname(filePath),
1133
+ process.cwd(),
1134
+ workspaceRoot,
1135
+ path.dirname(fileURLToPath(import.meta.url))
1136
+ ].filter((value) => Boolean(value));
1137
+ cachedTypeScriptCompiler = localRequire(localRequire.resolve("typescript", { paths: resolvePaths }));
1138
+ return cachedTypeScriptCompiler;
1139
+ }
1140
+ function formatDiagnosticMessage(compiler, diagnostic) {
1141
+ const message = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
1142
+ if (!diagnostic.file || diagnostic.start === void 0) return message;
1143
+ const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
1144
+ return `${line + 1}:${character + 1} ${message}`;
1145
+ }
755
1146
  function resolveSpecifier({ specifier, parentURL, workspaceRoot }) {
756
1147
  if (specifier.startsWith("node:")) return null;
757
1148
  if (specifier.startsWith("file:")) return toFileURL(resolveExistingPath(fileURLToPath(new URL(specifier))));
@@ -913,9 +1304,188 @@ const InvokeMethodEnumSchema = z.enum([
913
1304
  ]);
914
1305
  const InvokeMethodEnum = InvokeMethodEnumSchema.enum;
915
1306
 
1307
+ //#endregion
1308
+ //#region ../../packages/domain/src/value-objects/error.vo.ts
1309
+ const DEFAULT_INTERNAL_REASON = {
1310
+ en: "Internal Server Error",
1311
+ "zh-CN": "服务器内部错误"
1312
+ };
1313
+ var RegisteredError = class extends Error {
1314
+ code;
1315
+ reason;
1316
+ httpStatus;
1317
+ telemetryKind;
1318
+ visibility;
1319
+ severity;
1320
+ data;
1321
+ constructor(definition, options = {}) {
1322
+ super(options.message ?? definition.message, { cause: normalizeErrorCause(options.cause) });
1323
+ this.name = "RegisteredError";
1324
+ this.code = definition.code;
1325
+ this.reason = options.reason ?? definition.reason;
1326
+ this.httpStatus = definition.httpStatus ?? 500;
1327
+ this.telemetryKind = definition.telemetryKind;
1328
+ this.visibility = definition.visibility ?? "public";
1329
+ this.severity = definition.severity ?? "expected";
1330
+ this.data = options.data;
1331
+ }
1332
+ };
1333
+ function createReasonError(reason, options = {}) {
1334
+ const cause = normalizeErrorCause(options.cause);
1335
+ const error = cause === void 0 ? new Error(reason.en) : new Error(reason.en, { cause });
1336
+ return Object.assign(error, { reason });
1337
+ }
1338
+ function normalizeToError(error, fallbackMessage = "Unknown error") {
1339
+ if (error instanceof Error) return error;
1340
+ if (typeof error === "string") return new Error(error);
1341
+ if (isI18nString(error)) return createReasonError(error);
1342
+ if (isResultFailureLike(error)) {
1343
+ const reason = isI18nString(error.reason) ? error.reason : void 0;
1344
+ const cause = normalizeToError(error.error, reason?.en ?? fallbackMessage);
1345
+ if (reason && isSameI18nString(reason, getErrorReason(cause))) return cause;
1346
+ return reason ? createReasonError(reason, { cause }) : new Error(fallbackMessage, { cause });
1347
+ }
1348
+ return new Error(getUnknownErrorMessage(error, fallbackMessage), { cause: error });
1349
+ }
1350
+ function getErrorReason(error, fallback) {
1351
+ if (error instanceof RegisteredError) return error.visibility === "internal" ? DEFAULT_INTERNAL_REASON : error.reason;
1352
+ if (isI18nString(error)) return error;
1353
+ const record = isRecord(error) ? error : void 0;
1354
+ if (record && isI18nString(record.reason)) return record.reason;
1355
+ if (record && isI18nString(record.error)) return record.error;
1356
+ if (fallback) return fallback;
1357
+ if (error instanceof Error) return {
1358
+ en: error.message,
1359
+ "zh-CN": error.message
1360
+ };
1361
+ if (typeof error === "string") return {
1362
+ en: error,
1363
+ "zh-CN": error
1364
+ };
1365
+ return DEFAULT_INTERNAL_REASON;
1366
+ }
1367
+ function serializeError(error, options) {
1368
+ return serializeErrorInner(error, {
1369
+ includeStack: options?.includeStack ?? false,
1370
+ depth: 0,
1371
+ seen: /* @__PURE__ */ new WeakSet()
1372
+ });
1373
+ }
1374
+ function normalizeErrorCause(cause) {
1375
+ if (cause === void 0 || cause instanceof Error) return cause;
1376
+ if (typeof cause === "string") return new Error(cause);
1377
+ if (isI18nString(cause)) return createReasonError(cause);
1378
+ if (isResultFailureLike(cause)) return normalizeToError(cause);
1379
+ return cause;
1380
+ }
1381
+ function serializeErrorInner(error, state) {
1382
+ if (state.depth > 5) return {
1383
+ name: "Error",
1384
+ message: "Error cause depth exceeded"
1385
+ };
1386
+ if (error instanceof RegisteredError) return {
1387
+ name: error.name,
1388
+ code: error.code,
1389
+ message: error.message,
1390
+ reason: error.reason,
1391
+ httpStatus: error.httpStatus,
1392
+ telemetryKind: error.telemetryKind,
1393
+ visibility: error.visibility,
1394
+ data: error.data,
1395
+ ...state.includeStack && error.stack !== void 0 ? { stack: error.stack } : {},
1396
+ ...error.cause !== void 0 ? { cause: serializeErrorInner(error.cause, nextSerializeState(state, error)) } : {}
1397
+ };
1398
+ if (error instanceof Error) {
1399
+ const reason = getI18nStringField(error, "reason");
1400
+ return {
1401
+ name: error.name,
1402
+ code: getStringField(error, "code"),
1403
+ message: error.message,
1404
+ ...reason !== void 0 ? { reason } : {},
1405
+ data: getUnknownField(error, "data"),
1406
+ ...state.includeStack && error.stack !== void 0 ? { stack: error.stack } : {},
1407
+ ...error.cause !== void 0 ? { cause: serializeErrorInner(error.cause, nextSerializeState(state, error)) } : {}
1408
+ };
1409
+ }
1410
+ if (typeof error === "string") return {
1411
+ name: "Error",
1412
+ message: error
1413
+ };
1414
+ return {
1415
+ name: "Error",
1416
+ message: getUnknownErrorMessage(error, "Unknown error")
1417
+ };
1418
+ }
1419
+ function nextSerializeState(state, error) {
1420
+ if (state.seen.has(error)) return {
1421
+ ...state,
1422
+ depth: 6
1423
+ };
1424
+ state.seen.add(error);
1425
+ return {
1426
+ ...state,
1427
+ depth: state.depth + 1
1428
+ };
1429
+ }
1430
+ function getUnknownErrorMessage(error, fallback) {
1431
+ if (error === null || error === void 0) return fallback;
1432
+ if (isRecord(error)) {
1433
+ const message = error.message ?? error.msg;
1434
+ if (typeof message === "string" && message.length > 0) return message;
1435
+ }
1436
+ return String(error);
1437
+ }
1438
+ function getStringField(error, field) {
1439
+ const value = error[field];
1440
+ return typeof value === "string" ? value : void 0;
1441
+ }
1442
+ function getI18nStringField(error, field) {
1443
+ const value = error[field];
1444
+ return isI18nString(value) ? value : void 0;
1445
+ }
1446
+ function getUnknownField(error, field) {
1447
+ return error[field];
1448
+ }
1449
+ function isI18nString(value) {
1450
+ return isRecord(value) && typeof value.en === "string" && (value["zh-CN"] === void 0 || typeof value["zh-CN"] === "string") && (value["zh-Hant"] === void 0 || typeof value["zh-Hant"] === "string");
1451
+ }
1452
+ function isSameI18nString(left, right) {
1453
+ return left.en === right.en && left["zh-CN"] === right["zh-CN"] && left["zh-Hant"] === right["zh-Hant"];
1454
+ }
1455
+ function isRecord(value) {
1456
+ return Boolean(value && typeof value === "object");
1457
+ }
1458
+ function isResultFailureLike(value) {
1459
+ return isRecord(value) && "error" in value && ("reason" in value || isRecord(value.error));
1460
+ }
1461
+
916
1462
  //#endregion
917
1463
  //#region ../../packages/domain/src/value-objects/result.vo.ts
918
1464
  const successResult = (data) => [data, null];
1465
+ function failureResult(reasonOrFailure, error) {
1466
+ if (isResultFailure(reasonOrFailure)) return [null, toResultFailure(reasonOrFailure.reason, reasonOrFailure.error, reasonOrFailure)];
1467
+ if (reasonOrFailure instanceof Error) return [null, toResultFailure(getErrorReason(reasonOrFailure), reasonOrFailure)];
1468
+ return [null, toResultFailure(reasonOrFailure, error === void 0 ? createReasonError(reasonOrFailure) : createLegacyFailureError(reasonOrFailure, error))];
1469
+ }
1470
+ function isResultFailure(value) {
1471
+ return Boolean(value && typeof value === "object" && "reason" in value && "error" in value);
1472
+ }
1473
+ function createLegacyFailureError(reason, cause) {
1474
+ return createReasonError(reason, { cause: normalizeToError(cause, reason.en) });
1475
+ }
1476
+ function toResultFailure(reason, error, existing) {
1477
+ const serialized = serializeError(error);
1478
+ const code = existing?.code ?? serialized.code;
1479
+ const message = existing?.message ?? serialized.message;
1480
+ const data = existing && "data" in existing ? existing.data : serialized.data;
1481
+ return {
1482
+ reason,
1483
+ error,
1484
+ ...code !== void 0 ? { code } : {},
1485
+ ...message !== void 0 ? { message } : {},
1486
+ ...data !== void 0 ? { data } : {}
1487
+ };
1488
+ }
919
1489
 
920
1490
  //#endregion
921
1491
  //#region ../../packages/domain/src/value-objects/stream.vo.ts
@@ -977,63 +1547,6 @@ var StreamData = class StreamData {
977
1547
  }
978
1548
  };
979
1549
 
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
1550
  //#endregion
1038
1551
  //#region ../../packages/infrastructure/src/plugin/plugin-runtime/ports/channel/event/client.ts
1039
1552
  /**
@@ -1171,7 +1684,9 @@ const PluginChannelMessageIdSchema = z.union([z.string(), z.number()]);
1171
1684
  const PluginChannelErrorSchema = z.object({
1172
1685
  code: z.string(),
1173
1686
  message: z.string(),
1174
- data: z.unknown().optional()
1687
+ reason: I18nStringSchema.optional(),
1688
+ data: z.unknown().optional(),
1689
+ cause: z.lazy(() => PluginChannelErrorSchema).optional()
1175
1690
  });
1176
1691
  /**
1177
1692
  * request message:需要对端返回 success/error。
@@ -1467,7 +1982,7 @@ function createIncomingStream({ source, streamName, streamId, traceId, meta }) {
1467
1982
  };
1468
1983
  }
1469
1984
  function toStreamData(source) {
1470
- if (source instanceof StreamData) return source;
1985
+ if (isStreamDataLike$2(source)) return source;
1471
1986
  const output = StreamData.create();
1472
1987
  (async () => {
1473
1988
  try {
@@ -1479,6 +1994,9 @@ function toStreamData(source) {
1479
1994
  })();
1480
1995
  return output;
1481
1996
  }
1997
+ function isStreamDataLike$2(source) {
1998
+ return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
1999
+ }
1482
2000
 
1483
2001
  //#endregion
1484
2002
  //#region src/debug/session.ts
@@ -1511,7 +2029,7 @@ async function loadDebugSession({ entryDir, uploadDir }) {
1511
2029
  else process.env.RUNTIME_MODE = previousRuntimeMode;
1512
2030
  }
1513
2031
  }
1514
- async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemVar }) {
2032
+ async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemVar, traceId }) {
1515
2033
  const targetTool = pickTargetTool(snapshot, toolId);
1516
2034
  const mergedSystemVar = createDebugSystemVar(snapshot, targetTool.id, systemVar);
1517
2035
  const response = await runtime.invokePlugin("run", {
@@ -1519,7 +2037,7 @@ async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemV
1519
2037
  systemVar: mergedSystemVar,
1520
2038
  ...snapshot.isToolSet ? { childId: targetTool.id } : {},
1521
2039
  ...secrets ? { secrets } : {}
1522
- }, { traceId: randomUUID() });
2040
+ }, { traceId: traceId ?? randomUUID() });
1523
2041
  if (!response.output) throw new Error("调试运行未返回输出流。");
1524
2042
  const streamMessages = [];
1525
2043
  let finalResponse;
@@ -1579,7 +2097,7 @@ function createDebugSnapshot(tool, location) {
1579
2097
  author: getOptionalString(userManifest.author),
1580
2098
  tags: toStringArray(userManifest.tags),
1581
2099
  permissions: toStringArray(userManifest.permission),
1582
- secretSchema: ensurePlainObject(secretSchema),
2100
+ secretSchema: ensurePlainObject$1(secretSchema),
1583
2101
  isToolSet: children.length > 0,
1584
2102
  tools
1585
2103
  };
@@ -1592,8 +2110,8 @@ function createSingleToolSnapshot(tool, userManifest) {
1592
2110
  name: pickLocalizedText(userManifest.name),
1593
2111
  description: pickLocalizedText(userManifest.description),
1594
2112
  toolDescription: pickToolDescription(getOptionalString(userManifest.toolDescription), userManifest.description),
1595
- inputSchema: ensurePlainObject(z.toJSONSchema(handler.inputSchema)),
1596
- outputSchema: ensurePlainObject(z.toJSONSchema(handler.outputSchema))
2113
+ inputSchema: ensurePlainObject$1(z.toJSONSchema(handler.inputSchema)),
2114
+ outputSchema: ensurePlainObject$1(z.toJSONSchema(handler.outputSchema))
1597
2115
  };
1598
2116
  }
1599
2117
  function createChildToolSnapshot(tool, child) {
@@ -1604,8 +2122,8 @@ function createChildToolSnapshot(tool, child) {
1604
2122
  name: pickLocalizedText(child.name),
1605
2123
  description: pickLocalizedText(child.description),
1606
2124
  toolDescription: pickToolDescription(child.toolDescription, child.description),
1607
- inputSchema: ensurePlainObject(z.toJSONSchema(handler.inputSchema)),
1608
- outputSchema: ensurePlainObject(z.toJSONSchema(handler.outputSchema))
2125
+ inputSchema: ensurePlainObject$1(z.toJSONSchema(handler.inputSchema)),
2126
+ outputSchema: ensurePlainObject$1(z.toJSONSchema(handler.outputSchema))
1609
2127
  };
1610
2128
  }
1611
2129
  function pickTargetTool(snapshot, toolId) {
@@ -1617,10 +2135,10 @@ function pickTargetTool(snapshot, toolId) {
1617
2135
  }
1618
2136
  function createVirtualHostHandler(uploadDir) {
1619
2137
  return async ({ method, args, input }) => {
1620
- const invokeArgs = ensurePlainObject(args);
2138
+ const invokeArgs = ensurePlainObject$1(args);
1621
2139
  switch (method) {
1622
2140
  case InvokeMethodEnum.uploadFile: {
1623
- const buffer = await readSourceToBuffer(input);
2141
+ const buffer = await readSourceToBuffer$1(input);
1624
2142
  const fileName = sanitizeFileName(typeof invokeArgs.fileName === "string" && invokeArgs.fileName.trim().length > 0 ? invokeArgs.fileName : "debug-upload.bin");
1625
2143
  const contentType = typeof invokeArgs.contentType === "string" && invokeArgs.contentType.trim().length > 0 ? invokeArgs.contentType : "application/octet-stream";
1626
2144
  const saveDir = path.resolve(uploadDir);
@@ -1640,14 +2158,17 @@ function createVirtualHostHandler(uploadDir) {
1640
2158
  }
1641
2159
  };
1642
2160
  }
1643
- async function readSourceToBuffer(source) {
2161
+ async function readSourceToBuffer$1(source) {
1644
2162
  if (!source) return Buffer.alloc(0);
1645
2163
  const chunks = [];
1646
- const iterable = source instanceof StreamData ? source.values() : source;
1647
- for await (const chunk of iterable) chunks.push(toBuffer(chunk));
2164
+ const iterable = isStreamDataLike$1(source) ? source.values() : source;
2165
+ for await (const chunk of iterable) chunks.push(toBuffer$1(chunk));
1648
2166
  return Buffer.concat(chunks);
1649
2167
  }
1650
- function toBuffer(value) {
2168
+ function isStreamDataLike$1(source) {
2169
+ return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
2170
+ }
2171
+ function toBuffer$1(value) {
1651
2172
  if (Buffer.isBuffer(value)) return value;
1652
2173
  if (value instanceof Uint8Array) return Buffer.from(value);
1653
2174
  if (typeof value === "string") return Buffer.from(value);
@@ -1685,7 +2206,7 @@ function toStringArray(value) {
1685
2206
  const items = value.filter((item) => typeof item === "string");
1686
2207
  return items.length > 0 ? items : void 0;
1687
2208
  }
1688
- function ensurePlainObject(value) {
2209
+ function ensurePlainObject$1(value) {
1689
2210
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
1690
2211
  }
1691
2212
  function sanitizeFileName(fileName) {
@@ -1714,22 +2235,1227 @@ async function assertPathExists(targetPath, message) {
1714
2235
  }
1715
2236
  }
1716
2237
 
2238
+ //#endregion
2239
+ //#region src/debug/gateway.ts
2240
+ const GATEWAY_DUPLICATE_CONNECTION_CODE = "connection_gateway.session_already_bound";
2241
+ async function connectDebugGateway({ targets, options, onLog }) {
2242
+ if (targets.length === 0) throw new Error("Gateway debug targets cannot be empty");
2243
+ if (options.reconnect) return connectReconnectingDebugGateway({
2244
+ targets,
2245
+ options,
2246
+ onLog
2247
+ });
2248
+ return connectSingleDebugGateway({
2249
+ targets,
2250
+ options,
2251
+ onLog
2252
+ });
2253
+ }
2254
+ async function connectReconnectingDebugGateway({ targets, options, onLog }) {
2255
+ let closed = false;
2256
+ let current = null;
2257
+ let currentSession = null;
2258
+ let latestTargets = targets;
2259
+ let hasAttemptedConnection = false;
2260
+ let currentOptions = options;
2261
+ const resolveReconnectOptions = options.resolveReconnectOptions;
2262
+ const intervalMs = Math.max(100, options.reconnectIntervalMs ?? 2e3);
2263
+ const closedPromise = (async () => {
2264
+ while (!closed) {
2265
+ try {
2266
+ const attemptOptions = hasAttemptedConnection && resolveReconnectOptions ? await resolveReconnectOptions() : currentOptions;
2267
+ hasAttemptedConnection = true;
2268
+ currentOptions = {
2269
+ ...attemptOptions,
2270
+ resolveReconnectOptions
2271
+ };
2272
+ current = await connectSingleDebugGateway({
2273
+ targets: latestTargets,
2274
+ options: currentOptions,
2275
+ onLog
2276
+ });
2277
+ currentSession = current.session;
2278
+ await current.closed;
2279
+ } catch (error) {
2280
+ if (closed) return;
2281
+ if (isDuplicateConnectionError(error)) {
2282
+ closed = true;
2283
+ throw error;
2284
+ }
2285
+ onLog?.(`Connection Gateway 调试通道断开: ${formatErrorMessage(error)}`);
2286
+ } finally {
2287
+ current = null;
2288
+ }
2289
+ if (!closed) {
2290
+ onLog?.(`将在 ${intervalMs}ms 后重连 Connection Gateway。`);
2291
+ await delay(intervalMs);
2292
+ }
2293
+ }
2294
+ })();
2295
+ while (!currentSession && !closed) await delay(10);
2296
+ if (!currentSession) throw new Error("Connection Gateway debug session was closed before connecting");
2297
+ return {
2298
+ session: currentSession,
2299
+ updateTargets(targets) {
2300
+ latestTargets = targets;
2301
+ current?.updateTargets(targets);
2302
+ },
2303
+ close() {
2304
+ closed = true;
2305
+ current?.close();
2306
+ },
2307
+ closed: closedPromise
2308
+ };
2309
+ }
2310
+ async function connectSingleDebugGateway({ targets, options, onLog }) {
2311
+ const socket = await openWebSocket(options.gatewayUrl);
2312
+ let targetsByPluginId = createTargetsByPluginId(targets);
2313
+ let closed = false;
2314
+ let session = null;
2315
+ let boundResolve;
2316
+ let boundReject;
2317
+ const bound = new Promise((resolve, reject) => {
2318
+ boundResolve = resolve;
2319
+ boundReject = reject;
2320
+ });
2321
+ const closedPromise = new Promise((resolve) => {
2322
+ socket.addEventListener("close", () => {
2323
+ closed = true;
2324
+ resolve();
2325
+ });
2326
+ socket.addEventListener("error", () => {
2327
+ onLog?.("Connection Gateway WebSocket 错误");
2328
+ });
2329
+ });
2330
+ socket.addEventListener("message", (event) => {
2331
+ const message = parseWsServerMessage(event.data);
2332
+ if (message.type === "bound") {
2333
+ session = message.session;
2334
+ boundResolve(message.session);
2335
+ return;
2336
+ }
2337
+ if (message.type === "heartbeat") return;
2338
+ if (message.type === "error") {
2339
+ const error = createGatewayError(message.code, message.message);
2340
+ if (!session) boundReject(error);
2341
+ onLog?.(`Connection Gateway 错误: ${message.code} ${message.message}`);
2342
+ return;
2343
+ }
2344
+ if (message.type === "envelope") {
2345
+ const envelope = message.envelope;
2346
+ if (!session) {
2347
+ boundReject(/* @__PURE__ */ new Error("Gateway envelope received before session bound"));
2348
+ return;
2349
+ }
2350
+ handleGatewayEnvelope({
2351
+ socket,
2352
+ session,
2353
+ targetsByPluginId,
2354
+ envelope,
2355
+ onLog
2356
+ }).catch(async (error) => {
2357
+ if (session) await sendEnvelope(socket, makeErrorEnvelope(session, envelope, error));
2358
+ });
2359
+ }
2360
+ });
2361
+ await sendMessage(socket, {
2362
+ protocol: "connection-gateway.ws.v1",
2363
+ type: "bind",
2364
+ requestId: randomUUID(),
2365
+ token: options.connectToken,
2366
+ metadata: makePluginDebugMetadata(targets, options.source)
2367
+ }).catch(async (error) => {
2368
+ socket.close();
2369
+ throw error;
2370
+ });
2371
+ const boundSession = await bound;
2372
+ onLog?.(`已连接 Connection Gateway session: ${boundSession.id}`);
2373
+ return {
2374
+ session: boundSession,
2375
+ updateTargets(targets) {
2376
+ targetsByPluginId = createTargetsByPluginId(targets);
2377
+ onLog?.(`已热更新本地调试插件: ${targets.map((target) => target.snapshot.pluginId).join(", ")}`);
2378
+ },
2379
+ close() {
2380
+ if (!closed) socket.close();
2381
+ },
2382
+ closed: closedPromise
2383
+ };
2384
+ }
2385
+ function createTargetsByPluginId(targets) {
2386
+ return new Map(targets.map((target) => [target.snapshot.pluginId, target]));
2387
+ }
2388
+ async function handleGatewayEnvelope({ socket, session, targetsByPluginId, envelope, onLog }) {
2389
+ if (envelope.type !== "request") return;
2390
+ const request = ConnectionGatewayPluginDebugRequestPayloadSchema.parse(envelope.payload);
2391
+ const source = request.payload.source ?? session.sessionScope.source;
2392
+ if (!source) throw new Error("Gateway debug request missing source");
2393
+ const pluginId = request.payload.pluginId;
2394
+ if (!pluginId) throw new Error("Gateway debug request missing pluginId");
2395
+ const target = targetsByPluginId.get(pluginId);
2396
+ if (!target) throw new Error(`Gateway debug target not found: ${source} ${pluginId}`);
2397
+ const traceId = envelope.traceId ?? requiredRequestId(envelope);
2398
+ onLog?.(`收到远程调试请求: ${source} ${pluginId} ${envelope.requestId ?? "-"}`);
2399
+ await sendEnvelope(socket, {
2400
+ protocol: "connection-gateway.v1",
2401
+ sessionId: session.id,
2402
+ generation: session.generation,
2403
+ requestId: requiredRequestId(envelope),
2404
+ type: "response",
2405
+ consumerType: session.consumerType,
2406
+ capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
2407
+ traceId: envelope.traceId,
2408
+ createdAt: Date.now(),
2409
+ payload: { kind: "plugin-debug.accepted" }
2410
+ });
2411
+ target.invokeBridge?.bind(traceId, { invokeToken: request.payload.systemVar.invokeToken });
2412
+ try {
2413
+ const result = await runDebugTool({
2414
+ runtime: target.runtime,
2415
+ snapshot: target.snapshot,
2416
+ toolId: request.payload.childId,
2417
+ input: request.payload.input,
2418
+ secrets: request.payload.secrets,
2419
+ systemVar: request.payload.systemVar,
2420
+ traceId
2421
+ });
2422
+ for (const message of result.streamMessages) await sendStreamChunk(socket, session, envelope, message);
2423
+ } finally {
2424
+ target.invokeBridge?.release(traceId);
2425
+ }
2426
+ await sendEnvelope(socket, {
2427
+ protocol: "connection-gateway.v1",
2428
+ sessionId: session.id,
2429
+ generation: session.generation,
2430
+ requestId: requiredRequestId(envelope),
2431
+ type: "stream",
2432
+ consumerType: session.consumerType,
2433
+ capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
2434
+ traceId: envelope.traceId,
2435
+ createdAt: Date.now(),
2436
+ payload: {
2437
+ kind: "plugin-debug.stream",
2438
+ event: "end"
2439
+ }
2440
+ });
2441
+ }
2442
+ async function sendStreamChunk(socket, session, request, message) {
2443
+ await sendEnvelope(socket, {
2444
+ protocol: "connection-gateway.v1",
2445
+ sessionId: session.id,
2446
+ generation: session.generation,
2447
+ requestId: requiredRequestId(request),
2448
+ type: "stream",
2449
+ consumerType: session.consumerType,
2450
+ capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
2451
+ traceId: request.traceId,
2452
+ createdAt: Date.now(),
2453
+ payload: {
2454
+ kind: "plugin-debug.stream",
2455
+ event: "chunk",
2456
+ data: ToolStreamMessageSchema.parse(message)
2457
+ }
2458
+ });
2459
+ }
2460
+ function makeErrorEnvelope(session, request, error) {
2461
+ return {
2462
+ protocol: "connection-gateway.v1",
2463
+ sessionId: session.id,
2464
+ generation: session.generation,
2465
+ requestId: request.requestId,
2466
+ type: "stream",
2467
+ consumerType: session.consumerType,
2468
+ capability: CONNECTION_GATEWAY_PLUGIN_DEBUG_INVOKE_CAPABILITY,
2469
+ traceId: request.traceId,
2470
+ createdAt: Date.now(),
2471
+ payload: {
2472
+ kind: "plugin-debug.stream",
2473
+ event: "error",
2474
+ message: error instanceof Error ? error.message : String(error)
2475
+ }
2476
+ };
2477
+ }
2478
+ function makePluginDebugMetadata(targets, source) {
2479
+ return { pluginDebug: { targets: targets.map((target) => ({
2480
+ source,
2481
+ pluginId: target.snapshot.pluginId,
2482
+ version: target.snapshot.version,
2483
+ name: target.snapshot.name,
2484
+ description: target.snapshot.description,
2485
+ toolDescription: target.snapshot.toolDescription,
2486
+ author: target.snapshot.author,
2487
+ tags: target.snapshot.tags,
2488
+ permissions: target.snapshot.permissions,
2489
+ secretSchema: target.snapshot.secretSchema,
2490
+ isToolSet: target.snapshot.isToolSet,
2491
+ tools: target.snapshot.tools
2492
+ })) } };
2493
+ }
2494
+ async function openWebSocket(url) {
2495
+ return new Promise((resolve, reject) => {
2496
+ const socket = new WebSocket(url);
2497
+ socket.addEventListener("open", () => {
2498
+ resolve(socket);
2499
+ }, { once: true });
2500
+ socket.addEventListener("error", () => {
2501
+ reject(/* @__PURE__ */ new Error(`Connection Gateway WebSocket connect failed: ${url}`));
2502
+ }, { once: true });
2503
+ });
2504
+ }
2505
+ async function sendEnvelope(socket, envelope) {
2506
+ await sendMessage(socket, {
2507
+ protocol: "connection-gateway.ws.v1",
2508
+ type: "envelope",
2509
+ envelope
2510
+ });
2511
+ }
2512
+ async function sendMessage(socket, message) {
2513
+ if (socket.readyState !== WebSocket.OPEN) throw new Error("Connection Gateway WebSocket is not open");
2514
+ socket.send(JSON.stringify(message));
2515
+ }
2516
+ function requiredRequestId(envelope) {
2517
+ if (!envelope.requestId) throw new Error("Gateway request envelope missing requestId");
2518
+ return envelope.requestId;
2519
+ }
2520
+ function delay(ms) {
2521
+ return new Promise((resolve) => {
2522
+ setTimeout(resolve, ms);
2523
+ });
2524
+ }
2525
+ function formatErrorMessage(error) {
2526
+ return error instanceof Error ? error.message : String(error);
2527
+ }
2528
+ function createGatewayError(code, message) {
2529
+ const error = new Error(message);
2530
+ Object.assign(error, { code });
2531
+ return error;
2532
+ }
2533
+ function isDuplicateConnectionError(error) {
2534
+ return error instanceof Error && ("code" in error ? error.code === GATEWAY_DUPLICATE_CONNECTION_CODE : false);
2535
+ }
2536
+ function parseWsServerMessage(data) {
2537
+ 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);
2538
+ return ConnectionGatewayWsServerMessageSchema.parse(JSON.parse(text));
2539
+ }
2540
+
2541
+ //#endregion
2542
+ //#region ../../packages/infrastructure/src/plugin/invoke/invoke.impl.ts
2543
+ var InvokeManager = class {
2544
+ constructor(deps) {
2545
+ this.deps = deps;
2546
+ }
2547
+ async getWecomCorpToken() {
2548
+ const [result, responseErr] = await this.requestFastGPT({
2549
+ path: "/api/proApi/support/wecom/getCorpToken",
2550
+ init: { method: "GET" },
2551
+ failureReason: {
2552
+ en: "Get wecom corp token failed",
2553
+ "zh-CN": "获取企业微信企业令牌失败"
2554
+ },
2555
+ invalidJsonReason: {
2556
+ en: "Invalid wecom corp token output",
2557
+ "zh-CN": "获取企业微信企业令牌返回数据格式错误"
2558
+ }
2559
+ });
2560
+ if (responseErr) return failureResult(responseErr);
2561
+ return successResult(result);
2562
+ }
2563
+ async userInfo() {
2564
+ const [result, responseErr] = await this.requestFastGPT({
2565
+ path: "/api/invoke/userInfo",
2566
+ init: { method: "GET" },
2567
+ failureReason: {
2568
+ en: "Invoke user info failed",
2569
+ "zh-CN": "反向调用用户信息失败"
2570
+ },
2571
+ invalidJsonReason: {
2572
+ en: "Invalid invoke user info output",
2573
+ "zh-CN": "反向调用用户信息返回数据格式错误"
2574
+ }
2575
+ });
2576
+ if (responseErr) return failureResult(responseErr);
2577
+ try {
2578
+ return successResult(InvokeUserInfoOutputSchema.parse(result));
2579
+ } catch (error) {
2580
+ return failureResult({
2581
+ en: "Invalid invoke user info output",
2582
+ "zh-CN": "反向调用用户信息返回数据格式错误"
2583
+ }, error);
2584
+ }
2585
+ }
2586
+ async uploadFile(input) {
2587
+ let request;
2588
+ try {
2589
+ request = await this.buildUploadFileRequest(input);
2590
+ } catch (error) {
2591
+ return failureResult({
2592
+ en: "Invoke upload file failed",
2593
+ "zh-CN": "反向调用上传文件失败"
2594
+ }, error);
2595
+ }
2596
+ const [result, responseErr] = await this.requestFastGPT({
2597
+ path: "/api/invoke/fileUpload",
2598
+ init: {
2599
+ method: "POST",
2600
+ body: request.formData
2601
+ },
2602
+ failureReason: {
2603
+ en: "Invoke upload file failed",
2604
+ "zh-CN": "反向调用上传文件失败"
2605
+ },
2606
+ invalidJsonReason: {
2607
+ en: "Invalid invoke upload file output",
2608
+ "zh-CN": "反向调用上传文件返回数据格式错误"
2609
+ }
2610
+ });
2611
+ if (responseErr) return failureResult(responseErr);
2612
+ try {
2613
+ return successResult(InvokeUploadFileOutputSchema.parse({ accessURL: result.url }));
2614
+ } catch (error) {
2615
+ return failureResult({
2616
+ en: "Invalid invoke upload file output",
2617
+ "zh-CN": "反向调用上传文件返回数据格式错误"
2618
+ }, error);
2619
+ }
2620
+ }
2621
+ buildUrl(path) {
2622
+ return new URL(path, this.deps.fastgptBaseUrl).toString();
2623
+ }
2624
+ async buildUploadFileRequest(input) {
2625
+ const formData = new FormData();
2626
+ const fileBuffer = await this.toBuffer(input.file);
2627
+ const fileName = input.fileName ?? randomUUID();
2628
+ const contentType = input.contentType ?? "application/octet-stream";
2629
+ const blobPart = new Uint8Array(fileBuffer.byteLength);
2630
+ blobPart.set(fileBuffer);
2631
+ formData.append("file", new Blob([blobPart], { type: contentType }), fileName);
2632
+ if (input.fileName) formData.append("fileName", input.fileName);
2633
+ if (input.contentType) formData.append("contentType", input.contentType);
2634
+ return {
2635
+ formData,
2636
+ meta: {
2637
+ fileName,
2638
+ contentType
2639
+ }
2640
+ };
2641
+ }
2642
+ async toBuffer(file) {
2643
+ if (Buffer.isBuffer(file)) return file;
2644
+ if (file instanceof Readable$1) {
2645
+ const chunks = [];
2646
+ for await (const chunk of file) chunks.push(this.toBufferChunk(chunk));
2647
+ return Buffer.concat(chunks);
2648
+ }
2649
+ return Buffer.from(file);
2650
+ }
2651
+ toBufferChunk(chunk) {
2652
+ if (Buffer.isBuffer(chunk)) return chunk;
2653
+ if (chunk instanceof Uint8Array) return Buffer.from(chunk);
2654
+ if (chunk instanceof ArrayBuffer) return Buffer.from(chunk);
2655
+ if (ArrayBuffer.isView(chunk)) return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
2656
+ if (typeof chunk === "string") return Buffer.from(chunk);
2657
+ if (typeof chunk === "number") return Buffer.from([chunk]);
2658
+ if (this.isSerializedBufferChunk(chunk)) return Buffer.from(chunk.data);
2659
+ throw new TypeError(`Unsupported upload file stream chunk type: ${Object.prototype.toString.call(chunk)}`);
2660
+ }
2661
+ isSerializedBufferChunk(value) {
2662
+ return this.isRecord(value) && value.type === "Buffer" && Array.isArray(value.data) && value.data.every((item) => Number.isInteger(item) && Number.isSafeInteger(item) && item >= 0 && item <= 255);
2663
+ }
2664
+ async requestFastGPT({ path, init, failureReason, invalidJsonReason }) {
2665
+ const headers = new Headers(init?.headers);
2666
+ headers.set("Authorization", `Bearer ${this.deps.token}`);
2667
+ let response;
2668
+ try {
2669
+ response = await fetch(this.buildUrl(path), {
2670
+ ...init,
2671
+ headers
2672
+ });
2673
+ } catch (error) {
2674
+ return failureResult(failureReason, error);
2675
+ }
2676
+ if (!response.ok) return failureResult(failureReason, await this.readErrorResponse(response));
2677
+ let payload;
2678
+ try {
2679
+ payload = await response.json();
2680
+ } catch (error) {
2681
+ return failureResult(invalidJsonReason, error);
2682
+ }
2683
+ return this.parseResponse(payload, failureReason);
2684
+ }
2685
+ parseResponse(payload, fallbackReason) {
2686
+ if (!this.isRecord(payload)) return successResult(payload);
2687
+ if (payload.success === false) return failureResult({
2688
+ en: this.getResponseMessage(payload) ?? fallbackReason.en,
2689
+ "zh-CN": this.getResponseMessage(payload) ?? fallbackReason["zh-CN"]
2690
+ }, payload);
2691
+ if (typeof payload.code === "number" && payload.code >= 400) return failureResult({
2692
+ en: this.getResponseMessage(payload) ?? fallbackReason.en,
2693
+ "zh-CN": this.getResponseMessage(payload) ?? fallbackReason["zh-CN"]
2694
+ }, payload);
2695
+ return successResult("data" in payload ? payload.data : payload);
2696
+ }
2697
+ async readErrorResponse(response) {
2698
+ return {
2699
+ status: response.status,
2700
+ statusText: response.statusText,
2701
+ body: (await response.text().catch(() => "")).slice(0, 1e3)
2702
+ };
2703
+ }
2704
+ getResponseMessage(payload) {
2705
+ return typeof payload.message === "string" ? payload.message : void 0;
2706
+ }
2707
+ isRecord(value) {
2708
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
2709
+ }
2710
+ };
2711
+
2712
+ //#endregion
2713
+ //#region src/debug/remote-invoke.ts
2714
+ var RemoteDebugInvokeBridge = class {
2715
+ sessions = /* @__PURE__ */ new Map();
2716
+ constructor(fastgptBaseUrl) {
2717
+ this.fastgptBaseUrl = fastgptBaseUrl;
2718
+ }
2719
+ attach(runtime) {
2720
+ runtime.setHostRequestHandler((request) => this.handleHostRequest(request));
2721
+ }
2722
+ bind(traceId, input) {
2723
+ this.sessions.set(traceId, { invokeToken: input.invokeToken });
2724
+ }
2725
+ release(traceId) {
2726
+ this.sessions.delete(traceId);
2727
+ }
2728
+ async handleHostRequest({ method, args, input, traceId }) {
2729
+ if (!traceId) throw new Error("远程调试反向调用缺少 traceId。");
2730
+ const session = this.sessions.get(traceId);
2731
+ if (!session) throw new Error("远程调试反向调用会话不存在或已结束。");
2732
+ const invoke = new InvokeManager({
2733
+ token: session.invokeToken,
2734
+ fastgptBaseUrl: this.fastgptBaseUrl
2735
+ });
2736
+ switch (method) {
2737
+ case InvokeMethodEnum.uploadFile: return invoke.uploadFile({
2738
+ ...ensurePlainObject(args),
2739
+ file: input ? await readSourceToBuffer(input) : Buffer.alloc(0)
2740
+ });
2741
+ case InvokeMethodEnum.userInfo: return invoke.userInfo();
2742
+ case InvokeMethodEnum.wecomCorpToken: return invoke.getWecomCorpToken();
2743
+ default: throw new Error(`远程调试宿主暂不支持反向调用: ${String(method)}`);
2744
+ }
2745
+ }
2746
+ };
2747
+ function ensurePlainObject(value) {
2748
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2749
+ }
2750
+ async function readSourceToBuffer(source) {
2751
+ const chunks = [];
2752
+ const iterable = isStreamDataLike(source) ? source.values() : source;
2753
+ for await (const chunk of iterable) chunks.push(toBuffer(chunk));
2754
+ return Buffer.concat(chunks);
2755
+ }
2756
+ function isStreamDataLike(source) {
2757
+ return source instanceof StreamData || typeof source === "object" && source !== null && typeof source.values === "function" && typeof source.consume === "function";
2758
+ }
2759
+ function toBuffer(value) {
2760
+ if (Buffer.isBuffer(value)) return value;
2761
+ if (value instanceof Uint8Array) return Buffer.from(value);
2762
+ if (typeof value === "string") return Buffer.from(value);
2763
+ return Buffer.from(JSON.stringify(value));
2764
+ }
2765
+
2766
+ //#endregion
2767
+ //#region src/debug/remote-session.ts
2768
+ async function runRemoteDebugSession({ entriesInput, options, commandName, migrationHint }) {
2769
+ if (migrationHint) logger.warn(migrationHint);
2770
+ if (options.watch) {
2771
+ await runWatchRemoteDebugSession({
2772
+ entriesInput,
2773
+ options,
2774
+ commandName
2775
+ });
2776
+ return;
2777
+ }
2778
+ await runRemoteDebugSessionOnce({
2779
+ entriesInput,
2780
+ options,
2781
+ commandName
2782
+ });
2783
+ }
2784
+ async function runRemoteDebugSessionOnce({ entriesInput, options, commandName }) {
2785
+ const targets = await loadTargets(await resolveDebugEntries(entriesInput), options);
2786
+ await ensureConnectLink({
2787
+ options,
2788
+ commandName,
2789
+ targets
2790
+ });
2791
+ const gatewayOptions = await resolveGatewayOptions({
2792
+ options,
2793
+ commandName,
2794
+ targets
2795
+ });
2796
+ attachRemoteInvokeBridges(targets, gatewayOptions.fastgptBaseUrl);
2797
+ const reporter = createRemoteDebugReporter({
2798
+ commandName,
2799
+ options,
2800
+ gatewayOptions,
2801
+ targets
2802
+ });
2803
+ reporter.start();
2804
+ try {
2805
+ const gateway = await connectDebugGateway({
2806
+ targets,
2807
+ options: gatewayOptions,
2808
+ onLog: (message) => reporter.log(`[gateway] ${message}`)
2809
+ });
2810
+ reporter.attachClose(({ force }) => {
2811
+ gateway.close();
2812
+ if (force) process.exit(130);
2813
+ });
2814
+ const source = gateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
2815
+ reporter.ready(source);
2816
+ await gateway.closed;
2817
+ } finally {
2818
+ await reporter.end();
2819
+ }
2820
+ }
2821
+ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }) {
2822
+ const entries = await resolveDebugEntries(entriesInput);
2823
+ const initialTargets = await loadTargets(entries, options);
2824
+ await ensureConnectLink({
2825
+ options,
2826
+ commandName,
2827
+ targets: initialTargets
2828
+ });
2829
+ const gatewayOptions = await resolveGatewayOptions({
2830
+ options,
2831
+ commandName,
2832
+ targets: initialTargets
2833
+ });
2834
+ attachRemoteInvokeBridges(initialTargets, gatewayOptions.fastgptBaseUrl);
2835
+ const reporter = createRemoteDebugReporter({
2836
+ commandName,
2837
+ options,
2838
+ gatewayOptions,
2839
+ targets: initialTargets
2840
+ });
2841
+ let reloadRunning = false;
2842
+ let reloadPending = false;
2843
+ let closed = false;
2844
+ let gateway;
2845
+ const reloadTargets = async () => {
2846
+ if (reloadRunning) {
2847
+ reloadPending = true;
2848
+ return;
2849
+ }
2850
+ reloadRunning = true;
2851
+ try {
2852
+ do {
2853
+ reloadPending = false;
2854
+ try {
2855
+ const nextTargets = await loadTargets(entries, options);
2856
+ attachRemoteInvokeBridges(nextTargets, gatewayOptions.fastgptBaseUrl);
2857
+ gateway?.updateTargets(nextTargets);
2858
+ reporter.log(`检测到插件文件变化,已热更新 ${nextTargets.length} 个本地插件。`);
2859
+ } catch (error) {
2860
+ reporter.log(`插件热更新失败: ${formatConnectionKeyExchangeError(error)}`);
2861
+ }
2862
+ } while (reloadPending && !closed);
2863
+ } finally {
2864
+ reloadRunning = false;
2865
+ }
2866
+ };
2867
+ const watcher = watchDebugEntries(entries, () => {
2868
+ reloadTargets();
2869
+ });
2870
+ reporter.start();
2871
+ try {
2872
+ const connectedGateway = await connectDebugGateway({
2873
+ targets: initialTargets,
2874
+ options: gatewayOptions,
2875
+ onLog: (message) => reporter.log(`[gateway] ${message}`)
2876
+ });
2877
+ gateway = connectedGateway;
2878
+ reporter.attachClose(({ force }) => {
2879
+ closed = true;
2880
+ watcher.close();
2881
+ connectedGateway.close();
2882
+ if (force) process.exit(130);
2883
+ });
2884
+ const source = connectedGateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
2885
+ reporter.ready(source);
2886
+ await connectedGateway.closed;
2887
+ } finally {
2888
+ closed = true;
2889
+ watcher.close();
2890
+ await reporter.end();
2891
+ }
2892
+ }
2893
+ async function resolveDebugEntries(entriesInput) {
2894
+ const entries = (Array.isArray(entriesInput) ? entriesInput : [entriesInput]).filter((entry) => entry.trim().length > 0);
2895
+ if (entries.length > 0) return entries.map((entry) => path.resolve(entry));
2896
+ const discovered = await discoverDebugEntries();
2897
+ logger.info([`自动发现 ${discovered.length} 个可调试插件:`, ...discovered.map((entry) => ` - ${entry}`)].join("\n"));
2898
+ return discovered;
2899
+ }
2900
+ async function loadTargets(entries, options) {
2901
+ const targets = [];
2902
+ for (const [index, entryDir] of entries.entries()) {
2903
+ const session = await loadDebugSession({
2904
+ entryDir,
2905
+ uploadDir: resolveUploadDir({
2906
+ entryDir,
2907
+ index,
2908
+ total: entries.length,
2909
+ uploadDir: options.uploadDir
2910
+ })
2911
+ });
2912
+ targets.push({
2913
+ runtime: session.runtime,
2914
+ snapshot: session.snapshot
2915
+ });
2916
+ }
2917
+ const duplicatePluginIds = findDuplicateValues$1(targets.map((target) => target.snapshot.pluginId));
2918
+ if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
2919
+ return targets;
2920
+ }
2921
+ function attachRemoteInvokeBridges(targets, fastgptBaseUrl) {
2922
+ targets.forEach((target) => {
2923
+ const invokeBridge = new RemoteDebugInvokeBridge(fastgptBaseUrl);
2924
+ invokeBridge.attach(target.runtime);
2925
+ target.invokeBridge = invokeBridge;
2926
+ });
2927
+ }
2928
+ function watchDebugEntries(entries, onChange) {
2929
+ const watchers = entries.map((entry) => fsWatch(entry, {
2930
+ recursive: true,
2931
+ onChange
2932
+ }));
2933
+ return { close() {
2934
+ watchers.forEach((watcher) => watcher.close());
2935
+ } };
2936
+ }
2937
+ function fsWatch(dir, { recursive, onChange }) {
2938
+ let timer;
2939
+ const watcher = fs$1.watch(dir, { recursive }, () => {
2940
+ clearTimeout(timer);
2941
+ timer = setTimeout(onChange, 150);
2942
+ });
2943
+ return { close() {
2944
+ clearTimeout(timer);
2945
+ watcher.close();
2946
+ } };
2947
+ }
2948
+ function createRemoteDebugReporter({ commandName, options, gatewayOptions, targets }) {
2949
+ const summary = {
2950
+ commandName,
2951
+ mode: "FastGPT connection key",
2952
+ gateway: gatewayOptions.gatewayUrl,
2953
+ reconnect: gatewayOptions.reconnect === false ? "off" : "on",
2954
+ targets: targets.map((target) => target.snapshot)
2955
+ };
2956
+ return options.interactive !== false && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY) ? new TuiRemoteDebugReporter(summary) : new PlainRemoteDebugReporter(summary);
2957
+ }
2958
+ async function ensureConnectLink({ options, commandName, targets }) {
2959
+ if (options.connect) return;
2960
+ const savedConnect = await readSavedConnectionKey();
2961
+ if (savedConnect) {
2962
+ options.connect = savedConnect;
2963
+ options.connectPersistOnSuccess = true;
2964
+ logger.info(`已读取本地 FastGPT connection key 配置: ${getCliConfigPath()}`);
2965
+ return;
2966
+ }
2967
+ if (options.interactive === false || !process.stdin.isTTY || !process.stdout.isTTY) throw new Error("dev 需要 --connect,或在交互式终端中输入 FastGPT connection key。");
2968
+ options.connect = await promptConnectLink({
2969
+ commandName,
2970
+ targets
2971
+ });
2972
+ options.connectPersistOnSuccess = true;
2973
+ }
2974
+ async function promptConnectLink({ commandName, targets, defaultValue, errorMessage }) {
2975
+ process.stdout.write([
2976
+ "\x1B[?25h\x1B[2J\x1B[HFastGPT Plugin Dev",
2977
+ "",
2978
+ `Command fastgpt-plugin ${commandName}`,
2979
+ "Mode waiting for FastGPT connection key",
2980
+ "Status pending",
2981
+ "",
2982
+ "Plugins",
2983
+ ...targets.map((target) => ` ${target.snapshot.pluginId}@${target.snapshot.version}`),
2984
+ "",
2985
+ ...errorMessage ? [`Last error ${errorMessage}`, ""] : [],
2986
+ "Paste the FastGPT connection key or connect link to start the WSS debug session.",
2987
+ ...defaultValue ? ["Press Enter to reuse the current value, or type a new one to replace it.", ""] : [],
2988
+ ""
2989
+ ].join("\n"));
2990
+ return (await input({
2991
+ message: "FastGPT connection key",
2992
+ default: defaultValue,
2993
+ validate(value) {
2994
+ return value.trim().length > 0 || "请输入 FastGPT connection key";
2995
+ }
2996
+ })).trim();
2997
+ }
2998
+ async function readSavedConnectionKey() {
2999
+ return (await readCliConfig()).debug?.connectionKey?.trim() || void 0;
3000
+ }
3001
+ async function saveConnectionKey(connectionKey) {
3002
+ const trimmed = connectionKey.trim();
3003
+ if (!trimmed) return;
3004
+ const configPath = getCliConfigPath();
3005
+ const config = await readCliConfig();
3006
+ const nextConfig = {
3007
+ ...config,
3008
+ debug: {
3009
+ ...config.debug,
3010
+ connectionKey: trimmed
3011
+ }
3012
+ };
3013
+ await fs.mkdir(path.dirname(configPath), {
3014
+ recursive: true,
3015
+ mode: 448
3016
+ });
3017
+ await fs.writeFile(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, {
3018
+ encoding: "utf-8",
3019
+ mode: 384
3020
+ });
3021
+ }
3022
+ async function readCliConfig() {
3023
+ const configPath = getCliConfigPath();
3024
+ let raw;
3025
+ try {
3026
+ raw = await fs.readFile(configPath, "utf-8");
3027
+ } catch (error) {
3028
+ if (isNodeError(error) && error.code === "ENOENT") return {};
3029
+ throw error;
3030
+ }
3031
+ if (!raw.trim()) return {};
3032
+ return CliConfigSchema.parse(JSON.parse(raw));
3033
+ }
3034
+ function getCliConfigPath() {
3035
+ return path.join(getCliConfigDir(), CLI_CONFIG_FILE_NAME);
3036
+ }
3037
+ function getCliConfigDir() {
3038
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
3039
+ if (xdgConfigHome) return path.join(xdgConfigHome, CLI_CONFIG_DIR_NAME);
3040
+ return path.join(os.homedir(), ".config", CLI_CONFIG_DIR_NAME);
3041
+ }
3042
+ function isNodeError(error) {
3043
+ return error instanceof Error && "code" in error;
3044
+ }
3045
+ function formatTargetLine(snapshot) {
3046
+ const tools = snapshot.tools.map((tool) => tool.id).join(", ") || "-";
3047
+ return ` - ${snapshot.pluginId}@${snapshot.version} tools=[${tools}] entry=${snapshot.entryDir}`;
3048
+ }
3049
+ const CLI_CONFIG_FILE_NAME = "config.json";
3050
+ const CLI_CONFIG_DIR_NAME = "fastgpt-plugin";
3051
+ const CliConfigSchema = z.object({ debug: z.object({ connectionKey: z.string().min(1).optional() }).optional() }).passthrough();
3052
+ var PlainRemoteDebugReporter = class {
3053
+ constructor(summary) {
3054
+ this.summary = summary;
3055
+ }
3056
+ start() {
3057
+ logger.info([
3058
+ "FastGPT 插件开发会话",
3059
+ ` command: fastgpt-plugin ${this.summary.commandName}`,
3060
+ ` mode: ${this.summary.mode}`,
3061
+ " ui: plain",
3062
+ ` gateway: ${this.summary.gateway}`,
3063
+ ` reconnect: ${this.summary.reconnect}`,
3064
+ " plugins:",
3065
+ ...this.summary.targets.map(formatTargetLine)
3066
+ ].join("\n"));
3067
+ }
3068
+ log(message) {
3069
+ logger.info(message);
3070
+ }
3071
+ ready(source) {
3072
+ this.summary.targets.forEach((target) => {
3073
+ logger.success(`远程调试已就绪: ${source} ${target.pluginId}`);
3074
+ });
3075
+ logger.info(`已建立 1 个远程调试通道,挂载 ${this.summary.targets.length} 个插件,按 Ctrl+C 停止。`);
3076
+ }
3077
+ attachClose() {}
3078
+ end() {
3079
+ logger.info("远程调试会话已结束。");
3080
+ }
3081
+ };
3082
+ var TuiRemoteDebugReporter = class {
3083
+ status = "connecting";
3084
+ source = "-";
3085
+ logs = [];
3086
+ instance;
3087
+ closeSession;
3088
+ interruptCount = 0;
3089
+ onSigint = () => {
3090
+ this.requestClose("ctrl-c");
3091
+ };
3092
+ requestClose = (reason) => {
3093
+ if (reason === "q") {
3094
+ this.log("收到退出指令,正在关闭远程调试会话。");
3095
+ this.closeSession?.({ force: false });
3096
+ return;
3097
+ }
3098
+ this.interruptCount += 1;
3099
+ const force = this.interruptCount >= 2;
3100
+ this.status = force ? "closed" : "closing";
3101
+ this.log(force ? "再次收到 Ctrl+C,正在强制退出。" : "收到 Ctrl+C,正在关闭远程调试会话。再次按 Ctrl+C 强制退出。");
3102
+ this.closeSession?.({ force });
3103
+ if (force && !this.closeSession) process.exit(130);
3104
+ };
3105
+ constructor(summary) {
3106
+ this.summary = summary;
3107
+ }
3108
+ start() {
3109
+ process.on("SIGINT", this.onSigint);
3110
+ this.instance = render(this.createApp(), {
3111
+ stdin: process.stdin,
3112
+ stdout: process.stdout,
3113
+ stderr: process.stderr,
3114
+ exitOnCtrlC: false,
3115
+ interactive: true,
3116
+ patchConsole: false
3117
+ });
3118
+ }
3119
+ log(message) {
3120
+ this.logs.push({
3121
+ at: /* @__PURE__ */ new Date(),
3122
+ message
3123
+ });
3124
+ this.logs = this.logs.slice(-6);
3125
+ this.rerender();
3126
+ }
3127
+ ready(source) {
3128
+ this.status = "connected";
3129
+ this.source = source;
3130
+ this.log(`远程调试已就绪,挂载 ${this.summary.targets.length} 个插件。`);
3131
+ }
3132
+ attachClose(close) {
3133
+ this.closeSession = close;
3134
+ }
3135
+ async end() {
3136
+ process.off("SIGINT", this.onSigint);
3137
+ this.status = "closed";
3138
+ this.rerender();
3139
+ const instance = this.instance;
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
+ }
3146
+ logger.info("远程调试会话已结束。");
3147
+ }
3148
+ rerender() {
3149
+ this.instance?.rerender(this.createApp());
3150
+ }
3151
+ createApp() {
3152
+ return React.createElement(RemoteDebugInkApp, {
3153
+ summary: this.summary,
3154
+ status: this.status,
3155
+ source: this.source,
3156
+ logs: this.logs,
3157
+ onQuit: this.requestClose
3158
+ });
3159
+ }
3160
+ };
3161
+ function RemoteDebugInkApp({ summary, status, source, logs, onQuit }) {
3162
+ useInput((inputValue, key) => {
3163
+ if (key.ctrl && inputValue === "c") {
3164
+ onQuit("ctrl-c");
3165
+ return;
3166
+ }
3167
+ if (inputValue === "q") onQuit("q");
3168
+ });
3169
+ const statusMeta = getTuiStatusMeta(status);
3170
+ const sourceLabel = source === "-" ? "waiting for bind" : source;
3171
+ const rows = [
3172
+ ["command", `fastgpt-plugin ${summary.commandName}`],
3173
+ ["gateway", summary.gateway],
3174
+ ["source", sourceLabel],
3175
+ ["reconnect", summary.reconnect === "on" ? "enabled" : "disabled"]
3176
+ ];
3177
+ const plugins = summary.targets.map((target) => {
3178
+ const toolCount = target.tools.length;
3179
+ return {
3180
+ id: `${target.pluginId}:${target.version}`,
3181
+ name: `${target.pluginId}@${target.version}`,
3182
+ tools: toolCount === 1 ? "1 tool" : `${toolCount} tools`,
3183
+ entry: target.entryDir
3184
+ };
3185
+ });
3186
+ const activityItems = logs.length > 0 ? logs.map((entry, index) => React.createElement(Box, {
3187
+ key: `${entry.at.getTime()}:${index}`,
3188
+ flexDirection: "row"
3189
+ }, React.createElement(Text, { color: "gray" }, formatTuiTime(entry.at)), React.createElement(Text, { dimColor: true }, " │ "), React.createElement(Text, { wrap: "truncate-end" }, entry.message))) : [React.createElement(Text, {
3190
+ key: "empty-log",
3191
+ dimColor: true
3192
+ }, "waiting for gateway events")];
3193
+ return React.createElement(Box, {
3194
+ flexDirection: "column",
3195
+ paddingX: 1,
3196
+ paddingY: 1
3197
+ }, React.createElement(Box, {
3198
+ borderStyle: "round",
3199
+ borderColor: statusMeta.borderColor,
3200
+ paddingX: 2,
3201
+ paddingY: 1,
3202
+ flexDirection: "column"
3203
+ }, React.createElement(Box, { justifyContent: "space-between" }, React.createElement(Box, null, React.createElement(Text, {
3204
+ bold: true,
3205
+ color: "cyanBright"
3206
+ }, "FastGPT Plugin Dev"), React.createElement(Text, { dimColor: true }, " remote debug")), React.createElement(Box, null, React.createElement(Text, {
3207
+ color: statusMeta.color,
3208
+ bold: true
3209
+ }, 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, {
3210
+ marginTop: 1,
3211
+ columnGap: 1
3212
+ }, React.createElement(Box, {
3213
+ borderStyle: "round",
3214
+ borderColor: "gray",
3215
+ paddingX: 1,
3216
+ paddingY: 1,
3217
+ flexDirection: "column",
3218
+ width: "50%"
3219
+ }, React.createElement(Text, { bold: true }, "Session"), React.createElement(Box, {
3220
+ flexDirection: "column",
3221
+ marginTop: 1
3222
+ }, ...rows.map(([label, value]) => React.createElement(SessionRow, {
3223
+ key: label,
3224
+ label,
3225
+ value
3226
+ })))), React.createElement(Box, {
3227
+ borderStyle: "round",
3228
+ borderColor: "gray",
3229
+ paddingX: 1,
3230
+ paddingY: 1,
3231
+ flexDirection: "column",
3232
+ width: "50%"
3233
+ }, React.createElement(Box, { justifyContent: "space-between" }, React.createElement(Text, { bold: true }, "Plugins"), React.createElement(Text, { dimColor: true }, `${plugins.length} mounted`)), React.createElement(Box, {
3234
+ flexDirection: "column",
3235
+ marginTop: 1
3236
+ }, ...plugins.map((plugin, index) => React.createElement(Box, {
3237
+ key: plugin.id,
3238
+ flexDirection: "column",
3239
+ marginTop: index === 0 ? 0 : 1
3240
+ }, React.createElement(Box, null, React.createElement(Text, { color: "greenBright" }, "● "), React.createElement(Text, {
3241
+ bold: true,
3242
+ wrap: "truncate-end"
3243
+ }, plugin.name), React.createElement(Text, { dimColor: true }, ` ${plugin.tools}`)), React.createElement(Text, {
3244
+ dimColor: true,
3245
+ wrap: "truncate-end"
3246
+ }, ` ${plugin.entry}`)))))), React.createElement(Box, {
3247
+ borderStyle: "round",
3248
+ borderColor: "gray",
3249
+ paddingX: 1,
3250
+ paddingY: 1,
3251
+ flexDirection: "column",
3252
+ marginTop: 1
3253
+ }, React.createElement(Box, { justifyContent: "space-between" }, React.createElement(Text, { bold: true }, "Activity"), React.createElement(Text, { dimColor: true }, "latest 6 events")), React.createElement(Box, {
3254
+ flexDirection: "column",
3255
+ marginTop: 1
3256
+ }, ...activityItems)), React.createElement(Box, {
3257
+ marginTop: 1,
3258
+ justifyContent: "space-between"
3259
+ }, React.createElement(Text, { dimColor: true }, "q stop"), React.createElement(Text, { dimColor: true }, "Ctrl+C stop · second Ctrl+C force exit")));
3260
+ }
3261
+ function getTuiStatusMeta(status) {
3262
+ if (status === "connected") return {
3263
+ label: "CONNECTED",
3264
+ marker: "●",
3265
+ description: "Gateway channel is live and ready to receive FastGPT invocations.",
3266
+ color: "greenBright",
3267
+ borderColor: "green"
3268
+ };
3269
+ if (status === "closed") return {
3270
+ label: "CLOSED",
3271
+ marker: "■",
3272
+ description: "Remote debug session has been closed.",
3273
+ color: "gray",
3274
+ borderColor: "gray"
3275
+ };
3276
+ if (status === "closing") return {
3277
+ label: "CLOSING",
3278
+ marker: "◆",
3279
+ description: "Closing gateway channel. Press Ctrl+C again to force exit.",
3280
+ color: "yellowBright",
3281
+ borderColor: "yellow"
3282
+ };
3283
+ return {
3284
+ label: "CONNECTING",
3285
+ marker: "◆",
3286
+ description: "Binding local plugins to the FastGPT connection gateway.",
3287
+ color: "yellowBright",
3288
+ borderColor: "yellow"
3289
+ };
3290
+ }
3291
+ function SessionRow({ label, value }) {
3292
+ return React.createElement(Box, null, React.createElement(Text, { color: "gray" }, label.padEnd(10)), React.createElement(Text, { wrap: "truncate-end" }, value));
3293
+ }
3294
+ function formatTuiTime(value) {
3295
+ return [
3296
+ value.getHours().toString().padStart(2, "0"),
3297
+ value.getMinutes().toString().padStart(2, "0"),
3298
+ value.getSeconds().toString().padStart(2, "0")
3299
+ ].join(":");
3300
+ }
3301
+ async function resolveGatewayOptions({ options, commandName, targets }) {
3302
+ if (!options.connect) throw new Error("dev 需要 --connect 来建立远程调试通道。");
3303
+ while (true) try {
3304
+ const gatewayOptions = await resolveConnectGatewayOptions(options);
3305
+ if (options.connectPersistOnSuccess === true) {
3306
+ await saveConnectionKey(options.connect);
3307
+ logger.info(`已保存 FastGPT connection key 到本地配置: ${getCliConfigPath()}`);
3308
+ options.connectPersistOnSuccess = false;
3309
+ }
3310
+ return gatewayOptions;
3311
+ } catch (error) {
3312
+ const errorMessage = formatConnectionKeyExchangeError(error);
3313
+ if (!canPromptForConnectInput(options)) throw error;
3314
+ logger.error(errorMessage);
3315
+ options.connect = await promptConnectLink({
3316
+ commandName,
3317
+ targets,
3318
+ defaultValue: options.connect,
3319
+ errorMessage
3320
+ });
3321
+ options.connectPersistOnSuccess = true;
3322
+ }
3323
+ }
3324
+ function canPromptForConnectInput(options) {
3325
+ return options.interactive !== false && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
3326
+ }
3327
+ function formatConnectionKeyExchangeError(error) {
3328
+ return error instanceof Error ? error.message : String(error);
3329
+ }
3330
+ async function resolveConnectGatewayOptions(options) {
3331
+ const info = await exchangeConnectLink(options.connect);
3332
+ return {
3333
+ gatewayUrl: normalizeGatewayWsUrl(info.gatewayUrl),
3334
+ connectToken: info.connectToken,
3335
+ userId: info.tmbId,
3336
+ source: info.source,
3337
+ fastgptBaseUrl: info.fastgptBaseUrl,
3338
+ tokenTtlMs: Math.max(1, info.expiresAt - Date.now()),
3339
+ reconnect: options.noReconnect ? false : options.reconnect ?? true,
3340
+ reconnectIntervalMs: toPositiveInt(options.reconnectIntervalMs ?? process.env.CONNECTION_GATEWAY_RECONNECT_INTERVAL_MS ?? "2000", "reconnect-interval-ms"),
3341
+ resolveReconnectOptions: () => resolveConnectGatewayOptions(options)
3342
+ };
3343
+ }
3344
+ function resolveUploadDir({ entryDir, index, total, uploadDir }) {
3345
+ if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
3346
+ if (total === 1) return path.resolve(uploadDir);
3347
+ return path.resolve(uploadDir, `${index + 1}-${sanitizePathSegment$1(path.basename(entryDir))}`);
3348
+ }
3349
+ const ConnectInfoSchema = z.object({
3350
+ gatewayUrl: z.string().min(1),
3351
+ transport: z.literal("websocket"),
3352
+ source: z.string().min(1),
3353
+ connectToken: z.string().min(1),
3354
+ fastgptBaseUrl: z.string().min(1).optional(),
3355
+ expiresAt: z.number().int().positive()
3356
+ });
3357
+ async function exchangeConnectLink(connectInput) {
3358
+ const exchangeUrl = isHttpUrl(connectInput) ? connectInput : resolveConnectionKeyExchangeUrl();
3359
+ const response = isHttpUrl(connectInput) ? await fetch(exchangeUrl) : await fetch(exchangeUrl, {
3360
+ method: "POST",
3361
+ headers: { "Content-Type": "application/json" },
3362
+ body: JSON.stringify({ connectionKey: connectInput })
3363
+ });
3364
+ const text = await response.text();
3365
+ if (!response.ok) throw new Error(`connection key 请求失败: ${response.status} ${text}`);
3366
+ const payload = text ? JSON.parse(text) : {};
3367
+ const info = ConnectInfoSchema.parse(payload.data ?? payload);
3368
+ return {
3369
+ ...info,
3370
+ fastgptBaseUrl: info.fastgptBaseUrl ?? new URL(exchangeUrl).origin,
3371
+ tmbId: parseTmbIdFromDebugSource(info.source)
3372
+ };
3373
+ }
3374
+ function isHttpUrl(value) {
3375
+ try {
3376
+ const parsed = new URL(value);
3377
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
3378
+ } catch {
3379
+ return false;
3380
+ }
3381
+ }
3382
+ function resolveConnectionKeyExchangeUrl() {
3383
+ const explicit = process.env.FASTGPT_PLUGIN_DEBUG_CONNECT_URL;
3384
+ if (explicit) return explicit;
3385
+ const baseUrl = process.env.FASTGPT_PLUGIN_SERVER_URL ?? process.env.PLUGIN_SERVER_URL;
3386
+ if (!baseUrl) throw new Error("使用裸 connection key 时需要设置 FASTGPT_PLUGIN_DEBUG_CONNECT_URL 或 FASTGPT_PLUGIN_SERVER_URL。");
3387
+ return new URL("/api/plugin/debug-sessions/connection-key:exchange", baseUrl).toString();
3388
+ }
3389
+ function parseTmbIdFromDebugSource(source) {
3390
+ const parsed = parsePluginDebugSessionSource(source);
3391
+ if (!parsed) throw new Error(`debug source 缺少 tmbId: ${source}`);
3392
+ return parsed.tmbId;
3393
+ }
3394
+ function normalizeGatewayWsUrl(gatewayUrl) {
3395
+ const parsed = new URL(gatewayUrl);
3396
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new Error("gatewayUrl 必须使用 ws:// 或 wss:// 协议。");
3397
+ if (!parsed.hostname) throw new Error("gatewayUrl 必须包含 host。");
3398
+ return parsed.toString();
3399
+ }
3400
+ function toPositiveInt(value, label) {
3401
+ const parsed = Number(value);
3402
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new Error(`${label} 必须是正整数。`);
3403
+ return parsed;
3404
+ }
3405
+ function findDuplicateValues$1(values) {
3406
+ const seen = /* @__PURE__ */ new Set();
3407
+ const duplicates = /* @__PURE__ */ new Set();
3408
+ values.forEach((value) => {
3409
+ if (seen.has(value)) {
3410
+ duplicates.add(value);
3411
+ return;
3412
+ }
3413
+ seen.add(value);
3414
+ });
3415
+ return [...duplicates];
3416
+ }
3417
+ function sanitizePathSegment$1(value) {
3418
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_") || "plugin";
3419
+ }
3420
+
1717
3421
  //#endregion
1718
3422
  //#region src/commands/debug.ts
1719
3423
  var DebugCommand = class extends BaseCommand {
1720
3424
  register(parent) {
1721
- parent.command("debug <entry>").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 的输出目录").action(async (entry, opts) => {
1722
- await this.run(entry, opts);
3425
+ 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) => {
3426
+ await this.run(entries, opts);
1723
3427
  });
1724
3428
  }
1725
- async run(entry, options) {
1726
- const entryDir = path.resolve(entry);
1727
- const uploadDir = path.resolve(options.uploadDir ?? path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
1728
- const session = await loadDebugSession({
3429
+ async run(entriesInput, options) {
3430
+ const entries = (Array.isArray(entriesInput) ? entriesInput : [entriesInput]).map((entry) => path.resolve(entry));
3431
+ const isMultiEntry = entries.length > 1;
3432
+ if (options.connect) {
3433
+ await runRemoteDebugSession({
3434
+ entriesInput,
3435
+ options,
3436
+ commandName: "dev",
3437
+ migrationHint: "debug --connect 是兼容入口,远程集成调试建议改用 fastgpt-plugin dev。"
3438
+ });
3439
+ return;
3440
+ }
3441
+ if (isMultiEntry && !options.connect) throw new Error("debug 是轻量本地调试入口,一次只支持一个插件;多插件远程调试请使用 dev。");
3442
+ if (isMultiEntry && options.run) throw new Error("--run 只支持单个插件入口。");
3443
+ const sessions = [];
3444
+ for (const [index, entryDir] of entries.entries()) sessions.push(await loadDebugSession({
1729
3445
  entryDir,
1730
- uploadDir
3446
+ uploadDir: this.resolveUploadDir({
3447
+ entryDir,
3448
+ index,
3449
+ total: entries.length,
3450
+ uploadDir: options.uploadDir
3451
+ })
3452
+ }));
3453
+ sessions.forEach((session) => {
3454
+ this.printSnapshot(session.snapshot, session.uploadDir);
1731
3455
  });
1732
- this.printSnapshot(session.snapshot, uploadDir);
3456
+ const duplicatePluginIds = findDuplicateValues(sessions.map((session) => session.snapshot.pluginId));
3457
+ if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
3458
+ const [session] = sessions;
1733
3459
  if (!options.run) return;
1734
3460
  const input = await this.readJsonOption(options.input, options.inputFile, "input");
1735
3461
  const secrets = await this.readJsonOption(options.secrets, options.secretsFile, "secrets");
@@ -1810,6 +3536,11 @@ var DebugCommand = class extends BaseCommand {
1810
3536
  JSON.stringify(this.createInputExample(tool.inputSchema))
1811
3537
  ].map(quoteShellArg).join(" ");
1812
3538
  }
3539
+ resolveUploadDir({ entryDir, index, total, uploadDir }) {
3540
+ if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
3541
+ if (total === 1) return path.resolve(uploadDir);
3542
+ return path.resolve(uploadDir, `${index + 1}-${sanitizePathSegment(path.basename(entryDir))}`);
3543
+ }
1813
3544
  createInputExample(schema) {
1814
3545
  if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
1815
3546
  if ("const" in schema) return schema.const;
@@ -1853,6 +3584,38 @@ function quoteShellArg(value) {
1853
3584
  if (/^[a-zA-Z0-9_./:@-]+$/.test(value)) return value;
1854
3585
  return `'${value.replace(/'/g, `'"'"'`)}'`;
1855
3586
  }
3587
+ function findDuplicateValues(values) {
3588
+ const seen = /* @__PURE__ */ new Set();
3589
+ const duplicates = /* @__PURE__ */ new Set();
3590
+ values.forEach((value) => {
3591
+ if (seen.has(value)) {
3592
+ duplicates.add(value);
3593
+ return;
3594
+ }
3595
+ seen.add(value);
3596
+ });
3597
+ return [...duplicates];
3598
+ }
3599
+ function sanitizePathSegment(value) {
3600
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_") || "plugin";
3601
+ }
3602
+
3603
+ //#endregion
3604
+ //#region src/commands/dev.ts
3605
+ var DevCommand = class extends BaseCommand {
3606
+ register(parent) {
3607
+ 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) => {
3608
+ await this.run(entries, opts);
3609
+ });
3610
+ }
3611
+ async run(entriesInput, options) {
3612
+ await runRemoteDebugSession({
3613
+ entriesInput,
3614
+ options,
3615
+ commandName: "dev"
3616
+ });
3617
+ }
3618
+ };
1856
3619
 
1857
3620
  //#endregion
1858
3621
  //#region src/commands/pack.ts
@@ -1942,6 +3705,7 @@ function createProgram() {
1942
3705
  new CheckCommand().register(program);
1943
3706
  new CreateCommand().register(program);
1944
3707
  new DebugCommand().register(program);
3708
+ new DevCommand().register(program);
1945
3709
  new PackCommand().register(program);
1946
3710
  return program;
1947
3711
  }