ai-project-manage-cli 7.1.11 → 7.1.13
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/dist/index.js +1472 -552
- package/dist/webide-message-worker.js +356 -40
- package/package.json +1 -1
- package/template/AGENTS.md +2 -1
- package/template/rules/webide_git_commit.md +4 -1
- package/template/rules/webide_merge.md +16 -0
- package/template/rules/webide_terminal.md +35 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/commands/connect/webide-message-worker.ts
|
|
2
|
-
import { parentPort, workerData } from "node:worker_threads";
|
|
2
|
+
import { parentPort as parentPort2, workerData } from "node:worker_threads";
|
|
3
3
|
|
|
4
4
|
// src/api/client.ts
|
|
5
5
|
import { createApiClient } from "listpage-http";
|
|
@@ -100,6 +100,22 @@ var requestConfig = {
|
|
|
100
100
|
method: "PUT",
|
|
101
101
|
path: "/cli/webide/test-cases"
|
|
102
102
|
}),
|
|
103
|
+
webideListTerminals: defineEndpoint({
|
|
104
|
+
method: "GET",
|
|
105
|
+
path: "/cli/webide/terminals"
|
|
106
|
+
}),
|
|
107
|
+
webideUpsertTerminal: defineEndpoint({
|
|
108
|
+
method: "POST",
|
|
109
|
+
path: "/cli/webide/terminals"
|
|
110
|
+
}),
|
|
111
|
+
webideUpdateTerminalStatus: defineEndpoint({
|
|
112
|
+
method: "PUT",
|
|
113
|
+
path: "/cli/webide/terminals/status"
|
|
114
|
+
}),
|
|
115
|
+
webideUpdateTerminalMeta: defineEndpoint({
|
|
116
|
+
method: "PUT",
|
|
117
|
+
path: "/cli/webide/terminals/meta"
|
|
118
|
+
}),
|
|
103
119
|
branchBaseline: defineEndpoint({
|
|
104
120
|
method: "GET",
|
|
105
121
|
path: "/cli/tasks/branch-baseline"
|
|
@@ -136,6 +152,10 @@ var requestConfig = {
|
|
|
136
152
|
method: "POST",
|
|
137
153
|
path: "/cli/webide/draft-pull-requests"
|
|
138
154
|
}),
|
|
155
|
+
webideMergePullRequests: defineEndpoint({
|
|
156
|
+
method: "POST",
|
|
157
|
+
path: "/cli/webide/pull-requests/merge"
|
|
158
|
+
}),
|
|
139
159
|
getRepositoryProjectDocumentManifest: defineEndpoint({
|
|
140
160
|
method: "GET",
|
|
141
161
|
path: "/cli/repository-project-documents/manifest"
|
|
@@ -381,6 +401,22 @@ async function hasUpstream(cwd) {
|
|
|
381
401
|
return false;
|
|
382
402
|
}
|
|
383
403
|
}
|
|
404
|
+
async function pushCurrentBranch(cwd) {
|
|
405
|
+
if (!await isGitRepo(cwd)) {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
const originUrl = await tryReadGitOriginUrl(cwd);
|
|
409
|
+
if (!originUrl) {
|
|
410
|
+
console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8DF3\u8FC7 push");
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
if (await hasUpstream(cwd)) {
|
|
414
|
+
await execGit(cwd, ["push"]);
|
|
415
|
+
} else {
|
|
416
|
+
await execGit(cwd, ["push", "-u", "origin", "HEAD"]);
|
|
417
|
+
}
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
384
420
|
var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
|
|
385
421
|
async function commitAndPushGitignore(workdir) {
|
|
386
422
|
if (!await isGitRepo(workdir)) {
|
|
@@ -390,17 +426,12 @@ async function commitAndPushGitignore(workdir) {
|
|
|
390
426
|
await execGit(workdir, ["add", "--", ".gitignore"]);
|
|
391
427
|
await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
|
|
392
428
|
console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
|
|
393
|
-
const
|
|
394
|
-
if (
|
|
395
|
-
console.log("[apm] \
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
if (await hasUpstream(workdir)) {
|
|
399
|
-
await execGit(workdir, ["push"]);
|
|
429
|
+
const pushed = await pushCurrentBranch(workdir);
|
|
430
|
+
if (pushed) {
|
|
431
|
+
console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
|
|
400
432
|
} else {
|
|
401
|
-
|
|
433
|
+
console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
|
|
402
434
|
}
|
|
403
|
-
console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
|
|
404
435
|
}
|
|
405
436
|
|
|
406
437
|
// src/baseline-resolve.ts
|
|
@@ -854,6 +885,21 @@ async function commitWorkspaceReposIfDirty(workdir, message) {
|
|
|
854
885
|
}
|
|
855
886
|
return committed;
|
|
856
887
|
}
|
|
888
|
+
async function pushWorkspaceRepos(workdir) {
|
|
889
|
+
const workdirPath = resolveWorkdirPath(workdir);
|
|
890
|
+
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
891
|
+
const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
892
|
+
let pushed = 0;
|
|
893
|
+
for (const gitRoot of repoRoots) {
|
|
894
|
+
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
895
|
+
const did = await pushCurrentBranch(gitRoot);
|
|
896
|
+
if (did) {
|
|
897
|
+
pushed += 1;
|
|
898
|
+
console.log(`[apm] WebIDE \u5DF2 push @ ${label}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return pushed;
|
|
902
|
+
}
|
|
857
903
|
function branchNameForTask(taskId) {
|
|
858
904
|
const id = taskId.trim();
|
|
859
905
|
if (!id) {
|
|
@@ -1525,6 +1571,35 @@ ${payload}`;
|
|
|
1525
1571
|
};
|
|
1526
1572
|
}
|
|
1527
1573
|
|
|
1574
|
+
// src/commands/connect/webide-merge-tools.ts
|
|
1575
|
+
function createMergeWebIdePullRequestsTool(options) {
|
|
1576
|
+
const { cfg, taskId } = options;
|
|
1577
|
+
return {
|
|
1578
|
+
description: "Merge all open pull requests for this WebIDE task. Call once with confirmedConflictFree=true. No baseline sync or review is required before calling.",
|
|
1579
|
+
inputSchema: {
|
|
1580
|
+
type: "object",
|
|
1581
|
+
properties: {
|
|
1582
|
+
confirmedConflictFree: {
|
|
1583
|
+
type: "boolean",
|
|
1584
|
+
description: "Must be true to confirm you intend to merge this task's open PRs now."
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
required: ["confirmedConflictFree"]
|
|
1588
|
+
},
|
|
1589
|
+
execute: async (args) => {
|
|
1590
|
+
if (args.confirmedConflictFree !== true) {
|
|
1591
|
+
throw new Error(
|
|
1592
|
+
"MergeWebIdePullRequests \u8981\u6C42 confirmedConflictFree=true\u3002"
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
const api = createApmApiClient(cfg);
|
|
1596
|
+
console.log(`[apm] MergeWebIdePullRequests taskId=${taskId}`);
|
|
1597
|
+
const result = await api.cli.webideMergePullRequests({ taskId });
|
|
1598
|
+
return JSON.stringify(result, null, 2);
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1528
1603
|
// src/commands/connect/webide-plan-tools.ts
|
|
1529
1604
|
function asString(value) {
|
|
1530
1605
|
return typeof value === "string" ? value.trim() : "";
|
|
@@ -1679,6 +1754,164 @@ function createUpsertWebIdeTestCasesTool(options) {
|
|
|
1679
1754
|
};
|
|
1680
1755
|
}
|
|
1681
1756
|
|
|
1757
|
+
// src/commands/connect/webide-terminal-tools.ts
|
|
1758
|
+
function asString3(value) {
|
|
1759
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1760
|
+
}
|
|
1761
|
+
function asPorts(value) {
|
|
1762
|
+
if (!Array.isArray(value)) return void 0;
|
|
1763
|
+
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));
|
|
1764
|
+
return ports.length ? ports : void 0;
|
|
1765
|
+
}
|
|
1766
|
+
function createWebIdeTerminalTools(options) {
|
|
1767
|
+
const { cfg, taskId, workdir, rpc } = options;
|
|
1768
|
+
const StartWebIdeTerminal = {
|
|
1769
|
+
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.",
|
|
1770
|
+
inputSchema: {
|
|
1771
|
+
type: "object",
|
|
1772
|
+
properties: {
|
|
1773
|
+
key: {
|
|
1774
|
+
type: "string",
|
|
1775
|
+
description: "Stable key, e.g. frontend / backend"
|
|
1776
|
+
},
|
|
1777
|
+
name: { type: "string", description: "Display name" },
|
|
1778
|
+
cwd: {
|
|
1779
|
+
type: "string",
|
|
1780
|
+
description: "Working directory (absolute or under workspace)"
|
|
1781
|
+
},
|
|
1782
|
+
command: {
|
|
1783
|
+
type: "string",
|
|
1784
|
+
description: "Shell command to run, e.g. pnpm dev"
|
|
1785
|
+
},
|
|
1786
|
+
readyPattern: {
|
|
1787
|
+
type: "string",
|
|
1788
|
+
description: "Optional regex; when matched in logs, status becomes READY"
|
|
1789
|
+
},
|
|
1790
|
+
portsHint: {
|
|
1791
|
+
type: "array",
|
|
1792
|
+
items: { type: "number" },
|
|
1793
|
+
description: "Optional expected listen ports"
|
|
1794
|
+
}
|
|
1795
|
+
},
|
|
1796
|
+
required: ["key", "cwd", "command"]
|
|
1797
|
+
},
|
|
1798
|
+
execute: async (args) => {
|
|
1799
|
+
const key = asString3(args.key);
|
|
1800
|
+
const cwd = asString3(args.cwd) || workdir;
|
|
1801
|
+
const command = asString3(args.command);
|
|
1802
|
+
if (!key || !command) {
|
|
1803
|
+
return {
|
|
1804
|
+
content: [{ type: "text", text: "key / command \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
1805
|
+
isError: true
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
try {
|
|
1809
|
+
const result = rpc ? await rpc.start({
|
|
1810
|
+
taskId,
|
|
1811
|
+
key,
|
|
1812
|
+
name: asString3(args.name) || void 0,
|
|
1813
|
+
cwd,
|
|
1814
|
+
command,
|
|
1815
|
+
readyPattern: asString3(args.readyPattern) || void 0,
|
|
1816
|
+
portsHint: asPorts(args.portsHint)
|
|
1817
|
+
}) : await (async () => {
|
|
1818
|
+
throw new Error("Terminal RPC \u4E0D\u53EF\u7528\uFF08\u9700\u5728 connect \u4E3B\u8FDB\u7A0B\uFF09");
|
|
1819
|
+
})();
|
|
1820
|
+
return JSON.stringify(result, null, 2);
|
|
1821
|
+
} catch (err) {
|
|
1822
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1823
|
+
return {
|
|
1824
|
+
content: [{ type: "text", text: `\u542F\u52A8\u7EC8\u7AEF\u5931\u8D25: ${detail}` }],
|
|
1825
|
+
isError: true
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1830
|
+
const StopWebIdeTerminal = {
|
|
1831
|
+
description: "Stop a WebIDE terminal and kill its process tree. Soft-terminates (status EXITED, record kept).",
|
|
1832
|
+
inputSchema: {
|
|
1833
|
+
type: "object",
|
|
1834
|
+
properties: {
|
|
1835
|
+
key: { type: "string", description: "Terminal key" },
|
|
1836
|
+
terminalId: { type: "string", description: "Server terminal id" }
|
|
1837
|
+
}
|
|
1838
|
+
},
|
|
1839
|
+
execute: async (args) => {
|
|
1840
|
+
const key = asString3(args.key) || void 0;
|
|
1841
|
+
const terminalId = asString3(args.terminalId) || void 0;
|
|
1842
|
+
if (!key && !terminalId) {
|
|
1843
|
+
return {
|
|
1844
|
+
content: [{ type: "text", text: "\u9700\u8981 key \u6216 terminalId" }],
|
|
1845
|
+
isError: true
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
try {
|
|
1849
|
+
const result = rpc ? await rpc.stop({ taskId, key, terminalId }) : await (async () => {
|
|
1850
|
+
throw new Error("Terminal RPC \u4E0D\u53EF\u7528\uFF08\u9700\u5728 connect \u4E3B\u8FDB\u7A0B\uFF09");
|
|
1851
|
+
})();
|
|
1852
|
+
return JSON.stringify(result, null, 2);
|
|
1853
|
+
} catch (err) {
|
|
1854
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1855
|
+
return {
|
|
1856
|
+
content: [{ type: "text", text: `\u505C\u6B62\u7EC8\u7AEF\u5931\u8D25: ${detail}` }],
|
|
1857
|
+
isError: true
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
const StopAllWebIdeTerminals = {
|
|
1863
|
+
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.",
|
|
1864
|
+
inputSchema: {
|
|
1865
|
+
type: "object",
|
|
1866
|
+
properties: {}
|
|
1867
|
+
},
|
|
1868
|
+
execute: async () => {
|
|
1869
|
+
try {
|
|
1870
|
+
const result = rpc ? await rpc.stopAll({ taskId }) : await (async () => {
|
|
1871
|
+
throw new Error("Terminal RPC \u4E0D\u53EF\u7528\uFF08\u9700\u5728 connect \u4E3B\u8FDB\u7A0B\uFF09");
|
|
1872
|
+
})();
|
|
1873
|
+
return JSON.stringify(result, null, 2);
|
|
1874
|
+
} catch (err) {
|
|
1875
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1876
|
+
return {
|
|
1877
|
+
content: [{ type: "text", text: `\u5168\u90E8\u505C\u6B62\u5931\u8D25: ${detail}` }],
|
|
1878
|
+
isError: true
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1883
|
+
const ListWebIdeTerminals = {
|
|
1884
|
+
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.",
|
|
1885
|
+
inputSchema: {
|
|
1886
|
+
type: "object",
|
|
1887
|
+
properties: {}
|
|
1888
|
+
},
|
|
1889
|
+
execute: async () => {
|
|
1890
|
+
try {
|
|
1891
|
+
if (rpc) {
|
|
1892
|
+
const result2 = await rpc.list({ taskId });
|
|
1893
|
+
return JSON.stringify(result2, null, 2);
|
|
1894
|
+
}
|
|
1895
|
+
const api = createApmApiClient(cfg);
|
|
1896
|
+
const result = await api.cli.webideListTerminals({ taskId });
|
|
1897
|
+
return JSON.stringify(result, null, 2);
|
|
1898
|
+
} catch (err) {
|
|
1899
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1900
|
+
return {
|
|
1901
|
+
content: [{ type: "text", text: `\u5217\u51FA\u7EC8\u7AEF\u5931\u8D25: ${detail}` }],
|
|
1902
|
+
isError: true
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
return {
|
|
1908
|
+
StartWebIdeTerminal,
|
|
1909
|
+
StopWebIdeTerminal,
|
|
1910
|
+
StopAllWebIdeTerminals,
|
|
1911
|
+
ListWebIdeTerminals
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1682
1915
|
// src/commands/connect/cursor-custom-tools.ts
|
|
1683
1916
|
var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
|
|
1684
1917
|
AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
|
|
@@ -1712,6 +1945,21 @@ function createCursorCustomTools(cfg, messageId, options) {
|
|
|
1712
1945
|
cfg,
|
|
1713
1946
|
taskId: options.taskId
|
|
1714
1947
|
});
|
|
1948
|
+
tools.MergeWebIdePullRequests = createMergeWebIdePullRequestsTool({
|
|
1949
|
+
cfg,
|
|
1950
|
+
taskId: options.taskId
|
|
1951
|
+
});
|
|
1952
|
+
if (options.workdir) {
|
|
1953
|
+
Object.assign(
|
|
1954
|
+
tools,
|
|
1955
|
+
createWebIdeTerminalTools({
|
|
1956
|
+
cfg,
|
|
1957
|
+
taskId: options.taskId,
|
|
1958
|
+
workdir: options.workdir,
|
|
1959
|
+
rpc: options.terminalRpc
|
|
1960
|
+
})
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1715
1963
|
}
|
|
1716
1964
|
return tools;
|
|
1717
1965
|
}
|
|
@@ -1835,7 +2083,9 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1835
2083
|
appendMessageContent: options?.appendMessageContent,
|
|
1836
2084
|
askQuestionExecute: options?.askQuestionExecute,
|
|
1837
2085
|
enableWebIdePlanTools: options?.enableWebIdePlanTools,
|
|
1838
|
-
taskId: options?.taskId
|
|
2086
|
+
taskId: options?.taskId,
|
|
2087
|
+
workdir,
|
|
2088
|
+
terminalRpc: options?.terminalRpc
|
|
1839
2089
|
});
|
|
1840
2090
|
const enableSandbox = Boolean(options?.enableSandbox);
|
|
1841
2091
|
let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
|
|
@@ -2018,11 +2268,11 @@ function clearWebIdeAgentId(workdir, taskId) {
|
|
|
2018
2268
|
// src/commands/connect/webide-ask-question.ts
|
|
2019
2269
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2020
2270
|
var POLL_INTERVAL_MS = 2e3;
|
|
2021
|
-
function
|
|
2271
|
+
function asString4(value) {
|
|
2022
2272
|
return typeof value === "string" ? value.trim() : "";
|
|
2023
2273
|
}
|
|
2024
2274
|
function parseQuestions(args) {
|
|
2025
|
-
const title =
|
|
2275
|
+
const title = asString4(args.title) || void 0;
|
|
2026
2276
|
const raw = args.questions;
|
|
2027
2277
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
2028
2278
|
throw new Error("AskQuestion \u7F3A\u5C11 questions");
|
|
@@ -2031,16 +2281,16 @@ function parseQuestions(args) {
|
|
|
2031
2281
|
for (const item of raw) {
|
|
2032
2282
|
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
2033
2283
|
const row = item;
|
|
2034
|
-
const id =
|
|
2035
|
-
const prompt =
|
|
2284
|
+
const id = asString4(row.id);
|
|
2285
|
+
const prompt = asString4(row.prompt);
|
|
2036
2286
|
const optionsRaw = row.options;
|
|
2037
2287
|
if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
|
|
2038
2288
|
const options = [];
|
|
2039
2289
|
for (const opt of optionsRaw) {
|
|
2040
2290
|
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
|
|
2041
2291
|
const o = opt;
|
|
2042
|
-
const oid =
|
|
2043
|
-
const label =
|
|
2292
|
+
const oid = asString4(o.id);
|
|
2293
|
+
const label = asString4(o.label);
|
|
2044
2294
|
if (oid && label) options.push({ id: oid, label });
|
|
2045
2295
|
}
|
|
2046
2296
|
if (options.length < 2) {
|
|
@@ -2074,11 +2324,11 @@ function createWebIdeAskQuestionExecute(options) {
|
|
|
2074
2324
|
const byId = new Map(
|
|
2075
2325
|
list.assumptions.map((a) => [a.questionId, a])
|
|
2076
2326
|
);
|
|
2077
|
-
const
|
|
2327
|
+
const pending2 = parsed.questions.filter((q) => {
|
|
2078
2328
|
const row = byId.get(q.id);
|
|
2079
2329
|
return !row || row.status !== "RESOLVED";
|
|
2080
2330
|
});
|
|
2081
|
-
if (
|
|
2331
|
+
if (pending2.length === 0) {
|
|
2082
2332
|
const answers = parsed.questions.map((q) => {
|
|
2083
2333
|
const row = byId.get(q.id);
|
|
2084
2334
|
return {
|
|
@@ -2559,12 +2809,18 @@ function markBranchDone(sessionId, workdir) {
|
|
|
2559
2809
|
lastBranchKey = sessionWorkdirKey(sessionId, workdir);
|
|
2560
2810
|
}
|
|
2561
2811
|
|
|
2562
|
-
// src/commands/connect/webide-
|
|
2563
|
-
var
|
|
2564
|
-
|
|
2565
|
-
|
|
2812
|
+
// src/commands/connect/webide-pull-requests.ts
|
|
2813
|
+
var TEST_START_ACTIONS = /* @__PURE__ */ new Set([
|
|
2814
|
+
"generate-cases",
|
|
2815
|
+
"run-auto-test",
|
|
2816
|
+
// 跳过/直接进手工测试不发 Cursor,PR 延后到部署或验收
|
|
2817
|
+
"deploy",
|
|
2818
|
+
"accept"
|
|
2819
|
+
]);
|
|
2820
|
+
function shouldEnsureWebIdePullRequests(action) {
|
|
2821
|
+
return TEST_START_ACTIONS.has(action);
|
|
2566
2822
|
}
|
|
2567
|
-
async function
|
|
2823
|
+
async function ensureWebIdePullRequests(cfg, taskId, workdir) {
|
|
2568
2824
|
const api = createApmApiClient(cfg);
|
|
2569
2825
|
let repoRoots;
|
|
2570
2826
|
try {
|
|
@@ -2572,29 +2828,27 @@ async function ensureWebIdeDraftPullRequests(cfg, taskId, workdir) {
|
|
|
2572
2828
|
repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
2573
2829
|
} catch (err) {
|
|
2574
2830
|
console.warn(
|
|
2575
|
-
"[apm] WebIDE
|
|
2831
|
+
"[apm] WebIDE PR\uFF1A\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25:",
|
|
2576
2832
|
err instanceof Error ? err.message : err
|
|
2577
2833
|
);
|
|
2578
2834
|
return;
|
|
2579
2835
|
}
|
|
2580
2836
|
console.log(
|
|
2581
|
-
`[apm] WebIDE
|
|
2837
|
+
`[apm] WebIDE PR\uFF1A\u51C6\u5907\u4E3A ${repoRoots.length} \u4E2A\u4ED3\u5E93\u521B\u5EFA feat/task-${taskId}`
|
|
2582
2838
|
);
|
|
2583
2839
|
for (const gitRoot of repoRoots) {
|
|
2584
2840
|
const label = formatRepoLabel(workdir, gitRoot);
|
|
2585
2841
|
try {
|
|
2586
2842
|
const originUrl = await tryReadGitOriginUrl(gitRoot);
|
|
2587
2843
|
if (!originUrl) {
|
|
2588
|
-
console.warn(
|
|
2589
|
-
`[apm] WebIDE \u8349\u7A3F PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u65E0 remote.origin.url\uFF09`
|
|
2590
|
-
);
|
|
2844
|
+
console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u65E0 remote.origin.url\uFF09`);
|
|
2591
2845
|
continue;
|
|
2592
2846
|
}
|
|
2593
2847
|
const matched = await api.cli.matchRepository({ url: originUrl });
|
|
2594
2848
|
const repositoryId = matched.repositoryId?.trim();
|
|
2595
2849
|
if (!repositoryId) {
|
|
2596
2850
|
console.warn(
|
|
2597
|
-
`[apm] WebIDE
|
|
2851
|
+
`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u5E73\u53F0\u672A\u767B\u8BB0\u4ED3\u5E93 ${originUrl}\uFF09`
|
|
2598
2852
|
);
|
|
2599
2853
|
continue;
|
|
2600
2854
|
}
|
|
@@ -2603,11 +2857,11 @@ async function ensureWebIdeDraftPullRequests(cfg, taskId, workdir) {
|
|
|
2603
2857
|
repositoryId
|
|
2604
2858
|
});
|
|
2605
2859
|
console.log(
|
|
2606
|
-
`[apm] WebIDE
|
|
2860
|
+
`[apm] WebIDE PR\uFF1A${label} \u2192 #${pr.number} ${pr.state} ${pr.url || ""}`
|
|
2607
2861
|
);
|
|
2608
2862
|
} catch (err) {
|
|
2609
2863
|
console.warn(
|
|
2610
|
-
`[apm] WebIDE
|
|
2864
|
+
`[apm] WebIDE PR\uFF1A${label} \u5931\u8D25:`,
|
|
2611
2865
|
err instanceof Error ? err.message : err
|
|
2612
2866
|
);
|
|
2613
2867
|
}
|
|
@@ -2824,7 +3078,9 @@ function markSkillsSyncedForCliVersion(workdir, cliVersion) {
|
|
|
2824
3078
|
var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
|
|
2825
3079
|
"skip-plan",
|
|
2826
3080
|
"start-develop",
|
|
2827
|
-
"fix-and-retest"
|
|
3081
|
+
"fix-and-retest",
|
|
3082
|
+
"report-defect",
|
|
3083
|
+
"deploy"
|
|
2828
3084
|
]);
|
|
2829
3085
|
function shouldCommitAfterWebIdeMessage(action) {
|
|
2830
3086
|
return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
|
|
@@ -2841,7 +3097,7 @@ async function appendContent(cfg, messageId, content) {
|
|
|
2841
3097
|
const api = createApmApiClient(cfg);
|
|
2842
3098
|
await api.cli.webideAppendMessageContent({ id: messageId, content });
|
|
2843
3099
|
}
|
|
2844
|
-
async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
3100
|
+
async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
|
|
2845
3101
|
const workdir = requireRemoteWorkdir(msg.workdir);
|
|
2846
3102
|
const messageId = msg.messageId;
|
|
2847
3103
|
const taskId = msg.taskId;
|
|
@@ -2878,9 +3134,14 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2878
3134
|
`[apm] step=branch skipped taskId=${taskId} workdir=${workdir}`
|
|
2879
3135
|
);
|
|
2880
3136
|
}
|
|
2881
|
-
if (
|
|
3137
|
+
if (shouldEnsureWebIdePullRequests(msg.action)) {
|
|
2882
3138
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
2883
|
-
await
|
|
3139
|
+
const pushed = await pushWorkspaceRepos(workdir);
|
|
3140
|
+
console.log(
|
|
3141
|
+
`[apm] webide \u6D4B\u8BD5\u524D push action=${msg.action} repos=${pushed}`
|
|
3142
|
+
);
|
|
3143
|
+
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3144
|
+
await ensureWebIdePullRequests(cfg, taskId, workdir);
|
|
2884
3145
|
}
|
|
2885
3146
|
const savedAgentId = loadWebIdeAgentId(workdir, taskId);
|
|
2886
3147
|
const logSyncRef = { current: null };
|
|
@@ -2908,6 +3169,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2908
3169
|
// 本地 SDK sandbox 会拦截 custom-user-tools MCP,WebIDE 先关闭
|
|
2909
3170
|
enableSandbox: false,
|
|
2910
3171
|
taskId,
|
|
3172
|
+
terminalRpc: options?.terminalRpc,
|
|
2911
3173
|
createRemoteLogSync: (agentId) => {
|
|
2912
3174
|
saveWebIdeAgentId(workdir, taskId, agentId);
|
|
2913
3175
|
logSyncRef.current = createThrottledWebIdeMessageLogSync(
|
|
@@ -2974,6 +3236,11 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2974
3236
|
console.log(
|
|
2975
3237
|
`[apm] webide \u515C\u5E95 commit action=${msg.action} repos=${committed}`
|
|
2976
3238
|
);
|
|
3239
|
+
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3240
|
+
const pushed = await pushWorkspaceRepos(workdir);
|
|
3241
|
+
console.log(
|
|
3242
|
+
`[apm] webide \u81EA\u52A8 push action=${msg.action} repos=${pushed}`
|
|
3243
|
+
);
|
|
2977
3244
|
}
|
|
2978
3245
|
await logSyncRef.current?.markRun(outcome.runId, "finished");
|
|
2979
3246
|
await updateStatus(cfg, messageId, "SUCCESS");
|
|
@@ -2997,19 +3264,68 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2997
3264
|
}
|
|
2998
3265
|
}
|
|
2999
3266
|
|
|
3267
|
+
// src/commands/connect/webide-terminal-rpc.ts
|
|
3268
|
+
import { parentPort } from "node:worker_threads";
|
|
3269
|
+
var nextId = 1;
|
|
3270
|
+
var pending = /* @__PURE__ */ new Map();
|
|
3271
|
+
function installTerminalRpcListener() {
|
|
3272
|
+
parentPort?.on("message", (raw) => {
|
|
3273
|
+
if (!raw || raw.type !== "terminal-rpc-result") return;
|
|
3274
|
+
const p = pending.get(raw.id);
|
|
3275
|
+
if (!p) return;
|
|
3276
|
+
pending.delete(raw.id);
|
|
3277
|
+
if (raw.ok) p.resolve(raw.result);
|
|
3278
|
+
else p.reject(new Error(raw.error || "terminal rpc failed"));
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
function call(op, args) {
|
|
3282
|
+
const id = nextId++;
|
|
3283
|
+
return new Promise((resolve6, reject) => {
|
|
3284
|
+
if (!parentPort) {
|
|
3285
|
+
reject(new Error("parentPort \u4E0D\u53EF\u7528"));
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
pending.set(id, { resolve: resolve6, reject });
|
|
3289
|
+
parentPort.postMessage({
|
|
3290
|
+
type: "terminal-rpc",
|
|
3291
|
+
id,
|
|
3292
|
+
op,
|
|
3293
|
+
args
|
|
3294
|
+
});
|
|
3295
|
+
setTimeout(() => {
|
|
3296
|
+
if (pending.has(id)) {
|
|
3297
|
+
pending.delete(id);
|
|
3298
|
+
reject(new Error(`terminal rpc timeout op=${op}`));
|
|
3299
|
+
}
|
|
3300
|
+
}, 12e4);
|
|
3301
|
+
});
|
|
3302
|
+
}
|
|
3303
|
+
function createWorkerTerminalRpc() {
|
|
3304
|
+
return {
|
|
3305
|
+
start: (args) => call("start", args),
|
|
3306
|
+
stop: (args) => call("stop", args),
|
|
3307
|
+
stopAll: (args) => call("stopAll", args),
|
|
3308
|
+
list: (args) => call("list", args)
|
|
3309
|
+
};
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3000
3312
|
// src/commands/connect/webide-message-worker.ts
|
|
3001
3313
|
var controllers = /* @__PURE__ */ new Map();
|
|
3314
|
+
installTerminalRpcListener();
|
|
3315
|
+
var terminalRpc = createWorkerTerminalRpc();
|
|
3002
3316
|
async function runJob(cfg, msg) {
|
|
3003
3317
|
const controller = new AbortController();
|
|
3004
3318
|
controllers.set(msg.messageId, controller);
|
|
3005
3319
|
try {
|
|
3006
|
-
await handleWebIdeInboundMessage(cfg, msg, controller.signal
|
|
3007
|
-
|
|
3320
|
+
await handleWebIdeInboundMessage(cfg, msg, controller.signal, {
|
|
3321
|
+
terminalRpc
|
|
3322
|
+
});
|
|
3323
|
+
parentPort2?.postMessage({
|
|
3008
3324
|
type: "done",
|
|
3009
3325
|
messageId: msg.messageId
|
|
3010
3326
|
});
|
|
3011
3327
|
} catch (err) {
|
|
3012
|
-
|
|
3328
|
+
parentPort2?.postMessage({
|
|
3013
3329
|
type: "error",
|
|
3014
3330
|
messageId: msg.messageId,
|
|
3015
3331
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -3018,7 +3334,7 @@ async function runJob(cfg, msg) {
|
|
|
3018
3334
|
controllers.delete(msg.messageId);
|
|
3019
3335
|
}
|
|
3020
3336
|
}
|
|
3021
|
-
|
|
3337
|
+
parentPort2?.on("message", (raw) => {
|
|
3022
3338
|
if (raw.type === "cancel") {
|
|
3023
3339
|
controllers.get(raw.messageId)?.abort();
|
|
3024
3340
|
return;
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
- 规则: `.apm/rules/*`
|
|
30
30
|
- reply.md:当需要回复消息时需要读取这个文档,记住回复规则
|
|
31
31
|
- write_doc.md:当需要写文档时需要读取这个文档,记住写文档的规则
|
|
32
|
-
- webide_git_commit.md:WebIDE 开发/修码时读取,按规范分轮 commit
|
|
32
|
+
- webide_git_commit.md:WebIDE 开发/修码时读取,按规范分轮 commit,结束前 push
|
|
33
33
|
- webide_testcase.md:WebIDE 生成测试用例时读取,按规范编写并提交用例
|
|
34
|
+
- webide_merge.md:WebIDE 验收通过后读取;直接调用 MergeWebIdePullRequests 合并代码
|
|
34
35
|
|
|
35
36
|
- 仓库项目上下文(`.apm/project/`):
|
|
36
37
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## WebIDE 合并规范
|
|
2
|
+
|
|
3
|
+
适用场景:WebIDE 验收通过(`accept`)后合并本任务 PR。
|
|
4
|
+
|
|
5
|
+
### 目标
|
|
6
|
+
|
|
7
|
+
将本任务各仓库特性分支上的变更合并进基线分支(通常为 `main`)。
|
|
8
|
+
|
|
9
|
+
### 步骤
|
|
10
|
+
|
|
11
|
+
1. 调用一次 `MergeWebIdePullRequests`(`confirmedConflictFree=true`),由平台合并本任务全部 OPEN PR。
|
|
12
|
+
2. 用 `AppendMessage` 汇总合并结果(PR 编号、仓库)。
|
|
13
|
+
|
|
14
|
+
### 禁止
|
|
15
|
+
|
|
16
|
+
- 在合并之外自行执行同步基线、代码审核、部署等额外流程
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# WebIDE 长驻终端
|
|
2
|
+
|
|
3
|
+
长驻进程(如前后端 `dev`)必须用自定义工具管理,**禁止**用 Shell 启动会长期占用的 dev server。
|
|
4
|
+
|
|
5
|
+
## 工具
|
|
6
|
+
|
|
7
|
+
- `StartWebIdeTerminal`:`{ key, name?, cwd, command, readyPattern?, portsHint? }`
|
|
8
|
+
- `key` 稳定标识,如 `frontend` / `backend`;同 key 已在跑(RUNNING/READY)则幂等返回
|
|
9
|
+
- 进程挂在 CLI 主进程,跨多轮消息存活
|
|
10
|
+
- `StopWebIdeTerminal`:`{ key }` 或 `{ terminalId }`;杀进程树,状态改为 EXITED(不删记录)
|
|
11
|
+
- `StopAllWebIdeTerminals`:停掉本 task 全部仍存活的终端(重跑本地环境前使用)
|
|
12
|
+
- `ListWebIdeTerminals`:列出**存活**终端(STARTING / RUNNING / READY / STOPPING),**不含** EXITED / FAILED
|
|
13
|
+
|
|
14
|
+
## 启动命令来源
|
|
15
|
+
|
|
16
|
+
必须 Read `.apm/project/deploy.md`,按文档中的 cwd / command / 服务划分调用 `StartWebIdeTerminal`。
|
|
17
|
+
|
|
18
|
+
- 文档不存在或未写明启动方式 → 用 AppendMessage 说明无法启动本地环境,**禁止猜测命令**。
|
|
19
|
+
|
|
20
|
+
## 时机
|
|
21
|
+
|
|
22
|
+
1. **首次**(`start-develop` / 跳过计划后的开发轮次):实现完成后需要本地环境时
|
|
23
|
+
- 先 `ListWebIdeTerminals`
|
|
24
|
+
- 若无活终端 → Read `deploy.md` → 按前后端分别 `StartWebIdeTerminal`
|
|
25
|
+
- 等待 READY / 端口出现后,告知用户可在 WebIDE「终端」面板观察日志(勿粘贴整段 stdout)
|
|
26
|
+
2. **后续改码**(热更新):有活终端则**复用,不重起**
|
|
27
|
+
3. **显式重跑**(端口冲突、命令变更、或平台「重跑本地环境」指令):
|
|
28
|
+
`StopAllWebIdeTerminals` → 再 Read `deploy.md` → 重新 Start
|
|
29
|
+
不要在已死进程上糊弄;需要换命令时也是先停再建
|
|
30
|
+
|
|
31
|
+
## 规范
|
|
32
|
+
|
|
33
|
+
1. 禁止用 Shell / 后台进程方式起 `dev` / `serve` 等长驻命令。
|
|
34
|
+
2. 观察日志请用户看 WebIDE「终端」面板。
|
|
35
|
+
3. 任务结束或不再需要时可 Stop;不要留下孤儿进程。
|