ai-project-manage-cli 8.1.20 → 8.1.22

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,38 +115,10 @@ 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
- }),
134
118
  getApmLogStorage: defineEndpoint({
135
119
  method: "GET",
136
120
  path: "/cli/apm-log-storage"
137
121
  }),
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
- }),
150
122
  cursorStoreUpsertAgent: defineEndpoint({
151
123
  method: "PUT",
152
124
  path: "/cli/cursor-store/agents"
@@ -207,6 +179,15 @@ function resolveClientMachineId(cfg2) {
207
179
  function resolveApiKey(cfg2) {
208
180
  return (cfg2.apiKey ?? cfg2.token ?? "").trim();
209
181
  }
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
+ }
210
191
  function tryReadApmConfig() {
211
192
  try {
212
193
  const raw = readFileSync(APM_CONFIG_PATH, "utf8");
@@ -222,10 +203,12 @@ function tryReadApmConfig() {
222
203
  if (!apiKey) return null;
223
204
  const cfg2 = v;
224
205
  const clientMachineId = resolveClientMachineId(cfg2);
206
+ const socketServer = readConfigSocketServer(cfg2);
225
207
  return {
226
208
  baseUrl: cfg2.baseUrl.trim().replace(/\/+$/, ""),
227
209
  apiKey,
228
- ...clientMachineId ? { clientMachineId } : {}
210
+ ...clientMachineId ? { clientMachineId } : {},
211
+ ...socketServer ? { socketServer } : {}
229
212
  };
230
213
  } catch {
231
214
  return null;
@@ -248,10 +231,14 @@ async function ensureApmConfig() {
248
231
  async function writeApmConfig(cfg2) {
249
232
  const clientMachineId = resolveClientMachineId(cfg2);
250
233
  const apiKey = resolveApiKey(cfg2);
234
+ const socketServer = stripTrailingSlash(
235
+ cfg2.socketServer?.trim() || cfg2.wsUrl?.trim() || ""
236
+ );
251
237
  const normalized = {
252
238
  baseUrl: cfg2.baseUrl.trim().replace(/\/+$/, ""),
253
239
  apiKey,
254
- ...clientMachineId ? { clientMachineId } : {}
240
+ ...clientMachineId ? { clientMachineId } : {},
241
+ ...socketServer ? { socketServer } : {}
255
242
  };
256
243
  mkdirSync(APM_CONFIG_DIR, { recursive: true });
257
244
  writeFileSync(APM_CONFIG_PATH, JSON.stringify(normalized, null, 2) + "\n", {
@@ -284,8 +271,8 @@ function toFsPath(inputPath) {
284
271
  }
285
272
  return `\\\\?\\${normalized}`;
286
273
  }
287
- function normalizeWorkdirPath(path3) {
288
- let normalized = path3.trim().replace(/\\/g, "/").normalize("NFC");
274
+ function normalizeWorkdirPath(path) {
275
+ let normalized = path.trim().replace(/\\/g, "/").normalize("NFC");
289
276
  if (normalized.startsWith("//?/")) {
290
277
  normalized = normalized.slice(4);
291
278
  }
@@ -372,84 +359,66 @@ async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
372
359
  }
373
360
 
374
361
  // 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
379
362
  import { execFile } from "child_process";
363
+ import { dirname, join as join3 } from "path";
364
+ import { fileURLToPath } from "url";
380
365
  import { promisify } from "util";
381
366
  var execFileAsync = promisify(execFile);
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
- }
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");
407
382
  }
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;
383
+ if (!/\bBatchMode=/.test(base)) {
384
+ extras.push("-o", "BatchMode=yes");
423
385
  }
386
+ if (!/\bConnectTimeout=/.test(base)) {
387
+ extras.push("-o", "ConnectTimeout=20");
388
+ }
389
+ if (!/\bServerAliveInterval=/.test(base)) {
390
+ extras.push("-o", "ServerAliveInterval=10");
391
+ }
392
+ if (!/\bServerAliveCountMax=/.test(base)) {
393
+ extras.push("-o", "ServerAliveCountMax=3");
394
+ }
395
+ return extras.length ? `${base} ${extras.join(" ")}` : base;
424
396
  }
425
- async function tryReadHttpsGitOriginUrl(cwd) {
426
- const raw = await tryReadGitOriginUrl(cwd);
427
- if (!raw) return null;
428
- return toHttpsGitRemoteUrl(raw);
429
- }
430
-
431
- // src/utils/git.ts
432
- var execFileAsync2 = promisify2(execFile2);
433
397
  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 };
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
+ };
404
+ }
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;
445
410
  }
446
- async function execGit(cwd, args, quiet = false) {
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;
447
415
  try {
448
- const { stdout, stderr } = await execFileAsync2("git", args, {
416
+ const { stdout, stderr } = await execFileAsync("git", args, {
449
417
  cwd,
450
418
  env: gitExecEnv(),
451
419
  encoding: "utf8",
452
- maxBuffer: 10 * 1024 * 1024
420
+ maxBuffer: 10 * 1024 * 1024,
421
+ ...timeoutMs > 0 ? { timeout: timeoutMs, killSignal: "SIGTERM" } : {}
453
422
  });
454
423
  if (!quiet && stderr.trim()) {
455
424
  process.stderr.write(stderr);
@@ -457,113 +426,224 @@ async function execGit(cwd, args, quiet = false) {
457
426
  return stdout;
458
427
  } catch (err) {
459
428
  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
+ }
460
434
  const detail = (e.stderr ?? e.message ?? String(err)).trim();
461
435
  throw new Error(
462
436
  `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
463
437
  );
464
438
  }
465
439
  }
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
- }
474
440
  async function ensureGitRepo(cwd) {
475
441
  await execGit(cwd, ["rev-parse", "--git-dir"], true);
476
442
  }
477
443
  async function getCurrentBranch(cwd) {
478
444
  return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
479
445
  }
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
- }
492
446
  async function resolveGitRepoRoot(cwd) {
493
447
  return (await execGit(cwd, ["rev-parse", "--show-toplevel"], true)).trim();
494
448
  }
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
- );
449
+ var FEATURE_BRANCH_RE = /^feat\/task-(.+)$/;
450
+ function isFeatureBranchName(branch) {
451
+ return FEATURE_BRANCH_RE.test(branch.trim().replace(/^origin\//, ""));
503
452
  }
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;
510
- try {
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
- }
453
+ function resolveGitScript(name) {
454
+ return join3(dirname(fileURLToPath(import.meta.url)), "scripts", name);
455
+ }
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 ?? []];
522
461
  try {
523
- await execGit(cwd, ["diff", "--quiet", `${baseRef}...${headRef}`], true);
524
- return false;
525
- } catch {
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);
526
472
  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}` : ""}`);
527
487
  }
528
488
  }
529
- var FEATURE_BRANCH_RE = /^feat\/task-(.+)$/;
530
- function isFeatureBranchName(branch) {
531
- return FEATURE_BRANCH_RE.test(branch.trim().replace(/^origin\//, ""));
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
+ });
532
503
  }
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;
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
+ });
538
510
  }
539
511
  async function pushCurrentBranch(cwd) {
540
- if (!await isGitRepo(cwd)) {
541
- return false;
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
+ var HOME_REPOSITORY_JSON = "/home/repository.json";
522
+ function isSimpleChildDirName(name) {
523
+ if (!name || name === "." || name === "..") return false;
524
+ if (name.includes("/") || name.includes("\\")) return false;
525
+ return true;
526
+ }
527
+ function loadHomeRepositoryEntries() {
528
+ const path = toFsPath(HOME_REPOSITORY_JSON);
529
+ if (!existsSync2(path)) {
530
+ throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${HOME_REPOSITORY_JSON}`);
542
531
  }
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;
532
+ let parsed;
533
+ try {
534
+ parsed = JSON.parse(readFileSync2(path, "utf8"));
535
+ } catch (err) {
536
+ const detail = err instanceof Error ? err.message : String(err);
537
+ throw new Error(`[apm] \u89E3\u6790 ${HOME_REPOSITORY_JSON} \u5931\u8D25: ${detail}`);
547
538
  }
548
- await execGit(cwd, ["push", "-u", "origin", "HEAD"]);
549
- return true;
539
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
540
+ throw new Error(
541
+ `[apm] ${HOME_REPOSITORY_JSON} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
542
+ );
543
+ }
544
+ const entries = [];
545
+ for (const [rawKey, rawVal] of Object.entries(
546
+ parsed
547
+ )) {
548
+ const pathKey = rawKey.trim();
549
+ if (!isSimpleChildDirName(pathKey)) {
550
+ throw new Error(
551
+ `[apm] ${HOME_REPOSITORY_JSON} \u975E\u6CD5\u76EE\u5F55 key: ${JSON.stringify(rawKey)}\uFF08\u987B\u4E3A\u5F53\u524D\u4E0B\u7EA7\u76EE\u5F55\u540D\uFF09`
552
+ );
553
+ }
554
+ if (typeof rawVal !== "string" || !rawVal.trim()) {
555
+ throw new Error(
556
+ `[apm] ${HOME_REPOSITORY_JSON}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
557
+ );
558
+ }
559
+ entries.push({ path: pathKey, remoteUrl: rawVal.trim() });
560
+ }
561
+ entries.sort((a, b) => a.path.localeCompare(b.path));
562
+ if (entries.length === 0) {
563
+ throw new Error(`[apm] ${HOME_REPOSITORY_JSON} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
564
+ }
565
+ return entries;
566
+ }
567
+ function resolveHomeRepositoryAbsolutePaths(workdirInput, entries = loadHomeRepositoryEntries()) {
568
+ const workdir = resolveWorkdirPath(workdirInput);
569
+ return entries.map((entry) => resolve3(join4(workdir, entry.path)));
570
+ }
571
+ function tryLoadHomeRepositoryEntries() {
572
+ const path = toFsPath(HOME_REPOSITORY_JSON);
573
+ if (!existsSync2(path)) return null;
574
+ return loadHomeRepositoryEntries();
550
575
  }
551
576
 
552
577
  // src/utils/workspace-repos.ts
553
578
  import {
554
- existsSync as existsSync2,
579
+ existsSync as existsSync3,
555
580
  mkdirSync as mkdirSync3,
556
- readFileSync as readFileSync2,
581
+ readFileSync as readFileSync3,
557
582
  readdirSync as readdirSync2,
558
583
  statSync as statSync2,
559
584
  writeFileSync as writeFileSync2
560
585
  } from "fs";
561
- import { basename, dirname, join as join3, relative, resolve as resolve3 } from "path";
586
+ import { basename, dirname as dirname2, join as join5, relative, resolve as resolve4 } from "path";
587
+
588
+ // src/utils/git-remote.ts
589
+ import { execFile as execFile2 } from "child_process";
590
+ import { promisify as promisify2 } from "util";
591
+ var execFileAsync2 = promisify2(execFile2);
592
+ function toHttpsGitRemoteUrl(raw) {
593
+ let s = raw.trim();
594
+ if (!s) return null;
595
+ const sshScp = /^git@([^:]+):(.+)$/.exec(s);
596
+ if (sshScp) {
597
+ s = `https://${sshScp[1]}/${sshScp[2]}`;
598
+ } else {
599
+ const sshUri = /^ssh:\/\/(?:git@)?([^/]+)\/(.+)$/i.exec(s);
600
+ if (sshUri) {
601
+ s = `https://${sshUri[1]}/${sshUri[2]}`;
602
+ } else if (/^http:\/\//i.test(s)) {
603
+ s = `https://${s.slice("http://".length)}`;
604
+ } else if (!/^https:\/\//i.test(s)) {
605
+ const scpLike = /^([^/:]+):(.+)$/.exec(s);
606
+ if (scpLike && !scpLike[1].includes(".")) {
607
+ return null;
608
+ }
609
+ if (scpLike) {
610
+ s = `https://${scpLike[1]}/${scpLike[2]}`;
611
+ } else if (s.includes("/")) {
612
+ s = `https://${s}`;
613
+ } else {
614
+ return null;
615
+ }
616
+ }
617
+ }
618
+ s = s.replace(/\/+$/, "");
619
+ if (!/^https:\/\//i.test(s)) return null;
620
+ return s;
621
+ }
622
+ async function tryReadGitOriginUrl(cwd) {
623
+ try {
624
+ const { stdout } = await execFileAsync2(
625
+ "git",
626
+ ["config", "--get", "remote.origin.url"],
627
+ { cwd, encoding: "utf8", maxBuffer: 1024 * 1024 }
628
+ );
629
+ const url = stdout.trim();
630
+ return url || null;
631
+ } catch {
632
+ return null;
633
+ }
634
+ }
635
+ async function tryReadHttpsGitOriginUrl(cwd) {
636
+ const raw = await tryReadGitOriginUrl(cwd);
637
+ if (!raw) return null;
638
+ return toHttpsGitRemoteUrl(raw);
639
+ }
640
+
641
+ // src/utils/workspace-repos.ts
562
642
  var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
563
643
  var WORKSPACE_REPOS_VERSION = 1;
564
644
  var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([".apm", "node_modules"]);
565
645
  function hasGitAt(dir) {
566
- return existsSync2(toFsPath(join3(dir, ".git")));
646
+ return existsSync3(toFsPath(join5(dir, ".git")));
567
647
  }
568
648
  function isSkippableChildName(name) {
569
649
  if (SKIP_DIR_NAMES.has(name)) return true;
@@ -571,25 +651,21 @@ function isSkippableChildName(name) {
571
651
  return false;
572
652
  }
573
653
  function manifestPath(workdir) {
574
- return join3(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
654
+ return join5(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
575
655
  }
576
656
  function absoluteRepoPath(workdir, entry) {
577
- if (entry.path === "." || entry.path === "") {
578
- return workdir;
579
- }
580
- return resolve3(workdir, entry.path);
657
+ return resolve4(workdir, entry.path);
581
658
  }
582
659
  function toRelativeRepoPath(workdir, repoAbs) {
583
- const rel = relative(workdir, repoAbs).replace(/\\/g, "/");
584
- return rel === "" ? "." : rel;
660
+ return relative(workdir, repoAbs).replace(/\\/g, "/");
585
661
  }
586
662
  function normalizeRepoEntry(raw) {
587
663
  if (!raw || typeof raw !== "object") return null;
588
664
  const o = raw;
589
665
  if (typeof o.path !== "string" || !o.path.trim()) return null;
590
- const entry = {
591
- path: o.path.trim().replace(/\\/g, "/")
592
- };
666
+ const path = o.path.trim().replace(/\\/g, "/");
667
+ if (path === "." || path.includes("/")) return null;
668
+ const entry = { path };
593
669
  if (typeof o.remoteUrl === "string" && o.remoteUrl.trim()) {
594
670
  entry.remoteUrl = o.remoteUrl.trim();
595
671
  } else if (o.remoteUrl === null) {
@@ -598,18 +674,18 @@ function normalizeRepoEntry(raw) {
598
674
  return entry;
599
675
  }
600
676
  function readManifest(workdir) {
601
- const path3 = toFsPath(manifestPath(workdir));
602
- if (!existsSync2(path3)) {
677
+ const path = toFsPath(manifestPath(workdir));
678
+ if (!existsSync3(path)) {
603
679
  return null;
604
680
  }
605
681
  try {
606
682
  const raw = JSON.parse(
607
- readFileSync2(path3, "utf8")
683
+ readFileSync3(path, "utf8")
608
684
  );
609
685
  if (raw?.version !== WORKSPACE_REPOS_VERSION) {
610
686
  return null;
611
687
  }
612
- if (raw.kind !== "single" && raw.kind !== "multi") {
688
+ if (raw.kind !== "multi") {
613
689
  return null;
614
690
  }
615
691
  if (!Array.isArray(raw.repos) || raw.repos.length === 0) {
@@ -624,7 +700,7 @@ function readManifest(workdir) {
624
700
  }
625
701
  return {
626
702
  version: WORKSPACE_REPOS_VERSION,
627
- kind: raw.kind,
703
+ kind: "multi",
628
704
  workdir: raw.workdir,
629
705
  repos,
630
706
  scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt.trim() ? raw.scannedAt : (/* @__PURE__ */ new Date()).toISOString()
@@ -636,8 +712,8 @@ function readManifest(workdir) {
636
712
  function writeManifest(workdir, manifest) {
637
713
  const apmDir = toFsPath(workspaceApmDir(workdir));
638
714
  mkdirSync3(apmDir, { recursive: true });
639
- const path3 = toFsPath(manifestPath(workdir));
640
- writeFileSync2(path3, `${JSON.stringify(manifest, null, 2)}
715
+ const path = toFsPath(manifestPath(workdir));
716
+ writeFileSync2(path, `${JSON.stringify(manifest, null, 2)}
641
717
  `, "utf8");
642
718
  }
643
719
  function manifestStillValid(workdir, manifest) {
@@ -648,6 +724,9 @@ function manifestStillValid(workdir, manifest) {
648
724
  if (typeof entry?.path !== "string" || !entry.path.trim()) {
649
725
  return false;
650
726
  }
727
+ if (entry.path === "." || entry.path.includes("/")) {
728
+ return false;
729
+ }
651
730
  const abs = absoluteRepoPath(workdir, entry);
652
731
  if (!hasGitAt(abs)) {
653
732
  return false;
@@ -657,17 +736,8 @@ function manifestStillValid(workdir, manifest) {
657
736
  }
658
737
  function scanWorkspaceRepos(workdirInput) {
659
738
  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
- }
669
739
  const fsWorkdir = toFsPath(workdir);
670
- if (!existsSync2(fsWorkdir) || !statSync2(fsWorkdir).isDirectory()) {
740
+ if (!existsSync3(fsWorkdir) || !statSync2(fsWorkdir).isDirectory()) {
671
741
  throw new Error(`[apm] \u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${workdir}`);
672
742
  }
673
743
  const repos = [];
@@ -675,9 +745,9 @@ function scanWorkspaceRepos(workdirInput) {
675
745
  if (isSkippableChildName(name)) {
676
746
  continue;
677
747
  }
678
- const childAbs = resolve3(workdir, name);
748
+ const childAbs = resolve4(workdir, name);
679
749
  const childFs = toFsPath(childAbs);
680
- if (!existsSync2(childFs) || !statSync2(childFs).isDirectory()) {
750
+ if (!existsSync3(childFs) || !statSync2(childFs).isDirectory()) {
681
751
  continue;
682
752
  }
683
753
  if (!hasGitAt(childAbs)) {
@@ -688,7 +758,7 @@ function scanWorkspaceRepos(workdirInput) {
688
758
  repos.sort((a, b) => a.path.localeCompare(b.path));
689
759
  if (repos.length === 0) {
690
760
  throw new Error(
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`
761
+ `[apm] \u5DE5\u4F5C\u533A ${workdir} \u4E00\u7EA7\u5B50\u76EE\u5F55\u4E2D\u672A\u53D1\u73B0 git \u4ED3\u5E93`
692
762
  );
693
763
  }
694
764
  return {
@@ -716,7 +786,7 @@ function loadWorkspaceReposCache(workdirInput) {
716
786
  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`);
717
787
  }
718
788
  console.log(
719
- `[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 kind=${cached.kind} repos=${cached.repos.map((r) => r.path).join(", ")}`
789
+ `[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 repos=${cached.repos.map((r) => r.path).join(", ")}`
720
790
  );
721
791
  return {
722
792
  ...cached,
@@ -754,7 +824,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
754
824
  const cached = readManifest(workdir);
755
825
  if (cached && manifestStillValid(workdir, cached)) {
756
826
  console.log(
757
- `[apm] \u4F7F\u7528\u7F13\u5B58\u7684\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 kind=${cached.kind} repos=${cached.repos.length}`
827
+ `[apm] \u4F7F\u7528\u7F13\u5B58\u7684\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 repos=${cached.repos.length}`
758
828
  );
759
829
  return cached;
760
830
  }
@@ -771,7 +841,7 @@ function resolveWorkspaceRepos(workdirInput, options) {
771
841
  }
772
842
  writeManifest(workdir, scanned);
773
843
  console.log(
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}`
844
+ `[apm] \u5DF2\u626B\u63CF\u5DE5\u4F5C\u533A\u4ED3\u5E93 repos=${scanned.repos.map((r) => r.path).join(", ")} \u2192 .apm/${WORKSPACE_REPOS_MANIFEST}`
775
845
  );
776
846
  return scanned;
777
847
  }
@@ -782,48 +852,64 @@ function resolveWorkspaceRepoAbsolutePaths(manifest) {
782
852
  }
783
853
  function formatRepoLabel(workdir, repoAbs) {
784
854
  const rel = toRelativeRepoPath(resolveWorkdirPath(workdir), repoAbs);
785
- return rel === "." ? basename(repoAbs) || repoAbs : rel;
855
+ return rel || basename(repoAbs) || repoAbs;
786
856
  }
787
857
 
788
858
  // src/commands/branch.ts
789
859
  var TASK_BRANCH_PREFIX = "feat/task-";
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
- }
860
+ async function commitWorkspaceReposIfDirty(workdir, message) {
861
+ const result = await commitTaskReposIfDirty(workdir, message);
862
+ return result.committed;
801
863
  }
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;
864
+ async function commitTaskReposIfDirty(workdir, message) {
865
+ return syncTaskRepos(workdir, message);
812
866
  }
813
- async function commitWorkspaceReposIfDirty(workdir, message) {
867
+ async function syncTaskRepos(workdir, message) {
814
868
  const workdirPath = resolveWorkdirPath(workdir);
815
- const manifest = loadWorkspaceReposCache(workdirPath);
816
- const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
869
+ const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
817
870
  let committed = 0;
871
+ let commitSkipped = 0;
872
+ let pushed = 0;
873
+ let pushSkipped = 0;
874
+ const failed = [];
818
875
  for (const gitRoot of repoRoots) {
819
876
  const label = formatRepoLabel(workdirPath, gitRoot);
820
- const did = await commitWorkingTreeIfDirty(gitRoot, message);
821
- if (did) {
822
- committed += 1;
823
- console.log(`[apm] WebIDE \u515C\u5E95\u63D0\u4EA4 @ ${label}`);
877
+ try {
878
+ const didCommit = await commitWorkingTreeIfDirty(gitRoot, message);
879
+ if (didCommit) {
880
+ committed += 1;
881
+ console.log(`[apm] \u5DF2\u63D0\u4EA4 @ ${label}`);
882
+ } else {
883
+ commitSkipped += 1;
884
+ }
885
+ } catch (err) {
886
+ const detail = err instanceof Error ? err.message : String(err);
887
+ console.error(`[apm] \u4ED3\u5E93 ${label} \u63D0\u4EA4\u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
888
+ failed.push(label);
889
+ continue;
890
+ }
891
+ try {
892
+ const didPush = await pushCurrentBranch(gitRoot);
893
+ if (didPush) {
894
+ pushed += 1;
895
+ console.log(`[apm] \u5DF2 push @ ${label}`);
896
+ } else {
897
+ pushSkipped += 1;
898
+ }
899
+ } catch (err) {
900
+ const detail = err instanceof Error ? err.message : String(err);
901
+ console.error(`[apm] \u4ED3\u5E93 ${label} push \u5931\u8D25\uFF08\u7EE7\u7EED\u5176\u5B83\u4ED3\uFF09: ${detail}`);
902
+ failed.push(label);
824
903
  }
825
904
  }
826
- return committed;
905
+ return {
906
+ committed,
907
+ commitSkipped,
908
+ pushed,
909
+ pushSkipped,
910
+ failed,
911
+ repos: repoRoots
912
+ };
827
913
  }
828
914
  async function pushWorkspaceRepos(workdir) {
829
915
  const workdirPath = resolveWorkdirPath(workdir);
@@ -852,58 +938,13 @@ function branchNameForTask(taskId) {
852
938
  }
853
939
  return `${TASK_BRANCH_PREFIX}${id}`;
854
940
  }
855
- async function ensureFeatureBranch(branch, baselineBranch, options) {
941
+ async function ensureTaskFeatureBranch(branch, projectId, options) {
856
942
  const cwd = options.cwd ?? process.cwd();
857
943
  const gitRoot = await resolveGitRepoRoot(cwd);
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}`);
944
+ await ensureFeatureBranch(gitRoot, branch, projectId);
904
945
  return branch;
905
946
  }
906
- async function runTaskBranch(taskId, options = {}) {
947
+ async function resolveProjectIdForTask(taskId) {
907
948
  const trimmedTaskId = taskId.trim();
908
949
  if (!trimmedTaskId) {
909
950
  throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
@@ -913,75 +954,73 @@ async function runTaskBranch(taskId, options = {}) {
913
954
  const projectBaseline = await api.cli.projectBaseBranch({
914
955
  taskId: trimmedTaskId
915
956
  });
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");
957
+ const projectId = projectBaseline.baseBranch?.trim() ?? "";
958
+ if (!projectId) {
959
+ 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");
919
960
  }
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);
961
+ return projectId;
962
+ }
963
+ function resolveTaskBranchRepoRoots(workdirPath) {
964
+ const entries = tryLoadHomeRepositoryEntries();
965
+ if (entries) {
966
+ const roots = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
927
967
  console.log(
928
- `[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (project baseline=${baselineBranch})`
968
+ `[apm] \u8BFB\u53D6 ${HOME_REPOSITORY_JSON} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
929
969
  );
930
- await ensureFeatureBranch(branch, baselineBranch, {
931
- ...options,
932
- cwd: gitRoot
933
- });
970
+ return roots;
934
971
  }
935
- return {
936
- branch,
937
- baseline: baselineBranch,
938
- kind: manifest.kind,
939
- repos: repoRoots
940
- };
972
+ const manifest = loadWorkspaceReposCache(workdirPath);
973
+ return resolveWorkspaceRepoAbsolutePaths(manifest);
941
974
  }
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");
975
+ async function runTaskBranch(taskId, projectId, options = {}) {
976
+ const trimmedTaskId = taskId.trim();
977
+ if (!trimmedTaskId) {
978
+ throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
979
+ }
980
+ const trimmedProjectId = projectId.trim();
981
+ if (!trimmedProjectId) {
982
+ throw new Error("[apm] projectId\uFF08\u9879\u76EE ID\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
983
+ }
984
+ if (/[\s\\]/.test(trimmedProjectId) || trimmedProjectId.includes("..")) {
985
+ throw new Error("[apm] \u9879\u76EE ID \u975E\u6CD5");
946
986
  }
947
987
  const cwd = options.cwd ?? process.cwd();
948
988
  const workdirPath = resolveWorkdirPath(cwd);
949
- const manifest = loadWorkspaceReposCache(workdirPath);
950
- const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
989
+ const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
990
+ if (repoRoots.length === 0) {
991
+ throw new Error("[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7279\u6027\u5206\u652F");
992
+ }
993
+ const branch = branchNameForTask(trimmedTaskId);
994
+ const repoLabels = repoRoots.map(
995
+ (gitRoot) => formatRepoLabel(workdirPath, gitRoot)
996
+ );
997
+ console.log(
998
+ `[apm] \u591A\u4ED3\u786E\u8BA4\u7279\u6027\u5206\u652F ${branch} projectId=${trimmedProjectId} repos=${repoLabels.join(",")}`
999
+ );
951
1000
  for (const gitRoot of repoRoots) {
952
1001
  const label = formatRepoLabel(workdirPath, gitRoot);
953
- console.log(`[apm] \u5207\u6362\u57FA\u7EBF\u5206\u652F ${baseline} @ ${label}`);
954
- await checkoutBaselineBranch(baseline, { ...options, cwd: gitRoot });
1002
+ console.log(
1003
+ `[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (projectId=${trimmedProjectId})`
1004
+ );
1005
+ try {
1006
+ await ensureTaskFeatureBranch(branch, trimmedProjectId, {
1007
+ ...options,
1008
+ cwd: gitRoot
1009
+ });
1010
+ } catch (err) {
1011
+ const detail = err instanceof Error ? err.message : String(err);
1012
+ throw new Error(
1013
+ `[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}`
1014
+ );
1015
+ }
955
1016
  }
956
1017
  return {
957
- baseline,
958
- kind: manifest.kind,
1018
+ branch,
1019
+ projectId: trimmedProjectId,
1020
+ kind: "multi",
959
1021
  repos: repoRoots
960
1022
  };
961
1023
  }
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
- }
985
1024
 
986
1025
  // src/commands/connect/cursor-agent.ts
987
1026
  import {
@@ -1631,8 +1670,8 @@ function createUpsertWebIdeTestCasesTool(options) {
1631
1670
  }
1632
1671
 
1633
1672
  // src/commands/connect/tools/mysql-tools.ts
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";
1673
+ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
1674
+ import { isAbsolute, relative as relative2, resolve as resolve5 } from "node:path";
1636
1675
  import mysql from "mysql2/promise";
1637
1676
  var MAX_RESULT_ROWS = 100;
1638
1677
  var EXEC_TIMEOUT_MS = 6e4;
@@ -1703,14 +1742,14 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
1703
1742
  if (!isAbsolute(absolutePath)) {
1704
1743
  return { ok: false, error: "path \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
1705
1744
  }
1706
- const workdirAbs = resolve4(toFsPath(workdir));
1707
- const targetAbs = resolve4(toFsPath(absolutePath));
1745
+ const workdirAbs = resolve5(toFsPath(workdir));
1746
+ const targetAbs = resolve5(toFsPath(absolutePath));
1708
1747
  const rel = relative2(workdirAbs, targetAbs);
1709
1748
  if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
1710
1749
  return { ok: false, error: "path \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
1711
1750
  }
1712
1751
  const fsPath = toFsPath(targetAbs);
1713
- if (!existsSync3(fsPath)) {
1752
+ if (!existsSync4(fsPath)) {
1714
1753
  return { ok: false, error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${absolutePath}` };
1715
1754
  }
1716
1755
  let st;
@@ -1732,7 +1771,7 @@ function resolveSqlArtifactPath(workdir, absolutePath) {
1732
1771
  return { ok: true, absPath: targetAbs, displayPath };
1733
1772
  }
1734
1773
  function readSqlFile(absPath) {
1735
- return readFileSync3(toFsPath(absPath), "utf8");
1774
+ return readFileSync4(toFsPath(absPath), "utf8");
1736
1775
  }
1737
1776
  function createMysqlExecuteTool(options) {
1738
1777
  const { taskId, executionId, workdir, upsertSqlExecution } = options;
@@ -1910,2373 +1949,1213 @@ function createMysqlExecuteTool(options) {
1910
1949
  };
1911
1950
  }
1912
1951
 
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
- });
1952
+ // src/commands/connect/tools/index.ts
1953
+ var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
1954
+ AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
1955
+ \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
1956
+ \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
1957
+ var WORKSPACE_BOUNDARY_HINT = `[\u5DE5\u4F5C\u533A\u8FB9\u754C]
1958
+ \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
1959
+ \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`;
1960
+ function wantsTool(name, options, legacyDefault) {
1961
+ if (options.toolNames !== void 0) {
1962
+ return options.toolNames.includes(name);
1935
1963
  }
1936
- async ensureBucket(bucket) {
1937
- const exists = await this.inner.bucketExists(bucket);
1938
- if (!exists) {
1939
- await this.inner.makeBucket(bucket);
1964
+ return legacyDefault;
1965
+ }
1966
+ function createCursorCustomTools(cfg2, options) {
1967
+ const enableAppend = options.enableAppendMessage !== false && Boolean(options.appendMessageContent);
1968
+ const enableAsk = options.enableAskQuestion !== false;
1969
+ const enablePlan = options.enableWebIdePlanTools === true;
1970
+ const enableMysql = options.enableMysqlTools === true;
1971
+ const tools = {
1972
+ ...wantsTool("AppendMessage", options, enableAppend) && options.appendMessageContent ? createAppendMessageCustomTools({
1973
+ appendContent: options.appendMessageContent
1974
+ }) : {},
1975
+ ...wantsTool("AskQuestion", options, enableAsk) ? createAskQuestionTool({
1976
+ onInvoke: options?.onAskQuestion,
1977
+ execute: options?.askQuestionExecute
1978
+ }) : {}
1979
+ };
1980
+ if (options?.taskId && options.workdir) {
1981
+ const { cli } = createApmApiClient(cfg2);
1982
+ const taskId = options.taskId;
1983
+ if (wantsTool("RecommendPlan", options, enablePlan)) {
1984
+ Object.assign(
1985
+ tools,
1986
+ createRecommendPlanTool({
1987
+ webideRecommendPlan: (args) => cli.webideRecommendPlan({ taskId, ...args })
1988
+ })
1989
+ );
1940
1990
  }
1941
- }
1942
- async putObject(bucket, objectKey, body, meta) {
1943
- await this.inner.putObject(bucket, objectKey, body, body.length, meta);
1944
- }
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));
1991
+ if (wantsTool("UpsertWebIdePlan", options, enablePlan)) {
1992
+ Object.assign(
1993
+ tools,
1994
+ createUpsertWebIdePlanTool({
1995
+ webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
1996
+ })
1997
+ );
1998
+ }
1999
+ if (wantsTool("UpsertWebIdeChecklist", options, enablePlan)) {
2000
+ Object.assign(
2001
+ tools,
2002
+ createUpsertWebIdeChecklistTool({
2003
+ webideUpsertChecklist: (args) => cli.webideUpsertChecklist({ taskId, ...args })
2004
+ })
2005
+ );
2006
+ }
2007
+ if (wantsTool("GetWebIdeChecklist", options, enablePlan)) {
2008
+ Object.assign(
2009
+ tools,
2010
+ createGetWebIdeChecklistTool({
2011
+ boundTaskId: taskId,
2012
+ webideGetChecklist: (args) => cli.webideGetChecklist(args)
2013
+ })
2014
+ );
2015
+ }
2016
+ if (wantsTool("UpsertWebIdeTestCases", options, enablePlan)) {
2017
+ Object.assign(
2018
+ tools,
2019
+ createUpsertWebIdeTestCasesTool({
2020
+ webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
2021
+ })
2022
+ );
2023
+ }
2024
+ if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
2025
+ Object.assign(
2026
+ tools,
2027
+ createMergeWebIdePullRequestsTool({
2028
+ webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
2029
+ })
2030
+ );
2031
+ }
2032
+ if (wantsTool("MysqlExecute", options, enableMysql) && options.sqlExecutionId && options.workdir) {
2033
+ Object.assign(
2034
+ tools,
2035
+ createMysqlExecuteTool({
2036
+ taskId,
2037
+ executionId: options.sqlExecutionId,
2038
+ workdir: options.workdir,
2039
+ upsertSqlExecution: (args) => cli.webideUpsertSqlExecution(args)
2040
+ })
2041
+ );
1982
2042
  }
1983
- return Buffer.concat(chunks);
1984
2043
  }
1985
- };
1986
-
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);
1991
- }
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
- };
2044
+ return tools;
2005
2045
  }
2046
+ function withPlanModeToolHint(prompt, mode) {
2047
+ if (mode !== "plan") {
2048
+ return prompt;
2049
+ }
2050
+ return `${prompt.trim()}
2006
2051
 
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");
2052
+ ${PLAN_MODE_ASK_QUESTION_HINT}`;
2023
2053
  }
2024
- function sha256File(absPath) {
2025
- const hash = createHash("sha256");
2026
- hash.update(fs.readFileSync(absPath));
2027
- return hash.digest("hex");
2054
+ function withWorkspaceBoundaryHint(prompt) {
2055
+ return `${prompt.trim()}
2056
+
2057
+ ${WORKSPACE_BOUNDARY_HINT}`;
2028
2058
  }
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}`);
2059
+
2060
+ // src/commands/connect/local-sqlite-agent-store.ts
2061
+ import {
2062
+ composeLocalAgentStore
2063
+ } from "@cursor/sdk";
2064
+ import { SqliteLocalAgentStore } from "@cursor/sdk/sqlite";
2065
+
2066
+ // src/commands/connect/remote-local-agent-store.ts
2067
+ function asCheckpointRef(value) {
2068
+ if (value === void 0) return void 0;
2069
+ if (value === null) return null;
2070
+ if (!value || typeof value !== "object") return null;
2071
+ const row = value;
2072
+ if (typeof row.rootBlobId !== "string" || !row.rootBlobId.trim()) {
2073
+ return null;
2036
2074
  }
2037
- return trimmed;
2075
+ return {
2076
+ schemaVersion: 1,
2077
+ rootBlobId: row.rootBlobId
2078
+ };
2038
2079
  }
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
- }
2080
+ function toApiCheckpointRef(ref) {
2081
+ if (ref === void 0) return void 0;
2082
+ if (ref === null) return null;
2083
+ return {
2084
+ schemaVersion: ref.schemaVersion,
2085
+ rootBlobId: ref.rootBlobId
2072
2086
  };
2073
- walk(root);
2074
- return [...byRelative.values()].sort(
2075
- (a, b) => a.relativePath.localeCompare(b.relativePath)
2076
- );
2077
- }
2078
- function packageSqlObjectKey(projectId, repo, relativePath) {
2079
- return `packages/${projectId}/${repo}/${relativePath}`;
2080
2087
  }
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
- }
2088
+ function fromApiAgent(row) {
2143
2089
  return {
2144
- scanned: localFiles.length,
2145
- uploaded,
2146
- skipped,
2147
- deleted
2090
+ agentId: row.agentId,
2091
+ cwd: row.cwd,
2092
+ status: row.status,
2093
+ activeRunId: row.activeRunId,
2094
+ name: row.name,
2095
+ createdAt: row.createdAt,
2096
+ updatedAt: row.updatedAt,
2097
+ latestCheckpoint: asCheckpointRef(row.latestCheckpoint) ?? null,
2098
+ sdkMetadata: row.sdkMetadata ?? void 0
2148
2099
  };
2149
2100
  }
2150
-
2151
- // src/commands/connect/tools/package-sql-tool.ts
2152
- function asString4(value) {
2153
- return typeof value === "string" ? value.trim() : "";
2101
+ function fromApiRun(row) {
2102
+ return {
2103
+ runId: row.runId,
2104
+ requestId: row.requestId,
2105
+ agentId: row.agentId,
2106
+ turnNumber: row.turnNumber,
2107
+ status: row.status,
2108
+ model: row.model ?? null,
2109
+ result: row.result,
2110
+ error: row.error,
2111
+ usageRef: row.usageRef,
2112
+ usage: row.usage ?? null,
2113
+ createdAt: row.createdAt,
2114
+ updatedAt: row.updatedAt,
2115
+ startedAt: row.startedAt,
2116
+ endedAt: row.endedAt,
2117
+ startCheckpointRef: asCheckpointRef(row.startCheckpointRef) ?? null,
2118
+ latestCheckpointRef: asCheckpointRef(row.latestCheckpointRef) ?? null
2119
+ };
2154
2120
  }
2155
- function resolveLocalSqlDir(workdir, localDirArg) {
2156
- if (!isAbsolute2(localDirArg)) {
2157
- return { ok: false, error: "localDir \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
2121
+ var unsupportedCheckpoints = {
2122
+ async get() {
2123
+ return null;
2124
+ },
2125
+ async create() {
2126
+ },
2127
+ async update() {
2128
+ },
2129
+ async delete() {
2130
+ },
2131
+ async list() {
2132
+ return { items: [] };
2158
2133
  }
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" };
2134
+ };
2135
+ var unsupportedRunEvents = {
2136
+ async append() {
2137
+ throw new Error("runEvents \u4EC5\u5B58\u672C\u673A SQLite");
2138
+ },
2139
+ async list() {
2140
+ return { items: [] };
2141
+ },
2142
+ async delete() {
2164
2143
  }
2165
- const fsPath = toFsPath(targetAbs);
2166
- if (!existsSync4(fsPath)) {
2167
- return { ok: false, error: `\u76EE\u5F55\u4E0D\u5B58\u5728: ${localDirArg}` };
2144
+ };
2145
+ function createRemoteLocalAgentStore(cfg2) {
2146
+ const api = createApmApiClient(cfg2);
2147
+ async function upsertAgent(agent) {
2148
+ const row = await api.cli.cursorStoreUpsertAgent({
2149
+ agentId: agent.agentId,
2150
+ cwd: agent.cwd,
2151
+ status: agent.status,
2152
+ activeRunId: agent.activeRunId ?? null,
2153
+ name: agent.name ?? null,
2154
+ latestCheckpoint: toApiCheckpointRef(agent.latestCheckpoint) ?? null,
2155
+ sdkMetadata: agent.sdkMetadata ?? null,
2156
+ createdAt: agent.createdAt,
2157
+ updatedAt: agent.updatedAt
2158
+ });
2159
+ return fromApiAgent(row);
2168
2160
  }
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}` };
2161
+ async function upsertRun(run) {
2162
+ const row = await api.cli.cursorStoreUpsertRun({
2163
+ runId: run.runId,
2164
+ agentId: run.agentId,
2165
+ requestId: run.requestId ?? null,
2166
+ turnNumber: run.turnNumber,
2167
+ status: run.status,
2168
+ model: run.model ?? null,
2169
+ result: run.result ?? null,
2170
+ error: run.error ?? null,
2171
+ usageRef: run.usageRef ?? null,
2172
+ usage: run.usage ?? null,
2173
+ startCheckpointRef: toApiCheckpointRef(run.startCheckpointRef) ?? null,
2174
+ latestCheckpointRef: toApiCheckpointRef(run.latestCheckpointRef) ?? null,
2175
+ createdAt: run.createdAt,
2176
+ updatedAt: run.updatedAt,
2177
+ startedAt: run.startedAt ?? null,
2178
+ endedAt: run.endedAt ?? null
2179
+ });
2180
+ return fromApiRun(row);
2175
2181
  }
2176
- return { ok: true, absPath: targetAbs };
2177
- }
2178
- function createUploadPackageSqlTool(options) {
2179
- const { cfg: cfg2, workdir, projectId } = options;
2180
2182
  return {
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"]
2183
+ agents: {
2184
+ async get(input) {
2185
+ const row = await api.cli.cursorStoreGetAgent({
2186
+ agentId: input.agentId
2187
+ });
2188
+ return row ? fromApiAgent(row) : null;
2196
2189
  },
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
- };
2190
+ async create(input) {
2191
+ return upsertAgent(input.agent);
2192
+ },
2193
+ async update(input) {
2194
+ return upsertAgent(input.agent);
2195
+ },
2196
+ async delete(input) {
2197
+ const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
2198
+ const cwd = input.filter.cwd?.trim();
2199
+ if (agentIds && agentIds.length > 0) {
2200
+ await api.cli.cursorStoreDeleteAgents({ agentIds, cwd });
2201
+ return;
2205
2202
  }
2206
- if (!localDirRaw) {
2207
- return {
2208
- content: [{ type: "text", text: "localDir \u4E0D\u80FD\u4E3A\u7A7A" }],
2209
- isError: true
2210
- };
2203
+ if (cwd) {
2204
+ await api.cli.cursorStoreDeleteAgents({ cwd });
2205
+ return;
2211
2206
  }
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
- };
2207
+ let cursor;
2208
+ for (; ; ) {
2209
+ const page = await api.cli.cursorStoreListAgents({
2210
+ cursor,
2211
+ limit: 200
2212
+ });
2213
+ if (page.items.length === 0) break;
2214
+ await api.cli.cursorStoreDeleteAgents({
2215
+ agentIds: page.items.map((item) => item.agentId)
2216
+ });
2217
+ if (!page.nextCursor) break;
2218
+ cursor = page.nextCursor;
2221
2219
  }
2222
- const resolved = resolveLocalSqlDir(workdir, localDirRaw);
2223
- if (!resolved.ok) {
2224
- return {
2225
- content: [{ type: "text", text: resolved.error }],
2226
- isError: true
2227
- };
2220
+ },
2221
+ async list(input) {
2222
+ const page = await api.cli.cursorStoreListAgents({
2223
+ cursor: input?.filter?.cursor,
2224
+ limit: input?.filter?.limit,
2225
+ cwd: input?.filter?.cwd
2226
+ });
2227
+ const result = {
2228
+ items: page.items.map(fromApiAgent),
2229
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {}
2230
+ };
2231
+ return result;
2232
+ }
2233
+ },
2234
+ runs: {
2235
+ async get(input) {
2236
+ const page = await api.cli.cursorStoreListRuns({
2237
+ runIds: [input.runId],
2238
+ limit: 1
2239
+ });
2240
+ const row = page.items[0];
2241
+ return row ? fromApiRun(row) : null;
2242
+ },
2243
+ async create(input) {
2244
+ return upsertRun(input.run);
2245
+ },
2246
+ async update(input) {
2247
+ return upsertRun(input.run);
2248
+ },
2249
+ async delete(input) {
2250
+ const agentIds = input.filter.agentIds?.map((id) => id.trim()).filter(Boolean);
2251
+ const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
2252
+ if (agentIds && agentIds.length > 0 || runIds && runIds.length > 0) {
2253
+ await api.cli.cursorStoreDeleteRuns({ agentIds, runIds });
2254
+ return;
2228
2255
  }
2229
- try {
2230
- const result = await syncPackageSqlToMinio({
2231
- cfg: cfg2,
2232
- projectId,
2233
- repo,
2234
- localDir: resolved.absPath
2256
+ let cursor;
2257
+ for (; ; ) {
2258
+ const page = await api.cli.cursorStoreListRuns({
2259
+ cursor,
2260
+ limit: 200
2235
2261
  });
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
- };
2262
+ if (page.items.length === 0) break;
2263
+ await api.cli.cursorStoreDeleteRuns({
2264
+ runIds: page.items.map((item) => item.runId)
2265
+ });
2266
+ if (!page.nextCursor) break;
2267
+ cursor = page.nextCursor;
2243
2268
  }
2269
+ },
2270
+ async list(input) {
2271
+ const page = await api.cli.cursorStoreListRuns({
2272
+ agentIds: input?.filter?.agentIds ? [...input.filter.agentIds] : void 0,
2273
+ runIds: input?.filter?.runIds ? [...input.filter.runIds] : void 0,
2274
+ cursor: input?.filter?.cursor,
2275
+ limit: input?.filter?.limit
2276
+ });
2277
+ const result = {
2278
+ items: page.items.map(fromApiRun),
2279
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {}
2280
+ };
2281
+ return result;
2244
2282
  }
2245
- }
2283
+ },
2284
+ checkpoints: unsupportedCheckpoints,
2285
+ runEvents: unsupportedRunEvents
2246
2286
  };
2247
2287
  }
2248
2288
 
2249
- // src/commands/connect/tools/package-status-tool.ts
2250
- function createSetPackageFailedTool(options) {
2251
- const { packageId, setFailed } = options;
2252
- return {
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
- }
2283
- }
2284
- }
2285
- };
2286
- }
2287
-
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);
2299
- }
2300
- return legacyDefault;
2301
- }
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
- }) : {}
2315
- };
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
- })
2325
- );
2326
- }
2327
- if (wantsTool("UpsertWebIdePlan", options, enablePlan)) {
2328
- Object.assign(
2329
- tools,
2330
- createUpsertWebIdePlanTool({
2331
- webideUpsertPlan: (args) => cli.webideUpsertPlan({ taskId, ...args })
2332
- })
2333
- );
2334
- }
2335
- if (wantsTool("UpsertWebIdeChecklist", options, enablePlan)) {
2336
- Object.assign(
2337
- tools,
2338
- createUpsertWebIdeChecklistTool({
2339
- webideUpsertChecklist: (args) => cli.webideUpsertChecklist({ taskId, ...args })
2340
- })
2341
- );
2342
- }
2343
- if (wantsTool("GetWebIdeChecklist", options, enablePlan)) {
2344
- Object.assign(
2345
- tools,
2346
- createGetWebIdeChecklistTool({
2347
- boundTaskId: taskId,
2348
- webideGetChecklist: (args) => cli.webideGetChecklist(args)
2349
- })
2350
- );
2351
- }
2352
- if (wantsTool("UpsertWebIdeTestCases", options, enablePlan)) {
2353
- Object.assign(
2354
- tools,
2355
- createUpsertWebIdeTestCasesTool({
2356
- webideReplaceTestCases: (args) => cli.webideReplaceTestCases({ taskId, ...args })
2357
- })
2358
- );
2359
- }
2360
- if (wantsTool("MergeWebIdePullRequests", options, enablePlan)) {
2361
- Object.assign(
2362
- tools,
2363
- createMergeWebIdePullRequestsTool({
2364
- webideMergePullRequests: () => cli.webideMergePullRequests({ taskId })
2365
- })
2366
- );
2367
- }
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
- );
2378
- }
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
- })
2406
- );
2407
- }
2408
- }
2409
- return tools;
2410
- }
2411
- function withPlanModeToolHint(prompt, mode) {
2412
- if (mode !== "plan") {
2413
- return prompt;
2414
- }
2415
- return `${prompt.trim()}
2416
-
2417
- ${PLAN_MODE_ASK_QUESTION_HINT}`;
2418
- }
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}`
2443
- }
2444
- }
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;
2462
- }
2463
- return {
2464
- schemaVersion: 1,
2465
- rootBlobId: row.rootBlobId
2466
- };
2467
- }
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
- };
2475
- }
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
2289
+ // src/commands/connect/local-sqlite-agent-store.ts
2290
+ var sqliteByKey = /* @__PURE__ */ new Map();
2291
+ async function openSqliteLocalAgentStore(workspaceRef) {
2292
+ const cwd = workspaceRef.trim();
2293
+ if (!cwd) throw new Error("openSqliteLocalAgentStore: workspaceRef \u4E0D\u80FD\u4E3A\u7A7A");
2294
+ const existing = sqliteByKey.get(cwd);
2295
+ if (existing) return existing;
2296
+ const pending2 = SqliteLocalAgentStore.open({ workspaceRef: cwd }).then(
2297
+ (store) => {
2298
+ console.log(
2299
+ `[apm] Cursor store sqlite ready cwd=${cwd} stateRoot=${store.stateRoot}`
3103
2300
  );
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);
2301
+ return store;
3161
2302
  }
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
- `;
2303
+ );
2304
+ sqliteByKey.set(cwd, pending2);
2305
+ return pending2;
3180
2306
  }
3181
- function readRevision(apmDir) {
3182
- const path3 = toFsPath(join4(apmDir, REVISION_FILE));
3183
- if (!existsSync5(path3)) return null;
2307
+ function jsonSafePayload(value) {
2308
+ if (value == null) return null;
3184
2309
  try {
3185
- const parsed = JSON.parse(readFileSync4(path3, "utf8"));
3186
- if (parsed?.version === 1 && parsed.revision?.trim()) {
3187
- return parsed.revision.trim();
3188
- }
2310
+ return JSON.parse(JSON.stringify(value));
3189
2311
  } catch {
3190
2312
  return null;
3191
2313
  }
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
2314
  }
3202
- async function syncWebIdeAgentFiles(workdir, cfg2) {
2315
+ function mirrorRunEventsForVisualization(cfg2, local) {
3203
2316
  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
2317
  return {
3241
- revision,
3242
- wrote: true,
3243
- ruleCount: bundle.rules?.length ?? 0
2318
+ async append(input) {
2319
+ const row = await local.append(input);
2320
+ try {
2321
+ await api.cli.cursorStoreAppendRunEvents({
2322
+ events: [
2323
+ {
2324
+ runId: input.runId,
2325
+ eventType: input.eventType,
2326
+ payload: jsonSafePayload(input.payload),
2327
+ payloadRef: input.payloadRef ?? null,
2328
+ idempotencyKey: input.idempotencyKey ?? `${input.runId}:${row.seq}`,
2329
+ createdAt: row.createdAt
2330
+ }
2331
+ ]
2332
+ });
2333
+ } catch (err) {
2334
+ console.warn(
2335
+ `[apm] runEvents \u53EF\u89C6\u5316\u540C\u6B65\u8DF3\u8FC7 runId=${input.runId} seq=${row.seq}:`,
2336
+ err instanceof Error ? err.message : err
2337
+ );
2338
+ }
2339
+ return row;
2340
+ },
2341
+ list: (input) => local.list(input),
2342
+ async delete(input) {
2343
+ await local.delete(input);
2344
+ const runIds = input.filter.runIds?.map((id) => id.trim()).filter(Boolean);
2345
+ if (!runIds?.length) return;
2346
+ try {
2347
+ await api.cli.cursorStoreDeleteRunEvents({ runIds });
2348
+ } catch (err) {
2349
+ console.warn(
2350
+ `[apm] runEvents \u53EF\u89C6\u5316\u6E05\u7406\u8DF3\u8FC7:`,
2351
+ err instanceof Error ? err.message : err
2352
+ );
2353
+ }
2354
+ }
3244
2355
  };
3245
2356
  }
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
- }
2357
+ async function createApmCursorAgentStore(cfg2, workspaceRef) {
2358
+ const cwd = workspaceRef.trim();
2359
+ const [sqlite, remote] = await Promise.all([
2360
+ openSqliteLocalAgentStore(cwd),
2361
+ Promise.resolve(createRemoteLocalAgentStore(cfg2))
2362
+ ]);
2363
+ return composeLocalAgentStore({
2364
+ agents: remote.agents,
2365
+ runs: remote.runs,
2366
+ checkpoints: sqlite.checkpoints,
2367
+ runEvents: mirrorRunEventsForVisualization(cfg2, sqlite.runEvents)
2368
+ });
3321
2369
  }
3322
- function listLocalDocumentPaths(apmRoot, projectId) {
3323
- const root = projectDocumentsDir(apmRoot, projectId);
3324
- if (!existsSync6(root)) {
3325
- return [];
3326
- }
3327
- const paths = [];
3328
- const walk = (dir) => {
3329
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
3330
- const abs = join5(dir, entry.name);
3331
- if (entry.isDirectory()) {
3332
- walk(abs);
3333
- continue;
3334
- }
3335
- if (entry.isFile() && entry.name === MANIFEST_FILE) {
3336
- continue;
3337
- }
3338
- const rel = relative4(root, abs).split(sep).join("/");
3339
- paths.push(rel);
3340
- }
3341
- };
3342
- walk(root);
3343
- return paths.sort();
2370
+
2371
+ // src/commands/connect/core/worker/run-steer.ts
2372
+ var impl = null;
2373
+ var pending = null;
2374
+ function bindRunSteer(fn) {
2375
+ impl = fn;
2376
+ if (!pending) return;
2377
+ const text = pending;
2378
+ pending = null;
2379
+ void fn(text);
3344
2380
  }
3345
- function diffManifestPaths(remote, local) {
3346
- const remoteMap = new Map(
3347
- (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3348
- );
3349
- const localMap = new Map(
3350
- (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3351
- );
3352
- const download = [];
3353
- for (const [path3, hash] of remoteMap) {
3354
- if (localMap.get(path3) !== hash) {
3355
- download.push(path3);
3356
- }
3357
- }
3358
- const deleteLocal = [];
3359
- for (const path3 of localMap.keys()) {
3360
- if (!remoteMap.has(path3)) {
3361
- deleteLocal.push(path3);
3362
- }
2381
+ function clearRunSteer() {
2382
+ impl = null;
2383
+ pending = null;
2384
+ }
2385
+ function deliverRunSteer(text) {
2386
+ const trimmed = text.trim();
2387
+ if (!trimmed) return;
2388
+ if (impl) {
2389
+ void impl(trimmed);
2390
+ return;
3363
2391
  }
3364
- return { download, deleteLocal };
2392
+ pending = trimmed;
3365
2393
  }
3366
- async function resolveProjectIdForSync(api, workdirPath, options) {
3367
- const explicit = options?.projectId?.trim();
3368
- if (explicit) {
3369
- return { projectId: explicit, diagnostic: null };
2394
+
2395
+ // src/commands/connect/cursor-agent.ts
2396
+ var noopRemoteLogSync = {
2397
+ schedule(_session) {
2398
+ },
2399
+ async flush(_session) {
3370
2400
  }
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
- };
2401
+ };
2402
+ function formatCursorRunFailure(runId, options) {
2403
+ const details = [
2404
+ options?.statusError?.trim(),
2405
+ options?.resultText?.trim()
2406
+ ].filter((value, index, arr) => {
2407
+ if (!value) return false;
2408
+ return arr.indexOf(value) === index;
2409
+ });
2410
+ if (details.length === 0) {
2411
+ return `Cursor run \u5931\u8D25: ${runId}`;
3397
2412
  }
2413
+ return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
3398
2414
  }
3399
- async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
3400
- const empty = {
3401
- synced: false,
3402
- projectId: null,
3403
- downloaded: 0,
3404
- deleted: 0
2415
+ async function obtainAgent(ctx) {
2416
+ const store = await createApmCursorAgentStore(ctx.cfg, ctx.cwd);
2417
+ const agentOptions = {
2418
+ apiKey: ctx.apiKey,
2419
+ model: { id: ctx.model || "default" },
2420
+ local: {
2421
+ cwd: ctx.cwd,
2422
+ store,
2423
+ ...ctx.customTools ? { customTools: ctx.customTools } : {},
2424
+ ...ctx.enableSandbox ? {
2425
+ sandboxOptions: { enabled: true },
2426
+ // 只加载项目级设置,避免 ~/.cursor/mcp.json 里需 OAuth/审批的 MCP 在无头沙箱被拒
2427
+ settingSources: ["project"]
2428
+ } : {}
2429
+ },
2430
+ ...ctx.mode ? { mode: ctx.mode } : {}
3405
2431
  };
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)
2432
+ const savedAgentId = ctx.resumeAgentId?.trim();
2433
+ if (savedAgentId) {
2434
+ try {
2435
+ const agent = await Agent.resume(savedAgentId, agentOptions);
2436
+ console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
2437
+ return { agent, resumed: true };
2438
+ } catch (err) {
2439
+ console.warn(
2440
+ `[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
2441
+ err instanceof Error ? err.message : err
3448
2442
  );
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;
2443
+ ctx.onInvalidatePersistedAgentId?.();
3512
2444
  }
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
2445
  }
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
2446
  try {
3546
- fs2.rmSync(dir, { recursive: true, force: true });
2447
+ const agent = await Agent.create(agentOptions);
2448
+ return { agent, resumed: false };
3547
2449
  } 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
- );
2450
+ if (ctx.enableSandbox && err instanceof Error && /sandbox/i.test(err.message)) {
2451
+ console.warn("[apm] sandbox \u4E0D\u53EF\u7528\uFF0C\u56DE\u9000\u4E3A\u65E0 sandbox:", err.message);
2452
+ return obtainAgent({ ...ctx, enableSandbox: false });
2453
+ }
2454
+ throw err;
3552
2455
  }
3553
2456
  }
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}`);
2457
+ async function runCursorAgent(cfg2, ctx, options) {
2458
+ const signal = options.signal;
2459
+ if (signal?.aborted) {
2460
+ throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
3558
2461
  }
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}`);
2462
+ const apiKey = ctx.apiKey.trim();
2463
+ if (!apiKey) {
2464
+ throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
3562
2465
  }
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 } : {}
2466
+ const workdir = resolveWorkdirPath(ctx.workdir);
2467
+ const customTools = createCursorCustomTools(cfg2, {
2468
+ onAskQuestion: options.onAskQuestion,
2469
+ appendMessageContent: options.appendMessageContent,
2470
+ enableAppendMessage: options.enableAppendMessage,
2471
+ enableAskQuestion: options.enableAskQuestion,
2472
+ askQuestionExecute: options.askQuestionExecute,
2473
+ enableWebIdePlanTools: options.enableWebIdePlanTools,
2474
+ enableMysqlTools: options.enableMysqlTools,
2475
+ sqlExecutionId: options.sqlExecutionId,
2476
+ toolNames: options.toolNames,
2477
+ taskId: options.taskId,
2478
+ workdir
3585
2479
  });
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;
2480
+ const enableSandbox = Boolean(options.enableSandbox);
2481
+ let prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
2482
+ prompt = withWorkspaceBoundaryHint(prompt);
3592
2483
  console.log(
3593
- `[apm] webide-package target=${msg2.target} packageId=${packageId} projectId=${projectId} baseBranch=${msg2.baseBranch}`
2484
+ `[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} cwd=${workdir} sandbox=${enableSandbox}`
3594
2485
  );
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}`;
2486
+ const { agent, resumed } = await obtainAgent({
2487
+ cfg: cfg2,
2488
+ apiKey,
2489
+ model: ctx.model,
2490
+ cwd: workdir,
2491
+ user: ctx.user,
2492
+ mode: ctx.mode,
2493
+ resumeAgentId: ctx.resumeAgentId,
2494
+ customTools,
2495
+ enableSandbox,
2496
+ onInvalidatePersistedAgentId: options.onInvalidatePersistedAgentId
2497
+ });
2498
+ const invalidateAgentMapping = () => {
2499
+ options.onInvalidatePersistedAgentId?.();
2500
+ };
2501
+ const eventSession = options.eventSession ?? new EventSession(prompt);
2502
+ const syncRemoteLog = options.createRemoteLogSync ? options.createRemoteLogSync(agent.agentId) : noopRemoteLogSync;
2503
+ let activeRun;
2504
+ const abortRun = () => {
2505
+ if (!activeRun?.supports("cancel")) return;
2506
+ void activeRun.cancel().catch(() => void 0);
2507
+ };
2508
+ signal?.addEventListener("abort", abortRun, { once: true });
2509
+ try {
2510
+ const run = await agent.send(prompt, {
2511
+ ...ctx.mode ? { mode: ctx.mode } : {},
2512
+ local: {
2513
+ ...options.forceSend ? { force: true } : {},
2514
+ customTools
3689
2515
  }
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"
2516
+ });
2517
+ activeRun = run;
2518
+ console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
2519
+ bindRunSteer(async (text) => {
2520
+ const steer = run.steer;
2521
+ if (!steer) {
2522
+ console.warn(`[apm] \u5F53\u524D run \u4E0D\u652F\u6301 steer runId=${run.id}`);
2523
+ return;
2524
+ }
2525
+ try {
2526
+ const outcome = await steer(text);
2527
+ console.log(`[apm] steer outcome=${outcome} runId=${run.id}`);
2528
+ } catch (err) {
2529
+ console.error(
2530
+ `[apm] steer \u5931\u8D25 runId=${run.id}:`,
2531
+ err instanceof Error ? err.message : err
2532
+ );
2533
+ }
2534
+ });
2535
+ await options.onRunStarted?.({ agentId: agent.agentId, runId: run.id });
2536
+ let lastRunErrorStatus;
2537
+ for await (const event of run.stream()) {
2538
+ if (signal?.aborted) {
2539
+ abortRun();
2540
+ throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
2541
+ }
2542
+ if (event.type === "status" && event.status === "ERROR") {
2543
+ const message = event.message?.trim();
2544
+ if (message) {
2545
+ lastRunErrorStatus = message;
2546
+ console.error(
2547
+ `[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
3745
2548
  );
3746
- logSyncRef.current?.schedule(eventSession);
3747
- await logSyncRef.current?.markRun(runId, "running");
3748
- await logSyncRef.current?.flush(eventSession);
3749
2549
  }
3750
2550
  }
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
2551
+ options.onStreamEvent?.(event);
2552
+ eventSession.addEvent(event);
2553
+ syncRemoteLog.schedule(eventSession);
2554
+ }
2555
+ await syncRemoteLog.flush(eventSession);
2556
+ const result = await run.wait();
2557
+ if (result.usage) {
2558
+ eventSession.addRunUsage(result.usage, {
2559
+ durationMs: result.durationMs
3764
2560
  });
3765
- await logSyncRef.current?.markRun(
3766
- outcome.runId,
3767
- "error",
3768
- detail,
3769
- tokenUsage
2561
+ await syncRemoteLog.flush(eventSession);
2562
+ console.log(
2563
+ `[apm] Cursor usage total=${result.usage.totalTokens} in=${result.usage.inputTokens} out=${result.usage.outputTokens} cache=${result.usage.cacheReadTokens}/${result.usage.cacheWriteTokens}`
3770
2564
  );
3771
- if (!failedByTool) {
3772
- await updatePackageStatus(cfg2, packageId, "FAILED", { error: detail });
3773
- }
3774
- return;
3775
2565
  }
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"
2566
+ if (result.status === "error") {
2567
+ const failureMessage = formatCursorRunFailure(result.id, {
2568
+ statusError: lastRunErrorStatus,
2569
+ resultText: result.result
3785
2570
  });
3786
- return;
2571
+ console.error(`[apm] ${failureMessage}`);
2572
+ if (resumed) {
2573
+ invalidateAgentMapping();
2574
+ }
2575
+ throw new Error(failureMessage);
3787
2576
  }
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;
2577
+ if (result.status === "cancelled") {
2578
+ throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
3799
2579
  }
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
- );
2580
+ console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
2581
+ const artifacts = await agent.listArtifacts().catch(() => []);
2582
+ const artifactDocuments = [];
2583
+ for (const artifact of artifacts) {
2584
+ try {
2585
+ const content = (await agent.downloadArtifact(artifact.path)).toString(
2586
+ "utf8"
2587
+ );
2588
+ artifactDocuments.push({ path: artifact.path, content });
2589
+ } catch (err) {
2590
+ console.warn(
2591
+ `[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
2592
+ err instanceof Error ? err.message : err
2593
+ );
2594
+ }
2595
+ }
2596
+ return {
2597
+ runId: result.id,
2598
+ agentId: agent.agentId,
2599
+ status: result.status,
2600
+ result: result.result,
2601
+ durationMs: result.durationMs,
2602
+ usage: result.usage,
2603
+ modelId: result.model?.id?.trim() || ctx.model?.trim() || void 0,
2604
+ assistantText: eventSession.getAssistantText(),
2605
+ createPlan: eventSession.getCreatePlanContent(),
2606
+ artifacts,
2607
+ artifactDocuments
2608
+ };
3812
2609
  } 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 });
2610
+ if (err instanceof CursorAgentError) {
2611
+ if (resumed) {
2612
+ invalidateAgentMapping();
3822
2613
  }
3823
- } catch (statusErr) {
3824
- console.error(
3825
- "[apm] \u5199\u5165\u6253\u5305 FAILED \u5931\u8D25:",
3826
- statusErr instanceof Error ? statusErr.message : statusErr
2614
+ throw new Error(
2615
+ `Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
3827
2616
  );
3828
2617
  }
2618
+ throw err;
2619
+ } finally {
2620
+ clearRunSteer();
2621
+ signal?.removeEventListener("abort", abortRun);
2622
+ await agent[Symbol.asyncDispose]();
2623
+ }
2624
+ }
2625
+
2626
+ // src/commands/connect/webide-agent-registry.ts
2627
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
2628
+ import { dirname as dirname3, resolve as resolve6 } from "node:path";
2629
+ function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
2630
+ return resolve6(workdir, ".apm", "webide", taskId, fileName);
2631
+ }
2632
+ var DESIGN_AGENT_FILE = "design-agent.json";
2633
+ function readRegistry(path) {
2634
+ if (!existsSync5(path)) {
2635
+ return {};
2636
+ }
2637
+ try {
2638
+ const parsed = JSON.parse(readFileSync5(path, "utf8"));
2639
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2640
+ const raw = parsed;
2641
+ const state = {};
2642
+ if (typeof raw.agentId === "string" && raw.agentId.trim()) {
2643
+ state.agentId = raw.agentId.trim();
2644
+ }
2645
+ if (typeof raw.taskId === "string" && raw.taskId.trim()) {
2646
+ state.taskId = raw.taskId.trim();
2647
+ }
2648
+ if (typeof raw.messageId === "string" && raw.messageId.trim()) {
2649
+ state.messageId = raw.messageId.trim();
2650
+ }
2651
+ if (typeof raw.action === "string" && raw.action.trim()) {
2652
+ state.action = raw.action.trim();
2653
+ }
2654
+ if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
2655
+ state.status = raw.status;
2656
+ }
2657
+ if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
2658
+ state.updatedAt = raw.updatedAt.trim();
2659
+ }
2660
+ return state;
2661
+ }
2662
+ } catch {
2663
+ }
2664
+ return {};
2665
+ }
2666
+ function writeRegistry(path, registry) {
2667
+ mkdirSync4(dirname3(path), { recursive: true });
2668
+ writeFileSync3(path, `${JSON.stringify(registry, null, 2)}
2669
+ `, "utf8");
2670
+ }
2671
+ function syncWebIdeTaskState(workdir, taskId, patch) {
2672
+ const trimmedTaskId = taskId.trim();
2673
+ if (!trimmedTaskId) return;
2674
+ const path = registryPath(workdir, trimmedTaskId);
2675
+ const current = readRegistry(path);
2676
+ const next = {
2677
+ ...current,
2678
+ taskId: trimmedTaskId,
2679
+ updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
2680
+ };
2681
+ if (patch.agentId !== void 0) {
2682
+ const agentId = patch.agentId.trim();
2683
+ if (agentId) next.agentId = agentId;
2684
+ else delete next.agentId;
2685
+ }
2686
+ if (patch.messageId !== void 0) {
2687
+ const messageId = patch.messageId.trim();
2688
+ if (messageId) next.messageId = messageId;
2689
+ else delete next.messageId;
2690
+ }
2691
+ if (patch.action !== void 0) {
2692
+ const action = patch.action.trim();
2693
+ if (action) next.action = action;
2694
+ else delete next.action;
2695
+ }
2696
+ if (patch.status !== void 0) {
2697
+ next.status = patch.status;
3829
2698
  }
2699
+ writeRegistry(path, next);
2700
+ }
2701
+ function loadWebIdeAgentId(workdir, taskId) {
2702
+ return readRegistry(registryPath(workdir, taskId)).agentId;
2703
+ }
2704
+ function saveWebIdeAgentId(workdir, taskId, agentId) {
2705
+ syncWebIdeTaskState(workdir, taskId, { agentId });
2706
+ }
2707
+ function clearWebIdeAgentId(workdir, taskId) {
2708
+ const path = registryPath(workdir, taskId);
2709
+ if (!existsSync5(path)) return;
2710
+ syncWebIdeTaskState(workdir, taskId, { agentId: "" });
2711
+ }
2712
+ function loadWebIdeDesignAgentId(workdir, taskId) {
2713
+ return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
2714
+ }
2715
+ function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
2716
+ const trimmedTaskId = taskId.trim();
2717
+ if (!trimmedTaskId) return;
2718
+ const path = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
2719
+ const current = readRegistry(path);
2720
+ writeRegistry(path, {
2721
+ ...current,
2722
+ taskId: trimmedTaskId,
2723
+ agentId: agentId.trim() || void 0,
2724
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2725
+ });
2726
+ }
2727
+ function clearWebIdeDesignAgentId(workdir, taskId) {
2728
+ const path = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
2729
+ if (!existsSync5(path)) return;
2730
+ const current = readRegistry(path);
2731
+ const next = { ...current };
2732
+ delete next.agentId;
2733
+ writeRegistry(path, {
2734
+ ...next,
2735
+ taskId: taskId.trim(),
2736
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2737
+ });
3830
2738
  }
3831
2739
 
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;
2740
+ // src/commands/connect/webide-ask-question.ts
2741
+ function asString4(value) {
2742
+ return typeof value === "string" ? value.trim() : "";
2743
+ }
2744
+ function parseQuestions(args) {
2745
+ const title = asString4(args.title) || void 0;
2746
+ const raw = args.questions;
2747
+ if (!Array.isArray(raw) || raw.length === 0) {
2748
+ throw new Error("AskQuestion \u7F3A\u5C11 questions");
2749
+ }
2750
+ const questions = [];
2751
+ for (const item of raw) {
2752
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
2753
+ const row = item;
2754
+ const id = asString4(row.id);
2755
+ const prompt = asString4(row.prompt);
2756
+ const optionsRaw = row.options;
2757
+ if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
2758
+ const options = [];
2759
+ for (const opt of optionsRaw) {
2760
+ if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
2761
+ const o = opt;
2762
+ const oid = asString4(o.id);
2763
+ const label = asString4(o.label);
2764
+ if (oid && label) options.push({ id: oid, label });
2765
+ }
2766
+ if (options.length < 2) {
2767
+ throw new Error(`AskQuestion \u95EE\u9898 ${id} \u81F3\u5C11\u9700\u8981 2 \u4E2A\u9009\u9879`);
3843
2768
  }
2769
+ questions.push({ id, prompt, options });
3844
2770
  }
3845
- return DEFAULT_REPLY;
3846
- }
3847
-
3848
- // src/commands/connect/core/worker/handlers/start-project.ts
3849
- async function updateStatus(cfg2, messageId, status) {
3850
- const api = createApmApiClient(cfg2);
3851
- await api.cli.webideUpdateMessageStatus({ id: messageId, status });
2771
+ if (questions.length === 0) {
2772
+ throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
2773
+ }
2774
+ return { title, questions };
3852
2775
  }
3853
- async function setError(cfg2, messageId, error) {
2776
+ async function fetchAssumptionAnswersAndContinueAssessment(options) {
2777
+ const { cfg: cfg2, taskId, questionIds, title } = options;
3854
2778
  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;
2779
+ const list = await api.cli.webideListAssumptions({ taskId });
2780
+ const byId = new Map(list.assumptions.map((a) => [a.questionId, a]));
2781
+ const ids = questionIds && questionIds.length > 0 ? questionIds : list.assumptions.filter((a) => a.status === "RESOLVED").map((a) => a.questionId);
2782
+ if (ids.length === 0) {
2783
+ throw new Error("\u6CA1\u6709\u5DF2\u786E\u8BA4\u7684\u5047\u8BBE");
2784
+ }
2785
+ const answers = [];
2786
+ for (const id of ids) {
2787
+ const row = byId.get(id);
2788
+ if (!row || row.status !== "RESOLVED") {
2789
+ throw new Error(`\u5047\u8BBE\u5C1A\u672A\u5168\u90E8\u786E\u8BA4: ${id}`);
2790
+ }
2791
+ answers.push({
2792
+ questionId: id,
2793
+ prompt: row.prompt,
2794
+ selectedOptionId: row.selectedOptionId,
2795
+ customAnswer: row.customAnswer,
2796
+ resolution: row.resolution
2797
+ });
2798
+ }
3861
2799
  console.log(
3862
- `[apm] webide-start-project target=${msg2.target} restart=${msg2.restart} taskId=${taskId} messageId=${messageId}`
2800
+ `[apm] \u5047\u8BBE\u7ED3\u679C\u5DF2\u62C9\u53D6 taskId=${taskId} count=${answers.length}`
3863
2801
  );
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);
2802
+ return JSON.stringify({ title, answers }, null, 2);
2803
+ }
2804
+ function createWebIdeAskQuestionExecute(options) {
2805
+ const { cfg: cfg2, taskId, onAwaitingAssumptions } = options;
2806
+ return async (args) => {
2807
+ const parsed = parseQuestions(args);
3882
2808
  const api = createApmApiClient(cfg2);
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
- }
2809
+ console.log(
2810
+ `[apm] AskQuestion \u521B\u5EFA\u5F85\u786E\u8BA4\u9879 taskId=${taskId} count=${parsed.questions.length}`
3927
2811
  );
3928
- const tokenUsage = outcome.usage != null ? {
3929
- modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
3930
- inputTokens: outcome.usage.inputTokens,
3931
- outputTokens: outcome.usage.outputTokens,
3932
- cacheReadTokens: outcome.usage.cacheReadTokens,
3933
- cacheWriteTokens: outcome.usage.cacheWriteTokens,
3934
- totalTokens: outcome.usage.totalTokens,
3935
- ...outcome.usage.reasoningTokens != null ? { reasoningTokens: outcome.usage.reasoningTokens } : {}
3936
- } : void 0;
3937
- if (outcome.status === "error") {
3938
- const detail = formatCursorRunFailure(outcome.runId, {
3939
- resultText: outcome.result
3940
- });
3941
- await logSyncRef.current?.markRun(
3942
- outcome.runId,
3943
- "error",
3944
- detail,
3945
- tokenUsage
3946
- );
3947
- await setError(cfg2, messageId, detail);
3948
- return;
3949
- }
3950
- if (outcome.status === "cancelled" || signal.aborted) {
3951
- await logSyncRef.current?.markRun(
3952
- outcome.runId,
3953
- "cancelled",
3954
- "\u5DF2\u53D6\u6D88",
3955
- tokenUsage
3956
- );
3957
- await updateStatus(cfg2, messageId, "CANCELLED");
3958
- return;
3959
- }
3960
- const fallback = resolveMessageReplyFallback({
3961
- assistantText: outcome.assistantText,
3962
- result: outcome.result
2812
+ await api.cli.webideReplaceAssumptions({
2813
+ taskId,
2814
+ title: parsed.title,
2815
+ questions: parsed.questions
3963
2816
  });
3964
- await api.cli.webideEnsureMessageContent({
3965
- id: messageId,
3966
- content: fallback
2817
+ await api.cli.webideRunAction({
2818
+ taskId,
2819
+ action: "await-assumptions"
3967
2820
  });
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
- );
2821
+ onAwaitingAssumptions?.();
2822
+ return [
2823
+ "\u5047\u8BBE\u5DF2\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u6D41\u8FDB\u5165\u300C\u5F85\u786E\u8BA4\u5047\u8BBE\u300D\u3002",
2824
+ "\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",
2825
+ "\u7528\u6237\u786E\u8BA4\u540E\uFF0C\u4E0B\u4E00\u8F6E\u4F1A\u5E26\u4E0A\u7B54\u6848\u5E76\u7EE7\u7EED\u8BC4\u4F30\u3002"
2826
+ ].join("");
2827
+ };
2828
+ }
2829
+
2830
+ // src/utils/minio.ts
2831
+ import * as Minio from "minio";
2832
+ var MinioClient = class {
2833
+ inner;
2834
+ constructor(opts) {
2835
+ const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
2836
+ this.inner = new Minio.Client({
2837
+ endPoint,
2838
+ port: opts.port,
2839
+ useSSL: opts.useSSL,
2840
+ accessKey: opts.accessKey,
2841
+ secretKey: opts.secretKey
2842
+ });
2843
+ }
2844
+ async ensureBucket(bucket) {
2845
+ const exists = await this.inner.bucketExists(bucket);
2846
+ if (!exists) {
2847
+ await this.inner.makeBucket(bucket);
3988
2848
  }
3989
2849
  }
3990
- }
3991
-
3992
- // src/commands/connect/webide-agent-registry.ts
3993
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
3994
- import { dirname as dirname3, resolve as resolve6 } from "node:path";
3995
- function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
3996
- return resolve6(workdir, ".apm", "webide", taskId, fileName);
3997
- }
3998
- var DESIGN_AGENT_FILE = "design-agent.json";
3999
- function readRegistry(path3) {
4000
- if (!existsSync7(path3)) {
4001
- return {};
2850
+ async putObject(bucket, objectKey, body, meta) {
2851
+ await this.inner.putObject(bucket, objectKey, body, body.length, meta);
4002
2852
  }
4003
- try {
4004
- const parsed = JSON.parse(readFileSync6(path3, "utf8"));
4005
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4006
- const raw = parsed;
4007
- const state = {};
4008
- if (typeof raw.agentId === "string" && raw.agentId.trim()) {
4009
- state.agentId = raw.agentId.trim();
4010
- }
4011
- if (typeof raw.taskId === "string" && raw.taskId.trim()) {
4012
- state.taskId = raw.taskId.trim();
4013
- }
4014
- if (typeof raw.messageId === "string" && raw.messageId.trim()) {
4015
- state.messageId = raw.messageId.trim();
4016
- }
4017
- if (typeof raw.action === "string" && raw.action.trim()) {
4018
- state.action = raw.action.trim();
4019
- }
4020
- if (raw.status === "TYPING" || raw.status === "SUCCESS" || raw.status === "FAILED" || raw.status === "CANCELLED") {
4021
- state.status = raw.status;
4022
- }
4023
- if (typeof raw.updatedAt === "string" && raw.updatedAt.trim()) {
4024
- state.updatedAt = raw.updatedAt.trim();
4025
- }
4026
- return state;
4027
- }
4028
- } catch {
2853
+ /** 大文件:从本地路径流式上传,避免整包读入内存 */
2854
+ async fPutObject(bucket, objectKey, filePath, meta) {
2855
+ await this.inner.fPutObject(bucket, objectKey, filePath, meta);
4029
2856
  }
4030
- return {};
4031
- }
4032
- function writeRegistry(path3, registry) {
4033
- mkdirSync5(dirname3(path3), { recursive: true });
4034
- writeFileSync5(path3, `${JSON.stringify(registry, null, 2)}
4035
- `, "utf8");
4036
- }
4037
- function syncWebIdeTaskState(workdir, taskId, patch) {
4038
- const trimmedTaskId = taskId.trim();
4039
- if (!trimmedTaskId) return;
4040
- const path3 = registryPath(workdir, trimmedTaskId);
4041
- const current = readRegistry(path3);
4042
- const next = {
4043
- ...current,
4044
- taskId: trimmedTaskId,
4045
- updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
4046
- };
4047
- if (patch.agentId !== void 0) {
4048
- const agentId = patch.agentId.trim();
4049
- if (agentId) next.agentId = agentId;
4050
- else delete next.agentId;
2857
+ async listObjects(bucket, prefix) {
2858
+ const objects = [];
2859
+ const stream = this.inner.listObjectsV2(bucket, prefix, true);
2860
+ await new Promise((resolve7, reject) => {
2861
+ stream.on("data", (obj) => {
2862
+ if (!obj.name) return;
2863
+ objects.push({
2864
+ name: obj.name,
2865
+ size: typeof obj.size === "number" ? obj.size : 0,
2866
+ lastModified: obj.lastModified ?? null,
2867
+ ...typeof obj.etag === "string" ? { etag: obj.etag } : {}
2868
+ });
2869
+ });
2870
+ stream.on("error", reject);
2871
+ stream.on("end", () => resolve7());
2872
+ });
2873
+ return objects;
4051
2874
  }
4052
- if (patch.messageId !== void 0) {
4053
- const messageId = patch.messageId.trim();
4054
- if (messageId) next.messageId = messageId;
4055
- else delete next.messageId;
2875
+ async statObject(bucket, objectKey) {
2876
+ const stat = await this.inner.statObject(bucket, objectKey);
2877
+ return {
2878
+ size: typeof stat.size === "number" ? stat.size : 0,
2879
+ metaData: stat.metaData
2880
+ };
4056
2881
  }
4057
- if (patch.action !== void 0) {
4058
- const action = patch.action.trim();
4059
- if (action) next.action = action;
4060
- else delete next.action;
2882
+ async removeObject(bucket, objectKey) {
2883
+ await this.inner.removeObject(bucket, objectKey);
4061
2884
  }
4062
- if (patch.status !== void 0) {
4063
- next.status = patch.status;
2885
+ async getObjectBuffer(bucket, objectKey) {
2886
+ const stream = await this.inner.getObject(bucket, objectKey);
2887
+ const chunks = [];
2888
+ for await (const chunk of stream) {
2889
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
2890
+ }
2891
+ return Buffer.concat(chunks);
4064
2892
  }
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 });
2893
+ };
2894
+
2895
+ // src/commands/connect/apm-log-minio.ts
2896
+ async function fetchApmLogStorage(cfg2) {
2897
+ const api = createApmApiClient(cfg2);
2898
+ return api.cli.getApmLogStorage(void 0);
4072
2899
  }
4073
- function clearWebIdeAgentId(workdir, taskId) {
4074
- const path3 = registryPath(workdir, taskId);
4075
- if (!existsSync7(path3)) return;
4076
- syncWebIdeTaskState(workdir, taskId, { agentId: "" });
2900
+ async function createApmLogMinioClient(cfg2) {
2901
+ const storage = await fetchApmLogStorage(cfg2);
2902
+ return {
2903
+ client: new MinioClient({
2904
+ endPoint: storage.endpoint,
2905
+ port: storage.port,
2906
+ useSSL: storage.useSsl,
2907
+ accessKey: storage.accessKey,
2908
+ secretKey: storage.secretKey
2909
+ }),
2910
+ bucket: storage.bucket,
2911
+ ...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
2912
+ };
4077
2913
  }
4078
- function loadWebIdeDesignAgentId(workdir, taskId) {
4079
- return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
2914
+
2915
+ // src/commands/connect/webide-message-log.ts
2916
+ var SYNC_INTERVAL_MS = 2e3;
2917
+ function webIdeEventsObjectPrefix(taskId, messageId) {
2918
+ return `events/webide/${taskId}/${messageId}/`;
4080
2919
  }
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
- });
2920
+ async function putDirtyEvents(minio, bucket, prefix, events) {
2921
+ for (const event of events) {
2922
+ const key = `${prefix}${event.index}.json`;
2923
+ const body = Buffer.from(
2924
+ JSON.stringify({
2925
+ index: event.index,
2926
+ type: event.type,
2927
+ data: event.data
2928
+ }),
2929
+ "utf8"
2930
+ );
2931
+ await minio.putObject(bucket, key, body, {
2932
+ "Content-Type": "application/json"
2933
+ });
2934
+ }
4092
2935
  }
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()
2936
+ async function upsertLogHeader(cfg2, ctx, patch, objectPrefix) {
2937
+ const api = createApmApiClient(cfg2);
2938
+ await api.cli.webideUpsertMessageLog({
2939
+ messageId: ctx.messageId,
2940
+ agentId: ctx.agentId,
2941
+ objectPrefix,
2942
+ ...patch
4103
2943
  });
4104
2944
  }
4105
-
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
2945
+ function createThrottledWebIdeMessageLogSync(cfg2, ctx, onError, options) {
2946
+ let lastRunAt = 0;
2947
+ let timer;
2948
+ let latestSession;
2949
+ let syncChain = Promise.resolve();
2950
+ let minio;
2951
+ let bucket = "";
2952
+ let firstLogNotified = false;
2953
+ const resolvePrefix = () => options?.objectPrefixOverride?.trim() || webIdeEventsObjectPrefix(ctx.taskId, ctx.messageId);
2954
+ const ensureMinio = async () => {
2955
+ if (minio) return;
2956
+ const created = await createApmLogMinioClient(cfg2);
2957
+ minio = created.client;
2958
+ bucket = created.bucket;
2959
+ await minio.ensureBucket(bucket);
2960
+ };
2961
+ const notifyFirstLog = async () => {
2962
+ if (firstLogNotified) return;
2963
+ firstLogNotified = true;
2964
+ try {
2965
+ await options?.onFirstLogSynced?.();
2966
+ } catch (err) {
2967
+ firstLogNotified = false;
2968
+ onError(err);
2969
+ }
2970
+ };
2971
+ const syncDirtyEventsOnce = async (session) => {
2972
+ const events = session.getDirtyEvents();
2973
+ if (events.length === 0) return;
2974
+ lastRunAt = Date.now();
2975
+ try {
2976
+ await ensureMinio();
2977
+ const prefix = resolvePrefix();
2978
+ await putDirtyEvents(minio, bucket, prefix, events);
2979
+ await upsertLogHeader(
2980
+ cfg2,
2981
+ ctx,
2982
+ {
2983
+ eventCount: session.getEventCount()
2984
+ },
2985
+ prefix
2986
+ );
2987
+ session.clearDirtyIfUnchanged(events);
2988
+ await notifyFirstLog();
2989
+ } catch (err) {
2990
+ onError(err);
2991
+ }
2992
+ };
2993
+ const drainDirtyEvents = async (session) => {
2994
+ while (session.getDirtyEvents().length > 0) {
2995
+ await syncDirtyEventsOnce(session);
2996
+ }
2997
+ };
2998
+ const enqueueSync = (session) => {
2999
+ syncChain = syncChain.then(() => drainDirtyEvents(session));
3000
+ };
3001
+ return {
3002
+ async markRun(runId, runStatus, lastError, tokenUsage) {
3003
+ try {
3004
+ await upsertLogHeader(
3005
+ cfg2,
3006
+ ctx,
3007
+ {
3008
+ runId,
3009
+ runStatus,
3010
+ lastError: lastError ?? null,
3011
+ ...tokenUsage ? { tokenUsage } : {}
3012
+ },
3013
+ resolvePrefix()
3014
+ );
3015
+ } catch (err) {
3016
+ onError(err);
3017
+ }
3018
+ },
3019
+ schedule(session) {
3020
+ latestSession = session;
3021
+ const now = Date.now();
3022
+ const elapsed = now - lastRunAt;
3023
+ if (elapsed >= SYNC_INTERVAL_MS) {
3024
+ if (timer) {
3025
+ clearTimeout(timer);
3026
+ timer = void 0;
3027
+ }
3028
+ enqueueSync(session);
3029
+ return;
4121
3030
  }
4122
- });
4123
- for (const run of page.items) {
4124
- runIds.push(run.runId);
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
- });
4137
- }
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
- );
3031
+ if (timer) return;
3032
+ timer = setTimeout(() => {
3033
+ timer = void 0;
3034
+ if (latestSession) enqueueSync(latestSession);
3035
+ }, SYNC_INTERVAL_MS - elapsed);
3036
+ },
3037
+ async flush(session) {
3038
+ if (timer) {
3039
+ clearTimeout(timer);
3040
+ timer = void 0;
3041
+ }
3042
+ await syncChain;
3043
+ await drainDirtyEvents(session);
4147
3044
  }
4148
- }
4149
- return true;
3045
+ };
4150
3046
  }
4151
- async function cleanWebIdeWorkspaceCache(taskId, workdir, cfg2) {
4152
- const trimmedTaskId = taskId.trim();
4153
- const trimmedWorkdir = workdir.trim();
4154
- if (!trimmedTaskId || !trimmedWorkdir) {
4155
- return;
4156
- }
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
- );
3047
+
3048
+ // src/commands/connect/ensure-message-reply.ts
3049
+ var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
3050
+ function resolveMessageReplyFallback(fallback) {
3051
+ for (const candidate of [
3052
+ fallback.assistantText,
3053
+ fallback.result,
3054
+ fallback.createPlan
3055
+ ]) {
3056
+ const trimmed = candidate?.trim();
3057
+ if (trimmed) {
3058
+ return trimmed;
4175
3059
  }
4176
- } else {
4177
- console.log(
4178
- `[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
4179
- );
4180
- }
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}`);
4187
3060
  }
3061
+ return DEFAULT_REPLY;
4188
3062
  }
4189
3063
 
4190
- // src/commands/connect/webide-ask-question.ts
4191
- function asString5(value) {
4192
- return typeof value === "string" ? value.trim() : "";
3064
+ // src/commands/sync-webide-agent.ts
3065
+ import {
3066
+ existsSync as existsSync6,
3067
+ mkdirSync as mkdirSync5,
3068
+ readdirSync as readdirSync3,
3069
+ readFileSync as readFileSync6,
3070
+ unlinkSync,
3071
+ writeFileSync as writeFileSync4
3072
+ } from "fs";
3073
+ import { join as join6 } from "path";
3074
+ var REVISION_FILE = ".webide-agent.json";
3075
+ var RULE_KEY = /^[a-z0-9_]+$/;
3076
+ function withTrailingNewline(text) {
3077
+ return text.endsWith("\n") ? text : `${text}
3078
+ `;
4193
3079
  }
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`);
3080
+ function readRevision(apmDir) {
3081
+ const path = toFsPath(join6(apmDir, REVISION_FILE));
3082
+ if (!existsSync6(path)) return null;
3083
+ try {
3084
+ const parsed = JSON.parse(readFileSync6(path, "utf8"));
3085
+ if (parsed?.version === 1 && parsed.revision?.trim()) {
3086
+ return parsed.revision.trim();
4218
3087
  }
4219
- questions.push({ id, prompt, options });
4220
- }
4221
- if (questions.length === 0) {
4222
- throw new Error("AskQuestion \u65E0\u6709\u6548\u95EE\u9898");
3088
+ } catch {
3089
+ return null;
4223
3090
  }
4224
- return { title, questions };
3091
+ return null;
4225
3092
  }
4226
- async function fetchAssumptionAnswersAndContinueAssessment(options) {
4227
- const { cfg: cfg2, taskId, questionIds, title } = options;
3093
+ function writeRevision(apmDir, revision) {
3094
+ writeFileSync4(
3095
+ toFsPath(join6(apmDir, REVISION_FILE)),
3096
+ `${JSON.stringify({ version: 1, revision }, null, 2)}
3097
+ `,
3098
+ "utf8"
3099
+ );
3100
+ }
3101
+ async function syncWebIdeAgentFiles(workdir, cfg2) {
4228
3102
  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");
3103
+ const bundle = await api.cli.webideAgentConfig();
3104
+ const revision = bundle.revision?.trim();
3105
+ if (!revision) {
3106
+ throw new Error("[apm] \u5E73\u53F0\u672A\u8FD4\u56DE WebIDE \u89C4\u5219\u7248\u672C");
4234
3107
  }
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
- });
3108
+ const apmDir = workspaceApmDir(workdir);
3109
+ mkdirSync5(toFsPath(apmDir), { recursive: true });
3110
+ if (readRevision(apmDir) === revision) {
3111
+ return { revision, wrote: false, ruleCount: bundle.rules?.length ?? 0 };
4248
3112
  }
4249
- console.log(
4250
- `[apm] \u5047\u8BBE\u7ED3\u679C\u5DF2\u62C9\u53D6 taskId=${taskId} count=${answers.length}`
3113
+ const rulesDir = join6(apmDir, "rules");
3114
+ mkdirSync5(toFsPath(rulesDir), { recursive: true });
3115
+ writeFileSync4(
3116
+ toFsPath(join6(apmDir, "AGENTS.md")),
3117
+ withTrailingNewline(bundle.guide ?? ""),
3118
+ "utf8"
4251
3119
  );
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}`
3120
+ const keep = /* @__PURE__ */ new Set();
3121
+ for (const rule of bundle.rules ?? []) {
3122
+ const key = rule.key?.trim() ?? "";
3123
+ if (!RULE_KEY.test(key)) {
3124
+ throw new Error(`[apm] \u975E\u6CD5\u89C4\u5219\u540D: ${key || "(\u7A7A)"}`);
3125
+ }
3126
+ keep.add(`${key}.md`);
3127
+ writeFileSync4(
3128
+ toFsPath(join6(rulesDir, `${key}.md`)),
3129
+ withTrailingNewline(rule.body ?? ""),
3130
+ "utf8"
4261
3131
  );
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("");
3132
+ }
3133
+ for (const name of readdirSync3(toFsPath(rulesDir))) {
3134
+ if (!name.endsWith(".md")) continue;
3135
+ if (keep.has(name)) continue;
3136
+ unlinkSync(toFsPath(join6(rulesDir, name)));
3137
+ }
3138
+ writeRevision(apmDir, revision);
3139
+ return {
3140
+ revision,
3141
+ wrote: true,
3142
+ ruleCount: bundle.rules?.length ?? 0
4277
3143
  };
4278
3144
  }
4279
3145
 
3146
+ // src/commands/init.ts
3147
+ async function ensureWorkspaceInitialized(workdir) {
3148
+ if (isWorkspaceApmInitialized(workdir)) {
3149
+ return { didInit: false };
3150
+ }
3151
+ console.log(
3152
+ `[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`
3153
+ );
3154
+ await ensureWorkspaceApmDirForInit(workdir);
3155
+ console.log(`[apm] \u5DF2\u521B\u5EFA\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF1A${workspaceApmDir(workdir)}`);
3156
+ return { didInit: true };
3157
+ }
3158
+
4280
3159
  // src/commands/connect/pre-step-cache.ts
4281
3160
  function taskWorkdirKey(taskId, workdir) {
4282
3161
  return `${taskId}\0${workdir}`;
@@ -4501,9 +3380,9 @@ async function ensureWebIdePullRequests(cfg2, taskId, workdir) {
4501
3380
  }
4502
3381
 
4503
3382
  // src/commands/sync-webide-attachments.ts
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";
3383
+ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3384
+ import { join as join7 } from "path";
3385
+ var MANIFEST_FILE = ".sync-manifest.json";
4507
3386
  async function downloadAttachment(cfg2, attachmentId) {
4508
3387
  const base = cfg2.baseUrl.trim().replace(/\/+$/, "");
4509
3388
  const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
@@ -4518,13 +3397,13 @@ async function downloadAttachment(cfg2, attachmentId) {
4518
3397
  return Buffer.from(await res.arrayBuffer());
4519
3398
  }
4520
3399
  function loadManifest(dir) {
4521
- const path3 = join6(dir, MANIFEST_FILE2);
4522
- if (!existsSync9(path3)) {
3400
+ const path = join7(dir, MANIFEST_FILE);
3401
+ if (!existsSync7(path)) {
4523
3402
  return { version: 1, attachments: {} };
4524
3403
  }
4525
3404
  try {
4526
3405
  const parsed = JSON.parse(
4527
- readFileSync7(path3, "utf8")
3406
+ readFileSync7(path, "utf8")
4528
3407
  );
4529
3408
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
4530
3409
  return parsed;
@@ -4534,15 +3413,15 @@ function loadManifest(dir) {
4534
3413
  return { version: 1, attachments: {} };
4535
3414
  }
4536
3415
  function saveManifest(dir, manifest) {
4537
- writeFileSync6(
4538
- join6(dir, MANIFEST_FILE2),
3416
+ writeFileSync5(
3417
+ join7(dir, MANIFEST_FILE),
4539
3418
  `${JSON.stringify(manifest, null, 2)}
4540
3419
  `,
4541
3420
  "utf8"
4542
3421
  );
4543
3422
  }
4544
3423
  function isAttachmentUpToDate(entry, item, dest) {
4545
- if (!entry || !existsSync9(dest)) return false;
3424
+ if (!entry || !existsSync7(dest)) return false;
4546
3425
  if (entry.name !== item.name) return false;
4547
3426
  const createdAt = item.createdAt ?? "";
4548
3427
  return entry.createdAt === createdAt;
@@ -4557,7 +3436,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
4557
3436
  const nextManifest = { version: 1, attachments: {} };
4558
3437
  const names = [];
4559
3438
  for (const item of attachments) {
4560
- const dest = join6(dir, item.name);
3439
+ const dest = join7(dir, item.name);
4561
3440
  const entry = manifest.attachments[item.id];
4562
3441
  const createdAt = item.createdAt ?? "";
4563
3442
  names.push(item.name);
@@ -4567,7 +3446,7 @@ async function syncAttachmentsToDirectory(cfg2, attachments, dir, logLabel) {
4567
3446
  continue;
4568
3447
  }
4569
3448
  const buffer = await downloadAttachment(cfg2, item.id);
4570
- writeFileSync6(dest, buffer);
3449
+ writeFileSync5(dest, buffer);
4571
3450
  nextManifest.attachments[item.id] = {
4572
3451
  name: item.name,
4573
3452
  createdAt
@@ -4588,9 +3467,9 @@ async function syncWebIdeAttachments(cfg2, taskId, workdir, attachments) {
4588
3467
  }
4589
3468
 
4590
3469
  // src/commands/sync-webide-design.ts
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";
3470
+ import { createHash } from "crypto";
3471
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
3472
+ import { join as join8 } from "path";
4594
3473
 
4595
3474
  // src/opc-shared-types.ts
4596
3475
  function webIdeDesignObjectKey(taskId, fileName) {
@@ -4606,17 +3485,17 @@ function webIdeDesignArtifactContentType(name) {
4606
3485
  }
4607
3486
 
4608
3487
  // src/commands/sync-webide-design.ts
4609
- var MANIFEST_FILE3 = ".sync-manifest.json";
3488
+ var MANIFEST_FILE2 = ".sync-manifest.json";
4610
3489
  function hashBuffer(buf) {
4611
- return createHash3("sha256").update(buf).digest("hex");
3490
+ return createHash("sha256").update(buf).digest("hex");
4612
3491
  }
4613
3492
  function loadManifest2(dir) {
4614
- const path3 = join7(dir, MANIFEST_FILE3);
4615
- if (!existsSync10(path3)) {
3493
+ const path = join8(dir, MANIFEST_FILE2);
3494
+ if (!existsSync8(path)) {
4616
3495
  return { version: 1, files: {} };
4617
3496
  }
4618
3497
  try {
4619
- const parsed = JSON.parse(readFileSync8(path3, "utf8"));
3498
+ const parsed = JSON.parse(readFileSync8(path, "utf8"));
4620
3499
  if (parsed?.version === 1 && parsed.files && typeof parsed.files === "object") {
4621
3500
  return parsed;
4622
3501
  }
@@ -4625,8 +3504,8 @@ function loadManifest2(dir) {
4625
3504
  return { version: 1, files: {} };
4626
3505
  }
4627
3506
  function saveManifest2(dir, manifest) {
4628
- writeFileSync7(
4629
- join7(dir, MANIFEST_FILE3),
3507
+ writeFileSync6(
3508
+ join8(dir, MANIFEST_FILE2),
4630
3509
  `${JSON.stringify(manifest, null, 2)}
4631
3510
  `,
4632
3511
  "utf8"
@@ -4654,13 +3533,13 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
4654
3533
  for (const art of artifacts) {
4655
3534
  const name = art.name?.trim();
4656
3535
  if (!name || !isWebIdeDesignArtifactName(name)) continue;
4657
- const localPath = join7(dir, name);
3536
+ const localPath = join8(dir, name);
4658
3537
  const entry = manifest.files[name];
4659
- if (entry?.contentHash === art.contentHash && existsSync10(localPath)) {
3538
+ if (entry?.contentHash === art.contentHash && existsSync8(localPath)) {
4660
3539
  continue;
4661
3540
  }
4662
3541
  const buf = await downloadDesignFile(cfg2, taskId, name);
4663
- writeFileSync7(localPath, buf);
3542
+ writeFileSync6(localPath, buf);
4664
3543
  manifest.files[name] = { contentHash: hashBuffer(buf) };
4665
3544
  synced.push(name);
4666
3545
  }
@@ -4669,12 +3548,12 @@ async function syncWebIdeDesign(cfg2, taskId, workdir, artifacts) {
4669
3548
  }
4670
3549
  function listLocalDesignArtifactFiles(taskId, workdir) {
4671
3550
  const dir = webideDesignDir(taskId, workdir);
4672
- if (!existsSync10(dir)) return [];
3551
+ if (!existsSync8(dir)) return [];
4673
3552
  const out = [];
4674
- for (const name of readdirSync5(dir)) {
3553
+ for (const name of readdirSync4(dir)) {
4675
3554
  if (!isWebIdeDesignArtifactName(name)) continue;
4676
- const absPath = join7(dir, name);
4677
- if (!statSync5(absPath).isFile()) continue;
3555
+ const absPath = join8(dir, name);
3556
+ if (!statSync4(absPath).isFile()) continue;
4678
3557
  const buf = readFileSync8(absPath);
4679
3558
  if (buf.length === 0) continue;
4680
3559
  out.push({ name, absPath, contentHash: hashBuffer(buf) });
@@ -4759,8 +3638,6 @@ function startActionForInbound(action) {
4759
3638
  case "request-develop":
4760
3639
  case "skip-plan":
4761
3640
  return "start-develop";
4762
- case "request-start-project":
4763
- return "start-project";
4764
3641
  case "request-execute-sql":
4765
3642
  return "start-execute-sql";
4766
3643
  case "request-bugfix":
@@ -4795,9 +3672,6 @@ function completeActionForInbound(action) {
4795
3672
  case "skip-plan":
4796
3673
  case "start-develop":
4797
3674
  return "complete-develop";
4798
- case "request-start-project":
4799
- case "start-project":
4800
- return "complete-project";
4801
3675
  case "request-execute-sql":
4802
3676
  case "start-execute-sql":
4803
3677
  return "complete-execute-sql";
@@ -4869,9 +3743,6 @@ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
4869
3743
  function shouldCommitAfterWebIdeMessage(action) {
4870
3744
  return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
4871
3745
  }
4872
- function isStartLocalServicesAction(action) {
4873
- return action === "enter-manual-test" || action === "skip-test" || action === "request-start-project";
4874
- }
4875
3746
  function isDesignAction(action) {
4876
3747
  return action === "request-design" || action === "request-revise-design";
4877
3748
  }
@@ -4882,11 +3753,11 @@ function syncLocalTaskStatus(workdir, taskId, msg2, status) {
4882
3753
  status
4883
3754
  });
4884
3755
  }
4885
- async function updateStatus2(cfg2, messageId, status) {
3756
+ async function updateStatus(cfg2, messageId, status) {
4886
3757
  const api = createApmApiClient(cfg2);
4887
3758
  await api.cli.webideUpdateMessageStatus({ id: messageId, status });
4888
3759
  }
4889
- async function setError2(cfg2, messageId, error) {
3760
+ async function setError(cfg2, messageId, error) {
4890
3761
  const api = createApmApiClient(cfg2);
4891
3762
  await api.cli.webideSetMessageError({ id: messageId, error });
4892
3763
  }
@@ -4905,7 +3776,7 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
4905
3776
  console.log(
4906
3777
  `[apm] webide-message action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} taskId=${taskId} messageId=${messageId}`
4907
3778
  );
4908
- await updateStatus2(cfg2, messageId, "TYPING");
3779
+ await updateStatus(cfg2, messageId, "TYPING");
4909
3780
  syncLocalTaskStatus(workdir, taskId, msg2, "TYPING");
4910
3781
  const eventSession = new EventSession(msg2.content);
4911
3782
  const prepAgentId = loadWebIdeAgentId(workdir, taskId) ?? "webide-cli-prep";
@@ -4996,10 +3867,13 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
4996
3867
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4997
3868
  await runPrepStep(
4998
3869
  "\u51C6\u5907\u7279\u6027\u5206\u652F",
4999
- async () => runTaskBranch(taskId, { cwd: workdir }),
3870
+ async () => {
3871
+ const projectId = await resolveProjectIdForTask(taskId);
3872
+ return runTaskBranch(taskId, projectId, { cwd: workdir });
3873
+ },
5000
3874
  (branchResult) => {
5001
3875
  markBranchDone(branchCacheKey, workdir);
5002
- return `kind=${branchResult.kind} branch=${branchResult.branch} baseline=${branchResult.baseline} repos=${branchResult.repos.length}`;
3876
+ return `kind=${branchResult.kind} branch=${branchResult.branch} projectId=${branchResult.projectId} repos=${branchResult.repos.length}`;
5003
3877
  }
5004
3878
  );
5005
3879
  } else {
@@ -5025,43 +3899,6 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
5025
3899
  }
5026
3900
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
5027
3901
  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
- );
5065
3902
  await runPrepStep(
5066
3903
  "\u540C\u6B65\u4EFB\u52A1\u9644\u4EF6",
5067
3904
  async () => {
@@ -5112,18 +3949,17 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
5112
3949
  "running"
5113
3950
  );
5114
3951
  await syncPrepLog();
5115
- const startLocalServices = isStartLocalServicesAction(effectiveAction);
5116
3952
  const designAction = isDesignAction(effectiveAction);
5117
3953
  const useNewAgent = msg2.useNewAgent === true;
5118
3954
  const toolNames = Array.isArray(msg2.tools) ? msg2.tools.map((t) => String(t).trim()).filter(Boolean) : [];
5119
- if (useNewAgent && !startLocalServices) {
3955
+ if (useNewAgent) {
5120
3956
  if (designAction) {
5121
3957
  clearWebIdeDesignAgentId(workdir, taskId);
5122
3958
  } else {
5123
3959
  clearWebIdeAgentId(workdir, taskId);
5124
3960
  }
5125
3961
  }
5126
- const savedAgentId = startLocalServices || useNewAgent ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
3962
+ const savedAgentId = useNewAgent ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
5127
3963
  const promptPayload = toolNames.includes("MysqlExecute") ? continuePayload ?? parseWebIdePromptPayload(msg2.content) : null;
5128
3964
  const sqlExecutionId = toolNames.includes("MysqlExecute") && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
5129
3965
  if (toolNames.includes("MysqlExecute") && !sqlExecutionId) {
@@ -5132,7 +3968,6 @@ async function handleWebIdeInboundMessage(cfg2, msg2, signal) {
5132
3968
  );
5133
3969
  }
5134
3970
  const persistAgentId = (agentId) => {
5135
- if (startLocalServices) return;
5136
3971
  if (designAction) {
5137
3972
  saveWebIdeDesignAgentId(workdir, taskId, agentId);
5138
3973
  } else {
@@ -5158,7 +3993,7 @@ ${assumptionAnswersText}` : msg2.content,
5158
3993
  forceSend: true,
5159
3994
  eventSession,
5160
3995
  appendMessageContent: (content) => appendContent(cfg2, messageId, content),
5161
- askQuestionExecute: !startLocalServices && toolNames.includes("AskQuestion") ? createWebIdeAskQuestionExecute({
3996
+ askQuestionExecute: toolNames.includes("AskQuestion") ? createWebIdeAskQuestionExecute({
5162
3997
  cfg: cfg2,
5163
3998
  taskId,
5164
3999
  onAwaitingAssumptions: () => {
@@ -5170,11 +4005,10 @@ ${assumptionAnswersText}` : msg2.content,
5170
4005
  enableAskQuestion: false,
5171
4006
  enableWebIdePlanTools: false,
5172
4007
  enableMysqlTools: false,
5173
- enablePtySessionMcp: startLocalServices || toolNames.includes("PtySession"),
5174
4008
  sqlExecutionId,
5175
4009
  toolNames,
5176
4010
  enableSandbox: false,
5177
- onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => {
4011
+ onInvalidatePersistedAgentId: () => {
5178
4012
  if (designAction) {
5179
4013
  clearWebIdeDesignAgentId(workdir, taskId);
5180
4014
  } else {
@@ -5227,9 +4061,7 @@ ${assumptionAnswersText}` : msg2.content,
5227
4061
  }
5228
4062
  }
5229
4063
  );
5230
- if (!startLocalServices) {
5231
- persistAgentId(outcome.agentId);
5232
- }
4064
+ persistAgentId(outcome.agentId);
5233
4065
  const tokenUsage = outcome.usage != null ? {
5234
4066
  modelId: outcome.modelId ?? (msg2.model?.trim() || void 0),
5235
4067
  inputTokens: outcome.usage.inputTokens,
@@ -5263,7 +4095,7 @@ ${assumptionAnswersText}` : msg2.content,
5263
4095
  );
5264
4096
  }
5265
4097
  }
5266
- await setError2(cfg2, messageId, detail);
4098
+ await setError(cfg2, messageId, detail);
5267
4099
  syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
5268
4100
  return;
5269
4101
  }
@@ -5288,7 +4120,7 @@ ${assumptionAnswersText}` : msg2.content,
5288
4120
  );
5289
4121
  }
5290
4122
  }
5291
- await updateStatus2(cfg2, messageId, "CANCELLED");
4123
+ await updateStatus(cfg2, messageId, "CANCELLED");
5292
4124
  syncLocalTaskStatus(workdir, taskId, msg2, "CANCELLED");
5293
4125
  return;
5294
4126
  }
@@ -5333,7 +4165,7 @@ ${assumptionAnswersText}` : msg2.content,
5333
4165
  detail,
5334
4166
  tokenUsage
5335
4167
  );
5336
- await setError2(cfg2, messageId, detail);
4168
+ await setError(cfg2, messageId, detail);
5337
4169
  syncLocalTaskStatus(workdir, taskId, msg2, "FAILED");
5338
4170
  return;
5339
4171
  }
@@ -5410,16 +4242,6 @@ ${assumptionAnswersText}` : msg2.content,
5410
4242
  }
5411
4243
  }
5412
4244
  }
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
- }
5423
4245
  await logSyncRef.current?.markRun(
5424
4246
  outcome.runId,
5425
4247
  "finished",
@@ -5434,21 +4256,11 @@ ${assumptionAnswersText}` : msg2.content,
5434
4256
  `[apm] \u672C\u8F6E\u505C\u5728\u5F85\u786E\u8BA4\u5047\u8BBE\uFF0C\u8DF3\u8FC7 complete action=${effectiveAction} taskId=${taskId}`
5435
4257
  );
5436
4258
  }
5437
- await updateStatus2(cfg2, messageId, "SUCCESS");
4259
+ await updateStatus(cfg2, messageId, "SUCCESS");
5438
4260
  syncLocalTaskStatus(workdir, taskId, msg2, "SUCCESS");
5439
4261
  console.log(
5440
4262
  `[apm] webide-message \u5B8C\u6210 action=${msg2.action}${previousAction ? ` previousAction=${previousAction}` : ""} messageId=${messageId} agentId=${outcome.agentId}`
5441
4263
  );
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
- }
5452
4264
  } catch (err) {
5453
4265
  const detail = err instanceof Error ? err.message : String(err);
5454
4266
  console.error(`[apm] webide-message \u5931\u8D25: ${detail}`);
@@ -5459,7 +4271,7 @@ ${assumptionAnswersText}` : msg2.content,
5459
4271
  clearWebIdeAgentId(workdir, taskId);
5460
4272
  }
5461
4273
  try {
5462
- await setError2(cfg2, messageId, detail);
4274
+ await setError(cfg2, messageId, detail);
5463
4275
  } catch (statusErr) {
5464
4276
  console.error(
5465
4277
  "[apm] \u5199\u5165 WebIDE FAILED \u5931\u8D25:",
@@ -5483,23 +4295,7 @@ ${assumptionAnswersText}` : msg2.content,
5483
4295
 
5484
4296
  // src/commands/connect/core/worker/handlers/index.ts
5485
4297
  async function dispatchWorkerJob(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
- }
4298
+ await handleWebIdeInboundMessage(cfg2, msg2, signal);
5503
4299
  }
5504
4300
 
5505
4301
  // src/commands/connect/core/worker/worker-script.ts