pi-cursor-bridge 0.1.11 → 0.1.13
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.
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/cursor-bridge.mjs +474 -145
- package/extensions/index.ts +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-bridge",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0+codex.20260905074950",
|
|
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.
|
|
13
|
+
This Pi package embeds Cursor Bridge 5.9.0. 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
|
@@ -22604,7 +22604,7 @@ function updateCursorSessionRegistry(filePath, mutator, options = {}) {
|
|
|
22604
22604
|
// server.mjs
|
|
22605
22605
|
init_cursor_ensure_core();
|
|
22606
22606
|
init_lifecycle_paths();
|
|
22607
|
-
var PLUGIN_VERSION = "5.
|
|
22607
|
+
var PLUGIN_VERSION = "5.9.0";
|
|
22608
22608
|
var CDP_PORT2 = Number(process.env.CURSOR_BRIDGE_CDP_PORT || 9223);
|
|
22609
22609
|
var ORIGIN = `http://localhost:${CDP_PORT2}`;
|
|
22610
22610
|
var QUERY_TIMEOUT = Number(process.env.CURSOR_BRIDGE_TIMEOUT || 3e5);
|
|
@@ -22963,10 +22963,7 @@ function createChatPanelUnavailableError(snapshot) {
|
|
|
22963
22963
|
error2.uiDiagnostic = uiDiagnostic;
|
|
22964
22964
|
return error2;
|
|
22965
22965
|
}
|
|
22966
|
-
function
|
|
22967
|
-
const js = JSON.stringify(text);
|
|
22968
|
-
return `(function(){${INPUT_PICKER_BODY}const inp=pickInput();if(!inp)return 'NO_INPUT';inp.focus();try{const s=getSelection();const r=document.createRange();r.selectNodeContents(inp);s.removeAllRanges();s.addRange(r);}catch(e){}const ok=document.execCommand('insertText',false,${js});try{inp.dispatchEvent(new InputEvent('input',{bubbles:true,inputType:'insertText',data:${js}}));}catch(e){inp.dispatchEvent(new Event('input',{bubbles:true}));}return ok?(inp.innerText||'').slice(0,30):'EXEC_FAIL';})()`;
|
|
22969
|
-
}
|
|
22966
|
+
var EXPR_PREPARE_INPUT = `(function(){${INPUT_PICKER_BODY}const inp=pickInput();if(!inp)return 'NO_INPUT';inp.focus();try{const s=getSelection();const r=document.createRange();r.selectNodeContents(inp);s.removeAllRanges();s.addRange(r);}catch(e){}return 'READY';})()`;
|
|
22970
22967
|
var EXPR_SNAP = `(function(){
|
|
22971
22968
|
const md=[...document.querySelectorAll('.markdown-root,.aichat-container [class*=markdown]')]
|
|
22972
22969
|
.filter(e=>e.offsetParent!==null&&!e.closest('.ui-model-picker__trigger,[class*=model-picker]'));
|
|
@@ -22977,14 +22974,17 @@ var EXPR_SNAP = `(function(){
|
|
|
22977
22974
|
${INPUT_PICKER_BODY}
|
|
22978
22975
|
const input=pickInput();
|
|
22979
22976
|
const inputText=String(input&&(input.innerText||input.textContent)||'').trim();
|
|
22980
|
-
|
|
22977
|
+
const composer=input&&(input.closest('.composer-bar,.ui-prompt-input,.agent-prompt-input-root')||input.parentElement);
|
|
22978
|
+
const sendButtons=composer?[...composer.querySelectorAll('button.ui-prompt-input-submit-button[data-state="send"],button.ui-prompt-input-submit-button[aria-label="Send message"],button[aria-label="Send"]')]
|
|
22979
|
+
.filter(button=>button.offsetParent!==null&&!button.disabled&&button.closest('.composer-bar,.ui-prompt-input,.agent-prompt-input-root')===composer):[];
|
|
22980
|
+
return JSON.stringify({messageCount:texts.length,replyLength:last.length,replyHash:hash,stop,inputTextLength:inputText.length,sendReady:sendButtons.length===1});
|
|
22981
22981
|
})()`;
|
|
22982
22982
|
var EXPR_CLICK_SEND = `(function(){${INPUT_PICKER_BODY}
|
|
22983
22983
|
const input=pickInput();if(!input)return 'NO_INPUT';
|
|
22984
|
-
const composer=input.closest('.composer-bar')||input.parentElement;
|
|
22984
|
+
const composer=input.closest('.composer-bar,.ui-prompt-input,.agent-prompt-input-root')||input.parentElement;
|
|
22985
22985
|
if(!composer)return 'NO_COMPOSER';
|
|
22986
|
-
const buttons=[...composer.querySelectorAll('button.ui-prompt-input-submit-button[data-state="send"],button[aria-label="Send"]')]
|
|
22987
|
-
.filter(button=>button.offsetParent!==null&&!button.disabled&&button.closest('.composer-bar')===
|
|
22986
|
+
const buttons=[...composer.querySelectorAll('button.ui-prompt-input-submit-button[data-state="send"],button.ui-prompt-input-submit-button[aria-label="Send message"],button[aria-label="Send"]')]
|
|
22987
|
+
.filter(button=>button.offsetParent!==null&&!button.disabled&&button.closest('.composer-bar,.ui-prompt-input,.agent-prompt-input-root')===composer);
|
|
22988
22988
|
if(buttons.length!==1)return buttons.length?'AMBIGUOUS_SEND':'NO_SEND';
|
|
22989
22989
|
buttons[0].click();return 'CLICKED';
|
|
22990
22990
|
})()`;
|
|
@@ -23102,7 +23102,7 @@ function exprClickBoundComposerStop(agentId) {
|
|
|
23102
23102
|
var EXPR_FIND_NEWAGENT = `(function(){const b=[...document.querySelectorAll('button,[role=button],a.action-label,.codicon')].find(e=>{if(e.offsetParent===null||e.closest('.glass-sidebar-agent-menu-btn'))return false;const s=(e.getAttribute('aria-label')||'')+' '+(e.getAttribute('title')||'')+' '+(e.innerText||'');return /(?:^|\\s)New (?:Agent|Chat)(?:\\s|$)/i.test(s);});if(!b)return '';const r=b.getBoundingClientRect();return JSON.stringify({x:Math.round(r.x+r.width/2),y:Math.round(r.y+r.height/2)});})()`;
|
|
23103
23103
|
var MODEL_PICKER_VISIBLE_BODY = `
|
|
23104
23104
|
const visible=(node)=>!!(node&&(node.offsetParent!==null||(node.getClientRects&&node.getClientRects().length>0)));
|
|
23105
|
-
const composers=[...document.querySelectorAll('.composer-bar[data-composer-id],.composer-bar,.ui-prompt-input-root')].filter(visible);
|
|
23105
|
+
const composers=[...document.querySelectorAll('.composer-bar[data-composer-id],.composer-bar,.ui-prompt-input-root,.agent-prompt-input-root,.ui-prompt-input')].filter(visible);
|
|
23106
23106
|
const composer=composers[composers.length-1]||document;
|
|
23107
23107
|
`;
|
|
23108
23108
|
var EXPR_MODEL_PICKER_TRIGGER = `(function(){
|
|
@@ -23158,15 +23158,23 @@ function selectModelPickerRow(rows, requested, kind = "model") {
|
|
|
23158
23158
|
const candidates = (Array.isArray(rows) ? rows : []).filter((row) => row && row.disabled !== true && (kind === "any" || row.kind === kind)).map((row) => {
|
|
23159
23159
|
const normalized = normalizeModelPickerText(row.text);
|
|
23160
23160
|
let score = normalized === wanted ? 1e3 : 0;
|
|
23161
|
-
if (
|
|
23162
|
-
|
|
23163
|
-
|
|
23161
|
+
if (kind !== "parameter") {
|
|
23162
|
+
if (!score && normalized.startsWith(wanted)) score = 800;
|
|
23163
|
+
if (!score && wanted.startsWith(normalized)) score = 700;
|
|
23164
|
+
if (!score && normalized.includes(wanted)) score = 600;
|
|
23165
|
+
}
|
|
23164
23166
|
return { row, score, distance: Math.abs(normalized.length - wanted.length) };
|
|
23165
23167
|
}).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.distance - b.distance);
|
|
23166
23168
|
if (candidates.length === 0) return null;
|
|
23167
23169
|
if (candidates.length > 1 && candidates[0].score === candidates[1].score && candidates[0].distance === candidates[1].distance) return null;
|
|
23168
23170
|
return candidates[0].row;
|
|
23169
23171
|
}
|
|
23172
|
+
function createModelSelectionError(message, failureClass, retryable, diagnostic = {}) {
|
|
23173
|
+
const error2 = new Error(message);
|
|
23174
|
+
error2.code = `CURSOR_MODEL_${String(failureClass || "PROBE_ERROR").toUpperCase()}`;
|
|
23175
|
+
error2.modelSelectionFailure = { failureClass, retryable, ...diagnostic };
|
|
23176
|
+
return error2;
|
|
23177
|
+
}
|
|
23170
23178
|
var WORKSPACE_SECTION_BODY = `
|
|
23171
23179
|
const headText=(el)=>String(el&&(el.innerText||el.textContent)||'').trim();
|
|
23172
23180
|
const isNewAgentButton=(node)=>{
|
|
@@ -23682,6 +23690,7 @@ var CursorBridge = class {
|
|
|
23682
23690
|
this.projectPath = options.projectPath !== void 0 ? resolve7(String(options.projectPath)) : persistedWorkspace && persistedWorkspace.projectPath || null;
|
|
23683
23691
|
this.workspaceSource = options.projectPath !== void 0 ? "constructor" : persistedWorkspace ? "persistent_init" : "auto_detect";
|
|
23684
23692
|
this.workspaceUpdatedAt = persistedWorkspace && persistedWorkspace.updatedAt || null;
|
|
23693
|
+
this.workspaceConfirmationRequired = this.workspaceKey === "default" && !!persistedWorkspace;
|
|
23685
23694
|
this.modelPreferencesFile = options.modelPreferencesFile === null ? null : resolve7(options.modelPreferencesFile || resolveCursorModelPreferencesFile());
|
|
23686
23695
|
this.modelPreferences = readCursorModelPreferences(this.modelPreferencesFile);
|
|
23687
23696
|
this.sessionFile = options.sessionFile === null ? null : resolve7(options.sessionFile || resolveCursorSessionRegistryFile());
|
|
@@ -23704,6 +23713,8 @@ var CursorBridge = class {
|
|
|
23704
23713
|
const resolvedProjectPath = this.projectPath || this._lastLifecycle && this._lastLifecycle.projectPath || null;
|
|
23705
23714
|
return {
|
|
23706
23715
|
workspaceKey: this.workspaceKey,
|
|
23716
|
+
workspaceConfirmationRequired: this.workspaceConfirmationRequired,
|
|
23717
|
+
workspaceBindingWarning: this.workspaceConfirmationRequired ? "This adapter has no host workspace identity. Run cursor_init with the intended project before using its shared saved binding." : null,
|
|
23707
23718
|
projectPath: resolvedProjectPath,
|
|
23708
23719
|
workspaceSource: this.projectPath ? this.workspaceSource : resolvedProjectPath ? "host_auto_detect" : this.workspaceSource,
|
|
23709
23720
|
workspaceUpdatedAt: this.workspaceUpdatedAt,
|
|
@@ -23738,7 +23749,10 @@ var CursorBridge = class {
|
|
|
23738
23749
|
agentLabel: session.agentLabel || null,
|
|
23739
23750
|
turnIndex: Number(session.turnIndex || 0),
|
|
23740
23751
|
activeTaskId: session.activeTaskId || null,
|
|
23741
|
-
lastTask: session.lastTask
|
|
23752
|
+
lastTask: session.lastTask ? {
|
|
23753
|
+
...session.lastTask,
|
|
23754
|
+
resultUnavailable: this.tasks.get(session.lastTask.taskId)?.result == null
|
|
23755
|
+
} : null,
|
|
23742
23756
|
modelPreference: session.modelPreference || null,
|
|
23743
23757
|
readOnly: session.scopeEnvelope && session.scopeEnvelope.readOnly === true,
|
|
23744
23758
|
allowedPaths: session.scopeEnvelope && Array.isArray(session.scopeEnvelope.allowedPaths) ? session.scopeEnvelope.allowedPaths : [],
|
|
@@ -23750,6 +23764,7 @@ var CursorBridge = class {
|
|
|
23750
23764
|
};
|
|
23751
23765
|
}
|
|
23752
23766
|
_claimNewSession({ taskId, projectPath, readOnly, allowedPaths, modelPreference, requestId, timeoutMs }) {
|
|
23767
|
+
this._ensureTaskCapacity();
|
|
23753
23768
|
if (!this.sessionFile) throw cursorSessionError("SESSION_STORAGE_DISABLED", "persistent session storage is disabled");
|
|
23754
23769
|
const sessionId = createCursorSessionId();
|
|
23755
23770
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -23795,6 +23810,7 @@ var CursorBridge = class {
|
|
|
23795
23810
|
if (requestId && session.lastRequestId === requestId) {
|
|
23796
23811
|
return { duplicate: true, session: { ...session } };
|
|
23797
23812
|
}
|
|
23813
|
+
this._ensureTaskCapacity();
|
|
23798
23814
|
if (!sameSessionProject(session.projectPath, projectPath)) {
|
|
23799
23815
|
throw cursorSessionError("SESSION_WORKSPACE_MISMATCH", `session is bound to ${session.projectPath}`);
|
|
23800
23816
|
}
|
|
@@ -23814,6 +23830,15 @@ var CursorBridge = class {
|
|
|
23814
23830
|
if (session.state !== "ready") {
|
|
23815
23831
|
throw cursorSessionError("SESSION_NOT_READY", `state=${session.state}; recovery=${session.recoveryState || "none"}`);
|
|
23816
23832
|
}
|
|
23833
|
+
if (session.lastTask?.status === "completed" && !session.lastTask.resultCollectedAt && this.tasks.has(session.lastTask.taskId)) {
|
|
23834
|
+
throw cursorSessionError("SESSION_RESULT_UNCOLLECTED", `read cursor_status(task_id=${session.lastTask.taskId}) before continuing`);
|
|
23835
|
+
}
|
|
23836
|
+
if (session.lastTask?.status === "completed" && !session.lastTask.resultCollectedAt && !this.tasks.has(session.lastTask.taskId) && session.recoveryState !== "reconciled_result_uncollected") {
|
|
23837
|
+
throw cursorSessionError("SESSION_RECONCILE_REQUIRED", "the prior reply was not read before this adapter restarted; reconcile the exact Agent before continuing");
|
|
23838
|
+
}
|
|
23839
|
+
if (session.recoveryState === "reconciled_result_uncollected" && (Number(session.turnIndex) <= 1 || session.responseBaseline)) {
|
|
23840
|
+
throw cursorSessionError("SESSION_RESULT_UNCOLLECTED", "collect the interrupted reply with cursor_session_control(action=collect_result) before continuing");
|
|
23841
|
+
}
|
|
23817
23842
|
if (!session.agentId) throw cursorSessionError("SESSION_AGENT_NOT_BOUND", sessionId);
|
|
23818
23843
|
const envelope = session.scopeEnvelope || { readOnly: false, allowedPaths: [] };
|
|
23819
23844
|
if (envelope.readOnly === true) {
|
|
@@ -23833,6 +23858,7 @@ var CursorBridge = class {
|
|
|
23833
23858
|
session.epoch = epoch;
|
|
23834
23859
|
session.activeTaskId = taskId;
|
|
23835
23860
|
session.lastRequestId = requestId || null;
|
|
23861
|
+
delete session.responseBaseline;
|
|
23836
23862
|
session.lease = {
|
|
23837
23863
|
taskId,
|
|
23838
23864
|
instanceId: this.sessionInstanceId,
|
|
@@ -23861,6 +23887,20 @@ var CursorBridge = class {
|
|
|
23861
23887
|
});
|
|
23862
23888
|
job.sessionState = "busy";
|
|
23863
23889
|
}
|
|
23890
|
+
_persistSessionResponseBaseline(job) {
|
|
23891
|
+
if (!job.sessionId) return;
|
|
23892
|
+
const baseline = job.responseBaseline;
|
|
23893
|
+
if (!baseline || !["messageCount", "replyLength", "replyHash"].every((key) => Number.isFinite(baseline[key]))) {
|
|
23894
|
+
throw cursorSessionError("SESSION_BASELINE_UNAVAILABLE", "a numeric response baseline is required before sending");
|
|
23895
|
+
}
|
|
23896
|
+
updateCursorSessionRegistry(this.sessionFile, (registry2) => {
|
|
23897
|
+
const session = registry2.sessions[job.sessionId];
|
|
23898
|
+
if (!session || session.activeTaskId !== job.id || Number(session.epoch) !== Number(job.sessionEpoch)) {
|
|
23899
|
+
throw cursorSessionError("SESSION_STALE_SENDER", "the sender no longer owns this session turn");
|
|
23900
|
+
}
|
|
23901
|
+
session.responseBaseline = { messageCount: baseline.messageCount, replyLength: baseline.replyLength, replyHash: baseline.replyHash };
|
|
23902
|
+
});
|
|
23903
|
+
}
|
|
23864
23904
|
_settleSessionJob(job, outcome, options = {}) {
|
|
23865
23905
|
if (!job || !job.sessionId || !this.sessionFile) return;
|
|
23866
23906
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -23888,6 +23928,7 @@ var CursorBridge = class {
|
|
|
23888
23928
|
}
|
|
23889
23929
|
session.activeTaskId = null;
|
|
23890
23930
|
session.lease = null;
|
|
23931
|
+
if (outcome !== "completed") delete session.responseBaseline;
|
|
23891
23932
|
session.recoveryState = null;
|
|
23892
23933
|
session.attention = null;
|
|
23893
23934
|
session.state = session.agentId ? "ready" : "failed";
|
|
@@ -23902,6 +23943,19 @@ var CursorBridge = class {
|
|
|
23902
23943
|
job.sessionError = error2 instanceof Error ? error2.message : String(error2);
|
|
23903
23944
|
}
|
|
23904
23945
|
}
|
|
23946
|
+
_markTaskResultCollected(job) {
|
|
23947
|
+
if (!isTerminalTask(job) || job.result == null || job.resultCollectedAt) return;
|
|
23948
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23949
|
+
if (job.sessionId && this.sessionFile) {
|
|
23950
|
+
updateCursorSessionRegistry(this.sessionFile, (registry2) => {
|
|
23951
|
+
const session = registry2.sessions[job.sessionId];
|
|
23952
|
+
if (!session || session.lastTask?.taskId !== job.id || Number(session.epoch) !== Number(job.sessionEpoch)) return;
|
|
23953
|
+
session.lastTask.resultCollectedAt = now;
|
|
23954
|
+
delete session.responseBaseline;
|
|
23955
|
+
});
|
|
23956
|
+
}
|
|
23957
|
+
job.resultCollectedAt = now;
|
|
23958
|
+
}
|
|
23905
23959
|
sessionStatus(sessionId) {
|
|
23906
23960
|
const session = this._readSession(sessionId);
|
|
23907
23961
|
return {
|
|
@@ -23910,17 +23964,28 @@ var CursorBridge = class {
|
|
|
23910
23964
|
...session ? this._sessionView(session) : { sessionId: String(sessionId || "") }
|
|
23911
23965
|
};
|
|
23912
23966
|
}
|
|
23913
|
-
async _reconcileSession(sessionId) {
|
|
23967
|
+
async _reconcileSession(sessionId, { collectResult = false } = {}) {
|
|
23968
|
+
const action = collectResult ? "collect_result" : "reconcile";
|
|
23914
23969
|
const initial = this._readSession(sessionId);
|
|
23915
|
-
if (!initial) return { found: false, action
|
|
23916
|
-
if (
|
|
23917
|
-
return { found: true, changed: false, action
|
|
23970
|
+
if (!initial) return { found: false, action, sessionId };
|
|
23971
|
+
if (collectResult && initial.recoveryState === "reconciled_result_collected") {
|
|
23972
|
+
return { found: true, changed: false, action, ...this._sessionView(initial), state: "already_collected", resultPersisted: false };
|
|
23973
|
+
}
|
|
23974
|
+
const unreadAfterRestart = initial.lastTask?.status === "completed" && !initial.lastTask.resultCollectedAt && !this.tasks.has(initial.lastTask.taskId);
|
|
23975
|
+
if (!collectResult && initial.state === "ready" && !unreadAfterRestart || initial.state === "closed" || initial.state === "failed") {
|
|
23976
|
+
return { found: true, changed: false, action, ...this._sessionView(initial) };
|
|
23977
|
+
}
|
|
23978
|
+
if (collectResult && (initial.state !== "ready" || initial.activeTaskId || initial.lastTask?.status !== "completed" || initial.recoveryState !== "reconciled_result_uncollected")) {
|
|
23979
|
+
throw cursorSessionError("SESSION_RESULT_NOT_READY", "reconcile a completed turn before collecting its reply");
|
|
23980
|
+
}
|
|
23981
|
+
if (collectResult && Number(initial.turnIndex) > 1 && !initial.responseBaseline) {
|
|
23982
|
+
throw cursorSessionError("SESSION_RESULT_BASELINE_UNAVAILABLE", "this older interrupted turn has no reply signature; inspect the original Agent manually before continuing");
|
|
23918
23983
|
}
|
|
23919
23984
|
if (!initial.agentId) {
|
|
23920
23985
|
return {
|
|
23921
23986
|
found: true,
|
|
23922
23987
|
changed: false,
|
|
23923
|
-
action
|
|
23988
|
+
action,
|
|
23924
23989
|
state: "agent_missing",
|
|
23925
23990
|
attention: "No exact agentId is available. Confirm the Cursor task state manually before explicit abandon.",
|
|
23926
23991
|
...this._sessionView(initial)
|
|
@@ -23938,21 +24003,44 @@ var CursorBridge = class {
|
|
|
23938
24003
|
effectiveExecution: "parallel_agent",
|
|
23939
24004
|
lastRecoveryAt: null,
|
|
23940
24005
|
recoveryState: null,
|
|
23941
|
-
error: null
|
|
24006
|
+
error: null,
|
|
24007
|
+
responseBaseline: initial.responseBaseline || null
|
|
23942
24008
|
};
|
|
23943
24009
|
const observed = await this._readStableParallelEntry(probe);
|
|
24010
|
+
let collectedReply;
|
|
24011
|
+
if (collectResult) {
|
|
24012
|
+
if (!observed.stable || !observed.entry || observed.entry.showSpinner || classifyParallelTerminalIcon(observed.entry.icon) !== "completed") {
|
|
24013
|
+
throw cursorSessionError("SESSION_RESULT_NOT_READY", "the exact Agent must have a stable completed state");
|
|
24014
|
+
}
|
|
24015
|
+
try {
|
|
24016
|
+
collectedReply = await this._withUiLock(() => this._collectParallelAgent(probe));
|
|
24017
|
+
} catch (error2) {
|
|
24018
|
+
if (probe.uiDiagnostic && error2 && typeof error2 === "object") error2.uiDiagnostic = probe.uiDiagnostic;
|
|
24019
|
+
throw error2;
|
|
24020
|
+
}
|
|
24021
|
+
if (!String(collectedReply || "").trim()) throw cursorSessionError("SESSION_RESULT_UNAVAILABLE", "no final reply was collected");
|
|
24022
|
+
}
|
|
23944
24023
|
let result;
|
|
23945
24024
|
updateCursorSessionRegistry(this.sessionFile, (registry2) => {
|
|
23946
24025
|
const session = registry2.sessions[sessionId];
|
|
23947
24026
|
if (!session) {
|
|
23948
|
-
result = { found: false, action
|
|
24027
|
+
result = { found: false, action, sessionId };
|
|
23949
24028
|
return;
|
|
23950
24029
|
}
|
|
23951
|
-
if (Number(session.epoch || 0) !== Number(initial.epoch || 0) || session.activeTaskId !== initial.activeTaskId) {
|
|
23952
|
-
result = { found: true, changed: false, action
|
|
24030
|
+
if (Number(session.epoch || 0) !== Number(initial.epoch || 0) || session.activeTaskId !== initial.activeTaskId || session.state !== initial.state) {
|
|
24031
|
+
result = { found: true, changed: false, action, state: "stale_observation", ...this._sessionView(session) };
|
|
23953
24032
|
return;
|
|
23954
24033
|
}
|
|
23955
24034
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24035
|
+
if (collectResult) {
|
|
24036
|
+
session.lastTask.resultCollectedAt = now;
|
|
24037
|
+
delete session.responseBaseline;
|
|
24038
|
+
session.recoveryState = "reconciled_result_collected";
|
|
24039
|
+
session.attention = null;
|
|
24040
|
+
session.updatedAt = now;
|
|
24041
|
+
result = { found: true, changed: true, action, ...this._sessionView(session), result: collectedReply, resultPersisted: false, uiDiagnostic: probe.uiDiagnostic || null };
|
|
24042
|
+
return;
|
|
24043
|
+
}
|
|
23956
24044
|
if (!observed.stable || !observed.entry) {
|
|
23957
24045
|
session.state = "needs_attention";
|
|
23958
24046
|
session.recoveryState = probe.recoveryState || "history_unavailable";
|
|
@@ -23992,7 +24080,8 @@ var CursorBridge = class {
|
|
|
23992
24080
|
session.lease = null;
|
|
23993
24081
|
session.state = "ready";
|
|
23994
24082
|
session.recoveryState = terminalClass === "completed" ? "reconciled_result_uncollected" : null;
|
|
23995
|
-
session.attention = terminalClass === "completed" ? "The interrupted turn completed in Cursor, but its reply was not persisted.
|
|
24083
|
+
session.attention = terminalClass === "completed" ? Number(session.turnIndex) > 1 && !session.responseBaseline ? "This older interrupted turn has no saved reply signature. Inspect the original Agent manually before continuing; automatic collection cannot identify its reply safely." : "The interrupted turn completed in Cursor, but its reply was not persisted. Use cursor_session_control(action=collect_result) before the next turn." : null;
|
|
24084
|
+
if (terminalClass !== "completed") delete session.responseBaseline;
|
|
23996
24085
|
session.updatedAt = now;
|
|
23997
24086
|
result = { found: true, changed: true, action: "reconcile", state: terminalClass, ...this._sessionView(session) };
|
|
23998
24087
|
});
|
|
@@ -24002,10 +24091,11 @@ var CursorBridge = class {
|
|
|
24002
24091
|
const id = String(sessionId || "").trim();
|
|
24003
24092
|
if (!id) throw cursorSessionError("SESSION_REQUIRED", "session_id must not be empty");
|
|
24004
24093
|
const action = String(options.action || "").trim().toLowerCase();
|
|
24005
|
-
if (!["reconcile", "close", "forget", "abandon"].includes(action)) {
|
|
24006
|
-
throw cursorSessionError("SESSION_ACTION_UNSUPPORTED", "expected reconcile, close, forget, or abandon");
|
|
24094
|
+
if (!["reconcile", "collect_result", "close", "forget", "abandon"].includes(action)) {
|
|
24095
|
+
throw cursorSessionError("SESSION_ACTION_UNSUPPORTED", "expected reconcile, collect_result, close, forget, or abandon");
|
|
24007
24096
|
}
|
|
24008
24097
|
if (action === "reconcile") return this._reconcileSession(id);
|
|
24098
|
+
if (action === "collect_result") return this._reconcileSession(id, { collectResult: true });
|
|
24009
24099
|
let result;
|
|
24010
24100
|
updateCursorSessionRegistry(this.sessionFile, (registry2) => {
|
|
24011
24101
|
const session = registry2.sessions[id];
|
|
@@ -24063,6 +24153,7 @@ var CursorBridge = class {
|
|
|
24063
24153
|
}
|
|
24064
24154
|
const previousProjectPath = this.projectPath || this._lastLifecycle && this._lastLifecycle.projectPath || null;
|
|
24065
24155
|
const saved = writeWorkspaceBinding(this.workspaceFile, this.workspaceKey, projectPath);
|
|
24156
|
+
this.workspaceConfirmationRequired = false;
|
|
24066
24157
|
this.projectPath = saved.projectPath;
|
|
24067
24158
|
this.workspaceSource = "persistent_init";
|
|
24068
24159
|
this.workspaceUpdatedAt = saved.updatedAt;
|
|
@@ -24257,6 +24348,7 @@ var CursorBridge = class {
|
|
|
24257
24348
|
const text = String(query || "").trim();
|
|
24258
24349
|
if (!text) throw new Error("query must not be empty");
|
|
24259
24350
|
if (text.length > 2e4) throw new Error("query exceeds the 20,000-character limit");
|
|
24351
|
+
this._assertWorkspaceConfirmed();
|
|
24260
24352
|
if (this._hasGlobalReservation()) {
|
|
24261
24353
|
throw new Error("A global Cursor reservation has an unconfirmed Stop state; resolve blockingTaskIds from cursor_status first");
|
|
24262
24354
|
}
|
|
@@ -24269,7 +24361,9 @@ var CursorBridge = class {
|
|
|
24269
24361
|
allowedPaths: [],
|
|
24270
24362
|
modelPreference: this._modelPreferenceFor("cce")
|
|
24271
24363
|
});
|
|
24272
|
-
|
|
24364
|
+
const result = normalizeCceSearchResult(await job.promise);
|
|
24365
|
+
this._markTaskResultCollected(job);
|
|
24366
|
+
return result;
|
|
24273
24367
|
}
|
|
24274
24368
|
// Unlisted compatibility aliases for clients that cached the pre-3.0 tool surface.
|
|
24275
24369
|
async search(query) {
|
|
@@ -24285,6 +24379,7 @@ var CursorBridge = class {
|
|
|
24285
24379
|
const text = String(prompt || "").trim();
|
|
24286
24380
|
if (!text) throw new Error("prompt must not be empty");
|
|
24287
24381
|
if (text.length > 1e5) throw new Error("prompt exceeds the 100,000-character limit");
|
|
24382
|
+
this._assertWorkspaceConfirmed();
|
|
24288
24383
|
if (this._hasGlobalReservation()) {
|
|
24289
24384
|
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");
|
|
24290
24385
|
}
|
|
@@ -24420,6 +24515,7 @@ var CursorBridge = class {
|
|
|
24420
24515
|
return `cursor-${Date.now().toString(36)}-${this.nextTaskId++}`;
|
|
24421
24516
|
}
|
|
24422
24517
|
_enqueue(kind, prompt, options) {
|
|
24518
|
+
this._ensureTaskCapacity();
|
|
24423
24519
|
const id = options.taskId || this._nextTaskId();
|
|
24424
24520
|
let resolvePromise;
|
|
24425
24521
|
let rejectPromise;
|
|
@@ -24463,6 +24559,8 @@ var CursorBridge = class {
|
|
|
24463
24559
|
targetUiFlavor: null,
|
|
24464
24560
|
fallbackReason: null,
|
|
24465
24561
|
result: null,
|
|
24562
|
+
resultCollectedAt: null,
|
|
24563
|
+
waitDeadlineAt: null,
|
|
24466
24564
|
error: null,
|
|
24467
24565
|
cancelRequested: false,
|
|
24468
24566
|
cancelReason: null,
|
|
@@ -24821,7 +24919,7 @@ var CursorBridge = class {
|
|
|
24821
24919
|
this._throwIfCancelledBeforeSend(options);
|
|
24822
24920
|
await this._applyModelPreference(c, options.modelPreference, options);
|
|
24823
24921
|
this._throwIfCancelledBeforeSend(options);
|
|
24824
|
-
const filled = await
|
|
24922
|
+
const filled = await this._fillPrompt(c, prompt, options);
|
|
24825
24923
|
if (filled === "NO_INPUT") await this._throwChatPanelUnavailableAfterNoInput(c);
|
|
24826
24924
|
if (filled === "NO_INPUT" || filled === "EXEC_FAIL") throw new Error("Failed to enter the query because the input state was invalid");
|
|
24827
24925
|
await sleep2(450);
|
|
@@ -24941,25 +25039,83 @@ var CursorBridge = class {
|
|
|
24941
25039
|
modelRow = selectModelPickerRow(expanded && expanded.rows, requestedModel, "model");
|
|
24942
25040
|
return { snapshot: expanded, modelRow };
|
|
24943
25041
|
}
|
|
24944
|
-
|
|
24945
|
-
|
|
24946
|
-
|
|
24947
|
-
|
|
24948
|
-
|
|
24949
|
-
|
|
25042
|
+
_inspectModelPickerRows(snapshot, requested, kind) {
|
|
25043
|
+
const rows = (snapshot && Array.isArray(snapshot.rows) ? snapshot.rows : []).filter((row) => row && row.disabled !== true && row.kind === kind);
|
|
25044
|
+
const wanted = normalizeModelPickerText(requested);
|
|
25045
|
+
const exact = rows.filter((row) => normalizeModelPickerText(row.text) === wanted);
|
|
25046
|
+
return {
|
|
25047
|
+
snapshot,
|
|
25048
|
+
row: exact.length === 1 ? exact[0] : null,
|
|
25049
|
+
ambiguous: exact.length > 1,
|
|
25050
|
+
available: rows.map((row) => row.text)
|
|
25051
|
+
};
|
|
25052
|
+
}
|
|
25053
|
+
async _waitForModelPickerMatch(c, requested, kind, job, options = {}) {
|
|
25054
|
+
const timeoutMs = Math.max(0, Number(options.timeoutMs ?? 700));
|
|
25055
|
+
const pollMs = Math.max(10, Number(options.pollMs ?? 100));
|
|
25056
|
+
const stableMs = Math.max(0, Number(options.stableMs ?? 250));
|
|
25057
|
+
const deadline = Date.now() + timeoutMs;
|
|
25058
|
+
let stableSignature = null;
|
|
25059
|
+
let stableSince = 0;
|
|
25060
|
+
let last = this._inspectModelPickerRows({ open: false, rows: [] }, requested, kind);
|
|
25061
|
+
do {
|
|
25062
|
+
this._throwIfCancelledBeforeSend(job);
|
|
25063
|
+
last = this._inspectModelPickerRows(await this._readModelPickerRows(c), requested, kind);
|
|
25064
|
+
if (last.row) return { ...last, state: "matched" };
|
|
25065
|
+
const signature = JSON.stringify(last.available);
|
|
25066
|
+
if (last.available.length > 0) {
|
|
25067
|
+
if (signature !== stableSignature) {
|
|
25068
|
+
stableSignature = signature;
|
|
25069
|
+
stableSince = Date.now();
|
|
25070
|
+
} else if (Date.now() - stableSince >= stableMs) {
|
|
25071
|
+
return { ...last, state: last.ambiguous ? "ambiguous" : "unsupported" };
|
|
25072
|
+
}
|
|
25073
|
+
} else {
|
|
25074
|
+
stableSignature = null;
|
|
25075
|
+
stableSince = 0;
|
|
25076
|
+
}
|
|
25077
|
+
if (Date.now() >= deadline) break;
|
|
25078
|
+
await sleep2(Math.min(pollMs, Math.max(0, deadline - Date.now())));
|
|
25079
|
+
} while (Date.now() <= deadline);
|
|
25080
|
+
return { ...last, state: last.ambiguous ? "ambiguous" : "not_rendered" };
|
|
25081
|
+
}
|
|
25082
|
+
async _selectedEffortRow(c, modelRow, effort, job) {
|
|
25083
|
+
const requested = cursorEffortUiValue(effort);
|
|
25084
|
+
const snapshot = await this._readModelPickerRows(c);
|
|
25085
|
+
const immediate = this._inspectModelPickerRows(snapshot, requested, "parameter");
|
|
25086
|
+
if (immediate.row) return { ...immediate, state: "matched", attempts: ["visible"] };
|
|
25087
|
+
const attempts = [];
|
|
25088
|
+
const effortControl = (snapshot.rows || []).find((row) => row.kind === "effort_control" && row.disabled !== true);
|
|
25089
|
+
if (effortControl) {
|
|
25090
|
+
this._throwIfCancelledBeforeSend(job);
|
|
25091
|
+
attempts.push("effort_control");
|
|
25092
|
+
await this._clickModelPickerPoint(c, effortControl);
|
|
25093
|
+
const result2 = await this._waitForModelPickerMatch(c, requested, "parameter", job, { timeoutMs: 700 });
|
|
25094
|
+
if (result2.state !== "not_rendered") return { ...result2, attempts };
|
|
24950
25095
|
}
|
|
24951
|
-
|
|
25096
|
+
this._throwIfCancelledBeforeSend(job);
|
|
25097
|
+
attempts.push("model_hover");
|
|
24952
25098
|
await this._hoverModelPickerPoint(c, modelRow);
|
|
24953
|
-
await
|
|
24954
|
-
|
|
24955
|
-
|
|
24956
|
-
|
|
25099
|
+
let result = await this._waitForModelPickerMatch(c, requested, "parameter", job, { timeoutMs: 700 });
|
|
25100
|
+
if (result.state !== "not_rendered") return { ...result, attempts };
|
|
25101
|
+
if (modelRow && modelRow.hasSubmenu) {
|
|
25102
|
+
this._throwIfCancelledBeforeSend(job);
|
|
25103
|
+
attempts.push("model_click");
|
|
24957
25104
|
await this._clickModelPickerPoint(c, modelRow);
|
|
24958
|
-
await
|
|
24959
|
-
snapshot = await this._readModelPickerRows(c);
|
|
24960
|
-
effortRow = selectModelPickerRow(snapshot.rows, cursorEffortUiValue(effort), "parameter");
|
|
25105
|
+
result = await this._waitForModelPickerMatch(c, requested, "parameter", job, { timeoutMs: 1100 });
|
|
24961
25106
|
}
|
|
24962
|
-
return
|
|
25107
|
+
return { ...result, attempts };
|
|
25108
|
+
}
|
|
25109
|
+
async _waitForSelectedModelPickerRow(c, requested, kind, job, timeoutMs = 1500) {
|
|
25110
|
+
const deadline = Date.now() + timeoutMs;
|
|
25111
|
+
do {
|
|
25112
|
+
this._throwIfCancelledBeforeSend(job);
|
|
25113
|
+
const inspected = this._inspectModelPickerRows(await this._readModelPickerRows(c), requested, kind);
|
|
25114
|
+
if (inspected.row && inspected.row.selected) return inspected.row;
|
|
25115
|
+
if (Date.now() >= deadline) break;
|
|
25116
|
+
await sleep2(Math.min(100, Math.max(0, deadline - Date.now())));
|
|
25117
|
+
} while (Date.now() <= deadline);
|
|
25118
|
+
return null;
|
|
24963
25119
|
}
|
|
24964
25120
|
async _applyModelPreference(c, preference, job) {
|
|
24965
25121
|
if (!preference) {
|
|
@@ -24968,63 +25124,167 @@ var CursorBridge = class {
|
|
|
24968
25124
|
}
|
|
24969
25125
|
const requestedModel = String(preference.model || "").trim();
|
|
24970
25126
|
const requestedEffort = preference.effort ? normalizeCursorModelEffort(preference.effort, "") : null;
|
|
24971
|
-
|
|
24972
|
-
let located = await this._findModelPickerModel(c, opened, requestedModel);
|
|
24973
|
-
let modelRow = located.modelRow;
|
|
24974
|
-
if (!modelRow) {
|
|
24975
|
-
throw new Error(`Configured Cursor model is unavailable or ambiguous: ${requestedModel}`);
|
|
24976
|
-
}
|
|
24977
|
-
let selectedEffortRow = null;
|
|
24978
|
-
if (requestedEffort && modelRow.hasSubmenu) {
|
|
24979
|
-
selectedEffortRow = await this._selectedEffortRow(c, modelRow, requestedEffort);
|
|
24980
|
-
if (!selectedEffortRow) {
|
|
24981
|
-
throw new Error(`Cursor model ${requestedModel} does not expose effort ${requestedEffort}`);
|
|
24982
|
-
}
|
|
24983
|
-
if (!selectedEffortRow.selected || !modelRow.selected) {
|
|
24984
|
-
await this._clickModelPickerPoint(c, selectedEffortRow);
|
|
24985
|
-
await sleep2(550);
|
|
24986
|
-
}
|
|
24987
|
-
} else if (!modelRow.selected) {
|
|
24988
|
-
await this._clickModelPickerPoint(c, modelRow);
|
|
24989
|
-
await sleep2(550);
|
|
24990
|
-
}
|
|
24991
|
-
let trigger = await this._readModelPickerTrigger(c);
|
|
24992
|
-
if (!trigger.found || !normalizeModelPickerText(trigger.text).includes(normalizeModelPickerText(requestedModel))) {
|
|
24993
|
-
const reopened = await this._openModelPicker(c);
|
|
24994
|
-
located = await this._findModelPickerModel(c, reopened, requestedModel);
|
|
24995
|
-
const selected = selectModelPickerRow(located.snapshot.rows.filter((row) => row.selected), requestedModel, "model");
|
|
24996
|
-
if (!selected) throw new Error(`Cursor did not confirm configured model ${requestedModel}`);
|
|
24997
|
-
modelRow = selected;
|
|
24998
|
-
}
|
|
25127
|
+
let modelRow = null;
|
|
24999
25128
|
let effectiveEffort = null;
|
|
25000
|
-
|
|
25001
|
-
|
|
25002
|
-
|
|
25129
|
+
let primaryError = null;
|
|
25130
|
+
try {
|
|
25131
|
+
const opened = await this._openModelPicker(c);
|
|
25132
|
+
let located = await this._findModelPickerModel(c, opened, requestedModel);
|
|
25003
25133
|
modelRow = located.modelRow;
|
|
25004
|
-
if (!modelRow)
|
|
25005
|
-
|
|
25006
|
-
|
|
25007
|
-
|
|
25134
|
+
if (!modelRow) {
|
|
25135
|
+
throw createModelSelectionError(
|
|
25136
|
+
`Configured Cursor model is unavailable or ambiguous: ${requestedModel}`,
|
|
25137
|
+
"model_unavailable",
|
|
25138
|
+
false,
|
|
25139
|
+
{ available: (located.snapshot.rows || []).filter((row) => row.kind === "model").map((row) => row.text) }
|
|
25140
|
+
);
|
|
25008
25141
|
}
|
|
25009
|
-
|
|
25010
|
-
|
|
25011
|
-
await
|
|
25142
|
+
let effortPickerReopened = false;
|
|
25143
|
+
const resolveEffort = async () => {
|
|
25144
|
+
let outcome = await this._selectedEffortRow(c, modelRow, requestedEffort, job);
|
|
25145
|
+
if (!outcome.row && outcome.state === "not_rendered" && !effortPickerReopened) {
|
|
25146
|
+
effortPickerReopened = true;
|
|
25147
|
+
await this._closeModelPicker(c);
|
|
25148
|
+
const reopened = await this._openModelPicker(c);
|
|
25149
|
+
located = await this._findModelPickerModel(c, reopened, requestedModel);
|
|
25150
|
+
modelRow = located.modelRow;
|
|
25151
|
+
if (!modelRow) {
|
|
25152
|
+
throw createModelSelectionError(
|
|
25153
|
+
`Cursor model row disappeared while applying effort: ${requestedModel}`,
|
|
25154
|
+
"model_unavailable",
|
|
25155
|
+
true
|
|
25156
|
+
);
|
|
25157
|
+
}
|
|
25158
|
+
outcome = await this._selectedEffortRow(c, modelRow, requestedEffort, job);
|
|
25159
|
+
outcome.attempts = ["picker_reopen", ...outcome.attempts || []];
|
|
25160
|
+
}
|
|
25161
|
+
return outcome;
|
|
25162
|
+
};
|
|
25163
|
+
const throwEffortFailure = (outcome) => {
|
|
25164
|
+
const failureClass = outcome.state === "ambiguous" ? "effort_ambiguous" : outcome.state === "unsupported" ? "effort_unsupported" : "effort_menu_not_rendered";
|
|
25165
|
+
const message = failureClass === "effort_menu_not_rendered" ? `Cursor effort menu did not render for model ${requestedModel}` : failureClass === "effort_ambiguous" ? `Cursor model ${requestedModel} exposes ambiguous effort ${requestedEffort}` : `Cursor model ${requestedModel} does not expose effort ${requestedEffort}`;
|
|
25166
|
+
throw createModelSelectionError(
|
|
25167
|
+
message,
|
|
25168
|
+
failureClass,
|
|
25169
|
+
failureClass === "effort_menu_not_rendered",
|
|
25170
|
+
{ available: outcome.available || [], attempts: outcome.attempts || [] }
|
|
25171
|
+
);
|
|
25172
|
+
};
|
|
25173
|
+
if (requestedEffort && modelRow.hasSubmenu) {
|
|
25174
|
+
const selectedEffort = await resolveEffort();
|
|
25175
|
+
if (!selectedEffort.row) throwEffortFailure(selectedEffort);
|
|
25176
|
+
if (!selectedEffort.row.selected || !modelRow.selected) {
|
|
25177
|
+
this._throwIfCancelledBeforeSend(job);
|
|
25178
|
+
await this._clickModelPickerPoint(c, selectedEffort.row);
|
|
25179
|
+
await sleep2(550);
|
|
25180
|
+
}
|
|
25181
|
+
} else if (!modelRow.selected) {
|
|
25182
|
+
this._throwIfCancelledBeforeSend(job);
|
|
25183
|
+
await this._clickModelPickerPoint(c, modelRow);
|
|
25184
|
+
await sleep2(550);
|
|
25012
25185
|
}
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
const
|
|
25018
|
-
|
|
25019
|
-
|
|
25186
|
+
let trigger2 = await this._readModelPickerTrigger(c);
|
|
25187
|
+
if (!trigger2.found || !normalizeModelPickerText(trigger2.text).includes(normalizeModelPickerText(requestedModel))) {
|
|
25188
|
+
const reopened = await this._openModelPicker(c);
|
|
25189
|
+
located = await this._findModelPickerModel(c, reopened, requestedModel);
|
|
25190
|
+
const selected = selectModelPickerRow(located.snapshot.rows.filter((row) => row.selected), requestedModel, "model");
|
|
25191
|
+
if (!selected) {
|
|
25192
|
+
throw createModelSelectionError(
|
|
25193
|
+
`Cursor did not confirm configured model ${requestedModel}`,
|
|
25194
|
+
"model_not_confirmed",
|
|
25195
|
+
true
|
|
25196
|
+
);
|
|
25197
|
+
}
|
|
25198
|
+
modelRow = selected;
|
|
25199
|
+
}
|
|
25200
|
+
if (requestedEffort) {
|
|
25201
|
+
const reopened = await this._openModelPicker(c);
|
|
25202
|
+
located = await this._findModelPickerModel(c, reopened, requestedModel);
|
|
25203
|
+
modelRow = located.modelRow;
|
|
25204
|
+
if (!modelRow) {
|
|
25205
|
+
throw createModelSelectionError(
|
|
25206
|
+
`Cursor model row disappeared while applying effort: ${requestedModel}`,
|
|
25207
|
+
"model_unavailable",
|
|
25208
|
+
true
|
|
25209
|
+
);
|
|
25210
|
+
}
|
|
25211
|
+
const effortOutcome = await resolveEffort();
|
|
25212
|
+
if (!effortOutcome.row) throwEffortFailure(effortOutcome);
|
|
25213
|
+
let effortRow = effortOutcome.row;
|
|
25214
|
+
if (!effortRow.selected) {
|
|
25215
|
+
effortRow = await this._waitForSelectedModelPickerRow(
|
|
25216
|
+
c,
|
|
25217
|
+
cursorEffortUiValue(requestedEffort),
|
|
25218
|
+
"parameter",
|
|
25219
|
+
job
|
|
25220
|
+
);
|
|
25221
|
+
}
|
|
25020
25222
|
if (!effortRow || !effortRow.selected) {
|
|
25021
|
-
throw
|
|
25223
|
+
throw createModelSelectionError(
|
|
25224
|
+
`Cursor did not confirm effort ${requestedEffort} for model ${requestedModel}`,
|
|
25225
|
+
"effort_not_confirmed",
|
|
25226
|
+
true
|
|
25227
|
+
);
|
|
25022
25228
|
}
|
|
25229
|
+
effectiveEffort = requestedEffort;
|
|
25230
|
+
}
|
|
25231
|
+
} catch (error2) {
|
|
25232
|
+
primaryError = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
25233
|
+
let failure = primaryError.modelSelectionFailure;
|
|
25234
|
+
if (!failure) {
|
|
25235
|
+
const pickerUnavailable = /model picker (?:is unavailable|did not open)/i.test(primaryError.message);
|
|
25236
|
+
failure = {
|
|
25237
|
+
failureClass: pickerUnavailable ? "picker_did_not_open" : "probe_error",
|
|
25238
|
+
retryable: true
|
|
25239
|
+
};
|
|
25240
|
+
}
|
|
25241
|
+
const diagnostic = {
|
|
25242
|
+
configured: true,
|
|
25243
|
+
applied: false,
|
|
25244
|
+
requestedModel,
|
|
25245
|
+
requestedEffort,
|
|
25246
|
+
failureClass: failure.failureClass,
|
|
25247
|
+
retryable: failure.retryable === true,
|
|
25248
|
+
errorCode: primaryError.code || `CURSOR_MODEL_${String(failure.failureClass).toUpperCase()}`,
|
|
25249
|
+
available: failure.available || [],
|
|
25250
|
+
attempts: failure.attempts || [],
|
|
25251
|
+
runtimeMode: this.runtimeMode,
|
|
25252
|
+
lastError: primaryError.message,
|
|
25253
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25254
|
+
};
|
|
25255
|
+
primaryError.modelSelection = diagnostic;
|
|
25256
|
+
if (job) job.modelSelection = diagnostic;
|
|
25257
|
+
throw primaryError;
|
|
25258
|
+
} finally {
|
|
25259
|
+
try {
|
|
25260
|
+
await this._closeModelPicker(c);
|
|
25261
|
+
} catch (cleanupError) {
|
|
25262
|
+
const message = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
25263
|
+
if (!primaryError) {
|
|
25264
|
+
const error2 = createModelSelectionError(message, "picker_cleanup_failed", true);
|
|
25265
|
+
const diagnostic = {
|
|
25266
|
+
configured: true,
|
|
25267
|
+
applied: false,
|
|
25268
|
+
requestedModel,
|
|
25269
|
+
requestedEffort,
|
|
25270
|
+
failureClass: "picker_cleanup_failed",
|
|
25271
|
+
retryable: true,
|
|
25272
|
+
errorCode: error2.code,
|
|
25273
|
+
available: [],
|
|
25274
|
+
attempts: [],
|
|
25275
|
+
runtimeMode: this.runtimeMode,
|
|
25276
|
+
lastError: message,
|
|
25277
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25278
|
+
};
|
|
25279
|
+
error2.modelSelection = diagnostic;
|
|
25280
|
+
if (job) job.modelSelection = diagnostic;
|
|
25281
|
+
throw error2;
|
|
25282
|
+
}
|
|
25283
|
+
if (primaryError.modelSelection) primaryError.modelSelection.cleanupError = message;
|
|
25284
|
+
if (job && job.modelSelection) job.modelSelection.cleanupError = message;
|
|
25023
25285
|
}
|
|
25024
|
-
effectiveEffort = requestedEffort;
|
|
25025
25286
|
}
|
|
25026
|
-
await this.
|
|
25027
|
-
trigger = await this._readModelPickerTrigger(c);
|
|
25287
|
+
const trigger = await this._readModelPickerTrigger(c);
|
|
25028
25288
|
const result = {
|
|
25029
25289
|
configured: true,
|
|
25030
25290
|
applied: true,
|
|
@@ -25203,6 +25463,38 @@ var CursorBridge = class {
|
|
|
25203
25463
|
}
|
|
25204
25464
|
}
|
|
25205
25465
|
}
|
|
25466
|
+
async _fillPrompt(c, text, job, timeoutMs = 2500) {
|
|
25467
|
+
const prepared = await evalJS(c, EXPR_PREPARE_INPUT);
|
|
25468
|
+
if (prepared !== "READY") return prepared;
|
|
25469
|
+
try {
|
|
25470
|
+
await c.send("Input.insertText", { text: String(text) });
|
|
25471
|
+
} catch {
|
|
25472
|
+
return "EXEC_FAIL";
|
|
25473
|
+
}
|
|
25474
|
+
const deadline = Date.now() + timeoutMs;
|
|
25475
|
+
let snapshot = {};
|
|
25476
|
+
do {
|
|
25477
|
+
this._throwIfCancelledBeforeSend(job);
|
|
25478
|
+
try {
|
|
25479
|
+
snapshot = JSON.parse(await evalJS(c, EXPR_SNAP));
|
|
25480
|
+
} catch {
|
|
25481
|
+
}
|
|
25482
|
+
if (Number(snapshot.inputTextLength || 0) > 0 && snapshot.sendReady === true) {
|
|
25483
|
+
return String(text).slice(0, 30);
|
|
25484
|
+
}
|
|
25485
|
+
if (Date.now() >= deadline) break;
|
|
25486
|
+
await sleep2(Math.min(100, Math.max(0, deadline - Date.now())));
|
|
25487
|
+
} while (Date.now() <= deadline);
|
|
25488
|
+
const error2 = new Error("Cursor composer did not become send-ready after trusted input");
|
|
25489
|
+
error2.code = "CURSOR_COMPOSER_NOT_SEND_READY";
|
|
25490
|
+
error2.confirmedNotSent = true;
|
|
25491
|
+
error2.composerDiagnostic = {
|
|
25492
|
+
inputTextLength: Number(snapshot.inputTextLength || 0),
|
|
25493
|
+
sendReady: snapshot.sendReady === true,
|
|
25494
|
+
runtimeMode: this.runtimeMode
|
|
25495
|
+
};
|
|
25496
|
+
throw error2;
|
|
25497
|
+
}
|
|
25206
25498
|
async _confirmSubmission(c, baselineCount = 0, providerErrorBaseline = "") {
|
|
25207
25499
|
const accepted = async () => {
|
|
25208
25500
|
await this._throwIfNewProviderError(c, providerErrorBaseline);
|
|
@@ -25292,7 +25584,7 @@ var CursorBridge = class {
|
|
|
25292
25584
|
this._throwIfCancelledBeforeSend(job);
|
|
25293
25585
|
await this._applyModelPreference(c, job.modelPreference, job);
|
|
25294
25586
|
this._throwIfCancelledBeforeSend(job);
|
|
25295
|
-
const filled = await
|
|
25587
|
+
const filled = await this._fillPrompt(c, job.prompt, job);
|
|
25296
25588
|
if (filled === "NO_INPUT") await this._throwChatPanelUnavailableAfterNoInput(c);
|
|
25297
25589
|
if (filled === "NO_INPUT" || filled === "EXEC_FAIL") throw new Error("Failed to enter the parallel_agent task");
|
|
25298
25590
|
await sleep2(350);
|
|
@@ -25411,8 +25703,9 @@ var CursorBridge = class {
|
|
|
25411
25703
|
await this._applyModelPreference(c, job.modelPreference, job);
|
|
25412
25704
|
this._throwIfCancelledBeforeSend(job);
|
|
25413
25705
|
job.responseBaseline = await this._captureSessionResponseBaseline(c);
|
|
25706
|
+
this._persistSessionResponseBaseline(job);
|
|
25414
25707
|
const providerErrorBaseline = providerErrorSignature(await this._readProviderError(c));
|
|
25415
|
-
const filled = await
|
|
25708
|
+
const filled = await this._fillPrompt(c, job.prompt, job);
|
|
25416
25709
|
if (filled === "NO_INPUT") await this._throwChatPanelUnavailableAfterNoInput(c);
|
|
25417
25710
|
if (filled === "NO_INPUT" || filled === "EXEC_FAIL") {
|
|
25418
25711
|
throw cursorSessionError("SESSION_INPUT_FAILED", "failed to enter the continued task");
|
|
@@ -25485,8 +25778,16 @@ var CursorBridge = class {
|
|
|
25485
25778
|
job.monitorPromise = monitorPromise;
|
|
25486
25779
|
return true;
|
|
25487
25780
|
}
|
|
25781
|
+
_taskWaitDeadline(job, timeoutMs = job?.timeoutMs) {
|
|
25782
|
+
if (!job) return Date.now() + timeoutMs;
|
|
25783
|
+
if (!Number.isFinite(job.waitDeadlineAt)) {
|
|
25784
|
+
const sentAt = Date.parse(job.sentAt || "");
|
|
25785
|
+
job.waitDeadlineAt = (Number.isFinite(sentAt) ? sentAt : Date.now()) + timeoutMs;
|
|
25786
|
+
}
|
|
25787
|
+
return job.waitDeadlineAt;
|
|
25788
|
+
}
|
|
25488
25789
|
async _monitorParallelAgent(job, generation) {
|
|
25489
|
-
const
|
|
25790
|
+
const deadline = this._taskWaitDeadline(job);
|
|
25490
25791
|
let sawGenerating = false;
|
|
25491
25792
|
let completedStable = 0;
|
|
25492
25793
|
let missingPolls = 0;
|
|
@@ -25495,7 +25796,7 @@ var CursorBridge = class {
|
|
|
25495
25796
|
let lastTerminalErrorSignature = "";
|
|
25496
25797
|
let collectionAttempts = 0;
|
|
25497
25798
|
let lastCollectionError = "";
|
|
25498
|
-
while (Date.now()
|
|
25799
|
+
while (Date.now() < deadline) {
|
|
25499
25800
|
if (!this._monitorOwns(job, generation)) return;
|
|
25500
25801
|
await sleep2(1400);
|
|
25501
25802
|
if (!this._monitorOwns(job, generation)) return;
|
|
@@ -25613,51 +25914,63 @@ var CursorBridge = class {
|
|
|
25613
25914
|
}
|
|
25614
25915
|
throw new Error(`After opening ${agentId}, it could not be confirmed as the selected Agent`);
|
|
25615
25916
|
}
|
|
25917
|
+
async _withRestoredAgentSelection(c, job, operation) {
|
|
25918
|
+
const entries = await this._readAgentEntries(c, true);
|
|
25919
|
+
const previousSelectedId = (entries.find((entry) => entry.isSelected) || {}).id || null;
|
|
25920
|
+
try {
|
|
25921
|
+
return await operation();
|
|
25922
|
+
} finally {
|
|
25923
|
+
if (previousSelectedId && previousSelectedId !== job.agentId) {
|
|
25924
|
+
try {
|
|
25925
|
+
const opened = await this._requestExactAgentSelection(c, previousSelectedId);
|
|
25926
|
+
if (opened !== "OPENED") throw new Error(`Unable to restore ${previousSelectedId}: ${opened}`);
|
|
25927
|
+
await this._waitForSelectedAgent(c, previousSelectedId);
|
|
25928
|
+
} catch (error2) {
|
|
25929
|
+
job.uiDiagnostic = { ...job.uiDiagnostic, selectionRestore: { state: "failed", agentId: previousSelectedId, detail: String(error2.message).slice(0, 500) } };
|
|
25930
|
+
}
|
|
25931
|
+
}
|
|
25932
|
+
}
|
|
25933
|
+
}
|
|
25616
25934
|
async _collectParallelAgent(job) {
|
|
25617
25935
|
const page = await findPage({ targetId: job.targetId, purpose: "parallel_agent" });
|
|
25618
25936
|
const c = makeClient(page.webSocketDebuggerUrl);
|
|
25619
25937
|
await c.ready;
|
|
25620
|
-
let previousSelectedId = null;
|
|
25621
25938
|
try {
|
|
25622
|
-
|
|
25623
|
-
|
|
25624
|
-
|
|
25625
|
-
|
|
25626
|
-
|
|
25627
|
-
|
|
25628
|
-
|
|
25629
|
-
|
|
25630
|
-
|
|
25631
|
-
|
|
25632
|
-
|
|
25633
|
-
|
|
25634
|
-
|
|
25635
|
-
|
|
25636
|
-
|
|
25637
|
-
|
|
25638
|
-
|
|
25639
|
-
|
|
25640
|
-
|
|
25641
|
-
|
|
25642
|
-
|
|
25643
|
-
|
|
25644
|
-
|
|
25939
|
+
return await this._withRestoredAgentSelection(c, job, async () => {
|
|
25940
|
+
const opened = await evalJS(c, exprOpenAgent(job.agentId));
|
|
25941
|
+
if (opened !== "OPENED") throw new Error(`Unable to open ${job.agentId}: ${opened}`);
|
|
25942
|
+
await this._closeHistory(c);
|
|
25943
|
+
await this._waitForSelectedAgent(c, job.agentId);
|
|
25944
|
+
let answer = "";
|
|
25945
|
+
let lastKey = "";
|
|
25946
|
+
let stable = 0;
|
|
25947
|
+
for (let i = 0; i < 80; i++) {
|
|
25948
|
+
const snap = await this._readResponseSnapshot(c);
|
|
25949
|
+
const candidate = String(await evalJS(c, EXPR_EXTRACT) || "").trim();
|
|
25950
|
+
const hasNewTurn = isSessionTurnReplyReady(job.responseBaseline, snap);
|
|
25951
|
+
if (candidate && hasNewTurn && Number(snap.stop || 0) === 0) {
|
|
25952
|
+
const key = `${snap.replyLength}:${snap.replyHash}`;
|
|
25953
|
+
if (key === lastKey) stable++;
|
|
25954
|
+
else {
|
|
25955
|
+
lastKey = key;
|
|
25956
|
+
stable = 0;
|
|
25957
|
+
}
|
|
25958
|
+
if (stable >= 1) {
|
|
25959
|
+
answer = candidate;
|
|
25960
|
+
break;
|
|
25961
|
+
}
|
|
25645
25962
|
}
|
|
25963
|
+
await sleep2(300);
|
|
25646
25964
|
}
|
|
25647
|
-
|
|
25648
|
-
|
|
25649
|
-
|
|
25650
|
-
if (previousSelectedId && previousSelectedId !== job.agentId) {
|
|
25651
|
-
if (await this._ensureHistoryOpen(c)) {
|
|
25652
|
-
await evalJS(c, exprOpenAgent(previousSelectedId));
|
|
25653
|
-
await this._closeHistory(c);
|
|
25654
|
-
await this._waitForSelectedAgent(c, previousSelectedId);
|
|
25655
|
-
}
|
|
25656
|
-
}
|
|
25657
|
-
return answer;
|
|
25965
|
+
if (!answer) throw new Error(`${job.agentId} was opened, but no final assistant reply was found`);
|
|
25966
|
+
return answer;
|
|
25967
|
+
});
|
|
25658
25968
|
} finally {
|
|
25659
|
-
|
|
25660
|
-
|
|
25969
|
+
try {
|
|
25970
|
+
await this._closeHistory(c);
|
|
25971
|
+
} finally {
|
|
25972
|
+
c.close();
|
|
25973
|
+
}
|
|
25661
25974
|
}
|
|
25662
25975
|
}
|
|
25663
25976
|
_reapWithoutResult(job, error2, evidence) {
|
|
@@ -25672,7 +25985,6 @@ var CursorBridge = class {
|
|
|
25672
25985
|
job.terminalEvidence = evidence || "stable_completed_history_icon";
|
|
25673
25986
|
job.recoveryState = "terminal_result_uncollected";
|
|
25674
25987
|
job.reservationScope = uncertainSubmissionReservationScope(job, error2);
|
|
25675
|
-
job.recoveryState = error2.recoveryState || "monitoring_uncertain_submission";
|
|
25676
25988
|
this._safeSettleSessionJob(job, "terminal_result_uncollected", { needsAttention: true });
|
|
25677
25989
|
}
|
|
25678
25990
|
_abandonJob(job, reason) {
|
|
@@ -25749,6 +26061,7 @@ var CursorBridge = class {
|
|
|
25749
26061
|
job.phase = "running";
|
|
25750
26062
|
job.error = null;
|
|
25751
26063
|
job.recoveryState = "monitoring";
|
|
26064
|
+
if (options.reattach !== false && !job.cancelRequested) job.waitDeadlineAt = Date.now() + job.timeoutMs;
|
|
25752
26065
|
const attached = options.reattach !== false && !job.cancelRequested && this._startParallelMonitor(job);
|
|
25753
26066
|
return { changed: attached, state: "running", monitorReattached: attached, task: this._taskView(job, true) };
|
|
25754
26067
|
}
|
|
@@ -26147,12 +26460,12 @@ var CursorBridge = class {
|
|
|
26147
26460
|
}).catch((e) => console.error("\u26A0\uFE0F Failed to restore the original Cursor Agent: " + e.message));
|
|
26148
26461
|
}
|
|
26149
26462
|
async _waitComplete(c, timeoutMs = QUERY_TIMEOUT, baselineCount = 0, job = null, providerErrorBaseline = "") {
|
|
26150
|
-
const
|
|
26463
|
+
const deadline = this._taskWaitDeadline(job, timeoutMs);
|
|
26151
26464
|
const INTERVAL = 1e3;
|
|
26152
26465
|
let sawStop = false;
|
|
26153
26466
|
let lastReplyKey = "", stableReply = 0;
|
|
26154
26467
|
await sleep2(1200);
|
|
26155
|
-
while (Date.now()
|
|
26468
|
+
while (Date.now() < deadline) {
|
|
26156
26469
|
await this._throwIfNewProviderError(c, providerErrorBaseline);
|
|
26157
26470
|
if (job && job.cancelRequested) {
|
|
26158
26471
|
if (job.agentId) {
|
|
@@ -26217,6 +26530,7 @@ var CursorBridge = class {
|
|
|
26217
26530
|
throw new Error(`Cursor task timed out (${timeoutMs}ms) before generation was confirmed stopped with a complete assistant reply${taskHint}`);
|
|
26218
26531
|
}
|
|
26219
26532
|
_taskView(job, includeResult = false) {
|
|
26533
|
+
if (includeResult) this._markTaskResultCollected(job);
|
|
26220
26534
|
const view = {
|
|
26221
26535
|
taskId: job.id,
|
|
26222
26536
|
kind: job.kind,
|
|
@@ -26255,6 +26569,8 @@ var CursorBridge = class {
|
|
|
26255
26569
|
monitorAttached: job.monitorAttached,
|
|
26256
26570
|
monitorGeneration: job.monitorGeneration,
|
|
26257
26571
|
resultUnavailable: job.resultUnavailable,
|
|
26572
|
+
resultCollectedAt: job.resultCollectedAt || null,
|
|
26573
|
+
waitDeadlineAt: Number.isFinite(job.waitDeadlineAt) ? new Date(job.waitDeadlineAt).toISOString() : null,
|
|
26258
26574
|
terminalEvidence: job.terminalEvidence,
|
|
26259
26575
|
providerError: job.providerError,
|
|
26260
26576
|
uiDiagnostic: job.uiDiagnostic,
|
|
@@ -26264,7 +26580,7 @@ var CursorBridge = class {
|
|
|
26264
26580
|
blocksFifo: this.activeParallel.has(job.id) && !isTerminalTask(job),
|
|
26265
26581
|
blocksAll: this.activeParallel.has(job.id) && !isTerminalTask(job) && job.reservationScope === "global"
|
|
26266
26582
|
};
|
|
26267
|
-
if (includeResult
|
|
26583
|
+
if (includeResult) view.result = job.result;
|
|
26268
26584
|
if (job.phase === "orphaned") {
|
|
26269
26585
|
if (job.recoveryState === "terminal_result_uncollected") {
|
|
26270
26586
|
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.";
|
|
@@ -26274,11 +26590,22 @@ var CursorBridge = class {
|
|
|
26274
26590
|
}
|
|
26275
26591
|
return view;
|
|
26276
26592
|
}
|
|
26277
|
-
|
|
26278
|
-
if (this.
|
|
26593
|
+
_assertWorkspaceConfirmed() {
|
|
26594
|
+
if (this.workspaceConfirmationRequired) {
|
|
26595
|
+
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.");
|
|
26596
|
+
}
|
|
26597
|
+
}
|
|
26598
|
+
_ensureTaskCapacity() {
|
|
26599
|
+
this._trimTasks(49);
|
|
26600
|
+
if (this.tasks.size >= 50) {
|
|
26601
|
+
throw new Error("TASK_RETENTION_FULL: 50 tasks are active or have unread replies. Read unreadResultTaskIds with cursor_status(task_id), or wait for active tasks before submitting more work.");
|
|
26602
|
+
}
|
|
26603
|
+
}
|
|
26604
|
+
_trimTasks(limit = 50) {
|
|
26605
|
+
if (this.tasks.size <= limit) return;
|
|
26279
26606
|
for (const [id, job] of this.tasks) {
|
|
26280
|
-
if (isTerminalTask(job)) this.tasks.delete(id);
|
|
26281
|
-
if (this.tasks.size <=
|
|
26607
|
+
if (isTerminalTask(job) && (job.result == null || job.resultCollectedAt)) this.tasks.delete(id);
|
|
26608
|
+
if (this.tasks.size <= limit) break;
|
|
26282
26609
|
}
|
|
26283
26610
|
}
|
|
26284
26611
|
async status(taskId = "") {
|
|
@@ -26308,6 +26635,8 @@ var CursorBridge = class {
|
|
|
26308
26635
|
blockedQueuedCount: this.activeParallel.size > 0 ? this.queue.filter((job) => globalBlocked || job.effectiveExecution !== "parallel_agent").length : 0,
|
|
26309
26636
|
activeParallel: [...this.activeParallel.values()].map((job) => this._taskView(job)),
|
|
26310
26637
|
recentTasks: [...this.tasks.values()].slice(-10).map((job) => this._taskView(job)),
|
|
26638
|
+
unreadResultTaskIds: [...this.tasks.values()].filter((job) => isTerminalTask(job) && job.result != null && !job.resultCollectedAt).map((job) => job.id),
|
|
26639
|
+
taskRetentionLimit: 50,
|
|
26311
26640
|
cdpPort: CDP_PORT2,
|
|
26312
26641
|
lifecycle: this._lastLifecycle || {
|
|
26313
26642
|
adapterPid: process.pid,
|
|
@@ -26373,7 +26702,7 @@ function buildToolDefinitions(bridgeInstance) {
|
|
|
26373
26702
|
background: { type: "boolean", default: true, description: "When true, return the task ID immediately. When false, wait for the task to finish or need attention." },
|
|
26374
26703
|
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." },
|
|
26375
26704
|
read_only: { type: "boolean", default: false, description: "Set true when Cursor must not change the workspace." },
|
|
26376
|
-
timeout_ms: { type: "integer", minimum: 3e4, maximum: 9e5, default: 6e5, description: "
|
|
26705
|
+
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." },
|
|
26377
26706
|
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." },
|
|
26378
26707
|
completion_contract: { type: "string", description: "Optional acceptance checks or a required final-report format." },
|
|
26379
26708
|
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." },
|
|
@@ -26401,12 +26730,12 @@ function buildToolDefinitions(bridgeInstance) {
|
|
|
26401
26730
|
},
|
|
26402
26731
|
{
|
|
26403
26732
|
name: "cursor_session_control",
|
|
26404
|
-
description: "Recover, close, or forget one exact persistent cursor_do session. reconcile checks the exact Agent twice and never resends. close prevents future sends but does not stop or delete the Cursor Agent. An active or uncertain session cannot be closed. abandon is the explicit last resort when exact stop evidence is unavailable. forget requires confirm=true and is allowed only after close; it deletes only the Bridge mapping.",
|
|
26733
|
+
description: "Recover, collect a reply, close, or forget one exact persistent cursor_do session. reconcile checks the exact Agent twice and never resends. collect_result reads the completed reconciled turn from that Agent without sending a prompt; it may temporarily select that Agent. close prevents future sends but does not stop or delete the Cursor Agent. An active or uncertain session cannot be closed. abandon is the explicit last resort when exact stop evidence is unavailable. forget requires confirm=true and is allowed only after close; it deletes only the Bridge mapping.",
|
|
26405
26734
|
inputSchema: {
|
|
26406
26735
|
type: "object",
|
|
26407
26736
|
properties: {
|
|
26408
26737
|
session_id: { type: "string", description: "The exact session ID returned by cursor_do(session_mode=create)." },
|
|
26409
|
-
action: { type: "string", enum: ["reconcile", "close", "forget", "abandon"], description: "reconcile reads the exact Agent state; close ends safe continuity; forget removes a closed mapping; abandon releases an uncertain mapping without stop proof." },
|
|
26738
|
+
action: { type: "string", enum: ["reconcile", "collect_result", "close", "forget", "abandon"], description: "reconcile reads the exact Agent state; collect_result retrieves its completed latest turn before continuing; close ends safe continuity; forget removes a closed mapping; abandon releases an uncertain mapping without stop proof." },
|
|
26410
26739
|
confirm: { type: "boolean", default: false, description: "Required for forget and abandon." },
|
|
26411
26740
|
reason: { type: "string", description: "Required and non-empty for abandon." },
|
|
26412
26741
|
acknowledge_may_still_write: { type: "boolean", default: false, description: "Required for abandon; acknowledges that the underlying Cursor Agent may still run or write." }
|
|
@@ -26629,6 +26958,7 @@ export {
|
|
|
26629
26958
|
EXPR_MODEL_PICKER_ROWS,
|
|
26630
26959
|
EXPR_MODEL_PICKER_TRIGGER,
|
|
26631
26960
|
EXPR_PAGE_CAPABILITIES,
|
|
26961
|
+
EXPR_PREPARE_INPUT,
|
|
26632
26962
|
EXPR_PROVIDER_ERROR,
|
|
26633
26963
|
EXPR_VISIBLE,
|
|
26634
26964
|
EXPR_VISIBLE_COMPOSER,
|
|
@@ -26644,7 +26974,6 @@ export {
|
|
|
26644
26974
|
exprClickBoundComposerStop,
|
|
26645
26975
|
exprClickSelectedAgentStop,
|
|
26646
26976
|
exprCreateAgentForWorkspace,
|
|
26647
|
-
exprFill,
|
|
26648
26977
|
exprInspectWorkspaceRepository,
|
|
26649
26978
|
exprOpenAgent,
|
|
26650
26979
|
isConfirmedCompletedReply,
|
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.1.
|
|
13
|
+
packageVersion: "0.1.13",
|
|
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.
|
|
3
|
+
"version": "0.1.13",
|
|
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.
|
|
50
|
+
"embeddedProductVersion": "5.9.0"
|
|
51
51
|
}
|
|
52
52
|
}
|