create-windy 0.9.1 → 0.10.1

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 +1691 -1151
  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
@@ -1,3 +1,9 @@
1
+ import { createEnvironmentPolicyReader } from "./environment-policy-reader.js";
2
+ import { assessFeatureEnvironment } from "./feature-environment-assessment.js";
3
+ import {
4
+ createEnvironmentProbe,
5
+ type EnvironmentProbe,
6
+ } from "./host-diagnostics.js";
1
7
  import type { FeatureFlagDefinition, HttpMethod } from "@southwind-ai/shared";
2
8
  import type { ActiveAuthSession } from "../auth/repository.js";
3
9
  import type { RequestGuardContext } from "../guards.js";
@@ -32,6 +38,7 @@ interface OperationRouteContext {
32
38
  }
33
39
 
34
40
  export interface OperationRouteOptions {
41
+ environmentProbe?: EnvironmentProbe;
35
42
  redisHealthProbe?: RedisHealthProbe;
36
43
  databaseMonitorProbe?: DatabaseMonitorProbe;
37
44
  hostDiskUsageProbe?: HostDiskUsageProbe;
@@ -79,6 +86,8 @@ export function registerSystemOperationRoutes(
79
86
  );
80
87
  const cpuMonitorProbe = options.cpuMonitorProbe ?? createCpuMonitorProbe();
81
88
  const gpuMonitorProbe = options.gpuMonitorProbe ?? createGpuMonitorProbe();
89
+ const readEnvironmentPolicy = createEnvironmentPolicyReader();
90
+ const environmentProbe = options.environmentProbe ?? createEnvironmentProbe();
82
91
  const onlineUserProvider = options.onlineUserProvider ?? (async () => []);
83
92
  app.get("/system/operations", async (rawContext) => {
84
93
  const { guardContext } = rawContext as OperationRouteContext;
@@ -90,18 +99,29 @@ export function registerSystemOperationRoutes(
90
99
  },
91
100
  feature: findSystemFeature(features, "ops"),
92
101
  });
93
- return decision.allowed
94
- ? await createOperationSnapshot(
95
- redisHealthProbe,
96
- databaseMonitorProbe,
97
- hostDiskUsageProbe,
98
- systemUsageProbe,
99
- importDataUsageProbe,
100
- cpuMonitorProbe,
101
- gpuMonitorProbe,
102
- onlineUserProvider,
103
- )
104
- : forbidden(decision.reason);
102
+ if (!decision.allowed) return forbidden(decision.reason);
103
+ const snapshot = await createOperationSnapshot(
104
+ redisHealthProbe,
105
+ databaseMonitorProbe,
106
+ hostDiskUsageProbe,
107
+ systemUsageProbe,
108
+ importDataUsageProbe,
109
+ cpuMonitorProbe,
110
+ gpuMonitorProbe,
111
+ onlineUserProvider,
112
+ environmentProbe,
113
+ );
114
+ const featureAssessment = await assessFeatureEnvironment(
115
+ await readEnvironmentPolicy(),
116
+ {
117
+ environment: snapshot.environment,
118
+ definitions: runtime.featureDefinitions,
119
+ resolveEnabled: async (feature) =>
120
+ (await runtime.resolveFeature(guardContext, feature)).enabled,
121
+ environmentValues: process.env,
122
+ },
123
+ );
124
+ return { ...snapshot, featureAssessment };
105
125
  });
106
126
 
107
127
  return app;
@@ -123,6 +143,7 @@ async function createOperationSnapshot(
123
143
  cpuMonitorProbe: CpuMonitorProbe,
124
144
  gpuMonitorProbe: GpuMonitorProbe,
125
145
  onlineUserProvider: () => Promise<ActiveAuthSession[]>,
146
+ environmentProbe: EnvironmentProbe,
126
147
  ) {
127
148
  const [
128
149
  cacheMonitor,
@@ -133,6 +154,7 @@ async function createOperationSnapshot(
133
154
  cpuMonitor,
134
155
  gpuMonitor,
135
156
  activeSessions,
157
+ environment,
136
158
  ] = await Promise.all([
137
159
  redisHealthProbe(),
138
160
  databaseMonitorProbe(),
@@ -142,9 +164,11 @@ async function createOperationSnapshot(
142
164
  cpuMonitorProbe(),
143
165
  gpuMonitorProbe(),
144
166
  onlineUserProvider(),
167
+ environmentProbe(),
145
168
  ]);
146
169
  return {
147
170
  generatedAt: new Date().toISOString(),
171
+ environment,
148
172
  onlineUsers: activeSessions.map((session) => ({
149
173
  sessionId: session.sessionId,
150
174
  userId: session.userId,
@@ -0,0 +1,181 @@
1
+ // @windy-module system.agent begin
2
+ import {
3
+ afterAll,
4
+ beforeAll,
5
+ beforeEach,
6
+ describe,
7
+ expect,
8
+ test,
9
+ } from "bun:test";
10
+ import { resolve } from "node:path";
11
+ import { drizzle } from "drizzle-orm/node-postgres";
12
+ import { migrate } from "drizzle-orm/node-postgres/migrator";
13
+ import { Pool } from "pg";
14
+ import { agentSourceInvalidations } from "@southwind-ai/database";
15
+ import { createFoundationSnapshot } from "../foundation.js";
16
+ import type { RequestActor } from "../guards.js";
17
+ import { createSystemRepositories } from "./seed.js";
18
+ import { UserAccountPolicy } from "./user-account-policy.js";
19
+ import { invalidateActorAuthorization } from "../agent/session-invalidation/authorization-revocation.js";
20
+ import { ModuleAuthoritativeSourceAccessRegistry } from "../agent/context-memory/authoritative-source-registry.js";
21
+ import { DrizzleSessionInvalidationOutbox } from "../agent/session-persistence/drizzle-invalidation-outbox.js";
22
+
23
+ const databaseUrl = process.env.ORGANIZATION_TEST_DATABASE_URL;
24
+ const describePostgres = databaseUrl ? describe : describe.skip;
25
+ const originalPermissions = ["home.read", "case.read"];
26
+ const nextPermissions = [
27
+ ...originalPermissions,
28
+ "intelligence.read",
29
+ "analysis.overview.read",
30
+ ];
31
+ const actor: RequestActor = {
32
+ id: "role-test-admin",
33
+ type: "user",
34
+ name: "测试管理员",
35
+ roleCodes: ["super-admin"],
36
+ permissionKeys: ["system.role.manage"],
37
+ };
38
+
39
+ describePostgres("角色更新与 Agent 失效事件原子提交", () => {
40
+ let pool: Pool;
41
+ let repositories: ReturnType<typeof createSystemRepositories>;
42
+ let policy: UserAccountPolicy;
43
+ let failAfterPublish = false;
44
+
45
+ beforeAll(async () => {
46
+ pool = new Pool({ connectionString: databaseUrl });
47
+ await migrate(drizzle(pool), {
48
+ migrationsFolder: resolve(
49
+ import.meta.dir,
50
+ "../../../../packages/database/drizzle",
51
+ ),
52
+ migrationsSchema: "public",
53
+ migrationsTable: "__organization_test_migrations",
54
+ });
55
+ });
56
+ beforeEach(async () => {
57
+ await pool.query(
58
+ "truncate table users, roles, departments, agent_source_invalidations cascade",
59
+ );
60
+ const db = drizzle(pool);
61
+ repositories = createSystemRepositories(createFoundationSnapshot(), { db });
62
+ await repositories.departments.all();
63
+ await repositories.roles.create(
64
+ {
65
+ id: "test-role",
66
+ code: "demo.leader",
67
+ name: "测试领导",
68
+ dataScope: "department-and-children",
69
+ departmentIds: [],
70
+ permissionKeys: originalPermissions,
71
+ },
72
+ actor,
73
+ );
74
+ await repositories.users.create(
75
+ {
76
+ id: "test-officer",
77
+ username: "test-officer",
78
+ displayName: "测试用户",
79
+ departmentId: "dept_platform",
80
+ roleIds: ["test-role"],
81
+ },
82
+ actor,
83
+ );
84
+ const sources = new ModuleAuthoritativeSourceAccessRegistry([
85
+ {
86
+ moduleKey: "system.agent",
87
+ access: {
88
+ authorizeCurrent: async () => ({
89
+ outcome: "denied",
90
+ reason: "authorization-revoked",
91
+ }),
92
+ },
93
+ },
94
+ ]);
95
+ const outbox = new DrizzleSessionInvalidationOutbox(db);
96
+ failAfterPublish = false;
97
+ policy = new UserAccountPolicy(
98
+ repositories.users,
99
+ repositories.roles,
100
+ undefined,
101
+ {
102
+ async publish(event) {
103
+ const database =
104
+ repositories.organizationTransaction?.currentDatabase();
105
+ if (!database) throw new Error("missing organization transaction");
106
+ // 固定复现日志中的毫秒值;第二次更新也使用该时间,验证 latest + 1。
107
+ await invalidateActorAuthorization(
108
+ { ...event, occurredAt: "2026-11-06T15:34:43.424Z", database },
109
+ sources,
110
+ outbox,
111
+ );
112
+ if (failAfterPublish) throw new Error("forced transaction failure");
113
+ },
114
+ },
115
+ );
116
+ });
117
+ afterAll(async () => {
118
+ await pool?.end();
119
+ });
120
+
121
+ async function updatePermissions(permissionKeys: string[]) {
122
+ return repositories.organizationMutationRunner.run(async () => {
123
+ const current = await repositories.roles.get("test-role");
124
+ if (!current) throw new Error("missing role");
125
+ const updated = await repositories.roles.update(
126
+ current.id,
127
+ { permissionKeys },
128
+ actor,
129
+ );
130
+ if (!updated) throw new Error("missing updated role");
131
+ await policy.afterRoleUpdate(current, updated);
132
+ return updated;
133
+ });
134
+ }
135
+
136
+ test("现实时钟下增加、撤回权限均保存,并按 number 精确读取单调序号", async () => {
137
+ await updatePermissions(nextPermissions);
138
+ expect((await repositories.roles.get("test-role"))?.permissionKeys).toEqual(
139
+ nextPermissions,
140
+ );
141
+ await updatePermissions(originalPermissions);
142
+ const rows = await drizzle(pool)
143
+ .select()
144
+ .from(agentSourceInvalidations)
145
+ .orderBy(agentSourceInvalidations.sequence);
146
+ expect(rows.map(({ sequence }) => sequence)).toEqual([
147
+ 1793979283424, 1793979283425,
148
+ ]);
149
+ expect(rows.every(({ cause }) => cause === "authorization-revoked")).toBe(
150
+ true,
151
+ );
152
+ const deliveries = await pool.query(
153
+ "select count(*)::int as count from agent_source_invalidation_deliveries",
154
+ );
155
+ expect(deliveries.rows[0].count).toBeGreaterThan(0);
156
+ expect((await repositories.roles.get("test-role"))?.permissionKeys).toEqual(
157
+ originalPermissions,
158
+ );
159
+ });
160
+
161
+ test("事件追加后发生错误仍同时回滚角色和 outbox", async () => {
162
+ failAfterPublish = true;
163
+ await expect(updatePermissions(nextPermissions)).rejects.toThrow(
164
+ "forced transaction failure",
165
+ );
166
+ expect((await repositories.roles.get("test-role"))?.permissionKeys).toEqual(
167
+ originalPermissions,
168
+ );
169
+ expect(await drizzle(pool).select().from(agentSourceInvalidations)).toEqual(
170
+ [],
171
+ );
172
+ expect(
173
+ (
174
+ await pool.query(
175
+ "select count(*)::int as count from agent_source_invalidation_deliveries",
176
+ )
177
+ ).rows[0].count,
178
+ ).toBe(0);
179
+ });
180
+ });
181
+ // @windy-module system.agent end
@@ -1,8 +1,11 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM node:24.7.0-alpine AS node
3
+ ARG WINDY_NODE_IMAGE=node:24.7.0-alpine
4
+ ARG WINDY_WEB_BUN_IMAGE=oven/bun:1.3.14-alpine
4
5
 
5
- FROM oven/bun:1.3.14-alpine AS deps
6
+ FROM ${WINDY_NODE_IMAGE} AS node
7
+
8
+ FROM ${WINDY_WEB_BUN_IMAGE} AS deps
6
9
  COPY --from=node /usr/local/bin/node /usr/local/bin/node
7
10
  WORKDIR /app
8
11
 
@@ -30,7 +33,8 @@ COPY packages/server-sdk/package.json packages/server-sdk/package.json
30
33
  COPY packages/shared/package.json packages/shared/package.json
31
34
  COPY packages/storage/package.json packages/storage/package.json
32
35
  # windy-copy:deps end
33
- RUN --mount=type=cache,id=windy-bun-1.3.14,target=/root/.bun/install/cache,sharing=shared bun -e 'const child=Bun.spawn(["bun","install","--frozen-lockfile","--no-progress","--backend=copyfile","--linker=hoisted","--ignore-scripts"],{stdin:"inherit",stdout:"inherit",stderr:"inherit"});const heartbeat=setInterval(()=>console.log("[windy] Bun 依赖安装仍在进行"),15000);const exitCode=await child.exited;clearInterval(heartbeat);process.exit(exitCode)' && bun --cwd apps/server -e 'await import("argon2")'
36
+ ARG WINDY_NPM_REGISTRY=https://registry.npmjs.org
37
+ RUN --mount=type=cache,id=windy-bun-1.3.14,target=/root/.bun/install/cache,sharing=shared bun -e 'const child=Bun.spawn(["bun","install","--frozen-lockfile","--no-progress","--filter","*","--filter","!./apps/agent-server","--registry",process.env.WINDY_NPM_REGISTRY||"https://registry.npmjs.org","--backend=copyfile","--linker=hoisted","--ignore-scripts"],{stdin:"inherit",stdout:"inherit",stderr:"inherit"});const started=Date.now();const heartbeat=setInterval(()=>console.log("[windy] Bun 依赖安装尚未完成,已耗时 "+Math.round((Date.now()-started)/1000)+" 秒"),15000);const deadline=setTimeout(()=>{console.error("[windy] Bun 安装超过 300 秒,请检查 npm 下载网络或通过 WINDY_NPM_REGISTRY 指定可用源");child.kill("SIGKILL")},300000);const exitCode=await child.exited;clearInterval(heartbeat);clearTimeout(deadline);process.exit(exitCode)' && bun --cwd apps/server -e 'await import("argon2")'
34
38
 
35
39
  FROM deps AS build
36
40
  # windy-copy:build begin
@@ -3,26 +3,26 @@ import { selectDevPort, type PortState } from "./dev-port";
3
3
  import { windyDevIdentity } from "./dev-identity";
4
4
 
5
5
  describe("Windy 本地开发端口", () => {
6
- test("默认选择 8746", async () => {
6
+ test("默认选择 28746", async () => {
7
7
  const result = await selectDevPort({
8
8
  fingerprint: "project-a",
9
9
  inspect: async () => ({ kind: "available" }),
10
10
  });
11
11
 
12
- expect(result).toEqual({ kind: "selected", port: 8_746 });
12
+ expect(result).toEqual({ kind: "selected", port: 28_746 });
13
13
  });
14
14
 
15
15
  test("外部服务或其他 Windy 项目占用时自动递增", async () => {
16
16
  const states = new Map<number, PortState>([
17
- [8_746, { kind: "occupied" }],
18
- [8_747, { kind: "occupied", identity: windyDevIdentity("project-b") }],
17
+ [28_746, { kind: "occupied" }],
18
+ [28_747, { kind: "occupied", identity: windyDevIdentity("project-b") }],
19
19
  ]);
20
20
  const result = await selectDevPort({
21
21
  fingerprint: "project-a",
22
22
  inspect: async (port) => states.get(port) || { kind: "available" },
23
23
  });
24
24
 
25
- expect(result).toEqual({ kind: "selected", port: 8_748 });
25
+ expect(result).toEqual({ kind: "selected", port: 28_748 });
26
26
  });
27
27
 
28
28
  test("检测到同一项目时返回已有地址而不启动重复服务", async () => {
@@ -34,7 +34,7 @@ describe("Windy 本地开发端口", () => {
34
34
  }),
35
35
  });
36
36
 
37
- expect(result).toEqual({ kind: "existing", port: 8_746 });
37
+ expect(result).toEqual({ kind: "existing", port: 28_746 });
38
38
  });
39
39
 
40
40
  test("最多扫描 100 个端口", async () => {
@@ -1,6 +1,6 @@
1
1
  import { isWindyDevIdentity, type WindyDevIdentity } from "./dev-identity";
2
2
 
3
- export const DEFAULT_DEV_PORT = 8_746;
3
+ export const DEFAULT_DEV_PORT = 28_746;
4
4
  export const DEV_PORT_SCAN_LIMIT = 100;
5
5
 
6
6
  export type PortState =
@@ -4,7 +4,9 @@ import { projectFingerprint } from "./dev-identity";
4
4
  import { selectDevPort } from "./dev-port";
5
5
 
6
6
  const fingerprint = projectFingerprint(process.cwd());
7
- const configuredPort = parseConfiguredPort(process.env.WINDY_DEV_PORT);
7
+ const configuredPort = parseConfiguredPort(
8
+ process.env.WINDY_DEV_PORT ?? process.env.WINDY_WEB_PORT,
9
+ );
8
10
  const selection = configuredPort
9
11
  ? { kind: "selected" as const, port: configuredPort }
10
12
  : await selectDevPort({ fingerprint });
@@ -29,6 +31,9 @@ const child = Bun.spawn(
29
31
  env: {
30
32
  ...process.env,
31
33
  WINDY_DEV_PORT: String(selection.port),
34
+ API_UPSTREAM:
35
+ process.env.API_UPSTREAM ??
36
+ `http://127.0.0.1:${process.env.WINDY_SERVER_PORT ?? "19747"}`,
32
37
  WINDY_DEV_FINGERPRINT: fingerprint,
33
38
  },
34
39
  stdin: "inherit",
@@ -3,6 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" href="/src/assets/windy.svg" type="image/svg+xml" />
6
7
  <title>Windy Platform Template</title>
7
8
  <!-- Tailwind CSS -->
8
9
  <link rel="stylesheet" href="/src/style.css" />
@@ -13,38 +13,38 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@antv/g2": "^5.4.8",
16
- "@lucide/vue": "^1.23.0",
16
+ "@lucide/vue": "^1.70.0",
17
17
  "@southwind-ai/example-work-order": "workspace:*",
18
18
  "@southwind-ai/license": "workspace:*",
19
19
  "@southwind-ai/shared": "workspace:*",
20
- "@tailwindcss/vite": "^4.3.2",
21
- "@tanstack/vue-form": "^1.33.0",
20
+ "@tailwindcss/vite": "^4.3.4",
21
+ "@tanstack/vue-form": "^1.33.5",
22
22
  "@tanstack/vue-table": "^8.21.3",
23
23
  "@vue-flow/core": "^1.48.2",
24
- "@vueuse/core": "^14.3.0",
24
+ "@vueuse/core": "^14.4.0",
25
25
  "class-variance-authority": "^0.7.1",
26
26
  "clsx": "^2.1.1",
27
27
  "pinia": "^3.0.4",
28
28
  "qrcode": "^1.5.4",
29
- "reka-ui": "^2.10.1",
30
- "tailwind-merge": "^3.6.0",
31
- "tailwindcss": "^4.3.2",
29
+ "reka-ui": "^2.11.0",
30
+ "tailwind-merge": "^3.7.0",
31
+ "tailwindcss": "^4.3.4",
32
32
  "vee-validate": "^4.15.1",
33
- "vue": "^3.5.39",
34
- "vue-router": "^5.1.0",
33
+ "vue": "^3.5.45",
34
+ "vue-router": "^5.4.0",
35
35
  "vue-sonner": "^2.0.9",
36
- "ws": "^8.22.0"
36
+ "ws": "^8.22.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@southwind-ai/modules": "workspace:*",
40
40
  "@types/qrcode": "^1.5.6",
41
41
  "@types/ws": "^8.18.2",
42
- "@vitejs/plugin-vue": "^6.0.7",
43
- "@vue/test-utils": "^2.4.11",
44
- "happy-dom": "^20.10.6",
42
+ "@vitejs/plugin-vue": "^6.0.10",
43
+ "@vue/test-utils": "^2.5.1",
44
+ "happy-dom": "^20.14.6",
45
45
  "tw-animate-css": "^1.4.0",
46
- "vite": "^8.1.4",
47
- "vitest": "^4.1.10",
48
- "vue-tsc": "^3.3.7"
46
+ "vite": "^8.3.6",
47
+ "vitest": "^4.1.12",
48
+ "vue-tsc": "^3.3.14"
49
49
  }
50
50
  }
@@ -1,5 +1,7 @@
1
1
  import { describe, expect, mock, test } from "bun:test";
2
- import { mkdir, rm } from "node:fs/promises";
2
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
3
5
  import { createWebFetchHandler } from "./runtime-server";
4
6
 
5
7
  describe("Web 生产同源 API 代理", () => {
@@ -84,3 +86,22 @@ describe("Web 生产同源 API 代理", () => {
84
86
  }
85
87
  });
86
88
  });
89
+
90
+ test("缺失的构建资源返回 404,业务深链接仍返回 SPA 入口", async () => {
91
+ const distDir = await mkdtemp(join(tmpdir(), "windy-web-assets-"));
92
+ try {
93
+ await Bun.write(join(distDir, "index.html"), "<html>Windy</html>");
94
+ const handler = createWebFetchHandler({ distDir });
95
+ const missing = await handler(new Request("http://web/assets/stale.js"));
96
+ expect(missing.status).toBe(404);
97
+ expect(missing.headers.get("content-type")).toBe(
98
+ "text/plain; charset=utf-8",
99
+ );
100
+
101
+ const route = await handler(new Request("http://web/admin/system/users"));
102
+ expect(route.status).toBe(200);
103
+ expect(await route.text()).toBe("<html>Windy</html>");
104
+ } finally {
105
+ await rm(distDir, { recursive: true, force: true });
106
+ }
107
+ });
@@ -53,6 +53,12 @@ async function serveStatic(
53
53
  },
54
54
  });
55
55
  }
56
+ if (pathname.startsWith("/assets/")) {
57
+ return new Response("Not Found", {
58
+ status: 404,
59
+ headers: { "content-type": "text/plain; charset=utf-8" },
60
+ });
61
+ }
56
62
  return new Response(Bun.file(join(distDir, "index.html")), {
57
63
  headers: { "content-type": contentTypes[".html"] },
58
64
  });
@@ -1,10 +1,13 @@
1
1
  <script setup lang="ts">
2
- import AppShell from "@/layout/AppShell.vue";
3
- import AppLayout from "@/app/layouts/AppLayout.vue";
4
- import { computed } from "vue";
2
+ import { computed, defineAsyncComponent } from "vue";
5
3
  import { useRoute } from "vue-router";
6
4
  import { usePlatformSettings } from "@/composables/usePlatformSettings";
7
5
 
6
+ const AppShell = defineAsyncComponent(() => import("@/layout/AppShell.vue"));
7
+ const AppLayout = defineAsyncComponent(
8
+ () => import("@/app/layouts/AppLayout.vue"),
9
+ );
10
+
8
11
  const route = useRoute();
9
12
  const platformSettings = usePlatformSettings();
10
13
  const layout = computed(() => route.meta.layout);
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <rect width="64" height="64" rx="14" fill="#164b35"/>
3
+ <path d="M15 24h20a5 5 0 1 0-5-5M11 33h37a5 5 0 1 0-5-5M17 42h20a5 5 0 1 1-5 5" fill="none" stroke="#fff" stroke-width="4.5" stroke-linecap="round"/>
4
+ </svg>
@@ -34,7 +34,7 @@ function submit() {
34
34
 
35
35
  <template>
36
36
  <form class="space-y-4" data-testid="login-form" @submit.prevent="submit">
37
- <div class="space-y-2">
37
+ <div class="grid gap-2">
38
38
  <label for="username" class="text-sm font-medium">用户名</label>
39
39
  <Input
40
40
  id="username"
@@ -46,7 +46,7 @@ function submit() {
46
46
  required
47
47
  />
48
48
  </div>
49
- <div class="space-y-2">
49
+ <div class="grid gap-2">
50
50
  <label for="password" class="text-sm font-medium">密码</label>
51
51
  <Input
52
52
  id="password"
@@ -48,13 +48,24 @@ const canSubmit = computed(
48
48
  newPassword.value === confirmation.value,
49
49
  );
50
50
 
51
- function submit() {
51
+ function getInput(): PasswordChangeInput | undefined {
52
52
  if (!canSubmit.value) return;
53
- emit("submit", {
53
+ return {
54
54
  currentPassword: currentPassword.value,
55
55
  newPassword: newPassword.value,
56
- });
56
+ };
57
+ }
58
+
59
+ function submit() {
60
+ const input = getInput();
61
+ if (input) emit("submit", input);
57
62
  }
63
+
64
+ defineExpose({
65
+ getInput,
66
+ isDirty: () =>
67
+ Boolean(currentPassword.value || newPassword.value || confirmation.value),
68
+ });
58
69
  </script>
59
70
 
60
71
  <template>
@@ -84,7 +84,7 @@ async function canClose() {
84
84
  </DialogHeader>
85
85
 
86
86
  <form class="contents" @submit.prevent="emit('submit')">
87
- <ModalBody class="space-y-4">
87
+ <ModalBody class="-m-1 space-y-4 p-1">
88
88
  <slot />
89
89
  </ModalBody>
90
90
 
@@ -25,6 +25,9 @@ describe("License Web 流程", () => {
25
25
  "联系系统管理员",
26
26
  );
27
27
  expect(wrapper.find('[data-testid="machine-code"]').exists()).toBe(false);
28
+ expect(
29
+ wrapper.find('[data-testid="machine-code-qr-toggle"]').exists(),
30
+ ).toBe(false);
28
31
  expect(wrapper.text()).not.toContain("客户一");
29
32
  expect(wrapper.text()).not.toContain("capability.audit");
30
33
  });