create-windy 0.9.1 → 0.10.0

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 (253) hide show
  1. package/dist/cli.js +1769 -1277
  2. package/package.json +2 -2
  3. package/template/.agents/skills/windy-business-module/SKILL.md +33 -1
  4. package/template/.windy-template.json +6 -2
  5. package/template/AGENTS.md +4 -0
  6. package/template/README.md +2 -0
  7. package/template/apps/agent-server/Dockerfile +1 -1
  8. package/template/apps/agent-server/README.md +1 -1
  9. package/template/apps/agent-server/native/EXTENSIONS.md +2 -2
  10. package/template/apps/agent-server/native/parallel-harness.ts +2 -0
  11. package/template/apps/agent-server/native/storage-lifecycle.test.ts +2 -1
  12. package/template/apps/agent-server/package.json +42 -41
  13. package/template/apps/agent-server/plugins/downloads/README.md +1 -1
  14. package/template/apps/agent-server/plugins/drop-overlay/README.md +1 -1
  15. package/template/apps/agent-server/plugins/settings/README.md +1 -1
  16. package/template/apps/agent-server/plugins/skill-library/client-chunks.test.ts +4 -1
  17. package/template/apps/agent-server/plugins/theme/README.md +5 -1
  18. package/template/apps/agent-server/plugins/theme/source-tokens.ts +0 -1
  19. package/template/apps/agent-server/plugins/theme/tokens.json +0 -4
  20. package/template/apps/agent-server/plugins/tool-catalog/README.md +6 -3
  21. package/template/apps/agent-server/plugins/tool-catalog/discovery.mjs +13 -10
  22. package/template/apps/agent-server/plugins/tool-catalog/discovery.test.ts +68 -6
  23. package/template/apps/agent-server/runtime/compatibility.ts +7 -1
  24. package/template/apps/agent-server/runtime/dsh-engine.ts +2 -0
  25. package/template/apps/agent-server/runtime/questions-workflow.nodecheck.ts +2 -0
  26. package/template/apps/server/Dockerfile +2 -1
  27. package/template/apps/server/package.json +11 -11
  28. package/template/apps/server/scripts/validate-module-composition-once.ts +2 -0
  29. package/template/apps/server/scripts/validate-module-composition.ts +8 -4
  30. package/template/apps/server/src/admin/routes.test.ts +29 -1
  31. package/template/apps/server/src/admin/routes.ts +20 -2
  32. package/template/apps/server/src/agent/client-session/completion.ts +1 -1
  33. package/template/apps/server/src/agent/client-session/manifest-admission.ts +1 -1
  34. package/template/apps/server/src/agent/client-session/request.ts +1 -1
  35. package/template/apps/server/src/agent/context-version-routes.ts +37 -13
  36. package/template/apps/server/src/agent/run-facade.ts +1 -1
  37. package/template/apps/server/src/agent/run-input-verification.ts +1 -1
  38. package/template/apps/server/src/agent/run-routes.ts +1 -14
  39. package/template/apps/server/src/agent/run-start-preparation.ts +1 -1
  40. package/template/apps/server/src/agent/runtime-bootstrap-input.ts +2 -0
  41. package/template/apps/server/src/agent/runtime-bootstrap.ts +49 -157
  42. package/template/apps/server/src/agent/runtime-disabled-composition.ts +2 -1
  43. package/template/apps/server/src/agent/session-invalidation/authorization-revocation.ts +54 -0
  44. package/template/apps/server/src/agent/session-runtime-composition.ts +1 -51
  45. package/template/apps/server/src/agent/unavailable-routes.ts +13 -2
  46. package/template/apps/server/src/agent-workflow/runtime-composition.ts +3 -0
  47. package/template/apps/server/src/agent-workflow/service.ts +17 -1
  48. package/template/apps/server/src/ai-artifact/media-resource-route.test.ts +134 -0
  49. package/template/apps/server/src/ai-artifact/routes.ts +1 -1
  50. package/template/apps/server/src/ai-artifact/runtime.ts +12 -0
  51. package/template/apps/server/src/ai-runtime-readiness/operation-port.test-support.ts +1 -1
  52. package/template/apps/server/src/ai-runtime-readiness/operation-port.ts +8 -0
  53. package/template/apps/server/src/ai-runtime-security/internal-token.ts +14 -0
  54. package/template/apps/server/src/app/antivirus-composition.test.ts +63 -0
  55. package/template/apps/server/src/app/antivirus-composition.ts +29 -0
  56. package/template/apps/server/src/app/dsh-workbench-socket-audit.test.ts +2 -1
  57. package/template/apps/server/src/configuration/definition.ts +15 -11
  58. package/template/apps/server/src/document/facade-support.ts +2 -2
  59. package/template/apps/server/src/document/facade.ts +2 -2
  60. package/template/apps/server/src/document/output-artifact-composition.ts +9 -5
  61. package/template/apps/server/src/document/output-artifact.test-support.ts +1 -1
  62. package/template/apps/server/src/embedding/facade.ts +1 -1
  63. package/template/apps/server/src/embedding/input.ts +1 -1
  64. package/template/apps/server/src/embedding/routes.ts +1 -1
  65. package/template/apps/server/src/index.ts +60 -60
  66. package/template/apps/server/src/license/routes.ts +4 -1
  67. package/template/apps/server/src/license/runtime-business-access.test.ts +261 -0
  68. package/template/apps/server/src/media-runtime/composition.ts +157 -0
  69. package/template/apps/server/src/media-runtime/media-only-composition.ts +44 -0
  70. package/template/apps/server/src/media-runtime/module-ai-port-factory.test.ts +69 -0
  71. package/template/apps/server/src/media-runtime/module-ai-port-factory.ts +72 -0
  72. package/template/apps/server/src/{agent → media-runtime}/runtime-readiness-sources.ts +14 -6
  73. package/template/apps/server/src/media-runtime/shared-composition.ts +104 -0
  74. package/template/apps/server/src/module-composition.test.ts +22 -0
  75. package/template/apps/server/src/module-composition.ts +48 -0
  76. package/template/apps/server/src/module-host/ai-operation-port.ts +97 -30
  77. package/template/apps/server/src/module-host/ai-operation-speech-normalize.test.ts +1 -1
  78. package/template/apps/server/src/module-host/http-runtime.ts +9 -3
  79. package/template/apps/server/src/module-host/static-route-authorization.test.ts +6 -3
  80. package/template/apps/server/src/module-host/storage-authorization-binding.test.ts +4 -0
  81. package/template/apps/server/src/module-runtime-validation.ts +2 -0
  82. package/template/apps/server/src/observability/diagnostic-index.ts +26 -1
  83. package/template/apps/server/src/observability/diagnostic-repository.ts +40 -11
  84. package/template/apps/server/src/platform-http-runtime.ts +1 -1
  85. package/template/apps/server/src/runtime-feature.ts +5 -1
  86. package/template/apps/server/src/runtime.ts +3 -0
  87. package/template/apps/server/src/settings/drizzle-watermark-repository.ts +35 -4
  88. package/template/apps/server/src/settings/watermark-repository.ts +14 -0
  89. package/template/apps/server/src/settings/watermark-routes.test.ts +84 -2
  90. package/template/apps/server/src/settings/watermark-routes.ts +109 -0
  91. package/template/apps/server/src/settings/watermark-transfer.ts +79 -0
  92. package/template/apps/server/src/speech/facade-validation.ts +1 -1
  93. package/template/apps/server/src/speech/facade.ts +2 -2
  94. package/template/apps/server/src/startup-composition-validation.test.ts +32 -0
  95. package/template/apps/server/src/startup-finalization.ts +2 -0
  96. package/template/apps/server/src/startup-runtime.ts +77 -0
  97. package/template/apps/server/src/system/ai-runtime-build.ts +3 -0
  98. package/template/apps/server/src/system/ai-runtime-configuration.test.ts +36 -0
  99. package/template/apps/server/src/system/ai-runtime-configuration.ts +58 -0
  100. package/template/apps/server/src/system/diagnostic-routes.test.ts +45 -0
  101. package/template/apps/server/src/system/diagnostic-routes.ts +35 -0
  102. package/template/apps/server/src/system/environment-policy-reader.test.ts +74 -0
  103. package/template/apps/server/src/system/environment-policy-reader.ts +62 -0
  104. package/template/apps/server/src/system/feature-environment-assessment.test.ts +148 -0
  105. package/template/apps/server/src/system/feature-environment-assessment.ts +179 -0
  106. package/template/apps/server/src/system/host-diagnostics.test.ts +155 -0
  107. package/template/apps/server/src/system/host-diagnostics.ts +225 -0
  108. package/template/apps/server/src/system/menu-registry.test.ts +26 -0
  109. package/template/apps/server/src/system/menu-registry.ts +9 -7
  110. package/template/apps/server/src/system/operations.test.ts +5 -0
  111. package/template/apps/server/src/system/operations.ts +36 -12
  112. package/template/apps/server/src/system/role-authorization-postgres.integration.test.ts +181 -0
  113. package/template/apps/web/Dockerfile +7 -3
  114. package/template/apps/web/dev/dev-port.test.ts +6 -6
  115. package/template/apps/web/dev/dev-port.ts +1 -1
  116. package/template/apps/web/dev/start.ts +6 -1
  117. package/template/apps/web/index.html +1 -0
  118. package/template/apps/web/package.json +16 -16
  119. package/template/apps/web/runtime-server.test.ts +22 -1
  120. package/template/apps/web/runtime-server.ts +6 -0
  121. package/template/apps/web/src/App.vue +6 -3
  122. package/template/apps/web/src/assets/windy.svg +4 -0
  123. package/template/apps/web/src/components/auth/LoginForm.vue +2 -2
  124. package/template/apps/web/src/components/auth/PasswordChangeForm.vue +14 -3
  125. package/template/apps/web/src/components/common/FormDialog.vue +1 -1
  126. package/template/apps/web/src/components/license/LicenseFlow.webtest.ts +3 -0
  127. package/template/apps/web/src/components/license/LicenseMachineCodeQr.vue +99 -0
  128. package/template/apps/web/src/components/license/LicenseMachineCodeQr.webtest.ts +83 -0
  129. package/template/apps/web/src/components/license/LicensePairingCard.vue +1 -1
  130. package/template/apps/web/src/components/license/LicenseStatusCard.vue +14 -2
  131. package/template/apps/web/src/composables/useLoginFlow.ts +2 -2
  132. package/template/apps/web/src/composables/usePlatformSettings.ts +1 -1
  133. package/template/apps/web/src/composables/useWatermarkSettings.ts +1 -0
  134. package/template/apps/web/src/layout/AppSidebar.vue +5 -9
  135. package/template/apps/web/src/layout/AppSidebar.webtest.ts +4 -1
  136. package/template/apps/web/src/pages/auth/ChangePasswordPage.vue +67 -6
  137. package/template/apps/web/src/pages/auth/LoginPage.vue +3 -12
  138. package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.vue +30 -0
  139. package/template/apps/web/src/pages/configuration/components/ModuleConfigurationWorkbench.vue +25 -14
  140. package/template/apps/web/src/pages/configuration/components/TimeZoneSelect.vue +55 -0
  141. package/template/apps/web/src/pages/profile/PersonalSettingsPage.vue +64 -11
  142. package/template/apps/web/src/pages/profile/PersonalSettingsPage.webtest.ts +21 -1
  143. package/template/apps/web/src/pages/settings/BrandingSettingsSection.vue +7 -18
  144. package/template/apps/web/src/pages/settings/BrandingSettingsSection.webtest.ts +9 -4
  145. package/template/apps/web/src/pages/settings/GlobalSettingsPage.vue +51 -6
  146. package/template/apps/web/src/pages/settings/GlobalSettingsPage.webtest.ts +19 -3
  147. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +1 -0
  148. package/template/apps/web/src/pages/settings/WatermarkYamlTransfer.vue +211 -0
  149. package/template/apps/web/src/pages/system/DiagnosticLookupPage.vue +18 -3
  150. package/template/apps/web/src/pages/system/DiagnosticLookupPage.webtest.ts +4 -1
  151. package/template/apps/web/src/pages/system/SystemOperationsPage.vue +23 -0
  152. package/template/apps/web/src/pages/system/components/DiagnosticLogList.vue +120 -0
  153. package/template/apps/web/src/pages/system/components/OperationEnvironmentPanel.vue +237 -0
  154. package/template/apps/web/src/pages/system/components/OperationFeatureAssessmentPanel.vue +131 -0
  155. package/template/apps/web/src/pages/system/components/PagedSelectionList.vue +31 -24
  156. package/template/apps/web/src/pages/system/components/SystemResourceActions.vue +2 -1
  157. package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +11 -2
  158. package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +1 -0
  159. package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +6 -2
  160. package/template/apps/web/src/pages/system/components/agent-skills/AgentSkillImportDialog.vue +18 -9
  161. package/template/apps/web/src/pages/system/components/agent-workbench/SessionReplayTimeline.vue +18 -9
  162. package/template/apps/web/src/pages/system/components/agent-workbench/TestRunComposer.vue +66 -34
  163. package/template/apps/web/src/pages/system/components/agent-workbench/ToolCatalogPanel.vue +48 -33
  164. package/template/apps/web/src/pages/system/components/agent-workbench/ToolCatalogPanel.webtest.ts +9 -2
  165. package/template/apps/web/src/pages/system/components/agent-workbench/ToolRuleDetail.vue +23 -16
  166. package/template/apps/web/src/pages/system/components/dictionary-bulk/DictionaryBulkJobDetail.vue +10 -7
  167. package/template/apps/web/src/pages/system/components/user-account/RolePermissionSummary.vue +81 -57
  168. package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.vue +6 -4
  169. package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.webtest.ts +20 -4
  170. package/template/apps/web/src/pages/system/resource-config.ts +9 -0
  171. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +1 -1
  172. package/template/apps/web/src/router/disabled-feature-loading.webtest.ts +78 -0
  173. package/template/apps/web/src/router/index.ts +3 -6
  174. package/template/apps/web/src/router/manifest-routes.ts +2 -3
  175. package/template/apps/web/src/router/platform-admin-pages.ts +21 -62
  176. package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -4
  177. package/template/apps/web/src/services/diagnostics-api.ts +13 -1
  178. package/template/apps/web/src/services/operations-api.ts +4 -0
  179. package/template/apps/web/src/services/platform-settings-api.ts +35 -1
  180. package/template/apps/web/vite.config.ts +9 -3
  181. package/template/deploy/environment/windy.environment.example.yaml +53 -0
  182. package/template/docker-compose.yml +4 -2
  183. package/template/docs/architecture/agent-admin-workbench-implementation.md +1 -1
  184. package/template/docs/architecture/agent-admin-workbench-plan.md +1 -1
  185. package/template/docs/architecture/agent-workflow.md +8 -0
  186. package/template/docs/architecture/durable-jobs.md +17 -0
  187. package/template/docs/architecture/embedded-dsh-integration.md +1 -1
  188. package/template/docs/development/dsh-plugins.md +1 -1
  189. package/template/docs/development/performance-engineering.md +24 -0
  190. package/template/docs/operations/admin-settings-yaml.md +29 -0
  191. package/template/docs/operations/dsh-agent-migration.md +2 -2
  192. package/template/docs/operations/environment-capabilities.md +185 -0
  193. package/template/docs/platform/module-configuration.md +18 -3
  194. package/template/docs/platform/platform-authentication.md +1 -1
  195. package/template/package.json +10 -8
  196. package/template/packages/config/package.json +2 -2
  197. package/template/packages/config/src/definition.ts +14 -4
  198. package/template/packages/config/src/environment-loader.ts +27 -3
  199. package/template/packages/config/src/platform-ai-runtime.test.ts +94 -0
  200. package/template/packages/config/src/platform.ts +5 -2
  201. package/template/packages/database/drizzle/0065_durable_job_lease_snapshot_lookup.sql +1 -0
  202. package/template/packages/database/drizzle/0066_agent_source_sequence_bigint.sql +3 -0
  203. package/template/packages/database/drizzle/0067_media_runtime_access.sql +46 -0
  204. package/template/packages/database/drizzle/meta/0065_snapshot.json +15370 -0
  205. package/template/packages/database/drizzle/meta/0066_snapshot.json +15370 -0
  206. package/template/packages/database/drizzle/meta/0067_snapshot.json +15370 -0
  207. package/template/packages/database/drizzle/meta/_journal.json +21 -0
  208. package/template/packages/database/package.json +3 -3
  209. package/template/packages/database/src/schema/agent-session.ts +4 -3
  210. package/template/packages/database/src/schema/durable-jobs.ts +4 -0
  211. package/template/packages/database/src/schema/index.ts +3 -1
  212. package/template/packages/database/src/schema-durable-recovery.test.ts +21 -1
  213. package/template/packages/database/src/schema-source-sequence.test.ts +24 -0
  214. package/template/packages/example-work-order/package.json +2 -2
  215. package/template/packages/modules/package.json +1 -1
  216. package/template/packages/modules/src/admin-ui-boundary.test.ts +33 -0
  217. package/template/packages/modules/src/composition.ts +11 -28
  218. package/template/packages/modules/src/manifest.ts +2 -0
  219. package/template/packages/modules/src/menu-composition.ts +24 -0
  220. package/template/packages/modules/src/system-admin-routes.ts +2 -2
  221. package/template/packages/modules/src/system-admin-ui-catalog.ts +6 -1
  222. package/template/packages/modules/src/system-ai-api-bindings.ts +0 -16
  223. package/template/packages/modules/src/system-api-control-plane.ts +9 -1
  224. package/template/packages/modules/src/system-api-permissions.ts +7 -0
  225. package/template/packages/modules/src/system-api-watermark.ts +21 -0
  226. package/template/packages/modules/src/system-audit-actions.ts +3 -1
  227. package/template/packages/modules/src/system-features.ts +21 -3
  228. package/template/packages/modules/src/system-media-api-bindings.ts +97 -0
  229. package/template/packages/modules/src/system-module-audit-selection.ts +2 -0
  230. package/template/packages/modules/src/system-module-catalog.test.ts +13 -6
  231. package/template/packages/modules/src/system-module-catalog.ts +6 -241
  232. package/template/packages/modules/src/system-module-definitions.ts +260 -0
  233. package/template/packages/modules/src/system-modules-access.test.ts +2 -0
  234. package/template/packages/modules/src/system-modules.test.ts +7 -1
  235. package/template/packages/modules/src/system-modules.ts +25 -13
  236. package/template/packages/modules/src/system-permissions.ts +12 -0
  237. package/template/packages/server-sdk/package.json +1 -1
  238. package/template/packages/shared/index.ts +3 -0
  239. package/template/packages/shared/package.json +1 -1
  240. package/template/packages/shared/src/environment-assessment.ts +68 -0
  241. package/template/packages/shared/src/environment-policy.ts +205 -0
  242. package/template/packages/shared/src/license-console-capabilities.ts +7 -0
  243. package/template/packages/shared/src/license.ts +1 -0
  244. package/template/packages/shared/src/module-configuration.ts +2 -1
  245. package/template/scripts/operations/collect-device-info.py +230 -0
  246. package/template/apps/server/src/agent/module-ai-port-factory.ts +0 -41
  247. /package/template/apps/server/src/{agent → ai-runtime-security}/execution-grants.ts +0 -0
  248. /package/template/apps/server/src/{agent → ai-runtime-security}/idempotency-key.test.ts +0 -0
  249. /package/template/apps/server/src/{agent → ai-runtime-security}/idempotency-key.ts +0 -0
  250. /package/template/apps/server/src/{agent → ai-runtime-security}/provider-input-governance.test.ts +0 -0
  251. /package/template/apps/server/src/{agent → ai-runtime-security}/provider-input-governance.ts +0 -0
  252. /package/template/apps/server/src/{agent → media-runtime}/required-environment.ts +0 -0
  253. /package/template/apps/server/src/{agent → media-runtime}/runtime-readiness-sources.test.ts +0 -0
@@ -0,0 +1,69 @@
1
+ import { expect, test } from "bun:test";
2
+ import { systemModuleManifest } from "@southwind-ai/modules";
3
+ import {
4
+ operationKey,
5
+ readinessManifest,
6
+ readySnapshot,
7
+ } from "../ai-runtime-readiness/operation-port.test-support.js";
8
+ import { AiRuntimeReadinessTrustStoreV1 } from "../ai-runtime-readiness/trust-store.js";
9
+ import { createServerRuntime } from "../runtime.js";
10
+ import { createModuleAiPorts } from "./module-ai-port-factory.js";
11
+
12
+ test("已授权的业务 Operation 在 Document Feature 关闭时不探测远端", async () => {
13
+ const modules = [
14
+ readinessManifest(),
15
+ systemModuleManifest("system.media-runtime"),
16
+ ];
17
+ const featureCatalog = modules.flatMap((module) => module.features);
18
+ const runtime = createServerRuntime(
19
+ {},
20
+ {
21
+ featureCatalog,
22
+ disabledFeatureKeys: new Set(["system.document"]),
23
+ licenseEnforced: false,
24
+ },
25
+ );
26
+ const context = await runtime.createGuardContextForActor(
27
+ new Request("http://localhost/evidence"),
28
+ {
29
+ id: "ocr-operator",
30
+ type: "user",
31
+ name: "OCR operator",
32
+ roleCodes: [],
33
+ permissionKeys: ["evidence.document.ocr.execute"],
34
+ },
35
+ );
36
+ let probes = 0;
37
+ const ports = createModuleAiPorts({
38
+ modules,
39
+ runtime,
40
+ sources: [
41
+ {
42
+ kind: "document",
43
+ async health() {
44
+ probes += 1;
45
+ return Response.json(readySnapshot());
46
+ },
47
+ },
48
+ ],
49
+ operations: { modules },
50
+ trustStore: new AiRuntimeReadinessTrustStoreV1(),
51
+ });
52
+ const signal = AbortSignal.timeout(1_000);
53
+ await expect(
54
+ ports.readiness("evidence", context, signal).inspect({ operationKey }),
55
+ ).resolves.toMatchObject({
56
+ runtimeKind: "document",
57
+ status: "unavailable",
58
+ unavailableReasons: [{ code: "RUNTIME_NOT_CONFIGURED" }],
59
+ });
60
+ expect(probes).toBe(0);
61
+ await expect(
62
+ ports.operations("evidence", context, signal).startDocument({
63
+ operationKey,
64
+ sources: [],
65
+ options: {},
66
+ idempotencyKey: "regression",
67
+ }),
68
+ ).rejects.toMatchObject({ code: "AI_OPERATION_UNAVAILABLE" });
69
+ });
@@ -0,0 +1,72 @@
1
+ import type { AiRuntimeKind } from "@southwind-ai/shared";
2
+ import { ModuleAiOperationError } from "@southwind-ai/server-sdk";
3
+ import { evaluateFeatureGuard } from "../guards.js";
4
+ import type { ModuleManifest } from "@southwind-ai/modules";
5
+ import { createModuleAiOperationReadinessPort } from "../ai-runtime-readiness/operation-port.js";
6
+ import type { AiRuntimeReadinessTrustStoreV1 } from "../ai-runtime-readiness/trust-store.js";
7
+ import { createModuleAiOperationPort } from "../module-host/ai-operation-port.js";
8
+ import type { createServerRuntime } from "../runtime.js";
9
+
10
+ type OperationRuntime = Parameters<typeof createModuleAiOperationPort>[3];
11
+ type ReadinessInput = Parameters<
12
+ typeof createModuleAiOperationReadinessPort
13
+ >[0];
14
+
15
+ export function createModuleAiPorts(input: {
16
+ readonly modules: readonly ModuleManifest[];
17
+ readonly runtime: ReturnType<typeof createServerRuntime>;
18
+ readonly sources: ReadinessInput["sources"];
19
+ readonly trustStore: AiRuntimeReadinessTrustStoreV1;
20
+ readonly operations: OperationRuntime;
21
+ }) {
22
+ const features = new Map(
23
+ input.modules.flatMap((module) =>
24
+ module.features.map((feature) => [String(feature.key), feature] as const),
25
+ ),
26
+ );
27
+ const runtimeAllowed = async (
28
+ guardContext: Parameters<typeof createModuleAiOperationPort>[1],
29
+ kind: AiRuntimeKind,
30
+ ) => {
31
+ const feature = features.get(`system.${kind}`);
32
+ if (!feature) return false;
33
+ const decision = evaluateFeatureGuard(
34
+ guardContext,
35
+ await input.runtime.resolveFeature(guardContext, feature),
36
+ );
37
+ if (!decision.allowed) input.runtime.recordDecisions([decision]);
38
+ return decision.allowed;
39
+ };
40
+ const operations = (
41
+ moduleName: string,
42
+ guardContext: Parameters<typeof createModuleAiOperationPort>[1],
43
+ signal: AbortSignal,
44
+ ) =>
45
+ createModuleAiOperationPort(moduleName, guardContext, signal, {
46
+ ...input.operations,
47
+ async requireFeature(kind) {
48
+ if (!(await runtimeAllowed(guardContext, kind))) {
49
+ throw new ModuleAiOperationError(
50
+ "AI_OPERATION_UNAVAILABLE",
51
+ `${kind} Feature 未启用`,
52
+ );
53
+ }
54
+ },
55
+ });
56
+ const readiness = (
57
+ moduleName: string,
58
+ guardContext: Parameters<typeof createModuleAiOperationPort>[1],
59
+ signal: AbortSignal,
60
+ ) =>
61
+ createModuleAiOperationReadinessPort({
62
+ moduleName,
63
+ guardContext,
64
+ signal,
65
+ modules: input.modules,
66
+ sources: input.sources,
67
+ trustStore: input.trustStore,
68
+ runtimeAllowed: (kind) => runtimeAllowed(guardContext, kind),
69
+ runtime: input.runtime,
70
+ });
71
+ return { operations, readiness };
72
+ }
@@ -1,3 +1,4 @@
1
+ import type { AiRuntimeSwitches } from "@southwind-ai/shared";
1
2
  import type { AiRuntimeReadinessSourceV1 } from "../ai-runtime-readiness/operation-port.js";
2
3
 
3
4
  interface HealthPort {
@@ -5,16 +6,22 @@ interface HealthPort {
5
6
  }
6
7
 
7
8
  export function createAiRuntimeReadinessSources(input: {
8
- agent: HealthPort;
9
+ agent?: HealthPort;
9
10
  document: HealthPort;
10
11
  speech: HealthPort;
11
12
  embedding: HealthPort;
13
+ switches?: AiRuntimeSwitches;
12
14
  }): readonly AiRuntimeReadinessSourceV1[] {
13
- return [
14
- {
15
- kind: "agent",
16
- health: (signal) => input.agent.health(signal),
17
- },
15
+ const agent = input.agent;
16
+ const sources: AiRuntimeReadinessSourceV1[] = [
17
+ ...(agent
18
+ ? [
19
+ {
20
+ kind: "agent" as const,
21
+ health: (signal?: AbortSignal) => agent.health(signal),
22
+ },
23
+ ]
24
+ : []),
18
25
  {
19
26
  kind: "document",
20
27
  health: (signal) => input.document.health(signal),
@@ -28,4 +35,5 @@ export function createAiRuntimeReadinessSources(input: {
28
35
  health: (signal) => input.embedding.health(signal),
29
36
  },
30
37
  ];
38
+ return sources.filter(({ kind }) => input.switches?.[kind] !== false);
31
39
  }
@@ -0,0 +1,104 @@
1
+ import { AiArtifactCodec } from "../ai-artifact/codec.js";
2
+ import { registerAiArtifactRoutes } from "../ai-artifact/routes.js";
3
+ import { AiArtifactRuntime } from "../ai-artifact/runtime.js";
4
+ import { createModuleAiOperationReadinessPort } from "../ai-runtime-readiness/operation-port.js";
5
+ import { AiRuntimeReadinessTrustStoreV1 } from "../ai-runtime-readiness/trust-store.js";
6
+ import { connectMediaRuntimes, registerMediaRuntimes } from "./composition.js";
7
+ import { ExecutionGrantStore } from "../ai-runtime-security/execution-grants.js";
8
+ import type {
9
+ AiRuntimeSwitches,
10
+ FeatureFlagDefinition,
11
+ } from "@southwind-ai/shared";
12
+ import { createAiRuntimeReadinessSources } from "./runtime-readiness-sources.js";
13
+ import { createModuleAiPorts } from "./module-ai-port-factory.js";
14
+
15
+ type MediaInput = Parameters<typeof registerMediaRuntimes>[0];
16
+ type OperationServices = Parameters<
17
+ typeof createModuleAiPorts
18
+ >[0]["operations"];
19
+
20
+ export function composeSharedAiRuntime(
21
+ input: Pick<
22
+ MediaInput,
23
+ | "modules"
24
+ | "hosts"
25
+ | "runtime"
26
+ | "storage"
27
+ | "db"
28
+ | "resolveActor"
29
+ | "internalToken"
30
+ > & {
31
+ app: MediaInput["aiRouteApp"];
32
+ switches?: AiRuntimeSwitches;
33
+ features: readonly FeatureFlagDefinition[];
34
+ secret: string;
35
+ agent?: Parameters<typeof createAiRuntimeReadinessSources>[0]["agent"];
36
+ },
37
+ ) {
38
+ const aiArtifacts = new AiArtifactRuntime(
39
+ new AiArtifactCodec(input.secret),
40
+ input.hosts,
41
+ input.runtime,
42
+ input.features,
43
+ input.storage,
44
+ input.resolveActor,
45
+ );
46
+ registerAiArtifactRoutes(input.app, aiArtifacts, input.internalToken);
47
+ const grants = new ExecutionGrantStore();
48
+ const media = connectMediaRuntimes(input.internalToken, input.switches);
49
+ const { documentRemote, speechRemote, embeddingRemote } = media;
50
+ const readinessSources = createAiRuntimeReadinessSources({
51
+ agent: input.agent,
52
+ document: documentRemote,
53
+ speech: speechRemote,
54
+ embedding: embeddingRemote,
55
+ switches: input.switches,
56
+ });
57
+ const readinessTrustStore = new AiRuntimeReadinessTrustStoreV1();
58
+ const aiReadiness = createModuleAiOperationReadinessPort({
59
+ modules: input.modules,
60
+ sources: readinessSources,
61
+ trustStore: readinessTrustStore,
62
+ runtime: input.runtime,
63
+ });
64
+ const { documentFacadeWithOutput, speechFacade, embeddingFacade } =
65
+ registerMediaRuntimes({
66
+ ...media,
67
+ switches: input.switches,
68
+ modules: input.modules,
69
+ hosts: input.hosts,
70
+ runtime: input.runtime,
71
+ storage: input.storage,
72
+ db: input.db,
73
+ aiReadiness,
74
+ resolveActor: input.resolveActor,
75
+ aiArtifacts,
76
+ grants,
77
+ aiRouteApp: input.app,
78
+ internalToken: input.internalToken,
79
+ });
80
+
81
+ return {
82
+ aiArtifacts,
83
+ catalogReadiness: aiReadiness,
84
+ modulePorts(
85
+ agent: Pick<OperationServices, "agentFacade" | "agentRemote"> = {},
86
+ ) {
87
+ return createModuleAiPorts({
88
+ modules: input.modules,
89
+ runtime: input.runtime,
90
+ sources: readinessSources,
91
+ trustStore: readinessTrustStore,
92
+ operations: {
93
+ ...agent,
94
+ documentFacade: documentFacadeWithOutput,
95
+ documentRemote,
96
+ speechFacade,
97
+ speechRemote,
98
+ embeddingFacade,
99
+ modules: input.modules,
100
+ },
101
+ });
102
+ },
103
+ };
104
+ }
@@ -53,6 +53,28 @@ describe("Server Module Composition", () => {
53
53
  ).toThrow("API Route Handler Key 重复:POST /api/auth/users/:id/password");
54
54
  });
55
55
 
56
+ test("启动配置关闭的能力可省略 Handler,启用能力仍须完整注册", () => {
57
+ const module = fixture();
58
+ const feature = module.apiPermissions[0]!.featureKey!;
59
+ const input = { modules: [module], routes: [] };
60
+ expect(() => validateServerModuleComposition(input)).toThrow(
61
+ "缺少 Handler",
62
+ );
63
+ expect(() =>
64
+ validateServerModuleComposition({
65
+ ...input,
66
+ disabledFeatureKeys: new Set([feature]),
67
+ }),
68
+ ).not.toThrow();
69
+ expect(() =>
70
+ validateServerModuleComposition({
71
+ ...input,
72
+ routes: [{ method: "GET", path: "/api/unregistered" }],
73
+ disabledFeatureKeys: new Set([feature]),
74
+ }),
75
+ ).toThrow("未在 Manifest 声明");
76
+ });
77
+
56
78
  test("Host 注册实现必须在 Manifest 声明", () => {
57
79
  const app = new Elysia({ prefix: "/api" }).post(
58
80
  "/auth/users/:id/password",
@@ -28,6 +28,7 @@ export interface ServerCompositionInput {
28
28
  * 产品、运维、认证与恢复入口仍必须进入 Manifest。
29
29
  */
30
30
  frameworkRoutes?: readonly string[];
31
+ disabledFeatureKeys?: ReadonlySet<string>;
31
32
  tasks?: readonly { key: string }[];
32
33
  tools?: ReadonlyMap<string, unknown>;
33
34
  aiOperations?: readonly string[];
@@ -50,7 +51,37 @@ export function validateServerModuleComposition(
50
51
  apiRoutes,
51
52
  input.frameworkRoutes ?? [],
52
53
  );
54
+ const disabledFeatures = disabledRuntimeFeatureClosure(
55
+ input.modules,
56
+ input.disabledFeatureKeys,
57
+ );
58
+ const registered = new Set(apiRoutes);
59
+ const outputPrefix = "/api/internal/ai/output-artifacts";
60
+ const hasOutputSink = input.routes.some(({ path }) =>
61
+ path.startsWith(outputPrefix),
62
+ );
63
+ const unavailableApiRoutes = new Set(
64
+ input.modules.flatMap((module) =>
65
+ module.apiPermissions
66
+ .filter(
67
+ ({ featureKey, path }) =>
68
+ (featureKey !== undefined && disabledFeatures.has(featureKey)) ||
69
+ (!hasOutputSink &&
70
+ featureKey === "system.ai-artifact" &&
71
+ path.startsWith(outputPrefix)),
72
+ )
73
+ .map(apiRouteKey)
74
+ .filter((key) => !registered.has(key)),
75
+ ),
76
+ );
77
+ const unavailableTools = disabledFeatures.has("system.agent")
78
+ ? new Set(
79
+ input.modules.flatMap((module) => module.tools.map(({ key }) => key)),
80
+ )
81
+ : undefined;
53
82
  validateModuleComposition(input.modules, {
83
+ unavailableApiRoutes,
84
+ unavailableTools,
54
85
  apiRoutes,
55
86
  tasks: (input.tasks || []).map(({ key }) => key),
56
87
  tools: [...(input.tools?.keys() || [])],
@@ -62,6 +93,23 @@ export function validateServerModuleComposition(
62
93
  }
63
94
  }
64
95
 
96
+ function disabledRuntimeFeatureClosure(
97
+ modules: readonly ModuleManifest[],
98
+ disabledKeys: ReadonlySet<string> | undefined,
99
+ ) {
100
+ const disabled = new Set(disabledKeys);
101
+ const features = modules.flatMap((module) => module.features);
102
+ let previousSize = -1;
103
+ while (previousSize !== disabled.size) {
104
+ previousSize = disabled.size;
105
+ for (const feature of features) {
106
+ if (feature.dependencies?.some((key) => disabled.has(key)))
107
+ disabled.add(feature.key);
108
+ }
109
+ }
110
+ return disabled;
111
+ }
112
+
65
113
  function validateHttpRouteRegistration(
66
114
  modules: readonly ModuleManifest[],
67
115
  registeredRoutes: readonly string[],
@@ -1,15 +1,15 @@
1
- // @windy-module system.agent begin
1
+ // @windy-module system.media-runtime begin
2
2
  import type {
3
3
  ModuleAiOperationDefinition,
4
4
  ModuleManifest,
5
5
  } from "@southwind-ai/modules";
6
6
  import type {
7
+ ModuleAiArtifactRef,
7
8
  ModuleAiJsonValue,
9
+ ModuleAiRunReceipt,
8
10
  ModuleAiOperationPort,
9
11
  } from "@southwind-ai/server-sdk";
10
12
  import { ModuleAiOperationError } from "@southwind-ai/server-sdk";
11
- import type { AgentRunFacade } from "../agent/run-facade.js";
12
- import type { AgentRuntimePort } from "../agent/run-contracts.js";
13
13
  import type { DocumentRuntimePort } from "../document/contracts.js";
14
14
  import type { DocumentRunFacade } from "../document/facade.js";
15
15
  import type { EmbeddingFacade } from "../embedding/facade.js";
@@ -17,14 +17,48 @@ import type { RequestGuardContext } from "../guards.js";
17
17
  import type { SpeechRuntimePort } from "../speech/contracts.js";
18
18
  import type { SpeechRunFacade } from "../speech/facade.js";
19
19
 
20
+ interface AgentOperationService {
21
+ start(
22
+ operationKey: string,
23
+ input: ModuleAiJsonValue,
24
+ idempotencyKey: string,
25
+ context: RequestGuardContext,
26
+ artifacts?: readonly ModuleAiArtifactRef[],
27
+ signal?: AbortSignal,
28
+ ): Promise<ModuleAiRunReceipt>;
29
+ verifyRunInput(
30
+ operationKey: string,
31
+ runId: string,
32
+ input: ModuleAiJsonValue,
33
+ context: RequestGuardContext,
34
+ ): Promise<void>;
35
+ authorizeRun(
36
+ operationKey: string,
37
+ runId: string,
38
+ context: RequestGuardContext,
39
+ ): Promise<unknown>;
40
+ }
41
+
42
+ interface AgentRunTransport {
43
+ result(runId: string, signal?: AbortSignal): Promise<Response>;
44
+ cancel(
45
+ runId: string,
46
+ reason?: string,
47
+ signal?: AbortSignal,
48
+ ): Promise<Response>;
49
+ }
50
+
20
51
  export interface ModuleAiRuntimeServices {
21
- readonly agentFacade: AgentRunFacade;
22
- readonly agentRemote: AgentRuntimePort;
23
- readonly documentFacade: DocumentRunFacade;
24
- readonly documentRemote: DocumentRuntimePort;
25
- readonly speechFacade: SpeechRunFacade;
26
- readonly speechRemote: SpeechRuntimePort;
27
- readonly embeddingFacade: EmbeddingFacade;
52
+ readonly agentFacade?: AgentOperationService;
53
+ readonly agentRemote?: AgentRunTransport;
54
+ readonly documentFacade?: DocumentRunFacade;
55
+ readonly documentRemote?: DocumentRuntimePort;
56
+ readonly speechFacade?: SpeechRunFacade;
57
+ readonly speechRemote?: SpeechRuntimePort;
58
+ readonly embeddingFacade?: EmbeddingFacade;
59
+ readonly requireFeature?: (
60
+ kind: "agent" | "document" | "speech" | "embedding",
61
+ ) => Promise<void>;
28
62
  readonly modules: readonly ModuleManifest[];
29
63
  }
30
64
 
@@ -45,7 +79,8 @@ export function createModuleAiOperationPort(
45
79
  return {
46
80
  async startAgent(input) {
47
81
  own(input.operationKey);
48
- return services.agentFacade.start(
82
+ await services.requireFeature?.("agent");
83
+ return requireRuntime(services.agentFacade, "Agent").start(
49
84
  input.operationKey,
50
85
  input.input,
51
86
  input.idempotencyKey,
@@ -56,7 +91,8 @@ export function createModuleAiOperationPort(
56
91
  },
57
92
  async verifyAgentRun(input) {
58
93
  own(input.operationKey);
59
- await services.agentFacade.verifyRunInput(
94
+ await services.requireFeature?.("agent");
95
+ await requireRuntime(services.agentFacade, "Agent").verifyRunInput(
60
96
  input.operationKey,
61
97
  input.runId,
62
98
  input.input,
@@ -65,7 +101,8 @@ export function createModuleAiOperationPort(
65
101
  },
66
102
  async startDocument(input) {
67
103
  own(input.operationKey);
68
- return services.documentFacade.start(
104
+ await services.requireFeature?.("document");
105
+ return requireRuntime(services.documentFacade, "Document").start(
69
106
  input.operationKey,
70
107
  input.sources,
71
108
  input.options,
@@ -76,12 +113,13 @@ export function createModuleAiOperationPort(
76
113
  },
77
114
  async startSpeech(input) {
78
115
  own(input.operationKey);
116
+ await services.requireFeature?.("speech");
79
117
  const definition = ownSpeechDefinition(
80
118
  services.modules,
81
119
  moduleName,
82
120
  input.operationKey,
83
121
  );
84
- return services.speechFacade.start(
122
+ return requireRuntime(services.speechFacade, "Speech").start(
85
123
  input.operationKey,
86
124
  input.source,
87
125
  input.options,
@@ -93,8 +131,9 @@ export function createModuleAiOperationPort(
93
131
  },
94
132
  async embed(input) {
95
133
  own(input.operationKey);
134
+ await services.requireFeature?.("embedding");
96
135
  return jsonValue(
97
- await services.embeddingFacade.embed(
136
+ await requireRuntime(services.embeddingFacade, "Embedding").embed(
98
137
  input.operationKey,
99
138
  input.items,
100
139
  input.options,
@@ -106,51 +145,69 @@ export function createModuleAiOperationPort(
106
145
  },
107
146
  async result(input) {
108
147
  own(input.operationKey);
148
+ await services.requireFeature?.(input.kind);
109
149
  if (input.kind === "agent") {
110
- await services.agentFacade.authorizeRun(
150
+ await requireRuntime(services.agentFacade, "Agent").authorizeRun(
111
151
  input.operationKey,
112
152
  input.runId,
113
153
  context,
114
154
  );
115
155
  return responseJson(
116
- await services.agentRemote.result(input.runId, signal),
156
+ await requireRuntime(services.agentRemote, "Agent").result(
157
+ input.runId,
158
+ signal,
159
+ ),
117
160
  );
118
161
  }
119
162
  if (input.kind === "document") {
120
- await services.documentFacade.authorizeRun(input.runId, context);
163
+ await requireRuntime(services.documentFacade, "Document").authorizeRun(
164
+ input.runId,
165
+ context,
166
+ );
121
167
  return responseJson(
122
- await services.documentRemote.result(input.runId, signal),
168
+ await requireRuntime(services.documentRemote, "Document").result(
169
+ input.runId,
170
+ signal,
171
+ ),
123
172
  );
124
173
  }
125
- services.speechFacade.authorizeRun(input.runId, context);
174
+ requireRuntime(services.speechFacade, "Speech").authorizeRun(
175
+ input.runId,
176
+ context,
177
+ );
126
178
  return responseJson(
127
- await services.speechRemote.result(input.runId, signal),
179
+ await requireRuntime(services.speechRemote, "Speech").result(
180
+ input.runId,
181
+ signal,
182
+ ),
128
183
  );
129
184
  },
130
185
  async cancel(input) {
131
186
  own(input.operationKey);
187
+ await services.requireFeature?.(input.kind);
132
188
  let response: Response;
133
189
  if (input.kind === "agent") {
134
- await services.agentFacade.authorizeRun(
190
+ await requireRuntime(services.agentFacade, "Agent").authorizeRun(
135
191
  input.operationKey,
136
192
  input.runId,
137
193
  context,
138
194
  );
139
- response = await services.agentRemote.cancel(
195
+ response = await requireRuntime(services.agentRemote, "Agent").cancel(
140
196
  input.runId,
141
197
  input.reason,
142
198
  signal,
143
199
  );
144
200
  } else if (input.kind === "document") {
145
- response = await services.documentFacade.cancel(
201
+ response = await requireRuntime(
202
+ services.documentFacade,
203
+ "Document",
204
+ ).cancel(input.runId, input.reason, context, signal);
205
+ } else {
206
+ requireRuntime(services.speechFacade, "Speech").authorizeRun(
146
207
  input.runId,
147
- input.reason,
148
208
  context,
149
- signal,
150
209
  );
151
- } else {
152
- services.speechFacade.authorizeRun(input.runId, context);
153
- response = await services.speechRemote.cancel(
210
+ response = await requireRuntime(services.speechRemote, "Speech").cancel(
154
211
  input.runId,
155
212
  input.reason,
156
213
  signal,
@@ -161,6 +218,16 @@ export function createModuleAiOperationPort(
161
218
  };
162
219
  }
163
220
 
221
+ function requireRuntime<T>(service: T | undefined, kind: string): T {
222
+ if (service === undefined) {
223
+ throw new ModuleAiOperationError(
224
+ "AI_OPERATION_UNAVAILABLE",
225
+ `${kind} Runtime 未装配`,
226
+ );
227
+ }
228
+ return service;
229
+ }
230
+
164
231
  function ownSpeechDefinition(
165
232
  modules: readonly ModuleManifest[],
166
233
  moduleName: string,
@@ -235,4 +302,4 @@ function operationErrorCode(status: number, remoteCode: string) {
235
302
  function jsonValue(value: unknown): ModuleAiJsonValue {
236
303
  return JSON.parse(JSON.stringify(value)) as ModuleAiJsonValue;
237
304
  }
238
- // @windy-module system.agent end
305
+ // @windy-module system.media-runtime end
@@ -2,7 +2,7 @@
2
2
  import { expect, test } from "bun:test";
3
3
  import type { ModuleManifest } from "@southwind-ai/modules";
4
4
  import type { ModuleAiOperationReadinessV1 } from "@southwind-ai/server-sdk";
5
- import { ExecutionGrantStore } from "../agent/execution-grants.js";
5
+ import { ExecutionGrantStore } from "../ai-runtime-security/execution-grants.js";
6
6
  import { createRequestGuardContext } from "../guards.js";
7
7
  import { initializeModuleHosts } from "../module-host.js";
8
8
  import { createServerRuntime } from "../runtime.js";
@@ -10,11 +10,13 @@ import type { ReportStagingService } from "../report-staging/service.js";
10
10
  // @windy-module system.storage end
11
11
  import type { RouteApp } from "../system/route-types.js";
12
12
  import type { SystemRepositories } from "../system/seed.js";
13
- // @windy-module system.agent begin
13
+ // @windy-module system.media-runtime begin
14
14
  import type { RequestGuardContext } from "../guards.js";
15
15
  import type { AiArtifactRuntime } from "../ai-artifact/runtime.js";
16
16
  import type { ModuleAiOperationPort } from "@southwind-ai/server-sdk";
17
17
  import type { ModuleAiOperationReadinessPortV1 } from "@southwind-ai/server-sdk";
18
+ // @windy-module system.media-runtime end
19
+ // @windy-module system.agent begin
18
20
  import type { ModuleAiScreenshotIntakePortV1 } from "@southwind-ai/server-sdk";
19
21
  import type { ModuleAgentSessionMemoryPortV1 } from "@southwind-ai/server-sdk";
20
22
  import type { ModuleAuthoritativeSourceInvalidationPortV1 } from "@southwind-ai/server-sdk";
@@ -44,7 +46,7 @@ export function installModuleHttpRuntime(
44
46
  reportStaging?: ReportStagingService;
45
47
  // @windy-module system.storage end
46
48
  systemRepositories: SystemRepositories;
47
- // @windy-module system.agent begin
49
+ // @windy-module system.media-runtime begin
48
50
  aiArtifacts?: AiArtifactRuntime;
49
51
  aiOperations?: (
50
52
  moduleName: string,
@@ -56,6 +58,8 @@ export function installModuleHttpRuntime(
56
58
  guardContext: RequestGuardContext,
57
59
  signal: AbortSignal,
58
60
  ) => ModuleAiOperationReadinessPortV1;
61
+ // @windy-module system.media-runtime end
62
+ // @windy-module system.agent begin
59
63
  screenshots?: (
60
64
  moduleName: string,
61
65
  guardContext: RequestGuardContext,
@@ -95,7 +99,7 @@ export function installModuleHttpRuntime(
95
99
  userDirectory: {
96
100
  repositories: context.systemRepositories,
97
101
  },
98
- // @windy-module system.agent begin
102
+ // @windy-module system.media-runtime begin
99
103
  aiArtifacts: context.aiArtifacts
100
104
  ? (moduleName, guardContext, signal) =>
101
105
  context.aiArtifacts!.createModulePort(
@@ -114,6 +118,8 @@ export function installModuleHttpRuntime(
114
118
  : undefined,
115
119
  aiOperations: context.aiOperations,
116
120
  aiReadiness: context.aiReadiness,
121
+ // @windy-module system.media-runtime end
122
+ // @windy-module system.agent begin
117
123
  screenshots: context.screenshots,
118
124
  sessionMemory: context.sessionMemory,
119
125
  sourceInvalidation: context.sourceInvalidation,