ai-project-manage-cli 8.1.25 → 8.2.19
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 +6 -18
- package/dist/index.js +816 -814
- package/dist/worker-script.js +2731 -1564
- package/package.json +2 -2
- package/template/skills/webide-run-retro/SKILL.md +103 -0
- package/template/skills/webide-run-retro/attribution.md +26 -0
- package/template/skills/webide-run-retro/scripts/inspect-run.mjs +335 -0
- package/dist/scripts/branch_has_changes.sh +0 -53
- package/dist/scripts/commit_if_dirty.sh +0 -21
- package/dist/scripts/ensure_baseline.sh +0 -15
- package/dist/scripts/ensure_feature_branch.sh +0 -57
- package/dist/scripts/push_code.sh +0 -21
package/dist/worker-script.js
CHANGED
|
@@ -115,10 +115,38 @@ 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
|
+
}),
|
|
118
134
|
getApmLogStorage: defineEndpoint({
|
|
119
135
|
method: "GET",
|
|
120
136
|
path: "/cli/apm-log-storage"
|
|
121
137
|
}),
|
|
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
|
+
}),
|
|
122
150
|
cursorStoreUpsertAgent: defineEndpoint({
|
|
123
151
|
method: "PUT",
|
|
124
152
|
path: "/cli/cursor-store/agents"
|
|
@@ -179,15 +207,6 @@ function resolveClientMachineId(cfg2) {
|
|
|
179
207
|
function resolveApiKey(cfg2) {
|
|
180
208
|
return (cfg2.apiKey ?? cfg2.token ?? "").trim();
|
|
181
209
|
}
|
|
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
|
-
}
|
|
191
210
|
function tryReadApmConfig() {
|
|
192
211
|
try {
|
|
193
212
|
const raw = readFileSync(APM_CONFIG_PATH, "utf8");
|
|
@@ -203,12 +222,10 @@ function tryReadApmConfig() {
|
|
|
203
222
|
if (!apiKey) return null;
|
|
204
223
|
const cfg2 = v;
|
|
205
224
|
const clientMachineId = resolveClientMachineId(cfg2);
|
|
206
|
-
const socketServer = readConfigSocketServer(cfg2);
|
|
207
225
|
return {
|
|
208
226
|
baseUrl: cfg2.baseUrl.trim().replace(/\/+$/, ""),
|
|
209
227
|
apiKey,
|
|
210
|
-
...clientMachineId ? { clientMachineId } : {}
|
|
211
|
-
...socketServer ? { socketServer } : {}
|
|
228
|
+
...clientMachineId ? { clientMachineId } : {}
|
|
212
229
|
};
|
|
213
230
|
} catch {
|
|
214
231
|
return null;
|
|
@@ -231,14 +248,10 @@ async function ensureApmConfig() {
|
|
|
231
248
|
async function writeApmConfig(cfg2) {
|
|
232
249
|
const clientMachineId = resolveClientMachineId(cfg2);
|
|
233
250
|
const apiKey = resolveApiKey(cfg2);
|
|
234
|
-
const socketServer = stripTrailingSlash(
|
|
235
|
-
cfg2.socketServer?.trim() || cfg2.wsUrl?.trim() || ""
|
|
236
|
-
);
|
|
237
251
|
const normalized = {
|
|
238
252
|
baseUrl: cfg2.baseUrl.trim().replace(/\/+$/, ""),
|
|
239
253
|
apiKey,
|
|
240
|
-
...clientMachineId ? { clientMachineId } : {}
|
|
241
|
-
...socketServer ? { socketServer } : {}
|
|
254
|
+
...clientMachineId ? { clientMachineId } : {}
|
|
242
255
|
};
|
|
243
256
|
mkdirSync(APM_CONFIG_DIR, { recursive: true });
|
|
244
257
|
writeFileSync(APM_CONFIG_PATH, JSON.stringify(normalized, null, 2) + "\n", {
|
|
@@ -271,8 +284,8 @@ function toFsPath(inputPath) {
|
|
|
271
284
|
}
|
|
272
285
|
return `\\\\?\\${normalized}`;
|
|
273
286
|
}
|
|
274
|
-
function normalizeWorkdirPath(
|
|
275
|
-
let normalized =
|
|
287
|
+
function normalizeWorkdirPath(path3) {
|
|
288
|
+
let normalized = path3.trim().replace(/\\/g, "/").normalize("NFC");
|
|
276
289
|
if (normalized.startsWith("//?/")) {
|
|
277
290
|
normalized = normalized.slice(4);
|
|
278
291
|
}
|
|
@@ -359,66 +372,84 @@ async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
|
|
|
359
372
|
}
|
|
360
373
|
|
|
361
374
|
// 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
|
|
362
379
|
import { execFile } from "child_process";
|
|
363
|
-
import { dirname, join as join3 } from "path";
|
|
364
|
-
import { fileURLToPath } from "url";
|
|
365
380
|
import { promisify } from "util";
|
|
366
381
|
var execFileAsync = promisify(execFile);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
]
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
382
|
+
function toHttpsGitRemoteUrl(raw) {
|
|
383
|
+
let s = raw.trim();
|
|
384
|
+
if (!s) return null;
|
|
385
|
+
const sshScp = /^git@([^:]+):(.+)$/.exec(s);
|
|
386
|
+
if (sshScp) {
|
|
387
|
+
s = `https://${sshScp[1]}/${sshScp[2]}`;
|
|
388
|
+
} else {
|
|
389
|
+
const sshUri = /^ssh:\/\/(?:git@)?([^/]+)\/(.+)$/i.exec(s);
|
|
390
|
+
if (sshUri) {
|
|
391
|
+
s = `https://${sshUri[1]}/${sshUri[2]}`;
|
|
392
|
+
} else if (/^http:\/\//i.test(s)) {
|
|
393
|
+
s = `https://${s.slice("http://".length)}`;
|
|
394
|
+
} else if (!/^https:\/\//i.test(s)) {
|
|
395
|
+
const scpLike = /^([^/:]+):(.+)$/.exec(s);
|
|
396
|
+
if (scpLike && !scpLike[1].includes(".")) {
|
|
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
|
+
}
|
|
391
407
|
}
|
|
392
|
-
|
|
393
|
-
|
|
408
|
+
s = s.replace(/\/+$/, "");
|
|
409
|
+
if (!/^https:\/\//i.test(s)) return null;
|
|
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;
|
|
394
423
|
}
|
|
395
|
-
return extras.length ? `${base} ${extras.join(" ")}` : base;
|
|
396
424
|
}
|
|
397
|
-
function
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
// 避免 credential helper / askpass 在无 TTY 时挂起
|
|
402
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
403
|
-
};
|
|
425
|
+
async function tryReadHttpsGitOriginUrl(cwd) {
|
|
426
|
+
const raw = await tryReadGitOriginUrl(cwd);
|
|
427
|
+
if (!raw) return null;
|
|
428
|
+
return toHttpsGitRemoteUrl(raw);
|
|
404
429
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
430
|
+
|
|
431
|
+
// src/utils/git.ts
|
|
432
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
433
|
+
function gitExecEnv() {
|
|
434
|
+
const existing = process.env.GIT_SSH_COMMAND?.trim();
|
|
435
|
+
const acceptNew = "-o StrictHostKeyChecking=accept-new";
|
|
436
|
+
let gitSshCommand;
|
|
437
|
+
if (!existing) {
|
|
438
|
+
gitSshCommand = `ssh ${acceptNew}`;
|
|
439
|
+
} else if (/\bStrictHostKeyChecking=/.test(existing)) {
|
|
440
|
+
gitSshCommand = existing;
|
|
441
|
+
} else {
|
|
442
|
+
gitSshCommand = `${existing} ${acceptNew}`;
|
|
443
|
+
}
|
|
444
|
+
return { ...process.env, GIT_SSH_COMMAND: gitSshCommand };
|
|
410
445
|
}
|
|
411
|
-
async function execGit(cwd, args,
|
|
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;
|
|
446
|
+
async function execGit(cwd, args, quiet = false) {
|
|
415
447
|
try {
|
|
416
|
-
const { stdout, stderr } = await
|
|
448
|
+
const { stdout, stderr } = await execFileAsync2("git", args, {
|
|
417
449
|
cwd,
|
|
418
450
|
env: gitExecEnv(),
|
|
419
451
|
encoding: "utf8",
|
|
420
|
-
maxBuffer: 10 * 1024 * 1024
|
|
421
|
-
...timeoutMs > 0 ? { timeout: timeoutMs, killSignal: "SIGTERM" } : {}
|
|
452
|
+
maxBuffer: 10 * 1024 * 1024
|
|
422
453
|
});
|
|
423
454
|
if (!quiet && stderr.trim()) {
|
|
424
455
|
process.stderr.write(stderr);
|
|
@@ -426,232 +457,113 @@ async function execGit(cwd, args, quietOrOptions = false) {
|
|
|
426
457
|
return stdout;
|
|
427
458
|
} catch (err) {
|
|
428
459
|
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
|
-
}
|
|
434
460
|
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
435
461
|
throw new Error(
|
|
436
462
|
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
437
463
|
);
|
|
438
464
|
}
|
|
439
465
|
}
|
|
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
|
+
}
|
|
440
474
|
async function ensureGitRepo(cwd) {
|
|
441
475
|
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
442
476
|
}
|
|
443
477
|
async function getCurrentBranch(cwd) {
|
|
444
478
|
return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
445
479
|
}
|
|
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
|
+
}
|
|
446
492
|
async function resolveGitRepoRoot(cwd) {
|
|
447
493
|
return (await execGit(cwd, ["rev-parse", "--show-toplevel"], true)).trim();
|
|
448
494
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
495
|
+
async function ensureRemoteBaselineBranch(cwd, baselineBranch) {
|
|
496
|
+
await execGit(cwd, ["fetch", "origin", baselineBranch], true);
|
|
497
|
+
if (await remoteBranchExists(cwd, baselineBranch)) {
|
|
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
|
+
);
|
|
455
503
|
}
|
|
456
|
-
async function
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
504
|
+
async function branchHasChangesAgainstBase(cwd, baseBranch, headBranch) {
|
|
505
|
+
const base = baseBranch.trim().replace(/^origin\//, "");
|
|
506
|
+
const head = headBranch.trim().replace(/^origin\//, "");
|
|
507
|
+
if (!base || !head) return false;
|
|
508
|
+
await execGit(cwd, ["fetch", "origin", base], true);
|
|
509
|
+
let headRef = head;
|
|
461
510
|
try {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
511
|
+
await execGit(cwd, ["fetch", "origin", head], true);
|
|
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
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
await execGit(cwd, ["diff", "--quiet", `${baseRef}...${headRef}`], true);
|
|
524
|
+
return false;
|
|
525
|
+
} catch {
|
|
472
526
|
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}` : ""}`);
|
|
487
527
|
}
|
|
488
528
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
});
|
|
529
|
+
var FEATURE_BRANCH_RE = /^feat\/task-(.+)$/;
|
|
530
|
+
function isFeatureBranchName(branch) {
|
|
531
|
+
return FEATURE_BRANCH_RE.test(branch.trim().replace(/^origin\//, ""));
|
|
503
532
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
});
|
|
533
|
+
function taskIdFromFeatureBranch(branch) {
|
|
534
|
+
const name = branch.trim().replace(/^origin\//, "");
|
|
535
|
+
const match = name.match(FEATURE_BRANCH_RE);
|
|
536
|
+
const id = match?.[1]?.trim() ?? "";
|
|
537
|
+
return id || null;
|
|
510
538
|
}
|
|
511
539
|
async function pushCurrentBranch(cwd) {
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
function isSimpleChildDirName(name) {
|
|
522
|
-
if (!name || name === "." || name === "..") return false;
|
|
523
|
-
if (name.includes("/") || name.includes("\\")) return false;
|
|
524
|
-
return true;
|
|
525
|
-
}
|
|
526
|
-
function resolveReposManifestPath(explicit) {
|
|
527
|
-
const fromOpt = explicit?.trim() || "";
|
|
528
|
-
if (fromOpt) return fromOpt;
|
|
529
|
-
const fromEnv = process.env.APM_REPOS_MANIFEST?.trim() || "";
|
|
530
|
-
if (fromEnv) return fromEnv;
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
function loadHomeRepositoryEntries(manifestPath2) {
|
|
534
|
-
const label = manifestPath2.trim();
|
|
535
|
-
if (!label) {
|
|
536
|
-
throw new Error("[apm] \u591A\u4ED3\u6E05\u5355\u8DEF\u5F84\u4E3A\u7A7A");
|
|
537
|
-
}
|
|
538
|
-
const path = toFsPath(label);
|
|
539
|
-
if (!existsSync2(path)) {
|
|
540
|
-
throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${label}`);
|
|
541
|
-
}
|
|
542
|
-
let parsed;
|
|
543
|
-
try {
|
|
544
|
-
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
545
|
-
} catch (err) {
|
|
546
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
547
|
-
throw new Error(`[apm] \u89E3\u6790 ${label} \u5931\u8D25: ${detail}`);
|
|
548
|
-
}
|
|
549
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
550
|
-
throw new Error(
|
|
551
|
-
`[apm] ${label} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
|
|
552
|
-
);
|
|
553
|
-
}
|
|
554
|
-
const entries = [];
|
|
555
|
-
for (const [rawKey, rawVal] of Object.entries(
|
|
556
|
-
parsed
|
|
557
|
-
)) {
|
|
558
|
-
const pathKey = rawKey.trim();
|
|
559
|
-
if (!isSimpleChildDirName(pathKey)) {
|
|
560
|
-
throw new Error(
|
|
561
|
-
`[apm] ${label} \u975E\u6CD5\u76EE\u5F55 key: ${JSON.stringify(rawKey)}\uFF08\u987B\u4E3A\u5F53\u524D\u4E0B\u7EA7\u76EE\u5F55\u540D\uFF09`
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
if (typeof rawVal !== "string" || !rawVal.trim()) {
|
|
565
|
-
throw new Error(
|
|
566
|
-
`[apm] ${label}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
entries.push({ path: pathKey, remoteUrl: rawVal.trim() });
|
|
540
|
+
if (!await isGitRepo(cwd)) {
|
|
541
|
+
return false;
|
|
570
542
|
}
|
|
571
|
-
|
|
572
|
-
if (
|
|
573
|
-
|
|
543
|
+
const originUrl = await tryReadGitOriginUrl(cwd);
|
|
544
|
+
if (!originUrl) {
|
|
545
|
+
console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8DF3\u8FC7 push");
|
|
546
|
+
return false;
|
|
574
547
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
function resolveHomeRepositoryAbsolutePaths(workdirInput, entries) {
|
|
578
|
-
const workdir = resolveWorkdirPath(workdirInput);
|
|
579
|
-
return entries.map((entry) => resolve3(join4(workdir, entry.path)));
|
|
548
|
+
await execGit(cwd, ["push", "-u", "origin", "HEAD"]);
|
|
549
|
+
return true;
|
|
580
550
|
}
|
|
581
551
|
|
|
582
|
-
// src/commands/branch.ts
|
|
583
|
-
import { basename as basename2 } from "path";
|
|
584
|
-
|
|
585
552
|
// src/utils/workspace-repos.ts
|
|
586
553
|
import {
|
|
587
|
-
existsSync as
|
|
554
|
+
existsSync as existsSync2,
|
|
588
555
|
mkdirSync as mkdirSync3,
|
|
589
|
-
readFileSync as
|
|
556
|
+
readFileSync as readFileSync2,
|
|
590
557
|
readdirSync as readdirSync2,
|
|
591
558
|
statSync as statSync2,
|
|
592
559
|
writeFileSync as writeFileSync2
|
|
593
560
|
} from "fs";
|
|
594
|
-
import { basename, dirname
|
|
595
|
-
|
|
596
|
-
// src/utils/git-remote.ts
|
|
597
|
-
import { execFile as execFile2 } from "child_process";
|
|
598
|
-
import { promisify as promisify2 } from "util";
|
|
599
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
600
|
-
function toHttpsGitRemoteUrl(raw) {
|
|
601
|
-
let s = raw.trim();
|
|
602
|
-
if (!s) return null;
|
|
603
|
-
const sshScp = /^git@([^:]+):(.+)$/.exec(s);
|
|
604
|
-
if (sshScp) {
|
|
605
|
-
s = `https://${sshScp[1]}/${sshScp[2]}`;
|
|
606
|
-
} else {
|
|
607
|
-
const sshUri = /^ssh:\/\/(?:git@)?([^/]+)\/(.+)$/i.exec(s);
|
|
608
|
-
if (sshUri) {
|
|
609
|
-
s = `https://${sshUri[1]}/${sshUri[2]}`;
|
|
610
|
-
} else if (/^http:\/\//i.test(s)) {
|
|
611
|
-
s = `https://${s.slice("http://".length)}`;
|
|
612
|
-
} else if (!/^https:\/\//i.test(s)) {
|
|
613
|
-
const scpLike = /^([^/:]+):(.+)$/.exec(s);
|
|
614
|
-
if (scpLike && !scpLike[1].includes(".")) {
|
|
615
|
-
return null;
|
|
616
|
-
}
|
|
617
|
-
if (scpLike) {
|
|
618
|
-
s = `https://${scpLike[1]}/${scpLike[2]}`;
|
|
619
|
-
} else if (s.includes("/")) {
|
|
620
|
-
s = `https://${s}`;
|
|
621
|
-
} else {
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
s = s.replace(/\/+$/, "");
|
|
627
|
-
if (!/^https:\/\//i.test(s)) return null;
|
|
628
|
-
return s;
|
|
629
|
-
}
|
|
630
|
-
async function tryReadGitOriginUrl(cwd) {
|
|
631
|
-
try {
|
|
632
|
-
const { stdout } = await execFileAsync2(
|
|
633
|
-
"git",
|
|
634
|
-
["config", "--get", "remote.origin.url"],
|
|
635
|
-
{ cwd, encoding: "utf8", maxBuffer: 1024 * 1024 }
|
|
636
|
-
);
|
|
637
|
-
const url = stdout.trim();
|
|
638
|
-
return url || null;
|
|
639
|
-
} catch {
|
|
640
|
-
return null;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
async function tryReadHttpsGitOriginUrl(cwd) {
|
|
644
|
-
const raw = await tryReadGitOriginUrl(cwd);
|
|
645
|
-
if (!raw) return null;
|
|
646
|
-
return toHttpsGitRemoteUrl(raw);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// src/utils/workspace-repos.ts
|
|
561
|
+
import { basename, dirname, join as join3, relative, resolve as resolve3 } from "path";
|
|
650
562
|
var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
|
|
651
563
|
var WORKSPACE_REPOS_VERSION = 1;
|
|
652
564
|
var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([".apm", "node_modules"]);
|
|
653
565
|
function hasGitAt(dir) {
|
|
654
|
-
return
|
|
566
|
+
return existsSync2(toFsPath(join3(dir, ".git")));
|
|
655
567
|
}
|
|
656
568
|
function isSkippableChildName(name) {
|
|
657
569
|
if (SKIP_DIR_NAMES.has(name)) return true;
|
|
@@ -659,21 +571,25 @@ function isSkippableChildName(name) {
|
|
|
659
571
|
return false;
|
|
660
572
|
}
|
|
661
573
|
function manifestPath(workdir) {
|
|
662
|
-
return
|
|
574
|
+
return join3(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
|
|
663
575
|
}
|
|
664
576
|
function absoluteRepoPath(workdir, entry) {
|
|
665
|
-
|
|
577
|
+
if (entry.path === "." || entry.path === "") {
|
|
578
|
+
return workdir;
|
|
579
|
+
}
|
|
580
|
+
return resolve3(workdir, entry.path);
|
|
666
581
|
}
|
|
667
582
|
function toRelativeRepoPath(workdir, repoAbs) {
|
|
668
|
-
|
|
583
|
+
const rel = relative(workdir, repoAbs).replace(/\\/g, "/");
|
|
584
|
+
return rel === "" ? "." : rel;
|
|
669
585
|
}
|
|
670
586
|
function normalizeRepoEntry(raw) {
|
|
671
587
|
if (!raw || typeof raw !== "object") return null;
|
|
672
588
|
const o = raw;
|
|
673
589
|
if (typeof o.path !== "string" || !o.path.trim()) return null;
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
590
|
+
const entry = {
|
|
591
|
+
path: o.path.trim().replace(/\\/g, "/")
|
|
592
|
+
};
|
|
677
593
|
if (typeof o.remoteUrl === "string" && o.remoteUrl.trim()) {
|
|
678
594
|
entry.remoteUrl = o.remoteUrl.trim();
|
|
679
595
|
} else if (o.remoteUrl === null) {
|
|
@@ -682,18 +598,18 @@ function normalizeRepoEntry(raw) {
|
|
|
682
598
|
return entry;
|
|
683
599
|
}
|
|
684
600
|
function readManifest(workdir) {
|
|
685
|
-
const
|
|
686
|
-
if (!
|
|
601
|
+
const path3 = toFsPath(manifestPath(workdir));
|
|
602
|
+
if (!existsSync2(path3)) {
|
|
687
603
|
return null;
|
|
688
604
|
}
|
|
689
605
|
try {
|
|
690
606
|
const raw = JSON.parse(
|
|
691
|
-
|
|
607
|
+
readFileSync2(path3, "utf8")
|
|
692
608
|
);
|
|
693
609
|
if (raw?.version !== WORKSPACE_REPOS_VERSION) {
|
|
694
610
|
return null;
|
|
695
611
|
}
|
|
696
|
-
if (raw.kind !== "multi") {
|
|
612
|
+
if (raw.kind !== "single" && raw.kind !== "multi") {
|
|
697
613
|
return null;
|
|
698
614
|
}
|
|
699
615
|
if (!Array.isArray(raw.repos) || raw.repos.length === 0) {
|
|
@@ -708,7 +624,7 @@ function readManifest(workdir) {
|
|
|
708
624
|
}
|
|
709
625
|
return {
|
|
710
626
|
version: WORKSPACE_REPOS_VERSION,
|
|
711
|
-
kind:
|
|
627
|
+
kind: raw.kind,
|
|
712
628
|
workdir: raw.workdir,
|
|
713
629
|
repos,
|
|
714
630
|
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt.trim() ? raw.scannedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -720,8 +636,8 @@ function readManifest(workdir) {
|
|
|
720
636
|
function writeManifest(workdir, manifest) {
|
|
721
637
|
const apmDir = toFsPath(workspaceApmDir(workdir));
|
|
722
638
|
mkdirSync3(apmDir, { recursive: true });
|
|
723
|
-
const
|
|
724
|
-
writeFileSync2(
|
|
639
|
+
const path3 = toFsPath(manifestPath(workdir));
|
|
640
|
+
writeFileSync2(path3, `${JSON.stringify(manifest, null, 2)}
|
|
725
641
|
`, "utf8");
|
|
726
642
|
}
|
|
727
643
|
function manifestStillValid(workdir, manifest) {
|
|
@@ -732,9 +648,6 @@ function manifestStillValid(workdir, manifest) {
|
|
|
732
648
|
if (typeof entry?.path !== "string" || !entry.path.trim()) {
|
|
733
649
|
return false;
|
|
734
650
|
}
|
|
735
|
-
if (entry.path === "." || entry.path.includes("/")) {
|
|
736
|
-
return false;
|
|
737
|
-
}
|
|
738
651
|
const abs = absoluteRepoPath(workdir, entry);
|
|
739
652
|
if (!hasGitAt(abs)) {
|
|
740
653
|
return false;
|
|
@@ -744,8 +657,17 @@ function manifestStillValid(workdir, manifest) {
|
|
|
744
657
|
}
|
|
745
658
|
function scanWorkspaceRepos(workdirInput) {
|
|
746
659
|
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
|
+
}
|
|
747
669
|
const fsWorkdir = toFsPath(workdir);
|
|
748
|
-
if (!
|
|
670
|
+
if (!existsSync2(fsWorkdir) || !statSync2(fsWorkdir).isDirectory()) {
|
|
749
671
|
throw new Error(`[apm] \u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${workdir}`);
|
|
750
672
|
}
|
|
751
673
|
const repos = [];
|
|
@@ -753,9 +675,9 @@ function scanWorkspaceRepos(workdirInput) {
|
|
|
753
675
|
if (isSkippableChildName(name)) {
|
|
754
676
|
continue;
|
|
755
677
|
}
|
|
756
|
-
const childAbs =
|
|
678
|
+
const childAbs = resolve3(workdir, name);
|
|
757
679
|
const childFs = toFsPath(childAbs);
|
|
758
|
-
if (!
|
|
680
|
+
if (!existsSync2(childFs) || !statSync2(childFs).isDirectory()) {
|
|
759
681
|
continue;
|
|
760
682
|
}
|
|
761
683
|
if (!hasGitAt(childAbs)) {
|
|
@@ -766,7 +688,7 @@ function scanWorkspaceRepos(workdirInput) {
|
|
|
766
688
|
repos.sort((a, b) => a.path.localeCompare(b.path));
|
|
767
689
|
if (repos.length === 0) {
|
|
768
690
|
throw new Error(
|
|
769
|
-
`[apm] \u5DE5\u4F5C\u533A ${workdir} \u4E00\u7EA7\u5B50\u76EE\u5F55\u4E2D\u672A\u53D1\u73B0 git \u4ED3\u5E93`
|
|
691
|
+
`[apm] \u5DE5\u4F5C\u533A ${workdir} \u6839\u76EE\u5F55\u65E0 .git\uFF0C\u4E14\u4E00\u7EA7\u5B50\u76EE\u5F55\u4E2D\u672A\u53D1\u73B0 git \u4ED3\u5E93`
|
|
770
692
|
);
|
|
771
693
|
}
|
|
772
694
|
return {
|
|
@@ -794,7 +716,7 @@ function loadWorkspaceReposCache(workdirInput) {
|
|
|
794
716
|
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`);
|
|
795
717
|
}
|
|
796
718
|
console.log(
|
|
797
|
-
`[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 repos=${cached.repos.map((r) => r.path).join(", ")}`
|
|
719
|
+
`[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 kind=${cached.kind} repos=${cached.repos.map((r) => r.path).join(", ")}`
|
|
798
720
|
);
|
|
799
721
|
return {
|
|
800
722
|
...cached,
|
|
@@ -832,7 +754,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
|
|
|
832
754
|
const cached = readManifest(workdir);
|
|
833
755
|
if (cached && manifestStillValid(workdir, cached)) {
|
|
834
756
|
console.log(
|
|
835
|
-
`[apm] \u4F7F\u7528\u7F13\u5B58\u7684\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 repos=${cached.repos.length}`
|
|
757
|
+
`[apm] \u4F7F\u7528\u7F13\u5B58\u7684\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 kind=${cached.kind} repos=${cached.repos.length}`
|
|
836
758
|
);
|
|
837
759
|
return cached;
|
|
838
760
|
}
|
|
@@ -849,7 +771,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
|
|
|
849
771
|
}
|
|
850
772
|
writeManifest(workdir, scanned);
|
|
851
773
|
console.log(
|
|
852
|
-
`[apm] \u5DF2\u626B\u63CF\u5DE5\u4F5C\u533A\u4ED3\u5E93 repos=${scanned.repos.map((r) => r.path).join(", ")} \u2192 .apm/${WORKSPACE_REPOS_MANIFEST}`
|
|
774
|
+
`[apm] \u5DF2\u626B\u63CF\u5DE5\u4F5C\u533A\u4ED3\u5E93 kind=${scanned.kind} repos=${scanned.repos.map((r) => r.path).join(", ")} \u2192 .apm/${WORKSPACE_REPOS_MANIFEST}`
|
|
853
775
|
);
|
|
854
776
|
return scanned;
|
|
855
777
|
}
|
|
@@ -860,70 +782,48 @@ function resolveWorkspaceRepoAbsolutePaths(manifest) {
|
|
|
860
782
|
}
|
|
861
783
|
function formatRepoLabel(workdir, repoAbs) {
|
|
862
784
|
const rel = toRelativeRepoPath(resolveWorkdirPath(workdir), repoAbs);
|
|
863
|
-
return rel
|
|
785
|
+
return rel === "." ? basename(repoAbs) || repoAbs : rel;
|
|
864
786
|
}
|
|
865
787
|
|
|
866
788
|
// src/commands/branch.ts
|
|
867
|
-
import { existsSync as existsSync4 } from "fs";
|
|
868
789
|
var TASK_BRANCH_PREFIX = "feat/task-";
|
|
869
|
-
async function
|
|
870
|
-
|
|
871
|
-
|
|
790
|
+
async function localBranchExists(cwd, branch) {
|
|
791
|
+
try {
|
|
792
|
+
await execGit(
|
|
793
|
+
cwd,
|
|
794
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
795
|
+
true
|
|
796
|
+
);
|
|
797
|
+
return true;
|
|
798
|
+
} catch {
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
872
801
|
}
|
|
873
|
-
async function
|
|
874
|
-
|
|
802
|
+
async function commitWorkingTreeIfDirty(cwd, message) {
|
|
803
|
+
await ensureGitRepo(cwd);
|
|
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;
|
|
875
812
|
}
|
|
876
|
-
async function
|
|
877
|
-
const opts = typeof options === "string" ? { message: options } : options;
|
|
813
|
+
async function commitWorkspaceReposIfDirty(workdir, message) {
|
|
878
814
|
const workdirPath = resolveWorkdirPath(workdir);
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
opts.reposManifest
|
|
882
|
-
);
|
|
815
|
+
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
816
|
+
const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
883
817
|
let committed = 0;
|
|
884
|
-
let commitSkipped = 0;
|
|
885
|
-
let pushed = 0;
|
|
886
|
-
let pushSkipped = 0;
|
|
887
|
-
const failed = [];
|
|
888
|
-
const message = opts.message;
|
|
889
818
|
for (const gitRoot of repoRoots) {
|
|
890
819
|
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
console.log(`[apm] \u5DF2\u63D0\u4EA4 @ ${label}`);
|
|
896
|
-
} else {
|
|
897
|
-
commitSkipped += 1;
|
|
898
|
-
}
|
|
899
|
-
} catch (err) {
|
|
900
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
901
|
-
console.error(`[apm] \u4ED3\u5E93 ${label} \u63D0\u4EA4\u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
|
|
902
|
-
failed.push(label);
|
|
903
|
-
continue;
|
|
904
|
-
}
|
|
905
|
-
try {
|
|
906
|
-
const didPush = await pushCurrentBranch(gitRoot);
|
|
907
|
-
if (didPush) {
|
|
908
|
-
pushed += 1;
|
|
909
|
-
console.log(`[apm] \u5DF2 push @ ${label}`);
|
|
910
|
-
} else {
|
|
911
|
-
pushSkipped += 1;
|
|
912
|
-
}
|
|
913
|
-
} catch (err) {
|
|
914
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
915
|
-
console.error(`[apm] \u4ED3\u5E93 ${label} push \u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
|
|
916
|
-
failed.push(label);
|
|
820
|
+
const did = await commitWorkingTreeIfDirty(gitRoot, message);
|
|
821
|
+
if (did) {
|
|
822
|
+
committed += 1;
|
|
823
|
+
console.log(`[apm] WebIDE \u515C\u5E95\u63D0\u4EA4 @ ${label}`);
|
|
917
824
|
}
|
|
918
825
|
}
|
|
919
|
-
return
|
|
920
|
-
committed,
|
|
921
|
-
commitSkipped,
|
|
922
|
-
pushed,
|
|
923
|
-
pushSkipped,
|
|
924
|
-
failed,
|
|
925
|
-
repos: repoRoots
|
|
926
|
-
};
|
|
826
|
+
return committed;
|
|
927
827
|
}
|
|
928
828
|
async function pushWorkspaceRepos(workdir) {
|
|
929
829
|
const workdirPath = resolveWorkdirPath(workdir);
|
|
@@ -952,14 +852,59 @@ function branchNameForTask(taskId) {
|
|
|
952
852
|
}
|
|
953
853
|
return `${TASK_BRANCH_PREFIX}${id}`;
|
|
954
854
|
}
|
|
955
|
-
async function
|
|
855
|
+
async function ensureFeatureBranch(branch, baselineBranch, options) {
|
|
956
856
|
const cwd = options.cwd ?? process.cwd();
|
|
957
857
|
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
958
|
-
await
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
858
|
+
await ensureGitRepo(gitRoot);
|
|
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}`);
|
|
904
|
+
return branch;
|
|
905
|
+
}
|
|
906
|
+
async function runTaskBranch(taskId, options = {}) {
|
|
907
|
+
const trimmedTaskId = taskId.trim();
|
|
963
908
|
if (!trimmedTaskId) {
|
|
964
909
|
throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
965
910
|
}
|
|
@@ -968,93 +913,75 @@ async function resolveProjectIdForTask(taskId) {
|
|
|
968
913
|
const projectBaseline = await api.cli.projectBaseBranch({
|
|
969
914
|
taskId: trimmedTaskId
|
|
970
915
|
});
|
|
971
|
-
const
|
|
972
|
-
if (!
|
|
973
|
-
throw new Error("[apm] \u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F
|
|
916
|
+
const baselineBranch = projectBaseline.baseBranch?.trim() ?? "";
|
|
917
|
+
if (!baselineBranch) {
|
|
918
|
+
throw new Error("[apm] \u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F\uFF0C\u65E0\u6CD5\u521B\u5EFA WebIDE \u7279\u6027\u5206\u652F");
|
|
974
919
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
if (manifestPath2 && !isWorkspaceReposManifestFile(manifestPath2)) {
|
|
983
|
-
const entries = loadHomeRepositoryEntries(manifestPath2);
|
|
984
|
-
const roots2 = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
|
|
920
|
+
const cwd = options.cwd ?? process.cwd();
|
|
921
|
+
const workdirPath = resolveWorkdirPath(cwd);
|
|
922
|
+
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
923
|
+
const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
924
|
+
const branch = branchNameForTask(trimmedTaskId);
|
|
925
|
+
for (const gitRoot of repoRoots) {
|
|
926
|
+
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
985
927
|
console.log(
|
|
986
|
-
`[apm] \
|
|
928
|
+
`[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (project baseline=${baselineBranch})`
|
|
987
929
|
);
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
if (!existsSync4(fsPath)) {
|
|
993
|
-
throw new Error(`[apm] \u672A\u627E\u5230\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 ${manifestPath2}`);
|
|
994
|
-
}
|
|
995
|
-
console.log(`[apm] \u4F7F\u7528\u663E\u5F0F\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 ${manifestPath2}`);
|
|
930
|
+
await ensureFeatureBranch(branch, baselineBranch, {
|
|
931
|
+
...options,
|
|
932
|
+
cwd: gitRoot
|
|
933
|
+
});
|
|
996
934
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
);
|
|
1004
|
-
return roots;
|
|
935
|
+
return {
|
|
936
|
+
branch,
|
|
937
|
+
baseline: baselineBranch,
|
|
938
|
+
kind: manifest.kind,
|
|
939
|
+
repos: repoRoots
|
|
940
|
+
};
|
|
1005
941
|
}
|
|
1006
|
-
async function
|
|
1007
|
-
const
|
|
1008
|
-
if (!
|
|
1009
|
-
throw new Error("[apm]
|
|
1010
|
-
}
|
|
1011
|
-
const trimmedProjectId = projectId.trim();
|
|
1012
|
-
if (!trimmedProjectId) {
|
|
1013
|
-
throw new Error("[apm] projectId\uFF08\u9879\u76EE ID\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
|
|
1014
|
-
}
|
|
1015
|
-
if (/[\s\\]/.test(trimmedProjectId) || trimmedProjectId.includes("..")) {
|
|
1016
|
-
throw new Error("[apm] \u9879\u76EE ID \u975E\u6CD5");
|
|
942
|
+
async function checkoutBaselineBranches(baselineBranch, options = {}) {
|
|
943
|
+
const baseline = baselineBranch.trim().replace(/^origin\//, "");
|
|
944
|
+
if (!baseline) {
|
|
945
|
+
throw new Error("[apm] \u57FA\u7EBF\u5206\u652F\u4E0D\u80FD\u4E3A\u7A7A");
|
|
1017
946
|
}
|
|
1018
947
|
const cwd = options.cwd ?? process.cwd();
|
|
1019
948
|
const workdirPath = resolveWorkdirPath(cwd);
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
options.reposManifest
|
|
1023
|
-
);
|
|
1024
|
-
if (repoRoots.length === 0) {
|
|
1025
|
-
throw new Error("[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7279\u6027\u5206\u652F");
|
|
1026
|
-
}
|
|
1027
|
-
const branch = branchNameForTask(trimmedTaskId);
|
|
1028
|
-
const repoLabels = repoRoots.map(
|
|
1029
|
-
(gitRoot) => formatRepoLabel(workdirPath, gitRoot)
|
|
1030
|
-
);
|
|
1031
|
-
console.log(
|
|
1032
|
-
`[apm] \u591A\u4ED3\u786E\u8BA4\u7279\u6027\u5206\u652F ${branch} projectId=${trimmedProjectId} repos=${repoLabels.join(",")}`
|
|
1033
|
-
);
|
|
949
|
+
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
950
|
+
const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
1034
951
|
for (const gitRoot of repoRoots) {
|
|
1035
952
|
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
1036
|
-
console.log(
|
|
1037
|
-
|
|
1038
|
-
);
|
|
1039
|
-
try {
|
|
1040
|
-
await ensureTaskFeatureBranch(branch, trimmedProjectId, {
|
|
1041
|
-
...options,
|
|
1042
|
-
cwd: gitRoot
|
|
1043
|
-
});
|
|
1044
|
-
} catch (err) {
|
|
1045
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
1046
|
-
throw new Error(
|
|
1047
|
-
`[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}`
|
|
1048
|
-
);
|
|
1049
|
-
}
|
|
953
|
+
console.log(`[apm] \u5207\u6362\u57FA\u7EBF\u5206\u652F ${baseline} @ ${label}`);
|
|
954
|
+
await checkoutBaselineBranch(baseline, { ...options, cwd: gitRoot });
|
|
1050
955
|
}
|
|
1051
956
|
return {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
kind: "multi",
|
|
957
|
+
baseline,
|
|
958
|
+
kind: manifest.kind,
|
|
1055
959
|
repos: repoRoots
|
|
1056
960
|
};
|
|
1057
961
|
}
|
|
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
|
+
}
|
|
1058
985
|
|
|
1059
986
|
// src/commands/connect/cursor-agent.ts
|
|
1060
987
|
import {
|
|
@@ -1704,8 +1631,8 @@ function createUpsertWebIdeTestCasesTool(options) {
|
|
|
1704
1631
|
}
|
|
1705
1632
|
|
|
1706
1633
|
// src/commands/connect/tools/mysql-tools.ts
|
|
1707
|
-
import { existsSync as
|
|
1708
|
-
import { isAbsolute, relative as relative2, resolve as
|
|
1634
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
1635
|
+
import { isAbsolute, relative as relative2, resolve as resolve4 } from "node:path";
|
|
1709
1636
|
import mysql from "mysql2/promise";
|
|
1710
1637
|
var MAX_RESULT_ROWS = 100;
|
|
1711
1638
|
var EXEC_TIMEOUT_MS = 6e4;
|
|
@@ -1776,14 +1703,14 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
|
|
|
1776
1703
|
if (!isAbsolute(absolutePath)) {
|
|
1777
1704
|
return { ok: false, error: "path \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
|
|
1778
1705
|
}
|
|
1779
|
-
const workdirAbs =
|
|
1780
|
-
const targetAbs =
|
|
1706
|
+
const workdirAbs = resolve4(toFsPath(workdir));
|
|
1707
|
+
const targetAbs = resolve4(toFsPath(absolutePath));
|
|
1781
1708
|
const rel = relative2(workdirAbs, targetAbs);
|
|
1782
1709
|
if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
|
|
1783
1710
|
return { ok: false, error: "path \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
|
|
1784
1711
|
}
|
|
1785
1712
|
const fsPath = toFsPath(targetAbs);
|
|
1786
|
-
if (!
|
|
1713
|
+
if (!existsSync3(fsPath)) {
|
|
1787
1714
|
return { ok: false, error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${absolutePath}` };
|
|
1788
1715
|
}
|
|
1789
1716
|
let st;
|
|
@@ -1805,7 +1732,7 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
|
|
|
1805
1732
|
return { ok: true, absPath: targetAbs, displayPath };
|
|
1806
1733
|
}
|
|
1807
1734
|
function readSqlFile(absPath) {
|
|
1808
|
-
return
|
|
1735
|
+
return readFileSync3(toFsPath(absPath), "utf8");
|
|
1809
1736
|
}
|
|
1810
1737
|
function createMysqlExecuteTool(options) {
|
|
1811
1738
|
const { taskId, executionId, workdir, upsertSqlExecution } = options;
|
|
@@ -1983,1214 +1910,2371 @@ function createMysqlExecuteTool(options) {
|
|
|
1983
1910
|
};
|
|
1984
1911
|
}
|
|
1985
1912
|
|
|
1986
|
-
// src/commands/connect/tools/
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1913
|
+
// src/commands/connect/tools/package-sql-tool.ts
|
|
1914
|
+
import { existsSync as existsSync4, statSync as statSync4 } from "node:fs";
|
|
1915
|
+
import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve5 } from "node:path";
|
|
1916
|
+
|
|
1917
|
+
// src/commands/connect/sync-package-sql.ts
|
|
1918
|
+
import { createHash } from "node:crypto";
|
|
1919
|
+
import fs from "node:fs";
|
|
1920
|
+
import path from "node:path";
|
|
1921
|
+
|
|
1922
|
+
// src/utils/minio.ts
|
|
1923
|
+
import * as Minio from "minio";
|
|
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
|
+
});
|
|
1997
1935
|
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
const enableAsk = options.enableAskQuestion !== false;
|
|
2003
|
-
const enablePlan = options.enableWebIdePlanTools === true;
|
|
2004
|
-
const enableMysql = options.enableMysqlTools === true;
|
|
2005
|
-
const tools = {
|
|
2006
|
-
...wantsTool("AppendMessage", options, enableAppend) && options.appendMessageContent ? createAppendMessageCustomTools({
|
|
2007
|
-
appendContent: options.appendMessageContent
|
|
2008
|
-
}) : {},
|
|
2009
|
-
...wantsTool("AskQuestion", options, enableAsk) ? createAskQuestionTool({
|
|
2010
|
-
onInvoke: options?.onAskQuestion,
|
|
2011
|
-
execute: options?.askQuestionExecute
|
|
2012
|
-
}) : {}
|
|
2013
|
-
};
|
|
2014
|
-
if (options?.taskId && options.workdir) {
|
|
2015
|
-
const { cli } = createApmApiClient(cfg2);
|
|
2016
|
-
const taskId = options.taskId;
|
|
2017
|
-
if (wantsTool("RecommendPlan", options, enablePlan)) {
|
|
2018
|
-
Object.assign(
|
|
2019
|
-
tools,
|
|
2020
|
-
createRecommendPlanTool({
|
|
2021
|
-
webideRecommendPlan: (args) => cli.webideRecommendPlan({ taskId, ...args })
|
|
2022
|
-
})
|
|
2023
|
-
);
|
|
2024
|
-
}
|
|
2025
|
-
if (wantsTool("UpsertWebIdePlan", options, enablePlan)) {
|
|
2026
|
-
Object.assign(
|
|
2027
|
-
tools,
|
|
2028
|
-
createUpsertWebIdePlanTool({
|
|
2029
|
-
webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
|
|
2030
|
-
})
|
|
2031
|
-
);
|
|
2032
|
-
}
|
|
2033
|
-
if (wantsTool("UpsertWebIdeChecklist", options, enablePlan)) {
|
|
2034
|
-
Object.assign(
|
|
2035
|
-
tools,
|
|
2036
|
-
createUpsertWebIdeChecklistTool({
|
|
2037
|
-
webideUpsertChecklist: (args) => cli.webideUpsertChecklist({ taskId, ...args })
|
|
2038
|
-
})
|
|
2039
|
-
);
|
|
2040
|
-
}
|
|
2041
|
-
if (wantsTool("GetWebIdeChecklist", options, enablePlan)) {
|
|
2042
|
-
Object.assign(
|
|
2043
|
-
tools,
|
|
2044
|
-
createGetWebIdeChecklistTool({
|
|
2045
|
-
boundTaskId: taskId,
|
|
2046
|
-
webideGetChecklist: (args) => cli.webideGetChecklist(args)
|
|
2047
|
-
})
|
|
2048
|
-
);
|
|
2049
|
-
}
|
|
2050
|
-
if (wantsTool("UpsertWebIdeTestCases", options, enablePlan)) {
|
|
2051
|
-
Object.assign(
|
|
2052
|
-
tools,
|
|
2053
|
-
createUpsertWebIdeTestCasesTool({
|
|
2054
|
-
webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
|
|
2055
|
-
})
|
|
2056
|
-
);
|
|
2057
|
-
}
|
|
2058
|
-
if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
|
|
2059
|
-
Object.assign(
|
|
2060
|
-
tools,
|
|
2061
|
-
createMergeWebIdePullRequestsTool({
|
|
2062
|
-
webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
|
|
2063
|
-
})
|
|
2064
|
-
);
|
|
2065
|
-
}
|
|
2066
|
-
if (wantsTool("MysqlExecute", options, enableMysql) && options.sqlExecutionId && options.workdir) {
|
|
2067
|
-
Object.assign(
|
|
2068
|
-
tools,
|
|
2069
|
-
createMysqlExecuteTool({
|
|
2070
|
-
taskId,
|
|
2071
|
-
executionId: options.sqlExecutionId,
|
|
2072
|
-
workdir: options.workdir,
|
|
2073
|
-
upsertSqlExecution: (args) => cli.webideUpsertSqlExecution(args)
|
|
2074
|
-
})
|
|
2075
|
-
);
|
|
1936
|
+
async ensureBucket(bucket) {
|
|
1937
|
+
const exists = await this.inner.bucketExists(bucket);
|
|
1938
|
+
if (!exists) {
|
|
1939
|
+
await this.inner.makeBucket(bucket);
|
|
2076
1940
|
}
|
|
2077
1941
|
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
function withPlanModeToolHint(prompt, mode) {
|
|
2081
|
-
if (mode !== "plan") {
|
|
2082
|
-
return prompt;
|
|
1942
|
+
async putObject(bucket, objectKey, body, meta) {
|
|
1943
|
+
await this.inner.putObject(bucket, objectKey, body, body.length, meta);
|
|
2083
1944
|
}
|
|
2084
|
-
|
|
1945
|
+
/** 大文件:从本地路径流式上传,避免整包读入内存 */
|
|
1946
|
+
async fPutObject(bucket, objectKey, filePath, meta) {
|
|
1947
|
+
await this.inner.fPutObject(bucket, objectKey, filePath, meta);
|
|
1948
|
+
}
|
|
1949
|
+
async listObjects(bucket, prefix) {
|
|
1950
|
+
const objects = [];
|
|
1951
|
+
const stream = this.inner.listObjectsV2(bucket, prefix, true);
|
|
1952
|
+
await new Promise((resolve8, reject) => {
|
|
1953
|
+
stream.on("data", (obj) => {
|
|
1954
|
+
if (!obj.name) return;
|
|
1955
|
+
objects.push({
|
|
1956
|
+
name: obj.name,
|
|
1957
|
+
size: typeof obj.size === "number" ? obj.size : 0,
|
|
1958
|
+
lastModified: obj.lastModified ?? null,
|
|
1959
|
+
...typeof obj.etag === "string" ? { etag: obj.etag } : {}
|
|
1960
|
+
});
|
|
1961
|
+
});
|
|
1962
|
+
stream.on("error", reject);
|
|
1963
|
+
stream.on("end", () => resolve8());
|
|
1964
|
+
});
|
|
1965
|
+
return objects;
|
|
1966
|
+
}
|
|
1967
|
+
async statObject(bucket, objectKey) {
|
|
1968
|
+
const stat = await this.inner.statObject(bucket, objectKey);
|
|
1969
|
+
return {
|
|
1970
|
+
size: typeof stat.size === "number" ? stat.size : 0,
|
|
1971
|
+
metaData: stat.metaData
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
async removeObject(bucket, objectKey) {
|
|
1975
|
+
await this.inner.removeObject(bucket, objectKey);
|
|
1976
|
+
}
|
|
1977
|
+
async getObjectBuffer(bucket, objectKey) {
|
|
1978
|
+
const stream = await this.inner.getObject(bucket, objectKey);
|
|
1979
|
+
const chunks = [];
|
|
1980
|
+
for await (const chunk of stream) {
|
|
1981
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1982
|
+
}
|
|
1983
|
+
return Buffer.concat(chunks);
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
2085
1986
|
|
|
2086
|
-
|
|
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);
|
|
2087
1991
|
}
|
|
2088
|
-
function
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
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
|
+
};
|
|
2092
2005
|
}
|
|
2093
2006
|
|
|
2094
|
-
// src/commands/connect/
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
return
|
|
2110
|
-
schemaVersion: 1,
|
|
2111
|
-
rootBlobId: row.rootBlobId
|
|
2112
|
-
};
|
|
2007
|
+
// src/commands/connect/sync-package-sql.ts
|
|
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");
|
|
2113
2023
|
}
|
|
2114
|
-
function
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
return
|
|
2118
|
-
schemaVersion: ref.schemaVersion,
|
|
2119
|
-
rootBlobId: ref.rootBlobId
|
|
2120
|
-
};
|
|
2024
|
+
function sha256File(absPath) {
|
|
2025
|
+
const hash = createHash("sha256");
|
|
2026
|
+
hash.update(fs.readFileSync(absPath));
|
|
2027
|
+
return hash.digest("hex");
|
|
2121
2028
|
}
|
|
2122
|
-
function
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2029
|
+
function normalizeSqlRepoName(repo) {
|
|
2030
|
+
const trimmed = repo.trim().replace(/\\/g, "/");
|
|
2031
|
+
if (!trimmed) {
|
|
2032
|
+
throw new Error("repo \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2033
|
+
}
|
|
2034
|
+
if (trimmed.includes("/") || trimmed.includes("..") || trimmed === "." || trimmed.startsWith(".")) {
|
|
2035
|
+
throw new Error(`\u65E0\u6548\u7684 repo \u540D: ${repo}`);
|
|
2036
|
+
}
|
|
2037
|
+
return trimmed;
|
|
2038
|
+
}
|
|
2039
|
+
function scanSqlFilesUnderDir(localDir) {
|
|
2040
|
+
const root = path.resolve(toFsPath(localDir));
|
|
2041
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
2042
|
+
throw new Error(`localDir \u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${localDir}`);
|
|
2043
|
+
}
|
|
2044
|
+
const byRelative = /* @__PURE__ */ new Map();
|
|
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
|
+
}
|
|
2133
2072
|
};
|
|
2073
|
+
walk(root);
|
|
2074
|
+
return [...byRelative.values()].sort(
|
|
2075
|
+
(a, b) => a.relativePath.localeCompare(b.relativePath)
|
|
2076
|
+
);
|
|
2134
2077
|
}
|
|
2135
|
-
function
|
|
2078
|
+
function packageSqlObjectKey(projectId, repo, relativePath) {
|
|
2079
|
+
return `packages/${projectId}/${repo}/${relativePath}`;
|
|
2080
|
+
}
|
|
2081
|
+
function packageSqlRepoPrefix(projectId, repo) {
|
|
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
|
+
}
|
|
2136
2143
|
return {
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
status: row.status,
|
|
2142
|
-
model: row.model ?? null,
|
|
2143
|
-
result: row.result,
|
|
2144
|
-
error: row.error,
|
|
2145
|
-
usageRef: row.usageRef,
|
|
2146
|
-
usage: row.usage ?? null,
|
|
2147
|
-
createdAt: row.createdAt,
|
|
2148
|
-
updatedAt: row.updatedAt,
|
|
2149
|
-
startedAt: row.startedAt,
|
|
2150
|
-
endedAt: row.endedAt,
|
|
2151
|
-
startCheckpointRef: asCheckpointRef(row.startCheckpointRef) ?? null,
|
|
2152
|
-
latestCheckpointRef: asCheckpointRef(row.latestCheckpointRef) ?? null
|
|
2144
|
+
scanned: localFiles.length,
|
|
2145
|
+
uploaded,
|
|
2146
|
+
skipped,
|
|
2147
|
+
deleted
|
|
2153
2148
|
};
|
|
2154
2149
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
async delete() {
|
|
2164
|
-
},
|
|
2165
|
-
async list() {
|
|
2166
|
-
return { items: [] };
|
|
2150
|
+
|
|
2151
|
+
// src/commands/connect/tools/package-sql-tool.ts
|
|
2152
|
+
function asString4(value) {
|
|
2153
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2154
|
+
}
|
|
2155
|
+
function resolveLocalSqlDir(workdir, localDirArg) {
|
|
2156
|
+
if (!isAbsolute2(localDirArg)) {
|
|
2157
|
+
return { ok: false, error: "localDir \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
|
|
2167
2158
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
async list() {
|
|
2174
|
-
return { items: [] };
|
|
2175
|
-
},
|
|
2176
|
-
async delete() {
|
|
2159
|
+
const workdirAbs = resolve5(toFsPath(workdir));
|
|
2160
|
+
const targetAbs = resolve5(toFsPath(localDirArg));
|
|
2161
|
+
const rel = relative3(workdirAbs, targetAbs);
|
|
2162
|
+
if (!rel || rel.startsWith("..") || isAbsolute2(rel)) {
|
|
2163
|
+
return { ok: false, error: "localDir \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
|
|
2177
2164
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
async function upsertAgent(agent) {
|
|
2182
|
-
const row = await api.cli.cursorStoreUpsertAgent({
|
|
2183
|
-
agentId: agent.agentId,
|
|
2184
|
-
cwd: agent.cwd,
|
|
2185
|
-
status: agent.status,
|
|
2186
|
-
activeRunId: agent.activeRunId ?? null,
|
|
2187
|
-
name: agent.name ?? null,
|
|
2188
|
-
latestCheckpoint: toApiCheckpointRef(agent.latestCheckpoint) ?? null,
|
|
2189
|
-
sdkMetadata: agent.sdkMetadata ?? null,
|
|
2190
|
-
createdAt: agent.createdAt,
|
|
2191
|
-
updatedAt: agent.updatedAt
|
|
2192
|
-
});
|
|
2193
|
-
return fromApiAgent(row);
|
|
2165
|
+
const fsPath = toFsPath(targetAbs);
|
|
2166
|
+
if (!existsSync4(fsPath)) {
|
|
2167
|
+
return { ok: false, error: `\u76EE\u5F55\u4E0D\u5B58\u5728: ${localDirArg}` };
|
|
2194
2168
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
status: run.status,
|
|
2202
|
-
model: run.model ?? null,
|
|
2203
|
-
result: run.result ?? null,
|
|
2204
|
-
error: run.error ?? null,
|
|
2205
|
-
usageRef: run.usageRef ?? null,
|
|
2206
|
-
usage: run.usage ?? null,
|
|
2207
|
-
startCheckpointRef: toApiCheckpointRef(run.startCheckpointRef) ?? null,
|
|
2208
|
-
latestCheckpointRef: toApiCheckpointRef(run.latestCheckpointRef) ?? null,
|
|
2209
|
-
createdAt: run.createdAt,
|
|
2210
|
-
updatedAt: run.updatedAt,
|
|
2211
|
-
startedAt: run.startedAt ?? null,
|
|
2212
|
-
endedAt: run.endedAt ?? null
|
|
2213
|
-
});
|
|
2214
|
-
return fromApiRun(row);
|
|
2169
|
+
try {
|
|
2170
|
+
if (!statSync4(fsPath).isDirectory()) {
|
|
2171
|
+
return { ok: false, error: "localDir \u5FC5\u987B\u662F\u76EE\u5F55" };
|
|
2172
|
+
}
|
|
2173
|
+
} catch {
|
|
2174
|
+
return { ok: false, error: `\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55: ${localDirArg}` };
|
|
2215
2175
|
}
|
|
2176
|
+
return { ok: true, absPath: targetAbs };
|
|
2177
|
+
}
|
|
2178
|
+
function createUploadPackageSqlTool(options) {
|
|
2179
|
+
const { cfg: cfg2, workdir, projectId } = options;
|
|
2216
2180
|
return {
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2181
|
+
UploadPackageSql: {
|
|
2182
|
+
description: "Upload SQL scripts from a local directory to project MinIO mirror. Call after reading deploy docs: pass repo (directory name from\u300C\u8FDB\u5165 xxx \u76EE\u5F55\u300D) and localDir (absolute path to the SQL script directory). Syncs **/*.sql under localDir to packages/{projectId}/{repo}/.... Do not put SQL into jars.zip.",
|
|
2183
|
+
inputSchema: {
|
|
2184
|
+
type: "object",
|
|
2185
|
+
properties: {
|
|
2186
|
+
repo: {
|
|
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"]
|
|
2229
2196
|
},
|
|
2230
|
-
async
|
|
2231
|
-
const
|
|
2232
|
-
const
|
|
2233
|
-
if (
|
|
2234
|
-
|
|
2235
|
-
|
|
2197
|
+
execute: async (args) => {
|
|
2198
|
+
const repoRaw = asString4(args.repo);
|
|
2199
|
+
const localDirRaw = asString4(args.localDir);
|
|
2200
|
+
if (!repoRaw) {
|
|
2201
|
+
return {
|
|
2202
|
+
content: [{ type: "text", text: "repo \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
2203
|
+
isError: true
|
|
2204
|
+
};
|
|
2236
2205
|
}
|
|
2237
|
-
if (
|
|
2238
|
-
|
|
2239
|
-
|
|
2206
|
+
if (!localDirRaw) {
|
|
2207
|
+
return {
|
|
2208
|
+
content: [{ type: "text", text: "localDir \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
2209
|
+
isError: true
|
|
2210
|
+
};
|
|
2240
2211
|
}
|
|
2241
|
-
let
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
});
|
|
2251
|
-
if (!page.nextCursor) break;
|
|
2252
|
-
cursor = page.nextCursor;
|
|
2212
|
+
let repo;
|
|
2213
|
+
try {
|
|
2214
|
+
repo = normalizeSqlRepoName(repoRaw);
|
|
2215
|
+
} catch (err) {
|
|
2216
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
2217
|
+
return {
|
|
2218
|
+
content: [{ type: "text", text: detail }],
|
|
2219
|
+
isError: true
|
|
2220
|
+
};
|
|
2253
2221
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
});
|
|
2261
|
-
const result = {
|
|
2262
|
-
items: page.items.map(fromApiAgent),
|
|
2263
|
-
...page.nextCursor ? { nextCursor: page.nextCursor } : {}
|
|
2264
|
-
};
|
|
2265
|
-
return result;
|
|
2266
|
-
}
|
|
2267
|
-
},
|
|
2268
|
-
runs: {
|
|
2269
|
-
async get(input) {
|
|
2270
|
-
const page = await api.cli.cursorStoreListRuns({
|
|
2271
|
-
runIds: [input.runId],
|
|
2272
|
-
limit: 1
|
|
2273
|
-
});
|
|
2274
|
-
const row = page.items[0];
|
|
2275
|
-
return row ? fromApiRun(row) : null;
|
|
2276
|
-
},
|
|
2277
|
-
async create(input) {
|
|
2278
|
-
return upsertRun(input.run);
|
|
2279
|
-
},
|
|
2280
|
-
async update(input) {
|
|
2281
|
-
return upsertRun(input.run);
|
|
2282
|
-
},
|
|
2283
|
-
async delete(input) {
|
|
2284
|
-
const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
|
|
2285
|
-
const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
|
|
2286
|
-
if (agentIds && agentIds.length > 0 || runIds && runIds.length > 0) {
|
|
2287
|
-
await api.cli.cursorStoreDeleteRuns({ agentIds, runIds });
|
|
2288
|
-
return;
|
|
2222
|
+
const resolved = resolveLocalSqlDir(workdir, localDirRaw);
|
|
2223
|
+
if (!resolved.ok) {
|
|
2224
|
+
return {
|
|
2225
|
+
content: [{ type: "text", text: resolved.error }],
|
|
2226
|
+
isError: true
|
|
2227
|
+
};
|
|
2289
2228
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
if (page.items.length === 0) break;
|
|
2297
|
-
await api.cli.cursorStoreDeleteRuns({
|
|
2298
|
-
runIds: page.items.map((item) => item.runId)
|
|
2229
|
+
try {
|
|
2230
|
+
const result = await syncPackageSqlToMinio({
|
|
2231
|
+
cfg: cfg2,
|
|
2232
|
+
projectId,
|
|
2233
|
+
repo,
|
|
2234
|
+
localDir: resolved.absPath
|
|
2299
2235
|
});
|
|
2300
|
-
|
|
2301
|
-
|
|
2236
|
+
return `SQL \u5DF2\u540C\u6B65 repo=${repo} scanned=${result.scanned} uploaded=${result.uploaded} skipped=${result.skipped} deleted=${result.deleted}`;
|
|
2237
|
+
} catch (err) {
|
|
2238
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
2239
|
+
return {
|
|
2240
|
+
content: [{ type: "text", text: `\u4E0A\u4F20 SQL \u5931\u8D25: ${detail}` }],
|
|
2241
|
+
isError: true
|
|
2242
|
+
};
|
|
2302
2243
|
}
|
|
2303
|
-
},
|
|
2304
|
-
async list(input) {
|
|
2305
|
-
const page = await api.cli.cursorStoreListRuns({
|
|
2306
|
-
agentIds: input?.filter?.agentIds ? [...input.filter.agentIds] : void 0,
|
|
2307
|
-
runIds: input?.filter?.runIds ? [...input.filter.runIds] : void 0,
|
|
2308
|
-
cursor: input?.filter?.cursor,
|
|
2309
|
-
limit: input?.filter?.limit
|
|
2310
|
-
});
|
|
2311
|
-
const result = {
|
|
2312
|
-
items: page.items.map(fromApiRun),
|
|
2313
|
-
...page.nextCursor ? { nextCursor: page.nextCursor } : {}
|
|
2314
|
-
};
|
|
2315
|
-
return result;
|
|
2316
2244
|
}
|
|
2317
|
-
}
|
|
2318
|
-
checkpoints: unsupportedCheckpoints,
|
|
2319
|
-
runEvents: unsupportedRunEvents
|
|
2245
|
+
}
|
|
2320
2246
|
};
|
|
2321
2247
|
}
|
|
2322
2248
|
|
|
2323
|
-
// src/commands/connect/
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
const cwd = workspaceRef.trim();
|
|
2327
|
-
if (!cwd) throw new Error("openSqliteLocalAgentStore: workspaceRef \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2328
|
-
const existing = sqliteByKey.get(cwd);
|
|
2329
|
-
if (existing) return existing;
|
|
2330
|
-
const pending2 = SqliteLocalAgentStore.open({ workspaceRef: cwd }).then(
|
|
2331
|
-
(store) => {
|
|
2332
|
-
console.log(
|
|
2333
|
-
`[apm] Cursor store sqlite ready cwd=${cwd} stateRoot=${store.stateRoot}`
|
|
2334
|
-
);
|
|
2335
|
-
return store;
|
|
2336
|
-
}
|
|
2337
|
-
);
|
|
2338
|
-
sqliteByKey.set(cwd, pending2);
|
|
2339
|
-
return pending2;
|
|
2340
|
-
}
|
|
2341
|
-
function jsonSafePayload(value) {
|
|
2342
|
-
if (value == null) return null;
|
|
2343
|
-
try {
|
|
2344
|
-
return JSON.parse(JSON.stringify(value));
|
|
2345
|
-
} catch {
|
|
2346
|
-
return null;
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
function mirrorRunEventsForVisualization(cfg2, local) {
|
|
2350
|
-
const api = createApmApiClient(cfg2);
|
|
2249
|
+
// src/commands/connect/tools/package-status-tool.ts
|
|
2250
|
+
function createSetPackageFailedTool(options) {
|
|
2251
|
+
const { packageId, setFailed } = options;
|
|
2351
2252
|
return {
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
} catch (err) {
|
|
2383
|
-
console.warn(
|
|
2384
|
-
`[apm] runEvents \u53EF\u89C6\u5316\u6E05\u7406\u8DF3\u8FC7:`,
|
|
2385
|
-
err instanceof Error ? err.message : err
|
|
2386
|
-
);
|
|
2253
|
+
SetPackageFailed: {
|
|
2254
|
+
description: "\u6253\u5305\u5931\u8D25\u65F6\u8C03\u7528\uFF1A\u5C06\u5F53\u524D\u6253\u5305\u8BB0\u5F55\u6807\u4E3A FAILED\uFF0C\u5E76\u5199\u5165\u5931\u8D25\u539F\u56E0\u3002\u6784\u5EFA\u5931\u8D25\u3001\u7F3A\u4EA7\u7269\u3001\u6587\u6863\u7F3A\u5931\u7B49\u5747\u5E94\u8C03\u7528\uFF1B\u6210\u529F\u65F6\u4E0D\u8981\u8C03\u7528\u3002",
|
|
2255
|
+
inputSchema: {
|
|
2256
|
+
type: "object",
|
|
2257
|
+
properties: {
|
|
2258
|
+
error: {
|
|
2259
|
+
type: "string",
|
|
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
|
+
}
|
|
2387
2283
|
}
|
|
2388
2284
|
}
|
|
2389
2285
|
};
|
|
2390
2286
|
}
|
|
2391
|
-
async function createApmCursorAgentStore(cfg2, workspaceRef) {
|
|
2392
|
-
const cwd = workspaceRef.trim();
|
|
2393
|
-
const [sqlite, remote] = await Promise.all([
|
|
2394
|
-
openSqliteLocalAgentStore(cwd),
|
|
2395
|
-
Promise.resolve(createRemoteLocalAgentStore(cfg2))
|
|
2396
|
-
]);
|
|
2397
|
-
return composeLocalAgentStore({
|
|
2398
|
-
agents: remote.agents,
|
|
2399
|
-
runs: remote.runs,
|
|
2400
|
-
checkpoints: sqlite.checkpoints,
|
|
2401
|
-
runEvents: mirrorRunEventsForVisualization(cfg2, sqlite.runEvents)
|
|
2402
|
-
});
|
|
2403
|
-
}
|
|
2404
|
-
|
|
2405
|
-
// src/commands/connect/core/worker/run-steer.ts
|
|
2406
|
-
var impl = null;
|
|
2407
|
-
var pending = null;
|
|
2408
|
-
function bindRunSteer(fn) {
|
|
2409
|
-
impl = fn;
|
|
2410
|
-
if (!pending) return;
|
|
2411
|
-
const text = pending;
|
|
2412
|
-
pending = null;
|
|
2413
|
-
void fn(text);
|
|
2414
|
-
}
|
|
2415
|
-
function clearRunSteer() {
|
|
2416
|
-
impl = null;
|
|
2417
|
-
pending = null;
|
|
2418
|
-
}
|
|
2419
|
-
function deliverRunSteer(text) {
|
|
2420
|
-
const trimmed = text.trim();
|
|
2421
|
-
if (!trimmed) return;
|
|
2422
|
-
if (impl) {
|
|
2423
|
-
void impl(trimmed);
|
|
2424
|
-
return;
|
|
2425
|
-
}
|
|
2426
|
-
pending = trimmed;
|
|
2427
|
-
}
|
|
2428
2287
|
|
|
2429
|
-
// src/commands/connect/
|
|
2430
|
-
var
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
options
|
|
2440
|
-
].filter((value, index, arr) => {
|
|
2441
|
-
if (!value) return false;
|
|
2442
|
-
return arr.indexOf(value) === index;
|
|
2443
|
-
});
|
|
2444
|
-
if (details.length === 0) {
|
|
2445
|
-
return `Cursor run \u5931\u8D25: ${runId}`;
|
|
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);
|
|
2446
2299
|
}
|
|
2447
|
-
return
|
|
2300
|
+
return legacyDefault;
|
|
2448
2301
|
}
|
|
2449
|
-
|
|
2450
|
-
const
|
|
2451
|
-
const
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
} : {}
|
|
2463
|
-
},
|
|
2464
|
-
...ctx.mode ? { mode: ctx.mode } : {}
|
|
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
|
+
}) : {}
|
|
2465
2315
|
};
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
err instanceof Error ? err.message : err
|
|
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
|
+
})
|
|
2476
2325
|
);
|
|
2477
|
-
ctx.onInvalidatePersistedAgentId?.();
|
|
2478
2326
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
return obtainAgent({ ...ctx, enableSandbox: false });
|
|
2327
|
+
if (wantsTool("UpsertWebIdePlan", options, enablePlan)) {
|
|
2328
|
+
Object.assign(
|
|
2329
|
+
tools,
|
|
2330
|
+
createUpsertWebIdePlanTool({
|
|
2331
|
+
webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
|
|
2332
|
+
})
|
|
2333
|
+
);
|
|
2487
2334
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
}
|
|
2496
|
-
const apiKey = ctx.apiKey.trim();
|
|
2497
|
-
if (!apiKey) {
|
|
2498
|
-
throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
|
|
2499
|
-
}
|
|
2500
|
-
const workdir = resolveWorkdirPath(ctx.workdir);
|
|
2501
|
-
const customTools = createCursorCustomTools(cfg2, {
|
|
2502
|
-
onAskQuestion: options.onAskQuestion,
|
|
2503
|
-
appendMessageContent: options.appendMessageContent,
|
|
2504
|
-
enableAppendMessage: options.enableAppendMessage,
|
|
2505
|
-
enableAskQuestion: options.enableAskQuestion,
|
|
2506
|
-
askQuestionExecute: options.askQuestionExecute,
|
|
2507
|
-
enableWebIdePlanTools: options.enableWebIdePlanTools,
|
|
2508
|
-
enableMysqlTools: options.enableMysqlTools,
|
|
2509
|
-
sqlExecutionId: options.sqlExecutionId,
|
|
2510
|
-
toolNames: options.toolNames,
|
|
2511
|
-
taskId: options.taskId,
|
|
2512
|
-
workdir
|
|
2513
|
-
});
|
|
2514
|
-
const enableSandbox = Boolean(options.enableSandbox);
|
|
2515
|
-
let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
|
|
2516
|
-
prompt = withWorkspaceBoundaryHint(prompt);
|
|
2517
|
-
console.log(
|
|
2518
|
-
`[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} cwd=${workdir} sandbox=${enableSandbox}`
|
|
2519
|
-
);
|
|
2520
|
-
const { agent, resumed } = await obtainAgent({
|
|
2521
|
-
cfg: cfg2,
|
|
2522
|
-
apiKey,
|
|
2523
|
-
model: ctx.model,
|
|
2524
|
-
cwd: workdir,
|
|
2525
|
-
user: ctx.user,
|
|
2526
|
-
mode: ctx.mode,
|
|
2527
|
-
resumeAgentId: ctx.resumeAgentId,
|
|
2528
|
-
customTools,
|
|
2529
|
-
enableSandbox,
|
|
2530
|
-
onInvalidatePersistedAgentId: options.onInvalidatePersistedAgentId
|
|
2531
|
-
});
|
|
2532
|
-
const invalidateAgentMapping = () => {
|
|
2533
|
-
options.onInvalidatePersistedAgentId?.();
|
|
2534
|
-
};
|
|
2535
|
-
const eventSession = options.eventSession ?? new EventSession(prompt);
|
|
2536
|
-
const syncRemoteLog = options.createRemoteLogSync ? options.createRemoteLogSync(agent.agentId) : noopRemoteLogSync;
|
|
2537
|
-
let activeRun;
|
|
2538
|
-
const abortRun = () => {
|
|
2539
|
-
if (!activeRun?.supports("cancel")) return;
|
|
2540
|
-
void activeRun.cancel().catch(() => void 0);
|
|
2541
|
-
};
|
|
2542
|
-
signal?.addEventListener("abort", abortRun, { once: true });
|
|
2543
|
-
try {
|
|
2544
|
-
const run = await agent.send(prompt, {
|
|
2545
|
-
...ctx.mode ? { mode: ctx.mode } : {},
|
|
2546
|
-
local: {
|
|
2547
|
-
...options.forceSend ? { force: true } : {},
|
|
2548
|
-
customTools
|
|
2549
|
-
}
|
|
2550
|
-
});
|
|
2551
|
-
activeRun = run;
|
|
2552
|
-
console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
|
|
2553
|
-
bindRunSteer(async (text) => {
|
|
2554
|
-
const steer = run.steer;
|
|
2555
|
-
if (!steer) {
|
|
2556
|
-
console.warn(`[apm] \u5F53\u524D run \u4E0D\u652F\u6301 steer runId=${run.id}`);
|
|
2557
|
-
return;
|
|
2558
|
-
}
|
|
2559
|
-
try {
|
|
2560
|
-
const outcome = await steer(text);
|
|
2561
|
-
console.log(`[apm] steer outcome=${outcome} runId=${run.id}`);
|
|
2562
|
-
} catch (err) {
|
|
2563
|
-
console.error(
|
|
2564
|
-
`[apm] steer \u5931\u8D25 runId=${run.id}:`,
|
|
2565
|
-
err instanceof Error ? err.message : err
|
|
2566
|
-
);
|
|
2567
|
-
}
|
|
2568
|
-
});
|
|
2569
|
-
await options.onRunStarted?.({ agentId: agent.agentId, runId: run.id });
|
|
2570
|
-
let lastRunErrorStatus;
|
|
2571
|
-
for await (const event of run.stream()) {
|
|
2572
|
-
if (signal?.aborted) {
|
|
2573
|
-
abortRun();
|
|
2574
|
-
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
2575
|
-
}
|
|
2576
|
-
if (event.type === "status" && event.status === "ERROR") {
|
|
2577
|
-
const message = event.message?.trim();
|
|
2578
|
-
if (message) {
|
|
2579
|
-
lastRunErrorStatus = message;
|
|
2580
|
-
console.error(
|
|
2581
|
-
`[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
|
|
2582
|
-
);
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
options.onStreamEvent?.(event);
|
|
2586
|
-
eventSession.addEvent(event);
|
|
2587
|
-
syncRemoteLog.schedule(eventSession);
|
|
2335
|
+
if (wantsTool("UpsertWebIdeChecklist", options, enablePlan)) {
|
|
2336
|
+
Object.assign(
|
|
2337
|
+
tools,
|
|
2338
|
+
createUpsertWebIdeChecklistTool({
|
|
2339
|
+
webideUpsertChecklist: (args) => cli.webideUpsertChecklist({ taskId, ...args })
|
|
2340
|
+
})
|
|
2341
|
+
);
|
|
2588
2342
|
}
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
console.log(
|
|
2597
|
-
`[apm] Cursor usage total=${result.usage.totalTokens} in=${result.usage.inputTokens} out=${result.usage.outputTokens} cache=${result.usage.cacheReadTokens}/${result.usage.cacheWriteTokens}`
|
|
2343
|
+
if (wantsTool("GetWebIdeChecklist", options, enablePlan)) {
|
|
2344
|
+
Object.assign(
|
|
2345
|
+
tools,
|
|
2346
|
+
createGetWebIdeChecklistTool({
|
|
2347
|
+
boundTaskId: taskId,
|
|
2348
|
+
webideGetChecklist: (args) => cli.webideGetChecklist(args)
|
|
2349
|
+
})
|
|
2598
2350
|
);
|
|
2599
2351
|
}
|
|
2600
|
-
if (
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
invalidateAgentMapping();
|
|
2608
|
-
}
|
|
2609
|
-
throw new Error(failureMessage);
|
|
2352
|
+
if (wantsTool("UpsertWebIdeTestCases", options, enablePlan)) {
|
|
2353
|
+
Object.assign(
|
|
2354
|
+
tools,
|
|
2355
|
+
createUpsertWebIdeTestCasesTool({
|
|
2356
|
+
webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
|
|
2357
|
+
})
|
|
2358
|
+
);
|
|
2610
2359
|
}
|
|
2611
|
-
if (
|
|
2612
|
-
|
|
2360
|
+
if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
|
|
2361
|
+
Object.assign(
|
|
2362
|
+
tools,
|
|
2363
|
+
createMergeWebIdePullRequestsTool({
|
|
2364
|
+
webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
|
|
2365
|
+
})
|
|
2366
|
+
);
|
|
2613
2367
|
}
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
console.warn(
|
|
2625
|
-
`[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
|
|
2626
|
-
err instanceof Error ? err.message : err
|
|
2627
|
-
);
|
|
2628
|
-
}
|
|
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
|
+
);
|
|
2629
2378
|
}
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
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
|
+
})
|
|
2650
2406
|
);
|
|
2651
2407
|
}
|
|
2652
|
-
throw err;
|
|
2653
|
-
} finally {
|
|
2654
|
-
clearRunSteer();
|
|
2655
|
-
signal?.removeEventListener("abort", abortRun);
|
|
2656
|
-
await agent[Symbol.asyncDispose]();
|
|
2657
2408
|
}
|
|
2409
|
+
return tools;
|
|
2658
2410
|
}
|
|
2411
|
+
function withPlanModeToolHint(prompt, mode) {
|
|
2412
|
+
if (mode !== "plan") {
|
|
2413
|
+
return prompt;
|
|
2414
|
+
}
|
|
2415
|
+
return `${prompt.trim()}
|
|
2659
2416
|
|
|
2660
|
-
|
|
2661
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2662
|
-
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
2663
|
-
function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
|
|
2664
|
-
return resolve6(workdir, ".apm", "webide", taskId, fileName);
|
|
2417
|
+
${PLAN_MODE_ASK_QUESTION_HINT}`;
|
|
2665
2418
|
}
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
}
|
|
2691
|
-
if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
|
|
2692
|
-
state.updatedAt = raw.updatedAt.trim();
|
|
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}`
|
|
2693
2443
|
}
|
|
2694
|
-
return state;
|
|
2695
2444
|
}
|
|
2696
|
-
}
|
|
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;
|
|
2697
2462
|
}
|
|
2698
|
-
return {
|
|
2463
|
+
return {
|
|
2464
|
+
schemaVersion: 1,
|
|
2465
|
+
rootBlobId: row.rootBlobId
|
|
2466
|
+
};
|
|
2699
2467
|
}
|
|
2700
|
-
function
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
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
|
+
};
|
|
2704
2475
|
}
|
|
2705
|
-
function
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
updatedAt:
|
|
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
|
|
3103
|
+
);
|
|
3104
|
+
session.clearDirtyIfUnchanged(events);
|
|
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);
|
|
3161
|
+
}
|
|
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
|
+
`;
|
|
3180
|
+
}
|
|
3181
|
+
function readRevision(apmDir) {
|
|
3182
|
+
const path3 = toFsPath(join4(apmDir, REVISION_FILE));
|
|
3183
|
+
if (!existsSync5(path3)) return null;
|
|
3184
|
+
try {
|
|
3185
|
+
const parsed = JSON.parse(readFileSync4(path3, "utf8"));
|
|
3186
|
+
if (parsed?.version === 1 && parsed.revision?.trim()) {
|
|
3187
|
+
return parsed.revision.trim();
|
|
3188
|
+
}
|
|
3189
|
+
} catch {
|
|
3190
|
+
return null;
|
|
3191
|
+
}
|
|
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
|
+
}
|
|
3202
|
+
async function syncWebIdeAgentFiles(workdir, cfg2) {
|
|
3203
|
+
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
|
+
return {
|
|
3241
|
+
revision,
|
|
3242
|
+
wrote: true,
|
|
3243
|
+
ruleCount: bundle.rules?.length ?? 0
|
|
3244
|
+
};
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
// src/commands/init.ts
|
|
3248
|
+
async function ensureWorkspaceInitialized(workdir) {
|
|
3249
|
+
if (isWorkspaceApmInitialized(workdir)) {
|
|
3250
|
+
return { didInit: false };
|
|
3251
|
+
}
|
|
3252
|
+
console.log(
|
|
3253
|
+
`[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`
|
|
3254
|
+
);
|
|
3255
|
+
await ensureWorkspaceApmDirForInit(workdir);
|
|
3256
|
+
console.log(`[apm] \u5DF2\u521B\u5EFA\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF1A${workspaceApmDir(workdir)}`);
|
|
3257
|
+
return { didInit: true };
|
|
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
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
function listLocalDocumentPaths(apmRoot, projectId) {
|
|
3323
|
+
const root = projectDocumentsDir(apmRoot, projectId);
|
|
3324
|
+
if (!existsSync6(root)) {
|
|
3325
|
+
return [];
|
|
3326
|
+
}
|
|
3327
|
+
const paths = [];
|
|
3328
|
+
const walk = (dir) => {
|
|
3329
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
3330
|
+
const abs = join5(dir, entry.name);
|
|
3331
|
+
if (entry.isDirectory()) {
|
|
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
|
+
}
|
|
2714
3341
|
};
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
3342
|
+
walk(root);
|
|
3343
|
+
return paths.sort();
|
|
3344
|
+
}
|
|
3345
|
+
function diffManifestPaths(remote, local) {
|
|
3346
|
+
const remoteMap = new Map(
|
|
3347
|
+
(remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
3348
|
+
);
|
|
3349
|
+
const localMap = new Map(
|
|
3350
|
+
(local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
3351
|
+
);
|
|
3352
|
+
const download = [];
|
|
3353
|
+
for (const [path3, hash] of remoteMap) {
|
|
3354
|
+
if (localMap.get(path3) !== hash) {
|
|
3355
|
+
download.push(path3);
|
|
3356
|
+
}
|
|
2719
3357
|
}
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
if (
|
|
2723
|
-
|
|
3358
|
+
const deleteLocal = [];
|
|
3359
|
+
for (const path3 of localMap.keys()) {
|
|
3360
|
+
if (!remoteMap.has(path3)) {
|
|
3361
|
+
deleteLocal.push(path3);
|
|
3362
|
+
}
|
|
2724
3363
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
3364
|
+
return { download, deleteLocal };
|
|
3365
|
+
}
|
|
3366
|
+
async function resolveProjectIdForSync(api, workdirPath, options) {
|
|
3367
|
+
const explicit = options?.projectId?.trim();
|
|
3368
|
+
if (explicit) {
|
|
3369
|
+
return { projectId: explicit, diagnostic: null };
|
|
2729
3370
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
3371
|
+
try {
|
|
3372
|
+
const gitRoot = await resolveGitRepoRoot(workdirPath);
|
|
3373
|
+
const current = await getCurrentBranch(gitRoot);
|
|
3374
|
+
const taskId = taskIdFromFeatureBranch(current);
|
|
3375
|
+
if (!taskId) {
|
|
3376
|
+
return {
|
|
3377
|
+
projectId: null,
|
|
3378
|
+
diagnostic: `\u5F53\u524D\u5206\u652F ${current} \u4E0D\u662F feat/task-*\uFF0C\u65E0\u6CD5\u89E3\u6790\u6240\u5C5E\u9879\u76EE\u3002
|
|
3379
|
+
\u8BF7\u5728\u7279\u6027\u5206\u652F\u4E0A\u6267\u884C\uFF0C\u6216\u663E\u5F0F\u6307\u5B9A projectId\u3002`
|
|
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
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
|
|
3400
|
+
const empty = {
|
|
3401
|
+
synced: false,
|
|
3402
|
+
projectId: null,
|
|
3403
|
+
downloaded: 0,
|
|
3404
|
+
deleted: 0
|
|
3405
|
+
};
|
|
3406
|
+
const cfg2 = await tryReadApmConfig();
|
|
3407
|
+
if (!cfg2 || !resolveApiKey(cfg2)) {
|
|
3408
|
+
console.log(
|
|
3409
|
+
"[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
|
|
3410
|
+
);
|
|
3411
|
+
return empty;
|
|
3412
|
+
}
|
|
3413
|
+
const api = createApmApiClient(cfg2);
|
|
3414
|
+
const { projectId, diagnostic } = await resolveProjectIdForSync(
|
|
3415
|
+
api,
|
|
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)
|
|
3448
|
+
);
|
|
3449
|
+
await ensureDirExists(dirname2(absPath));
|
|
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;
|
|
3512
|
+
}
|
|
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
|
+
}
|
|
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
|
+
try {
|
|
3546
|
+
fs2.rmSync(dir, { recursive: true, force: true });
|
|
3547
|
+
} catch (err) {
|
|
3548
|
+
console.warn(
|
|
3549
|
+
`[apm] \u6E05\u7406\u6253\u5305\u4E34\u65F6\u76EE\u5F55\u5931\u8D25 ${dir}:`,
|
|
3550
|
+
err instanceof Error ? err.message : err
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
async function uploadPackageArtifact(cfg2, target, packageId, projectId) {
|
|
3555
|
+
const localPath = expectedLocalArtifactPath(target, packageId);
|
|
3556
|
+
if (!fs2.existsSync(localPath)) {
|
|
3557
|
+
throw new Error(`\u6253\u5305\u4EA7\u7269\u4E0D\u5B58\u5728: ${localPath}`);
|
|
3558
|
+
}
|
|
3559
|
+
const stat = fs2.statSync(localPath);
|
|
3560
|
+
if (!stat.isFile() || stat.size <= 0) {
|
|
3561
|
+
throw new Error(`\u6253\u5305\u4EA7\u7269\u65E0\u6548\u6216\u4E3A\u7A7A: ${localPath}`);
|
|
3562
|
+
}
|
|
3563
|
+
const objectKey = packageArtifactObjectKey(target, projectId);
|
|
3564
|
+
const { client, bucket, publicEndpoint } = await createApmLogMinioClient(cfg2);
|
|
3565
|
+
await client.ensureBucket(bucket);
|
|
3566
|
+
await client.fPutObject(bucket, objectKey, localPath, {
|
|
3567
|
+
"Content-Type": "application/zip"
|
|
3568
|
+
});
|
|
3569
|
+
removePackageTmpDir(packageId);
|
|
3570
|
+
const base = publicEndpoint?.replace(/\/$/, "") ?? "";
|
|
3571
|
+
const artifactUrl = base ? `${base}/${bucket}/${objectKey}` : null;
|
|
3572
|
+
return { artifactPath: objectKey, artifactUrl };
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
// src/commands/connect/core/worker/handlers/package.ts
|
|
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 } : {}
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
3587
|
+
async function handleWebIdePackage(cfg2, msg2, signal) {
|
|
3588
|
+
const workdir = requireRemoteWorkdir(msg2.workdir);
|
|
3589
|
+
const messageId = msg2.messageId;
|
|
3590
|
+
const packageId = msg2.packageId;
|
|
3591
|
+
const projectId = msg2.projectId;
|
|
3592
|
+
console.log(
|
|
3593
|
+
`[apm] webide-package target=${msg2.target} packageId=${packageId} projectId=${projectId} baseBranch=${msg2.baseBranch}`
|
|
3594
|
+
);
|
|
3595
|
+
await updatePackageStatus(cfg2, packageId, "RUNNING");
|
|
3596
|
+
const eventSession = new EventSession(msg2.content);
|
|
3597
|
+
const logSyncRef = {
|
|
3598
|
+
current: createThrottledWebIdeMessageLogSync(
|
|
3599
|
+
cfg2,
|
|
3600
|
+
{
|
|
3601
|
+
taskId: `package:${packageId}`,
|
|
3602
|
+
messageId,
|
|
3603
|
+
agentId: "webide-package"
|
|
3604
|
+
},
|
|
3605
|
+
(err) => {
|
|
3606
|
+
console.warn(
|
|
3607
|
+
"[apm] WebIDE \u6253\u5305\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
|
|
3608
|
+
err instanceof Error ? err.message : err
|
|
3609
|
+
);
|
|
3610
|
+
},
|
|
3611
|
+
{
|
|
3612
|
+
objectPrefixOverride: `events/webide-package/${packageId}/`
|
|
3613
|
+
}
|
|
3614
|
+
)
|
|
3615
|
+
};
|
|
3616
|
+
const syncPrepLog = async () => {
|
|
3617
|
+
const sync = logSyncRef.current;
|
|
3618
|
+
if (!sync) return;
|
|
3619
|
+
sync.schedule(eventSession);
|
|
3620
|
+
await sync.flush(eventSession);
|
|
3621
|
+
};
|
|
3622
|
+
const runPrepStep = async (step, work, formatOk) => {
|
|
3623
|
+
eventSession.addCliStep(step, "\u8FDB\u884C\u4E2D\u2026", "running");
|
|
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}`;
|
|
3689
|
+
}
|
|
3690
|
+
);
|
|
3691
|
+
eventSession.addCliStep("\u542F\u52A8\u6253\u5305 Agent", "\u51C6\u5907 create\u2026", "running");
|
|
3692
|
+
await syncPrepLog();
|
|
3693
|
+
let failedByTool = false;
|
|
3694
|
+
const outcome = await runCursorAgent(
|
|
3695
|
+
cfg2,
|
|
3696
|
+
{
|
|
3697
|
+
messageId,
|
|
3698
|
+
prompt: msg2.content,
|
|
3699
|
+
model: msg2.model,
|
|
3700
|
+
apiKey: msg2.apiKey,
|
|
3701
|
+
workdir,
|
|
3702
|
+
user: msg2.user || "webide"
|
|
3703
|
+
},
|
|
3704
|
+
{
|
|
3705
|
+
signal,
|
|
3706
|
+
forceSend: true,
|
|
3707
|
+
eventSession,
|
|
3708
|
+
enableAppendMessage: false,
|
|
3709
|
+
enableAskQuestion: false,
|
|
3710
|
+
enableWebIdePlanTools: false,
|
|
3711
|
+
enablePtySessionMcp: false,
|
|
3712
|
+
enableSandbox: false,
|
|
3713
|
+
enablePackageStatusTools: true,
|
|
3714
|
+
packageId,
|
|
3715
|
+
projectId,
|
|
3716
|
+
onPackageFailed: async () => {
|
|
3717
|
+
failedByTool = true;
|
|
3718
|
+
},
|
|
3719
|
+
taskId: `package:${packageId}`,
|
|
3720
|
+
createRemoteLogSync: (agentId) => {
|
|
3721
|
+
logSyncRef.current = createThrottledWebIdeMessageLogSync(
|
|
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"
|
|
3745
|
+
);
|
|
3746
|
+
logSyncRef.current?.schedule(eventSession);
|
|
3747
|
+
await logSyncRef.current?.markRun(runId, "running");
|
|
3748
|
+
await logSyncRef.current?.flush(eventSession);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
);
|
|
3752
|
+
const tokenUsage = outcome.usage != null ? {
|
|
3753
|
+
modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
|
|
3754
|
+
inputTokens: outcome.usage.inputTokens,
|
|
3755
|
+
outputTokens: outcome.usage.outputTokens,
|
|
3756
|
+
cacheReadTokens: outcome.usage.cacheReadTokens,
|
|
3757
|
+
cacheWriteTokens: outcome.usage.cacheWriteTokens,
|
|
3758
|
+
totalTokens: outcome.usage.totalTokens,
|
|
3759
|
+
...outcome.usage.reasoningTokens != null ? { reasoningTokens: outcome.usage.reasoningTokens } : {}
|
|
3760
|
+
} : void 0;
|
|
3761
|
+
if (outcome.status === "error") {
|
|
3762
|
+
const detail = formatCursorRunFailure(outcome.runId, {
|
|
3763
|
+
resultText: outcome.result
|
|
3764
|
+
});
|
|
3765
|
+
await logSyncRef.current?.markRun(
|
|
3766
|
+
outcome.runId,
|
|
3767
|
+
"error",
|
|
3768
|
+
detail,
|
|
3769
|
+
tokenUsage
|
|
3770
|
+
);
|
|
3771
|
+
if (!failedByTool) {
|
|
3772
|
+
await updatePackageStatus(cfg2, packageId, "FAILED", { error: detail });
|
|
3773
|
+
}
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
if (outcome.status === "cancelled" || signal.aborted) {
|
|
3777
|
+
await logSyncRef.current?.markRun(
|
|
3778
|
+
outcome.runId,
|
|
3779
|
+
"cancelled",
|
|
3780
|
+
"\u5DF2\u53D6\u6D88",
|
|
3781
|
+
tokenUsage
|
|
3782
|
+
);
|
|
3783
|
+
await updatePackageStatus(cfg2, packageId, "CANCELLED", {
|
|
3784
|
+
error: "\u5DF2\u53D6\u6D88"
|
|
3785
|
+
});
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
await logSyncRef.current?.markRun(
|
|
3789
|
+
outcome.runId,
|
|
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;
|
|
3799
|
+
}
|
|
3800
|
+
const uploaded = await runPrepStep(
|
|
3801
|
+
"\u4E0A\u4F20\u6253\u5305\u4EA7\u7269\u5230 MinIO",
|
|
3802
|
+
() => uploadPackageArtifact(cfg2, msg2.target, packageId, projectId),
|
|
3803
|
+
(result) => `objectKey=${result.artifactPath}${result.artifactUrl ? ` url=${result.artifactUrl}` : ""}`
|
|
3804
|
+
);
|
|
3805
|
+
await updatePackageStatus(cfg2, packageId, "SUCCESS", {
|
|
3806
|
+
artifactPath: uploaded.artifactPath,
|
|
3807
|
+
...uploaded.artifactUrl ? { artifactUrl: uploaded.artifactUrl } : {}
|
|
3808
|
+
});
|
|
3809
|
+
console.log(
|
|
3810
|
+
`[apm] webide-package \u5B8C\u6210 target=${msg2.target} packageId=${packageId} agentId=${outcome.agentId} artifact=${uploaded.artifactPath}`
|
|
3811
|
+
);
|
|
3812
|
+
} catch (err) {
|
|
3813
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
3814
|
+
console.error(`[apm] webide-package \u5931\u8D25: ${detail}`);
|
|
3815
|
+
try {
|
|
3816
|
+
if (signal.aborted) {
|
|
3817
|
+
await updatePackageStatus(cfg2, packageId, "CANCELLED", {
|
|
3818
|
+
error: detail
|
|
3819
|
+
});
|
|
3820
|
+
} else {
|
|
3821
|
+
await updatePackageStatus(cfg2, packageId, "FAILED", { error: detail });
|
|
3822
|
+
}
|
|
3823
|
+
} catch (statusErr) {
|
|
3824
|
+
console.error(
|
|
3825
|
+
"[apm] \u5199\u5165\u6253\u5305 FAILED \u5931\u8D25:",
|
|
3826
|
+
statusErr instanceof Error ? statusErr.message : statusErr
|
|
3827
|
+
);
|
|
3828
|
+
}
|
|
2732
3829
|
}
|
|
2733
|
-
writeRegistry(path, next);
|
|
2734
|
-
}
|
|
2735
|
-
function loadWebIdeAgentId(workdir, taskId) {
|
|
2736
|
-
return readRegistry(registryPath(workdir, taskId)).agentId;
|
|
2737
|
-
}
|
|
2738
|
-
function saveWebIdeAgentId(workdir, taskId, agentId) {
|
|
2739
|
-
syncWebIdeTaskState(workdir, taskId, { agentId });
|
|
2740
|
-
}
|
|
2741
|
-
function clearWebIdeAgentId(workdir, taskId) {
|
|
2742
|
-
const path = registryPath(workdir, taskId);
|
|
2743
|
-
if (!existsSync6(path)) return;
|
|
2744
|
-
syncWebIdeTaskState(workdir, taskId, { agentId: "" });
|
|
2745
|
-
}
|
|
2746
|
-
function loadWebIdeDesignAgentId(workdir, taskId) {
|
|
2747
|
-
return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
|
|
2748
|
-
}
|
|
2749
|
-
function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
|
|
2750
|
-
const trimmedTaskId = taskId.trim();
|
|
2751
|
-
if (!trimmedTaskId) return;
|
|
2752
|
-
const path = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
|
|
2753
|
-
const current = readRegistry(path);
|
|
2754
|
-
writeRegistry(path, {
|
|
2755
|
-
...current,
|
|
2756
|
-
taskId: trimmedTaskId,
|
|
2757
|
-
agentId: agentId.trim() || void 0,
|
|
2758
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2759
|
-
});
|
|
2760
|
-
}
|
|
2761
|
-
function clearWebIdeDesignAgentId(workdir, taskId) {
|
|
2762
|
-
const path = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
|
|
2763
|
-
if (!existsSync6(path)) return;
|
|
2764
|
-
const current = readRegistry(path);
|
|
2765
|
-
const next = { ...current };
|
|
2766
|
-
delete next.agentId;
|
|
2767
|
-
writeRegistry(path, {
|
|
2768
|
-
...next,
|
|
2769
|
-
taskId: taskId.trim(),
|
|
2770
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2771
|
-
});
|
|
2772
3830
|
}
|
|
2773
3831
|
|
|
2774
|
-
// src/commands/connect/
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
for (const item of raw) {
|
|
2786
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
2787
|
-
const row = item;
|
|
2788
|
-
const id = asString4(row.id);
|
|
2789
|
-
const prompt = asString4(row.prompt);
|
|
2790
|
-
const optionsRaw = row.options;
|
|
2791
|
-
if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
|
|
2792
|
-
const options = [];
|
|
2793
|
-
for (const opt of optionsRaw) {
|
|
2794
|
-
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
|
|
2795
|
-
const o = opt;
|
|
2796
|
-
const oid = asString4(o.id);
|
|
2797
|
-
const label = asString4(o.label);
|
|
2798
|
-
if (oid && label) options.push({ id: oid, label });
|
|
2799
|
-
}
|
|
2800
|
-
if (options.length < 2) {
|
|
2801
|
-
throw new Error(`AskQuestion \u95EE\u9898 ${id} \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`);
|
|
3832
|
+
// src/commands/connect/ensure-message-reply.ts
|
|
3833
|
+
var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
|
|
3834
|
+
function resolveMessageReplyFallback(fallback) {
|
|
3835
|
+
for (const candidate of [
|
|
3836
|
+
fallback.assistantText,
|
|
3837
|
+
fallback.result,
|
|
3838
|
+
fallback.createPlan
|
|
3839
|
+
]) {
|
|
3840
|
+
const trimmed = candidate?.trim();
|
|
3841
|
+
if (trimmed) {
|
|
3842
|
+
return trimmed;
|
|
2802
3843
|
}
|
|
2803
|
-
questions.push({ id, prompt, options });
|
|
2804
3844
|
}
|
|
2805
|
-
|
|
2806
|
-
throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
|
|
2807
|
-
}
|
|
2808
|
-
return { title, questions };
|
|
3845
|
+
return DEFAULT_REPLY;
|
|
2809
3846
|
}
|
|
2810
|
-
|
|
2811
|
-
|
|
3847
|
+
|
|
3848
|
+
// src/commands/connect/core/worker/handlers/start-project.ts
|
|
3849
|
+
async function updateStatus(cfg2, messageId, status) {
|
|
2812
3850
|
const api = createApmApiClient(cfg2);
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
let index = 0;
|
|
2824
|
-
for (const id of ids) {
|
|
2825
|
-
const row = byId.get(id);
|
|
2826
|
-
if (!row || row.status !== "RESOLVED") {
|
|
2827
|
-
throw new Error(`\u5047\u8BBE\u5C1A\u672A\u5168\u90E8\u786E\u8BA4: ${id}`);
|
|
2828
|
-
}
|
|
2829
|
-
const text = asString4(row.resolution) || asString4(row.customAnswer) || asString4(row.selectedOptionId);
|
|
2830
|
-
if (!text) {
|
|
2831
|
-
throw new Error(`\u5047\u8BBE\u7B54\u6848\u4E3A\u7A7A: ${id}`);
|
|
2832
|
-
}
|
|
2833
|
-
index += 1;
|
|
2834
|
-
lines.push(`${index}. ${text}`);
|
|
2835
|
-
}
|
|
3851
|
+
await api.cli.webideUpdateMessageStatus({ id: messageId, status });
|
|
3852
|
+
}
|
|
3853
|
+
async function setError(cfg2, messageId, error) {
|
|
3854
|
+
const api = createApmApiClient(cfg2);
|
|
3855
|
+
await api.cli.webideSetMessageError({ id: messageId, error });
|
|
3856
|
+
}
|
|
3857
|
+
async function handleStartProject(cfg2, msg2, signal) {
|
|
3858
|
+
const workdir = requireRemoteWorkdir(msg2.workdir);
|
|
3859
|
+
const messageId = msg2.messageId;
|
|
3860
|
+
const taskId = msg2.taskId;
|
|
2836
3861
|
console.log(
|
|
2837
|
-
`[apm]
|
|
3862
|
+
`[apm] webide-start-project target=${msg2.target} restart=${msg2.restart} taskId=${taskId} messageId=${messageId}`
|
|
2838
3863
|
);
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
3864
|
+
await updateStatus(cfg2, messageId, "TYPING");
|
|
3865
|
+
const eventSession = new EventSession(msg2.content);
|
|
3866
|
+
const logSyncRef = {
|
|
3867
|
+
current: createThrottledWebIdeMessageLogSync(
|
|
3868
|
+
cfg2,
|
|
3869
|
+
{ taskId, messageId, agentId: "webide-start-project" },
|
|
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);
|
|
2845
3882
|
const api = createApmApiClient(cfg2);
|
|
2846
|
-
|
|
2847
|
-
|
|
3883
|
+
const outcome = await runCursorAgent(
|
|
3884
|
+
cfg2,
|
|
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
|
+
}
|
|
2848
3927
|
);
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
}
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
"\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",
|
|
2862
|
-
"\u7528\u6237\u786E\u8BA4\u540E\uFF0C\u4E0B\u4E00\u8F6E\u4F1A\u5E26\u4E0A\u7B54\u6848\u5E76\u7EE7\u7EED\u8BC4\u4F30\u3002"
|
|
2863
|
-
].join("");
|
|
2864
|
-
};
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
// src/utils/minio.ts
|
|
2868
|
-
import * as Minio from "minio";
|
|
2869
|
-
var MinioClient = class {
|
|
2870
|
-
inner;
|
|
2871
|
-
constructor(opts) {
|
|
2872
|
-
const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
|
|
2873
|
-
this.inner = new Minio.Client({
|
|
2874
|
-
endPoint,
|
|
2875
|
-
port: opts.port,
|
|
2876
|
-
useSSL: opts.useSSL,
|
|
2877
|
-
accessKey: opts.accessKey,
|
|
2878
|
-
secretKey: opts.secretKey
|
|
2879
|
-
});
|
|
2880
|
-
}
|
|
2881
|
-
async ensureBucket(bucket) {
|
|
2882
|
-
const exists = await this.inner.bucketExists(bucket);
|
|
2883
|
-
if (!exists) {
|
|
2884
|
-
await this.inner.makeBucket(bucket);
|
|
2885
|
-
}
|
|
2886
|
-
}
|
|
2887
|
-
async putObject(bucket, objectKey, body, meta) {
|
|
2888
|
-
await this.inner.putObject(bucket, objectKey, body, body.length, meta);
|
|
2889
|
-
}
|
|
2890
|
-
/** 大文件:从本地路径流式上传,避免整包读入内存 */
|
|
2891
|
-
async fPutObject(bucket, objectKey, filePath, meta) {
|
|
2892
|
-
await this.inner.fPutObject(bucket, objectKey, filePath, meta);
|
|
2893
|
-
}
|
|
2894
|
-
async listObjects(bucket, prefix) {
|
|
2895
|
-
const objects = [];
|
|
2896
|
-
const stream = this.inner.listObjectsV2(bucket, prefix, true);
|
|
2897
|
-
await new Promise((resolve7, reject) => {
|
|
2898
|
-
stream.on("data", (obj) => {
|
|
2899
|
-
if (!obj.name) return;
|
|
2900
|
-
objects.push({
|
|
2901
|
-
name: obj.name,
|
|
2902
|
-
size: typeof obj.size === "number" ? obj.size : 0,
|
|
2903
|
-
lastModified: obj.lastModified ?? null,
|
|
2904
|
-
...typeof obj.etag === "string" ? { etag: obj.etag } : {}
|
|
2905
|
-
});
|
|
3928
|
+
const tokenUsage = outcome.usage != null ? {
|
|
3929
|
+
modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
|
|
3930
|
+
inputTokens: outcome.usage.inputTokens,
|
|
3931
|
+
outputTokens: outcome.usage.outputTokens,
|
|
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
|
|
2906
3940
|
});
|
|
2907
|
-
|
|
2908
|
-
|
|
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
|
|
2909
3963
|
});
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
await
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
3964
|
+
await api.cli.webideEnsureMessageContent({
|
|
3965
|
+
id: messageId,
|
|
3966
|
+
content: fallback
|
|
3967
|
+
});
|
|
3968
|
+
await logSyncRef.current?.markRun(
|
|
3969
|
+
outcome.runId,
|
|
3970
|
+
"finished",
|
|
3971
|
+
null,
|
|
3972
|
+
tokenUsage
|
|
3973
|
+
);
|
|
3974
|
+
await updateStatus(cfg2, messageId, "SUCCESS");
|
|
3975
|
+
console.log(
|
|
3976
|
+
`[apm] webide-start-project \u5B8C\u6210 target=${msg2.target} messageId=${messageId} agentId=${outcome.agentId}`
|
|
3977
|
+
);
|
|
3978
|
+
} catch (err) {
|
|
3979
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
3980
|
+
console.error(`[apm] webide-start-project \u5931\u8D25: ${detail}`);
|
|
3981
|
+
try {
|
|
3982
|
+
await setError(cfg2, messageId, detail);
|
|
3983
|
+
} catch (statusErr) {
|
|
3984
|
+
console.error(
|
|
3985
|
+
"[apm] \u5199\u5165\u542F\u52A8\u9879\u76EE FAILED \u5931\u8D25:",
|
|
3986
|
+
statusErr instanceof Error ? statusErr.message : statusErr
|
|
3987
|
+
);
|
|
2927
3988
|
}
|
|
2928
|
-
return Buffer.concat(chunks);
|
|
2929
3989
|
}
|
|
2930
|
-
};
|
|
2931
|
-
|
|
2932
|
-
// src/commands/connect/apm-log-minio.ts
|
|
2933
|
-
async function fetchApmLogStorage(cfg2) {
|
|
2934
|
-
const api = createApmApiClient(cfg2);
|
|
2935
|
-
return api.cli.getApmLogStorage(void 0);
|
|
2936
|
-
}
|
|
2937
|
-
async function createApmLogMinioClient(cfg2) {
|
|
2938
|
-
const storage = await fetchApmLogStorage(cfg2);
|
|
2939
|
-
return {
|
|
2940
|
-
client: new MinioClient({
|
|
2941
|
-
endPoint: storage.endpoint,
|
|
2942
|
-
port: storage.port,
|
|
2943
|
-
useSSL: storage.useSsl,
|
|
2944
|
-
accessKey: storage.accessKey,
|
|
2945
|
-
secretKey: storage.secretKey
|
|
2946
|
-
}),
|
|
2947
|
-
bucket: storage.bucket,
|
|
2948
|
-
...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
|
|
2949
|
-
};
|
|
2950
3990
|
}
|
|
2951
3991
|
|
|
2952
|
-
// src/commands/connect/webide-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
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);
|
|
2956
3997
|
}
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
JSON.stringify({
|
|
2962
|
-
index: event.index,
|
|
2963
|
-
type: event.type,
|
|
2964
|
-
data: event.data
|
|
2965
|
-
}),
|
|
2966
|
-
"utf8"
|
|
2967
|
-
);
|
|
2968
|
-
await minio.putObject(bucket, key, body, {
|
|
2969
|
-
"Content-Type": "application/json"
|
|
2970
|
-
});
|
|
3998
|
+
var DESIGN_AGENT_FILE = "design-agent.json";
|
|
3999
|
+
function readRegistry(path3) {
|
|
4000
|
+
if (!existsSync7(path3)) {
|
|
4001
|
+
return {};
|
|
2971
4002
|
}
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
...patch
|
|
2980
|
-
});
|
|
2981
|
-
}
|
|
2982
|
-
function createThrottledWebIdeMessageLogSync(cfg2, ctx, onError, options) {
|
|
2983
|
-
let lastRunAt = 0;
|
|
2984
|
-
let timer;
|
|
2985
|
-
let latestSession;
|
|
2986
|
-
let syncChain = Promise.resolve();
|
|
2987
|
-
let minio;
|
|
2988
|
-
let bucket = "";
|
|
2989
|
-
let firstLogNotified = false;
|
|
2990
|
-
const resolvePrefix = () => options?.objectPrefixOverride?.trim() || webIdeEventsObjectPrefix(ctx.taskId, ctx.messageId);
|
|
2991
|
-
const ensureMinio = async () => {
|
|
2992
|
-
if (minio) return;
|
|
2993
|
-
const created = await createApmLogMinioClient(cfg2);
|
|
2994
|
-
minio = created.client;
|
|
2995
|
-
bucket = created.bucket;
|
|
2996
|
-
await minio.ensureBucket(bucket);
|
|
2997
|
-
};
|
|
2998
|
-
const notifyFirstLog = async () => {
|
|
2999
|
-
if (firstLogNotified) return;
|
|
3000
|
-
firstLogNotified = true;
|
|
3001
|
-
try {
|
|
3002
|
-
await options?.onFirstLogSynced?.();
|
|
3003
|
-
} catch (err) {
|
|
3004
|
-
firstLogNotified = false;
|
|
3005
|
-
onError(err);
|
|
3006
|
-
}
|
|
3007
|
-
};
|
|
3008
|
-
const syncDirtyEventsOnce = async (session) => {
|
|
3009
|
-
const events = session.getDirtyEvents();
|
|
3010
|
-
if (events.length === 0) return;
|
|
3011
|
-
lastRunAt = Date.now();
|
|
3012
|
-
try {
|
|
3013
|
-
await ensureMinio();
|
|
3014
|
-
const prefix = resolvePrefix();
|
|
3015
|
-
await putDirtyEvents(minio, bucket, prefix, events);
|
|
3016
|
-
await upsertLogHeader(
|
|
3017
|
-
cfg2,
|
|
3018
|
-
ctx,
|
|
3019
|
-
{
|
|
3020
|
-
eventCount: session.getEventCount()
|
|
3021
|
-
},
|
|
3022
|
-
prefix
|
|
3023
|
-
);
|
|
3024
|
-
session.clearDirtyIfUnchanged(events);
|
|
3025
|
-
await notifyFirstLog();
|
|
3026
|
-
} catch (err) {
|
|
3027
|
-
onError(err);
|
|
3028
|
-
}
|
|
3029
|
-
};
|
|
3030
|
-
const drainDirtyEvents = async (session) => {
|
|
3031
|
-
while (session.getDirtyEvents().length > 0) {
|
|
3032
|
-
await syncDirtyEventsOnce(session);
|
|
3033
|
-
}
|
|
3034
|
-
};
|
|
3035
|
-
const enqueueSync = (session) => {
|
|
3036
|
-
syncChain = syncChain.then(() => drainDirtyEvents(session));
|
|
3037
|
-
};
|
|
3038
|
-
return {
|
|
3039
|
-
async markRun(runId, runStatus, lastError, tokenUsage) {
|
|
3040
|
-
try {
|
|
3041
|
-
await upsertLogHeader(
|
|
3042
|
-
cfg2,
|
|
3043
|
-
ctx,
|
|
3044
|
-
{
|
|
3045
|
-
runId,
|
|
3046
|
-
runStatus,
|
|
3047
|
-
lastError: lastError ?? null,
|
|
3048
|
-
...tokenUsage ? { tokenUsage } : {}
|
|
3049
|
-
},
|
|
3050
|
-
resolvePrefix()
|
|
3051
|
-
);
|
|
3052
|
-
} catch (err) {
|
|
3053
|
-
onError(err);
|
|
4003
|
+
try {
|
|
4004
|
+
const parsed = JSON.parse(readFileSync6(path3, "utf8"));
|
|
4005
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4006
|
+
const raw = parsed;
|
|
4007
|
+
const state = {};
|
|
4008
|
+
if (typeof raw.agentId === "string" && raw.agentId.trim()) {
|
|
4009
|
+
state.agentId = raw.agentId.trim();
|
|
3054
4010
|
}
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
latestSession = session;
|
|
3058
|
-
const now = Date.now();
|
|
3059
|
-
const elapsed = now - lastRunAt;
|
|
3060
|
-
if (elapsed >= SYNC_INTERVAL_MS) {
|
|
3061
|
-
if (timer) {
|
|
3062
|
-
clearTimeout(timer);
|
|
3063
|
-
timer = void 0;
|
|
3064
|
-
}
|
|
3065
|
-
enqueueSync(session);
|
|
3066
|
-
return;
|
|
4011
|
+
if (typeof raw.taskId === "string" && raw.taskId.trim()) {
|
|
4012
|
+
state.taskId = raw.taskId.trim();
|
|
3067
4013
|
}
|
|
3068
|
-
if (
|
|
3069
|
-
|
|
3070
|
-
timer = void 0;
|
|
3071
|
-
if (latestSession) enqueueSync(latestSession);
|
|
3072
|
-
}, SYNC_INTERVAL_MS - elapsed);
|
|
3073
|
-
},
|
|
3074
|
-
async flush(session) {
|
|
3075
|
-
if (timer) {
|
|
3076
|
-
clearTimeout(timer);
|
|
3077
|
-
timer = void 0;
|
|
4014
|
+
if (typeof raw.messageId === "string" && raw.messageId.trim()) {
|
|
4015
|
+
state.messageId = raw.messageId.trim();
|
|
3078
4016
|
}
|
|
3079
|
-
|
|
3080
|
-
|
|
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;
|
|
3081
4027
|
}
|
|
4028
|
+
} catch {
|
|
4029
|
+
}
|
|
4030
|
+
return {};
|
|
4031
|
+
}
|
|
4032
|
+
function writeRegistry(path3, registry) {
|
|
4033
|
+
mkdirSync5(dirname3(path3), { recursive: true });
|
|
4034
|
+
writeFileSync5(path3, `${JSON.stringify(registry, null, 2)}
|
|
4035
|
+
`, "utf8");
|
|
4036
|
+
}
|
|
4037
|
+
function syncWebIdeTaskState(workdir, taskId, patch) {
|
|
4038
|
+
const trimmedTaskId = taskId.trim();
|
|
4039
|
+
if (!trimmedTaskId) return;
|
|
4040
|
+
const path3 = registryPath(workdir, trimmedTaskId);
|
|
4041
|
+
const current = readRegistry(path3);
|
|
4042
|
+
const next = {
|
|
4043
|
+
...current,
|
|
4044
|
+
taskId: trimmedTaskId,
|
|
4045
|
+
updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3082
4046
|
};
|
|
4047
|
+
if (patch.agentId !== void 0) {
|
|
4048
|
+
const agentId = patch.agentId.trim();
|
|
4049
|
+
if (agentId) next.agentId = agentId;
|
|
4050
|
+
else delete next.agentId;
|
|
4051
|
+
}
|
|
4052
|
+
if (patch.messageId !== void 0) {
|
|
4053
|
+
const messageId = patch.messageId.trim();
|
|
4054
|
+
if (messageId) next.messageId = messageId;
|
|
4055
|
+
else delete next.messageId;
|
|
4056
|
+
}
|
|
4057
|
+
if (patch.action !== void 0) {
|
|
4058
|
+
const action = patch.action.trim();
|
|
4059
|
+
if (action) next.action = action;
|
|
4060
|
+
else delete next.action;
|
|
4061
|
+
}
|
|
4062
|
+
if (patch.status !== void 0) {
|
|
4063
|
+
next.status = patch.status;
|
|
4064
|
+
}
|
|
4065
|
+
writeRegistry(path3, next);
|
|
4066
|
+
}
|
|
4067
|
+
function loadWebIdeAgentId(workdir, taskId) {
|
|
4068
|
+
return readRegistry(registryPath(workdir, taskId)).agentId;
|
|
4069
|
+
}
|
|
4070
|
+
function saveWebIdeAgentId(workdir, taskId, agentId) {
|
|
4071
|
+
syncWebIdeTaskState(workdir, taskId, { agentId });
|
|
4072
|
+
}
|
|
4073
|
+
function clearWebIdeAgentId(workdir, taskId) {
|
|
4074
|
+
const path3 = registryPath(workdir, taskId);
|
|
4075
|
+
if (!existsSync7(path3)) return;
|
|
4076
|
+
syncWebIdeTaskState(workdir, taskId, { agentId: "" });
|
|
4077
|
+
}
|
|
4078
|
+
function loadWebIdeDesignAgentId(workdir, taskId) {
|
|
4079
|
+
return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
|
|
4080
|
+
}
|
|
4081
|
+
function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
|
|
4082
|
+
const trimmedTaskId = taskId.trim();
|
|
4083
|
+
if (!trimmedTaskId) return;
|
|
4084
|
+
const path3 = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
|
|
4085
|
+
const current = readRegistry(path3);
|
|
4086
|
+
writeRegistry(path3, {
|
|
4087
|
+
...current,
|
|
4088
|
+
taskId: trimmedTaskId,
|
|
4089
|
+
agentId: agentId.trim() || void 0,
|
|
4090
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
4093
|
+
function clearWebIdeDesignAgentId(workdir, taskId) {
|
|
4094
|
+
const path3 = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
|
|
4095
|
+
if (!existsSync7(path3)) return;
|
|
4096
|
+
const current = readRegistry(path3);
|
|
4097
|
+
const next = { ...current };
|
|
4098
|
+
delete next.agentId;
|
|
4099
|
+
writeRegistry(path3, {
|
|
4100
|
+
...next,
|
|
4101
|
+
taskId: taskId.trim(),
|
|
4102
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4103
|
+
});
|
|
3083
4104
|
}
|
|
3084
4105
|
|
|
3085
|
-
// src/commands/
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
]
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
4106
|
+
// src/commands/clean-webide-cache.ts
|
|
4107
|
+
import { existsSync as existsSync8, rmSync as rmSync2 } from "node:fs";
|
|
4108
|
+
import { resolve as resolve7 } from "node:path";
|
|
4109
|
+
async function purgeCursorAgentStoreForAgent(cfg2, agentId, workspaceRef) {
|
|
4110
|
+
const trimmedAgentId = agentId.trim();
|
|
4111
|
+
if (!trimmedAgentId) return false;
|
|
4112
|
+
const store = createRemoteLocalAgentStore(cfg2);
|
|
4113
|
+
const runIds = [];
|
|
4114
|
+
let cursor;
|
|
4115
|
+
do {
|
|
4116
|
+
const page = await store.runs.list({
|
|
4117
|
+
filter: {
|
|
4118
|
+
agentIds: [trimmedAgentId],
|
|
4119
|
+
...cursor ? { cursor } : {},
|
|
4120
|
+
limit: 200
|
|
4121
|
+
}
|
|
4122
|
+
});
|
|
4123
|
+
for (const run of page.items) {
|
|
4124
|
+
runIds.push(run.runId);
|
|
3096
4125
|
}
|
|
4126
|
+
cursor = page.nextCursor;
|
|
4127
|
+
} while (cursor);
|
|
4128
|
+
await store.runs.delete({ filter: { agentIds: [trimmedAgentId] } });
|
|
4129
|
+
await store.agents.delete({ filter: { agentIds: [trimmedAgentId] } });
|
|
4130
|
+
const workdir = workspaceRef?.trim();
|
|
4131
|
+
if (workdir) {
|
|
4132
|
+
await purgeLocalSqliteAgentData({
|
|
4133
|
+
workspaceRef: workdir,
|
|
4134
|
+
agentId: trimmedAgentId,
|
|
4135
|
+
runIds
|
|
4136
|
+
});
|
|
3097
4137
|
}
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
unlinkSync,
|
|
3108
|
-
writeFileSync as writeFileSync4
|
|
3109
|
-
} from "fs";
|
|
3110
|
-
import { join as join6 } from "path";
|
|
3111
|
-
var REVISION_FILE = ".webide-agent.json";
|
|
3112
|
-
var RULE_KEY = /^[a-z0-9_]+$/;
|
|
3113
|
-
function withTrailingNewline(text) {
|
|
3114
|
-
return text.endsWith("\n") ? text : `${text}
|
|
3115
|
-
`;
|
|
3116
|
-
}
|
|
3117
|
-
function readRevision(apmDir) {
|
|
3118
|
-
const path = toFsPath(join6(apmDir, REVISION_FILE));
|
|
3119
|
-
if (!existsSync7(path)) return null;
|
|
3120
|
-
try {
|
|
3121
|
-
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
3122
|
-
if (parsed?.version === 1 && parsed.revision?.trim()) {
|
|
3123
|
-
return parsed.revision.trim();
|
|
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
|
+
);
|
|
3124
4147
|
}
|
|
3125
|
-
} catch {
|
|
3126
|
-
return null;
|
|
3127
4148
|
}
|
|
3128
|
-
return
|
|
3129
|
-
}
|
|
3130
|
-
function writeRevision(apmDir, revision) {
|
|
3131
|
-
writeFileSync4(
|
|
3132
|
-
toFsPath(join6(apmDir, REVISION_FILE)),
|
|
3133
|
-
`${JSON.stringify({ version: 1, revision }, null, 2)}
|
|
3134
|
-
`,
|
|
3135
|
-
"utf8"
|
|
3136
|
-
);
|
|
4149
|
+
return true;
|
|
3137
4150
|
}
|
|
3138
|
-
async function
|
|
3139
|
-
const
|
|
3140
|
-
const
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
throw new Error("[apm] \u5E73\u53F0\u672A\u8FD4\u56DE WebIDE \u89C4\u5219\u7248\u672C");
|
|
3144
|
-
}
|
|
3145
|
-
const apmDir = workspaceApmDir(workdir);
|
|
3146
|
-
mkdirSync5(toFsPath(apmDir), { recursive: true });
|
|
3147
|
-
if (readRevision(apmDir) === revision) {
|
|
3148
|
-
return { revision, wrote: false, ruleCount: bundle.rules?.length ?? 0 };
|
|
4151
|
+
async function cleanWebIdeWorkspaceCache(taskId, workdir, cfg2) {
|
|
4152
|
+
const trimmedTaskId = taskId.trim();
|
|
4153
|
+
const trimmedWorkdir = workdir.trim();
|
|
4154
|
+
if (!trimmedTaskId || !trimmedWorkdir) {
|
|
4155
|
+
return;
|
|
3149
4156
|
}
|
|
3150
|
-
const
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
4157
|
+
const agentId = loadWebIdeAgentId(trimmedWorkdir, trimmedTaskId);
|
|
4158
|
+
if (agentId) {
|
|
4159
|
+
try {
|
|
4160
|
+
const purged = await purgeCursorAgentStoreForAgent(
|
|
4161
|
+
cfg2,
|
|
4162
|
+
agentId,
|
|
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
|
+
);
|
|
3162
4175
|
}
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
withTrailingNewline(rule.body ?? ""),
|
|
3167
|
-
"utf8"
|
|
4176
|
+
} else {
|
|
4177
|
+
console.log(
|
|
4178
|
+
`[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
|
|
3168
4179
|
);
|
|
3169
4180
|
}
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
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}`);
|
|
3174
4187
|
}
|
|
3175
|
-
writeRevision(apmDir, revision);
|
|
3176
|
-
return {
|
|
3177
|
-
revision,
|
|
3178
|
-
wrote: true,
|
|
3179
|
-
ruleCount: bundle.rules?.length ?? 0
|
|
3180
|
-
};
|
|
3181
4188
|
}
|
|
3182
4189
|
|
|
3183
|
-
// src/commands/
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
4190
|
+
// src/commands/connect/webide-ask-question.ts
|
|
4191
|
+
function asString5(value) {
|
|
4192
|
+
return typeof value === "string" ? value.trim() : "";
|
|
4193
|
+
}
|
|
4194
|
+
function parseQuestions(args) {
|
|
4195
|
+
const title = asString5(args.title) || void 0;
|
|
4196
|
+
const raw = args.questions;
|
|
4197
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
4198
|
+
throw new Error("AskQuestion \u7F3A\u5C11 questions");
|
|
4199
|
+
}
|
|
4200
|
+
const questions = [];
|
|
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`);
|
|
4218
|
+
}
|
|
4219
|
+
questions.push({ id, prompt, options });
|
|
4220
|
+
}
|
|
4221
|
+
if (questions.length === 0) {
|
|
4222
|
+
throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
|
|
4223
|
+
}
|
|
4224
|
+
return { title, questions };
|
|
4225
|
+
}
|
|
4226
|
+
async function fetchAssumptionAnswersAndContinueAssessment(options) {
|
|
4227
|
+
const { cfg: cfg2, taskId, questionIds, title } = options;
|
|
4228
|
+
const api = createApmApiClient(cfg2);
|
|
4229
|
+
const list = await api.cli.webideListAssumptions({ taskId });
|
|
4230
|
+
const byId = new Map(list.assumptions.map((a) => [a.questionId, a]));
|
|
4231
|
+
const ids = questionIds && questionIds.length > 0 ? questionIds : list.assumptions.filter((a) => a.status === "RESOLVED").map((a) => a.questionId);
|
|
4232
|
+
if (ids.length === 0) {
|
|
4233
|
+
throw new Error("\u6CA1\u6709\u5DF2\u786E\u8BA4\u7684\u5047\u8BBE");
|
|
4234
|
+
}
|
|
4235
|
+
const answers = [];
|
|
4236
|
+
for (const id of ids) {
|
|
4237
|
+
const row = byId.get(id);
|
|
4238
|
+
if (!row || row.status !== "RESOLVED") {
|
|
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
|
+
});
|
|
3187
4248
|
}
|
|
3188
4249
|
console.log(
|
|
3189
|
-
`[apm] \
|
|
4250
|
+
`[apm] \u5047\u8BBE\u7ED3\u679C\u5DF2\u62C9\u53D6 taskId=${taskId} count=${answers.length}`
|
|
3190
4251
|
);
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
4252
|
+
return JSON.stringify({ title, answers }, null, 2);
|
|
4253
|
+
}
|
|
4254
|
+
function createWebIdeAskQuestionExecute(options) {
|
|
4255
|
+
const { cfg: cfg2, taskId, onAwaitingAssumptions } = options;
|
|
4256
|
+
return async (args) => {
|
|
4257
|
+
const parsed = parseQuestions(args);
|
|
4258
|
+
const api = createApmApiClient(cfg2);
|
|
4259
|
+
console.log(
|
|
4260
|
+
`[apm] AskQuestion \u521B\u5EFA\u5F85\u786E\u8BA4\u9879 taskId=${taskId} count=${parsed.questions.length}`
|
|
4261
|
+
);
|
|
4262
|
+
await api.cli.webideReplaceAssumptions({
|
|
4263
|
+
taskId,
|
|
4264
|
+
title: parsed.title,
|
|
4265
|
+
questions: parsed.questions
|
|
4266
|
+
});
|
|
4267
|
+
await api.cli.webideRunAction({
|
|
4268
|
+
taskId,
|
|
4269
|
+
action: "await-assumptions"
|
|
4270
|
+
});
|
|
4271
|
+
onAwaitingAssumptions?.();
|
|
4272
|
+
return [
|
|
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("");
|
|
4277
|
+
};
|
|
3194
4278
|
}
|
|
3195
4279
|
|
|
3196
4280
|
// src/commands/connect/pre-step-cache.ts
|
|
@@ -3417,9 +4501,9 @@ async function ensureWebIdePullRequests(cfg2, taskId, workdir) {
|
|
|
3417
4501
|
}
|
|
3418
4502
|
|
|
3419
4503
|
// src/commands/sync-webide-attachments.ts
|
|
3420
|
-
import { existsSync as
|
|
3421
|
-
import { join as
|
|
3422
|
-
var
|
|
4504
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
4505
|
+
import { join as join6 } from "path";
|
|
4506
|
+
var MANIFEST_FILE2 = ".sync-manifest.json";
|
|
3423
4507
|
async function downloadAttachment(cfg2, attachmentId) {
|
|
3424
4508
|
const base = cfg2.baseUrl.trim().replace(/\/+$/, "");
|
|
3425
4509
|
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
|
|
@@ -3434,13 +4518,13 @@ async function downloadAttachment(cfg2, attachmentId) {
|
|
|
3434
4518
|
return Buffer.from(await res.arrayBuffer());
|
|
3435
4519
|
}
|
|
3436
4520
|
function loadManifest(dir) {
|
|
3437
|
-
const
|
|
3438
|
-
if (!
|
|
4521
|
+
const path3 = join6(dir, MANIFEST_FILE2);
|
|
4522
|
+
if (!existsSync9(path3)) {
|
|
3439
4523
|
return { version: 1, attachments: {} };
|
|
3440
4524
|
}
|
|
3441
4525
|
try {
|
|
3442
4526
|
const parsed = JSON.parse(
|
|
3443
|
-
readFileSync7(
|
|
4527
|
+
readFileSync7(path3, "utf8")
|
|
3444
4528
|
);
|
|
3445
4529
|
if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
|
|
3446
4530
|
return parsed;
|
|
@@ -3450,15 +4534,15 @@ function loadManifest(dir) {
|
|
|
3450
4534
|
return { version: 1, attachments: {} };
|
|
3451
4535
|
}
|
|
3452
4536
|
function saveManifest(dir, manifest) {
|
|
3453
|
-
|
|
3454
|
-
|
|
4537
|
+
writeFileSync6(
|
|
4538
|
+
join6(dir, MANIFEST_FILE2),
|
|
3455
4539
|
`${JSON.stringify(manifest, null, 2)}
|
|
3456
4540
|
`,
|
|
3457
4541
|
"utf8"
|
|
3458
4542
|
);
|
|
3459
4543
|
}
|
|
3460
4544
|
function isAttachmentUpToDate(entry, item, dest) {
|
|
3461
|
-
if (!entry || !
|
|
4545
|
+
if (!entry || !existsSync9(dest)) return false;
|
|
3462
4546
|
if (entry.name !== item.name) return false;
|
|
3463
4547
|
const createdAt = item.createdAt ?? "";
|
|
3464
4548
|
return entry.createdAt === createdAt;
|
|
@@ -3473,7 +4557,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
|
|
|
3473
4557
|
const nextManifest = { version: 1, attachments: {} };
|
|
3474
4558
|
const names = [];
|
|
3475
4559
|
for (const item of attachments) {
|
|
3476
|
-
const dest =
|
|
4560
|
+
const dest = join6(dir, item.name);
|
|
3477
4561
|
const entry = manifest.attachments[item.id];
|
|
3478
4562
|
const createdAt = item.createdAt ?? "";
|
|
3479
4563
|
names.push(item.name);
|
|
@@ -3483,7 +4567,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
|
|
|
3483
4567
|
continue;
|
|
3484
4568
|
}
|
|
3485
4569
|
const buffer = await downloadAttachment(cfg2, item.id);
|
|
3486
|
-
|
|
4570
|
+
writeFileSync6(dest, buffer);
|
|
3487
4571
|
nextManifest.attachments[item.id] = {
|
|
3488
4572
|
name: item.name,
|
|
3489
4573
|
createdAt
|
|
@@ -3504,9 +4588,9 @@ async function syncWebIdeAttachments(cfg2, taskId, workdir, attachments) {
|
|
|
3504
4588
|
}
|
|
3505
4589
|
|
|
3506
4590
|
// src/commands/sync-webide-design.ts
|
|
3507
|
-
import { createHash } from "crypto";
|
|
3508
|
-
import { existsSync as
|
|
3509
|
-
import { join as
|
|
4591
|
+
import { createHash as createHash3 } from "crypto";
|
|
4592
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
4593
|
+
import { join as join7 } from "path";
|
|
3510
4594
|
|
|
3511
4595
|
// src/opc-shared-types.ts
|
|
3512
4596
|
function webIdeDesignObjectKey(taskId, fileName) {
|
|
@@ -3522,17 +4606,17 @@ function webIdeDesignArtifactContentType(name) {
|
|
|
3522
4606
|
}
|
|
3523
4607
|
|
|
3524
4608
|
// src/commands/sync-webide-design.ts
|
|
3525
|
-
var
|
|
4609
|
+
var MANIFEST_FILE3 = ".sync-manifest.json";
|
|
3526
4610
|
function hashBuffer(buf) {
|
|
3527
|
-
return
|
|
4611
|
+
return createHash3("sha256").update(buf).digest("hex");
|
|
3528
4612
|
}
|
|
3529
4613
|
function loadManifest2(dir) {
|
|
3530
|
-
const
|
|
3531
|
-
if (!
|
|
4614
|
+
const path3 = join7(dir, MANIFEST_FILE3);
|
|
4615
|
+
if (!existsSync10(path3)) {
|
|
3532
4616
|
return { version: 1, files: {} };
|
|
3533
4617
|
}
|
|
3534
4618
|
try {
|
|
3535
|
-
const parsed = JSON.parse(readFileSync8(
|
|
4619
|
+
const parsed = JSON.parse(readFileSync8(path3, "utf8"));
|
|
3536
4620
|
if (parsed?.version === 1 && parsed.files && typeof parsed.files === "object") {
|
|
3537
4621
|
return parsed;
|
|
3538
4622
|
}
|
|
@@ -3541,8 +4625,8 @@ function loadManifest2(dir) {
|
|
|
3541
4625
|
return { version: 1, files: {} };
|
|
3542
4626
|
}
|
|
3543
4627
|
function saveManifest2(dir, manifest) {
|
|
3544
|
-
|
|
3545
|
-
|
|
4628
|
+
writeFileSync7(
|
|
4629
|
+
join7(dir, MANIFEST_FILE3),
|
|
3546
4630
|
`${JSON.stringify(manifest, null, 2)}
|
|
3547
4631
|
`,
|
|
3548
4632
|
"utf8"
|
|
@@ -3570,13 +4654,13 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
|
|
|
3570
4654
|
for (const art of artifacts) {
|
|
3571
4655
|
const name = art.name?.trim();
|
|
3572
4656
|
if (!name || !isWebIdeDesignArtifactName(name)) continue;
|
|
3573
|
-
const localPath =
|
|
4657
|
+
const localPath = join7(dir, name);
|
|
3574
4658
|
const entry = manifest.files[name];
|
|
3575
|
-
if (entry?.contentHash === art.contentHash &&
|
|
4659
|
+
if (entry?.contentHash === art.contentHash && existsSync10(localPath)) {
|
|
3576
4660
|
continue;
|
|
3577
4661
|
}
|
|
3578
4662
|
const buf = await downloadDesignFile(cfg2, taskId, name);
|
|
3579
|
-
|
|
4663
|
+
writeFileSync7(localPath, buf);
|
|
3580
4664
|
manifest.files[name] = { contentHash: hashBuffer(buf) };
|
|
3581
4665
|
synced.push(name);
|
|
3582
4666
|
}
|
|
@@ -3585,12 +4669,12 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
|
|
|
3585
4669
|
}
|
|
3586
4670
|
function listLocalDesignArtifactFiles(taskId, workdir) {
|
|
3587
4671
|
const dir = webideDesignDir(taskId, workdir);
|
|
3588
|
-
if (!
|
|
4672
|
+
if (!existsSync10(dir)) return [];
|
|
3589
4673
|
const out = [];
|
|
3590
|
-
for (const name of
|
|
4674
|
+
for (const name of readdirSync5(dir)) {
|
|
3591
4675
|
if (!isWebIdeDesignArtifactName(name)) continue;
|
|
3592
|
-
const absPath =
|
|
3593
|
-
if (!
|
|
4676
|
+
const absPath = join7(dir, name);
|
|
4677
|
+
if (!statSync5(absPath).isFile()) continue;
|
|
3594
4678
|
const buf = readFileSync8(absPath);
|
|
3595
4679
|
if (buf.length === 0) continue;
|
|
3596
4680
|
out.push({ name, absPath, contentHash: hashBuffer(buf) });
|
|
@@ -3675,6 +4759,8 @@ function startActionForInbound(action) {
|
|
|
3675
4759
|
case "request-develop":
|
|
3676
4760
|
case "skip-plan":
|
|
3677
4761
|
return "start-develop";
|
|
4762
|
+
case "request-start-project":
|
|
4763
|
+
return "start-project";
|
|
3678
4764
|
case "request-execute-sql":
|
|
3679
4765
|
return "start-execute-sql";
|
|
3680
4766
|
case "request-bugfix":
|
|
@@ -3709,6 +4795,9 @@ function completeActionForInbound(action) {
|
|
|
3709
4795
|
case "skip-plan":
|
|
3710
4796
|
case "start-develop":
|
|
3711
4797
|
return "complete-develop";
|
|
4798
|
+
case "request-start-project":
|
|
4799
|
+
case "start-project":
|
|
4800
|
+
return "complete-project";
|
|
3712
4801
|
case "request-execute-sql":
|
|
3713
4802
|
case "start-execute-sql":
|
|
3714
4803
|
return "complete-execute-sql";
|
|
@@ -3780,6 +4869,9 @@ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
3780
4869
|
function shouldCommitAfterWebIdeMessage(action) {
|
|
3781
4870
|
return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
|
|
3782
4871
|
}
|
|
4872
|
+
function isStartLocalServicesAction(action) {
|
|
4873
|
+
return action === "enter-manual-test" || action === "skip-test" || action === "request-start-project";
|
|
4874
|
+
}
|
|
3783
4875
|
function isDesignAction(action) {
|
|
3784
4876
|
return action === "request-design" || action === "request-revise-design";
|
|
3785
4877
|
}
|
|
@@ -3790,11 +4882,11 @@ function syncLocalTaskStatus(workdir, taskId, msg2, status) {
|
|
|
3790
4882
|
status
|
|
3791
4883
|
});
|
|
3792
4884
|
}
|
|
3793
|
-
async function
|
|
4885
|
+
async function updateStatus2(cfg2, messageId, status) {
|
|
3794
4886
|
const api = createApmApiClient(cfg2);
|
|
3795
4887
|
await api.cli.webideUpdateMessageStatus({ id: messageId, status });
|
|
3796
4888
|
}
|
|
3797
|
-
async function
|
|
4889
|
+
async function setError2(cfg2, messageId, error) {
|
|
3798
4890
|
const api = createApmApiClient(cfg2);
|
|
3799
4891
|
await api.cli.webideSetMessageError({ id: messageId, error });
|
|
3800
4892
|
}
|
|
@@ -3813,7 +4905,7 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
3813
4905
|
console.log(
|
|
3814
4906
|
`[apm] webide-message action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} taskId=${taskId} messageId=${messageId}`
|
|
3815
4907
|
);
|
|
3816
|
-
await
|
|
4908
|
+
await updateStatus2(cfg2, messageId, "TYPING");
|
|
3817
4909
|
syncLocalTaskStatus(workdir, taskId, msg2, "TYPING");
|
|
3818
4910
|
const eventSession = new EventSession(msg2.content);
|
|
3819
4911
|
const prepAgentId = loadWebIdeAgentId(workdir, taskId) ?? "webide-cli-prep";
|
|
@@ -3904,13 +4996,10 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
3904
4996
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3905
4997
|
await runPrepStep(
|
|
3906
4998
|
"\u51C6\u5907\u7279\u6027\u5206\u652F",
|
|
3907
|
-
async () => {
|
|
3908
|
-
const projectId = await resolveProjectIdForTask(taskId);
|
|
3909
|
-
return runTaskBranch(taskId, projectId, { cwd: workdir });
|
|
3910
|
-
},
|
|
4999
|
+
async () => runTaskBranch(taskId, { cwd: workdir }),
|
|
3911
5000
|
(branchResult) => {
|
|
3912
5001
|
markBranchDone(branchCacheKey, workdir);
|
|
3913
|
-
return `kind=${branchResult.kind} branch=${branchResult.branch}
|
|
5002
|
+
return `kind=${branchResult.kind} branch=${branchResult.branch} baseline=${branchResult.baseline} repos=${branchResult.repos.length}`;
|
|
3914
5003
|
}
|
|
3915
5004
|
);
|
|
3916
5005
|
} else {
|
|
@@ -3936,6 +5025,43 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
3936
5025
|
}
|
|
3937
5026
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3938
5027
|
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
|
+
);
|
|
3939
5065
|
await runPrepStep(
|
|
3940
5066
|
"\u540C\u6B65\u4EFB\u52A1\u9644\u4EF6",
|
|
3941
5067
|
async () => {
|
|
@@ -3986,17 +5112,18 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
3986
5112
|
"running"
|
|
3987
5113
|
);
|
|
3988
5114
|
await syncPrepLog();
|
|
5115
|
+
const startLocalServices = isStartLocalServicesAction(effectiveAction);
|
|
3989
5116
|
const designAction = isDesignAction(effectiveAction);
|
|
3990
5117
|
const useNewAgent = msg2.useNewAgent === true;
|
|
3991
5118
|
const toolNames = Array.isArray(msg2.tools) ? msg2.tools.map((t) => String(t).trim()).filter(Boolean) : [];
|
|
3992
|
-
if (useNewAgent) {
|
|
5119
|
+
if (useNewAgent && !startLocalServices) {
|
|
3993
5120
|
if (designAction) {
|
|
3994
5121
|
clearWebIdeDesignAgentId(workdir, taskId);
|
|
3995
5122
|
} else {
|
|
3996
5123
|
clearWebIdeAgentId(workdir, taskId);
|
|
3997
5124
|
}
|
|
3998
5125
|
}
|
|
3999
|
-
const savedAgentId = useNewAgent ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
|
|
5126
|
+
const savedAgentId = startLocalServices || useNewAgent ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
|
|
4000
5127
|
const promptPayload = toolNames.includes("MysqlExecute") ? continuePayload ?? parseWebIdePromptPayload(msg2.content) : null;
|
|
4001
5128
|
const sqlExecutionId = toolNames.includes("MysqlExecute") && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
|
|
4002
5129
|
if (toolNames.includes("MysqlExecute") && !sqlExecutionId) {
|
|
@@ -4005,6 +5132,7 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
4005
5132
|
);
|
|
4006
5133
|
}
|
|
4007
5134
|
const persistAgentId = (agentId) => {
|
|
5135
|
+
if (startLocalServices) return;
|
|
4008
5136
|
if (designAction) {
|
|
4009
5137
|
saveWebIdeDesignAgentId(workdir, taskId, agentId);
|
|
4010
5138
|
} else {
|
|
@@ -4030,7 +5158,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4030
5158
|
forceSend: true,
|
|
4031
5159
|
eventSession,
|
|
4032
5160
|
appendMessageContent: (content) => appendContent(cfg2, messageId, content),
|
|
4033
|
-
askQuestionExecute: toolNames.includes("AskQuestion") ? createWebIdeAskQuestionExecute({
|
|
5161
|
+
askQuestionExecute: !startLocalServices && toolNames.includes("AskQuestion") ? createWebIdeAskQuestionExecute({
|
|
4034
5162
|
cfg: cfg2,
|
|
4035
5163
|
taskId,
|
|
4036
5164
|
onAwaitingAssumptions: () => {
|
|
@@ -4042,10 +5170,11 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4042
5170
|
enableAskQuestion: false,
|
|
4043
5171
|
enableWebIdePlanTools: false,
|
|
4044
5172
|
enableMysqlTools: false,
|
|
5173
|
+
enablePtySessionMcp: startLocalServices || toolNames.includes("PtySession"),
|
|
4045
5174
|
sqlExecutionId,
|
|
4046
5175
|
toolNames,
|
|
4047
5176
|
enableSandbox: false,
|
|
4048
|
-
onInvalidatePersistedAgentId: () => {
|
|
5177
|
+
onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => {
|
|
4049
5178
|
if (designAction) {
|
|
4050
5179
|
clearWebIdeDesignAgentId(workdir, taskId);
|
|
4051
5180
|
} else {
|
|
@@ -4098,7 +5227,9 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4098
5227
|
}
|
|
4099
5228
|
}
|
|
4100
5229
|
);
|
|
4101
|
-
|
|
5230
|
+
if (!startLocalServices) {
|
|
5231
|
+
persistAgentId(outcome.agentId);
|
|
5232
|
+
}
|
|
4102
5233
|
const tokenUsage = outcome.usage != null ? {
|
|
4103
5234
|
modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
|
|
4104
5235
|
inputTokens: outcome.usage.inputTokens,
|
|
@@ -4132,7 +5263,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4132
5263
|
);
|
|
4133
5264
|
}
|
|
4134
5265
|
}
|
|
4135
|
-
await
|
|
5266
|
+
await setError2(cfg2, messageId, detail);
|
|
4136
5267
|
syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
|
|
4137
5268
|
return;
|
|
4138
5269
|
}
|
|
@@ -4157,7 +5288,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4157
5288
|
);
|
|
4158
5289
|
}
|
|
4159
5290
|
}
|
|
4160
|
-
await
|
|
5291
|
+
await updateStatus2(cfg2, messageId, "CANCELLED");
|
|
4161
5292
|
syncLocalTaskStatus(workdir, taskId, msg2, "CANCELLED");
|
|
4162
5293
|
return;
|
|
4163
5294
|
}
|
|
@@ -4202,7 +5333,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4202
5333
|
detail,
|
|
4203
5334
|
tokenUsage
|
|
4204
5335
|
);
|
|
4205
|
-
await
|
|
5336
|
+
await setError2(cfg2, messageId, detail);
|
|
4206
5337
|
syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
|
|
4207
5338
|
return;
|
|
4208
5339
|
}
|
|
@@ -4279,6 +5410,16 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4279
5410
|
}
|
|
4280
5411
|
}
|
|
4281
5412
|
}
|
|
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
|
+
}
|
|
4282
5423
|
await logSyncRef.current?.markRun(
|
|
4283
5424
|
outcome.runId,
|
|
4284
5425
|
"finished",
|
|
@@ -4293,11 +5434,21 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4293
5434
|
`[apm] \u672C\u8F6E\u505C\u5728\u5F85\u786E\u8BA4\u5047\u8BBE\uFF0C\u8DF3\u8FC7 complete action=${effectiveAction} taskId=${taskId}`
|
|
4294
5435
|
);
|
|
4295
5436
|
}
|
|
4296
|
-
await
|
|
5437
|
+
await updateStatus2(cfg2, messageId, "SUCCESS");
|
|
4297
5438
|
syncLocalTaskStatus(workdir, taskId, msg2, "SUCCESS");
|
|
4298
5439
|
console.log(
|
|
4299
5440
|
`[apm] webide-message \u5B8C\u6210 action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} messageId=${messageId} agentId=${outcome.agentId}`
|
|
4300
5441
|
);
|
|
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
|
+
}
|
|
4301
5452
|
} catch (err) {
|
|
4302
5453
|
const detail = err instanceof Error ? err.message : String(err);
|
|
4303
5454
|
console.error(`[apm] webide-message \u5931\u8D25: ${detail}`);
|
|
@@ -4308,7 +5459,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4308
5459
|
clearWebIdeAgentId(workdir, taskId);
|
|
4309
5460
|
}
|
|
4310
5461
|
try {
|
|
4311
|
-
await
|
|
5462
|
+
await setError2(cfg2, messageId, detail);
|
|
4312
5463
|
} catch (statusErr) {
|
|
4313
5464
|
console.error(
|
|
4314
5465
|
"[apm] \u5199\u5165 WebIDE FAILED \u5931\u8D25:",
|
|
@@ -4332,7 +5483,23 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4332
5483
|
|
|
4333
5484
|
// src/commands/connect/core/worker/handlers/index.ts
|
|
4334
5485
|
async function dispatchWorkerJob(cfg2, msg2, signal) {
|
|
4335
|
-
|
|
5486
|
+
switch (msg2.type) {
|
|
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
|
+
}
|
|
4336
5503
|
}
|
|
4337
5504
|
|
|
4338
5505
|
// src/commands/connect/core/worker/worker-script.ts
|