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.
- package/README.md +4 -18
- package/dist/index.js +651 -6156
- package/dist/webide-message-worker.js +373 -668
- package/package.json +3 -10
- package/template/AGENTS.webide.md +2 -2
- package/template/rules/webide_terminal.md +22 -18
- package/template/skills/webide-run-retro/SKILL.md +103 -0
- package/template/skills/webide-run-retro/attribution.md +26 -0
- package/template/skills/webide-run-retro/scripts/inspect-run.mjs +335 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/commands/connect/webide-message-worker.ts
|
|
2
|
-
import { parentPort
|
|
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/
|
|
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/
|
|
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
|
|
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
|
|
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
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
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 {
|
|
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
|
|
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/
|
|
1369
|
-
|
|
1370
|
-
|
|
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
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
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
|
-
|
|
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 {
|
|
1344
|
+
const { webideMergePullRequests } = options;
|
|
1527
1345
|
return {
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
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
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
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 {
|
|
1376
|
+
const { webideRecommendPlan } = options;
|
|
1559
1377
|
return {
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
-
|
|
1570
|
-
type: "string",
|
|
1571
|
-
description: "Short Chinese reason for the recommendation"
|
|
1572
|
-
}
|
|
1393
|
+
required: ["recommendation"]
|
|
1573
1394
|
},
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
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 {
|
|
1409
|
+
const { webideUpsertPlan } = options;
|
|
1595
1410
|
return {
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
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
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
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 {
|
|
1463
|
+
const { webideReplaceTestCases } = options;
|
|
1651
1464
|
return {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
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
|
-
|
|
1676
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
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
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
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/
|
|
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,
|
|
1526
|
+
function createCursorCustomTools(cfg, options) {
|
|
1874
1527
|
const tools = {
|
|
1875
|
-
...
|
|
1876
|
-
|
|
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?.
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
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
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
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,
|
|
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
|
|
1977
|
+
function asString3(value) {
|
|
2313
1978
|
return typeof value === "string" ? value.trim() : "";
|
|
2314
1979
|
}
|
|
2315
1980
|
function parseQuestions(args) {
|
|
2316
|
-
const title =
|
|
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 =
|
|
2326
|
-
const 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 =
|
|
2334
|
-
const 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
|
|
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 (
|
|
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/
|
|
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\
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
3099
|
-
const
|
|
3100
|
-
|
|
3101
|
-
|
|
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
|
|
3104
|
-
|
|
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:
|
|
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
|
|
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/
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
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
|
|
3756
|
-
const
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
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
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
id,
|
|
3766
|
-
op,
|
|
3767
|
-
args
|
|
3459
|
+
const fallback = resolveMessageReplyFallback({
|
|
3460
|
+
assistantText: outcome.assistantText,
|
|
3461
|
+
result: outcome.result
|
|
3768
3462
|
});
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
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
|
-
|
|
3795
|
-
|
|
3796
|
-
}
|
|
3797
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3516
|
+
parentPort?.on("message", (raw) => {
|
|
3812
3517
|
if (raw.type === "cancel") {
|
|
3813
3518
|
controllers.get(raw.messageId)?.abort();
|
|
3814
3519
|
return;
|