ai-project-manage-cli 8.1.24 → 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 -800
- package/dist/worker-script.js +2731 -1547
- 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,229 +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
552
|
// src/utils/workspace-repos.ts
|
|
583
553
|
import {
|
|
584
|
-
existsSync as
|
|
554
|
+
existsSync as existsSync2,
|
|
585
555
|
mkdirSync as mkdirSync3,
|
|
586
|
-
readFileSync as
|
|
556
|
+
readFileSync as readFileSync2,
|
|
587
557
|
readdirSync as readdirSync2,
|
|
588
558
|
statSync as statSync2,
|
|
589
559
|
writeFileSync as writeFileSync2
|
|
590
560
|
} from "fs";
|
|
591
|
-
import { basename, dirname
|
|
592
|
-
|
|
593
|
-
// src/utils/git-remote.ts
|
|
594
|
-
import { execFile as execFile2 } from "child_process";
|
|
595
|
-
import { promisify as promisify2 } from "util";
|
|
596
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
597
|
-
function toHttpsGitRemoteUrl(raw) {
|
|
598
|
-
let s = raw.trim();
|
|
599
|
-
if (!s) return null;
|
|
600
|
-
const sshScp = /^git@([^:]+):(.+)$/.exec(s);
|
|
601
|
-
if (sshScp) {
|
|
602
|
-
s = `https://${sshScp[1]}/${sshScp[2]}`;
|
|
603
|
-
} else {
|
|
604
|
-
const sshUri = /^ssh:\/\/(?:git@)?([^/]+)\/(.+)$/i.exec(s);
|
|
605
|
-
if (sshUri) {
|
|
606
|
-
s = `https://${sshUri[1]}/${sshUri[2]}`;
|
|
607
|
-
} else if (/^http:\/\//i.test(s)) {
|
|
608
|
-
s = `https://${s.slice("http://".length)}`;
|
|
609
|
-
} else if (!/^https:\/\//i.test(s)) {
|
|
610
|
-
const scpLike = /^([^/:]+):(.+)$/.exec(s);
|
|
611
|
-
if (scpLike && !scpLike[1].includes(".")) {
|
|
612
|
-
return null;
|
|
613
|
-
}
|
|
614
|
-
if (scpLike) {
|
|
615
|
-
s = `https://${scpLike[1]}/${scpLike[2]}`;
|
|
616
|
-
} else if (s.includes("/")) {
|
|
617
|
-
s = `https://${s}`;
|
|
618
|
-
} else {
|
|
619
|
-
return null;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
s = s.replace(/\/+$/, "");
|
|
624
|
-
if (!/^https:\/\//i.test(s)) return null;
|
|
625
|
-
return s;
|
|
626
|
-
}
|
|
627
|
-
async function tryReadGitOriginUrl(cwd) {
|
|
628
|
-
try {
|
|
629
|
-
const { stdout } = await execFileAsync2(
|
|
630
|
-
"git",
|
|
631
|
-
["config", "--get", "remote.origin.url"],
|
|
632
|
-
{ cwd, encoding: "utf8", maxBuffer: 1024 * 1024 }
|
|
633
|
-
);
|
|
634
|
-
const url = stdout.trim();
|
|
635
|
-
return url || null;
|
|
636
|
-
} catch {
|
|
637
|
-
return null;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
async function tryReadHttpsGitOriginUrl(cwd) {
|
|
641
|
-
const raw = await tryReadGitOriginUrl(cwd);
|
|
642
|
-
if (!raw) return null;
|
|
643
|
-
return toHttpsGitRemoteUrl(raw);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// src/utils/workspace-repos.ts
|
|
561
|
+
import { basename, dirname, join as join3, relative, resolve as resolve3 } from "path";
|
|
647
562
|
var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
|
|
648
563
|
var WORKSPACE_REPOS_VERSION = 1;
|
|
649
564
|
var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([".apm", "node_modules"]);
|
|
650
565
|
function hasGitAt(dir) {
|
|
651
|
-
return
|
|
566
|
+
return existsSync2(toFsPath(join3(dir, ".git")));
|
|
652
567
|
}
|
|
653
568
|
function isSkippableChildName(name) {
|
|
654
569
|
if (SKIP_DIR_NAMES.has(name)) return true;
|
|
@@ -656,21 +571,25 @@ function isSkippableChildName(name) {
|
|
|
656
571
|
return false;
|
|
657
572
|
}
|
|
658
573
|
function manifestPath(workdir) {
|
|
659
|
-
return
|
|
574
|
+
return join3(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
|
|
660
575
|
}
|
|
661
576
|
function absoluteRepoPath(workdir, entry) {
|
|
662
|
-
|
|
577
|
+
if (entry.path === "." || entry.path === "") {
|
|
578
|
+
return workdir;
|
|
579
|
+
}
|
|
580
|
+
return resolve3(workdir, entry.path);
|
|
663
581
|
}
|
|
664
582
|
function toRelativeRepoPath(workdir, repoAbs) {
|
|
665
|
-
|
|
583
|
+
const rel = relative(workdir, repoAbs).replace(/\\/g, "/");
|
|
584
|
+
return rel === "" ? "." : rel;
|
|
666
585
|
}
|
|
667
586
|
function normalizeRepoEntry(raw) {
|
|
668
587
|
if (!raw || typeof raw !== "object") return null;
|
|
669
588
|
const o = raw;
|
|
670
589
|
if (typeof o.path !== "string" || !o.path.trim()) return null;
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
590
|
+
const entry = {
|
|
591
|
+
path: o.path.trim().replace(/\\/g, "/")
|
|
592
|
+
};
|
|
674
593
|
if (typeof o.remoteUrl === "string" && o.remoteUrl.trim()) {
|
|
675
594
|
entry.remoteUrl = o.remoteUrl.trim();
|
|
676
595
|
} else if (o.remoteUrl === null) {
|
|
@@ -679,18 +598,18 @@ function normalizeRepoEntry(raw) {
|
|
|
679
598
|
return entry;
|
|
680
599
|
}
|
|
681
600
|
function readManifest(workdir) {
|
|
682
|
-
const
|
|
683
|
-
if (!
|
|
601
|
+
const path3 = toFsPath(manifestPath(workdir));
|
|
602
|
+
if (!existsSync2(path3)) {
|
|
684
603
|
return null;
|
|
685
604
|
}
|
|
686
605
|
try {
|
|
687
606
|
const raw = JSON.parse(
|
|
688
|
-
|
|
607
|
+
readFileSync2(path3, "utf8")
|
|
689
608
|
);
|
|
690
609
|
if (raw?.version !== WORKSPACE_REPOS_VERSION) {
|
|
691
610
|
return null;
|
|
692
611
|
}
|
|
693
|
-
if (raw.kind !== "multi") {
|
|
612
|
+
if (raw.kind !== "single" && raw.kind !== "multi") {
|
|
694
613
|
return null;
|
|
695
614
|
}
|
|
696
615
|
if (!Array.isArray(raw.repos) || raw.repos.length === 0) {
|
|
@@ -705,7 +624,7 @@ function readManifest(workdir) {
|
|
|
705
624
|
}
|
|
706
625
|
return {
|
|
707
626
|
version: WORKSPACE_REPOS_VERSION,
|
|
708
|
-
kind:
|
|
627
|
+
kind: raw.kind,
|
|
709
628
|
workdir: raw.workdir,
|
|
710
629
|
repos,
|
|
711
630
|
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt.trim() ? raw.scannedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -717,8 +636,8 @@ function readManifest(workdir) {
|
|
|
717
636
|
function writeManifest(workdir, manifest) {
|
|
718
637
|
const apmDir = toFsPath(workspaceApmDir(workdir));
|
|
719
638
|
mkdirSync3(apmDir, { recursive: true });
|
|
720
|
-
const
|
|
721
|
-
writeFileSync2(
|
|
639
|
+
const path3 = toFsPath(manifestPath(workdir));
|
|
640
|
+
writeFileSync2(path3, `${JSON.stringify(manifest, null, 2)}
|
|
722
641
|
`, "utf8");
|
|
723
642
|
}
|
|
724
643
|
function manifestStillValid(workdir, manifest) {
|
|
@@ -729,9 +648,6 @@ function manifestStillValid(workdir, manifest) {
|
|
|
729
648
|
if (typeof entry?.path !== "string" || !entry.path.trim()) {
|
|
730
649
|
return false;
|
|
731
650
|
}
|
|
732
|
-
if (entry.path === "." || entry.path.includes("/")) {
|
|
733
|
-
return false;
|
|
734
|
-
}
|
|
735
651
|
const abs = absoluteRepoPath(workdir, entry);
|
|
736
652
|
if (!hasGitAt(abs)) {
|
|
737
653
|
return false;
|
|
@@ -741,8 +657,17 @@ function manifestStillValid(workdir, manifest) {
|
|
|
741
657
|
}
|
|
742
658
|
function scanWorkspaceRepos(workdirInput) {
|
|
743
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
|
+
}
|
|
744
669
|
const fsWorkdir = toFsPath(workdir);
|
|
745
|
-
if (!
|
|
670
|
+
if (!existsSync2(fsWorkdir) || !statSync2(fsWorkdir).isDirectory()) {
|
|
746
671
|
throw new Error(`[apm] \u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${workdir}`);
|
|
747
672
|
}
|
|
748
673
|
const repos = [];
|
|
@@ -750,9 +675,9 @@ function scanWorkspaceRepos(workdirInput) {
|
|
|
750
675
|
if (isSkippableChildName(name)) {
|
|
751
676
|
continue;
|
|
752
677
|
}
|
|
753
|
-
const childAbs =
|
|
678
|
+
const childAbs = resolve3(workdir, name);
|
|
754
679
|
const childFs = toFsPath(childAbs);
|
|
755
|
-
if (!
|
|
680
|
+
if (!existsSync2(childFs) || !statSync2(childFs).isDirectory()) {
|
|
756
681
|
continue;
|
|
757
682
|
}
|
|
758
683
|
if (!hasGitAt(childAbs)) {
|
|
@@ -763,7 +688,7 @@ function scanWorkspaceRepos(workdirInput) {
|
|
|
763
688
|
repos.sort((a, b) => a.path.localeCompare(b.path));
|
|
764
689
|
if (repos.length === 0) {
|
|
765
690
|
throw new Error(
|
|
766
|
-
`[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`
|
|
767
692
|
);
|
|
768
693
|
}
|
|
769
694
|
return {
|
|
@@ -791,7 +716,7 @@ function loadWorkspaceReposCache(workdirInput) {
|
|
|
791
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`);
|
|
792
717
|
}
|
|
793
718
|
console.log(
|
|
794
|
-
`[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(", ")}`
|
|
795
720
|
);
|
|
796
721
|
return {
|
|
797
722
|
...cached,
|
|
@@ -829,7 +754,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
|
|
|
829
754
|
const cached = readManifest(workdir);
|
|
830
755
|
if (cached && manifestStillValid(workdir, cached)) {
|
|
831
756
|
console.log(
|
|
832
|
-
`[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}`
|
|
833
758
|
);
|
|
834
759
|
return cached;
|
|
835
760
|
}
|
|
@@ -846,7 +771,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
|
|
|
846
771
|
}
|
|
847
772
|
writeManifest(workdir, scanned);
|
|
848
773
|
console.log(
|
|
849
|
-
`[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}`
|
|
850
775
|
);
|
|
851
776
|
return scanned;
|
|
852
777
|
}
|
|
@@ -857,69 +782,48 @@ function resolveWorkspaceRepoAbsolutePaths(manifest) {
|
|
|
857
782
|
}
|
|
858
783
|
function formatRepoLabel(workdir, repoAbs) {
|
|
859
784
|
const rel = toRelativeRepoPath(resolveWorkdirPath(workdir), repoAbs);
|
|
860
|
-
return rel
|
|
785
|
+
return rel === "." ? basename(repoAbs) || repoAbs : rel;
|
|
861
786
|
}
|
|
862
787
|
|
|
863
788
|
// src/commands/branch.ts
|
|
864
789
|
var TASK_BRANCH_PREFIX = "feat/task-";
|
|
865
|
-
async function
|
|
866
|
-
|
|
867
|
-
|
|
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
|
+
}
|
|
868
801
|
}
|
|
869
|
-
async function
|
|
870
|
-
|
|
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;
|
|
871
812
|
}
|
|
872
|
-
async function
|
|
873
|
-
const opts = typeof options === "string" ? { message: options } : options;
|
|
813
|
+
async function commitWorkspaceReposIfDirty(workdir, message) {
|
|
874
814
|
const workdirPath = resolveWorkdirPath(workdir);
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
opts.reposManifest
|
|
878
|
-
);
|
|
815
|
+
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
816
|
+
const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
879
817
|
let committed = 0;
|
|
880
|
-
let commitSkipped = 0;
|
|
881
|
-
let pushed = 0;
|
|
882
|
-
let pushSkipped = 0;
|
|
883
|
-
const failed = [];
|
|
884
|
-
const message = opts.message;
|
|
885
818
|
for (const gitRoot of repoRoots) {
|
|
886
819
|
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
console.log(`[apm] \u5DF2\u63D0\u4EA4 @ ${label}`);
|
|
892
|
-
} else {
|
|
893
|
-
commitSkipped += 1;
|
|
894
|
-
}
|
|
895
|
-
} catch (err) {
|
|
896
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
897
|
-
console.error(`[apm] \u4ED3\u5E93 ${label} \u63D0\u4EA4\u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
|
|
898
|
-
failed.push(label);
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
try {
|
|
902
|
-
const didPush = await pushCurrentBranch(gitRoot);
|
|
903
|
-
if (didPush) {
|
|
904
|
-
pushed += 1;
|
|
905
|
-
console.log(`[apm] \u5DF2 push @ ${label}`);
|
|
906
|
-
} else {
|
|
907
|
-
pushSkipped += 1;
|
|
908
|
-
}
|
|
909
|
-
} catch (err) {
|
|
910
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
911
|
-
console.error(`[apm] \u4ED3\u5E93 ${label} push \u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
|
|
912
|
-
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}`);
|
|
913
824
|
}
|
|
914
825
|
}
|
|
915
|
-
return
|
|
916
|
-
committed,
|
|
917
|
-
commitSkipped,
|
|
918
|
-
pushed,
|
|
919
|
-
pushSkipped,
|
|
920
|
-
failed,
|
|
921
|
-
repos: repoRoots
|
|
922
|
-
};
|
|
826
|
+
return committed;
|
|
923
827
|
}
|
|
924
828
|
async function pushWorkspaceRepos(workdir) {
|
|
925
829
|
const workdirPath = resolveWorkdirPath(workdir);
|
|
@@ -948,99 +852,136 @@ function branchNameForTask(taskId) {
|
|
|
948
852
|
}
|
|
949
853
|
return `${TASK_BRANCH_PREFIX}${id}`;
|
|
950
854
|
}
|
|
951
|
-
async function
|
|
855
|
+
async function ensureFeatureBranch(branch, baselineBranch, options) {
|
|
952
856
|
const cwd = options.cwd ?? process.cwd();
|
|
953
857
|
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
954
|
-
await
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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();
|
|
908
|
+
if (!trimmedTaskId) {
|
|
909
|
+
throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
961
910
|
}
|
|
962
911
|
const cfg2 = await ensureLoggedConfig();
|
|
963
912
|
const api = createApmApiClient(cfg2);
|
|
964
913
|
const projectBaseline = await api.cli.projectBaseBranch({
|
|
965
914
|
taskId: trimmedTaskId
|
|
966
915
|
});
|
|
967
|
-
const
|
|
968
|
-
if (!
|
|
969
|
-
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");
|
|
970
919
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const
|
|
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);
|
|
978
927
|
console.log(
|
|
979
|
-
`[apm] \
|
|
928
|
+
`[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (project baseline=${baselineBranch})`
|
|
980
929
|
);
|
|
981
|
-
|
|
930
|
+
await ensureFeatureBranch(branch, baselineBranch, {
|
|
931
|
+
...options,
|
|
932
|
+
cwd: gitRoot
|
|
933
|
+
});
|
|
982
934
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
);
|
|
990
|
-
return roots;
|
|
935
|
+
return {
|
|
936
|
+
branch,
|
|
937
|
+
baseline: baselineBranch,
|
|
938
|
+
kind: manifest.kind,
|
|
939
|
+
repos: repoRoots
|
|
940
|
+
};
|
|
991
941
|
}
|
|
992
|
-
async function
|
|
993
|
-
const
|
|
994
|
-
if (!
|
|
995
|
-
throw new Error("[apm]
|
|
996
|
-
}
|
|
997
|
-
const trimmedProjectId = projectId.trim();
|
|
998
|
-
if (!trimmedProjectId) {
|
|
999
|
-
throw new Error("[apm] projectId\uFF08\u9879\u76EE ID\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
|
|
1000
|
-
}
|
|
1001
|
-
if (/[\s\\]/.test(trimmedProjectId) || trimmedProjectId.includes("..")) {
|
|
1002
|
-
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");
|
|
1003
946
|
}
|
|
1004
947
|
const cwd = options.cwd ?? process.cwd();
|
|
1005
948
|
const workdirPath = resolveWorkdirPath(cwd);
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
options.reposManifest
|
|
1009
|
-
);
|
|
1010
|
-
if (repoRoots.length === 0) {
|
|
1011
|
-
throw new Error("[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7279\u6027\u5206\u652F");
|
|
1012
|
-
}
|
|
1013
|
-
const branch = branchNameForTask(trimmedTaskId);
|
|
1014
|
-
const repoLabels = repoRoots.map(
|
|
1015
|
-
(gitRoot) => formatRepoLabel(workdirPath, gitRoot)
|
|
1016
|
-
);
|
|
1017
|
-
console.log(
|
|
1018
|
-
`[apm] \u591A\u4ED3\u786E\u8BA4\u7279\u6027\u5206\u652F ${branch} projectId=${trimmedProjectId} repos=${repoLabels.join(",")}`
|
|
1019
|
-
);
|
|
949
|
+
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
950
|
+
const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
1020
951
|
for (const gitRoot of repoRoots) {
|
|
1021
952
|
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
1022
|
-
console.log(
|
|
1023
|
-
|
|
1024
|
-
);
|
|
1025
|
-
try {
|
|
1026
|
-
await ensureTaskFeatureBranch(branch, trimmedProjectId, {
|
|
1027
|
-
...options,
|
|
1028
|
-
cwd: gitRoot
|
|
1029
|
-
});
|
|
1030
|
-
} catch (err) {
|
|
1031
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
1032
|
-
throw new Error(
|
|
1033
|
-
`[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}`
|
|
1034
|
-
);
|
|
1035
|
-
}
|
|
953
|
+
console.log(`[apm] \u5207\u6362\u57FA\u7EBF\u5206\u652F ${baseline} @ ${label}`);
|
|
954
|
+
await checkoutBaselineBranch(baseline, { ...options, cwd: gitRoot });
|
|
1036
955
|
}
|
|
1037
956
|
return {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
kind: "multi",
|
|
957
|
+
baseline,
|
|
958
|
+
kind: manifest.kind,
|
|
1041
959
|
repos: repoRoots
|
|
1042
960
|
};
|
|
1043
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
|
+
}
|
|
1044
985
|
|
|
1045
986
|
// src/commands/connect/cursor-agent.ts
|
|
1046
987
|
import {
|
|
@@ -1690,8 +1631,8 @@ function createUpsertWebIdeTestCasesTool(options) {
|
|
|
1690
1631
|
}
|
|
1691
1632
|
|
|
1692
1633
|
// src/commands/connect/tools/mysql-tools.ts
|
|
1693
|
-
import { existsSync as
|
|
1694
|
-
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";
|
|
1695
1636
|
import mysql from "mysql2/promise";
|
|
1696
1637
|
var MAX_RESULT_ROWS = 100;
|
|
1697
1638
|
var EXEC_TIMEOUT_MS = 6e4;
|
|
@@ -1762,14 +1703,14 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
|
|
|
1762
1703
|
if (!isAbsolute(absolutePath)) {
|
|
1763
1704
|
return { ok: false, error: "path \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
|
|
1764
1705
|
}
|
|
1765
|
-
const workdirAbs =
|
|
1766
|
-
const targetAbs =
|
|
1706
|
+
const workdirAbs = resolve4(toFsPath(workdir));
|
|
1707
|
+
const targetAbs = resolve4(toFsPath(absolutePath));
|
|
1767
1708
|
const rel = relative2(workdirAbs, targetAbs);
|
|
1768
1709
|
if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
|
|
1769
1710
|
return { ok: false, error: "path \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
|
|
1770
1711
|
}
|
|
1771
1712
|
const fsPath = toFsPath(targetAbs);
|
|
1772
|
-
if (!
|
|
1713
|
+
if (!existsSync3(fsPath)) {
|
|
1773
1714
|
return { ok: false, error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${absolutePath}` };
|
|
1774
1715
|
}
|
|
1775
1716
|
let st;
|
|
@@ -1791,7 +1732,7 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
|
|
|
1791
1732
|
return { ok: true, absPath: targetAbs, displayPath };
|
|
1792
1733
|
}
|
|
1793
1734
|
function readSqlFile(absPath) {
|
|
1794
|
-
return
|
|
1735
|
+
return readFileSync3(toFsPath(absPath), "utf8");
|
|
1795
1736
|
}
|
|
1796
1737
|
function createMysqlExecuteTool(options) {
|
|
1797
1738
|
const { taskId, executionId, workdir, upsertSqlExecution } = options;
|
|
@@ -1969,1211 +1910,2371 @@ function createMysqlExecuteTool(options) {
|
|
|
1969
1910
|
};
|
|
1970
1911
|
}
|
|
1971
1912
|
|
|
1972
|
-
// src/commands/connect/tools/
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
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
|
+
});
|
|
1983
1935
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
const enableAsk = options.enableAskQuestion !== false;
|
|
1989
|
-
const enablePlan = options.enableWebIdePlanTools === true;
|
|
1990
|
-
const enableMysql = options.enableMysqlTools === true;
|
|
1991
|
-
const tools = {
|
|
1992
|
-
...wantsTool("AppendMessage", options, enableAppend) && options.appendMessageContent ? createAppendMessageCustomTools({
|
|
1993
|
-
appendContent: options.appendMessageContent
|
|
1994
|
-
}) : {},
|
|
1995
|
-
...wantsTool("AskQuestion", options, enableAsk) ? createAskQuestionTool({
|
|
1996
|
-
onInvoke: options?.onAskQuestion,
|
|
1997
|
-
execute: options?.askQuestionExecute
|
|
1998
|
-
}) : {}
|
|
1999
|
-
};
|
|
2000
|
-
if (options?.taskId && options.workdir) {
|
|
2001
|
-
const { cli } = createApmApiClient(cfg2);
|
|
2002
|
-
const taskId = options.taskId;
|
|
2003
|
-
if (wantsTool("RecommendPlan", options, enablePlan)) {
|
|
2004
|
-
Object.assign(
|
|
2005
|
-
tools,
|
|
2006
|
-
createRecommendPlanTool({
|
|
2007
|
-
webideRecommendPlan: (args) => cli.webideRecommendPlan({ taskId, ...args })
|
|
2008
|
-
})
|
|
2009
|
-
);
|
|
2010
|
-
}
|
|
2011
|
-
if (wantsTool("UpsertWebIdePlan", options, enablePlan)) {
|
|
2012
|
-
Object.assign(
|
|
2013
|
-
tools,
|
|
2014
|
-
createUpsertWebIdePlanTool({
|
|
2015
|
-
webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
|
|
2016
|
-
})
|
|
2017
|
-
);
|
|
2018
|
-
}
|
|
2019
|
-
if (wantsTool("UpsertWebIdeChecklist", options, enablePlan)) {
|
|
2020
|
-
Object.assign(
|
|
2021
|
-
tools,
|
|
2022
|
-
createUpsertWebIdeChecklistTool({
|
|
2023
|
-
webideUpsertChecklist: (args) => cli.webideUpsertChecklist({ taskId, ...args })
|
|
2024
|
-
})
|
|
2025
|
-
);
|
|
2026
|
-
}
|
|
2027
|
-
if (wantsTool("GetWebIdeChecklist", options, enablePlan)) {
|
|
2028
|
-
Object.assign(
|
|
2029
|
-
tools,
|
|
2030
|
-
createGetWebIdeChecklistTool({
|
|
2031
|
-
boundTaskId: taskId,
|
|
2032
|
-
webideGetChecklist: (args) => cli.webideGetChecklist(args)
|
|
2033
|
-
})
|
|
2034
|
-
);
|
|
2035
|
-
}
|
|
2036
|
-
if (wantsTool("UpsertWebIdeTestCases", options, enablePlan)) {
|
|
2037
|
-
Object.assign(
|
|
2038
|
-
tools,
|
|
2039
|
-
createUpsertWebIdeTestCasesTool({
|
|
2040
|
-
webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
|
|
2041
|
-
})
|
|
2042
|
-
);
|
|
2043
|
-
}
|
|
2044
|
-
if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
|
|
2045
|
-
Object.assign(
|
|
2046
|
-
tools,
|
|
2047
|
-
createMergeWebIdePullRequestsTool({
|
|
2048
|
-
webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
|
|
2049
|
-
})
|
|
2050
|
-
);
|
|
2051
|
-
}
|
|
2052
|
-
if (wantsTool("MysqlExecute", options, enableMysql) && options.sqlExecutionId && options.workdir) {
|
|
2053
|
-
Object.assign(
|
|
2054
|
-
tools,
|
|
2055
|
-
createMysqlExecuteTool({
|
|
2056
|
-
taskId,
|
|
2057
|
-
executionId: options.sqlExecutionId,
|
|
2058
|
-
workdir: options.workdir,
|
|
2059
|
-
upsertSqlExecution: (args) => cli.webideUpsertSqlExecution(args)
|
|
2060
|
-
})
|
|
2061
|
-
);
|
|
1936
|
+
async ensureBucket(bucket) {
|
|
1937
|
+
const exists = await this.inner.bucketExists(bucket);
|
|
1938
|
+
if (!exists) {
|
|
1939
|
+
await this.inner.makeBucket(bucket);
|
|
2062
1940
|
}
|
|
2063
1941
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
function withPlanModeToolHint(prompt, mode) {
|
|
2067
|
-
if (mode !== "plan") {
|
|
2068
|
-
return prompt;
|
|
1942
|
+
async putObject(bucket, objectKey, body, meta) {
|
|
1943
|
+
await this.inner.putObject(bucket, objectKey, body, body.length, meta);
|
|
2069
1944
|
}
|
|
2070
|
-
|
|
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
|
+
};
|
|
2071
1986
|
|
|
2072
|
-
|
|
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);
|
|
2073
1991
|
}
|
|
2074
|
-
function
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
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
|
+
};
|
|
2078
2005
|
}
|
|
2079
2006
|
|
|
2080
|
-
// src/commands/connect/
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
return
|
|
2096
|
-
schemaVersion: 1,
|
|
2097
|
-
rootBlobId: row.rootBlobId
|
|
2098
|
-
};
|
|
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");
|
|
2099
2023
|
}
|
|
2100
|
-
function
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
return
|
|
2104
|
-
schemaVersion: ref.schemaVersion,
|
|
2105
|
-
rootBlobId: ref.rootBlobId
|
|
2106
|
-
};
|
|
2024
|
+
function sha256File(absPath) {
|
|
2025
|
+
const hash = createHash("sha256");
|
|
2026
|
+
hash.update(fs.readFileSync(absPath));
|
|
2027
|
+
return hash.digest("hex");
|
|
2107
2028
|
}
|
|
2108
|
-
function
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
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
|
+
}
|
|
2119
2072
|
};
|
|
2073
|
+
walk(root);
|
|
2074
|
+
return [...byRelative.values()].sort(
|
|
2075
|
+
(a, b) => a.relativePath.localeCompare(b.relativePath)
|
|
2076
|
+
);
|
|
2120
2077
|
}
|
|
2121
|
-
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
|
+
}
|
|
2122
2143
|
return {
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
status: row.status,
|
|
2128
|
-
model: row.model ?? null,
|
|
2129
|
-
result: row.result,
|
|
2130
|
-
error: row.error,
|
|
2131
|
-
usageRef: row.usageRef,
|
|
2132
|
-
usage: row.usage ?? null,
|
|
2133
|
-
createdAt: row.createdAt,
|
|
2134
|
-
updatedAt: row.updatedAt,
|
|
2135
|
-
startedAt: row.startedAt,
|
|
2136
|
-
endedAt: row.endedAt,
|
|
2137
|
-
startCheckpointRef: asCheckpointRef(row.startCheckpointRef) ?? null,
|
|
2138
|
-
latestCheckpointRef: asCheckpointRef(row.latestCheckpointRef) ?? null
|
|
2144
|
+
scanned: localFiles.length,
|
|
2145
|
+
uploaded,
|
|
2146
|
+
skipped,
|
|
2147
|
+
deleted
|
|
2139
2148
|
};
|
|
2140
2149
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
async delete() {
|
|
2150
|
-
},
|
|
2151
|
-
async list() {
|
|
2152
|
-
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" };
|
|
2153
2158
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
async list() {
|
|
2160
|
-
return { items: [] };
|
|
2161
|
-
},
|
|
2162
|
-
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" };
|
|
2163
2164
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
async function upsertAgent(agent) {
|
|
2168
|
-
const row = await api.cli.cursorStoreUpsertAgent({
|
|
2169
|
-
agentId: agent.agentId,
|
|
2170
|
-
cwd: agent.cwd,
|
|
2171
|
-
status: agent.status,
|
|
2172
|
-
activeRunId: agent.activeRunId ?? null,
|
|
2173
|
-
name: agent.name ?? null,
|
|
2174
|
-
latestCheckpoint: toApiCheckpointRef(agent.latestCheckpoint) ?? null,
|
|
2175
|
-
sdkMetadata: agent.sdkMetadata ?? null,
|
|
2176
|
-
createdAt: agent.createdAt,
|
|
2177
|
-
updatedAt: agent.updatedAt
|
|
2178
|
-
});
|
|
2179
|
-
return fromApiAgent(row);
|
|
2165
|
+
const fsPath = toFsPath(targetAbs);
|
|
2166
|
+
if (!existsSync4(fsPath)) {
|
|
2167
|
+
return { ok: false, error: `\u76EE\u5F55\u4E0D\u5B58\u5728: ${localDirArg}` };
|
|
2180
2168
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
status: run.status,
|
|
2188
|
-
model: run.model ?? null,
|
|
2189
|
-
result: run.result ?? null,
|
|
2190
|
-
error: run.error ?? null,
|
|
2191
|
-
usageRef: run.usageRef ?? null,
|
|
2192
|
-
usage: run.usage ?? null,
|
|
2193
|
-
startCheckpointRef: toApiCheckpointRef(run.startCheckpointRef) ?? null,
|
|
2194
|
-
latestCheckpointRef: toApiCheckpointRef(run.latestCheckpointRef) ?? null,
|
|
2195
|
-
createdAt: run.createdAt,
|
|
2196
|
-
updatedAt: run.updatedAt,
|
|
2197
|
-
startedAt: run.startedAt ?? null,
|
|
2198
|
-
endedAt: run.endedAt ?? null
|
|
2199
|
-
});
|
|
2200
|
-
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}` };
|
|
2201
2175
|
}
|
|
2176
|
+
return { ok: true, absPath: targetAbs };
|
|
2177
|
+
}
|
|
2178
|
+
function createUploadPackageSqlTool(options) {
|
|
2179
|
+
const { cfg: cfg2, workdir, projectId } = options;
|
|
2202
2180
|
return {
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
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"]
|
|
2215
2196
|
},
|
|
2216
|
-
async
|
|
2217
|
-
const
|
|
2218
|
-
const
|
|
2219
|
-
if (
|
|
2220
|
-
|
|
2221
|
-
|
|
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
|
+
};
|
|
2222
2205
|
}
|
|
2223
|
-
if (
|
|
2224
|
-
|
|
2225
|
-
|
|
2206
|
+
if (!localDirRaw) {
|
|
2207
|
+
return {
|
|
2208
|
+
content: [{ type: "text", text: "localDir \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
2209
|
+
isError: true
|
|
2210
|
+
};
|
|
2226
2211
|
}
|
|
2227
|
-
let
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
});
|
|
2237
|
-
if (!page.nextCursor) break;
|
|
2238
|
-
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
|
+
};
|
|
2239
2221
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
});
|
|
2247
|
-
const result = {
|
|
2248
|
-
items: page.items.map(fromApiAgent),
|
|
2249
|
-
...page.nextCursor ? { nextCursor: page.nextCursor } : {}
|
|
2250
|
-
};
|
|
2251
|
-
return result;
|
|
2252
|
-
}
|
|
2253
|
-
},
|
|
2254
|
-
runs: {
|
|
2255
|
-
async get(input) {
|
|
2256
|
-
const page = await api.cli.cursorStoreListRuns({
|
|
2257
|
-
runIds: [input.runId],
|
|
2258
|
-
limit: 1
|
|
2259
|
-
});
|
|
2260
|
-
const row = page.items[0];
|
|
2261
|
-
return row ? fromApiRun(row) : null;
|
|
2262
|
-
},
|
|
2263
|
-
async create(input) {
|
|
2264
|
-
return upsertRun(input.run);
|
|
2265
|
-
},
|
|
2266
|
-
async update(input) {
|
|
2267
|
-
return upsertRun(input.run);
|
|
2268
|
-
},
|
|
2269
|
-
async delete(input) {
|
|
2270
|
-
const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
|
|
2271
|
-
const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
|
|
2272
|
-
if (agentIds && agentIds.length > 0 || runIds && runIds.length > 0) {
|
|
2273
|
-
await api.cli.cursorStoreDeleteRuns({ agentIds, runIds });
|
|
2274
|
-
return;
|
|
2222
|
+
const resolved = resolveLocalSqlDir(workdir, localDirRaw);
|
|
2223
|
+
if (!resolved.ok) {
|
|
2224
|
+
return {
|
|
2225
|
+
content: [{ type: "text", text: resolved.error }],
|
|
2226
|
+
isError: true
|
|
2227
|
+
};
|
|
2275
2228
|
}
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
if (page.items.length === 0) break;
|
|
2283
|
-
await api.cli.cursorStoreDeleteRuns({
|
|
2284
|
-
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
|
|
2285
2235
|
});
|
|
2286
|
-
|
|
2287
|
-
|
|
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
|
+
};
|
|
2288
2243
|
}
|
|
2289
|
-
},
|
|
2290
|
-
async list(input) {
|
|
2291
|
-
const page = await api.cli.cursorStoreListRuns({
|
|
2292
|
-
agentIds: input?.filter?.agentIds ? [...input.filter.agentIds] : void 0,
|
|
2293
|
-
runIds: input?.filter?.runIds ? [...input.filter.runIds] : void 0,
|
|
2294
|
-
cursor: input?.filter?.cursor,
|
|
2295
|
-
limit: input?.filter?.limit
|
|
2296
|
-
});
|
|
2297
|
-
const result = {
|
|
2298
|
-
items: page.items.map(fromApiRun),
|
|
2299
|
-
...page.nextCursor ? { nextCursor: page.nextCursor } : {}
|
|
2300
|
-
};
|
|
2301
|
-
return result;
|
|
2302
2244
|
}
|
|
2303
|
-
}
|
|
2304
|
-
checkpoints: unsupportedCheckpoints,
|
|
2305
|
-
runEvents: unsupportedRunEvents
|
|
2245
|
+
}
|
|
2306
2246
|
};
|
|
2307
2247
|
}
|
|
2308
2248
|
|
|
2309
|
-
// src/commands/connect/
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
const cwd = workspaceRef.trim();
|
|
2313
|
-
if (!cwd) throw new Error("openSqliteLocalAgentStore: workspaceRef \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2314
|
-
const existing = sqliteByKey.get(cwd);
|
|
2315
|
-
if (existing) return existing;
|
|
2316
|
-
const pending2 = SqliteLocalAgentStore.open({ workspaceRef: cwd }).then(
|
|
2317
|
-
(store) => {
|
|
2318
|
-
console.log(
|
|
2319
|
-
`[apm] Cursor store sqlite ready cwd=${cwd} stateRoot=${store.stateRoot}`
|
|
2320
|
-
);
|
|
2321
|
-
return store;
|
|
2322
|
-
}
|
|
2323
|
-
);
|
|
2324
|
-
sqliteByKey.set(cwd, pending2);
|
|
2325
|
-
return pending2;
|
|
2326
|
-
}
|
|
2327
|
-
function jsonSafePayload(value) {
|
|
2328
|
-
if (value == null) return null;
|
|
2329
|
-
try {
|
|
2330
|
-
return JSON.parse(JSON.stringify(value));
|
|
2331
|
-
} catch {
|
|
2332
|
-
return null;
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
function mirrorRunEventsForVisualization(cfg2, local) {
|
|
2336
|
-
const api = createApmApiClient(cfg2);
|
|
2249
|
+
// src/commands/connect/tools/package-status-tool.ts
|
|
2250
|
+
function createSetPackageFailedTool(options) {
|
|
2251
|
+
const { packageId, setFailed } = options;
|
|
2337
2252
|
return {
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
} catch (err) {
|
|
2369
|
-
console.warn(
|
|
2370
|
-
`[apm] runEvents \u53EF\u89C6\u5316\u6E05\u7406\u8DF3\u8FC7:`,
|
|
2371
|
-
err instanceof Error ? err.message : err
|
|
2372
|
-
);
|
|
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
|
+
}
|
|
2373
2283
|
}
|
|
2374
2284
|
}
|
|
2375
2285
|
};
|
|
2376
2286
|
}
|
|
2377
|
-
async function createApmCursorAgentStore(cfg2, workspaceRef) {
|
|
2378
|
-
const cwd = workspaceRef.trim();
|
|
2379
|
-
const [sqlite, remote] = await Promise.all([
|
|
2380
|
-
openSqliteLocalAgentStore(cwd),
|
|
2381
|
-
Promise.resolve(createRemoteLocalAgentStore(cfg2))
|
|
2382
|
-
]);
|
|
2383
|
-
return composeLocalAgentStore({
|
|
2384
|
-
agents: remote.agents,
|
|
2385
|
-
runs: remote.runs,
|
|
2386
|
-
checkpoints: sqlite.checkpoints,
|
|
2387
|
-
runEvents: mirrorRunEventsForVisualization(cfg2, sqlite.runEvents)
|
|
2388
|
-
});
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
// src/commands/connect/core/worker/run-steer.ts
|
|
2392
|
-
var impl = null;
|
|
2393
|
-
var pending = null;
|
|
2394
|
-
function bindRunSteer(fn) {
|
|
2395
|
-
impl = fn;
|
|
2396
|
-
if (!pending) return;
|
|
2397
|
-
const text = pending;
|
|
2398
|
-
pending = null;
|
|
2399
|
-
void fn(text);
|
|
2400
|
-
}
|
|
2401
|
-
function clearRunSteer() {
|
|
2402
|
-
impl = null;
|
|
2403
|
-
pending = null;
|
|
2404
|
-
}
|
|
2405
|
-
function deliverRunSteer(text) {
|
|
2406
|
-
const trimmed = text.trim();
|
|
2407
|
-
if (!trimmed) return;
|
|
2408
|
-
if (impl) {
|
|
2409
|
-
void impl(trimmed);
|
|
2410
|
-
return;
|
|
2411
|
-
}
|
|
2412
|
-
pending = trimmed;
|
|
2413
|
-
}
|
|
2414
2287
|
|
|
2415
|
-
// src/commands/connect/
|
|
2416
|
-
var
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
options
|
|
2426
|
-
].filter((value, index, arr) => {
|
|
2427
|
-
if (!value) return false;
|
|
2428
|
-
return arr.indexOf(value) === index;
|
|
2429
|
-
});
|
|
2430
|
-
if (details.length === 0) {
|
|
2431
|
-
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);
|
|
2432
2299
|
}
|
|
2433
|
-
return
|
|
2300
|
+
return legacyDefault;
|
|
2434
2301
|
}
|
|
2435
|
-
|
|
2436
|
-
const
|
|
2437
|
-
const
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
} : {}
|
|
2449
|
-
},
|
|
2450
|
-
...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
|
+
}) : {}
|
|
2451
2315
|
};
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
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
|
+
})
|
|
2462
2325
|
);
|
|
2463
|
-
ctx.onInvalidatePersistedAgentId?.();
|
|
2464
2326
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
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
|
+
);
|
|
2473
2334
|
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
}
|
|
2482
|
-
const apiKey = ctx.apiKey.trim();
|
|
2483
|
-
if (!apiKey) {
|
|
2484
|
-
throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
|
|
2485
|
-
}
|
|
2486
|
-
const workdir = resolveWorkdirPath(ctx.workdir);
|
|
2487
|
-
const customTools = createCursorCustomTools(cfg2, {
|
|
2488
|
-
onAskQuestion: options.onAskQuestion,
|
|
2489
|
-
appendMessageContent: options.appendMessageContent,
|
|
2490
|
-
enableAppendMessage: options.enableAppendMessage,
|
|
2491
|
-
enableAskQuestion: options.enableAskQuestion,
|
|
2492
|
-
askQuestionExecute: options.askQuestionExecute,
|
|
2493
|
-
enableWebIdePlanTools: options.enableWebIdePlanTools,
|
|
2494
|
-
enableMysqlTools: options.enableMysqlTools,
|
|
2495
|
-
sqlExecutionId: options.sqlExecutionId,
|
|
2496
|
-
toolNames: options.toolNames,
|
|
2497
|
-
taskId: options.taskId,
|
|
2498
|
-
workdir
|
|
2499
|
-
});
|
|
2500
|
-
const enableSandbox = Boolean(options.enableSandbox);
|
|
2501
|
-
let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
|
|
2502
|
-
prompt = withWorkspaceBoundaryHint(prompt);
|
|
2503
|
-
console.log(
|
|
2504
|
-
`[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} cwd=${workdir} sandbox=${enableSandbox}`
|
|
2505
|
-
);
|
|
2506
|
-
const { agent, resumed } = await obtainAgent({
|
|
2507
|
-
cfg: cfg2,
|
|
2508
|
-
apiKey,
|
|
2509
|
-
model: ctx.model,
|
|
2510
|
-
cwd: workdir,
|
|
2511
|
-
user: ctx.user,
|
|
2512
|
-
mode: ctx.mode,
|
|
2513
|
-
resumeAgentId: ctx.resumeAgentId,
|
|
2514
|
-
customTools,
|
|
2515
|
-
enableSandbox,
|
|
2516
|
-
onInvalidatePersistedAgentId: options.onInvalidatePersistedAgentId
|
|
2517
|
-
});
|
|
2518
|
-
const invalidateAgentMapping = () => {
|
|
2519
|
-
options.onInvalidatePersistedAgentId?.();
|
|
2520
|
-
};
|
|
2521
|
-
const eventSession = options.eventSession ?? new EventSession(prompt);
|
|
2522
|
-
const syncRemoteLog = options.createRemoteLogSync ? options.createRemoteLogSync(agent.agentId) : noopRemoteLogSync;
|
|
2523
|
-
let activeRun;
|
|
2524
|
-
const abortRun = () => {
|
|
2525
|
-
if (!activeRun?.supports("cancel")) return;
|
|
2526
|
-
void activeRun.cancel().catch(() => void 0);
|
|
2527
|
-
};
|
|
2528
|
-
signal?.addEventListener("abort", abortRun, { once: true });
|
|
2529
|
-
try {
|
|
2530
|
-
const run = await agent.send(prompt, {
|
|
2531
|
-
...ctx.mode ? { mode: ctx.mode } : {},
|
|
2532
|
-
local: {
|
|
2533
|
-
...options.forceSend ? { force: true } : {},
|
|
2534
|
-
customTools
|
|
2535
|
-
}
|
|
2536
|
-
});
|
|
2537
|
-
activeRun = run;
|
|
2538
|
-
console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
|
|
2539
|
-
bindRunSteer(async (text) => {
|
|
2540
|
-
const steer = run.steer;
|
|
2541
|
-
if (!steer) {
|
|
2542
|
-
console.warn(`[apm] \u5F53\u524D run \u4E0D\u652F\u6301 steer runId=${run.id}`);
|
|
2543
|
-
return;
|
|
2544
|
-
}
|
|
2545
|
-
try {
|
|
2546
|
-
const outcome = await steer(text);
|
|
2547
|
-
console.log(`[apm] steer outcome=${outcome} runId=${run.id}`);
|
|
2548
|
-
} catch (err) {
|
|
2549
|
-
console.error(
|
|
2550
|
-
`[apm] steer \u5931\u8D25 runId=${run.id}:`,
|
|
2551
|
-
err instanceof Error ? err.message : err
|
|
2552
|
-
);
|
|
2553
|
-
}
|
|
2554
|
-
});
|
|
2555
|
-
await options.onRunStarted?.({ agentId: agent.agentId, runId: run.id });
|
|
2556
|
-
let lastRunErrorStatus;
|
|
2557
|
-
for await (const event of run.stream()) {
|
|
2558
|
-
if (signal?.aborted) {
|
|
2559
|
-
abortRun();
|
|
2560
|
-
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
2561
|
-
}
|
|
2562
|
-
if (event.type === "status" && event.status === "ERROR") {
|
|
2563
|
-
const message = event.message?.trim();
|
|
2564
|
-
if (message) {
|
|
2565
|
-
lastRunErrorStatus = message;
|
|
2566
|
-
console.error(
|
|
2567
|
-
`[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
|
|
2568
|
-
);
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
options.onStreamEvent?.(event);
|
|
2572
|
-
eventSession.addEvent(event);
|
|
2573
|
-
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
|
+
);
|
|
2574
2342
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
console.log(
|
|
2583
|
-
`[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
|
+
})
|
|
2584
2350
|
);
|
|
2585
2351
|
}
|
|
2586
|
-
if (
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
invalidateAgentMapping();
|
|
2594
|
-
}
|
|
2595
|
-
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
|
+
);
|
|
2596
2359
|
}
|
|
2597
|
-
if (
|
|
2598
|
-
|
|
2360
|
+
if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
|
|
2361
|
+
Object.assign(
|
|
2362
|
+
tools,
|
|
2363
|
+
createMergeWebIdePullRequestsTool({
|
|
2364
|
+
webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
|
|
2365
|
+
})
|
|
2366
|
+
);
|
|
2599
2367
|
}
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
console.warn(
|
|
2611
|
-
`[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
|
|
2612
|
-
err instanceof Error ? err.message : err
|
|
2613
|
-
);
|
|
2614
|
-
}
|
|
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
|
+
);
|
|
2615
2378
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
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
|
+
})
|
|
2636
2406
|
);
|
|
2637
2407
|
}
|
|
2638
|
-
throw err;
|
|
2639
|
-
} finally {
|
|
2640
|
-
clearRunSteer();
|
|
2641
|
-
signal?.removeEventListener("abort", abortRun);
|
|
2642
|
-
await agent[Symbol.asyncDispose]();
|
|
2643
2408
|
}
|
|
2409
|
+
return tools;
|
|
2644
2410
|
}
|
|
2411
|
+
function withPlanModeToolHint(prompt, mode) {
|
|
2412
|
+
if (mode !== "plan") {
|
|
2413
|
+
return prompt;
|
|
2414
|
+
}
|
|
2415
|
+
return `${prompt.trim()}
|
|
2645
2416
|
|
|
2646
|
-
|
|
2647
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2648
|
-
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
2649
|
-
function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
|
|
2650
|
-
return resolve6(workdir, ".apm", "webide", taskId, fileName);
|
|
2417
|
+
${PLAN_MODE_ASK_QUESTION_HINT}`;
|
|
2651
2418
|
}
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
}
|
|
2677
|
-
if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
|
|
2678
|
-
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}`
|
|
2679
2443
|
}
|
|
2680
|
-
return state;
|
|
2681
2444
|
}
|
|
2682
|
-
}
|
|
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;
|
|
2683
2462
|
}
|
|
2684
|
-
return {
|
|
2463
|
+
return {
|
|
2464
|
+
schemaVersion: 1,
|
|
2465
|
+
rootBlobId: row.rootBlobId
|
|
2466
|
+
};
|
|
2685
2467
|
}
|
|
2686
|
-
function
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
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
|
+
};
|
|
2690
2475
|
}
|
|
2691
|
-
function
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
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
|
+
}
|
|
3341
|
+
};
|
|
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
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
const deleteLocal = [];
|
|
3359
|
+
for (const path3 of localMap.keys()) {
|
|
3360
|
+
if (!remoteMap.has(path3)) {
|
|
3361
|
+
deleteLocal.push(path3);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
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 };
|
|
3370
|
+
}
|
|
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
|
|
2700
3405
|
};
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
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;
|
|
2705
3412
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
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
|
+
}
|
|
2710
3453
|
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
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
|
+
}
|
|
2715
3463
|
}
|
|
2716
|
-
|
|
2717
|
-
|
|
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
|
+
}
|
|
2718
3829
|
}
|
|
2719
|
-
writeRegistry(path, next);
|
|
2720
|
-
}
|
|
2721
|
-
function loadWebIdeAgentId(workdir, taskId) {
|
|
2722
|
-
return readRegistry(registryPath(workdir, taskId)).agentId;
|
|
2723
|
-
}
|
|
2724
|
-
function saveWebIdeAgentId(workdir, taskId, agentId) {
|
|
2725
|
-
syncWebIdeTaskState(workdir, taskId, { agentId });
|
|
2726
|
-
}
|
|
2727
|
-
function clearWebIdeAgentId(workdir, taskId) {
|
|
2728
|
-
const path = registryPath(workdir, taskId);
|
|
2729
|
-
if (!existsSync5(path)) return;
|
|
2730
|
-
syncWebIdeTaskState(workdir, taskId, { agentId: "" });
|
|
2731
|
-
}
|
|
2732
|
-
function loadWebIdeDesignAgentId(workdir, taskId) {
|
|
2733
|
-
return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
|
|
2734
|
-
}
|
|
2735
|
-
function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
|
|
2736
|
-
const trimmedTaskId = taskId.trim();
|
|
2737
|
-
if (!trimmedTaskId) return;
|
|
2738
|
-
const path = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
|
|
2739
|
-
const current = readRegistry(path);
|
|
2740
|
-
writeRegistry(path, {
|
|
2741
|
-
...current,
|
|
2742
|
-
taskId: trimmedTaskId,
|
|
2743
|
-
agentId: agentId.trim() || void 0,
|
|
2744
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2745
|
-
});
|
|
2746
|
-
}
|
|
2747
|
-
function clearWebIdeDesignAgentId(workdir, taskId) {
|
|
2748
|
-
const path = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
|
|
2749
|
-
if (!existsSync5(path)) return;
|
|
2750
|
-
const current = readRegistry(path);
|
|
2751
|
-
const next = { ...current };
|
|
2752
|
-
delete next.agentId;
|
|
2753
|
-
writeRegistry(path, {
|
|
2754
|
-
...next,
|
|
2755
|
-
taskId: taskId.trim(),
|
|
2756
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2757
|
-
});
|
|
2758
3830
|
}
|
|
2759
3831
|
|
|
2760
|
-
// src/commands/connect/
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
for (const item of raw) {
|
|
2772
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
2773
|
-
const row = item;
|
|
2774
|
-
const id = asString4(row.id);
|
|
2775
|
-
const prompt = asString4(row.prompt);
|
|
2776
|
-
const optionsRaw = row.options;
|
|
2777
|
-
if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
|
|
2778
|
-
const options = [];
|
|
2779
|
-
for (const opt of optionsRaw) {
|
|
2780
|
-
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
|
|
2781
|
-
const o = opt;
|
|
2782
|
-
const oid = asString4(o.id);
|
|
2783
|
-
const label = asString4(o.label);
|
|
2784
|
-
if (oid && label) options.push({ id: oid, label });
|
|
2785
|
-
}
|
|
2786
|
-
if (options.length < 2) {
|
|
2787
|
-
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;
|
|
2788
3843
|
}
|
|
2789
|
-
questions.push({ id, prompt, options });
|
|
2790
3844
|
}
|
|
2791
|
-
|
|
2792
|
-
throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
|
|
2793
|
-
}
|
|
2794
|
-
return { title, questions };
|
|
3845
|
+
return DEFAULT_REPLY;
|
|
2795
3846
|
}
|
|
2796
|
-
|
|
2797
|
-
|
|
3847
|
+
|
|
3848
|
+
// src/commands/connect/core/worker/handlers/start-project.ts
|
|
3849
|
+
async function updateStatus(cfg2, messageId, status) {
|
|
2798
3850
|
const api = createApmApiClient(cfg2);
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
throw new Error(`\u5047\u8BBE\u5C1A\u672A\u5168\u90E8\u786E\u8BA4: ${id}`);
|
|
2810
|
-
}
|
|
2811
|
-
answers.push({
|
|
2812
|
-
questionId: id,
|
|
2813
|
-
prompt: row.prompt,
|
|
2814
|
-
selectedOptionId: row.selectedOptionId,
|
|
2815
|
-
customAnswer: row.customAnswer,
|
|
2816
|
-
resolution: row.resolution
|
|
2817
|
-
});
|
|
2818
|
-
}
|
|
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;
|
|
2819
3861
|
console.log(
|
|
2820
|
-
`[apm]
|
|
3862
|
+
`[apm] webide-start-project target=${msg2.target} restart=${msg2.restart} taskId=${taskId} messageId=${messageId}`
|
|
2821
3863
|
);
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
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);
|
|
2828
3882
|
const api = createApmApiClient(cfg2);
|
|
2829
|
-
|
|
2830
|
-
|
|
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
|
+
}
|
|
2831
3927
|
);
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
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
|
|
3940
|
+
});
|
|
3941
|
+
await logSyncRef.current?.markRun(
|
|
3942
|
+
outcome.runId,
|
|
3943
|
+
"error",
|
|
3944
|
+
detail,
|
|
3945
|
+
tokenUsage
|
|
3946
|
+
);
|
|
3947
|
+
await setError(cfg2, messageId, detail);
|
|
3948
|
+
return;
|
|
3949
|
+
}
|
|
3950
|
+
if (outcome.status === "cancelled" || signal.aborted) {
|
|
3951
|
+
await logSyncRef.current?.markRun(
|
|
3952
|
+
outcome.runId,
|
|
3953
|
+
"cancelled",
|
|
3954
|
+
"\u5DF2\u53D6\u6D88",
|
|
3955
|
+
tokenUsage
|
|
3956
|
+
);
|
|
3957
|
+
await updateStatus(cfg2, messageId, "CANCELLED");
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
const fallback = resolveMessageReplyFallback({
|
|
3961
|
+
assistantText: outcome.assistantText,
|
|
3962
|
+
result: outcome.result
|
|
2840
3963
|
});
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
"\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",
|
|
2845
|
-
"\u7528\u6237\u786E\u8BA4\u540E\uFF0C\u4E0B\u4E00\u8F6E\u4F1A\u5E26\u4E0A\u7B54\u6848\u5E76\u7EE7\u7EED\u8BC4\u4F30\u3002"
|
|
2846
|
-
].join("");
|
|
2847
|
-
};
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
|
-
// src/utils/minio.ts
|
|
2851
|
-
import * as Minio from "minio";
|
|
2852
|
-
var MinioClient = class {
|
|
2853
|
-
inner;
|
|
2854
|
-
constructor(opts) {
|
|
2855
|
-
const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
|
|
2856
|
-
this.inner = new Minio.Client({
|
|
2857
|
-
endPoint,
|
|
2858
|
-
port: opts.port,
|
|
2859
|
-
useSSL: opts.useSSL,
|
|
2860
|
-
accessKey: opts.accessKey,
|
|
2861
|
-
secretKey: opts.secretKey
|
|
3964
|
+
await api.cli.webideEnsureMessageContent({
|
|
3965
|
+
id: messageId,
|
|
3966
|
+
content: fallback
|
|
2862
3967
|
});
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
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
|
+
);
|
|
2868
3988
|
}
|
|
2869
3989
|
}
|
|
2870
|
-
|
|
2871
|
-
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
// src/commands/connect/webide-agent-registry.ts
|
|
3993
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3994
|
+
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
3995
|
+
function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
|
|
3996
|
+
return resolve6(workdir, ".apm", "webide", taskId, fileName);
|
|
3997
|
+
}
|
|
3998
|
+
var DESIGN_AGENT_FILE = "design-agent.json";
|
|
3999
|
+
function readRegistry(path3) {
|
|
4000
|
+
if (!existsSync7(path3)) {
|
|
4001
|
+
return {};
|
|
2872
4002
|
}
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
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();
|
|
4010
|
+
}
|
|
4011
|
+
if (typeof raw.taskId === "string" && raw.taskId.trim()) {
|
|
4012
|
+
state.taskId = raw.taskId.trim();
|
|
4013
|
+
}
|
|
4014
|
+
if (typeof raw.messageId === "string" && raw.messageId.trim()) {
|
|
4015
|
+
state.messageId = raw.messageId.trim();
|
|
4016
|
+
}
|
|
4017
|
+
if (typeof raw.action === "string" && raw.action.trim()) {
|
|
4018
|
+
state.action = raw.action.trim();
|
|
4019
|
+
}
|
|
4020
|
+
if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
|
|
4021
|
+
state.status = raw.status;
|
|
4022
|
+
}
|
|
4023
|
+
if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
|
|
4024
|
+
state.updatedAt = raw.updatedAt.trim();
|
|
4025
|
+
}
|
|
4026
|
+
return state;
|
|
4027
|
+
}
|
|
4028
|
+
} catch {
|
|
2876
4029
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
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()
|
|
4046
|
+
};
|
|
4047
|
+
if (patch.agentId !== void 0) {
|
|
4048
|
+
const agentId = patch.agentId.trim();
|
|
4049
|
+
if (agentId) next.agentId = agentId;
|
|
4050
|
+
else delete next.agentId;
|
|
2894
4051
|
}
|
|
2895
|
-
|
|
2896
|
-
const
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
metaData: stat.metaData
|
|
2900
|
-
};
|
|
4052
|
+
if (patch.messageId !== void 0) {
|
|
4053
|
+
const messageId = patch.messageId.trim();
|
|
4054
|
+
if (messageId) next.messageId = messageId;
|
|
4055
|
+
else delete next.messageId;
|
|
2901
4056
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
4057
|
+
if (patch.action !== void 0) {
|
|
4058
|
+
const action = patch.action.trim();
|
|
4059
|
+
if (action) next.action = action;
|
|
4060
|
+
else delete next.action;
|
|
2904
4061
|
}
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
const chunks = [];
|
|
2908
|
-
for await (const chunk of stream) {
|
|
2909
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2910
|
-
}
|
|
2911
|
-
return Buffer.concat(chunks);
|
|
4062
|
+
if (patch.status !== void 0) {
|
|
4063
|
+
next.status = patch.status;
|
|
2912
4064
|
}
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
// src/commands/connect/apm-log-minio.ts
|
|
2916
|
-
async function fetchApmLogStorage(cfg2) {
|
|
2917
|
-
const api = createApmApiClient(cfg2);
|
|
2918
|
-
return api.cli.getApmLogStorage(void 0);
|
|
4065
|
+
writeRegistry(path3, next);
|
|
2919
4066
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
return {
|
|
2923
|
-
client: new MinioClient({
|
|
2924
|
-
endPoint: storage.endpoint,
|
|
2925
|
-
port: storage.port,
|
|
2926
|
-
useSSL: storage.useSsl,
|
|
2927
|
-
accessKey: storage.accessKey,
|
|
2928
|
-
secretKey: storage.secretKey
|
|
2929
|
-
}),
|
|
2930
|
-
bucket: storage.bucket,
|
|
2931
|
-
...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
|
|
2932
|
-
};
|
|
4067
|
+
function loadWebIdeAgentId(workdir, taskId) {
|
|
4068
|
+
return readRegistry(registryPath(workdir, taskId)).agentId;
|
|
2933
4069
|
}
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
var SYNC_INTERVAL_MS = 2e3;
|
|
2937
|
-
function webIdeEventsObjectPrefix(taskId, messageId) {
|
|
2938
|
-
return `events/webide/${taskId}/${messageId}/`;
|
|
4070
|
+
function saveWebIdeAgentId(workdir, taskId, agentId) {
|
|
4071
|
+
syncWebIdeTaskState(workdir, taskId, { agentId });
|
|
2939
4072
|
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
JSON.stringify({
|
|
2945
|
-
index: event.index,
|
|
2946
|
-
type: event.type,
|
|
2947
|
-
data: event.data
|
|
2948
|
-
}),
|
|
2949
|
-
"utf8"
|
|
2950
|
-
);
|
|
2951
|
-
await minio.putObject(bucket, key, body, {
|
|
2952
|
-
"Content-Type": "application/json"
|
|
2953
|
-
});
|
|
2954
|
-
}
|
|
4073
|
+
function clearWebIdeAgentId(workdir, taskId) {
|
|
4074
|
+
const path3 = registryPath(workdir, taskId);
|
|
4075
|
+
if (!existsSync7(path3)) return;
|
|
4076
|
+
syncWebIdeTaskState(workdir, taskId, { agentId: "" });
|
|
2955
4077
|
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
await api.cli.webideUpsertMessageLog({
|
|
2959
|
-
messageId: ctx.messageId,
|
|
2960
|
-
agentId: ctx.agentId,
|
|
2961
|
-
objectPrefix,
|
|
2962
|
-
...patch
|
|
2963
|
-
});
|
|
4078
|
+
function loadWebIdeDesignAgentId(workdir, taskId) {
|
|
4079
|
+
return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
|
|
2964
4080
|
}
|
|
2965
|
-
function
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
const
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
onError(err);
|
|
2989
|
-
}
|
|
2990
|
-
};
|
|
2991
|
-
const syncDirtyEventsOnce = async (session) => {
|
|
2992
|
-
const events = session.getDirtyEvents();
|
|
2993
|
-
if (events.length === 0) return;
|
|
2994
|
-
lastRunAt = Date.now();
|
|
2995
|
-
try {
|
|
2996
|
-
await ensureMinio();
|
|
2997
|
-
const prefix = resolvePrefix();
|
|
2998
|
-
await putDirtyEvents(minio, bucket, prefix, events);
|
|
2999
|
-
await upsertLogHeader(
|
|
3000
|
-
cfg2,
|
|
3001
|
-
ctx,
|
|
3002
|
-
{
|
|
3003
|
-
eventCount: session.getEventCount()
|
|
3004
|
-
},
|
|
3005
|
-
prefix
|
|
3006
|
-
);
|
|
3007
|
-
session.clearDirtyIfUnchanged(events);
|
|
3008
|
-
await notifyFirstLog();
|
|
3009
|
-
} catch (err) {
|
|
3010
|
-
onError(err);
|
|
3011
|
-
}
|
|
3012
|
-
};
|
|
3013
|
-
const drainDirtyEvents = async (session) => {
|
|
3014
|
-
while (session.getDirtyEvents().length > 0) {
|
|
3015
|
-
await syncDirtyEventsOnce(session);
|
|
3016
|
-
}
|
|
3017
|
-
};
|
|
3018
|
-
const enqueueSync = (session) => {
|
|
3019
|
-
syncChain = syncChain.then(() => drainDirtyEvents(session));
|
|
3020
|
-
};
|
|
3021
|
-
return {
|
|
3022
|
-
async markRun(runId, runStatus, lastError, tokenUsage) {
|
|
3023
|
-
try {
|
|
3024
|
-
await upsertLogHeader(
|
|
3025
|
-
cfg2,
|
|
3026
|
-
ctx,
|
|
3027
|
-
{
|
|
3028
|
-
runId,
|
|
3029
|
-
runStatus,
|
|
3030
|
-
lastError: lastError ?? null,
|
|
3031
|
-
...tokenUsage ? { tokenUsage } : {}
|
|
3032
|
-
},
|
|
3033
|
-
resolvePrefix()
|
|
3034
|
-
);
|
|
3035
|
-
} catch (err) {
|
|
3036
|
-
onError(err);
|
|
3037
|
-
}
|
|
3038
|
-
},
|
|
3039
|
-
schedule(session) {
|
|
3040
|
-
latestSession = session;
|
|
3041
|
-
const now = Date.now();
|
|
3042
|
-
const elapsed = now - lastRunAt;
|
|
3043
|
-
if (elapsed >= SYNC_INTERVAL_MS) {
|
|
3044
|
-
if (timer) {
|
|
3045
|
-
clearTimeout(timer);
|
|
3046
|
-
timer = void 0;
|
|
3047
|
-
}
|
|
3048
|
-
enqueueSync(session);
|
|
3049
|
-
return;
|
|
3050
|
-
}
|
|
3051
|
-
if (timer) return;
|
|
3052
|
-
timer = setTimeout(() => {
|
|
3053
|
-
timer = void 0;
|
|
3054
|
-
if (latestSession) enqueueSync(latestSession);
|
|
3055
|
-
}, SYNC_INTERVAL_MS - elapsed);
|
|
3056
|
-
},
|
|
3057
|
-
async flush(session) {
|
|
3058
|
-
if (timer) {
|
|
3059
|
-
clearTimeout(timer);
|
|
3060
|
-
timer = void 0;
|
|
3061
|
-
}
|
|
3062
|
-
await syncChain;
|
|
3063
|
-
await drainDirtyEvents(session);
|
|
3064
|
-
}
|
|
3065
|
-
};
|
|
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
|
+
});
|
|
3066
4104
|
}
|
|
3067
4105
|
|
|
3068
|
-
// src/commands/
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
]
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
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);
|
|
3079
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
|
+
});
|
|
3080
4137
|
}
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
unlinkSync,
|
|
3091
|
-
writeFileSync as writeFileSync4
|
|
3092
|
-
} from "fs";
|
|
3093
|
-
import { join as join6 } from "path";
|
|
3094
|
-
var REVISION_FILE = ".webide-agent.json";
|
|
3095
|
-
var RULE_KEY = /^[a-z0-9_]+$/;
|
|
3096
|
-
function withTrailingNewline(text) {
|
|
3097
|
-
return text.endsWith("\n") ? text : `${text}
|
|
3098
|
-
`;
|
|
3099
|
-
}
|
|
3100
|
-
function readRevision(apmDir) {
|
|
3101
|
-
const path = toFsPath(join6(apmDir, REVISION_FILE));
|
|
3102
|
-
if (!existsSync6(path)) return null;
|
|
3103
|
-
try {
|
|
3104
|
-
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
3105
|
-
if (parsed?.version === 1 && parsed.revision?.trim()) {
|
|
3106
|
-
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
|
+
);
|
|
3107
4147
|
}
|
|
3108
|
-
} catch {
|
|
3109
|
-
return null;
|
|
3110
4148
|
}
|
|
3111
|
-
return
|
|
3112
|
-
}
|
|
3113
|
-
function writeRevision(apmDir, revision) {
|
|
3114
|
-
writeFileSync4(
|
|
3115
|
-
toFsPath(join6(apmDir, REVISION_FILE)),
|
|
3116
|
-
`${JSON.stringify({ version: 1, revision }, null, 2)}
|
|
3117
|
-
`,
|
|
3118
|
-
"utf8"
|
|
3119
|
-
);
|
|
4149
|
+
return true;
|
|
3120
4150
|
}
|
|
3121
|
-
async function
|
|
3122
|
-
const
|
|
3123
|
-
const
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
throw new Error("[apm] \u5E73\u53F0\u672A\u8FD4\u56DE WebIDE \u89C4\u5219\u7248\u672C");
|
|
3127
|
-
}
|
|
3128
|
-
const apmDir = workspaceApmDir(workdir);
|
|
3129
|
-
mkdirSync5(toFsPath(apmDir), { recursive: true });
|
|
3130
|
-
if (readRevision(apmDir) === revision) {
|
|
3131
|
-
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;
|
|
3132
4156
|
}
|
|
3133
|
-
const
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
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
|
+
);
|
|
3145
4175
|
}
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
withTrailingNewline(rule.body ?? ""),
|
|
3150
|
-
"utf8"
|
|
4176
|
+
} else {
|
|
4177
|
+
console.log(
|
|
4178
|
+
`[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
|
|
3151
4179
|
);
|
|
3152
4180
|
}
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
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}`);
|
|
3157
4187
|
}
|
|
3158
|
-
writeRevision(apmDir, revision);
|
|
3159
|
-
return {
|
|
3160
|
-
revision,
|
|
3161
|
-
wrote: true,
|
|
3162
|
-
ruleCount: bundle.rules?.length ?? 0
|
|
3163
|
-
};
|
|
3164
4188
|
}
|
|
3165
4189
|
|
|
3166
|
-
// src/commands/
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
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
|
+
});
|
|
3170
4248
|
}
|
|
3171
4249
|
console.log(
|
|
3172
|
-
`[apm] \
|
|
4250
|
+
`[apm] \u5047\u8BBE\u7ED3\u679C\u5DF2\u62C9\u53D6 taskId=${taskId} count=${answers.length}`
|
|
3173
4251
|
);
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
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
|
+
};
|
|
3177
4278
|
}
|
|
3178
4279
|
|
|
3179
4280
|
// src/commands/connect/pre-step-cache.ts
|
|
@@ -3400,9 +4501,9 @@ async function ensureWebIdePullRequests(cfg2, taskId, workdir) {
|
|
|
3400
4501
|
}
|
|
3401
4502
|
|
|
3402
4503
|
// src/commands/sync-webide-attachments.ts
|
|
3403
|
-
import { existsSync as
|
|
3404
|
-
import { join as
|
|
3405
|
-
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";
|
|
3406
4507
|
async function downloadAttachment(cfg2, attachmentId) {
|
|
3407
4508
|
const base = cfg2.baseUrl.trim().replace(/\/+$/, "");
|
|
3408
4509
|
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
|
|
@@ -3417,13 +4518,13 @@ async function downloadAttachment(cfg2, attachmentId) {
|
|
|
3417
4518
|
return Buffer.from(await res.arrayBuffer());
|
|
3418
4519
|
}
|
|
3419
4520
|
function loadManifest(dir) {
|
|
3420
|
-
const
|
|
3421
|
-
if (!
|
|
4521
|
+
const path3 = join6(dir, MANIFEST_FILE2);
|
|
4522
|
+
if (!existsSync9(path3)) {
|
|
3422
4523
|
return { version: 1, attachments: {} };
|
|
3423
4524
|
}
|
|
3424
4525
|
try {
|
|
3425
4526
|
const parsed = JSON.parse(
|
|
3426
|
-
readFileSync7(
|
|
4527
|
+
readFileSync7(path3, "utf8")
|
|
3427
4528
|
);
|
|
3428
4529
|
if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
|
|
3429
4530
|
return parsed;
|
|
@@ -3433,15 +4534,15 @@ function loadManifest(dir) {
|
|
|
3433
4534
|
return { version: 1, attachments: {} };
|
|
3434
4535
|
}
|
|
3435
4536
|
function saveManifest(dir, manifest) {
|
|
3436
|
-
|
|
3437
|
-
|
|
4537
|
+
writeFileSync6(
|
|
4538
|
+
join6(dir, MANIFEST_FILE2),
|
|
3438
4539
|
`${JSON.stringify(manifest, null, 2)}
|
|
3439
4540
|
`,
|
|
3440
4541
|
"utf8"
|
|
3441
4542
|
);
|
|
3442
4543
|
}
|
|
3443
4544
|
function isAttachmentUpToDate(entry, item, dest) {
|
|
3444
|
-
if (!entry || !
|
|
4545
|
+
if (!entry || !existsSync9(dest)) return false;
|
|
3445
4546
|
if (entry.name !== item.name) return false;
|
|
3446
4547
|
const createdAt = item.createdAt ?? "";
|
|
3447
4548
|
return entry.createdAt === createdAt;
|
|
@@ -3456,7 +4557,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
|
|
|
3456
4557
|
const nextManifest = { version: 1, attachments: {} };
|
|
3457
4558
|
const names = [];
|
|
3458
4559
|
for (const item of attachments) {
|
|
3459
|
-
const dest =
|
|
4560
|
+
const dest = join6(dir, item.name);
|
|
3460
4561
|
const entry = manifest.attachments[item.id];
|
|
3461
4562
|
const createdAt = item.createdAt ?? "";
|
|
3462
4563
|
names.push(item.name);
|
|
@@ -3466,7 +4567,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
|
|
|
3466
4567
|
continue;
|
|
3467
4568
|
}
|
|
3468
4569
|
const buffer = await downloadAttachment(cfg2, item.id);
|
|
3469
|
-
|
|
4570
|
+
writeFileSync6(dest, buffer);
|
|
3470
4571
|
nextManifest.attachments[item.id] = {
|
|
3471
4572
|
name: item.name,
|
|
3472
4573
|
createdAt
|
|
@@ -3487,9 +4588,9 @@ async function syncWebIdeAttachments(cfg2, taskId, workdir, attachments) {
|
|
|
3487
4588
|
}
|
|
3488
4589
|
|
|
3489
4590
|
// src/commands/sync-webide-design.ts
|
|
3490
|
-
import { createHash } from "crypto";
|
|
3491
|
-
import { existsSync as
|
|
3492
|
-
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";
|
|
3493
4594
|
|
|
3494
4595
|
// src/opc-shared-types.ts
|
|
3495
4596
|
function webIdeDesignObjectKey(taskId, fileName) {
|
|
@@ -3505,17 +4606,17 @@ function webIdeDesignArtifactContentType(name) {
|
|
|
3505
4606
|
}
|
|
3506
4607
|
|
|
3507
4608
|
// src/commands/sync-webide-design.ts
|
|
3508
|
-
var
|
|
4609
|
+
var MANIFEST_FILE3 = ".sync-manifest.json";
|
|
3509
4610
|
function hashBuffer(buf) {
|
|
3510
|
-
return
|
|
4611
|
+
return createHash3("sha256").update(buf).digest("hex");
|
|
3511
4612
|
}
|
|
3512
4613
|
function loadManifest2(dir) {
|
|
3513
|
-
const
|
|
3514
|
-
if (!
|
|
4614
|
+
const path3 = join7(dir, MANIFEST_FILE3);
|
|
4615
|
+
if (!existsSync10(path3)) {
|
|
3515
4616
|
return { version: 1, files: {} };
|
|
3516
4617
|
}
|
|
3517
4618
|
try {
|
|
3518
|
-
const parsed = JSON.parse(readFileSync8(
|
|
4619
|
+
const parsed = JSON.parse(readFileSync8(path3, "utf8"));
|
|
3519
4620
|
if (parsed?.version === 1 && parsed.files && typeof parsed.files === "object") {
|
|
3520
4621
|
return parsed;
|
|
3521
4622
|
}
|
|
@@ -3524,8 +4625,8 @@ function loadManifest2(dir) {
|
|
|
3524
4625
|
return { version: 1, files: {} };
|
|
3525
4626
|
}
|
|
3526
4627
|
function saveManifest2(dir, manifest) {
|
|
3527
|
-
|
|
3528
|
-
|
|
4628
|
+
writeFileSync7(
|
|
4629
|
+
join7(dir, MANIFEST_FILE3),
|
|
3529
4630
|
`${JSON.stringify(manifest, null, 2)}
|
|
3530
4631
|
`,
|
|
3531
4632
|
"utf8"
|
|
@@ -3553,13 +4654,13 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
|
|
|
3553
4654
|
for (const art of artifacts) {
|
|
3554
4655
|
const name = art.name?.trim();
|
|
3555
4656
|
if (!name || !isWebIdeDesignArtifactName(name)) continue;
|
|
3556
|
-
const localPath =
|
|
4657
|
+
const localPath = join7(dir, name);
|
|
3557
4658
|
const entry = manifest.files[name];
|
|
3558
|
-
if (entry?.contentHash === art.contentHash &&
|
|
4659
|
+
if (entry?.contentHash === art.contentHash && existsSync10(localPath)) {
|
|
3559
4660
|
continue;
|
|
3560
4661
|
}
|
|
3561
4662
|
const buf = await downloadDesignFile(cfg2, taskId, name);
|
|
3562
|
-
|
|
4663
|
+
writeFileSync7(localPath, buf);
|
|
3563
4664
|
manifest.files[name] = { contentHash: hashBuffer(buf) };
|
|
3564
4665
|
synced.push(name);
|
|
3565
4666
|
}
|
|
@@ -3568,12 +4669,12 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
|
|
|
3568
4669
|
}
|
|
3569
4670
|
function listLocalDesignArtifactFiles(taskId, workdir) {
|
|
3570
4671
|
const dir = webideDesignDir(taskId, workdir);
|
|
3571
|
-
if (!
|
|
4672
|
+
if (!existsSync10(dir)) return [];
|
|
3572
4673
|
const out = [];
|
|
3573
|
-
for (const name of
|
|
4674
|
+
for (const name of readdirSync5(dir)) {
|
|
3574
4675
|
if (!isWebIdeDesignArtifactName(name)) continue;
|
|
3575
|
-
const absPath =
|
|
3576
|
-
if (!
|
|
4676
|
+
const absPath = join7(dir, name);
|
|
4677
|
+
if (!statSync5(absPath).isFile()) continue;
|
|
3577
4678
|
const buf = readFileSync8(absPath);
|
|
3578
4679
|
if (buf.length === 0) continue;
|
|
3579
4680
|
out.push({ name, absPath, contentHash: hashBuffer(buf) });
|
|
@@ -3658,6 +4759,8 @@ function startActionForInbound(action) {
|
|
|
3658
4759
|
case "request-develop":
|
|
3659
4760
|
case "skip-plan":
|
|
3660
4761
|
return "start-develop";
|
|
4762
|
+
case "request-start-project":
|
|
4763
|
+
return "start-project";
|
|
3661
4764
|
case "request-execute-sql":
|
|
3662
4765
|
return "start-execute-sql";
|
|
3663
4766
|
case "request-bugfix":
|
|
@@ -3692,6 +4795,9 @@ function completeActionForInbound(action) {
|
|
|
3692
4795
|
case "skip-plan":
|
|
3693
4796
|
case "start-develop":
|
|
3694
4797
|
return "complete-develop";
|
|
4798
|
+
case "request-start-project":
|
|
4799
|
+
case "start-project":
|
|
4800
|
+
return "complete-project";
|
|
3695
4801
|
case "request-execute-sql":
|
|
3696
4802
|
case "start-execute-sql":
|
|
3697
4803
|
return "complete-execute-sql";
|
|
@@ -3763,6 +4869,9 @@ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
3763
4869
|
function shouldCommitAfterWebIdeMessage(action) {
|
|
3764
4870
|
return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
|
|
3765
4871
|
}
|
|
4872
|
+
function isStartLocalServicesAction(action) {
|
|
4873
|
+
return action === "enter-manual-test" || action === "skip-test" || action === "request-start-project";
|
|
4874
|
+
}
|
|
3766
4875
|
function isDesignAction(action) {
|
|
3767
4876
|
return action === "request-design" || action === "request-revise-design";
|
|
3768
4877
|
}
|
|
@@ -3773,11 +4882,11 @@ function syncLocalTaskStatus(workdir, taskId, msg2, status) {
|
|
|
3773
4882
|
status
|
|
3774
4883
|
});
|
|
3775
4884
|
}
|
|
3776
|
-
async function
|
|
4885
|
+
async function updateStatus2(cfg2, messageId, status) {
|
|
3777
4886
|
const api = createApmApiClient(cfg2);
|
|
3778
4887
|
await api.cli.webideUpdateMessageStatus({ id: messageId, status });
|
|
3779
4888
|
}
|
|
3780
|
-
async function
|
|
4889
|
+
async function setError2(cfg2, messageId, error) {
|
|
3781
4890
|
const api = createApmApiClient(cfg2);
|
|
3782
4891
|
await api.cli.webideSetMessageError({ id: messageId, error });
|
|
3783
4892
|
}
|
|
@@ -3796,7 +4905,7 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
3796
4905
|
console.log(
|
|
3797
4906
|
`[apm] webide-message action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} taskId=${taskId} messageId=${messageId}`
|
|
3798
4907
|
);
|
|
3799
|
-
await
|
|
4908
|
+
await updateStatus2(cfg2, messageId, "TYPING");
|
|
3800
4909
|
syncLocalTaskStatus(workdir, taskId, msg2, "TYPING");
|
|
3801
4910
|
const eventSession = new EventSession(msg2.content);
|
|
3802
4911
|
const prepAgentId = loadWebIdeAgentId(workdir, taskId) ?? "webide-cli-prep";
|
|
@@ -3887,13 +4996,10 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
3887
4996
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3888
4997
|
await runPrepStep(
|
|
3889
4998
|
"\u51C6\u5907\u7279\u6027\u5206\u652F",
|
|
3890
|
-
async () => {
|
|
3891
|
-
const projectId = await resolveProjectIdForTask(taskId);
|
|
3892
|
-
return runTaskBranch(taskId, projectId, { cwd: workdir });
|
|
3893
|
-
},
|
|
4999
|
+
async () => runTaskBranch(taskId, { cwd: workdir }),
|
|
3894
5000
|
(branchResult) => {
|
|
3895
5001
|
markBranchDone(branchCacheKey, workdir);
|
|
3896
|
-
return `kind=${branchResult.kind} branch=${branchResult.branch}
|
|
5002
|
+
return `kind=${branchResult.kind} branch=${branchResult.branch} baseline=${branchResult.baseline} repos=${branchResult.repos.length}`;
|
|
3897
5003
|
}
|
|
3898
5004
|
);
|
|
3899
5005
|
} else {
|
|
@@ -3919,6 +5025,43 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
3919
5025
|
}
|
|
3920
5026
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3921
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
|
+
);
|
|
3922
5065
|
await runPrepStep(
|
|
3923
5066
|
"\u540C\u6B65\u4EFB\u52A1\u9644\u4EF6",
|
|
3924
5067
|
async () => {
|
|
@@ -3969,17 +5112,18 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
3969
5112
|
"running"
|
|
3970
5113
|
);
|
|
3971
5114
|
await syncPrepLog();
|
|
5115
|
+
const startLocalServices = isStartLocalServicesAction(effectiveAction);
|
|
3972
5116
|
const designAction = isDesignAction(effectiveAction);
|
|
3973
5117
|
const useNewAgent = msg2.useNewAgent === true;
|
|
3974
5118
|
const toolNames = Array.isArray(msg2.tools) ? msg2.tools.map((t) => String(t).trim()).filter(Boolean) : [];
|
|
3975
|
-
if (useNewAgent) {
|
|
5119
|
+
if (useNewAgent && !startLocalServices) {
|
|
3976
5120
|
if (designAction) {
|
|
3977
5121
|
clearWebIdeDesignAgentId(workdir, taskId);
|
|
3978
5122
|
} else {
|
|
3979
5123
|
clearWebIdeAgentId(workdir, taskId);
|
|
3980
5124
|
}
|
|
3981
5125
|
}
|
|
3982
|
-
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);
|
|
3983
5127
|
const promptPayload = toolNames.includes("MysqlExecute") ? continuePayload ?? parseWebIdePromptPayload(msg2.content) : null;
|
|
3984
5128
|
const sqlExecutionId = toolNames.includes("MysqlExecute") && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
|
|
3985
5129
|
if (toolNames.includes("MysqlExecute") && !sqlExecutionId) {
|
|
@@ -3988,6 +5132,7 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
|
|
|
3988
5132
|
);
|
|
3989
5133
|
}
|
|
3990
5134
|
const persistAgentId = (agentId) => {
|
|
5135
|
+
if (startLocalServices) return;
|
|
3991
5136
|
if (designAction) {
|
|
3992
5137
|
saveWebIdeDesignAgentId(workdir, taskId, agentId);
|
|
3993
5138
|
} else {
|
|
@@ -4013,7 +5158,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4013
5158
|
forceSend: true,
|
|
4014
5159
|
eventSession,
|
|
4015
5160
|
appendMessageContent: (content) => appendContent(cfg2, messageId, content),
|
|
4016
|
-
askQuestionExecute: toolNames.includes("AskQuestion") ? createWebIdeAskQuestionExecute({
|
|
5161
|
+
askQuestionExecute: !startLocalServices && toolNames.includes("AskQuestion") ? createWebIdeAskQuestionExecute({
|
|
4017
5162
|
cfg: cfg2,
|
|
4018
5163
|
taskId,
|
|
4019
5164
|
onAwaitingAssumptions: () => {
|
|
@@ -4025,10 +5170,11 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4025
5170
|
enableAskQuestion: false,
|
|
4026
5171
|
enableWebIdePlanTools: false,
|
|
4027
5172
|
enableMysqlTools: false,
|
|
5173
|
+
enablePtySessionMcp: startLocalServices || toolNames.includes("PtySession"),
|
|
4028
5174
|
sqlExecutionId,
|
|
4029
5175
|
toolNames,
|
|
4030
5176
|
enableSandbox: false,
|
|
4031
|
-
onInvalidatePersistedAgentId: () => {
|
|
5177
|
+
onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => {
|
|
4032
5178
|
if (designAction) {
|
|
4033
5179
|
clearWebIdeDesignAgentId(workdir, taskId);
|
|
4034
5180
|
} else {
|
|
@@ -4081,7 +5227,9 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4081
5227
|
}
|
|
4082
5228
|
}
|
|
4083
5229
|
);
|
|
4084
|
-
|
|
5230
|
+
if (!startLocalServices) {
|
|
5231
|
+
persistAgentId(outcome.agentId);
|
|
5232
|
+
}
|
|
4085
5233
|
const tokenUsage = outcome.usage != null ? {
|
|
4086
5234
|
modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
|
|
4087
5235
|
inputTokens: outcome.usage.inputTokens,
|
|
@@ -4115,7 +5263,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4115
5263
|
);
|
|
4116
5264
|
}
|
|
4117
5265
|
}
|
|
4118
|
-
await
|
|
5266
|
+
await setError2(cfg2, messageId, detail);
|
|
4119
5267
|
syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
|
|
4120
5268
|
return;
|
|
4121
5269
|
}
|
|
@@ -4140,7 +5288,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4140
5288
|
);
|
|
4141
5289
|
}
|
|
4142
5290
|
}
|
|
4143
|
-
await
|
|
5291
|
+
await updateStatus2(cfg2, messageId, "CANCELLED");
|
|
4144
5292
|
syncLocalTaskStatus(workdir, taskId, msg2, "CANCELLED");
|
|
4145
5293
|
return;
|
|
4146
5294
|
}
|
|
@@ -4185,7 +5333,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4185
5333
|
detail,
|
|
4186
5334
|
tokenUsage
|
|
4187
5335
|
);
|
|
4188
|
-
await
|
|
5336
|
+
await setError2(cfg2, messageId, detail);
|
|
4189
5337
|
syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
|
|
4190
5338
|
return;
|
|
4191
5339
|
}
|
|
@@ -4262,6 +5410,16 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4262
5410
|
}
|
|
4263
5411
|
}
|
|
4264
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
|
+
}
|
|
4265
5423
|
await logSyncRef.current?.markRun(
|
|
4266
5424
|
outcome.runId,
|
|
4267
5425
|
"finished",
|
|
@@ -4276,11 +5434,21 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4276
5434
|
`[apm] \u672C\u8F6E\u505C\u5728\u5F85\u786E\u8BA4\u5047\u8BBE\uFF0C\u8DF3\u8FC7 complete action=${effectiveAction} taskId=${taskId}`
|
|
4277
5435
|
);
|
|
4278
5436
|
}
|
|
4279
|
-
await
|
|
5437
|
+
await updateStatus2(cfg2, messageId, "SUCCESS");
|
|
4280
5438
|
syncLocalTaskStatus(workdir, taskId, msg2, "SUCCESS");
|
|
4281
5439
|
console.log(
|
|
4282
5440
|
`[apm] webide-message \u5B8C\u6210 action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} messageId=${messageId} agentId=${outcome.agentId}`
|
|
4283
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
|
+
}
|
|
4284
5452
|
} catch (err) {
|
|
4285
5453
|
const detail = err instanceof Error ? err.message : String(err);
|
|
4286
5454
|
console.error(`[apm] webide-message \u5931\u8D25: ${detail}`);
|
|
@@ -4291,7 +5459,7 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4291
5459
|
clearWebIdeAgentId(workdir, taskId);
|
|
4292
5460
|
}
|
|
4293
5461
|
try {
|
|
4294
|
-
await
|
|
5462
|
+
await setError2(cfg2, messageId, detail);
|
|
4295
5463
|
} catch (statusErr) {
|
|
4296
5464
|
console.error(
|
|
4297
5465
|
"[apm] \u5199\u5165 WebIDE FAILED \u5931\u8D25:",
|
|
@@ -4315,7 +5483,23 @@ ${assumptionAnswersText}` : msg2.content,
|
|
|
4315
5483
|
|
|
4316
5484
|
// src/commands/connect/core/worker/handlers/index.ts
|
|
4317
5485
|
async function dispatchWorkerJob(cfg2, msg2, signal) {
|
|
4318
|
-
|
|
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
|
+
}
|
|
4319
5503
|
}
|
|
4320
5504
|
|
|
4321
5505
|
// src/commands/connect/core/worker/worker-script.ts
|