ai-project-manage-cli 7.0.4 → 7.0.6

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 CHANGED
@@ -177,20 +177,6 @@ function isWorkspaceApmInitialized(workdir) {
177
177
  }
178
178
  return readdirSync(fsApmDir).length > 0;
179
179
  }
180
- function assertWorkspaceApmDirExists(workdir) {
181
- if (!isWorkspaceApmInitialized(workdir)) {
182
- const apmDir = workspaceApmDir(workdir);
183
- const fsApmDir = toFsPath(apmDir);
184
- if (!existsSync2(fsApmDir)) {
185
- throw new Error(
186
- `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B\u672A\u68C0\u6D4B\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5728\u8BE5\u4ED3\u5E93\u6839\u76EE\u5F55\u6267\u884C apm init \u5B8C\u6210\u63A5\u5165\u3002`
187
- );
188
- }
189
- throw new Error(
190
- `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B .apm \u76EE\u5F55\u4E3A\u7A7A\uFF0C\u8BF7\u5728\u8BE5\u4ED3\u5E93\u6839\u76EE\u5F55\u6267\u884C apm init \u5B8C\u6210\u63A5\u5165\u3002`
191
- );
192
- }
193
- }
194
180
  var APM_GITIGNORE_PATTERNS = [
195
181
  /^\.apm\/?$/,
196
182
  /^\.apm\/\*\*$/,
@@ -461,6 +447,10 @@ var requestConfig = {
461
447
  method: "PUT",
462
448
  path: "/cli/mailbox-messages/complete"
463
449
  }),
450
+ createCursorAgentQuestions: defineEndpoint({
451
+ method: "PUT",
452
+ path: "/cli/cursor-agent/questions"
453
+ }),
464
454
  upsertCursorLog: defineEndpoint({
465
455
  method: "PUT",
466
456
  path: "/cli/cursor-logs"
@@ -1376,13 +1366,65 @@ function validateReceivedMailPush(o) {
1376
1366
  }
1377
1367
  };
1378
1368
  }
1369
+ function validateCursorAgentResumePush(o) {
1370
+ if (o.type !== "cursor_agent_resume") {
1371
+ return { ok: false, reason: "\u671F\u671B cursor_agent_resume" };
1372
+ }
1373
+ if (o.anchorType !== "mailbox") {
1374
+ return { ok: false, reason: "cursor_agent_resume.anchorType \u65E0\u6548" };
1375
+ }
1376
+ if (!nonEmptyString(o.anchorId)) {
1377
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 anchorId" };
1378
+ }
1379
+ if (!nonEmptyString(o.taskId)) {
1380
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 taskId" };
1381
+ }
1382
+ if (!nonEmptyString(o.cursorAgentId)) {
1383
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 cursorAgentId" };
1384
+ }
1385
+ if (!nonEmptyString(o.draftReplyId)) {
1386
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 draftReplyId" };
1387
+ }
1388
+ if (!nonEmptyString(o.workdir)) {
1389
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 workdir" };
1390
+ }
1391
+ if (!nonEmptyString(o.model)) {
1392
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 model" };
1393
+ }
1394
+ if (!nonEmptyString(o.memberDisplayName)) {
1395
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 memberDisplayName" };
1396
+ }
1397
+ const mode = o.mode;
1398
+ if (mode !== "plan" && mode !== "agent") {
1399
+ return { ok: false, reason: "cursor_agent_resume.mode \u65E0\u6548" };
1400
+ }
1401
+ if (!nonEmptyString(o.prompt)) {
1402
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 prompt" };
1403
+ }
1404
+ return {
1405
+ ok: true,
1406
+ data: {
1407
+ type: "cursor_agent_resume",
1408
+ anchorType: "mailbox",
1409
+ anchorId: o.anchorId.trim(),
1410
+ taskId: o.taskId.trim(),
1411
+ cursorAgentId: o.cursorAgentId.trim(),
1412
+ draftReplyId: o.draftReplyId.trim(),
1413
+ workdir: o.workdir.trim(),
1414
+ model: o.model.trim(),
1415
+ memberDisplayName: o.memberDisplayName.trim(),
1416
+ mode,
1417
+ prompt: o.prompt.trim()
1418
+ }
1419
+ };
1420
+ }
1379
1421
  function validateAgentWsMessage(value, kind) {
1380
1422
  if (typeof value !== "object" || value === null) {
1381
1423
  return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
1382
1424
  }
1383
1425
  const o = value;
1384
1426
  const type = o.type;
1385
- if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail") {
1427
+ if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail" && type !== "cursor_agent_resume") {
1386
1428
  return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
1387
1429
  }
1388
1430
  if (kind === "heartbeat" || type === "heartbeat") {
@@ -1391,6 +1433,9 @@ function validateAgentWsMessage(value, kind) {
1391
1433
  if (type === "deploy") {
1392
1434
  return validateDeployPush(o);
1393
1435
  }
1436
+ if (type === "cursor_agent_resume") {
1437
+ return validateCursorAgentResumePush(o);
1438
+ }
1394
1439
  return validateReceivedMailPush(o);
1395
1440
  }
1396
1441
 
@@ -2010,6 +2055,176 @@ function clearTaskAgentId(workdir, taskId, user) {
2010
2055
  writeRegistry(path13, registry);
2011
2056
  }
2012
2057
 
2058
+ // src/commands/connect/ask-question-tool.ts
2059
+ import { randomUUID } from "node:crypto";
2060
+
2061
+ // src/ask-question.ts
2062
+ function nonEmptyString2(value) {
2063
+ return typeof value === "string" && value.trim().length > 0;
2064
+ }
2065
+ function parseOption(value, index) {
2066
+ if (typeof value !== "object" || value === null) {
2067
+ return `questions[].options[${index}] \u5FC5\u987B\u662F\u5BF9\u8C61`;
2068
+ }
2069
+ const option = value;
2070
+ if (!nonEmptyString2(option.id)) {
2071
+ return `questions[].options[${index}].id \u4E0D\u80FD\u4E3A\u7A7A`;
2072
+ }
2073
+ if (!nonEmptyString2(option.label)) {
2074
+ return `questions[].options[${index}].label \u4E0D\u80FD\u4E3A\u7A7A`;
2075
+ }
2076
+ return {
2077
+ id: option.id.trim(),
2078
+ label: option.label.trim()
2079
+ };
2080
+ }
2081
+ function parseQuestion(value, index) {
2082
+ if (typeof value !== "object" || value === null) {
2083
+ return `questions[${index}] \u5FC5\u987B\u662F\u5BF9\u8C61`;
2084
+ }
2085
+ const question = value;
2086
+ if (!nonEmptyString2(question.id)) {
2087
+ return `questions[${index}].id \u4E0D\u80FD\u4E3A\u7A7A`;
2088
+ }
2089
+ if (!nonEmptyString2(question.prompt)) {
2090
+ return `questions[${index}].prompt \u4E0D\u80FD\u4E3A\u7A7A`;
2091
+ }
2092
+ if (!Array.isArray(question.options)) {
2093
+ return `questions[${index}].options \u5FC5\u987B\u662F\u6570\u7EC4`;
2094
+ }
2095
+ if (question.options.length < 2) {
2096
+ return `questions[${index}].options \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`;
2097
+ }
2098
+ const options = [];
2099
+ for (let i = 0; i < question.options.length; i++) {
2100
+ const parsed = parseOption(question.options[i], i);
2101
+ if (typeof parsed === "string") {
2102
+ return parsed;
2103
+ }
2104
+ options.push(parsed);
2105
+ }
2106
+ const ids = new Set(options.map((option) => option.id));
2107
+ if (ids.size !== options.length) {
2108
+ return `questions[${index}].options \u7684 id \u4E0D\u80FD\u91CD\u590D`;
2109
+ }
2110
+ return {
2111
+ id: question.id.trim(),
2112
+ prompt: question.prompt.trim(),
2113
+ ...question.allow_multiple === true ? { allow_multiple: true } : {},
2114
+ options
2115
+ };
2116
+ }
2117
+ function validateAskQuestionInput(value) {
2118
+ if (typeof value !== "object" || value === null) {
2119
+ return { ok: false, error: "AskQuestion \u5165\u53C2\u5FC5\u987B\u662F\u5BF9\u8C61" };
2120
+ }
2121
+ const input = value;
2122
+ if (!Array.isArray(input.questions)) {
2123
+ return { ok: false, error: "questions \u5FC5\u987B\u662F\u6570\u7EC4" };
2124
+ }
2125
+ if (input.questions.length < 1) {
2126
+ return { ok: false, error: "questions \u81F3\u5C11\u9700\u8981 1 \u9053\u9898" };
2127
+ }
2128
+ const questions = [];
2129
+ for (let i = 0; i < input.questions.length; i++) {
2130
+ const parsed = parseQuestion(input.questions[i], i);
2131
+ if (typeof parsed === "string") {
2132
+ return { ok: false, error: parsed };
2133
+ }
2134
+ questions.push(parsed);
2135
+ }
2136
+ const questionIds = new Set(questions.map((question) => question.id));
2137
+ if (questionIds.size !== questions.length) {
2138
+ return { ok: false, error: "questions[].id \u4E0D\u80FD\u91CD\u590D" };
2139
+ }
2140
+ const title = nonEmptyString2(input.title) ? input.title.trim() : void 0;
2141
+ return {
2142
+ ok: true,
2143
+ input: {
2144
+ ...title ? { title } : {},
2145
+ questions
2146
+ }
2147
+ };
2148
+ }
2149
+
2150
+ // src/commands/connect/ask-question-tool.ts
2151
+ var ASK_QUESTION_DESCRIPTION = "Collect structured multiple-choice answers from the user. Use when you need to clarify requirements or preferences before proceeding. When options are provided, do not re-ask the same question in prose.";
2152
+ var ASK_QUESTION_WAIT_MESSAGE = "\u95EE\u9898\u5DF2\u63D0\u4EA4\uFF0C\u7B49\u5F85\u7528\u6237\u56DE\u7B54\u3002\u8BF7\u52FF\u7EE7\u7EED\u89C4\u5212\u6216\u4EA7\u51FA\u6700\u7EC8\u7ED3\u8BBA\uFF0C\u76F4\u5230\u540E\u7EED\u6D88\u606F\u6CE8\u5165\u7528\u6237\u7B54\u6848\u3002";
2153
+ function createAskQuestionTool(handlers) {
2154
+ const batches = [];
2155
+ const tool = {
2156
+ description: ASK_QUESTION_DESCRIPTION,
2157
+ inputSchema: {
2158
+ type: "object",
2159
+ properties: {
2160
+ title: {
2161
+ type: "string",
2162
+ description: "Optional title for the question form"
2163
+ },
2164
+ questions: {
2165
+ type: "array",
2166
+ description: "Questions to ask the user",
2167
+ items: {
2168
+ type: "object",
2169
+ properties: {
2170
+ id: {
2171
+ type: "string",
2172
+ description: "Unique identifier for this question"
2173
+ },
2174
+ prompt: {
2175
+ type: "string",
2176
+ description: "The question text to display"
2177
+ },
2178
+ allow_multiple: {
2179
+ type: "boolean",
2180
+ description: "Whether the user may select multiple options"
2181
+ },
2182
+ options: {
2183
+ type: "array",
2184
+ description: "At least 2 answer options",
2185
+ items: {
2186
+ type: "object",
2187
+ properties: {
2188
+ id: { type: "string" },
2189
+ label: { type: "string" }
2190
+ },
2191
+ required: ["id", "label"]
2192
+ }
2193
+ }
2194
+ },
2195
+ required: ["id", "prompt", "options"]
2196
+ }
2197
+ }
2198
+ },
2199
+ required: ["questions"]
2200
+ },
2201
+ execute: async (args) => {
2202
+ const validated = validateAskQuestionInput(args);
2203
+ if (!validated.ok) {
2204
+ return {
2205
+ content: [{ type: "text", text: validated.error }],
2206
+ isError: true
2207
+ };
2208
+ }
2209
+ const batch = {
2210
+ batchId: randomUUID(),
2211
+ ...validated.input
2212
+ };
2213
+ batches.push(batch);
2214
+ await handlers.onAskQuestion(batch);
2215
+ console.log(
2216
+ `[apm] AskQuestion \u5DF2\u63D0\u4EA4 batchId=${batch.batchId} questions=${batch.questions.length}`
2217
+ );
2218
+ return ASK_QUESTION_WAIT_MESSAGE;
2219
+ }
2220
+ };
2221
+ return {
2222
+ tool,
2223
+ getAskedBatches: () => [...batches],
2224
+ hasPendingQuestions: () => batches.length > 0
2225
+ };
2226
+ }
2227
+
2013
2228
  // src/commands/connect/cursor-log.ts
2014
2229
  var CURSOR_LOG_SYNC_INTERVAL_MS = 2e3;
2015
2230
  function createThrottledCursorLogSync(cfg, ctx, onError) {
@@ -2136,6 +2351,34 @@ async function obtainAgent(ctx) {
2136
2351
  }
2137
2352
  return { agent, resumed: false };
2138
2353
  }
2354
+ var DEFAULT_MAIL_PROMPT_FOOTER = (taskId) => `\u8BF7\u5B8C\u6210\u4E0A\u8FF0\u4EFB\u52A1\u3002\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BF7\u7528 append_mail_reply \u589E\u91CF\u66F4\u65B0\u7ED9\u53D1\u4FE1\u4EBA\u7684\u56DE\u590D\uFF08\u53EF\u5148\u7B80\u77ED\u786E\u8BA4\uFF0C\u6709\u8FDB\u5C55\u7EE7\u7EED\u8FFD\u52A0\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\u3002\u5982\u9700\u7F16\u5199\u6587\u6863\uFF0C\u4FDD\u5B58\u5230 \`.apm/tasks/${taskId}/docs/\` \u76EE\u5F55\u3002`;
2355
+ function buildPrompt(basePrompt, taskId, mode, promptFooter, hasAskQuestion) {
2356
+ const trimmed = basePrompt.trim();
2357
+ let footer;
2358
+ if (promptFooter === null || promptFooter === "") {
2359
+ footer = "";
2360
+ } else if (promptFooter === void 0) {
2361
+ footer = DEFAULT_MAIL_PROMPT_FOOTER(taskId);
2362
+ } else {
2363
+ footer = promptFooter;
2364
+ }
2365
+ const askQuestionHint = hasAskQuestion && mode === "plan" ? "\u9700\u8981\u7528\u6237\u786E\u8BA4\u65F6\u8BF7\u4F7F\u7528 AskQuestion \u5DE5\u5177\uFF0C\u52FF\u5728\u6B63\u6587\u4E2D\u7F57\u5217\u9009\u9879\u8BA9\u7528\u6237\u6587\u5B57\u56DE\u590D\u3002" : "";
2366
+ const parts = [trimmed];
2367
+ const hints = [footer, askQuestionHint].filter(Boolean);
2368
+ if (hints.length > 0) {
2369
+ parts.push("---", ...hints);
2370
+ }
2371
+ return parts.join("\n\n");
2372
+ }
2373
+ function mergeCustomTools(customTools, askQuestionTool) {
2374
+ if (!askQuestionTool) {
2375
+ return customTools;
2376
+ }
2377
+ return {
2378
+ ...customTools,
2379
+ AskQuestion: askQuestionTool
2380
+ };
2381
+ }
2139
2382
  async function runCursorAgent(cfg, ctx, options) {
2140
2383
  const signal = options?.signal;
2141
2384
  logAbortSignalStats(signal, "runCursorAgent:start");
@@ -2147,12 +2390,24 @@ async function runCursorAgent(cfg, ctx, options) {
2147
2390
  throw new Error("\u7F3A\u5C11 Cursor API Key");
2148
2391
  }
2149
2392
  const workdir = requireExistingWorkdir(ctx.workdir);
2150
- const customTools = options?.customTools ?? {};
2151
- const prompt = `${ctx.prompt.trim()}
2152
-
2153
- ---
2154
- \u8BF7\u5B8C\u6210\u4E0A\u8FF0\u4EFB\u52A1\u3002\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BF7\u7528 append_mail_reply \u589E\u91CF\u66F4\u65B0\u7ED9\u53D1\u4FE1\u4EBA\u7684\u56DE\u590D\uFF08\u53EF\u5148\u7B80\u77ED\u786E\u8BA4\uFF0C\u6709\u8FDB\u5C55\u7EE7\u7EED\u8FFD\u52A0\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\u3002\u5982\u9700\u7F16\u5199\u6587\u6863\uFF0C\u4FDD\u5B58\u5230 \`.apm/tasks/${ctx.taskId}/docs/\` \u76EE\u5F55\u3002`;
2393
+ const baseCustomTools = options?.customTools ?? {};
2394
+ const agentContext = { agentId: "" };
2395
+ const askQuestion = options?.onAskQuestion ? createAskQuestionTool({
2396
+ onAskQuestion: async (batch) => {
2397
+ await options.onAskQuestion(batch, {
2398
+ agentId: agentContext.agentId
2399
+ });
2400
+ }
2401
+ }) : void 0;
2402
+ const customTools = mergeCustomTools(baseCustomTools, askQuestion?.tool);
2155
2403
  const mode = ctx.mode ?? "agent";
2404
+ const prompt = buildPrompt(
2405
+ ctx.prompt,
2406
+ ctx.taskId,
2407
+ mode,
2408
+ options?.promptFooter,
2409
+ Boolean(askQuestion)
2410
+ );
2156
2411
  console.log(
2157
2412
  `[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} mode=${mode} cwd=${workdir}`
2158
2413
  );
@@ -2165,6 +2420,7 @@ async function runCursorAgent(cfg, ctx, options) {
2165
2420
  mode,
2166
2421
  customTools
2167
2422
  });
2423
+ agentContext.agentId = agent.agentId;
2168
2424
  const eventSession = new EventSession(prompt);
2169
2425
  const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorLogSync(
2170
2426
  cfg,
@@ -2223,11 +2479,14 @@ async function runCursorAgent(cfg, ctx, options) {
2223
2479
  throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
2224
2480
  }
2225
2481
  console.log(`[apm] Cursor Agent \u5B8C\u6210 mailId=${ctx.mailId}`);
2482
+ const askQuestionBatches = askQuestion?.getAskedBatches() ?? [];
2226
2483
  return {
2227
2484
  runId: result.id,
2228
2485
  agentId: agent.agentId,
2229
2486
  status: result.status,
2230
2487
  assistantText: assistantText || result.result || "",
2488
+ askQuestionBatches,
2489
+ hasPendingQuestions: askQuestionBatches.length > 0,
2231
2490
  ...createPlanContent ? { createPlanContent } : {}
2232
2491
  };
2233
2492
  } catch (err) {
@@ -2433,7 +2692,7 @@ async function syncPlatformRules(cfg, workdirPath, apmRoot) {
2433
2692
  // src/commands/connect/task-pull.ts
2434
2693
  async function runTaskPull(cfg, detail) {
2435
2694
  const { taskId, workdir } = detail;
2436
- assertWorkspaceApmDirExists(workdir);
2695
+ await ensureWorkspaceInitialized(workdir);
2437
2696
  const apmRoot = workspaceApmDir(workdir);
2438
2697
  const dir = taskDir(taskId, apmRoot, workdir);
2439
2698
  const docsDir = taskDocsDir(taskId, apmRoot, workdir);
@@ -2543,13 +2802,36 @@ async function processMail(mail, options) {
2543
2802
  });
2544
2803
  try {
2545
2804
  const cursorMode = resolveCursorAgentMode(detail.studio?.phase);
2805
+ const mailContext = {
2806
+ mailId: detail.id,
2807
+ taskId: detail.taskId,
2808
+ draftReplyId,
2809
+ workdir: detail.workdir,
2810
+ model: detail.recipient.model?.trim() || "default",
2811
+ memberDisplayName: detail.recipient.displayName,
2812
+ cursorMode
2813
+ };
2814
+ const submitQuestions = async (batch, ctx) => {
2815
+ await api.cli.createCursorAgentQuestions({
2816
+ anchorType: "mailbox",
2817
+ anchorId: mailContext.mailId,
2818
+ taskId: mailContext.taskId,
2819
+ cursorAgentId: ctx.agentId,
2820
+ draftReplyId: mailContext.draftReplyId,
2821
+ workdir: mailContext.workdir,
2822
+ model: mailContext.model,
2823
+ memberDisplayName: mailContext.memberDisplayName,
2824
+ mode: mailContext.cursorMode,
2825
+ batch
2826
+ });
2827
+ };
2546
2828
  const result = await runCursorAgent(
2547
2829
  options.cfg,
2548
2830
  {
2549
2831
  mailId: detail.id,
2550
2832
  taskId: detail.taskId,
2551
2833
  prompt: detail.content,
2552
- model: detail.recipient.model?.trim() || "default",
2834
+ model: mailContext.model,
2553
2835
  apiKey: cursorApiKey,
2554
2836
  workdir: detail.workdir,
2555
2837
  user: detail.recipient.displayName,
@@ -2557,9 +2839,16 @@ async function processMail(mail, options) {
2557
2839
  },
2558
2840
  {
2559
2841
  signal: options.signal,
2560
- customTools: { append_mail_reply: replyTool }
2842
+ customTools: { append_mail_reply: replyTool },
2843
+ onAskQuestion: submitQuestions
2561
2844
  }
2562
2845
  );
2846
+ if (result.hasPendingQuestions) {
2847
+ console.log(
2848
+ `[apm] \u4FE1\u4EF6 id=${mail.id} \u6709\u5F85\u7B54 AskQuestion\uFF0C\u7B49\u5F85\u7528\u6237\u5728\u5DE5\u4F5C\u5BA4\u7B54\u9898\u540E\u7EED\u804A`
2849
+ );
2850
+ return;
2851
+ }
2563
2852
  const replyContent = (hasReplyContent() ? getReplyContent() : result.assistantText).trim();
2564
2853
  if (!replyContent) {
2565
2854
  throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
@@ -2659,6 +2948,148 @@ function createMailProcessor(options) {
2659
2948
  };
2660
2949
  }
2661
2950
 
2951
+ // src/commands/connect/cursor-agent-resume-handler.ts
2952
+ async function runMailboxCursorAgentResume(push, options) {
2953
+ const cursorApiKey = options.getCursorApiKey().trim();
2954
+ if (!cursorApiKey) {
2955
+ options.onFatalError(
2956
+ "[apm] \u5BA2\u6237\u673A\u672A\u914D\u7F6E Cursor API Key\uFF0C\u65E0\u6CD5\u7EED\u804A Cursor Agent"
2957
+ );
2958
+ return;
2959
+ }
2960
+ const api = createApmApiClient(options.cfg);
2961
+ let detail;
2962
+ try {
2963
+ detail = await api.cli.getMailboxMessageDetail({ id: push.anchorId });
2964
+ } catch (err) {
2965
+ console.error(
2966
+ `[apm] \u7EED\u804A\u83B7\u53D6\u4FE1\u4EF6\u8BE6\u60C5\u5931\u8D25 id=${push.anchorId}:`,
2967
+ err instanceof Error ? err.message : err
2968
+ );
2969
+ return;
2970
+ }
2971
+ const {
2972
+ tool: replyTool,
2973
+ getReplyContent,
2974
+ hasReplyContent
2975
+ } = createMailReplyDraft({
2976
+ appendDraft: async (content) => {
2977
+ await api.cli.appendMailboxDraft({
2978
+ id: push.draftReplyId,
2979
+ content
2980
+ });
2981
+ }
2982
+ });
2983
+ const cursorMode = push.mode;
2984
+ const submitQuestions = async (batch, ctx) => {
2985
+ await api.cli.createCursorAgentQuestions({
2986
+ anchorType: "mailbox",
2987
+ anchorId: push.anchorId,
2988
+ taskId: push.taskId,
2989
+ cursorAgentId: ctx.agentId,
2990
+ draftReplyId: push.draftReplyId,
2991
+ workdir: push.workdir,
2992
+ model: push.model,
2993
+ memberDisplayName: push.memberDisplayName,
2994
+ mode: cursorMode,
2995
+ batch
2996
+ });
2997
+ };
2998
+ try {
2999
+ const result = await runCursorAgent(
3000
+ options.cfg,
3001
+ {
3002
+ mailId: push.anchorId,
3003
+ taskId: push.taskId,
3004
+ prompt: push.prompt,
3005
+ model: push.model,
3006
+ apiKey: cursorApiKey,
3007
+ workdir: push.workdir,
3008
+ user: push.memberDisplayName,
3009
+ mode: cursorMode
3010
+ },
3011
+ {
3012
+ signal: options.signal,
3013
+ customTools: { append_mail_reply: replyTool },
3014
+ onAskQuestion: submitQuestions,
3015
+ forceSend: true
3016
+ }
3017
+ );
3018
+ if (result.hasPendingQuestions) {
3019
+ console.log(
3020
+ `[apm] \u7EED\u804A\u540E\u4ECD\u6709\u5F85\u7B54 AskQuestion mailId=${push.anchorId}\uFF0C\u7B49\u5F85\u7528\u6237\u7B54\u9898`
3021
+ );
3022
+ return;
3023
+ }
3024
+ const replyContent = (hasReplyContent() ? getReplyContent() : result.assistantText).trim();
3025
+ if (!replyContent) {
3026
+ throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
3027
+ }
3028
+ if (cursorMode === "plan" && result.createPlanContent) {
3029
+ saveMemberPlanDocument({
3030
+ workdir: push.workdir,
3031
+ taskId: push.taskId,
3032
+ memberDisplayName: push.memberDisplayName,
3033
+ content: result.createPlanContent
3034
+ });
3035
+ }
3036
+ await api.cli.completeMailboxMessage({
3037
+ id: push.draftReplyId,
3038
+ status: "SUCCEEDED",
3039
+ replyContent
3040
+ });
3041
+ markMailReplied(push.anchorId);
3042
+ await syncTaskDocuments(options.cfg, push.taskId, push.workdir, {
3043
+ api,
3044
+ remoteDocuments: detail.documents
3045
+ });
3046
+ console.log(`[apm] Cursor Agent \u7EED\u804A\u5B8C\u6210 mailId=${push.anchorId}`);
3047
+ } catch (err) {
3048
+ const message = err instanceof Error ? err.message : String(err);
3049
+ console.error(
3050
+ `[apm] Cursor Agent \u7EED\u804A\u5931\u8D25 mailId=${push.anchorId}: ${message}`
3051
+ );
3052
+ try {
3053
+ await api.cli.completeMailboxMessage({
3054
+ id: push.draftReplyId,
3055
+ status: "FAILED",
3056
+ error: message
3057
+ });
3058
+ } catch (completeErr) {
3059
+ console.error(
3060
+ `[apm] \u6807\u8BB0\u7EED\u804A\u5931\u8D25\u72B6\u6001\u65F6\u51FA\u9519 mailId=${push.anchorId}:`,
3061
+ completeErr instanceof Error ? completeErr.message : completeErr
3062
+ );
3063
+ }
3064
+ markMailReplied(push.anchorId);
3065
+ }
3066
+ }
3067
+ function createCursorAgentResumeHandler(options) {
3068
+ let running = false;
3069
+ return {
3070
+ receive(push) {
3071
+ if (options.signal.aborted) {
3072
+ return;
3073
+ }
3074
+ if (running) {
3075
+ console.warn(
3076
+ `[apm] \u5DF2\u6709 Cursor Agent \u7EED\u804A\u4EFB\u52A1\u8FDB\u884C\u4E2D\uFF0C\u5FFD\u7565 mailId=${push.anchorId}`
3077
+ );
3078
+ return;
3079
+ }
3080
+ running = true;
3081
+ void runMailboxCursorAgentResume(push, options).catch((err) => {
3082
+ console.error(
3083
+ `[apm] Cursor Agent \u7EED\u804A\u5F02\u5E38 mailId=${push.anchorId}:`,
3084
+ err instanceof Error ? err.message : err
3085
+ );
3086
+ }).finally(() => {
3087
+ running = false;
3088
+ });
3089
+ }
3090
+ };
3091
+ }
3092
+
2662
3093
  // src/commands/connect/mail-sync.ts
2663
3094
  function parsePendingMail(value) {
2664
3095
  if (typeof value !== "object" || value === null) {
@@ -2751,6 +3182,15 @@ async function runConnect(options) {
2751
3182
  shutdown(1);
2752
3183
  }
2753
3184
  });
3185
+ const cursorAgentResumeHandler = createCursorAgentResumeHandler({
3186
+ cfg,
3187
+ signal: shutdownAbort.signal,
3188
+ getCursorApiKey: () => cursorApiKey,
3189
+ onFatalError: (message) => {
3190
+ console.error(message);
3191
+ shutdown(1);
3192
+ }
3193
+ });
2754
3194
  const shutdown = (code = 0) => {
2755
3195
  if (shuttingDown) return;
2756
3196
  shuttingDown = true;
@@ -2815,6 +3255,10 @@ async function runConnect(options) {
2815
3255
  }
2816
3256
  if (validated.data.type === "received_mail") {
2817
3257
  mailProcessor.receive(validated.data);
3258
+ return;
3259
+ }
3260
+ if (validated.data.type === "cursor_agent_resume") {
3261
+ cursorAgentResumeHandler.receive(validated.data);
2818
3262
  }
2819
3263
  });
2820
3264
  ws.on("close", (code, reason) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "7.0.4",
3
+ "version": "7.0.6",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,8 +0,0 @@
1
- ## 前端产物下载地址
2
-
3
- http://<服务器地址>/dist.zip
4
- http://<服务器地址>/{name}.jar.zip
5
-
6
- ## 测试地址
7
-
8
- http://<服务器地址>