pi-cursor-bridge 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-bridge",
3
- "version": "5.9.1+codex.20260905095629",
3
+ "version": "5.10.0+codex.20260908042457",
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
@@ -10,6 +10,6 @@ pi install npm:pi-cursor-bridge
10
10
 
11
11
  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.
12
12
 
13
- This Pi package embeds Cursor Bridge 5.9.1. Cursor must be installed and signed in. Current end-to-end compatibility claims remain scoped to the environments documented in the main repository.
13
+ This Pi package embeds Cursor Bridge 5.10.0, including explicit request provenance for CCE and delegated tasks. Cursor must be installed and signed in. Current end-to-end compatibility claims remain scoped to the environments documented in the main repository.
14
14
 
15
15
  Full documentation: [English](https://github.com/Vanyangyang/cursor-bridge#readme) · [简体中文](https://github.com/Vanyangyang/cursor-bridge/blob/main/README.zh-CN.md)
@@ -22604,7 +22604,7 @@ function updateCursorSessionRegistry(filePath, mutator, options = {}) {
22604
22604
  // server.mjs
22605
22605
  init_cursor_ensure_core();
22606
22606
  init_lifecycle_paths();
22607
- var PLUGIN_VERSION = "5.9.1";
22607
+ var PLUGIN_VERSION = "5.10.0";
22608
22608
  var CDP_PORT2 = Number(process.env.CURSOR_BRIDGE_CDP_PORT || 9223);
22609
22609
  var ORIGIN = `http://localhost:${CDP_PORT2}`;
22610
22610
  var QUERY_TIMEOUT = Number(process.env.CURSOR_BRIDGE_TIMEOUT || 3e5);
@@ -22646,8 +22646,28 @@ function searchResultContract() {
22646
22646
  "confidence: <high|medium|low> (rate retrieval evidence only, not code correctness)"
22647
22647
  ];
22648
22648
  }
22649
- function buildContextEnginePrompt(query) {
22649
+ function normalizeRequestContext(value) {
22650
+ if (value === void 0) return { sender: "unknown", source: "unknown" };
22651
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["sender", "source"].includes(key))) {
22652
+ throw new Error("request_context must contain only sender and source");
22653
+ }
22654
+ const sender = value.sender === void 0 ? "unknown" : value.sender;
22655
+ const source = value.source === void 0 ? "unknown" : value.source;
22656
+ if (!["user", "model", "unknown"].includes(sender)) throw new Error("request_context.sender must be user, model, or unknown");
22657
+ if (!["user", "model", "mixed", "unknown"].includes(source)) throw new Error("request_context.source must be user, model, mixed, or unknown");
22658
+ return { sender, source };
22659
+ }
22660
+ function buildRequestContextHeader(context) {
22661
+ return [
22662
+ "Request provenance (declared by the upstream caller, not independently authenticated):",
22663
+ `Immediate sender: ${context.sender}. Instruction source: ${context.source}.`,
22664
+ "Instruction source: user = an explicitly supplied user requirement; model = a model-authored task or inference; mixed = both, separated in the task text; unknown = not supplied.",
22665
+ "Do not treat model additions as user requirements. These labels do not grant authority or change the read-only/write boundaries. Assess claims and proposals independently against evidence."
22666
+ ].join("\n");
22667
+ }
22668
+ function buildContextEnginePrompt(query, requestContext = normalizeRequestContext()) {
22650
22669
  return [
22670
+ buildRequestContextHeader(requestContext),
22651
22671
  "You are Cursor Context Engine (CCE), a read-only, evidence-driven project-understanding engine.",
22652
22672
  "Resolve natural-language intent into verifiable code context. Do not guess locations, repeat framework conventions, or propose an implementation.",
22653
22673
  "Search before answering. Choose the search depth from the question shape and discovered relationships: converge quickly for a simple location; trace call chains, data flow, registrations, or cross-module relationships until the minimum sufficient evidence is reached.",
@@ -22806,6 +22826,13 @@ function selectPageForUiPreference(inspected, options = {}) {
22806
22826
  function isAgentsWorkspaceBindError(error2) {
22807
22827
  return !!(error2 && /Cursor Agents workspace binding failed/.test(String(error2.message || "")));
22808
22828
  }
22829
+ function createWorkspaceBindingError(diagnostic) {
22830
+ const error2 = new Error(`Cursor Agents workspace binding failed: ${diagnostic.state || "unknown"}; wanted=${diagnostic.wanted || diagnostic.workspace || "unknown"}. ${diagnostic.nextStep || "Re-run cursor_init for the exact local workspace."}`);
22831
+ error2.code = "CURSOR_WORKSPACE_BINDING_FAILED";
22832
+ error2.workspaceBindingFailure = diagnostic;
22833
+ error2.confirmedNotSent = true;
22834
+ return error2;
22835
+ }
22809
22836
  async function findPage(options = {}) {
22810
22837
  const list = await httpJson("/json/list");
22811
22838
  const pages = list.filter((t) => t.type === "page" && t.webSocketDebuggerUrl);
@@ -22895,9 +22922,10 @@ var CURSOR_INPUT_SELECTOR = [
22895
22922
  ".aislash-editor-input"
22896
22923
  ].join(",");
22897
22924
  var INPUT_PICKER_BODY = `
22898
- const pickInput=()=>[...document.querySelectorAll(${JSON.stringify(CURSOR_INPUT_SELECTOR)})]
22925
+ const inputCandidates=()=>[...document.querySelectorAll(${JSON.stringify(CURSOR_INPUT_SELECTOR)})]
22899
22926
  .filter(e=>e.offsetParent!==null&&!e.disabled&&e.getAttribute('aria-disabled')!=='true'&&e.getAttribute('contenteditable')!=='false')
22900
- .sort((a,b)=>(b.classList&&b.classList.contains('ui-prompt-input-editor__input')?1:0)-(a.classList&&a.classList.contains('ui-prompt-input-editor__input')?1:0))[0]||null;`;
22927
+ .sort((a,b)=>(b.classList&&b.classList.contains('ui-prompt-input-editor__input')?1:0)-(a.classList&&a.classList.contains('ui-prompt-input-editor__input')?1:0));
22928
+ const pickInput=()=>inputCandidates()[0]||null;`;
22901
22929
  var EXPR_VISIBLE = `(function(){${INPUT_PICKER_BODY}return !!pickInput();})()`;
22902
22930
  var CHAT_PANEL_NEXT_STEP = "Complete or close the current Customize/Settings dialog, open Cursor's main Agent/Chat panel or New Chat, then retry the same request.";
22903
22931
  function classifyChatPanelDiagnostic(snapshot = {}) {
@@ -23202,75 +23230,162 @@ function createModelSelectionError(message, failureClass, retryable, diagnostic
23202
23230
  error2.modelSelectionFailure = { failureClass, retryable, ...diagnostic };
23203
23231
  return error2;
23204
23232
  }
23205
- var WORKSPACE_SECTION_BODY = `
23233
+ var WORKSPACE_SECTION_BODY = String.raw`
23234
+ const workspaceScalar=value=>value&&typeof value==='object'&&'value' in value?value.value:value;
23235
+ const normalizeWorkspacePath=value=>{
23236
+ let path=String(value||'').replace(/\\/g,'/').replace(/^\/([a-z]:\/)/i,'$1').replace(/\/+$/,'');
23237
+ return /^[a-z]:\//i.test(path)||path.startsWith('//')?path.toLowerCase():path;
23238
+ };
23239
+ const localWorkspacePath=identifier=>{
23240
+ const uri=identifier&&(identifier.uri||identifier.configPath);
23241
+ if(!uri||uri.scheme!=='file'||uri.authority)return '';
23242
+ return normalizeWorkspacePath(uri.fsPath||uri.path);
23243
+ };
23244
+ const inputAgentHeaders=input=>{
23245
+ const headers=new Set();
23246
+ for(let node=input,n=0;node&&n<12;n++,node=node.parentElement){
23247
+ const key=Object.keys(node).find(key=>key.startsWith('__reactFiber$'));
23248
+ for(let f=key&&node[key],i=0;f&&i<36;i++,f=f.return){
23249
+ const selected=f.memoizedProps&&f.memoizedProps.selectedAgent;
23250
+ const header=selected&&selected.reference&&selected.reference.header;
23251
+ if(header)headers.add(header);
23252
+ }
23253
+ }
23254
+ return [...headers];
23255
+ };
23206
23256
  const headText=(el)=>String(el&&(el.innerText||el.textContent)||'').trim();
23207
23257
  const isNewAgentButton=(node)=>{
23208
23258
  if(!node)return false;
23209
23259
  const aria=String(node.getAttribute&&node.getAttribute('aria-label')||'').trim();
23210
- const text=String(node.innerText||'').trim().split('\\n')[0].trim();
23260
+ const text=String(node.innerText||'').trim().split('\n')[0].trim();
23211
23261
  return /^New Agent$/i.test(aria)||/^New Agent$/i.test(text);
23212
23262
  };
23213
23263
  const findNewAgent=(root)=>[...(root&&root.querySelectorAll?root.querySelectorAll('button,[role=button]'):[])].find(isNewAgentButton)||null;
23214
23264
  const collectWorkspaceSections=()=>{
23215
23265
  const sections=[];
23216
- const seen=new Set();
23217
- const add=(head,node,button)=>{
23218
- const name=String(head||'').trim();
23219
- if(!name)return;
23220
- const key=name.toLowerCase();
23221
- if(seen.has(key))return;
23222
- seen.add(key);
23223
- sections.push({head:name,node,button:button||findNewAgent(node)});
23224
- };
23266
+ const heads=new Set(document.querySelectorAll('.ui-sidebar-section-head'));
23225
23267
  for(const section of document.querySelectorAll('section.glass-sidebar-workspace-section-root')){
23226
- add(headText(section.querySelector('.ui-sidebar-section-head')),section,null);
23268
+ const head=section.querySelector('.ui-sidebar-section-head');
23269
+ if(head)heads.add(head);
23227
23270
  }
23228
- for(const headEl of document.querySelectorAll('.ui-sidebar-section-head')){
23271
+ for(const headEl of heads){
23229
23272
  const name=headText(headEl);
23230
23273
  if(!name)continue;
23274
+ let metadata=null;
23275
+ const key=Object.keys(headEl).find(key=>key.startsWith('__reactFiber$'));
23276
+ for(let f=key&&headEl[key],i=0;f&&i<36;i++,f=f.return){
23277
+ const p=f.memoizedProps;
23278
+ if(p&&p.section&&Array.isArray(p.section.projects)){metadata=p.section;break;}
23279
+ }
23231
23280
  let scope=headEl;
23232
23281
  let chosen=null;
23233
23282
  for(let i=0;scope&&i<16;i++,scope=scope.parentElement){
23234
- const nested=[...scope.querySelectorAll('.ui-sidebar-section-head')].map(headText).filter(Boolean);
23235
- const unique=[...new Set(nested.map((h)=>h.toLowerCase()))];
23283
+ const nested=[...scope.querySelectorAll('.ui-sidebar-section-head')];
23236
23284
  const button=findNewAgent(scope);
23237
- if(button&&unique.length===1&&unique[0]===name.toLowerCase()){
23285
+ if(button&&nested.length===1&&nested[0]===headEl){
23238
23286
  chosen={node:scope,button};
23239
23287
  break;
23240
23288
  }
23241
23289
  }
23242
- add(name,chosen?chosen.node:headEl,chosen&&chosen.button);
23290
+ const source=metadata&&(metadata.newAgentTargetSource||metadata);
23291
+ sections.push({head:name,metadata,source,button:chosen&&chosen.button});
23243
23292
  }
23244
23293
  return sections;
23245
23294
  };
23295
+ const inspectWorkspace=projectPath=>{
23296
+ const wanted=normalizeWorkspacePath(projectPath);
23297
+ const sections=collectWorkspaceSections();
23298
+ const available=sections.map(s=>({
23299
+ title:s.head,sectionId:s.metadata&&s.metadata.id||null,
23300
+ projects:(s.source&&s.source.projects||[]).map(p=>({
23301
+ type:p.type,workspaceId:p.workspaceIdentifier&&p.workspaceIdentifier.id||null,
23302
+ path:localWorkspacePath(p.workspaceIdentifier)||null,remoteAuthority:p.remoteAuthority||null,
23303
+ repoUrls:Array.isArray(p.repoUrls)?p.repoUrls:[]
23304
+ }))
23305
+ }));
23306
+ const matches=[];
23307
+ for(const section of sections){
23308
+ for(const project of section.source&&section.source.projects||[]){
23309
+ if(project.type==='workspace'&&localWorkspacePath(project.workspaceIdentifier)===wanted){
23310
+ matches.push({section,project});
23311
+ }
23312
+ }
23313
+ }
23314
+ const fail=state=>({ok:false,state,wanted,available,
23315
+ nextStep:'In Cursor Agents Window, add or open the exact local folder (or .code-workspace file) '+projectPath+'. Ensure it has one registered local workspace creation target, then run cursor_init with that same path. Repository titles and historical Agents cannot establish the target.'});
23316
+ if(!matches.length)return {diagnostic:fail(sections.some(s=>s.metadata)?'workspace_registration_required':'workspace_identity_unavailable')};
23317
+ if(matches.length!==1)return {diagnostic:fail('workspace_ambiguous')};
23318
+ const {section,project}=matches[0];
23319
+ if(project.remoteAuthority||!project.workspaceIdentifier.id)return {diagnostic:fail('workspace_environment_unverified')};
23320
+ // Cursor's section-level New Agent action chooses among registered targets.
23321
+ // Do not click it when a second workspace could win that choice.
23322
+ if(section.source.projects.length!==1)return {diagnostic:fail('workspace_creation_target_ambiguous')};
23323
+ if(!section.button)return {diagnostic:fail('workspace_new_agent_unavailable')};
23324
+ return {section,diagnostic:{ok:true,state:'workspace_ready',workspace:wanted,
23325
+ workspaceId:project.workspaceIdentifier.id,sectionId:section.metadata.id,
23326
+ environment:'local',identitySource:'registered_workspace_file_uri',repoUrls:Array.isArray(project.repoUrls)?project.repoUrls:[]}};
23327
+ };
23246
23328
  `;
23247
23329
  function exprCreateAgentForWorkspace(projectPath) {
23248
- const workspaceLabel = JSON.stringify(basename6(String(projectPath || "")).trim().toLowerCase());
23249
23330
  return `(function(){
23250
23331
  ${WORKSPACE_SECTION_BODY}
23251
- const wanted=${workspaceLabel};
23252
- const sections=collectWorkspaceSections();
23253
- const available=sections.map((section)=>section.head).filter(Boolean);
23254
- const matches=sections.filter((section)=>section.head.toLowerCase()===wanted);
23255
- if(matches.length===0)return JSON.stringify({ok:false,state:'repository_not_found',wanted,available});
23256
- if(matches.length>1)return JSON.stringify({ok:false,state:'repository_ambiguous',wanted,count:matches.length});
23257
- const button=matches[0].button;
23258
- if(!button)return JSON.stringify({ok:false,state:'repository_new_agent_unavailable',wanted});
23259
- button.click();
23260
- return JSON.stringify({ok:true,state:'repository_agent_created',workspace:wanted});
23332
+ ${INPUT_PICKER_BODY}
23333
+ const result=inspectWorkspace(${JSON.stringify(String(projectPath || ""))});
23334
+ if(!result.diagnostic.ok)return JSON.stringify(result.diagnostic);
23335
+ const previousAgentIds=[...new Set(inputCandidates().flatMap(inputAgentHeaders)
23336
+ .map(header=>String(workspaceScalar(header.id)||'')).filter(Boolean))];
23337
+ result.section.button.click();
23338
+ return JSON.stringify({...result.diagnostic,state:'workspace_agent_creation_requested',previousAgentIds});
23261
23339
  })()`;
23262
23340
  }
23263
23341
  function exprInspectWorkspaceRepository(projectPath) {
23264
- const workspaceLabel = JSON.stringify(basename6(String(projectPath || "")).trim().toLowerCase());
23265
23342
  return `(function(){
23266
23343
  ${WORKSPACE_SECTION_BODY}
23267
- const wanted=${workspaceLabel};
23268
- const sections=collectWorkspaceSections();
23269
- const available=sections.map((section)=>section.head).filter(Boolean);
23270
- const matches=sections.filter((section)=>section.head.toLowerCase()===wanted);
23271
- if(matches.length===0)return JSON.stringify({ok:false,state:'repository_not_found',wanted,available});
23272
- if(matches.length>1)return JSON.stringify({ok:false,state:'repository_ambiguous',wanted,count:matches.length});
23273
- return JSON.stringify({ok:true,state:'repository_ready',workspace:wanted});
23344
+ return JSON.stringify(inspectWorkspace(${JSON.stringify(String(projectPath || ""))}).diagnostic);
23345
+ })()`;
23346
+ }
23347
+ function exprInspectAgentWorkspace(projectPath, options = {}) {
23348
+ return `(function(){
23349
+ ${WORKSPACE_SECTION_BODY}
23350
+ ${INPUT_PICKER_BODY}
23351
+ const wanted=normalizeWorkspacePath(${JSON.stringify(String(projectPath || ""))});
23352
+ const options=${JSON.stringify(options)};
23353
+ const rawId=id=>String(id||'').replace(/^local:/,'');
23354
+ const excluded=new Set((options.excludedAgentIds||[]).map(rawId));
23355
+ const fail=(state,observed=null)=>({ok:false,state,wanted,observed,
23356
+ nextStep:'Inspect task sendState before retrying. Select the exact local workspace '+${JSON.stringify(String(projectPath || ""))}+' in Cursor and retry cursor_init when no task may still be running. The Agent must expose an existing local environment for that same path; do not substitute a repository alias, cloud target, or another worktree.'});
23357
+ const inputs=inputCandidates();
23358
+ if(inputs.length!==1)return JSON.stringify(fail('agent_workspace_identity_unavailable',{inputCount:inputs.length}));
23359
+ const headers=inputAgentHeaders(inputs[0]);
23360
+ const ids=[...new Set(headers.map(header=>rawId(workspaceScalar(header.id))).filter(Boolean))];
23361
+ if(ids.length!==1||excluded.has(ids[0])||options.agentId&&ids[0]!==rawId(options.agentId)){
23362
+ return JSON.stringify(fail('agent_workspace_identity_unavailable',{
23363
+ inputCount:inputs.length,headerCount:headers.length,candidateAgentIds:ids,
23364
+ identitySource:'writable_input_react_ancestors',expectedAgentId:options.agentId||null,
23365
+ excludedAgentIdsMatched:ids.filter(id=>excluded.has(id))
23366
+ }));
23367
+ }
23368
+ const id=ids[0];
23369
+ if(options.requireInputFocus&&document.activeElement!==inputs[0])return JSON.stringify(fail('agent_input_focus_mismatch'));
23370
+ let observed;
23371
+ for(const header of headers){
23372
+ const target=workspaceScalar(header.targetEnvironment);
23373
+ const environment=workspaceScalar(header.environment);
23374
+ const location=workspaceScalar(header.location);
23375
+ observed={agentId:'local:'+id,targetType:target&&target.type||null,
23376
+ workspaceId:environment&&environment.id||null,path:localWorkspacePath(environment)||null,
23377
+ targetPath:localWorkspacePath(target&&target.environment)||null,locationType:location&&location.type||null};
23378
+ if(!wanted||!environment||!environment.id||!target||target.type!=='existing'
23379
+ ||environment.remoteAuthority||target.remoteAuthority||target.environment&&target.environment.remoteAuthority
23380
+ ||observed.path!==wanted||observed.targetPath!==wanted||target.environment.id!==environment.id
23381
+ ||location&&(location.type!=='local'&&location.type!=='worktree'
23382
+ ||localWorkspacePath(location.environment)!==wanted
23383
+ ||location.type==='worktree'&&normalizeWorkspacePath(location.worktreePath)!==wanted)){
23384
+ return JSON.stringify(fail('agent_workspace_mismatch',observed));
23385
+ }
23386
+ }
23387
+ return JSON.stringify({ok:true,state:'agent_workspace_verified',workspace:wanted,
23388
+ environment:'local',identitySource:'selected_agent_existing_file_uri',...observed});
23274
23389
  })()`;
23275
23390
  }
23276
23391
  var EXPR_HISTORY_OPEN = `(function(){return !![...document.querySelectorAll('.compact-agent-history-react-menu-label')].find(e=>e.offsetParent!==null);})()`;
@@ -23749,7 +23864,8 @@ var CursorBridge = class {
23749
23864
  initialized: !!this.projectPath,
23750
23865
  reinitializable: true,
23751
23866
  interactionPreference: "agents_v2_when_open_else_legacy",
23752
- cursorUiPreferencePreserved: true
23867
+ cursorUiPreferencePreserved: true,
23868
+ workspaceBinding: this._lastWorkspaceBinding || null
23753
23869
  };
23754
23870
  }
23755
23871
  sessionRegistryView() {
@@ -24185,6 +24301,7 @@ var CursorBridge = class {
24185
24301
  this.workspaceSource = "persistent_init";
24186
24302
  this.workspaceUpdatedAt = saved.updatedAt;
24187
24303
  this._lastLifecycle = null;
24304
+ this._lastWorkspaceBinding = null;
24188
24305
  try {
24189
24306
  await this._ensureCursor();
24190
24307
  } catch (error2) {
@@ -24224,6 +24341,7 @@ var CursorBridge = class {
24224
24341
  }
24225
24342
  async _findAgentsWorkspace(projectPath) {
24226
24343
  if (!projectPath) return null;
24344
+ this._lastWorkspaceBinding = null;
24227
24345
  let page;
24228
24346
  try {
24229
24347
  page = await findPage({ purpose: "fifo", preferAgentsV2: true });
@@ -24235,13 +24353,15 @@ var CursorBridge = class {
24235
24353
  try {
24236
24354
  await c.ready;
24237
24355
  const repository = JSON.parse(await evalJS(c, exprInspectWorkspaceRepository(projectPath)) || "{}");
24356
+ this._lastWorkspaceBinding = { ...repository, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
24238
24357
  if (!repository.ok) return null;
24239
24358
  return {
24240
24359
  targetId: page.id,
24241
24360
  targetUiFlavor: "agents_v2",
24242
24361
  workspace: repository.workspace
24243
24362
  };
24244
- } catch {
24363
+ } catch (error2) {
24364
+ this._lastWorkspaceBinding = { ok: false, state: "workspace_probe_failed", wanted: projectPath, error: String(error2.message || error2) };
24245
24365
  return null;
24246
24366
  } finally {
24247
24367
  c.close();
@@ -24371,16 +24491,18 @@ var CursorBridge = class {
24371
24491
  recovery: normalized === "minimal" ? "Switch CCE to normal mode before opening Cursor manually." : null
24372
24492
  };
24373
24493
  }
24374
- async contextEngine(query) {
24494
+ async contextEngine(query, options = {}) {
24375
24495
  const text = String(query || "").trim();
24376
24496
  if (!text) throw new Error("query must not be empty");
24377
24497
  if (text.length > 2e4) throw new Error("query exceeds the 20,000-character limit");
24498
+ const requestContext = normalizeRequestContext(options.requestContext);
24378
24499
  this._assertWorkspaceConfirmed();
24379
24500
  if (this._hasGlobalReservation()) {
24380
24501
  throw new Error("A global Cursor reservation has an unconfirmed Stop state; resolve blockingTaskIds from cursor_status first");
24381
24502
  }
24382
24503
  await this._ensureCursor();
24383
- const job = this._enqueue("context_engine", buildContextEnginePrompt(text), {
24504
+ const job = this._enqueue("context_engine", buildContextEnginePrompt(text, requestContext), {
24505
+ requestContext,
24384
24506
  timeoutMs: QUERY_TIMEOUT,
24385
24507
  newChat: true,
24386
24508
  execution: "fifo",
@@ -24406,6 +24528,7 @@ var CursorBridge = class {
24406
24528
  const text = String(prompt || "").trim();
24407
24529
  if (!text) throw new Error("prompt must not be empty");
24408
24530
  if (text.length > 1e5) throw new Error("prompt exceeds the 100,000-character limit");
24531
+ const requestContext = normalizeRequestContext(options.requestContext);
24409
24532
  this._assertWorkspaceConfirmed();
24410
24533
  if (this._hasGlobalReservation()) {
24411
24534
  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");
@@ -24454,7 +24577,7 @@ var CursorBridge = class {
24454
24577
  throw cursorSessionError("SESSION_WORKSPACE_REQUIRED", "initialize one workspace before creating or continuing a session");
24455
24578
  }
24456
24579
  const contract = String(options.completionContract || "").trim();
24457
- let fullPrompt = text + DO_LANGUAGE_CONTRACT;
24580
+ let fullPrompt = buildRequestContextHeader(requestContext) + "\n\nTask:\n" + text + DO_LANGUAGE_CONTRACT;
24458
24581
  if (readOnly) fullPrompt += "\n\nRead-only boundary: Do not modify, create, or delete files, and do not run commands that change workspace state.";
24459
24582
  if (allowedPaths.length > 0) {
24460
24583
  fullPrompt += "\n\nAllowed modification scope (do not cross this boundary):\n" + allowedPaths.map((x) => "- " + x).join("\n");
@@ -24493,6 +24616,7 @@ var CursorBridge = class {
24493
24616
  }
24494
24617
  }
24495
24618
  const job = this._enqueue("do", fullPrompt, {
24619
+ requestContext,
24496
24620
  taskId,
24497
24621
  timeoutMs,
24498
24622
  newChat: sessionMode !== "continue",
@@ -24556,6 +24680,7 @@ var CursorBridge = class {
24556
24680
  id,
24557
24681
  kind,
24558
24682
  prompt,
24683
+ requestContext: options.requestContext || normalizeRequestContext(),
24559
24684
  timeoutMs: options.timeoutMs,
24560
24685
  newChat: options.newChat,
24561
24686
  preferLegacyUi: options.preferLegacyUi === true,
@@ -24635,6 +24760,7 @@ var CursorBridge = class {
24635
24760
  if (isTerminalTask(job)) return;
24636
24761
  const e = error2 instanceof Error ? error2 : new Error(String(error2));
24637
24762
  job.error = e.message;
24763
+ if (e.workspaceBindingFailure) job.workspaceBinding = e.workspaceBindingFailure;
24638
24764
  if (e.providerError) job.providerError = e.providerError;
24639
24765
  if (e.uiDiagnostic) job.uiDiagnostic = e.uiDiagnostic;
24640
24766
  if (e.terminalEvidence) job.terminalEvidence = e.terminalEvidence;
@@ -24740,7 +24866,7 @@ var CursorBridge = class {
24740
24866
  this._lastLifecycle = promoteAgentsWorkspaceLifecycle(this._lastLifecycle, agentsWorkspace);
24741
24867
  }
24742
24868
  }
24743
- if (rr.ok && rr.lifecycleMode === "attached" && rr.workspaceAction === "reused-agents-window" && rr.projectPath) {
24869
+ if (rr.ok && rr.workspaceAction === "reused-agents-window" && rr.projectPath) {
24744
24870
  const agentsWorkspace = await this._findAgentsWorkspace(rr.projectPath);
24745
24871
  if (agentsWorkspace) {
24746
24872
  this._lastLifecycle = promoteAgentsWorkspaceLifecycle(this._lastLifecycle, agentsWorkspace);
@@ -24748,9 +24874,9 @@ var CursorBridge = class {
24748
24874
  this._lastLifecycle = {
24749
24875
  ...this._lastLifecycle,
24750
24876
  status: "workspace-not-ready",
24751
- message: `Cursor is reachable, but Cursor Bridge could not verify workspace ${rr.projectPath} in the attached Agents Window.`,
24877
+ message: `Cursor is reachable, but Cursor Bridge could not verify workspace ${rr.projectPath} in the Agents Window (${this._lastWorkspaceBinding?.state || "workspace_probe_unavailable"}).`,
24752
24878
  needsAction: "open_workspace_in_cursor",
24753
- nextStep: `Open workspace ${rr.projectPath} in Cursor, then retry the same operation.`,
24879
+ nextStep: this._lastWorkspaceBinding?.nextStep || `Open workspace ${rr.projectPath} in Cursor, then retry the same operation.`,
24754
24880
  retryable: true
24755
24881
  };
24756
24882
  }
@@ -24927,10 +25053,11 @@ var CursorBridge = class {
24927
25053
  try {
24928
25054
  await this._newChat(c, {
24929
25055
  uiFlavor: options.targetUiFlavor,
24930
- projectPath: options.projectPath || this._lastLifecycle && this._lastLifecycle.projectPath || this.projectPath
25056
+ projectPath: options.projectPath || this._lastLifecycle && this._lastLifecycle.projectPath || this.projectPath,
25057
+ job: options
24931
25058
  });
24932
25059
  } catch (error2) {
24933
- if (attempt === 0 && options.targetUiFlavor === "agents_v2" && isAgentsWorkspaceBindError(error2)) {
25060
+ if (attempt === 0 && options.targetUiFlavor === "agents_v2" && isAgentsWorkspaceBindError(error2) && !error2.workspaceBindingFailure) {
24934
25061
  const fallback = await findPage({ purpose: "fifo", preferLegacy: true });
24935
25062
  if (fallback && fallback.id !== page.id) {
24936
25063
  options.fallbackReason = "agents_window_unbound_use_workbench";
@@ -24957,10 +25084,11 @@ var CursorBridge = class {
24957
25084
  } catch {
24958
25085
  }
24959
25086
  const providerErrorBaseline = providerErrorSignature(await this._readProviderError(c));
25087
+ await this._verifyAgentsWorkspace(c, options, "before_send");
24960
25088
  options.sendState = "dispatching";
24961
25089
  try {
24962
25090
  await chord(c, 0, "Enter", "Enter", 13);
24963
- await this._confirmSubmission(c, baseline.messageCount || 0, providerErrorBaseline);
25091
+ await this._confirmSubmission(c, baseline.messageCount || 0, providerErrorBaseline, options);
24964
25092
  options.sendState = "sent";
24965
25093
  options.sentAt = options.sentAt || (/* @__PURE__ */ new Date()).toISOString();
24966
25094
  await this._bindFifoAgentAfterSend(c, options, historyBefore, providerErrorBaseline);
@@ -25350,11 +25478,19 @@ var CursorBridge = class {
25350
25478
  async _newChat(c, options = {}) {
25351
25479
  if (options.uiFlavor === "agents_v2" && options.projectPath) {
25352
25480
  const created = JSON.parse(await evalJS(c, exprCreateAgentForWorkspace(options.projectPath)) || "{}");
25481
+ if (options.job) {
25482
+ options.job.workspaceBindingChecks = { before_create: { ...created, checkedAt: (/* @__PURE__ */ new Date()).toISOString() } };
25483
+ }
25353
25484
  if (!created.ok) {
25354
- const available = Array.isArray(created.available) ? `; available=${created.available.join(", ")}` : "";
25355
- throw new Error(`Cursor Agents workspace binding failed: ${created.state || "unknown"}; wanted=${created.wanted || basename6(options.projectPath)}${available}`);
25485
+ throw createWorkspaceBindingError(created);
25356
25486
  }
25357
25487
  await sleep2(1100);
25488
+ const actual = JSON.parse(await evalJS(c, exprInspectAgentWorkspace(options.projectPath, { excludedAgentIds: created.previousAgentIds })) || "{}");
25489
+ if (options.job) options.job.workspaceBindingChecks.after_create = { ...actual, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
25490
+ if (!actual.ok) throw createWorkspaceBindingError(actual);
25491
+ if (actual.workspaceId !== created.workspaceId) {
25492
+ throw createWorkspaceBindingError({ ...actual, ok: false, state: "created_workspace_id_mismatch" });
25493
+ }
25358
25494
  return true;
25359
25495
  }
25360
25496
  return this._clickNewAgent(c, true);
@@ -25491,6 +25627,7 @@ var CursorBridge = class {
25491
25627
  }
25492
25628
  }
25493
25629
  async _fillPrompt(c, text, job, timeoutMs = 2500) {
25630
+ await this._verifyAgentsWorkspace(c, job);
25494
25631
  const prepared = await evalJS(c, EXPR_PREPARE_INPUT);
25495
25632
  if (prepared !== "READY") return prepared;
25496
25633
  try {
@@ -25522,7 +25659,18 @@ var CursorBridge = class {
25522
25659
  };
25523
25660
  throw error2;
25524
25661
  }
25525
- async _confirmSubmission(c, baselineCount = 0, providerErrorBaseline = "") {
25662
+ async _verifyAgentsWorkspace(c, job, stage = "before_fill") {
25663
+ if (!job || job.targetUiFlavor !== "agents_v2") return;
25664
+ const projectPath = job.projectPath || this.projectPath;
25665
+ const diagnostic = JSON.parse(await evalJS(c, exprInspectAgentWorkspace(projectPath, {
25666
+ agentId: job.agentId || job.provisionalAgentId || job.workspaceBinding?.agentId,
25667
+ requireInputFocus: stage === "before_send"
25668
+ })) || "{}");
25669
+ job.workspaceBinding = { ...diagnostic, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
25670
+ job.workspaceBindingChecks = { ...job.workspaceBindingChecks, [stage]: job.workspaceBinding };
25671
+ if (!diagnostic.ok) throw createWorkspaceBindingError(diagnostic);
25672
+ }
25673
+ async _confirmSubmission(c, baselineCount = 0, providerErrorBaseline = "", job = null) {
25526
25674
  const accepted = async () => {
25527
25675
  await this._throwIfNewProviderError(c, providerErrorBaseline);
25528
25676
  let snap = {};
@@ -25537,6 +25685,13 @@ var CursorBridge = class {
25537
25685
  await sleep2(250);
25538
25686
  if (await accepted()) return "enter";
25539
25687
  }
25688
+ try {
25689
+ await this._verifyAgentsWorkspace(c, job, "before_send_fallback");
25690
+ } catch (error3) {
25691
+ error3.confirmedNotSent = false;
25692
+ error3.sent = true;
25693
+ throw error3;
25694
+ }
25540
25695
  const clicked = await evalJS(c, EXPR_CLICK_SEND);
25541
25696
  if (clicked === "CLICKED") {
25542
25697
  for (let i = 0; i < 20; i++) {
@@ -25587,9 +25742,9 @@ var CursorBridge = class {
25587
25742
  this._throwIfCancelledBeforeSend(job);
25588
25743
  let createdForWorkspace = false;
25589
25744
  try {
25590
- createdForWorkspace = job.targetUiFlavor === "agents_v2" && job.projectPath ? await this._newChat(c, { uiFlavor: job.targetUiFlavor, projectPath: job.projectPath }) : await this._clickNewAgent(c, false);
25745
+ createdForWorkspace = job.targetUiFlavor === "agents_v2" && job.projectPath ? await this._newChat(c, { uiFlavor: job.targetUiFlavor, projectPath: job.projectPath, job }) : await this._clickNewAgent(c, false);
25591
25746
  } catch (error2) {
25592
- if (isAgentsWorkspaceBindError(error2)) {
25747
+ if (isAgentsWorkspaceBindError(error2) && !error2.workspaceBindingFailure) {
25593
25748
  return { fallbackReason: "Agents Window could not bind the current repository; downgraded to FIFO/workbench before submission" };
25594
25749
  }
25595
25750
  throw error2;
@@ -25622,9 +25777,10 @@ var CursorBridge = class {
25622
25777
  baseline = JSON.parse(await evalJS(c, EXPR_SNAP));
25623
25778
  } catch {
25624
25779
  }
25780
+ await this._verifyAgentsWorkspace(c, job, "before_send");
25625
25781
  job.sendState = "dispatching";
25626
25782
  await chord(c, 0, "Enter", "Enter", 13);
25627
- await this._confirmSubmission(c, baseline.messageCount || 0, providerErrorBaseline);
25783
+ await this._confirmSubmission(c, baseline.messageCount || 0, providerErrorBaseline, job);
25628
25784
  sent = true;
25629
25785
  job.sendState = "sent";
25630
25786
  job.sentAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -25739,9 +25895,10 @@ var CursorBridge = class {
25739
25895
  }
25740
25896
  await sleep2(350);
25741
25897
  this._throwIfCancelledBeforeSend(job);
25898
+ await this._verifyAgentsWorkspace(c, job, "before_send");
25742
25899
  job.sendState = "dispatching";
25743
25900
  await chord(c, 0, "Enter", "Enter", 13);
25744
- await this._confirmSubmission(c, job.responseBaseline.messageCount, providerErrorBaseline);
25901
+ await this._confirmSubmission(c, job.responseBaseline.messageCount, providerErrorBaseline, job);
25745
25902
  sent = true;
25746
25903
  job.sendState = "sent";
25747
25904
  job.sentAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -26569,6 +26726,9 @@ var CursorBridge = class {
26569
26726
  allowedPaths: job.allowedPaths,
26570
26727
  modelPreference: job.modelPreference,
26571
26728
  modelSelection: job.modelSelection,
26729
+ requestContext: job.requestContext || normalizeRequestContext(),
26730
+ workspaceBinding: job.workspaceBinding || null,
26731
+ workspaceBindingChecks: job.workspaceBindingChecks || null,
26572
26732
  projectPath: job.projectPath,
26573
26733
  sessionMode: job.sessionMode || "isolated",
26574
26734
  sessionId: job.sessionId || null,
@@ -26696,11 +26856,21 @@ function buildSearchInputSchema() {
26696
26856
  return {
26697
26857
  type: "object",
26698
26858
  properties: {
26699
- query: { type: "string", description: "Describe the behavior, concept, symbol relationship, or ownership boundary to locate. State intent instead of guessing a directory." }
26859
+ query: { type: "string", description: "Describe the behavior, concept, symbol relationship, or ownership boundary to locate. State intent instead of guessing a directory." },
26860
+ request_context: REQUEST_CONTEXT_SCHEMA
26700
26861
  },
26701
26862
  required: ["query"]
26702
26863
  };
26703
26864
  }
26865
+ var REQUEST_CONTEXT_SCHEMA = {
26866
+ type: "object",
26867
+ additionalProperties: false,
26868
+ description: "Caller-declared provenance for this request only, not authentication or authorization. Missing values remain unknown; never infer them from the chosen model. Separate user requirements and model additions in mixed task text.",
26869
+ properties: {
26870
+ sender: { type: "string", enum: ["user", "model", "unknown"], description: "Who directly sends this Bridge request. AI callers should declare model, even when acting on a user request." },
26871
+ 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." }
26872
+ }
26873
+ };
26704
26874
  function buildToolDefinitions(bridgeInstance) {
26705
26875
  return [
26706
26876
  {
@@ -26726,6 +26896,7 @@ function buildToolDefinitions(bridgeInstance) {
26726
26896
  type: "object",
26727
26897
  properties: {
26728
26898
  prompt: { type: "string", description: "The task Cursor should receive. State the goal, boundaries, and what a complete result looks like." },
26899
+ request_context: REQUEST_CONTEXT_SCHEMA,
26729
26900
  background: { type: "boolean", default: true, description: "When true, return the task ID immediately. When false, wait for the task to finish or need attention." },
26730
26901
  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." },
26731
26902
  read_only: { type: "boolean", default: false, description: "Set true when Cursor must not change the workspace." },
@@ -26855,11 +27026,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request2) => {
26855
27026
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
26856
27027
  }
26857
27028
  if (name === "cursor_context_engine" || name === "cursor_search" || name === "cursor_search_deep") {
26858
- const result = await bridge.contextEngine(String(args && args.query || ""));
27029
+ const result = await bridge.contextEngine(String(args && args.query || ""), { requestContext: args && args.request_context });
26859
27030
  return { content: [{ type: "text", text: String(result) }] };
26860
27031
  }
26861
27032
  if (name === "cursor_do") {
26862
27033
  const result = await bridge.doTask(String(args && args.prompt || ""), {
27034
+ requestContext: args && args.request_context,
26863
27035
  background: !args || args.background !== false,
26864
27036
  execution: args && args.execution,
26865
27037
  readOnly: !!(args && args.read_only),
@@ -27002,6 +27174,7 @@ export {
27002
27174
  exprClickBoundComposerStop,
27003
27175
  exprClickSelectedAgentStop,
27004
27176
  exprCreateAgentForWorkspace,
27177
+ exprInspectAgentWorkspace,
27005
27178
  exprInspectWorkspaceRepository,
27006
27179
  exprOpenAgent,
27007
27180
  isConfirmedCompletedReply,
@@ -27017,6 +27190,7 @@ export {
27017
27190
  normalizeCursorRuntimeMode,
27018
27191
  normalizeDelegationMode,
27019
27192
  normalizeModelPickerText,
27193
+ normalizeRequestContext,
27020
27194
  pathsOverlap,
27021
27195
  promoteAgentsWorkspaceLifecycle,
27022
27196
  providerErrorSignature,
@@ -10,7 +10,7 @@ const hostWorkspaceId = hostCwd.replace(/\\/g, "/").toLowerCase();
10
10
  export default createStdioMcpExtension({
11
11
  label: "Cursor Bridge",
12
12
  clientName: "pi-cursor-bridge",
13
- packageVersion: "0.1.14",
13
+ packageVersion: "0.1.16",
14
14
  serverName: "cursor-bridge",
15
15
  serverScript: join(packageRoot, "dist", "cursor-bridge.mjs"),
16
16
  cwd: hostCwd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cursor-bridge",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "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": "5.9.1"
50
+ "embeddedProductVersion": "5.10.0"
51
51
  }
52
52
  }
@@ -41,7 +41,7 @@ Call `cursor_context_engine` once with the question's real intent. Include a kno
41
41
  - Describe the relationship or behavior to establish and the evidence needed.
42
42
  - Preserve the language of the user's current substantive request unless the user explicitly asks for another language. Do not infer or persist a different language from the operating system when the conversation already provides a clear signal.
43
43
  - Do not prescribe Cursor's internal search sequence, harness, Explore usage, or number of files.
44
- - Do not invent hidden parameters; the public input is only `query`.
44
+ - Keep investigation intent in `query`. Use the optional `request_context` only to declare provenance: AI callers use `sender="model"`; `source` is `user`, `model`, `mixed`, or `unknown`. A retrieval question you inferred is model-authored even when it serves a user goal. Separate user requirements and your additions when using `mixed`; never guess missing provenance.
45
45
  - Allow a cold or large workspace enough time to complete its serialized Cursor UI turn.
46
46
 
47
47
  ## Verify and continue
@@ -7,6 +7,8 @@ description: "Delegate bounded light-to-medium implementation, investigation, do
7
7
 
8
8
  Use Cursor as an execution partner. Keep direction, scope decisions, risk ownership, result review, and final verification with the primary agent.
9
9
 
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
+
10
12
  ## Respect execution controls
11
13
 
12
14
  - 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.
@@ -16,6 +16,7 @@ Provide every task independently:
16
16
  | Field | Requirement |
17
17
  |---|---|
18
18
  | `prompt` | State one objective, the necessary context, prohibited actions, and the expected report. Use the current user-task language unless the user explicitly requests another language. Do not ask Cursor to repeat the primary agent's scope decision. |
19
+ | `request_context` | Declare `sender` (`user/model/unknown`) and instruction `source` (`user/model/mixed/unknown`) for this turn. AI callers use sender=model. Label user-confirmed requirements and model additions separately for mixed prompts. Omitted fields remain unknown; these declarations never authorize work. |
19
20
  | `execution` | Use only `fifo` or `parallel_agent`. Use `fifo` when safe parallelism cannot be demonstrated. |
20
21
  | `read_only` | Use `true` for lookup and analysis; use `false` for any file modification. |
21
22
  | `allowed_paths` | Required when `read_only=false`. Provide the smallest workspace-relative path set, with no glob, absolute path, or workspace-escaping `..`. Omit it when `read_only=true`. This is not a filesystem sandbox. |
@@ -40,6 +41,7 @@ Parallel read-only task:
40
41
  ```json
41
42
  {
42
43
  "prompt": "Read the specified files and return conclusions without modifying any file.",
44
+ "request_context": { "sender": "model", "source": "model" },
43
45
  "execution": "parallel_agent",
44
46
  "read_only": true,
45
47
  "background": true,
@@ -52,6 +54,7 @@ Bounded write task:
52
54
  ```json
53
55
  {
54
56
  "prompt": "Implement the specified tool script under the fixed design without expanding scope.",
57
+ "request_context": { "sender": "model", "source": "model" },
55
58
  "execution": "parallel_agent",
56
59
  "read_only": false,
57
60
  "background": true,