@zhin.js/host-http 1.1.14 → 1.1.16

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.
@@ -3,7 +3,6 @@ import type { DatabaseHostConsole } from '@zhin.js/plugin-runtime';
3
3
  import {
4
4
  CONFIG_PATCH_LIMITS,
5
5
  CONFIG_RPC,
6
- ENDPOINT_RPC,
7
6
  type ConsoleConfigPatchRequest,
8
7
  type ConsoleConfigPatchResult,
9
8
  type ConsoleConfigProjectPatchResult,
@@ -14,6 +13,13 @@ import {
14
13
  } from '@zhin.js/console-protocol';
15
14
  import { dispatchExtendedConsoleRpc, type ConsoleRpcExtendedCtx } from './console-rpc-extended/index.js';
16
15
  import { dispatchPluginConsoleRpc } from './console-plugin-rpc.js';
16
+ import {
17
+ configurationSaveMessage,
18
+ dispatchHostResourceConsoleRpc,
19
+ invalidProjectPatchOperations,
20
+ } from './console-rpc-handlers.js';
21
+
22
+ export { isDemoHttpAllowed } from './console-rpc-handlers.js';
17
23
 
18
24
  /**
19
25
  * Console RPC 请求结构:未锚定 endpoint 的会话地址(结构对齐
@@ -30,26 +36,6 @@ export interface ConversationAddress {
30
36
  readonly threadId?: string;
31
37
  }
32
38
 
33
- /** Canonical Console wire fields → runtime conversation address. */
34
- function wireConversation(
35
- channelType: string,
36
- channelId: string,
37
- parent: unknown,
38
- ): ConversationAddress {
39
- const kind: ConversationAddress['kind'] =
40
- channelType === 'group' || channelType === 'channel' ? channelType : 'private';
41
- const wire = parent && typeof parent === 'object'
42
- ? parent as { readonly type?: unknown; readonly id?: unknown }
43
- : undefined;
44
- const parentKind = wire?.type === 'group' || wire?.type === 'channel' ? wire.type : undefined;
45
- const parentId = typeof wire?.id === 'string' && wire.id ? wire.id : undefined;
46
- return {
47
- kind,
48
- id: channelId,
49
- ...(parentKind && parentId ? { parent: { kind: parentKind, id: parentId } } : {}),
50
- };
51
- }
52
-
53
39
  export type RuntimeConsoleRpcMessage = {
54
40
  readonly type?: unknown;
55
41
  readonly requestId?: unknown;
@@ -285,31 +271,6 @@ export function pickRpcReply(
285
271
  );
286
272
  }
287
273
 
288
- const FORBIDDEN_PATCH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype']);
289
-
290
- /**
291
- * Structural check for `config:patch-project` operations at the RPC boundary, so no project
292
- * port implementation receives a malformed request. The store repeats the full validation.
293
- */
294
- function invalidProjectPatchOperations(patches: readonly unknown[]): string | undefined {
295
- if (patches.length === 0) return 'patches 必须是非空数组';
296
- if (patches.length > CONFIG_PATCH_LIMITS.maxOperations) return `一次最多 ${CONFIG_PATCH_LIMITS.maxOperations} 个 patch`;
297
- for (const [index, item] of patches.entries()) {
298
- if (!item || typeof item !== 'object' || Array.isArray(item)) return `patches[${index}] 必须是对象`;
299
- const operation = item as Record<string, unknown>;
300
- const path = operation.path;
301
- if (!Array.isArray(path) || path.length === 0 || path.length > CONFIG_PATCH_LIMITS.maxPathDepth
302
- || path.some(segment => typeof segment !== 'string' || !segment
303
- || segment.length > CONFIG_PATCH_LIMITS.maxSegmentLength || FORBIDDEN_PATCH_SEGMENTS.has(segment))) {
304
- return `patches[${index}].path 必须是 1–${CONFIG_PATCH_LIMITS.maxPathDepth} 段的有效字符串数组`;
305
- }
306
- if (operation.op === 'set') {
307
- if (!Object.hasOwn(operation, 'value') || operation.value === undefined) return `patches[${index}] 的 set 需要 value`;
308
- } else if (operation.op !== 'remove') return `patches[${index}].op 只能是 set 或 remove`;
309
- }
310
- return undefined;
311
- }
312
-
313
274
  export async function dispatchRuntimeConsoleRpc(
314
275
  message: RuntimeConsoleRpcMessage,
315
276
  ctx: RuntimeConsoleRpcContext,
@@ -631,465 +592,10 @@ export async function dispatchRuntimeConsoleRpc(
631
592
  }
632
593
  return payloads;
633
594
  }
634
- case 'files:tree': {
635
- try {
636
- const tree = ctx.listProjectFiles ? await ctx.listProjectFiles() : [];
637
- emit({ requestId, data: { tree } });
638
- } catch (error) {
639
- emit({
640
- requestId,
641
- error: `Failed to build file tree: ${error instanceof Error ? error.message : String(error)}`,
642
- });
643
- }
644
- return payloads;
645
- }
646
- case 'files:read': {
647
- try {
648
- const filePath = String(message.filePath ?? '');
649
- if (!filePath) {
650
- emit({ requestId, error: 'filePath is required' });
651
- return payloads;
652
- }
653
- if (!ctx.readProjectFile) {
654
- emit({ requestId, error: 'File read is not configured' });
655
- return payloads;
656
- }
657
- const file = await ctx.readProjectFile(filePath);
658
- emit({ requestId, data: file });
659
- } catch (error) {
660
- emit({
661
- requestId,
662
- error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
663
- });
664
- }
665
- return payloads;
666
- }
667
- case 'files:save': {
668
- try {
669
- const filePath = String(message.filePath ?? '');
670
- const content = message.content;
671
- if (!filePath) {
672
- emit({ requestId, error: 'filePath is required' });
673
- return payloads;
674
- }
675
- if (typeof content !== 'string') {
676
- emit({ requestId, error: 'content field is required' });
677
- return payloads;
678
- }
679
- if (!ctx.saveProjectFile) {
680
- emit({ requestId, error: 'File write is not configured' });
681
- return payloads;
682
- }
683
- await ctx.saveProjectFile(filePath, content);
684
- emit({ requestId, data: { success: true, message: `文件已保存: ${filePath}` } });
685
- } catch (error) {
686
- emit({
687
- requestId,
688
- error: `Failed to save file: ${error instanceof Error ? error.message : String(error)}`,
689
- });
690
- }
691
- return payloads;
692
- }
693
- case 'env:list': {
694
- try {
695
- const files = ctx.listEnvFiles ? await ctx.listEnvFiles() : [];
696
- emit({ requestId, data: { files } });
697
- } catch (error) {
698
- emit({
699
- requestId,
700
- error: `Failed to list env files: ${error instanceof Error ? error.message : String(error)}`,
701
- });
702
- }
703
- return payloads;
704
- }
705
- case 'env:get': {
706
- try {
707
- const filename = String(message.filename ?? '');
708
- if (!filename) {
709
- emit({ requestId, error: 'filename is required' });
710
- return payloads;
711
- }
712
- if (!ctx.readEnvFile) {
713
- emit({ requestId, error: 'Env read is not configured' });
714
- return payloads;
715
- }
716
- const content = await ctx.readEnvFile(filename);
717
- emit({ requestId, data: { content } });
718
- } catch (error) {
719
- emit({
720
- requestId,
721
- error: `Failed to read env file: ${error instanceof Error ? error.message : String(error)}`,
722
- });
723
- }
724
- return payloads;
725
- }
726
- case 'env:save': {
727
- try {
728
- const filename = String(message.filename ?? '');
729
- const content = message.content;
730
- if (!filename) {
731
- emit({ requestId, error: 'filename is required' });
732
- return payloads;
733
- }
734
- if (typeof content !== 'string') {
735
- emit({ requestId, error: 'content field is required' });
736
- return payloads;
737
- }
738
- if (!ctx.writeEnvFile) {
739
- emit({ requestId, error: 'Env write is not configured' });
740
- return payloads;
741
- }
742
- await ctx.writeEnvFile(filename, content);
743
- emit({ requestId, data: { success: true, message: `环境文件已保存: ${filename}` } });
744
- } catch (error) {
745
- emit({
746
- requestId,
747
- error: `Failed to save env file: ${error instanceof Error ? error.message : String(error)}`,
748
- });
749
- }
750
- return payloads;
751
- }
752
- case 'schema:get': {
753
- try {
754
- const pluginName = typeof message.pluginName === 'string'
755
- ? message.pluginName
756
- : undefined;
757
- const schema = ctx.getSchema ? await ctx.getSchema(pluginName) : null;
758
- emit({ requestId, data: schema });
759
- } catch (error) {
760
- emit({
761
- requestId,
762
- error: `Failed to get schema: ${error instanceof Error ? error.message : String(error)}`,
763
- });
764
- }
765
- return payloads;
766
- }
767
- case 'schema:get-all': {
768
- try {
769
- const schemas = ctx.getAllSchemas ? await ctx.getAllSchemas() : {};
770
- emit({ requestId, data: schemas });
771
- } catch (error) {
772
- emit({
773
- requestId,
774
- error: `Failed to get all schemas: ${error instanceof Error ? error.message : String(error)}`,
775
- });
776
- }
777
- return payloads;
778
- }
779
- case 'db:info': {
780
- if (ctx.database) {
781
- emit({ requestId, data: ctx.database.info() });
782
- return payloads;
783
- }
784
- if (ctx.dbInfo) {
785
- try {
786
- emit({ requestId, data: await ctx.dbInfo() });
787
- } catch (error) {
788
- emit({
789
- requestId,
790
- error: error instanceof Error ? error.message : String(error),
791
- });
792
- }
793
- return payloads;
794
- }
795
- emit({
796
- requestId,
797
- data: { dialect: null, connected: false, message: 'Database not configured on Plugin Runtime Host' },
798
- });
799
- return payloads;
800
- }
801
- case 'db:tables': {
802
- try {
803
- if (ctx.database) {
804
- emit({ requestId, data: { tables: ctx.database.tables() } });
805
- return payloads;
806
- }
807
- const tables = ctx.dbTables ? await ctx.dbTables() : [];
808
- emit({ requestId, data: tables });
809
- } catch (error) {
810
- emit({
811
- requestId,
812
- error: error instanceof Error ? error.message : String(error),
813
- });
814
- }
815
- return payloads;
816
- }
817
- case 'db:select': {
818
- const table = stringField(message, 'table');
819
- if (!table) return errorReply(payloads, requestId, 'table is required');
820
- const page = positiveInteger(message.page, 1);
821
- const pageSize = Math.min(positiveInteger(message.pageSize, 50), 500);
822
- const where = recordField(message.where);
823
- if (!ctx.database) {
824
- emit({ requestId, data: { rows: [], total: 0, page, pageSize } });
595
+ default: {
596
+ if (await dispatchHostResourceConsoleRpc(type, message, ctx, requestId, payloads)) {
825
597
  return payloads;
826
598
  }
827
- try {
828
- emit({ requestId, data: await ctx.database.select(table, page, pageSize, where) });
829
- } catch (error) {
830
- emit({ requestId, error: `Failed to select: ${errorMessage(error)}` });
831
- }
832
- return payloads;
833
- }
834
- case 'db:insert': {
835
- if (!ctx.database) return errorReply(payloads, requestId, 'Database RPC is not available on Plugin Runtime Host');
836
- const table = stringField(message, 'table');
837
- const row = recordField(message.row);
838
- if (!table || !row) return errorReply(payloads, requestId, 'table and row are required');
839
- try {
840
- await ctx.database.insert(table, row);
841
- emit({ requestId, data: { success: true } });
842
- } catch (error) {
843
- emit({ requestId, error: `Failed to insert: ${errorMessage(error)}` });
844
- }
845
- return payloads;
846
- }
847
- case 'db:update': {
848
- if (!ctx.database) return errorReply(payloads, requestId, 'Database RPC is not available on Plugin Runtime Host');
849
- const table = stringField(message, 'table');
850
- const row = recordField(message.row);
851
- const where = recordField(message.where);
852
- if (!table || !row || !where) {
853
- return errorReply(payloads, requestId, 'table, row, and where are required');
854
- }
855
- try {
856
- const affected = await ctx.database.update(table, row, where);
857
- emit({ requestId, data: { success: true, affected } });
858
- } catch (error) {
859
- emit({ requestId, error: `Failed to update: ${errorMessage(error)}` });
860
- }
861
- return payloads;
862
- }
863
- case 'db:delete': {
864
- if (!ctx.database) return errorReply(payloads, requestId, 'Database RPC is not available on Plugin Runtime Host');
865
- const table = stringField(message, 'table');
866
- const where = recordField(message.where);
867
- if (!table || !where) return errorReply(payloads, requestId, 'table and where are required');
868
- try {
869
- const deleted = await ctx.database.delete(table, where);
870
- emit({ requestId, data: { success: true, deleted } });
871
- } catch (error) {
872
- emit({ requestId, error: `Failed to delete: ${errorMessage(error)}` });
873
- }
874
- return payloads;
875
- }
876
- case 'db:drop-table': {
877
- if (!ctx.database) return errorReply(payloads, requestId, 'Database RPC is not available on Plugin Runtime Host');
878
- const table = stringField(message, 'table');
879
- if (!table) return errorReply(payloads, requestId, 'table is required');
880
- try {
881
- await ctx.database.dropTable(table);
882
- emit({ requestId, data: { success: true } });
883
- } catch (error) {
884
- emit({ requestId, error: `Failed to drop table: ${errorMessage(error)}` });
885
- }
886
- return payloads;
887
- }
888
- case 'db:kv:get': {
889
- const table = stringField(message, 'table');
890
- const key = stringField(message, 'key');
891
- if (!table || !key) return errorReply(payloads, requestId, 'table and key are required');
892
- if (!ctx.database) {
893
- emit({ requestId, data: { key, value: null } });
894
- return payloads;
895
- }
896
- try {
897
- emit({ requestId, data: { key, value: await ctx.database.kvGet(table, key) } });
898
- } catch (error) {
899
- emit({ requestId, error: `Failed to get kv: ${errorMessage(error)}` });
900
- }
901
- return payloads;
902
- }
903
- case 'db:kv:set': {
904
- if (!ctx.database) return errorReply(payloads, requestId, 'Database RPC is not available on Plugin Runtime Host');
905
- const table = stringField(message, 'table');
906
- const key = stringField(message, 'key');
907
- if (!table || !key) return errorReply(payloads, requestId, 'table and key are required');
908
- try {
909
- const ttl = typeof message.ttl === 'number' && Number.isFinite(message.ttl)
910
- ? message.ttl
911
- : undefined;
912
- await ctx.database.kvSet(table, key, message.value, ttl);
913
- emit({ requestId, data: { success: true } });
914
- } catch (error) {
915
- emit({ requestId, error: `Failed to set kv: ${errorMessage(error)}` });
916
- }
917
- return payloads;
918
- }
919
- case 'db:kv:delete': {
920
- if (!ctx.database) return errorReply(payloads, requestId, 'Database RPC is not available on Plugin Runtime Host');
921
- const table = stringField(message, 'table');
922
- const key = stringField(message, 'key');
923
- if (!table || !key) return errorReply(payloads, requestId, 'table and key are required');
924
- try {
925
- await ctx.database.kvDelete(table, key);
926
- emit({ requestId, data: { success: true } });
927
- } catch (error) {
928
- emit({ requestId, error: `Failed to delete kv: ${errorMessage(error)}` });
929
- }
930
- return payloads;
931
- }
932
- case 'db:kv:entries': {
933
- const table = stringField(message, 'table');
934
- if (!table) return errorReply(payloads, requestId, 'table is required');
935
- if (!ctx.database) {
936
- emit({ requestId, data: { entries: [] } });
937
- return payloads;
938
- }
939
- try {
940
- emit({ requestId, data: { entries: await ctx.database.kvEntries(table) } });
941
- } catch (error) {
942
- emit({ requestId, error: `Failed to get entries: ${errorMessage(error)}` });
943
- }
944
- return payloads;
945
- }
946
- case 'endpoint.list': {
947
- try {
948
- const endpoints = ctx.listEndpoints ? await ctx.listEndpoints() : [];
949
- emit({ requestId, data: { endpoints } });
950
- } catch (error) {
951
- emit({
952
- requestId,
953
- error: error instanceof Error ? error.message : String(error),
954
- });
955
- }
956
- return payloads;
957
- }
958
- case 'endpoint.info': {
959
- try {
960
- const data = recordField(message.data) ?? message;
961
- const adapter = String(data.adapter ?? '');
962
- const endpointKey = String(data.endpointKey ?? '');
963
- if (!adapter || !endpointKey) {
964
- emit({ requestId, error: 'adapter and endpointKey are required' });
965
- return payloads;
966
- }
967
- if (!ctx.getEndpoint) {
968
- emit({ requestId, error: 'Endpoint registry is not configured' });
969
- return payloads;
970
- }
971
- const endpoint = await ctx.getEndpoint(adapter, endpointKey);
972
- if (!endpoint) {
973
- emit({ requestId, error: 'endpoint not found' });
974
- return payloads;
975
- }
976
- emit({ requestId, data: endpoint });
977
- } catch (error) {
978
- emit({
979
- requestId,
980
- error: error instanceof Error ? error.message : String(error),
981
- });
982
- }
983
- return payloads;
984
- }
985
- case ENDPOINT_RPC.TEST: {
986
- const data = recordField(message.data) ?? message;
987
- const adapter = stringField(data, 'adapter');
988
- const endpointKey = stringField(data, 'endpointKey');
989
- if (!adapter || !endpointKey) {
990
- emit({ requestId, error: 'adapter and endpointKey are required' });
991
- return payloads;
992
- }
993
- if (!ctx.getEndpoint) {
994
- emit({ requestId, error: 'Endpoint registry is not configured' });
995
- return payloads;
996
- }
997
- const startedAt = Date.now();
998
- try {
999
- const endpoint = await ctx.getEndpoint(adapter, endpointKey);
1000
- if (!endpoint) {
1001
- emit({ requestId, data: {
1002
- reachable: false,
1003
- connected: false,
1004
- phase: 'unconfigured',
1005
- latencyMs: Date.now() - startedAt,
1006
- message: 'Endpoint 未注册到当前 generation',
1007
- } });
1008
- return payloads;
1009
- }
1010
- emit({ requestId, data: {
1011
- reachable: endpoint.connected && endpoint.status === 'online',
1012
- connected: endpoint.connected,
1013
- status: endpoint.status,
1014
- admitted: endpoint.admitted,
1015
- transportState: endpoint.transportState,
1016
- phase: endpoint.phase ?? (endpoint.connected ? 'online' : 'pending'),
1017
- pendingLogin: endpoint.pendingLogin ?? false,
1018
- latencyMs: Date.now() - startedAt,
1019
- message: endpoint.connected
1020
- ? 'Endpoint 已连接'
1021
- : endpoint.pendingLogin
1022
- ? 'Endpoint 等待登录操作'
1023
- : 'Endpoint 当前离线',
1024
- } });
1025
- } catch (error) {
1026
- emit({ requestId, data: {
1027
- reachable: false,
1028
- connected: false,
1029
- phase: 'failed',
1030
- latencyMs: Date.now() - startedAt,
1031
- message: error instanceof Error ? error.message : String(error),
1032
- } });
1033
- }
1034
- return payloads;
1035
- }
1036
- case 'endpoint.send_message': {
1037
- try {
1038
- const data = recordField(message.data) ?? message;
1039
- const adapter = String(data.adapter ?? '');
1040
- const endpointKey = String(data.endpointKey ?? '');
1041
- const channelId = String(data.channelId ?? data.id ?? '');
1042
- const channelType = String(data.channelType ?? data.type ?? '');
1043
- const content = data.content;
1044
- if (!adapter || !endpointKey || !channelId || !channelType || content === undefined) {
1045
- emit({
1046
- requestId,
1047
- error: 'adapter, endpointKey, channelId, channelType, and content are required',
1048
- });
1049
- return payloads;
1050
- }
1051
- if (!ctx.sendEndpointMessage) {
1052
- emit({ requestId, error: 'Endpoint send is not configured' });
1053
- return payloads;
1054
- }
1055
- const result = await ctx.sendEndpointMessage({
1056
- adapter,
1057
- endpointKey,
1058
- conversation: wireConversation(channelType, channelId, data.parent),
1059
- content,
1060
- });
1061
- emit({ requestId, data: { messageId: result.messageId } });
1062
- } catch (error) {
1063
- emit({
1064
- requestId,
1065
- error: error instanceof Error ? error.message : String(error),
1066
- });
1067
- }
1068
- return payloads;
1069
- }
1070
- case 'system:restart': {
1071
- try {
1072
- if (!ctx.requestRestart) {
1073
- emit({ requestId, error: 'Process restart is not configured' });
1074
- return payloads;
1075
- }
1076
- ctx.publishEvent?.('system:restarting', { timestamp: Date.now() });
1077
- emit({ requestId, data: { success: true, message: '正在重启...' } });
1078
- // Delay so the HTTP response can flush (parity with legacy host-api).
1079
- setTimeout(() => {
1080
- void Promise.resolve(ctx.requestRestart?.()).catch(() => {
1081
- /* restart is best-effort */
1082
- });
1083
- }, 500);
1084
- } catch (error) {
1085
- emit({
1086
- requestId,
1087
- error: error instanceof Error ? error.message : String(error),
1088
- });
1089
- }
1090
- return payloads;
1091
- }
1092
- default:
1093
599
  if (type.startsWith('db:')) {
1094
600
  emit({
1095
601
  requestId,
@@ -1099,81 +605,6 @@ export async function dispatchRuntimeConsoleRpc(
1099
605
  }
1100
606
  emit({ requestId, error: `Unknown RPC type: ${type || '<empty>'}` });
1101
607
  return payloads;
608
+ }
1102
609
  }
1103
610
  }
1104
-
1105
- function stringField(message: RuntimeConsoleRpcMessage, key: string): string {
1106
- const value = message[key];
1107
- return typeof value === 'string' ? value.trim() : '';
1108
- }
1109
-
1110
- function recordField(value: unknown): Record<string, unknown> | undefined {
1111
- return value && typeof value === 'object' && !Array.isArray(value)
1112
- ? value as Record<string, unknown>
1113
- : undefined;
1114
- }
1115
-
1116
- function positiveInteger(value: unknown, fallback: number): number {
1117
- const parsed = Number(value ?? fallback);
1118
- return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
1119
- }
1120
-
1121
- function errorMessage(error: unknown): string {
1122
- return error instanceof Error ? error.message : String(error);
1123
- }
1124
-
1125
- function errorReply(
1126
- payloads: RuntimeConsoleRpcReply[],
1127
- requestId: number | string | undefined,
1128
- error: string,
1129
- ): RuntimeConsoleRpcReply[] {
1130
- payloads.push({ requestId, error });
1131
- return payloads;
1132
- }
1133
-
1134
- /** HTTP paths under apiBase that demo scope may call (ADR 0016). */
1135
- export function isDemoHttpAllowed(method: string, pathname: string, apiBase: string): boolean {
1136
- const normalizedBase = apiBase.replace(/\/$/u, '') || '/api';
1137
- const m = method.toUpperCase();
1138
- // Each inference handler resolves a static authenticated personal binding before reading or mutating anything.
1139
- if (m === 'GET' && ['', '/owners', '/export', '/mutation', '/sessions', '/session'].some(path => pathname === `${normalizedBase}/agent/inference${path}`)) return true;
1140
- if (m === 'PUT' && pathname === `${normalizedBase}/agent/inference/session`) return true;
1141
- if (m === 'POST' && ['/plan', '/apply', '/key/prepare', '/discover', '/profile-schema'].some(path => pathname === `${normalizedBase}/agent/inference${path}`)) return true;
1142
- if (m === 'GET' && pathname === `${normalizedBase}/events`) return true;
1143
- if (m === 'GET' && pathname === `${normalizedBase}/events/history`) return true;
1144
- if (m === 'GET' && DEMO_INTROSPECTION_PATHS.has(pathname.slice(normalizedBase.length))) return true;
1145
- if (m === 'POST' && pathname === `${normalizedBase}/console/request`) return true;
1146
- // Read-only dashboard / plugin inspection routes (legacy host-api parity).
1147
- if (m === 'GET' && pathname === `${normalizedBase}/system/status`) return true;
1148
- if (m === 'GET' && pathname === `${normalizedBase}/stats`) return true;
1149
- if (m === 'GET' && (pathname === `${normalizedBase}/plugins`
1150
- || pathname.startsWith(`${normalizedBase}/plugins/`))) return true;
1151
- return false;
1152
- }
1153
-
1154
- function configurationSaveMessage(result: { restartRequired: boolean; revision?: string }
1155
- & import('@zhin.js/console-protocol').ConsoleConfigApplicationStatus): string {
1156
- if (result.applicationError === 'superseded') return '配置已保存,但该次生效申请已被替代';
1157
- if (result.applicationError === 'invalid') return '配置已保存,但配置校验失败,未生效';
1158
- if (result.applicationError) return '配置已保存,生效结果未确认';
1159
- if (result.application) {
1160
- if (result.revision && result.application.sourceRevision !== result.revision) return '配置已保存,但已被较新修订替代';
1161
- if (result.application.groups.some(group => group.status === 'unknown')) return '配置已保存,生效结果未确认';
1162
- if (result.application.groups.some(group => group.status === 'superseded')) return '配置已保存,但该次生效申请已被替代';
1163
- if (result.application.groups.some(group => ['failed', 'blocked'].includes(group.status))) return '配置已保存,部分配置未生效';
1164
- if (result.restartRequired) return '配置已保存,部分配置待重启生效';
1165
- return '配置已保存并已生效';
1166
- }
1167
- return result.restartRequired ? '配置已保存,需重启进程才能生效' : '配置已保存';
1168
- }
1169
-
1170
- const DEMO_INTROSPECTION_PATHS: ReadonlySet<string> = new Set([
1171
- '/introspection/commands',
1172
- '/introspection/middlewares',
1173
- '/introspection/components',
1174
- '/introspection/endpoints',
1175
- '/introspection/bindings',
1176
- '/introspection/tools',
1177
- '/introspection/prompt-sections',
1178
- '/introspection/mcp',
1179
- ]);