ai-project-manage-cli 8.0.1 → 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,36 +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
- matchRepository: defineEndpoint({
88
- method: "GET",
89
- path: "/cli/repositories/match"
90
- }),
91
67
  createWebIdeDraftPullRequest: defineEndpoint({
92
68
  method: "POST",
93
69
  path: "/cli/webide/draft-pull-requests"
@@ -112,35 +88,9 @@ var requestConfig = {
112
88
  method: "DELETE",
113
89
  path: "/cli/project-documents"
114
90
  }),
115
- createTaskDeployment: defineEndpoint({
116
- method: "POST",
117
- path: "/cli/task-deployments"
118
- }),
119
- updateTaskDeploymentStatus: defineEndpoint({
120
- method: "PUT",
121
- path: "/cli/task-deployments/status"
122
- }),
123
- syncTaskDeploymentLog: defineEndpoint({
124
- method: "PUT",
125
- path: "/cli/task-deployments/log"
126
- }),
127
- completeTaskDeployment: defineEndpoint({
128
- method: "PUT",
129
- path: "/cli/task-deployments/complete"
130
- }),
131
- getDeployArtifactStorage: defineEndpoint(
132
- {
133
- method: "GET",
134
- path: "/cli/deploy-artifact-storage"
135
- }
136
- ),
137
91
  getApmLogStorage: defineEndpoint({
138
92
  method: "GET",
139
93
  path: "/cli/apm-log-storage"
140
- }),
141
- attachTaskDeploymentArtifact: defineEndpoint({
142
- method: "PUT",
143
- path: "/cli/task-deployments/artifact"
144
94
  })
145
95
  }
146
96
  };
@@ -221,7 +171,7 @@ function createApmApiClient(cfg) {
221
171
  });
222
172
  }
223
173
 
224
- // src/workdir-path.ts
174
+ // src/utils/path.ts
225
175
  import { realpathSync } from "fs";
226
176
  import { platform } from "os";
227
177
  import { resolve } from "path";
@@ -268,7 +218,7 @@ function requireRemoteWorkdir(workdir) {
268
218
  return resolveWorkdirPath(trimmed);
269
219
  }
270
220
 
271
- // src/command-utils.ts
221
+ // src/utils/workspace.ts
272
222
  import { copyFileSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
273
223
  import { dirname, join as join2, resolve as resolve2 } from "path";
274
224
  import { fileURLToPath } from "url";
@@ -405,11 +355,11 @@ async function copyWebIdeTemplateFiles(targetDir, workdir = resolveWorkdirPath()
405
355
  assertWebIdeTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
406
356
  }
407
357
 
408
- // src/git-utils.ts
358
+ // src/utils/git.ts
409
359
  import { execFile as execFile2 } from "child_process";
410
360
  import { promisify as promisify2 } from "util";
411
361
 
412
- // src/git-remote.ts
362
+ // src/utils/git-remote.ts
413
363
  import { execFile } from "child_process";
414
364
  import { promisify } from "util";
415
365
  var execFileAsync = promisify(execFile);
@@ -462,7 +412,7 @@ async function tryReadHttpsGitOriginUrl(cwd) {
462
412
  return toHttpsGitRemoteUrl(raw);
463
413
  }
464
414
 
465
- // src/git-utils.ts
415
+ // src/utils/git.ts
466
416
  var execFileAsync2 = promisify2(execFile2);
467
417
  function gitExecEnv() {
468
418
  const existing = process.env.GIT_SSH_COMMAND?.trim();
@@ -560,90 +510,15 @@ async function branchHasChangesAgainstBase(cwd, baseBranch, headBranch) {
560
510
  return true;
561
511
  }
562
512
  }
563
- var FEATURE_BRANCH_RE = /^feat\/task-.+/;
513
+ var FEATURE_BRANCH_RE = /^feat\/task-(.+)$/;
564
514
  function isFeatureBranchName(branch) {
565
515
  return FEATURE_BRANCH_RE.test(branch.trim().replace(/^origin\//, ""));
566
516
  }
567
- async function resolveFeatureBranchBaseFromGit(cwd, headBranch) {
568
- const head = headBranch.trim().replace(/^origin\//, "");
569
- if (!head) {
570
- throw new Error("[apm] head \u5206\u652F\u540D\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u63A8\u65AD\u57FA\u7EBF");
571
- }
572
- try {
573
- const reflog = await execGit(
574
- cwd,
575
- ["reflog", "show", "--date=iso", head],
576
- true
577
- );
578
- for (const line of reflog.split("\n")) {
579
- const created = line.match(
580
- /Created from (?:refs\/remotes\/origin\/|origin\/)?(\S+)/i
581
- );
582
- if (created?.[1]) {
583
- const name = created[1].replace(/^origin\//, "");
584
- if (name && name !== head && !isFeatureBranchName(name)) {
585
- return name;
586
- }
587
- }
588
- const moving = line.match(
589
- /checkout: moving from (\S+) to (?:origin\/)?\S+/i
590
- );
591
- if (moving?.[1]) {
592
- const from = moving[1].replace(/^origin\//, "");
593
- if (from && from !== head && !isFeatureBranchName(from)) {
594
- return from;
595
- }
596
- }
597
- }
598
- } catch {
599
- }
600
- await execGit(cwd, ["fetch", "origin", "--prune"], true);
601
- let headRef = head;
602
- try {
603
- await execGit(cwd, ["rev-parse", "--verify", `origin/${head}`], true);
604
- headRef = `origin/${head}`;
605
- } catch {
606
- await execGit(cwd, ["rev-parse", "--verify", head], true);
607
- }
608
- const refsOut = await execGit(
609
- cwd,
610
- ["for-each-ref", "--format=%(refname:short)", "refs/remotes/origin/"],
611
- true
612
- );
613
- let best = null;
614
- for (const ref of refsOut.split("\n").map((s) => s.trim()).filter(Boolean)) {
615
- const name = ref.replace(/^origin\//, "");
616
- if (!name || name === "HEAD" || name === head || isFeatureBranchName(name)) {
617
- continue;
618
- }
619
- try {
620
- await execGit(
621
- cwd,
622
- ["merge-base", "--is-ancestor", `origin/${name}`, headRef],
623
- true
624
- );
625
- const ahead = Number.parseInt(
626
- (await execGit(
627
- cwd,
628
- ["rev-list", "--count", `origin/${name}..${headRef}`],
629
- true
630
- )).trim(),
631
- 10
632
- );
633
- if (!Number.isFinite(ahead) || ahead < 0) continue;
634
- if (!best || ahead < best.ahead) {
635
- best = { name, ahead };
636
- }
637
- } catch {
638
- continue;
639
- }
640
- }
641
- if (best) {
642
- return best.name;
643
- }
644
- throw new Error(
645
- `[apm] \u65E0\u6CD5\u63A8\u65AD ${head} \u57FA\u4E8E\u54EA\u6761\u5206\u652F\u521B\u5EFA\uFF0C\u8BF7\u786E\u8BA4\u672C\u5730 reflog \u6216 origin \u8FDC\u7A0B\u5206\u652F`
646
- );
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;
647
522
  }
648
523
  async function hasUpstream(cwd) {
649
524
  try {
@@ -670,7 +545,7 @@ async function pushCurrentBranch(cwd) {
670
545
  return true;
671
546
  }
672
547
 
673
- // src/workspace-repos.ts
548
+ // src/utils/workspace-repos.ts
674
549
  import {
675
550
  existsSync as existsSync2,
676
551
  mkdirSync as mkdirSync3,
@@ -1052,7 +927,12 @@ async function runTaskBranch(taskId, options = {}) {
1052
927
  cwd: gitRoot
1053
928
  });
1054
929
  }
1055
- return { branch, kind: manifest.kind, repos: repoRoots };
930
+ return {
931
+ branch,
932
+ baseline: baselineBranch,
933
+ kind: manifest.kind,
934
+ repos: repoRoots
935
+ };
1056
936
  }
1057
937
 
1058
938
  // src/commands/connect/cursor-agent.ts
@@ -1060,9 +940,8 @@ import {
1060
940
  Agent,
1061
941
  CursorAgentError
1062
942
  } from "@cursor/sdk";
1063
- import { setMaxListeners as setMaxListeners2 } from "node:events";
1064
943
 
1065
- // src/plan-format.ts
944
+ // src/utils/plan-format.ts
1066
945
  function formatPlanMarkdown(raw) {
1067
946
  let text = raw.trim();
1068
947
  if (!text) {
@@ -1083,7 +962,7 @@ function formatPlanMarkdown(raw) {
1083
962
  return text.replace(/\r\n/g, "\n").trimEnd() + "\n";
1084
963
  }
1085
964
 
1086
- // src/session-utils.ts
965
+ // src/utils/session.ts
1087
966
  function serializeTokenUsage(usage) {
1088
967
  return {
1089
968
  inputTokens: usage.inputTokens,
@@ -1365,71 +1244,9 @@ ${JSON.stringify(event, null, 2)}
1365
1244
  \`\`\``;
1366
1245
  }
1367
1246
 
1368
- // src/commands/connect/abort-signal-debug.ts
1369
- import {
1370
- getEventListeners,
1371
- getMaxListeners,
1372
- setMaxListeners
1373
- } from "node:events";
1374
- function isAbortSignalDebugEnabled() {
1375
- const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
1376
- return v === "1" || v === "true" || v === "yes";
1377
- }
1378
- function formatAbortSignalStats(signal, label) {
1379
- if (!signal) {
1380
- return `[apm:abort-debug] ${label}: (no signal)`;
1381
- }
1382
- const listeners = getEventListeners(signal, "abort");
1383
- const max = getMaxListeners(signal);
1384
- return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
1385
- }
1386
- function logAbortSignalStats(signal, label) {
1387
- if (!isAbortSignalDebugEnabled()) return;
1388
- console.log(formatAbortSignalStats(signal, label));
1389
- }
1390
- var installed = false;
1391
- function installAbortSignalDebug() {
1392
- if (!isAbortSignalDebugEnabled() || installed) return;
1393
- installed = true;
1394
- const maxFromEnv = Number.parseInt(
1395
- process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
1396
- 10
1397
- );
1398
- if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
1399
- setMaxListeners(maxFromEnv);
1400
- console.log(
1401
- `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
1402
- );
1403
- }
1404
- process.on("warning", (warning) => {
1405
- if (warning.name !== "MaxListenersExceededWarning") return;
1406
- console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
1407
- if (warning.stack) {
1408
- console.warn(warning.stack);
1409
- }
1410
- });
1411
- const proto = AbortSignal.prototype;
1412
- const original = proto.addEventListener;
1413
- proto.addEventListener = function(type, listener, options) {
1414
- if (type === "abort") {
1415
- const sig = this;
1416
- const before = getEventListeners(sig, "abort").length;
1417
- const max = getMaxListeners(sig);
1418
- const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
1419
- console.log(
1420
- `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
1421
- ${stack}`
1422
- );
1423
- }
1424
- return original.call(this, type, listener, options);
1425
- };
1426
- console.log(
1427
- "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
1428
- );
1429
- }
1430
-
1431
- // src/commands/connect/append-message-tool.ts
1432
- function createAppendMessageCustomTools(messageId, appendContent2) {
1247
+ // src/commands/connect/tools/append-message-tool.ts
1248
+ function createAppendMessageCustomTools(options) {
1249
+ const { appendContent: appendContent2 } = options;
1433
1250
  return {
1434
1251
  AppendMessage: {
1435
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",
@@ -1453,7 +1270,6 @@ function createAppendMessageCustomTools(messageId, appendContent2) {
1453
1270
  }
1454
1271
  try {
1455
1272
  await appendContent2(content);
1456
- console.log(`[apm] AppendMessage \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
1457
1273
  return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
1458
1274
  } catch (err) {
1459
1275
  const detail = err instanceof Error ? err.message : String(err);
@@ -1467,7 +1283,7 @@ function createAppendMessageCustomTools(messageId, appendContent2) {
1467
1283
  };
1468
1284
  }
1469
1285
 
1470
- // src/commands/connect/ask-question-tool.ts
1286
+ // src/commands/connect/tools/ask-question-tool.ts
1471
1287
  var INPUT_SCHEMA = {
1472
1288
  type: "object",
1473
1289
  properties: {
@@ -1505,121 +1321,118 @@ var INPUT_SCHEMA = {
1505
1321
  };
1506
1322
  function createAskQuestionTool(options) {
1507
1323
  return {
1508
- description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
1509
- inputSchema: INPUT_SCHEMA,
1510
- execute: async (args) => {
1511
- options?.onInvoke?.(args);
1512
- if (options?.execute) {
1513
- return options.execute(args);
1514
- }
1515
- const payload = JSON.stringify(args, null, 2);
1516
- 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:
1517
1334
  ${payload}`);
1518
- 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:
1519
1336
  ${payload}`;
1337
+ }
1520
1338
  }
1521
1339
  };
1522
1340
  }
1523
1341
 
1524
- // src/commands/connect/webide-merge-tools.ts
1342
+ // src/commands/connect/tools/webide-merge-tools.ts
1525
1343
  function createMergeWebIdePullRequestsTool(options) {
1526
- const { cfg, taskId } = options;
1344
+ const { webideMergePullRequests } = options;
1527
1345
  return {
1528
- description: "Merge all open pull requests for this WebIDE task. Call once with confirmedConflictFree=true. No baseline sync or review is required before calling.",
1529
- inputSchema: {
1530
- type: "object",
1531
- properties: {
1532
- confirmedConflictFree: {
1533
- type: "boolean",
1534
- description: "Must be true to confirm you intend to merge this task's open PRs now."
1535
- }
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"]
1536
1357
  },
1537
- required: ["confirmedConflictFree"]
1538
- },
1539
- execute: async (args) => {
1540
- if (args.confirmedConflictFree !== true) {
1541
- throw new Error(
1542
- "MergeWebIdePullRequests \u8981\u6C42 confirmedConflictFree=true\u3002"
1543
- );
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);
1544
1366
  }
1545
- const api = createApmApiClient(cfg);
1546
- console.log(`[apm] MergeWebIdePullRequests taskId=${taskId}`);
1547
- const result = await api.cli.webideMergePullRequests({ taskId });
1548
- return JSON.stringify(result, null, 2);
1549
1367
  }
1550
1368
  };
1551
1369
  }
1552
1370
 
1553
- // src/commands/connect/webide-plan-tools.ts
1371
+ // src/commands/connect/tools/webide-plan-tools.ts
1554
1372
  function asString(value) {
1555
1373
  return typeof value === "string" ? value.trim() : "";
1556
1374
  }
1557
1375
  function createRecommendPlanTool(options) {
1558
- const { cfg, taskId } = options;
1376
+ const { webideRecommendPlan } = options;
1559
1377
  return {
1560
- 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.",
1561
- inputSchema: {
1562
- type: "object",
1563
- properties: {
1564
- recommendation: {
1565
- type: "string",
1566
- enum: ["write", "skip", "unknown"],
1567
- 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
+ }
1568
1392
  },
1569
- reason: {
1570
- type: "string",
1571
- description: "Short Chinese reason for the recommendation"
1572
- }
1393
+ required: ["recommendation"]
1573
1394
  },
1574
- required: ["recommendation"]
1575
- },
1576
- execute: async (args) => {
1577
- const raw = asString(args.recommendation);
1578
- const recommendation = raw === "write" || raw === "skip" || raw === "unknown" ? raw : "unknown";
1579
- const reason = asString(args.reason) || void 0;
1580
- const api = createApmApiClient(cfg);
1581
- await api.cli.webideRecommendPlan({
1582
- taskId,
1583
- recommendation,
1584
- reason
1585
- });
1586
- console.log(
1587
- `[apm] RecommendPlan taskId=${taskId} recommendation=${recommendation}`
1588
- );
1589
- 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
+ }
1590
1405
  }
1591
1406
  };
1592
1407
  }
1593
1408
  function createUpsertWebIdePlanTool(options) {
1594
- const { cfg, taskId } = options;
1409
+ const { webideUpsertPlan } = options;
1595
1410
  return {
1596
- 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.",
1597
- inputSchema: {
1598
- type: "object",
1599
- properties: {
1600
- content: {
1601
- type: "string",
1602
- description: "Full implementation plan in Markdown (Chinese)"
1603
- }
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"]
1604
1422
  },
1605
- required: ["content"]
1606
- },
1607
- execute: async (args) => {
1608
- const content = asString(args.content);
1609
- if (!content) {
1610
- 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);
1611
1430
  }
1612
- const api = createApmApiClient(cfg);
1613
- await api.cli.webideUpsertPlan({ taskId, content });
1614
- console.log(
1615
- `[apm] UpsertWebIdePlan taskId=${taskId} chars=${content.length}`
1616
- );
1617
- return JSON.stringify({ ok: true, chars: content.length }, null, 2);
1618
1431
  }
1619
1432
  };
1620
1433
  }
1621
1434
 
1622
- // src/commands/connect/webide-test-case-tools.ts
1435
+ // src/commands/connect/tools/webide-test-case-tools.ts
1623
1436
  function asString2(value) {
1624
1437
  return typeof value === "string" ? value.trim() : "";
1625
1438
  }
@@ -1647,222 +1460,62 @@ function parseCases(raw) {
1647
1460
  return cases;
1648
1461
  }
1649
1462
  function createUpsertWebIdeTestCasesTool(options) {
1650
- const { cfg, taskId } = options;
1463
+ const { webideReplaceTestCases } = options;
1651
1464
  return {
1652
- 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.",
1653
- inputSchema: {
1654
- type: "object",
1655
- properties: {
1656
- cases: {
1657
- type: "array",
1658
- description: "Complete list of test cases to store on the platform",
1659
- items: {
1660
- type: "object",
1661
- properties: {
1662
- name: {
1663
- type: "string",
1664
- description: "Short stable case id / name"
1665
- },
1666
- description: {
1667
- type: "string",
1668
- description: "What this case verifies (Chinese ok)"
1669
- },
1670
- steps: {
1671
- type: "array",
1672
- items: { type: "string" },
1673
- 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
+ }
1674
1493
  },
1675
- sortOrder: {
1676
- type: "number",
1677
- description: "Optional display order (0-based)"
1678
- }
1679
- },
1680
- required: ["name", "description", "steps"]
1494
+ required: ["name", "description", "steps"]
1495
+ }
1681
1496
  }
1682
- }
1683
- },
1684
- required: ["cases"]
1685
- },
1686
- execute: async (args) => {
1687
- const cases = parseCases(args.cases);
1688
- if (cases.length === 0) {
1689
- throw new Error(
1690
- "UpsertWebIdeTestCases \u7F3A\u5C11\u6709\u6548 cases\uFF08\u6BCF\u6761\u9700 name \u4E0E\u975E\u7A7A steps\uFF09"
1691
- );
1692
- }
1693
- const api = createApmApiClient(cfg);
1694
- const result = await api.cli.webideReplaceTestCases({ taskId, cases });
1695
- console.log(
1696
- `[apm] UpsertWebIdeTestCases taskId=${taskId} count=${cases.length}`
1697
- );
1698
- return JSON.stringify(
1699
- { ok: true, count: result.count ?? cases.length },
1700
- null,
1701
- 2
1702
- );
1703
- }
1704
- };
1705
- }
1706
-
1707
- // src/commands/connect/webide-terminal-tools.ts
1708
- function asString3(value) {
1709
- return typeof value === "string" ? value.trim() : "";
1710
- }
1711
- function asPorts(value) {
1712
- if (!Array.isArray(value)) return void 0;
1713
- 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));
1714
- return ports.length ? ports : void 0;
1715
- }
1716
- function createWebIdeTerminalTools(options) {
1717
- const { cfg, taskId, workdir, rpc } = options;
1718
- const StartWebIdeTerminal = {
1719
- 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.",
1720
- inputSchema: {
1721
- type: "object",
1722
- properties: {
1723
- key: {
1724
- type: "string",
1725
- description: "Stable key, e.g. frontend / backend"
1726
- },
1727
- name: { type: "string", description: "Display name" },
1728
- cwd: {
1729
- type: "string",
1730
- description: "Working directory (absolute or under workspace)"
1731
1497
  },
1732
- command: {
1733
- type: "string",
1734
- description: "Shell command to run, e.g. pnpm dev"
1735
- },
1736
- readyPattern: {
1737
- type: "string",
1738
- description: "Optional regex; when matched in logs, status becomes READY"
1739
- },
1740
- portsHint: {
1741
- type: "array",
1742
- items: { type: "number" },
1743
- description: "Optional expected listen ports"
1744
- }
1498
+ required: ["cases"]
1745
1499
  },
1746
- required: ["key", "cwd", "command"]
1747
- },
1748
- execute: async (args) => {
1749
- const key = asString3(args.key);
1750
- const cwd = asString3(args.cwd) || workdir;
1751
- const command = asString3(args.command);
1752
- if (!key || !command) {
1753
- return {
1754
- content: [{ type: "text", text: "key / command \u4E0D\u80FD\u4E3A\u7A7A" }],
1755
- isError: true
1756
- };
1757
- }
1758
- try {
1759
- const result = rpc ? await rpc.start({
1760
- taskId,
1761
- key,
1762
- name: asString3(args.name) || void 0,
1763
- cwd,
1764
- command,
1765
- readyPattern: asString3(args.readyPattern) || void 0,
1766
- portsHint: asPorts(args.portsHint)
1767
- }) : await (async () => {
1768
- throw new Error("Terminal RPC \u4E0D\u53EF\u7528\uFF08\u9700\u5728 connect \u4E3B\u8FDB\u7A0B\uFF09");
1769
- })();
1770
- return JSON.stringify(result, null, 2);
1771
- } catch (err) {
1772
- const detail = err instanceof Error ? err.message : String(err);
1773
- return {
1774
- content: [{ type: "text", text: `\u542F\u52A8\u7EC8\u7AEF\u5931\u8D25: ${detail}` }],
1775
- isError: true
1776
- };
1777
- }
1778
- }
1779
- };
1780
- const StopWebIdeTerminal = {
1781
- description: "Stop a WebIDE terminal and kill its process tree. Soft-terminates (status EXITED, record kept).",
1782
- inputSchema: {
1783
- type: "object",
1784
- properties: {
1785
- key: { type: "string", description: "Terminal key" },
1786
- terminalId: { type: "string", description: "Server terminal id" }
1787
- }
1788
- },
1789
- execute: async (args) => {
1790
- const key = asString3(args.key) || void 0;
1791
- const terminalId = asString3(args.terminalId) || void 0;
1792
- if (!key && !terminalId) {
1793
- return {
1794
- content: [{ type: "text", text: "\u9700\u8981 key \u6216 terminalId" }],
1795
- isError: true
1796
- };
1797
- }
1798
- try {
1799
- const result = rpc ? await rpc.stop({ taskId, key, terminalId }) : await (async () => {
1800
- throw new Error("Terminal RPC \u4E0D\u53EF\u7528\uFF08\u9700\u5728 connect \u4E3B\u8FDB\u7A0B\uFF09");
1801
- })();
1802
- return JSON.stringify(result, null, 2);
1803
- } catch (err) {
1804
- const detail = err instanceof Error ? err.message : String(err);
1805
- return {
1806
- content: [{ type: "text", text: `\u505C\u6B62\u7EC8\u7AEF\u5931\u8D25: ${detail}` }],
1807
- isError: true
1808
- };
1809
- }
1810
- }
1811
- };
1812
- const StopAllWebIdeTerminals = {
1813
- 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.",
1814
- inputSchema: {
1815
- type: "object",
1816
- properties: {}
1817
- },
1818
- execute: async () => {
1819
- try {
1820
- const result = rpc ? await rpc.stopAll({ taskId }) : await (async () => {
1821
- throw new Error("Terminal RPC \u4E0D\u53EF\u7528\uFF08\u9700\u5728 connect \u4E3B\u8FDB\u7A0B\uFF09");
1822
- })();
1823
- return JSON.stringify(result, null, 2);
1824
- } catch (err) {
1825
- const detail = err instanceof Error ? err.message : String(err);
1826
- return {
1827
- content: [{ type: "text", text: `\u5168\u90E8\u505C\u6B62\u5931\u8D25: ${detail}` }],
1828
- isError: true
1829
- };
1830
- }
1831
- }
1832
- };
1833
- const ListWebIdeTerminals = {
1834
- 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.",
1835
- inputSchema: {
1836
- type: "object",
1837
- properties: {}
1838
- },
1839
- execute: async () => {
1840
- try {
1841
- if (rpc) {
1842
- const result2 = await rpc.list({ taskId });
1843
- 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
+ );
1844
1506
  }
1845
- const api = createApmApiClient(cfg);
1846
- const result = await api.cli.webideListTerminals({ taskId });
1847
- return JSON.stringify(result, null, 2);
1848
- } catch (err) {
1849
- const detail = err instanceof Error ? err.message : String(err);
1850
- return {
1851
- content: [{ type: "text", text: `\u5217\u51FA\u7EC8\u7AEF\u5931\u8D25: ${detail}` }],
1852
- isError: true
1853
- };
1507
+ const result = await webideReplaceTestCases({ cases });
1508
+ return JSON.stringify(
1509
+ { ok: true, count: result.count ?? cases.length },
1510
+ null,
1511
+ 2
1512
+ );
1854
1513
  }
1855
1514
  }
1856
1515
  };
1857
- return {
1858
- StartWebIdeTerminal,
1859
- StopWebIdeTerminal,
1860
- StopAllWebIdeTerminals,
1861
- ListWebIdeTerminals
1862
- };
1863
1516
  }
1864
1517
 
1865
- // src/commands/connect/cursor-custom-tools.ts
1518
+ // src/commands/connect/tools/index.ts
1866
1519
  var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
1867
1520
  AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
1868
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
@@ -1870,39 +1523,33 @@ AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u
1870
1523
  var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
1871
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
1872
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`;
1873
- function createCursorCustomTools(cfg, messageId, options) {
1526
+ function createCursorCustomTools(cfg, options) {
1874
1527
  const tools = {
1875
- ...createAppendMessageCustomTools(messageId, options.appendMessageContent),
1876
- AskQuestion: createAskQuestionTool({
1528
+ ...options.enableAppendMessage !== false && options.appendMessageContent ? createAppendMessageCustomTools({
1529
+ appendContent: options.appendMessageContent
1530
+ }) : {},
1531
+ ...createAskQuestionTool({
1877
1532
  onInvoke: options?.onAskQuestion,
1878
1533
  execute: options?.askQuestionExecute
1879
1534
  })
1880
1535
  };
1881
- if (options?.enableWebIdePlanTools && options.taskId) {
1882
- tools.RecommendPlan = createRecommendPlanTool({
1883
- cfg,
1884
- taskId: options.taskId
1885
- });
1886
- tools.UpsertWebIdePlan = createUpsertWebIdePlanTool({
1887
- cfg,
1888
- taskId: options.taskId
1889
- });
1890
- tools.UpsertWebIdeTestCases = createUpsertWebIdeTestCasesTool({
1891
- cfg,
1892
- taskId: options.taskId
1893
- });
1894
- tools.MergeWebIdePullRequests = createMergeWebIdePullRequestsTool({
1895
- cfg,
1896
- taskId: options.taskId
1897
- });
1898
- if (options.workdir) {
1536
+ if (options?.taskId && options.workdir) {
1537
+ const { cli } = createApmApiClient(cfg);
1538
+ const taskId = options.taskId;
1539
+ if (options.enableWebIdePlanTools) {
1899
1540
  Object.assign(
1900
1541
  tools,
1901
- createWebIdeTerminalTools({
1902
- cfg,
1903
- taskId: options.taskId,
1904
- workdir: options.workdir,
1905
- 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 })
1906
1553
  })
1907
1554
  );
1908
1555
  }
@@ -1923,9 +1570,30 @@ function withWorkspaceBoundaryHint(prompt) {
1923
1570
  ${WORKSPACE_BOUNDARY_HINT}`;
1924
1571
  }
1925
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
+
1926
1596
  // src/commands/connect/cursor-agent.ts
1927
- setMaxListeners2(100);
1928
- installAbortSignalDebug();
1929
1597
  var noopRemoteLogSync = {
1930
1598
  schedule(_session) {
1931
1599
  },
@@ -1988,7 +1656,6 @@ async function obtainAgent(ctx) {
1988
1656
  }
1989
1657
  async function runCursorAgent(cfg, ctx, options) {
1990
1658
  const signal = options.signal;
1991
- logAbortSignalStats(signal, "runCursorAgent:start");
1992
1659
  if (signal?.aborted) {
1993
1660
  throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
1994
1661
  }
@@ -1997,16 +1664,17 @@ async function runCursorAgent(cfg, ctx, options) {
1997
1664
  throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
1998
1665
  }
1999
1666
  const workdir = resolveWorkdirPath(ctx.workdir);
2000
- const customTools = createCursorCustomTools(cfg, ctx.messageId, {
1667
+ const customTools = createCursorCustomTools(cfg, {
2001
1668
  onAskQuestion: options.onAskQuestion,
2002
1669
  appendMessageContent: options.appendMessageContent,
1670
+ enableAppendMessage: options.enableAppendMessage,
2003
1671
  askQuestionExecute: options.askQuestionExecute,
2004
1672
  enableWebIdePlanTools: options.enableWebIdePlanTools,
2005
1673
  taskId: options.taskId,
2006
- workdir,
2007
- terminalRpc: options.terminalRpc
1674
+ workdir
2008
1675
  });
2009
1676
  const enableSandbox = Boolean(options.enableSandbox);
1677
+ const mcpServers = options.enablePtySessionMcp ? createPtySessionMcpServers(cfg) : void 0;
2010
1678
  let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
2011
1679
  prompt = withWorkspaceBoundaryHint(prompt);
2012
1680
  console.log(
@@ -2021,6 +1689,7 @@ async function runCursorAgent(cfg, ctx, options) {
2021
1689
  resumeAgentId: ctx.resumeAgentId,
2022
1690
  customTools,
2023
1691
  enableSandbox,
1692
+ mcpServers,
2024
1693
  onInvalidatePersistedAgentId: options.onInvalidatePersistedAgentId
2025
1694
  });
2026
1695
  const invalidateAgentMapping = () => {
@@ -2034,7 +1703,6 @@ async function runCursorAgent(cfg, ctx, options) {
2034
1703
  void activeRun.cancel().catch(() => void 0);
2035
1704
  };
2036
1705
  signal?.addEventListener("abort", abortRun, { once: true });
2037
- logAbortSignalStats(signal, "runCursorAgent:after-addListener");
2038
1706
  try {
2039
1707
  const run = await agent.send(prompt, {
2040
1708
  ...ctx.mode ? { mode: ctx.mode } : {},
@@ -2044,7 +1712,6 @@ async function runCursorAgent(cfg, ctx, options) {
2044
1712
  }
2045
1713
  });
2046
1714
  activeRun = run;
2047
- logAbortSignalStats(signal, "runCursorAgent:after-send");
2048
1715
  console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
2049
1716
  await options.onRunStarted?.({ agentId: agent.agentId, runId: run.id });
2050
1717
  let lastRunErrorStatus;
@@ -2131,9 +1798,7 @@ async function runCursorAgent(cfg, ctx, options) {
2131
1798
  }
2132
1799
  throw err;
2133
1800
  } finally {
2134
- logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
2135
1801
  signal?.removeEventListener("abort", abortRun);
2136
- logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
2137
1802
  await agent[Symbol.asyncDispose]();
2138
1803
  }
2139
1804
  }
@@ -2309,11 +1974,11 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
2309
1974
  // src/commands/connect/webide-ask-question.ts
2310
1975
  import { setTimeout as delay } from "node:timers/promises";
2311
1976
  var POLL_INTERVAL_MS = 2e3;
2312
- function asString4(value) {
1977
+ function asString3(value) {
2313
1978
  return typeof value === "string" ? value.trim() : "";
2314
1979
  }
2315
1980
  function parseQuestions(args) {
2316
- const title = asString4(args.title) || void 0;
1981
+ const title = asString3(args.title) || void 0;
2317
1982
  const raw = args.questions;
2318
1983
  if (!Array.isArray(raw) || raw.length === 0) {
2319
1984
  throw new Error("AskQuestion \u7F3A\u5C11 questions");
@@ -2322,16 +1987,16 @@ function parseQuestions(args) {
2322
1987
  for (const item of raw) {
2323
1988
  if (!item || typeof item !== "object" || Array.isArray(item)) continue;
2324
1989
  const row = item;
2325
- const id = asString4(row.id);
2326
- const prompt = asString4(row.prompt);
1990
+ const id = asString3(row.id);
1991
+ const prompt = asString3(row.prompt);
2327
1992
  const optionsRaw = row.options;
2328
1993
  if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
2329
1994
  const options = [];
2330
1995
  for (const opt of optionsRaw) {
2331
1996
  if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
2332
1997
  const o = opt;
2333
- const oid = asString4(o.id);
2334
- const label = asString4(o.label);
1998
+ const oid = asString3(o.id);
1999
+ const label = asString3(o.label);
2335
2000
  if (oid && label) options.push({ id: oid, label });
2336
2001
  }
2337
2002
  if (options.length < 2) {
@@ -2365,11 +2030,11 @@ function createWebIdeAskQuestionExecute(options) {
2365
2030
  const byId = new Map(
2366
2031
  list.assumptions.map((a) => [a.questionId, a])
2367
2032
  );
2368
- const pending2 = parsed.questions.filter((q) => {
2033
+ const pending = parsed.questions.filter((q) => {
2369
2034
  const row = byId.get(q.id);
2370
2035
  return !row || row.status !== "RESOLVED";
2371
2036
  });
2372
- if (pending2.length === 0) {
2037
+ if (pending.length === 0) {
2373
2038
  const answers = parsed.questions.map((q) => {
2374
2039
  const row = byId.get(q.id);
2375
2040
  return {
@@ -2397,7 +2062,7 @@ function createWebIdeAskQuestionExecute(options) {
2397
2062
  };
2398
2063
  }
2399
2064
 
2400
- // src/commands/deploy/internal/minio.ts
2065
+ // src/utils/minio.ts
2401
2066
  import * as Minio from "minio";
2402
2067
  var MinioClient = class {
2403
2068
  inner;
@@ -2417,45 +2082,9 @@ var MinioClient = class {
2417
2082
  await this.inner.makeBucket(bucket);
2418
2083
  }
2419
2084
  }
2420
- async deleteObjectsByPrefix(bucket, prefix) {
2421
- const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
2422
- const keys = [];
2423
- await new Promise((resolve6, reject) => {
2424
- objectsStream.on("data", (obj) => {
2425
- if (obj.name) {
2426
- keys.push(obj.name);
2427
- }
2428
- });
2429
- objectsStream.on("error", reject);
2430
- objectsStream.on("end", resolve6);
2431
- });
2432
- const chunkSize = 500;
2433
- for (let i = 0; i < keys.length; i += chunkSize) {
2434
- const chunk = keys.slice(i, i + chunkSize);
2435
- await this.inner.removeObjects(
2436
- bucket,
2437
- chunk.map((name) => name)
2438
- );
2439
- }
2440
- }
2441
2085
  async putObject(bucket, objectKey, body, meta) {
2442
2086
  await this.inner.putObject(bucket, objectKey, body, body.length, meta);
2443
2087
  }
2444
- /** 匿名可读当前桶全部对象(便于静态站点直链) */
2445
- async setBucketPublicRead(bucket) {
2446
- const policy = {
2447
- Version: "2012-10-17",
2448
- Statement: [
2449
- {
2450
- Effect: "Allow",
2451
- Principal: { AWS: ["*"] },
2452
- Action: ["s3:GetObject"],
2453
- Resource: [`arn:aws:s3:::${bucket}/*`]
2454
- }
2455
- ]
2456
- };
2457
- await this.inner.setBucketPolicy(bucket, JSON.stringify(policy));
2458
- }
2459
2088
  };
2460
2089
 
2461
2090
  // src/commands/connect/apm-log-minio.ts
@@ -2740,7 +2369,7 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
2740
2369
  };
2741
2370
  }
2742
2371
  if (!projectBaseBranch) {
2743
- const tip = "\u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F\uFF0C\u65E0\u6CD5\u6309\u57FA\u7EBF\u5339\u914D\u5E73\u53F0\u4ED3\u5E93";
2372
+ const tip = "\u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F\uFF0C\u65E0\u6CD5\u521B\u5EFA PR";
2744
2373
  console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
2745
2374
  return {
2746
2375
  ...empty(),
@@ -2790,7 +2419,7 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
2790
2419
  continue;
2791
2420
  }
2792
2421
  const headBranch = current;
2793
- const originUrl = await tryReadGitOriginUrl(gitRoot);
2422
+ const originUrl = await tryReadHttpsGitOriginUrl(gitRoot) ?? await tryReadGitOriginUrl(gitRoot);
2794
2423
  if (!originUrl) {
2795
2424
  const tip = `${label}\uFF1A\u65E0 remote.origin.url`;
2796
2425
  console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${tip}`);
@@ -2804,29 +2433,7 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
2804
2433
  });
2805
2434
  continue;
2806
2435
  }
2807
- const matched = await api.cli.matchRepository({
2808
- url: originUrl,
2809
- baseBranch: projectBaseBranch
2810
- });
2811
- const repositoryId = matched.repositoryId?.trim();
2812
- if (!repositoryId) {
2813
- const tip = `${label}\uFF1A\u5E73\u53F0\u672A\u627E\u5230\u4ED3\u5E93\uFF08remote=${originUrl}\uFF0C\u57FA\u7EBF=${projectBaseBranch}\uFF09`;
2814
- console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${tip}`);
2815
- tips.push(tip);
2816
- skipped += 1;
2817
- items.push({
2818
- label,
2819
- outcome: "skipped",
2820
- headBranch,
2821
- baseBranch: projectBaseBranch,
2822
- detail: tip
2823
- });
2824
- continue;
2825
- }
2826
- const baseBranch = await resolveFeatureBranchBaseFromGit(
2827
- gitRoot,
2828
- headBranch
2829
- );
2436
+ const baseBranch = projectBaseBranch;
2830
2437
  const hasChanges = await branchHasChangesAgainstBase(
2831
2438
  gitRoot,
2832
2439
  baseBranch,
@@ -2848,7 +2455,7 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
2848
2455
  }
2849
2456
  const pr = await api.cli.createWebIdeDraftPullRequest({
2850
2457
  taskId,
2851
- repositoryId,
2458
+ originUrl,
2852
2459
  headBranch,
2853
2460
  baseBranch
2854
2461
  });
@@ -2983,7 +2590,7 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
2983
2590
  );
2984
2591
  }
2985
2592
 
2986
- // src/project-documents-sync.ts
2593
+ // src/utils/project-documents.ts
2987
2594
  import {
2988
2595
  existsSync as existsSync6,
2989
2596
  readdirSync as readdirSync3,
@@ -3095,24 +2702,25 @@ async function resolveProjectIdForSync(api, workdirPath, options) {
3095
2702
  return { projectId: explicit, diagnostic: null };
3096
2703
  }
3097
2704
  try {
3098
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
3099
- const projectId = baseline.projectId?.trim() || null;
3100
- if (projectId) {
3101
- 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
+ };
3102
2714
  }
3103
- const projectIds = baseline.projectIds ?? [];
3104
- if (projectIds.length === 0) {
2715
+ const baseline = await api.cli.projectBaseBranch({ taskId });
2716
+ const projectId = baseline.projectId?.trim() || null;
2717
+ if (!projectId) {
3105
2718
  return {
3106
2719
  projectId: null,
3107
- 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
3108
- \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`
3109
2721
  };
3110
2722
  }
3111
- return {
3112
- projectId: null,
3113
- diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
3114
- \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
3115
- };
2723
+ return { projectId, diagnostic: null };
3116
2724
  } catch (err) {
3117
2725
  const detail = err instanceof Error ? err.message : String(err);
3118
2726
  return {
@@ -3331,7 +2939,7 @@ async function appendContent(cfg, messageId, content) {
3331
2939
  const api = createApmApiClient(cfg);
3332
2940
  await api.cli.webideAppendMessageContent({ id: messageId, content });
3333
2941
  }
3334
- async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
2942
+ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3335
2943
  const workdir = requireRemoteWorkdir(msg.workdir);
3336
2944
  const messageId = msg.messageId;
3337
2945
  const taskId = msg.taskId;
@@ -3429,7 +3037,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3429
3037
  async () => runTaskBranch(taskId, { cwd: workdir }),
3430
3038
  (branchResult) => {
3431
3039
  markBranchDone(branchCacheKey, workdir);
3432
- 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}`;
3433
3041
  }
3434
3042
  );
3435
3043
  } else {
@@ -3546,7 +3154,6 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3546
3154
  enableSandbox: false,
3547
3155
  onInvalidatePersistedAgentId: () => clearWebIdeAgentId(workdir, taskId),
3548
3156
  taskId,
3549
- terminalRpc: options?.terminalRpc,
3550
3157
  createRemoteLogSync: (agentId) => {
3551
3158
  saveWebIdeAgentId(workdir, taskId, agentId);
3552
3159
  logSyncRef.current = createThrottledWebIdeMessageLogSync(
@@ -3738,68 +3345,166 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3738
3345
  }
3739
3346
  }
3740
3347
 
3741
- // src/commands/connect/webide-terminal-rpc.ts
3742
- import { parentPort } from "node:worker_threads";
3743
- var nextId = 1;
3744
- var pending = /* @__PURE__ */ new Map();
3745
- function installTerminalRpcListener() {
3746
- parentPort?.on("message", (raw) => {
3747
- if (!raw || raw.type !== "terminal-rpc-result") return;
3748
- const p = pending.get(raw.id);
3749
- if (!p) return;
3750
- pending.delete(raw.id);
3751
- if (raw.ok) p.resolve(raw.result);
3752
- else p.reject(new Error(raw.error || "terminal rpc failed"));
3753
- });
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 });
3352
+ }
3353
+ async function setError2(cfg, messageId, error) {
3354
+ const api = createApmApiClient(cfg);
3355
+ await api.cli.webideSetMessageError({ id: messageId, error });
3754
3356
  }
3755
- function call(op, args) {
3756
- const id = nextId++;
3757
- return new Promise((resolve6, reject) => {
3758
- if (!parentPort) {
3759
- reject(new Error("parentPort \u4E0D\u53EF\u7528"));
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);
3447
+ return;
3448
+ }
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");
3760
3457
  return;
3761
3458
  }
3762
- pending.set(id, { resolve: resolve6, reject });
3763
- parentPort.postMessage({
3764
- type: "terminal-rpc",
3765
- id,
3766
- op,
3767
- args
3459
+ const fallback = resolveMessageReplyFallback({
3460
+ assistantText: outcome.assistantText,
3461
+ result: outcome.result
3768
3462
  });
3769
- setTimeout(() => {
3770
- if (pending.has(id)) {
3771
- pending.delete(id);
3772
- reject(new Error(`terminal rpc timeout op=${op}`));
3773
- }
3774
- }, 12e4);
3775
- });
3776
- }
3777
- function createWorkerTerminalRpc() {
3778
- return {
3779
- start: (args) => call("start", args),
3780
- stop: (args) => call("stop", args),
3781
- stopAll: (args) => call("stopAll", args),
3782
- list: (args) => call("list", args)
3783
- };
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
+ }
3784
3489
  }
3785
3490
 
3786
3491
  // src/commands/connect/webide-message-worker.ts
3787
3492
  var controllers = /* @__PURE__ */ new Map();
3788
- installTerminalRpcListener();
3789
- var terminalRpc = createWorkerTerminalRpc();
3790
3493
  async function runJob(cfg, msg) {
3791
3494
  const controller = new AbortController();
3792
3495
  controllers.set(msg.messageId, controller);
3793
3496
  try {
3794
- await handleWebIdeInboundMessage(cfg, msg, controller.signal, {
3795
- terminalRpc
3796
- });
3797
- 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({
3798
3503
  type: "done",
3799
3504
  messageId: msg.messageId
3800
3505
  });
3801
3506
  } catch (err) {
3802
- parentPort2?.postMessage({
3507
+ parentPort?.postMessage({
3803
3508
  type: "error",
3804
3509
  messageId: msg.messageId,
3805
3510
  error: err instanceof Error ? err.message : String(err)
@@ -3808,7 +3513,7 @@ async function runJob(cfg, msg) {
3808
3513
  controllers.delete(msg.messageId);
3809
3514
  }
3810
3515
  }
3811
- parentPort2?.on("message", (raw) => {
3516
+ parentPort?.on("message", (raw) => {
3812
3517
  if (raw.type === "cancel") {
3813
3518
  controllers.get(raw.messageId)?.abort();
3814
3519
  return;