@southwind-ai/modules 0.1.1 → 0.1.3
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/index.ts +2 -0
- package/package.json +2 -2
- package/src/ai-network-policy.ts +50 -0
- package/src/ai-operation-composition.ts +275 -0
- package/src/catalog-validation.ts +19 -2
- package/src/composition.ts +22 -37
- package/src/implementation-bindings.ts +70 -0
- package/src/manifest.ts +95 -2
- package/src/system-agent-tools.ts +53 -1
- package/src/system-api-permissions.ts +9 -0
- package/src/system-api-watermark.ts +26 -0
- package/src/system-audit-actions.ts +5 -0
- package/src/system-features.ts +6 -1
- package/src/system-module-catalog.ts +27 -3
- package/src/system-modules.ts +9 -1
- package/src/system-permissions.ts +2 -0
- package/src/tool-composition.ts +86 -0
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@southwind-ai/modules",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"!src/**/*.test.ts"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@southwind-ai/shared": "0.1.
|
|
15
|
+
"@southwind-ai/shared": "0.1.2"
|
|
16
16
|
},
|
|
17
17
|
"exports": {
|
|
18
18
|
".": "./index.ts"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AiProviderEgressPolicy,
|
|
3
|
+
AiToolNetworkPolicy,
|
|
4
|
+
ModuleAiOperationDefinition,
|
|
5
|
+
} from "./manifest.js";
|
|
6
|
+
|
|
7
|
+
const providerForbidden: AiProviderEgressPolicy = Object.freeze({
|
|
8
|
+
mode: "forbidden",
|
|
9
|
+
});
|
|
10
|
+
const toolNetworkForbidden: AiToolNetworkPolicy = Object.freeze({
|
|
11
|
+
mode: "forbidden",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export function resolveProviderEgress(
|
|
15
|
+
operation: ModuleAiOperationDefinition,
|
|
16
|
+
): AiProviderEgressPolicy {
|
|
17
|
+
return operation.providerEgress ?? providerForbidden;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveToolNetworkPolicy(
|
|
21
|
+
operation: ModuleAiOperationDefinition,
|
|
22
|
+
): AiToolNetworkPolicy {
|
|
23
|
+
return operation.toolNetworkPolicy ?? toolNetworkForbidden;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function validateAiNetworkPolicy(
|
|
27
|
+
operation: ModuleAiOperationDefinition,
|
|
28
|
+
): void {
|
|
29
|
+
const provider = resolveProviderEgress(operation);
|
|
30
|
+
if (provider.mode === "configured-endpoint") {
|
|
31
|
+
if (!provider.deploymentKey.trim()) {
|
|
32
|
+
throw new Error(`${operation.key} Provider Deployment Key 不能为空`);
|
|
33
|
+
}
|
|
34
|
+
} else if (provider.mode !== "forbidden") {
|
|
35
|
+
throw new Error(`${operation.key} Provider 出网策略无效`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const tool = resolveToolNetworkPolicy(operation);
|
|
39
|
+
if (tool.mode !== "forbidden") {
|
|
40
|
+
throw new Error(`${operation.key} Tool 网络策略无效`);
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
operation.publicNetworkAccess === "allowed" &&
|
|
44
|
+
operation.providerEgress === undefined
|
|
45
|
+
) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`${operation.key} 旧 publicNetworkAccess=allowed 不能安全映射,请声明 providerEgress`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModuleAiOperationDefinition,
|
|
3
|
+
ModuleManifest,
|
|
4
|
+
} from "./manifest.js";
|
|
5
|
+
import { validateAiNetworkPolicy } from "./ai-network-policy.js";
|
|
6
|
+
|
|
7
|
+
interface AiOperationCatalogs {
|
|
8
|
+
permissions: ReadonlySet<string>;
|
|
9
|
+
features: ReadonlySet<string>;
|
|
10
|
+
audits: ReadonlySet<string>;
|
|
11
|
+
tools: ReadonlySet<string>;
|
|
12
|
+
schemas: ReadonlySet<string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validateAiOperations(
|
|
16
|
+
modules: readonly ModuleManifest[],
|
|
17
|
+
catalogs: AiOperationCatalogs,
|
|
18
|
+
): ModuleAiOperationDefinition[] {
|
|
19
|
+
const operations = modules.flatMap((module) => module.aiOperations || []);
|
|
20
|
+
uniqueValues(
|
|
21
|
+
"AI Operation",
|
|
22
|
+
operations.map(({ key }) => key),
|
|
23
|
+
);
|
|
24
|
+
for (const module of modules) {
|
|
25
|
+
for (const operation of module.aiOperations || []) {
|
|
26
|
+
validateOperation(module.name, operation, catalogs);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return operations;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function validateAiOperationImplementations(
|
|
33
|
+
declarations: readonly ModuleAiOperationDefinition[],
|
|
34
|
+
implementations: readonly string[] | undefined,
|
|
35
|
+
): void {
|
|
36
|
+
if (!implementations) return;
|
|
37
|
+
const declared = new Set(declarations.map(({ key }) => key));
|
|
38
|
+
const registered = uniqueValues("AI Operation Handler", implementations);
|
|
39
|
+
for (const key of declared) {
|
|
40
|
+
if (!registered.has(key)) {
|
|
41
|
+
throw new Error(`AI Operation ${key} 缺少 Handler`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const key of registered) {
|
|
45
|
+
if (!declared.has(key)) {
|
|
46
|
+
throw new Error(`AI Operation Handler ${key} 缺少 Manifest 声明`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateOperation(
|
|
52
|
+
moduleName: string,
|
|
53
|
+
operation: ModuleAiOperationDefinition,
|
|
54
|
+
catalogs: AiOperationCatalogs,
|
|
55
|
+
): void {
|
|
56
|
+
const strings = [
|
|
57
|
+
operation.key,
|
|
58
|
+
operation.featureKey,
|
|
59
|
+
operation.permissionKey,
|
|
60
|
+
operation.auditAction,
|
|
61
|
+
operation.inputSchemaKey,
|
|
62
|
+
operation.outputSchemaKey,
|
|
63
|
+
];
|
|
64
|
+
if (strings.some((value) => !value.trim())) {
|
|
65
|
+
throw new Error("AI Operation Key 与引用字段不能为空");
|
|
66
|
+
}
|
|
67
|
+
if (!operation.key.startsWith(`${moduleName}.`)) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`AI Operation ${operation.key} 必须使用模块命名空间 ${moduleName}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
requiredReference(
|
|
73
|
+
operation.key,
|
|
74
|
+
"权限",
|
|
75
|
+
operation.permissionKey,
|
|
76
|
+
catalogs.permissions,
|
|
77
|
+
);
|
|
78
|
+
requiredReference(
|
|
79
|
+
operation.key,
|
|
80
|
+
"Feature",
|
|
81
|
+
operation.featureKey,
|
|
82
|
+
catalogs.features,
|
|
83
|
+
);
|
|
84
|
+
requiredReference(
|
|
85
|
+
operation.key,
|
|
86
|
+
"审计动作",
|
|
87
|
+
operation.auditAction,
|
|
88
|
+
catalogs.audits,
|
|
89
|
+
);
|
|
90
|
+
requiredReference(
|
|
91
|
+
operation.key,
|
|
92
|
+
" JSON Schema",
|
|
93
|
+
operation.inputSchemaKey,
|
|
94
|
+
catalogs.schemas,
|
|
95
|
+
);
|
|
96
|
+
requiredReference(
|
|
97
|
+
operation.key,
|
|
98
|
+
" JSON Schema",
|
|
99
|
+
operation.outputSchemaKey,
|
|
100
|
+
catalogs.schemas,
|
|
101
|
+
);
|
|
102
|
+
validateCapabilities(operation);
|
|
103
|
+
validateTools(operation, catalogs.tools);
|
|
104
|
+
validateAiNetworkPolicy(operation);
|
|
105
|
+
validateProviderDataPolicy(operation);
|
|
106
|
+
validateLimits(operation);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const providerDataFields = new Set([
|
|
110
|
+
"instructions",
|
|
111
|
+
"user-prompt",
|
|
112
|
+
"tool-definitions",
|
|
113
|
+
"provider-output",
|
|
114
|
+
"tool-results",
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
export function validateProviderDataPolicy(
|
|
118
|
+
operation: ModuleAiOperationDefinition,
|
|
119
|
+
): void {
|
|
120
|
+
const policy = operation.providerDataPolicy;
|
|
121
|
+
if (operation.providerEgress?.mode !== "configured-endpoint") {
|
|
122
|
+
if (policy) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`${operation.key} 禁止 Provider 出网时不能声明字段发送策略`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!policy?.inputFields.length || !policy.envelopeFields.length) {
|
|
130
|
+
throw new Error(`${operation.key} Provider 字段发送 allowlist 缺失`);
|
|
131
|
+
}
|
|
132
|
+
validateInputFields(operation.key, policy.inputFields);
|
|
133
|
+
const fields = new Set<string>();
|
|
134
|
+
for (const rule of policy.envelopeFields) {
|
|
135
|
+
if (!providerDataFields.has(rule.field)) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`${operation.key} Provider 字段发送 allowlist 含未知字段`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (fields.has(rule.field)) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`${operation.key} Provider 字段发送规则重复:${rule.field}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
fields.add(rule.field);
|
|
146
|
+
if (!["public", "internal", "sensitive"].includes(rule.classification)) {
|
|
147
|
+
throw new Error(`${operation.key} Provider 字段分类无效:${rule.field}`);
|
|
148
|
+
}
|
|
149
|
+
if (!["reject", "redact"].includes(rule.dlpAction)) {
|
|
150
|
+
throw new Error(`${operation.key} Provider DLP 动作无效:${rule.field}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const field of ["instructions", "user-prompt"]) {
|
|
154
|
+
if (!fields.has(field)) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`${operation.key} Provider 字段发送 allowlist 缺少 ${field}`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (operation.allowedToolKeys.length) {
|
|
161
|
+
for (const field of [
|
|
162
|
+
"tool-definitions",
|
|
163
|
+
"provider-output",
|
|
164
|
+
"tool-results",
|
|
165
|
+
]) {
|
|
166
|
+
if (!fields.has(field)) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`${operation.key} 使用 Tool 时 Provider 字段发送 allowlist 缺少 ${field}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function validateInputFields(
|
|
176
|
+
operationKey: string,
|
|
177
|
+
fields: NonNullable<
|
|
178
|
+
ModuleAiOperationDefinition["providerDataPolicy"]
|
|
179
|
+
>["inputFields"],
|
|
180
|
+
): void {
|
|
181
|
+
const pointers = new Set<string>();
|
|
182
|
+
for (const rule of fields) {
|
|
183
|
+
const segments = rule.jsonPointer
|
|
184
|
+
.slice(1)
|
|
185
|
+
.split("/")
|
|
186
|
+
.map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"));
|
|
187
|
+
if (
|
|
188
|
+
!rule.jsonPointer.startsWith("/") ||
|
|
189
|
+
rule.jsonPointer === "/" ||
|
|
190
|
+
rule.jsonPointer.endsWith("/") ||
|
|
191
|
+
rule.jsonPointer.includes("//") ||
|
|
192
|
+
/~(?:[^01]|$)/u.test(rule.jsonPointer) ||
|
|
193
|
+
segments.some((segment) =>
|
|
194
|
+
["__proto__", "prototype", "constructor"].includes(segment),
|
|
195
|
+
)
|
|
196
|
+
) {
|
|
197
|
+
throw new Error(`${operationKey} Provider 输入字段 JSON Pointer 无效`);
|
|
198
|
+
}
|
|
199
|
+
if (
|
|
200
|
+
[...pointers].some(
|
|
201
|
+
(pointer) =>
|
|
202
|
+
pointer.startsWith(`${rule.jsonPointer}/`) ||
|
|
203
|
+
rule.jsonPointer.startsWith(`${pointer}/`),
|
|
204
|
+
)
|
|
205
|
+
) {
|
|
206
|
+
throw new Error(`${operationKey} Provider 输入字段规则不能父子重叠`);
|
|
207
|
+
}
|
|
208
|
+
if (pointers.has(rule.jsonPointer)) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`${operationKey} Provider 输入字段规则重复:${rule.jsonPointer}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
pointers.add(rule.jsonPointer);
|
|
214
|
+
if (!["public", "internal", "sensitive"].includes(rule.classification)) {
|
|
215
|
+
throw new Error(`${operationKey} Provider 输入字段分类无效`);
|
|
216
|
+
}
|
|
217
|
+
if (!["reject", "redact"].includes(rule.dlpAction)) {
|
|
218
|
+
throw new Error(`${operationKey} Provider 输入字段 DLP 动作无效`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function validateCapabilities(operation: ModuleAiOperationDefinition): void {
|
|
224
|
+
if (!operation.requiredCapabilities.length) {
|
|
225
|
+
throw new Error(`${operation.key} 至少需要一个 capability`);
|
|
226
|
+
}
|
|
227
|
+
validateNonEmptyValues(
|
|
228
|
+
`${operation.key} capability`,
|
|
229
|
+
operation.requiredCapabilities,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function validateTools(
|
|
234
|
+
operation: ModuleAiOperationDefinition,
|
|
235
|
+
tools: ReadonlySet<string>,
|
|
236
|
+
): void {
|
|
237
|
+
validateNonEmptyValues(`${operation.key} Tool`, operation.allowedToolKeys);
|
|
238
|
+
for (const toolKey of operation.allowedToolKeys) {
|
|
239
|
+
requiredReference(operation.key, "Agent Tool", toolKey, tools);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function validateLimits(operation: ModuleAiOperationDefinition): void {
|
|
244
|
+
const values = Object.values(operation.limits);
|
|
245
|
+
if (values.some((value) => !Number.isSafeInteger(value) || value <= 0)) {
|
|
246
|
+
throw new Error(`${operation.key} 运行与输入输出上限必须是正整数`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function validateNonEmptyValues(label: string, values: readonly string[]) {
|
|
251
|
+
if (values.some((value) => !value.trim())) {
|
|
252
|
+
throw new Error(`${label} 不能为空`);
|
|
253
|
+
}
|
|
254
|
+
uniqueValues(label, values);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function requiredReference(
|
|
258
|
+
operationKey: string,
|
|
259
|
+
label: string,
|
|
260
|
+
key: string,
|
|
261
|
+
catalog: ReadonlySet<string>,
|
|
262
|
+
): void {
|
|
263
|
+
if (!catalog.has(key)) {
|
|
264
|
+
throw new Error(`${operationKey} 引用了未注册${label}:${key}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function uniqueValues(label: string, values: readonly string[]): Set<string> {
|
|
269
|
+
const result = new Set<string>();
|
|
270
|
+
for (const value of values) {
|
|
271
|
+
if (result.has(value)) throw new Error(`${label} Key 重复:${value}`);
|
|
272
|
+
result.add(value);
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
@@ -2,6 +2,9 @@ import {
|
|
|
2
2
|
AUDIT_ACTION_DEFINITIONS,
|
|
3
3
|
isAuditActionDefinition,
|
|
4
4
|
LICENSE_CATALOG,
|
|
5
|
+
normalizeManifestBundles,
|
|
6
|
+
validateAndOrderBundles,
|
|
7
|
+
validateLicenseCatalog,
|
|
5
8
|
type AuditActionDefinition,
|
|
6
9
|
type FeatureFlagDefinition,
|
|
7
10
|
type LicenseCatalogDefinition,
|
|
@@ -15,15 +18,29 @@ export interface PlatformCapabilityCatalogs {
|
|
|
15
18
|
|
|
16
19
|
export function validateCapabilityCatalogs(
|
|
17
20
|
modules: readonly ModuleManifest[],
|
|
21
|
+
licenseCatalog: readonly LicenseCatalogDefinition[] = LICENSE_CATALOG,
|
|
18
22
|
): PlatformCapabilityCatalogs {
|
|
19
23
|
const auditActions = composeAuditActionCatalog(modules);
|
|
20
|
-
|
|
24
|
+
validateLicenseCatalog(licenseCatalog);
|
|
25
|
+
validateFeatureCatalog(modules, licenseCatalog);
|
|
26
|
+
validateMigrationBundleCatalog(modules);
|
|
21
27
|
return {
|
|
22
28
|
auditActions,
|
|
23
|
-
licenseVersions:
|
|
29
|
+
licenseVersions: licenseCatalog,
|
|
24
30
|
};
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 启动期结构校验显式声明 migrationBundle 的模块;无 bundle 的旧式模块
|
|
35
|
+
* 由 drizzle 规范迁移管理,不参与模块级 Bundle 校验。
|
|
36
|
+
*/
|
|
37
|
+
export function validateMigrationBundleCatalog(
|
|
38
|
+
modules: readonly ModuleManifest[],
|
|
39
|
+
): void {
|
|
40
|
+
const bundled = modules.filter((module) => module.migrationBundle);
|
|
41
|
+
validateAndOrderBundles(normalizeManifestBundles(bundled));
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
export function composeAuditActionCatalog(
|
|
28
45
|
modules: readonly ModuleManifest[],
|
|
29
46
|
): AuditActionDefinition[] {
|
package/src/composition.ts
CHANGED
|
@@ -3,8 +3,11 @@ import type {
|
|
|
3
3
|
ModuleImplementationBindings,
|
|
4
4
|
ModuleManifest,
|
|
5
5
|
} from "./manifest.js";
|
|
6
|
+
import { validateAiOperations } from "./ai-operation-composition.js";
|
|
7
|
+
import { validateImplementationBindings } from "./implementation-bindings.js";
|
|
6
8
|
import { composeModuleMenus } from "./menu-composition.js";
|
|
7
9
|
import { validateCapabilityCatalogs } from "./catalog-validation.js";
|
|
10
|
+
import { validateToolContracts } from "./tool-composition.js";
|
|
8
11
|
|
|
9
12
|
export interface ValidatedModuleComposition {
|
|
10
13
|
modules: readonly ModuleManifest[];
|
|
@@ -33,6 +36,7 @@ export function validateModuleComposition(
|
|
|
33
36
|
modules.flatMap((module) => module.features.map(({ key }) => key)),
|
|
34
37
|
);
|
|
35
38
|
const catalogs = validateCapabilityCatalogs(modules);
|
|
39
|
+
const jsonSchemas = validateToolContracts(modules);
|
|
36
40
|
const audits = new Set(catalogs.auditActions.map(({ key }) => key));
|
|
37
41
|
const menus = composeModuleMenus(modules).flatMap((menu) =>
|
|
38
42
|
flattenMenus([menu]),
|
|
@@ -58,10 +62,17 @@ export function validateModuleComposition(
|
|
|
58
62
|
tasks.map(({ key }) => key),
|
|
59
63
|
);
|
|
60
64
|
const tools = modules.flatMap((module) => module.tools);
|
|
61
|
-
unique(
|
|
65
|
+
const toolKeys = unique(
|
|
62
66
|
"Agent Tool",
|
|
63
67
|
tools.map(({ key }) => key),
|
|
64
68
|
);
|
|
69
|
+
const aiOperations = validateAiOperations(modules, {
|
|
70
|
+
permissions,
|
|
71
|
+
features,
|
|
72
|
+
audits,
|
|
73
|
+
tools: toolKeys,
|
|
74
|
+
schemas: new Set(jsonSchemas.keys()),
|
|
75
|
+
});
|
|
65
76
|
const providers = modules.flatMap((module) => module.providers);
|
|
66
77
|
unique(
|
|
67
78
|
"Provider",
|
|
@@ -92,30 +103,16 @@ export function validateModuleComposition(
|
|
|
92
103
|
}
|
|
93
104
|
validateMenuRoutes(menus, adminRoutes);
|
|
94
105
|
validateSchemaMigrations(schemas);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
validateImplementations(
|
|
106
|
-
"定时任务",
|
|
107
|
-
tasks.map(({ key }) => key),
|
|
108
|
-
implementations.tasks,
|
|
109
|
-
);
|
|
110
|
-
validateImplementations(
|
|
111
|
-
"Agent Tool",
|
|
112
|
-
tools.map(({ key }) => key),
|
|
113
|
-
implementations.tools,
|
|
114
|
-
);
|
|
115
|
-
validateImplementations(
|
|
116
|
-
"Provider",
|
|
117
|
-
providers.map(({ key }) => key),
|
|
118
|
-
implementations.providers,
|
|
106
|
+
validateImplementationBindings(
|
|
107
|
+
{
|
|
108
|
+
adminRoutes: adminRoutes.map(({ key }) => key),
|
|
109
|
+
apiRoutes: apiRoutes.map(apiRouteKey),
|
|
110
|
+
tasks: tasks.map(({ key }) => key),
|
|
111
|
+
tools: tools.map(({ key }) => key),
|
|
112
|
+
providers: providers.map(({ key }) => key),
|
|
113
|
+
},
|
|
114
|
+
aiOperations,
|
|
115
|
+
implementations,
|
|
119
116
|
);
|
|
120
117
|
|
|
121
118
|
return { modules, moduleOrder };
|
|
@@ -318,18 +315,6 @@ function validateSchemaMigrations(
|
|
|
318
315
|
}
|
|
319
316
|
}
|
|
320
317
|
|
|
321
|
-
function validateImplementations(
|
|
322
|
-
label: string,
|
|
323
|
-
declarations: readonly string[],
|
|
324
|
-
implementations: readonly string[] | undefined,
|
|
325
|
-
): void {
|
|
326
|
-
if (!implementations) return;
|
|
327
|
-
const registered = unique(`${label} Handler`, implementations);
|
|
328
|
-
for (const key of declarations) {
|
|
329
|
-
if (!registered.has(key)) throw new Error(`${label} ${key} 缺少 Handler`);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
318
|
function unique(label: string, values: readonly string[]): Set<string> {
|
|
334
319
|
const result = new Set<string>();
|
|
335
320
|
for (const value of values) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { validateAiOperationImplementations } from "./ai-operation-composition.js";
|
|
2
|
+
import type {
|
|
3
|
+
ModuleAiOperationDefinition,
|
|
4
|
+
ModuleImplementationBindings,
|
|
5
|
+
} from "./manifest.js";
|
|
6
|
+
|
|
7
|
+
interface ModuleDeclarationKeys {
|
|
8
|
+
adminRoutes: readonly string[];
|
|
9
|
+
apiRoutes: readonly string[];
|
|
10
|
+
tasks: readonly string[];
|
|
11
|
+
tools: readonly string[];
|
|
12
|
+
providers: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validateImplementationBindings(
|
|
16
|
+
declarations: ModuleDeclarationKeys,
|
|
17
|
+
aiOperations: readonly ModuleAiOperationDefinition[],
|
|
18
|
+
implementations: ModuleImplementationBindings,
|
|
19
|
+
): void {
|
|
20
|
+
validateImplementations(
|
|
21
|
+
"Admin Route",
|
|
22
|
+
declarations.adminRoutes,
|
|
23
|
+
implementations.adminRoutes,
|
|
24
|
+
);
|
|
25
|
+
validateImplementations(
|
|
26
|
+
"API Route",
|
|
27
|
+
declarations.apiRoutes,
|
|
28
|
+
implementations.apiRoutes,
|
|
29
|
+
);
|
|
30
|
+
validateImplementations(
|
|
31
|
+
"定时任务",
|
|
32
|
+
declarations.tasks,
|
|
33
|
+
implementations.tasks,
|
|
34
|
+
);
|
|
35
|
+
validateImplementations(
|
|
36
|
+
"Agent Tool",
|
|
37
|
+
declarations.tools,
|
|
38
|
+
implementations.tools,
|
|
39
|
+
);
|
|
40
|
+
validateAiOperationImplementations(
|
|
41
|
+
aiOperations,
|
|
42
|
+
implementations.aiOperations,
|
|
43
|
+
);
|
|
44
|
+
validateImplementations(
|
|
45
|
+
"Provider",
|
|
46
|
+
declarations.providers,
|
|
47
|
+
implementations.providers,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateImplementations(
|
|
52
|
+
label: string,
|
|
53
|
+
declarations: readonly string[],
|
|
54
|
+
implementations: readonly string[] | undefined,
|
|
55
|
+
): void {
|
|
56
|
+
if (!implementations) return;
|
|
57
|
+
const registered = unique(`${label} Handler`, implementations);
|
|
58
|
+
for (const key of declarations) {
|
|
59
|
+
if (!registered.has(key)) throw new Error(`${label} ${key} 缺少 Handler`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function unique(label: string, values: readonly string[]): Set<string> {
|
|
64
|
+
const result = new Set<string>();
|
|
65
|
+
for (const value of values) {
|
|
66
|
+
if (result.has(value)) throw new Error(`${label} Key 重复:${value}`);
|
|
67
|
+
result.add(value);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
package/src/manifest.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
AuditActionType,
|
|
5
5
|
DataGovernancePolicyDefinition,
|
|
6
6
|
FeatureFlagDefinition,
|
|
7
|
+
JsonObject,
|
|
7
8
|
MigrationBundle,
|
|
8
9
|
MigrationDefinition,
|
|
9
10
|
MenuNode,
|
|
@@ -11,15 +12,35 @@ import type {
|
|
|
11
12
|
SearchProviderManifestDefinition,
|
|
12
13
|
} from "@southwind-ai/shared";
|
|
13
14
|
|
|
14
|
-
export type {
|
|
15
|
+
export type {
|
|
16
|
+
MigrationDefinition,
|
|
17
|
+
MigrationDirection,
|
|
18
|
+
} from "@southwind-ai/shared";
|
|
15
19
|
|
|
16
20
|
export interface ModuleToolDefinition {
|
|
17
21
|
key: string;
|
|
18
22
|
label: string;
|
|
19
23
|
description: string;
|
|
24
|
+
inputSchemaKey: string;
|
|
25
|
+
outputSchemaKey: string;
|
|
26
|
+
operation: "query" | "command" | "background";
|
|
20
27
|
permissionKey: string;
|
|
21
|
-
featureKey
|
|
28
|
+
featureKey: string;
|
|
22
29
|
auditAction: AuditActionType;
|
|
30
|
+
scopeMode: "platform" | "request";
|
|
31
|
+
dataClassification: "public" | "internal" | "sensitive";
|
|
32
|
+
idempotency: "none" | "supported" | "required";
|
|
33
|
+
approval: "none" | "required";
|
|
34
|
+
limits: {
|
|
35
|
+
maxDurationSeconds: number;
|
|
36
|
+
maxInputBytes: number;
|
|
37
|
+
maxOutputBytes: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ModuleJsonSchemaDefinition {
|
|
42
|
+
key: string;
|
|
43
|
+
schema: JsonObject;
|
|
23
44
|
}
|
|
24
45
|
|
|
25
46
|
export type AdminRouteSurface = "sidebar" | "account" | "hidden";
|
|
@@ -59,6 +80,75 @@ export interface ModuleScheduledTaskDefinition {
|
|
|
59
80
|
retryDelaySeconds: number;
|
|
60
81
|
}
|
|
61
82
|
|
|
83
|
+
export type AiOperationKind = "agent" | "document" | "speech";
|
|
84
|
+
export type AiOperationDataClassification = "public" | "internal" | "sensitive";
|
|
85
|
+
export type AiOperationExecution =
|
|
86
|
+
| "interactive"
|
|
87
|
+
| "streaming"
|
|
88
|
+
| "background"
|
|
89
|
+
| "live";
|
|
90
|
+
export type AiOperationPublicNetworkAccess = "forbidden" | "allowed";
|
|
91
|
+
export type AiProviderEgressPolicy =
|
|
92
|
+
| { mode: "forbidden" }
|
|
93
|
+
| {
|
|
94
|
+
mode: "configured-endpoint";
|
|
95
|
+
deploymentKey: string;
|
|
96
|
+
};
|
|
97
|
+
export interface AiToolNetworkPolicy {
|
|
98
|
+
mode: "forbidden";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type AiProviderDataField =
|
|
102
|
+
| "instructions"
|
|
103
|
+
| "user-prompt"
|
|
104
|
+
| "tool-definitions"
|
|
105
|
+
| "provider-output"
|
|
106
|
+
| "tool-results";
|
|
107
|
+
export type AiProviderDlpAction = "reject" | "redact";
|
|
108
|
+
|
|
109
|
+
export interface AiProviderFieldPolicy {
|
|
110
|
+
field: AiProviderDataField;
|
|
111
|
+
classification: AiOperationDataClassification;
|
|
112
|
+
dlpAction: AiProviderDlpAction;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface AiProviderInputFieldPolicy {
|
|
116
|
+
jsonPointer: string;
|
|
117
|
+
classification: AiOperationDataClassification;
|
|
118
|
+
dlpAction: AiProviderDlpAction;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface AiProviderDataPolicy {
|
|
122
|
+
inputFields: readonly AiProviderInputFieldPolicy[];
|
|
123
|
+
envelopeFields: readonly AiProviderFieldPolicy[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface AiOperationLimits {
|
|
127
|
+
maxDurationSeconds: number;
|
|
128
|
+
maxInputBytes: number;
|
|
129
|
+
maxOutputBytes: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ModuleAiOperationDefinition {
|
|
133
|
+
key: string;
|
|
134
|
+
kind: AiOperationKind;
|
|
135
|
+
featureKey: string;
|
|
136
|
+
permissionKey: string;
|
|
137
|
+
auditAction: AuditActionType;
|
|
138
|
+
inputSchemaKey: string;
|
|
139
|
+
outputSchemaKey: string;
|
|
140
|
+
requiredCapabilities: readonly string[];
|
|
141
|
+
allowedToolKeys: readonly string[];
|
|
142
|
+
dataClassification: AiOperationDataClassification;
|
|
143
|
+
execution: AiOperationExecution;
|
|
144
|
+
/** @deprecated 使用 providerEgress 与 toolNetworkPolicy 拆分两类网络边界。 */
|
|
145
|
+
publicNetworkAccess?: AiOperationPublicNetworkAccess;
|
|
146
|
+
providerEgress?: AiProviderEgressPolicy;
|
|
147
|
+
providerDataPolicy?: AiProviderDataPolicy;
|
|
148
|
+
toolNetworkPolicy?: AiToolNetworkPolicy;
|
|
149
|
+
limits: AiOperationLimits;
|
|
150
|
+
}
|
|
151
|
+
|
|
62
152
|
export interface ModuleManifest {
|
|
63
153
|
name: string;
|
|
64
154
|
version: string;
|
|
@@ -73,8 +163,10 @@ export interface ModuleManifest {
|
|
|
73
163
|
migrations: MigrationDefinition[];
|
|
74
164
|
migrationBundle?: MigrationBundle;
|
|
75
165
|
schemaExports: ModuleSchemaExportDefinition[];
|
|
166
|
+
jsonSchemas?: ModuleJsonSchemaDefinition[];
|
|
76
167
|
tasks: ModuleScheduledTaskDefinition[];
|
|
77
168
|
tools: ModuleToolDefinition[];
|
|
169
|
+
aiOperations?: ModuleAiOperationDefinition[];
|
|
78
170
|
providers: Array<ModuleProviderDefinition | SearchProviderManifestDefinition>;
|
|
79
171
|
dataPolicies?: DataGovernancePolicyDefinition[];
|
|
80
172
|
auditActions: AuditActionRegistration[];
|
|
@@ -88,5 +180,6 @@ export interface ModuleImplementationBindings {
|
|
|
88
180
|
apiRoutes?: readonly string[];
|
|
89
181
|
tasks?: readonly string[];
|
|
90
182
|
tools?: readonly string[];
|
|
183
|
+
aiOperations?: readonly string[];
|
|
91
184
|
providers?: readonly string[];
|
|
92
185
|
}
|
|
@@ -1,15 +1,66 @@
|
|
|
1
|
-
import type { ModuleManifest } from "./manifest.js";
|
|
1
|
+
import type { ModuleJsonSchemaDefinition, ModuleManifest } from "./manifest.js";
|
|
2
|
+
|
|
3
|
+
export const systemAgentToolSchemas: ModuleJsonSchemaDefinition[] = [
|
|
4
|
+
{
|
|
5
|
+
key: "system.pagination.input.v1",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
page: { type: "integer", minimum: 1, default: 1 },
|
|
10
|
+
pageSize: {
|
|
11
|
+
type: "integer",
|
|
12
|
+
minimum: 1,
|
|
13
|
+
maximum: 50,
|
|
14
|
+
default: 10,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
key: "system.page.output.v1",
|
|
22
|
+
schema: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
items: { type: "array", items: { type: "object" } },
|
|
26
|
+
total: { type: "integer", minimum: 0 },
|
|
27
|
+
page: { type: "integer", minimum: 1 },
|
|
28
|
+
pageSize: { type: "integer", minimum: 1 },
|
|
29
|
+
},
|
|
30
|
+
required: ["items", "total", "page", "pageSize"],
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const queryContract = {
|
|
37
|
+
inputSchemaKey: "system.pagination.input.v1",
|
|
38
|
+
outputSchemaKey: "system.page.output.v1",
|
|
39
|
+
operation: "query" as const,
|
|
40
|
+
scopeMode: "platform" as const,
|
|
41
|
+
dataClassification: "internal" as const,
|
|
42
|
+
idempotency: "none" as const,
|
|
43
|
+
approval: "none" as const,
|
|
44
|
+
limits: {
|
|
45
|
+
maxDurationSeconds: 10,
|
|
46
|
+
maxInputBytes: 4_096,
|
|
47
|
+
maxOutputBytes: 1_048_576,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
2
50
|
|
|
3
51
|
export const systemAgentTools: ModuleManifest["tools"] = [
|
|
4
52
|
{
|
|
53
|
+
...queryContract,
|
|
5
54
|
key: "system.users.list",
|
|
6
55
|
label: "查询用户",
|
|
7
56
|
description: "分页查询平台用户账号。",
|
|
8
57
|
permissionKey: "system.user.read",
|
|
9
58
|
featureKey: "system.rbac",
|
|
10
59
|
auditAction: "agent.tool.execute",
|
|
60
|
+
scopeMode: "request",
|
|
11
61
|
},
|
|
12
62
|
{
|
|
63
|
+
...queryContract,
|
|
13
64
|
key: "system.features.list",
|
|
14
65
|
label: "查询功能开关",
|
|
15
66
|
description: "分页查询平台功能开关状态。",
|
|
@@ -18,6 +69,7 @@ export const systemAgentTools: ModuleManifest["tools"] = [
|
|
|
18
69
|
auditAction: "agent.tool.execute",
|
|
19
70
|
},
|
|
20
71
|
{
|
|
72
|
+
...queryContract,
|
|
21
73
|
key: "system.audit.list",
|
|
22
74
|
label: "查询审计日志",
|
|
23
75
|
description: "分页查询平台审计事件。",
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { ApiPermissionBinding } from "@southwind-ai/shared";
|
|
2
2
|
import { binding, resourceBindings } from "./system-api-binding.js";
|
|
3
|
+
// @windy-module system.settings begin
|
|
4
|
+
import { watermarkApiPermissionBindings } from "./system-api-watermark.js";
|
|
5
|
+
// @windy-module system.settings end
|
|
3
6
|
// @windy-module system.data-governance begin
|
|
4
7
|
import { dataGovernanceApiPermissionBindings } from "./system-api-data-governance.js";
|
|
5
8
|
// @windy-module system.data-governance end
|
|
@@ -22,6 +25,11 @@ export function systemApiPermissionBindings(): ApiPermissionBinding[] {
|
|
|
22
25
|
),
|
|
23
26
|
binding("POST", "/api/search", "system.search.read", "system.search"),
|
|
24
27
|
// @windy-module system.search end
|
|
28
|
+
// @windy-module system.agent begin
|
|
29
|
+
binding("GET", "/api/mcp", "system.agent.read", "system.agent"),
|
|
30
|
+
binding("POST", "/api/mcp", "system.agent.read", "system.agent"),
|
|
31
|
+
binding("DELETE", "/api/mcp", "system.agent.read", "system.agent"),
|
|
32
|
+
// @windy-module system.agent end
|
|
25
33
|
...resourceBindings({
|
|
26
34
|
path: "users",
|
|
27
35
|
permissionResource: "user",
|
|
@@ -109,6 +117,7 @@ export function systemApiPermissionBindings(): ApiPermissionBinding[] {
|
|
|
109
117
|
"system.settings.manage",
|
|
110
118
|
"system.settings",
|
|
111
119
|
),
|
|
120
|
+
...watermarkApiPermissionBindings(),
|
|
112
121
|
// @windy-module system.settings end
|
|
113
122
|
// @windy-module system.configuration begin
|
|
114
123
|
binding(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ApiPermissionBinding } from "@southwind-ai/shared";
|
|
2
|
+
import { binding } from "./system-api-binding.js";
|
|
3
|
+
|
|
4
|
+
export function watermarkApiPermissionBindings(): ApiPermissionBinding[] {
|
|
5
|
+
return [
|
|
6
|
+
binding(
|
|
7
|
+
"GET",
|
|
8
|
+
"/api/platform/watermark-policy",
|
|
9
|
+
"system.watermark.read",
|
|
10
|
+
undefined,
|
|
11
|
+
true,
|
|
12
|
+
),
|
|
13
|
+
binding(
|
|
14
|
+
"GET",
|
|
15
|
+
"/api/system/settings/watermark",
|
|
16
|
+
"system.watermark.read",
|
|
17
|
+
"system.watermark",
|
|
18
|
+
),
|
|
19
|
+
binding(
|
|
20
|
+
"PUT",
|
|
21
|
+
"/api/system/settings/watermark",
|
|
22
|
+
"system.watermark.manage",
|
|
23
|
+
"system.watermark",
|
|
24
|
+
),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
@@ -8,6 +8,11 @@ export const systemAuditActions: ModuleManifest["auditActions"] = [
|
|
|
8
8
|
"crud.create",
|
|
9
9
|
"crud.update",
|
|
10
10
|
"crud.delete",
|
|
11
|
+
{
|
|
12
|
+
key: "system.user-directory.query",
|
|
13
|
+
label: "查询业务用户目录",
|
|
14
|
+
description: "业务模块在平台授权与数据范围内查询启用用户最小投影。",
|
|
15
|
+
},
|
|
11
16
|
// @windy-module system.configuration begin
|
|
12
17
|
"config.update",
|
|
13
18
|
"config.rollback",
|
package/src/system-features.ts
CHANGED
|
@@ -17,6 +17,11 @@ export const systemFeatures: ModuleManifest["features"] = [
|
|
|
17
17
|
"RBAC 权限链路",
|
|
18
18
|
"用户、角色、权限点与数据范围的统一访问控制链路。",
|
|
19
19
|
),
|
|
20
|
+
feature(
|
|
21
|
+
"system.user-directory",
|
|
22
|
+
"业务用户目录",
|
|
23
|
+
"向获授权业务模块提供受数据范围约束的启用用户最小只读投影。",
|
|
24
|
+
),
|
|
20
25
|
feature(
|
|
21
26
|
"system.profile",
|
|
22
27
|
"个人设置",
|
|
@@ -43,7 +48,7 @@ export const systemFeatures: ModuleManifest["features"] = [
|
|
|
43
48
|
feature(
|
|
44
49
|
"system.watermark",
|
|
45
50
|
"页面水印",
|
|
46
|
-
"
|
|
51
|
+
"由平台统一管理并覆盖 Admin 与业务页面的身份水印策略。",
|
|
47
52
|
),
|
|
48
53
|
// @windy-module system.settings end
|
|
49
54
|
// @windy-module system.license begin
|
|
@@ -27,9 +27,17 @@ export const systemModuleDefinitions = [
|
|
|
27
27
|
version: "0.1.0",
|
|
28
28
|
label: "身份与权限",
|
|
29
29
|
description: "用户、角色、部门、菜单、字典、个人资料和数据范围。",
|
|
30
|
-
resources: [
|
|
30
|
+
resources: [
|
|
31
|
+
"user",
|
|
32
|
+
"user-directory",
|
|
33
|
+
"role",
|
|
34
|
+
"department",
|
|
35
|
+
"menu",
|
|
36
|
+
"dict",
|
|
37
|
+
"profile",
|
|
38
|
+
],
|
|
31
39
|
excludedPermissionKeys: ["system.user.reveal", "system.user.export"],
|
|
32
|
-
featureKeys: ["system.rbac", "system.profile"],
|
|
40
|
+
featureKeys: ["system.rbac", "system.user-directory", "system.profile"],
|
|
33
41
|
},
|
|
34
42
|
// @windy-module system.settings begin
|
|
35
43
|
{
|
|
@@ -218,6 +226,7 @@ function createSystemCapabilityModule(
|
|
|
218
226
|
.map((feature) => ({ ...feature, module: definition.name })),
|
|
219
227
|
migrations: isCore ? aggregate.migrations : [],
|
|
220
228
|
schemaExports: isCore ? aggregate.schemaExports : [],
|
|
229
|
+
jsonSchemas: isCore ? aggregate.jsonSchemas : [],
|
|
221
230
|
tasks: aggregate.tasks.filter(ownsGuard),
|
|
222
231
|
tools: aggregate.tools.filter(ownsGuard),
|
|
223
232
|
providers: aggregate.providers.filter(ownsGuard),
|
|
@@ -225,11 +234,26 @@ function createSystemCapabilityModule(
|
|
|
225
234
|
definition.name === "system.data-governance"
|
|
226
235
|
? aggregate.dataPolicies
|
|
227
236
|
: [],
|
|
228
|
-
auditActions: isCore
|
|
237
|
+
auditActions: isCore
|
|
238
|
+
? aggregate.auditActions.filter(
|
|
239
|
+
(action) => auditActionKey(action) !== "system.user-directory.query",
|
|
240
|
+
)
|
|
241
|
+
: definition.name === "system.identity"
|
|
242
|
+
? aggregate.auditActions.filter(
|
|
243
|
+
(action) =>
|
|
244
|
+
auditActionKey(action) === "system.user-directory.query",
|
|
245
|
+
)
|
|
246
|
+
: [],
|
|
229
247
|
metadata: { kind: "platform-capability" },
|
|
230
248
|
};
|
|
231
249
|
}
|
|
232
250
|
|
|
251
|
+
function auditActionKey(
|
|
252
|
+
action: ModuleManifest["auditActions"][number],
|
|
253
|
+
): string {
|
|
254
|
+
return typeof action === "string" ? action : action.key;
|
|
255
|
+
}
|
|
256
|
+
|
|
233
257
|
function permissionResource(permissionKey: string): string {
|
|
234
258
|
const parts = permissionKey.split(".");
|
|
235
259
|
return parts.length >= 3 ? parts.slice(1, -1).join(".") : "";
|
package/src/system-modules.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { ModuleManifest } from "./manifest.js";
|
|
2
2
|
// @windy-module system.agent begin
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
systemAgentToolSchemas,
|
|
5
|
+
systemAgentTools,
|
|
6
|
+
} from "./system-agent-tools.js";
|
|
4
7
|
// @windy-module system.agent end
|
|
5
8
|
import { systemAdminRoutes } from "./system-admin-routes.js";
|
|
6
9
|
import { systemApiPermissionBindings } from "./system-api-permissions.js";
|
|
@@ -249,6 +252,11 @@ export function systemFoundationModule(): ModuleManifest {
|
|
|
249
252
|
migrationIds: ["0000..0028"],
|
|
250
253
|
},
|
|
251
254
|
],
|
|
255
|
+
jsonSchemas: [
|
|
256
|
+
// @windy-module system.agent begin
|
|
257
|
+
...systemAgentToolSchemas,
|
|
258
|
+
// @windy-module system.agent end
|
|
259
|
+
],
|
|
252
260
|
tasks: [
|
|
253
261
|
// @windy-module system.scheduler begin
|
|
254
262
|
{
|
|
@@ -3,6 +3,7 @@ import type { ModuleManifest } from "./manifest.js";
|
|
|
3
3
|
const systemResourceLabels = {
|
|
4
4
|
admin: "管理面准入",
|
|
5
5
|
user: "用户管理",
|
|
6
|
+
"user-directory": "业务用户目录",
|
|
6
7
|
role: "角色与权限",
|
|
7
8
|
department: "部门组织",
|
|
8
9
|
menu: "菜单管理",
|
|
@@ -75,6 +76,7 @@ export const systemPermissions: ModuleManifest["permissions"] = [
|
|
|
75
76
|
permission("user", "export", "governed-export"),
|
|
76
77
|
// @windy-module system.data-governance end
|
|
77
78
|
permission("user", "manage"),
|
|
79
|
+
permission("user-directory", "read"),
|
|
78
80
|
permission("role", "read"),
|
|
79
81
|
permission("role", "manage"),
|
|
80
82
|
permission("department", "read"),
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModuleJsonSchemaDefinition,
|
|
3
|
+
ModuleManifest,
|
|
4
|
+
ModuleToolDefinition,
|
|
5
|
+
} from "./manifest.js";
|
|
6
|
+
|
|
7
|
+
export function validateToolContracts(
|
|
8
|
+
modules: readonly ModuleManifest[],
|
|
9
|
+
): ReadonlyMap<string, ModuleJsonSchemaDefinition> {
|
|
10
|
+
const schemas = new Map<string, ModuleJsonSchemaDefinition>();
|
|
11
|
+
for (const module of modules) {
|
|
12
|
+
for (const definition of module.jsonSchemas ?? []) {
|
|
13
|
+
validateSchema(module.name, definition);
|
|
14
|
+
if (schemas.has(definition.key)) {
|
|
15
|
+
throw new Error(`JSON Schema Key 重复:${definition.key}`);
|
|
16
|
+
}
|
|
17
|
+
schemas.set(definition.key, definition);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (const module of modules) {
|
|
21
|
+
for (const tool of module.tools) {
|
|
22
|
+
validateTool(module.name, tool, schemas);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return schemas;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateSchema(
|
|
29
|
+
moduleName: string,
|
|
30
|
+
definition: ModuleJsonSchemaDefinition,
|
|
31
|
+
): void {
|
|
32
|
+
if (!definition.key.trim()) throw new Error("JSON Schema Key 不能为空");
|
|
33
|
+
if (!definition.key.startsWith(`${moduleName}.`)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`JSON Schema ${definition.key} 必须使用模块命名空间 ${moduleName}`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (definition.schema.type !== "object") {
|
|
39
|
+
throw new Error(`JSON Schema ${definition.key} 顶层类型必须是 object`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateTool(
|
|
44
|
+
moduleName: string,
|
|
45
|
+
tool: ModuleToolDefinition,
|
|
46
|
+
schemas: ReadonlyMap<string, ModuleJsonSchemaDefinition>,
|
|
47
|
+
): void {
|
|
48
|
+
const fields: Array<string | undefined> = [
|
|
49
|
+
tool.key,
|
|
50
|
+
tool.label,
|
|
51
|
+
tool.description,
|
|
52
|
+
tool.inputSchemaKey,
|
|
53
|
+
tool.outputSchemaKey,
|
|
54
|
+
tool.featureKey,
|
|
55
|
+
tool.permissionKey,
|
|
56
|
+
tool.auditAction,
|
|
57
|
+
];
|
|
58
|
+
if (fields.some((value) => !value?.trim())) {
|
|
59
|
+
throw new Error("Agent Tool Key、展示信息与引用字段不能为空");
|
|
60
|
+
}
|
|
61
|
+
const rootNamespace = moduleName.split(".")[0]!;
|
|
62
|
+
if (!tool.key.startsWith(`${rootNamespace}.`)) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Agent Tool ${tool.key} 必须使用模块根命名空间 ${rootNamespace}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
requireSchema(tool.key, tool.inputSchemaKey, schemas);
|
|
68
|
+
requireSchema(tool.key, tool.outputSchemaKey, schemas);
|
|
69
|
+
if (tool.operation === "query" && tool.approval !== "none") {
|
|
70
|
+
throw new Error(`${tool.key} 查询能力不能要求执行审批`);
|
|
71
|
+
}
|
|
72
|
+
const limits = Object.values(tool.limits);
|
|
73
|
+
if (limits.some((value) => !Number.isSafeInteger(value) || value <= 0)) {
|
|
74
|
+
throw new Error(`${tool.key} 运行与输入输出上限必须是正整数`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function requireSchema(
|
|
79
|
+
toolKey: string,
|
|
80
|
+
schemaKey: string,
|
|
81
|
+
schemas: ReadonlyMap<string, ModuleJsonSchemaDefinition>,
|
|
82
|
+
): void {
|
|
83
|
+
if (!schemas.has(schemaKey)) {
|
|
84
|
+
throw new Error(`${toolKey} 引用了未注册 JSON Schema:${schemaKey}`);
|
|
85
|
+
}
|
|
86
|
+
}
|