ai-project-manage-cli 7.0.4 → 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 +466 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -461,6 +461,10 @@ var requestConfig = {
461
461
  method: "PUT",
462
462
  path: "/cli/mailbox-messages/complete"
463
463
  }),
464
+ createCursorAgentQuestions: defineEndpoint({
465
+ method: "PUT",
466
+ path: "/cli/cursor-agent/questions"
467
+ }),
464
468
  upsertCursorLog: defineEndpoint({
465
469
  method: "PUT",
466
470
  path: "/cli/cursor-logs"
@@ -1376,13 +1380,65 @@ function validateReceivedMailPush(o) {
1376
1380
  }
1377
1381
  };
1378
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
+ }
1379
1435
  function validateAgentWsMessage(value, kind) {
1380
1436
  if (typeof value !== "object" || value === null) {
1381
1437
  return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
1382
1438
  }
1383
1439
  const o = value;
1384
1440
  const type = o.type;
1385
- if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail") {
1441
+ if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail" && type !== "cursor_agent_resume") {
1386
1442
  return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
1387
1443
  }
1388
1444
  if (kind === "heartbeat" || type === "heartbeat") {
@@ -1391,6 +1447,9 @@ function validateAgentWsMessage(value, kind) {
1391
1447
  if (type === "deploy") {
1392
1448
  return validateDeployPush(o);
1393
1449
  }
1450
+ if (type === "cursor_agent_resume") {
1451
+ return validateCursorAgentResumePush(o);
1452
+ }
1394
1453
  return validateReceivedMailPush(o);
1395
1454
  }
1396
1455
 
@@ -2010,6 +2069,176 @@ function clearTaskAgentId(workdir, taskId, user) {
2010
2069
  writeRegistry(path13, registry);
2011
2070
  }
2012
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
+
2013
2242
  // src/commands/connect/cursor-log.ts
2014
2243
  var CURSOR_LOG_SYNC_INTERVAL_MS = 2e3;
2015
2244
  function createThrottledCursorLogSync(cfg, ctx, onError) {
@@ -2136,6 +2365,34 @@ async function obtainAgent(ctx) {
2136
2365
  }
2137
2366
  return { agent, resumed: false };
2138
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
+ }
2139
2396
  async function runCursorAgent(cfg, ctx, options) {
2140
2397
  const signal = options?.signal;
2141
2398
  logAbortSignalStats(signal, "runCursorAgent:start");
@@ -2147,12 +2404,24 @@ async function runCursorAgent(cfg, ctx, options) {
2147
2404
  throw new Error("\u7F3A\u5C11 Cursor API Key");
2148
2405
  }
2149
2406
  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`;
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);
2155
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
+ );
2156
2425
  console.log(
2157
2426
  `[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} mode=${mode} cwd=${workdir}`
2158
2427
  );
@@ -2165,6 +2434,7 @@ async function runCursorAgent(cfg, ctx, options) {
2165
2434
  mode,
2166
2435
  customTools
2167
2436
  });
2437
+ agentContext.agentId = agent.agentId;
2168
2438
  const eventSession = new EventSession(prompt);
2169
2439
  const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorLogSync(
2170
2440
  cfg,
@@ -2223,11 +2493,14 @@ async function runCursorAgent(cfg, ctx, options) {
2223
2493
  throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
2224
2494
  }
2225
2495
  console.log(`[apm] Cursor Agent \u5B8C\u6210 mailId=${ctx.mailId}`);
2496
+ const askQuestionBatches = askQuestion?.getAskedBatches() ?? [];
2226
2497
  return {
2227
2498
  runId: result.id,
2228
2499
  agentId: agent.agentId,
2229
2500
  status: result.status,
2230
2501
  assistantText: assistantText || result.result || "",
2502
+ askQuestionBatches,
2503
+ hasPendingQuestions: askQuestionBatches.length > 0,
2231
2504
  ...createPlanContent ? { createPlanContent } : {}
2232
2505
  };
2233
2506
  } catch (err) {
@@ -2543,13 +2816,36 @@ async function processMail(mail, options) {
2543
2816
  });
2544
2817
  try {
2545
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
+ };
2546
2842
  const result = await runCursorAgent(
2547
2843
  options.cfg,
2548
2844
  {
2549
2845
  mailId: detail.id,
2550
2846
  taskId: detail.taskId,
2551
2847
  prompt: detail.content,
2552
- model: detail.recipient.model?.trim() || "default",
2848
+ model: mailContext.model,
2553
2849
  apiKey: cursorApiKey,
2554
2850
  workdir: detail.workdir,
2555
2851
  user: detail.recipient.displayName,
@@ -2557,9 +2853,16 @@ async function processMail(mail, options) {
2557
2853
  },
2558
2854
  {
2559
2855
  signal: options.signal,
2560
- customTools: { append_mail_reply: replyTool }
2856
+ customTools: { append_mail_reply: replyTool },
2857
+ onAskQuestion: submitQuestions
2561
2858
  }
2562
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
+ }
2563
2866
  const replyContent = (hasReplyContent() ? getReplyContent() : result.assistantText).trim();
2564
2867
  if (!replyContent) {
2565
2868
  throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
@@ -2659,6 +2962,148 @@ function createMailProcessor(options) {
2659
2962
  };
2660
2963
  }
2661
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
+
2662
3107
  // src/commands/connect/mail-sync.ts
2663
3108
  function parsePendingMail(value) {
2664
3109
  if (typeof value !== "object" || value === null) {
@@ -2751,6 +3196,15 @@ async function runConnect(options) {
2751
3196
  shutdown(1);
2752
3197
  }
2753
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
+ });
2754
3208
  const shutdown = (code = 0) => {
2755
3209
  if (shuttingDown) return;
2756
3210
  shuttingDown = true;
@@ -2815,6 +3269,10 @@ async function runConnect(options) {
2815
3269
  }
2816
3270
  if (validated.data.type === "received_mail") {
2817
3271
  mailProcessor.receive(validated.data);
3272
+ return;
3273
+ }
3274
+ if (validated.data.type === "cursor_agent_resume") {
3275
+ cursorAgentResumeHandler.receive(validated.data);
2818
3276
  }
2819
3277
  });
2820
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.4",
3
+ "version": "7.0.5",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,