pi-cursor-sdk 0.1.40 → 0.1.42
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 +25 -0
- package/README.md +12 -9
- package/docs/cursor-dogfood-checklist.md +6 -0
- package/docs/cursor-live-smoke-checklist.md +4 -4
- package/docs/cursor-model-ux-spec.md +6 -6
- package/docs/cursor-native-tool-replay.md +11 -7
- package/docs/cursor-native-tool-visual-audit.md +2 -2
- package/docs/cursor-testing-lessons.md +1 -1
- package/docs/cursor-tool-surfaces.md +4 -0
- package/docs/platform-smoke.md +9 -1
- package/package.json +8 -5
- package/scripts/lib/cursor-visual-manifest.d.mts +3 -0
- package/scripts/lib/cursor-visual-manifest.mjs +82 -0
- package/scripts/platform-smoke/artifacts.mjs +147 -2
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/scripts/platform-smoke/doctor.mjs +53 -8
- package/scripts/platform-smoke/scenarios.mjs +1 -1
- package/scripts/platform-smoke.mjs +69 -7
- package/scripts/visual-tui-smoke-self-test.mjs +229 -0
- package/scripts/visual-tui-smoke.mjs +45 -179
- package/src/context.ts +25 -10
- package/src/cursor-active-tools.ts +7 -0
- package/src/cursor-compact-tool-summary.ts +81 -0
- package/src/cursor-native-tool-display-registration.ts +31 -21
- package/src/cursor-native-tool-display-replay.ts +13 -2
- package/src/cursor-native-tool-display-state.ts +13 -4
- package/src/cursor-pi-tool-bridge-run.ts +6 -3
- package/src/cursor-pi-tool-bridge-types.ts +2 -2
- package/src/cursor-provider-errors.ts +2 -1
- package/src/cursor-provider-live-run-drain.ts +1 -1
- package/src/cursor-provider-turn-prepare.ts +1 -1
- package/src/cursor-provider-turn-send.ts +2 -0
- package/src/cursor-question-tool.ts +2 -1
- package/src/cursor-replay-activity-builders.ts +12 -4
- package/src/cursor-replay-summary-args.ts +21 -2
- package/src/cursor-sdk-event-debug.ts +3 -1
- package/src/cursor-skill-tool.ts +2 -1
- package/src/cursor-task-presentation.ts +77 -0
- package/src/cursor-tool-manifest.ts +2 -1
- package/src/cursor-tool-presentation-registry.ts +16 -2
- package/src/cursor-tool-result-display-readers.ts +13 -8
- package/src/cursor-transcript-tool-formatters.ts +5 -5
- package/src/cursor-usage-accounting.ts +5 -4
|
@@ -150,7 +150,8 @@ export class CursorPiToolBridgeRunImpl implements CursorPiToolBridgeRun {
|
|
|
150
150
|
this.debugRecorder = recorder;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
resolveToolResults(toolResults: readonly ToolResultMessage[]): void {
|
|
153
|
+
async resolveToolResults(toolResults: readonly ToolResultMessage[]): Promise<void> {
|
|
154
|
+
let resolvedCount = 0;
|
|
154
155
|
for (const toolResult of toolResults) {
|
|
155
156
|
const pending = this.pendingByPiToolCallId.get(toolResult.toolCallId);
|
|
156
157
|
if (!pending || pending.settled) continue;
|
|
@@ -158,11 +159,13 @@ export class CursorPiToolBridgeRunImpl implements CursorPiToolBridgeRun {
|
|
|
158
159
|
content: convertPiContentToMcpContent(toolResult.content),
|
|
159
160
|
isError: toolResult.isError || undefined,
|
|
160
161
|
});
|
|
162
|
+
resolvedCount += 1;
|
|
161
163
|
}
|
|
164
|
+
if (resolvedCount > 0) await waitForProtocolFlush();
|
|
162
165
|
}
|
|
163
166
|
|
|
164
|
-
resolveToolResultsFromContext(context: Context): void {
|
|
165
|
-
this.resolveToolResults(context.messages.map(asToolResultMessage).filter((message): message is ToolResultMessage => message !== undefined));
|
|
167
|
+
async resolveToolResultsFromContext(context: Context): Promise<void> {
|
|
168
|
+
await this.resolveToolResults(context.messages.map(asToolResultMessage).filter((message): message is ToolResultMessage => message !== undefined));
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
hasPendingPiToolCallId(piToolCallId: string): boolean {
|
|
@@ -61,8 +61,8 @@ export interface CursorPiToolBridgeRun {
|
|
|
61
61
|
mcpServers?: Record<string, McpServerConfig>;
|
|
62
62
|
snapshot: CursorPiToolBridgeSnapshot;
|
|
63
63
|
takeQueuedToolRequests(): CursorPiBridgeToolRequest[];
|
|
64
|
-
resolveToolResults(toolResults: readonly ToolResultMessage[]): void
|
|
65
|
-
resolveToolResultsFromContext(context: Context): void
|
|
64
|
+
resolveToolResults(toolResults: readonly ToolResultMessage[]): Promise<void>;
|
|
65
|
+
resolveToolResultsFromContext(context: Context): Promise<void>;
|
|
66
66
|
hasPendingPiToolCallId(piToolCallId: string): boolean;
|
|
67
67
|
isBridgeMcpToolCall(toolCall: unknown): boolean;
|
|
68
68
|
setOnToolRequest(handler?: (request: CursorPiBridgeToolRequest) => void): void;
|
|
@@ -15,7 +15,7 @@ const NETWORK_CURSOR_SDK_ERROR_MESSAGE =
|
|
|
15
15
|
// Keep this phrase aligned with pi's agent-level retry classifier (`provider.?returned.?error`).
|
|
16
16
|
const RETRYABLE_CURSOR_RUN_FAILURE_PREFIX = "Provider returned error: Cursor SDK run failed";
|
|
17
17
|
|
|
18
|
-
export type CursorSdkRunFailureSource = Pick<RunResult, "id" | "status" | "durationMs" | "model" | "result">;
|
|
18
|
+
export type CursorSdkRunFailureSource = Pick<RunResult, "id" | "requestId" | "status" | "durationMs" | "model" | "result">;
|
|
19
19
|
|
|
20
20
|
function isGenericErrorMessage(message: string): boolean {
|
|
21
21
|
const normalized = message.trim().toLowerCase();
|
|
@@ -159,6 +159,7 @@ export function formatCursorSdkRunFailureDetail(result: CursorSdkRunFailureSourc
|
|
|
159
159
|
const parts = [RETRYABLE_CURSOR_RUN_FAILURE_PREFIX];
|
|
160
160
|
if (result.model?.id) parts.push(`model ${result.model.id}`);
|
|
161
161
|
parts.push(`run ${shortRunId(result.id)}`);
|
|
162
|
+
if (result.requestId) parts.push(`request ${shortRunId(result.requestId)}`);
|
|
162
163
|
if (typeof result.durationMs === "number") parts.push(`${result.durationMs}ms`);
|
|
163
164
|
return parts.join(" · ");
|
|
164
165
|
}
|
|
@@ -414,7 +414,7 @@ export async function drainExistingCursorLiveRunBeforeSend(
|
|
|
414
414
|
const outcome = await cursorLiveRuns.withRunLease(run, signal, async () => {
|
|
415
415
|
if (run.disposed) return "continue_send" as const;
|
|
416
416
|
const consumed = cursorLiveRuns.consumeToolResults(run, context, getCursorNativeReplayIdFromToolCallId);
|
|
417
|
-
run.bridgeRun?.resolveToolResults(consumed.toolResults);
|
|
417
|
+
await run.bridgeRun?.resolveToolResults(consumed.toolResults);
|
|
418
418
|
const shouldChainUserInput = run.chainUserInputAfterCompletion || hasTrailingUserMessagesAfterToolResults(context);
|
|
419
419
|
if (shouldChainUserInput) run.chainUserInputAfterCompletion = true;
|
|
420
420
|
while (!cursorLiveRuns.isReady(run)) {
|
|
@@ -89,7 +89,7 @@ export async function prepareCursorProviderTurn(
|
|
|
89
89
|
throwIfAborted();
|
|
90
90
|
|
|
91
91
|
const buildPromptOptions = (plan: ReturnType<typeof planCursorSessionSend>) => {
|
|
92
|
-
const promptOptions = getCursorPromptOptions(model);
|
|
92
|
+
const promptOptions = { ...getCursorPromptOptions(model), agentMode };
|
|
93
93
|
if (plan.mode !== "bootstrap" || !resolveCursorToolManifestEnabled()) {
|
|
94
94
|
return promptOptions;
|
|
95
95
|
}
|
|
@@ -78,12 +78,14 @@ export async function sendCursorProviderTurn(sendParams: SendCursorProviderTurnP
|
|
|
78
78
|
sdkRun = run;
|
|
79
79
|
sdkEventDebug?.recordRunMeta({
|
|
80
80
|
runId: run.id,
|
|
81
|
+
requestId: run.requestId,
|
|
81
82
|
agentId: run.agentId,
|
|
82
83
|
status: run.status,
|
|
83
84
|
});
|
|
84
85
|
sdkEventDebug?.attachRunStream(run);
|
|
85
86
|
sdkEventDebug?.recordProviderEvent("agent_send_returned", {
|
|
86
87
|
runId: run.id,
|
|
88
|
+
requestId: run.requestId,
|
|
87
89
|
agentId: run.agentId,
|
|
88
90
|
status: run.status,
|
|
89
91
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
+
import { arePiToolsDisabled } from "./cursor-active-tools.js";
|
|
4
5
|
import { isCursorModel } from "./cursor-model.js";
|
|
5
6
|
import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
|
|
6
7
|
import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
|
|
@@ -175,7 +176,7 @@ async function askOneQuestion(question: CursorQuestion, ctx: { ui: ExtensionCont
|
|
|
175
176
|
|
|
176
177
|
function syncCursorQuestionToolForModel(pi: Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">, model: ExtensionContext["model"]): void {
|
|
177
178
|
const activeToolNames = new Set(pi.getActiveTools());
|
|
178
|
-
const shouldBeActive = isCursorModel(model) && resolveCursorPiToolBridgeEnabled();
|
|
179
|
+
const shouldBeActive = !arePiToolsDisabled(pi) && isCursorModel(model) && resolveCursorPiToolBridgeEnabled();
|
|
179
180
|
const alreadyActive = activeToolNames.has(CURSOR_ASK_QUESTION_TOOL_NAME);
|
|
180
181
|
if (shouldBeActive === alreadyActive) return;
|
|
181
182
|
if (shouldBeActive) {
|
|
@@ -21,12 +21,12 @@ import {
|
|
|
21
21
|
readMcpDisplayResult,
|
|
22
22
|
getReadLintDiagnostics,
|
|
23
23
|
getReadLintPaths,
|
|
24
|
-
getTaskDescription,
|
|
25
24
|
getTodoItems,
|
|
26
25
|
getTodoTotalCount,
|
|
27
26
|
inferImageMimeType,
|
|
28
27
|
} from "./cursor-tool-result-display-readers.js";
|
|
29
28
|
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-args.js";
|
|
29
|
+
import { formatCursorTaskAgentId, formatCursorTaskKind, getCursorTaskDescription, getCursorTaskPresentationMode, readCursorTaskMetadata } from "./cursor-task-presentation.js";
|
|
30
30
|
|
|
31
31
|
export interface CursorReplayActivityBuildContext {
|
|
32
32
|
args: Record<string, unknown>;
|
|
@@ -88,12 +88,20 @@ export function buildCreatePlanReplaySummaryArgs({ args, result }: CursorReplayA
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
export function buildTaskReplaySummaryArgs({ args, result }: CursorReplayActivityBuildContext): CursorReplayTaskSummaryArgs {
|
|
92
|
-
const description =
|
|
93
|
-
const preview = firstNonEmptyLine(collectTaskText(result));
|
|
91
|
+
export function buildTaskReplaySummaryArgs({ args, result, options }: CursorReplayActivityBuildContext): CursorReplayTaskSummaryArgs {
|
|
92
|
+
const description = getCursorTaskDescription(args, result.value);
|
|
93
|
+
const preview = firstNonEmptyLine(collectTaskText(result, options));
|
|
94
|
+
const metadata = readCursorTaskMetadata(args, result.value);
|
|
95
|
+
const displayAgentId = formatCursorTaskAgentId(metadata.agentId);
|
|
96
|
+
const includeMetadata = getCursorTaskPresentationMode() === "subagent-meta";
|
|
94
97
|
return {
|
|
95
98
|
description: truncateArg(description),
|
|
96
99
|
...(preview ? { preview: truncateArg(preview) } : {}),
|
|
100
|
+
...(includeMetadata && metadata.subagentName ? { subagentName: truncateArg(metadata.subagentName) } : {}),
|
|
101
|
+
...(includeMetadata && metadata.subagentKind ? { subagentKind: truncateArg(formatCursorTaskKind(metadata.subagentKind) ?? metadata.subagentKind) } : {}),
|
|
102
|
+
...(includeMetadata && metadata.model ? { model: truncateArg(metadata.model) } : {}),
|
|
103
|
+
...(includeMetadata && displayAgentId ? { agentId: truncateArg(displayAgentId) } : {}),
|
|
104
|
+
...(includeMetadata && metadata.isBackground === true ? { isBackground: true } : {}),
|
|
97
105
|
};
|
|
98
106
|
}
|
|
99
107
|
|
|
@@ -31,6 +31,11 @@ export interface CursorReplayPlanSummaryArgs extends CursorReplayTodoSummaryArgs
|
|
|
31
31
|
export interface CursorReplayTaskSummaryArgs extends CursorReplayActivitySummaryOverride {
|
|
32
32
|
description?: string;
|
|
33
33
|
preview?: string;
|
|
34
|
+
subagentName?: string;
|
|
35
|
+
subagentKind?: string;
|
|
36
|
+
model?: string;
|
|
37
|
+
agentId?: string;
|
|
38
|
+
isBackground?: boolean;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
export interface CursorReplayGenerateImageSummaryArgs extends CursorReplayActivitySummaryOverride {
|
|
@@ -150,8 +155,22 @@ export function summarizeReplayPlan(args: CursorReplayPlanSummaryArgs | undefine
|
|
|
150
155
|
export function summarizeReplayTask(args: CursorReplayTaskSummaryArgs | undefined): string | undefined {
|
|
151
156
|
const description = readCursorReplaySummaryString(args, "description");
|
|
152
157
|
const preview = readCursorReplaySummaryString(args, "preview");
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
const subagentName = readCursorReplaySummaryString(args, "subagentName");
|
|
159
|
+
const subagentKind = readCursorReplaySummaryString(args, "subagentKind");
|
|
160
|
+
const model = readCursorReplaySummaryString(args, "model");
|
|
161
|
+
const agentId = readCursorReplaySummaryString(args, "agentId");
|
|
162
|
+
const metadataParts = [
|
|
163
|
+
subagentKind,
|
|
164
|
+
model,
|
|
165
|
+
agentId ? `ID: ${agentId}` : undefined,
|
|
166
|
+
args?.isBackground === true ? "backgrounded" : undefined,
|
|
167
|
+
].filter((part): part is string => Boolean(part));
|
|
168
|
+
const subjectParts = [description].filter((part): part is string => Boolean(part));
|
|
169
|
+
const subject = subjectParts.length > 0 ? subjectParts.join(" · ") : undefined;
|
|
170
|
+
const head = metadataParts.length > 0 ? [subject, ...metadataParts].filter(Boolean).join(" · ") : subject;
|
|
171
|
+
if (metadataParts.length > 0) return head;
|
|
172
|
+
if (head && preview && preview !== description && preview !== subagentName) return `${head}: ${preview}`;
|
|
173
|
+
return head ?? preview;
|
|
155
174
|
}
|
|
156
175
|
|
|
157
176
|
export function summarizeReplayMcp(args: CursorReplayMcpSummaryArgs | undefined): string | undefined {
|
|
@@ -78,12 +78,14 @@ export interface CursorSdkEventDebugSendMeta {
|
|
|
78
78
|
|
|
79
79
|
export interface CursorSdkEventDebugRunMeta {
|
|
80
80
|
runId: string;
|
|
81
|
+
requestId?: string;
|
|
81
82
|
agentId: string;
|
|
82
83
|
status: string;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
interface CursorSdkRunLike {
|
|
86
87
|
id: string;
|
|
88
|
+
requestId?: string;
|
|
87
89
|
agentId?: string;
|
|
88
90
|
status?: string;
|
|
89
91
|
stream?: () => AsyncIterable<unknown>;
|
|
@@ -373,7 +375,7 @@ export class CursorSdkEventDebugSink {
|
|
|
373
375
|
attachRunStream(run: unknown): void {
|
|
374
376
|
const sdkRun = run as CursorSdkRunLike;
|
|
375
377
|
if (typeof sdkRun.stream !== "function") {
|
|
376
|
-
this.recordProviderEvent("run_stream_unavailable", { runId: sdkRun.id });
|
|
378
|
+
this.recordProviderEvent("run_stream_unavailable", { runId: sdkRun.id, requestId: sdkRun.requestId });
|
|
377
379
|
return;
|
|
378
380
|
}
|
|
379
381
|
this.streamCapturePromise = (async () => {
|
package/src/cursor-skill-tool.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
Skill,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { Type } from "typebox";
|
|
11
|
+
import { arePiToolsDisabled } from "./cursor-active-tools.js";
|
|
11
12
|
import { isCursorModel } from "./cursor-model.js";
|
|
12
13
|
import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
|
|
13
14
|
import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
|
|
@@ -62,7 +63,7 @@ function shouldExposeSkillTool(model: ExtensionContext["model"]): boolean {
|
|
|
62
63
|
|
|
63
64
|
function syncCursorSkillToolForModel(pi: Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">, model: ExtensionContext["model"]): void {
|
|
64
65
|
const activeToolNames = new Set(pi.getActiveTools());
|
|
65
|
-
const shouldBeActive = shouldExposeSkillTool(model);
|
|
66
|
+
const shouldBeActive = !arePiToolsDisabled(pi) && shouldExposeSkillTool(model);
|
|
66
67
|
const alreadyActive = activeToolNames.has(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
67
68
|
if (shouldBeActive === alreadyActive) return;
|
|
68
69
|
if (shouldBeActive) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { asRecord, getBoolean, getRecord, getString } from "./cursor-record-utils.js";
|
|
2
|
+
|
|
3
|
+
export const CURSOR_TASK_PRESENTATION_ENV = "PI_CURSOR_TASK_PRESENTATION";
|
|
4
|
+
|
|
5
|
+
export type CursorTaskPresentationMode = "task" | "subagent" | "subagent-meta";
|
|
6
|
+
|
|
7
|
+
const VALID_CURSOR_TASK_PRESENTATION_MODES = new Set<CursorTaskPresentationMode>([
|
|
8
|
+
"task",
|
|
9
|
+
"subagent",
|
|
10
|
+
"subagent-meta",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function getCursorTaskPresentationMode(env: NodeJS.ProcessEnv = process.env): CursorTaskPresentationMode {
|
|
14
|
+
const raw = env[CURSOR_TASK_PRESENTATION_ENV]?.trim();
|
|
15
|
+
return raw && VALID_CURSOR_TASK_PRESENTATION_MODES.has(raw as CursorTaskPresentationMode)
|
|
16
|
+
? (raw as CursorTaskPresentationMode)
|
|
17
|
+
: "subagent-meta";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CursorTaskMetadata {
|
|
21
|
+
description?: string;
|
|
22
|
+
subagentKind?: string;
|
|
23
|
+
subagentName?: string;
|
|
24
|
+
model?: string;
|
|
25
|
+
agentId?: string;
|
|
26
|
+
isBackground?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getCursorTaskDescription(args: Record<string, unknown>, resultValue?: unknown): string {
|
|
30
|
+
return getString(args, "description") ?? getString(asRecord(resultValue), "description") ?? "task";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function readCursorTaskMetadata(args: Record<string, unknown>, resultValue?: unknown): CursorTaskMetadata {
|
|
34
|
+
const subagentType = getRecord(args, "subagentType");
|
|
35
|
+
const result = asRecord(resultValue);
|
|
36
|
+
return {
|
|
37
|
+
description: getCursorTaskDescription(args, resultValue),
|
|
38
|
+
subagentKind: getString(subagentType, "kind"),
|
|
39
|
+
subagentName: getString(subagentType, "name"),
|
|
40
|
+
model: getString(args, "model"),
|
|
41
|
+
agentId: getString(args, "agentId") ?? getString(result, "agentId"),
|
|
42
|
+
isBackground: getBoolean(result, "isBackground"),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function cleanMetadataValue(value: string | undefined): string | undefined {
|
|
47
|
+
const trimmed = value?.trim();
|
|
48
|
+
return trimmed || undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getCursorTaskActivityTitle(): string {
|
|
52
|
+
return getCursorTaskPresentationMode() === "task" ? "Cursor task" : "Cursor subagent";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getCursorTaskTranscriptHeader(args: Record<string, unknown>, resultValue?: unknown): string {
|
|
56
|
+
const metadata = readCursorTaskMetadata(args, resultValue);
|
|
57
|
+
const description = cleanMetadataValue(metadata.description) ?? "task";
|
|
58
|
+
const mode = getCursorTaskPresentationMode();
|
|
59
|
+
if (mode === "task") return `task ${description}`;
|
|
60
|
+
if (mode === "subagent-meta") {
|
|
61
|
+
const subagentName = cleanMetadataValue(metadata.subagentName);
|
|
62
|
+
return subagentName ? `subagent ${subagentName} ${description}` : `subagent ${description}`;
|
|
63
|
+
}
|
|
64
|
+
return `subagent ${description}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function formatCursorTaskKind(value: string | undefined): string | undefined {
|
|
68
|
+
const cleaned = cleanMetadataValue(value);
|
|
69
|
+
if (!cleaned) return undefined;
|
|
70
|
+
return cleaned.slice(0, 1).toUpperCase() + cleaned.slice(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function formatCursorTaskAgentId(value: string | undefined): string | undefined {
|
|
74
|
+
const cleaned = cleanMetadataValue(value);
|
|
75
|
+
if (!cleaned || !/^[A-Za-z0-9_.:-]+$/.test(cleaned)) return undefined;
|
|
76
|
+
return cleaned.length > 12 ? cleaned.slice(0, 8) : cleaned;
|
|
77
|
+
}
|
|
@@ -4,7 +4,7 @@ import type { CursorPiToolBridgeSnapshot } from "./cursor-pi-tool-bridge-types.j
|
|
|
4
4
|
export const CURSOR_TOOL_MANIFEST_ENV = "PI_CURSOR_TOOL_MANIFEST";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Representative @cursor/sdk@1.0.
|
|
7
|
+
* Representative @cursor/sdk@1.0.18 local-agent ToolType values; actual exposure can vary by run.
|
|
8
8
|
* See docs/cursor-native-tool-replay.md#sdk-tooltype-replay-matrix.
|
|
9
9
|
*/
|
|
10
10
|
export const CURSOR_HOST_TOOL_MANIFEST_SUMMARY =
|
|
@@ -26,6 +26,7 @@ export function buildCursorToolManifestText(options: {
|
|
|
26
26
|
"Callable tool surfaces this run:",
|
|
27
27
|
`- Cursor SDK host tools (callable; not listed in MCP listTools): ${CURSOR_HOST_TOOL_MANIFEST_SUMMARY}.`,
|
|
28
28
|
"- Configured Cursor MCP servers: discovered at runtime via MCP listTools (depends on Cursor settings and PI_CURSOR_SETTING_SOURCES).",
|
|
29
|
+
"- Pi CLI tool toggles such as --no-tools affect pi tools and bridge exposure only; they do not disable Cursor SDK host tools or configured Cursor MCP.",
|
|
29
30
|
];
|
|
30
31
|
const bridgeTools = options.bridgeSnapshot?.tools ?? [];
|
|
31
32
|
if (!piBridgeEnabled) {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type CursorReplayWebSearchSummaryArgs,
|
|
24
24
|
} from "./cursor-replay-summary-args.js";
|
|
25
25
|
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
26
|
+
import { getCursorTaskActivityTitle } from "./cursor-task-presentation.js";
|
|
26
27
|
import {
|
|
27
28
|
buildCreatePlanReplaySummaryArgs,
|
|
28
29
|
buildDeleteReplayDetailFields,
|
|
@@ -75,6 +76,10 @@ export interface CursorToolVisibilityPolicy {
|
|
|
75
76
|
fastLocalDiscovery?: boolean;
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
export interface CursorToolReplayDisplayPolicy {
|
|
80
|
+
showCollapsedExpandHint?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
export interface CursorToolActivityReplaySpec<TArgs extends CursorReplaySummaryArgs = CursorReplaySummaryArgs> {
|
|
79
84
|
buildActivityArgs: (context: CursorReplayActivityBuildContext) => TArgs;
|
|
80
85
|
buildDetails: (context: CursorReplayActivityBuildContext, contentText: string) => CursorReplayActivityDetailFields;
|
|
@@ -90,7 +95,9 @@ export interface CursorToolPresentationSpec {
|
|
|
90
95
|
/** Raw SDK/host names that resolve to this tool via {@link normalizeCursorToolName}. */
|
|
91
96
|
nameAliases?: readonly string[];
|
|
92
97
|
displayLabel: string;
|
|
98
|
+
getActivityTitle?: () => string;
|
|
93
99
|
visibility: CursorToolVisibilityPolicy;
|
|
100
|
+
replayDisplay?: CursorToolReplayDisplayPolicy;
|
|
94
101
|
webKind?: CursorWebToolKind;
|
|
95
102
|
/** Regexes matched against lowercased trimmed tool names for {@link classifyCursorWebToolKind}. */
|
|
96
103
|
webNamePatterns?: readonly RegExp[];
|
|
@@ -209,8 +216,10 @@ export const CURSOR_TOOL_PRESENTATION_SPECS = [
|
|
|
209
216
|
},
|
|
210
217
|
{
|
|
211
218
|
normalizedName: "task",
|
|
212
|
-
displayLabel: "Cursor
|
|
219
|
+
displayLabel: "Cursor subagent",
|
|
220
|
+
getActivityTitle: getCursorTaskActivityTitle,
|
|
213
221
|
visibility: { lifecycleEligible: true },
|
|
222
|
+
replayDisplay: { showCollapsedExpandHint: true },
|
|
214
223
|
lifecycleLabelKind: "task",
|
|
215
224
|
replayCallSummary: withActivitySummaryFallback(summarizeReplayTask),
|
|
216
225
|
activityReplay: {
|
|
@@ -381,7 +390,7 @@ export function getCursorReplayPromptLabel(toolName: string): string {
|
|
|
381
390
|
export function getCursorReplayActivityTitle(toolName: string): string | undefined {
|
|
382
391
|
const spec = getCursorToolPresentationSpec(toolName);
|
|
383
392
|
if (!spec || !hasNeutralActivityTitle(spec)) return undefined;
|
|
384
|
-
return spec.displayLabel;
|
|
393
|
+
return spec.getActivityTitle?.() ?? spec.displayLabel;
|
|
385
394
|
}
|
|
386
395
|
|
|
387
396
|
function buildCursorGenericActivityTitle(displayName: string): string {
|
|
@@ -418,6 +427,11 @@ export function getCursorToolGenerateImageReplaySpec(normalizedKey: string): Cur
|
|
|
418
427
|
return getCursorToolPresentationSpecByNormalizedKey(normalizedKey)?.generateImageReplay;
|
|
419
428
|
}
|
|
420
429
|
|
|
430
|
+
export function shouldShowCursorReplayCollapsedExpandHint(normalizedKey: string | undefined): boolean {
|
|
431
|
+
if (!normalizedKey) return false;
|
|
432
|
+
return getCursorToolPresentationSpecByNormalizedKey(normalizedKey)?.replayDisplay?.showCollapsedExpandHint === true;
|
|
433
|
+
}
|
|
434
|
+
|
|
421
435
|
export function getCursorReplayCallSummary(
|
|
422
436
|
toolName: CursorReplayToolName | CursorReplaySourceToolName,
|
|
423
437
|
args: CursorReplaySummaryArgs | undefined,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { asRecord, getArray, getNumber, getRecord, getString, stringifyUnknown } from "./cursor-record-utils.js";
|
|
2
|
+
import { summarizeCursorCompactConversationToolCall } from "./cursor-compact-tool-summary.js";
|
|
2
3
|
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
3
4
|
import { firstNonEmptyLine, formatDisplayPath, truncateArg } from "./cursor-transcript-utils.js";
|
|
4
5
|
|
|
@@ -70,10 +71,6 @@ export function getTodoTotalCount(args: Record<string, unknown>, result: CursorT
|
|
|
70
71
|
return getNumber(asRecord(result.value), "totalCount") ?? getNumber(args, "totalCount") ?? todos.length;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
export function getTaskDescription(args: Record<string, unknown>, result: CursorToolResultLike): string {
|
|
74
|
-
return getString(args, "description") ?? getString(asRecord(result.value), "description") ?? "task";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
74
|
function getNestedRecord(record: Record<string, unknown> | undefined, ...keys: string[]): Record<string, unknown> | undefined {
|
|
78
75
|
let current = record;
|
|
79
76
|
for (const key of keys) {
|
|
@@ -83,16 +80,24 @@ function getNestedRecord(record: Record<string, unknown> | undefined, ...keys: s
|
|
|
83
80
|
return current;
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
|
|
83
|
+
function readConversationStepAssistantText(step: unknown): string | undefined {
|
|
84
|
+
const record = asRecord(step);
|
|
85
|
+
const legacyText = getString(getRecord(record, "assistantMessage"), "text");
|
|
86
|
+
if (legacyText) return legacyText;
|
|
87
|
+
if (getString(record, "type") !== "assistantMessage") return undefined;
|
|
88
|
+
return getString(getRecord(record, "message"), "text");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function collectTaskText(result: CursorToolResultLike, options: CursorToolResultReaderOptions = {}): string {
|
|
87
92
|
const value = asRecord(result.value);
|
|
88
93
|
const success = getNestedRecord(value, "result", "success");
|
|
89
94
|
const command = getString(success, "command");
|
|
90
95
|
const stdout = getString(success, "stdout");
|
|
91
96
|
const interleavedOutput = getString(success, "interleavedOutput");
|
|
92
|
-
const
|
|
93
|
-
.map((step) =>
|
|
97
|
+
const conversationParts = (getArray(value, "conversationSteps") ?? [])
|
|
98
|
+
.map((step) => summarizeCursorCompactConversationToolCall(step, options) ?? readConversationStepAssistantText(step))
|
|
94
99
|
.filter((entry): entry is string => Boolean(entry));
|
|
95
|
-
const parts = [command ? `$ ${command}` : undefined, stdout || interleavedOutput, ...
|
|
100
|
+
const parts = [command ? `$ ${command}` : undefined, stdout || interleavedOutput, ...conversationParts].filter((part): part is string => Boolean(part));
|
|
96
101
|
return parts.join("\n");
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
getRecord,
|
|
10
10
|
getString,
|
|
11
11
|
} from "./cursor-record-utils.js";
|
|
12
|
+
import { getCursorTaskTranscriptHeader } from "./cursor-task-presentation.js";
|
|
12
13
|
import {
|
|
13
14
|
collectTaskText,
|
|
14
15
|
getGenerateImageDisplayPath,
|
|
15
16
|
readMcpDisplayResult,
|
|
16
17
|
getReadLintDiagnostics,
|
|
17
18
|
getReadLintPaths,
|
|
18
|
-
getTaskDescription,
|
|
19
19
|
getTodoItems,
|
|
20
20
|
} from "./cursor-tool-result-display-readers.js";
|
|
21
21
|
|
|
@@ -487,10 +487,10 @@ export function formatPlan(args: Record<string, unknown>, result: NormalizedResu
|
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
export function formatTask(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
490
|
-
const
|
|
491
|
-
if (result.status === "error") return joinSections(
|
|
492
|
-
const taskText = collectTaskText(result);
|
|
493
|
-
return joinSections(
|
|
490
|
+
const header = getCursorTaskTranscriptHeader(args, result.value);
|
|
491
|
+
if (result.status === "error") return joinSections(header, formatError(result.error));
|
|
492
|
+
const taskText = collectTaskText(result, options);
|
|
493
|
+
return joinSections(header, limitText(taskText || stringifyUnknown(result.value), options));
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
export function formatGenerateImage(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
@@ -56,10 +56,11 @@ export function estimateCursorContextTotalTokens(partial: AssistantMessage, mode
|
|
|
56
56
|
return estimateCursorContextTokens(withAssistantMessage(context, partial), getCursorPromptOptions(model));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export function applyCursorApproximateUsage(partial: AssistantMessage,
|
|
60
|
-
|
|
61
|
-
partial.usage.
|
|
59
|
+
export function applyCursorApproximateUsage(partial: AssistantMessage, _model: Model<Api>, _context: Context, sessionInputTokens: number): void {
|
|
60
|
+
const outputTokens = estimateCursorAssistantSessionOutputTokens(partial);
|
|
61
|
+
partial.usage.input = Math.max(0, sessionInputTokens);
|
|
62
|
+
partial.usage.output = outputTokens;
|
|
62
63
|
partial.usage.cacheRead = 0;
|
|
63
64
|
partial.usage.cacheWrite = 0;
|
|
64
|
-
partial.usage.totalTokens =
|
|
65
|
+
partial.usage.totalTokens = partial.usage.input + partial.usage.output + partial.usage.cacheRead + partial.usage.cacheWrite;
|
|
65
66
|
}
|