create-windy 0.6.18 → 0.6.19

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 (32) hide show
  1. package/dist/cli.js +126 -54
  2. package/package.json +1 -1
  3. package/template/.windy-template.json +2 -2
  4. package/template/README.md +5 -2
  5. package/template/apps/agent-server/README.md +6 -0
  6. package/template/apps/agent-server/src/southwind_agent_server/app.py +23 -0
  7. package/template/apps/agent-server/src/southwind_agent_server/health_routes.py +9 -0
  8. package/template/apps/agent-server/src/southwind_agent_server/runtime_compatibility.py +89 -0
  9. package/template/apps/agent-server/tests/test_runtime_compatibility.py +119 -0
  10. package/template/apps/server/src/agent/remote-runtime-compatibility.test.ts +147 -0
  11. package/template/apps/server/src/agent/remote-runtime.ts +66 -4
  12. package/template/apps/server/src/agent/runtime-bootstrap.ts +1 -0
  13. package/template/apps/web/src/components/common/FormDialog.vue +1 -0
  14. package/template/apps/web/src/components/common/ModalLayout.webtest.ts +1 -0
  15. package/template/apps/web/src/components/ui/dialog/DialogContent.vue +1 -1
  16. package/template/apps/web/src/components/ui/dialog/DialogScrollContent.vue +1 -1
  17. package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +1 -0
  18. package/template/apps/web/src/pages/system/components/user-account/CreateDepartmentDialog.vue +182 -0
  19. package/template/apps/web/src/pages/system/components/user-account/CreateDepartmentDialog.webtest.ts +93 -0
  20. package/template/apps/web/src/pages/system/components/user-account/RolePermissionSummary.vue +59 -0
  21. package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +21 -71
  22. package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.webtest.ts +71 -0
  23. package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.vue +48 -27
  24. package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.webtest.ts +30 -1
  25. package/template/docs/architecture/ai-runtime.md +8 -15
  26. package/template/docs/platform/agent-runtime.md +5 -0
  27. package/template/packages/server-sdk/contracts/agent-runtime-compatibility/v1/offer.json +22 -0
  28. package/template/packages/server-sdk/index.ts +19 -0
  29. package/template/packages/server-sdk/package.json +2 -0
  30. package/template/packages/server-sdk/src/agent-runtime-compatibility.contract.test.ts +143 -0
  31. package/template/packages/server-sdk/src/agent-runtime-compatibility.public-api.test.ts +19 -0
  32. package/template/packages/server-sdk/src/agent-runtime-compatibility.ts +284 -0
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import re
6
+
7
+ SCHEMA_VERSION = 1
8
+ PROTOCOL_VERSION = 1
9
+ RUNTIME_KIND = "agent"
10
+ COMPATIBILITY_PATH = "/api/agent/runtime/compatibility"
11
+ PROTOCOL_HEADER = "X-Windy-Agent-Runtime-Protocol"
12
+ OFFER_DIGEST_HEADER = "X-Windy-Agent-Runtime-Offer-Digest"
13
+ TOOL_CALLBACK_CONTRACT_VERSION = 1
14
+ TOOL_CALLBACK_AUTHENTICATION = "windy-internal-bearer-v1"
15
+ PROTOCOL_FEATURES = (
16
+ "canonical-sse-replay.v1",
17
+ "execution-grant-forwarding.v1",
18
+ "operation-release-attestation.v1",
19
+ "run-cancel.v1",
20
+ "run-lifecycle.v1",
21
+ "runtime-reporting.v1",
22
+ "tool-callback.v1",
23
+ )
24
+
25
+ _STABLE_VALUE = re.compile(r"^[a-z0-9][a-z0-9._-]{0,159}$")
26
+
27
+
28
+ class RuntimeCompatibilityError(ValueError):
29
+ pass
30
+
31
+
32
+ def runtime_compatibility_offer(engine_id: str) -> dict[str, object]:
33
+ if not _STABLE_VALUE.fullmatch(engine_id):
34
+ raise RuntimeCompatibilityError("Agent Runtime engineId invalid")
35
+ protocol_versions = [PROTOCOL_VERSION]
36
+ protocol_features = sorted(PROTOCOL_FEATURES)
37
+ tool_callback: dict[str, object] = {
38
+ "contractVersion": TOOL_CALLBACK_CONTRACT_VERSION,
39
+ "authentication": TOOL_CALLBACK_AUTHENTICATION,
40
+ }
41
+ release_digest = _digest(
42
+ {
43
+ "runtimeKind": RUNTIME_KIND,
44
+ "engineId": engine_id,
45
+ "protocolVersions": protocol_versions,
46
+ }
47
+ )
48
+ capabilities_digest = _digest(
49
+ {
50
+ "protocolFeatures": protocol_features,
51
+ "toolCallback": tool_callback,
52
+ }
53
+ )
54
+ body: dict[str, object] = {
55
+ "schemaVersion": SCHEMA_VERSION,
56
+ "runtimeKind": RUNTIME_KIND,
57
+ "engineId": engine_id,
58
+ "protocolVersions": protocol_versions,
59
+ "protocolFeatures": protocol_features,
60
+ "toolCallback": tool_callback,
61
+ "releaseDigest": release_digest,
62
+ "capabilitiesDigest": capabilities_digest,
63
+ }
64
+ return {**body, "offerDigest": _digest(body)}
65
+
66
+
67
+ def validate_runtime_compatibility_pin(
68
+ protocol_header: str | None,
69
+ offer_digest_header: str | None,
70
+ engine_id: str,
71
+ ) -> None:
72
+ if protocol_header is None and offer_digest_header is None:
73
+ return
74
+ if protocol_header is None or offer_digest_header is None:
75
+ raise RuntimeCompatibilityError("Agent Runtime compatibility pin incomplete")
76
+ if protocol_header != str(PROTOCOL_VERSION):
77
+ raise RuntimeCompatibilityError("Agent Runtime protocol unsupported")
78
+ expected = runtime_compatibility_offer(engine_id)["offerDigest"]
79
+ if offer_digest_header != expected:
80
+ raise RuntimeCompatibilityError("Agent Runtime offer digest mismatch")
81
+
82
+
83
+ def _digest(value: object) -> str:
84
+ encoded = json.dumps(
85
+ value,
86
+ ensure_ascii=False,
87
+ separators=(",", ":"),
88
+ ).encode()
89
+ return f"sha256:{hashlib.sha256(encoded).hexdigest()}"
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from collections.abc import AsyncIterator
5
+ from pathlib import Path
6
+ from typing import cast
7
+
8
+ import httpx
9
+ import pytest
10
+
11
+ from southwind_agent_server.app import create_app
12
+ from southwind_agent_server.deterministic import DeterministicEngineAdapter
13
+ from southwind_agent_server.runtime_compatibility import (
14
+ runtime_compatibility_offer,
15
+ )
16
+
17
+ TOKEN = "runtime-compatibility-test-token"
18
+
19
+
20
+ @pytest.fixture
21
+ async def client() -> AsyncIterator[httpx.AsyncClient]:
22
+ app = create_app(DeterministicEngineAdapter(), internal_token=TOKEN)
23
+ async with httpx.AsyncClient(
24
+ transport=httpx.ASGITransport(app=app),
25
+ base_url="http://agent.test",
26
+ ) as value:
27
+ yield value
28
+
29
+
30
+ async def test_compatibility_offer_is_authenticated_and_matches_public_fixture(
31
+ client: httpx.AsyncClient,
32
+ ) -> None:
33
+ denied = await client.get("/api/agent/runtime/compatibility")
34
+ allowed = await client.get(
35
+ "/api/agent/runtime/compatibility",
36
+ headers={"Authorization": f"Bearer {TOKEN}"},
37
+ )
38
+
39
+ assert denied.status_code == 403
40
+ assert allowed.status_code == 200
41
+ assert allowed.json() == public_fixture()
42
+
43
+
44
+ async def test_start_accepts_legacy_without_pin(
45
+ client: httpx.AsyncClient,
46
+ ) -> None:
47
+ legacy = await start(client, "legacy-pin")
48
+
49
+ assert legacy.status_code == 200
50
+
51
+
52
+ async def test_start_accepts_complete_matching_pin(
53
+ client: httpx.AsyncClient,
54
+ ) -> None:
55
+ offer = runtime_compatibility_offer("windy.direct.openai-compatible.v1")
56
+ pinned = await start(
57
+ client,
58
+ "current-pin",
59
+ {
60
+ "X-Windy-Agent-Runtime-Protocol": "1",
61
+ "X-Windy-Agent-Runtime-Offer-Digest": str(offer["offerDigest"]),
62
+ },
63
+ )
64
+
65
+ assert pinned.status_code == 200
66
+
67
+
68
+ @pytest.mark.parametrize(
69
+ "headers",
70
+ [
71
+ {"X-Windy-Agent-Runtime-Protocol": "1"},
72
+ {"X-Windy-Agent-Runtime-Offer-Digest": "sha256:" + "0" * 64},
73
+ {
74
+ "X-Windy-Agent-Runtime-Protocol": "2",
75
+ "X-Windy-Agent-Runtime-Offer-Digest": "sha256:" + "0" * 64,
76
+ },
77
+ {
78
+ "X-Windy-Agent-Runtime-Protocol": "1",
79
+ "X-Windy-Agent-Runtime-Offer-Digest": "sha256:" + "0" * 64,
80
+ },
81
+ ],
82
+ )
83
+ async def test_start_rejects_partial_or_incompatible_pin_before_admission(
84
+ client: httpx.AsyncClient,
85
+ headers: dict[str, str],
86
+ ) -> None:
87
+ response = await start(client, "rejected-pin", headers)
88
+
89
+ assert response.status_code == 409
90
+ assert response.json() == {
91
+ "code": "RUNTIME_UNAVAILABLE",
92
+ "message": "Agent Runtime compatibility pin 不匹配",
93
+ "requestId": response.json()["requestId"],
94
+ "retryable": False,
95
+ }
96
+
97
+
98
+ async def start(
99
+ client: httpx.AsyncClient,
100
+ key: str,
101
+ headers: dict[str, str] | None = None,
102
+ ) -> httpx.Response:
103
+ return await client.post(
104
+ "/api/agent/runs",
105
+ headers={
106
+ "Authorization": f"Bearer {TOKEN}",
107
+ "Idempotency-Key": key,
108
+ **(headers or {}),
109
+ },
110
+ json={"operationKey": "test.echo", "input": {"text": key}},
111
+ )
112
+
113
+
114
+ def public_fixture() -> dict[str, object]:
115
+ path = (
116
+ Path(__file__).parents[3]
117
+ / "packages/server-sdk/contracts/agent-runtime-compatibility/v1/offer.json"
118
+ )
119
+ return cast(dict[str, object], json.loads(path.read_text(encoding="utf8")))
@@ -0,0 +1,147 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { createAgentRuntimeCompatibilityOfferV1 } from "@southwind-ai/server-sdk";
3
+ import type { AgentRuntimeOperation } from "./run-contracts.js";
4
+ import { RemoteAgentRuntime } from "./remote-runtime.js";
5
+
6
+ const servers: Bun.Server<unknown>[] = [];
7
+
8
+ afterEach(() => {
9
+ for (const server of servers.splice(0)) server.stop(true);
10
+ });
11
+
12
+ describe("RemoteAgentRuntime compatibility", () => {
13
+ test("V1 协商成功后固定协议版本与 Offer 摘要", async () => {
14
+ let startHeaders: Headers | undefined;
15
+ const offer = createAgentRuntimeCompatibilityOfferV1(
16
+ "windy.direct.openai-compatible.v1",
17
+ );
18
+ const server = Bun.serve({
19
+ port: 0,
20
+ fetch(request) {
21
+ if (isCompatibilityRequest(request)) return Response.json(offer);
22
+ startHeaders = request.headers;
23
+ return accepted("run_pinned");
24
+ },
25
+ });
26
+ servers.push(server);
27
+ const runtime = remote(server, "require-v1");
28
+
29
+ await runtime.start(agentOperation(), "pinned-1");
30
+
31
+ expect(startHeaders?.get("x-windy-agent-runtime-protocol")).toBe("1");
32
+ expect(startHeaders?.get("x-windy-agent-runtime-offer-digest")).toBe(
33
+ offer.offerDigest,
34
+ );
35
+ expect(startHeaders?.get("authorization")).toBe("Bearer test-token");
36
+ });
37
+
38
+ test("prefer-v1 只对旧 Runtime 的 404 回退为 legacy", async () => {
39
+ let starts = 0;
40
+ const server = Bun.serve({
41
+ port: 0,
42
+ fetch(request) {
43
+ if (isCompatibilityRequest(request)) {
44
+ return new Response(null, { status: 404 });
45
+ }
46
+ starts += 1;
47
+ return accepted("run_legacy");
48
+ },
49
+ });
50
+ servers.push(server);
51
+
52
+ await expect(
53
+ remote(server, "prefer-v1").start(agentOperation(), "legacy-1"),
54
+ ).resolves.toEqual({
55
+ runId: "run_legacy",
56
+ status: "accepted",
57
+ eventsUrl: "/api/agent/runs/run_legacy/events",
58
+ });
59
+ expect(starts).toBe(1);
60
+ });
61
+
62
+ test("require-v1 拒绝旧 Runtime,畸形 Offer 也不会降级", async () => {
63
+ for (const response of [
64
+ new Response(null, { status: 404 }),
65
+ Response.json({ schemaVersion: 1 }),
66
+ ]) {
67
+ let starts = 0;
68
+ const server = Bun.serve({
69
+ port: 0,
70
+ fetch(request) {
71
+ if (isCompatibilityRequest(request)) return response.clone();
72
+ starts += 1;
73
+ return Response.json({});
74
+ },
75
+ });
76
+ servers.push(server);
77
+
78
+ await expect(
79
+ remote(server, "require-v1").start(agentOperation(), "required-1"),
80
+ ).rejects.toMatchObject({
81
+ code: "RUNTIME_UNAVAILABLE",
82
+ retryable: false,
83
+ });
84
+ expect(starts).toBe(0);
85
+ }
86
+ });
87
+ });
88
+
89
+ function remote(server: Bun.Server<unknown>, mode: "prefer-v1" | "require-v1") {
90
+ return new RemoteAgentRuntime({
91
+ baseUrl: server.url.toString(),
92
+ internalToken: "test-token",
93
+ toolHostUrl: "http://server.internal",
94
+ compatibilityMode: mode,
95
+ });
96
+ }
97
+
98
+ function isCompatibilityRequest(request: Request): boolean {
99
+ return new URL(request.url).pathname === "/api/agent/runtime/compatibility";
100
+ }
101
+
102
+ function accepted(runId: string): Response {
103
+ return Response.json({
104
+ runId,
105
+ status: "accepted",
106
+ eventsUrl: `/api/agent/runs/${runId}/events`,
107
+ });
108
+ }
109
+
110
+ function agentOperation(): AgentRuntimeOperation {
111
+ return {
112
+ kind: "agent",
113
+ operationKey: "business.answer",
114
+ input: { question: "你好" },
115
+ inputModalities: ["text"],
116
+ requiredCapabilities: ["text-generation"],
117
+ artifacts: [],
118
+ instructions: "回答业务问题",
119
+ userPrompt: "你好",
120
+ outputSchemaKey: "business.output.v1",
121
+ allowedTools: [],
122
+ limits: {
123
+ maxDurationSeconds: 60,
124
+ maxInputBytes: 4_096,
125
+ maxOutputBytes: 4_096,
126
+ },
127
+ providerEgress: {
128
+ mode: "configured-endpoint",
129
+ deploymentKey: "ai.openai-compatible",
130
+ },
131
+ providerDataPolicy: { inputFields: [], envelopeFields: [] },
132
+ toolNetworkPolicy: { mode: "forbidden" },
133
+ executionGrant: "grant_1",
134
+ release: {
135
+ releaseId: "release_test",
136
+ operationVersion: "1.0.0",
137
+ workflowVersion: "workflow-1",
138
+ promptVersion: "prompt-1",
139
+ routeVersion: "route-1",
140
+ outputSchemaVersion: "output-schema-1",
141
+ toolSchemaVersion: "schema-1",
142
+ manifestDigest: "0".repeat(64),
143
+ },
144
+ traceId: "trace_1",
145
+ reporting: { actorId: "user_1", actorType: "user" },
146
+ };
147
+ }
@@ -1,8 +1,16 @@
1
- import type {
2
- AgentRuntimeOperation,
3
- AgentRuntimePort,
4
- AgentRuntimeStartResponse,
1
+ import {
2
+ AgentRuntimeError,
3
+ type AgentRuntimeOperation,
4
+ type AgentRuntimePort,
5
+ type AgentRuntimeStartResponse,
5
6
  } from "./run-contracts.js";
7
+ import {
8
+ AGENT_RUNTIME_COMPATIBILITY_PATH_V1,
9
+ AGENT_RUNTIME_OFFER_DIGEST_HEADER_V1,
10
+ AGENT_RUNTIME_PROTOCOL_HEADER_V1,
11
+ localAgentRuntimeCompatibilityRequirementsV1,
12
+ negotiateAgentRuntimeCompatibilityV1,
13
+ } from "@southwind-ai/server-sdk";
6
14
  import { assertVisionReadinessPin } from "./vision-readiness-pin.js";
7
15
  import {
8
16
  parseRemoteStart,
@@ -15,6 +23,8 @@ export interface RemoteAgentRuntimeOptions {
15
23
  internalToken: string;
16
24
  toolHostUrl: string;
17
25
  timeoutMs?: number;
26
+ /** Legacy is the default for direct callers; platform composition opts into rolling V1. */
27
+ compatibilityMode?: "prefer-v1" | "require-v1";
18
28
  }
19
29
 
20
30
  export class RemoteAgentRuntime implements AgentRuntimePort {
@@ -33,6 +43,7 @@ export class RemoteAgentRuntime implements AgentRuntimePort {
33
43
  request: AgentRuntimeOperation,
34
44
  idempotencyKey: string,
35
45
  ): Promise<AgentRuntimeStartResponse> {
46
+ const compatibilityHeaders = await this.compatibilityHeaders();
36
47
  if (request.readinessPin) {
37
48
  await assertVisionReadinessPin(request.readinessPin, () => this.health());
38
49
  }
@@ -103,6 +114,7 @@ export class RemoteAgentRuntime implements AgentRuntimePort {
103
114
  headers: {
104
115
  "content-type": "application/json",
105
116
  "idempotency-key": idempotencyKey,
117
+ ...compatibilityHeaders,
106
118
  },
107
119
  body: JSON.stringify(remoteRequest),
108
120
  });
@@ -160,6 +172,38 @@ export class RemoteAgentRuntime implements AgentRuntimePort {
160
172
  return { deploymentKey, model };
161
173
  }
162
174
 
175
+ private async compatibilityHeaders(): Promise<Record<string, string>> {
176
+ const mode = this.options.compatibilityMode;
177
+ if (!mode) return {};
178
+ let response: Response;
179
+ try {
180
+ response = await this.request(AGENT_RUNTIME_COMPATIBILITY_PATH_V1);
181
+ } catch {
182
+ throw new AgentRuntimeError(
183
+ "RUNTIME_UNAVAILABLE",
184
+ "Agent Runtime compatibility 探测失败",
185
+ true,
186
+ );
187
+ }
188
+ if (response.status === 404 && mode === "prefer-v1") return {};
189
+ if (!response.ok) throw await remoteRuntimeError(response);
190
+ let value: unknown;
191
+ try {
192
+ value = await readBoundedCompatibilityOffer(response);
193
+ } catch {
194
+ throw incompatibleRuntime("CONTRACT_INVALID");
195
+ }
196
+ const negotiation = negotiateAgentRuntimeCompatibilityV1(
197
+ value,
198
+ localAgentRuntimeCompatibilityRequirementsV1(),
199
+ );
200
+ if (!negotiation.ok) throw incompatibleRuntime(negotiation.code);
201
+ return {
202
+ [AGENT_RUNTIME_PROTOCOL_HEADER_V1]: String(negotiation.protocolVersion),
203
+ [AGENT_RUNTIME_OFFER_DIGEST_HEADER_V1]: negotiation.offer.offerDigest,
204
+ };
205
+ }
206
+
163
207
  private async request(
164
208
  path: string,
165
209
  init: RequestInit = {},
@@ -187,6 +231,24 @@ export class RemoteAgentRuntime implements AgentRuntimePort {
187
231
  }
188
232
  }
189
233
 
234
+ function incompatibleRuntime(code: string): AgentRuntimeError {
235
+ return new AgentRuntimeError(
236
+ "RUNTIME_UNAVAILABLE",
237
+ `Agent Runtime compatibility 不匹配(${code})`,
238
+ false,
239
+ );
240
+ }
241
+
242
+ async function readBoundedCompatibilityOffer(
243
+ response: Response,
244
+ ): Promise<unknown> {
245
+ const bytes = new Uint8Array(await response.arrayBuffer());
246
+ if (bytes.byteLength > 64 * 1_024) {
247
+ throw new Error("Agent Runtime compatibility Offer 超过上限");
248
+ }
249
+ return JSON.parse(new TextDecoder().decode(bytes)) as unknown;
250
+ }
251
+
190
252
  function isRecord(value: unknown): value is Record<string, unknown> {
191
253
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
192
254
  }
@@ -104,6 +104,7 @@ export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
104
104
  baseUrl: agentServerUrl,
105
105
  internalToken,
106
106
  toolHostUrl: process.env.AGENT_TOOL_HOST_URL || "http://server:9747",
107
+ compatibilityMode: "prefer-v1",
107
108
  });
108
109
  const clientAgentSessionRuntime = createClientAgentSessionRuntime({
109
110
  app: input.app,
@@ -34,6 +34,7 @@ const props = withDefaults(
34
34
  loading: false,
35
35
  submitDisabled: false,
36
36
  preventOutsideClose: false,
37
+ contentClass: "sm:max-w-2xl",
37
38
  },
38
39
  );
39
40
 
@@ -33,6 +33,7 @@ describe("Modal 布局", () => {
33
33
  "min-h-[min(12rem,calc(100dvh-2rem))]",
34
34
  );
35
35
  expect(content?.className).toContain("overflow-hidden");
36
+ expect(content?.className).toContain("sm:max-w-2xl");
36
37
  expect(body?.className).toContain("overflow-y-auto");
37
38
  expect(footer?.className).toContain("shrink-0");
38
39
 
@@ -44,7 +44,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
44
44
  v-bind="{ ...$attrs, ...forwarded }"
45
45
  :class="
46
46
  cn(
47
- 'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 fixed top-1/2 left-1/2 z-50 flex min-h-[min(12rem,calc(100dvh-2rem))] max-h-[calc(100dvh-2rem)] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 overflow-hidden rounded-xl p-4 text-sm ring-1 duration-100 outline-none sm:max-w-sm',
47
+ 'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 fixed top-1/2 left-1/2 z-50 flex min-h-[min(12rem,calc(100dvh-2rem))] max-h-[calc(100dvh-2rem)] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 overflow-hidden rounded-xl p-4 text-sm ring-1 duration-100 outline-none sm:max-w-xl',
48
48
  props.class,
49
49
  )
50
50
  "
@@ -35,7 +35,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
35
35
  <DialogContent
36
36
  :class="
37
37
  cn(
38
- 'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
38
+ 'relative z-50 my-8 grid w-full max-w-2xl gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
39
39
  props.class,
40
40
  )
41
41
  "
@@ -52,6 +52,7 @@ const title = computed(() => (props.item ? "编辑记录" : "新增记录"));
52
52
  ref="resourceForm"
53
53
  :item="item"
54
54
  :defaults="defaults"
55
+ :permissions="permissions || []"
55
56
  :department-create-mode="departmentCreateMode"
56
57
  @submit="emit('submit', $event)"
57
58
  />