ai-project-manage-cli 8.0.2 → 8.0.3

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,5 +1,5 @@
1
1
  // src/commands/connect/webide-message-worker.ts
2
- import { parentPort as parentPort2, workerData } from "node:worker_threads";
2
+ import { parentPort, workerData } from "node:worker_threads";
3
3
 
4
4
  // src/api/client.ts
5
5
  import { createApiClient } from "listpage-http";
@@ -58,32 +58,12 @@ var requestConfig = {
58
58
  method: "PUT",
59
59
  path: "/cli/webide/test-cases"
60
60
  }),
61
- webideListTerminals: defineEndpoint({
62
- method: "GET",
63
- path: "/cli/webide/terminals"
64
- }),
65
- webideUpsertTerminal: defineEndpoint({
66
- method: "POST",
67
- path: "/cli/webide/terminals"
68
- }),
69
- webideUpdateTerminalStatus: defineEndpoint({
70
- method: "PUT",
71
- path: "/cli/webide/terminals/status"
72
- }),
73
- webideUpdateTerminalMeta: defineEndpoint({
74
- method: "PUT",
75
- path: "/cli/webide/terminals/meta"
76
- }),
77
61
  projectBaseBranch: defineEndpoint(
78
62
  {
79
63
  method: "GET",
80
64
  path: "/cli/tasks/project-base-branch"
81
65
  }
82
66
  ),
83
- workspaceBaseline: defineEndpoint({
84
- method: "GET",
85
- path: "/cli/workspaces/baseline"
86
- }),
87
67
  createWebIdeDraftPullRequest: defineEndpoint({
88
68
  method: "POST",
89
69
  path: "/cli/webide/draft-pull-requests"
@@ -108,12 +88,6 @@ var requestConfig = {
108
88
  method: "DELETE",
109
89
  path: "/cli/project-documents"
110
90
  }),
111
- getDeployArtifactStorage: defineEndpoint(
112
- {
113
- method: "GET",
114
- path: "/cli/deploy-artifact-storage"
115
- }
116
- ),
117
91
  getApmLogStorage: defineEndpoint({
118
92
  method: "GET",
119
93
  path: "/cli/apm-log-storage"
@@ -197,7 +171,7 @@ function createApmApiClient(cfg) {
197
171
  });
198
172
  }
199
173
 
200
- // src/workdir-path.ts
174
+ // src/utils/path.ts
201
175
  import { realpathSync } from "fs";
202
176
  import { platform } from "os";
203
177
  import { resolve } from "path";
@@ -244,7 +218,7 @@ function requireRemoteWorkdir(workdir) {
244
218
  return resolveWorkdirPath(trimmed);
245
219
  }
246
220
 
247
- // src/command-utils.ts
221
+ // src/utils/workspace.ts
248
222
  import { copyFileSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
249
223
  import { dirname, join as join2, resolve as resolve2 } from "path";
250
224
  import { fileURLToPath } from "url";
@@ -381,11 +355,11 @@ async function copyWebIdeTemplateFiles(targetDir, workdir = resolveWorkdirPath()
381
355
  assertWebIdeTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
382
356
  }
383
357
 
384
- // src/git-utils.ts
358
+ // src/utils/git.ts
385
359
  import { execFile as execFile2 } from "child_process";
386
360
  import { promisify as promisify2 } from "util";
387
361
 
388
- // src/git-remote.ts
362
+ // src/utils/git-remote.ts
389
363
  import { execFile } from "child_process";
390
364
  import { promisify } from "util";
391
365
  var execFileAsync = promisify(execFile);
@@ -438,7 +412,7 @@ async function tryReadHttpsGitOriginUrl(cwd) {
438
412
  return toHttpsGitRemoteUrl(raw);
439
413
  }
440
414
 
441
- // src/git-utils.ts
415
+ // src/utils/git.ts
442
416
  var execFileAsync2 = promisify2(execFile2);
443
417
  function gitExecEnv() {
444
418
  const existing = process.env.GIT_SSH_COMMAND?.trim();
@@ -536,10 +510,16 @@ async function branchHasChangesAgainstBase(cwd, baseBranch, headBranch) {
536
510
  return true;
537
511
  }
538
512
  }
539
- var FEATURE_BRANCH_RE = /^feat\/task-.+/;
513
+ var FEATURE_BRANCH_RE = /^feat\/task-(.+)$/;
540
514
  function isFeatureBranchName(branch) {
541
515
  return FEATURE_BRANCH_RE.test(branch.trim().replace(/^origin\//, ""));
542
516
  }
517
+ function taskIdFromFeatureBranch(branch) {
518
+ const name = branch.trim().replace(/^origin\//, "");
519
+ const match = name.match(FEATURE_BRANCH_RE);
520
+ const id = match?.[1]?.trim() ?? "";
521
+ return id || null;
522
+ }
543
523
  async function hasUpstream(cwd) {
544
524
  try {
545
525
  await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
@@ -565,7 +545,7 @@ async function pushCurrentBranch(cwd) {
565
545
  return true;
566
546
  }
567
547
 
568
- // src/workspace-repos.ts
548
+ // src/utils/workspace-repos.ts
569
549
  import {
570
550
  existsSync as existsSync2,
571
551
  mkdirSync as mkdirSync3,
@@ -947,7 +927,12 @@ async function runTaskBranch(taskId, options = {}) {
947
927
  cwd: gitRoot
948
928
  });
949
929
  }
950
- return { branch, kind: manifest.kind, repos: repoRoots };
930
+ return {
931
+ branch,
932
+ baseline: baselineBranch,
933
+ kind: manifest.kind,
934
+ repos: repoRoots
935
+ };
951
936
  }
952
937
 
953
938
  // src/commands/connect/cursor-agent.ts
@@ -955,9 +940,8 @@ import {
955
940
  Agent,
956
941
  CursorAgentError
957
942
  } from "@cursor/sdk";
958
- import { setMaxListeners as setMaxListeners2 } from "node:events";
959
943
 
960
- // src/plan-format.ts
944
+ // src/utils/plan-format.ts
961
945
  function formatPlanMarkdown(raw) {
962
946
  let text = raw.trim();
963
947
  if (!text) {
@@ -978,7 +962,7 @@ function formatPlanMarkdown(raw) {
978
962
  return text.replace(/\r\n/g, "\n").trimEnd() + "\n";
979
963
  }
980
964
 
981
- // src/session-utils.ts
965
+ // src/utils/session.ts
982
966
  function serializeTokenUsage(usage) {
983
967
  return {
984
968
  inputTokens: usage.inputTokens,
@@ -1260,71 +1244,9 @@ ${JSON.stringify(event, null, 2)}
1260
1244
  \`\`\``;
1261
1245
  }
1262
1246
 
1263
- // src/commands/connect/abort-signal-debug.ts
1264
- import {
1265
- getEventListeners,
1266
- getMaxListeners,
1267
- setMaxListeners
1268
- } from "node:events";
1269
- function isAbortSignalDebugEnabled() {
1270
- const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
1271
- return v === "1" || v === "true" || v === "yes";
1272
- }
1273
- function formatAbortSignalStats(signal, label) {
1274
- if (!signal) {
1275
- return `[apm:abort-debug] ${label}: (no signal)`;
1276
- }
1277
- const listeners = getEventListeners(signal, "abort");
1278
- const max = getMaxListeners(signal);
1279
- return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
1280
- }
1281
- function logAbortSignalStats(signal, label) {
1282
- if (!isAbortSignalDebugEnabled()) return;
1283
- console.log(formatAbortSignalStats(signal, label));
1284
- }
1285
- var installed = false;
1286
- function installAbortSignalDebug() {
1287
- if (!isAbortSignalDebugEnabled() || installed) return;
1288
- installed = true;
1289
- const maxFromEnv = Number.parseInt(
1290
- process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
1291
- 10
1292
- );
1293
- if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
1294
- setMaxListeners(maxFromEnv);
1295
- console.log(
1296
- `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
1297
- );
1298
- }
1299
- process.on("warning", (warning) => {
1300
- if (warning.name !== "MaxListenersExceededWarning") return;
1301
- console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
1302
- if (warning.stack) {
1303
- console.warn(warning.stack);
1304
- }
1305
- });
1306
- const proto = AbortSignal.prototype;
1307
- const original = proto.addEventListener;
1308
- proto.addEventListener = function(type, listener, options) {
1309
- if (type === "abort") {
1310
- const sig = this;
1311
- const before = getEventListeners(sig, "abort").length;
1312
- const max = getMaxListeners(sig);
1313
- const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
1314
- console.log(
1315
- `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
1316
- ${stack}`
1317
- );
1318
- }
1319
- return original.call(this, type, listener, options);
1320
- };
1321
- console.log(
1322
- "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
1323
- );
1324
- }
1325
-
1326
- // src/commands/connect/append-message-tool.ts
1327
- function createAppendMessageCustomTools(messageId, appendContent2) {
1247
+ // src/commands/connect/tools/append-message-tool.ts
1248
+ function createAppendMessageCustomTools(options) {
1249
+ const { appendContent: appendContent2 } = options;
1328
1250
  return {
1329
1251
  AppendMessage: {
1330
1252
  description: "\u5411\u5F53\u524D WebIDE \u6D88\u606F\u8FFD\u52A0\u7528\u6237\u53EF\u89C1\u56DE\u590D\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8865\u5145\u8FDB\u5C55\uFF1B\u88AB @ \u65F6\u6536\u5230\u540E\u5E94\u5148\u7B80\u77ED\u786E\u8BA4\u518D\u6267\u884C\u4EFB\u52A1\u3002\u7ED3\u8BBA\u4E0E\u8FDB\u5C55\u5E94\u5199\u5728\u672C\u5DE5\u5177\u4E2D\uFF1B\u6700\u7EC8\u81EA\u7136\u8BED\u8A00\u6536\u5C3E\u52FF\u518D\u590D\u8FF0\u6B64\u5904\u5185\u5BB9\u3002",
@@ -1348,7 +1270,6 @@ function createAppendMessageCustomTools(messageId, appendContent2) {
1348
1270
  }
1349
1271
  try {
1350
1272
  await appendContent2(content);
1351
- console.log(`[apm] AppendMessage \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
1352
1273
  return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
1353
1274
  } catch (err) {
1354
1275
  const detail = err instanceof Error ? err.message : String(err);
@@ -1362,7 +1283,7 @@ function createAppendMessageCustomTools(messageId, appendContent2) {
1362
1283
  };
1363
1284
  }
1364
1285
 
1365
- // src/commands/connect/ask-question-tool.ts
1286
+ // src/commands/connect/tools/ask-question-tool.ts
1366
1287
  var INPUT_SCHEMA = {
1367
1288
  type: "object",
1368
1289
  properties: {
@@ -1400,121 +1321,118 @@ var INPUT_SCHEMA = {
1400
1321
  };
1401
1322
  function createAskQuestionTool(options) {
1402
1323
  return {
1403
- description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
1404
- inputSchema: INPUT_SCHEMA,
1405
- execute: async (args) => {
1406
- options?.onInvoke?.(args);
1407
- if (options?.execute) {
1408
- return options.execute(args);
1409
- }
1410
- const payload = JSON.stringify(args, null, 2);
1411
- console.log(`[apm] AskQuestion\uFF08\u672C\u5730\u5360\u4F4D\uFF0C\u672A\u7B49\u5F85\u7528\u6237\uFF09:
1324
+ AskQuestion: {
1325
+ description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
1326
+ inputSchema: INPUT_SCHEMA,
1327
+ execute: async (args) => {
1328
+ options?.onInvoke?.(args);
1329
+ if (options?.execute) {
1330
+ return options.execute(args);
1331
+ }
1332
+ const payload = JSON.stringify(args, null, 2);
1333
+ console.log(`[apm] AskQuestion\uFF08\u672C\u5730\u5360\u4F4D\uFF0C\u672A\u7B49\u5F85\u7528\u6237\uFF09:
1412
1334
  ${payload}`);
1413
- return `[local] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
1335
+ return `[local] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
1414
1336
  ${payload}`;
1337
+ }
1415
1338
  }
1416
1339
  };
1417
1340
  }
1418
1341
 
1419
- // src/commands/connect/webide-merge-tools.ts
1342
+ // src/commands/connect/tools/webide-merge-tools.ts
1420
1343
  function createMergeWebIdePullRequestsTool(options) {
1421
- const { cfg, taskId } = options;
1344
+ const { webideMergePullRequests } = options;
1422
1345
  return {
1423
- description: "Merge all open pull requests for this WebIDE task. Call once with confirmedConflictFree=true. No baseline sync or review is required before calling.",
1424
- inputSchema: {
1425
- type: "object",
1426
- properties: {
1427
- confirmedConflictFree: {
1428
- type: "boolean",
1429
- description: "Must be true to confirm you intend to merge this task's open PRs now."
1430
- }
1346
+ MergeWebIdePullRequests: {
1347
+ description: "Merge all open pull requests for this WebIDE task. Call once with confirmedConflictFree=true. No baseline sync or review is required before calling.",
1348
+ inputSchema: {
1349
+ type: "object",
1350
+ properties: {
1351
+ confirmedConflictFree: {
1352
+ type: "boolean",
1353
+ description: "Must be true to confirm you intend to merge this task's open PRs now."
1354
+ }
1355
+ },
1356
+ required: ["confirmedConflictFree"]
1431
1357
  },
1432
- required: ["confirmedConflictFree"]
1433
- },
1434
- execute: async (args) => {
1435
- if (args.confirmedConflictFree !== true) {
1436
- throw new Error(
1437
- "MergeWebIdePullRequests \u8981\u6C42 confirmedConflictFree=true\u3002"
1438
- );
1358
+ execute: async (args) => {
1359
+ if (args.confirmedConflictFree !== true) {
1360
+ throw new Error(
1361
+ "MergeWebIdePullRequests \u8981\u6C42 confirmedConflictFree=true\u3002"
1362
+ );
1363
+ }
1364
+ const result = await webideMergePullRequests();
1365
+ return JSON.stringify(result, null, 2);
1439
1366
  }
1440
- const api = createApmApiClient(cfg);
1441
- console.log(`[apm] MergeWebIdePullRequests taskId=${taskId}`);
1442
- const result = await api.cli.webideMergePullRequests({ taskId });
1443
- return JSON.stringify(result, null, 2);
1444
1367
  }
1445
1368
  };
1446
1369
  }
1447
1370
 
1448
- // src/commands/connect/webide-plan-tools.ts
1371
+ // src/commands/connect/tools/webide-plan-tools.ts
1449
1372
  function asString(value) {
1450
1373
  return typeof value === "string" ? value.trim() : "";
1451
1374
  }
1452
1375
  function createRecommendPlanTool(options) {
1453
- const { cfg, taskId } = options;
1376
+ const { webideRecommendPlan } = options;
1454
1377
  return {
1455
- description: "Recommend whether the human should write an implementation plan or skip it. Does not change workflow phase; the human decides in the WebIDE UI.",
1456
- inputSchema: {
1457
- type: "object",
1458
- properties: {
1459
- recommendation: {
1460
- type: "string",
1461
- enum: ["write", "skip", "unknown"],
1462
- description: "write = suggest generating a plan; skip = plan optional/unnecessary; unknown = unsure"
1378
+ RecommendPlan: {
1379
+ description: "Recommend whether the human should write an implementation plan or skip it. Does not change workflow phase; the human decides in the WebIDE UI.",
1380
+ inputSchema: {
1381
+ type: "object",
1382
+ properties: {
1383
+ recommendation: {
1384
+ type: "string",
1385
+ enum: ["write", "skip", "unknown"],
1386
+ description: "write = suggest generating a plan; skip = plan optional/unnecessary; unknown = unsure"
1387
+ },
1388
+ reason: {
1389
+ type: "string",
1390
+ description: "Short Chinese reason for the recommendation"
1391
+ }
1463
1392
  },
1464
- reason: {
1465
- type: "string",
1466
- description: "Short Chinese reason for the recommendation"
1467
- }
1393
+ required: ["recommendation"]
1468
1394
  },
1469
- required: ["recommendation"]
1470
- },
1471
- execute: async (args) => {
1472
- const raw = asString(args.recommendation);
1473
- const recommendation = raw === "write" || raw === "skip" || raw === "unknown" ? raw : "unknown";
1474
- const reason = asString(args.reason) || void 0;
1475
- const api = createApmApiClient(cfg);
1476
- await api.cli.webideRecommendPlan({
1477
- taskId,
1478
- recommendation,
1479
- reason
1480
- });
1481
- console.log(
1482
- `[apm] RecommendPlan taskId=${taskId} recommendation=${recommendation}`
1483
- );
1484
- return JSON.stringify({ ok: true, recommendation, reason }, null, 2);
1395
+ execute: async (args) => {
1396
+ const raw = asString(args.recommendation);
1397
+ const recommendation = raw === "write" || raw === "skip" || raw === "unknown" ? raw : "unknown";
1398
+ const reason = asString(args.reason) || void 0;
1399
+ await webideRecommendPlan({
1400
+ recommendation,
1401
+ reason
1402
+ });
1403
+ return JSON.stringify({ ok: true, recommendation, reason }, null, 2);
1404
+ }
1485
1405
  }
1486
1406
  };
1487
1407
  }
1488
1408
  function createUpsertWebIdePlanTool(options) {
1489
- const { cfg, taskId } = options;
1409
+ const { webideUpsertPlan } = options;
1490
1410
  return {
1491
- description: "Persist or replace the implementation plan markdown for this WebIDE task and mark it ready for human confirmation. Call with the full plan document (not a diff). Use after generating or revising the plan.",
1492
- inputSchema: {
1493
- type: "object",
1494
- properties: {
1495
- content: {
1496
- type: "string",
1497
- description: "Full implementation plan in Markdown (Chinese)"
1498
- }
1411
+ UpsertWebIdePlan: {
1412
+ description: "Persist or replace the implementation plan markdown for this WebIDE task and mark it ready for human confirmation. Call with the full plan document (not a diff). Use after generating or revising the plan.",
1413
+ inputSchema: {
1414
+ type: "object",
1415
+ properties: {
1416
+ content: {
1417
+ type: "string",
1418
+ description: "Full implementation plan in Markdown (Chinese)"
1419
+ }
1420
+ },
1421
+ required: ["content"]
1499
1422
  },
1500
- required: ["content"]
1501
- },
1502
- execute: async (args) => {
1503
- const content = asString(args.content);
1504
- if (!content) {
1505
- throw new Error("UpsertWebIdePlan \u7F3A\u5C11 content");
1423
+ execute: async (args) => {
1424
+ const content = asString(args.content);
1425
+ if (!content) {
1426
+ throw new Error("UpsertWebIdePlan \u7F3A\u5C11 content");
1427
+ }
1428
+ await webideUpsertPlan({ content });
1429
+ return JSON.stringify({ ok: true, chars: content.length }, null, 2);
1506
1430
  }
1507
- const api = createApmApiClient(cfg);
1508
- await api.cli.webideUpsertPlan({ taskId, content });
1509
- console.log(
1510
- `[apm] UpsertWebIdePlan taskId=${taskId} chars=${content.length}`
1511
- );
1512
- return JSON.stringify({ ok: true, chars: content.length }, null, 2);
1513
1431
  }
1514
1432
  };
1515
1433
  }
1516
1434
 
1517
- // src/commands/connect/webide-test-case-tools.ts
1435
+ // src/commands/connect/tools/webide-test-case-tools.ts
1518
1436
  function asString2(value) {
1519
1437
  return typeof value === "string" ? value.trim() : "";
1520
1438
  }
@@ -1542,222 +1460,62 @@ function parseCases(raw) {
1542
1460
  return cases;
1543
1461
  }
1544
1462
  function createUpsertWebIdeTestCasesTool(options) {
1545
- const { cfg, taskId } = options;
1463
+ const { webideReplaceTestCases } = options;
1546
1464
  return {
1547
- description: "Persist generated automated test cases for this WebIDE task (full replace) and mark the workflow TEST_READY. Call once with the complete case list after reviewing the task and code changes.",
1548
- inputSchema: {
1549
- type: "object",
1550
- properties: {
1551
- cases: {
1552
- type: "array",
1553
- description: "Complete list of test cases to store on the platform",
1554
- items: {
1555
- type: "object",
1556
- properties: {
1557
- name: {
1558
- type: "string",
1559
- description: "Short stable case id / name"
1560
- },
1561
- description: {
1562
- type: "string",
1563
- description: "What this case verifies (Chinese ok)"
1564
- },
1565
- steps: {
1566
- type: "array",
1567
- items: { type: "string" },
1568
- description: "Ordered human-readable steps"
1465
+ UpsertWebIdeTestCases: {
1466
+ description: "Persist generated automated test cases for this WebIDE task (full replace) and mark the workflow TEST_READY. Call once with the complete case list after reviewing the task and code changes.",
1467
+ inputSchema: {
1468
+ type: "object",
1469
+ properties: {
1470
+ cases: {
1471
+ type: "array",
1472
+ description: "Complete list of test cases to store on the platform",
1473
+ items: {
1474
+ type: "object",
1475
+ properties: {
1476
+ name: {
1477
+ type: "string",
1478
+ description: "Short stable case id / name"
1479
+ },
1480
+ description: {
1481
+ type: "string",
1482
+ description: "What this case verifies (Chinese ok)"
1483
+ },
1484
+ steps: {
1485
+ type: "array",
1486
+ items: { type: "string" },
1487
+ description: "Ordered human-readable steps"
1488
+ },
1489
+ sortOrder: {
1490
+ type: "number",
1491
+ description: "Optional display order (0-based)"
1492
+ }
1569
1493
  },
1570
- sortOrder: {
1571
- type: "number",
1572
- description: "Optional display order (0-based)"
1573
- }
1574
- },
1575
- required: ["name", "description", "steps"]
1494
+ required: ["name", "description", "steps"]
1495
+ }
1576
1496
  }
1577
- }
1578
- },
1579
- required: ["cases"]
1580
- },
1581
- execute: async (args) => {
1582
- const cases = parseCases(args.cases);
1583
- if (cases.length === 0) {
1584
- throw new Error(
1585
- "UpsertWebIdeTestCases \u7F3A\u5C11\u6709\u6548 cases\uFF08\u6BCF\u6761\u9700 name \u4E0E\u975E\u7A7A steps\uFF09"
1586
- );
1587
- }
1588
- const api = createApmApiClient(cfg);
1589
- const result = await api.cli.webideReplaceTestCases({ taskId, cases });
1590
- console.log(
1591
- `[apm] UpsertWebIdeTestCases taskId=${taskId} count=${cases.length}`
1592
- );
1593
- return JSON.stringify(
1594
- { ok: true, count: result.count ?? cases.length },
1595
- null,
1596
- 2
1597
- );
1598
- }
1599
- };
1600
- }
1601
-
1602
- // src/commands/connect/webide-terminal-tools.ts
1603
- function asString3(value) {
1604
- return typeof value === "string" ? value.trim() : "";
1605
- }
1606
- function asPorts(value) {
1607
- if (!Array.isArray(value)) return void 0;
1608
- const ports = value.map((p) => typeof p === "number" ? p : Number(p)).filter((p) => Number.isFinite(p) && p > 0 && p < 65536).map((p) => Math.trunc(p));
1609
- return ports.length ? ports : void 0;
1610
- }
1611
- function createWebIdeTerminalTools(options) {
1612
- const { cfg, taskId, workdir, rpc } = options;
1613
- const StartWebIdeTerminal = {
1614
- description: "Start a long-running WebIDE terminal (e.g. frontend/backend dev server). Process outlives this message. Same key already RUNNING/READY returns idempotently. Do NOT use Shell for long-lived dev servers. Returns terminalId, pid, ports.",
1615
- inputSchema: {
1616
- type: "object",
1617
- properties: {
1618
- key: {
1619
- type: "string",
1620
- description: "Stable key, e.g. frontend / backend"
1621
- },
1622
- name: { type: "string", description: "Display name" },
1623
- cwd: {
1624
- type: "string",
1625
- description: "Working directory (absolute or under workspace)"
1626
- },
1627
- command: {
1628
- type: "string",
1629
- description: "Shell command to run, e.g. pnpm dev"
1630
1497
  },
1631
- readyPattern: {
1632
- type: "string",
1633
- description: "Optional regex; when matched in logs, status becomes READY"
1634
- },
1635
- portsHint: {
1636
- type: "array",
1637
- items: { type: "number" },
1638
- description: "Optional expected listen ports"
1639
- }
1498
+ required: ["cases"]
1640
1499
  },
1641
- required: ["key", "cwd", "command"]
1642
- },
1643
- execute: async (args) => {
1644
- const key = asString3(args.key);
1645
- const cwd = asString3(args.cwd) || workdir;
1646
- const command = asString3(args.command);
1647
- if (!key || !command) {
1648
- return {
1649
- content: [{ type: "text", text: "key / command \u4E0D\u80FD\u4E3A\u7A7A" }],
1650
- isError: true
1651
- };
1652
- }
1653
- try {
1654
- const result = rpc ? await rpc.start({
1655
- taskId,
1656
- key,
1657
- name: asString3(args.name) || void 0,
1658
- cwd,
1659
- command,
1660
- readyPattern: asString3(args.readyPattern) || void 0,
1661
- portsHint: asPorts(args.portsHint)
1662
- }) : await (async () => {
1663
- throw new Error("Terminal RPC \u4E0D\u53EF\u7528\uFF08\u9700\u5728 connect \u4E3B\u8FDB\u7A0B\uFF09");
1664
- })();
1665
- return JSON.stringify(result, null, 2);
1666
- } catch (err) {
1667
- const detail = err instanceof Error ? err.message : String(err);
1668
- return {
1669
- content: [{ type: "text", text: `\u542F\u52A8\u7EC8\u7AEF\u5931\u8D25: ${detail}` }],
1670
- isError: true
1671
- };
1672
- }
1673
- }
1674
- };
1675
- const StopWebIdeTerminal = {
1676
- description: "Stop a WebIDE terminal and kill its process tree. Soft-terminates (status EXITED, record kept).",
1677
- inputSchema: {
1678
- type: "object",
1679
- properties: {
1680
- key: { type: "string", description: "Terminal key" },
1681
- terminalId: { type: "string", description: "Server terminal id" }
1682
- }
1683
- },
1684
- execute: async (args) => {
1685
- const key = asString3(args.key) || void 0;
1686
- const terminalId = asString3(args.terminalId) || void 0;
1687
- if (!key && !terminalId) {
1688
- return {
1689
- content: [{ type: "text", text: "\u9700\u8981 key \u6216 terminalId" }],
1690
- isError: true
1691
- };
1692
- }
1693
- try {
1694
- const result = rpc ? await rpc.stop({ taskId, key, terminalId }) : await (async () => {
1695
- throw new Error("Terminal RPC \u4E0D\u53EF\u7528\uFF08\u9700\u5728 connect \u4E3B\u8FDB\u7A0B\uFF09");
1696
- })();
1697
- return JSON.stringify(result, null, 2);
1698
- } catch (err) {
1699
- const detail = err instanceof Error ? err.message : String(err);
1700
- return {
1701
- content: [{ type: "text", text: `\u505C\u6B62\u7EC8\u7AEF\u5931\u8D25: ${detail}` }],
1702
- isError: true
1703
- };
1704
- }
1705
- }
1706
- };
1707
- const StopAllWebIdeTerminals = {
1708
- description: "Stop all living WebIDE terminals for the current task (kill process trees). Use before re-running local dev to avoid port conflicts. Soft-terminates records.",
1709
- inputSchema: {
1710
- type: "object",
1711
- properties: {}
1712
- },
1713
- execute: async () => {
1714
- try {
1715
- const result = rpc ? await rpc.stopAll({ taskId }) : await (async () => {
1716
- throw new Error("Terminal RPC \u4E0D\u53EF\u7528\uFF08\u9700\u5728 connect \u4E3B\u8FDB\u7A0B\uFF09");
1717
- })();
1718
- return JSON.stringify(result, null, 2);
1719
- } catch (err) {
1720
- const detail = err instanceof Error ? err.message : String(err);
1721
- return {
1722
- content: [{ type: "text", text: `\u5168\u90E8\u505C\u6B62\u5931\u8D25: ${detail}` }],
1723
- isError: true
1724
- };
1725
- }
1726
- }
1727
- };
1728
- const ListWebIdeTerminals = {
1729
- description: "List living WebIDE terminals for the current task (STARTING/RUNNING/READY/STOPPING only). Does NOT include EXITED/FAILED. Returns id, key, status, pid, ports.",
1730
- inputSchema: {
1731
- type: "object",
1732
- properties: {}
1733
- },
1734
- execute: async () => {
1735
- try {
1736
- if (rpc) {
1737
- const result2 = await rpc.list({ taskId });
1738
- return JSON.stringify(result2, null, 2);
1500
+ execute: async (args) => {
1501
+ const cases = parseCases(args.cases);
1502
+ if (cases.length === 0) {
1503
+ throw new Error(
1504
+ "UpsertWebIdeTestCases \u7F3A\u5C11\u6709\u6548 cases\uFF08\u6BCF\u6761\u9700 name \u4E0E\u975E\u7A7A steps\uFF09"
1505
+ );
1739
1506
  }
1740
- const api = createApmApiClient(cfg);
1741
- const result = await api.cli.webideListTerminals({ taskId });
1742
- return JSON.stringify(result, null, 2);
1743
- } catch (err) {
1744
- const detail = err instanceof Error ? err.message : String(err);
1745
- return {
1746
- content: [{ type: "text", text: `\u5217\u51FA\u7EC8\u7AEF\u5931\u8D25: ${detail}` }],
1747
- isError: true
1748
- };
1507
+ const result = await webideReplaceTestCases({ cases });
1508
+ return JSON.stringify(
1509
+ { ok: true, count: result.count ?? cases.length },
1510
+ null,
1511
+ 2
1512
+ );
1749
1513
  }
1750
1514
  }
1751
1515
  };
1752
- return {
1753
- StartWebIdeTerminal,
1754
- StopWebIdeTerminal,
1755
- StopAllWebIdeTerminals,
1756
- ListWebIdeTerminals
1757
- };
1758
1516
  }
1759
1517
 
1760
- // src/commands/connect/cursor-custom-tools.ts
1518
+ // src/commands/connect/tools/index.ts
1761
1519
  var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
1762
1520
  AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
1763
1521
  \u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u65F6\uFF0C\u8BF7\u8C03\u7528 AskQuestion\uFF08\u7ECF CallMcpTool / custom-user-tools\uFF09\uFF0C\u4E0D\u8981\u5047\u8BBE IDE \u5185\u7F6E AskQuestion \u4E0D\u53EF\u7528\u3002
@@ -1765,39 +1523,33 @@ AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u
1765
1523
  var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
1766
1524
  \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
1767
1525
  \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`;
1768
- function createCursorCustomTools(cfg, messageId, options) {
1526
+ function createCursorCustomTools(cfg, options) {
1769
1527
  const tools = {
1770
- ...createAppendMessageCustomTools(messageId, options.appendMessageContent),
1771
- AskQuestion: createAskQuestionTool({
1528
+ ...options.enableAppendMessage !== false && options.appendMessageContent ? createAppendMessageCustomTools({
1529
+ appendContent: options.appendMessageContent
1530
+ }) : {},
1531
+ ...createAskQuestionTool({
1772
1532
  onInvoke: options?.onAskQuestion,
1773
1533
  execute: options?.askQuestionExecute
1774
1534
  })
1775
1535
  };
1776
- if (options?.enableWebIdePlanTools && options.taskId) {
1777
- tools.RecommendPlan = createRecommendPlanTool({
1778
- cfg,
1779
- taskId: options.taskId
1780
- });
1781
- tools.UpsertWebIdePlan = createUpsertWebIdePlanTool({
1782
- cfg,
1783
- taskId: options.taskId
1784
- });
1785
- tools.UpsertWebIdeTestCases = createUpsertWebIdeTestCasesTool({
1786
- cfg,
1787
- taskId: options.taskId
1788
- });
1789
- tools.MergeWebIdePullRequests = createMergeWebIdePullRequestsTool({
1790
- cfg,
1791
- taskId: options.taskId
1792
- });
1793
- if (options.workdir) {
1536
+ if (options?.taskId && options.workdir) {
1537
+ const { cli } = createApmApiClient(cfg);
1538
+ const taskId = options.taskId;
1539
+ if (options.enableWebIdePlanTools) {
1794
1540
  Object.assign(
1795
1541
  tools,
1796
- createWebIdeTerminalTools({
1797
- cfg,
1798
- taskId: options.taskId,
1799
- workdir: options.workdir,
1800
- rpc: options.terminalRpc
1542
+ createRecommendPlanTool({
1543
+ webideRecommendPlan: (args) => cli.webideRecommendPlan({ taskId, ...args })
1544
+ }),
1545
+ createUpsertWebIdePlanTool({
1546
+ webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
1547
+ }),
1548
+ createUpsertWebIdeTestCasesTool({
1549
+ webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
1550
+ }),
1551
+ createMergeWebIdePullRequestsTool({
1552
+ webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
1801
1553
  })
1802
1554
  );
1803
1555
  }
@@ -1818,9 +1570,30 @@ function withWorkspaceBoundaryHint(prompt) {
1818
1570
  ${WORKSPACE_BOUNDARY_HINT}`;
1819
1571
  }
1820
1572
 
1573
+ // src/commands/connect/pty-session-mcp.ts
1574
+ var PTY_SESSION_MCP_NAME = "pty-session";
1575
+ var DEFAULT_PTY_SESSION_PORT = "7788";
1576
+ function resolvePtySessionBaseUrl(env = process.env) {
1577
+ const explicit = env.LOCAL_TERMINAL_URL?.trim();
1578
+ if (explicit) return explicit.replace(/\/+$/, "");
1579
+ const port = env.LOCAL_TERMINAL_PORT?.trim() || DEFAULT_PTY_SESSION_PORT;
1580
+ return `http://127.0.0.1:${port}`;
1581
+ }
1582
+ function createPtySessionMcpServers(cfg) {
1583
+ const token = resolveApiKey(cfg);
1584
+ if (!token) return void 0;
1585
+ return {
1586
+ [PTY_SESSION_MCP_NAME]: {
1587
+ type: "http",
1588
+ url: `${resolvePtySessionBaseUrl()}/mcp`,
1589
+ headers: {
1590
+ Authorization: `Bearer ${token}`
1591
+ }
1592
+ }
1593
+ };
1594
+ }
1595
+
1821
1596
  // src/commands/connect/cursor-agent.ts
1822
- setMaxListeners2(100);
1823
- installAbortSignalDebug();
1824
1597
  var noopRemoteLogSync = {
1825
1598
  schedule(_session) {
1826
1599
  },
@@ -1883,7 +1656,6 @@ async function obtainAgent(ctx) {
1883
1656
  }
1884
1657
  async function runCursorAgent(cfg, ctx, options) {
1885
1658
  const signal = options.signal;
1886
- logAbortSignalStats(signal, "runCursorAgent:start");
1887
1659
  if (signal?.aborted) {
1888
1660
  throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
1889
1661
  }
@@ -1892,16 +1664,17 @@ async function runCursorAgent(cfg, ctx, options) {
1892
1664
  throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
1893
1665
  }
1894
1666
  const workdir = resolveWorkdirPath(ctx.workdir);
1895
- const customTools = createCursorCustomTools(cfg, ctx.messageId, {
1667
+ const customTools = createCursorCustomTools(cfg, {
1896
1668
  onAskQuestion: options.onAskQuestion,
1897
1669
  appendMessageContent: options.appendMessageContent,
1670
+ enableAppendMessage: options.enableAppendMessage,
1898
1671
  askQuestionExecute: options.askQuestionExecute,
1899
1672
  enableWebIdePlanTools: options.enableWebIdePlanTools,
1900
1673
  taskId: options.taskId,
1901
- workdir,
1902
- terminalRpc: options.terminalRpc
1674
+ workdir
1903
1675
  });
1904
1676
  const enableSandbox = Boolean(options.enableSandbox);
1677
+ const mcpServers = options.enablePtySessionMcp ? createPtySessionMcpServers(cfg) : void 0;
1905
1678
  let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
1906
1679
  prompt = withWorkspaceBoundaryHint(prompt);
1907
1680
  console.log(
@@ -1916,6 +1689,7 @@ async function runCursorAgent(cfg, ctx, options) {
1916
1689
  resumeAgentId: ctx.resumeAgentId,
1917
1690
  customTools,
1918
1691
  enableSandbox,
1692
+ mcpServers,
1919
1693
  onInvalidatePersistedAgentId: options.onInvalidatePersistedAgentId
1920
1694
  });
1921
1695
  const invalidateAgentMapping = () => {
@@ -1929,7 +1703,6 @@ async function runCursorAgent(cfg, ctx, options) {
1929
1703
  void activeRun.cancel().catch(() => void 0);
1930
1704
  };
1931
1705
  signal?.addEventListener("abort", abortRun, { once: true });
1932
- logAbortSignalStats(signal, "runCursorAgent:after-addListener");
1933
1706
  try {
1934
1707
  const run = await agent.send(prompt, {
1935
1708
  ...ctx.mode ? { mode: ctx.mode } : {},
@@ -1939,7 +1712,6 @@ async function runCursorAgent(cfg, ctx, options) {
1939
1712
  }
1940
1713
  });
1941
1714
  activeRun = run;
1942
- logAbortSignalStats(signal, "runCursorAgent:after-send");
1943
1715
  console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
1944
1716
  await options.onRunStarted?.({ agentId: agent.agentId, runId: run.id });
1945
1717
  let lastRunErrorStatus;
@@ -2026,9 +1798,7 @@ async function runCursorAgent(cfg, ctx, options) {
2026
1798
  }
2027
1799
  throw err;
2028
1800
  } finally {
2029
- logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
2030
1801
  signal?.removeEventListener("abort", abortRun);
2031
- logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
2032
1802
  await agent[Symbol.asyncDispose]();
2033
1803
  }
2034
1804
  }
@@ -2204,11 +1974,11 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
2204
1974
  // src/commands/connect/webide-ask-question.ts
2205
1975
  import { setTimeout as delay } from "node:timers/promises";
2206
1976
  var POLL_INTERVAL_MS = 2e3;
2207
- function asString4(value) {
1977
+ function asString3(value) {
2208
1978
  return typeof value === "string" ? value.trim() : "";
2209
1979
  }
2210
1980
  function parseQuestions(args) {
2211
- const title = asString4(args.title) || void 0;
1981
+ const title = asString3(args.title) || void 0;
2212
1982
  const raw = args.questions;
2213
1983
  if (!Array.isArray(raw) || raw.length === 0) {
2214
1984
  throw new Error("AskQuestion \u7F3A\u5C11 questions");
@@ -2217,16 +1987,16 @@ function parseQuestions(args) {
2217
1987
  for (const item of raw) {
2218
1988
  if (!item || typeof item !== "object" || Array.isArray(item)) continue;
2219
1989
  const row = item;
2220
- const id = asString4(row.id);
2221
- const prompt = asString4(row.prompt);
1990
+ const id = asString3(row.id);
1991
+ const prompt = asString3(row.prompt);
2222
1992
  const optionsRaw = row.options;
2223
1993
  if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
2224
1994
  const options = [];
2225
1995
  for (const opt of optionsRaw) {
2226
1996
  if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
2227
1997
  const o = opt;
2228
- const oid = asString4(o.id);
2229
- const label = asString4(o.label);
1998
+ const oid = asString3(o.id);
1999
+ const label = asString3(o.label);
2230
2000
  if (oid && label) options.push({ id: oid, label });
2231
2001
  }
2232
2002
  if (options.length < 2) {
@@ -2260,11 +2030,11 @@ function createWebIdeAskQuestionExecute(options) {
2260
2030
  const byId = new Map(
2261
2031
  list.assumptions.map((a) => [a.questionId, a])
2262
2032
  );
2263
- const pending2 = parsed.questions.filter((q) => {
2033
+ const pending = parsed.questions.filter((q) => {
2264
2034
  const row = byId.get(q.id);
2265
2035
  return !row || row.status !== "RESOLVED";
2266
2036
  });
2267
- if (pending2.length === 0) {
2037
+ if (pending.length === 0) {
2268
2038
  const answers = parsed.questions.map((q) => {
2269
2039
  const row = byId.get(q.id);
2270
2040
  return {
@@ -2292,7 +2062,7 @@ function createWebIdeAskQuestionExecute(options) {
2292
2062
  };
2293
2063
  }
2294
2064
 
2295
- // src/commands/deploy/internal/minio.ts
2065
+ // src/utils/minio.ts
2296
2066
  import * as Minio from "minio";
2297
2067
  var MinioClient = class {
2298
2068
  inner;
@@ -2312,45 +2082,9 @@ var MinioClient = class {
2312
2082
  await this.inner.makeBucket(bucket);
2313
2083
  }
2314
2084
  }
2315
- async deleteObjectsByPrefix(bucket, prefix) {
2316
- const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
2317
- const keys = [];
2318
- await new Promise((resolve6, reject) => {
2319
- objectsStream.on("data", (obj) => {
2320
- if (obj.name) {
2321
- keys.push(obj.name);
2322
- }
2323
- });
2324
- objectsStream.on("error", reject);
2325
- objectsStream.on("end", resolve6);
2326
- });
2327
- const chunkSize = 500;
2328
- for (let i = 0; i < keys.length; i += chunkSize) {
2329
- const chunk = keys.slice(i, i + chunkSize);
2330
- await this.inner.removeObjects(
2331
- bucket,
2332
- chunk.map((name) => name)
2333
- );
2334
- }
2335
- }
2336
2085
  async putObject(bucket, objectKey, body, meta) {
2337
2086
  await this.inner.putObject(bucket, objectKey, body, body.length, meta);
2338
2087
  }
2339
- /** 匿名可读当前桶全部对象(便于静态站点直链) */
2340
- async setBucketPublicRead(bucket) {
2341
- const policy = {
2342
- Version: "2012-10-17",
2343
- Statement: [
2344
- {
2345
- Effect: "Allow",
2346
- Principal: { AWS: ["*"] },
2347
- Action: ["s3:GetObject"],
2348
- Resource: [`arn:aws:s3:::${bucket}/*`]
2349
- }
2350
- ]
2351
- };
2352
- await this.inner.setBucketPolicy(bucket, JSON.stringify(policy));
2353
- }
2354
2088
  };
2355
2089
 
2356
2090
  // src/commands/connect/apm-log-minio.ts
@@ -2856,7 +2590,7 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
2856
2590
  );
2857
2591
  }
2858
2592
 
2859
- // src/project-documents-sync.ts
2593
+ // src/utils/project-documents.ts
2860
2594
  import {
2861
2595
  existsSync as existsSync6,
2862
2596
  readdirSync as readdirSync3,
@@ -2968,24 +2702,25 @@ async function resolveProjectIdForSync(api, workdirPath, options) {
2968
2702
  return { projectId: explicit, diagnostic: null };
2969
2703
  }
2970
2704
  try {
2971
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
2972
- const projectId = baseline.projectId?.trim() || null;
2973
- if (projectId) {
2974
- return { projectId, diagnostic: null };
2705
+ const gitRoot = await resolveGitRepoRoot(workdirPath);
2706
+ const current = await getCurrentBranch(gitRoot);
2707
+ const taskId = taskIdFromFeatureBranch(current);
2708
+ if (!taskId) {
2709
+ return {
2710
+ projectId: null,
2711
+ diagnostic: `\u5F53\u524D\u5206\u652F ${current} \u4E0D\u662F feat/task-*\uFF0C\u65E0\u6CD5\u89E3\u6790\u6240\u5C5E\u9879\u76EE\u3002
2712
+ \u8BF7\u5728\u7279\u6027\u5206\u652F\u4E0A\u6267\u884C\uFF0C\u6216\u663E\u5F0F\u6307\u5B9A projectId\u3002`
2713
+ };
2975
2714
  }
2976
- const projectIds = baseline.projectIds ?? [];
2977
- if (projectIds.length === 0) {
2715
+ const baseline = await api.cli.projectBaseBranch({ taskId });
2716
+ const projectId = baseline.projectId?.trim() || null;
2717
+ if (!projectId) {
2978
2718
  return {
2979
2719
  projectId: null,
2980
- diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
2981
- \u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
2720
+ diagnostic: `\u4EFB\u52A1 ${taskId} \u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
2982
2721
  };
2983
2722
  }
2984
- return {
2985
- projectId: null,
2986
- diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
2987
- \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
2988
- };
2723
+ return { projectId, diagnostic: null };
2989
2724
  } catch (err) {
2990
2725
  const detail = err instanceof Error ? err.message : String(err);
2991
2726
  return {
@@ -3204,7 +2939,7 @@ async function appendContent(cfg, messageId, content) {
3204
2939
  const api = createApmApiClient(cfg);
3205
2940
  await api.cli.webideAppendMessageContent({ id: messageId, content });
3206
2941
  }
3207
- async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
2942
+ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3208
2943
  const workdir = requireRemoteWorkdir(msg.workdir);
3209
2944
  const messageId = msg.messageId;
3210
2945
  const taskId = msg.taskId;
@@ -3302,7 +3037,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3302
3037
  async () => runTaskBranch(taskId, { cwd: workdir }),
3303
3038
  (branchResult) => {
3304
3039
  markBranchDone(branchCacheKey, workdir);
3305
- return `kind=${branchResult.kind} branch=${branchResult.branch} repos=${branchResult.repos.length}`;
3040
+ return `kind=${branchResult.kind} branch=${branchResult.branch} baseline=${branchResult.baseline} repos=${branchResult.repos.length}`;
3306
3041
  }
3307
3042
  );
3308
3043
  } else {
@@ -3419,7 +3154,6 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3419
3154
  enableSandbox: false,
3420
3155
  onInvalidatePersistedAgentId: () => clearWebIdeAgentId(workdir, taskId),
3421
3156
  taskId,
3422
- terminalRpc: options?.terminalRpc,
3423
3157
  createRemoteLogSync: (agentId) => {
3424
3158
  saveWebIdeAgentId(workdir, taskId, agentId);
3425
3159
  logSyncRef.current = createThrottledWebIdeMessageLogSync(
@@ -3611,68 +3345,166 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3611
3345
  }
3612
3346
  }
3613
3347
 
3614
- // src/commands/connect/webide-terminal-rpc.ts
3615
- import { parentPort } from "node:worker_threads";
3616
- var nextId = 1;
3617
- var pending = /* @__PURE__ */ new Map();
3618
- function installTerminalRpcListener() {
3619
- parentPort?.on("message", (raw) => {
3620
- if (!raw || raw.type !== "terminal-rpc-result") return;
3621
- const p = pending.get(raw.id);
3622
- if (!p) return;
3623
- pending.delete(raw.id);
3624
- if (raw.ok) p.resolve(raw.result);
3625
- else p.reject(new Error(raw.error || "terminal rpc failed"));
3626
- });
3348
+ // src/commands/connect/handle-start-project.ts
3349
+ async function updateStatus2(cfg, messageId, status) {
3350
+ const api = createApmApiClient(cfg);
3351
+ await api.cli.webideUpdateMessageStatus({ id: messageId, status });
3627
3352
  }
3628
- function call(op, args) {
3629
- const id = nextId++;
3630
- return new Promise((resolve6, reject) => {
3631
- if (!parentPort) {
3632
- reject(new Error("parentPort \u4E0D\u53EF\u7528"));
3353
+ async function setError2(cfg, messageId, error) {
3354
+ const api = createApmApiClient(cfg);
3355
+ await api.cli.webideSetMessageError({ id: messageId, error });
3356
+ }
3357
+ async function handleStartProject(cfg, msg, signal) {
3358
+ const workdir = requireRemoteWorkdir(msg.workdir);
3359
+ const messageId = msg.messageId;
3360
+ const taskId = msg.taskId;
3361
+ console.log(
3362
+ `[apm] webide-start-project target=${msg.target} restart=${msg.restart} taskId=${taskId} messageId=${messageId}`
3363
+ );
3364
+ await updateStatus2(cfg, messageId, "TYPING");
3365
+ const eventSession = new EventSession(msg.content);
3366
+ const logSyncRef = {
3367
+ current: createThrottledWebIdeMessageLogSync(
3368
+ cfg,
3369
+ { taskId, messageId, agentId: "webide-start-project" },
3370
+ (err) => {
3371
+ console.warn(
3372
+ "[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3373
+ err instanceof Error ? err.message : err
3374
+ );
3375
+ }
3376
+ )
3377
+ };
3378
+ try {
3379
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3380
+ await ensureWorkspaceInitialized(workdir);
3381
+ const api = createApmApiClient(cfg);
3382
+ const outcome = await runCursorAgent(
3383
+ cfg,
3384
+ {
3385
+ messageId,
3386
+ prompt: msg.content,
3387
+ model: msg.model,
3388
+ apiKey: msg.apiKey,
3389
+ workdir,
3390
+ user: msg.user || "webide"
3391
+ },
3392
+ {
3393
+ signal,
3394
+ forceSend: true,
3395
+ eventSession,
3396
+ enableAppendMessage: false,
3397
+ askQuestionExecute: async () => "\u8FD9\u662F\u65E0\u4EBA\u503C\u5B88\u7684\u542F\u52A8\u4EFB\u52A1\uFF0C\u8BF7\u6839\u636E deploy.md \u81EA\u884C\u51B3\u5B9A\uFF0C\u4E0D\u8981\u7B49\u5F85\u7528\u6237\u3002",
3398
+ enableWebIdePlanTools: false,
3399
+ enablePtySessionMcp: true,
3400
+ enableSandbox: false,
3401
+ taskId,
3402
+ createRemoteLogSync: (agentId) => {
3403
+ logSyncRef.current = createThrottledWebIdeMessageLogSync(
3404
+ cfg,
3405
+ { taskId, messageId, agentId },
3406
+ (err) => {
3407
+ console.warn(
3408
+ "[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3409
+ err instanceof Error ? err.message : err
3410
+ );
3411
+ }
3412
+ );
3413
+ return logSyncRef.current;
3414
+ },
3415
+ onRunStarted: async ({ agentId, runId }) => {
3416
+ eventSession.addCliStep(
3417
+ "\u542F\u52A8\u72EC\u7ACB Agent",
3418
+ `agentId=${agentId} runId=${runId}`,
3419
+ "ok"
3420
+ );
3421
+ logSyncRef.current?.schedule(eventSession);
3422
+ await logSyncRef.current?.markRun(runId, "running");
3423
+ await logSyncRef.current?.flush(eventSession);
3424
+ }
3425
+ }
3426
+ );
3427
+ const tokenUsage = outcome.usage != null ? {
3428
+ modelId: outcome.modelId ?? (msg.model?.trim() || void 0),
3429
+ inputTokens: outcome.usage.inputTokens,
3430
+ outputTokens: outcome.usage.outputTokens,
3431
+ cacheReadTokens: outcome.usage.cacheReadTokens,
3432
+ cacheWriteTokens: outcome.usage.cacheWriteTokens,
3433
+ totalTokens: outcome.usage.totalTokens,
3434
+ ...outcome.usage.reasoningTokens != null ? { reasoningTokens: outcome.usage.reasoningTokens } : {}
3435
+ } : void 0;
3436
+ if (outcome.status === "error") {
3437
+ const detail = formatCursorRunFailure(outcome.runId, {
3438
+ resultText: outcome.result
3439
+ });
3440
+ await logSyncRef.current?.markRun(
3441
+ outcome.runId,
3442
+ "error",
3443
+ detail,
3444
+ tokenUsage
3445
+ );
3446
+ await setError2(cfg, messageId, detail);
3633
3447
  return;
3634
3448
  }
3635
- pending.set(id, { resolve: resolve6, reject });
3636
- parentPort.postMessage({
3637
- type: "terminal-rpc",
3638
- id,
3639
- op,
3640
- args
3449
+ if (outcome.status === "cancelled" || signal.aborted) {
3450
+ await logSyncRef.current?.markRun(
3451
+ outcome.runId,
3452
+ "cancelled",
3453
+ "\u5DF2\u53D6\u6D88",
3454
+ tokenUsage
3455
+ );
3456
+ await updateStatus2(cfg, messageId, "CANCELLED");
3457
+ return;
3458
+ }
3459
+ const fallback = resolveMessageReplyFallback({
3460
+ assistantText: outcome.assistantText,
3461
+ result: outcome.result
3641
3462
  });
3642
- setTimeout(() => {
3643
- if (pending.has(id)) {
3644
- pending.delete(id);
3645
- reject(new Error(`terminal rpc timeout op=${op}`));
3646
- }
3647
- }, 12e4);
3648
- });
3649
- }
3650
- function createWorkerTerminalRpc() {
3651
- return {
3652
- start: (args) => call("start", args),
3653
- stop: (args) => call("stop", args),
3654
- stopAll: (args) => call("stopAll", args),
3655
- list: (args) => call("list", args)
3656
- };
3463
+ await api.cli.webideEnsureMessageContent({
3464
+ id: messageId,
3465
+ content: fallback
3466
+ });
3467
+ await logSyncRef.current?.markRun(
3468
+ outcome.runId,
3469
+ "finished",
3470
+ null,
3471
+ tokenUsage
3472
+ );
3473
+ await updateStatus2(cfg, messageId, "SUCCESS");
3474
+ console.log(
3475
+ `[apm] webide-start-project \u5B8C\u6210 target=${msg.target} messageId=${messageId} agentId=${outcome.agentId}`
3476
+ );
3477
+ } catch (err) {
3478
+ const detail = err instanceof Error ? err.message : String(err);
3479
+ console.error(`[apm] webide-start-project \u5931\u8D25: ${detail}`);
3480
+ try {
3481
+ await setError2(cfg, messageId, detail);
3482
+ } catch (statusErr) {
3483
+ console.error(
3484
+ "[apm] \u5199\u5165\u542F\u52A8\u9879\u76EE FAILED \u5931\u8D25:",
3485
+ statusErr instanceof Error ? statusErr.message : statusErr
3486
+ );
3487
+ }
3488
+ }
3657
3489
  }
3658
3490
 
3659
3491
  // src/commands/connect/webide-message-worker.ts
3660
3492
  var controllers = /* @__PURE__ */ new Map();
3661
- installTerminalRpcListener();
3662
- var terminalRpc = createWorkerTerminalRpc();
3663
3493
  async function runJob(cfg, msg) {
3664
3494
  const controller = new AbortController();
3665
3495
  controllers.set(msg.messageId, controller);
3666
3496
  try {
3667
- await handleWebIdeInboundMessage(cfg, msg, controller.signal, {
3668
- terminalRpc
3669
- });
3670
- parentPort2?.postMessage({
3497
+ if (msg.type === "webide-start-project") {
3498
+ await handleStartProject(cfg, msg, controller.signal);
3499
+ } else {
3500
+ await handleWebIdeInboundMessage(cfg, msg, controller.signal);
3501
+ }
3502
+ parentPort?.postMessage({
3671
3503
  type: "done",
3672
3504
  messageId: msg.messageId
3673
3505
  });
3674
3506
  } catch (err) {
3675
- parentPort2?.postMessage({
3507
+ parentPort?.postMessage({
3676
3508
  type: "error",
3677
3509
  messageId: msg.messageId,
3678
3510
  error: err instanceof Error ? err.message : String(err)
@@ -3681,7 +3513,7 @@ async function runJob(cfg, msg) {
3681
3513
  controllers.delete(msg.messageId);
3682
3514
  }
3683
3515
  }
3684
- parentPort2?.on("message", (raw) => {
3516
+ parentPort?.on("message", (raw) => {
3685
3517
  if (raw.type === "cancel") {
3686
3518
  controllers.get(raw.messageId)?.abort();
3687
3519
  return;