pi-cursor-sdk 0.2.0 → 0.3.6
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 +62 -0
- package/README.md +54 -50
- package/dist/bundled-context-windows.js +39 -0
- package/dist/context-window-cache.js +92 -0
- package/dist/context.js +338 -0
- package/dist/cursor-active-tools.js +3 -0
- package/dist/cursor-agent-message-web-tools.js +165 -0
- package/dist/cursor-agents-context-registration.js +17 -0
- package/dist/cursor-agents-context.js +113 -0
- package/dist/cursor-api-key.js +34 -0
- package/dist/cursor-bridge-contract.js +27 -0
- package/dist/cursor-cloud-lifecycle.js +650 -0
- package/dist/cursor-cloud-local-state.js +460 -0
- package/dist/cursor-cloud-options.js +145 -0
- package/dist/cursor-cloud-reporting.js +222 -0
- package/dist/cursor-compact-tool-summary.js +75 -0
- package/dist/cursor-config.js +613 -0
- package/dist/cursor-context-tools.js +4 -0
- package/dist/cursor-display-only-trace.js +11 -0
- package/dist/cursor-display-text.js +18 -0
- package/dist/cursor-durable-fs.js +53 -0
- package/dist/cursor-edit-diff.js +13 -0
- package/dist/cursor-env-boolean.js +19 -0
- package/dist/cursor-fallback-models.generated.js +7760 -0
- package/dist/cursor-fallback-warning.js +15 -0
- package/dist/cursor-http1.js +45 -0
- package/dist/cursor-incomplete-tool-visibility.js +83 -0
- package/dist/cursor-live-run-accounting.js +55 -0
- package/dist/cursor-live-run-coordinator.js +406 -0
- package/dist/cursor-mcp-timeout-override.js +116 -0
- package/dist/cursor-model-lifecycle.js +32 -0
- package/dist/cursor-model.js +5 -0
- package/dist/cursor-native-replay-routing.js +35 -0
- package/dist/cursor-native-replay-trace.js +28 -0
- package/dist/cursor-native-tool-display-registration.js +102 -0
- package/dist/cursor-native-tool-display-replay.js +428 -0
- package/dist/cursor-native-tool-display-state.js +70 -0
- package/dist/cursor-native-tool-display-tools.js +123 -0
- package/dist/cursor-native-tool-names.js +11 -0
- package/dist/cursor-partial-content-emitter.js +118 -0
- package/dist/cursor-pi-tool-bridge-abort.js +114 -0
- package/dist/cursor-pi-tool-bridge-constants.js +6 -0
- package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
- package/dist/cursor-pi-tool-bridge-env.js +18 -0
- package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
- package/dist/cursor-pi-tool-bridge-run.js +355 -0
- package/dist/cursor-pi-tool-bridge-server.js +163 -0
- package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
- package/dist/cursor-pi-tool-bridge-types.js +1 -0
- package/dist/cursor-pi-tool-bridge.js +123 -0
- package/dist/cursor-provider-errors.js +311 -0
- package/dist/cursor-provider-lazy.js +39 -0
- package/dist/cursor-provider-live-run-drain.js +412 -0
- package/dist/cursor-provider-overflow.js +90 -0
- package/dist/cursor-provider-run-finalizer.js +176 -0
- package/dist/cursor-provider-run-outcome.js +76 -0
- package/dist/cursor-provider-turn-coordinator.js +275 -0
- package/dist/cursor-provider-turn-display-router.js +158 -0
- package/dist/cursor-provider-turn-emit.js +35 -0
- package/dist/cursor-provider-turn-finalize.js +154 -0
- package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
- package/dist/cursor-provider-turn-prepare.js +374 -0
- package/dist/cursor-provider-turn-runner.js +146 -0
- package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
- package/dist/cursor-provider-turn-send.js +138 -0
- package/dist/cursor-provider-turn-shell-output.js +117 -0
- package/dist/cursor-provider-turn-tool-ledger.js +99 -0
- package/dist/cursor-provider-turn-types.js +1 -0
- package/dist/cursor-provider.js +72 -0
- package/dist/cursor-question-tool.js +194 -0
- package/dist/cursor-record-utils.js +61 -0
- package/dist/cursor-replay-activity-builders.js +114 -0
- package/dist/cursor-replay-source-names.js +27 -0
- package/dist/cursor-replay-summary-args.js +105 -0
- package/dist/cursor-replay-tool-details.js +167 -0
- package/dist/cursor-ripgrep-path.js +27 -0
- package/dist/cursor-run-final-text.js +63 -0
- package/dist/cursor-runtime-state.js +380 -0
- package/dist/cursor-sdk-billed-usage.js +86 -0
- package/dist/cursor-sdk-event-debug-constants.js +33 -0
- package/dist/cursor-sdk-event-debug-session.js +107 -0
- package/dist/cursor-sdk-event-debug.js +461 -0
- package/dist/cursor-sdk-output-filter.js +1 -0
- package/dist/cursor-sdk-process-error-guard.js +221 -0
- package/dist/cursor-sdk-runtime.js +3 -0
- package/dist/cursor-sensitive-text.js +23 -0
- package/dist/cursor-session-agent-cleanup.js +311 -0
- package/dist/cursor-session-agent-lifecycle.js +32 -0
- package/dist/cursor-session-agent-lineage.js +103 -0
- package/dist/cursor-session-agent-resume.js +417 -0
- package/dist/cursor-session-agent.js +507 -0
- package/dist/cursor-session-compaction-prep.js +21 -0
- package/dist/cursor-session-scope.js +113 -0
- package/dist/cursor-session-send-policy.js +18 -0
- package/dist/cursor-session-store.js +105 -0
- package/dist/cursor-session-turn-queue.js +50 -0
- package/dist/cursor-setting-sources.js +15 -0
- package/dist/cursor-skill-tool.js +230 -0
- package/dist/cursor-state.js +519 -0
- package/dist/cursor-task-presentation.js +59 -0
- package/dist/cursor-tool-lifecycle.js +94 -0
- package/dist/cursor-tool-manifest.js +34 -0
- package/dist/cursor-tool-presentation-registry.js +305 -0
- package/dist/cursor-tool-result-display-readers.js +158 -0
- package/dist/cursor-tool-transcript.js +32 -0
- package/dist/cursor-tool-visibility.js +28 -0
- package/dist/cursor-transcript-tool-formatters.js +618 -0
- package/dist/cursor-transcript-tool-specs.js +284 -0
- package/dist/cursor-transcript-utils.js +170 -0
- package/dist/cursor-usage-accounting.js +166 -0
- package/dist/cursor-web-tool-activity.js +23 -0
- package/dist/cursor-web-tool-args.js +12 -0
- package/dist/index.js +82 -0
- package/dist/model-discovery.js +305 -0
- package/dist/model-list-cache.js +156 -0
- package/docs/cursor-dogfood-checklist.md +2 -1
- package/docs/cursor-live-smoke-checklist.md +13 -12
- package/docs/cursor-model-ux-spec.md +25 -6
- package/docs/cursor-native-tool-replay.md +13 -13
- package/docs/cursor-native-tool-visual-audit.md +6 -3
- package/docs/cursor-testing-lessons.md +7 -7
- package/docs/cursor-tool-surfaces.md +5 -5
- package/docs/platform-smoke.md +3 -3
- package/package.json +19 -7
- package/platform-smoke.config.mjs +1 -1
- package/scripts/build.mjs +126 -0
- package/scripts/cloud-runtime-smoke.mjs +3 -1
- package/scripts/debug-provider-events.mjs +3 -1
- package/scripts/debug-sdk-events.mjs +1 -1
- package/scripts/isolated-cursor-smoke.sh +5 -4
- package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
- package/scripts/lib/cursor-visual-render.mjs +1 -1
- package/scripts/lib/ensure-built.mjs +16 -0
- package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
- package/scripts/local-resume-smoke.mjs +3 -1
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/scripts/platform-smoke/live-suite-runner.mjs +1 -1
- package/scripts/platform-smoke/render-ansi.mjs +1 -1
- package/scripts/platform-smoke/targets.mjs +2 -2
- package/scripts/prepare.mjs +102 -0
- package/scripts/steering-rpc-smoke.mjs +4 -2
- package/scripts/tmux-live-smoke.sh +11 -11
- package/scripts/visual-tui-smoke-self-test.mjs +4 -1
- package/scripts/visual-tui-smoke.mjs +51 -7
- package/src/bundled-context-windows.ts +4 -1
- package/src/cursor-agents-context-registration.ts +2 -2
- package/src/cursor-agents-context.ts +0 -2
- package/src/cursor-api-key.ts +5 -4
- package/src/cursor-cloud-reporting.ts +23 -2
- package/src/cursor-fallback-models.generated.ts +592 -204
- package/src/cursor-live-run-coordinator.ts +1 -0
- package/src/cursor-native-tool-display-registration.ts +10 -11
- package/src/cursor-provider-errors.ts +1 -1
- package/src/cursor-provider-lazy.ts +5 -4
- package/src/cursor-provider-live-run-drain.ts +1 -0
- package/src/cursor-provider-run-finalizer.ts +1 -0
- package/src/cursor-provider-turn-finalize.ts +12 -0
- package/src/cursor-provider-turn-types.ts +2 -0
- package/src/cursor-sdk-billed-usage.ts +107 -0
- package/src/cursor-sdk-process-error-guard.ts +6 -4
- package/src/cursor-session-agent-lifecycle.ts +8 -9
- package/src/cursor-session-agent-resume.ts +23 -0
- package/src/cursor-session-compaction-prep.ts +4 -0
- package/src/cursor-tool-manifest.ts +1 -1
- package/src/cursor-usage-accounting.ts +70 -9
- package/src/index.ts +1 -1
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import { buildCursorToolManifestText, CURSOR_TOOL_MANIFEST_ENV, resolveCursorToolManifestEnabled, } from "./cursor-tool-manifest.js";
|
|
2
|
+
import { runCursorSessionAgentCleanupCommand } from "./cursor-session-agent-cleanup.js";
|
|
3
|
+
import { CURSOR_HTTP1_ENTRY_TYPE, getStoredCursorHttp1Enabled, isCursorHttp1EntryData, setCursorHttp1GlobalPreferenceAuthoritative, setStoredCursorHttp1Enabled, } from "./cursor-http1.js";
|
|
4
|
+
import { buildCursorPiToolBridgeSnapshot, CURSOR_PI_TOOL_BRIDGE_ENV, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-snapshot.js";
|
|
5
|
+
import { CURSOR_SETTING_SOURCES_ENV, DEFAULT_CURSOR_SETTING_SOURCES, resolveCursorSettingSources, } from "./cursor-setting-sources.js";
|
|
6
|
+
import { isCursorModel } from "./cursor-model.js";
|
|
7
|
+
import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
|
|
8
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
9
|
+
import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
10
|
+
import { refreshSessionCursorAgentConfig } from "./cursor-session-agent.js";
|
|
11
|
+
import { getCursorModelMetadata } from "./model-discovery.js";
|
|
12
|
+
import { cursorFastDefaultsFromConfig, getCursorSdkUserConfigPath, loadCursorSdkUserConfig, mergeCursorSdkConfigForUpdate, resolveCursorFastDefault, updateCursorSdkConfig, } from "./cursor-config.js";
|
|
13
|
+
import { CURSOR_RUNTIME_ENTRY_TYPE, formatCursorStatus, registerCursorCloudRuntimeControls, resetCursorRuntimeStateForTests, resolveCursorStatusRuntime, restoreCursorCliState, restoreSessionCursorRuntimeState, } from "./cursor-runtime-state.js";
|
|
14
|
+
export { consumeCursorLocalForceOverride, getCursorCliConfig, getCursorSessionConfig, } from "./cursor-runtime-state.js";
|
|
15
|
+
const FAST_ENTRY_TYPE = "cursor-fast-state";
|
|
16
|
+
const MODE_ENTRY_TYPE = "cursor-mode-state";
|
|
17
|
+
const DEFAULT_CURSOR_AGENT_MODE = "agent";
|
|
18
|
+
const sessionFastPreferences = new Map();
|
|
19
|
+
const authoritativeGlobalFastPreferenceIds = new Set();
|
|
20
|
+
let globalFastPreferences = new Map();
|
|
21
|
+
let cliForceFast = false;
|
|
22
|
+
let cliForceNoFast = false;
|
|
23
|
+
let sessionCursorAgentMode;
|
|
24
|
+
let cliCursorModeState = { kind: "unset" };
|
|
25
|
+
const invalidCursorModeNotifiedSessionScopeKeys = new Set();
|
|
26
|
+
export function isCursorAgentMode(value) {
|
|
27
|
+
return value === "agent" || value === "plan";
|
|
28
|
+
}
|
|
29
|
+
export function parseCursorAgentMode(raw) {
|
|
30
|
+
if (typeof raw !== "string")
|
|
31
|
+
return undefined;
|
|
32
|
+
const mode = raw.trim();
|
|
33
|
+
return isCursorAgentMode(mode) ? mode : undefined;
|
|
34
|
+
}
|
|
35
|
+
function isCursorFastEntryData(value) {
|
|
36
|
+
const record = asRecord(value);
|
|
37
|
+
if (!record)
|
|
38
|
+
return false;
|
|
39
|
+
return (typeof record.modelId === "string" || typeof record.baseModelId === "string") && typeof record.fast === "boolean";
|
|
40
|
+
}
|
|
41
|
+
function getCursorFastEntryModelId(data) {
|
|
42
|
+
return data.modelId ?? data.baseModelId ?? "";
|
|
43
|
+
}
|
|
44
|
+
function isCursorModeEntryData(value) {
|
|
45
|
+
return isCursorAgentMode(asRecord(value)?.mode);
|
|
46
|
+
}
|
|
47
|
+
function getConfigPath() {
|
|
48
|
+
return getCursorSdkUserConfigPath();
|
|
49
|
+
}
|
|
50
|
+
function loadGlobalFastPreferences() {
|
|
51
|
+
return cursorFastDefaultsFromConfig(loadCursorSdkUserConfig());
|
|
52
|
+
}
|
|
53
|
+
function saveGlobalFastPreference(modelId, fast) {
|
|
54
|
+
updateCursorSdkConfig(getConfigPath(), (current) => {
|
|
55
|
+
const fastDefaults = { ...asRecord(current.fastDefaults), [modelId]: fast };
|
|
56
|
+
return {
|
|
57
|
+
...current,
|
|
58
|
+
fastDefaults: Object.fromEntries(Object.entries(fastDefaults).sort(([a], [b]) => a.localeCompare(b))),
|
|
59
|
+
};
|
|
60
|
+
}, { newFileMode: 0o600 });
|
|
61
|
+
}
|
|
62
|
+
function saveGlobalCursorHttp1Enabled(enabled) {
|
|
63
|
+
updateCursorSdkConfig(getConfigPath(), (current) => mergeCursorSdkConfigForUpdate(current, {
|
|
64
|
+
local: { useHttp1ForAgent: enabled },
|
|
65
|
+
}), { newFileMode: 0o600 });
|
|
66
|
+
}
|
|
67
|
+
function restoreSessionFastPreferences(branch) {
|
|
68
|
+
sessionFastPreferences.clear();
|
|
69
|
+
for (const entry of branch) {
|
|
70
|
+
if (entry.type !== "custom" || entry.customType !== FAST_ENTRY_TYPE)
|
|
71
|
+
continue;
|
|
72
|
+
if (isCursorFastEntryData(entry.data)) {
|
|
73
|
+
const modelId = getCursorFastEntryModelId(entry.data);
|
|
74
|
+
if (modelId)
|
|
75
|
+
sessionFastPreferences.set(modelId, entry.data.fast);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function restoreSessionCursorMode(branch) {
|
|
80
|
+
sessionCursorAgentMode = undefined;
|
|
81
|
+
for (const entry of branch) {
|
|
82
|
+
if (entry.type !== "custom" || entry.customType !== MODE_ENTRY_TYPE)
|
|
83
|
+
continue;
|
|
84
|
+
if (isCursorModeEntryData(entry.data)) {
|
|
85
|
+
sessionCursorAgentMode = entry.data.mode;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function restoreSessionCursorPreferences(ctx) {
|
|
90
|
+
const branch = ctx.sessionManager.getBranch();
|
|
91
|
+
restoreSessionCursorRuntimeState(branch);
|
|
92
|
+
restoreSessionFastPreferences(branch);
|
|
93
|
+
restoreSessionCursorMode(branch);
|
|
94
|
+
restoreSessionCursorHttp1(branch);
|
|
95
|
+
}
|
|
96
|
+
function restoreSessionCursorHttp1(branch) {
|
|
97
|
+
setStoredCursorHttp1Enabled(undefined);
|
|
98
|
+
for (const entry of branch) {
|
|
99
|
+
if (entry.type !== "custom" || entry.customType !== CURSOR_HTTP1_ENTRY_TYPE)
|
|
100
|
+
continue;
|
|
101
|
+
if (isCursorHttp1EntryData(entry.data)) {
|
|
102
|
+
setStoredCursorHttp1Enabled(entry.data.enabled);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function getFastPreferenceModelId(metadata) {
|
|
107
|
+
return metadata.selectionModelId || metadata.baseModelId;
|
|
108
|
+
}
|
|
109
|
+
function getVirtualFastBaseModelId(modelId) {
|
|
110
|
+
return modelId.replace(/:(?:fast|slow)$/, "");
|
|
111
|
+
}
|
|
112
|
+
function getMapFastPreference(map, metadata) {
|
|
113
|
+
const preferenceModelId = getFastPreferenceModelId(metadata);
|
|
114
|
+
return map.get(preferenceModelId) ?? (preferenceModelId !== metadata.baseModelId ? map.get(metadata.baseModelId) : undefined);
|
|
115
|
+
}
|
|
116
|
+
function getEffectiveFast(modelId) {
|
|
117
|
+
const metadata = getCursorModelMetadata(modelId);
|
|
118
|
+
if (!metadata?.supportsFast)
|
|
119
|
+
return undefined;
|
|
120
|
+
return resolveCursorFastDefault({
|
|
121
|
+
cliForceNoFast,
|
|
122
|
+
cliForceFast,
|
|
123
|
+
aliasOverride: metadata.fastOverride,
|
|
124
|
+
sessionValue: authoritativeGlobalFastPreferenceIds.has(getFastPreferenceModelId(metadata))
|
|
125
|
+
? undefined
|
|
126
|
+
: getMapFastPreference(sessionFastPreferences, metadata),
|
|
127
|
+
userValue: getMapFastPreference(globalFastPreferences, metadata),
|
|
128
|
+
modelDefault: metadata.defaultFast,
|
|
129
|
+
}).value;
|
|
130
|
+
}
|
|
131
|
+
function formatInvalidCursorMode(raw) {
|
|
132
|
+
return `Invalid --cursor-mode "${raw}". Use "agent" or "plan".`;
|
|
133
|
+
}
|
|
134
|
+
export function getStoredCursorAgentMode() {
|
|
135
|
+
return sessionCursorAgentMode ?? DEFAULT_CURSOR_AGENT_MODE;
|
|
136
|
+
}
|
|
137
|
+
export function resolveCursorAgentMode() {
|
|
138
|
+
switch (cliCursorModeState.kind) {
|
|
139
|
+
case "valid":
|
|
140
|
+
return { kind: "valid", mode: cliCursorModeState.mode };
|
|
141
|
+
case "invalid":
|
|
142
|
+
return { kind: "invalid", raw: cliCursorModeState.raw, message: cliCursorModeState.message };
|
|
143
|
+
case "unset":
|
|
144
|
+
return { kind: "valid", mode: getStoredCursorAgentMode() };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export function getCursorProviderAgentModeOrThrow() {
|
|
148
|
+
const resolution = resolveCursorAgentMode();
|
|
149
|
+
if (resolution.kind === "invalid")
|
|
150
|
+
throw new Error(resolution.message);
|
|
151
|
+
return resolution.mode;
|
|
152
|
+
}
|
|
153
|
+
function updateCursorStatus(ctx, model = ctx.model) {
|
|
154
|
+
if (!model || !isCursorModel(model)) {
|
|
155
|
+
ctx.ui.setStatus("cursor", undefined);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const metadata = getCursorModelMetadata(model.id);
|
|
159
|
+
const resolution = resolveCursorStatusRuntime(ctx);
|
|
160
|
+
const modeResolution = resolveCursorAgentMode();
|
|
161
|
+
const mode = modeResolution.kind === "invalid" ? "invalid" : modeResolution.mode;
|
|
162
|
+
if (resolution.kind === "invalid") {
|
|
163
|
+
ctx.ui.setStatus("cursor", formatCursorStatus("invalid", undefined, mode));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const runtime = resolution.runtime.value;
|
|
167
|
+
const fast = runtime === "cloud" ? undefined : metadata?.supportsFast ? getEffectiveFast(model.id) : undefined;
|
|
168
|
+
ctx.ui.setStatus("cursor", formatCursorStatus(runtime, fast, mode, resolution.useHttp1ForAgent.value));
|
|
169
|
+
}
|
|
170
|
+
function getCurrentCursorMetadata(ctx) {
|
|
171
|
+
const model = ctx.model;
|
|
172
|
+
if (!model || !isCursorModel(model))
|
|
173
|
+
return undefined;
|
|
174
|
+
return getCursorModelMetadata(model.id);
|
|
175
|
+
}
|
|
176
|
+
function restoreMapValue(map, key, previous) {
|
|
177
|
+
if (previous === undefined) {
|
|
178
|
+
map.delete(key);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
map.set(key, previous);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function persistFastPreference(pi, modelId, fast) {
|
|
185
|
+
const previousSession = sessionFastPreferences.get(modelId);
|
|
186
|
+
const previousGlobal = globalFastPreferences.get(modelId);
|
|
187
|
+
sessionFastPreferences.set(modelId, fast);
|
|
188
|
+
globalFastPreferences.set(modelId, fast);
|
|
189
|
+
try {
|
|
190
|
+
saveGlobalFastPreference(modelId, fast);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
restoreMapValue(sessionFastPreferences, modelId, previousSession);
|
|
194
|
+
restoreMapValue(globalFastPreferences, modelId, previousGlobal);
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
pi.appendEntry(FAST_ENTRY_TYPE, { modelId, fast });
|
|
199
|
+
authoritativeGlobalFastPreferenceIds.delete(modelId);
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
authoritativeGlobalFastPreferenceIds.add(modelId);
|
|
204
|
+
return error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function persistCursorModePreference(pi, mode) {
|
|
208
|
+
const previousMode = sessionCursorAgentMode;
|
|
209
|
+
sessionCursorAgentMode = mode;
|
|
210
|
+
try {
|
|
211
|
+
pi.appendEntry(MODE_ENTRY_TYPE, { mode });
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
sessionCursorAgentMode = previousMode;
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function persistCursorHttp1Preference(pi, enabled) {
|
|
219
|
+
const previousSession = getStoredCursorHttp1Enabled();
|
|
220
|
+
setStoredCursorHttp1Enabled(enabled);
|
|
221
|
+
try {
|
|
222
|
+
saveGlobalCursorHttp1Enabled(enabled);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
setStoredCursorHttp1Enabled(previousSession);
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
pi.appendEntry(CURSOR_HTTP1_ENTRY_TYPE, { enabled });
|
|
230
|
+
setCursorHttp1GlobalPreferenceAuthoritative(false);
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
setCursorHttp1GlobalPreferenceAuthoritative(true);
|
|
235
|
+
return error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function restoreCliCursorMode(raw) {
|
|
239
|
+
cliCursorModeState = { kind: "unset" };
|
|
240
|
+
if (raw === undefined || raw === "" || raw === false)
|
|
241
|
+
return;
|
|
242
|
+
const parsed = parseCursorAgentMode(raw);
|
|
243
|
+
if (parsed) {
|
|
244
|
+
cliCursorModeState = { kind: "valid", mode: parsed };
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const rawText = String(raw);
|
|
248
|
+
const message = formatInvalidCursorMode(rawText);
|
|
249
|
+
cliCursorModeState = { kind: "invalid", raw: rawText, message };
|
|
250
|
+
}
|
|
251
|
+
function notifyInvalidCursorModeIfCursorActive(ctx) {
|
|
252
|
+
const modeResolution = resolveCursorAgentMode();
|
|
253
|
+
if (modeResolution.kind !== "invalid" || !ctx.hasUI || ctx.mode !== "tui")
|
|
254
|
+
return;
|
|
255
|
+
const scopeKey = getCursorSessionScopeKey();
|
|
256
|
+
if (invalidCursorModeNotifiedSessionScopeKeys.has(scopeKey))
|
|
257
|
+
return;
|
|
258
|
+
invalidCursorModeNotifiedSessionScopeKeys.add(scopeKey);
|
|
259
|
+
ctx.ui.notify(modeResolution.message, "error");
|
|
260
|
+
}
|
|
261
|
+
function formatEffectiveCursorSettingSourcesLabel(raw = process.env[CURSOR_SETTING_SOURCES_ENV]) {
|
|
262
|
+
const effective = resolveCursorSettingSources(raw);
|
|
263
|
+
const effectiveLabel = effective === undefined ? "none" : effective.join(",");
|
|
264
|
+
const rawLabel = raw?.trim() ? raw.trim() : `(unset → ${DEFAULT_CURSOR_SETTING_SOURCES.join(",")})`;
|
|
265
|
+
return `${rawLabel} (effective: ${effectiveLabel})`;
|
|
266
|
+
}
|
|
267
|
+
export function formatCursorToolsDebugReport(pi, env = process.env) {
|
|
268
|
+
const bridgeEnabled = resolveCursorPiToolBridgeEnabled(env);
|
|
269
|
+
const manifestEnabled = resolveCursorToolManifestEnabled(env);
|
|
270
|
+
const lines = [
|
|
271
|
+
"Cursor tool surfaces (current session):",
|
|
272
|
+
`${CURSOR_PI_TOOL_BRIDGE_ENV}: ${bridgeEnabled ? "enabled" : "disabled"}`,
|
|
273
|
+
`${CURSOR_TOOL_MANIFEST_ENV}: ${manifestEnabled ? "enabled" : "disabled"}`,
|
|
274
|
+
`${CURSOR_SETTING_SOURCES_ENV}: ${formatEffectiveCursorSettingSourcesLabel(env[CURSOR_SETTING_SOURCES_ENV])}`,
|
|
275
|
+
];
|
|
276
|
+
let bridgeSnapshot;
|
|
277
|
+
if (bridgeEnabled) {
|
|
278
|
+
try {
|
|
279
|
+
bridgeSnapshot = buildCursorPiToolBridgeSnapshot(pi);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
lines.push("Pi bridge snapshot: unavailable (extension tool APIs required).");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
lines.push(buildCursorToolManifestText({ bridgeSnapshot, piBridgeEnabled: bridgeEnabled }));
|
|
286
|
+
return lines.join("\n");
|
|
287
|
+
}
|
|
288
|
+
function emitCursorToolsDebugReport(pi, ctx) {
|
|
289
|
+
const report = formatCursorToolsDebugReport(pi);
|
|
290
|
+
if (ctx.hasUI) {
|
|
291
|
+
ctx.ui.notify(report, "info");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
console.log(report);
|
|
295
|
+
}
|
|
296
|
+
export function getEffectiveFastForModelId(modelId) {
|
|
297
|
+
return getEffectiveFast(modelId);
|
|
298
|
+
}
|
|
299
|
+
export function registerCursorRuntimeControls(pi) {
|
|
300
|
+
registerCursorCloudRuntimeControls(pi, { refreshStatus: updateCursorStatus });
|
|
301
|
+
pi.registerFlag("cursor-fast", {
|
|
302
|
+
description: "Force Cursor fast mode for this run when the selected Cursor model supports it",
|
|
303
|
+
type: "boolean",
|
|
304
|
+
default: false,
|
|
305
|
+
});
|
|
306
|
+
pi.registerFlag("cursor-no-fast", {
|
|
307
|
+
description: "Force Cursor fast mode off for this run when the selected Cursor model supports it",
|
|
308
|
+
type: "boolean",
|
|
309
|
+
default: false,
|
|
310
|
+
});
|
|
311
|
+
pi.registerFlag("cursor-mode", {
|
|
312
|
+
description: "Set Cursor SDK conversation mode for this run: agent or plan",
|
|
313
|
+
type: "string",
|
|
314
|
+
default: "",
|
|
315
|
+
});
|
|
316
|
+
pi.registerCommand("cursor-fast", {
|
|
317
|
+
description: "Toggle Cursor fast mode for the selected Cursor model",
|
|
318
|
+
handler: async (_args, ctx) => {
|
|
319
|
+
const metadata = getCurrentCursorMetadata(ctx);
|
|
320
|
+
if (!metadata?.supportsFast || !ctx.model) {
|
|
321
|
+
const modelName = ctx.model?.id ?? "current model";
|
|
322
|
+
ctx.ui.notify(`Fast mode not supported by ${modelName}`, "info");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (cliForceNoFast) {
|
|
326
|
+
ctx.ui.notify("Cursor fast is forced off by --cursor-no-fast", "info");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (cliForceFast) {
|
|
330
|
+
ctx.ui.notify("Cursor fast is forced by --cursor-fast", "info");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (metadata.fastOverride !== undefined) {
|
|
334
|
+
const state = metadata.fastOverride ? "enabled" : "disabled";
|
|
335
|
+
ctx.ui.notify(`Cursor fast is fixed ${state} by selected model ${metadata.piModelId}; choose ${getVirtualFastBaseModelId(metadata.piModelId)} to use /cursor-fast preferences`, "info");
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const preferenceModelId = getFastPreferenceModelId(metadata);
|
|
339
|
+
const current = getEffectiveFast(metadata.piModelId) ?? false;
|
|
340
|
+
const next = !current;
|
|
341
|
+
let appendError;
|
|
342
|
+
try {
|
|
343
|
+
appendError = persistFastPreference(pi, preferenceModelId, next);
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
updateCursorStatus(ctx);
|
|
347
|
+
ctx.ui.notify(`Failed to save Cursor fast preference: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
updateCursorStatus(ctx);
|
|
351
|
+
if (appendError !== undefined) {
|
|
352
|
+
ctx.ui.notify(`Cursor fast ${next ? "enabled" : "disabled"} was saved globally, but persisting the session entry failed: ${appendError instanceof Error ? appendError.message : String(appendError)}`, "error");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
ctx.ui.notify(`Cursor fast ${next ? "enabled" : "disabled"}`, "info");
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
pi.registerCommand("cursor-tools", {
|
|
359
|
+
description: "Show live Cursor tool surfaces for this session (maintainer debug)",
|
|
360
|
+
handler: async (_args, ctx) => {
|
|
361
|
+
emitCursorToolsDebugReport(pi, ctx);
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
pi.registerCommand("cursor-http", {
|
|
365
|
+
description: "Toggle Cursor SDK HTTP/1.1/SSE transport compatibility mode",
|
|
366
|
+
handler: async (args, ctx) => {
|
|
367
|
+
const usage = "Usage: /cursor-http [on|off|toggle]";
|
|
368
|
+
const normalized = args.trim().toLowerCase();
|
|
369
|
+
const resolution = resolveCursorStatusRuntime(ctx);
|
|
370
|
+
if (resolution.kind === "invalid") {
|
|
371
|
+
ctx.ui.notify(resolution.message, "error");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const setting = resolution.useHttp1ForAgent;
|
|
375
|
+
if (!normalized) {
|
|
376
|
+
ctx.ui.notify(`Cursor HTTP/1.1/SSE transport is ${setting.value ? "enabled" : "disabled"} (source: ${setting.source}). ${usage}`, "info");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const next = normalized === "on"
|
|
380
|
+
? true
|
|
381
|
+
: normalized === "off"
|
|
382
|
+
? false
|
|
383
|
+
: normalized === "toggle"
|
|
384
|
+
? !setting.value
|
|
385
|
+
: undefined;
|
|
386
|
+
if (next === undefined) {
|
|
387
|
+
ctx.ui.notify(`Invalid Cursor HTTP transport mode "${args.trim()}". ${usage}`, "error");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
let appendError;
|
|
391
|
+
try {
|
|
392
|
+
appendError = persistCursorHttp1Preference(pi, next);
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
updateCursorStatus(ctx);
|
|
396
|
+
ctx.ui.notify(`Failed to save Cursor HTTP/1.1 preference: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
updateCursorStatus(ctx);
|
|
400
|
+
if (appendError !== undefined) {
|
|
401
|
+
ctx.ui.notify(`Cursor HTTP/1.1 preference was saved globally, but persisting the session entry failed: ${appendError instanceof Error ? appendError.message : String(appendError)}`, "error");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
ctx.ui.notify(`Cursor HTTP/1.1/SSE transport ${next ? "enabled" : "disabled"}`, "info");
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
pi.registerCommand("cursor-local-resume-cleanup", {
|
|
408
|
+
description: "Dry-run or delete recorded superseded local Cursor SDK agents",
|
|
409
|
+
handler: async (args, ctx) => {
|
|
410
|
+
await runCursorSessionAgentCleanupCommand(pi, args, ctx);
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
pi.registerCommand("cursor-refresh-config", {
|
|
414
|
+
description: "Refresh filesystem Cursor config in the current pooled SDK agent",
|
|
415
|
+
handler: async (_args, ctx) => {
|
|
416
|
+
if (!isCursorModel(ctx.model)) {
|
|
417
|
+
ctx.ui.notify("Cursor config refresh is available only for Cursor models.", "info");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
const result = await refreshSessionCursorAgentConfig();
|
|
422
|
+
const messages = {
|
|
423
|
+
reloaded: "Cursor SDK agent config refreshed.",
|
|
424
|
+
"no-agent": "No Cursor SDK agent exists yet; config will load on the next Cursor run.",
|
|
425
|
+
busy: "Cursor SDK agent is still finalizing a run; retry /cursor-refresh-config after it finishes.",
|
|
426
|
+
unsupported: "Current Cursor SDK agent does not support config reload.",
|
|
427
|
+
};
|
|
428
|
+
ctx.ui.notify(messages[result], result === "reloaded" ? "info" : "warning");
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
ctx.ui.notify(`Failed to refresh Cursor config: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
pi.registerCommand("cursor-mode", {
|
|
436
|
+
description: "Set Cursor SDK conversation mode: agent or plan",
|
|
437
|
+
handler: async (args, ctx) => {
|
|
438
|
+
const usage = "Usage: /cursor-mode agent|plan";
|
|
439
|
+
const mode = parseCursorAgentMode(args);
|
|
440
|
+
if (!args.trim()) {
|
|
441
|
+
const modeResolution = resolveCursorAgentMode();
|
|
442
|
+
if (modeResolution.kind === "invalid") {
|
|
443
|
+
ctx.ui.notify(`${modeResolution.message} ${usage}`, "error");
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
ctx.ui.notify(`Cursor mode is ${modeResolution.mode}. ${usage}`, "info");
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (!mode) {
|
|
451
|
+
ctx.ui.notify(`Invalid Cursor mode "${args.trim()}". ${usage}`, "error");
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (cliCursorModeState.kind === "valid") {
|
|
455
|
+
ctx.ui.notify(`Cursor mode is forced to ${cliCursorModeState.mode} by --cursor-mode`, "info");
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const clearedInvalidCliMode = cliCursorModeState.kind === "invalid";
|
|
459
|
+
try {
|
|
460
|
+
persistCursorModePreference(pi, mode);
|
|
461
|
+
if (clearedInvalidCliMode)
|
|
462
|
+
cliCursorModeState = { kind: "unset" };
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
updateCursorStatus(ctx);
|
|
466
|
+
ctx.ui.notify(`Failed to save Cursor mode preference: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
updateCursorStatus(ctx);
|
|
470
|
+
ctx.ui.notify(clearedInvalidCliMode
|
|
471
|
+
? `Cursor mode set to ${mode}; cleared invalid --cursor-mode override`
|
|
472
|
+
: `Cursor mode set to ${mode}`, "info");
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
476
|
+
restoreSessionCursorPreferences(ctx);
|
|
477
|
+
updateCursorStatus(ctx);
|
|
478
|
+
});
|
|
479
|
+
registerCursorModelLifecycle(pi, {
|
|
480
|
+
sessionStart: (_event, ctx) => {
|
|
481
|
+
authoritativeGlobalFastPreferenceIds.clear();
|
|
482
|
+
setCursorHttp1GlobalPreferenceAuthoritative(false);
|
|
483
|
+
globalFastPreferences = loadGlobalFastPreferences();
|
|
484
|
+
cliForceFast = pi.getFlag("cursor-fast") === true;
|
|
485
|
+
cliForceNoFast = pi.getFlag("cursor-no-fast") === true;
|
|
486
|
+
restoreCursorCliState(pi);
|
|
487
|
+
restoreSessionCursorPreferences(ctx);
|
|
488
|
+
restoreCliCursorMode(pi.getFlag("cursor-mode"));
|
|
489
|
+
},
|
|
490
|
+
sync: (ctx) => {
|
|
491
|
+
if (isCursorModel(ctx.model))
|
|
492
|
+
notifyInvalidCursorModeIfCursorActive(ctx);
|
|
493
|
+
updateCursorStatus(ctx);
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
function resetCursorModeStateForTests() {
|
|
498
|
+
sessionCursorAgentMode = undefined;
|
|
499
|
+
setCursorHttp1GlobalPreferenceAuthoritative(false);
|
|
500
|
+
setStoredCursorHttp1Enabled(undefined);
|
|
501
|
+
cliCursorModeState = { kind: "unset" };
|
|
502
|
+
resetCursorRuntimeStateForTests();
|
|
503
|
+
invalidCursorModeNotifiedSessionScopeKeys.clear();
|
|
504
|
+
authoritativeGlobalFastPreferenceIds.clear();
|
|
505
|
+
}
|
|
506
|
+
export const __testUtils = {
|
|
507
|
+
FAST_ENTRY_TYPE,
|
|
508
|
+
MODE_ENTRY_TYPE,
|
|
509
|
+
CURSOR_HTTP1_ENTRY_TYPE,
|
|
510
|
+
RUNTIME_ENTRY_TYPE: CURSOR_RUNTIME_ENTRY_TYPE,
|
|
511
|
+
DEFAULT_CURSOR_AGENT_MODE,
|
|
512
|
+
getConfigPath,
|
|
513
|
+
loadGlobalFastPreferences,
|
|
514
|
+
sessionFastPreferences,
|
|
515
|
+
getSessionCursorAgentMode: () => sessionCursorAgentMode,
|
|
516
|
+
getCliCursorAgentMode: () => (cliCursorModeState.kind === "valid" ? cliCursorModeState.mode : undefined),
|
|
517
|
+
getCliCursorModeState: () => cliCursorModeState,
|
|
518
|
+
resetCursorModeStateForTests,
|
|
519
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { asRecord, getBoolean, getRecord, getString } from "./cursor-record-utils.js";
|
|
2
|
+
export const CURSOR_TASK_PRESENTATION_ENV = "PI_CURSOR_TASK_PRESENTATION";
|
|
3
|
+
const VALID_CURSOR_TASK_PRESENTATION_MODES = new Set([
|
|
4
|
+
"task",
|
|
5
|
+
"subagent",
|
|
6
|
+
"subagent-meta",
|
|
7
|
+
]);
|
|
8
|
+
export function getCursorTaskPresentationMode(env = process.env) {
|
|
9
|
+
const raw = env[CURSOR_TASK_PRESENTATION_ENV]?.trim();
|
|
10
|
+
return raw && VALID_CURSOR_TASK_PRESENTATION_MODES.has(raw)
|
|
11
|
+
? raw
|
|
12
|
+
: "subagent-meta";
|
|
13
|
+
}
|
|
14
|
+
export function getCursorTaskDescription(args, resultValue) {
|
|
15
|
+
return getString(args, "description") ?? getString(asRecord(resultValue), "description") ?? "task";
|
|
16
|
+
}
|
|
17
|
+
export function readCursorTaskMetadata(args, resultValue) {
|
|
18
|
+
const subagentType = getRecord(args, "subagentType");
|
|
19
|
+
const result = asRecord(resultValue);
|
|
20
|
+
return {
|
|
21
|
+
description: getCursorTaskDescription(args, resultValue),
|
|
22
|
+
subagentKind: getString(subagentType, "kind"),
|
|
23
|
+
subagentName: getString(subagentType, "name"),
|
|
24
|
+
model: getString(args, "model"),
|
|
25
|
+
agentId: getString(args, "agentId") ?? getString(result, "agentId"),
|
|
26
|
+
isBackground: getBoolean(result, "isBackground"),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function cleanMetadataValue(value) {
|
|
30
|
+
const trimmed = value?.trim();
|
|
31
|
+
return trimmed || undefined;
|
|
32
|
+
}
|
|
33
|
+
export function getCursorTaskActivityTitle() {
|
|
34
|
+
return getCursorTaskPresentationMode() === "task" ? "Cursor task" : "Cursor subagent";
|
|
35
|
+
}
|
|
36
|
+
export function getCursorTaskTranscriptHeader(args, resultValue) {
|
|
37
|
+
const metadata = readCursorTaskMetadata(args, resultValue);
|
|
38
|
+
const description = cleanMetadataValue(metadata.description) ?? "task";
|
|
39
|
+
const mode = getCursorTaskPresentationMode();
|
|
40
|
+
if (mode === "task")
|
|
41
|
+
return `task ${description}`;
|
|
42
|
+
if (mode === "subagent-meta") {
|
|
43
|
+
const subagentName = cleanMetadataValue(metadata.subagentName);
|
|
44
|
+
return subagentName ? `subagent ${subagentName} ${description}` : `subagent ${description}`;
|
|
45
|
+
}
|
|
46
|
+
return `subagent ${description}`;
|
|
47
|
+
}
|
|
48
|
+
export function formatCursorTaskKind(value) {
|
|
49
|
+
const cleaned = cleanMetadataValue(value);
|
|
50
|
+
if (!cleaned)
|
|
51
|
+
return undefined;
|
|
52
|
+
return cleaned.slice(0, 1).toUpperCase() + cleaned.slice(1);
|
|
53
|
+
}
|
|
54
|
+
export function formatCursorTaskAgentId(value) {
|
|
55
|
+
const cleaned = cleanMetadataValue(value);
|
|
56
|
+
if (!cleaned || !/^[A-Za-z0-9_.:-]+$/.test(cleaned))
|
|
57
|
+
return undefined;
|
|
58
|
+
return cleaned.length > 12 ? cleaned.slice(0, 8) : cleaned;
|
|
59
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
2
|
+
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
3
|
+
import { getCursorToolLifecycleLabelKind } from "./cursor-tool-presentation-registry.js";
|
|
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 } from "./cursor-tool-visibility.js";
|
|
8
|
+
/** Defer pending lifecycle lines so fast start+complete pairs coalesce into the completed replay card only. */
|
|
9
|
+
export const CURSOR_TOOL_LIFECYCLE_DEFER_MS = 75;
|
|
10
|
+
export function isCursorToolLifecycleEligible(toolCall) {
|
|
11
|
+
return classifyCursorToolVisibility(toolCall).lifecycleEligible;
|
|
12
|
+
}
|
|
13
|
+
function getCursorToolLifecycleTitle(visibility) {
|
|
14
|
+
return visibility.lifecycleTitle ?? `Cursor ${visibility.normalizedName}`;
|
|
15
|
+
}
|
|
16
|
+
/** Prefixes that commonly introduce path/URI values in free-text pending lifecycle details. */
|
|
17
|
+
const LIFECYCLE_DETAIL_PATH_PREFIX = String.raw `(?:^|[\s'"({=,:;\[\]{}])`;
|
|
18
|
+
function containsCursorLifecycleUnsafeDetail(text) {
|
|
19
|
+
if (/\b[a-z][a-z0-9+.-]*:\/\//i.test(text))
|
|
20
|
+
return true;
|
|
21
|
+
if (/\bwww\.\S+/i.test(text))
|
|
22
|
+
return true;
|
|
23
|
+
if (new RegExp(`${LIFECYCLE_DETAIL_PATH_PREFIX}~\\/\\S*`).test(text))
|
|
24
|
+
return true;
|
|
25
|
+
if (new RegExp(`${LIFECYCLE_DETAIL_PATH_PREFIX}\\/\\S+`).test(text))
|
|
26
|
+
return true;
|
|
27
|
+
if (new RegExp(`${LIFECYCLE_DETAIL_PATH_PREFIX}[A-Za-z]:[\\\\/]`).test(text))
|
|
28
|
+
return true;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
function scrubLifecycleDetail(value, apiKey) {
|
|
32
|
+
if (!value?.trim())
|
|
33
|
+
return undefined;
|
|
34
|
+
const scrubbed = truncateCursorDisplayLine(scrubSensitiveText(value, apiKey));
|
|
35
|
+
if (containsCursorLifecycleUnsafeDetail(scrubbed))
|
|
36
|
+
return undefined;
|
|
37
|
+
return scrubbed;
|
|
38
|
+
}
|
|
39
|
+
function scrubShellLifecycleDetail(value, apiKey) {
|
|
40
|
+
if (!value?.trim())
|
|
41
|
+
return undefined;
|
|
42
|
+
return truncateCursorDisplayLine(scrubSensitiveText(value, apiKey));
|
|
43
|
+
}
|
|
44
|
+
function buildCursorToolLifecycleLabelFromVisibility(visibility, apiKey) {
|
|
45
|
+
const args = visibility.args;
|
|
46
|
+
switch (getCursorToolLifecycleLabelKind(visibility.normalizedKey)) {
|
|
47
|
+
case "task": {
|
|
48
|
+
return scrubLifecycleDetail(getString(args, "description"), apiKey) ?? "task";
|
|
49
|
+
}
|
|
50
|
+
case "shell": {
|
|
51
|
+
return scrubShellLifecycleDetail(getString(args, "command") ?? getString(args, "cmd"), apiKey);
|
|
52
|
+
}
|
|
53
|
+
case "mcp": {
|
|
54
|
+
return scrubLifecycleDetail(getString(args, "toolName"), apiKey) ?? "mcp";
|
|
55
|
+
}
|
|
56
|
+
case "generateImage": {
|
|
57
|
+
return scrubLifecycleDetail(getString(args, "prompt") ?? getString(args, "description"), apiKey) ?? "image generation";
|
|
58
|
+
}
|
|
59
|
+
case "recordScreen": {
|
|
60
|
+
return scrubLifecycleDetail(getString(args, "mode"), apiKey) ?? "screen recording";
|
|
61
|
+
}
|
|
62
|
+
case "semSearch": {
|
|
63
|
+
return scrubLifecycleDetail(getString(args, "query"), apiKey) ?? "semantic search";
|
|
64
|
+
}
|
|
65
|
+
case "webSearch": {
|
|
66
|
+
return scrubLifecycleDetail(extractWebSearchQuery(args), apiKey) ?? "web search";
|
|
67
|
+
}
|
|
68
|
+
case "webFetch": {
|
|
69
|
+
return "web fetch";
|
|
70
|
+
}
|
|
71
|
+
case "createPlan": {
|
|
72
|
+
const plan = getString(args, "plan");
|
|
73
|
+
return scrubLifecycleDetail(plan ? firstNonEmptyLine(plan) ?? plan : undefined, apiKey) ?? "plan";
|
|
74
|
+
}
|
|
75
|
+
case "updateTodos": {
|
|
76
|
+
const todos = getArray(args, "todos") ?? getArray(args, "items");
|
|
77
|
+
if (todos && todos.length > 0)
|
|
78
|
+
return truncateArg(`${todos.length} item${todos.length === 1 ? "" : "s"}`);
|
|
79
|
+
return "todos";
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function buildCursorToolLifecycleLabel(toolCall, apiKey) {
|
|
86
|
+
return buildCursorToolLifecycleLabelFromVisibility(classifyCursorToolVisibility(toolCall), apiKey);
|
|
87
|
+
}
|
|
88
|
+
export function formatCursorToolLifecycleProgressText(toolCall, apiKey) {
|
|
89
|
+
const visibility = classifyCursorToolVisibility(toolCall);
|
|
90
|
+
const label = buildCursorToolLifecycleLabelFromVisibility(visibility, apiKey);
|
|
91
|
+
if (!label)
|
|
92
|
+
return undefined;
|
|
93
|
+
return `${getCursorToolLifecycleTitle(visibility)}: ${label}\n`;
|
|
94
|
+
}
|