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.
@@ -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 after conflicts are resolved locally and pushed. Call once with no arguments. Do NOT call if conflicts contradict the task requirements \u2014 report the conflict and fail instead.",
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: you have checked each repo, resolved mergeable conflicts consistently with requirements, and pushed. If conflicts contradict requirements, do not call this tool."
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\u82E5\u5B58\u5728\u4E0E\u9700\u6C42\u76F8\u6096\u7684\u51B2\u7A81\uFF0C\u8BF7\u52FF\u8C03\u7528\u672C\u5DE5\u5177\uFF0C\u7528 AppendMessage \u8BF4\u660E\u5E76\u5931\u8D25\u7ED3\u675F\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 asString3(value) {
2271
+ function asString4(value) {
2085
2272
  return typeof value === "string" ? value.trim() : "";
2086
2273
  }
2087
2274
  function parseQuestions(args) {
2088
- const title = asString3(args.title) || void 0;
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 = asString3(row.id);
2098
- const prompt = asString3(row.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 = asString3(o.id);
2106
- const label = asString3(o.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 pending = parsed.questions.filter((q) => {
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 (pending.length === 0) {
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
- "skip-test",
2630
- "enter-manual-test"
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
- parentPort?.postMessage({
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
- parentPort?.postMessage({
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
- parentPort?.on("message", (raw) => {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "7.1.12",
3
+ "version": "7.1.13",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -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 验收合并时读取;先解冲突再调 MergeWebIdePullRequests,需求相悖则拒绝合并
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`)后:同步最新基线 → 对照原始任务审核 → 合并本任务 PR。
3
+ 适用场景:WebIDE 验收通过(`accept`)后合并本任务 PR。
4
4
 
5
5
  ### 目标
6
6
 
7
- 将本任务各仓库 `feat/task-<taskId>` 分支上的变更安全合并进基线分支(通常为 `main`)。
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;不要留下孤儿进程。