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.
Files changed (127) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +392 -160
  3. package/package.json +1 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/README.md +1 -0
  6. package/template/apps/agent-server/Dockerfile +20 -0
  7. package/template/apps/agent-server/README.md +46 -0
  8. package/template/apps/agent-server/pyproject.toml +44 -0
  9. package/template/apps/agent-server/src/southwind_agent_server/__init__.py +5 -0
  10. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +39 -0
  11. package/template/apps/agent-server/src/southwind_agent_server/app.py +263 -0
  12. package/template/apps/agent-server/src/southwind_agent_server/config.py +141 -0
  13. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +99 -0
  14. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +52 -0
  15. package/template/apps/agent-server/src/southwind_agent_server/engine.py +85 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +197 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +246 -0
  18. package/template/apps/agent-server/src/southwind_agent_server/service.py +180 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +317 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/sse.py +41 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/store.py +144 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +80 -0
  23. package/template/apps/agent-server/tests/test_api.py +207 -0
  24. package/template/apps/agent-server/tests/test_config.py +56 -0
  25. package/template/apps/agent-server/tests/test_openai_provider.py +224 -0
  26. package/template/apps/agent-server/tests/test_sqlite_store.py +93 -0
  27. package/template/apps/agent-server/uv.lock +925 -0
  28. package/template/apps/server/src/agent/execution-grants.ts +89 -0
  29. package/template/apps/server/src/agent/remote-runtime.ts +128 -0
  30. package/template/apps/server/src/agent/run-contracts.ts +43 -0
  31. package/template/apps/server/src/agent/run-facade.test.ts +257 -0
  32. package/template/apps/server/src/agent/run-facade.ts +190 -0
  33. package/template/apps/server/src/agent/run-routes.ts +222 -0
  34. package/template/apps/server/src/agent/runtime-bootstrap.ts +53 -0
  35. package/template/apps/server/src/agent/tool-host.test.ts +242 -0
  36. package/template/apps/server/src/agent/tool-host.ts +153 -0
  37. package/template/apps/server/src/application-services.ts +2 -2
  38. package/template/apps/server/src/audit/search-summary.ts +15 -8
  39. package/template/apps/server/src/example-modules.ts +58 -70
  40. package/template/apps/server/src/foundation.ts +8 -4
  41. package/template/apps/server/src/guards.test.ts +13 -0
  42. package/template/apps/server/src/guards.ts +12 -2
  43. package/template/apps/server/src/index.ts +48 -95
  44. package/template/apps/server/src/installed-business-modules.ts +23 -4
  45. package/template/apps/server/src/module-bootstrap.ts +83 -0
  46. package/template/apps/server/src/module-composition.test.ts +54 -1
  47. package/template/apps/server/src/module-composition.ts +58 -0
  48. package/template/apps/server/src/module-host/initialize-contracts.ts +67 -0
  49. package/template/apps/server/src/module-host/initialize.test.ts +192 -0
  50. package/template/apps/server/src/module-host/initialize.ts +328 -0
  51. package/template/apps/server/src/module-host/request-context.test.ts +66 -0
  52. package/template/apps/server/src/module-host/request-context.ts +156 -0
  53. package/template/apps/server/src/module-host/role-presets.test.ts +94 -0
  54. package/template/apps/server/src/module-host/role-presets.ts +76 -0
  55. package/template/apps/server/src/module-host/route-installer.test.ts +317 -0
  56. package/template/apps/server/src/module-host/route-installer.ts +97 -0
  57. package/template/apps/server/src/module-host/route-registration.test.ts +160 -0
  58. package/template/apps/server/src/module-host/search-providers.ts +31 -0
  59. package/template/apps/server/src/module-host/storage-port.test.ts +165 -0
  60. package/template/apps/server/src/module-host/storage-port.ts +59 -0
  61. package/template/apps/server/src/module-host/task-definitions.ts +41 -0
  62. package/template/apps/server/src/module-host/test-fixtures.ts +26 -0
  63. package/template/apps/server/src/module-host/tool-handlers.ts +25 -0
  64. package/template/apps/server/src/module-host.test.ts +202 -58
  65. package/template/apps/server/src/module-host.ts +35 -113
  66. package/template/apps/server/src/module-runtime-validation.ts +40 -0
  67. package/template/apps/server/src/route-guards.test.ts +112 -1
  68. package/template/apps/server/src/runtime-feature.ts +66 -31
  69. package/template/apps/server/src/runtime.test.ts +1 -0
  70. package/template/apps/server/src/runtime.ts +3 -1
  71. package/template/apps/server/src/system/built-in-roles.ts +1 -1
  72. package/template/apps/server/src/work-order/foundation-modules.test.ts +30 -10
  73. package/template/apps/server/src/work-order/routes.test.ts +30 -18
  74. package/template/apps/server/src/work-order/routes.ts +104 -135
  75. package/template/apps/server/src/work-order/search-api.test.ts +14 -3
  76. package/template/apps/server/src/work-order/search-provider.test.ts +23 -15
  77. package/template/apps/server/src/work-order/search-provider.ts +17 -12
  78. package/template/apps/server/src/work-order/task.test.ts +16 -11
  79. package/template/apps/web/Dockerfile +4 -1
  80. package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +4 -1
  81. package/template/apps/web/src/layout/GlobalWatermark.vue +2 -0
  82. package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +4 -1
  83. package/template/docker-compose.yml +25 -0
  84. package/template/docs/architecture/ai-runtime.md +744 -0
  85. package/template/docs/architecture/object-storage.md +12 -0
  86. package/template/docs/platform/agent-runtime.md +128 -0
  87. package/template/docs/platform/agent-tools.md +8 -1
  88. package/template/package.json +1 -0
  89. package/template/packages/config/index.test.ts +100 -0
  90. package/template/packages/config/index.ts +1 -0
  91. package/template/packages/config/package.json +2 -1
  92. package/template/packages/config/src/ai-openai-compatible.ts +131 -0
  93. package/template/packages/config/src/platform.ts +28 -1
  94. package/template/packages/database/package.json +1 -1
  95. package/template/packages/database/src/runner.ts +7 -9
  96. package/template/packages/example-work-order/index.ts +1 -0
  97. package/template/packages/example-work-order/package.json +2 -0
  98. package/template/packages/example-work-order/src/server-module.ts +61 -0
  99. package/template/packages/jobs/package.json +1 -1
  100. package/template/packages/jobs/src/types.ts +7 -1
  101. package/template/packages/modules/package.json +1 -1
  102. package/template/packages/modules/src/catalog-validation.test.ts +212 -0
  103. package/template/packages/modules/src/catalog-validation.ts +19 -2
  104. package/template/packages/modules/src/migration-bundle-validation.test.ts +191 -0
  105. package/template/packages/server-sdk/index.ts +53 -0
  106. package/template/packages/server-sdk/package.json +18 -3
  107. package/template/packages/server-sdk/src/actor.ts +15 -0
  108. package/template/packages/server-sdk/src/ai-operation-registration.ts +31 -0
  109. package/template/packages/server-sdk/src/audit-port.ts +22 -0
  110. package/template/packages/server-sdk/src/jobs-port.ts +24 -0
  111. package/template/packages/server-sdk/src/module-host.ts +41 -1
  112. package/template/packages/server-sdk/src/namespace.test.ts +26 -0
  113. package/template/packages/server-sdk/src/namespace.ts +13 -0
  114. package/template/packages/server-sdk/src/request-context.ts +21 -0
  115. package/template/packages/server-sdk/src/role-preset.ts +18 -0
  116. package/template/packages/server-sdk/src/route-definition.ts +26 -0
  117. package/template/packages/server-sdk/src/search-provider-port.ts +48 -0
  118. package/template/packages/server-sdk/src/storage-port.ts +25 -0
  119. package/template/packages/server-sdk/src/task-registration.ts +18 -0
  120. package/template/packages/server-sdk/src/tool-host-port.ts +22 -0
  121. package/template/packages/server-sdk/src/tool-registration.ts +29 -0
  122. package/template/packages/shared/index.ts +1 -0
  123. package/template/packages/shared/package.json +1 -1
  124. package/template/packages/shared/src/license-catalog.test.ts +73 -1
  125. package/template/packages/shared/src/license-catalog.ts +95 -0
  126. package/template/packages/{database/src/bundle-validation.ts → shared/src/migration-bundle-validation.ts} +2 -1
  127. package/template/apps/server/src/work-order/task.ts +0 -30
package/dist/cli.js CHANGED
@@ -228,10 +228,15 @@ var optionalModuleManifests = [
228
228
  "system.audit"
229
229
  ]),
230
230
  fileRoots: [
231
+ "apps/agent-server",
231
232
  "apps/server/src/agent",
232
233
  "packages/modules/src/system-agent-tools.ts"
233
234
  ],
234
- documentation: ["docs/platform/agent-tools.md"]
235
+ documentation: [
236
+ "docs/architecture/ai-runtime.md",
237
+ "docs/platform/agent-runtime.md",
238
+ "docs/platform/agent-tools.md"
239
+ ]
235
240
  },
236
241
  {
237
242
  ...module("system.scheduler", "任务调度", "任务、重试、历史和恢复。", [
@@ -727,7 +732,7 @@ var helpText = `create-windy <项目名> [选项]
727
732
  `;
728
733
 
729
734
  // src/create-project.ts
730
- import { mkdir as mkdir4, readdir as readdir5, rm as rm4 } from "node:fs/promises";
735
+ import { mkdir as mkdir4, readdir as readdir6, rm as rm4 } from "node:fs/promises";
731
736
 
732
737
  // src/commands.ts
733
738
  import { spawn } from "node:child_process";
@@ -881,7 +886,10 @@ async function initializeLocalEnvironment(projectRoot, generateSecret = defaultS
881
886
  if (!gitignore.split(/\r?\n/).some((line) => line.trim() === ".env")) {
882
887
  throw new Error("生成项目的 .gitignore 未排除 .env,拒绝写入本地密码");
883
888
  }
884
- const content = setEmptyVariable(setEmptyVariable(example, "POSTGRES_PASSWORD", generateSecret()), "WINDY_BOOTSTRAP_ADMIN_PASSWORD", generateSecret());
889
+ let content = setEmptyVariable(setEmptyVariable(example, "POSTGRES_PASSWORD", generateSecret()), "WINDY_BOOTSTRAP_ADMIN_PASSWORD", generateSecret());
890
+ if (/^AGENT_SERVER_INTERNAL_TOKEN=[ \t\r]*$/m.test(content)) {
891
+ content = setEmptyVariable(content, "AGENT_SERVER_INTERNAL_TOKEN", generateSecret());
892
+ }
885
893
  let handle;
886
894
  try {
887
895
  handle = await open(target, "wx", 384);
@@ -920,16 +928,147 @@ import {
920
928
  copyFile,
921
929
  lstat as lstat4,
922
930
  mkdir as mkdir3,
923
- readFile as readFile9,
924
- readdir as readdir4,
931
+ readFile as readFile10,
932
+ readdir as readdir5,
925
933
  rm as rm3,
926
- writeFile as writeFile8
934
+ writeFile as writeFile9
927
935
  } from "node:fs/promises";
928
- import { basename as basename3, extname as extname3, join as join9 } from "node:path";
936
+ import { basename as basename3, extname as extname3, join as join10 } from "node:path";
929
937
 
930
938
  // src/customer-docker.ts
931
- import { readFile as readFile2, writeFile } from "node:fs/promises";
939
+ import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
940
+ import { join as join3 } from "node:path";
941
+
942
+ // src/docker-copy-sync.ts
943
+ import { readdir, readFile as readFile2, writeFile } from "node:fs/promises";
932
944
  import { join as join2 } from "node:path";
945
+ var webDockerfilePath = "apps/web/Dockerfile";
946
+ var webEntryDirectory = "apps/web";
947
+ var depsCopyAnchor = "windy-copy:deps";
948
+ var buildCopyAnchor = "windy-copy:build";
949
+ async function listWorkspacePackages(root) {
950
+ const manifest = await readManifest(join2(root, "package.json"));
951
+ const packages = [];
952
+ for (const pattern of manifest.workspaces ?? []) {
953
+ if (!pattern.endsWith("/*")) {
954
+ throw new Error(`暂不支持的 workspace pattern: ${pattern}`);
955
+ }
956
+ const base = pattern.slice(0, -2);
957
+ const entries = await readdir(join2(root, base), {
958
+ withFileTypes: true
959
+ }).catch((error) => {
960
+ if (isMissing(error))
961
+ return [];
962
+ throw error;
963
+ });
964
+ for (const entry of entries) {
965
+ if (!entry.isDirectory())
966
+ continue;
967
+ const directory = `${base}/${entry.name}`;
968
+ const manifestPath = join2(root, directory, "package.json");
969
+ if (!await Bun.file(manifestPath).exists())
970
+ continue;
971
+ const value = await readManifest(manifestPath);
972
+ packages.push({
973
+ name: value.name,
974
+ directory,
975
+ dependencies: [
976
+ ...Object.keys(value.dependencies ?? {}),
977
+ ...Object.keys(value.devDependencies ?? {})
978
+ ]
979
+ });
980
+ }
981
+ }
982
+ return packages.sort((a, b) => a.directory.localeCompare(b.directory));
983
+ }
984
+ function workspaceDependencyClosure(packages, entryDirectory) {
985
+ const directoryByName = new Map;
986
+ const packageByDirectory = new Map;
987
+ for (const pkg of packages) {
988
+ if (pkg.name)
989
+ directoryByName.set(pkg.name, pkg.directory);
990
+ packageByDirectory.set(pkg.directory, pkg);
991
+ }
992
+ const visited = new Set([entryDirectory]);
993
+ const queue = [entryDirectory];
994
+ while (queue.length) {
995
+ const current = packageByDirectory.get(queue.shift());
996
+ if (!current)
997
+ continue;
998
+ for (const dependency of current.dependencies) {
999
+ const directory = directoryByName.get(dependency);
1000
+ if (directory && !visited.has(directory)) {
1001
+ visited.add(directory);
1002
+ queue.push(directory);
1003
+ }
1004
+ }
1005
+ }
1006
+ return [...visited].sort((a, b) => a.localeCompare(b));
1007
+ }
1008
+ function dockerCopyLines(directories) {
1009
+ return directories.map((directory) => `COPY ${directory} ${directory}`);
1010
+ }
1011
+ function dockerManifestCopyLines(directories) {
1012
+ return directories.map((directory) => `COPY ${directory}/package.json ${directory}/package.json`);
1013
+ }
1014
+ function rewriteAnchoredSection(source, anchor, lines) {
1015
+ const { begin, end } = locateAnchoredSection(source, anchor);
1016
+ const all = source.split(`
1017
+ `);
1018
+ return [...all.slice(0, begin + 1), ...lines, ...all.slice(end)].join(`
1019
+ `);
1020
+ }
1021
+ function hasCopyAnchors(source) {
1022
+ return source.includes(`# ${depsCopyAnchor} begin`) && source.includes(`# ${buildCopyAnchor} begin`);
1023
+ }
1024
+ function syncWebDockerfileSource(source, workspaceDirectories, buildDirectories) {
1025
+ const withDeps = rewriteAnchoredSection(source, depsCopyAnchor, dockerManifestCopyLines(workspaceDirectories));
1026
+ return rewriteAnchoredSection(withDeps, buildCopyAnchor, dockerCopyLines(buildDirectories));
1027
+ }
1028
+ async function syncWebDockerfileInProject(root, options = {}) {
1029
+ const path = join2(root, webDockerfilePath);
1030
+ let source;
1031
+ try {
1032
+ source = await readFile2(path, "utf8");
1033
+ } catch (error) {
1034
+ if (isMissing(error))
1035
+ return { synced: false, changed: false };
1036
+ throw error;
1037
+ }
1038
+ if (!hasCopyAnchors(source)) {
1039
+ if (!options.requireAnchors)
1040
+ return { synced: false, changed: false };
1041
+ throw new Error(`${webDockerfilePath} 缺少 # ${depsCopyAnchor} / # ${buildCopyAnchor} 锚点段,` + `请先按最新模板为 deps 与 build 阶段的 COPY 段添加锚点后再同步`);
1042
+ }
1043
+ const packages = await listWorkspacePackages(root);
1044
+ const next = syncWebDockerfileSource(source, packages.map(({ directory }) => directory), workspaceDependencyClosure(packages, webEntryDirectory));
1045
+ if (next !== source)
1046
+ await writeFile(path, next);
1047
+ return { synced: true, changed: next !== source };
1048
+ }
1049
+ function locateAnchoredSection(source, anchor) {
1050
+ const beginMarker = `# ${anchor} begin`;
1051
+ const endMarker = `# ${anchor} end`;
1052
+ const lines = source.split(`
1053
+ `);
1054
+ const begin = lines.indexOf(beginMarker);
1055
+ const end = lines.indexOf(endMarker);
1056
+ if (begin === -1 || end === -1 || end <= begin) {
1057
+ throw new Error(`Dockerfile 缺少锚点段 ${beginMarker} ... ${endMarker},请先迁移到锚点格式`);
1058
+ }
1059
+ if (lines.indexOf(beginMarker, begin + 1) !== -1 || lines.indexOf(endMarker, end + 1) !== -1) {
1060
+ throw new Error(`Dockerfile 锚点段 ${anchor} 重复,请保留唯一一段`);
1061
+ }
1062
+ return { begin, end };
1063
+ }
1064
+ async function readManifest(path) {
1065
+ return JSON.parse(await readFile2(path, "utf8"));
1066
+ }
1067
+ function isMissing(error) {
1068
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
1069
+ }
1070
+
1071
+ // src/customer-docker.ts
933
1072
  var supplierWorkspaceCopies = [
934
1073
  "COPY apps/license/package.json apps/license/package.json",
935
1074
  "COPY apps/signing-service/package.json apps/signing-service/package.json",
@@ -937,11 +1076,12 @@ var supplierWorkspaceCopies = [
937
1076
  ];
938
1077
  var supplierWorkspacePaths = ["packages/license-sdk"];
939
1078
  async function prepareCustomerDockerfiles(root) {
940
- await Promise.all(["apps/server/Dockerfile", "apps/web/Dockerfile"].map(async (path) => {
941
- const absolutePath = join2(root, path);
942
- const source = await readFile2(absolutePath, "utf8");
943
- await writeFile(absolutePath, rewriteCustomerDockerfile(source));
1079
+ await Promise.all(["apps/server/Dockerfile", webDockerfilePath].map(async (path) => {
1080
+ const absolutePath = join3(root, path);
1081
+ const source = await readFile3(absolutePath, "utf8");
1082
+ await writeFile2(absolutePath, rewriteCustomerDockerfile(source));
944
1083
  }));
1084
+ await syncWebDockerfileInProject(root, { requireAnchors: true });
945
1085
  }
946
1086
  function rewriteCustomerDockerfile(source) {
947
1087
  const lines = source.split(`
@@ -953,8 +1093,8 @@ function rewriteCustomerDockerfile(source) {
953
1093
  }
954
1094
 
955
1095
  // src/code-quality.ts
956
- import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
957
- import { join as join3 } from "node:path";
1096
+ import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
1097
+ import { join as join4 } from "node:path";
958
1098
  var oxcStarterPolicy = [
959
1099
  "- 生成时默认预装 Oxlint、Oxfmt、lint-staged 与 Husky。",
960
1100
  "- 提交前自动格式化暂存文件;lint、typecheck 和 test 保留为显式命令。",
@@ -963,30 +1103,30 @@ var oxcStarterPolicy = [
963
1103
  `);
964
1104
  var noCodeQualityPolicy = "- 本项目生成时未引入 lint 或 format 工具;项目所有者可按团队需要自行选择。";
965
1105
  async function reflectCodeQualityChoice(targetDirectory, includeOxc) {
966
- const path = join3(targetDirectory, "AGENTS.md");
967
- const source = await readFile3(path, "utf8");
1106
+ const path = join4(targetDirectory, "AGENTS.md");
1107
+ const source = await readFile4(path, "utf8");
968
1108
  if (includeOxc)
969
1109
  return;
970
1110
  if (!source.includes(oxcStarterPolicy)) {
971
1111
  throw new Error("无法同步 --no-oxc 的项目开发约束");
972
1112
  }
973
- await writeFile2(path, source.replace(oxcStarterPolicy, noCodeQualityPolicy));
1113
+ await writeFile3(path, source.replace(oxcStarterPolicy, noCodeQualityPolicy));
974
1114
  }
975
1115
 
976
1116
  // src/license-files.ts
977
- import { mkdir, readFile as readFile4, rm, writeFile as writeFile3 } from "node:fs/promises";
978
- import { dirname, join as join4, resolve as resolve2 } from "node:path";
1117
+ import { mkdir, readFile as readFile5, rm, writeFile as writeFile4 } from "node:fs/promises";
1118
+ import { dirname, join as join5, resolve as resolve2 } from "node:path";
979
1119
  import { fileURLToPath } from "node:url";
980
1120
  var packageRoot = resolve2(dirname(fileURLToPath(import.meta.url)), "..");
981
1121
  var apacheLicensePath = resolve2(packageRoot, "LICENSE");
982
1122
  var windyLicensePath = ".windy/licenses/WINDY-APACHE-2.0.txt";
983
1123
  async function applyWindyBaseLicense(targetDirectory) {
984
- const target = join4(targetDirectory, windyLicensePath);
1124
+ const target = join5(targetDirectory, windyLicensePath);
985
1125
  await mkdir(dirname(target), { recursive: true });
986
- await writeFile3(target, await readFile4(apacheLicensePath));
1126
+ await writeFile4(target, await readFile5(apacheLicensePath));
987
1127
  }
988
1128
  async function applyProjectLicense(targetDirectory, license, holder, year = new Date().getFullYear()) {
989
- const target = join4(targetDirectory, "LICENSE");
1129
+ const target = join5(targetDirectory, "LICENSE");
990
1130
  if (license === "UNLICENSED") {
991
1131
  await rm(target, { force: true });
992
1132
  return;
@@ -996,11 +1136,11 @@ async function applyProjectLicense(targetDirectory, license, holder, year = new
996
1136
  if (!owner)
997
1137
  throw new Error("选择 MIT 时必须填写许可证权利人名称");
998
1138
  await mkdir(targetDirectory, { recursive: true });
999
- await writeFile3(target, mitLicense(year, owner));
1139
+ await writeFile4(target, mitLicense(year, owner));
1000
1140
  return;
1001
1141
  }
1002
1142
  await mkdir(targetDirectory, { recursive: true });
1003
- await writeFile3(target, await readFile4(apacheLicensePath));
1143
+ await writeFile4(target, await readFile5(apacheLicensePath));
1004
1144
  }
1005
1145
  function mitLicense(year, holder) {
1006
1146
  return `MIT License
@@ -1029,8 +1169,8 @@ SOFTWARE.
1029
1169
 
1030
1170
  // src/managed-files.ts
1031
1171
  import { createHash } from "node:crypto";
1032
- import { lstat, readdir, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
1033
- import { join as join5, relative, sep } from "node:path";
1172
+ import { lstat, readdir as readdir2, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
1173
+ import { join as join6, relative, sep } from "node:path";
1034
1174
  var managedFilesManifestPath = ".windy/files.json";
1035
1175
  var ignoredDirectoryNames = new Set([".git", "node_modules", "dist"]);
1036
1176
  var ignoredFiles = new Set([
@@ -1060,7 +1200,7 @@ var sharedScaffoldRoots = [
1060
1200
  async function writeManagedFilesManifest(targetDirectory, generatorVersion) {
1061
1201
  const paths = await collectFiles(targetDirectory);
1062
1202
  const files = await Promise.all(paths.map(async (path) => {
1063
- const content = await readFile5(join5(targetDirectory, path));
1203
+ const content = await readFile6(join6(targetDirectory, path));
1064
1204
  return {
1065
1205
  path,
1066
1206
  ownership: classifyOwnership(path),
@@ -1073,11 +1213,11 @@ async function writeManagedFilesManifest(targetDirectory, generatorVersion) {
1073
1213
  algorithm: "sha256",
1074
1214
  files
1075
1215
  };
1076
- await writeFile4(join5(targetDirectory, managedFilesManifestPath), `${JSON.stringify(manifest, null, 2)}
1216
+ await writeFile5(join6(targetDirectory, managedFilesManifestPath), `${JSON.stringify(manifest, null, 2)}
1077
1217
  `);
1078
1218
  }
1079
1219
  async function readManagedFilesManifest(targetDirectory) {
1080
- const parsed = JSON.parse(await readFile5(join5(targetDirectory, managedFilesManifestPath), "utf8"));
1220
+ const parsed = JSON.parse(await readFile6(join6(targetDirectory, managedFilesManifestPath), "utf8"));
1081
1221
  if (!isManagedFilesManifest(parsed)) {
1082
1222
  throw new Error("受管文件清单无效,无法安全更新");
1083
1223
  }
@@ -1098,10 +1238,10 @@ async function collectFiles(root) {
1098
1238
  await visit(root, files);
1099
1239
  return files.sort();
1100
1240
  async function visit(directory, output) {
1101
- for (const name of (await readdir(directory)).sort()) {
1241
+ for (const name of (await readdir2(directory)).sort()) {
1102
1242
  if (ignoredDirectoryNames.has(name))
1103
1243
  continue;
1104
- const path = join5(directory, name);
1244
+ const path = join6(directory, name);
1105
1245
  const metadata = await lstat(path);
1106
1246
  if (metadata.isSymbolicLink())
1107
1247
  continue;
@@ -1135,8 +1275,8 @@ function isManagedFilesManifest(value) {
1135
1275
  }
1136
1276
 
1137
1277
  // src/module-materializer.ts
1138
- import { lstat as lstat2, readFile as readFile6, readdir as readdir2, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
1139
- import { basename, extname, join as join6, relative as relative2, sep as sep2 } from "node:path";
1278
+ import { lstat as lstat2, readFile as readFile7, readdir as readdir3, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
1279
+ import { basename, extname, join as join7, relative as relative2, sep as sep2 } from "node:path";
1140
1280
  var textExtensions = new Set([
1141
1281
  ".css",
1142
1282
  ".html",
@@ -1169,18 +1309,18 @@ async function materializeSelectedModules(targetDirectory, selectedNames) {
1169
1309
  ...manifest.documentation,
1170
1310
  ...manifest.schemaFiles
1171
1311
  ]);
1172
- await Promise.all([...new Set(removedRoots)].map((path) => rm2(join6(targetDirectory, path), { recursive: true, force: true })));
1312
+ await Promise.all([...new Set(removedRoots)].map((path) => rm2(join7(targetDirectory, path), { recursive: true, force: true })));
1173
1313
  await pruneWorkspaceReferences(targetDirectory, removedPackageNames, removedRoots);
1174
1314
  }
1175
1315
  async function stripModuleBlocks(root, selected) {
1176
1316
  for (const path of await collectFiles2(root)) {
1177
1317
  if (!textExtensions.has(extname(path)))
1178
1318
  continue;
1179
- const source = await readFile6(path, "utf8");
1319
+ const source = await readFile7(path, "utf8");
1180
1320
  if (!source.includes("@windy-module"))
1181
1321
  continue;
1182
1322
  const output = stripTaggedSource(source, selected, relativePath(root, path));
1183
- await writeFile5(path, output);
1323
+ await writeFile6(path, output);
1184
1324
  }
1185
1325
  }
1186
1326
  function stripTaggedSource(source, selected, path = "source") {
@@ -1213,11 +1353,11 @@ async function readRemovedPackageNames(root, roots) {
1213
1353
  const names = new Set;
1214
1354
  for (const packageRoot2 of roots) {
1215
1355
  try {
1216
- const value = JSON.parse(await readFile6(join6(root, packageRoot2, "package.json"), "utf8"));
1356
+ const value = JSON.parse(await readFile7(join7(root, packageRoot2, "package.json"), "utf8"));
1217
1357
  if (value.name)
1218
1358
  names.add(value.name);
1219
1359
  } catch (error) {
1220
- if (!isMissing(error))
1360
+ if (!isMissing2(error))
1221
1361
  throw error;
1222
1362
  }
1223
1363
  }
@@ -1226,7 +1366,7 @@ async function readRemovedPackageNames(root, roots) {
1226
1366
  async function pruneWorkspaceReferences(root, removedPackages, removedRoots) {
1227
1367
  for (const path of await collectFiles2(root)) {
1228
1368
  if (basename(path) === "package.json") {
1229
- const value = JSON.parse(await readFile6(path, "utf8"));
1369
+ const value = JSON.parse(await readFile7(path, "utf8"));
1230
1370
  let changed = false;
1231
1371
  for (const section of ["dependencies", "devDependencies"]) {
1232
1372
  const dependencies = value[section];
@@ -1238,45 +1378,45 @@ async function pruneWorkspaceReferences(root, removedPackages, removedRoots) {
1238
1378
  }
1239
1379
  }
1240
1380
  if (changed)
1241
- await writeFile5(path, `${JSON.stringify(value, null, 2)}
1381
+ await writeFile6(path, `${JSON.stringify(value, null, 2)}
1242
1382
  `);
1243
1383
  continue;
1244
1384
  }
1245
1385
  if (basename(path) !== "Dockerfile")
1246
1386
  continue;
1247
- const source = await readFile6(path, "utf8");
1387
+ const source = await readFile7(path, "utf8");
1248
1388
  const output = source.split(`
1249
1389
  `).filter((line) => !removedRoots.some((root2) => line.includes(root2))).join(`
1250
1390
  `);
1251
1391
  if (output !== source)
1252
- await writeFile5(path, output);
1392
+ await writeFile6(path, output);
1253
1393
  }
1254
1394
  }
1255
1395
  async function pruneDatabaseArtifacts(root, tableNames, statementTokens) {
1256
1396
  if (!tableNames.length && !statementTokens.length)
1257
1397
  return;
1258
1398
  const tablePatterns = tableNames.map((name) => new RegExp(`(?:"${escapeRegExp(name)}"|\\b${escapeRegExp(name)}\\b)`));
1259
- const drizzleRoot = join6(root, "packages/database/drizzle");
1399
+ const drizzleRoot = join7(root, "packages/database/drizzle");
1260
1400
  try {
1261
1401
  for (const path of await collectFiles2(drizzleRoot)) {
1262
1402
  if (path.endsWith(".sql")) {
1263
- const source = await readFile6(path, "utf8");
1403
+ const source = await readFile7(path, "utf8");
1264
1404
  const statements = source.split("--> statement-breakpoint").map((statement) => statement.trim()).filter(Boolean).filter((statement) => !tablePatterns.some((pattern) => pattern.test(statement)) && !statementTokens.some((token) => statement.includes(token)));
1265
- await writeFile5(path, statements.length ? `${statements.join(`
1405
+ await writeFile6(path, statements.length ? `${statements.join(`
1266
1406
  --> statement-breakpoint
1267
1407
  `)}
1268
1408
  ` : `-- 此 migration 的模块未被当前项目选择。
1269
1409
  `);
1270
1410
  } else if (path.endsWith("_snapshot.json")) {
1271
- const snapshot = JSON.parse(await readFile6(path, "utf8"));
1411
+ const snapshot = JSON.parse(await readFile7(path, "utf8"));
1272
1412
  for (const name of tableNames)
1273
1413
  delete snapshot.tables?.[`public.${name}`];
1274
- await writeFile5(path, `${JSON.stringify(snapshot, null, 2)}
1414
+ await writeFile6(path, `${JSON.stringify(snapshot, null, 2)}
1275
1415
  `);
1276
1416
  }
1277
1417
  }
1278
1418
  } catch (error) {
1279
- if (!isMissing(error))
1419
+ if (!isMissing2(error))
1280
1420
  throw error;
1281
1421
  }
1282
1422
  }
@@ -1285,10 +1425,10 @@ async function collectFiles2(root) {
1285
1425
  await visit(root);
1286
1426
  return files.sort();
1287
1427
  async function visit(directory) {
1288
- for (const name of (await readdir2(directory)).sort()) {
1428
+ for (const name of (await readdir3(directory)).sort()) {
1289
1429
  if ([".git", "node_modules", "dist"].includes(name))
1290
1430
  continue;
1291
- const path = join6(directory, name);
1431
+ const path = join7(directory, name);
1292
1432
  const metadata = await lstat2(path);
1293
1433
  if (metadata.isSymbolicLink())
1294
1434
  continue;
@@ -1305,12 +1445,12 @@ function relativePath(root, path) {
1305
1445
  function escapeRegExp(value) {
1306
1446
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1307
1447
  }
1308
- function isMissing(error) {
1448
+ function isMissing2(error) {
1309
1449
  return error instanceof Error && "code" in error && error.code === "ENOENT";
1310
1450
  }
1311
1451
 
1312
- // src/project-files.ts
1313
- var starterCompose = `services:
1452
+ // src/runtime-profile-files.ts
1453
+ var starterComposeBase = `services:
1314
1454
  postgres:
1315
1455
  image: postgres:17-alpine
1316
1456
  environment:
@@ -1354,6 +1494,10 @@ var starterCompose = `services:
1354
1494
  WINDY_CONTAINERIZED: "1"
1355
1495
  BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD: "true"
1356
1496
  WINDY_SYSTEM_USAGE_PATHS: /app/data/audit
1497
+ PLATFORM_ENABLE_AI: \${PLATFORM_ENABLE_AI:-false}
1498
+ AGENT_SERVER_URL: http://agent-server:8080
1499
+ AGENT_TOOL_HOST_URL: http://server:9747
1500
+ AGENT_SERVER_INTERNAL_TOKEN: \${AGENT_SERVER_INTERNAL_TOKEN:-}
1357
1501
  ports:
1358
1502
  - "9747:9747"
1359
1503
  volumes:
@@ -1397,11 +1541,50 @@ var starterCompose = `services:
1397
1541
  volumes:
1398
1542
  postgres-data:
1399
1543
  server-audit-data:
1544
+ __AGENT_VOLUME__
1545
+ `;
1546
+ var agentComposeService = ` agent-server:
1547
+ profiles: ["agent"]
1548
+ build:
1549
+ context: .
1550
+ dockerfile: apps/agent-server/Dockerfile
1551
+ environment:
1552
+ AGENT_SERVER_INTERNAL_TOKEN: \${AGENT_SERVER_INTERNAL_TOKEN:-}
1553
+ AGENT_RUN_STORE_PATH: /app/data/agent/runs.sqlite3
1554
+ QWEN_API_URL: \${QWEN_API_URL:-}
1555
+ QWEN_API_KEY: \${QWEN_API_KEY:-}
1556
+ QWEN_MODEL: \${QWEN_MODEL:-}
1557
+ AI_PROVIDER_CONNECT_TIMEOUT_MS: \${AI_PROVIDER_CONNECT_TIMEOUT_MS:-5000}
1558
+ AI_PROVIDER_REQUEST_TIMEOUT_MS: \${AI_PROVIDER_REQUEST_TIMEOUT_MS:-60000}
1559
+ AI_PROVIDER_MAX_RESPONSE_BYTES: \${AI_PROVIDER_MAX_RESPONSE_BYTES:-2097152}
1560
+ volumes:
1561
+ - agent-run-data:/app/data/agent
1562
+ depends_on:
1563
+ server:
1564
+ condition: service_started
1565
+
1400
1566
  `;
1401
- function environmentExample(includeExample) {
1567
+ function starterComposeFor(includeAgent) {
1568
+ return starterComposeBase.replace(` web:
1569
+ `, `${includeAgent ? agentComposeService : ""} web:
1570
+ `).replace("__AGENT_VOLUME__", includeAgent ? " agent-run-data:" : "");
1571
+ }
1572
+ var starterCompose = starterComposeFor(true);
1573
+ function environmentExample(includeExample, includeAgent = true) {
1402
1574
  return `# 复制为 .env 后填写。不要提交真实密码。
1403
1575
  POSTGRES_PASSWORD=
1404
1576
  WINDY_BOOTSTRAP_ADMIN_PASSWORD=
1577
+ ${includeAgent ? `
1578
+ # Agent profile 仅由 Server 读取;不要使用 VITE_ 前缀。
1579
+ PLATFORM_ENABLE_AI=false
1580
+ AGENT_SERVER_INTERNAL_TOKEN=
1581
+ QWEN_API_URL=
1582
+ QWEN_API_KEY=
1583
+ QWEN_MODEL=
1584
+ # AI_PROVIDER_CONNECT_TIMEOUT_MS=5000
1585
+ # AI_PROVIDER_REQUEST_TIMEOUT_MS=60000
1586
+ # AI_PROVIDER_MAX_RESPONSE_BYTES=2097152
1587
+ ` : ""}
1405
1588
 
1406
1589
  # 可选搜索配置;不配置时启用 Manifest 中默认开启的 Provider。${includeExample ? `
1407
1590
  # SEARCH_PROVIDER_KEYS=work-order.ticket.search` : `
@@ -1411,7 +1594,8 @@ WINDY_BOOTSTRAP_ADMIN_PASSWORD=
1411
1594
  # SEARCH_MAX_REQUESTS_PER_MINUTE=30
1412
1595
  `;
1413
1596
  }
1414
- function starterDocsReadme(includeOfflineLicense = true) {
1597
+ // src/project-files.ts
1598
+ function starterDocsReadme(includeOfflineLicense = true, includeAgent = true) {
1415
1599
  return `# Windy Starter 文档
1416
1600
 
1417
1601
  本目录只包含客户运行项目需要的平台架构、开发接入和运维可观测性文档。
@@ -1422,11 +1606,13 @@ License 签发中心、独立签名服务、Vault、发布治理、内部任务
1422
1606
  - 平台开发:从 [系统 CRUD API](./platform/system-crud-api.md) 和 [CRUD 生成器](./development/crud-generator.md) 开始。
1423
1607
  ${includeOfflineLicense ? "- License 消费端:[离线激活与安装协议](./license/license-offline-activation-code.md);运行时能力由 npm 依赖 `@southwind-ai/license` 提供。" : ""}
1424
1608
  - 运维:[可观测性](./operations/observability.md) 与 [运维总览](./platform/operations-overview.md)。
1609
+ ${includeAgent ? "- Agent:[Agent Runtime 接入](./platform/agent-runtime.md) 与 [受控工具](./platform/agent-tools.md)。" : ""}
1425
1610
  - 兼容:[RuoYi-Vue Adapter](./adapters/ruoyi-vue-compatibility.md)。
1426
1611
  `;
1427
1612
  }
1428
1613
  function starterReadme(projectName, includeExample = true, includeOxc = true, projectLicense = "UNLICENSED", selectedModules = []) {
1429
1614
  const selected = new Set(selectedModules);
1615
+ const includeAgent = selected.has("system.agent");
1430
1616
  const moduleRows = moduleDistributionCatalog.map(({ name, description, selection }) => `| \`${name}\` | ${selected.has(name) ? "已包含" : "未包含"} | ${selection === "required" ? "H0" : "H2"} | ${description} |`).join(`
1431
1617
  `);
1432
1618
  return `# ${projectName}
@@ -1482,6 +1668,21 @@ bun run dev:server
1482
1668
  直接输出已有地址,其它服务占用时会自动使用后续端口,最多检查 100 个。实际地址始终
1483
1669
  以终端输出为准。\`dev:server\` 不会自动启动 PostgreSQL 或执行 migration;未配置
1484
1670
  \`DATABASE_URL\` 时使用内存模式。
1671
+ ${includeAgent ? `
1672
+ ## OpenAI-compatible Agent profile
1673
+
1674
+ Agent Runtime 默认不启动。先在仅供本机使用的 \`.env\` 中设置
1675
+ \`PLATFORM_ENABLE_AI=true\`、\`AGENT_SERVER_INTERNAL_TOKEN\`、\`QWEN_API_URL\`、
1676
+ \`QWEN_API_KEY\` 与 \`QWEN_MODEL\`,再运行:
1677
+
1678
+ \`\`\`bash
1679
+ bun run dev:agent
1680
+ \`\`\`
1681
+
1682
+ 公网 Provider URL 必须使用 HTTPS;本机、容器服务名和内网地址允许 HTTP。
1683
+ \`QWEN_API_KEY\` 只进入 Agent Server,不会进入 Web 环境或返回给客户端。运行与模块
1684
+ 接入细节见 [Agent Runtime 接入](./docs/platform/agent-runtime.md)。
1685
+ ` : ""}
1485
1686
 
1486
1687
  ## 页面与登录
1487
1688
 
@@ -1604,7 +1805,9 @@ function createStarterPackage(source, projectName, includeOxc = true, projectLic
1604
1805
  prepare: "git rev-parse --git-dir >/dev/null 2>&1 || exit 0; husky"
1605
1806
  } : {},
1606
1807
  dev: "docker compose up",
1607
- "dev:build": "docker compose up --build",
1808
+ "docker:sync": "create-windy sync-docker",
1809
+ "dev:build": "bun run docker:sync && docker compose up --build",
1810
+ ...selectedModules.includes("system.agent") ? { "dev:agent": "docker compose --profile agent up --build" } : {},
1608
1811
  ...generatorVersion ? { windy: "create-windy" } : {},
1609
1812
  "dev:web": "bun run --cwd apps/web dev",
1610
1813
  "dev:server": "bun run --cwd apps/server dev",
@@ -1652,6 +1855,7 @@ var templateRootFiles = [
1652
1855
  ];
1653
1856
  var templateDirectories = [
1654
1857
  "apps/server",
1858
+ "apps/agent-server",
1655
1859
  "apps/web",
1656
1860
  "examples",
1657
1861
  "packages/ai-client",
@@ -1668,6 +1872,7 @@ var templateDirectories = [
1668
1872
  var templateDocumentationFiles = [
1669
1873
  "docs/adapters/ruoyi-vue-compatibility.md",
1670
1874
  "docs/architecture/admin-surface-boundary.md",
1875
+ "docs/architecture/ai-runtime.md",
1671
1876
  "docs/architecture/data-scope-query-enforcement.md",
1672
1877
  "docs/architecture/durable-jobs.md",
1673
1878
  "docs/architecture/minimal-approval-workflow.md",
@@ -1678,6 +1883,7 @@ var templateDocumentationFiles = [
1678
1883
  "docs/license/license-offline-activation-code.md",
1679
1884
  "docs/operations/observability.md",
1680
1885
  "docs/platform/agent-tools.md",
1886
+ "docs/platform/agent-runtime.md",
1681
1887
  "docs/platform/audit-reliability.md",
1682
1888
  "docs/platform/bulk-data-jobs.md",
1683
1889
  "docs/platform/data-access-control.md",
@@ -1725,21 +1931,21 @@ var excludedRuntimeFiles = new Set([
1725
1931
  ]);
1726
1932
 
1727
1933
  // src/provenance.ts
1728
- import { mkdir as mkdir2, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
1729
- import { join as join7 } from "node:path";
1934
+ import { mkdir as mkdir2, readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
1935
+ import { join as join8 } from "node:path";
1730
1936
  var templateMetadataFile = ".windy-template.json";
1731
1937
  var generationMetadataFile = ".windy/generation.json";
1732
1938
  async function readTemplateMetadata(sourceRoot) {
1733
- const path = join7(sourceRoot, templateMetadataFile);
1734
- const parsed = JSON.parse(await readFile7(path, "utf8"));
1939
+ const path = join8(sourceRoot, templateMetadataFile);
1940
+ const parsed = JSON.parse(await readFile8(path, "utf8"));
1735
1941
  if (!isTemplateMetadata(parsed)) {
1736
1942
  throw new Error("create-windy 模板来源元数据无效");
1737
1943
  }
1738
1944
  return parsed;
1739
1945
  }
1740
1946
  async function readGenerationProvenance(targetDirectory) {
1741
- const path = join7(targetDirectory, generationMetadataFile);
1742
- const parsed = JSON.parse(await readFile7(path, "utf8"));
1947
+ const path = join8(targetDirectory, generationMetadataFile);
1948
+ const parsed = JSON.parse(await readFile8(path, "utf8"));
1743
1949
  if (!isGenerationProvenance(parsed)) {
1744
1950
  throw new Error("当前目录不是受支持的 Windy 项目:Project Lock 无效");
1745
1951
  }
@@ -1771,9 +1977,9 @@ async function writeGenerationProvenance(targetDirectory, metadata, profile, sel
1771
1977
  includeOxc
1772
1978
  }
1773
1979
  };
1774
- const path = join7(targetDirectory, generationMetadataFile);
1775
- await mkdir2(join7(path, ".."), { recursive: true });
1776
- await writeFile6(path, `${JSON.stringify(provenance, null, 2)}
1980
+ const path = join8(targetDirectory, generationMetadataFile);
1981
+ await mkdir2(join8(path, ".."), { recursive: true });
1982
+ await writeFile7(path, `${JSON.stringify(provenance, null, 2)}
1777
1983
  `);
1778
1984
  }
1779
1985
  function isTemplateMetadata(value) {
@@ -1798,8 +2004,8 @@ function isModuleVersion(value) {
1798
2004
  }
1799
2005
 
1800
2006
  // src/registry-dependencies.ts
1801
- import { lstat as lstat3, readFile as readFile8, readdir as readdir3, writeFile as writeFile7 } from "node:fs/promises";
1802
- import { basename as basename2, extname as extname2, join as join8 } from "node:path";
2007
+ import { lstat as lstat3, readFile as readFile9, readdir as readdir4, writeFile as writeFile8 } from "node:fs/promises";
2008
+ import { basename as basename2, extname as extname2, join as join9 } from "node:path";
1803
2009
  var dependencySections = [
1804
2010
  "dependencies",
1805
2011
  "devDependencies",
@@ -1827,14 +2033,14 @@ async function materializeRegistryDependencies(root, dependencies) {
1827
2033
  await rewritePackageJson(path, dependencies);
1828
2034
  continue;
1829
2035
  }
1830
- const source = await readFile8(path, "utf8");
2036
+ const source = await readFile9(path, "utf8");
1831
2037
  const output = dependencies.reduce((value, dependency) => value.replaceAll(dependency.sourceName, dependency.name), source);
1832
2038
  if (output !== source)
1833
- await writeFile7(path, output);
2039
+ await writeFile8(path, output);
1834
2040
  }
1835
2041
  }
1836
2042
  async function rewritePackageJson(path, registryDependencies) {
1837
- const value = JSON.parse(await readFile8(path, "utf8"));
2043
+ const value = JSON.parse(await readFile9(path, "utf8"));
1838
2044
  let changed = false;
1839
2045
  for (const section of dependencySections) {
1840
2046
  const dependencies = value[section];
@@ -1849,7 +2055,7 @@ async function rewritePackageJson(path, registryDependencies) {
1849
2055
  }
1850
2056
  }
1851
2057
  if (changed)
1852
- await writeFile7(path, `${JSON.stringify(value, null, 2)}
2058
+ await writeFile8(path, `${JSON.stringify(value, null, 2)}
1853
2059
  `);
1854
2060
  }
1855
2061
  async function collectFiles3(root) {
@@ -1857,10 +2063,10 @@ async function collectFiles3(root) {
1857
2063
  await visit(root);
1858
2064
  return files.sort();
1859
2065
  async function visit(directory) {
1860
- for (const name of (await readdir3(directory)).sort()) {
2066
+ for (const name of (await readdir4(directory)).sort()) {
1861
2067
  if ([".git", "node_modules", "dist"].includes(name))
1862
2068
  continue;
1863
- const path = join8(directory, name);
2069
+ const path = join9(directory, name);
1864
2070
  const metadata = await lstat3(path);
1865
2071
  if (metadata.isSymbolicLink())
1866
2072
  continue;
@@ -1884,39 +2090,39 @@ async function generateProject(input) {
1884
2090
  const selectedModules = input.selectedModules ?? templateMetadata.modules.filter(({ name }) => requestedIncludeExample || name !== "work-order").map(({ name }) => name);
1885
2091
  const includeExample = selectedModules.includes("work-order");
1886
2092
  for (const relativePath2 of generationTemplatePaths(true, includeOxc)) {
1887
- await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2), join9(input.targetDirectory, relativePath2));
2093
+ await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2), join10(input.targetDirectory, relativePath2));
1888
2094
  }
1889
2095
  await materializeSelectedModules(input.targetDirectory, selectedModules);
1890
2096
  await materializeRegistryDependencies(input.targetDirectory, selectedModuleManifests(selectedModules).flatMap(({ registryDependencies }) => registryDependencies));
1891
- const packagePath = join9(input.targetDirectory, "package.json");
1892
- const packageJson = JSON.parse(await readFile9(packagePath, "utf8"));
1893
- await writeFile8(packagePath, `${JSON.stringify(createStarterPackage(packageJson, input.projectName, includeOxc, projectLicense, selectedModules, templateMetadata.generatorVersion), null, 2)}
2097
+ const packagePath = join10(input.targetDirectory, "package.json");
2098
+ const packageJson = JSON.parse(await readFile10(packagePath, "utf8"));
2099
+ await writeFile9(packagePath, `${JSON.stringify(createStarterPackage(packageJson, input.projectName, includeOxc, projectLicense, selectedModules, templateMetadata.generatorVersion), null, 2)}
1894
2100
  `);
1895
- await writeFile8(join9(input.targetDirectory, "docker-compose.yml"), starterCompose);
1896
- await writeFile8(join9(input.targetDirectory, ".env.example"), environmentExample(includeExample));
1897
- await writeFile8(join9(input.targetDirectory, "README.md"), starterReadme(input.projectName, includeExample, includeOxc, projectLicense, selectedModules));
1898
- await writeFile8(join9(input.targetDirectory, "docs/README.md"), starterDocsReadme(selectedModules.includes("system.license")));
2101
+ await writeFile9(join10(input.targetDirectory, "docker-compose.yml"), starterComposeFor(selectedModules.includes("system.agent")));
2102
+ await writeFile9(join10(input.targetDirectory, ".env.example"), environmentExample(includeExample, selectedModules.includes("system.agent")));
2103
+ await writeFile9(join10(input.targetDirectory, "README.md"), starterReadme(input.projectName, includeExample, includeOxc, projectLicense, selectedModules));
2104
+ await writeFile9(join10(input.targetDirectory, "docs/README.md"), starterDocsReadme(selectedModules.includes("system.license"), selectedModules.includes("system.agent")));
1899
2105
  await reflectCodeQualityChoice(input.targetDirectory, includeOxc);
1900
2106
  await applyWindyBaseLicense(input.targetDirectory);
1901
2107
  await applyProjectLicense(input.targetDirectory, projectLicense, input.licenseHolder);
1902
2108
  await prepareCustomerDockerfiles(input.targetDirectory);
1903
2109
  await writeGenerationProvenance(input.targetDirectory, templateMetadata, input.profile ?? starterProfile.key, selectedModules, includeOxc, projectLicense);
1904
- await rm3(join9(input.targetDirectory, "bun.lock"), { force: true });
2110
+ await rm3(join10(input.targetDirectory, "bun.lock"), { force: true });
1905
2111
  await writeManagedFilesManifest(input.targetDirectory, templateMetadata.generatorVersion);
1906
2112
  }
1907
2113
  async function resolveTemplateSource(sourceRoot, relativePath2) {
1908
- const source = join9(sourceRoot, relativePath2);
2114
+ const source = join10(sourceRoot, relativePath2);
1909
2115
  try {
1910
2116
  await lstat4(source);
1911
2117
  return source;
1912
2118
  } catch (error) {
1913
- if (!isMissing2(error))
2119
+ if (!isMissing3(error))
1914
2120
  throw error;
1915
2121
  }
1916
2122
  const alias = npmTemplateAliases[relativePath2];
1917
2123
  if (!alias)
1918
2124
  return source;
1919
- return join9(sourceRoot, alias);
2125
+ return join10(sourceRoot, alias);
1920
2126
  }
1921
2127
  async function assertGeneratedProjectIsClean(targetDirectory) {
1922
2128
  const violations = [];
@@ -1941,11 +2147,11 @@ function validateProjectName(projectName) {
1941
2147
  }
1942
2148
  async function assertTargetAvailable(targetDirectory) {
1943
2149
  try {
1944
- const entries = await readdir4(targetDirectory);
2150
+ const entries = await readdir5(targetDirectory);
1945
2151
  if (entries.length)
1946
2152
  throw new Error("目标目录不是空目录");
1947
2153
  } catch (error) {
1948
- if (isMissing2(error))
2154
+ if (isMissing3(error))
1949
2155
  return;
1950
2156
  throw error;
1951
2157
  }
@@ -1957,26 +2163,26 @@ async function copyTree(source, target) {
1957
2163
  return;
1958
2164
  if (metadata.isDirectory()) {
1959
2165
  await mkdir3(target, { recursive: true });
1960
- for (const child of await readdir4(source)) {
1961
- await copyTree(join9(source, child), join9(target, child));
2166
+ for (const child of await readdir5(source)) {
2167
+ await copyTree(join10(source, child), join10(target, child));
1962
2168
  }
1963
2169
  return;
1964
2170
  }
1965
- await mkdir3(join9(target, ".."), { recursive: true });
2171
+ await mkdir3(join10(target, ".."), { recursive: true });
1966
2172
  await copyFile(source, target);
1967
2173
  }
1968
2174
  function shouldExclude(name) {
1969
2175
  return excludedNames.has(name) || excludedRuntimeFiles.has(name);
1970
2176
  }
1971
2177
  async function walk(directory, visit) {
1972
- for (const name of await readdir4(directory)) {
1973
- const path = join9(directory, name);
2178
+ for (const name of await readdir5(directory)) {
2179
+ const path = join10(directory, name);
1974
2180
  await visit(path);
1975
2181
  if ((await lstat4(path)).isDirectory())
1976
2182
  await walk(path, visit);
1977
2183
  }
1978
2184
  }
1979
- function isMissing2(error) {
2185
+ function isMissing3(error) {
1980
2186
  return error instanceof Error && "code" in error && error.code === "ENOENT";
1981
2187
  }
1982
2188
 
@@ -2049,9 +2255,9 @@ async function createProject(options, dependencies = {}) {
2049
2255
  }
2050
2256
  async function inspectTarget(path) {
2051
2257
  try {
2052
- return (await readdir5(path)).length === 0 ? "empty" : "non-empty";
2258
+ return (await readdir6(path)).length === 0 ? "empty" : "non-empty";
2053
2259
  } catch (error) {
2054
- if (isMissing3(error))
2260
+ if (isMissing4(error))
2055
2261
  return "missing";
2056
2262
  throw error;
2057
2263
  }
@@ -2073,7 +2279,7 @@ function stageError(stage, error) {
2073
2279
  function sanitizeReason(reason) {
2074
2280
  return reason.replace(/\b([A-Z][A-Z0-9_]*(?:PASSWORD|SECRET|TOKEN|DATABASE_URL)[A-Z0-9_]*)=\S+/g, "$1=[已隐藏]").replace(/:\/\/([^\s:/]+):([^\s@]+)@/g, "://$1:[已隐藏]@");
2075
2281
  }
2076
- function isMissing3(error) {
2282
+ function isMissing4(error) {
2077
2283
  return error instanceof Error && "code" in error && error.code === "ENOENT";
2078
2284
  }
2079
2285
 
@@ -3821,8 +4027,8 @@ import { rm as rm8 } from "node:fs/promises";
3821
4027
  import { resolve as resolve5 } from "node:path";
3822
4028
 
3823
4029
  // src/update/doctor.ts
3824
- import { readFile as readFile11 } from "node:fs/promises";
3825
- import { join as join11 } from "node:path";
4030
+ import { readFile as readFile12 } from "node:fs/promises";
4031
+ import { join as join12 } from "node:path";
3826
4032
 
3827
4033
  // src/update/process.ts
3828
4034
  import { spawn as spawn3 } from "node:child_process";
@@ -3854,12 +4060,12 @@ import {
3854
4060
  access as access2,
3855
4061
  copyFile as copyFile2,
3856
4062
  mkdir as mkdir5,
3857
- readFile as readFile10,
4063
+ readFile as readFile11,
3858
4064
  rm as rm5,
3859
4065
  rmdir,
3860
- writeFile as writeFile9
4066
+ writeFile as writeFile10
3861
4067
  } from "node:fs/promises";
3862
- import { dirname as dirname3, join as join10, resolve as resolve4, sep as sep3 } from "node:path";
4068
+ import { dirname as dirname3, join as join11, resolve as resolve4, sep as sep3 } from "node:path";
3863
4069
  var updateStatePath = ".windy/update-state.json";
3864
4070
  async function applyTransaction(plan) {
3865
4071
  if (plan.conflicts.length) {
@@ -3868,21 +4074,22 @@ async function applyTransaction(plan) {
3868
4074
  const changed = plan.files.filter(({ action }) => action === "create" || action === "update" || action === "delete");
3869
4075
  for (const file of changed)
3870
4076
  assertSafePath(file.path);
3871
- const backupDirectory = join10(plan.projectDirectory, ".windy/backups", plan.id);
4077
+ const backupDirectory = join11(plan.projectDirectory, ".windy/backups", plan.id);
3872
4078
  const protectedPaths = new Set([
3873
4079
  ...changed.map(({ path: path2 }) => path2),
3874
4080
  ".windy/generation.json",
3875
4081
  ".windy/files.json",
4082
+ "apps/web/Dockerfile",
3876
4083
  "bun.lock"
3877
4084
  ]);
3878
4085
  const backupEntries = [];
3879
4086
  for (const path2 of protectedPaths) {
3880
- const source = join10(plan.projectDirectory, path2);
4087
+ const source = join11(plan.projectDirectory, path2);
3881
4088
  const existed = await exists(source);
3882
4089
  backupEntries.push({ path: path2, existed });
3883
4090
  if (!existed)
3884
4091
  continue;
3885
- const target = join10(backupDirectory, "files", path2);
4092
+ const target = join11(backupDirectory, "files", path2);
3886
4093
  await mkdir5(dirname3(target), { recursive: true });
3887
4094
  await copyFile2(source, target);
3888
4095
  }
@@ -3895,7 +4102,7 @@ async function applyTransaction(plan) {
3895
4102
  await writeState(plan.projectDirectory, state);
3896
4103
  try {
3897
4104
  for (const file of changed) {
3898
- const target = join10(plan.projectDirectory, file.path);
4105
+ const target = join11(plan.projectDirectory, file.path);
3899
4106
  if (file.action === "delete") {
3900
4107
  const existed = await exists(target);
3901
4108
  await rm5(target, { force: true });
@@ -3907,7 +4114,7 @@ async function applyTransaction(plan) {
3907
4114
  if (!file.content)
3908
4115
  throw new Error(`更新计划缺少文件内容:${file.path}`);
3909
4116
  await mkdir5(dirname3(target), { recursive: true });
3910
- await writeFile9(target, file.content);
4117
+ await writeFile10(target, file.content);
3911
4118
  }
3912
4119
  } catch (error) {
3913
4120
  await restoreUpdateState(plan.projectDirectory, state);
@@ -3922,7 +4129,7 @@ async function applyTransaction(plan) {
3922
4129
  }
3923
4130
  async function readUpdateState(projectDirectory) {
3924
4131
  try {
3925
- const parsed = JSON.parse(await readFile10(join10(projectDirectory, updateStatePath), "utf8"));
4132
+ const parsed = JSON.parse(await readFile11(join11(projectDirectory, updateStatePath), "utf8"));
3926
4133
  if (!isUpdateState(parsed))
3927
4134
  throw new Error("更新恢复状态无效,请人工检查 .windy 目录");
3928
4135
  return parsed;
@@ -3935,25 +4142,25 @@ async function readUpdateState(projectDirectory) {
3935
4142
  async function restoreUpdateState(projectDirectory, state) {
3936
4143
  for (const entry of state.entries) {
3937
4144
  assertSafePath(entry.path);
3938
- const target = join10(projectDirectory, entry.path);
4145
+ const target = join11(projectDirectory, entry.path);
3939
4146
  if (!entry.existed) {
3940
4147
  await rm5(target, { force: true });
3941
4148
  continue;
3942
4149
  }
3943
- const backup = join10(state.backupDirectory, "files", entry.path);
4150
+ const backup = join11(state.backupDirectory, "files", entry.path);
3944
4151
  await mkdir5(dirname3(target), { recursive: true });
3945
4152
  await copyFile2(backup, target);
3946
4153
  }
3947
- await rm5(join10(projectDirectory, updateStatePath), { force: true });
4154
+ await rm5(join11(projectDirectory, updateStatePath), { force: true });
3948
4155
  await rm5(state.backupDirectory, { recursive: true, force: true });
3949
4156
  }
3950
4157
  async function clearUpdateState(projectDirectory) {
3951
- await rm5(join10(projectDirectory, updateStatePath), { force: true });
4158
+ await rm5(join11(projectDirectory, updateStatePath), { force: true });
3952
4159
  }
3953
4160
  async function writeState(projectDirectory, state) {
3954
- const path2 = join10(projectDirectory, updateStatePath);
4161
+ const path2 = join11(projectDirectory, updateStatePath);
3955
4162
  await mkdir5(dirname3(path2), { recursive: true });
3956
- await writeFile9(path2, `${JSON.stringify(state, null, 2)}
4163
+ await writeFile10(path2, `${JSON.stringify(state, null, 2)}
3957
4164
  `);
3958
4165
  }
3959
4166
  async function exists(path2) {
@@ -4021,7 +4228,7 @@ async function diagnoseProject(projectDirectory, repair = false) {
4021
4228
  if (file.ownership === "project-owned")
4022
4229
  continue;
4023
4230
  try {
4024
- const changed = hashContent(await readFile11(join11(projectDirectory, file.path))) !== file.hash;
4231
+ const changed = hashContent(await readFile12(join12(projectDirectory, file.path))) !== file.hash;
4025
4232
  if (!changed)
4026
4233
  continue;
4027
4234
  modifiedManagedFiles.push(file.path);
@@ -4070,7 +4277,9 @@ var recipes = [
4070
4277
  { id: "starter-0.2.15-to-0.2.16", from: "0.2.15", to: "0.2.16" },
4071
4278
  { id: "starter-0.2.16-to-0.2.17", from: "0.2.16", to: "0.2.17" },
4072
4279
  { id: "starter-0.2.17-to-0.2.18", from: "0.2.17", to: "0.2.18" },
4073
- { id: "starter-0.2.18-to-0.2.19", from: "0.2.18", to: "0.2.19" }
4280
+ { id: "starter-0.2.18-to-0.2.19", from: "0.2.18", to: "0.2.19" },
4281
+ { id: "starter-0.2.19-to-0.2.20", from: "0.2.19", to: "0.2.20" },
4282
+ { id: "starter-0.2.20-to-0.2.21", from: "0.2.20", to: "0.2.21" }
4074
4283
  ];
4075
4284
  function resolveRecipeChain(sourceVersion, targetVersion) {
4076
4285
  if (sourceVersion === targetVersion)
@@ -4095,19 +4304,19 @@ function resolveRecipeChain(sourceVersion, targetVersion) {
4095
4304
  }
4096
4305
 
4097
4306
  // src/update/updater.ts
4098
- import { mkdir as mkdir7, readFile as readFile15, rm as rm7, writeFile as writeFile11 } from "node:fs/promises";
4099
- import { dirname as dirname4, join as join16 } from "node:path";
4307
+ import { mkdir as mkdir7, readFile as readFile16, rm as rm7, writeFile as writeFile12 } from "node:fs/promises";
4308
+ import { dirname as dirname4, join as join17 } from "node:path";
4100
4309
 
4101
4310
  // src/update/artifacts.ts
4102
- import { mkdir as mkdir6, mkdtemp, readFile as readFile12 } from "node:fs/promises";
4311
+ import { mkdir as mkdir6, mkdtemp, readFile as readFile13 } from "node:fs/promises";
4103
4312
  import { tmpdir } from "node:os";
4104
- import { basename as basename4, join as join12 } from "node:path";
4313
+ import { basename as basename4, join as join13 } from "node:path";
4105
4314
  class PublishedArtifactProvider {
4106
4315
  async prepare(projectDirectory, provenance) {
4107
- const temporaryRoot = await mkdtemp(join12(tmpdir(), "windy-update-"));
4316
+ const temporaryRoot = await mkdtemp(join13(tmpdir(), "windy-update-"));
4108
4317
  const projectName = await readProjectName2(projectDirectory);
4109
- const baseDirectory = join12(temporaryRoot, "base", projectName);
4110
- const targetDirectory = join12(temporaryRoot, "target", projectName);
4318
+ const baseDirectory = join13(temporaryRoot, "base", projectName);
4319
+ const targetDirectory = join13(temporaryRoot, "target", projectName);
4111
4320
  await generatePublishedBase(temporaryRoot, projectName, provenance, baseDirectory);
4112
4321
  await generateProject({
4113
4322
  sourceRoot: await resolveTemplateRoot(),
@@ -4124,7 +4333,7 @@ class PublishedArtifactProvider {
4124
4333
  }
4125
4334
  }
4126
4335
  async function generatePublishedBase(temporaryRoot, projectName, provenance, expectedDirectory) {
4127
- const parent = join12(temporaryRoot, "base");
4336
+ const parent = join13(temporaryRoot, "base");
4128
4337
  await mkdir6(parent, { recursive: true });
4129
4338
  const args = [
4130
4339
  "--bun",
@@ -4148,18 +4357,18 @@ async function generatePublishedBase(temporaryRoot, projectName, provenance, exp
4148
4357
  }
4149
4358
  }
4150
4359
  await assertProcess("bunx", args, parent);
4151
- if (join12(parent, projectName) !== expectedDirectory) {
4360
+ if (join13(parent, projectName) !== expectedDirectory) {
4152
4361
  throw new Error("历史 artifact 生成目录异常");
4153
4362
  }
4154
4363
  }
4155
4364
  async function readProjectName2(projectDirectory) {
4156
- const source = JSON.parse(await readFile12(join12(projectDirectory, "package.json"), "utf8"));
4365
+ const source = JSON.parse(await readFile13(join13(projectDirectory, "package.json"), "utf8"));
4157
4366
  return source.name || basename4(projectDirectory);
4158
4367
  }
4159
4368
 
4160
4369
  // src/update/planner.ts
4161
- import { access as access3, readFile as readFile14 } from "node:fs/promises";
4162
- import { join as join15 } from "node:path";
4370
+ import { access as access3, readFile as readFile15 } from "node:fs/promises";
4371
+ import { join as join16 } from "node:path";
4163
4372
 
4164
4373
  // src/update/known-repairs.ts
4165
4374
  var wrongTextVersions = new Set(["0.2.12", "0.2.13"]);
@@ -4202,19 +4411,19 @@ function normalizeTableCell(cell) {
4202
4411
  }
4203
4412
 
4204
4413
  // src/update/merge.ts
4205
- import { mkdtemp as mkdtemp2, rm as rm6, writeFile as writeFile10 } from "node:fs/promises";
4414
+ import { mkdtemp as mkdtemp2, rm as rm6, writeFile as writeFile11 } from "node:fs/promises";
4206
4415
  import { tmpdir as tmpdir2 } from "node:os";
4207
- import { join as join13 } from "node:path";
4416
+ import { join as join14 } from "node:path";
4208
4417
  async function mergeTextFile(base, ours, theirs) {
4209
- const root = await mkdtemp2(join13(tmpdir2(), "windy-merge-"));
4418
+ const root = await mkdtemp2(join14(tmpdir2(), "windy-merge-"));
4210
4419
  try {
4211
- const oursPath = join13(root, "ours");
4212
- const basePath = join13(root, "base");
4213
- const theirsPath = join13(root, "theirs");
4420
+ const oursPath = join14(root, "ours");
4421
+ const basePath = join14(root, "base");
4422
+ const theirsPath = join14(root, "theirs");
4214
4423
  await Promise.all([
4215
- writeFile10(oursPath, ours),
4216
- writeFile10(basePath, base),
4217
- writeFile10(theirsPath, theirs)
4424
+ writeFile11(oursPath, ours),
4425
+ writeFile11(basePath, base),
4426
+ writeFile11(theirsPath, theirs)
4218
4427
  ]);
4219
4428
  const result = await captureProcess("git", ["merge-file", "-p", "--diff3", oursPath, basePath, theirsPath], root);
4220
4429
  if (result.code === 0) {
@@ -4382,8 +4591,8 @@ function isTableSeparator(cell) {
4382
4591
  }
4383
4592
 
4384
4593
  // src/update/migration-reconciler.ts
4385
- import { readFile as readFile13 } from "node:fs/promises";
4386
- import { join as join14 } from "node:path";
4594
+ import { readFile as readFile14 } from "node:fs/promises";
4595
+ import { join as join15 } from "node:path";
4387
4596
 
4388
4597
  // src/update/snapshot-delta.ts
4389
4598
  function applySnapshotDelta(before, after, current, path2, conflicts, conflictPath) {
@@ -4490,7 +4699,7 @@ async function reconcileDrizzleMigrations(input) {
4490
4699
  const tag = `${prefix}_${name}`;
4491
4700
  const sqlPath = `${drizzleRoot}/${tag}.sql`;
4492
4701
  const snapshotPath = `${drizzleRoot}/meta/${prefix}_snapshot.json`;
4493
- const sql = await readFile13(join14(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
4702
+ const sql = await readFile14(join15(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
4494
4703
  merged.id = targetAfter.id;
4495
4704
  merged.prevId = currentSnapshot.id;
4496
4705
  const snapshot = encodeJson(merged);
@@ -4537,13 +4746,13 @@ function unchanged(targetManifest) {
4537
4746
  async function readJournals(input) {
4538
4747
  try {
4539
4748
  const [base, project, target] = await Promise.all([
4540
- readJson(join14(input.baseDirectory, journalPath)),
4541
- readJson(join14(input.projectDirectory, journalPath)),
4542
- readJson(join14(input.targetDirectory, journalPath))
4749
+ readJson(join15(input.baseDirectory, journalPath)),
4750
+ readJson(join15(input.projectDirectory, journalPath)),
4751
+ readJson(join15(input.targetDirectory, journalPath))
4543
4752
  ]);
4544
4753
  return { base, project, target };
4545
4754
  } catch (error) {
4546
- if (isMissing4(error))
4755
+ if (isMissing5(error))
4547
4756
  return;
4548
4757
  throw error;
4549
4758
  }
@@ -4551,7 +4760,7 @@ async function readJournals(input) {
4551
4760
  async function preservePublishedHistory(manifest, projectDirectory, overrides) {
4552
4761
  const entries = manifest.files.filter(({ path: path2 }) => isMigrationArtifact(path2) && path2 !== journalPath);
4553
4762
  await Promise.all(entries.map(async ({ path: path2 }) => {
4554
- overrides.set(path2, await readFile13(join14(projectDirectory, path2)));
4763
+ overrides.set(path2, await readFile14(join15(projectDirectory, path2)));
4555
4764
  }));
4556
4765
  return entries;
4557
4766
  }
@@ -4565,7 +4774,7 @@ async function readSnapshot(root, tag) {
4565
4774
  const match = migrationPattern.exec(tag);
4566
4775
  if (!match)
4567
4776
  throw new Error(`${journalPath}:migration tag 格式无效:${tag}`);
4568
- return await readJson(join14(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
4777
+ return await readJson(join15(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
4569
4778
  }
4570
4779
  function validateUniqueNames(journal, label, conflicts) {
4571
4780
  const seen = new Set;
@@ -4606,9 +4815,9 @@ function encodeJson(value) {
4606
4815
  `);
4607
4816
  }
4608
4817
  async function readJson(path2) {
4609
- return JSON.parse(await readFile13(path2, "utf8"));
4818
+ return JSON.parse(await readFile14(path2, "utf8"));
4610
4819
  }
4611
- function isMissing4(error) {
4820
+ function isMissing5(error) {
4612
4821
  return error instanceof Error && "code" in error && error.code === "ENOENT";
4613
4822
  }
4614
4823
 
@@ -4729,9 +4938,9 @@ async function createUpdatePlan(request, artifacts) {
4729
4938
  }
4730
4939
  async function planFile(path2, project, baseRoot, targetRoot, source, target, provenance, targetContentOverride) {
4731
4940
  const [ours, base, theirs] = await Promise.all([
4732
- readOptional(join15(project, path2)),
4733
- readOptional(join15(baseRoot, path2)),
4734
- targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join15(targetRoot, path2))
4941
+ readOptional(join16(project, path2)),
4942
+ readOptional(join16(baseRoot, path2)),
4943
+ targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join16(targetRoot, path2))
4735
4944
  ]);
4736
4945
  const ownership = target?.ownership || source?.ownership || classifyOwnership(path2);
4737
4946
  const repaired = ours && theirs && provenance ? repairKnownDerivedFile({
@@ -4813,7 +5022,7 @@ function conflict(path2, ownership, reason) {
4813
5022
  }
4814
5023
  async function readOptional(path2) {
4815
5024
  try {
4816
- return await readFile14(path2);
5025
+ return await readFile15(path2);
4817
5026
  } catch (error) {
4818
5027
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
4819
5028
  return;
@@ -4829,7 +5038,7 @@ async function assertGitClean(projectDirectory) {
4829
5038
  }
4830
5039
  async function assertNoPendingUpdate(projectDirectory) {
4831
5040
  try {
4832
- await access3(join15(projectDirectory, ".windy/update-state.json"));
5041
+ await access3(join16(projectDirectory, ".windy/update-state.json"));
4833
5042
  throw new Error("检测到未完成更新,请先运行 bun run windy doctor --repair");
4834
5043
  } catch (error) {
4835
5044
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
@@ -4863,9 +5072,17 @@ class DefaultStarterUpdater {
4863
5072
  async verify(result2) {
4864
5073
  const steps = [];
4865
5074
  try {
5075
+ const dockerSync = await syncWebDockerfileInProject(result2.plan.projectDirectory, { requireAnchors: true });
5076
+ if (dockerSync.changed) {
5077
+ addChangedFile(result2, "apps/web/Dockerfile");
5078
+ }
5079
+ steps.push({
5080
+ name: "同步 Web Dockerfile workspace COPY 清单",
5081
+ status: dockerSync.synced ? "passed" : "skipped"
5082
+ });
4866
5083
  await this.#execute("bun", ["install", "--force"], result2.plan.projectDirectory);
4867
5084
  steps.push({ name: "bun install --force", status: "passed" });
4868
- const packageJson = JSON.parse(await readFile15(join16(result2.plan.projectDirectory, "package.json"), "utf8"));
5085
+ const packageJson = JSON.parse(await readFile16(join17(result2.plan.projectDirectory, "package.json"), "utf8"));
4869
5086
  for (const name of ["lint", "typecheck", "test", "build"]) {
4870
5087
  if (!packageJson.scripts?.[name]) {
4871
5088
  steps.push({ name: `bun run ${name}`, status: "skipped" });
@@ -4896,12 +5113,12 @@ async function finalize(result2) {
4896
5113
  const root = result2.plan.projectDirectory;
4897
5114
  const generationContent = `${JSON.stringify(result2.plan.targetProvenance, null, 2)}
4898
5115
  `;
4899
- await writeFile11(join16(root, ".windy/generation.json"), generationContent);
5116
+ await writeFile12(join17(root, ".windy/generation.json"), generationContent);
4900
5117
  const refreshedPaths = new Set([
4901
5118
  ".windy/generation.json",
4902
- ...result2.plan.files.filter(({ action }) => action === "create" || action === "update").map(({ path: path2 }) => path2)
5119
+ ...result2.changedFiles
4903
5120
  ]);
4904
- const refreshedHashes = new Map(await Promise.all([...refreshedPaths].map(async (path2) => [path2, hashContent(await readFile15(join16(root, path2)))])));
5121
+ const refreshedHashes = new Map(await Promise.all([...refreshedPaths].map(async (path2) => [path2, hashContent(await readFile16(join17(root, path2)))])));
4905
5122
  const targetManifest = {
4906
5123
  ...result2.plan.targetManifest,
4907
5124
  files: result2.plan.targetManifest.files.map((entry) => ({
@@ -4909,11 +5126,15 @@ async function finalize(result2) {
4909
5126
  hash: refreshedHashes.get(entry.path) ?? entry.hash
4910
5127
  }))
4911
5128
  };
4912
- await writeFile11(join16(root, ".windy/files.json"), `${JSON.stringify(targetManifest, null, 2)}
5129
+ await writeFile12(join17(root, ".windy/files.json"), `${JSON.stringify(targetManifest, null, 2)}
4913
5130
  `);
4914
5131
  }
5132
+ function addChangedFile(result2, path2) {
5133
+ if (!result2.changedFiles.includes(path2))
5134
+ result2.changedFiles.push(path2);
5135
+ }
4915
5136
  async function writeReport(result2, steps, relativePath2) {
4916
- const path2 = join16(result2.plan.projectDirectory, relativePath2);
5137
+ const path2 = join17(result2.plan.projectDirectory, relativePath2);
4917
5138
  await mkdir7(dirname4(path2), { recursive: true });
4918
5139
  const lines = [
4919
5140
  `# Windy 更新报告 ${result2.plan.id}`,
@@ -4931,16 +5152,27 @@ async function writeReport(result2, steps, relativePath2) {
4931
5152
  `备份保留在 \`.windy/backups/${result2.plan.id}\`,确认稳定后可人工删除。`,
4932
5153
  ""
4933
5154
  ];
4934
- await writeFile11(path2, lines.join(`
5155
+ await writeFile12(path2, lines.join(`
4935
5156
  `));
4936
5157
  }
4937
5158
 
4938
5159
  // src/update/cli.ts
4939
5160
  async function runLifecycleCommand(args) {
4940
5161
  const command = args[0];
4941
- if (command !== "doctor" && command !== "update")
5162
+ if (command !== "doctor" && command !== "update" && command !== "sync-docker") {
4942
5163
  return false;
5164
+ }
4943
5165
  const projectDirectory = resolve5(readValue2(args, "--cwd") || process.cwd());
5166
+ if (command === "sync-docker") {
5167
+ const result2 = await syncWebDockerfileInProject(projectDirectory, {
5168
+ requireAnchors: true
5169
+ });
5170
+ if (!result2.synced) {
5171
+ throw new Error("项目缺少 apps/web/Dockerfile,无法同步 COPY 清单");
5172
+ }
5173
+ console.log(result2.changed ? "已同步 apps/web/Dockerfile workspace COPY 清单" : "apps/web/Dockerfile workspace COPY 清单已是最新");
5174
+ return true;
5175
+ }
4944
5176
  if (command === "doctor") {
4945
5177
  const report = await diagnoseProject(projectDirectory, args.includes("--repair"));
4946
5178
  printDoctor(report);