ai-project-manage-cli 8.0.17 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- // src/commands/connect/webide-message-worker.ts
1
+ // src/commands/connect/core/worker/worker-script.ts
2
2
  import { parentPort, workerData } from "node:worker_threads";
3
3
 
4
4
  // src/api/client.ts
@@ -30,6 +30,11 @@ var requestConfig = {
30
30
  method: "PUT",
31
31
  path: "/cli/webide/messages/status"
32
32
  }),
33
+ /** CLI 改工作流状态统一入口(start-/complete- 等) */
34
+ webideRunAction: defineEndpoint({
35
+ method: "POST",
36
+ path: "/cli/webide/actions"
37
+ }),
33
38
  webideUpsertMessageLog: defineEndpoint({
34
39
  method: "PUT",
35
40
  path: "/cli/webide/message-logs"
@@ -46,6 +51,14 @@ var requestConfig = {
46
51
  method: "GET",
47
52
  path: "/cli/webide/attachments"
48
53
  }),
54
+ webideListDesignArtifacts: defineEndpoint({
55
+ method: "GET",
56
+ path: "/cli/webide/design-artifacts"
57
+ }),
58
+ webideRegisterDesignArtifacts: defineEndpoint({
59
+ method: "PUT",
60
+ path: "/cli/webide/design-artifacts"
61
+ }),
49
62
  webideRecommendPlan: defineEndpoint({
50
63
  method: "PUT",
51
64
  path: "/cli/webide/plan-recommendation"
@@ -122,13 +135,13 @@ import { join } from "path";
122
135
  var APM_CONFIG_DIR = join(homedir(), ".config", "apm");
123
136
  var APM_CONFIG_PATH = join(APM_CONFIG_DIR, "config.json");
124
137
  var DEFAULT_BASE_URL = "http://127.0.0.1:3000";
125
- function resolveClientMachineId(cfg) {
126
- return (cfg.clientMachineId ?? cfg.userId ?? "").trim();
138
+ function resolveClientMachineId(cfg2) {
139
+ return (cfg2.clientMachineId ?? cfg2.userId ?? "").trim();
127
140
  }
128
- function resolveApiKey(cfg) {
129
- return (cfg.apiKey ?? cfg.token ?? "").trim();
141
+ function resolveApiKey(cfg2) {
142
+ return (cfg2.apiKey ?? cfg2.token ?? "").trim();
130
143
  }
131
- async function tryReadApmConfig() {
144
+ function tryReadApmConfig() {
132
145
  try {
133
146
  const raw = readFileSync(APM_CONFIG_PATH, "utf8");
134
147
  const v = JSON.parse(raw);
@@ -141,10 +154,10 @@ async function tryReadApmConfig() {
141
154
  }
142
155
  const apiKey = resolveApiKey(rawCfg);
143
156
  if (!apiKey) return null;
144
- const cfg = v;
145
- const clientMachineId = resolveClientMachineId(cfg);
157
+ const cfg2 = v;
158
+ const clientMachineId = resolveClientMachineId(cfg2);
146
159
  return {
147
- baseUrl: cfg.baseUrl.trim().replace(/\/+$/, ""),
160
+ baseUrl: cfg2.baseUrl.trim().replace(/\/+$/, ""),
148
161
  apiKey,
149
162
  ...clientMachineId ? { clientMachineId } : {}
150
163
  };
@@ -166,11 +179,11 @@ async function ensureApmConfig() {
166
179
  await writeApmConfig(defaults);
167
180
  return defaults;
168
181
  }
169
- async function writeApmConfig(cfg) {
170
- const clientMachineId = resolveClientMachineId(cfg);
171
- const apiKey = resolveApiKey(cfg);
182
+ async function writeApmConfig(cfg2) {
183
+ const clientMachineId = resolveClientMachineId(cfg2);
184
+ const apiKey = resolveApiKey(cfg2);
172
185
  const normalized = {
173
- baseUrl: cfg.baseUrl.trim().replace(/\/+$/, ""),
186
+ baseUrl: cfg2.baseUrl.trim().replace(/\/+$/, ""),
174
187
  apiKey,
175
188
  ...clientMachineId ? { clientMachineId } : {}
176
189
  };
@@ -181,11 +194,11 @@ async function writeApmConfig(cfg) {
181
194
  }
182
195
 
183
196
  // src/api/client.ts
184
- function createApmApiClient(cfg) {
185
- const baseURL = `${cfg.baseUrl.trim().replace(/\/+$/, "")}/api/v1`;
197
+ function createApmApiClient(cfg2) {
198
+ const baseURL = `${cfg2.baseUrl.trim().replace(/\/+$/, "")}/api/v1`;
186
199
  return createApiClient(requestConfig, {
187
200
  baseURL,
188
- getToken: () => resolveApiKey(cfg) || void 0,
201
+ getToken: () => resolveApiKey(cfg2) || void 0,
189
202
  successCodes: [0],
190
203
  unauthorizedCodes: [401]
191
204
  });
@@ -263,21 +276,25 @@ function isWorkspaceApmInitialized(workdir) {
263
276
  }
264
277
  var WEBIDE_SUBDIR = "webide";
265
278
  var WEBIDE_ATTACHMENTS_SUBDIR = "attachments";
279
+ var WEBIDE_DESIGN_SUBDIR = "design";
266
280
  function webideTaskDir(taskId, workdir) {
267
281
  return join2(workdir, ".apm", WEBIDE_SUBDIR, taskId);
268
282
  }
269
283
  function webideAttachmentsDir(taskId, workdir) {
270
284
  return join2(webideTaskDir(taskId, workdir), WEBIDE_ATTACHMENTS_SUBDIR);
271
285
  }
286
+ function webideDesignDir(taskId, workdir) {
287
+ return join2(webideTaskDir(taskId, workdir), WEBIDE_DESIGN_SUBDIR);
288
+ }
272
289
  async function ensureLoggedConfig() {
273
- const cfg = await ensureApmConfig();
274
- if (!resolveApiKey(cfg)) {
290
+ const cfg2 = await ensureApmConfig();
291
+ if (!resolveApiKey(cfg2)) {
275
292
  console.error(
276
293
  "[apm] \u672A\u68C0\u6D4B\u5230 API Key\uFF0C\u8BF7\u5148\u6267\u884C: apm login --api-key cm_xxx"
277
294
  );
278
295
  process.exit(1);
279
296
  }
280
- return cfg;
297
+ return cfg2;
281
298
  }
282
299
  async function ensureDirExists(dir) {
283
300
  mkdirSync2(dir, { recursive: true });
@@ -923,8 +940,8 @@ async function runTaskBranch(taskId, options = {}) {
923
940
  if (!trimmedTaskId) {
924
941
  throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
925
942
  }
926
- const cfg = await ensureLoggedConfig();
927
- const api = createApmApiClient(cfg);
943
+ const cfg2 = await ensureLoggedConfig();
944
+ const api = createApmApiClient(cfg2);
928
945
  const projectBaseline = await api.cli.projectBaseBranch({
929
946
  taskId: trimmedTaskId
930
947
  });
@@ -1708,7 +1725,7 @@ function createMysqlExecuteTool(options) {
1708
1725
  const password = asString3(args.password);
1709
1726
  const database = asString3(args.database);
1710
1727
  const pathArg = asString3(args.path);
1711
- const port = asNumber(args.port) ?? 3306;
1728
+ const port2 = asNumber(args.port) ?? 3306;
1712
1729
  if (!host || !user || !database || !pathArg) {
1713
1730
  return {
1714
1731
  content: [
@@ -1730,7 +1747,7 @@ function createMysqlExecuteTool(options) {
1730
1747
  artifactPath: pathArg,
1731
1748
  error: resolved.error,
1732
1749
  host,
1733
- port,
1750
+ port: port2,
1734
1751
  database,
1735
1752
  dbUser: user
1736
1753
  });
@@ -1754,7 +1771,7 @@ function createMysqlExecuteTool(options) {
1754
1771
  artifactPath: resolved.displayPath,
1755
1772
  error: `\u8BFB\u53D6 SQL \u6587\u4EF6\u5931\u8D25: ${detail}`,
1756
1773
  host,
1757
- port,
1774
+ port: port2,
1758
1775
  database,
1759
1776
  dbUser: user
1760
1777
  });
@@ -1780,7 +1797,7 @@ function createMysqlExecuteTool(options) {
1780
1797
  sql: "",
1781
1798
  error: "SQL \u6587\u4EF6\u4E3A\u7A7A",
1782
1799
  host,
1783
- port,
1800
+ port: port2,
1784
1801
  database,
1785
1802
  dbUser: user
1786
1803
  });
@@ -1795,7 +1812,7 @@ function createMysqlExecuteTool(options) {
1795
1812
  try {
1796
1813
  connection = await mysql.createConnection({
1797
1814
  host,
1798
- port,
1815
+ port: port2,
1799
1816
  user,
1800
1817
  password,
1801
1818
  database,
@@ -1812,7 +1829,7 @@ function createMysqlExecuteTool(options) {
1812
1829
  path: resolved.displayPath,
1813
1830
  statements,
1814
1831
  host,
1815
- port,
1832
+ port: port2,
1816
1833
  database,
1817
1834
  dbUser: user
1818
1835
  };
@@ -1824,7 +1841,7 @@ function createMysqlExecuteTool(options) {
1824
1841
  sql,
1825
1842
  result: resultPayload,
1826
1843
  host,
1827
- port,
1844
+ port: port2,
1828
1845
  database,
1829
1846
  dbUser: user
1830
1847
  });
@@ -1840,7 +1857,7 @@ function createMysqlExecuteTool(options) {
1840
1857
  sql,
1841
1858
  error: detail,
1842
1859
  host: host || void 0,
1843
- port,
1860
+ port: port2,
1844
1861
  database: database || void 0,
1845
1862
  dbUser: user || void 0
1846
1863
  });
@@ -1925,12 +1942,12 @@ var MinioClient = class {
1925
1942
  };
1926
1943
 
1927
1944
  // src/commands/connect/apm-log-minio.ts
1928
- async function fetchApmLogStorage(cfg) {
1929
- const api = createApmApiClient(cfg);
1945
+ async function fetchApmLogStorage(cfg2) {
1946
+ const api = createApmApiClient(cfg2);
1930
1947
  return api.cli.getApmLogStorage(void 0);
1931
1948
  }
1932
- async function createApmLogMinioClient(cfg) {
1933
- const storage = await fetchApmLogStorage(cfg);
1949
+ async function createApmLogMinioClient(cfg2) {
1950
+ const storage = await fetchApmLogStorage(cfg2);
1934
1951
  return {
1935
1952
  client: new MinioClient({
1936
1953
  endPoint: storage.endpoint,
@@ -2116,7 +2133,7 @@ function resolveLocalSqlDir(workdir, localDirArg) {
2116
2133
  return { ok: true, absPath: targetAbs };
2117
2134
  }
2118
2135
  function createUploadPackageSqlTool(options) {
2119
- const { cfg, workdir, projectId } = options;
2136
+ const { cfg: cfg2, workdir, projectId } = options;
2120
2137
  return {
2121
2138
  UploadPackageSql: {
2122
2139
  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.",
@@ -2168,7 +2185,7 @@ function createUploadPackageSqlTool(options) {
2168
2185
  }
2169
2186
  try {
2170
2187
  const result = await syncPackageSqlToMinio({
2171
- cfg,
2188
+ cfg: cfg2,
2172
2189
  projectId,
2173
2190
  repo,
2174
2191
  localDir: resolved.absPath
@@ -2233,7 +2250,7 @@ AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u
2233
2250
  var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
2234
2251
  \u6240\u6709\u6587\u4EF6\u8BFB\u5199\u3001\u641C\u7D22\uFF08Grep/Glob/Read\uFF09\u3001Shell \u5FC5\u987B\u4E25\u683C\u9650\u5236\u5728\u5F53\u524D\u5DE5\u4F5C\u533A cwd \u5185\u3002
2235
2252
  \u7981\u6B62\u8BBF\u95EE\u5DE5\u4F5C\u533A\u5916\u8DEF\u5F84\uFF08\u5982\u5176\u5B83\u9879\u76EE\u3001~/.cursor\u3001agent-transcripts\u3001\u7CFB\u7EDF\u76EE\u5F55\u7B49\uFF09\u3002`;
2236
- function createCursorCustomTools(cfg, options) {
2253
+ function createCursorCustomTools(cfg2, options) {
2237
2254
  const tools = {
2238
2255
  ...options.enableAppendMessage !== false && options.appendMessageContent ? createAppendMessageCustomTools({
2239
2256
  appendContent: options.appendMessageContent
@@ -2244,7 +2261,7 @@ function createCursorCustomTools(cfg, options) {
2244
2261
  }) : {}
2245
2262
  };
2246
2263
  if (options?.taskId && options.workdir) {
2247
- const { cli } = createApmApiClient(cfg);
2264
+ const { cli } = createApmApiClient(cfg2);
2248
2265
  const taskId = options.taskId;
2249
2266
  if (options.enableWebIdePlanTools) {
2250
2267
  Object.assign(
@@ -2277,7 +2294,7 @@ function createCursorCustomTools(cfg, options) {
2277
2294
  }
2278
2295
  if (options.enablePackageStatusTools && options.packageId) {
2279
2296
  const packageId = options.packageId;
2280
- const { cli } = createApmApiClient(cfg);
2297
+ const { cli } = createApmApiClient(cfg2);
2281
2298
  Object.assign(
2282
2299
  tools,
2283
2300
  createSetPackageFailedTool({
@@ -2297,7 +2314,7 @@ function createCursorCustomTools(cfg, options) {
2297
2314
  Object.assign(
2298
2315
  tools,
2299
2316
  createUploadPackageSqlTool({
2300
- cfg,
2317
+ cfg: cfg2,
2301
2318
  workdir: options.workdir,
2302
2319
  projectId
2303
2320
  })
@@ -2320,17 +2337,17 @@ function withWorkspaceBoundaryHint(prompt) {
2320
2337
  ${WORKSPACE_BOUNDARY_HINT}`;
2321
2338
  }
2322
2339
 
2323
- // src/commands/connect/pty-session-mcp.ts
2340
+ // src/commands/connect/mcps/pty-session-mcp.ts
2324
2341
  var PTY_SESSION_MCP_NAME = "pty-session";
2325
2342
  var DEFAULT_PTY_SESSION_PORT = "7788";
2326
2343
  function resolvePtySessionBaseUrl(env = process.env) {
2327
2344
  const explicit = env.LOCAL_TERMINAL_URL?.trim();
2328
2345
  if (explicit) return explicit.replace(/\/+$/, "");
2329
- const port = env.LOCAL_TERMINAL_PORT?.trim() || DEFAULT_PTY_SESSION_PORT;
2330
- return `http://127.0.0.1:${port}`;
2346
+ const port2 = env.LOCAL_TERMINAL_PORT?.trim() || DEFAULT_PTY_SESSION_PORT;
2347
+ return `http://127.0.0.1:${port2}`;
2331
2348
  }
2332
- function createPtySessionMcpServers(cfg) {
2333
- const token = resolveApiKey(cfg);
2349
+ function createPtySessionMcpServers(cfg2) {
2350
+ const token = resolveApiKey(cfg2);
2334
2351
  if (!token) return void 0;
2335
2352
  return {
2336
2353
  [PTY_SESSION_MCP_NAME]: {
@@ -2404,7 +2421,7 @@ async function obtainAgent(ctx) {
2404
2421
  throw err;
2405
2422
  }
2406
2423
  }
2407
- async function runCursorAgent(cfg, ctx, options) {
2424
+ async function runCursorAgent(cfg2, ctx, options) {
2408
2425
  const signal = options.signal;
2409
2426
  if (signal?.aborted) {
2410
2427
  throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
@@ -2414,7 +2431,7 @@ async function runCursorAgent(cfg, ctx, options) {
2414
2431
  throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
2415
2432
  }
2416
2433
  const workdir = resolveWorkdirPath(ctx.workdir);
2417
- const customTools = createCursorCustomTools(cfg, {
2434
+ const customTools = createCursorCustomTools(cfg2, {
2418
2435
  onAskQuestion: options.onAskQuestion,
2419
2436
  appendMessageContent: options.appendMessageContent,
2420
2437
  enableAppendMessage: options.enableAppendMessage,
@@ -2431,7 +2448,7 @@ async function runCursorAgent(cfg, ctx, options) {
2431
2448
  onPackageFailed: options.onPackageFailed
2432
2449
  });
2433
2450
  const enableSandbox = Boolean(options.enableSandbox);
2434
- const mcpServers = options.enablePtySessionMcp ? createPtySessionMcpServers(cfg) : void 0;
2451
+ const mcpServers = options.enablePtySessionMcp ? createPtySessionMcpServers(cfg2) : void 0;
2435
2452
  let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
2436
2453
  prompt = withWorkspaceBoundaryHint(prompt);
2437
2454
  console.log(
@@ -2560,265 +2577,6 @@ async function runCursorAgent(cfg, ctx, options) {
2560
2577
  }
2561
2578
  }
2562
2579
 
2563
- // src/commands/connect/webide-agent-registry.ts
2564
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2565
- import { dirname as dirname3, resolve as resolve6 } from "node:path";
2566
- function registryPath(workdir, taskId) {
2567
- return resolve6(workdir, ".apm", "webide", taskId, "cursor-agent.json");
2568
- }
2569
- function readRegistry(path3) {
2570
- if (!existsSync5(path3)) {
2571
- return {};
2572
- }
2573
- try {
2574
- const parsed = JSON.parse(readFileSync4(path3, "utf8"));
2575
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2576
- const raw = parsed;
2577
- const state = {};
2578
- if (typeof raw.agentId === "string" && raw.agentId.trim()) {
2579
- state.agentId = raw.agentId.trim();
2580
- }
2581
- if (typeof raw.taskId === "string" && raw.taskId.trim()) {
2582
- state.taskId = raw.taskId.trim();
2583
- }
2584
- if (typeof raw.messageId === "string" && raw.messageId.trim()) {
2585
- state.messageId = raw.messageId.trim();
2586
- }
2587
- if (typeof raw.action === "string" && raw.action.trim()) {
2588
- state.action = raw.action.trim();
2589
- }
2590
- if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
2591
- state.status = raw.status;
2592
- }
2593
- if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
2594
- state.updatedAt = raw.updatedAt.trim();
2595
- }
2596
- return state;
2597
- }
2598
- } catch {
2599
- }
2600
- return {};
2601
- }
2602
- function writeRegistry(path3, registry) {
2603
- mkdirSync4(dirname3(path3), { recursive: true });
2604
- writeFileSync3(path3, `${JSON.stringify(registry, null, 2)}
2605
- `, "utf8");
2606
- }
2607
- function syncWebIdeTaskState(workdir, taskId, patch) {
2608
- const trimmedTaskId = taskId.trim();
2609
- if (!trimmedTaskId) return;
2610
- const path3 = registryPath(workdir, trimmedTaskId);
2611
- const current = readRegistry(path3);
2612
- const next = {
2613
- ...current,
2614
- taskId: trimmedTaskId,
2615
- updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
2616
- };
2617
- if (patch.agentId !== void 0) {
2618
- const agentId = patch.agentId.trim();
2619
- if (agentId) next.agentId = agentId;
2620
- else delete next.agentId;
2621
- }
2622
- if (patch.messageId !== void 0) {
2623
- const messageId = patch.messageId.trim();
2624
- if (messageId) next.messageId = messageId;
2625
- else delete next.messageId;
2626
- }
2627
- if (patch.action !== void 0) {
2628
- const action = patch.action.trim();
2629
- if (action) next.action = action;
2630
- else delete next.action;
2631
- }
2632
- if (patch.status !== void 0) {
2633
- next.status = patch.status;
2634
- }
2635
- writeRegistry(path3, next);
2636
- }
2637
- function loadWebIdeAgentId(workdir, taskId) {
2638
- return readRegistry(registryPath(workdir, taskId)).agentId;
2639
- }
2640
- function saveWebIdeAgentId(workdir, taskId, agentId) {
2641
- syncWebIdeTaskState(workdir, taskId, { agentId });
2642
- }
2643
- function clearWebIdeAgentId(workdir, taskId) {
2644
- const path3 = registryPath(workdir, taskId);
2645
- if (!existsSync5(path3)) return;
2646
- syncWebIdeTaskState(workdir, taskId, { agentId: "" });
2647
- }
2648
-
2649
- // src/commands/clean-webide-cache.ts
2650
- import { existsSync as existsSync6, rmSync } from "node:fs";
2651
- import { resolve as resolve7 } from "node:path";
2652
- import { getDefaultSdkStateRoot } from "@cursor/sdk";
2653
- async function purgeCursorAgentStoreForAgent(workdir, agentId) {
2654
- const trimmedAgentId = agentId.trim();
2655
- const trimmedWorkdir = workdir.trim();
2656
- if (!trimmedAgentId || !trimmedWorkdir) return false;
2657
- const stateRoot = getDefaultSdkStateRoot(trimmedWorkdir);
2658
- if (!existsSync6(stateRoot)) return false;
2659
- const { SqliteLocalAgentStore } = await import(
2660
- /* @vite-ignore */
2661
- "@cursor/sdk/sqlite"
2662
- );
2663
- const store = await SqliteLocalAgentStore.open({
2664
- workspaceRef: trimmedWorkdir
2665
- });
2666
- try {
2667
- const runIds = [];
2668
- let cursor;
2669
- do {
2670
- const page = await store.runs.list({
2671
- filter: {
2672
- agentIds: [trimmedAgentId],
2673
- ...cursor ? { cursor } : {},
2674
- limit: 200
2675
- }
2676
- });
2677
- for (const run of page.items) {
2678
- runIds.push(run.runId);
2679
- }
2680
- cursor = page.nextCursor;
2681
- } while (cursor);
2682
- if (runIds.length > 0) {
2683
- await store.runEvents.delete({ filter: { runIds } });
2684
- }
2685
- await store.runs.delete({ filter: { agentIds: [trimmedAgentId] } });
2686
- await store.checkpoints.delete({ filter: { agentIds: [trimmedAgentId] } });
2687
- await store.agents.delete({ filter: { agentIds: [trimmedAgentId] } });
2688
- } finally {
2689
- await store.dispose();
2690
- }
2691
- return true;
2692
- }
2693
- async function cleanWebIdeWorkspaceCache(taskId, workdir) {
2694
- const trimmedTaskId = taskId.trim();
2695
- const trimmedWorkdir = workdir.trim();
2696
- if (!trimmedTaskId || !trimmedWorkdir) {
2697
- return;
2698
- }
2699
- const agentId = loadWebIdeAgentId(trimmedWorkdir, trimmedTaskId);
2700
- if (agentId) {
2701
- try {
2702
- const purged = await purgeCursorAgentStoreForAgent(
2703
- trimmedWorkdir,
2704
- agentId
2705
- );
2706
- if (purged) {
2707
- console.log(
2708
- `[apm] \u5DF2\u6E05\u7406 Cursor agent store agentId=${agentId} taskId=${trimmedTaskId}`
2709
- );
2710
- }
2711
- } catch (err) {
2712
- console.warn(
2713
- `[apm] \u6E05\u7406 Cursor agent store \u5931\u8D25 agentId=${agentId}:`,
2714
- err instanceof Error ? err.message : err
2715
- );
2716
- }
2717
- } else {
2718
- console.log(
2719
- `[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
2720
- );
2721
- }
2722
- const dir = resolve7(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
2723
- if (existsSync6(dir)) {
2724
- rmSync(dir, { recursive: true, force: true });
2725
- console.log(`[apm] \u5DF2\u6E05\u7406 WebIDE \u7F13\u5B58 ${dir}`);
2726
- } else {
2727
- console.log(`[apm] WebIDE \u7F13\u5B58\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7 ${dir}`);
2728
- }
2729
- }
2730
-
2731
- // src/commands/connect/webide-ask-question.ts
2732
- import { setTimeout as delay } from "node:timers/promises";
2733
- var POLL_INTERVAL_MS = 2e3;
2734
- function asString5(value) {
2735
- return typeof value === "string" ? value.trim() : "";
2736
- }
2737
- function parseQuestions(args) {
2738
- const title = asString5(args.title) || void 0;
2739
- const raw = args.questions;
2740
- if (!Array.isArray(raw) || raw.length === 0) {
2741
- throw new Error("AskQuestion \u7F3A\u5C11 questions");
2742
- }
2743
- const questions = [];
2744
- for (const item of raw) {
2745
- if (!item || typeof item !== "object" || Array.isArray(item)) continue;
2746
- const row = item;
2747
- const id = asString5(row.id);
2748
- const prompt = asString5(row.prompt);
2749
- const optionsRaw = row.options;
2750
- if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
2751
- const options = [];
2752
- for (const opt of optionsRaw) {
2753
- if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
2754
- const o = opt;
2755
- const oid = asString5(o.id);
2756
- const label = asString5(o.label);
2757
- if (oid && label) options.push({ id: oid, label });
2758
- }
2759
- if (options.length < 2) {
2760
- throw new Error(`AskQuestion \u95EE\u9898 ${id} \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`);
2761
- }
2762
- questions.push({ id, prompt, options });
2763
- }
2764
- if (questions.length === 0) {
2765
- throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
2766
- }
2767
- return { title, questions };
2768
- }
2769
- function createWebIdeAskQuestionExecute(options) {
2770
- const { cfg, taskId, signal } = options;
2771
- return async (args) => {
2772
- const parsed = parseQuestions(args);
2773
- const api = createApmApiClient(cfg);
2774
- console.log(
2775
- `[apm] AskQuestion \u521B\u5EFA\u5F85\u786E\u8BA4\u9879 taskId=${taskId} count=${parsed.questions.length}`
2776
- );
2777
- await api.cli.webideReplaceAssumptions({
2778
- taskId,
2779
- title: parsed.title,
2780
- questions: parsed.questions
2781
- });
2782
- while (true) {
2783
- if (signal?.aborted) {
2784
- throw new Error("AskQuestion \u5DF2\u53D6\u6D88");
2785
- }
2786
- const list = await api.cli.webideListAssumptions({ taskId });
2787
- const byId = new Map(
2788
- list.assumptions.map((a) => [a.questionId, a])
2789
- );
2790
- const pending = parsed.questions.filter((q) => {
2791
- const row = byId.get(q.id);
2792
- return !row || row.status !== "RESOLVED";
2793
- });
2794
- if (pending.length === 0) {
2795
- const answers = parsed.questions.map((q) => {
2796
- const row = byId.get(q.id);
2797
- return {
2798
- questionId: q.id,
2799
- prompt: q.prompt,
2800
- selectedOptionId: row.selectedOptionId,
2801
- customAnswer: row.customAnswer,
2802
- resolution: row.resolution
2803
- };
2804
- });
2805
- console.log(
2806
- `[apm] AskQuestion \u7528\u6237\u5DF2\u786E\u8BA4 taskId=${taskId} count=${answers.length}`
2807
- );
2808
- return JSON.stringify(
2809
- {
2810
- title: parsed.title,
2811
- answers
2812
- },
2813
- null,
2814
- 2
2815
- );
2816
- }
2817
- await delay(POLL_INTERVAL_MS);
2818
- }
2819
- };
2820
- }
2821
-
2822
2580
  // src/commands/connect/webide-message-log.ts
2823
2581
  var SYNC_INTERVAL_MS = 2e3;
2824
2582
  function webIdeEventsObjectPrefix(taskId, messageId) {
@@ -2840,8 +2598,8 @@ async function putDirtyEvents(minio, bucket, prefix, events) {
2840
2598
  });
2841
2599
  }
2842
2600
  }
2843
- async function upsertLogHeader(cfg, ctx, patch, objectPrefix) {
2844
- const api = createApmApiClient(cfg);
2601
+ async function upsertLogHeader(cfg2, ctx, patch, objectPrefix) {
2602
+ const api = createApmApiClient(cfg2);
2845
2603
  await api.cli.webideUpsertMessageLog({
2846
2604
  messageId: ctx.messageId,
2847
2605
  agentId: ctx.agentId,
@@ -2849,7 +2607,7 @@ async function upsertLogHeader(cfg, ctx, patch, objectPrefix) {
2849
2607
  ...patch
2850
2608
  });
2851
2609
  }
2852
- function createThrottledWebIdeMessageLogSync(cfg, ctx, onError, options) {
2610
+ function createThrottledWebIdeMessageLogSync(cfg2, ctx, onError, options) {
2853
2611
  let lastRunAt = 0;
2854
2612
  let timer;
2855
2613
  let latestSession;
@@ -2860,7 +2618,7 @@ function createThrottledWebIdeMessageLogSync(cfg, ctx, onError, options) {
2860
2618
  const resolvePrefix = () => options?.objectPrefixOverride?.trim() || webIdeEventsObjectPrefix(ctx.taskId, ctx.messageId);
2861
2619
  const ensureMinio = async () => {
2862
2620
  if (minio) return;
2863
- const created = await createApmLogMinioClient(cfg);
2621
+ const created = await createApmLogMinioClient(cfg2);
2864
2622
  minio = created.client;
2865
2623
  bucket = created.bucket;
2866
2624
  await minio.ensureBucket(bucket);
@@ -2884,7 +2642,7 @@ function createThrottledWebIdeMessageLogSync(cfg, ctx, onError, options) {
2884
2642
  const prefix = resolvePrefix();
2885
2643
  await putDirtyEvents(minio, bucket, prefix, events);
2886
2644
  await upsertLogHeader(
2887
- cfg,
2645
+ cfg2,
2888
2646
  ctx,
2889
2647
  {
2890
2648
  eventCount: session.getEventCount()
@@ -2909,7 +2667,7 @@ function createThrottledWebIdeMessageLogSync(cfg, ctx, onError, options) {
2909
2667
  async markRun(runId, runStatus, lastError, tokenUsage) {
2910
2668
  try {
2911
2669
  await upsertLogHeader(
2912
- cfg,
2670
+ cfg2,
2913
2671
  ctx,
2914
2672
  {
2915
2673
  runId,
@@ -2952,22 +2710,6 @@ function createThrottledWebIdeMessageLogSync(cfg, ctx, onError, options) {
2952
2710
  };
2953
2711
  }
2954
2712
 
2955
- // src/commands/connect/ensure-message-reply.ts
2956
- var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
2957
- function resolveMessageReplyFallback(fallback) {
2958
- for (const candidate of [
2959
- fallback.assistantText,
2960
- fallback.result,
2961
- fallback.createPlan
2962
- ]) {
2963
- const trimmed = candidate?.trim();
2964
- if (trimmed) {
2965
- return trimmed;
2966
- }
2967
- }
2968
- return DEFAULT_REPLY;
2969
- }
2970
-
2971
2713
  // src/commands/init.ts
2972
2714
  async function initializeWorkspace(workdir) {
2973
2715
  await ensureWorkspaceApmDirForInit(workdir);
@@ -2986,240 +2728,282 @@ async function ensureWorkspaceInitialized(workdir) {
2986
2728
  return initializeWorkspace(workdir);
2987
2729
  }
2988
2730
 
2989
- // src/commands/connect/pre-step-cache.ts
2990
- function taskWorkdirKey(taskId, workdir) {
2991
- return `${taskId}\0${workdir}`;
2992
- }
2993
- var lastBranchKey = null;
2994
- function shouldRunBranch(taskId, workdir) {
2995
- return lastBranchKey !== taskWorkdirKey(taskId, workdir);
2996
- }
2997
- function markBranchDone(taskId, workdir) {
2998
- lastBranchKey = taskWorkdirKey(taskId, workdir);
2999
- }
3000
-
3001
- // src/commands/connect/webide-pull-requests.ts
3002
- var PRE_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set([
3003
- "generate-cases",
3004
- "run-auto-test",
3005
- // 验收合并前兜底确保 PR 存在
3006
- "accept"
3007
- ]);
3008
- var POST_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set(["create-pr", "finish-manual-test"]);
3009
- function shouldEnsureWebIdePullRequests(action) {
3010
- return PRE_AGENT_PR_ACTIONS.has(action);
2731
+ // src/utils/project-documents.ts
2732
+ import {
2733
+ existsSync as existsSync5,
2734
+ readdirSync as readdirSync3,
2735
+ readFileSync as readFileSync4,
2736
+ rmSync,
2737
+ writeFileSync as writeFileSync3
2738
+ } from "fs";
2739
+ import { createHash as createHash2 } from "crypto";
2740
+ import { dirname as dirname3, join as join4, relative as relative4, sep } from "path";
2741
+ var MANIFEST_FILE = "manifest.json";
2742
+ function normalizeProjectIdForPath(projectId) {
2743
+ const id = projectId.trim();
2744
+ if (!id) {
2745
+ throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
2746
+ }
2747
+ if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
2748
+ throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
2749
+ }
2750
+ return id;
3011
2751
  }
3012
- function shouldEnsureWebIdePullRequestsAfterAgent(action) {
3013
- return POST_AGENT_PR_ACTIONS.has(action);
2752
+ function projectDocumentsDir(apmRoot, projectId) {
2753
+ const id = normalizeProjectIdForPath(projectId);
2754
+ return join4(apmRoot ?? workspaceApmDir(), "project", id);
3014
2755
  }
3015
- function formatWebIdePullRequestSummary(result) {
3016
- const lines = [];
3017
- lines.push("## PR \u521B\u5EFA\u7ED3\u679C");
3018
- lines.push(
3019
- `\u5408\u8BA1\uFF1A\u6210\u529F ${result.ensured} \u6761\uFF0C\u65E0\u53D8\u66F4\u8DF3\u8FC7 ${result.skippedNoChanges} \u6761\uFF0C\u5176\u4ED6\u8DF3\u8FC7 ${result.skipped} \u6761\uFF0C\u5931\u8D25 ${result.failed} \u6761\u3002`
2756
+ function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
2757
+ const normalized = normalizeLocalDocumentPath(documentPath);
2758
+ return join4(
2759
+ projectDocumentsDir(apmRoot, projectId),
2760
+ ...normalized.split("/")
3020
2761
  );
3021
- if (result.items.length === 0) {
3022
- lines.push("\u672A\u5904\u7406\u4EFB\u4F55\u4ED3\u5E93\u3002");
3023
- return lines.join("\n");
2762
+ }
2763
+ function normalizeLocalDocumentPath(path3) {
2764
+ const trimmed = path3.trim().replace(/\\/g, "/");
2765
+ if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
2766
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path3}`);
3024
2767
  }
3025
- lines.push("");
3026
- for (const item of result.items) {
3027
- if (item.outcome === "created") {
3028
- const num = item.number != null && item.number > 0 ? `#${item.number}` : "\uFF08\u65E0\u7F16\u53F7\uFF09";
3029
- const branch = item.headBranch && item.baseBranch ? `${item.headBranch} \u2192 ${item.baseBranch}` : item.baseBranch ? `\u76EE\u6807\u5206\u652F ${item.baseBranch}` : "";
3030
- const state = item.state ? `\u72B6\u6001 ${item.state}` : "";
3031
- const url = item.url?.trim() ? item.url.trim() : "";
3032
- lines.push(
3033
- `- **${item.label}**\uFF1A\u5DF2\u521B\u5EFA ${num}${branch ? `\uFF0C${branch}` : ""}${state ? `\uFF0C${state}` : ""}${url ? `\uFF0C${url}` : ""}`
3034
- );
3035
- continue;
2768
+ const segments = trimmed.split("/").filter(Boolean);
2769
+ if (segments.some((segment) => segment === ".." || segment === ".")) {
2770
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path3}`);
2771
+ }
2772
+ return segments.join("/");
2773
+ }
2774
+ function hashLocalFileContent(content) {
2775
+ return createHash2("sha256").update(content, "utf8").digest("hex");
2776
+ }
2777
+ function readLocalManifest(apmRoot, projectId) {
2778
+ const manifestPath3 = join4(
2779
+ projectDocumentsDir(apmRoot, projectId),
2780
+ MANIFEST_FILE
2781
+ );
2782
+ if (!existsSync5(manifestPath3)) {
2783
+ return null;
2784
+ }
2785
+ try {
2786
+ return JSON.parse(
2787
+ readFileSync4(manifestPath3, "utf8")
2788
+ );
2789
+ } catch {
2790
+ return null;
2791
+ }
2792
+ }
2793
+ function listLocalDocumentPaths(apmRoot, projectId) {
2794
+ const root = projectDocumentsDir(apmRoot, projectId);
2795
+ if (!existsSync5(root)) {
2796
+ return [];
2797
+ }
2798
+ const paths = [];
2799
+ const walk = (dir) => {
2800
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
2801
+ const abs = join4(dir, entry.name);
2802
+ if (entry.isDirectory()) {
2803
+ walk(abs);
2804
+ continue;
2805
+ }
2806
+ if (entry.isFile() && entry.name === MANIFEST_FILE) {
2807
+ continue;
2808
+ }
2809
+ const rel = relative4(root, abs).split(sep).join("/");
2810
+ paths.push(rel);
3036
2811
  }
3037
- if (item.outcome === "skipped_no_changes") {
3038
- lines.push(
3039
- `- **${item.label}**\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${item.baseBranch || "\uFF08\u672A\u77E5\uFF09"} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u672A\u521B\u5EFA PR`
3040
- );
3041
- continue;
2812
+ };
2813
+ walk(root);
2814
+ return paths.sort();
2815
+ }
2816
+ function diffManifestPaths(remote, local) {
2817
+ const remoteMap = new Map(
2818
+ (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
2819
+ );
2820
+ const localMap = new Map(
2821
+ (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
2822
+ );
2823
+ const download = [];
2824
+ for (const [path3, hash] of remoteMap) {
2825
+ if (localMap.get(path3) !== hash) {
2826
+ download.push(path3);
3042
2827
  }
3043
- if (item.outcome === "skipped") {
3044
- lines.push(`- **${item.label}**\uFF1A\u5DF2\u8DF3\u8FC7 \u2014 ${item.detail}`);
3045
- continue;
2828
+ }
2829
+ const deleteLocal = [];
2830
+ for (const path3 of localMap.keys()) {
2831
+ if (!remoteMap.has(path3)) {
2832
+ deleteLocal.push(path3);
3046
2833
  }
3047
- lines.push(`- **${item.label}**\uFF1A\u521B\u5EFA\u5931\u8D25 \u2014 ${item.detail}`);
3048
2834
  }
3049
- return lines.join("\n");
2835
+ return { download, deleteLocal };
3050
2836
  }
3051
- async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3052
- const api = createApmApiClient(cfg);
3053
- const empty = () => ({
3054
- ensured: 0,
3055
- skipped: 0,
3056
- skippedNoChanges: 0,
3057
- failed: 0,
3058
- items: [],
3059
- tips: []
3060
- });
3061
- let repoRoots;
3062
- try {
3063
- const manifest = loadWorkspaceReposCache(workdir);
3064
- repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
3065
- } catch (err) {
3066
- const tip = `\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25: ${err instanceof Error ? err.message : err}`;
3067
- console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3068
- return {
3069
- ...empty(),
3070
- failed: 1,
3071
- tips: [tip],
3072
- items: [
3073
- {
3074
- label: "\u5DE5\u4F5C\u533A",
3075
- outcome: "failed",
3076
- detail: tip
3077
- }
3078
- ]
3079
- };
2837
+ async function resolveProjectIdForSync(api, workdirPath, options) {
2838
+ const explicit = options?.projectId?.trim();
2839
+ if (explicit) {
2840
+ return { projectId: explicit, diagnostic: null };
3080
2841
  }
3081
- let projectBaseBranch = "";
3082
2842
  try {
2843
+ const gitRoot = await resolveGitRepoRoot(workdirPath);
2844
+ const current = await getCurrentBranch(gitRoot);
2845
+ const taskId = taskIdFromFeatureBranch(current);
2846
+ if (!taskId) {
2847
+ return {
2848
+ projectId: null,
2849
+ diagnostic: `\u5F53\u524D\u5206\u652F ${current} \u4E0D\u662F feat/task-*\uFF0C\u65E0\u6CD5\u89E3\u6790\u6240\u5C5E\u9879\u76EE\u3002
2850
+ \u8BF7\u5728\u7279\u6027\u5206\u652F\u4E0A\u6267\u884C\uFF0C\u6216\u663E\u5F0F\u6307\u5B9A projectId\u3002`
2851
+ };
2852
+ }
3083
2853
  const baseline = await api.cli.projectBaseBranch({ taskId });
3084
- projectBaseBranch = baseline.baseBranch?.trim() ?? "";
2854
+ const projectId = baseline.projectId?.trim() || null;
2855
+ if (!projectId) {
2856
+ return {
2857
+ projectId: null,
2858
+ diagnostic: `\u4EFB\u52A1 ${taskId} \u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
2859
+ };
2860
+ }
2861
+ return { projectId, diagnostic: null };
3085
2862
  } catch (err) {
3086
- const tip = `\u8BFB\u53D6\u9879\u76EE\u57FA\u7EBF\u5206\u652F\u5931\u8D25: ${err instanceof Error ? err.message : err}`;
3087
- console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
2863
+ const detail = err instanceof Error ? err.message : String(err);
3088
2864
  return {
3089
- ...empty(),
3090
- failed: 1,
3091
- tips: [tip],
3092
- items: [{ label: "\u5DE5\u4F5C\u533A", outcome: "failed", detail: tip }]
2865
+ projectId: null,
2866
+ diagnostic: detail.replace(/^\[apm\]\s*/, "")
3093
2867
  };
3094
2868
  }
3095
- if (!projectBaseBranch) {
3096
- const tip = "\u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F\uFF0C\u65E0\u6CD5\u521B\u5EFA PR";
3097
- console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3098
- return {
3099
- ...empty(),
3100
- failed: 1,
3101
- tips: [tip],
3102
- items: [{ label: "\u5DE5\u4F5C\u533A", outcome: "failed", detail: tip }]
3103
- };
2869
+ }
2870
+ async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
2871
+ const empty = {
2872
+ synced: false,
2873
+ projectId: null,
2874
+ downloaded: 0,
2875
+ deleted: 0
2876
+ };
2877
+ const cfg2 = await tryReadApmConfig();
2878
+ if (!cfg2 || !resolveApiKey(cfg2)) {
2879
+ console.log(
2880
+ "[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
2881
+ );
2882
+ return empty;
3104
2883
  }
3105
- const expectedHead = `feat/task-${taskId}`;
2884
+ const api = createApmApiClient(cfg2);
2885
+ const { projectId, diagnostic } = await resolveProjectIdForSync(
2886
+ api,
2887
+ workdirPath,
2888
+ options
2889
+ );
2890
+ if (!projectId) {
2891
+ console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
2892
+ ${diagnostic ?? ""}`);
2893
+ return empty;
2894
+ }
2895
+ const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
2896
+ const docsDir = projectDocumentsDir(targetApmDir, projectId);
2897
+ await ensureDirExists(docsDir);
2898
+ const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
2899
+ { projectId }
2900
+ );
2901
+ if (!remoteManifest) {
2902
+ console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
2903
+ return { ...empty, projectId };
2904
+ }
2905
+ const localManifest = readLocalManifest(targetApmDir, projectId);
2906
+ const { download, deleteLocal } = diffManifestPaths(
2907
+ remoteManifest,
2908
+ localManifest
2909
+ );
2910
+ let downloaded = 0;
2911
+ if (download.length > 0) {
2912
+ const { list } = await api.cli.listProjectDocuments({
2913
+ projectId,
2914
+ paths: download.join(",")
2915
+ });
2916
+ for (const doc of list) {
2917
+ const absPath = toFsPath(
2918
+ projectDocumentLocalPath(targetApmDir, projectId, doc.path)
2919
+ );
2920
+ await ensureDirExists(dirname3(absPath));
2921
+ writeFileSync3(absPath, doc.content, "utf8");
2922
+ downloaded += 1;
2923
+ }
2924
+ }
2925
+ let deleted = 0;
2926
+ for (const path3 of deleteLocal) {
2927
+ const absPath = toFsPath(
2928
+ projectDocumentLocalPath(targetApmDir, projectId, path3)
2929
+ );
2930
+ if (existsSync5(absPath)) {
2931
+ rmSync(absPath, { force: true });
2932
+ deleted += 1;
2933
+ }
2934
+ }
2935
+ writeFileSync3(
2936
+ toFsPath(join4(docsDir, MANIFEST_FILE)),
2937
+ `${JSON.stringify(remoteManifest, null, 2)}
2938
+ `,
2939
+ "utf8"
2940
+ );
3106
2941
  console.log(
3107
- `[apm] WebIDE PR\uFF1A\u51C6\u5907\u68C0\u67E5 ${repoRoots.length} \u4E2A\u4ED3\u5E93\uFF08\u671F\u671B\u7279\u6027\u5206\u652F ${expectedHead}\uFF0C\u9879\u76EE\u57FA\u7EBF ${projectBaseBranch}\uFF09`
2942
+ `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
3108
2943
  );
3109
- let ensured = 0;
3110
- let skipped = 0;
3111
- let skippedNoChanges = 0;
3112
- let failed = 0;
3113
- const tips = [];
3114
- const items = [];
3115
- for (const gitRoot of repoRoots) {
3116
- const label = formatRepoLabel(workdir, gitRoot);
3117
- try {
3118
- const current = await getCurrentBranch(gitRoot);
3119
- if (!isFeatureBranchName(current)) {
3120
- const tip = `${label}\uFF1A\u5F53\u524D\u5206\u652F ${current} \u4E0D\u662F feat/task-*\uFF0C\u4E0D\u7B26\u5408\u521B\u5EFA PR \u6761\u4EF6`;
3121
- console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3122
- tips.push(tip);
3123
- skipped += 1;
3124
- items.push({
3125
- label,
3126
- outcome: "skipped",
3127
- headBranch: current,
3128
- detail: tip
3129
- });
3130
- continue;
3131
- }
3132
- if (current !== expectedHead) {
3133
- const tip = `${label}\uFF1A\u5F53\u524D\u4E3A ${current}\uFF0C\u671F\u671B ${expectedHead}\uFF0C\u5DF2\u8DF3\u8FC7`;
3134
- console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3135
- tips.push(tip);
3136
- skipped += 1;
3137
- items.push({
3138
- label,
3139
- outcome: "skipped",
3140
- headBranch: current,
3141
- detail: tip
3142
- });
3143
- continue;
3144
- }
3145
- const headBranch = current;
3146
- const originUrl = await tryReadHttpsGitOriginUrl(gitRoot) ?? await tryReadGitOriginUrl(gitRoot);
3147
- if (!originUrl) {
3148
- const tip = `${label}\uFF1A\u65E0 remote.origin.url`;
3149
- console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${tip}`);
3150
- tips.push(tip);
3151
- skipped += 1;
3152
- items.push({
3153
- label,
3154
- outcome: "skipped",
3155
- headBranch,
3156
- detail: tip
3157
- });
3158
- continue;
3159
- }
3160
- const baseBranch = projectBaseBranch;
3161
- const hasChanges = await branchHasChangesAgainstBase(
3162
- gitRoot,
3163
- baseBranch,
3164
- headBranch
3165
- );
3166
- if (!hasChanges) {
3167
- const tip = `${label}\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${baseBranch} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u65E0\u9700\u521B\u5EFA PR`;
3168
- console.log(`[apm] WebIDE PR\uFF1A${tip}`);
3169
- tips.push(tip);
3170
- skippedNoChanges += 1;
3171
- items.push({
3172
- label,
3173
- outcome: "skipped_no_changes",
3174
- headBranch,
3175
- baseBranch,
3176
- detail: tip
3177
- });
3178
- continue;
3179
- }
3180
- const pr = await api.cli.createWebIdeDraftPullRequest({
3181
- taskId,
3182
- originUrl,
3183
- headBranch,
3184
- baseBranch
3185
- });
3186
- ensured += 1;
3187
- items.push({
3188
- label,
3189
- outcome: "created",
3190
- headBranch,
3191
- baseBranch,
3192
- number: pr.number,
3193
- state: pr.state,
3194
- url: pr.url,
3195
- detail: `\u5DF2\u521B\u5EFA #${pr.number} ${headBranch} \u2192 ${baseBranch}`
3196
- });
3197
- console.log(
3198
- `[apm] WebIDE PR\uFF1A${label} \u2192 #${pr.number} ${pr.state} ${headBranch} \u2192 ${baseBranch} ${pr.url || ""}`
3199
- );
3200
- } catch (err) {
3201
- failed += 1;
3202
- const tip = `${label}\uFF1A${err instanceof Error ? err.message : err}`;
3203
- console.warn(`[apm] WebIDE PR\uFF1A\u5931\u8D25 ${tip}`);
3204
- tips.push(tip);
3205
- items.push({
3206
- label,
3207
- outcome: "failed",
3208
- detail: tip
3209
- });
2944
+ return {
2945
+ synced: true,
2946
+ projectId,
2947
+ downloaded,
2948
+ deleted
2949
+ };
2950
+ }
2951
+ async function syncProjectDocumentsPush(cfg2, workdirPath, apmRoot, options) {
2952
+ const api = createApmApiClient(cfg2);
2953
+ const { projectId } = await resolveProjectIdForSync(
2954
+ api,
2955
+ workdirPath,
2956
+ options
2957
+ );
2958
+ if (!projectId) {
2959
+ return 0;
2960
+ }
2961
+ const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
2962
+ const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
2963
+ if (localPaths.length === 0) {
2964
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
2965
+ return 0;
2966
+ }
2967
+ const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
2968
+ const remoteHashByPath = new Map(
2969
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
2970
+ );
2971
+ const remoteDescriptionByPath = new Map(
2972
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
2973
+ );
2974
+ let synced = 0;
2975
+ for (const path3 of localPaths) {
2976
+ const absPath = toFsPath(
2977
+ projectDocumentLocalPath(targetApmDir, projectId, path3)
2978
+ );
2979
+ const content = readFileSync4(absPath, "utf8");
2980
+ const contentHash = hashLocalFileContent(content);
2981
+ if (remoteHashByPath.get(path3) === contentHash) {
2982
+ continue;
3210
2983
  }
2984
+ await api.cli.upsertProjectDocument({
2985
+ projectId,
2986
+ path: path3,
2987
+ content,
2988
+ description: remoteDescriptionByPath.get(path3) ?? void 0
2989
+ });
2990
+ synced += 1;
2991
+ console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path3}`);
3211
2992
  }
3212
- return { ensured, skipped, skippedNoChanges, failed, items, tips };
2993
+ if (synced === 0) {
2994
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
2995
+ }
2996
+ return synced;
3213
2997
  }
3214
2998
 
3215
2999
  // src/version.ts
3216
3000
  import { readFileSync as readFileSync5 } from "fs";
3217
- import { dirname as dirname4, join as join4 } from "path";
3001
+ import { dirname as dirname4, join as join5 } from "path";
3218
3002
  import { fileURLToPath as fileURLToPath2 } from "url";
3219
3003
  function readCliVersion() {
3220
3004
  try {
3221
3005
  const dir = dirname4(fileURLToPath2(import.meta.url));
3222
- const pkgPath = join4(dir, "..", "package.json");
3006
+ const pkgPath = join5(dir, "..", "package.json");
3223
3007
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
3224
3008
  return pkg.version ?? "0.0.0";
3225
3009
  } catch {
@@ -3227,699 +3011,451 @@ function readCliVersion() {
3227
3011
  }
3228
3012
  }
3229
3013
 
3230
- // src/commands/sync-webide-attachments.ts
3231
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3232
- import { join as join5 } from "path";
3233
- var MANIFEST_FILE = ".sync-manifest.json";
3234
- async function downloadAttachment(cfg, attachmentId) {
3235
- const base = cfg.baseUrl.trim().replace(/\/+$/, "");
3236
- const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
3237
- attachmentId
3238
- })}`;
3239
- const res = await fetch(url);
3240
- if (!res.ok) {
3241
- throw new Error(
3242
- `[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
3243
- );
3244
- }
3245
- return Buffer.from(await res.arrayBuffer());
3014
+ // src/commands/connect/cli-version-sync.ts
3015
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3016
+ import { join as join6 } from "path";
3017
+ var CLI_VERSION_FILE = ".cli-version.json";
3018
+ function manifestPath2(apmDir) {
3019
+ return join6(apmDir, CLI_VERSION_FILE);
3246
3020
  }
3247
- function loadManifest(dir) {
3248
- const path3 = join5(dir, MANIFEST_FILE);
3249
- if (!existsSync7(path3)) {
3250
- return { version: 1, attachments: {} };
3021
+ function loadManifest(apmDir) {
3022
+ const path3 = toFsPath(manifestPath2(apmDir));
3023
+ if (!existsSync6(path3)) {
3024
+ return null;
3251
3025
  }
3252
3026
  try {
3253
3027
  const parsed = JSON.parse(
3254
3028
  readFileSync6(path3, "utf8")
3255
3029
  );
3256
- if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
3030
+ if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
3257
3031
  return parsed;
3258
3032
  }
3259
3033
  } catch {
3260
3034
  }
3261
- return { version: 1, attachments: {} };
3035
+ return null;
3262
3036
  }
3263
- function saveManifest(dir, manifest) {
3037
+ function saveManifest(apmDir, cliVersion) {
3038
+ const manifest = { version: 1, cliVersion };
3264
3039
  writeFileSync4(
3265
- join5(dir, MANIFEST_FILE),
3040
+ toFsPath(manifestPath2(apmDir)),
3266
3041
  `${JSON.stringify(manifest, null, 2)}
3267
3042
  `,
3268
3043
  "utf8"
3269
3044
  );
3270
3045
  }
3271
- function isAttachmentUpToDate(entry, item, dest) {
3272
- if (!entry || !existsSync7(dest)) return false;
3273
- if (entry.name !== item.name) return false;
3274
- const createdAt = item.createdAt ?? "";
3275
- return entry.createdAt === createdAt;
3276
- }
3277
- async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
3278
- await ensureDirExists(dir);
3279
- if (attachments.length === 0) {
3280
- saveManifest(dir, { version: 1, attachments: {} });
3281
- return [];
3046
+ var syncedInSession = /* @__PURE__ */ new Map();
3047
+ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
3048
+ const cached = syncedInSession.get(workdir);
3049
+ if (cached === currentVersion) {
3050
+ return false;
3282
3051
  }
3283
- const manifest = loadManifest(dir);
3284
- const nextManifest = { version: 1, attachments: {} };
3285
- const names = [];
3286
- for (const item of attachments) {
3287
- const dest = join5(dir, item.name);
3288
- const entry = manifest.attachments[item.id];
3289
- const createdAt = item.createdAt ?? "";
3290
- names.push(item.name);
3291
- if (isAttachmentUpToDate(entry, item, dest)) {
3292
- nextManifest.attachments[item.id] = entry;
3293
- console.log(`[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${logLabel}/${item.name}`);
3294
- continue;
3295
- }
3296
- const buffer = await downloadAttachment(cfg, item.id);
3297
- writeFileSync4(dest, buffer);
3298
- nextManifest.attachments[item.id] = {
3299
- name: item.name,
3300
- createdAt
3301
- };
3302
- console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${logLabel}/${item.name}`);
3052
+ const stored = loadManifest(workspaceApmDir(workdir));
3053
+ if (stored?.cliVersion === currentVersion) {
3054
+ syncedInSession.set(workdir, currentVersion);
3055
+ return false;
3303
3056
  }
3304
- saveManifest(dir, nextManifest);
3305
- return names;
3057
+ return true;
3306
3058
  }
3307
- async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
3308
- const dir = webideAttachmentsDir(taskId, workdir);
3309
- return syncAttachmentsToDirectory(
3310
- cfg,
3311
- attachments,
3312
- dir,
3313
- `.apm/webide/${taskId}/attachments`
3314
- );
3059
+ function markSkillsSyncedForCliVersion(workdir, cliVersion) {
3060
+ saveManifest(workspaceApmDir(workdir), cliVersion);
3061
+ syncedInSession.set(workdir, cliVersion);
3315
3062
  }
3316
3063
 
3317
- // src/utils/project-documents.ts
3318
- import {
3319
- existsSync as existsSync8,
3320
- readdirSync as readdirSync3,
3321
- readFileSync as readFileSync7,
3322
- rmSync as rmSync2,
3323
- writeFileSync as writeFileSync5
3324
- } from "fs";
3325
- import { createHash as createHash2 } from "crypto";
3326
- import { dirname as dirname5, join as join6, relative as relative4, sep } from "path";
3327
- var MANIFEST_FILE2 = "manifest.json";
3328
- function normalizeProjectIdForPath(projectId) {
3329
- const id = projectId.trim();
3330
- if (!id) {
3331
- throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
3332
- }
3333
- if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
3334
- throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
3335
- }
3336
- return id;
3337
- }
3338
- function projectDocumentsDir(apmRoot, projectId) {
3339
- const id = normalizeProjectIdForPath(projectId);
3340
- return join6(apmRoot ?? workspaceApmDir(), "project", id);
3064
+ // src/commands/connect/upload-package-artifact.ts
3065
+ import fs2 from "node:fs";
3066
+ import path2 from "node:path";
3067
+ function packageTmpDir(packageId) {
3068
+ return `/data/package-tmp/${packageId}`;
3341
3069
  }
3342
- function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
3343
- const normalized = normalizeLocalDocumentPath(documentPath);
3344
- return join6(
3345
- projectDocumentsDir(apmRoot, projectId),
3346
- ...normalized.split("/")
3347
- );
3070
+ function expectedLocalArtifactPath(target, packageId) {
3071
+ const fileName = target === "frontend" ? "dist.zip" : "jars.zip";
3072
+ return path2.join(packageTmpDir(packageId), fileName);
3348
3073
  }
3349
- function normalizeLocalDocumentPath(path3) {
3350
- const trimmed = path3.trim().replace(/\\/g, "/");
3351
- if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
3352
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path3}`);
3353
- }
3354
- const segments = trimmed.split("/").filter(Boolean);
3355
- if (segments.some((segment) => segment === ".." || segment === ".")) {
3356
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path3}`);
3357
- }
3358
- return segments.join("/");
3074
+ function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
3075
+ const folder = target === "frontend" ? "vue" : "java";
3076
+ const suffix = target === "frontend" ? "dist.zip" : "jar.zip";
3077
+ return `packages/${projectId}/${folder}/${timestampMs}.${suffix}`;
3359
3078
  }
3360
- function hashLocalFileContent(content) {
3361
- return createHash2("sha256").update(content, "utf8").digest("hex");
3362
- }
3363
- function readLocalManifest(apmRoot, projectId) {
3364
- const manifestPath3 = join6(
3365
- projectDocumentsDir(apmRoot, projectId),
3366
- MANIFEST_FILE2
3367
- );
3368
- if (!existsSync8(manifestPath3)) {
3369
- return null;
3370
- }
3079
+ function removePackageTmpDir(packageId) {
3080
+ const dir = packageTmpDir(packageId);
3371
3081
  try {
3372
- return JSON.parse(
3373
- readFileSync7(manifestPath3, "utf8")
3082
+ fs2.rmSync(dir, { recursive: true, force: true });
3083
+ } catch (err) {
3084
+ console.warn(
3085
+ `[apm] \u6E05\u7406\u6253\u5305\u4E34\u65F6\u76EE\u5F55\u5931\u8D25 ${dir}:`,
3086
+ err instanceof Error ? err.message : err
3374
3087
  );
3375
- } catch {
3376
- return null;
3377
- }
3378
- }
3379
- function listLocalDocumentPaths(apmRoot, projectId) {
3380
- const root = projectDocumentsDir(apmRoot, projectId);
3381
- if (!existsSync8(root)) {
3382
- return [];
3383
3088
  }
3384
- const paths = [];
3385
- const walk = (dir) => {
3386
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
3387
- const abs = join6(dir, entry.name);
3388
- if (entry.isDirectory()) {
3389
- walk(abs);
3390
- continue;
3391
- }
3392
- if (entry.isFile() && entry.name === MANIFEST_FILE2) {
3393
- continue;
3394
- }
3395
- const rel = relative4(root, abs).split(sep).join("/");
3396
- paths.push(rel);
3397
- }
3398
- };
3399
- walk(root);
3400
- return paths.sort();
3401
3089
  }
3402
- function diffManifestPaths(remote, local) {
3403
- const remoteMap = new Map(
3404
- (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3405
- );
3406
- const localMap = new Map(
3407
- (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3408
- );
3409
- const download = [];
3410
- for (const [path3, hash] of remoteMap) {
3411
- if (localMap.get(path3) !== hash) {
3412
- download.push(path3);
3413
- }
3090
+ async function uploadPackageArtifact(cfg2, target, packageId, projectId) {
3091
+ const localPath = expectedLocalArtifactPath(target, packageId);
3092
+ if (!fs2.existsSync(localPath)) {
3093
+ throw new Error(`\u6253\u5305\u4EA7\u7269\u4E0D\u5B58\u5728: ${localPath}`);
3414
3094
  }
3415
- const deleteLocal = [];
3416
- for (const path3 of localMap.keys()) {
3417
- if (!remoteMap.has(path3)) {
3418
- deleteLocal.push(path3);
3419
- }
3095
+ const stat = fs2.statSync(localPath);
3096
+ if (!stat.isFile() || stat.size <= 0) {
3097
+ throw new Error(`\u6253\u5305\u4EA7\u7269\u65E0\u6548\u6216\u4E3A\u7A7A: ${localPath}`);
3420
3098
  }
3421
- return { download, deleteLocal };
3099
+ const objectKey = packageArtifactObjectKey(target, projectId);
3100
+ const { client, bucket, publicEndpoint } = await createApmLogMinioClient(cfg2);
3101
+ await client.ensureBucket(bucket);
3102
+ await client.fPutObject(bucket, objectKey, localPath, {
3103
+ "Content-Type": "application/zip"
3104
+ });
3105
+ removePackageTmpDir(packageId);
3106
+ const base = publicEndpoint?.replace(/\/$/, "") ?? "";
3107
+ const artifactUrl = base ? `${base}/${bucket}/${objectKey}` : null;
3108
+ return { artifactPath: objectKey, artifactUrl };
3422
3109
  }
3423
- async function resolveProjectIdForSync(api, workdirPath, options) {
3424
- const explicit = options?.projectId?.trim();
3425
- if (explicit) {
3426
- return { projectId: explicit, diagnostic: null };
3427
- }
3428
- try {
3429
- const gitRoot = await resolveGitRepoRoot(workdirPath);
3430
- const current = await getCurrentBranch(gitRoot);
3431
- const taskId = taskIdFromFeatureBranch(current);
3432
- if (!taskId) {
3433
- return {
3434
- projectId: null,
3435
- diagnostic: `\u5F53\u524D\u5206\u652F ${current} \u4E0D\u662F feat/task-*\uFF0C\u65E0\u6CD5\u89E3\u6790\u6240\u5C5E\u9879\u76EE\u3002
3436
- \u8BF7\u5728\u7279\u6027\u5206\u652F\u4E0A\u6267\u884C\uFF0C\u6216\u663E\u5F0F\u6307\u5B9A projectId\u3002`
3437
- };
3438
- }
3439
- const baseline = await api.cli.projectBaseBranch({ taskId });
3440
- const projectId = baseline.projectId?.trim() || null;
3441
- if (!projectId) {
3442
- return {
3443
- projectId: null,
3444
- diagnostic: `\u4EFB\u52A1 ${taskId} \u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
3445
- };
3446
- }
3447
- return { projectId, diagnostic: null };
3448
- } catch (err) {
3449
- const detail = err instanceof Error ? err.message : String(err);
3450
- return {
3451
- projectId: null,
3452
- diagnostic: detail.replace(/^\[apm\]\s*/, "")
3453
- };
3454
- }
3110
+
3111
+ // src/commands/connect/core/worker/handlers/package.ts
3112
+ async function updatePackageStatus(cfg2, packageId, status, extra) {
3113
+ const api = createApmApiClient(cfg2);
3114
+ await api.cli.projectPackageUpdateStatus({
3115
+ id: packageId,
3116
+ status,
3117
+ ...extra?.error != null ? { error: extra.error } : {},
3118
+ ...extra?.content != null ? { content: extra.content } : {},
3119
+ ...extra?.artifactPath != null ? { artifactPath: extra.artifactPath } : {},
3120
+ ...extra?.artifactUrl != null ? { artifactUrl: extra.artifactUrl } : {}
3121
+ });
3455
3122
  }
3456
- async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
3457
- const empty = {
3458
- synced: false,
3459
- projectId: null,
3460
- downloaded: 0,
3461
- deleted: 0
3123
+ async function handleWebIdePackage(cfg2, msg2, signal) {
3124
+ const workdir = requireRemoteWorkdir(msg2.workdir);
3125
+ const messageId = msg2.messageId;
3126
+ const packageId = msg2.packageId;
3127
+ const projectId = msg2.projectId;
3128
+ console.log(
3129
+ `[apm] webide-package target=${msg2.target} packageId=${packageId} projectId=${projectId} baseBranch=${msg2.baseBranch}`
3130
+ );
3131
+ await updatePackageStatus(cfg2, packageId, "RUNNING");
3132
+ const eventSession = new EventSession(msg2.content);
3133
+ const logSyncRef = {
3134
+ current: createThrottledWebIdeMessageLogSync(
3135
+ cfg2,
3136
+ {
3137
+ taskId: `package:${packageId}`,
3138
+ messageId,
3139
+ agentId: "webide-package"
3140
+ },
3141
+ (err) => {
3142
+ console.warn(
3143
+ "[apm] WebIDE \u6253\u5305\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3144
+ err instanceof Error ? err.message : err
3145
+ );
3146
+ },
3147
+ {
3148
+ objectPrefixOverride: `events/webide-package/${packageId}/`
3149
+ }
3150
+ )
3462
3151
  };
3463
- const cfg = await tryReadApmConfig();
3464
- if (!cfg || !resolveApiKey(cfg)) {
3465
- console.log(
3466
- "[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
3152
+ const syncPrepLog = async () => {
3153
+ const sync = logSyncRef.current;
3154
+ if (!sync) return;
3155
+ sync.schedule(eventSession);
3156
+ await sync.flush(eventSession);
3157
+ };
3158
+ const runPrepStep = async (step, work, formatOk) => {
3159
+ eventSession.addCliStep(step, "\u8FDB\u884C\u4E2D\u2026", "running");
3160
+ await syncPrepLog();
3161
+ try {
3162
+ const result = await work();
3163
+ eventSession.addCliStep(step, formatOk(result), "ok");
3164
+ await syncPrepLog();
3165
+ return result;
3166
+ } catch (err) {
3167
+ const detail = err instanceof Error ? err.message : String(err);
3168
+ eventSession.addCliStep(step, detail, "error");
3169
+ await syncPrepLog();
3170
+ throw err;
3171
+ }
3172
+ };
3173
+ try {
3174
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3175
+ await runPrepStep(
3176
+ "\u521D\u59CB\u5316\u5DE5\u4F5C\u533A",
3177
+ async () => {
3178
+ const { didInit } = await ensureWorkspaceInitialized(workdir);
3179
+ return didInit;
3180
+ },
3181
+ (didInit) => didInit ? "\u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A" : "\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA"
3467
3182
  );
3468
- return empty;
3469
- }
3470
- const api = createApmApiClient(cfg);
3471
- const { projectId, diagnostic } = await resolveProjectIdForSync(
3472
- api,
3473
- workdirPath,
3474
- options
3475
- );
3476
- if (!projectId) {
3477
- console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
3478
- ${diagnostic ?? ""}`);
3479
- return empty;
3480
- }
3481
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
3482
- const docsDir = projectDocumentsDir(targetApmDir, projectId);
3483
- await ensureDirExists(docsDir);
3484
- const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
3485
- { projectId }
3486
- );
3487
- if (!remoteManifest) {
3488
- console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
3489
- return { ...empty, projectId };
3490
- }
3491
- const localManifest = readLocalManifest(targetApmDir, projectId);
3492
- const { download, deleteLocal } = diffManifestPaths(
3493
- remoteManifest,
3494
- localManifest
3495
- );
3496
- let downloaded = 0;
3497
- if (download.length > 0) {
3498
- const { list } = await api.cli.listProjectDocuments({
3499
- projectId,
3500
- paths: download.join(",")
3501
- });
3502
- for (const doc of list) {
3503
- const absPath = toFsPath(
3504
- projectDocumentLocalPath(targetApmDir, projectId, doc.path)
3183
+ const cliVersion = readCliVersion();
3184
+ if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
3185
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3186
+ await runPrepStep(
3187
+ "\u540C\u6B65 WebIDE \u89C4\u5219",
3188
+ async () => {
3189
+ await copyWebIdeTemplateFiles(workspaceApmDir(workdir), workdir);
3190
+ markSkillsSyncedForCliVersion(workdir, cliVersion);
3191
+ return cliVersion;
3192
+ },
3193
+ (version) => `\u5DF2\u540C\u6B65\u81F3 CLI ${version}`
3505
3194
  );
3506
- await ensureDirExists(dirname5(absPath));
3507
- writeFileSync5(absPath, doc.content, "utf8");
3508
- downloaded += 1;
3195
+ } else {
3196
+ eventSession.addCliStep(
3197
+ "\u540C\u6B65 WebIDE \u89C4\u5219",
3198
+ `\u7248\u672C\u4E00\u81F4\uFF08${cliVersion}\uFF09\uFF0C\u8DF3\u8FC7`,
3199
+ "skip"
3200
+ );
3201
+ await syncPrepLog();
3509
3202
  }
3510
- }
3511
- let deleted = 0;
3512
- for (const path3 of deleteLocal) {
3513
- const absPath = toFsPath(
3514
- projectDocumentLocalPath(targetApmDir, projectId, path3)
3203
+ await runPrepStep(
3204
+ "\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355",
3205
+ async () => {
3206
+ const workspaceRepos = resolveWorkspaceRepos(workdir);
3207
+ try {
3208
+ await enrichWorkspaceReposRemoteUrls(workspaceRepos);
3209
+ } catch (err) {
3210
+ console.warn(
3211
+ "[apm] \u8865\u9F50 workspace-repos remoteUrl \u5931\u8D25:",
3212
+ err instanceof Error ? err.message : err
3213
+ );
3214
+ }
3215
+ return workspaceRepos;
3216
+ },
3217
+ (repos) => `kind=${repos.kind} repos=${repos.repos.length}`
3515
3218
  );
3516
- if (existsSync8(absPath)) {
3517
- rmSync2(absPath, { force: true });
3518
- deleted += 1;
3519
- }
3520
- }
3521
- writeFileSync5(
3522
- toFsPath(join6(docsDir, MANIFEST_FILE2)),
3523
- `${JSON.stringify(remoteManifest, null, 2)}
3524
- `,
3525
- "utf8"
3526
- );
3527
- console.log(
3528
- `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
3529
- );
3530
- return {
3531
- synced: true,
3532
- projectId,
3533
- downloaded,
3534
- deleted
3535
- };
3536
- }
3537
- async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
3538
- const api = createApmApiClient(cfg);
3539
- const { projectId } = await resolveProjectIdForSync(
3540
- api,
3541
- workdirPath,
3542
- options
3543
- );
3544
- if (!projectId) {
3545
- return 0;
3546
- }
3547
- const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
3548
- const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
3549
- if (localPaths.length === 0) {
3550
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3551
- return 0;
3552
- }
3553
- const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
3554
- const remoteHashByPath = new Map(
3555
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3556
- );
3557
- const remoteDescriptionByPath = new Map(
3558
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
3559
- );
3560
- let synced = 0;
3561
- for (const path3 of localPaths) {
3562
- const absPath = toFsPath(
3563
- projectDocumentLocalPath(targetApmDir, projectId, path3)
3219
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3220
+ await runPrepStep(
3221
+ "\u5207\u6362\u57FA\u7EBF\u5E76\u62C9\u53D6\u6700\u65B0",
3222
+ async () => checkoutBaselineBranches(msg2.baseBranch, { cwd: workdir }),
3223
+ (result) => `baseline=${result.baseline} kind=${result.kind} repos=${result.repos.length}`
3564
3224
  );
3565
- const content = readFileSync7(absPath, "utf8");
3566
- const contentHash = hashLocalFileContent(content);
3567
- if (remoteHashByPath.get(path3) === contentHash) {
3568
- continue;
3225
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3226
+ await runPrepStep(
3227
+ "\u540C\u6B65\u9879\u76EE\u6587\u6863",
3228
+ async () => {
3229
+ const docs = await syncProjectDocumentsPull(workdir, void 0, {
3230
+ projectId
3231
+ });
3232
+ return docs;
3233
+ },
3234
+ (result) => {
3235
+ if (!result.synced) {
3236
+ return result.projectId ? "\u65E0\u9700\u540C\u6B65\u6216\u8DF3\u8FC7" : "\u8DF3\u8FC7\uFF1A\u65E0 projectId";
3237
+ }
3238
+ return `downloaded=${result.downloaded} deleted=${result.deleted}`;
3239
+ }
3240
+ );
3241
+ eventSession.addCliStep("\u542F\u52A8\u6253\u5305 Agent", "\u51C6\u5907 create\u2026", "running");
3242
+ await syncPrepLog();
3243
+ let failedByTool = false;
3244
+ const outcome = await runCursorAgent(
3245
+ cfg2,
3246
+ {
3247
+ messageId,
3248
+ prompt: msg2.content,
3249
+ model: msg2.model,
3250
+ apiKey: msg2.apiKey,
3251
+ workdir,
3252
+ user: msg2.user || "webide"
3253
+ },
3254
+ {
3255
+ signal,
3256
+ forceSend: true,
3257
+ eventSession,
3258
+ enableAppendMessage: false,
3259
+ enableAskQuestion: false,
3260
+ enableWebIdePlanTools: false,
3261
+ enablePtySessionMcp: false,
3262
+ enableSandbox: false,
3263
+ enablePackageStatusTools: true,
3264
+ packageId,
3265
+ projectId,
3266
+ onPackageFailed: async () => {
3267
+ failedByTool = true;
3268
+ },
3269
+ taskId: `package:${packageId}`,
3270
+ createRemoteLogSync: (agentId) => {
3271
+ logSyncRef.current = createThrottledWebIdeMessageLogSync(
3272
+ cfg2,
3273
+ {
3274
+ taskId: `package:${packageId}`,
3275
+ messageId,
3276
+ agentId
3277
+ },
3278
+ (err) => {
3279
+ console.warn(
3280
+ "[apm] WebIDE \u6253\u5305\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3281
+ err instanceof Error ? err.message : err
3282
+ );
3283
+ },
3284
+ {
3285
+ objectPrefixOverride: `events/webide-package/${packageId}/`
3286
+ }
3287
+ );
3288
+ return logSyncRef.current;
3289
+ },
3290
+ onRunStarted: async ({ agentId, runId }) => {
3291
+ eventSession.addCliStep(
3292
+ "\u542F\u52A8\u6253\u5305 Agent",
3293
+ `agentId=${agentId} runId=${runId}`,
3294
+ "ok"
3295
+ );
3296
+ logSyncRef.current?.schedule(eventSession);
3297
+ await logSyncRef.current?.markRun(runId, "running");
3298
+ await logSyncRef.current?.flush(eventSession);
3299
+ }
3300
+ }
3301
+ );
3302
+ const tokenUsage = outcome.usage != null ? {
3303
+ modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
3304
+ inputTokens: outcome.usage.inputTokens,
3305
+ outputTokens: outcome.usage.outputTokens,
3306
+ cacheReadTokens: outcome.usage.cacheReadTokens,
3307
+ cacheWriteTokens: outcome.usage.cacheWriteTokens,
3308
+ totalTokens: outcome.usage.totalTokens,
3309
+ ...outcome.usage.reasoningTokens != null ? { reasoningTokens: outcome.usage.reasoningTokens } : {}
3310
+ } : void 0;
3311
+ if (outcome.status === "error") {
3312
+ const detail = formatCursorRunFailure(outcome.runId, {
3313
+ resultText: outcome.result
3314
+ });
3315
+ await logSyncRef.current?.markRun(
3316
+ outcome.runId,
3317
+ "error",
3318
+ detail,
3319
+ tokenUsage
3320
+ );
3321
+ if (!failedByTool) {
3322
+ await updatePackageStatus(cfg2, packageId, "FAILED", { error: detail });
3323
+ }
3324
+ return;
3569
3325
  }
3570
- await api.cli.upsertProjectDocument({
3571
- projectId,
3572
- path: path3,
3573
- content,
3574
- description: remoteDescriptionByPath.get(path3) ?? void 0
3326
+ if (outcome.status === "cancelled" || signal.aborted) {
3327
+ await logSyncRef.current?.markRun(
3328
+ outcome.runId,
3329
+ "cancelled",
3330
+ "\u5DF2\u53D6\u6D88",
3331
+ tokenUsage
3332
+ );
3333
+ await updatePackageStatus(cfg2, packageId, "CANCELLED", {
3334
+ error: "\u5DF2\u53D6\u6D88"
3335
+ });
3336
+ return;
3337
+ }
3338
+ await logSyncRef.current?.markRun(
3339
+ outcome.runId,
3340
+ "finished",
3341
+ null,
3342
+ tokenUsage
3343
+ );
3344
+ if (failedByTool) {
3345
+ console.log(
3346
+ `[apm] webide-package Agent \u5DF2\u6807 FAILED packageId=${packageId}`
3347
+ );
3348
+ return;
3349
+ }
3350
+ const uploaded = await runPrepStep(
3351
+ "\u4E0A\u4F20\u6253\u5305\u4EA7\u7269\u5230 MinIO",
3352
+ () => uploadPackageArtifact(cfg2, msg2.target, packageId, projectId),
3353
+ (result) => `objectKey=${result.artifactPath}${result.artifactUrl ? ` url=${result.artifactUrl}` : ""}`
3354
+ );
3355
+ await updatePackageStatus(cfg2, packageId, "SUCCESS", {
3356
+ artifactPath: uploaded.artifactPath,
3357
+ ...uploaded.artifactUrl ? { artifactUrl: uploaded.artifactUrl } : {}
3575
3358
  });
3576
- synced += 1;
3577
- console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path3}`);
3578
- }
3579
- if (synced === 0) {
3580
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3581
- }
3582
- return synced;
3583
- }
3584
-
3585
- // src/commands/connect/cli-version-sync.ts
3586
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
3587
- import { join as join7 } from "path";
3588
- var CLI_VERSION_FILE = ".cli-version.json";
3589
- function manifestPath2(apmDir) {
3590
- return join7(apmDir, CLI_VERSION_FILE);
3591
- }
3592
- function loadManifest2(apmDir) {
3593
- const path3 = toFsPath(manifestPath2(apmDir));
3594
- if (!existsSync9(path3)) {
3595
- return null;
3596
- }
3597
- try {
3598
- const parsed = JSON.parse(
3599
- readFileSync8(path3, "utf8")
3359
+ console.log(
3360
+ `[apm] webide-package \u5B8C\u6210 target=${msg2.target} packageId=${packageId} agentId=${outcome.agentId} artifact=${uploaded.artifactPath}`
3600
3361
  );
3601
- if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
3602
- return parsed;
3362
+ } catch (err) {
3363
+ const detail = err instanceof Error ? err.message : String(err);
3364
+ console.error(`[apm] webide-package \u5931\u8D25: ${detail}`);
3365
+ try {
3366
+ if (signal.aborted) {
3367
+ await updatePackageStatus(cfg2, packageId, "CANCELLED", {
3368
+ error: detail
3369
+ });
3370
+ } else {
3371
+ await updatePackageStatus(cfg2, packageId, "FAILED", { error: detail });
3372
+ }
3373
+ } catch (statusErr) {
3374
+ console.error(
3375
+ "[apm] \u5199\u5165\u6253\u5305 FAILED \u5931\u8D25:",
3376
+ statusErr instanceof Error ? statusErr.message : statusErr
3377
+ );
3603
3378
  }
3604
- } catch {
3605
- }
3606
- return null;
3607
- }
3608
- function saveManifest2(apmDir, cliVersion) {
3609
- const manifest = { version: 1, cliVersion };
3610
- writeFileSync6(
3611
- toFsPath(manifestPath2(apmDir)),
3612
- `${JSON.stringify(manifest, null, 2)}
3613
- `,
3614
- "utf8"
3615
- );
3616
- }
3617
- var syncedInSession = /* @__PURE__ */ new Map();
3618
- function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
3619
- const cached = syncedInSession.get(workdir);
3620
- if (cached === currentVersion) {
3621
- return false;
3622
- }
3623
- const stored = loadManifest2(workspaceApmDir(workdir));
3624
- if (stored?.cliVersion === currentVersion) {
3625
- syncedInSession.set(workdir, currentVersion);
3626
- return false;
3627
3379
  }
3628
- return true;
3629
- }
3630
- function markSkillsSyncedForCliVersion(workdir, cliVersion) {
3631
- saveManifest2(workspaceApmDir(workdir), cliVersion);
3632
- syncedInSession.set(workdir, cliVersion);
3633
3380
  }
3634
3381
 
3635
- // src/commands/connect/parse-webide-prompt-payload.ts
3636
- function parseWebIdePromptPayload(content) {
3637
- const marker = "\n\npayload=";
3638
- const index = content.lastIndexOf(marker);
3639
- if (index < 0) return null;
3640
- const jsonText = content.slice(index + marker.length).trim();
3641
- if (!jsonText) return null;
3642
- try {
3643
- const parsed = JSON.parse(jsonText);
3644
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3645
- return parsed;
3382
+ // src/commands/connect/ensure-message-reply.ts
3383
+ var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
3384
+ function resolveMessageReplyFallback(fallback) {
3385
+ for (const candidate of [
3386
+ fallback.assistantText,
3387
+ fallback.result,
3388
+ fallback.createPlan
3389
+ ]) {
3390
+ const trimmed = candidate?.trim();
3391
+ if (trimmed) {
3392
+ return trimmed;
3646
3393
  }
3647
- } catch {
3648
- return null;
3649
3394
  }
3650
- return null;
3395
+ return DEFAULT_REPLY;
3651
3396
  }
3652
3397
 
3653
- // src/commands/connect/handle-webide-message.ts
3654
- var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
3655
- "skip-plan",
3656
- "start-develop",
3657
- "fix-and-retest",
3658
- "report-defect",
3659
- "create-pr",
3660
- "finish-manual-test"
3661
- ]);
3662
- function shouldCommitAfterWebIdeMessage(action) {
3663
- return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
3664
- }
3665
- function isStartLocalServicesAction(action) {
3666
- return action === "enter-manual-test" || action === "skip-test";
3667
- }
3668
- function isExecuteSqlAction(action) {
3669
- return action === "execute-sql";
3670
- }
3671
- function syncLocalTaskStatus(workdir, taskId, msg, status) {
3672
- syncWebIdeTaskState(workdir, taskId, {
3673
- messageId: msg.messageId,
3674
- action: msg.action,
3675
- status
3676
- });
3677
- }
3678
- async function updateStatus(cfg, messageId, status) {
3679
- const api = createApmApiClient(cfg);
3398
+ // src/commands/connect/core/worker/handlers/start-project.ts
3399
+ async function updateStatus(cfg2, messageId, status) {
3400
+ const api = createApmApiClient(cfg2);
3680
3401
  await api.cli.webideUpdateMessageStatus({ id: messageId, status });
3681
3402
  }
3682
- async function setError(cfg, messageId, error) {
3683
- const api = createApmApiClient(cfg);
3403
+ async function setError(cfg2, messageId, error) {
3404
+ const api = createApmApiClient(cfg2);
3684
3405
  await api.cli.webideSetMessageError({ id: messageId, error });
3685
3406
  }
3686
- async function appendContent(cfg, messageId, content) {
3687
- const api = createApmApiClient(cfg);
3688
- await api.cli.webideAppendMessageContent({ id: messageId, content });
3689
- }
3690
- async function handleWebIdeInboundMessage(cfg, msg, signal) {
3691
- const workdir = requireRemoteWorkdir(msg.workdir);
3692
- const messageId = msg.messageId;
3693
- const taskId = msg.taskId;
3694
- const branchCacheKey = taskId;
3407
+ async function handleStartProject(cfg2, msg2, signal) {
3408
+ const workdir = requireRemoteWorkdir(msg2.workdir);
3409
+ const messageId = msg2.messageId;
3410
+ const taskId = msg2.taskId;
3695
3411
  console.log(
3696
- `[apm] webide-message action=${msg.action} taskId=${taskId} messageId=${messageId}`
3412
+ `[apm] webide-start-project target=${msg2.target} restart=${msg2.restart} taskId=${taskId} messageId=${messageId}`
3697
3413
  );
3698
- await updateStatus(cfg, messageId, "TYPING");
3699
- syncLocalTaskStatus(workdir, taskId, msg, "TYPING");
3700
- const eventSession = new EventSession(msg.content);
3701
- const prepAgentId = loadWebIdeAgentId(workdir, taskId) ?? "webide-cli-prep";
3414
+ await updateStatus(cfg2, messageId, "TYPING");
3415
+ const eventSession = new EventSession(msg2.content);
3702
3416
  const logSyncRef = {
3703
3417
  current: createThrottledWebIdeMessageLogSync(
3704
- cfg,
3705
- { taskId, messageId, agentId: prepAgentId },
3418
+ cfg2,
3419
+ { taskId, messageId, agentId: "webide-start-project" },
3706
3420
  (err) => {
3707
3421
  console.warn(
3708
- "[apm] WebIDE \u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3422
+ "[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3709
3423
  err instanceof Error ? err.message : err
3710
3424
  );
3711
3425
  }
3712
3426
  )
3713
3427
  };
3714
- const syncPrepLog = async () => {
3715
- const sync = logSyncRef.current;
3716
- if (!sync) return;
3717
- sync.schedule(eventSession);
3718
- await sync.flush(eventSession);
3719
- };
3720
- const runPrepStep = async (step, work, formatOk) => {
3721
- eventSession.addCliStep(step, "\u8FDB\u884C\u4E2D\u2026", "running");
3722
- await syncPrepLog();
3723
- try {
3724
- const result = await work();
3725
- eventSession.addCliStep(step, formatOk(result), "ok");
3726
- await syncPrepLog();
3727
- return result;
3728
- } catch (err) {
3729
- const detail = err instanceof Error ? err.message : String(err);
3730
- eventSession.addCliStep(step, detail, "error");
3731
- await syncPrepLog();
3732
- throw err;
3733
- }
3734
- };
3735
3428
  try {
3736
3429
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3737
- await runPrepStep(
3738
- "\u521D\u59CB\u5316\u5DE5\u4F5C\u533A",
3739
- async () => {
3740
- const { didInit } = await ensureWorkspaceInitialized(workdir);
3741
- return didInit;
3742
- },
3743
- (didInit) => didInit ? "\u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A" : "\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA"
3744
- );
3745
- const cliVersion = readCliVersion();
3746
- if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
3747
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3748
- await runPrepStep(
3749
- "\u540C\u6B65 WebIDE \u89C4\u5219",
3750
- async () => {
3751
- await copyWebIdeTemplateFiles(workspaceApmDir(workdir), workdir);
3752
- markSkillsSyncedForCliVersion(workdir, cliVersion);
3753
- return cliVersion;
3754
- },
3755
- (version) => `\u5DF2\u540C\u6B65\u81F3 CLI ${version}`
3756
- );
3757
- } else {
3758
- eventSession.addCliStep(
3759
- "\u540C\u6B65 WebIDE \u89C4\u5219",
3760
- `\u7248\u672C\u4E00\u81F4\uFF08${cliVersion}\uFF09\uFF0C\u8DF3\u8FC7`,
3761
- "skip"
3762
- );
3763
- await syncPrepLog();
3764
- }
3765
- await runPrepStep(
3766
- "\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355",
3767
- async () => {
3768
- const workspaceRepos = resolveWorkspaceRepos(workdir);
3769
- try {
3770
- await enrichWorkspaceReposRemoteUrls(workspaceRepos);
3771
- } catch (err) {
3772
- console.warn(
3773
- "[apm] \u8865\u9F50 workspace-repos remoteUrl \u5931\u8D25:",
3774
- err instanceof Error ? err.message : err
3775
- );
3776
- }
3777
- return workspaceRepos;
3778
- },
3779
- (repos) => `kind=${repos.kind} repos=${repos.repos.length}`
3780
- );
3781
- if (shouldRunBranch(branchCacheKey, workdir)) {
3782
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3783
- await runPrepStep(
3784
- "\u51C6\u5907\u7279\u6027\u5206\u652F",
3785
- async () => runTaskBranch(taskId, { cwd: workdir }),
3786
- (branchResult) => {
3787
- markBranchDone(branchCacheKey, workdir);
3788
- return `kind=${branchResult.kind} branch=${branchResult.branch} baseline=${branchResult.baseline} repos=${branchResult.repos.length}`;
3789
- }
3790
- );
3791
- } else {
3792
- eventSession.addCliStep("\u51C6\u5907\u7279\u6027\u5206\u652F", "\u5DF2\u7F13\u5B58\uFF0C\u8DF3\u8FC7", "skip");
3793
- await syncPrepLog();
3794
- }
3795
- if (shouldEnsureWebIdePullRequests(msg.action)) {
3796
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3797
- await runPrepStep(
3798
- "\u6D4B\u8BD5\u524D\u63A8\u9001",
3799
- async () => pushWorkspaceRepos(workdir),
3800
- (pushed) => `\u5DF2 push ${pushed} \u4E2A\u4ED3\u5E93`
3801
- );
3802
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3803
- await runPrepStep(
3804
- "\u786E\u4FDD PR",
3805
- async () => {
3806
- await ensureWebIdePullRequests(cfg, taskId, workdir);
3807
- return true;
3808
- },
3809
- () => "PR \u68C0\u67E5\u5B8C\u6210"
3810
- );
3811
- }
3812
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3813
- const api = createApmApiClient(cfg);
3814
- let projectId = null;
3815
- await runPrepStep(
3816
- "\u540C\u6B65\u9879\u76EE\u6587\u6863",
3817
- async () => {
3818
- try {
3819
- const baseline = await api.cli.projectBaseBranch({ taskId });
3820
- projectId = baseline.projectId?.trim() || null;
3821
- if (!projectId) {
3822
- return { synced: false, reason: "\u65E0 projectId" };
3823
- }
3824
- const docs = await syncProjectDocumentsPull(workdir, void 0, {
3825
- projectId
3826
- });
3827
- return {
3828
- synced: docs.synced,
3829
- downloaded: docs.downloaded,
3830
- deleted: docs.deleted
3831
- };
3832
- } catch (err) {
3833
- console.warn(
3834
- "[apm] WebIDE \u9879\u76EE\u6587\u6863\u540C\u6B65\u5931\u8D25:",
3835
- err instanceof Error ? err.message : err
3836
- );
3837
- return {
3838
- synced: false,
3839
- reason: err instanceof Error ? err.message : String(err)
3840
- };
3841
- }
3842
- },
3843
- (result) => {
3844
- if ("reason" in result && result.reason) {
3845
- return `\u8DF3\u8FC7\uFF1A${result.reason}`;
3846
- }
3847
- if (!result.synced) return "\u65E0\u9700\u540C\u6B65";
3848
- return `downloaded=${result.downloaded ?? 0} deleted=${result.deleted ?? 0}`;
3849
- }
3850
- );
3851
- await runPrepStep(
3852
- "\u540C\u6B65\u4EFB\u52A1\u9644\u4EF6",
3853
- async () => {
3854
- try {
3855
- const attachments = await api.cli.webideListAttachments({ taskId });
3856
- const names = await syncWebIdeAttachments(
3857
- cfg,
3858
- taskId,
3859
- workdir,
3860
- attachments ?? []
3861
- );
3862
- return names;
3863
- } catch (err) {
3864
- console.warn(
3865
- "[apm] WebIDE \u9644\u4EF6\u540C\u6B65\u5931\u8D25:",
3866
- err instanceof Error ? err.message : err
3867
- );
3868
- return [];
3869
- }
3870
- },
3871
- (names) => names.length > 0 ? `\u5DF2\u540C\u6B65 ${names.length} \u4E2A\u9644\u4EF6` : "\u65E0\u9644\u4EF6\u6216\u540C\u6B65\u8DF3\u8FC7"
3872
- );
3873
- eventSession.addCliStep(
3874
- "\u542F\u52A8 Cursor Agent",
3875
- "\u51C6\u5907 resume/create\u2026",
3876
- "running"
3877
- );
3878
- await syncPrepLog();
3879
- const startLocalServices = isStartLocalServicesAction(msg.action);
3880
- const executeSql = isExecuteSqlAction(msg.action);
3881
- const savedAgentId = startLocalServices ? void 0 : loadWebIdeAgentId(workdir, taskId);
3882
- const promptPayload = executeSql ? parseWebIdePromptPayload(msg.content) : null;
3883
- const sqlExecutionId = executeSql && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
3430
+ await ensureWorkspaceInitialized(workdir);
3431
+ const api = createApmApiClient(cfg2);
3884
3432
  const outcome = await runCursorAgent(
3885
- cfg,
3433
+ cfg2,
3886
3434
  {
3887
3435
  messageId,
3888
- prompt: msg.content,
3889
- model: msg.model,
3890
- apiKey: msg.apiKey,
3436
+ prompt: msg2.content,
3437
+ model: msg2.model,
3438
+ apiKey: msg2.apiKey,
3891
3439
  workdir,
3892
- user: msg.user || "webide",
3893
- resumeAgentId: savedAgentId
3440
+ user: msg2.user || "webide"
3894
3441
  },
3895
3442
  {
3896
3443
  signal,
3897
3444
  forceSend: true,
3898
3445
  eventSession,
3899
- appendMessageContent: (content) => appendContent(cfg, messageId, content),
3900
- askQuestionExecute: startLocalServices ? void 0 : createWebIdeAskQuestionExecute({
3901
- cfg,
3902
- taskId,
3903
- signal
3904
- }),
3905
- enableAskQuestion: !startLocalServices && !executeSql,
3906
- enableWebIdePlanTools: !startLocalServices && !executeSql,
3907
- enablePtySessionMcp: startLocalServices,
3908
- enableMysqlTools: executeSql,
3909
- sqlExecutionId,
3446
+ enableAppendMessage: false,
3447
+ enableAskQuestion: false,
3448
+ enableWebIdePlanTools: false,
3449
+ enablePtySessionMcp: true,
3910
3450
  enableSandbox: false,
3911
- onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => clearWebIdeAgentId(workdir, taskId),
3912
3451
  taskId,
3913
3452
  createRemoteLogSync: (agentId) => {
3914
- if (!startLocalServices) {
3915
- saveWebIdeAgentId(workdir, taskId, agentId);
3916
- }
3917
3453
  logSyncRef.current = createThrottledWebIdeMessageLogSync(
3918
- cfg,
3454
+ cfg2,
3919
3455
  { taskId, messageId, agentId },
3920
3456
  (err) => {
3921
3457
  console.warn(
3922
- "[apm] WebIDE \u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3458
+ "[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3923
3459
  err instanceof Error ? err.message : err
3924
3460
  );
3925
3461
  }
@@ -3927,11 +3463,8 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3927
3463
  return logSyncRef.current;
3928
3464
  },
3929
3465
  onRunStarted: async ({ agentId, runId }) => {
3930
- if (!startLocalServices) {
3931
- saveWebIdeAgentId(workdir, taskId, agentId);
3932
- }
3933
3466
  eventSession.addCliStep(
3934
- "\u542F\u52A8 Cursor Agent",
3467
+ "\u542F\u52A8\u72EC\u7ACB Agent",
3935
3468
  `agentId=${agentId} runId=${runId}`,
3936
3469
  "ok"
3937
3470
  );
@@ -3941,11 +3474,8 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3941
3474
  }
3942
3475
  }
3943
3476
  );
3944
- if (!startLocalServices) {
3945
- saveWebIdeAgentId(workdir, taskId, outcome.agentId);
3946
- }
3947
3477
  const tokenUsage = outcome.usage != null ? {
3948
- modelId: outcome.modelId ?? (msg.model?.trim() || void 0),
3478
+ modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
3949
3479
  inputTokens: outcome.usage.inputTokens,
3950
3480
  outputTokens: outcome.usage.outputTokens,
3951
3481
  cacheReadTokens: outcome.usage.cacheReadTokens,
@@ -3963,22 +3493,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3963
3493
  detail,
3964
3494
  tokenUsage
3965
3495
  );
3966
- if (executeSql && sqlExecutionId) {
3967
- try {
3968
- await api.cli.webideFinalizeSqlExecution({
3969
- executionId: sqlExecutionId,
3970
- status: "FAILED",
3971
- error: detail
3972
- });
3973
- } catch (finalizeErr) {
3974
- console.warn(
3975
- "[apm] finalize sql execution failed:",
3976
- finalizeErr instanceof Error ? finalizeErr.message : finalizeErr
3977
- );
3978
- }
3979
- }
3980
- await setError(cfg, messageId, detail);
3981
- syncLocalTaskStatus(workdir, taskId, msg, "FAILED");
3496
+ await setError(cfg2, messageId, detail);
3982
3497
  return;
3983
3498
  }
3984
3499
  if (outcome.status === "cancelled" || signal.aborted) {
@@ -3988,381 +3503,914 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3988
3503
  "\u5DF2\u53D6\u6D88",
3989
3504
  tokenUsage
3990
3505
  );
3991
- if (executeSql && sqlExecutionId) {
3992
- try {
3993
- await api.cli.webideFinalizeSqlExecution({
3994
- executionId: sqlExecutionId,
3995
- status: "CANCELLED",
3996
- error: "\u5DF2\u53D6\u6D88"
3997
- });
3998
- } catch (finalizeErr) {
3999
- console.warn(
4000
- "[apm] finalize sql execution cancelled:",
4001
- finalizeErr instanceof Error ? finalizeErr.message : finalizeErr
4002
- );
4003
- }
4004
- }
4005
- await updateStatus(cfg, messageId, "CANCELLED");
4006
- syncLocalTaskStatus(workdir, taskId, msg, "CANCELLED");
3506
+ await updateStatus(cfg2, messageId, "CANCELLED");
4007
3507
  return;
4008
3508
  }
4009
3509
  const fallback = resolveMessageReplyFallback({
4010
3510
  assistantText: outcome.assistantText,
4011
- result: outcome.result,
4012
- createPlan: outcome.createPlan
3511
+ result: outcome.result
4013
3512
  });
4014
3513
  await api.cli.webideEnsureMessageContent({
4015
3514
  id: messageId,
4016
3515
  content: fallback
4017
3516
  });
4018
- if ((msg.action === "write-plan" || msg.action === "revise-plan") && outcome.createPlan && outcome.createPlan.trim()) {
4019
- try {
4020
- await api.cli.webideUpsertPlan({
4021
- taskId,
4022
- content: outcome.createPlan.trim()
4023
- });
4024
- } catch (err) {
4025
- console.warn(
4026
- `[apm] ${msg.action} \u8865\u5199\u8BA1\u5212\u5931\u8D25:`,
4027
- err instanceof Error ? err.message : err
4028
- );
4029
- }
4030
- }
4031
- if (shouldCommitAfterWebIdeMessage(msg.action)) {
4032
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4033
- const committed = await commitWorkspaceReposIfDirty(
4034
- workdir,
4035
- `chore(webide): sync after ${msg.action} (${taskId})`
4036
- );
4037
- console.log(
4038
- `[apm] webide \u515C\u5E95 commit action=${msg.action} repos=${committed}`
4039
- );
4040
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4041
- const pushed = await pushWorkspaceRepos(workdir);
4042
- console.log(
4043
- `[apm] webide \u81EA\u52A8 push action=${msg.action} repos=${pushed}`
4044
- );
4045
- }
4046
- if (shouldEnsureWebIdePullRequestsAfterAgent(msg.action)) {
4047
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4048
- if (!shouldCommitAfterWebIdeMessage(msg.action)) {
4049
- const pushed = await pushWorkspaceRepos(workdir);
4050
- console.log(
4051
- `[apm] webide create-pr \u524D push action=${msg.action} repos=${pushed}`
4052
- );
3517
+ await logSyncRef.current?.markRun(
3518
+ outcome.runId,
3519
+ "finished",
3520
+ null,
3521
+ tokenUsage
3522
+ );
3523
+ await updateStatus(cfg2, messageId, "SUCCESS");
3524
+ console.log(
3525
+ `[apm] webide-start-project \u5B8C\u6210 target=${msg2.target} messageId=${messageId} agentId=${outcome.agentId}`
3526
+ );
3527
+ } catch (err) {
3528
+ const detail = err instanceof Error ? err.message : String(err);
3529
+ console.error(`[apm] webide-start-project \u5931\u8D25: ${detail}`);
3530
+ try {
3531
+ await setError(cfg2, messageId, detail);
3532
+ } catch (statusErr) {
3533
+ console.error(
3534
+ "[apm] \u5199\u5165\u542F\u52A8\u9879\u76EE FAILED \u5931\u8D25:",
3535
+ statusErr instanceof Error ? statusErr.message : statusErr
3536
+ );
3537
+ }
3538
+ }
3539
+ }
3540
+
3541
+ // src/commands/connect/webide-agent-registry.ts
3542
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
3543
+ import { dirname as dirname5, resolve as resolve6 } from "node:path";
3544
+ function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
3545
+ return resolve6(workdir, ".apm", "webide", taskId, fileName);
3546
+ }
3547
+ var DESIGN_AGENT_FILE = "design-agent.json";
3548
+ function readRegistry(path3) {
3549
+ if (!existsSync7(path3)) {
3550
+ return {};
3551
+ }
3552
+ try {
3553
+ const parsed = JSON.parse(readFileSync7(path3, "utf8"));
3554
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3555
+ const raw = parsed;
3556
+ const state = {};
3557
+ if (typeof raw.agentId === "string" && raw.agentId.trim()) {
3558
+ state.agentId = raw.agentId.trim();
4053
3559
  }
4054
- const prResult = await ensureWebIdePullRequests(cfg, taskId, workdir);
4055
- if (prResult.tips.length > 0) {
4056
- for (const tip of prResult.tips) {
4057
- console.log(`[apm] webide create-pr \u63D0\u793A: ${tip}`);
3560
+ if (typeof raw.taskId === "string" && raw.taskId.trim()) {
3561
+ state.taskId = raw.taskId.trim();
3562
+ }
3563
+ if (typeof raw.messageId === "string" && raw.messageId.trim()) {
3564
+ state.messageId = raw.messageId.trim();
3565
+ }
3566
+ if (typeof raw.action === "string" && raw.action.trim()) {
3567
+ state.action = raw.action.trim();
3568
+ }
3569
+ if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
3570
+ state.status = raw.status;
3571
+ }
3572
+ if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
3573
+ state.updatedAt = raw.updatedAt.trim();
3574
+ }
3575
+ return state;
3576
+ }
3577
+ } catch {
3578
+ }
3579
+ return {};
3580
+ }
3581
+ function writeRegistry(path3, registry) {
3582
+ mkdirSync4(dirname5(path3), { recursive: true });
3583
+ writeFileSync5(path3, `${JSON.stringify(registry, null, 2)}
3584
+ `, "utf8");
3585
+ }
3586
+ function syncWebIdeTaskState(workdir, taskId, patch) {
3587
+ const trimmedTaskId = taskId.trim();
3588
+ if (!trimmedTaskId) return;
3589
+ const path3 = registryPath(workdir, trimmedTaskId);
3590
+ const current = readRegistry(path3);
3591
+ const next = {
3592
+ ...current,
3593
+ taskId: trimmedTaskId,
3594
+ updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
3595
+ };
3596
+ if (patch.agentId !== void 0) {
3597
+ const agentId = patch.agentId.trim();
3598
+ if (agentId) next.agentId = agentId;
3599
+ else delete next.agentId;
3600
+ }
3601
+ if (patch.messageId !== void 0) {
3602
+ const messageId = patch.messageId.trim();
3603
+ if (messageId) next.messageId = messageId;
3604
+ else delete next.messageId;
3605
+ }
3606
+ if (patch.action !== void 0) {
3607
+ const action = patch.action.trim();
3608
+ if (action) next.action = action;
3609
+ else delete next.action;
3610
+ }
3611
+ if (patch.status !== void 0) {
3612
+ next.status = patch.status;
3613
+ }
3614
+ writeRegistry(path3, next);
3615
+ }
3616
+ function loadWebIdeAgentId(workdir, taskId) {
3617
+ return readRegistry(registryPath(workdir, taskId)).agentId;
3618
+ }
3619
+ function saveWebIdeAgentId(workdir, taskId, agentId) {
3620
+ syncWebIdeTaskState(workdir, taskId, { agentId });
3621
+ }
3622
+ function clearWebIdeAgentId(workdir, taskId) {
3623
+ const path3 = registryPath(workdir, taskId);
3624
+ if (!existsSync7(path3)) return;
3625
+ syncWebIdeTaskState(workdir, taskId, { agentId: "" });
3626
+ }
3627
+ function loadWebIdeDesignAgentId(workdir, taskId) {
3628
+ return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
3629
+ }
3630
+ function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
3631
+ const trimmedTaskId = taskId.trim();
3632
+ if (!trimmedTaskId) return;
3633
+ const path3 = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
3634
+ const current = readRegistry(path3);
3635
+ writeRegistry(path3, {
3636
+ ...current,
3637
+ taskId: trimmedTaskId,
3638
+ agentId: agentId.trim() || void 0,
3639
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3640
+ });
3641
+ }
3642
+ function clearWebIdeDesignAgentId(workdir, taskId) {
3643
+ const path3 = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
3644
+ if (!existsSync7(path3)) return;
3645
+ const current = readRegistry(path3);
3646
+ const next = { ...current };
3647
+ delete next.agentId;
3648
+ writeRegistry(path3, {
3649
+ ...next,
3650
+ taskId: taskId.trim(),
3651
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3652
+ });
3653
+ }
3654
+
3655
+ // src/commands/clean-webide-cache.ts
3656
+ import { existsSync as existsSync8, rmSync as rmSync2 } from "node:fs";
3657
+ import { resolve as resolve7 } from "node:path";
3658
+ import { getDefaultSdkStateRoot } from "@cursor/sdk";
3659
+ async function purgeCursorAgentStoreForAgent(workdir, agentId) {
3660
+ const trimmedAgentId = agentId.trim();
3661
+ const trimmedWorkdir = workdir.trim();
3662
+ if (!trimmedAgentId || !trimmedWorkdir) return false;
3663
+ const stateRoot = getDefaultSdkStateRoot(trimmedWorkdir);
3664
+ if (!existsSync8(stateRoot)) return false;
3665
+ const { SqliteLocalAgentStore } = await import(
3666
+ /* @vite-ignore */
3667
+ "@cursor/sdk/sqlite"
3668
+ );
3669
+ const store = await SqliteLocalAgentStore.open({
3670
+ workspaceRef: trimmedWorkdir
3671
+ });
3672
+ try {
3673
+ const runIds = [];
3674
+ let cursor;
3675
+ do {
3676
+ const page = await store.runs.list({
3677
+ filter: {
3678
+ agentIds: [trimmedAgentId],
3679
+ ...cursor ? { cursor } : {},
3680
+ limit: 200
4058
3681
  }
3682
+ });
3683
+ for (const run of page.items) {
3684
+ runIds.push(run.runId);
4059
3685
  }
4060
- try {
4061
- await appendContent(
4062
- cfg,
4063
- messageId,
4064
- formatWebIdePullRequestSummary(prResult)
4065
- );
4066
- } catch (appendErr) {
4067
- console.warn(
4068
- "[apm] webide create-pr \u8FFD\u52A0 PR \u7ED3\u679C\u5931\u8D25:",
4069
- appendErr instanceof Error ? appendErr.message : appendErr
3686
+ cursor = page.nextCursor;
3687
+ } while (cursor);
3688
+ if (runIds.length > 0) {
3689
+ await store.runEvents.delete({ filter: { runIds } });
3690
+ }
3691
+ await store.runs.delete({ filter: { agentIds: [trimmedAgentId] } });
3692
+ await store.checkpoints.delete({ filter: { agentIds: [trimmedAgentId] } });
3693
+ await store.agents.delete({ filter: { agentIds: [trimmedAgentId] } });
3694
+ } finally {
3695
+ await store.dispose();
3696
+ }
3697
+ return true;
3698
+ }
3699
+ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
3700
+ const trimmedTaskId = taskId.trim();
3701
+ const trimmedWorkdir = workdir.trim();
3702
+ if (!trimmedTaskId || !trimmedWorkdir) {
3703
+ return;
3704
+ }
3705
+ const agentId = loadWebIdeAgentId(trimmedWorkdir, trimmedTaskId);
3706
+ if (agentId) {
3707
+ try {
3708
+ const purged = await purgeCursorAgentStoreForAgent(
3709
+ trimmedWorkdir,
3710
+ agentId
3711
+ );
3712
+ if (purged) {
3713
+ console.log(
3714
+ `[apm] \u5DF2\u6E05\u7406 Cursor agent store agentId=${agentId} taskId=${trimmedTaskId}`
4070
3715
  );
4071
3716
  }
4072
- if (prResult.ensured === 0) {
4073
- if (prResult.failed > 0) {
4074
- throw new Error(
4075
- `\u521B\u5EFA PR \u5931\u8D25\uFF08${prResult.failed} \u4E2A\u4ED3\u5E93\u51FA\u9519\uFF09${prResult.tips[0] ? `\uFF1A${prResult.tips[0]}` : ""}`
4076
- );
4077
- }
4078
- if (prResult.skippedNoChanges > 0 && prResult.skipped === 0) {
4079
- console.log(
4080
- "[apm] webide create-pr\uFF1A\u5404\u4ED3\u5E93\u76F8\u5BF9\u63A8\u65AD\u57FA\u7EBF\u5747\u65E0\u53D8\u66F4\uFF0C\u8DF3\u8FC7\u521B\u5EFA PR"
4081
- );
4082
- } else {
4083
- throw new Error(
4084
- prResult.tips[0] || "\u672A\u80FD\u521B\u5EFA PR\uFF1A\u8BF7\u786E\u8BA4\u5404\u4ED3\u5F53\u524D\u4F4D\u4E8E feat/task-* \u5206\u652F\uFF0C\u4E14 remote \u5DF2\u5728\u5E73\u53F0\u767B\u8BB0"
4085
- );
4086
- }
3717
+ } catch (err) {
3718
+ console.warn(
3719
+ `[apm] \u6E05\u7406 Cursor agent store \u5931\u8D25 agentId=${agentId}:`,
3720
+ err instanceof Error ? err.message : err
3721
+ );
3722
+ }
3723
+ } else {
3724
+ console.log(
3725
+ `[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
3726
+ );
3727
+ }
3728
+ const dir = resolve7(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
3729
+ if (existsSync8(dir)) {
3730
+ rmSync2(dir, { recursive: true, force: true });
3731
+ console.log(`[apm] \u5DF2\u6E05\u7406 WebIDE \u7F13\u5B58 ${dir}`);
3732
+ } else {
3733
+ console.log(`[apm] WebIDE \u7F13\u5B58\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7 ${dir}`);
3734
+ }
3735
+ }
3736
+
3737
+ // src/commands/connect/webide-run-action.ts
3738
+ function startActionForInbound(action) {
3739
+ switch (action) {
3740
+ case "request-assessment":
3741
+ return "start-assessment";
3742
+ case "request-design":
3743
+ case "request-revise-design":
3744
+ return "start-design";
3745
+ case "request-write-plan":
3746
+ case "request-revise-plan":
3747
+ return "start-write-plan";
3748
+ case "request-develop":
3749
+ case "skip-plan":
3750
+ return "start-develop";
3751
+ case "request-start-project":
3752
+ return "start-project";
3753
+ case "request-execute-sql":
3754
+ return "start-execute-sql";
3755
+ case "request-bugfix":
3756
+ return "start-bugfix";
3757
+ case "request-organize-code":
3758
+ case "request-patch":
3759
+ return "start-organize-code";
3760
+ case "request-accept":
3761
+ return "start-accept";
3762
+ default:
3763
+ return null;
3764
+ }
3765
+ }
3766
+ function completeActionForInbound(action) {
3767
+ switch (action) {
3768
+ case "request-assessment":
3769
+ case "confirm-assumptions":
3770
+ return "complete-assessment";
3771
+ case "request-design":
3772
+ case "request-revise-design":
3773
+ return "complete-design";
3774
+ case "request-write-plan":
3775
+ case "request-revise-plan":
3776
+ return "complete-write-plan";
3777
+ case "request-develop":
3778
+ case "skip-plan":
3779
+ return "complete-develop";
3780
+ case "request-start-project":
3781
+ return "complete-project";
3782
+ case "request-execute-sql":
3783
+ return "complete-execute-sql";
3784
+ case "request-bugfix":
3785
+ return "complete-bugfix";
3786
+ case "request-organize-code":
3787
+ case "request-patch":
3788
+ return "complete-organize-code";
3789
+ case "request-accept":
3790
+ return "complete-accept";
3791
+ default:
3792
+ return null;
3793
+ }
3794
+ }
3795
+ async function webIdeRunAction(cfg2, taskId, action, params) {
3796
+ const api = createApmApiClient(cfg2);
3797
+ await api.cli.webideRunAction({ taskId, action, params });
3798
+ console.log(`[apm] webide runAction ok action=${action} taskId=${taskId}`);
3799
+ }
3800
+
3801
+ // src/commands/connect/webide-ask-question.ts
3802
+ function asString5(value) {
3803
+ return typeof value === "string" ? value.trim() : "";
3804
+ }
3805
+ function parseQuestions(args) {
3806
+ const title = asString5(args.title) || void 0;
3807
+ const raw = args.questions;
3808
+ if (!Array.isArray(raw) || raw.length === 0) {
3809
+ throw new Error("AskQuestion \u7F3A\u5C11 questions");
3810
+ }
3811
+ const questions = [];
3812
+ for (const item of raw) {
3813
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
3814
+ const row = item;
3815
+ const id = asString5(row.id);
3816
+ const prompt = asString5(row.prompt);
3817
+ const optionsRaw = row.options;
3818
+ if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
3819
+ const options = [];
3820
+ for (const opt of optionsRaw) {
3821
+ if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
3822
+ const o = opt;
3823
+ const oid = asString5(o.id);
3824
+ const label = asString5(o.label);
3825
+ if (oid && label) options.push({ id: oid, label });
3826
+ }
3827
+ if (options.length < 2) {
3828
+ throw new Error(`AskQuestion \u95EE\u9898 ${id} \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`);
3829
+ }
3830
+ questions.push({ id, prompt, options });
3831
+ }
3832
+ if (questions.length === 0) {
3833
+ throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
3834
+ }
3835
+ return { title, questions };
3836
+ }
3837
+ async function fetchAssumptionAnswersAndContinueAssessment(options) {
3838
+ const { cfg: cfg2, taskId, questionIds, title } = options;
3839
+ const api = createApmApiClient(cfg2);
3840
+ const list = await api.cli.webideListAssumptions({ taskId });
3841
+ const byId = new Map(list.assumptions.map((a) => [a.questionId, a]));
3842
+ const ids = questionIds && questionIds.length > 0 ? questionIds : list.assumptions.filter((a) => a.status === "RESOLVED").map((a) => a.questionId);
3843
+ if (ids.length === 0) {
3844
+ throw new Error("\u6CA1\u6709\u5DF2\u786E\u8BA4\u7684\u5047\u8BBE");
3845
+ }
3846
+ const answers = [];
3847
+ for (const id of ids) {
3848
+ const row = byId.get(id);
3849
+ if (!row || row.status !== "RESOLVED") {
3850
+ throw new Error(`\u5047\u8BBE\u5C1A\u672A\u5168\u90E8\u786E\u8BA4: ${id}`);
3851
+ }
3852
+ answers.push({
3853
+ questionId: id,
3854
+ prompt: row.prompt,
3855
+ selectedOptionId: row.selectedOptionId,
3856
+ customAnswer: row.customAnswer,
3857
+ resolution: row.resolution
3858
+ });
3859
+ }
3860
+ await webIdeRunAction(cfg2, taskId, "continue-assessment");
3861
+ console.log(
3862
+ `[apm] \u5047\u8BBE\u7ED3\u679C\u5DF2\u62C9\u53D6\u5E76\u7EE7\u7EED\u8BC4\u4F30 taskId=${taskId} count=${answers.length}`
3863
+ );
3864
+ return JSON.stringify({ title, answers }, null, 2);
3865
+ }
3866
+ function createWebIdeAskQuestionExecute(options) {
3867
+ const { cfg: cfg2, taskId, onAwaitingAssumptions } = options;
3868
+ return async (args) => {
3869
+ const parsed = parseQuestions(args);
3870
+ const api = createApmApiClient(cfg2);
3871
+ console.log(
3872
+ `[apm] AskQuestion \u521B\u5EFA\u5F85\u786E\u8BA4\u9879 taskId=${taskId} count=${parsed.questions.length}`
3873
+ );
3874
+ await api.cli.webideReplaceAssumptions({
3875
+ taskId,
3876
+ title: parsed.title,
3877
+ questions: parsed.questions
3878
+ });
3879
+ await api.cli.webideRunAction({
3880
+ taskId,
3881
+ action: "await-assumptions"
3882
+ });
3883
+ onAwaitingAssumptions?.();
3884
+ return [
3885
+ "\u5047\u8BBE\u5DF2\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u6D41\u8FDB\u5165\u300C\u5F85\u786E\u8BA4\u5047\u8BBE\u300D\u3002",
3886
+ "\u672C\u8F6E\u5FC5\u987B\u7ACB\u5373\u7ED3\u675F\uFF1A\u4E0D\u8981\u7EE7\u7EED\u8BC4\u4F30\uFF0C\u4E0D\u8981\u8C03\u7528 RecommendPlan\uFF0C\u4E0D\u8981\u518D\u6B21\u8C03\u7528 AskQuestion\u3002",
3887
+ "\u7528\u6237\u786E\u8BA4\u540E\uFF0C\u4E0B\u4E00\u8F6E\u4F1A\u5E26\u4E0A\u7B54\u6848\u5E76\u7EE7\u7EED\u8BC4\u4F30\u3002"
3888
+ ].join("");
3889
+ };
3890
+ }
3891
+
3892
+ // src/commands/connect/pre-step-cache.ts
3893
+ function taskWorkdirKey(taskId, workdir) {
3894
+ return `${taskId}\0${workdir}`;
3895
+ }
3896
+ var lastBranchKey = null;
3897
+ function shouldRunBranch(taskId, workdir) {
3898
+ return lastBranchKey !== taskWorkdirKey(taskId, workdir);
3899
+ }
3900
+ function markBranchDone(taskId, workdir) {
3901
+ lastBranchKey = taskWorkdirKey(taskId, workdir);
3902
+ }
3903
+
3904
+ // src/commands/connect/webide-pull-requests.ts
3905
+ var PRE_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set(["request-accept"]);
3906
+ var POST_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set(["request-organize-code"]);
3907
+ function shouldEnsureWebIdePullRequests(action) {
3908
+ return PRE_AGENT_PR_ACTIONS.has(action);
3909
+ }
3910
+ function shouldEnsureWebIdePullRequestsAfterAgent(action) {
3911
+ return POST_AGENT_PR_ACTIONS.has(action);
3912
+ }
3913
+ function formatWebIdePullRequestSummary(result) {
3914
+ const lines = [];
3915
+ lines.push("## PR \u521B\u5EFA\u7ED3\u679C");
3916
+ lines.push(
3917
+ `\u5408\u8BA1\uFF1A\u6210\u529F ${result.ensured} \u6761\uFF0C\u65E0\u53D8\u66F4\u8DF3\u8FC7 ${result.skippedNoChanges} \u6761\uFF0C\u5176\u4ED6\u8DF3\u8FC7 ${result.skipped} \u6761\uFF0C\u5931\u8D25 ${result.failed} \u6761\u3002`
3918
+ );
3919
+ if (result.items.length === 0) {
3920
+ lines.push("\u672A\u5904\u7406\u4EFB\u4F55\u4ED3\u5E93\u3002");
3921
+ return lines.join("\n");
3922
+ }
3923
+ lines.push("");
3924
+ for (const item of result.items) {
3925
+ if (item.outcome === "created") {
3926
+ const num = item.number != null && item.number > 0 ? `#${item.number}` : "\uFF08\u65E0\u7F16\u53F7\uFF09";
3927
+ const branch = item.headBranch && item.baseBranch ? `${item.headBranch} \u2192 ${item.baseBranch}` : item.baseBranch ? `\u76EE\u6807\u5206\u652F ${item.baseBranch}` : "";
3928
+ const state = item.state ? `\u72B6\u6001 ${item.state}` : "";
3929
+ const url = item.url?.trim() ? item.url.trim() : "";
3930
+ lines.push(
3931
+ `- **${item.label}**\uFF1A\u5DF2\u521B\u5EFA ${num}${branch ? `\uFF0C${branch}` : ""}${state ? `\uFF0C${state}` : ""}${url ? `\uFF0C${url}` : ""}`
3932
+ );
3933
+ continue;
3934
+ }
3935
+ if (item.outcome === "skipped_no_changes") {
3936
+ lines.push(
3937
+ `- **${item.label}**\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${item.baseBranch || "\uFF08\u672A\u77E5\uFF09"} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u672A\u521B\u5EFA PR`
3938
+ );
3939
+ continue;
3940
+ }
3941
+ if (item.outcome === "skipped") {
3942
+ lines.push(`- **${item.label}**\uFF1A\u5DF2\u8DF3\u8FC7 \u2014 ${item.detail}`);
3943
+ continue;
3944
+ }
3945
+ lines.push(`- **${item.label}**\uFF1A\u521B\u5EFA\u5931\u8D25 \u2014 ${item.detail}`);
3946
+ }
3947
+ return lines.join("\n");
3948
+ }
3949
+ async function ensureWebIdePullRequests(cfg2, taskId, workdir) {
3950
+ const api = createApmApiClient(cfg2);
3951
+ const empty = () => ({
3952
+ ensured: 0,
3953
+ skipped: 0,
3954
+ skippedNoChanges: 0,
3955
+ failed: 0,
3956
+ items: [],
3957
+ tips: []
3958
+ });
3959
+ let repoRoots;
3960
+ try {
3961
+ const manifest = loadWorkspaceReposCache(workdir);
3962
+ repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
3963
+ } catch (err) {
3964
+ const tip = `\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25: ${err instanceof Error ? err.message : err}`;
3965
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3966
+ return {
3967
+ ...empty(),
3968
+ failed: 1,
3969
+ tips: [tip],
3970
+ items: [
3971
+ {
3972
+ label: "\u5DE5\u4F5C\u533A",
3973
+ outcome: "failed",
3974
+ detail: tip
3975
+ }
3976
+ ]
3977
+ };
3978
+ }
3979
+ let projectBaseBranch = "";
3980
+ try {
3981
+ const baseline = await api.cli.projectBaseBranch({ taskId });
3982
+ projectBaseBranch = baseline.baseBranch?.trim() ?? "";
3983
+ } catch (err) {
3984
+ const tip = `\u8BFB\u53D6\u9879\u76EE\u57FA\u7EBF\u5206\u652F\u5931\u8D25: ${err instanceof Error ? err.message : err}`;
3985
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3986
+ return {
3987
+ ...empty(),
3988
+ failed: 1,
3989
+ tips: [tip],
3990
+ items: [{ label: "\u5DE5\u4F5C\u533A", outcome: "failed", detail: tip }]
3991
+ };
3992
+ }
3993
+ if (!projectBaseBranch) {
3994
+ const tip = "\u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F\uFF0C\u65E0\u6CD5\u521B\u5EFA PR";
3995
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3996
+ return {
3997
+ ...empty(),
3998
+ failed: 1,
3999
+ tips: [tip],
4000
+ items: [{ label: "\u5DE5\u4F5C\u533A", outcome: "failed", detail: tip }]
4001
+ };
4002
+ }
4003
+ const expectedHead = `feat/task-${taskId}`;
4004
+ console.log(
4005
+ `[apm] WebIDE PR\uFF1A\u51C6\u5907\u68C0\u67E5 ${repoRoots.length} \u4E2A\u4ED3\u5E93\uFF08\u671F\u671B\u7279\u6027\u5206\u652F ${expectedHead}\uFF0C\u9879\u76EE\u57FA\u7EBF ${projectBaseBranch}\uFF09`
4006
+ );
4007
+ let ensured = 0;
4008
+ let skipped = 0;
4009
+ let skippedNoChanges = 0;
4010
+ let failed = 0;
4011
+ const tips = [];
4012
+ const items = [];
4013
+ for (const gitRoot of repoRoots) {
4014
+ const label = formatRepoLabel(workdir, gitRoot);
4015
+ try {
4016
+ const current = await getCurrentBranch(gitRoot);
4017
+ if (!isFeatureBranchName(current)) {
4018
+ const tip = `${label}\uFF1A\u5F53\u524D\u5206\u652F ${current} \u4E0D\u662F feat/task-*\uFF0C\u4E0D\u7B26\u5408\u521B\u5EFA PR \u6761\u4EF6`;
4019
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
4020
+ tips.push(tip);
4021
+ skipped += 1;
4022
+ items.push({
4023
+ label,
4024
+ outcome: "skipped",
4025
+ headBranch: current,
4026
+ detail: tip
4027
+ });
4028
+ continue;
4029
+ }
4030
+ if (current !== expectedHead) {
4031
+ const tip = `${label}\uFF1A\u5F53\u524D\u4E3A ${current}\uFF0C\u671F\u671B ${expectedHead}\uFF0C\u5DF2\u8DF3\u8FC7`;
4032
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
4033
+ tips.push(tip);
4034
+ skipped += 1;
4035
+ items.push({
4036
+ label,
4037
+ outcome: "skipped",
4038
+ headBranch: current,
4039
+ detail: tip
4040
+ });
4041
+ continue;
4087
4042
  }
4088
- }
4089
- if (projectId) {
4090
- try {
4091
- await syncProjectDocumentsPush(cfg, workdir, void 0, { projectId });
4092
- } catch (err) {
4093
- console.warn(
4094
- "[apm] WebIDE \u9879\u76EE\u6587\u6863\u63A8\u9001\u5931\u8D25:",
4095
- err instanceof Error ? err.message : err
4096
- );
4043
+ const headBranch = current;
4044
+ const originUrl = await tryReadHttpsGitOriginUrl(gitRoot) ?? await tryReadGitOriginUrl(gitRoot);
4045
+ if (!originUrl) {
4046
+ const tip = `${label}\uFF1A\u65E0 remote.origin.url`;
4047
+ console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${tip}`);
4048
+ tips.push(tip);
4049
+ skipped += 1;
4050
+ items.push({
4051
+ label,
4052
+ outcome: "skipped",
4053
+ headBranch,
4054
+ detail: tip
4055
+ });
4056
+ continue;
4097
4057
  }
4098
- }
4099
- await logSyncRef.current?.markRun(
4100
- outcome.runId,
4101
- "finished",
4102
- null,
4103
- tokenUsage
4104
- );
4105
- await updateStatus(cfg, messageId, "SUCCESS");
4106
- syncLocalTaskStatus(workdir, taskId, msg, "SUCCESS");
4107
- console.log(
4108
- `[apm] webide-message \u5B8C\u6210 action=${msg.action} messageId=${messageId} agentId=${outcome.agentId}`
4109
- );
4110
- if (msg.action === "accept") {
4111
- try {
4112
- await cleanWebIdeWorkspaceCache(taskId, workdir);
4113
- } catch (err) {
4114
- console.warn(
4115
- "[apm] \u5408\u5E76 PR \u540E\u6E05\u7406 WebIDE agent \u7F13\u5B58\u5931\u8D25:",
4116
- err instanceof Error ? err.message : err
4117
- );
4058
+ const baseBranch = projectBaseBranch;
4059
+ const hasChanges = await branchHasChangesAgainstBase(
4060
+ gitRoot,
4061
+ baseBranch,
4062
+ headBranch
4063
+ );
4064
+ if (!hasChanges) {
4065
+ const tip = `${label}\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${baseBranch} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u65E0\u9700\u521B\u5EFA PR`;
4066
+ console.log(`[apm] WebIDE PR\uFF1A${tip}`);
4067
+ tips.push(tip);
4068
+ skippedNoChanges += 1;
4069
+ items.push({
4070
+ label,
4071
+ outcome: "skipped_no_changes",
4072
+ headBranch,
4073
+ baseBranch,
4074
+ detail: tip
4075
+ });
4076
+ continue;
4118
4077
  }
4119
- }
4120
- } catch (err) {
4121
- const detail = err instanceof Error ? err.message : String(err);
4122
- console.error(`[apm] webide-message \u5931\u8D25: ${detail}`);
4123
- if (detail.includes("\u590D\u7528 Agent") || detail.includes("resume")) {
4124
- clearWebIdeAgentId(workdir, taskId);
4125
- }
4126
- try {
4127
- await setError(cfg, messageId, detail);
4128
- } catch (statusErr) {
4129
- console.error(
4130
- "[apm] \u5199\u5165 WebIDE FAILED \u5931\u8D25:",
4131
- statusErr instanceof Error ? statusErr.message : statusErr
4078
+ const pr = await api.cli.createWebIdeDraftPullRequest({
4079
+ taskId,
4080
+ originUrl,
4081
+ headBranch,
4082
+ baseBranch
4083
+ });
4084
+ ensured += 1;
4085
+ items.push({
4086
+ label,
4087
+ outcome: "created",
4088
+ headBranch,
4089
+ baseBranch,
4090
+ number: pr.number,
4091
+ state: pr.state,
4092
+ url: pr.url,
4093
+ detail: `\u5DF2\u521B\u5EFA #${pr.number} ${headBranch} \u2192 ${baseBranch}`
4094
+ });
4095
+ console.log(
4096
+ `[apm] WebIDE PR\uFF1A${label} \u2192 #${pr.number} ${pr.state} ${headBranch} \u2192 ${baseBranch} ${pr.url || ""}`
4132
4097
  );
4098
+ } catch (err) {
4099
+ failed += 1;
4100
+ const tip = `${label}\uFF1A${err instanceof Error ? err.message : err}`;
4101
+ console.warn(`[apm] WebIDE PR\uFF1A\u5931\u8D25 ${tip}`);
4102
+ tips.push(tip);
4103
+ items.push({
4104
+ label,
4105
+ outcome: "failed",
4106
+ detail: tip
4107
+ });
4133
4108
  }
4134
- syncLocalTaskStatus(workdir, taskId, msg, "FAILED");
4135
4109
  }
4110
+ return { ensured, skipped, skippedNoChanges, failed, items, tips };
4136
4111
  }
4137
4112
 
4138
- // src/commands/connect/handle-start-project.ts
4139
- async function updateStatus2(cfg, messageId, status) {
4140
- const api = createApmApiClient(cfg);
4141
- await api.cli.webideUpdateMessageStatus({ id: messageId, status });
4142
- }
4143
- async function setError2(cfg, messageId, error) {
4144
- const api = createApmApiClient(cfg);
4145
- await api.cli.webideSetMessageError({ id: messageId, error });
4113
+ // src/commands/sync-webide-attachments.ts
4114
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
4115
+ import { join as join7 } from "path";
4116
+ var MANIFEST_FILE2 = ".sync-manifest.json";
4117
+ async function downloadAttachment(cfg2, attachmentId) {
4118
+ const base = cfg2.baseUrl.trim().replace(/\/+$/, "");
4119
+ const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
4120
+ attachmentId
4121
+ })}`;
4122
+ const res = await fetch(url);
4123
+ if (!res.ok) {
4124
+ throw new Error(
4125
+ `[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
4126
+ );
4127
+ }
4128
+ return Buffer.from(await res.arrayBuffer());
4146
4129
  }
4147
- async function handleStartProject(cfg, msg, signal) {
4148
- const workdir = requireRemoteWorkdir(msg.workdir);
4149
- const messageId = msg.messageId;
4150
- const taskId = msg.taskId;
4151
- console.log(
4152
- `[apm] webide-start-project target=${msg.target} restart=${msg.restart} taskId=${taskId} messageId=${messageId}`
4153
- );
4154
- await updateStatus2(cfg, messageId, "TYPING");
4155
- const eventSession = new EventSession(msg.content);
4156
- const logSyncRef = {
4157
- current: createThrottledWebIdeMessageLogSync(
4158
- cfg,
4159
- { taskId, messageId, agentId: "webide-start-project" },
4160
- (err) => {
4161
- console.warn(
4162
- "[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
4163
- err instanceof Error ? err.message : err
4164
- );
4165
- }
4166
- )
4167
- };
4130
+ function loadManifest2(dir) {
4131
+ const path3 = join7(dir, MANIFEST_FILE2);
4132
+ if (!existsSync9(path3)) {
4133
+ return { version: 1, attachments: {} };
4134
+ }
4168
4135
  try {
4169
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4170
- await ensureWorkspaceInitialized(workdir);
4171
- const api = createApmApiClient(cfg);
4172
- const outcome = await runCursorAgent(
4173
- cfg,
4174
- {
4175
- messageId,
4176
- prompt: msg.content,
4177
- model: msg.model,
4178
- apiKey: msg.apiKey,
4179
- workdir,
4180
- user: msg.user || "webide"
4181
- },
4182
- {
4183
- signal,
4184
- forceSend: true,
4185
- eventSession,
4186
- enableAppendMessage: false,
4187
- enableAskQuestion: false,
4188
- enableWebIdePlanTools: false,
4189
- enablePtySessionMcp: true,
4190
- enableSandbox: false,
4191
- taskId,
4192
- createRemoteLogSync: (agentId) => {
4193
- logSyncRef.current = createThrottledWebIdeMessageLogSync(
4194
- cfg,
4195
- { taskId, messageId, agentId },
4196
- (err) => {
4197
- console.warn(
4198
- "[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
4199
- err instanceof Error ? err.message : err
4200
- );
4201
- }
4202
- );
4203
- return logSyncRef.current;
4204
- },
4205
- onRunStarted: async ({ agentId, runId }) => {
4206
- eventSession.addCliStep(
4207
- "\u542F\u52A8\u72EC\u7ACB Agent",
4208
- `agentId=${agentId} runId=${runId}`,
4209
- "ok"
4210
- );
4211
- logSyncRef.current?.schedule(eventSession);
4212
- await logSyncRef.current?.markRun(runId, "running");
4213
- await logSyncRef.current?.flush(eventSession);
4214
- }
4215
- }
4216
- );
4217
- const tokenUsage = outcome.usage != null ? {
4218
- modelId: outcome.modelId ?? (msg.model?.trim() || void 0),
4219
- inputTokens: outcome.usage.inputTokens,
4220
- outputTokens: outcome.usage.outputTokens,
4221
- cacheReadTokens: outcome.usage.cacheReadTokens,
4222
- cacheWriteTokens: outcome.usage.cacheWriteTokens,
4223
- totalTokens: outcome.usage.totalTokens,
4224
- ...outcome.usage.reasoningTokens != null ? { reasoningTokens: outcome.usage.reasoningTokens } : {}
4225
- } : void 0;
4226
- if (outcome.status === "error") {
4227
- const detail = formatCursorRunFailure(outcome.runId, {
4228
- resultText: outcome.result
4229
- });
4230
- await logSyncRef.current?.markRun(
4231
- outcome.runId,
4232
- "error",
4233
- detail,
4234
- tokenUsage
4235
- );
4236
- await setError2(cfg, messageId, detail);
4237
- return;
4238
- }
4239
- if (outcome.status === "cancelled" || signal.aborted) {
4240
- await logSyncRef.current?.markRun(
4241
- outcome.runId,
4242
- "cancelled",
4243
- "\u5DF2\u53D6\u6D88",
4244
- tokenUsage
4245
- );
4246
- await updateStatus2(cfg, messageId, "CANCELLED");
4247
- return;
4248
- }
4249
- const fallback = resolveMessageReplyFallback({
4250
- assistantText: outcome.assistantText,
4251
- result: outcome.result
4252
- });
4253
- await api.cli.webideEnsureMessageContent({
4254
- id: messageId,
4255
- content: fallback
4256
- });
4257
- await logSyncRef.current?.markRun(
4258
- outcome.runId,
4259
- "finished",
4260
- null,
4261
- tokenUsage
4262
- );
4263
- await updateStatus2(cfg, messageId, "SUCCESS");
4264
- console.log(
4265
- `[apm] webide-start-project \u5B8C\u6210 target=${msg.target} messageId=${messageId} agentId=${outcome.agentId}`
4136
+ const parsed = JSON.parse(
4137
+ readFileSync8(path3, "utf8")
4266
4138
  );
4267
- } catch (err) {
4268
- const detail = err instanceof Error ? err.message : String(err);
4269
- console.error(`[apm] webide-start-project \u5931\u8D25: ${detail}`);
4270
- try {
4271
- await setError2(cfg, messageId, detail);
4272
- } catch (statusErr) {
4273
- console.error(
4274
- "[apm] \u5199\u5165\u542F\u52A8\u9879\u76EE FAILED \u5931\u8D25:",
4275
- statusErr instanceof Error ? statusErr.message : statusErr
4276
- );
4139
+ if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
4140
+ return parsed;
4277
4141
  }
4142
+ } catch {
4278
4143
  }
4144
+ return { version: 1, attachments: {} };
4279
4145
  }
4280
-
4281
- // src/commands/connect/upload-package-artifact.ts
4282
- import fs2 from "node:fs";
4283
- import path2 from "node:path";
4284
- function packageTmpDir(packageId) {
4285
- return `/data/package-tmp/${packageId}`;
4146
+ function saveManifest2(dir, manifest) {
4147
+ writeFileSync6(
4148
+ join7(dir, MANIFEST_FILE2),
4149
+ `${JSON.stringify(manifest, null, 2)}
4150
+ `,
4151
+ "utf8"
4152
+ );
4286
4153
  }
4287
- function expectedLocalArtifactPath(target, packageId) {
4288
- const fileName = target === "frontend" ? "dist.zip" : "jars.zip";
4289
- return path2.join(packageTmpDir(packageId), fileName);
4154
+ function isAttachmentUpToDate(entry, item, dest) {
4155
+ if (!entry || !existsSync9(dest)) return false;
4156
+ if (entry.name !== item.name) return false;
4157
+ const createdAt = item.createdAt ?? "";
4158
+ return entry.createdAt === createdAt;
4290
4159
  }
4291
- function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
4292
- const folder = target === "frontend" ? "vue" : "java";
4293
- const suffix = target === "frontend" ? "dist.zip" : "jar.zip";
4294
- return `packages/${projectId}/${folder}/${timestampMs}.${suffix}`;
4160
+ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
4161
+ await ensureDirExists(dir);
4162
+ if (attachments.length === 0) {
4163
+ saveManifest2(dir, { version: 1, attachments: {} });
4164
+ return [];
4165
+ }
4166
+ const manifest = loadManifest2(dir);
4167
+ const nextManifest = { version: 1, attachments: {} };
4168
+ const names = [];
4169
+ for (const item of attachments) {
4170
+ const dest = join7(dir, item.name);
4171
+ const entry = manifest.attachments[item.id];
4172
+ const createdAt = item.createdAt ?? "";
4173
+ names.push(item.name);
4174
+ if (isAttachmentUpToDate(entry, item, dest)) {
4175
+ nextManifest.attachments[item.id] = entry;
4176
+ console.log(`[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${logLabel}/${item.name}`);
4177
+ continue;
4178
+ }
4179
+ const buffer = await downloadAttachment(cfg2, item.id);
4180
+ writeFileSync6(dest, buffer);
4181
+ nextManifest.attachments[item.id] = {
4182
+ name: item.name,
4183
+ createdAt
4184
+ };
4185
+ console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${logLabel}/${item.name}`);
4186
+ }
4187
+ saveManifest2(dir, nextManifest);
4188
+ return names;
4295
4189
  }
4296
- function removePackageTmpDir(packageId) {
4297
- const dir = packageTmpDir(packageId);
4190
+ async function syncWebIdeAttachments(cfg2, taskId, workdir, attachments) {
4191
+ const dir = webideAttachmentsDir(taskId, workdir);
4192
+ return syncAttachmentsToDirectory(
4193
+ cfg2,
4194
+ attachments,
4195
+ dir,
4196
+ `.apm/webide/${taskId}/attachments`
4197
+ );
4198
+ }
4199
+
4200
+ // src/commands/sync-webide-design.ts
4201
+ import { createHash as createHash3 } from "crypto";
4202
+ import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
4203
+ import { join as join8 } from "path";
4204
+ var MANIFEST_FILE3 = ".sync-manifest.json";
4205
+ function hashBuffer(buf) {
4206
+ return createHash3("sha256").update(buf).digest("hex");
4207
+ }
4208
+ function loadManifest3(dir) {
4209
+ const path3 = join8(dir, MANIFEST_FILE3);
4210
+ if (!existsSync10(path3)) {
4211
+ return { version: 1, files: {} };
4212
+ }
4298
4213
  try {
4299
- fs2.rmSync(dir, { recursive: true, force: true });
4300
- } catch (err) {
4301
- console.warn(
4302
- `[apm] \u6E05\u7406\u6253\u5305\u4E34\u65F6\u76EE\u5F55\u5931\u8D25 ${dir}:`,
4303
- err instanceof Error ? err.message : err
4214
+ const parsed = JSON.parse(readFileSync9(path3, "utf8"));
4215
+ if (parsed?.version === 1 && parsed.files && typeof parsed.files === "object") {
4216
+ return parsed;
4217
+ }
4218
+ } catch {
4219
+ }
4220
+ return { version: 1, files: {} };
4221
+ }
4222
+ function saveManifest3(dir, manifest) {
4223
+ writeFileSync7(
4224
+ join8(dir, MANIFEST_FILE3),
4225
+ `${JSON.stringify(manifest, null, 2)}
4226
+ `,
4227
+ "utf8"
4228
+ );
4229
+ }
4230
+ async function downloadDesignFile(cfg2, taskId, name) {
4231
+ const base = cfg2.baseUrl.trim().replace(/\/+$/, "");
4232
+ const apiKey = resolveApiKey(cfg2);
4233
+ const params = new URLSearchParams({ taskId, name });
4234
+ if (apiKey) params.set("apiKey", apiKey);
4235
+ const url = `${base}/api/v1/cli/webide/design-artifacts/file?${params}`;
4236
+ const res = await fetch(url);
4237
+ if (!res.ok) {
4238
+ throw new Error(
4239
+ `[apm] \u4E0B\u8F7D\u8BBE\u8BA1\u6587\u4EF6\u5931\u8D25 (${res.status}): taskId=${taskId} name=${name}`
4304
4240
  );
4305
4241
  }
4242
+ return Buffer.from(await res.arrayBuffer());
4306
4243
  }
4307
- async function uploadPackageArtifact(cfg, target, packageId, projectId) {
4308
- const localPath = expectedLocalArtifactPath(target, packageId);
4309
- if (!fs2.existsSync(localPath)) {
4310
- throw new Error(`\u6253\u5305\u4EA7\u7269\u4E0D\u5B58\u5728: ${localPath}`);
4244
+ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
4245
+ const dir = webideDesignDir(taskId, workdir);
4246
+ await ensureDirExists(dir);
4247
+ const manifest = loadManifest3(dir);
4248
+ const synced = [];
4249
+ for (const art of artifacts) {
4250
+ const name = art.name?.trim();
4251
+ if (!name || !name.toLowerCase().endsWith(".html")) continue;
4252
+ const localPath = join8(dir, name);
4253
+ const entry = manifest.files[name];
4254
+ if (entry?.contentHash === art.contentHash && existsSync10(localPath)) {
4255
+ continue;
4256
+ }
4257
+ const buf = await downloadDesignFile(cfg2, taskId, name);
4258
+ writeFileSync7(localPath, buf);
4259
+ manifest.files[name] = { contentHash: hashBuffer(buf) };
4260
+ synced.push(name);
4311
4261
  }
4312
- const stat = fs2.statSync(localPath);
4313
- if (!stat.isFile() || stat.size <= 0) {
4314
- throw new Error(`\u6253\u5305\u4EA7\u7269\u65E0\u6548\u6216\u4E3A\u7A7A: ${localPath}`);
4262
+ saveManifest3(dir, manifest);
4263
+ return synced;
4264
+ }
4265
+ function listLocalDesignHtmlFiles(taskId, workdir) {
4266
+ const dir = webideDesignDir(taskId, workdir);
4267
+ if (!existsSync10(dir)) return [];
4268
+ const out = [];
4269
+ for (const name of readdirSync4(dir)) {
4270
+ if (!name.toLowerCase().endsWith(".html")) continue;
4271
+ const absPath = join8(dir, name);
4272
+ const buf = readFileSync9(absPath);
4273
+ if (buf.length === 0) continue;
4274
+ out.push({ name, absPath, contentHash: hashBuffer(buf) });
4315
4275
  }
4316
- const objectKey = packageArtifactObjectKey(target, projectId);
4317
- const { client, bucket, publicEndpoint } = await createApmLogMinioClient(cfg);
4276
+ return out;
4277
+ }
4278
+
4279
+ // src/opc-shared-types.ts
4280
+ function webIdeDesignObjectKey(taskId, fileName) {
4281
+ const safe = fileName.replace(/[/\\:*?"<>|]/g, "_").trim() || "index.html";
4282
+ return `tasks/${taskId.trim()}/design/${safe}`;
4283
+ }
4284
+
4285
+ // src/commands/connect/upload-webide-design.ts
4286
+ async function uploadWebIdeDesignArtifacts(cfg2, taskId, workdir) {
4287
+ const locals = listLocalDesignHtmlFiles(taskId, workdir);
4288
+ if (locals.length === 0) {
4289
+ throw new Error(
4290
+ `\u8BBE\u8BA1\u76EE\u5F55\u65E0 HTML \u4EA7\u7269\uFF1A.apm/webide/${taskId}/design/\uFF08\u8BF7\u5199\u5165\u81F3\u5C11\u4E00\u4EFD .html\uFF09`
4291
+ );
4292
+ }
4293
+ const api = createApmApiClient(cfg2);
4294
+ const remote = await api.cli.webideListDesignArtifacts({ taskId }) ?? [];
4295
+ const remoteByName = new Map(
4296
+ remote.map((r) => [r.name, r])
4297
+ );
4298
+ const { client, bucket } = await createApmLogMinioClient(cfg2);
4318
4299
  await client.ensureBucket(bucket);
4319
- await client.fPutObject(bucket, objectKey, localPath, {
4320
- "Content-Type": "application/zip"
4300
+ let uploaded = 0;
4301
+ let skipped = 0;
4302
+ const artifacts = [];
4303
+ for (const file of locals) {
4304
+ const objectKey = webIdeDesignObjectKey(taskId, file.name);
4305
+ const prev = remoteByName.get(file.name);
4306
+ if (prev?.contentHash === file.contentHash) {
4307
+ skipped += 1;
4308
+ artifacts.push({
4309
+ name: file.name,
4310
+ objectKey: prev.objectKey || objectKey,
4311
+ contentHash: file.contentHash
4312
+ });
4313
+ continue;
4314
+ }
4315
+ await client.fPutObject(bucket, objectKey, file.absPath, {
4316
+ "Content-Type": "text/html; charset=utf-8"
4317
+ });
4318
+ uploaded += 1;
4319
+ artifacts.push({
4320
+ name: file.name,
4321
+ objectKey,
4322
+ contentHash: file.contentHash
4323
+ });
4324
+ }
4325
+ await api.cli.webideRegisterDesignArtifacts({
4326
+ taskId,
4327
+ artifacts
4321
4328
  });
4322
- removePackageTmpDir(packageId);
4323
- const base = publicEndpoint?.replace(/\/$/, "") ?? "";
4324
- const artifactUrl = base ? `${base}/${bucket}/${objectKey}` : null;
4325
- return { artifactPath: objectKey, artifactUrl };
4329
+ return { uploaded, skipped, artifacts };
4326
4330
  }
4327
4331
 
4328
- // src/commands/connect/handle-package.ts
4329
- async function updatePackageStatus(cfg, packageId, status, extra) {
4330
- const api = createApmApiClient(cfg);
4331
- await api.cli.projectPackageUpdateStatus({
4332
- id: packageId,
4333
- status,
4334
- ...extra?.error != null ? { error: extra.error } : {},
4335
- ...extra?.content != null ? { content: extra.content } : {},
4336
- ...extra?.artifactPath != null ? { artifactPath: extra.artifactPath } : {},
4337
- ...extra?.artifactUrl != null ? { artifactUrl: extra.artifactUrl } : {}
4332
+ // src/commands/connect/parse-webide-prompt-payload.ts
4333
+ function parseWebIdePromptPayload(content) {
4334
+ const marker = "\n\npayload=";
4335
+ const index = content.lastIndexOf(marker);
4336
+ if (index < 0) return null;
4337
+ const jsonText = content.slice(index + marker.length).trim();
4338
+ if (!jsonText) return null;
4339
+ try {
4340
+ const parsed = JSON.parse(jsonText);
4341
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4342
+ return parsed;
4343
+ }
4344
+ } catch {
4345
+ return null;
4346
+ }
4347
+ return null;
4348
+ }
4349
+
4350
+ // src/commands/connect/core/worker/handlers/webide-message.ts
4351
+ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
4352
+ "skip-plan",
4353
+ "request-develop",
4354
+ "request-bugfix",
4355
+ "request-organize-code",
4356
+ "request-patch"
4357
+ ]);
4358
+ function shouldCommitAfterWebIdeMessage(action) {
4359
+ return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
4360
+ }
4361
+ function isStartLocalServicesAction(_action) {
4362
+ return false;
4363
+ }
4364
+ function isDesignAction(action) {
4365
+ return action === "request-design" || action === "request-revise-design";
4366
+ }
4367
+ function isExecuteSqlAction(action) {
4368
+ return action === "request-execute-sql";
4369
+ }
4370
+ function syncLocalTaskStatus(workdir, taskId, msg2, status) {
4371
+ syncWebIdeTaskState(workdir, taskId, {
4372
+ messageId: msg2.messageId,
4373
+ action: msg2.action,
4374
+ status
4338
4375
  });
4339
4376
  }
4340
- async function handleWebIdePackage(cfg, msg, signal) {
4341
- const workdir = requireRemoteWorkdir(msg.workdir);
4342
- const messageId = msg.messageId;
4343
- const packageId = msg.packageId;
4344
- const projectId = msg.projectId;
4377
+ async function updateStatus2(cfg2, messageId, status) {
4378
+ const api = createApmApiClient(cfg2);
4379
+ await api.cli.webideUpdateMessageStatus({ id: messageId, status });
4380
+ }
4381
+ async function setError2(cfg2, messageId, error) {
4382
+ const api = createApmApiClient(cfg2);
4383
+ await api.cli.webideSetMessageError({ id: messageId, error });
4384
+ }
4385
+ async function appendContent(cfg2, messageId, content) {
4386
+ const api = createApmApiClient(cfg2);
4387
+ await api.cli.webideAppendMessageContent({ id: messageId, content });
4388
+ }
4389
+ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
4390
+ const workdir = requireRemoteWorkdir(msg2.workdir);
4391
+ const messageId = msg2.messageId;
4392
+ const taskId = msg2.taskId;
4393
+ const branchCacheKey = taskId;
4345
4394
  console.log(
4346
- `[apm] webide-package target=${msg.target} packageId=${packageId} projectId=${projectId} baseBranch=${msg.baseBranch}`
4395
+ `[apm] webide-message action=${msg2.action} taskId=${taskId} messageId=${messageId}`
4347
4396
  );
4348
- await updatePackageStatus(cfg, packageId, "RUNNING");
4349
- const eventSession = new EventSession(msg.content);
4397
+ await updateStatus2(cfg2, messageId, "TYPING");
4398
+ syncLocalTaskStatus(workdir, taskId, msg2, "TYPING");
4399
+ const startAction = startActionForInbound(msg2.action);
4400
+ if (startAction) {
4401
+ await webIdeRunAction(cfg2, taskId, startAction);
4402
+ }
4403
+ const eventSession = new EventSession(msg2.content);
4404
+ const prepAgentId = loadWebIdeAgentId(workdir, taskId) ?? "webide-cli-prep";
4350
4405
  const logSyncRef = {
4351
4406
  current: createThrottledWebIdeMessageLogSync(
4352
- cfg,
4353
- {
4354
- taskId: `package:${packageId}`,
4355
- messageId,
4356
- agentId: "webide-package"
4357
- },
4407
+ cfg2,
4408
+ { taskId, messageId, agentId: prepAgentId },
4358
4409
  (err) => {
4359
4410
  console.warn(
4360
- "[apm] WebIDE \u6253\u5305\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
4411
+ "[apm] WebIDE \u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
4361
4412
  err instanceof Error ? err.message : err
4362
4413
  );
4363
- },
4364
- {
4365
- objectPrefixOverride: `events/webide-package/${packageId}/`
4366
4414
  }
4367
4415
  )
4368
4416
  };
@@ -4389,6 +4437,13 @@ async function handleWebIdePackage(cfg, msg, signal) {
4389
4437
  };
4390
4438
  try {
4391
4439
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4440
+ let assumptionAnswersText;
4441
+ if (msg2.action === "confirm-assumptions") {
4442
+ assumptionAnswersText = await fetchAssumptionAnswersAndContinueAssessment(
4443
+ { cfg: cfg2, taskId }
4444
+ );
4445
+ }
4446
+ let pausedForAssumptions = false;
4392
4447
  await runPrepStep(
4393
4448
  "\u521D\u59CB\u5316\u5DE5\u4F5C\u533A",
4394
4449
  async () => {
@@ -4422,91 +4477,209 @@ async function handleWebIdePackage(cfg, msg, signal) {
4422
4477
  async () => {
4423
4478
  const workspaceRepos = resolveWorkspaceRepos(workdir);
4424
4479
  try {
4425
- await enrichWorkspaceReposRemoteUrls(workspaceRepos);
4480
+ await enrichWorkspaceReposRemoteUrls(workspaceRepos);
4481
+ } catch (err) {
4482
+ console.warn(
4483
+ "[apm] \u8865\u9F50 workspace-repos remoteUrl \u5931\u8D25:",
4484
+ err instanceof Error ? err.message : err
4485
+ );
4486
+ }
4487
+ return workspaceRepos;
4488
+ },
4489
+ (repos) => `kind=${repos.kind} repos=${repos.repos.length}`
4490
+ );
4491
+ if (shouldRunBranch(branchCacheKey, workdir)) {
4492
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4493
+ await runPrepStep(
4494
+ "\u51C6\u5907\u7279\u6027\u5206\u652F",
4495
+ async () => runTaskBranch(taskId, { cwd: workdir }),
4496
+ (branchResult) => {
4497
+ markBranchDone(branchCacheKey, workdir);
4498
+ return `kind=${branchResult.kind} branch=${branchResult.branch} baseline=${branchResult.baseline} repos=${branchResult.repos.length}`;
4499
+ }
4500
+ );
4501
+ } else {
4502
+ eventSession.addCliStep("\u51C6\u5907\u7279\u6027\u5206\u652F", "\u5DF2\u7F13\u5B58\uFF0C\u8DF3\u8FC7", "skip");
4503
+ await syncPrepLog();
4504
+ }
4505
+ if (shouldEnsureWebIdePullRequests(msg2.action)) {
4506
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4507
+ await runPrepStep(
4508
+ "\u6D4B\u8BD5\u524D\u63A8\u9001",
4509
+ async () => pushWorkspaceRepos(workdir),
4510
+ (pushed) => `\u5DF2 push ${pushed} \u4E2A\u4ED3\u5E93`
4511
+ );
4512
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4513
+ await runPrepStep(
4514
+ "\u786E\u4FDD PR",
4515
+ async () => {
4516
+ await ensureWebIdePullRequests(cfg2, taskId, workdir);
4517
+ return true;
4518
+ },
4519
+ () => "PR \u68C0\u67E5\u5B8C\u6210"
4520
+ );
4521
+ }
4522
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4523
+ const api = createApmApiClient(cfg2);
4524
+ let projectId = null;
4525
+ await runPrepStep(
4526
+ "\u540C\u6B65\u9879\u76EE\u6587\u6863",
4527
+ async () => {
4528
+ try {
4529
+ const baseline = await api.cli.projectBaseBranch({ taskId });
4530
+ projectId = baseline.projectId?.trim() || null;
4531
+ if (!projectId) {
4532
+ return { synced: false, reason: "\u65E0 projectId" };
4533
+ }
4534
+ const docs = await syncProjectDocumentsPull(workdir, void 0, {
4535
+ projectId
4536
+ });
4537
+ return {
4538
+ synced: docs.synced,
4539
+ downloaded: docs.downloaded,
4540
+ deleted: docs.deleted
4541
+ };
4542
+ } catch (err) {
4543
+ console.warn(
4544
+ "[apm] WebIDE \u9879\u76EE\u6587\u6863\u540C\u6B65\u5931\u8D25:",
4545
+ err instanceof Error ? err.message : err
4546
+ );
4547
+ return {
4548
+ synced: false,
4549
+ reason: err instanceof Error ? err.message : String(err)
4550
+ };
4551
+ }
4552
+ },
4553
+ (result) => {
4554
+ if ("reason" in result && result.reason) {
4555
+ return `\u8DF3\u8FC7\uFF1A${result.reason}`;
4556
+ }
4557
+ if (!result.synced) return "\u65E0\u9700\u540C\u6B65";
4558
+ return `downloaded=${result.downloaded ?? 0} deleted=${result.deleted ?? 0}`;
4559
+ }
4560
+ );
4561
+ await runPrepStep(
4562
+ "\u540C\u6B65\u4EFB\u52A1\u9644\u4EF6",
4563
+ async () => {
4564
+ try {
4565
+ const attachments = await api.cli.webideListAttachments({ taskId });
4566
+ const names = await syncWebIdeAttachments(
4567
+ cfg2,
4568
+ taskId,
4569
+ workdir,
4570
+ attachments ?? []
4571
+ );
4572
+ return names;
4426
4573
  } catch (err) {
4427
4574
  console.warn(
4428
- "[apm] \u8865\u9F50 workspace-repos remoteUrl \u5931\u8D25:",
4575
+ "[apm] WebIDE \u9644\u4EF6\u540C\u6B65\u5931\u8D25:",
4429
4576
  err instanceof Error ? err.message : err
4430
4577
  );
4578
+ return [];
4431
4579
  }
4432
- return workspaceRepos;
4433
4580
  },
4434
- (repos) => `kind=${repos.kind} repos=${repos.repos.length}`
4435
- );
4436
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4437
- await runPrepStep(
4438
- "\u5207\u6362\u57FA\u7EBF\u5E76\u62C9\u53D6\u6700\u65B0",
4439
- async () => checkoutBaselineBranches(msg.baseBranch, { cwd: workdir }),
4440
- (result) => `baseline=${result.baseline} kind=${result.kind} repos=${result.repos.length}`
4581
+ (names) => names.length > 0 ? `\u5DF2\u540C\u6B65 ${names.length} \u4E2A\u9644\u4EF6` : "\u65E0\u9644\u4EF6\u6216\u540C\u6B65\u8DF3\u8FC7"
4441
4582
  );
4442
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4443
4583
  await runPrepStep(
4444
- "\u540C\u6B65\u9879\u76EE\u6587\u6863",
4584
+ "\u540C\u6B65\u8BBE\u8BA1\u4EA7\u7269",
4445
4585
  async () => {
4446
- const docs = await syncProjectDocumentsPull(workdir, void 0, {
4447
- projectId
4448
- });
4449
- return docs;
4450
- },
4451
- (result) => {
4452
- if (!result.synced) {
4453
- return result.projectId ? "\u65E0\u9700\u540C\u6B65\u6216\u8DF3\u8FC7" : "\u8DF3\u8FC7\uFF1A\u65E0 projectId";
4586
+ try {
4587
+ const artifacts = await api.cli.webideListDesignArtifacts({ taskId });
4588
+ const names = await syncWebIdeDesign(
4589
+ cfg2,
4590
+ taskId,
4591
+ workdir,
4592
+ artifacts ?? []
4593
+ );
4594
+ return names;
4595
+ } catch (err) {
4596
+ console.warn(
4597
+ "[apm] WebIDE \u8BBE\u8BA1\u4EA7\u7269\u540C\u6B65\u5931\u8D25:",
4598
+ err instanceof Error ? err.message : err
4599
+ );
4600
+ return [];
4454
4601
  }
4455
- return `downloaded=${result.downloaded} deleted=${result.deleted}`;
4456
- }
4602
+ },
4603
+ (names) => names.length > 0 ? `\u5DF2\u540C\u6B65 ${names.length} \u4E2A\u8BBE\u8BA1\u6587\u4EF6` : "\u65E0\u8BBE\u8BA1\u6587\u4EF6\u6216\u540C\u6B65\u8DF3\u8FC7"
4604
+ );
4605
+ eventSession.addCliStep(
4606
+ "\u542F\u52A8 Cursor Agent",
4607
+ "\u51C6\u5907 resume/create\u2026",
4608
+ "running"
4457
4609
  );
4458
- eventSession.addCliStep("\u542F\u52A8\u6253\u5305 Agent", "\u51C6\u5907 create\u2026", "running");
4459
4610
  await syncPrepLog();
4460
- let failedByTool = false;
4611
+ const startLocalServices = isStartLocalServicesAction(msg2.action);
4612
+ const designAction = isDesignAction(msg2.action);
4613
+ const executeSql = isExecuteSqlAction(msg2.action);
4614
+ const savedAgentId = startLocalServices ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
4615
+ const promptPayload = executeSql ? parseWebIdePromptPayload(msg2.content) : null;
4616
+ const sqlExecutionId = executeSql && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
4617
+ const persistAgentId = (agentId) => {
4618
+ if (startLocalServices) return;
4619
+ if (designAction) {
4620
+ saveWebIdeDesignAgentId(workdir, taskId, agentId);
4621
+ } else {
4622
+ saveWebIdeAgentId(workdir, taskId, agentId);
4623
+ }
4624
+ };
4461
4625
  const outcome = await runCursorAgent(
4462
- cfg,
4626
+ cfg2,
4463
4627
  {
4464
4628
  messageId,
4465
- prompt: msg.content,
4466
- model: msg.model,
4467
- apiKey: msg.apiKey,
4629
+ prompt: assumptionAnswersText ? `${msg2.content}
4630
+
4631
+ ## \u5DF2\u786E\u8BA4\u5047\u8BBE
4632
+ ${assumptionAnswersText}` : msg2.content,
4633
+ model: msg2.model,
4634
+ apiKey: msg2.apiKey,
4468
4635
  workdir,
4469
- user: msg.user || "webide"
4636
+ user: msg2.user || "webide",
4637
+ resumeAgentId: savedAgentId
4470
4638
  },
4471
4639
  {
4472
4640
  signal,
4473
4641
  forceSend: true,
4474
4642
  eventSession,
4475
- enableAppendMessage: false,
4476
- enableAskQuestion: false,
4477
- enableWebIdePlanTools: false,
4478
- enablePtySessionMcp: false,
4643
+ appendMessageContent: (content) => appendContent(cfg2, messageId, content),
4644
+ askQuestionExecute: startLocalServices ? void 0 : createWebIdeAskQuestionExecute({
4645
+ cfg: cfg2,
4646
+ taskId,
4647
+ onAwaitingAssumptions: () => {
4648
+ pausedForAssumptions = true;
4649
+ }
4650
+ }),
4651
+ enableAskQuestion: !startLocalServices && !executeSql,
4652
+ enableWebIdePlanTools: !startLocalServices && !executeSql && !designAction,
4653
+ enablePtySessionMcp: startLocalServices,
4654
+ enableMysqlTools: executeSql,
4655
+ sqlExecutionId,
4479
4656
  enableSandbox: false,
4480
- enablePackageStatusTools: true,
4481
- packageId,
4482
- projectId,
4483
- onPackageFailed: async () => {
4484
- failedByTool = true;
4657
+ onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => {
4658
+ if (designAction) {
4659
+ clearWebIdeDesignAgentId(workdir, taskId);
4660
+ } else {
4661
+ clearWebIdeAgentId(workdir, taskId);
4662
+ }
4485
4663
  },
4486
- taskId: `package:${packageId}`,
4664
+ taskId,
4487
4665
  createRemoteLogSync: (agentId) => {
4666
+ persistAgentId(agentId);
4488
4667
  logSyncRef.current = createThrottledWebIdeMessageLogSync(
4489
- cfg,
4490
- {
4491
- taskId: `package:${packageId}`,
4492
- messageId,
4493
- agentId
4494
- },
4668
+ cfg2,
4669
+ { taskId, messageId, agentId },
4495
4670
  (err) => {
4496
4671
  console.warn(
4497
- "[apm] WebIDE \u6253\u5305\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
4672
+ "[apm] WebIDE \u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
4498
4673
  err instanceof Error ? err.message : err
4499
4674
  );
4500
- },
4501
- {
4502
- objectPrefixOverride: `events/webide-package/${packageId}/`
4503
4675
  }
4504
4676
  );
4505
4677
  return logSyncRef.current;
4506
4678
  },
4507
4679
  onRunStarted: async ({ agentId, runId }) => {
4680
+ persistAgentId(agentId);
4508
4681
  eventSession.addCliStep(
4509
- "\u542F\u52A8\u6253\u5305 Agent",
4682
+ "\u542F\u52A8 Cursor Agent",
4510
4683
  `agentId=${agentId} runId=${runId}`,
4511
4684
  "ok"
4512
4685
  );
@@ -4516,8 +4689,11 @@ async function handleWebIdePackage(cfg, msg, signal) {
4516
4689
  }
4517
4690
  }
4518
4691
  );
4692
+ if (!startLocalServices) {
4693
+ persistAgentId(outcome.agentId);
4694
+ }
4519
4695
  const tokenUsage = outcome.usage != null ? {
4520
- modelId: outcome.modelId ?? (msg.model?.trim() || void 0),
4696
+ modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
4521
4697
  inputTokens: outcome.usage.inputTokens,
4522
4698
  outputTokens: outcome.usage.outputTokens,
4523
4699
  cacheReadTokens: outcome.usage.cacheReadTokens,
@@ -4535,9 +4711,22 @@ async function handleWebIdePackage(cfg, msg, signal) {
4535
4711
  detail,
4536
4712
  tokenUsage
4537
4713
  );
4538
- if (!failedByTool) {
4539
- await updatePackageStatus(cfg, packageId, "FAILED", { error: detail });
4714
+ if (executeSql && sqlExecutionId) {
4715
+ try {
4716
+ await api.cli.webideFinalizeSqlExecution({
4717
+ executionId: sqlExecutionId,
4718
+ status: "FAILED",
4719
+ error: detail
4720
+ });
4721
+ } catch (finalizeErr) {
4722
+ console.warn(
4723
+ "[apm] finalize sql execution failed:",
4724
+ finalizeErr instanceof Error ? finalizeErr.message : finalizeErr
4725
+ );
4726
+ }
4540
4727
  }
4728
+ await setError2(cfg2, messageId, detail);
4729
+ syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
4541
4730
  return;
4542
4731
  }
4543
4732
  if (outcome.status === "cancelled" || signal.aborted) {
@@ -4547,92 +4736,226 @@ async function handleWebIdePackage(cfg, msg, signal) {
4547
4736
  "\u5DF2\u53D6\u6D88",
4548
4737
  tokenUsage
4549
4738
  );
4550
- await updatePackageStatus(cfg, packageId, "CANCELLED", {
4551
- error: "\u5DF2\u53D6\u6D88"
4552
- });
4739
+ if (executeSql && sqlExecutionId) {
4740
+ try {
4741
+ await api.cli.webideFinalizeSqlExecution({
4742
+ executionId: sqlExecutionId,
4743
+ status: "CANCELLED",
4744
+ error: "\u5DF2\u53D6\u6D88"
4745
+ });
4746
+ } catch (finalizeErr) {
4747
+ console.warn(
4748
+ "[apm] finalize sql execution cancelled:",
4749
+ finalizeErr instanceof Error ? finalizeErr.message : finalizeErr
4750
+ );
4751
+ }
4752
+ }
4753
+ await updateStatus2(cfg2, messageId, "CANCELLED");
4754
+ syncLocalTaskStatus(workdir, taskId, msg2, "CANCELLED");
4553
4755
  return;
4554
4756
  }
4757
+ const fallback = resolveMessageReplyFallback({
4758
+ assistantText: outcome.assistantText,
4759
+ result: outcome.result,
4760
+ createPlan: outcome.createPlan
4761
+ });
4762
+ await api.cli.webideEnsureMessageContent({
4763
+ id: messageId,
4764
+ content: fallback
4765
+ });
4766
+ if ((msg2.action === "write-plan" || msg2.action === "revise-plan") && outcome.createPlan && outcome.createPlan.trim()) {
4767
+ try {
4768
+ await api.cli.webideUpsertPlan({
4769
+ taskId,
4770
+ content: outcome.createPlan.trim()
4771
+ });
4772
+ } catch (err) {
4773
+ console.warn(
4774
+ `[apm] ${msg2.action} \u8865\u5199\u8BA1\u5212\u5931\u8D25:`,
4775
+ err instanceof Error ? err.message : err
4776
+ );
4777
+ }
4778
+ }
4779
+ if (isDesignAction(msg2.action)) {
4780
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4781
+ try {
4782
+ const uploaded = await uploadWebIdeDesignArtifacts(
4783
+ cfg2,
4784
+ taskId,
4785
+ workdir
4786
+ );
4787
+ console.log(
4788
+ `[apm] webide \u8BBE\u8BA1\u4EA7\u7269\u4E0A\u4F20 action=${msg2.action} uploaded=${uploaded.uploaded} skipped=${uploaded.skipped} files=${uploaded.artifacts.map((a) => a.name).join(",")}`
4789
+ );
4790
+ } catch (err) {
4791
+ const detail = err instanceof Error ? err.message : String(err);
4792
+ await logSyncRef.current?.markRun(
4793
+ outcome.runId,
4794
+ "error",
4795
+ detail,
4796
+ tokenUsage
4797
+ );
4798
+ await setError2(cfg2, messageId, detail);
4799
+ syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
4800
+ return;
4801
+ }
4802
+ }
4803
+ if (shouldCommitAfterWebIdeMessage(msg2.action)) {
4804
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4805
+ const committed = await commitWorkspaceReposIfDirty(
4806
+ workdir,
4807
+ `chore(webide): sync after ${msg2.action} (${taskId})`
4808
+ );
4809
+ console.log(
4810
+ `[apm] webide \u515C\u5E95 commit action=${msg2.action} repos=${committed}`
4811
+ );
4812
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4813
+ const pushed = await pushWorkspaceRepos(workdir);
4814
+ console.log(
4815
+ `[apm] webide \u81EA\u52A8 push action=${msg2.action} repos=${pushed}`
4816
+ );
4817
+ }
4818
+ if (shouldEnsureWebIdePullRequestsAfterAgent(msg2.action)) {
4819
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4820
+ if (!shouldCommitAfterWebIdeMessage(msg2.action)) {
4821
+ const pushed = await pushWorkspaceRepos(workdir);
4822
+ console.log(
4823
+ `[apm] webide create-pr \u524D push action=${msg2.action} repos=${pushed}`
4824
+ );
4825
+ }
4826
+ const prResult = await ensureWebIdePullRequests(cfg2, taskId, workdir);
4827
+ if (prResult.tips.length > 0) {
4828
+ for (const tip of prResult.tips) {
4829
+ console.log(`[apm] webide create-pr \u63D0\u793A: ${tip}`);
4830
+ }
4831
+ }
4832
+ try {
4833
+ await appendContent(
4834
+ cfg2,
4835
+ messageId,
4836
+ formatWebIdePullRequestSummary(prResult)
4837
+ );
4838
+ } catch (appendErr) {
4839
+ console.warn(
4840
+ "[apm] webide create-pr \u8FFD\u52A0 PR \u7ED3\u679C\u5931\u8D25:",
4841
+ appendErr instanceof Error ? appendErr.message : appendErr
4842
+ );
4843
+ }
4844
+ if (prResult.ensured === 0) {
4845
+ if (prResult.failed > 0) {
4846
+ throw new Error(
4847
+ `\u521B\u5EFA PR \u5931\u8D25\uFF08${prResult.failed} \u4E2A\u4ED3\u5E93\u51FA\u9519\uFF09${prResult.tips[0] ? `\uFF1A${prResult.tips[0]}` : ""}`
4848
+ );
4849
+ }
4850
+ if (prResult.skippedNoChanges > 0 && prResult.skipped === 0) {
4851
+ console.log(
4852
+ "[apm] webide create-pr\uFF1A\u5404\u4ED3\u5E93\u76F8\u5BF9\u63A8\u65AD\u57FA\u7EBF\u5747\u65E0\u53D8\u66F4\uFF0C\u8DF3\u8FC7\u521B\u5EFA PR"
4853
+ );
4854
+ } else {
4855
+ throw new Error(
4856
+ prResult.tips[0] || "\u672A\u80FD\u521B\u5EFA PR\uFF1A\u8BF7\u786E\u8BA4\u5404\u4ED3\u5F53\u524D\u4F4D\u4E8E feat/task-* \u5206\u652F\uFF0C\u4E14 remote \u5DF2\u5728\u5E73\u53F0\u767B\u8BB0"
4857
+ );
4858
+ }
4859
+ }
4860
+ }
4861
+ if (projectId) {
4862
+ try {
4863
+ await syncProjectDocumentsPush(cfg2, workdir, void 0, { projectId });
4864
+ } catch (err) {
4865
+ console.warn(
4866
+ "[apm] WebIDE \u9879\u76EE\u6587\u6863\u63A8\u9001\u5931\u8D25:",
4867
+ err instanceof Error ? err.message : err
4868
+ );
4869
+ }
4870
+ }
4555
4871
  await logSyncRef.current?.markRun(
4556
4872
  outcome.runId,
4557
4873
  "finished",
4558
4874
  null,
4559
4875
  tokenUsage
4560
4876
  );
4561
- if (failedByTool) {
4877
+ const completeAction = completeActionForInbound(msg2.action);
4878
+ if (completeAction && !pausedForAssumptions) {
4879
+ await webIdeRunAction(cfg2, taskId, completeAction);
4880
+ } else if (pausedForAssumptions) {
4562
4881
  console.log(
4563
- `[apm] webide-package Agent \u5DF2\u6807 FAILED packageId=${packageId}`
4882
+ `[apm] \u672C\u8F6E\u505C\u5728\u5F85\u786E\u8BA4\u5047\u8BBE\uFF0C\u8DF3\u8FC7 complete action=${msg2.action} taskId=${taskId}`
4564
4883
  );
4565
- return;
4566
4884
  }
4567
- const uploaded = await runPrepStep(
4568
- "\u4E0A\u4F20\u6253\u5305\u4EA7\u7269\u5230 MinIO",
4569
- () => uploadPackageArtifact(cfg, msg.target, packageId, projectId),
4570
- (result) => `objectKey=${result.artifactPath}${result.artifactUrl ? ` url=${result.artifactUrl}` : ""}`
4571
- );
4572
- await updatePackageStatus(cfg, packageId, "SUCCESS", {
4573
- artifactPath: uploaded.artifactPath,
4574
- ...uploaded.artifactUrl ? { artifactUrl: uploaded.artifactUrl } : {}
4575
- });
4885
+ await updateStatus2(cfg2, messageId, "SUCCESS");
4886
+ syncLocalTaskStatus(workdir, taskId, msg2, "SUCCESS");
4576
4887
  console.log(
4577
- `[apm] webide-package \u5B8C\u6210 target=${msg.target} packageId=${packageId} agentId=${outcome.agentId} artifact=${uploaded.artifactPath}`
4888
+ `[apm] webide-message \u5B8C\u6210 action=${msg2.action} messageId=${messageId} agentId=${outcome.agentId}`
4578
4889
  );
4890
+ if (msg2.action === "request-accept") {
4891
+ try {
4892
+ await cleanWebIdeWorkspaceCache(taskId, workdir);
4893
+ } catch (err) {
4894
+ console.warn(
4895
+ "[apm] \u5408\u5E76 PR \u540E\u6E05\u7406 WebIDE agent \u7F13\u5B58\u5931\u8D25:",
4896
+ err instanceof Error ? err.message : err
4897
+ );
4898
+ }
4899
+ }
4579
4900
  } catch (err) {
4580
4901
  const detail = err instanceof Error ? err.message : String(err);
4581
- console.error(`[apm] webide-package \u5931\u8D25: ${detail}`);
4902
+ console.error(`[apm] webide-message \u5931\u8D25: ${detail}`);
4903
+ if (detail.includes("\u590D\u7528 Agent") || detail.includes("resume")) {
4904
+ clearWebIdeAgentId(workdir, taskId);
4905
+ }
4582
4906
  try {
4583
- if (signal.aborted) {
4584
- await updatePackageStatus(cfg, packageId, "CANCELLED", {
4585
- error: detail
4586
- });
4587
- } else {
4588
- await updatePackageStatus(cfg, packageId, "FAILED", { error: detail });
4589
- }
4907
+ await setError2(cfg2, messageId, detail);
4590
4908
  } catch (statusErr) {
4591
4909
  console.error(
4592
- "[apm] \u5199\u5165\u6253\u5305 FAILED \u5931\u8D25:",
4910
+ "[apm] \u5199\u5165 WebIDE FAILED \u5931\u8D25:",
4593
4911
  statusErr instanceof Error ? statusErr.message : statusErr
4594
4912
  );
4595
4913
  }
4914
+ syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
4596
4915
  }
4597
4916
  }
4598
4917
 
4599
- // src/commands/connect/webide-message-worker.ts
4600
- var controllers = /* @__PURE__ */ new Map();
4601
- async function runJob(cfg, msg) {
4602
- const controller = new AbortController();
4603
- controllers.set(msg.messageId, controller);
4604
- try {
4605
- if (msg.type === "webide-start-project") {
4606
- await handleStartProject(cfg, msg, controller.signal);
4607
- } else if (msg.type === "webide-package") {
4608
- await handleWebIdePackage(cfg, msg, controller.signal);
4609
- } else {
4610
- await handleWebIdeInboundMessage(cfg, msg, controller.signal);
4918
+ // src/commands/connect/core/worker/handlers/index.ts
4919
+ async function dispatchWorkerJob(cfg2, msg2, signal) {
4920
+ switch (msg2.type) {
4921
+ case "webide-message":
4922
+ await handleWebIdeInboundMessage(cfg2, msg2, signal);
4923
+ return;
4924
+ case "webide-start-project":
4925
+ await handleStartProject(cfg2, msg2, signal);
4926
+ return;
4927
+ case "webide-package":
4928
+ await handleWebIdePackage(cfg2, msg2, signal);
4929
+ return;
4930
+ default: {
4931
+ const unknown = msg2;
4932
+ throw new Error(
4933
+ `\u672A\u77E5 WebIDE worker \u4EFB\u52A1: ${unknown.type ?? "unknown"}`
4934
+ );
4611
4935
  }
4612
- parentPort?.postMessage({
4613
- type: "done",
4614
- messageId: msg.messageId
4615
- });
4616
- } catch (err) {
4617
- parentPort?.postMessage({
4618
- type: "error",
4619
- messageId: msg.messageId,
4620
- error: err instanceof Error ? err.message : String(err)
4621
- });
4622
- } finally {
4623
- controllers.delete(msg.messageId);
4624
4936
  }
4625
4937
  }
4626
- parentPort?.on("message", (raw) => {
4627
- if (raw.type === "cancel") {
4628
- controllers.get(raw.messageId)?.abort();
4629
- return;
4630
- }
4631
- if (raw.type === "run") {
4632
- void runJob(raw.cfg, raw.msg);
4938
+
4939
+ // src/commands/connect/core/worker/worker-script.ts
4940
+ var port = parentPort;
4941
+ if (!port) throw new Error("worker-script \u5FC5\u987B\u5728 Worker \u7EBF\u7A0B\u4E2D\u8FD0\u884C");
4942
+ var cfg = tryReadApmConfig();
4943
+ var { msg } = workerData;
4944
+ if (!cfg || !msg) throw new Error("worker-script \u7F3A\u5C11 workerData");
4945
+ var controller = new AbortController();
4946
+ port.on("message", (raw) => {
4947
+ if (raw?.type === "cancel" && raw.messageId === msg.messageId) {
4948
+ controller.abort();
4633
4949
  }
4634
4950
  });
4635
- var boot = workerData;
4636
- if (boot?.cfg && boot?.msg) {
4637
- void runJob(boot.cfg, boot.msg);
4951
+ var reply = (body) => port.postMessage(body);
4952
+ try {
4953
+ await dispatchWorkerJob(cfg, msg, controller.signal);
4954
+ reply({ type: "done", messageId: msg.messageId });
4955
+ } catch (err) {
4956
+ reply({
4957
+ type: "error",
4958
+ messageId: msg.messageId,
4959
+ error: err instanceof Error ? err.message : String(err)
4960
+ });
4638
4961
  }