@southwind-ai/modules 0.1.2 → 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 +1 -1
- package/src/ai-network-policy.ts +50 -0
- package/src/ai-operation-composition.ts +130 -0
- package/src/composition.ts +3 -0
- package/src/manifest.ts +59 -2
- package/src/system-agent-tools.ts +53 -1
- package/src/system-api-permissions.ts +5 -0
- package/src/system-audit-actions.ts +5 -0
- package/src/system-features.ts +5 -0
- 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
|
@@ -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
|
+
}
|
|
@@ -2,12 +2,14 @@ import type {
|
|
|
2
2
|
ModuleAiOperationDefinition,
|
|
3
3
|
ModuleManifest,
|
|
4
4
|
} from "./manifest.js";
|
|
5
|
+
import { validateAiNetworkPolicy } from "./ai-network-policy.js";
|
|
5
6
|
|
|
6
7
|
interface AiOperationCatalogs {
|
|
7
8
|
permissions: ReadonlySet<string>;
|
|
8
9
|
features: ReadonlySet<string>;
|
|
9
10
|
audits: ReadonlySet<string>;
|
|
10
11
|
tools: ReadonlySet<string>;
|
|
12
|
+
schemas: ReadonlySet<string>;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function validateAiOperations(
|
|
@@ -85,11 +87,139 @@ function validateOperation(
|
|
|
85
87
|
operation.auditAction,
|
|
86
88
|
catalogs.audits,
|
|
87
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
|
+
);
|
|
88
102
|
validateCapabilities(operation);
|
|
89
103
|
validateTools(operation, catalogs.tools);
|
|
104
|
+
validateAiNetworkPolicy(operation);
|
|
105
|
+
validateProviderDataPolicy(operation);
|
|
90
106
|
validateLimits(operation);
|
|
91
107
|
}
|
|
92
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
|
+
|
|
93
223
|
function validateCapabilities(operation: ModuleAiOperationDefinition): void {
|
|
94
224
|
if (!operation.requiredCapabilities.length) {
|
|
95
225
|
throw new Error(`${operation.key} 至少需要一个 capability`);
|
package/src/composition.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { validateAiOperations } from "./ai-operation-composition.js";
|
|
|
7
7
|
import { validateImplementationBindings } from "./implementation-bindings.js";
|
|
8
8
|
import { composeModuleMenus } from "./menu-composition.js";
|
|
9
9
|
import { validateCapabilityCatalogs } from "./catalog-validation.js";
|
|
10
|
+
import { validateToolContracts } from "./tool-composition.js";
|
|
10
11
|
|
|
11
12
|
export interface ValidatedModuleComposition {
|
|
12
13
|
modules: readonly ModuleManifest[];
|
|
@@ -35,6 +36,7 @@ export function validateModuleComposition(
|
|
|
35
36
|
modules.flatMap((module) => module.features.map(({ key }) => key)),
|
|
36
37
|
);
|
|
37
38
|
const catalogs = validateCapabilityCatalogs(modules);
|
|
39
|
+
const jsonSchemas = validateToolContracts(modules);
|
|
38
40
|
const audits = new Set(catalogs.auditActions.map(({ key }) => key));
|
|
39
41
|
const menus = composeModuleMenus(modules).flatMap((menu) =>
|
|
40
42
|
flattenMenus([menu]),
|
|
@@ -69,6 +71,7 @@ export function validateModuleComposition(
|
|
|
69
71
|
features,
|
|
70
72
|
audits,
|
|
71
73
|
tools: toolKeys,
|
|
74
|
+
schemas: new Set(jsonSchemas.keys()),
|
|
72
75
|
});
|
|
73
76
|
const providers = modules.flatMap((module) => module.providers);
|
|
74
77
|
unique(
|
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,
|
|
@@ -20,9 +21,26 @@ export interface ModuleToolDefinition {
|
|
|
20
21
|
key: string;
|
|
21
22
|
label: string;
|
|
22
23
|
description: string;
|
|
24
|
+
inputSchemaKey: string;
|
|
25
|
+
outputSchemaKey: string;
|
|
26
|
+
operation: "query" | "command" | "background";
|
|
23
27
|
permissionKey: string;
|
|
24
|
-
featureKey
|
|
28
|
+
featureKey: string;
|
|
25
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;
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
export type AdminRouteSurface = "sidebar" | "account" | "hidden";
|
|
@@ -70,6 +88,40 @@ export type AiOperationExecution =
|
|
|
70
88
|
| "background"
|
|
71
89
|
| "live";
|
|
72
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
|
+
}
|
|
73
125
|
|
|
74
126
|
export interface AiOperationLimits {
|
|
75
127
|
maxDurationSeconds: number;
|
|
@@ -89,7 +141,11 @@ export interface ModuleAiOperationDefinition {
|
|
|
89
141
|
allowedToolKeys: readonly string[];
|
|
90
142
|
dataClassification: AiOperationDataClassification;
|
|
91
143
|
execution: AiOperationExecution;
|
|
92
|
-
|
|
144
|
+
/** @deprecated 使用 providerEgress 与 toolNetworkPolicy 拆分两类网络边界。 */
|
|
145
|
+
publicNetworkAccess?: AiOperationPublicNetworkAccess;
|
|
146
|
+
providerEgress?: AiProviderEgressPolicy;
|
|
147
|
+
providerDataPolicy?: AiProviderDataPolicy;
|
|
148
|
+
toolNetworkPolicy?: AiToolNetworkPolicy;
|
|
93
149
|
limits: AiOperationLimits;
|
|
94
150
|
}
|
|
95
151
|
|
|
@@ -107,6 +163,7 @@ export interface ModuleManifest {
|
|
|
107
163
|
migrations: MigrationDefinition[];
|
|
108
164
|
migrationBundle?: MigrationBundle;
|
|
109
165
|
schemaExports: ModuleSchemaExportDefinition[];
|
|
166
|
+
jsonSchemas?: ModuleJsonSchemaDefinition[];
|
|
110
167
|
tasks: ModuleScheduledTaskDefinition[];
|
|
111
168
|
tools: ModuleToolDefinition[];
|
|
112
169
|
aiOperations?: ModuleAiOperationDefinition[];
|
|
@@ -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: "分页查询平台审计事件。",
|
|
@@ -25,6 +25,11 @@ export function systemApiPermissionBindings(): ApiPermissionBinding[] {
|
|
|
25
25
|
),
|
|
26
26
|
binding("POST", "/api/search", "system.search.read", "system.search"),
|
|
27
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
|
|
28
33
|
...resourceBindings({
|
|
29
34
|
path: "users",
|
|
30
35
|
permissionResource: "user",
|
|
@@ -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
|
@@ -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
|
+
}
|