create-windy 0.2.33 → 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.
Files changed (69) hide show
  1. package/dist/cli.js +7 -1
  2. package/package.json +1 -1
  3. package/template/.agents/skills/windy-business-module/SKILL.md +10 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/AGENTS.md +4 -0
  6. package/template/apps/agent-server/README.md +10 -0
  7. package/template/apps/agent-server/src/southwind_agent_server/app.py +62 -1
  8. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +35 -0
  9. package/template/apps/agent-server/src/southwind_agent_server/engine.py +2 -0
  10. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +31 -3
  11. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +22 -9
  12. package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +144 -0
  13. package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +227 -0
  14. package/template/apps/agent-server/src/southwind_agent_server/service.py +1 -0
  15. package/template/apps/agent-server/tests/test_api.py +16 -1
  16. package/template/apps/agent-server/tests/test_openai_provider.py +79 -0
  17. package/template/apps/agent-server/tests/test_provider_governance.py +215 -0
  18. package/template/apps/server/src/agent/provider-input-governance.test.ts +82 -0
  19. package/template/apps/server/src/agent/provider-input-governance.ts +140 -0
  20. package/template/apps/server/src/agent/remote-runtime.test.ts +22 -0
  21. package/template/apps/server/src/agent/remote-runtime.ts +1 -0
  22. package/template/apps/server/src/agent/run-contracts.ts +2 -0
  23. package/template/apps/server/src/agent/run-facade.test.ts +52 -2
  24. package/template/apps/server/src/agent/run-facade.ts +36 -2
  25. package/template/apps/server/src/index.ts +28 -45
  26. package/template/apps/server/src/module-host/initialize-contracts.ts +6 -0
  27. package/template/apps/server/src/module-host/initialize.ts +30 -44
  28. package/template/apps/server/src/module-host/request-context.ts +23 -17
  29. package/template/apps/server/src/module-host/resource-file-policies.test.ts +197 -0
  30. package/template/apps/server/src/module-host/resource-file-policies.ts +108 -0
  31. package/template/apps/server/src/module-host/route-declaration.ts +45 -0
  32. package/template/apps/server/src/module-host/route-installer.ts +27 -3
  33. package/template/apps/server/src/module-host/storage-port.test.ts +170 -1
  34. package/template/apps/server/src/module-host/storage-port.ts +134 -1
  35. package/template/apps/server/src/module-host/storage-scope.ts +96 -0
  36. package/template/apps/server/src/module-host/tool-handlers.test.ts +305 -0
  37. package/template/apps/server/src/module-host/tool-handlers.ts +38 -4
  38. package/template/apps/server/src/module-host/user-directory-composition.test.ts +126 -0
  39. package/template/apps/server/src/module-host/user-directory-port.test.ts +238 -0
  40. package/template/apps/server/src/module-host/user-directory-port.ts +143 -0
  41. package/template/apps/server/src/module-host.ts +4 -0
  42. package/template/apps/server/src/runtime-development.ts +1 -0
  43. package/template/apps/server/src/system/built-in-roles.ts +1 -0
  44. package/template/docker-compose.yml +2 -0
  45. package/template/docs/architecture/ai-egress.md +49 -7
  46. package/template/docs/architecture/ai-runtime.md +20 -1
  47. package/template/docs/architecture/data-scope-query-enforcement.md +4 -0
  48. package/template/docs/architecture/object-storage.md +22 -2
  49. package/template/docs/development/custom-login-page.md +3 -3
  50. package/template/docs/platform/agent-runtime.md +44 -1
  51. package/template/docs/platform/business-user-directory.md +82 -0
  52. package/template/packages/modules/package.json +1 -1
  53. package/template/packages/modules/src/ai-operation-composition.test.ts +68 -0
  54. package/template/packages/modules/src/ai-operation-composition.ts +115 -0
  55. package/template/packages/modules/src/manifest.ts +26 -0
  56. package/template/packages/modules/src/provider-data-policy-composition.test.ts +74 -0
  57. package/template/packages/modules/src/system-audit-actions.ts +5 -0
  58. package/template/packages/modules/src/system-features.ts +5 -0
  59. package/template/packages/modules/src/system-module-catalog.test.ts +16 -0
  60. package/template/packages/modules/src/system-module-catalog.ts +26 -3
  61. package/template/packages/modules/src/system-permissions.ts +2 -0
  62. package/template/packages/server-sdk/index.ts +11 -0
  63. package/template/packages/server-sdk/package.json +3 -2
  64. package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -1
  65. package/template/packages/server-sdk/src/module-host.ts +8 -0
  66. package/template/packages/server-sdk/src/request-context.ts +2 -0
  67. package/template/packages/server-sdk/src/storage-port.ts +66 -0
  68. package/template/packages/server-sdk/src/tool-registration.ts +6 -0
  69. package/template/packages/server-sdk/src/user-directory-port.ts +42 -0
@@ -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
- publicNetworkAccess: "forbidden",
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 plan = await operation.handler.prepare(input, {
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: foundationSnapshot.features,
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
- foundationSnapshot.features,
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, foundationSnapshot.features);
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, foundationSnapshot.features);
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
- foundationSnapshot.features,
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, foundationSnapshot.features);
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
- app,
277
- systemRepositories,
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, foundationSnapshot.features, {
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, foundationSnapshot.features);
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: foundationSnapshot.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
  }