ai-project-manage-cli 8.1.20 → 8.1.22
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/README.md +18 -6
- package/dist/index.js +769 -816
- package/dist/scripts/branch_has_changes.sh +53 -0
- package/dist/scripts/commit_if_dirty.sh +21 -0
- package/dist/scripts/ensure_baseline.sh +15 -0
- package/dist/scripts/ensure_feature_branch.sh +55 -0
- package/dist/scripts/push_code.sh +21 -0
- package/dist/worker-script.js +1507 -2711
- package/package.json +2 -2
- package/template/README.md +13 -0
- package/template/hooks/after-agent-commit-repos.cjs +112 -0
- package/template/hooks/before-prompt-prepare.cjs +206 -0
- package/template/hooks.json +18 -0
- package/template/mcp.json +18 -0
- package/template/skills/webide-run-retro/SKILL.md +0 -103
- package/template/skills/webide-run-retro/attribution.md +0 -26
- package/template/skills/webide-run-retro/scripts/inspect-run.mjs +0 -335
package/dist/worker-script.js
CHANGED
|
@@ -115,38 +115,10 @@ var requestConfig = {
|
|
|
115
115
|
method: "POST",
|
|
116
116
|
path: "/cli/webide/pull-requests/merge"
|
|
117
117
|
}),
|
|
118
|
-
getProjectDocumentManifest: defineEndpoint({
|
|
119
|
-
method: "GET",
|
|
120
|
-
path: "/cli/project-documents/manifest"
|
|
121
|
-
}),
|
|
122
|
-
listProjectDocuments: defineEndpoint({
|
|
123
|
-
method: "GET",
|
|
124
|
-
path: "/cli/project-documents"
|
|
125
|
-
}),
|
|
126
|
-
upsertProjectDocument: defineEndpoint({
|
|
127
|
-
method: "PUT",
|
|
128
|
-
path: "/cli/project-documents/upsert"
|
|
129
|
-
}),
|
|
130
|
-
removeProjectDocument: defineEndpoint({
|
|
131
|
-
method: "DELETE",
|
|
132
|
-
path: "/cli/project-documents"
|
|
133
|
-
}),
|
|
134
118
|
getApmLogStorage: defineEndpoint({
|
|
135
119
|
method: "GET",
|
|
136
120
|
path: "/cli/apm-log-storage"
|
|
137
121
|
}),
|
|
138
|
-
projectPackageUpdateStatus: defineEndpoint({
|
|
139
|
-
method: "PUT",
|
|
140
|
-
path: "/cli/project-packages/status"
|
|
141
|
-
}),
|
|
142
|
-
projectPackageAppendContent: defineEndpoint({
|
|
143
|
-
method: "PUT",
|
|
144
|
-
path: "/cli/project-packages/content"
|
|
145
|
-
}),
|
|
146
|
-
projectPackageEnsureContent: defineEndpoint({
|
|
147
|
-
method: "PUT",
|
|
148
|
-
path: "/cli/project-packages/ensure-content"
|
|
149
|
-
}),
|
|
150
122
|
cursorStoreUpsertAgent: defineEndpoint({
|
|
151
123
|
method: "PUT",
|
|
152
124
|
path: "/cli/cursor-store/agents"
|
|
@@ -207,6 +179,15 @@ function resolveClientMachineId(cfg2) {
|
|
|
207
179
|
function resolveApiKey(cfg2) {
|
|
208
180
|
return (cfg2.apiKey ?? cfg2.token ?? "").trim();
|
|
209
181
|
}
|
|
182
|
+
function stripTrailingSlash(raw) {
|
|
183
|
+
return raw.trim().replace(/\/+$/, "");
|
|
184
|
+
}
|
|
185
|
+
function readConfigSocketServer(cfg2) {
|
|
186
|
+
const fromNew = typeof cfg2.socketServer === "string" ? cfg2.socketServer.trim().replace(/\/+$/, "") : "";
|
|
187
|
+
if (fromNew) return fromNew;
|
|
188
|
+
const fromLegacy = typeof cfg2.wsUrl === "string" ? cfg2.wsUrl.trim().replace(/\/+$/, "") : "";
|
|
189
|
+
return fromLegacy;
|
|
190
|
+
}
|
|
210
191
|
function tryReadApmConfig() {
|
|
211
192
|
try {
|
|
212
193
|
const raw = readFileSync(APM_CONFIG_PATH, "utf8");
|
|
@@ -222,10 +203,12 @@ function tryReadApmConfig() {
|
|
|
222
203
|
if (!apiKey) return null;
|
|
223
204
|
const cfg2 = v;
|
|
224
205
|
const clientMachineId = resolveClientMachineId(cfg2);
|
|
206
|
+
const socketServer = readConfigSocketServer(cfg2);
|
|
225
207
|
return {
|
|
226
208
|
baseUrl: cfg2.baseUrl.trim().replace(/\/+$/, ""),
|
|
227
209
|
apiKey,
|
|
228
|
-
...clientMachineId ? { clientMachineId } : {}
|
|
210
|
+
...clientMachineId ? { clientMachineId } : {},
|
|
211
|
+
...socketServer ? { socketServer } : {}
|
|
229
212
|
};
|
|
230
213
|
} catch {
|
|
231
214
|
return null;
|
|
@@ -248,10 +231,14 @@ async function ensureApmConfig() {
|
|
|
248
231
|
async function writeApmConfig(cfg2) {
|
|
249
232
|
const clientMachineId = resolveClientMachineId(cfg2);
|
|
250
233
|
const apiKey = resolveApiKey(cfg2);
|
|
234
|
+
const socketServer = stripTrailingSlash(
|
|
235
|
+
cfg2.socketServer?.trim() || cfg2.wsUrl?.trim() || ""
|
|
236
|
+
);
|
|
251
237
|
const normalized = {
|
|
252
238
|
baseUrl: cfg2.baseUrl.trim().replace(/\/+$/, ""),
|
|
253
239
|
apiKey,
|
|
254
|
-
...clientMachineId ? { clientMachineId } : {}
|
|
240
|
+
...clientMachineId ? { clientMachineId } : {},
|
|
241
|
+
...socketServer ? { socketServer } : {}
|
|
255
242
|
};
|
|
256
243
|
mkdirSync(APM_CONFIG_DIR, { recursive: true });
|
|
257
244
|
writeFileSync(APM_CONFIG_PATH, JSON.stringify(normalized, null, 2) + "\n", {
|
|
@@ -284,8 +271,8 @@ function toFsPath(inputPath) {
|
|
|
284
271
|
}
|
|
285
272
|
return `\\\\?\\${normalized}`;
|
|
286
273
|
}
|
|
287
|
-
function normalizeWorkdirPath(
|
|
288
|
-
let normalized =
|
|
274
|
+
function normalizeWorkdirPath(path) {
|
|
275
|
+
let normalized = path.trim().replace(/\\/g, "/").normalize("NFC");
|
|
289
276
|
if (normalized.startsWith("//?/")) {
|
|
290
277
|
normalized = normalized.slice(4);
|
|
291
278
|
}
|
|
@@ -372,84 +359,66 @@ async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
|
|
|
372
359
|
}
|
|
373
360
|
|
|
374
361
|
// src/utils/git.ts
|
|
375
|
-
import { execFile as execFile2 } from "child_process";
|
|
376
|
-
import { promisify as promisify2 } from "util";
|
|
377
|
-
|
|
378
|
-
// src/utils/git-remote.ts
|
|
379
362
|
import { execFile } from "child_process";
|
|
363
|
+
import { dirname, join as join3 } from "path";
|
|
364
|
+
import { fileURLToPath } from "url";
|
|
380
365
|
import { promisify } from "util";
|
|
381
366
|
var execFileAsync = promisify(execFile);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
if (scpLike) {
|
|
400
|
-
s = `https://${scpLike[1]}/${scpLike[2]}`;
|
|
401
|
-
} else if (s.includes("/")) {
|
|
402
|
-
s = `https://${s}`;
|
|
403
|
-
} else {
|
|
404
|
-
return null;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
367
|
+
var GIT_NETWORK_TIMEOUT_MS = 9e4;
|
|
368
|
+
var GIT_PUSH_TIMEOUT_MS = 12e4;
|
|
369
|
+
var GIT_LOCAL_TIMEOUT_MS = 6e4;
|
|
370
|
+
var GIT_NETWORK_CMDS = /* @__PURE__ */ new Set([
|
|
371
|
+
"fetch",
|
|
372
|
+
"push",
|
|
373
|
+
"ls-remote",
|
|
374
|
+
"clone",
|
|
375
|
+
"pull"
|
|
376
|
+
]);
|
|
377
|
+
function mergeGitSshCommand(existing) {
|
|
378
|
+
const base = existing?.trim() || "ssh";
|
|
379
|
+
const extras = [];
|
|
380
|
+
if (!/\bStrictHostKeyChecking=/.test(base)) {
|
|
381
|
+
extras.push("-o", "StrictHostKeyChecking=accept-new");
|
|
407
382
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return s;
|
|
411
|
-
}
|
|
412
|
-
async function tryReadGitOriginUrl(cwd) {
|
|
413
|
-
try {
|
|
414
|
-
const { stdout } = await execFileAsync(
|
|
415
|
-
"git",
|
|
416
|
-
["config", "--get", "remote.origin.url"],
|
|
417
|
-
{ cwd, encoding: "utf8", maxBuffer: 1024 * 1024 }
|
|
418
|
-
);
|
|
419
|
-
const url = stdout.trim();
|
|
420
|
-
return url || null;
|
|
421
|
-
} catch {
|
|
422
|
-
return null;
|
|
383
|
+
if (!/\bBatchMode=/.test(base)) {
|
|
384
|
+
extras.push("-o", "BatchMode=yes");
|
|
423
385
|
}
|
|
386
|
+
if (!/\bConnectTimeout=/.test(base)) {
|
|
387
|
+
extras.push("-o", "ConnectTimeout=20");
|
|
388
|
+
}
|
|
389
|
+
if (!/\bServerAliveInterval=/.test(base)) {
|
|
390
|
+
extras.push("-o", "ServerAliveInterval=10");
|
|
391
|
+
}
|
|
392
|
+
if (!/\bServerAliveCountMax=/.test(base)) {
|
|
393
|
+
extras.push("-o", "ServerAliveCountMax=3");
|
|
394
|
+
}
|
|
395
|
+
return extras.length ? `${base} ${extras.join(" ")}` : base;
|
|
424
396
|
}
|
|
425
|
-
async function tryReadHttpsGitOriginUrl(cwd) {
|
|
426
|
-
const raw = await tryReadGitOriginUrl(cwd);
|
|
427
|
-
if (!raw) return null;
|
|
428
|
-
return toHttpsGitRemoteUrl(raw);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// src/utils/git.ts
|
|
432
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
433
397
|
function gitExecEnv() {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
398
|
+
return {
|
|
399
|
+
...process.env,
|
|
400
|
+
GIT_SSH_COMMAND: mergeGitSshCommand(process.env.GIT_SSH_COMMAND),
|
|
401
|
+
// 避免 credential helper / askpass 在无 TTY 时挂起
|
|
402
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function defaultGitTimeoutMs(args) {
|
|
406
|
+
const cmd = args[0] ?? "";
|
|
407
|
+
if (cmd === "push") return GIT_PUSH_TIMEOUT_MS;
|
|
408
|
+
if (GIT_NETWORK_CMDS.has(cmd)) return GIT_NETWORK_TIMEOUT_MS;
|
|
409
|
+
return GIT_LOCAL_TIMEOUT_MS;
|
|
445
410
|
}
|
|
446
|
-
async function execGit(cwd, args,
|
|
411
|
+
async function execGit(cwd, args, quietOrOptions = false) {
|
|
412
|
+
const options = typeof quietOrOptions === "boolean" ? { quiet: quietOrOptions } : quietOrOptions;
|
|
413
|
+
const quiet = options.quiet ?? false;
|
|
414
|
+
const timeoutMs = options.timeoutMs === void 0 ? defaultGitTimeoutMs(args) : options.timeoutMs;
|
|
447
415
|
try {
|
|
448
|
-
const { stdout, stderr } = await
|
|
416
|
+
const { stdout, stderr } = await execFileAsync("git", args, {
|
|
449
417
|
cwd,
|
|
450
418
|
env: gitExecEnv(),
|
|
451
419
|
encoding: "utf8",
|
|
452
|
-
maxBuffer: 10 * 1024 * 1024
|
|
420
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
421
|
+
...timeoutMs > 0 ? { timeout: timeoutMs, killSignal: "SIGTERM" } : {}
|
|
453
422
|
});
|
|
454
423
|
if (!quiet && stderr.trim()) {
|
|
455
424
|
process.stderr.write(stderr);
|
|
@@ -457,113 +426,224 @@ async function execGit(cwd, args, quiet = false) {
|
|
|
457
426
|
return stdout;
|
|
458
427
|
} catch (err) {
|
|
459
428
|
const e = err;
|
|
429
|
+
if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
|
|
430
|
+
throw new Error(
|
|
431
|
+
`[apm] git ${args.join(" ")} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
460
434
|
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
461
435
|
throw new Error(
|
|
462
436
|
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
463
437
|
);
|
|
464
438
|
}
|
|
465
439
|
}
|
|
466
|
-
async function isGitRepo(cwd) {
|
|
467
|
-
try {
|
|
468
|
-
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
469
|
-
return true;
|
|
470
|
-
} catch {
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
440
|
async function ensureGitRepo(cwd) {
|
|
475
441
|
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
476
442
|
}
|
|
477
443
|
async function getCurrentBranch(cwd) {
|
|
478
444
|
return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
479
445
|
}
|
|
480
|
-
async function isWorkingTreeDirty(cwd) {
|
|
481
|
-
const out = await execGit(cwd, ["status", "--porcelain"], true);
|
|
482
|
-
return out.trim().length > 0;
|
|
483
|
-
}
|
|
484
|
-
async function remoteBranchExists(cwd, branch) {
|
|
485
|
-
const out = await execGit(
|
|
486
|
-
cwd,
|
|
487
|
-
["ls-remote", "--heads", "origin", branch],
|
|
488
|
-
true
|
|
489
|
-
);
|
|
490
|
-
return out.trim().length > 0;
|
|
491
|
-
}
|
|
492
446
|
async function resolveGitRepoRoot(cwd) {
|
|
493
447
|
return (await execGit(cwd, ["rev-parse", "--show-toplevel"], true)).trim();
|
|
494
448
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
throw new Error(
|
|
501
|
-
`[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
|
|
502
|
-
);
|
|
449
|
+
var FEATURE_BRANCH_RE = /^feat\/task-(.+)$/;
|
|
450
|
+
function isFeatureBranchName(branch) {
|
|
451
|
+
return FEATURE_BRANCH_RE.test(branch.trim().replace(/^origin\//, ""));
|
|
503
452
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
await execGit(cwd, ["rev-parse", "--verify", `origin/${head}`], true);
|
|
513
|
-
headRef = `origin/${head}`;
|
|
514
|
-
} catch {
|
|
515
|
-
await execGit(cwd, ["rev-parse", "--verify", head], true);
|
|
516
|
-
}
|
|
517
|
-
const baseRef = `origin/${base}`;
|
|
518
|
-
const ahead = (await execGit(cwd, ["rev-list", "--count", `${baseRef}..${headRef}`], true)).trim();
|
|
519
|
-
if ((Number.parseInt(ahead, 10) || 0) <= 0) {
|
|
520
|
-
return false;
|
|
521
|
-
}
|
|
453
|
+
function resolveGitScript(name) {
|
|
454
|
+
return join3(dirname(fileURLToPath(import.meta.url)), "scripts", name);
|
|
455
|
+
}
|
|
456
|
+
async function runGitScript(name, options) {
|
|
457
|
+
const skipCode = options.skipCode ?? 2;
|
|
458
|
+
const timeoutMs = options.timeoutMs ?? GIT_NETWORK_TIMEOUT_MS;
|
|
459
|
+
const script = resolveGitScript(name);
|
|
460
|
+
const args = [script, ...options.args ?? []];
|
|
522
461
|
try {
|
|
523
|
-
await
|
|
524
|
-
|
|
525
|
-
|
|
462
|
+
const { stdout, stderr } = await execFileAsync("bash", args, {
|
|
463
|
+
cwd: options.cwd,
|
|
464
|
+
env: gitExecEnv(),
|
|
465
|
+
encoding: "utf8",
|
|
466
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
467
|
+
timeout: timeoutMs,
|
|
468
|
+
killSignal: "SIGTERM"
|
|
469
|
+
});
|
|
470
|
+
if (stdout.trim()) process.stdout.write(stdout);
|
|
471
|
+
if (stderr.trim()) process.stderr.write(stderr);
|
|
526
472
|
return true;
|
|
473
|
+
} catch (err) {
|
|
474
|
+
const e = err;
|
|
475
|
+
if (e.stdout?.trim()) process.stdout.write(e.stdout);
|
|
476
|
+
if (e.stderr?.trim()) process.stderr.write(e.stderr);
|
|
477
|
+
if (e.code === skipCode) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
|
|
481
|
+
throw new Error(
|
|
482
|
+
`[apm] ${name} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
486
|
+
throw new Error(`[apm] ${name} \u5931\u8D25${detail ? `: ${detail}` : ""}`);
|
|
527
487
|
}
|
|
528
488
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
489
|
+
async function branchHasChangesAgainstBase(cwd, baseBranch, headBranch) {
|
|
490
|
+
return runGitScript("branch_has_changes.sh", {
|
|
491
|
+
cwd,
|
|
492
|
+
args: [baseBranch, headBranch],
|
|
493
|
+
timeoutMs: GIT_NETWORK_TIMEOUT_MS
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
async function commitWorkingTreeIfDirty(cwd, message) {
|
|
497
|
+
await ensureGitRepo(cwd);
|
|
498
|
+
return runGitScript("commit_if_dirty.sh", {
|
|
499
|
+
cwd,
|
|
500
|
+
args: message?.trim() ? [message.trim()] : [],
|
|
501
|
+
timeoutMs: GIT_LOCAL_TIMEOUT_MS
|
|
502
|
+
});
|
|
532
503
|
}
|
|
533
|
-
function
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
504
|
+
async function ensureFeatureBranch(cwd, branch, baselineBranch) {
|
|
505
|
+
await runGitScript("ensure_feature_branch.sh", {
|
|
506
|
+
cwd,
|
|
507
|
+
args: [branch, baselineBranch],
|
|
508
|
+
timeoutMs: GIT_PUSH_TIMEOUT_MS
|
|
509
|
+
});
|
|
538
510
|
}
|
|
539
511
|
async function pushCurrentBranch(cwd) {
|
|
540
|
-
|
|
541
|
-
|
|
512
|
+
return runGitScript("push_code.sh", {
|
|
513
|
+
cwd,
|
|
514
|
+
timeoutMs: GIT_PUSH_TIMEOUT_MS
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/utils/home-repository.ts
|
|
519
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
520
|
+
import { join as join4, resolve as resolve3 } from "path";
|
|
521
|
+
var HOME_REPOSITORY_JSON = "/home/repository.json";
|
|
522
|
+
function isSimpleChildDirName(name) {
|
|
523
|
+
if (!name || name === "." || name === "..") return false;
|
|
524
|
+
if (name.includes("/") || name.includes("\\")) return false;
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
function loadHomeRepositoryEntries() {
|
|
528
|
+
const path = toFsPath(HOME_REPOSITORY_JSON);
|
|
529
|
+
if (!existsSync2(path)) {
|
|
530
|
+
throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${HOME_REPOSITORY_JSON}`);
|
|
542
531
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
532
|
+
let parsed;
|
|
533
|
+
try {
|
|
534
|
+
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
535
|
+
} catch (err) {
|
|
536
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
537
|
+
throw new Error(`[apm] \u89E3\u6790 ${HOME_REPOSITORY_JSON} \u5931\u8D25: ${detail}`);
|
|
547
538
|
}
|
|
548
|
-
|
|
549
|
-
|
|
539
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`[apm] ${HOME_REPOSITORY_JSON} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
const entries = [];
|
|
545
|
+
for (const [rawKey, rawVal] of Object.entries(
|
|
546
|
+
parsed
|
|
547
|
+
)) {
|
|
548
|
+
const pathKey = rawKey.trim();
|
|
549
|
+
if (!isSimpleChildDirName(pathKey)) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`[apm] ${HOME_REPOSITORY_JSON} \u975E\u6CD5\u76EE\u5F55 key: ${JSON.stringify(rawKey)}\uFF08\u987B\u4E3A\u5F53\u524D\u4E0B\u7EA7\u76EE\u5F55\u540D\uFF09`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
if (typeof rawVal !== "string" || !rawVal.trim()) {
|
|
555
|
+
throw new Error(
|
|
556
|
+
`[apm] ${HOME_REPOSITORY_JSON}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
entries.push({ path: pathKey, remoteUrl: rawVal.trim() });
|
|
560
|
+
}
|
|
561
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
562
|
+
if (entries.length === 0) {
|
|
563
|
+
throw new Error(`[apm] ${HOME_REPOSITORY_JSON} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
|
|
564
|
+
}
|
|
565
|
+
return entries;
|
|
566
|
+
}
|
|
567
|
+
function resolveHomeRepositoryAbsolutePaths(workdirInput, entries = loadHomeRepositoryEntries()) {
|
|
568
|
+
const workdir = resolveWorkdirPath(workdirInput);
|
|
569
|
+
return entries.map((entry) => resolve3(join4(workdir, entry.path)));
|
|
570
|
+
}
|
|
571
|
+
function tryLoadHomeRepositoryEntries() {
|
|
572
|
+
const path = toFsPath(HOME_REPOSITORY_JSON);
|
|
573
|
+
if (!existsSync2(path)) return null;
|
|
574
|
+
return loadHomeRepositoryEntries();
|
|
550
575
|
}
|
|
551
576
|
|
|
552
577
|
// src/utils/workspace-repos.ts
|
|
553
578
|
import {
|
|
554
|
-
existsSync as
|
|
579
|
+
existsSync as existsSync3,
|
|
555
580
|
mkdirSync as mkdirSync3,
|
|
556
|
-
readFileSync as
|
|
581
|
+
readFileSync as readFileSync3,
|
|
557
582
|
readdirSync as readdirSync2,
|
|
558
583
|
statSync as statSync2,
|
|
559
584
|
writeFileSync as writeFileSync2
|
|
560
585
|
} from "fs";
|
|
561
|
-
import { basename, dirname, join as
|
|
586
|
+
import { basename, dirname as dirname2, join as join5, relative, resolve as resolve4 } from "path";
|
|
587
|
+
|
|
588
|
+
// src/utils/git-remote.ts
|
|
589
|
+
import { execFile as execFile2 } from "child_process";
|
|
590
|
+
import { promisify as promisify2 } from "util";
|
|
591
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
592
|
+
function toHttpsGitRemoteUrl(raw) {
|
|
593
|
+
let s = raw.trim();
|
|
594
|
+
if (!s) return null;
|
|
595
|
+
const sshScp = /^git@([^:]+):(.+)$/.exec(s);
|
|
596
|
+
if (sshScp) {
|
|
597
|
+
s = `https://${sshScp[1]}/${sshScp[2]}`;
|
|
598
|
+
} else {
|
|
599
|
+
const sshUri = /^ssh:\/\/(?:git@)?([^/]+)\/(.+)$/i.exec(s);
|
|
600
|
+
if (sshUri) {
|
|
601
|
+
s = `https://${sshUri[1]}/${sshUri[2]}`;
|
|
602
|
+
} else if (/^http:\/\//i.test(s)) {
|
|
603
|
+
s = `https://${s.slice("http://".length)}`;
|
|
604
|
+
} else if (!/^https:\/\//i.test(s)) {
|
|
605
|
+
const scpLike = /^([^/:]+):(.+)$/.exec(s);
|
|
606
|
+
if (scpLike && !scpLike[1].includes(".")) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
if (scpLike) {
|
|
610
|
+
s = `https://${scpLike[1]}/${scpLike[2]}`;
|
|
611
|
+
} else if (s.includes("/")) {
|
|
612
|
+
s = `https://${s}`;
|
|
613
|
+
} else {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
s = s.replace(/\/+$/, "");
|
|
619
|
+
if (!/^https:\/\//i.test(s)) return null;
|
|
620
|
+
return s;
|
|
621
|
+
}
|
|
622
|
+
async function tryReadGitOriginUrl(cwd) {
|
|
623
|
+
try {
|
|
624
|
+
const { stdout } = await execFileAsync2(
|
|
625
|
+
"git",
|
|
626
|
+
["config", "--get", "remote.origin.url"],
|
|
627
|
+
{ cwd, encoding: "utf8", maxBuffer: 1024 * 1024 }
|
|
628
|
+
);
|
|
629
|
+
const url = stdout.trim();
|
|
630
|
+
return url || null;
|
|
631
|
+
} catch {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async function tryReadHttpsGitOriginUrl(cwd) {
|
|
636
|
+
const raw = await tryReadGitOriginUrl(cwd);
|
|
637
|
+
if (!raw) return null;
|
|
638
|
+
return toHttpsGitRemoteUrl(raw);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/utils/workspace-repos.ts
|
|
562
642
|
var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
|
|
563
643
|
var WORKSPACE_REPOS_VERSION = 1;
|
|
564
644
|
var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([".apm", "node_modules"]);
|
|
565
645
|
function hasGitAt(dir) {
|
|
566
|
-
return
|
|
646
|
+
return existsSync3(toFsPath(join5(dir, ".git")));
|
|
567
647
|
}
|
|
568
648
|
function isSkippableChildName(name) {
|
|
569
649
|
if (SKIP_DIR_NAMES.has(name)) return true;
|
|
@@ -571,25 +651,21 @@ function isSkippableChildName(name) {
|
|
|
571
651
|
return false;
|
|
572
652
|
}
|
|
573
653
|
function manifestPath(workdir) {
|
|
574
|
-
return
|
|
654
|
+
return join5(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
|
|
575
655
|
}
|
|
576
656
|
function absoluteRepoPath(workdir, entry) {
|
|
577
|
-
|
|
578
|
-
return workdir;
|
|
579
|
-
}
|
|
580
|
-
return resolve3(workdir, entry.path);
|
|
657
|
+
return resolve4(workdir, entry.path);
|
|
581
658
|
}
|
|
582
659
|
function toRelativeRepoPath(workdir, repoAbs) {
|
|
583
|
-
|
|
584
|
-
return rel === "" ? "." : rel;
|
|
660
|
+
return relative(workdir, repoAbs).replace(/\\/g, "/");
|
|
585
661
|
}
|
|
586
662
|
function normalizeRepoEntry(raw) {
|
|
587
663
|
if (!raw || typeof raw !== "object") return null;
|
|
588
664
|
const o = raw;
|
|
589
665
|
if (typeof o.path !== "string" || !o.path.trim()) return null;
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
};
|
|
666
|
+
const path = o.path.trim().replace(/\\/g, "/");
|
|
667
|
+
if (path === "." || path.includes("/")) return null;
|
|
668
|
+
const entry = { path };
|
|
593
669
|
if (typeof o.remoteUrl === "string" && o.remoteUrl.trim()) {
|
|
594
670
|
entry.remoteUrl = o.remoteUrl.trim();
|
|
595
671
|
} else if (o.remoteUrl === null) {
|
|
@@ -598,18 +674,18 @@ function normalizeRepoEntry(raw) {
|
|
|
598
674
|
return entry;
|
|
599
675
|
}
|
|
600
676
|
function readManifest(workdir) {
|
|
601
|
-
const
|
|
602
|
-
if (!
|
|
677
|
+
const path = toFsPath(manifestPath(workdir));
|
|
678
|
+
if (!existsSync3(path)) {
|
|
603
679
|
return null;
|
|
604
680
|
}
|
|
605
681
|
try {
|
|
606
682
|
const raw = JSON.parse(
|
|
607
|
-
|
|
683
|
+
readFileSync3(path, "utf8")
|
|
608
684
|
);
|
|
609
685
|
if (raw?.version !== WORKSPACE_REPOS_VERSION) {
|
|
610
686
|
return null;
|
|
611
687
|
}
|
|
612
|
-
if (raw.kind !== "
|
|
688
|
+
if (raw.kind !== "multi") {
|
|
613
689
|
return null;
|
|
614
690
|
}
|
|
615
691
|
if (!Array.isArray(raw.repos) || raw.repos.length === 0) {
|
|
@@ -624,7 +700,7 @@ function readManifest(workdir) {
|
|
|
624
700
|
}
|
|
625
701
|
return {
|
|
626
702
|
version: WORKSPACE_REPOS_VERSION,
|
|
627
|
-
kind:
|
|
703
|
+
kind: "multi",
|
|
628
704
|
workdir: raw.workdir,
|
|
629
705
|
repos,
|
|
630
706
|
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt.trim() ? raw.scannedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -636,8 +712,8 @@ function readManifest(workdir) {
|
|
|
636
712
|
function writeManifest(workdir, manifest) {
|
|
637
713
|
const apmDir = toFsPath(workspaceApmDir(workdir));
|
|
638
714
|
mkdirSync3(apmDir, { recursive: true });
|
|
639
|
-
const
|
|
640
|
-
writeFileSync2(
|
|
715
|
+
const path = toFsPath(manifestPath(workdir));
|
|
716
|
+
writeFileSync2(path, `${JSON.stringify(manifest, null, 2)}
|
|
641
717
|
`, "utf8");
|
|
642
718
|
}
|
|
643
719
|
function manifestStillValid(workdir, manifest) {
|
|
@@ -648,6 +724,9 @@ function manifestStillValid(workdir, manifest) {
|
|
|
648
724
|
if (typeof entry?.path !== "string" || !entry.path.trim()) {
|
|
649
725
|
return false;
|
|
650
726
|
}
|
|
727
|
+
if (entry.path === "." || entry.path.includes("/")) {
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
651
730
|
const abs = absoluteRepoPath(workdir, entry);
|
|
652
731
|
if (!hasGitAt(abs)) {
|
|
653
732
|
return false;
|
|
@@ -657,17 +736,8 @@ function manifestStillValid(workdir, manifest) {
|
|
|
657
736
|
}
|
|
658
737
|
function scanWorkspaceRepos(workdirInput) {
|
|
659
738
|
const workdir = resolveWorkdirPath(workdirInput);
|
|
660
|
-
if (hasGitAt(workdir)) {
|
|
661
|
-
return {
|
|
662
|
-
version: WORKSPACE_REPOS_VERSION,
|
|
663
|
-
kind: "single",
|
|
664
|
-
workdir,
|
|
665
|
-
repos: [{ path: "." }],
|
|
666
|
-
scannedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
739
|
const fsWorkdir = toFsPath(workdir);
|
|
670
|
-
if (!
|
|
740
|
+
if (!existsSync3(fsWorkdir) || !statSync2(fsWorkdir).isDirectory()) {
|
|
671
741
|
throw new Error(`[apm] \u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${workdir}`);
|
|
672
742
|
}
|
|
673
743
|
const repos = [];
|
|
@@ -675,9 +745,9 @@ function scanWorkspaceRepos(workdirInput) {
|
|
|
675
745
|
if (isSkippableChildName(name)) {
|
|
676
746
|
continue;
|
|
677
747
|
}
|
|
678
|
-
const childAbs =
|
|
748
|
+
const childAbs = resolve4(workdir, name);
|
|
679
749
|
const childFs = toFsPath(childAbs);
|
|
680
|
-
if (!
|
|
750
|
+
if (!existsSync3(childFs) || !statSync2(childFs).isDirectory()) {
|
|
681
751
|
continue;
|
|
682
752
|
}
|
|
683
753
|
if (!hasGitAt(childAbs)) {
|
|
@@ -688,7 +758,7 @@ function scanWorkspaceRepos(workdirInput) {
|
|
|
688
758
|
repos.sort((a, b) => a.path.localeCompare(b.path));
|
|
689
759
|
if (repos.length === 0) {
|
|
690
760
|
throw new Error(
|
|
691
|
-
`[apm] \u5DE5\u4F5C\u533A ${workdir} \
|
|
761
|
+
`[apm] \u5DE5\u4F5C\u533A ${workdir} \u4E00\u7EA7\u5B50\u76EE\u5F55\u4E2D\u672A\u53D1\u73B0 git \u4ED3\u5E93`
|
|
692
762
|
);
|
|
693
763
|
}
|
|
694
764
|
return {
|
|
@@ -716,7 +786,7 @@ function loadWorkspaceReposCache(workdirInput) {
|
|
|
716
786
|
throw new Error(`[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58\u65E0\u6548\uFF08repos \u4E3A\u7A7A\uFF09\uFF0C\u8BF7\u5F3A\u5236\u91CD\u65B0\u626B\u63CF`);
|
|
717
787
|
}
|
|
718
788
|
console.log(
|
|
719
|
-
`[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58
|
|
789
|
+
`[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 repos=${cached.repos.map((r) => r.path).join(", ")}`
|
|
720
790
|
);
|
|
721
791
|
return {
|
|
722
792
|
...cached,
|
|
@@ -754,7 +824,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
|
|
|
754
824
|
const cached = readManifest(workdir);
|
|
755
825
|
if (cached && manifestStillValid(workdir, cached)) {
|
|
756
826
|
console.log(
|
|
757
|
-
`[apm] \u4F7F\u7528\u7F13\u5B58\u7684\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355
|
|
827
|
+
`[apm] \u4F7F\u7528\u7F13\u5B58\u7684\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 repos=${cached.repos.length}`
|
|
758
828
|
);
|
|
759
829
|
return cached;
|
|
760
830
|
}
|
|
@@ -771,7 +841,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
|
|
|
771
841
|
}
|
|
772
842
|
writeManifest(workdir, scanned);
|
|
773
843
|
console.log(
|
|
774
|
-
`[apm] \u5DF2\u626B\u63CF\u5DE5\u4F5C\u533A\u4ED3\u5E93
|
|
844
|
+
`[apm] \u5DF2\u626B\u63CF\u5DE5\u4F5C\u533A\u4ED3\u5E93 repos=${scanned.repos.map((r) => r.path).join(", ")} \u2192 .apm/${WORKSPACE_REPOS_MANIFEST}`
|
|
775
845
|
);
|
|
776
846
|
return scanned;
|
|
777
847
|
}
|
|
@@ -782,48 +852,64 @@ function resolveWorkspaceRepoAbsolutePaths(manifest) {
|
|
|
782
852
|
}
|
|
783
853
|
function formatRepoLabel(workdir, repoAbs) {
|
|
784
854
|
const rel = toRelativeRepoPath(resolveWorkdirPath(workdir), repoAbs);
|
|
785
|
-
return rel
|
|
855
|
+
return rel || basename(repoAbs) || repoAbs;
|
|
786
856
|
}
|
|
787
857
|
|
|
788
858
|
// src/commands/branch.ts
|
|
789
859
|
var TASK_BRANCH_PREFIX = "feat/task-";
|
|
790
|
-
async function
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
cwd,
|
|
794
|
-
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
795
|
-
true
|
|
796
|
-
);
|
|
797
|
-
return true;
|
|
798
|
-
} catch {
|
|
799
|
-
return false;
|
|
800
|
-
}
|
|
860
|
+
async function commitWorkspaceReposIfDirty(workdir, message) {
|
|
861
|
+
const result = await commitTaskReposIfDirty(workdir, message);
|
|
862
|
+
return result.committed;
|
|
801
863
|
}
|
|
802
|
-
async function
|
|
803
|
-
|
|
804
|
-
if (!await isWorkingTreeDirty(cwd)) {
|
|
805
|
-
return false;
|
|
806
|
-
}
|
|
807
|
-
const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
|
|
808
|
-
await execGit(cwd, ["add", "-A"]);
|
|
809
|
-
await execGit(cwd, ["commit", "-m", commitMessage]);
|
|
810
|
-
console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
|
|
811
|
-
return true;
|
|
864
|
+
async function commitTaskReposIfDirty(workdir, message) {
|
|
865
|
+
return syncTaskRepos(workdir, message);
|
|
812
866
|
}
|
|
813
|
-
async function
|
|
867
|
+
async function syncTaskRepos(workdir, message) {
|
|
814
868
|
const workdirPath = resolveWorkdirPath(workdir);
|
|
815
|
-
const
|
|
816
|
-
const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
869
|
+
const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
|
|
817
870
|
let committed = 0;
|
|
871
|
+
let commitSkipped = 0;
|
|
872
|
+
let pushed = 0;
|
|
873
|
+
let pushSkipped = 0;
|
|
874
|
+
const failed = [];
|
|
818
875
|
for (const gitRoot of repoRoots) {
|
|
819
876
|
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
877
|
+
try {
|
|
878
|
+
const didCommit = await commitWorkingTreeIfDirty(gitRoot, message);
|
|
879
|
+
if (didCommit) {
|
|
880
|
+
committed += 1;
|
|
881
|
+
console.log(`[apm] \u5DF2\u63D0\u4EA4 @ ${label}`);
|
|
882
|
+
} else {
|
|
883
|
+
commitSkipped += 1;
|
|
884
|
+
}
|
|
885
|
+
} catch (err) {
|
|
886
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
887
|
+
console.error(`[apm] \u4ED3\u5E93 ${label} \u63D0\u4EA4\u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
|
|
888
|
+
failed.push(label);
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
try {
|
|
892
|
+
const didPush = await pushCurrentBranch(gitRoot);
|
|
893
|
+
if (didPush) {
|
|
894
|
+
pushed += 1;
|
|
895
|
+
console.log(`[apm] \u5DF2 push @ ${label}`);
|
|
896
|
+
} else {
|
|
897
|
+
pushSkipped += 1;
|
|
898
|
+
}
|
|
899
|
+
} catch (err) {
|
|
900
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
901
|
+
console.error(`[apm] \u4ED3\u5E93 ${label} push \u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
|
|
902
|
+
failed.push(label);
|
|
824
903
|
}
|
|
825
904
|
}
|
|
826
|
-
return
|
|
905
|
+
return {
|
|
906
|
+
committed,
|
|
907
|
+
commitSkipped,
|
|
908
|
+
pushed,
|
|
909
|
+
pushSkipped,
|
|
910
|
+
failed,
|
|
911
|
+
repos: repoRoots
|
|
912
|
+
};
|
|
827
913
|
}
|
|
828
914
|
async function pushWorkspaceRepos(workdir) {
|
|
829
915
|
const workdirPath = resolveWorkdirPath(workdir);
|
|
@@ -852,58 +938,13 @@ function branchNameForTask(taskId) {
|
|
|
852
938
|
}
|
|
853
939
|
return `${TASK_BRANCH_PREFIX}${id}`;
|
|
854
940
|
}
|
|
855
|
-
async function
|
|
941
|
+
async function ensureTaskFeatureBranch(branch, projectId, options) {
|
|
856
942
|
const cwd = options.cwd ?? process.cwd();
|
|
857
943
|
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
858
|
-
await
|
|
859
|
-
const current = await getCurrentBranch(gitRoot);
|
|
860
|
-
if (current === branch) {
|
|
861
|
-
console.log(`[apm] \u5DF2\u5728\u5206\u652F ${branch}\uFF0C\u8DF3\u8FC7`);
|
|
862
|
-
return branch;
|
|
863
|
-
}
|
|
864
|
-
await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
|
|
865
|
-
if (await isWorkingTreeDirty(gitRoot)) {
|
|
866
|
-
await execGit(gitRoot, [
|
|
867
|
-
"stash",
|
|
868
|
-
"push",
|
|
869
|
-
"-u",
|
|
870
|
-
"-m",
|
|
871
|
-
`apm: switch to ${branch}`
|
|
872
|
-
]);
|
|
873
|
-
}
|
|
874
|
-
const remoteExists = await remoteBranchExists(gitRoot, branch);
|
|
875
|
-
if (remoteExists) {
|
|
876
|
-
await execGit(gitRoot, ["fetch", "origin", branch]);
|
|
877
|
-
await execGit(gitRoot, ["checkout", "-B", branch, `origin/${branch}`]);
|
|
878
|
-
} else if (await localBranchExists(gitRoot, branch)) {
|
|
879
|
-
await execGit(gitRoot, ["checkout", branch]);
|
|
880
|
-
console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
|
|
881
|
-
} else {
|
|
882
|
-
await execGit(gitRoot, ["fetch", "origin", baselineBranch]);
|
|
883
|
-
try {
|
|
884
|
-
await execGit(gitRoot, [
|
|
885
|
-
"checkout",
|
|
886
|
-
"--no-track",
|
|
887
|
-
"-b",
|
|
888
|
-
branch,
|
|
889
|
-
`origin/${baselineBranch}`
|
|
890
|
-
]);
|
|
891
|
-
await execGit(gitRoot, ["push", "-u", "origin", branch]);
|
|
892
|
-
} catch (err) {
|
|
893
|
-
if (await localBranchExists(gitRoot, branch)) {
|
|
894
|
-
if (await getCurrentBranch(gitRoot) !== branch) {
|
|
895
|
-
await execGit(gitRoot, ["checkout", branch]);
|
|
896
|
-
}
|
|
897
|
-
console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
|
|
898
|
-
} else {
|
|
899
|
-
throw err;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
|
|
944
|
+
await ensureFeatureBranch(gitRoot, branch, projectId);
|
|
904
945
|
return branch;
|
|
905
946
|
}
|
|
906
|
-
async function
|
|
947
|
+
async function resolveProjectIdForTask(taskId) {
|
|
907
948
|
const trimmedTaskId = taskId.trim();
|
|
908
949
|
if (!trimmedTaskId) {
|
|
909
950
|
throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
@@ -913,75 +954,73 @@ async function runTaskBranch(taskId, options = {}) {
|
|
|
913
954
|
const projectBaseline = await api.cli.projectBaseBranch({
|
|
914
955
|
taskId: trimmedTaskId
|
|
915
956
|
});
|
|
916
|
-
const
|
|
917
|
-
if (!
|
|
918
|
-
throw new Error("[apm] \u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F\uFF0C\u65E0\u6CD5\u521B\u5EFA WebIDE \u7279\u6027\u5206\u652F");
|
|
957
|
+
const projectId = projectBaseline.baseBranch?.trim() ?? "";
|
|
958
|
+
if (!projectId) {
|
|
959
|
+
throw new Error("[apm] \u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F / \u9879\u76EE ID\uFF0C\u65E0\u6CD5\u521B\u5EFA WebIDE \u7279\u6027\u5206\u652F");
|
|
919
960
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
961
|
+
return projectId;
|
|
962
|
+
}
|
|
963
|
+
function resolveTaskBranchRepoRoots(workdirPath) {
|
|
964
|
+
const entries = tryLoadHomeRepositoryEntries();
|
|
965
|
+
if (entries) {
|
|
966
|
+
const roots = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
|
|
927
967
|
console.log(
|
|
928
|
-
`[apm] \
|
|
968
|
+
`[apm] \u8BFB\u53D6 ${HOME_REPOSITORY_JSON} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
|
|
929
969
|
);
|
|
930
|
-
|
|
931
|
-
...options,
|
|
932
|
-
cwd: gitRoot
|
|
933
|
-
});
|
|
970
|
+
return roots;
|
|
934
971
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
baseline: baselineBranch,
|
|
938
|
-
kind: manifest.kind,
|
|
939
|
-
repos: repoRoots
|
|
940
|
-
};
|
|
972
|
+
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
973
|
+
return resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
941
974
|
}
|
|
942
|
-
async function
|
|
943
|
-
const
|
|
944
|
-
if (!
|
|
945
|
-
throw new Error("[apm] \
|
|
975
|
+
async function runTaskBranch(taskId, projectId, options = {}) {
|
|
976
|
+
const trimmedTaskId = taskId.trim();
|
|
977
|
+
if (!trimmedTaskId) {
|
|
978
|
+
throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
979
|
+
}
|
|
980
|
+
const trimmedProjectId = projectId.trim();
|
|
981
|
+
if (!trimmedProjectId) {
|
|
982
|
+
throw new Error("[apm] projectId\uFF08\u9879\u76EE ID\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
|
|
983
|
+
}
|
|
984
|
+
if (/[\s\\]/.test(trimmedProjectId) || trimmedProjectId.includes("..")) {
|
|
985
|
+
throw new Error("[apm] \u9879\u76EE ID \u975E\u6CD5");
|
|
946
986
|
}
|
|
947
987
|
const cwd = options.cwd ?? process.cwd();
|
|
948
988
|
const workdirPath = resolveWorkdirPath(cwd);
|
|
949
|
-
const
|
|
950
|
-
|
|
989
|
+
const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
|
|
990
|
+
if (repoRoots.length === 0) {
|
|
991
|
+
throw new Error("[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7279\u6027\u5206\u652F");
|
|
992
|
+
}
|
|
993
|
+
const branch = branchNameForTask(trimmedTaskId);
|
|
994
|
+
const repoLabels = repoRoots.map(
|
|
995
|
+
(gitRoot) => formatRepoLabel(workdirPath, gitRoot)
|
|
996
|
+
);
|
|
997
|
+
console.log(
|
|
998
|
+
`[apm] \u591A\u4ED3\u786E\u8BA4\u7279\u6027\u5206\u652F ${branch} projectId=${trimmedProjectId} repos=${repoLabels.join(",")}`
|
|
999
|
+
);
|
|
951
1000
|
for (const gitRoot of repoRoots) {
|
|
952
1001
|
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
953
|
-
console.log(
|
|
954
|
-
|
|
1002
|
+
console.log(
|
|
1003
|
+
`[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (projectId=${trimmedProjectId})`
|
|
1004
|
+
);
|
|
1005
|
+
try {
|
|
1006
|
+
await ensureTaskFeatureBranch(branch, trimmedProjectId, {
|
|
1007
|
+
...options,
|
|
1008
|
+
cwd: gitRoot
|
|
1009
|
+
});
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1012
|
+
throw new Error(
|
|
1013
|
+
`[apm] \u4ED3\u5E93 ${label} \u786E\u8BA4\u7279\u6027\u5206\u652F\u5931\u8D25\uFF08\u591A\u4ED3\u4EFB\u4E00\u5931\u8D25\u5219\u6574\u6B21\u5931\u8D25\uFF09: ${detail}`
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
955
1016
|
}
|
|
956
1017
|
return {
|
|
957
|
-
|
|
958
|
-
|
|
1018
|
+
branch,
|
|
1019
|
+
projectId: trimmedProjectId,
|
|
1020
|
+
kind: "multi",
|
|
959
1021
|
repos: repoRoots
|
|
960
1022
|
};
|
|
961
1023
|
}
|
|
962
|
-
async function checkoutBaselineBranch(baselineBranch, options = {}) {
|
|
963
|
-
const baseline = baselineBranch.trim().replace(/^origin\//, "");
|
|
964
|
-
if (!baseline) {
|
|
965
|
-
throw new Error("[apm] \u57FA\u7EBF\u5206\u652F\u4E0D\u80FD\u4E3A\u7A7A");
|
|
966
|
-
}
|
|
967
|
-
const cwd = options.cwd ?? process.cwd();
|
|
968
|
-
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
969
|
-
await ensureGitRepo(gitRoot);
|
|
970
|
-
if (await isWorkingTreeDirty(gitRoot)) {
|
|
971
|
-
throw new Error(`[apm] \u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u53D8\u66F4\uFF0C\u65E0\u6CD5\u5207\u6362\u5230\u57FA\u7EBF\u5206\u652F ${baseline}`);
|
|
972
|
-
}
|
|
973
|
-
await ensureRemoteBaselineBranch(gitRoot, baseline);
|
|
974
|
-
const current = await getCurrentBranch(gitRoot);
|
|
975
|
-
if (current !== baseline) {
|
|
976
|
-
await execGit(gitRoot, ["checkout", "-B", baseline, `origin/${baseline}`]);
|
|
977
|
-
}
|
|
978
|
-
await execGit(gitRoot, ["reset", "--hard", `origin/${baseline}`]);
|
|
979
|
-
const head = (await execGit(gitRoot, ["rev-parse", "--short", "HEAD"], true)).trim();
|
|
980
|
-
console.log(
|
|
981
|
-
`[apm] \u5DF2\u5207\u6362\u5230\u57FA\u7EBF ${baseline}\uFF0C\u5E76\u5BF9\u9F50 origin/${baseline}\uFF08${head}\uFF09`
|
|
982
|
-
);
|
|
983
|
-
return baseline;
|
|
984
|
-
}
|
|
985
1024
|
|
|
986
1025
|
// src/commands/connect/cursor-agent.ts
|
|
987
1026
|
import {
|
|
@@ -1631,8 +1670,8 @@ function createUpsertWebIdeTestCasesTool(options) {
|
|
|
1631
1670
|
}
|
|
1632
1671
|
|
|
1633
1672
|
// src/commands/connect/tools/mysql-tools.ts
|
|
1634
|
-
import { existsSync as
|
|
1635
|
-
import { isAbsolute, relative as relative2, resolve as
|
|
1673
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
1674
|
+
import { isAbsolute, relative as relative2, resolve as resolve5 } from "node:path";
|
|
1636
1675
|
import mysql from "mysql2/promise";
|
|
1637
1676
|
var MAX_RESULT_ROWS = 100;
|
|
1638
1677
|
var EXEC_TIMEOUT_MS = 6e4;
|
|
@@ -1703,14 +1742,14 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
|
|
|
1703
1742
|
if (!isAbsolute(absolutePath)) {
|
|
1704
1743
|
return { ok: false, error: "path \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
|
|
1705
1744
|
}
|
|
1706
|
-
const workdirAbs =
|
|
1707
|
-
const targetAbs =
|
|
1745
|
+
const workdirAbs = resolve5(toFsPath(workdir));
|
|
1746
|
+
const targetAbs = resolve5(toFsPath(absolutePath));
|
|
1708
1747
|
const rel = relative2(workdirAbs, targetAbs);
|
|
1709
1748
|
if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
|
|
1710
1749
|
return { ok: false, error: "path \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
|
|
1711
1750
|
}
|
|
1712
1751
|
const fsPath = toFsPath(targetAbs);
|
|
1713
|
-
if (!
|
|
1752
|
+
if (!existsSync4(fsPath)) {
|
|
1714
1753
|
return { ok: false, error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${absolutePath}` };
|
|
1715
1754
|
}
|
|
1716
1755
|
let st;
|
|
@@ -1732,7 +1771,7 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
|
|
|
1732
1771
|
return { ok: true, absPath: targetAbs, displayPath };
|
|
1733
1772
|
}
|
|
1734
1773
|
function readSqlFile(absPath) {
|
|
1735
|
-
return
|
|
1774
|
+
return readFileSync4(toFsPath(absPath), "utf8");
|
|
1736
1775
|
}
|
|
1737
1776
|
function createMysqlExecuteTool(options) {
|
|
1738
1777
|
const { taskId, executionId, workdir, upsertSqlExecution } = options;
|
|
@@ -1910,2373 +1949,1213 @@ function createMysqlExecuteTool(options) {
|
|
|
1910
1949
|
};
|
|
1911
1950
|
}
|
|
1912
1951
|
|
|
1913
|
-
// src/commands/connect/tools/
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
var MinioClient = class {
|
|
1925
|
-
inner;
|
|
1926
|
-
constructor(opts) {
|
|
1927
|
-
const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
|
|
1928
|
-
this.inner = new Minio.Client({
|
|
1929
|
-
endPoint,
|
|
1930
|
-
port: opts.port,
|
|
1931
|
-
useSSL: opts.useSSL,
|
|
1932
|
-
accessKey: opts.accessKey,
|
|
1933
|
-
secretKey: opts.secretKey
|
|
1934
|
-
});
|
|
1952
|
+
// src/commands/connect/tools/index.ts
|
|
1953
|
+
var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
|
|
1954
|
+
AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
|
|
1955
|
+
\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
|
|
1956
|
+
\u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
|
|
1957
|
+
var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
|
|
1958
|
+
\u6240\u6709\u6587\u4EF6\u8BFB\u5199\u3001\u641C\u7D22\uFF08Grep/Glob/Read\uFF09\u3001Shell \u5FC5\u987B\u4E25\u683C\u9650\u5236\u5728\u5F53\u524D\u5DE5\u4F5C\u533A cwd \u5185\u3002
|
|
1959
|
+
\u7981\u6B62\u8BBF\u95EE\u5DE5\u4F5C\u533A\u5916\u8DEF\u5F84\uFF08\u5982\u5176\u5B83\u9879\u76EE\u3001~/.cursor\u3001agent-transcripts\u3001\u7CFB\u7EDF\u76EE\u5F55\u7B49\uFF09\u3002`;
|
|
1960
|
+
function wantsTool(name, options, legacyDefault) {
|
|
1961
|
+
if (options.toolNames !== void 0) {
|
|
1962
|
+
return options.toolNames.includes(name);
|
|
1935
1963
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1964
|
+
return legacyDefault;
|
|
1965
|
+
}
|
|
1966
|
+
function createCursorCustomTools(cfg2, options) {
|
|
1967
|
+
const enableAppend = options.enableAppendMessage !== false && Boolean(options.appendMessageContent);
|
|
1968
|
+
const enableAsk = options.enableAskQuestion !== false;
|
|
1969
|
+
const enablePlan = options.enableWebIdePlanTools === true;
|
|
1970
|
+
const enableMysql = options.enableMysqlTools === true;
|
|
1971
|
+
const tools = {
|
|
1972
|
+
...wantsTool("AppendMessage", options, enableAppend) && options.appendMessageContent ? createAppendMessageCustomTools({
|
|
1973
|
+
appendContent: options.appendMessageContent
|
|
1974
|
+
}) : {},
|
|
1975
|
+
...wantsTool("AskQuestion", options, enableAsk) ? createAskQuestionTool({
|
|
1976
|
+
onInvoke: options?.onAskQuestion,
|
|
1977
|
+
execute: options?.askQuestionExecute
|
|
1978
|
+
}) : {}
|
|
1979
|
+
};
|
|
1980
|
+
if (options?.taskId && options.workdir) {
|
|
1981
|
+
const { cli } = createApmApiClient(cfg2);
|
|
1982
|
+
const taskId = options.taskId;
|
|
1983
|
+
if (wantsTool("RecommendPlan", options, enablePlan)) {
|
|
1984
|
+
Object.assign(
|
|
1985
|
+
tools,
|
|
1986
|
+
createRecommendPlanTool({
|
|
1987
|
+
webideRecommendPlan: (args) => cli.webideRecommendPlan({ taskId, ...args })
|
|
1988
|
+
})
|
|
1989
|
+
);
|
|
1940
1990
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1991
|
+
if (wantsTool("UpsertWebIdePlan", options, enablePlan)) {
|
|
1992
|
+
Object.assign(
|
|
1993
|
+
tools,
|
|
1994
|
+
createUpsertWebIdePlanTool({
|
|
1995
|
+
webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
|
|
1996
|
+
})
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
if (wantsTool("UpsertWebIdeChecklist", options, enablePlan)) {
|
|
2000
|
+
Object.assign(
|
|
2001
|
+
tools,
|
|
2002
|
+
createUpsertWebIdeChecklistTool({
|
|
2003
|
+
webideUpsertChecklist: (args) => cli.webideUpsertChecklist({ taskId, ...args })
|
|
2004
|
+
})
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
if (wantsTool("GetWebIdeChecklist", options, enablePlan)) {
|
|
2008
|
+
Object.assign(
|
|
2009
|
+
tools,
|
|
2010
|
+
createGetWebIdeChecklistTool({
|
|
2011
|
+
boundTaskId: taskId,
|
|
2012
|
+
webideGetChecklist: (args) => cli.webideGetChecklist(args)
|
|
2013
|
+
})
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
2016
|
+
if (wantsTool("UpsertWebIdeTestCases", options, enablePlan)) {
|
|
2017
|
+
Object.assign(
|
|
2018
|
+
tools,
|
|
2019
|
+
createUpsertWebIdeTestCasesTool({
|
|
2020
|
+
webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
|
|
2021
|
+
})
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
|
|
2025
|
+
Object.assign(
|
|
2026
|
+
tools,
|
|
2027
|
+
createMergeWebIdePullRequestsTool({
|
|
2028
|
+
webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
|
|
2029
|
+
})
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
if (wantsTool("MysqlExecute", options, enableMysql) && options.sqlExecutionId && options.workdir) {
|
|
2033
|
+
Object.assign(
|
|
2034
|
+
tools,
|
|
2035
|
+
createMysqlExecuteTool({
|
|
2036
|
+
taskId,
|
|
2037
|
+
executionId: options.sqlExecutionId,
|
|
2038
|
+
workdir: options.workdir,
|
|
2039
|
+
upsertSqlExecution: (args) => cli.webideUpsertSqlExecution(args)
|
|
2040
|
+
})
|
|
2041
|
+
);
|
|
1982
2042
|
}
|
|
1983
|
-
return Buffer.concat(chunks);
|
|
1984
2043
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
// src/commands/connect/apm-log-minio.ts
|
|
1988
|
-
async function fetchApmLogStorage(cfg2) {
|
|
1989
|
-
const api = createApmApiClient(cfg2);
|
|
1990
|
-
return api.cli.getApmLogStorage(void 0);
|
|
1991
|
-
}
|
|
1992
|
-
async function createApmLogMinioClient(cfg2) {
|
|
1993
|
-
const storage = await fetchApmLogStorage(cfg2);
|
|
1994
|
-
return {
|
|
1995
|
-
client: new MinioClient({
|
|
1996
|
-
endPoint: storage.endpoint,
|
|
1997
|
-
port: storage.port,
|
|
1998
|
-
useSSL: storage.useSsl,
|
|
1999
|
-
accessKey: storage.accessKey,
|
|
2000
|
-
secretKey: storage.secretKey
|
|
2001
|
-
}),
|
|
2002
|
-
bucket: storage.bucket,
|
|
2003
|
-
...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
|
|
2004
|
-
};
|
|
2044
|
+
return tools;
|
|
2005
2045
|
}
|
|
2046
|
+
function withPlanModeToolHint(prompt, mode) {
|
|
2047
|
+
if (mode !== "plan") {
|
|
2048
|
+
return prompt;
|
|
2049
|
+
}
|
|
2050
|
+
return `${prompt.trim()}
|
|
2006
2051
|
|
|
2007
|
-
|
|
2008
|
-
var SKIP_DIR_NAMES2 = /* @__PURE__ */ new Set([
|
|
2009
|
-
".apm",
|
|
2010
|
-
".git",
|
|
2011
|
-
".idea",
|
|
2012
|
-
".vscode",
|
|
2013
|
-
"node_modules",
|
|
2014
|
-
"target",
|
|
2015
|
-
"dist",
|
|
2016
|
-
"build",
|
|
2017
|
-
"out",
|
|
2018
|
-
"coverage",
|
|
2019
|
-
"__pycache__"
|
|
2020
|
-
]);
|
|
2021
|
-
function isSqlFileName(name) {
|
|
2022
|
-
return name.toLowerCase().endsWith(".sql");
|
|
2052
|
+
${PLAN_MODE_ASK_QUESTION_HINT}`;
|
|
2023
2053
|
}
|
|
2024
|
-
function
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2054
|
+
function withWorkspaceBoundaryHint(prompt) {
|
|
2055
|
+
return `${prompt.trim()}
|
|
2056
|
+
|
|
2057
|
+
${WORKSPACE_BOUNDARY_HINT}`;
|
|
2028
2058
|
}
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2059
|
+
|
|
2060
|
+
// src/commands/connect/local-sqlite-agent-store.ts
|
|
2061
|
+
import {
|
|
2062
|
+
composeLocalAgentStore
|
|
2063
|
+
} from "@cursor/sdk";
|
|
2064
|
+
import { SqliteLocalAgentStore } from "@cursor/sdk/sqlite";
|
|
2065
|
+
|
|
2066
|
+
// src/commands/connect/remote-local-agent-store.ts
|
|
2067
|
+
function asCheckpointRef(value) {
|
|
2068
|
+
if (value === void 0) return void 0;
|
|
2069
|
+
if (value === null) return null;
|
|
2070
|
+
if (!value || typeof value !== "object") return null;
|
|
2071
|
+
const row = value;
|
|
2072
|
+
if (typeof row.rootBlobId !== "string" || !row.rootBlobId.trim()) {
|
|
2073
|
+
return null;
|
|
2036
2074
|
}
|
|
2037
|
-
return
|
|
2075
|
+
return {
|
|
2076
|
+
schemaVersion: 1,
|
|
2077
|
+
rootBlobId: row.rootBlobId
|
|
2078
|
+
};
|
|
2038
2079
|
}
|
|
2039
|
-
function
|
|
2040
|
-
|
|
2041
|
-
if (
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
const walk = (dir) => {
|
|
2046
|
-
let entries;
|
|
2047
|
-
try {
|
|
2048
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
2049
|
-
} catch {
|
|
2050
|
-
return;
|
|
2051
|
-
}
|
|
2052
|
-
for (const entry of entries) {
|
|
2053
|
-
const name = entry.name;
|
|
2054
|
-
if (entry.isDirectory()) {
|
|
2055
|
-
if (SKIP_DIR_NAMES2.has(name) || name.startsWith(".")) continue;
|
|
2056
|
-
walk(path.join(dir, name));
|
|
2057
|
-
continue;
|
|
2058
|
-
}
|
|
2059
|
-
if (!entry.isFile() || !isSqlFileName(name)) continue;
|
|
2060
|
-
const absPath = path.join(dir, name);
|
|
2061
|
-
const relativePath = path.relative(root, absPath).replace(/\\/g, "/");
|
|
2062
|
-
if (!relativePath || relativePath.includes("..")) continue;
|
|
2063
|
-
const stat = fs.statSync(absPath);
|
|
2064
|
-
if (!stat.isFile() || stat.size <= 0) continue;
|
|
2065
|
-
byRelative.set(relativePath, {
|
|
2066
|
-
absPath,
|
|
2067
|
-
relativePath,
|
|
2068
|
-
size: stat.size,
|
|
2069
|
-
sha256: sha256File(absPath)
|
|
2070
|
-
});
|
|
2071
|
-
}
|
|
2080
|
+
function toApiCheckpointRef(ref) {
|
|
2081
|
+
if (ref === void 0) return void 0;
|
|
2082
|
+
if (ref === null) return null;
|
|
2083
|
+
return {
|
|
2084
|
+
schemaVersion: ref.schemaVersion,
|
|
2085
|
+
rootBlobId: ref.rootBlobId
|
|
2072
2086
|
};
|
|
2073
|
-
walk(root);
|
|
2074
|
-
return [...byRelative.values()].sort(
|
|
2075
|
-
(a, b) => a.relativePath.localeCompare(b.relativePath)
|
|
2076
|
-
);
|
|
2077
|
-
}
|
|
2078
|
-
function packageSqlObjectKey(projectId, repo, relativePath) {
|
|
2079
|
-
return `packages/${projectId}/${repo}/${relativePath}`;
|
|
2080
2087
|
}
|
|
2081
|
-
function
|
|
2082
|
-
return `packages/${projectId}/${repo}/`;
|
|
2083
|
-
}
|
|
2084
|
-
async function syncPackageSqlToMinio(params) {
|
|
2085
|
-
const id = params.projectId.trim();
|
|
2086
|
-
if (!id) {
|
|
2087
|
-
throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2088
|
-
}
|
|
2089
|
-
const repo = normalizeSqlRepoName(params.repo);
|
|
2090
|
-
const localFiles = scanSqlFilesUnderDir(params.localDir);
|
|
2091
|
-
const { client, bucket } = await createApmLogMinioClient(params.cfg);
|
|
2092
|
-
await client.ensureBucket(bucket);
|
|
2093
|
-
const prefix = packageSqlRepoPrefix(id, repo);
|
|
2094
|
-
const remoteObjects = await client.listObjects(bucket, prefix);
|
|
2095
|
-
const remoteByRelative = /* @__PURE__ */ new Map();
|
|
2096
|
-
for (const obj of remoteObjects) {
|
|
2097
|
-
if (!obj.name.startsWith(prefix) || obj.name.endsWith("/")) continue;
|
|
2098
|
-
if (!obj.name.toLowerCase().endsWith(".sql")) continue;
|
|
2099
|
-
if (obj.name.includes("..")) continue;
|
|
2100
|
-
const relativePath = obj.name.slice(prefix.length);
|
|
2101
|
-
if (!relativePath) continue;
|
|
2102
|
-
remoteByRelative.set(relativePath, {
|
|
2103
|
-
objectKey: obj.name,
|
|
2104
|
-
size: obj.size
|
|
2105
|
-
});
|
|
2106
|
-
}
|
|
2107
|
-
for (const [relativePath, remote] of remoteByRelative) {
|
|
2108
|
-
const local = localFiles.find((f) => f.relativePath === relativePath);
|
|
2109
|
-
if (!local) continue;
|
|
2110
|
-
if (local.size !== remote.size) continue;
|
|
2111
|
-
try {
|
|
2112
|
-
const stat = await client.statObject(bucket, remote.objectKey);
|
|
2113
|
-
const meta = stat.metaData ?? {};
|
|
2114
|
-
const sha = meta["sha256"] || meta["x-amz-meta-sha256"] || meta["Sha256"] || void 0;
|
|
2115
|
-
if (typeof sha === "string" && sha.trim()) {
|
|
2116
|
-
remote.sha256 = sha.trim().toLowerCase();
|
|
2117
|
-
}
|
|
2118
|
-
} catch {
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
let uploaded = 0;
|
|
2122
|
-
let skipped = 0;
|
|
2123
|
-
const localRelativeSet = new Set(localFiles.map((f) => f.relativePath));
|
|
2124
|
-
for (const file of localFiles) {
|
|
2125
|
-
const objectKey = packageSqlObjectKey(id, repo, file.relativePath);
|
|
2126
|
-
const remote = remoteByRelative.get(file.relativePath);
|
|
2127
|
-
if (remote?.sha256 && remote.sha256 === file.sha256) {
|
|
2128
|
-
skipped += 1;
|
|
2129
|
-
continue;
|
|
2130
|
-
}
|
|
2131
|
-
await client.fPutObject(bucket, objectKey, file.absPath, {
|
|
2132
|
-
"Content-Type": "application/sql",
|
|
2133
|
-
sha256: file.sha256
|
|
2134
|
-
});
|
|
2135
|
-
uploaded += 1;
|
|
2136
|
-
}
|
|
2137
|
-
let deleted = 0;
|
|
2138
|
-
for (const [relativePath, remote] of remoteByRelative) {
|
|
2139
|
-
if (localRelativeSet.has(relativePath)) continue;
|
|
2140
|
-
await client.removeObject(bucket, remote.objectKey);
|
|
2141
|
-
deleted += 1;
|
|
2142
|
-
}
|
|
2088
|
+
function fromApiAgent(row) {
|
|
2143
2089
|
return {
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2090
|
+
agentId: row.agentId,
|
|
2091
|
+
cwd: row.cwd,
|
|
2092
|
+
status: row.status,
|
|
2093
|
+
activeRunId: row.activeRunId,
|
|
2094
|
+
name: row.name,
|
|
2095
|
+
createdAt: row.createdAt,
|
|
2096
|
+
updatedAt: row.updatedAt,
|
|
2097
|
+
latestCheckpoint: asCheckpointRef(row.latestCheckpoint) ?? null,
|
|
2098
|
+
sdkMetadata: row.sdkMetadata ?? void 0
|
|
2148
2099
|
};
|
|
2149
2100
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2101
|
+
function fromApiRun(row) {
|
|
2102
|
+
return {
|
|
2103
|
+
runId: row.runId,
|
|
2104
|
+
requestId: row.requestId,
|
|
2105
|
+
agentId: row.agentId,
|
|
2106
|
+
turnNumber: row.turnNumber,
|
|
2107
|
+
status: row.status,
|
|
2108
|
+
model: row.model ?? null,
|
|
2109
|
+
result: row.result,
|
|
2110
|
+
error: row.error,
|
|
2111
|
+
usageRef: row.usageRef,
|
|
2112
|
+
usage: row.usage ?? null,
|
|
2113
|
+
createdAt: row.createdAt,
|
|
2114
|
+
updatedAt: row.updatedAt,
|
|
2115
|
+
startedAt: row.startedAt,
|
|
2116
|
+
endedAt: row.endedAt,
|
|
2117
|
+
startCheckpointRef: asCheckpointRef(row.startCheckpointRef) ?? null,
|
|
2118
|
+
latestCheckpointRef: asCheckpointRef(row.latestCheckpointRef) ?? null
|
|
2119
|
+
};
|
|
2154
2120
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
return
|
|
2121
|
+
var unsupportedCheckpoints = {
|
|
2122
|
+
async get() {
|
|
2123
|
+
return null;
|
|
2124
|
+
},
|
|
2125
|
+
async create() {
|
|
2126
|
+
},
|
|
2127
|
+
async update() {
|
|
2128
|
+
},
|
|
2129
|
+
async delete() {
|
|
2130
|
+
},
|
|
2131
|
+
async list() {
|
|
2132
|
+
return { items: [] };
|
|
2158
2133
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2134
|
+
};
|
|
2135
|
+
var unsupportedRunEvents = {
|
|
2136
|
+
async append() {
|
|
2137
|
+
throw new Error("runEvents \u4EC5\u5B58\u672C\u673A SQLite");
|
|
2138
|
+
},
|
|
2139
|
+
async list() {
|
|
2140
|
+
return { items: [] };
|
|
2141
|
+
},
|
|
2142
|
+
async delete() {
|
|
2164
2143
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2144
|
+
};
|
|
2145
|
+
function createRemoteLocalAgentStore(cfg2) {
|
|
2146
|
+
const api = createApmApiClient(cfg2);
|
|
2147
|
+
async function upsertAgent(agent) {
|
|
2148
|
+
const row = await api.cli.cursorStoreUpsertAgent({
|
|
2149
|
+
agentId: agent.agentId,
|
|
2150
|
+
cwd: agent.cwd,
|
|
2151
|
+
status: agent.status,
|
|
2152
|
+
activeRunId: agent.activeRunId ?? null,
|
|
2153
|
+
name: agent.name ?? null,
|
|
2154
|
+
latestCheckpoint: toApiCheckpointRef(agent.latestCheckpoint) ?? null,
|
|
2155
|
+
sdkMetadata: agent.sdkMetadata ?? null,
|
|
2156
|
+
createdAt: agent.createdAt,
|
|
2157
|
+
updatedAt: agent.updatedAt
|
|
2158
|
+
});
|
|
2159
|
+
return fromApiAgent(row);
|
|
2168
2160
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2161
|
+
async function upsertRun(run) {
|
|
2162
|
+
const row = await api.cli.cursorStoreUpsertRun({
|
|
2163
|
+
runId: run.runId,
|
|
2164
|
+
agentId: run.agentId,
|
|
2165
|
+
requestId: run.requestId ?? null,
|
|
2166
|
+
turnNumber: run.turnNumber,
|
|
2167
|
+
status: run.status,
|
|
2168
|
+
model: run.model ?? null,
|
|
2169
|
+
result: run.result ?? null,
|
|
2170
|
+
error: run.error ?? null,
|
|
2171
|
+
usageRef: run.usageRef ?? null,
|
|
2172
|
+
usage: run.usage ?? null,
|
|
2173
|
+
startCheckpointRef: toApiCheckpointRef(run.startCheckpointRef) ?? null,
|
|
2174
|
+
latestCheckpointRef: toApiCheckpointRef(run.latestCheckpointRef) ?? null,
|
|
2175
|
+
createdAt: run.createdAt,
|
|
2176
|
+
updatedAt: run.updatedAt,
|
|
2177
|
+
startedAt: run.startedAt ?? null,
|
|
2178
|
+
endedAt: run.endedAt ?? null
|
|
2179
|
+
});
|
|
2180
|
+
return fromApiRun(row);
|
|
2175
2181
|
}
|
|
2176
|
-
return { ok: true, absPath: targetAbs };
|
|
2177
|
-
}
|
|
2178
|
-
function createUploadPackageSqlTool(options) {
|
|
2179
|
-
const { cfg: cfg2, workdir, projectId } = options;
|
|
2180
2182
|
return {
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
type: "string",
|
|
2188
|
-
description: "Backend repo root directory name, e.g. mmis-java (from deploy\u300C\u8FDB\u5165 xxx \u76EE\u5F55\u300D)"
|
|
2189
|
-
},
|
|
2190
|
-
localDir: {
|
|
2191
|
-
type: "string",
|
|
2192
|
-
description: "Absolute path to the SQL script root directory under the workspace (deploy\u300CSQL\u811A\u672C\u76EE\u5F55\u300Dwithout the /**/*.sql suffix)"
|
|
2193
|
-
}
|
|
2194
|
-
},
|
|
2195
|
-
required: ["repo", "localDir"]
|
|
2183
|
+
agents: {
|
|
2184
|
+
async get(input) {
|
|
2185
|
+
const row = await api.cli.cursorStoreGetAgent({
|
|
2186
|
+
agentId: input.agentId
|
|
2187
|
+
});
|
|
2188
|
+
return row ? fromApiAgent(row) : null;
|
|
2196
2189
|
},
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2190
|
+
async create(input) {
|
|
2191
|
+
return upsertAgent(input.agent);
|
|
2192
|
+
},
|
|
2193
|
+
async update(input) {
|
|
2194
|
+
return upsertAgent(input.agent);
|
|
2195
|
+
},
|
|
2196
|
+
async delete(input) {
|
|
2197
|
+
const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
|
|
2198
|
+
const cwd = input.filter.cwd?.trim();
|
|
2199
|
+
if (agentIds && agentIds.length > 0) {
|
|
2200
|
+
await api.cli.cursorStoreDeleteAgents({ agentIds, cwd });
|
|
2201
|
+
return;
|
|
2205
2202
|
}
|
|
2206
|
-
if (
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
isError: true
|
|
2210
|
-
};
|
|
2203
|
+
if (cwd) {
|
|
2204
|
+
await api.cli.cursorStoreDeleteAgents({ cwd });
|
|
2205
|
+
return;
|
|
2211
2206
|
}
|
|
2212
|
-
let
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2207
|
+
let cursor;
|
|
2208
|
+
for (; ; ) {
|
|
2209
|
+
const page = await api.cli.cursorStoreListAgents({
|
|
2210
|
+
cursor,
|
|
2211
|
+
limit: 200
|
|
2212
|
+
});
|
|
2213
|
+
if (page.items.length === 0) break;
|
|
2214
|
+
await api.cli.cursorStoreDeleteAgents({
|
|
2215
|
+
agentIds: page.items.map((item) => item.agentId)
|
|
2216
|
+
});
|
|
2217
|
+
if (!page.nextCursor) break;
|
|
2218
|
+
cursor = page.nextCursor;
|
|
2221
2219
|
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2220
|
+
},
|
|
2221
|
+
async list(input) {
|
|
2222
|
+
const page = await api.cli.cursorStoreListAgents({
|
|
2223
|
+
cursor: input?.filter?.cursor,
|
|
2224
|
+
limit: input?.filter?.limit,
|
|
2225
|
+
cwd: input?.filter?.cwd
|
|
2226
|
+
});
|
|
2227
|
+
const result = {
|
|
2228
|
+
items: page.items.map(fromApiAgent),
|
|
2229
|
+
...page.nextCursor ? { nextCursor: page.nextCursor } : {}
|
|
2230
|
+
};
|
|
2231
|
+
return result;
|
|
2232
|
+
}
|
|
2233
|
+
},
|
|
2234
|
+
runs: {
|
|
2235
|
+
async get(input) {
|
|
2236
|
+
const page = await api.cli.cursorStoreListRuns({
|
|
2237
|
+
runIds: [input.runId],
|
|
2238
|
+
limit: 1
|
|
2239
|
+
});
|
|
2240
|
+
const row = page.items[0];
|
|
2241
|
+
return row ? fromApiRun(row) : null;
|
|
2242
|
+
},
|
|
2243
|
+
async create(input) {
|
|
2244
|
+
return upsertRun(input.run);
|
|
2245
|
+
},
|
|
2246
|
+
async update(input) {
|
|
2247
|
+
return upsertRun(input.run);
|
|
2248
|
+
},
|
|
2249
|
+
async delete(input) {
|
|
2250
|
+
const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
|
|
2251
|
+
const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
|
|
2252
|
+
if (agentIds && agentIds.length > 0 || runIds && runIds.length > 0) {
|
|
2253
|
+
await api.cli.cursorStoreDeleteRuns({ agentIds, runIds });
|
|
2254
|
+
return;
|
|
2228
2255
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
localDir: resolved.absPath
|
|
2256
|
+
let cursor;
|
|
2257
|
+
for (; ; ) {
|
|
2258
|
+
const page = await api.cli.cursorStoreListRuns({
|
|
2259
|
+
cursor,
|
|
2260
|
+
limit: 200
|
|
2235
2261
|
});
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
};
|
|
2262
|
+
if (page.items.length === 0) break;
|
|
2263
|
+
await api.cli.cursorStoreDeleteRuns({
|
|
2264
|
+
runIds: page.items.map((item) => item.runId)
|
|
2265
|
+
});
|
|
2266
|
+
if (!page.nextCursor) break;
|
|
2267
|
+
cursor = page.nextCursor;
|
|
2243
2268
|
}
|
|
2269
|
+
},
|
|
2270
|
+
async list(input) {
|
|
2271
|
+
const page = await api.cli.cursorStoreListRuns({
|
|
2272
|
+
agentIds: input?.filter?.agentIds ? [...input.filter.agentIds] : void 0,
|
|
2273
|
+
runIds: input?.filter?.runIds ? [...input.filter.runIds] : void 0,
|
|
2274
|
+
cursor: input?.filter?.cursor,
|
|
2275
|
+
limit: input?.filter?.limit
|
|
2276
|
+
});
|
|
2277
|
+
const result = {
|
|
2278
|
+
items: page.items.map(fromApiRun),
|
|
2279
|
+
...page.nextCursor ? { nextCursor: page.nextCursor } : {}
|
|
2280
|
+
};
|
|
2281
|
+
return result;
|
|
2244
2282
|
}
|
|
2245
|
-
}
|
|
2283
|
+
},
|
|
2284
|
+
checkpoints: unsupportedCheckpoints,
|
|
2285
|
+
runEvents: unsupportedRunEvents
|
|
2246
2286
|
};
|
|
2247
2287
|
}
|
|
2248
2288
|
|
|
2249
|
-
// src/commands/connect/
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
description: "\u5931\u8D25\u539F\u56E0\u6458\u8981\uFF08\u4F1A\u5199\u5165\u6253\u5305\u8BB0\u5F55\uFF0C\u4F9B\u5217\u8868/\u65E5\u5FD7\u5C55\u793A\uFF09"
|
|
2261
|
-
}
|
|
2262
|
-
},
|
|
2263
|
-
required: ["error"]
|
|
2264
|
-
},
|
|
2265
|
-
execute: async (args) => {
|
|
2266
|
-
const error = typeof args.error === "string" ? args.error.trim() : "";
|
|
2267
|
-
if (!error) {
|
|
2268
|
-
return {
|
|
2269
|
-
content: [{ type: "text", text: "error \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
2270
|
-
isError: true
|
|
2271
|
-
};
|
|
2272
|
-
}
|
|
2273
|
-
try {
|
|
2274
|
-
await setFailed(error);
|
|
2275
|
-
return `\u5DF2\u5C06\u6253\u5305\u8BB0\u5F55 ${packageId} \u6807\u4E3A FAILED`;
|
|
2276
|
-
} catch (err) {
|
|
2277
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
2278
|
-
return {
|
|
2279
|
-
content: [{ type: "text", text: `\u5199\u5165\u5931\u8D25\u72B6\u6001\u5931\u8D25: ${detail}` }],
|
|
2280
|
-
isError: true
|
|
2281
|
-
};
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
};
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
|
-
// src/commands/connect/tools/index.ts
|
|
2289
|
-
var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
|
|
2290
|
-
AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
|
|
2291
|
-
\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
|
|
2292
|
-
\u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
|
|
2293
|
-
var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
|
|
2294
|
-
\u6240\u6709\u6587\u4EF6\u8BFB\u5199\u3001\u641C\u7D22\uFF08Grep/Glob/Read\uFF09\u3001Shell \u5FC5\u987B\u4E25\u683C\u9650\u5236\u5728\u5F53\u524D\u5DE5\u4F5C\u533A cwd \u5185\u3002
|
|
2295
|
-
\u7981\u6B62\u8BBF\u95EE\u5DE5\u4F5C\u533A\u5916\u8DEF\u5F84\uFF08\u5982\u5176\u5B83\u9879\u76EE\u3001~/.cursor\u3001agent-transcripts\u3001\u7CFB\u7EDF\u76EE\u5F55\u7B49\uFF09\u3002`;
|
|
2296
|
-
function wantsTool(name, options, legacyDefault) {
|
|
2297
|
-
if (options.toolNames !== void 0) {
|
|
2298
|
-
return options.toolNames.includes(name);
|
|
2299
|
-
}
|
|
2300
|
-
return legacyDefault;
|
|
2301
|
-
}
|
|
2302
|
-
function createCursorCustomTools(cfg2, options) {
|
|
2303
|
-
const enableAppend = options.enableAppendMessage !== false && Boolean(options.appendMessageContent);
|
|
2304
|
-
const enableAsk = options.enableAskQuestion !== false;
|
|
2305
|
-
const enablePlan = options.enableWebIdePlanTools === true;
|
|
2306
|
-
const enableMysql = options.enableMysqlTools === true;
|
|
2307
|
-
const tools = {
|
|
2308
|
-
...wantsTool("AppendMessage", options, enableAppend) && options.appendMessageContent ? createAppendMessageCustomTools({
|
|
2309
|
-
appendContent: options.appendMessageContent
|
|
2310
|
-
}) : {},
|
|
2311
|
-
...wantsTool("AskQuestion", options, enableAsk) ? createAskQuestionTool({
|
|
2312
|
-
onInvoke: options?.onAskQuestion,
|
|
2313
|
-
execute: options?.askQuestionExecute
|
|
2314
|
-
}) : {}
|
|
2315
|
-
};
|
|
2316
|
-
if (options?.taskId && options.workdir) {
|
|
2317
|
-
const { cli } = createApmApiClient(cfg2);
|
|
2318
|
-
const taskId = options.taskId;
|
|
2319
|
-
if (wantsTool("RecommendPlan", options, enablePlan)) {
|
|
2320
|
-
Object.assign(
|
|
2321
|
-
tools,
|
|
2322
|
-
createRecommendPlanTool({
|
|
2323
|
-
webideRecommendPlan: (args) => cli.webideRecommendPlan({ taskId, ...args })
|
|
2324
|
-
})
|
|
2325
|
-
);
|
|
2326
|
-
}
|
|
2327
|
-
if (wantsTool("UpsertWebIdePlan", options, enablePlan)) {
|
|
2328
|
-
Object.assign(
|
|
2329
|
-
tools,
|
|
2330
|
-
createUpsertWebIdePlanTool({
|
|
2331
|
-
webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
|
|
2332
|
-
})
|
|
2333
|
-
);
|
|
2334
|
-
}
|
|
2335
|
-
if (wantsTool("UpsertWebIdeChecklist", options, enablePlan)) {
|
|
2336
|
-
Object.assign(
|
|
2337
|
-
tools,
|
|
2338
|
-
createUpsertWebIdeChecklistTool({
|
|
2339
|
-
webideUpsertChecklist: (args) => cli.webideUpsertChecklist({ taskId, ...args })
|
|
2340
|
-
})
|
|
2341
|
-
);
|
|
2342
|
-
}
|
|
2343
|
-
if (wantsTool("GetWebIdeChecklist", options, enablePlan)) {
|
|
2344
|
-
Object.assign(
|
|
2345
|
-
tools,
|
|
2346
|
-
createGetWebIdeChecklistTool({
|
|
2347
|
-
boundTaskId: taskId,
|
|
2348
|
-
webideGetChecklist: (args) => cli.webideGetChecklist(args)
|
|
2349
|
-
})
|
|
2350
|
-
);
|
|
2351
|
-
}
|
|
2352
|
-
if (wantsTool("UpsertWebIdeTestCases", options, enablePlan)) {
|
|
2353
|
-
Object.assign(
|
|
2354
|
-
tools,
|
|
2355
|
-
createUpsertWebIdeTestCasesTool({
|
|
2356
|
-
webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
|
|
2357
|
-
})
|
|
2358
|
-
);
|
|
2359
|
-
}
|
|
2360
|
-
if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
|
|
2361
|
-
Object.assign(
|
|
2362
|
-
tools,
|
|
2363
|
-
createMergeWebIdePullRequestsTool({
|
|
2364
|
-
webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
|
|
2365
|
-
})
|
|
2366
|
-
);
|
|
2367
|
-
}
|
|
2368
|
-
if (wantsTool("MysqlExecute", options, enableMysql) && options.sqlExecutionId && options.workdir) {
|
|
2369
|
-
Object.assign(
|
|
2370
|
-
tools,
|
|
2371
|
-
createMysqlExecuteTool({
|
|
2372
|
-
taskId,
|
|
2373
|
-
executionId: options.sqlExecutionId,
|
|
2374
|
-
workdir: options.workdir,
|
|
2375
|
-
upsertSqlExecution: (args) => cli.webideUpsertSqlExecution(args)
|
|
2376
|
-
})
|
|
2377
|
-
);
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
if (options.enablePackageStatusTools && options.packageId) {
|
|
2381
|
-
const packageId = options.packageId;
|
|
2382
|
-
const { cli } = createApmApiClient(cfg2);
|
|
2383
|
-
Object.assign(
|
|
2384
|
-
tools,
|
|
2385
|
-
createSetPackageFailedTool({
|
|
2386
|
-
packageId,
|
|
2387
|
-
setFailed: async (error) => {
|
|
2388
|
-
await cli.projectPackageUpdateStatus({
|
|
2389
|
-
id: packageId,
|
|
2390
|
-
status: "FAILED",
|
|
2391
|
-
error
|
|
2392
|
-
});
|
|
2393
|
-
await options.onPackageFailed?.(error);
|
|
2394
|
-
}
|
|
2395
|
-
})
|
|
2396
|
-
);
|
|
2397
|
-
const projectId = options.projectId?.trim();
|
|
2398
|
-
if (projectId && options.workdir) {
|
|
2399
|
-
Object.assign(
|
|
2400
|
-
tools,
|
|
2401
|
-
createUploadPackageSqlTool({
|
|
2402
|
-
cfg: cfg2,
|
|
2403
|
-
workdir: options.workdir,
|
|
2404
|
-
projectId
|
|
2405
|
-
})
|
|
2406
|
-
);
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
return tools;
|
|
2410
|
-
}
|
|
2411
|
-
function withPlanModeToolHint(prompt, mode) {
|
|
2412
|
-
if (mode !== "plan") {
|
|
2413
|
-
return prompt;
|
|
2414
|
-
}
|
|
2415
|
-
return `${prompt.trim()}
|
|
2416
|
-
|
|
2417
|
-
${PLAN_MODE_ASK_QUESTION_HINT}`;
|
|
2418
|
-
}
|
|
2419
|
-
function withWorkspaceBoundaryHint(prompt) {
|
|
2420
|
-
return `${prompt.trim()}
|
|
2421
|
-
|
|
2422
|
-
${WORKSPACE_BOUNDARY_HINT}`;
|
|
2423
|
-
}
|
|
2424
|
-
|
|
2425
|
-
// src/commands/connect/mcps/pty-session-mcp.ts
|
|
2426
|
-
var PTY_SESSION_MCP_NAME = "pty-session";
|
|
2427
|
-
var DEFAULT_PTY_SESSION_PORT = "7788";
|
|
2428
|
-
function resolvePtySessionBaseUrl(env = process.env) {
|
|
2429
|
-
const explicit = env.LOCAL_TERMINAL_URL?.trim();
|
|
2430
|
-
if (explicit) return explicit.replace(/\/+$/, "");
|
|
2431
|
-
const port2 = env.LOCAL_TERMINAL_PORT?.trim() || DEFAULT_PTY_SESSION_PORT;
|
|
2432
|
-
return `http://127.0.0.1:${port2}`;
|
|
2433
|
-
}
|
|
2434
|
-
function createPtySessionMcpServers(cfg2) {
|
|
2435
|
-
const token = resolveApiKey(cfg2);
|
|
2436
|
-
if (!token) return void 0;
|
|
2437
|
-
return {
|
|
2438
|
-
[PTY_SESSION_MCP_NAME]: {
|
|
2439
|
-
type: "http",
|
|
2440
|
-
url: `${resolvePtySessionBaseUrl()}/mcp`,
|
|
2441
|
-
headers: {
|
|
2442
|
-
Authorization: `Bearer ${token}`
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
2445
|
-
};
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
// src/commands/connect/local-sqlite-agent-store.ts
|
|
2449
|
-
import {
|
|
2450
|
-
composeLocalAgentStore
|
|
2451
|
-
} from "@cursor/sdk";
|
|
2452
|
-
import { SqliteLocalAgentStore } from "@cursor/sdk/sqlite";
|
|
2453
|
-
|
|
2454
|
-
// src/commands/connect/remote-local-agent-store.ts
|
|
2455
|
-
function asCheckpointRef(value) {
|
|
2456
|
-
if (value === void 0) return void 0;
|
|
2457
|
-
if (value === null) return null;
|
|
2458
|
-
if (!value || typeof value !== "object") return null;
|
|
2459
|
-
const row = value;
|
|
2460
|
-
if (typeof row.rootBlobId !== "string" || !row.rootBlobId.trim()) {
|
|
2461
|
-
return null;
|
|
2462
|
-
}
|
|
2463
|
-
return {
|
|
2464
|
-
schemaVersion: 1,
|
|
2465
|
-
rootBlobId: row.rootBlobId
|
|
2466
|
-
};
|
|
2467
|
-
}
|
|
2468
|
-
function toApiCheckpointRef(ref) {
|
|
2469
|
-
if (ref === void 0) return void 0;
|
|
2470
|
-
if (ref === null) return null;
|
|
2471
|
-
return {
|
|
2472
|
-
schemaVersion: ref.schemaVersion,
|
|
2473
|
-
rootBlobId: ref.rootBlobId
|
|
2474
|
-
};
|
|
2475
|
-
}
|
|
2476
|
-
function fromApiAgent(row) {
|
|
2477
|
-
return {
|
|
2478
|
-
agentId: row.agentId,
|
|
2479
|
-
cwd: row.cwd,
|
|
2480
|
-
status: row.status,
|
|
2481
|
-
activeRunId: row.activeRunId,
|
|
2482
|
-
name: row.name,
|
|
2483
|
-
createdAt: row.createdAt,
|
|
2484
|
-
updatedAt: row.updatedAt,
|
|
2485
|
-
latestCheckpoint: asCheckpointRef(row.latestCheckpoint) ?? null,
|
|
2486
|
-
sdkMetadata: row.sdkMetadata ?? void 0
|
|
2487
|
-
};
|
|
2488
|
-
}
|
|
2489
|
-
function fromApiRun(row) {
|
|
2490
|
-
return {
|
|
2491
|
-
runId: row.runId,
|
|
2492
|
-
requestId: row.requestId,
|
|
2493
|
-
agentId: row.agentId,
|
|
2494
|
-
turnNumber: row.turnNumber,
|
|
2495
|
-
status: row.status,
|
|
2496
|
-
model: row.model ?? null,
|
|
2497
|
-
result: row.result,
|
|
2498
|
-
error: row.error,
|
|
2499
|
-
usageRef: row.usageRef,
|
|
2500
|
-
usage: row.usage ?? null,
|
|
2501
|
-
createdAt: row.createdAt,
|
|
2502
|
-
updatedAt: row.updatedAt,
|
|
2503
|
-
startedAt: row.startedAt,
|
|
2504
|
-
endedAt: row.endedAt,
|
|
2505
|
-
startCheckpointRef: asCheckpointRef(row.startCheckpointRef) ?? null,
|
|
2506
|
-
latestCheckpointRef: asCheckpointRef(row.latestCheckpointRef) ?? null
|
|
2507
|
-
};
|
|
2508
|
-
}
|
|
2509
|
-
var unsupportedCheckpoints = {
|
|
2510
|
-
async get() {
|
|
2511
|
-
return null;
|
|
2512
|
-
},
|
|
2513
|
-
async create() {
|
|
2514
|
-
},
|
|
2515
|
-
async update() {
|
|
2516
|
-
},
|
|
2517
|
-
async delete() {
|
|
2518
|
-
},
|
|
2519
|
-
async list() {
|
|
2520
|
-
return { items: [] };
|
|
2521
|
-
}
|
|
2522
|
-
};
|
|
2523
|
-
var unsupportedRunEvents = {
|
|
2524
|
-
async append() {
|
|
2525
|
-
throw new Error("runEvents \u4EC5\u5B58\u672C\u673A SQLite");
|
|
2526
|
-
},
|
|
2527
|
-
async list() {
|
|
2528
|
-
return { items: [] };
|
|
2529
|
-
},
|
|
2530
|
-
async delete() {
|
|
2531
|
-
}
|
|
2532
|
-
};
|
|
2533
|
-
function createRemoteLocalAgentStore(cfg2) {
|
|
2534
|
-
const api = createApmApiClient(cfg2);
|
|
2535
|
-
async function upsertAgent(agent) {
|
|
2536
|
-
const row = await api.cli.cursorStoreUpsertAgent({
|
|
2537
|
-
agentId: agent.agentId,
|
|
2538
|
-
cwd: agent.cwd,
|
|
2539
|
-
status: agent.status,
|
|
2540
|
-
activeRunId: agent.activeRunId ?? null,
|
|
2541
|
-
name: agent.name ?? null,
|
|
2542
|
-
latestCheckpoint: toApiCheckpointRef(agent.latestCheckpoint) ?? null,
|
|
2543
|
-
sdkMetadata: agent.sdkMetadata ?? null,
|
|
2544
|
-
createdAt: agent.createdAt,
|
|
2545
|
-
updatedAt: agent.updatedAt
|
|
2546
|
-
});
|
|
2547
|
-
return fromApiAgent(row);
|
|
2548
|
-
}
|
|
2549
|
-
async function upsertRun(run) {
|
|
2550
|
-
const row = await api.cli.cursorStoreUpsertRun({
|
|
2551
|
-
runId: run.runId,
|
|
2552
|
-
agentId: run.agentId,
|
|
2553
|
-
requestId: run.requestId ?? null,
|
|
2554
|
-
turnNumber: run.turnNumber,
|
|
2555
|
-
status: run.status,
|
|
2556
|
-
model: run.model ?? null,
|
|
2557
|
-
result: run.result ?? null,
|
|
2558
|
-
error: run.error ?? null,
|
|
2559
|
-
usageRef: run.usageRef ?? null,
|
|
2560
|
-
usage: run.usage ?? null,
|
|
2561
|
-
startCheckpointRef: toApiCheckpointRef(run.startCheckpointRef) ?? null,
|
|
2562
|
-
latestCheckpointRef: toApiCheckpointRef(run.latestCheckpointRef) ?? null,
|
|
2563
|
-
createdAt: run.createdAt,
|
|
2564
|
-
updatedAt: run.updatedAt,
|
|
2565
|
-
startedAt: run.startedAt ?? null,
|
|
2566
|
-
endedAt: run.endedAt ?? null
|
|
2567
|
-
});
|
|
2568
|
-
return fromApiRun(row);
|
|
2569
|
-
}
|
|
2570
|
-
return {
|
|
2571
|
-
agents: {
|
|
2572
|
-
async get(input) {
|
|
2573
|
-
const row = await api.cli.cursorStoreGetAgent({
|
|
2574
|
-
agentId: input.agentId
|
|
2575
|
-
});
|
|
2576
|
-
return row ? fromApiAgent(row) : null;
|
|
2577
|
-
},
|
|
2578
|
-
async create(input) {
|
|
2579
|
-
return upsertAgent(input.agent);
|
|
2580
|
-
},
|
|
2581
|
-
async update(input) {
|
|
2582
|
-
return upsertAgent(input.agent);
|
|
2583
|
-
},
|
|
2584
|
-
async delete(input) {
|
|
2585
|
-
const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
|
|
2586
|
-
const cwd = input.filter.cwd?.trim();
|
|
2587
|
-
if (agentIds && agentIds.length > 0) {
|
|
2588
|
-
await api.cli.cursorStoreDeleteAgents({ agentIds, cwd });
|
|
2589
|
-
return;
|
|
2590
|
-
}
|
|
2591
|
-
if (cwd) {
|
|
2592
|
-
await api.cli.cursorStoreDeleteAgents({ cwd });
|
|
2593
|
-
return;
|
|
2594
|
-
}
|
|
2595
|
-
let cursor;
|
|
2596
|
-
for (; ; ) {
|
|
2597
|
-
const page = await api.cli.cursorStoreListAgents({
|
|
2598
|
-
cursor,
|
|
2599
|
-
limit: 200
|
|
2600
|
-
});
|
|
2601
|
-
if (page.items.length === 0) break;
|
|
2602
|
-
await api.cli.cursorStoreDeleteAgents({
|
|
2603
|
-
agentIds: page.items.map((item) => item.agentId)
|
|
2604
|
-
});
|
|
2605
|
-
if (!page.nextCursor) break;
|
|
2606
|
-
cursor = page.nextCursor;
|
|
2607
|
-
}
|
|
2608
|
-
},
|
|
2609
|
-
async list(input) {
|
|
2610
|
-
const page = await api.cli.cursorStoreListAgents({
|
|
2611
|
-
cursor: input?.filter?.cursor,
|
|
2612
|
-
limit: input?.filter?.limit,
|
|
2613
|
-
cwd: input?.filter?.cwd
|
|
2614
|
-
});
|
|
2615
|
-
const result = {
|
|
2616
|
-
items: page.items.map(fromApiAgent),
|
|
2617
|
-
...page.nextCursor ? { nextCursor: page.nextCursor } : {}
|
|
2618
|
-
};
|
|
2619
|
-
return result;
|
|
2620
|
-
}
|
|
2621
|
-
},
|
|
2622
|
-
runs: {
|
|
2623
|
-
async get(input) {
|
|
2624
|
-
const page = await api.cli.cursorStoreListRuns({
|
|
2625
|
-
runIds: [input.runId],
|
|
2626
|
-
limit: 1
|
|
2627
|
-
});
|
|
2628
|
-
const row = page.items[0];
|
|
2629
|
-
return row ? fromApiRun(row) : null;
|
|
2630
|
-
},
|
|
2631
|
-
async create(input) {
|
|
2632
|
-
return upsertRun(input.run);
|
|
2633
|
-
},
|
|
2634
|
-
async update(input) {
|
|
2635
|
-
return upsertRun(input.run);
|
|
2636
|
-
},
|
|
2637
|
-
async delete(input) {
|
|
2638
|
-
const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
|
|
2639
|
-
const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
|
|
2640
|
-
if (agentIds && agentIds.length > 0 || runIds && runIds.length > 0) {
|
|
2641
|
-
await api.cli.cursorStoreDeleteRuns({ agentIds, runIds });
|
|
2642
|
-
return;
|
|
2643
|
-
}
|
|
2644
|
-
let cursor;
|
|
2645
|
-
for (; ; ) {
|
|
2646
|
-
const page = await api.cli.cursorStoreListRuns({
|
|
2647
|
-
cursor,
|
|
2648
|
-
limit: 200
|
|
2649
|
-
});
|
|
2650
|
-
if (page.items.length === 0) break;
|
|
2651
|
-
await api.cli.cursorStoreDeleteRuns({
|
|
2652
|
-
runIds: page.items.map((item) => item.runId)
|
|
2653
|
-
});
|
|
2654
|
-
if (!page.nextCursor) break;
|
|
2655
|
-
cursor = page.nextCursor;
|
|
2656
|
-
}
|
|
2657
|
-
},
|
|
2658
|
-
async list(input) {
|
|
2659
|
-
const page = await api.cli.cursorStoreListRuns({
|
|
2660
|
-
agentIds: input?.filter?.agentIds ? [...input.filter.agentIds] : void 0,
|
|
2661
|
-
runIds: input?.filter?.runIds ? [...input.filter.runIds] : void 0,
|
|
2662
|
-
cursor: input?.filter?.cursor,
|
|
2663
|
-
limit: input?.filter?.limit
|
|
2664
|
-
});
|
|
2665
|
-
const result = {
|
|
2666
|
-
items: page.items.map(fromApiRun),
|
|
2667
|
-
...page.nextCursor ? { nextCursor: page.nextCursor } : {}
|
|
2668
|
-
};
|
|
2669
|
-
return result;
|
|
2670
|
-
}
|
|
2671
|
-
},
|
|
2672
|
-
checkpoints: unsupportedCheckpoints,
|
|
2673
|
-
runEvents: unsupportedRunEvents
|
|
2674
|
-
};
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
|
-
// src/commands/connect/local-sqlite-agent-store.ts
|
|
2678
|
-
var sqliteByKey = /* @__PURE__ */ new Map();
|
|
2679
|
-
async function openSqliteLocalAgentStore(workspaceRef) {
|
|
2680
|
-
const cwd = workspaceRef.trim();
|
|
2681
|
-
if (!cwd) throw new Error("openSqliteLocalAgentStore: workspaceRef \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2682
|
-
const existing = sqliteByKey.get(cwd);
|
|
2683
|
-
if (existing) return existing;
|
|
2684
|
-
const pending2 = SqliteLocalAgentStore.open({ workspaceRef: cwd }).then(
|
|
2685
|
-
(store) => {
|
|
2686
|
-
console.log(
|
|
2687
|
-
`[apm] Cursor store sqlite ready cwd=${cwd} stateRoot=${store.stateRoot}`
|
|
2688
|
-
);
|
|
2689
|
-
return store;
|
|
2690
|
-
}
|
|
2691
|
-
);
|
|
2692
|
-
sqliteByKey.set(cwd, pending2);
|
|
2693
|
-
return pending2;
|
|
2694
|
-
}
|
|
2695
|
-
function jsonSafePayload(value) {
|
|
2696
|
-
if (value == null) return null;
|
|
2697
|
-
try {
|
|
2698
|
-
return JSON.parse(JSON.stringify(value));
|
|
2699
|
-
} catch {
|
|
2700
|
-
return null;
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
function mirrorRunEventsForVisualization(cfg2, local) {
|
|
2704
|
-
const api = createApmApiClient(cfg2);
|
|
2705
|
-
return {
|
|
2706
|
-
async append(input) {
|
|
2707
|
-
const row = await local.append(input);
|
|
2708
|
-
try {
|
|
2709
|
-
await api.cli.cursorStoreAppendRunEvents({
|
|
2710
|
-
events: [
|
|
2711
|
-
{
|
|
2712
|
-
runId: input.runId,
|
|
2713
|
-
eventType: input.eventType,
|
|
2714
|
-
payload: jsonSafePayload(input.payload),
|
|
2715
|
-
payloadRef: input.payloadRef ?? null,
|
|
2716
|
-
idempotencyKey: input.idempotencyKey ?? `${input.runId}:${row.seq}`,
|
|
2717
|
-
createdAt: row.createdAt
|
|
2718
|
-
}
|
|
2719
|
-
]
|
|
2720
|
-
});
|
|
2721
|
-
} catch (err) {
|
|
2722
|
-
console.warn(
|
|
2723
|
-
`[apm] runEvents \u53EF\u89C6\u5316\u540C\u6B65\u8DF3\u8FC7 runId=${input.runId} seq=${row.seq}:`,
|
|
2724
|
-
err instanceof Error ? err.message : err
|
|
2725
|
-
);
|
|
2726
|
-
}
|
|
2727
|
-
return row;
|
|
2728
|
-
},
|
|
2729
|
-
list: (input) => local.list(input),
|
|
2730
|
-
async delete(input) {
|
|
2731
|
-
await local.delete(input);
|
|
2732
|
-
const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
|
|
2733
|
-
if (!runIds?.length) return;
|
|
2734
|
-
try {
|
|
2735
|
-
await api.cli.cursorStoreDeleteRunEvents({ runIds });
|
|
2736
|
-
} catch (err) {
|
|
2737
|
-
console.warn(
|
|
2738
|
-
`[apm] runEvents \u53EF\u89C6\u5316\u6E05\u7406\u8DF3\u8FC7:`,
|
|
2739
|
-
err instanceof Error ? err.message : err
|
|
2740
|
-
);
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2745
|
-
async function createApmCursorAgentStore(cfg2, workspaceRef) {
|
|
2746
|
-
const cwd = workspaceRef.trim();
|
|
2747
|
-
const [sqlite, remote] = await Promise.all([
|
|
2748
|
-
openSqliteLocalAgentStore(cwd),
|
|
2749
|
-
Promise.resolve(createRemoteLocalAgentStore(cfg2))
|
|
2750
|
-
]);
|
|
2751
|
-
return composeLocalAgentStore({
|
|
2752
|
-
agents: remote.agents,
|
|
2753
|
-
runs: remote.runs,
|
|
2754
|
-
checkpoints: sqlite.checkpoints,
|
|
2755
|
-
runEvents: mirrorRunEventsForVisualization(cfg2, sqlite.runEvents)
|
|
2756
|
-
});
|
|
2757
|
-
}
|
|
2758
|
-
async function purgeLocalSqliteAgentData(input) {
|
|
2759
|
-
const agentId = input.agentId.trim();
|
|
2760
|
-
const workspaceRef = input.workspaceRef.trim();
|
|
2761
|
-
if (!agentId || !workspaceRef) return;
|
|
2762
|
-
const sqlite = await openSqliteLocalAgentStore(workspaceRef);
|
|
2763
|
-
const runIds = [...input.runIds ?? []];
|
|
2764
|
-
if (runIds.length > 0) {
|
|
2765
|
-
await sqlite.runEvents.delete({ filter: { runIds } });
|
|
2766
|
-
}
|
|
2767
|
-
await sqlite.checkpoints.delete({ filter: { agentIds: [agentId] } });
|
|
2768
|
-
}
|
|
2769
|
-
|
|
2770
|
-
// src/commands/connect/core/worker/run-steer.ts
|
|
2771
|
-
var impl = null;
|
|
2772
|
-
var pending = null;
|
|
2773
|
-
function bindRunSteer(fn) {
|
|
2774
|
-
impl = fn;
|
|
2775
|
-
if (!pending) return;
|
|
2776
|
-
const text = pending;
|
|
2777
|
-
pending = null;
|
|
2778
|
-
void fn(text);
|
|
2779
|
-
}
|
|
2780
|
-
function clearRunSteer() {
|
|
2781
|
-
impl = null;
|
|
2782
|
-
pending = null;
|
|
2783
|
-
}
|
|
2784
|
-
function deliverRunSteer(text) {
|
|
2785
|
-
const trimmed = text.trim();
|
|
2786
|
-
if (!trimmed) return;
|
|
2787
|
-
if (impl) {
|
|
2788
|
-
void impl(trimmed);
|
|
2789
|
-
return;
|
|
2790
|
-
}
|
|
2791
|
-
pending = trimmed;
|
|
2792
|
-
}
|
|
2793
|
-
|
|
2794
|
-
// src/commands/connect/cursor-agent.ts
|
|
2795
|
-
var noopRemoteLogSync = {
|
|
2796
|
-
schedule(_session) {
|
|
2797
|
-
},
|
|
2798
|
-
async flush(_session) {
|
|
2799
|
-
}
|
|
2800
|
-
};
|
|
2801
|
-
function formatCursorRunFailure(runId, options) {
|
|
2802
|
-
const details = [
|
|
2803
|
-
options?.statusError?.trim(),
|
|
2804
|
-
options?.resultText?.trim()
|
|
2805
|
-
].filter((value, index, arr) => {
|
|
2806
|
-
if (!value) return false;
|
|
2807
|
-
return arr.indexOf(value) === index;
|
|
2808
|
-
});
|
|
2809
|
-
if (details.length === 0) {
|
|
2810
|
-
return `Cursor run \u5931\u8D25: ${runId}`;
|
|
2811
|
-
}
|
|
2812
|
-
return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
|
|
2813
|
-
}
|
|
2814
|
-
async function obtainAgent(ctx) {
|
|
2815
|
-
const store = await createApmCursorAgentStore(ctx.cfg, ctx.cwd);
|
|
2816
|
-
const agentOptions = {
|
|
2817
|
-
apiKey: ctx.apiKey,
|
|
2818
|
-
model: { id: ctx.model || "default" },
|
|
2819
|
-
local: {
|
|
2820
|
-
cwd: ctx.cwd,
|
|
2821
|
-
store,
|
|
2822
|
-
...ctx.customTools ? { customTools: ctx.customTools } : {},
|
|
2823
|
-
...ctx.enableSandbox ? {
|
|
2824
|
-
sandboxOptions: { enabled: true },
|
|
2825
|
-
// 只加载项目级设置,避免 ~/.cursor/mcp.json 里需 OAuth/审批的 MCP 在无头沙箱被拒
|
|
2826
|
-
settingSources: ["project"]
|
|
2827
|
-
} : {}
|
|
2828
|
-
},
|
|
2829
|
-
...ctx.mode ? { mode: ctx.mode } : {},
|
|
2830
|
-
...ctx.mcpServers ? { mcpServers: ctx.mcpServers } : {}
|
|
2831
|
-
};
|
|
2832
|
-
const savedAgentId = ctx.resumeAgentId?.trim();
|
|
2833
|
-
if (savedAgentId) {
|
|
2834
|
-
try {
|
|
2835
|
-
const agent = await Agent.resume(savedAgentId, agentOptions);
|
|
2836
|
-
console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
|
|
2837
|
-
return { agent, resumed: true };
|
|
2838
|
-
} catch (err) {
|
|
2839
|
-
console.warn(
|
|
2840
|
-
`[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
|
|
2841
|
-
err instanceof Error ? err.message : err
|
|
2842
|
-
);
|
|
2843
|
-
ctx.onInvalidatePersistedAgentId?.();
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
try {
|
|
2847
|
-
const agent = await Agent.create(agentOptions);
|
|
2848
|
-
return { agent, resumed: false };
|
|
2849
|
-
} catch (err) {
|
|
2850
|
-
if (ctx.enableSandbox && err instanceof Error && /sandbox/i.test(err.message)) {
|
|
2851
|
-
console.warn("[apm] sandbox \u4E0D\u53EF\u7528\uFF0C\u56DE\u9000\u4E3A\u65E0 sandbox:", err.message);
|
|
2852
|
-
return obtainAgent({ ...ctx, enableSandbox: false });
|
|
2853
|
-
}
|
|
2854
|
-
throw err;
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
async function runCursorAgent(cfg2, ctx, options) {
|
|
2858
|
-
const signal = options.signal;
|
|
2859
|
-
if (signal?.aborted) {
|
|
2860
|
-
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
2861
|
-
}
|
|
2862
|
-
const apiKey = ctx.apiKey.trim();
|
|
2863
|
-
if (!apiKey) {
|
|
2864
|
-
throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
|
|
2865
|
-
}
|
|
2866
|
-
const workdir = resolveWorkdirPath(ctx.workdir);
|
|
2867
|
-
const customTools = createCursorCustomTools(cfg2, {
|
|
2868
|
-
onAskQuestion: options.onAskQuestion,
|
|
2869
|
-
appendMessageContent: options.appendMessageContent,
|
|
2870
|
-
enableAppendMessage: options.enableAppendMessage,
|
|
2871
|
-
enableAskQuestion: options.enableAskQuestion,
|
|
2872
|
-
askQuestionExecute: options.askQuestionExecute,
|
|
2873
|
-
enableWebIdePlanTools: options.enableWebIdePlanTools,
|
|
2874
|
-
enableMysqlTools: options.enableMysqlTools,
|
|
2875
|
-
sqlExecutionId: options.sqlExecutionId,
|
|
2876
|
-
toolNames: options.toolNames,
|
|
2877
|
-
taskId: options.taskId,
|
|
2878
|
-
workdir,
|
|
2879
|
-
enablePackageStatusTools: options.enablePackageStatusTools,
|
|
2880
|
-
packageId: options.packageId,
|
|
2881
|
-
projectId: options.projectId,
|
|
2882
|
-
onPackageFailed: options.onPackageFailed
|
|
2883
|
-
});
|
|
2884
|
-
const enableSandbox = Boolean(options.enableSandbox);
|
|
2885
|
-
const mcpServers = options.enablePtySessionMcp ? createPtySessionMcpServers(cfg2) : void 0;
|
|
2886
|
-
let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
|
|
2887
|
-
prompt = withWorkspaceBoundaryHint(prompt);
|
|
2888
|
-
console.log(
|
|
2889
|
-
`[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} cwd=${workdir} sandbox=${enableSandbox}`
|
|
2890
|
-
);
|
|
2891
|
-
const { agent, resumed } = await obtainAgent({
|
|
2892
|
-
cfg: cfg2,
|
|
2893
|
-
apiKey,
|
|
2894
|
-
model: ctx.model,
|
|
2895
|
-
cwd: workdir,
|
|
2896
|
-
user: ctx.user,
|
|
2897
|
-
mode: ctx.mode,
|
|
2898
|
-
resumeAgentId: ctx.resumeAgentId,
|
|
2899
|
-
customTools,
|
|
2900
|
-
enableSandbox,
|
|
2901
|
-
mcpServers,
|
|
2902
|
-
onInvalidatePersistedAgentId: options.onInvalidatePersistedAgentId
|
|
2903
|
-
});
|
|
2904
|
-
const invalidateAgentMapping = () => {
|
|
2905
|
-
options.onInvalidatePersistedAgentId?.();
|
|
2906
|
-
};
|
|
2907
|
-
const eventSession = options.eventSession ?? new EventSession(prompt);
|
|
2908
|
-
const syncRemoteLog = options.createRemoteLogSync ? options.createRemoteLogSync(agent.agentId) : noopRemoteLogSync;
|
|
2909
|
-
let activeRun;
|
|
2910
|
-
const abortRun = () => {
|
|
2911
|
-
if (!activeRun?.supports("cancel")) return;
|
|
2912
|
-
void activeRun.cancel().catch(() => void 0);
|
|
2913
|
-
};
|
|
2914
|
-
signal?.addEventListener("abort", abortRun, { once: true });
|
|
2915
|
-
try {
|
|
2916
|
-
const run = await agent.send(prompt, {
|
|
2917
|
-
...ctx.mode ? { mode: ctx.mode } : {},
|
|
2918
|
-
local: {
|
|
2919
|
-
...options.forceSend ? { force: true } : {},
|
|
2920
|
-
customTools
|
|
2921
|
-
}
|
|
2922
|
-
});
|
|
2923
|
-
activeRun = run;
|
|
2924
|
-
console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
|
|
2925
|
-
bindRunSteer(async (text) => {
|
|
2926
|
-
const steer = run.steer;
|
|
2927
|
-
if (!steer) {
|
|
2928
|
-
console.warn(`[apm] \u5F53\u524D run \u4E0D\u652F\u6301 steer runId=${run.id}`);
|
|
2929
|
-
return;
|
|
2930
|
-
}
|
|
2931
|
-
try {
|
|
2932
|
-
const outcome = await steer(text);
|
|
2933
|
-
console.log(`[apm] steer outcome=${outcome} runId=${run.id}`);
|
|
2934
|
-
} catch (err) {
|
|
2935
|
-
console.error(
|
|
2936
|
-
`[apm] steer \u5931\u8D25 runId=${run.id}:`,
|
|
2937
|
-
err instanceof Error ? err.message : err
|
|
2938
|
-
);
|
|
2939
|
-
}
|
|
2940
|
-
});
|
|
2941
|
-
await options.onRunStarted?.({ agentId: agent.agentId, runId: run.id });
|
|
2942
|
-
let lastRunErrorStatus;
|
|
2943
|
-
for await (const event of run.stream()) {
|
|
2944
|
-
if (signal?.aborted) {
|
|
2945
|
-
abortRun();
|
|
2946
|
-
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
2947
|
-
}
|
|
2948
|
-
if (event.type === "status" && event.status === "ERROR") {
|
|
2949
|
-
const message = event.message?.trim();
|
|
2950
|
-
if (message) {
|
|
2951
|
-
lastRunErrorStatus = message;
|
|
2952
|
-
console.error(
|
|
2953
|
-
`[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
|
|
2954
|
-
);
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
options.onStreamEvent?.(event);
|
|
2958
|
-
eventSession.addEvent(event);
|
|
2959
|
-
syncRemoteLog.schedule(eventSession);
|
|
2960
|
-
}
|
|
2961
|
-
await syncRemoteLog.flush(eventSession);
|
|
2962
|
-
const result = await run.wait();
|
|
2963
|
-
if (result.usage) {
|
|
2964
|
-
eventSession.addRunUsage(result.usage, {
|
|
2965
|
-
durationMs: result.durationMs
|
|
2966
|
-
});
|
|
2967
|
-
await syncRemoteLog.flush(eventSession);
|
|
2968
|
-
console.log(
|
|
2969
|
-
`[apm] Cursor usage total=${result.usage.totalTokens} in=${result.usage.inputTokens} out=${result.usage.outputTokens} cache=${result.usage.cacheReadTokens}/${result.usage.cacheWriteTokens}`
|
|
2970
|
-
);
|
|
2971
|
-
}
|
|
2972
|
-
if (result.status === "error") {
|
|
2973
|
-
const failureMessage = formatCursorRunFailure(result.id, {
|
|
2974
|
-
statusError: lastRunErrorStatus,
|
|
2975
|
-
resultText: result.result
|
|
2976
|
-
});
|
|
2977
|
-
console.error(`[apm] ${failureMessage}`);
|
|
2978
|
-
if (resumed) {
|
|
2979
|
-
invalidateAgentMapping();
|
|
2980
|
-
}
|
|
2981
|
-
throw new Error(failureMessage);
|
|
2982
|
-
}
|
|
2983
|
-
if (result.status === "cancelled") {
|
|
2984
|
-
throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
|
|
2985
|
-
}
|
|
2986
|
-
console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
|
|
2987
|
-
const artifacts = await agent.listArtifacts().catch(() => []);
|
|
2988
|
-
const artifactDocuments = [];
|
|
2989
|
-
for (const artifact of artifacts) {
|
|
2990
|
-
try {
|
|
2991
|
-
const content = (await agent.downloadArtifact(artifact.path)).toString(
|
|
2992
|
-
"utf8"
|
|
2993
|
-
);
|
|
2994
|
-
artifactDocuments.push({ path: artifact.path, content });
|
|
2995
|
-
} catch (err) {
|
|
2996
|
-
console.warn(
|
|
2997
|
-
`[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
|
|
2998
|
-
err instanceof Error ? err.message : err
|
|
2999
|
-
);
|
|
3000
|
-
}
|
|
3001
|
-
}
|
|
3002
|
-
return {
|
|
3003
|
-
runId: result.id,
|
|
3004
|
-
agentId: agent.agentId,
|
|
3005
|
-
status: result.status,
|
|
3006
|
-
result: result.result,
|
|
3007
|
-
durationMs: result.durationMs,
|
|
3008
|
-
usage: result.usage,
|
|
3009
|
-
modelId: result.model?.id?.trim() || ctx.model?.trim() || void 0,
|
|
3010
|
-
assistantText: eventSession.getAssistantText(),
|
|
3011
|
-
createPlan: eventSession.getCreatePlanContent(),
|
|
3012
|
-
artifacts,
|
|
3013
|
-
artifactDocuments
|
|
3014
|
-
};
|
|
3015
|
-
} catch (err) {
|
|
3016
|
-
if (err instanceof CursorAgentError) {
|
|
3017
|
-
if (resumed) {
|
|
3018
|
-
invalidateAgentMapping();
|
|
3019
|
-
}
|
|
3020
|
-
throw new Error(
|
|
3021
|
-
`Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
|
|
3022
|
-
);
|
|
3023
|
-
}
|
|
3024
|
-
throw err;
|
|
3025
|
-
} finally {
|
|
3026
|
-
clearRunSteer();
|
|
3027
|
-
signal?.removeEventListener("abort", abortRun);
|
|
3028
|
-
await agent[Symbol.asyncDispose]();
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
|
|
3032
|
-
// src/commands/connect/webide-message-log.ts
|
|
3033
|
-
var SYNC_INTERVAL_MS = 2e3;
|
|
3034
|
-
function webIdeEventsObjectPrefix(taskId, messageId) {
|
|
3035
|
-
return `events/webide/${taskId}/${messageId}/`;
|
|
3036
|
-
}
|
|
3037
|
-
async function putDirtyEvents(minio, bucket, prefix, events) {
|
|
3038
|
-
for (const event of events) {
|
|
3039
|
-
const key = `${prefix}${event.index}.json`;
|
|
3040
|
-
const body = Buffer.from(
|
|
3041
|
-
JSON.stringify({
|
|
3042
|
-
index: event.index,
|
|
3043
|
-
type: event.type,
|
|
3044
|
-
data: event.data
|
|
3045
|
-
}),
|
|
3046
|
-
"utf8"
|
|
3047
|
-
);
|
|
3048
|
-
await minio.putObject(bucket, key, body, {
|
|
3049
|
-
"Content-Type": "application/json"
|
|
3050
|
-
});
|
|
3051
|
-
}
|
|
3052
|
-
}
|
|
3053
|
-
async function upsertLogHeader(cfg2, ctx, patch, objectPrefix) {
|
|
3054
|
-
const api = createApmApiClient(cfg2);
|
|
3055
|
-
await api.cli.webideUpsertMessageLog({
|
|
3056
|
-
messageId: ctx.messageId,
|
|
3057
|
-
agentId: ctx.agentId,
|
|
3058
|
-
objectPrefix,
|
|
3059
|
-
...patch
|
|
3060
|
-
});
|
|
3061
|
-
}
|
|
3062
|
-
function createThrottledWebIdeMessageLogSync(cfg2, ctx, onError, options) {
|
|
3063
|
-
let lastRunAt = 0;
|
|
3064
|
-
let timer;
|
|
3065
|
-
let latestSession;
|
|
3066
|
-
let syncChain = Promise.resolve();
|
|
3067
|
-
let minio;
|
|
3068
|
-
let bucket = "";
|
|
3069
|
-
let firstLogNotified = false;
|
|
3070
|
-
const resolvePrefix = () => options?.objectPrefixOverride?.trim() || webIdeEventsObjectPrefix(ctx.taskId, ctx.messageId);
|
|
3071
|
-
const ensureMinio = async () => {
|
|
3072
|
-
if (minio) return;
|
|
3073
|
-
const created = await createApmLogMinioClient(cfg2);
|
|
3074
|
-
minio = created.client;
|
|
3075
|
-
bucket = created.bucket;
|
|
3076
|
-
await minio.ensureBucket(bucket);
|
|
3077
|
-
};
|
|
3078
|
-
const notifyFirstLog = async () => {
|
|
3079
|
-
if (firstLogNotified) return;
|
|
3080
|
-
firstLogNotified = true;
|
|
3081
|
-
try {
|
|
3082
|
-
await options?.onFirstLogSynced?.();
|
|
3083
|
-
} catch (err) {
|
|
3084
|
-
firstLogNotified = false;
|
|
3085
|
-
onError(err);
|
|
3086
|
-
}
|
|
3087
|
-
};
|
|
3088
|
-
const syncDirtyEventsOnce = async (session) => {
|
|
3089
|
-
const events = session.getDirtyEvents();
|
|
3090
|
-
if (events.length === 0) return;
|
|
3091
|
-
lastRunAt = Date.now();
|
|
3092
|
-
try {
|
|
3093
|
-
await ensureMinio();
|
|
3094
|
-
const prefix = resolvePrefix();
|
|
3095
|
-
await putDirtyEvents(minio, bucket, prefix, events);
|
|
3096
|
-
await upsertLogHeader(
|
|
3097
|
-
cfg2,
|
|
3098
|
-
ctx,
|
|
3099
|
-
{
|
|
3100
|
-
eventCount: session.getEventCount()
|
|
3101
|
-
},
|
|
3102
|
-
prefix
|
|
2289
|
+
// src/commands/connect/local-sqlite-agent-store.ts
|
|
2290
|
+
var sqliteByKey = /* @__PURE__ */ new Map();
|
|
2291
|
+
async function openSqliteLocalAgentStore(workspaceRef) {
|
|
2292
|
+
const cwd = workspaceRef.trim();
|
|
2293
|
+
if (!cwd) throw new Error("openSqliteLocalAgentStore: workspaceRef \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2294
|
+
const existing = sqliteByKey.get(cwd);
|
|
2295
|
+
if (existing) return existing;
|
|
2296
|
+
const pending2 = SqliteLocalAgentStore.open({ workspaceRef: cwd }).then(
|
|
2297
|
+
(store) => {
|
|
2298
|
+
console.log(
|
|
2299
|
+
`[apm] Cursor store sqlite ready cwd=${cwd} stateRoot=${store.stateRoot}`
|
|
3103
2300
|
);
|
|
3104
|
-
|
|
3105
|
-
await notifyFirstLog();
|
|
3106
|
-
} catch (err) {
|
|
3107
|
-
onError(err);
|
|
3108
|
-
}
|
|
3109
|
-
};
|
|
3110
|
-
const drainDirtyEvents = async (session) => {
|
|
3111
|
-
while (session.getDirtyEvents().length > 0) {
|
|
3112
|
-
await syncDirtyEventsOnce(session);
|
|
3113
|
-
}
|
|
3114
|
-
};
|
|
3115
|
-
const enqueueSync = (session) => {
|
|
3116
|
-
syncChain = syncChain.then(() => drainDirtyEvents(session));
|
|
3117
|
-
};
|
|
3118
|
-
return {
|
|
3119
|
-
async markRun(runId, runStatus, lastError, tokenUsage) {
|
|
3120
|
-
try {
|
|
3121
|
-
await upsertLogHeader(
|
|
3122
|
-
cfg2,
|
|
3123
|
-
ctx,
|
|
3124
|
-
{
|
|
3125
|
-
runId,
|
|
3126
|
-
runStatus,
|
|
3127
|
-
lastError: lastError ?? null,
|
|
3128
|
-
...tokenUsage ? { tokenUsage } : {}
|
|
3129
|
-
},
|
|
3130
|
-
resolvePrefix()
|
|
3131
|
-
);
|
|
3132
|
-
} catch (err) {
|
|
3133
|
-
onError(err);
|
|
3134
|
-
}
|
|
3135
|
-
},
|
|
3136
|
-
schedule(session) {
|
|
3137
|
-
latestSession = session;
|
|
3138
|
-
const now = Date.now();
|
|
3139
|
-
const elapsed = now - lastRunAt;
|
|
3140
|
-
if (elapsed >= SYNC_INTERVAL_MS) {
|
|
3141
|
-
if (timer) {
|
|
3142
|
-
clearTimeout(timer);
|
|
3143
|
-
timer = void 0;
|
|
3144
|
-
}
|
|
3145
|
-
enqueueSync(session);
|
|
3146
|
-
return;
|
|
3147
|
-
}
|
|
3148
|
-
if (timer) return;
|
|
3149
|
-
timer = setTimeout(() => {
|
|
3150
|
-
timer = void 0;
|
|
3151
|
-
if (latestSession) enqueueSync(latestSession);
|
|
3152
|
-
}, SYNC_INTERVAL_MS - elapsed);
|
|
3153
|
-
},
|
|
3154
|
-
async flush(session) {
|
|
3155
|
-
if (timer) {
|
|
3156
|
-
clearTimeout(timer);
|
|
3157
|
-
timer = void 0;
|
|
3158
|
-
}
|
|
3159
|
-
await syncChain;
|
|
3160
|
-
await drainDirtyEvents(session);
|
|
2301
|
+
return store;
|
|
3161
2302
|
}
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
// src/commands/sync-webide-agent.ts
|
|
3166
|
-
import {
|
|
3167
|
-
existsSync as existsSync5,
|
|
3168
|
-
mkdirSync as mkdirSync4,
|
|
3169
|
-
readdirSync as readdirSync3,
|
|
3170
|
-
readFileSync as readFileSync4,
|
|
3171
|
-
unlinkSync,
|
|
3172
|
-
writeFileSync as writeFileSync3
|
|
3173
|
-
} from "fs";
|
|
3174
|
-
import { join as join4 } from "path";
|
|
3175
|
-
var REVISION_FILE = ".webide-agent.json";
|
|
3176
|
-
var RULE_KEY = /^[a-z0-9_]+$/;
|
|
3177
|
-
function withTrailingNewline(text) {
|
|
3178
|
-
return text.endsWith("\n") ? text : `${text}
|
|
3179
|
-
`;
|
|
2303
|
+
);
|
|
2304
|
+
sqliteByKey.set(cwd, pending2);
|
|
2305
|
+
return pending2;
|
|
3180
2306
|
}
|
|
3181
|
-
function
|
|
3182
|
-
|
|
3183
|
-
if (!existsSync5(path3)) return null;
|
|
2307
|
+
function jsonSafePayload(value) {
|
|
2308
|
+
if (value == null) return null;
|
|
3184
2309
|
try {
|
|
3185
|
-
|
|
3186
|
-
if (parsed?.version === 1 && parsed.revision?.trim()) {
|
|
3187
|
-
return parsed.revision.trim();
|
|
3188
|
-
}
|
|
2310
|
+
return JSON.parse(JSON.stringify(value));
|
|
3189
2311
|
} catch {
|
|
3190
2312
|
return null;
|
|
3191
2313
|
}
|
|
3192
|
-
return null;
|
|
3193
|
-
}
|
|
3194
|
-
function writeRevision(apmDir, revision) {
|
|
3195
|
-
writeFileSync3(
|
|
3196
|
-
toFsPath(join4(apmDir, REVISION_FILE)),
|
|
3197
|
-
`${JSON.stringify({ version: 1, revision }, null, 2)}
|
|
3198
|
-
`,
|
|
3199
|
-
"utf8"
|
|
3200
|
-
);
|
|
3201
2314
|
}
|
|
3202
|
-
|
|
2315
|
+
function mirrorRunEventsForVisualization(cfg2, local) {
|
|
3203
2316
|
const api = createApmApiClient(cfg2);
|
|
3204
|
-
const bundle = await api.cli.webideAgentConfig();
|
|
3205
|
-
const revision = bundle.revision?.trim();
|
|
3206
|
-
if (!revision) {
|
|
3207
|
-
throw new Error("[apm] \u5E73\u53F0\u672A\u8FD4\u56DE WebIDE \u89C4\u5219\u7248\u672C");
|
|
3208
|
-
}
|
|
3209
|
-
const apmDir = workspaceApmDir(workdir);
|
|
3210
|
-
mkdirSync4(toFsPath(apmDir), { recursive: true });
|
|
3211
|
-
if (readRevision(apmDir) === revision) {
|
|
3212
|
-
return { revision, wrote: false, ruleCount: bundle.rules?.length ?? 0 };
|
|
3213
|
-
}
|
|
3214
|
-
const rulesDir = join4(apmDir, "rules");
|
|
3215
|
-
mkdirSync4(toFsPath(rulesDir), { recursive: true });
|
|
3216
|
-
writeFileSync3(
|
|
3217
|
-
toFsPath(join4(apmDir, "AGENTS.md")),
|
|
3218
|
-
withTrailingNewline(bundle.guide ?? ""),
|
|
3219
|
-
"utf8"
|
|
3220
|
-
);
|
|
3221
|
-
const keep = /* @__PURE__ */ new Set();
|
|
3222
|
-
for (const rule of bundle.rules ?? []) {
|
|
3223
|
-
const key = rule.key?.trim() ?? "";
|
|
3224
|
-
if (!RULE_KEY.test(key)) {
|
|
3225
|
-
throw new Error(`[apm] \u975E\u6CD5\u89C4\u5219\u540D: ${key || "(\u7A7A)"}`);
|
|
3226
|
-
}
|
|
3227
|
-
keep.add(`${key}.md`);
|
|
3228
|
-
writeFileSync3(
|
|
3229
|
-
toFsPath(join4(rulesDir, `${key}.md`)),
|
|
3230
|
-
withTrailingNewline(rule.body ?? ""),
|
|
3231
|
-
"utf8"
|
|
3232
|
-
);
|
|
3233
|
-
}
|
|
3234
|
-
for (const name of readdirSync3(toFsPath(rulesDir))) {
|
|
3235
|
-
if (!name.endsWith(".md")) continue;
|
|
3236
|
-
if (keep.has(name)) continue;
|
|
3237
|
-
unlinkSync(toFsPath(join4(rulesDir, name)));
|
|
3238
|
-
}
|
|
3239
|
-
writeRevision(apmDir, revision);
|
|
3240
2317
|
return {
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
2318
|
+
async append(input) {
|
|
2319
|
+
const row = await local.append(input);
|
|
2320
|
+
try {
|
|
2321
|
+
await api.cli.cursorStoreAppendRunEvents({
|
|
2322
|
+
events: [
|
|
2323
|
+
{
|
|
2324
|
+
runId: input.runId,
|
|
2325
|
+
eventType: input.eventType,
|
|
2326
|
+
payload: jsonSafePayload(input.payload),
|
|
2327
|
+
payloadRef: input.payloadRef ?? null,
|
|
2328
|
+
idempotencyKey: input.idempotencyKey ?? `${input.runId}:${row.seq}`,
|
|
2329
|
+
createdAt: row.createdAt
|
|
2330
|
+
}
|
|
2331
|
+
]
|
|
2332
|
+
});
|
|
2333
|
+
} catch (err) {
|
|
2334
|
+
console.warn(
|
|
2335
|
+
`[apm] runEvents \u53EF\u89C6\u5316\u540C\u6B65\u8DF3\u8FC7 runId=${input.runId} seq=${row.seq}:`,
|
|
2336
|
+
err instanceof Error ? err.message : err
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
return row;
|
|
2340
|
+
},
|
|
2341
|
+
list: (input) => local.list(input),
|
|
2342
|
+
async delete(input) {
|
|
2343
|
+
await local.delete(input);
|
|
2344
|
+
const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
|
|
2345
|
+
if (!runIds?.length) return;
|
|
2346
|
+
try {
|
|
2347
|
+
await api.cli.cursorStoreDeleteRunEvents({ runIds });
|
|
2348
|
+
} catch (err) {
|
|
2349
|
+
console.warn(
|
|
2350
|
+
`[apm] runEvents \u53EF\u89C6\u5316\u6E05\u7406\u8DF3\u8FC7:`,
|
|
2351
|
+
err instanceof Error ? err.message : err
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
3244
2355
|
};
|
|
3245
2356
|
}
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
}
|
|
3259
|
-
|
|
3260
|
-
// src/utils/project-documents.ts
|
|
3261
|
-
import {
|
|
3262
|
-
existsSync as existsSync6,
|
|
3263
|
-
readdirSync as readdirSync4,
|
|
3264
|
-
readFileSync as readFileSync5,
|
|
3265
|
-
rmSync,
|
|
3266
|
-
writeFileSync as writeFileSync4
|
|
3267
|
-
} from "fs";
|
|
3268
|
-
import { createHash as createHash2 } from "crypto";
|
|
3269
|
-
import { dirname as dirname2, join as join5, relative as relative4, sep } from "path";
|
|
3270
|
-
var MANIFEST_FILE = "manifest.json";
|
|
3271
|
-
function normalizeProjectIdForPath(projectId) {
|
|
3272
|
-
const id = projectId.trim();
|
|
3273
|
-
if (!id) {
|
|
3274
|
-
throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
3275
|
-
}
|
|
3276
|
-
if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
|
|
3277
|
-
throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
|
|
3278
|
-
}
|
|
3279
|
-
return id;
|
|
3280
|
-
}
|
|
3281
|
-
function projectDocumentsDir(apmRoot, projectId) {
|
|
3282
|
-
const id = normalizeProjectIdForPath(projectId);
|
|
3283
|
-
return join5(apmRoot ?? workspaceApmDir(), "project", id);
|
|
3284
|
-
}
|
|
3285
|
-
function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
|
|
3286
|
-
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
3287
|
-
return join5(
|
|
3288
|
-
projectDocumentsDir(apmRoot, projectId),
|
|
3289
|
-
...normalized.split("/")
|
|
3290
|
-
);
|
|
3291
|
-
}
|
|
3292
|
-
function normalizeLocalDocumentPath(path3) {
|
|
3293
|
-
const trimmed = path3.trim().replace(/\\/g, "/");
|
|
3294
|
-
if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
|
|
3295
|
-
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path3}`);
|
|
3296
|
-
}
|
|
3297
|
-
const segments = trimmed.split("/").filter(Boolean);
|
|
3298
|
-
if (segments.some((segment) => segment === ".." || segment === ".")) {
|
|
3299
|
-
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path3}`);
|
|
3300
|
-
}
|
|
3301
|
-
return segments.join("/");
|
|
3302
|
-
}
|
|
3303
|
-
function hashLocalFileContent(content) {
|
|
3304
|
-
return createHash2("sha256").update(content, "utf8").digest("hex");
|
|
3305
|
-
}
|
|
3306
|
-
function readLocalManifest(apmRoot, projectId) {
|
|
3307
|
-
const manifestPath2 = join5(
|
|
3308
|
-
projectDocumentsDir(apmRoot, projectId),
|
|
3309
|
-
MANIFEST_FILE
|
|
3310
|
-
);
|
|
3311
|
-
if (!existsSync6(manifestPath2)) {
|
|
3312
|
-
return null;
|
|
3313
|
-
}
|
|
3314
|
-
try {
|
|
3315
|
-
return JSON.parse(
|
|
3316
|
-
readFileSync5(manifestPath2, "utf8")
|
|
3317
|
-
);
|
|
3318
|
-
} catch {
|
|
3319
|
-
return null;
|
|
3320
|
-
}
|
|
2357
|
+
async function createApmCursorAgentStore(cfg2, workspaceRef) {
|
|
2358
|
+
const cwd = workspaceRef.trim();
|
|
2359
|
+
const [sqlite, remote] = await Promise.all([
|
|
2360
|
+
openSqliteLocalAgentStore(cwd),
|
|
2361
|
+
Promise.resolve(createRemoteLocalAgentStore(cfg2))
|
|
2362
|
+
]);
|
|
2363
|
+
return composeLocalAgentStore({
|
|
2364
|
+
agents: remote.agents,
|
|
2365
|
+
runs: remote.runs,
|
|
2366
|
+
checkpoints: sqlite.checkpoints,
|
|
2367
|
+
runEvents: mirrorRunEventsForVisualization(cfg2, sqlite.runEvents)
|
|
2368
|
+
});
|
|
3321
2369
|
}
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
walk(abs);
|
|
3333
|
-
continue;
|
|
3334
|
-
}
|
|
3335
|
-
if (entry.isFile() && entry.name === MANIFEST_FILE) {
|
|
3336
|
-
continue;
|
|
3337
|
-
}
|
|
3338
|
-
const rel = relative4(root, abs).split(sep).join("/");
|
|
3339
|
-
paths.push(rel);
|
|
3340
|
-
}
|
|
3341
|
-
};
|
|
3342
|
-
walk(root);
|
|
3343
|
-
return paths.sort();
|
|
2370
|
+
|
|
2371
|
+
// src/commands/connect/core/worker/run-steer.ts
|
|
2372
|
+
var impl = null;
|
|
2373
|
+
var pending = null;
|
|
2374
|
+
function bindRunSteer(fn) {
|
|
2375
|
+
impl = fn;
|
|
2376
|
+
if (!pending) return;
|
|
2377
|
+
const text = pending;
|
|
2378
|
+
pending = null;
|
|
2379
|
+
void fn(text);
|
|
3344
2380
|
}
|
|
3345
|
-
function
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
);
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
download.push(path3);
|
|
3356
|
-
}
|
|
3357
|
-
}
|
|
3358
|
-
const deleteLocal = [];
|
|
3359
|
-
for (const path3 of localMap.keys()) {
|
|
3360
|
-
if (!remoteMap.has(path3)) {
|
|
3361
|
-
deleteLocal.push(path3);
|
|
3362
|
-
}
|
|
2381
|
+
function clearRunSteer() {
|
|
2382
|
+
impl = null;
|
|
2383
|
+
pending = null;
|
|
2384
|
+
}
|
|
2385
|
+
function deliverRunSteer(text) {
|
|
2386
|
+
const trimmed = text.trim();
|
|
2387
|
+
if (!trimmed) return;
|
|
2388
|
+
if (impl) {
|
|
2389
|
+
void impl(trimmed);
|
|
2390
|
+
return;
|
|
3363
2391
|
}
|
|
3364
|
-
|
|
2392
|
+
pending = trimmed;
|
|
3365
2393
|
}
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
2394
|
+
|
|
2395
|
+
// src/commands/connect/cursor-agent.ts
|
|
2396
|
+
var noopRemoteLogSync = {
|
|
2397
|
+
schedule(_session) {
|
|
2398
|
+
},
|
|
2399
|
+
async flush(_session) {
|
|
3370
2400
|
}
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
}
|
|
3382
|
-
const baseline = await api.cli.projectBaseBranch({ taskId });
|
|
3383
|
-
const projectId = baseline.projectId?.trim() || null;
|
|
3384
|
-
if (!projectId) {
|
|
3385
|
-
return {
|
|
3386
|
-
projectId: null,
|
|
3387
|
-
diagnostic: `\u4EFB\u52A1 ${taskId} \u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
|
|
3388
|
-
};
|
|
3389
|
-
}
|
|
3390
|
-
return { projectId, diagnostic: null };
|
|
3391
|
-
} catch (err) {
|
|
3392
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
3393
|
-
return {
|
|
3394
|
-
projectId: null,
|
|
3395
|
-
diagnostic: detail.replace(/^\[apm\]\s*/, "")
|
|
3396
|
-
};
|
|
2401
|
+
};
|
|
2402
|
+
function formatCursorRunFailure(runId, options) {
|
|
2403
|
+
const details = [
|
|
2404
|
+
options?.statusError?.trim(),
|
|
2405
|
+
options?.resultText?.trim()
|
|
2406
|
+
].filter((value, index, arr) => {
|
|
2407
|
+
if (!value) return false;
|
|
2408
|
+
return arr.indexOf(value) === index;
|
|
2409
|
+
});
|
|
2410
|
+
if (details.length === 0) {
|
|
2411
|
+
return `Cursor run \u5931\u8D25: ${runId}`;
|
|
3397
2412
|
}
|
|
2413
|
+
return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
|
|
3398
2414
|
}
|
|
3399
|
-
async function
|
|
3400
|
-
const
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
2415
|
+
async function obtainAgent(ctx) {
|
|
2416
|
+
const store = await createApmCursorAgentStore(ctx.cfg, ctx.cwd);
|
|
2417
|
+
const agentOptions = {
|
|
2418
|
+
apiKey: ctx.apiKey,
|
|
2419
|
+
model: { id: ctx.model || "default" },
|
|
2420
|
+
local: {
|
|
2421
|
+
cwd: ctx.cwd,
|
|
2422
|
+
store,
|
|
2423
|
+
...ctx.customTools ? { customTools: ctx.customTools } : {},
|
|
2424
|
+
...ctx.enableSandbox ? {
|
|
2425
|
+
sandboxOptions: { enabled: true },
|
|
2426
|
+
// 只加载项目级设置,避免 ~/.cursor/mcp.json 里需 OAuth/审批的 MCP 在无头沙箱被拒
|
|
2427
|
+
settingSources: ["project"]
|
|
2428
|
+
} : {}
|
|
2429
|
+
},
|
|
2430
|
+
...ctx.mode ? { mode: ctx.mode } : {}
|
|
3405
2431
|
};
|
|
3406
|
-
const
|
|
3407
|
-
if (
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
workdirPath,
|
|
3417
|
-
options
|
|
3418
|
-
);
|
|
3419
|
-
if (!projectId) {
|
|
3420
|
-
console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
|
|
3421
|
-
${diagnostic ?? ""}`);
|
|
3422
|
-
return empty;
|
|
3423
|
-
}
|
|
3424
|
-
const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
|
|
3425
|
-
const docsDir = projectDocumentsDir(targetApmDir, projectId);
|
|
3426
|
-
await ensureDirExists(docsDir);
|
|
3427
|
-
const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
|
|
3428
|
-
{ projectId }
|
|
3429
|
-
);
|
|
3430
|
-
if (!remoteManifest) {
|
|
3431
|
-
console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
|
|
3432
|
-
return { ...empty, projectId };
|
|
3433
|
-
}
|
|
3434
|
-
const localManifest = readLocalManifest(targetApmDir, projectId);
|
|
3435
|
-
const { download, deleteLocal } = diffManifestPaths(
|
|
3436
|
-
remoteManifest,
|
|
3437
|
-
localManifest
|
|
3438
|
-
);
|
|
3439
|
-
let downloaded = 0;
|
|
3440
|
-
if (download.length > 0) {
|
|
3441
|
-
const { list } = await api.cli.listProjectDocuments({
|
|
3442
|
-
projectId,
|
|
3443
|
-
paths: download.join(",")
|
|
3444
|
-
});
|
|
3445
|
-
for (const doc of list) {
|
|
3446
|
-
const absPath = toFsPath(
|
|
3447
|
-
projectDocumentLocalPath(targetApmDir, projectId, doc.path)
|
|
2432
|
+
const savedAgentId = ctx.resumeAgentId?.trim();
|
|
2433
|
+
if (savedAgentId) {
|
|
2434
|
+
try {
|
|
2435
|
+
const agent = await Agent.resume(savedAgentId, agentOptions);
|
|
2436
|
+
console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
|
|
2437
|
+
return { agent, resumed: true };
|
|
2438
|
+
} catch (err) {
|
|
2439
|
+
console.warn(
|
|
2440
|
+
`[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
|
|
2441
|
+
err instanceof Error ? err.message : err
|
|
3448
2442
|
);
|
|
3449
|
-
|
|
3450
|
-
writeFileSync4(absPath, doc.content, "utf8");
|
|
3451
|
-
downloaded += 1;
|
|
3452
|
-
}
|
|
3453
|
-
}
|
|
3454
|
-
let deleted = 0;
|
|
3455
|
-
for (const path3 of deleteLocal) {
|
|
3456
|
-
const absPath = toFsPath(
|
|
3457
|
-
projectDocumentLocalPath(targetApmDir, projectId, path3)
|
|
3458
|
-
);
|
|
3459
|
-
if (existsSync6(absPath)) {
|
|
3460
|
-
rmSync(absPath, { force: true });
|
|
3461
|
-
deleted += 1;
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
writeFileSync4(
|
|
3465
|
-
toFsPath(join5(docsDir, MANIFEST_FILE)),
|
|
3466
|
-
`${JSON.stringify(remoteManifest, null, 2)}
|
|
3467
|
-
`,
|
|
3468
|
-
"utf8"
|
|
3469
|
-
);
|
|
3470
|
-
console.log(
|
|
3471
|
-
`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
|
|
3472
|
-
);
|
|
3473
|
-
return {
|
|
3474
|
-
synced: true,
|
|
3475
|
-
projectId,
|
|
3476
|
-
downloaded,
|
|
3477
|
-
deleted
|
|
3478
|
-
};
|
|
3479
|
-
}
|
|
3480
|
-
async function syncProjectDocumentsPush(cfg2, workdirPath, apmRoot, options) {
|
|
3481
|
-
const api = createApmApiClient(cfg2);
|
|
3482
|
-
const { projectId } = await resolveProjectIdForSync(
|
|
3483
|
-
api,
|
|
3484
|
-
workdirPath,
|
|
3485
|
-
options
|
|
3486
|
-
);
|
|
3487
|
-
if (!projectId) {
|
|
3488
|
-
return 0;
|
|
3489
|
-
}
|
|
3490
|
-
const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
|
|
3491
|
-
const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
|
|
3492
|
-
if (localPaths.length === 0) {
|
|
3493
|
-
console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
3494
|
-
return 0;
|
|
3495
|
-
}
|
|
3496
|
-
const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
|
|
3497
|
-
const remoteHashByPath = new Map(
|
|
3498
|
-
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
3499
|
-
);
|
|
3500
|
-
const remoteDescriptionByPath = new Map(
|
|
3501
|
-
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
|
|
3502
|
-
);
|
|
3503
|
-
let synced = 0;
|
|
3504
|
-
for (const path3 of localPaths) {
|
|
3505
|
-
const absPath = toFsPath(
|
|
3506
|
-
projectDocumentLocalPath(targetApmDir, projectId, path3)
|
|
3507
|
-
);
|
|
3508
|
-
const content = readFileSync5(absPath, "utf8");
|
|
3509
|
-
const contentHash = hashLocalFileContent(content);
|
|
3510
|
-
if (remoteHashByPath.get(path3) === contentHash) {
|
|
3511
|
-
continue;
|
|
2443
|
+
ctx.onInvalidatePersistedAgentId?.();
|
|
3512
2444
|
}
|
|
3513
|
-
await api.cli.upsertProjectDocument({
|
|
3514
|
-
projectId,
|
|
3515
|
-
path: path3,
|
|
3516
|
-
content,
|
|
3517
|
-
description: remoteDescriptionByPath.get(path3) ?? void 0
|
|
3518
|
-
});
|
|
3519
|
-
synced += 1;
|
|
3520
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path3}`);
|
|
3521
|
-
}
|
|
3522
|
-
if (synced === 0) {
|
|
3523
|
-
console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
3524
2445
|
}
|
|
3525
|
-
return synced;
|
|
3526
|
-
}
|
|
3527
|
-
|
|
3528
|
-
// src/commands/connect/upload-package-artifact.ts
|
|
3529
|
-
import fs2 from "node:fs";
|
|
3530
|
-
import path2 from "node:path";
|
|
3531
|
-
function packageTmpDir(packageId) {
|
|
3532
|
-
return `/data/package-tmp/${packageId}`;
|
|
3533
|
-
}
|
|
3534
|
-
function expectedLocalArtifactPath(target, packageId) {
|
|
3535
|
-
const fileName = target === "frontend" ? "dist.zip" : "jars.zip";
|
|
3536
|
-
return path2.join(packageTmpDir(packageId), fileName);
|
|
3537
|
-
}
|
|
3538
|
-
function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
|
|
3539
|
-
const folder = target === "frontend" ? "vue" : "java";
|
|
3540
|
-
const suffix = target === "frontend" ? "dist.zip" : "jar.zip";
|
|
3541
|
-
return `packages/${projectId}/${folder}/${timestampMs}.${suffix}`;
|
|
3542
|
-
}
|
|
3543
|
-
function removePackageTmpDir(packageId) {
|
|
3544
|
-
const dir = packageTmpDir(packageId);
|
|
3545
2446
|
try {
|
|
3546
|
-
|
|
2447
|
+
const agent = await Agent.create(agentOptions);
|
|
2448
|
+
return { agent, resumed: false };
|
|
3547
2449
|
} catch (err) {
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
2450
|
+
if (ctx.enableSandbox && err instanceof Error && /sandbox/i.test(err.message)) {
|
|
2451
|
+
console.warn("[apm] sandbox \u4E0D\u53EF\u7528\uFF0C\u56DE\u9000\u4E3A\u65E0 sandbox:", err.message);
|
|
2452
|
+
return obtainAgent({ ...ctx, enableSandbox: false });
|
|
2453
|
+
}
|
|
2454
|
+
throw err;
|
|
3552
2455
|
}
|
|
3553
2456
|
}
|
|
3554
|
-
async function
|
|
3555
|
-
const
|
|
3556
|
-
if (
|
|
3557
|
-
throw new Error(
|
|
2457
|
+
async function runCursorAgent(cfg2, ctx, options) {
|
|
2458
|
+
const signal = options.signal;
|
|
2459
|
+
if (signal?.aborted) {
|
|
2460
|
+
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
3558
2461
|
}
|
|
3559
|
-
const
|
|
3560
|
-
if (!
|
|
3561
|
-
throw new Error(
|
|
2462
|
+
const apiKey = ctx.apiKey.trim();
|
|
2463
|
+
if (!apiKey) {
|
|
2464
|
+
throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
|
|
3562
2465
|
}
|
|
3563
|
-
const
|
|
3564
|
-
const
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
async function updatePackageStatus(cfg2, packageId, status, extra) {
|
|
3577
|
-
const api = createApmApiClient(cfg2);
|
|
3578
|
-
await api.cli.projectPackageUpdateStatus({
|
|
3579
|
-
id: packageId,
|
|
3580
|
-
status,
|
|
3581
|
-
...extra?.error != null ? { error: extra.error } : {},
|
|
3582
|
-
...extra?.content != null ? { content: extra.content } : {},
|
|
3583
|
-
...extra?.artifactPath != null ? { artifactPath: extra.artifactPath } : {},
|
|
3584
|
-
...extra?.artifactUrl != null ? { artifactUrl: extra.artifactUrl } : {}
|
|
2466
|
+
const workdir = resolveWorkdirPath(ctx.workdir);
|
|
2467
|
+
const customTools = createCursorCustomTools(cfg2, {
|
|
2468
|
+
onAskQuestion: options.onAskQuestion,
|
|
2469
|
+
appendMessageContent: options.appendMessageContent,
|
|
2470
|
+
enableAppendMessage: options.enableAppendMessage,
|
|
2471
|
+
enableAskQuestion: options.enableAskQuestion,
|
|
2472
|
+
askQuestionExecute: options.askQuestionExecute,
|
|
2473
|
+
enableWebIdePlanTools: options.enableWebIdePlanTools,
|
|
2474
|
+
enableMysqlTools: options.enableMysqlTools,
|
|
2475
|
+
sqlExecutionId: options.sqlExecutionId,
|
|
2476
|
+
toolNames: options.toolNames,
|
|
2477
|
+
taskId: options.taskId,
|
|
2478
|
+
workdir
|
|
3585
2479
|
});
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
const messageId = msg2.messageId;
|
|
3590
|
-
const packageId = msg2.packageId;
|
|
3591
|
-
const projectId = msg2.projectId;
|
|
2480
|
+
const enableSandbox = Boolean(options.enableSandbox);
|
|
2481
|
+
let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
|
|
2482
|
+
prompt = withWorkspaceBoundaryHint(prompt);
|
|
3592
2483
|
console.log(
|
|
3593
|
-
`[apm]
|
|
2484
|
+
`[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} cwd=${workdir} sandbox=${enableSandbox}`
|
|
3594
2485
|
);
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
)
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
await syncPrepLog();
|
|
3625
|
-
try {
|
|
3626
|
-
const result = await work();
|
|
3627
|
-
eventSession.addCliStep(step, formatOk(result), "ok");
|
|
3628
|
-
await syncPrepLog();
|
|
3629
|
-
return result;
|
|
3630
|
-
} catch (err) {
|
|
3631
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
3632
|
-
eventSession.addCliStep(step, detail, "error");
|
|
3633
|
-
await syncPrepLog();
|
|
3634
|
-
throw err;
|
|
3635
|
-
}
|
|
3636
|
-
};
|
|
3637
|
-
try {
|
|
3638
|
-
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3639
|
-
await runPrepStep(
|
|
3640
|
-
"\u521D\u59CB\u5316\u5DE5\u4F5C\u533A",
|
|
3641
|
-
async () => {
|
|
3642
|
-
const { didInit } = await ensureWorkspaceInitialized(workdir);
|
|
3643
|
-
return didInit;
|
|
3644
|
-
},
|
|
3645
|
-
(didInit) => didInit ? "\u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A" : "\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA"
|
|
3646
|
-
);
|
|
3647
|
-
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3648
|
-
await runPrepStep(
|
|
3649
|
-
"\u540C\u6B65 WebIDE \u89C4\u5219",
|
|
3650
|
-
async () => syncWebIdeAgentFiles(workdir, cfg2),
|
|
3651
|
-
(result) => result.wrote ? `\u5DF2\u5199\u5165 ${result.ruleCount} \u6761\u89C4\u5219` : "\u89C4\u5219\u672A\u53D8\u5316\uFF0C\u8DF3\u8FC7"
|
|
3652
|
-
);
|
|
3653
|
-
await runPrepStep(
|
|
3654
|
-
"\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355",
|
|
3655
|
-
async () => {
|
|
3656
|
-
const workspaceRepos = resolveWorkspaceRepos(workdir);
|
|
3657
|
-
try {
|
|
3658
|
-
await enrichWorkspaceReposRemoteUrls(workspaceRepos);
|
|
3659
|
-
} catch (err) {
|
|
3660
|
-
console.warn(
|
|
3661
|
-
"[apm] \u8865\u9F50 workspace-repos remoteUrl \u5931\u8D25:",
|
|
3662
|
-
err instanceof Error ? err.message : err
|
|
3663
|
-
);
|
|
3664
|
-
}
|
|
3665
|
-
return workspaceRepos;
|
|
3666
|
-
},
|
|
3667
|
-
(repos) => `kind=${repos.kind} repos=${repos.repos.length}`
|
|
3668
|
-
);
|
|
3669
|
-
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3670
|
-
await runPrepStep(
|
|
3671
|
-
"\u5207\u6362\u57FA\u7EBF\u5E76\u62C9\u53D6\u6700\u65B0",
|
|
3672
|
-
async () => checkoutBaselineBranches(msg2.baseBranch, { cwd: workdir }),
|
|
3673
|
-
(result) => `baseline=${result.baseline} kind=${result.kind} repos=${result.repos.length}`
|
|
3674
|
-
);
|
|
3675
|
-
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3676
|
-
await runPrepStep(
|
|
3677
|
-
"\u540C\u6B65\u9879\u76EE\u6587\u6863",
|
|
3678
|
-
async () => {
|
|
3679
|
-
const docs = await syncProjectDocumentsPull(workdir, void 0, {
|
|
3680
|
-
projectId
|
|
3681
|
-
});
|
|
3682
|
-
return docs;
|
|
3683
|
-
},
|
|
3684
|
-
(result) => {
|
|
3685
|
-
if (!result.synced) {
|
|
3686
|
-
return result.projectId ? "\u65E0\u9700\u540C\u6B65\u6216\u8DF3\u8FC7" : "\u8DF3\u8FC7\uFF1A\u65E0 projectId";
|
|
3687
|
-
}
|
|
3688
|
-
return `downloaded=${result.downloaded} deleted=${result.deleted}`;
|
|
2486
|
+
const { agent, resumed } = await obtainAgent({
|
|
2487
|
+
cfg: cfg2,
|
|
2488
|
+
apiKey,
|
|
2489
|
+
model: ctx.model,
|
|
2490
|
+
cwd: workdir,
|
|
2491
|
+
user: ctx.user,
|
|
2492
|
+
mode: ctx.mode,
|
|
2493
|
+
resumeAgentId: ctx.resumeAgentId,
|
|
2494
|
+
customTools,
|
|
2495
|
+
enableSandbox,
|
|
2496
|
+
onInvalidatePersistedAgentId: options.onInvalidatePersistedAgentId
|
|
2497
|
+
});
|
|
2498
|
+
const invalidateAgentMapping = () => {
|
|
2499
|
+
options.onInvalidatePersistedAgentId?.();
|
|
2500
|
+
};
|
|
2501
|
+
const eventSession = options.eventSession ?? new EventSession(prompt);
|
|
2502
|
+
const syncRemoteLog = options.createRemoteLogSync ? options.createRemoteLogSync(agent.agentId) : noopRemoteLogSync;
|
|
2503
|
+
let activeRun;
|
|
2504
|
+
const abortRun = () => {
|
|
2505
|
+
if (!activeRun?.supports("cancel")) return;
|
|
2506
|
+
void activeRun.cancel().catch(() => void 0);
|
|
2507
|
+
};
|
|
2508
|
+
signal?.addEventListener("abort", abortRun, { once: true });
|
|
2509
|
+
try {
|
|
2510
|
+
const run = await agent.send(prompt, {
|
|
2511
|
+
...ctx.mode ? { mode: ctx.mode } : {},
|
|
2512
|
+
local: {
|
|
2513
|
+
...options.forceSend ? { force: true } : {},
|
|
2514
|
+
customTools
|
|
3689
2515
|
}
|
|
3690
|
-
);
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
cfg2,
|
|
3723
|
-
{
|
|
3724
|
-
taskId: `package:${packageId}`,
|
|
3725
|
-
messageId,
|
|
3726
|
-
agentId
|
|
3727
|
-
},
|
|
3728
|
-
(err) => {
|
|
3729
|
-
console.warn(
|
|
3730
|
-
"[apm] WebIDE \u6253\u5305\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
|
|
3731
|
-
err instanceof Error ? err.message : err
|
|
3732
|
-
);
|
|
3733
|
-
},
|
|
3734
|
-
{
|
|
3735
|
-
objectPrefixOverride: `events/webide-package/${packageId}/`
|
|
3736
|
-
}
|
|
3737
|
-
);
|
|
3738
|
-
return logSyncRef.current;
|
|
3739
|
-
},
|
|
3740
|
-
onRunStarted: async ({ agentId, runId }) => {
|
|
3741
|
-
eventSession.addCliStep(
|
|
3742
|
-
"\u542F\u52A8\u6253\u5305 Agent",
|
|
3743
|
-
`agentId=${agentId} runId=${runId}`,
|
|
3744
|
-
"ok"
|
|
2516
|
+
});
|
|
2517
|
+
activeRun = run;
|
|
2518
|
+
console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
|
|
2519
|
+
bindRunSteer(async (text) => {
|
|
2520
|
+
const steer = run.steer;
|
|
2521
|
+
if (!steer) {
|
|
2522
|
+
console.warn(`[apm] \u5F53\u524D run \u4E0D\u652F\u6301 steer runId=${run.id}`);
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
try {
|
|
2526
|
+
const outcome = await steer(text);
|
|
2527
|
+
console.log(`[apm] steer outcome=${outcome} runId=${run.id}`);
|
|
2528
|
+
} catch (err) {
|
|
2529
|
+
console.error(
|
|
2530
|
+
`[apm] steer \u5931\u8D25 runId=${run.id}:`,
|
|
2531
|
+
err instanceof Error ? err.message : err
|
|
2532
|
+
);
|
|
2533
|
+
}
|
|
2534
|
+
});
|
|
2535
|
+
await options.onRunStarted?.({ agentId: agent.agentId, runId: run.id });
|
|
2536
|
+
let lastRunErrorStatus;
|
|
2537
|
+
for await (const event of run.stream()) {
|
|
2538
|
+
if (signal?.aborted) {
|
|
2539
|
+
abortRun();
|
|
2540
|
+
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
2541
|
+
}
|
|
2542
|
+
if (event.type === "status" && event.status === "ERROR") {
|
|
2543
|
+
const message = event.message?.trim();
|
|
2544
|
+
if (message) {
|
|
2545
|
+
lastRunErrorStatus = message;
|
|
2546
|
+
console.error(
|
|
2547
|
+
`[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
|
|
3745
2548
|
);
|
|
3746
|
-
logSyncRef.current?.schedule(eventSession);
|
|
3747
|
-
await logSyncRef.current?.markRun(runId, "running");
|
|
3748
|
-
await logSyncRef.current?.flush(eventSession);
|
|
3749
2549
|
}
|
|
3750
2550
|
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
} : void 0;
|
|
3761
|
-
if (outcome.status === "error") {
|
|
3762
|
-
const detail = formatCursorRunFailure(outcome.runId, {
|
|
3763
|
-
resultText: outcome.result
|
|
2551
|
+
options.onStreamEvent?.(event);
|
|
2552
|
+
eventSession.addEvent(event);
|
|
2553
|
+
syncRemoteLog.schedule(eventSession);
|
|
2554
|
+
}
|
|
2555
|
+
await syncRemoteLog.flush(eventSession);
|
|
2556
|
+
const result = await run.wait();
|
|
2557
|
+
if (result.usage) {
|
|
2558
|
+
eventSession.addRunUsage(result.usage, {
|
|
2559
|
+
durationMs: result.durationMs
|
|
3764
2560
|
});
|
|
3765
|
-
await
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
detail,
|
|
3769
|
-
tokenUsage
|
|
2561
|
+
await syncRemoteLog.flush(eventSession);
|
|
2562
|
+
console.log(
|
|
2563
|
+
`[apm] Cursor usage total=${result.usage.totalTokens} in=${result.usage.inputTokens} out=${result.usage.outputTokens} cache=${result.usage.cacheReadTokens}/${result.usage.cacheWriteTokens}`
|
|
3770
2564
|
);
|
|
3771
|
-
if (!failedByTool) {
|
|
3772
|
-
await updatePackageStatus(cfg2, packageId, "FAILED", { error: detail });
|
|
3773
|
-
}
|
|
3774
|
-
return;
|
|
3775
2565
|
}
|
|
3776
|
-
if (
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
"\u5DF2\u53D6\u6D88",
|
|
3781
|
-
tokenUsage
|
|
3782
|
-
);
|
|
3783
|
-
await updatePackageStatus(cfg2, packageId, "CANCELLED", {
|
|
3784
|
-
error: "\u5DF2\u53D6\u6D88"
|
|
2566
|
+
if (result.status === "error") {
|
|
2567
|
+
const failureMessage = formatCursorRunFailure(result.id, {
|
|
2568
|
+
statusError: lastRunErrorStatus,
|
|
2569
|
+
resultText: result.result
|
|
3785
2570
|
});
|
|
3786
|
-
|
|
2571
|
+
console.error(`[apm] ${failureMessage}`);
|
|
2572
|
+
if (resumed) {
|
|
2573
|
+
invalidateAgentMapping();
|
|
2574
|
+
}
|
|
2575
|
+
throw new Error(failureMessage);
|
|
3787
2576
|
}
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
"finished",
|
|
3791
|
-
null,
|
|
3792
|
-
tokenUsage
|
|
3793
|
-
);
|
|
3794
|
-
if (failedByTool) {
|
|
3795
|
-
console.log(
|
|
3796
|
-
`[apm] webide-package Agent \u5DF2\u6807 FAILED packageId=${packageId}`
|
|
3797
|
-
);
|
|
3798
|
-
return;
|
|
2577
|
+
if (result.status === "cancelled") {
|
|
2578
|
+
throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
|
|
3799
2579
|
}
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
2580
|
+
console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
|
|
2581
|
+
const artifacts = await agent.listArtifacts().catch(() => []);
|
|
2582
|
+
const artifactDocuments = [];
|
|
2583
|
+
for (const artifact of artifacts) {
|
|
2584
|
+
try {
|
|
2585
|
+
const content = (await agent.downloadArtifact(artifact.path)).toString(
|
|
2586
|
+
"utf8"
|
|
2587
|
+
);
|
|
2588
|
+
artifactDocuments.push({ path: artifact.path, content });
|
|
2589
|
+
} catch (err) {
|
|
2590
|
+
console.warn(
|
|
2591
|
+
`[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
|
|
2592
|
+
err instanceof Error ? err.message : err
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
return {
|
|
2597
|
+
runId: result.id,
|
|
2598
|
+
agentId: agent.agentId,
|
|
2599
|
+
status: result.status,
|
|
2600
|
+
result: result.result,
|
|
2601
|
+
durationMs: result.durationMs,
|
|
2602
|
+
usage: result.usage,
|
|
2603
|
+
modelId: result.model?.id?.trim() || ctx.model?.trim() || void 0,
|
|
2604
|
+
assistantText: eventSession.getAssistantText(),
|
|
2605
|
+
createPlan: eventSession.getCreatePlanContent(),
|
|
2606
|
+
artifacts,
|
|
2607
|
+
artifactDocuments
|
|
2608
|
+
};
|
|
3812
2609
|
} catch (err) {
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
if (signal.aborted) {
|
|
3817
|
-
await updatePackageStatus(cfg2, packageId, "CANCELLED", {
|
|
3818
|
-
error: detail
|
|
3819
|
-
});
|
|
3820
|
-
} else {
|
|
3821
|
-
await updatePackageStatus(cfg2, packageId, "FAILED", { error: detail });
|
|
2610
|
+
if (err instanceof CursorAgentError) {
|
|
2611
|
+
if (resumed) {
|
|
2612
|
+
invalidateAgentMapping();
|
|
3822
2613
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
"[apm] \u5199\u5165\u6253\u5305 FAILED \u5931\u8D25:",
|
|
3826
|
-
statusErr instanceof Error ? statusErr.message : statusErr
|
|
2614
|
+
throw new Error(
|
|
2615
|
+
`Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
|
|
3827
2616
|
);
|
|
3828
2617
|
}
|
|
2618
|
+
throw err;
|
|
2619
|
+
} finally {
|
|
2620
|
+
clearRunSteer();
|
|
2621
|
+
signal?.removeEventListener("abort", abortRun);
|
|
2622
|
+
await agent[Symbol.asyncDispose]();
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
// src/commands/connect/webide-agent-registry.ts
|
|
2627
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2628
|
+
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
2629
|
+
function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
|
|
2630
|
+
return resolve6(workdir, ".apm", "webide", taskId, fileName);
|
|
2631
|
+
}
|
|
2632
|
+
var DESIGN_AGENT_FILE = "design-agent.json";
|
|
2633
|
+
function readRegistry(path) {
|
|
2634
|
+
if (!existsSync5(path)) {
|
|
2635
|
+
return {};
|
|
2636
|
+
}
|
|
2637
|
+
try {
|
|
2638
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
2639
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2640
|
+
const raw = parsed;
|
|
2641
|
+
const state = {};
|
|
2642
|
+
if (typeof raw.agentId === "string" && raw.agentId.trim()) {
|
|
2643
|
+
state.agentId = raw.agentId.trim();
|
|
2644
|
+
}
|
|
2645
|
+
if (typeof raw.taskId === "string" && raw.taskId.trim()) {
|
|
2646
|
+
state.taskId = raw.taskId.trim();
|
|
2647
|
+
}
|
|
2648
|
+
if (typeof raw.messageId === "string" && raw.messageId.trim()) {
|
|
2649
|
+
state.messageId = raw.messageId.trim();
|
|
2650
|
+
}
|
|
2651
|
+
if (typeof raw.action === "string" && raw.action.trim()) {
|
|
2652
|
+
state.action = raw.action.trim();
|
|
2653
|
+
}
|
|
2654
|
+
if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
|
|
2655
|
+
state.status = raw.status;
|
|
2656
|
+
}
|
|
2657
|
+
if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
|
|
2658
|
+
state.updatedAt = raw.updatedAt.trim();
|
|
2659
|
+
}
|
|
2660
|
+
return state;
|
|
2661
|
+
}
|
|
2662
|
+
} catch {
|
|
2663
|
+
}
|
|
2664
|
+
return {};
|
|
2665
|
+
}
|
|
2666
|
+
function writeRegistry(path, registry) {
|
|
2667
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
2668
|
+
writeFileSync3(path, `${JSON.stringify(registry, null, 2)}
|
|
2669
|
+
`, "utf8");
|
|
2670
|
+
}
|
|
2671
|
+
function syncWebIdeTaskState(workdir, taskId, patch) {
|
|
2672
|
+
const trimmedTaskId = taskId.trim();
|
|
2673
|
+
if (!trimmedTaskId) return;
|
|
2674
|
+
const path = registryPath(workdir, trimmedTaskId);
|
|
2675
|
+
const current = readRegistry(path);
|
|
2676
|
+
const next = {
|
|
2677
|
+
...current,
|
|
2678
|
+
taskId: trimmedTaskId,
|
|
2679
|
+
updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2680
|
+
};
|
|
2681
|
+
if (patch.agentId !== void 0) {
|
|
2682
|
+
const agentId = patch.agentId.trim();
|
|
2683
|
+
if (agentId) next.agentId = agentId;
|
|
2684
|
+
else delete next.agentId;
|
|
2685
|
+
}
|
|
2686
|
+
if (patch.messageId !== void 0) {
|
|
2687
|
+
const messageId = patch.messageId.trim();
|
|
2688
|
+
if (messageId) next.messageId = messageId;
|
|
2689
|
+
else delete next.messageId;
|
|
2690
|
+
}
|
|
2691
|
+
if (patch.action !== void 0) {
|
|
2692
|
+
const action = patch.action.trim();
|
|
2693
|
+
if (action) next.action = action;
|
|
2694
|
+
else delete next.action;
|
|
2695
|
+
}
|
|
2696
|
+
if (patch.status !== void 0) {
|
|
2697
|
+
next.status = patch.status;
|
|
3829
2698
|
}
|
|
2699
|
+
writeRegistry(path, next);
|
|
2700
|
+
}
|
|
2701
|
+
function loadWebIdeAgentId(workdir, taskId) {
|
|
2702
|
+
return readRegistry(registryPath(workdir, taskId)).agentId;
|
|
2703
|
+
}
|
|
2704
|
+
function saveWebIdeAgentId(workdir, taskId, agentId) {
|
|
2705
|
+
syncWebIdeTaskState(workdir, taskId, { agentId });
|
|
2706
|
+
}
|
|
2707
|
+
function clearWebIdeAgentId(workdir, taskId) {
|
|
2708
|
+
const path = registryPath(workdir, taskId);
|
|
2709
|
+
if (!existsSync5(path)) return;
|
|
2710
|
+
syncWebIdeTaskState(workdir, taskId, { agentId: "" });
|
|
2711
|
+
}
|
|
2712
|
+
function loadWebIdeDesignAgentId(workdir, taskId) {
|
|
2713
|
+
return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
|
|
2714
|
+
}
|
|
2715
|
+
function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
|
|
2716
|
+
const trimmedTaskId = taskId.trim();
|
|
2717
|
+
if (!trimmedTaskId) return;
|
|
2718
|
+
const path = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
|
|
2719
|
+
const current = readRegistry(path);
|
|
2720
|
+
writeRegistry(path, {
|
|
2721
|
+
...current,
|
|
2722
|
+
taskId: trimmedTaskId,
|
|
2723
|
+
agentId: agentId.trim() || void 0,
|
|
2724
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
function clearWebIdeDesignAgentId(workdir, taskId) {
|
|
2728
|
+
const path = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
|
|
2729
|
+
if (!existsSync5(path)) return;
|
|
2730
|
+
const current = readRegistry(path);
|
|
2731
|
+
const next = { ...current };
|
|
2732
|
+
delete next.agentId;
|
|
2733
|
+
writeRegistry(path, {
|
|
2734
|
+
...next,
|
|
2735
|
+
taskId: taskId.trim(),
|
|
2736
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2737
|
+
});
|
|
3830
2738
|
}
|
|
3831
2739
|
|
|
3832
|
-
// src/commands/connect/
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
2740
|
+
// src/commands/connect/webide-ask-question.ts
|
|
2741
|
+
function asString4(value) {
|
|
2742
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2743
|
+
}
|
|
2744
|
+
function parseQuestions(args) {
|
|
2745
|
+
const title = asString4(args.title) || void 0;
|
|
2746
|
+
const raw = args.questions;
|
|
2747
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
2748
|
+
throw new Error("AskQuestion \u7F3A\u5C11 questions");
|
|
2749
|
+
}
|
|
2750
|
+
const questions = [];
|
|
2751
|
+
for (const item of raw) {
|
|
2752
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
2753
|
+
const row = item;
|
|
2754
|
+
const id = asString4(row.id);
|
|
2755
|
+
const prompt = asString4(row.prompt);
|
|
2756
|
+
const optionsRaw = row.options;
|
|
2757
|
+
if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
|
|
2758
|
+
const options = [];
|
|
2759
|
+
for (const opt of optionsRaw) {
|
|
2760
|
+
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
|
|
2761
|
+
const o = opt;
|
|
2762
|
+
const oid = asString4(o.id);
|
|
2763
|
+
const label = asString4(o.label);
|
|
2764
|
+
if (oid && label) options.push({ id: oid, label });
|
|
2765
|
+
}
|
|
2766
|
+
if (options.length < 2) {
|
|
2767
|
+
throw new Error(`AskQuestion \u95EE\u9898 ${id} \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`);
|
|
3843
2768
|
}
|
|
2769
|
+
questions.push({ id, prompt, options });
|
|
3844
2770
|
}
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
async function updateStatus(cfg2, messageId, status) {
|
|
3850
|
-
const api = createApmApiClient(cfg2);
|
|
3851
|
-
await api.cli.webideUpdateMessageStatus({ id: messageId, status });
|
|
2771
|
+
if (questions.length === 0) {
|
|
2772
|
+
throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
|
|
2773
|
+
}
|
|
2774
|
+
return { title, questions };
|
|
3852
2775
|
}
|
|
3853
|
-
async function
|
|
2776
|
+
async function fetchAssumptionAnswersAndContinueAssessment(options) {
|
|
2777
|
+
const { cfg: cfg2, taskId, questionIds, title } = options;
|
|
3854
2778
|
const api = createApmApiClient(cfg2);
|
|
3855
|
-
await api.cli.
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
2779
|
+
const list = await api.cli.webideListAssumptions({ taskId });
|
|
2780
|
+
const byId = new Map(list.assumptions.map((a) => [a.questionId, a]));
|
|
2781
|
+
const ids = questionIds && questionIds.length > 0 ? questionIds : list.assumptions.filter((a) => a.status === "RESOLVED").map((a) => a.questionId);
|
|
2782
|
+
if (ids.length === 0) {
|
|
2783
|
+
throw new Error("\u6CA1\u6709\u5DF2\u786E\u8BA4\u7684\u5047\u8BBE");
|
|
2784
|
+
}
|
|
2785
|
+
const answers = [];
|
|
2786
|
+
for (const id of ids) {
|
|
2787
|
+
const row = byId.get(id);
|
|
2788
|
+
if (!row || row.status !== "RESOLVED") {
|
|
2789
|
+
throw new Error(`\u5047\u8BBE\u5C1A\u672A\u5168\u90E8\u786E\u8BA4: ${id}`);
|
|
2790
|
+
}
|
|
2791
|
+
answers.push({
|
|
2792
|
+
questionId: id,
|
|
2793
|
+
prompt: row.prompt,
|
|
2794
|
+
selectedOptionId: row.selectedOptionId,
|
|
2795
|
+
customAnswer: row.customAnswer,
|
|
2796
|
+
resolution: row.resolution
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
3861
2799
|
console.log(
|
|
3862
|
-
`[apm]
|
|
2800
|
+
`[apm] \u5047\u8BBE\u7ED3\u679C\u5DF2\u62C9\u53D6 taskId=${taskId} count=${answers.length}`
|
|
3863
2801
|
);
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
(err) => {
|
|
3871
|
-
console.warn(
|
|
3872
|
-
"[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
|
|
3873
|
-
err instanceof Error ? err.message : err
|
|
3874
|
-
);
|
|
3875
|
-
}
|
|
3876
|
-
)
|
|
3877
|
-
};
|
|
3878
|
-
try {
|
|
3879
|
-
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3880
|
-
await ensureWorkspaceInitialized(workdir);
|
|
3881
|
-
await syncWebIdeAgentFiles(workdir, cfg2);
|
|
2802
|
+
return JSON.stringify({ title, answers }, null, 2);
|
|
2803
|
+
}
|
|
2804
|
+
function createWebIdeAskQuestionExecute(options) {
|
|
2805
|
+
const { cfg: cfg2, taskId, onAwaitingAssumptions } = options;
|
|
2806
|
+
return async (args) => {
|
|
2807
|
+
const parsed = parseQuestions(args);
|
|
3882
2808
|
const api = createApmApiClient(cfg2);
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
{
|
|
3886
|
-
messageId,
|
|
3887
|
-
prompt: msg2.content,
|
|
3888
|
-
model: msg2.model,
|
|
3889
|
-
apiKey: msg2.apiKey,
|
|
3890
|
-
workdir,
|
|
3891
|
-
user: msg2.user || "webide"
|
|
3892
|
-
},
|
|
3893
|
-
{
|
|
3894
|
-
signal,
|
|
3895
|
-
forceSend: true,
|
|
3896
|
-
eventSession,
|
|
3897
|
-
enableAppendMessage: false,
|
|
3898
|
-
enableAskQuestion: false,
|
|
3899
|
-
enableWebIdePlanTools: false,
|
|
3900
|
-
enablePtySessionMcp: true,
|
|
3901
|
-
enableSandbox: false,
|
|
3902
|
-
taskId,
|
|
3903
|
-
createRemoteLogSync: (agentId) => {
|
|
3904
|
-
logSyncRef.current = createThrottledWebIdeMessageLogSync(
|
|
3905
|
-
cfg2,
|
|
3906
|
-
{ taskId, messageId, agentId },
|
|
3907
|
-
(err) => {
|
|
3908
|
-
console.warn(
|
|
3909
|
-
"[apm] WebIDE \u542F\u52A8\u9879\u76EE\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
|
|
3910
|
-
err instanceof Error ? err.message : err
|
|
3911
|
-
);
|
|
3912
|
-
}
|
|
3913
|
-
);
|
|
3914
|
-
return logSyncRef.current;
|
|
3915
|
-
},
|
|
3916
|
-
onRunStarted: async ({ agentId, runId }) => {
|
|
3917
|
-
eventSession.addCliStep(
|
|
3918
|
-
"\u542F\u52A8\u72EC\u7ACB Agent",
|
|
3919
|
-
`agentId=${agentId} runId=${runId}`,
|
|
3920
|
-
"ok"
|
|
3921
|
-
);
|
|
3922
|
-
logSyncRef.current?.schedule(eventSession);
|
|
3923
|
-
await logSyncRef.current?.markRun(runId, "running");
|
|
3924
|
-
await logSyncRef.current?.flush(eventSession);
|
|
3925
|
-
}
|
|
3926
|
-
}
|
|
2809
|
+
console.log(
|
|
2810
|
+
`[apm] AskQuestion \u521B\u5EFA\u5F85\u786E\u8BA4\u9879 taskId=${taskId} count=${parsed.questions.length}`
|
|
3927
2811
|
);
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
cacheReadTokens: outcome.usage.cacheReadTokens,
|
|
3933
|
-
cacheWriteTokens: outcome.usage.cacheWriteTokens,
|
|
3934
|
-
totalTokens: outcome.usage.totalTokens,
|
|
3935
|
-
...outcome.usage.reasoningTokens != null ? { reasoningTokens: outcome.usage.reasoningTokens } : {}
|
|
3936
|
-
} : void 0;
|
|
3937
|
-
if (outcome.status === "error") {
|
|
3938
|
-
const detail = formatCursorRunFailure(outcome.runId, {
|
|
3939
|
-
resultText: outcome.result
|
|
3940
|
-
});
|
|
3941
|
-
await logSyncRef.current?.markRun(
|
|
3942
|
-
outcome.runId,
|
|
3943
|
-
"error",
|
|
3944
|
-
detail,
|
|
3945
|
-
tokenUsage
|
|
3946
|
-
);
|
|
3947
|
-
await setError(cfg2, messageId, detail);
|
|
3948
|
-
return;
|
|
3949
|
-
}
|
|
3950
|
-
if (outcome.status === "cancelled" || signal.aborted) {
|
|
3951
|
-
await logSyncRef.current?.markRun(
|
|
3952
|
-
outcome.runId,
|
|
3953
|
-
"cancelled",
|
|
3954
|
-
"\u5DF2\u53D6\u6D88",
|
|
3955
|
-
tokenUsage
|
|
3956
|
-
);
|
|
3957
|
-
await updateStatus(cfg2, messageId, "CANCELLED");
|
|
3958
|
-
return;
|
|
3959
|
-
}
|
|
3960
|
-
const fallback = resolveMessageReplyFallback({
|
|
3961
|
-
assistantText: outcome.assistantText,
|
|
3962
|
-
result: outcome.result
|
|
2812
|
+
await api.cli.webideReplaceAssumptions({
|
|
2813
|
+
taskId,
|
|
2814
|
+
title: parsed.title,
|
|
2815
|
+
questions: parsed.questions
|
|
3963
2816
|
});
|
|
3964
|
-
await api.cli.
|
|
3965
|
-
|
|
3966
|
-
|
|
2817
|
+
await api.cli.webideRunAction({
|
|
2818
|
+
taskId,
|
|
2819
|
+
action: "await-assumptions"
|
|
3967
2820
|
});
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
"
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
);
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
2821
|
+
onAwaitingAssumptions?.();
|
|
2822
|
+
return [
|
|
2823
|
+
"\u5047\u8BBE\u5DF2\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u6D41\u8FDB\u5165\u300C\u5F85\u786E\u8BA4\u5047\u8BBE\u300D\u3002",
|
|
2824
|
+
"\u672C\u8F6E\u5FC5\u987B\u7ACB\u5373\u7ED3\u675F\uFF1A\u4E0D\u8981\u7EE7\u7EED\u8BC4\u4F30\uFF0C\u4E0D\u8981\u8C03\u7528 RecommendPlan\uFF0C\u4E0D\u8981\u518D\u6B21\u8C03\u7528 AskQuestion\u3002",
|
|
2825
|
+
"\u7528\u6237\u786E\u8BA4\u540E\uFF0C\u4E0B\u4E00\u8F6E\u4F1A\u5E26\u4E0A\u7B54\u6848\u5E76\u7EE7\u7EED\u8BC4\u4F30\u3002"
|
|
2826
|
+
].join("");
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
// src/utils/minio.ts
|
|
2831
|
+
import * as Minio from "minio";
|
|
2832
|
+
var MinioClient = class {
|
|
2833
|
+
inner;
|
|
2834
|
+
constructor(opts) {
|
|
2835
|
+
const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
|
|
2836
|
+
this.inner = new Minio.Client({
|
|
2837
|
+
endPoint,
|
|
2838
|
+
port: opts.port,
|
|
2839
|
+
useSSL: opts.useSSL,
|
|
2840
|
+
accessKey: opts.accessKey,
|
|
2841
|
+
secretKey: opts.secretKey
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
async ensureBucket(bucket) {
|
|
2845
|
+
const exists = await this.inner.bucketExists(bucket);
|
|
2846
|
+
if (!exists) {
|
|
2847
|
+
await this.inner.makeBucket(bucket);
|
|
3988
2848
|
}
|
|
3989
2849
|
}
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
// src/commands/connect/webide-agent-registry.ts
|
|
3993
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3994
|
-
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
3995
|
-
function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
|
|
3996
|
-
return resolve6(workdir, ".apm", "webide", taskId, fileName);
|
|
3997
|
-
}
|
|
3998
|
-
var DESIGN_AGENT_FILE = "design-agent.json";
|
|
3999
|
-
function readRegistry(path3) {
|
|
4000
|
-
if (!existsSync7(path3)) {
|
|
4001
|
-
return {};
|
|
2850
|
+
async putObject(bucket, objectKey, body, meta) {
|
|
2851
|
+
await this.inner.putObject(bucket, objectKey, body, body.length, meta);
|
|
4002
2852
|
}
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
const raw = parsed;
|
|
4007
|
-
const state = {};
|
|
4008
|
-
if (typeof raw.agentId === "string" && raw.agentId.trim()) {
|
|
4009
|
-
state.agentId = raw.agentId.trim();
|
|
4010
|
-
}
|
|
4011
|
-
if (typeof raw.taskId === "string" && raw.taskId.trim()) {
|
|
4012
|
-
state.taskId = raw.taskId.trim();
|
|
4013
|
-
}
|
|
4014
|
-
if (typeof raw.messageId === "string" && raw.messageId.trim()) {
|
|
4015
|
-
state.messageId = raw.messageId.trim();
|
|
4016
|
-
}
|
|
4017
|
-
if (typeof raw.action === "string" && raw.action.trim()) {
|
|
4018
|
-
state.action = raw.action.trim();
|
|
4019
|
-
}
|
|
4020
|
-
if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
|
|
4021
|
-
state.status = raw.status;
|
|
4022
|
-
}
|
|
4023
|
-
if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
|
|
4024
|
-
state.updatedAt = raw.updatedAt.trim();
|
|
4025
|
-
}
|
|
4026
|
-
return state;
|
|
4027
|
-
}
|
|
4028
|
-
} catch {
|
|
2853
|
+
/** 大文件:从本地路径流式上传,避免整包读入内存 */
|
|
2854
|
+
async fPutObject(bucket, objectKey, filePath, meta) {
|
|
2855
|
+
await this.inner.fPutObject(bucket, objectKey, filePath, meta);
|
|
4029
2856
|
}
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
if (patch.agentId !== void 0) {
|
|
4048
|
-
const agentId = patch.agentId.trim();
|
|
4049
|
-
if (agentId) next.agentId = agentId;
|
|
4050
|
-
else delete next.agentId;
|
|
2857
|
+
async listObjects(bucket, prefix) {
|
|
2858
|
+
const objects = [];
|
|
2859
|
+
const stream = this.inner.listObjectsV2(bucket, prefix, true);
|
|
2860
|
+
await new Promise((resolve7, reject) => {
|
|
2861
|
+
stream.on("data", (obj) => {
|
|
2862
|
+
if (!obj.name) return;
|
|
2863
|
+
objects.push({
|
|
2864
|
+
name: obj.name,
|
|
2865
|
+
size: typeof obj.size === "number" ? obj.size : 0,
|
|
2866
|
+
lastModified: obj.lastModified ?? null,
|
|
2867
|
+
...typeof obj.etag === "string" ? { etag: obj.etag } : {}
|
|
2868
|
+
});
|
|
2869
|
+
});
|
|
2870
|
+
stream.on("error", reject);
|
|
2871
|
+
stream.on("end", () => resolve7());
|
|
2872
|
+
});
|
|
2873
|
+
return objects;
|
|
4051
2874
|
}
|
|
4052
|
-
|
|
4053
|
-
const
|
|
4054
|
-
|
|
4055
|
-
|
|
2875
|
+
async statObject(bucket, objectKey) {
|
|
2876
|
+
const stat = await this.inner.statObject(bucket, objectKey);
|
|
2877
|
+
return {
|
|
2878
|
+
size: typeof stat.size === "number" ? stat.size : 0,
|
|
2879
|
+
metaData: stat.metaData
|
|
2880
|
+
};
|
|
4056
2881
|
}
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
if (action) next.action = action;
|
|
4060
|
-
else delete next.action;
|
|
2882
|
+
async removeObject(bucket, objectKey) {
|
|
2883
|
+
await this.inner.removeObject(bucket, objectKey);
|
|
4061
2884
|
}
|
|
4062
|
-
|
|
4063
|
-
|
|
2885
|
+
async getObjectBuffer(bucket, objectKey) {
|
|
2886
|
+
const stream = await this.inner.getObject(bucket, objectKey);
|
|
2887
|
+
const chunks = [];
|
|
2888
|
+
for await (const chunk of stream) {
|
|
2889
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2890
|
+
}
|
|
2891
|
+
return Buffer.concat(chunks);
|
|
4064
2892
|
}
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
syncWebIdeTaskState(workdir, taskId, { agentId });
|
|
2893
|
+
};
|
|
2894
|
+
|
|
2895
|
+
// src/commands/connect/apm-log-minio.ts
|
|
2896
|
+
async function fetchApmLogStorage(cfg2) {
|
|
2897
|
+
const api = createApmApiClient(cfg2);
|
|
2898
|
+
return api.cli.getApmLogStorage(void 0);
|
|
4072
2899
|
}
|
|
4073
|
-
function
|
|
4074
|
-
const
|
|
4075
|
-
|
|
4076
|
-
|
|
2900
|
+
async function createApmLogMinioClient(cfg2) {
|
|
2901
|
+
const storage = await fetchApmLogStorage(cfg2);
|
|
2902
|
+
return {
|
|
2903
|
+
client: new MinioClient({
|
|
2904
|
+
endPoint: storage.endpoint,
|
|
2905
|
+
port: storage.port,
|
|
2906
|
+
useSSL: storage.useSsl,
|
|
2907
|
+
accessKey: storage.accessKey,
|
|
2908
|
+
secretKey: storage.secretKey
|
|
2909
|
+
}),
|
|
2910
|
+
bucket: storage.bucket,
|
|
2911
|
+
...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
|
|
2912
|
+
};
|
|
4077
2913
|
}
|
|
4078
|
-
|
|
4079
|
-
|
|
2914
|
+
|
|
2915
|
+
// src/commands/connect/webide-message-log.ts
|
|
2916
|
+
var SYNC_INTERVAL_MS = 2e3;
|
|
2917
|
+
function webIdeEventsObjectPrefix(taskId, messageId) {
|
|
2918
|
+
return `events/webide/${taskId}/${messageId}/`;
|
|
4080
2919
|
}
|
|
4081
|
-
function
|
|
4082
|
-
const
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
2920
|
+
async function putDirtyEvents(minio, bucket, prefix, events) {
|
|
2921
|
+
for (const event of events) {
|
|
2922
|
+
const key = `${prefix}${event.index}.json`;
|
|
2923
|
+
const body = Buffer.from(
|
|
2924
|
+
JSON.stringify({
|
|
2925
|
+
index: event.index,
|
|
2926
|
+
type: event.type,
|
|
2927
|
+
data: event.data
|
|
2928
|
+
}),
|
|
2929
|
+
"utf8"
|
|
2930
|
+
);
|
|
2931
|
+
await minio.putObject(bucket, key, body, {
|
|
2932
|
+
"Content-Type": "application/json"
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
4092
2935
|
}
|
|
4093
|
-
function
|
|
4094
|
-
const
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
...next,
|
|
4101
|
-
taskId: taskId.trim(),
|
|
4102
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2936
|
+
async function upsertLogHeader(cfg2, ctx, patch, objectPrefix) {
|
|
2937
|
+
const api = createApmApiClient(cfg2);
|
|
2938
|
+
await api.cli.webideUpsertMessageLog({
|
|
2939
|
+
messageId: ctx.messageId,
|
|
2940
|
+
agentId: ctx.agentId,
|
|
2941
|
+
objectPrefix,
|
|
2942
|
+
...patch
|
|
4103
2943
|
});
|
|
4104
2944
|
}
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
const
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
const
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
2945
|
+
function createThrottledWebIdeMessageLogSync(cfg2, ctx, onError, options) {
|
|
2946
|
+
let lastRunAt = 0;
|
|
2947
|
+
let timer;
|
|
2948
|
+
let latestSession;
|
|
2949
|
+
let syncChain = Promise.resolve();
|
|
2950
|
+
let minio;
|
|
2951
|
+
let bucket = "";
|
|
2952
|
+
let firstLogNotified = false;
|
|
2953
|
+
const resolvePrefix = () => options?.objectPrefixOverride?.trim() || webIdeEventsObjectPrefix(ctx.taskId, ctx.messageId);
|
|
2954
|
+
const ensureMinio = async () => {
|
|
2955
|
+
if (minio) return;
|
|
2956
|
+
const created = await createApmLogMinioClient(cfg2);
|
|
2957
|
+
minio = created.client;
|
|
2958
|
+
bucket = created.bucket;
|
|
2959
|
+
await minio.ensureBucket(bucket);
|
|
2960
|
+
};
|
|
2961
|
+
const notifyFirstLog = async () => {
|
|
2962
|
+
if (firstLogNotified) return;
|
|
2963
|
+
firstLogNotified = true;
|
|
2964
|
+
try {
|
|
2965
|
+
await options?.onFirstLogSynced?.();
|
|
2966
|
+
} catch (err) {
|
|
2967
|
+
firstLogNotified = false;
|
|
2968
|
+
onError(err);
|
|
2969
|
+
}
|
|
2970
|
+
};
|
|
2971
|
+
const syncDirtyEventsOnce = async (session) => {
|
|
2972
|
+
const events = session.getDirtyEvents();
|
|
2973
|
+
if (events.length === 0) return;
|
|
2974
|
+
lastRunAt = Date.now();
|
|
2975
|
+
try {
|
|
2976
|
+
await ensureMinio();
|
|
2977
|
+
const prefix = resolvePrefix();
|
|
2978
|
+
await putDirtyEvents(minio, bucket, prefix, events);
|
|
2979
|
+
await upsertLogHeader(
|
|
2980
|
+
cfg2,
|
|
2981
|
+
ctx,
|
|
2982
|
+
{
|
|
2983
|
+
eventCount: session.getEventCount()
|
|
2984
|
+
},
|
|
2985
|
+
prefix
|
|
2986
|
+
);
|
|
2987
|
+
session.clearDirtyIfUnchanged(events);
|
|
2988
|
+
await notifyFirstLog();
|
|
2989
|
+
} catch (err) {
|
|
2990
|
+
onError(err);
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2993
|
+
const drainDirtyEvents = async (session) => {
|
|
2994
|
+
while (session.getDirtyEvents().length > 0) {
|
|
2995
|
+
await syncDirtyEventsOnce(session);
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
const enqueueSync = (session) => {
|
|
2999
|
+
syncChain = syncChain.then(() => drainDirtyEvents(session));
|
|
3000
|
+
};
|
|
3001
|
+
return {
|
|
3002
|
+
async markRun(runId, runStatus, lastError, tokenUsage) {
|
|
3003
|
+
try {
|
|
3004
|
+
await upsertLogHeader(
|
|
3005
|
+
cfg2,
|
|
3006
|
+
ctx,
|
|
3007
|
+
{
|
|
3008
|
+
runId,
|
|
3009
|
+
runStatus,
|
|
3010
|
+
lastError: lastError ?? null,
|
|
3011
|
+
...tokenUsage ? { tokenUsage } : {}
|
|
3012
|
+
},
|
|
3013
|
+
resolvePrefix()
|
|
3014
|
+
);
|
|
3015
|
+
} catch (err) {
|
|
3016
|
+
onError(err);
|
|
3017
|
+
}
|
|
3018
|
+
},
|
|
3019
|
+
schedule(session) {
|
|
3020
|
+
latestSession = session;
|
|
3021
|
+
const now = Date.now();
|
|
3022
|
+
const elapsed = now - lastRunAt;
|
|
3023
|
+
if (elapsed >= SYNC_INTERVAL_MS) {
|
|
3024
|
+
if (timer) {
|
|
3025
|
+
clearTimeout(timer);
|
|
3026
|
+
timer = void 0;
|
|
3027
|
+
}
|
|
3028
|
+
enqueueSync(session);
|
|
3029
|
+
return;
|
|
4121
3030
|
}
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
runIds
|
|
4136
|
-
});
|
|
4137
|
-
}
|
|
4138
|
-
if (runIds.length > 0) {
|
|
4139
|
-
try {
|
|
4140
|
-
const api = createApmApiClient(cfg2);
|
|
4141
|
-
await api.cli.cursorStoreDeleteRunEvents({ runIds });
|
|
4142
|
-
} catch (err) {
|
|
4143
|
-
console.warn(
|
|
4144
|
-
`[apm] \u6E05\u7406\u5E73\u53F0 runEvents \u5931\u8D25 agentId=${trimmedAgentId}:`,
|
|
4145
|
-
err instanceof Error ? err.message : err
|
|
4146
|
-
);
|
|
3031
|
+
if (timer) return;
|
|
3032
|
+
timer = setTimeout(() => {
|
|
3033
|
+
timer = void 0;
|
|
3034
|
+
if (latestSession) enqueueSync(latestSession);
|
|
3035
|
+
}, SYNC_INTERVAL_MS - elapsed);
|
|
3036
|
+
},
|
|
3037
|
+
async flush(session) {
|
|
3038
|
+
if (timer) {
|
|
3039
|
+
clearTimeout(timer);
|
|
3040
|
+
timer = void 0;
|
|
3041
|
+
}
|
|
3042
|
+
await syncChain;
|
|
3043
|
+
await drainDirtyEvents(session);
|
|
4147
3044
|
}
|
|
4148
|
-
}
|
|
4149
|
-
return true;
|
|
3045
|
+
};
|
|
4150
3046
|
}
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
trimmedWorkdir
|
|
4164
|
-
);
|
|
4165
|
-
if (purged) {
|
|
4166
|
-
console.log(
|
|
4167
|
-
`[apm] \u5DF2\u6E05\u7406 Cursor agent store agentId=${agentId} taskId=${trimmedTaskId}`
|
|
4168
|
-
);
|
|
4169
|
-
}
|
|
4170
|
-
} catch (err) {
|
|
4171
|
-
console.warn(
|
|
4172
|
-
`[apm] \u6E05\u7406 Cursor agent store \u5931\u8D25 agentId=${agentId}:`,
|
|
4173
|
-
err instanceof Error ? err.message : err
|
|
4174
|
-
);
|
|
3047
|
+
|
|
3048
|
+
// src/commands/connect/ensure-message-reply.ts
|
|
3049
|
+
var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
|
|
3050
|
+
function resolveMessageReplyFallback(fallback) {
|
|
3051
|
+
for (const candidate of [
|
|
3052
|
+
fallback.assistantText,
|
|
3053
|
+
fallback.result,
|
|
3054
|
+
fallback.createPlan
|
|
3055
|
+
]) {
|
|
3056
|
+
const trimmed = candidate?.trim();
|
|
3057
|
+
if (trimmed) {
|
|
3058
|
+
return trimmed;
|
|
4175
3059
|
}
|
|
4176
|
-
} else {
|
|
4177
|
-
console.log(
|
|
4178
|
-
`[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
|
|
4179
|
-
);
|
|
4180
|
-
}
|
|
4181
|
-
const dir = resolve7(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
|
|
4182
|
-
if (existsSync8(dir)) {
|
|
4183
|
-
rmSync2(dir, { recursive: true, force: true });
|
|
4184
|
-
console.log(`[apm] \u5DF2\u6E05\u7406 WebIDE \u7F13\u5B58 ${dir}`);
|
|
4185
|
-
} else {
|
|
4186
|
-
console.log(`[apm] WebIDE \u7F13\u5B58\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7 ${dir}`);
|
|
4187
3060
|
}
|
|
3061
|
+
return DEFAULT_REPLY;
|
|
4188
3062
|
}
|
|
4189
3063
|
|
|
4190
|
-
// src/commands/
|
|
4191
|
-
|
|
4192
|
-
|
|
3064
|
+
// src/commands/sync-webide-agent.ts
|
|
3065
|
+
import {
|
|
3066
|
+
existsSync as existsSync6,
|
|
3067
|
+
mkdirSync as mkdirSync5,
|
|
3068
|
+
readdirSync as readdirSync3,
|
|
3069
|
+
readFileSync as readFileSync6,
|
|
3070
|
+
unlinkSync,
|
|
3071
|
+
writeFileSync as writeFileSync4
|
|
3072
|
+
} from "fs";
|
|
3073
|
+
import { join as join6 } from "path";
|
|
3074
|
+
var REVISION_FILE = ".webide-agent.json";
|
|
3075
|
+
var RULE_KEY = /^[a-z0-9_]+$/;
|
|
3076
|
+
function withTrailingNewline(text) {
|
|
3077
|
+
return text.endsWith("\n") ? text : `${text}
|
|
3078
|
+
`;
|
|
4193
3079
|
}
|
|
4194
|
-
function
|
|
4195
|
-
const
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
for (const item of raw) {
|
|
4202
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
4203
|
-
const row = item;
|
|
4204
|
-
const id = asString5(row.id);
|
|
4205
|
-
const prompt = asString5(row.prompt);
|
|
4206
|
-
const optionsRaw = row.options;
|
|
4207
|
-
if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
|
|
4208
|
-
const options = [];
|
|
4209
|
-
for (const opt of optionsRaw) {
|
|
4210
|
-
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
|
|
4211
|
-
const o = opt;
|
|
4212
|
-
const oid = asString5(o.id);
|
|
4213
|
-
const label = asString5(o.label);
|
|
4214
|
-
if (oid && label) options.push({ id: oid, label });
|
|
4215
|
-
}
|
|
4216
|
-
if (options.length < 2) {
|
|
4217
|
-
throw new Error(`AskQuestion \u95EE\u9898 ${id} \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`);
|
|
3080
|
+
function readRevision(apmDir) {
|
|
3081
|
+
const path = toFsPath(join6(apmDir, REVISION_FILE));
|
|
3082
|
+
if (!existsSync6(path)) return null;
|
|
3083
|
+
try {
|
|
3084
|
+
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
3085
|
+
if (parsed?.version === 1 && parsed.revision?.trim()) {
|
|
3086
|
+
return parsed.revision.trim();
|
|
4218
3087
|
}
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
if (questions.length === 0) {
|
|
4222
|
-
throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
|
|
3088
|
+
} catch {
|
|
3089
|
+
return null;
|
|
4223
3090
|
}
|
|
4224
|
-
return
|
|
3091
|
+
return null;
|
|
4225
3092
|
}
|
|
4226
|
-
|
|
4227
|
-
|
|
3093
|
+
function writeRevision(apmDir, revision) {
|
|
3094
|
+
writeFileSync4(
|
|
3095
|
+
toFsPath(join6(apmDir, REVISION_FILE)),
|
|
3096
|
+
`${JSON.stringify({ version: 1, revision }, null, 2)}
|
|
3097
|
+
`,
|
|
3098
|
+
"utf8"
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
3101
|
+
async function syncWebIdeAgentFiles(workdir, cfg2) {
|
|
4228
3102
|
const api = createApmApiClient(cfg2);
|
|
4229
|
-
const
|
|
4230
|
-
const
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
throw new Error("\u6CA1\u6709\u5DF2\u786E\u8BA4\u7684\u5047\u8BBE");
|
|
3103
|
+
const bundle = await api.cli.webideAgentConfig();
|
|
3104
|
+
const revision = bundle.revision?.trim();
|
|
3105
|
+
if (!revision) {
|
|
3106
|
+
throw new Error("[apm] \u5E73\u53F0\u672A\u8FD4\u56DE WebIDE \u89C4\u5219\u7248\u672C");
|
|
4234
3107
|
}
|
|
4235
|
-
const
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
throw new Error(`\u5047\u8BBE\u5C1A\u672A\u5168\u90E8\u786E\u8BA4: ${id}`);
|
|
4240
|
-
}
|
|
4241
|
-
answers.push({
|
|
4242
|
-
questionId: id,
|
|
4243
|
-
prompt: row.prompt,
|
|
4244
|
-
selectedOptionId: row.selectedOptionId,
|
|
4245
|
-
customAnswer: row.customAnswer,
|
|
4246
|
-
resolution: row.resolution
|
|
4247
|
-
});
|
|
3108
|
+
const apmDir = workspaceApmDir(workdir);
|
|
3109
|
+
mkdirSync5(toFsPath(apmDir), { recursive: true });
|
|
3110
|
+
if (readRevision(apmDir) === revision) {
|
|
3111
|
+
return { revision, wrote: false, ruleCount: bundle.rules?.length ?? 0 };
|
|
4248
3112
|
}
|
|
4249
|
-
|
|
4250
|
-
|
|
3113
|
+
const rulesDir = join6(apmDir, "rules");
|
|
3114
|
+
mkdirSync5(toFsPath(rulesDir), { recursive: true });
|
|
3115
|
+
writeFileSync4(
|
|
3116
|
+
toFsPath(join6(apmDir, "AGENTS.md")),
|
|
3117
|
+
withTrailingNewline(bundle.guide ?? ""),
|
|
3118
|
+
"utf8"
|
|
4251
3119
|
);
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
3120
|
+
const keep = /* @__PURE__ */ new Set();
|
|
3121
|
+
for (const rule of bundle.rules ?? []) {
|
|
3122
|
+
const key = rule.key?.trim() ?? "";
|
|
3123
|
+
if (!RULE_KEY.test(key)) {
|
|
3124
|
+
throw new Error(`[apm] \u975E\u6CD5\u89C4\u5219\u540D: ${key || "(\u7A7A)"}`);
|
|
3125
|
+
}
|
|
3126
|
+
keep.add(`${key}.md`);
|
|
3127
|
+
writeFileSync4(
|
|
3128
|
+
toFsPath(join6(rulesDir, `${key}.md`)),
|
|
3129
|
+
withTrailingNewline(rule.body ?? ""),
|
|
3130
|
+
"utf8"
|
|
4261
3131
|
);
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
"\u5047\u8BBE\u5DF2\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u6D41\u8FDB\u5165\u300C\u5F85\u786E\u8BA4\u5047\u8BBE\u300D\u3002",
|
|
4274
|
-
"\u672C\u8F6E\u5FC5\u987B\u7ACB\u5373\u7ED3\u675F\uFF1A\u4E0D\u8981\u7EE7\u7EED\u8BC4\u4F30\uFF0C\u4E0D\u8981\u8C03\u7528 RecommendPlan\uFF0C\u4E0D\u8981\u518D\u6B21\u8C03\u7528 AskQuestion\u3002",
|
|
4275
|
-
"\u7528\u6237\u786E\u8BA4\u540E\uFF0C\u4E0B\u4E00\u8F6E\u4F1A\u5E26\u4E0A\u7B54\u6848\u5E76\u7EE7\u7EED\u8BC4\u4F30\u3002"
|
|
4276
|
-
].join("");
|
|
3132
|
+
}
|
|
3133
|
+
for (const name of readdirSync3(toFsPath(rulesDir))) {
|
|
3134
|
+
if (!name.endsWith(".md")) continue;
|
|
3135
|
+
if (keep.has(name)) continue;
|
|
3136
|
+
unlinkSync(toFsPath(join6(rulesDir, name)));
|
|
3137
|
+
}
|
|
3138
|
+
writeRevision(apmDir, revision);
|
|
3139
|
+
return {
|
|
3140
|
+
revision,
|
|
3141
|
+
wrote: true,
|
|
3142
|
+
ruleCount: bundle.rules?.length ?? 0
|
|
4277
3143
|
};
|
|
4278
3144
|
}
|
|
4279
3145
|
|
|
3146
|
+
// src/commands/init.ts
|
|
3147
|
+
async function ensureWorkspaceInitialized(workdir) {
|
|
3148
|
+
if (isWorkspaceApmInitialized(workdir)) {
|
|
3149
|
+
return { didInit: false };
|
|
3150
|
+
}
|
|
3151
|
+
console.log(
|
|
3152
|
+
`[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u6309 WebIDE \u81EA\u52A8\u521D\u59CB\u5316\u2026`
|
|
3153
|
+
);
|
|
3154
|
+
await ensureWorkspaceApmDirForInit(workdir);
|
|
3155
|
+
console.log(`[apm] \u5DF2\u521B\u5EFA\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF1A${workspaceApmDir(workdir)}`);
|
|
3156
|
+
return { didInit: true };
|
|
3157
|
+
}
|
|
3158
|
+
|
|
4280
3159
|
// src/commands/connect/pre-step-cache.ts
|
|
4281
3160
|
function taskWorkdirKey(taskId, workdir) {
|
|
4282
3161
|
return `${taskId}\0${workdir}`;
|
|
@@ -4501,9 +3380,9 @@ async function ensureWebIdePullRequests(cfg2, taskId, workdir) {
|
|
|
4501
3380
|
}
|
|
4502
3381
|
|
|
4503
3382
|
// src/commands/sync-webide-attachments.ts
|
|
4504
|
-
import { existsSync as
|
|
4505
|
-
import { join as
|
|
4506
|
-
var
|
|
3383
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
3384
|
+
import { join as join7 } from "path";
|
|
3385
|
+
var MANIFEST_FILE = ".sync-manifest.json";
|
|
4507
3386
|
async function downloadAttachment(cfg2, attachmentId) {
|
|
4508
3387
|
const base = cfg2.baseUrl.trim().replace(/\/+$/, "");
|
|
4509
3388
|
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
|
|
@@ -4518,13 +3397,13 @@ async function downloadAttachment(cfg2, attachmentId) {
|
|
|
4518
3397
|
return Buffer.from(await res.arrayBuffer());
|
|
4519
3398
|
}
|
|
4520
3399
|
function loadManifest(dir) {
|
|
4521
|
-
const
|
|
4522
|
-
if (!
|
|
3400
|
+
const path = join7(dir, MANIFEST_FILE);
|
|
3401
|
+
if (!existsSync7(path)) {
|
|
4523
3402
|
return { version: 1, attachments: {} };
|
|
4524
3403
|
}
|
|
4525
3404
|
try {
|
|
4526
3405
|
const parsed = JSON.parse(
|
|
4527
|
-
readFileSync7(
|
|
3406
|
+
readFileSync7(path, "utf8")
|
|
4528
3407
|
);
|
|
4529
3408
|
if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
|
|
4530
3409
|
return parsed;
|
|
@@ -4534,15 +3413,15 @@ function loadManifest(dir) {
|
|
|
4534
3413
|
return { version: 1, attachments: {} };
|
|
4535
3414
|
}
|
|
4536
3415
|
function saveManifest(dir, manifest) {
|
|
4537
|
-
|
|
4538
|
-
|
|
3416
|
+
writeFileSync5(
|
|
3417
|
+
join7(dir, MANIFEST_FILE),
|
|
4539
3418
|
`${JSON.stringify(manifest, null, 2)}
|
|
4540
3419
|
`,
|
|
4541
3420
|
"utf8"
|
|
4542
3421
|
);
|
|
4543
3422
|
}
|
|
4544
3423
|
function isAttachmentUpToDate(entry, item, dest) {
|
|
4545
|
-
if (!entry || !
|
|
3424
|
+
if (!entry || !existsSync7(dest)) return false;
|
|
4546
3425
|
if (entry.name !== item.name) return false;
|
|
4547
3426
|
const createdAt = item.createdAt ?? "";
|
|
4548
3427
|
return entry.createdAt === createdAt;
|
|
@@ -4557,7 +3436,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
|
|
|
4557
3436
|
const nextManifest = { version: 1, attachments: {} };
|
|
4558
3437
|
const names = [];
|
|
4559
3438
|
for (const item of attachments) {
|
|
4560
|
-
const dest =
|
|
3439
|
+
const dest = join7(dir, item.name);
|
|
4561
3440
|
const entry = manifest.attachments[item.id];
|
|
4562
3441
|
const createdAt = item.createdAt ?? "";
|
|
4563
3442
|
names.push(item.name);
|
|
@@ -4567,7 +3446,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
|
|
|
4567
3446
|
continue;
|
|
4568
3447
|
}
|
|
4569
3448
|
const buffer = await downloadAttachment(cfg2, item.id);
|
|
4570
|
-
|
|
3449
|
+
writeFileSync5(dest, buffer);
|
|
4571
3450
|
nextManifest.attachments[item.id] = {
|
|
4572
3451
|
name: item.name,
|
|
4573
3452
|
createdAt
|
|
@@ -4588,9 +3467,9 @@ async function syncWebIdeAttachments(cfg2, taskId, workdir, attachments) {
|
|
|
4588
3467
|
}
|
|
4589
3468
|
|
|
4590
3469
|
// src/commands/sync-webide-design.ts
|
|
4591
|
-
import { createHash
|
|
4592
|
-
import { existsSync as
|
|
4593
|
-
import { join as
|
|
3470
|
+
import { createHash } from "crypto";
|
|
3471
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
3472
|
+
import { join as join8 } from "path";
|
|
4594
3473
|
|
|
4595
3474
|
// src/opc-shared-types.ts
|
|
4596
3475
|
function webIdeDesignObjectKey(taskId, fileName) {
|
|
@@ -4606,17 +3485,17 @@ function webIdeDesignArtifactContentType(name) {
|
|
|
4606
3485
|
}
|
|
4607
3486
|
|
|
4608
3487
|
// src/commands/sync-webide-design.ts
|
|
4609
|
-
var
|
|
3488
|
+
var MANIFEST_FILE2 = ".sync-manifest.json";
|
|
4610
3489
|
function hashBuffer(buf) {
|
|
4611
|
-
return
|
|
3490
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
4612
3491
|
}
|
|
4613
3492
|
function loadManifest2(dir) {
|
|
4614
|
-
const
|
|
4615
|
-
if (!
|
|
3493
|
+
const path = join8(dir, MANIFEST_FILE2);
|
|
3494
|
+
if (!existsSync8(path)) {
|
|
4616
3495
|
return { version: 1, files: {} };
|
|
4617
3496
|
}
|
|
4618
3497
|
try {
|
|
4619
|
-
const parsed = JSON.parse(readFileSync8(
|
|
3498
|
+
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
4620
3499
|
if (parsed?.version === 1 && parsed.files && typeof parsed.files === "object") {
|
|
4621
3500
|
return parsed;
|
|
4622
3501
|
}
|
|
@@ -4625,8 +3504,8 @@ function loadManifest2(dir) {
|
|
|
4625
3504
|
return { version: 1, files: {} };
|
|
4626
3505
|
}
|
|
4627
3506
|
function saveManifest2(dir, manifest) {
|
|
4628
|
-
|
|
4629
|
-
|
|
3507
|
+
writeFileSync6(
|
|
3508
|
+
join8(dir, MANIFEST_FILE2),
|
|
4630
3509
|
`${JSON.stringify(manifest, null, 2)}
|
|
4631
3510
|
`,
|
|
4632
3511
|
"utf8"
|
|
@@ -4654,13 +3533,13 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
|
|
|
4654
3533
|
for (const art of artifacts) {
|
|
4655
3534
|
const name = art.name?.trim();
|
|
4656
3535
|
if (!name || !isWebIdeDesignArtifactName(name)) continue;
|
|
4657
|
-
const localPath =
|
|
3536
|
+
const localPath = join8(dir, name);
|
|
4658
3537
|
const entry = manifest.files[name];
|
|
4659
|
-
if (entry?.contentHash === art.contentHash &&
|
|
3538
|
+
if (entry?.contentHash === art.contentHash && existsSync8(localPath)) {
|
|
4660
3539
|
continue;
|
|
4661
3540
|
}
|
|
4662
3541
|
const buf = await downloadDesignFile(cfg2, taskId, name);
|
|
4663
|
-
|
|
3542
|
+
writeFileSync6(localPath, buf);
|
|
4664
3543
|
manifest.files[name] = { contentHash: hashBuffer(buf) };
|
|
4665
3544
|
synced.push(name);
|
|
4666
3545
|
}
|
|
@@ -4669,12 +3548,12 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
|
|
|
4669
3548
|
}
|
|
4670
3549
|
function listLocalDesignArtifactFiles(taskId, workdir) {
|
|
4671
3550
|
const dir = webideDesignDir(taskId, workdir);
|
|
4672
|
-
if (!
|
|
3551
|
+
if (!existsSync8(dir)) return [];
|
|
4673
3552
|
const out = [];
|
|
4674
|
-
for (const name of
|
|
3553
|
+
for (const name of readdirSync4(dir)) {
|
|
4675
3554
|
if (!isWebIdeDesignArtifactName(name)) continue;
|
|
4676
|
-
const absPath =
|
|
4677
|
-
if (!
|
|
3555
|
+
const absPath = join8(dir, name);
|
|
3556
|
+
if (!statSync4(absPath).isFile()) continue;
|
|
4678
3557
|
const buf = readFileSync8(absPath);
|
|
4679
3558
|
if (buf.length === 0) continue;
|
|
4680
3559
|
out.push({ name, absPath, contentHash: hashBuffer(buf) });
|
|
@@ -4759,8 +3638,6 @@ function startActionForInbound(action) {
|
|
|
4759
3638
|
case "request-develop":
|
|
4760
3639
|
case "skip-plan":
|
|
4761
3640
|
return "start-develop";
|
|
4762
|
-
case "request-start-project":
|
|
4763
|
-
return "start-project";
|
|
4764
3641
|
case "request-execute-sql":
|
|
4765
3642
|
return "start-execute-sql";
|
|
4766
3643
|
case "request-bugfix":
|
|
@@ -4795,9 +3672,6 @@ function completeActionForInbound(action) {
|
|
|
4795
3672
|
case "skip-plan":
|
|
4796
3673
|
case "start-develop":
|
|
4797
3674
|
return "complete-develop";
|
|
4798
|
-
case "request-start-project":
|
|
4799
|
-
case "start-project":
|
|
4800
|
-
return "complete-project";
|
|
4801
3675
|
case "request-execute-sql":
|
|
4802
3676
|
case "start-execute-sql":
|
|
4803
3677
|
return "complete-execute-sql";
|
|
@@ -4869,9 +3743,6 @@ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
4869
3743
|
function shouldCommitAfterWebIdeMessage(action) {
|
|
4870
3744
|
return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
|
|
4871
3745
|
}
|
|
4872
|
-
function isStartLocalServicesAction(action) {
|
|
4873
|
-
return action === "enter-manual-test" || action === "skip-test" || action === "request-start-project";
|
|
4874
|
-
}
|
|
4875
3746
|
function isDesignAction(action) {
|
|
4876
3747
|
return action === "request-design" || action === "request-revise-design";
|
|
4877
3748
|
}
|
|
@@ -4882,11 +3753,11 @@ function syncLocalTaskStatus(workdir, taskId, msg2, status) {
|
|
|
4882
3753
|
status
|
|
4883
3754
|
});
|
|
4884
3755
|
}
|
|
4885
|
-
async function
|
|
3756
|
+
async function updateStatus(cfg2, messageId, status) {
|
|
4886
3757
|
const api = createApmApiClient(cfg2);
|
|
4887
3758
|
await api.cli.webideUpdateMessageStatus({ id: messageId, status });
|
|
4888
3759
|
}
|
|
4889
|
-
async function
|
|
3760
|
+
async function setError(cfg2, messageId, error) {
|
|
4890
3761
|
const api = createApmApiClient(cfg2);
|
|
4891
3762
|
await api.cli.webideSetMessageError({ id: messageId, error });
|
|
4892
3763
|
}
|
|
@@ -4905,7 +3776,7 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
4905
3776
|
console.log(
|
|
4906
3777
|
`[apm] webide-message action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} taskId=${taskId} messageId=${messageId}`
|
|
4907
3778
|
);
|
|
4908
|
-
await
|
|
3779
|
+
await updateStatus(cfg2, messageId, "TYPING");
|
|
4909
3780
|
syncLocalTaskStatus(workdir, taskId, msg2, "TYPING");
|
|
4910
3781
|
const eventSession = new EventSession(msg2.content);
|
|
4911
3782
|
const prepAgentId = loadWebIdeAgentId(workdir, taskId) ?? "webide-cli-prep";
|
|
@@ -4996,10 +3867,13 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
4996
3867
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
4997
3868
|
await runPrepStep(
|
|
4998
3869
|
"\u51C6\u5907\u7279\u6027\u5206\u652F",
|
|
4999
|
-
async () =>
|
|
3870
|
+
async () => {
|
|
3871
|
+
const projectId = await resolveProjectIdForTask(taskId);
|
|
3872
|
+
return runTaskBranch(taskId, projectId, { cwd: workdir });
|
|
3873
|
+
},
|
|
5000
3874
|
(branchResult) => {
|
|
5001
3875
|
markBranchDone(branchCacheKey, workdir);
|
|
5002
|
-
return `kind=${branchResult.kind} branch=${branchResult.branch}
|
|
3876
|
+
return `kind=${branchResult.kind} branch=${branchResult.branch} projectId=${branchResult.projectId} repos=${branchResult.repos.length}`;
|
|
5003
3877
|
}
|
|
5004
3878
|
);
|
|
5005
3879
|
} else {
|
|
@@ -5025,43 +3899,6 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
5025
3899
|
}
|
|
5026
3900
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
5027
3901
|
const api = createApmApiClient(cfg2);
|
|
5028
|
-
let projectId = null;
|
|
5029
|
-
await runPrepStep(
|
|
5030
|
-
"\u540C\u6B65\u9879\u76EE\u6587\u6863",
|
|
5031
|
-
async () => {
|
|
5032
|
-
try {
|
|
5033
|
-
const baseline = await api.cli.projectBaseBranch({ taskId });
|
|
5034
|
-
projectId = baseline.projectId?.trim() || null;
|
|
5035
|
-
if (!projectId) {
|
|
5036
|
-
return { synced: false, reason: "\u65E0 projectId" };
|
|
5037
|
-
}
|
|
5038
|
-
const docs = await syncProjectDocumentsPull(workdir, void 0, {
|
|
5039
|
-
projectId
|
|
5040
|
-
});
|
|
5041
|
-
return {
|
|
5042
|
-
synced: docs.synced,
|
|
5043
|
-
downloaded: docs.downloaded,
|
|
5044
|
-
deleted: docs.deleted
|
|
5045
|
-
};
|
|
5046
|
-
} catch (err) {
|
|
5047
|
-
console.warn(
|
|
5048
|
-
"[apm] WebIDE \u9879\u76EE\u6587\u6863\u540C\u6B65\u5931\u8D25:",
|
|
5049
|
-
err instanceof Error ? err.message : err
|
|
5050
|
-
);
|
|
5051
|
-
return {
|
|
5052
|
-
synced: false,
|
|
5053
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
5054
|
-
};
|
|
5055
|
-
}
|
|
5056
|
-
},
|
|
5057
|
-
(result) => {
|
|
5058
|
-
if ("reason" in result && result.reason) {
|
|
5059
|
-
return `\u8DF3\u8FC7\uFF1A${result.reason}`;
|
|
5060
|
-
}
|
|
5061
|
-
if (!result.synced) return "\u65E0\u9700\u540C\u6B65";
|
|
5062
|
-
return `downloaded=${result.downloaded ?? 0} deleted=${result.deleted ?? 0}`;
|
|
5063
|
-
}
|
|
5064
|
-
);
|
|
5065
3902
|
await runPrepStep(
|
|
5066
3903
|
"\u540C\u6B65\u4EFB\u52A1\u9644\u4EF6",
|
|
5067
3904
|
async () => {
|
|
@@ -5112,18 +3949,17 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
5112
3949
|
"running"
|
|
5113
3950
|
);
|
|
5114
3951
|
await syncPrepLog();
|
|
5115
|
-
const startLocalServices = isStartLocalServicesAction(effectiveAction);
|
|
5116
3952
|
const designAction = isDesignAction(effectiveAction);
|
|
5117
3953
|
const useNewAgent = msg2.useNewAgent === true;
|
|
5118
3954
|
const toolNames = Array.isArray(msg2.tools) ? msg2.tools.map((t) => String(t).trim()).filter(Boolean) : [];
|
|
5119
|
-
if (useNewAgent
|
|
3955
|
+
if (useNewAgent) {
|
|
5120
3956
|
if (designAction) {
|
|
5121
3957
|
clearWebIdeDesignAgentId(workdir, taskId);
|
|
5122
3958
|
} else {
|
|
5123
3959
|
clearWebIdeAgentId(workdir, taskId);
|
|
5124
3960
|
}
|
|
5125
3961
|
}
|
|
5126
|
-
const savedAgentId =
|
|
3962
|
+
const savedAgentId = useNewAgent ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
|
|
5127
3963
|
const promptPayload = toolNames.includes("MysqlExecute") ? continuePayload ?? parseWebIdePromptPayload(msg2.content) : null;
|
|
5128
3964
|
const sqlExecutionId = toolNames.includes("MysqlExecute") && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
|
|
5129
3965
|
if (toolNames.includes("MysqlExecute") && !sqlExecutionId) {
|
|
@@ -5132,7 +3968,6 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
5132
3968
|
);
|
|
5133
3969
|
}
|
|
5134
3970
|
const persistAgentId = (agentId) => {
|
|
5135
|
-
if (startLocalServices) return;
|
|
5136
3971
|
if (designAction) {
|
|
5137
3972
|
saveWebIdeDesignAgentId(workdir, taskId, agentId);
|
|
5138
3973
|
} else {
|
|
@@ -5158,7 +3993,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5158
3993
|
forceSend: true,
|
|
5159
3994
|
eventSession,
|
|
5160
3995
|
appendMessageContent: (content) => appendContent(cfg2, messageId, content),
|
|
5161
|
-
askQuestionExecute:
|
|
3996
|
+
askQuestionExecute: toolNames.includes("AskQuestion") ? createWebIdeAskQuestionExecute({
|
|
5162
3997
|
cfg: cfg2,
|
|
5163
3998
|
taskId,
|
|
5164
3999
|
onAwaitingAssumptions: () => {
|
|
@@ -5170,11 +4005,10 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5170
4005
|
enableAskQuestion: false,
|
|
5171
4006
|
enableWebIdePlanTools: false,
|
|
5172
4007
|
enableMysqlTools: false,
|
|
5173
|
-
enablePtySessionMcp: startLocalServices || toolNames.includes("PtySession"),
|
|
5174
4008
|
sqlExecutionId,
|
|
5175
4009
|
toolNames,
|
|
5176
4010
|
enableSandbox: false,
|
|
5177
|
-
onInvalidatePersistedAgentId:
|
|
4011
|
+
onInvalidatePersistedAgentId: () => {
|
|
5178
4012
|
if (designAction) {
|
|
5179
4013
|
clearWebIdeDesignAgentId(workdir, taskId);
|
|
5180
4014
|
} else {
|
|
@@ -5227,9 +4061,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5227
4061
|
}
|
|
5228
4062
|
}
|
|
5229
4063
|
);
|
|
5230
|
-
|
|
5231
|
-
persistAgentId(outcome.agentId);
|
|
5232
|
-
}
|
|
4064
|
+
persistAgentId(outcome.agentId);
|
|
5233
4065
|
const tokenUsage = outcome.usage != null ? {
|
|
5234
4066
|
modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
|
|
5235
4067
|
inputTokens: outcome.usage.inputTokens,
|
|
@@ -5263,7 +4095,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5263
4095
|
);
|
|
5264
4096
|
}
|
|
5265
4097
|
}
|
|
5266
|
-
await
|
|
4098
|
+
await setError(cfg2, messageId, detail);
|
|
5267
4099
|
syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
|
|
5268
4100
|
return;
|
|
5269
4101
|
}
|
|
@@ -5288,7 +4120,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5288
4120
|
);
|
|
5289
4121
|
}
|
|
5290
4122
|
}
|
|
5291
|
-
await
|
|
4123
|
+
await updateStatus(cfg2, messageId, "CANCELLED");
|
|
5292
4124
|
syncLocalTaskStatus(workdir, taskId, msg2, "CANCELLED");
|
|
5293
4125
|
return;
|
|
5294
4126
|
}
|
|
@@ -5333,7 +4165,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5333
4165
|
detail,
|
|
5334
4166
|
tokenUsage
|
|
5335
4167
|
);
|
|
5336
|
-
await
|
|
4168
|
+
await setError(cfg2, messageId, detail);
|
|
5337
4169
|
syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
|
|
5338
4170
|
return;
|
|
5339
4171
|
}
|
|
@@ -5410,16 +4242,6 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5410
4242
|
}
|
|
5411
4243
|
}
|
|
5412
4244
|
}
|
|
5413
|
-
if (projectId) {
|
|
5414
|
-
try {
|
|
5415
|
-
await syncProjectDocumentsPush(cfg2, workdir, void 0, { projectId });
|
|
5416
|
-
} catch (err) {
|
|
5417
|
-
console.warn(
|
|
5418
|
-
"[apm] WebIDE \u9879\u76EE\u6587\u6863\u63A8\u9001\u5931\u8D25:",
|
|
5419
|
-
err instanceof Error ? err.message : err
|
|
5420
|
-
);
|
|
5421
|
-
}
|
|
5422
|
-
}
|
|
5423
4245
|
await logSyncRef.current?.markRun(
|
|
5424
4246
|
outcome.runId,
|
|
5425
4247
|
"finished",
|
|
@@ -5434,21 +4256,11 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5434
4256
|
`[apm] \u672C\u8F6E\u505C\u5728\u5F85\u786E\u8BA4\u5047\u8BBE\uFF0C\u8DF3\u8FC7 complete action=${effectiveAction} taskId=${taskId}`
|
|
5435
4257
|
);
|
|
5436
4258
|
}
|
|
5437
|
-
await
|
|
4259
|
+
await updateStatus(cfg2, messageId, "SUCCESS");
|
|
5438
4260
|
syncLocalTaskStatus(workdir, taskId, msg2, "SUCCESS");
|
|
5439
4261
|
console.log(
|
|
5440
4262
|
`[apm] webide-message \u5B8C\u6210 action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} messageId=${messageId} agentId=${outcome.agentId}`
|
|
5441
4263
|
);
|
|
5442
|
-
if (effectiveAction === "request-accept" || effectiveAction === "start-accept") {
|
|
5443
|
-
try {
|
|
5444
|
-
await cleanWebIdeWorkspaceCache(taskId, workdir, cfg2);
|
|
5445
|
-
} catch (err) {
|
|
5446
|
-
console.warn(
|
|
5447
|
-
"[apm] \u5408\u5E76 PR \u540E\u6E05\u7406 WebIDE agent \u7F13\u5B58\u5931\u8D25:",
|
|
5448
|
-
err instanceof Error ? err.message : err
|
|
5449
|
-
);
|
|
5450
|
-
}
|
|
5451
|
-
}
|
|
5452
4264
|
} catch (err) {
|
|
5453
4265
|
const detail = err instanceof Error ? err.message : String(err);
|
|
5454
4266
|
console.error(`[apm] webide-message \u5931\u8D25: ${detail}`);
|
|
@@ -5459,7 +4271,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5459
4271
|
clearWebIdeAgentId(workdir, taskId);
|
|
5460
4272
|
}
|
|
5461
4273
|
try {
|
|
5462
|
-
await
|
|
4274
|
+
await setError(cfg2, messageId, detail);
|
|
5463
4275
|
} catch (statusErr) {
|
|
5464
4276
|
console.error(
|
|
5465
4277
|
"[apm] \u5199\u5165 WebIDE FAILED \u5931\u8D25:",
|
|
@@ -5483,23 +4295,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
5483
4295
|
|
|
5484
4296
|
// src/commands/connect/core/worker/handlers/index.ts
|
|
5485
4297
|
async function dispatchWorkerJob(cfg2, msg2, signal) {
|
|
5486
|
-
|
|
5487
|
-
case "webide-message":
|
|
5488
|
-
await handleWebIdeInboundMessage(cfg2, msg2, signal);
|
|
5489
|
-
return;
|
|
5490
|
-
case "webide-start-project":
|
|
5491
|
-
await handleStartProject(cfg2, msg2, signal);
|
|
5492
|
-
return;
|
|
5493
|
-
case "webide-package":
|
|
5494
|
-
await handleWebIdePackage(cfg2, msg2, signal);
|
|
5495
|
-
return;
|
|
5496
|
-
default: {
|
|
5497
|
-
const unknown = msg2;
|
|
5498
|
-
throw new Error(
|
|
5499
|
-
`\u672A\u77E5 WebIDE worker \u4EFB\u52A1: ${unknown.type ?? "unknown"}`
|
|
5500
|
-
);
|
|
5501
|
-
}
|
|
5502
|
-
}
|
|
4298
|
+
await handleWebIdeInboundMessage(cfg2, msg2, signal);
|
|
5503
4299
|
}
|
|
5504
4300
|
|
|
5505
4301
|
// src/commands/connect/core/worker/worker-script.ts
|