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.
- package/README.md +5 -1
- package/dist/cli.js +392 -160
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/README.md +1 -0
- package/template/apps/agent-server/Dockerfile +20 -0
- package/template/apps/agent-server/README.md +46 -0
- package/template/apps/agent-server/pyproject.toml +44 -0
- package/template/apps/agent-server/src/southwind_agent_server/__init__.py +5 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +39 -0
- package/template/apps/agent-server/src/southwind_agent_server/app.py +263 -0
- package/template/apps/agent-server/src/southwind_agent_server/config.py +141 -0
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +99 -0
- package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +52 -0
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +85 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +197 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +246 -0
- package/template/apps/agent-server/src/southwind_agent_server/service.py +180 -0
- package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +317 -0
- package/template/apps/agent-server/src/southwind_agent_server/sse.py +41 -0
- package/template/apps/agent-server/src/southwind_agent_server/store.py +144 -0
- package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +80 -0
- package/template/apps/agent-server/tests/test_api.py +207 -0
- package/template/apps/agent-server/tests/test_config.py +56 -0
- package/template/apps/agent-server/tests/test_openai_provider.py +224 -0
- package/template/apps/agent-server/tests/test_sqlite_store.py +93 -0
- package/template/apps/agent-server/uv.lock +925 -0
- package/template/apps/server/src/agent/execution-grants.ts +89 -0
- package/template/apps/server/src/agent/remote-runtime.ts +128 -0
- package/template/apps/server/src/agent/run-contracts.ts +43 -0
- package/template/apps/server/src/agent/run-facade.test.ts +257 -0
- package/template/apps/server/src/agent/run-facade.ts +190 -0
- package/template/apps/server/src/agent/run-routes.ts +222 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +53 -0
- package/template/apps/server/src/agent/tool-host.test.ts +242 -0
- package/template/apps/server/src/agent/tool-host.ts +153 -0
- package/template/apps/server/src/application-services.ts +2 -2
- package/template/apps/server/src/audit/search-summary.ts +15 -8
- package/template/apps/server/src/example-modules.ts +58 -70
- package/template/apps/server/src/foundation.ts +8 -4
- package/template/apps/server/src/guards.test.ts +13 -0
- package/template/apps/server/src/guards.ts +12 -2
- package/template/apps/server/src/index.ts +48 -95
- package/template/apps/server/src/installed-business-modules.ts +23 -4
- package/template/apps/server/src/module-bootstrap.ts +83 -0
- package/template/apps/server/src/module-composition.test.ts +54 -1
- package/template/apps/server/src/module-composition.ts +58 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +67 -0
- package/template/apps/server/src/module-host/initialize.test.ts +192 -0
- package/template/apps/server/src/module-host/initialize.ts +328 -0
- package/template/apps/server/src/module-host/request-context.test.ts +66 -0
- package/template/apps/server/src/module-host/request-context.ts +156 -0
- package/template/apps/server/src/module-host/role-presets.test.ts +94 -0
- package/template/apps/server/src/module-host/role-presets.ts +76 -0
- package/template/apps/server/src/module-host/route-installer.test.ts +317 -0
- package/template/apps/server/src/module-host/route-installer.ts +97 -0
- package/template/apps/server/src/module-host/route-registration.test.ts +160 -0
- package/template/apps/server/src/module-host/search-providers.ts +31 -0
- package/template/apps/server/src/module-host/storage-port.test.ts +165 -0
- package/template/apps/server/src/module-host/storage-port.ts +59 -0
- package/template/apps/server/src/module-host/task-definitions.ts +41 -0
- package/template/apps/server/src/module-host/test-fixtures.ts +26 -0
- package/template/apps/server/src/module-host/tool-handlers.ts +25 -0
- package/template/apps/server/src/module-host.test.ts +202 -58
- package/template/apps/server/src/module-host.ts +35 -113
- package/template/apps/server/src/module-runtime-validation.ts +40 -0
- package/template/apps/server/src/route-guards.test.ts +112 -1
- package/template/apps/server/src/runtime-feature.ts +66 -31
- package/template/apps/server/src/runtime.test.ts +1 -0
- package/template/apps/server/src/runtime.ts +3 -1
- package/template/apps/server/src/system/built-in-roles.ts +1 -1
- package/template/apps/server/src/work-order/foundation-modules.test.ts +30 -10
- package/template/apps/server/src/work-order/routes.test.ts +30 -18
- package/template/apps/server/src/work-order/routes.ts +104 -135
- package/template/apps/server/src/work-order/search-api.test.ts +14 -3
- package/template/apps/server/src/work-order/search-provider.test.ts +23 -15
- package/template/apps/server/src/work-order/search-provider.ts +17 -12
- package/template/apps/server/src/work-order/task.test.ts +16 -11
- package/template/apps/web/Dockerfile +4 -1
- package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +4 -1
- package/template/apps/web/src/layout/GlobalWatermark.vue +2 -0
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +4 -1
- package/template/docker-compose.yml +25 -0
- package/template/docs/architecture/ai-runtime.md +744 -0
- package/template/docs/architecture/object-storage.md +12 -0
- package/template/docs/platform/agent-runtime.md +128 -0
- package/template/docs/platform/agent-tools.md +8 -1
- package/template/package.json +1 -0
- package/template/packages/config/index.test.ts +100 -0
- package/template/packages/config/index.ts +1 -0
- package/template/packages/config/package.json +2 -1
- package/template/packages/config/src/ai-openai-compatible.ts +131 -0
- package/template/packages/config/src/platform.ts +28 -1
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/runner.ts +7 -9
- package/template/packages/example-work-order/index.ts +1 -0
- package/template/packages/example-work-order/package.json +2 -0
- package/template/packages/example-work-order/src/server-module.ts +61 -0
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/jobs/src/types.ts +7 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/catalog-validation.test.ts +212 -0
- package/template/packages/modules/src/catalog-validation.ts +19 -2
- package/template/packages/modules/src/migration-bundle-validation.test.ts +191 -0
- package/template/packages/server-sdk/index.ts +53 -0
- package/template/packages/server-sdk/package.json +18 -3
- package/template/packages/server-sdk/src/actor.ts +15 -0
- package/template/packages/server-sdk/src/ai-operation-registration.ts +31 -0
- package/template/packages/server-sdk/src/audit-port.ts +22 -0
- package/template/packages/server-sdk/src/jobs-port.ts +24 -0
- package/template/packages/server-sdk/src/module-host.ts +41 -1
- package/template/packages/server-sdk/src/namespace.test.ts +26 -0
- package/template/packages/server-sdk/src/namespace.ts +13 -0
- package/template/packages/server-sdk/src/request-context.ts +21 -0
- package/template/packages/server-sdk/src/role-preset.ts +18 -0
- package/template/packages/server-sdk/src/route-definition.ts +26 -0
- package/template/packages/server-sdk/src/search-provider-port.ts +48 -0
- package/template/packages/server-sdk/src/storage-port.ts +25 -0
- package/template/packages/server-sdk/src/task-registration.ts +18 -0
- package/template/packages/server-sdk/src/tool-host-port.ts +22 -0
- package/template/packages/server-sdk/src/tool-registration.ts +29 -0
- package/template/packages/shared/index.ts +1 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/license-catalog.test.ts +73 -1
- package/template/packages/shared/src/license-catalog.ts +95 -0
- package/template/packages/{database/src/bundle-validation.ts → shared/src/migration-bundle-validation.ts} +2 -1
- package/template/apps/server/src/work-order/task.ts +0 -30
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
AUDIT_ACTION_DEFINITIONS,
|
|
4
|
+
featureKey,
|
|
5
|
+
licenseVersionKey,
|
|
6
|
+
LICENSE_CATALOG,
|
|
7
|
+
type AuditActionRegistration,
|
|
8
|
+
type FeatureFlagDefinition,
|
|
9
|
+
type LicenseCatalogDefinition,
|
|
10
|
+
} from "@southwind-ai/shared";
|
|
11
|
+
import {
|
|
12
|
+
composeAuditActionCatalog,
|
|
13
|
+
validateCapabilityCatalogs,
|
|
14
|
+
} from "./catalog-validation.js";
|
|
15
|
+
import type { ModuleManifest } from "./manifest.js";
|
|
16
|
+
|
|
17
|
+
const customCatalog: LicenseCatalogDefinition[] = [
|
|
18
|
+
{
|
|
19
|
+
key: licenseVersionKey("oem-basic"),
|
|
20
|
+
label: "OEM 基础版",
|
|
21
|
+
description: "宿主定制基础版本。",
|
|
22
|
+
order: 10,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: licenseVersionKey("oem-pro"),
|
|
26
|
+
label: "OEM 专业版",
|
|
27
|
+
description: "宿主定制专业版本。",
|
|
28
|
+
order: 20,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
describe("validateCapabilityCatalogs License 版本目录", () => {
|
|
33
|
+
test("宿主自定义 catalog 下 manifest 引用未知版本时抛错", () => {
|
|
34
|
+
const manifest = moduleWithFeature(
|
|
35
|
+
feature("business.report", ["standard"]),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(() => validateCapabilityCatalogs([manifest], customCatalog)).toThrow(
|
|
39
|
+
"Feature business.report 引用了未知 License 版本:standard",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("合法自定义 catalog 通过校验并作为快照目录返回", () => {
|
|
44
|
+
const manifest = moduleWithFeature(feature("business.report", ["oem-pro"]));
|
|
45
|
+
|
|
46
|
+
const catalogs = validateCapabilityCatalogs([manifest], customCatalog);
|
|
47
|
+
|
|
48
|
+
expect(catalogs.licenseVersions).toBe(customCatalog);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("未传 catalog 时回退默认三级目录,保持向后兼容", () => {
|
|
52
|
+
const manifest = moduleWithFeature(
|
|
53
|
+
feature("business.report", ["standard"]),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const catalogs = validateCapabilityCatalogs([manifest]);
|
|
57
|
+
|
|
58
|
+
expect(catalogs.licenseVersions).toBe(LICENSE_CATALOG);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("自定义 catalog 自身非法时快速报错", () => {
|
|
62
|
+
const manifest = moduleWithFeature(feature("business.report"));
|
|
63
|
+
|
|
64
|
+
expect(() =>
|
|
65
|
+
validateCapabilityCatalogs(
|
|
66
|
+
[manifest],
|
|
67
|
+
[customCatalog[0]!, customCatalog[0]!],
|
|
68
|
+
),
|
|
69
|
+
).toThrow("License 版本目录 Key 重复:oem-basic");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("Audit Action Catalog 开放注册", () => {
|
|
74
|
+
test("模块以定义对象贡献动作,聚合进目录且内置动作保留", () => {
|
|
75
|
+
const module = auditModule("ticket", [
|
|
76
|
+
{
|
|
77
|
+
key: "ticket.assign",
|
|
78
|
+
label: "工单指派",
|
|
79
|
+
description: "将工单指派给处理人。",
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const catalog = composeAuditActionCatalog([module]);
|
|
84
|
+
|
|
85
|
+
expect(catalog).toContainEqual({
|
|
86
|
+
key: "ticket.assign",
|
|
87
|
+
label: "工单指派",
|
|
88
|
+
description: "将工单指派给处理人。",
|
|
89
|
+
});
|
|
90
|
+
for (const builtIn of AUDIT_ACTION_DEFINITIONS) {
|
|
91
|
+
expect(catalog).toContainEqual(builtIn);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("目录输出结构符合 FoundationSnapshot 契约", () => {
|
|
96
|
+
const catalogs = validateCapabilityCatalogs([
|
|
97
|
+
auditModule("ticket", [
|
|
98
|
+
{
|
|
99
|
+
key: "ticket.assign",
|
|
100
|
+
label: "工单指派",
|
|
101
|
+
description: "将工单指派给处理人。",
|
|
102
|
+
},
|
|
103
|
+
"crud.create",
|
|
104
|
+
]),
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
expect(catalogs.licenseVersions).toBe(LICENSE_CATALOG);
|
|
108
|
+
expect(catalogs.auditActions.length).toBe(
|
|
109
|
+
AUDIT_ACTION_DEFINITIONS.length + 1,
|
|
110
|
+
);
|
|
111
|
+
for (const definition of catalogs.auditActions) {
|
|
112
|
+
expect(typeof definition.key).toBe("string");
|
|
113
|
+
expect(definition.label.trim()).not.toBe("");
|
|
114
|
+
expect(definition.description.trim()).not.toBe("");
|
|
115
|
+
}
|
|
116
|
+
const keys = catalogs.auditActions.map(({ key }) => key);
|
|
117
|
+
expect(keys).toEqual([...keys].sort((a, b) => a.localeCompare(b)));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("重复定义同一 Key 被拒绝", () => {
|
|
121
|
+
const first = auditModule("first", [
|
|
122
|
+
{ key: "ticket.assign", label: "工单指派", description: "定义一。" },
|
|
123
|
+
]);
|
|
124
|
+
const second = auditModule("second", [
|
|
125
|
+
{ key: "ticket.assign", label: "工单指派", description: "定义二。" },
|
|
126
|
+
]);
|
|
127
|
+
expect(() => composeAuditActionCatalog([first, second])).toThrow(
|
|
128
|
+
"审计动作定义重复:ticket.assign",
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const override = auditModule("override", [
|
|
132
|
+
{ key: "crud.create", label: "覆盖", description: "不允许。" },
|
|
133
|
+
]);
|
|
134
|
+
expect(() => composeAuditActionCatalog([override])).toThrow(
|
|
135
|
+
"审计动作定义重复:crud.create",
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("引用未定义动作被拒绝", () => {
|
|
140
|
+
const module = auditModule("broken", ["ticket.assign"]);
|
|
141
|
+
expect(() => composeAuditActionCatalog([module])).toThrow(
|
|
142
|
+
"引用了未定义审计动作:ticket.assign",
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("定义对象的 Key、名称和说明不能为空", () => {
|
|
147
|
+
const module = auditModule("empty", [
|
|
148
|
+
{ key: "ticket.assign", label: " ", description: "缺少名称。" },
|
|
149
|
+
]);
|
|
150
|
+
expect(() => composeAuditActionCatalog([module])).toThrow(
|
|
151
|
+
"审计动作 Key、名称和说明不能为空",
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
function auditModule(
|
|
157
|
+
name: string,
|
|
158
|
+
auditActions: AuditActionRegistration[],
|
|
159
|
+
): ModuleManifest {
|
|
160
|
+
return {
|
|
161
|
+
name,
|
|
162
|
+
version: "0.1.0",
|
|
163
|
+
label: name,
|
|
164
|
+
description: `${name} module`,
|
|
165
|
+
menus: [],
|
|
166
|
+
adminRoutes: [],
|
|
167
|
+
permissions: [],
|
|
168
|
+
apiPermissions: [],
|
|
169
|
+
features: [],
|
|
170
|
+
migrations: [],
|
|
171
|
+
schemaExports: [],
|
|
172
|
+
tasks: [],
|
|
173
|
+
tools: [],
|
|
174
|
+
providers: [],
|
|
175
|
+
auditActions,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function feature(
|
|
180
|
+
key: string,
|
|
181
|
+
allowedLicenseVersions?: string[],
|
|
182
|
+
): FeatureFlagDefinition {
|
|
183
|
+
return {
|
|
184
|
+
key: featureKey(key),
|
|
185
|
+
label: key,
|
|
186
|
+
module: "business",
|
|
187
|
+
visible: "visible",
|
|
188
|
+
enabled: true,
|
|
189
|
+
stage: "stable",
|
|
190
|
+
allowedLicenseVersions: allowedLicenseVersions?.map(licenseVersionKey),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function moduleWithFeature(feature: FeatureFlagDefinition): ModuleManifest {
|
|
195
|
+
return {
|
|
196
|
+
name: "business",
|
|
197
|
+
version: "1.0.0",
|
|
198
|
+
label: "业务模块",
|
|
199
|
+
description: "测试用业务模块。",
|
|
200
|
+
menus: [],
|
|
201
|
+
adminRoutes: [],
|
|
202
|
+
permissions: [],
|
|
203
|
+
apiPermissions: [],
|
|
204
|
+
features: [feature],
|
|
205
|
+
migrations: [],
|
|
206
|
+
schemaExports: [],
|
|
207
|
+
tasks: [],
|
|
208
|
+
tools: [],
|
|
209
|
+
providers: [],
|
|
210
|
+
auditActions: [],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -2,6 +2,9 @@ import {
|
|
|
2
2
|
AUDIT_ACTION_DEFINITIONS,
|
|
3
3
|
isAuditActionDefinition,
|
|
4
4
|
LICENSE_CATALOG,
|
|
5
|
+
normalizeManifestBundles,
|
|
6
|
+
validateAndOrderBundles,
|
|
7
|
+
validateLicenseCatalog,
|
|
5
8
|
type AuditActionDefinition,
|
|
6
9
|
type FeatureFlagDefinition,
|
|
7
10
|
type LicenseCatalogDefinition,
|
|
@@ -15,15 +18,29 @@ export interface PlatformCapabilityCatalogs {
|
|
|
15
18
|
|
|
16
19
|
export function validateCapabilityCatalogs(
|
|
17
20
|
modules: readonly ModuleManifest[],
|
|
21
|
+
licenseCatalog: readonly LicenseCatalogDefinition[] = LICENSE_CATALOG,
|
|
18
22
|
): PlatformCapabilityCatalogs {
|
|
19
23
|
const auditActions = composeAuditActionCatalog(modules);
|
|
20
|
-
|
|
24
|
+
validateLicenseCatalog(licenseCatalog);
|
|
25
|
+
validateFeatureCatalog(modules, licenseCatalog);
|
|
26
|
+
validateMigrationBundleCatalog(modules);
|
|
21
27
|
return {
|
|
22
28
|
auditActions,
|
|
23
|
-
licenseVersions:
|
|
29
|
+
licenseVersions: licenseCatalog,
|
|
24
30
|
};
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 启动期结构校验显式声明 migrationBundle 的模块;无 bundle 的旧式模块
|
|
35
|
+
* 由 drizzle 规范迁移管理,不参与模块级 Bundle 校验。
|
|
36
|
+
*/
|
|
37
|
+
export function validateMigrationBundleCatalog(
|
|
38
|
+
modules: readonly ModuleManifest[],
|
|
39
|
+
): void {
|
|
40
|
+
const bundled = modules.filter((module) => module.migrationBundle);
|
|
41
|
+
validateAndOrderBundles(normalizeManifestBundles(bundled));
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
export function composeAuditActionCatalog(
|
|
28
45
|
modules: readonly ModuleManifest[],
|
|
29
46
|
): AuditActionDefinition[] {
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
MIGRATION_BUNDLE_FORMAT_VERSION,
|
|
4
|
+
type BundleMigrationDefinition,
|
|
5
|
+
type MigrationBundle,
|
|
6
|
+
} from "@southwind-ai/shared";
|
|
7
|
+
import {
|
|
8
|
+
validateCapabilityCatalogs,
|
|
9
|
+
validateMigrationBundleCatalog,
|
|
10
|
+
} from "./catalog-validation.js";
|
|
11
|
+
import type { ModuleManifest } from "./manifest.js";
|
|
12
|
+
import { systemModuleFactories } from "./system-module-catalog.js";
|
|
13
|
+
|
|
14
|
+
describe("Migration Bundle 启动校验", () => {
|
|
15
|
+
test("无 bundle 的旧式模块保持兼容", () => {
|
|
16
|
+
const legacy = fixture("system");
|
|
17
|
+
legacy.schemaExports = [
|
|
18
|
+
{
|
|
19
|
+
key: "system.database.schema",
|
|
20
|
+
exportName: "systemSchema",
|
|
21
|
+
migrationIds: ["0000..0028"],
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
expect(() => validateCapabilityCatalogs([legacy])).not.toThrow();
|
|
26
|
+
expect(() =>
|
|
27
|
+
validateCapabilityCatalogs(
|
|
28
|
+
systemModuleFactories.map((factory) => factory()),
|
|
29
|
+
),
|
|
30
|
+
).not.toThrow();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("合法 bundle 通过校验,跨模块依赖需显式声明", () => {
|
|
34
|
+
const base = bundledModule("base", [migration("base", "base.0001")]);
|
|
35
|
+
const feature = bundledModule(
|
|
36
|
+
"feature",
|
|
37
|
+
[migration("feature", "feature.0001", ["base.0001"])],
|
|
38
|
+
["base"],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
expect(() => validateMigrationBundleCatalog([feature, base])).not.toThrow();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("重复 Migration ID 被拒绝", () => {
|
|
45
|
+
const first = bundledModule("first", [migration("first", "shared.0001")]);
|
|
46
|
+
const second = bundledModule("second", [
|
|
47
|
+
migration("second", "shared.0001"),
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
expect(() => validateMigrationBundleCatalog([first, second])).toThrow(
|
|
51
|
+
"Migration ID 重复:shared.0001",
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("缺少 checksum 被拒绝", () => {
|
|
56
|
+
const broken = bundledModule("broken", [
|
|
57
|
+
{ ...migration("broken", "broken.0001"), checksum: "" },
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
expect(() => validateMigrationBundleCatalog([broken])).toThrow(
|
|
61
|
+
"Migration broken.0001 缺少 checksum",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("非 semver 版本被拒绝", () => {
|
|
66
|
+
const broken = bundledModule("broken", [
|
|
67
|
+
migration("broken", "broken.0001"),
|
|
68
|
+
]);
|
|
69
|
+
broken.version = "0.1";
|
|
70
|
+
broken.migrationBundle = { ...broken.migrationBundle!, version: "0.1" };
|
|
71
|
+
|
|
72
|
+
expect(() => validateMigrationBundleCatalog([broken])).toThrow(
|
|
73
|
+
"Migration Bundle broken 版本无效:0.1",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("跨模块 Migration 依赖必须在 Bundle 中声明模块依赖", () => {
|
|
78
|
+
const base = bundledModule("base", [migration("base", "base.0001")]);
|
|
79
|
+
const feature = bundledModule("feature", [
|
|
80
|
+
migration("feature", "feature.0001", ["base.0001"]),
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
expect(() => validateMigrationBundleCatalog([base, feature])).toThrow(
|
|
84
|
+
"Migration feature.0001 跨模块依赖 base,但 Bundle 未声明该模块依赖",
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("模块依赖存在循环时被拒绝", () => {
|
|
89
|
+
const first = bundledModule(
|
|
90
|
+
"first",
|
|
91
|
+
[migration("first", "first.0001")],
|
|
92
|
+
["second"],
|
|
93
|
+
);
|
|
94
|
+
const second = bundledModule(
|
|
95
|
+
"second",
|
|
96
|
+
[migration("second", "second.0001")],
|
|
97
|
+
["first"],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(() => validateMigrationBundleCatalog([first, second])).toThrow(
|
|
101
|
+
"Migration Bundle 模块依赖存在循环",
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("单个 Bundle 内 Migration 顺序存在循环时被拒绝", () => {
|
|
106
|
+
const broken = bundledModule("broken", [
|
|
107
|
+
migration("broken", "broken.0001", ["broken.0002"]),
|
|
108
|
+
migration("broken", "broken.0002", ["broken.0001"]),
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
expect(() => validateMigrationBundleCatalog([broken])).toThrow(
|
|
112
|
+
"Migration Bundle broken 的迁移依赖存在循环",
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("Bundle 与 Manifest 声明不一致时被拒绝", () => {
|
|
117
|
+
const drift = bundledModule("drift", [migration("drift", "drift.0001")]);
|
|
118
|
+
drift.migrations = [];
|
|
119
|
+
|
|
120
|
+
expect(() => validateMigrationBundleCatalog([drift])).toThrow(
|
|
121
|
+
"Manifest drift 的 migrations 必须与 Migration Bundle 完全一致",
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const versionDrift = bundledModule("version-drift", [
|
|
125
|
+
migration("version-drift", "version-drift.0001"),
|
|
126
|
+
]);
|
|
127
|
+
versionDrift.migrationBundle = {
|
|
128
|
+
...versionDrift.migrationBundle!,
|
|
129
|
+
version: "9.9.9",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
expect(() => validateMigrationBundleCatalog([versionDrift])).toThrow(
|
|
133
|
+
"Migration Bundle version-drift 版本 9.9.9 与 Manifest 0.1.0 不一致",
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
function fixture(name: string): ModuleManifest {
|
|
139
|
+
return {
|
|
140
|
+
name,
|
|
141
|
+
version: "0.1.0",
|
|
142
|
+
label: name,
|
|
143
|
+
description: `${name} module`,
|
|
144
|
+
menus: [],
|
|
145
|
+
adminRoutes: [],
|
|
146
|
+
permissions: [],
|
|
147
|
+
apiPermissions: [],
|
|
148
|
+
features: [],
|
|
149
|
+
migrations: [],
|
|
150
|
+
schemaExports: [],
|
|
151
|
+
tasks: [],
|
|
152
|
+
tools: [],
|
|
153
|
+
providers: [],
|
|
154
|
+
auditActions: [],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function migration(
|
|
159
|
+
module: string,
|
|
160
|
+
id: string,
|
|
161
|
+
dependsOn: readonly string[] = [],
|
|
162
|
+
): BundleMigrationDefinition {
|
|
163
|
+
return {
|
|
164
|
+
id,
|
|
165
|
+
module,
|
|
166
|
+
description: `${id} migration`,
|
|
167
|
+
direction: "up",
|
|
168
|
+
checksum: `sha256:${id}`,
|
|
169
|
+
dependsOn,
|
|
170
|
+
run: async () => {},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function bundledModule(
|
|
175
|
+
name: string,
|
|
176
|
+
migrations: BundleMigrationDefinition[],
|
|
177
|
+
dependencies: string[] = [],
|
|
178
|
+
): ModuleManifest {
|
|
179
|
+
const manifest = fixture(name);
|
|
180
|
+
manifest.dependencies = dependencies;
|
|
181
|
+
manifest.migrations = [...migrations];
|
|
182
|
+
const bundle: MigrationBundle = {
|
|
183
|
+
formatVersion: MIGRATION_BUNDLE_FORMAT_VERSION,
|
|
184
|
+
module: name,
|
|
185
|
+
version: manifest.version,
|
|
186
|
+
dependencies,
|
|
187
|
+
migrations,
|
|
188
|
+
};
|
|
189
|
+
manifest.migrationBundle = bundle;
|
|
190
|
+
return manifest;
|
|
191
|
+
}
|
|
@@ -1,5 +1,58 @@
|
|
|
1
|
+
export type { ModuleActor } from "./src/actor.js";
|
|
1
2
|
export type {
|
|
3
|
+
ModuleAuditEmitter,
|
|
4
|
+
ModuleAuditEmitInput,
|
|
5
|
+
ModuleAuditTarget,
|
|
6
|
+
} from "./src/audit-port.js";
|
|
7
|
+
export type {
|
|
8
|
+
ModuleJobSubmitter,
|
|
9
|
+
ModuleJobSubmitInput,
|
|
10
|
+
ModuleJobSubmitResult,
|
|
11
|
+
} from "./src/jobs-port.js";
|
|
12
|
+
export type {
|
|
13
|
+
InstallableServerModule,
|
|
2
14
|
ModuleHost,
|
|
3
15
|
ModuleInitializer,
|
|
4
16
|
ModuleUploadPolicy,
|
|
5
17
|
} from "./src/module-host.js";
|
|
18
|
+
export { requireModuleNamespace } from "./src/namespace.js";
|
|
19
|
+
export type { ModuleRequestContext } from "./src/request-context.js";
|
|
20
|
+
export type { ModuleRolePreset } from "./src/role-preset.js";
|
|
21
|
+
export type {
|
|
22
|
+
ModuleRouteDefinition,
|
|
23
|
+
ModuleRouteHandler,
|
|
24
|
+
ModuleRouteMethod,
|
|
25
|
+
} from "./src/route-definition.js";
|
|
26
|
+
export type {
|
|
27
|
+
ModuleSearchContext,
|
|
28
|
+
ModuleSearchHealth,
|
|
29
|
+
ModuleSearchHit,
|
|
30
|
+
ModuleSearchPage,
|
|
31
|
+
ModuleSearchProvider,
|
|
32
|
+
ModuleSearchQuery,
|
|
33
|
+
} from "./src/search-provider-port.js";
|
|
34
|
+
export type {
|
|
35
|
+
ModuleStoragePort,
|
|
36
|
+
ModuleUploadSessionInput,
|
|
37
|
+
} from "./src/storage-port.js";
|
|
38
|
+
export type {
|
|
39
|
+
ModuleTaskHandler,
|
|
40
|
+
ModuleTaskHandlerRegistration,
|
|
41
|
+
ModuleTaskResult,
|
|
42
|
+
} from "./src/task-registration.js";
|
|
43
|
+
export type {
|
|
44
|
+
ModuleAgentToolHostPort,
|
|
45
|
+
ModuleAgentToolInvocation,
|
|
46
|
+
ModuleAgentToolResult,
|
|
47
|
+
} from "./src/tool-host-port.js";
|
|
48
|
+
export type {
|
|
49
|
+
ModuleToolContext,
|
|
50
|
+
ModuleToolHandlerRegistration,
|
|
51
|
+
ModuleToolResult,
|
|
52
|
+
} from "./src/tool-registration.js";
|
|
53
|
+
export type {
|
|
54
|
+
ModuleAiJsonValue,
|
|
55
|
+
ModuleAiOperationContext,
|
|
56
|
+
ModuleAiOperationHandlerRegistration,
|
|
57
|
+
ModuleAiOperationPlan,
|
|
58
|
+
} from "./src/ai-operation-registration.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@southwind-ai/server-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -8,13 +8,28 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"index.ts",
|
|
11
|
-
"src/
|
|
11
|
+
"src/actor.ts",
|
|
12
|
+
"src/ai-operation-registration.ts",
|
|
13
|
+
"src/audit-port.ts",
|
|
14
|
+
"src/jobs-port.ts",
|
|
15
|
+
"src/module-host.ts",
|
|
16
|
+
"src/namespace.ts",
|
|
17
|
+
"src/request-context.ts",
|
|
18
|
+
"src/role-preset.ts",
|
|
19
|
+
"src/route-definition.ts",
|
|
20
|
+
"src/search-provider-port.ts",
|
|
21
|
+
"src/storage-port.ts",
|
|
22
|
+
"src/task-registration.ts",
|
|
23
|
+
"src/tool-registration.ts",
|
|
24
|
+
"src/tool-host-port.ts"
|
|
12
25
|
],
|
|
13
26
|
"scripts": {
|
|
27
|
+
"test": "bun test",
|
|
14
28
|
"typecheck": "tsc --noEmit --project tsconfig.json"
|
|
15
29
|
},
|
|
16
30
|
"dependencies": {
|
|
17
|
-
"@southwind-ai/jobs": "workspace:*"
|
|
31
|
+
"@southwind-ai/jobs": "workspace:*",
|
|
32
|
+
"@southwind-ai/storage": "workspace:*"
|
|
18
33
|
},
|
|
19
34
|
"exports": {
|
|
20
35
|
".": "./index.ts"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 当前操作者的只读视图,与 Server 现有 Actor 模型对齐。
|
|
3
|
+
*
|
|
4
|
+
* 视图只暴露身份、角色与权限;凭据状态、数据范围令牌等宿主内部字段不下发。
|
|
5
|
+
* 运行时对象由宿主颁发,模块不得自行构造或修改。
|
|
6
|
+
*/
|
|
7
|
+
export interface ModuleActor {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly type: "anonymous" | "user" | "service";
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly username?: string;
|
|
12
|
+
readonly displayName?: string;
|
|
13
|
+
readonly roleCodes: readonly string[];
|
|
14
|
+
readonly permissionKeys: readonly string[];
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ModuleActor } from "./actor.js";
|
|
2
|
+
|
|
3
|
+
export type ModuleAiJsonValue =
|
|
4
|
+
| string
|
|
5
|
+
| number
|
|
6
|
+
| boolean
|
|
7
|
+
| null
|
|
8
|
+
| ModuleAiJsonValue[]
|
|
9
|
+
| { [key: string]: ModuleAiJsonValue };
|
|
10
|
+
|
|
11
|
+
export interface ModuleAiOperationContext {
|
|
12
|
+
readonly actor: ModuleActor;
|
|
13
|
+
readonly requestId: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 业务模块只负责把类型化输入收敛为模型无关的指令和用户消息。
|
|
18
|
+
* Provider、Deployment、Secret、Execution Grant 与 Tool 授权均由宿主持有。
|
|
19
|
+
*/
|
|
20
|
+
export interface ModuleAiOperationPlan {
|
|
21
|
+
readonly instructions: string;
|
|
22
|
+
readonly userPrompt: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ModuleAiOperationHandlerRegistration {
|
|
26
|
+
readonly operationKey: string;
|
|
27
|
+
prepare(
|
|
28
|
+
input: ModuleAiJsonValue,
|
|
29
|
+
context: ModuleAiOperationContext,
|
|
30
|
+
): Promise<ModuleAiOperationPlan> | ModuleAiOperationPlan;
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块审计事件的受控写入入口。
|
|
3
|
+
*
|
|
4
|
+
* 只允许 emit 本模块 Manifest `auditActions` 已注册的动作;越权动作在调用时抛错。
|
|
5
|
+
* 事件由宿主补齐 Actor、RequestId 与时间戳并写入统一 Audit Sink。
|
|
6
|
+
*/
|
|
7
|
+
export interface ModuleAuditTarget {
|
|
8
|
+
readonly type: string;
|
|
9
|
+
readonly id?: string;
|
|
10
|
+
readonly label?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ModuleAuditEmitInput {
|
|
14
|
+
readonly target?: ModuleAuditTarget;
|
|
15
|
+
readonly metadata?: Record<string, unknown>;
|
|
16
|
+
/** 关键事件走宿主 Critical 写入策略(写入失败会抛出)。 */
|
|
17
|
+
readonly critical?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ModuleAuditEmitter {
|
|
21
|
+
emit(action: string, input?: ModuleAuditEmitInput): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块提交 Durable Job 的窄接口。
|
|
3
|
+
*
|
|
4
|
+
* 只能提交到本模块命名空间的 Handler;Queue 由宿主强制为模块命名空间,
|
|
5
|
+
* 提交人由宿主以当前 Actor 身份补齐,模块无法伪造。
|
|
6
|
+
*/
|
|
7
|
+
export interface ModuleJobSubmitInput {
|
|
8
|
+
readonly handlerKey: string;
|
|
9
|
+
readonly idempotencyKey: string;
|
|
10
|
+
readonly payload: Record<string, unknown>;
|
|
11
|
+
readonly priority?: number;
|
|
12
|
+
readonly maxAttempts?: number;
|
|
13
|
+
readonly timeoutSeconds?: number;
|
|
14
|
+
readonly availableAt?: Date;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ModuleJobSubmitResult {
|
|
18
|
+
readonly jobId: string;
|
|
19
|
+
readonly created: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ModuleJobSubmitter {
|
|
23
|
+
submit(input: ModuleJobSubmitInput): Promise<ModuleJobSubmitResult>;
|
|
24
|
+
}
|