pi-cursor-bridge 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-bridge",
3
- "version": "5.6.0+codex.20260827111309",
3
+ "version": "5.6.1+codex.20260829070740",
4
4
  "description": "Evidence-backed Cursor Context Engine search and bounded Cursor Agent execution, including a UI-suppressed minimal runtime.",
5
5
  "author": {
6
6
  "name": "Vanyangyang"
package/README.md CHANGED
@@ -8,6 +8,6 @@ pi install npm:pi-cursor-bridge
8
8
 
9
9
  Restart Pi after installation. Initialize the current project by asking Pi to initialize Cursor Bridge for the absolute project path, then use it normally. The package includes the `cce-routing` and `cursor-delegate` Skills and registers the native Cursor Bridge MCP tools directly in Pi.
10
10
 
11
- This Pi package embeds Cursor Bridge 5.6.0. Cursor must be installed and signed in. Current end-to-end compatibility claims remain scoped to the environments documented in the main repository.
11
+ This Pi package embeds Cursor Bridge 5.6.1. Cursor must be installed and signed in. Current end-to-end compatibility claims remain scoped to the environments documented in the main repository.
12
12
 
13
13
  Full documentation: [English](https://github.com/Vanyangyang/cursor-bridge#readme) · [简体中文](https://github.com/Vanyangyang/cursor-bridge/blob/master/README.zh-CN.md)
@@ -10786,6 +10786,8 @@ function startMinimalWindowGuard(pid, options = {}) {
10786
10786
  "-EncodedCommand",
10787
10787
  encodePowerShell(script)
10788
10788
  ], { detached: !lifetime, stdio: "ignore", windowsHide: true });
10789
+ if (child && typeof child.once === "function") child.once("error", () => {
10790
+ });
10789
10791
  if (!lifetime && child && typeof child.unref === "function") child.unref();
10790
10792
  return {
10791
10793
  started: true,
@@ -11292,6 +11294,58 @@ async function waitForCdp(maxMs = 3e4, stepMs = 1e3) {
11292
11294
  }
11293
11295
  return false;
11294
11296
  }
11297
+ async function spawnDetachedSafely(spawnImpl, file, args, spawnOptions) {
11298
+ let child;
11299
+ try {
11300
+ child = spawnImpl(file, args, spawnOptions);
11301
+ } catch (error2) {
11302
+ return {
11303
+ ok: false,
11304
+ child: null,
11305
+ error: error2,
11306
+ errorCode: error2 && typeof error2 === "object" && error2.code != null ? String(error2.code) : null
11307
+ };
11308
+ }
11309
+ if (child && typeof child.once === "function") {
11310
+ const startup = await new Promise((resolvePromise) => {
11311
+ let settled = false;
11312
+ const finish = (result) => {
11313
+ if (settled) return;
11314
+ settled = true;
11315
+ child.off?.("spawn", onSpawn);
11316
+ child.off?.("error", onError);
11317
+ resolvePromise(result);
11318
+ };
11319
+ const onSpawn = () => finish({ ok: true });
11320
+ const onError = (error2) => finish({ ok: false, error: error2 });
11321
+ child.once("spawn", onSpawn);
11322
+ child.once("error", onError);
11323
+ if (Number.isInteger(child.pid) && child.pid > 0) queueMicrotask(onSpawn);
11324
+ });
11325
+ if (!startup.ok) {
11326
+ return {
11327
+ ok: false,
11328
+ child,
11329
+ error: startup.error,
11330
+ errorCode: startup.error && typeof startup.error === "object" && startup.error.code != null ? String(startup.error.code) : null
11331
+ };
11332
+ }
11333
+ child.once("error", () => {
11334
+ });
11335
+ }
11336
+ if (child && typeof child.unref === "function") child.unref();
11337
+ return { ok: true, child };
11338
+ }
11339
+ function attachedPresentation(runtimeMode, port) {
11340
+ if (runtimeMode !== "minimal") return null;
11341
+ return {
11342
+ supported: true,
11343
+ applied: false,
11344
+ action: "hide",
11345
+ port,
11346
+ reason: "attached lifecycle cannot start the PowerShell window guard under the current process policy"
11347
+ };
11348
+ }
11295
11349
  async function ensureCursorRunningLocal(options = {}) {
11296
11350
  const waitMs = Number(options.waitMs || 3e4);
11297
11351
  const runtimeMode = options.runtimeMode || "normal";
@@ -11303,12 +11357,14 @@ async function ensureCursorRunningLocal(options = {}) {
11303
11357
  const projectPath = Object.hasOwn(options, "projectPath") ? options.projectPath ? resolve4(String(options.projectPath)) : null : resolveProjectPath();
11304
11358
  const listCdpPageTargetsImpl = options.listCdpPageTargetsImpl || listCdpPageTargets;
11305
11359
  const spawnImpl = options.spawnImpl || spawn2;
11360
+ const allowSpawn = options.allowSpawn !== false;
11361
+ const allowProcessControl = options.allowProcessControl !== false;
11306
11362
  if (await cdpUpImpl()) {
11307
11363
  const isCursor = await cdpIsCursorImpl();
11308
11364
  if (isCursor) {
11309
- const cursorPid2 = findCursorPidByPort(CDP_PORT);
11310
- const windowGuard2 = effectiveRuntimeMode === "minimal" && cursorPid2 ? startMinimalWindowGuard(cursorPid2) : null;
11311
- const presentation2 = effectiveRuntimeMode === "minimal" ? setCursorWindowPresentation({ action: "hide", port: CDP_PORT, pid: cursorPid2 }) : null;
11365
+ const cursorPid2 = allowProcessControl ? findCursorPidByPort(CDP_PORT) : null;
11366
+ const windowGuard2 = allowProcessControl && effectiveRuntimeMode === "minimal" && cursorPid2 ? startMinimalWindowGuard(cursorPid2) : null;
11367
+ const presentation2 = effectiveRuntimeMode === "minimal" ? allowProcessControl ? setCursorWindowPresentation({ action: "hide", port: CDP_PORT, pid: cursorPid2 }) : attachedPresentation(effectiveRuntimeMode, CDP_PORT) : null;
11312
11368
  const currentTargets = await listCdpPageTargetsImpl();
11313
11369
  const projectKey = normalizeProjectKey(projectPath);
11314
11370
  let targetId2 = projectKey ? PROJECT_TARGETS.get(projectKey) || null : currentTargets[0] && currentTargets[0].id || null;
@@ -11337,6 +11393,22 @@ async function ensureCursorRunningLocal(options = {}) {
11337
11393
  }
11338
11394
  }
11339
11395
  if (projectPath && existsSync2(projectPath) && !targetId2) {
11396
+ if (!allowSpawn) {
11397
+ return {
11398
+ ok: false,
11399
+ status: "workspace-not-ready",
11400
+ port: CDP_PORT,
11401
+ cursorPid: cursorPid2,
11402
+ runtimeMode: effectiveRuntimeMode,
11403
+ projectPath,
11404
+ presentation: presentation2,
11405
+ windowGuard: windowGuard2,
11406
+ needsAction: "open_workspace_in_cursor",
11407
+ retryable: true,
11408
+ nextStep: `Open workspace ${projectPath} in the existing Cursor Agents Window, then retry the same operation.`,
11409
+ message: `CCE connected to Cursor, but the current workspace target for ${projectPath} is not ready and the current lifecycle cannot open a new window.`
11410
+ };
11411
+ }
11340
11412
  const cursorExecutable2 = findCursorExeDetailsImpl();
11341
11413
  const exe2 = cursorExecutable2 && cursorExecutable2.path;
11342
11414
  if (!exe2) {
@@ -11356,12 +11428,28 @@ async function ensureCursorRunningLocal(options = {}) {
11356
11428
  };
11357
11429
  }
11358
11430
  const beforeTargetIds = new Set(currentTargets.map((target2) => target2.id));
11359
- const opener = spawnImpl(exe2, ["--new-window", projectPath], {
11431
+ const opened = await spawnDetachedSafely(spawnImpl, exe2, ["--new-window", projectPath], {
11360
11432
  detached: true,
11361
11433
  stdio: "ignore",
11362
11434
  windowsHide: effectiveRuntimeMode === "minimal"
11363
11435
  });
11364
- opener.unref();
11436
+ if (!opened.ok) {
11437
+ return {
11438
+ ok: false,
11439
+ status: "spawn-blocked",
11440
+ port: CDP_PORT,
11441
+ cursorPid: cursorPid2,
11442
+ runtimeMode: effectiveRuntimeMode,
11443
+ projectPath,
11444
+ presentation: presentation2,
11445
+ windowGuard: windowGuard2,
11446
+ errorCode: opened.errorCode,
11447
+ needsAction: "open_workspace_in_cursor",
11448
+ retryable: true,
11449
+ nextStep: `Open workspace ${projectPath} in Cursor, then retry the same operation.`,
11450
+ message: `Cursor Bridge could not open a new workspace window: ${opened.error instanceof Error ? opened.error.message : String(opened.error)}`
11451
+ };
11452
+ }
11365
11453
  workspaceAction = "opened-new-window";
11366
11454
  const openedTarget2 = await waitForNewCdpTarget(beforeTargetIds, 12e3, projectPath, listCdpPageTargetsImpl);
11367
11455
  if (!openedTarget2) {
@@ -11410,6 +11498,21 @@ async function ensureCursorRunningLocal(options = {}) {
11410
11498
  message: `CCE cannot connect to Cursor because required local port ${CDP_PORT} is occupied by another program.`
11411
11499
  };
11412
11500
  }
11501
+ if (!allowSpawn) {
11502
+ return {
11503
+ ok: false,
11504
+ status: "external-launch-required",
11505
+ port: CDP_PORT,
11506
+ cursorPid: null,
11507
+ runtimeMode: effectiveRuntimeMode,
11508
+ projectPath,
11509
+ presentation: attachedPresentation(effectiveRuntimeMode, CDP_PORT),
11510
+ needsAction: "launch_cursor_with_cdp",
11511
+ retryable: true,
11512
+ nextStep: `Start Cursor with its remote debugging connection on port ${CDP_PORT}, open ${projectPath || "the target workspace"}, then retry the same operation.`,
11513
+ message: `Cursor is not reachable on the configured CDP port ${CDP_PORT}, and the current lifecycle policy cannot launch it.`
11514
+ };
11515
+ }
11413
11516
  if (cursorRunningImpl()) {
11414
11517
  const cursorExecutable2 = findCursorExeDetailsImpl();
11415
11518
  return {
@@ -11447,13 +11550,29 @@ async function ensureCursorRunningLocal(options = {}) {
11447
11550
  );
11448
11551
  }
11449
11552
  if (projectPath && existsSync2(projectPath)) args.push(projectPath);
11450
- const child = spawnImpl(exe, args, {
11553
+ const launched = await spawnDetachedSafely(spawnImpl, exe, args, {
11451
11554
  detached: true,
11452
11555
  stdio: "ignore",
11453
11556
  windowsHide: effectiveRuntimeMode === "minimal"
11454
11557
  });
11455
- child.unref();
11456
- const startupWindowGuard = effectiveRuntimeMode === "minimal" ? startMinimalWindowGuard(child.pid) : null;
11558
+ if (!launched.ok) {
11559
+ return {
11560
+ ok: false,
11561
+ status: "spawn-blocked",
11562
+ exe,
11563
+ port: CDP_PORT,
11564
+ cursorPid: null,
11565
+ runtimeMode: effectiveRuntimeMode,
11566
+ projectPath,
11567
+ errorCode: launched.errorCode,
11568
+ needsAction: "launch_cursor_manually",
11569
+ retryable: true,
11570
+ nextStep: `Start Cursor with its remote debugging connection on port ${CDP_PORT}, then retry the same operation.`,
11571
+ message: `Cursor Bridge could not launch Cursor: ${launched.error instanceof Error ? launched.error.message : String(launched.error)}`
11572
+ };
11573
+ }
11574
+ const child = launched.child;
11575
+ const startupWindowGuard = effectiveRuntimeMode === "minimal" ? startMinimalWindowGuard(child && child.pid) : null;
11457
11576
  const up = await waitForCdp(waitMs);
11458
11577
  if (!up) {
11459
11578
  return {
@@ -11643,6 +11762,67 @@ function whichNode() {
11643
11762
  }
11644
11763
  return process.execPath;
11645
11764
  }
11765
+ function wmiReturnValueFromError(error2) {
11766
+ const message = error2 instanceof Error ? error2.message : String(error2 || "");
11767
+ const match = message.match(/Win32_Process\.Create failed:\s*(\d+)/i);
11768
+ return match ? Number(match[1]) : null;
11769
+ }
11770
+ function classifyOutsideJobSpawnError(error2) {
11771
+ const code = error2 && typeof error2 === "object" && error2.code != null ? String(error2.code) : null;
11772
+ const returnValue = wmiReturnValueFromError(error2);
11773
+ if (code === "EPERM" || code === "EACCES") {
11774
+ return {
11775
+ errorKind: "policy-blocked",
11776
+ degradedReason: "spawn-policy-blocked",
11777
+ errorCode: code,
11778
+ returnValue,
11779
+ canAttachFallback: true
11780
+ };
11781
+ }
11782
+ if (returnValue === 2 || returnValue === 3) {
11783
+ return {
11784
+ errorKind: "policy-blocked",
11785
+ degradedReason: "wmi-access-denied",
11786
+ errorCode: returnValue,
11787
+ returnValue,
11788
+ canAttachFallback: true
11789
+ };
11790
+ }
11791
+ if (returnValue === 8) {
11792
+ return {
11793
+ errorKind: "wmi-unknown",
11794
+ degradedReason: "wmi-unknown-8",
11795
+ errorCode: returnValue,
11796
+ returnValue,
11797
+ canAttachFallback: true
11798
+ };
11799
+ }
11800
+ if (returnValue === 9 || returnValue === 21) {
11801
+ return {
11802
+ errorKind: "configuration",
11803
+ degradedReason: null,
11804
+ errorCode: returnValue,
11805
+ returnValue,
11806
+ canAttachFallback: false
11807
+ };
11808
+ }
11809
+ if (code === "ETIMEDOUT" || error2 && typeof error2 === "object" && error2.killed === true) {
11810
+ return {
11811
+ errorKind: "timeout",
11812
+ degradedReason: "spawn-timeout",
11813
+ errorCode: code || "ETIMEDOUT",
11814
+ returnValue,
11815
+ canAttachFallback: true
11816
+ };
11817
+ }
11818
+ return {
11819
+ errorKind: "unknown",
11820
+ degradedReason: null,
11821
+ errorCode: code,
11822
+ returnValue,
11823
+ canAttachFallback: false
11824
+ };
11825
+ }
11646
11826
  function spawnOutsideJob(file, args = [], options = {}) {
11647
11827
  const cwd = options.cwd || process.cwd();
11648
11828
  const env = options.env || process.env;
@@ -11663,7 +11843,8 @@ function spawnOutsideJob(file, args = [], options = {}) {
11663
11843
  throw new Error("forced WMI failure for tests");
11664
11844
  }
11665
11845
  const ps = buildHiddenWmiCreateScript(commandLine, cwd);
11666
- const out = execFileSync3("powershell.exe", [
11846
+ const run = options.execFileSyncImpl || execFileSync3;
11847
+ const out = run("powershell.exe", [
11667
11848
  "-NoProfile",
11668
11849
  "-NonInteractive",
11669
11850
  "-ExecutionPolicy",
@@ -11683,10 +11864,14 @@ function spawnOutsideJob(file, args = [], options = {}) {
11683
11864
  return { ok: true, method: "wmi-win32-process-create", pid, commandLine };
11684
11865
  } catch (wmiError) {
11685
11866
  const wmiMsg = wmiError instanceof Error ? wmiError.message : String(wmiError);
11867
+ const classification = classifyOutsideJobSpawnError(wmiError);
11868
+ const stderr = wmiError && typeof wmiError === "object" && wmiError.stderr != null ? String(wmiError.stderr).trim() || null : null;
11686
11869
  return {
11687
11870
  ok: false,
11688
11871
  method: "failed",
11689
11872
  commandLine,
11873
+ ...classification,
11874
+ stderr,
11690
11875
  error: `WMI Win32_Process.Create failed: ${wmiMsg}. Launch stopped without a shell fallback so Cursor Bridge cannot flash a console or create an unreliable orphan.`
11691
11876
  };
11692
11877
  }
@@ -11760,16 +11945,23 @@ function writeRuntimeFile(target, content) {
11760
11945
  }
11761
11946
  }
11762
11947
  function materializeLifecycleSupervisorRuntime({ sourceScript, dir = defaultLifecycleDir() } = {}) {
11763
- const source = resolve5(sourceScript || resolveSupervisorScript());
11764
- if (!existsSync4(source)) throw new Error(`lifecycle supervisor script missing: ${source}`);
11765
- const content = readFileSync4(source);
11766
- const fingerprint = createHash2("sha256").update(content).digest("hex");
11948
+ const described = describeLifecycleSupervisorRuntime({ sourceScript, dir });
11949
+ const { sourceScript: source, content, fingerprint } = described;
11767
11950
  const runtimeRoot = join6(ensureLifecycleDir(dir), "runtime", `supervisor-${fingerprint.slice(0, 20)}`);
11768
11951
  mkdirSync5(runtimeRoot, { recursive: true });
11769
11952
  const script = join6(runtimeRoot, "cursor-lifecycle-supervisor.mjs");
11770
11953
  writeRuntimeFile(script, content);
11771
11954
  return { sourceScript: source, script, runtimeRoot, fingerprint };
11772
11955
  }
11956
+ function describeLifecycleSupervisorRuntime({ sourceScript, dir = defaultLifecycleDir() } = {}) {
11957
+ const source = resolve5(sourceScript || resolveSupervisorScript());
11958
+ if (!existsSync4(source)) throw new Error(`lifecycle supervisor script missing: ${source}`);
11959
+ const content = readFileSync4(source);
11960
+ const fingerprint = createHash2("sha256").update(content).digest("hex");
11961
+ const runtimeRoot = join6(dir, "runtime", `supervisor-${fingerprint.slice(0, 20)}`);
11962
+ const script = join6(runtimeRoot, "cursor-lifecycle-supervisor.mjs");
11963
+ return { sourceScript: source, script, runtimeRoot, fingerprint, content };
11964
+ }
11773
11965
  function isProcessAlive(pid) {
11774
11966
  if (!pid || !Number.isFinite(pid)) return false;
11775
11967
  try {
@@ -11867,6 +12059,28 @@ async function tryConnect(sock) {
11867
12059
  return null;
11868
12060
  }
11869
12061
  }
12062
+ async function tryConnectDetailed(sock, connectImpl = connectSupervisor) {
12063
+ try {
12064
+ return { socket: await connectImpl(sock, 1500), error: null };
12065
+ } catch (error2) {
12066
+ return { socket: null, error: error2 };
12067
+ }
12068
+ }
12069
+ function lifecycleClientError(message, details = {}, cause = null) {
12070
+ const error2 = new Error(message, cause ? { cause } : void 0);
12071
+ Object.assign(error2, details);
12072
+ return error2;
12073
+ }
12074
+ function filesystemFallbackDetails(error2) {
12075
+ const code = error2 && typeof error2 === "object" && error2.code != null ? String(error2.code) : null;
12076
+ const blocked = code === "EPERM" || code === "EACCES" || code === "EROFS";
12077
+ return {
12078
+ errorKind: blocked ? "policy-blocked" : "configuration",
12079
+ degradedReason: blocked ? "fs-policy-blocked" : null,
12080
+ errorCode: code,
12081
+ canAttachFallback: blocked
12082
+ };
12083
+ }
11870
12084
  function tryUnlink(path) {
11871
12085
  try {
11872
12086
  if (path && existsSync4(path)) unlinkSync(path);
@@ -11890,65 +12104,87 @@ function writeBootEnv(dir, extra = {}) {
11890
12104
  return bootPath;
11891
12105
  }
11892
12106
  async function ensureSupervisorConnected(options = {}) {
11893
- const dir = ensureLifecycleDir(options.dir || defaultLifecycleDir());
12107
+ const dir = options.dir || defaultLifecycleDir();
11894
12108
  const sock = options.sock || supervisorSockPath(dir);
11895
12109
  const pidPath = options.pidPath || supervisorPidPath(dir);
11896
12110
  const lockPath = options.lockPath || supervisorLockPath(dir);
11897
12111
  const createWaitMs = Number(options.createWaitMs || DEFAULT_CREATE_WAIT_MS);
11898
12112
  const sourceScript = options.supervisorScript || resolveSupervisorScript();
11899
- const runtime = options.persistSupervisorRuntime === false ? {
11900
- sourceScript: resolve5(sourceScript),
11901
- script: resolve5(sourceScript),
11902
- runtimeRoot: dirname4(resolve5(sourceScript)),
11903
- fingerprint: null
11904
- } : materializeLifecycleSupervisorRuntime({ sourceScript, dir });
11905
- let socket = await tryConnect(sock);
12113
+ const connectImpl = options.connectSupervisorImpl || connectSupervisor;
12114
+ const initialConnection = await tryConnectDetailed(sock, connectImpl);
12115
+ let socket = initialConnection.socket;
11906
12116
  if (socket) {
11907
12117
  const pid = readPidFile(pidPath);
11908
12118
  let current = null;
11909
12119
  try {
11910
12120
  current = await request(socket, { type: "ping" }, 5e3);
11911
- } catch {
11912
- }
11913
- const mismatch = Boolean(runtime.fingerprint && current?.runtimeFingerprint && current.runtimeFingerprint !== runtime.fingerprint);
11914
- if (mismatch) {
11915
- let upgrade = null;
12121
+ } catch (error2) {
11916
12122
  try {
11917
- upgrade = await request(socket, {
11918
- type: "shutdown_if_idle",
11919
- confirmation: "ROLL_CURSOR_LIFECYCLE_SUPERVISOR",
11920
- targetRuntimeFingerprint: runtime.fingerprint
11921
- }, 5e3);
12123
+ socket.destroy();
11922
12124
  } catch {
11923
12125
  }
11924
- if (upgrade?.restarting === true) {
11925
- try {
11926
- socket.end();
11927
- } catch {
11928
- }
11929
- try {
11930
- socket.destroy();
11931
- } catch {
11932
- }
11933
- socket = null;
11934
- const deadline = Date.now() + 5e3;
11935
- while (Date.now() < deadline && isProcessAlive(pid)) await sleep(50);
11936
- }
12126
+ throw lifecycleClientError(`lifecycle supervisor is reachable but unresponsive: ${error2 instanceof Error ? error2.message : String(error2)}`, {
12127
+ errorKind: "supervisor-unresponsive",
12128
+ degradedReason: "supervisor-unresponsive",
12129
+ errorCode: error2 && typeof error2 === "object" && error2.code != null ? String(error2.code) : null,
12130
+ canAttachFallback: true
12131
+ }, error2);
11937
12132
  }
11938
- if (socket) {
11939
- return {
11940
- socket,
11941
- sock,
11942
- dir,
11943
- supervisorPid: pid,
11944
- reusedSupervisor: true,
11945
- createdSupervisor: false,
11946
- spawnMethod: null,
11947
- runtimeFingerprint: current?.runtimeFingerprint || null,
11948
- runtimeScript: current?.runtimeScript || null,
11949
- targetRuntimeFingerprint: runtime.fingerprint
11950
- };
12133
+ let targetRuntime = null;
12134
+ try {
12135
+ targetRuntime = options.persistSupervisorRuntime === false ? {
12136
+ sourceScript: resolve5(sourceScript),
12137
+ script: resolve5(sourceScript),
12138
+ runtimeRoot: dirname4(resolve5(sourceScript)),
12139
+ fingerprint: null
12140
+ } : describeLifecycleSupervisorRuntime({ sourceScript, dir });
12141
+ } catch {
11951
12142
  }
12143
+ const mismatch = Boolean(targetRuntime?.fingerprint && current?.runtimeFingerprint && current.runtimeFingerprint !== targetRuntime.fingerprint);
12144
+ return {
12145
+ socket,
12146
+ sock,
12147
+ dir,
12148
+ supervisorPid: pid,
12149
+ reusedSupervisor: true,
12150
+ createdSupervisor: false,
12151
+ spawnMethod: null,
12152
+ runtimeFingerprint: current?.runtimeFingerprint || null,
12153
+ runtimeScript: current?.runtimeScript || null,
12154
+ targetRuntimeFingerprint: targetRuntime?.fingerprint || null,
12155
+ runtimeUpgradeDeferred: mismatch
12156
+ };
12157
+ }
12158
+ const connectCode = initialConnection.error && typeof initialConnection.error === "object" ? String(initialConnection.error.code || "") : "";
12159
+ const connectMessage = initialConnection.error instanceof Error ? initialConnection.error.message : String(initialConnection.error || "");
12160
+ if (connectCode === "EPERM" || connectCode === "EACCES") {
12161
+ throw lifecycleClientError(`lifecycle supervisor pipe access was blocked: ${connectMessage || connectCode}`, {
12162
+ errorKind: "policy-blocked",
12163
+ degradedReason: "pipe-policy-blocked",
12164
+ errorCode: connectCode,
12165
+ canAttachFallback: true
12166
+ }, initialConnection.error);
12167
+ }
12168
+ const normalAbsenceCodes = /* @__PURE__ */ new Set(["ENOENT", "ECONNREFUSED", "ECONNRESET", "EPIPE"]);
12169
+ if (initialConnection.error && (!normalAbsenceCodes.has(connectCode) || /connect timeout/i.test(connectMessage))) {
12170
+ throw lifecycleClientError(`lifecycle supervisor pipe is unavailable without a clean absence signal: ${connectMessage || connectCode || "unknown connect error"}`, {
12171
+ errorKind: "supervisor-unresponsive",
12172
+ degradedReason: "supervisor-unresponsive",
12173
+ errorCode: connectCode || null,
12174
+ canAttachFallback: true
12175
+ }, initialConnection.error);
12176
+ }
12177
+ let runtime;
12178
+ try {
12179
+ ensureLifecycleDir(dir);
12180
+ runtime = options.persistSupervisorRuntime === false ? {
12181
+ sourceScript: resolve5(sourceScript),
12182
+ script: resolve5(sourceScript),
12183
+ runtimeRoot: dirname4(resolve5(sourceScript)),
12184
+ fingerprint: null
12185
+ } : (options.materializeRuntimeImpl || materializeLifecycleSupervisorRuntime)({ sourceScript, dir });
12186
+ } catch (error2) {
12187
+ throw lifecycleClientError(`failed to prepare lifecycle supervisor runtime: ${error2 instanceof Error ? error2.message : String(error2)}`, filesystemFallbackDetails(error2), error2);
11952
12188
  }
11953
12189
  const stalePid = readPidFile(pidPath);
11954
12190
  if (stalePid && !isProcessAlive(stalePid)) {
@@ -11984,12 +12220,20 @@ async function ensureSupervisorConnected(options = {}) {
11984
12220
  CURSOR_BRIDGE_LIFECYCLE_DIR: dir,
11985
12221
  CURSOR_BRIDGE_SUPERVISOR_SOCK: sock
11986
12222
  };
11987
- const spawned = spawnNodeOutsideJob(script, scriptArgs, {
12223
+ const spawned = (options.spawnNodeOutsideJobImpl || spawnNodeOutsideJob)(script, scriptArgs, {
11988
12224
  cwd: options.cwd || runtime.runtimeRoot,
11989
12225
  env: childEnv
11990
12226
  });
11991
12227
  if (!spawned.ok) {
11992
- throw new Error(`failed to spawn lifecycle supervisor: ${spawned.error || spawned.method}`);
12228
+ throw lifecycleClientError(`failed to spawn lifecycle supervisor: ${spawned.error || spawned.method}`, {
12229
+ errorKind: spawned.errorKind || "unknown",
12230
+ degradedReason: spawned.degradedReason || null,
12231
+ errorCode: spawned.errorCode ?? null,
12232
+ returnValue: spawned.returnValue ?? null,
12233
+ canAttachFallback: spawned.canAttachFallback === true,
12234
+ commandLine: spawned.commandLine || null,
12235
+ stderr: spawned.stderr || null
12236
+ });
11993
12237
  }
11994
12238
  const deadline = Date.now() + createWaitMs;
11995
12239
  while (Date.now() < deadline) {
@@ -12009,7 +12253,8 @@ async function ensureSupervisorConnected(options = {}) {
12009
12253
  unsafe: !!spawned.unsafe,
12010
12254
  runtimeFingerprint: runtime.fingerprint,
12011
12255
  runtimeScript: script,
12012
- targetRuntimeFingerprint: runtime.fingerprint
12256
+ targetRuntimeFingerprint: runtime.fingerprint,
12257
+ runtimeUpgradeDeferred: false
12013
12258
  };
12014
12259
  }
12015
12260
  await sleep(100);
@@ -12045,7 +12290,8 @@ async function ensureCursorViaSupervisor(options = {}) {
12045
12290
  launchReason: "supervisor-error",
12046
12291
  spawnMethod: conn.spawnMethod,
12047
12292
  runtimeFingerprint: conn.runtimeFingerprint || null,
12048
- runtimeScript: conn.runtimeScript || null
12293
+ runtimeScript: conn.runtimeScript || null,
12294
+ runtimeUpgradeDeferred: conn.runtimeUpgradeDeferred === true
12049
12295
  };
12050
12296
  }
12051
12297
  return {
@@ -12070,7 +12316,8 @@ async function ensureCursorViaSupervisor(options = {}) {
12070
12316
  spawnMethod: conn.spawnMethod,
12071
12317
  ensureCount: response.ensureCount,
12072
12318
  runtimeFingerprint: response.runtimeFingerprint || conn.runtimeFingerprint || null,
12073
- runtimeScript: response.runtimeScript || conn.runtimeScript || null
12319
+ runtimeScript: response.runtimeScript || conn.runtimeScript || null,
12320
+ runtimeUpgradeDeferred: conn.runtimeUpgradeDeferred === true
12074
12321
  };
12075
12322
  } finally {
12076
12323
  try {
@@ -12109,6 +12356,26 @@ __export(launch_cursor_exports, {
12109
12356
  waitForCdp: () => waitForCdp
12110
12357
  });
12111
12358
  import { pathToFileURL } from "url";
12359
+ function lifecycleCapabilities(mode) {
12360
+ if (mode === "supervised") {
12361
+ return { canLaunchCursor: true, canOpenWorkspaceWindow: true, survivesHostExit: "cursor-yes-tasks-no" };
12362
+ }
12363
+ if (mode === "attached") {
12364
+ return { canLaunchCursor: false, canOpenWorkspaceWindow: false, survivesHostExit: "cursor-yes-tasks-no" };
12365
+ }
12366
+ return { canLaunchCursor: true, canOpenWorkspaceWindow: true, survivesHostExit: "not-guaranteed" };
12367
+ }
12368
+ function withLifecycle(result, mode, extra = {}) {
12369
+ return {
12370
+ ...result,
12371
+ lifecycleMode: mode,
12372
+ persistent: mode === "supervised",
12373
+ degradedReason: extra.degradedReason || null,
12374
+ spawnErrorCode: extra.spawnErrorCode ?? null,
12375
+ capabilities: lifecycleCapabilities(mode),
12376
+ ...extra
12377
+ };
12378
+ }
12112
12379
  async function ensureCursorRunning(options = {}) {
12113
12380
  const bindingFile = options.workspaceFile || resolveWorkspaceBindingFile();
12114
12381
  const bindingKey = options.workspaceKey || resolveWorkspaceBindingKey();
@@ -12120,19 +12387,47 @@ async function ensureCursorRunning(options = {}) {
12120
12387
  const ensureOptions = { ...options, projectPath };
12121
12388
  if (process.env.CURSOR_BRIDGE_INLINE_ENSURE === "1" || process.env.CURSOR_BRIDGE_NO_SUPERVISOR === "1") {
12122
12389
  const local = await ensureCursorRunningLocal(ensureOptions);
12123
- return {
12390
+ return withLifecycle({
12124
12391
  ...local,
12125
12392
  adapterPid: process.pid,
12126
12393
  supervisorPid: null,
12127
12394
  reusedSupervisor: false,
12128
12395
  createdSupervisor: false,
12129
12396
  launchReason: local.status === "launched" ? "inline-spawned-cursor" : `inline-${local.status}`
12130
- };
12397
+ }, "inline");
12398
+ }
12399
+ try {
12400
+ const supervised = await ensureCursorViaSupervisor({
12401
+ ...ensureOptions,
12402
+ reason: options.reason || "ensureCursorRunning"
12403
+ });
12404
+ return withLifecycle(supervised, "supervised", {
12405
+ runtimeUpgradeDeferred: supervised.runtimeUpgradeDeferred === true
12406
+ });
12407
+ } catch (error2) {
12408
+ if (error2?.canAttachFallback !== true) throw error2;
12409
+ const local = await ensureCursorRunningLocal({
12410
+ ...ensureOptions,
12411
+ allowSpawn: false,
12412
+ allowProcessControl: false
12413
+ });
12414
+ return withLifecycle({
12415
+ ...local,
12416
+ adapterPid: process.pid,
12417
+ supervisorPid: null,
12418
+ reusedSupervisor: false,
12419
+ createdSupervisor: false,
12420
+ spawnMethod: null,
12421
+ launchReason: local.ok ? "attached-after-supervisor-blocked" : `attached-${local.status}`,
12422
+ supervisorError: error2 instanceof Error ? error2.message : String(error2)
12423
+ }, "attached", {
12424
+ degradedReason: error2.degradedReason || "supervisor-unavailable",
12425
+ spawnErrorCode: error2.errorCode ?? error2.returnValue ?? null,
12426
+ supervisorErrorKind: error2.errorKind || null,
12427
+ supervisorCommandLine: error2.commandLine || null,
12428
+ runtimeUpgradeDeferred: false
12429
+ });
12131
12430
  }
12132
- return ensureCursorViaSupervisor({
12133
- ...ensureOptions,
12134
- reason: options.reason || "ensureCursorRunning"
12135
- });
12136
12431
  }
12137
12432
  var isMain;
12138
12433
  var init_launch_cursor = __esm({
@@ -20847,7 +21142,7 @@ function updateCursorModelPreferences(filePath, { action, target, model, effort
20847
21142
  init_workspace_binding();
20848
21143
  init_cursor_ensure_core();
20849
21144
  init_lifecycle_paths();
20850
- var PLUGIN_VERSION = "5.6.0";
21145
+ var PLUGIN_VERSION = "5.6.1";
20851
21146
  var CDP_PORT2 = Number(process.env.CURSOR_BRIDGE_CDP_PORT || 9223);
20852
21147
  var ORIGIN = `http://localhost:${CDP_PORT2}`;
20853
21148
  var QUERY_TIMEOUT = Number(process.env.CURSOR_BRIDGE_TIMEOUT || 3e5);
@@ -21727,6 +22022,40 @@ function promoteAgentsWorkspaceLifecycle(lifecycle, agentsWorkspace) {
21727
22022
  retryable: false
21728
22023
  };
21729
22024
  }
22025
+ function lifecycleFromEnsureResult(result, fallbackRuntimeMode) {
22026
+ return {
22027
+ adapterPid: result.adapterPid ?? process.pid,
22028
+ supervisorPid: result.supervisorPid ?? null,
22029
+ reusedSupervisor: !!result.reusedSupervisor,
22030
+ createdSupervisor: !!result.createdSupervisor,
22031
+ launchReason: result.launchReason || result.status,
22032
+ status: result.status,
22033
+ spawnMethod: result.spawnMethod || null,
22034
+ lifecycleMode: result.lifecycleMode || null,
22035
+ persistent: result.persistent === true,
22036
+ degradedReason: result.degradedReason || null,
22037
+ spawnErrorCode: result.spawnErrorCode ?? null,
22038
+ supervisorErrorKind: result.supervisorErrorKind || null,
22039
+ capabilities: result.capabilities || null,
22040
+ cursorPid: result.cursorPid || null,
22041
+ runtimeMode: result.runtimeMode || fallbackRuntimeMode,
22042
+ projectPath: result.projectPath || null,
22043
+ targetId: result.targetId || null,
22044
+ workspaceAction: result.workspaceAction || null,
22045
+ presentation: result.presentation || null,
22046
+ windowGuard: result.windowGuard || null,
22047
+ startupWindowGuard: result.startupWindowGuard || null,
22048
+ message: result.message || null,
22049
+ needsAction: result.needsAction || null,
22050
+ nextStep: result.nextStep || null,
22051
+ retryable: result.retryable === true,
22052
+ cursorExecutable: result.cursorExecutable || null,
22053
+ cursorExecutableSource: result.cursorExecutableSource || null,
22054
+ runtimeFingerprint: result.runtimeFingerprint || null,
22055
+ runtimeScript: result.runtimeScript || null,
22056
+ runtimeUpgradeDeferred: result.runtimeUpgradeDeferred === true
22057
+ };
22058
+ }
21730
22059
  function releaseAdapterWorkingDirectory({ targetDir = defaultLifecycleDir(), chdir = process.chdir } = {}) {
21731
22060
  const target = ensureLifecycleDir(targetDir);
21732
22061
  chdir(target);
@@ -21801,7 +22130,9 @@ var CursorBridge = class {
21801
22130
  "port-not-cursor",
21802
22131
  "no-exe",
21803
22132
  "timeout",
21804
- "workspace-not-ready"
22133
+ "workspace-not-ready",
22134
+ "external-launch-required",
22135
+ "spawn-blocked"
21805
22136
  ]);
21806
22137
  if (!lifecycle || !recoverableStatuses.has(lifecycle.status)) throw error2;
21807
22138
  return {
@@ -22244,42 +22575,35 @@ var CursorBridge = class {
22244
22575
  adapterStartCwd: this.adapterStartCwd,
22245
22576
  ...this.projectPath ? { projectPath: this.projectPath } : {}
22246
22577
  });
22247
- this._lastLifecycle = {
22248
- adapterPid: rr.adapterPid ?? process.pid,
22249
- supervisorPid: rr.supervisorPid ?? null,
22250
- reusedSupervisor: !!rr.reusedSupervisor,
22251
- createdSupervisor: !!rr.createdSupervisor,
22252
- launchReason: rr.launchReason || rr.status,
22253
- status: rr.status,
22254
- spawnMethod: rr.spawnMethod || null,
22255
- cursorPid: rr.cursorPid || null,
22256
- runtimeMode: rr.runtimeMode || this.runtimeMode,
22257
- projectPath: rr.projectPath || null,
22258
- targetId: rr.targetId || null,
22259
- workspaceAction: rr.workspaceAction || null,
22260
- presentation: rr.presentation || null,
22261
- message: rr.message || null,
22262
- needsAction: rr.needsAction || null,
22263
- nextStep: rr.nextStep || null,
22264
- retryable: rr.retryable === true,
22265
- cursorExecutable: rr.cursorExecutable || null,
22266
- cursorExecutableSource: rr.cursorExecutableSource || null,
22267
- runtimeFingerprint: rr.runtimeFingerprint || null,
22268
- runtimeScript: rr.runtimeScript || null
22269
- };
22578
+ this._lastLifecycle = lifecycleFromEnsureResult(rr, this.runtimeMode);
22270
22579
  if (!rr.ok && rr.status === "workspace-not-ready" && rr.projectPath) {
22271
22580
  const agentsWorkspace = await this._findAgentsWorkspace(rr.projectPath);
22272
22581
  if (agentsWorkspace) {
22273
22582
  this._lastLifecycle = promoteAgentsWorkspaceLifecycle(this._lastLifecycle, agentsWorkspace);
22274
22583
  }
22275
22584
  }
22585
+ if (rr.ok && rr.lifecycleMode === "attached" && rr.workspaceAction === "reused-agents-window" && rr.projectPath) {
22586
+ const agentsWorkspace = await this._findAgentsWorkspace(rr.projectPath);
22587
+ if (agentsWorkspace) {
22588
+ this._lastLifecycle = promoteAgentsWorkspaceLifecycle(this._lastLifecycle, agentsWorkspace);
22589
+ } else {
22590
+ this._lastLifecycle = {
22591
+ ...this._lastLifecycle,
22592
+ status: "workspace-not-ready",
22593
+ message: `Cursor is reachable, but Cursor Bridge could not verify workspace ${rr.projectPath} in the attached Agents Window.`,
22594
+ needsAction: "open_workspace_in_cursor",
22595
+ nextStep: `Open workspace ${rr.projectPath} in Cursor, then retry the same operation.`,
22596
+ retryable: true
22597
+ };
22598
+ }
22599
+ }
22276
22600
  if (this.runtimeMode === "minimal") {
22277
22601
  this._lastPresentation = rr.presentation ? { ...rr.presentation, at: (/* @__PURE__ */ new Date()).toISOString() } : await this.applyRuntimePresentation("hide");
22278
22602
  } else {
22279
22603
  await this.recoverNormalAgentsPresentation(this._lastLifecycle);
22280
22604
  }
22281
22605
  const life = "adapterPid=" + this._lastLifecycle.adapterPid + " supervisorPid=" + this._lastLifecycle.supervisorPid + " reused=" + this._lastLifecycle.reusedSupervisor + " reason=" + this._lastLifecycle.launchReason;
22282
- if (!rr.ok && this._lastLifecycle.status !== "agents-workspace-ready") {
22606
+ if ((!rr.ok || this._lastLifecycle.status === "workspace-not-ready") && this._lastLifecycle.status !== "agents-workspace-ready") {
22283
22607
  throw new Error([rr.message || `Cursor lifecycle failed: ${rr.status}`, rr.nextStep].filter(Boolean).join(" "));
22284
22608
  }
22285
22609
  if (this._lastLifecycle.status === "agents-workspace-ready") {
@@ -23821,6 +24145,11 @@ var CursorBridge = class {
23821
24145
  launchReason: null,
23822
24146
  status: null,
23823
24147
  spawnMethod: null,
24148
+ lifecycleMode: null,
24149
+ persistent: null,
24150
+ degradedReason: null,
24151
+ spawnErrorCode: null,
24152
+ capabilities: null,
23824
24153
  cursorPid: null,
23825
24154
  runtimeMode: this.runtimeMode,
23826
24155
  presentation: null
@@ -23930,7 +24259,7 @@ function buildToolDefinitions(bridgeInstance) {
23930
24259
  var ADAPTER_START_CWD = process.cwd();
23931
24260
  var bridge = new CursorBridge({ adapterStartCwd: ADAPTER_START_CWD });
23932
24261
  var server = new Server(
23933
- { name: "cursor-bridge", version: "5.6.0" },
24262
+ { name: "cursor-bridge", version: "5.6.1" },
23934
24263
  { capabilities: { tools: { listChanged: true } } }
23935
24264
  );
23936
24265
  async function ensureBridgeCursor(targetBridge, reason) {
@@ -23942,31 +24271,7 @@ async function ensureBridgeCursor(targetBridge, reason) {
23942
24271
  adapterStartCwd: targetBridge.adapterStartCwd,
23943
24272
  ...targetBridge.projectPath ? { projectPath: targetBridge.projectPath } : {}
23944
24273
  });
23945
- targetBridge._lastLifecycle = {
23946
- adapterPid: r.adapterPid ?? process.pid,
23947
- supervisorPid: r.supervisorPid ?? null,
23948
- reusedSupervisor: !!r.reusedSupervisor,
23949
- createdSupervisor: !!r.createdSupervisor,
23950
- launchReason: r.launchReason || r.status,
23951
- status: r.status,
23952
- spawnMethod: r.spawnMethod || null,
23953
- cursorPid: r.cursorPid || null,
23954
- runtimeMode: r.runtimeMode || targetBridge.runtimeMode,
23955
- projectPath: r.projectPath || null,
23956
- targetId: r.targetId || null,
23957
- workspaceAction: r.workspaceAction || null,
23958
- presentation: r.presentation || null,
23959
- windowGuard: r.windowGuard || null,
23960
- startupWindowGuard: r.startupWindowGuard || null,
23961
- message: r.message || null,
23962
- needsAction: r.needsAction || null,
23963
- nextStep: r.nextStep || null,
23964
- retryable: r.retryable === true,
23965
- cursorExecutable: r.cursorExecutable || null,
23966
- cursorExecutableSource: r.cursorExecutableSource || null,
23967
- runtimeFingerprint: r.runtimeFingerprint || null,
23968
- runtimeScript: r.runtimeScript || null
23969
- };
24274
+ targetBridge._lastLifecycle = lifecycleFromEnsureResult(r, targetBridge.runtimeMode);
23970
24275
  if (targetBridge.runtimeMode === "minimal") {
23971
24276
  targetBridge._lastPresentation = r.presentation ? { ...r.presentation, at: (/* @__PURE__ */ new Date()).toISOString() } : await targetBridge.applyRuntimePresentation("hide");
23972
24277
  }
@@ -181,6 +181,8 @@ function startMinimalWindowGuard(pid, options = {}) {
181
181
  "-EncodedCommand",
182
182
  encodePowerShell(script)
183
183
  ], { detached: !lifetime, stdio: "ignore", windowsHide: true });
184
+ if (child && typeof child.once === "function") child.once("error", () => {
185
+ });
184
186
  if (!lifetime && child && typeof child.unref === "function") child.unref();
185
187
  return {
186
188
  started: true,
@@ -542,6 +544,58 @@ async function waitForCdp(maxMs = 3e4, stepMs = 1e3) {
542
544
  }
543
545
  return false;
544
546
  }
547
+ async function spawnDetachedSafely(spawnImpl, file, args, spawnOptions) {
548
+ let child;
549
+ try {
550
+ child = spawnImpl(file, args, spawnOptions);
551
+ } catch (error) {
552
+ return {
553
+ ok: false,
554
+ child: null,
555
+ error,
556
+ errorCode: error && typeof error === "object" && error.code != null ? String(error.code) : null
557
+ };
558
+ }
559
+ if (child && typeof child.once === "function") {
560
+ const startup = await new Promise((resolvePromise) => {
561
+ let settled = false;
562
+ const finish = (result) => {
563
+ if (settled) return;
564
+ settled = true;
565
+ child.off?.("spawn", onSpawn);
566
+ child.off?.("error", onError);
567
+ resolvePromise(result);
568
+ };
569
+ const onSpawn = () => finish({ ok: true });
570
+ const onError = (error) => finish({ ok: false, error });
571
+ child.once("spawn", onSpawn);
572
+ child.once("error", onError);
573
+ if (Number.isInteger(child.pid) && child.pid > 0) queueMicrotask(onSpawn);
574
+ });
575
+ if (!startup.ok) {
576
+ return {
577
+ ok: false,
578
+ child,
579
+ error: startup.error,
580
+ errorCode: startup.error && typeof startup.error === "object" && startup.error.code != null ? String(startup.error.code) : null
581
+ };
582
+ }
583
+ child.once("error", () => {
584
+ });
585
+ }
586
+ if (child && typeof child.unref === "function") child.unref();
587
+ return { ok: true, child };
588
+ }
589
+ function attachedPresentation(runtimeMode, port) {
590
+ if (runtimeMode !== "minimal") return null;
591
+ return {
592
+ supported: true,
593
+ applied: false,
594
+ action: "hide",
595
+ port,
596
+ reason: "attached lifecycle cannot start the PowerShell window guard under the current process policy"
597
+ };
598
+ }
545
599
  async function ensureCursorRunningLocal(options = {}) {
546
600
  const waitMs = Number(options.waitMs || 3e4);
547
601
  const runtimeMode = options.runtimeMode || "normal";
@@ -553,12 +607,14 @@ async function ensureCursorRunningLocal(options = {}) {
553
607
  const projectPath = Object.hasOwn(options, "projectPath") ? options.projectPath ? resolve2(String(options.projectPath)) : null : resolveProjectPath();
554
608
  const listCdpPageTargetsImpl = options.listCdpPageTargetsImpl || listCdpPageTargets;
555
609
  const spawnImpl = options.spawnImpl || spawn2;
610
+ const allowSpawn = options.allowSpawn !== false;
611
+ const allowProcessControl = options.allowProcessControl !== false;
556
612
  if (await cdpUpImpl()) {
557
613
  const isCursor = await cdpIsCursorImpl();
558
614
  if (isCursor) {
559
- const cursorPid2 = findCursorPidByPort(CDP_PORT);
560
- const windowGuard2 = effectiveRuntimeMode === "minimal" && cursorPid2 ? startMinimalWindowGuard(cursorPid2) : null;
561
- const presentation2 = effectiveRuntimeMode === "minimal" ? setCursorWindowPresentation({ action: "hide", port: CDP_PORT, pid: cursorPid2 }) : null;
615
+ const cursorPid2 = allowProcessControl ? findCursorPidByPort(CDP_PORT) : null;
616
+ const windowGuard2 = allowProcessControl && effectiveRuntimeMode === "minimal" && cursorPid2 ? startMinimalWindowGuard(cursorPid2) : null;
617
+ const presentation2 = effectiveRuntimeMode === "minimal" ? allowProcessControl ? setCursorWindowPresentation({ action: "hide", port: CDP_PORT, pid: cursorPid2 }) : attachedPresentation(effectiveRuntimeMode, CDP_PORT) : null;
562
618
  const currentTargets = await listCdpPageTargetsImpl();
563
619
  const projectKey = normalizeProjectKey(projectPath);
564
620
  let targetId2 = projectKey ? PROJECT_TARGETS.get(projectKey) || null : currentTargets[0] && currentTargets[0].id || null;
@@ -587,6 +643,22 @@ async function ensureCursorRunningLocal(options = {}) {
587
643
  }
588
644
  }
589
645
  if (projectPath && existsSync(projectPath) && !targetId2) {
646
+ if (!allowSpawn) {
647
+ return {
648
+ ok: false,
649
+ status: "workspace-not-ready",
650
+ port: CDP_PORT,
651
+ cursorPid: cursorPid2,
652
+ runtimeMode: effectiveRuntimeMode,
653
+ projectPath,
654
+ presentation: presentation2,
655
+ windowGuard: windowGuard2,
656
+ needsAction: "open_workspace_in_cursor",
657
+ retryable: true,
658
+ nextStep: `Open workspace ${projectPath} in the existing Cursor Agents Window, then retry the same operation.`,
659
+ message: `CCE connected to Cursor, but the current workspace target for ${projectPath} is not ready and the current lifecycle cannot open a new window.`
660
+ };
661
+ }
590
662
  const cursorExecutable2 = findCursorExeDetailsImpl();
591
663
  const exe2 = cursorExecutable2 && cursorExecutable2.path;
592
664
  if (!exe2) {
@@ -606,12 +678,28 @@ async function ensureCursorRunningLocal(options = {}) {
606
678
  };
607
679
  }
608
680
  const beforeTargetIds = new Set(currentTargets.map((target2) => target2.id));
609
- const opener = spawnImpl(exe2, ["--new-window", projectPath], {
681
+ const opened = await spawnDetachedSafely(spawnImpl, exe2, ["--new-window", projectPath], {
610
682
  detached: true,
611
683
  stdio: "ignore",
612
684
  windowsHide: effectiveRuntimeMode === "minimal"
613
685
  });
614
- opener.unref();
686
+ if (!opened.ok) {
687
+ return {
688
+ ok: false,
689
+ status: "spawn-blocked",
690
+ port: CDP_PORT,
691
+ cursorPid: cursorPid2,
692
+ runtimeMode: effectiveRuntimeMode,
693
+ projectPath,
694
+ presentation: presentation2,
695
+ windowGuard: windowGuard2,
696
+ errorCode: opened.errorCode,
697
+ needsAction: "open_workspace_in_cursor",
698
+ retryable: true,
699
+ nextStep: `Open workspace ${projectPath} in Cursor, then retry the same operation.`,
700
+ message: `Cursor Bridge could not open a new workspace window: ${opened.error instanceof Error ? opened.error.message : String(opened.error)}`
701
+ };
702
+ }
615
703
  workspaceAction = "opened-new-window";
616
704
  const openedTarget2 = await waitForNewCdpTarget(beforeTargetIds, 12e3, projectPath, listCdpPageTargetsImpl);
617
705
  if (!openedTarget2) {
@@ -660,6 +748,21 @@ async function ensureCursorRunningLocal(options = {}) {
660
748
  message: `CCE cannot connect to Cursor because required local port ${CDP_PORT} is occupied by another program.`
661
749
  };
662
750
  }
751
+ if (!allowSpawn) {
752
+ return {
753
+ ok: false,
754
+ status: "external-launch-required",
755
+ port: CDP_PORT,
756
+ cursorPid: null,
757
+ runtimeMode: effectiveRuntimeMode,
758
+ projectPath,
759
+ presentation: attachedPresentation(effectiveRuntimeMode, CDP_PORT),
760
+ needsAction: "launch_cursor_with_cdp",
761
+ retryable: true,
762
+ nextStep: `Start Cursor with its remote debugging connection on port ${CDP_PORT}, open ${projectPath || "the target workspace"}, then retry the same operation.`,
763
+ message: `Cursor is not reachable on the configured CDP port ${CDP_PORT}, and the current lifecycle policy cannot launch it.`
764
+ };
765
+ }
663
766
  if (cursorRunningImpl()) {
664
767
  const cursorExecutable2 = findCursorExeDetailsImpl();
665
768
  return {
@@ -697,13 +800,29 @@ async function ensureCursorRunningLocal(options = {}) {
697
800
  );
698
801
  }
699
802
  if (projectPath && existsSync(projectPath)) args.push(projectPath);
700
- const child = spawnImpl(exe, args, {
803
+ const launched = await spawnDetachedSafely(spawnImpl, exe, args, {
701
804
  detached: true,
702
805
  stdio: "ignore",
703
806
  windowsHide: effectiveRuntimeMode === "minimal"
704
807
  });
705
- child.unref();
706
- const startupWindowGuard = effectiveRuntimeMode === "minimal" ? startMinimalWindowGuard(child.pid) : null;
808
+ if (!launched.ok) {
809
+ return {
810
+ ok: false,
811
+ status: "spawn-blocked",
812
+ exe,
813
+ port: CDP_PORT,
814
+ cursorPid: null,
815
+ runtimeMode: effectiveRuntimeMode,
816
+ projectPath,
817
+ errorCode: launched.errorCode,
818
+ needsAction: "launch_cursor_manually",
819
+ retryable: true,
820
+ nextStep: `Start Cursor with its remote debugging connection on port ${CDP_PORT}, then retry the same operation.`,
821
+ message: `Cursor Bridge could not launch Cursor: ${launched.error instanceof Error ? launched.error.message : String(launched.error)}`
822
+ };
823
+ }
824
+ const child = launched.child;
825
+ const startupWindowGuard = effectiveRuntimeMode === "minimal" ? startMinimalWindowGuard(child && child.pid) : null;
707
826
  const up = await waitForCdp(waitMs);
708
827
  if (!up) {
709
828
  return {
@@ -1048,7 +1167,8 @@ async function startSupervisor(options = {}) {
1048
1167
  tryRemove(sock);
1049
1168
  }
1050
1169
  const ensureLocal = await loadEnsure(ensureModule);
1051
- let ensureInflight = null;
1170
+ const ensureInflight = /* @__PURE__ */ new Map();
1171
+ let ensureTail = Promise.resolve();
1052
1172
  let ensureCount = 0;
1053
1173
  let lastEnsure = null;
1054
1174
  const clients = /* @__PURE__ */ new Set();
@@ -1074,14 +1194,17 @@ async function startSupervisor(options = {}) {
1074
1194
  if (typeof idleTimer.unref === "function") idleTimer.unref();
1075
1195
  };
1076
1196
  const runEnsure = async (request = {}) => {
1077
- if (ensureInflight) return ensureInflight;
1078
- ensureInflight = (async () => {
1197
+ const requestRuntimeMode = request.runtimeMode || "normal";
1198
+ const requestProjectPath = Object.hasOwn(request, "projectPath") ? request.projectPath : null;
1199
+ const ensureKey = JSON.stringify([requestRuntimeMode, requestProjectPath]);
1200
+ if (ensureInflight.has(ensureKey)) return ensureInflight.get(ensureKey);
1201
+ const task = ensureTail.then(async () => {
1079
1202
  ensureCount += 1;
1080
1203
  const waitMs = Number(request.waitMs || 3e4);
1081
1204
  const result = await ensureLocal({
1082
1205
  waitMs,
1083
- runtimeMode: request.runtimeMode || "normal",
1084
- projectPath: Object.hasOwn(request, "projectPath") ? request.projectPath : null
1206
+ runtimeMode: requestRuntimeMode,
1207
+ projectPath: requestProjectPath
1085
1208
  });
1086
1209
  lastEnsure = {
1087
1210
  ...result,
@@ -1089,8 +1212,8 @@ async function startSupervisor(options = {}) {
1089
1212
  at: (/* @__PURE__ */ new Date()).toISOString(),
1090
1213
  requestReason: request.reason || null,
1091
1214
  requestAdapterPid: request.adapterPid || null,
1092
- requestRuntimeMode: request.runtimeMode || "normal",
1093
- requestProjectPath: request.projectPath || null
1215
+ requestRuntimeMode,
1216
+ requestProjectPath
1094
1217
  };
1095
1218
  writeSupervisorDiag(logPath, "ensure-result", {
1096
1219
  ok: !!result.ok,
@@ -1100,11 +1223,14 @@ async function startSupervisor(options = {}) {
1100
1223
  ensureCount
1101
1224
  });
1102
1225
  return lastEnsure;
1103
- })();
1226
+ });
1227
+ ensureInflight.set(ensureKey, task);
1228
+ ensureTail = task.catch(() => {
1229
+ });
1104
1230
  try {
1105
- return await ensureInflight;
1231
+ return await task;
1106
1232
  } finally {
1107
- ensureInflight = null;
1233
+ if (ensureInflight.get(ensureKey) === task) ensureInflight.delete(ensureKey);
1108
1234
  }
1109
1235
  };
1110
1236
  const server = net.createServer((socket) => {
@@ -10,7 +10,7 @@ const hostWorkspaceId = hostCwd.replace(/\\/g, "/").toLowerCase();
10
10
  export default createStdioMcpExtension({
11
11
  label: "Cursor Bridge",
12
12
  clientName: "pi-cursor-bridge",
13
- packageVersion: "0.1.5",
13
+ packageVersion: "0.1.6",
14
14
  serverName: "cursor-bridge",
15
15
  serverScript: join(packageRoot, "dist", "cursor-bridge.mjs"),
16
16
  cwd: hostCwd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cursor-bridge",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Use Cursor Context Engine and bounded Cursor Agent execution from the Pi coding agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,6 +47,6 @@
47
47
  },
48
48
  "piPackage": {
49
49
  "embeddedProduct": "Cursor Bridge",
50
- "embeddedProductVersion": "5.6.0"
50
+ "embeddedProductVersion": "5.6.1"
51
51
  }
52
52
  }