ai-project-manage-cli 8.0.2 → 8.0.4

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
+ ...options.enableAskQuestion !== false ? 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,18 @@ 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,
1671
+ enableAskQuestion: options.enableAskQuestion,
1898
1672
  askQuestionExecute: options.askQuestionExecute,
1899
1673
  enableWebIdePlanTools: options.enableWebIdePlanTools,
1900
1674
  taskId: options.taskId,
1901
- workdir,
1902
- terminalRpc: options.terminalRpc
1675
+ workdir
1903
1676
  });
1904
1677
  const enableSandbox = Boolean(options.enableSandbox);
1678
+ const mcpServers = options.enablePtySessionMcp ? createPtySessionMcpServers(cfg) : void 0;
1905
1679
  let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
1906
1680
  prompt = withWorkspaceBoundaryHint(prompt);
1907
1681
  console.log(
@@ -1916,6 +1690,7 @@ async function runCursorAgent(cfg, ctx, options) {
1916
1690
  resumeAgentId: ctx.resumeAgentId,
1917
1691
  customTools,
1918
1692
  enableSandbox,
1693
+ mcpServers,
1919
1694
  onInvalidatePersistedAgentId: options.onInvalidatePersistedAgentId
1920
1695
  });
1921
1696
  const invalidateAgentMapping = () => {
@@ -1929,7 +1704,6 @@ async function runCursorAgent(cfg, ctx, options) {
1929
1704
  void activeRun.cancel().catch(() => void 0);
1930
1705
  };
1931
1706
  signal?.addEventListener("abort", abortRun, { once: true });
1932
- logAbortSignalStats(signal, "runCursorAgent:after-addListener");
1933
1707
  try {
1934
1708
  const run = await agent.send(prompt, {
1935
1709
  ...ctx.mode ? { mode: ctx.mode } : {},
@@ -1939,7 +1713,6 @@ async function runCursorAgent(cfg, ctx, options) {
1939
1713
  }
1940
1714
  });
1941
1715
  activeRun = run;
1942
- logAbortSignalStats(signal, "runCursorAgent:after-send");
1943
1716
  console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
1944
1717
  await options.onRunStarted?.({ agentId: agent.agentId, runId: run.id });
1945
1718
  let lastRunErrorStatus;
@@ -2026,9 +1799,7 @@ async function runCursorAgent(cfg, ctx, options) {
2026
1799
  }
2027
1800
  throw err;
2028
1801
  } finally {
2029
- logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
2030
1802
  signal?.removeEventListener("abort", abortRun);
2031
- logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
2032
1803
  await agent[Symbol.asyncDispose]();
2033
1804
  }
2034
1805
  }
@@ -2204,11 +1975,11 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
2204
1975
  // src/commands/connect/webide-ask-question.ts
2205
1976
  import { setTimeout as delay } from "node:timers/promises";
2206
1977
  var POLL_INTERVAL_MS = 2e3;
2207
- function asString4(value) {
1978
+ function asString3(value) {
2208
1979
  return typeof value === "string" ? value.trim() : "";
2209
1980
  }
2210
1981
  function parseQuestions(args) {
2211
- const title = asString4(args.title) || void 0;
1982
+ const title = asString3(args.title) || void 0;
2212
1983
  const raw = args.questions;
2213
1984
  if (!Array.isArray(raw) || raw.length === 0) {
2214
1985
  throw new Error("AskQuestion \u7F3A\u5C11 questions");
@@ -2217,16 +1988,16 @@ function parseQuestions(args) {
2217
1988
  for (const item of raw) {
2218
1989
  if (!item || typeof item !== "object" || Array.isArray(item)) continue;
2219
1990
  const row = item;
2220
- const id = asString4(row.id);
2221
- const prompt = asString4(row.prompt);
1991
+ const id = asString3(row.id);
1992
+ const prompt = asString3(row.prompt);
2222
1993
  const optionsRaw = row.options;
2223
1994
  if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
2224
1995
  const options = [];
2225
1996
  for (const opt of optionsRaw) {
2226
1997
  if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
2227
1998
  const o = opt;
2228
- const oid = asString4(o.id);
2229
- const label = asString4(o.label);
1999
+ const oid = asString3(o.id);
2000
+ const label = asString3(o.label);
2230
2001
  if (oid && label) options.push({ id: oid, label });
2231
2002
  }
2232
2003
  if (options.length < 2) {
@@ -2260,11 +2031,11 @@ function createWebIdeAskQuestionExecute(options) {
2260
2031
  const byId = new Map(
2261
2032
  list.assumptions.map((a) => [a.questionId, a])
2262
2033
  );
2263
- const pending2 = parsed.questions.filter((q) => {
2034
+ const pending = parsed.questions.filter((q) => {
2264
2035
  const row = byId.get(q.id);
2265
2036
  return !row || row.status !== "RESOLVED";
2266
2037
  });
2267
- if (pending2.length === 0) {
2038
+ if (pending.length === 0) {
2268
2039
  const answers = parsed.questions.map((q) => {
2269
2040
  const row = byId.get(q.id);
2270
2041
  return {
@@ -2292,7 +2063,7 @@ function createWebIdeAskQuestionExecute(options) {
2292
2063
  };
2293
2064
  }
2294
2065
 
2295
- // src/commands/deploy/internal/minio.ts
2066
+ // src/utils/minio.ts
2296
2067
  import * as Minio from "minio";
2297
2068
  var MinioClient = class {
2298
2069
  inner;
@@ -2312,45 +2083,9 @@ var MinioClient = class {
2312
2083
  await this.inner.makeBucket(bucket);
2313
2084
  }
2314
2085
  }
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
2086
  async putObject(bucket, objectKey, body, meta) {
2337
2087
  await this.inner.putObject(bucket, objectKey, body, body.length, meta);
2338
2088
  }
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
2089
  };
2355
2090
 
2356
2091
  // src/commands/connect/apm-log-minio.ts
@@ -2856,7 +2591,7 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
2856
2591
  );
2857
2592
  }
2858
2593
 
2859
- // src/project-documents-sync.ts
2594
+ // src/utils/project-documents.ts
2860
2595
  import {
2861
2596
  existsSync as existsSync6,
2862
2597
  readdirSync as readdirSync3,
@@ -2968,24 +2703,25 @@ async function resolveProjectIdForSync(api, workdirPath, options) {
2968
2703
  return { projectId: explicit, diagnostic: null };
2969
2704
  }
2970
2705
  try {
2971
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
2972
- const projectId = baseline.projectId?.trim() || null;
2973
- if (projectId) {
2974
- return { projectId, diagnostic: null };
2706
+ const gitRoot = await resolveGitRepoRoot(workdirPath);
2707
+ const current = await getCurrentBranch(gitRoot);
2708
+ const taskId = taskIdFromFeatureBranch(current);
2709
+ if (!taskId) {
2710
+ return {
2711
+ projectId: null,
2712
+ diagnostic: `\u5F53\u524D\u5206\u652F ${current} \u4E0D\u662F feat/task-*\uFF0C\u65E0\u6CD5\u89E3\u6790\u6240\u5C5E\u9879\u76EE\u3002
2713
+ \u8BF7\u5728\u7279\u6027\u5206\u652F\u4E0A\u6267\u884C\uFF0C\u6216\u663E\u5F0F\u6307\u5B9A projectId\u3002`
2714
+ };
2975
2715
  }
2976
- const projectIds = baseline.projectIds ?? [];
2977
- if (projectIds.length === 0) {
2716
+ const baseline = await api.cli.projectBaseBranch({ taskId });
2717
+ const projectId = baseline.projectId?.trim() || null;
2718
+ if (!projectId) {
2978
2719
  return {
2979
2720
  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`
2721
+ diagnostic: `\u4EFB\u52A1 ${taskId} \u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
2982
2722
  };
2983
2723
  }
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
- };
2724
+ return { projectId, diagnostic: null };
2989
2725
  } catch (err) {
2990
2726
  const detail = err instanceof Error ? err.message : String(err);
2991
2727
  return {
@@ -3204,7 +2940,7 @@ async function appendContent(cfg, messageId, content) {
3204
2940
  const api = createApmApiClient(cfg);
3205
2941
  await api.cli.webideAppendMessageContent({ id: messageId, content });
3206
2942
  }
3207
- async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
2943
+ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3208
2944
  const workdir = requireRemoteWorkdir(msg.workdir);
3209
2945
  const messageId = msg.messageId;
3210
2946
  const taskId = msg.taskId;
@@ -3302,7 +3038,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3302
3038
  async () => runTaskBranch(taskId, { cwd: workdir }),
3303
3039
  (branchResult) => {
3304
3040
  markBranchDone(branchCacheKey, workdir);
3305
- return `kind=${branchResult.kind} branch=${branchResult.branch} repos=${branchResult.repos.length}`;
3041
+ return `kind=${branchResult.kind} branch=${branchResult.branch} baseline=${branchResult.baseline} repos=${branchResult.repos.length}`;
3306
3042
  }
3307
3043
  );
3308
3044
  } else {
@@ -3419,7 +3155,6 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3419
3155
  enableSandbox: false,
3420
3156
  onInvalidatePersistedAgentId: () => clearWebIdeAgentId(workdir, taskId),
3421
3157
  taskId,
3422
- terminalRpc: options?.terminalRpc,
3423
3158
  createRemoteLogSync: (agentId) => {
3424
3159
  saveWebIdeAgentId(workdir, taskId, agentId);
3425
3160
  logSyncRef.current = createThrottledWebIdeMessageLogSync(
@@ -3611,68 +3346,166 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3611
3346
  }
3612
3347
  }
3613
3348
 
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
- });
3349
+ // src/commands/connect/handle-start-project.ts
3350
+ async function updateStatus2(cfg, messageId, status) {
3351
+ const api = createApmApiClient(cfg);
3352
+ await api.cli.webideUpdateMessageStatus({ id: messageId, status });
3353
+ }
3354
+ async function setError2(cfg, messageId, error) {
3355
+ const api = createApmApiClient(cfg);
3356
+ await api.cli.webideSetMessageError({ id: messageId, error });
3627
3357
  }
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"));
3358
+ async function handleStartProject(cfg, msg, signal) {
3359
+ const workdir = requireRemoteWorkdir(msg.workdir);
3360
+ const messageId = msg.messageId;
3361
+ const taskId = msg.taskId;
3362
+ console.log(
3363
+ `[apm] webide-start-project target=${msg.target} restart=${msg.restart} taskId=${taskId} messageId=${messageId}`
3364
+ );
3365
+ await updateStatus2(cfg, messageId, "TYPING");
3366
+ const eventSession = new EventSession(msg.content);
3367
+ const logSyncRef = {
3368
+ current: createThrottledWebIdeMessageLogSync(
3369
+ cfg,
3370
+ { taskId, messageId, agentId: "webide-start-project" },
3371
+ (err) => {
3372
+ console.warn(
3373
+ "[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3374
+ err instanceof Error ? err.message : err
3375
+ );
3376
+ }
3377
+ )
3378
+ };
3379
+ try {
3380
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3381
+ await ensureWorkspaceInitialized(workdir);
3382
+ const api = createApmApiClient(cfg);
3383
+ const outcome = await runCursorAgent(
3384
+ cfg,
3385
+ {
3386
+ messageId,
3387
+ prompt: msg.content,
3388
+ model: msg.model,
3389
+ apiKey: msg.apiKey,
3390
+ workdir,
3391
+ user: msg.user || "webide"
3392
+ },
3393
+ {
3394
+ signal,
3395
+ forceSend: true,
3396
+ eventSession,
3397
+ enableAppendMessage: false,
3398
+ enableAskQuestion: false,
3399
+ enableWebIdePlanTools: false,
3400
+ enablePtySessionMcp: true,
3401
+ enableSandbox: false,
3402
+ taskId,
3403
+ createRemoteLogSync: (agentId) => {
3404
+ logSyncRef.current = createThrottledWebIdeMessageLogSync(
3405
+ cfg,
3406
+ { taskId, messageId, agentId },
3407
+ (err) => {
3408
+ console.warn(
3409
+ "[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3410
+ err instanceof Error ? err.message : err
3411
+ );
3412
+ }
3413
+ );
3414
+ return logSyncRef.current;
3415
+ },
3416
+ onRunStarted: async ({ agentId, runId }) => {
3417
+ eventSession.addCliStep(
3418
+ "\u542F\u52A8\u72EC\u7ACB Agent",
3419
+ `agentId=${agentId} runId=${runId}`,
3420
+ "ok"
3421
+ );
3422
+ logSyncRef.current?.schedule(eventSession);
3423
+ await logSyncRef.current?.markRun(runId, "running");
3424
+ await logSyncRef.current?.flush(eventSession);
3425
+ }
3426
+ }
3427
+ );
3428
+ const tokenUsage = outcome.usage != null ? {
3429
+ modelId: outcome.modelId ?? (msg.model?.trim() || void 0),
3430
+ inputTokens: outcome.usage.inputTokens,
3431
+ outputTokens: outcome.usage.outputTokens,
3432
+ cacheReadTokens: outcome.usage.cacheReadTokens,
3433
+ cacheWriteTokens: outcome.usage.cacheWriteTokens,
3434
+ totalTokens: outcome.usage.totalTokens,
3435
+ ...outcome.usage.reasoningTokens != null ? { reasoningTokens: outcome.usage.reasoningTokens } : {}
3436
+ } : void 0;
3437
+ if (outcome.status === "error") {
3438
+ const detail = formatCursorRunFailure(outcome.runId, {
3439
+ resultText: outcome.result
3440
+ });
3441
+ await logSyncRef.current?.markRun(
3442
+ outcome.runId,
3443
+ "error",
3444
+ detail,
3445
+ tokenUsage
3446
+ );
3447
+ await setError2(cfg, messageId, detail);
3633
3448
  return;
3634
3449
  }
3635
- pending.set(id, { resolve: resolve6, reject });
3636
- parentPort.postMessage({
3637
- type: "terminal-rpc",
3638
- id,
3639
- op,
3640
- args
3450
+ if (outcome.status === "cancelled" || signal.aborted) {
3451
+ await logSyncRef.current?.markRun(
3452
+ outcome.runId,
3453
+ "cancelled",
3454
+ "\u5DF2\u53D6\u6D88",
3455
+ tokenUsage
3456
+ );
3457
+ await updateStatus2(cfg, messageId, "CANCELLED");
3458
+ return;
3459
+ }
3460
+ const fallback = resolveMessageReplyFallback({
3461
+ assistantText: outcome.assistantText,
3462
+ result: outcome.result
3641
3463
  });
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
- };
3464
+ await api.cli.webideEnsureMessageContent({
3465
+ id: messageId,
3466
+ content: fallback
3467
+ });
3468
+ await logSyncRef.current?.markRun(
3469
+ outcome.runId,
3470
+ "finished",
3471
+ null,
3472
+ tokenUsage
3473
+ );
3474
+ await updateStatus2(cfg, messageId, "SUCCESS");
3475
+ console.log(
3476
+ `[apm] webide-start-project \u5B8C\u6210 target=${msg.target} messageId=${messageId} agentId=${outcome.agentId}`
3477
+ );
3478
+ } catch (err) {
3479
+ const detail = err instanceof Error ? err.message : String(err);
3480
+ console.error(`[apm] webide-start-project \u5931\u8D25: ${detail}`);
3481
+ try {
3482
+ await setError2(cfg, messageId, detail);
3483
+ } catch (statusErr) {
3484
+ console.error(
3485
+ "[apm] \u5199\u5165\u542F\u52A8\u9879\u76EE FAILED \u5931\u8D25:",
3486
+ statusErr instanceof Error ? statusErr.message : statusErr
3487
+ );
3488
+ }
3489
+ }
3657
3490
  }
3658
3491
 
3659
3492
  // src/commands/connect/webide-message-worker.ts
3660
3493
  var controllers = /* @__PURE__ */ new Map();
3661
- installTerminalRpcListener();
3662
- var terminalRpc = createWorkerTerminalRpc();
3663
3494
  async function runJob(cfg, msg) {
3664
3495
  const controller = new AbortController();
3665
3496
  controllers.set(msg.messageId, controller);
3666
3497
  try {
3667
- await handleWebIdeInboundMessage(cfg, msg, controller.signal, {
3668
- terminalRpc
3669
- });
3670
- parentPort2?.postMessage({
3498
+ if (msg.type === "webide-start-project") {
3499
+ await handleStartProject(cfg, msg, controller.signal);
3500
+ } else {
3501
+ await handleWebIdeInboundMessage(cfg, msg, controller.signal);
3502
+ }
3503
+ parentPort?.postMessage({
3671
3504
  type: "done",
3672
3505
  messageId: msg.messageId
3673
3506
  });
3674
3507
  } catch (err) {
3675
- parentPort2?.postMessage({
3508
+ parentPort?.postMessage({
3676
3509
  type: "error",
3677
3510
  messageId: msg.messageId,
3678
3511
  error: err instanceof Error ? err.message : String(err)
@@ -3681,7 +3514,7 @@ async function runJob(cfg, msg) {
3681
3514
  controllers.delete(msg.messageId);
3682
3515
  }
3683
3516
  }
3684
- parentPort2?.on("message", (raw) => {
3517
+ parentPort?.on("message", (raw) => {
3685
3518
  if (raw.type === "cancel") {
3686
3519
  controllers.get(raw.messageId)?.abort();
3687
3520
  return;