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.
@@ -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(path) {
275
- let normalized = path.trim().replace(/\\/g, "/").normalize("NFC");
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
- var GIT_NETWORK_TIMEOUT_MS = 9e4;
368
- var GIT_PUSH_TIMEOUT_MS = 12e4;
369
- var GIT_LOCAL_TIMEOUT_MS = 6e4;
370
- var GIT_NETWORK_CMDS = /* @__PURE__ */ new Set([
371
- "fetch",
372
- "push",
373
- "ls-remote",
374
- "clone",
375
- "pull"
376
- ]);
377
- function mergeGitSshCommand(existing) {
378
- const base = existing?.trim() || "ssh";
379
- const extras = [];
380
- if (!/\bStrictHostKeyChecking=/.test(base)) {
381
- extras.push("-o", "StrictHostKeyChecking=accept-new");
382
- }
383
- if (!/\bBatchMode=/.test(base)) {
384
- extras.push("-o", "BatchMode=yes");
385
- }
386
- if (!/\bConnectTimeout=/.test(base)) {
387
- extras.push("-o", "ConnectTimeout=20");
388
- }
389
- if (!/\bServerAliveInterval=/.test(base)) {
390
- extras.push("-o", "ServerAliveInterval=10");
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
- if (!/\bServerAliveCountMax=/.test(base)) {
393
- extras.push("-o", "ServerAliveCountMax=3");
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 gitExecEnv() {
398
- return {
399
- ...process.env,
400
- GIT_SSH_COMMAND: mergeGitSshCommand(process.env.GIT_SSH_COMMAND),
401
- // 避免 credential helper / askpass 在无 TTY 时挂起
402
- GIT_TERMINAL_PROMPT: "0"
403
- };
425
+ async function tryReadHttpsGitOriginUrl(cwd) {
426
+ const raw = await tryReadGitOriginUrl(cwd);
427
+ if (!raw) return null;
428
+ return toHttpsGitRemoteUrl(raw);
404
429
  }
405
- function defaultGitTimeoutMs(args) {
406
- const cmd = args[0] ?? "";
407
- if (cmd === "push") return GIT_PUSH_TIMEOUT_MS;
408
- if (GIT_NETWORK_CMDS.has(cmd)) return GIT_NETWORK_TIMEOUT_MS;
409
- return GIT_LOCAL_TIMEOUT_MS;
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, quietOrOptions = false) {
412
- const options = typeof quietOrOptions === "boolean" ? { quiet: quietOrOptions } : quietOrOptions;
413
- const quiet = options.quiet ?? false;
414
- const timeoutMs = options.timeoutMs === void 0 ? defaultGitTimeoutMs(args) : options.timeoutMs;
446
+ async function execGit(cwd, args, quiet = false) {
415
447
  try {
416
- const { stdout, stderr } = await execFileAsync("git", args, {
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
- var FEATURE_BRANCH_RE = /^feat\/task-(.+)$/;
450
- function isFeatureBranchName(branch) {
451
- return FEATURE_BRANCH_RE.test(branch.trim().replace(/^origin\//, ""));
452
- }
453
- function resolveGitScript(name) {
454
- return join3(dirname(fileURLToPath(import.meta.url)), "scripts", name);
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 runGitScript(name, options) {
457
- const skipCode = options.skipCode ?? 2;
458
- const timeoutMs = options.timeoutMs ?? GIT_NETWORK_TIMEOUT_MS;
459
- const script = resolveGitScript(name);
460
- const args = [script, ...options.args ?? []];
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
- const { stdout, stderr } = await execFileAsync("bash", args, {
463
- cwd: options.cwd,
464
- env: gitExecEnv(),
465
- encoding: "utf8",
466
- maxBuffer: 10 * 1024 * 1024,
467
- timeout: timeoutMs,
468
- killSignal: "SIGTERM"
469
- });
470
- if (stdout.trim()) process.stdout.write(stdout);
471
- if (stderr.trim()) process.stderr.write(stderr);
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
- async function branchHasChangesAgainstBase(cwd, baseBranch, headBranch) {
490
- return runGitScript("branch_has_changes.sh", {
491
- cwd,
492
- args: [baseBranch, headBranch],
493
- timeoutMs: GIT_NETWORK_TIMEOUT_MS
494
- });
495
- }
496
- async function commitWorkingTreeIfDirty(cwd, message) {
497
- await ensureGitRepo(cwd);
498
- return runGitScript("commit_if_dirty.sh", {
499
- cwd,
500
- args: message?.trim() ? [message.trim()] : [],
501
- timeoutMs: GIT_LOCAL_TIMEOUT_MS
502
- });
529
+ var FEATURE_BRANCH_RE = /^feat\/task-(.+)$/;
530
+ function isFeatureBranchName(branch) {
531
+ return FEATURE_BRANCH_RE.test(branch.trim().replace(/^origin\//, ""));
503
532
  }
504
- async function ensureFeatureBranch(cwd, branch, baselineBranch) {
505
- await runGitScript("ensure_feature_branch.sh", {
506
- cwd,
507
- args: [branch, baselineBranch],
508
- timeoutMs: GIT_PUSH_TIMEOUT_MS
509
- });
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
- return runGitScript("push_code.sh", {
513
- cwd,
514
- timeoutMs: GIT_PUSH_TIMEOUT_MS
515
- });
516
- }
517
-
518
- // src/utils/home-repository.ts
519
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
520
- import { join as join4, resolve as resolve3 } from "path";
521
- 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
- entries.sort((a, b) => a.path.localeCompare(b.path));
572
- if (entries.length === 0) {
573
- throw new Error(`[apm] ${label} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
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
- return entries;
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 existsSync3,
554
+ existsSync as existsSync2,
585
555
  mkdirSync as mkdirSync3,
586
- readFileSync as readFileSync3,
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 as dirname2, join as join5, relative, resolve as resolve4 } from "path";
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 existsSync3(toFsPath(join5(dir, ".git")));
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 join5(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
574
+ return join3(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
660
575
  }
661
576
  function absoluteRepoPath(workdir, entry) {
662
- return resolve4(workdir, entry.path);
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
- return relative(workdir, repoAbs).replace(/\\/g, "/");
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 path = o.path.trim().replace(/\\/g, "/");
672
- if (path === "." || path.includes("/")) return null;
673
- const entry = { path };
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 path = toFsPath(manifestPath(workdir));
683
- if (!existsSync3(path)) {
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
- readFileSync3(path, "utf8")
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: "multi",
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 path = toFsPath(manifestPath(workdir));
721
- writeFileSync2(path, `${JSON.stringify(manifest, null, 2)}
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 (!existsSync3(fsWorkdir) || !statSync2(fsWorkdir).isDirectory()) {
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 = resolve4(workdir, name);
678
+ const childAbs = resolve3(workdir, name);
754
679
  const childFs = toFsPath(childAbs);
755
- if (!existsSync3(childFs) || !statSync2(childFs).isDirectory()) {
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 || basename(repoAbs) || repoAbs;
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 commitWorkspaceReposIfDirty(workdir, message, reposManifest) {
866
- const result = await commitTaskReposIfDirty(workdir, message, reposManifest);
867
- return result.committed;
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 commitTaskReposIfDirty(workdir, message, reposManifest) {
870
- return syncTaskRepos(workdir, { message, reposManifest });
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 syncTaskRepos(workdir, options = {}) {
873
- const opts = typeof options === "string" ? { message: options } : options;
813
+ async function commitWorkspaceReposIfDirty(workdir, message) {
874
814
  const workdirPath = resolveWorkdirPath(workdir);
875
- const repoRoots = resolveTaskBranchRepoRoots(
876
- workdirPath,
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
- try {
888
- const didCommit = await commitWorkingTreeIfDirty(gitRoot, message);
889
- if (didCommit) {
890
- committed += 1;
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 ensureTaskFeatureBranch(branch, projectId, options) {
855
+ async function ensureFeatureBranch(branch, baselineBranch, options) {
952
856
  const cwd = options.cwd ?? process.cwd();
953
857
  const gitRoot = await resolveGitRepoRoot(cwd);
954
- await ensureFeatureBranch(gitRoot, branch, projectId);
955
- return branch;
956
- }
957
- async function resolveProjectIdForTask(taskId) {
958
- const trimmedTaskId = taskId.trim();
959
- if (!trimmedTaskId) {
960
- throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
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 projectId = projectBaseline.baseBranch?.trim() ?? "";
968
- if (!projectId) {
969
- throw new Error("[apm] \u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F / \u9879\u76EE ID\uFF0C\u65E0\u6CD5\u521B\u5EFA WebIDE \u7279\u6027\u5206\u652F");
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
- return projectId;
972
- }
973
- function resolveTaskBranchRepoRoots(workdirPath, reposManifest) {
974
- const manifestPath2 = resolveReposManifestPath(reposManifest);
975
- if (manifestPath2) {
976
- const entries = loadHomeRepositoryEntries(manifestPath2);
977
- const roots2 = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
920
+ const cwd = options.cwd ?? process.cwd();
921
+ const workdirPath = resolveWorkdirPath(cwd);
922
+ const manifest = loadWorkspaceReposCache(workdirPath);
923
+ const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
924
+ const branch = branchNameForTask(trimmedTaskId);
925
+ for (const gitRoot of repoRoots) {
926
+ const label = formatRepoLabel(workdirPath, gitRoot);
978
927
  console.log(
979
- `[apm] \u8BFB\u53D6\u5916\u90E8\u591A\u4ED3\u6E05\u5355 ${manifestPath2} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
928
+ `[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (project baseline=${baselineBranch})`
980
929
  );
981
- return roots2;
930
+ await ensureFeatureBranch(branch, baselineBranch, {
931
+ ...options,
932
+ cwd: gitRoot
933
+ });
982
934
  }
983
- const workspace = loadWorkspaceReposCache(workdirPath);
984
- const roots = resolveWorkspaceRepoAbsolutePaths(workspace);
985
- console.log(
986
- `[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 repos=${workspace.repos.map(
987
- (r) => r.remoteUrl ? `${r.path}\u2192${r.remoteUrl}` : r.path
988
- ).join(", ")}`
989
- );
990
- return roots;
935
+ return {
936
+ branch,
937
+ baseline: baselineBranch,
938
+ kind: manifest.kind,
939
+ repos: repoRoots
940
+ };
991
941
  }
992
- async function runTaskBranch(taskId, projectId, options = {}) {
993
- const trimmedTaskId = taskId.trim();
994
- if (!trimmedTaskId) {
995
- throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
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 repoRoots = resolveTaskBranchRepoRoots(
1007
- workdirPath,
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
- `[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (projectId=${trimmedProjectId})`
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
- branch,
1039
- projectId: trimmedProjectId,
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 existsSync4, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
1694
- import { isAbsolute, relative as relative2, resolve as resolve5 } from "node:path";
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 = resolve5(toFsPath(workdir));
1766
- const targetAbs = resolve5(toFsPath(absolutePath));
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 (!existsSync4(fsPath)) {
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 readFileSync4(toFsPath(absPath), "utf8");
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/index.ts
1973
- var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
1974
- AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
1975
- \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
1976
- \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
1977
- var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
1978
- \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
1979
- \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`;
1980
- function wantsTool(name, options, legacyDefault) {
1981
- if (options.toolNames !== void 0) {
1982
- return options.toolNames.includes(name);
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
- return legacyDefault;
1985
- }
1986
- function createCursorCustomTools(cfg2, options) {
1987
- const enableAppend = options.enableAppendMessage !== false && Boolean(options.appendMessageContent);
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
- return tools;
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
- return `${prompt.trim()}
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
- ${PLAN_MODE_ASK_QUESTION_HINT}`;
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 withWorkspaceBoundaryHint(prompt) {
2075
- return `${prompt.trim()}
2076
-
2077
- ${WORKSPACE_BOUNDARY_HINT}`;
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/local-sqlite-agent-store.ts
2081
- import {
2082
- composeLocalAgentStore
2083
- } from "@cursor/sdk";
2084
- import { SqliteLocalAgentStore } from "@cursor/sdk/sqlite";
2085
-
2086
- // src/commands/connect/remote-local-agent-store.ts
2087
- function asCheckpointRef(value) {
2088
- if (value === void 0) return void 0;
2089
- if (value === null) return null;
2090
- if (!value || typeof value !== "object") return null;
2091
- const row = value;
2092
- if (typeof row.rootBlobId !== "string" || !row.rootBlobId.trim()) {
2093
- return null;
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 toApiCheckpointRef(ref) {
2101
- if (ref === void 0) return void 0;
2102
- if (ref === null) return null;
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 fromApiAgent(row) {
2109
- return {
2110
- agentId: row.agentId,
2111
- cwd: row.cwd,
2112
- status: row.status,
2113
- activeRunId: row.activeRunId,
2114
- name: row.name,
2115
- createdAt: row.createdAt,
2116
- updatedAt: row.updatedAt,
2117
- latestCheckpoint: asCheckpointRef(row.latestCheckpoint) ?? null,
2118
- sdkMetadata: row.sdkMetadata ?? void 0
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 fromApiRun(row) {
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
- runId: row.runId,
2124
- requestId: row.requestId,
2125
- agentId: row.agentId,
2126
- turnNumber: row.turnNumber,
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
- var unsupportedCheckpoints = {
2142
- async get() {
2143
- return null;
2144
- },
2145
- async create() {
2146
- },
2147
- async update() {
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
- var unsupportedRunEvents = {
2156
- async append() {
2157
- throw new Error("runEvents \u4EC5\u5B58\u672C\u673A SQLite");
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
- function createRemoteLocalAgentStore(cfg2) {
2166
- const api = createApmApiClient(cfg2);
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
- async function upsertRun(run) {
2182
- const row = await api.cli.cursorStoreUpsertRun({
2183
- runId: run.runId,
2184
- agentId: run.agentId,
2185
- requestId: run.requestId ?? null,
2186
- turnNumber: run.turnNumber,
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
- agents: {
2204
- async get(input) {
2205
- const row = await api.cli.cursorStoreGetAgent({
2206
- agentId: input.agentId
2207
- });
2208
- return row ? fromApiAgent(row) : null;
2209
- },
2210
- async create(input) {
2211
- return upsertAgent(input.agent);
2212
- },
2213
- async update(input) {
2214
- return upsertAgent(input.agent);
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 delete(input) {
2217
- const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
2218
- const cwd = input.filter.cwd?.trim();
2219
- if (agentIds && agentIds.length > 0) {
2220
- await api.cli.cursorStoreDeleteAgents({ agentIds, cwd });
2221
- return;
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 (cwd) {
2224
- await api.cli.cursorStoreDeleteAgents({ cwd });
2225
- return;
2206
+ if (!localDirRaw) {
2207
+ return {
2208
+ content: [{ type: "text", text: "localDir \u4E0D\u80FD\u4E3A\u7A7A" }],
2209
+ isError: true
2210
+ };
2226
2211
  }
2227
- let cursor;
2228
- for (; ; ) {
2229
- const page = await api.cli.cursorStoreListAgents({
2230
- cursor,
2231
- limit: 200
2232
- });
2233
- if (page.items.length === 0) break;
2234
- await api.cli.cursorStoreDeleteAgents({
2235
- agentIds: page.items.map((item) => item.agentId)
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
- async list(input) {
2242
- const page = await api.cli.cursorStoreListAgents({
2243
- cursor: input?.filter?.cursor,
2244
- limit: input?.filter?.limit,
2245
- cwd: input?.filter?.cwd
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
- let cursor;
2277
- for (; ; ) {
2278
- const page = await api.cli.cursorStoreListRuns({
2279
- cursor,
2280
- limit: 200
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
- if (!page.nextCursor) break;
2287
- cursor = page.nextCursor;
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/local-sqlite-agent-store.ts
2310
- var sqliteByKey = /* @__PURE__ */ new Map();
2311
- async function openSqliteLocalAgentStore(workspaceRef) {
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
- async append(input) {
2339
- const row = await local.append(input);
2340
- try {
2341
- await api.cli.cursorStoreAppendRunEvents({
2342
- events: [
2343
- {
2344
- runId: input.runId,
2345
- eventType: input.eventType,
2346
- payload: jsonSafePayload(input.payload),
2347
- payloadRef: input.payloadRef ?? null,
2348
- idempotencyKey: input.idempotencyKey ?? `${input.runId}:${row.seq}`,
2349
- createdAt: row.createdAt
2350
- }
2351
- ]
2352
- });
2353
- } catch (err) {
2354
- console.warn(
2355
- `[apm] runEvents \u53EF\u89C6\u5316\u540C\u6B65\u8DF3\u8FC7 runId=${input.runId} seq=${row.seq}:`,
2356
- err instanceof Error ? err.message : err
2357
- );
2358
- }
2359
- return row;
2360
- },
2361
- list: (input) => local.list(input),
2362
- async delete(input) {
2363
- await local.delete(input);
2364
- const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
2365
- if (!runIds?.length) return;
2366
- try {
2367
- await api.cli.cursorStoreDeleteRunEvents({ runIds });
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/cursor-agent.ts
2416
- var noopRemoteLogSync = {
2417
- schedule(_session) {
2418
- },
2419
- async flush(_session) {
2420
- }
2421
- };
2422
- function formatCursorRunFailure(runId, options) {
2423
- const details = [
2424
- options?.statusError?.trim(),
2425
- options?.resultText?.trim()
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 `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
2300
+ return legacyDefault;
2434
2301
  }
2435
- async function obtainAgent(ctx) {
2436
- const store = await createApmCursorAgentStore(ctx.cfg, ctx.cwd);
2437
- const agentOptions = {
2438
- apiKey: ctx.apiKey,
2439
- model: { id: ctx.model || "default" },
2440
- local: {
2441
- cwd: ctx.cwd,
2442
- store,
2443
- ...ctx.customTools ? { customTools: ctx.customTools } : {},
2444
- ...ctx.enableSandbox ? {
2445
- sandboxOptions: { enabled: true },
2446
- // 只加载项目级设置,避免 ~/.cursor/mcp.json 里需 OAuth/审批的 MCP 在无头沙箱被拒
2447
- settingSources: ["project"]
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
- const savedAgentId = ctx.resumeAgentId?.trim();
2453
- if (savedAgentId) {
2454
- try {
2455
- const agent = await Agent.resume(savedAgentId, agentOptions);
2456
- console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
2457
- return { agent, resumed: true };
2458
- } catch (err) {
2459
- console.warn(
2460
- `[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
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
- try {
2467
- const agent = await Agent.create(agentOptions);
2468
- return { agent, resumed: false };
2469
- } catch (err) {
2470
- if (ctx.enableSandbox && err instanceof Error && /sandbox/i.test(err.message)) {
2471
- console.warn("[apm] sandbox \u4E0D\u53EF\u7528\uFF0C\u56DE\u9000\u4E3A\u65E0 sandbox:", err.message);
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
- throw err;
2475
- }
2476
- }
2477
- async function runCursorAgent(cfg2, ctx, options) {
2478
- const signal = options.signal;
2479
- if (signal?.aborted) {
2480
- throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
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
- await syncRemoteLog.flush(eventSession);
2576
- const result = await run.wait();
2577
- if (result.usage) {
2578
- eventSession.addRunUsage(result.usage, {
2579
- durationMs: result.durationMs
2580
- });
2581
- await syncRemoteLog.flush(eventSession);
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 (result.status === "error") {
2587
- const failureMessage = formatCursorRunFailure(result.id, {
2588
- statusError: lastRunErrorStatus,
2589
- resultText: result.result
2590
- });
2591
- console.error(`[apm] ${failureMessage}`);
2592
- if (resumed) {
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 (result.status === "cancelled") {
2598
- throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
2360
+ if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
2361
+ Object.assign(
2362
+ tools,
2363
+ createMergeWebIdePullRequestsTool({
2364
+ webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
2365
+ })
2366
+ );
2599
2367
  }
2600
- console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
2601
- const artifacts = await agent.listArtifacts().catch(() => []);
2602
- const artifactDocuments = [];
2603
- for (const artifact of artifacts) {
2604
- try {
2605
- const content = (await agent.downloadArtifact(artifact.path)).toString(
2606
- "utf8"
2607
- );
2608
- artifactDocuments.push({ path: artifact.path, content });
2609
- } catch (err) {
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
- return {
2617
- runId: result.id,
2618
- agentId: agent.agentId,
2619
- status: result.status,
2620
- result: result.result,
2621
- durationMs: result.durationMs,
2622
- usage: result.usage,
2623
- modelId: result.model?.id?.trim() || ctx.model?.trim() || void 0,
2624
- assistantText: eventSession.getAssistantText(),
2625
- createPlan: eventSession.getCreatePlanContent(),
2626
- artifacts,
2627
- artifactDocuments
2628
- };
2629
- } catch (err) {
2630
- if (err instanceof CursorAgentError) {
2631
- if (resumed) {
2632
- invalidateAgentMapping();
2633
- }
2634
- throw new Error(
2635
- `Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
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
- // src/commands/connect/webide-agent-registry.ts
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
- var DESIGN_AGENT_FILE = "design-agent.json";
2653
- function readRegistry(path) {
2654
- if (!existsSync5(path)) {
2655
- return {};
2656
- }
2657
- try {
2658
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
2659
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2660
- const raw = parsed;
2661
- const state = {};
2662
- if (typeof raw.agentId === "string" && raw.agentId.trim()) {
2663
- state.agentId = raw.agentId.trim();
2664
- }
2665
- if (typeof raw.taskId === "string" && raw.taskId.trim()) {
2666
- state.taskId = raw.taskId.trim();
2667
- }
2668
- if (typeof raw.messageId === "string" && raw.messageId.trim()) {
2669
- state.messageId = raw.messageId.trim();
2670
- }
2671
- if (typeof raw.action === "string" && raw.action.trim()) {
2672
- state.action = raw.action.trim();
2673
- }
2674
- if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
2675
- state.status = raw.status;
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
- } catch {
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 writeRegistry(path, registry) {
2687
- mkdirSync4(dirname3(path), { recursive: true });
2688
- writeFileSync3(path, `${JSON.stringify(registry, null, 2)}
2689
- `, "utf8");
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 syncWebIdeTaskState(workdir, taskId, patch) {
2692
- const trimmedTaskId = taskId.trim();
2693
- if (!trimmedTaskId) return;
2694
- const path = registryPath(workdir, trimmedTaskId);
2695
- const current = readRegistry(path);
2696
- const next = {
2697
- ...current,
2698
- taskId: trimmedTaskId,
2699
- updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
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
- if (patch.agentId !== void 0) {
2702
- const agentId = patch.agentId.trim();
2703
- if (agentId) next.agentId = agentId;
2704
- else delete next.agentId;
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
- if (patch.messageId !== void 0) {
2707
- const messageId = patch.messageId.trim();
2708
- if (messageId) next.messageId = messageId;
2709
- else delete next.messageId;
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
- if (patch.action !== void 0) {
2712
- const action = patch.action.trim();
2713
- if (action) next.action = action;
2714
- else delete next.action;
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
- if (patch.status !== void 0) {
2717
- next.status = patch.status;
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/webide-ask-question.ts
2761
- function asString4(value) {
2762
- return typeof value === "string" ? value.trim() : "";
2763
- }
2764
- function parseQuestions(args) {
2765
- const title = asString4(args.title) || void 0;
2766
- const raw = args.questions;
2767
- if (!Array.isArray(raw) || raw.length === 0) {
2768
- throw new Error("AskQuestion \u7F3A\u5C11 questions");
2769
- }
2770
- const questions = [];
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
- if (questions.length === 0) {
2792
- throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
2793
- }
2794
- return { title, questions };
3845
+ return DEFAULT_REPLY;
2795
3846
  }
2796
- async function fetchAssumptionAnswersAndContinueAssessment(options) {
2797
- const { cfg: cfg2, taskId, questionIds, title } = options;
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
- const list = await api.cli.webideListAssumptions({ taskId });
2800
- const byId = new Map(list.assumptions.map((a) => [a.questionId, a]));
2801
- const ids = questionIds && questionIds.length > 0 ? questionIds : list.assumptions.filter((a) => a.status === "RESOLVED").map((a) => a.questionId);
2802
- if (ids.length === 0) {
2803
- throw new Error("\u6CA1\u6709\u5DF2\u786E\u8BA4\u7684\u5047\u8BBE");
2804
- }
2805
- const answers = [];
2806
- for (const id of ids) {
2807
- const row = byId.get(id);
2808
- if (!row || row.status !== "RESOLVED") {
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] \u5047\u8BBE\u7ED3\u679C\u5DF2\u62C9\u53D6 taskId=${taskId} count=${answers.length}`
3862
+ `[apm] webide-start-project target=${msg2.target} restart=${msg2.restart} taskId=${taskId} messageId=${messageId}`
2821
3863
  );
2822
- return JSON.stringify({ title, answers }, null, 2);
2823
- }
2824
- function createWebIdeAskQuestionExecute(options) {
2825
- const { cfg: cfg2, taskId, onAwaitingAssumptions } = options;
2826
- return async (args) => {
2827
- const parsed = parseQuestions(args);
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
- console.log(
2830
- `[apm] AskQuestion \u521B\u5EFA\u5F85\u786E\u8BA4\u9879 taskId=${taskId} count=${parsed.questions.length}`
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
- await api.cli.webideReplaceAssumptions({
2833
- taskId,
2834
- title: parsed.title,
2835
- questions: parsed.questions
2836
- });
2837
- await api.cli.webideRunAction({
2838
- taskId,
2839
- action: "await-assumptions"
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
- onAwaitingAssumptions?.();
2842
- return [
2843
- "\u5047\u8BBE\u5DF2\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u6D41\u8FDB\u5165\u300C\u5F85\u786E\u8BA4\u5047\u8BBE\u300D\u3002",
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
- async ensureBucket(bucket) {
2865
- const exists = await this.inner.bucketExists(bucket);
2866
- if (!exists) {
2867
- await this.inner.makeBucket(bucket);
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
- async putObject(bucket, objectKey, body, meta) {
2871
- await this.inner.putObject(bucket, objectKey, body, body.length, meta);
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
- async fPutObject(bucket, objectKey, filePath, meta) {
2875
- await this.inner.fPutObject(bucket, objectKey, filePath, meta);
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
- async listObjects(bucket, prefix) {
2878
- const objects = [];
2879
- const stream = this.inner.listObjectsV2(bucket, prefix, true);
2880
- await new Promise((resolve7, reject) => {
2881
- stream.on("data", (obj) => {
2882
- if (!obj.name) return;
2883
- objects.push({
2884
- name: obj.name,
2885
- size: typeof obj.size === "number" ? obj.size : 0,
2886
- lastModified: obj.lastModified ?? null,
2887
- ...typeof obj.etag === "string" ? { etag: obj.etag } : {}
2888
- });
2889
- });
2890
- stream.on("error", reject);
2891
- stream.on("end", () => resolve7());
2892
- });
2893
- return objects;
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
- async statObject(bucket, objectKey) {
2896
- const stat = await this.inner.statObject(bucket, objectKey);
2897
- return {
2898
- size: typeof stat.size === "number" ? stat.size : 0,
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
- async removeObject(bucket, objectKey) {
2903
- await this.inner.removeObject(bucket, objectKey);
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
- async getObjectBuffer(bucket, objectKey) {
2906
- const stream = await this.inner.getObject(bucket, objectKey);
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
- async function createApmLogMinioClient(cfg2) {
2921
- const storage = await fetchApmLogStorage(cfg2);
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
- // src/commands/connect/webide-message-log.ts
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
- async function putDirtyEvents(minio, bucket, prefix, events) {
2941
- for (const event of events) {
2942
- const key = `${prefix}${event.index}.json`;
2943
- const body = Buffer.from(
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
- async function upsertLogHeader(cfg2, ctx, patch, objectPrefix) {
2957
- const api = createApmApiClient(cfg2);
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 createThrottledWebIdeMessageLogSync(cfg2, ctx, onError, options) {
2966
- let lastRunAt = 0;
2967
- let timer;
2968
- let latestSession;
2969
- let syncChain = Promise.resolve();
2970
- let minio;
2971
- let bucket = "";
2972
- let firstLogNotified = false;
2973
- const resolvePrefix = () => options?.objectPrefixOverride?.trim() || webIdeEventsObjectPrefix(ctx.taskId, ctx.messageId);
2974
- const ensureMinio = async () => {
2975
- if (minio) return;
2976
- const created = await createApmLogMinioClient(cfg2);
2977
- minio = created.client;
2978
- bucket = created.bucket;
2979
- await minio.ensureBucket(bucket);
2980
- };
2981
- const notifyFirstLog = async () => {
2982
- if (firstLogNotified) return;
2983
- firstLogNotified = true;
2984
- try {
2985
- await options?.onFirstLogSynced?.();
2986
- } catch (err) {
2987
- firstLogNotified = false;
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/connect/ensure-message-reply.ts
3069
- var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
3070
- function resolveMessageReplyFallback(fallback) {
3071
- for (const candidate of [
3072
- fallback.assistantText,
3073
- fallback.result,
3074
- fallback.createPlan
3075
- ]) {
3076
- const trimmed = candidate?.trim();
3077
- if (trimmed) {
3078
- return trimmed;
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
- return DEFAULT_REPLY;
3082
- }
3083
-
3084
- // src/commands/sync-webide-agent.ts
3085
- import {
3086
- existsSync as existsSync6,
3087
- mkdirSync as mkdirSync5,
3088
- readdirSync as readdirSync3,
3089
- readFileSync as readFileSync6,
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 null;
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 syncWebIdeAgentFiles(workdir, cfg2) {
3122
- const api = createApmApiClient(cfg2);
3123
- const bundle = await api.cli.webideAgentConfig();
3124
- const revision = bundle.revision?.trim();
3125
- if (!revision) {
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 rulesDir = join6(apmDir, "rules");
3134
- mkdirSync5(toFsPath(rulesDir), { recursive: true });
3135
- writeFileSync4(
3136
- toFsPath(join6(apmDir, "AGENTS.md")),
3137
- withTrailingNewline(bundle.guide ?? ""),
3138
- "utf8"
3139
- );
3140
- const keep = /* @__PURE__ */ new Set();
3141
- for (const rule of bundle.rules ?? []) {
3142
- const key = rule.key?.trim() ?? "";
3143
- if (!RULE_KEY.test(key)) {
3144
- throw new Error(`[apm] \u975E\u6CD5\u89C4\u5219\u540D: ${key || "(\u7A7A)"}`);
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
- keep.add(`${key}.md`);
3147
- writeFileSync4(
3148
- toFsPath(join6(rulesDir, `${key}.md`)),
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
- for (const name of readdirSync3(toFsPath(rulesDir))) {
3154
- if (!name.endsWith(".md")) continue;
3155
- if (keep.has(name)) continue;
3156
- unlinkSync(toFsPath(join6(rulesDir, name)));
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/init.ts
3167
- async function ensureWorkspaceInitialized(workdir) {
3168
- if (isWorkspaceApmInitialized(workdir)) {
3169
- return { didInit: false };
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] \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`
4250
+ `[apm] \u5047\u8BBE\u7ED3\u679C\u5DF2\u62C9\u53D6 taskId=${taskId} count=${answers.length}`
3173
4251
  );
3174
- await ensureWorkspaceApmDirForInit(workdir);
3175
- console.log(`[apm] \u5DF2\u521B\u5EFA\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF1A${workspaceApmDir(workdir)}`);
3176
- return { didInit: true };
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 existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3404
- import { join as join7 } from "path";
3405
- var MANIFEST_FILE = ".sync-manifest.json";
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 path = join7(dir, MANIFEST_FILE);
3421
- if (!existsSync7(path)) {
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(path, "utf8")
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
- writeFileSync5(
3437
- join7(dir, MANIFEST_FILE),
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 || !existsSync7(dest)) return false;
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 = join7(dir, item.name);
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
- writeFileSync5(dest, buffer);
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 existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
3492
- import { join as join8 } from "path";
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 MANIFEST_FILE2 = ".sync-manifest.json";
4609
+ var MANIFEST_FILE3 = ".sync-manifest.json";
3509
4610
  function hashBuffer(buf) {
3510
- return createHash("sha256").update(buf).digest("hex");
4611
+ return createHash3("sha256").update(buf).digest("hex");
3511
4612
  }
3512
4613
  function loadManifest2(dir) {
3513
- const path = join8(dir, MANIFEST_FILE2);
3514
- if (!existsSync8(path)) {
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(path, "utf8"));
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
- writeFileSync6(
3528
- join8(dir, MANIFEST_FILE2),
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 = join8(dir, name);
4657
+ const localPath = join7(dir, name);
3557
4658
  const entry = manifest.files[name];
3558
- if (entry?.contentHash === art.contentHash && existsSync8(localPath)) {
4659
+ if (entry?.contentHash === art.contentHash && existsSync10(localPath)) {
3559
4660
  continue;
3560
4661
  }
3561
4662
  const buf = await downloadDesignFile(cfg2, taskId, name);
3562
- writeFileSync6(localPath, buf);
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 (!existsSync8(dir)) return [];
4672
+ if (!existsSync10(dir)) return [];
3572
4673
  const out = [];
3573
- for (const name of readdirSync4(dir)) {
4674
+ for (const name of readdirSync5(dir)) {
3574
4675
  if (!isWebIdeDesignArtifactName(name)) continue;
3575
- const absPath = join8(dir, name);
3576
- if (!statSync4(absPath).isFile()) continue;
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 updateStatus(cfg2, messageId, status) {
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 setError(cfg2, messageId, error) {
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 updateStatus(cfg2, messageId, "TYPING");
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} projectId=${branchResult.projectId} repos=${branchResult.repos.length}`;
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
- persistAgentId(outcome.agentId);
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 setError(cfg2, messageId, detail);
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 updateStatus(cfg2, messageId, "CANCELLED");
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 setError(cfg2, messageId, detail);
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 updateStatus(cfg2, messageId, "SUCCESS");
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 setError(cfg2, messageId, detail);
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
- await handleWebIdeInboundMessage(cfg2, msg2, signal);
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