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 +288 -84
- package/dist/webide-message-worker.js +301 -40
- package/package.json +2 -2
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((
|
|
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",
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
5194
|
-
client.on("ready", () =>
|
|
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((
|
|
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
|
-
|
|
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
|
|
6438
|
-
import { dirname as dirname6, resolve as
|
|
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
|
|
6532
|
+
return resolve6(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
|
|
6441
6533
|
}
|
|
6442
6534
|
function readRegistry(path19) {
|
|
6443
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
6759
|
+
recommendation: {
|
|
6610
6760
|
type: "string",
|
|
6611
|
-
|
|
6761
|
+
enum: ["write", "skip", "unknown"],
|
|
6762
|
+
description: "write = suggest generating a plan; skip = plan optional/unnecessary; unknown = unsure"
|
|
6612
6763
|
},
|
|
6613
|
-
|
|
6614
|
-
type: "
|
|
6615
|
-
|
|
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: ["
|
|
6769
|
+
required: ["recommendation"]
|
|
6640
6770
|
},
|
|
6641
6771
|
execute: async (args) => {
|
|
6642
|
-
const
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
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
|
-
|
|
6823
|
+
const tools = {
|
|
6659
6824
|
...createAppendMessageCustomTools(
|
|
6660
6825
|
cfg,
|
|
6661
6826
|
messageId,
|
|
6662
6827
|
options?.appendMessageContent
|
|
6663
6828
|
),
|
|
6664
|
-
AskQuestion:
|
|
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(
|
|
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
|
|
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 (!
|
|
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((
|
|
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
|
-
|
|
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((
|
|
7042
|
-
resolveDone =
|
|
7231
|
+
const done = new Promise((resolve7) => {
|
|
7232
|
+
resolveDone = resolve7;
|
|
7043
7233
|
});
|
|
7044
|
-
worker.on(
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
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((
|
|
7232
|
-
const timer = setTimeout(
|
|
7423
|
+
return new Promise((resolve7) => {
|
|
7424
|
+
const timer = setTimeout(resolve7, ms);
|
|
7233
7425
|
const onAbort = () => {
|
|
7234
7426
|
clearTimeout(timer);
|
|
7235
|
-
|
|
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((
|
|
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(
|
|
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
|
|
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 (
|
|
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((
|
|
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
|
-
|
|
8190
|
+
resolve7(output);
|
|
7987
8191
|
});
|
|
7988
8192
|
});
|
|
7989
|
-
await new Promise((
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
1002
|
+
recommendation: {
|
|
893
1003
|
type: "string",
|
|
894
|
-
|
|
1004
|
+
enum: ["write", "skip", "unknown"],
|
|
1005
|
+
description: "write = suggest generating a plan; skip = plan optional/unnecessary; unknown = unsure"
|
|
895
1006
|
},
|
|
896
|
-
|
|
897
|
-
type: "
|
|
898
|
-
|
|
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: ["
|
|
1012
|
+
required: ["recommendation"]
|
|
923
1013
|
},
|
|
924
1014
|
execute: async (args) => {
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
-
|
|
1066
|
+
const tools = {
|
|
942
1067
|
...createAppendMessageCustomTools(
|
|
943
1068
|
cfg,
|
|
944
1069
|
messageId,
|
|
945
1070
|
options?.appendMessageContent
|
|
946
1071
|
),
|
|
947
|
-
AskQuestion:
|
|
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(
|
|
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.
|
|
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": "
|
|
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",
|