@southwind-ai/shared 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -47,3 +47,6 @@ export * from "./src/diagnostics.js";
47
47
  // @windy-module system.agent begin
48
48
  export * from "./src/agent-workflow-presets.js";
49
49
  // @windy-module system.agent end
50
+
51
+ export * from "./src/environment-assessment.js";
52
+ export * from "./src/environment-policy.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/shared",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -0,0 +1,68 @@
1
+ /** Host evidence is supplied by an explicitly mounted collector report, never inferred from a container. */
2
+ export const HOST_PROBE_KEYS = [
3
+ "cpu",
4
+ "memory",
5
+ "blockDevices",
6
+ "disks",
7
+ "mounts",
8
+ "network",
9
+ "systemd",
10
+ "docker",
11
+ "compose",
12
+ "containers",
13
+ "gpu",
14
+ ] as const;
15
+ export type HostProbeKey = (typeof HOST_PROBE_KEYS)[number];
16
+ export interface HostDiagnosticProbe {
17
+ status:
18
+ | "ok"
19
+ | "missing"
20
+ | "permission-denied"
21
+ | "timeout"
22
+ | "failed"
23
+ | "unsupported";
24
+ exitCode: number | null;
25
+ output: string;
26
+ truncated: boolean;
27
+ }
28
+ export interface HostDiagnosticsReport {
29
+ schemaVersion: 1;
30
+ scope: "host";
31
+ collector: "windy-host-diagnostics-v1";
32
+ collectedAt: string;
33
+ facts: {
34
+ os: string;
35
+ distribution?: string;
36
+ kernel: string;
37
+ architecture: string;
38
+ cpuLogicalCount: number;
39
+ memoryTotalBytes: number | null;
40
+ /** Present only when a successful GPU query establishes the count. */
41
+ gpuCount?: number;
42
+ };
43
+ probes: Record<HostProbeKey, HostDiagnosticProbe>;
44
+ deployment?: {
45
+ status: "available" | "invalid" | "unreadable";
46
+ releaseId?: string;
47
+ gitSha?: string;
48
+ platform?: string;
49
+ };
50
+ }
51
+ export interface EnvironmentSnapshot {
52
+ generatedAt: string;
53
+ process: {
54
+ scope: "process";
55
+ platform: string;
56
+ architecture: string;
57
+ nodeVersion: string;
58
+ bunVersion?: string;
59
+ containerized: boolean;
60
+ logicalCpuCount: number;
61
+ memoryTotalBytes: number;
62
+ };
63
+ host: {
64
+ status: "available" | "not-configured" | "invalid" | "stale" | "unreadable";
65
+ message: string;
66
+ report?: HostDiagnosticsReport;
67
+ };
68
+ }
@@ -0,0 +1,205 @@
1
+ /** Deployment requirements are recommendations, not authorization or probe results. */
2
+ export interface FeatureEnvironmentPolicy {
3
+ key: string;
4
+ title: string;
5
+ execution: "none" | "cpu" | "local-gpu" | "remote";
6
+ recommendedEnabled?: boolean;
7
+ supportedPlatforms?: string[];
8
+ supportedArchitectures?: string[];
9
+ minMemoryGiB?: number;
10
+ requiredEnvironmentKeys?: string[];
11
+ }
12
+ export const AI_RUNTIME_KINDS = [
13
+ "agent",
14
+ "document",
15
+ "speech",
16
+ "embedding",
17
+ ] as const;
18
+ export type AiRuntimeKind = (typeof AI_RUNTIME_KINDS)[number];
19
+ export type AiRuntimeSwitches = Partial<Record<AiRuntimeKind, boolean>>;
20
+
21
+ export interface AntivirusEnvironmentPolicy {
22
+ packaged: boolean;
23
+ enabled: boolean;
24
+ }
25
+
26
+ export interface EnvironmentPolicy {
27
+ schemaVersion: 1;
28
+ build: { modules: string[]; runtimes?: AiRuntimeSwitches };
29
+ runtime?: AiRuntimeSwitches;
30
+ antivirus?: AntivirusEnvironmentPolicy;
31
+ features: FeatureEnvironmentPolicy[];
32
+ }
33
+ export interface FeatureEnvironmentAssessment {
34
+ key: string;
35
+ title: string;
36
+ status:
37
+ | "hardware"
38
+ | "software"
39
+ | "not-packaged"
40
+ | "configuration"
41
+ | "unknown"
42
+ | "available"
43
+ | "disabled";
44
+ message: string;
45
+ evidence: string[];
46
+ recommendedEnabled?: boolean;
47
+ }
48
+ export interface EnvironmentAssessmentSnapshot {
49
+ status: "available" | "not-configured" | "invalid";
50
+ message: string;
51
+ features: FeatureEnvironmentAssessment[];
52
+ }
53
+
54
+ function object(
55
+ value: unknown,
56
+ required: string[],
57
+ optional: string[] = [],
58
+ ): Record<string, unknown> {
59
+ if (!value || typeof value !== "object" || Array.isArray(value))
60
+ throw new Error("环境预设必须为对象");
61
+ const record = value as Record<string, unknown>;
62
+ if (
63
+ required.some((key) => !Object.hasOwn(record, key)) ||
64
+ Object.keys(record).some((key) => ![...required, ...optional].includes(key))
65
+ )
66
+ throw new Error("环境预设缺少必需字段或包含未知字段");
67
+ return record;
68
+ }
69
+ function strings(value: unknown, pattern: RegExp, max = 128): string[] {
70
+ if (
71
+ !Array.isArray(value) ||
72
+ value.length > max ||
73
+ value.some(
74
+ (item) =>
75
+ typeof item !== "string" || item.length > 128 || !pattern.test(item),
76
+ ) ||
77
+ new Set(value).size !== value.length
78
+ )
79
+ throw new Error("环境预设列表无效或重复");
80
+ return value as string[];
81
+ }
82
+ export function parseEnvironmentPolicy(value: unknown): EnvironmentPolicy {
83
+ const root = object(
84
+ value,
85
+ ["schemaVersion", "build", "features"],
86
+ ["runtime", "antivirus"],
87
+ );
88
+ if (root.schemaVersion !== 1) throw new Error("不支持的环境预设版本");
89
+ const build = object(root.build, ["modules"], ["runtimes"]);
90
+ const modules = strings(build.modules, /^[a-z][a-z0-9.-]*$/);
91
+ if (!Array.isArray(root.features) || root.features.length > 128)
92
+ throw new Error("功能评估配置超出上限");
93
+ const features = root.features.map((value): FeatureEnvironmentPolicy => {
94
+ const row = object(
95
+ value,
96
+ ["key", "title", "execution"],
97
+ [
98
+ "recommendedEnabled",
99
+ "supportedPlatforms",
100
+ "supportedArchitectures",
101
+ "minMemoryGiB",
102
+ "requiredEnvironmentKeys",
103
+ ],
104
+ );
105
+ const [key] = strings([row.key], /^[a-z][a-z0-9.-]*$/);
106
+ if (
107
+ typeof row.title !== "string" ||
108
+ !row.title.trim() ||
109
+ row.title.length > 120
110
+ )
111
+ throw new Error("功能评估标题无效");
112
+ if (!["none", "cpu", "local-gpu", "remote"].includes(String(row.execution)))
113
+ throw new Error("功能执行方式无效");
114
+ if (
115
+ row.recommendedEnabled !== undefined &&
116
+ typeof row.recommendedEnabled !== "boolean"
117
+ )
118
+ throw new Error("推荐开关必须为布尔值");
119
+ if (
120
+ row.minMemoryGiB !== undefined &&
121
+ (typeof row.minMemoryGiB !== "number" ||
122
+ !Number.isFinite(row.minMemoryGiB) ||
123
+ row.minMemoryGiB <= 0 ||
124
+ row.minMemoryGiB > 65536)
125
+ )
126
+ throw new Error("最低内存要求无效");
127
+ return {
128
+ key: key!,
129
+ title: row.title,
130
+ execution: row.execution as FeatureEnvironmentPolicy["execution"],
131
+ ...(row.recommendedEnabled === undefined
132
+ ? {}
133
+ : { recommendedEnabled: row.recommendedEnabled as boolean }),
134
+ ...(row.minMemoryGiB === undefined
135
+ ? {}
136
+ : { minMemoryGiB: row.minMemoryGiB as number }),
137
+ ...(row.supportedPlatforms === undefined
138
+ ? {}
139
+ : {
140
+ supportedPlatforms: strings(
141
+ row.supportedPlatforms,
142
+ /^(linux|darwin|win32)$/,
143
+ ),
144
+ }),
145
+ ...(row.supportedArchitectures === undefined
146
+ ? {}
147
+ : {
148
+ supportedArchitectures: strings(
149
+ row.supportedArchitectures,
150
+ /^(x64|arm64|arm|ia32|ppc64|s390x|riscv64|loong64)$/,
151
+ ),
152
+ }),
153
+ ...(row.requiredEnvironmentKeys === undefined
154
+ ? {}
155
+ : {
156
+ requiredEnvironmentKeys: strings(
157
+ row.requiredEnvironmentKeys,
158
+ /^[A-Z][A-Z0-9_]*$/,
159
+ ),
160
+ }),
161
+ };
162
+ });
163
+ if (new Set(features.map(({ key }) => key)).size !== features.length)
164
+ throw new Error("功能评估 Key 重复");
165
+ const runtime = Object.hasOwn(root, "runtime")
166
+ ? parseRuntimeSwitches(root.runtime)
167
+ : undefined;
168
+ const runtimes = Object.hasOwn(build, "runtimes")
169
+ ? parseRuntimeSwitches(build.runtimes)
170
+ : undefined;
171
+ for (const kind of AI_RUNTIME_KINDS) {
172
+ if (runtimes?.[kind] === false && runtime?.[kind] === true)
173
+ throw new Error(`运行时未随包交付:${kind}`);
174
+ }
175
+ const antivirus = Object.hasOwn(root, "antivirus")
176
+ ? parseAntivirusPolicy(root.antivirus)
177
+ : undefined;
178
+ return {
179
+ schemaVersion: 1,
180
+ build: { modules, ...(runtimes ? { runtimes } : {}) },
181
+ features,
182
+ ...(runtime ? { runtime } : {}),
183
+ ...(antivirus ? { antivirus } : {}),
184
+ };
185
+ }
186
+
187
+ function parseRuntimeSwitches(value: unknown): AiRuntimeSwitches {
188
+ const switches = object(value, [], [...AI_RUNTIME_KINDS]);
189
+ const runtime: AiRuntimeSwitches = {};
190
+ for (const kind of AI_RUNTIME_KINDS) {
191
+ if (!Object.hasOwn(switches, kind)) continue;
192
+ const enabled = switches[kind];
193
+ if (typeof enabled !== "boolean") throw new Error("运行时开关必须为布尔值");
194
+ runtime[kind] = enabled;
195
+ }
196
+ return runtime;
197
+ }
198
+
199
+ function parseAntivirusPolicy(value: unknown): AntivirusEnvironmentPolicy {
200
+ const { packaged, enabled } = object(value, ["packaged", "enabled"]);
201
+ if (typeof packaged !== "boolean" || typeof enabled !== "boolean")
202
+ throw new Error("病毒扫描交付和启用设置必须为布尔值");
203
+ if (enabled && !packaged) throw new Error("病毒扫描未随项目交付,不能启用");
204
+ return { packaged, enabled };
205
+ }
@@ -101,6 +101,13 @@ export const LICENSE_CONSOLE_CAPABILITIES = {
101
101
  apis: ["POST /api/auth/sessions/:id/revoke"],
102
102
  auditActions: ["auth.session.revoke"],
103
103
  },
104
+ profile: {
105
+ feature: "license.profile",
106
+ permission: "license.profile.update",
107
+ entry: "profile",
108
+ apis: ["PATCH /api/auth/me"],
109
+ auditActions: ["license.operator.updated"],
110
+ },
104
111
  audit: {
105
112
  feature: "license.audit",
106
113
  permission: "license.audit.read",
package/src/license.ts CHANGED
@@ -205,6 +205,7 @@ export type LicenseOperatorRole =
205
205
  export type LicenseOperatorPermission =
206
206
  | "license.record.read"
207
207
  | "license.audit.read"
208
+ | "license.profile.update"
208
209
  | "license.issue"
209
210
  | "license.project.manage"
210
211
  | "license.key.rotate"
@@ -20,7 +20,8 @@ export interface ModuleConfigFieldDescriptor {
20
20
  path: string;
21
21
  label: string;
22
22
  description: string;
23
- input: "boolean" | "integer" | "text";
23
+ input: "boolean" | "integer" | "text" | "time-zone";
24
+ unit?: "bytes";
24
25
  applyMode?: ModuleConfigApplyMode;
25
26
  minimum?: number;
26
27
  maximum?: number;