ai-project-manage-cli 7.1.4 → 7.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +163 -23
- package/dist/webide-message-worker.js +1022 -434
- package/package.json +2 -2
|
@@ -96,6 +96,10 @@ var requestConfig = {
|
|
|
96
96
|
method: "PUT",
|
|
97
97
|
path: "/cli/webide/plan"
|
|
98
98
|
}),
|
|
99
|
+
webideReplaceTestCases: defineEndpoint({
|
|
100
|
+
method: "PUT",
|
|
101
|
+
path: "/cli/webide/test-cases"
|
|
102
|
+
}),
|
|
99
103
|
branchBaseline: defineEndpoint({
|
|
100
104
|
method: "GET",
|
|
101
105
|
path: "/cli/tasks/branch-baseline"
|
|
@@ -128,6 +132,10 @@ var requestConfig = {
|
|
|
128
132
|
method: "POST",
|
|
129
133
|
path: "/cli/pull-requests"
|
|
130
134
|
}),
|
|
135
|
+
createWebIdeDraftPullRequest: defineEndpoint({
|
|
136
|
+
method: "POST",
|
|
137
|
+
path: "/cli/webide/draft-pull-requests"
|
|
138
|
+
}),
|
|
131
139
|
getRepositoryProjectDocumentManifest: defineEndpoint({
|
|
132
140
|
method: "GET",
|
|
133
141
|
path: "/cli/repository-project-documents/manifest"
|
|
@@ -240,32 +248,691 @@ function normalizeWorkdirPath(path) {
|
|
|
240
248
|
if (normalized.startsWith("//?/")) {
|
|
241
249
|
normalized = normalized.slice(4);
|
|
242
250
|
}
|
|
243
|
-
const windowsDrive = /^([A-Za-z]:)\/*(.*)$/.exec(normalized);
|
|
244
|
-
if (windowsDrive) {
|
|
245
|
-
const drive = windowsDrive[1].toLowerCase();
|
|
246
|
-
const rest = windowsDrive[2].replace(/\/+/g, "/").replace(/\/$/, "");
|
|
247
|
-
return rest ? `${drive}/${rest}` : drive;
|
|
251
|
+
const windowsDrive = /^([A-Za-z]:)\/*(.*)$/.exec(normalized);
|
|
252
|
+
if (windowsDrive) {
|
|
253
|
+
const drive = windowsDrive[1].toLowerCase();
|
|
254
|
+
const rest = windowsDrive[2].replace(/\/+/g, "/").replace(/\/$/, "");
|
|
255
|
+
return rest ? `${drive}/${rest}` : drive;
|
|
256
|
+
}
|
|
257
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
258
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
259
|
+
normalized = normalized.slice(0, -1);
|
|
260
|
+
}
|
|
261
|
+
return normalized;
|
|
262
|
+
}
|
|
263
|
+
function resolveWorkdirPath(cwd = process.cwd()) {
|
|
264
|
+
const absolute = resolve(cwd);
|
|
265
|
+
try {
|
|
266
|
+
return normalizeWorkdirPath(realpathSync.native(absolute));
|
|
267
|
+
} catch {
|
|
268
|
+
return normalizeWorkdirPath(absolute);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function requireRemoteWorkdir(workdir) {
|
|
272
|
+
const trimmed = typeof workdir === "string" ? workdir.trim() : "";
|
|
273
|
+
if (!trimmed) {
|
|
274
|
+
throw new Error("[apm] \u8FDC\u7A0B\u6D88\u606F\u7F3A\u5C11\u5DE5\u4F5C\u76EE\u5F55 workdir");
|
|
275
|
+
}
|
|
276
|
+
return resolveWorkdirPath(trimmed);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/git-remote.ts
|
|
280
|
+
import { execFile } from "child_process";
|
|
281
|
+
import { promisify } from "util";
|
|
282
|
+
var execFileAsync = promisify(execFile);
|
|
283
|
+
async function tryReadGitOriginUrl(cwd) {
|
|
284
|
+
try {
|
|
285
|
+
const { stdout } = await execFileAsync(
|
|
286
|
+
"git",
|
|
287
|
+
["config", "--get", "remote.origin.url"],
|
|
288
|
+
{ cwd, encoding: "utf8", maxBuffer: 1024 * 1024 }
|
|
289
|
+
);
|
|
290
|
+
const url = stdout.trim();
|
|
291
|
+
return url || null;
|
|
292
|
+
} catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/git-utils.ts
|
|
298
|
+
import { execFile as execFile2 } from "child_process";
|
|
299
|
+
import { promisify as promisify2 } from "util";
|
|
300
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
301
|
+
async function execGit(cwd, args, quiet = false) {
|
|
302
|
+
try {
|
|
303
|
+
const { stdout, stderr } = await execFileAsync2("git", args, {
|
|
304
|
+
cwd,
|
|
305
|
+
encoding: "utf8",
|
|
306
|
+
maxBuffer: 10 * 1024 * 1024
|
|
307
|
+
});
|
|
308
|
+
if (!quiet && stderr.trim()) {
|
|
309
|
+
process.stderr.write(stderr);
|
|
310
|
+
}
|
|
311
|
+
return stdout;
|
|
312
|
+
} catch (err) {
|
|
313
|
+
const e = err;
|
|
314
|
+
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
315
|
+
throw new Error(
|
|
316
|
+
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function isGitRepo(cwd) {
|
|
321
|
+
try {
|
|
322
|
+
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
323
|
+
return true;
|
|
324
|
+
} catch {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function ensureGitRepo(cwd) {
|
|
329
|
+
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
330
|
+
}
|
|
331
|
+
async function getCurrentBranch(cwd) {
|
|
332
|
+
return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
333
|
+
}
|
|
334
|
+
async function isWorkingTreeDirty(cwd) {
|
|
335
|
+
const out = await execGit(cwd, ["status", "--porcelain"], true);
|
|
336
|
+
return out.trim().length > 0;
|
|
337
|
+
}
|
|
338
|
+
async function remoteBranchExists(cwd, branch) {
|
|
339
|
+
const out = await execGit(
|
|
340
|
+
cwd,
|
|
341
|
+
["ls-remote", "--heads", "origin", branch],
|
|
342
|
+
true
|
|
343
|
+
);
|
|
344
|
+
return out.trim().length > 0;
|
|
345
|
+
}
|
|
346
|
+
async function resolveGitRepoRoot(cwd) {
|
|
347
|
+
return (await execGit(cwd, ["rev-parse", "--show-toplevel"], true)).trim();
|
|
348
|
+
}
|
|
349
|
+
async function ensureRemoteBaselineBranch(cwd, baselineBranch) {
|
|
350
|
+
await execGit(cwd, ["fetch", "origin", baselineBranch], true);
|
|
351
|
+
if (await remoteBranchExists(cwd, baselineBranch)) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
throw new Error(
|
|
355
|
+
`[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`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
async function resolveDefaultRemoteBranch(cwd) {
|
|
359
|
+
try {
|
|
360
|
+
const ref = (await execGit(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], true)).trim();
|
|
361
|
+
const match = ref.match(/^refs\/remotes\/origin\/(.+)$/);
|
|
362
|
+
if (match?.[1]) {
|
|
363
|
+
return match[1];
|
|
364
|
+
}
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
for (const candidate of ["main", "master"]) {
|
|
368
|
+
if (await remoteBranchExists(cwd, candidate)) {
|
|
369
|
+
return candidate;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
throw new Error(
|
|
373
|
+
"[apm] \u65E0\u6CD5\u786E\u5B9A\u8FDC\u7A0B\u9ED8\u8BA4\u5206\u652F\uFF08origin/HEAD\u3001main\u3001master \u5747\u4E0D\u53EF\u7528\uFF09"
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
async function hasUpstream(cwd) {
|
|
377
|
+
try {
|
|
378
|
+
await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
|
|
379
|
+
return true;
|
|
380
|
+
} catch {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
|
|
385
|
+
async function commitAndPushGitignore(workdir) {
|
|
386
|
+
if (!await isGitRepo(workdir)) {
|
|
387
|
+
console.log("[apm] \u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8BF7\u624B\u52A8\u63D0\u4EA4 .gitignore");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
await execGit(workdir, ["add", "--", ".gitignore"]);
|
|
391
|
+
await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
|
|
392
|
+
console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
|
|
393
|
+
const originUrl = await tryReadGitOriginUrl(workdir);
|
|
394
|
+
if (!originUrl) {
|
|
395
|
+
console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (await hasUpstream(workdir)) {
|
|
399
|
+
await execGit(workdir, ["push"]);
|
|
400
|
+
} else {
|
|
401
|
+
await execGit(workdir, ["push", "-u", "origin", "HEAD"]);
|
|
402
|
+
}
|
|
403
|
+
console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/baseline-resolve.ts
|
|
407
|
+
function formatBaselineDiagnostic(workdirPath, baselineWorkdirPath, diagnostic) {
|
|
408
|
+
return diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baselineWorkdirPath}\uFF09
|
|
409
|
+
\u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
|
|
410
|
+
}
|
|
411
|
+
async function matchRepositoryByGitRemote(api, workdirPath) {
|
|
412
|
+
const gitRoot = await resolveGitRepoRoot(workdirPath);
|
|
413
|
+
const gitUrl = await tryReadGitOriginUrl(gitRoot);
|
|
414
|
+
if (!gitUrl) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
const matched = await api.cli.matchRepository({ url: gitUrl });
|
|
418
|
+
const repositoryId = matched.repositoryId?.trim();
|
|
419
|
+
const defaultBranch = matched.defaultBranch?.trim();
|
|
420
|
+
if (!repositoryId || !defaultBranch) {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
console.log(
|
|
424
|
+
`[apm] \u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u672A\u5339\u914D\uFF0C\u5DF2\u901A\u8FC7 git remote \u5173\u8054\u4ED3\u5E93: ${gitUrl}`
|
|
425
|
+
);
|
|
426
|
+
return { repositoryId, defaultBranch };
|
|
427
|
+
}
|
|
428
|
+
async function resolveWorkspaceBaseline(api, workdirPath) {
|
|
429
|
+
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
430
|
+
const repositoryId = baseline.repositoryId?.trim();
|
|
431
|
+
const defaultBranch = baseline.defaultBranch?.trim();
|
|
432
|
+
if (repositoryId && defaultBranch) {
|
|
433
|
+
return {
|
|
434
|
+
repositoryId,
|
|
435
|
+
defaultBranch,
|
|
436
|
+
workdirPath: baseline.workdirPath,
|
|
437
|
+
matchedViaGitRemote: false
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const viaGit = await matchRepositoryByGitRemote(api, workdirPath);
|
|
441
|
+
if (viaGit) {
|
|
442
|
+
return {
|
|
443
|
+
...viaGit,
|
|
444
|
+
workdirPath: baseline.workdirPath,
|
|
445
|
+
matchedViaGitRemote: true
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
throw new Error(
|
|
449
|
+
`[apm] ${formatBaselineDiagnostic(
|
|
450
|
+
workdirPath,
|
|
451
|
+
baseline.workdirPath,
|
|
452
|
+
baseline.diagnostic
|
|
453
|
+
)}`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/command-utils.ts
|
|
458
|
+
import {
|
|
459
|
+
copyFileSync,
|
|
460
|
+
existsSync,
|
|
461
|
+
mkdirSync as mkdirSync2,
|
|
462
|
+
readFileSync as readFileSync2,
|
|
463
|
+
readdirSync,
|
|
464
|
+
statSync,
|
|
465
|
+
writeFileSync as writeFileSync2
|
|
466
|
+
} from "fs";
|
|
467
|
+
import { basename, dirname, extname, join as join2, resolve as resolve2 } from "path";
|
|
468
|
+
import { fileURLToPath } from "url";
|
|
469
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
470
|
+
var CLI_TEMPLATE_DIR = resolve2(__dirname, "../template");
|
|
471
|
+
function workspaceApmDir(cwd = resolveWorkdirPath()) {
|
|
472
|
+
return resolve2(resolve2(cwd), ".apm");
|
|
473
|
+
}
|
|
474
|
+
function isWorkspaceApmInitialized(workdir) {
|
|
475
|
+
const apmDir = workspaceApmDir(workdir);
|
|
476
|
+
const fsApmDir = toFsPath(apmDir);
|
|
477
|
+
if (!existsSync(fsApmDir)) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
const st = statSync(fsApmDir);
|
|
481
|
+
if (!st.isDirectory()) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B\u7684 .apm \u4E0D\u662F\u76EE\u5F55\uFF0C\u8BF7\u68C0\u67E5\u672C\u5730\u63A5\u5165\u72B6\u6001\u3002`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
return readdirSync(fsApmDir).length > 0;
|
|
487
|
+
}
|
|
488
|
+
var APM_GITIGNORE_PATTERNS = [
|
|
489
|
+
/^\.apm\/?$/,
|
|
490
|
+
/^\.apm\/\*\*$/,
|
|
491
|
+
/^\*\*\/\.apm\/?$/,
|
|
492
|
+
/^\*\*\/\.apm\/\*\*$/,
|
|
493
|
+
/^\/\.apm\/?$/,
|
|
494
|
+
/^\/\.apm\/\*\*$/
|
|
495
|
+
];
|
|
496
|
+
function normalizeGitignorePattern(line) {
|
|
497
|
+
const trimmed = line.trim();
|
|
498
|
+
if (!trimmed || trimmed.startsWith("#")) return "";
|
|
499
|
+
if (trimmed.startsWith("!")) return "";
|
|
500
|
+
const hashIndex = trimmed.indexOf("#");
|
|
501
|
+
return (hashIndex >= 0 ? trimmed.slice(0, hashIndex) : trimmed).trim();
|
|
502
|
+
}
|
|
503
|
+
function gitignoreIgnoresApm(line) {
|
|
504
|
+
const pattern = normalizeGitignorePattern(line);
|
|
505
|
+
if (!pattern) return false;
|
|
506
|
+
return APM_GITIGNORE_PATTERNS.some((re) => re.test(pattern));
|
|
507
|
+
}
|
|
508
|
+
var APM_GITIGNORE_LINE = "**/.apm/**";
|
|
509
|
+
function ensureApmGitignoredInRepo(workdir) {
|
|
510
|
+
const gitignorePath = join2(workdir, ".gitignore");
|
|
511
|
+
const fsGitignorePath = toFsPath(gitignorePath);
|
|
512
|
+
if (!existsSync(fsGitignorePath)) {
|
|
513
|
+
writeFileSync2(fsGitignorePath, `${APM_GITIGNORE_LINE}
|
|
514
|
+
`, "utf8");
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
const content = readFileSync2(fsGitignorePath, "utf8");
|
|
518
|
+
if (content.split(/\r?\n/).some(gitignoreIgnoresApm)) {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
const suffix = content.endsWith("\n") || content.length === 0 ? "" : "\n";
|
|
522
|
+
writeFileSync2(
|
|
523
|
+
fsGitignorePath,
|
|
524
|
+
`${content}${suffix}${APM_GITIGNORE_LINE}
|
|
525
|
+
`,
|
|
526
|
+
"utf8"
|
|
527
|
+
);
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
function assertApmGitignoredInRepo(workdir) {
|
|
531
|
+
const gitignorePath = join2(workdir, ".gitignore");
|
|
532
|
+
const fsGitignorePath = toFsPath(gitignorePath);
|
|
533
|
+
if (!existsSync(fsGitignorePath)) {
|
|
534
|
+
throw new Error(
|
|
535
|
+
`\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7F3A\u5C11 .gitignore \u6587\u4EF6\uFF0C\u8BF7\u6DFB\u52A0\u5BF9 .apm \u7684\u5FFD\u7565\u89C4\u5219\uFF08\u4F8B\u5982 **/.apm/**\uFF09\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
const content = readFileSync2(fsGitignorePath, "utf8");
|
|
539
|
+
if (!content.split(/\r?\n/).some(gitignoreIgnoresApm)) {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7684 .gitignore \u672A\u5FFD\u7565 .apm\uFF0C\u8BF7\u6DFB\u52A0 **/.apm/** \u6216 .apm/\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
async function ensureDirExists(dir) {
|
|
546
|
+
mkdirSync2(dir, { recursive: true });
|
|
547
|
+
}
|
|
548
|
+
async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
|
|
549
|
+
const dir = workspaceApmDir(cwd);
|
|
550
|
+
const fsDir = toFsPath(dir);
|
|
551
|
+
if (!existsSync(fsDir)) {
|
|
552
|
+
mkdirSync2(fsDir, { recursive: true });
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const st = statSync(fsDir);
|
|
556
|
+
if (!st.isDirectory()) {
|
|
557
|
+
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${dir}`);
|
|
558
|
+
}
|
|
559
|
+
if (readdirSync(fsDir).length > 0) {
|
|
560
|
+
throw new Error(
|
|
561
|
+
"[apm] .apm \u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\uFF0C\u8BF7\u5148\u5907\u4EFD\u3001\u6E05\u7A7A\u6216\u5220\u9664\u540E\u518D\u6267\u884C init"
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
var WORKSPACE_TEMPLATE_SUBDIRS = [
|
|
566
|
+
"sessions",
|
|
567
|
+
"skills",
|
|
568
|
+
"rules",
|
|
569
|
+
"deploy"
|
|
570
|
+
];
|
|
571
|
+
function shouldSkipTemplateEntry(name) {
|
|
572
|
+
return name === ".DS_Store" || name === "Thumbs.db";
|
|
573
|
+
}
|
|
574
|
+
function copyTemplateEntry(src, dest) {
|
|
575
|
+
const fsSrc = toFsPath(src);
|
|
576
|
+
const fsDest = toFsPath(dest);
|
|
577
|
+
const st = statSync(fsSrc);
|
|
578
|
+
if (st.isDirectory()) {
|
|
579
|
+
mkdirSync2(fsDest, { recursive: true });
|
|
580
|
+
for (const name of readdirSync(fsSrc)) {
|
|
581
|
+
if (shouldSkipTemplateEntry(name)) continue;
|
|
582
|
+
copyTemplateEntry(join2(src, name), join2(dest, name));
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (!st.isFile()) return;
|
|
587
|
+
mkdirSync2(toFsPath(dirname(dest)), { recursive: true });
|
|
588
|
+
copyFileSync(fsSrc, fsDest);
|
|
589
|
+
}
|
|
590
|
+
function assertTemplateCopiedToApm(apmDir, workdir) {
|
|
591
|
+
const required = [
|
|
592
|
+
"AGENTS.md",
|
|
593
|
+
"apm.config.json",
|
|
594
|
+
"rules",
|
|
595
|
+
"skills",
|
|
596
|
+
"sessions"
|
|
597
|
+
];
|
|
598
|
+
for (const item of required) {
|
|
599
|
+
const path = join2(apmDir, item);
|
|
600
|
+
if (!existsSync(toFsPath(path))) {
|
|
601
|
+
throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const leakedRules = join2(workdir, "rules");
|
|
605
|
+
const apmRules = join2(apmDir, "rules");
|
|
606
|
+
if (existsSync(toFsPath(leakedRules)) && !existsSync(toFsPath(join2(apmRules, "reply.md")))) {
|
|
607
|
+
throw new Error(
|
|
608
|
+
`[apm] \u6A21\u677F\u88AB\u590D\u5236\u5230\u9519\u8BEF\u4F4D\u7F6E: ${leakedRules}\uFF08\u5E94\u5728 ${apmRules}\uFF09`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
|
|
613
|
+
const resolvedTarget = resolve2(targetDir);
|
|
614
|
+
const templateDir = resolve2(CLI_TEMPLATE_DIR);
|
|
615
|
+
const fsTemplateDir = toFsPath(templateDir);
|
|
616
|
+
if (!existsSync(fsTemplateDir)) {
|
|
617
|
+
throw new Error(`[apm] \u672A\u627E\u5230 CLI \u6A21\u677F\u76EE\u5F55: ${templateDir}`);
|
|
618
|
+
}
|
|
619
|
+
const dirStat = statSync(fsTemplateDir);
|
|
620
|
+
if (!dirStat.isDirectory()) {
|
|
621
|
+
throw new Error(`[apm] CLI \u6A21\u677F\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55: ${templateDir}`);
|
|
622
|
+
}
|
|
623
|
+
const entries = readdirSync(fsTemplateDir).filter(
|
|
624
|
+
(name) => !shouldSkipTemplateEntry(name)
|
|
625
|
+
);
|
|
626
|
+
if (entries.length === 0) {
|
|
627
|
+
throw new Error(`[apm] CLI \u6A21\u677F\u76EE\u5F55\u4E3A\u7A7A: ${templateDir}`);
|
|
628
|
+
}
|
|
629
|
+
mkdirSync2(toFsPath(resolvedTarget), { recursive: true });
|
|
630
|
+
for (const name of entries) {
|
|
631
|
+
copyTemplateEntry(join2(templateDir, name), join2(resolvedTarget, name));
|
|
632
|
+
}
|
|
633
|
+
for (const subdir of WORKSPACE_TEMPLATE_SUBDIRS) {
|
|
634
|
+
mkdirSync2(toFsPath(join2(resolvedTarget, subdir)), { recursive: true });
|
|
635
|
+
}
|
|
636
|
+
assertTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/workspace-repos.ts
|
|
640
|
+
import {
|
|
641
|
+
existsSync as existsSync2,
|
|
642
|
+
mkdirSync as mkdirSync3,
|
|
643
|
+
readFileSync as readFileSync3,
|
|
644
|
+
readdirSync as readdirSync2,
|
|
645
|
+
statSync as statSync2,
|
|
646
|
+
writeFileSync as writeFileSync3
|
|
647
|
+
} from "fs";
|
|
648
|
+
import { basename as basename2, join as join3, relative, resolve as resolve3 } from "path";
|
|
649
|
+
var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
|
|
650
|
+
var WORKSPACE_REPOS_VERSION = 1;
|
|
651
|
+
var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([".apm", "node_modules"]);
|
|
652
|
+
function hasGitAt(dir) {
|
|
653
|
+
return existsSync2(toFsPath(join3(dir, ".git")));
|
|
654
|
+
}
|
|
655
|
+
function isSkippableChildName(name) {
|
|
656
|
+
if (SKIP_DIR_NAMES.has(name)) return true;
|
|
657
|
+
if (name.startsWith(".")) return true;
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
function manifestPath(workdir) {
|
|
661
|
+
return join3(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
|
|
662
|
+
}
|
|
663
|
+
function absoluteRepoPath(workdir, entry) {
|
|
664
|
+
if (entry.path === "." || entry.path === "") {
|
|
665
|
+
return workdir;
|
|
666
|
+
}
|
|
667
|
+
return resolve3(workdir, entry.path);
|
|
668
|
+
}
|
|
669
|
+
function toRelativeRepoPath(workdir, repoAbs) {
|
|
670
|
+
const rel = relative(workdir, repoAbs).replace(/\\/g, "/");
|
|
671
|
+
return rel === "" ? "." : rel;
|
|
672
|
+
}
|
|
673
|
+
function readManifest(workdir) {
|
|
674
|
+
const path = toFsPath(manifestPath(workdir));
|
|
675
|
+
if (!existsSync2(path)) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
const raw = JSON.parse(
|
|
680
|
+
readFileSync3(path, "utf8")
|
|
681
|
+
);
|
|
682
|
+
if (raw?.version !== WORKSPACE_REPOS_VERSION) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
if (raw.kind !== "single" && raw.kind !== "multi") {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
if (!Array.isArray(raw.repos) || raw.repos.length === 0) {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
if (typeof raw.workdir !== "string" || !raw.workdir.trim()) {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
return raw;
|
|
695
|
+
} catch {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function writeManifest(workdir, manifest) {
|
|
700
|
+
const apmDir = toFsPath(workspaceApmDir(workdir));
|
|
701
|
+
mkdirSync3(apmDir, { recursive: true });
|
|
702
|
+
const path = toFsPath(manifestPath(workdir));
|
|
703
|
+
writeFileSync3(path, `${JSON.stringify(manifest, null, 2)}
|
|
704
|
+
`, "utf8");
|
|
705
|
+
}
|
|
706
|
+
function manifestStillValid(workdir, manifest) {
|
|
707
|
+
if (normalizeWorkdirPath(manifest.workdir) !== normalizeWorkdirPath(workdir)) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
for (const entry of manifest.repos) {
|
|
711
|
+
if (typeof entry?.path !== "string" || !entry.path.trim()) {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
const abs = absoluteRepoPath(workdir, entry);
|
|
715
|
+
if (!hasGitAt(abs)) {
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
function scanWorkspaceRepos(workdirInput) {
|
|
722
|
+
const workdir = resolveWorkdirPath(workdirInput);
|
|
723
|
+
if (hasGitAt(workdir)) {
|
|
724
|
+
return {
|
|
725
|
+
version: WORKSPACE_REPOS_VERSION,
|
|
726
|
+
kind: "single",
|
|
727
|
+
workdir,
|
|
728
|
+
repos: [{ path: "." }],
|
|
729
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
const fsWorkdir = toFsPath(workdir);
|
|
733
|
+
if (!existsSync2(fsWorkdir) || !statSync2(fsWorkdir).isDirectory()) {
|
|
734
|
+
throw new Error(`[apm] \u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${workdir}`);
|
|
735
|
+
}
|
|
736
|
+
const repos = [];
|
|
737
|
+
for (const name of readdirSync2(fsWorkdir)) {
|
|
738
|
+
if (isSkippableChildName(name)) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
const childAbs = resolve3(workdir, name);
|
|
742
|
+
const childFs = toFsPath(childAbs);
|
|
743
|
+
if (!existsSync2(childFs) || !statSync2(childFs).isDirectory()) {
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
if (!hasGitAt(childAbs)) {
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
repos.push({ path: toRelativeRepoPath(workdir, childAbs) });
|
|
750
|
+
}
|
|
751
|
+
repos.sort((a, b) => a.path.localeCompare(b.path));
|
|
752
|
+
if (repos.length === 0) {
|
|
753
|
+
throw new Error(
|
|
754
|
+
`[apm] \u5DE5\u4F5C\u533A ${workdir} \u6839\u76EE\u5F55\u65E0 .git\uFF0C\u4E14\u4E00\u7EA7\u5B50\u76EE\u5F55\u4E2D\u672A\u53D1\u73B0 git \u4ED3\u5E93`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
version: WORKSPACE_REPOS_VERSION,
|
|
759
|
+
kind: "multi",
|
|
760
|
+
workdir,
|
|
761
|
+
repos,
|
|
762
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function loadWorkspaceReposCache(workdirInput) {
|
|
766
|
+
const workdir = resolveWorkdirPath(workdirInput);
|
|
767
|
+
const cached = readManifest(workdir);
|
|
768
|
+
if (!cached) {
|
|
769
|
+
throw new Error(
|
|
770
|
+
`[apm] \u672A\u627E\u5230\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 .apm/${WORKSPACE_REPOS_MANIFEST}\uFF0C\u8BF7\u5148\u6267\u884C\u4ED3\u5E93\u68C0\u6D4B`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
if (normalizeWorkdirPath(cached.workdir) !== normalizeWorkdirPath(workdir)) {
|
|
774
|
+
throw new Error(
|
|
775
|
+
`[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 workdir \u4E0D\u5339\u914D\uFF1A\u7F13\u5B58=${cached.workdir} \u5F53\u524D=${workdir}\uFF0C\u8BF7\u5F3A\u5236\u91CD\u65B0\u626B\u63CF`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
if (!Array.isArray(cached.repos) || cached.repos.length === 0) {
|
|
779
|
+
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`);
|
|
248
780
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
781
|
+
console.log(
|
|
782
|
+
`[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 kind=${cached.kind} repos=${cached.repos.map((r) => r.path).join(", ")}`
|
|
783
|
+
);
|
|
784
|
+
return {
|
|
785
|
+
...cached,
|
|
786
|
+
workdir
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function resolveWorkspaceRepos(workdirInput, options) {
|
|
790
|
+
const workdir = resolveWorkdirPath(workdirInput);
|
|
791
|
+
if (!options?.forceRescan) {
|
|
792
|
+
const cached = readManifest(workdir);
|
|
793
|
+
if (cached && manifestStillValid(workdir, cached)) {
|
|
794
|
+
console.log(
|
|
795
|
+
`[apm] \u4F7F\u7528\u7F13\u5B58\u7684\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 kind=${cached.kind} repos=${cached.repos.length}`
|
|
796
|
+
);
|
|
797
|
+
return cached;
|
|
798
|
+
}
|
|
252
799
|
}
|
|
253
|
-
|
|
800
|
+
const scanned = scanWorkspaceRepos(workdir);
|
|
801
|
+
writeManifest(workdir, scanned);
|
|
802
|
+
console.log(
|
|
803
|
+
`[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}`
|
|
804
|
+
);
|
|
805
|
+
return scanned;
|
|
254
806
|
}
|
|
255
|
-
function
|
|
256
|
-
|
|
807
|
+
function resolveWorkspaceRepoAbsolutePaths(manifest) {
|
|
808
|
+
return manifest.repos.map(
|
|
809
|
+
(entry) => absoluteRepoPath(manifest.workdir, entry)
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
function formatRepoLabel(workdir, repoAbs) {
|
|
813
|
+
const rel = toRelativeRepoPath(resolveWorkdirPath(workdir), repoAbs);
|
|
814
|
+
return rel === "." ? basename2(repoAbs) || repoAbs : rel;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/commands/branch.ts
|
|
818
|
+
var TASK_BRANCH_PREFIX = "feat/task-";
|
|
819
|
+
async function localBranchExists(cwd, branch) {
|
|
257
820
|
try {
|
|
258
|
-
|
|
821
|
+
await execGit(
|
|
822
|
+
cwd,
|
|
823
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
824
|
+
true
|
|
825
|
+
);
|
|
826
|
+
return true;
|
|
259
827
|
} catch {
|
|
260
|
-
return
|
|
828
|
+
return false;
|
|
261
829
|
}
|
|
262
830
|
}
|
|
263
|
-
function
|
|
264
|
-
|
|
265
|
-
if (!
|
|
266
|
-
|
|
831
|
+
async function commitWorkingTreeIfDirty(cwd, message) {
|
|
832
|
+
await ensureGitRepo(cwd);
|
|
833
|
+
if (!await isWorkingTreeDirty(cwd)) {
|
|
834
|
+
return false;
|
|
267
835
|
}
|
|
268
|
-
|
|
836
|
+
const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
|
|
837
|
+
await execGit(cwd, ["add", "-A"]);
|
|
838
|
+
await execGit(cwd, ["commit", "-m", commitMessage]);
|
|
839
|
+
console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
|
|
840
|
+
return true;
|
|
841
|
+
}
|
|
842
|
+
function branchNameForTask(taskId) {
|
|
843
|
+
const id = taskId.trim();
|
|
844
|
+
if (!id) {
|
|
845
|
+
throw new Error("[apm] \u4EFB\u52A1 ID \u4E0D\u80FD\u4E3A\u7A7A");
|
|
846
|
+
}
|
|
847
|
+
if (/[\s/\\]/.test(id)) {
|
|
848
|
+
throw new Error(
|
|
849
|
+
"[apm] \u4EFB\u52A1 ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
return `${TASK_BRANCH_PREFIX}${id}`;
|
|
853
|
+
}
|
|
854
|
+
async function ensureFeatureBranch(branch, baselineBranch, options) {
|
|
855
|
+
const cwd = options.cwd ?? process.cwd();
|
|
856
|
+
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
857
|
+
const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
|
|
858
|
+
await ensureGitRepo(gitRoot);
|
|
859
|
+
await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
|
|
860
|
+
const current = await getCurrentBranch(gitRoot);
|
|
861
|
+
const dirty = await isWorkingTreeDirty(gitRoot);
|
|
862
|
+
if (dirty) {
|
|
863
|
+
if (current === branch) {
|
|
864
|
+
await commitWorkingTreeIfDirty(gitRoot, commitMessage);
|
|
865
|
+
} else {
|
|
866
|
+
await execGit(gitRoot, [
|
|
867
|
+
"stash",
|
|
868
|
+
"push",
|
|
869
|
+
"-u",
|
|
870
|
+
"-m",
|
|
871
|
+
`apm: switch to ${branch}`
|
|
872
|
+
]);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
const onTargetBranch = await getCurrentBranch(gitRoot) === branch;
|
|
876
|
+
if (onTargetBranch) {
|
|
877
|
+
await execGit(gitRoot, ["fetch", "origin", baselineBranch]);
|
|
878
|
+
await execGit(gitRoot, ["merge", `origin/${baselineBranch}`, "--no-edit"]);
|
|
879
|
+
} else {
|
|
880
|
+
const remoteExists = await remoteBranchExists(gitRoot, branch);
|
|
881
|
+
if (remoteExists) {
|
|
882
|
+
await execGit(gitRoot, ["fetch", "origin", branch]);
|
|
883
|
+
await execGit(gitRoot, ["checkout", "-B", branch, `origin/${branch}`]);
|
|
884
|
+
} else if (await localBranchExists(gitRoot, branch)) {
|
|
885
|
+
if (await getCurrentBranch(gitRoot) !== branch) {
|
|
886
|
+
await execGit(gitRoot, ["checkout", branch]);
|
|
887
|
+
}
|
|
888
|
+
console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
|
|
889
|
+
} else {
|
|
890
|
+
await execGit(gitRoot, ["fetch", "origin", baselineBranch]);
|
|
891
|
+
try {
|
|
892
|
+
await execGit(gitRoot, [
|
|
893
|
+
"checkout",
|
|
894
|
+
"-b",
|
|
895
|
+
branch,
|
|
896
|
+
`origin/${baselineBranch}`
|
|
897
|
+
]);
|
|
898
|
+
await execGit(gitRoot, ["push", "-u", "origin", branch]);
|
|
899
|
+
} catch (err) {
|
|
900
|
+
if (await localBranchExists(gitRoot, branch)) {
|
|
901
|
+
if (await getCurrentBranch(gitRoot) !== branch) {
|
|
902
|
+
await execGit(gitRoot, ["checkout", branch]);
|
|
903
|
+
}
|
|
904
|
+
console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
|
|
905
|
+
} else {
|
|
906
|
+
throw err;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
|
|
912
|
+
return branch;
|
|
913
|
+
}
|
|
914
|
+
async function runTaskBranch(taskId, options = {}) {
|
|
915
|
+
const trimmedTaskId = taskId.trim();
|
|
916
|
+
if (!trimmedTaskId) {
|
|
917
|
+
throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
918
|
+
}
|
|
919
|
+
const cwd = options.cwd ?? process.cwd();
|
|
920
|
+
const workdirPath = resolveWorkdirPath(cwd);
|
|
921
|
+
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
922
|
+
const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
923
|
+
const branch = branchNameForTask(trimmedTaskId);
|
|
924
|
+
for (const gitRoot of repoRoots) {
|
|
925
|
+
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
926
|
+
const baselineBranch = await resolveDefaultRemoteBranch(gitRoot);
|
|
927
|
+
console.log(
|
|
928
|
+
`[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (baseline=${baselineBranch})`
|
|
929
|
+
);
|
|
930
|
+
await ensureFeatureBranch(branch, baselineBranch, {
|
|
931
|
+
...options,
|
|
932
|
+
cwd: gitRoot
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
return { branch, kind: manifest.kind, repos: repoRoots };
|
|
269
936
|
}
|
|
270
937
|
|
|
271
938
|
// src/commands/connect/cursor-agent.ts
|
|
@@ -327,16 +994,16 @@ var EventSession = class {
|
|
|
327
994
|
return;
|
|
328
995
|
}
|
|
329
996
|
if (formatedEvent.type === "tool_call") {
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
997
|
+
const callId = typeof formatedEvent.call_id === "string" ? formatedEvent.call_id.trim() : "";
|
|
998
|
+
if (callId) {
|
|
999
|
+
const existingIndex = this.events.findIndex(
|
|
1000
|
+
(e) => e.type === "tool_call" && typeof e.call_id === "string" && e.call_id === callId
|
|
1001
|
+
);
|
|
1002
|
+
if (existingIndex >= 0) {
|
|
1003
|
+
mergeToolCallEvent(this.events[existingIndex], formatedEvent);
|
|
1004
|
+
this.markDirty(existingIndex);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
340
1007
|
}
|
|
341
1008
|
this.events.push(formatedEvent);
|
|
342
1009
|
this.markDirty(this.events.length - 1);
|
|
@@ -398,15 +1065,18 @@ var EventSession = class {
|
|
|
398
1065
|
type: "thinking",
|
|
399
1066
|
content: event.text || event.content || ""
|
|
400
1067
|
};
|
|
401
|
-
case "tool_call":
|
|
1068
|
+
case "tool_call": {
|
|
1069
|
+
const raw = event;
|
|
1070
|
+
const callId = String(raw.call_id ?? raw.callId ?? "").trim();
|
|
402
1071
|
return {
|
|
403
1072
|
type: "tool_call",
|
|
404
1073
|
args: event.args,
|
|
405
1074
|
result: event.result,
|
|
406
1075
|
status: event.status,
|
|
407
|
-
call_id:
|
|
1076
|
+
call_id: callId,
|
|
408
1077
|
name: event.name
|
|
409
1078
|
};
|
|
1079
|
+
}
|
|
410
1080
|
case "task":
|
|
411
1081
|
return {
|
|
412
1082
|
type: "task",
|
|
@@ -471,6 +1141,24 @@ var EventSession = class {
|
|
|
471
1141
|
return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
|
|
472
1142
|
}
|
|
473
1143
|
};
|
|
1144
|
+
function mergeToolCallEvent(existing, incoming) {
|
|
1145
|
+
if (incoming.name) {
|
|
1146
|
+
existing.name = incoming.name;
|
|
1147
|
+
}
|
|
1148
|
+
if (incoming.status) {
|
|
1149
|
+
existing.status = incoming.status;
|
|
1150
|
+
}
|
|
1151
|
+
if (incoming.args != null && typeof incoming.args === "object" && !Array.isArray(incoming.args) && Object.keys(incoming.args).length > 0) {
|
|
1152
|
+
existing.args = { ...existing.args ?? {}, ...incoming.args };
|
|
1153
|
+
}
|
|
1154
|
+
if (incoming.result != null) {
|
|
1155
|
+
if (typeof incoming.result === "object" && !Array.isArray(incoming.result) && typeof existing.result === "object" && existing.result != null && !Array.isArray(existing.result)) {
|
|
1156
|
+
existing.result = { ...existing.result, ...incoming.result };
|
|
1157
|
+
} else {
|
|
1158
|
+
existing.result = incoming.result;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
474
1162
|
function formatLogEvent(type, event) {
|
|
475
1163
|
if (type === "input") {
|
|
476
1164
|
return `## \u7528\u6237\u8F93\u5165
|
|
@@ -511,17 +1199,17 @@ ${JSON.stringify(event, null, 2)}
|
|
|
511
1199
|
}
|
|
512
1200
|
|
|
513
1201
|
// src/commands/connect/agent-session-registry.ts
|
|
514
|
-
import { existsSync, mkdirSync as
|
|
515
|
-
import { dirname, resolve as
|
|
1202
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1203
|
+
import { dirname as dirname2, resolve as resolve4 } from "node:path";
|
|
516
1204
|
function registryPath(workdir, sessionId) {
|
|
517
|
-
return
|
|
1205
|
+
return resolve4(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
|
|
518
1206
|
}
|
|
519
1207
|
function readRegistry(path) {
|
|
520
|
-
if (!
|
|
1208
|
+
if (!existsSync3(path)) {
|
|
521
1209
|
return {};
|
|
522
1210
|
}
|
|
523
1211
|
try {
|
|
524
|
-
const parsed = JSON.parse(
|
|
1212
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
525
1213
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
526
1214
|
const result = {};
|
|
527
1215
|
for (const [key, value] of Object.entries(
|
|
@@ -538,8 +1226,8 @@ function readRegistry(path) {
|
|
|
538
1226
|
return {};
|
|
539
1227
|
}
|
|
540
1228
|
function writeRegistry(path, registry) {
|
|
541
|
-
|
|
542
|
-
|
|
1229
|
+
mkdirSync4(dirname2(path), { recursive: true });
|
|
1230
|
+
writeFileSync4(path, `${JSON.stringify(registry, null, 2)}
|
|
543
1231
|
`, "utf8");
|
|
544
1232
|
}
|
|
545
1233
|
function loadSessionAgentId(workdir, sessionId, user) {
|
|
@@ -670,215 +1358,33 @@ function installAbortSignalDebug() {
|
|
|
670
1358
|
setMaxListeners(maxFromEnv);
|
|
671
1359
|
console.log(
|
|
672
1360
|
`[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
|
|
673
|
-
);
|
|
674
|
-
}
|
|
675
|
-
process.on("warning", (warning) => {
|
|
676
|
-
if (warning.name !== "MaxListenersExceededWarning") return;
|
|
677
|
-
console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
|
|
678
|
-
if (warning.stack) {
|
|
679
|
-
console.warn(warning.stack);
|
|
680
|
-
}
|
|
681
|
-
});
|
|
682
|
-
const proto = AbortSignal.prototype;
|
|
683
|
-
const original = proto.addEventListener;
|
|
684
|
-
proto.addEventListener = function(type, listener, options) {
|
|
685
|
-
if (type === "abort") {
|
|
686
|
-
const sig = this;
|
|
687
|
-
const before = getEventListeners(sig, "abort").length;
|
|
688
|
-
const max = getMaxListeners(sig);
|
|
689
|
-
const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
|
|
690
|
-
console.log(
|
|
691
|
-
`[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
|
|
692
|
-
${stack}`
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
|
-
return original.call(this, type, listener, options);
|
|
696
|
-
};
|
|
697
|
-
console.log(
|
|
698
|
-
"[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
|
|
699
|
-
);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// src/command-utils.ts
|
|
703
|
-
import {
|
|
704
|
-
copyFileSync,
|
|
705
|
-
existsSync as existsSync2,
|
|
706
|
-
mkdirSync as mkdirSync3,
|
|
707
|
-
readFileSync as readFileSync3,
|
|
708
|
-
readdirSync,
|
|
709
|
-
statSync,
|
|
710
|
-
writeFileSync as writeFileSync3
|
|
711
|
-
} from "fs";
|
|
712
|
-
import { basename, dirname as dirname2, extname, join as join2, resolve as resolve3 } from "path";
|
|
713
|
-
import { fileURLToPath } from "url";
|
|
714
|
-
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
715
|
-
var CLI_TEMPLATE_DIR = resolve3(__dirname, "../template");
|
|
716
|
-
function workspaceApmDir(cwd = resolveWorkdirPath()) {
|
|
717
|
-
return resolve3(resolve3(cwd), ".apm");
|
|
718
|
-
}
|
|
719
|
-
function isWorkspaceApmInitialized(workdir) {
|
|
720
|
-
const apmDir = workspaceApmDir(workdir);
|
|
721
|
-
const fsApmDir = toFsPath(apmDir);
|
|
722
|
-
if (!existsSync2(fsApmDir)) {
|
|
723
|
-
return false;
|
|
724
|
-
}
|
|
725
|
-
const st = statSync(fsApmDir);
|
|
726
|
-
if (!st.isDirectory()) {
|
|
727
|
-
throw new Error(
|
|
728
|
-
`\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B\u7684 .apm \u4E0D\u662F\u76EE\u5F55\uFF0C\u8BF7\u68C0\u67E5\u672C\u5730\u63A5\u5165\u72B6\u6001\u3002`
|
|
729
|
-
);
|
|
730
|
-
}
|
|
731
|
-
return readdirSync(fsApmDir).length > 0;
|
|
732
|
-
}
|
|
733
|
-
var APM_GITIGNORE_PATTERNS = [
|
|
734
|
-
/^\.apm\/?$/,
|
|
735
|
-
/^\.apm\/\*\*$/,
|
|
736
|
-
/^\*\*\/\.apm\/?$/,
|
|
737
|
-
/^\*\*\/\.apm\/\*\*$/,
|
|
738
|
-
/^\/\.apm\/?$/,
|
|
739
|
-
/^\/\.apm\/\*\*$/
|
|
740
|
-
];
|
|
741
|
-
function normalizeGitignorePattern(line) {
|
|
742
|
-
const trimmed = line.trim();
|
|
743
|
-
if (!trimmed || trimmed.startsWith("#")) return "";
|
|
744
|
-
if (trimmed.startsWith("!")) return "";
|
|
745
|
-
const hashIndex = trimmed.indexOf("#");
|
|
746
|
-
return (hashIndex >= 0 ? trimmed.slice(0, hashIndex) : trimmed).trim();
|
|
747
|
-
}
|
|
748
|
-
function gitignoreIgnoresApm(line) {
|
|
749
|
-
const pattern = normalizeGitignorePattern(line);
|
|
750
|
-
if (!pattern) return false;
|
|
751
|
-
return APM_GITIGNORE_PATTERNS.some((re) => re.test(pattern));
|
|
752
|
-
}
|
|
753
|
-
var APM_GITIGNORE_LINE = "**/.apm/**";
|
|
754
|
-
function ensureApmGitignoredInRepo(workdir) {
|
|
755
|
-
const gitignorePath = join2(workdir, ".gitignore");
|
|
756
|
-
const fsGitignorePath = toFsPath(gitignorePath);
|
|
757
|
-
if (!existsSync2(fsGitignorePath)) {
|
|
758
|
-
writeFileSync3(fsGitignorePath, `${APM_GITIGNORE_LINE}
|
|
759
|
-
`, "utf8");
|
|
760
|
-
return true;
|
|
761
|
-
}
|
|
762
|
-
const content = readFileSync3(fsGitignorePath, "utf8");
|
|
763
|
-
if (content.split(/\r?\n/).some(gitignoreIgnoresApm)) {
|
|
764
|
-
return false;
|
|
765
|
-
}
|
|
766
|
-
const suffix = content.endsWith("\n") || content.length === 0 ? "" : "\n";
|
|
767
|
-
writeFileSync3(
|
|
768
|
-
fsGitignorePath,
|
|
769
|
-
`${content}${suffix}${APM_GITIGNORE_LINE}
|
|
770
|
-
`,
|
|
771
|
-
"utf8"
|
|
772
|
-
);
|
|
773
|
-
return true;
|
|
774
|
-
}
|
|
775
|
-
function assertApmGitignoredInRepo(workdir) {
|
|
776
|
-
const gitignorePath = join2(workdir, ".gitignore");
|
|
777
|
-
const fsGitignorePath = toFsPath(gitignorePath);
|
|
778
|
-
if (!existsSync2(fsGitignorePath)) {
|
|
779
|
-
throw new Error(
|
|
780
|
-
`\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7F3A\u5C11 .gitignore \u6587\u4EF6\uFF0C\u8BF7\u6DFB\u52A0\u5BF9 .apm \u7684\u5FFD\u7565\u89C4\u5219\uFF08\u4F8B\u5982 **/.apm/**\uFF09\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
|
|
781
|
-
);
|
|
782
|
-
}
|
|
783
|
-
const content = readFileSync3(fsGitignorePath, "utf8");
|
|
784
|
-
if (!content.split(/\r?\n/).some(gitignoreIgnoresApm)) {
|
|
785
|
-
throw new Error(
|
|
786
|
-
`\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7684 .gitignore \u672A\u5FFD\u7565 .apm\uFF0C\u8BF7\u6DFB\u52A0 **/.apm/** \u6216 .apm/\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
|
|
787
|
-
);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
async function ensureDirExists(dir) {
|
|
791
|
-
mkdirSync3(dir, { recursive: true });
|
|
792
|
-
}
|
|
793
|
-
async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
|
|
794
|
-
const dir = workspaceApmDir(cwd);
|
|
795
|
-
const fsDir = toFsPath(dir);
|
|
796
|
-
if (!existsSync2(fsDir)) {
|
|
797
|
-
mkdirSync3(fsDir, { recursive: true });
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
const st = statSync(fsDir);
|
|
801
|
-
if (!st.isDirectory()) {
|
|
802
|
-
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${dir}`);
|
|
803
|
-
}
|
|
804
|
-
if (readdirSync(fsDir).length > 0) {
|
|
805
|
-
throw new Error(
|
|
806
|
-
"[apm] .apm \u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\uFF0C\u8BF7\u5148\u5907\u4EFD\u3001\u6E05\u7A7A\u6216\u5220\u9664\u540E\u518D\u6267\u884C init"
|
|
807
|
-
);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
var WORKSPACE_TEMPLATE_SUBDIRS = [
|
|
811
|
-
"sessions",
|
|
812
|
-
"skills",
|
|
813
|
-
"rules",
|
|
814
|
-
"deploy"
|
|
815
|
-
];
|
|
816
|
-
function shouldSkipTemplateEntry(name) {
|
|
817
|
-
return name === ".DS_Store" || name === "Thumbs.db";
|
|
818
|
-
}
|
|
819
|
-
function copyTemplateEntry(src, dest) {
|
|
820
|
-
const fsSrc = toFsPath(src);
|
|
821
|
-
const fsDest = toFsPath(dest);
|
|
822
|
-
const st = statSync(fsSrc);
|
|
823
|
-
if (st.isDirectory()) {
|
|
824
|
-
mkdirSync3(fsDest, { recursive: true });
|
|
825
|
-
for (const name of readdirSync(fsSrc)) {
|
|
826
|
-
if (shouldSkipTemplateEntry(name)) continue;
|
|
827
|
-
copyTemplateEntry(join2(src, name), join2(dest, name));
|
|
828
|
-
}
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
if (!st.isFile()) return;
|
|
832
|
-
mkdirSync3(toFsPath(dirname2(dest)), { recursive: true });
|
|
833
|
-
copyFileSync(fsSrc, fsDest);
|
|
834
|
-
}
|
|
835
|
-
function assertTemplateCopiedToApm(apmDir, workdir) {
|
|
836
|
-
const required = [
|
|
837
|
-
"AGENTS.md",
|
|
838
|
-
"apm.config.json",
|
|
839
|
-
"rules",
|
|
840
|
-
"skills",
|
|
841
|
-
"sessions"
|
|
842
|
-
];
|
|
843
|
-
for (const item of required) {
|
|
844
|
-
const path = join2(apmDir, item);
|
|
845
|
-
if (!existsSync2(toFsPath(path))) {
|
|
846
|
-
throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path}`);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
const leakedRules = join2(workdir, "rules");
|
|
850
|
-
const apmRules = join2(apmDir, "rules");
|
|
851
|
-
if (existsSync2(toFsPath(leakedRules)) && !existsSync2(toFsPath(join2(apmRules, "reply.md")))) {
|
|
852
|
-
throw new Error(
|
|
853
|
-
`[apm] \u6A21\u677F\u88AB\u590D\u5236\u5230\u9519\u8BEF\u4F4D\u7F6E: ${leakedRules}\uFF08\u5E94\u5728 ${apmRules}\uFF09`
|
|
854
|
-
);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
|
|
858
|
-
const resolvedTarget = resolve3(targetDir);
|
|
859
|
-
const templateDir = resolve3(CLI_TEMPLATE_DIR);
|
|
860
|
-
const fsTemplateDir = toFsPath(templateDir);
|
|
861
|
-
if (!existsSync2(fsTemplateDir)) {
|
|
862
|
-
throw new Error(`[apm] \u672A\u627E\u5230 CLI \u6A21\u677F\u76EE\u5F55: ${templateDir}`);
|
|
863
|
-
}
|
|
864
|
-
const dirStat = statSync(fsTemplateDir);
|
|
865
|
-
if (!dirStat.isDirectory()) {
|
|
866
|
-
throw new Error(`[apm] CLI \u6A21\u677F\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55: ${templateDir}`);
|
|
867
|
-
}
|
|
868
|
-
const entries = readdirSync(fsTemplateDir).filter(
|
|
869
|
-
(name) => !shouldSkipTemplateEntry(name)
|
|
870
|
-
);
|
|
871
|
-
if (entries.length === 0) {
|
|
872
|
-
throw new Error(`[apm] CLI \u6A21\u677F\u76EE\u5F55\u4E3A\u7A7A: ${templateDir}`);
|
|
873
|
-
}
|
|
874
|
-
mkdirSync3(toFsPath(resolvedTarget), { recursive: true });
|
|
875
|
-
for (const name of entries) {
|
|
876
|
-
copyTemplateEntry(join2(templateDir, name), join2(resolvedTarget, name));
|
|
877
|
-
}
|
|
878
|
-
for (const subdir of WORKSPACE_TEMPLATE_SUBDIRS) {
|
|
879
|
-
mkdirSync3(toFsPath(join2(resolvedTarget, subdir)), { recursive: true });
|
|
1361
|
+
);
|
|
880
1362
|
}
|
|
881
|
-
|
|
1363
|
+
process.on("warning", (warning) => {
|
|
1364
|
+
if (warning.name !== "MaxListenersExceededWarning") return;
|
|
1365
|
+
console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
|
|
1366
|
+
if (warning.stack) {
|
|
1367
|
+
console.warn(warning.stack);
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
const proto = AbortSignal.prototype;
|
|
1371
|
+
const original = proto.addEventListener;
|
|
1372
|
+
proto.addEventListener = function(type, listener, options) {
|
|
1373
|
+
if (type === "abort") {
|
|
1374
|
+
const sig = this;
|
|
1375
|
+
const before = getEventListeners(sig, "abort").length;
|
|
1376
|
+
const max = getMaxListeners(sig);
|
|
1377
|
+
const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
|
|
1378
|
+
console.log(
|
|
1379
|
+
`[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
|
|
1380
|
+
${stack}`
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
return original.call(this, type, listener, options);
|
|
1384
|
+
};
|
|
1385
|
+
console.log(
|
|
1386
|
+
"[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
|
|
1387
|
+
);
|
|
882
1388
|
}
|
|
883
1389
|
|
|
884
1390
|
// src/commands/append-message.ts
|
|
@@ -1057,11 +1563,99 @@ function createUpsertWebIdePlanTool(options) {
|
|
|
1057
1563
|
};
|
|
1058
1564
|
}
|
|
1059
1565
|
|
|
1566
|
+
// src/commands/connect/webide-test-case-tools.ts
|
|
1567
|
+
function asString2(value) {
|
|
1568
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1569
|
+
}
|
|
1570
|
+
function asStringArray(value) {
|
|
1571
|
+
if (!Array.isArray(value)) return [];
|
|
1572
|
+
return value.map((item) => typeof item === "string" ? item.trim() : String(item)).filter(Boolean);
|
|
1573
|
+
}
|
|
1574
|
+
function parseCases(raw) {
|
|
1575
|
+
if (!Array.isArray(raw)) return [];
|
|
1576
|
+
const cases = [];
|
|
1577
|
+
for (const [index, item] of raw.entries()) {
|
|
1578
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
1579
|
+
const row = item;
|
|
1580
|
+
const name = asString2(row.name);
|
|
1581
|
+
if (!name) continue;
|
|
1582
|
+
const steps = asStringArray(row.steps);
|
|
1583
|
+
if (steps.length === 0) continue;
|
|
1584
|
+
cases.push({
|
|
1585
|
+
name,
|
|
1586
|
+
description: asString2(row.description),
|
|
1587
|
+
steps,
|
|
1588
|
+
sortOrder: typeof row.sortOrder === "number" && Number.isFinite(row.sortOrder) ? row.sortOrder : index
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
return cases;
|
|
1592
|
+
}
|
|
1593
|
+
function createUpsertWebIdeTestCasesTool(options) {
|
|
1594
|
+
const { cfg, taskId } = options;
|
|
1595
|
+
return {
|
|
1596
|
+
description: "Persist generated automated test cases for this WebIDE task (full replace) and mark the workflow TEST_READY. Call once with the complete case list after reviewing the task and code changes.",
|
|
1597
|
+
inputSchema: {
|
|
1598
|
+
type: "object",
|
|
1599
|
+
properties: {
|
|
1600
|
+
cases: {
|
|
1601
|
+
type: "array",
|
|
1602
|
+
description: "Complete list of test cases to store on the platform",
|
|
1603
|
+
items: {
|
|
1604
|
+
type: "object",
|
|
1605
|
+
properties: {
|
|
1606
|
+
name: {
|
|
1607
|
+
type: "string",
|
|
1608
|
+
description: "Short stable case id / name"
|
|
1609
|
+
},
|
|
1610
|
+
description: {
|
|
1611
|
+
type: "string",
|
|
1612
|
+
description: "What this case verifies (Chinese ok)"
|
|
1613
|
+
},
|
|
1614
|
+
steps: {
|
|
1615
|
+
type: "array",
|
|
1616
|
+
items: { type: "string" },
|
|
1617
|
+
description: "Ordered human-readable steps"
|
|
1618
|
+
},
|
|
1619
|
+
sortOrder: {
|
|
1620
|
+
type: "number",
|
|
1621
|
+
description: "Optional display order (0-based)"
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
required: ["name", "description", "steps"]
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
},
|
|
1628
|
+
required: ["cases"]
|
|
1629
|
+
},
|
|
1630
|
+
execute: async (args) => {
|
|
1631
|
+
const cases = parseCases(args.cases);
|
|
1632
|
+
if (cases.length === 0) {
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
"UpsertWebIdeTestCases \u7F3A\u5C11\u6709\u6548 cases\uFF08\u6BCF\u6761\u9700 name \u4E0E\u975E\u7A7A steps\uFF09"
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
const api = createApmApiClient(cfg);
|
|
1638
|
+
const result = await api.cli.webideReplaceTestCases({ taskId, cases });
|
|
1639
|
+
console.log(
|
|
1640
|
+
`[apm] UpsertWebIdeTestCases taskId=${taskId} count=${cases.length}`
|
|
1641
|
+
);
|
|
1642
|
+
return JSON.stringify(
|
|
1643
|
+
{ ok: true, count: result.count ?? cases.length },
|
|
1644
|
+
null,
|
|
1645
|
+
2
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1060
1651
|
// src/commands/connect/cursor-custom-tools.ts
|
|
1061
1652
|
var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
|
|
1062
1653
|
AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
|
|
1063
1654
|
\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
|
|
1064
1655
|
\u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
|
|
1656
|
+
var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
|
|
1657
|
+
\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
|
|
1658
|
+
\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`;
|
|
1065
1659
|
function createCursorCustomTools(cfg, messageId, options) {
|
|
1066
1660
|
const tools = {
|
|
1067
1661
|
...createAppendMessageCustomTools(
|
|
@@ -1083,6 +1677,10 @@ function createCursorCustomTools(cfg, messageId, options) {
|
|
|
1083
1677
|
cfg,
|
|
1084
1678
|
taskId: options.taskId
|
|
1085
1679
|
});
|
|
1680
|
+
tools.UpsertWebIdeTestCases = createUpsertWebIdeTestCasesTool({
|
|
1681
|
+
cfg,
|
|
1682
|
+
taskId: options.taskId
|
|
1683
|
+
});
|
|
1086
1684
|
}
|
|
1087
1685
|
return tools;
|
|
1088
1686
|
}
|
|
@@ -1094,14 +1692,19 @@ function withPlanModeToolHint(prompt, mode) {
|
|
|
1094
1692
|
|
|
1095
1693
|
${PLAN_MODE_ASK_QUESTION_HINT}`;
|
|
1096
1694
|
}
|
|
1695
|
+
function withWorkspaceBoundaryHint(prompt) {
|
|
1696
|
+
return `${prompt.trim()}
|
|
1697
|
+
|
|
1698
|
+
${WORKSPACE_BOUNDARY_HINT}`;
|
|
1699
|
+
}
|
|
1097
1700
|
|
|
1098
1701
|
// src/commands/connect/local-agent-store.ts
|
|
1099
|
-
import { mkdirSync as
|
|
1100
|
-
import { join as
|
|
1702
|
+
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
1703
|
+
import { join as join4 } from "node:path";
|
|
1101
1704
|
import { JsonlLocalAgentStore } from "@cursor/sdk";
|
|
1102
1705
|
function createWorkspaceLocalAgentStore(workdir) {
|
|
1103
|
-
const rootDir =
|
|
1104
|
-
|
|
1706
|
+
const rootDir = join4(workdir, ".apm", "cursor-agent-store");
|
|
1707
|
+
mkdirSync5(rootDir, { recursive: true });
|
|
1105
1708
|
return new JsonlLocalAgentStore(rootDir);
|
|
1106
1709
|
}
|
|
1107
1710
|
|
|
@@ -1139,7 +1742,8 @@ async function obtainAgent(ctx) {
|
|
|
1139
1742
|
local: {
|
|
1140
1743
|
cwd: ctx.cwd,
|
|
1141
1744
|
store: createWorkspaceLocalAgentStore(ctx.workdir),
|
|
1142
|
-
...ctx.customTools ? { customTools: ctx.customTools } : {}
|
|
1745
|
+
...ctx.customTools ? { customTools: ctx.customTools } : {},
|
|
1746
|
+
...ctx.enableSandbox ? { sandboxOptions: { enabled: true } } : {}
|
|
1143
1747
|
},
|
|
1144
1748
|
...ctx.mode ? { mode: ctx.mode } : {}
|
|
1145
1749
|
// mcpServers: createPlaywrightMcpServers(),
|
|
@@ -1148,14 +1752,14 @@ async function obtainAgent(ctx) {
|
|
|
1148
1752
|
const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
|
|
1149
1753
|
if (savedAgentId) {
|
|
1150
1754
|
try {
|
|
1151
|
-
const
|
|
1755
|
+
const agent = await Agent.resume(savedAgentId, agentOptions);
|
|
1152
1756
|
console.log(
|
|
1153
1757
|
`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}${explicitAgentId ? "\uFF08\u53C2\u6570\u6307\u5B9A\uFF09" : ""}`
|
|
1154
1758
|
);
|
|
1155
1759
|
if (ctx.user) {
|
|
1156
|
-
saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user,
|
|
1760
|
+
saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent.agentId);
|
|
1157
1761
|
}
|
|
1158
|
-
return { agent
|
|
1762
|
+
return { agent, resumed: true };
|
|
1159
1763
|
} catch (err) {
|
|
1160
1764
|
console.warn(
|
|
1161
1765
|
`[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
|
|
@@ -1166,11 +1770,19 @@ async function obtainAgent(ctx) {
|
|
|
1166
1770
|
}
|
|
1167
1771
|
}
|
|
1168
1772
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1773
|
+
try {
|
|
1774
|
+
const agent = await Agent.create(agentOptions);
|
|
1775
|
+
if (ctx.user) {
|
|
1776
|
+
saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent.agentId);
|
|
1777
|
+
}
|
|
1778
|
+
return { agent, resumed: false };
|
|
1779
|
+
} catch (err) {
|
|
1780
|
+
if (ctx.enableSandbox && err instanceof Error && /sandbox/i.test(err.message)) {
|
|
1781
|
+
console.warn("[apm] sandbox \u4E0D\u53EF\u7528\uFF0C\u56DE\u9000\u4E3A\u65E0 sandbox:", err.message);
|
|
1782
|
+
return obtainAgent({ ...ctx, enableSandbox: false });
|
|
1783
|
+
}
|
|
1784
|
+
throw err;
|
|
1172
1785
|
}
|
|
1173
|
-
return { agent, resumed: false };
|
|
1174
1786
|
}
|
|
1175
1787
|
async function runCursorAgent(cfg, ctx, options) {
|
|
1176
1788
|
const signal = options?.signal;
|
|
@@ -1190,9 +1802,13 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1190
1802
|
enableWebIdePlanTools: options?.enableWebIdePlanTools,
|
|
1191
1803
|
taskId: options?.taskId
|
|
1192
1804
|
});
|
|
1193
|
-
const
|
|
1805
|
+
const enableSandbox = Boolean(options?.enableSandbox);
|
|
1806
|
+
let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
|
|
1807
|
+
if (enableSandbox) {
|
|
1808
|
+
prompt = withWorkspaceBoundaryHint(prompt);
|
|
1809
|
+
}
|
|
1194
1810
|
console.log(
|
|
1195
|
-
`[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${workdir}`
|
|
1811
|
+
`[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${workdir} sandbox=${enableSandbox}`
|
|
1196
1812
|
);
|
|
1197
1813
|
const { agent, resumed } = await obtainAgent({
|
|
1198
1814
|
apiKey,
|
|
@@ -1203,7 +1819,8 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1203
1819
|
user: ctx.user,
|
|
1204
1820
|
mode: ctx.mode,
|
|
1205
1821
|
resumeAgentId: ctx.resumeAgentId,
|
|
1206
|
-
customTools
|
|
1822
|
+
customTools,
|
|
1823
|
+
enableSandbox
|
|
1207
1824
|
});
|
|
1208
1825
|
const eventSession = new EventSession(prompt);
|
|
1209
1826
|
const syncRemoteLog = options?.createRemoteLogSync ? options.createRemoteLogSync(agent.agentId) : options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorMessageLogSync(
|
|
@@ -1327,17 +1944,17 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1327
1944
|
}
|
|
1328
1945
|
|
|
1329
1946
|
// src/commands/connect/webide-agent-registry.ts
|
|
1330
|
-
import { existsSync as
|
|
1331
|
-
import { dirname as dirname3, resolve as
|
|
1947
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1948
|
+
import { dirname as dirname3, resolve as resolve5 } from "node:path";
|
|
1332
1949
|
function registryPath2(workdir, taskId) {
|
|
1333
|
-
return
|
|
1950
|
+
return resolve5(workdir, ".apm", "webide", taskId, "cursor-agent.json");
|
|
1334
1951
|
}
|
|
1335
1952
|
function readRegistry2(path) {
|
|
1336
|
-
if (!
|
|
1953
|
+
if (!existsSync4(path)) {
|
|
1337
1954
|
return {};
|
|
1338
1955
|
}
|
|
1339
1956
|
try {
|
|
1340
|
-
const parsed = JSON.parse(
|
|
1957
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
1341
1958
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1342
1959
|
const agentId = parsed.agentId;
|
|
1343
1960
|
if (typeof agentId === "string" && agentId.trim()) {
|
|
@@ -1349,8 +1966,8 @@ function readRegistry2(path) {
|
|
|
1349
1966
|
return {};
|
|
1350
1967
|
}
|
|
1351
1968
|
function writeRegistry2(path, registry) {
|
|
1352
|
-
|
|
1353
|
-
|
|
1969
|
+
mkdirSync6(dirname3(path), { recursive: true });
|
|
1970
|
+
writeFileSync5(path, `${JSON.stringify(registry, null, 2)}
|
|
1354
1971
|
`, "utf8");
|
|
1355
1972
|
}
|
|
1356
1973
|
function loadWebIdeAgentId(workdir, taskId) {
|
|
@@ -1361,18 +1978,18 @@ function saveWebIdeAgentId(workdir, taskId, agentId) {
|
|
|
1361
1978
|
}
|
|
1362
1979
|
function clearWebIdeAgentId(workdir, taskId) {
|
|
1363
1980
|
const path = registryPath2(workdir, taskId);
|
|
1364
|
-
if (!
|
|
1981
|
+
if (!existsSync4(path)) return;
|
|
1365
1982
|
writeRegistry2(path, {});
|
|
1366
1983
|
}
|
|
1367
1984
|
|
|
1368
1985
|
// src/commands/connect/webide-ask-question.ts
|
|
1369
1986
|
import { setTimeout as delay } from "node:timers/promises";
|
|
1370
1987
|
var POLL_INTERVAL_MS = 2e3;
|
|
1371
|
-
function
|
|
1988
|
+
function asString3(value) {
|
|
1372
1989
|
return typeof value === "string" ? value.trim() : "";
|
|
1373
1990
|
}
|
|
1374
1991
|
function parseQuestions(args) {
|
|
1375
|
-
const title =
|
|
1992
|
+
const title = asString3(args.title) || void 0;
|
|
1376
1993
|
const raw = args.questions;
|
|
1377
1994
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
1378
1995
|
throw new Error("AskQuestion \u7F3A\u5C11 questions");
|
|
@@ -1381,16 +1998,16 @@ function parseQuestions(args) {
|
|
|
1381
1998
|
for (const item of raw) {
|
|
1382
1999
|
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
1383
2000
|
const row = item;
|
|
1384
|
-
const id =
|
|
1385
|
-
const prompt =
|
|
2001
|
+
const id = asString3(row.id);
|
|
2002
|
+
const prompt = asString3(row.prompt);
|
|
1386
2003
|
const optionsRaw = row.options;
|
|
1387
2004
|
if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
|
|
1388
2005
|
const options = [];
|
|
1389
2006
|
for (const opt of optionsRaw) {
|
|
1390
2007
|
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
|
|
1391
2008
|
const o = opt;
|
|
1392
|
-
const oid =
|
|
1393
|
-
const label =
|
|
2009
|
+
const oid = asString3(o.id);
|
|
2010
|
+
const label = asString3(o.label);
|
|
1394
2011
|
if (oid && label) options.push({ id: oid, label });
|
|
1395
2012
|
}
|
|
1396
2013
|
if (options.length < 2) {
|
|
@@ -1479,14 +2096,14 @@ var MinioClient = class {
|
|
|
1479
2096
|
async deleteObjectsByPrefix(bucket, prefix) {
|
|
1480
2097
|
const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
|
|
1481
2098
|
const keys = [];
|
|
1482
|
-
await new Promise((
|
|
2099
|
+
await new Promise((resolve6, reject) => {
|
|
1483
2100
|
objectsStream.on("data", (obj) => {
|
|
1484
2101
|
if (obj.name) {
|
|
1485
2102
|
keys.push(obj.name);
|
|
1486
2103
|
}
|
|
1487
2104
|
});
|
|
1488
2105
|
objectsStream.on("error", reject);
|
|
1489
|
-
objectsStream.on("end",
|
|
2106
|
+
objectsStream.on("end", resolve6);
|
|
1490
2107
|
});
|
|
1491
2108
|
const chunkSize = 500;
|
|
1492
2109
|
for (let i = 0; i < keys.length; i += chunkSize) {
|
|
@@ -1654,147 +2271,12 @@ function resolveMessageReplyFallback(fallback) {
|
|
|
1654
2271
|
}
|
|
1655
2272
|
|
|
1656
2273
|
// src/commands/init.ts
|
|
1657
|
-
import { join as
|
|
1658
|
-
import { readFileSync as
|
|
1659
|
-
|
|
1660
|
-
// src/deployment-config-sync.ts
|
|
1661
|
-
import { join as join4 } from "path";
|
|
1662
|
-
import { writeFileSync as writeFileSync5 } from "fs";
|
|
1663
|
-
|
|
1664
|
-
// src/git-remote.ts
|
|
1665
|
-
import { execFile } from "child_process";
|
|
1666
|
-
import { promisify } from "util";
|
|
1667
|
-
var execFileAsync = promisify(execFile);
|
|
1668
|
-
async function tryReadGitOriginUrl(cwd) {
|
|
1669
|
-
try {
|
|
1670
|
-
const { stdout } = await execFileAsync(
|
|
1671
|
-
"git",
|
|
1672
|
-
["config", "--get", "remote.origin.url"],
|
|
1673
|
-
{ cwd, encoding: "utf8", maxBuffer: 1024 * 1024 }
|
|
1674
|
-
);
|
|
1675
|
-
const url = stdout.trim();
|
|
1676
|
-
return url || null;
|
|
1677
|
-
} catch {
|
|
1678
|
-
return null;
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
// src/git-utils.ts
|
|
1683
|
-
import { execFile as execFile2 } from "child_process";
|
|
1684
|
-
import { promisify as promisify2 } from "util";
|
|
1685
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
1686
|
-
async function execGit(cwd, args, quiet = false) {
|
|
1687
|
-
try {
|
|
1688
|
-
const { stdout, stderr } = await execFileAsync2("git", args, {
|
|
1689
|
-
cwd,
|
|
1690
|
-
encoding: "utf8",
|
|
1691
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1692
|
-
});
|
|
1693
|
-
if (!quiet && stderr.trim()) {
|
|
1694
|
-
process.stderr.write(stderr);
|
|
1695
|
-
}
|
|
1696
|
-
return stdout;
|
|
1697
|
-
} catch (err) {
|
|
1698
|
-
const e = err;
|
|
1699
|
-
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
1700
|
-
throw new Error(
|
|
1701
|
-
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
1702
|
-
);
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
async function isGitRepo(cwd) {
|
|
1706
|
-
try {
|
|
1707
|
-
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
1708
|
-
return true;
|
|
1709
|
-
} catch {
|
|
1710
|
-
return false;
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
async function resolveGitRepoRoot(cwd) {
|
|
1714
|
-
return (await execGit(cwd, ["rev-parse", "--show-toplevel"], true)).trim();
|
|
1715
|
-
}
|
|
1716
|
-
async function hasUpstream(cwd) {
|
|
1717
|
-
try {
|
|
1718
|
-
await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
|
|
1719
|
-
return true;
|
|
1720
|
-
} catch {
|
|
1721
|
-
return false;
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
|
|
1725
|
-
async function commitAndPushGitignore(workdir) {
|
|
1726
|
-
if (!await isGitRepo(workdir)) {
|
|
1727
|
-
console.log("[apm] \u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8BF7\u624B\u52A8\u63D0\u4EA4 .gitignore");
|
|
1728
|
-
return;
|
|
1729
|
-
}
|
|
1730
|
-
await execGit(workdir, ["add", "--", ".gitignore"]);
|
|
1731
|
-
await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
|
|
1732
|
-
console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
|
|
1733
|
-
const originUrl = await tryReadGitOriginUrl(workdir);
|
|
1734
|
-
if (!originUrl) {
|
|
1735
|
-
console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
|
|
1736
|
-
return;
|
|
1737
|
-
}
|
|
1738
|
-
if (await hasUpstream(workdir)) {
|
|
1739
|
-
await execGit(workdir, ["push"]);
|
|
1740
|
-
} else {
|
|
1741
|
-
await execGit(workdir, ["push", "-u", "origin", "HEAD"]);
|
|
1742
|
-
}
|
|
1743
|
-
console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
// src/baseline-resolve.ts
|
|
1747
|
-
function formatBaselineDiagnostic(workdirPath, baselineWorkdirPath, diagnostic) {
|
|
1748
|
-
return diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baselineWorkdirPath}\uFF09
|
|
1749
|
-
\u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
|
|
1750
|
-
}
|
|
1751
|
-
async function matchRepositoryByGitRemote(api, workdirPath) {
|
|
1752
|
-
const gitRoot = await resolveGitRepoRoot(workdirPath);
|
|
1753
|
-
const gitUrl = await tryReadGitOriginUrl(gitRoot);
|
|
1754
|
-
if (!gitUrl) {
|
|
1755
|
-
return null;
|
|
1756
|
-
}
|
|
1757
|
-
const matched = await api.cli.matchRepository({ url: gitUrl });
|
|
1758
|
-
const repositoryId = matched.repositoryId?.trim();
|
|
1759
|
-
const defaultBranch = matched.defaultBranch?.trim();
|
|
1760
|
-
if (!repositoryId || !defaultBranch) {
|
|
1761
|
-
return null;
|
|
1762
|
-
}
|
|
1763
|
-
console.log(
|
|
1764
|
-
`[apm] \u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u672A\u5339\u914D\uFF0C\u5DF2\u901A\u8FC7 git remote \u5173\u8054\u4ED3\u5E93: ${gitUrl}`
|
|
1765
|
-
);
|
|
1766
|
-
return { repositoryId, defaultBranch };
|
|
1767
|
-
}
|
|
1768
|
-
async function resolveWorkspaceBaseline(api, workdirPath) {
|
|
1769
|
-
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
1770
|
-
const repositoryId = baseline.repositoryId?.trim();
|
|
1771
|
-
const defaultBranch = baseline.defaultBranch?.trim();
|
|
1772
|
-
if (repositoryId && defaultBranch) {
|
|
1773
|
-
return {
|
|
1774
|
-
repositoryId,
|
|
1775
|
-
defaultBranch,
|
|
1776
|
-
workdirPath: baseline.workdirPath,
|
|
1777
|
-
matchedViaGitRemote: false
|
|
1778
|
-
};
|
|
1779
|
-
}
|
|
1780
|
-
const viaGit = await matchRepositoryByGitRemote(api, workdirPath);
|
|
1781
|
-
if (viaGit) {
|
|
1782
|
-
return {
|
|
1783
|
-
...viaGit,
|
|
1784
|
-
workdirPath: baseline.workdirPath,
|
|
1785
|
-
matchedViaGitRemote: true
|
|
1786
|
-
};
|
|
1787
|
-
}
|
|
1788
|
-
throw new Error(
|
|
1789
|
-
`[apm] ${formatBaselineDiagnostic(
|
|
1790
|
-
workdirPath,
|
|
1791
|
-
baseline.workdirPath,
|
|
1792
|
-
baseline.diagnostic
|
|
1793
|
-
)}`
|
|
1794
|
-
);
|
|
1795
|
-
}
|
|
2274
|
+
import { join as join7 } from "path";
|
|
2275
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
1796
2276
|
|
|
1797
2277
|
// src/deployment-config-sync.ts
|
|
2278
|
+
import { join as join5 } from "path";
|
|
2279
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
1798
2280
|
var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json";
|
|
1799
2281
|
var SYNC_HINT = "\u767B\u8BB0\u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u3001\u7ED1\u5B9A\u4ED3\u5E93\u540E\uFF0C\u53EF\u6267\u884C: apm sync-deploy-config";
|
|
1800
2282
|
async function resolveRepositoryIdForSync(api, workdirPath) {
|
|
@@ -1849,8 +2331,8 @@ ${diagnostic ?? ""}
|
|
|
1849
2331
|
return { synced: false, repositoryId };
|
|
1850
2332
|
}
|
|
1851
2333
|
const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
|
|
1852
|
-
const apmConfigPath = toFsPath(
|
|
1853
|
-
|
|
2334
|
+
const apmConfigPath = toFsPath(join5(targetApmDir, "apm.config.json"));
|
|
2335
|
+
writeFileSync6(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
|
|
1854
2336
|
`, "utf8");
|
|
1855
2337
|
console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${config.name}`);
|
|
1856
2338
|
console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
|
|
@@ -1859,20 +2341,20 @@ ${diagnostic ?? ""}
|
|
|
1859
2341
|
|
|
1860
2342
|
// src/repository-project-documents-sync.ts
|
|
1861
2343
|
import {
|
|
1862
|
-
existsSync as
|
|
1863
|
-
readdirSync as
|
|
1864
|
-
readFileSync as
|
|
2344
|
+
existsSync as existsSync5,
|
|
2345
|
+
readdirSync as readdirSync3,
|
|
2346
|
+
readFileSync as readFileSync6,
|
|
1865
2347
|
rmSync,
|
|
1866
|
-
writeFileSync as
|
|
2348
|
+
writeFileSync as writeFileSync7
|
|
1867
2349
|
} from "fs";
|
|
1868
|
-
import { dirname as dirname4, join as
|
|
2350
|
+
import { dirname as dirname4, join as join6, relative as relative2, sep } from "path";
|
|
1869
2351
|
var MANIFEST_FILE = "manifest.json";
|
|
1870
2352
|
function projectDocumentsDir(apmRoot) {
|
|
1871
|
-
return
|
|
2353
|
+
return join6(apmRoot ?? workspaceApmDir(), "project");
|
|
1872
2354
|
}
|
|
1873
2355
|
function projectDocumentLocalPath(apmRoot, documentPath) {
|
|
1874
2356
|
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
1875
|
-
return
|
|
2357
|
+
return join6(projectDocumentsDir(apmRoot), ...normalized.split("/"));
|
|
1876
2358
|
}
|
|
1877
2359
|
function normalizeLocalDocumentPath(path) {
|
|
1878
2360
|
const trimmed = path.trim().replace(/\\/g, "/");
|
|
@@ -1886,13 +2368,13 @@ function normalizeLocalDocumentPath(path) {
|
|
|
1886
2368
|
return segments.join("/");
|
|
1887
2369
|
}
|
|
1888
2370
|
function readLocalManifest(apmRoot) {
|
|
1889
|
-
const
|
|
1890
|
-
if (!
|
|
2371
|
+
const manifestPath2 = join6(projectDocumentsDir(apmRoot), MANIFEST_FILE);
|
|
2372
|
+
if (!existsSync5(manifestPath2)) {
|
|
1891
2373
|
return null;
|
|
1892
2374
|
}
|
|
1893
2375
|
try {
|
|
1894
2376
|
return JSON.parse(
|
|
1895
|
-
|
|
2377
|
+
readFileSync6(manifestPath2, "utf8")
|
|
1896
2378
|
);
|
|
1897
2379
|
} catch {
|
|
1898
2380
|
return null;
|
|
@@ -1969,20 +2451,20 @@ ${diagnostic ?? ""}`
|
|
|
1969
2451
|
for (const doc of list) {
|
|
1970
2452
|
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, doc.path));
|
|
1971
2453
|
await ensureDirExists(dirname4(absPath));
|
|
1972
|
-
|
|
2454
|
+
writeFileSync7(absPath, doc.content, "utf8");
|
|
1973
2455
|
downloaded += 1;
|
|
1974
2456
|
}
|
|
1975
2457
|
}
|
|
1976
2458
|
let deleted = 0;
|
|
1977
2459
|
for (const path of deleteLocal) {
|
|
1978
2460
|
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path));
|
|
1979
|
-
if (
|
|
2461
|
+
if (existsSync5(absPath)) {
|
|
1980
2462
|
rmSync(absPath, { force: true });
|
|
1981
2463
|
deleted += 1;
|
|
1982
2464
|
}
|
|
1983
2465
|
}
|
|
1984
|
-
|
|
1985
|
-
toFsPath(
|
|
2466
|
+
writeFileSync7(
|
|
2467
|
+
toFsPath(join6(projectDir, MANIFEST_FILE)),
|
|
1986
2468
|
`${JSON.stringify(remoteManifest, null, 2)}
|
|
1987
2469
|
`,
|
|
1988
2470
|
"utf8"
|
|
@@ -2017,11 +2499,11 @@ async function ensureWorkspaceInitialized(workdir, options) {
|
|
|
2017
2499
|
await syncRepositoryProjectDocumentsPull(workdir, apmDir);
|
|
2018
2500
|
const trimmedName = options?.name?.trim();
|
|
2019
2501
|
if (trimmedName) {
|
|
2020
|
-
const apmConfigPath = toFsPath(
|
|
2021
|
-
const config =
|
|
2502
|
+
const apmConfigPath = toFsPath(join7(apmDir, "apm.config.json"));
|
|
2503
|
+
const config = readFileSync7(apmConfigPath, "utf8");
|
|
2022
2504
|
const configJson = JSON.parse(config);
|
|
2023
2505
|
configJson.name = trimmedName;
|
|
2024
|
-
|
|
2506
|
+
writeFileSync8(
|
|
2025
2507
|
apmConfigPath,
|
|
2026
2508
|
`${JSON.stringify(configJson, null, 2)}
|
|
2027
2509
|
`,
|
|
@@ -2032,6 +2514,92 @@ async function ensureWorkspaceInitialized(workdir, options) {
|
|
|
2032
2514
|
return { didInit: true, syncResult };
|
|
2033
2515
|
}
|
|
2034
2516
|
|
|
2517
|
+
// src/commands/connect/pre-step-cache.ts
|
|
2518
|
+
function sessionWorkdirKey(sessionId, workdir) {
|
|
2519
|
+
return `${sessionId}\0${workdir}`;
|
|
2520
|
+
}
|
|
2521
|
+
var lastBranchKey = null;
|
|
2522
|
+
function shouldRunBranch(sessionId, workdir) {
|
|
2523
|
+
return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
|
|
2524
|
+
}
|
|
2525
|
+
function markBranchDone(sessionId, workdir) {
|
|
2526
|
+
lastBranchKey = sessionWorkdirKey(sessionId, workdir);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
// src/commands/connect/webide-draft-prs.ts
|
|
2530
|
+
var DEVELOP_ACTIONS = /* @__PURE__ */ new Set(["start-develop", "skip-plan"]);
|
|
2531
|
+
function shouldEnsureWebIdeDraftPullRequests(action) {
|
|
2532
|
+
return DEVELOP_ACTIONS.has(action);
|
|
2533
|
+
}
|
|
2534
|
+
async function ensureWebIdeDraftPullRequests(cfg, taskId, workdir) {
|
|
2535
|
+
const api = createApmApiClient(cfg);
|
|
2536
|
+
let repoRoots;
|
|
2537
|
+
try {
|
|
2538
|
+
const manifest = loadWorkspaceReposCache(workdir);
|
|
2539
|
+
repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
|
|
2540
|
+
} catch (err) {
|
|
2541
|
+
console.warn(
|
|
2542
|
+
"[apm] WebIDE \u8349\u7A3F PR\uFF1A\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25:",
|
|
2543
|
+
err instanceof Error ? err.message : err
|
|
2544
|
+
);
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
console.log(
|
|
2548
|
+
`[apm] WebIDE \u8349\u7A3F PR\uFF1A\u51C6\u5907\u4E3A ${repoRoots.length} \u4E2A\u4ED3\u5E93\u521B\u5EFA feat/task-${taskId}`
|
|
2549
|
+
);
|
|
2550
|
+
for (const gitRoot of repoRoots) {
|
|
2551
|
+
const label = formatRepoLabel(workdir, gitRoot);
|
|
2552
|
+
try {
|
|
2553
|
+
const originUrl = await tryReadGitOriginUrl(gitRoot);
|
|
2554
|
+
if (!originUrl) {
|
|
2555
|
+
console.warn(
|
|
2556
|
+
`[apm] WebIDE \u8349\u7A3F PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u65E0 remote.origin.url\uFF09`
|
|
2557
|
+
);
|
|
2558
|
+
continue;
|
|
2559
|
+
}
|
|
2560
|
+
const matched = await api.cli.matchRepository({ url: originUrl });
|
|
2561
|
+
const repositoryId = matched.repositoryId?.trim();
|
|
2562
|
+
if (!repositoryId) {
|
|
2563
|
+
console.warn(
|
|
2564
|
+
`[apm] WebIDE \u8349\u7A3F PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u5E73\u53F0\u672A\u767B\u8BB0\u4ED3\u5E93 ${originUrl}\uFF09`
|
|
2565
|
+
);
|
|
2566
|
+
continue;
|
|
2567
|
+
}
|
|
2568
|
+
const pr = await api.cli.createWebIdeDraftPullRequest({
|
|
2569
|
+
taskId,
|
|
2570
|
+
repositoryId
|
|
2571
|
+
});
|
|
2572
|
+
console.log(
|
|
2573
|
+
`[apm] WebIDE \u8349\u7A3F PR\uFF1A${label} \u2192 #${pr.number} ${pr.state} ${pr.url || ""}`
|
|
2574
|
+
);
|
|
2575
|
+
} catch (err) {
|
|
2576
|
+
console.warn(
|
|
2577
|
+
`[apm] WebIDE \u8349\u7A3F PR\uFF1A${label} \u5931\u8D25:`,
|
|
2578
|
+
err instanceof Error ? err.message : err
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// src/commands/connect/ensure-workspace-sandbox.ts
|
|
2585
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9, existsSync as existsSync6 } from "fs";
|
|
2586
|
+
import { join as join8 } from "path";
|
|
2587
|
+
var DEFAULT_SANDBOX_JSON = `{
|
|
2588
|
+
"type": "workspace_readwrite",
|
|
2589
|
+
"networkPolicy": {
|
|
2590
|
+
"default": "allow"
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
`;
|
|
2594
|
+
function ensureWorkspaceSandboxConfig(workdir) {
|
|
2595
|
+
const cursorDir = toFsPath(join8(workdir, ".cursor"));
|
|
2596
|
+
const sandboxPath = toFsPath(join8(cursorDir, "sandbox.json"));
|
|
2597
|
+
if (existsSync6(sandboxPath)) return;
|
|
2598
|
+
mkdirSync7(cursorDir, { recursive: true });
|
|
2599
|
+
writeFileSync9(sandboxPath, DEFAULT_SANDBOX_JSON, "utf8");
|
|
2600
|
+
console.log(`[apm] \u5DF2\u5199\u5165\u5DE5\u4F5C\u533A sandbox \u914D\u7F6E\uFF1A${sandboxPath}`);
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2035
2603
|
// src/commands/connect/handle-webide-message.ts
|
|
2036
2604
|
async function updateStatus(cfg, messageId, status) {
|
|
2037
2605
|
const api = createApmApiClient(cfg);
|
|
@@ -2049,6 +2617,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2049
2617
|
const workdir = requireRemoteWorkdir(msg.workdir);
|
|
2050
2618
|
const messageId = msg.messageId;
|
|
2051
2619
|
const taskId = msg.taskId;
|
|
2620
|
+
const branchCacheKey = `webide:${taskId}`;
|
|
2052
2621
|
console.log(
|
|
2053
2622
|
`[apm] webide-message action=${msg.action} taskId=${taskId} messageId=${messageId}`
|
|
2054
2623
|
);
|
|
@@ -2059,6 +2628,24 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2059
2628
|
if (!didInit) {
|
|
2060
2629
|
assertApmGitignoredInRepo(workdir);
|
|
2061
2630
|
}
|
|
2631
|
+
ensureWorkspaceSandboxConfig(workdir);
|
|
2632
|
+
resolveWorkspaceRepos(workdir);
|
|
2633
|
+
if (shouldRunBranch(branchCacheKey, workdir)) {
|
|
2634
|
+
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
2635
|
+
const branchResult = await runTaskBranch(taskId, { cwd: workdir });
|
|
2636
|
+
markBranchDone(branchCacheKey, workdir);
|
|
2637
|
+
console.log(
|
|
2638
|
+
`[apm] webide branch ready kind=${branchResult.kind} branch=${branchResult.branch} repos=${branchResult.repos.length}`
|
|
2639
|
+
);
|
|
2640
|
+
} else {
|
|
2641
|
+
console.log(
|
|
2642
|
+
`[apm] step=branch skipped taskId=${taskId} workdir=${workdir}`
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
if (shouldEnsureWebIdeDraftPullRequests(msg.action)) {
|
|
2646
|
+
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
2647
|
+
await ensureWebIdeDraftPullRequests(cfg, taskId, workdir);
|
|
2648
|
+
}
|
|
2062
2649
|
const savedAgentId = loadWebIdeAgentId(workdir, taskId);
|
|
2063
2650
|
const logSyncRef = { current: null };
|
|
2064
2651
|
const outcome = await runCursorAgent(
|
|
@@ -2082,6 +2669,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
2082
2669
|
signal
|
|
2083
2670
|
}),
|
|
2084
2671
|
enableWebIdePlanTools: true,
|
|
2672
|
+
enableSandbox: true,
|
|
2085
2673
|
taskId,
|
|
2086
2674
|
createRemoteLogSync: (agentId) => {
|
|
2087
2675
|
saveWebIdeAgentId(workdir, taskId, agentId);
|