pi-cursor-bridge 0.1.16 → 0.2.0
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": "
|
|
3
|
+
"version": "6.0.0+codex.20260908194916",
|
|
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
|
-
|
|
13
|
+
Pi wrapper 0.2.0 embeds Cursor Bridge 6.0.0, including explicit request provenance for CCE and delegated tasks. Its breaking result contract returns a compact receipt for background `cursor_do` work: poll `cursor_status(task_id)` normally, then retrieve a terminal result with `cursor_status(task_id, detail="full")`. 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)
|
package/dist/cursor-bridge.mjs
CHANGED
|
@@ -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 = "
|
|
22608
|
+
var PLUGIN_VERSION = "6.0.0";
|
|
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);
|
|
@@ -23344,6 +23345,60 @@ function exprInspectWorkspaceRepository(projectPath) {
|
|
|
23344
23345
|
return JSON.stringify(inspectWorkspace(${JSON.stringify(String(projectPath || ""))}).diagnostic);
|
|
23345
23346
|
})()`;
|
|
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
|
+
}
|
|
23347
23402
|
function exprInspectAgentWorkspace(projectPath, options = {}) {
|
|
23348
23403
|
return `(function(){
|
|
23349
23404
|
${WORKSPACE_SECTION_BODY}
|
|
@@ -23974,7 +24029,7 @@ var CursorBridge = class {
|
|
|
23974
24029
|
throw cursorSessionError("SESSION_NOT_READY", `state=${session.state}; recovery=${session.recoveryState || "none"}`);
|
|
23975
24030
|
}
|
|
23976
24031
|
if (session.lastTask?.status === "completed" && !session.lastTask.resultCollectedAt && this.tasks.has(session.lastTask.taskId)) {
|
|
23977
|
-
throw cursorSessionError("SESSION_RESULT_UNCOLLECTED", `read cursor_status(task_id=${session.lastTask.taskId}) before continuing`);
|
|
24032
|
+
throw cursorSessionError("SESSION_RESULT_UNCOLLECTED", `read cursor_status(task_id=${session.lastTask.taskId}, detail="full") before continuing`);
|
|
23978
24033
|
}
|
|
23979
24034
|
if (session.lastTask?.status === "completed" && !session.lastTask.resultCollectedAt && !this.tasks.has(session.lastTask.taskId) && session.recoveryState !== "reconciled_result_uncollected") {
|
|
23980
24035
|
throw cursorSessionError("SESSION_RECONCILE_REQUIRED", "the prior reply was not read before this adapter restarted; reconcile the exact Agent before continuing");
|
|
@@ -24291,6 +24346,8 @@ var CursorBridge = class {
|
|
|
24291
24346
|
return result;
|
|
24292
24347
|
}
|
|
24293
24348
|
async initializeWorkspace(projectPath) {
|
|
24349
|
+
if (this._healing) await this._healing.catch(() => {
|
|
24350
|
+
});
|
|
24294
24351
|
if (this.busy || this.activeParallel.size > 0 || this.queue.length > 0) {
|
|
24295
24352
|
throw new Error("cursor_init cannot change workspace while Cursor tasks are queued or running");
|
|
24296
24353
|
}
|
|
@@ -24303,7 +24360,7 @@ var CursorBridge = class {
|
|
|
24303
24360
|
this._lastLifecycle = null;
|
|
24304
24361
|
this._lastWorkspaceBinding = null;
|
|
24305
24362
|
try {
|
|
24306
|
-
await this._ensureCursor();
|
|
24363
|
+
await this._ensureCursor({ registerWorkspace: true });
|
|
24307
24364
|
} catch (error2) {
|
|
24308
24365
|
const lifecycle = this._lastLifecycle;
|
|
24309
24366
|
const recoverableStatuses = /* @__PURE__ */ new Set([
|
|
@@ -24339,7 +24396,7 @@ var CursorBridge = class {
|
|
|
24339
24396
|
lifecycle: this._lastLifecycle
|
|
24340
24397
|
};
|
|
24341
24398
|
}
|
|
24342
|
-
async _findAgentsWorkspace(projectPath) {
|
|
24399
|
+
async _findAgentsWorkspace(projectPath, { registerWorkspace = false } = {}) {
|
|
24343
24400
|
if (!projectPath) return null;
|
|
24344
24401
|
this._lastWorkspaceBinding = null;
|
|
24345
24402
|
let page;
|
|
@@ -24352,7 +24409,24 @@ var CursorBridge = class {
|
|
|
24352
24409
|
const c = makeClient(page.webSocketDebuggerUrl);
|
|
24353
24410
|
try {
|
|
24354
24411
|
await c.ready;
|
|
24355
|
-
|
|
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
|
+
}
|
|
24356
24430
|
this._lastWorkspaceBinding = { ...repository, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
24357
24431
|
if (!repository.ok) return null;
|
|
24358
24432
|
return {
|
|
@@ -24556,7 +24630,9 @@ var CursorBridge = class {
|
|
|
24556
24630
|
throw cursorSessionError("SESSION_EXECUTION_INVALID", "persistent sessions require execution=parallel_agent and never fall back to FIFO");
|
|
24557
24631
|
}
|
|
24558
24632
|
const readOnly = options.readOnly === true;
|
|
24559
|
-
const
|
|
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));
|
|
24560
24636
|
const allowedPaths = Array.isArray(options.allowedPaths) ? options.allowedPaths.map((x) => String(x).trim()).filter(Boolean) : [];
|
|
24561
24637
|
if (readOnly && allowedPaths.length > 0) {
|
|
24562
24638
|
throw new Error("read_only=true cannot be combined with allowed_paths because a read-only task cannot declare a write scope");
|
|
@@ -24612,13 +24688,14 @@ var CursorBridge = class {
|
|
|
24612
24688
|
modelPreference = session.modelPreference || null;
|
|
24613
24689
|
if (duplicate) {
|
|
24614
24690
|
const existing = this.tasks.get(session.activeTaskId || session.lastTask && session.lastTask.taskId || "");
|
|
24615
|
-
return existing ? { duplicate: true, ...this.
|
|
24691
|
+
return existing ? { duplicate: true, ...options.background === false ? this._collectedTaskView(existing) : this._compactTaskView(existing) } : { duplicate: true, ...this._sessionView(session) };
|
|
24616
24692
|
}
|
|
24617
24693
|
}
|
|
24618
24694
|
const job = this._enqueue("do", fullPrompt, {
|
|
24619
24695
|
requestContext,
|
|
24620
24696
|
taskId,
|
|
24621
24697
|
timeoutMs,
|
|
24698
|
+
requestedTimeoutMs,
|
|
24622
24699
|
newChat: sessionMode !== "continue",
|
|
24623
24700
|
execution,
|
|
24624
24701
|
readOnly,
|
|
@@ -24635,9 +24712,9 @@ var CursorBridge = class {
|
|
|
24635
24712
|
agentId: sessionMode === "continue" ? session && session.agentId : null,
|
|
24636
24713
|
agentLabel: sessionMode === "continue" ? session && session.agentLabel : null
|
|
24637
24714
|
});
|
|
24638
|
-
if (options.background !== false) return this.
|
|
24715
|
+
if (options.background !== false) return this._compactTaskView(job);
|
|
24639
24716
|
await job.promise;
|
|
24640
|
-
return this.
|
|
24717
|
+
return this._collectedTaskView(job);
|
|
24641
24718
|
}
|
|
24642
24719
|
_assertNoParallelPathConflict(allowedPaths) {
|
|
24643
24720
|
if (this._hasGlobalReservation()) {
|
|
@@ -24682,6 +24759,7 @@ var CursorBridge = class {
|
|
|
24682
24759
|
prompt,
|
|
24683
24760
|
requestContext: options.requestContext || normalizeRequestContext(),
|
|
24684
24761
|
timeoutMs: options.timeoutMs,
|
|
24762
|
+
requestedTimeoutMs: options.requestedTimeoutMs ?? options.timeoutMs,
|
|
24685
24763
|
newChat: options.newChat,
|
|
24686
24764
|
preferLegacyUi: options.preferLegacyUi === true,
|
|
24687
24765
|
execution: options.execution || "fifo",
|
|
@@ -24847,7 +24925,7 @@ var CursorBridge = class {
|
|
|
24847
24925
|
// 自愈:每次查询前委托 ensureCursorRunning。并发去重。失败静默降级(_run 报清晰错)。
|
|
24848
24926
|
// 统一走 ensureCursorRunning 复用其【单一身份校验来源】(cdpUp + cdpIsCursor)——避免热路径裸 /json/version 检查
|
|
24849
24927
|
// 绕过身份校验、在别的 IDE 占 9223 时驱动错应用(2026-06-08 review #6)。
|
|
24850
|
-
_ensureCursor() {
|
|
24928
|
+
_ensureCursor({ registerWorkspace = false } = {}) {
|
|
24851
24929
|
this._refreshPersistedRuntimeMode();
|
|
24852
24930
|
if (this._healing) return this._healing;
|
|
24853
24931
|
this._healing = (async () => {
|
|
@@ -24860,17 +24938,11 @@ var CursorBridge = class {
|
|
|
24860
24938
|
...this.projectPath ? { projectPath: this.projectPath } : {}
|
|
24861
24939
|
});
|
|
24862
24940
|
this._lastLifecycle = lifecycleFromEnsureResult(rr, this.runtimeMode);
|
|
24863
|
-
if (
|
|
24864
|
-
const agentsWorkspace = await this._findAgentsWorkspace(rr.projectPath);
|
|
24865
|
-
if (agentsWorkspace) {
|
|
24866
|
-
this._lastLifecycle = promoteAgentsWorkspaceLifecycle(this._lastLifecycle, agentsWorkspace);
|
|
24867
|
-
}
|
|
24868
|
-
}
|
|
24869
|
-
if (rr.ok && rr.workspaceAction === "reused-agents-window" && rr.projectPath) {
|
|
24870
|
-
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 });
|
|
24871
24943
|
if (agentsWorkspace) {
|
|
24872
24944
|
this._lastLifecycle = promoteAgentsWorkspaceLifecycle(this._lastLifecycle, agentsWorkspace);
|
|
24873
|
-
} else {
|
|
24945
|
+
} else if (rr.ok && (rr.workspaceAction === "reused-agents-window" || this._lastWorkspaceBinding)) {
|
|
24874
24946
|
this._lastLifecycle = {
|
|
24875
24947
|
...this._lastLifecycle,
|
|
24876
24948
|
status: "workspace-not-ready",
|
|
@@ -25011,10 +25083,11 @@ var CursorBridge = class {
|
|
|
25011
25083
|
this.activeParallel.delete(job.id);
|
|
25012
25084
|
this._failJob(job, error2);
|
|
25013
25085
|
} else if (error2 && error2.sent) {
|
|
25086
|
+
job.firstWaitError ||= error2.message;
|
|
25014
25087
|
job.error = `Submission state is uncertain; monitoring continues by agentId: ${error2.message}`;
|
|
25015
25088
|
if (job.agentId) {
|
|
25016
25089
|
job.phase = "running";
|
|
25017
|
-
job.reservationScope = job.readOnly ? "agent" : "paths";
|
|
25090
|
+
job.reservationScope = job.effectiveExecution === "fifo" ? "global" : job.readOnly ? "agent" : "paths";
|
|
25018
25091
|
this.activeParallel.set(job.id, job);
|
|
25019
25092
|
this._startParallelMonitor(job);
|
|
25020
25093
|
} else {
|
|
@@ -25083,6 +25156,7 @@ var CursorBridge = class {
|
|
|
25083
25156
|
baseline = JSON.parse(await evalJS(c, EXPR_SNAP));
|
|
25084
25157
|
} catch {
|
|
25085
25158
|
}
|
|
25159
|
+
options.responseBaseline = baseline;
|
|
25086
25160
|
const providerErrorBaseline = providerErrorSignature(await this._readProviderError(c));
|
|
25087
25161
|
await this._verifyAgentsWorkspace(c, options, "before_send");
|
|
25088
25162
|
options.sendState = "dispatching";
|
|
@@ -25671,6 +25745,7 @@ var CursorBridge = class {
|
|
|
25671
25745
|
if (!diagnostic.ok) throw createWorkspaceBindingError(diagnostic);
|
|
25672
25746
|
}
|
|
25673
25747
|
async _confirmSubmission(c, baselineCount = 0, providerErrorBaseline = "", job = null) {
|
|
25748
|
+
let lastSnapshot = {};
|
|
25674
25749
|
const accepted = async () => {
|
|
25675
25750
|
await this._throwIfNewProviderError(c, providerErrorBaseline);
|
|
25676
25751
|
let snap = {};
|
|
@@ -25678,6 +25753,7 @@ var CursorBridge = class {
|
|
|
25678
25753
|
snap = JSON.parse(await evalJS(c, EXPR_SNAP));
|
|
25679
25754
|
} catch {
|
|
25680
25755
|
}
|
|
25756
|
+
lastSnapshot = snap;
|
|
25681
25757
|
const inputTextLength = Number(snap.inputTextLength);
|
|
25682
25758
|
return Number(snap.stop || 0) > 0 || Number(snap.messageCount || 0) > Number(baselineCount || 0) || Number.isFinite(inputTextLength) && inputTextLength === 0;
|
|
25683
25759
|
};
|
|
@@ -25693,14 +25769,22 @@ var CursorBridge = class {
|
|
|
25693
25769
|
throw error3;
|
|
25694
25770
|
}
|
|
25695
25771
|
const clicked = await evalJS(c, EXPR_CLICK_SEND);
|
|
25696
|
-
if (clicked === "CLICKED") {
|
|
25772
|
+
if (clicked === "CLICKED" || clicked === "NO_SEND") {
|
|
25697
25773
|
for (let i = 0; i < 20; i++) {
|
|
25698
25774
|
await sleep2(250);
|
|
25699
|
-
if (await accepted()) return "button";
|
|
25775
|
+
if (await accepted()) return clicked === "CLICKED" ? "button" : "enter";
|
|
25700
25776
|
}
|
|
25701
25777
|
}
|
|
25702
|
-
const error2 = new Error(`Cursor
|
|
25703
|
-
error2.confirmedNotSent =
|
|
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
|
+
};
|
|
25704
25788
|
throw error2;
|
|
25705
25789
|
}
|
|
25706
25790
|
async _readProviderError(c) {
|
|
@@ -26072,7 +26156,12 @@ var CursorBridge = class {
|
|
|
26072
26156
|
}
|
|
26073
26157
|
if (!this._monitorOwns(job, generation)) return;
|
|
26074
26158
|
const detail = lastCollectionError ? `; last collection error: ${lastCollectionError}` : "";
|
|
26075
|
-
|
|
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}`);
|
|
26076
26165
|
}
|
|
26077
26166
|
async _requestExactAgentSelection(c, agentId) {
|
|
26078
26167
|
if (!await this._ensureHistoryOpen(c)) return "REACT_ADAPTER_UNAVAILABLE";
|
|
@@ -26168,7 +26257,7 @@ var CursorBridge = class {
|
|
|
26168
26257
|
job.resultUnavailable = true;
|
|
26169
26258
|
job.terminalEvidence = evidence || "stable_completed_history_icon";
|
|
26170
26259
|
job.recoveryState = "terminal_result_uncollected";
|
|
26171
|
-
job.reservationScope = uncertainSubmissionReservationScope(job, error2);
|
|
26260
|
+
job.reservationScope = job.effectiveExecution === "fifo" ? "global" : uncertainSubmissionReservationScope(job, error2);
|
|
26172
26261
|
this._safeSettleSessionJob(job, "terminal_result_uncollected", { needsAttention: true });
|
|
26173
26262
|
}
|
|
26174
26263
|
_abandonJob(job, reason) {
|
|
@@ -26216,7 +26305,7 @@ var CursorBridge = class {
|
|
|
26216
26305
|
job.recoveryState = "agent_missing";
|
|
26217
26306
|
return { stable: false, error: "The bound agentId was not found in Agent History", entry: second || first || null };
|
|
26218
26307
|
}
|
|
26219
|
-
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 || "");
|
|
26220
26309
|
return { stable, entry: second, error: stable ? null : "Agent History state is not yet stable" };
|
|
26221
26310
|
}
|
|
26222
26311
|
async _reapParallelJob(job, options = {}) {
|
|
@@ -26225,15 +26314,15 @@ var CursorBridge = class {
|
|
|
26225
26314
|
}
|
|
26226
26315
|
async _reapParallelJobLocked(job, options = {}) {
|
|
26227
26316
|
if (!job || isTerminalTask(job)) return { changed: false, state: "terminal", task: this._taskView(job, true) };
|
|
26228
|
-
if (
|
|
26317
|
+
if (!this.activeParallel.has(job.id)) {
|
|
26229
26318
|
return { changed: false, state: "not_parallel_reservation", task: this._taskView(job, true) };
|
|
26230
26319
|
}
|
|
26231
26320
|
if (!job.agentId) {
|
|
26232
26321
|
job.lastRecoveryAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26233
26322
|
job.recoveryState = "unbound_agent";
|
|
26234
|
-
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) };
|
|
26235
26324
|
}
|
|
26236
|
-
this._invalidateParallelMonitor(job);
|
|
26325
|
+
if (!options.preserveMonitor) this._invalidateParallelMonitor(job);
|
|
26237
26326
|
const observed = await this._readStableParallelEntry(job);
|
|
26238
26327
|
if (!observed.stable || !observed.entry) {
|
|
26239
26328
|
return { changed: false, state: job.recoveryState || "unstable", error: observed.error, task: this._taskView(job, true) };
|
|
@@ -26471,17 +26560,19 @@ var CursorBridge = class {
|
|
|
26471
26560
|
job.cancelReason = reason || (job.execution === "parallel_agent" ? "User requested Cursor Agent cancellation" : "User requested FIFO task cancellation");
|
|
26472
26561
|
job.cancelRequestSeq = Number(job.cancelRequestSeq || 0) + 1;
|
|
26473
26562
|
}
|
|
26474
|
-
|
|
26563
|
+
const result = await this._withJobLock(job, () => this._taskControlLocked(job, action, {
|
|
26475
26564
|
...options,
|
|
26476
26565
|
reason,
|
|
26477
26566
|
expectedAgentId
|
|
26478
26567
|
}));
|
|
26568
|
+
if (result && result.task) result.task = this._compactTaskView(job);
|
|
26569
|
+
return result;
|
|
26479
26570
|
}
|
|
26480
26571
|
async _taskControlLocked(job, action, options) {
|
|
26481
26572
|
if (action === "reap") {
|
|
26482
26573
|
const result = await this._reapParallelJobLocked(job, { reattach: true });
|
|
26483
26574
|
if (result.state === "not_parallel_reservation" && job.phase === "orphaned") {
|
|
26484
|
-
result.next = job.agentId ? "
|
|
26575
|
+
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.";
|
|
26485
26576
|
}
|
|
26486
26577
|
return { found: true, action, ...result };
|
|
26487
26578
|
}
|
|
@@ -26539,10 +26630,13 @@ var CursorBridge = class {
|
|
|
26539
26630
|
}
|
|
26540
26631
|
const canTargetStop = job.execution === "parallel_agent" || !!job.agentId;
|
|
26541
26632
|
if (canTargetStop && (job.execution === "parallel_agent" || job.phase === "orphaned")) {
|
|
26542
|
-
if (job.
|
|
26633
|
+
if (this.activeParallel.has(job.id)) {
|
|
26543
26634
|
this._invalidateParallelMonitor(job);
|
|
26544
26635
|
const reaped = await this._reapParallelJobLocked(job, { reattach: false });
|
|
26545
|
-
if (isTerminalTask(job)
|
|
26636
|
+
if (isTerminalTask(job) || reaped.state === "terminal_uncollected") {
|
|
26637
|
+
job.cancelRequested = false;
|
|
26638
|
+
return { found: true, action, ...reaped };
|
|
26639
|
+
}
|
|
26546
26640
|
}
|
|
26547
26641
|
job.phase = "cancelling";
|
|
26548
26642
|
job.recoveryState = "stopping";
|
|
@@ -26677,6 +26771,7 @@ var CursorBridge = class {
|
|
|
26677
26771
|
} catch {
|
|
26678
26772
|
s = { stop: 0, messageCount: 0, replyLength: 0, replyHash: 0 };
|
|
26679
26773
|
}
|
|
26774
|
+
if (job) job.lastWaitObservation = { at: (/* @__PURE__ */ new Date()).toISOString(), stop: s.stop, messageCount: s.messageCount, replyLength: s.replyLength, sawStop: sawStop || s.stop > 0 };
|
|
26680
26775
|
if (s.stop > 0) sawStop = true;
|
|
26681
26776
|
if (sawStop && s.stop === 0 && s.replyLength > 0) {
|
|
26682
26777
|
await sleep2(800);
|
|
@@ -26714,9 +26809,12 @@ var CursorBridge = class {
|
|
|
26714
26809
|
throw new Error(`Cursor task timed out (${timeoutMs}ms) before generation was confirmed stopped with a complete assistant reply${taskHint}`);
|
|
26715
26810
|
}
|
|
26716
26811
|
_taskView(job, includeResult = false) {
|
|
26717
|
-
if (includeResult) this._markTaskResultCollected(job);
|
|
26718
26812
|
const view = {
|
|
26719
26813
|
taskId: job.id,
|
|
26814
|
+
requestedTimeoutMs: job.requestedTimeoutMs ?? job.timeoutMs,
|
|
26815
|
+
effectiveTimeoutMs: job.timeoutMs,
|
|
26816
|
+
firstWaitError: job.firstWaitError || null,
|
|
26817
|
+
lastWaitObservation: job.lastWaitObservation || null,
|
|
26720
26818
|
kind: job.kind,
|
|
26721
26819
|
status: job.status,
|
|
26722
26820
|
phase: job.phase,
|
|
@@ -26772,11 +26870,162 @@ var CursorBridge = class {
|
|
|
26772
26870
|
if (job.recoveryState === "terminal_result_uncollected") {
|
|
26773
26871
|
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.";
|
|
26774
26872
|
} else {
|
|
26775
|
-
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
|
|
26873
|
+
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).";
|
|
26776
26874
|
}
|
|
26777
26875
|
}
|
|
26778
26876
|
return view;
|
|
26779
26877
|
}
|
|
26878
|
+
_collectedTaskView(job) {
|
|
26879
|
+
this._markTaskResultCollected(job);
|
|
26880
|
+
return this._taskView(job, true);
|
|
26881
|
+
}
|
|
26882
|
+
_compactTaskView(job, { summary = false } = {}) {
|
|
26883
|
+
const full = this._taskView(job);
|
|
26884
|
+
const selection = full.modelSelection && {
|
|
26885
|
+
configured: full.modelSelection.configured,
|
|
26886
|
+
applied: full.modelSelection.applied,
|
|
26887
|
+
model: full.modelSelection.model,
|
|
26888
|
+
effort: full.modelSelection.effort,
|
|
26889
|
+
requestedModel: full.modelSelection.requestedModel ?? full.modelSelection.model ?? full.modelPreference?.model,
|
|
26890
|
+
requestedEffort: full.modelSelection.requestedEffort ?? full.modelSelection.effort ?? full.modelPreference?.effort,
|
|
26891
|
+
effectiveModel: full.modelSelection.effectiveModel,
|
|
26892
|
+
effectiveEffort: full.modelSelection.effectiveEffort,
|
|
26893
|
+
failureClass: full.modelSelection.failureClass,
|
|
26894
|
+
retryable: full.modelSelection.retryable,
|
|
26895
|
+
errorCode: full.modelSelection.errorCode,
|
|
26896
|
+
lastError: full.modelSelection.lastError,
|
|
26897
|
+
failedAt: full.modelSelection.failedAt,
|
|
26898
|
+
verifiedAt: full.modelSelection.verifiedAt
|
|
26899
|
+
};
|
|
26900
|
+
const view = {
|
|
26901
|
+
taskId: full.taskId,
|
|
26902
|
+
kind: full.kind,
|
|
26903
|
+
status: full.status,
|
|
26904
|
+
phase: full.phase,
|
|
26905
|
+
execution: full.execution,
|
|
26906
|
+
effectiveExecution: full.effectiveExecution,
|
|
26907
|
+
projectPath: full.projectPath,
|
|
26908
|
+
sessionId: full.sessionId,
|
|
26909
|
+
agentId: full.agentId,
|
|
26910
|
+
sendState: full.sendState,
|
|
26911
|
+
modelSelection: selection,
|
|
26912
|
+
reservationHeld: full.reservationHeld,
|
|
26913
|
+
reservationScope: full.reservationScope,
|
|
26914
|
+
blocksFifo: full.blocksFifo,
|
|
26915
|
+
blocksAll: full.blocksAll,
|
|
26916
|
+
recoveryState: full.recoveryState,
|
|
26917
|
+
attention: full.attention,
|
|
26918
|
+
cancelRequested: full.cancelRequested,
|
|
26919
|
+
cancelReason: full.cancelReason,
|
|
26920
|
+
underlyingStopConfirmed: full.underlyingStopConfirmed,
|
|
26921
|
+
lastRecoveryAt: full.lastRecoveryAt,
|
|
26922
|
+
terminalEvidence: full.terminalEvidence,
|
|
26923
|
+
resultAvailable: job.result != null,
|
|
26924
|
+
resultLength: job.result == null ? 0 : String(job.result).length,
|
|
26925
|
+
resultUnread: job.result != null && !full.resultCollectedAt,
|
|
26926
|
+
resultUnavailable: full.resultUnavailable,
|
|
26927
|
+
resultCollectedAt: full.resultCollectedAt,
|
|
26928
|
+
error: full.error
|
|
26929
|
+
};
|
|
26930
|
+
if (selection && Object.values(selection).every((value) => value === void 0)) view.modelSelection = null;
|
|
26931
|
+
if (full.sessionError) view.sessionError = full.sessionError;
|
|
26932
|
+
if (full.firstWaitError) view.firstWaitError = full.firstWaitError;
|
|
26933
|
+
if (full.providerError) view.providerError = full.providerError;
|
|
26934
|
+
if (full.uiDiagnostic) view.uiDiagnostic = full.uiDiagnostic;
|
|
26935
|
+
if (full.workspaceBinding && full.workspaceBinding.ok === false) view.workspaceBinding = full.workspaceBinding;
|
|
26936
|
+
if ((full.error || full.status === "needs_attention") && full.workspaceBindingChecks) {
|
|
26937
|
+
view.workspaceBindingChecks = full.workspaceBindingChecks;
|
|
26938
|
+
}
|
|
26939
|
+
if (full.status === "needs_attention" && full.lastWaitObservation) view.lastWaitObservation = full.lastWaitObservation;
|
|
26940
|
+
if (summary) {
|
|
26941
|
+
return {
|
|
26942
|
+
taskId: view.taskId,
|
|
26943
|
+
status: view.status,
|
|
26944
|
+
phase: view.phase,
|
|
26945
|
+
agentId: view.agentId,
|
|
26946
|
+
sessionId: view.sessionId,
|
|
26947
|
+
resultAvailable: view.resultAvailable,
|
|
26948
|
+
resultLength: view.resultLength,
|
|
26949
|
+
resultUnread: view.resultUnread,
|
|
26950
|
+
resultCollectedAt: view.resultCollectedAt,
|
|
26951
|
+
...view.sendState ? { sendState: view.sendState } : {},
|
|
26952
|
+
...view.reservationHeld ? { reservationHeld: true, reservationScope: view.reservationScope } : {},
|
|
26953
|
+
...view.blocksFifo ? { blocksFifo: true } : {},
|
|
26954
|
+
...view.blocksAll ? { blocksAll: true } : {},
|
|
26955
|
+
...view.recoveryState ? { recoveryState: view.recoveryState } : {},
|
|
26956
|
+
...view.attention ? { attention: view.attention } : {},
|
|
26957
|
+
...view.cancelRequested ? { cancelRequested: true } : {},
|
|
26958
|
+
...view.cancelReason ? { cancelReason: view.cancelReason } : {},
|
|
26959
|
+
...view.underlyingStopConfirmed != null ? { underlyingStopConfirmed: view.underlyingStopConfirmed } : {},
|
|
26960
|
+
...view.lastRecoveryAt ? { lastRecoveryAt: view.lastRecoveryAt } : {},
|
|
26961
|
+
...view.terminalEvidence ? { terminalEvidence: view.terminalEvidence } : {},
|
|
26962
|
+
...view.resultUnavailable ? { resultUnavailable: true } : {},
|
|
26963
|
+
...view.error ? { error: view.error } : {},
|
|
26964
|
+
...view.providerError ? { providerError: view.providerError } : {},
|
|
26965
|
+
...view.uiDiagnostic ? { uiDiagnostic: view.uiDiagnostic } : {},
|
|
26966
|
+
...view.workspaceBinding ? { workspaceBinding: view.workspaceBinding } : {},
|
|
26967
|
+
...view.workspaceBindingChecks ? { workspaceBindingChecks: view.workspaceBindingChecks } : {}
|
|
26968
|
+
};
|
|
26969
|
+
}
|
|
26970
|
+
return {
|
|
26971
|
+
...view,
|
|
26972
|
+
requestedTimeoutMs: full.requestedTimeoutMs,
|
|
26973
|
+
effectiveTimeoutMs: full.effectiveTimeoutMs,
|
|
26974
|
+
readOnly: full.readOnly,
|
|
26975
|
+
allowedPaths: full.allowedPaths,
|
|
26976
|
+
modelPreference: full.modelPreference,
|
|
26977
|
+
requestContext: full.requestContext,
|
|
26978
|
+
sessionMode: full.sessionMode,
|
|
26979
|
+
sessionTurn: full.sessionTurn,
|
|
26980
|
+
sessionState: full.sessionState,
|
|
26981
|
+
requestId: full.requestId,
|
|
26982
|
+
provisionalAgentId: full.provisionalAgentId,
|
|
26983
|
+
agentLabel: full.agentLabel,
|
|
26984
|
+
createdAt: full.createdAt,
|
|
26985
|
+
startedAt: full.startedAt,
|
|
26986
|
+
sentAt: full.sentAt,
|
|
26987
|
+
finishedAt: full.finishedAt
|
|
26988
|
+
};
|
|
26989
|
+
}
|
|
26990
|
+
_compactStatusCommon() {
|
|
26991
|
+
const workspace = this.workspaceView();
|
|
26992
|
+
const runtime = this.runtimeModeView();
|
|
26993
|
+
const models = this.modelPreferencesView();
|
|
26994
|
+
return {
|
|
26995
|
+
pluginVersion: PLUGIN_VERSION,
|
|
26996
|
+
statusPath: "json-list",
|
|
26997
|
+
workspaceKey: workspace.workspaceKey,
|
|
26998
|
+
workspaceConfirmationRequired: workspace.workspaceConfirmationRequired,
|
|
26999
|
+
workspaceBindingWarning: workspace.workspaceBindingWarning,
|
|
27000
|
+
projectPath: workspace.projectPath,
|
|
27001
|
+
initialized: workspace.initialized,
|
|
27002
|
+
...workspace.workspaceBinding && workspace.workspaceBinding.ok === false ? { workspaceBinding: workspace.workspaceBinding } : {},
|
|
27003
|
+
...this.delegationView(),
|
|
27004
|
+
runtimeMode: runtime.runtimeMode,
|
|
27005
|
+
minimalModeWarning: runtime.minimalModeWarning,
|
|
27006
|
+
modelPreferences: models.modelPreferences,
|
|
27007
|
+
modelPreferencesUpdatedAt: models.modelPreferencesUpdatedAt,
|
|
27008
|
+
sessionStoragePersistent: !!this.sessionFile
|
|
27009
|
+
};
|
|
27010
|
+
}
|
|
27011
|
+
_compactLifecycleView() {
|
|
27012
|
+
const lifecycle = this._lastLifecycle;
|
|
27013
|
+
if (!lifecycle) {
|
|
27014
|
+
return { adapterPid: process.pid, status: null, lifecycleMode: null, persistent: null, degradedReason: null };
|
|
27015
|
+
}
|
|
27016
|
+
if (lifecycle.degradedReason || lifecycle.spawnErrorCode || lifecycle.error || lifecycle.supervisorError || lifecycle.needsAction || lifecycle.nextStep || lifecycle.retryable || lifecycle.status === "failed") {
|
|
27017
|
+
return lifecycle;
|
|
27018
|
+
}
|
|
27019
|
+
return {
|
|
27020
|
+
adapterPid: lifecycle.adapterPid,
|
|
27021
|
+
supervisorPid: lifecycle.supervisorPid,
|
|
27022
|
+
status: lifecycle.status,
|
|
27023
|
+
lifecycleMode: lifecycle.lifecycleMode,
|
|
27024
|
+
persistent: lifecycle.persistent,
|
|
27025
|
+
degradedReason: lifecycle.degradedReason,
|
|
27026
|
+
cursorPid: lifecycle.cursorPid
|
|
27027
|
+
};
|
|
27028
|
+
}
|
|
26780
27029
|
_assertWorkspaceConfirmed() {
|
|
26781
27030
|
if (this.workspaceConfirmationRequired) {
|
|
26782
27031
|
throw new Error("WORKSPACE_CONFIRMATION_REQUIRED: the saved default binding has no host workspace identity. Run cursor_init with the intended project before submitting work.");
|
|
@@ -26785,7 +27034,7 @@ var CursorBridge = class {
|
|
|
26785
27034
|
_ensureTaskCapacity() {
|
|
26786
27035
|
this._trimTasks(49);
|
|
26787
27036
|
if (this.tasks.size >= 50) {
|
|
26788
|
-
throw new Error(
|
|
27037
|
+
throw new Error('TASK_RETENTION_FULL: 50 tasks are active or have unread replies. Read each unreadResultTaskId with cursor_status(task_id, detail="full"), or wait for active tasks before submitting more work.');
|
|
26789
27038
|
}
|
|
26790
27039
|
}
|
|
26791
27040
|
_trimTasks(limit = 50) {
|
|
@@ -26795,16 +27044,20 @@ var CursorBridge = class {
|
|
|
26795
27044
|
if (this.tasks.size <= limit) break;
|
|
26796
27045
|
}
|
|
26797
27046
|
}
|
|
26798
|
-
async status(taskId = "") {
|
|
27047
|
+
async status(taskId = "", { detail = "compact" } = {}) {
|
|
27048
|
+
const normalizedDetail = normalizeStatusDetail(detail);
|
|
26799
27049
|
if (taskId) {
|
|
26800
27050
|
const job = this.tasks.get(String(taskId));
|
|
26801
|
-
if (
|
|
26802
|
-
|
|
27051
|
+
if (normalizedDetail === "full") {
|
|
27052
|
+
if (!job) return { found: false, taskId: String(taskId), ...this.workspaceView(), ...this.delegationView(), ...this.runtimeModeView(), ...this.modelPreferencesView(), ...this.sessionRegistryView() };
|
|
27053
|
+
return { found: true, ...this.workspaceView(), ...this.delegationView(), ...this.runtimeModeView(), ...this.modelPreferencesView(), ...this.sessionRegistryView(), ...this._collectedTaskView(job) };
|
|
27054
|
+
}
|
|
27055
|
+
return { found: !!job, ...this._compactStatusCommon(), ...job ? this._compactTaskView(job) : { taskId: String(taskId) } };
|
|
26803
27056
|
}
|
|
26804
27057
|
const parallelRunning = this.activeParallel.size;
|
|
26805
27058
|
const uiBusy = this.busy;
|
|
26806
27059
|
const globalBlocked = this._hasGlobalReservation();
|
|
26807
|
-
const common = {
|
|
27060
|
+
const common = normalizedDetail === "full" ? {
|
|
26808
27061
|
pluginVersion: PLUGIN_VERSION,
|
|
26809
27062
|
statusPath: "json-list",
|
|
26810
27063
|
...this.workspaceView(),
|
|
@@ -26842,6 +27095,21 @@ var CursorBridge = class {
|
|
|
26842
27095
|
runtimeMode: this.runtimeMode,
|
|
26843
27096
|
presentation: null
|
|
26844
27097
|
}
|
|
27098
|
+
} : {
|
|
27099
|
+
...this._compactStatusCommon(),
|
|
27100
|
+
busy: uiBusy || parallelRunning > 0 || this.queue.length > 0,
|
|
27101
|
+
uiBusy,
|
|
27102
|
+
parallelRunning,
|
|
27103
|
+
idle: !uiBusy && parallelRunning === 0 && this.queue.length === 0,
|
|
27104
|
+
queued: this.queue.length,
|
|
27105
|
+
blockingTaskIds: [...this.activeParallel.values()].filter((job) => !isTerminalTask(job)).map((job) => job.id),
|
|
27106
|
+
globallyBlocked: globalBlocked,
|
|
27107
|
+
blockedQueuedCount: this.activeParallel.size > 0 ? this.queue.filter((job) => globalBlocked || job.effectiveExecution !== "parallel_agent").length : 0,
|
|
27108
|
+
activeParallel: [...this.activeParallel.values()].map((job) => this._compactTaskView(job, { summary: true })),
|
|
27109
|
+
recentTasks: [...this.tasks.values()].filter((job) => !this.activeParallel.has(job.id)).slice(-10).map((job) => this._compactTaskView(job, { summary: true })),
|
|
27110
|
+
unreadResultTaskIds: [...this.tasks.values()].filter((job) => isTerminalTask(job) && job.result != null && !job.resultCollectedAt).map((job) => job.id),
|
|
27111
|
+
taskRetentionLimit: 50,
|
|
27112
|
+
lifecycle: this._compactLifecycleView()
|
|
26845
27113
|
};
|
|
26846
27114
|
try {
|
|
26847
27115
|
const ver = await httpJson("/json/version");
|
|
@@ -26862,6 +27130,13 @@ function buildSearchInputSchema() {
|
|
|
26862
27130
|
required: ["query"]
|
|
26863
27131
|
};
|
|
26864
27132
|
}
|
|
27133
|
+
function normalizeStatusDetail(detail) {
|
|
27134
|
+
if (detail === void 0) return "compact";
|
|
27135
|
+
if (typeof detail !== "string" || !["compact", "full"].includes(detail)) {
|
|
27136
|
+
throw new Error("detail must be compact or full");
|
|
27137
|
+
}
|
|
27138
|
+
return detail;
|
|
27139
|
+
}
|
|
26865
27140
|
var REQUEST_CONTEXT_SCHEMA = {
|
|
26866
27141
|
type: "object",
|
|
26867
27142
|
additionalProperties: false,
|
|
@@ -26875,7 +27150,7 @@ function buildToolDefinitions(bridgeInstance) {
|
|
|
26875
27150
|
return [
|
|
26876
27151
|
{
|
|
26877
27152
|
name: "cursor_init",
|
|
26878
|
-
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.",
|
|
27153
|
+
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.",
|
|
26879
27154
|
inputSchema: {
|
|
26880
27155
|
type: "object",
|
|
26881
27156
|
properties: {
|
|
@@ -26891,7 +27166,7 @@ function buildToolDefinitions(bridgeInstance) {
|
|
|
26891
27166
|
},
|
|
26892
27167
|
bridgeInstance.environmentDelegationMode !== "off" ? {
|
|
26893
27168
|
name: "cursor_do",
|
|
26894
|
-
description:
|
|
27169
|
+
description: 'Give Cursor a clearly bounded task and get back a task ID. fifo means first in, first out: Bridge runs one queued task at a time, starting it in a clean chat. parallel_agent creates a separate top-level Cursor Agent. Persistent continuity is explicit: session_mode=create starts one durable top-level Agent association, and session_mode=continue requires its exact session_id. Omission keeps the existing isolated behavior. Parallel write tasks must declare non-overlapping allowed_paths; mark read-only work with read_only=true. Collect the result with cursor_status(task_id, detail="full"); ordinary status calls are compact and do not acknowledge the reply. Cursor can do the work, but the main agent still owns review and final verification. A direct user opt-out always wins.',
|
|
26895
27170
|
inputSchema: {
|
|
26896
27171
|
type: "object",
|
|
26897
27172
|
properties: {
|
|
@@ -26900,7 +27175,7 @@ function buildToolDefinitions(bridgeInstance) {
|
|
|
26900
27175
|
background: { type: "boolean", default: true, description: "When true, return the task ID immediately. When false, wait for the task to finish or need attention." },
|
|
26901
27176
|
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." },
|
|
26902
27177
|
read_only: { type: "boolean", default: false, description: "Set true when Cursor must not change the workspace." },
|
|
26903
|
-
timeout_ms: { type: "integer", minimum: 3e4, maximum: 9e5, default: 6e5, description: "One monitoring budget after submission, shared by FIFO and automatic Agent recovery. Expiry
|
|
27178
|
+
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." },
|
|
26904
27179
|
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." },
|
|
26905
27180
|
completion_contract: { type: "string", description: "Optional acceptance checks or a required final-report format." },
|
|
26906
27181
|
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." },
|
|
@@ -26912,7 +27187,7 @@ function buildToolDefinitions(bridgeInstance) {
|
|
|
26912
27187
|
} : null,
|
|
26913
27188
|
{
|
|
26914
27189
|
name: "cursor_task_control",
|
|
26915
|
-
description:
|
|
27190
|
+
description: 'Recover or terminate one exact in-memory Cursor task without resubmitting it; task records do not survive this MCP server process. Use reap for needs_attention/orphaned work only when it has a bound agentId; it explicitly rechecks that Agent and stores a stable terminal result when possible. Responses stay compact and never acknowledge the result; collect it later with cursor_status(task_id, detail="full"). Use cancel with confirm=true and the exact expected_agent_id to target Stop safely. FIFO or unbound orphans globally block delegation and require manual verification before abandon. Use abandon only with an explicit reason and acknowledge_may_still_write=true; it releases reservations without proving the Cursor Agent stopped.',
|
|
26916
27191
|
inputSchema: {
|
|
26917
27192
|
type: "object",
|
|
26918
27193
|
properties: {
|
|
@@ -26968,12 +27243,14 @@ function buildToolDefinitions(bridgeInstance) {
|
|
|
26968
27243
|
},
|
|
26969
27244
|
{
|
|
26970
27245
|
name: "cursor_status",
|
|
26971
|
-
description:
|
|
27246
|
+
description: 'Read-only snapshot of Cursor connectivity, queued/running work, reservations, execution availability, persistent model/effort defaults, sessions, and normal/minimal runtime presentation. Compact is the default and never includes or acknowledges a task result. Use detail="full" with task_id to receive the complete legacy task details and result; this marks the result collected while keeping repeat full reads available. Pass task_id for its configured and effective model selection or session_id for the durable association; never pass both. This tool never switches Agents, reconciles, or stops work.',
|
|
26972
27247
|
inputSchema: {
|
|
26973
27248
|
type: "object",
|
|
27249
|
+
additionalProperties: false,
|
|
26974
27250
|
properties: {
|
|
26975
27251
|
task_id: { type: "string", description: "A task ID returned by cursor_do." },
|
|
26976
|
-
session_id: { type: "string", description: "A persistent session ID returned by cursor_do(session_mode=create)." }
|
|
27252
|
+
session_id: { type: "string", description: "A persistent session ID returned by cursor_do(session_mode=create)." },
|
|
27253
|
+
detail: { type: "string", enum: ["compact", "full"], default: "compact", description: "compact returns status and safety metadata without the result body or receipt side effect. full returns legacy complete details and explicitly collects a task result." }
|
|
26977
27254
|
}
|
|
26978
27255
|
}
|
|
26979
27256
|
}
|
|
@@ -27090,15 +27367,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request2) => {
|
|
|
27090
27367
|
if (args && args.task_id && args.session_id) {
|
|
27091
27368
|
throw cursorSessionError("STATUS_SELECTOR_AMBIGUOUS", "pass task_id or session_id, not both");
|
|
27092
27369
|
}
|
|
27370
|
+
const detail = normalizeStatusDetail(args && args.detail);
|
|
27093
27371
|
const statusMs = Math.max(1e3, Number(process.env.CURSOR_BRIDGE_STATUS_TIMEOUT || 8e3));
|
|
27094
27372
|
let result;
|
|
27095
27373
|
try {
|
|
27096
27374
|
result = await Promise.race([
|
|
27097
|
-
args && args.session_id ? Promise.resolve(bridge.sessionStatus(args.session_id)) : bridge.status(args && args.task_id),
|
|
27375
|
+
args && args.session_id ? Promise.resolve(bridge.sessionStatus(args.session_id)) : bridge.status(args && args.task_id, { detail }),
|
|
27098
27376
|
new Promise((_, reject) => setTimeout(() => reject(new Error(`cursor_status_timeout_${statusMs}`)), statusMs))
|
|
27099
27377
|
]);
|
|
27100
27378
|
} catch (error2) {
|
|
27101
|
-
result = {
|
|
27379
|
+
result = detail === "full" ? {
|
|
27102
27380
|
connected: false,
|
|
27103
27381
|
pluginVersion: PLUGIN_VERSION,
|
|
27104
27382
|
statusPath: "json-list",
|
|
@@ -27106,7 +27384,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request2) => {
|
|
|
27106
27384
|
...bridge.workspaceView(),
|
|
27107
27385
|
...bridge.delegationView(),
|
|
27108
27386
|
...bridge.runtimeModeView(),
|
|
27387
|
+
...bridge.modelPreferencesView(),
|
|
27109
27388
|
...bridge.sessionRegistryView()
|
|
27389
|
+
} : {
|
|
27390
|
+
connected: false,
|
|
27391
|
+
...bridge._compactStatusCommon(),
|
|
27392
|
+
...args && args.task_id ? { found: false, taskId: String(args.task_id) } : {},
|
|
27393
|
+
...args && args.session_id ? { found: false, sessionId: String(args.session_id) } : {},
|
|
27394
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
27110
27395
|
};
|
|
27111
27396
|
}
|
|
27112
27397
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
@@ -27177,6 +27462,7 @@ export {
|
|
|
27177
27462
|
exprInspectAgentWorkspace,
|
|
27178
27463
|
exprInspectWorkspaceRepository,
|
|
27179
27464
|
exprOpenAgent,
|
|
27465
|
+
exprRegisterAgentsWorkspace,
|
|
27180
27466
|
isConfirmedCompletedReply,
|
|
27181
27467
|
isCursorEffortOptionText,
|
|
27182
27468
|
isDurablyRegisteredParallelEntry,
|
package/extensions/index.ts
CHANGED
|
@@ -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.
|
|
13
|
+
packageVersion: "0.2.0",
|
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
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": "
|
|
50
|
+
"embeddedProductVersion": "6.0.0"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cursor-delegate
|
|
3
|
-
description: "Delegate bounded light-to-medium implementation, investigation, documentation, configuration, testing, and tooling work to Cursor Bridge after the primary agent owns direction and risk boundaries. Also use when the user explicitly asks to create, keep, continue, inspect, or close the same Cursor execution session, including phrases such as '持续会话', '同一个 Cursor 会话', or 'continue the Cursor session'. Generic '继续' is not enough to reuse a session.
|
|
3
|
+
description: "Delegate bounded light-to-medium implementation, investigation, documentation, configuration, testing, and tooling work to Cursor Bridge after the primary agent owns direction and risk boundaries. Also use when the user explicitly asks to create, keep, continue, inspect, or close the same Cursor execution session, including phrases such as '持续会话', '同一个 Cursor 会话', or 'continue the Cursor session'. Generic '继续' is not enough to reuse a session. Poll each turn compactly by task_id, then explicitly retrieve a terminal result by task_id and verify it in the primary agent. Do not use when the user opts out, cursor_do is unavailable or administrator-disabled, or for product direction, architecture decisions, exclusive GUI operations, formal verification verdicts, governance state decisions, or unbounded investigation."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Cursor Delegate
|
|
@@ -21,11 +21,11 @@ Declare `request_context` for each call: an AI caller uses `sender="model"`; set
|
|
|
21
21
|
|
|
22
22
|
Use this responsibility chain:
|
|
23
23
|
|
|
24
|
-
`primary agent defines purpose, invariants, and risk boundaries -> form a bounded task envelope -> Cursor investigates locally and executes within the envelope ->
|
|
24
|
+
`primary agent defines purpose, invariants, and risk boundaries -> form a bounded task envelope -> Cursor investigates locally and executes within the envelope -> poll compactly and retrieve the terminal result by task_id -> primary agent inspects the real changes and verifies them`
|
|
25
25
|
|
|
26
26
|
- Decide what should be achieved, why it matters, what must not change, where Cursor may work, and what evidence makes the result acceptable. Do not delegate product direction, architecture boundaries, or state verdicts.
|
|
27
27
|
- Allow Cursor to locate relevant implementation, compare local approaches, and complete code, documentation, configuration, scripts, tests, and tooling inside those boundaries. Do not require the primary agent to pre-solve the task line by line.
|
|
28
|
-
- Once a task has been selected for delegation, normally call `cursor_do` once with `background=true`, then continue non-conflicting primary-agent work. Bridge starts FIFO work in a clean chat automatically.
|
|
28
|
+
- Once a task has been selected for delegation, normally call `cursor_do` once with `background=true`, receive its compact submission receipt, then continue non-conflicting primary-agent work. Bridge starts FIFO work in a clean chat automatically.
|
|
29
29
|
- Prefer `execution=fifo` unless the parallel contract is clearly satisfied.
|
|
30
30
|
- Do not inject a unique completion marker or impose a minimum response length. Rely on task state, stable `task_id` or `agent_id`, and the actual result.
|
|
31
31
|
|
|
@@ -75,7 +75,7 @@ Do not choose parallel execution merely because there are many tasks. When depen
|
|
|
75
75
|
|
|
76
76
|
1. Record the relevant pre-dispatch workspace state so later review can distinguish existing user changes.
|
|
77
77
|
2. Form one independent task envelope per task using [delegation-contract.md](references/delegation-contract.md). Write its narrative instructions in the language of the user's current substantive task unless the user explicitly requests another language. Do not persist an inferred language or replace a clear conversational signal with the host/OS locale.
|
|
78
|
-
3. Call `cursor_do` with `background=true
|
|
78
|
+
3. Call `cursor_do` with `background=true` and save its compact submission receipt. Use `background=false` only when an immediate synchronous full result is required. Use only the documented `session_mode` and `session_id` fields when continuity is explicit; never infer continuity from the visible chat.
|
|
79
79
|
4. Save each returned `task_id`; for persistent work also save `session_id`. Treat `agent_id` as verification evidence, not the continuation handle.
|
|
80
80
|
5. If a parallel submission does not return a usable `agent_id`, stop expanding the parallel batch and use `fifo` or report the ambiguous state.
|
|
81
81
|
|
|
@@ -83,12 +83,13 @@ The envelope may contain a small number of local implementation `open_questions`
|
|
|
83
83
|
|
|
84
84
|
## Collect and verify
|
|
85
85
|
|
|
86
|
-
1. Always query `cursor_status(task_id)` for the exact task. Do not treat the currently visible Cursor chat as task identity.
|
|
86
|
+
1. Always query `cursor_status(task_id)` for the exact task. Its default compact view is for normal polling; use `detail="full"` during progress only when detailed diagnostics are needed. Do not treat the currently visible Cursor chat as task identity.
|
|
87
87
|
2. Treat `submitting`, `running`, and `collecting` as normal in-progress states. More than two minutes is not itself a failure; wait for an explicit terminal state.
|
|
88
|
-
3.
|
|
89
|
-
4.
|
|
90
|
-
5.
|
|
91
|
-
6.
|
|
88
|
+
3. After a terminal state, call `cursor_status(task_id, detail="full")`. It returns the complete retained result and records explicit receipt; repeat full reads remain allowed while the task is retained.
|
|
89
|
+
4. Compare Cursor's claimed work with the real diff, `allowed_paths`, and acceptance contract.
|
|
90
|
+
5. When `cursor_status` reports a configured model default, confirm `modelSelection.applied=true` and preserve its configured/effective model and effort fields in any failure report.
|
|
91
|
+
6. Run risk-proportionate verification in the primary agent. Cursor's response alone cannot support a formal pass, verified state, or governance transition.
|
|
92
|
+
7. Record each task as complete, partial, failed, timed out, or ambiguous before summarizing the batch.
|
|
92
93
|
|
|
93
94
|
Report the accepted result in the language of the user's current task. Keep `task_id`, `agent_id`, tool names, states, enum values, paths, commands, hashes, exact permission options, and error/status codes verbatim. If Cursor returned an artifact or report in another language, preserve it and summarize the relevant facts in the current task language.
|
|
94
95
|
|
|
@@ -97,14 +98,14 @@ Read [delegation-contract.md](references/delegation-contract.md) for state inter
|
|
|
97
98
|
## Handle abnormal states
|
|
98
99
|
|
|
99
100
|
- For `needs_attention`, `orphaned`, ambiguous state, or an unbound session, assume the real Cursor Agent may still be running. Preserve path ownership and never resubmit automatically.
|
|
100
|
-
- For a parallel orphan with a bound `agent_id`, first call `cursor_task_control(action=reap)`. This explicitly rechecks and, when possible, resumes monitoring or
|
|
101
|
+
- For a parallel orphan with a bound `agent_id`, first call `cursor_task_control(action=reap)`. This explicitly rechecks and, when possible, resumes monitoring or recovers that task's terminal state. It returns only an action/state summary; after a terminal state, retrieve any result with `cursor_status(task_id, detail="full")`.
|
|
101
102
|
- For an unbound FIFO or any orphan without an `agent_id`, do not call `reap` as if an identity existed. It globally blocks delegation; manually verify Cursor has stopped, then use the explicitly acknowledged `abandon` path.
|
|
102
103
|
- To stop a bound task, use `cursor_task_control(action=cancel, confirm=true, expected_agent_id=<exact id>)`. This includes FIFO tasks that have published an Agent ID. If Stop cannot be confirmed, the reservation remains held.
|
|
103
104
|
- Use `action=abandon` only after manual verification and an explicit user decision to accept the risk. It requires `confirm=true`, a non-empty reason, `acknowledge_may_still_write=true`, and the exact `expected_agent_id` when one is already bound; report that the underlying Agent may still run or write.
|
|
104
|
-
- If Cursor shows a final UI response but Bridge has not collected it, use explicit `reap` against the original bound task. A `terminal_uncollected` result keeps the reservation for retry. Do not add a completion marker, increase a response-length requirement, or submit the same task again.
|
|
105
|
+
- If Cursor shows a final UI response but Bridge has not collected it, use explicit `reap` against the original bound task. A `terminal_uncollected` result keeps the reservation for retry; after recovery reaches a terminal state, retrieve the result through explicit full task status. Do not add a completion marker, increase a response-length requirement, or submit the same task again.
|
|
105
106
|
- Task identity and reservations are process-local. After an MCP/Codex restart, do not claim the old `task_id` is recoverable; inspect Cursor Agent History and workspace changes manually before overlapping work.
|
|
106
107
|
- A ready persistent `session_id` is stored outside the versioned plugin cache and may survive MCP/Codex restart or plugin update. Query `cursor_status(session_id)` before continuing. If it reports `needs_attention`, an expired sender lease, or a missing exact Agent binding, do not resubmit or silently create a replacement session.
|
|
107
|
-
- Use `cursor_session_control(action=reconcile)` to check the exact Agent twice after an interrupted adapter. It may return the session to `ready` only from stable terminal evidence; an interrupted completed reply remains explicitly uncollected.
|
|
108
|
+
- Use `cursor_session_control(action=reconcile)` to check the exact Agent twice after an interrupted adapter. It may return the session to `ready` only from stable terminal evidence; an interrupted completed reply remains explicitly uncollected and `cursor_session_control(action=collect_result)` returns that session reply in full.
|
|
108
109
|
- `cursor_session_control(action=abandon)` is the last resort for an uncertain session and requires `confirm=true`, a non-empty reason, and `acknowledge_may_still_write=true`. It closes only the Bridge mapping and does not prove that Cursor stopped.
|
|
109
110
|
- If a timed-out task changed files, inspect the changes before deciding whether to continue, retry, or revert.
|
|
110
111
|
- If changes exceed `allowed_paths`, stop accepting the result and report the scope violation.
|
|
@@ -21,7 +21,7 @@ Provide every task independently:
|
|
|
21
21
|
| `read_only` | Use `true` for lookup and analysis; use `false` for any file modification. |
|
|
22
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. |
|
|
23
23
|
| `completion_contract` | State the deliverables, validation commands, permitted incomplete items, final report format, and that narrative output should follow the task language. Preserve paths, commands, identifiers, and machine tokens verbatim. |
|
|
24
|
-
| `background` | Default to `true` so the primary agent may continue independent work. |
|
|
24
|
+
| `background` | Default to `true` so the primary agent may continue independent work; it returns a compact submission receipt. Set `false` only to wait synchronously for the full result body. |
|
|
25
25
|
|
|
26
26
|
## Routing contract
|
|
27
27
|
|
|
@@ -65,12 +65,20 @@ Bounded write task:
|
|
|
65
65
|
|
|
66
66
|
For dependent or path-overlapping work, change `execution` to `fifo` and submit the next task only after accepting its predecessor.
|
|
67
67
|
|
|
68
|
+
Compact collection flow:
|
|
69
|
+
|
|
70
|
+
1. After `cursor_do(background=true)`, save the compact receipt's `task_id`.
|
|
71
|
+
2. Poll `cursor_status(task_id)` with its default compact view while the task is active.
|
|
72
|
+
3. Once terminal, call `cursor_status(task_id, detail="full")` to retrieve the full retained result and record receipt. Repeat explicit full reads are allowed while the task record remains retained.
|
|
73
|
+
|
|
68
74
|
## Identity and collection contract
|
|
69
75
|
|
|
70
76
|
- `task_id` is the stable identity used by the primary agent to query and summarize a task. Save it immediately after dispatch.
|
|
71
77
|
- `agent_id` binds a task to one specific Agents Window session when Bridge publishes it. `parallel_agent` always needs this identity. FIFO may also publish one; if it does not, do not assume a safe Stop target.
|
|
72
|
-
- Determine task state only through `cursor_status(task_id)`, not the currently selected chat or latest visible response.
|
|
73
|
-
- A collected result should include at least task state, summary, changed files, validation performed, failures or blockers, and the raw Cursor response.
|
|
78
|
+
- Determine task state only through `cursor_status(task_id)`, not the currently selected chat or latest visible response. Its default compact view never returns a result body or records receipt.
|
|
79
|
+
- After a terminal state, use `cursor_status(task_id, detail="full")` to retrieve the complete retained result and record explicit receipt. A collected result should include at least task state, summary, changed files, validation performed, failures or blockers, and the raw Cursor response.
|
|
80
|
+
- `cursor_task_control` returns an action and compact task-state summary without a result body or implicit receipt. Retrieve a terminal result afterward through explicit full task status.
|
|
81
|
+
- `cursor_session_control(action=collect_result)` always returns the full session reply.
|
|
74
82
|
- Do not require a unique completion marker or minimum response length. Bridge determines completion from Agent state, stopped generation, and response stability.
|
|
75
83
|
|
|
76
84
|
### State table
|
|
@@ -78,15 +86,15 @@ For dependent or path-overlapping work, change `execution` to `fifo` and submit
|
|
|
78
86
|
| State or phase | Primary-agent action |
|
|
79
87
|
|---|---|
|
|
80
88
|
| `queued/submitting/running/collecting` | Keep the original task and continue polling by `task_id`. More than two minutes is not a failure. |
|
|
81
|
-
| `completed` |
|
|
89
|
+
| `completed` | Call `cursor_status(task_id, detail="full")` to read the raw response, then inspect the real diff, allowed paths, and completion contract. |
|
|
82
90
|
| `failed` | Read the explicit error and determine whether the Cursor Agent actually failed before deciding to rework. |
|
|
83
91
|
| `needs_attention/orphaned` with bound `agent_id` | Preserve path ownership and explicitly call `cursor_task_control(action=reap)` for the same in-memory task. Do not resubmit automatically. |
|
|
84
92
|
| FIFO or unbound orphan | A global reservation blocks all new delegation. If an `agent_id` was published, use targeted `cancel`. Otherwise manually verify Cursor has stopped, then use explicitly acknowledged `abandon`; there is no safe `reap` target. |
|
|
85
|
-
| `terminal_uncollected` | Agent History is stably terminal but the final response extraction failed. Keep the reservation and retry explicit `reap`;
|
|
93
|
+
| `terminal_uncollected` | Agent History is stably terminal but the final response extraction failed. Keep the reservation and retry explicit `reap`; after recovery, use full task status to retrieve the result. Do not release on one DOM failure. |
|
|
86
94
|
| `cancelled` | The exact Agent Stop action or an unsent queued cancellation was confirmed; the reservation is released. |
|
|
87
95
|
| `abandoned` | The reservation was explicitly released without proof that the underlying Agent stopped. Treat the warning as live risk and inspect workspace changes before any overlapping write. |
|
|
88
96
|
|
|
89
|
-
For an R6-style false negative, continue
|
|
97
|
+
For an R6-style false negative, continue compact polling of the original `task_id` when Agent History already contains a complete final response but automatic collection has not finished. Bridge should retry extraction against the original `agent_id`; once terminal, use explicit full task status. Do not work around collection by requiring a longer reply, injecting a completion marker, or submitting the same task again.
|
|
90
98
|
|
|
91
99
|
## Primary-agent acceptance contract
|
|
92
100
|
|
|
@@ -106,5 +114,5 @@ Cursor's completion statement means only that delegated execution ended; it is n
|
|
|
106
114
|
- Stop automatic integration when parallel tasks conflict and return the batch to primary-agent review.
|
|
107
115
|
- If Agent History or the response DOM is temporarily unreadable, let Bridge wait and retry against the same `agent_id`. Enter `needs_attention` after persistent failure; do not incorrectly mark the task complete or create a duplicate Agent.
|
|
108
116
|
- For a bound orphan, use `reap` before `cancel`. `cancel` requires the exact `expected_agent_id` and only releases after stable Stop evidence. `abandon` requires explicit confirmation, a reason, acknowledgement that the Agent may still write, and the exact `expected_agent_id` when one is bound.
|
|
109
|
-
- `cursor_status` is a pure snapshot.
|
|
117
|
+
- Default compact `cursor_status` is a pure snapshot. `cursor_status(task_id, detail="full")` is the explicit result-receipt operation; reconciliation still happens only through explicit `cursor_task_control`.
|
|
110
118
|
- Task records and reservations live only for the current Bridge MCP process. After restart, inspect Cursor Agent History and the workspace manually; persistent cross-process task leases are outside the current contract.
|