ai-project-manage-cli 8.0.16 → 8.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -142,6 +142,14 @@ var init_request_config = __esm({
142
142
  method: "GET",
143
143
  path: "/cli/webide/attachments"
144
144
  }),
145
+ webideListDesignArtifacts: defineEndpoint({
146
+ method: "GET",
147
+ path: "/cli/webide/design-artifacts"
148
+ }),
149
+ webideRegisterDesignArtifacts: defineEndpoint({
150
+ method: "PUT",
151
+ path: "/cli/webide/design-artifacts"
152
+ }),
145
153
  webideRecommendPlan: defineEndpoint({
146
154
  method: "PUT",
147
155
  path: "/cli/webide/plan-recommendation"
@@ -46,6 +46,14 @@ var requestConfig = {
46
46
  method: "GET",
47
47
  path: "/cli/webide/attachments"
48
48
  }),
49
+ webideListDesignArtifacts: defineEndpoint({
50
+ method: "GET",
51
+ path: "/cli/webide/design-artifacts"
52
+ }),
53
+ webideRegisterDesignArtifacts: defineEndpoint({
54
+ method: "PUT",
55
+ path: "/cli/webide/design-artifacts"
56
+ }),
49
57
  webideRecommendPlan: defineEndpoint({
50
58
  method: "PUT",
51
59
  path: "/cli/webide/plan-recommendation"
@@ -263,12 +271,16 @@ function isWorkspaceApmInitialized(workdir) {
263
271
  }
264
272
  var WEBIDE_SUBDIR = "webide";
265
273
  var WEBIDE_ATTACHMENTS_SUBDIR = "attachments";
274
+ var WEBIDE_DESIGN_SUBDIR = "design";
266
275
  function webideTaskDir(taskId, workdir) {
267
276
  return join2(workdir, ".apm", WEBIDE_SUBDIR, taskId);
268
277
  }
269
278
  function webideAttachmentsDir(taskId, workdir) {
270
279
  return join2(webideTaskDir(taskId, workdir), WEBIDE_ATTACHMENTS_SUBDIR);
271
280
  }
281
+ function webideDesignDir(taskId, workdir) {
282
+ return join2(webideTaskDir(taskId, workdir), WEBIDE_DESIGN_SUBDIR);
283
+ }
272
284
  async function ensureLoggedConfig() {
273
285
  const cfg = await ensureApmConfig();
274
286
  if (!resolveApiKey(cfg)) {
@@ -1858,6 +1870,334 @@ function createMysqlExecuteTool(options) {
1858
1870
  };
1859
1871
  }
1860
1872
 
1873
+ // src/commands/connect/tools/package-sql-tool.ts
1874
+ import { existsSync as existsSync4, statSync as statSync4 } from "node:fs";
1875
+ import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve5 } from "node:path";
1876
+
1877
+ // src/commands/connect/sync-package-sql.ts
1878
+ import { createHash } from "node:crypto";
1879
+ import fs from "node:fs";
1880
+ import path from "node:path";
1881
+
1882
+ // src/utils/minio.ts
1883
+ import * as Minio from "minio";
1884
+ var MinioClient = class {
1885
+ inner;
1886
+ constructor(opts) {
1887
+ const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
1888
+ this.inner = new Minio.Client({
1889
+ endPoint,
1890
+ port: opts.port,
1891
+ useSSL: opts.useSSL,
1892
+ accessKey: opts.accessKey,
1893
+ secretKey: opts.secretKey
1894
+ });
1895
+ }
1896
+ async ensureBucket(bucket) {
1897
+ const exists = await this.inner.bucketExists(bucket);
1898
+ if (!exists) {
1899
+ await this.inner.makeBucket(bucket);
1900
+ }
1901
+ }
1902
+ async putObject(bucket, objectKey, body, meta) {
1903
+ await this.inner.putObject(bucket, objectKey, body, body.length, meta);
1904
+ }
1905
+ /** 大文件:从本地路径流式上传,避免整包读入内存 */
1906
+ async fPutObject(bucket, objectKey, filePath, meta) {
1907
+ await this.inner.fPutObject(bucket, objectKey, filePath, meta);
1908
+ }
1909
+ async listObjects(bucket, prefix) {
1910
+ const objects = [];
1911
+ const stream = this.inner.listObjectsV2(bucket, prefix, true);
1912
+ await new Promise((resolve8, reject) => {
1913
+ stream.on("data", (obj) => {
1914
+ if (!obj.name) return;
1915
+ objects.push({
1916
+ name: obj.name,
1917
+ size: typeof obj.size === "number" ? obj.size : 0,
1918
+ lastModified: obj.lastModified ?? null,
1919
+ ...typeof obj.etag === "string" ? { etag: obj.etag } : {}
1920
+ });
1921
+ });
1922
+ stream.on("error", reject);
1923
+ stream.on("end", () => resolve8());
1924
+ });
1925
+ return objects;
1926
+ }
1927
+ async statObject(bucket, objectKey) {
1928
+ const stat = await this.inner.statObject(bucket, objectKey);
1929
+ return {
1930
+ size: typeof stat.size === "number" ? stat.size : 0,
1931
+ metaData: stat.metaData
1932
+ };
1933
+ }
1934
+ async removeObject(bucket, objectKey) {
1935
+ await this.inner.removeObject(bucket, objectKey);
1936
+ }
1937
+ };
1938
+
1939
+ // src/commands/connect/apm-log-minio.ts
1940
+ async function fetchApmLogStorage(cfg) {
1941
+ const api = createApmApiClient(cfg);
1942
+ return api.cli.getApmLogStorage(void 0);
1943
+ }
1944
+ async function createApmLogMinioClient(cfg) {
1945
+ const storage = await fetchApmLogStorage(cfg);
1946
+ return {
1947
+ client: new MinioClient({
1948
+ endPoint: storage.endpoint,
1949
+ port: storage.port,
1950
+ useSSL: storage.useSsl,
1951
+ accessKey: storage.accessKey,
1952
+ secretKey: storage.secretKey
1953
+ }),
1954
+ bucket: storage.bucket,
1955
+ ...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
1956
+ };
1957
+ }
1958
+
1959
+ // src/commands/connect/sync-package-sql.ts
1960
+ var SKIP_DIR_NAMES2 = /* @__PURE__ */ new Set([
1961
+ ".apm",
1962
+ ".git",
1963
+ ".idea",
1964
+ ".vscode",
1965
+ "node_modules",
1966
+ "target",
1967
+ "dist",
1968
+ "build",
1969
+ "out",
1970
+ "coverage",
1971
+ "__pycache__"
1972
+ ]);
1973
+ function isSqlFileName(name) {
1974
+ return name.toLowerCase().endsWith(".sql");
1975
+ }
1976
+ function sha256File(absPath) {
1977
+ const hash = createHash("sha256");
1978
+ hash.update(fs.readFileSync(absPath));
1979
+ return hash.digest("hex");
1980
+ }
1981
+ function normalizeSqlRepoName(repo) {
1982
+ const trimmed = repo.trim().replace(/\\/g, "/");
1983
+ if (!trimmed) {
1984
+ throw new Error("repo \u4E0D\u80FD\u4E3A\u7A7A");
1985
+ }
1986
+ if (trimmed.includes("/") || trimmed.includes("..") || trimmed === "." || trimmed.startsWith(".")) {
1987
+ throw new Error(`\u65E0\u6548\u7684 repo \u540D: ${repo}`);
1988
+ }
1989
+ return trimmed;
1990
+ }
1991
+ function scanSqlFilesUnderDir(localDir) {
1992
+ const root = path.resolve(toFsPath(localDir));
1993
+ if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
1994
+ throw new Error(`localDir \u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${localDir}`);
1995
+ }
1996
+ const byRelative = /* @__PURE__ */ new Map();
1997
+ const walk = (dir) => {
1998
+ let entries;
1999
+ try {
2000
+ entries = fs.readdirSync(dir, { withFileTypes: true });
2001
+ } catch {
2002
+ return;
2003
+ }
2004
+ for (const entry of entries) {
2005
+ const name = entry.name;
2006
+ if (entry.isDirectory()) {
2007
+ if (SKIP_DIR_NAMES2.has(name) || name.startsWith(".")) continue;
2008
+ walk(path.join(dir, name));
2009
+ continue;
2010
+ }
2011
+ if (!entry.isFile() || !isSqlFileName(name)) continue;
2012
+ const absPath = path.join(dir, name);
2013
+ const relativePath = path.relative(root, absPath).replace(/\\/g, "/");
2014
+ if (!relativePath || relativePath.includes("..")) continue;
2015
+ const stat = fs.statSync(absPath);
2016
+ if (!stat.isFile() || stat.size <= 0) continue;
2017
+ byRelative.set(relativePath, {
2018
+ absPath,
2019
+ relativePath,
2020
+ size: stat.size,
2021
+ sha256: sha256File(absPath)
2022
+ });
2023
+ }
2024
+ };
2025
+ walk(root);
2026
+ return [...byRelative.values()].sort(
2027
+ (a, b) => a.relativePath.localeCompare(b.relativePath)
2028
+ );
2029
+ }
2030
+ function packageSqlObjectKey(projectId, repo, relativePath) {
2031
+ return `packages/${projectId}/${repo}/${relativePath}`;
2032
+ }
2033
+ function packageSqlRepoPrefix(projectId, repo) {
2034
+ return `packages/${projectId}/${repo}/`;
2035
+ }
2036
+ async function syncPackageSqlToMinio(params) {
2037
+ const id = params.projectId.trim();
2038
+ if (!id) {
2039
+ throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
2040
+ }
2041
+ const repo = normalizeSqlRepoName(params.repo);
2042
+ const localFiles = scanSqlFilesUnderDir(params.localDir);
2043
+ const { client, bucket } = await createApmLogMinioClient(params.cfg);
2044
+ await client.ensureBucket(bucket);
2045
+ const prefix = packageSqlRepoPrefix(id, repo);
2046
+ const remoteObjects = await client.listObjects(bucket, prefix);
2047
+ const remoteByRelative = /* @__PURE__ */ new Map();
2048
+ for (const obj of remoteObjects) {
2049
+ if (!obj.name.startsWith(prefix) || obj.name.endsWith("/")) continue;
2050
+ if (!obj.name.toLowerCase().endsWith(".sql")) continue;
2051
+ if (obj.name.includes("..")) continue;
2052
+ const relativePath = obj.name.slice(prefix.length);
2053
+ if (!relativePath) continue;
2054
+ remoteByRelative.set(relativePath, {
2055
+ objectKey: obj.name,
2056
+ size: obj.size
2057
+ });
2058
+ }
2059
+ for (const [relativePath, remote] of remoteByRelative) {
2060
+ const local = localFiles.find((f) => f.relativePath === relativePath);
2061
+ if (!local) continue;
2062
+ if (local.size !== remote.size) continue;
2063
+ try {
2064
+ const stat = await client.statObject(bucket, remote.objectKey);
2065
+ const meta = stat.metaData ?? {};
2066
+ const sha = meta["sha256"] || meta["x-amz-meta-sha256"] || meta["Sha256"] || void 0;
2067
+ if (typeof sha === "string" && sha.trim()) {
2068
+ remote.sha256 = sha.trim().toLowerCase();
2069
+ }
2070
+ } catch {
2071
+ }
2072
+ }
2073
+ let uploaded = 0;
2074
+ let skipped = 0;
2075
+ const localRelativeSet = new Set(localFiles.map((f) => f.relativePath));
2076
+ for (const file of localFiles) {
2077
+ const objectKey = packageSqlObjectKey(id, repo, file.relativePath);
2078
+ const remote = remoteByRelative.get(file.relativePath);
2079
+ if (remote?.sha256 && remote.sha256 === file.sha256) {
2080
+ skipped += 1;
2081
+ continue;
2082
+ }
2083
+ await client.fPutObject(bucket, objectKey, file.absPath, {
2084
+ "Content-Type": "application/sql",
2085
+ sha256: file.sha256
2086
+ });
2087
+ uploaded += 1;
2088
+ }
2089
+ let deleted = 0;
2090
+ for (const [relativePath, remote] of remoteByRelative) {
2091
+ if (localRelativeSet.has(relativePath)) continue;
2092
+ await client.removeObject(bucket, remote.objectKey);
2093
+ deleted += 1;
2094
+ }
2095
+ return {
2096
+ scanned: localFiles.length,
2097
+ uploaded,
2098
+ skipped,
2099
+ deleted
2100
+ };
2101
+ }
2102
+
2103
+ // src/commands/connect/tools/package-sql-tool.ts
2104
+ function asString4(value) {
2105
+ return typeof value === "string" ? value.trim() : "";
2106
+ }
2107
+ function resolveLocalSqlDir(workdir, localDirArg) {
2108
+ if (!isAbsolute2(localDirArg)) {
2109
+ return { ok: false, error: "localDir \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
2110
+ }
2111
+ const workdirAbs = resolve5(toFsPath(workdir));
2112
+ const targetAbs = resolve5(toFsPath(localDirArg));
2113
+ const rel = relative3(workdirAbs, targetAbs);
2114
+ if (!rel || rel.startsWith("..") || isAbsolute2(rel)) {
2115
+ return { ok: false, error: "localDir \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
2116
+ }
2117
+ const fsPath = toFsPath(targetAbs);
2118
+ if (!existsSync4(fsPath)) {
2119
+ return { ok: false, error: `\u76EE\u5F55\u4E0D\u5B58\u5728: ${localDirArg}` };
2120
+ }
2121
+ try {
2122
+ if (!statSync4(fsPath).isDirectory()) {
2123
+ return { ok: false, error: "localDir \u5FC5\u987B\u662F\u76EE\u5F55" };
2124
+ }
2125
+ } catch {
2126
+ return { ok: false, error: `\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55: ${localDirArg}` };
2127
+ }
2128
+ return { ok: true, absPath: targetAbs };
2129
+ }
2130
+ function createUploadPackageSqlTool(options) {
2131
+ const { cfg, workdir, projectId } = options;
2132
+ return {
2133
+ UploadPackageSql: {
2134
+ description: "Upload SQL scripts from a local directory to project MinIO mirror. Call after reading deploy docs: pass repo (directory name from\u300C\u8FDB\u5165 xxx \u76EE\u5F55\u300D) and localDir (absolute path to the SQL script directory). Syncs **/*.sql under localDir to packages/{projectId}/{repo}/.... Do not put SQL into jars.zip.",
2135
+ inputSchema: {
2136
+ type: "object",
2137
+ properties: {
2138
+ repo: {
2139
+ type: "string",
2140
+ description: "Backend repo root directory name, e.g. mmis-java (from deploy\u300C\u8FDB\u5165 xxx \u76EE\u5F55\u300D)"
2141
+ },
2142
+ localDir: {
2143
+ type: "string",
2144
+ description: "Absolute path to the SQL script root directory under the workspace (deploy\u300CSQL\u811A\u672C\u76EE\u5F55\u300Dwithout the /**/*.sql suffix)"
2145
+ }
2146
+ },
2147
+ required: ["repo", "localDir"]
2148
+ },
2149
+ execute: async (args) => {
2150
+ const repoRaw = asString4(args.repo);
2151
+ const localDirRaw = asString4(args.localDir);
2152
+ if (!repoRaw) {
2153
+ return {
2154
+ content: [{ type: "text", text: "repo \u4E0D\u80FD\u4E3A\u7A7A" }],
2155
+ isError: true
2156
+ };
2157
+ }
2158
+ if (!localDirRaw) {
2159
+ return {
2160
+ content: [{ type: "text", text: "localDir \u4E0D\u80FD\u4E3A\u7A7A" }],
2161
+ isError: true
2162
+ };
2163
+ }
2164
+ let repo;
2165
+ try {
2166
+ repo = normalizeSqlRepoName(repoRaw);
2167
+ } catch (err) {
2168
+ const detail = err instanceof Error ? err.message : String(err);
2169
+ return {
2170
+ content: [{ type: "text", text: detail }],
2171
+ isError: true
2172
+ };
2173
+ }
2174
+ const resolved = resolveLocalSqlDir(workdir, localDirRaw);
2175
+ if (!resolved.ok) {
2176
+ return {
2177
+ content: [{ type: "text", text: resolved.error }],
2178
+ isError: true
2179
+ };
2180
+ }
2181
+ try {
2182
+ const result = await syncPackageSqlToMinio({
2183
+ cfg,
2184
+ projectId,
2185
+ repo,
2186
+ localDir: resolved.absPath
2187
+ });
2188
+ return `SQL \u5DF2\u540C\u6B65 repo=${repo} scanned=${result.scanned} uploaded=${result.uploaded} skipped=${result.skipped} deleted=${result.deleted}`;
2189
+ } catch (err) {
2190
+ const detail = err instanceof Error ? err.message : String(err);
2191
+ return {
2192
+ content: [{ type: "text", text: `\u4E0A\u4F20 SQL \u5931\u8D25: ${detail}` }],
2193
+ isError: true
2194
+ };
2195
+ }
2196
+ }
2197
+ }
2198
+ };
2199
+ }
2200
+
1861
2201
  // src/commands/connect/tools/package-status-tool.ts
1862
2202
  function createSetPackageFailedTool(options) {
1863
2203
  const { packageId, setFailed } = options;
@@ -1964,6 +2304,17 @@ function createCursorCustomTools(cfg, options) {
1964
2304
  }
1965
2305
  })
1966
2306
  );
2307
+ const projectId = options.projectId?.trim();
2308
+ if (projectId && options.workdir) {
2309
+ Object.assign(
2310
+ tools,
2311
+ createUploadPackageSqlTool({
2312
+ cfg,
2313
+ workdir: options.workdir,
2314
+ projectId
2315
+ })
2316
+ );
2317
+ }
1967
2318
  }
1968
2319
  return tools;
1969
2320
  }
@@ -2088,6 +2439,7 @@ async function runCursorAgent(cfg, ctx, options) {
2088
2439
  workdir,
2089
2440
  enablePackageStatusTools: options.enablePackageStatusTools,
2090
2441
  packageId: options.packageId,
2442
+ projectId: options.projectId,
2091
2443
  onPackageFailed: options.onPackageFailed
2092
2444
  });
2093
2445
  const enableSandbox = Boolean(options.enableSandbox);
@@ -2221,13 +2573,14 @@ async function runCursorAgent(cfg, ctx, options) {
2221
2573
  }
2222
2574
 
2223
2575
  // src/commands/connect/webide-agent-registry.ts
2224
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2225
- import { dirname as dirname3, resolve as resolve5 } from "node:path";
2226
- function registryPath(workdir, taskId) {
2227
- return resolve5(workdir, ".apm", "webide", taskId, "cursor-agent.json");
2576
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2577
+ import { dirname as dirname3, resolve as resolve6 } from "node:path";
2578
+ function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
2579
+ return resolve6(workdir, ".apm", "webide", taskId, fileName);
2228
2580
  }
2581
+ var DESIGN_AGENT_FILE = "design-agent.json";
2229
2582
  function readRegistry(path3) {
2230
- if (!existsSync4(path3)) {
2583
+ if (!existsSync5(path3)) {
2231
2584
  return {};
2232
2585
  }
2233
2586
  try {
@@ -2302,20 +2655,47 @@ function saveWebIdeAgentId(workdir, taskId, agentId) {
2302
2655
  }
2303
2656
  function clearWebIdeAgentId(workdir, taskId) {
2304
2657
  const path3 = registryPath(workdir, taskId);
2305
- if (!existsSync4(path3)) return;
2658
+ if (!existsSync5(path3)) return;
2306
2659
  syncWebIdeTaskState(workdir, taskId, { agentId: "" });
2307
2660
  }
2661
+ function loadWebIdeDesignAgentId(workdir, taskId) {
2662
+ return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
2663
+ }
2664
+ function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
2665
+ const trimmedTaskId = taskId.trim();
2666
+ if (!trimmedTaskId) return;
2667
+ const path3 = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
2668
+ const current = readRegistry(path3);
2669
+ writeRegistry(path3, {
2670
+ ...current,
2671
+ taskId: trimmedTaskId,
2672
+ agentId: agentId.trim() || void 0,
2673
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2674
+ });
2675
+ }
2676
+ function clearWebIdeDesignAgentId(workdir, taskId) {
2677
+ const path3 = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
2678
+ if (!existsSync5(path3)) return;
2679
+ const current = readRegistry(path3);
2680
+ const next = { ...current };
2681
+ delete next.agentId;
2682
+ writeRegistry(path3, {
2683
+ ...next,
2684
+ taskId: taskId.trim(),
2685
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2686
+ });
2687
+ }
2308
2688
 
2309
2689
  // src/commands/clean-webide-cache.ts
2310
- import { existsSync as existsSync5, rmSync } from "node:fs";
2311
- import { resolve as resolve6 } from "node:path";
2690
+ import { existsSync as existsSync6, rmSync } from "node:fs";
2691
+ import { resolve as resolve7 } from "node:path";
2312
2692
  import { getDefaultSdkStateRoot } from "@cursor/sdk";
2313
2693
  async function purgeCursorAgentStoreForAgent(workdir, agentId) {
2314
2694
  const trimmedAgentId = agentId.trim();
2315
2695
  const trimmedWorkdir = workdir.trim();
2316
2696
  if (!trimmedAgentId || !trimmedWorkdir) return false;
2317
2697
  const stateRoot = getDefaultSdkStateRoot(trimmedWorkdir);
2318
- if (!existsSync5(stateRoot)) return false;
2698
+ if (!existsSync6(stateRoot)) return false;
2319
2699
  const { SqliteLocalAgentStore } = await import(
2320
2700
  /* @vite-ignore */
2321
2701
  "@cursor/sdk/sqlite"
@@ -2379,8 +2759,8 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
2379
2759
  `[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
2380
2760
  );
2381
2761
  }
2382
- const dir = resolve6(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
2383
- if (existsSync5(dir)) {
2762
+ const dir = resolve7(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
2763
+ if (existsSync6(dir)) {
2384
2764
  rmSync(dir, { recursive: true, force: true });
2385
2765
  console.log(`[apm] \u5DF2\u6E05\u7406 WebIDE \u7F13\u5B58 ${dir}`);
2386
2766
  } else {
@@ -2391,11 +2771,11 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
2391
2771
  // src/commands/connect/webide-ask-question.ts
2392
2772
  import { setTimeout as delay } from "node:timers/promises";
2393
2773
  var POLL_INTERVAL_MS = 2e3;
2394
- function asString4(value) {
2774
+ function asString5(value) {
2395
2775
  return typeof value === "string" ? value.trim() : "";
2396
2776
  }
2397
2777
  function parseQuestions(args) {
2398
- const title = asString4(args.title) || void 0;
2778
+ const title = asString5(args.title) || void 0;
2399
2779
  const raw = args.questions;
2400
2780
  if (!Array.isArray(raw) || raw.length === 0) {
2401
2781
  throw new Error("AskQuestion \u7F3A\u5C11 questions");
@@ -2404,16 +2784,16 @@ function parseQuestions(args) {
2404
2784
  for (const item of raw) {
2405
2785
  if (!item || typeof item !== "object" || Array.isArray(item)) continue;
2406
2786
  const row = item;
2407
- const id = asString4(row.id);
2408
- const prompt = asString4(row.prompt);
2787
+ const id = asString5(row.id);
2788
+ const prompt = asString5(row.prompt);
2409
2789
  const optionsRaw = row.options;
2410
2790
  if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
2411
2791
  const options = [];
2412
2792
  for (const opt of optionsRaw) {
2413
2793
  if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
2414
2794
  const o = opt;
2415
- const oid = asString4(o.id);
2416
- const label = asString4(o.label);
2795
+ const oid = asString5(o.id);
2796
+ const label = asString5(o.label);
2417
2797
  if (oid && label) options.push({ id: oid, label });
2418
2798
  }
2419
2799
  if (options.length < 2) {
@@ -2479,83 +2859,6 @@ function createWebIdeAskQuestionExecute(options) {
2479
2859
  };
2480
2860
  }
2481
2861
 
2482
- // src/utils/minio.ts
2483
- import * as Minio from "minio";
2484
- var MinioClient = class {
2485
- inner;
2486
- constructor(opts) {
2487
- const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
2488
- this.inner = new Minio.Client({
2489
- endPoint,
2490
- port: opts.port,
2491
- useSSL: opts.useSSL,
2492
- accessKey: opts.accessKey,
2493
- secretKey: opts.secretKey
2494
- });
2495
- }
2496
- async ensureBucket(bucket) {
2497
- const exists = await this.inner.bucketExists(bucket);
2498
- if (!exists) {
2499
- await this.inner.makeBucket(bucket);
2500
- }
2501
- }
2502
- async putObject(bucket, objectKey, body, meta) {
2503
- await this.inner.putObject(bucket, objectKey, body, body.length, meta);
2504
- }
2505
- /** 大文件:从本地路径流式上传,避免整包读入内存 */
2506
- async fPutObject(bucket, objectKey, filePath, meta) {
2507
- await this.inner.fPutObject(bucket, objectKey, filePath, meta);
2508
- }
2509
- async listObjects(bucket, prefix) {
2510
- const objects = [];
2511
- const stream = this.inner.listObjectsV2(bucket, prefix, true);
2512
- await new Promise((resolve7, reject) => {
2513
- stream.on("data", (obj) => {
2514
- if (!obj.name) return;
2515
- objects.push({
2516
- name: obj.name,
2517
- size: typeof obj.size === "number" ? obj.size : 0,
2518
- lastModified: obj.lastModified ?? null,
2519
- ...typeof obj.etag === "string" ? { etag: obj.etag } : {}
2520
- });
2521
- });
2522
- stream.on("error", reject);
2523
- stream.on("end", () => resolve7());
2524
- });
2525
- return objects;
2526
- }
2527
- async statObject(bucket, objectKey) {
2528
- const stat = await this.inner.statObject(bucket, objectKey);
2529
- return {
2530
- size: typeof stat.size === "number" ? stat.size : 0,
2531
- metaData: stat.metaData
2532
- };
2533
- }
2534
- async removeObject(bucket, objectKey) {
2535
- await this.inner.removeObject(bucket, objectKey);
2536
- }
2537
- };
2538
-
2539
- // src/commands/connect/apm-log-minio.ts
2540
- async function fetchApmLogStorage(cfg) {
2541
- const api = createApmApiClient(cfg);
2542
- return api.cli.getApmLogStorage(void 0);
2543
- }
2544
- async function createApmLogMinioClient(cfg) {
2545
- const storage = await fetchApmLogStorage(cfg);
2546
- return {
2547
- client: new MinioClient({
2548
- endPoint: storage.endpoint,
2549
- port: storage.port,
2550
- useSSL: storage.useSsl,
2551
- accessKey: storage.accessKey,
2552
- secretKey: storage.secretKey
2553
- }),
2554
- bucket: storage.bucket,
2555
- ...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
2556
- };
2557
- }
2558
-
2559
2862
  // src/commands/connect/webide-message-log.ts
2560
2863
  var SYNC_INTERVAL_MS = 2e3;
2561
2864
  function webIdeEventsObjectPrefix(taskId, messageId) {
@@ -2965,7 +3268,7 @@ function readCliVersion() {
2965
3268
  }
2966
3269
 
2967
3270
  // src/commands/sync-webide-attachments.ts
2968
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3271
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2969
3272
  import { join as join5 } from "path";
2970
3273
  var MANIFEST_FILE = ".sync-manifest.json";
2971
3274
  async function downloadAttachment(cfg, attachmentId) {
@@ -2983,7 +3286,7 @@ async function downloadAttachment(cfg, attachmentId) {
2983
3286
  }
2984
3287
  function loadManifest(dir) {
2985
3288
  const path3 = join5(dir, MANIFEST_FILE);
2986
- if (!existsSync6(path3)) {
3289
+ if (!existsSync7(path3)) {
2987
3290
  return { version: 1, attachments: {} };
2988
3291
  }
2989
3292
  try {
@@ -3006,7 +3309,7 @@ function saveManifest(dir, manifest) {
3006
3309
  );
3007
3310
  }
3008
3311
  function isAttachmentUpToDate(entry, item, dest) {
3009
- if (!entry || !existsSync6(dest)) return false;
3312
+ if (!entry || !existsSync7(dest)) return false;
3010
3313
  if (entry.name !== item.name) return false;
3011
3314
  const createdAt = item.createdAt ?? "";
3012
3315
  return entry.createdAt === createdAt;
@@ -3051,17 +3354,149 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
3051
3354
  );
3052
3355
  }
3053
3356
 
3357
+ // src/commands/sync-webide-design.ts
3358
+ import { createHash as createHash2 } from "crypto";
3359
+ import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3360
+ import { join as join6 } from "path";
3361
+ var MANIFEST_FILE2 = ".sync-manifest.json";
3362
+ function hashBuffer(buf) {
3363
+ return createHash2("sha256").update(buf).digest("hex");
3364
+ }
3365
+ function loadManifest2(dir) {
3366
+ const path3 = join6(dir, MANIFEST_FILE2);
3367
+ if (!existsSync8(path3)) {
3368
+ return { version: 1, files: {} };
3369
+ }
3370
+ try {
3371
+ const parsed = JSON.parse(readFileSync7(path3, "utf8"));
3372
+ if (parsed?.version === 1 && parsed.files && typeof parsed.files === "object") {
3373
+ return parsed;
3374
+ }
3375
+ } catch {
3376
+ }
3377
+ return { version: 1, files: {} };
3378
+ }
3379
+ function saveManifest2(dir, manifest) {
3380
+ writeFileSync5(
3381
+ join6(dir, MANIFEST_FILE2),
3382
+ `${JSON.stringify(manifest, null, 2)}
3383
+ `,
3384
+ "utf8"
3385
+ );
3386
+ }
3387
+ async function downloadDesignFile(cfg, taskId, name) {
3388
+ const base = cfg.baseUrl.trim().replace(/\/+$/, "");
3389
+ const apiKey = resolveApiKey(cfg);
3390
+ const params = new URLSearchParams({ taskId, name });
3391
+ if (apiKey) params.set("apiKey", apiKey);
3392
+ const url = `${base}/api/v1/cli/webide/design-artifacts/file?${params}`;
3393
+ const res = await fetch(url);
3394
+ if (!res.ok) {
3395
+ throw new Error(
3396
+ `[apm] \u4E0B\u8F7D\u8BBE\u8BA1\u6587\u4EF6\u5931\u8D25 (${res.status}): taskId=${taskId} name=${name}`
3397
+ );
3398
+ }
3399
+ return Buffer.from(await res.arrayBuffer());
3400
+ }
3401
+ async function syncWebIdeDesign(cfg, taskId, workdir, artifacts) {
3402
+ const dir = webideDesignDir(taskId, workdir);
3403
+ await ensureDirExists(dir);
3404
+ const manifest = loadManifest2(dir);
3405
+ const synced = [];
3406
+ for (const art of artifacts) {
3407
+ const name = art.name?.trim();
3408
+ if (!name || !name.toLowerCase().endsWith(".html")) continue;
3409
+ const localPath = join6(dir, name);
3410
+ const entry = manifest.files[name];
3411
+ if (entry?.contentHash === art.contentHash && existsSync8(localPath)) {
3412
+ continue;
3413
+ }
3414
+ const buf = await downloadDesignFile(cfg, taskId, name);
3415
+ writeFileSync5(localPath, buf);
3416
+ manifest.files[name] = { contentHash: hashBuffer(buf) };
3417
+ synced.push(name);
3418
+ }
3419
+ saveManifest2(dir, manifest);
3420
+ return synced;
3421
+ }
3422
+ function listLocalDesignHtmlFiles(taskId, workdir) {
3423
+ const dir = webideDesignDir(taskId, workdir);
3424
+ if (!existsSync8(dir)) return [];
3425
+ const out = [];
3426
+ for (const name of readdirSync3(dir)) {
3427
+ if (!name.toLowerCase().endsWith(".html")) continue;
3428
+ const absPath = join6(dir, name);
3429
+ const buf = readFileSync7(absPath);
3430
+ if (buf.length === 0) continue;
3431
+ out.push({ name, absPath, contentHash: hashBuffer(buf) });
3432
+ }
3433
+ return out;
3434
+ }
3435
+
3436
+ // src/opc-shared-types.ts
3437
+ function webIdeDesignObjectKey(taskId, fileName) {
3438
+ const safe = fileName.replace(/[/\\:*?"<>|]/g, "_").trim() || "index.html";
3439
+ return `tasks/${taskId.trim()}/design/${safe}`;
3440
+ }
3441
+
3442
+ // src/commands/connect/upload-webide-design.ts
3443
+ async function uploadWebIdeDesignArtifacts(cfg, taskId, workdir) {
3444
+ const locals = listLocalDesignHtmlFiles(taskId, workdir);
3445
+ if (locals.length === 0) {
3446
+ throw new Error(
3447
+ `\u8BBE\u8BA1\u76EE\u5F55\u65E0 HTML \u4EA7\u7269\uFF1A.apm/webide/${taskId}/design/\uFF08\u8BF7\u5199\u5165\u81F3\u5C11\u4E00\u4EFD .html\uFF09`
3448
+ );
3449
+ }
3450
+ const api = createApmApiClient(cfg);
3451
+ const remote = await api.cli.webideListDesignArtifacts({ taskId }) ?? [];
3452
+ const remoteByName = new Map(
3453
+ remote.map((r) => [r.name, r])
3454
+ );
3455
+ const { client, bucket } = await createApmLogMinioClient(cfg);
3456
+ await client.ensureBucket(bucket);
3457
+ let uploaded = 0;
3458
+ let skipped = 0;
3459
+ const artifacts = [];
3460
+ for (const file of locals) {
3461
+ const objectKey = webIdeDesignObjectKey(taskId, file.name);
3462
+ const prev = remoteByName.get(file.name);
3463
+ if (prev?.contentHash === file.contentHash) {
3464
+ skipped += 1;
3465
+ artifacts.push({
3466
+ name: file.name,
3467
+ objectKey: prev.objectKey || objectKey,
3468
+ contentHash: file.contentHash
3469
+ });
3470
+ continue;
3471
+ }
3472
+ await client.fPutObject(bucket, objectKey, file.absPath, {
3473
+ "Content-Type": "text/html; charset=utf-8"
3474
+ });
3475
+ uploaded += 1;
3476
+ artifacts.push({
3477
+ name: file.name,
3478
+ objectKey,
3479
+ contentHash: file.contentHash
3480
+ });
3481
+ }
3482
+ await api.cli.webideRegisterDesignArtifacts({
3483
+ taskId,
3484
+ artifacts
3485
+ });
3486
+ return { uploaded, skipped, artifacts };
3487
+ }
3488
+
3054
3489
  // src/utils/project-documents.ts
3055
3490
  import {
3056
- existsSync as existsSync7,
3057
- readdirSync as readdirSync3,
3058
- readFileSync as readFileSync7,
3491
+ existsSync as existsSync9,
3492
+ readdirSync as readdirSync4,
3493
+ readFileSync as readFileSync8,
3059
3494
  rmSync as rmSync2,
3060
- writeFileSync as writeFileSync5
3495
+ writeFileSync as writeFileSync6
3061
3496
  } from "fs";
3062
- import { createHash } from "crypto";
3063
- import { dirname as dirname5, join as join6, relative as relative3, sep } from "path";
3064
- var MANIFEST_FILE2 = "manifest.json";
3497
+ import { createHash as createHash3 } from "crypto";
3498
+ import { dirname as dirname5, join as join7, relative as relative4, sep } from "path";
3499
+ var MANIFEST_FILE3 = "manifest.json";
3065
3500
  function normalizeProjectIdForPath(projectId) {
3066
3501
  const id = projectId.trim();
3067
3502
  if (!id) {
@@ -3074,11 +3509,11 @@ function normalizeProjectIdForPath(projectId) {
3074
3509
  }
3075
3510
  function projectDocumentsDir(apmRoot, projectId) {
3076
3511
  const id = normalizeProjectIdForPath(projectId);
3077
- return join6(apmRoot ?? workspaceApmDir(), "project", id);
3512
+ return join7(apmRoot ?? workspaceApmDir(), "project", id);
3078
3513
  }
3079
3514
  function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
3080
3515
  const normalized = normalizeLocalDocumentPath(documentPath);
3081
- return join6(
3516
+ return join7(
3082
3517
  projectDocumentsDir(apmRoot, projectId),
3083
3518
  ...normalized.split("/")
3084
3519
  );
@@ -3095,19 +3530,19 @@ function normalizeLocalDocumentPath(path3) {
3095
3530
  return segments.join("/");
3096
3531
  }
3097
3532
  function hashLocalFileContent(content) {
3098
- return createHash("sha256").update(content, "utf8").digest("hex");
3533
+ return createHash3("sha256").update(content, "utf8").digest("hex");
3099
3534
  }
3100
3535
  function readLocalManifest(apmRoot, projectId) {
3101
- const manifestPath3 = join6(
3536
+ const manifestPath3 = join7(
3102
3537
  projectDocumentsDir(apmRoot, projectId),
3103
- MANIFEST_FILE2
3538
+ MANIFEST_FILE3
3104
3539
  );
3105
- if (!existsSync7(manifestPath3)) {
3540
+ if (!existsSync9(manifestPath3)) {
3106
3541
  return null;
3107
3542
  }
3108
3543
  try {
3109
3544
  return JSON.parse(
3110
- readFileSync7(manifestPath3, "utf8")
3545
+ readFileSync8(manifestPath3, "utf8")
3111
3546
  );
3112
3547
  } catch {
3113
3548
  return null;
@@ -3115,21 +3550,21 @@ function readLocalManifest(apmRoot, projectId) {
3115
3550
  }
3116
3551
  function listLocalDocumentPaths(apmRoot, projectId) {
3117
3552
  const root = projectDocumentsDir(apmRoot, projectId);
3118
- if (!existsSync7(root)) {
3553
+ if (!existsSync9(root)) {
3119
3554
  return [];
3120
3555
  }
3121
3556
  const paths = [];
3122
3557
  const walk = (dir) => {
3123
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
3124
- const abs = join6(dir, entry.name);
3558
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
3559
+ const abs = join7(dir, entry.name);
3125
3560
  if (entry.isDirectory()) {
3126
3561
  walk(abs);
3127
3562
  continue;
3128
3563
  }
3129
- if (entry.isFile() && entry.name === MANIFEST_FILE2) {
3564
+ if (entry.isFile() && entry.name === MANIFEST_FILE3) {
3130
3565
  continue;
3131
3566
  }
3132
- const rel = relative3(root, abs).split(sep).join("/");
3567
+ const rel = relative4(root, abs).split(sep).join("/");
3133
3568
  paths.push(rel);
3134
3569
  }
3135
3570
  };
@@ -3241,7 +3676,7 @@ ${diagnostic ?? ""}`);
3241
3676
  projectDocumentLocalPath(targetApmDir, projectId, doc.path)
3242
3677
  );
3243
3678
  await ensureDirExists(dirname5(absPath));
3244
- writeFileSync5(absPath, doc.content, "utf8");
3679
+ writeFileSync6(absPath, doc.content, "utf8");
3245
3680
  downloaded += 1;
3246
3681
  }
3247
3682
  }
@@ -3250,13 +3685,13 @@ ${diagnostic ?? ""}`);
3250
3685
  const absPath = toFsPath(
3251
3686
  projectDocumentLocalPath(targetApmDir, projectId, path3)
3252
3687
  );
3253
- if (existsSync7(absPath)) {
3688
+ if (existsSync9(absPath)) {
3254
3689
  rmSync2(absPath, { force: true });
3255
3690
  deleted += 1;
3256
3691
  }
3257
3692
  }
3258
- writeFileSync5(
3259
- toFsPath(join6(docsDir, MANIFEST_FILE2)),
3693
+ writeFileSync6(
3694
+ toFsPath(join7(docsDir, MANIFEST_FILE3)),
3260
3695
  `${JSON.stringify(remoteManifest, null, 2)}
3261
3696
  `,
3262
3697
  "utf8"
@@ -3299,7 +3734,7 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
3299
3734
  const absPath = toFsPath(
3300
3735
  projectDocumentLocalPath(targetApmDir, projectId, path3)
3301
3736
  );
3302
- const content = readFileSync7(absPath, "utf8");
3737
+ const content = readFileSync8(absPath, "utf8");
3303
3738
  const contentHash = hashLocalFileContent(content);
3304
3739
  if (remoteHashByPath.get(path3) === contentHash) {
3305
3740
  continue;
@@ -3320,20 +3755,20 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
3320
3755
  }
3321
3756
 
3322
3757
  // src/commands/connect/cli-version-sync.ts
3323
- import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
3324
- import { join as join7 } from "path";
3758
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
3759
+ import { join as join8 } from "path";
3325
3760
  var CLI_VERSION_FILE = ".cli-version.json";
3326
3761
  function manifestPath2(apmDir) {
3327
- return join7(apmDir, CLI_VERSION_FILE);
3762
+ return join8(apmDir, CLI_VERSION_FILE);
3328
3763
  }
3329
- function loadManifest2(apmDir) {
3764
+ function loadManifest3(apmDir) {
3330
3765
  const path3 = toFsPath(manifestPath2(apmDir));
3331
- if (!existsSync8(path3)) {
3766
+ if (!existsSync10(path3)) {
3332
3767
  return null;
3333
3768
  }
3334
3769
  try {
3335
3770
  const parsed = JSON.parse(
3336
- readFileSync8(path3, "utf8")
3771
+ readFileSync9(path3, "utf8")
3337
3772
  );
3338
3773
  if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
3339
3774
  return parsed;
@@ -3342,9 +3777,9 @@ function loadManifest2(apmDir) {
3342
3777
  }
3343
3778
  return null;
3344
3779
  }
3345
- function saveManifest2(apmDir, cliVersion) {
3780
+ function saveManifest3(apmDir, cliVersion) {
3346
3781
  const manifest = { version: 1, cliVersion };
3347
- writeFileSync6(
3782
+ writeFileSync7(
3348
3783
  toFsPath(manifestPath2(apmDir)),
3349
3784
  `${JSON.stringify(manifest, null, 2)}
3350
3785
  `,
@@ -3357,7 +3792,7 @@ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
3357
3792
  if (cached === currentVersion) {
3358
3793
  return false;
3359
3794
  }
3360
- const stored = loadManifest2(workspaceApmDir(workdir));
3795
+ const stored = loadManifest3(workspaceApmDir(workdir));
3361
3796
  if (stored?.cliVersion === currentVersion) {
3362
3797
  syncedInSession.set(workdir, currentVersion);
3363
3798
  return false;
@@ -3365,7 +3800,7 @@ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
3365
3800
  return true;
3366
3801
  }
3367
3802
  function markSkillsSyncedForCliVersion(workdir, cliVersion) {
3368
- saveManifest2(workspaceApmDir(workdir), cliVersion);
3803
+ saveManifest3(workspaceApmDir(workdir), cliVersion);
3369
3804
  syncedInSession.set(workdir, cliVersion);
3370
3805
  }
3371
3806
 
@@ -3402,6 +3837,9 @@ function shouldCommitAfterWebIdeMessage(action) {
3402
3837
  function isStartLocalServicesAction(action) {
3403
3838
  return action === "enter-manual-test" || action === "skip-test";
3404
3839
  }
3840
+ function isDesignAction(action) {
3841
+ return action === "start-design" || action === "revise-design";
3842
+ }
3405
3843
  function isExecuteSqlAction(action) {
3406
3844
  return action === "execute-sql";
3407
3845
  }
@@ -3607,6 +4045,28 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3607
4045
  },
3608
4046
  (names) => names.length > 0 ? `\u5DF2\u540C\u6B65 ${names.length} \u4E2A\u9644\u4EF6` : "\u65E0\u9644\u4EF6\u6216\u540C\u6B65\u8DF3\u8FC7"
3609
4047
  );
4048
+ await runPrepStep(
4049
+ "\u540C\u6B65\u8BBE\u8BA1\u4EA7\u7269",
4050
+ async () => {
4051
+ try {
4052
+ const artifacts = await api.cli.webideListDesignArtifacts({ taskId });
4053
+ const names = await syncWebIdeDesign(
4054
+ cfg,
4055
+ taskId,
4056
+ workdir,
4057
+ artifacts ?? []
4058
+ );
4059
+ return names;
4060
+ } catch (err) {
4061
+ console.warn(
4062
+ "[apm] WebIDE \u8BBE\u8BA1\u4EA7\u7269\u540C\u6B65\u5931\u8D25:",
4063
+ err instanceof Error ? err.message : err
4064
+ );
4065
+ return [];
4066
+ }
4067
+ },
4068
+ (names) => names.length > 0 ? `\u5DF2\u540C\u6B65 ${names.length} \u4E2A\u8BBE\u8BA1\u6587\u4EF6` : "\u65E0\u8BBE\u8BA1\u6587\u4EF6\u6216\u540C\u6B65\u8DF3\u8FC7"
4069
+ );
3610
4070
  eventSession.addCliStep(
3611
4071
  "\u542F\u52A8 Cursor Agent",
3612
4072
  "\u51C6\u5907 resume/create\u2026",
@@ -3614,10 +4074,19 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3614
4074
  );
3615
4075
  await syncPrepLog();
3616
4076
  const startLocalServices = isStartLocalServicesAction(msg.action);
4077
+ const designAction = isDesignAction(msg.action);
3617
4078
  const executeSql = isExecuteSqlAction(msg.action);
3618
- const savedAgentId = startLocalServices ? void 0 : loadWebIdeAgentId(workdir, taskId);
4079
+ const savedAgentId = startLocalServices ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
3619
4080
  const promptPayload = executeSql ? parseWebIdePromptPayload(msg.content) : null;
3620
4081
  const sqlExecutionId = executeSql && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
4082
+ const persistAgentId = (agentId) => {
4083
+ if (startLocalServices) return;
4084
+ if (designAction) {
4085
+ saveWebIdeDesignAgentId(workdir, taskId, agentId);
4086
+ } else {
4087
+ saveWebIdeAgentId(workdir, taskId, agentId);
4088
+ }
4089
+ };
3621
4090
  const outcome = await runCursorAgent(
3622
4091
  cfg,
3623
4092
  {
@@ -3640,17 +4109,21 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3640
4109
  signal
3641
4110
  }),
3642
4111
  enableAskQuestion: !startLocalServices && !executeSql,
3643
- enableWebIdePlanTools: !startLocalServices && !executeSql,
4112
+ enableWebIdePlanTools: !startLocalServices && !executeSql && !designAction,
3644
4113
  enablePtySessionMcp: startLocalServices,
3645
4114
  enableMysqlTools: executeSql,
3646
4115
  sqlExecutionId,
3647
4116
  enableSandbox: false,
3648
- onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => clearWebIdeAgentId(workdir, taskId),
4117
+ onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => {
4118
+ if (designAction) {
4119
+ clearWebIdeDesignAgentId(workdir, taskId);
4120
+ } else {
4121
+ clearWebIdeAgentId(workdir, taskId);
4122
+ }
4123
+ },
3649
4124
  taskId,
3650
4125
  createRemoteLogSync: (agentId) => {
3651
- if (!startLocalServices) {
3652
- saveWebIdeAgentId(workdir, taskId, agentId);
3653
- }
4126
+ persistAgentId(agentId);
3654
4127
  logSyncRef.current = createThrottledWebIdeMessageLogSync(
3655
4128
  cfg,
3656
4129
  { taskId, messageId, agentId },
@@ -3664,9 +4137,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3664
4137
  return logSyncRef.current;
3665
4138
  },
3666
4139
  onRunStarted: async ({ agentId, runId }) => {
3667
- if (!startLocalServices) {
3668
- saveWebIdeAgentId(workdir, taskId, agentId);
3669
- }
4140
+ persistAgentId(agentId);
3670
4141
  eventSession.addCliStep(
3671
4142
  "\u542F\u52A8 Cursor Agent",
3672
4143
  `agentId=${agentId} runId=${runId}`,
@@ -3679,7 +4150,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3679
4150
  }
3680
4151
  );
3681
4152
  if (!startLocalServices) {
3682
- saveWebIdeAgentId(workdir, taskId, outcome.agentId);
4153
+ persistAgentId(outcome.agentId);
3683
4154
  }
3684
4155
  const tokenUsage = outcome.usage != null ? {
3685
4156
  modelId: outcome.modelId ?? (msg.model?.trim() || void 0),
@@ -3765,6 +4236,30 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3765
4236
  );
3766
4237
  }
3767
4238
  }
4239
+ if (isDesignAction(msg.action)) {
4240
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4241
+ try {
4242
+ const uploaded = await uploadWebIdeDesignArtifacts(
4243
+ cfg,
4244
+ taskId,
4245
+ workdir
4246
+ );
4247
+ console.log(
4248
+ `[apm] webide \u8BBE\u8BA1\u4EA7\u7269\u4E0A\u4F20 action=${msg.action} uploaded=${uploaded.uploaded} skipped=${uploaded.skipped} files=${uploaded.artifacts.map((a) => a.name).join(",")}`
4249
+ );
4250
+ } catch (err) {
4251
+ const detail = err instanceof Error ? err.message : String(err);
4252
+ await logSyncRef.current?.markRun(
4253
+ outcome.runId,
4254
+ "error",
4255
+ detail,
4256
+ tokenUsage
4257
+ );
4258
+ await setError(cfg, messageId, detail);
4259
+ syncLocalTaskStatus(workdir, taskId, msg, "FAILED");
4260
+ return;
4261
+ }
4262
+ }
3768
4263
  if (shouldCommitAfterWebIdeMessage(msg.action)) {
3769
4264
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3770
4265
  const committed = await commitWorkspaceReposIfDirty(
@@ -4016,14 +4511,14 @@ async function handleStartProject(cfg, msg, signal) {
4016
4511
  }
4017
4512
 
4018
4513
  // src/commands/connect/upload-package-artifact.ts
4019
- import fs from "node:fs";
4020
- import path from "node:path";
4514
+ import fs2 from "node:fs";
4515
+ import path2 from "node:path";
4021
4516
  function packageTmpDir(packageId) {
4022
4517
  return `/data/package-tmp/${packageId}`;
4023
4518
  }
4024
4519
  function expectedLocalArtifactPath(target, packageId) {
4025
4520
  const fileName = target === "frontend" ? "dist.zip" : "jars.zip";
4026
- return path.join(packageTmpDir(packageId), fileName);
4521
+ return path2.join(packageTmpDir(packageId), fileName);
4027
4522
  }
4028
4523
  function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
4029
4524
  const folder = target === "frontend" ? "vue" : "java";
@@ -4033,7 +4528,7 @@ function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
4033
4528
  function removePackageTmpDir(packageId) {
4034
4529
  const dir = packageTmpDir(packageId);
4035
4530
  try {
4036
- fs.rmSync(dir, { recursive: true, force: true });
4531
+ fs2.rmSync(dir, { recursive: true, force: true });
4037
4532
  } catch (err) {
4038
4533
  console.warn(
4039
4534
  `[apm] \u6E05\u7406\u6253\u5305\u4E34\u65F6\u76EE\u5F55\u5931\u8D25 ${dir}:`,
@@ -4043,10 +4538,10 @@ function removePackageTmpDir(packageId) {
4043
4538
  }
4044
4539
  async function uploadPackageArtifact(cfg, target, packageId, projectId) {
4045
4540
  const localPath = expectedLocalArtifactPath(target, packageId);
4046
- if (!fs.existsSync(localPath)) {
4541
+ if (!fs2.existsSync(localPath)) {
4047
4542
  throw new Error(`\u6253\u5305\u4EA7\u7269\u4E0D\u5B58\u5728: ${localPath}`);
4048
4543
  }
4049
- const stat = fs.statSync(localPath);
4544
+ const stat = fs2.statSync(localPath);
4050
4545
  if (!stat.isFile() || stat.size <= 0) {
4051
4546
  throw new Error(`\u6253\u5305\u4EA7\u7269\u65E0\u6548\u6216\u4E3A\u7A7A: ${localPath}`);
4052
4547
  }
@@ -4062,163 +4557,6 @@ async function uploadPackageArtifact(cfg, target, packageId, projectId) {
4062
4557
  return { artifactPath: objectKey, artifactUrl };
4063
4558
  }
4064
4559
 
4065
- // src/commands/connect/sync-package-sql.ts
4066
- import { createHash as createHash2 } from "node:crypto";
4067
- import fs2 from "node:fs";
4068
- import path2 from "node:path";
4069
- var SKIP_DIR_NAMES2 = /* @__PURE__ */ new Set([
4070
- ".apm",
4071
- ".git",
4072
- ".idea",
4073
- ".vscode",
4074
- "node_modules",
4075
- "target",
4076
- "dist",
4077
- "build",
4078
- "out",
4079
- "coverage",
4080
- "__pycache__"
4081
- ]);
4082
- var SQL_DIR_MARKERS = [
4083
- "/db/migration/",
4084
- "/script/sql/update/"
4085
- ];
4086
- function relativePathUnderSqlRoot(normalizedAbsPath) {
4087
- const lower = normalizedAbsPath.toLowerCase();
4088
- for (const marker of SQL_DIR_MARKERS) {
4089
- const idx = lower.lastIndexOf(marker);
4090
- if (idx < 0) continue;
4091
- const relativePath = normalizedAbsPath.slice(idx + marker.length);
4092
- if (!relativePath || relativePath.includes("..")) continue;
4093
- return relativePath;
4094
- }
4095
- return null;
4096
- }
4097
- function isSqlFileName(name) {
4098
- return name.toLowerCase().endsWith(".sql");
4099
- }
4100
- function sha256File(absPath) {
4101
- const hash = createHash2("sha256");
4102
- hash.update(fs2.readFileSync(absPath));
4103
- return hash.digest("hex");
4104
- }
4105
- function scanLocalMigrationSqlFiles(workdir) {
4106
- const root = path2.resolve(workdir);
4107
- const byRelative = /* @__PURE__ */ new Map();
4108
- const walk = (dir) => {
4109
- let entries;
4110
- try {
4111
- entries = fs2.readdirSync(dir, { withFileTypes: true });
4112
- } catch {
4113
- return;
4114
- }
4115
- for (const entry of entries) {
4116
- const name = entry.name;
4117
- if (entry.isDirectory()) {
4118
- if (SKIP_DIR_NAMES2.has(name) || name.startsWith(".")) continue;
4119
- walk(path2.join(dir, name));
4120
- continue;
4121
- }
4122
- if (!entry.isFile() || !isSqlFileName(name)) continue;
4123
- const absPath = path2.join(dir, name);
4124
- const normalized = absPath.replace(/\\/g, "/");
4125
- const relativePath = relativePathUnderSqlRoot(normalized);
4126
- if (!relativePath) continue;
4127
- const stat = fs2.statSync(absPath);
4128
- if (!stat.isFile() || stat.size <= 0) continue;
4129
- const item = {
4130
- absPath,
4131
- relativePath,
4132
- size: stat.size,
4133
- sha256: sha256File(absPath)
4134
- };
4135
- const prev = byRelative.get(relativePath);
4136
- if (prev) {
4137
- console.warn(
4138
- `[apm] SQL relative path conflict, overwrite: ${relativePath}
4139
- old: ${prev.absPath}
4140
- new: ${absPath}`
4141
- );
4142
- }
4143
- byRelative.set(relativePath, item);
4144
- }
4145
- };
4146
- walk(root);
4147
- return [...byRelative.values()].sort(
4148
- (a, b) => a.relativePath.localeCompare(b.relativePath)
4149
- );
4150
- }
4151
- function packageSqlObjectKey(projectId, relativePath) {
4152
- return `packages/${projectId}/sql/${relativePath}`;
4153
- }
4154
- function packageSqlPrefix(projectId) {
4155
- return `packages/${projectId}/sql/`;
4156
- }
4157
- async function syncPackageSqlToMinio(cfg, workdir, projectId) {
4158
- const id = projectId.trim();
4159
- if (!id) {
4160
- throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
4161
- }
4162
- const localFiles = scanLocalMigrationSqlFiles(workdir);
4163
- const { client, bucket } = await createApmLogMinioClient(cfg);
4164
- await client.ensureBucket(bucket);
4165
- const prefix = packageSqlPrefix(id);
4166
- const remoteObjects = await client.listObjects(bucket, prefix);
4167
- const remoteByRelative = /* @__PURE__ */ new Map();
4168
- for (const obj of remoteObjects) {
4169
- if (!obj.name.startsWith(prefix) || obj.name.endsWith("/")) continue;
4170
- if (!obj.name.toLowerCase().endsWith(".sql")) continue;
4171
- const relativePath = obj.name.slice(prefix.length);
4172
- if (!relativePath) continue;
4173
- remoteByRelative.set(relativePath, {
4174
- objectKey: obj.name,
4175
- size: obj.size
4176
- });
4177
- }
4178
- for (const [relativePath, remote] of remoteByRelative) {
4179
- const local = localFiles.find((f) => f.relativePath === relativePath);
4180
- if (!local) continue;
4181
- if (local.size !== remote.size) continue;
4182
- try {
4183
- const stat = await client.statObject(bucket, remote.objectKey);
4184
- const meta = stat.metaData ?? {};
4185
- const sha = meta["sha256"] || meta["x-amz-meta-sha256"] || meta["Sha256"] || void 0;
4186
- if (typeof sha === "string" && sha.trim()) {
4187
- remote.sha256 = sha.trim().toLowerCase();
4188
- }
4189
- } catch {
4190
- }
4191
- }
4192
- let uploaded = 0;
4193
- let skipped = 0;
4194
- const localRelativeSet = new Set(localFiles.map((f) => f.relativePath));
4195
- for (const file of localFiles) {
4196
- const objectKey = packageSqlObjectKey(id, file.relativePath);
4197
- const remote = remoteByRelative.get(file.relativePath);
4198
- if (remote?.sha256 && remote.sha256 === file.sha256) {
4199
- skipped += 1;
4200
- continue;
4201
- }
4202
- await client.fPutObject(bucket, objectKey, file.absPath, {
4203
- "Content-Type": "application/sql",
4204
- sha256: file.sha256
4205
- });
4206
- uploaded += 1;
4207
- }
4208
- let deleted = 0;
4209
- for (const [relativePath, remote] of remoteByRelative) {
4210
- if (localRelativeSet.has(relativePath)) continue;
4211
- await client.removeObject(bucket, remote.objectKey);
4212
- deleted += 1;
4213
- }
4214
- return {
4215
- scanned: localFiles.length,
4216
- uploaded,
4217
- skipped,
4218
- deleted
4219
- };
4220
- }
4221
-
4222
4560
  // src/commands/connect/handle-package.ts
4223
4561
  async function updatePackageStatus(cfg, packageId, status, extra) {
4224
4562
  const api = createApmApiClient(cfg);
@@ -4349,21 +4687,6 @@ async function handleWebIdePackage(cfg, msg, signal) {
4349
4687
  return `downloaded=${result.downloaded} deleted=${result.deleted}`;
4350
4688
  }
4351
4689
  );
4352
- if (msg.target === "backend") {
4353
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4354
- try {
4355
- await runPrepStep(
4356
- "\u540C\u6B65 SQL \u5230 MinIO",
4357
- () => syncPackageSqlToMinio(cfg, workdir, projectId),
4358
- (result) => `scanned=${result.scanned} uploaded=${result.uploaded} skipped=${result.skipped} deleted=${result.deleted}`
4359
- );
4360
- } catch (err) {
4361
- console.warn(
4362
- `[apm] \u540C\u6B65 SQL \u5230 MinIO \u5931\u8D25\uFF08\u4E0D\u963B\u65AD\u6253\u5305\uFF09:`,
4363
- err instanceof Error ? err.message : err
4364
- );
4365
- }
4366
- }
4367
4690
  eventSession.addCliStep("\u542F\u52A8\u6253\u5305 Agent", "\u51C6\u5907 create\u2026", "running");
4368
4691
  await syncPrepLog();
4369
4692
  let failedByTool = false;
@@ -4388,6 +4711,7 @@ async function handleWebIdePackage(cfg, msg, signal) {
4388
4711
  enableSandbox: false,
4389
4712
  enablePackageStatusTools: true,
4390
4713
  packageId,
4714
+ projectId,
4391
4715
  onPackageFailed: async () => {
4392
4716
  failedByTool = true;
4393
4717
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "8.0.16",
3
+ "version": "8.0.18",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -40,7 +40,7 @@
40
40
  ### target=backend(SpringBoot)
41
41
 
42
42
  1. 按 deploy 文档进入后端目录,执行文档写明的构建命令(通常 `mvn clean package`)。
43
- 2. **只**收集 deploy 文档标明的交付 jar(路径/文件名/glob 以文档为准)。**禁止**全量收集 `target/lib`、**禁止**把 SQL 打进 jars.zip、**禁止**在规则或命令里写死具体 jar 名。SQL 镜像由 CLI 自动扫描本地 `db/migration`、`db/migrate`、`script/sql/update` 下的 `.sql` 同步到 MinIO,Agent **不要**上传 SQL。
43
+ 2. **只**收集 deploy 文档标明的交付 jar(路径/文件名/glob 以文档为准)。**禁止**全量收集 `target/lib`、**禁止**把 SQL 打进 jars.zip、**禁止**在规则或命令里写死具体 jar 名。
44
44
  3. 确认文档列出的每个 jar 均已生成;缺任一文件 → SetPackageFailed。
45
45
  4. 在临时目录将上述 jar **打成一个 zip**(勿在工作区生成交付 zip),落到:
46
46
 
@@ -57,7 +57,11 @@
57
57
  zip /data/package-tmp/<packageId>/jars.zip ./*.jar
58
58
  ```
59
59
 
60
- 5. 可选:同临时目录写 `commitId.txt`(后端仓 `git rev-parse HEAD` 一行),非必须。
60
+ 5. **SQL 镜像上传**(deploy 打包章节写有「SQL脚本目录」时必须做;无则跳过):
61
+ - 从「进入 `xxx` 目录」取 `repo`(如 `mmis-java`)
62
+ - 从「SQL脚本目录」取相对路径(去掉末尾 `/**/*.sql`),拼成工作区内 **绝对路径** `localDir`
63
+ - 调用 **UploadPackageSql**(`repo` + `localDir`);工具会同步该目录下全部 `**/*.sql` 到 MinIO `packages/{projectId}/{repo}/...`
64
+ - **禁止**猜测路径;**禁止**把 SQL 打进 jars.zip
61
65
  6. 构建或落盘失败 → 调用 **SetPackageFailed**(传入失败原因)后结束。
62
66
 
63
67
  ### 结束