ai-project-manage-cli 8.1.25 → 8.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,232 +457,113 @@ async function execGit(cwd, args, quietOrOptions = false) {
426
457
  return stdout;
427
458
  } catch (err) {
428
459
  const e = err;
429
- if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
430
- throw new Error(
431
- `[apm] git ${args.join(" ")} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
432
- );
433
- }
434
460
  const detail = (e.stderr ?? e.message ?? String(err)).trim();
435
461
  throw new Error(
436
462
  `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
437
463
  );
438
464
  }
439
465
  }
466
+ async function isGitRepo(cwd) {
467
+ try {
468
+ await execGit(cwd, ["rev-parse", "--git-dir"], true);
469
+ return true;
470
+ } catch {
471
+ return false;
472
+ }
473
+ }
440
474
  async function ensureGitRepo(cwd) {
441
475
  await execGit(cwd, ["rev-parse", "--git-dir"], true);
442
476
  }
443
477
  async function getCurrentBranch(cwd) {
444
478
  return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
445
479
  }
480
+ async function isWorkingTreeDirty(cwd) {
481
+ const out = await execGit(cwd, ["status", "--porcelain"], true);
482
+ return out.trim().length > 0;
483
+ }
484
+ async function remoteBranchExists(cwd, branch) {
485
+ const out = await execGit(
486
+ cwd,
487
+ ["ls-remote", "--heads", "origin", branch],
488
+ true
489
+ );
490
+ return out.trim().length > 0;
491
+ }
446
492
  async function resolveGitRepoRoot(cwd) {
447
493
  return (await execGit(cwd, ["rev-parse", "--show-toplevel"], true)).trim();
448
494
  }
449
- 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
- // src/commands/branch.ts
583
- import { basename as basename2 } from "path";
584
-
585
552
  // src/utils/workspace-repos.ts
586
553
  import {
587
- existsSync as existsSync3,
554
+ existsSync as existsSync2,
588
555
  mkdirSync as mkdirSync3,
589
- readFileSync as readFileSync3,
556
+ readFileSync as readFileSync2,
590
557
  readdirSync as readdirSync2,
591
558
  statSync as statSync2,
592
559
  writeFileSync as writeFileSync2
593
560
  } from "fs";
594
- import { basename, dirname as dirname2, join as join5, relative, resolve as resolve4 } from "path";
595
-
596
- // src/utils/git-remote.ts
597
- import { execFile as execFile2 } from "child_process";
598
- import { promisify as promisify2 } from "util";
599
- var execFileAsync2 = promisify2(execFile2);
600
- function toHttpsGitRemoteUrl(raw) {
601
- let s = raw.trim();
602
- if (!s) return null;
603
- const sshScp = /^git@([^:]+):(.+)$/.exec(s);
604
- if (sshScp) {
605
- s = `https://${sshScp[1]}/${sshScp[2]}`;
606
- } else {
607
- const sshUri = /^ssh:\/\/(?:git@)?([^/]+)\/(.+)$/i.exec(s);
608
- if (sshUri) {
609
- s = `https://${sshUri[1]}/${sshUri[2]}`;
610
- } else if (/^http:\/\//i.test(s)) {
611
- s = `https://${s.slice("http://".length)}`;
612
- } else if (!/^https:\/\//i.test(s)) {
613
- const scpLike = /^([^/:]+):(.+)$/.exec(s);
614
- if (scpLike && !scpLike[1].includes(".")) {
615
- return null;
616
- }
617
- if (scpLike) {
618
- s = `https://${scpLike[1]}/${scpLike[2]}`;
619
- } else if (s.includes("/")) {
620
- s = `https://${s}`;
621
- } else {
622
- return null;
623
- }
624
- }
625
- }
626
- s = s.replace(/\/+$/, "");
627
- if (!/^https:\/\//i.test(s)) return null;
628
- return s;
629
- }
630
- async function tryReadGitOriginUrl(cwd) {
631
- try {
632
- const { stdout } = await execFileAsync2(
633
- "git",
634
- ["config", "--get", "remote.origin.url"],
635
- { cwd, encoding: "utf8", maxBuffer: 1024 * 1024 }
636
- );
637
- const url = stdout.trim();
638
- return url || null;
639
- } catch {
640
- return null;
641
- }
642
- }
643
- async function tryReadHttpsGitOriginUrl(cwd) {
644
- const raw = await tryReadGitOriginUrl(cwd);
645
- if (!raw) return null;
646
- return toHttpsGitRemoteUrl(raw);
647
- }
648
-
649
- // src/utils/workspace-repos.ts
561
+ import { basename, dirname, join as join3, relative, resolve as resolve3 } from "path";
650
562
  var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
651
563
  var WORKSPACE_REPOS_VERSION = 1;
652
564
  var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([".apm", "node_modules"]);
653
565
  function hasGitAt(dir) {
654
- return existsSync3(toFsPath(join5(dir, ".git")));
566
+ return existsSync2(toFsPath(join3(dir, ".git")));
655
567
  }
656
568
  function isSkippableChildName(name) {
657
569
  if (SKIP_DIR_NAMES.has(name)) return true;
@@ -659,21 +571,25 @@ function isSkippableChildName(name) {
659
571
  return false;
660
572
  }
661
573
  function manifestPath(workdir) {
662
- return join5(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
574
+ return join3(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
663
575
  }
664
576
  function absoluteRepoPath(workdir, entry) {
665
- return resolve4(workdir, entry.path);
577
+ if (entry.path === "." || entry.path === "") {
578
+ return workdir;
579
+ }
580
+ return resolve3(workdir, entry.path);
666
581
  }
667
582
  function toRelativeRepoPath(workdir, repoAbs) {
668
- return relative(workdir, repoAbs).replace(/\\/g, "/");
583
+ const rel = relative(workdir, repoAbs).replace(/\\/g, "/");
584
+ return rel === "" ? "." : rel;
669
585
  }
670
586
  function normalizeRepoEntry(raw) {
671
587
  if (!raw || typeof raw !== "object") return null;
672
588
  const o = raw;
673
589
  if (typeof o.path !== "string" || !o.path.trim()) return null;
674
- const path = o.path.trim().replace(/\\/g, "/");
675
- if (path === "." || path.includes("/")) return null;
676
- const entry = { path };
590
+ const entry = {
591
+ path: o.path.trim().replace(/\\/g, "/")
592
+ };
677
593
  if (typeof o.remoteUrl === "string" && o.remoteUrl.trim()) {
678
594
  entry.remoteUrl = o.remoteUrl.trim();
679
595
  } else if (o.remoteUrl === null) {
@@ -682,18 +598,18 @@ function normalizeRepoEntry(raw) {
682
598
  return entry;
683
599
  }
684
600
  function readManifest(workdir) {
685
- const path = toFsPath(manifestPath(workdir));
686
- if (!existsSync3(path)) {
601
+ const path3 = toFsPath(manifestPath(workdir));
602
+ if (!existsSync2(path3)) {
687
603
  return null;
688
604
  }
689
605
  try {
690
606
  const raw = JSON.parse(
691
- readFileSync3(path, "utf8")
607
+ readFileSync2(path3, "utf8")
692
608
  );
693
609
  if (raw?.version !== WORKSPACE_REPOS_VERSION) {
694
610
  return null;
695
611
  }
696
- if (raw.kind !== "multi") {
612
+ if (raw.kind !== "single" && raw.kind !== "multi") {
697
613
  return null;
698
614
  }
699
615
  if (!Array.isArray(raw.repos) || raw.repos.length === 0) {
@@ -708,7 +624,7 @@ function readManifest(workdir) {
708
624
  }
709
625
  return {
710
626
  version: WORKSPACE_REPOS_VERSION,
711
- kind: "multi",
627
+ kind: raw.kind,
712
628
  workdir: raw.workdir,
713
629
  repos,
714
630
  scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt.trim() ? raw.scannedAt : (/* @__PURE__ */ new Date()).toISOString()
@@ -720,8 +636,8 @@ function readManifest(workdir) {
720
636
  function writeManifest(workdir, manifest) {
721
637
  const apmDir = toFsPath(workspaceApmDir(workdir));
722
638
  mkdirSync3(apmDir, { recursive: true });
723
- const path = toFsPath(manifestPath(workdir));
724
- writeFileSync2(path, `${JSON.stringify(manifest, null, 2)}
639
+ const path3 = toFsPath(manifestPath(workdir));
640
+ writeFileSync2(path3, `${JSON.stringify(manifest, null, 2)}
725
641
  `, "utf8");
726
642
  }
727
643
  function manifestStillValid(workdir, manifest) {
@@ -732,9 +648,6 @@ function manifestStillValid(workdir, manifest) {
732
648
  if (typeof entry?.path !== "string" || !entry.path.trim()) {
733
649
  return false;
734
650
  }
735
- if (entry.path === "." || entry.path.includes("/")) {
736
- return false;
737
- }
738
651
  const abs = absoluteRepoPath(workdir, entry);
739
652
  if (!hasGitAt(abs)) {
740
653
  return false;
@@ -744,8 +657,17 @@ function manifestStillValid(workdir, manifest) {
744
657
  }
745
658
  function scanWorkspaceRepos(workdirInput) {
746
659
  const workdir = resolveWorkdirPath(workdirInput);
660
+ if (hasGitAt(workdir)) {
661
+ return {
662
+ version: WORKSPACE_REPOS_VERSION,
663
+ kind: "single",
664
+ workdir,
665
+ repos: [{ path: "." }],
666
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString()
667
+ };
668
+ }
747
669
  const fsWorkdir = toFsPath(workdir);
748
- if (!existsSync3(fsWorkdir) || !statSync2(fsWorkdir).isDirectory()) {
670
+ if (!existsSync2(fsWorkdir) || !statSync2(fsWorkdir).isDirectory()) {
749
671
  throw new Error(`[apm] \u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${workdir}`);
750
672
  }
751
673
  const repos = [];
@@ -753,9 +675,9 @@ function scanWorkspaceRepos(workdirInput) {
753
675
  if (isSkippableChildName(name)) {
754
676
  continue;
755
677
  }
756
- const childAbs = resolve4(workdir, name);
678
+ const childAbs = resolve3(workdir, name);
757
679
  const childFs = toFsPath(childAbs);
758
- if (!existsSync3(childFs) || !statSync2(childFs).isDirectory()) {
680
+ if (!existsSync2(childFs) || !statSync2(childFs).isDirectory()) {
759
681
  continue;
760
682
  }
761
683
  if (!hasGitAt(childAbs)) {
@@ -766,7 +688,7 @@ function scanWorkspaceRepos(workdirInput) {
766
688
  repos.sort((a, b) => a.path.localeCompare(b.path));
767
689
  if (repos.length === 0) {
768
690
  throw new Error(
769
- `[apm] \u5DE5\u4F5C\u533A ${workdir} \u4E00\u7EA7\u5B50\u76EE\u5F55\u4E2D\u672A\u53D1\u73B0 git \u4ED3\u5E93`
691
+ `[apm] \u5DE5\u4F5C\u533A ${workdir} \u6839\u76EE\u5F55\u65E0 .git\uFF0C\u4E14\u4E00\u7EA7\u5B50\u76EE\u5F55\u4E2D\u672A\u53D1\u73B0 git \u4ED3\u5E93`
770
692
  );
771
693
  }
772
694
  return {
@@ -794,7 +716,7 @@ function loadWorkspaceReposCache(workdirInput) {
794
716
  throw new Error(`[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58\u65E0\u6548\uFF08repos \u4E3A\u7A7A\uFF09\uFF0C\u8BF7\u5F3A\u5236\u91CD\u65B0\u626B\u63CF`);
795
717
  }
796
718
  console.log(
797
- `[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 repos=${cached.repos.map((r) => r.path).join(", ")}`
719
+ `[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 kind=${cached.kind} repos=${cached.repos.map((r) => r.path).join(", ")}`
798
720
  );
799
721
  return {
800
722
  ...cached,
@@ -832,7 +754,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
832
754
  const cached = readManifest(workdir);
833
755
  if (cached && manifestStillValid(workdir, cached)) {
834
756
  console.log(
835
- `[apm] \u4F7F\u7528\u7F13\u5B58\u7684\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 repos=${cached.repos.length}`
757
+ `[apm] \u4F7F\u7528\u7F13\u5B58\u7684\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 kind=${cached.kind} repos=${cached.repos.length}`
836
758
  );
837
759
  return cached;
838
760
  }
@@ -849,7 +771,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
849
771
  }
850
772
  writeManifest(workdir, scanned);
851
773
  console.log(
852
- `[apm] \u5DF2\u626B\u63CF\u5DE5\u4F5C\u533A\u4ED3\u5E93 repos=${scanned.repos.map((r) => r.path).join(", ")} \u2192 .apm/${WORKSPACE_REPOS_MANIFEST}`
774
+ `[apm] \u5DF2\u626B\u63CF\u5DE5\u4F5C\u533A\u4ED3\u5E93 kind=${scanned.kind} repos=${scanned.repos.map((r) => r.path).join(", ")} \u2192 .apm/${WORKSPACE_REPOS_MANIFEST}`
853
775
  );
854
776
  return scanned;
855
777
  }
@@ -860,70 +782,48 @@ function resolveWorkspaceRepoAbsolutePaths(manifest) {
860
782
  }
861
783
  function formatRepoLabel(workdir, repoAbs) {
862
784
  const rel = toRelativeRepoPath(resolveWorkdirPath(workdir), repoAbs);
863
- return rel || basename(repoAbs) || repoAbs;
785
+ return rel === "." ? basename(repoAbs) || repoAbs : rel;
864
786
  }
865
787
 
866
788
  // src/commands/branch.ts
867
- import { existsSync as existsSync4 } from "fs";
868
789
  var TASK_BRANCH_PREFIX = "feat/task-";
869
- async function commitWorkspaceReposIfDirty(workdir, message, reposManifest) {
870
- const result = await commitTaskReposIfDirty(workdir, message, reposManifest);
871
- 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
+ }
872
801
  }
873
- async function commitTaskReposIfDirty(workdir, message, reposManifest) {
874
- 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;
875
812
  }
876
- async function syncTaskRepos(workdir, options = {}) {
877
- const opts = typeof options === "string" ? { message: options } : options;
813
+ async function commitWorkspaceReposIfDirty(workdir, message) {
878
814
  const workdirPath = resolveWorkdirPath(workdir);
879
- const repoRoots = resolveTaskBranchRepoRoots(
880
- workdirPath,
881
- opts.reposManifest
882
- );
815
+ const manifest = loadWorkspaceReposCache(workdirPath);
816
+ const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
883
817
  let committed = 0;
884
- let commitSkipped = 0;
885
- let pushed = 0;
886
- let pushSkipped = 0;
887
- const failed = [];
888
- const message = opts.message;
889
818
  for (const gitRoot of repoRoots) {
890
819
  const label = formatRepoLabel(workdirPath, gitRoot);
891
- try {
892
- const didCommit = await commitWorkingTreeIfDirty(gitRoot, message);
893
- if (didCommit) {
894
- committed += 1;
895
- console.log(`[apm] \u5DF2\u63D0\u4EA4 @ ${label}`);
896
- } else {
897
- commitSkipped += 1;
898
- }
899
- } catch (err) {
900
- const detail = err instanceof Error ? err.message : String(err);
901
- console.error(`[apm] \u4ED3\u5E93 ${label} \u63D0\u4EA4\u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
902
- failed.push(label);
903
- continue;
904
- }
905
- try {
906
- const didPush = await pushCurrentBranch(gitRoot);
907
- if (didPush) {
908
- pushed += 1;
909
- console.log(`[apm] \u5DF2 push @ ${label}`);
910
- } else {
911
- pushSkipped += 1;
912
- }
913
- } catch (err) {
914
- const detail = err instanceof Error ? err.message : String(err);
915
- console.error(`[apm] \u4ED3\u5E93 ${label} push \u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
916
- failed.push(label);
820
+ const did = await commitWorkingTreeIfDirty(gitRoot, message);
821
+ if (did) {
822
+ committed += 1;
823
+ console.log(`[apm] WebIDE \u515C\u5E95\u63D0\u4EA4 @ ${label}`);
917
824
  }
918
825
  }
919
- return {
920
- committed,
921
- commitSkipped,
922
- pushed,
923
- pushSkipped,
924
- failed,
925
- repos: repoRoots
926
- };
826
+ return committed;
927
827
  }
928
828
  async function pushWorkspaceRepos(workdir) {
929
829
  const workdirPath = resolveWorkdirPath(workdir);
@@ -952,14 +852,59 @@ function branchNameForTask(taskId) {
952
852
  }
953
853
  return `${TASK_BRANCH_PREFIX}${id}`;
954
854
  }
955
- async function ensureTaskFeatureBranch(branch, projectId, options) {
855
+ async function ensureFeatureBranch(branch, baselineBranch, options) {
956
856
  const cwd = options.cwd ?? process.cwd();
957
857
  const gitRoot = await resolveGitRepoRoot(cwd);
958
- await ensureFeatureBranch(gitRoot, branch, projectId);
959
- return branch;
960
- }
961
- async function resolveProjectIdForTask(taskId) {
962
- const trimmedTaskId = taskId.trim();
858
+ await ensureGitRepo(gitRoot);
859
+ const current = await getCurrentBranch(gitRoot);
860
+ if (current === branch) {
861
+ console.log(`[apm] \u5DF2\u5728\u5206\u652F ${branch}\uFF0C\u8DF3\u8FC7`);
862
+ return branch;
863
+ }
864
+ await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
865
+ if (await isWorkingTreeDirty(gitRoot)) {
866
+ await execGit(gitRoot, [
867
+ "stash",
868
+ "push",
869
+ "-u",
870
+ "-m",
871
+ `apm: switch to ${branch}`
872
+ ]);
873
+ }
874
+ const remoteExists = await remoteBranchExists(gitRoot, branch);
875
+ if (remoteExists) {
876
+ await execGit(gitRoot, ["fetch", "origin", branch]);
877
+ await execGit(gitRoot, ["checkout", "-B", branch, `origin/${branch}`]);
878
+ } else if (await localBranchExists(gitRoot, branch)) {
879
+ await execGit(gitRoot, ["checkout", branch]);
880
+ console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
881
+ } else {
882
+ await execGit(gitRoot, ["fetch", "origin", baselineBranch]);
883
+ try {
884
+ await execGit(gitRoot, [
885
+ "checkout",
886
+ "--no-track",
887
+ "-b",
888
+ branch,
889
+ `origin/${baselineBranch}`
890
+ ]);
891
+ await execGit(gitRoot, ["push", "-u", "origin", branch]);
892
+ } catch (err) {
893
+ if (await localBranchExists(gitRoot, branch)) {
894
+ if (await getCurrentBranch(gitRoot) !== branch) {
895
+ await execGit(gitRoot, ["checkout", branch]);
896
+ }
897
+ console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
898
+ } else {
899
+ throw err;
900
+ }
901
+ }
902
+ }
903
+ console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
904
+ return branch;
905
+ }
906
+ async function runTaskBranch(taskId, options = {}) {
907
+ const trimmedTaskId = taskId.trim();
963
908
  if (!trimmedTaskId) {
964
909
  throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
965
910
  }
@@ -968,93 +913,75 @@ async function resolveProjectIdForTask(taskId) {
968
913
  const projectBaseline = await api.cli.projectBaseBranch({
969
914
  taskId: trimmedTaskId
970
915
  });
971
- const projectId = projectBaseline.baseBranch?.trim() ?? "";
972
- if (!projectId) {
973
- 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");
974
919
  }
975
- return projectId;
976
- }
977
- function isWorkspaceReposManifestFile(manifestPath2) {
978
- return basename2(manifestPath2.replace(/\\/g, "/")) === WORKSPACE_REPOS_MANIFEST;
979
- }
980
- function resolveTaskBranchRepoRoots(workdirPath, reposManifest) {
981
- const manifestPath2 = resolveReposManifestPath(reposManifest);
982
- if (manifestPath2 && !isWorkspaceReposManifestFile(manifestPath2)) {
983
- const entries = loadHomeRepositoryEntries(manifestPath2);
984
- const roots2 = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
920
+ const cwd = options.cwd ?? process.cwd();
921
+ const workdirPath = resolveWorkdirPath(cwd);
922
+ const manifest = loadWorkspaceReposCache(workdirPath);
923
+ const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
924
+ const branch = branchNameForTask(trimmedTaskId);
925
+ for (const gitRoot of repoRoots) {
926
+ const label = formatRepoLabel(workdirPath, gitRoot);
985
927
  console.log(
986
- `[apm] \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})`
987
929
  );
988
- return roots2;
989
- }
990
- if (manifestPath2 && isWorkspaceReposManifestFile(manifestPath2)) {
991
- const fsPath = toFsPath(manifestPath2);
992
- if (!existsSync4(fsPath)) {
993
- throw new Error(`[apm] \u672A\u627E\u5230\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 ${manifestPath2}`);
994
- }
995
- console.log(`[apm] \u4F7F\u7528\u663E\u5F0F\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 ${manifestPath2}`);
930
+ await ensureFeatureBranch(branch, baselineBranch, {
931
+ ...options,
932
+ cwd: gitRoot
933
+ });
996
934
  }
997
- const workspace = loadWorkspaceReposCache(workdirPath);
998
- const roots = resolveWorkspaceRepoAbsolutePaths(workspace);
999
- console.log(
1000
- `[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 repos=${workspace.repos.map(
1001
- (r) => r.remoteUrl ? `${r.path}\u2192${r.remoteUrl}` : r.path
1002
- ).join(", ")}`
1003
- );
1004
- return roots;
935
+ return {
936
+ branch,
937
+ baseline: baselineBranch,
938
+ kind: manifest.kind,
939
+ repos: repoRoots
940
+ };
1005
941
  }
1006
- async function runTaskBranch(taskId, projectId, options = {}) {
1007
- const trimmedTaskId = taskId.trim();
1008
- if (!trimmedTaskId) {
1009
- throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
1010
- }
1011
- const trimmedProjectId = projectId.trim();
1012
- if (!trimmedProjectId) {
1013
- throw new Error("[apm] projectId\uFF08\u9879\u76EE ID\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
1014
- }
1015
- if (/[\s\\]/.test(trimmedProjectId) || trimmedProjectId.includes("..")) {
1016
- throw new Error("[apm] \u9879\u76EE ID \u975E\u6CD5");
942
+ async function checkoutBaselineBranches(baselineBranch, options = {}) {
943
+ const baseline = baselineBranch.trim().replace(/^origin\//, "");
944
+ if (!baseline) {
945
+ throw new Error("[apm] \u57FA\u7EBF\u5206\u652F\u4E0D\u80FD\u4E3A\u7A7A");
1017
946
  }
1018
947
  const cwd = options.cwd ?? process.cwd();
1019
948
  const workdirPath = resolveWorkdirPath(cwd);
1020
- const repoRoots = resolveTaskBranchRepoRoots(
1021
- workdirPath,
1022
- options.reposManifest
1023
- );
1024
- if (repoRoots.length === 0) {
1025
- throw new Error("[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7279\u6027\u5206\u652F");
1026
- }
1027
- const branch = branchNameForTask(trimmedTaskId);
1028
- const repoLabels = repoRoots.map(
1029
- (gitRoot) => formatRepoLabel(workdirPath, gitRoot)
1030
- );
1031
- console.log(
1032
- `[apm] \u591A\u4ED3\u786E\u8BA4\u7279\u6027\u5206\u652F ${branch} projectId=${trimmedProjectId} repos=${repoLabels.join(",")}`
1033
- );
949
+ const manifest = loadWorkspaceReposCache(workdirPath);
950
+ const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
1034
951
  for (const gitRoot of repoRoots) {
1035
952
  const label = formatRepoLabel(workdirPath, gitRoot);
1036
- console.log(
1037
- `[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (projectId=${trimmedProjectId})`
1038
- );
1039
- try {
1040
- await ensureTaskFeatureBranch(branch, trimmedProjectId, {
1041
- ...options,
1042
- cwd: gitRoot
1043
- });
1044
- } catch (err) {
1045
- const detail = err instanceof Error ? err.message : String(err);
1046
- throw new Error(
1047
- `[apm] \u4ED3\u5E93 ${label} \u786E\u8BA4\u7279\u6027\u5206\u652F\u5931\u8D25\uFF08\u591A\u4ED3\u4EFB\u4E00\u5931\u8D25\u5219\u6574\u6B21\u5931\u8D25\uFF09: ${detail}`
1048
- );
1049
- }
953
+ console.log(`[apm] \u5207\u6362\u57FA\u7EBF\u5206\u652F ${baseline} @ ${label}`);
954
+ await checkoutBaselineBranch(baseline, { ...options, cwd: gitRoot });
1050
955
  }
1051
956
  return {
1052
- branch,
1053
- projectId: trimmedProjectId,
1054
- kind: "multi",
957
+ baseline,
958
+ kind: manifest.kind,
1055
959
  repos: repoRoots
1056
960
  };
1057
961
  }
962
+ async function checkoutBaselineBranch(baselineBranch, options = {}) {
963
+ const baseline = baselineBranch.trim().replace(/^origin\//, "");
964
+ if (!baseline) {
965
+ throw new Error("[apm] \u57FA\u7EBF\u5206\u652F\u4E0D\u80FD\u4E3A\u7A7A");
966
+ }
967
+ const cwd = options.cwd ?? process.cwd();
968
+ const gitRoot = await resolveGitRepoRoot(cwd);
969
+ await ensureGitRepo(gitRoot);
970
+ if (await isWorkingTreeDirty(gitRoot)) {
971
+ throw new Error(`[apm] \u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u53D8\u66F4\uFF0C\u65E0\u6CD5\u5207\u6362\u5230\u57FA\u7EBF\u5206\u652F ${baseline}`);
972
+ }
973
+ await ensureRemoteBaselineBranch(gitRoot, baseline);
974
+ const current = await getCurrentBranch(gitRoot);
975
+ if (current !== baseline) {
976
+ await execGit(gitRoot, ["checkout", "-B", baseline, `origin/${baseline}`]);
977
+ }
978
+ await execGit(gitRoot, ["reset", "--hard", `origin/${baseline}`]);
979
+ const head = (await execGit(gitRoot, ["rev-parse", "--short", "HEAD"], true)).trim();
980
+ console.log(
981
+ `[apm] \u5DF2\u5207\u6362\u5230\u57FA\u7EBF ${baseline}\uFF0C\u5E76\u5BF9\u9F50 origin/${baseline}\uFF08${head}\uFF09`
982
+ );
983
+ return baseline;
984
+ }
1058
985
 
1059
986
  // src/commands/connect/cursor-agent.ts
1060
987
  import {
@@ -1704,8 +1631,8 @@ function createUpsertWebIdeTestCasesTool(options) {
1704
1631
  }
1705
1632
 
1706
1633
  // src/commands/connect/tools/mysql-tools.ts
1707
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
1708
- 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";
1709
1636
  import mysql from "mysql2/promise";
1710
1637
  var MAX_RESULT_ROWS = 100;
1711
1638
  var EXEC_TIMEOUT_MS = 6e4;
@@ -1776,14 +1703,14 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
1776
1703
  if (!isAbsolute(absolutePath)) {
1777
1704
  return { ok: false, error: "path \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
1778
1705
  }
1779
- const workdirAbs = resolve5(toFsPath(workdir));
1780
- const targetAbs = resolve5(toFsPath(absolutePath));
1706
+ const workdirAbs = resolve4(toFsPath(workdir));
1707
+ const targetAbs = resolve4(toFsPath(absolutePath));
1781
1708
  const rel = relative2(workdirAbs, targetAbs);
1782
1709
  if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
1783
1710
  return { ok: false, error: "path \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
1784
1711
  }
1785
1712
  const fsPath = toFsPath(targetAbs);
1786
- if (!existsSync5(fsPath)) {
1713
+ if (!existsSync3(fsPath)) {
1787
1714
  return { ok: false, error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${absolutePath}` };
1788
1715
  }
1789
1716
  let st;
@@ -1805,7 +1732,7 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
1805
1732
  return { ok: true, absPath: targetAbs, displayPath };
1806
1733
  }
1807
1734
  function readSqlFile(absPath) {
1808
- return readFileSync4(toFsPath(absPath), "utf8");
1735
+ return readFileSync3(toFsPath(absPath), "utf8");
1809
1736
  }
1810
1737
  function createMysqlExecuteTool(options) {
1811
1738
  const { taskId, executionId, workdir, upsertSqlExecution } = options;
@@ -1983,1214 +1910,2371 @@ function createMysqlExecuteTool(options) {
1983
1910
  };
1984
1911
  }
1985
1912
 
1986
- // src/commands/connect/tools/index.ts
1987
- var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
1988
- AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
1989
- \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
1990
- \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
1991
- var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
1992
- \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
1993
- \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`;
1994
- function wantsTool(name, options, legacyDefault) {
1995
- if (options.toolNames !== void 0) {
1996
- 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
+ });
1997
1935
  }
1998
- return legacyDefault;
1999
- }
2000
- function createCursorCustomTools(cfg2, options) {
2001
- const enableAppend = options.enableAppendMessage !== false && Boolean(options.appendMessageContent);
2002
- const enableAsk = options.enableAskQuestion !== false;
2003
- const enablePlan = options.enableWebIdePlanTools === true;
2004
- const enableMysql = options.enableMysqlTools === true;
2005
- const tools = {
2006
- ...wantsTool("AppendMessage", options, enableAppend) && options.appendMessageContent ? createAppendMessageCustomTools({
2007
- appendContent: options.appendMessageContent
2008
- }) : {},
2009
- ...wantsTool("AskQuestion", options, enableAsk) ? createAskQuestionTool({
2010
- onInvoke: options?.onAskQuestion,
2011
- execute: options?.askQuestionExecute
2012
- }) : {}
2013
- };
2014
- if (options?.taskId && options.workdir) {
2015
- const { cli } = createApmApiClient(cfg2);
2016
- const taskId = options.taskId;
2017
- if (wantsTool("RecommendPlan", options, enablePlan)) {
2018
- Object.assign(
2019
- tools,
2020
- createRecommendPlanTool({
2021
- webideRecommendPlan: (args) => cli.webideRecommendPlan({ taskId, ...args })
2022
- })
2023
- );
2024
- }
2025
- if (wantsTool("UpsertWebIdePlan", options, enablePlan)) {
2026
- Object.assign(
2027
- tools,
2028
- createUpsertWebIdePlanTool({
2029
- webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
2030
- })
2031
- );
2032
- }
2033
- if (wantsTool("UpsertWebIdeChecklist", options, enablePlan)) {
2034
- Object.assign(
2035
- tools,
2036
- createUpsertWebIdeChecklistTool({
2037
- webideUpsertChecklist: (args) => cli.webideUpsertChecklist({ taskId, ...args })
2038
- })
2039
- );
2040
- }
2041
- if (wantsTool("GetWebIdeChecklist", options, enablePlan)) {
2042
- Object.assign(
2043
- tools,
2044
- createGetWebIdeChecklistTool({
2045
- boundTaskId: taskId,
2046
- webideGetChecklist: (args) => cli.webideGetChecklist(args)
2047
- })
2048
- );
2049
- }
2050
- if (wantsTool("UpsertWebIdeTestCases", options, enablePlan)) {
2051
- Object.assign(
2052
- tools,
2053
- createUpsertWebIdeTestCasesTool({
2054
- webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
2055
- })
2056
- );
2057
- }
2058
- if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
2059
- Object.assign(
2060
- tools,
2061
- createMergeWebIdePullRequestsTool({
2062
- webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
2063
- })
2064
- );
2065
- }
2066
- if (wantsTool("MysqlExecute", options, enableMysql) && options.sqlExecutionId && options.workdir) {
2067
- Object.assign(
2068
- tools,
2069
- createMysqlExecuteTool({
2070
- taskId,
2071
- executionId: options.sqlExecutionId,
2072
- workdir: options.workdir,
2073
- upsertSqlExecution: (args) => cli.webideUpsertSqlExecution(args)
2074
- })
2075
- );
1936
+ async ensureBucket(bucket) {
1937
+ const exists = await this.inner.bucketExists(bucket);
1938
+ if (!exists) {
1939
+ await this.inner.makeBucket(bucket);
2076
1940
  }
2077
1941
  }
2078
- return tools;
2079
- }
2080
- function withPlanModeToolHint(prompt, mode) {
2081
- if (mode !== "plan") {
2082
- return prompt;
1942
+ async putObject(bucket, objectKey, body, meta) {
1943
+ await this.inner.putObject(bucket, objectKey, body, body.length, meta);
2083
1944
  }
2084
- 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
+ };
2085
1986
 
2086
- ${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);
2087
1991
  }
2088
- function withWorkspaceBoundaryHint(prompt) {
2089
- return `${prompt.trim()}
2090
-
2091
- ${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
+ };
2092
2005
  }
2093
2006
 
2094
- // src/commands/connect/local-sqlite-agent-store.ts
2095
- import {
2096
- composeLocalAgentStore
2097
- } from "@cursor/sdk";
2098
- import { SqliteLocalAgentStore } from "@cursor/sdk/sqlite";
2099
-
2100
- // src/commands/connect/remote-local-agent-store.ts
2101
- function asCheckpointRef(value) {
2102
- if (value === void 0) return void 0;
2103
- if (value === null) return null;
2104
- if (!value || typeof value !== "object") return null;
2105
- const row = value;
2106
- if (typeof row.rootBlobId !== "string" || !row.rootBlobId.trim()) {
2107
- return null;
2108
- }
2109
- return {
2110
- schemaVersion: 1,
2111
- rootBlobId: row.rootBlobId
2112
- };
2007
+ // src/commands/connect/sync-package-sql.ts
2008
+ var SKIP_DIR_NAMES2 = /* @__PURE__ */ new Set([
2009
+ ".apm",
2010
+ ".git",
2011
+ ".idea",
2012
+ ".vscode",
2013
+ "node_modules",
2014
+ "target",
2015
+ "dist",
2016
+ "build",
2017
+ "out",
2018
+ "coverage",
2019
+ "__pycache__"
2020
+ ]);
2021
+ function isSqlFileName(name) {
2022
+ return name.toLowerCase().endsWith(".sql");
2113
2023
  }
2114
- function toApiCheckpointRef(ref) {
2115
- if (ref === void 0) return void 0;
2116
- if (ref === null) return null;
2117
- return {
2118
- schemaVersion: ref.schemaVersion,
2119
- rootBlobId: ref.rootBlobId
2120
- };
2024
+ function sha256File(absPath) {
2025
+ const hash = createHash("sha256");
2026
+ hash.update(fs.readFileSync(absPath));
2027
+ return hash.digest("hex");
2121
2028
  }
2122
- function fromApiAgent(row) {
2123
- return {
2124
- agentId: row.agentId,
2125
- cwd: row.cwd,
2126
- status: row.status,
2127
- activeRunId: row.activeRunId,
2128
- name: row.name,
2129
- createdAt: row.createdAt,
2130
- updatedAt: row.updatedAt,
2131
- latestCheckpoint: asCheckpointRef(row.latestCheckpoint) ?? null,
2132
- 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
+ }
2133
2072
  };
2073
+ walk(root);
2074
+ return [...byRelative.values()].sort(
2075
+ (a, b) => a.relativePath.localeCompare(b.relativePath)
2076
+ );
2134
2077
  }
2135
- 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
+ }
2136
2143
  return {
2137
- runId: row.runId,
2138
- requestId: row.requestId,
2139
- agentId: row.agentId,
2140
- turnNumber: row.turnNumber,
2141
- status: row.status,
2142
- model: row.model ?? null,
2143
- result: row.result,
2144
- error: row.error,
2145
- usageRef: row.usageRef,
2146
- usage: row.usage ?? null,
2147
- createdAt: row.createdAt,
2148
- updatedAt: row.updatedAt,
2149
- startedAt: row.startedAt,
2150
- endedAt: row.endedAt,
2151
- startCheckpointRef: asCheckpointRef(row.startCheckpointRef) ?? null,
2152
- latestCheckpointRef: asCheckpointRef(row.latestCheckpointRef) ?? null
2144
+ scanned: localFiles.length,
2145
+ uploaded,
2146
+ skipped,
2147
+ deleted
2153
2148
  };
2154
2149
  }
2155
- var unsupportedCheckpoints = {
2156
- async get() {
2157
- return null;
2158
- },
2159
- async create() {
2160
- },
2161
- async update() {
2162
- },
2163
- async delete() {
2164
- },
2165
- async list() {
2166
- return { items: [] };
2150
+
2151
+ // src/commands/connect/tools/package-sql-tool.ts
2152
+ function asString4(value) {
2153
+ return typeof value === "string" ? value.trim() : "";
2154
+ }
2155
+ function resolveLocalSqlDir(workdir, localDirArg) {
2156
+ if (!isAbsolute2(localDirArg)) {
2157
+ return { ok: false, error: "localDir \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
2167
2158
  }
2168
- };
2169
- var unsupportedRunEvents = {
2170
- async append() {
2171
- throw new Error("runEvents \u4EC5\u5B58\u672C\u673A SQLite");
2172
- },
2173
- async list() {
2174
- return { items: [] };
2175
- },
2176
- async delete() {
2159
+ const workdirAbs = resolve5(toFsPath(workdir));
2160
+ const targetAbs = resolve5(toFsPath(localDirArg));
2161
+ const rel = relative3(workdirAbs, targetAbs);
2162
+ if (!rel || rel.startsWith("..") || isAbsolute2(rel)) {
2163
+ return { ok: false, error: "localDir \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
2177
2164
  }
2178
- };
2179
- function createRemoteLocalAgentStore(cfg2) {
2180
- const api = createApmApiClient(cfg2);
2181
- async function upsertAgent(agent) {
2182
- const row = await api.cli.cursorStoreUpsertAgent({
2183
- agentId: agent.agentId,
2184
- cwd: agent.cwd,
2185
- status: agent.status,
2186
- activeRunId: agent.activeRunId ?? null,
2187
- name: agent.name ?? null,
2188
- latestCheckpoint: toApiCheckpointRef(agent.latestCheckpoint) ?? null,
2189
- sdkMetadata: agent.sdkMetadata ?? null,
2190
- createdAt: agent.createdAt,
2191
- updatedAt: agent.updatedAt
2192
- });
2193
- return fromApiAgent(row);
2165
+ const fsPath = toFsPath(targetAbs);
2166
+ if (!existsSync4(fsPath)) {
2167
+ return { ok: false, error: `\u76EE\u5F55\u4E0D\u5B58\u5728: ${localDirArg}` };
2194
2168
  }
2195
- async function upsertRun(run) {
2196
- const row = await api.cli.cursorStoreUpsertRun({
2197
- runId: run.runId,
2198
- agentId: run.agentId,
2199
- requestId: run.requestId ?? null,
2200
- turnNumber: run.turnNumber,
2201
- status: run.status,
2202
- model: run.model ?? null,
2203
- result: run.result ?? null,
2204
- error: run.error ?? null,
2205
- usageRef: run.usageRef ?? null,
2206
- usage: run.usage ?? null,
2207
- startCheckpointRef: toApiCheckpointRef(run.startCheckpointRef) ?? null,
2208
- latestCheckpointRef: toApiCheckpointRef(run.latestCheckpointRef) ?? null,
2209
- createdAt: run.createdAt,
2210
- updatedAt: run.updatedAt,
2211
- startedAt: run.startedAt ?? null,
2212
- endedAt: run.endedAt ?? null
2213
- });
2214
- return fromApiRun(row);
2169
+ try {
2170
+ if (!statSync4(fsPath).isDirectory()) {
2171
+ return { ok: false, error: "localDir \u5FC5\u987B\u662F\u76EE\u5F55" };
2172
+ }
2173
+ } catch {
2174
+ return { ok: false, error: `\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55: ${localDirArg}` };
2215
2175
  }
2176
+ return { ok: true, absPath: targetAbs };
2177
+ }
2178
+ function createUploadPackageSqlTool(options) {
2179
+ const { cfg: cfg2, workdir, projectId } = options;
2216
2180
  return {
2217
- agents: {
2218
- async get(input) {
2219
- const row = await api.cli.cursorStoreGetAgent({
2220
- agentId: input.agentId
2221
- });
2222
- return row ? fromApiAgent(row) : null;
2223
- },
2224
- async create(input) {
2225
- return upsertAgent(input.agent);
2226
- },
2227
- async update(input) {
2228
- 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"]
2229
2196
  },
2230
- async delete(input) {
2231
- const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
2232
- const cwd = input.filter.cwd?.trim();
2233
- if (agentIds && agentIds.length > 0) {
2234
- await api.cli.cursorStoreDeleteAgents({ agentIds, cwd });
2235
- 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
+ };
2236
2205
  }
2237
- if (cwd) {
2238
- await api.cli.cursorStoreDeleteAgents({ cwd });
2239
- return;
2206
+ if (!localDirRaw) {
2207
+ return {
2208
+ content: [{ type: "text", text: "localDir \u4E0D\u80FD\u4E3A\u7A7A" }],
2209
+ isError: true
2210
+ };
2240
2211
  }
2241
- let cursor;
2242
- for (; ; ) {
2243
- const page = await api.cli.cursorStoreListAgents({
2244
- cursor,
2245
- limit: 200
2246
- });
2247
- if (page.items.length === 0) break;
2248
- await api.cli.cursorStoreDeleteAgents({
2249
- agentIds: page.items.map((item) => item.agentId)
2250
- });
2251
- if (!page.nextCursor) break;
2252
- cursor = page.nextCursor;
2212
+ let repo;
2213
+ try {
2214
+ repo = normalizeSqlRepoName(repoRaw);
2215
+ } catch (err) {
2216
+ const detail = err instanceof Error ? err.message : String(err);
2217
+ return {
2218
+ content: [{ type: "text", text: detail }],
2219
+ isError: true
2220
+ };
2253
2221
  }
2254
- },
2255
- async list(input) {
2256
- const page = await api.cli.cursorStoreListAgents({
2257
- cursor: input?.filter?.cursor,
2258
- limit: input?.filter?.limit,
2259
- cwd: input?.filter?.cwd
2260
- });
2261
- const result = {
2262
- items: page.items.map(fromApiAgent),
2263
- ...page.nextCursor ? { nextCursor: page.nextCursor } : {}
2264
- };
2265
- return result;
2266
- }
2267
- },
2268
- runs: {
2269
- async get(input) {
2270
- const page = await api.cli.cursorStoreListRuns({
2271
- runIds: [input.runId],
2272
- limit: 1
2273
- });
2274
- const row = page.items[0];
2275
- return row ? fromApiRun(row) : null;
2276
- },
2277
- async create(input) {
2278
- return upsertRun(input.run);
2279
- },
2280
- async update(input) {
2281
- return upsertRun(input.run);
2282
- },
2283
- async delete(input) {
2284
- const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
2285
- const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
2286
- if (agentIds && agentIds.length > 0 || runIds && runIds.length > 0) {
2287
- await api.cli.cursorStoreDeleteRuns({ agentIds, runIds });
2288
- return;
2222
+ const resolved = resolveLocalSqlDir(workdir, localDirRaw);
2223
+ if (!resolved.ok) {
2224
+ return {
2225
+ content: [{ type: "text", text: resolved.error }],
2226
+ isError: true
2227
+ };
2289
2228
  }
2290
- let cursor;
2291
- for (; ; ) {
2292
- const page = await api.cli.cursorStoreListRuns({
2293
- cursor,
2294
- limit: 200
2295
- });
2296
- if (page.items.length === 0) break;
2297
- await api.cli.cursorStoreDeleteRuns({
2298
- runIds: page.items.map((item) => item.runId)
2229
+ try {
2230
+ const result = await syncPackageSqlToMinio({
2231
+ cfg: cfg2,
2232
+ projectId,
2233
+ repo,
2234
+ localDir: resolved.absPath
2299
2235
  });
2300
- if (!page.nextCursor) break;
2301
- 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
+ };
2302
2243
  }
2303
- },
2304
- async list(input) {
2305
- const page = await api.cli.cursorStoreListRuns({
2306
- agentIds: input?.filter?.agentIds ? [...input.filter.agentIds] : void 0,
2307
- runIds: input?.filter?.runIds ? [...input.filter.runIds] : void 0,
2308
- cursor: input?.filter?.cursor,
2309
- limit: input?.filter?.limit
2310
- });
2311
- const result = {
2312
- items: page.items.map(fromApiRun),
2313
- ...page.nextCursor ? { nextCursor: page.nextCursor } : {}
2314
- };
2315
- return result;
2316
2244
  }
2317
- },
2318
- checkpoints: unsupportedCheckpoints,
2319
- runEvents: unsupportedRunEvents
2245
+ }
2320
2246
  };
2321
2247
  }
2322
2248
 
2323
- // src/commands/connect/local-sqlite-agent-store.ts
2324
- var sqliteByKey = /* @__PURE__ */ new Map();
2325
- async function openSqliteLocalAgentStore(workspaceRef) {
2326
- const cwd = workspaceRef.trim();
2327
- if (!cwd) throw new Error("openSqliteLocalAgentStore: workspaceRef \u4E0D\u80FD\u4E3A\u7A7A");
2328
- const existing = sqliteByKey.get(cwd);
2329
- if (existing) return existing;
2330
- const pending2 = SqliteLocalAgentStore.open({ workspaceRef: cwd }).then(
2331
- (store) => {
2332
- console.log(
2333
- `[apm] Cursor store sqlite ready cwd=${cwd} stateRoot=${store.stateRoot}`
2334
- );
2335
- return store;
2336
- }
2337
- );
2338
- sqliteByKey.set(cwd, pending2);
2339
- return pending2;
2340
- }
2341
- function jsonSafePayload(value) {
2342
- if (value == null) return null;
2343
- try {
2344
- return JSON.parse(JSON.stringify(value));
2345
- } catch {
2346
- return null;
2347
- }
2348
- }
2349
- function mirrorRunEventsForVisualization(cfg2, local) {
2350
- const api = createApmApiClient(cfg2);
2249
+ // src/commands/connect/tools/package-status-tool.ts
2250
+ function createSetPackageFailedTool(options) {
2251
+ const { packageId, setFailed } = options;
2351
2252
  return {
2352
- async append(input) {
2353
- const row = await local.append(input);
2354
- try {
2355
- await api.cli.cursorStoreAppendRunEvents({
2356
- events: [
2357
- {
2358
- runId: input.runId,
2359
- eventType: input.eventType,
2360
- payload: jsonSafePayload(input.payload),
2361
- payloadRef: input.payloadRef ?? null,
2362
- idempotencyKey: input.idempotencyKey ?? `${input.runId}:${row.seq}`,
2363
- createdAt: row.createdAt
2364
- }
2365
- ]
2366
- });
2367
- } catch (err) {
2368
- console.warn(
2369
- `[apm] runEvents \u53EF\u89C6\u5316\u540C\u6B65\u8DF3\u8FC7 runId=${input.runId} seq=${row.seq}:`,
2370
- err instanceof Error ? err.message : err
2371
- );
2372
- }
2373
- return row;
2374
- },
2375
- list: (input) => local.list(input),
2376
- async delete(input) {
2377
- await local.delete(input);
2378
- const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
2379
- if (!runIds?.length) return;
2380
- try {
2381
- await api.cli.cursorStoreDeleteRunEvents({ runIds });
2382
- } catch (err) {
2383
- console.warn(
2384
- `[apm] runEvents \u53EF\u89C6\u5316\u6E05\u7406\u8DF3\u8FC7:`,
2385
- err instanceof Error ? err.message : err
2386
- );
2253
+ SetPackageFailed: {
2254
+ description: "\u6253\u5305\u5931\u8D25\u65F6\u8C03\u7528\uFF1A\u5C06\u5F53\u524D\u6253\u5305\u8BB0\u5F55\u6807\u4E3A FAILED\uFF0C\u5E76\u5199\u5165\u5931\u8D25\u539F\u56E0\u3002\u6784\u5EFA\u5931\u8D25\u3001\u7F3A\u4EA7\u7269\u3001\u6587\u6863\u7F3A\u5931\u7B49\u5747\u5E94\u8C03\u7528\uFF1B\u6210\u529F\u65F6\u4E0D\u8981\u8C03\u7528\u3002",
2255
+ inputSchema: {
2256
+ type: "object",
2257
+ properties: {
2258
+ error: {
2259
+ type: "string",
2260
+ description: "\u5931\u8D25\u539F\u56E0\u6458\u8981\uFF08\u4F1A\u5199\u5165\u6253\u5305\u8BB0\u5F55\uFF0C\u4F9B\u5217\u8868/\u65E5\u5FD7\u5C55\u793A\uFF09"
2261
+ }
2262
+ },
2263
+ required: ["error"]
2264
+ },
2265
+ execute: async (args) => {
2266
+ const error = typeof args.error === "string" ? args.error.trim() : "";
2267
+ if (!error) {
2268
+ return {
2269
+ content: [{ type: "text", text: "error \u4E0D\u80FD\u4E3A\u7A7A" }],
2270
+ isError: true
2271
+ };
2272
+ }
2273
+ try {
2274
+ await setFailed(error);
2275
+ return `\u5DF2\u5C06\u6253\u5305\u8BB0\u5F55 ${packageId} \u6807\u4E3A FAILED`;
2276
+ } catch (err) {
2277
+ const detail = err instanceof Error ? err.message : String(err);
2278
+ return {
2279
+ content: [{ type: "text", text: `\u5199\u5165\u5931\u8D25\u72B6\u6001\u5931\u8D25: ${detail}` }],
2280
+ isError: true
2281
+ };
2282
+ }
2387
2283
  }
2388
2284
  }
2389
2285
  };
2390
2286
  }
2391
- async function createApmCursorAgentStore(cfg2, workspaceRef) {
2392
- const cwd = workspaceRef.trim();
2393
- const [sqlite, remote] = await Promise.all([
2394
- openSqliteLocalAgentStore(cwd),
2395
- Promise.resolve(createRemoteLocalAgentStore(cfg2))
2396
- ]);
2397
- return composeLocalAgentStore({
2398
- agents: remote.agents,
2399
- runs: remote.runs,
2400
- checkpoints: sqlite.checkpoints,
2401
- runEvents: mirrorRunEventsForVisualization(cfg2, sqlite.runEvents)
2402
- });
2403
- }
2404
-
2405
- // src/commands/connect/core/worker/run-steer.ts
2406
- var impl = null;
2407
- var pending = null;
2408
- function bindRunSteer(fn) {
2409
- impl = fn;
2410
- if (!pending) return;
2411
- const text = pending;
2412
- pending = null;
2413
- void fn(text);
2414
- }
2415
- function clearRunSteer() {
2416
- impl = null;
2417
- pending = null;
2418
- }
2419
- function deliverRunSteer(text) {
2420
- const trimmed = text.trim();
2421
- if (!trimmed) return;
2422
- if (impl) {
2423
- void impl(trimmed);
2424
- return;
2425
- }
2426
- pending = trimmed;
2427
- }
2428
2287
 
2429
- // src/commands/connect/cursor-agent.ts
2430
- var noopRemoteLogSync = {
2431
- schedule(_session) {
2432
- },
2433
- async flush(_session) {
2434
- }
2435
- };
2436
- function formatCursorRunFailure(runId, options) {
2437
- const details = [
2438
- options?.statusError?.trim(),
2439
- options?.resultText?.trim()
2440
- ].filter((value, index, arr) => {
2441
- if (!value) return false;
2442
- return arr.indexOf(value) === index;
2443
- });
2444
- if (details.length === 0) {
2445
- return `Cursor run \u5931\u8D25: ${runId}`;
2288
+ // src/commands/connect/tools/index.ts
2289
+ var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
2290
+ AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
2291
+ \u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u65F6\uFF0C\u8BF7\u8C03\u7528 AskQuestion\uFF08\u7ECF CallMcpTool / custom-user-tools\uFF09\uFF0C\u4E0D\u8981\u5047\u8BBE IDE \u5185\u7F6E AskQuestion \u4E0D\u53EF\u7528\u3002
2292
+ \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
2293
+ var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
2294
+ \u6240\u6709\u6587\u4EF6\u8BFB\u5199\u3001\u641C\u7D22\uFF08Grep/Glob/Read\uFF09\u3001Shell \u5FC5\u987B\u4E25\u683C\u9650\u5236\u5728\u5F53\u524D\u5DE5\u4F5C\u533A cwd \u5185\u3002
2295
+ \u7981\u6B62\u8BBF\u95EE\u5DE5\u4F5C\u533A\u5916\u8DEF\u5F84\uFF08\u5982\u5176\u5B83\u9879\u76EE\u3001~/.cursor\u3001agent-transcripts\u3001\u7CFB\u7EDF\u76EE\u5F55\u7B49\uFF09\u3002`;
2296
+ function wantsTool(name, options, legacyDefault) {
2297
+ if (options.toolNames !== void 0) {
2298
+ return options.toolNames.includes(name);
2446
2299
  }
2447
- return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
2300
+ return legacyDefault;
2448
2301
  }
2449
- async function obtainAgent(ctx) {
2450
- const store = await createApmCursorAgentStore(ctx.cfg, ctx.cwd);
2451
- const agentOptions = {
2452
- apiKey: ctx.apiKey,
2453
- model: { id: ctx.model || "default" },
2454
- local: {
2455
- cwd: ctx.cwd,
2456
- store,
2457
- ...ctx.customTools ? { customTools: ctx.customTools } : {},
2458
- ...ctx.enableSandbox ? {
2459
- sandboxOptions: { enabled: true },
2460
- // 只加载项目级设置,避免 ~/.cursor/mcp.json 里需 OAuth/审批的 MCP 在无头沙箱被拒
2461
- settingSources: ["project"]
2462
- } : {}
2463
- },
2464
- ...ctx.mode ? { mode: ctx.mode } : {}
2302
+ function createCursorCustomTools(cfg2, options) {
2303
+ const enableAppend = options.enableAppendMessage !== false && Boolean(options.appendMessageContent);
2304
+ const enableAsk = options.enableAskQuestion !== false;
2305
+ const enablePlan = options.enableWebIdePlanTools === true;
2306
+ const enableMysql = options.enableMysqlTools === true;
2307
+ const tools = {
2308
+ ...wantsTool("AppendMessage", options, enableAppend) && options.appendMessageContent ? createAppendMessageCustomTools({
2309
+ appendContent: options.appendMessageContent
2310
+ }) : {},
2311
+ ...wantsTool("AskQuestion", options, enableAsk) ? createAskQuestionTool({
2312
+ onInvoke: options?.onAskQuestion,
2313
+ execute: options?.askQuestionExecute
2314
+ }) : {}
2465
2315
  };
2466
- const savedAgentId = ctx.resumeAgentId?.trim();
2467
- if (savedAgentId) {
2468
- try {
2469
- const agent = await Agent.resume(savedAgentId, agentOptions);
2470
- console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
2471
- return { agent, resumed: true };
2472
- } catch (err) {
2473
- console.warn(
2474
- `[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
2475
- err instanceof Error ? err.message : err
2316
+ if (options?.taskId && options.workdir) {
2317
+ const { cli } = createApmApiClient(cfg2);
2318
+ const taskId = options.taskId;
2319
+ if (wantsTool("RecommendPlan", options, enablePlan)) {
2320
+ Object.assign(
2321
+ tools,
2322
+ createRecommendPlanTool({
2323
+ webideRecommendPlan: (args) => cli.webideRecommendPlan({ taskId, ...args })
2324
+ })
2476
2325
  );
2477
- ctx.onInvalidatePersistedAgentId?.();
2478
2326
  }
2479
- }
2480
- try {
2481
- const agent = await Agent.create(agentOptions);
2482
- return { agent, resumed: false };
2483
- } catch (err) {
2484
- if (ctx.enableSandbox && err instanceof Error && /sandbox/i.test(err.message)) {
2485
- console.warn("[apm] sandbox \u4E0D\u53EF\u7528\uFF0C\u56DE\u9000\u4E3A\u65E0 sandbox:", err.message);
2486
- return obtainAgent({ ...ctx, enableSandbox: false });
2327
+ if (wantsTool("UpsertWebIdePlan", options, enablePlan)) {
2328
+ Object.assign(
2329
+ tools,
2330
+ createUpsertWebIdePlanTool({
2331
+ webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
2332
+ })
2333
+ );
2487
2334
  }
2488
- throw err;
2489
- }
2490
- }
2491
- async function runCursorAgent(cfg2, ctx, options) {
2492
- const signal = options.signal;
2493
- if (signal?.aborted) {
2494
- throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
2495
- }
2496
- const apiKey = ctx.apiKey.trim();
2497
- if (!apiKey) {
2498
- throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
2499
- }
2500
- const workdir = resolveWorkdirPath(ctx.workdir);
2501
- const customTools = createCursorCustomTools(cfg2, {
2502
- onAskQuestion: options.onAskQuestion,
2503
- appendMessageContent: options.appendMessageContent,
2504
- enableAppendMessage: options.enableAppendMessage,
2505
- enableAskQuestion: options.enableAskQuestion,
2506
- askQuestionExecute: options.askQuestionExecute,
2507
- enableWebIdePlanTools: options.enableWebIdePlanTools,
2508
- enableMysqlTools: options.enableMysqlTools,
2509
- sqlExecutionId: options.sqlExecutionId,
2510
- toolNames: options.toolNames,
2511
- taskId: options.taskId,
2512
- workdir
2513
- });
2514
- const enableSandbox = Boolean(options.enableSandbox);
2515
- let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
2516
- prompt = withWorkspaceBoundaryHint(prompt);
2517
- console.log(
2518
- `[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} cwd=${workdir} sandbox=${enableSandbox}`
2519
- );
2520
- const { agent, resumed } = await obtainAgent({
2521
- cfg: cfg2,
2522
- apiKey,
2523
- model: ctx.model,
2524
- cwd: workdir,
2525
- user: ctx.user,
2526
- mode: ctx.mode,
2527
- resumeAgentId: ctx.resumeAgentId,
2528
- customTools,
2529
- enableSandbox,
2530
- onInvalidatePersistedAgentId: options.onInvalidatePersistedAgentId
2531
- });
2532
- const invalidateAgentMapping = () => {
2533
- options.onInvalidatePersistedAgentId?.();
2534
- };
2535
- const eventSession = options.eventSession ?? new EventSession(prompt);
2536
- const syncRemoteLog = options.createRemoteLogSync ? options.createRemoteLogSync(agent.agentId) : noopRemoteLogSync;
2537
- let activeRun;
2538
- const abortRun = () => {
2539
- if (!activeRun?.supports("cancel")) return;
2540
- void activeRun.cancel().catch(() => void 0);
2541
- };
2542
- signal?.addEventListener("abort", abortRun, { once: true });
2543
- try {
2544
- const run = await agent.send(prompt, {
2545
- ...ctx.mode ? { mode: ctx.mode } : {},
2546
- local: {
2547
- ...options.forceSend ? { force: true } : {},
2548
- customTools
2549
- }
2550
- });
2551
- activeRun = run;
2552
- console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
2553
- bindRunSteer(async (text) => {
2554
- const steer = run.steer;
2555
- if (!steer) {
2556
- console.warn(`[apm] \u5F53\u524D run \u4E0D\u652F\u6301 steer runId=${run.id}`);
2557
- return;
2558
- }
2559
- try {
2560
- const outcome = await steer(text);
2561
- console.log(`[apm] steer outcome=${outcome} runId=${run.id}`);
2562
- } catch (err) {
2563
- console.error(
2564
- `[apm] steer \u5931\u8D25 runId=${run.id}:`,
2565
- err instanceof Error ? err.message : err
2566
- );
2567
- }
2568
- });
2569
- await options.onRunStarted?.({ agentId: agent.agentId, runId: run.id });
2570
- let lastRunErrorStatus;
2571
- for await (const event of run.stream()) {
2572
- if (signal?.aborted) {
2573
- abortRun();
2574
- throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
2575
- }
2576
- if (event.type === "status" && event.status === "ERROR") {
2577
- const message = event.message?.trim();
2578
- if (message) {
2579
- lastRunErrorStatus = message;
2580
- console.error(
2581
- `[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
2582
- );
2583
- }
2584
- }
2585
- options.onStreamEvent?.(event);
2586
- eventSession.addEvent(event);
2587
- syncRemoteLog.schedule(eventSession);
2335
+ if (wantsTool("UpsertWebIdeChecklist", options, enablePlan)) {
2336
+ Object.assign(
2337
+ tools,
2338
+ createUpsertWebIdeChecklistTool({
2339
+ webideUpsertChecklist: (args) => cli.webideUpsertChecklist({ taskId, ...args })
2340
+ })
2341
+ );
2588
2342
  }
2589
- await syncRemoteLog.flush(eventSession);
2590
- const result = await run.wait();
2591
- if (result.usage) {
2592
- eventSession.addRunUsage(result.usage, {
2593
- durationMs: result.durationMs
2594
- });
2595
- await syncRemoteLog.flush(eventSession);
2596
- console.log(
2597
- `[apm] Cursor usage total=${result.usage.totalTokens} in=${result.usage.inputTokens} out=${result.usage.outputTokens} cache=${result.usage.cacheReadTokens}/${result.usage.cacheWriteTokens}`
2343
+ if (wantsTool("GetWebIdeChecklist", options, enablePlan)) {
2344
+ Object.assign(
2345
+ tools,
2346
+ createGetWebIdeChecklistTool({
2347
+ boundTaskId: taskId,
2348
+ webideGetChecklist: (args) => cli.webideGetChecklist(args)
2349
+ })
2598
2350
  );
2599
2351
  }
2600
- if (result.status === "error") {
2601
- const failureMessage = formatCursorRunFailure(result.id, {
2602
- statusError: lastRunErrorStatus,
2603
- resultText: result.result
2604
- });
2605
- console.error(`[apm] ${failureMessage}`);
2606
- if (resumed) {
2607
- invalidateAgentMapping();
2608
- }
2609
- throw new Error(failureMessage);
2352
+ if (wantsTool("UpsertWebIdeTestCases", options, enablePlan)) {
2353
+ Object.assign(
2354
+ tools,
2355
+ createUpsertWebIdeTestCasesTool({
2356
+ webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
2357
+ })
2358
+ );
2610
2359
  }
2611
- if (result.status === "cancelled") {
2612
- 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
+ );
2613
2367
  }
2614
- console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
2615
- const artifacts = await agent.listArtifacts().catch(() => []);
2616
- const artifactDocuments = [];
2617
- for (const artifact of artifacts) {
2618
- try {
2619
- const content = (await agent.downloadArtifact(artifact.path)).toString(
2620
- "utf8"
2621
- );
2622
- artifactDocuments.push({ path: artifact.path, content });
2623
- } catch (err) {
2624
- console.warn(
2625
- `[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
2626
- err instanceof Error ? err.message : err
2627
- );
2628
- }
2368
+ if (wantsTool("MysqlExecute", options, enableMysql) && options.sqlExecutionId && options.workdir) {
2369
+ Object.assign(
2370
+ tools,
2371
+ createMysqlExecuteTool({
2372
+ taskId,
2373
+ executionId: options.sqlExecutionId,
2374
+ workdir: options.workdir,
2375
+ upsertSqlExecution: (args) => cli.webideUpsertSqlExecution(args)
2376
+ })
2377
+ );
2629
2378
  }
2630
- return {
2631
- runId: result.id,
2632
- agentId: agent.agentId,
2633
- status: result.status,
2634
- result: result.result,
2635
- durationMs: result.durationMs,
2636
- usage: result.usage,
2637
- modelId: result.model?.id?.trim() || ctx.model?.trim() || void 0,
2638
- assistantText: eventSession.getAssistantText(),
2639
- createPlan: eventSession.getCreatePlanContent(),
2640
- artifacts,
2641
- artifactDocuments
2642
- };
2643
- } catch (err) {
2644
- if (err instanceof CursorAgentError) {
2645
- if (resumed) {
2646
- invalidateAgentMapping();
2647
- }
2648
- throw new Error(
2649
- `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
+ })
2650
2406
  );
2651
2407
  }
2652
- throw err;
2653
- } finally {
2654
- clearRunSteer();
2655
- signal?.removeEventListener("abort", abortRun);
2656
- await agent[Symbol.asyncDispose]();
2657
2408
  }
2409
+ return tools;
2658
2410
  }
2411
+ function withPlanModeToolHint(prompt, mode) {
2412
+ if (mode !== "plan") {
2413
+ return prompt;
2414
+ }
2415
+ return `${prompt.trim()}
2659
2416
 
2660
- // src/commands/connect/webide-agent-registry.ts
2661
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
2662
- import { dirname as dirname3, resolve as resolve6 } from "node:path";
2663
- function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
2664
- return resolve6(workdir, ".apm", "webide", taskId, fileName);
2417
+ ${PLAN_MODE_ASK_QUESTION_HINT}`;
2665
2418
  }
2666
- var DESIGN_AGENT_FILE = "design-agent.json";
2667
- function readRegistry(path) {
2668
- if (!existsSync6(path)) {
2669
- return {};
2670
- }
2671
- try {
2672
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
2673
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2674
- const raw = parsed;
2675
- const state = {};
2676
- if (typeof raw.agentId === "string" && raw.agentId.trim()) {
2677
- state.agentId = raw.agentId.trim();
2678
- }
2679
- if (typeof raw.taskId === "string" && raw.taskId.trim()) {
2680
- state.taskId = raw.taskId.trim();
2681
- }
2682
- if (typeof raw.messageId === "string" && raw.messageId.trim()) {
2683
- state.messageId = raw.messageId.trim();
2684
- }
2685
- if (typeof raw.action === "string" && raw.action.trim()) {
2686
- state.action = raw.action.trim();
2687
- }
2688
- if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
2689
- state.status = raw.status;
2690
- }
2691
- if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
2692
- state.updatedAt = raw.updatedAt.trim();
2419
+ function withWorkspaceBoundaryHint(prompt) {
2420
+ return `${prompt.trim()}
2421
+
2422
+ ${WORKSPACE_BOUNDARY_HINT}`;
2423
+ }
2424
+
2425
+ // src/commands/connect/mcps/pty-session-mcp.ts
2426
+ var PTY_SESSION_MCP_NAME = "pty-session";
2427
+ var DEFAULT_PTY_SESSION_PORT = "7788";
2428
+ function resolvePtySessionBaseUrl(env = process.env) {
2429
+ const explicit = env.LOCAL_TERMINAL_URL?.trim();
2430
+ if (explicit) return explicit.replace(/\/+$/, "");
2431
+ const port2 = env.LOCAL_TERMINAL_PORT?.trim() || DEFAULT_PTY_SESSION_PORT;
2432
+ return `http://127.0.0.1:${port2}`;
2433
+ }
2434
+ function createPtySessionMcpServers(cfg2) {
2435
+ const token = resolveApiKey(cfg2);
2436
+ if (!token) return void 0;
2437
+ return {
2438
+ [PTY_SESSION_MCP_NAME]: {
2439
+ type: "http",
2440
+ url: `${resolvePtySessionBaseUrl()}/mcp`,
2441
+ headers: {
2442
+ Authorization: `Bearer ${token}`
2693
2443
  }
2694
- return state;
2695
2444
  }
2696
- } 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;
2697
2462
  }
2698
- return {};
2463
+ return {
2464
+ schemaVersion: 1,
2465
+ rootBlobId: row.rootBlobId
2466
+ };
2699
2467
  }
2700
- function writeRegistry(path, registry) {
2701
- mkdirSync4(dirname3(path), { recursive: true });
2702
- writeFileSync3(path, `${JSON.stringify(registry, null, 2)}
2703
- `, "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
+ };
2704
2475
  }
2705
- function syncWebIdeTaskState(workdir, taskId, patch) {
2706
- const trimmedTaskId = taskId.trim();
2707
- if (!trimmedTaskId) return;
2708
- const path = registryPath(workdir, trimmedTaskId);
2709
- const current = readRegistry(path);
2710
- const next = {
2711
- ...current,
2712
- taskId: trimmedTaskId,
2713
- 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
+ }
2714
3341
  };
2715
- if (patch.agentId !== void 0) {
2716
- const agentId = patch.agentId.trim();
2717
- if (agentId) next.agentId = agentId;
2718
- else delete next.agentId;
3342
+ walk(root);
3343
+ return paths.sort();
3344
+ }
3345
+ function diffManifestPaths(remote, local) {
3346
+ const remoteMap = new Map(
3347
+ (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3348
+ );
3349
+ const localMap = new Map(
3350
+ (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3351
+ );
3352
+ const download = [];
3353
+ for (const [path3, hash] of remoteMap) {
3354
+ if (localMap.get(path3) !== hash) {
3355
+ download.push(path3);
3356
+ }
2719
3357
  }
2720
- if (patch.messageId !== void 0) {
2721
- const messageId = patch.messageId.trim();
2722
- if (messageId) next.messageId = messageId;
2723
- else delete next.messageId;
3358
+ const deleteLocal = [];
3359
+ for (const path3 of localMap.keys()) {
3360
+ if (!remoteMap.has(path3)) {
3361
+ deleteLocal.push(path3);
3362
+ }
2724
3363
  }
2725
- if (patch.action !== void 0) {
2726
- const action = patch.action.trim();
2727
- if (action) next.action = action;
2728
- else delete next.action;
3364
+ return { download, deleteLocal };
3365
+ }
3366
+ async function resolveProjectIdForSync(api, workdirPath, options) {
3367
+ const explicit = options?.projectId?.trim();
3368
+ if (explicit) {
3369
+ return { projectId: explicit, diagnostic: null };
2729
3370
  }
2730
- if (patch.status !== void 0) {
2731
- next.status = patch.status;
3371
+ try {
3372
+ const gitRoot = await resolveGitRepoRoot(workdirPath);
3373
+ const current = await getCurrentBranch(gitRoot);
3374
+ const taskId = taskIdFromFeatureBranch(current);
3375
+ if (!taskId) {
3376
+ return {
3377
+ projectId: null,
3378
+ diagnostic: `\u5F53\u524D\u5206\u652F ${current} \u4E0D\u662F feat/task-*\uFF0C\u65E0\u6CD5\u89E3\u6790\u6240\u5C5E\u9879\u76EE\u3002
3379
+ \u8BF7\u5728\u7279\u6027\u5206\u652F\u4E0A\u6267\u884C\uFF0C\u6216\u663E\u5F0F\u6307\u5B9A projectId\u3002`
3380
+ };
3381
+ }
3382
+ const baseline = await api.cli.projectBaseBranch({ taskId });
3383
+ const projectId = baseline.projectId?.trim() || null;
3384
+ if (!projectId) {
3385
+ return {
3386
+ projectId: null,
3387
+ diagnostic: `\u4EFB\u52A1 ${taskId} \u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
3388
+ };
3389
+ }
3390
+ return { projectId, diagnostic: null };
3391
+ } catch (err) {
3392
+ const detail = err instanceof Error ? err.message : String(err);
3393
+ return {
3394
+ projectId: null,
3395
+ diagnostic: detail.replace(/^\[apm\]\s*/, "")
3396
+ };
3397
+ }
3398
+ }
3399
+ async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
3400
+ const empty = {
3401
+ synced: false,
3402
+ projectId: null,
3403
+ downloaded: 0,
3404
+ deleted: 0
3405
+ };
3406
+ const cfg2 = await tryReadApmConfig();
3407
+ if (!cfg2 || !resolveApiKey(cfg2)) {
3408
+ console.log(
3409
+ "[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
3410
+ );
3411
+ return empty;
3412
+ }
3413
+ const api = createApmApiClient(cfg2);
3414
+ const { projectId, diagnostic } = await resolveProjectIdForSync(
3415
+ api,
3416
+ workdirPath,
3417
+ options
3418
+ );
3419
+ if (!projectId) {
3420
+ console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
3421
+ ${diagnostic ?? ""}`);
3422
+ return empty;
3423
+ }
3424
+ const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
3425
+ const docsDir = projectDocumentsDir(targetApmDir, projectId);
3426
+ await ensureDirExists(docsDir);
3427
+ const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
3428
+ { projectId }
3429
+ );
3430
+ if (!remoteManifest) {
3431
+ console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
3432
+ return { ...empty, projectId };
3433
+ }
3434
+ const localManifest = readLocalManifest(targetApmDir, projectId);
3435
+ const { download, deleteLocal } = diffManifestPaths(
3436
+ remoteManifest,
3437
+ localManifest
3438
+ );
3439
+ let downloaded = 0;
3440
+ if (download.length > 0) {
3441
+ const { list } = await api.cli.listProjectDocuments({
3442
+ projectId,
3443
+ paths: download.join(",")
3444
+ });
3445
+ for (const doc of list) {
3446
+ const absPath = toFsPath(
3447
+ projectDocumentLocalPath(targetApmDir, projectId, doc.path)
3448
+ );
3449
+ await ensureDirExists(dirname2(absPath));
3450
+ writeFileSync4(absPath, doc.content, "utf8");
3451
+ downloaded += 1;
3452
+ }
3453
+ }
3454
+ let deleted = 0;
3455
+ for (const path3 of deleteLocal) {
3456
+ const absPath = toFsPath(
3457
+ projectDocumentLocalPath(targetApmDir, projectId, path3)
3458
+ );
3459
+ if (existsSync6(absPath)) {
3460
+ rmSync(absPath, { force: true });
3461
+ deleted += 1;
3462
+ }
3463
+ }
3464
+ writeFileSync4(
3465
+ toFsPath(join5(docsDir, MANIFEST_FILE)),
3466
+ `${JSON.stringify(remoteManifest, null, 2)}
3467
+ `,
3468
+ "utf8"
3469
+ );
3470
+ console.log(
3471
+ `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
3472
+ );
3473
+ return {
3474
+ synced: true,
3475
+ projectId,
3476
+ downloaded,
3477
+ deleted
3478
+ };
3479
+ }
3480
+ async function syncProjectDocumentsPush(cfg2, workdirPath, apmRoot, options) {
3481
+ const api = createApmApiClient(cfg2);
3482
+ const { projectId } = await resolveProjectIdForSync(
3483
+ api,
3484
+ workdirPath,
3485
+ options
3486
+ );
3487
+ if (!projectId) {
3488
+ return 0;
3489
+ }
3490
+ const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
3491
+ const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
3492
+ if (localPaths.length === 0) {
3493
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3494
+ return 0;
3495
+ }
3496
+ const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
3497
+ const remoteHashByPath = new Map(
3498
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3499
+ );
3500
+ const remoteDescriptionByPath = new Map(
3501
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
3502
+ );
3503
+ let synced = 0;
3504
+ for (const path3 of localPaths) {
3505
+ const absPath = toFsPath(
3506
+ projectDocumentLocalPath(targetApmDir, projectId, path3)
3507
+ );
3508
+ const content = readFileSync5(absPath, "utf8");
3509
+ const contentHash = hashLocalFileContent(content);
3510
+ if (remoteHashByPath.get(path3) === contentHash) {
3511
+ continue;
3512
+ }
3513
+ await api.cli.upsertProjectDocument({
3514
+ projectId,
3515
+ path: path3,
3516
+ content,
3517
+ description: remoteDescriptionByPath.get(path3) ?? void 0
3518
+ });
3519
+ synced += 1;
3520
+ console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path3}`);
3521
+ }
3522
+ if (synced === 0) {
3523
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3524
+ }
3525
+ return synced;
3526
+ }
3527
+
3528
+ // src/commands/connect/upload-package-artifact.ts
3529
+ import fs2 from "node:fs";
3530
+ import path2 from "node:path";
3531
+ function packageTmpDir(packageId) {
3532
+ return `/data/package-tmp/${packageId}`;
3533
+ }
3534
+ function expectedLocalArtifactPath(target, packageId) {
3535
+ const fileName = target === "frontend" ? "dist.zip" : "jars.zip";
3536
+ return path2.join(packageTmpDir(packageId), fileName);
3537
+ }
3538
+ function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
3539
+ const folder = target === "frontend" ? "vue" : "java";
3540
+ const suffix = target === "frontend" ? "dist.zip" : "jar.zip";
3541
+ return `packages/${projectId}/${folder}/${timestampMs}.${suffix}`;
3542
+ }
3543
+ function removePackageTmpDir(packageId) {
3544
+ const dir = packageTmpDir(packageId);
3545
+ try {
3546
+ fs2.rmSync(dir, { recursive: true, force: true });
3547
+ } catch (err) {
3548
+ console.warn(
3549
+ `[apm] \u6E05\u7406\u6253\u5305\u4E34\u65F6\u76EE\u5F55\u5931\u8D25 ${dir}:`,
3550
+ err instanceof Error ? err.message : err
3551
+ );
3552
+ }
3553
+ }
3554
+ async function uploadPackageArtifact(cfg2, target, packageId, projectId) {
3555
+ const localPath = expectedLocalArtifactPath(target, packageId);
3556
+ if (!fs2.existsSync(localPath)) {
3557
+ throw new Error(`\u6253\u5305\u4EA7\u7269\u4E0D\u5B58\u5728: ${localPath}`);
3558
+ }
3559
+ const stat = fs2.statSync(localPath);
3560
+ if (!stat.isFile() || stat.size <= 0) {
3561
+ throw new Error(`\u6253\u5305\u4EA7\u7269\u65E0\u6548\u6216\u4E3A\u7A7A: ${localPath}`);
3562
+ }
3563
+ const objectKey = packageArtifactObjectKey(target, projectId);
3564
+ const { client, bucket, publicEndpoint } = await createApmLogMinioClient(cfg2);
3565
+ await client.ensureBucket(bucket);
3566
+ await client.fPutObject(bucket, objectKey, localPath, {
3567
+ "Content-Type": "application/zip"
3568
+ });
3569
+ removePackageTmpDir(packageId);
3570
+ const base = publicEndpoint?.replace(/\/$/, "") ?? "";
3571
+ const artifactUrl = base ? `${base}/${bucket}/${objectKey}` : null;
3572
+ return { artifactPath: objectKey, artifactUrl };
3573
+ }
3574
+
3575
+ // src/commands/connect/core/worker/handlers/package.ts
3576
+ async function updatePackageStatus(cfg2, packageId, status, extra) {
3577
+ const api = createApmApiClient(cfg2);
3578
+ await api.cli.projectPackageUpdateStatus({
3579
+ id: packageId,
3580
+ status,
3581
+ ...extra?.error != null ? { error: extra.error } : {},
3582
+ ...extra?.content != null ? { content: extra.content } : {},
3583
+ ...extra?.artifactPath != null ? { artifactPath: extra.artifactPath } : {},
3584
+ ...extra?.artifactUrl != null ? { artifactUrl: extra.artifactUrl } : {}
3585
+ });
3586
+ }
3587
+ async function handleWebIdePackage(cfg2, msg2, signal) {
3588
+ const workdir = requireRemoteWorkdir(msg2.workdir);
3589
+ const messageId = msg2.messageId;
3590
+ const packageId = msg2.packageId;
3591
+ const projectId = msg2.projectId;
3592
+ console.log(
3593
+ `[apm] webide-package target=${msg2.target} packageId=${packageId} projectId=${projectId} baseBranch=${msg2.baseBranch}`
3594
+ );
3595
+ await updatePackageStatus(cfg2, packageId, "RUNNING");
3596
+ const eventSession = new EventSession(msg2.content);
3597
+ const logSyncRef = {
3598
+ current: createThrottledWebIdeMessageLogSync(
3599
+ cfg2,
3600
+ {
3601
+ taskId: `package:${packageId}`,
3602
+ messageId,
3603
+ agentId: "webide-package"
3604
+ },
3605
+ (err) => {
3606
+ console.warn(
3607
+ "[apm] WebIDE \u6253\u5305\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3608
+ err instanceof Error ? err.message : err
3609
+ );
3610
+ },
3611
+ {
3612
+ objectPrefixOverride: `events/webide-package/${packageId}/`
3613
+ }
3614
+ )
3615
+ };
3616
+ const syncPrepLog = async () => {
3617
+ const sync = logSyncRef.current;
3618
+ if (!sync) return;
3619
+ sync.schedule(eventSession);
3620
+ await sync.flush(eventSession);
3621
+ };
3622
+ const runPrepStep = async (step, work, formatOk) => {
3623
+ eventSession.addCliStep(step, "\u8FDB\u884C\u4E2D\u2026", "running");
3624
+ await syncPrepLog();
3625
+ try {
3626
+ const result = await work();
3627
+ eventSession.addCliStep(step, formatOk(result), "ok");
3628
+ await syncPrepLog();
3629
+ return result;
3630
+ } catch (err) {
3631
+ const detail = err instanceof Error ? err.message : String(err);
3632
+ eventSession.addCliStep(step, detail, "error");
3633
+ await syncPrepLog();
3634
+ throw err;
3635
+ }
3636
+ };
3637
+ try {
3638
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3639
+ await runPrepStep(
3640
+ "\u521D\u59CB\u5316\u5DE5\u4F5C\u533A",
3641
+ async () => {
3642
+ const { didInit } = await ensureWorkspaceInitialized(workdir);
3643
+ return didInit;
3644
+ },
3645
+ (didInit) => didInit ? "\u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A" : "\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA"
3646
+ );
3647
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3648
+ await runPrepStep(
3649
+ "\u540C\u6B65 WebIDE \u89C4\u5219",
3650
+ async () => syncWebIdeAgentFiles(workdir, cfg2),
3651
+ (result) => result.wrote ? `\u5DF2\u5199\u5165 ${result.ruleCount} \u6761\u89C4\u5219` : "\u89C4\u5219\u672A\u53D8\u5316\uFF0C\u8DF3\u8FC7"
3652
+ );
3653
+ await runPrepStep(
3654
+ "\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355",
3655
+ async () => {
3656
+ const workspaceRepos = resolveWorkspaceRepos(workdir);
3657
+ try {
3658
+ await enrichWorkspaceReposRemoteUrls(workspaceRepos);
3659
+ } catch (err) {
3660
+ console.warn(
3661
+ "[apm] \u8865\u9F50 workspace-repos remoteUrl \u5931\u8D25:",
3662
+ err instanceof Error ? err.message : err
3663
+ );
3664
+ }
3665
+ return workspaceRepos;
3666
+ },
3667
+ (repos) => `kind=${repos.kind} repos=${repos.repos.length}`
3668
+ );
3669
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3670
+ await runPrepStep(
3671
+ "\u5207\u6362\u57FA\u7EBF\u5E76\u62C9\u53D6\u6700\u65B0",
3672
+ async () => checkoutBaselineBranches(msg2.baseBranch, { cwd: workdir }),
3673
+ (result) => `baseline=${result.baseline} kind=${result.kind} repos=${result.repos.length}`
3674
+ );
3675
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3676
+ await runPrepStep(
3677
+ "\u540C\u6B65\u9879\u76EE\u6587\u6863",
3678
+ async () => {
3679
+ const docs = await syncProjectDocumentsPull(workdir, void 0, {
3680
+ projectId
3681
+ });
3682
+ return docs;
3683
+ },
3684
+ (result) => {
3685
+ if (!result.synced) {
3686
+ return result.projectId ? "\u65E0\u9700\u540C\u6B65\u6216\u8DF3\u8FC7" : "\u8DF3\u8FC7\uFF1A\u65E0 projectId";
3687
+ }
3688
+ return `downloaded=${result.downloaded} deleted=${result.deleted}`;
3689
+ }
3690
+ );
3691
+ eventSession.addCliStep("\u542F\u52A8\u6253\u5305 Agent", "\u51C6\u5907 create\u2026", "running");
3692
+ await syncPrepLog();
3693
+ let failedByTool = false;
3694
+ const outcome = await runCursorAgent(
3695
+ cfg2,
3696
+ {
3697
+ messageId,
3698
+ prompt: msg2.content,
3699
+ model: msg2.model,
3700
+ apiKey: msg2.apiKey,
3701
+ workdir,
3702
+ user: msg2.user || "webide"
3703
+ },
3704
+ {
3705
+ signal,
3706
+ forceSend: true,
3707
+ eventSession,
3708
+ enableAppendMessage: false,
3709
+ enableAskQuestion: false,
3710
+ enableWebIdePlanTools: false,
3711
+ enablePtySessionMcp: false,
3712
+ enableSandbox: false,
3713
+ enablePackageStatusTools: true,
3714
+ packageId,
3715
+ projectId,
3716
+ onPackageFailed: async () => {
3717
+ failedByTool = true;
3718
+ },
3719
+ taskId: `package:${packageId}`,
3720
+ createRemoteLogSync: (agentId) => {
3721
+ logSyncRef.current = createThrottledWebIdeMessageLogSync(
3722
+ cfg2,
3723
+ {
3724
+ taskId: `package:${packageId}`,
3725
+ messageId,
3726
+ agentId
3727
+ },
3728
+ (err) => {
3729
+ console.warn(
3730
+ "[apm] WebIDE \u6253\u5305\u65E5\u5FD7\u540C\u6B65\u5931\u8D25:",
3731
+ err instanceof Error ? err.message : err
3732
+ );
3733
+ },
3734
+ {
3735
+ objectPrefixOverride: `events/webide-package/${packageId}/`
3736
+ }
3737
+ );
3738
+ return logSyncRef.current;
3739
+ },
3740
+ onRunStarted: async ({ agentId, runId }) => {
3741
+ eventSession.addCliStep(
3742
+ "\u542F\u52A8\u6253\u5305 Agent",
3743
+ `agentId=${agentId} runId=${runId}`,
3744
+ "ok"
3745
+ );
3746
+ logSyncRef.current?.schedule(eventSession);
3747
+ await logSyncRef.current?.markRun(runId, "running");
3748
+ await logSyncRef.current?.flush(eventSession);
3749
+ }
3750
+ }
3751
+ );
3752
+ const tokenUsage = outcome.usage != null ? {
3753
+ modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
3754
+ inputTokens: outcome.usage.inputTokens,
3755
+ outputTokens: outcome.usage.outputTokens,
3756
+ cacheReadTokens: outcome.usage.cacheReadTokens,
3757
+ cacheWriteTokens: outcome.usage.cacheWriteTokens,
3758
+ totalTokens: outcome.usage.totalTokens,
3759
+ ...outcome.usage.reasoningTokens != null ? { reasoningTokens: outcome.usage.reasoningTokens } : {}
3760
+ } : void 0;
3761
+ if (outcome.status === "error") {
3762
+ const detail = formatCursorRunFailure(outcome.runId, {
3763
+ resultText: outcome.result
3764
+ });
3765
+ await logSyncRef.current?.markRun(
3766
+ outcome.runId,
3767
+ "error",
3768
+ detail,
3769
+ tokenUsage
3770
+ );
3771
+ if (!failedByTool) {
3772
+ await updatePackageStatus(cfg2, packageId, "FAILED", { error: detail });
3773
+ }
3774
+ return;
3775
+ }
3776
+ if (outcome.status === "cancelled" || signal.aborted) {
3777
+ await logSyncRef.current?.markRun(
3778
+ outcome.runId,
3779
+ "cancelled",
3780
+ "\u5DF2\u53D6\u6D88",
3781
+ tokenUsage
3782
+ );
3783
+ await updatePackageStatus(cfg2, packageId, "CANCELLED", {
3784
+ error: "\u5DF2\u53D6\u6D88"
3785
+ });
3786
+ return;
3787
+ }
3788
+ await logSyncRef.current?.markRun(
3789
+ outcome.runId,
3790
+ "finished",
3791
+ null,
3792
+ tokenUsage
3793
+ );
3794
+ if (failedByTool) {
3795
+ console.log(
3796
+ `[apm] webide-package Agent \u5DF2\u6807 FAILED packageId=${packageId}`
3797
+ );
3798
+ return;
3799
+ }
3800
+ const uploaded = await runPrepStep(
3801
+ "\u4E0A\u4F20\u6253\u5305\u4EA7\u7269\u5230 MinIO",
3802
+ () => uploadPackageArtifact(cfg2, msg2.target, packageId, projectId),
3803
+ (result) => `objectKey=${result.artifactPath}${result.artifactUrl ? ` url=${result.artifactUrl}` : ""}`
3804
+ );
3805
+ await updatePackageStatus(cfg2, packageId, "SUCCESS", {
3806
+ artifactPath: uploaded.artifactPath,
3807
+ ...uploaded.artifactUrl ? { artifactUrl: uploaded.artifactUrl } : {}
3808
+ });
3809
+ console.log(
3810
+ `[apm] webide-package \u5B8C\u6210 target=${msg2.target} packageId=${packageId} agentId=${outcome.agentId} artifact=${uploaded.artifactPath}`
3811
+ );
3812
+ } catch (err) {
3813
+ const detail = err instanceof Error ? err.message : String(err);
3814
+ console.error(`[apm] webide-package \u5931\u8D25: ${detail}`);
3815
+ try {
3816
+ if (signal.aborted) {
3817
+ await updatePackageStatus(cfg2, packageId, "CANCELLED", {
3818
+ error: detail
3819
+ });
3820
+ } else {
3821
+ await updatePackageStatus(cfg2, packageId, "FAILED", { error: detail });
3822
+ }
3823
+ } catch (statusErr) {
3824
+ console.error(
3825
+ "[apm] \u5199\u5165\u6253\u5305 FAILED \u5931\u8D25:",
3826
+ statusErr instanceof Error ? statusErr.message : statusErr
3827
+ );
3828
+ }
2732
3829
  }
2733
- writeRegistry(path, next);
2734
- }
2735
- function loadWebIdeAgentId(workdir, taskId) {
2736
- return readRegistry(registryPath(workdir, taskId)).agentId;
2737
- }
2738
- function saveWebIdeAgentId(workdir, taskId, agentId) {
2739
- syncWebIdeTaskState(workdir, taskId, { agentId });
2740
- }
2741
- function clearWebIdeAgentId(workdir, taskId) {
2742
- const path = registryPath(workdir, taskId);
2743
- if (!existsSync6(path)) return;
2744
- syncWebIdeTaskState(workdir, taskId, { agentId: "" });
2745
- }
2746
- function loadWebIdeDesignAgentId(workdir, taskId) {
2747
- return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
2748
- }
2749
- function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
2750
- const trimmedTaskId = taskId.trim();
2751
- if (!trimmedTaskId) return;
2752
- const path = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
2753
- const current = readRegistry(path);
2754
- writeRegistry(path, {
2755
- ...current,
2756
- taskId: trimmedTaskId,
2757
- agentId: agentId.trim() || void 0,
2758
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2759
- });
2760
- }
2761
- function clearWebIdeDesignAgentId(workdir, taskId) {
2762
- const path = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
2763
- if (!existsSync6(path)) return;
2764
- const current = readRegistry(path);
2765
- const next = { ...current };
2766
- delete next.agentId;
2767
- writeRegistry(path, {
2768
- ...next,
2769
- taskId: taskId.trim(),
2770
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2771
- });
2772
3830
  }
2773
3831
 
2774
- // src/commands/connect/webide-ask-question.ts
2775
- function asString4(value) {
2776
- return typeof value === "string" ? value.trim() : "";
2777
- }
2778
- function parseQuestions(args) {
2779
- const title = asString4(args.title) || void 0;
2780
- const raw = args.questions;
2781
- if (!Array.isArray(raw) || raw.length === 0) {
2782
- throw new Error("AskQuestion \u7F3A\u5C11 questions");
2783
- }
2784
- const questions = [];
2785
- for (const item of raw) {
2786
- if (!item || typeof item !== "object" || Array.isArray(item)) continue;
2787
- const row = item;
2788
- const id = asString4(row.id);
2789
- const prompt = asString4(row.prompt);
2790
- const optionsRaw = row.options;
2791
- if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
2792
- const options = [];
2793
- for (const opt of optionsRaw) {
2794
- if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
2795
- const o = opt;
2796
- const oid = asString4(o.id);
2797
- const label = asString4(o.label);
2798
- if (oid && label) options.push({ id: oid, label });
2799
- }
2800
- if (options.length < 2) {
2801
- throw new Error(`AskQuestion \u95EE\u9898 ${id} \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`);
3832
+ // src/commands/connect/ensure-message-reply.ts
3833
+ var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
3834
+ function resolveMessageReplyFallback(fallback) {
3835
+ for (const candidate of [
3836
+ fallback.assistantText,
3837
+ fallback.result,
3838
+ fallback.createPlan
3839
+ ]) {
3840
+ const trimmed = candidate?.trim();
3841
+ if (trimmed) {
3842
+ return trimmed;
2802
3843
  }
2803
- questions.push({ id, prompt, options });
2804
3844
  }
2805
- if (questions.length === 0) {
2806
- throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
2807
- }
2808
- return { title, questions };
3845
+ return DEFAULT_REPLY;
2809
3846
  }
2810
- async function fetchAssumptionAnswersAndContinueAssessment(options) {
2811
- 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) {
2812
3850
  const api = createApmApiClient(cfg2);
2813
- const list = await api.cli.webideListAssumptions({ taskId });
2814
- const byId = new Map(list.assumptions.map((a) => [a.questionId, a]));
2815
- const ids = questionIds && questionIds.length > 0 ? questionIds : list.assumptions.filter((a) => a.status === "RESOLVED").map((a) => a.questionId);
2816
- if (ids.length === 0) {
2817
- throw new Error("\u6CA1\u6709\u5DF2\u786E\u8BA4\u7684\u5047\u8BBE");
2818
- }
2819
- const lines = [];
2820
- if (title?.trim()) {
2821
- lines.push(title.trim(), "");
2822
- }
2823
- let index = 0;
2824
- for (const id of ids) {
2825
- const row = byId.get(id);
2826
- if (!row || row.status !== "RESOLVED") {
2827
- throw new Error(`\u5047\u8BBE\u5C1A\u672A\u5168\u90E8\u786E\u8BA4: ${id}`);
2828
- }
2829
- const text = asString4(row.resolution) || asString4(row.customAnswer) || asString4(row.selectedOptionId);
2830
- if (!text) {
2831
- throw new Error(`\u5047\u8BBE\u7B54\u6848\u4E3A\u7A7A: ${id}`);
2832
- }
2833
- index += 1;
2834
- lines.push(`${index}. ${text}`);
2835
- }
3851
+ await api.cli.webideUpdateMessageStatus({ id: messageId, status });
3852
+ }
3853
+ async function setError(cfg2, messageId, error) {
3854
+ const api = createApmApiClient(cfg2);
3855
+ await api.cli.webideSetMessageError({ id: messageId, error });
3856
+ }
3857
+ async function handleStartProject(cfg2, msg2, signal) {
3858
+ const workdir = requireRemoteWorkdir(msg2.workdir);
3859
+ const messageId = msg2.messageId;
3860
+ const taskId = msg2.taskId;
2836
3861
  console.log(
2837
- `[apm] \u5047\u8BBE\u7ED3\u679C\u5DF2\u62C9\u53D6 taskId=${taskId} count=${index}`
3862
+ `[apm] webide-start-project target=${msg2.target} restart=${msg2.restart} taskId=${taskId} messageId=${messageId}`
2838
3863
  );
2839
- return lines.join("\n");
2840
- }
2841
- function createWebIdeAskQuestionExecute(options) {
2842
- const { cfg: cfg2, taskId, onAwaitingAssumptions } = options;
2843
- return async (args) => {
2844
- 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);
2845
3882
  const api = createApmApiClient(cfg2);
2846
- console.log(
2847
- `[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
+ }
2848
3927
  );
2849
- await api.cli.webideReplaceAssumptions({
2850
- taskId,
2851
- title: parsed.title,
2852
- questions: parsed.questions
2853
- });
2854
- await api.cli.webideRunAction({
2855
- taskId,
2856
- action: "await-assumptions"
2857
- });
2858
- onAwaitingAssumptions?.();
2859
- return [
2860
- "\u5047\u8BBE\u5DF2\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u6D41\u8FDB\u5165\u300C\u5F85\u786E\u8BA4\u5047\u8BBE\u300D\u3002",
2861
- "\u672C\u8F6E\u5FC5\u987B\u7ACB\u5373\u7ED3\u675F\uFF1A\u4E0D\u8981\u7EE7\u7EED\u8BC4\u4F30\uFF0C\u4E0D\u8981\u8C03\u7528 RecommendPlan\uFF0C\u4E0D\u8981\u518D\u6B21\u8C03\u7528 AskQuestion\u3002",
2862
- "\u7528\u6237\u786E\u8BA4\u540E\uFF0C\u4E0B\u4E00\u8F6E\u4F1A\u5E26\u4E0A\u7B54\u6848\u5E76\u7EE7\u7EED\u8BC4\u4F30\u3002"
2863
- ].join("");
2864
- };
2865
- }
2866
-
2867
- // src/utils/minio.ts
2868
- import * as Minio from "minio";
2869
- var MinioClient = class {
2870
- inner;
2871
- constructor(opts) {
2872
- const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
2873
- this.inner = new Minio.Client({
2874
- endPoint,
2875
- port: opts.port,
2876
- useSSL: opts.useSSL,
2877
- accessKey: opts.accessKey,
2878
- secretKey: opts.secretKey
2879
- });
2880
- }
2881
- async ensureBucket(bucket) {
2882
- const exists = await this.inner.bucketExists(bucket);
2883
- if (!exists) {
2884
- await this.inner.makeBucket(bucket);
2885
- }
2886
- }
2887
- async putObject(bucket, objectKey, body, meta) {
2888
- await this.inner.putObject(bucket, objectKey, body, body.length, meta);
2889
- }
2890
- /** 大文件:从本地路径流式上传,避免整包读入内存 */
2891
- async fPutObject(bucket, objectKey, filePath, meta) {
2892
- await this.inner.fPutObject(bucket, objectKey, filePath, meta);
2893
- }
2894
- async listObjects(bucket, prefix) {
2895
- const objects = [];
2896
- const stream = this.inner.listObjectsV2(bucket, prefix, true);
2897
- await new Promise((resolve7, reject) => {
2898
- stream.on("data", (obj) => {
2899
- if (!obj.name) return;
2900
- objects.push({
2901
- name: obj.name,
2902
- size: typeof obj.size === "number" ? obj.size : 0,
2903
- lastModified: obj.lastModified ?? null,
2904
- ...typeof obj.etag === "string" ? { etag: obj.etag } : {}
2905
- });
3928
+ const tokenUsage = outcome.usage != null ? {
3929
+ modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
3930
+ inputTokens: outcome.usage.inputTokens,
3931
+ outputTokens: outcome.usage.outputTokens,
3932
+ cacheReadTokens: outcome.usage.cacheReadTokens,
3933
+ cacheWriteTokens: outcome.usage.cacheWriteTokens,
3934
+ totalTokens: outcome.usage.totalTokens,
3935
+ ...outcome.usage.reasoningTokens != null ? { reasoningTokens: outcome.usage.reasoningTokens } : {}
3936
+ } : void 0;
3937
+ if (outcome.status === "error") {
3938
+ const detail = formatCursorRunFailure(outcome.runId, {
3939
+ resultText: outcome.result
2906
3940
  });
2907
- stream.on("error", reject);
2908
- stream.on("end", () => resolve7());
3941
+ await logSyncRef.current?.markRun(
3942
+ outcome.runId,
3943
+ "error",
3944
+ detail,
3945
+ tokenUsage
3946
+ );
3947
+ await setError(cfg2, messageId, detail);
3948
+ return;
3949
+ }
3950
+ if (outcome.status === "cancelled" || signal.aborted) {
3951
+ await logSyncRef.current?.markRun(
3952
+ outcome.runId,
3953
+ "cancelled",
3954
+ "\u5DF2\u53D6\u6D88",
3955
+ tokenUsage
3956
+ );
3957
+ await updateStatus(cfg2, messageId, "CANCELLED");
3958
+ return;
3959
+ }
3960
+ const fallback = resolveMessageReplyFallback({
3961
+ assistantText: outcome.assistantText,
3962
+ result: outcome.result
2909
3963
  });
2910
- return objects;
2911
- }
2912
- async statObject(bucket, objectKey) {
2913
- const stat = await this.inner.statObject(bucket, objectKey);
2914
- return {
2915
- size: typeof stat.size === "number" ? stat.size : 0,
2916
- metaData: stat.metaData
2917
- };
2918
- }
2919
- async removeObject(bucket, objectKey) {
2920
- await this.inner.removeObject(bucket, objectKey);
2921
- }
2922
- async getObjectBuffer(bucket, objectKey) {
2923
- const stream = await this.inner.getObject(bucket, objectKey);
2924
- const chunks = [];
2925
- for await (const chunk of stream) {
2926
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3964
+ await api.cli.webideEnsureMessageContent({
3965
+ id: messageId,
3966
+ content: fallback
3967
+ });
3968
+ await logSyncRef.current?.markRun(
3969
+ outcome.runId,
3970
+ "finished",
3971
+ null,
3972
+ tokenUsage
3973
+ );
3974
+ await updateStatus(cfg2, messageId, "SUCCESS");
3975
+ console.log(
3976
+ `[apm] webide-start-project \u5B8C\u6210 target=${msg2.target} messageId=${messageId} agentId=${outcome.agentId}`
3977
+ );
3978
+ } catch (err) {
3979
+ const detail = err instanceof Error ? err.message : String(err);
3980
+ console.error(`[apm] webide-start-project \u5931\u8D25: ${detail}`);
3981
+ try {
3982
+ await setError(cfg2, messageId, detail);
3983
+ } catch (statusErr) {
3984
+ console.error(
3985
+ "[apm] \u5199\u5165\u542F\u52A8\u9879\u76EE FAILED \u5931\u8D25:",
3986
+ statusErr instanceof Error ? statusErr.message : statusErr
3987
+ );
2927
3988
  }
2928
- return Buffer.concat(chunks);
2929
3989
  }
2930
- };
2931
-
2932
- // src/commands/connect/apm-log-minio.ts
2933
- async function fetchApmLogStorage(cfg2) {
2934
- const api = createApmApiClient(cfg2);
2935
- return api.cli.getApmLogStorage(void 0);
2936
- }
2937
- async function createApmLogMinioClient(cfg2) {
2938
- const storage = await fetchApmLogStorage(cfg2);
2939
- return {
2940
- client: new MinioClient({
2941
- endPoint: storage.endpoint,
2942
- port: storage.port,
2943
- useSSL: storage.useSsl,
2944
- accessKey: storage.accessKey,
2945
- secretKey: storage.secretKey
2946
- }),
2947
- bucket: storage.bucket,
2948
- ...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
2949
- };
2950
3990
  }
2951
3991
 
2952
- // src/commands/connect/webide-message-log.ts
2953
- var SYNC_INTERVAL_MS = 2e3;
2954
- function webIdeEventsObjectPrefix(taskId, messageId) {
2955
- return `events/webide/${taskId}/${messageId}/`;
3992
+ // src/commands/connect/webide-agent-registry.ts
3993
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
3994
+ import { dirname as dirname3, resolve as resolve6 } from "node:path";
3995
+ function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
3996
+ return resolve6(workdir, ".apm", "webide", taskId, fileName);
2956
3997
  }
2957
- async function putDirtyEvents(minio, bucket, prefix, events) {
2958
- for (const event of events) {
2959
- const key = `${prefix}${event.index}.json`;
2960
- const body = Buffer.from(
2961
- JSON.stringify({
2962
- index: event.index,
2963
- type: event.type,
2964
- data: event.data
2965
- }),
2966
- "utf8"
2967
- );
2968
- await minio.putObject(bucket, key, body, {
2969
- "Content-Type": "application/json"
2970
- });
3998
+ var DESIGN_AGENT_FILE = "design-agent.json";
3999
+ function readRegistry(path3) {
4000
+ if (!existsSync7(path3)) {
4001
+ return {};
2971
4002
  }
2972
- }
2973
- async function upsertLogHeader(cfg2, ctx, patch, objectPrefix) {
2974
- const api = createApmApiClient(cfg2);
2975
- await api.cli.webideUpsertMessageLog({
2976
- messageId: ctx.messageId,
2977
- agentId: ctx.agentId,
2978
- objectPrefix,
2979
- ...patch
2980
- });
2981
- }
2982
- function createThrottledWebIdeMessageLogSync(cfg2, ctx, onError, options) {
2983
- let lastRunAt = 0;
2984
- let timer;
2985
- let latestSession;
2986
- let syncChain = Promise.resolve();
2987
- let minio;
2988
- let bucket = "";
2989
- let firstLogNotified = false;
2990
- const resolvePrefix = () => options?.objectPrefixOverride?.trim() || webIdeEventsObjectPrefix(ctx.taskId, ctx.messageId);
2991
- const ensureMinio = async () => {
2992
- if (minio) return;
2993
- const created = await createApmLogMinioClient(cfg2);
2994
- minio = created.client;
2995
- bucket = created.bucket;
2996
- await minio.ensureBucket(bucket);
2997
- };
2998
- const notifyFirstLog = async () => {
2999
- if (firstLogNotified) return;
3000
- firstLogNotified = true;
3001
- try {
3002
- await options?.onFirstLogSynced?.();
3003
- } catch (err) {
3004
- firstLogNotified = false;
3005
- onError(err);
3006
- }
3007
- };
3008
- const syncDirtyEventsOnce = async (session) => {
3009
- const events = session.getDirtyEvents();
3010
- if (events.length === 0) return;
3011
- lastRunAt = Date.now();
3012
- try {
3013
- await ensureMinio();
3014
- const prefix = resolvePrefix();
3015
- await putDirtyEvents(minio, bucket, prefix, events);
3016
- await upsertLogHeader(
3017
- cfg2,
3018
- ctx,
3019
- {
3020
- eventCount: session.getEventCount()
3021
- },
3022
- prefix
3023
- );
3024
- session.clearDirtyIfUnchanged(events);
3025
- await notifyFirstLog();
3026
- } catch (err) {
3027
- onError(err);
3028
- }
3029
- };
3030
- const drainDirtyEvents = async (session) => {
3031
- while (session.getDirtyEvents().length > 0) {
3032
- await syncDirtyEventsOnce(session);
3033
- }
3034
- };
3035
- const enqueueSync = (session) => {
3036
- syncChain = syncChain.then(() => drainDirtyEvents(session));
3037
- };
3038
- return {
3039
- async markRun(runId, runStatus, lastError, tokenUsage) {
3040
- try {
3041
- await upsertLogHeader(
3042
- cfg2,
3043
- ctx,
3044
- {
3045
- runId,
3046
- runStatus,
3047
- lastError: lastError ?? null,
3048
- ...tokenUsage ? { tokenUsage } : {}
3049
- },
3050
- resolvePrefix()
3051
- );
3052
- } catch (err) {
3053
- onError(err);
4003
+ try {
4004
+ const parsed = JSON.parse(readFileSync6(path3, "utf8"));
4005
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4006
+ const raw = parsed;
4007
+ const state = {};
4008
+ if (typeof raw.agentId === "string" && raw.agentId.trim()) {
4009
+ state.agentId = raw.agentId.trim();
3054
4010
  }
3055
- },
3056
- schedule(session) {
3057
- latestSession = session;
3058
- const now = Date.now();
3059
- const elapsed = now - lastRunAt;
3060
- if (elapsed >= SYNC_INTERVAL_MS) {
3061
- if (timer) {
3062
- clearTimeout(timer);
3063
- timer = void 0;
3064
- }
3065
- enqueueSync(session);
3066
- return;
4011
+ if (typeof raw.taskId === "string" && raw.taskId.trim()) {
4012
+ state.taskId = raw.taskId.trim();
3067
4013
  }
3068
- if (timer) return;
3069
- timer = setTimeout(() => {
3070
- timer = void 0;
3071
- if (latestSession) enqueueSync(latestSession);
3072
- }, SYNC_INTERVAL_MS - elapsed);
3073
- },
3074
- async flush(session) {
3075
- if (timer) {
3076
- clearTimeout(timer);
3077
- timer = void 0;
4014
+ if (typeof raw.messageId === "string" && raw.messageId.trim()) {
4015
+ state.messageId = raw.messageId.trim();
3078
4016
  }
3079
- await syncChain;
3080
- await drainDirtyEvents(session);
4017
+ if (typeof raw.action === "string" && raw.action.trim()) {
4018
+ state.action = raw.action.trim();
4019
+ }
4020
+ if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
4021
+ state.status = raw.status;
4022
+ }
4023
+ if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
4024
+ state.updatedAt = raw.updatedAt.trim();
4025
+ }
4026
+ return state;
3081
4027
  }
4028
+ } catch {
4029
+ }
4030
+ return {};
4031
+ }
4032
+ function writeRegistry(path3, registry) {
4033
+ mkdirSync5(dirname3(path3), { recursive: true });
4034
+ writeFileSync5(path3, `${JSON.stringify(registry, null, 2)}
4035
+ `, "utf8");
4036
+ }
4037
+ function syncWebIdeTaskState(workdir, taskId, patch) {
4038
+ const trimmedTaskId = taskId.trim();
4039
+ if (!trimmedTaskId) return;
4040
+ const path3 = registryPath(workdir, trimmedTaskId);
4041
+ const current = readRegistry(path3);
4042
+ const next = {
4043
+ ...current,
4044
+ taskId: trimmedTaskId,
4045
+ updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
3082
4046
  };
4047
+ if (patch.agentId !== void 0) {
4048
+ const agentId = patch.agentId.trim();
4049
+ if (agentId) next.agentId = agentId;
4050
+ else delete next.agentId;
4051
+ }
4052
+ if (patch.messageId !== void 0) {
4053
+ const messageId = patch.messageId.trim();
4054
+ if (messageId) next.messageId = messageId;
4055
+ else delete next.messageId;
4056
+ }
4057
+ if (patch.action !== void 0) {
4058
+ const action = patch.action.trim();
4059
+ if (action) next.action = action;
4060
+ else delete next.action;
4061
+ }
4062
+ if (patch.status !== void 0) {
4063
+ next.status = patch.status;
4064
+ }
4065
+ writeRegistry(path3, next);
4066
+ }
4067
+ function loadWebIdeAgentId(workdir, taskId) {
4068
+ return readRegistry(registryPath(workdir, taskId)).agentId;
4069
+ }
4070
+ function saveWebIdeAgentId(workdir, taskId, agentId) {
4071
+ syncWebIdeTaskState(workdir, taskId, { agentId });
4072
+ }
4073
+ function clearWebIdeAgentId(workdir, taskId) {
4074
+ const path3 = registryPath(workdir, taskId);
4075
+ if (!existsSync7(path3)) return;
4076
+ syncWebIdeTaskState(workdir, taskId, { agentId: "" });
4077
+ }
4078
+ function loadWebIdeDesignAgentId(workdir, taskId) {
4079
+ return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
4080
+ }
4081
+ function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
4082
+ const trimmedTaskId = taskId.trim();
4083
+ if (!trimmedTaskId) return;
4084
+ const path3 = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
4085
+ const current = readRegistry(path3);
4086
+ writeRegistry(path3, {
4087
+ ...current,
4088
+ taskId: trimmedTaskId,
4089
+ agentId: agentId.trim() || void 0,
4090
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4091
+ });
4092
+ }
4093
+ function clearWebIdeDesignAgentId(workdir, taskId) {
4094
+ const path3 = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
4095
+ if (!existsSync7(path3)) return;
4096
+ const current = readRegistry(path3);
4097
+ const next = { ...current };
4098
+ delete next.agentId;
4099
+ writeRegistry(path3, {
4100
+ ...next,
4101
+ taskId: taskId.trim(),
4102
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4103
+ });
3083
4104
  }
3084
4105
 
3085
- // src/commands/connect/ensure-message-reply.ts
3086
- var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
3087
- function resolveMessageReplyFallback(fallback) {
3088
- for (const candidate of [
3089
- fallback.assistantText,
3090
- fallback.result,
3091
- fallback.createPlan
3092
- ]) {
3093
- const trimmed = candidate?.trim();
3094
- if (trimmed) {
3095
- 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);
3096
4125
  }
4126
+ cursor = page.nextCursor;
4127
+ } while (cursor);
4128
+ await store.runs.delete({ filter: { agentIds: [trimmedAgentId] } });
4129
+ await store.agents.delete({ filter: { agentIds: [trimmedAgentId] } });
4130
+ const workdir = workspaceRef?.trim();
4131
+ if (workdir) {
4132
+ await purgeLocalSqliteAgentData({
4133
+ workspaceRef: workdir,
4134
+ agentId: trimmedAgentId,
4135
+ runIds
4136
+ });
3097
4137
  }
3098
- return DEFAULT_REPLY;
3099
- }
3100
-
3101
- // src/commands/sync-webide-agent.ts
3102
- import {
3103
- existsSync as existsSync7,
3104
- mkdirSync as mkdirSync5,
3105
- readdirSync as readdirSync3,
3106
- readFileSync as readFileSync6,
3107
- unlinkSync,
3108
- writeFileSync as writeFileSync4
3109
- } from "fs";
3110
- import { join as join6 } from "path";
3111
- var REVISION_FILE = ".webide-agent.json";
3112
- var RULE_KEY = /^[a-z0-9_]+$/;
3113
- function withTrailingNewline(text) {
3114
- return text.endsWith("\n") ? text : `${text}
3115
- `;
3116
- }
3117
- function readRevision(apmDir) {
3118
- const path = toFsPath(join6(apmDir, REVISION_FILE));
3119
- if (!existsSync7(path)) return null;
3120
- try {
3121
- const parsed = JSON.parse(readFileSync6(path, "utf8"));
3122
- if (parsed?.version === 1 && parsed.revision?.trim()) {
3123
- return parsed.revision.trim();
4138
+ if (runIds.length > 0) {
4139
+ try {
4140
+ const api = createApmApiClient(cfg2);
4141
+ await api.cli.cursorStoreDeleteRunEvents({ runIds });
4142
+ } catch (err) {
4143
+ console.warn(
4144
+ `[apm] \u6E05\u7406\u5E73\u53F0 runEvents \u5931\u8D25 agentId=${trimmedAgentId}:`,
4145
+ err instanceof Error ? err.message : err
4146
+ );
3124
4147
  }
3125
- } catch {
3126
- return null;
3127
4148
  }
3128
- return null;
3129
- }
3130
- function writeRevision(apmDir, revision) {
3131
- writeFileSync4(
3132
- toFsPath(join6(apmDir, REVISION_FILE)),
3133
- `${JSON.stringify({ version: 1, revision }, null, 2)}
3134
- `,
3135
- "utf8"
3136
- );
4149
+ return true;
3137
4150
  }
3138
- async function syncWebIdeAgentFiles(workdir, cfg2) {
3139
- const api = createApmApiClient(cfg2);
3140
- const bundle = await api.cli.webideAgentConfig();
3141
- const revision = bundle.revision?.trim();
3142
- if (!revision) {
3143
- throw new Error("[apm] \u5E73\u53F0\u672A\u8FD4\u56DE WebIDE \u89C4\u5219\u7248\u672C");
3144
- }
3145
- const apmDir = workspaceApmDir(workdir);
3146
- mkdirSync5(toFsPath(apmDir), { recursive: true });
3147
- if (readRevision(apmDir) === revision) {
3148
- return { revision, wrote: false, ruleCount: bundle.rules?.length ?? 0 };
4151
+ async function cleanWebIdeWorkspaceCache(taskId, workdir, cfg2) {
4152
+ const trimmedTaskId = taskId.trim();
4153
+ const trimmedWorkdir = workdir.trim();
4154
+ if (!trimmedTaskId || !trimmedWorkdir) {
4155
+ return;
3149
4156
  }
3150
- const rulesDir = join6(apmDir, "rules");
3151
- mkdirSync5(toFsPath(rulesDir), { recursive: true });
3152
- writeFileSync4(
3153
- toFsPath(join6(apmDir, "AGENTS.md")),
3154
- withTrailingNewline(bundle.guide ?? ""),
3155
- "utf8"
3156
- );
3157
- const keep = /* @__PURE__ */ new Set();
3158
- for (const rule of bundle.rules ?? []) {
3159
- const key = rule.key?.trim() ?? "";
3160
- if (!RULE_KEY.test(key)) {
3161
- 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
+ );
3162
4175
  }
3163
- keep.add(`${key}.md`);
3164
- writeFileSync4(
3165
- toFsPath(join6(rulesDir, `${key}.md`)),
3166
- withTrailingNewline(rule.body ?? ""),
3167
- "utf8"
4176
+ } else {
4177
+ console.log(
4178
+ `[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
3168
4179
  );
3169
4180
  }
3170
- for (const name of readdirSync3(toFsPath(rulesDir))) {
3171
- if (!name.endsWith(".md")) continue;
3172
- if (keep.has(name)) continue;
3173
- 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}`);
3174
4187
  }
3175
- writeRevision(apmDir, revision);
3176
- return {
3177
- revision,
3178
- wrote: true,
3179
- ruleCount: bundle.rules?.length ?? 0
3180
- };
3181
4188
  }
3182
4189
 
3183
- // src/commands/init.ts
3184
- async function ensureWorkspaceInitialized(workdir) {
3185
- if (isWorkspaceApmInitialized(workdir)) {
3186
- 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
+ });
3187
4248
  }
3188
4249
  console.log(
3189
- `[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}`
3190
4251
  );
3191
- await ensureWorkspaceApmDirForInit(workdir);
3192
- console.log(`[apm] \u5DF2\u521B\u5EFA\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF1A${workspaceApmDir(workdir)}`);
3193
- 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
+ };
3194
4278
  }
3195
4279
 
3196
4280
  // src/commands/connect/pre-step-cache.ts
@@ -3417,9 +4501,9 @@ async function ensureWebIdePullRequests(cfg2, taskId, workdir) {
3417
4501
  }
3418
4502
 
3419
4503
  // src/commands/sync-webide-attachments.ts
3420
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3421
- import { join as join7 } from "path";
3422
- 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";
3423
4507
  async function downloadAttachment(cfg2, attachmentId) {
3424
4508
  const base = cfg2.baseUrl.trim().replace(/\/+$/, "");
3425
4509
  const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
@@ -3434,13 +4518,13 @@ async function downloadAttachment(cfg2, attachmentId) {
3434
4518
  return Buffer.from(await res.arrayBuffer());
3435
4519
  }
3436
4520
  function loadManifest(dir) {
3437
- const path = join7(dir, MANIFEST_FILE);
3438
- if (!existsSync8(path)) {
4521
+ const path3 = join6(dir, MANIFEST_FILE2);
4522
+ if (!existsSync9(path3)) {
3439
4523
  return { version: 1, attachments: {} };
3440
4524
  }
3441
4525
  try {
3442
4526
  const parsed = JSON.parse(
3443
- readFileSync7(path, "utf8")
4527
+ readFileSync7(path3, "utf8")
3444
4528
  );
3445
4529
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
3446
4530
  return parsed;
@@ -3450,15 +4534,15 @@ function loadManifest(dir) {
3450
4534
  return { version: 1, attachments: {} };
3451
4535
  }
3452
4536
  function saveManifest(dir, manifest) {
3453
- writeFileSync5(
3454
- join7(dir, MANIFEST_FILE),
4537
+ writeFileSync6(
4538
+ join6(dir, MANIFEST_FILE2),
3455
4539
  `${JSON.stringify(manifest, null, 2)}
3456
4540
  `,
3457
4541
  "utf8"
3458
4542
  );
3459
4543
  }
3460
4544
  function isAttachmentUpToDate(entry, item, dest) {
3461
- if (!entry || !existsSync8(dest)) return false;
4545
+ if (!entry || !existsSync9(dest)) return false;
3462
4546
  if (entry.name !== item.name) return false;
3463
4547
  const createdAt = item.createdAt ?? "";
3464
4548
  return entry.createdAt === createdAt;
@@ -3473,7 +4557,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
3473
4557
  const nextManifest = { version: 1, attachments: {} };
3474
4558
  const names = [];
3475
4559
  for (const item of attachments) {
3476
- const dest = join7(dir, item.name);
4560
+ const dest = join6(dir, item.name);
3477
4561
  const entry = manifest.attachments[item.id];
3478
4562
  const createdAt = item.createdAt ?? "";
3479
4563
  names.push(item.name);
@@ -3483,7 +4567,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
3483
4567
  continue;
3484
4568
  }
3485
4569
  const buffer = await downloadAttachment(cfg2, item.id);
3486
- writeFileSync5(dest, buffer);
4570
+ writeFileSync6(dest, buffer);
3487
4571
  nextManifest.attachments[item.id] = {
3488
4572
  name: item.name,
3489
4573
  createdAt
@@ -3504,9 +4588,9 @@ async function syncWebIdeAttachments(cfg2, taskId, workdir, attachments) {
3504
4588
  }
3505
4589
 
3506
4590
  // src/commands/sync-webide-design.ts
3507
- import { createHash } from "crypto";
3508
- import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
3509
- 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";
3510
4594
 
3511
4595
  // src/opc-shared-types.ts
3512
4596
  function webIdeDesignObjectKey(taskId, fileName) {
@@ -3522,17 +4606,17 @@ function webIdeDesignArtifactContentType(name) {
3522
4606
  }
3523
4607
 
3524
4608
  // src/commands/sync-webide-design.ts
3525
- var MANIFEST_FILE2 = ".sync-manifest.json";
4609
+ var MANIFEST_FILE3 = ".sync-manifest.json";
3526
4610
  function hashBuffer(buf) {
3527
- return createHash("sha256").update(buf).digest("hex");
4611
+ return createHash3("sha256").update(buf).digest("hex");
3528
4612
  }
3529
4613
  function loadManifest2(dir) {
3530
- const path = join8(dir, MANIFEST_FILE2);
3531
- if (!existsSync9(path)) {
4614
+ const path3 = join7(dir, MANIFEST_FILE3);
4615
+ if (!existsSync10(path3)) {
3532
4616
  return { version: 1, files: {} };
3533
4617
  }
3534
4618
  try {
3535
- const parsed = JSON.parse(readFileSync8(path, "utf8"));
4619
+ const parsed = JSON.parse(readFileSync8(path3, "utf8"));
3536
4620
  if (parsed?.version === 1 && parsed.files && typeof parsed.files === "object") {
3537
4621
  return parsed;
3538
4622
  }
@@ -3541,8 +4625,8 @@ function loadManifest2(dir) {
3541
4625
  return { version: 1, files: {} };
3542
4626
  }
3543
4627
  function saveManifest2(dir, manifest) {
3544
- writeFileSync6(
3545
- join8(dir, MANIFEST_FILE2),
4628
+ writeFileSync7(
4629
+ join7(dir, MANIFEST_FILE3),
3546
4630
  `${JSON.stringify(manifest, null, 2)}
3547
4631
  `,
3548
4632
  "utf8"
@@ -3570,13 +4654,13 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
3570
4654
  for (const art of artifacts) {
3571
4655
  const name = art.name?.trim();
3572
4656
  if (!name || !isWebIdeDesignArtifactName(name)) continue;
3573
- const localPath = join8(dir, name);
4657
+ const localPath = join7(dir, name);
3574
4658
  const entry = manifest.files[name];
3575
- if (entry?.contentHash === art.contentHash && existsSync9(localPath)) {
4659
+ if (entry?.contentHash === art.contentHash && existsSync10(localPath)) {
3576
4660
  continue;
3577
4661
  }
3578
4662
  const buf = await downloadDesignFile(cfg2, taskId, name);
3579
- writeFileSync6(localPath, buf);
4663
+ writeFileSync7(localPath, buf);
3580
4664
  manifest.files[name] = { contentHash: hashBuffer(buf) };
3581
4665
  synced.push(name);
3582
4666
  }
@@ -3585,12 +4669,12 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
3585
4669
  }
3586
4670
  function listLocalDesignArtifactFiles(taskId, workdir) {
3587
4671
  const dir = webideDesignDir(taskId, workdir);
3588
- if (!existsSync9(dir)) return [];
4672
+ if (!existsSync10(dir)) return [];
3589
4673
  const out = [];
3590
- for (const name of readdirSync4(dir)) {
4674
+ for (const name of readdirSync5(dir)) {
3591
4675
  if (!isWebIdeDesignArtifactName(name)) continue;
3592
- const absPath = join8(dir, name);
3593
- if (!statSync4(absPath).isFile()) continue;
4676
+ const absPath = join7(dir, name);
4677
+ if (!statSync5(absPath).isFile()) continue;
3594
4678
  const buf = readFileSync8(absPath);
3595
4679
  if (buf.length === 0) continue;
3596
4680
  out.push({ name, absPath, contentHash: hashBuffer(buf) });
@@ -3675,6 +4759,8 @@ function startActionForInbound(action) {
3675
4759
  case "request-develop":
3676
4760
  case "skip-plan":
3677
4761
  return "start-develop";
4762
+ case "request-start-project":
4763
+ return "start-project";
3678
4764
  case "request-execute-sql":
3679
4765
  return "start-execute-sql";
3680
4766
  case "request-bugfix":
@@ -3709,6 +4795,9 @@ function completeActionForInbound(action) {
3709
4795
  case "skip-plan":
3710
4796
  case "start-develop":
3711
4797
  return "complete-develop";
4798
+ case "request-start-project":
4799
+ case "start-project":
4800
+ return "complete-project";
3712
4801
  case "request-execute-sql":
3713
4802
  case "start-execute-sql":
3714
4803
  return "complete-execute-sql";
@@ -3780,6 +4869,9 @@ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
3780
4869
  function shouldCommitAfterWebIdeMessage(action) {
3781
4870
  return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
3782
4871
  }
4872
+ function isStartLocalServicesAction(action) {
4873
+ return action === "enter-manual-test" || action === "skip-test" || action === "request-start-project";
4874
+ }
3783
4875
  function isDesignAction(action) {
3784
4876
  return action === "request-design" || action === "request-revise-design";
3785
4877
  }
@@ -3790,11 +4882,11 @@ function syncLocalTaskStatus(workdir, taskId, msg2, status) {
3790
4882
  status
3791
4883
  });
3792
4884
  }
3793
- async function updateStatus(cfg2, messageId, status) {
4885
+ async function updateStatus2(cfg2, messageId, status) {
3794
4886
  const api = createApmApiClient(cfg2);
3795
4887
  await api.cli.webideUpdateMessageStatus({ id: messageId, status });
3796
4888
  }
3797
- async function setError(cfg2, messageId, error) {
4889
+ async function setError2(cfg2, messageId, error) {
3798
4890
  const api = createApmApiClient(cfg2);
3799
4891
  await api.cli.webideSetMessageError({ id: messageId, error });
3800
4892
  }
@@ -3813,7 +4905,7 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
3813
4905
  console.log(
3814
4906
  `[apm] webide-message action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} taskId=${taskId} messageId=${messageId}`
3815
4907
  );
3816
- await updateStatus(cfg2, messageId, "TYPING");
4908
+ await updateStatus2(cfg2, messageId, "TYPING");
3817
4909
  syncLocalTaskStatus(workdir, taskId, msg2, "TYPING");
3818
4910
  const eventSession = new EventSession(msg2.content);
3819
4911
  const prepAgentId = loadWebIdeAgentId(workdir, taskId) ?? "webide-cli-prep";
@@ -3904,13 +4996,10 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
3904
4996
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3905
4997
  await runPrepStep(
3906
4998
  "\u51C6\u5907\u7279\u6027\u5206\u652F",
3907
- async () => {
3908
- const projectId = await resolveProjectIdForTask(taskId);
3909
- return runTaskBranch(taskId, projectId, { cwd: workdir });
3910
- },
4999
+ async () => runTaskBranch(taskId, { cwd: workdir }),
3911
5000
  (branchResult) => {
3912
5001
  markBranchDone(branchCacheKey, workdir);
3913
- return `kind=${branchResult.kind} branch=${branchResult.branch} projectId=${branchResult.projectId} repos=${branchResult.repos.length}`;
5002
+ return `kind=${branchResult.kind} branch=${branchResult.branch} baseline=${branchResult.baseline} repos=${branchResult.repos.length}`;
3914
5003
  }
3915
5004
  );
3916
5005
  } else {
@@ -3936,6 +5025,43 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
3936
5025
  }
3937
5026
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3938
5027
  const api = createApmApiClient(cfg2);
5028
+ let projectId = null;
5029
+ await runPrepStep(
5030
+ "\u540C\u6B65\u9879\u76EE\u6587\u6863",
5031
+ async () => {
5032
+ try {
5033
+ const baseline = await api.cli.projectBaseBranch({ taskId });
5034
+ projectId = baseline.projectId?.trim() || null;
5035
+ if (!projectId) {
5036
+ return { synced: false, reason: "\u65E0 projectId" };
5037
+ }
5038
+ const docs = await syncProjectDocumentsPull(workdir, void 0, {
5039
+ projectId
5040
+ });
5041
+ return {
5042
+ synced: docs.synced,
5043
+ downloaded: docs.downloaded,
5044
+ deleted: docs.deleted
5045
+ };
5046
+ } catch (err) {
5047
+ console.warn(
5048
+ "[apm] WebIDE \u9879\u76EE\u6587\u6863\u540C\u6B65\u5931\u8D25:",
5049
+ err instanceof Error ? err.message : err
5050
+ );
5051
+ return {
5052
+ synced: false,
5053
+ reason: err instanceof Error ? err.message : String(err)
5054
+ };
5055
+ }
5056
+ },
5057
+ (result) => {
5058
+ if ("reason" in result && result.reason) {
5059
+ return `\u8DF3\u8FC7\uFF1A${result.reason}`;
5060
+ }
5061
+ if (!result.synced) return "\u65E0\u9700\u540C\u6B65";
5062
+ return `downloaded=${result.downloaded ?? 0} deleted=${result.deleted ?? 0}`;
5063
+ }
5064
+ );
3939
5065
  await runPrepStep(
3940
5066
  "\u540C\u6B65\u4EFB\u52A1\u9644\u4EF6",
3941
5067
  async () => {
@@ -3986,17 +5112,18 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
3986
5112
  "running"
3987
5113
  );
3988
5114
  await syncPrepLog();
5115
+ const startLocalServices = isStartLocalServicesAction(effectiveAction);
3989
5116
  const designAction = isDesignAction(effectiveAction);
3990
5117
  const useNewAgent = msg2.useNewAgent === true;
3991
5118
  const toolNames = Array.isArray(msg2.tools) ? msg2.tools.map((t) => String(t).trim()).filter(Boolean) : [];
3992
- if (useNewAgent) {
5119
+ if (useNewAgent && !startLocalServices) {
3993
5120
  if (designAction) {
3994
5121
  clearWebIdeDesignAgentId(workdir, taskId);
3995
5122
  } else {
3996
5123
  clearWebIdeAgentId(workdir, taskId);
3997
5124
  }
3998
5125
  }
3999
- const savedAgentId = useNewAgent ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
5126
+ const savedAgentId = startLocalServices || useNewAgent ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
4000
5127
  const promptPayload = toolNames.includes("MysqlExecute") ? continuePayload ?? parseWebIdePromptPayload(msg2.content) : null;
4001
5128
  const sqlExecutionId = toolNames.includes("MysqlExecute") && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
4002
5129
  if (toolNames.includes("MysqlExecute") && !sqlExecutionId) {
@@ -4005,6 +5132,7 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
4005
5132
  );
4006
5133
  }
4007
5134
  const persistAgentId = (agentId) => {
5135
+ if (startLocalServices) return;
4008
5136
  if (designAction) {
4009
5137
  saveWebIdeDesignAgentId(workdir, taskId, agentId);
4010
5138
  } else {
@@ -4030,7 +5158,7 @@ ${assumptionAnswersText}` : msg2.content,
4030
5158
  forceSend: true,
4031
5159
  eventSession,
4032
5160
  appendMessageContent: (content) => appendContent(cfg2, messageId, content),
4033
- askQuestionExecute: toolNames.includes("AskQuestion") ? createWebIdeAskQuestionExecute({
5161
+ askQuestionExecute: !startLocalServices && toolNames.includes("AskQuestion") ? createWebIdeAskQuestionExecute({
4034
5162
  cfg: cfg2,
4035
5163
  taskId,
4036
5164
  onAwaitingAssumptions: () => {
@@ -4042,10 +5170,11 @@ ${assumptionAnswersText}` : msg2.content,
4042
5170
  enableAskQuestion: false,
4043
5171
  enableWebIdePlanTools: false,
4044
5172
  enableMysqlTools: false,
5173
+ enablePtySessionMcp: startLocalServices || toolNames.includes("PtySession"),
4045
5174
  sqlExecutionId,
4046
5175
  toolNames,
4047
5176
  enableSandbox: false,
4048
- onInvalidatePersistedAgentId: () => {
5177
+ onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => {
4049
5178
  if (designAction) {
4050
5179
  clearWebIdeDesignAgentId(workdir, taskId);
4051
5180
  } else {
@@ -4098,7 +5227,9 @@ ${assumptionAnswersText}` : msg2.content,
4098
5227
  }
4099
5228
  }
4100
5229
  );
4101
- persistAgentId(outcome.agentId);
5230
+ if (!startLocalServices) {
5231
+ persistAgentId(outcome.agentId);
5232
+ }
4102
5233
  const tokenUsage = outcome.usage != null ? {
4103
5234
  modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
4104
5235
  inputTokens: outcome.usage.inputTokens,
@@ -4132,7 +5263,7 @@ ${assumptionAnswersText}` : msg2.content,
4132
5263
  );
4133
5264
  }
4134
5265
  }
4135
- await setError(cfg2, messageId, detail);
5266
+ await setError2(cfg2, messageId, detail);
4136
5267
  syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
4137
5268
  return;
4138
5269
  }
@@ -4157,7 +5288,7 @@ ${assumptionAnswersText}` : msg2.content,
4157
5288
  );
4158
5289
  }
4159
5290
  }
4160
- await updateStatus(cfg2, messageId, "CANCELLED");
5291
+ await updateStatus2(cfg2, messageId, "CANCELLED");
4161
5292
  syncLocalTaskStatus(workdir, taskId, msg2, "CANCELLED");
4162
5293
  return;
4163
5294
  }
@@ -4202,7 +5333,7 @@ ${assumptionAnswersText}` : msg2.content,
4202
5333
  detail,
4203
5334
  tokenUsage
4204
5335
  );
4205
- await setError(cfg2, messageId, detail);
5336
+ await setError2(cfg2, messageId, detail);
4206
5337
  syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
4207
5338
  return;
4208
5339
  }
@@ -4279,6 +5410,16 @@ ${assumptionAnswersText}` : msg2.content,
4279
5410
  }
4280
5411
  }
4281
5412
  }
5413
+ if (projectId) {
5414
+ try {
5415
+ await syncProjectDocumentsPush(cfg2, workdir, void 0, { projectId });
5416
+ } catch (err) {
5417
+ console.warn(
5418
+ "[apm] WebIDE \u9879\u76EE\u6587\u6863\u63A8\u9001\u5931\u8D25:",
5419
+ err instanceof Error ? err.message : err
5420
+ );
5421
+ }
5422
+ }
4282
5423
  await logSyncRef.current?.markRun(
4283
5424
  outcome.runId,
4284
5425
  "finished",
@@ -4293,11 +5434,21 @@ ${assumptionAnswersText}` : msg2.content,
4293
5434
  `[apm] \u672C\u8F6E\u505C\u5728\u5F85\u786E\u8BA4\u5047\u8BBE\uFF0C\u8DF3\u8FC7 complete action=${effectiveAction} taskId=${taskId}`
4294
5435
  );
4295
5436
  }
4296
- await updateStatus(cfg2, messageId, "SUCCESS");
5437
+ await updateStatus2(cfg2, messageId, "SUCCESS");
4297
5438
  syncLocalTaskStatus(workdir, taskId, msg2, "SUCCESS");
4298
5439
  console.log(
4299
5440
  `[apm] webide-message \u5B8C\u6210 action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} messageId=${messageId} agentId=${outcome.agentId}`
4300
5441
  );
5442
+ if (effectiveAction === "request-accept" || effectiveAction === "start-accept") {
5443
+ try {
5444
+ await cleanWebIdeWorkspaceCache(taskId, workdir, cfg2);
5445
+ } catch (err) {
5446
+ console.warn(
5447
+ "[apm] \u5408\u5E76 PR \u540E\u6E05\u7406 WebIDE agent \u7F13\u5B58\u5931\u8D25:",
5448
+ err instanceof Error ? err.message : err
5449
+ );
5450
+ }
5451
+ }
4301
5452
  } catch (err) {
4302
5453
  const detail = err instanceof Error ? err.message : String(err);
4303
5454
  console.error(`[apm] webide-message \u5931\u8D25: ${detail}`);
@@ -4308,7 +5459,7 @@ ${assumptionAnswersText}` : msg2.content,
4308
5459
  clearWebIdeAgentId(workdir, taskId);
4309
5460
  }
4310
5461
  try {
4311
- await setError(cfg2, messageId, detail);
5462
+ await setError2(cfg2, messageId, detail);
4312
5463
  } catch (statusErr) {
4313
5464
  console.error(
4314
5465
  "[apm] \u5199\u5165 WebIDE FAILED \u5931\u8D25:",
@@ -4332,7 +5483,23 @@ ${assumptionAnswersText}` : msg2.content,
4332
5483
 
4333
5484
  // src/commands/connect/core/worker/handlers/index.ts
4334
5485
  async function dispatchWorkerJob(cfg2, msg2, signal) {
4335
- 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
+ }
4336
5503
  }
4337
5504
 
4338
5505
  // src/commands/connect/core/worker/worker-script.ts