ai-project-manage-cli 7.1.12 → 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 +1434 -562
- package/dist/webide-message-worker.js +260 -101
- package/package.json +1 -1
- package/template/AGENTS.md +1 -1
- package/template/rules/webide_merge.md +5 -28
- 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"
|
|
@@ -1559,13 +1575,13 @@ ${payload}`;
|
|
|
1559
1575
|
function createMergeWebIdePullRequestsTool(options) {
|
|
1560
1576
|
const { cfg, taskId } = options;
|
|
1561
1577
|
return {
|
|
1562
|
-
description: "Merge all open pull requests for this WebIDE task
|
|
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.",
|
|
1563
1579
|
inputSchema: {
|
|
1564
1580
|
type: "object",
|
|
1565
1581
|
properties: {
|
|
1566
1582
|
confirmedConflictFree: {
|
|
1567
1583
|
type: "boolean",
|
|
1568
|
-
description: "Must be true
|
|
1584
|
+
description: "Must be true to confirm you intend to merge this task's open PRs now."
|
|
1569
1585
|
}
|
|
1570
1586
|
},
|
|
1571
1587
|
required: ["confirmedConflictFree"]
|
|
@@ -1573,7 +1589,7 @@ function createMergeWebIdePullRequestsTool(options) {
|
|
|
1573
1589
|
execute: async (args) => {
|
|
1574
1590
|
if (args.confirmedConflictFree !== true) {
|
|
1575
1591
|
throw new Error(
|
|
1576
|
-
"MergeWebIdePullRequests \u8981\u6C42 confirmedConflictFree=true\u3002
|
|
1592
|
+
"MergeWebIdePullRequests \u8981\u6C42 confirmedConflictFree=true\u3002"
|
|
1577
1593
|
);
|
|
1578
1594
|
}
|
|
1579
1595
|
const api = createApmApiClient(cfg);
|
|
@@ -1738,6 +1754,164 @@ function createUpsertWebIdeTestCasesTool(options) {
|
|
|
1738
1754
|
};
|
|
1739
1755
|
}
|
|
1740
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
|
+
|
|
1741
1915
|
// src/commands/connect/cursor-custom-tools.ts
|
|
1742
1916
|
var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
|
|
1743
1917
|
AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
|
|
@@ -1775,6 +1949,17 @@ function createCursorCustomTools(cfg, messageId, options) {
|
|
|
1775
1949
|
cfg,
|
|
1776
1950
|
taskId: options.taskId
|
|
1777
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
|
+
}
|
|
1778
1963
|
}
|
|
1779
1964
|
return tools;
|
|
1780
1965
|
}
|
|
@@ -1898,7 +2083,9 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1898
2083
|
appendMessageContent: options?.appendMessageContent,
|
|
1899
2084
|
askQuestionExecute: options?.askQuestionExecute,
|
|
1900
2085
|
enableWebIdePlanTools: options?.enableWebIdePlanTools,
|
|
1901
|
-
taskId: options?.taskId
|
|
2086
|
+
taskId: options?.taskId,
|
|
2087
|
+
workdir,
|
|
2088
|
+
terminalRpc: options?.terminalRpc
|
|
1902
2089
|
});
|
|
1903
2090
|
const enableSandbox = Boolean(options?.enableSandbox);
|
|
1904
2091
|
let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
|
|
@@ -2081,11 +2268,11 @@ function clearWebIdeAgentId(workdir, taskId) {
|
|
|
2081
2268
|
// src/commands/connect/webide-ask-question.ts
|
|
2082
2269
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2083
2270
|
var POLL_INTERVAL_MS = 2e3;
|
|
2084
|
-
function
|
|
2271
|
+
function asString4(value) {
|
|
2085
2272
|
return typeof value === "string" ? value.trim() : "";
|
|
2086
2273
|
}
|
|
2087
2274
|
function parseQuestions(args) {
|
|
2088
|
-
const title =
|
|
2275
|
+
const title = asString4(args.title) || void 0;
|
|
2089
2276
|
const raw = args.questions;
|
|
2090
2277
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
2091
2278
|
throw new Error("AskQuestion \u7F3A\u5C11 questions");
|
|
@@ -2094,16 +2281,16 @@ function parseQuestions(args) {
|
|
|
2094
2281
|
for (const item of raw) {
|
|
2095
2282
|
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
2096
2283
|
const row = item;
|
|
2097
|
-
const id =
|
|
2098
|
-
const prompt =
|
|
2284
|
+
const id = asString4(row.id);
|
|
2285
|
+
const prompt = asString4(row.prompt);
|
|
2099
2286
|
const optionsRaw = row.options;
|
|
2100
2287
|
if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
|
|
2101
2288
|
const options = [];
|
|
2102
2289
|
for (const opt of optionsRaw) {
|
|
2103
2290
|
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
|
|
2104
2291
|
const o = opt;
|
|
2105
|
-
const oid =
|
|
2106
|
-
const label =
|
|
2292
|
+
const oid = asString4(o.id);
|
|
2293
|
+
const label = asString4(o.label);
|
|
2107
2294
|
if (oid && label) options.push({ id: oid, label });
|
|
2108
2295
|
}
|
|
2109
2296
|
if (options.length < 2) {
|
|
@@ -2137,11 +2324,11 @@ function createWebIdeAskQuestionExecute(options) {
|
|
|
2137
2324
|
const byId = new Map(
|
|
2138
2325
|
list.assumptions.map((a) => [a.questionId, a])
|
|
2139
2326
|
);
|
|
2140
|
-
const
|
|
2327
|
+
const pending2 = parsed.questions.filter((q) => {
|
|
2141
2328
|
const row = byId.get(q.id);
|
|
2142
2329
|
return !row || row.status !== "RESOLVED";
|
|
2143
2330
|
});
|
|
2144
|
-
if (
|
|
2331
|
+
if (pending2.length === 0) {
|
|
2145
2332
|
const answers = parsed.questions.map((q) => {
|
|
2146
2333
|
const row = byId.get(q.id);
|
|
2147
2334
|
return {
|
|
@@ -2626,8 +2813,9 @@ function markBranchDone(sessionId, workdir) {
|
|
|
2626
2813
|
var TEST_START_ACTIONS = /* @__PURE__ */ new Set([
|
|
2627
2814
|
"generate-cases",
|
|
2628
2815
|
"run-auto-test",
|
|
2629
|
-
|
|
2630
|
-
"
|
|
2816
|
+
// 跳过/直接进手工测试不发 Cursor,PR 延后到部署或验收
|
|
2817
|
+
"deploy",
|
|
2818
|
+
"accept"
|
|
2631
2819
|
]);
|
|
2632
2820
|
function shouldEnsureWebIdePullRequests(action) {
|
|
2633
2821
|
return TEST_START_ACTIONS.has(action);
|
|
@@ -2680,70 +2868,6 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
|
|
|
2680
2868
|
}
|
|
2681
2869
|
}
|
|
2682
2870
|
|
|
2683
|
-
// src/commands/connect/webide-baseline-sync.ts
|
|
2684
|
-
async function abortMergeOrRebase(cwd) {
|
|
2685
|
-
try {
|
|
2686
|
-
await execGit(cwd, ["merge", "--abort"], true);
|
|
2687
|
-
} catch {
|
|
2688
|
-
}
|
|
2689
|
-
try {
|
|
2690
|
-
await execGit(cwd, ["rebase", "--abort"], true);
|
|
2691
|
-
} catch {
|
|
2692
|
-
}
|
|
2693
|
-
}
|
|
2694
|
-
async function syncWebIdeBaselineOrFail(workdir) {
|
|
2695
|
-
let repoRoots;
|
|
2696
|
-
try {
|
|
2697
|
-
const manifest = loadWorkspaceReposCache(workdir);
|
|
2698
|
-
repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
2699
|
-
} catch (err) {
|
|
2700
|
-
return {
|
|
2701
|
-
ok: false,
|
|
2702
|
-
error: `\u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`
|
|
2703
|
-
};
|
|
2704
|
-
}
|
|
2705
|
-
const failures = [];
|
|
2706
|
-
for (const repoRoot of repoRoots) {
|
|
2707
|
-
const label = formatRepoLabel(workdir, repoRoot);
|
|
2708
|
-
try {
|
|
2709
|
-
const gitRoot = await resolveGitRepoRoot(repoRoot);
|
|
2710
|
-
const baselineBranch = await resolveDefaultRemoteBranch(gitRoot);
|
|
2711
|
-
await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
|
|
2712
|
-
const currentBranch = await getCurrentBranch(gitRoot);
|
|
2713
|
-
console.log(
|
|
2714
|
-
`[apm] webide accept sync ${label}: ${currentBranch} \u2190 origin/${baselineBranch}`
|
|
2715
|
-
);
|
|
2716
|
-
try {
|
|
2717
|
-
await execGit(
|
|
2718
|
-
gitRoot,
|
|
2719
|
-
["merge", `origin/${baselineBranch}`, "--no-edit"],
|
|
2720
|
-
true
|
|
2721
|
-
);
|
|
2722
|
-
} catch (mergeErr) {
|
|
2723
|
-
await abortMergeOrRebase(gitRoot);
|
|
2724
|
-
const detail = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
|
|
2725
|
-
failures.push(
|
|
2726
|
-
`${label}\uFF1A\u5408\u5E76 origin/${baselineBranch} \u51B2\u7A81\uFF0C\u5DF2\u6267\u884C merge --abort \u64A4\u56DE\u672C\u6B21\u540C\u6B65\u3002${detail}`
|
|
2727
|
-
);
|
|
2728
|
-
}
|
|
2729
|
-
} catch (err) {
|
|
2730
|
-
failures.push(
|
|
2731
|
-
`${label}\uFF1A${err instanceof Error ? err.message : String(err)}`
|
|
2732
|
-
);
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
if (failures.length > 0) {
|
|
2736
|
-
return {
|
|
2737
|
-
ok: false,
|
|
2738
|
-
error: [
|
|
2739
|
-
"\u62C9\u53D6\u6700\u65B0\u57FA\u7EBF\u5931\u8D25\uFF0C\u5DF2\u64A4\u56DE\u672C\u6B21\u540C\u6B65\u64CD\u4F5C\uFF0C\u8BF7\u4EBA\u5DE5\u5904\u7406\u51B2\u7A81\u540E\u91CD\u8BD5\u9A8C\u6536\u5408\u5E76\u3002",
|
|
2740
|
-
...failures.map((f) => `- ${f}`)
|
|
2741
|
-
].join("\n")
|
|
2742
|
-
};
|
|
2743
|
-
}
|
|
2744
|
-
return { ok: true };
|
|
2745
|
-
}
|
|
2746
|
-
|
|
2747
2871
|
// src/version.ts
|
|
2748
2872
|
import { readFileSync as readFileSync8 } from "fs";
|
|
2749
2873
|
import { dirname as dirname5, join as join8 } from "path";
|
|
@@ -2955,7 +3079,8 @@ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
2955
3079
|
"skip-plan",
|
|
2956
3080
|
"start-develop",
|
|
2957
3081
|
"fix-and-retest",
|
|
2958
|
-
"report-defect"
|
|
3082
|
+
"report-defect",
|
|
3083
|
+
"deploy"
|
|
2959
3084
|
]);
|
|
2960
3085
|
function shouldCommitAfterWebIdeMessage(action) {
|
|
2961
3086
|
return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
|
|
@@ -2972,7 +3097,7 @@ async function appendContent(cfg, messageId, content) {
|
|
|
2972
3097
|
const api = createApmApiClient(cfg);
|
|
2973
3098
|
await api.cli.webideAppendMessageContent({ id: messageId, content });
|
|
2974
3099
|
}
|
|
2975
|
-
async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
3100
|
+
async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
|
|
2976
3101
|
const workdir = requireRemoteWorkdir(msg.workdir);
|
|
2977
3102
|
const messageId = msg.messageId;
|
|
2978
3103
|
const taskId = msg.taskId;
|
|
@@ -3018,22 +3143,6 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
3018
3143
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3019
3144
|
await ensureWebIdePullRequests(cfg, taskId, workdir);
|
|
3020
3145
|
}
|
|
3021
|
-
if (msg.action === "accept") {
|
|
3022
|
-
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3023
|
-
const sync = await syncWebIdeBaselineOrFail(workdir);
|
|
3024
|
-
if (!sync.ok) {
|
|
3025
|
-
await setError(cfg, messageId, sync.error);
|
|
3026
|
-
return;
|
|
3027
|
-
}
|
|
3028
|
-
try {
|
|
3029
|
-
await pushWorkspaceRepos(workdir);
|
|
3030
|
-
} catch (err) {
|
|
3031
|
-
console.warn(
|
|
3032
|
-
"[apm] webide accept \u540C\u6B65\u540E push \u5931\u8D25:",
|
|
3033
|
-
err instanceof Error ? err.message : err
|
|
3034
|
-
);
|
|
3035
|
-
}
|
|
3036
|
-
}
|
|
3037
3146
|
const savedAgentId = loadWebIdeAgentId(workdir, taskId);
|
|
3038
3147
|
const logSyncRef = { current: null };
|
|
3039
3148
|
const outcome = await runCursorAgent(
|
|
@@ -3060,6 +3169,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
3060
3169
|
// 本地 SDK sandbox 会拦截 custom-user-tools MCP,WebIDE 先关闭
|
|
3061
3170
|
enableSandbox: false,
|
|
3062
3171
|
taskId,
|
|
3172
|
+
terminalRpc: options?.terminalRpc,
|
|
3063
3173
|
createRemoteLogSync: (agentId) => {
|
|
3064
3174
|
saveWebIdeAgentId(workdir, taskId, agentId);
|
|
3065
3175
|
logSyncRef.current = createThrottledWebIdeMessageLogSync(
|
|
@@ -3154,19 +3264,68 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
3154
3264
|
}
|
|
3155
3265
|
}
|
|
3156
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
|
+
|
|
3157
3312
|
// src/commands/connect/webide-message-worker.ts
|
|
3158
3313
|
var controllers = /* @__PURE__ */ new Map();
|
|
3314
|
+
installTerminalRpcListener();
|
|
3315
|
+
var terminalRpc = createWorkerTerminalRpc();
|
|
3159
3316
|
async function runJob(cfg, msg) {
|
|
3160
3317
|
const controller = new AbortController();
|
|
3161
3318
|
controllers.set(msg.messageId, controller);
|
|
3162
3319
|
try {
|
|
3163
|
-
await handleWebIdeInboundMessage(cfg, msg, controller.signal
|
|
3164
|
-
|
|
3320
|
+
await handleWebIdeInboundMessage(cfg, msg, controller.signal, {
|
|
3321
|
+
terminalRpc
|
|
3322
|
+
});
|
|
3323
|
+
parentPort2?.postMessage({
|
|
3165
3324
|
type: "done",
|
|
3166
3325
|
messageId: msg.messageId
|
|
3167
3326
|
});
|
|
3168
3327
|
} catch (err) {
|
|
3169
|
-
|
|
3328
|
+
parentPort2?.postMessage({
|
|
3170
3329
|
type: "error",
|
|
3171
3330
|
messageId: msg.messageId,
|
|
3172
3331
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -3175,7 +3334,7 @@ async function runJob(cfg, msg) {
|
|
|
3175
3334
|
controllers.delete(msg.messageId);
|
|
3176
3335
|
}
|
|
3177
3336
|
}
|
|
3178
|
-
|
|
3337
|
+
parentPort2?.on("message", (raw) => {
|
|
3179
3338
|
if (raw.type === "cancel") {
|
|
3180
3339
|
controllers.get(raw.messageId)?.abort();
|
|
3181
3340
|
return;
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
- write_doc.md:当需要写文档时需要读取这个文档,记住写文档的规则
|
|
32
32
|
- webide_git_commit.md:WebIDE 开发/修码时读取,按规范分轮 commit,结束前 push
|
|
33
33
|
- webide_testcase.md:WebIDE 生成测试用例时读取,按规范编写并提交用例
|
|
34
|
-
- webide_merge.md:WebIDE
|
|
34
|
+
- webide_merge.md:WebIDE 验收通过后读取;直接调用 MergeWebIdePullRequests 合并代码
|
|
35
35
|
|
|
36
36
|
- 仓库项目上下文(`.apm/project/`):
|
|
37
37
|
|
|
@@ -1,39 +1,16 @@
|
|
|
1
1
|
## WebIDE 合并规范
|
|
2
2
|
|
|
3
|
-
适用场景:WebIDE 验收通过(`accept
|
|
3
|
+
适用场景:WebIDE 验收通过(`accept`)后合并本任务 PR。
|
|
4
4
|
|
|
5
5
|
### 目标
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
将本任务各仓库特性分支上的变更合并进基线分支(通常为 `main`)。
|
|
8
8
|
|
|
9
9
|
### 步骤
|
|
10
10
|
|
|
11
|
-
1.
|
|
12
|
-
|
|
13
|
-
- 在各相关仓库拉取最新基线(`git fetch`),将基线 merge/rebase 进特性分支。
|
|
14
|
-
- 有冲突时:在本地解冲突后 commit 并 `git push`(必要时 `--force-with-lease` 仅用于已 rebase 的特性分支)。
|
|
15
|
-
- 解冲突原则:保留本任务需求与已确认计划所要求的行为;吸收基线中合理且不冲突的改动。
|
|
16
|
-
- **解冲突失败或无法安全取舍**:立即 `git merge --abort` / `git rebase --abort` 撤回本次同步,保持特性分支为同步前状态;**禁止**调用合并工具。
|
|
17
|
-
|
|
18
|
-
2. **不可调和冲突 / 同步失败 → 拒绝合并**
|
|
19
|
-
|
|
20
|
-
- 若冲突双方改动在需求/业务逻辑上相悖,或本地同步已 abort:
|
|
21
|
-
- **禁止**调用 `MergeWebIdePullRequests`
|
|
22
|
-
- 用 `AppendMessage` 说明冲突文件、矛盾点、为何无法自动取舍,以及已执行的回退
|
|
23
|
-
- 以错误结束本轮(抛错或明确失败),等待人工介入(前端将展示失败信息)
|
|
24
|
-
|
|
25
|
-
3. **对照原始任务审核代码**
|
|
26
|
-
|
|
27
|
-
- 在合并前快速核对:改动是否覆盖任务需求与已确认计划;是否引入明显与需求相悖的行为。
|
|
28
|
-
- 若审核不通过:同样禁止合并,AppendMessage 说明问题并以错误结束。
|
|
29
|
-
|
|
30
|
-
4. **确认可安全合并后**
|
|
31
|
-
- 调用一次 `MergeWebIdePullRequests`(无参数),由平台合并本任务全部 OPEN PR
|
|
32
|
-
- 再用 `AppendMessage` 汇总合并结果(PR 编号、仓库)
|
|
11
|
+
1. 调用一次 `MergeWebIdePullRequests`(`confirmedConflictFree=true`),由平台合并本任务全部 OPEN PR。
|
|
12
|
+
2. 用 `AppendMessage` 汇总合并结果(PR 编号、仓库)。
|
|
33
13
|
|
|
34
14
|
### 禁止
|
|
35
15
|
|
|
36
|
-
-
|
|
37
|
-
- 同步冲突未 abort 就继续合并
|
|
38
|
-
- 为强行合并而删改与需求相悖的关键逻辑却不说明
|
|
39
|
-
- 在仍有草稿 PR / 明显冲突未解决时假装成功
|
|
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;不要留下孤儿进程。
|