pi-cursor-bridge 0.2.7 → 0.2.9

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.7",
3
+ "version": "6.0.9",
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.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.
15
+ Pi wrapper 0.2.9 embeds Cursor Bridge 6.0.9, 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)
@@ -3115,7 +3115,6 @@ var require_utils = __commonJS({
3115
3115
  "use strict";
3116
3116
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3117
3117
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
3118
- var isPort = RegExp.prototype.test.bind(/^\d*$/u);
3119
3118
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3120
3119
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3121
3120
  var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
@@ -3581,12 +3580,8 @@ var require_utils = __commonJS({
3581
3580
  uriTokens.push(host);
3582
3581
  }
3583
3582
  if (typeof component.port === "number" || typeof component.port === "string") {
3584
- const port = String(component.port);
3585
- if (!isPort(port)) {
3586
- throw new TypeError("URI port is malformed.");
3587
- }
3588
3583
  uriTokens.push(":");
3589
- uriTokens.push(port);
3584
+ uriTokens.push(String(component.port));
3590
3585
  }
3591
3586
  return uriTokens.length ? uriTokens.join("") : void 0;
3592
3587
  }
@@ -4029,15 +4024,12 @@ var require_fast_uri = __commonJS({
4029
4024
  }
4030
4025
  return false;
4031
4026
  }
4032
- function isIPLiteral(host) {
4033
- return host[0] === "[" && host[host.length - 1] === "]";
4034
- }
4035
4027
  function hasMalformedComponentPercentEncoding(matches) {
4036
4028
  const host = matches[4];
4037
- return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
4029
+ return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !(host[0] === "[" && host[host.length - 1] === "]") && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
4038
4030
  }
4039
4031
  function canonicalizeHost(parsed, options, schemeHandler, isIP) {
4040
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
4032
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && parsed.host[0] !== "[" && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
4041
4033
  try {
4042
4034
  parsed.host = new URL("http://" + parsed.host).hostname;
4043
4035
  } catch (e) {
@@ -4124,11 +4116,10 @@ var require_fast_uri = __commonJS({
4124
4116
  if (parsed.host) {
4125
4117
  const ipv4result = isIPv4(parsed.host);
4126
4118
  if (ipv4result === false) {
4127
- const bracketedIPLiteral = isIPLiteral(parsed.host);
4128
- const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
4119
+ const bracketedIPLiteral = parsed.host[0] === "[" && parsed.host[parsed.host.length - 1] === "]";
4129
4120
  const ipv6result = normalizeIPv6(parsed.host);
4130
4121
  isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
4131
- malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
4122
+ malformedIPLiteral = bracketedIPLiteral && ipv6result.error === true;
4132
4123
  parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
4133
4124
  if (malformedIPLiteral) {
4134
4125
  parsed.error = parsed.error || "URI host is malformed.";
@@ -4151,17 +4142,14 @@ var require_fast_uri = __commonJS({
4151
4142
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
4152
4143
  }
4153
4144
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
4154
- if (!malformedIPLiteral) {
4155
- malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
4156
- }
4157
- if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
4158
- let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4159
- if (!isIP) {
4160
- host = normalizePercentEncoding(host.toLowerCase());
4161
- }
4162
- parsed.host = reescapeHostDelimiters(host, isIP);
4163
- }
4145
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
4164
4146
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4147
+ if (uri.indexOf("%") !== -1) {
4148
+ if (parsed.host !== void 0 && !malformedIPLiteral) {
4149
+ const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4150
+ parsed.host = reescapeHostDelimiters(host, isIP);
4151
+ }
4152
+ }
4165
4153
  if (parsed.path) {
4166
4154
  parsed.path = normalizePathEncoding(parsed.path);
4167
4155
  }
@@ -11402,6 +11390,36 @@ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as r
11402
11390
  import { createHash as createHash2 } from "node:crypto";
11403
11391
  import { homedir as homedir6 } from "node:os";
11404
11392
  import { dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
11393
+ import * as startupModule from "node:module";
11394
+ function readCursorStartupPreference(options = {}) {
11395
+ let database;
11396
+ try {
11397
+ const { DatabaseSync } = (options.requireImpl || requireOptional)("node:sqlite");
11398
+ const file = join6(dirname5(options.file || cursorWindowStatePath(options)), "state.vscdb");
11399
+ database = new DatabaseSync(file, { readOnly: true });
11400
+ const rows = database.prepare("SELECT key, value FROM ItemTable WHERE key IN (?, ?)").all(STARTUP_PREFERENCE_KEY, LEGACY_STARTUP_PREFERENCE_KEY);
11401
+ const values = new Map(rows.map(({ key, value }) => {
11402
+ try {
11403
+ return [key, JSON.parse(value)];
11404
+ } catch {
11405
+ return [key, value];
11406
+ }
11407
+ }));
11408
+ const preference = values.get(STARTUP_PREFERENCE_KEY);
11409
+ if (["default", "agentWindow", "lastUsedWindows"].includes(preference)) return preference;
11410
+ const legacy = values.get(LEGACY_STARTUP_PREFERENCE_KEY);
11411
+ if (legacy === true || legacy === "true" || legacy === "agentWindow") return "agentWindow";
11412
+ if (legacy === false || legacy === "false" || legacy === "lastUsedWindows") return "lastUsedWindows";
11413
+ return "default";
11414
+ } catch {
11415
+ return null;
11416
+ } finally {
11417
+ try {
11418
+ database?.close();
11419
+ } catch {
11420
+ }
11421
+ }
11422
+ }
11405
11423
  function cursorWindowStatePath(options = {}) {
11406
11424
  const env = options.env || process.env;
11407
11425
  const home = options.home || homedir6();
@@ -11431,6 +11449,12 @@ function stateSignature(last) {
11431
11449
  return createHash2("sha256").update(JSON.stringify(last)).digest("hex");
11432
11450
  }
11433
11451
  function readCursorStartupWindow(options = {}) {
11452
+ let preference = null;
11453
+ try {
11454
+ preference = (options.readStartupPreferenceImpl || readCursorStartupPreference)(options);
11455
+ } catch {
11456
+ }
11457
+ if (preference === "agentWindow") return { uiFlavor: "agents_v2", source: "cursor-startup-preference" };
11434
11458
  const file = options.file || cursorWindowStatePath(options);
11435
11459
  const last = nativeWindowState(options);
11436
11460
  try {
@@ -11449,8 +11473,12 @@ function cursorStartupWindowArgs(startupWindow, projectPath) {
11449
11473
  if (startupWindow.uiFlavor !== "legacy") return ["--glass"];
11450
11474
  return ["--classic", ...projectPath ? [projectPath] : ["--new-window"]];
11451
11475
  }
11476
+ var requireOptional, STARTUP_PREFERENCE_KEY, LEGACY_STARTUP_PREFERENCE_KEY;
11452
11477
  var init_cursor_startup_window = __esm({
11453
11478
  "cursor-startup-window.mjs"() {
11479
+ requireOptional = startupModule.createRequire(import.meta.url);
11480
+ STARTUP_PREFERENCE_KEY = "cursor/userWindowRestorationPreference";
11481
+ LEGACY_STARTUP_PREFERENCE_KEY = "cursor/userOpenAgentsWindowOnStartupPreference";
11454
11482
  }
11455
11483
  });
11456
11484
 
@@ -22990,7 +23018,7 @@ function updateCursorSessionRegistry(filePath, mutator, options = {}) {
22990
23018
  // server.mjs
22991
23019
  init_cursor_ensure_core();
22992
23020
  init_lifecycle_paths();
22993
- var PLUGIN_VERSION = "6.0.7";
23021
+ var PLUGIN_VERSION = "6.0.9";
22994
23022
  var CDP_PORT2 = Number(process.env.CURSOR_BRIDGE_CDP_PORT || 9223);
22995
23023
  var ORIGIN = `http://localhost:${CDP_PORT2}`;
22996
23024
  var QUERY_TIMEOUT = Number(process.env.CURSOR_BRIDGE_TIMEOUT || 3e5);
@@ -23481,21 +23509,54 @@ function exprClickBoundComposerStop(agentId) {
23481
23509
  }
23482
23510
  var EXPR_FIND_NEWAGENT = `(function(){const b=[...document.querySelectorAll('button,[role=button],a.action-label,.codicon')].find(e=>{if(e.offsetParent===null||e.closest('.glass-sidebar-agent-menu-btn'))return false;const s=(e.getAttribute('aria-label')||'')+' '+(e.getAttribute('title')||'')+' '+(e.innerText||'');return /(?:^|\\s)New (?:Agent|Chat)(?:\\s|$)/i.test(s);});if(!b)return '';const r=b.getBoundingClientRect();return JSON.stringify({x:Math.round(r.x+r.width/2),y:Math.round(r.y+r.height/2)});})()`;
23483
23511
  var MODEL_PICKER_VISIBLE_BODY = `
23512
+ ${INPUT_PICKER_BODY}
23484
23513
  const visible=(node)=>!!(node&&(node.offsetParent!==null||(node.getClientRects&&node.getClientRects().length>0)));
23485
- const composers=[...document.querySelectorAll('.composer-bar[data-composer-id],.composer-bar,.ui-prompt-input-root,.agent-prompt-input-root,.ui-prompt-input')].filter(visible);
23486
- const composer=composers[composers.length-1]||document;
23514
+ const inputs=inputCandidates();
23515
+ const ownerSelector='.composer-bar[data-composer-id],.composer-bar,.ui-prompt-input-root,.agent-prompt-input-root,.ui-prompt-input';
23516
+ const composer=inputs.length===1?inputs[0].closest(ownerSelector):null;
23517
+ const triggerSelector='.ui-model-picker__trigger,.vscode-model-picker__trigger';
23518
+ const candidates=composer?[...composer.querySelectorAll(triggerSelector)]
23519
+ .filter(node=>visible(node)&&node.closest(ownerSelector)===composer):[];
23520
+ const trigger=candidates.length===1?candidates[0]:null;
23521
+ const shell=inputs.length===1?inputs[0].closest('[data-agent-panel-conversation-shell][data-glass-agent-id]'):null;
23522
+ const owner=trigger?{triggerId:trigger.getAttribute('id'),composerId:composer.getAttribute('data-composer-id'),agentId:shell&&shell.getAttribute('data-glass-agent-id')}:null;
23487
23523
  `;
23488
23524
  var EXPR_MODEL_PICKER_TRIGGER = `(function(){
23489
23525
  ${MODEL_PICKER_VISIBLE_BODY}
23490
- const triggerSelector='.ui-model-picker__trigger,.vscode-model-picker__trigger';
23491
- const candidates=[...composer.querySelectorAll(triggerSelector)].filter(visible);
23492
- const trigger=candidates[candidates.length-1]||[...document.querySelectorAll(triggerSelector)].filter(visible).pop();
23493
- if(!trigger)return JSON.stringify({found:false,state:'trigger_missing'});
23526
+ if(!trigger)return JSON.stringify({found:false,state:inputs.length!==1?'input_ambiguous':!composer?'composer_missing':candidates.length>1?'trigger_ambiguous':'trigger_missing',inputCount:inputs.length,triggerCount:candidates.length});
23494
23527
  const rect=trigger.getBoundingClientRect();
23495
23528
  const text=String(trigger.querySelector('.ui-model-picker__trigger-text,.vscode-model-picker__trigger-text')?.innerText||trigger.innerText||'').replace(/\\s+/g,' ').trim();
23496
23529
  const detail=String(trigger.querySelector('.ui-model-picker__trigger-variant-suffix,.vscode-model-picker__trigger-variant-suffix')?.innerText||'').replace(/\\s+/g,' ').trim();
23497
- return JSON.stringify({found:true,state:'ready',text,detail,x:Math.round(rect.x+rect.width/2),y:Math.round(rect.y+rect.height/2)});
23530
+ const x=Math.round(rect.x+rect.width/2),y=Math.round(rect.y+rect.height/2);
23531
+ let state='ready',occluder=null;
23532
+ if(trigger.disabled||trigger.getAttribute('aria-disabled')==='true'||trigger.closest('[inert]')||getComputedStyle(trigger).pointerEvents==='none')state='trigger_disabled';
23533
+ else if(!Number.isFinite(x)||!Number.isFinite(y)||rect.width<=0||rect.height<=0||x<0||y<0||x>=innerWidth||y>=innerHeight)state='trigger_offscreen';
23534
+ else {
23535
+ const hit=document.elementFromPoint(x,y);
23536
+ if(!hit||hit!==trigger&&!trigger.contains(hit)){
23537
+ state='trigger_occluded';
23538
+ const menu=hit&&hit.closest('[data-component="menu-popup"]');
23539
+ // A transient Cursor menu can consume/block a click on the composer.
23540
+ // Never dismiss dialogs, arbitrary overlays, or offscreen controls.
23541
+ if(menu&&!hit.closest('[role="dialog"],[aria-modal="true"]'))occluder={kind:'menu',id:menu.getAttribute('id')||null};
23542
+ }
23543
+ }
23544
+ return JSON.stringify({found:true,state,interactive:state==='ready',expanded:trigger.getAttribute('aria-expanded'),occluder,text,detail,x,y,...owner});
23498
23545
  })()`;
23546
+ function exprActivateClosedModelPicker(expected) {
23547
+ return `(function(){
23548
+ const expected=${JSON.stringify(expected)};
23549
+ const current=JSON.parse(${EXPR_MODEL_PICKER_TRIGGER});
23550
+ if(!current.found||current.interactive!==true||current.expanded!=='false')return JSON.stringify({clicked:false,state:'trigger_not_closed_and_interactive'});
23551
+ if(!current.triggerId||['triggerId','composerId','agentId','x','y'].some(key=>(current[key]??null)!==(expected[key]??null)))return JSON.stringify({clicked:false,state:'trigger_changed'});
23552
+ ${MODEL_PICKER_VISIBLE_BODY}
23553
+ if(!trigger||trigger.getAttribute('id')!==current.triggerId)return JSON.stringify({clicked:false,state:'trigger_changed'});
23554
+ // Use the same button's normal click handler after a native click failed
23555
+ // to expand it. The closed-state and hit checks occur in this same turn.
23556
+ trigger.click();
23557
+ return JSON.stringify({clicked:true,state:'closed_trigger_activated',owner});
23558
+ })()`;
23559
+ }
23499
23560
  function classifyModelPickerRowKind(text, hasItemName, menuTestId, submenuTestId, ariaHaspopup, hasParamCheck, inSubmenu) {
23500
23561
  const menu = String(menuTestId || "").toLowerCase();
23501
23562
  const submenu = String(submenuTestId || "").toLowerCase();
@@ -23512,7 +23573,32 @@ function classifyModelPickerRowKind(text, hasItemName, menuTestId, submenuTestId
23512
23573
  var EXPR_MODEL_PICKER_ROWS = `(function(){
23513
23574
  ${MODEL_PICKER_VISIBLE_BODY}
23514
23575
  ${classifyModelPickerRowKind.toString()}
23515
- const menus=[...document.querySelectorAll('[data-testid="model-picker-menu"],[data-testid="selected-auto-menu"],[data-testid*="model-parameters"],[data-testid*="parameter-submenu"],[data-component="menu-popup"][data-submenu]')].filter(visible);
23576
+ const visibleMenus=[...document.querySelectorAll('[data-testid="model-picker-menu"],[data-testid="selected-auto-menu"],[data-testid*="model-parameters"],[data-testid*="parameter-submenu"],[data-component="menu-popup"][data-submenu]')].filter(visible);
23577
+ const menus=[];
23578
+ if(trigger){
23579
+ // Modern menus are portalled. Follow the exact trigger's accessibility
23580
+ // links, including submenu triggers, instead of adopting another picker.
23581
+ const ids=new Set(),controls=new Set();
23582
+ const link=node=>{
23583
+ const id=node.getAttribute('id');if(id)ids.add(id);
23584
+ for(const id of String(node.getAttribute('aria-controls')||'').split(/\\s+/).filter(Boolean))controls.add(id);
23585
+ };
23586
+ link(trigger);
23587
+ for(let pass=0;pass<visibleMenus.length;pass++){
23588
+ let changed=false;
23589
+ for(const menu of visibleMenus){
23590
+ if(menus.includes(menu))continue;
23591
+ const id=menu.getAttribute('id'),labelledBy=String(menu.getAttribute('aria-labelledby')||'').split(/\\s+/).filter(Boolean);
23592
+ if(!(id&&controls.has(id))&&!labelledBy.some(id=>ids.has(id)))continue;
23593
+ menus.push(menu);link(menu);
23594
+ for(const node of menu.querySelectorAll('[id],[aria-controls]'))link(node);
23595
+ changed=true;
23596
+ }
23597
+ if(!changed)break;
23598
+ }
23599
+ if(!ids.size&&!controls.size&&visibleMenus.length===1
23600
+ &&[...document.querySelectorAll(triggerSelector)].filter(visible).length===1)menus.push(visibleMenus[0]);
23601
+ }
23516
23602
  const rows=[];
23517
23603
  const seen=new Set();
23518
23604
  for(const menu of menus){
@@ -23547,8 +23633,11 @@ var EXPR_MODEL_PICKER_ROWS = `(function(){
23547
23633
  });
23548
23634
  }
23549
23635
  }
23550
- return JSON.stringify({open:menus.length>0,rows});
23636
+ return JSON.stringify({open:menus.length>0,rows,owner});
23551
23637
  })()`;
23638
+ function modelPickerOwnerKey(owner) {
23639
+ return owner && JSON.stringify([owner.triggerId || null, owner.composerId || null, owner.agentId || null]);
23640
+ }
23552
23641
  var EXPR_SELECTED_AGENT_MODEL_CONFIG = `(function(){
23553
23642
  ${INPUT_PICKER_BODY}
23554
23643
  const inputs=inputCandidates();
@@ -23820,19 +23909,24 @@ function exprSelectAgentWorkspace(projectPath) {
23820
23909
  const fail=(state,extra={})=>JSON.stringify({ok:false,state,wanted,...extra,
23821
23910
  nextStep:'Select the exact local workspace '+${JSON.stringify(String(projectPath || ""))}+' in the new Agent project picker before retrying.'});
23822
23911
  if(triggers.length!==1)return fail(triggers.length?'workspace_project_selector_ambiguous':'workspace_project_selector_unavailable',{triggerCount:triggers.length});
23823
- triggers[0].click();
23912
+ if(triggers[0].getAttribute?.('aria-expanded')!=='true')triggers[0].click();
23824
23913
  await new Promise(resolve=>setTimeout(resolve,350));
23825
- const menus=[...new Set([...document.querySelectorAll('[data-component="menu-popup"],[role="menu"]')].filter(visible)
23914
+ const readMenus=()=>[...new Set([...document.querySelectorAll('[data-component="menu-popup"],[role="menu"]')].filter(visible)
23826
23915
  .map(menu=>menu.getAttribute('role')==='menu'&&menu.getAttribute('aria-label')==='Select a project'
23827
23916
  ?menu:menu.querySelector('[role="menu"][aria-label="Select a project"]')).filter(visible))];
23917
+ let menus=readMenus();
23828
23918
  if(menus.length!==1)return fail(menus.length?'workspace_project_option_ambiguous':'workspace_project_option_unavailable',{menuCount:menus.length});
23829
- const rows=[];const seen=new Set();
23830
- for(const menu of menus){
23831
- for(const row of menu.querySelectorAll('[data-component="menu-row"][role="menuitem"],[role="option"]')){
23832
- if(!visible(row)||seen.has(row))continue;seen.add(row);rows.push(row);
23919
+ const readRows=()=>{
23920
+ const rows=[];const seen=new Set();
23921
+ for(const menu of menus){
23922
+ for(const row of menu.querySelectorAll('[data-component="menu-row"][role="menuitem"],[role="option"]')){
23923
+ if(!visible(row)||seen.has(row))continue;seen.add(row);rows.push(row);
23924
+ }
23833
23925
  }
23834
- }
23835
- const available=rows.map(row=>String(row.innerText||row.textContent||'').replace(/\s+/g,' ').trim());
23926
+ return rows;
23927
+ };
23928
+ let rows=readRows();
23929
+ let available=rows.map(row=>String(row.innerText||row.textContent||'').replace(/\s+/g,' ').trim());
23836
23930
  const rowWorkspace=row=>{
23837
23931
  const key=Object.keys(row).find(key=>key.startsWith('__reactFiber$'));
23838
23932
  const levels=[];const visited=new Set();
@@ -23892,7 +23986,26 @@ function exprSelectAgentWorkspace(projectPath) {
23892
23986
  const [workspaceId,path]=JSON.parse([...identities][0]);
23893
23987
  return {path,workspaceId,root,currentRoot};
23894
23988
  };
23895
- const matches=rows.map(row=>({row,identity:rowWorkspace(row)})).filter(match=>match.identity&&match.identity.path===wanted);
23989
+ const findMatches=()=>rows.map(row=>({row,identity:rowWorkspace(row)})).filter(match=>match.identity&&match.identity.path===wanted);
23990
+ let matches=findMatches();
23991
+ if(!matches.length){
23992
+ // Cursor's Recents view omits older registered workspaces. Search the
23993
+ // full path, then apply the same committed local-identity checks.
23994
+ const searches=[...menus[0].querySelectorAll('input[aria-label="Search menu items"]')].filter(visible);
23995
+ if(searches.length===1&&typeof HTMLInputElement!=='undefined'){
23996
+ const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value')?.set;
23997
+ if(setter){
23998
+ setter.call(searches[0],${JSON.stringify(String(projectPath || ""))});
23999
+ searches[0].dispatchEvent(new Event('input',{bubbles:true}));
24000
+ await new Promise(resolve=>setTimeout(resolve,350));
24001
+ menus=readMenus();
24002
+ if(menus.length!==1)return fail(menus.length?'workspace_project_option_ambiguous':'workspace_project_option_unavailable',{menuCount:menus.length});
24003
+ rows=readRows();
24004
+ available=rows.map(row=>String(row.innerText||row.textContent||'').replace(/\s+/g,' ').trim());
24005
+ matches=findMatches();
24006
+ }
24007
+ }
24008
+ }
23896
24009
  if(matches.length!==1)return fail(matches.length?'workspace_project_option_ambiguous':'workspace_project_option_unavailable',{available});
23897
24010
  const selected=matches[0];
23898
24011
  if(selected.identity.root&&selected.identity.root.current!==selected.identity.currentRoot)return fail('workspace_project_option_unavailable',{available});
@@ -25843,7 +25956,7 @@ var CursorBridge = class {
25843
25956
  }
25844
25957
  async _readModelPickerRows(c, options) {
25845
25958
  const snapshot = await this._readModelPickerValue(c, EXPR_MODEL_PICKER_ROWS, "rows", options);
25846
- return { open: snapshot.open === true, rows: Array.isArray(snapshot.rows) ? snapshot.rows : [] };
25959
+ return { open: snapshot.open === true, rows: Array.isArray(snapshot.rows) ? snapshot.rows : [], owner: snapshot.owner || null };
25847
25960
  }
25848
25961
  async _readSelectedAgentModelConfig(c, options) {
25849
25962
  return this._readModelPickerValue(c, EXPR_SELECTED_AGENT_MODEL_CONFIG, "selected_agent_model_config", options);
@@ -25861,19 +25974,83 @@ var CursorBridge = class {
25861
25974
  if (!point || !Number.isFinite(Number(point.x)) || !Number.isFinite(Number(point.y))) return;
25862
25975
  await c.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: Number(point.x), y: Number(point.y) });
25863
25976
  }
25864
- async _openModelPicker(c) {
25865
- const trigger = await this._readModelPickerTrigger(c);
25866
- if (!trigger.found) {
25867
- throw new Error("Cursor model picker is unavailable in the active Agent composer");
25868
- }
25869
- let snapshot = await this._readModelPickerRows(c);
25870
- if (!snapshot.open) {
25871
- await this._clickModelPickerPoint(c, trigger);
25872
- await sleep2(450);
25873
- snapshot = await this._readModelPickerRows(c);
25874
- }
25875
- if (!snapshot.open) throw new Error("Cursor model picker did not open");
25876
- return { trigger, ...snapshot };
25977
+ async _activateClosedModelPicker(c, trigger, options) {
25978
+ return this._readModelPickerValue(c, exprActivateClosedModelPicker(trigger), "closed_trigger_activation", options);
25979
+ }
25980
+ async _openModelPicker(c, job = null, options = {}) {
25981
+ const timeoutMs = Math.max(0, Number(options.timeoutMs ?? 2e3));
25982
+ const pollMs = Math.max(10, Number(options.pollMs ?? 50));
25983
+ const stableMs = Math.max(0, Number(options.stableMs ?? 100));
25984
+ const recoveryDelayMs = Math.max(0, Number(options.recoveryDelayMs ?? 750));
25985
+ const deadline = Date.now() + timeoutMs;
25986
+ let trigger = null, snapshot = { open: false, rows: [] };
25987
+ let stableSignature = null, stableSince = 0, clicked = false, clickedOwner = null;
25988
+ let clickedAt = 0, recoveryAttempted = false, lastPollError = null;
25989
+ let dismissedMenus = 0, dismissalOwner = null;
25990
+ const attempts = [];
25991
+ do {
25992
+ this._throwIfCancelledBeforeSend(job);
25993
+ if (deadline - Date.now() < 10) break;
25994
+ try {
25995
+ snapshot = await this._readModelPickerRows(c, { timeoutMs: deadline - Date.now() });
25996
+ if (deadline - Date.now() < 10) break;
25997
+ trigger = await this._readModelPickerTrigger(c, { timeoutMs: deadline - Date.now() });
25998
+ } catch (error2) {
25999
+ if (error2.code !== "CDP_COMMAND_TIMEOUT" || Date.now() < deadline) throw error2;
26000
+ lastPollError = error2.pickerRead || { code: error2.code, message: error2.message };
26001
+ break;
26002
+ }
26003
+ this._throwIfCancelledBeforeSend(job);
26004
+ const sameOwner = modelPickerOwnerKey(snapshot.owner) === modelPickerOwnerKey(trigger);
26005
+ if (dismissalOwner !== null && dismissalOwner !== modelPickerOwnerKey(trigger)) {
26006
+ attempts.push("owner_changed_after_menu_dismissal");
26007
+ break;
26008
+ }
26009
+ if (snapshot.open && trigger.found && sameOwner && (!clicked || clickedOwner === modelPickerOwnerKey(trigger))) return { trigger, ...snapshot };
26010
+ if (snapshot.open && !sameOwner && !attempts.includes("owner_changed")) attempts.push("owner_changed");
26011
+ if (!clicked && !snapshot.open && trigger.found && trigger.state === "trigger_occluded" && trigger.expanded === "false" && trigger.occluder?.kind === "menu" && dismissedMenus < 3) {
26012
+ this._throwIfCancelledBeforeSend(job);
26013
+ dismissalOwner = modelPickerOwnerKey(trigger);
26014
+ dismissedMenus++;
26015
+ attempts.push("dismiss_occluding_menu");
26016
+ await c.send("Input.dispatchKeyEvent", { type: "keyDown", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 27 });
26017
+ await c.send("Input.dispatchKeyEvent", { type: "keyUp", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 27 });
26018
+ stableSignature = null;
26019
+ await sleep2(Math.min(150, Math.max(0, deadline - Date.now())));
26020
+ continue;
26021
+ }
26022
+ if (!clicked) {
26023
+ const interactive = trigger.found && trigger.interactive !== false;
26024
+ const signature = interactive ? JSON.stringify([trigger.triggerId, trigger.composerId, trigger.agentId, trigger.x, trigger.y]) : null;
26025
+ if (!interactive || signature !== stableSignature) {
26026
+ stableSignature = signature;
26027
+ stableSince = Date.now();
26028
+ const state = trigger.state || (interactive ? "ready" : "trigger_missing");
26029
+ if (!attempts.includes(state)) attempts.push(state);
26030
+ }
26031
+ if (interactive && Date.now() - stableSince >= stableMs) {
26032
+ this._throwIfCancelledBeforeSend(job);
26033
+ await this._clickModelPickerPoint(c, trigger);
26034
+ clicked = true;
26035
+ clickedAt = Date.now();
26036
+ clickedOwner = modelPickerOwnerKey(trigger);
26037
+ attempts.push("click_once", "wait_menu");
26038
+ }
26039
+ } else if (!recoveryAttempted && !snapshot.open && sameOwner && clickedOwner === modelPickerOwnerKey(trigger) && trigger.interactive === true && trigger.expanded === "false" && Date.now() - clickedAt >= recoveryDelayMs && deadline - Date.now() >= 10) {
26040
+ this._throwIfCancelledBeforeSend(job);
26041
+ recoveryAttempted = true;
26042
+ const recovered = await this._activateClosedModelPicker(c, trigger, { timeoutMs: deadline - Date.now() });
26043
+ attempts.push(recovered.clicked ? "activate_confirmed_closed_trigger" : recovered.state || "activation_rejected");
26044
+ }
26045
+ if (Date.now() >= deadline) break;
26046
+ await sleep2(Math.min(pollMs, Math.max(0, deadline - Date.now())));
26047
+ } while (Date.now() < deadline);
26048
+ throw createModelSelectionError("Cursor model picker did not open", "picker_did_not_open", true, {
26049
+ attempts,
26050
+ trigger,
26051
+ lastPollError,
26052
+ menu: { open: snapshot.open, rows: (snapshot.rows || []).slice(0, 20) }
26053
+ });
25877
26054
  }
25878
26055
  async _closeModelPicker(c) {
25879
26056
  for (let attempt = 0; attempt < 3; attempt++) {
@@ -25885,24 +26062,29 @@ var CursorBridge = class {
25885
26062
  }
25886
26063
  if ((await this._readModelPickerRows(c)).open) throw new Error("Cursor model picker did not close after selection");
25887
26064
  }
25888
- async _openModelPickerControl(c, snapshot, controlKind) {
26065
+ async _openModelPickerControl(c, snapshot, controlKind, job = null) {
26066
+ this._throwIfCancelledBeforeSend(job);
25889
26067
  const control = (snapshot && snapshot.rows || []).find((row) => row.kind === controlKind && row.disabled !== true);
25890
26068
  if (!control) return snapshot;
26069
+ this._throwIfCancelledBeforeSend(job);
25891
26070
  await this._clickModelPickerPoint(c, control);
25892
26071
  await sleep2(400);
25893
26072
  let next = await this._readModelPickerRows(c);
25894
26073
  const expectedKind = controlKind === "model_control" ? "model" : "parameter";
25895
26074
  if (!next.rows.some((row) => row.kind === expectedKind)) {
26075
+ this._throwIfCancelledBeforeSend(job);
25896
26076
  await this._hoverModelPickerPoint(c, control);
25897
26077
  await sleep2(350);
25898
26078
  next = await this._readModelPickerRows(c);
25899
26079
  }
25900
26080
  return next;
25901
26081
  }
25902
- async _findModelPickerModel(c, snapshot, requestedModel) {
26082
+ async _findModelPickerModel(c, snapshot, requestedModel, job = null) {
26083
+ this._throwIfCancelledBeforeSend(job);
25903
26084
  let modelRow = selectModelPickerRow(snapshot && snapshot.rows, requestedModel, "model");
25904
26085
  if (modelRow) return { snapshot, modelRow };
25905
- const expanded = await this._openModelPickerControl(c, snapshot, "model_control");
26086
+ const expanded = await this._openModelPickerControl(c, snapshot, "model_control", job);
26087
+ this._throwIfCancelledBeforeSend(job);
25906
26088
  modelRow = selectModelPickerRow(expanded && expanded.rows, requestedModel, "model");
25907
26089
  return { snapshot: expanded, modelRow };
25908
26090
  }
@@ -26047,9 +26229,9 @@ var CursorBridge = class {
26047
26229
  if (job) job.modelSelection = result2;
26048
26230
  return result2;
26049
26231
  }
26050
- const opened = await this._openModelPicker(c);
26232
+ const opened = await this._openModelPicker(c, job);
26051
26233
  stage = "locate_model";
26052
- let located = await this._findModelPickerModel(c, opened, requestedModel);
26234
+ let located = await this._findModelPickerModel(c, opened, requestedModel, job);
26053
26235
  modelRow = located.modelRow;
26054
26236
  if (!modelRow) {
26055
26237
  throw createModelSelectionError(
@@ -26065,8 +26247,8 @@ var CursorBridge = class {
26065
26247
  if (!outcome.row && outcome.state === "not_rendered" && !effortPickerReopened) {
26066
26248
  effortPickerReopened = true;
26067
26249
  await this._closeModelPicker(c);
26068
- const reopened = await this._openModelPicker(c);
26069
- located = await this._findModelPickerModel(c, reopened, requestedModel);
26250
+ const reopened = await this._openModelPicker(c, job);
26251
+ located = await this._findModelPickerModel(c, reopened, requestedModel, job);
26070
26252
  modelRow = located.modelRow;
26071
26253
  if (!modelRow) {
26072
26254
  throw createModelSelectionError(
@@ -26109,8 +26291,8 @@ var CursorBridge = class {
26109
26291
  this._throwIfCancelledBeforeSend(job);
26110
26292
  await this._clickModelPickerPoint(c, modelRow);
26111
26293
  await sleep2(550);
26112
- const reopened = await this._openModelPicker(c);
26113
- located = await this._findModelPickerModel(c, reopened, requestedModel);
26294
+ const reopened = await this._openModelPicker(c, job);
26295
+ located = await this._findModelPickerModel(c, reopened, requestedModel, job);
26114
26296
  modelRow = located.modelRow;
26115
26297
  if (!modelRow) {
26116
26298
  throw createModelSelectionError(
@@ -26137,8 +26319,8 @@ var CursorBridge = class {
26137
26319
  stage = "verify_model";
26138
26320
  let trigger2 = await this._readModelPickerTrigger(c);
26139
26321
  if (!trigger2.found || !normalizeModelPickerModelText(trigger2.text).includes(normalizeModelPickerModelText(requestedModel))) {
26140
- const reopened = await this._openModelPicker(c);
26141
- located = await this._findModelPickerModel(c, reopened, requestedModel);
26322
+ const reopened = await this._openModelPicker(c, job);
26323
+ located = await this._findModelPickerModel(c, reopened, requestedModel, job);
26142
26324
  const selected = selectModelPickerRow(located.snapshot.rows.filter((row) => row.selected), requestedModel, "model");
26143
26325
  if (!selected) {
26144
26326
  throw createModelSelectionError(
@@ -26151,8 +26333,8 @@ var CursorBridge = class {
26151
26333
  }
26152
26334
  if (requestedEffort) {
26153
26335
  stage = "verify_effort";
26154
- const reopened = await this._openModelPicker(c);
26155
- located = await this._findModelPickerModel(c, reopened, requestedModel);
26336
+ const reopened = await this._openModelPicker(c, job);
26337
+ located = await this._findModelPickerModel(c, reopened, requestedModel, job);
26156
26338
  modelRow = located.modelRow;
26157
26339
  if (!modelRow) {
26158
26340
  throw createModelSelectionError(
@@ -26207,6 +26389,8 @@ var CursorBridge = class {
26207
26389
  errorCode: primaryError.code || `CURSOR_MODEL_${String(failure.failureClass).toUpperCase()}`,
26208
26390
  available: failure.available || [],
26209
26391
  attempts: failure.attempts || [],
26392
+ trigger: failure.trigger || null,
26393
+ lastPollError: failure.lastPollError || null,
26210
26394
  menu: failure.menu || null,
26211
26395
  runtimeMode: this.runtimeMode,
26212
26396
  lastError: primaryError.message,
@@ -28294,6 +28478,7 @@ export {
28294
28478
  createChatPanelUnavailableError,
28295
28479
  createProviderError,
28296
28480
  cursorStartupBehavior,
28481
+ exprActivateClosedModelPicker,
28297
28482
  exprClickBoundComposerStop,
28298
28483
  exprClickSelectedAgentStop,
28299
28484
  exprCreateAgentForWorkspace,
@@ -3713,6 +3713,36 @@ import { mkdirSync, readFileSync, renameSync, writeFileSync, rmSync } from "node
3713
3713
  import { createHash } from "node:crypto";
3714
3714
  import { homedir } from "node:os";
3715
3715
  import { dirname, join, resolve } from "node:path";
3716
+ import * as startupModule from "node:module";
3717
+ function readCursorStartupPreference(options = {}) {
3718
+ let database;
3719
+ try {
3720
+ const { DatabaseSync } = (options.requireImpl || requireOptional)("node:sqlite");
3721
+ const file = join(dirname(options.file || cursorWindowStatePath(options)), "state.vscdb");
3722
+ database = new DatabaseSync(file, { readOnly: true });
3723
+ const rows = database.prepare("SELECT key, value FROM ItemTable WHERE key IN (?, ?)").all(STARTUP_PREFERENCE_KEY, LEGACY_STARTUP_PREFERENCE_KEY);
3724
+ const values = new Map(rows.map(({ key, value }) => {
3725
+ try {
3726
+ return [key, JSON.parse(value)];
3727
+ } catch {
3728
+ return [key, value];
3729
+ }
3730
+ }));
3731
+ const preference = values.get(STARTUP_PREFERENCE_KEY);
3732
+ if (["default", "agentWindow", "lastUsedWindows"].includes(preference)) return preference;
3733
+ const legacy = values.get(LEGACY_STARTUP_PREFERENCE_KEY);
3734
+ if (legacy === true || legacy === "true" || legacy === "agentWindow") return "agentWindow";
3735
+ if (legacy === false || legacy === "false" || legacy === "lastUsedWindows") return "lastUsedWindows";
3736
+ return "default";
3737
+ } catch {
3738
+ return null;
3739
+ } finally {
3740
+ try {
3741
+ database?.close();
3742
+ } catch {
3743
+ }
3744
+ }
3745
+ }
3716
3746
  function cursorWindowStatePath(options = {}) {
3717
3747
  const env = options.env || process.env;
3718
3748
  const home = options.home || homedir();
@@ -3742,6 +3772,12 @@ function stateSignature(last) {
3742
3772
  return createHash("sha256").update(JSON.stringify(last)).digest("hex");
3743
3773
  }
3744
3774
  function readCursorStartupWindow(options = {}) {
3775
+ let preference = null;
3776
+ try {
3777
+ preference = (options.readStartupPreferenceImpl || readCursorStartupPreference)(options);
3778
+ } catch {
3779
+ }
3780
+ if (preference === "agentWindow") return { uiFlavor: "agents_v2", source: "cursor-startup-preference" };
3745
3781
  const file = options.file || cursorWindowStatePath(options);
3746
3782
  const last = nativeWindowState(options);
3747
3783
  try {
@@ -3779,8 +3815,12 @@ function cursorStartupWindowArgs(startupWindow, projectPath) {
3779
3815
  if (startupWindow.uiFlavor !== "legacy") return ["--glass"];
3780
3816
  return ["--classic", ...projectPath ? [projectPath] : ["--new-window"]];
3781
3817
  }
3818
+ var requireOptional, STARTUP_PREFERENCE_KEY, LEGACY_STARTUP_PREFERENCE_KEY;
3782
3819
  var init_cursor_startup_window = __esm({
3783
3820
  "cursor-startup-window.mjs"() {
3821
+ requireOptional = startupModule.createRequire(import.meta.url);
3822
+ STARTUP_PREFERENCE_KEY = "cursor/userWindowRestorationPreference";
3823
+ LEGACY_STARTUP_PREFERENCE_KEY = "cursor/userOpenAgentsWindowOnStartupPreference";
3784
3824
  }
3785
3825
  });
3786
3826
 
@@ -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.7",
13
+ packageVersion: "0.2.9",
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.7",
3
+ "version": "0.2.9",
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.7"
50
+ "embeddedProductVersion": "6.0.9"
51
51
  }
52
52
  }