ai-project-manage-cli 7.0.3 → 7.0.5

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.
Files changed (2) hide show
  1. package/dist/index.js +484 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -453,10 +453,18 @@ var requestConfig = {
453
453
  method: "PUT",
454
454
  path: "/cli/mailbox-messages/claim"
455
455
  }),
456
+ appendMailboxDraft: defineEndpoint({
457
+ method: "PUT",
458
+ path: "/cli/mailbox-messages/append-draft"
459
+ }),
456
460
  completeMailboxMessage: defineEndpoint({
457
461
  method: "PUT",
458
462
  path: "/cli/mailbox-messages/complete"
459
463
  }),
464
+ createCursorAgentQuestions: defineEndpoint({
465
+ method: "PUT",
466
+ path: "/cli/cursor-agent/questions"
467
+ }),
460
468
  upsertCursorLog: defineEndpoint({
461
469
  method: "PUT",
462
470
  path: "/cli/cursor-logs"
@@ -1372,13 +1380,65 @@ function validateReceivedMailPush(o) {
1372
1380
  }
1373
1381
  };
1374
1382
  }
1383
+ function validateCursorAgentResumePush(o) {
1384
+ if (o.type !== "cursor_agent_resume") {
1385
+ return { ok: false, reason: "\u671F\u671B cursor_agent_resume" };
1386
+ }
1387
+ if (o.anchorType !== "mailbox") {
1388
+ return { ok: false, reason: "cursor_agent_resume.anchorType \u65E0\u6548" };
1389
+ }
1390
+ if (!nonEmptyString(o.anchorId)) {
1391
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 anchorId" };
1392
+ }
1393
+ if (!nonEmptyString(o.taskId)) {
1394
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 taskId" };
1395
+ }
1396
+ if (!nonEmptyString(o.cursorAgentId)) {
1397
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 cursorAgentId" };
1398
+ }
1399
+ if (!nonEmptyString(o.draftReplyId)) {
1400
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 draftReplyId" };
1401
+ }
1402
+ if (!nonEmptyString(o.workdir)) {
1403
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 workdir" };
1404
+ }
1405
+ if (!nonEmptyString(o.model)) {
1406
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 model" };
1407
+ }
1408
+ if (!nonEmptyString(o.memberDisplayName)) {
1409
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 memberDisplayName" };
1410
+ }
1411
+ const mode = o.mode;
1412
+ if (mode !== "plan" && mode !== "agent") {
1413
+ return { ok: false, reason: "cursor_agent_resume.mode \u65E0\u6548" };
1414
+ }
1415
+ if (!nonEmptyString(o.prompt)) {
1416
+ return { ok: false, reason: "cursor_agent_resume \u7F3A\u5C11 prompt" };
1417
+ }
1418
+ return {
1419
+ ok: true,
1420
+ data: {
1421
+ type: "cursor_agent_resume",
1422
+ anchorType: "mailbox",
1423
+ anchorId: o.anchorId.trim(),
1424
+ taskId: o.taskId.trim(),
1425
+ cursorAgentId: o.cursorAgentId.trim(),
1426
+ draftReplyId: o.draftReplyId.trim(),
1427
+ workdir: o.workdir.trim(),
1428
+ model: o.model.trim(),
1429
+ memberDisplayName: o.memberDisplayName.trim(),
1430
+ mode,
1431
+ prompt: o.prompt.trim()
1432
+ }
1433
+ };
1434
+ }
1375
1435
  function validateAgentWsMessage(value, kind) {
1376
1436
  if (typeof value !== "object" || value === null) {
1377
1437
  return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
1378
1438
  }
1379
1439
  const o = value;
1380
1440
  const type = o.type;
1381
- if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail") {
1441
+ if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail" && type !== "cursor_agent_resume") {
1382
1442
  return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
1383
1443
  }
1384
1444
  if (kind === "heartbeat" || type === "heartbeat") {
@@ -1387,6 +1447,9 @@ function validateAgentWsMessage(value, kind) {
1387
1447
  if (type === "deploy") {
1388
1448
  return validateDeployPush(o);
1389
1449
  }
1450
+ if (type === "cursor_agent_resume") {
1451
+ return validateCursorAgentResumePush(o);
1452
+ }
1390
1453
  return validateReceivedMailPush(o);
1391
1454
  }
1392
1455
 
@@ -2006,6 +2069,176 @@ function clearTaskAgentId(workdir, taskId, user) {
2006
2069
  writeRegistry(path13, registry);
2007
2070
  }
2008
2071
 
2072
+ // src/commands/connect/ask-question-tool.ts
2073
+ import { randomUUID } from "node:crypto";
2074
+
2075
+ // src/ask-question.ts
2076
+ function nonEmptyString2(value) {
2077
+ return typeof value === "string" && value.trim().length > 0;
2078
+ }
2079
+ function parseOption(value, index) {
2080
+ if (typeof value !== "object" || value === null) {
2081
+ return `questions[].options[${index}] \u5FC5\u987B\u662F\u5BF9\u8C61`;
2082
+ }
2083
+ const option = value;
2084
+ if (!nonEmptyString2(option.id)) {
2085
+ return `questions[].options[${index}].id \u4E0D\u80FD\u4E3A\u7A7A`;
2086
+ }
2087
+ if (!nonEmptyString2(option.label)) {
2088
+ return `questions[].options[${index}].label \u4E0D\u80FD\u4E3A\u7A7A`;
2089
+ }
2090
+ return {
2091
+ id: option.id.trim(),
2092
+ label: option.label.trim()
2093
+ };
2094
+ }
2095
+ function parseQuestion(value, index) {
2096
+ if (typeof value !== "object" || value === null) {
2097
+ return `questions[${index}] \u5FC5\u987B\u662F\u5BF9\u8C61`;
2098
+ }
2099
+ const question = value;
2100
+ if (!nonEmptyString2(question.id)) {
2101
+ return `questions[${index}].id \u4E0D\u80FD\u4E3A\u7A7A`;
2102
+ }
2103
+ if (!nonEmptyString2(question.prompt)) {
2104
+ return `questions[${index}].prompt \u4E0D\u80FD\u4E3A\u7A7A`;
2105
+ }
2106
+ if (!Array.isArray(question.options)) {
2107
+ return `questions[${index}].options \u5FC5\u987B\u662F\u6570\u7EC4`;
2108
+ }
2109
+ if (question.options.length < 2) {
2110
+ return `questions[${index}].options \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`;
2111
+ }
2112
+ const options = [];
2113
+ for (let i = 0; i < question.options.length; i++) {
2114
+ const parsed = parseOption(question.options[i], i);
2115
+ if (typeof parsed === "string") {
2116
+ return parsed;
2117
+ }
2118
+ options.push(parsed);
2119
+ }
2120
+ const ids = new Set(options.map((option) => option.id));
2121
+ if (ids.size !== options.length) {
2122
+ return `questions[${index}].options \u7684 id \u4E0D\u80FD\u91CD\u590D`;
2123
+ }
2124
+ return {
2125
+ id: question.id.trim(),
2126
+ prompt: question.prompt.trim(),
2127
+ ...question.allow_multiple === true ? { allow_multiple: true } : {},
2128
+ options
2129
+ };
2130
+ }
2131
+ function validateAskQuestionInput(value) {
2132
+ if (typeof value !== "object" || value === null) {
2133
+ return { ok: false, error: "AskQuestion \u5165\u53C2\u5FC5\u987B\u662F\u5BF9\u8C61" };
2134
+ }
2135
+ const input = value;
2136
+ if (!Array.isArray(input.questions)) {
2137
+ return { ok: false, error: "questions \u5FC5\u987B\u662F\u6570\u7EC4" };
2138
+ }
2139
+ if (input.questions.length < 1) {
2140
+ return { ok: false, error: "questions \u81F3\u5C11\u9700\u8981 1 \u9053\u9898" };
2141
+ }
2142
+ const questions = [];
2143
+ for (let i = 0; i < input.questions.length; i++) {
2144
+ const parsed = parseQuestion(input.questions[i], i);
2145
+ if (typeof parsed === "string") {
2146
+ return { ok: false, error: parsed };
2147
+ }
2148
+ questions.push(parsed);
2149
+ }
2150
+ const questionIds = new Set(questions.map((question) => question.id));
2151
+ if (questionIds.size !== questions.length) {
2152
+ return { ok: false, error: "questions[].id \u4E0D\u80FD\u91CD\u590D" };
2153
+ }
2154
+ const title = nonEmptyString2(input.title) ? input.title.trim() : void 0;
2155
+ return {
2156
+ ok: true,
2157
+ input: {
2158
+ ...title ? { title } : {},
2159
+ questions
2160
+ }
2161
+ };
2162
+ }
2163
+
2164
+ // src/commands/connect/ask-question-tool.ts
2165
+ 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.";
2166
+ 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";
2167
+ function createAskQuestionTool(handlers) {
2168
+ const batches = [];
2169
+ const tool = {
2170
+ description: ASK_QUESTION_DESCRIPTION,
2171
+ inputSchema: {
2172
+ type: "object",
2173
+ properties: {
2174
+ title: {
2175
+ type: "string",
2176
+ description: "Optional title for the question form"
2177
+ },
2178
+ questions: {
2179
+ type: "array",
2180
+ description: "Questions to ask the user",
2181
+ items: {
2182
+ type: "object",
2183
+ properties: {
2184
+ id: {
2185
+ type: "string",
2186
+ description: "Unique identifier for this question"
2187
+ },
2188
+ prompt: {
2189
+ type: "string",
2190
+ description: "The question text to display"
2191
+ },
2192
+ allow_multiple: {
2193
+ type: "boolean",
2194
+ description: "Whether the user may select multiple options"
2195
+ },
2196
+ options: {
2197
+ type: "array",
2198
+ description: "At least 2 answer options",
2199
+ items: {
2200
+ type: "object",
2201
+ properties: {
2202
+ id: { type: "string" },
2203
+ label: { type: "string" }
2204
+ },
2205
+ required: ["id", "label"]
2206
+ }
2207
+ }
2208
+ },
2209
+ required: ["id", "prompt", "options"]
2210
+ }
2211
+ }
2212
+ },
2213
+ required: ["questions"]
2214
+ },
2215
+ execute: async (args) => {
2216
+ const validated = validateAskQuestionInput(args);
2217
+ if (!validated.ok) {
2218
+ return {
2219
+ content: [{ type: "text", text: validated.error }],
2220
+ isError: true
2221
+ };
2222
+ }
2223
+ const batch = {
2224
+ batchId: randomUUID(),
2225
+ ...validated.input
2226
+ };
2227
+ batches.push(batch);
2228
+ await handlers.onAskQuestion(batch);
2229
+ console.log(
2230
+ `[apm] AskQuestion \u5DF2\u63D0\u4EA4 batchId=${batch.batchId} questions=${batch.questions.length}`
2231
+ );
2232
+ return ASK_QUESTION_WAIT_MESSAGE;
2233
+ }
2234
+ };
2235
+ return {
2236
+ tool,
2237
+ getAskedBatches: () => [...batches],
2238
+ hasPendingQuestions: () => batches.length > 0
2239
+ };
2240
+ }
2241
+
2009
2242
  // src/commands/connect/cursor-log.ts
2010
2243
  var CURSOR_LOG_SYNC_INTERVAL_MS = 2e3;
2011
2244
  function createThrottledCursorLogSync(cfg, ctx, onError) {
@@ -2132,6 +2365,34 @@ async function obtainAgent(ctx) {
2132
2365
  }
2133
2366
  return { agent, resumed: false };
2134
2367
  }
2368
+ 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`;
2369
+ function buildPrompt(basePrompt, taskId, mode, promptFooter, hasAskQuestion) {
2370
+ const trimmed = basePrompt.trim();
2371
+ let footer;
2372
+ if (promptFooter === null || promptFooter === "") {
2373
+ footer = "";
2374
+ } else if (promptFooter === void 0) {
2375
+ footer = DEFAULT_MAIL_PROMPT_FOOTER(taskId);
2376
+ } else {
2377
+ footer = promptFooter;
2378
+ }
2379
+ 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" : "";
2380
+ const parts = [trimmed];
2381
+ const hints = [footer, askQuestionHint].filter(Boolean);
2382
+ if (hints.length > 0) {
2383
+ parts.push("---", ...hints);
2384
+ }
2385
+ return parts.join("\n\n");
2386
+ }
2387
+ function mergeCustomTools(customTools, askQuestionTool) {
2388
+ if (!askQuestionTool) {
2389
+ return customTools;
2390
+ }
2391
+ return {
2392
+ ...customTools,
2393
+ AskQuestion: askQuestionTool
2394
+ };
2395
+ }
2135
2396
  async function runCursorAgent(cfg, ctx, options) {
2136
2397
  const signal = options?.signal;
2137
2398
  logAbortSignalStats(signal, "runCursorAgent:start");
@@ -2143,12 +2404,24 @@ async function runCursorAgent(cfg, ctx, options) {
2143
2404
  throw new Error("\u7F3A\u5C11 Cursor API Key");
2144
2405
  }
2145
2406
  const workdir = requireExistingWorkdir(ctx.workdir);
2146
- const customTools = options?.customTools ?? {};
2147
- const prompt = `${ctx.prompt.trim()}
2148
-
2149
- ---
2150
- \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`;
2407
+ const baseCustomTools = options?.customTools ?? {};
2408
+ const agentContext = { agentId: "" };
2409
+ const askQuestion = options?.onAskQuestion ? createAskQuestionTool({
2410
+ onAskQuestion: async (batch) => {
2411
+ await options.onAskQuestion(batch, {
2412
+ agentId: agentContext.agentId
2413
+ });
2414
+ }
2415
+ }) : void 0;
2416
+ const customTools = mergeCustomTools(baseCustomTools, askQuestion?.tool);
2151
2417
  const mode = ctx.mode ?? "agent";
2418
+ const prompt = buildPrompt(
2419
+ ctx.prompt,
2420
+ ctx.taskId,
2421
+ mode,
2422
+ options?.promptFooter,
2423
+ Boolean(askQuestion)
2424
+ );
2152
2425
  console.log(
2153
2426
  `[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} mode=${mode} cwd=${workdir}`
2154
2427
  );
@@ -2161,6 +2434,7 @@ async function runCursorAgent(cfg, ctx, options) {
2161
2434
  mode,
2162
2435
  customTools
2163
2436
  });
2437
+ agentContext.agentId = agent.agentId;
2164
2438
  const eventSession = new EventSession(prompt);
2165
2439
  const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorLogSync(
2166
2440
  cfg,
@@ -2219,11 +2493,14 @@ async function runCursorAgent(cfg, ctx, options) {
2219
2493
  throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
2220
2494
  }
2221
2495
  console.log(`[apm] Cursor Agent \u5B8C\u6210 mailId=${ctx.mailId}`);
2496
+ const askQuestionBatches = askQuestion?.getAskedBatches() ?? [];
2222
2497
  return {
2223
2498
  runId: result.id,
2224
2499
  agentId: agent.agentId,
2225
2500
  status: result.status,
2226
2501
  assistantText: assistantText || result.result || "",
2502
+ askQuestionBatches,
2503
+ hasPendingQuestions: askQuestionBatches.length > 0,
2227
2504
  ...createPlanContent ? { createPlanContent } : {}
2228
2505
  };
2229
2506
  } catch (err) {
@@ -2281,7 +2558,7 @@ function removeMailById(mailId) {
2281
2558
  }
2282
2559
 
2283
2560
  // src/commands/connect/reply-mail-tool.ts
2284
- function createMailReplyDraft() {
2561
+ function createMailReplyDraft(input) {
2285
2562
  const parts = [];
2286
2563
  const tool = {
2287
2564
  description: "\u5411\u53D1\u4FE1\u4EBA\u589E\u91CF\u66F4\u65B0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8FFD\u52A0\u8FDB\u5C55\uFF08\u5982\u5148\u786E\u8BA4\u6536\u5230\u3001\u518D\u6C47\u62A5\u7ED3\u679C\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\uFF0C\u65E0\u9700\u53E6\u884C\u6536\u5C3E\u3002",
@@ -2304,6 +2581,7 @@ function createMailReplyDraft() {
2304
2581
  };
2305
2582
  }
2306
2583
  parts.push(content);
2584
+ await input.appendDraft(content);
2307
2585
  console.log(`[apm] \u589E\u91CF\u66F4\u65B0\u56DE\u4FE1\uFF08\u7B2C ${parts.length} \u6BB5\uFF09`);
2308
2586
  return "\u5DF2\u8FFD\u52A0\u5230\u56DE\u4FE1\u8349\u7A3F\uFF0CAgent \u7ED3\u675F\u540E\u5C06\u4E00\u5E76\u63D0\u4EA4\u3002";
2309
2587
  }
@@ -2508,15 +2786,17 @@ async function processMail(mail, options) {
2508
2786
  );
2509
2787
  return;
2510
2788
  }
2511
- if (detail.status !== "PENDING") {
2789
+ if (detail.status !== "UNREAD") {
2512
2790
  console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u72B6\u6001\u4E3A ${detail.status}\uFF0C\u8DF3\u8FC7\u5904\u7406`);
2513
- if (detail.status === "SUCCEEDED" || detail.status === "FAILED") {
2791
+ if (detail.status === "READ") {
2514
2792
  markMailReplied(mail.id);
2515
2793
  }
2516
2794
  return;
2517
2795
  }
2796
+ let draftReplyId;
2518
2797
  try {
2519
- await api.cli.claimMailboxMessage({ id: mail.id });
2798
+ const claimed = await api.cli.claimMailboxMessage({ id: mail.id });
2799
+ draftReplyId = claimed.draftReplyId;
2520
2800
  } catch (err) {
2521
2801
  console.error(
2522
2802
  `[apm] \u8BA4\u9886\u4FE1\u4EF6\u5931\u8D25 id=${mail.id}:`,
@@ -2529,16 +2809,43 @@ async function processMail(mail, options) {
2529
2809
  tool: replyTool,
2530
2810
  getReplyContent,
2531
2811
  hasReplyContent
2532
- } = createMailReplyDraft();
2812
+ } = createMailReplyDraft({
2813
+ appendDraft: async (content) => {
2814
+ await api.cli.appendMailboxDraft({ id: draftReplyId, content });
2815
+ }
2816
+ });
2533
2817
  try {
2534
2818
  const cursorMode = resolveCursorAgentMode(detail.studio?.phase);
2819
+ const mailContext = {
2820
+ mailId: detail.id,
2821
+ taskId: detail.taskId,
2822
+ draftReplyId,
2823
+ workdir: detail.workdir,
2824
+ model: detail.recipient.model?.trim() || "default",
2825
+ memberDisplayName: detail.recipient.displayName,
2826
+ cursorMode
2827
+ };
2828
+ const submitQuestions = async (batch, ctx) => {
2829
+ await api.cli.createCursorAgentQuestions({
2830
+ anchorType: "mailbox",
2831
+ anchorId: mailContext.mailId,
2832
+ taskId: mailContext.taskId,
2833
+ cursorAgentId: ctx.agentId,
2834
+ draftReplyId: mailContext.draftReplyId,
2835
+ workdir: mailContext.workdir,
2836
+ model: mailContext.model,
2837
+ memberDisplayName: mailContext.memberDisplayName,
2838
+ mode: mailContext.cursorMode,
2839
+ batch
2840
+ });
2841
+ };
2535
2842
  const result = await runCursorAgent(
2536
2843
  options.cfg,
2537
2844
  {
2538
2845
  mailId: detail.id,
2539
2846
  taskId: detail.taskId,
2540
2847
  prompt: detail.content,
2541
- model: detail.recipient.model?.trim() || "default",
2848
+ model: mailContext.model,
2542
2849
  apiKey: cursorApiKey,
2543
2850
  workdir: detail.workdir,
2544
2851
  user: detail.recipient.displayName,
@@ -2546,9 +2853,16 @@ async function processMail(mail, options) {
2546
2853
  },
2547
2854
  {
2548
2855
  signal: options.signal,
2549
- customTools: { append_mail_reply: replyTool }
2856
+ customTools: { append_mail_reply: replyTool },
2857
+ onAskQuestion: submitQuestions
2550
2858
  }
2551
2859
  );
2860
+ if (result.hasPendingQuestions) {
2861
+ console.log(
2862
+ `[apm] \u4FE1\u4EF6 id=${mail.id} \u6709\u5F85\u7B54 AskQuestion\uFF0C\u7B49\u5F85\u7528\u6237\u5728\u5DE5\u4F5C\u5BA4\u7B54\u9898\u540E\u7EED\u804A`
2863
+ );
2864
+ return;
2865
+ }
2552
2866
  const replyContent = (hasReplyContent() ? getReplyContent() : result.assistantText).trim();
2553
2867
  if (!replyContent) {
2554
2868
  throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
@@ -2562,7 +2876,7 @@ async function processMail(mail, options) {
2562
2876
  });
2563
2877
  }
2564
2878
  await api.cli.completeMailboxMessage({
2565
- id: detail.id,
2879
+ id: draftReplyId,
2566
2880
  status: "SUCCEEDED",
2567
2881
  replyContent
2568
2882
  });
@@ -2577,7 +2891,7 @@ async function processMail(mail, options) {
2577
2891
  console.error(`[apm] \u4FE1\u4EF6\u5904\u7406\u5931\u8D25 id=${mail.id}: ${message}`);
2578
2892
  try {
2579
2893
  await api.cli.completeMailboxMessage({
2580
- id: detail.id,
2894
+ id: draftReplyId,
2581
2895
  status: "FAILED",
2582
2896
  error: message
2583
2897
  });
@@ -2648,6 +2962,148 @@ function createMailProcessor(options) {
2648
2962
  };
2649
2963
  }
2650
2964
 
2965
+ // src/commands/connect/cursor-agent-resume-handler.ts
2966
+ async function runMailboxCursorAgentResume(push, options) {
2967
+ const cursorApiKey = options.getCursorApiKey().trim();
2968
+ if (!cursorApiKey) {
2969
+ options.onFatalError(
2970
+ "[apm] \u5BA2\u6237\u673A\u672A\u914D\u7F6E Cursor API Key\uFF0C\u65E0\u6CD5\u7EED\u804A Cursor Agent"
2971
+ );
2972
+ return;
2973
+ }
2974
+ const api = createApmApiClient(options.cfg);
2975
+ let detail;
2976
+ try {
2977
+ detail = await api.cli.getMailboxMessageDetail({ id: push.anchorId });
2978
+ } catch (err) {
2979
+ console.error(
2980
+ `[apm] \u7EED\u804A\u83B7\u53D6\u4FE1\u4EF6\u8BE6\u60C5\u5931\u8D25 id=${push.anchorId}:`,
2981
+ err instanceof Error ? err.message : err
2982
+ );
2983
+ return;
2984
+ }
2985
+ const {
2986
+ tool: replyTool,
2987
+ getReplyContent,
2988
+ hasReplyContent
2989
+ } = createMailReplyDraft({
2990
+ appendDraft: async (content) => {
2991
+ await api.cli.appendMailboxDraft({
2992
+ id: push.draftReplyId,
2993
+ content
2994
+ });
2995
+ }
2996
+ });
2997
+ const cursorMode = push.mode;
2998
+ const submitQuestions = async (batch, ctx) => {
2999
+ await api.cli.createCursorAgentQuestions({
3000
+ anchorType: "mailbox",
3001
+ anchorId: push.anchorId,
3002
+ taskId: push.taskId,
3003
+ cursorAgentId: ctx.agentId,
3004
+ draftReplyId: push.draftReplyId,
3005
+ workdir: push.workdir,
3006
+ model: push.model,
3007
+ memberDisplayName: push.memberDisplayName,
3008
+ mode: cursorMode,
3009
+ batch
3010
+ });
3011
+ };
3012
+ try {
3013
+ const result = await runCursorAgent(
3014
+ options.cfg,
3015
+ {
3016
+ mailId: push.anchorId,
3017
+ taskId: push.taskId,
3018
+ prompt: push.prompt,
3019
+ model: push.model,
3020
+ apiKey: cursorApiKey,
3021
+ workdir: push.workdir,
3022
+ user: push.memberDisplayName,
3023
+ mode: cursorMode
3024
+ },
3025
+ {
3026
+ signal: options.signal,
3027
+ customTools: { append_mail_reply: replyTool },
3028
+ onAskQuestion: submitQuestions,
3029
+ forceSend: true
3030
+ }
3031
+ );
3032
+ if (result.hasPendingQuestions) {
3033
+ console.log(
3034
+ `[apm] \u7EED\u804A\u540E\u4ECD\u6709\u5F85\u7B54 AskQuestion mailId=${push.anchorId}\uFF0C\u7B49\u5F85\u7528\u6237\u7B54\u9898`
3035
+ );
3036
+ return;
3037
+ }
3038
+ const replyContent = (hasReplyContent() ? getReplyContent() : result.assistantText).trim();
3039
+ if (!replyContent) {
3040
+ throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
3041
+ }
3042
+ if (cursorMode === "plan" && result.createPlanContent) {
3043
+ saveMemberPlanDocument({
3044
+ workdir: push.workdir,
3045
+ taskId: push.taskId,
3046
+ memberDisplayName: push.memberDisplayName,
3047
+ content: result.createPlanContent
3048
+ });
3049
+ }
3050
+ await api.cli.completeMailboxMessage({
3051
+ id: push.draftReplyId,
3052
+ status: "SUCCEEDED",
3053
+ replyContent
3054
+ });
3055
+ markMailReplied(push.anchorId);
3056
+ await syncTaskDocuments(options.cfg, push.taskId, push.workdir, {
3057
+ api,
3058
+ remoteDocuments: detail.documents
3059
+ });
3060
+ console.log(`[apm] Cursor Agent \u7EED\u804A\u5B8C\u6210 mailId=${push.anchorId}`);
3061
+ } catch (err) {
3062
+ const message = err instanceof Error ? err.message : String(err);
3063
+ console.error(
3064
+ `[apm] Cursor Agent \u7EED\u804A\u5931\u8D25 mailId=${push.anchorId}: ${message}`
3065
+ );
3066
+ try {
3067
+ await api.cli.completeMailboxMessage({
3068
+ id: push.draftReplyId,
3069
+ status: "FAILED",
3070
+ error: message
3071
+ });
3072
+ } catch (completeErr) {
3073
+ console.error(
3074
+ `[apm] \u6807\u8BB0\u7EED\u804A\u5931\u8D25\u72B6\u6001\u65F6\u51FA\u9519 mailId=${push.anchorId}:`,
3075
+ completeErr instanceof Error ? completeErr.message : completeErr
3076
+ );
3077
+ }
3078
+ markMailReplied(push.anchorId);
3079
+ }
3080
+ }
3081
+ function createCursorAgentResumeHandler(options) {
3082
+ let running = false;
3083
+ return {
3084
+ receive(push) {
3085
+ if (options.signal.aborted) {
3086
+ return;
3087
+ }
3088
+ if (running) {
3089
+ console.warn(
3090
+ `[apm] \u5DF2\u6709 Cursor Agent \u7EED\u804A\u4EFB\u52A1\u8FDB\u884C\u4E2D\uFF0C\u5FFD\u7565 mailId=${push.anchorId}`
3091
+ );
3092
+ return;
3093
+ }
3094
+ running = true;
3095
+ void runMailboxCursorAgentResume(push, options).catch((err) => {
3096
+ console.error(
3097
+ `[apm] Cursor Agent \u7EED\u804A\u5F02\u5E38 mailId=${push.anchorId}:`,
3098
+ err instanceof Error ? err.message : err
3099
+ );
3100
+ }).finally(() => {
3101
+ running = false;
3102
+ });
3103
+ }
3104
+ };
3105
+ }
3106
+
2651
3107
  // src/commands/connect/mail-sync.ts
2652
3108
  function parsePendingMail(value) {
2653
3109
  if (typeof value !== "object" || value === null) {
@@ -2740,6 +3196,15 @@ async function runConnect(options) {
2740
3196
  shutdown(1);
2741
3197
  }
2742
3198
  });
3199
+ const cursorAgentResumeHandler = createCursorAgentResumeHandler({
3200
+ cfg,
3201
+ signal: shutdownAbort.signal,
3202
+ getCursorApiKey: () => cursorApiKey,
3203
+ onFatalError: (message) => {
3204
+ console.error(message);
3205
+ shutdown(1);
3206
+ }
3207
+ });
2743
3208
  const shutdown = (code = 0) => {
2744
3209
  if (shuttingDown) return;
2745
3210
  shuttingDown = true;
@@ -2804,6 +3269,10 @@ async function runConnect(options) {
2804
3269
  }
2805
3270
  if (validated.data.type === "received_mail") {
2806
3271
  mailProcessor.receive(validated.data);
3272
+ return;
3273
+ }
3274
+ if (validated.data.type === "cursor_agent_resume") {
3275
+ cursorAgentResumeHandler.receive(validated.data);
2807
3276
  }
2808
3277
  });
2809
3278
  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.3",
3
+ "version": "7.0.5",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,