pi-cursor-bridge 0.2.5 → 0.2.7

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.7",
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.7 embeds Cursor Bridge 6.0.7, 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)
@@ -12907,6 +12907,11 @@ async function ensureCursorViaSupervisor(options = {}) {
12907
12907
  ok: !!response.ok,
12908
12908
  status: response.status,
12909
12909
  message: response.message,
12910
+ needsAction: response.needsAction || null,
12911
+ nextStep: response.nextStep || null,
12912
+ retryable: response.retryable === true,
12913
+ cursorExecutable: response.cursorExecutable || null,
12914
+ cursorExecutableSource: response.cursorExecutableSource || null,
12910
12915
  port: response.port,
12911
12916
  exe: response.exe,
12912
12917
  cursorPid: response.cursorPid || null,
@@ -12989,8 +12994,8 @@ function withLifecycle(result, mode, extra = {}) {
12989
12994
  };
12990
12995
  }
12991
12996
  async function ensureCursorRunning(options = {}) {
12992
- const bindingFile = options.workspaceFile || resolveWorkspaceBindingFile();
12993
- const bindingKey = options.workspaceKey || resolveWorkspaceBindingKey();
12997
+ const bindingFile = Object.hasOwn(options, "workspaceFile") ? options.workspaceFile : resolveWorkspaceBindingFile();
12998
+ const bindingKey = options.workspaceKey || resolveWorkspaceBindingKey(process.env, { cwd: options.adapterStartCwd ?? process.cwd() });
12994
12999
  const persistedBinding = readWorkspaceBinding(bindingFile, bindingKey);
12995
13000
  const hostProjectPath = resolveClaudeProjectPath();
12996
13001
  const projectPath = Object.hasOwn(options, "projectPath") ? options.projectPath : resolveProjectPath(persistedBinding && persistedBinding.projectPath || process.env.CURSOR_PROJECT_PATH, {
@@ -22985,7 +22990,7 @@ function updateCursorSessionRegistry(filePath, mutator, options = {}) {
22985
22990
  // server.mjs
22986
22991
  init_cursor_ensure_core();
22987
22992
  init_lifecycle_paths();
22988
- var PLUGIN_VERSION = "6.0.5";
22993
+ var PLUGIN_VERSION = "6.0.7";
22989
22994
  var CDP_PORT2 = Number(process.env.CURSOR_BRIDGE_CDP_PORT || 9223);
22990
22995
  var ORIGIN = `http://localhost:${CDP_PORT2}`;
22991
22996
  var QUERY_TIMEOUT = Number(process.env.CURSOR_BRIDGE_TIMEOUT || 3e5);
@@ -23373,6 +23378,24 @@ var EXPR_PROVIDER_ERROR = `(function(){
23373
23378
  const signature=[title,message,requestId||''].join('|');
23374
23379
  return JSON.stringify({found:true,title,message,requestId,retryAvailable,signature});
23375
23380
  })()`;
23381
+ var STOP_COMPOSER_BODY = `
23382
+ const findStopComposers=(rawId)=>{
23383
+ const legacy=[...document.querySelectorAll('.composer-bar[data-composer-id]')]
23384
+ .filter(e=>e.offsetParent!==null&&e.dataset.composerId&&(!rawId||e.dataset.composerId===rawId));
23385
+ if(legacy.length)return {modern:false,composers:legacy};
23386
+ return {modern:true,composers:[...document.querySelectorAll('[data-agent-panel-conversation-shell][data-glass-agent-id]')]
23387
+ .filter(e=>e.offsetParent!==null&&e.dataset.glassAgentId&&(!rawId||e.dataset.glassAgentId===rawId))};
23388
+ };
23389
+ const modernComposerStatus=(composer,entries)=>{
23390
+ const ownStatus=composer.dataset.composerStatus;
23391
+ if(ownStatus)return ownStatus;
23392
+ const matches=entries.filter(e=>e&&e.id==='local:'+composer.dataset.glassAgentId);
23393
+ if(matches.length!==1)return null;
23394
+ const entry=matches[0];
23395
+ if(entry.showSpinner||entry.icon==='loading')return 'generating';
23396
+ return ({'check-circled':'completed','circle-slash':'cancelled',warning:'failed','needs-attention':'needs_attention'})[entry.icon]||null;
23397
+ };
23398
+ `;
23376
23399
  function exprClickSelectedAgentStop(agentId) {
23377
23400
  const expected = JSON.stringify(String(agentId));
23378
23401
  return `(function(){
@@ -23385,52 +23408,52 @@ function exprClickSelectedAgentStop(agentId) {
23385
23408
  if(!selected||selected.id!==${expected}){
23386
23409
  return JSON.stringify({clicked:false,state:'selected_agent_mismatch',selectedId:selected&&selected.id||null,selectedCount:selectedEntries.length});
23387
23410
  }
23411
+ ${STOP_COMPOSER_BODY}
23388
23412
  const expectedComposerId=String(${expected}).replace(/^local:/,'');
23389
- const composers=[...document.querySelectorAll('.composer-bar[data-composer-id]')]
23390
- .filter(e=>e.offsetParent!==null&&e.dataset.composerId===expectedComposerId);
23413
+ const {modern,composers}=findStopComposers(expectedComposerId);
23391
23414
  if(composers.length!==1){
23392
23415
  return JSON.stringify({clicked:false,state:'composer_identity_mismatch',selectedId:selected.id,count:composers.length});
23393
23416
  }
23394
23417
  const composer=composers[0];
23395
- if(composer.dataset.composerStatus!=='generating'){
23396
- return JSON.stringify({clicked:false,state:'composer_not_generating',selectedId:selected.id,status:composer.dataset.composerStatus||null});
23418
+ const status=modern?modernComposerStatus(composer,entries):composer.dataset.composerStatus||null;
23419
+ if(status!=='generating'){
23420
+ return JSON.stringify({clicked:false,state:'composer_not_generating',selectedId:selected.id,status});
23397
23421
  }
23398
- const generationButtons=[...composer.querySelectorAll('button.ui-prompt-input-submit-button[data-state="stop"][aria-label="Stop generation"]')]
23399
- .filter(button=>button.offsetParent!==null&&!button.disabled&&button.closest('.composer-bar')===composer);
23422
+ const belongs=button=>button.offsetParent!==null&&!button.disabled&&button.closest(modern?'[data-glass-agent-id]':'.composer-bar')===composer;
23423
+ const generationButtons=[...composer.querySelectorAll(modern?'button.ui-prompt-input-submit-button[aria-label="Stop generation"]':'button.ui-prompt-input-submit-button[data-state="stop"][aria-label="Stop generation"]')]
23424
+ .filter(belongs);
23400
23425
  const commandButtons=[...composer.querySelectorAll('button.ui-shell-tool-call__glass-stop[aria-label="Stop command"]')]
23401
- .filter(button=>button.offsetParent!==null&&!button.disabled&&button.closest('.composer-bar')===composer);
23426
+ .filter(belongs);
23402
23427
  const buttons=[...generationButtons,...commandButtons];
23403
23428
  if(buttons.length!==1){
23404
23429
  return JSON.stringify({clicked:false,state:buttons.length?'ambiguous_stop_controls':'stop_control_missing',selectedId:selected.id,count:buttons.length});
23405
23430
  }
23406
23431
  buttons[0].click();
23407
- return JSON.stringify({clicked:true,state:'clicked',selectedId:selected.id,composerId:composer.dataset.composerId,control:generationButtons.length?'stop_generation':'stop_command'});
23432
+ return JSON.stringify({clicked:true,state:'clicked',selectedId:selected.id,composerId:expectedComposerId,control:generationButtons.length?'stop_generation':'stop_command'});
23408
23433
  })()`;
23409
23434
  }
23410
- var EXPR_VISIBLE_COMPOSER = `(function(){
23411
- const composers=[...document.querySelectorAll('.composer-bar[data-composer-id]')]
23412
- .filter(e=>e.offsetParent!==null&&e.dataset.composerId);
23413
- if(composers.length!==1){
23414
- return JSON.stringify({ok:false,state:composers.length?'ambiguous_composers':'composer_missing',count:composers.length});
23415
- }
23416
- const composer=composers[0];
23417
- return JSON.stringify({
23418
- ok:true,
23419
- id:'local:'+composer.dataset.composerId,
23420
- composerId:composer.dataset.composerId,
23421
- status:composer.dataset.composerStatus||null
23422
- });
23423
- })()`;
23424
23435
  function exprClickBoundComposerStop(agentId) {
23425
23436
  const expected = JSON.stringify(String(agentId));
23426
23437
  return `(function(){
23438
+ ${STOP_COMPOSER_BODY}
23427
23439
  const expectedComposerId=String(${expected}).replace(/^local:/,'');
23428
- const composers=[...document.querySelectorAll('.composer-bar[data-composer-id]')]
23429
- .filter(e=>e.offsetParent!==null&&e.dataset.composerId===expectedComposerId);
23440
+ const {modern,composers}=findStopComposers(expectedComposerId);
23430
23441
  if(composers.length!==1){
23431
23442
  return JSON.stringify({clicked:false,state:'composer_identity_mismatch',count:composers.length});
23432
23443
  }
23433
23444
  const composer=composers[0];
23445
+ if(modern){
23446
+ ${REACT_ADAPTER_BODY}
23447
+ const status=modernComposerStatus(composer,a?a.entries():[]);
23448
+ if(status!=='generating')return JSON.stringify({clicked:false,state:'composer_not_generating',status,composerId:expectedComposerId});
23449
+ const belongs=button=>button.offsetParent!==null&&!button.disabled&&button.closest('[data-glass-agent-id]')===composer;
23450
+ const generationButtons=[...composer.querySelectorAll('button.ui-prompt-input-submit-button[aria-label="Stop generation"]')].filter(belongs);
23451
+ const commandButtons=[...composer.querySelectorAll('button.ui-shell-tool-call__glass-stop[aria-label="Stop command"]')].filter(belongs);
23452
+ const buttons=[...generationButtons,...commandButtons];
23453
+ if(buttons.length!==1)return JSON.stringify({clicked:false,state:buttons.length?'ambiguous_stop_controls':'stop_control_missing',count:buttons.length,composerId:expectedComposerId});
23454
+ buttons[0].click();
23455
+ return JSON.stringify({clicked:true,state:'clicked',composerId:expectedComposerId,control:generationButtons.length?'stop_generation':'stop_command'});
23456
+ }
23434
23457
  if(composer.dataset.composerStatus!=='generating'){
23435
23458
  return JSON.stringify({clicked:false,state:'composer_not_generating',status:composer.dataset.composerStatus||null,composerId:composer.dataset.composerId});
23436
23459
  }
@@ -23540,12 +23563,90 @@ var EXPR_SELECTED_AGENT_MODEL_CONFIG = `(function(){
23540
23563
  if(data&&data.modelConfig&&!configs.includes(data.modelConfig))configs.push(data.modelConfig);
23541
23564
  }
23542
23565
  }
23543
- if(configs.length!==1)return JSON.stringify({found:false,state:'model_config_ambiguous',configCount:configs.length});
23544
- const config=configs[0];
23545
- return JSON.stringify({found:true,modelName:String(config.modelName||''),selectedModels:(config.selectedModels||[]).map(model=>({
23546
- modelId:String(model&&model.modelId||''),
23547
- parameters:Object.fromEntries((model&&model.parameters||[]).map(parameter=>[String(parameter&&parameter.id||''),String(parameter&&parameter.value||'')]))
23548
- }))});
23566
+ if(configs.length>1)return JSON.stringify({found:false,state:'model_config_ambiguous',configCount:configs.length});
23567
+ if(configs.length===1){
23568
+ const config=configs[0];
23569
+ return JSON.stringify({found:true,modelName:String(config.modelName||''),selectedModels:(config.selectedModels||[]).map(model=>({
23570
+ modelId:String(model&&model.modelId||''),
23571
+ parameters:Object.fromEntries((model&&model.parameters||[]).map(parameter=>[String(parameter&&parameter.id||''),String(parameter&&parameter.value||'')]))
23572
+ }))});
23573
+ }
23574
+ // Cursor 3.23's IDE input is Solid; its model trigger is a mounted React
23575
+ // picker whose initialSelections/initialAutoMode come from this composer.
23576
+ const source='legacy_composer_model_trigger';
23577
+ const fail=(state)=>JSON.stringify({found:false,state,source});
23578
+ const input=inputs[0],composer=input.closest('.composer-bar[data-composer-id]');
23579
+ if(!composer||input.closest('.composer-bar')!==composer
23580
+ ||composer.parentElement&&composer.parentElement.closest('.composer-bar')
23581
+ ||composer.querySelectorAll('.composer-bar').length)return fail('legacy_composer_ambiguous');
23582
+ const composerId=composer.getAttribute('data-composer-id');
23583
+ const owners=[...document.querySelectorAll('.composer-bar[data-composer-id]')]
23584
+ .filter(node=>node.getAttribute('data-composer-id')===composerId);
23585
+ if(typeof composerId!=='string'||!composerId.trim()||owners.length!==1||owners[0]!==composer)return fail('legacy_composer_ambiguous');
23586
+ const triggers=[...composer.querySelectorAll('.vscode-model-picker__trigger')];
23587
+ if(triggers.length!==1||!triggers[0].classList.contains('vscode-model-picker__trigger')
23588
+ ||triggers[0].closest('.composer-bar')!==composer)return fail('legacy_model_trigger_ambiguous');
23589
+ const trigger=triggers[0],keys=Object.keys(trigger).filter(key=>key.startsWith('__reactFiber$'));
23590
+ if(keys.length!==1)return fail('legacy_model_tree_unconfirmed');
23591
+ const levels=[],visited=new Set();
23592
+ let root=null,currentRoot=null;
23593
+ for(let fiber=trigger[keys[0]];fiber;fiber=fiber.return){
23594
+ if(levels.length>=256||visited.has(fiber))return fail('legacy_model_tree_unconfirmed');
23595
+ const alternate=fiber.alternate||null;
23596
+ if(alternate&&(alternate===fiber||alternate.alternate!==fiber||visited.has(alternate)))return fail('legacy_model_tree_unconfirmed');
23597
+ visited.add(fiber);if(alternate)visited.add(alternate);
23598
+ const pair=alternate?[fiber,alternate]:[fiber];
23599
+ levels.push(pair);
23600
+ const state=fiber.stateNode;
23601
+ if(state&&state.current){
23602
+ if(fiber.tag!==3||fiber.return||!pair.includes(state.current)||alternate&&(alternate.tag!==3||alternate.stateNode!==state))return fail('legacy_model_tree_unconfirmed');
23603
+ root=state;currentRoot=state.current;break;
23604
+ }
23605
+ }
23606
+ if(!root)return fail('legacy_model_tree_unconfirmed');
23607
+ // DOM expandos may retain an old fiber or a borrowed return chain. Each
23608
+ // edge must belong to the committed parent's actual child/sibling list.
23609
+ const currentPath=[currentRoot],currentVisited=new Set(currentPath);
23610
+ let parent=currentRoot,nodeBudget=2048;
23611
+ for(let depth=levels.length-2;depth>=0;depth--){
23612
+ const candidates=[],siblings=new Set();
23613
+ for(let child=parent.child;child;child=child.sibling){
23614
+ if(--nodeBudget<0||siblings.has(child))return fail('legacy_model_tree_unconfirmed');
23615
+ siblings.add(child);
23616
+ if(levels[depth].includes(child))candidates.push(child);
23617
+ }
23618
+ if(candidates.length!==1||currentVisited.has(candidates[0]))return fail('legacy_model_tree_unconfirmed');
23619
+ parent=candidates[0];currentVisited.add(parent);currentPath.push(parent);
23620
+ }
23621
+ if(parent.stateNode!==trigger)return fail('legacy_model_tree_unconfirmed');
23622
+ const snapshots=new Set();
23623
+ for(const fiber of currentPath){
23624
+ const props=fiber.memoizedProps;
23625
+ if(!props||!Object.prototype.hasOwnProperty.call(props,'initialSelections')
23626
+ &&!Object.prototype.hasOwnProperty.call(props,'initialAutoMode'))continue;
23627
+ if(!Object.prototype.hasOwnProperty.call(props,'initialSelections')||!Object.prototype.hasOwnProperty.call(props,'initialAutoMode')
23628
+ ||!Array.isArray(props.initialSelections)||typeof props.initialAutoMode!=='boolean')return fail('legacy_model_schema_unconfirmed');
23629
+ // Auto is never evidence for a named model, even if stale selections remain.
23630
+ if(props.initialAutoMode)return fail('legacy_model_auto');
23631
+ if(props.initialSelections.length!==1)return fail('legacy_model_selection_ambiguous');
23632
+ const selected=props.initialSelections[0];
23633
+ if(!selected||typeof selected.modelId!=='string'||!selected.modelId.trim()
23634
+ ||selected.modelId!==selected.modelId.trim()||selected.count!==1||!Array.isArray(selected.parameters))return fail('legacy_model_schema_unconfirmed');
23635
+ if(selected.modelId.toLowerCase()==='default')return fail('legacy_model_auto');
23636
+ const parameters=[];
23637
+ for(const parameter of selected.parameters){
23638
+ if(!parameter||typeof parameter.id!=='string'||!parameter.id.trim()||parameter.id!==parameter.id.trim()
23639
+ ||typeof parameter.value!=='string'||!parameter.value.length||parameters.some(entry=>entry[0]===parameter.id))return fail('legacy_model_schema_unconfirmed');
23640
+ parameters.push([parameter.id,parameter.value]);
23641
+ }
23642
+ parameters.sort((a,b)=>a[0]<b[0]?-1:a[0]>b[0]?1:0);
23643
+ snapshots.add(JSON.stringify({modelName:selected.modelId,selectedModels:[{modelId:selected.modelId,parameters:Object.fromEntries(parameters)}]}));
23644
+ }
23645
+ if(!snapshots.size)return fail('legacy_model_schema_unconfirmed');
23646
+ if(snapshots.size!==1)return fail('legacy_model_config_ambiguous');
23647
+ const snapshot=JSON.parse([...snapshots][0]);
23648
+ if(root.current!==currentRoot)return fail('legacy_model_tree_unconfirmed');
23649
+ return JSON.stringify({found:true,...snapshot,source});
23549
23650
  })()`;
23550
23651
  function normalizeModelPickerText(value) {
23551
23652
  return String(value || "").trim().toLowerCase().replace(/extra[\s_-]*high/g, "xhigh").replace(/[^a-z0-9]+/g, "");
@@ -23709,30 +23810,93 @@ function matchSelectedAgentModelConfig(snapshot, requestedModel, requestedEffort
23709
23810
  return { modelId: match.modelId, effort: effort || null };
23710
23811
  }
23711
23812
  function exprSelectAgentWorkspace(projectPath) {
23712
- return `(async function(){
23713
- const wanted=String(${JSON.stringify(String(projectPath || ""))}).replace(/\\\\/g,'/').replace(/^\\/([a-z]:\\/)/i,'$1').replace(/\\/+$/,'').toLowerCase();
23714
- const normalize=value=>String(value||'').replace(/\\\\/g,'/').replace(/^\\/([a-z]:\\/)/i,'$1').replace(/\\/+$/,'').toLowerCase();
23813
+ return String.raw`(async function(){
23814
+ const normalize=value=>String(value||'').replace(/\\/g,'/').replace(/^\/([a-z]:\/)/i,'$1').replace(/\/+$/,'').toLowerCase();
23815
+ const wanted=normalize(${JSON.stringify(String(projectPath || ""))});
23715
23816
  const visible=node=>!!(node&&(node.offsetParent!==null||(node.getClientRects&&node.getClientRects().length>0)));
23716
23817
  const pathTriggers=()=>[...document.querySelectorAll('button.ui-select-trigger,button[aria-haspopup="menu"]')]
23717
- .filter(visible).filter(node=>/^[a-z]:\\//i.test(normalize(node.innerText||node.textContent)));
23818
+ .filter(visible).filter(node=>/^[a-z]:\//i.test(normalize(node.innerText||node.textContent)));
23718
23819
  const triggers=pathTriggers();
23719
23820
  const fail=(state,extra={})=>JSON.stringify({ok:false,state,wanted,...extra,
23720
23821
  nextStep:'Select the exact local workspace '+${JSON.stringify(String(projectPath || ""))}+' in the new Agent project picker before retrying.'});
23721
23822
  if(triggers.length!==1)return fail(triggers.length?'workspace_project_selector_ambiguous':'workspace_project_selector_unavailable',{triggerCount:triggers.length});
23722
23823
  triggers[0].click();
23723
23824
  await new Promise(resolve=>setTimeout(resolve,350));
23724
- const menus=[...document.querySelectorAll('[data-component="menu-popup"],[role="menu"]')].filter(visible)
23725
- .filter(menu=>String(menu.getAttribute('aria-label')||'')==='Select a project'||menu.querySelector('[aria-label="Select a project"]'));
23825
+ const menus=[...new Set([...document.querySelectorAll('[data-component="menu-popup"],[role="menu"]')].filter(visible)
23826
+ .map(menu=>menu.getAttribute('role')==='menu'&&menu.getAttribute('aria-label')==='Select a project'
23827
+ ?menu:menu.querySelector('[role="menu"][aria-label="Select a project"]')).filter(visible))];
23828
+ if(menus.length!==1)return fail(menus.length?'workspace_project_option_ambiguous':'workspace_project_option_unavailable',{menuCount:menus.length});
23726
23829
  const rows=[];const seen=new Set();
23727
23830
  for(const menu of menus){
23728
23831
  for(const row of menu.querySelectorAll('[data-component="menu-row"][role="menuitem"],[role="option"]')){
23729
23832
  if(!visible(row)||seen.has(row))continue;seen.add(row);rows.push(row);
23730
23833
  }
23731
23834
  }
23732
- const available=rows.map(row=>String(row.innerText||row.textContent||'').replace(/\\s+/g,' ').trim());
23733
- const matches=rows.filter(row=>normalize(row.innerText||row.textContent)===wanted);
23835
+ const available=rows.map(row=>String(row.innerText||row.textContent||'').replace(/\s+/g,' ').trim());
23836
+ const rowWorkspace=row=>{
23837
+ const key=Object.keys(row).find(key=>key.startsWith('__reactFiber$'));
23838
+ const levels=[];const visited=new Set();
23839
+ let root=null,currentRoot=null,hasProject=false,belowMenu=true;
23840
+ for(let fiber=key&&row[key];fiber;fiber=fiber.return){
23841
+ if(levels.length>=256||visited.has(fiber))return null;
23842
+ visited.add(fiber);
23843
+ const alternate=fiber.alternate||null;
23844
+ if(alternate&&(alternate===fiber||alternate.alternate!==fiber||visited.has(alternate)))return null;
23845
+ const pair=alternate?[fiber,alternate]:[fiber];
23846
+ levels.push(pair);
23847
+ if(pair.some(node=>node.stateNode===menus[0]))belowMenu=false;
23848
+ if(belowMenu&&pair.some(node=>node.memoizedProps&&Object.prototype.hasOwnProperty.call(node.memoizedProps,'project')))hasProject=true;
23849
+ const state=fiber.stateNode;
23850
+ if(state&&state.current){
23851
+ if(!pair.includes(state.current)||alternate&&alternate.stateNode!==state)return null;
23852
+ root=state;currentRoot=state.current;break;
23853
+ }
23854
+ }
23855
+ // Older pickers expose the complete path as row text without project metadata.
23856
+ if(!hasProject)return {path:normalize(row.innerText||row.textContent)};
23857
+ if(!root)return null;
23858
+ // A DOM expando can retain an old fiber, including a borrowed current return
23859
+ // chain. Prove every edge through the committed parent's child/sibling list.
23860
+ const currentPath=[currentRoot];const currentVisited=new Set(currentPath);
23861
+ let parent=currentRoot,nodeBudget=2048;
23862
+ for(let depth=levels.length-2;depth>=0;depth--){
23863
+ const candidates=[];const siblings=new Set();
23864
+ for(let child=parent.child;child;child=child.sibling){
23865
+ if(--nodeBudget<0||siblings.has(child))return null;
23866
+ siblings.add(child);
23867
+ if(levels[depth].includes(child))candidates.push(child);
23868
+ }
23869
+ if(candidates.length!==1||currentVisited.has(candidates[0]))return null;
23870
+ parent=candidates[0];currentVisited.add(parent);currentPath.push(parent);
23871
+ }
23872
+ if(parent.stateNode!==row)return null;
23873
+ const menuIndexes=currentPath.flatMap((node,index)=>node.stateNode===menus[0]?[index]:[]);
23874
+ if(menuIndexes.length!==1)return null;
23875
+ const projects=new Set();
23876
+ for(const fiber of currentPath.slice(menuIndexes[0]+1)){
23877
+ const props=fiber.memoizedProps;
23878
+ if(props&&Object.prototype.hasOwnProperty.call(props,'project'))projects.add(props.project);
23879
+ }
23880
+ if(!projects.size)return null;
23881
+ const identities=new Set();
23882
+ for(const project of projects){
23883
+ const identifier=project&&project.workspaceIdentifier;
23884
+ const uri=identifier&&(identifier.uri||identifier.configPath);
23885
+ if(!project||project.remoteAuthority||!identifier||identifier.remoteAuthority||typeof identifier.id!=='string'||!identifier.id.trim()
23886
+ ||!uri||uri.scheme!=='file'||uri.authority||uri.remoteAuthority)return null;
23887
+ const paths=[uri.fsPath,uri._fsPath,uri.path].filter(value=>typeof value==='string'&&value.length).map(normalize);
23888
+ if(!paths.length||paths.some(path=>path!==paths[0]))return null;
23889
+ identities.add(JSON.stringify([identifier.id,paths[0]]));
23890
+ }
23891
+ if(identities.size!==1)return null;
23892
+ const [workspaceId,path]=JSON.parse([...identities][0]);
23893
+ return {path,workspaceId,root,currentRoot};
23894
+ };
23895
+ const matches=rows.map(row=>({row,identity:rowWorkspace(row)})).filter(match=>match.identity&&match.identity.path===wanted);
23734
23896
  if(matches.length!==1)return fail(matches.length?'workspace_project_option_ambiguous':'workspace_project_option_unavailable',{available});
23735
- matches[0].click();
23897
+ const selected=matches[0];
23898
+ if(selected.identity.root&&selected.identity.root.current!==selected.identity.currentRoot)return fail('workspace_project_option_unavailable',{available});
23899
+ selected.row.click();
23736
23900
  await new Promise(resolve=>setTimeout(resolve,350));
23737
23901
  return JSON.stringify({ok:true,state:'workspace_project_selection_requested',workspace:wanted,available});
23738
23902
  })()`;
@@ -23902,7 +24066,11 @@ var REACT_ADAPTER_BODY = `
23902
24066
  let globalSelectAgent=null;
23903
24067
  let globalSelectedAgentId=null;
23904
24068
  let sectionIdByAgentId=null;
23905
- for(const root of document.querySelectorAll('.glass-sidebar-agent-list-container')){
24069
+ const roots=new Set([
24070
+ ...document.querySelectorAll('.glass-sidebar-agent-list-container'),
24071
+ ...document.querySelectorAll('.ui-sidebar-section-head')
24072
+ ]);
24073
+ for(const root of roots){
23906
24074
  const nodes=[]; let n=root;
23907
24075
  for(let i=0;n&&i<24;i++,n=n.parentElement)nodes.push(n);
23908
24076
  for(const node of nodes){
@@ -24021,6 +24189,26 @@ var REACT_ADAPTER_BODY = `
24021
24189
  };
24022
24190
  };
24023
24191
  const a=findAdapter();`;
24192
+ var EXPR_VISIBLE_COMPOSER = `(function(){
24193
+ ${STOP_COMPOSER_BODY}
24194
+ const {modern,composers}=findStopComposers(null);
24195
+ if(composers.length!==1){
24196
+ return JSON.stringify({ok:false,state:composers.length?'ambiguous_composers':'composer_missing',count:composers.length});
24197
+ }
24198
+ const composer=composers[0];
24199
+ let status=composer.dataset.composerStatus||null;
24200
+ if(modern){
24201
+ ${REACT_ADAPTER_BODY}
24202
+ status=modernComposerStatus(composer,a?a.entries():[]);
24203
+ }
24204
+ const composerId=modern?composer.dataset.glassAgentId:composer.dataset.composerId;
24205
+ return JSON.stringify({
24206
+ ok:true,
24207
+ id:'local:'+composerId,
24208
+ composerId,
24209
+ status
24210
+ });
24211
+ })()`;
24024
24212
  var EXPR_AGENT_ADAPTER_READY = `(function(){${REACT_ADAPTER_BODY}return !!a;})()`;
24025
24213
  var EXPR_PAGE_CAPABILITIES = `(function(){${INPUT_PICKER_BODY}
24026
24214
  const visible=node=>!!(node&&(node.offsetParent!==null||(typeof node.getClientRects==='function'&&node.getClientRects().length>0)));
@@ -24282,12 +24470,12 @@ var CursorBridge = class {
24282
24470
  this.runtimeModeSource = options.runtimeMode !== void 0 ? "constructor" : persistedRuntimeMode ? "persistent" : process.env.CURSOR_BRIDGE_RUNTIME_MODE ? "environment" : "default";
24283
24471
  this.runtimeModeScope = persistedRuntimeMode ? "persistent" : options.runtimeMode !== void 0 ? "constructor" : process.env.CURSOR_BRIDGE_RUNTIME_MODE ? "environment" : "default";
24284
24472
  this.workspaceFile = options.workspaceFile === null ? null : resolve8(options.workspaceFile || resolveWorkspaceBindingFile());
24285
- this.workspaceKey = options.workspaceKey || resolveWorkspaceBindingKey();
24473
+ this.workspaceKey = options.workspaceKey || resolveWorkspaceBindingKey(process.env, { cwd: this.adapterStartCwd });
24286
24474
  const persistedWorkspace = options.projectPath === void 0 ? readWorkspaceBinding(this.workspaceFile, this.workspaceKey) : null;
24287
24475
  this.projectPath = options.projectPath !== void 0 ? resolve8(String(options.projectPath)) : persistedWorkspace && persistedWorkspace.projectPath || null;
24288
24476
  this.workspaceSource = options.projectPath !== void 0 ? "constructor" : persistedWorkspace ? "persistent_init" : "auto_detect";
24289
24477
  this.workspaceUpdatedAt = persistedWorkspace && persistedWorkspace.updatedAt || null;
24290
- this.workspaceConfirmationRequired = this.workspaceKey === "default" && !!persistedWorkspace;
24478
+ this.workspaceConfirmationRequired = this.workspaceKey === "default" && options.projectPath === void 0;
24291
24479
  this.modelPreferencesFile = options.modelPreferencesFile === null ? null : resolve8(options.modelPreferencesFile || resolveCursorModelPreferencesFile());
24292
24480
  this.modelPreferences = readCursorModelPreferences(this.modelPreferencesFile);
24293
24481
  this.sessionFile = options.sessionFile === null ? null : resolve8(options.sessionFile || resolveCursorSessionRegistryFile());
@@ -24303,6 +24491,8 @@ var CursorBridge = class {
24303
24491
  this.activeParallel = /* @__PURE__ */ new Map();
24304
24492
  this.currentJob = null;
24305
24493
  this._uiTail = Promise.resolve();
24494
+ this._workspaceTail = Promise.resolve();
24495
+ this._workspacePending = 0;
24306
24496
  this.parallelRestoreAgentId = null;
24307
24497
  this.parallelRestoreTargetId = null;
24308
24498
  }
@@ -24310,6 +24500,8 @@ var CursorBridge = class {
24310
24500
  const resolvedProjectPath = this.projectPath || this._lastLifecycle && this._lastLifecycle.projectPath || null;
24311
24501
  return {
24312
24502
  workspaceKey: this.workspaceKey,
24503
+ workspaceIdentitySource: this.workspaceKey === "default" ? "unavailable" : this.workspaceKey.split(":")[0],
24504
+ workspaceBusy: this._workspacePending > 0,
24313
24505
  workspaceConfirmationRequired: this.workspaceConfirmationRequired,
24314
24506
  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
24507
  projectPath: resolvedProjectPath,
@@ -24692,8 +24884,12 @@ var CursorBridge = class {
24692
24884
  if (!["reconcile", "collect_result", "close", "forget", "abandon"].includes(action)) {
24693
24885
  throw cursorSessionError("SESSION_ACTION_UNSUPPORTED", "expected reconcile, collect_result, close, forget, or abandon");
24694
24886
  }
24695
- if (action === "reconcile") return this._reconcileSession(id);
24696
- if (action === "collect_result") return this._reconcileSession(id, { collectResult: true });
24887
+ if (action === "reconcile" || action === "collect_result") {
24888
+ return this._withWorkspaceAdmission(() => {
24889
+ this._assertWorkspaceConfirmed();
24890
+ return this._reconcileSession(id, { collectResult: action === "collect_result" });
24891
+ });
24892
+ }
24697
24893
  let result;
24698
24894
  updateCursorSessionRegistry(this.sessionFile, (registry2) => {
24699
24895
  const session = registry2.sessions[id];
@@ -24746,6 +24942,10 @@ var CursorBridge = class {
24746
24942
  return result;
24747
24943
  }
24748
24944
  async initializeWorkspace(projectPath) {
24945
+ const result = await this._withWorkspaceAdmission(() => this._initializeWorkspace(projectPath));
24946
+ return { ...result, workspaceBusy: this._workspacePending > 0 };
24947
+ }
24948
+ async _initializeWorkspace(projectPath) {
24749
24949
  if (this._healing) await this._healing.catch(() => {
24750
24950
  });
24751
24951
  if (this.busy || this.activeParallel.size > 0 || this.queue.length > 0) {
@@ -24971,19 +25171,23 @@ var CursorBridge = class {
24971
25171
  if (!text) throw new Error("query must not be empty");
24972
25172
  if (text.length > 2e4) throw new Error("query exceeds the 20,000-character limit");
24973
25173
  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")
25174
+ const job = await this._withWorkspaceAdmission(async () => {
25175
+ this._assertWorkspaceConfirmed(options.workspacePath);
25176
+ if (this._hasGlobalReservation()) {
25177
+ throw new Error("A global Cursor reservation has an unconfirmed Stop state; resolve blockingTaskIds from cursor_status first");
25178
+ }
25179
+ await this._ensureCursor();
25180
+ this._assertWorkspaceConfirmed(options.workspacePath);
25181
+ return this._enqueue("context_engine", buildContextEnginePrompt(text, requestContext), {
25182
+ requestContext,
25183
+ timeoutMs: QUERY_TIMEOUT,
25184
+ newChat: true,
25185
+ execution: "fifo",
25186
+ readOnly: true,
25187
+ allowedPaths: [],
25188
+ modelPreference: this._modelPreferenceFor("cce"),
25189
+ projectPath: this._lastLifecycle?.projectPath || this.projectPath || null
25190
+ });
24987
25191
  });
24988
25192
  const result = normalizeCceSearchResult(await job.promise);
24989
25193
  this._markTaskResultCollected(job);
@@ -24997,6 +25201,14 @@ var CursorBridge = class {
24997
25201
  return this.contextEngine(query);
24998
25202
  }
24999
25203
  async doTask(prompt, options = {}) {
25204
+ const admitted = await this._withWorkspaceAdmission(() => this._prepareTask(prompt, options));
25205
+ if (admitted.duplicate) return admitted.response;
25206
+ const job = admitted.job;
25207
+ if (options.background !== false) return this._compactTaskView(job);
25208
+ await job.promise;
25209
+ return this._collectedTaskView(job);
25210
+ }
25211
+ async _prepareTask(prompt, options = {}) {
25000
25212
  if (!this.delegationEnabled) {
25001
25213
  throw new Error("cursor_do is disabled by CURSOR_BRIDGE_DELEGATION=off; restart the MCP server without that setting to enable delegation");
25002
25214
  }
@@ -25004,7 +25216,7 @@ var CursorBridge = class {
25004
25216
  if (!text) throw new Error("prompt must not be empty");
25005
25217
  if (text.length > 1e5) throw new Error("prompt exceeds the 100,000-character limit");
25006
25218
  const requestContext = normalizeRequestContext(options.requestContext);
25007
- this._assertWorkspaceConfirmed();
25219
+ this._assertWorkspaceConfirmed(options.workspacePath);
25008
25220
  if (this._hasGlobalReservation()) {
25009
25221
  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
25222
  }
@@ -25049,6 +25261,7 @@ var CursorBridge = class {
25049
25261
  throw cursorSessionError("SESSION_SCOPE_REQUIRED", "repeat read_only=true or an allowed_paths subset for every continued turn");
25050
25262
  }
25051
25263
  await this._ensureCursor();
25264
+ this._assertWorkspaceConfirmed(options.workspacePath);
25052
25265
  const projectPath = this._lastLifecycle && this._lastLifecycle.projectPath || this.projectPath || null;
25053
25266
  if (sessionMode !== "isolated" && !projectPath) {
25054
25267
  throw cursorSessionError("SESSION_WORKSPACE_REQUIRED", "initialize one workspace before creating or continuing a session");
@@ -25089,7 +25302,8 @@ var CursorBridge = class {
25089
25302
  modelPreference = session.modelPreference || null;
25090
25303
  if (duplicate) {
25091
25304
  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) };
25305
+ const response = existing ? { duplicate: true, ...options.background === false ? this._collectedTaskView(existing) : this._compactTaskView(existing) } : { duplicate: true, ...this._sessionView(session) };
25306
+ return { duplicate: true, response };
25093
25307
  }
25094
25308
  }
25095
25309
  const job = this._enqueue("do", fullPrompt, {
@@ -25113,9 +25327,7 @@ var CursorBridge = class {
25113
25327
  agentId: sessionMode === "continue" ? session && session.agentId : null,
25114
25328
  agentLabel: sessionMode === "continue" ? session && session.agentLabel : null
25115
25329
  });
25116
- if (options.background !== false) return this._compactTaskView(job);
25117
- await job.promise;
25118
- return this._collectedTaskView(job);
25330
+ return { job };
25119
25331
  }
25120
25332
  _assertNoParallelPathConflict(allowedPaths) {
25121
25333
  if (this._hasGlobalReservation()) {
@@ -25305,6 +25517,17 @@ var CursorBridge = class {
25305
25517
  });
25306
25518
  return run;
25307
25519
  }
25520
+ // Serialize target changes with preparation through enqueue, never with a running Agent.
25521
+ // The UI lock cannot serve this purpose: initialization may acquire it to register a project.
25522
+ _withWorkspaceAdmission(fn) {
25523
+ this._workspacePending++;
25524
+ const run = this._workspaceTail.then(fn, fn).finally(() => {
25525
+ this._workspacePending--;
25526
+ });
25527
+ this._workspaceTail = run.catch(() => {
25528
+ });
25529
+ return run;
25530
+ }
25308
25531
  _withJobLock(job, fn) {
25309
25532
  const previous = job.controlTail || Promise.resolve();
25310
25533
  const run = previous.then(fn, fn);
@@ -25336,6 +25559,8 @@ var CursorBridge = class {
25336
25559
  reason: "adapter-heal",
25337
25560
  runtimeMode: this.runtimeMode,
25338
25561
  adapterStartCwd: this.adapterStartCwd,
25562
+ workspaceKey: this.workspaceKey,
25563
+ workspaceFile: this.workspaceFile,
25339
25564
  ...this.projectPath ? { projectPath: this.projectPath } : {}
25340
25565
  });
25341
25566
  this._lastLifecycle = lifecycleFromEnsureResult(rr, this.runtimeMode);
@@ -27537,6 +27762,8 @@ var CursorBridge = class {
27537
27762
  pluginVersion: PLUGIN_VERSION,
27538
27763
  statusPath: "json-list",
27539
27764
  workspaceKey: workspace.workspaceKey,
27765
+ workspaceIdentitySource: workspace.workspaceIdentitySource,
27766
+ workspaceBusy: workspace.workspaceBusy,
27540
27767
  workspaceConfirmationRequired: workspace.workspaceConfirmationRequired,
27541
27768
  workspaceBindingWarning: workspace.workspaceBindingWarning,
27542
27769
  projectPath: workspace.projectPath,
@@ -27568,9 +27795,45 @@ var CursorBridge = class {
27568
27795
  cursorPid: lifecycle.cursorPid
27569
27796
  };
27570
27797
  }
27571
- _assertWorkspaceConfirmed() {
27798
+ _workspaceRecoveryError(code, message, expectedProjectPath = null) {
27799
+ const error2 = new Error(`${code}: ${message}`);
27800
+ error2.code = code;
27801
+ error2.workspaceRecovery = {
27802
+ workspaceKey: this.workspaceKey,
27803
+ currentProjectPath: this._lastLifecycle?.projectPath || this.projectPath || null,
27804
+ expectedProjectPath,
27805
+ tool: "cursor_init",
27806
+ arguments: expectedProjectPath ? { path: expectedProjectPath } : null,
27807
+ requiresIdle: true,
27808
+ retrySafe: true,
27809
+ submissionStarted: false,
27810
+ 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."
27811
+ };
27812
+ return error2;
27813
+ }
27814
+ _assertWorkspaceConfirmed(workspacePath) {
27815
+ let expected = null;
27816
+ if (workspacePath !== void 0) {
27817
+ if (typeof workspacePath !== "string" || !isAbsoluteWorkspacePath(workspacePath)) {
27818
+ throw cursorSessionError("WORKSPACE_PATH_INVALID", "workspace_path must be an absolute project directory or .code-workspace path");
27819
+ }
27820
+ expected = normalizeWorkspacePath(workspacePath);
27821
+ }
27572
27822
  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.");
27823
+ 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);
27824
+ }
27825
+ if (expected) {
27826
+ const current = this._lastLifecycle?.projectPath || this.projectPath;
27827
+ if (!current) {
27828
+ throw this._workspaceRecoveryError("WORKSPACE_INITIALIZATION_REQUIRED", "Run cursor_init with workspace_path before submitting work.", expected);
27829
+ }
27830
+ const canonical = (path) => {
27831
+ const normalized = normalizeWorkspacePath(path);
27832
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
27833
+ };
27834
+ if (canonical(current) !== canonical(expected)) {
27835
+ 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);
27836
+ }
27574
27837
  }
27575
27838
  }
27576
27839
  _ensureTaskCapacity() {
@@ -27618,10 +27881,10 @@ var CursorBridge = class {
27618
27881
  ...this.runtimeModeView(),
27619
27882
  ...this.modelPreferencesView(),
27620
27883
  ...this.sessionRegistryView(),
27621
- busy: uiBusy || parallelRunning > 0 || this.queue.length > 0,
27884
+ busy: uiBusy || parallelRunning > 0 || this.queue.length > 0 || this._workspacePending > 0,
27622
27885
  uiBusy,
27623
27886
  parallelRunning,
27624
- idle: !uiBusy && parallelRunning === 0 && this.queue.length === 0,
27887
+ idle: !uiBusy && parallelRunning === 0 && this.queue.length === 0 && this._workspacePending === 0,
27625
27888
  queued: this.queue.length,
27626
27889
  blockingTaskIds: [...this.activeParallel.values()].filter((job) => !isTerminalTask(job)).map((job) => job.id),
27627
27890
  globallyBlocked: globalBlocked,
@@ -27650,10 +27913,10 @@ var CursorBridge = class {
27650
27913
  }
27651
27914
  } : {
27652
27915
  ...this._compactStatusCommon(),
27653
- busy: uiBusy || parallelRunning > 0 || this.queue.length > 0,
27916
+ busy: uiBusy || parallelRunning > 0 || this.queue.length > 0 || this._workspacePending > 0,
27654
27917
  uiBusy,
27655
27918
  parallelRunning,
27656
- idle: !uiBusy && parallelRunning === 0 && this.queue.length === 0,
27919
+ idle: !uiBusy && parallelRunning === 0 && this.queue.length === 0 && this._workspacePending === 0,
27657
27920
  queued: this.queue.length,
27658
27921
  blockingTaskIds: [...this.activeParallel.values()].filter((job) => !isTerminalTask(job)).map((job) => job.id),
27659
27922
  globallyBlocked: globalBlocked,
@@ -27678,6 +27941,7 @@ function buildSearchInputSchema() {
27678
27941
  type: "object",
27679
27942
  properties: {
27680
27943
  query: { type: "string", description: "Describe the behavior, concept, symbol relationship, or ownership boundary to locate. State intent instead of guessing a directory." },
27944
+ workspace_path: WORKSPACE_PATH_SCHEMA,
27681
27945
  request_context: REQUEST_CONTEXT_SCHEMA
27682
27946
  },
27683
27947
  required: ["query"]
@@ -27699,6 +27963,10 @@ var REQUEST_CONTEXT_SCHEMA = {
27699
27963
  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
27964
  }
27701
27965
  };
27966
+ var WORKSPACE_PATH_SCHEMA = {
27967
+ type: "string",
27968
+ 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."
27969
+ };
27702
27970
  function buildToolDefinitions(bridgeInstance) {
27703
27971
  return [
27704
27972
  {
@@ -27724,6 +27992,7 @@ function buildToolDefinitions(bridgeInstance) {
27724
27992
  type: "object",
27725
27993
  properties: {
27726
27994
  prompt: { type: "string", description: "The task Cursor should receive. State the goal, boundaries, and what a complete result looks like." },
27995
+ workspace_path: WORKSPACE_PATH_SCHEMA,
27727
27996
  request_context: REQUEST_CONTEXT_SCHEMA,
27728
27997
  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
27998
  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 +28085,35 @@ var server = new Server(
27816
28085
  { capabilities: { tools: { listChanged: true } } }
27817
28086
  );
27818
28087
  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 } : {}
28088
+ return targetBridge._withWorkspaceAdmission(async () => {
28089
+ targetBridge._refreshPersistedRuntimeMode();
28090
+ const { ensureCursorRunning: ensureCursorRunning2 } = await Promise.resolve().then(() => (init_launch_cursor(), launch_cursor_exports));
28091
+ const r = await ensureCursorRunning2({
28092
+ reason,
28093
+ runtimeMode: targetBridge.runtimeMode,
28094
+ adapterStartCwd: targetBridge.adapterStartCwd,
28095
+ workspaceKey: targetBridge.workspaceKey,
28096
+ workspaceFile: targetBridge.workspaceFile,
28097
+ // Prewarming Cursor is not permission to open another task's saved default project.
28098
+ ...targetBridge.workspaceConfirmationRequired ? { projectPath: null } : targetBridge.projectPath ? { projectPath: targetBridge.projectPath } : {}
28099
+ });
28100
+ targetBridge._lastLifecycle = lifecycleFromEnsureResult(r, targetBridge.runtimeMode);
28101
+ if (targetBridge.runtimeMode === "minimal") {
28102
+ targetBridge._lastPresentation = r.presentation ? { ...r.presentation, at: (/* @__PURE__ */ new Date()).toISOString() } : await targetBridge.applyRuntimePresentation("hide");
28103
+ }
28104
+ return r;
27826
28105
  });
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
28106
  }
27833
28107
  function toolErrorResult(error2) {
27834
28108
  const message = error2 instanceof Error ? error2.message : String(error2);
27835
- if (error2 && (error2.uiDiagnostic || error2.modelSelection || error2.pickerRead || error2.cdp)) {
28109
+ if (error2 && (error2.uiDiagnostic || error2.modelSelection || error2.pickerRead || error2.cdp || error2.workspaceRecovery)) {
27836
28110
  const payload = {
27837
28111
  error: { code: error2.code || error2.uiDiagnostic?.code || "CURSOR_BRIDGE_ERROR", message },
27838
28112
  ...error2.uiDiagnostic ? { uiDiagnostic: error2.uiDiagnostic } : {},
27839
28113
  ...error2.modelSelection ? { modelSelection: error2.modelSelection } : {},
27840
28114
  ...error2.pickerRead ? { pickerRead: error2.pickerRead } : {},
27841
- ...error2.cdp ? { cdp: error2.cdp } : {}
28115
+ ...error2.cdp ? { cdp: error2.cdp } : {},
28116
+ ...error2.workspaceRecovery ? { workspaceRecovery: error2.workspaceRecovery } : {}
27842
28117
  };
27843
28118
  return {
27844
28119
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
@@ -27859,11 +28134,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request2) => {
27859
28134
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
27860
28135
  }
27861
28136
  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 });
28137
+ const result = await bridge.contextEngine(String(args && args.query || ""), { requestContext: args && args.request_context, workspacePath: args && args.workspace_path });
27863
28138
  return { content: [{ type: "text", text: String(result) }] };
27864
28139
  }
27865
28140
  if (name === "cursor_do") {
27866
28141
  const result = await bridge.doTask(String(args && args.prompt || ""), {
28142
+ workspacePath: args && args.workspace_path,
27867
28143
  requestContext: args && args.request_context,
27868
28144
  background: !args || args.background !== false,
27869
28145
  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.7",
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.7",
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.7"
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