create-windy 0.2.32 → 0.2.34
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/cli.js +9 -1
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +10 -1
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +11 -0
- package/template/apps/agent-server/README.md +10 -0
- package/template/apps/agent-server/src/southwind_agent_server/app.py +62 -1
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +35 -0
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +2 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +31 -3
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +22 -9
- package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +144 -0
- package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +227 -0
- package/template/apps/agent-server/src/southwind_agent_server/service.py +1 -0
- package/template/apps/agent-server/tests/test_api.py +16 -1
- package/template/apps/agent-server/tests/test_openai_provider.py +79 -0
- package/template/apps/agent-server/tests/test_provider_governance.py +215 -0
- package/template/apps/server/src/agent/provider-input-governance.test.ts +82 -0
- package/template/apps/server/src/agent/provider-input-governance.ts +140 -0
- package/template/apps/server/src/agent/remote-runtime.test.ts +22 -0
- package/template/apps/server/src/agent/remote-runtime.ts +1 -0
- package/template/apps/server/src/agent/run-contracts.ts +2 -0
- package/template/apps/server/src/agent/run-facade.test.ts +52 -2
- package/template/apps/server/src/agent/run-facade.ts +36 -2
- package/template/apps/server/src/index.ts +28 -45
- package/template/apps/server/src/module-host/initialize-contracts.ts +6 -0
- package/template/apps/server/src/module-host/initialize.ts +30 -44
- package/template/apps/server/src/module-host/request-context.ts +23 -17
- package/template/apps/server/src/module-host/resource-file-policies.test.ts +197 -0
- package/template/apps/server/src/module-host/resource-file-policies.ts +108 -0
- package/template/apps/server/src/module-host/route-declaration.ts +45 -0
- package/template/apps/server/src/module-host/route-installer.ts +27 -3
- package/template/apps/server/src/module-host/storage-port.test.ts +170 -1
- package/template/apps/server/src/module-host/storage-port.ts +134 -1
- package/template/apps/server/src/module-host/storage-scope.ts +96 -0
- package/template/apps/server/src/module-host/tool-handlers.test.ts +305 -0
- package/template/apps/server/src/module-host/tool-handlers.ts +38 -4
- package/template/apps/server/src/module-host/user-directory-composition.test.ts +126 -0
- package/template/apps/server/src/module-host/user-directory-port.test.ts +238 -0
- package/template/apps/server/src/module-host/user-directory-port.ts +143 -0
- package/template/apps/server/src/module-host.ts +4 -0
- package/template/apps/server/src/runtime-development.ts +1 -0
- package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +4 -0
- package/template/apps/server/src/settings/drizzle-watermark-repository.ts +6 -0
- package/template/apps/server/src/settings/watermark-routes.test.ts +13 -3
- package/template/apps/server/src/settings/watermark-routes.ts +41 -3
- package/template/apps/server/src/system/built-in-roles.ts +1 -0
- package/template/apps/web/src/app/auth/login-page.ts +4 -0
- package/template/apps/web/src/composables/useLoginFlow.webtest.ts +91 -0
- package/template/apps/web/src/composables/useWatermarkSettings.ts +7 -3
- package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +14 -0
- package/template/apps/web/src/composables/watermark-settings.ts +131 -9
- package/template/apps/web/src/composables/watermark-settings.webtest.ts +73 -7
- package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +3 -1
- package/template/apps/web/src/layout/GlobalWatermark.vue +24 -11
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +25 -11
- package/template/apps/web/src/pages/auth/login-page-extension.ts +9 -0
- package/template/apps/web/src/pages/auth/login-page-extension.webtest.ts +10 -0
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +102 -4
- package/template/apps/web/src/router/index.ts +2 -2
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +3 -2
- package/template/docker-compose.yml +2 -0
- package/template/docs/architecture/ai-egress.md +49 -7
- package/template/docs/architecture/ai-runtime.md +20 -1
- package/template/docs/architecture/data-scope-query-enforcement.md +4 -0
- package/template/docs/architecture/object-storage.md +22 -2
- package/template/docs/development/custom-login-page.md +90 -0
- package/template/docs/platform/agent-runtime.md +44 -1
- package/template/docs/platform/business-user-directory.md +82 -0
- package/template/docs/platform/platform-shell-settings.md +14 -9
- package/template/docs/platform/web-system-crud.md +1 -1
- package/template/packages/database/drizzle/0034_funny_mesmero.sql +5 -0
- package/template/packages/database/drizzle/meta/0034_snapshot.json +7253 -0
- package/template/packages/database/drizzle/meta/_journal.json +7 -0
- package/template/packages/database/src/schema/platform-settings.ts +13 -0
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/ai-operation-composition.test.ts +68 -0
- package/template/packages/modules/src/ai-operation-composition.ts +115 -0
- package/template/packages/modules/src/manifest.ts +26 -0
- package/template/packages/modules/src/provider-data-policy-composition.test.ts +74 -0
- package/template/packages/modules/src/system-audit-actions.ts +5 -0
- package/template/packages/modules/src/system-features.ts +5 -0
- package/template/packages/modules/src/system-module-catalog.test.ts +16 -0
- package/template/packages/modules/src/system-module-catalog.ts +26 -3
- package/template/packages/modules/src/system-permissions.ts +2 -0
- package/template/packages/server-sdk/index.ts +11 -0
- package/template/packages/server-sdk/package.json +3 -2
- package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -1
- package/template/packages/server-sdk/src/module-host.ts +8 -0
- package/template/packages/server-sdk/src/request-context.ts +2 -0
- package/template/packages/server-sdk/src/storage-port.ts +66 -0
- package/template/packages/server-sdk/src/tool-registration.ts +6 -0
- package/template/packages/server-sdk/src/user-directory-port.ts +42 -0
- package/template/packages/shared/src/platform-settings.ts +17 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { AiProviderDataPolicy } from "@southwind-ai/modules";
|
|
3
|
+
import {
|
|
4
|
+
governProviderInput,
|
|
5
|
+
ProviderInputGovernanceError,
|
|
6
|
+
} from "./provider-input-governance.js";
|
|
7
|
+
|
|
8
|
+
const policy: AiProviderDataPolicy = {
|
|
9
|
+
inputFields: [
|
|
10
|
+
{
|
|
11
|
+
jsonPointer: "/question",
|
|
12
|
+
classification: "internal",
|
|
13
|
+
dlpAction: "redact",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
jsonPointer: "/identity",
|
|
17
|
+
classification: "sensitive",
|
|
18
|
+
dlpAction: "reject",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
envelopeFields: [
|
|
22
|
+
{
|
|
23
|
+
field: "instructions",
|
|
24
|
+
classification: "internal",
|
|
25
|
+
dlpAction: "reject",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
field: "user-prompt",
|
|
29
|
+
classification: "internal",
|
|
30
|
+
dlpAction: "redact",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe("Provider 输入字段治理", () => {
|
|
36
|
+
test("只保留 JSON Pointer allowlist 并在 Handler 前脱敏", () => {
|
|
37
|
+
const governed = governProviderInput(
|
|
38
|
+
{
|
|
39
|
+
question: "联系 me@example.com 或 13800138000",
|
|
40
|
+
internalCaseId: "case-secret",
|
|
41
|
+
},
|
|
42
|
+
policy,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(governed).toEqual({
|
|
46
|
+
question: "联系 [REDACTED] 或 [REDACTED]",
|
|
47
|
+
});
|
|
48
|
+
expect(JSON.stringify(governed)).not.toContain("case-secret");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test.each([
|
|
52
|
+
"password=do-not-send",
|
|
53
|
+
"Bearer abcdefghijklmnop",
|
|
54
|
+
"11010519491231002X",
|
|
55
|
+
"6222 0201 2345 6789 012",
|
|
56
|
+
])("高风险字段按 reject 策略拒绝:%s", (identity) => {
|
|
57
|
+
expect(() =>
|
|
58
|
+
governProviderInput({ question: "ok", identity }, policy),
|
|
59
|
+
).toThrow(ProviderInputGovernanceError);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("拒绝非对象输入和非法 JSON Pointer", () => {
|
|
63
|
+
expect(() => governProviderInput("raw", policy)).toThrow("输入必须是对象");
|
|
64
|
+
expect(() =>
|
|
65
|
+
governProviderInput(
|
|
66
|
+
{ question: "ok" },
|
|
67
|
+
{
|
|
68
|
+
...policy,
|
|
69
|
+
inputFields: [{ ...policy.inputFields[0]!, jsonPointer: "/bad~2" }],
|
|
70
|
+
},
|
|
71
|
+
),
|
|
72
|
+
).toThrow("JSON Pointer 无效");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("allowlist 只读取输入自身字段,不继承原型属性", () => {
|
|
76
|
+
const inherited = Object.create({
|
|
77
|
+
question: "prototype-secret",
|
|
78
|
+
}) as Record<string, string>;
|
|
79
|
+
|
|
80
|
+
expect(governProviderInput(inherited, policy)).toEqual({});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AiProviderDataPolicy,
|
|
3
|
+
AiProviderDlpAction,
|
|
4
|
+
} from "@southwind-ai/modules";
|
|
5
|
+
import type { ModuleAiJsonValue } from "@southwind-ai/server-sdk";
|
|
6
|
+
|
|
7
|
+
export class ProviderInputGovernanceError extends Error {
|
|
8
|
+
constructor(
|
|
9
|
+
readonly code: "INVALID_INPUT" | "FORBIDDEN",
|
|
10
|
+
message: string,
|
|
11
|
+
) {
|
|
12
|
+
super(message);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const dlpPatterns = [
|
|
17
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}\b/giu,
|
|
18
|
+
/\b(?:api[_-]?key|secret|password|token)\s*[:=]\s*\S+/giu,
|
|
19
|
+
/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu,
|
|
20
|
+
/\b1[3-9]\d{9}\b/gu,
|
|
21
|
+
/\b\d{17}[\dXx]\b/gu,
|
|
22
|
+
/\b(?:\d[ -]?){16,30}\b/gu,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export function governProviderInput(
|
|
26
|
+
input: ModuleAiJsonValue,
|
|
27
|
+
policy: AiProviderDataPolicy,
|
|
28
|
+
): ModuleAiJsonValue {
|
|
29
|
+
if (!isRecord(input)) {
|
|
30
|
+
throw new ProviderInputGovernanceError(
|
|
31
|
+
"INVALID_INPUT",
|
|
32
|
+
"Provider Operation 输入必须是对象",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const governed: Record<string, ModuleAiJsonValue> = Object.create(null);
|
|
36
|
+
for (const rule of policy.inputFields) {
|
|
37
|
+
const segments = parseJsonPointer(rule.jsonPointer);
|
|
38
|
+
const result = readPath(input, segments);
|
|
39
|
+
if (!result.found) continue;
|
|
40
|
+
writePath(governed, segments, governValue(result.value, rule.dlpAction));
|
|
41
|
+
}
|
|
42
|
+
return governed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function governValue(
|
|
46
|
+
value: ModuleAiJsonValue,
|
|
47
|
+
action: AiProviderDlpAction,
|
|
48
|
+
): ModuleAiJsonValue {
|
|
49
|
+
if (typeof value === "string") {
|
|
50
|
+
if (!dlpPatterns.some((pattern) => matches(pattern, value))) return value;
|
|
51
|
+
if (action === "reject") {
|
|
52
|
+
throw new ProviderInputGovernanceError(
|
|
53
|
+
"FORBIDDEN",
|
|
54
|
+
"Provider 输入被 DLP 策略拒绝",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return dlpPatterns.reduce(
|
|
58
|
+
(result, pattern) => result.replace(pattern, "[REDACTED]"),
|
|
59
|
+
value,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
return value.map((item) => governValue(item, action));
|
|
64
|
+
}
|
|
65
|
+
if (isRecord(value)) {
|
|
66
|
+
return Object.fromEntries(
|
|
67
|
+
Object.entries(value).map(([key, item]) => [
|
|
68
|
+
key,
|
|
69
|
+
governValue(item, action),
|
|
70
|
+
]),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseJsonPointer(pointer: string): string[] {
|
|
77
|
+
return pointer
|
|
78
|
+
.slice(1)
|
|
79
|
+
.split("/")
|
|
80
|
+
.map((segment) => {
|
|
81
|
+
if (/~(?:[^01]|$)/u.test(segment)) {
|
|
82
|
+
throw new ProviderInputGovernanceError(
|
|
83
|
+
"INVALID_INPUT",
|
|
84
|
+
"Provider 输入字段 JSON Pointer 无效",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const decoded = segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
88
|
+
if (["__proto__", "prototype", "constructor"].includes(decoded)) {
|
|
89
|
+
throw new ProviderInputGovernanceError(
|
|
90
|
+
"INVALID_INPUT",
|
|
91
|
+
"Provider 输入字段 JSON Pointer 无效",
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return decoded;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readPath(
|
|
99
|
+
input: Record<string, ModuleAiJsonValue>,
|
|
100
|
+
segments: readonly string[],
|
|
101
|
+
): { found: boolean; value: ModuleAiJsonValue } {
|
|
102
|
+
let current: ModuleAiJsonValue = input;
|
|
103
|
+
for (const segment of segments) {
|
|
104
|
+
if (!isRecord(current) || !Object.hasOwn(current, segment)) {
|
|
105
|
+
return { found: false, value: null };
|
|
106
|
+
}
|
|
107
|
+
current = current[segment]!;
|
|
108
|
+
}
|
|
109
|
+
return { found: true, value: current };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function writePath(
|
|
113
|
+
output: Record<string, ModuleAiJsonValue>,
|
|
114
|
+
segments: readonly string[],
|
|
115
|
+
value: ModuleAiJsonValue,
|
|
116
|
+
): void {
|
|
117
|
+
let current = output;
|
|
118
|
+
segments.forEach((segment, index) => {
|
|
119
|
+
if (index === segments.length - 1) {
|
|
120
|
+
current[segment] = value;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const next = current[segment];
|
|
124
|
+
if (!isRecord(next)) {
|
|
125
|
+
current[segment] = Object.create(null);
|
|
126
|
+
}
|
|
127
|
+
current = current[segment] as Record<string, ModuleAiJsonValue>;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function matches(pattern: RegExp, value: string): boolean {
|
|
132
|
+
pattern.lastIndex = 0;
|
|
133
|
+
return pattern.test(value);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isRecord(
|
|
137
|
+
value: ModuleAiJsonValue,
|
|
138
|
+
): value is Record<string, ModuleAiJsonValue> {
|
|
139
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
140
|
+
}
|
|
@@ -40,6 +40,27 @@ describe("RemoteAgentRuntime", () => {
|
|
|
40
40
|
mode: "configured-endpoint",
|
|
41
41
|
deploymentKey: "ai.openai-compatible",
|
|
42
42
|
},
|
|
43
|
+
providerDataPolicy: {
|
|
44
|
+
inputFields: [
|
|
45
|
+
{
|
|
46
|
+
jsonPointer: "/question",
|
|
47
|
+
classification: "internal",
|
|
48
|
+
dlpAction: "redact",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
envelopeFields: [
|
|
52
|
+
{
|
|
53
|
+
field: "instructions",
|
|
54
|
+
classification: "internal",
|
|
55
|
+
dlpAction: "reject",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
field: "user-prompt",
|
|
59
|
+
classification: "internal",
|
|
60
|
+
dlpAction: "redact",
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
43
64
|
toolNetworkPolicy: { mode: "forbidden" },
|
|
44
65
|
executionGrant: "grant_1",
|
|
45
66
|
};
|
|
@@ -60,6 +81,7 @@ describe("RemoteAgentRuntime", () => {
|
|
|
60
81
|
mode: "configured-endpoint",
|
|
61
82
|
deploymentKey: "ai.openai-compatible",
|
|
62
83
|
},
|
|
84
|
+
providerDataPolicy: operation.providerDataPolicy,
|
|
63
85
|
toolNetworkPolicy: { mode: "forbidden" },
|
|
64
86
|
});
|
|
65
87
|
expect(
|
|
@@ -38,6 +38,7 @@ export class RemoteAgentRuntime implements AgentRuntimePort {
|
|
|
38
38
|
maxOutputBytes: request.limits.maxOutputBytes,
|
|
39
39
|
},
|
|
40
40
|
providerEgress: request.providerEgress,
|
|
41
|
+
providerDataPolicy: request.providerDataPolicy,
|
|
41
42
|
toolNetworkPolicy: request.toolNetworkPolicy,
|
|
42
43
|
executionGrant: request.executionGrant,
|
|
43
44
|
toolHostUrl: this.options.toolHostUrl,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AiProviderDataPolicy,
|
|
2
3
|
AiProviderEgressPolicy,
|
|
3
4
|
AiToolNetworkPolicy,
|
|
4
5
|
} from "@southwind-ai/modules";
|
|
@@ -19,6 +20,7 @@ export interface AgentRuntimeOperation {
|
|
|
19
20
|
maxOutputBytes: number;
|
|
20
21
|
};
|
|
21
22
|
providerEgress: AiProviderEgressPolicy;
|
|
23
|
+
providerDataPolicy: AiProviderDataPolicy;
|
|
22
24
|
toolNetworkPolicy: AiToolNetworkPolicy;
|
|
23
25
|
executionGrant: string;
|
|
24
26
|
}
|
|
@@ -17,7 +17,7 @@ describe("Guarded Agent Run facade", () => {
|
|
|
17
17
|
const fixture = await createFixture();
|
|
18
18
|
const created = await fixture.facade.start(
|
|
19
19
|
"business.answer",
|
|
20
|
-
{ question: "你好" },
|
|
20
|
+
{ question: "你好", internalCaseId: "case-secret" },
|
|
21
21
|
"request-1",
|
|
22
22
|
fixture.context,
|
|
23
23
|
);
|
|
@@ -33,8 +33,18 @@ describe("Guarded Agent Run facade", () => {
|
|
|
33
33
|
instructions: "只回答当前业务问题。",
|
|
34
34
|
userPrompt: "你好",
|
|
35
35
|
allowedTools: [{ key: "business.records.list" }],
|
|
36
|
+
providerDataPolicy: {
|
|
37
|
+
inputFields: expect.arrayContaining([
|
|
38
|
+
expect.objectContaining({ jsonPointer: "/question" }),
|
|
39
|
+
]),
|
|
40
|
+
envelopeFields: expect.arrayContaining([
|
|
41
|
+
expect.objectContaining({ field: "instructions" }),
|
|
42
|
+
]),
|
|
43
|
+
},
|
|
36
44
|
});
|
|
37
45
|
expect(fixture.remote.started[0]?.executionGrant).toMatch(/^grant_/);
|
|
46
|
+
expect(fixture.remote.started[0]?.input).toEqual({ question: "你好" });
|
|
47
|
+
expect(JSON.stringify(fixture.prepared)).not.toContain("case-secret");
|
|
38
48
|
expect(() =>
|
|
39
49
|
fixture.facade.authorizeRun("run_1", fixture.context),
|
|
40
50
|
).not.toThrow();
|
|
@@ -250,7 +260,47 @@ function manifest(): ModuleManifest {
|
|
|
250
260
|
allowedToolKeys: ["business.records.list"],
|
|
251
261
|
dataClassification: "internal",
|
|
252
262
|
execution: "streaming",
|
|
253
|
-
|
|
263
|
+
providerEgress: {
|
|
264
|
+
mode: "configured-endpoint",
|
|
265
|
+
deploymentKey: "ai.openai-compatible",
|
|
266
|
+
},
|
|
267
|
+
providerDataPolicy: {
|
|
268
|
+
inputFields: [
|
|
269
|
+
{
|
|
270
|
+
jsonPointer: "/question",
|
|
271
|
+
classification: "internal",
|
|
272
|
+
dlpAction: "redact",
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
envelopeFields: [
|
|
276
|
+
{
|
|
277
|
+
field: "instructions",
|
|
278
|
+
classification: "internal",
|
|
279
|
+
dlpAction: "reject",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
field: "user-prompt",
|
|
283
|
+
classification: "internal",
|
|
284
|
+
dlpAction: "redact",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
field: "tool-definitions",
|
|
288
|
+
classification: "internal",
|
|
289
|
+
dlpAction: "reject",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
field: "provider-output",
|
|
293
|
+
classification: "internal",
|
|
294
|
+
dlpAction: "redact",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
field: "tool-results",
|
|
298
|
+
classification: "sensitive",
|
|
299
|
+
dlpAction: "redact",
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
toolNetworkPolicy: { mode: "forbidden" },
|
|
254
304
|
limits: {
|
|
255
305
|
maxDurationSeconds: 60,
|
|
256
306
|
maxInputBytes: 4096,
|
|
@@ -22,6 +22,10 @@ import type {
|
|
|
22
22
|
AgentRuntimePort,
|
|
23
23
|
AgentRuntimeStartResponse,
|
|
24
24
|
} from "./run-contracts.js";
|
|
25
|
+
import {
|
|
26
|
+
governProviderInput,
|
|
27
|
+
ProviderInputGovernanceError,
|
|
28
|
+
} from "./provider-input-governance.js";
|
|
25
29
|
|
|
26
30
|
type ServerRuntime = ReturnType<typeof createServerRuntime>;
|
|
27
31
|
|
|
@@ -88,7 +92,33 @@ export class AgentRunFacade {
|
|
|
88
92
|
);
|
|
89
93
|
}
|
|
90
94
|
assertInputSize(input, operation.definition.limits.maxInputBytes);
|
|
91
|
-
const
|
|
95
|
+
const providerPolicy = operation.definition.providerDataPolicy;
|
|
96
|
+
if (
|
|
97
|
+
operation.definition.providerEgress?.mode === "configured-endpoint" &&
|
|
98
|
+
!providerPolicy
|
|
99
|
+
) {
|
|
100
|
+
throw new AgentRunFacadeError(
|
|
101
|
+
"FORBIDDEN",
|
|
102
|
+
"Agent Operation 缺少 Provider 字段治理策略",
|
|
103
|
+
403,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
let governedInput = input;
|
|
107
|
+
try {
|
|
108
|
+
governedInput = providerPolicy
|
|
109
|
+
? governProviderInput(input, providerPolicy)
|
|
110
|
+
: input;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error instanceof ProviderInputGovernanceError) {
|
|
113
|
+
throw new AgentRunFacadeError(
|
|
114
|
+
error.code,
|
|
115
|
+
error.message,
|
|
116
|
+
error.code === "FORBIDDEN" ? 403 : 400,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
const plan = await operation.handler.prepare(governedInput, {
|
|
92
122
|
actor: toModuleActor(context.actor),
|
|
93
123
|
requestId: context.requestId,
|
|
94
124
|
});
|
|
@@ -104,12 +134,16 @@ export class AgentRunFacade {
|
|
|
104
134
|
const created = await this.remote.start(
|
|
105
135
|
{
|
|
106
136
|
operationKey,
|
|
107
|
-
input,
|
|
137
|
+
input: governedInput,
|
|
108
138
|
instructions: plan.instructions,
|
|
109
139
|
userPrompt: plan.userPrompt,
|
|
110
140
|
allowedTools: operation.tools,
|
|
111
141
|
limits: operation.definition.limits,
|
|
112
142
|
providerEgress: resolveProviderEgress(operation.definition),
|
|
143
|
+
providerDataPolicy: operation.definition.providerDataPolicy ?? {
|
|
144
|
+
inputFields: [],
|
|
145
|
+
envelopeFields: [],
|
|
146
|
+
},
|
|
113
147
|
toolNetworkPolicy: resolveToolNetworkPolicy(operation.definition),
|
|
114
148
|
executionGrant: grant.token,
|
|
115
149
|
},
|
|
@@ -104,6 +104,7 @@ const {
|
|
|
104
104
|
moduleHosts,
|
|
105
105
|
systemRepositories,
|
|
106
106
|
} = await bootstrapServerModules(platformConfig, persistence);
|
|
107
|
+
const features = foundationSnapshot.features;
|
|
107
108
|
// @windy-module system.license begin
|
|
108
109
|
const platformLicenseOptions = createPlatformLicenseRuntimeOptions(process.env);
|
|
109
110
|
const platformLicenseRuntime = new PlatformLicenseRuntime(
|
|
@@ -121,7 +122,7 @@ const runtime = createServerRuntime(process.env, {
|
|
|
121
122
|
: persistence?.licenseResolver,
|
|
122
123
|
// @windy-module system.license end
|
|
123
124
|
featureResolver: persistence ? systemRepositories.features : undefined,
|
|
124
|
-
featureCatalog:
|
|
125
|
+
featureCatalog: features,
|
|
125
126
|
// @windy-module system.audit begin
|
|
126
127
|
auditReader: persistence?.auditReader,
|
|
127
128
|
auditWriter: persistence?.auditWriter,
|
|
@@ -169,7 +170,7 @@ const searchHost = createSearchHost(
|
|
|
169
170
|
// @windy-module system.scheduler begin
|
|
170
171
|
const moduleTaskDefinitions = createModuleTaskDefinitions(
|
|
171
172
|
moduleHosts,
|
|
172
|
-
|
|
173
|
+
features,
|
|
173
174
|
);
|
|
174
175
|
const {
|
|
175
176
|
bulkDataService,
|
|
@@ -209,33 +210,23 @@ registerAuthRoutes(app, authService, {
|
|
|
209
210
|
sessionCookieName: platformConfig.security.sessionCookieName,
|
|
210
211
|
secureCookie: process.env.NODE_ENV === "production",
|
|
211
212
|
});
|
|
212
|
-
registerProfileRoutes(
|
|
213
|
-
app,
|
|
214
|
-
authRepository,
|
|
215
|
-
runtime,
|
|
216
|
-
foundationSnapshot.features,
|
|
217
|
-
);
|
|
213
|
+
registerProfileRoutes(app, authRepository, runtime, features);
|
|
218
214
|
|
|
219
215
|
// @windy-module system.license begin
|
|
220
216
|
registerPlatformLicenseRoutes(app, platformLicenseRuntime);
|
|
221
217
|
// @windy-module system.license end
|
|
222
218
|
|
|
223
219
|
// @windy-module system.settings begin
|
|
224
|
-
platformSettings.register(app, runtime,
|
|
220
|
+
platformSettings.register(app, runtime, features);
|
|
225
221
|
// @windy-module system.settings end
|
|
226
222
|
// @windy-module system.notification begin
|
|
227
|
-
registerNotificationRoutes(
|
|
228
|
-
app,
|
|
229
|
-
notificationRepository,
|
|
230
|
-
runtime,
|
|
231
|
-
foundationSnapshot.features,
|
|
232
|
-
);
|
|
223
|
+
registerNotificationRoutes(app, notificationRepository, runtime, features);
|
|
233
224
|
// @windy-module system.notification end
|
|
234
225
|
// @windy-module system.workflow begin
|
|
235
226
|
registerWorkflowRoutes(app, workflowService, runtime);
|
|
236
227
|
// @windy-module system.workflow end
|
|
237
228
|
// @windy-module system.configuration begin
|
|
238
|
-
configurationRuntime.register(app, runtime,
|
|
229
|
+
configurationRuntime.register(app, runtime, features);
|
|
239
230
|
// @windy-module system.configuration end
|
|
240
231
|
// @windy-module system.storage begin
|
|
241
232
|
registerStorageRoutes(
|
|
@@ -248,7 +239,7 @@ registerStorageRoutes(
|
|
|
248
239
|
`/api/platform/files/${fileId}`,
|
|
249
240
|
},
|
|
250
241
|
runtime,
|
|
251
|
-
|
|
242
|
+
features,
|
|
252
243
|
);
|
|
253
244
|
// @windy-module system.storage end
|
|
254
245
|
// @windy-module system.search begin
|
|
@@ -260,27 +251,18 @@ registerAdminBootstrapRoute(app, runtime, {
|
|
|
260
251
|
snapshot: foundationSnapshot,
|
|
261
252
|
loadMenus: () => systemRepositories.menuRegistry.tree(),
|
|
262
253
|
});
|
|
263
|
-
registerSessionContextRoute(app, runtime,
|
|
254
|
+
registerSessionContextRoute(app, runtime, features);
|
|
264
255
|
|
|
265
256
|
registerFoundationRoute(app, runtime, foundationSnapshot, systemRepositories);
|
|
266
257
|
|
|
267
258
|
// @windy-module system.data-governance begin
|
|
268
|
-
registerGovernedUserExportRoutes(
|
|
269
|
-
app,
|
|
270
|
-
governedExportService,
|
|
271
|
-
runtime,
|
|
272
|
-
foundationSnapshot.features,
|
|
273
|
-
);
|
|
259
|
+
registerGovernedUserExportRoutes(app, governedExportService, runtime, features);
|
|
274
260
|
// @windy-module system.data-governance end
|
|
275
|
-
registerSystemRoutes(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
runtime,
|
|
279
|
-
foundationSnapshot.features,
|
|
280
|
-
{ authRepository: persistence?.authRepository },
|
|
281
|
-
);
|
|
261
|
+
registerSystemRoutes(app, systemRepositories, runtime, features, {
|
|
262
|
+
authRepository: persistence?.authRepository,
|
|
263
|
+
});
|
|
282
264
|
// @windy-module system.operations begin
|
|
283
|
-
registerSystemOperationRoutes(app, runtime,
|
|
265
|
+
registerSystemOperationRoutes(app, runtime, features, {
|
|
284
266
|
redisHealthProbe: configurationRuntime.redisHealthProbe,
|
|
285
267
|
onlineUserProvider: () =>
|
|
286
268
|
persistence?.authRepository.listActiveSessions(
|
|
@@ -293,7 +275,13 @@ registerSystemOperationRoutes(app, runtime, foundationSnapshot.features, {
|
|
|
293
275
|
// @windy-module system.agent begin
|
|
294
276
|
const toolHandlers = new Map([
|
|
295
277
|
...createSystemToolHandlers(systemRepositories, runtime),
|
|
296
|
-
...createModuleToolHandlerMap(moduleHosts
|
|
278
|
+
...createModuleToolHandlerMap(moduleHosts, {
|
|
279
|
+
runtime,
|
|
280
|
+
features: foundationSnapshot.features,
|
|
281
|
+
// @windy-module system.storage begin
|
|
282
|
+
storage: storageRuntime,
|
|
283
|
+
// @windy-module system.storage end
|
|
284
|
+
}),
|
|
297
285
|
]);
|
|
298
286
|
const toolRegistry = new GovernedToolRegistry(
|
|
299
287
|
foundationModules,
|
|
@@ -301,10 +289,7 @@ const toolRegistry = new GovernedToolRegistry(
|
|
|
301
289
|
runtime,
|
|
302
290
|
);
|
|
303
291
|
registerAgentRoutes(app, toolRegistry);
|
|
304
|
-
registerMcpRoutes(
|
|
305
|
-
app,
|
|
306
|
-
new PlatformMcpAdapter(toolRegistry, runtime, foundationSnapshot.features),
|
|
307
|
-
);
|
|
292
|
+
registerMcpRoutes(app, new PlatformMcpAdapter(toolRegistry, runtime, features));
|
|
308
293
|
registerAgentPlatformRuntime({
|
|
309
294
|
app,
|
|
310
295
|
platformConfig,
|
|
@@ -315,19 +300,14 @@ registerAgentPlatformRuntime({
|
|
|
315
300
|
});
|
|
316
301
|
// @windy-module system.agent end
|
|
317
302
|
// @windy-module system.scheduler begin
|
|
318
|
-
registerSchedulerRoutes(app, scheduler, runtime,
|
|
303
|
+
registerSchedulerRoutes(app, scheduler, runtime, features);
|
|
319
304
|
// @windy-module system.scheduler end
|
|
320
305
|
// @windy-module system.bulk-data begin
|
|
321
|
-
registerBulkDataRoutes(
|
|
322
|
-
app,
|
|
323
|
-
bulkDataService,
|
|
324
|
-
runtime,
|
|
325
|
-
foundationSnapshot.features,
|
|
326
|
-
);
|
|
306
|
+
registerBulkDataRoutes(app, bulkDataService, runtime, features);
|
|
327
307
|
// @windy-module system.bulk-data end
|
|
328
308
|
installModuleRoutes(moduleHosts, app, {
|
|
329
309
|
runtime,
|
|
330
|
-
features
|
|
310
|
+
features,
|
|
331
311
|
// @windy-module system.scheduler begin
|
|
332
312
|
jobs: durableJobRuntime.service,
|
|
333
313
|
// @windy-module system.scheduler end
|
|
@@ -337,6 +317,9 @@ installModuleRoutes(moduleHosts, app, {
|
|
|
337
317
|
files: storageRuntime.files,
|
|
338
318
|
},
|
|
339
319
|
// @windy-module system.storage end
|
|
320
|
+
userDirectory: {
|
|
321
|
+
repositories: systemRepositories,
|
|
322
|
+
},
|
|
340
323
|
});
|
|
341
324
|
|
|
342
325
|
validateInstalledRuntime({
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ModuleAiOperationHandlerRegistration,
|
|
6
6
|
ModuleInitializer,
|
|
7
7
|
ModuleRolePreset,
|
|
8
|
+
ModuleResourceFileAccessPolicy,
|
|
8
9
|
ModuleRouteDefinition,
|
|
9
10
|
ModuleSearchProvider,
|
|
10
11
|
ModuleTaskHandlerRegistration,
|
|
@@ -54,6 +55,7 @@ export interface InitializeModuleHostsInput {
|
|
|
54
55
|
export interface InitializedModuleHosts {
|
|
55
56
|
readonly manifests: readonly ModuleManifest[];
|
|
56
57
|
readonly uploadPolicies: readonly ModuleUploadPolicy[];
|
|
58
|
+
readonly resourceFileAccessPolicies: readonly ModuleResourceFileAccessPolicy[];
|
|
57
59
|
readonly routes: readonly RegisteredModuleRoute[];
|
|
58
60
|
readonly taskHandlers: readonly RegisteredModuleTaskHandler[];
|
|
59
61
|
readonly toolHandlers: readonly RegisteredModuleToolHandler[];
|
|
@@ -63,5 +65,9 @@ export interface InitializedModuleHosts {
|
|
|
63
65
|
manifestOf(moduleName: string): ModuleManifest;
|
|
64
66
|
/** 该模块经 Host 注册的上传用途集合(不含平台基础用途)。 */
|
|
65
67
|
uploadPurposesOf(moduleName: string): ReadonlySet<string>;
|
|
68
|
+
/** 该模块注册的资源文件读取策略。 */
|
|
69
|
+
resourceFileAccessPoliciesOf(
|
|
70
|
+
moduleName: string,
|
|
71
|
+
): readonly ModuleResourceFileAccessPolicy[];
|
|
66
72
|
installDurableHandlers(registry: DurableHandlerRegistry): void;
|
|
67
73
|
}
|