ai-project-manage-cli 6.0.63 → 6.0.64

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 +545 -79
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -484,6 +484,22 @@ var requestConfig = {
484
484
  completeCoordinatorDeployment: defineEndpoint({
485
485
  method: "PUT",
486
486
  path: "/cli/coordinator-deployments/complete"
487
+ }),
488
+ updateCoordinatorDispatchStatus: defineEndpoint({
489
+ method: "PUT",
490
+ path: "/cli/coordinator-dispatches/status"
491
+ }),
492
+ appendCoordinatorDispatchResponse: defineEndpoint({
493
+ method: "PUT",
494
+ path: "/cli/coordinator-dispatches/append-response"
495
+ }),
496
+ completeCoordinatorDispatch: defineEndpoint({
497
+ method: "PUT",
498
+ path: "/cli/coordinator-dispatches/complete"
499
+ }),
500
+ createCoordinatorDispatchQuestions: defineEndpoint({
501
+ method: "POST",
502
+ path: "/cli/coordinator-dispatches/questions"
487
503
  })
488
504
  }
489
505
  };
@@ -2152,13 +2168,113 @@ function validateDeployPush(o) {
2152
2168
  }
2153
2169
  };
2154
2170
  }
2171
+ function validateCoordinatorDispatchMode(mode) {
2172
+ return mode === "plan" || mode === "agent";
2173
+ }
2174
+ function validateCoordinatorDispatchPush(o) {
2175
+ if (o.type !== "coordinator_dispatch") {
2176
+ return { ok: false, reason: "\u671F\u671B coordinator_dispatch" };
2177
+ }
2178
+ if (!nonEmptyString(o.dispatchId)) {
2179
+ return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 dispatchId" };
2180
+ }
2181
+ if (!nonEmptyString(o.dispatchSessionId)) {
2182
+ return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 dispatchSessionId" };
2183
+ }
2184
+ if (!validateCoordinatorDispatchMode(o.mode)) {
2185
+ return { ok: false, reason: "coordinator_dispatch.mode \u65E0\u6548" };
2186
+ }
2187
+ if (!nonEmptyString(o.content)) {
2188
+ return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 content" };
2189
+ }
2190
+ if (!nonEmptyString(o.model)) {
2191
+ return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 model" };
2192
+ }
2193
+ if (!nonEmptyString(o.apiKey)) {
2194
+ return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 apiKey" };
2195
+ }
2196
+ if (!nonEmptyString(o.workdir)) {
2197
+ return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 workdir" };
2198
+ }
2199
+ if (!nonEmptyString(o.user)) {
2200
+ return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 user" };
2201
+ }
2202
+ const data = {
2203
+ type: "coordinator_dispatch",
2204
+ dispatchId: o.dispatchId.trim(),
2205
+ dispatchSessionId: o.dispatchSessionId.trim(),
2206
+ mode: o.mode,
2207
+ content: o.content,
2208
+ model: o.model.trim(),
2209
+ apiKey: o.apiKey.trim(),
2210
+ workdir: o.workdir.trim(),
2211
+ user: o.user.trim()
2212
+ };
2213
+ if (nonEmptyString(o.resumeAgentId)) {
2214
+ data.resumeAgentId = o.resumeAgentId.trim();
2215
+ }
2216
+ return { ok: true, data };
2217
+ }
2218
+ function validateCoordinatorDispatchResumePush(o) {
2219
+ if (o.type !== "coordinator_dispatch_resume") {
2220
+ return { ok: false, reason: "\u671F\u671B coordinator_dispatch_resume" };
2221
+ }
2222
+ if (!nonEmptyString(o.dispatchId)) {
2223
+ return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchId" };
2224
+ }
2225
+ if (!nonEmptyString(o.dispatchSessionId)) {
2226
+ return {
2227
+ ok: false,
2228
+ reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchSessionId"
2229
+ };
2230
+ }
2231
+ if (!validateCoordinatorDispatchMode(o.mode)) {
2232
+ return { ok: false, reason: "coordinator_dispatch_resume.mode \u65E0\u6548" };
2233
+ }
2234
+ if (!nonEmptyString(o.content)) {
2235
+ return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 content" };
2236
+ }
2237
+ if (!nonEmptyString(o.resumeAgentId)) {
2238
+ return {
2239
+ ok: false,
2240
+ reason: "coordinator_dispatch_resume \u7F3A\u5C11 resumeAgentId"
2241
+ };
2242
+ }
2243
+ if (!nonEmptyString(o.model)) {
2244
+ return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 model" };
2245
+ }
2246
+ if (!nonEmptyString(o.apiKey)) {
2247
+ return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 apiKey" };
2248
+ }
2249
+ if (!nonEmptyString(o.workdir)) {
2250
+ return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 workdir" };
2251
+ }
2252
+ if (!nonEmptyString(o.user)) {
2253
+ return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 user" };
2254
+ }
2255
+ return {
2256
+ ok: true,
2257
+ data: {
2258
+ type: "coordinator_dispatch_resume",
2259
+ dispatchId: o.dispatchId.trim(),
2260
+ dispatchSessionId: o.dispatchSessionId.trim(),
2261
+ mode: o.mode,
2262
+ content: o.content,
2263
+ resumeAgentId: o.resumeAgentId.trim(),
2264
+ model: o.model.trim(),
2265
+ apiKey: o.apiKey.trim(),
2266
+ workdir: o.workdir.trim(),
2267
+ user: o.user.trim()
2268
+ }
2269
+ };
2270
+ }
2155
2271
  function validateAgentWsMessage(value, kind) {
2156
2272
  if (typeof value !== "object" || value === null) {
2157
2273
  return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
2158
2274
  }
2159
2275
  const o = value;
2160
2276
  const type = o.type;
2161
- if (type !== "heartbeat" && type !== "message" && type !== "cancel" && type !== "deploy") {
2277
+ if (type !== "heartbeat" && type !== "message" && type !== "cancel" && type !== "deploy" && type !== "coordinator_dispatch" && type !== "coordinator_dispatch_resume") {
2162
2278
  return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
2163
2279
  }
2164
2280
  if (kind === "heartbeat" || type === "heartbeat") {
@@ -2170,6 +2286,12 @@ function validateAgentWsMessage(value, kind) {
2170
2286
  if (type === "deploy") {
2171
2287
  return validateDeployPush(o);
2172
2288
  }
2289
+ if (type === "coordinator_dispatch") {
2290
+ return validateCoordinatorDispatchPush(o);
2291
+ }
2292
+ if (type === "coordinator_dispatch_resume") {
2293
+ return validateCoordinatorDispatchResumePush(o);
2294
+ }
2173
2295
  return validateMessagePush(o);
2174
2296
  }
2175
2297
 
@@ -2341,69 +2463,6 @@ async function handleInboundDeploy(cfg, msg, signal) {
2341
2463
  }
2342
2464
  }
2343
2465
 
2344
- // src/commands/connect/abort-signal-debug.ts
2345
- import {
2346
- getEventListeners,
2347
- getMaxListeners,
2348
- setMaxListeners
2349
- } from "node:events";
2350
- function isAbortSignalDebugEnabled() {
2351
- const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
2352
- return v === "1" || v === "true" || v === "yes";
2353
- }
2354
- function formatAbortSignalStats(signal, label) {
2355
- if (!signal) {
2356
- return `[apm:abort-debug] ${label}: (no signal)`;
2357
- }
2358
- const listeners = getEventListeners(signal, "abort");
2359
- const max = getMaxListeners(signal);
2360
- return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
2361
- }
2362
- function logAbortSignalStats(signal, label) {
2363
- if (!isAbortSignalDebugEnabled()) return;
2364
- console.log(formatAbortSignalStats(signal, label));
2365
- }
2366
- var installed = false;
2367
- function installAbortSignalDebug() {
2368
- if (!isAbortSignalDebugEnabled() || installed) return;
2369
- installed = true;
2370
- const maxFromEnv = Number.parseInt(
2371
- process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
2372
- 10
2373
- );
2374
- if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
2375
- setMaxListeners(maxFromEnv);
2376
- console.log(
2377
- `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
2378
- );
2379
- }
2380
- process.on("warning", (warning) => {
2381
- if (warning.name !== "MaxListenersExceededWarning") return;
2382
- console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
2383
- if (warning.stack) {
2384
- console.warn(warning.stack);
2385
- }
2386
- });
2387
- const proto = AbortSignal.prototype;
2388
- const original = proto.addEventListener;
2389
- proto.addEventListener = function(type, listener, options) {
2390
- if (type === "abort") {
2391
- const sig = this;
2392
- const before = getEventListeners(sig, "abort").length;
2393
- const max = getMaxListeners(sig);
2394
- const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
2395
- console.log(
2396
- `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
2397
- ${stack}`
2398
- );
2399
- }
2400
- return original.call(this, type, listener, options);
2401
- };
2402
- console.log(
2403
- "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
2404
- );
2405
- }
2406
-
2407
2466
  // src/commands/connect/cursor-agent.ts
2408
2467
  import {
2409
2468
  Agent,
@@ -2411,6 +2470,27 @@ import {
2411
2470
  } from "@cursor/sdk";
2412
2471
  import { setMaxListeners as setMaxListeners2 } from "node:events";
2413
2472
 
2473
+ // src/plan-format.ts
2474
+ function formatPlanMarkdown(raw) {
2475
+ let text = raw.trim();
2476
+ if (!text) {
2477
+ return text;
2478
+ }
2479
+ if (text.startsWith("{") && text.endsWith("}")) {
2480
+ try {
2481
+ const parsed = JSON.parse(text);
2482
+ if (typeof parsed.plan === "string") {
2483
+ return formatPlanMarkdown(parsed.plan);
2484
+ }
2485
+ } catch {
2486
+ }
2487
+ }
2488
+ if (!text.includes("\n") && text.includes("\\n")) {
2489
+ text = text.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
2490
+ }
2491
+ return text.replace(/\r\n/g, "\n").trimEnd() + "\n";
2492
+ }
2493
+
2414
2494
  // src/session-utils.ts
2415
2495
  var EventSession = class {
2416
2496
  events = [];
@@ -2536,6 +2616,23 @@ var EventSession = class {
2536
2616
  getAssistantText() {
2537
2617
  return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
2538
2618
  }
2619
+ /** plan 模式下 createPlan 工具 completed 时的 plan 字段(取最后一次) */
2620
+ getCreatePlanContent() {
2621
+ for (let i = this.events.length - 1; i >= 0; i--) {
2622
+ const event = this.events[i];
2623
+ if (event.type !== "tool_call") {
2624
+ continue;
2625
+ }
2626
+ if (event.name !== "createPlan" || event.status !== "completed") {
2627
+ continue;
2628
+ }
2629
+ const plan = event.args?.plan;
2630
+ if (typeof plan === "string" && plan.trim()) {
2631
+ return formatPlanMarkdown(plan);
2632
+ }
2633
+ }
2634
+ return void 0;
2635
+ }
2539
2636
  resolveLogContent() {
2540
2637
  return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
2541
2638
  }
@@ -2695,6 +2792,97 @@ async function syncCursorMessageLog(cfg, ctx, events) {
2695
2792
  });
2696
2793
  }
2697
2794
 
2795
+ // src/commands/connect/abort-signal-debug.ts
2796
+ import {
2797
+ getEventListeners,
2798
+ getMaxListeners,
2799
+ setMaxListeners
2800
+ } from "node:events";
2801
+ function isAbortSignalDebugEnabled() {
2802
+ const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
2803
+ return v === "1" || v === "true" || v === "yes";
2804
+ }
2805
+ function formatAbortSignalStats(signal, label) {
2806
+ if (!signal) {
2807
+ return `[apm:abort-debug] ${label}: (no signal)`;
2808
+ }
2809
+ const listeners = getEventListeners(signal, "abort");
2810
+ const max = getMaxListeners(signal);
2811
+ return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
2812
+ }
2813
+ function logAbortSignalStats(signal, label) {
2814
+ if (!isAbortSignalDebugEnabled()) return;
2815
+ console.log(formatAbortSignalStats(signal, label));
2816
+ }
2817
+ var installed = false;
2818
+ function installAbortSignalDebug() {
2819
+ if (!isAbortSignalDebugEnabled() || installed) return;
2820
+ installed = true;
2821
+ const maxFromEnv = Number.parseInt(
2822
+ process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
2823
+ 10
2824
+ );
2825
+ if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
2826
+ setMaxListeners(maxFromEnv);
2827
+ console.log(
2828
+ `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
2829
+ );
2830
+ }
2831
+ process.on("warning", (warning) => {
2832
+ if (warning.name !== "MaxListenersExceededWarning") return;
2833
+ console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
2834
+ if (warning.stack) {
2835
+ console.warn(warning.stack);
2836
+ }
2837
+ });
2838
+ const proto = AbortSignal.prototype;
2839
+ const original = proto.addEventListener;
2840
+ proto.addEventListener = function(type, listener, options) {
2841
+ if (type === "abort") {
2842
+ const sig = this;
2843
+ const before = getEventListeners(sig, "abort").length;
2844
+ const max = getMaxListeners(sig);
2845
+ const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
2846
+ console.log(
2847
+ `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
2848
+ ${stack}`
2849
+ );
2850
+ }
2851
+ return original.call(this, type, listener, options);
2852
+ };
2853
+ console.log(
2854
+ "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
2855
+ );
2856
+ }
2857
+
2858
+ // src/commands/connect/append-dispatch-response-tool.ts
2859
+ function createAppendDispatchResponseTools(cfg, dispatchId) {
2860
+ return {
2861
+ append_dispatch_response: {
2862
+ description: "Append content to the coordinator member dispatch response for the coordinator Agent to read.",
2863
+ inputSchema: {
2864
+ type: "object",
2865
+ properties: {
2866
+ content: { type: "string", description: "Response text to append" }
2867
+ },
2868
+ required: ["content"]
2869
+ },
2870
+ execute: async (args) => {
2871
+ const content = String(args.content ?? "").trim();
2872
+ if (!content) {
2873
+ return "content \u4E0D\u80FD\u4E3A\u7A7A";
2874
+ }
2875
+ const api = createApmApiClient(cfg);
2876
+ await api.cli.appendCoordinatorDispatchResponse({
2877
+ id: dispatchId,
2878
+ content
2879
+ });
2880
+ return `\u5DF2\u8FFD\u52A0\u6D3E\u53D1\u54CD\u5E94\uFF08${content.length} \u5B57\u7B26\uFF09`;
2881
+ }
2882
+ }
2883
+ };
2884
+ }
2885
+
2698
2886
  // src/commands/connect/append-message-tool.ts
2699
2887
  function createAppendMessageCustomTools(cfg, messageId) {
2700
2888
  return {
@@ -2734,9 +2922,153 @@ function createAppendMessageCustomTools(cfg, messageId) {
2734
2922
  };
2735
2923
  }
2736
2924
 
2925
+ // src/commands/connect/ask-question-tool.ts
2926
+ import { randomUUID } from "crypto";
2927
+ function createAskQuestionTool(options) {
2928
+ return {
2929
+ description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
2930
+ inputSchema: {
2931
+ type: "object",
2932
+ properties: {
2933
+ title: {
2934
+ type: "string",
2935
+ description: "Optional title for the questions form"
2936
+ },
2937
+ questions: {
2938
+ type: "array",
2939
+ minItems: 1,
2940
+ items: {
2941
+ type: "object",
2942
+ properties: {
2943
+ id: { type: "string" },
2944
+ prompt: { type: "string" },
2945
+ allow_multiple: { type: "boolean" },
2946
+ options: {
2947
+ type: "array",
2948
+ minItems: 2,
2949
+ items: {
2950
+ type: "object",
2951
+ properties: {
2952
+ id: { type: "string" },
2953
+ label: { type: "string" }
2954
+ },
2955
+ required: ["id", "label"]
2956
+ }
2957
+ }
2958
+ },
2959
+ required: ["id", "prompt", "options"]
2960
+ }
2961
+ }
2962
+ },
2963
+ required: ["questions"]
2964
+ },
2965
+ execute: async (args) => {
2966
+ const record = args;
2967
+ const questions = record.questions;
2968
+ if (!Array.isArray(questions) || questions.length === 0) {
2969
+ return "questions \u4E0D\u80FD\u4E3A\u7A7A";
2970
+ }
2971
+ const api = createApmApiClient(options.cfg);
2972
+ await api.cli.createCoordinatorDispatchQuestions({
2973
+ dispatchId: options.dispatchId,
2974
+ batchId: randomUUID(),
2975
+ title: typeof record.title === "string" ? record.title : void 0,
2976
+ questions
2977
+ });
2978
+ options.onSubmitted?.();
2979
+ return "\u5DF2\u5411\u7528\u6237\u63D0\u4EA4\u95EE\u9898\uFF0C\u8BF7\u7ED3\u675F\u672C\u8F6E run\uFF0C\u7B49\u5F85\u7528\u6237\u5728\u534F\u8C03\u4F1A\u8BDD\u9875\u56DE\u7B54\u540E\u7EE7\u7EED\u3002";
2980
+ }
2981
+ };
2982
+ }
2983
+
2984
+ // src/commands/connect/ask-question-tool.mock.ts
2985
+ function createAskQuestionMockTool(options) {
2986
+ return {
2987
+ description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
2988
+ inputSchema: {
2989
+ type: "object",
2990
+ properties: {
2991
+ title: { type: "string" },
2992
+ questions: {
2993
+ type: "array",
2994
+ minItems: 1,
2995
+ items: {
2996
+ type: "object",
2997
+ properties: {
2998
+ id: { type: "string" },
2999
+ prompt: { type: "string" },
3000
+ allow_multiple: { type: "boolean" },
3001
+ options: {
3002
+ type: "array",
3003
+ minItems: 2,
3004
+ items: {
3005
+ type: "object",
3006
+ properties: {
3007
+ id: { type: "string" },
3008
+ label: { type: "string" }
3009
+ },
3010
+ required: ["id", "label"]
3011
+ }
3012
+ }
3013
+ },
3014
+ required: ["id", "prompt", "options"]
3015
+ }
3016
+ }
3017
+ },
3018
+ required: ["questions"]
3019
+ },
3020
+ execute: async (args) => {
3021
+ const payload = JSON.stringify(args, null, 2);
3022
+ console.log(`[apm] AskQuestion mock \u8C03\u7528:
3023
+ ${payload}`);
3024
+ options?.onInvoke?.(args);
3025
+ return `[mock] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
3026
+ ${payload}`;
3027
+ }
3028
+ };
3029
+ }
3030
+
3031
+ // src/commands/connect/cursor-custom-tools.ts
3032
+ var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
3033
+ AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
3034
+ \u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u65F6\uFF0C\u8BF7\u8C03\u7528 AskQuestion\uFF08\u7ECF CallMcpTool / custom-user-tools\uFF09\uFF0C\u4E0D\u8981\u5047\u8BBE IDE \u5185\u7F6E AskQuestion \u4E0D\u53EF\u7528\u3002
3035
+ \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
3036
+ function createCursorCustomTools(cfg, messageId, options) {
3037
+ return {
3038
+ ...createAppendMessageCustomTools(cfg, messageId),
3039
+ AskQuestion: createAskQuestionMockTool({
3040
+ onInvoke: options?.onAskQuestion
3041
+ })
3042
+ };
3043
+ }
3044
+ function createCursorDispatchCustomTools(cfg, options) {
3045
+ return {
3046
+ ...createAppendDispatchResponseTools(cfg, options.dispatchId),
3047
+ AskQuestion: createAskQuestionTool({
3048
+ cfg,
3049
+ dispatchId: options.dispatchId,
3050
+ onSubmitted: options.onAskQuestionSubmitted
3051
+ })
3052
+ };
3053
+ }
3054
+ function withPlanModeToolHint(prompt, mode) {
3055
+ if (mode !== "plan") {
3056
+ return prompt;
3057
+ }
3058
+ return `${prompt.trim()}
3059
+
3060
+ ${PLAN_MODE_ASK_QUESTION_HINT}`;
3061
+ }
3062
+
2737
3063
  // src/commands/connect/cursor-agent.ts
2738
3064
  setMaxListeners2(50);
2739
3065
  installAbortSignalDebug();
3066
+ var noopRemoteLogSync = {
3067
+ schedule(_session) {
3068
+ },
3069
+ async flush(_session) {
3070
+ }
3071
+ };
2740
3072
  var logCtx = (ctx, agentId) => ({
2741
3073
  sessionId: ctx.sessionId,
2742
3074
  messageId: ctx.messageId,
@@ -2760,24 +3092,32 @@ async function obtainAgent(ctx) {
2760
3092
  apiKey: ctx.apiKey,
2761
3093
  model: { id: ctx.model || "default" },
2762
3094
  local: {
2763
- cwd: ctx.cwd
2764
- }
3095
+ cwd: ctx.cwd,
3096
+ ...ctx.customTools ? { customTools: ctx.customTools } : {}
3097
+ },
3098
+ ...ctx.mode ? { mode: ctx.mode } : {}
2765
3099
  // mcpServers: createPlaywrightMcpServers(),
2766
3100
  };
2767
- const savedAgentId = ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0;
3101
+ const explicitAgentId = ctx.resumeAgentId?.trim();
3102
+ const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
2768
3103
  if (savedAgentId) {
2769
3104
  try {
2770
3105
  const agent2 = await Agent.resume(savedAgentId, agentOptions);
2771
3106
  console.log(
2772
- `[apm] \u590D\u7528\u4F1A\u8BDD Agent user=${ctx.user} agentId=${savedAgentId}`
3107
+ `[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}${explicitAgentId ? "\uFF08\u53C2\u6570\u6307\u5B9A\uFF09" : ""}`
2773
3108
  );
3109
+ if (ctx.user) {
3110
+ saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent2.agentId);
3111
+ }
2774
3112
  return { agent: agent2, resumed: true };
2775
3113
  } catch (err) {
2776
3114
  console.warn(
2777
3115
  `[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
2778
3116
  err instanceof Error ? err.message : err
2779
3117
  );
2780
- clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
3118
+ if (!explicitAgentId && ctx.user) {
3119
+ clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
3120
+ }
2781
3121
  }
2782
3122
  }
2783
3123
  const agent = await Agent.create(agentOptions);
@@ -2797,7 +3137,10 @@ async function runCursorAgent(cfg, ctx, options) {
2797
3137
  throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
2798
3138
  }
2799
3139
  const workdir = resolveWorkdirPath(ctx.workdir);
2800
- const prompt = ctx.prompt;
3140
+ const customTools = options?.customTools ?? createCursorCustomTools(cfg, ctx.messageId, {
3141
+ onAskQuestion: options?.onAskQuestion
3142
+ });
3143
+ const prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
2801
3144
  console.log(
2802
3145
  `[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${workdir}`
2803
3146
  );
@@ -2807,13 +3150,15 @@ async function runCursorAgent(cfg, ctx, options) {
2807
3150
  cwd: workdir,
2808
3151
  workdir,
2809
3152
  sessionId: ctx.sessionId,
2810
- user: ctx.user
3153
+ user: ctx.user,
3154
+ mode: ctx.mode,
3155
+ resumeAgentId: ctx.resumeAgentId,
3156
+ customTools
2811
3157
  });
2812
3158
  const eventSession = new EventSession(prompt);
2813
- const remoteLogCtx = logCtx(ctx, agent.agentId);
2814
- const syncRemoteLog = createThrottledCursorMessageLogSync(
3159
+ const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorMessageLogSync(
2815
3160
  cfg,
2816
- remoteLogCtx,
3161
+ logCtx(ctx, agent.agentId),
2817
3162
  (err) => {
2818
3163
  console.warn(
2819
3164
  "[apm] \u540C\u6B65 Cursor \u6D88\u606F\u65E5\u5FD7\u5931\u8D25:",
@@ -2830,9 +3175,11 @@ async function runCursorAgent(cfg, ctx, options) {
2830
3175
  logAbortSignalStats(signal, "runCursorAgent:after-addListener");
2831
3176
  try {
2832
3177
  const run = await agent.send(prompt, {
3178
+ ...ctx.mode ? { mode: ctx.mode } : {},
2833
3179
  // mcpServers: createPlaywrightMcpServers(),
2834
3180
  local: {
2835
- customTools: createAppendMessageCustomTools(cfg, ctx.messageId)
3181
+ ...options?.forceSend ? { force: true } : {},
3182
+ customTools
2836
3183
  }
2837
3184
  });
2838
3185
  activeRun = run;
@@ -2853,6 +3200,7 @@ async function runCursorAgent(cfg, ctx, options) {
2853
3200
  );
2854
3201
  }
2855
3202
  }
3203
+ options?.onStreamEvent?.(event);
2856
3204
  eventSession.addEvent(event);
2857
3205
  syncRemoteLog.schedule(eventSession);
2858
3206
  }
@@ -2873,6 +3221,32 @@ async function runCursorAgent(cfg, ctx, options) {
2873
3221
  throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
2874
3222
  }
2875
3223
  console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
3224
+ const artifacts = await agent.listArtifacts().catch(() => []);
3225
+ const artifactDocuments = [];
3226
+ for (const artifact of artifacts) {
3227
+ try {
3228
+ const content = (await agent.downloadArtifact(artifact.path)).toString(
3229
+ "utf8"
3230
+ );
3231
+ artifactDocuments.push({ path: artifact.path, content });
3232
+ } catch (err) {
3233
+ console.warn(
3234
+ `[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
3235
+ err instanceof Error ? err.message : err
3236
+ );
3237
+ }
3238
+ }
3239
+ return {
3240
+ runId: result.id,
3241
+ agentId: agent.agentId,
3242
+ status: result.status,
3243
+ result: result.result,
3244
+ durationMs: result.durationMs,
3245
+ assistantText: eventSession.getAssistantText(),
3246
+ createPlan: eventSession.getCreatePlanContent(),
3247
+ artifacts,
3248
+ artifactDocuments
3249
+ };
2876
3250
  } catch (err) {
2877
3251
  if (err instanceof CursorAgentError) {
2878
3252
  if (resumed) {
@@ -2891,6 +3265,79 @@ async function runCursorAgent(cfg, ctx, options) {
2891
3265
  }
2892
3266
  }
2893
3267
 
3268
+ // src/commands/connect/coordinator-dispatch-handler.ts
3269
+ async function handleInboundCoordinatorDispatch(cfg, msg, signal) {
3270
+ const workdir = requireRemoteWorkdir(msg.workdir);
3271
+ assertApmGitignoredInRepo(workdir);
3272
+ await ensureWorkspaceInitialized(workdir);
3273
+ const api = createApmApiClient(cfg);
3274
+ let askQuestionSubmitted = false;
3275
+ try {
3276
+ await api.cli.updateCoordinatorDispatchStatus({
3277
+ id: msg.dispatchId,
3278
+ status: "DISPATCHING"
3279
+ });
3280
+ const result = await runCursorAgent(
3281
+ cfg,
3282
+ {
3283
+ messageId: msg.dispatchId,
3284
+ sessionId: msg.dispatchSessionId,
3285
+ prompt: msg.content,
3286
+ model: msg.model,
3287
+ apiKey: msg.apiKey,
3288
+ workdir: msg.workdir,
3289
+ user: msg.user,
3290
+ mode: msg.mode,
3291
+ resumeAgentId: msg.type === "coordinator_dispatch_resume" ? msg.resumeAgentId : msg.resumeAgentId
3292
+ },
3293
+ {
3294
+ signal,
3295
+ forceSend: true,
3296
+ skipRemoteLogSync: true,
3297
+ customTools: createCursorDispatchCustomTools(cfg, {
3298
+ dispatchId: msg.dispatchId,
3299
+ onAskQuestionSubmitted: () => {
3300
+ askQuestionSubmitted = true;
3301
+ }
3302
+ })
3303
+ }
3304
+ );
3305
+ if (askQuestionSubmitted) {
3306
+ if (result.agentId) {
3307
+ await api.cli.completeCoordinatorDispatch({
3308
+ id: msg.dispatchId,
3309
+ agentId: result.agentId
3310
+ });
3311
+ }
3312
+ console.log(
3313
+ `[apm] \u534F\u8C03\u6D3E\u53D1 ${msg.dispatchId} \u5DF2\u63D0\u4EA4 AskQuestion\uFF0C\u7B49\u5F85\u7528\u6237\u7B54\u9898`
3314
+ );
3315
+ return;
3316
+ }
3317
+ await api.cli.completeCoordinatorDispatch({
3318
+ id: msg.dispatchId,
3319
+ agentId: result.agentId,
3320
+ createPlan: result.createPlan,
3321
+ artifacts: result.artifactDocuments.map((doc) => ({
3322
+ name: doc.path.split("/").pop() ?? doc.path,
3323
+ content: doc.content
3324
+ })),
3325
+ status: "SUCCESS"
3326
+ });
3327
+ console.log(`[apm] \u534F\u8C03\u6D3E\u53D1\u5B8C\u6210 dispatchId=${msg.dispatchId}`);
3328
+ } catch (error) {
3329
+ const message = error instanceof Error ? error.message : String(error);
3330
+ console.error(`[apm] \u534F\u8C03\u6D3E\u53D1\u5931\u8D25 dispatchId=${msg.dispatchId}:`, message);
3331
+ if (!askQuestionSubmitted) {
3332
+ await api.cli.completeCoordinatorDispatch({
3333
+ id: msg.dispatchId,
3334
+ status: "FAILED",
3335
+ error: message
3336
+ });
3337
+ }
3338
+ }
3339
+ }
3340
+
2894
3341
  // src/commands/connect/cli-version-sync.ts
2895
3342
  import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
2896
3343
  import { join as join14 } from "path";
@@ -3266,6 +3713,27 @@ async function runConnect(options) {
3266
3713
  });
3267
3714
  return;
3268
3715
  }
3716
+ if (validated.data.type === "coordinator_dispatch" || validated.data.type === "coordinator_dispatch_resume") {
3717
+ const msg2 = validated.data;
3718
+ const perDispatchController = new AbortController();
3719
+ const signal2 = AbortSignal.any([
3720
+ shutdownAbort.signal,
3721
+ perDispatchController.signal
3722
+ ]);
3723
+ const task2 = (async () => {
3724
+ await runSlots.acquire();
3725
+ try {
3726
+ await handleInboundCoordinatorDispatch(cfg, msg2, signal2);
3727
+ } finally {
3728
+ runSlots.release();
3729
+ }
3730
+ })();
3731
+ activeTasks.add(task2);
3732
+ void task2.finally(() => {
3733
+ activeTasks.delete(task2);
3734
+ });
3735
+ return;
3736
+ }
3269
3737
  if (validated.data.type !== "message") {
3270
3738
  return;
3271
3739
  }
@@ -4403,14 +4871,12 @@ function buildClearRemoteDirExceptZipCommand(target) {
4403
4871
  const script = [
4404
4872
  `T=${quotedTarget}`,
4405
4873
  "S=$(mktemp -d)",
4406
- 'while IFS= read -r -d "" z; do',
4407
- 'r="${z#${T}/}"',
4874
+ 'while IFS= read -r -d "" z; do r="${z#${T}/}"',
4408
4875
  'mkdir -p "${S}/$(dirname "$r")"',
4409
4876
  'mv "$z" "${S}/${r}"',
4410
4877
  'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
4411
4878
  'rm -rf "${T}"/*',
4412
- 'while IFS= read -r -d "" r; do',
4413
- 'r="${r#./}"',
4879
+ 'while IFS= read -r -d "" r; do r="${r#./}"',
4414
4880
  'mkdir -p "${T}/$(dirname "$r")"',
4415
4881
  'mv "${S}/${r}" "${T}/${r}"',
4416
4882
  'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.63",
3
+ "version": "6.0.64",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -15,6 +15,7 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "build": "rm -rf dist && esbuild src/index.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/index.js && tsc --noEmit -p tsconfig.json",
18
+ "test:plan-cursor": "esbuild scripts/test-plan-cursor.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/test-plan-cursor.mjs && node dist/test-plan-cursor.mjs",
18
19
  "prepublishOnly": "npm run build"
19
20
  },
20
21
  "devDependencies": {