pi-cursor-bridge 0.1.15 → 0.1.17

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.1+codex.20260908161937",
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.1, 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)
@@ -22344,6 +22344,7 @@ var StdioServerTransport = class {
22344
22344
 
22345
22345
  // server.mjs
22346
22346
  import { basename as basename6, dirname as dirname6, join as join8, resolve as resolve7 } from "node:path";
22347
+ import { statSync as statSync3 } from "node:fs";
22347
22348
 
22348
22349
  // node_modules/ws/wrapper.mjs
22349
22350
  var import_stream = __toESM(require_stream(), 1);
@@ -22604,7 +22605,7 @@ function updateCursorSessionRegistry(filePath, mutator, options = {}) {
22604
22605
  // server.mjs
22605
22606
  init_cursor_ensure_core();
22606
22607
  init_lifecycle_paths();
22607
- var PLUGIN_VERSION = "5.9.1";
22608
+ var PLUGIN_VERSION = "5.10.1";
22608
22609
  var CDP_PORT2 = Number(process.env.CURSOR_BRIDGE_CDP_PORT || 9223);
22609
22610
  var ORIGIN = `http://localhost:${CDP_PORT2}`;
22610
22611
  var QUERY_TIMEOUT = Number(process.env.CURSOR_BRIDGE_TIMEOUT || 3e5);
@@ -22646,8 +22647,28 @@ function searchResultContract() {
22646
22647
  "confidence: <high|medium|low> (rate retrieval evidence only, not code correctness)"
22647
22648
  ];
22648
22649
  }
22649
- function buildContextEnginePrompt(query) {
22650
+ function normalizeRequestContext(value) {
22651
+ if (value === void 0) return { sender: "unknown", source: "unknown" };
22652
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["sender", "source"].includes(key))) {
22653
+ throw new Error("request_context must contain only sender and source");
22654
+ }
22655
+ const sender = value.sender === void 0 ? "unknown" : value.sender;
22656
+ const source = value.source === void 0 ? "unknown" : value.source;
22657
+ if (!["user", "model", "unknown"].includes(sender)) throw new Error("request_context.sender must be user, model, or unknown");
22658
+ if (!["user", "model", "mixed", "unknown"].includes(source)) throw new Error("request_context.source must be user, model, mixed, or unknown");
22659
+ return { sender, source };
22660
+ }
22661
+ function buildRequestContextHeader(context) {
22650
22662
  return [
22663
+ "Request provenance (declared by the upstream caller, not independently authenticated):",
22664
+ `Immediate sender: ${context.sender}. Instruction source: ${context.source}.`,
22665
+ "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.",
22666
+ "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."
22667
+ ].join("\n");
22668
+ }
22669
+ function buildContextEnginePrompt(query, requestContext = normalizeRequestContext()) {
22670
+ return [
22671
+ buildRequestContextHeader(requestContext),
22651
22672
  "You are Cursor Context Engine (CCE), a read-only, evidence-driven project-understanding engine.",
22652
22673
  "Resolve natural-language intent into verifiable code context. Do not guess locations, repeat framework conventions, or propose an implementation.",
22653
22674
  "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 +22827,13 @@ function selectPageForUiPreference(inspected, options = {}) {
22806
22827
  function isAgentsWorkspaceBindError(error2) {
22807
22828
  return !!(error2 && /Cursor Agents workspace binding failed/.test(String(error2.message || "")));
22808
22829
  }
22830
+ function createWorkspaceBindingError(diagnostic) {
22831
+ 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."}`);
22832
+ error2.code = "CURSOR_WORKSPACE_BINDING_FAILED";
22833
+ error2.workspaceBindingFailure = diagnostic;
22834
+ error2.confirmedNotSent = true;
22835
+ return error2;
22836
+ }
22809
22837
  async function findPage(options = {}) {
22810
22838
  const list = await httpJson("/json/list");
22811
22839
  const pages = list.filter((t) => t.type === "page" && t.webSocketDebuggerUrl);
@@ -22895,9 +22923,10 @@ var CURSOR_INPUT_SELECTOR = [
22895
22923
  ".aislash-editor-input"
22896
22924
  ].join(",");
22897
22925
  var INPUT_PICKER_BODY = `
22898
- const pickInput=()=>[...document.querySelectorAll(${JSON.stringify(CURSOR_INPUT_SELECTOR)})]
22926
+ const inputCandidates=()=>[...document.querySelectorAll(${JSON.stringify(CURSOR_INPUT_SELECTOR)})]
22899
22927
  .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;`;
22928
+ .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));
22929
+ const pickInput=()=>inputCandidates()[0]||null;`;
22901
22930
  var EXPR_VISIBLE = `(function(){${INPUT_PICKER_BODY}return !!pickInput();})()`;
22902
22931
  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
22932
  function classifyChatPanelDiagnostic(snapshot = {}) {
@@ -23202,75 +23231,216 @@ function createModelSelectionError(message, failureClass, retryable, diagnostic
23202
23231
  error2.modelSelectionFailure = { failureClass, retryable, ...diagnostic };
23203
23232
  return error2;
23204
23233
  }
23205
- var WORKSPACE_SECTION_BODY = `
23234
+ var WORKSPACE_SECTION_BODY = String.raw`
23235
+ const workspaceScalar=value=>value&&typeof value==='object'&&'value' in value?value.value:value;
23236
+ const normalizeWorkspacePath=value=>{
23237
+ let path=String(value||'').replace(/\\/g,'/').replace(/^\/([a-z]:\/)/i,'$1').replace(/\/+$/,'');
23238
+ return /^[a-z]:\//i.test(path)||path.startsWith('//')?path.toLowerCase():path;
23239
+ };
23240
+ const localWorkspacePath=identifier=>{
23241
+ const uri=identifier&&(identifier.uri||identifier.configPath);
23242
+ if(!uri||uri.scheme!=='file'||uri.authority)return '';
23243
+ return normalizeWorkspacePath(uri.fsPath||uri.path);
23244
+ };
23245
+ const inputAgentHeaders=input=>{
23246
+ const headers=new Set();
23247
+ for(let node=input,n=0;node&&n<12;n++,node=node.parentElement){
23248
+ const key=Object.keys(node).find(key=>key.startsWith('__reactFiber$'));
23249
+ for(let f=key&&node[key],i=0;f&&i<36;i++,f=f.return){
23250
+ const selected=f.memoizedProps&&f.memoizedProps.selectedAgent;
23251
+ const header=selected&&selected.reference&&selected.reference.header;
23252
+ if(header)headers.add(header);
23253
+ }
23254
+ }
23255
+ return [...headers];
23256
+ };
23206
23257
  const headText=(el)=>String(el&&(el.innerText||el.textContent)||'').trim();
23207
23258
  const isNewAgentButton=(node)=>{
23208
23259
  if(!node)return false;
23209
23260
  const aria=String(node.getAttribute&&node.getAttribute('aria-label')||'').trim();
23210
- const text=String(node.innerText||'').trim().split('\\n')[0].trim();
23261
+ const text=String(node.innerText||'').trim().split('\n')[0].trim();
23211
23262
  return /^New Agent$/i.test(aria)||/^New Agent$/i.test(text);
23212
23263
  };
23213
23264
  const findNewAgent=(root)=>[...(root&&root.querySelectorAll?root.querySelectorAll('button,[role=button]'):[])].find(isNewAgentButton)||null;
23214
23265
  const collectWorkspaceSections=()=>{
23215
23266
  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
- };
23267
+ const heads=new Set(document.querySelectorAll('.ui-sidebar-section-head'));
23225
23268
  for(const section of document.querySelectorAll('section.glass-sidebar-workspace-section-root')){
23226
- add(headText(section.querySelector('.ui-sidebar-section-head')),section,null);
23269
+ const head=section.querySelector('.ui-sidebar-section-head');
23270
+ if(head)heads.add(head);
23227
23271
  }
23228
- for(const headEl of document.querySelectorAll('.ui-sidebar-section-head')){
23272
+ for(const headEl of heads){
23229
23273
  const name=headText(headEl);
23230
23274
  if(!name)continue;
23275
+ let metadata=null;
23276
+ const key=Object.keys(headEl).find(key=>key.startsWith('__reactFiber$'));
23277
+ for(let f=key&&headEl[key],i=0;f&&i<36;i++,f=f.return){
23278
+ const p=f.memoizedProps;
23279
+ if(p&&p.section&&Array.isArray(p.section.projects)){metadata=p.section;break;}
23280
+ }
23231
23281
  let scope=headEl;
23232
23282
  let chosen=null;
23233
23283
  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()))];
23284
+ const nested=[...scope.querySelectorAll('.ui-sidebar-section-head')];
23236
23285
  const button=findNewAgent(scope);
23237
- if(button&&unique.length===1&&unique[0]===name.toLowerCase()){
23286
+ if(button&&nested.length===1&&nested[0]===headEl){
23238
23287
  chosen={node:scope,button};
23239
23288
  break;
23240
23289
  }
23241
23290
  }
23242
- add(name,chosen?chosen.node:headEl,chosen&&chosen.button);
23291
+ const source=metadata&&(metadata.newAgentTargetSource||metadata);
23292
+ sections.push({head:name,metadata,source,button:chosen&&chosen.button});
23243
23293
  }
23244
23294
  return sections;
23245
23295
  };
23296
+ const inspectWorkspace=projectPath=>{
23297
+ const wanted=normalizeWorkspacePath(projectPath);
23298
+ const sections=collectWorkspaceSections();
23299
+ const available=sections.map(s=>({
23300
+ title:s.head,sectionId:s.metadata&&s.metadata.id||null,
23301
+ projects:(s.source&&s.source.projects||[]).map(p=>({
23302
+ type:p.type,workspaceId:p.workspaceIdentifier&&p.workspaceIdentifier.id||null,
23303
+ path:localWorkspacePath(p.workspaceIdentifier)||null,remoteAuthority:p.remoteAuthority||null,
23304
+ repoUrls:Array.isArray(p.repoUrls)?p.repoUrls:[]
23305
+ }))
23306
+ }));
23307
+ const matches=[];
23308
+ for(const section of sections){
23309
+ for(const project of section.source&&section.source.projects||[]){
23310
+ if(project.type==='workspace'&&localWorkspacePath(project.workspaceIdentifier)===wanted){
23311
+ matches.push({section,project});
23312
+ }
23313
+ }
23314
+ }
23315
+ const fail=state=>({ok:false,state,wanted,available,
23316
+ 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.'});
23317
+ if(!matches.length)return {diagnostic:fail(sections.some(s=>s.metadata)?'workspace_registration_required':'workspace_identity_unavailable')};
23318
+ if(matches.length!==1)return {diagnostic:fail('workspace_ambiguous')};
23319
+ const {section,project}=matches[0];
23320
+ if(project.remoteAuthority||!project.workspaceIdentifier.id)return {diagnostic:fail('workspace_environment_unverified')};
23321
+ // Cursor's section-level New Agent action chooses among registered targets.
23322
+ // Do not click it when a second workspace could win that choice.
23323
+ if(section.source.projects.length!==1)return {diagnostic:fail('workspace_creation_target_ambiguous')};
23324
+ if(!section.button)return {diagnostic:fail('workspace_new_agent_unavailable')};
23325
+ return {section,diagnostic:{ok:true,state:'workspace_ready',workspace:wanted,
23326
+ workspaceId:project.workspaceIdentifier.id,sectionId:section.metadata.id,
23327
+ environment:'local',identitySource:'registered_workspace_file_uri',repoUrls:Array.isArray(project.repoUrls)?project.repoUrls:[]}};
23328
+ };
23246
23329
  `;
23247
23330
  function exprCreateAgentForWorkspace(projectPath) {
23248
- const workspaceLabel = JSON.stringify(basename6(String(projectPath || "")).trim().toLowerCase());
23249
23331
  return `(function(){
23250
23332
  ${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});
23333
+ ${INPUT_PICKER_BODY}
23334
+ const result=inspectWorkspace(${JSON.stringify(String(projectPath || ""))});
23335
+ if(!result.diagnostic.ok)return JSON.stringify(result.diagnostic);
23336
+ const previousAgentIds=[...new Set(inputCandidates().flatMap(inputAgentHeaders)
23337
+ .map(header=>String(workspaceScalar(header.id)||'')).filter(Boolean))];
23338
+ result.section.button.click();
23339
+ return JSON.stringify({...result.diagnostic,state:'workspace_agent_creation_requested',previousAgentIds});
23261
23340
  })()`;
23262
23341
  }
23263
23342
  function exprInspectWorkspaceRepository(projectPath) {
23264
- const workspaceLabel = JSON.stringify(basename6(String(projectPath || "")).trim().toLowerCase());
23265
23343
  return `(function(){
23266
23344
  ${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});
23345
+ return JSON.stringify(inspectWorkspace(${JSON.stringify(String(projectPath || ""))}).diagnostic);
23346
+ })()`;
23347
+ }
23348
+ function exprRegisterAgentsWorkspace(projectPath, workspaceFile = false) {
23349
+ return `(async function(){
23350
+ ${WORKSPACE_SECTION_BODY}
23351
+ const path=${JSON.stringify(String(projectPath || ""))};
23352
+ const before=inspectWorkspace(path).diagnostic;
23353
+ if(before.state!=='workspace_registration_required')return JSON.stringify(before);
23354
+ const fail=(state,error)=>JSON.stringify({...before,state,error});
23355
+ const services=new Set();
23356
+ for(const node of document.querySelectorAll('button[aria-label="Open Workspace"],.ui-sidebar-section-head')){
23357
+ const key=Object.keys(node).find(k=>k.startsWith('__reactFiber$'));
23358
+ for(let f=key&&node[key],i=0;f&&i<48;i++,f=f.return){
23359
+ for(let d=f.dependencies&&f.dependencies.firstContext;d;d=d.next){
23360
+ const service=d.memoizedValue&&d.memoizedValue.workspace&&d.memoizedValue.workspace.instantiationService;
23361
+ if(service)services.add(service);
23362
+ }
23363
+ }
23364
+ }
23365
+ const candidates=new Map();
23366
+ for(const service of services){
23367
+ const entries=new Map();
23368
+ for(let s=service,i=0;s&&i<8;i++,s=s._parent){
23369
+ for(const [id,value] of s._services&&s._services._entries||[]){
23370
+ if(!entries.has(String(id)))entries.set(String(id),value);
23371
+ }
23372
+ }
23373
+ const projects=entries.get('glassWorkspacesService');
23374
+ const workspaces=entries.get('workspacesService');
23375
+ const URI=entries.get('environmentService')?.userHome?.constructor;
23376
+ if(typeof projects?.replaceWorkspaceProject==='function'&&typeof URI?.file==='function'
23377
+ &&typeof workspaces?.getSingleFolderWorkspaceIdentifier==='function'
23378
+ &&typeof workspaces?.getWorkspaceIdentifier==='function'
23379
+ &&typeof projects?.refresh==='function'){
23380
+ candidates.set(projects,{projects,workspaces,URI});
23381
+ }
23382
+ }
23383
+ if(candidates.size!==1)return fail('workspace_registration_unavailable','Expected one Cursor workspace registration service');
23384
+ try{
23385
+ const {projects,workspaces,URI}=[...candidates.values()][0];
23386
+ const uri=URI.file(path);
23387
+ const identifier=await workspaces[${JSON.stringify(workspaceFile ? "getWorkspaceIdentifier" : "getSingleFolderWorkspaceIdentifier")}](uri);
23388
+ if(!identifier?.id||localWorkspacePath(identifier)!==normalizeWorkspacePath(path)){
23389
+ return fail('workspace_registration_identity_mismatch','Cursor returned a different workspace identity');
23390
+ }
23391
+ const current=inspectWorkspace(path).diagnostic;
23392
+ if(current.state!=='workspace_registration_required')return JSON.stringify(current);
23393
+ // addProject can skip folders already represented by Agent history.
23394
+ // With no 'replaces', this public service method upserts only this exact ID.
23395
+ await projects.replaceWorkspaceProject({project:{type:'workspace',workspaceIdentifier:identifier}});
23396
+ await projects.refresh();
23397
+ return JSON.stringify({ok:false,state:'workspace_registration_requested',wanted:before.wanted,
23398
+ workspaceId:identifier.id,registrationSource:'cursor_glass_workspaces_service'});
23399
+ }catch(error){return fail('workspace_registration_failed',String(error.message||error));}
23400
+ })()`;
23401
+ }
23402
+ function exprInspectAgentWorkspace(projectPath, options = {}) {
23403
+ return `(function(){
23404
+ ${WORKSPACE_SECTION_BODY}
23405
+ ${INPUT_PICKER_BODY}
23406
+ const wanted=normalizeWorkspacePath(${JSON.stringify(String(projectPath || ""))});
23407
+ const options=${JSON.stringify(options)};
23408
+ const rawId=id=>String(id||'').replace(/^local:/,'');
23409
+ const excluded=new Set((options.excludedAgentIds||[]).map(rawId));
23410
+ const fail=(state,observed=null)=>({ok:false,state,wanted,observed,
23411
+ 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.'});
23412
+ const inputs=inputCandidates();
23413
+ if(inputs.length!==1)return JSON.stringify(fail('agent_workspace_identity_unavailable',{inputCount:inputs.length}));
23414
+ const headers=inputAgentHeaders(inputs[0]);
23415
+ const ids=[...new Set(headers.map(header=>rawId(workspaceScalar(header.id))).filter(Boolean))];
23416
+ if(ids.length!==1||excluded.has(ids[0])||options.agentId&&ids[0]!==rawId(options.agentId)){
23417
+ return JSON.stringify(fail('agent_workspace_identity_unavailable',{
23418
+ inputCount:inputs.length,headerCount:headers.length,candidateAgentIds:ids,
23419
+ identitySource:'writable_input_react_ancestors',expectedAgentId:options.agentId||null,
23420
+ excludedAgentIdsMatched:ids.filter(id=>excluded.has(id))
23421
+ }));
23422
+ }
23423
+ const id=ids[0];
23424
+ if(options.requireInputFocus&&document.activeElement!==inputs[0])return JSON.stringify(fail('agent_input_focus_mismatch'));
23425
+ let observed;
23426
+ for(const header of headers){
23427
+ const target=workspaceScalar(header.targetEnvironment);
23428
+ const environment=workspaceScalar(header.environment);
23429
+ const location=workspaceScalar(header.location);
23430
+ observed={agentId:'local:'+id,targetType:target&&target.type||null,
23431
+ workspaceId:environment&&environment.id||null,path:localWorkspacePath(environment)||null,
23432
+ targetPath:localWorkspacePath(target&&target.environment)||null,locationType:location&&location.type||null};
23433
+ if(!wanted||!environment||!environment.id||!target||target.type!=='existing'
23434
+ ||environment.remoteAuthority||target.remoteAuthority||target.environment&&target.environment.remoteAuthority
23435
+ ||observed.path!==wanted||observed.targetPath!==wanted||target.environment.id!==environment.id
23436
+ ||location&&(location.type!=='local'&&location.type!=='worktree'
23437
+ ||localWorkspacePath(location.environment)!==wanted
23438
+ ||location.type==='worktree'&&normalizeWorkspacePath(location.worktreePath)!==wanted)){
23439
+ return JSON.stringify(fail('agent_workspace_mismatch',observed));
23440
+ }
23441
+ }
23442
+ return JSON.stringify({ok:true,state:'agent_workspace_verified',workspace:wanted,
23443
+ environment:'local',identitySource:'selected_agent_existing_file_uri',...observed});
23274
23444
  })()`;
23275
23445
  }
23276
23446
  var EXPR_HISTORY_OPEN = `(function(){return !![...document.querySelectorAll('.compact-agent-history-react-menu-label')].find(e=>e.offsetParent!==null);})()`;
@@ -23749,7 +23919,8 @@ var CursorBridge = class {
23749
23919
  initialized: !!this.projectPath,
23750
23920
  reinitializable: true,
23751
23921
  interactionPreference: "agents_v2_when_open_else_legacy",
23752
- cursorUiPreferencePreserved: true
23922
+ cursorUiPreferencePreserved: true,
23923
+ workspaceBinding: this._lastWorkspaceBinding || null
23753
23924
  };
23754
23925
  }
23755
23926
  sessionRegistryView() {
@@ -24175,6 +24346,8 @@ var CursorBridge = class {
24175
24346
  return result;
24176
24347
  }
24177
24348
  async initializeWorkspace(projectPath) {
24349
+ if (this._healing) await this._healing.catch(() => {
24350
+ });
24178
24351
  if (this.busy || this.activeParallel.size > 0 || this.queue.length > 0) {
24179
24352
  throw new Error("cursor_init cannot change workspace while Cursor tasks are queued or running");
24180
24353
  }
@@ -24185,8 +24358,9 @@ var CursorBridge = class {
24185
24358
  this.workspaceSource = "persistent_init";
24186
24359
  this.workspaceUpdatedAt = saved.updatedAt;
24187
24360
  this._lastLifecycle = null;
24361
+ this._lastWorkspaceBinding = null;
24188
24362
  try {
24189
- await this._ensureCursor();
24363
+ await this._ensureCursor({ registerWorkspace: true });
24190
24364
  } catch (error2) {
24191
24365
  const lifecycle = this._lastLifecycle;
24192
24366
  const recoverableStatuses = /* @__PURE__ */ new Set([
@@ -24222,8 +24396,9 @@ var CursorBridge = class {
24222
24396
  lifecycle: this._lastLifecycle
24223
24397
  };
24224
24398
  }
24225
- async _findAgentsWorkspace(projectPath) {
24399
+ async _findAgentsWorkspace(projectPath, { registerWorkspace = false } = {}) {
24226
24400
  if (!projectPath) return null;
24401
+ this._lastWorkspaceBinding = null;
24227
24402
  let page;
24228
24403
  try {
24229
24404
  page = await findPage({ purpose: "fifo", preferAgentsV2: true });
@@ -24234,14 +24409,33 @@ var CursorBridge = class {
24234
24409
  const c = makeClient(page.webSocketDebuggerUrl);
24235
24410
  try {
24236
24411
  await c.ready;
24237
- const repository = JSON.parse(await evalJS(c, exprInspectWorkspaceRepository(projectPath)) || "{}");
24412
+ let repository = JSON.parse(await evalJS(c, exprInspectWorkspaceRepository(projectPath)) || "{}");
24413
+ if (registerWorkspace && repository.state === "workspace_registration_required") {
24414
+ const registration = await this._withUiLock(async () => {
24415
+ if (this.busy || this.activeParallel.size > 0 || this.queue.length > 0) {
24416
+ return { ok: false, state: "workspace_registration_busy", error: "Cursor tasks became queued or running before registration" };
24417
+ }
24418
+ return JSON.parse(await evalJS(c, exprRegisterAgentsWorkspace(projectPath, statSync3(projectPath).isFile())) || "{}");
24419
+ });
24420
+ repository = registration;
24421
+ if (registration.state === "workspace_registration_requested") {
24422
+ for (let attempt = 0; attempt < 20; attempt++) {
24423
+ repository = JSON.parse(await evalJS(c, exprInspectWorkspaceRepository(projectPath)) || "{}");
24424
+ if (repository.state !== "workspace_registration_required") break;
24425
+ await sleep2(250);
24426
+ }
24427
+ repository = { ...repository, registration };
24428
+ }
24429
+ }
24430
+ this._lastWorkspaceBinding = { ...repository, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
24238
24431
  if (!repository.ok) return null;
24239
24432
  return {
24240
24433
  targetId: page.id,
24241
24434
  targetUiFlavor: "agents_v2",
24242
24435
  workspace: repository.workspace
24243
24436
  };
24244
- } catch {
24437
+ } catch (error2) {
24438
+ this._lastWorkspaceBinding = { ok: false, state: "workspace_probe_failed", wanted: projectPath, error: String(error2.message || error2) };
24245
24439
  return null;
24246
24440
  } finally {
24247
24441
  c.close();
@@ -24371,16 +24565,18 @@ var CursorBridge = class {
24371
24565
  recovery: normalized === "minimal" ? "Switch CCE to normal mode before opening Cursor manually." : null
24372
24566
  };
24373
24567
  }
24374
- async contextEngine(query) {
24568
+ async contextEngine(query, options = {}) {
24375
24569
  const text = String(query || "").trim();
24376
24570
  if (!text) throw new Error("query must not be empty");
24377
24571
  if (text.length > 2e4) throw new Error("query exceeds the 20,000-character limit");
24572
+ const requestContext = normalizeRequestContext(options.requestContext);
24378
24573
  this._assertWorkspaceConfirmed();
24379
24574
  if (this._hasGlobalReservation()) {
24380
24575
  throw new Error("A global Cursor reservation has an unconfirmed Stop state; resolve blockingTaskIds from cursor_status first");
24381
24576
  }
24382
24577
  await this._ensureCursor();
24383
- const job = this._enqueue("context_engine", buildContextEnginePrompt(text), {
24578
+ const job = this._enqueue("context_engine", buildContextEnginePrompt(text, requestContext), {
24579
+ requestContext,
24384
24580
  timeoutMs: QUERY_TIMEOUT,
24385
24581
  newChat: true,
24386
24582
  execution: "fifo",
@@ -24406,6 +24602,7 @@ var CursorBridge = class {
24406
24602
  const text = String(prompt || "").trim();
24407
24603
  if (!text) throw new Error("prompt must not be empty");
24408
24604
  if (text.length > 1e5) throw new Error("prompt exceeds the 100,000-character limit");
24605
+ const requestContext = normalizeRequestContext(options.requestContext);
24409
24606
  this._assertWorkspaceConfirmed();
24410
24607
  if (this._hasGlobalReservation()) {
24411
24608
  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");
@@ -24433,7 +24630,9 @@ var CursorBridge = class {
24433
24630
  throw cursorSessionError("SESSION_EXECUTION_INVALID", "persistent sessions require execution=parallel_agent and never fall back to FIFO");
24434
24631
  }
24435
24632
  const readOnly = options.readOnly === true;
24436
- const timeoutMs = Math.max(3e4, Math.min(9e5, Number(options.timeoutMs || 6e5)));
24633
+ const requestedTimeoutMs = Number(options.timeoutMs ?? 6e5);
24634
+ if (!Number.isFinite(requestedTimeoutMs)) throw new Error("timeout_ms must be finite");
24635
+ const timeoutMs = Math.max(3e4, Math.min(9e5, requestedTimeoutMs));
24437
24636
  const allowedPaths = Array.isArray(options.allowedPaths) ? options.allowedPaths.map((x) => String(x).trim()).filter(Boolean) : [];
24438
24637
  if (readOnly && allowedPaths.length > 0) {
24439
24638
  throw new Error("read_only=true cannot be combined with allowed_paths because a read-only task cannot declare a write scope");
@@ -24454,7 +24653,7 @@ var CursorBridge = class {
24454
24653
  throw cursorSessionError("SESSION_WORKSPACE_REQUIRED", "initialize one workspace before creating or continuing a session");
24455
24654
  }
24456
24655
  const contract = String(options.completionContract || "").trim();
24457
- let fullPrompt = text + DO_LANGUAGE_CONTRACT;
24656
+ let fullPrompt = buildRequestContextHeader(requestContext) + "\n\nTask:\n" + text + DO_LANGUAGE_CONTRACT;
24458
24657
  if (readOnly) fullPrompt += "\n\nRead-only boundary: Do not modify, create, or delete files, and do not run commands that change workspace state.";
24459
24658
  if (allowedPaths.length > 0) {
24460
24659
  fullPrompt += "\n\nAllowed modification scope (do not cross this boundary):\n" + allowedPaths.map((x) => "- " + x).join("\n");
@@ -24493,8 +24692,10 @@ var CursorBridge = class {
24493
24692
  }
24494
24693
  }
24495
24694
  const job = this._enqueue("do", fullPrompt, {
24695
+ requestContext,
24496
24696
  taskId,
24497
24697
  timeoutMs,
24698
+ requestedTimeoutMs,
24498
24699
  newChat: sessionMode !== "continue",
24499
24700
  execution,
24500
24701
  readOnly,
@@ -24556,7 +24757,9 @@ var CursorBridge = class {
24556
24757
  id,
24557
24758
  kind,
24558
24759
  prompt,
24760
+ requestContext: options.requestContext || normalizeRequestContext(),
24559
24761
  timeoutMs: options.timeoutMs,
24762
+ requestedTimeoutMs: options.requestedTimeoutMs ?? options.timeoutMs,
24560
24763
  newChat: options.newChat,
24561
24764
  preferLegacyUi: options.preferLegacyUi === true,
24562
24765
  execution: options.execution || "fifo",
@@ -24635,6 +24838,7 @@ var CursorBridge = class {
24635
24838
  if (isTerminalTask(job)) return;
24636
24839
  const e = error2 instanceof Error ? error2 : new Error(String(error2));
24637
24840
  job.error = e.message;
24841
+ if (e.workspaceBindingFailure) job.workspaceBinding = e.workspaceBindingFailure;
24638
24842
  if (e.providerError) job.providerError = e.providerError;
24639
24843
  if (e.uiDiagnostic) job.uiDiagnostic = e.uiDiagnostic;
24640
24844
  if (e.terminalEvidence) job.terminalEvidence = e.terminalEvidence;
@@ -24721,7 +24925,7 @@ var CursorBridge = class {
24721
24925
  // 自愈:每次查询前委托 ensureCursorRunning。并发去重。失败静默降级(_run 报清晰错)。
24722
24926
  // 统一走 ensureCursorRunning 复用其【单一身份校验来源】(cdpUp + cdpIsCursor)——避免热路径裸 /json/version 检查
24723
24927
  // 绕过身份校验、在别的 IDE 占 9223 时驱动错应用(2026-06-08 review #6)。
24724
- _ensureCursor() {
24928
+ _ensureCursor({ registerWorkspace = false } = {}) {
24725
24929
  this._refreshPersistedRuntimeMode();
24726
24930
  if (this._healing) return this._healing;
24727
24931
  this._healing = (async () => {
@@ -24734,23 +24938,17 @@ var CursorBridge = class {
24734
24938
  ...this.projectPath ? { projectPath: this.projectPath } : {}
24735
24939
  });
24736
24940
  this._lastLifecycle = lifecycleFromEnsureResult(rr, this.runtimeMode);
24737
- if (!rr.ok && rr.status === "workspace-not-ready" && rr.projectPath) {
24738
- const agentsWorkspace = await this._findAgentsWorkspace(rr.projectPath);
24941
+ if ((rr.ok || rr.status === "workspace-not-ready") && rr.projectPath) {
24942
+ const agentsWorkspace = await this._findAgentsWorkspace(rr.projectPath, { registerWorkspace });
24739
24943
  if (agentsWorkspace) {
24740
24944
  this._lastLifecycle = promoteAgentsWorkspaceLifecycle(this._lastLifecycle, agentsWorkspace);
24741
- }
24742
- }
24743
- if (rr.ok && rr.lifecycleMode === "attached" && rr.workspaceAction === "reused-agents-window" && rr.projectPath) {
24744
- const agentsWorkspace = await this._findAgentsWorkspace(rr.projectPath);
24745
- if (agentsWorkspace) {
24746
- this._lastLifecycle = promoteAgentsWorkspaceLifecycle(this._lastLifecycle, agentsWorkspace);
24747
- } else {
24945
+ } else if (rr.ok && (rr.workspaceAction === "reused-agents-window" || this._lastWorkspaceBinding)) {
24748
24946
  this._lastLifecycle = {
24749
24947
  ...this._lastLifecycle,
24750
24948
  status: "workspace-not-ready",
24751
- message: `Cursor is reachable, but Cursor Bridge could not verify workspace ${rr.projectPath} in the attached Agents Window.`,
24949
+ message: `Cursor is reachable, but Cursor Bridge could not verify workspace ${rr.projectPath} in the Agents Window (${this._lastWorkspaceBinding?.state || "workspace_probe_unavailable"}).`,
24752
24950
  needsAction: "open_workspace_in_cursor",
24753
- nextStep: `Open workspace ${rr.projectPath} in Cursor, then retry the same operation.`,
24951
+ nextStep: this._lastWorkspaceBinding?.nextStep || `Open workspace ${rr.projectPath} in Cursor, then retry the same operation.`,
24754
24952
  retryable: true
24755
24953
  };
24756
24954
  }
@@ -24885,10 +25083,11 @@ var CursorBridge = class {
24885
25083
  this.activeParallel.delete(job.id);
24886
25084
  this._failJob(job, error2);
24887
25085
  } else if (error2 && error2.sent) {
25086
+ job.firstWaitError ||= error2.message;
24888
25087
  job.error = `Submission state is uncertain; monitoring continues by agentId: ${error2.message}`;
24889
25088
  if (job.agentId) {
24890
25089
  job.phase = "running";
24891
- job.reservationScope = job.readOnly ? "agent" : "paths";
25090
+ job.reservationScope = job.effectiveExecution === "fifo" ? "global" : job.readOnly ? "agent" : "paths";
24892
25091
  this.activeParallel.set(job.id, job);
24893
25092
  this._startParallelMonitor(job);
24894
25093
  } else {
@@ -24927,10 +25126,11 @@ var CursorBridge = class {
24927
25126
  try {
24928
25127
  await this._newChat(c, {
24929
25128
  uiFlavor: options.targetUiFlavor,
24930
- projectPath: options.projectPath || this._lastLifecycle && this._lastLifecycle.projectPath || this.projectPath
25129
+ projectPath: options.projectPath || this._lastLifecycle && this._lastLifecycle.projectPath || this.projectPath,
25130
+ job: options
24931
25131
  });
24932
25132
  } catch (error2) {
24933
- if (attempt === 0 && options.targetUiFlavor === "agents_v2" && isAgentsWorkspaceBindError(error2)) {
25133
+ if (attempt === 0 && options.targetUiFlavor === "agents_v2" && isAgentsWorkspaceBindError(error2) && !error2.workspaceBindingFailure) {
24934
25134
  const fallback = await findPage({ purpose: "fifo", preferLegacy: true });
24935
25135
  if (fallback && fallback.id !== page.id) {
24936
25136
  options.fallbackReason = "agents_window_unbound_use_workbench";
@@ -24956,11 +25156,13 @@ var CursorBridge = class {
24956
25156
  baseline = JSON.parse(await evalJS(c, EXPR_SNAP));
24957
25157
  } catch {
24958
25158
  }
25159
+ options.responseBaseline = baseline;
24959
25160
  const providerErrorBaseline = providerErrorSignature(await this._readProviderError(c));
25161
+ await this._verifyAgentsWorkspace(c, options, "before_send");
24960
25162
  options.sendState = "dispatching";
24961
25163
  try {
24962
25164
  await chord(c, 0, "Enter", "Enter", 13);
24963
- await this._confirmSubmission(c, baseline.messageCount || 0, providerErrorBaseline);
25165
+ await this._confirmSubmission(c, baseline.messageCount || 0, providerErrorBaseline, options);
24964
25166
  options.sendState = "sent";
24965
25167
  options.sentAt = options.sentAt || (/* @__PURE__ */ new Date()).toISOString();
24966
25168
  await this._bindFifoAgentAfterSend(c, options, historyBefore, providerErrorBaseline);
@@ -25350,11 +25552,19 @@ var CursorBridge = class {
25350
25552
  async _newChat(c, options = {}) {
25351
25553
  if (options.uiFlavor === "agents_v2" && options.projectPath) {
25352
25554
  const created = JSON.parse(await evalJS(c, exprCreateAgentForWorkspace(options.projectPath)) || "{}");
25555
+ if (options.job) {
25556
+ options.job.workspaceBindingChecks = { before_create: { ...created, checkedAt: (/* @__PURE__ */ new Date()).toISOString() } };
25557
+ }
25353
25558
  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}`);
25559
+ throw createWorkspaceBindingError(created);
25356
25560
  }
25357
25561
  await sleep2(1100);
25562
+ const actual = JSON.parse(await evalJS(c, exprInspectAgentWorkspace(options.projectPath, { excludedAgentIds: created.previousAgentIds })) || "{}");
25563
+ if (options.job) options.job.workspaceBindingChecks.after_create = { ...actual, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
25564
+ if (!actual.ok) throw createWorkspaceBindingError(actual);
25565
+ if (actual.workspaceId !== created.workspaceId) {
25566
+ throw createWorkspaceBindingError({ ...actual, ok: false, state: "created_workspace_id_mismatch" });
25567
+ }
25358
25568
  return true;
25359
25569
  }
25360
25570
  return this._clickNewAgent(c, true);
@@ -25491,6 +25701,7 @@ var CursorBridge = class {
25491
25701
  }
25492
25702
  }
25493
25703
  async _fillPrompt(c, text, job, timeoutMs = 2500) {
25704
+ await this._verifyAgentsWorkspace(c, job);
25494
25705
  const prepared = await evalJS(c, EXPR_PREPARE_INPUT);
25495
25706
  if (prepared !== "READY") return prepared;
25496
25707
  try {
@@ -25522,7 +25733,19 @@ var CursorBridge = class {
25522
25733
  };
25523
25734
  throw error2;
25524
25735
  }
25525
- async _confirmSubmission(c, baselineCount = 0, providerErrorBaseline = "") {
25736
+ async _verifyAgentsWorkspace(c, job, stage = "before_fill") {
25737
+ if (!job || job.targetUiFlavor !== "agents_v2") return;
25738
+ const projectPath = job.projectPath || this.projectPath;
25739
+ const diagnostic = JSON.parse(await evalJS(c, exprInspectAgentWorkspace(projectPath, {
25740
+ agentId: job.agentId || job.provisionalAgentId || job.workspaceBinding?.agentId,
25741
+ requireInputFocus: stage === "before_send"
25742
+ })) || "{}");
25743
+ job.workspaceBinding = { ...diagnostic, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
25744
+ job.workspaceBindingChecks = { ...job.workspaceBindingChecks, [stage]: job.workspaceBinding };
25745
+ if (!diagnostic.ok) throw createWorkspaceBindingError(diagnostic);
25746
+ }
25747
+ async _confirmSubmission(c, baselineCount = 0, providerErrorBaseline = "", job = null) {
25748
+ let lastSnapshot = {};
25526
25749
  const accepted = async () => {
25527
25750
  await this._throwIfNewProviderError(c, providerErrorBaseline);
25528
25751
  let snap = {};
@@ -25530,6 +25753,7 @@ var CursorBridge = class {
25530
25753
  snap = JSON.parse(await evalJS(c, EXPR_SNAP));
25531
25754
  } catch {
25532
25755
  }
25756
+ lastSnapshot = snap;
25533
25757
  const inputTextLength = Number(snap.inputTextLength);
25534
25758
  return Number(snap.stop || 0) > 0 || Number(snap.messageCount || 0) > Number(baselineCount || 0) || Number.isFinite(inputTextLength) && inputTextLength === 0;
25535
25759
  };
@@ -25537,15 +25761,30 @@ var CursorBridge = class {
25537
25761
  await sleep2(250);
25538
25762
  if (await accepted()) return "enter";
25539
25763
  }
25764
+ try {
25765
+ await this._verifyAgentsWorkspace(c, job, "before_send_fallback");
25766
+ } catch (error3) {
25767
+ error3.confirmedNotSent = false;
25768
+ error3.sent = true;
25769
+ throw error3;
25770
+ }
25540
25771
  const clicked = await evalJS(c, EXPR_CLICK_SEND);
25541
- if (clicked === "CLICKED") {
25772
+ if (clicked === "CLICKED" || clicked === "NO_SEND") {
25542
25773
  for (let i = 0; i < 20; i++) {
25543
25774
  await sleep2(250);
25544
- if (await accepted()) return "button";
25775
+ if (await accepted()) return clicked === "CLICKED" ? "button" : "enter";
25545
25776
  }
25546
25777
  }
25547
- const error2 = new Error(`Cursor did not accept the submission (submit_not_accepted: ${clicked || "unknown"}); the prompt remains in the input and no orphan task was created`);
25548
- error2.confirmedNotSent = true;
25778
+ const error2 = new Error(`Cursor submission could not be confirmed (submission_uncertain: ${clicked || "unknown"}); Enter was dispatched, so inspect the bound Agent before retrying`);
25779
+ error2.confirmedNotSent = false;
25780
+ error2.sent = true;
25781
+ error2.composerDiagnostic = {
25782
+ inputTextLength: Number.isFinite(Number(lastSnapshot.inputTextLength)) ? Number(lastSnapshot.inputTextLength) : null,
25783
+ sendReady: lastSnapshot.sendReady === true,
25784
+ stop: Number(lastSnapshot.stop || 0),
25785
+ messageCount: Number(lastSnapshot.messageCount || 0),
25786
+ fallbackResult: clicked || "unknown"
25787
+ };
25549
25788
  throw error2;
25550
25789
  }
25551
25790
  async _readProviderError(c) {
@@ -25587,9 +25826,9 @@ var CursorBridge = class {
25587
25826
  this._throwIfCancelledBeforeSend(job);
25588
25827
  let createdForWorkspace = false;
25589
25828
  try {
25590
- createdForWorkspace = job.targetUiFlavor === "agents_v2" && job.projectPath ? await this._newChat(c, { uiFlavor: job.targetUiFlavor, projectPath: job.projectPath }) : await this._clickNewAgent(c, false);
25829
+ createdForWorkspace = job.targetUiFlavor === "agents_v2" && job.projectPath ? await this._newChat(c, { uiFlavor: job.targetUiFlavor, projectPath: job.projectPath, job }) : await this._clickNewAgent(c, false);
25591
25830
  } catch (error2) {
25592
- if (isAgentsWorkspaceBindError(error2)) {
25831
+ if (isAgentsWorkspaceBindError(error2) && !error2.workspaceBindingFailure) {
25593
25832
  return { fallbackReason: "Agents Window could not bind the current repository; downgraded to FIFO/workbench before submission" };
25594
25833
  }
25595
25834
  throw error2;
@@ -25622,9 +25861,10 @@ var CursorBridge = class {
25622
25861
  baseline = JSON.parse(await evalJS(c, EXPR_SNAP));
25623
25862
  } catch {
25624
25863
  }
25864
+ await this._verifyAgentsWorkspace(c, job, "before_send");
25625
25865
  job.sendState = "dispatching";
25626
25866
  await chord(c, 0, "Enter", "Enter", 13);
25627
- await this._confirmSubmission(c, baseline.messageCount || 0, providerErrorBaseline);
25867
+ await this._confirmSubmission(c, baseline.messageCount || 0, providerErrorBaseline, job);
25628
25868
  sent = true;
25629
25869
  job.sendState = "sent";
25630
25870
  job.sentAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -25739,9 +25979,10 @@ var CursorBridge = class {
25739
25979
  }
25740
25980
  await sleep2(350);
25741
25981
  this._throwIfCancelledBeforeSend(job);
25982
+ await this._verifyAgentsWorkspace(c, job, "before_send");
25742
25983
  job.sendState = "dispatching";
25743
25984
  await chord(c, 0, "Enter", "Enter", 13);
25744
- await this._confirmSubmission(c, job.responseBaseline.messageCount, providerErrorBaseline);
25985
+ await this._confirmSubmission(c, job.responseBaseline.messageCount, providerErrorBaseline, job);
25745
25986
  sent = true;
25746
25987
  job.sendState = "sent";
25747
25988
  job.sentAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -25915,7 +26156,12 @@ var CursorBridge = class {
25915
26156
  }
25916
26157
  if (!this._monitorOwns(job, generation)) return;
25917
26158
  const detail = lastCollectionError ? `; last collection error: ${lastCollectionError}` : "";
25918
- throw new Error(`Cursor parallel_agent task timed out (${job.timeoutMs}ms)${detail}`);
26159
+ const recovered = await this._withJobLock(job, async () => {
26160
+ if (!this._monitorOwns(job, generation)) return true;
26161
+ const result = await this._reapParallelJobLocked(job, { reattach: false, preserveMonitor: true });
26162
+ return isTerminalTask(job) || result.state === "terminal_uncollected";
26163
+ });
26164
+ if (!recovered) throw new Error(`Cursor ${job.effectiveExecution || job.execution || "Agent"} task timed out (${job.timeoutMs}ms)${detail}`);
25919
26165
  }
25920
26166
  async _requestExactAgentSelection(c, agentId) {
25921
26167
  if (!await this._ensureHistoryOpen(c)) return "REACT_ADAPTER_UNAVAILABLE";
@@ -26011,7 +26257,7 @@ var CursorBridge = class {
26011
26257
  job.resultUnavailable = true;
26012
26258
  job.terminalEvidence = evidence || "stable_completed_history_icon";
26013
26259
  job.recoveryState = "terminal_result_uncollected";
26014
- job.reservationScope = uncertainSubmissionReservationScope(job, error2);
26260
+ job.reservationScope = job.effectiveExecution === "fifo" ? "global" : uncertainSubmissionReservationScope(job, error2);
26015
26261
  this._safeSettleSessionJob(job, "terminal_result_uncollected", { needsAttention: true });
26016
26262
  }
26017
26263
  _abandonJob(job, reason) {
@@ -26059,7 +26305,7 @@ var CursorBridge = class {
26059
26305
  job.recoveryState = "agent_missing";
26060
26306
  return { stable: false, error: "The bound agentId was not found in Agent History", entry: second || first || null };
26061
26307
  }
26062
- const stable = first.id === second.id && first.showSpinner === second.showSpinner && String(first.icon || "") === String(second.icon || "");
26308
+ const stable = first.id === second.id && second.id === job.agentId && first.showSpinner === second.showSpinner && String(first.icon || "") === String(second.icon || "");
26063
26309
  return { stable, entry: second, error: stable ? null : "Agent History state is not yet stable" };
26064
26310
  }
26065
26311
  async _reapParallelJob(job, options = {}) {
@@ -26068,15 +26314,15 @@ var CursorBridge = class {
26068
26314
  }
26069
26315
  async _reapParallelJobLocked(job, options = {}) {
26070
26316
  if (!job || isTerminalTask(job)) return { changed: false, state: "terminal", task: this._taskView(job, true) };
26071
- if (job.execution !== "parallel_agent" || !this.activeParallel.has(job.id)) {
26317
+ if (!this.activeParallel.has(job.id)) {
26072
26318
  return { changed: false, state: "not_parallel_reservation", task: this._taskView(job, true) };
26073
26319
  }
26074
26320
  if (!job.agentId) {
26075
26321
  job.lastRecoveryAt = (/* @__PURE__ */ new Date()).toISOString();
26076
26322
  job.recoveryState = "unbound_agent";
26077
- return { changed: false, state: "unbound_agent", task: this._taskView(job, true) };
26323
+ return { changed: false, state: "unbound_agent", next: "The FIFO orphan has no agentId that can be safely rebound. Confirm that it stopped in the Cursor UI before explicitly abandoning it.", task: this._taskView(job, true) };
26078
26324
  }
26079
- this._invalidateParallelMonitor(job);
26325
+ if (!options.preserveMonitor) this._invalidateParallelMonitor(job);
26080
26326
  const observed = await this._readStableParallelEntry(job);
26081
26327
  if (!observed.stable || !observed.entry) {
26082
26328
  return { changed: false, state: job.recoveryState || "unstable", error: observed.error, task: this._taskView(job, true) };
@@ -26324,7 +26570,7 @@ var CursorBridge = class {
26324
26570
  if (action === "reap") {
26325
26571
  const result = await this._reapParallelJobLocked(job, { reattach: true });
26326
26572
  if (result.state === "not_parallel_reservation" && job.phase === "orphaned") {
26327
- result.next = job.agentId ? "FIFO is bound to an agentId, but reap applies only to parallel_agent. Use cancel for a targeted stop, or abandon after confirmation." : "The FIFO orphan has no agentId that can be safely rebound. Confirm that it stopped in the Cursor UI before explicitly abandoning it.";
26573
+ result.next = job.agentId ? "The bound task has no recoverable reservation. Inspect its current state before cancelling." : "The FIFO orphan has no agentId that can be safely rebound. Confirm that it stopped in the Cursor UI before explicitly abandoning it.";
26328
26574
  }
26329
26575
  return { found: true, action, ...result };
26330
26576
  }
@@ -26382,10 +26628,13 @@ var CursorBridge = class {
26382
26628
  }
26383
26629
  const canTargetStop = job.execution === "parallel_agent" || !!job.agentId;
26384
26630
  if (canTargetStop && (job.execution === "parallel_agent" || job.phase === "orphaned")) {
26385
- if (job.execution === "parallel_agent") {
26631
+ if (this.activeParallel.has(job.id)) {
26386
26632
  this._invalidateParallelMonitor(job);
26387
26633
  const reaped = await this._reapParallelJobLocked(job, { reattach: false });
26388
- if (isTerminalTask(job)) return { found: true, action, ...reaped };
26634
+ if (isTerminalTask(job) || reaped.state === "terminal_uncollected") {
26635
+ job.cancelRequested = false;
26636
+ return { found: true, action, ...reaped };
26637
+ }
26389
26638
  }
26390
26639
  job.phase = "cancelling";
26391
26640
  job.recoveryState = "stopping";
@@ -26520,6 +26769,7 @@ var CursorBridge = class {
26520
26769
  } catch {
26521
26770
  s = { stop: 0, messageCount: 0, replyLength: 0, replyHash: 0 };
26522
26771
  }
26772
+ if (job) job.lastWaitObservation = { at: (/* @__PURE__ */ new Date()).toISOString(), stop: s.stop, messageCount: s.messageCount, replyLength: s.replyLength, sawStop: sawStop || s.stop > 0 };
26523
26773
  if (s.stop > 0) sawStop = true;
26524
26774
  if (sawStop && s.stop === 0 && s.replyLength > 0) {
26525
26775
  await sleep2(800);
@@ -26560,6 +26810,10 @@ var CursorBridge = class {
26560
26810
  if (includeResult) this._markTaskResultCollected(job);
26561
26811
  const view = {
26562
26812
  taskId: job.id,
26813
+ requestedTimeoutMs: job.requestedTimeoutMs ?? job.timeoutMs,
26814
+ effectiveTimeoutMs: job.timeoutMs,
26815
+ firstWaitError: job.firstWaitError || null,
26816
+ lastWaitObservation: job.lastWaitObservation || null,
26563
26817
  kind: job.kind,
26564
26818
  status: job.status,
26565
26819
  phase: job.phase,
@@ -26569,6 +26823,9 @@ var CursorBridge = class {
26569
26823
  allowedPaths: job.allowedPaths,
26570
26824
  modelPreference: job.modelPreference,
26571
26825
  modelSelection: job.modelSelection,
26826
+ requestContext: job.requestContext || normalizeRequestContext(),
26827
+ workspaceBinding: job.workspaceBinding || null,
26828
+ workspaceBindingChecks: job.workspaceBindingChecks || null,
26572
26829
  projectPath: job.projectPath,
26573
26830
  sessionMode: job.sessionMode || "isolated",
26574
26831
  sessionId: job.sessionId || null,
@@ -26612,7 +26869,7 @@ var CursorBridge = class {
26612
26869
  if (job.recoveryState === "terminal_result_uncollected") {
26613
26870
  view.attention = "Agent History proves that the underlying task ended, but its final reply has not been collected. Retry with reap, or explicitly abandon only if a missing reply is acceptable.";
26614
26871
  } else {
26615
- view.attention = job.agentId ? job.execution === "parallel_agent" ? "Use cursor_task_control(action=reap) to recheck the original agentId. Use cancel when a stop is needed, and abandon only after manual confirmation while accepting residual write risk." : "FIFO is bound to an agentId. Use cursor_task_control(action=cancel) with the exact expected_agent_id for a targeted stop." : "This orphan has no agentId that can be safely rebound and globally blocks new delegation. Confirm that it stopped in the Cursor UI, then explicitly release it with cursor_task_control(action=abandon).";
26872
+ view.attention = job.agentId ? job.execution === "parallel_agent" ? "Use cursor_task_control(action=reap) to recheck the original agentId. Use cancel when a stop is needed, and abandon only after manual confirmation while accepting residual write risk." : "FIFO is bound to an agentId. Use reap to collect or resume monitoring; use cancel with the exact expected_agent_id to stop running work." : "This orphan has no agentId that can be safely rebound and globally blocks new delegation. Confirm that it stopped in the Cursor UI, then explicitly release it with cursor_task_control(action=abandon).";
26616
26873
  }
26617
26874
  }
26618
26875
  return view;
@@ -26696,16 +26953,26 @@ function buildSearchInputSchema() {
26696
26953
  return {
26697
26954
  type: "object",
26698
26955
  properties: {
26699
- query: { type: "string", description: "Describe the behavior, concept, symbol relationship, or ownership boundary to locate. State intent instead of guessing a directory." }
26956
+ query: { type: "string", description: "Describe the behavior, concept, symbol relationship, or ownership boundary to locate. State intent instead of guessing a directory." },
26957
+ request_context: REQUEST_CONTEXT_SCHEMA
26700
26958
  },
26701
26959
  required: ["query"]
26702
26960
  };
26703
26961
  }
26962
+ var REQUEST_CONTEXT_SCHEMA = {
26963
+ type: "object",
26964
+ additionalProperties: false,
26965
+ 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.",
26966
+ properties: {
26967
+ 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." },
26968
+ 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." }
26969
+ }
26970
+ };
26704
26971
  function buildToolDefinitions(bridgeInstance) {
26705
26972
  return [
26706
26973
  {
26707
26974
  name: "cursor_init",
26708
- description: "Initialize or reinitialize CCE for one local workspace. Give only the project path: Bridge saves it, finds Cursor, ensures the required connection, and opens or verifies the matching project. If Cursor was opened too early without CCE access, initialization safely keeps the binding and tells the user to save, close Cursor once, and repeat the same initialization sentence; it never force-closes Cursor. Cursor login and the user's old/new UI preference are preserved. When both UIs are open, Bridge selects the new Agents Window and creates work in the matching repository instead of Home.",
26975
+ description: "Initialize or reinitialize CCE for one local workspace. Give only the project path: Bridge saves it, finds Cursor, ensures the required connection, and opens or verifies the matching project. In Agents Window, initialization registers a missing local workspace through Cursor's project service and verifies its exact path before reporting ready. If Cursor was opened too early without CCE access, initialization safely keeps the binding and tells the user to save, close Cursor once, and repeat the same initialization sentence; it never force-closes Cursor. Cursor login and the user's old/new UI preference are preserved. When both UIs are open, Bridge selects the new Agents Window and creates work in the matching repository instead of Home.",
26709
26976
  inputSchema: {
26710
26977
  type: "object",
26711
26978
  properties: {
@@ -26726,10 +26993,11 @@ function buildToolDefinitions(bridgeInstance) {
26726
26993
  type: "object",
26727
26994
  properties: {
26728
26995
  prompt: { type: "string", description: "The task Cursor should receive. State the goal, boundaries, and what a complete result looks like." },
26996
+ request_context: REQUEST_CONTEXT_SCHEMA,
26729
26997
  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
26998
  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
26999
  read_only: { type: "boolean", default: false, description: "Set true when Cursor must not change the workspace." },
26732
- timeout_ms: { type: "integer", minimum: 3e4, maximum: 9e5, default: 6e5, description: "One monitoring budget after submission, shared by FIFO and automatic Agent recovery. Expiry needs attention; it does not cancel work. Explicit reap may start a new budget. The default is 10 minutes." },
27000
+ timeout_ms: { type: "integer", minimum: 3e4, maximum: 9e5, default: 6e5, description: "One monitoring budget after submission, shared by FIFO and automatic Agent recovery. Default 10 minutes, maximum 15 minutes; task status reports requested and effective budgets. Expiry probes the bound Agent once for completion; it does not cancel or resend work. Explicit reap may start a new budget for a still-running bound Agent, including FIFO." },
26733
27001
  allowed_paths: { type: "array", items: { type: "string" }, description: "Workspace-relative paths Cursor may write. Parallel write tasks require non-overlapping paths. This declaration is not a filesystem sandbox." },
26734
27002
  completion_contract: { type: "string", description: "Optional acceptance checks or a required final-report format." },
26735
27003
  session_mode: { type: "string", enum: [...CURSOR_SESSION_MODES], default: "isolated", description: "isolated preserves the current clean-task behavior. create starts an update-safe persistent session. continue sends one new turn to the exact session_id." },
@@ -26855,11 +27123,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request2) => {
26855
27123
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
26856
27124
  }
26857
27125
  if (name === "cursor_context_engine" || name === "cursor_search" || name === "cursor_search_deep") {
26858
- const result = await bridge.contextEngine(String(args && args.query || ""));
27126
+ const result = await bridge.contextEngine(String(args && args.query || ""), { requestContext: args && args.request_context });
26859
27127
  return { content: [{ type: "text", text: String(result) }] };
26860
27128
  }
26861
27129
  if (name === "cursor_do") {
26862
27130
  const result = await bridge.doTask(String(args && args.prompt || ""), {
27131
+ requestContext: args && args.request_context,
26863
27132
  background: !args || args.background !== false,
26864
27133
  execution: args && args.execution,
26865
27134
  readOnly: !!(args && args.read_only),
@@ -27002,8 +27271,10 @@ export {
27002
27271
  exprClickBoundComposerStop,
27003
27272
  exprClickSelectedAgentStop,
27004
27273
  exprCreateAgentForWorkspace,
27274
+ exprInspectAgentWorkspace,
27005
27275
  exprInspectWorkspaceRepository,
27006
27276
  exprOpenAgent,
27277
+ exprRegisterAgentsWorkspace,
27007
27278
  isConfirmedCompletedReply,
27008
27279
  isCursorEffortOptionText,
27009
27280
  isDurablyRegisteredParallelEntry,
@@ -27017,6 +27288,7 @@ export {
27017
27288
  normalizeCursorRuntimeMode,
27018
27289
  normalizeDelegationMode,
27019
27290
  normalizeModelPickerText,
27291
+ normalizeRequestContext,
27020
27292
  pathsOverlap,
27021
27293
  promoteAgentsWorkspaceLifecycle,
27022
27294
  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.15",
13
+ packageVersion: "0.1.17",
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.15",
3
+ "version": "0.1.17",
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.1"
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,