create-windy 0.2.29 → 0.2.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +385 -9
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/README.md +3 -0
- package/template/apps/agent-server/README.md +4 -2
- package/template/apps/agent-server/src/southwind_agent_server/config.py +48 -0
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +22 -2
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +10 -1
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +20 -2
- package/template/apps/agent-server/src/southwind_agent_server/service.py +2 -0
- package/template/apps/agent-server/tests/test_api.py +28 -0
- package/template/apps/agent-server/tests/test_config.py +46 -0
- package/template/apps/agent-server/tests/test_openai_provider.py +61 -1
- package/template/apps/server/src/agent/remote-runtime.test.ts +14 -0
- package/template/apps/server/src/agent/remote-runtime.ts +2 -0
- package/template/apps/server/src/agent/run-contracts.ts +6 -0
- package/template/apps/server/src/agent/run-facade.ts +6 -0
- package/template/apps/server/src/agent/tool-host.test.ts +25 -0
- package/template/apps/server/src/agent/tool-host.ts +10 -1
- package/template/apps/server/src/work-order/drizzle-repository.ts +1 -2
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +7 -5
- package/template/docker-compose.yml +1 -0
- package/template/docs/architecture/ai-egress.md +80 -0
- package/template/docs/architecture/ai-runtime.md +13 -1
- package/template/docs/architecture/migration-bundle.md +151 -0
- package/template/docs/platform/agent-runtime.md +20 -1
- package/template/module-schema.lineages.json +8 -0
- package/template/packages/config/index.test.ts +20 -0
- package/template/packages/config/src/ai-openai-compatible.ts +58 -2
- package/template/packages/database/drizzle/0033_rich_george_stacy.sql +2 -0
- package/template/packages/database/drizzle/meta/0033_snapshot.json +7239 -0
- package/template/packages/database/drizzle/meta/_journal.json +7 -0
- package/template/packages/database/src/schema/index.ts +0 -3
- package/template/packages/example-work-order/drizzle/0000_bitter_the_santerians.sql +2 -0
- package/template/packages/example-work-order/drizzle/meta/0000_snapshot.json +179 -0
- package/template/packages/example-work-order/drizzle/meta/_journal.json +13 -0
- package/template/packages/example-work-order/drizzle.config.ts +8 -0
- package/template/packages/example-work-order/index.ts +1 -0
- package/template/packages/example-work-order/package.json +4 -0
- package/template/packages/{database/src/schema/work-orders.ts → example-work-order/src/drizzle-schema.ts} +16 -2
- package/template/packages/modules/index.ts +1 -0
- package/template/packages/modules/src/ai-network-policy.ts +50 -0
- package/template/packages/modules/src/ai-operation-composition.test.ts +50 -0
- package/template/packages/modules/src/ai-operation-composition.ts +2 -0
- package/template/packages/modules/src/manifest.ts +13 -1
|
@@ -232,6 +232,13 @@
|
|
|
232
232
|
"when": 1784922914250,
|
|
233
233
|
"tag": "0032_marvelous_pepper_potts",
|
|
234
234
|
"breakpoints": true
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"idx": 33,
|
|
238
|
+
"version": "7",
|
|
239
|
+
"when": 1784925586891,
|
|
240
|
+
"tag": "0033_rich_george_stacy",
|
|
241
|
+
"breakpoints": true
|
|
235
242
|
}
|
|
236
243
|
]
|
|
237
244
|
}
|
|
@@ -25,9 +25,6 @@ export * from "./platform-settings.js";
|
|
|
25
25
|
// @windy-module system.scheduler begin
|
|
26
26
|
export * from "./scheduler.js";
|
|
27
27
|
// @windy-module system.scheduler end
|
|
28
|
-
// @windy-module work-order begin
|
|
29
|
-
export * from "./work-orders.js";
|
|
30
|
-
// @windy-module work-order end
|
|
31
28
|
// @windy-module system.workflow begin
|
|
32
29
|
export * from "./workflow.js";
|
|
33
30
|
// @windy-module system.workflow end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "6dc0667a-7382-40b9-8036-681641b826df",
|
|
3
|
+
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
|
+
"version": "7",
|
|
5
|
+
"dialect": "postgresql",
|
|
6
|
+
"tables": {
|
|
7
|
+
"public.work_orders": {
|
|
8
|
+
"name": "work_orders",
|
|
9
|
+
"schema": "",
|
|
10
|
+
"columns": {
|
|
11
|
+
"id": {
|
|
12
|
+
"name": "id",
|
|
13
|
+
"type": "varchar(64)",
|
|
14
|
+
"primaryKey": true,
|
|
15
|
+
"notNull": true
|
|
16
|
+
},
|
|
17
|
+
"title": {
|
|
18
|
+
"name": "title",
|
|
19
|
+
"type": "varchar(120)",
|
|
20
|
+
"primaryKey": false,
|
|
21
|
+
"notNull": true
|
|
22
|
+
},
|
|
23
|
+
"description": {
|
|
24
|
+
"name": "description",
|
|
25
|
+
"type": "text",
|
|
26
|
+
"primaryKey": false,
|
|
27
|
+
"notNull": true
|
|
28
|
+
},
|
|
29
|
+
"priority": {
|
|
30
|
+
"name": "priority",
|
|
31
|
+
"type": "varchar(24)",
|
|
32
|
+
"primaryKey": false,
|
|
33
|
+
"notNull": true
|
|
34
|
+
},
|
|
35
|
+
"workflow_status": {
|
|
36
|
+
"name": "workflow_status",
|
|
37
|
+
"type": "varchar(32)",
|
|
38
|
+
"primaryKey": false,
|
|
39
|
+
"notNull": true
|
|
40
|
+
},
|
|
41
|
+
"owner_id": {
|
|
42
|
+
"name": "owner_id",
|
|
43
|
+
"type": "varchar(64)",
|
|
44
|
+
"primaryKey": false,
|
|
45
|
+
"notNull": true
|
|
46
|
+
},
|
|
47
|
+
"department_id": {
|
|
48
|
+
"name": "department_id",
|
|
49
|
+
"type": "varchar(64)",
|
|
50
|
+
"primaryKey": false,
|
|
51
|
+
"notNull": false
|
|
52
|
+
},
|
|
53
|
+
"status": {
|
|
54
|
+
"name": "status",
|
|
55
|
+
"type": "varchar(32)",
|
|
56
|
+
"primaryKey": false,
|
|
57
|
+
"notNull": true
|
|
58
|
+
},
|
|
59
|
+
"created_at": {
|
|
60
|
+
"name": "created_at",
|
|
61
|
+
"type": "timestamp with time zone",
|
|
62
|
+
"primaryKey": false,
|
|
63
|
+
"notNull": true,
|
|
64
|
+
"default": "now()"
|
|
65
|
+
},
|
|
66
|
+
"created_by": {
|
|
67
|
+
"name": "created_by",
|
|
68
|
+
"type": "varchar(64)",
|
|
69
|
+
"primaryKey": false,
|
|
70
|
+
"notNull": true
|
|
71
|
+
},
|
|
72
|
+
"updated_at": {
|
|
73
|
+
"name": "updated_at",
|
|
74
|
+
"type": "timestamp with time zone",
|
|
75
|
+
"primaryKey": false,
|
|
76
|
+
"notNull": true,
|
|
77
|
+
"default": "now()"
|
|
78
|
+
},
|
|
79
|
+
"updated_by": {
|
|
80
|
+
"name": "updated_by",
|
|
81
|
+
"type": "varchar(64)",
|
|
82
|
+
"primaryKey": false,
|
|
83
|
+
"notNull": true
|
|
84
|
+
},
|
|
85
|
+
"deleted_at": {
|
|
86
|
+
"name": "deleted_at",
|
|
87
|
+
"type": "timestamp with time zone",
|
|
88
|
+
"primaryKey": false,
|
|
89
|
+
"notNull": false
|
|
90
|
+
},
|
|
91
|
+
"deleted_by": {
|
|
92
|
+
"name": "deleted_by",
|
|
93
|
+
"type": "varchar(64)",
|
|
94
|
+
"primaryKey": false,
|
|
95
|
+
"notNull": false
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"indexes": {
|
|
99
|
+
"idx_work_orders_owner": {
|
|
100
|
+
"name": "idx_work_orders_owner",
|
|
101
|
+
"columns": [
|
|
102
|
+
{
|
|
103
|
+
"expression": "owner_id",
|
|
104
|
+
"isExpression": false,
|
|
105
|
+
"asc": true,
|
|
106
|
+
"nulls": "last"
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"isUnique": false,
|
|
110
|
+
"concurrently": false,
|
|
111
|
+
"method": "btree",
|
|
112
|
+
"with": {}
|
|
113
|
+
},
|
|
114
|
+
"idx_work_orders_department": {
|
|
115
|
+
"name": "idx_work_orders_department",
|
|
116
|
+
"columns": [
|
|
117
|
+
{
|
|
118
|
+
"expression": "department_id",
|
|
119
|
+
"isExpression": false,
|
|
120
|
+
"asc": true,
|
|
121
|
+
"nulls": "last"
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"isUnique": false,
|
|
125
|
+
"concurrently": false,
|
|
126
|
+
"method": "btree",
|
|
127
|
+
"with": {}
|
|
128
|
+
},
|
|
129
|
+
"idx_work_orders_status": {
|
|
130
|
+
"name": "idx_work_orders_status",
|
|
131
|
+
"columns": [
|
|
132
|
+
{
|
|
133
|
+
"expression": "status",
|
|
134
|
+
"isExpression": false,
|
|
135
|
+
"asc": true,
|
|
136
|
+
"nulls": "last"
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"isUnique": false,
|
|
140
|
+
"concurrently": false,
|
|
141
|
+
"method": "btree",
|
|
142
|
+
"with": {}
|
|
143
|
+
},
|
|
144
|
+
"idx_work_orders_workflow": {
|
|
145
|
+
"name": "idx_work_orders_workflow",
|
|
146
|
+
"columns": [
|
|
147
|
+
{
|
|
148
|
+
"expression": "workflow_status",
|
|
149
|
+
"isExpression": false,
|
|
150
|
+
"asc": true,
|
|
151
|
+
"nulls": "last"
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"isUnique": false,
|
|
155
|
+
"concurrently": false,
|
|
156
|
+
"method": "btree",
|
|
157
|
+
"with": {}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"foreignKeys": {},
|
|
161
|
+
"compositePrimaryKeys": {},
|
|
162
|
+
"uniqueConstraints": {},
|
|
163
|
+
"policies": {},
|
|
164
|
+
"checkConstraints": {},
|
|
165
|
+
"isRLSEnabled": false
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"enums": {},
|
|
169
|
+
"schemas": {},
|
|
170
|
+
"sequences": {},
|
|
171
|
+
"roles": {},
|
|
172
|
+
"policies": {},
|
|
173
|
+
"views": {},
|
|
174
|
+
"_meta": {
|
|
175
|
+
"columns": {},
|
|
176
|
+
"schemas": {},
|
|
177
|
+
"tables": {}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"db:generate": "drizzle-kit generate --config drizzle.config.ts"
|
|
8
|
+
},
|
|
6
9
|
"dependencies": {
|
|
7
10
|
"@southwind-ai/modules": "workspace:*",
|
|
8
11
|
"@southwind-ai/server-sdk": "workspace:*",
|
|
9
12
|
"@southwind-ai/shared": "workspace:*",
|
|
13
|
+
"drizzle-orm": "^0.45.2",
|
|
10
14
|
"zod": "^4.4.3"
|
|
11
15
|
},
|
|
12
16
|
"exports": {
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import { index, pgTable, text, varchar } from "drizzle-orm/pg-core";
|
|
2
|
-
|
|
1
|
+
import { index, pgTable, text, timestamp, varchar } from "drizzle-orm/pg-core";
|
|
2
|
+
|
|
3
|
+
const idColumn = () => varchar("id", { length: 64 });
|
|
4
|
+
const statusColumn = () => varchar("status", { length: 32 });
|
|
5
|
+
const auditColumns = {
|
|
6
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
7
|
+
.notNull()
|
|
8
|
+
.defaultNow(),
|
|
9
|
+
createdBy: varchar("created_by", { length: 64 }).notNull(),
|
|
10
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
11
|
+
.notNull()
|
|
12
|
+
.defaultNow(),
|
|
13
|
+
updatedBy: varchar("updated_by", { length: 64 }).notNull(),
|
|
14
|
+
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
|
15
|
+
deletedBy: varchar("deleted_by", { length: 64 }),
|
|
16
|
+
};
|
|
3
17
|
|
|
4
18
|
export const workOrders = pgTable(
|
|
5
19
|
"work_orders",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AiProviderEgressPolicy,
|
|
3
|
+
AiToolNetworkPolicy,
|
|
4
|
+
ModuleAiOperationDefinition,
|
|
5
|
+
} from "./manifest.js";
|
|
6
|
+
|
|
7
|
+
const providerForbidden: AiProviderEgressPolicy = Object.freeze({
|
|
8
|
+
mode: "forbidden",
|
|
9
|
+
});
|
|
10
|
+
const toolNetworkForbidden: AiToolNetworkPolicy = Object.freeze({
|
|
11
|
+
mode: "forbidden",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export function resolveProviderEgress(
|
|
15
|
+
operation: ModuleAiOperationDefinition,
|
|
16
|
+
): AiProviderEgressPolicy {
|
|
17
|
+
return operation.providerEgress ?? providerForbidden;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveToolNetworkPolicy(
|
|
21
|
+
operation: ModuleAiOperationDefinition,
|
|
22
|
+
): AiToolNetworkPolicy {
|
|
23
|
+
return operation.toolNetworkPolicy ?? toolNetworkForbidden;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function validateAiNetworkPolicy(
|
|
27
|
+
operation: ModuleAiOperationDefinition,
|
|
28
|
+
): void {
|
|
29
|
+
const provider = resolveProviderEgress(operation);
|
|
30
|
+
if (provider.mode === "configured-endpoint") {
|
|
31
|
+
if (!provider.deploymentKey.trim()) {
|
|
32
|
+
throw new Error(`${operation.key} Provider Deployment Key 不能为空`);
|
|
33
|
+
}
|
|
34
|
+
} else if (provider.mode !== "forbidden") {
|
|
35
|
+
throw new Error(`${operation.key} Provider 出网策略无效`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const tool = resolveToolNetworkPolicy(operation);
|
|
39
|
+
if (tool.mode !== "forbidden") {
|
|
40
|
+
throw new Error(`${operation.key} Tool 网络策略无效`);
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
operation.publicNetworkAccess === "allowed" &&
|
|
44
|
+
operation.providerEgress === undefined
|
|
45
|
+
) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`${operation.key} 旧 publicNetworkAccess=allowed 不能安全映射,请声明 providerEgress`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -98,6 +98,56 @@ describe("AI Operation Composition", () => {
|
|
|
98
98
|
expect(() => validateModuleComposition([module])).toThrow("Tool 不能为空");
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
test("Provider 与 Tool 网络边界独立且旧声明默认 fail closed", () => {
|
|
102
|
+
const module = fixture("business");
|
|
103
|
+
const legacy = operation("business.legacy");
|
|
104
|
+
module.aiOperations = [legacy];
|
|
105
|
+
expect(() => validateModuleComposition([module])).not.toThrow();
|
|
106
|
+
|
|
107
|
+
module.aiOperations = [
|
|
108
|
+
{
|
|
109
|
+
...operation("business.generate"),
|
|
110
|
+
publicNetworkAccess: undefined,
|
|
111
|
+
providerEgress: {
|
|
112
|
+
mode: "configured-endpoint",
|
|
113
|
+
deploymentKey: "ai.openai-compatible",
|
|
114
|
+
},
|
|
115
|
+
toolNetworkPolicy: { mode: "forbidden" },
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
expect(() => validateModuleComposition([module])).not.toThrow();
|
|
119
|
+
|
|
120
|
+
module.aiOperations[0] = {
|
|
121
|
+
...operation("business.generate"),
|
|
122
|
+
publicNetworkAccess: "allowed",
|
|
123
|
+
};
|
|
124
|
+
expect(() => validateModuleComposition([module])).toThrow("不能安全映射");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("拒绝空 Provider Deployment 与未知 Tool 网络模式", () => {
|
|
128
|
+
const module = fixture("business");
|
|
129
|
+
module.aiOperations = [
|
|
130
|
+
{
|
|
131
|
+
...operation("business.generate"),
|
|
132
|
+
providerEgress: {
|
|
133
|
+
mode: "configured-endpoint",
|
|
134
|
+
deploymentKey: " ",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
139
|
+
"Deployment Key 不能为空",
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
module.aiOperations[0] = {
|
|
143
|
+
...operation("business.generate"),
|
|
144
|
+
toolNetworkPolicy: { mode: "allow" },
|
|
145
|
+
} as unknown as ModuleAiOperationDefinition;
|
|
146
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
147
|
+
"Tool 网络策略无效",
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
101
151
|
test("Operation Handler 必须与 Manifest 声明一一对应", () => {
|
|
102
152
|
const module = fixture("business");
|
|
103
153
|
module.aiOperations = [operation("business.generate")];
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ModuleAiOperationDefinition,
|
|
3
3
|
ModuleManifest,
|
|
4
4
|
} from "./manifest.js";
|
|
5
|
+
import { validateAiNetworkPolicy } from "./ai-network-policy.js";
|
|
5
6
|
|
|
6
7
|
interface AiOperationCatalogs {
|
|
7
8
|
permissions: ReadonlySet<string>;
|
|
@@ -100,6 +101,7 @@ function validateOperation(
|
|
|
100
101
|
);
|
|
101
102
|
validateCapabilities(operation);
|
|
102
103
|
validateTools(operation, catalogs.tools);
|
|
104
|
+
validateAiNetworkPolicy(operation);
|
|
103
105
|
validateLimits(operation);
|
|
104
106
|
}
|
|
105
107
|
|
|
@@ -88,6 +88,15 @@ export type AiOperationExecution =
|
|
|
88
88
|
| "background"
|
|
89
89
|
| "live";
|
|
90
90
|
export type AiOperationPublicNetworkAccess = "forbidden" | "allowed";
|
|
91
|
+
export type AiProviderEgressPolicy =
|
|
92
|
+
| { mode: "forbidden" }
|
|
93
|
+
| {
|
|
94
|
+
mode: "configured-endpoint";
|
|
95
|
+
deploymentKey: string;
|
|
96
|
+
};
|
|
97
|
+
export interface AiToolNetworkPolicy {
|
|
98
|
+
mode: "forbidden";
|
|
99
|
+
}
|
|
91
100
|
|
|
92
101
|
export interface AiOperationLimits {
|
|
93
102
|
maxDurationSeconds: number;
|
|
@@ -107,7 +116,10 @@ export interface ModuleAiOperationDefinition {
|
|
|
107
116
|
allowedToolKeys: readonly string[];
|
|
108
117
|
dataClassification: AiOperationDataClassification;
|
|
109
118
|
execution: AiOperationExecution;
|
|
110
|
-
|
|
119
|
+
/** @deprecated 使用 providerEgress 与 toolNetworkPolicy 拆分两类网络边界。 */
|
|
120
|
+
publicNetworkAccess?: AiOperationPublicNetworkAccess;
|
|
121
|
+
providerEgress?: AiProviderEgressPolicy;
|
|
122
|
+
toolNetworkPolicy?: AiToolNetworkPolicy;
|
|
111
123
|
limits: AiOperationLimits;
|
|
112
124
|
}
|
|
113
125
|
|