pi-cursor-bridge 0.1.17 → 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": "5.10.1+codex.20260908161937",
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
- This Pi package embeds Cursor Bridge 5.10.1, including explicit request provenance for CCE and delegated tasks. Cursor must be installed and signed in. Current end-to-end compatibility claims remain scoped to the environments documented in the main repository.
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)
@@ -22605,7 +22605,7 @@ function updateCursorSessionRegistry(filePath, mutator, options = {}) {
22605
22605
  // server.mjs
22606
22606
  init_cursor_ensure_core();
22607
22607
  init_lifecycle_paths();
22608
- var PLUGIN_VERSION = "5.10.1";
22608
+ var PLUGIN_VERSION = "6.0.0";
22609
22609
  var CDP_PORT2 = Number(process.env.CURSOR_BRIDGE_CDP_PORT || 9223);
22610
22610
  var ORIGIN = `http://localhost:${CDP_PORT2}`;
22611
22611
  var QUERY_TIMEOUT = Number(process.env.CURSOR_BRIDGE_TIMEOUT || 3e5);
@@ -24029,7 +24029,7 @@ var CursorBridge = class {
24029
24029
  throw cursorSessionError("SESSION_NOT_READY", `state=${session.state}; recovery=${session.recoveryState || "none"}`);
24030
24030
  }
24031
24031
  if (session.lastTask?.status === "completed" && !session.lastTask.resultCollectedAt && this.tasks.has(session.lastTask.taskId)) {
24032
- 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`);
24033
24033
  }
24034
24034
  if (session.lastTask?.status === "completed" && !session.lastTask.resultCollectedAt && !this.tasks.has(session.lastTask.taskId) && session.recoveryState !== "reconciled_result_uncollected") {
24035
24035
  throw cursorSessionError("SESSION_RECONCILE_REQUIRED", "the prior reply was not read before this adapter restarted; reconcile the exact Agent before continuing");
@@ -24688,7 +24688,7 @@ var CursorBridge = class {
24688
24688
  modelPreference = session.modelPreference || null;
24689
24689
  if (duplicate) {
24690
24690
  const existing = this.tasks.get(session.activeTaskId || session.lastTask && session.lastTask.taskId || "");
24691
- return existing ? { duplicate: true, ...this._taskView(existing, true) } : { duplicate: true, ...this._sessionView(session) };
24691
+ return existing ? { duplicate: true, ...options.background === false ? this._collectedTaskView(existing) : this._compactTaskView(existing) } : { duplicate: true, ...this._sessionView(session) };
24692
24692
  }
24693
24693
  }
24694
24694
  const job = this._enqueue("do", fullPrompt, {
@@ -24712,9 +24712,9 @@ var CursorBridge = class {
24712
24712
  agentId: sessionMode === "continue" ? session && session.agentId : null,
24713
24713
  agentLabel: sessionMode === "continue" ? session && session.agentLabel : null
24714
24714
  });
24715
- if (options.background !== false) return this._taskView(job);
24715
+ if (options.background !== false) return this._compactTaskView(job);
24716
24716
  await job.promise;
24717
- return this._taskView(job, true);
24717
+ return this._collectedTaskView(job);
24718
24718
  }
24719
24719
  _assertNoParallelPathConflict(allowedPaths) {
24720
24720
  if (this._hasGlobalReservation()) {
@@ -26560,11 +26560,13 @@ var CursorBridge = class {
26560
26560
  job.cancelReason = reason || (job.execution === "parallel_agent" ? "User requested Cursor Agent cancellation" : "User requested FIFO task cancellation");
26561
26561
  job.cancelRequestSeq = Number(job.cancelRequestSeq || 0) + 1;
26562
26562
  }
26563
- return this._withJobLock(job, () => this._taskControlLocked(job, action, {
26563
+ const result = await this._withJobLock(job, () => this._taskControlLocked(job, action, {
26564
26564
  ...options,
26565
26565
  reason,
26566
26566
  expectedAgentId
26567
26567
  }));
26568
+ if (result && result.task) result.task = this._compactTaskView(job);
26569
+ return result;
26568
26570
  }
26569
26571
  async _taskControlLocked(job, action, options) {
26570
26572
  if (action === "reap") {
@@ -26807,7 +26809,6 @@ var CursorBridge = class {
26807
26809
  throw new Error(`Cursor task timed out (${timeoutMs}ms) before generation was confirmed stopped with a complete assistant reply${taskHint}`);
26808
26810
  }
26809
26811
  _taskView(job, includeResult = false) {
26810
- if (includeResult) this._markTaskResultCollected(job);
26811
26812
  const view = {
26812
26813
  taskId: job.id,
26813
26814
  requestedTimeoutMs: job.requestedTimeoutMs ?? job.timeoutMs,
@@ -26874,6 +26875,157 @@ var CursorBridge = class {
26874
26875
  }
26875
26876
  return view;
26876
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
+ }
26877
27029
  _assertWorkspaceConfirmed() {
26878
27030
  if (this.workspaceConfirmationRequired) {
26879
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.");
@@ -26882,7 +27034,7 @@ var CursorBridge = class {
26882
27034
  _ensureTaskCapacity() {
26883
27035
  this._trimTasks(49);
26884
27036
  if (this.tasks.size >= 50) {
26885
- 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.");
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.');
26886
27038
  }
26887
27039
  }
26888
27040
  _trimTasks(limit = 50) {
@@ -26892,16 +27044,20 @@ var CursorBridge = class {
26892
27044
  if (this.tasks.size <= limit) break;
26893
27045
  }
26894
27046
  }
26895
- async status(taskId = "") {
27047
+ async status(taskId = "", { detail = "compact" } = {}) {
27048
+ const normalizedDetail = normalizeStatusDetail(detail);
26896
27049
  if (taskId) {
26897
27050
  const job = this.tasks.get(String(taskId));
26898
- if (!job) return { found: false, taskId: String(taskId), ...this.workspaceView(), ...this.delegationView(), ...this.runtimeModeView(), ...this.modelPreferencesView(), ...this.sessionRegistryView() };
26899
- return { found: true, ...this.workspaceView(), ...this.delegationView(), ...this.runtimeModeView(), ...this.modelPreferencesView(), ...this.sessionRegistryView(), ...this._taskView(job, true) };
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) } };
26900
27056
  }
26901
27057
  const parallelRunning = this.activeParallel.size;
26902
27058
  const uiBusy = this.busy;
26903
27059
  const globalBlocked = this._hasGlobalReservation();
26904
- const common = {
27060
+ const common = normalizedDetail === "full" ? {
26905
27061
  pluginVersion: PLUGIN_VERSION,
26906
27062
  statusPath: "json-list",
26907
27063
  ...this.workspaceView(),
@@ -26939,6 +27095,21 @@ var CursorBridge = class {
26939
27095
  runtimeMode: this.runtimeMode,
26940
27096
  presentation: null
26941
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()
26942
27113
  };
26943
27114
  try {
26944
27115
  const ver = await httpJson("/json/version");
@@ -26959,6 +27130,13 @@ function buildSearchInputSchema() {
26959
27130
  required: ["query"]
26960
27131
  };
26961
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
+ }
26962
27140
  var REQUEST_CONTEXT_SCHEMA = {
26963
27141
  type: "object",
26964
27142
  additionalProperties: false,
@@ -26988,7 +27166,7 @@ function buildToolDefinitions(bridgeInstance) {
26988
27166
  },
26989
27167
  bridgeInstance.environmentDelegationMode !== "off" ? {
26990
27168
  name: "cursor_do",
26991
- 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). Cursor can do the work, but the main agent still owns review and final verification. A direct user opt-out always wins.",
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.',
26992
27170
  inputSchema: {
26993
27171
  type: "object",
26994
27172
  properties: {
@@ -27009,7 +27187,7 @@ function buildToolDefinitions(bridgeInstance) {
27009
27187
  } : null,
27010
27188
  {
27011
27189
  name: "cursor_task_control",
27012
- 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 collects a stable terminal result when possible. 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.",
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.',
27013
27191
  inputSchema: {
27014
27192
  type: "object",
27015
27193
  properties: {
@@ -27065,12 +27243,14 @@ function buildToolDefinitions(bridgeInstance) {
27065
27243
  },
27066
27244
  {
27067
27245
  name: "cursor_status",
27068
- description: "Read-only snapshot of Cursor connectivity, queued/running work, reservations, execution availability, persistent model/effort defaults, sessions, and normal/minimal runtime presentation. 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.",
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.',
27069
27247
  inputSchema: {
27070
27248
  type: "object",
27249
+ additionalProperties: false,
27071
27250
  properties: {
27072
27251
  task_id: { type: "string", description: "A task ID returned by cursor_do." },
27073
- 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." }
27074
27254
  }
27075
27255
  }
27076
27256
  }
@@ -27187,15 +27367,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request2) => {
27187
27367
  if (args && args.task_id && args.session_id) {
27188
27368
  throw cursorSessionError("STATUS_SELECTOR_AMBIGUOUS", "pass task_id or session_id, not both");
27189
27369
  }
27370
+ const detail = normalizeStatusDetail(args && args.detail);
27190
27371
  const statusMs = Math.max(1e3, Number(process.env.CURSOR_BRIDGE_STATUS_TIMEOUT || 8e3));
27191
27372
  let result;
27192
27373
  try {
27193
27374
  result = await Promise.race([
27194
- 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 }),
27195
27376
  new Promise((_, reject) => setTimeout(() => reject(new Error(`cursor_status_timeout_${statusMs}`)), statusMs))
27196
27377
  ]);
27197
27378
  } catch (error2) {
27198
- result = {
27379
+ result = detail === "full" ? {
27199
27380
  connected: false,
27200
27381
  pluginVersion: PLUGIN_VERSION,
27201
27382
  statusPath: "json-list",
@@ -27203,7 +27384,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request2) => {
27203
27384
  ...bridge.workspaceView(),
27204
27385
  ...bridge.delegationView(),
27205
27386
  ...bridge.runtimeModeView(),
27387
+ ...bridge.modelPreferencesView(),
27206
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)
27207
27395
  };
27208
27396
  }
27209
27397
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -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.17",
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.1.17",
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": "5.10.1"
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. Collect each turn 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."
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 -> collect by task_id -> primary agent inspects the real changes and verifies them`
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`. Use only the documented `session_mode` and `session_id` fields when continuity is explicit; never infer continuity from the visible chat.
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. Compare Cursor's claimed work with the real diff, `allowed_paths`, and acceptance contract.
89
- 4. 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.
90
- 5. Run risk-proportionate verification in the primary agent. Cursor's response alone cannot support a formal pass, verified state, or governance transition.
91
- 6. Record each task as complete, partial, failed, timed out, or ambiguous before summarizing the batch.
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 collects that exact Agent. `cursor_status` is read-only and does not reap automatically.
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` | Read the raw response, then inspect the real diff, allowed paths, and completion contract. |
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`; do not release on one DOM failure. |
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 querying 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`. Do not work around collection by requiring a longer reply, injecting a completion marker, or submitting the same task again.
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. Reconciliation happens only through explicit `cursor_task_control`.
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.