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
|
@@ -1,141 +1,123 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createWorkOrderSchema,
|
|
3
3
|
updateWorkOrderSchema,
|
|
4
|
-
workOrderModule,
|
|
5
4
|
} from "@southwind-ai/example-work-order";
|
|
6
|
-
import type {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { evaluateRouteGuards } from "../route-guards.js";
|
|
10
|
-
import type { createServerRuntime } from "../runtime.js";
|
|
11
|
-
import {
|
|
12
|
-
forbidden,
|
|
13
|
-
notFound,
|
|
14
|
-
resourceEvent,
|
|
15
|
-
routeContext,
|
|
16
|
-
} from "../system/route-helpers.js";
|
|
17
|
-
import type { RouteApp } from "../system/route-types.js";
|
|
5
|
+
import type { ModuleRouteDefinition } from "@southwind-ai/server-sdk";
|
|
6
|
+
import { searchAuditMetadata } from "../audit/search-summary.js";
|
|
7
|
+
import { requestActorFromModule } from "../module-host/request-context.js";
|
|
18
8
|
import { parsePagination } from "../pagination.js";
|
|
9
|
+
import { accessCall } from "../system/resource-access-response.js";
|
|
10
|
+
import { notFound } from "../system/route-helpers.js";
|
|
19
11
|
import type { WorkOrderService } from "./service.js";
|
|
20
|
-
import { searchAuditEvent } from "../audit/search-summary.js";
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
searchAuditEvent(
|
|
45
|
-
context.guardContext,
|
|
46
|
-
"work-order",
|
|
47
|
-
context.query.q,
|
|
48
|
-
result.total,
|
|
13
|
+
/**
|
|
14
|
+
* 示例工单的 Guarded Route 定义。
|
|
15
|
+
*
|
|
16
|
+
* 只声明 method/path/permissionKey 与 Handler;Guard、审计上下文与受限 Port
|
|
17
|
+
* 由宿主在 installModuleRoutes 时统一绑定,不再手工查询 Manifest 绑定。
|
|
18
|
+
* `requireService` 延迟解析宿主装配完成后才存在的模块服务。
|
|
19
|
+
*/
|
|
20
|
+
export function createWorkOrderRouteDefinitions(
|
|
21
|
+
requireService: () => WorkOrderService,
|
|
22
|
+
): ModuleRouteDefinition[] {
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
method: "GET",
|
|
26
|
+
path: "/api/work-orders",
|
|
27
|
+
permissionKey: "work-order.ticket.read",
|
|
28
|
+
handler: async (context) => {
|
|
29
|
+
const pagination = parsePagination(context.query);
|
|
30
|
+
if (pagination instanceof Response) return pagination;
|
|
31
|
+
const result = await accessCall(() =>
|
|
32
|
+
requireService().list(
|
|
33
|
+
{ ...pagination, q: context.query.q },
|
|
34
|
+
requestActorFromModule(context.actor),
|
|
49
35
|
),
|
|
50
36
|
);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
: item || notFound("work-order", id);
|
|
66
|
-
}),
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
app.post("/work-orders", (raw) =>
|
|
70
|
-
guarded(raw, binding("POST", "/api/work-orders"), runtime, async () => {
|
|
71
|
-
const context = routeContext(raw);
|
|
72
|
-
const parsed = createWorkOrderSchema.safeParse(context.body);
|
|
73
|
-
if (!parsed.success) return invalid(parsed.error.issues[0]?.message);
|
|
74
|
-
const item = await accessCall(() =>
|
|
75
|
-
service.create(parsed.data, context.guardContext.actor),
|
|
76
|
-
);
|
|
77
|
-
if (item instanceof Response) return item;
|
|
78
|
-
await audit(runtime, context.guardContext, "crud.create", item.id);
|
|
79
|
-
return item;
|
|
80
|
-
}),
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
app.put("/work-orders/:id", (raw) =>
|
|
84
|
-
guarded(raw, binding("PUT", "/api/work-orders/:id"), runtime, async () => {
|
|
85
|
-
const context = routeContext(raw);
|
|
86
|
-
const id = context.params.id || "";
|
|
87
|
-
const parsed = updateWorkOrderSchema.safeParse(context.body);
|
|
88
|
-
if (!parsed.success) return invalid(parsed.error.issues[0]?.message);
|
|
89
|
-
const item = await accessCall(() =>
|
|
90
|
-
service.update(id, parsed.data, context.guardContext.actor),
|
|
91
|
-
);
|
|
92
|
-
if (item instanceof Response) return item;
|
|
93
|
-
if (!item) return notFound("work-order", id);
|
|
94
|
-
await audit(runtime, context.guardContext, "crud.update", id);
|
|
95
|
-
return item;
|
|
96
|
-
}),
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
app.delete("/work-orders/:id", (raw) =>
|
|
100
|
-
guarded(
|
|
101
|
-
raw,
|
|
102
|
-
binding("DELETE", "/api/work-orders/:id"),
|
|
103
|
-
runtime,
|
|
104
|
-
async () => {
|
|
105
|
-
const context = routeContext(raw);
|
|
37
|
+
if (!(result instanceof Response) && context.query.q?.trim()) {
|
|
38
|
+
await context.audit.emit("data.search", {
|
|
39
|
+
target: { type: "work-order" },
|
|
40
|
+
metadata: searchAuditMetadata(context.query.q, result.total),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
method: "GET",
|
|
48
|
+
path: "/api/work-orders/:id",
|
|
49
|
+
permissionKey: "work-order.ticket.read",
|
|
50
|
+
handler: async (context) => {
|
|
106
51
|
const id = context.params.id || "";
|
|
107
52
|
const item = await accessCall(() =>
|
|
108
|
-
|
|
53
|
+
requireService().get(id, requestActorFromModule(context.actor)),
|
|
54
|
+
);
|
|
55
|
+
return item instanceof Response
|
|
56
|
+
? item
|
|
57
|
+
: item || notFound("work-order", id);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
method: "POST",
|
|
62
|
+
path: "/api/work-orders",
|
|
63
|
+
permissionKey: "work-order.ticket.create",
|
|
64
|
+
handler: async (context) => {
|
|
65
|
+
const parsed = createWorkOrderSchema.safeParse(context.body);
|
|
66
|
+
if (!parsed.success) return invalid(parsed.error.issues[0]?.message);
|
|
67
|
+
const item = await accessCall(() =>
|
|
68
|
+
requireService().create(
|
|
69
|
+
parsed.data,
|
|
70
|
+
requestActorFromModule(context.actor),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
if (item instanceof Response) return item;
|
|
74
|
+
await context.audit.emit("crud.create", {
|
|
75
|
+
target: { type: "work-order", id: item.id },
|
|
76
|
+
});
|
|
77
|
+
return item;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
method: "PUT",
|
|
82
|
+
path: "/api/work-orders/:id",
|
|
83
|
+
permissionKey: "work-order.ticket.manage",
|
|
84
|
+
handler: async (context) => {
|
|
85
|
+
const id = context.params.id || "";
|
|
86
|
+
const parsed = updateWorkOrderSchema.safeParse(context.body);
|
|
87
|
+
if (!parsed.success) return invalid(parsed.error.issues[0]?.message);
|
|
88
|
+
const item = await accessCall(() =>
|
|
89
|
+
requireService().update(
|
|
90
|
+
id,
|
|
91
|
+
parsed.data,
|
|
92
|
+
requestActorFromModule(context.actor),
|
|
93
|
+
),
|
|
109
94
|
);
|
|
110
95
|
if (item instanceof Response) return item;
|
|
111
96
|
if (!item) return notFound("work-order", id);
|
|
112
|
-
await audit(
|
|
97
|
+
await context.audit.emit("crud.update", {
|
|
98
|
+
target: { type: "work-order", id },
|
|
99
|
+
});
|
|
113
100
|
return item;
|
|
114
101
|
},
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const result = manifest.apiPermissions.find(
|
|
135
|
-
(item) => item.method === method && item.path === path,
|
|
136
|
-
);
|
|
137
|
-
if (!result) throw new Error(`示例工单缺少 API 绑定:${method} ${path}`);
|
|
138
|
-
return result;
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
method: "DELETE",
|
|
105
|
+
path: "/api/work-orders/:id",
|
|
106
|
+
permissionKey: "work-order.ticket.manage",
|
|
107
|
+
handler: async (context) => {
|
|
108
|
+
const id = context.params.id || "";
|
|
109
|
+
const item = await accessCall(() =>
|
|
110
|
+
requireService().archive(id, requestActorFromModule(context.actor)),
|
|
111
|
+
);
|
|
112
|
+
if (item instanceof Response) return item;
|
|
113
|
+
if (!item) return notFound("work-order", id);
|
|
114
|
+
await context.audit.emit("crud.delete", {
|
|
115
|
+
target: { type: "work-order", id },
|
|
116
|
+
});
|
|
117
|
+
return item;
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
];
|
|
139
121
|
}
|
|
140
122
|
|
|
141
123
|
function invalid(message = "工单内容无效") {
|
|
@@ -144,16 +126,3 @@ function invalid(message = "工单内容无效") {
|
|
|
144
126
|
{ status: 400 },
|
|
145
127
|
);
|
|
146
128
|
}
|
|
147
|
-
|
|
148
|
-
function audit(
|
|
149
|
-
runtime: ServerRuntime,
|
|
150
|
-
context: ReturnType<typeof routeContext>["guardContext"],
|
|
151
|
-
action: "crud.create" | "crud.update" | "crud.delete",
|
|
152
|
-
id: string,
|
|
153
|
-
) {
|
|
154
|
-
return recordMutationAudit(
|
|
155
|
-
runtime,
|
|
156
|
-
"work-orders",
|
|
157
|
-
resourceEvent(context, action, "work-order", id),
|
|
158
|
-
);
|
|
159
|
-
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
workOrderModule,
|
|
4
|
+
type WorkOrder,
|
|
5
|
+
} from "@southwind-ai/example-work-order";
|
|
6
|
+
import {
|
|
7
|
+
systemFoundationModule,
|
|
8
|
+
type ModuleManifest,
|
|
9
|
+
} from "@southwind-ai/modules";
|
|
4
10
|
import { licenseVersionKey } from "@southwind-ai/shared";
|
|
5
11
|
import { buildScopeContext } from "../data-access/context.js";
|
|
6
12
|
import { InMemoryScopedEntityRepository } from "../data-access/scoped-repository.js";
|
|
@@ -11,6 +17,7 @@ import { searchTestContext } from "../search/test-support.js";
|
|
|
11
17
|
import type { ManagedDepartment } from "../system/entities.js";
|
|
12
18
|
import { InMemoryEntityRepository } from "../system/repository.js";
|
|
13
19
|
import type { RouteApp } from "../system/route-types.js";
|
|
20
|
+
import { adaptModuleSearchProvider } from "../module-host.js";
|
|
14
21
|
import { workOrderDataScopeBinding } from "./repository.js";
|
|
15
22
|
import { WorkOrderSearchProvider } from "./search-provider.js";
|
|
16
23
|
import { WorkOrderService } from "./service.js";
|
|
@@ -45,7 +52,11 @@ describe("搜索 API 数据范围", () => {
|
|
|
45
52
|
);
|
|
46
53
|
const service = new SearchService(
|
|
47
54
|
modules,
|
|
48
|
-
[
|
|
55
|
+
[
|
|
56
|
+
adaptModuleSearchProvider(
|
|
57
|
+
new WorkOrderSearchProvider(() => workOrders),
|
|
58
|
+
),
|
|
59
|
+
],
|
|
49
60
|
runtime,
|
|
50
61
|
);
|
|
51
62
|
const context = searchTestContext([
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
workOrderModule,
|
|
4
|
+
type WorkOrder,
|
|
5
|
+
} from "@southwind-ai/example-work-order";
|
|
3
6
|
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
4
7
|
import { licenseVersionKey } from "@southwind-ai/shared";
|
|
5
8
|
import { buildScopeContext } from "../data-access/context.js";
|
|
@@ -11,6 +14,7 @@ import type { SearchProvider } from "../search/contracts.js";
|
|
|
11
14
|
import { SearchService } from "../search/service.js";
|
|
12
15
|
import type { ManagedDepartment } from "../system/entities.js";
|
|
13
16
|
import { InMemoryEntityRepository } from "../system/repository.js";
|
|
17
|
+
import { adaptModuleSearchProvider } from "../module-host.js";
|
|
14
18
|
import { workOrderDataScopeBinding } from "./repository.js";
|
|
15
19
|
import {
|
|
16
20
|
WorkOrderSearchProvider,
|
|
@@ -40,10 +44,13 @@ describe("示例工单 SearchProvider", () => {
|
|
|
40
44
|
throw new Error("搜索不得调用裸 Repository.list");
|
|
41
45
|
};
|
|
42
46
|
const service = searchService([
|
|
43
|
-
|
|
44
|
-
new
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
adaptModuleSearchProvider(
|
|
48
|
+
new WorkOrderSearchProvider(
|
|
49
|
+
() =>
|
|
50
|
+
new WorkOrderService(
|
|
51
|
+
repository,
|
|
52
|
+
new InMemoryEntityRepository<ManagedDepartment>(),
|
|
53
|
+
),
|
|
47
54
|
),
|
|
48
55
|
),
|
|
49
56
|
]);
|
|
@@ -84,17 +91,18 @@ describe("示例工单 SearchProvider", () => {
|
|
|
84
91
|
});
|
|
85
92
|
|
|
86
93
|
test("Provider 游标映射到 scoped Repository 分页", async () => {
|
|
87
|
-
const
|
|
88
|
-
new
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
workOrderDataScopeBinding,
|
|
95
|
-
),
|
|
96
|
-
new InMemoryEntityRepository<ManagedDepartment>(),
|
|
94
|
+
const workOrders = new WorkOrderService(
|
|
95
|
+
new InMemoryScopedEntityRepository<WorkOrder>(
|
|
96
|
+
[
|
|
97
|
+
ticket("ticket_1", "user_self", "故障一"),
|
|
98
|
+
ticket("ticket_2", "user_self", "故障二"),
|
|
99
|
+
],
|
|
100
|
+
workOrderDataScopeBinding,
|
|
97
101
|
),
|
|
102
|
+
new InMemoryEntityRepository<ManagedDepartment>(),
|
|
103
|
+
);
|
|
104
|
+
const provider = adaptModuleSearchProvider(
|
|
105
|
+
new WorkOrderSearchProvider(() => workOrders),
|
|
98
106
|
);
|
|
99
107
|
const service = searchService([provider]);
|
|
100
108
|
const first = await service.search(
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "
|
|
2
|
+
ModuleSearchContext,
|
|
3
|
+
ModuleSearchPage,
|
|
4
|
+
ModuleSearchProvider,
|
|
5
|
+
ModuleSearchQuery,
|
|
6
|
+
} from "@southwind-ai/server-sdk";
|
|
7
|
+
import { requestActorFromModule } from "../module-host/request-context.js";
|
|
7
8
|
import { SearchProtocolError } from "../search/errors.js";
|
|
8
9
|
import { maskSearchText } from "../search/masking.js";
|
|
9
10
|
import type { WorkOrderService } from "./service.js";
|
|
@@ -11,21 +12,25 @@ import type { WorkOrderService } from "./service.js";
|
|
|
11
12
|
export const WORK_ORDER_SEARCH_PROVIDER_KEY = "work-order.ticket.search";
|
|
12
13
|
export const WORK_ORDER_SEARCH_PROVIDER_VERSION = "0.1.0";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* 示例工单搜索 Provider,实现 server-sdk 的受限 Provider 协议。
|
|
17
|
+
* `requireService` 延迟解析宿主装配完成后才存在的模块服务。
|
|
18
|
+
*/
|
|
19
|
+
export class WorkOrderSearchProvider implements ModuleSearchProvider {
|
|
15
20
|
readonly key = WORK_ORDER_SEARCH_PROVIDER_KEY;
|
|
16
21
|
readonly providerVersion = WORK_ORDER_SEARCH_PROVIDER_VERSION;
|
|
17
22
|
|
|
18
|
-
constructor(private readonly
|
|
23
|
+
constructor(private readonly requireService: () => WorkOrderService) {}
|
|
19
24
|
|
|
20
25
|
async search(
|
|
21
|
-
request:
|
|
22
|
-
context:
|
|
23
|
-
): Promise<
|
|
26
|
+
request: ModuleSearchQuery,
|
|
27
|
+
context: ModuleSearchContext,
|
|
28
|
+
): Promise<ModuleSearchPage> {
|
|
24
29
|
if (request.signal.aborted) throw new Error("search aborted");
|
|
25
30
|
const page = parseProviderCursor(request.cursor);
|
|
26
|
-
const result = await this.
|
|
31
|
+
const result = await this.requireService().list(
|
|
27
32
|
{ q: request.text, page, pageSize: request.limit },
|
|
28
|
-
context.actor,
|
|
33
|
+
requestActorFromModule(context.actor),
|
|
29
34
|
);
|
|
30
35
|
if (request.signal.aborted) throw new Error("search aborted");
|
|
31
36
|
return {
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import type
|
|
2
|
+
import { type WorkOrder } from "@southwind-ai/example-work-order";
|
|
3
|
+
import { createWorkOrderModule } from "../example-modules.js";
|
|
3
4
|
import { createFoundationSnapshot } from "../foundation.js";
|
|
5
|
+
import {
|
|
6
|
+
createModuleTaskDefinitions,
|
|
7
|
+
initializeModuleHosts,
|
|
8
|
+
} from "../module-host.js";
|
|
4
9
|
import { createServerRuntime } from "../runtime.js";
|
|
5
10
|
import { InMemorySchedulerRepository } from "../scheduler/repository.js";
|
|
6
11
|
import { SchedulerService } from "../scheduler/service.js";
|
|
7
12
|
import type { ManagedDepartment } from "../system/entities.js";
|
|
8
13
|
import { InMemoryEntityRepository } from "../system/repository.js";
|
|
9
14
|
import { InMemoryScopedEntityRepository } from "../data-access/scoped-repository.js";
|
|
10
|
-
import { WorkOrderService } from "./service.js";
|
|
11
15
|
import { workOrderDataScopeBinding } from "./repository.js";
|
|
12
|
-
import { createWorkOrderTaskDefinitions } from "./task.js";
|
|
13
16
|
|
|
14
17
|
describe("示例工单后台任务", () => {
|
|
15
18
|
test("通过统一调度 Guard 执行并把业务快照写入历史", async () => {
|
|
16
19
|
const runtime = createServerRuntime({ WINDY_DEV_ADMIN_TOKEN: "token" });
|
|
17
|
-
const
|
|
18
|
-
|
|
20
|
+
const module = createWorkOrderModule();
|
|
21
|
+
const hosts = await initializeModuleHosts({
|
|
22
|
+
modules: [module.manifest],
|
|
23
|
+
installations: [module],
|
|
24
|
+
});
|
|
25
|
+
module.attach({
|
|
26
|
+
departments: new InMemoryEntityRepository<ManagedDepartment>(),
|
|
27
|
+
repository: new InMemoryScopedEntityRepository<WorkOrder>(
|
|
19
28
|
[
|
|
20
29
|
item("ticket_high", "high", "open"),
|
|
21
30
|
item("ticket_progress", "medium", "in-progress"),
|
|
@@ -23,14 +32,10 @@ describe("示例工单后台任务", () => {
|
|
|
23
32
|
],
|
|
24
33
|
workOrderDataScopeBinding,
|
|
25
34
|
),
|
|
26
|
-
|
|
27
|
-
);
|
|
35
|
+
});
|
|
28
36
|
const scheduler = new SchedulerService(
|
|
29
37
|
new InMemorySchedulerRepository(),
|
|
30
|
-
|
|
31
|
-
createFoundationSnapshot().features,
|
|
32
|
-
service,
|
|
33
|
-
),
|
|
38
|
+
createModuleTaskDefinitions(hosts, createFoundationSnapshot().features),
|
|
34
39
|
runtime,
|
|
35
40
|
);
|
|
36
41
|
await scheduler.initialize();
|
|
@@ -4,6 +4,7 @@ FROM oven/bun:1.3.14-alpine AS deps
|
|
|
4
4
|
WORKDIR /app
|
|
5
5
|
|
|
6
6
|
COPY package.json bun.lock ./
|
|
7
|
+
# windy-copy:deps begin
|
|
7
8
|
COPY apps/license/package.json apps/license/package.json
|
|
8
9
|
COPY apps/server/package.json apps/server/package.json
|
|
9
10
|
COPY apps/signing-service/package.json apps/signing-service/package.json
|
|
@@ -20,14 +21,16 @@ COPY packages/modules/package.json packages/modules/package.json
|
|
|
20
21
|
COPY packages/server-sdk/package.json packages/server-sdk/package.json
|
|
21
22
|
COPY packages/shared/package.json packages/shared/package.json
|
|
22
23
|
COPY packages/storage/package.json packages/storage/package.json
|
|
24
|
+
# windy-copy:deps end
|
|
23
25
|
RUN bun install --frozen-lockfile
|
|
24
26
|
|
|
25
27
|
FROM deps AS build
|
|
28
|
+
# windy-copy:build begin
|
|
26
29
|
COPY apps/web apps/web
|
|
27
|
-
COPY packages/example-work-order packages/example-work-order
|
|
28
30
|
COPY packages/license-sdk packages/license-sdk
|
|
29
31
|
COPY packages/modules packages/modules
|
|
30
32
|
COPY packages/shared packages/shared
|
|
33
|
+
# windy-copy:build end
|
|
31
34
|
RUN bun run --cwd apps/web build
|
|
32
35
|
|
|
33
36
|
FROM deps AS runner
|
|
@@ -42,7 +42,10 @@ const state = vi.hoisted(() => {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
vi.mock("@/composables/useAccessSnapshot", () => ({
|
|
45
|
-
useAccessSnapshot: () => ({
|
|
45
|
+
useAccessSnapshot: () => ({
|
|
46
|
+
snapshot: state.access,
|
|
47
|
+
ensureLoaded: vi.fn(async () => state.access.value),
|
|
48
|
+
}),
|
|
46
49
|
}));
|
|
47
50
|
|
|
48
51
|
vi.mock("@/composables/useAuthSession", () => ({
|
|
@@ -18,6 +18,8 @@ const access = useAccessSnapshot();
|
|
|
18
18
|
const watermark = useWatermarkSettings(auth.actor);
|
|
19
19
|
onMounted(() => {
|
|
20
20
|
void watermark.ensureLoaded();
|
|
21
|
+
// 业务页路由守卫不会预取访问快照,这里兜底加载,失败时保持水印隐藏。
|
|
22
|
+
void access.ensureLoaded().catch(() => undefined);
|
|
21
23
|
});
|
|
22
24
|
const allowed = computed(() => {
|
|
23
25
|
const snapshot = access.snapshot.value;
|
|
@@ -45,7 +45,10 @@ const state = vi.hoisted(() => ({
|
|
|
45
45
|
}));
|
|
46
46
|
|
|
47
47
|
vi.mock("@/composables/useAccessSnapshot", () => ({
|
|
48
|
-
useAccessSnapshot: () => ({
|
|
48
|
+
useAccessSnapshot: () => ({
|
|
49
|
+
snapshot: state.access,
|
|
50
|
+
ensureLoaded: vi.fn(async () => state.access.value),
|
|
51
|
+
}),
|
|
49
52
|
}));
|
|
50
53
|
|
|
51
54
|
vi.mock("@/composables/useAuthSession", () => ({
|
|
@@ -42,6 +42,10 @@ services:
|
|
|
42
42
|
WINDY_CONTAINERIZED: "1"
|
|
43
43
|
BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD: "true"
|
|
44
44
|
WINDY_SYSTEM_USAGE_PATHS: /app/data/audit
|
|
45
|
+
PLATFORM_ENABLE_AI: ${PLATFORM_ENABLE_AI:-false}
|
|
46
|
+
AGENT_SERVER_URL: http://agent-server:8080
|
|
47
|
+
AGENT_TOOL_HOST_URL: http://server:9747
|
|
48
|
+
AGENT_SERVER_INTERNAL_TOKEN: ${AGENT_SERVER_INTERNAL_TOKEN:-}
|
|
45
49
|
ports:
|
|
46
50
|
- "9747:9747"
|
|
47
51
|
volumes:
|
|
@@ -52,6 +56,26 @@ services:
|
|
|
52
56
|
migrate:
|
|
53
57
|
condition: service_completed_successfully
|
|
54
58
|
|
|
59
|
+
agent-server:
|
|
60
|
+
profiles: ["agent"]
|
|
61
|
+
build:
|
|
62
|
+
context: .
|
|
63
|
+
dockerfile: apps/agent-server/Dockerfile
|
|
64
|
+
environment:
|
|
65
|
+
AGENT_SERVER_INTERNAL_TOKEN: ${AGENT_SERVER_INTERNAL_TOKEN:-}
|
|
66
|
+
AGENT_RUN_STORE_PATH: /app/data/agent/runs.sqlite3
|
|
67
|
+
QWEN_API_URL: ${QWEN_API_URL:-}
|
|
68
|
+
QWEN_API_KEY: ${QWEN_API_KEY:-}
|
|
69
|
+
QWEN_MODEL: ${QWEN_MODEL:-}
|
|
70
|
+
AI_PROVIDER_CONNECT_TIMEOUT_MS: ${AI_PROVIDER_CONNECT_TIMEOUT_MS:-5000}
|
|
71
|
+
AI_PROVIDER_REQUEST_TIMEOUT_MS: ${AI_PROVIDER_REQUEST_TIMEOUT_MS:-60000}
|
|
72
|
+
AI_PROVIDER_MAX_RESPONSE_BYTES: ${AI_PROVIDER_MAX_RESPONSE_BYTES:-2097152}
|
|
73
|
+
volumes:
|
|
74
|
+
- agent-run-data:/app/data/agent
|
|
75
|
+
depends_on:
|
|
76
|
+
server:
|
|
77
|
+
condition: service_started
|
|
78
|
+
|
|
55
79
|
web:
|
|
56
80
|
build:
|
|
57
81
|
context: .
|
|
@@ -85,3 +109,4 @@ services:
|
|
|
85
109
|
volumes:
|
|
86
110
|
postgres-data:
|
|
87
111
|
server-audit-data:
|
|
112
|
+
agent-run-data:
|