pi-cursor-sdk 0.1.16 → 0.1.18
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 +53 -1
- package/README.md +2 -2
- package/docs/cursor-live-smoke-checklist.md +54 -41
- package/docs/cursor-model-ux-spec.md +4 -3
- package/docs/cursor-testing-lessons.md +199 -0
- package/package.json +14 -5
- package/scripts/isolated-cursor-smoke.sh +226 -0
- package/scripts/steering-rpc-smoke.mjs +238 -0
- package/scripts/tmux-live-smoke.sh +418 -0
- package/scripts/validate-smoke-jsonl.mjs +207 -0
- package/src/cursor-context-tools.ts +6 -0
- package/src/cursor-display-text.ts +10 -0
- package/src/cursor-edit-diff.ts +11 -0
- package/src/cursor-env-boolean.ts +22 -0
- package/src/cursor-live-run-coordinator.ts +483 -0
- package/src/cursor-native-replay-routing.ts +48 -0
- package/src/cursor-native-replay-trace.ts +29 -0
- package/src/cursor-native-tool-display-registration.ts +103 -0
- package/src/cursor-native-tool-display-replay.ts +465 -0
- package/src/cursor-native-tool-display-state.ts +78 -0
- package/src/cursor-native-tool-display-tools.ts +102 -0
- package/src/cursor-native-tool-display.ts +10 -648
- package/src/cursor-partial-content-emitter.ts +121 -0
- package/src/cursor-pi-tool-bridge-abort.ts +133 -0
- package/src/cursor-pi-tool-bridge-diagnostics.ts +179 -0
- package/src/cursor-pi-tool-bridge-mcp.ts +118 -0
- package/src/cursor-pi-tool-bridge-run.ts +384 -0
- package/src/cursor-pi-tool-bridge-server.ts +182 -0
- package/src/cursor-pi-tool-bridge-snapshot.ts +88 -0
- package/src/cursor-pi-tool-bridge-types.ts +80 -0
- package/src/cursor-pi-tool-bridge.ts +42 -1104
- package/src/cursor-provider-live-run-drain.ts +405 -0
- package/src/cursor-provider-turn-coordinator.ts +460 -0
- package/src/cursor-provider.ts +77 -1103
- package/src/cursor-question-tool.ts +9 -1
- package/src/cursor-record-utils.ts +26 -0
- package/src/cursor-sdk-output-filter.ts +100 -0
- package/src/cursor-sensitive-text.ts +37 -0
- package/src/cursor-tool-transcript.ts +28 -1229
- package/src/cursor-transcript-tool-formatters.ts +641 -0
- package/src/cursor-transcript-tool-specs.ts +441 -0
- package/src/cursor-transcript-utils.ts +276 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext, ExtensionHandler, SessionStartEvent } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { BeforeAgentStartEvent, ExtensionAPI, ExtensionContext, ExtensionHandler, SessionStartEvent, TurnStartEvent } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
4
|
import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge.js";
|
|
@@ -36,6 +36,8 @@ interface CursorQuestionDetails {
|
|
|
36
36
|
|
|
37
37
|
interface CursorQuestionToolExtensionApi extends Pick<ExtensionAPI, "getActiveTools" | "registerTool" | "setActiveTools"> {
|
|
38
38
|
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
|
39
|
+
on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent>): void;
|
|
40
|
+
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
|
|
39
41
|
on(event: "model_select", handler: (event: { model: ExtensionContext["model"] }, ctx: ExtensionContext) => Promise<void> | void): void;
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -246,6 +248,12 @@ export function registerCursorQuestionTool(pi: CursorQuestionToolExtensionApi):
|
|
|
246
248
|
pi.on("session_start", (_event, ctx) => {
|
|
247
249
|
syncCursorQuestionToolForModel(pi, ctx.model);
|
|
248
250
|
});
|
|
251
|
+
pi.on("before_agent_start", (_event, ctx) => {
|
|
252
|
+
syncCursorQuestionToolForModel(pi, ctx.model);
|
|
253
|
+
});
|
|
254
|
+
pi.on("turn_start", (_event, ctx) => {
|
|
255
|
+
syncCursorQuestionToolForModel(pi, ctx.model);
|
|
256
|
+
});
|
|
249
257
|
pi.on("model_select", (event) => {
|
|
250
258
|
syncCursorQuestionToolForModel(pi, event.model);
|
|
251
259
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
2
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function getField(value: unknown, field: string): unknown {
|
|
6
|
+
return asRecord(value)?.[field];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function hasUsableText(value: string | undefined): value is string {
|
|
10
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getFirstStringByKeys(
|
|
14
|
+
record: Record<string, unknown> | undefined,
|
|
15
|
+
keys: readonly string[],
|
|
16
|
+
options?: { nonEmpty?: boolean },
|
|
17
|
+
): string | undefined {
|
|
18
|
+
if (!record) return undefined;
|
|
19
|
+
for (const key of keys) {
|
|
20
|
+
const value = record[key];
|
|
21
|
+
if (typeof value !== "string") continue;
|
|
22
|
+
if (options?.nonEmpty && !value) continue;
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
const cursorSdkOutputSuppression = new AsyncLocalStorage<boolean>();
|
|
4
|
+
|
|
5
|
+
export const CURSOR_SDK_STARTUP_NOISE_PATTERNS = [
|
|
6
|
+
"[hooks]",
|
|
7
|
+
"managed_skills.",
|
|
8
|
+
"CursorPluginsAgentSkillsService load completed",
|
|
9
|
+
"LocalCursorRulesService load completed",
|
|
10
|
+
"AgentSkillsCursorRulesService load completed",
|
|
11
|
+
"Error initializing ignore mapping for",
|
|
12
|
+
"Ripgrep path not configured. Call configureRipgrepPath() at startup.",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
export function isCursorSdkOutputSuppressed(): boolean {
|
|
16
|
+
return cursorSdkOutputSuppression.getStore() === true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function suppressCursorSdkOutput<T>(operation: () => Promise<T>): Promise<T> {
|
|
20
|
+
return cursorSdkOutputSuppression.run(true, operation);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isCursorSdkStartupNoise(text: string): boolean {
|
|
24
|
+
return CURSOR_SDK_STARTUP_NOISE_PATTERNS.some((pattern) => text.includes(pattern));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createFilteredProcessWrite<TWrite extends typeof process.stdout.write>(write: TWrite, stream: NodeJS.WriteStream): TWrite {
|
|
28
|
+
return ((
|
|
29
|
+
chunk: string | Uint8Array,
|
|
30
|
+
encodingOrCallback?: BufferEncoding | ((error?: Error | null) => void),
|
|
31
|
+
callback?: (error?: Error | null) => void,
|
|
32
|
+
): boolean => {
|
|
33
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
34
|
+
if (isCursorSdkOutputSuppressed() || isCursorSdkStartupNoise(text)) {
|
|
35
|
+
const done = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
36
|
+
done?.();
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return write.call(stream, chunk as string, encodingOrCallback as BufferEncoding, callback);
|
|
40
|
+
}) as TWrite;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createFilteredConsoleMethod<TMethod extends typeof console.log>(method: TMethod): TMethod {
|
|
44
|
+
return ((...args: Parameters<TMethod>): void => {
|
|
45
|
+
const text = args.map((arg) => (typeof arg === "string" ? arg : String(arg))).join(" ");
|
|
46
|
+
if (isCursorSdkOutputSuppressed() || isCursorSdkStartupNoise(text)) return;
|
|
47
|
+
method(...args);
|
|
48
|
+
}) as TMethod;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface CursorSdkOutputFilterOriginals {
|
|
52
|
+
stdoutWrite: typeof process.stdout.write;
|
|
53
|
+
stderrWrite: typeof process.stderr.write;
|
|
54
|
+
consoleLog: typeof console.log;
|
|
55
|
+
consoleInfo: typeof console.info;
|
|
56
|
+
consoleWarn: typeof console.warn;
|
|
57
|
+
consoleError: typeof console.error;
|
|
58
|
+
consoleDebug: typeof console.debug;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let activeOutputFilterInstalls = 0;
|
|
62
|
+
let outputFilterOriginals: CursorSdkOutputFilterOriginals | undefined;
|
|
63
|
+
|
|
64
|
+
export function installCursorSdkOutputFilter(): () => void {
|
|
65
|
+
if (activeOutputFilterInstalls === 0) {
|
|
66
|
+
outputFilterOriginals = {
|
|
67
|
+
stdoutWrite: process.stdout.write,
|
|
68
|
+
stderrWrite: process.stderr.write,
|
|
69
|
+
consoleLog: console.log,
|
|
70
|
+
consoleInfo: console.info,
|
|
71
|
+
consoleWarn: console.warn,
|
|
72
|
+
consoleError: console.error,
|
|
73
|
+
consoleDebug: console.debug,
|
|
74
|
+
};
|
|
75
|
+
process.stdout.write = createFilteredProcessWrite(outputFilterOriginals.stdoutWrite, process.stdout);
|
|
76
|
+
process.stderr.write = createFilteredProcessWrite(outputFilterOriginals.stderrWrite, process.stderr) as typeof process.stderr.write;
|
|
77
|
+
console.log = createFilteredConsoleMethod(outputFilterOriginals.consoleLog);
|
|
78
|
+
console.info = createFilteredConsoleMethod(outputFilterOriginals.consoleInfo);
|
|
79
|
+
console.warn = createFilteredConsoleMethod(outputFilterOriginals.consoleWarn);
|
|
80
|
+
console.error = createFilteredConsoleMethod(outputFilterOriginals.consoleError);
|
|
81
|
+
console.debug = createFilteredConsoleMethod(outputFilterOriginals.consoleDebug);
|
|
82
|
+
}
|
|
83
|
+
activeOutputFilterInstalls += 1;
|
|
84
|
+
|
|
85
|
+
let restored = false;
|
|
86
|
+
return () => {
|
|
87
|
+
if (restored) return;
|
|
88
|
+
restored = true;
|
|
89
|
+
activeOutputFilterInstalls = Math.max(activeOutputFilterInstalls - 1, 0);
|
|
90
|
+
if (activeOutputFilterInstalls > 0 || !outputFilterOriginals) return;
|
|
91
|
+
process.stdout.write = outputFilterOriginals.stdoutWrite;
|
|
92
|
+
process.stderr.write = outputFilterOriginals.stderrWrite;
|
|
93
|
+
console.log = outputFilterOriginals.consoleLog;
|
|
94
|
+
console.info = outputFilterOriginals.consoleInfo;
|
|
95
|
+
console.warn = outputFilterOriginals.consoleWarn;
|
|
96
|
+
console.error = outputFilterOriginals.consoleError;
|
|
97
|
+
console.debug = outputFilterOriginals.consoleDebug;
|
|
98
|
+
outputFilterOriginals = undefined;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CursorPiToolDisplay } from "./cursor-transcript-utils.js";
|
|
2
|
+
|
|
3
|
+
function escapeRegExp(value: string): string {
|
|
4
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function scrubSensitiveText(text: string, apiKey?: string): string {
|
|
8
|
+
let scrubbed = text;
|
|
9
|
+
const trimmedKey = apiKey?.trim();
|
|
10
|
+
if (trimmedKey) {
|
|
11
|
+
scrubbed = scrubbed.replace(new RegExp(escapeRegExp(trimmedKey), "g"), "[redacted]");
|
|
12
|
+
}
|
|
13
|
+
return scrubbed
|
|
14
|
+
.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")
|
|
15
|
+
.replace(/((?:^|[\s,{])cookie["']?\s*[:=]\s*["']?)[^\n]+/gi, "$1[redacted]")
|
|
16
|
+
.replace(
|
|
17
|
+
/((?:authorization|api[_-]?key|apiKey|token|session(?:[_-]?id)?)["']?\s*[:=]\s*["']?)[^"'\s,;}]+/gi,
|
|
18
|
+
"$1[redacted]",
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function scrubDisplayValue(value: unknown, apiKey?: string): unknown {
|
|
23
|
+
if (typeof value === "string") return scrubSensitiveText(value, apiKey);
|
|
24
|
+
if (Array.isArray(value)) return value.map((entry) => scrubDisplayValue(entry, apiKey));
|
|
25
|
+
if (value && typeof value === "object") {
|
|
26
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, scrubDisplayValue(entry, apiKey)]));
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function scrubPiToolDisplay(display: CursorPiToolDisplay, apiKey?: string): CursorPiToolDisplay {
|
|
32
|
+
return {
|
|
33
|
+
...display,
|
|
34
|
+
args: scrubDisplayValue(display.args, apiKey) as Record<string, unknown>,
|
|
35
|
+
result: scrubDisplayValue(display.result, apiKey) as CursorPiToolDisplay["result"],
|
|
36
|
+
};
|
|
37
|
+
}
|