pi-cursor-sdk 0.1.37 → 0.1.38
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 +27 -0
- package/docs/cursor-native-tool-replay.md +3 -3
- package/package.json +1 -1
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/src/context-window-cache.ts +10 -14
- package/src/context.ts +1 -1
- package/src/cursor-agent-message-web-tools.ts +2 -1
- package/src/cursor-agents-context-registration.ts +18 -0
- package/src/cursor-agents-context.ts +21 -30
- package/src/cursor-edit-diff.ts +4 -2
- package/src/cursor-fallback-warning.ts +22 -0
- package/src/cursor-incomplete-tool-visibility.ts +5 -10
- package/src/cursor-live-run-coordinator.ts +1 -1
- package/src/cursor-mcp-timeout-override.ts +0 -2
- package/src/cursor-model-lifecycle.ts +72 -0
- package/src/cursor-native-replay-routing.ts +1 -1
- package/src/cursor-native-replay-trace.ts +1 -1
- package/src/cursor-native-tool-display-registration.ts +16 -28
- package/src/cursor-native-tool-display-replay.ts +4 -21
- package/src/cursor-native-tool-display-state.ts +1 -1
- package/src/cursor-native-tool-display-tools.ts +10 -17
- package/src/cursor-native-tool-names.ts +16 -0
- package/src/cursor-pi-tool-bridge-env.ts +12 -0
- package/src/cursor-pi-tool-bridge-mcp.ts +16 -21
- package/src/cursor-pi-tool-bridge-run.ts +5 -5
- package/src/cursor-pi-tool-bridge-server.ts +8 -3
- package/src/cursor-pi-tool-bridge-snapshot.ts +7 -13
- package/src/cursor-pi-tool-bridge.ts +7 -7
- package/src/cursor-provider-lazy.ts +51 -0
- package/src/cursor-provider-live-run-drain.ts +1 -1
- package/src/cursor-provider-run-finalizer.ts +5 -5
- package/src/cursor-provider-run-outcome.ts +0 -1
- package/src/cursor-provider-turn-coordinator.ts +4 -5
- package/src/cursor-provider-turn-display-router.ts +5 -1
- package/src/cursor-provider-turn-emit.ts +1 -1
- package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -5
- package/src/cursor-provider-turn-prepare.ts +13 -9
- package/src/cursor-provider-turn-runner.ts +3 -11
- package/src/cursor-provider-turn-sdk-normalizer.ts +28 -5
- package/src/cursor-provider-turn-send.ts +7 -2
- package/src/cursor-provider-turn-types.ts +1 -3
- package/src/cursor-provider.ts +3 -2
- package/src/cursor-question-tool.ts +5 -18
- package/src/cursor-record-utils.ts +42 -0
- package/src/cursor-replay-activity-builders.ts +16 -122
- package/src/cursor-replay-tool-details.ts +52 -80
- package/src/cursor-sdk-event-debug.ts +6 -6
- package/src/cursor-sensitive-text.ts +4 -4
- package/src/cursor-session-agent-lifecycle.ts +47 -0
- package/src/cursor-session-agent.ts +9 -47
- package/src/cursor-session-scope.ts +23 -4
- package/src/cursor-setting-sources.ts +8 -8
- package/src/cursor-skill-tool.ts +25 -32
- package/src/cursor-state.ts +66 -45
- package/src/cursor-tool-lifecycle.ts +16 -9
- package/src/cursor-tool-presentation-registry.ts +27 -18
- package/src/cursor-tool-result-display-readers.ts +185 -0
- package/src/cursor-tool-transcript.ts +17 -33
- package/src/cursor-tool-visibility.ts +9 -1
- package/src/cursor-transcript-tool-formatters.ts +23 -172
- package/src/cursor-transcript-tool-specs.ts +16 -41
- package/src/cursor-transcript-utils.ts +2 -34
- package/src/cursor-usage-accounting.ts +0 -6
- package/src/cursor-web-tool-activity.ts +4 -12
- package/src/cursor-web-tool-args.ts +1 -9
- package/src/index.ts +15 -16
- package/src/model-discovery.ts +5 -4
- package/src/model-list-cache.ts +37 -38
- package/src/cursor-native-tool-display.ts +0 -10
- package/src/cursor-provider-turn-api-key.ts +0 -1
- package/src/cursor-provider-turn-message-offset.ts +0 -15
- package/src/cursor-session-cwd.ts +0 -28
- package/src/cursor-tool-names.ts +0 -9
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionHandler,
|
|
3
|
-
SessionBeforeTreeEvent,
|
|
4
|
-
SessionCompactEvent,
|
|
5
|
-
SessionShutdownEvent,
|
|
6
|
-
SessionTreeEvent,
|
|
7
|
-
} from "@earendil-works/pi-coding-agent";
|
|
8
1
|
import { createHash } from "node:crypto";
|
|
9
2
|
import type { AgentModeOption, ModelSelection, SDKAgent, SettingSource } from "@cursor/sdk";
|
|
10
3
|
import type { Context } from "@earendil-works/pi-ai";
|
|
@@ -14,7 +7,7 @@ import {
|
|
|
14
7
|
type CursorPiToolBridgeRun,
|
|
15
8
|
} from "./cursor-pi-tool-bridge.js";
|
|
16
9
|
import { computeCursorContextFingerprint } from "./context.js";
|
|
17
|
-
import {
|
|
10
|
+
import { getCursorSessionScopeGeneration, getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
18
11
|
import type { CursorSdkEventDebugRecorder } from "./cursor-sdk-event-debug.js";
|
|
19
12
|
import { loadCursorSdk, type CursorSdkModule } from "./cursor-sdk-runtime.js";
|
|
20
13
|
|
|
@@ -88,9 +81,12 @@ export class SessionCursorAgentScopeClosedError extends Error {
|
|
|
88
81
|
}
|
|
89
82
|
|
|
90
83
|
function assertScopeAcceptsAcquire(scopeKey: string): void {
|
|
91
|
-
|
|
84
|
+
const terminalGeneration = terminalDisposedScopeGenerations.get(scopeKey);
|
|
85
|
+
if (terminalGeneration === undefined) return;
|
|
86
|
+
if (terminalGeneration >= getCursorSessionScopeGeneration(scopeKey)) {
|
|
92
87
|
throw new SessionCursorAgentScopeClosedError();
|
|
93
88
|
}
|
|
89
|
+
terminalDisposedScopeGenerations.delete(scopeKey);
|
|
94
90
|
}
|
|
95
91
|
|
|
96
92
|
function rethrowSupersededWhenReplacedByDifferentPoolKey(scopeKey: string, poolKey: string, error: unknown): void {
|
|
@@ -112,17 +108,9 @@ interface SessionCursorAgentCreateParams {
|
|
|
112
108
|
createAgent?: CursorSdkModule["Agent"]["create"];
|
|
113
109
|
}
|
|
114
110
|
|
|
115
|
-
interface CursorSessionAgentExtensionApi {
|
|
116
|
-
on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;
|
|
117
|
-
on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): void;
|
|
118
|
-
on(event: "session_before_tree", handler: ExtensionHandler<SessionBeforeTreeEvent>): void;
|
|
119
|
-
on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>): void;
|
|
120
|
-
on(event: "model_select", handler: ExtensionHandler<{ model: unknown }>): void;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
111
|
const sessionAgentsByScope = new Map<string, SessionCursorAgentPoolEntry>();
|
|
124
112
|
const invalidatedScopeKeys = new Set<string>();
|
|
125
|
-
const
|
|
113
|
+
const terminalDisposedScopeGenerations = new Map<string, number>();
|
|
126
114
|
const scopeCreationGenerations = new Map<string, number>();
|
|
127
115
|
const EMPTY_POOL_STATE: SessionCursorAgentPoolState = { status: "empty" };
|
|
128
116
|
let nextSessionAgentInstanceId = 1;
|
|
@@ -194,7 +182,7 @@ async function disposePoolEntry(entry: SessionCursorAgentPoolEntry): Promise<voi
|
|
|
194
182
|
async function disposePoolEntryForScope(scopeKey: string, options?: { terminal?: boolean }): Promise<void> {
|
|
195
183
|
invalidateScopeCreations(scopeKey);
|
|
196
184
|
if (options?.terminal) {
|
|
197
|
-
|
|
185
|
+
terminalDisposedScopeGenerations.set(scopeKey, getCursorSessionScopeGeneration(scopeKey));
|
|
198
186
|
}
|
|
199
187
|
const entry = sessionAgentsByScope.get(scopeKey);
|
|
200
188
|
invalidatedScopeKeys.delete(scopeKey);
|
|
@@ -416,7 +404,6 @@ export {
|
|
|
416
404
|
planCursorSessionSend,
|
|
417
405
|
type CursorSessionSendPlan,
|
|
418
406
|
} from "./cursor-session-send-policy.js";
|
|
419
|
-
export { shouldBootstrapCursorContext, shouldBootstrapCursorSend } from "./context.js";
|
|
420
407
|
|
|
421
408
|
export function invalidateSessionAgent(scopeKey: string = getCursorSessionScopeKey()): void {
|
|
422
409
|
invalidatedScopeKeys.add(scopeKey);
|
|
@@ -526,35 +513,10 @@ export async function disposeSessionCursorAgent(scopeKey: string = getCursorSess
|
|
|
526
513
|
}
|
|
527
514
|
|
|
528
515
|
export async function disposeAllSessionCursorAgents(): Promise<void> {
|
|
529
|
-
const scopeKeys = [...new Set([...sessionAgentsByScope.keys(), ...
|
|
516
|
+
const scopeKeys = [...new Set([...sessionAgentsByScope.keys(), ...terminalDisposedScopeGenerations.keys()])];
|
|
530
517
|
await Promise.all(scopeKeys.map((scopeKey) => disposePoolEntryForScope(scopeKey, { terminal: true })));
|
|
531
518
|
invalidatedScopeKeys.clear();
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
export function registerCursorSessionAgent(_pi: CursorSessionAgentExtensionApi): void {
|
|
536
|
-
onCursorSessionScopeKeyChange((previousScopeKey) => {
|
|
537
|
-
void disposePoolEntryForScope(previousScopeKey, { terminal: true });
|
|
538
|
-
});
|
|
539
|
-
_pi.on("session_shutdown", async (event) => {
|
|
540
|
-
if (event.reason === "reload") {
|
|
541
|
-
await resetSessionCursorAgent();
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
await disposeSessionCursorAgent();
|
|
545
|
-
});
|
|
546
|
-
_pi.on("session_compact", () => {
|
|
547
|
-
invalidateSessionAgent();
|
|
548
|
-
});
|
|
549
|
-
_pi.on("session_before_tree", () => {
|
|
550
|
-
invalidateSessionAgent();
|
|
551
|
-
});
|
|
552
|
-
_pi.on("session_tree", async () => {
|
|
553
|
-
await resetSessionCursorAgent();
|
|
554
|
-
});
|
|
555
|
-
_pi.on("model_select", () => {
|
|
556
|
-
invalidateSessionAgent();
|
|
557
|
-
});
|
|
519
|
+
terminalDisposedScopeGenerations.clear();
|
|
558
520
|
}
|
|
559
521
|
|
|
560
522
|
export const __testUtils = {
|
|
@@ -7,14 +7,17 @@ interface CursorSessionScopeExtensionApi {
|
|
|
7
7
|
const ANONYMOUS_SESSION_SCOPE_KEY = "__anonymous__";
|
|
8
8
|
const EPHEMERAL_SESSION_SCOPE_PREFIX = "__ephemeral__:";
|
|
9
9
|
|
|
10
|
-
type CursorSessionScopeChangeHandler = (previousScopeKey: string) => void;
|
|
10
|
+
type CursorSessionScopeChangeHandler = (previousScopeKey: string) => Promise<void> | void;
|
|
11
11
|
|
|
12
12
|
const state = {
|
|
13
13
|
sessionCwd: process.cwd(),
|
|
14
14
|
sessionFile: undefined as string | undefined,
|
|
15
15
|
sessionId: undefined as string | undefined,
|
|
16
|
+
sessionGeneration: 0,
|
|
16
17
|
};
|
|
17
18
|
|
|
19
|
+
const scopeGenerations = new Map<string, number>([[ANONYMOUS_SESSION_SCOPE_KEY, state.sessionGeneration]]);
|
|
20
|
+
let nextSessionGeneration = 1;
|
|
18
21
|
let scopeChangeHandler: CursorSessionScopeChangeHandler | undefined;
|
|
19
22
|
|
|
20
23
|
/**
|
|
@@ -34,7 +37,16 @@ export function getCursorSessionScopeKey(): string {
|
|
|
34
37
|
return ANONYMOUS_SESSION_SCOPE_KEY;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
export function
|
|
40
|
+
export function getCursorSessionScopeGeneration(scopeKey: string = getCursorSessionScopeKey()): number {
|
|
41
|
+
return scopeGenerations.get(scopeKey) ?? 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Pi session cwd when known; falls back to process.cwd() before session_start.
|
|
46
|
+
* Updated on session_start only until pi threads cwd into streamSimple—mid-session cwd
|
|
47
|
+
* changes without a new session_start event are not reflected here.
|
|
48
|
+
*/
|
|
49
|
+
export function getCursorSessionCwd(): string {
|
|
38
50
|
return state.sessionCwd;
|
|
39
51
|
}
|
|
40
52
|
|
|
@@ -42,12 +54,19 @@ function setCursorSessionScope(cwd: string, sessionFile: string | undefined, ses
|
|
|
42
54
|
state.sessionCwd = cwd;
|
|
43
55
|
state.sessionFile = sessionFile;
|
|
44
56
|
state.sessionId = sessionId;
|
|
57
|
+
state.sessionGeneration = nextSessionGeneration;
|
|
58
|
+
nextSessionGeneration += 1;
|
|
59
|
+
scopeGenerations.set(getCursorSessionScopeKey(), state.sessionGeneration);
|
|
45
60
|
}
|
|
46
61
|
|
|
47
62
|
function resetCursorSessionScope(): void {
|
|
48
63
|
state.sessionCwd = process.cwd();
|
|
49
64
|
state.sessionFile = undefined;
|
|
50
65
|
state.sessionId = undefined;
|
|
66
|
+
state.sessionGeneration = 0;
|
|
67
|
+
nextSessionGeneration = 1;
|
|
68
|
+
scopeGenerations.clear();
|
|
69
|
+
scopeGenerations.set(ANONYMOUS_SESSION_SCOPE_KEY, state.sessionGeneration);
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
export function onCursorSessionScopeKeyChange(handler: CursorSessionScopeChangeHandler): void {
|
|
@@ -55,7 +74,7 @@ export function onCursorSessionScopeKeyChange(handler: CursorSessionScopeChangeH
|
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi): void {
|
|
58
|
-
pi.on("session_start", (_event, ctx) => {
|
|
77
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
59
78
|
const previousScopeKey = getCursorSessionScopeKey();
|
|
60
79
|
setCursorSessionScope(
|
|
61
80
|
ctx.cwd,
|
|
@@ -63,7 +82,7 @@ export function registerCursorSessionScope(pi: CursorSessionScopeExtensionApi):
|
|
|
63
82
|
ctx.sessionManager?.getSessionId?.() ?? undefined,
|
|
64
83
|
);
|
|
65
84
|
if (previousScopeKey !== getCursorSessionScopeKey()) {
|
|
66
|
-
scopeChangeHandler?.(previousScopeKey);
|
|
85
|
+
await scopeChangeHandler?.(previousScopeKey);
|
|
67
86
|
}
|
|
68
87
|
});
|
|
69
88
|
}
|
|
@@ -11,16 +11,16 @@ export function resolveCursorSettingSources(raw?: string): SettingSource[] | und
|
|
|
11
11
|
return resolveCursorSettingSourcesJs(raw) as SettingSource[] | undefined;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function getEffectiveCursorSettingSources(
|
|
14
|
+
export function getEffectiveCursorSettingSources(
|
|
15
|
+
raw: string | undefined = process.env[CURSOR_SETTING_SOURCES_ENV],
|
|
16
|
+
): SettingSource[] | undefined {
|
|
15
17
|
return resolveCursorSettingSources(raw);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
export function
|
|
20
|
+
export function cursorSettingSourcesIncludes(
|
|
21
|
+
settingSources: SettingSource[] | undefined,
|
|
22
|
+
source: Extract<SettingSource, "user" | "project">,
|
|
23
|
+
): boolean {
|
|
19
24
|
if (!settingSources?.length) return false;
|
|
20
|
-
return settingSources.includes("all") || settingSources.includes(
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function cursorSettingSourcesLoadProjectAgentsRules(settingSources: SettingSource[] | undefined): boolean {
|
|
24
|
-
if (!settingSources?.length) return false;
|
|
25
|
-
return settingSources.includes("all") || settingSources.includes("project");
|
|
25
|
+
return settingSources.includes("all") || settingSources.includes(source);
|
|
26
26
|
}
|
package/src/cursor-skill-tool.ts
CHANGED
|
@@ -2,19 +2,15 @@ import type { Dirent } from "node:fs";
|
|
|
2
2
|
import { readdir, readFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join, relative } from "node:path";
|
|
4
4
|
import type {
|
|
5
|
-
BeforeAgentStartEvent,
|
|
6
|
-
BeforeAgentStartEventResult,
|
|
7
5
|
BuildSystemPromptOptions,
|
|
8
6
|
ExtensionAPI,
|
|
9
7
|
ExtensionContext,
|
|
10
|
-
ExtensionHandler,
|
|
11
|
-
SessionStartEvent,
|
|
12
8
|
Skill,
|
|
13
|
-
TurnStartEvent,
|
|
14
9
|
} from "@earendil-works/pi-coding-agent";
|
|
15
10
|
import { Type } from "typebox";
|
|
16
11
|
import { isCursorModel } from "./cursor-model.js";
|
|
17
|
-
import {
|
|
12
|
+
import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
|
|
13
|
+
import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
|
|
18
14
|
|
|
19
15
|
export const CURSOR_ACTIVATE_SKILL_TOOL_NAME = "cursor_activate_skill";
|
|
20
16
|
export const CURSOR_ACTIVATE_SKILL_MCP_NAME = "pi__cursor_activate_skill";
|
|
@@ -23,12 +19,7 @@ const AVAILABLE_SKILLS_SECTION_PATTERN = /\n\nThe following skills provide speci
|
|
|
23
19
|
const MAX_SKILL_RESOURCES = 80;
|
|
24
20
|
const RESOURCE_DIR_NAMES = ["scripts", "references", "assets"] as const;
|
|
25
21
|
|
|
26
|
-
type CursorSkillToolExtensionApi = Pick<ExtensionAPI, "getActiveTools" | "registerTool" | "setActiveTools"> &
|
|
27
|
-
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
|
28
|
-
on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;
|
|
29
|
-
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
|
|
30
|
-
on(event: "model_select", handler: (event: { model: ExtensionContext["model"] }, ctx: ExtensionContext) => Promise<void> | void): void;
|
|
31
|
-
};
|
|
22
|
+
type CursorSkillToolExtensionApi = Pick<ExtensionAPI, "getActiveTools" | "registerTool" | "setActiveTools"> & CursorModelLifecycleExtensionApi;
|
|
32
23
|
|
|
33
24
|
type CursorActivateSkillParams = {
|
|
34
25
|
name?: string;
|
|
@@ -229,26 +220,28 @@ export function registerCursorSkillTool(pi: CursorSkillToolExtensionApi): void {
|
|
|
229
220
|
syncCursorSkillToolForModel(pi, model);
|
|
230
221
|
};
|
|
231
222
|
|
|
232
|
-
pi
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
223
|
+
registerCursorModelLifecycle(pi, {
|
|
224
|
+
sessionStart: (_event, ctx) => {
|
|
225
|
+
clearSkillsAndSync(ctx.model);
|
|
226
|
+
},
|
|
227
|
+
modelSelect: (event) => {
|
|
228
|
+
clearSkillsAndSync(event.model);
|
|
229
|
+
},
|
|
230
|
+
turnStart: (_event, ctx) => {
|
|
231
|
+
if (!isCursorModel(ctx.model)) setCurrentSkills([]);
|
|
232
|
+
syncCursorSkillToolForModel(pi, ctx.model);
|
|
233
|
+
},
|
|
234
|
+
beforeAgentStart: (event, ctx) => {
|
|
235
|
+
if (isCursorModel(ctx.model)) {
|
|
236
|
+
setCurrentSkills(event.systemPromptOptions?.skills);
|
|
237
|
+
} else {
|
|
238
|
+
setCurrentSkills([]);
|
|
239
|
+
}
|
|
240
|
+
syncCursorSkillToolForModel(pi, ctx.model);
|
|
241
|
+
const resolved = resolveCursorSkillSystemPrompt(event.systemPrompt, ctx.model, event.systemPromptOptions);
|
|
242
|
+
if (resolved === event.systemPrompt) return undefined;
|
|
243
|
+
return { systemPrompt: resolved };
|
|
244
|
+
},
|
|
252
245
|
});
|
|
253
246
|
}
|
|
254
247
|
|
package/src/cursor-state.ts
CHANGED
|
@@ -15,9 +15,12 @@ import {
|
|
|
15
15
|
} from "./cursor-pi-tool-bridge-snapshot.js";
|
|
16
16
|
import {
|
|
17
17
|
CURSOR_SETTING_SOURCES_ENV,
|
|
18
|
-
|
|
18
|
+
resolveCursorSettingSources,
|
|
19
19
|
} from "./cursor-setting-sources.js";
|
|
20
20
|
import { isCursorModel } from "./cursor-model.js";
|
|
21
|
+
import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
|
|
22
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
23
|
+
import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
21
24
|
import { getCursorModelMetadata } from "./model-discovery.js";
|
|
22
25
|
|
|
23
26
|
const FAST_ENTRY_TYPE = "cursor-fast-state";
|
|
@@ -58,6 +61,7 @@ let cliForceFast = false;
|
|
|
58
61
|
let cliForceNoFast = false;
|
|
59
62
|
let sessionCursorAgentMode: AgentModeOption | undefined;
|
|
60
63
|
let cliCursorModeState: CursorCliModeState = { kind: "unset" };
|
|
64
|
+
const invalidCursorModeNotifiedSessionScopeKeys = new Set<string>();
|
|
61
65
|
|
|
62
66
|
export function isCursorAgentMode(value: unknown): value is AgentModeOption {
|
|
63
67
|
return value === "agent" || value === "plan";
|
|
@@ -69,13 +73,10 @@ export function parseCursorAgentMode(raw: unknown): AgentModeOption | undefined
|
|
|
69
73
|
return isCursorAgentMode(mode) ? mode : undefined;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
73
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
76
|
function isCursorFastEntryData(value: unknown): value is CursorFastEntryData {
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
const record = asRecord(value);
|
|
78
|
+
if (!record) return false;
|
|
79
|
+
return (typeof record.modelId === "string" || typeof record.baseModelId === "string") && typeof record.fast === "boolean";
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
function getCursorFastEntryModelId(data: CursorFastEntryData): string {
|
|
@@ -83,17 +84,19 @@ function getCursorFastEntryModelId(data: CursorFastEntryData): string {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
function isCursorModeEntryData(value: unknown): value is CursorModeEntryData {
|
|
86
|
-
return
|
|
87
|
+
return isCursorAgentMode(asRecord(value)?.mode);
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
function parseCursorGlobalConfig(value: unknown): CursorGlobalConfig | undefined {
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
const record = asRecord(value);
|
|
92
|
+
if (!record) return undefined;
|
|
93
|
+
const { fastDefaults } = record;
|
|
92
94
|
if (fastDefaults === undefined) return {};
|
|
93
|
-
|
|
95
|
+
const fastDefaultsRecord = asRecord(fastDefaults);
|
|
96
|
+
if (!fastDefaultsRecord) return undefined;
|
|
94
97
|
return {
|
|
95
98
|
fastDefaults: Object.fromEntries(
|
|
96
|
-
Object.entries(
|
|
99
|
+
Object.entries(fastDefaultsRecord).filter((entry): entry is [string, boolean] => typeof entry[1] === "boolean"),
|
|
97
100
|
),
|
|
98
101
|
};
|
|
99
102
|
}
|
|
@@ -174,23 +177,38 @@ function formatInvalidCursorMode(raw: string): string {
|
|
|
174
177
|
return `Invalid --cursor-mode "${raw}". Use "agent" or "plan".`;
|
|
175
178
|
}
|
|
176
179
|
|
|
177
|
-
export
|
|
180
|
+
export type CursorAgentModeResolution =
|
|
181
|
+
| { kind: "valid"; mode: AgentModeOption }
|
|
182
|
+
| { kind: "invalid"; raw: string; message: string };
|
|
183
|
+
|
|
184
|
+
export function getStoredCursorAgentMode(): AgentModeOption {
|
|
185
|
+
return sessionCursorAgentMode ?? DEFAULT_CURSOR_AGENT_MODE;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function resolveCursorAgentMode(): CursorAgentModeResolution {
|
|
178
189
|
switch (cliCursorModeState.kind) {
|
|
179
190
|
case "valid":
|
|
180
|
-
return cliCursorModeState.mode;
|
|
191
|
+
return { kind: "valid", mode: cliCursorModeState.mode };
|
|
181
192
|
case "invalid":
|
|
182
|
-
|
|
193
|
+
return { kind: "invalid", raw: cliCursorModeState.raw, message: cliCursorModeState.message };
|
|
183
194
|
case "unset":
|
|
184
|
-
return
|
|
195
|
+
return { kind: "valid", mode: getStoredCursorAgentMode() };
|
|
185
196
|
}
|
|
186
197
|
}
|
|
187
198
|
|
|
199
|
+
export function getCursorProviderAgentModeOrThrow(): AgentModeOption {
|
|
200
|
+
const resolution = resolveCursorAgentMode();
|
|
201
|
+
if (resolution.kind === "invalid") throw new Error(resolution.message);
|
|
202
|
+
return resolution.mode;
|
|
203
|
+
}
|
|
204
|
+
|
|
188
205
|
function formatCursorStatus(fast: boolean | undefined): string | undefined {
|
|
189
206
|
const parts: string[] = [];
|
|
207
|
+
const modeResolution = resolveCursorAgentMode();
|
|
190
208
|
if (fast === true) parts.push("fast");
|
|
191
|
-
if (
|
|
209
|
+
if (modeResolution.kind === "invalid") {
|
|
192
210
|
parts.push("mode invalid");
|
|
193
|
-
} else if (
|
|
211
|
+
} else if (modeResolution.mode === "plan") {
|
|
194
212
|
parts.push("plan");
|
|
195
213
|
}
|
|
196
214
|
return parts.length > 0 ? `cursor ${parts.join(" · ")}` : undefined;
|
|
@@ -255,7 +273,7 @@ function persistCursorModePreference(pi: Pick<ExtensionAPI, "appendEntry">, mode
|
|
|
255
273
|
}
|
|
256
274
|
}
|
|
257
275
|
|
|
258
|
-
function restoreCliCursorMode(raw: boolean | string | undefined
|
|
276
|
+
function restoreCliCursorMode(raw: boolean | string | undefined): void {
|
|
259
277
|
cliCursorModeState = { kind: "unset" };
|
|
260
278
|
if (raw === undefined || raw === "" || raw === false) return;
|
|
261
279
|
const parsed = parseCursorAgentMode(raw);
|
|
@@ -266,15 +284,19 @@ function restoreCliCursorMode(raw: boolean | string | undefined, mode: Extension
|
|
|
266
284
|
const rawText = String(raw);
|
|
267
285
|
const message = formatInvalidCursorMode(rawText);
|
|
268
286
|
cliCursorModeState = { kind: "invalid", raw: rawText, message };
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function notifyInvalidCursorModeIfCursorActive(ctx: Pick<ExtensionContext, "hasUI" | "mode" | "ui">): void {
|
|
290
|
+
const modeResolution = resolveCursorAgentMode();
|
|
291
|
+
if (modeResolution.kind !== "invalid" || !ctx.hasUI || ctx.mode !== "tui") return;
|
|
292
|
+
const scopeKey = getCursorSessionScopeKey();
|
|
293
|
+
if (invalidCursorModeNotifiedSessionScopeKeys.has(scopeKey)) return;
|
|
294
|
+
invalidCursorModeNotifiedSessionScopeKeys.add(scopeKey);
|
|
295
|
+
ctx.ui.notify(modeResolution.message, "error");
|
|
274
296
|
}
|
|
275
297
|
|
|
276
298
|
function formatEffectiveCursorSettingSourcesLabel(raw: string | undefined = process.env[CURSOR_SETTING_SOURCES_ENV]): string {
|
|
277
|
-
const effective =
|
|
299
|
+
const effective = resolveCursorSettingSources(raw);
|
|
278
300
|
const effectiveLabel = effective === undefined ? "none" : effective.join(",");
|
|
279
301
|
const rawLabel = raw?.trim() ? raw.trim() : "(unset → all)";
|
|
280
302
|
return `${rawLabel} (effective: ${effectiveLabel})`;
|
|
@@ -395,10 +417,11 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
395
417
|
const usage = "Usage: /cursor-mode agent|plan";
|
|
396
418
|
const mode = parseCursorAgentMode(args);
|
|
397
419
|
if (!args.trim()) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
420
|
+
const modeResolution = resolveCursorAgentMode();
|
|
421
|
+
if (modeResolution.kind === "invalid") {
|
|
422
|
+
ctx.ui.notify(`${modeResolution.message} ${usage}`, "error");
|
|
423
|
+
} else {
|
|
424
|
+
ctx.ui.notify(`Cursor mode is ${modeResolution.mode}. ${usage}`, "info");
|
|
402
425
|
}
|
|
403
426
|
return;
|
|
404
427
|
}
|
|
@@ -429,28 +452,26 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
429
452
|
},
|
|
430
453
|
});
|
|
431
454
|
|
|
432
|
-
pi
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
pi.on("turn_start", async (_event, ctx) => {
|
|
447
|
-
updateCursorStatus(ctx);
|
|
455
|
+
registerCursorModelLifecycle(pi, {
|
|
456
|
+
sessionStart: (_event, ctx) => {
|
|
457
|
+
globalFastPreferences = loadGlobalFastPreferences();
|
|
458
|
+
cliForceFast = pi.getFlag("cursor-fast") === true;
|
|
459
|
+
cliForceNoFast = pi.getFlag("cursor-no-fast") === true;
|
|
460
|
+
restoreSessionFastPreferences(ctx);
|
|
461
|
+
restoreSessionCursorMode(ctx);
|
|
462
|
+
restoreCliCursorMode(pi.getFlag("cursor-mode"));
|
|
463
|
+
},
|
|
464
|
+
sync: (ctx) => {
|
|
465
|
+
if (isCursorModel(ctx.model)) notifyInvalidCursorModeIfCursorActive(ctx);
|
|
466
|
+
updateCursorStatus(ctx);
|
|
467
|
+
},
|
|
448
468
|
});
|
|
449
469
|
}
|
|
450
470
|
|
|
451
471
|
function resetCursorModeStateForTests(): void {
|
|
452
472
|
sessionCursorAgentMode = undefined;
|
|
453
473
|
cliCursorModeState = { kind: "unset" };
|
|
474
|
+
invalidCursorModeNotifiedSessionScopeKeys.clear();
|
|
454
475
|
}
|
|
455
476
|
|
|
456
477
|
export const __testUtils = {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
2
2
|
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
3
3
|
import { getCursorToolLifecycleLabelKind } from "./cursor-tool-presentation-registry.js";
|
|
4
|
-
import { extractWebSearchQuery } from "./cursor-web-tool-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { extractWebSearchQuery } from "./cursor-web-tool-args.js";
|
|
5
|
+
import { getArray, getString } from "./cursor-record-utils.js";
|
|
6
|
+
import { firstNonEmptyLine, truncateArg } from "./cursor-transcript-utils.js";
|
|
7
|
+
import { classifyCursorToolVisibility, type CursorToolVisibility } from "./cursor-tool-visibility.js";
|
|
7
8
|
|
|
8
9
|
/** Defer pending lifecycle lines so fast start+complete pairs coalesce into the completed replay card only. */
|
|
9
10
|
export const CURSOR_TOOL_LIFECYCLE_DEFER_MS = 75;
|
|
@@ -12,8 +13,7 @@ export function isCursorToolLifecycleEligible(toolCall: unknown): boolean {
|
|
|
12
13
|
return classifyCursorToolVisibility(toolCall).lifecycleEligible;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
function getCursorToolLifecycleTitle(
|
|
16
|
-
const visibility = classifyCursorToolVisibility(toolCall);
|
|
16
|
+
function getCursorToolLifecycleTitle(visibility: CursorToolVisibility): string {
|
|
17
17
|
return visibility.lifecycleTitle ?? `Cursor ${visibility.normalizedName}`;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -36,8 +36,10 @@ function scrubLifecycleDetail(value: string | undefined, apiKey?: string): strin
|
|
|
36
36
|
return scrubbed;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
function buildCursorToolLifecycleLabelFromVisibility(
|
|
40
|
+
visibility: CursorToolVisibility,
|
|
41
|
+
apiKey?: string,
|
|
42
|
+
): string | undefined {
|
|
41
43
|
const args = visibility.args;
|
|
42
44
|
|
|
43
45
|
switch (getCursorToolLifecycleLabelKind(visibility.normalizedKey)) {
|
|
@@ -79,8 +81,13 @@ export function buildCursorToolLifecycleLabel(toolCall: unknown, apiKey?: string
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
export function buildCursorToolLifecycleLabel(toolCall: unknown, apiKey?: string): string | undefined {
|
|
85
|
+
return buildCursorToolLifecycleLabelFromVisibility(classifyCursorToolVisibility(toolCall), apiKey);
|
|
86
|
+
}
|
|
87
|
+
|
|
82
88
|
export function formatCursorToolLifecycleProgressText(toolCall: unknown, apiKey?: string): string | undefined {
|
|
83
|
-
const
|
|
89
|
+
const visibility = classifyCursorToolVisibility(toolCall);
|
|
90
|
+
const label = buildCursorToolLifecycleLabelFromVisibility(visibility, apiKey);
|
|
84
91
|
if (!label) return undefined;
|
|
85
|
-
return `${getCursorToolLifecycleTitle(
|
|
92
|
+
return `${getCursorToolLifecycleTitle(visibility)}: ${label}\n`;
|
|
86
93
|
}
|