pi-cursor-sdk 0.1.62 → 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 +85 -0
- package/README.md +57 -53
- 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 +15 -14
- package/docs/cursor-model-ux-spec.md +29 -8
- 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 +6 -6
- package/node_modules/fast-uri/index.js +37 -1
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/security.test.js +136 -0
- package/node_modules/hono/dist/cjs/context.js +28 -13
- package/node_modules/hono/dist/cjs/helper/proxy/index.js +4 -0
- package/node_modules/hono/dist/cjs/helper/streaming/sse.js +1 -1
- package/node_modules/hono/dist/cjs/hono-base.js +3 -2
- package/node_modules/hono/dist/cjs/jsx/base.js +27 -23
- package/node_modules/hono/dist/cjs/jsx/hooks/index.js +16 -13
- package/node_modules/hono/dist/cjs/middleware/cache/index.js +103 -8
- package/node_modules/hono/dist/cjs/middleware/compress/index.js +5 -0
- package/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -2
- package/node_modules/hono/dist/cjs/middleware/etag/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/jwk/jwk.js +9 -4
- package/node_modules/hono/dist/cjs/middleware/jwt/jwt.js +9 -4
- package/node_modules/hono/dist/cjs/middleware/language/language.js +10 -6
- package/node_modules/hono/dist/cjs/middleware/method-not-allowed/index.js +90 -0
- package/node_modules/hono/dist/cjs/middleware/secure-headers/secure-headers.js +10 -4
- package/node_modules/hono/dist/cjs/request.js +7 -9
- package/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +55 -56
- package/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +64 -85
- package/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +13 -5
- package/node_modules/hono/dist/cjs/router.js +1 -1
- package/node_modules/hono/dist/cjs/utils/accept.js +1 -1
- package/node_modules/hono/dist/cjs/utils/cookie.js +3 -2
- package/node_modules/hono/dist/cjs/utils/url.js +8 -8
- package/node_modules/hono/dist/context.js +28 -13
- package/node_modules/hono/dist/helper/proxy/index.js +4 -0
- package/node_modules/hono/dist/helper/streaming/sse.js +1 -1
- package/node_modules/hono/dist/hono-base.js +3 -2
- package/node_modules/hono/dist/jsx/base.js +27 -23
- package/node_modules/hono/dist/jsx/hooks/index.js +16 -13
- package/node_modules/hono/dist/middleware/cache/index.js +103 -8
- package/node_modules/hono/dist/middleware/compress/index.js +5 -0
- package/node_modules/hono/dist/middleware/cors/index.js +2 -2
- package/node_modules/hono/dist/middleware/etag/index.js +1 -1
- package/node_modules/hono/dist/middleware/jwk/jwk.js +9 -4
- package/node_modules/hono/dist/middleware/jwt/jwt.js +9 -4
- package/node_modules/hono/dist/middleware/language/language.js +10 -6
- package/node_modules/hono/dist/middleware/method-not-allowed/index.js +68 -0
- package/node_modules/hono/dist/middleware/secure-headers/secure-headers.js +10 -4
- package/node_modules/hono/dist/request.js +8 -10
- package/node_modules/hono/dist/router/reg-exp-router/node.js +55 -56
- package/node_modules/hono/dist/router/reg-exp-router/router.js +64 -85
- package/node_modules/hono/dist/router/reg-exp-router/trie.js +13 -5
- package/node_modules/hono/dist/router.js +1 -1
- package/node_modules/hono/dist/types/adapter/aws-lambda/types.d.ts +9 -0
- package/node_modules/hono/dist/types/client/types.d.ts +1 -1
- package/node_modules/hono/dist/types/hono-base.d.ts +4 -3
- package/node_modules/hono/dist/types/jsx/base.d.ts +4 -2
- package/node_modules/hono/dist/types/jsx/dom/index.d.ts +5 -5
- package/node_modules/hono/dist/types/jsx/dom/intrinsic-element/components.d.ts +2 -2
- package/node_modules/hono/dist/types/jsx/dom/server.d.ts +5 -5
- package/node_modules/hono/dist/types/jsx/hooks/index.d.ts +8 -6
- package/node_modules/hono/dist/types/jsx/index.d.ts +5 -5
- package/node_modules/hono/dist/types/middleware/cache/index.d.ts +6 -4
- package/node_modules/hono/dist/types/middleware/cors/index.d.ts +1 -1
- package/node_modules/hono/dist/types/middleware/jsx-renderer/index.d.ts +2 -2
- package/node_modules/hono/dist/types/middleware/jwk/jwk.d.ts +2 -0
- package/node_modules/hono/dist/types/middleware/jwt/jwt.d.ts +2 -0
- package/node_modules/hono/dist/types/middleware/method-not-allowed/index.d.ts +49 -0
- package/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +1 -1
- package/node_modules/hono/dist/types/router/reg-exp-router/trie.d.ts +2 -1
- package/node_modules/hono/dist/types/router.d.ts +1 -1
- package/node_modules/hono/dist/types/utils/headers.d.ts +2 -2
- package/node_modules/hono/dist/types/utils/url.d.ts +1 -0
- package/node_modules/hono/dist/utils/accept.js +1 -1
- package/node_modules/hono/dist/utils/cookie.js +4 -3
- package/node_modules/hono/dist/utils/url.js +6 -7
- package/node_modules/hono/package.json +10 -3
- package/node_modules/ip-address/README.md +146 -126
- package/node_modules/ip-address/dist/common.d.ts +29 -0
- package/node_modules/ip-address/dist/common.js +44 -5
- package/node_modules/ip-address/dist/common.js.map +1 -1
- package/node_modules/ip-address/dist/ipv4.d.ts +8 -1
- package/node_modules/ip-address/dist/ipv4.js +24 -20
- package/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/ip-address/dist/ipv6.d.ts +68 -3
- package/node_modules/ip-address/dist/ipv6.js +180 -53
- package/node_modules/ip-address/dist/ipv6.js.map +1 -1
- package/node_modules/ip-address/dist/v4/constants.js +5 -1
- package/node_modules/ip-address/dist/v4/constants.js.map +1 -1
- package/node_modules/ip-address/dist/v6/constants.js +3 -2
- package/node_modules/ip-address/dist/v6/constants.js.map +1 -1
- package/node_modules/ip-address/package.json +6 -4
- package/package.json +23 -11
- 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 +16 -40
- package/scripts/platform-smoke/platform-build-windows.ps1 +1 -1
- package/scripts/platform-smoke/render-ansi.mjs +1 -1
- package/scripts/platform-smoke/scenarios.mjs +1 -1
- package/scripts/platform-smoke/targets.mjs +2 -2
- package/scripts/platform-smoke.mjs +5 -5
- package/scripts/prepare.mjs +102 -0
- package/scripts/refresh-cursor-model-snapshots.mjs +16 -7
- 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/shared/cursor-model-selection-identities.d.mts +21 -0
- package/shared/cursor-model-selection-identities.mjs +95 -0
- package/src/bundled-context-windows.ts +21 -12
- package/src/context.ts +1 -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-context-tools.ts +1 -1
- package/src/cursor-display-only-trace.ts +1 -1
- package/src/cursor-fallback-models.generated.ts +1726 -824
- package/src/cursor-live-run-accounting.ts +1 -1
- package/src/cursor-live-run-coordinator.ts +2 -1
- package/src/cursor-native-replay-routing.ts +1 -1
- package/src/cursor-native-tool-display-registration.ts +10 -11
- package/src/cursor-partial-content-emitter.ts +1 -1
- package/src/cursor-pi-tool-bridge-mcp.ts +1 -1
- package/src/cursor-pi-tool-bridge-run.ts +1 -1
- package/src/cursor-pi-tool-bridge-types.ts +1 -1
- package/src/cursor-provider-errors.ts +1 -1
- package/src/cursor-provider-lazy.ts +6 -5
- package/src/cursor-provider-live-run-drain.ts +2 -1
- package/src/cursor-provider-overflow.ts +1 -1
- package/src/cursor-provider-run-finalizer.ts +2 -1
- package/src/cursor-provider-turn-coordinator.ts +1 -1
- package/src/cursor-provider-turn-finalize.ts +12 -0
- package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -1
- package/src/cursor-provider-turn-prepare.ts +1 -1
- package/src/cursor-provider-turn-types.ts +3 -1
- package/src/cursor-provider.ts +1 -1
- package/src/cursor-run-final-text.ts +1 -1
- package/src/cursor-sdk-billed-usage.ts +107 -0
- package/src/cursor-sdk-event-debug.ts +1 -1
- 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-agent.ts +1 -1
- package/src/cursor-session-compaction-prep.ts +4 -0
- package/src/cursor-session-send-policy.ts +1 -1
- package/src/cursor-tool-manifest.ts +1 -1
- package/src/cursor-usage-accounting.ts +71 -10
- package/src/index.ts +1 -1
- package/src/model-discovery.ts +32 -89
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +16 -0
- package/node_modules/cross-spawn/node_modules/which/CHANGELOG.md +0 -166
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { arePiToolsDisabled } from "./cursor-active-tools.js";
|
|
4
|
+
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
5
|
+
import { isCursorModel } from "./cursor-model.js";
|
|
6
|
+
import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
|
|
7
|
+
import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
|
|
8
|
+
export const CURSOR_ASK_QUESTION_TOOL_NAME = "cursor_ask_question";
|
|
9
|
+
export const CURSOR_ASK_QUESTION_ENV = "PI_CURSOR_ASK_QUESTION";
|
|
10
|
+
export function resolveCursorAskQuestionEnabled(env = process.env) {
|
|
11
|
+
return parseEnvBoolean(env[CURSOR_ASK_QUESTION_ENV], true);
|
|
12
|
+
}
|
|
13
|
+
/** Package-namespaced event while `cursor_ask_question` awaits pi UI input. */
|
|
14
|
+
export const CURSOR_ASK_QUESTION_BLOCKED_EVENT = "pi-cursor-sdk:ask-question:blocked";
|
|
15
|
+
const QuestionOptionSchema = Type.Union([
|
|
16
|
+
Type.String(),
|
|
17
|
+
Type.Object({
|
|
18
|
+
label: Type.String({ description: "User-facing option label" }),
|
|
19
|
+
value: Type.Optional(Type.String({ description: "Optional value returned to Cursor; defaults to label" })),
|
|
20
|
+
description: Type.Optional(Type.String({ description: "Optional helper text shown by compatible pi UIs" })),
|
|
21
|
+
}),
|
|
22
|
+
]);
|
|
23
|
+
const QuestionSchema = Type.Object({
|
|
24
|
+
id: Type.Optional(Type.String({ description: "Stable question identifier" })),
|
|
25
|
+
question: Type.Optional(Type.String({ description: "Question to ask the user" })),
|
|
26
|
+
prompt: Type.Optional(Type.String({ description: "Alias for question" })),
|
|
27
|
+
options: Type.Optional(Type.Array(QuestionOptionSchema, { description: "Choices the user can select" })),
|
|
28
|
+
choices: Type.Optional(Type.Array(QuestionOptionSchema, { description: "Alias for options" })),
|
|
29
|
+
allowCustom: Type.Optional(Type.Boolean({ description: "Allow a typed answer in addition to listed options; defaults to true" })),
|
|
30
|
+
});
|
|
31
|
+
const CursorAskQuestionParamsSchema = Type.Object({
|
|
32
|
+
question: Type.Optional(Type.String({ description: "Question to ask the user" })),
|
|
33
|
+
prompt: Type.Optional(Type.String({ description: "Alias for question" })),
|
|
34
|
+
options: Type.Optional(Type.Array(QuestionOptionSchema, { description: "Choices the user can select" })),
|
|
35
|
+
choices: Type.Optional(Type.Array(QuestionOptionSchema, { description: "Alias for options" })),
|
|
36
|
+
allowCustom: Type.Optional(Type.Boolean({ description: "Allow a typed answer in addition to listed options; defaults to true" })),
|
|
37
|
+
questions: Type.Optional(Type.Array(QuestionSchema, { description: "Ask multiple questions sequentially" })),
|
|
38
|
+
});
|
|
39
|
+
function normalizeOption(option, index) {
|
|
40
|
+
if (typeof option === "string") {
|
|
41
|
+
const trimmed = option.trim();
|
|
42
|
+
return trimmed ? { label: trimmed, value: trimmed } : undefined;
|
|
43
|
+
}
|
|
44
|
+
const label = option.label?.trim() || option.value?.trim() || `Option ${index + 1}`;
|
|
45
|
+
return {
|
|
46
|
+
label,
|
|
47
|
+
value: option.value?.trim() || label,
|
|
48
|
+
...(option.description?.trim() ? { description: option.description.trim() } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function normalizeOptions(options) {
|
|
52
|
+
return (options ?? []).map(normalizeOption).filter((option) => option !== undefined);
|
|
53
|
+
}
|
|
54
|
+
function normalizeQuestion(raw, index) {
|
|
55
|
+
const question = raw.question?.trim() || raw.prompt?.trim();
|
|
56
|
+
if (!question)
|
|
57
|
+
return undefined;
|
|
58
|
+
return {
|
|
59
|
+
id: raw.id?.trim() || `question_${index + 1}`,
|
|
60
|
+
question,
|
|
61
|
+
options: normalizeOptions(raw.options ?? raw.choices),
|
|
62
|
+
allowCustom: raw.allowCustom !== false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function normalizeQuestions(params) {
|
|
66
|
+
const rawQuestions = Array.isArray(params.questions) && params.questions.length > 0 ? params.questions : [params];
|
|
67
|
+
return rawQuestions.map(normalizeQuestion).filter((question) => question !== undefined);
|
|
68
|
+
}
|
|
69
|
+
function summarizeAnswers(answers) {
|
|
70
|
+
if (answers.length === 0)
|
|
71
|
+
return "No answer was collected.";
|
|
72
|
+
if (answers.length === 1) {
|
|
73
|
+
const [answer] = answers;
|
|
74
|
+
return answer.cancelled || answer.answer === null ? "User cancelled the question." : `User answered: ${answer.answer}`;
|
|
75
|
+
}
|
|
76
|
+
return [
|
|
77
|
+
"User answered:",
|
|
78
|
+
...answers.map((answer) => {
|
|
79
|
+
const value = answer.cancelled || answer.answer === null ? "cancelled" : answer.answer;
|
|
80
|
+
return `- ${answer.id}: ${value}`;
|
|
81
|
+
}),
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
function buildDetails(questions, answers, uiAvailable) {
|
|
85
|
+
return {
|
|
86
|
+
questions,
|
|
87
|
+
answers,
|
|
88
|
+
uiAvailable,
|
|
89
|
+
cancelled: answers.some((answer) => answer.cancelled),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function askOneQuestion(question, ctx) {
|
|
93
|
+
if (question.options.length > 0) {
|
|
94
|
+
const labels = question.options.map((option) => option.description ? `${option.label} — ${option.description}` : option.label);
|
|
95
|
+
const customLabel = "Type a custom answer";
|
|
96
|
+
const choices = question.allowCustom ? [...labels, customLabel] : labels;
|
|
97
|
+
const selected = await ctx.ui.select(question.question, choices);
|
|
98
|
+
if (!selected) {
|
|
99
|
+
return { id: question.id, question: question.question, answer: null, wasCustom: false, cancelled: true };
|
|
100
|
+
}
|
|
101
|
+
if (selected === customLabel) {
|
|
102
|
+
const customAnswer = await ctx.ui.input(question.question, "Type your answer");
|
|
103
|
+
const trimmed = customAnswer?.trim();
|
|
104
|
+
return trimmed
|
|
105
|
+
? { id: question.id, question: question.question, answer: trimmed, value: trimmed, wasCustom: true, cancelled: false }
|
|
106
|
+
: { id: question.id, question: question.question, answer: null, wasCustom: true, cancelled: true };
|
|
107
|
+
}
|
|
108
|
+
const selectedIndex = labels.indexOf(selected);
|
|
109
|
+
const selectedOption = selectedIndex >= 0 ? question.options[selectedIndex] : undefined;
|
|
110
|
+
const answer = selectedOption?.label ?? selected;
|
|
111
|
+
return {
|
|
112
|
+
id: question.id,
|
|
113
|
+
question: question.question,
|
|
114
|
+
answer,
|
|
115
|
+
value: selectedOption?.value ?? answer,
|
|
116
|
+
wasCustom: false,
|
|
117
|
+
cancelled: false,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const answer = await ctx.ui.input(question.question, "Type your answer");
|
|
121
|
+
const trimmed = answer?.trim();
|
|
122
|
+
return trimmed
|
|
123
|
+
? { id: question.id, question: question.question, answer: trimmed, value: trimmed, wasCustom: true, cancelled: false }
|
|
124
|
+
: { id: question.id, question: question.question, answer: null, wasCustom: true, cancelled: true };
|
|
125
|
+
}
|
|
126
|
+
function syncCursorQuestionToolForModel(pi, model) {
|
|
127
|
+
const activeToolNames = new Set(pi.getActiveTools());
|
|
128
|
+
const shouldBeActive = !arePiToolsDisabled(pi) && isCursorModel(model) && resolveCursorPiToolBridgeEnabled();
|
|
129
|
+
const alreadyActive = activeToolNames.has(CURSOR_ASK_QUESTION_TOOL_NAME);
|
|
130
|
+
if (shouldBeActive === alreadyActive)
|
|
131
|
+
return;
|
|
132
|
+
if (shouldBeActive) {
|
|
133
|
+
activeToolNames.add(CURSOR_ASK_QUESTION_TOOL_NAME);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
activeToolNames.delete(CURSOR_ASK_QUESTION_TOOL_NAME);
|
|
137
|
+
}
|
|
138
|
+
pi.setActiveTools([...activeToolNames]);
|
|
139
|
+
}
|
|
140
|
+
function emitCursorAskQuestionBlockedEvent(pi, payload) {
|
|
141
|
+
pi.events.emit(CURSOR_ASK_QUESTION_BLOCKED_EVENT, payload);
|
|
142
|
+
}
|
|
143
|
+
export function registerCursorQuestionTool(pi) {
|
|
144
|
+
if (!resolveCursorAskQuestionEnabled())
|
|
145
|
+
return;
|
|
146
|
+
pi.registerTool({
|
|
147
|
+
name: CURSOR_ASK_QUESTION_TOOL_NAME,
|
|
148
|
+
label: "Cursor question",
|
|
149
|
+
description: "Ask the user a clarifying question from Cursor. Use when user preferences materially affect the next step; provide options when possible.",
|
|
150
|
+
promptSnippet: "Ask the user a clarifying question through pi UI when material choices affect Cursor's next step",
|
|
151
|
+
executionMode: "sequential",
|
|
152
|
+
parameters: CursorAskQuestionParamsSchema,
|
|
153
|
+
promptGuidelines: [
|
|
154
|
+
"Use cursor_ask_question only when running a Cursor model and user input would materially change the plan, scope, platform, or implementation path.",
|
|
155
|
+
"Prefer cursor_ask_question with 2-4 concrete options instead of guessing when Cursor plan mode needs user choices.",
|
|
156
|
+
],
|
|
157
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
158
|
+
const questions = normalizeQuestions(params);
|
|
159
|
+
if (questions.length === 0) {
|
|
160
|
+
throw new Error("No valid question was provided.");
|
|
161
|
+
}
|
|
162
|
+
if (!ctx.hasUI) {
|
|
163
|
+
throw new Error("Cannot ask the user because pi UI is unavailable. Make a reasonable default choice and state the assumption before proceeding.");
|
|
164
|
+
}
|
|
165
|
+
// Emit a package-namespaced blocked signal while the questionnaire
|
|
166
|
+
// awaits input so consumers (e.g. Herdr) can map it to blocked/working.
|
|
167
|
+
emitCursorAskQuestionBlockedEvent(pi, { active: true });
|
|
168
|
+
try {
|
|
169
|
+
const answers = [];
|
|
170
|
+
for (const question of questions) {
|
|
171
|
+
const answer = await askOneQuestion(question, ctx);
|
|
172
|
+
answers.push(answer);
|
|
173
|
+
if (answer.cancelled)
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: "text", text: summarizeAnswers(answers) }],
|
|
178
|
+
details: buildDetails(questions, answers, true),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
emitCursorAskQuestionBlockedEvent(pi, { active: false });
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
renderCall(args, theme) {
|
|
186
|
+
const questions = normalizeQuestions(args);
|
|
187
|
+
const label = questions[0]?.question ?? "Ask the user";
|
|
188
|
+
return new Text(theme.fg("toolTitle", theme.bold("cursor question ")) + theme.fg("muted", label), 0, 0);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
registerCursorModelLifecycle(pi, (ctx) => {
|
|
192
|
+
syncCursorQuestionToolForModel(pi, ctx.model);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function asRecord(value) {
|
|
2
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
3
|
+
}
|
|
4
|
+
export function getField(value, field) {
|
|
5
|
+
return asRecord(value)?.[field];
|
|
6
|
+
}
|
|
7
|
+
export function hasUsableText(value) {
|
|
8
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
9
|
+
}
|
|
10
|
+
export function getString(record, key) {
|
|
11
|
+
const value = record?.[key];
|
|
12
|
+
return typeof value === "string" ? value : undefined;
|
|
13
|
+
}
|
|
14
|
+
export function getNumber(record, key) {
|
|
15
|
+
const value = record?.[key];
|
|
16
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
17
|
+
}
|
|
18
|
+
export function getBoolean(record, key) {
|
|
19
|
+
const value = record?.[key];
|
|
20
|
+
return typeof value === "boolean" ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
export function getRecord(record, key) {
|
|
23
|
+
return asRecord(record?.[key]);
|
|
24
|
+
}
|
|
25
|
+
export function getArray(record, key) {
|
|
26
|
+
const value = record?.[key];
|
|
27
|
+
return Array.isArray(value) ? value : undefined;
|
|
28
|
+
}
|
|
29
|
+
export function firstNonEmptyString(...values) {
|
|
30
|
+
for (const value of values) {
|
|
31
|
+
const trimmed = value?.trim();
|
|
32
|
+
if (trimmed)
|
|
33
|
+
return trimmed;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
export function stringifyUnknown(value, options = {}) {
|
|
38
|
+
if (value === undefined)
|
|
39
|
+
return "";
|
|
40
|
+
if (typeof value === "string")
|
|
41
|
+
return value;
|
|
42
|
+
try {
|
|
43
|
+
return JSON.stringify(value, null, options.pretty ? 2 : undefined) ?? String(value);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return String(value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function getFirstStringByKeys(record, keys, options) {
|
|
50
|
+
if (!record)
|
|
51
|
+
return undefined;
|
|
52
|
+
for (const key of keys) {
|
|
53
|
+
const value = record[key];
|
|
54
|
+
if (typeof value !== "string")
|
|
55
|
+
continue;
|
|
56
|
+
if (options?.nonEmpty && !value)
|
|
57
|
+
continue;
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { asRecord, getArray, getNumber, getString } from "./cursor-record-utils.js";
|
|
2
|
+
import { firstNonEmptyLine, formatDisplayPath, truncateArg } from "./cursor-transcript-utils.js";
|
|
3
|
+
import { collectTaskText, getGenerateImageDisplayPath, getGenerateImagePath, readMcpDisplayResult, getReadLintDiagnostics, getReadLintPaths, getTodoItems, getTodoTotalCount, inferImageMimeType, } from "./cursor-tool-result-display-readers.js";
|
|
4
|
+
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-args.js";
|
|
5
|
+
import { formatCursorTaskAgentId, formatCursorTaskKind, getCursorTaskDescription, getCursorTaskPresentationMode, readCursorTaskMetadata } from "./cursor-task-presentation.js";
|
|
6
|
+
export function buildDeleteReplaySummaryArgs({ args, options }) {
|
|
7
|
+
const displayPath = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
|
|
8
|
+
return displayPath ? { path: displayPath } : {};
|
|
9
|
+
}
|
|
10
|
+
export function buildDeleteReplayDetailFields({ args, result, options }) {
|
|
11
|
+
const displayPath = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
|
|
12
|
+
const value = asRecord(result.value);
|
|
13
|
+
return {
|
|
14
|
+
path: displayPath,
|
|
15
|
+
fileSize: getNumber(value, "fileSize"),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function buildReadLintsReplaySummaryArgs({ args, result, options, }) {
|
|
19
|
+
const paths = getReadLintPaths(args, result, options);
|
|
20
|
+
const diagnosticCount = getReadLintDiagnostics(result, options).length;
|
|
21
|
+
return {
|
|
22
|
+
...(paths.length > 0 ? { paths } : {}),
|
|
23
|
+
...(paths.length === 1 ? { path: paths[0] } : {}),
|
|
24
|
+
...(paths.length > 0 ? { diagnosticCount } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function buildTodoReplaySummaryArgs(args, result) {
|
|
28
|
+
const todos = getTodoItems(args, result);
|
|
29
|
+
const totalCount = getTodoTotalCount(args, result, todos);
|
|
30
|
+
const completedCount = todos.filter((todo) => todo.status === "completed").length;
|
|
31
|
+
const inProgressCount = todos.filter((todo) => todo.status === "inProgress").length;
|
|
32
|
+
const pendingCount = todos.filter((todo) => todo.status === "pending").length;
|
|
33
|
+
return todos.length > 0
|
|
34
|
+
? { totalCount, completedCount, inProgressCount, pendingCount }
|
|
35
|
+
: { totalCount };
|
|
36
|
+
}
|
|
37
|
+
export function buildCreatePlanReplaySummaryArgs({ args, result }) {
|
|
38
|
+
const plan = getString(args, "plan") ?? getString(asRecord(result.value) ?? {}, "plan");
|
|
39
|
+
const planTitle = plan ? firstNonEmptyLine(plan) : undefined;
|
|
40
|
+
return {
|
|
41
|
+
...buildTodoReplaySummaryArgs(args, result),
|
|
42
|
+
...(planTitle ? { planTitle: truncateArg(planTitle) } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function buildTaskReplaySummaryArgs({ args, result, options }) {
|
|
46
|
+
const description = getCursorTaskDescription(args, result.value);
|
|
47
|
+
const preview = firstNonEmptyLine(collectTaskText(result, options));
|
|
48
|
+
const metadata = readCursorTaskMetadata(args, result.value);
|
|
49
|
+
const displayAgentId = formatCursorTaskAgentId(metadata.agentId);
|
|
50
|
+
const includeMetadata = getCursorTaskPresentationMode() === "subagent-meta";
|
|
51
|
+
return {
|
|
52
|
+
description: truncateArg(description),
|
|
53
|
+
...(preview ? { preview: truncateArg(preview) } : {}),
|
|
54
|
+
...(includeMetadata && metadata.subagentName ? { subagentName: truncateArg(metadata.subagentName) } : {}),
|
|
55
|
+
...(includeMetadata && metadata.subagentKind ? { subagentKind: truncateArg(formatCursorTaskKind(metadata.subagentKind) ?? metadata.subagentKind) } : {}),
|
|
56
|
+
...(includeMetadata && metadata.model ? { model: truncateArg(metadata.model) } : {}),
|
|
57
|
+
...(includeMetadata && displayAgentId ? { agentId: truncateArg(displayAgentId) } : {}),
|
|
58
|
+
...(includeMetadata && metadata.isBackground === true ? { isBackground: true } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function buildGenerateImageReplaySummaryArgs({ args, result, options, }) {
|
|
62
|
+
const prompt = getString(args, "prompt") ?? getString(args, "description") ?? "image";
|
|
63
|
+
const imageDisplayPath = getGenerateImageDisplayPath(args, result, options);
|
|
64
|
+
return {
|
|
65
|
+
prompt: truncateArg(prompt),
|
|
66
|
+
...(imageDisplayPath ? { path: imageDisplayPath } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function buildMcpReplaySummaryArgs({ args, result }) {
|
|
70
|
+
const toolName = getString(args, "toolName") ?? "mcp";
|
|
71
|
+
const preview = readMcpDisplayResult(result).preview;
|
|
72
|
+
return {
|
|
73
|
+
toolName: truncateArg(toolName),
|
|
74
|
+
...(preview ? { preview } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function buildSemSearchReplaySummaryArgs({ args }) {
|
|
78
|
+
const query = getString(args, "query") ?? "semantic search";
|
|
79
|
+
const targetDirectories = (getArray(args, "targetDirectories") ?? []).filter((entry) => typeof entry === "string");
|
|
80
|
+
return {
|
|
81
|
+
query: truncateArg(query),
|
|
82
|
+
...(targetDirectories.length > 0 ? { targetDirectories } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function buildRecordScreenReplaySummaryArgs({ args, result, options, }) {
|
|
86
|
+
const mode = getString(args, "mode");
|
|
87
|
+
const value = asRecord(result.value) ?? {};
|
|
88
|
+
const path = getString(value, "path");
|
|
89
|
+
const recordingDurationMs = getNumber(value, "recordingDurationMs");
|
|
90
|
+
return {
|
|
91
|
+
...(mode ? { mode } : {}),
|
|
92
|
+
...(path ? { path: formatDisplayPath(path, options.cwd) } : {}),
|
|
93
|
+
...(recordingDurationMs !== undefined ? { recordingDurationMs } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function buildWebSearchReplaySummaryArgs({ args }) {
|
|
97
|
+
const query = extractWebSearchQuery(args);
|
|
98
|
+
return query ? { query: truncateArg(query) } : {};
|
|
99
|
+
}
|
|
100
|
+
export function buildWebFetchReplaySummaryArgs({ args }) {
|
|
101
|
+
const url = extractWebFetchTarget(args);
|
|
102
|
+
return url ? { url: truncateArg(url) } : {};
|
|
103
|
+
}
|
|
104
|
+
export function buildGenerateImageReplayDetailFields(context, contentText) {
|
|
105
|
+
const { args, result, options } = context;
|
|
106
|
+
const imagePath = getGenerateImagePath(args, result);
|
|
107
|
+
const imageDisplayPath = getGenerateImageDisplayPath(args, result, options);
|
|
108
|
+
return {
|
|
109
|
+
imagePath,
|
|
110
|
+
imageDisplayPath,
|
|
111
|
+
imageMimeType: inferImageMimeType(imagePath),
|
|
112
|
+
expandedText: contentText,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const CURSOR_REPLAY_SOURCE_TOOL_NAMES = [
|
|
2
|
+
"read",
|
|
3
|
+
"grep",
|
|
4
|
+
"glob",
|
|
5
|
+
"ls",
|
|
6
|
+
"shell",
|
|
7
|
+
"edit",
|
|
8
|
+
"write",
|
|
9
|
+
"delete",
|
|
10
|
+
"readLints",
|
|
11
|
+
"updateTodos",
|
|
12
|
+
"createPlan",
|
|
13
|
+
"task",
|
|
14
|
+
"generateImage",
|
|
15
|
+
"mcp",
|
|
16
|
+
"semSearch",
|
|
17
|
+
"recordScreen",
|
|
18
|
+
"webSearch",
|
|
19
|
+
"webFetch",
|
|
20
|
+
];
|
|
21
|
+
const CURSOR_REPLAY_SOURCE_TOOL_NAME_SET = new Set(CURSOR_REPLAY_SOURCE_TOOL_NAMES);
|
|
22
|
+
export function isCursorReplaySourceToolName(name) {
|
|
23
|
+
return CURSOR_REPLAY_SOURCE_TOOL_NAME_SET.has(name);
|
|
24
|
+
}
|
|
25
|
+
export function isCursorReplayActivitySourceName(name) {
|
|
26
|
+
return name !== "generateImage" && isCursorReplaySourceToolName(name);
|
|
27
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed replay summary argument payloads shared by the presentation registry
|
|
3
|
+
* and transcript replay builders.
|
|
4
|
+
*/
|
|
5
|
+
export function readCursorReplaySummaryString(args, key) {
|
|
6
|
+
const value = args?.[key];
|
|
7
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
8
|
+
}
|
|
9
|
+
export function readCursorReplaySummaryNumber(args, key) {
|
|
10
|
+
const value = args?.[key];
|
|
11
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
export function readCursorReplaySummaryStringArray(args, key) {
|
|
14
|
+
const value = args?.[key];
|
|
15
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
16
|
+
}
|
|
17
|
+
function formatReplayRecordingDurationMs(ms) {
|
|
18
|
+
if (ms === undefined || !Number.isFinite(ms) || ms < 0)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (ms < 1000)
|
|
21
|
+
return `${Math.round(ms)}ms`;
|
|
22
|
+
const seconds = ms / 1000;
|
|
23
|
+
return seconds < 60 ? `${seconds.toFixed(1)}s` : `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
|
|
24
|
+
}
|
|
25
|
+
export function summarizeReplayPath(args) {
|
|
26
|
+
return readCursorReplaySummaryString(args, "path") ?? "unknown";
|
|
27
|
+
}
|
|
28
|
+
export function summarizeReplayReadLints(args) {
|
|
29
|
+
const paths = readCursorReplaySummaryStringArray(args, "paths");
|
|
30
|
+
const path = readCursorReplaySummaryString(args, "path");
|
|
31
|
+
const diagnosticCount = readCursorReplaySummaryNumber(args, "diagnosticCount");
|
|
32
|
+
const target = paths.length > 0 ? paths.join(", ") : path;
|
|
33
|
+
if (target && diagnosticCount !== undefined) {
|
|
34
|
+
return `${diagnosticCount} diagnostic${diagnosticCount === 1 ? "" : "s"} in ${target}`;
|
|
35
|
+
}
|
|
36
|
+
return target;
|
|
37
|
+
}
|
|
38
|
+
export function summarizeReplayTodoCount(args) {
|
|
39
|
+
const totalCount = readCursorReplaySummaryNumber(args, "totalCount");
|
|
40
|
+
const completedCount = readCursorReplaySummaryNumber(args, "completedCount");
|
|
41
|
+
const inProgressCount = readCursorReplaySummaryNumber(args, "inProgressCount");
|
|
42
|
+
const pendingCount = readCursorReplaySummaryNumber(args, "pendingCount");
|
|
43
|
+
if (totalCount !== undefined && completedCount !== undefined) {
|
|
44
|
+
const parts = [`${completedCount}/${totalCount} completed`];
|
|
45
|
+
if (inProgressCount && inProgressCount > 0)
|
|
46
|
+
parts.push(`${inProgressCount} in progress`);
|
|
47
|
+
if (pendingCount && pendingCount > 0)
|
|
48
|
+
parts.push(`${pendingCount} pending`);
|
|
49
|
+
return parts.join(", ");
|
|
50
|
+
}
|
|
51
|
+
return totalCount !== undefined ? `${totalCount} item${totalCount === 1 ? "" : "s"}` : undefined;
|
|
52
|
+
}
|
|
53
|
+
export function summarizeReplayPlan(args) {
|
|
54
|
+
return readCursorReplaySummaryString(args, "planTitle") ?? summarizeReplayTodoCount(args);
|
|
55
|
+
}
|
|
56
|
+
export function summarizeReplayTask(args) {
|
|
57
|
+
const description = readCursorReplaySummaryString(args, "description");
|
|
58
|
+
const preview = readCursorReplaySummaryString(args, "preview");
|
|
59
|
+
const subagentName = readCursorReplaySummaryString(args, "subagentName");
|
|
60
|
+
const subagentKind = readCursorReplaySummaryString(args, "subagentKind");
|
|
61
|
+
const model = readCursorReplaySummaryString(args, "model");
|
|
62
|
+
const agentId = readCursorReplaySummaryString(args, "agentId");
|
|
63
|
+
const metadataParts = [
|
|
64
|
+
subagentKind,
|
|
65
|
+
model,
|
|
66
|
+
agentId ? `ID: ${agentId}` : undefined,
|
|
67
|
+
args?.isBackground === true ? "backgrounded" : undefined,
|
|
68
|
+
].filter((part) => Boolean(part));
|
|
69
|
+
const subjectParts = [description].filter((part) => Boolean(part));
|
|
70
|
+
const subject = subjectParts.length > 0 ? subjectParts.join(" · ") : undefined;
|
|
71
|
+
const head = metadataParts.length > 0 ? [subject, ...metadataParts].filter(Boolean).join(" · ") : subject;
|
|
72
|
+
if (metadataParts.length > 0)
|
|
73
|
+
return head;
|
|
74
|
+
if (head && preview && preview !== description && preview !== subagentName)
|
|
75
|
+
return `${head}: ${preview}`;
|
|
76
|
+
return head ?? preview;
|
|
77
|
+
}
|
|
78
|
+
export function summarizeReplayMcp(args) {
|
|
79
|
+
const toolName = readCursorReplaySummaryString(args, "toolName") ?? "mcp";
|
|
80
|
+
const preview = readCursorReplaySummaryString(args, "preview");
|
|
81
|
+
return preview && preview !== toolName ? `${toolName} · ${preview}` : toolName;
|
|
82
|
+
}
|
|
83
|
+
export function summarizeReplayRecordScreen(args) {
|
|
84
|
+
const path = readCursorReplaySummaryString(args, "path");
|
|
85
|
+
const duration = formatReplayRecordingDurationMs(readCursorReplaySummaryNumber(args, "recordingDurationMs"));
|
|
86
|
+
if (path && duration)
|
|
87
|
+
return `${path} · ${duration}`;
|
|
88
|
+
return path ?? readCursorReplaySummaryString(args, "mode");
|
|
89
|
+
}
|
|
90
|
+
export function formatReplaySemSearchQuery(args) {
|
|
91
|
+
const query = readCursorReplaySummaryString(args, "query");
|
|
92
|
+
if (!query)
|
|
93
|
+
return undefined;
|
|
94
|
+
const targetDirectories = readCursorReplaySummaryStringArray(args, "targetDirectories");
|
|
95
|
+
const dirHint = targetDirectories.length > 0 ? ` (${targetDirectories.length} dir${targetDirectories.length === 1 ? "" : "s"})` : "";
|
|
96
|
+
return `${query}${dirHint}`;
|
|
97
|
+
}
|
|
98
|
+
export function summarizeReplayGenericActivity(args) {
|
|
99
|
+
return (readCursorReplaySummaryString(args, "path")
|
|
100
|
+
?? readCursorReplaySummaryString(args, "toolName")
|
|
101
|
+
?? formatReplaySemSearchQuery(args));
|
|
102
|
+
}
|
|
103
|
+
export function withActivitySummaryFallback(buildSummary) {
|
|
104
|
+
return (args) => readCursorReplaySummaryString(args, "activitySummary") ?? buildSummary(args);
|
|
105
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { asRecord, getBoolean, getNumber, getString } from "./cursor-record-utils.js";
|
|
2
|
+
import { isCursorReplayActivitySourceName } from "./cursor-replay-source-names.js";
|
|
3
|
+
/**
|
|
4
|
+
* Sentinel source tool name for activity cards whose SDK name is not a known registry entry.
|
|
5
|
+
* Display identity lives in `title` and replay args.
|
|
6
|
+
*/
|
|
7
|
+
export const CURSOR_REPLAY_UNREGISTERED_ACTIVITY_TOOL_NAME = "unregisteredActivity";
|
|
8
|
+
function readSourceToolName(record) {
|
|
9
|
+
const sourceToolName = getString(record, "sourceToolName");
|
|
10
|
+
return sourceToolName?.trim() ? sourceToolName.trim() : undefined;
|
|
11
|
+
}
|
|
12
|
+
function readVariant(record) {
|
|
13
|
+
const variant = getString(record, "variant");
|
|
14
|
+
return variant?.trim() ? variant.trim() : undefined;
|
|
15
|
+
}
|
|
16
|
+
function parseCursorReplayNativeEditDetails(record) {
|
|
17
|
+
return {
|
|
18
|
+
variant: "nativeEdit",
|
|
19
|
+
path: getString(record, "path"),
|
|
20
|
+
linesAdded: getNumber(record, "linesAdded"),
|
|
21
|
+
linesRemoved: getNumber(record, "linesRemoved"),
|
|
22
|
+
diffString: getString(record, "diffString"),
|
|
23
|
+
diff: getString(record, "diff"),
|
|
24
|
+
firstChangedLine: getNumber(record, "firstChangedLine"),
|
|
25
|
+
summary: getString(record, "summary"),
|
|
26
|
+
expandedText: getString(record, "expandedText"),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function parseCursorReplayNativeWriteDetails(record) {
|
|
30
|
+
return {
|
|
31
|
+
variant: "nativeWrite",
|
|
32
|
+
path: getString(record, "path"),
|
|
33
|
+
linesCreated: getNumber(record, "linesCreated"),
|
|
34
|
+
fileSize: getNumber(record, "fileSize"),
|
|
35
|
+
fileContentAfterWrite: getString(record, "fileContentAfterWrite"),
|
|
36
|
+
expandedText: getString(record, "expandedText"),
|
|
37
|
+
summary: getString(record, "summary"),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function parseCursorReplayGenerateImageDetails(record) {
|
|
41
|
+
const collapseDetailsByDefault = getBoolean(record, "collapseDetailsByDefault");
|
|
42
|
+
return {
|
|
43
|
+
variant: "generateImage",
|
|
44
|
+
imagePath: getString(record, "imagePath"),
|
|
45
|
+
imageDisplayPath: getString(record, "imageDisplayPath"),
|
|
46
|
+
imageMimeType: getString(record, "imageMimeType"),
|
|
47
|
+
summary: getString(record, "summary"),
|
|
48
|
+
expandedText: getString(record, "expandedText"),
|
|
49
|
+
...(collapseDetailsByDefault !== undefined ? { collapseDetailsByDefault } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function parseCursorReplayActivityDetails(record, sourceToolName, title) {
|
|
53
|
+
return {
|
|
54
|
+
variant: "activity",
|
|
55
|
+
sourceToolName,
|
|
56
|
+
title,
|
|
57
|
+
summary: getString(record, "summary"),
|
|
58
|
+
expandedText: getString(record, "expandedText"),
|
|
59
|
+
collapseDetailsByDefault: getBoolean(record, "collapseDetailsByDefault"),
|
|
60
|
+
path: getString(record, "path"),
|
|
61
|
+
fileSize: getNumber(record, "fileSize"),
|
|
62
|
+
diffString: getString(record, "diffString"),
|
|
63
|
+
diff: getString(record, "diff"),
|
|
64
|
+
linesAdded: getNumber(record, "linesAdded"),
|
|
65
|
+
linesRemoved: getNumber(record, "linesRemoved"),
|
|
66
|
+
fileContentAfterWrite: getString(record, "fileContentAfterWrite"),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function brandCursorReplayUnknownSourceToolName(sourceToolName) {
|
|
70
|
+
return sourceToolName;
|
|
71
|
+
}
|
|
72
|
+
function parseCursorReplayGenericFallbackDetails(record, sourceToolName) {
|
|
73
|
+
return {
|
|
74
|
+
variant: "genericFallback",
|
|
75
|
+
sourceToolName: brandCursorReplayUnknownSourceToolName(sourceToolName),
|
|
76
|
+
summary: getString(record, "summary"),
|
|
77
|
+
expandedText: getString(record, "expandedText"),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function isCursorReplayActivitySourceToolName(name) {
|
|
81
|
+
if (name === CURSOR_REPLAY_UNREGISTERED_ACTIVITY_TOOL_NAME)
|
|
82
|
+
return true;
|
|
83
|
+
if (name === "generateImage")
|
|
84
|
+
return false;
|
|
85
|
+
return isCursorReplayActivitySourceName(name);
|
|
86
|
+
}
|
|
87
|
+
function resolveParseActivitySourceToolName(sourceToolName) {
|
|
88
|
+
return isCursorReplayActivitySourceToolName(sourceToolName)
|
|
89
|
+
? sourceToolName
|
|
90
|
+
: CURSOR_REPLAY_UNREGISTERED_ACTIVITY_TOOL_NAME;
|
|
91
|
+
}
|
|
92
|
+
/** Maps incomplete or non-activity replay source names onto activity-card source tool names. */
|
|
93
|
+
export function resolveIncompleteReplayActivitySourceToolName(sourceToolName) {
|
|
94
|
+
if (sourceToolName === "generateImage")
|
|
95
|
+
return CURSOR_REPLAY_UNREGISTERED_ACTIVITY_TOOL_NAME;
|
|
96
|
+
return resolveParseActivitySourceToolName(sourceToolName);
|
|
97
|
+
}
|
|
98
|
+
function parseActivityVariantDetails(record) {
|
|
99
|
+
const title = getString(record, "title")?.trim();
|
|
100
|
+
if (!title)
|
|
101
|
+
return undefined;
|
|
102
|
+
return parseCursorReplayActivityDetails(record, resolveParseActivitySourceToolName(readSourceToolName(record) ?? CURSOR_REPLAY_UNREGISTERED_ACTIVITY_TOOL_NAME), title);
|
|
103
|
+
}
|
|
104
|
+
export function parseCursorReplayToolDetails(value) {
|
|
105
|
+
const record = asRecord(value);
|
|
106
|
+
if (!record)
|
|
107
|
+
return undefined;
|
|
108
|
+
switch (readVariant(record)) {
|
|
109
|
+
case "nativeEdit":
|
|
110
|
+
return parseCursorReplayNativeEditDetails(record);
|
|
111
|
+
case "nativeWrite":
|
|
112
|
+
return parseCursorReplayNativeWriteDetails(record);
|
|
113
|
+
case "generateImage":
|
|
114
|
+
return parseCursorReplayGenerateImageDetails(record);
|
|
115
|
+
case "activity":
|
|
116
|
+
return parseActivityVariantDetails(record);
|
|
117
|
+
case "genericFallback":
|
|
118
|
+
return parseCursorReplayGenericFallbackDetails(record, readSourceToolName(record) ?? "tool");
|
|
119
|
+
default:
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function assembleCursorReplayActivityDetails(sourceToolName, title, fields, contentText, isError, activitySummary) {
|
|
124
|
+
const summary = isError ? fields.summary : (fields.summary ?? activitySummary);
|
|
125
|
+
return {
|
|
126
|
+
variant: "activity",
|
|
127
|
+
sourceToolName,
|
|
128
|
+
title,
|
|
129
|
+
summary,
|
|
130
|
+
expandedText: fields.expandedText ?? contentText,
|
|
131
|
+
...(fields.collapseDetailsByDefault !== undefined ? { collapseDetailsByDefault: fields.collapseDetailsByDefault } : {}),
|
|
132
|
+
...(fields.path !== undefined ? { path: fields.path } : {}),
|
|
133
|
+
...(fields.fileSize !== undefined ? { fileSize: fields.fileSize } : {}),
|
|
134
|
+
...(fields.diffString !== undefined ? { diffString: fields.diffString } : {}),
|
|
135
|
+
...(fields.diff !== undefined ? { diff: fields.diff } : {}),
|
|
136
|
+
...(fields.linesAdded !== undefined ? { linesAdded: fields.linesAdded } : {}),
|
|
137
|
+
...(fields.linesRemoved !== undefined ? { linesRemoved: fields.linesRemoved } : {}),
|
|
138
|
+
...(fields.fileContentAfterWrite !== undefined ? { fileContentAfterWrite: fields.fileContentAfterWrite } : {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export const CURSOR_REPLAY_GENERATE_IMAGE_RESULT_TITLE = "Cursor image generation";
|
|
142
|
+
export function assembleCursorReplayGenerateImageDetails(fields, contentText, isError, activitySummary) {
|
|
143
|
+
const summary = isError ? fields.summary : (fields.summary ?? activitySummary);
|
|
144
|
+
return {
|
|
145
|
+
variant: "generateImage",
|
|
146
|
+
imagePath: fields.imagePath,
|
|
147
|
+
imageDisplayPath: fields.imageDisplayPath,
|
|
148
|
+
imageMimeType: fields.imageMimeType,
|
|
149
|
+
summary,
|
|
150
|
+
expandedText: fields.expandedText ?? contentText,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export function isCursorReplayNativeEditDetails(details) {
|
|
154
|
+
return details.variant === "nativeEdit";
|
|
155
|
+
}
|
|
156
|
+
export function isCursorReplayNativeWriteDetails(details) {
|
|
157
|
+
return details.variant === "nativeWrite";
|
|
158
|
+
}
|
|
159
|
+
export function isCursorReplayGenerateImageDetails(details) {
|
|
160
|
+
return details.variant === "generateImage";
|
|
161
|
+
}
|
|
162
|
+
export function isCursorReplayActivityDetails(details) {
|
|
163
|
+
return details.variant === "activity";
|
|
164
|
+
}
|
|
165
|
+
export function isCursorReplayGenericFallbackDetails(details) {
|
|
166
|
+
return details.variant === "genericFallback";
|
|
167
|
+
}
|