pi-cursor-bridge 0.2.5 → 0.2.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": "6.0.5+codex.20260922110122",
3
+ "version": "6.0.6",
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
@@ -12,6 +12,6 @@ pi install npm:pi-cursor-bridge
12
12
 
13
13
  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.
14
14
 
15
- Pi wrapper 0.2.5 embeds Cursor Bridge 6.0.5, including Cursor 3.21 model-provider alias matching and `reasoning_effort` verification for Grok 4.7/high. Its result contract returns a compact receipt for background `cursor_do` work: poll `cursor_status(task_id)` normally, retrieve the terminal raw reply with `cursor_status(task_id, detail="result")`, check `isError` before using its content, and use `detail="full"` only when diagnostics are needed. Cursor must be installed and signed in. Current end-to-end compatibility claims remain scoped to the environments documented in the main repository.
15
+ Pi wrapper 0.2.6 embeds Cursor Bridge 6.0.6, including Cursor 3.21 model-provider alias matching and `reasoning_effort` verification for Grok 4.7/high. Its result contract returns a compact receipt for background `cursor_do` work: poll `cursor_status(task_id)` normally, retrieve the terminal raw reply with `cursor_status(task_id, detail="result")`, check `isError` before using its content, and use `detail="full"` only when diagnostics are needed. Cursor must be installed and signed in. Current end-to-end compatibility claims remain scoped to the environments documented in the main repository.
16
16
 
17
17
  Full documentation: [English](https://github.com/Vanyangyang/cursor-bridge#readme) · [简体中文](https://github.com/Vanyangyang/cursor-bridge/blob/main/README.zh-CN.md)
@@ -12989,8 +12989,8 @@ function withLifecycle(result, mode, extra = {}) {
12989
12989
  };
12990
12990
  }
12991
12991
  async function ensureCursorRunning(options = {}) {
12992
- const bindingFile = options.workspaceFile || resolveWorkspaceBindingFile();
12993
- const bindingKey = options.workspaceKey || resolveWorkspaceBindingKey();
12992
+ const bindingFile = Object.hasOwn(options, "workspaceFile") ? options.workspaceFile : resolveWorkspaceBindingFile();
12993
+ const bindingKey = options.workspaceKey || resolveWorkspaceBindingKey(process.env, { cwd: options.adapterStartCwd ?? process.cwd() });
12994
12994
  const persistedBinding = readWorkspaceBinding(bindingFile, bindingKey);
12995
12995
  const hostProjectPath = resolveClaudeProjectPath();
12996
12996
  const projectPath = Object.hasOwn(options, "projectPath") ? options.projectPath : resolveProjectPath(persistedBinding && persistedBinding.projectPath || process.env.CURSOR_PROJECT_PATH, {
@@ -22985,7 +22985,7 @@ function updateCursorSessionRegistry(filePath, mutator, options = {}) {
22985
22985
  // server.mjs
22986
22986
  init_cursor_ensure_core();
22987
22987
  init_lifecycle_paths();
22988
- var PLUGIN_VERSION = "6.0.5";
22988
+ var PLUGIN_VERSION = "6.0.6";
22989
22989
  var CDP_PORT2 = Number(process.env.CURSOR_BRIDGE_CDP_PORT || 9223);
22990
22990
  var ORIGIN = `http://localhost:${CDP_PORT2}`;
22991
22991
  var QUERY_TIMEOUT = Number(process.env.CURSOR_BRIDGE_TIMEOUT || 3e5);
@@ -24282,12 +24282,12 @@ var CursorBridge = class {
24282
24282
  this.runtimeModeSource = options.runtimeMode !== void 0 ? "constructor" : persistedRuntimeMode ? "persistent" : process.env.CURSOR_BRIDGE_RUNTIME_MODE ? "environment" : "default";
24283
24283
  this.runtimeModeScope = persistedRuntimeMode ? "persistent" : options.runtimeMode !== void 0 ? "constructor" : process.env.CURSOR_BRIDGE_RUNTIME_MODE ? "environment" : "default";
24284
24284
  this.workspaceFile = options.workspaceFile === null ? null : resolve8(options.workspaceFile || resolveWorkspaceBindingFile());
24285
- this.workspaceKey = options.workspaceKey || resolveWorkspaceBindingKey();
24285
+ this.workspaceKey = options.workspaceKey || resolveWorkspaceBindingKey(process.env, { cwd: this.adapterStartCwd });
24286
24286
  const persistedWorkspace = options.projectPath === void 0 ? readWorkspaceBinding(this.workspaceFile, this.workspaceKey) : null;
24287
24287
  this.projectPath = options.projectPath !== void 0 ? resolve8(String(options.projectPath)) : persistedWorkspace && persistedWorkspace.projectPath || null;
24288
24288
  this.workspaceSource = options.projectPath !== void 0 ? "constructor" : persistedWorkspace ? "persistent_init" : "auto_detect";
24289
24289
  this.workspaceUpdatedAt = persistedWorkspace && persistedWorkspace.updatedAt || null;
24290
- this.workspaceConfirmationRequired = this.workspaceKey === "default" && !!persistedWorkspace;
24290
+ this.workspaceConfirmationRequired = this.workspaceKey === "default" && options.projectPath === void 0;
24291
24291
  this.modelPreferencesFile = options.modelPreferencesFile === null ? null : resolve8(options.modelPreferencesFile || resolveCursorModelPreferencesFile());
24292
24292
  this.modelPreferences = readCursorModelPreferences(this.modelPreferencesFile);
24293
24293
  this.sessionFile = options.sessionFile === null ? null : resolve8(options.sessionFile || resolveCursorSessionRegistryFile());
@@ -24303,6 +24303,8 @@ var CursorBridge = class {
24303
24303
  this.activeParallel = /* @__PURE__ */ new Map();
24304
24304
  this.currentJob = null;
24305
24305
  this._uiTail = Promise.resolve();
24306
+ this._workspaceTail = Promise.resolve();
24307
+ this._workspacePending = 0;
24306
24308
  this.parallelRestoreAgentId = null;
24307
24309
  this.parallelRestoreTargetId = null;
24308
24310
  }
@@ -24310,6 +24312,8 @@ var CursorBridge = class {
24310
24312
  const resolvedProjectPath = this.projectPath || this._lastLifecycle && this._lastLifecycle.projectPath || null;
24311
24313
  return {
24312
24314
  workspaceKey: this.workspaceKey,
24315
+ workspaceIdentitySource: this.workspaceKey === "default" ? "unavailable" : this.workspaceKey.split(":")[0],
24316
+ workspaceBusy: this._workspacePending > 0,
24313
24317
  workspaceConfirmationRequired: this.workspaceConfirmationRequired,
24314
24318
  workspaceBindingWarning: this.workspaceConfirmationRequired ? "This adapter has no host workspace identity. Run cursor_init with the intended project before using its shared saved binding." : null,
24315
24319
  projectPath: resolvedProjectPath,
@@ -24692,8 +24696,12 @@ var CursorBridge = class {
24692
24696
  if (!["reconcile", "collect_result", "close", "forget", "abandon"].includes(action)) {
24693
24697
  throw cursorSessionError("SESSION_ACTION_UNSUPPORTED", "expected reconcile, collect_result, close, forget, or abandon");
24694
24698
  }
24695
- if (action === "reconcile") return this._reconcileSession(id);
24696
- if (action === "collect_result") return this._reconcileSession(id, { collectResult: true });
24699
+ if (action === "reconcile" || action === "collect_result") {
24700
+ return this._withWorkspaceAdmission(() => {
24701
+ this._assertWorkspaceConfirmed();
24702
+ return this._reconcileSession(id, { collectResult: action === "collect_result" });
24703
+ });
24704
+ }
24697
24705
  let result;
24698
24706
  updateCursorSessionRegistry(this.sessionFile, (registry2) => {
24699
24707
  const session = registry2.sessions[id];
@@ -24746,6 +24754,10 @@ var CursorBridge = class {
24746
24754
  return result;
24747
24755
  }
24748
24756
  async initializeWorkspace(projectPath) {
24757
+ const result = await this._withWorkspaceAdmission(() => this._initializeWorkspace(projectPath));
24758
+ return { ...result, workspaceBusy: this._workspacePending > 0 };
24759
+ }
24760
+ async _initializeWorkspace(projectPath) {
24749
24761
  if (this._healing) await this._healing.catch(() => {
24750
24762
  });
24751
24763
  if (this.busy || this.activeParallel.size > 0 || this.queue.length > 0) {
@@ -24971,19 +24983,23 @@ var CursorBridge = class {
24971
24983
  if (!text) throw new Error("query must not be empty");
24972
24984
  if (text.length > 2e4) throw new Error("query exceeds the 20,000-character limit");
24973
24985
  const requestContext = normalizeRequestContext(options.requestContext);
24974
- this._assertWorkspaceConfirmed();
24975
- if (this._hasGlobalReservation()) {
24976
- throw new Error("A global Cursor reservation has an unconfirmed Stop state; resolve blockingTaskIds from cursor_status first");
24977
- }
24978
- await this._ensureCursor();
24979
- const job = this._enqueue("context_engine", buildContextEnginePrompt(text, requestContext), {
24980
- requestContext,
24981
- timeoutMs: QUERY_TIMEOUT,
24982
- newChat: true,
24983
- execution: "fifo",
24984
- readOnly: true,
24985
- allowedPaths: [],
24986
- modelPreference: this._modelPreferenceFor("cce")
24986
+ const job = await this._withWorkspaceAdmission(async () => {
24987
+ this._assertWorkspaceConfirmed(options.workspacePath);
24988
+ if (this._hasGlobalReservation()) {
24989
+ throw new Error("A global Cursor reservation has an unconfirmed Stop state; resolve blockingTaskIds from cursor_status first");
24990
+ }
24991
+ await this._ensureCursor();
24992
+ this._assertWorkspaceConfirmed(options.workspacePath);
24993
+ return this._enqueue("context_engine", buildContextEnginePrompt(text, requestContext), {
24994
+ requestContext,
24995
+ timeoutMs: QUERY_TIMEOUT,
24996
+ newChat: true,
24997
+ execution: "fifo",
24998
+ readOnly: true,
24999
+ allowedPaths: [],
25000
+ modelPreference: this._modelPreferenceFor("cce"),
25001
+ projectPath: this._lastLifecycle?.projectPath || this.projectPath || null
25002
+ });
24987
25003
  });
24988
25004
  const result = normalizeCceSearchResult(await job.promise);
24989
25005
  this._markTaskResultCollected(job);
@@ -24997,6 +25013,14 @@ var CursorBridge = class {
24997
25013
  return this.contextEngine(query);
24998
25014
  }
24999
25015
  async doTask(prompt, options = {}) {
25016
+ const admitted = await this._withWorkspaceAdmission(() => this._prepareTask(prompt, options));
25017
+ if (admitted.duplicate) return admitted.response;
25018
+ const job = admitted.job;
25019
+ if (options.background !== false) return this._compactTaskView(job);
25020
+ await job.promise;
25021
+ return this._collectedTaskView(job);
25022
+ }
25023
+ async _prepareTask(prompt, options = {}) {
25000
25024
  if (!this.delegationEnabled) {
25001
25025
  throw new Error("cursor_do is disabled by CURSOR_BRIDGE_DELEGATION=off; restart the MCP server without that setting to enable delegation");
25002
25026
  }
@@ -25004,7 +25028,7 @@ var CursorBridge = class {
25004
25028
  if (!text) throw new Error("prompt must not be empty");
25005
25029
  if (text.length > 1e5) throw new Error("prompt exceeds the 100,000-character limit");
25006
25030
  const requestContext = normalizeRequestContext(options.requestContext);
25007
- this._assertWorkspaceConfirmed();
25031
+ this._assertWorkspaceConfirmed(options.workspacePath);
25008
25032
  if (this._hasGlobalReservation()) {
25009
25033
  throw new Error("A global Cursor reservation has an unconfirmed Stop state; no new task may be submitted until it is explicitly recovered or released");
25010
25034
  }
@@ -25049,6 +25073,7 @@ var CursorBridge = class {
25049
25073
  throw cursorSessionError("SESSION_SCOPE_REQUIRED", "repeat read_only=true or an allowed_paths subset for every continued turn");
25050
25074
  }
25051
25075
  await this._ensureCursor();
25076
+ this._assertWorkspaceConfirmed(options.workspacePath);
25052
25077
  const projectPath = this._lastLifecycle && this._lastLifecycle.projectPath || this.projectPath || null;
25053
25078
  if (sessionMode !== "isolated" && !projectPath) {
25054
25079
  throw cursorSessionError("SESSION_WORKSPACE_REQUIRED", "initialize one workspace before creating or continuing a session");
@@ -25089,7 +25114,8 @@ var CursorBridge = class {
25089
25114
  modelPreference = session.modelPreference || null;
25090
25115
  if (duplicate) {
25091
25116
  const existing = this.tasks.get(session.activeTaskId || session.lastTask && session.lastTask.taskId || "");
25092
- return existing ? { duplicate: true, ...options.background === false ? this._collectedTaskView(existing) : this._compactTaskView(existing) } : { duplicate: true, ...this._sessionView(session) };
25117
+ const response = existing ? { duplicate: true, ...options.background === false ? this._collectedTaskView(existing) : this._compactTaskView(existing) } : { duplicate: true, ...this._sessionView(session) };
25118
+ return { duplicate: true, response };
25093
25119
  }
25094
25120
  }
25095
25121
  const job = this._enqueue("do", fullPrompt, {
@@ -25113,9 +25139,7 @@ var CursorBridge = class {
25113
25139
  agentId: sessionMode === "continue" ? session && session.agentId : null,
25114
25140
  agentLabel: sessionMode === "continue" ? session && session.agentLabel : null
25115
25141
  });
25116
- if (options.background !== false) return this._compactTaskView(job);
25117
- await job.promise;
25118
- return this._collectedTaskView(job);
25142
+ return { job };
25119
25143
  }
25120
25144
  _assertNoParallelPathConflict(allowedPaths) {
25121
25145
  if (this._hasGlobalReservation()) {
@@ -25305,6 +25329,17 @@ var CursorBridge = class {
25305
25329
  });
25306
25330
  return run;
25307
25331
  }
25332
+ // Serialize target changes with preparation through enqueue, never with a running Agent.
25333
+ // The UI lock cannot serve this purpose: initialization may acquire it to register a project.
25334
+ _withWorkspaceAdmission(fn) {
25335
+ this._workspacePending++;
25336
+ const run = this._workspaceTail.then(fn, fn).finally(() => {
25337
+ this._workspacePending--;
25338
+ });
25339
+ this._workspaceTail = run.catch(() => {
25340
+ });
25341
+ return run;
25342
+ }
25308
25343
  _withJobLock(job, fn) {
25309
25344
  const previous = job.controlTail || Promise.resolve();
25310
25345
  const run = previous.then(fn, fn);
@@ -25336,6 +25371,8 @@ var CursorBridge = class {
25336
25371
  reason: "adapter-heal",
25337
25372
  runtimeMode: this.runtimeMode,
25338
25373
  adapterStartCwd: this.adapterStartCwd,
25374
+ workspaceKey: this.workspaceKey,
25375
+ workspaceFile: this.workspaceFile,
25339
25376
  ...this.projectPath ? { projectPath: this.projectPath } : {}
25340
25377
  });
25341
25378
  this._lastLifecycle = lifecycleFromEnsureResult(rr, this.runtimeMode);
@@ -27537,6 +27574,8 @@ var CursorBridge = class {
27537
27574
  pluginVersion: PLUGIN_VERSION,
27538
27575
  statusPath: "json-list",
27539
27576
  workspaceKey: workspace.workspaceKey,
27577
+ workspaceIdentitySource: workspace.workspaceIdentitySource,
27578
+ workspaceBusy: workspace.workspaceBusy,
27540
27579
  workspaceConfirmationRequired: workspace.workspaceConfirmationRequired,
27541
27580
  workspaceBindingWarning: workspace.workspaceBindingWarning,
27542
27581
  projectPath: workspace.projectPath,
@@ -27568,9 +27607,45 @@ var CursorBridge = class {
27568
27607
  cursorPid: lifecycle.cursorPid
27569
27608
  };
27570
27609
  }
27571
- _assertWorkspaceConfirmed() {
27610
+ _workspaceRecoveryError(code, message, expectedProjectPath = null) {
27611
+ const error2 = new Error(`${code}: ${message}`);
27612
+ error2.code = code;
27613
+ error2.workspaceRecovery = {
27614
+ workspaceKey: this.workspaceKey,
27615
+ currentProjectPath: this._lastLifecycle?.projectPath || this.projectPath || null,
27616
+ expectedProjectPath,
27617
+ tool: "cursor_init",
27618
+ arguments: expectedProjectPath ? { path: expectedProjectPath } : null,
27619
+ requiresIdle: true,
27620
+ retrySafe: true,
27621
+ submissionStarted: false,
27622
+ nextStep: "Read cursor_status. When idle with no blocking tasks, run cursor_init with the caller's intended absolute project path, verify ready and the exact path, then retry this unsent request once. Never infer the target from a saved default binding or repository name."
27623
+ };
27624
+ return error2;
27625
+ }
27626
+ _assertWorkspaceConfirmed(workspacePath) {
27627
+ let expected = null;
27628
+ if (workspacePath !== void 0) {
27629
+ if (typeof workspacePath !== "string" || !isAbsoluteWorkspacePath(workspacePath)) {
27630
+ throw cursorSessionError("WORKSPACE_PATH_INVALID", "workspace_path must be an absolute project directory or .code-workspace path");
27631
+ }
27632
+ expected = normalizeWorkspacePath(workspacePath);
27633
+ }
27572
27634
  if (this.workspaceConfirmationRequired) {
27573
- throw new Error("WORKSPACE_CONFIRMATION_REQUIRED: the saved default binding has no host workspace identity. Run cursor_init with the intended project before submitting work.");
27635
+ throw this._workspaceRecoveryError("WORKSPACE_CONFIRMATION_REQUIRED", "this adapter has no host workspace identity. Run cursor_init with the intended project before submitting work; a saved default path is not confirmation.", expected);
27636
+ }
27637
+ if (expected) {
27638
+ const current = this._lastLifecycle?.projectPath || this.projectPath;
27639
+ if (!current) {
27640
+ throw this._workspaceRecoveryError("WORKSPACE_INITIALIZATION_REQUIRED", "Run cursor_init with workspace_path before submitting work.", expected);
27641
+ }
27642
+ const canonical = (path) => {
27643
+ const normalized = normalizeWorkspacePath(path);
27644
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
27645
+ };
27646
+ if (canonical(current) !== canonical(expected)) {
27647
+ throw this._workspaceRecoveryError("WORKSPACE_MISMATCH", "workspace_path does not match the current binding. Run cursor_init with the intended project when idle; no work was submitted.", expected);
27648
+ }
27574
27649
  }
27575
27650
  }
27576
27651
  _ensureTaskCapacity() {
@@ -27618,10 +27693,10 @@ var CursorBridge = class {
27618
27693
  ...this.runtimeModeView(),
27619
27694
  ...this.modelPreferencesView(),
27620
27695
  ...this.sessionRegistryView(),
27621
- busy: uiBusy || parallelRunning > 0 || this.queue.length > 0,
27696
+ busy: uiBusy || parallelRunning > 0 || this.queue.length > 0 || this._workspacePending > 0,
27622
27697
  uiBusy,
27623
27698
  parallelRunning,
27624
- idle: !uiBusy && parallelRunning === 0 && this.queue.length === 0,
27699
+ idle: !uiBusy && parallelRunning === 0 && this.queue.length === 0 && this._workspacePending === 0,
27625
27700
  queued: this.queue.length,
27626
27701
  blockingTaskIds: [...this.activeParallel.values()].filter((job) => !isTerminalTask(job)).map((job) => job.id),
27627
27702
  globallyBlocked: globalBlocked,
@@ -27650,10 +27725,10 @@ var CursorBridge = class {
27650
27725
  }
27651
27726
  } : {
27652
27727
  ...this._compactStatusCommon(),
27653
- busy: uiBusy || parallelRunning > 0 || this.queue.length > 0,
27728
+ busy: uiBusy || parallelRunning > 0 || this.queue.length > 0 || this._workspacePending > 0,
27654
27729
  uiBusy,
27655
27730
  parallelRunning,
27656
- idle: !uiBusy && parallelRunning === 0 && this.queue.length === 0,
27731
+ idle: !uiBusy && parallelRunning === 0 && this.queue.length === 0 && this._workspacePending === 0,
27657
27732
  queued: this.queue.length,
27658
27733
  blockingTaskIds: [...this.activeParallel.values()].filter((job) => !isTerminalTask(job)).map((job) => job.id),
27659
27734
  globallyBlocked: globalBlocked,
@@ -27678,6 +27753,7 @@ function buildSearchInputSchema() {
27678
27753
  type: "object",
27679
27754
  properties: {
27680
27755
  query: { type: "string", description: "Describe the behavior, concept, symbol relationship, or ownership boundary to locate. State intent instead of guessing a directory." },
27756
+ workspace_path: WORKSPACE_PATH_SCHEMA,
27681
27757
  request_context: REQUEST_CONTEXT_SCHEMA
27682
27758
  },
27683
27759
  required: ["query"]
@@ -27699,6 +27775,10 @@ var REQUEST_CONTEXT_SCHEMA = {
27699
27775
  source: { type: "string", enum: ["user", "model", "mixed", "unknown"], description: "user for explicitly supplied user requirements, model for model-authored tasks/inferences, mixed when the prompt clearly separates both; unknown when not established." }
27700
27776
  }
27701
27777
  };
27778
+ var WORKSPACE_PATH_SCHEMA = {
27779
+ type: "string",
27780
+ description: "The caller's intended absolute project directory or .code-workspace path. Supply it when known to reject stale or cross-task bindings before sending. This is a target assertion, not an automatic workspace switch. On a workspace confirmation, initialization, or mismatch error, check idle status, run cursor_init with this path, verify ready, and retry once."
27781
+ };
27702
27782
  function buildToolDefinitions(bridgeInstance) {
27703
27783
  return [
27704
27784
  {
@@ -27724,6 +27804,7 @@ function buildToolDefinitions(bridgeInstance) {
27724
27804
  type: "object",
27725
27805
  properties: {
27726
27806
  prompt: { type: "string", description: "The task Cursor should receive. State the goal, boundaries, and what a complete result looks like." },
27807
+ workspace_path: WORKSPACE_PATH_SCHEMA,
27727
27808
  request_context: REQUEST_CONTEXT_SCHEMA,
27728
27809
  background: { type: "boolean", default: true, description: "When true, return the task ID immediately. When false, wait for the task to finish or need attention." },
27729
27810
  execution: { type: "string", enum: ["fifo", "parallel_agent"], default: "fifo", description: "fifo is the first-in, first-out serial queue and runs one task at a time in a clean chat. parallel_agent creates a separate top-level Cursor Agent." },
@@ -27816,29 +27897,35 @@ var server = new Server(
27816
27897
  { capabilities: { tools: { listChanged: true } } }
27817
27898
  );
27818
27899
  async function ensureBridgeCursor(targetBridge, reason) {
27819
- targetBridge._refreshPersistedRuntimeMode();
27820
- const { ensureCursorRunning: ensureCursorRunning2 } = await Promise.resolve().then(() => (init_launch_cursor(), launch_cursor_exports));
27821
- const r = await ensureCursorRunning2({
27822
- reason,
27823
- runtimeMode: targetBridge.runtimeMode,
27824
- adapterStartCwd: targetBridge.adapterStartCwd,
27825
- ...targetBridge.projectPath ? { projectPath: targetBridge.projectPath } : {}
27900
+ return targetBridge._withWorkspaceAdmission(async () => {
27901
+ targetBridge._refreshPersistedRuntimeMode();
27902
+ const { ensureCursorRunning: ensureCursorRunning2 } = await Promise.resolve().then(() => (init_launch_cursor(), launch_cursor_exports));
27903
+ const r = await ensureCursorRunning2({
27904
+ reason,
27905
+ runtimeMode: targetBridge.runtimeMode,
27906
+ adapterStartCwd: targetBridge.adapterStartCwd,
27907
+ workspaceKey: targetBridge.workspaceKey,
27908
+ workspaceFile: targetBridge.workspaceFile,
27909
+ // Prewarming Cursor is not permission to open another task's saved default project.
27910
+ ...targetBridge.workspaceConfirmationRequired ? { projectPath: null } : targetBridge.projectPath ? { projectPath: targetBridge.projectPath } : {}
27911
+ });
27912
+ targetBridge._lastLifecycle = lifecycleFromEnsureResult(r, targetBridge.runtimeMode);
27913
+ if (targetBridge.runtimeMode === "minimal") {
27914
+ targetBridge._lastPresentation = r.presentation ? { ...r.presentation, at: (/* @__PURE__ */ new Date()).toISOString() } : await targetBridge.applyRuntimePresentation("hide");
27915
+ }
27916
+ return r;
27826
27917
  });
27827
- targetBridge._lastLifecycle = lifecycleFromEnsureResult(r, targetBridge.runtimeMode);
27828
- if (targetBridge.runtimeMode === "minimal") {
27829
- targetBridge._lastPresentation = r.presentation ? { ...r.presentation, at: (/* @__PURE__ */ new Date()).toISOString() } : await targetBridge.applyRuntimePresentation("hide");
27830
- }
27831
- return r;
27832
27918
  }
27833
27919
  function toolErrorResult(error2) {
27834
27920
  const message = error2 instanceof Error ? error2.message : String(error2);
27835
- if (error2 && (error2.uiDiagnostic || error2.modelSelection || error2.pickerRead || error2.cdp)) {
27921
+ if (error2 && (error2.uiDiagnostic || error2.modelSelection || error2.pickerRead || error2.cdp || error2.workspaceRecovery)) {
27836
27922
  const payload = {
27837
27923
  error: { code: error2.code || error2.uiDiagnostic?.code || "CURSOR_BRIDGE_ERROR", message },
27838
27924
  ...error2.uiDiagnostic ? { uiDiagnostic: error2.uiDiagnostic } : {},
27839
27925
  ...error2.modelSelection ? { modelSelection: error2.modelSelection } : {},
27840
27926
  ...error2.pickerRead ? { pickerRead: error2.pickerRead } : {},
27841
- ...error2.cdp ? { cdp: error2.cdp } : {}
27927
+ ...error2.cdp ? { cdp: error2.cdp } : {},
27928
+ ...error2.workspaceRecovery ? { workspaceRecovery: error2.workspaceRecovery } : {}
27842
27929
  };
27843
27930
  return {
27844
27931
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
@@ -27859,11 +27946,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request2) => {
27859
27946
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
27860
27947
  }
27861
27948
  if (name === "cursor_context_engine" || name === "cursor_search" || name === "cursor_search_deep") {
27862
- const result = await bridge.contextEngine(String(args && args.query || ""), { requestContext: args && args.request_context });
27949
+ const result = await bridge.contextEngine(String(args && args.query || ""), { requestContext: args && args.request_context, workspacePath: args && args.workspace_path });
27863
27950
  return { content: [{ type: "text", text: String(result) }] };
27864
27951
  }
27865
27952
  if (name === "cursor_do") {
27866
27953
  const result = await bridge.doTask(String(args && args.prompt || ""), {
27954
+ workspacePath: args && args.workspace_path,
27867
27955
  requestContext: args && args.request_context,
27868
27956
  background: !args || args.background !== false,
27869
27957
  execution: args && args.execution,
@@ -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.2.5",
13
+ packageVersion: "0.2.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.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Windows only. Use Cursor Context Engine and bounded, explicitly continuous 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": "6.0.5"
50
+ "embeddedProductVersion": "6.0.6"
51
51
  }
52
52
  }
@@ -20,7 +20,7 @@ Call `cursor_context_engine` when one or more of these are true:
20
20
 
21
21
  Prefer one CCE investigation over starting an Explore subagent merely to locate or understand project code. Let Cursor choose focused or extended depth from what it discovers.
22
22
 
23
- When these semantic conditions match, make CCE the first project-discovery surface. Do not establish the answer through generic context-mode, grep, or blind local exploration before trying CCE. If Claude Code denies an initial context-mode collection call with a CCE routing message, call `cursor_context_engine` once instead of retrying another `ctx_*` tool. A failed, denied, unavailable, or `NOT_FOUND` CCE attempt releases this priority and allows a bounded local fallback.
23
+ When these semantic conditions match, make CCE the first project-discovery surface. Do not establish the answer through generic context-mode, grep, or blind local exploration before trying CCE. If Claude Code denies an initial context-mode collection call with a CCE routing message, call `cursor_context_engine` once instead of retrying another `ctx_*` tool. A failed, denied, unavailable, or `NOT_FOUND` CCE attempt releases this priority and allows a bounded local fallback. A pre-send workspace-confirmation error may be recoverable; handle it below before falling back.
24
24
 
25
25
  ## Keep deterministic work local
26
26
 
@@ -30,10 +30,28 @@ Do not call CCE when any of these apply:
30
30
  - The needed code is already present in the current context.
31
31
  - The work is a trivial single-file edit or only requires running tests, inspecting logs, checking a build, or examining Git state.
32
32
  - The question concerns external libraries, APIs, current documentation, or the public web.
33
- - The user opted out of Cursor, CCE is unavailable, or the workspace is not initialized.
33
+ - The user opted out of Cursor or CCE is unavailable.
34
34
 
35
35
  Do not submit the same lookup to CCE and another semantic system in parallel. Use a second evidence surface only to close a specific gap or verify a consequential claim.
36
36
 
37
+ ## Bind a known workspace before sending
38
+
39
+ Pass the intended absolute `workspace_path` to `cursor_context_engine` when the installed schema supports it. Use the host-provided workspace root/current-task cwd unless the request explicitly targets another project. The field asserts the target before sending; it never switches or registers a project. Do not infer the target from a repository basename or a saved default binding.
40
+
41
+ For an older schema without `workspace_path`, use `cursor_status` to verify the exact ready project path before sending. `initialized=true` only shows that a binding exists; it does not prove the current request is confirmed for the intended workspace. `cursor_init` is the only explicit operation that may initialize, register, or switch a workspace.
42
+
43
+ If no target is known, do not infer or add `workspace_path`, and never call `cursor_init` for a guessed path. Existing safe host bindings may still be used; a workspace-confirmation error without a known target cannot use this recovery and should be reported before any necessary local fallback.
44
+
45
+ ## Recover one known workspace
46
+
47
+ For a pre-send `WORKSPACE_CONFIRMATION_REQUIRED`, `WORKSPACE_INITIALIZATION_REQUIRED`, or `WORKSPACE_MISMATCH` error with a known target:
48
+
49
+ 1. Call `cursor_status`. Continue only when it confirms idle, no queued or blocking work, and `workspaceBusy=false` when that field is exposed.
50
+ 2. Call `cursor_init` with that exact absolute path. Require `ready=true`, `workspaceConfirmationRequired=false`, and the exact `projectPath`; in Agents Window also check `workspaceBinding.ok`, the local file-URI identity and workspace ID.
51
+ 3. Retry the original CCE query once.
52
+
53
+ Do not treat these errors as CCE unavailability before this recovery. Do not initialize when status is busy, identity is ambiguous, `needs_attention` is present, or send state is uncertain; explain the condition and use only the necessary local fallback. Do not cancel other work, change the model, or retry in a loop.
54
+
37
55
  ## Submit one natural-language intent
38
56
 
39
57
  Call `cursor_context_engine` once with the question's real intent. Include a known symbol, subsystem, or path only when it is a useful lead.
@@ -9,6 +9,14 @@ Use Cursor as an execution partner. Keep direction, scope decisions, risk owners
9
9
 
10
10
  Declare `request_context` for each call: an AI caller uses `sender="model"`; set `source="user"` for explicitly supplied user requirements, `"model"` for your own task/inference, or `"mixed"` when both appear. In mixed prompts, label user-confirmed requirements separately from your additions. Use `"unknown"` where provenance is unavailable; a user asking you to use Cursor does not make your authored message user-authored. These labels are declarations, not authentication or extra permission, and do not carry over to later session turns.
11
11
 
12
+ ## Bind a known workspace
13
+
14
+ For CCE or `cursor_do`, pass the intended absolute `workspace_path` when the installed tool schema supports it. Use the host-provided workspace root/current-task cwd unless the request explicitly targets another project. The field is a pre-send assertion: it never selects, switches, or registers a workspace. Do not infer the target from a repository basename or a saved default binding.
15
+
16
+ With an older schema, use `cursor_status` to verify the exact ready project path before sending. `initialized=true` only means a binding exists; it does not confirm the workspace for this request. `cursor_init` remains the only explicit initialization, registration, or switch operation.
17
+
18
+ On pre-send `WORKSPACE_CONFIRMATION_REQUIRED`, `WORKSPACE_INITIALIZATION_REQUIRED`, or `WORKSPACE_MISMATCH` with a known target, first call `cursor_status`. Only if it confirms idle, no queued or blocking work, and `workspaceBusy=false` when exposed, call `cursor_init` for the exact path. Require `ready=true`, `workspaceConfirmationRequired=false`, and the exact `projectPath`; in Agents Window also check `workspaceBinding.ok`, the local file-URI identity and workspace ID. Retry the original CCE query or `cursor_do` submission once. Do not run this recovery for `needs_attention`, uncertain send state, busy status, or ambiguous identity; report the condition and use the necessary local fallback. Do not cancel other work, change model settings, or retry in a loop.
19
+
12
20
  ## Respect execution controls
13
21
 
14
22
  - Do not call `cursor_do` when the user explicitly says not to use Cursor or not to delegate. A direct user opt-out always wins.
@@ -75,7 +83,7 @@ Do not choose parallel execution merely because there are many tasks. When depen
75
83
 
76
84
  1. Record the relevant pre-dispatch workspace state so later review can distinguish existing user changes.
77
85
  2. Form one independent task envelope per task using [delegation-contract.md](references/delegation-contract.md). Write its narrative instructions in the language of the user's current substantive task unless the user explicitly requests another language. Do not persist an inferred language or replace a clear conversational signal with the host/OS locale.
78
- 3. Call `cursor_do` with `background=true` and save its compact submission receipt. Use `background=false` only when an immediate synchronous full result is required. Use only the documented `session_mode` and `session_id` fields when continuity is explicit; never infer continuity from the visible chat.
86
+ 3. Call `cursor_do` with `background=true` and save its compact submission receipt. Include `workspace_path` when the exact target is known and the schema supports it. Use `background=false` only when an immediate synchronous full result is required. Use only the documented `session_mode` and `session_id` fields when continuity is explicit; never infer continuity from the visible chat.
79
87
  4. Save each returned `task_id`; for persistent work also save `session_id`. Treat `agent_id` as verification evidence, not the continuation handle.
80
88
  5. If a parallel submission does not return a usable `agent_id`, stop expanding the parallel batch and use `fifo` or report the ambiguous state.
81
89