create-windy 0.2.19 → 0.2.21

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 (127) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +392 -160
  3. package/package.json +1 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/README.md +1 -0
  6. package/template/apps/agent-server/Dockerfile +20 -0
  7. package/template/apps/agent-server/README.md +46 -0
  8. package/template/apps/agent-server/pyproject.toml +44 -0
  9. package/template/apps/agent-server/src/southwind_agent_server/__init__.py +5 -0
  10. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +39 -0
  11. package/template/apps/agent-server/src/southwind_agent_server/app.py +263 -0
  12. package/template/apps/agent-server/src/southwind_agent_server/config.py +141 -0
  13. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +99 -0
  14. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +52 -0
  15. package/template/apps/agent-server/src/southwind_agent_server/engine.py +85 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +197 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +246 -0
  18. package/template/apps/agent-server/src/southwind_agent_server/service.py +180 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +317 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/sse.py +41 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/store.py +144 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +80 -0
  23. package/template/apps/agent-server/tests/test_api.py +207 -0
  24. package/template/apps/agent-server/tests/test_config.py +56 -0
  25. package/template/apps/agent-server/tests/test_openai_provider.py +224 -0
  26. package/template/apps/agent-server/tests/test_sqlite_store.py +93 -0
  27. package/template/apps/agent-server/uv.lock +925 -0
  28. package/template/apps/server/src/agent/execution-grants.ts +89 -0
  29. package/template/apps/server/src/agent/remote-runtime.ts +128 -0
  30. package/template/apps/server/src/agent/run-contracts.ts +43 -0
  31. package/template/apps/server/src/agent/run-facade.test.ts +257 -0
  32. package/template/apps/server/src/agent/run-facade.ts +190 -0
  33. package/template/apps/server/src/agent/run-routes.ts +222 -0
  34. package/template/apps/server/src/agent/runtime-bootstrap.ts +53 -0
  35. package/template/apps/server/src/agent/tool-host.test.ts +242 -0
  36. package/template/apps/server/src/agent/tool-host.ts +153 -0
  37. package/template/apps/server/src/application-services.ts +2 -2
  38. package/template/apps/server/src/audit/search-summary.ts +15 -8
  39. package/template/apps/server/src/example-modules.ts +58 -70
  40. package/template/apps/server/src/foundation.ts +8 -4
  41. package/template/apps/server/src/guards.test.ts +13 -0
  42. package/template/apps/server/src/guards.ts +12 -2
  43. package/template/apps/server/src/index.ts +48 -95
  44. package/template/apps/server/src/installed-business-modules.ts +23 -4
  45. package/template/apps/server/src/module-bootstrap.ts +83 -0
  46. package/template/apps/server/src/module-composition.test.ts +54 -1
  47. package/template/apps/server/src/module-composition.ts +58 -0
  48. package/template/apps/server/src/module-host/initialize-contracts.ts +67 -0
  49. package/template/apps/server/src/module-host/initialize.test.ts +192 -0
  50. package/template/apps/server/src/module-host/initialize.ts +328 -0
  51. package/template/apps/server/src/module-host/request-context.test.ts +66 -0
  52. package/template/apps/server/src/module-host/request-context.ts +156 -0
  53. package/template/apps/server/src/module-host/role-presets.test.ts +94 -0
  54. package/template/apps/server/src/module-host/role-presets.ts +76 -0
  55. package/template/apps/server/src/module-host/route-installer.test.ts +317 -0
  56. package/template/apps/server/src/module-host/route-installer.ts +97 -0
  57. package/template/apps/server/src/module-host/route-registration.test.ts +160 -0
  58. package/template/apps/server/src/module-host/search-providers.ts +31 -0
  59. package/template/apps/server/src/module-host/storage-port.test.ts +165 -0
  60. package/template/apps/server/src/module-host/storage-port.ts +59 -0
  61. package/template/apps/server/src/module-host/task-definitions.ts +41 -0
  62. package/template/apps/server/src/module-host/test-fixtures.ts +26 -0
  63. package/template/apps/server/src/module-host/tool-handlers.ts +25 -0
  64. package/template/apps/server/src/module-host.test.ts +202 -58
  65. package/template/apps/server/src/module-host.ts +35 -113
  66. package/template/apps/server/src/module-runtime-validation.ts +40 -0
  67. package/template/apps/server/src/route-guards.test.ts +112 -1
  68. package/template/apps/server/src/runtime-feature.ts +66 -31
  69. package/template/apps/server/src/runtime.test.ts +1 -0
  70. package/template/apps/server/src/runtime.ts +3 -1
  71. package/template/apps/server/src/system/built-in-roles.ts +1 -1
  72. package/template/apps/server/src/work-order/foundation-modules.test.ts +30 -10
  73. package/template/apps/server/src/work-order/routes.test.ts +30 -18
  74. package/template/apps/server/src/work-order/routes.ts +104 -135
  75. package/template/apps/server/src/work-order/search-api.test.ts +14 -3
  76. package/template/apps/server/src/work-order/search-provider.test.ts +23 -15
  77. package/template/apps/server/src/work-order/search-provider.ts +17 -12
  78. package/template/apps/server/src/work-order/task.test.ts +16 -11
  79. package/template/apps/web/Dockerfile +4 -1
  80. package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +4 -1
  81. package/template/apps/web/src/layout/GlobalWatermark.vue +2 -0
  82. package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +4 -1
  83. package/template/docker-compose.yml +25 -0
  84. package/template/docs/architecture/ai-runtime.md +744 -0
  85. package/template/docs/architecture/object-storage.md +12 -0
  86. package/template/docs/platform/agent-runtime.md +128 -0
  87. package/template/docs/platform/agent-tools.md +8 -1
  88. package/template/package.json +1 -0
  89. package/template/packages/config/index.test.ts +100 -0
  90. package/template/packages/config/index.ts +1 -0
  91. package/template/packages/config/package.json +2 -1
  92. package/template/packages/config/src/ai-openai-compatible.ts +131 -0
  93. package/template/packages/config/src/platform.ts +28 -1
  94. package/template/packages/database/package.json +1 -1
  95. package/template/packages/database/src/runner.ts +7 -9
  96. package/template/packages/example-work-order/index.ts +1 -0
  97. package/template/packages/example-work-order/package.json +2 -0
  98. package/template/packages/example-work-order/src/server-module.ts +61 -0
  99. package/template/packages/jobs/package.json +1 -1
  100. package/template/packages/jobs/src/types.ts +7 -1
  101. package/template/packages/modules/package.json +1 -1
  102. package/template/packages/modules/src/catalog-validation.test.ts +212 -0
  103. package/template/packages/modules/src/catalog-validation.ts +19 -2
  104. package/template/packages/modules/src/migration-bundle-validation.test.ts +191 -0
  105. package/template/packages/server-sdk/index.ts +53 -0
  106. package/template/packages/server-sdk/package.json +18 -3
  107. package/template/packages/server-sdk/src/actor.ts +15 -0
  108. package/template/packages/server-sdk/src/ai-operation-registration.ts +31 -0
  109. package/template/packages/server-sdk/src/audit-port.ts +22 -0
  110. package/template/packages/server-sdk/src/jobs-port.ts +24 -0
  111. package/template/packages/server-sdk/src/module-host.ts +41 -1
  112. package/template/packages/server-sdk/src/namespace.test.ts +26 -0
  113. package/template/packages/server-sdk/src/namespace.ts +13 -0
  114. package/template/packages/server-sdk/src/request-context.ts +21 -0
  115. package/template/packages/server-sdk/src/role-preset.ts +18 -0
  116. package/template/packages/server-sdk/src/route-definition.ts +26 -0
  117. package/template/packages/server-sdk/src/search-provider-port.ts +48 -0
  118. package/template/packages/server-sdk/src/storage-port.ts +25 -0
  119. package/template/packages/server-sdk/src/task-registration.ts +18 -0
  120. package/template/packages/server-sdk/src/tool-host-port.ts +22 -0
  121. package/template/packages/server-sdk/src/tool-registration.ts +29 -0
  122. package/template/packages/shared/index.ts +1 -0
  123. package/template/packages/shared/package.json +1 -1
  124. package/template/packages/shared/src/license-catalog.test.ts +73 -1
  125. package/template/packages/shared/src/license-catalog.ts +95 -0
  126. package/template/packages/{database/src/bundle-validation.ts → shared/src/migration-bundle-validation.ts} +2 -1
  127. package/template/apps/server/src/work-order/task.ts +0 -30
@@ -2,13 +2,26 @@ import type { AuditEvent } from "@southwind-ai/shared";
2
2
  import { randomUUIDv7 } from "bun";
3
3
  import type { RequestGuardContext } from "../guards.js";
4
4
 
5
+ export function searchAuditMetadata(
6
+ query: string,
7
+ matched: number,
8
+ ): Record<string, unknown> {
9
+ const normalized = query.trim();
10
+ return {
11
+ queryDigest: new Bun.CryptoHasher("sha256")
12
+ .update(normalized)
13
+ .digest("hex"),
14
+ queryLength: normalized.length,
15
+ matched,
16
+ };
17
+ }
18
+
5
19
  export function searchAuditEvent(
6
20
  context: RequestGuardContext,
7
21
  resourceName: string,
8
22
  query: string,
9
23
  matched: number,
10
24
  ): AuditEvent {
11
- const normalized = query.trim();
12
25
  return {
13
26
  id: randomUUIDv7(),
14
27
  action: "data.search",
@@ -20,13 +33,7 @@ export function searchAuditEvent(
20
33
  },
21
34
  target: { type: resourceName },
22
35
  requestId: context.requestId,
23
- metadata: {
24
- queryDigest: new Bun.CryptoHasher("sha256")
25
- .update(normalized)
26
- .digest("hex"),
27
- queryLength: normalized.length,
28
- matched,
29
- },
36
+ metadata: searchAuditMetadata(query, matched),
30
37
  occurredAt: new Date().toISOString(),
31
38
  };
32
39
  }
@@ -1,86 +1,74 @@
1
- import type { FeatureFlagDefinition } from "@southwind-ai/shared";
1
+ // @windy-module work-order begin
2
+ import {
3
+ createWorkOrderServerModule,
4
+ type WorkOrder,
5
+ } from "@southwind-ai/example-work-order";
6
+ import type { ModuleManifest } from "@southwind-ai/modules";
7
+ import type {
8
+ InstallableServerModule,
9
+ ModuleInitializer,
10
+ } from "@southwind-ai/server-sdk";
2
11
  import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
- import type { createServerRuntime } from "./runtime.js";
4
- import type { ScheduledTaskDefinition } from "./scheduler/types.js";
5
- import type { SearchProvider } from "./search/contracts.js";
12
+ import { platformDataActor } from "./data-access/platform-actor.js";
13
+ import type { ScopedEntityRepository } from "./data-access/scoped-repository.js";
6
14
  import type { ManagedDepartment } from "./system/entities.js";
7
15
  import type { EntityRepository } from "./system/repository.js";
8
- import type { RouteApp } from "./system/route-types.js";
9
- // @windy-module work-order begin
10
- import type { ModuleInitializer } from "@southwind-ai/server-sdk";
11
16
  import { createWorkOrderRepository } from "./work-order/repository.js";
12
17
  import { createDrizzleWorkOrderRepository } from "./work-order/drizzle-repository.js";
13
- import { registerWorkOrderRoutes } from "./work-order/routes.js";
14
- import { WorkOrderService } from "./work-order/service.js";
18
+ import { createWorkOrderRouteDefinitions } from "./work-order/routes.js";
15
19
  import { WorkOrderSearchProvider } from "./work-order/search-provider.js";
16
- import { createWorkOrderTaskDefinitions } from "./work-order/task.js";
17
- // @windy-module work-order end
18
-
19
- // @windy-module work-order begin
20
- export const workOrderModuleInitializer: ModuleInitializer = {
21
- moduleName: "work-order",
22
- initialize(host) {
23
- host.registerUploadPolicy({
24
- purpose: "work-order.ticket.attachment",
25
- maxByteSize: 20 * 1024 * 1024,
26
- maxPartByteSize: 4 * 1024 * 1024,
27
- sessionTtlMs: 2 * 60 * 60_000,
28
- allowedTypes: {
29
- "application/pdf": ["pdf"],
30
- "image/png": ["png"],
31
- "image/jpeg": ["jpg", "jpeg"],
32
- "text/plain": ["txt"],
33
- },
34
- signature: "none",
35
- });
36
- },
37
- };
38
- // @windy-module work-order end
20
+ import { WorkOrderService } from "./work-order/service.js";
39
21
 
40
- export interface ExampleModuleRuntimeDependencies {
41
- features: FeatureFlagDefinition[];
22
+ export interface WorkOrderModuleDependencies {
42
23
  departments: EntityRepository<ManagedDepartment>;
43
24
  db?: NodePgDatabase;
25
+ /** 测试可注入预置数据的仓储;默认按 db 选择 Drizzle 或内存实现。 */
26
+ repository?: ScopedEntityRepository<WorkOrder>;
44
27
  }
45
28
 
46
- export interface ExampleModuleRuntime {
47
- service: WorkOrderService;
48
- tasks: ScheduledTaskDefinition[];
49
- providers: SearchProvider[];
29
+ export interface WorkOrderModule {
30
+ readonly manifest: ModuleManifest;
31
+ setup: InstallableServerModule<ModuleManifest>["setup"];
32
+ /** @deprecated 测试兼容入口;生产装配使用 manifest + setup。 */
33
+ readonly initializer: ModuleInitializer;
34
+ /** 宿主运行时就绪后挂载模块服务;路由与任务 Handler 延迟解析它。 */
35
+ attach(dependencies: WorkOrderModuleDependencies): void;
50
36
  }
51
37
 
52
- export function createExampleModuleRuntime(
53
- dependencies: ExampleModuleRuntimeDependencies,
54
- ): ExampleModuleRuntime | undefined {
55
- if (!dependencies.features.some(({ key }) => key === "work-order.ticket")) {
56
- return undefined;
57
- }
58
- const service = new WorkOrderService(
59
- dependencies.db
60
- ? createDrizzleWorkOrderRepository(dependencies.db)
61
- : createWorkOrderRepository(),
62
- dependencies.departments,
63
- );
38
+ /**
39
+ * 示例工单模块:以 initializer + Host 声明式装配的样板。
40
+ * Manifest 声明 Route/Task/Provider,初始化器注册实现与受限 Port 定义,
41
+ * 不再由宿主手工逐项接线。
42
+ */
43
+ export function createWorkOrderModule(): WorkOrderModule {
44
+ let service: WorkOrderService | undefined;
45
+ const requireService = () => {
46
+ if (!service) throw new Error("示例工单模块尚未挂载");
47
+ return service;
48
+ };
49
+ const serverModule = createWorkOrderServerModule({
50
+ routes: createWorkOrderRouteDefinitions(requireService),
51
+ snapshot: () =>
52
+ requireService().operationalSnapshot(
53
+ platformDataActor("task_work_order_snapshot", "工单运营快照任务"),
54
+ ),
55
+ searchProvider: new WorkOrderSearchProvider(requireService),
56
+ });
64
57
  return {
65
- service,
66
- tasks: createWorkOrderTaskDefinitions(dependencies.features, service),
67
- providers: [new WorkOrderSearchProvider(service)],
58
+ ...serverModule,
59
+ initializer: {
60
+ moduleName: serverModule.manifest.name,
61
+ initialize: serverModule.setup,
62
+ },
63
+ attach(dependencies) {
64
+ service = new WorkOrderService(
65
+ dependencies.repository ??
66
+ (dependencies.db
67
+ ? createDrizzleWorkOrderRepository(dependencies.db)
68
+ : createWorkOrderRepository()),
69
+ dependencies.departments,
70
+ );
71
+ },
68
72
  };
69
73
  }
70
-
71
- export interface ExampleModuleRouteDependencies {
72
- app: RouteApp;
73
- runtime: ReturnType<typeof createServerRuntime>;
74
- moduleRuntime?: ExampleModuleRuntime;
75
- }
76
-
77
- export function registerExampleModuleRoutes(
78
- dependencies: ExampleModuleRouteDependencies,
79
- ): void {
80
- if (!dependencies.moduleRuntime) return;
81
- registerWorkOrderRoutes(
82
- dependencies.app,
83
- dependencies.moduleRuntime.service,
84
- dependencies.runtime,
85
- );
86
- }
74
+ // @windy-module work-order end
@@ -13,7 +13,8 @@ import type {
13
13
  MenuNode,
14
14
  PermissionDefinition,
15
15
  } from "@southwind-ai/shared";
16
- import { installedBusinessModuleFactories } from "./installed-business-modules.js";
16
+ import { resolveLicenseCatalog } from "@southwind-ai/shared";
17
+ import { installedBusinessModules } from "./installed-business-modules.js";
17
18
 
18
19
  export interface FoundationSnapshot {
19
20
  platform: {
@@ -38,8 +39,8 @@ export interface FoundationSnapshot {
38
39
  export function loadFoundationModules(): ModuleManifest[] {
39
40
  const registry = createModuleRegistry();
40
41
  for (const factory of systemModuleFactories) registry.register(factory);
41
- for (const factory of installedBusinessModuleFactories) {
42
- registry.register(factory);
42
+ for (const module of installedBusinessModules) {
43
+ registry.register(() => module.manifest);
43
44
  }
44
45
  return registry.list();
45
46
  }
@@ -72,7 +73,10 @@ export function createFoundationSnapshot(
72
73
  config: PlatformConfig = loadPlatformConfig(),
73
74
  ): FoundationSnapshot {
74
75
  const modules = loadFoundationModules();
75
- const catalogs = validateCapabilityCatalogs(modules);
76
+ const catalogs = validateCapabilityCatalogs(
77
+ modules,
78
+ resolveLicenseCatalog(config.license.versions),
79
+ );
76
80
 
77
81
  return {
78
82
  platform: {
@@ -71,4 +71,17 @@ describe("guards", () => {
71
71
  }).reason,
72
72
  ).toBe("entitlement-not-granted");
73
73
  });
74
+
75
+ test("运行时解析出依赖缺失时拒绝并记录 security.denied", () => {
76
+ const denied = evaluateFeatureGuard(context, {
77
+ ...feature,
78
+ enabled: false,
79
+ disabledReason: "dependency-missing",
80
+ });
81
+
82
+ expect(denied.allowed).toBe(false);
83
+ expect(denied.reason).toBe("dependency-missing");
84
+ expect(denied.auditEvent?.action).toBe("security.denied");
85
+ expect(denied.auditEvent?.target?.id).toBe(feature.key);
86
+ });
74
87
  });
@@ -3,6 +3,7 @@ import type {
3
3
  AuditEvent,
4
4
  EntitlementKey,
5
5
  FeatureFlagDefinition,
6
+ FeatureFlagState,
6
7
  LicenseVersionKey,
7
8
  } from "@southwind-ai/shared";
8
9
  import { correlationId, isLicenseVersionAllowed } from "@southwind-ai/shared";
@@ -47,6 +48,7 @@ export type GuardDecisionReason =
47
48
  | "missing-permission"
48
49
  | "role-not-allowed"
49
50
  | "feature-disabled"
51
+ | "dependency-missing"
50
52
  | "license-version-not-allowed"
51
53
  | "entitlement-not-granted"
52
54
  | "license-restricted";
@@ -120,13 +122,21 @@ export function evaluateRoleGuard(
120
122
 
121
123
  export function evaluateFeatureGuard(
122
124
  context: RequestGuardContext,
123
- feature: FeatureFlagDefinition,
125
+ feature: FeatureFlagDefinition & {
126
+ disabledReason?: FeatureFlagState["reason"];
127
+ },
124
128
  ): GuardDecision {
125
129
  if (feature.enabled && feature.visible === "visible") {
126
130
  return { allowed: true, reason: "allowed" };
127
131
  }
128
132
 
129
- return deniedDecision(context, "feature-disabled", feature.key);
133
+ return deniedDecision(
134
+ context,
135
+ feature.disabledReason === "dependency-missing"
136
+ ? "dependency-missing"
137
+ : "feature-disabled",
138
+ feature.key,
139
+ );
130
140
  }
131
141
 
132
142
  export function evaluateEntitlementGuard(
@@ -2,10 +2,6 @@ import { loadPlatformConfig } from "@southwind-ai/config";
2
2
  import pino from "pino";
3
3
  import { registerAdminBootstrapRoute } from "./admin/routes.js";
4
4
  import { registerSessionContextRoute } from "./session/routes.js";
5
- import {
6
- createFoundationSnapshot,
7
- loadFoundationModules,
8
- } from "./foundation.js";
9
5
  import { registerFoundationRoute } from "./foundation-route.js";
10
6
  // @windy-module system.scheduler begin
11
7
  import { createApplicationServices } from "./application-services.js";
@@ -25,6 +21,7 @@ import {
25
21
  createSystemToolHandlers,
26
22
  registerAgentRoutes,
27
23
  } from "./agent/routes.js";
24
+ import { registerAgentPlatformRuntime } from "./agent/runtime-bootstrap.js";
28
25
  // @windy-module system.agent end
29
26
  import { registerAuthRoutes, registerAuthSessionRoute } from "./auth/routes.js";
30
27
  import { createAuthRuntime } from "./auth/runtime.js";
@@ -44,10 +41,6 @@ import { registerSystemOperationRoutes } from "./system/operations.js";
44
41
  import { createPostgresMonitorProbe } from "./system/postgres-monitoring.js";
45
42
  // @windy-module system.operations end
46
43
  import { registerSystemRoutes } from "./system/routes.js";
47
- import {
48
- createSystemRepositories,
49
- synchronizeBuiltInRoles,
50
- } from "./system/seed.js";
51
44
  // @windy-module system.scheduler begin
52
45
  import { registerSchedulerRoutes } from "./scheduler/routes.js";
53
46
  import { SchedulerWorker } from "./scheduler/worker.js";
@@ -59,25 +52,17 @@ import { registerProfileRoutes } from "./profile/routes.js";
59
52
  // @windy-module system.notification begin
60
53
  import { registerNotificationRoutes } from "./notification/routes.js";
61
54
  // @windy-module system.notification end
62
- // @windy-module work-order begin
63
- import {
64
- createExampleModuleRuntime,
65
- registerExampleModuleRoutes,
66
- workOrderModuleInitializer,
67
- } from "./example-modules.js";
68
- // @windy-module work-order end
69
- // @windy-module system.configuration begin
70
- import { bootstrapModuleConfiguration } from "./configuration/bootstrap.js";
71
- // @windy-module system.configuration end
72
55
  // @windy-module system.storage begin
73
- import {
74
- createStorageRuntime,
75
- platformUploadPolicies,
76
- } from "./storage/runtime.js";
56
+ import { createStorageRuntime } from "./storage/runtime.js";
77
57
  import { registerStorageRoutes } from "./storage/routes.js";
78
58
  // @windy-module system.storage end
79
- import { validateServerModuleComposition } from "./module-composition.js";
80
- import { initializeModuleHosts } from "./module-host.js";
59
+ import { validateInstalledRuntime } from "./module-runtime-validation.js";
60
+ import {
61
+ adaptModuleSearchProviders,
62
+ createModuleTaskDefinitions,
63
+ createModuleToolHandlerMap,
64
+ installModuleRoutes,
65
+ } from "./module-host.js";
81
66
  // @windy-module system.search begin
82
67
  import { createSearchHost } from "./search/host.js";
83
68
  // @windy-module system.search end
@@ -88,6 +73,7 @@ import {
88
73
  createPlatformObservability,
89
74
  logPlatformStartup,
90
75
  } from "./observability/bootstrap.js";
76
+ import { bootstrapServerModules } from "./module-bootstrap.js";
91
77
 
92
78
  const logger = pino({ name: "windy-server" });
93
79
  const platformConfig = loadPlatformConfig();
@@ -101,45 +87,16 @@ const persistence = createServerPersistence(
101
87
  // @windy-module system.audit begin
102
88
  await persistence?.auditWriter.recover();
103
89
  // @windy-module system.audit end
104
- // @windy-module system.configuration begin
105
- const configurationRuntime = await bootstrapModuleConfiguration(
106
- platformConfig,
107
- persistence?.configVersionRepository,
108
- );
109
- // @windy-module system.configuration end
110
- const foundationSnapshot = createFoundationSnapshot(platformConfig);
111
- // @windy-module system.configuration begin
112
- configurationRuntime.bindFoundation(foundationSnapshot);
113
- // @windy-module system.configuration end
114
- const foundationModules = loadFoundationModules();
115
- const moduleHosts = await initializeModuleHosts({
116
- modules: foundationModules,
117
- initializers: [
118
- // @windy-module work-order begin
119
- workOrderModuleInitializer,
120
- // @windy-module work-order end
121
- ],
122
- baseUploadPolicies: [
123
- // @windy-module system.storage begin
124
- ...platformUploadPolicies,
125
- // @windy-module system.storage end
126
- ],
127
- });
128
- void moduleHosts;
129
- const systemRepositories = createSystemRepositories(foundationSnapshot, {
130
- db: persistence?.db,
131
- });
132
- const featureSync = await systemRepositories.features.syncManifest(
133
- foundationSnapshot.features,
134
- );
135
- await systemRepositories.menuRegistry.synchronize({
136
- id: "system_bootstrap",
137
- type: "service",
138
- name: "平台初始化",
139
- roleCodes: ["system"],
140
- permissionKeys: ["system.menu.manage"],
141
- });
142
- await synchronizeBuiltInRoles(systemRepositories, foundationSnapshot);
90
+ const {
91
+ // @windy-module system.configuration begin
92
+ configurationRuntime,
93
+ // @windy-module system.configuration end
94
+ featureSync,
95
+ foundationModules,
96
+ foundationSnapshot,
97
+ moduleHosts,
98
+ systemRepositories,
99
+ } = await bootstrapServerModules(platformConfig, persistence);
143
100
  // @windy-module system.license begin
144
101
  const platformLicenseOptions = createPlatformLicenseRuntimeOptions(process.env);
145
102
  const platformLicenseRuntime = new PlatformLicenseRuntime(
@@ -195,33 +152,18 @@ const platformSettings = createPlatformSettingsRuntime(persistence?.db, {
195
152
  logoUrl: platformConfig.logoUrl,
196
153
  });
197
154
  // @windy-module system.settings end
198
- // @windy-module work-order begin
199
- const exampleModuleRuntime = createExampleModuleRuntime({
200
- features: foundationSnapshot.features,
201
- departments: systemRepositories.departments,
202
- db: persistence?.db,
203
- });
204
- // @windy-module work-order end
205
- // @windy-module system.search begin
206
- const businessSearchProviders = [
207
- // @windy-module work-order begin
208
- ...(exampleModuleRuntime?.providers || []),
209
- // @windy-module work-order end
210
- ];
211
- // @windy-module system.search end
212
155
  // @windy-module system.search begin
213
156
  const searchHost = createSearchHost(
214
157
  foundationModules,
215
- { providers: businessSearchProviders },
158
+ { providers: adaptModuleSearchProviders(moduleHosts) },
216
159
  runtime,
217
160
  );
218
161
  // @windy-module system.search end
219
162
  // @windy-module system.scheduler begin
220
- const businessTaskDefinitions = [
221
- // @windy-module work-order begin
222
- ...(exampleModuleRuntime?.tasks || []),
223
- // @windy-module work-order end
224
- ];
163
+ const moduleTaskDefinitions = createModuleTaskDefinitions(
164
+ moduleHosts,
165
+ foundationSnapshot.features,
166
+ );
225
167
  const {
226
168
  bulkDataService,
227
169
  scheduler,
@@ -229,13 +171,14 @@ const {
229
171
  notifications: notificationRepository,
230
172
  workflow: workflowService,
231
173
  governedExportService,
174
+ durable: durableJobRuntime,
232
175
  } = await createApplicationServices({
233
176
  persistence,
234
177
  systemRepositories,
235
178
  foundation: foundationSnapshot,
236
179
  runtime,
237
180
  storage: storageRuntime,
238
- exampleTasks: businessTaskDefinitions,
181
+ moduleTasks: moduleTaskDefinitions,
239
182
  bulkRetentionDays: readPositiveInt(process.env.BULK_DATA_RETENTION_DAYS, 7),
240
183
  installModuleDurableHandlers: moduleHosts.installDurableHandlers,
241
184
  });
@@ -246,19 +189,15 @@ const schedulerWorker = new SchedulerWorker(
246
189
  (result) => observability.metrics.observeSchedulerTick(result),
247
190
  );
248
191
  // @windy-module system.scheduler end
249
-
250
192
  await bootstrapAdminPassword({
251
193
  authService,
252
194
  authRepository: persistence?.authRepository,
253
195
  repositories: systemRepositories,
254
196
  password: process.env.WINDY_BOOTSTRAP_ADMIN_PASSWORD,
255
197
  });
256
-
257
198
  const app = createPlatformHttpApp(runtime, observability);
258
199
  observability.register(app);
259
-
260
200
  registerAuthSessionRoute(app);
261
-
262
201
  registerAuthRoutes(app, authService, {
263
202
  sessionCookieName: platformConfig.security.sessionCookieName,
264
203
  secureCookie: process.env.NODE_ENV === "production",
@@ -345,8 +284,19 @@ registerSystemOperationRoutes(app, runtime, foundationSnapshot.features, {
345
284
  });
346
285
  // @windy-module system.operations end
347
286
  // @windy-module system.agent begin
348
- const toolHandlers = createSystemToolHandlers(systemRepositories, runtime);
287
+ const toolHandlers = new Map([
288
+ ...createSystemToolHandlers(systemRepositories, runtime),
289
+ ...createModuleToolHandlerMap(moduleHosts),
290
+ ]);
349
291
  registerAgentRoutes(app, foundationModules, toolHandlers, runtime);
292
+ registerAgentPlatformRuntime({
293
+ app,
294
+ platformConfig,
295
+ modules: foundationModules,
296
+ hosts: moduleHosts,
297
+ runtime,
298
+ toolHandlers,
299
+ });
350
300
  // @windy-module system.agent end
351
301
  // @windy-module system.scheduler begin
352
302
  registerSchedulerRoutes(app, scheduler, runtime, foundationSnapshot.features);
@@ -360,25 +310,28 @@ registerBulkDataRoutes(
360
310
  );
361
311
  // @windy-module system.bulk-data end
362
312
  // @windy-module work-order begin
363
- registerExampleModuleRoutes({
364
- app,
313
+ installModuleRoutes(moduleHosts, app, {
365
314
  runtime,
366
- moduleRuntime: exampleModuleRuntime,
315
+ features: foundationSnapshot.features,
316
+ jobs: durableJobRuntime.service,
317
+ storage: {
318
+ uploads: storageRuntime.uploads,
319
+ files: storageRuntime.files,
320
+ },
367
321
  });
368
322
  // @windy-module work-order end
369
323
 
370
- validateServerModuleComposition({
324
+ validateInstalledRuntime({
371
325
  modules: foundationModules,
372
326
  routes: app.routes,
373
327
  // @windy-module system.scheduler begin
374
328
  tasks: scheduledTaskDefinitions,
375
329
  // @windy-module system.scheduler end
376
- // @windy-module system.agent begin
377
330
  tools: toolHandlers,
378
- // @windy-module system.agent end
379
331
  // @windy-module system.search begin
380
332
  providers: searchHost.providerKeys,
381
333
  // @windy-module system.search end
334
+ hosts: moduleHosts,
382
335
  });
383
336
 
384
337
  const port = process.env.PORT ? Number(process.env.PORT) : 9_747;
@@ -1,10 +1,29 @@
1
1
  // @windy-module work-order begin
2
- import { workOrderModule } from "@southwind-ai/example-work-order";
2
+ import { createWorkOrderModule } from "./example-modules.js";
3
3
  // @windy-module work-order end
4
- import type { ModuleFactory } from "@southwind-ai/modules";
4
+ import type { ModuleManifest } from "@southwind-ai/modules";
5
+ import type { InstallableServerModule } from "@southwind-ai/server-sdk";
6
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
7
+ import type { ManagedDepartment } from "./system/entities.js";
8
+ import type { EntityRepository } from "./system/repository.js";
5
9
 
6
- export const installedBusinessModuleFactories: ModuleFactory[] = [
10
+ export interface BusinessModuleRuntime {
11
+ readonly db?: NodePgDatabase;
12
+ readonly departments: EntityRepository<ManagedDepartment>;
13
+ }
14
+
15
+ interface InstalledBusinessModule extends InstallableServerModule<ModuleManifest> {
16
+ attach(runtime: BusinessModuleRuntime): void;
17
+ }
18
+
19
+ export const installedBusinessModules: readonly InstalledBusinessModule[] = [
7
20
  // @windy-module work-order begin
8
- workOrderModule,
21
+ createWorkOrderModule(),
9
22
  // @windy-module work-order end
10
23
  ];
24
+
25
+ export function attachInstalledBusinessModules(
26
+ runtime: BusinessModuleRuntime,
27
+ ): void {
28
+ for (const module of installedBusinessModules) module.attach(runtime);
29
+ }
@@ -0,0 +1,83 @@
1
+ import type { PlatformConfig } from "@southwind-ai/config";
2
+ // @windy-module system.configuration begin
3
+ import { bootstrapModuleConfiguration } from "./configuration/bootstrap.js";
4
+ // @windy-module system.configuration end
5
+ import {
6
+ createFoundationSnapshot,
7
+ loadFoundationModules,
8
+ } from "./foundation.js";
9
+ import {
10
+ attachInstalledBusinessModules,
11
+ installedBusinessModules,
12
+ } from "./installed-business-modules.js";
13
+ import {
14
+ initializeModuleHosts,
15
+ synchronizeModuleRolePresets,
16
+ } from "./module-host.js";
17
+ import type { ServerPersistence } from "./persistence.js";
18
+ // @windy-module system.storage begin
19
+ import { platformUploadPolicies } from "./storage/runtime.js";
20
+ // @windy-module system.storage end
21
+ import {
22
+ createSystemRepositories,
23
+ synchronizeBuiltInRoles,
24
+ } from "./system/seed.js";
25
+
26
+ export async function bootstrapServerModules(
27
+ platformConfig: PlatformConfig,
28
+ persistence: ServerPersistence | undefined,
29
+ ) {
30
+ // @windy-module system.configuration begin
31
+ const configurationRuntime = await bootstrapModuleConfiguration(
32
+ platformConfig,
33
+ persistence?.configVersionRepository,
34
+ );
35
+ // @windy-module system.configuration end
36
+ const foundationSnapshot = createFoundationSnapshot(platformConfig);
37
+ // @windy-module system.configuration begin
38
+ configurationRuntime.bindFoundation(foundationSnapshot);
39
+ // @windy-module system.configuration end
40
+ const foundationModules = loadFoundationModules();
41
+ const moduleHosts = await initializeModuleHosts({
42
+ modules: foundationModules,
43
+ installations: installedBusinessModules,
44
+ baseUploadPolicies: [
45
+ // @windy-module system.storage begin
46
+ ...platformUploadPolicies,
47
+ // @windy-module system.storage end
48
+ ],
49
+ });
50
+ const systemRepositories = createSystemRepositories(foundationSnapshot, {
51
+ db: persistence?.db,
52
+ });
53
+ const featureSync = await systemRepositories.features.syncManifest(
54
+ foundationSnapshot.features,
55
+ );
56
+ await systemRepositories.menuRegistry.synchronize({
57
+ id: "system_bootstrap",
58
+ type: "service",
59
+ name: "平台初始化",
60
+ roleCodes: ["system"],
61
+ permissionKeys: ["system.menu.manage"],
62
+ });
63
+ await synchronizeBuiltInRoles(systemRepositories, foundationSnapshot);
64
+ await synchronizeModuleRolePresets(
65
+ systemRepositories,
66
+ foundationSnapshot,
67
+ moduleHosts.rolePresets,
68
+ );
69
+ attachInstalledBusinessModules({
70
+ db: persistence?.db,
71
+ departments: systemRepositories.departments,
72
+ });
73
+ return {
74
+ // @windy-module system.configuration begin
75
+ configurationRuntime,
76
+ // @windy-module system.configuration end
77
+ featureSync,
78
+ foundationModules,
79
+ foundationSnapshot,
80
+ moduleHosts,
81
+ systemRepositories,
82
+ };
83
+ }