ai-project-manage-cli 7.1.1 → 7.1.3

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
@@ -449,6 +449,22 @@ var requestConfig = {
449
449
  method: "PUT",
450
450
  path: "/cli/webide/message-logs"
451
451
  }),
452
+ webideReplaceAssumptions: defineEndpoint({
453
+ method: "PUT",
454
+ path: "/cli/webide/assumptions"
455
+ }),
456
+ webideListAssumptions: defineEndpoint({
457
+ method: "GET",
458
+ path: "/cli/webide/assumptions"
459
+ }),
460
+ webideRecommendPlan: defineEndpoint({
461
+ method: "PUT",
462
+ path: "/cli/webide/plan-recommendation"
463
+ }),
464
+ webideUpsertPlan: defineEndpoint({
465
+ method: "PUT",
466
+ path: "/cli/webide/plan"
467
+ }),
452
468
  branchBaseline: defineEndpoint({
453
469
  method: "GET",
454
470
  path: "/cli/tasks/branch-baseline"
@@ -2719,13 +2735,32 @@ function validateSessionCacheCleanupPush(o) {
2719
2735
  }
2720
2736
  };
2721
2737
  }
2738
+ function validateWebIdeCacheCleanupPush(o) {
2739
+ if (o.type !== "webide-cache-cleanup") {
2740
+ return { ok: false, reason: "\u671F\u671B webide-cache-cleanup" };
2741
+ }
2742
+ if (!nonEmptyString(o.taskId)) {
2743
+ return { ok: false, reason: "webide-cache-cleanup \u7F3A\u5C11 taskId" };
2744
+ }
2745
+ if (!nonEmptyString(o.workdir)) {
2746
+ return { ok: false, reason: "webide-cache-cleanup \u7F3A\u5C11 workdir" };
2747
+ }
2748
+ return {
2749
+ ok: true,
2750
+ data: {
2751
+ type: "webide-cache-cleanup",
2752
+ taskId: o.taskId.trim(),
2753
+ workdir: o.workdir.trim()
2754
+ }
2755
+ };
2756
+ }
2722
2757
  function validateAgentWsMessage(value, kind) {
2723
2758
  if (typeof value !== "object" || value === null) {
2724
2759
  return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
2725
2760
  }
2726
2761
  const o = value;
2727
2762
  const type = o.type;
2728
- if (type !== "heartbeat" && type !== "message" && type !== "webide-message" && type !== "cancel" && type !== "deploy" && type !== "session-cache-cleanup") {
2763
+ if (type !== "heartbeat" && type !== "message" && type !== "webide-message" && type !== "cancel" && type !== "deploy" && type !== "session-cache-cleanup" && type !== "webide-cache-cleanup") {
2729
2764
  return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
2730
2765
  }
2731
2766
  if (kind === "heartbeat" || type === "heartbeat") {
@@ -2740,6 +2775,9 @@ function validateAgentWsMessage(value, kind) {
2740
2775
  if (type === "session-cache-cleanup") {
2741
2776
  return validateSessionCacheCleanupPush(o);
2742
2777
  }
2778
+ if (type === "webide-cache-cleanup") {
2779
+ return validateWebIdeCacheCleanupPush(o);
2780
+ }
2743
2781
  if (type === "webide-message") {
2744
2782
  return validateWebIdeMessagePush(o);
2745
2783
  }
@@ -3194,14 +3232,14 @@ var MinioClient = class {
3194
3232
  async deleteObjectsByPrefix(bucket, prefix) {
3195
3233
  const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
3196
3234
  const keys = [];
3197
- await new Promise((resolve6, reject) => {
3235
+ await new Promise((resolve7, reject) => {
3198
3236
  objectsStream.on("data", (obj) => {
3199
3237
  if (obj.name) {
3200
3238
  keys.push(obj.name);
3201
3239
  }
3202
3240
  });
3203
3241
  objectsStream.on("error", reject);
3204
- objectsStream.on("end", resolve6);
3242
+ objectsStream.on("end", resolve7);
3205
3243
  });
3206
3244
  const chunkSize = 500;
3207
3245
  for (let i = 0; i < keys.length; i += chunkSize) {
@@ -4661,7 +4699,7 @@ function buildSftpConnectOptions(settings) {
4661
4699
  };
4662
4700
  }
4663
4701
  async function sleep(ms) {
4664
- await new Promise((resolve6) => setTimeout(resolve6, ms));
4702
+ await new Promise((resolve7) => setTimeout(resolve7, ms));
4665
4703
  }
4666
4704
  async function uploadZipWithRetry(settings, localZip, remoteZipPath) {
4667
4705
  let lastError;
@@ -4705,7 +4743,7 @@ async function ensureRemoteDir(sftp, dir) {
4705
4743
  }
4706
4744
  }
4707
4745
  function execCommand(client, command) {
4708
- return new Promise((resolve6, reject) => {
4746
+ return new Promise((resolve7, reject) => {
4709
4747
  client.exec(command, (err, stream) => {
4710
4748
  if (err) return reject(err);
4711
4749
  let stdout = "";
@@ -4715,7 +4753,7 @@ function execCommand(client, command) {
4715
4753
  reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
4716
4754
  return;
4717
4755
  }
4718
- resolve6(stdout);
4756
+ resolve7(stdout);
4719
4757
  }).on("data", (data) => {
4720
4758
  stdout += data.toString();
4721
4759
  });
@@ -5190,8 +5228,8 @@ function springbootOutputIndicatesSuccess(action, combined) {
5190
5228
  async function connectSsh(config) {
5191
5229
  const client = new Client2();
5192
5230
  log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
5193
- await new Promise((resolve6, reject) => {
5194
- client.on("ready", () => resolve6()).on("error", (err) => reject(err)).connect({
5231
+ await new Promise((resolve7, reject) => {
5232
+ client.on("ready", () => resolve7()).on("error", (err) => reject(err)).connect({
5195
5233
  host: config.host,
5196
5234
  port: config.port,
5197
5235
  username: config.username,
@@ -5259,7 +5297,7 @@ async function runRemoteCommand(client, command, options) {
5259
5297
  const check = options?.check ?? true;
5260
5298
  const stream = options?.stream ?? false;
5261
5299
  const label = options?.label ?? "\u8FDC\u7A0B\u547D\u4EE4";
5262
- return new Promise((resolve6, reject) => {
5300
+ return new Promise((resolve7, reject) => {
5263
5301
  client.exec(command, (err, execStream) => {
5264
5302
  if (err) {
5265
5303
  reject(err);
@@ -5289,7 +5327,7 @@ ${errText}`.trim();
5289
5327
  \u8F93\u51FA: ${combined}` : "")
5290
5328
  );
5291
5329
  }
5292
- resolve6({ exitCode: code, out: out.trim(), err: errText.trim() });
5330
+ resolve7({ exitCode: code, out: out.trim(), err: errText.trim() });
5293
5331
  });
5294
5332
  });
5295
5333
  });
@@ -6164,6 +6202,24 @@ function cleanSessionWorkspaceCache(sessionId, workdir) {
6164
6202
  clearPreStepCacheForSession(trimmedSessionId, trimmedWorkdir);
6165
6203
  }
6166
6204
 
6205
+ // src/commands/clean-webide-cache.ts
6206
+ import { existsSync as existsSync22, rmSync as rmSync5 } from "node:fs";
6207
+ import { resolve as resolve5 } from "node:path";
6208
+ function cleanWebIdeWorkspaceCache(taskId, workdir) {
6209
+ const trimmedTaskId = taskId.trim();
6210
+ const trimmedWorkdir = workdir.trim();
6211
+ if (!trimmedTaskId || !trimmedWorkdir) {
6212
+ return;
6213
+ }
6214
+ const dir = resolve5(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
6215
+ if (existsSync22(dir)) {
6216
+ rmSync5(dir, { recursive: true, force: true });
6217
+ console.log(`[apm] \u5DF2\u6E05\u7406 WebIDE \u7F13\u5B58 ${dir}`);
6218
+ } else {
6219
+ console.log(`[apm] WebIDE \u7F13\u5B58\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7 ${dir}`);
6220
+ }
6221
+ }
6222
+
6167
6223
  // src/commands/connect/abort-signal-debug.ts
6168
6224
  import {
6169
6225
  getEventListeners,
@@ -6256,6 +6312,16 @@ function formatPlanMarkdown(raw) {
6256
6312
  }
6257
6313
 
6258
6314
  // src/session-utils.ts
6315
+ function serializeTokenUsage(usage) {
6316
+ return {
6317
+ inputTokens: usage.inputTokens,
6318
+ outputTokens: usage.outputTokens,
6319
+ cacheReadTokens: usage.cacheReadTokens,
6320
+ cacheWriteTokens: usage.cacheWriteTokens,
6321
+ totalTokens: usage.totalTokens,
6322
+ ...usage.reasoningTokens != null ? { reasoningTokens: usage.reasoningTokens } : {}
6323
+ };
6324
+ }
6259
6325
  var EventSession = class {
6260
6326
  events = [];
6261
6327
  dirtyIndices = /* @__PURE__ */ new Set();
@@ -6294,7 +6360,7 @@ var EventSession = class {
6294
6360
  if (formatedEvent.type === "status") {
6295
6361
  return;
6296
6362
  }
6297
- if (formatedEvent.type === "request") {
6363
+ if (formatedEvent.type === "request" || formatedEvent.type === "usage") {
6298
6364
  this.events.push(formatedEvent);
6299
6365
  this.markDirty(this.events.length - 1);
6300
6366
  return;
@@ -6318,6 +6384,16 @@ var EventSession = class {
6318
6384
  this.events.push(formatedEvent);
6319
6385
  this.markDirty(this.events.length - 1);
6320
6386
  }
6387
+ /** 写入 run.wait() 返回的累计 TokenUsage(整次 run 汇总) */
6388
+ addRunUsage(usage, options) {
6389
+ this.events.push({
6390
+ type: "usage",
6391
+ scope: "run",
6392
+ usage: serializeTokenUsage(usage),
6393
+ ...options?.durationMs != null ? { durationMs: options.durationMs } : {}
6394
+ });
6395
+ this.markDirty(this.events.length - 1);
6396
+ }
6321
6397
  formatEvent(event) {
6322
6398
  switch (event.type) {
6323
6399
  case "assistant": {
@@ -6352,6 +6428,12 @@ var EventSession = class {
6352
6428
  status: event.status,
6353
6429
  text: event.text
6354
6430
  };
6431
+ case "usage":
6432
+ return {
6433
+ type: "usage",
6434
+ scope: "turn",
6435
+ usage: serializeTokenUsage(event.usage)
6436
+ };
6355
6437
  case "request":
6356
6438
  return {
6357
6439
  ...event,
@@ -6426,6 +6508,16 @@ ${String(event.content ?? "")}
6426
6508
  if (type === "tool_call") {
6427
6509
  return "````toolcall\n" + JSON.stringify(event, null, 2) + "\n````\n";
6428
6510
  }
6511
+ if (type === "usage") {
6512
+ const usage = event.usage;
6513
+ const scope = event.scope === "run" ? "\u7D2F\u8BA1" : "\u672C\u8F6E";
6514
+ return `## Token \u4F7F\u7528\u91CF\uFF08${scope}\uFF09
6515
+
6516
+ \`\`\`json
6517
+ ${JSON.stringify(usage ?? event, null, 2)}
6518
+ \`\`\`
6519
+ `;
6520
+ }
6429
6521
  return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type}
6430
6522
 
6431
6523
  \`\`\`json
@@ -6434,13 +6526,13 @@ ${JSON.stringify(event, null, 2)}
6434
6526
  }
6435
6527
 
6436
6528
  // src/commands/connect/agent-session-registry.ts
6437
- import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "node:fs";
6438
- import { dirname as dirname6, resolve as resolve5 } from "node:path";
6529
+ import { existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "node:fs";
6530
+ import { dirname as dirname6, resolve as resolve6 } from "node:path";
6439
6531
  function registryPath(workdir, sessionId) {
6440
- return resolve5(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
6532
+ return resolve6(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
6441
6533
  }
6442
6534
  function readRegistry(path19) {
6443
- if (!existsSync22(path19)) {
6535
+ if (!existsSync23(path19)) {
6444
6536
  return {};
6445
6537
  }
6446
6538
  try {
@@ -6600,51 +6692,124 @@ function createAppendMessageCustomTools(cfg, messageId, appendContent) {
6600
6692
  }
6601
6693
 
6602
6694
  // src/commands/connect/ask-question-tool.ts
6603
- function createAskQuestionMockTool(options) {
6695
+ var INPUT_SCHEMA = {
6696
+ type: "object",
6697
+ properties: {
6698
+ title: {
6699
+ type: "string",
6700
+ description: "Optional title for the questions form"
6701
+ },
6702
+ questions: {
6703
+ type: "array",
6704
+ minItems: 1,
6705
+ items: {
6706
+ type: "object",
6707
+ properties: {
6708
+ id: { type: "string" },
6709
+ prompt: { type: "string" },
6710
+ allow_multiple: { type: "boolean" },
6711
+ options: {
6712
+ type: "array",
6713
+ minItems: 2,
6714
+ items: {
6715
+ type: "object",
6716
+ properties: {
6717
+ id: { type: "string" },
6718
+ label: { type: "string" }
6719
+ },
6720
+ required: ["id", "label"]
6721
+ }
6722
+ }
6723
+ },
6724
+ required: ["id", "prompt", "options"]
6725
+ }
6726
+ }
6727
+ },
6728
+ required: ["questions"]
6729
+ };
6730
+ function createAskQuestionTool(options) {
6604
6731
  return {
6605
6732
  description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
6733
+ inputSchema: INPUT_SCHEMA,
6734
+ execute: async (args) => {
6735
+ options?.onInvoke?.(args);
6736
+ if (options?.execute) {
6737
+ return options.execute(args);
6738
+ }
6739
+ const payload = JSON.stringify(args, null, 2);
6740
+ console.log(`[apm] AskQuestion\uFF08\u672C\u5730\u5360\u4F4D\uFF0C\u672A\u7B49\u5F85\u7528\u6237\uFF09:
6741
+ ${payload}`);
6742
+ return `[local] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
6743
+ ${payload}`;
6744
+ }
6745
+ };
6746
+ }
6747
+
6748
+ // src/commands/connect/webide-plan-tools.ts
6749
+ function asString(value) {
6750
+ return typeof value === "string" ? value.trim() : "";
6751
+ }
6752
+ function createRecommendPlanTool(options) {
6753
+ const { cfg, taskId } = options;
6754
+ return {
6755
+ description: "Recommend whether the human should write an implementation plan or skip it. Does not change workflow phase; the human decides in the WebIDE UI.",
6606
6756
  inputSchema: {
6607
6757
  type: "object",
6608
6758
  properties: {
6609
- title: {
6759
+ recommendation: {
6610
6760
  type: "string",
6611
- description: "Optional title for the questions form"
6761
+ enum: ["write", "skip", "unknown"],
6762
+ description: "write = suggest generating a plan; skip = plan optional/unnecessary; unknown = unsure"
6612
6763
  },
6613
- questions: {
6614
- type: "array",
6615
- minItems: 1,
6616
- items: {
6617
- type: "object",
6618
- properties: {
6619
- id: { type: "string" },
6620
- prompt: { type: "string" },
6621
- allow_multiple: { type: "boolean" },
6622
- options: {
6623
- type: "array",
6624
- minItems: 2,
6625
- items: {
6626
- type: "object",
6627
- properties: {
6628
- id: { type: "string" },
6629
- label: { type: "string" }
6630
- },
6631
- required: ["id", "label"]
6632
- }
6633
- }
6634
- },
6635
- required: ["id", "prompt", "options"]
6636
- }
6764
+ reason: {
6765
+ type: "string",
6766
+ description: "Short Chinese reason for the recommendation"
6637
6767
  }
6638
6768
  },
6639
- required: ["questions"]
6769
+ required: ["recommendation"]
6640
6770
  },
6641
6771
  execute: async (args) => {
6642
- const payload = JSON.stringify(args, null, 2);
6643
- console.log(`[apm] AskQuestion mock \u8C03\u7528:
6644
- ${payload}`);
6645
- options?.onInvoke?.(args);
6646
- 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:
6647
- ${payload}`;
6772
+ const raw = asString(args.recommendation);
6773
+ const recommendation = raw === "write" || raw === "skip" || raw === "unknown" ? raw : "unknown";
6774
+ const reason = asString(args.reason) || void 0;
6775
+ const api = createApmApiClient(cfg);
6776
+ await api.cli.webideRecommendPlan({
6777
+ taskId,
6778
+ recommendation,
6779
+ reason
6780
+ });
6781
+ console.log(
6782
+ `[apm] RecommendPlan taskId=${taskId} recommendation=${recommendation}`
6783
+ );
6784
+ return JSON.stringify({ ok: true, recommendation, reason }, null, 2);
6785
+ }
6786
+ };
6787
+ }
6788
+ function createUpsertWebIdePlanTool(options) {
6789
+ const { cfg, taskId } = options;
6790
+ return {
6791
+ description: "Persist the implementation plan markdown for this WebIDE task and mark the plan as ready for human confirmation. Call once with the full plan document.",
6792
+ inputSchema: {
6793
+ type: "object",
6794
+ properties: {
6795
+ content: {
6796
+ type: "string",
6797
+ description: "Full implementation plan in Markdown (Chinese)"
6798
+ }
6799
+ },
6800
+ required: ["content"]
6801
+ },
6802
+ execute: async (args) => {
6803
+ const content = asString(args.content);
6804
+ if (!content) {
6805
+ throw new Error("UpsertWebIdePlan \u7F3A\u5C11 content");
6806
+ }
6807
+ const api = createApmApiClient(cfg);
6808
+ await api.cli.webideUpsertPlan({ taskId, content });
6809
+ console.log(
6810
+ `[apm] UpsertWebIdePlan taskId=${taskId} chars=${content.length}`
6811
+ );
6812
+ return JSON.stringify({ ok: true, chars: content.length }, null, 2);
6648
6813
  }
6649
6814
  };
6650
6815
  }
@@ -6655,16 +6820,28 @@ AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u
6655
6820
  \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
6656
6821
  \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
6657
6822
  function createCursorCustomTools(cfg, messageId, options) {
6658
- return {
6823
+ const tools = {
6659
6824
  ...createAppendMessageCustomTools(
6660
6825
  cfg,
6661
6826
  messageId,
6662
6827
  options?.appendMessageContent
6663
6828
  ),
6664
- AskQuestion: createAskQuestionMockTool({
6665
- onInvoke: options?.onAskQuestion
6829
+ AskQuestion: createAskQuestionTool({
6830
+ onInvoke: options?.onAskQuestion,
6831
+ execute: options?.askQuestionExecute
6666
6832
  })
6667
6833
  };
6834
+ if (options?.enableWebIdePlanTools && options.taskId) {
6835
+ tools.RecommendPlan = createRecommendPlanTool({
6836
+ cfg,
6837
+ taskId: options.taskId
6838
+ });
6839
+ tools.UpsertWebIdePlan = createUpsertWebIdePlanTool({
6840
+ cfg,
6841
+ taskId: options.taskId
6842
+ });
6843
+ }
6844
+ return tools;
6668
6845
  }
6669
6846
  function withPlanModeToolHint(prompt, mode) {
6670
6847
  if (mode !== "plan") {
@@ -6676,7 +6853,7 @@ ${PLAN_MODE_ASK_QUESTION_HINT}`;
6676
6853
  }
6677
6854
 
6678
6855
  // src/commands/connect/cursor-agent.ts
6679
- setMaxListeners2(50);
6856
+ setMaxListeners2(100);
6680
6857
  installAbortSignalDebug();
6681
6858
  var noopRemoteLogSync = {
6682
6859
  schedule(_session) {
@@ -6754,7 +6931,10 @@ async function runCursorAgent(cfg, ctx, options) {
6754
6931
  const workdir = resolveWorkdirPath(ctx.workdir);
6755
6932
  const customTools = createCursorCustomTools(cfg, ctx.messageId, {
6756
6933
  onAskQuestion: options?.onAskQuestion,
6757
- appendMessageContent: options?.appendMessageContent
6934
+ appendMessageContent: options?.appendMessageContent,
6935
+ askQuestionExecute: options?.askQuestionExecute,
6936
+ enableWebIdePlanTools: options?.enableWebIdePlanTools,
6937
+ taskId: options?.taskId
6758
6938
  });
6759
6939
  const prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
6760
6940
  console.log(
@@ -6823,6 +7003,15 @@ async function runCursorAgent(cfg, ctx, options) {
6823
7003
  }
6824
7004
  await syncRemoteLog.flush(eventSession);
6825
7005
  const result = await run.wait();
7006
+ if (result.usage) {
7007
+ eventSession.addRunUsage(result.usage, {
7008
+ durationMs: result.durationMs
7009
+ });
7010
+ await syncRemoteLog.flush(eventSession);
7011
+ console.log(
7012
+ `[apm] Cursor usage total=${result.usage.totalTokens} in=${result.usage.inputTokens} out=${result.usage.outputTokens} cache=${result.usage.cacheReadTokens}/${result.usage.cacheWriteTokens}`
7013
+ );
7014
+ }
6826
7015
  if (result.status === "error") {
6827
7016
  const failureMessage = formatCursorRunFailure(result.id, {
6828
7017
  statusError: lastRunErrorStatus,
@@ -6859,6 +7048,7 @@ async function runCursorAgent(cfg, ctx, options) {
6859
7048
  status: result.status,
6860
7049
  result: result.result,
6861
7050
  durationMs: result.durationMs,
7051
+ usage: result.usage,
6862
7052
  assistantText: eventSession.getAssistantText(),
6863
7053
  createPlan: eventSession.getCreatePlanContent(),
6864
7054
  artifacts,
@@ -6919,7 +7109,7 @@ async function ensureMessageHasReply(cfg, sessionId, messageId, fallback) {
6919
7109
  }
6920
7110
 
6921
7111
  // src/commands/connect/cli-version-sync.ts
6922
- import { existsSync as existsSync23, readFileSync as readFileSync17, writeFileSync as writeFileSync15 } from "fs";
7112
+ import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync15 } from "fs";
6923
7113
  import { join as join18 } from "path";
6924
7114
  var CLI_VERSION_FILE = ".cli-version.json";
6925
7115
  function manifestPath(apmDir) {
@@ -6927,7 +7117,7 @@ function manifestPath(apmDir) {
6927
7117
  }
6928
7118
  function loadManifest4(apmDir) {
6929
7119
  const path19 = toFsPath(manifestPath(apmDir));
6930
- if (!existsSync23(path19)) {
7120
+ if (!existsSync24(path19)) {
6931
7121
  return null;
6932
7122
  }
6933
7123
  try {
@@ -6997,7 +7187,7 @@ function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT, options = {})
6997
7187
  );
6998
7188
  }
6999
7189
  const queuedAt = Date.now();
7000
- return new Promise((resolve6) => {
7190
+ return new Promise((resolve7) => {
7001
7191
  waiters.push(() => {
7002
7192
  active += 1;
7003
7193
  const waitedMs = Date.now() - queuedAt;
@@ -7008,7 +7198,7 @@ function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT, options = {})
7008
7198
  )}s\uFF0C\u5F53\u524D\u5360\u7528 ${active}/${maxConcurrent}`
7009
7199
  );
7010
7200
  }
7011
- resolve6();
7201
+ resolve7();
7012
7202
  });
7013
7203
  });
7014
7204
  };
@@ -7038,19 +7228,22 @@ function spawnWebIdeMessageWorker(cfg, msg) {
7038
7228
  workerData: { cfg, msg }
7039
7229
  });
7040
7230
  let resolveDone;
7041
- const done = new Promise((resolve6) => {
7042
- resolveDone = resolve6;
7231
+ const done = new Promise((resolve7) => {
7232
+ resolveDone = resolve7;
7043
7233
  });
7044
- worker.on("message", (m) => {
7045
- if (m?.type === "done" || m?.type === "error") {
7046
- if (m.type === "error") {
7047
- console.error(
7048
- `[apm] webide worker error messageId=${m.messageId}: ${m.error}`
7049
- );
7234
+ worker.on(
7235
+ "message",
7236
+ (m) => {
7237
+ if (m?.type === "done" || m?.type === "error") {
7238
+ if (m.type === "error") {
7239
+ console.error(
7240
+ `[apm] webide worker error messageId=${m.messageId}: ${m.error}`
7241
+ );
7242
+ }
7243
+ void worker.terminate().finally(() => resolveDone());
7050
7244
  }
7051
- void worker.terminate().finally(() => resolveDone());
7052
7245
  }
7053
- });
7246
+ );
7054
7247
  worker.on("error", (err) => {
7055
7248
  console.error("[apm] webide worker crashed:", err);
7056
7249
  resolveDone();
@@ -7061,7 +7254,6 @@ function spawnWebIdeMessageWorker(cfg, msg) {
7061
7254
  }
7062
7255
  resolveDone();
7063
7256
  });
7064
- worker.postMessage({ type: "run", cfg, msg });
7065
7257
  return {
7066
7258
  cancel(messageId) {
7067
7259
  worker.postMessage({ type: "cancel", messageId });
@@ -7228,11 +7420,11 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
7228
7420
  }
7229
7421
  function interruptibleSleep(ms, signal) {
7230
7422
  if (signal.aborted) return Promise.resolve();
7231
- return new Promise((resolve6) => {
7232
- const timer = setTimeout(resolve6, ms);
7423
+ return new Promise((resolve7) => {
7424
+ const timer = setTimeout(resolve7, ms);
7233
7425
  const onAbort = () => {
7234
7426
  clearTimeout(timer);
7235
- resolve6();
7427
+ resolve7();
7236
7428
  };
7237
7429
  signal.addEventListener("abort", onAbort, { once: true });
7238
7430
  });
@@ -7324,6 +7516,18 @@ function attachWsHandlers(ws, ctx, onOpen) {
7324
7516
  });
7325
7517
  return;
7326
7518
  }
7519
+ if (validated.data.type === "webide-cache-cleanup") {
7520
+ const msg2 = validated.data;
7521
+ const task2 = (async () => {
7522
+ const workdir = requireRemoteWorkdir(msg2.workdir);
7523
+ cleanWebIdeWorkspaceCache(msg2.taskId, workdir);
7524
+ })();
7525
+ activeTasks.add(task2);
7526
+ void task2.finally(() => {
7527
+ activeTasks.delete(task2);
7528
+ });
7529
+ return;
7530
+ }
7327
7531
  if (validated.data.type === "webide-message") {
7328
7532
  const msg2 = validated.data;
7329
7533
  const perMessageController2 = new AbortController();
@@ -7412,7 +7616,7 @@ function attachWsHandlers(ws, ctx, onOpen) {
7412
7616
  });
7413
7617
  }
7414
7618
  function connectOnce(url, ctx, connectionAbort, onConnected) {
7415
- return new Promise((resolve6, reject) => {
7619
+ return new Promise((resolve7, reject) => {
7416
7620
  const ws = new WebSocket(url);
7417
7621
  let stopHeartbeat;
7418
7622
  let settled = false;
@@ -7441,7 +7645,7 @@ function connectOnce(url, ctx, connectionAbort, onConnected) {
7441
7645
  finish(() => reject(new Error("shutdown")));
7442
7646
  return;
7443
7647
  }
7444
- finish(resolve6);
7648
+ finish(resolve7);
7445
7649
  });
7446
7650
  ws.on("error", (err) => {
7447
7651
  console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
@@ -7838,7 +8042,7 @@ import path15 from "node:path";
7838
8042
  import Docker from "dockerode";
7839
8043
 
7840
8044
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
7841
- import { existsSync as existsSync24, readFileSync as readFileSync18 } from "node:fs";
8045
+ import { existsSync as existsSync25, readFileSync as readFileSync18 } from "node:fs";
7842
8046
  import path12 from "node:path";
7843
8047
  function asOptionalTlsBuffer(value) {
7844
8048
  if (typeof value !== "string") {
@@ -7850,7 +8054,7 @@ function asOptionalTlsBuffer(value) {
7850
8054
  if (normalized === "") {
7851
8055
  return void 0;
7852
8056
  }
7853
- if (existsSync24(normalized)) {
8057
+ if (existsSync25(normalized)) {
7854
8058
  return readFileSync18(normalized);
7855
8059
  }
7856
8060
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
@@ -7976,17 +8180,17 @@ var DockerodeClient = class {
7976
8180
  await this.client.getImage(image).remove({ force: true });
7977
8181
  }
7978
8182
  async pullImage(image, auth) {
7979
- const stream = await new Promise((resolve6, reject) => {
8183
+ const stream = await new Promise((resolve7, reject) => {
7980
8184
  const pullOptions = auth ? { authconfig: auth } : void 0;
7981
8185
  this.client.pull(image, pullOptions, (err, output) => {
7982
8186
  if (err || !output) {
7983
8187
  reject(err ?? new Error("docker pull \u8FD4\u56DE\u7A7A\u8F93\u51FA"));
7984
8188
  return;
7985
8189
  }
7986
- resolve6(output);
8190
+ resolve7(output);
7987
8191
  });
7988
8192
  });
7989
- await new Promise((resolve6, reject) => {
8193
+ await new Promise((resolve7, reject) => {
7990
8194
  this.client.modem.followProgress(
7991
8195
  stream,
7992
8196
  (err) => {
@@ -7994,7 +8198,7 @@ var DockerodeClient = class {
7994
8198
  reject(err);
7995
8199
  return;
7996
8200
  }
7997
- resolve6();
8201
+ resolve7();
7998
8202
  },
7999
8203
  () => void 0
8000
8204
  );
@@ -8061,7 +8265,7 @@ var DockerodeClient = class {
8061
8265
  var createDockerodeClient = (config) => new DockerodeClient(config);
8062
8266
 
8063
8267
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
8064
- import { existsSync as existsSync25, readFileSync as readFileSync19, statSync as statSync10 } from "node:fs";
8268
+ import { existsSync as existsSync26, readFileSync as readFileSync19, statSync as statSync10 } from "node:fs";
8065
8269
  import path13 from "node:path";
8066
8270
  function stripSurroundingQuotes(value) {
8067
8271
  const t = value.trim();
@@ -8078,7 +8282,7 @@ function loadEnvFromFile(envFilePath) {
8078
8282
  return {};
8079
8283
  }
8080
8284
  const targetPath = path13.resolve(envFilePath);
8081
- if (!existsSync25(targetPath) || !statSync10(targetPath).isFile()) {
8285
+ if (!existsSync26(targetPath) || !statSync10(targetPath).isFile()) {
8082
8286
  return {};
8083
8287
  }
8084
8288
  const raw = readFileSync19(targetPath, "utf-8");
@@ -8252,12 +8456,12 @@ function dockerPushImage(params, cwd) {
8252
8456
  }
8253
8457
 
8254
8458
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
8255
- import { existsSync as existsSync26 } from "node:fs";
8459
+ import { existsSync as existsSync27 } from "node:fs";
8256
8460
  import path14 from "node:path";
8257
8461
  function resolveDockerBuildPaths(cwd) {
8258
8462
  const dockerfilePath = path14.join(cwd, "Dockerfile");
8259
8463
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
8260
- if (!existsSync26(dockerfilePath)) {
8464
+ if (!existsSync27(dockerfilePath)) {
8261
8465
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
8262
8466
  }
8263
8467
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -80,6 +80,22 @@ var requestConfig = {
80
80
  method: "PUT",
81
81
  path: "/cli/webide/message-logs"
82
82
  }),
83
+ webideReplaceAssumptions: defineEndpoint({
84
+ method: "PUT",
85
+ path: "/cli/webide/assumptions"
86
+ }),
87
+ webideListAssumptions: defineEndpoint({
88
+ method: "GET",
89
+ path: "/cli/webide/assumptions"
90
+ }),
91
+ webideRecommendPlan: defineEndpoint({
92
+ method: "PUT",
93
+ path: "/cli/webide/plan-recommendation"
94
+ }),
95
+ webideUpsertPlan: defineEndpoint({
96
+ method: "PUT",
97
+ path: "/cli/webide/plan"
98
+ }),
83
99
  branchBaseline: defineEndpoint({
84
100
  method: "GET",
85
101
  path: "/cli/tasks/branch-baseline"
@@ -281,6 +297,16 @@ function formatPlanMarkdown(raw) {
281
297
  }
282
298
 
283
299
  // src/session-utils.ts
300
+ function serializeTokenUsage(usage) {
301
+ return {
302
+ inputTokens: usage.inputTokens,
303
+ outputTokens: usage.outputTokens,
304
+ cacheReadTokens: usage.cacheReadTokens,
305
+ cacheWriteTokens: usage.cacheWriteTokens,
306
+ totalTokens: usage.totalTokens,
307
+ ...usage.reasoningTokens != null ? { reasoningTokens: usage.reasoningTokens } : {}
308
+ };
309
+ }
284
310
  var EventSession = class {
285
311
  events = [];
286
312
  dirtyIndices = /* @__PURE__ */ new Set();
@@ -319,7 +345,7 @@ var EventSession = class {
319
345
  if (formatedEvent.type === "status") {
320
346
  return;
321
347
  }
322
- if (formatedEvent.type === "request") {
348
+ if (formatedEvent.type === "request" || formatedEvent.type === "usage") {
323
349
  this.events.push(formatedEvent);
324
350
  this.markDirty(this.events.length - 1);
325
351
  return;
@@ -343,6 +369,16 @@ var EventSession = class {
343
369
  this.events.push(formatedEvent);
344
370
  this.markDirty(this.events.length - 1);
345
371
  }
372
+ /** 写入 run.wait() 返回的累计 TokenUsage(整次 run 汇总) */
373
+ addRunUsage(usage, options) {
374
+ this.events.push({
375
+ type: "usage",
376
+ scope: "run",
377
+ usage: serializeTokenUsage(usage),
378
+ ...options?.durationMs != null ? { durationMs: options.durationMs } : {}
379
+ });
380
+ this.markDirty(this.events.length - 1);
381
+ }
346
382
  formatEvent(event) {
347
383
  switch (event.type) {
348
384
  case "assistant": {
@@ -377,6 +413,12 @@ var EventSession = class {
377
413
  status: event.status,
378
414
  text: event.text
379
415
  };
416
+ case "usage":
417
+ return {
418
+ type: "usage",
419
+ scope: "turn",
420
+ usage: serializeTokenUsage(event.usage)
421
+ };
380
422
  case "request":
381
423
  return {
382
424
  ...event,
@@ -451,6 +493,16 @@ ${String(event.content ?? "")}
451
493
  if (type === "tool_call") {
452
494
  return "````toolcall\n" + JSON.stringify(event, null, 2) + "\n````\n";
453
495
  }
496
+ if (type === "usage") {
497
+ const usage = event.usage;
498
+ const scope = event.scope === "run" ? "\u7D2F\u8BA1" : "\u672C\u8F6E";
499
+ return `## Token \u4F7F\u7528\u91CF\uFF08${scope}\uFF09
500
+
501
+ \`\`\`json
502
+ ${JSON.stringify(usage ?? event, null, 2)}
503
+ \`\`\`
504
+ `;
505
+ }
454
506
  return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type}
455
507
 
456
508
  \`\`\`json
@@ -883,51 +935,124 @@ function createAppendMessageCustomTools(cfg, messageId, appendContent2) {
883
935
  }
884
936
 
885
937
  // src/commands/connect/ask-question-tool.ts
886
- function createAskQuestionMockTool(options) {
938
+ var INPUT_SCHEMA = {
939
+ type: "object",
940
+ properties: {
941
+ title: {
942
+ type: "string",
943
+ description: "Optional title for the questions form"
944
+ },
945
+ questions: {
946
+ type: "array",
947
+ minItems: 1,
948
+ items: {
949
+ type: "object",
950
+ properties: {
951
+ id: { type: "string" },
952
+ prompt: { type: "string" },
953
+ allow_multiple: { type: "boolean" },
954
+ options: {
955
+ type: "array",
956
+ minItems: 2,
957
+ items: {
958
+ type: "object",
959
+ properties: {
960
+ id: { type: "string" },
961
+ label: { type: "string" }
962
+ },
963
+ required: ["id", "label"]
964
+ }
965
+ }
966
+ },
967
+ required: ["id", "prompt", "options"]
968
+ }
969
+ }
970
+ },
971
+ required: ["questions"]
972
+ };
973
+ function createAskQuestionTool(options) {
887
974
  return {
888
975
  description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
976
+ inputSchema: INPUT_SCHEMA,
977
+ execute: async (args) => {
978
+ options?.onInvoke?.(args);
979
+ if (options?.execute) {
980
+ return options.execute(args);
981
+ }
982
+ const payload = JSON.stringify(args, null, 2);
983
+ console.log(`[apm] AskQuestion\uFF08\u672C\u5730\u5360\u4F4D\uFF0C\u672A\u7B49\u5F85\u7528\u6237\uFF09:
984
+ ${payload}`);
985
+ return `[local] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
986
+ ${payload}`;
987
+ }
988
+ };
989
+ }
990
+
991
+ // src/commands/connect/webide-plan-tools.ts
992
+ function asString(value) {
993
+ return typeof value === "string" ? value.trim() : "";
994
+ }
995
+ function createRecommendPlanTool(options) {
996
+ const { cfg, taskId } = options;
997
+ return {
998
+ description: "Recommend whether the human should write an implementation plan or skip it. Does not change workflow phase; the human decides in the WebIDE UI.",
889
999
  inputSchema: {
890
1000
  type: "object",
891
1001
  properties: {
892
- title: {
1002
+ recommendation: {
893
1003
  type: "string",
894
- description: "Optional title for the questions form"
1004
+ enum: ["write", "skip", "unknown"],
1005
+ description: "write = suggest generating a plan; skip = plan optional/unnecessary; unknown = unsure"
895
1006
  },
896
- questions: {
897
- type: "array",
898
- minItems: 1,
899
- items: {
900
- type: "object",
901
- properties: {
902
- id: { type: "string" },
903
- prompt: { type: "string" },
904
- allow_multiple: { type: "boolean" },
905
- options: {
906
- type: "array",
907
- minItems: 2,
908
- items: {
909
- type: "object",
910
- properties: {
911
- id: { type: "string" },
912
- label: { type: "string" }
913
- },
914
- required: ["id", "label"]
915
- }
916
- }
917
- },
918
- required: ["id", "prompt", "options"]
919
- }
1007
+ reason: {
1008
+ type: "string",
1009
+ description: "Short Chinese reason for the recommendation"
920
1010
  }
921
1011
  },
922
- required: ["questions"]
1012
+ required: ["recommendation"]
923
1013
  },
924
1014
  execute: async (args) => {
925
- const payload = JSON.stringify(args, null, 2);
926
- console.log(`[apm] AskQuestion mock \u8C03\u7528:
927
- ${payload}`);
928
- options?.onInvoke?.(args);
929
- 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:
930
- ${payload}`;
1015
+ const raw = asString(args.recommendation);
1016
+ const recommendation = raw === "write" || raw === "skip" || raw === "unknown" ? raw : "unknown";
1017
+ const reason = asString(args.reason) || void 0;
1018
+ const api = createApmApiClient(cfg);
1019
+ await api.cli.webideRecommendPlan({
1020
+ taskId,
1021
+ recommendation,
1022
+ reason
1023
+ });
1024
+ console.log(
1025
+ `[apm] RecommendPlan taskId=${taskId} recommendation=${recommendation}`
1026
+ );
1027
+ return JSON.stringify({ ok: true, recommendation, reason }, null, 2);
1028
+ }
1029
+ };
1030
+ }
1031
+ function createUpsertWebIdePlanTool(options) {
1032
+ const { cfg, taskId } = options;
1033
+ return {
1034
+ description: "Persist the implementation plan markdown for this WebIDE task and mark the plan as ready for human confirmation. Call once with the full plan document.",
1035
+ inputSchema: {
1036
+ type: "object",
1037
+ properties: {
1038
+ content: {
1039
+ type: "string",
1040
+ description: "Full implementation plan in Markdown (Chinese)"
1041
+ }
1042
+ },
1043
+ required: ["content"]
1044
+ },
1045
+ execute: async (args) => {
1046
+ const content = asString(args.content);
1047
+ if (!content) {
1048
+ throw new Error("UpsertWebIdePlan \u7F3A\u5C11 content");
1049
+ }
1050
+ const api = createApmApiClient(cfg);
1051
+ await api.cli.webideUpsertPlan({ taskId, content });
1052
+ console.log(
1053
+ `[apm] UpsertWebIdePlan taskId=${taskId} chars=${content.length}`
1054
+ );
1055
+ return JSON.stringify({ ok: true, chars: content.length }, null, 2);
931
1056
  }
932
1057
  };
933
1058
  }
@@ -938,16 +1063,28 @@ AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u
938
1063
  \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
939
1064
  \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
940
1065
  function createCursorCustomTools(cfg, messageId, options) {
941
- return {
1066
+ const tools = {
942
1067
  ...createAppendMessageCustomTools(
943
1068
  cfg,
944
1069
  messageId,
945
1070
  options?.appendMessageContent
946
1071
  ),
947
- AskQuestion: createAskQuestionMockTool({
948
- onInvoke: options?.onAskQuestion
1072
+ AskQuestion: createAskQuestionTool({
1073
+ onInvoke: options?.onAskQuestion,
1074
+ execute: options?.askQuestionExecute
949
1075
  })
950
1076
  };
1077
+ if (options?.enableWebIdePlanTools && options.taskId) {
1078
+ tools.RecommendPlan = createRecommendPlanTool({
1079
+ cfg,
1080
+ taskId: options.taskId
1081
+ });
1082
+ tools.UpsertWebIdePlan = createUpsertWebIdePlanTool({
1083
+ cfg,
1084
+ taskId: options.taskId
1085
+ });
1086
+ }
1087
+ return tools;
951
1088
  }
952
1089
  function withPlanModeToolHint(prompt, mode) {
953
1090
  if (mode !== "plan") {
@@ -959,7 +1096,7 @@ ${PLAN_MODE_ASK_QUESTION_HINT}`;
959
1096
  }
960
1097
 
961
1098
  // src/commands/connect/cursor-agent.ts
962
- setMaxListeners2(50);
1099
+ setMaxListeners2(100);
963
1100
  installAbortSignalDebug();
964
1101
  var noopRemoteLogSync = {
965
1102
  schedule(_session) {
@@ -1037,7 +1174,10 @@ async function runCursorAgent(cfg, ctx, options) {
1037
1174
  const workdir = resolveWorkdirPath(ctx.workdir);
1038
1175
  const customTools = createCursorCustomTools(cfg, ctx.messageId, {
1039
1176
  onAskQuestion: options?.onAskQuestion,
1040
- appendMessageContent: options?.appendMessageContent
1177
+ appendMessageContent: options?.appendMessageContent,
1178
+ askQuestionExecute: options?.askQuestionExecute,
1179
+ enableWebIdePlanTools: options?.enableWebIdePlanTools,
1180
+ taskId: options?.taskId
1041
1181
  });
1042
1182
  const prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
1043
1183
  console.log(
@@ -1106,6 +1246,15 @@ async function runCursorAgent(cfg, ctx, options) {
1106
1246
  }
1107
1247
  await syncRemoteLog.flush(eventSession);
1108
1248
  const result = await run.wait();
1249
+ if (result.usage) {
1250
+ eventSession.addRunUsage(result.usage, {
1251
+ durationMs: result.durationMs
1252
+ });
1253
+ await syncRemoteLog.flush(eventSession);
1254
+ console.log(
1255
+ `[apm] Cursor usage total=${result.usage.totalTokens} in=${result.usage.inputTokens} out=${result.usage.outputTokens} cache=${result.usage.cacheReadTokens}/${result.usage.cacheWriteTokens}`
1256
+ );
1257
+ }
1109
1258
  if (result.status === "error") {
1110
1259
  const failureMessage = formatCursorRunFailure(result.id, {
1111
1260
  statusError: lastRunErrorStatus,
@@ -1142,6 +1291,7 @@ async function runCursorAgent(cfg, ctx, options) {
1142
1291
  status: result.status,
1143
1292
  result: result.result,
1144
1293
  durationMs: result.durationMs,
1294
+ usage: result.usage,
1145
1295
  assistantText: eventSession.getAssistantText(),
1146
1296
  createPlan: eventSession.getCreatePlanContent(),
1147
1297
  artifacts,
@@ -1204,6 +1354,97 @@ function clearWebIdeAgentId(workdir, taskId) {
1204
1354
  writeRegistry2(path, {});
1205
1355
  }
1206
1356
 
1357
+ // src/commands/connect/webide-ask-question.ts
1358
+ import { setTimeout as delay } from "node:timers/promises";
1359
+ var POLL_INTERVAL_MS = 2e3;
1360
+ function asString2(value) {
1361
+ return typeof value === "string" ? value.trim() : "";
1362
+ }
1363
+ function parseQuestions(args) {
1364
+ const title = asString2(args.title) || void 0;
1365
+ const raw = args.questions;
1366
+ if (!Array.isArray(raw) || raw.length === 0) {
1367
+ throw new Error("AskQuestion \u7F3A\u5C11 questions");
1368
+ }
1369
+ const questions = [];
1370
+ for (const item of raw) {
1371
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
1372
+ const row = item;
1373
+ const id = asString2(row.id);
1374
+ const prompt = asString2(row.prompt);
1375
+ const optionsRaw = row.options;
1376
+ if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
1377
+ const options = [];
1378
+ for (const opt of optionsRaw) {
1379
+ if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
1380
+ const o = opt;
1381
+ const oid = asString2(o.id);
1382
+ const label = asString2(o.label);
1383
+ if (oid && label) options.push({ id: oid, label });
1384
+ }
1385
+ if (options.length < 2) {
1386
+ throw new Error(`AskQuestion \u95EE\u9898 ${id} \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`);
1387
+ }
1388
+ questions.push({ id, prompt, options });
1389
+ }
1390
+ if (questions.length === 0) {
1391
+ throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
1392
+ }
1393
+ return { title, questions };
1394
+ }
1395
+ function createWebIdeAskQuestionExecute(options) {
1396
+ const { cfg, taskId, signal } = options;
1397
+ return async (args) => {
1398
+ const parsed = parseQuestions(args);
1399
+ const api = createApmApiClient(cfg);
1400
+ console.log(
1401
+ `[apm] AskQuestion \u521B\u5EFA\u5F85\u786E\u8BA4\u9879 taskId=${taskId} count=${parsed.questions.length}`
1402
+ );
1403
+ await api.cli.webideReplaceAssumptions({
1404
+ taskId,
1405
+ title: parsed.title,
1406
+ questions: parsed.questions
1407
+ });
1408
+ while (true) {
1409
+ if (signal?.aborted) {
1410
+ throw new Error("AskQuestion \u5DF2\u53D6\u6D88");
1411
+ }
1412
+ const list = await api.cli.webideListAssumptions({ taskId });
1413
+ const byId = new Map(
1414
+ list.assumptions.map((a) => [a.questionId, a])
1415
+ );
1416
+ const pending = parsed.questions.filter((q) => {
1417
+ const row = byId.get(q.id);
1418
+ return !row || row.status !== "RESOLVED";
1419
+ });
1420
+ if (pending.length === 0) {
1421
+ const answers = parsed.questions.map((q) => {
1422
+ const row = byId.get(q.id);
1423
+ return {
1424
+ questionId: q.id,
1425
+ prompt: q.prompt,
1426
+ selectedOptionId: row.selectedOptionId,
1427
+ customAnswer: row.customAnswer,
1428
+ resolution: row.resolution
1429
+ };
1430
+ });
1431
+ console.log(
1432
+ `[apm] AskQuestion \u7528\u6237\u5DF2\u786E\u8BA4 taskId=${taskId} count=${answers.length}`
1433
+ );
1434
+ return JSON.stringify(
1435
+ {
1436
+ title: parsed.title,
1437
+ answers
1438
+ },
1439
+ null,
1440
+ 2
1441
+ );
1442
+ }
1443
+ await delay(POLL_INTERVAL_MS);
1444
+ }
1445
+ };
1446
+ }
1447
+
1207
1448
  // src/commands/deploy/internal/minio.ts
1208
1449
  import * as Minio from "minio";
1209
1450
  var MinioClient = class {
@@ -1824,6 +2065,13 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
1824
2065
  {
1825
2066
  signal,
1826
2067
  appendMessageContent: (content) => appendContent(cfg, messageId, content),
2068
+ askQuestionExecute: createWebIdeAskQuestionExecute({
2069
+ cfg,
2070
+ taskId,
2071
+ signal
2072
+ }),
2073
+ enableWebIdePlanTools: true,
2074
+ taskId,
1827
2075
  createRemoteLogSync: (agentId) => {
1828
2076
  saveWebIdeAgentId(workdir, taskId, agentId);
1829
2077
  logSyncRef.current = createThrottledWebIdeMessageLogSync(
@@ -1868,6 +2116,19 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
1868
2116
  id: messageId,
1869
2117
  content: fallback
1870
2118
  });
2119
+ if (msg.action === "write-plan" && outcome.createPlan && outcome.createPlan.trim()) {
2120
+ try {
2121
+ await api.cli.webideUpsertPlan({
2122
+ taskId,
2123
+ content: outcome.createPlan.trim()
2124
+ });
2125
+ } catch (err) {
2126
+ console.warn(
2127
+ "[apm] write-plan \u8865\u5199\u8BA1\u5212\u5931\u8D25:",
2128
+ err instanceof Error ? err.message : err
2129
+ );
2130
+ }
2131
+ }
1871
2132
  await logSyncRef.current?.markRun(outcome.runId, "finished");
1872
2133
  await updateStatus(cfg, messageId, "SUCCESS");
1873
2134
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "7.1.1",
3
+ "version": "7.1.3",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -35,7 +35,7 @@
35
35
  "@bufbuild/protobuf": "1.10.0",
36
36
  "@connectrpc/connect": "~1.7.0",
37
37
  "@connectrpc/connect-node": "~1.7.0",
38
- "@cursor/sdk": "~1.0.18",
38
+ "@cursor/sdk": "^1.0.22",
39
39
  "ws": "~8.18.0",
40
40
  "listpage-http": "~0.0.318",
41
41
  "commander": "~14.0.3",