pi-cursor-sdk 0.5.2 → 0.5.3
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/CHANGELOG.md +26 -0
- package/README.md +38 -17
- package/dist/context.js +2 -2
- package/dist/cursor-custom-subagent-definitions.js +1 -1
- package/dist/cursor-fallback-models.generated.js +402 -1
- package/dist/cursor-local-agent-ownership.js +17 -0
- package/dist/cursor-mcp-timeout-override.js +1 -1
- package/dist/cursor-pi-tool-bridge-diagnostics.js +4 -0
- package/dist/cursor-pi-tool-bridge-run.js +10 -3
- package/dist/cursor-provider-errors.js +2 -2
- package/dist/cursor-provider-turn-summary.js +1 -1
- package/dist/cursor-session-agent-cleanup.js +10 -1
- package/dist/cursor-session-agent-lifecycle.js +2 -2
- package/dist/cursor-session-agent-lineage.js +11 -7
- package/dist/cursor-session-agent-resume.js +15 -9
- package/dist/cursor-session-agent.js +22 -7
- package/dist/cursor-session-scope.js +27 -14
- package/dist/cursor-session-settings.js +3 -3
- package/dist/cursor-session-store.js +1 -1
- package/dist/cursor-state.js +17 -6
- package/dist/model-discovery.js +8 -9
- package/docs/cursor-live-smoke-checklist.md +2 -2
- package/docs/cursor-model-ux-spec.md +23 -14
- package/docs/cursor-native-tool-replay.md +6 -3
- package/docs/cursor-native-tool-visual-audit.md +1 -1
- package/docs/cursor-testing-lessons.md +24 -6
- package/docs/cursor-tool-surfaces.md +16 -1
- package/package.json +8 -8
- package/src/context.ts +2 -2
- package/src/cursor-custom-subagent-definitions.ts +1 -1
- package/src/cursor-fallback-models.generated.ts +402 -1
- package/src/cursor-local-agent-ownership.ts +18 -0
- package/src/cursor-mcp-timeout-override.ts +1 -1
- package/src/cursor-pi-tool-bridge-diagnostics.ts +17 -0
- package/src/cursor-pi-tool-bridge-run.ts +10 -3
- package/src/cursor-provider-errors.ts +2 -2
- package/src/cursor-provider-turn-summary.ts +1 -1
- package/src/cursor-session-agent-cleanup.ts +6 -1
- package/src/cursor-session-agent-lifecycle.ts +2 -2
- package/src/cursor-session-agent-lineage.ts +10 -5
- package/src/cursor-session-agent-resume.ts +14 -7
- package/src/cursor-session-agent.ts +23 -7
- package/src/cursor-session-scope.ts +33 -11
- package/src/cursor-session-settings.ts +3 -3
- package/src/cursor-session-store.ts +1 -1
- package/src/cursor-state.ts +25 -5
- package/src/model-discovery.ts +7 -8
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CursorSessionStoreIdentity } from "./cursor-session-store.js";
|
|
2
|
+
|
|
3
|
+
// ponytail: process-local claims protect extension-owned handles and cleanup.
|
|
4
|
+
// Cross-process SDK ownership requires an upstream store/agent lease contract.
|
|
5
|
+
// Unproven disposal retains the exact handle claim until process exit.
|
|
6
|
+
const claims = new Set<string>();
|
|
7
|
+
|
|
8
|
+
export function claimCursorLocalAgent(identity: CursorSessionStoreIdentity, agentId: string): (() => void) | undefined {
|
|
9
|
+
const key = JSON.stringify([identity.stateRoot, agentId]);
|
|
10
|
+
if (claims.has(key)) return undefined;
|
|
11
|
+
claims.add(key);
|
|
12
|
+
let released = false;
|
|
13
|
+
return () => {
|
|
14
|
+
if (released) return;
|
|
15
|
+
released = true;
|
|
16
|
+
claims.delete(key);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -91,7 +91,7 @@ export function isCursorSdkMcpToolTimeoutStack(stack: string | undefined): boole
|
|
|
91
91
|
|
|
92
92
|
export function isCursorSdkMcpConnectTimeoutStack(stack: string | undefined): boolean {
|
|
93
93
|
if (!stack || !isCursorSdkMcpProtocolTimeoutStack(stack)) return false;
|
|
94
|
-
// SDK
|
|
94
|
+
// The SDK minifies the Client class name; the protocol method and SDK
|
|
95
95
|
// frame, not that constructor's spelling, establish this timeout purpose.
|
|
96
96
|
return /^\s*at (?:[\w$]+\.)?(?:connect|listTools|getTools)\s*\(.*(?:node_modules[/\\]@cursor[/\\]sdk|@cursor\/sdk\/dist)/m.test(stack);
|
|
97
97
|
}
|
|
@@ -49,6 +49,17 @@ interface CursorPiToolBridgeRunDisposedDiagnostic extends CursorPiToolBridgeLife
|
|
|
49
49
|
event: "run_disposed";
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
interface CursorPiToolBridgeMcpInitializedDiagnostic {
|
|
53
|
+
event: "mcp_initialized";
|
|
54
|
+
runId: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface CursorPiToolBridgeMcpToolsListDiagnostic {
|
|
58
|
+
event: "mcp_tools_list";
|
|
59
|
+
runId: string;
|
|
60
|
+
toolCount: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
export interface CursorPiToolBridgeRequestDiagnosticFields {
|
|
53
64
|
runId: string;
|
|
54
65
|
bridgeCallId: string;
|
|
@@ -79,6 +90,8 @@ export type CursorPiToolBridgeDiagnosticEvent =
|
|
|
79
90
|
| CursorPiToolBridgeToolsExposedDiagnostic
|
|
80
91
|
| CursorPiToolBridgeRunCancelledDiagnostic
|
|
81
92
|
| CursorPiToolBridgeRunDisposedDiagnostic
|
|
93
|
+
| CursorPiToolBridgeMcpInitializedDiagnostic
|
|
94
|
+
| CursorPiToolBridgeMcpToolsListDiagnostic
|
|
82
95
|
| CursorPiToolBridgeRequestQueuedDiagnostic
|
|
83
96
|
| CursorPiToolBridgeRequestResolvedDiagnostic
|
|
84
97
|
| CursorPiToolBridgeRequestRejectedDiagnostic;
|
|
@@ -89,6 +102,10 @@ function assertNeverDiagnosticEvent(_event: never): never {
|
|
|
89
102
|
|
|
90
103
|
export function serializeCursorPiToolBridgeDiagnostic(event: CursorPiToolBridgeDiagnosticEvent): Record<string, unknown> {
|
|
91
104
|
switch (event.event) {
|
|
105
|
+
case "mcp_initialized":
|
|
106
|
+
return { event: event.event, runId: event.runId };
|
|
107
|
+
case "mcp_tools_list":
|
|
108
|
+
return { event: event.event, runId: event.runId, toolCount: event.toolCount };
|
|
92
109
|
case "run_created":
|
|
93
110
|
return {
|
|
94
111
|
event: event.event,
|
|
@@ -274,9 +274,16 @@ export class CursorPiToolBridgeRunImpl implements CursorPiToolBridgeRun {
|
|
|
274
274
|
sessionIdGenerator: randomUUID,
|
|
275
275
|
});
|
|
276
276
|
|
|
277
|
-
server
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
// ponytail: server-side receipts only, not SDK/model consumption or ongoing
|
|
278
|
+
// connection health; upgrade when Cursor exposes public MCP lifecycle callbacks.
|
|
279
|
+
server.oninitialized = () => {
|
|
280
|
+
this.emitDiagnostic({ event: "mcp_initialized", runId: this.id });
|
|
281
|
+
};
|
|
282
|
+
server.setRequestHandler("tools/list", async () => {
|
|
283
|
+
const tools = this.snapshot.tools.map(snapshotToolToMcpTool);
|
|
284
|
+
this.emitDiagnostic({ event: "mcp_tools_list", runId: this.id, toolCount: tools.length });
|
|
285
|
+
return { tools };
|
|
286
|
+
});
|
|
280
287
|
server.setRequestHandler("tools/call", async (request, context) => {
|
|
281
288
|
return this.enqueueToolRequest(
|
|
282
289
|
request.params.name,
|
|
@@ -168,7 +168,7 @@ function isCursorSdkStallAbortNetworkError(code: unknown, evidence: string, stac
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
|
-
* @cursor/sdk@1.0.
|
|
171
|
+
* @cursor/sdk@1.0.37 throws internal RetriableError (name/kind "RetriableError") with message
|
|
172
172
|
* "Connection stalled" or "Connection stalled repeatedly" after fetchWithRetry exhausts stalls.
|
|
173
173
|
* The ConnectError is only the cause; the top-level error is not a ConnectError.
|
|
174
174
|
*/
|
|
@@ -229,7 +229,7 @@ export function classifyCursorConnectError(error: unknown): CursorConnectErrorCl
|
|
|
229
229
|
|
|
230
230
|
const directAbort = (code === 1 || code === "canceled") &&
|
|
231
231
|
(causeName === "AbortError" || /AbortError/.test(stack));
|
|
232
|
-
// The vendored
|
|
232
|
+
// The vendored SDK transport can wrap a canceled ConnectError as Unknown.
|
|
233
233
|
// Require the observed canceled -> AbortError cause chain, not generic text.
|
|
234
234
|
const wrappedAbort = (code === 2 || code === "unknown") &&
|
|
235
235
|
causeName === "ConnectError" &&
|
|
@@ -40,7 +40,7 @@ export async function prepareCursorSummaryProviderTurn(
|
|
|
40
40
|
configureCursorSdkHttp1(sdk, resolvedConfig.local.useHttp1ForAgent);
|
|
41
41
|
restoreCursorSdkOutputFilter = installCursorSdkOutputFilter();
|
|
42
42
|
sessionStore = (await openCursorSessionStoreForScope({
|
|
43
|
-
cwd, scopeKey: params.scope.scopeKey, persistent: false,
|
|
43
|
+
cwd, scopeKey: params.scope.persistentScopeKey ?? params.scope.scopeKey, persistent: false,
|
|
44
44
|
})).sessionStore;
|
|
45
45
|
throwIfAborted();
|
|
46
46
|
agent = await suppressCursorSdkOutput(() => sdk.Agent.create({
|
|
@@ -5,6 +5,7 @@ import { fsyncExistingRegularFile } from "./cursor-durable-fs.js";
|
|
|
5
5
|
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
6
6
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
7
7
|
import { cursorSessionScopeKeyForManager } from "./cursor-session-scope.js";
|
|
8
|
+
import { claimCursorLocalAgent } from "./cursor-local-agent-ownership.js";
|
|
8
9
|
import {
|
|
9
10
|
CURSOR_SESSION_AGENT_RESUME_ENTRY_TYPE,
|
|
10
11
|
isCursorLocalAgentId,
|
|
@@ -323,7 +324,11 @@ export async function runCursorSessionAgentCleanupCommand(pi: LocalResumeCleanup
|
|
|
323
324
|
openedStore = await openCursorSessionStore(ctx.cwd, identity, identities.workspaceRoot, identities.storeRoot);
|
|
324
325
|
openedStores.set(identity.stateRoot, openedStore);
|
|
325
326
|
}
|
|
326
|
-
|
|
327
|
+
const release = claimCursorLocalAgent(identity, agentId);
|
|
328
|
+
if (!release) throw new Error("Cursor local agent is still owned by an active runtime");
|
|
329
|
+
try {
|
|
330
|
+
await operations.delete(agentId, { cwd: ctx.cwd, store: openedStore.store });
|
|
331
|
+
} finally { release(); }
|
|
327
332
|
deletedAgentIds.push(agentId);
|
|
328
333
|
} catch (error) {
|
|
329
334
|
failedAgentIds.push({
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
SessionStartEvent,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { clearCursorSdkHttp1 } from "./cursor-http1.js";
|
|
10
|
-
import {
|
|
10
|
+
import { cursorRuntimeSessionScopeKeyForManager, hasLiveCursorSessions, onCursorSessionScopeKeyChange } from "./cursor-session-scope.js";
|
|
11
11
|
import {
|
|
12
12
|
disposeSessionCursorAgent,
|
|
13
13
|
invalidateSessionAgent,
|
|
@@ -25,7 +25,7 @@ export interface CursorSessionAgentLifecycleExtensionApi {
|
|
|
25
25
|
|
|
26
26
|
export function registerCursorSessionAgentLifecycle(pi: CursorSessionAgentLifecycleExtensionApi): void {
|
|
27
27
|
let scopeKey: string | undefined;
|
|
28
|
-
pi.on("session_start", (_event, ctx) => { scopeKey =
|
|
28
|
+
pi.on("session_start", (_event, ctx) => { scopeKey = cursorRuntimeSessionScopeKeyForManager(ctx.sessionManager); });
|
|
29
29
|
onCursorSessionScopeKeyChange(pi, async (previousScopeKey) => {
|
|
30
30
|
await disposeSessionCursorAgent(previousScopeKey);
|
|
31
31
|
});
|
|
@@ -29,6 +29,7 @@ interface CursorSessionAgentLineageState {
|
|
|
29
29
|
sessionId?: string;
|
|
30
30
|
sessionFile?: string;
|
|
31
31
|
scopeKey?: string;
|
|
32
|
+
runtimeScopeKey?: string;
|
|
32
33
|
cwd?: string;
|
|
33
34
|
recordedAgentIds: Set<string>;
|
|
34
35
|
}
|
|
@@ -82,7 +83,7 @@ function readRecordedAgentIds(entries: readonly SessionEntry[], sessionId: strin
|
|
|
82
83
|
export function recordCursorSessionAgentLineage(agentId: string, turnScopeKey?: string): void {
|
|
83
84
|
const sessionState = turnScopeKey === undefined
|
|
84
85
|
? state
|
|
85
|
-
: statesByScope.get(turnScopeKey) ?? (state.scopeKey === turnScopeKey ? state : undefined);
|
|
86
|
+
: statesByScope.get(turnScopeKey) ?? ((state.runtimeScopeKey ?? state.scopeKey) === turnScopeKey ? state : undefined);
|
|
86
87
|
if (!sessionState) return;
|
|
87
88
|
const { appendEntry, sessionId, sessionFile, scopeKey, cwd } = sessionState;
|
|
88
89
|
if (!appendEntry || !sessionId || !scopeKey || !cwd) return;
|
|
@@ -115,22 +116,25 @@ export function registerCursorSessionAgentLineage(pi: CursorSessionAgentLineageE
|
|
|
115
116
|
pi.on("session_start", (_event, ctx) => {
|
|
116
117
|
state = sessionState;
|
|
117
118
|
const record = sessionState;
|
|
118
|
-
if (record.
|
|
119
|
+
if (record.runtimeScopeKey && statesByScope.get(record.runtimeScopeKey) === record) statesByScope.delete(record.runtimeScopeKey);
|
|
119
120
|
record.appendEntry = pi.appendEntry;
|
|
120
121
|
record.sessionId = ctx.sessionManager.getSessionId();
|
|
121
122
|
record.sessionFile = ctx.sessionManager.getSessionFile() ?? undefined;
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
const scope = getCursorSessionScopeSnapshot(pi);
|
|
124
|
+
record.runtimeScopeKey = scope.scopeKey;
|
|
125
|
+
record.scopeKey = scope.persistentScopeKey ?? scope.scopeKey;
|
|
126
|
+
statesByScope.set(scope.scopeKey, record);
|
|
124
127
|
record.cwd = ctx.cwd;
|
|
125
128
|
record.recordedAgentIds = readRecordedAgentIds(ctx.sessionManager.getEntries(), record.sessionId);
|
|
126
129
|
});
|
|
127
130
|
pi.on("session_shutdown", () => {
|
|
128
131
|
const state = sessionState;
|
|
129
|
-
if (state.
|
|
132
|
+
if (state.runtimeScopeKey && statesByScope.get(state.runtimeScopeKey) === state) statesByScope.delete(state.runtimeScopeKey);
|
|
130
133
|
state.appendEntry = undefined;
|
|
131
134
|
state.sessionId = undefined;
|
|
132
135
|
state.sessionFile = undefined;
|
|
133
136
|
state.scopeKey = undefined;
|
|
137
|
+
state.runtimeScopeKey = undefined;
|
|
134
138
|
state.cwd = undefined;
|
|
135
139
|
state.recordedAgentIds = new Set();
|
|
136
140
|
});
|
|
@@ -142,6 +146,7 @@ function resetStateForTests(): void {
|
|
|
142
146
|
state.sessionId = undefined;
|
|
143
147
|
state.sessionFile = undefined;
|
|
144
148
|
state.scopeKey = undefined;
|
|
149
|
+
state.runtimeScopeKey = undefined;
|
|
145
150
|
state.cwd = undefined;
|
|
146
151
|
state.recordedAgentIds = new Set();
|
|
147
152
|
}
|
|
@@ -60,6 +60,7 @@ interface PendingCursorSessionAgentResumeHandle {
|
|
|
60
60
|
|
|
61
61
|
interface CursorSessionResumeState {
|
|
62
62
|
appendEntry?: ExtensionAPI["appendEntry"];
|
|
63
|
+
runtimeScopeKey?: string;
|
|
63
64
|
scopeKey: string;
|
|
64
65
|
sessionFile?: string;
|
|
65
66
|
sessionId?: string;
|
|
@@ -355,7 +356,7 @@ function restoreFromBranch(branch: readonly SessionEntry[], allEntries: readonly
|
|
|
355
356
|
|
|
356
357
|
export function getMatchingCursorSessionAgentResumeHandle(poolKey: string, scopeKey?: string): CursorSessionAgentResumeEntryData | undefined {
|
|
357
358
|
const state = getResumeState(scopeKey);
|
|
358
|
-
if (scopeKey !== undefined && state.scopeKey !== scopeKey) return undefined;
|
|
359
|
+
if (scopeKey !== undefined && (state.runtimeScopeKey ?? state.scopeKey) !== scopeKey) return undefined;
|
|
359
360
|
const handle = state.activeHandle;
|
|
360
361
|
if (!handle || !isCursorLocalAgentId(handle.agentId)) return undefined;
|
|
361
362
|
if (handle.poolKey !== poolKey) return undefined;
|
|
@@ -373,7 +374,7 @@ export function getMatchingCursorSessionAgentResumeHandle(poolKey: string, scope
|
|
|
373
374
|
|
|
374
375
|
export function persistCursorSessionAgentResumeHandle(input: PendingCursorSessionAgentResumeHandle, scopeKey?: string): void {
|
|
375
376
|
const state = getResumeState(scopeKey);
|
|
376
|
-
if (scopeKey !== undefined && state.scopeKey !== scopeKey) return;
|
|
377
|
+
if (scopeKey !== undefined && (state.runtimeScopeKey ?? state.scopeKey) !== scopeKey) return;
|
|
377
378
|
if (!isCursorLocalAgentId(input.agentId)) return;
|
|
378
379
|
state.pendingHandle = {
|
|
379
380
|
runtime: input.runtime,
|
|
@@ -427,8 +428,9 @@ interface CursorSessionAgentResumeExtensionApi {
|
|
|
427
428
|
}
|
|
428
429
|
|
|
429
430
|
export function registerCursorSessionAgentResume(pi: CursorSessionAgentResumeExtensionApi): void {
|
|
431
|
+
const scope = getCursorSessionScopeSnapshot(pi);
|
|
430
432
|
const sessionState: CursorSessionResumeState = {
|
|
431
|
-
appendEntry: pi.appendEntry, scopeKey:
|
|
433
|
+
appendEntry: pi.appendEntry, scopeKey: scope.persistentScopeKey ?? scope.scopeKey,
|
|
432
434
|
cwd: process.cwd(), branchPathHash: EMPTY_BRANCH_HASH,
|
|
433
435
|
compactionGeneration: 0, unownedUserEntryIds: new Set(),
|
|
434
436
|
};
|
|
@@ -439,9 +441,12 @@ export function registerCursorSessionAgentResume(pi: CursorSessionAgentResumeExt
|
|
|
439
441
|
restoreFromBranch(branch, entries.length > 0 ? entries : branch, state);
|
|
440
442
|
};
|
|
441
443
|
pi.on("session_start", (_event, ctx) => {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
444
|
+
const key = state.runtimeScopeKey ?? state.scopeKey;
|
|
445
|
+
if (statesByScope.get(key) === state) statesByScope.delete(key);
|
|
446
|
+
const scope = getCursorSessionScopeSnapshot(pi);
|
|
447
|
+
state.runtimeScopeKey = scope.scopeKey;
|
|
448
|
+
state.scopeKey = scope.persistentScopeKey ?? scope.scopeKey;
|
|
449
|
+
statesByScope.set(scope.scopeKey, state);
|
|
445
450
|
lastBoundState = state;
|
|
446
451
|
state.sessionFile = ctx.sessionManager.getSessionFile?.() ?? undefined;
|
|
447
452
|
state.sessionId = ctx.sessionManager.getSessionId?.() ?? undefined;
|
|
@@ -482,7 +487,8 @@ export function registerCursorSessionAgentResume(pi: CursorSessionAgentResumeExt
|
|
|
482
487
|
state.branchPathHash = hashBranchStep(state.branchPathHash, event.compactionEntry);
|
|
483
488
|
});
|
|
484
489
|
pi.on("session_shutdown", () => {
|
|
485
|
-
|
|
490
|
+
const key = state.runtimeScopeKey ?? state.scopeKey;
|
|
491
|
+
if (statesByScope.get(key) === state) statesByScope.delete(key);
|
|
486
492
|
state.appendEntry = undefined;
|
|
487
493
|
state.pendingHandle = undefined;
|
|
488
494
|
});
|
|
@@ -496,6 +502,7 @@ function resetStateForTests(): void {
|
|
|
496
502
|
const state = lastBoundState;
|
|
497
503
|
state.appendEntry = undefined;
|
|
498
504
|
state.scopeKey = getCursorSessionScopeKey();
|
|
505
|
+
state.runtimeScopeKey = undefined;
|
|
499
506
|
state.sessionFile = undefined;
|
|
500
507
|
state.sessionId = undefined;
|
|
501
508
|
state.cwd = process.cwd();
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import type { AgentModeOption, AgentOptions, LocalAgentOptions, LocalAgentStore, ModelSelection, SDKAgent, SettingSource } from "@cursor/sdk";
|
|
3
3
|
import type { Context } from "@earendil-works/pi-ai";
|
|
4
4
|
import { normalizeCursorCustomStoreRoot } from "./cursor-custom-store-path.js";
|
|
5
|
+
import { claimCursorLocalAgent } from "./cursor-local-agent-ownership.js";
|
|
5
6
|
import {
|
|
6
7
|
getRegisteredCursorPiToolBridge,
|
|
7
8
|
type CursorPiToolBridge,
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
type CursorPiToolBridgeRun,
|
|
10
11
|
} from "./cursor-pi-tool-bridge.js";
|
|
11
12
|
import { computeCursorContextFingerprint } from "./context.js";
|
|
12
|
-
import {
|
|
13
|
+
import { getCursorSessionScopeSnapshot, getCursorSessionScopeGeneration, getCursorSessionScopeKey, type CursorTurnScope } from "./cursor-session-scope.js";
|
|
13
14
|
import {
|
|
14
15
|
getMatchingCursorSessionAgentResumeHandle,
|
|
15
16
|
persistCursorSessionAgentResumeHandle,
|
|
@@ -68,6 +69,7 @@ interface SessionCursorAgentReadyEntry extends SessionCursorAgentPoolEntryBase {
|
|
|
68
69
|
resumeEnabled: boolean;
|
|
69
70
|
resumed: boolean;
|
|
70
71
|
resumeNotice?: string;
|
|
72
|
+
releaseAgentClaim: () => void;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
interface SessionCursorAgentBusyEntry extends SessionCursorAgentPoolEntryBase {
|
|
@@ -78,6 +80,7 @@ interface SessionCursorAgentBusyEntry extends SessionCursorAgentPoolEntryBase {
|
|
|
78
80
|
resumeEnabled: boolean;
|
|
79
81
|
resumed: boolean;
|
|
80
82
|
resumeNotice?: string;
|
|
83
|
+
releaseAgentClaim: () => void;
|
|
81
84
|
completionSettled: Promise<void>;
|
|
82
85
|
pendingCompletion: Promise<void>;
|
|
83
86
|
releaseBusyWait: () => void;
|
|
@@ -238,7 +241,7 @@ function buildCustomSubagentPoolKeySuffix(agents: AgentOptions["agents"]): strin
|
|
|
238
241
|
|
|
239
242
|
function buildSessionAgentPoolKey(scopeKey: string, params: SessionCursorAgentCreateParams): string {
|
|
240
243
|
return [
|
|
241
|
-
scopeKey,
|
|
244
|
+
params.scope?.persistentScopeKey ?? scopeKey,
|
|
242
245
|
params.cwd,
|
|
243
246
|
buildModelPoolKey(params.modelSelection),
|
|
244
247
|
buildSettingSourcesPoolKey(params.settingSources),
|
|
@@ -262,8 +265,9 @@ async function disposePoolEntry(entry: SessionCursorAgentPoolEntry, options?: {
|
|
|
262
265
|
} catch {
|
|
263
266
|
// disposal failure should not block session replacement
|
|
264
267
|
}
|
|
268
|
+
let disposal: Promise<unknown> | undefined;
|
|
265
269
|
try {
|
|
266
|
-
|
|
270
|
+
disposal = Promise.resolve(entry.agent[Symbol.asyncDispose]());
|
|
267
271
|
// A dead local transport may never settle SDK disposal; bound the wait so the
|
|
268
272
|
// next acquire recreates instead of hanging on the dead agent.
|
|
269
273
|
await (options?.deadTransport
|
|
@@ -276,6 +280,8 @@ async function disposePoolEntry(entry: SessionCursorAgentPoolEntry, options?: {
|
|
|
276
280
|
// disposal failure should not block session replacement
|
|
277
281
|
}
|
|
278
282
|
await entry.sessionStore.dispose().catch(() => undefined);
|
|
283
|
+
// Timeout/rejection is not proof of closure; only successful SDK disposal releases ownership.
|
|
284
|
+
void disposal?.then(entry.releaseAgentClaim, () => {});
|
|
279
285
|
}
|
|
280
286
|
|
|
281
287
|
async function disposePoolEntryForScope(scopeKey: string, options?: { terminal?: boolean }): Promise<void> {
|
|
@@ -470,6 +476,7 @@ async function createSessionAgentEntry(
|
|
|
470
476
|
): Promise<SessionCursorAgentReadyEntry> {
|
|
471
477
|
let bridgeRun: CursorPiToolBridgeRun | undefined;
|
|
472
478
|
let sessionStore: OpenCursorSessionStore | undefined;
|
|
479
|
+
let releaseAgentClaim: (() => void) | undefined;
|
|
473
480
|
try {
|
|
474
481
|
const registeredBridge = params.bridge;
|
|
475
482
|
if (registeredBridge) {
|
|
@@ -495,7 +502,7 @@ async function createSessionAgentEntry(
|
|
|
495
502
|
const resumeHandle = resumeEligible ? getMatchingCursorSessionAgentResumeHandle(resolvedPoolKey, scopeKey) : undefined;
|
|
496
503
|
const storeSelection = await openCursorSessionStoreForScope({
|
|
497
504
|
cwd: params.cwd,
|
|
498
|
-
scopeKey,
|
|
505
|
+
scopeKey: params.scope?.persistentScopeKey ?? scopeKey,
|
|
499
506
|
persistent: persistentStore,
|
|
500
507
|
storeRoot: params.storeRoot,
|
|
501
508
|
resume: resumeHandle ? { identity: resumeHandle.storeIdentity, agentId: resumeHandle.agentId } : undefined,
|
|
@@ -520,11 +527,16 @@ async function createSessionAgentEntry(
|
|
|
520
527
|
let effectiveSendState = sendState;
|
|
521
528
|
let resumed = false;
|
|
522
529
|
if (resumeHandle && resumeAttemptAllowed && resumeAgent) {
|
|
530
|
+
releaseAgentClaim = claimCursorLocalAgent(sessionStore.identity, resumeHandle.agentId);
|
|
531
|
+
}
|
|
532
|
+
if (resumeHandle && resumeAttemptAllowed && resumeAgent && releaseAgentClaim) {
|
|
523
533
|
try {
|
|
524
534
|
agent = await resumeAgent(resumeHandle.agentId, buildAgentOptions());
|
|
525
535
|
effectiveSendState = { ...resumeHandle.sendState };
|
|
526
536
|
resumed = true;
|
|
527
537
|
} catch {
|
|
538
|
+
releaseAgentClaim();
|
|
539
|
+
releaseAgentClaim = undefined;
|
|
528
540
|
if (persistentStore) resumeNotice = LOCAL_RESUME_FALLBACK_NOTICE;
|
|
529
541
|
if (storeSelection.persistent && !cursorSessionStoreIdentitiesEqual(sessionStore.identity, storeSelection.identities.sessionStore)) {
|
|
530
542
|
const { identities } = storeSelection;
|
|
@@ -537,6 +549,8 @@ async function createSessionAgentEntry(
|
|
|
537
549
|
agent ??= await createAgent(buildAgentOptions());
|
|
538
550
|
if (!agent) throw new Error("Cursor SDK agent creation returned no agent");
|
|
539
551
|
if (!sessionStore) throw new Error("Cursor SDK session store was not opened");
|
|
552
|
+
releaseAgentClaim ??= claimCursorLocalAgent(sessionStore.identity, agent.agentId);
|
|
553
|
+
if (!releaseAgentClaim) throw new Error("Cursor SDK agent is already owned by another runtime");
|
|
540
554
|
|
|
541
555
|
return {
|
|
542
556
|
status: "ready",
|
|
@@ -550,12 +564,14 @@ async function createSessionAgentEntry(
|
|
|
550
564
|
sendState: effectiveSendState,
|
|
551
565
|
resumeEnabled: params.localResume === true,
|
|
552
566
|
resumed,
|
|
567
|
+
releaseAgentClaim,
|
|
553
568
|
...(resumeNotice ? { resumeNotice } : {}),
|
|
554
569
|
};
|
|
555
570
|
} catch (error) {
|
|
556
571
|
bridgeRun?.cancel("Cursor session agent create failed");
|
|
557
572
|
await bridgeRun?.dispose().catch(() => undefined);
|
|
558
573
|
await sessionStore?.dispose().catch(() => undefined);
|
|
574
|
+
releaseAgentClaim?.();
|
|
559
575
|
throw error;
|
|
560
576
|
}
|
|
561
577
|
}
|
|
@@ -576,9 +592,9 @@ export function invalidateSessionAgent(
|
|
|
576
592
|
}
|
|
577
593
|
|
|
578
594
|
export async function acquireSessionCursorAgent(input: SessionCursorAgentCreateParams): Promise<SessionCursorAgentLease> {
|
|
579
|
-
const params = { ...input, bridge: "bridge" in input ? input.bridge : getRegisteredCursorPiToolBridge() };
|
|
580
|
-
const scopeKey = params.scope
|
|
581
|
-
const persistentStore =
|
|
595
|
+
const params = { ...input, scope: input.scope ?? getCursorSessionScopeSnapshot(), bridge: "bridge" in input ? input.bridge : getRegisteredCursorPiToolBridge() };
|
|
596
|
+
const scopeKey = params.scope.scopeKey;
|
|
597
|
+
const persistentStore = params.scope.sessionFile !== undefined;
|
|
582
598
|
params.storeRoot = persistentStore && params.storeRoot !== undefined ? normalizeCursorCustomStoreRoot(params.storeRoot) : undefined;
|
|
583
599
|
|
|
584
600
|
while (true) {
|
|
@@ -19,6 +19,8 @@ type CursorSessionScopeChangeHandler = (previousScopeKey: string) => Promise<voi
|
|
|
19
19
|
export interface CursorTurnScope {
|
|
20
20
|
readonly cwd: string;
|
|
21
21
|
readonly scopeKey: string;
|
|
22
|
+
/** Durable identity only; never use it to own live runtime resources. */
|
|
23
|
+
readonly persistentScopeKey?: string;
|
|
22
24
|
readonly sessionFile: string | undefined;
|
|
23
25
|
readonly sessionId: string | undefined;
|
|
24
26
|
readonly sessionName: string | undefined;
|
|
@@ -33,6 +35,7 @@ const state = {
|
|
|
33
35
|
sessionName: undefined as string | undefined,
|
|
34
36
|
projectTrusted: false,
|
|
35
37
|
sessionGeneration: 0,
|
|
38
|
+
runtimeOwner: undefined as number | undefined,
|
|
36
39
|
};
|
|
37
40
|
type ScopeRecord = typeof state;
|
|
38
41
|
const scopes = new Map<object, ScopeRecord>();
|
|
@@ -40,20 +43,34 @@ const scopeChangeHandlers = new Map<object, CursorSessionScopeChangeHandler>();
|
|
|
40
43
|
const scopeGenerations = new Map<string, number>([[ANONYMOUS_SESSION_SCOPE_KEY, 0]]);
|
|
41
44
|
const projectTrustResolutionCwds = new Set<string>();
|
|
42
45
|
let nextSessionGeneration = 1;
|
|
46
|
+
let nextRuntimeOwner = 1;
|
|
47
|
+
let runtimeOwners = new WeakMap<object, number>();
|
|
48
|
+
|
|
49
|
+
function persistentScopeKeyFor(record: Pick<ScopeRecord, "sessionFile" | "sessionId">): string {
|
|
50
|
+
return record.sessionFile ?? (record.sessionId ? `${EPHEMERAL_SESSION_SCOPE_PREFIX}${record.sessionId}` : ANONYMOUS_SESSION_SCOPE_KEY);
|
|
51
|
+
}
|
|
43
52
|
|
|
44
53
|
function scopeKeyFor(record: ScopeRecord): string {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return ANONYMOUS_SESSION_SCOPE_KEY;
|
|
54
|
+
const persistent = persistentScopeKeyFor(record);
|
|
55
|
+
return record.runtimeOwner === undefined ? persistent : `${persistent}\0runtime:${record.runtimeOwner}`;
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
export function cursorSessionScopeKeyForManager(manager: {
|
|
51
59
|
getSessionFile?(): string | undefined | null;
|
|
52
60
|
getSessionId?(): string | undefined;
|
|
53
61
|
}): string {
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
return persistentScopeKeyFor({
|
|
63
|
+
sessionFile: manager.getSessionFile?.() ?? undefined, sessionId: manager.getSessionId?.(),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function cursorRuntimeSessionScopeKeyForManager(manager: {
|
|
68
|
+
getSessionFile?(): string | undefined | null;
|
|
69
|
+
getSessionId?(): string | undefined;
|
|
70
|
+
}): string {
|
|
71
|
+
const persistent = cursorSessionScopeKeyForManager(manager);
|
|
72
|
+
const owner = runtimeOwners.get(manager);
|
|
73
|
+
return owner === undefined ? persistent : `${persistent}\0runtime:${owner}`;
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
export function getCursorSessionScopeSnapshot(pi?: object): CursorTurnScope {
|
|
@@ -64,6 +81,7 @@ export function getCursorSessionScopeSnapshot(pi?: object): CursorTurnScope {
|
|
|
64
81
|
return Object.freeze({
|
|
65
82
|
cwd: record.sessionCwd,
|
|
66
83
|
scopeKey,
|
|
84
|
+
persistentScopeKey: persistentScopeKeyFor(record),
|
|
67
85
|
sessionFile: record.sessionFile,
|
|
68
86
|
sessionId: record.sessionId,
|
|
69
87
|
sessionName: record.sessionName,
|
|
@@ -76,11 +94,7 @@ export function hasLiveCursorSessions(): boolean {
|
|
|
76
94
|
return scopes.size > 0;
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
|
|
80
|
-
return state.sessionFile;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Stable pool key, with process-local anonymous fallback before session_start. */
|
|
97
|
+
/** Runtime ownership key; persisted identities are captured separately. */
|
|
84
98
|
export function getCursorSessionScopeKey(): string {
|
|
85
99
|
return scopeKeyFor(state);
|
|
86
100
|
}
|
|
@@ -112,6 +126,7 @@ function setCursorSessionScope(
|
|
|
112
126
|
sessionId?: string,
|
|
113
127
|
projectTrusted = false,
|
|
114
128
|
sessionName?: string,
|
|
129
|
+
runtimeOwner?: number,
|
|
115
130
|
): void {
|
|
116
131
|
state.sessionCwd = cwd;
|
|
117
132
|
state.sessionFile = sessionFile;
|
|
@@ -119,6 +134,7 @@ function setCursorSessionScope(
|
|
|
119
134
|
state.sessionName = normalizeCursorSessionName(sessionName);
|
|
120
135
|
state.projectTrusted = projectTrusted;
|
|
121
136
|
state.sessionGeneration = nextSessionGeneration++;
|
|
137
|
+
state.runtimeOwner = runtimeOwner;
|
|
122
138
|
scopeGenerations.set(scopeKeyFor(state), state.sessionGeneration);
|
|
123
139
|
}
|
|
124
140
|
|
|
@@ -134,6 +150,7 @@ function resetCursorSessionScope(): void {
|
|
|
134
150
|
Object.assign(state, {
|
|
135
151
|
sessionCwd: process.cwd(), sessionFile: undefined, sessionId: undefined,
|
|
136
152
|
sessionName: undefined, projectTrusted: false, sessionGeneration: 0,
|
|
153
|
+
runtimeOwner: undefined,
|
|
137
154
|
});
|
|
138
155
|
nextSessionGeneration = 1;
|
|
139
156
|
scopeGenerations.clear();
|
|
@@ -141,6 +158,8 @@ function resetCursorSessionScope(): void {
|
|
|
141
158
|
projectTrustResolutionCwds.clear();
|
|
142
159
|
scopes.clear();
|
|
143
160
|
scopeChangeHandlers.clear();
|
|
161
|
+
runtimeOwners = new WeakMap();
|
|
162
|
+
nextRuntimeOwner = 1;
|
|
144
163
|
}
|
|
145
164
|
|
|
146
165
|
export function onCursorSessionScopeKeyChange(pi: object, handler: CursorSessionScopeChangeHandler): void {
|
|
@@ -152,6 +171,7 @@ export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi):
|
|
|
152
171
|
const record: ScopeRecord = {
|
|
153
172
|
sessionCwd: process.cwd(), sessionFile: undefined, sessionId: undefined,
|
|
154
173
|
sessionName: undefined, projectTrusted: false, sessionGeneration: 0,
|
|
174
|
+
runtimeOwner: undefined,
|
|
155
175
|
};
|
|
156
176
|
scopes.set(pi, record);
|
|
157
177
|
const trustedCwds = new Set<string>();
|
|
@@ -162,6 +182,7 @@ export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi):
|
|
|
162
182
|
pi.on("session_start", async (_event, ctx) => {
|
|
163
183
|
const previousScopeKey = scopeKeyFor(record);
|
|
164
184
|
const wasBound = record.sessionGeneration > 0;
|
|
185
|
+
if (!runtimeOwners.has(ctx.sessionManager)) runtimeOwners.set(ctx.sessionManager, nextRuntimeOwner++);
|
|
165
186
|
setCursorSessionScope(
|
|
166
187
|
ctx.cwd,
|
|
167
188
|
ctx.sessionManager?.getSessionFile?.() ?? undefined,
|
|
@@ -169,6 +190,7 @@ export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi):
|
|
|
169
190
|
ctx.isProjectTrusted?.() === true
|
|
170
191
|
&& (trustedCwds.has(resolve(ctx.cwd)) || projectTrustResolutionCwds.has(resolve(ctx.cwd)) || isCliProjectTrustApproved()),
|
|
171
192
|
ctx.sessionManager?.getSessionName?.() ?? undefined,
|
|
193
|
+
runtimeOwners.get(ctx.sessionManager),
|
|
172
194
|
);
|
|
173
195
|
Object.assign(record, state);
|
|
174
196
|
scopes.set(pi, record);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentModeOption } from "@cursor/sdk";
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import type { CursorExplicitSdkConfig, CursorRuntime } from "./cursor-config.js";
|
|
4
|
-
import {
|
|
4
|
+
import { cursorRuntimeSessionScopeKeyForManager, getCursorSessionProjectTrusted } from "./cursor-session-scope.js";
|
|
5
5
|
|
|
6
6
|
export type CursorCliModeState =
|
|
7
7
|
| { kind: "unset" }
|
|
@@ -45,7 +45,7 @@ export function getCursorSessionSettings(scopeKey?: string) {
|
|
|
45
45
|
|
|
46
46
|
export function cursorSettingsScopeForContext(ctx: Partial<Pick<ExtensionContext, "sessionManager">>): string | undefined {
|
|
47
47
|
const manager = ctx.sessionManager;
|
|
48
|
-
return manager ?
|
|
48
|
+
return manager ? cursorRuntimeSessionScopeKeyForManager(manager) : undefined;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function registerCursorSessionSettings(pi: Pick<ExtensionAPI, "on">): void {
|
|
@@ -53,7 +53,7 @@ export function registerCursorSessionSettings(pi: Pick<ExtensionAPI, "on">): voi
|
|
|
53
53
|
let cli = createCliSettings();
|
|
54
54
|
pi.on("session_start", (_event, ctx) => {
|
|
55
55
|
if (scopeKey) settings.delete(scopeKey);
|
|
56
|
-
scopeKey =
|
|
56
|
+
scopeKey = cursorRuntimeSessionScopeKeyForManager(ctx.sessionManager);
|
|
57
57
|
legacySettings = getCursorSessionSettings(scopeKey);
|
|
58
58
|
// Reload creates a new ExtensionAPI but retains the same public manager.
|
|
59
59
|
// Keep the run's one-shot CLI consumption without retaining dead managers.
|
|
@@ -194,7 +194,7 @@ function workspaceHashes(cwd: string): { current: string; legacy: string | undef
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
function assertSafeWorkspaceLayout(cwd: string): void {
|
|
197
|
-
// ponytail: SDK 1.0.
|
|
197
|
+
// ponytail: SDK 1.0.37 has no public read-only root resolver; use one when exposed.
|
|
198
198
|
// This pure layout is contract-verified against its factory and public getter;
|
|
199
199
|
// guard it before that getter can rename MD5 history through an owned link.
|
|
200
200
|
const slug = cwd.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
package/src/cursor-state.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentModeOption } from "@cursor/sdk";
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
CURSOR_HOST_TOOL_MANIFEST_SUMMARY,
|
|
5
5
|
CURSOR_TOOL_MANIFEST_ENV,
|
|
6
6
|
resolveCursorToolManifestEnabled,
|
|
7
7
|
} from "./cursor-tool-manifest.js";
|
|
@@ -17,8 +17,10 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
buildCursorPiToolBridgeSnapshot,
|
|
19
19
|
CURSOR_PI_TOOL_BRIDGE_ENV,
|
|
20
|
+
resolveCursorPiToolBridgeBuiltinsEnabled,
|
|
20
21
|
resolveCursorPiToolBridgeEnabled,
|
|
21
22
|
} from "./cursor-pi-tool-bridge-snapshot.js";
|
|
23
|
+
import { CURSOR_PI_TOOL_BRIDGE_DEBUG_ENV } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
22
24
|
import {
|
|
23
25
|
CURSOR_SETTING_SOURCES_ENV,
|
|
24
26
|
DEFAULT_CURSOR_SETTING_SOURCES,
|
|
@@ -388,7 +390,7 @@ export function formatCursorToolsDebugReport(
|
|
|
388
390
|
const bridgeEnabled = resolveCursorPiToolBridgeEnabled(env);
|
|
389
391
|
const manifestEnabled = resolveCursorToolManifestEnabled(env);
|
|
390
392
|
const lines = [
|
|
391
|
-
"Cursor tool surfaces (current
|
|
393
|
+
"Cursor tool surfaces (current registry snapshot; not a live run):",
|
|
392
394
|
`${CURSOR_PI_TOOL_BRIDGE_ENV}: ${bridgeEnabled ? "enabled" : "disabled"}`,
|
|
393
395
|
`${CURSOR_TOOL_MANIFEST_ENV}: ${manifestEnabled ? "enabled" : "disabled"}`,
|
|
394
396
|
`${CURSOR_SETTING_SOURCES_ENV}: ${formatEffectiveCursorSettingSourcesLabel(env[CURSOR_SETTING_SOURCES_ENV])}`,
|
|
@@ -397,13 +399,31 @@ export function formatCursorToolsDebugReport(
|
|
|
397
399
|
let bridgeSnapshot;
|
|
398
400
|
if (bridgeEnabled) {
|
|
399
401
|
try {
|
|
400
|
-
bridgeSnapshot = buildCursorPiToolBridgeSnapshot(pi
|
|
402
|
+
bridgeSnapshot = buildCursorPiToolBridgeSnapshot(pi, {
|
|
403
|
+
exposeOverlappingBuiltins: resolveCursorPiToolBridgeBuiltinsEnabled(env),
|
|
404
|
+
});
|
|
401
405
|
} catch {
|
|
402
406
|
lines.push("Pi bridge snapshot: unavailable (extension tool APIs required).");
|
|
403
407
|
}
|
|
404
408
|
}
|
|
405
409
|
|
|
406
|
-
lines.push(
|
|
410
|
+
lines.push(
|
|
411
|
+
`Cursor host/MCP: ${CURSOR_HOST_TOOL_MANIFEST_SUMMARY}; configured MCP depends on Cursor settings.`,
|
|
412
|
+
"Pi tool toggles affect pi tools/bridge exposure only; they do not disable Cursor host/configured MCP tools.",
|
|
413
|
+
);
|
|
414
|
+
if (!bridgeEnabled) {
|
|
415
|
+
lines.push("Pi bridge: disabled (PI_CURSOR_PI_TOOL_BRIDGE=0).");
|
|
416
|
+
} else if (bridgeSnapshot) {
|
|
417
|
+
const names = bridgeSnapshot.tools.map((tool) => tool.mcpToolName).sort();
|
|
418
|
+
lines.push(`Pi bridge exposure eligibility: ${names.length ? names.join(", ") : "no eligible pi__* tools"}.`);
|
|
419
|
+
}
|
|
420
|
+
lines.push(
|
|
421
|
+
"This snapshot does not prove a run's endpoint attachment, MCP initialization, catalog fetch, or tool calls.",
|
|
422
|
+
`For per-run observations, enable ${CURSOR_PI_TOOL_BRIDGE_DEBUG_ENV}=1 before the turn.`,
|
|
423
|
+
"run_created/tools_exposed record exposure only; mcp_initialized records a validated notifications/initialized receipt; mcp_tools_list records catalog-handler output (toolCount), not client/model consumption or ongoing connection health.",
|
|
424
|
+
"request_queued/resolved/rejected record actual bridge call handling; absent receipts do not diagnose why a client/model made no request.",
|
|
425
|
+
"Not callable: cursor-replay-* IDs, pi history names, transcript labels.",
|
|
426
|
+
);
|
|
407
427
|
return lines.join("\n");
|
|
408
428
|
}
|
|
409
429
|
|
|
@@ -496,7 +516,7 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
496
516
|
});
|
|
497
517
|
|
|
498
518
|
pi.registerCommand("cursor-tools", {
|
|
499
|
-
description: "Show
|
|
519
|
+
description: "Show current-registry Cursor tool exposure eligibility (maintainer debug)",
|
|
500
520
|
handler: async (_args, ctx) => {
|
|
501
521
|
emitCursorToolsDebugReport(pi, ctx);
|
|
502
522
|
},
|